|
@@ -0,0 +1,352 @@
|
|
|
+import { StringUtil } from "./StringUtil";
|
|
|
+import { isSpecialMark, isSpeedKeyword, isGradientWords, GRADIENT_SPEED_RESET_TAG } from "./speed-tag";
|
|
|
+
|
|
|
+export const formatXML = (xml: string): string => {
|
|
|
+ if (!xml) return "";
|
|
|
+ const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
|
|
|
+ const measures: any = xmlParse.getElementsByTagName("measure");
|
|
|
+ // const repeats: any = Array.from(xmlParse.querySelectorAll('repeat'))
|
|
|
+ // 处理重复小节信息
|
|
|
+ // let speed = -1
|
|
|
+ let beats = -1;
|
|
|
+ let beatType = -1;
|
|
|
+ // 小节中如果没有节点默认为休止符
|
|
|
+ for (const measure of measures) {
|
|
|
+ if (beats === -1 && measure.getElementsByTagName("beats").length) {
|
|
|
+ beats = parseInt(measure.getElementsByTagName("beats")[0].textContent || "4");
|
|
|
+ }
|
|
|
+ if (beatType === -1 && measure.getElementsByTagName("beat-type").length) {
|
|
|
+ beatType = parseInt(measure.getElementsByTagName("beat-type")[0].textContent || "4");
|
|
|
+ }
|
|
|
+ // if (speed === -1 && measure.getElementsByTagName('per-minute').length) {
|
|
|
+ // speed = parseInt(measure.getElementsByTagName('per-minute')[0].textContent || this.firstLib?.speed)
|
|
|
+ // }
|
|
|
+ const divisions = parseInt(measure.getElementsByTagName("divisions")[0]?.textContent || "256");
|
|
|
+ if (measure.getElementsByTagName("note").length === 0) {
|
|
|
+ const forwardTimeElement = measure.getElementsByTagName("forward")[0]?.getElementsByTagName("duration")[0];
|
|
|
+ if (forwardTimeElement) {
|
|
|
+ forwardTimeElement.textContent = "0";
|
|
|
+ }
|
|
|
+ measure.innerHTML =
|
|
|
+ measure.innerHTML +
|
|
|
+ `
|
|
|
+ <note>
|
|
|
+ <rest measure="yes"/>
|
|
|
+ <duration>${divisions * beats}</duration>
|
|
|
+ <voice>1</voice>
|
|
|
+ <type>whole</type>
|
|
|
+ </note>`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return new XMLSerializer().serializeToString(xmlParse);
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+export const onlyVisible = (xml: string, partIndex: number): string => {
|
|
|
+ if (!xml) return "";
|
|
|
+ const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
|
|
|
+ const partList = xmlParse.getElementsByTagName("part-list")?.[0]?.getElementsByTagName("score-part") || [];
|
|
|
+ const partListNames = Array.from(partList).map((item) => item.getElementsByTagName("part-name")?.[0].textContent || "");
|
|
|
+ const parts: any = xmlParse.getElementsByTagName("part");
|
|
|
+ // const firstTimeInfo = parts[0]?.getElementsByTagName('metronome')[0]?.parentElement?.parentElement?.cloneNode(true)
|
|
|
+ const part: any = parts[0]
|
|
|
+ const firstMeasures = [...part.getElementsByTagName("measure")];
|
|
|
+ const metronomes = [...part.getElementsByTagName("metronome")];
|
|
|
+ const words = [...part.getElementsByTagName("words")];
|
|
|
+ const codas = [...part.getElementsByTagName("coda")];
|
|
|
+ const rehearsals = [...part.getElementsByTagName("rehearsal")];
|
|
|
+
|
|
|
+ /** 第一分谱如果是约定的配置分谱则跳过 */
|
|
|
+ if (partListNames[0]?.toLocaleUpperCase?.() === "COMMON") {
|
|
|
+ partIndex++;
|
|
|
+ partListNames.shift();
|
|
|
+ }
|
|
|
+ const visiblePartInfo = partList[partIndex];
|
|
|
+ // console.log(visiblePartInfo, partIndex)
|
|
|
+ if (visiblePartInfo) {
|
|
|
+ const id = visiblePartInfo.getAttribute("id");
|
|
|
+ Array.from(parts).forEach((part: any) => {
|
|
|
+ if (part && part.getAttribute("id") !== id) {
|
|
|
+ part.parentNode?.removeChild(part);
|
|
|
+ // 不等于第一行才添加避免重复添加
|
|
|
+ } else if (part && part.getAttribute("id") !== "P1") {
|
|
|
+ // 速度标记仅保留最后一个
|
|
|
+ const metronomeData: {
|
|
|
+ [key in string]: Element;
|
|
|
+ } = {};
|
|
|
+ for (let i = 0; i < metronomes.length; i++) {
|
|
|
+ const metronome = metronomes[i];
|
|
|
+ const metronomeContainer = metronome.parentElement?.parentElement?.parentElement;
|
|
|
+ if (metronomeContainer) {
|
|
|
+ const index = firstMeasures.indexOf(metronomeContainer);
|
|
|
+ metronomeData[index] = metronome;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Object.values(metronomeData).forEach((metronome) => {
|
|
|
+ const metronomeContainer: any = metronome.parentElement?.parentElement;
|
|
|
+ const parentMeasure: any = metronomeContainer?.parentElement;
|
|
|
+ const measureMetronomes = [...(parentMeasure?.childNodes || [])];
|
|
|
+ const metronomesIndex = metronomeContainer ? measureMetronomes.indexOf(metronomeContainer) : -1;
|
|
|
+ // console.log(parentMeasure)
|
|
|
+ if (parentMeasure && metronomesIndex > -1) {
|
|
|
+ const index = firstMeasures.indexOf(parentMeasure);
|
|
|
+ const activeMeasure = part.getElementsByTagName("measure")[index];
|
|
|
+ setElementNoteBefore(metronomeContainer, parentMeasure, activeMeasure);
|
|
|
+ // console.log(measureMetronomes, metronomesIndex, activeMeasure?.childNodes, activeMeasure?.childNodes[metronomesIndex])
|
|
|
+ // activeMeasure?.insertBefore(metronomeContainer.cloneNode(true), activeMeasure?.childNodes[metronomesIndex])
|
|
|
+ // // part.getElementsByTagName('measure')[index]?.appendChild(metronomeContainer.cloneNode(true))
|
|
|
+ // // console.log(index, parentMeasure, firstMeasures.indexOf(parentMeasure))
|
|
|
+ }
|
|
|
+ });
|
|
|
+ /** word比较特殊需要精确到note位置 */
|
|
|
+ // words.forEach((word) => {
|
|
|
+ // let text = word.textContent || "";
|
|
|
+ // text = ["cresc."].includes(text) ? "" : text;
|
|
|
+ // if ((isSpecialMark(text) || isSpeedKeyword(text) || isGradientWords(text) || isRepeatWord(text) || GRADIENT_SPEED_RESET_TAG) && text) {
|
|
|
+ // const wordContainer = word.parentElement?.parentElement;
|
|
|
+ // const parentMeasure = wordContainer?.parentElement;
|
|
|
+ // const measureWords = [...(parentMeasure?.childNodes || [])];
|
|
|
+ // const wordIndex = wordContainer ? measureWords.indexOf(wordContainer) : -1;
|
|
|
+ // if (wordContainer && parentMeasure && wordIndex > -1) {
|
|
|
+ // const index = firstMeasures.indexOf(parentMeasure);
|
|
|
+ // const activeMeasure = part.getElementsByTagName("measure")[index];
|
|
|
+ // // 找当前小节是否包含word标签
|
|
|
+ // const _words: any = Array.from(activeMeasure?.getElementsByTagName("words") || []);
|
|
|
+ // // 遍历word标签,检查是否和第一小节重复,如果有重复则不平移word
|
|
|
+ // const total = _words.reduce((total: any, _word) => {
|
|
|
+ // if (_word.textContent?.includes(text)) {
|
|
|
+ // total++;
|
|
|
+ // }
|
|
|
+ // return total;
|
|
|
+ // }, 0);
|
|
|
+ // if (total === 0) {
|
|
|
+ // setElementNoteBefore(wordContainer, parentMeasure, activeMeasure);
|
|
|
+
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // });
|
|
|
+ /** word比较特殊需要精确到note位置 */
|
|
|
+ // codas.forEach((coda) => {
|
|
|
+ // const wordContainer = coda.parentElement?.parentElement;
|
|
|
+ // const parentMeasure = wordContainer?.parentElement;
|
|
|
+ // const measureWords = [...(parentMeasure?.childNodes || [])];
|
|
|
+ // const wordIndex = wordContainer ? measureWords.indexOf(wordContainer) : -1;
|
|
|
+ // if (wordContainer && parentMeasure && wordIndex > -1) {
|
|
|
+ // const index = firstMeasures.indexOf(parentMeasure);
|
|
|
+ // const activeMeasure = part.getElementsByTagName("measure")[index];
|
|
|
+
|
|
|
+ // setElementNoteBefore(wordContainer, parentMeasure, activeMeasure);
|
|
|
+
|
|
|
+ // }
|
|
|
+ // });
|
|
|
+ // rehearsals.forEach((rehearsal) => {
|
|
|
+ // const container = rehearsal.parentElement?.parentElement;
|
|
|
+ // const parentMeasure = container?.parentElement;
|
|
|
+ // // console.log(rehearsal)
|
|
|
+ // if (parentMeasure) {
|
|
|
+ // const index = firstMeasures.indexOf(parentMeasure);
|
|
|
+ // part.getElementsByTagName("measure")[index]?.appendChild(container.cloneNode(true));
|
|
|
+ // // console.log(index, parentMeasure, firstMeasures.indexOf(parentMeasure))
|
|
|
+ // }
|
|
|
+ // });
|
|
|
+ } else {
|
|
|
+ // words.forEach((word) => {
|
|
|
+ // const text = word.textContent || "";
|
|
|
+ // if (isSpeedKeyword(text) && text) {
|
|
|
+ // const wordContainer = word.parentElement?.parentElement?.parentElement;
|
|
|
+ // if (wordContainer && wordContainer.firstElementChild && wordContainer.firstElementChild !== word) {
|
|
|
+ // const wordParent = word.parentElement?.parentElement;
|
|
|
+ // const fisrt = wordContainer.firstElementChild;
|
|
|
+ // wordContainer.insertBefore(wordParent, fisrt);
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 最后一个小节的结束线元素不在最后 调整
|
|
|
+ if (part && part.getAttribute("id") === id) {
|
|
|
+ const barlines = part.getElementsByTagName("barline");
|
|
|
+ const lastParent = barlines[barlines.length - 1]?.parentElement;
|
|
|
+ if (lastParent?.lastElementChild?.tagName !== "barline") {
|
|
|
+ const children: any = lastParent?.children || [];
|
|
|
+ for (const el of children) {
|
|
|
+ if (el.tagName === "barline") {
|
|
|
+ // 将结束线元素放到最后
|
|
|
+ lastParent?.appendChild(el);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ Array.from(partList).forEach((part) => {
|
|
|
+ if (part && part.getAttribute("id") !== id) {
|
|
|
+ part.parentNode?.removeChild(part);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ // 处理装饰音问题
|
|
|
+ const notes = xmlParse.getElementsByTagName("note");
|
|
|
+ const getNextvNoteDuration = (i: number) => {
|
|
|
+ let nextNote = notes[i + 1];
|
|
|
+ // 可能存在多个装饰音问题,取下一个非装饰音时值
|
|
|
+ for (let index = i; index < notes.length; index++) {
|
|
|
+ const note = notes[index];
|
|
|
+ if (!note.getElementsByTagName("grace")?.length) {
|
|
|
+ nextNote = note;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const nextNoteDuration = nextNote?.getElementsByTagName("duration")[0];
|
|
|
+ return nextNoteDuration;
|
|
|
+ };
|
|
|
+ Array.from(notes).forEach((note, i) => {
|
|
|
+ const graces = note.getElementsByTagName("grace");
|
|
|
+ if (graces && graces.length) {
|
|
|
+ // if (i !== 0) {
|
|
|
+ // note.appendChild(getNextvNoteDuration(i)?.cloneNode(true))
|
|
|
+ // }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ // console.log(xmlParse)
|
|
|
+ return new XMLSerializer().serializeToString(appoggianceFormate(xmlParse));
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+// 倚音后连音线
|
|
|
+export const appoggianceFormate = (xmlParse: Document): Document => {
|
|
|
+ if (!xmlParse) return xmlParse;
|
|
|
+ const graces: any = xmlParse.querySelectorAll("grace");
|
|
|
+ if (!graces.length) return xmlParse;
|
|
|
+ const getNextElement = (el: HTMLElement): HTMLElement => {
|
|
|
+ if (el.querySelector("grace")) {
|
|
|
+ return getNextElement(el?.nextElementSibling as HTMLElement);
|
|
|
+ }
|
|
|
+ return el;
|
|
|
+ };
|
|
|
+ for (const grace of graces) {
|
|
|
+ const notations = grace.parentElement?.querySelector("notations");
|
|
|
+ if (notations && notations.querySelectorAll("slur").length > 1) {
|
|
|
+ const nextEle: Element = getNextElement(grace.parentElement?.nextElementSibling as HTMLElement);
|
|
|
+ if (nextEle && nextEle.querySelectorAll("slur").length > 0) {
|
|
|
+ const slurNumber = Array.from(nextEle.querySelector("notations")?.children || []).map((el: Element) => {
|
|
|
+ return el.getAttribute("number");
|
|
|
+ });
|
|
|
+ const slurs = notations.querySelectorAll("slur");
|
|
|
+ for (const nota of slurs) {
|
|
|
+ if (!slurNumber.includes(nota.getAttribute("number"))) {
|
|
|
+ nextEle.querySelector("notations")?.appendChild(nota);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return xmlParse;
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * 添加第一分谱信息至当前分谱
|
|
|
+ * @param ele 需要插入的元素
|
|
|
+ * @param fitstParent 合奏谱第一个分谱
|
|
|
+ * @param parent 需要添加的分谱
|
|
|
+ */
|
|
|
+const setElementNoteBefore = (ele: Element, fitstParent: Element, parent?: Element | null) => {
|
|
|
+ let noteIndex = 0;
|
|
|
+ if (!fitstParent) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ for (let index = 0; index < fitstParent.childNodes.length; index++) {
|
|
|
+ const element = fitstParent.childNodes[index];
|
|
|
+ if (element.nodeName === "note") {
|
|
|
+ noteIndex++;
|
|
|
+ }
|
|
|
+ if (element === ele) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (noteIndex === 0 && parent) {
|
|
|
+ parent.insertBefore(ele, parent.childNodes[0]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (parent && parent.childNodes.length > 0) {
|
|
|
+ let noteIndex2 = 0;
|
|
|
+ const notes = Array.from(parent.childNodes).filter((child) => child.nodeName === "note");
|
|
|
+ const lastNote = notes[notes.length - 1];
|
|
|
+ if (noteIndex >= notes.length && lastNote) {
|
|
|
+ parent.insertBefore(ele, parent.childNodes[Array.from(parent.childNodes).indexOf(lastNote)]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ for (let index = 0; index < notes.length; index++) {
|
|
|
+ const element = notes[index];
|
|
|
+ if (element.nodeName === "note") {
|
|
|
+ noteIndex2 = noteIndex2 + 1;
|
|
|
+ if (noteIndex2 === noteIndex) {
|
|
|
+ parent.insertBefore(ele, element);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // console.log(noteIndex, parent)
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * 检查传入文字是否为重复关键词
|
|
|
+ * @param text 总谱xml
|
|
|
+ * @returns 是否是重复关键词
|
|
|
+ */
|
|
|
+export const isRepeatWord = (text: string): boolean => {
|
|
|
+ if (text) {
|
|
|
+ const innerText = text.toLocaleLowerCase();
|
|
|
+ const dsRegEx = "d\\s?\\.s\\.";
|
|
|
+ const dcRegEx = "d\\.\\s?c\\.";
|
|
|
+
|
|
|
+ return (
|
|
|
+ innerText === "@" ||
|
|
|
+ StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + " al fine", true) ||
|
|
|
+ StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + " al coda", true) ||
|
|
|
+ StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + " al fine", true) ||
|
|
|
+ StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + " al coda", true) ||
|
|
|
+ StringUtil.StringContainsSeparatedWord(innerText, dcRegEx) ||
|
|
|
+ StringUtil.StringContainsSeparatedWord(innerText, "da\\s?capo", true) ||
|
|
|
+ StringUtil.StringContainsSeparatedWord(innerText, dsRegEx, true) ||
|
|
|
+ StringUtil.StringContainsSeparatedWord(innerText, "dal\\s?segno", true) ||
|
|
|
+ StringUtil.StringContainsSeparatedWord(innerText, "al\\s?coda", true) ||
|
|
|
+ StringUtil.StringContainsSeparatedWord(innerText, "to\\s?coda", true) ||
|
|
|
+ StringUtil.StringContainsSeparatedWord(innerText, "a (la )?coda", true) ||
|
|
|
+ StringUtil.StringContainsSeparatedWord(innerText, "fine", true) ||
|
|
|
+ StringUtil.StringContainsSeparatedWord(innerText, "coda", true) ||
|
|
|
+ StringUtil.StringContainsSeparatedWord(innerText, "segno", true)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+};
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/** 从xml中获取自定义信息,并删除多余的字符串 */
|
|
|
+export const getCustomInfo = (xml: string): any => {
|
|
|
+ const data = {
|
|
|
+ showSpeed: true,
|
|
|
+ parsedXML: xml,
|
|
|
+ };
|
|
|
+ const xmlParse = new DOMParser().parseFromString(xml, "text/xml");
|
|
|
+ const words: any = xmlParse.getElementsByTagName("words");
|
|
|
+ for (const word of words) {
|
|
|
+ if (word && word.textContent?.trim() === "隐藏速度") {
|
|
|
+ data.showSpeed = false;
|
|
|
+ word.textContent = "";
|
|
|
+ }
|
|
|
+ if (word && word.textContent?.trim() === "@") {
|
|
|
+ word.textContent = "segno";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ data.parsedXML = new XMLSerializer().serializeToString(xmlParse);
|
|
|
+ return data;
|
|
|
+};
|