Add XPath Evaluation
This commit is contained in:
parent
6f0bbe3ed7
commit
1c083d15e3
@ -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…
Reference in New Issue
Block a user