Merge pull request #182 from DotJoshJohnson/release-2.2.0
Release v2.2.0
This commit is contained in:
		
						commit
						516cf1ffcb
					
				
					 22 changed files with 303 additions and 92 deletions
				
			
		| 
						 | 
				
			
			@ -21,6 +21,7 @@
 | 
			
		|||
* **`xmlTools.enableXmlTreeView`:** Enables the XML Tree View for XML documents.
 | 
			
		||||
* **`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.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.persistXPathQuery`:** Remember the last XPath query used.
 | 
			
		||||
* **`xmlTools.removeCommentsOnMinify`:** Remove XML comments during minification.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										22
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										22
									
								
								package.json
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -2,7 +2,7 @@
 | 
			
		|||
    "name": "xml",
 | 
			
		||||
    "displayName": "XML Tools",
 | 
			
		||||
    "description": "XML Formatting, XQuery, and XPath Tools for Visual Studio Code",
 | 
			
		||||
    "version": "2.1.1",
 | 
			
		||||
    "version": "2.2.0",
 | 
			
		||||
    "preview": false,
 | 
			
		||||
    "publisher": "DotJoshJohnson",
 | 
			
		||||
    "author": {
 | 
			
		||||
| 
						 | 
				
			
			@ -55,6 +55,10 @@
 | 
			
		|||
                "command": "xmlTools.formatAsXml",
 | 
			
		||||
                "title": "XML Tools: Format as XML"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "command": "xmlTools.getCurrentXPath",
 | 
			
		||||
                "title": "XML Tools: Get Current XPath"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "command": "xmlTools.minifyXml",
 | 
			
		||||
                "title": "XML Tools: Minify XML"
 | 
			
		||||
| 
						 | 
				
			
			@ -78,10 +82,16 @@
 | 
			
		|||
                },
 | 
			
		||||
                "xmlTools.enableXmlTreeViewCursorSync": {
 | 
			
		||||
                    "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.",
 | 
			
		||||
                    "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": {
 | 
			
		||||
                    "type": "boolean",
 | 
			
		||||
                    "default": true,
 | 
			
		||||
| 
						 | 
				
			
			@ -114,6 +124,10 @@
 | 
			
		|||
                },
 | 
			
		||||
                "xmlTools.xmlFormatterImplementation": {
 | 
			
		||||
                    "type": "string",
 | 
			
		||||
                    "enum": [
 | 
			
		||||
                        "classic",
 | 
			
		||||
                        "v2"
 | 
			
		||||
                    ],
 | 
			
		||||
                    "default": "v2",
 | 
			
		||||
                    "description": "Supported XML Formatters: classic",
 | 
			
		||||
                    "scope": "window"
 | 
			
		||||
| 
						 | 
				
			
			@ -195,6 +209,10 @@
 | 
			
		|||
                    "command": "xmlTools.executeXQuery",
 | 
			
		||||
                    "when": "editorLangId == xquery"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "command": "xmlTools.getCurrentXPath",
 | 
			
		||||
                    "when": "editorLangId == xml"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "command": "xmlTools.minifyXml",
 | 
			
		||||
                    "when": "editorLangId == xml"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,6 +43,10 @@ export class Configuration {
 | 
			
		|||
        return this._getForWindow<string>("xqueryExecutionInputSearchPattern");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static enforcePrettySelfClosingTagOnFormat(resource: Uri): boolean {
 | 
			
		||||
        return this._getForResource<boolean>("enforcePrettySelfClosingTagOnFormat", resource);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static removeCommentsOnMinify(resource: Uri): boolean {
 | 
			
		||||
        return this._getForResource<boolean>("removeCommentsOnMinify", resource);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,3 +2,4 @@ export * from "./configuration";
 | 
			
		|||
export * from "./create-document-selector";
 | 
			
		||||
export * from "./extension-state";
 | 
			
		||||
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 executeXQuery = "xmlTools.executeXQuery";
 | 
			
		||||
    export const formatAsXml = "xmlTools.formatAsXml";
 | 
			
		||||
    export const getCurrentXPath = "xmlTools.getCurrentXPath";
 | 
			
		||||
    export const minifyXml = "xmlTools.minifyXml";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ import { XmlFormatterFactory, XmlFormattingEditProvider } from "./formatting";
 | 
			
		|||
import { formatAsXml, minifyXml } from "./formatting/commands";
 | 
			
		||||
import { XQueryLinter } from "./linting";
 | 
			
		||||
import { XmlTreeDataProvider } from "./tree-view";
 | 
			
		||||
import { evaluateXPath } from "./xpath/commands";
 | 
			
		||||
import { evaluateXPath, getCurrentXPath } from "./xpath/commands";
 | 
			
		||||
import { executeXQuery } from "./xquery-execution/commands";
 | 
			
		||||
 | 
			
		||||
import * as constants from "./constants";
 | 
			
		||||
| 
						 | 
				
			
			@ -61,7 +61,8 @@ export function activate(context: ExtensionContext) {
 | 
			
		|||
 | 
			
		||||
    /* XPath Features */
 | 
			
		||||
    context.subscriptions.push(
 | 
			
		||||
        commands.registerTextEditorCommand(constants.commands.evaluateXPath, evaluateXPath)
 | 
			
		||||
        commands.registerTextEditorCommand(constants.commands.evaluateXPath, evaluateXPath),
 | 
			
		||||
        commands.registerTextEditorCommand(constants.commands.getCurrentXPath, getCurrentXPath)
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    /* XQuery Features */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,6 +39,7 @@ export class V2XmlFormatter implements XmlFormatter {
 | 
			
		|||
            const nc = xml.charAt(i + 1);
 | 
			
		||||
            const nnc = xml.charAt(i + 2);
 | 
			
		||||
            const pc = xml.charAt(i - 1);
 | 
			
		||||
            const ppc = xml.charAt(i - 2);
 | 
			
		||||
 | 
			
		||||
            // entering CData
 | 
			
		||||
            if (location === Location.Text && cc === "<" && nc === "!" && nnc === "[") {
 | 
			
		||||
| 
						 | 
				
			
			@ -86,7 +87,8 @@ export class V2XmlFormatter implements XmlFormatter {
 | 
			
		|||
            // entering StartTag.StartTagName
 | 
			
		||||
            else if (location === Location.Text && cc === "<" && ["/", "!"].indexOf(nc) === -1) {
 | 
			
		||||
                // 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)}<`;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -120,19 +122,27 @@ export class V2XmlFormatter implements XmlFormatter {
 | 
			
		|||
            }
 | 
			
		||||
 | 
			
		||||
            // entering StartTag.Attribute.AttributeValue
 | 
			
		||||
            else if (location === Location.Attribute && cc === "\"") {
 | 
			
		||||
                output += "\"";
 | 
			
		||||
            else if (location === Location.Attribute && (cc === "\"" || cc === "'")) {
 | 
			
		||||
                output += cc;
 | 
			
		||||
                lastNonTextLocation = location;
 | 
			
		||||
                location = Location.AttributeValue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // exiting StartTag.Attribute.AttributeValue, entering StartTag
 | 
			
		||||
            else if (location === Location.AttributeValue && cc === "\"") {
 | 
			
		||||
                output += "\"";
 | 
			
		||||
            else if (location === Location.AttributeValue && (cc === "\"" || cc === "'")) {
 | 
			
		||||
                output += cc;
 | 
			
		||||
                lastNonTextLocation = location;
 | 
			
		||||
                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
 | 
			
		||||
            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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import * as constants from "../constants";
 | 
			
		|||
 | 
			
		||||
export interface XmlFormattingOptions {
 | 
			
		||||
    editorOptions: FormattingOptions;
 | 
			
		||||
    enforcePrettySelfClosingTagOnFormat: boolean;
 | 
			
		||||
    newLine: string;
 | 
			
		||||
    removeCommentsOnMinify: boolean;
 | 
			
		||||
    splitAttributesOnFormat: boolean;
 | 
			
		||||
| 
						 | 
				
			
			@ -15,6 +16,7 @@ export class XmlFormattingOptionsFactory {
 | 
			
		|||
    static getXmlFormattingOptions(formattingOptions: FormattingOptions, document: TextDocument): XmlFormattingOptions {
 | 
			
		||||
        return {
 | 
			
		||||
            editorOptions: formattingOptions,
 | 
			
		||||
            enforcePrettySelfClosingTagOnFormat: Configuration.enforcePrettySelfClosingTagOnFormat(document.uri),
 | 
			
		||||
            newLine: (document.eol === EndOfLine.CRLF) ? "\r\n" : "\n",
 | 
			
		||||
            removeCommentsOnMinify: Configuration.removeCommentsOnMinify(document.uri),
 | 
			
		||||
            splitAttributesOnFormat: Configuration.splitAttributesOnFormat(document.uri),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,7 @@ describe("V2XmlFormatter", () => {
 | 
			
		|||
                insertSpaces: true,
 | 
			
		||||
                tabSize: 4
 | 
			
		||||
            },
 | 
			
		||||
            enforcePrettySelfClosingTagOnFormat: false,
 | 
			
		||||
            newLine: "\r\n",
 | 
			
		||||
            removeCommentsOnMinify: false,
 | 
			
		||||
            splitAttributesOnFormat: false,
 | 
			
		||||
| 
						 | 
				
			
			@ -51,6 +52,22 @@ describe("V2XmlFormatter", () => {
 | 
			
		|||
            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 { DOMParser } from "xmldom";
 | 
			
		||||
 | 
			
		||||
import { Configuration, NativeCommands } from "../common";
 | 
			
		||||
import { Configuration, NativeCommands, XmlTraverser } from "../common";
 | 
			
		||||
import * as constants from "../constants";
 | 
			
		||||
 | 
			
		||||
export class XmlTreeDataProvider implements TreeDataProvider<any> {
 | 
			
		||||
    private _onDidChangeTreeData: EventEmitter<any> = new EventEmitter<any>();
 | 
			
		||||
    private _xmlDocument: Document;
 | 
			
		||||
    private _xmlTraverser: XmlTraverser;
 | 
			
		||||
 | 
			
		||||
    constructor(private _context: ExtensionContext) {
 | 
			
		||||
        window.onDidChangeActiveTextEditor(() => {
 | 
			
		||||
| 
						 | 
				
			
			@ -38,13 +39,13 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
 | 
			
		|||
 | 
			
		||||
        const treeItem = new TreeItem(element.localName);
 | 
			
		||||
 | 
			
		||||
        if (!this._isElement(element)) {
 | 
			
		||||
        if (!this._xmlTraverser.isElement(element)) {
 | 
			
		||||
            treeItem.label = `${element.localName} = "${element.nodeValue}"`;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        else if (enableMetadata) {
 | 
			
		||||
            const childAttributes = this._getChildAttributeArray(<Element>element);
 | 
			
		||||
            const childElements = this._getChildElementArray(<Element>element);
 | 
			
		||||
            const childAttributes = this._xmlTraverser.getChildAttributeArray(<Element>element);
 | 
			
		||||
            const childElements = this._xmlTraverser.getChildElementArray(<Element>element);
 | 
			
		||||
            const totalChildren = (childAttributes.length + childElements.length);
 | 
			
		||||
 | 
			
		||||
            if (totalChildren > 0) {
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +65,7 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
 | 
			
		|||
                treeItem.label += ")";
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this._hasSimilarSiblings(<Element>element) && enableSync) {
 | 
			
		||||
            if (this._xmlTraverser.hasSimilarSiblings(<Element>element) && enableSync) {
 | 
			
		||||
                treeItem.label += ` [line ${(element as any).lineNumber}]`;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -88,8 +89,8 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
 | 
			
		|||
            this._refreshTree();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this._isElement(element)) {
 | 
			
		||||
            return [].concat(this._getChildAttributeArray(<Element>element), this._getChildElementArray(<Element>element));
 | 
			
		||||
        if (this._xmlTraverser.isElement(element)) {
 | 
			
		||||
            return [].concat(this._xmlTraverser.getChildAttributeArray(<Element>element), this._xmlTraverser.getChildElementArray(<Element>element));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        else if (this._xmlDocument) {
 | 
			
		||||
| 
						 | 
				
			
			@ -102,78 +103,21 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    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 element.parentNode;
 | 
			
		||||
        return element.parentNode || (element as any).ownerElement;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getNodeAtPosition(position: Position): Node {
 | 
			
		||||
        return this._getNodeAtPositionCore(position, this._xmlDocument.documentElement);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
        return this._xmlTraverser.getNodeAtPosition(position);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private _getIcon(element: Node): any {
 | 
			
		||||
        let type = "element";
 | 
			
		||||
 | 
			
		||||
        if (!this._isElement(element)) {
 | 
			
		||||
        if (!this._xmlTraverser.isElement(element)) {
 | 
			
		||||
            type = "attribute";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -185,20 +129,6 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
 | 
			
		|||
        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 {
 | 
			
		||||
        if (!this.activeEditor || this.activeEditor.document.languageId !== constants.languageIds.xml) {
 | 
			
		||||
            NativeCommands.setContext(constants.contextKeys.xmlTreeViewEnabled, false);
 | 
			
		||||
| 
						 | 
				
			
			@ -227,6 +157,11 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
 | 
			
		|||
            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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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 "./getCurrentXPath";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +1,2 @@
 | 
			
		|||
export * from "./xpath-builder";
 | 
			
		||||
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
 | 
			
		||||
            .replace("$(script)", editor.document.uri.fsPath)
 | 
			
		||||
            .replace("$(input)", inputFile.fsPath)
 | 
			
		||||
            .replace("$(project)", workspace.rootPath);
 | 
			
		||||
            .replace("$(project)", (workspace.workspaceFolders) ? workspace.workspaceFolders[0].uri.fsPath : "");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue