Add XPath Evaluation
This commit is contained in:
		
							parent
							
								
									6f0bbe3ed7
								
							
						
					
					
						commit
						1c083d15e3
					
				
					 7 changed files with 190 additions and 1 deletions
				
			
		| 
						 | 
					@ -14,6 +14,7 @@
 | 
				
			||||||
*In Progress*
 | 
					*In Progress*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Extension Settings
 | 
					## Extension Settings
 | 
				
			||||||
 | 
					* `xmlTools.enableXmlTreeView`: Enables the XML Tree View for XML documents.
 | 
				
			||||||
* `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.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										5
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -3078,6 +3078,11 @@
 | 
				
			||||||
            "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz",
 | 
				
			||||||
            "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk="
 | 
					            "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk="
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "xpath": {
 | 
				
			||||||
 | 
					            "version": "0.0.27",
 | 
				
			||||||
 | 
					            "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz",
 | 
				
			||||||
 | 
					            "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ=="
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "xqlint": {
 | 
					        "xqlint": {
 | 
				
			||||||
            "version": "0.4.0",
 | 
					            "version": "0.4.0",
 | 
				
			||||||
            "resolved": "https://registry.npmjs.org/xqlint/-/xqlint-0.4.0.tgz",
 | 
					            "resolved": "https://registry.npmjs.org/xqlint/-/xqlint-0.4.0.tgz",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -188,6 +188,7 @@
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "dependencies": {
 | 
					    "dependencies": {
 | 
				
			||||||
        "xmldom": "^0.1.27",
 | 
					        "xmldom": "^0.1.27",
 | 
				
			||||||
 | 
					        "xpath": "0.0.27",
 | 
				
			||||||
        "xqlint": "^0.4.0"
 | 
					        "xqlint": "^0.4.0"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
export const extensionPrefix = "xmlTools";
 | 
					export const extensionPrefix = "xmlTools";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export namespace commands {
 | 
					export namespace commands {
 | 
				
			||||||
 | 
					    export const evaluateXPath = "xmlTools.evaluateXPath";
 | 
				
			||||||
    export const setContext = "setContext";
 | 
					    export const setContext = "setContext";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,4 +11,11 @@ export namespace contextKeys {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export namespace configKeys {
 | 
					export namespace configKeys {
 | 
				
			||||||
    export const enableXmlTreeView = "enableXmlTreeView";
 | 
					    export const enableXmlTreeView = "enableXmlTreeView";
 | 
				
			||||||
 | 
					    export const ignoreDefaultNamespace = "ignoreDefaultNamespace";
 | 
				
			||||||
 | 
					    export const persistXPathQuery = "persistXPathQuery";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export namespace stateKeys {
 | 
				
			||||||
 | 
					    export const xpathQueryHistory = "xpathQueryHistory";
 | 
				
			||||||
 | 
					    export const xPathQueryLast = "xPathQueryLast";
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
import { languages, window, workspace, commands } from "vscode";
 | 
					import { languages, window, workspace, commands } from "vscode";
 | 
				
			||||||
import { ExtensionContext, TextEditor, TextEditorSelectionChangeEvent, WorkspaceConfiguration } from "vscode";
 | 
					import { ExtensionContext, Memento, TextEditor, TextEditorSelectionChangeEvent, WorkspaceConfiguration } from "vscode";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { XQueryCompletionItemProvider } from "./completion/xquery-completion-item-provider";
 | 
					import { XQueryCompletionItemProvider } from "./completion/xquery-completion-item-provider";
 | 
				
			||||||
import { FormatAsXmlCommandName, formatAsXml } from "./formatting/commands/formatAsXml";
 | 
					import { FormatAsXmlCommandName, formatAsXml } from "./formatting/commands/formatAsXml";
 | 
				
			||||||
| 
						 | 
					@ -8,10 +8,16 @@ import { XmlFormatterFactory } from "./formatting/xml-formatter";
 | 
				
			||||||
import { XmlFormattingEditProvider } from "./formatting/xml-formatting-edit-provider";
 | 
					import { XmlFormattingEditProvider } from "./formatting/xml-formatting-edit-provider";
 | 
				
			||||||
import { XQueryLinter } from "./linting/xquery-linter";
 | 
					import { XQueryLinter } from "./linting/xquery-linter";
 | 
				
			||||||
import { XmlTreeDataProvider } from "./tree-view/xml-tree-data-provider";
 | 
					import { XmlTreeDataProvider } from "./tree-view/xml-tree-data-provider";
 | 
				
			||||||
 | 
					import { evaluateXPath } from "./xpath/commands/evaluateXPath";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as constants from "./constants";
 | 
					import * as constants from "./constants";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ExtensionState: { global?: Memento, workspace?: Memento } = { };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function activate(context: ExtensionContext) {
 | 
					export function activate(context: ExtensionContext) {
 | 
				
			||||||
 | 
					    ExtensionState.global = context.globalState;
 | 
				
			||||||
 | 
					    ExtensionState.workspace = context.workspaceState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const config = workspace.getConfiguration(constants.extensionPrefix);
 | 
					    const config = workspace.getConfiguration(constants.extensionPrefix);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /* Completion Features */
 | 
					    /* Completion Features */
 | 
				
			||||||
| 
						 | 
					@ -39,6 +45,11 @@ export function activate(context: ExtensionContext) {
 | 
				
			||||||
    context.subscriptions.push(
 | 
					    context.subscriptions.push(
 | 
				
			||||||
        window.registerTreeDataProvider("xmlTreeView", new XmlTreeDataProvider(context))
 | 
					        window.registerTreeDataProvider("xmlTreeView", new XmlTreeDataProvider(context))
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* XPath Features */
 | 
				
			||||||
 | 
					    context.subscriptions.push(
 | 
				
			||||||
 | 
					        commands.registerTextEditorCommand(constants.commands.evaluateXPath, evaluateXPath)
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function deactivate() {
 | 
					export function deactivate() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										104
									
								
								src/xpath/commands/evaluateXPath.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/xpath/commands/evaluateXPath.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,104 @@
 | 
				
			||||||
 | 
					import { window, workspace } from "vscode";
 | 
				
			||||||
 | 
					import { TextEditor, TextEditorEdit, ViewColumn } from "vscode";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as constants from "../../constants";
 | 
				
			||||||
 | 
					import { ExtensionState } from "../../extension";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { EvaluatorResult, EvaluatorResultType, XPathEvaluator } from "../xpath-evaluator";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function evaluateXPath(editor: TextEditor, edit: TextEditorEdit): Promise<void> {
 | 
				
			||||||
 | 
					    const config = workspace.getConfiguration(constants.extensionPrefix);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // if there is no workspace, we will track queries in the global Memento
 | 
				
			||||||
 | 
					    const memento = ExtensionState.workspace || ExtensionState.global;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // get the xpath persistence setting
 | 
				
			||||||
 | 
					    const persistQueries = config.get<boolean>(constants.configKeys.persistXPathQuery, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // get the last query if there is one for this document
 | 
				
			||||||
 | 
					    // if not, try pulling the last query ran, regardless of document
 | 
				
			||||||
 | 
					    // NOTE: if the user has focus on the output channel when opening the xquery prompt, the channel is the "active" document
 | 
				
			||||||
 | 
					    const history = memento.get<HistoricQuery[]>(constants.stateKeys.xpathQueryHistory, new Array<HistoricQuery>());
 | 
				
			||||||
 | 
					    const globalLastQuery = memento.get<string>(constants.stateKeys.xPathQueryLast, "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const lastQuery = history.find(x => {
 | 
				
			||||||
 | 
					        return (x.uri === editor.document.uri.toString());
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // set the inital display value and prompt the user
 | 
				
			||||||
 | 
					    let query = (lastQuery) ? lastQuery.query : globalLastQuery;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    query = await window.showInputBox({
 | 
				
			||||||
 | 
					        placeHolder: "XPath Query",
 | 
				
			||||||
 | 
					        prompt: "Please enter an XPath query to evaluate.",
 | 
				
			||||||
 | 
					        value: query
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // showInputBox() will return undefined if the user dimissed the prompt
 | 
				
			||||||
 | 
					    if (!query) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const ignoreDefaultNamespace = config.get<boolean>(constants.configKeys.ignoreDefaultNamespace, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // run the query
 | 
				
			||||||
 | 
					    const xml = editor.document.getText();
 | 
				
			||||||
 | 
					    let evalResult: EvaluatorResult;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        evalResult = XPathEvaluator.evaluate(query, xml, ignoreDefaultNamespace);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    catch (error) {
 | 
				
			||||||
 | 
					        console.error(error);
 | 
				
			||||||
 | 
					        window.showErrorMessage(`Something went wrong while evaluating the XPath: ${error}`);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // show the results to the user
 | 
				
			||||||
 | 
					    const outputChannel = window.createOutputChannel("XPath Results");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    outputChannel.clear();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    outputChannel.appendLine(`XPath Query: ${query}`);
 | 
				
			||||||
 | 
					    outputChannel.append("\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (evalResult.type === EvaluatorResultType.NODE_COLLECTION) {
 | 
				
			||||||
 | 
					        (evalResult.result as Node[]).forEach((node: any) => {
 | 
				
			||||||
 | 
					            outputChannel.appendLine(`[Line ${node.lineNumber}] ${node.localName}: ${node.textContent}`);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    else {
 | 
				
			||||||
 | 
					        outputChannel.appendLine(`[Result]: ${evalResult.result}`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    outputChannel.show(ViewColumn.Three);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (persistQueries) {
 | 
				
			||||||
 | 
					        const historicQuery = new HistoricQuery(editor.document.uri.toString(), query);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const affectedIndex = history.findIndex(x => x.uri === historicQuery.uri);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (affectedIndex === -1) {
 | 
				
			||||||
 | 
					            history.push(historicQuery);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            history[affectedIndex].query = query;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        memento.update(constants.stateKeys.xpathQueryHistory, history);
 | 
				
			||||||
 | 
					        memento.update(constants.stateKeys.xPathQueryLast, query);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HistoricQuery {
 | 
				
			||||||
 | 
					    constructor(uri: string, query: string) {
 | 
				
			||||||
 | 
					        this.uri = uri;
 | 
				
			||||||
 | 
					        this.query = query;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uri: string;
 | 
				
			||||||
 | 
					    query: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										59
									
								
								src/xpath/xpath-evaluator.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/xpath/xpath-evaluator.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,59 @@
 | 
				
			||||||
 | 
					import * as xpath from "xpath";
 | 
				
			||||||
 | 
					import { SelectedValue, XPathSelect } from "xpath";
 | 
				
			||||||
 | 
					import { DOMParser } from "xmldom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class EvaluatorResult {
 | 
				
			||||||
 | 
					    type: EvaluatorResultType;
 | 
				
			||||||
 | 
					    result: Node[] | number | string | boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class EvaluatorResultType {
 | 
				
			||||||
 | 
					    static SCALAR_TYPE = 0;
 | 
				
			||||||
 | 
					    static NODE_COLLECTION = 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class XPathEvaluator {
 | 
				
			||||||
 | 
					    static evaluate(query: string, xml: string, ignoreDefaultNamespace: boolean): EvaluatorResult {
 | 
				
			||||||
 | 
					        if (ignoreDefaultNamespace) {
 | 
				
			||||||
 | 
					            xml = xml.replace(/xmlns=".+"/g, (match: string) => {
 | 
				
			||||||
 | 
					                return match.replace(/xmlns/g, "xmlns:default");
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const nodes = new Array<Node>();
 | 
				
			||||||
 | 
					        const xdoc: Document = new DOMParser().parseFromString(xml, "text/xml");
 | 
				
			||||||
 | 
					        const resolver = (xpath as any).createNSResolver(xdoc);
 | 
				
			||||||
 | 
					        const xPathResult = xpath.evaluate(query, xdoc, resolver, 0, null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const evaluatorResult = new EvaluatorResult();
 | 
				
			||||||
 | 
					        evaluatorResult.type = EvaluatorResultType.SCALAR_TYPE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        switch (xPathResult.resultType) {
 | 
				
			||||||
 | 
					            case xPathResult.NUMBER_TYPE:
 | 
				
			||||||
 | 
					                evaluatorResult.result = xPathResult.numberValue;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case xPathResult.STRING_TYPE:
 | 
				
			||||||
 | 
					                evaluatorResult.result = xPathResult.stringValue;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case xPathResult.BOOLEAN_TYPE:
 | 
				
			||||||
 | 
					                evaluatorResult.result = xPathResult.booleanValue;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case xPathResult.UNORDERED_NODE_ITERATOR_TYPE:
 | 
				
			||||||
 | 
					            case xPathResult.ORDERED_NODE_ITERATOR_TYPE:
 | 
				
			||||||
 | 
					                evaluatorResult.result = xPathResult.booleanValue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let node: Node;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                while (node = xPathResult.iterateNext()) {
 | 
				
			||||||
 | 
					                    nodes.push(node);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                evaluatorResult.result = nodes;
 | 
				
			||||||
 | 
					                evaluatorResult.type = EvaluatorResultType.NODE_COLLECTION;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return evaluatorResult;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue