@prettier/plugin-xml
Advanced tools
Comparing version 0.2.0 to 0.3.0
@@ -8,2 +8,3 @@ { | ||
"rules": { | ||
"no-cond-assign": "off", | ||
"no-unused-vars": [ | ||
@@ -10,0 +11,0 @@ "error", |
@@ -9,2 +9,15 @@ # Changelog | ||
## [0.3.0] - 2019-11-14 | ||
### Added | ||
- Support for cdata tags. | ||
- Support for the `locStart` and `locEnd` functions by tracking node metadata in the new parser. | ||
- Support for comment nodes. | ||
- Support for `<?xml ... ?>` and `<?xml-model ... ?>` tags. | ||
### Changed | ||
- Dropped the dependency on `fast-xml-parser` in favor of writing our own for better control over comments and node location. | ||
## [0.2.0] - 2019-11-12 | ||
@@ -22,4 +35,5 @@ | ||
[unreleased]: https://github.com/prettier/plugin-xml/compare/v0.2.0...HEAD | ||
[unreleased]: https://github.com/prettier/plugin-xml/compare/v0.3.0...HEAD | ||
[0.3.0]: https://github.com/prettier/plugin-xml/compare/v0.2.0...v0.3.0 | ||
[0.2.0]: https://github.com/prettier/plugin-xml/compare/v0.1.0...v0.2.0 | ||
[0.1.0]: https://github.com/prettier/plugin-xml/compare/289f2a...v0.1.0 |
{ | ||
"name": "@prettier/plugin-xml", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "prettier plugin for XML", | ||
@@ -22,3 +22,2 @@ "main": "src/plugin.js", | ||
"dependencies": { | ||
"fast-xml-parser": "^3.14.0", | ||
"prettier": ">=1.10" | ||
@@ -25,0 +24,0 @@ }, |
141
src/parse.js
@@ -1,48 +0,119 @@ | ||
const parser = require("fast-xml-parser"); | ||
const attrsPattern = "([^\\s=]+)\\s*(=\\s*(['\"])(.*?)\\3)?"; | ||
const attributeNamePrefix = "@_"; | ||
const textNodeName = "#text"; | ||
const cDataPattern = "(!\\[CDATA\\[([\\s\\S]*?)(]]>))"; | ||
const translate = (node, name) => { | ||
if (typeof node !== "object") { | ||
return { type: "leaf", name, attrs: {}, value: node.toString() }; | ||
const tagNamePattern = "(([\\w:\\-._]*:)?([\\w:\\-._]+))"; | ||
const startTagPattern = `${tagNamePattern}([^>]*)>`; | ||
const endTagPattern = `((\\/)${tagNamePattern}\\s*>)`; | ||
const commentPattern = "(!--)(.+?)-->"; | ||
const declPattern = "((\\?xml)(-model)?)(.+)\\?>"; | ||
const tagPattern = `<(${cDataPattern}|${startTagPattern}|${endTagPattern}|${commentPattern}|${declPattern})([^<]*)`; | ||
class XMLNode { | ||
constructor(tagname, opts) { | ||
this.tagname = tagname; | ||
this.children = []; | ||
this.parent = opts.parent; | ||
this.value = opts.value || ""; | ||
this.locStart = opts.locStart || 0; | ||
this.locEnd = opts.locEnd || 0; | ||
this.attrs = {}; | ||
this.parseAttrs(opts.attrs); | ||
} | ||
const attrs = {}; | ||
let value = []; | ||
parseAttrs(attrs) { | ||
if (typeof attrs !== "string" || !attrs) { | ||
return; | ||
} | ||
Object.keys(node).forEach(key => { | ||
const children = node[key]; | ||
const normal = attrs.replace(/\r?\n/g, " "); | ||
const attrsRegex = new RegExp(attrsPattern, "g"); | ||
let match; | ||
if (key.startsWith(attributeNamePrefix)) { | ||
attrs[key.slice(2)] = children; | ||
} else if (Array.isArray(children)) { | ||
value = value.concat(children.map(child => translate(child, key))); | ||
} else if (key !== textNodeName) { | ||
value.push(translate(children, key)); | ||
while ((match = attrsRegex.exec(normal))) { | ||
const name = match[1]; | ||
if (name.length) { | ||
this.attrs[name] = match[4] === undefined ? true : match[4].trim(); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
if (Object.prototype.hasOwnProperty.call(node, textNodeName)) { | ||
return { type: "leaf", name, attrs, value: node[textNodeName] }; | ||
const parse = (text, _parsers, _opts) => { | ||
const rootNode = new XMLNode("!root", { locStart: 0, locEnd: text.length }); | ||
let node = rootNode; | ||
const tagRegex = new RegExp(tagPattern, "g"); | ||
let tag; | ||
while ((tag = tagRegex.exec(text))) { | ||
const value = (tag[20] || "").trim(); | ||
if (tag[17] === "?xml") { | ||
node.children.push( | ||
new XMLNode(`!${tag[16]}`, { | ||
parent: node, | ||
attrs: tag[19], | ||
locStart: tag.index, | ||
locEnd: tag.index + tag[0].trim().length | ||
}) | ||
); | ||
} else if (tag[14] === "!--") { | ||
node.children.push( | ||
new XMLNode("!comment", { | ||
parent: node, | ||
value: tag[15].trim(), | ||
locStart: tag.index, | ||
locEnd: tag.index + tag[0].trim().length | ||
}) | ||
); | ||
} else if (tag[4] === "]]>") { | ||
node.children.push( | ||
new XMLNode("!cdata", { | ||
parent: node, | ||
value: tag[3], | ||
attrs: tag[8], | ||
locStart: tag.index, | ||
locEnd: tag.index + tag[0].trim().length | ||
}) | ||
); | ||
node.value += `\\c${value}`; | ||
} else if (tag[10] === "/") { | ||
node.locEnd = tag.index + tag[0].trim().length; | ||
node.parent.value += value; | ||
node = node.parent; | ||
} else if ( | ||
typeof tag[8] !== "undefined" && | ||
tag[8].charAt(tag[8].length - 1) === "/" | ||
) { | ||
node.value += value; | ||
node.children.push( | ||
new XMLNode(tag[5], { | ||
parent: node, | ||
attrs: tag[8].slice(0, -1), | ||
locStart: tag.index, | ||
locEnd: tag.index + tag[0].trim().length | ||
}) | ||
); | ||
} else { | ||
node = new XMLNode(tag[5], { | ||
parent: node, | ||
value, | ||
attrs: tag[8], | ||
locStart: tag.index | ||
}); | ||
node.parent.children.push(node); | ||
} | ||
} | ||
return { type: "node", name, attrs, value }; | ||
return rootNode; | ||
}; | ||
const parse = (text, _parsers, _opts) => | ||
Object.assign( | ||
{}, | ||
translate( | ||
parser.parse(text, { | ||
allowBooleanAttributes: true, | ||
attributeNamePrefix, | ||
ignoreAttributes: false, | ||
parseAttributeValue: true, | ||
textNodeName | ||
}) | ||
), | ||
{ type: "root" } | ||
); | ||
module.exports = parse; |
const parse = require("./parse"); | ||
const print = require("./print"); | ||
// These functions are necessary or the print with cursor function breaks. | ||
// Eventually we should fill them in with the correct metadata, but the parser | ||
// doesn't provide it at the moment. | ||
const locStart = _node => 0; | ||
const locEnd = _node => 0; | ||
const locStart = node => node.locStart; | ||
const locEnd = node => node.locEnd; | ||
@@ -15,3 +12,4 @@ const plugin = { | ||
parsers: ["xml"], | ||
extensions: [".xml"] | ||
extensions: [".dita", ".ditamap", ".ditaval", ".xml"], | ||
vscodeLanguageIds: ["xml"] | ||
} | ||
@@ -18,0 +16,0 @@ ], |
103
src/print.js
@@ -11,5 +11,2 @@ const { | ||
const getFirstNonBlankLine = originalText => | ||
originalText.split("\n").find(text => text.trim().length !== 0); | ||
const printAttrs = attrs => { | ||
@@ -32,66 +29,62 @@ if (Object.keys(attrs).length === 0) { | ||
const printSelfClosingTag = (name, attrs) => | ||
group(concat(["<", name, printAttrs(attrs), line, "/>"])); | ||
const printOpeningTag = (name, attrs) => { | ||
if (name === "!cdata") { | ||
return "<![CDATA["; | ||
} | ||
return group(concat(["<", name, printAttrs(attrs), softline, ">"])); | ||
}; | ||
const printSelfClosingTag = (name, attrs) => { | ||
if (name === "!?xml" || name === "!?xml-model") { | ||
return group(concat(["<", name.slice(1), printAttrs(attrs), line, "?>"])); | ||
} | ||
return group(concat(["<", name, printAttrs(attrs), line, "/>"])); | ||
}; | ||
const genericPrint = (path, opts, print) => { | ||
const { type, name, attrs, value } = path.getValue(); | ||
const { tagname, children, attrs, value } = path.getValue(); | ||
switch (type) { | ||
case "leaf": { | ||
if (!value && opts.xmlSelfClosingTags) { | ||
return printSelfClosingTag(name, attrs); | ||
} | ||
if (tagname === "!root") { | ||
return concat([join(hardline, path.map(print, "children")), hardline]); | ||
} | ||
return group( | ||
concat([ | ||
group(concat(["<", name, printAttrs(attrs), softline, ">"])), | ||
indent(concat([softline, value])), | ||
softline, | ||
"</", | ||
name, | ||
">" | ||
]) | ||
); | ||
} | ||
case "node": { | ||
if (value.length === 0 && opts.xmlSelfClosingTags) { | ||
return printSelfClosingTag(name, attrs); | ||
} | ||
if (tagname === "!comment") { | ||
return group( | ||
concat(["<!--", indent(concat([line, value])), concat([line, "-->"])]) | ||
); | ||
} | ||
let inner; | ||
if (Object.keys(children).length === 0 && !value && opts.xmlSelfClosingTags) { | ||
return printSelfClosingTag(tagname, attrs); | ||
} | ||
if (value.length === 0) { | ||
inner = softline; | ||
} else { | ||
inner = concat([ | ||
indent(concat([hardline, join(hardline, path.map(print, "value"))])), | ||
hardline | ||
]); | ||
} | ||
const openingTag = printOpeningTag(tagname, attrs); | ||
const closingTag = tagname === "!cdata" ? "]]>" : `</${tagname}>`; | ||
return group( | ||
concat([ | ||
group(concat(["<", name, printAttrs(attrs), softline, ">"])), | ||
inner, | ||
"</", | ||
name, | ||
">" | ||
]) | ||
); | ||
} | ||
case "root": { | ||
const parts = [join(hardline, path.map(print, "value")), hardline]; | ||
if (Object.keys(children).length === 0) { | ||
return group( | ||
concat([ | ||
openingTag, | ||
indent(concat([softline, value])), | ||
softline, | ||
closingTag | ||
]) | ||
); | ||
} | ||
const firstNonBlankLine = getFirstNonBlankLine(opts.originalText); | ||
if (firstNonBlankLine && firstNonBlankLine.startsWith("<?xml")) { | ||
parts.unshift(firstNonBlankLine, hardline); | ||
} | ||
let inner; | ||
return concat(parts); | ||
} | ||
default: | ||
throw new Error(`Unsupported node encountered: ${type}`); | ||
if (children.length === 0) { | ||
inner = softline; | ||
} else { | ||
inner = concat([ | ||
indent(concat([hardline, join(hardline, path.map(print, "children"))])), | ||
hardline | ||
]); | ||
} | ||
return group(concat([openingTag, inner, closingTag])); | ||
}; | ||
module.exports = genericPrint; |
const { here } = require("./utils"); | ||
describe("doctype", () => { | ||
test("prints out first line if it's a doctype", () => { | ||
test("renders appropriately", () => { | ||
const content = here(` | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
<?xml-model href="model.rnc" type="application/relax-ng-compact-sync-syntax" ?> | ||
<foo /> | ||
@@ -8,0 +9,0 @@ `); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
18192
1
21
440
- Removedfast-xml-parser@^3.14.0
- Removedfast-xml-parser@3.21.1(transitive)
- Removedstrnum@1.0.5(transitive)