diff --git a/src/formatting/formatters/classic-xml-formatter.ts b/src/formatting/formatters/classic-xml-formatter.ts index 12a10c9..d5bc8d0 100644 --- a/src/formatting/formatters/classic-xml-formatter.ts +++ b/src/formatting/formatters/classic-xml-formatter.ts @@ -66,7 +66,7 @@ export class ClassicXmlFormatter implements XmlFormatter { } minifyXml(xml: string, options: XmlFormattingOptions): string { - xml = this._stripLineBreaks(options, xml); // all line breaks outside of CDATA elements + xml = this._stripLineBreaks(options, xml); // all line breaks outside of CDATA elements and comments xml = (options.removeCommentsOnMinify) ? xml.replace(/\/g, "") : xml; xml = xml.replace(/>\s{0,}<"); // insignificant whitespace between tags xml = xml.replace(/"\s+(?=[^\s]+=)/g, "\" "); // spaces between attributes @@ -87,28 +87,62 @@ export class ClassicXmlFormatter implements XmlFormatter { return `${options.newLine}${indentPattern.repeat(level)}${trailingValue}`; } + /** + * Removes line breaks outside of CDATA, comment, and xml:space="preserve" blocks. + */ private _stripLineBreaks(options: XmlFormattingOptions, xml: string): string { let output = ""; const inTag = false; const inTagName = false; - let inCdata = false; + let inCdataOrComment = false; const inAttribute = false; + let preserveSpace = false; + let level = 0; + let levelpreserveSpaceActivated = 0; + for (let i = 0; i < xml.length; i++) { const char: string = xml.charAt(i); const prev: string = xml.charAt(i - 1); const next: string = xml.charAt(i + 1); + // CDATA and comments if (char === "!" && (xml.substr(i, 8) === "![CDATA[" || xml.substr(i, 3) === "!--")) { - inCdata = true; - } else if (char === "]" && (xml.substr(i, 3) === "]]>")) { - inCdata = false; - } else if (char === "-" && (xml.substr(i, 3) === "-->")) { - inCdata = false; - } else if (char.search(/[\r\n]/g) > -1 && !inCdata) { + inCdataOrComment = true; + } + + else if (char === "]" && (xml.substr(i, 3) === "]]>")) { + inCdataOrComment = false; + } + + else if (char === "-" && (xml.substr(i, 3) === "-->")) { + inCdataOrComment = false; + } + + // xml:space="preserve" + if (char === ">" && prev !== "/") { + level++; + } + + else if (!inCdataOrComment && char === "/" && (prev === "<" || next === ">")) { + level--; + } + + if (char === "x" && (xml.substr(i, 20).toLowerCase() === `xml:space="preserve"`)) { + preserveSpace = true; + levelpreserveSpaceActivated = level; + } + + else if (!inCdataOrComment && preserveSpace && (char === "/" && (prev === "<" || next === ">")) && (level === levelpreserveSpaceActivated)) { + preserveSpace = false; + } + + if (char.search(/[\r\n]/g) > -1 && !inCdataOrComment && !preserveSpace) { if (/\r/.test(char) && /\S|\r|\n/.test(prev) && /\S|\r|\n/.test(xml.charAt(i + options.newLine.length))) { output += char; - } else if (/\n/.test(char) && /\S|\r|\n/.test(xml.charAt(i - options.newLine.length)) && /\S|\r|\n/.test(next)) { + } + + else if (/\n/.test(char) && /\S|\r|\n/.test(xml.charAt(i - options.newLine.length)) && /\S|\r|\n/.test(next)) { output += char; } diff --git a/src/test/test-data/issue-262.minified.xml b/src/test/test-data/issue-262.minified.xml index beed2a0..57f223c 100644 --- a/src/test/test-data/issue-262.minified.xml +++ b/src/test/test-data/issue-262.minified.xml @@ -4,10 +4,7 @@ 4.1. 2. 3. -4.1. - 2. - 3. - 4.1. +4.1. 2. 3. 4.1. 2. 3. 4. \ No newline at end of file