Merge pull request #312 from DotJoshJohnson/bug/262-preserve-whitespace
Fix Issue #262
This commit is contained in:
commit
1015e19253
6 changed files with 636 additions and 545 deletions
1070
package-lock.json
generated
1070
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -277,4 +277,4 @@
|
||||||
"xpath": "0.0.27",
|
"xpath": "0.0.27",
|
||||||
"xqlint": "^0.4.1"
|
"xqlint": "^0.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ export class ClassicXmlFormatter implements XmlFormatter {
|
||||||
}
|
}
|
||||||
|
|
||||||
minifyXml(xml: string, options: XmlFormattingOptions): string {
|
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(/\<![ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t]*)\>/g, "") : xml;
|
xml = (options.removeCommentsOnMinify) ? xml.replace(/\<![ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t]*)\>/g, "") : xml;
|
||||||
xml = xml.replace(/>\s{0,}</g, "><"); // insignificant whitespace between tags
|
xml = xml.replace(/>\s{0,}</g, "><"); // insignificant whitespace between tags
|
||||||
xml = xml.replace(/"\s+(?=[^\s]+=)/g, "\" "); // spaces between attributes
|
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}`;
|
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 {
|
private _stripLineBreaks(options: XmlFormattingOptions, xml: string): string {
|
||||||
let output = "";
|
let output = "";
|
||||||
const inTag = false;
|
const inTag = false;
|
||||||
const inTagName = false;
|
const inTagName = false;
|
||||||
let inCdata = false;
|
let inCdataOrComment = false;
|
||||||
const inAttribute = false;
|
const inAttribute = false;
|
||||||
|
|
||||||
|
let preserveSpace = false;
|
||||||
|
let level = 0;
|
||||||
|
let levelpreserveSpaceActivated = 0;
|
||||||
|
|
||||||
for (let i = 0; i < xml.length; i++) {
|
for (let i = 0; i < xml.length; i++) {
|
||||||
const char: string = xml.charAt(i);
|
const char: string = xml.charAt(i);
|
||||||
const prev: string = xml.charAt(i - 1);
|
const prev: string = xml.charAt(i - 1);
|
||||||
const next: 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) === "!--")) {
|
if (char === "!" && (xml.substr(i, 8) === "![CDATA[" || xml.substr(i, 3) === "!--")) {
|
||||||
inCdata = true;
|
inCdataOrComment = true;
|
||||||
} else if (char === "]" && (xml.substr(i, 3) === "]]>")) {
|
}
|
||||||
inCdata = false;
|
|
||||||
} else if (char === "-" && (xml.substr(i, 3) === "-->")) {
|
else if (char === "]" && (xml.substr(i, 3) === "]]>")) {
|
||||||
inCdata = false;
|
inCdataOrComment = false;
|
||||||
} else if (char.search(/[\r\n]/g) > -1 && !inCdata) {
|
}
|
||||||
|
|
||||||
|
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))) {
|
if (/\r/.test(char) && /\S|\r|\n/.test(prev) && /\S|\r|\n/.test(xml.charAt(i + options.newLine.length))) {
|
||||||
output += char;
|
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;
|
output += char;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,26 @@ describe("V2XmlFormatter", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#minifyXml(xml, options)", () => {
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
editorOptions: {
|
||||||
|
insertSpaces: true,
|
||||||
|
tabSize: 4
|
||||||
|
},
|
||||||
|
enforcePrettySelfClosingTagOnFormat: false,
|
||||||
|
newLine: "\r\n",
|
||||||
|
removeCommentsOnMinify: false,
|
||||||
|
splitAttributesOnFormat: false,
|
||||||
|
splitXmlnsOnFormat: true
|
||||||
|
};
|
||||||
|
|
||||||
|
it("should preserve whitespace on minify if xml:space is set to 'preserve-whitespace'", () => {
|
||||||
|
testMinifier(xmlFormatter, options, "issue-262");
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function testFormatter(xmlFormatter: XmlFormatter, options: XmlFormattingOptions, fileLabel: string): void {
|
function testFormatter(xmlFormatter: XmlFormatter, options: XmlFormattingOptions, fileLabel: string): void {
|
||||||
|
@ -111,3 +131,12 @@ function testFormatter(xmlFormatter: XmlFormatter, options: XmlFormattingOptions
|
||||||
|
|
||||||
assert.equal(actualFormattedXml, expectedFormattedXml, "Actual formatted XML does not match expected formatted XML.");
|
assert.equal(actualFormattedXml, expectedFormattedXml, "Actual formatted XML does not match expected formatted XML.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testMinifier(xmlFormatter: XmlFormatter, options: XmlFormattingOptions, fileLabel: string): void {
|
||||||
|
const expectedMinifiedXml = TestDataLoader.load(`${fileLabel}.minified.xml`);
|
||||||
|
const unminifiedXml = TestDataLoader.load(`${fileLabel}.unminified.xml`);
|
||||||
|
|
||||||
|
const actualMinifiedXml = xmlFormatter.minifyXml(unminifiedXml, options);
|
||||||
|
|
||||||
|
assert.equal(actualMinifiedXml, expectedMinifiedXml, "Actual minified XML does not match expected minified XML.");
|
||||||
|
}
|
||||||
|
|
10
src/test/test-data/issue-262.minified.xml
Normal file
10
src/test/test-data/issue-262.minified.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<tests><test>1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.</test><test xml:space="preserve">1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.</test><test>1. 2. 3. 4.</test><test xml:space="preserve">1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.</test></tests>
|
18
src/test/test-data/issue-262.unminified.xml
Normal file
18
src/test/test-data/issue-262.unminified.xml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<tests>
|
||||||
|
<test>1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.</test>
|
||||||
|
<test xml:space="preserve">1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.</test>
|
||||||
|
<test>1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.</test>
|
||||||
|
<test xml:space="preserve">1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
4.</test>
|
||||||
|
</tests>
|
Loading…
Add table
Reference in a new issue