Spaces:
Sleeping
Sleeping
| import {DOMParser, XMLSerializer} from "xmldom"; | |
| import * as domUtils from "../inc/domUtils"; | |
| const isWordDirection = direction => { | |
| const directionTypes = domUtils.childrenWithTag(direction, "direction-type"); | |
| for (const dt of directionTypes) { | |
| if (domUtils.hasChildrenWithTag(dt, "words")) | |
| return true; | |
| } | |
| return false; | |
| }; | |
| const moveWordDirection = measure => { | |
| //console.log("measure:", measure); | |
| const words = []; | |
| for (let i = 0; i < measure.childNodes.length; ++i) { | |
| const child = measure.childNodes[i]; | |
| switch (child.tagName) { | |
| case "direction": | |
| if (isWordDirection(child)) { | |
| words.push(child); | |
| measure.removeChild(child); | |
| } | |
| break; | |
| case "note": | |
| const next = measure.childNodes[i + 1]; | |
| words.forEach(word => { | |
| if (next) | |
| measure.insertBefore(word, next); | |
| else | |
| measure.appendChild(word); | |
| }); | |
| return; | |
| } | |
| } | |
| }; | |
| const preprocessXml = (xml: string, { | |
| removeMeasureImplicit = true, | |
| replaceEncoding = true, | |
| removeNullDynamics = true, | |
| fixHeadMarkup = true, | |
| fixBackSlashes = true, | |
| roundTempo = true, | |
| escapedWordsDoubleQuotation = true, | |
| removeTrivialRests = true, | |
| removeBadMetronome = true, | |
| removeInvalidHarmonies = true, | |
| removeAllHarmonies = false, | |
| fixChordVoice = true, | |
| fixBarlines = true, | |
| removeInvalidClef = true, | |
| } = {}): string => { | |
| if (!removeMeasureImplicit && !replaceEncoding && !removeNullDynamics && !fixHeadMarkup && !fixBackSlashes && !roundTempo | |
| && !escapedWordsDoubleQuotation && !removeTrivialRests && !removeBadMetronome && !removeInvalidHarmonies && !removeAllHarmonies | |
| && !fixChordVoice && !fixBarlines) | |
| return xml; | |
| // @ts-ignore | |
| const dom = new DOMParser().parseFromString(xml, "text/xml"); | |
| if (replaceEncoding) { | |
| const headNode = Array.prototype.find.call(dom.childNodes, node => node.tagName === "xml"); | |
| if (headNode) | |
| headNode.data = headNode.data.replace(/UTF-16/, "UTF-8"); | |
| } | |
| const needTraverse = removeMeasureImplicit || removeNullDynamics || fixHeadMarkup || fixBackSlashes || roundTempo | |
| || escapedWordsDoubleQuotation || removeTrivialRests || removeBadMetronome || removeInvalidHarmonies || removeAllHarmonies | |
| || fixChordVoice || fixBarlines; | |
| if (needTraverse) { | |
| domUtils.traverse(dom, node => { | |
| if (removeMeasureImplicit) { | |
| if (node.tagName === "measure") | |
| node.removeAttribute("implicit"); | |
| } | |
| if (removeNullDynamics) { | |
| if (node.tagName === "other-dynamics") { | |
| const content = node.textContent; | |
| if (!/\w/.test(content)) | |
| node.parentNode.removeChild(node); | |
| } | |
| } | |
| if (fixHeadMarkup) { | |
| if (node.tagName === "measure") { | |
| //console.log("measure:", node); | |
| moveWordDirection(node); | |
| } | |
| } | |
| if (fixBackSlashes) { | |
| if (["words", "credit-words", "text"].includes(node.tagName)) { | |
| if (/^\\+/.test(node.textContent) || /\\+$/.test(node.textContent)) { | |
| console.debug("replaced invalid text:", node.textContent); | |
| node.textContent = node.textContent.replace(/^\\+/, "").replace(/\\+$/, ""); | |
| } | |
| } | |
| } | |
| if (roundTempo) { | |
| if (node.tagName === "sound") { | |
| const tempo = Number(node.getAttribute("tempo")); | |
| if (Number.isFinite(tempo) && Math.floor(tempo) !== tempo) | |
| node.setAttribute("tempo", Math.round(tempo).toFixed(0)); | |
| } | |
| } | |
| if (escapedWordsDoubleQuotation) { | |
| if (["words", "credit-words", "text"].includes(node.tagName)) { | |
| if (node.textContent && /"/.test(node.textContent)) | |
| node.textContent = node.textContent.replace(/"/g, "'"); | |
| } | |
| } | |
| if (removeTrivialRests) { | |
| if (node.tagName === "note") { | |
| if (domUtils.hasChildrenWithTag(node, "rest") && !domUtils.hasChildrenWithTag(node, "type")) { | |
| // append an empty tag: <type></type> | |
| const type = dom.createElement("type"); | |
| node.appendChild(type); | |
| } | |
| } | |
| } | |
| if (removeBadMetronome) { | |
| if (node.tagName === "metronome") { | |
| if (!domUtils.hasChildrenWithTag(node, "per-minute")) { | |
| console.debug("metronome without 'per-minute' removed:", node.toString()); | |
| node.parentNode.removeChild(node); | |
| } | |
| } | |
| } | |
| if (removeInvalidHarmonies || removeAllHarmonies) { | |
| if (node.tagName === "harmony") { | |
| if (removeAllHarmonies) | |
| node.parentNode.removeChild(node); | |
| else { | |
| let next = node.nextSibling; | |
| while (next && !next.tagName) | |
| next = next.nextSibling; | |
| //console.log("sibling:", next); | |
| if (!next || next.tagName !== "note") { | |
| node.parentNode.removeChild(node); | |
| console.debug("invalid harmony removed:", next && next.tagName); | |
| } | |
| } | |
| } | |
| } | |
| if (fixChordVoice) { | |
| if (node.tagName === "note" && domUtils.hasChildrenWithTag(node, "chord") && !domUtils.hasChildrenWithTag(node, "voice")) { | |
| //console.log("bad note:", node); | |
| const previousNote = domUtils.findPreviousSibling(node, "note"); | |
| if (previousNote) { | |
| const voice = domUtils.childrenWithTag(previousNote, "voice")[0]; | |
| //console.log("chord voice:", node, voice); | |
| if (voice) | |
| node.appendChild(voice.cloneNode(true)); | |
| } | |
| } | |
| } | |
| if (fixBarlines) { | |
| if (node.tagName === "barline") { | |
| if ((node.getAttribute("location") === "right" || domUtils.hasChildrenWithTag(node, "bar-style")) | |
| && domUtils.findNextSibling(node, "backup")) { | |
| // move the barline to the end of this measure | |
| node.parentNode.removeChild(node); | |
| node.parentNode.appendChild(node); | |
| } | |
| } | |
| } | |
| if(removeInvalidClef) { | |
| if (node.tagName === "attributes") { | |
| const clef = domUtils.childrenWithTag(node, "clef")[0]; | |
| if (clef) { | |
| const n = Number(clef.getAttribute("number")); | |
| if (!domUtils.findPreviousSibling(node, "backup") && n > 1) { | |
| node.parentNode.removeChild(node); | |
| console.debug("invalid clef removed:", n); | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| //console.log("dom:", dom); | |
| // @ts-ignore | |
| return new XMLSerializer().serializeToString(dom); | |
| }; | |
| export { | |
| preprocessXml, | |
| }; | |