Merge pull request #182 from DotJoshJohnson/release-2.2.0
Release v2.2.0
This commit is contained in:
commit
516cf1ffcb
@ -21,6 +21,7 @@
|
|||||||
* **`xmlTools.enableXmlTreeView`:** Enables the XML Tree View for XML documents.
|
* **`xmlTools.enableXmlTreeView`:** Enables the XML Tree View for XML documents.
|
||||||
* **`xmlTools.enableXmlTreeViewMetadata`:** Enables attribute and child element counts in the XML Document view.
|
* **`xmlTools.enableXmlTreeViewMetadata`:** Enables attribute and child element counts in the XML Document view.
|
||||||
* **`xmlTools.enableXmlTreeViewCursorSync`:** Enables auto-reveal of elements in the XML Document view when a start tag is clicked in the editor.
|
* **`xmlTools.enableXmlTreeViewCursorSync`:** Enables auto-reveal of elements in the XML Document view when a start tag is clicked in the editor.
|
||||||
|
* **`xmlTools.enforcePrettySelfClosingTagOnFormat`:** Ensures a space is added before the forward slash at the end of a self-closing tag.
|
||||||
* **`xmlTools.ignoreDefaultNamespace`:** Ignore default xmlns attributes when evaluating XPath.
|
* **`xmlTools.ignoreDefaultNamespace`:** Ignore default xmlns attributes when evaluating XPath.
|
||||||
* **`xmlTools.persistXPathQuery`:** Remember the last XPath query used.
|
* **`xmlTools.persistXPathQuery`:** Remember the last XPath query used.
|
||||||
* **`xmlTools.removeCommentsOnMinify`:** Remove XML comments during minification.
|
* **`xmlTools.removeCommentsOnMinify`:** Remove XML comments during minification.
|
||||||
|
22
package.json
22
package.json
@ -2,7 +2,7 @@
|
|||||||
"name": "xml",
|
"name": "xml",
|
||||||
"displayName": "XML Tools",
|
"displayName": "XML Tools",
|
||||||
"description": "XML Formatting, XQuery, and XPath Tools for Visual Studio Code",
|
"description": "XML Formatting, XQuery, and XPath Tools for Visual Studio Code",
|
||||||
"version": "2.1.1",
|
"version": "2.2.0",
|
||||||
"preview": false,
|
"preview": false,
|
||||||
"publisher": "DotJoshJohnson",
|
"publisher": "DotJoshJohnson",
|
||||||
"author": {
|
"author": {
|
||||||
@ -55,6 +55,10 @@
|
|||||||
"command": "xmlTools.formatAsXml",
|
"command": "xmlTools.formatAsXml",
|
||||||
"title": "XML Tools: Format as XML"
|
"title": "XML Tools: Format as XML"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "xmlTools.getCurrentXPath",
|
||||||
|
"title": "XML Tools: Get Current XPath"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "xmlTools.minifyXml",
|
"command": "xmlTools.minifyXml",
|
||||||
"title": "XML Tools: Minify XML"
|
"title": "XML Tools: Minify XML"
|
||||||
@ -78,10 +82,16 @@
|
|||||||
},
|
},
|
||||||
"xmlTools.enableXmlTreeViewCursorSync": {
|
"xmlTools.enableXmlTreeViewCursorSync": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": true,
|
"default": false,
|
||||||
"description": "Enables auto-reveal of elements in the XML Document view when a start tag is clicked in the editor.",
|
"description": "Enables auto-reveal of elements in the XML Document view when a start tag is clicked in the editor.",
|
||||||
"scope": "window"
|
"scope": "window"
|
||||||
},
|
},
|
||||||
|
"xmlTools.enforcePrettySelfClosingTagOnFormat": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enforces a space before the forward slash at the end of a self-closing XML tag.",
|
||||||
|
"scope": "resource"
|
||||||
|
},
|
||||||
"xmlTools.ignoreDefaultNamespace": {
|
"xmlTools.ignoreDefaultNamespace": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": true,
|
"default": true,
|
||||||
@ -114,6 +124,10 @@
|
|||||||
},
|
},
|
||||||
"xmlTools.xmlFormatterImplementation": {
|
"xmlTools.xmlFormatterImplementation": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"classic",
|
||||||
|
"v2"
|
||||||
|
],
|
||||||
"default": "v2",
|
"default": "v2",
|
||||||
"description": "Supported XML Formatters: classic",
|
"description": "Supported XML Formatters: classic",
|
||||||
"scope": "window"
|
"scope": "window"
|
||||||
@ -195,6 +209,10 @@
|
|||||||
"command": "xmlTools.executeXQuery",
|
"command": "xmlTools.executeXQuery",
|
||||||
"when": "editorLangId == xquery"
|
"when": "editorLangId == xquery"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "xmlTools.getCurrentXPath",
|
||||||
|
"when": "editorLangId == xml"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "xmlTools.minifyXml",
|
"command": "xmlTools.minifyXml",
|
||||||
"when": "editorLangId == xml"
|
"when": "editorLangId == xml"
|
||||||
|
@ -43,6 +43,10 @@ export class Configuration {
|
|||||||
return this._getForWindow<string>("xqueryExecutionInputSearchPattern");
|
return this._getForWindow<string>("xqueryExecutionInputSearchPattern");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static enforcePrettySelfClosingTagOnFormat(resource: Uri): boolean {
|
||||||
|
return this._getForResource<boolean>("enforcePrettySelfClosingTagOnFormat", resource);
|
||||||
|
}
|
||||||
|
|
||||||
static removeCommentsOnMinify(resource: Uri): boolean {
|
static removeCommentsOnMinify(resource: Uri): boolean {
|
||||||
return this._getForResource<boolean>("removeCommentsOnMinify", resource);
|
return this._getForResource<boolean>("removeCommentsOnMinify", resource);
|
||||||
}
|
}
|
||||||
|
@ -2,3 +2,4 @@ export * from "./configuration";
|
|||||||
export * from "./create-document-selector";
|
export * from "./create-document-selector";
|
||||||
export * from "./extension-state";
|
export * from "./extension-state";
|
||||||
export * from "./native-commands";
|
export * from "./native-commands";
|
||||||
|
export * from "./xml-traverser";
|
||||||
|
129
src/common/xml-traverser.ts
Normal file
129
src/common/xml-traverser.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import { Position } from "vscode";
|
||||||
|
import { DOMParser } from "xmldom";
|
||||||
|
|
||||||
|
export class XmlTraverser {
|
||||||
|
|
||||||
|
constructor(private _xmlDocument: Document) { }
|
||||||
|
|
||||||
|
get xmlDocument(): Document {
|
||||||
|
return this._xmlDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
set xmlDocument(value: Document) {
|
||||||
|
this._xmlDocument = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
getChildAttributeArray(node: Element): any[] {
|
||||||
|
if (!node.attributes) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const array = new Array<any>();
|
||||||
|
|
||||||
|
for (let i = 0; i < node.attributes.length; i++) {
|
||||||
|
array.push(node.attributes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
getChildElementArray(node: Node): any[] {
|
||||||
|
if (!node.childNodes) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const array = new Array<any>();
|
||||||
|
|
||||||
|
for (let i = 0; i < node.childNodes.length; i++) {
|
||||||
|
const child = node.childNodes[i];
|
||||||
|
|
||||||
|
if (this.isElement(child)) {
|
||||||
|
array.push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
getElementAtPosition(position: Position): Element {
|
||||||
|
const node = this.getNodeAtPosition(position);
|
||||||
|
|
||||||
|
return this.getNearestElementAncestor(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
getNearestElementAncestor(node: Node): Element {
|
||||||
|
if (!this.isElement) {
|
||||||
|
return this.getNearestElementAncestor(node.parentNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Element>node;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeAtPosition(position: Position): Node {
|
||||||
|
return this._getNodeAtPositionCore(position, this._xmlDocument.documentElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSiblings(node: Node): Node[] {
|
||||||
|
return [...this.getChildAttributeArray(<Element>node.parentNode), ...this.getChildElementArray(node.parentNode)];
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSimilarSiblings(node: Node): boolean {
|
||||||
|
if (!node || !node.parentNode || !this.isElement(node)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const siblings = this.getChildElementArray(<Element>node.parentNode);
|
||||||
|
|
||||||
|
return (siblings.filter(x => x.tagName === (node as Element).tagName).length > 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
isElement(node: Node): boolean {
|
||||||
|
return (!!node && !!(node as Element).tagName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getNodeAtPositionCore(position: Position, contextNode: Node): Node {
|
||||||
|
if (!contextNode) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineNumber = (contextNode as any).lineNumber;
|
||||||
|
const columnNumber = (contextNode as any).columnNumber;
|
||||||
|
const columnRange = [columnNumber, (columnNumber + (this._getNodeWidthInCharacters(contextNode) - 1))];
|
||||||
|
|
||||||
|
// for some reason, xmldom sets the column number for attributes to the "="
|
||||||
|
if (!this.isElement(contextNode)) {
|
||||||
|
columnRange[0] = (columnRange[0] - contextNode.nodeName.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineNumber === (position.line + 1) && ((position.character + 1) >= columnRange[0] && (position.character + 1) < columnRange[1])) {
|
||||||
|
return contextNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isElement(contextNode)) {
|
||||||
|
const children = [...this.getChildAttributeArray(<Element>contextNode), ...this.getChildElementArray(contextNode)];
|
||||||
|
let result: Node;
|
||||||
|
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const child = children[i];
|
||||||
|
|
||||||
|
result = this._getNodeAtPositionCore(position, child);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getNodeWidthInCharacters(node: Node) {
|
||||||
|
if (this.isElement(node)) {
|
||||||
|
return (node.nodeName.length + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return (node.nodeName.length + node.nodeValue.length + 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ export namespace commands {
|
|||||||
export const evaluateXPath = "xmlTools.evaluateXPath";
|
export const evaluateXPath = "xmlTools.evaluateXPath";
|
||||||
export const executeXQuery = "xmlTools.executeXQuery";
|
export const executeXQuery = "xmlTools.executeXQuery";
|
||||||
export const formatAsXml = "xmlTools.formatAsXml";
|
export const formatAsXml = "xmlTools.formatAsXml";
|
||||||
|
export const getCurrentXPath = "xmlTools.getCurrentXPath";
|
||||||
export const minifyXml = "xmlTools.minifyXml";
|
export const minifyXml = "xmlTools.minifyXml";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import { XmlFormatterFactory, XmlFormattingEditProvider } from "./formatting";
|
|||||||
import { formatAsXml, minifyXml } from "./formatting/commands";
|
import { formatAsXml, minifyXml } from "./formatting/commands";
|
||||||
import { XQueryLinter } from "./linting";
|
import { XQueryLinter } from "./linting";
|
||||||
import { XmlTreeDataProvider } from "./tree-view";
|
import { XmlTreeDataProvider } from "./tree-view";
|
||||||
import { evaluateXPath } from "./xpath/commands";
|
import { evaluateXPath, getCurrentXPath } from "./xpath/commands";
|
||||||
import { executeXQuery } from "./xquery-execution/commands";
|
import { executeXQuery } from "./xquery-execution/commands";
|
||||||
|
|
||||||
import * as constants from "./constants";
|
import * as constants from "./constants";
|
||||||
@ -61,7 +61,8 @@ export function activate(context: ExtensionContext) {
|
|||||||
|
|
||||||
/* XPath Features */
|
/* XPath Features */
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
commands.registerTextEditorCommand(constants.commands.evaluateXPath, evaluateXPath)
|
commands.registerTextEditorCommand(constants.commands.evaluateXPath, evaluateXPath),
|
||||||
|
commands.registerTextEditorCommand(constants.commands.getCurrentXPath, getCurrentXPath)
|
||||||
);
|
);
|
||||||
|
|
||||||
/* XQuery Features */
|
/* XQuery Features */
|
||||||
|
@ -39,6 +39,7 @@ export class V2XmlFormatter implements XmlFormatter {
|
|||||||
const nc = xml.charAt(i + 1);
|
const nc = xml.charAt(i + 1);
|
||||||
const nnc = xml.charAt(i + 2);
|
const nnc = xml.charAt(i + 2);
|
||||||
const pc = xml.charAt(i - 1);
|
const pc = xml.charAt(i - 1);
|
||||||
|
const ppc = xml.charAt(i - 2);
|
||||||
|
|
||||||
// entering CData
|
// entering CData
|
||||||
if (location === Location.Text && cc === "<" && nc === "!" && nnc === "[") {
|
if (location === Location.Text && cc === "<" && nc === "!" && nnc === "[") {
|
||||||
@ -86,7 +87,8 @@ export class V2XmlFormatter implements XmlFormatter {
|
|||||||
// entering StartTag.StartTagName
|
// entering StartTag.StartTagName
|
||||||
else if (location === Location.Text && cc === "<" && ["/", "!"].indexOf(nc) === -1) {
|
else if (location === Location.Text && cc === "<" && ["/", "!"].indexOf(nc) === -1) {
|
||||||
// if this occurs after another tag, prepend a line break
|
// if this occurs after another tag, prepend a line break
|
||||||
if (pc === ">") {
|
// but do not add one if the previous tag was self-closing (it already adds its own)
|
||||||
|
if (pc === ">" && ppc !== "/") {
|
||||||
output += `${options.newLine}${this._getIndent(options, indentLevel)}<`;
|
output += `${options.newLine}${this._getIndent(options, indentLevel)}<`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,19 +122,27 @@ export class V2XmlFormatter implements XmlFormatter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// entering StartTag.Attribute.AttributeValue
|
// entering StartTag.Attribute.AttributeValue
|
||||||
else if (location === Location.Attribute && cc === "\"") {
|
else if (location === Location.Attribute && (cc === "\"" || cc === "'")) {
|
||||||
output += "\"";
|
output += cc;
|
||||||
lastNonTextLocation = location;
|
lastNonTextLocation = location;
|
||||||
location = Location.AttributeValue;
|
location = Location.AttributeValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// exiting StartTag.Attribute.AttributeValue, entering StartTag
|
// exiting StartTag.Attribute.AttributeValue, entering StartTag
|
||||||
else if (location === Location.AttributeValue && cc === "\"") {
|
else if (location === Location.AttributeValue && (cc === "\"" || cc === "'")) {
|
||||||
output += "\"";
|
output += cc;
|
||||||
lastNonTextLocation = location;
|
lastNonTextLocation = location;
|
||||||
location = Location.StartTag;
|
location = Location.StartTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// approaching the end of a self-closing tag where there was no whitespace (issue #149)
|
||||||
|
else if ((location === Location.StartTag || location === Location.StartTagName)
|
||||||
|
&& cc === "/"
|
||||||
|
&& pc !== " "
|
||||||
|
&& options.enforcePrettySelfClosingTagOnFormat) {
|
||||||
|
output += " /";
|
||||||
|
}
|
||||||
|
|
||||||
// exiting StartTag or StartTag.StartTagName, entering Text
|
// exiting StartTag or StartTag.StartTagName, entering Text
|
||||||
else if ((location === Location.StartTag || location === Location.StartTagName) && cc === ">") {
|
else if ((location === Location.StartTag || location === Location.StartTagName) && cc === ">") {
|
||||||
// if this was a self-closing tag, we need to decrement the indent level and add a newLine
|
// if this was a self-closing tag, we need to decrement the indent level and add a newLine
|
||||||
|
@ -5,6 +5,7 @@ import * as constants from "../constants";
|
|||||||
|
|
||||||
export interface XmlFormattingOptions {
|
export interface XmlFormattingOptions {
|
||||||
editorOptions: FormattingOptions;
|
editorOptions: FormattingOptions;
|
||||||
|
enforcePrettySelfClosingTagOnFormat: boolean;
|
||||||
newLine: string;
|
newLine: string;
|
||||||
removeCommentsOnMinify: boolean;
|
removeCommentsOnMinify: boolean;
|
||||||
splitAttributesOnFormat: boolean;
|
splitAttributesOnFormat: boolean;
|
||||||
@ -15,6 +16,7 @@ export class XmlFormattingOptionsFactory {
|
|||||||
static getXmlFormattingOptions(formattingOptions: FormattingOptions, document: TextDocument): XmlFormattingOptions {
|
static getXmlFormattingOptions(formattingOptions: FormattingOptions, document: TextDocument): XmlFormattingOptions {
|
||||||
return {
|
return {
|
||||||
editorOptions: formattingOptions,
|
editorOptions: formattingOptions,
|
||||||
|
enforcePrettySelfClosingTagOnFormat: Configuration.enforcePrettySelfClosingTagOnFormat(document.uri),
|
||||||
newLine: (document.eol === EndOfLine.CRLF) ? "\r\n" : "\n",
|
newLine: (document.eol === EndOfLine.CRLF) ? "\r\n" : "\n",
|
||||||
removeCommentsOnMinify: Configuration.removeCommentsOnMinify(document.uri),
|
removeCommentsOnMinify: Configuration.removeCommentsOnMinify(document.uri),
|
||||||
splitAttributesOnFormat: Configuration.splitAttributesOnFormat(document.uri),
|
splitAttributesOnFormat: Configuration.splitAttributesOnFormat(document.uri),
|
||||||
|
@ -17,6 +17,7 @@ describe("V2XmlFormatter", () => {
|
|||||||
insertSpaces: true,
|
insertSpaces: true,
|
||||||
tabSize: 4
|
tabSize: 4
|
||||||
},
|
},
|
||||||
|
enforcePrettySelfClosingTagOnFormat: false,
|
||||||
newLine: "\r\n",
|
newLine: "\r\n",
|
||||||
removeCommentsOnMinify: false,
|
removeCommentsOnMinify: false,
|
||||||
splitAttributesOnFormat: false,
|
splitAttributesOnFormat: false,
|
||||||
@ -51,6 +52,22 @@ describe("V2XmlFormatter", () => {
|
|||||||
testFormatter(xmlFormatter, options, "maintain-comment-formatting");
|
testFormatter(xmlFormatter, options, "maintain-comment-formatting");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should handle single-quotes in attributes", () => {
|
||||||
|
testFormatter(xmlFormatter, options, "single-quotes");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not add extra line breaks before start tags", () => {
|
||||||
|
testFormatter(xmlFormatter, options, "issue-178");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow users to enforce space before self-closing tag slash", () => {
|
||||||
|
options.enforcePrettySelfClosingTagOnFormat = true;
|
||||||
|
|
||||||
|
testFormatter(xmlFormatter, options, "issue-149");
|
||||||
|
|
||||||
|
options.enforcePrettySelfClosingTagOnFormat = false;
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
9
src/test/test-data/issue-149.formatted.xml
Normal file
9
src/test/test-data/issue-149.formatted.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<root>
|
||||||
|
<entry>
|
||||||
|
<field1>One</field1>
|
||||||
|
<field2 />
|
||||||
|
<field3>Three</field3>
|
||||||
|
<field4 />
|
||||||
|
<field5>Five</field5>
|
||||||
|
</entry>
|
||||||
|
</root>
|
1
src/test/test-data/issue-149.unformatted.xml
Normal file
1
src/test/test-data/issue-149.unformatted.xml
Normal file
@ -0,0 +1 @@
|
|||||||
|
<root><entry><field1>One</field1><field2/><field3>Three</field3><field4/><field5>Five</field5></entry></root>
|
9
src/test/test-data/issue-178.formatted.xml
Normal file
9
src/test/test-data/issue-178.formatted.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<root>
|
||||||
|
<entry>
|
||||||
|
<field1>One</field1>
|
||||||
|
<field2/>
|
||||||
|
<field3>Three</field3>
|
||||||
|
<field4/>
|
||||||
|
<field5>Five</field5>
|
||||||
|
</entry>
|
||||||
|
</root>
|
1
src/test/test-data/issue-178.unformatted.xml
Normal file
1
src/test/test-data/issue-178.unformatted.xml
Normal file
@ -0,0 +1 @@
|
|||||||
|
<root><entry><field1>One</field1><field2/><field3>Three</field3><field4/><field5>Five</field5></entry></root>
|
4
src/test/test-data/single-quotes.formatted.xml
Normal file
4
src/test/test-data/single-quotes.formatted.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<root>
|
||||||
|
<element attr='1' />
|
||||||
|
<element attr='2' />
|
||||||
|
</root>
|
4
src/test/test-data/single-quotes.unformatted.xml
Normal file
4
src/test/test-data/single-quotes.unformatted.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<root>
|
||||||
|
<element attr='1' />
|
||||||
|
<element attr='2' />
|
||||||
|
</root>
|
@ -7,12 +7,13 @@ import {
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { DOMParser } from "xmldom";
|
import { DOMParser } from "xmldom";
|
||||||
|
|
||||||
import { Configuration, NativeCommands } from "../common";
|
import { Configuration, NativeCommands, XmlTraverser } from "../common";
|
||||||
import * as constants from "../constants";
|
import * as constants from "../constants";
|
||||||
|
|
||||||
export class XmlTreeDataProvider implements TreeDataProvider<any> {
|
export class XmlTreeDataProvider implements TreeDataProvider<any> {
|
||||||
private _onDidChangeTreeData: EventEmitter<any> = new EventEmitter<any>();
|
private _onDidChangeTreeData: EventEmitter<any> = new EventEmitter<any>();
|
||||||
private _xmlDocument: Document;
|
private _xmlDocument: Document;
|
||||||
|
private _xmlTraverser: XmlTraverser;
|
||||||
|
|
||||||
constructor(private _context: ExtensionContext) {
|
constructor(private _context: ExtensionContext) {
|
||||||
window.onDidChangeActiveTextEditor(() => {
|
window.onDidChangeActiveTextEditor(() => {
|
||||||
@ -38,13 +39,13 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
|
|||||||
|
|
||||||
const treeItem = new TreeItem(element.localName);
|
const treeItem = new TreeItem(element.localName);
|
||||||
|
|
||||||
if (!this._isElement(element)) {
|
if (!this._xmlTraverser.isElement(element)) {
|
||||||
treeItem.label = `${element.localName} = "${element.nodeValue}"`;
|
treeItem.label = `${element.localName} = "${element.nodeValue}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (enableMetadata) {
|
else if (enableMetadata) {
|
||||||
const childAttributes = this._getChildAttributeArray(<Element>element);
|
const childAttributes = this._xmlTraverser.getChildAttributeArray(<Element>element);
|
||||||
const childElements = this._getChildElementArray(<Element>element);
|
const childElements = this._xmlTraverser.getChildElementArray(<Element>element);
|
||||||
const totalChildren = (childAttributes.length + childElements.length);
|
const totalChildren = (childAttributes.length + childElements.length);
|
||||||
|
|
||||||
if (totalChildren > 0) {
|
if (totalChildren > 0) {
|
||||||
@ -64,7 +65,7 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
|
|||||||
treeItem.label += ")";
|
treeItem.label += ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._hasSimilarSiblings(<Element>element) && enableSync) {
|
if (this._xmlTraverser.hasSimilarSiblings(<Element>element) && enableSync) {
|
||||||
treeItem.label += ` [line ${(element as any).lineNumber}]`;
|
treeItem.label += ` [line ${(element as any).lineNumber}]`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,8 +89,8 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
|
|||||||
this._refreshTree();
|
this._refreshTree();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._isElement(element)) {
|
if (this._xmlTraverser.isElement(element)) {
|
||||||
return [].concat(this._getChildAttributeArray(<Element>element), this._getChildElementArray(<Element>element));
|
return [].concat(this._xmlTraverser.getChildAttributeArray(<Element>element), this._xmlTraverser.getChildElementArray(<Element>element));
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (this._xmlDocument) {
|
else if (this._xmlDocument) {
|
||||||
@ -102,78 +103,21 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getParent(element: Node): Node {
|
getParent(element: Node): Node {
|
||||||
if (!element || !element.parentNode || !element.parentNode.parentNode) {
|
if ((!element || !element.parentNode || !element.parentNode.parentNode) && !(element as any).ownerElement) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return element.parentNode;
|
return element.parentNode || (element as any).ownerElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNodeAtPosition(position: Position): Node {
|
getNodeAtPosition(position: Position): Node {
|
||||||
return this._getNodeAtPositionCore(position, this._xmlDocument.documentElement);
|
return this._xmlTraverser.getNodeAtPosition(position);
|
||||||
}
|
|
||||||
|
|
||||||
private _getNodeAtPositionCore(position: Position, contextElement: Element): Node {
|
|
||||||
if (!contextElement) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (((contextElement as any).lineNumber - 1) === position.line) {
|
|
||||||
return contextElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
const children = this._getChildElementArray(<Element>contextElement);
|
|
||||||
let result: Node;
|
|
||||||
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
|
||||||
const child = children[i];
|
|
||||||
|
|
||||||
result = this._getNodeAtPositionCore(position, child);
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getChildAttributeArray(node: Element): any[] {
|
|
||||||
if (!node.attributes) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const array = new Array<any>();
|
|
||||||
|
|
||||||
for (let i = 0; i < node.attributes.length; i++) {
|
|
||||||
array.push(node.attributes[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getChildElementArray(node: Element): any[] {
|
|
||||||
if (!node.childNodes) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const array = new Array<any>();
|
|
||||||
|
|
||||||
for (let i = 0; i < node.childNodes.length; i++) {
|
|
||||||
const child = node.childNodes[i];
|
|
||||||
|
|
||||||
if (this._isElement(child)) {
|
|
||||||
array.push(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getIcon(element: Node): any {
|
private _getIcon(element: Node): any {
|
||||||
let type = "element";
|
let type = "element";
|
||||||
|
|
||||||
if (!this._isElement(element)) {
|
if (!this._xmlTraverser.isElement(element)) {
|
||||||
type = "attribute";
|
type = "attribute";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,20 +129,6 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
|
|||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _hasSimilarSiblings(element: Element): boolean {
|
|
||||||
if (!element || !element.parentNode) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const siblings = this._getChildElementArray(<Element>element.parentNode);
|
|
||||||
|
|
||||||
return (siblings.filter(x => x.tagName === element.tagName).length > 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _isElement(node: Node): boolean {
|
|
||||||
return (!!node && !!(node as Element).tagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _refreshTree(): void {
|
private _refreshTree(): void {
|
||||||
if (!this.activeEditor || this.activeEditor.document.languageId !== constants.languageIds.xml) {
|
if (!this.activeEditor || this.activeEditor.document.languageId !== constants.languageIds.xml) {
|
||||||
NativeCommands.setContext(constants.contextKeys.xmlTreeViewEnabled, false);
|
NativeCommands.setContext(constants.contextKeys.xmlTreeViewEnabled, false);
|
||||||
@ -227,6 +157,11 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
|
|||||||
this._xmlDocument = new DOMParser().parseFromString("<InvalidDocument />", "text/xml");
|
this._xmlDocument = new DOMParser().parseFromString("<InvalidDocument />", "text/xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finally {
|
||||||
|
this._xmlTraverser = this._xmlTraverser || new XmlTraverser(this._xmlDocument);
|
||||||
|
this._xmlTraverser.xmlDocument = this._xmlDocument;
|
||||||
|
}
|
||||||
|
|
||||||
this._onDidChangeTreeData.fire();
|
this._onDidChangeTreeData.fire();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
src/xpath/commands/getCurrentXPath.ts
Normal file
21
src/xpath/commands/getCurrentXPath.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { window } from "vscode";
|
||||||
|
import { TextEditor, TextEditorEdit } from "vscode";
|
||||||
|
import { DOMParser } from "xmldom";
|
||||||
|
|
||||||
|
import { XPathBuilder } from "../xpath-builder";
|
||||||
|
|
||||||
|
export function getCurrentXPath(editor: TextEditor, edit: TextEditorEdit): void {
|
||||||
|
if (!editor.selection) {
|
||||||
|
window.showInformationMessage("Please put your cursor in an element or attribute name.");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const document = new DOMParser().parseFromString(editor.document.getText());
|
||||||
|
const xpath = new XPathBuilder(document).build(editor.selection.start);
|
||||||
|
|
||||||
|
window.showInputBox({
|
||||||
|
value: xpath,
|
||||||
|
valueSelection: undefined
|
||||||
|
});
|
||||||
|
}
|
@ -1 +1,2 @@
|
|||||||
export * from "./evaluateXPath";
|
export * from "./evaluateXPath";
|
||||||
|
export * from "./getCurrentXPath";
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
export * from "./xpath-builder";
|
||||||
export * from "./xpath-evaluator";
|
export * from "./xpath-evaluator";
|
||||||
|
41
src/xpath/xpath-builder.ts
Normal file
41
src/xpath/xpath-builder.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { Position } from "vscode";
|
||||||
|
import { DOMParser } from "xmldom";
|
||||||
|
|
||||||
|
import { XmlTraverser } from "../common";
|
||||||
|
|
||||||
|
export class XPathBuilder {
|
||||||
|
|
||||||
|
private _xmlTraverser: XmlTraverser;
|
||||||
|
|
||||||
|
constructor(private _xmlDocument: Document) {
|
||||||
|
this._xmlTraverser = new XmlTraverser(this._xmlDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
build(position: Position): string {
|
||||||
|
const selectedNode = this._xmlTraverser.getNodeAtPosition(position);
|
||||||
|
|
||||||
|
return this._buildCore(selectedNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _buildCore(selectedNode: Node): string {
|
||||||
|
if (selectedNode === this._xmlDocument.documentElement) {
|
||||||
|
return `/${selectedNode.nodeName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._xmlTraverser.isElement(selectedNode)) {
|
||||||
|
return `${this._buildCore((selectedNode as any).ownerElement)}/@${selectedNode.nodeName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (this._xmlTraverser.hasSimilarSiblings(selectedNode)) {
|
||||||
|
const siblings = this._xmlTraverser.getSiblings(selectedNode);
|
||||||
|
const xPathIndex = (siblings.indexOf(selectedNode) + 1);
|
||||||
|
|
||||||
|
return `${this._buildCore(selectedNode.parentNode)}/${selectedNode.nodeName}[${xPathIndex}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
return `${this._buildCore(selectedNode.parentNode)}/${selectedNode.nodeName}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -101,7 +101,7 @@ export async function executeXQuery(editor: TextEditor, edit: TextEditorEdit): P
|
|||||||
return value
|
return value
|
||||||
.replace("$(script)", editor.document.uri.fsPath)
|
.replace("$(script)", editor.document.uri.fsPath)
|
||||||
.replace("$(input)", inputFile.fsPath)
|
.replace("$(input)", inputFile.fsPath)
|
||||||
.replace("$(project)", workspace.rootPath);
|
.replace("$(project)", (workspace.workspaceFolders) ? workspace.workspaceFolders[0].uri.fsPath : "");
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
Loading…
Reference in New Issue
Block a user