// import { isSpecialMark, isSpeedKeyword, isGradientWords, GRADIENT_SPEED_RESET_TAG } from "./speed-tag" // import { isSpecialMark, isSpeedKeyword, isGradientWords, GRADIENT_SPEED_RESET_TAG } from "./speed-tag" export class StringUtil { public static StringContainsSeparatedWord(str: string, wordRegExString: string, ignoreCase = false): boolean { const regExp = new RegExp("( |^)" + wordRegExString + "([ .]|$)", ignoreCase ? "i" : undefined) return regExp.test(str) } } 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 + ` ${divisions * beats} 1 whole ` } } 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") || [] // @typescript-eslint/no-unused-vars 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) } }) } // 最后一个小节的结束线元素不在最后 调整 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 }