Merge pull request #141 from DotJoshJohnson/vnext

v2 Rewrite
This commit is contained in:
Josh Johnson 2018-05-25 00:22:20 -04:00 committed by GitHub
commit e60b94b384
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
85 changed files with 6570 additions and 4611 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 B

5
.gitignore vendored
View file

@ -1,3 +1,4 @@
out
node_modules
src/**/*.js
**/*.vsix
.vscode-test/
.vsix

View file

@ -1,10 +1,10 @@
notifications:
email: false
sudo: false
install:
- npm install
script:
- gulp build
notifications:
email: false
sudo: false
install:
- npm install
script:
- npm run test

41
.vscode/launch.json vendored
View file

@ -1,15 +1,28 @@
// A launch configuration that compiles the extension and then opens it inside a new window
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}"
],
"preLaunchTask": "build"
}
]
}
"version": "0.1.0",
"configurations": [
{
"name": "Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [ "${workspaceRoot}/out/**/*.js" ],
"preLaunchTask": "npm: watch"
},
{
"name": "Extension Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
"stopOnEntry": false,
"sourceMaps": true,
"outFiles": [ "${workspaceRoot}/out/test/**/*.js" ],
"preLaunchTask": "npm: watch"
}
]
}

14
.vscode/settings.json vendored
View file

@ -1,11 +1,9 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.exclude": {
"**/.git": true,
"**/.DS_Store": true,
"**/*.vsix": true,
"**/*.js": {"when": "$(basename).ts"}
}
,
"typescript.tsdk": "./node_modules/typescript/lib"
"files.exclude": {
"out": false // set this to true to hide the "out" folder with the compiled JS files
},
"search.exclude": {
"out": true // set this to false to include "out" folder in search results
}
}

29
.vscode/tasks.json vendored
View file

@ -1,21 +1,20 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "0.1.0",
"command": "gulp",
"isShellCommand": true,
"args": [
"--no-color"
],
"version": "2.0.0",
"tasks": [
{
"taskName": "build",
"args": [],
"isBuildCommand": true,
"isWatching": false,
"problemMatcher": [
"$lessCompile",
"$tsc",
"$jshint"
]
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

View file

@ -1,8 +1,8 @@
tsconfig.json
CONTRIBUTING.md
.gitignore
.vscode
resources/wiki-images
typings
**/*.ts
**/*.vsix
.vscode/**
.vscode-test/**
out/test/**
out/**/*.map
src/**
.gitignore
tsconfig.json
vsc-extension-quickstart.md

View file

@ -1,54 +0,0 @@
# Contribution Guide
Thanks for helping out! This guide provides some guidelines on coding styles and architecture for this extension.
Keep in mind that these are guidelines, not rules. Use your best judgement and feel free to make suggestions!
## Workflow
Please use the follow general workflow when making contributions to the code:
1. Fork the repository.
2. Create a feature branch and make your changes.
3. Create a pull request to request that your commits be merged to `master`.
## Building the Extension
1. Clone the repository locally and run `npm install` from the vscode-xml directory.
2. Run the `build` task in VS Code (`CTRL + SHIFT + B`) or start extension debugging (`F5`), which will run the `build` task for you.
## Style Guide
### Quotes
Use double quotes for strings whenever possible.
### Imports
As a general rule, always import the `vscode` namespace using the `vsc` alias for consistency.
### Static Classes
When possible, try to use a static class to wrap utility functions so you are importing the class, not just a function.
For libraries that do not follow this construct (such as the `xpath` module), *always* import the module using an alias (`import * as xpath from "xpath").
### Constants
Where applicable, try to use constants instead of inline strings and numbers.
## Implicit Types
Moving forward, the compiler is instructed (via tsconfig.json) to throw warnings if any expressions imply an `any` type. In other words, always use type declarations where applicable so it is clear what type is being used.
There is an exception to this guideline. If you are using a thrid party library that does not have a *.d.ts file available, you do not need to write one. Just ensure the API is documented (either in this repo or in the library's repo).
## Folder Structure
All TypeScript files should reside under the top `src` folder. Under this, there are several subfolders and top-level files defined below:
### providers
This folder contains any classes that implement provider interfaces from the `vscode` namespace. This folder also contains any code that works directly with the
APIs exposed by the `vscode` namespace, such as user interaction or configuration access. Aside for the `vscode` module, no code in this folder should be directly dependent on
any external NodeJS modules or libraries.
### services
This folder contains any classes that perform actions/logic required by the providers mentioned above. Services should not be dependent on the `vscode` namespace and *can* be
dependent on external Node modules and libraries.
### utils
This folder contains any utility classes/functions.
### Commands.ts
This file acts as an interface to all registered commands in the extension. If a substantial amount of code is required to implement a command, it should be moved to a provider and/or service.
### Extension.ts
Previously named `main.ts`, this is the primary entry point to the extension. Anything that needs done on activation or deactivation of the extension is done here. Both the workspace and global `Memento` instances are exposed from this module, which can be used by providers as needed.

View file

@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

View file

@ -10,6 +10,22 @@
* [XQuery Execution](https://github.com/DotJoshJohnson/vscode-xml/wiki/xquery-script-execution)
* [XQuery Code Completion](https://github.com/DotJoshJohnson/vscode-xml/wiki/xquery-code-completion)
## Requirements
* VS Code `1.22.2` or higher
## Extension Settings
* **`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.ignoreDefaultNamespace`:** Ignore default xmlns attributes when evaluating XPath.
* **`xmlTools.persistXPathQuery`:** Remember the last XPath query used.
* **`xmlTools.removeCommentsOnMinify`:** Remove XML comments during minification.
* **`xmlTools.splitAttributesOnFormat`:** Put each attribute on a new line when formatting XML. Overrides `xmlTools.splitXmlsOnFormat` if set to `true`. (V2 Formatter Only)
* **`xmlTools.splitXmlnsOnFormat`:** Put each xmlns attribute on a new line when formatting XML.
* **`xmlTools.xmlFormatterImplementation`:** Supported XML Formatters: `classic`, `v2`.
* **`xmlTools.xqueryExecutionArguments`:** Arguments to be passed to the XQuery execution engine.
* **`xmlTools.xqueryExecutionEngine`:** The full path to the executable to run when executing XQuery scripts.
## Release Notes
Detailed release notes are available [here](https://github.com/DotJoshJohnson/vscode-xml/releases).
@ -19,4 +35,4 @@ Run into a bug? Report it [here](https://github.com/DotJoshJohnson/vscode-xml/is
## Icon Credits
Icons used in the XML Tree View are used under the Creative Commons 3.0 BY license.
* "Code" icon by Dave Gandy from www.flaticon.com
* "At" icon by FreePik from www.flaticon.com
* "At" icon by FreePik from www.flaticon.com

View file

@ -1,9 +0,0 @@
var gulp = require("gulp");
var shell = require("gulp-shell");
gulp.task("compile-typescript", function () {
gulp.src("package.json").pipe(shell("tsc"));
});
gulp.task("build", ["compile-typescript"]);

View file

@ -1,11 +1,23 @@
{
"comments": {
"lineComment": "//",
"blockComment": ["/*", "*/"]
},
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
]
{
"comments": {
"lineComment": "//",
"blockComment": [
"/*",
"*/"
]
},
"brackets": [
[
"{",
"}"
],
[
"[",
"]"
],
[
"(",
")"
]
]
}

File diff suppressed because one or more lines are too long

3454
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,47 +1,51 @@
{
"name": "xml",
"displayName": "XML Tools",
"description": "XML Formatting, XQuery, and XPath Tools for Visual Studio Code",
"version": "1.9.2",
"publisher": "DotJoshJohnson",
"author": {
"name": "Josh Johnson",
"url": "https://github.com/DotJoshJohnson"
},
"icon": "resources/xml.png",
"galleryBanner": {
"color": "#FFFFFF",
"theme": "light"
},
"homepage": "https://github.com/DotJoshJohnson/vscode-xml",
"repository": {
"type": "git",
"url": "https://github.com/DotJoshJohnson/vscode-xml.git"
},
"bugs": {
"url": "https://github.com/DotJoshJohnson/vscode-xml/issues"
},
"engines": {
"vscode": "^1.13.0",
"node": "^0.12.0"
},
"categories": [
"Languages",
"name": "xml",
"displayName": "XML Tools",
"description": "XML Formatting, XQuery, and XPath Tools for Visual Studio Code",
"version": "2.0.0-preview.3",
"preview": true,
"publisher": "DotJoshJohnson",
"author": {
"name": "Josh Johnson",
"url": "https://github.com/DotJoshJohnson"
},
"galleryBanner": {
"color": "#FFFFFF",
"theme": "light"
},
"homepage": "https://github.com/DotJoshJohnson/vscode-xml",
"repository": {
"type": "git",
"url": "https://github.com/DotJoshJohnson/vscode-xml.git"
},
"bugs": {
"url": "https://github.com/DotJoshJohnson/vscode-xml/issues"
},
"engines": {
"vscode": "^1.22.2"
},
"categories": [
"Formatters",
"Languages",
"Linters",
"Other",
"Formatters"
],
"main": "./src/Extension",
"contributes": {
"commands": [
{
"command": "xmlTools.minifyXml",
"title": "XML Tools: Minify XML"
},
{
"command": "xmlTools.evaluateXPath",
"title": "XML Tools: Evaluate XPath"
},
"Other"
],
"activationEvents": [
"onCommand:xmlTools.evaluateXPath",
"onCommand:xmlTools.executeXQuery",
"onCommand:xmlTools.formatAsXml",
"onCommand:xmlTools.minifyXml",
"onLanguage:xml",
"onLanguage:xquery",
"onLanguage:xsl"
],
"main": "./out/extension",
"contributes": {
"commands": [
{
"command": "xmlTools.evaluateXPath",
"title": "XML Tools: Evaluate XPath"
},
{
"command": "xmlTools.executeXQuery",
"title": "XML Tools: Execute XQuery"
@ -49,100 +53,175 @@
{
"command": "xmlTools.formatAsXml",
"title": "XML Tools: Format as XML"
},
{
"command": "xmlTools.minifyXml",
"title": "XML Tools: Minify XML"
}
],
],
"configuration": {
"title": "XML Tools Configuration",
"type": "object",
"properties": {
"xmlTools.persistXPathQuery": {
"xmlTools.enableXmlTreeView": {
"type": "boolean",
"default": true,
"description": "Remember the last XPath query used."
"description": "Enables the XML Document view in the explorer for XML documents.",
"scope": "window"
},
"xmlTools.removeCommentsOnMinify": {
"type": "boolean",
"default": false,
"description": "Remove XML comments when XML is minified."
},
"xmlTools.splitXmlnsOnFormat": {
"xmlTools.enableXmlTreeViewMetadata": {
"type": "boolean",
"default": true,
"description": "Put each xmlns attribute on a new line when fromatting XML."
"description": "Enables attribute and child element counts in the XML Document view.",
"scope": "window"
},
"xmlTools.xqueryExecutionEngine": {
"type": "string",
"default": "",
"description": "The full path to the execution engine executable."
},
"xmlTools.xqueryExecutionArguments": {
"type": "array",
"default": ["-xquery", "$(script)", "-in", "$(input)", "-out", "$(input).output.xml"],
"description": "Arguments to be passed to the execution engine. '$(script)' and '$(input)' refer to the XQuery script and input XML file, respectively."
"xmlTools.enableXmlTreeViewCursorSync": {
"type": "boolean",
"default": true,
"description": "Enables auto-reveal of elements in the XML Document view when a start tag is clicked in the editor.",
"scope": "window"
},
"xmlTools.ignoreDefaultNamespace": {
"type": "boolean",
"default": true,
"description": "Ignores default xmlns attribute when evaluating XPath."
"description": "Ignore default xmlns attributes when evaluating XPath.",
"scope": "window"
},
"xmlTools.persistXPathQuery": {
"type": "boolean",
"default": true,
"description": "Remember the last XPath query used.",
"scope": "window"
},
"xmlTools.removeCommentsOnMinify": {
"type": "boolean",
"default": false,
"description": "Remove XML comments during minification.",
"scope": "resource"
},
"xmlTools.splitAttributesOnFormat": {
"type": "boolean",
"default": false,
"description": "Put each attribute on a new line when formatting XML. Overrides `xmlTools.splitXmlsOnFormat` if set to `true`.",
"scope": "resource"
},
"xmlTools.splitXmlnsOnFormat": {
"type": "boolean",
"default": true,
"description": "Put each xmlns attribute on a new line when formatting XML.",
"scope": "resource"
},
"xmlTools.xmlFormatterImplementation": {
"type": "string",
"default": "v2",
"description": "Supported XML Formatters: classic",
"scope": "window"
},
"xmlTools.xqueryExecutionArguments": {
"type": "array",
"default": [
"-xquery",
"$(script)",
"-in",
"$(input)",
"-out",
"$(input.output.xml"
],
"description": "Arguments to be passed to the XQuery execution engine.",
"scope": "window"
},
"xmlTools.xqueryExecutionEngine": {
"type": "string",
"default": "",
"description": "The full path to the executable to run when executing XQuery scripts.",
"scope": "window"
},
"xmlTools.xqueryExecutionInputLimit": {
"type": "integer",
"default": 100,
"description": "The maximum number of input files to enumerate when executing XQuery scripts.",
"scope": "window"
},
"xmlTools.xqueryExecutionInputSearchPattern": {
"type": "string",
"default": "**/*.xml",
"description": "The pattern used to search for input XML files when executing XQuery scripts.",
"scope": "window"
}
}
},
"keybindings": [
{
"key": "ctrl+shift+alt+b",
"command": "xmlTools.formatXml"
},
{
"key": "ctrl+shift+alt+x",
"command": "xmlTools.evaluateXPath"
}
],
"languages": [
{
"id": "xquery",
"aliases": ["XQuery", "xquery"],
"extensions": [".xq",".xql",".xqm",".xqy",".xquery"],
"configuration": "./languages/xquery/xquery.json"
}
],
"grammars": [
{
"language": "xquery",
"scopeName": "source.xquery",
"path": "./languages/xquery/xquery.tmLanguage"
"path": "./languages/xquery/xquery.tmLanguage",
"scopeName": "source.xquery"
}
],
"keybindings": [
{
"key": "ctrl+shift+alt+x",
"command": "xmlTools.evaluateXPath"
},
{
"key": "ctrl+shift+alt+b",
"command": "xmlTools.formatAsXml"
}
],
"languages": [
{
"id": "xquery",
"aliases": [
"XQuery",
"xquery"
],
"extensions": [
".xq",
".xql",
".xqm",
".xqy",
".xquery"
],
"configuration": "./languages/xquery/xquery.json"
}
],
"menus": {
"editor/context": [
{
"command": "xmlTools.minifyXml",
"group": "1_modification@100",
"when": "editorLangId == 'xml'"
}
]
},
"views": {
"explorer": [
{
"id": "xmlTreeView",
"name": "XML Document"
"name": "XML Document",
"when": "xmlTreeViewEnabled"
}
]
}
},
"activationEvents": [
"onLanguage:xml",
"onLanguage:xsl",
"onLanguage:xquery",
"onCommand:xmlTools.minifyXml",
"onCommand:xmlTools.evaluateXPath",
"onCommand:xmlTools.executeXQuery",
"onCommand:xmlTools.formatAsXml"
],
"devDependencies": {
"vscode": "^1.1.0",
"typescript": "^2.3.4",
"gulp": "^3.9.0",
"gulp-shell": "^0.5.1"
},
"dependencies": {
"xmldom": "^0.1.22",
"xpath": "^0.0.9",
"xqlint": "^0.2.9"
},
},
"scripts": {
"vscode:prepublish": "tsc",
"postinstall": "node ./node_modules/vscode/bin/install"
"vscode:prepublish": "npm run compile",
"compile": "npm run lint && tsc -p ./",
"watch": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "npm run compile && mocha ./out/test/**/*.js",
"lint": "tslint -p tslint.json --fix"
},
"devDependencies": {
"@types/mocha": "^2.2.42",
"@types/node": "^7.0.43",
"@types/xmldom": "^0.1.29",
"tslint": "^5.9.1",
"typescript": "^2.6.1",
"vscode": "^1.1.16"
},
"dependencies": {
"xmldom": "^0.1.27",
"xpath": "0.0.27",
"xqlint": "^0.4.1"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -1,69 +0,0 @@
import * as vsc from "vscode";
import * as ext from "./Extension";
import * as xpath from "xpath";
import { RangeUtil } from "./utils/RangeUtil";
import { XmlFormatter } from "./services/XmlFormatter";
import { XPathFeatureProvider } from "./providers/XPath";
import { XQueryExecutionProvider } from "./providers/Execution";
import { XmlFormattingEditProvider } from "./providers/Formatting";
const CFG_SECTION: string = "xmlTools";
const CFG_REMOVE_COMMENTS: string = "removeCommentsOnMinify";
export class TextEditorCommands {
static minifyXml(editor: vsc.TextEditor, edit: vsc.TextEditorEdit): void {
let removeComments: boolean = vsc.workspace.getConfiguration(CFG_SECTION).get<boolean>(CFG_REMOVE_COMMENTS, false);
let range: vsc.Range = RangeUtil.getRangeForDocument(editor.document);
let formatter: XmlFormatter = new XmlFormatter();
let xml: string = formatter.minify(editor.document.getText());
edit.replace(range, xml);
}
static evaluateXPath(editor: vsc.TextEditor, edit: vsc.TextEditorEdit): void {
XPathFeatureProvider.evaluateXPathAsync(editor, edit);
}
static executeXQuery(editor: vsc.TextEditor, edit: vsc.TextEditorEdit): void {
XQueryExecutionProvider.executeXQueryAsync(editor);
}
static formatAsXml(editor: vsc.TextEditor, edit: vsc.TextEditorEdit): void {
let edits: vsc.TextEdit[];
let formattingEditProvider = new XmlFormattingEditProvider();
let formattingOptions: vsc.FormattingOptions = {
insertSpaces: (editor.options.insertSpaces as boolean),
tabSize: (editor.options.tabSize as number)
};
// if the user has selected text, only format what is selected
// otherwise, attempt to format the entire document
if (!editor.selection.isEmpty) {
edits = formattingEditProvider.provideDocumentRangeFormattingEdits(editor.document, editor.selection, formattingOptions);
}
else {
edits = formattingEditProvider.provideDocumentFormattingEdits(editor.document, formattingOptions);
}
if (edits) {
for (let i = 0; i < edits.length; i++) {
editor.edit(async (editBuilder) => {
editBuilder.replace(edits[i].range, edits[i].newText);
// wiggle the cursor to deselect the formatted XML (is there a non-hacky way to go about this?)
await vsc.commands.executeCommand("cursorMove", {
to: "left",
by: "character"
});
await vsc.commands.executeCommand("cursorMove", {
to: "right",
by: "character"
});
});
}
}
}
}

View file

@ -1,76 +0,0 @@
import * as vsc from "vscode";
import { TextEditorCommands } from "./Commands";
import { XmlFormattingEditProvider } from "./providers/Formatting";
import { XQueryLintingFeatureProvider } from "./providers/Linting";
import { XQueryCompletionItemProvider } from "./providers/Completion";
import { XmlTreeViewDataProvider } from "./providers/XmlTreeView";
export var GlobalState: vsc.Memento;
export var WorkspaceState: vsc.Memento;
const LANG_XML: string = "xml";
const LANG_XSL: string = "xsl";
const LANG_XQUERY: string = "xquery;"
const MEM_QUERY_HISTORY: string = "xpathQueryHistory";
export function activate(ctx: vsc.ExtensionContext) {
console.log("activate extension");
// expose global and workspace state to the entire extension
GlobalState = ctx.globalState;
WorkspaceState = ctx.workspaceState;
// register palette commands
ctx.subscriptions.push(
vsc.commands.registerTextEditorCommand("xmlTools.minifyXml", TextEditorCommands.minifyXml),
vsc.commands.registerTextEditorCommand("xmlTools.evaluateXPath", TextEditorCommands.evaluateXPath),
vsc.commands.registerTextEditorCommand("xmlTools.executeXQuery", TextEditorCommands.executeXQuery),
vsc.commands.registerTextEditorCommand("xmlTools.formatAsXml", TextEditorCommands.formatAsXml)
);
// register language feature providers
ctx.subscriptions.push(
vsc.languages.registerDocumentFormattingEditProvider([LANG_XML, LANG_XSL], new XmlFormattingEditProvider()),
vsc.languages.registerDocumentRangeFormattingEditProvider([LANG_XML, LANG_XSL], new XmlFormattingEditProvider()),
vsc.languages.registerCompletionItemProvider(LANG_XQUERY, new XQueryCompletionItemProvider(), ":", "$")
);
// listen to editor events (for linting)
ctx.subscriptions.push(
vsc.window.onDidChangeActiveTextEditor(_handleChangeActiveTextEditor),
vsc.window.onDidChangeTextEditorSelection(_handleChangeTextEditorSelection)
);
// add views
ctx.subscriptions.push(
vsc.window.registerTreeDataProvider("xmlTreeView", new XmlTreeViewDataProvider(ctx))
);
}
export function deactivate() {
// clean up xpath history
let memento: vsc.Memento = WorkspaceState || GlobalState;
let history = memento.get<any[]>(MEM_QUERY_HISTORY, []);
history.splice(0);
memento.update(MEM_QUERY_HISTORY, history);
}
function _handleContextChange(editor: vsc.TextEditor): void {
if (!editor || !editor.document) {
return;
}
switch (editor.document.languageId) {
case "xquery":
XQueryLintingFeatureProvider.provideXQueryDiagnostics(editor);
break;
}
}
function _handleChangeActiveTextEditor(editor: vsc.TextEditor): void {
_handleContextChange(editor);
}
function _handleChangeTextEditorSelection(e: vsc.TextEditorSelectionChangeEvent): void {
_handleContextChange(e.textEditor);
}

View file

@ -0,0 +1,65 @@
import { workspace, Uri } from "vscode";
const ExtensionTopLevelSection = "xmlTools";
export class Configuration {
static get enableXmlTreeView(): boolean {
return this._getForWindow<boolean>("enableXmlTreeView");
}
static get enableXmlTreeViewMetadata(): boolean {
return this._getForWindow<boolean>("enableXmlTreeViewMetadata");
}
static get enableXmlTreeViewCursorSync(): boolean {
return this._getForWindow<boolean>("enableXmlTreeViewCursorSync");
}
static get ignoreDefaultNamespace(): boolean {
return this._getForWindow<boolean>("ignoreDefaultNamespace");
}
static get persistXPathQuery(): boolean {
return this._getForWindow<boolean>("persistXPathQuery");
}
static get xmlFormatterImplementation(): string {
return this._getForWindow<string>("xmlFormatterImplementation");
}
static get xqueryExecutionArguments(): string[] {
return this._getForWindow<string[]>("xqueryExecutionArguments");
}
static get xqueryExecutionEngine(): string {
return this._getForWindow<string>("xqueryExecutionEngine");
}
static get xqueryExecutionInputLimit(): number {
return this._getForWindow<number>("xqueryExecutionInputLimit");
}
static get xqueryExecutionInputSearchPattern(): string {
return this._getForWindow<string>("xqueryExecutionInputSearchPattern");
}
static removeCommentsOnMinify(resource: Uri): boolean {
return this._getForResource<boolean>("removeCommentsOnMinify", resource);
}
static splitAttributesOnFormat(resource: Uri): boolean {
return this._getForResource<boolean>("splitAttributesOnFormat", resource);
}
static splitXmlnsOnFormat(resource: Uri): boolean {
return this._getForResource<boolean>("splitXmlnsOnFormat", resource);
}
private static _getForResource<T>(section: string, resource: Uri): T {
return workspace.getConfiguration(ExtensionTopLevelSection, resource).get<T>(section);
}
private static _getForWindow<T>(section: string): T {
return workspace.getConfiguration(ExtensionTopLevelSection).get<T>(section);
}
}

View file

@ -0,0 +1,10 @@
import { DocumentFilter } from "vscode";
import * as constants from "../constants";
export function createDocumentSelector(language: string): DocumentFilter[] {
return [
{ language, scheme: constants.uriSchemes.file },
{ language, scheme: constants.uriSchemes.untitled },
];
}

View file

@ -0,0 +1,17 @@
import { ExtensionContext, Memento } from "vscode";
export class ExtensionState {
private static _context: ExtensionContext;
static get global(): Memento {
return this._context.globalState;
}
static get workspace(): Memento {
return this._context.workspaceState;
}
static configure(context: ExtensionContext): void {
this._context = context;
}
}

4
src/common/index.ts Normal file
View file

@ -0,0 +1,4 @@
export * from "./configuration";
export * from "./create-document-selector";
export * from "./extension-state";
export * from "./native-commands";

View file

@ -0,0 +1,18 @@
import { commands } from "vscode";
export class NativeCommands {
static async cursorMove(to: string, by: string): Promise<void> {
await commands.executeCommand("cursorMove", {
to: to,
by: by
});
}
static openGlobalSettings(): void {
commands.executeCommand("workbench.action.openGlobalSettings");
}
static setContext(key: string, value: any): void {
commands.executeCommand("setContext", key, value);
}
}

1
src/completion/index.ts Normal file
View file

@ -0,0 +1 @@
export * from "./xquery-completion-item-provider";

View file

@ -0,0 +1,51 @@
import { CompletionItem, CompletionItemKind, CompletionItemProvider, Position, TextDocument } from "vscode";
const XQLint = require("xqlint").XQLint;
export class XQueryCompletionItemProvider implements CompletionItemProvider {
provideCompletionItems(document: TextDocument, position: Position): CompletionItem[] {
const completionItems = new Array<CompletionItem>();
const linter = new XQLint(document.getText());
linter.getCompletions({ line: position.line, col: position.character }).forEach((x: any) => {
completionItems.push(this._getCompletionItem(x));
});
return completionItems;
}
private _getCompletionItem(xqLintCompletionItem: any): CompletionItem {
const completionItem = new CompletionItem(xqLintCompletionItem.name);
completionItem.insertText = xqLintCompletionItem.value;
switch (xqLintCompletionItem.meta) {
// functions (always qualified with a colon)
case "function":
completionItem.kind = CompletionItemKind.Function;
const funcStart = (xqLintCompletionItem.value.indexOf(":") + 1);
const funcEnd = xqLintCompletionItem.value.indexOf("(");
completionItem.insertText = xqLintCompletionItem.value.substring(funcStart, funcEnd);
break;
// variables and parameters (always qualified with a dollar sign)
case "Let binding":
case "Local variable":
case "Window variable":
case "Function parameter":
completionItem.kind = CompletionItemKind.Variable;
completionItem.insertText = xqLintCompletionItem.value.substring(1);
break;
// everything else
default:
completionItem.kind = CompletionItemKind.Text;
break;
}
return completionItem;
}
}

43
src/constants.ts Normal file
View file

@ -0,0 +1,43 @@
export namespace commands {
export const evaluateXPath = "xmlTools.evaluateXPath";
export const executeXQuery = "xmlTools.executeXQuery";
export const formatAsXml = "xmlTools.formatAsXml";
export const minifyXml = "xmlTools.minifyXml";
}
export namespace contextKeys {
export const xmlTreeViewEnabled = "xmlTreeViewEnabled";
}
export namespace diagnosticCollections {
export const xquery = "XQueryDiagnostics";
}
export namespace languageIds {
export const xml = "xml";
export const xsd = "xsd";
export const xquery = "xquery";
}
export namespace nativeCommands {
export const revealLine = "revealLine";
}
export namespace stateKeys {
export const xpathQueryHistory = "xpathQueryHistory";
export const xPathQueryLast = "xPathQueryLast";
}
export namespace uriSchemes {
export const file = "file";
export const untitled = "untitled";
}
export namespace views {
export const xmlTreeView = "xmlTreeView";
}
export namespace xmlFormatterImplementations {
export const classic = "classic";
export const v2 = "v2";
}

100
src/extension.ts Normal file
View file

@ -0,0 +1,100 @@
import {
commands, languages, window, workspace, ExtensionContext, Memento,
TextEditor, TextEditorSelectionChangeEvent, TextEditorSelectionChangeKind
} from "vscode";
import { createDocumentSelector, ExtensionState, Configuration } from "./common";
import { XQueryCompletionItemProvider } from "./completion";
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 { executeXQuery } from "./xquery-execution/commands";
import * as constants from "./constants";
export function activate(context: ExtensionContext) {
ExtensionState.configure(context);
const xmlXsdDocSelector = [...createDocumentSelector(constants.languageIds.xml), ...createDocumentSelector(constants.languageIds.xsd)];
const xqueryDocSelector = createDocumentSelector(constants.languageIds.xquery);
/* Completion Features */
context.subscriptions.push(
languages.registerCompletionItemProvider(xqueryDocSelector, new XQueryCompletionItemProvider(), ":", "$")
);
/* Formatting Features */
const xmlFormattingEditProvider = new XmlFormattingEditProvider(XmlFormatterFactory.getXmlFormatter());
context.subscriptions.push(
commands.registerTextEditorCommand(constants.commands.formatAsXml, formatAsXml),
commands.registerTextEditorCommand(constants.commands.minifyXml, minifyXml),
languages.registerDocumentFormattingEditProvider(xmlXsdDocSelector, xmlFormattingEditProvider),
languages.registerDocumentRangeFormattingEditProvider(xmlXsdDocSelector, xmlFormattingEditProvider)
);
/* Linting Features */
context.subscriptions.push(
window.onDidChangeActiveTextEditor(_handleChangeActiveTextEditor),
window.onDidChangeTextEditorSelection(_handleChangeTextEditorSelection)
);
/* Tree View Features */
const treeViewDataProvider = new XmlTreeDataProvider(context);
const treeView = window.createTreeView<Node>(constants.views.xmlTreeView, {
treeDataProvider: treeViewDataProvider
});
if (Configuration.enableXmlTreeViewCursorSync) {
window.onDidChangeTextEditorSelection(x => {
if (x.kind === TextEditorSelectionChangeKind.Mouse && x.selections.length > 0) {
treeView.reveal(treeViewDataProvider.getNodeAtPosition(x.selections[0].start));
}
});
}
context.subscriptions.push(
treeView
);
/* XPath Features */
context.subscriptions.push(
commands.registerTextEditorCommand(constants.commands.evaluateXPath, evaluateXPath)
);
/* XQuery Features */
context.subscriptions.push(
commands.registerTextEditorCommand(constants.commands.executeXQuery, executeXQuery)
);
}
export function deactivate() {
// do nothing
}
function _handleContextChange(editor: TextEditor): void {
const supportedSchemes = [constants.uriSchemes.file, constants.uriSchemes.untitled];
if (!editor || !editor.document || supportedSchemes.indexOf(editor.document.uri.scheme) === -1) {
return;
}
switch (editor.document.languageId) {
case constants.languageIds.xquery:
languages
.createDiagnosticCollection(constants.diagnosticCollections.xquery)
.set(editor.document.uri, new XQueryLinter().lint(editor.document.getText()));
break;
}
}
function _handleChangeActiveTextEditor(editor: TextEditor): void {
_handleContextChange(editor);
}
function _handleChangeTextEditorSelection(e: TextEditorSelectionChangeEvent): void {
_handleContextChange(e.textEditor);
}

View file

@ -0,0 +1,46 @@
import { workspace } from "vscode";
import { ProviderResult, Range, TextEdit, TextEditor, TextEditorEdit } from "vscode";
import { NativeCommands } from "../../common";
import * as constants from "../../constants";
import { XmlFormatterFactory } from "../xml-formatter";
import { XmlFormattingEditProvider } from "../xml-formatting-edit-provider";
import { XmlFormattingOptionsFactory } from "../xml-formatting-options";
export function formatAsXml(editor: TextEditor, edit: TextEditorEdit): void {
const xmlFormattingEditProvider = new XmlFormattingEditProvider(XmlFormatterFactory.getXmlFormatter());
const formattingOptions = {
insertSpaces: <boolean>editor.options.insertSpaces,
tabSize: <number>editor.options.tabSize
};
let edits: ProviderResult<TextEdit[]>;
if (!editor.selection.isEmpty) {
edits = xmlFormattingEditProvider.provideDocumentRangeFormattingEdits(
editor.document,
new Range(editor.selection.start, editor.selection.end),
formattingOptions,
null);
}
else {
edits = xmlFormattingEditProvider.provideDocumentFormattingEdits(
editor.document,
formattingOptions,
null);
}
for (let i = 0; i < (edits as TextEdit[]).length; i++) {
const textEdit = (edits as TextEdit[])[i];
editor.edit(async (editBuilder) => {
editBuilder.replace(textEdit.range, textEdit.newText);
// wiggle the cursor to deselect the formatted XML (is there a non-hacky way to go about this?)
await NativeCommands.cursorMove("left", "character");
await NativeCommands.cursorMove("right", "character");
});
}
}

View file

@ -0,0 +1,2 @@
export * from "./formatAsXml";
export * from "./minifyXml";

View file

@ -0,0 +1,21 @@
import { workspace } from "vscode";
import { ProviderResult, Range, TextEdit, TextEditor, TextEditorEdit } from "vscode";
import * as constants from "../../constants";
import { XmlFormatterFactory } from "../xml-formatter";
import { XmlFormattingEditProvider } from "../xml-formatting-edit-provider";
import { XmlFormattingOptionsFactory } from "../xml-formatting-options";
export function minifyXml(editor: TextEditor, edit: TextEditorEdit): void {
const xmlFormatter = XmlFormatterFactory.getXmlFormatter();
const xmlFormattingOptions = XmlFormattingOptionsFactory.getXmlFormattingOptions({
insertSpaces: <boolean>editor.options.insertSpaces,
tabSize: <number>editor.options.tabSize
}, editor.document);
const endPosition = editor.document.lineAt(editor.document.lineCount - 1).rangeIncludingLineBreak.end;
const range = new Range(editor.document.positionAt(0), endPosition);
edit.replace(range, xmlFormatter.minifyXml(editor.document.getText(), xmlFormattingOptions));
}

View file

@ -0,0 +1,123 @@
import { XmlFormatter } from "../xml-formatter";
import { XmlFormattingOptions } from "../xml-formatting-options";
export class ClassicXmlFormatter implements XmlFormatter {
formatXml(xml: string, options: XmlFormattingOptions): string {
xml = this.minifyXml(xml, options);
xml = xml.replace(/</g, "~::~<");
if (options.splitXmlnsOnFormat) {
xml = xml
.replace(/xmlns\:/g, "~::~xmlns:")
.replace(/xmlns\=/g, "~::~xmlns=");
}
const parts: string[] = xml.split("~::~");
let inComment = false;
let level = 0;
let output = "";
for (let i = 0; i < parts.length; i++) {
// <!
if (parts[i].search(/<!/) > -1) {
output += this._getIndent(options, level, parts[i]);
inComment = true;
// end <!
if (parts[i].search(/-->/) > -1 || parts[i].search(/\]>/) > -1 || parts[i].search(/!DOCTYPE/) > -1) {
inComment = false;
}
} else if (parts[i].search(/-->/) > -1 || parts[i].search(/\]>/) > -1) {
output += parts[i];
inComment = false;
} else if (/^<(\w|:)/.test(parts[i - 1]) && /^<\/(\w|:)/.test(parts[i])
&& /^<[\w:\-\.\,\/]+/.exec(parts[i - 1])[0] === /^<\/[\w:\-\.\,]+/.exec(parts[i])[0].replace("/", "")) {
output += parts[i];
if (!inComment) { level--; }
} else if (parts[i].search(/<(\w|:)/) > -1 && parts[i].search(/<\//) === -1 && parts[i].search(/\/>/) === -1) {
output = (!inComment) ? output += this._getIndent(options, level++, parts[i]) : output += parts[i];
} else if (parts[i].search(/<(\w|:)/) > -1 && parts[i].search(/<\//) > -1) {
output = (!inComment) ? output += this._getIndent(options, level, parts[i]) : output += parts[i];
} else if (parts[i].search(/<\//) > -1) {
output = (!inComment) ? output += this._getIndent(options, --level, parts[i]) : output += parts[i];
} else if (parts[i].search(/\/>/) > -1 && (!options.splitXmlnsOnFormat || parts[i].search(/xmlns(:|=)/) === -1)) {
output = (!inComment) ? output += this._getIndent(options, level, parts[i]) : output += parts[i];
} else if (parts[i].search(/\/>/) > -1 && parts[i].search(/xmlns(:|=)/) > -1 && options.splitXmlnsOnFormat) {
output = (!inComment) ? output += this._getIndent(options, level--, parts[i]) : output += parts[i];
} else if (parts[i].search(/<\?/) > -1) {
output += this._getIndent(options, level, parts[i]);
} else if (options.splitXmlnsOnFormat && (parts[i].search(/xmlns\:/) > -1 || parts[i].search(/xmlns\=/) > -1)) {
output += this._getIndent(options, level, parts[i]);
} else {
output += parts[i];
}
}
// remove leading newline
if (output[0] === options.newLine) {
output = output.slice(1);
} else if (output.substring(0, 1) === options.newLine) {
output = output.slice(2);
}
return output;
}
minifyXml(xml: string, options: XmlFormattingOptions): string {
xml = this._stripLineBreaks(options, xml); // all line breaks outside of CDATA elements
xml = (options.removeCommentsOnMinify) ? xml.replace(/\<![ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t]*)\>/g, "") : xml;
xml = xml.replace(/>\s{0,}</g, "><"); // insignificant whitespace between tags
xml = xml.replace(/"\s+(?=[^\s]+=)/g, "\" "); // spaces between attributes
xml = xml.replace(/"\s+(?=>)/g, "\""); // spaces between the last attribute and tag close (>)
xml = xml.replace(/"\s+(?=\/>)/g, "\" "); // spaces between the last attribute and tag close (/>)
xml = xml.replace(/[^ <>="]\s+[^ <>="]+=/g, (match: string) => { // spaces between the node name and the first attribute
return match.replace(/\s+/g, " ");
});
return xml;
}
private _getIndent(options: XmlFormattingOptions, level: number, trailingValue?: string): string {
trailingValue = trailingValue || "";
const indentPattern = (options.editorOptions.preferSpaces) ? " ".repeat(options.editorOptions.tabSize) : "\t";
return `${options.newLine}${indentPattern.repeat(level)}${trailingValue}`;
}
private _stripLineBreaks(options: XmlFormattingOptions, xml: string): string {
let output = "";
const inTag = false;
const inTagName = false;
let inCdata = false;
const inAttribute = false;
for (let i = 0; i < xml.length; i++) {
const char: string = xml.charAt(i);
const prev: string = xml.charAt(i - 1);
const next: string = xml.charAt(i + 1);
if (char === "!" && (xml.substr(i, 8) === "![CDATA[" || xml.substr(i, 3) === "!--")) {
inCdata = true;
} else if (char === "]" && (xml.substr(i, 3) === "]]>")) {
inCdata = false;
} else if (char === "-" && (xml.substr(i, 3) === "-->")) {
inCdata = false;
} else if (char.search(/[\r\n]/g) > -1 && !inCdata) {
if (/\r/.test(char) && /\S|\r|\n/.test(prev) && /\S|\r|\n/.test(xml.charAt(i + options.newLine.length))) {
output += char;
} else if (/\n/.test(char) && /\S|\r|\n/.test(xml.charAt(i - options.newLine.length)) && /\S|\r|\n/.test(next)) {
output += char;
}
continue;
}
output += char;
}
return output;
}
}

View file

@ -0,0 +1,2 @@
export * from "./classic-xml-formatter";
export * from "./v2-xml-formatter";

View file

@ -0,0 +1,254 @@
import { XmlFormatter } from "../xml-formatter";
import { XmlFormattingOptions } from "../xml-formatting-options";
import { ClassicXmlFormatter } from "./classic-xml-formatter";
const MagicalStringOfWonders = "~::~MAAAGIC~::~";
/* tslint:disable no-use-before-declare */
export class V2XmlFormatter implements XmlFormatter {
formatXml(xml: string, options: XmlFormattingOptions): string {
// this replaces all "<" brackets inside of comments to a magical string
// so the following minification steps don't mess with comment formatting
xml = this._sanitizeComments(xml);
// remove whitespace from between tags, except for line breaks
xml = xml.replace(/>\s{0,}</g, (match: string) => {
return match.replace(/[^\S\r\n]/g, "");
});
// do some light minification to get rid of insignificant whitespace
xml = xml.replace(/"\s+(?=[^\s]+=)/g, "\" "); // spaces between attributes
xml = xml.replace(/"\s+(?=>)/g, "\""); // spaces between the last attribute and tag close (>)
xml = xml.replace(/"\s+(?=\/>)/g, "\" "); // spaces between the last attribute and tag close (/>)
xml = xml.replace(/[^ <>="]\s+[^ <>="]+=/g, (match: string) => { // spaces between the node name and the first attribute
return match.replace(/\s+/g, " ");
});
// the coast is clear - we can drop those "<" brackets back in
xml = this._unsanitizeComments(xml);
let output = "";
let indentLevel = 0;
let location = Location.Text;
let lastNonTextLocation = Location.Text; // hah
// NOTE: all "exiting" checks should appear after their associated "entering" checks
for (let i = 0; i < xml.length; i++) {
const cc = xml[i];
const nc = xml.charAt(i + 1);
const nnc = xml.charAt(i + 2);
const pc = xml.charAt(i - 1);
// entering CData
if (location === Location.Text && cc === "<" && nc === "!" && nnc === "[") {
output += `${this._getIndent(options, indentLevel)}<`;
location = Location.CData;
}
// exiting CData
else if (location === Location.CData && cc === "]" && nc === "]" && nnc === ">") {
output += "]]>";
i += 2;
lastNonTextLocation = location;
location = Location.Text;
}
// entering Comment
else if (location === Location.Text && cc === "<" && nc === "!" && nnc === "-") {
output += `${this._getIndent(options, indentLevel)}<`;
location = Location.Comment;
}
// exiting Comment
else if (location === Location.Comment && cc === "-" && nc === "-" && nnc === ">") {
output += "-->";
i += 2;
lastNonTextLocation = location;
location = Location.Text;
}
// entering SpecialTag
else if (location === Location.Text && cc === "<" && (nc === "!" || nc === "?")) {
output += `${this._getIndent(options, indentLevel)}<`;
location = Location.SpecialTag;
}
// exiting SpecialTag
else if (location === Location.SpecialTag && cc === ">") {
output += `>`;
lastNonTextLocation = location;
location = Location.Text;
}
// entering StartTag.StartTagName
else if (location === Location.Text && cc === "<" && ["/", "!"].indexOf(nc) === -1) {
// if this occurs after another tag, prepend a line break
if (pc === ">") {
output += `${options.newLine}${this._getIndent(options, indentLevel)}<`;
}
else {
output += `${this._getIndent(options, indentLevel)}<`;
}
indentLevel++;
location = Location.StartTagName;
}
// exiting StartTag.StartTagName, enter StartTag
else if (location === Location.StartTagName && cc === " ") {
output += " ";
lastNonTextLocation = location;
location = Location.StartTag;
}
// entering StartTag.Attribute
else if (location === Location.StartTag && [" ", "/", ">"].indexOf(cc) === -1) {
if (lastNonTextLocation === Location.AttributeValue
&& ((options.splitXmlnsOnFormat
&& xml.substr(i, 5).toLowerCase() === "xmlns")
|| options.splitAttributesOnFormat)) {
output += `${options.newLine}${this._getIndent(options, indentLevel)}`;
}
output += cc;
lastNonTextLocation = location;
location = Location.Attribute;
}
// entering StartTag.Attribute.AttributeValue
else if (location === Location.Attribute && cc === "\"") {
output += "\"";
lastNonTextLocation = location;
location = Location.AttributeValue;
}
// exiting StartTag.Attribute.AttributeValue, entering StartTag
else if (location === Location.AttributeValue && cc === "\"") {
output += "\"";
lastNonTextLocation = location;
location = Location.StartTag;
}
// 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
if (pc === "/") {
indentLevel--;
output += ">";
// only add a newline here if one doesn't already exist (issue #147)
if (nc !== "\r" && nc !== "\n") {
output += options.newLine;
}
}
else {
output += ">";
}
lastNonTextLocation = location;
location = Location.Text;
}
// entering EndTag
else if (location === Location.Text && cc === "<" && nc === "/") {
indentLevel--;
// if the end tag immediately follows a line break, just add an indentation
// if the end tag immediately follows another end tag, add a line break and indent
// otherwise, this should be treated as a same-line end tag(ex. <element>text</element>)
if (pc === "\n") {
output += `${this._getIndent(options, indentLevel)}<`;
}
else if (lastNonTextLocation === Location.EndTag) {
output += `${options.newLine}${this._getIndent(options, indentLevel)}<`;
}
else {
output += "<";
}
location = Location.EndTag;
}
// exiting EndTag, entering Text
else if (location === Location.EndTag && cc === ">") {
output += ">";
lastNonTextLocation = location;
location = Location.Text;
}
// Text
else {
output += cc;
}
}
return output;
}
minifyXml(xml: string, options: XmlFormattingOptions): string {
return new ClassicXmlFormatter().minifyXml(xml, options);
}
private _getIndent(options: XmlFormattingOptions, indentLevel: number): string {
return ((options.editorOptions.insertSpaces) ? " ".repeat(options.editorOptions.tabSize) : "\t").repeat(indentLevel);
}
private _sanitizeComments(xml: string): string {
let output = "";
let inComment = false;
for (let i = 0; i < xml.length; i++) {
const cc = xml[i];
const nc = xml.charAt(i + 1);
const nnc = xml.charAt(i + 2);
const pc = xml.charAt(i - 1);
if (!inComment && cc === "<" && nc === "!" && nnc === "-") {
inComment = true;
output += "<!--";
i += 3;
}
else if (inComment && cc === "<") {
output += MagicalStringOfWonders;
}
else if (inComment && cc === "-" && nc === "-" && nnc === ">") {
inComment = false;
output += "-->";
i += 2;
}
else {
output += cc;
}
}
return output;
}
private _unsanitizeComments(xml: string): string {
return xml.replace(new RegExp(MagicalStringOfWonders, "g"), "<");
}
}
enum Location {
Attribute,
AttributeValue,
CData,
Comment,
EndTag,
SpecialTag,
StartTag,
StartTagName,
Text
}

3
src/formatting/index.ts Normal file
View file

@ -0,0 +1,3 @@
export * from "./xml-formatter";
export * from "./xml-formatting-edit-provider";
export * from "./xml-formatting-options";

View file

@ -0,0 +1,45 @@
import { window, workspace } from "vscode";
import { Configuration, ExtensionState } from "../common";
import * as constants from "../constants";
import { ClassicXmlFormatter } from "./formatters/classic-xml-formatter";
import { V2XmlFormatter } from "./formatters/v2-xml-formatter";
import { XmlFormattingOptions } from "./xml-formatting-options";
export interface XmlFormatter {
formatXml(xml: string, options: XmlFormattingOptions): string;
minifyXml(xml: string, options: XmlFormattingOptions): string;
}
export class XmlFormatterFactory {
private static _xmlFormatter: XmlFormatter;
static getXmlFormatter(): XmlFormatter {
if (XmlFormatterFactory._xmlFormatter) {
return XmlFormatterFactory._xmlFormatter;
}
const xmlFormatterImplementationSetting = Configuration.xmlFormatterImplementation;
let xmlFormatterImplementation: XmlFormatter;
switch (xmlFormatterImplementationSetting) {
case constants.xmlFormatterImplementations.classic: xmlFormatterImplementation = new ClassicXmlFormatter(); break;
case constants.xmlFormatterImplementations.v2:
default: xmlFormatterImplementation = new V2XmlFormatter(); break;
}
// warn users about the new formatter
const formatterWarningKey = "xmlTools.v2FormatterWarning.shown";
if (!ExtensionState.global.get<boolean>(formatterWarningKey) && xmlFormatterImplementation instanceof V2XmlFormatter) {
// tslint:disable-next-line:max-line-length
window.showInformationMessage("Heads up! We've rewritten the XML formatter. If you liked the old one better, it's still there. Just set the 'xmlTools.xmlFormatterImplementation' setting to 'classic'.")
.then(() => {
ExtensionState.global.update(formatterWarningKey, true);
});
}
return (XmlFormatterFactory._xmlFormatter = xmlFormatterImplementation);
}
}

View file

@ -0,0 +1,31 @@
import { workspace } from "vscode";
import {
CancellationToken, DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider, EndOfLine,
FormattingOptions, ProviderResult, Range, TextDocument, TextEdit
} from "vscode";
import * as constants from "../constants";
import { XmlFormatter } from "./xml-formatter";
import { XmlFormattingOptionsFactory } from "./xml-formatting-options";
export class XmlFormattingEditProvider implements DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider {
constructor(
public xmlFormatter: XmlFormatter
) { }
provideDocumentFormattingEdits(document: TextDocument, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]> {
const lastLine = document.lineAt(document.lineCount - 1);
const documentRange = new Range(document.positionAt(0), lastLine.range.end);
return this.provideDocumentRangeFormattingEdits(document, documentRange, options, token);
}
provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]> {
let xml = document.getText(range);
xml = this.xmlFormatter.formatXml(xml, XmlFormattingOptionsFactory.getXmlFormattingOptions(options, document));
return [ TextEdit.replace(range, xml) ];
}
}

View file

@ -0,0 +1,24 @@
import { EndOfLine, FormattingOptions, TextDocument } from "vscode";
import { Configuration } from "../common";
import * as constants from "../constants";
export interface XmlFormattingOptions {
editorOptions: FormattingOptions;
newLine: string;
removeCommentsOnMinify: boolean;
splitAttributesOnFormat: boolean;
splitXmlnsOnFormat: boolean;
}
export class XmlFormattingOptionsFactory {
static getXmlFormattingOptions(formattingOptions: FormattingOptions, document: TextDocument): XmlFormattingOptions {
return {
editorOptions: formattingOptions,
newLine: (document.eol === EndOfLine.CRLF) ? "\r\n" : "\n",
removeCommentsOnMinify: Configuration.removeCommentsOnMinify(document.uri),
splitAttributesOnFormat: Configuration.splitAttributesOnFormat(document.uri),
splitXmlnsOnFormat: Configuration.splitXmlnsOnFormat(document.uri)
};
}
}

1
src/linting/index.ts Normal file
View file

@ -0,0 +1 @@
export * from "./xquery-linter";

View file

@ -0,0 +1,37 @@
import { Diagnostic, DiagnosticSeverity, Position, Range } from "vscode";
const XQLint = require("xqlint").XQLint;
export class XQueryLinter {
static SEVERITY_WARNING = 1;
static SEVERITY_ERROR = 2;
lint(text: string): Diagnostic[] {
const linter = new XQLint(text);
const diagnostics = new Array<Diagnostic>();
linter.getErrors().forEach((error: any) => {
diagnostics.push(new Diagnostic(
new Range(
new Position(error.pos.sl, error.pos.sc),
new Position(error.pos.el, error.pos.ec)
),
error.message,
DiagnosticSeverity.Error
));
});
linter.getWarnings().forEach((warning: any) => {
diagnostics.push(new Diagnostic(
new Range(
new Position(warning.pos.sl, warning.pos.sc),
new Position(warning.pos.el, warning.pos.ec)
),
warning.message,
DiagnosticSeverity.Warning
));
});
return diagnostics;
}
}

View file

@ -1,44 +0,0 @@
import * as vsc from "vscode";
import { XQueryCompleter, XQueryCompletionItem } from "../services/XQueryCompleter";
export class XQueryCompletionItemProvider implements vsc.CompletionItemProvider {
provideCompletionItems(document: vsc.TextDocument, position: vsc.Position): vsc.CompletionItem[] {
let items: vsc.CompletionItem[] = new Array<vsc.CompletionItem>();
let completer: XQueryCompleter = new XQueryCompleter(document.getText());
let completions: XQueryCompletionItem[] = completer.getCompletions(position.line, position.character);
completions.forEach((completion: XQueryCompletionItem) => {
let item: vsc.CompletionItem = new vsc.CompletionItem(completion.name);
item.insertText = completion.value;
switch (completion.meta) {
// functions (always qualified with a colon)
case "function":
item.kind = vsc.CompletionItemKind.Function;
let funcStart = (completion.value.indexOf(":") + 1);
let funcEnd = completion.value.indexOf("(");
item.insertText = completion.value.substring(funcStart, funcEnd);
break;
// variables and parameters (always qualified with a dollar sign)
case "Let binding":
case "Local variable":
case "Window variable":
case "Function parameter":
item.kind = vsc.CompletionItemKind.Variable;
item.insertText = completion.value.substring(1);
break;
// everything else
default: item.kind = vsc.CompletionItemKind.Text;
}
items.push(item);
});
return items;
}
}

View file

@ -1,127 +0,0 @@
import * as vsc from "vscode";
import { ChildProcess } from "../services/ChildProcess";
const CFG_SECTION: string = "xmlTools";
const CFG_XQEXEC: string = "xqueryExecutionEngine";
const CFG_XQARGS: string = "xqueryExecutionArguments";
export class XQueryExecutionProvider {
static async executeXQueryAsync(editor: vsc.TextEditor): Promise<void> {
// this disposable will be used for creating status bar messages
let disposable: vsc.Disposable;
if (editor.document.languageId !== "xquery") {
vsc.window.showErrorMessage("This action can only be performed on an XQuery file.");
return;
}
let executable = vsc.workspace.getConfiguration(CFG_SECTION).get<string>(CFG_XQEXEC, null);
let args = vsc.workspace.getConfiguration(CFG_SECTION).get<string[]>(CFG_XQARGS, []);
if (!executable || executable == "") {
let action = await vsc.window.showWarningMessage("An XQuery execution engine has not been defined.", "Define Now");
if (action == "Define Now") {
vsc.commands.executeCommand("workbench.action.openGlobalSettings");
}
return;
}
let inputFile: vsc.Uri;
disposable = vsc.window.setStatusBarMessage("Searching for XML files in folder...");
let files: vsc.Uri[] = await vsc.workspace.findFiles("**/*.xml", "", 100);
disposable.dispose();
// user does not have a folder open - prompt for file name
if (typeof files === "undefined") {
vsc.window.showErrorMessage("You must have a folder opened in VS Code to use this feature.");
return;
}
// if there is only one XML file, default it
// otherwise, prompt the user to select one from the open folder
if (files.length > 1) {
let qpItems: any[] = new Array<any>();
files.forEach((file) => {
let filename: string = file.fsPath.replace("\\", "/");
qpItems.push({ // must implement vscode.QuickPickItem
label: filename.substring(filename.lastIndexOf("/") + 1),
description: file.fsPath,
file: file
});
});
let selection = await vsc.window.showQuickPick(qpItems, { placeHolder: "Please select an input file." });
if (!selection) {
return;
}
inputFile = selection.file;
}
else {
inputFile = files[0];
}
// prompt for output file name
let outputPath: string = null;
let outputPathPos: number = -1;
for (let i = 0; i < args.length; i++) {
if (i > 0) {
if (args[i - 1].search(/out|result/)) {
outputPath = args[i];
outputPathPos = i;
}
}
}
if (outputPath) {
outputPath = await vsc.window.showInputBox({
placeHolder: "ex. C:\\TEMP\XQueryOutput\\MyOutputFile.xml",
prompt: "Please specify the output file path. Existing file behavior is determined by the execution engine you have specified.",
value: outputPath
});
args[outputPathPos] = outputPath;
}
// call out to the execution engine
disposable = vsc.window.setStatusBarMessage("Executing XQuery Script...");
args = args.map<string>((value: string) => {
return value
.replace("$(script)", editor.document.uri.fsPath)
.replace("$(input)", inputFile.fsPath)
.replace("$(project)", vsc.workspace.rootPath);
});
try {
await ChildProcess.spawnAsync(executable, args);
}
catch (error) {
if (error.message.search(/[Ll]ine:?\s*\d+/gm) > -1) {
let match: RegExpExecArray = /[Ll]ine:?\s*\d+/gm.exec(error.message);
let line: number = (Number.parseInt(match[0].replace(/([Ll]ine:?\s*)|\s/, "")) - 1);
let selection: string = await vsc.window.showErrorMessage(error.message, `Go to Line ${line}`);
if (selection == `Go to Line ${line}`) {
editor.revealRange(new vsc.Range(line, 0, line, 0));
}
}
else {
vsc.window.showErrorMessage(error.message);
}
}
finally {
disposable.dispose();
}
}
}

View file

@ -1,33 +0,0 @@
import * as vsc from "vscode";
import { RangeUtil } from "../utils/RangeUtil";
import { XmlFormatter, IXmlFormatterOptions } from "../services/XmlFormatter";
const CFG_SECTION: string = "xmlTools";
const CFG_SPLIT_NAMESPACES: string = "splitXmlnsOnFormat";
export class XmlFormattingEditProvider implements vsc.DocumentFormattingEditProvider, vsc.DocumentRangeFormattingEditProvider {
provideDocumentFormattingEdits(document: vsc.TextDocument, options: vsc.FormattingOptions): vsc.TextEdit[] {
let range = RangeUtil.getRangeForDocument(document);
return this._provideFormattingEdits(document, range, options);
}
provideDocumentRangeFormattingEdits(document: vsc.TextDocument, range: vsc.Range, options: vsc.FormattingOptions): vsc.TextEdit[] {
return this._provideFormattingEdits(document, range, options);
}
private _provideFormattingEdits(document: vsc.TextDocument, range: vsc.Range, options: vsc.FormattingOptions): vsc.TextEdit[] {
let splitNamespaces: boolean = vsc.workspace.getConfiguration(CFG_SECTION).get<boolean>(CFG_SPLIT_NAMESPACES, true);
let formatterOptions: IXmlFormatterOptions = {
preferSpaces: options.insertSpaces,
tabSize: options.tabSize,
splitNamespaces: splitNamespaces
};
let formatter = new XmlFormatter(formatterOptions);
let xml = formatter.format(document.getText(range));
return [ vsc.TextEdit.replace(range, xml) ];
}
}

View file

@ -1,32 +0,0 @@
import * as vsc from "vscode";
import { XQueryLinter, XQueryDiagnostic } from "../services/XQueryLinter";
export class XQueryLintingFeatureProvider {
private static _coreDiagnostics: vsc.DiagnosticCollection;
static get coreDiagnostics(): vsc.DiagnosticCollection {
if (!XQueryLintingFeatureProvider._coreDiagnostics) {
XQueryLintingFeatureProvider._coreDiagnostics = vsc.languages.createDiagnosticCollection("XQueryDiagnostics");
}
return XQueryLintingFeatureProvider._coreDiagnostics;
}
static provideXQueryDiagnostics(editor: vsc.TextEditor): void {
let diagnostics: vsc.Diagnostic[] = new Array<vsc.Diagnostic>();
let xqDiagnostics: XQueryDiagnostic[] = XQueryLinter.lint(editor.document.getText());
xqDiagnostics.forEach((xqd: XQueryDiagnostic) => {
let vSeverity: vsc.DiagnosticSeverity = (xqd.severity == 1) ? vsc.DiagnosticSeverity.Warning : vsc.DiagnosticSeverity.Error;
let startPos: vsc.Position = new vsc.Position(xqd.startLine, xqd.startColumn);
let endPos: vsc.Position = new vsc.Position(xqd.endLine, xqd.endColumn);
let range: vsc.Range = new vsc.Range(startPos, endPos);
let diagnostic: vsc.Diagnostic = new vsc.Diagnostic(range, xqd.message, vSeverity);
diagnostics.push(diagnostic);
});
XQueryLintingFeatureProvider.coreDiagnostics.set(editor.document.uri, diagnostics);
}
}

View file

@ -1,120 +0,0 @@
import * as vsc from "vscode";
import * as ext from "../Extension";
import { XPathEvaluator, EvaluatorResult, EvaluatorResultType } from "../services/XPathEvaluator";
const CFG_SECTION: string = "xmlTools";
const CFG_PERSIST_QUERY: string = "persistXPathQuery";
const CFG_IGNORE_DEFAULT_XMLNS: string = "ignoreDefaultNamespace";
const MEM_QUERY_HISTORY: string = "xpathQueryHistory";
const MEM_QUERY_LAST: string = "xPathQueryLast";
const OUTPUT_CHANNEL: string = "XPath Results";
export class XPathFeatureProvider {
static async evaluateXPathAsync(editor: vsc.TextEditor, edit: vsc.TextEditorEdit): Promise<void> {
// if there is no workspace, we will track queries in the global Memento
let memento: vsc.Memento = ext.WorkspaceState || ext.GlobalState;
// get the xpath persistence setting
let persistQueries: boolean = vsc.workspace.getConfiguration(CFG_SECTION).get<boolean>(CFG_PERSIST_QUERY, 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
let history: HistoricQuery[] = memento.get<HistoricQuery[]>(MEM_QUERY_HISTORY, new Array<HistoricQuery>());
let globalLastQuery: string = memento.get<string>(MEM_QUERY_LAST, "");
let lastQuery: HistoricQuery = history.find((item: HistoricQuery) => {
if (item.uri == editor.document.uri.toString()) {
return true;
}
return false;
});
// set the inital display value and prompt the user
let query: string = "";
if (persistQueries) {
if (lastQuery) {
query = lastQuery.query;
}
else {
query = globalLastQuery;
}
}
query = await vsc.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) {
let ignoreDefaultNamespace: boolean = vsc.workspace.getConfiguration(CFG_SECTION).get<boolean>(CFG_IGNORE_DEFAULT_XMLNS, true);
// run the query
let xml: string = editor.document.getText();
let evalResult: EvaluatorResult;
try {
evalResult = XPathEvaluator.evaluate(query, xml, ignoreDefaultNamespace);
}
catch (error) {
console.error(error);
vsc.window.showErrorMessage(`Something went wrong while evaluating the XPath: ${error}`);
return;
}
// show the results to the user
let outputChannel: vsc.OutputChannel = vsc.window.createOutputChannel(OUTPUT_CHANNEL);
outputChannel.clear();
outputChannel.appendLine(`XPath Query: ${query}`);
outputChannel.append("\n");
if (evalResult.type === EvaluatorResultType.NODE_COLLECTION) {
(evalResult.result as Node[]).forEach((node: XmlNode) => {
outputChannel.appendLine(`[Line ${node.lineNumber}] ${node.localName}: ${node.textContent}`);
});
} else {
outputChannel.appendLine(`[Result]: ${evalResult.result}`);
}
outputChannel.show(vsc.ViewColumn.Three);
// if persistence is enabled, save the query for later
if (persistQueries) {
lastQuery = new HistoricQuery(editor.document.uri.toString(), query);
let affectedIndex: number = -1;
history = history.map<HistoricQuery>((item: HistoricQuery, index: number) => {
if (item.uri == lastQuery.uri) {
item.query = query;
affectedIndex = index;
}
return item;
});
if (affectedIndex == -1) {
history.push(lastQuery);
}
memento.update(MEM_QUERY_HISTORY, history);
memento.update(MEM_QUERY_LAST, query);
}
}
}
}
class HistoricQuery {
constructor(uri: string, query: string) {
this.uri = uri;
this.query = query;
}
uri: string;
query: string;
}

View file

@ -1,128 +0,0 @@
import * as vsc from "vscode";
import * as path from "path";
let DOMParser = require("xmldom").DOMParser;
export class XmlTreeViewDataProvider implements vsc.TreeDataProvider<Node> {
private _onDidChangeTreeData: vsc.EventEmitter<Node | null> = new vsc.EventEmitter<Node | null>();
private _xmlDocument: Document;
constructor(private _context: vsc.ExtensionContext) {
vsc.window.onDidChangeActiveTextEditor((editor) => {
this._refreshTree();
});
vsc.workspace.onDidChangeTextDocument((e) => {
this._refreshTree();
});
}
readonly onDidChangeTreeData: vsc.Event<Node | null> = this._onDidChangeTreeData.event;
get activeEditor(): vsc.TextEditor | null {
return vsc.window.activeTextEditor || null;
}
getChildren(element?: Node): Node[] {
if (!this._xmlDocument) {
this._refreshTree();
}
if (element) {
return [].concat(this._getChildAttributeArray(element), this._getChildElementArray(element));
}
else if (this._xmlDocument) {
return [ this._xmlDocument.lastChild ];
}
else {
return [];
}
}
getTreeItem(element: Node): vsc.TreeItem {
let treeItem = new vsc.TreeItem(element.localName);
if (this._getChildAttributeArray(element).length > 0) {
treeItem.collapsibleState = vsc.TreeItemCollapsibleState.Collapsed;
}
if (this._getChildElementArray(element).length > 0) {
treeItem.collapsibleState = vsc.TreeItemCollapsibleState.Collapsed;
}
treeItem.command = {
command: "revealLine",
title: "",
arguments: [{
lineNumber: (element as any).lineNumber - 1,
at: "top"
}]
};
treeItem.iconPath = this._getIcon(element);
return treeItem;
}
private _getChildAttributeArray(node: Node): Node[] {
if (!node.attributes) {
return [];
}
let array = new Array<Node>();
for (let i = 0; i < node.attributes.length; i++) {
array.push(node.attributes[i]);
}
return array;
}
private _getChildElementArray(node: Node): Node[] {
if (!node.childNodes) {
return [];
}
let array = new Array<Node>();
for (let i = 0; i < node.childNodes.length; i++) {
let child = node.childNodes[i];
if ((child as any).tagName) {
array.push(child);
}
}
return array;
}
private _getIcon(element: Node): any {
let type = "element";
if (!(element as any).tagName) {
type = "attribute";
}
let icon = {
dark: this._context.asAbsolutePath(path.join("resources", "icons", `${type}.dark.svg`)),
light: this._context.asAbsolutePath(path.join("resources", "icons", `${type}.light.svg`))
};
return icon;
}
private _refreshTree(): void {
if (!this.activeEditor || this.activeEditor.document.languageId !== "xml") {
this._xmlDocument = null;
this._onDidChangeTreeData.fire();
return;
}
let xml = this.activeEditor.document.getText();
this._xmlDocument = new DOMParser().parseFromString(xml, "text/xml");
this._onDidChangeTreeData.fire();
}
}

View file

@ -1,30 +0,0 @@
let child_process = require("child_process");
export class ChildProcess {
static async spawnAsync(executable: string, args: string[]): Promise<void> {
return new Promise<void>((resolve, reject) => {
let output: string = "";
let handle = child_process.spawn(executable, args);
handle.stdout.on("data", (data: string) => {
output += data;
});
handle.stderr.on("data", (data: string) => {
output += data;
});
handle.on("close", (code: string) => {
if (code == "0") {
resolve();
}
else {
reject({ code: code, message: output });
}
});
});
}
}

View file

@ -1,63 +0,0 @@
import * as xpath from "xpath";
let DOMParser = require("xmldom").DOMParser;
export class EvaluatorResult {
type: EvaluatorResultType;
result: Node[]|number|string|boolean;
}
export class EvaluatorResultType {
static SCALAR_TYPE: number = 0;
static NODE_COLLECTION: number = 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");
});
}
let nodes: Node[] = new Array<Node>();
let xdoc: Document = new DOMParser().parseFromString(xml, "text/xml");
let resolver: xpath.XPathNSResolver = xpath.createNSResolver(xdoc);
let result: xpath.XPathResult = xpath.evaluate(
query, // xpathExpression
xdoc, // contextNode
resolver, // namespaceResolver
xpath.XPathResult.ANY_TYPE, // resultType
null // result
)
let evalResult = new EvaluatorResult();
evalResult.type = EvaluatorResultType.SCALAR_TYPE;
switch(result.resultType) {
case xpath.XPathResult.NUMBER_TYPE:
evalResult.result = result.numberValue;
break;
case xpath.XPathResult.STRING_TYPE:
evalResult.result = result.stringValue;
break;
case xpath.XPathResult.BOOLEAN_TYPE:
evalResult.result = result.booleanValue;
break;
case xpath.XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
case xpath.XPathResult.ORDERED_NODE_ITERATOR_TYPE:
evalResult.result = result.booleanValue;
let node: Node;
while (node = result.iterateNext()) {
nodes.push(node);
}
evalResult.result = nodes;
evalResult.type = EvaluatorResultType.NODE_COLLECTION;
break;
}
return evalResult;
}
}

View file

@ -1,41 +0,0 @@
let XQLint = require("xqlint").XQLint;
export class XQueryCompleter {
constructor(script: string) {
this.script = script;
}
private _script: string;
private _linter: any;
get script(): string {
return this._script;
}
set script(value: string) {
this._script = value;
this._linter = new XQLint(this._script);
}
getCompletions(line: number, column: number): XQueryCompletionItem[] {
let items: XQueryCompletionItem[] = new Array<XQueryCompletionItem>();
this._linter.getCompletions({line: line, col: column}).forEach((completion: any) => {
items.push(new XQueryCompletionItem(completion.name, completion.value, completion.meta));
});
return items;
}
}
export class XQueryCompletionItem {
constructor(name: string, value: string, meta: string) {
this.name = name;
this.value = value;
this.meta = meta;
}
name: string;
value: string;
meta: string;
}

View file

@ -1,39 +0,0 @@
let XQLint = require("xqlint").XQLint;
export class XQueryLinter {
static SEVERITY_WARNING: number = 1;
static SEVERITY_ERROR: number = 2;
static lint(text: string): XQueryDiagnostic[] {
let linter = new XQLint(text);
let diagnostics: XQueryDiagnostic[] = new Array<XQueryDiagnostic>();
linter.getErrors().forEach((error: any) => {
diagnostics.push(new XQueryDiagnostic(XQueryLinter.SEVERITY_ERROR, error.message, error.pos.sl, error.pos.sc, error.pos.el, error.pos.ec));
});
linter.getWarnings().forEach((warning: any) => {
diagnostics.push(new XQueryDiagnostic(XQueryLinter.SEVERITY_WARNING, warning.message, warning.pos.sl, warning.pos.sc, warning.pos.el, warning.pos.ec));
});
return diagnostics;
}
}
export class XQueryDiagnostic {
constructor(severity: number, message: string, startLine: number, startColumn: number, endLine: number, endColumn: number) {
this.severity = severity;
this.message = message;
this.startLine = startLine;
this.startColumn = startColumn;
this.endLine = endLine;
this.endColumn = endColumn;
}
severity: number;
message: string;
startLine: number;
startColumn: number;
endLine: number;
endColumn: number;
}

View file

@ -1,192 +0,0 @@
// Based on pretty-data (https://github.com/vkiryukhin/pretty-data)
export class XmlFormatter {
constructor(options?: IXmlFormatterOptions) {
options = options || {};
if (typeof options.preferSpaces === "undefined") {
options.preferSpaces = false;
}
if (typeof options.splitNamespaces === "undefined") {
options.splitNamespaces = true;
}
options.tabSize = options.tabSize || 4;
options.newLine = options.newLine || "\n";
this.newLine = options.newLine || "\n";
this.indentPattern = (options.preferSpaces) ? " ".repeat(options.tabSize) : "\t";
this.splitNamespaces = options.splitNamespaces;
}
newLine: string;
indentPattern: string;
splitNamespaces: boolean;
format(xml: string): string {
xml = this.minify(xml, false);
xml = xml.replace(/</g, "~::~<");
if (this.splitNamespaces) {
xml = xml
.replace(/xmlns\:/g, "~::~xmlns:")
.replace(/xmlns\=/g, "~::~xmlns=");
}
let parts: string[] = xml.split("~::~");
console.log(parts);
let inComment: boolean = false;
let level: number = 0;
let output: string = "";
for (let i = 0; i < parts.length; i++) {
// <!
if (parts[i].search(/<!/) > -1) {
output += this._getIndent(level, parts[i]);
inComment = true;
// end <!
if (parts[i].search(/-->/) > -1 || parts[i].search(/\]>/) > -1 || parts[i].search(/!DOCTYPE/) > -1) {
inComment = false;
}
}
// end <!
else if (parts[i].search(/-->/) > -1 || parts[i].search(/\]>/) > -1) {
output += parts[i];
inComment = false;
}
// <elm></elm>
else if (/^<(\w|:)/.test(parts[i - 1]) && /^<\/(\w|:)/.test(parts[i])
&& /^<[\w:\-\.\,\/]+/.exec(parts[i - 1])[0] == /^<\/[\w:\-\.\,]+/.exec(parts[i])[0].replace("/", "")) {
output += parts[i];
if (!inComment) level--;
}
// <elm>
else if (parts[i].search(/<(\w|:)/) > -1 && parts[i].search(/<\//) == -1 && parts[i].search(/\/>/) == -1) {
output = (!inComment) ? output += this._getIndent(level++, parts[i]) : output += parts[i];
}
// <elm>...</elm>
else if (parts[i].search(/<(\w|:)/) > -1 && parts[i].search(/<\//) > -1) {
output = (!inComment) ? output += this._getIndent(level, parts[i]) : output += parts[i];
}
// </elm>
else if (parts[i].search(/<\//) > -1) {
output = (!inComment) ? output += this._getIndent(--level, parts[i]) : output += parts[i];
}
// <elm />
else if (parts[i].search(/\/>/) > -1 && (!this.splitNamespaces || parts[i].search(/xmlns(:|=)/) == -1)) {
output = (!inComment) ? output += this._getIndent(level, parts[i]) : output += parts[i];
}
// xmlns />
else if (parts[i].search(/\/>/) > -1 && parts[i].search(/xmlns(:|=)/) > -1 && this.splitNamespaces) {
output = (!inComment) ? output += this._getIndent(level--, parts[i]) : output += parts[i];
}
// <?xml ... ?>
else if (parts[i].search(/<\?/) > -1) {
output += this._getIndent(level, parts[i]);
}
// xmlns
else if (this.splitNamespaces && (parts[i].search(/xmlns\:/) > -1 || parts[i].search(/xmlns\=/) > -1)) {
output += this._getIndent(level, parts[i]);
}
else {
output += parts[i];
}
}
// remove leading newline
if (output[0] == this.newLine) {
output = output.slice(1);
}
else if (output.substring(0, 1) == this.newLine) {
output = output.slice(2);
}
return output;
}
minify(xml: string, removeComments?: boolean): string {
if (typeof removeComments === "undefined") {
removeComments = false;
}
xml = this._stripLineBreaks(xml); // all line breaks outside of CDATA elements
xml = (removeComments) ? xml.replace(/\<![ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t]*)\>/g, "") : xml;
xml = xml.replace(/>\s{0,}</g, "><"); // insignificant whitespace between tags
xml = xml.replace(/"\s+(?=[^\s]+=)/g, "\" "); // spaces between attributes
xml = xml.replace(/"\s+(?=>)/g, "\""); // spaces between the last attribute and tag close (>)
xml = xml.replace(/"\s+(?=\/>)/g, "\" "); // spaces between the last attribute and tag close (/>)
xml = xml.replace(/[^ <>="]\s+[^ <>="]+=/g, (match: string) => { // spaces between the node name and the first attribute
return match.replace(/\s+/g, " ");
});
return xml;
}
private _getIndent(level: number, trailingValue?: string): string {
trailingValue = trailingValue || "";
return `${this.newLine}${this.indentPattern.repeat(level)}${trailingValue}`;
}
private _stripLineBreaks(xml: string): string {
let output: string = "";
let inTag: boolean = false;
let inTagName: boolean = false;
let inCdata: boolean = false;
let inAttribute: boolean = false;
for (let i = 0; i < xml.length; i++) {
let char: string = xml.charAt(i);
let prev: string = xml.charAt(i - 1);
let next: string = xml.charAt(i + 1);
if (char == "!" && (xml.substr(i, 8) == "![CDATA[" || xml.substr(i, 3) == "!--")) {
inCdata = true;
}
else if (char == "]" && (xml.substr(i, 3) == "]]>")) {
inCdata = false;
}
else if (char == "-" && (xml.substr(i, 3) == "-->")) {
inCdata = false;
}
else if (char.search(/[\r\n]/g) > -1 && !inCdata) {
if (/\r/.test(char) && /\S|\r|\n/.test(prev) && /\S|\r|\n/.test(xml.charAt(i + this.newLine.length))) {
output += char;
}
else if (/\n/.test(char) && /\S|\r|\n/.test(xml.charAt(i - this.newLine.length)) && /\S|\r|\n/.test(next)) {
output += char;
}
continue;
}
output += char;
}
return output;
}
}
export interface IXmlFormatterOptions {
preferSpaces?: boolean;
tabSize?: number;
newLine?: string;
splitNamespaces?: boolean;
}

View file

@ -0,0 +1,65 @@
import * as assert from "assert";
import { FormattingOptions } from "vscode";
import { TestDataLoader } from "./test-utils/test-data-loader";
import { XmlFormatter, XmlFormattingOptions } from "../formatting";
import { V2XmlFormatter } from "../formatting/formatters";
describe("V2XmlFormatter", () => {
const xmlFormatter = new V2XmlFormatter();
describe("#formatXml(xml, options)", () => {
const options = {
editorOptions: {
insertSpaces: true,
tabSize: 4
},
newLine: "\r\n",
removeCommentsOnMinify: false,
splitAttributesOnFormat: false,
splitXmlnsOnFormat: true
};
it("should handle basic XML", () => {
testFormatter(xmlFormatter, options, "basic");
});
it("should handle unicode element names", () => {
testFormatter(xmlFormatter, options, "unicode");
});
it("should handle self-closing elements", () => {
testFormatter(xmlFormatter, options, "self-closing");
});
it("should handle text-only lines", () => {
testFormatter(xmlFormatter, options, "text-only-line");
});
it("should handle preformatted xml", () => {
testFormatter(xmlFormatter, options, "preformatted");
});
it ("should preserve line breaks between elements", () => {
testFormatter(xmlFormatter, options, "preserve-breaks");
});
it("should maintain comment formatting", () => {
testFormatter(xmlFormatter, options, "maintain-comment-formatting");
});
});
});
function testFormatter(xmlFormatter: XmlFormatter, options: XmlFormattingOptions, fileLabel: string): void {
const expectedFormattedXml = TestDataLoader.load(`${fileLabel}.formatted.xml`);
const unformattedXml = TestDataLoader.load(`${fileLabel}.unformatted.xml`);
const actualFormattedXml = xmlFormatter.formatXml(unformattedXml, options);
assert.equal(actualFormattedXml, expectedFormattedXml, "Actual formatted XML does not match expected formatted XML.");
}

View file

@ -0,0 +1,3 @@
<root>
<element>text</element>
</root>

View file

@ -0,0 +1 @@
<root><element>text</element></root>

View file

@ -0,0 +1,13 @@
<root>
<element>text</element>
<!--
<Description>
Any description.
</Description>
<Keyword UUID="fd8e6b13-9865-4d8c-9ecd-5ff08a0cf2e2"/>
<Parameters>
<Parameter Name="ParameterName1">The Parameter Name.</Parameter>
<Parameter Name="ParameterName2" PossibleValues="Val1,Val2" DefaultValue="Val1"/>
</Parameters>
-->
</root>

View file

@ -0,0 +1,13 @@
<root>
<element>text</element>
<!--
<Description>
Any description.
</Description>
<Keyword UUID="fd8e6b13-9865-4d8c-9ecd-5ff08a0cf2e2"/>
<Parameters>
<Parameter Name="ParameterName1">The Parameter Name.</Parameter>
<Parameter Name="ParameterName2" PossibleValues="Val1,Val2" DefaultValue="Val1"/>
</Parameters>
-->
</root>

View file

@ -0,0 +1,9 @@
<root>
<element>text</element>
<element>text</element>
<element>text</element>
<element>text</element>
<element>
<element2>text</element2>
</element2>
</root>

View file

@ -0,0 +1,9 @@
<root>
<element>text</element>
<element>text</element>
<element>text</element>
<element>text</element>
<element>
<element2>text</element2>
</element2>
</root>

View file

@ -0,0 +1,12 @@
<root>
<element>text</element>
<element>text</element>
<element>text</element>
<element>text</element>
<element>
<element2>text</element2>
</element2>
</root>

View file

@ -0,0 +1,12 @@
<root>
<element>text</element>
<element>text</element>
<element>text</element>
<element>text</element>
<element>
<element2>text</element2>
</element2>
</root>

View file

@ -0,0 +1,3 @@
<Node>
<Node name="testChild"/>
</Node>

View file

@ -0,0 +1 @@
<Node><Node name="testChild"/></Node>

View file

@ -0,0 +1,6 @@
<Tag>
<Tag2>
Text1
Text2
</Tag2>
</Tag>

View file

@ -0,0 +1,6 @@
<Tag>
<Tag2>
Text1
Text2
</Tag2>
</Tag>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<Имя>
<element>text</element>
</Имя>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<Имя><element>text</element></Имя>

View file

@ -0,0 +1,7 @@
import * as fs from "fs";
export class TestDataLoader {
static load(fileName: string): string {
return fs.readFileSync(`${__dirname}/../../../src/test/test-data/${fileName}`, "UTF-8");
}
}

1
src/tree-view/index.ts Normal file
View file

@ -0,0 +1 @@
export * from "./xml-tree-data-provider";

View file

@ -0,0 +1,233 @@
import { window, workspace } from "vscode";
import {
Event, EventEmitter, ExtensionContext, Position, TextEditor, TreeDataProvider,
TreeItem, TreeItemCollapsibleState
} from "vscode";
import * as path from "path";
import { DOMParser } from "xmldom";
import { Configuration, NativeCommands } from "../common";
import * as constants from "../constants";
export class XmlTreeDataProvider implements TreeDataProvider<any> {
private _onDidChangeTreeData: EventEmitter<any> = new EventEmitter<any>();
private _xmlDocument: Document;
constructor(private _context: ExtensionContext) {
window.onDidChangeActiveTextEditor(() => {
this._refreshTree();
});
workspace.onDidChangeTextDocument(() => {
this._refreshTree();
});
this._refreshTree();
}
onDidChangeTreeData = this._onDidChangeTreeData.event;
get activeEditor(): TextEditor {
return window.activeTextEditor || null;
}
getTreeItem(element: Node): TreeItem | Thenable<TreeItem> {
const enableMetadata = Configuration.enableXmlTreeViewMetadata;
const enableSync = Configuration.enableXmlTreeViewCursorSync;
const treeItem = new TreeItem(element.localName);
if (!this._isElement(element)) {
treeItem.label = `${element.localName} = "${element.nodeValue}"`;
}
else if (enableMetadata) {
const childAttributes = this._getChildAttributeArray(<Element>element);
const childElements = this._getChildElementArray(<Element>element);
const totalChildren = (childAttributes.length + childElements.length);
if (totalChildren > 0) {
treeItem.label += " (";
if (childAttributes.length > 0) {
treeItem.label += `attributes: ${childAttributes.length}, `;
treeItem.collapsibleState = TreeItemCollapsibleState.Collapsed;
}
if (childElements.length > 0) {
treeItem.label += `children: ${childElements.length}, `;
treeItem.collapsibleState = TreeItemCollapsibleState.Collapsed;
}
treeItem.label = treeItem.label.substr(0, treeItem.label.length - 2);
treeItem.label += ")";
}
if (this._hasSimilarSiblings(<Element>element) && enableSync) {
treeItem.label += ` [line ${(element as any).lineNumber}]`;
}
}
treeItem.command = {
command: constants.nativeCommands.revealLine,
title: "",
arguments: [{
lineNumber: (element as any).lineNumber - 1,
at: "top"
}]
};
treeItem.iconPath = this._getIcon(element);
return treeItem;
}
getChildren(element?: Node): Node[] | Thenable<Node[]> {
if (!this._xmlDocument) {
this._refreshTree();
}
if (this._isElement(element)) {
return [].concat(this._getChildAttributeArray(<Element>element), this._getChildElementArray(<Element>element));
}
else if (this._xmlDocument) {
return [this._xmlDocument.lastChild];
}
else {
return [];
}
}
getParent(element: Node): Node {
if (!element || !element.parentNode || !element.parentNode.parentNode) {
return undefined;
}
return element.parentNode;
}
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;
}
private _getIcon(element: Node): any {
let type = "element";
if (!this._isElement(element)) {
type = "attribute";
}
const icon = {
dark: this._context.asAbsolutePath(path.join("resources", "icons", `${type}.dark.svg`)),
light: this._context.asAbsolutePath(path.join("resources", "icons", `${type}.light.svg`))
};
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);
this._xmlDocument = null;
this._onDidChangeTreeData.fire();
return;
}
const enableTreeView = Configuration.enableXmlTreeView;
NativeCommands.setContext(constants.contextKeys.xmlTreeViewEnabled, enableTreeView);
const xml = this.activeEditor.document.getText();
try {
this._xmlDocument = new DOMParser({
errorHandler: () => {
throw new Error("Invalid Document");
},
locator: {}
}).parseFromString(xml, "text/xml");
}
catch {
this._xmlDocument = new DOMParser().parseFromString("<InvalidDocument />", "text/xml");
}
this._onDidChangeTreeData.fire();
}
}

View file

@ -1,11 +0,0 @@
import * as vsc from "vscode";
export class RangeUtil {
static getRangeForDocument(document: vsc.TextDocument): vsc.Range {
let lastLineIndex = (document.lineCount - 1);
let range = new vsc.Range(new vsc.Position(0, 0), new vsc.Position(lastLineIndex, Number.MAX_VALUE));
range = document.validateRange(range);
return range;
}
}

View file

@ -0,0 +1,102 @@
import { window } from "vscode";
import { TextEditor, TextEditorEdit, ViewColumn } from "vscode";
import { Configuration, ExtensionState } from "../../common";
import * as constants from "../../constants";
import { EvaluatorResult, EvaluatorResultType, XPathEvaluator } from "../xpath-evaluator";
class HistoricQuery {
constructor(uri: string, query: string) {
this.uri = uri;
this.query = query;
}
uri: string;
query: string;
}
export async function evaluateXPath(editor: TextEditor, edit: TextEditorEdit): Promise<void> {
// 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 = Configuration.persistXPathQuery;
// 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 = Configuration.ignoreDefaultNamespace;
// 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(false);
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);
}
}

View file

@ -0,0 +1 @@
export * from "./evaluateXPath";

1
src/xpath/index.ts Normal file
View file

@ -0,0 +1 @@
export * from "./xpath-evaluator";

View 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;
}
}

View file

@ -0,0 +1,30 @@
const child_process = require("child_process");
export class ChildProcess {
static async spawn(executable: string, args: string[]): Promise<void> {
return new Promise<void>((resolve, reject) => {
let output = "";
const handle = child_process.spawn(executable, args);
handle.stdout.on("data", (data: string) => {
output += data;
});
handle.stderr.on("data", (data: string) => {
output += data;
});
handle.on("close", (code: string) => {
if (code === "0") {
resolve();
}
else {
reject({ code: code, message: output });
}
});
});
}
}

View file

@ -0,0 +1,131 @@
import { window, workspace } from "vscode";
import { Disposable, Range, TextEditor, TextEditorEdit, Uri } from "vscode";
import * as constants from "../../constants";
import { ChildProcess } from "../child-process";
import { Configuration, NativeCommands } from "../../common";
export async function executeXQuery(editor: TextEditor, edit: TextEditorEdit): Promise<void> {
// this disposable will be used for creating status bar messages
let disposable: Disposable;
if (editor.document.languageId !== constants.languageIds.xquery) {
window.showErrorMessage("This action can only be performed on an XQuery file.");
return;
}
const executable = Configuration.xqueryExecutionEngine;
let args = Configuration.xqueryExecutionArguments || [];
if (!executable || executable === "") {
const action = await window.showWarningMessage("An XQuery execution engine has not been defined.", "Define Now");
if (action === "Define Now") {
NativeCommands.openGlobalSettings();
}
return;
}
let inputFile: Uri;
disposable = window.setStatusBarMessage("Searching for XML files in folder...");
const searchPattern = Configuration.xqueryExecutionInputSearchPattern;
const inputLimit = Configuration.xqueryExecutionInputLimit;
const files = await workspace.findFiles(searchPattern, "", inputLimit);
disposable.dispose();
// user does not have a folder open - prompt for file name
if (typeof files === "undefined") {
window.showErrorMessage("You must have a folder opened in VS Code to use this feature.");
return;
}
// if there is only one XML file, default it
// otherwise, prompt the user to select one from the open folder
if (files.length > 1) {
const qpItems = new Array<any>();
files.forEach((file) => {
const filename = file.fsPath.replace("\\", "/");
qpItems.push({ // must implement vscode.QuickPickItem
label: filename.substring(filename.lastIndexOf("/") + 1),
description: file.fsPath,
file: file
});
});
const selection = await window.showQuickPick(qpItems, { placeHolder: "Please select an input file." });
if (!selection) {
return;
}
inputFile = selection.file;
}
else {
inputFile = files[0];
}
// prompt for output file name
let outputPath: string = null;
let outputPathPos = -1;
for (let i = 0; i < args.length; i++) {
if (i > 0) {
if (args[i - 1].search(/out|result/)) {
outputPath = args[i];
outputPathPos = i;
}
}
}
if (outputPath) {
outputPath = await window.showInputBox({
placeHolder: "ex. C:\\TEMP\XQueryOutput\\MyOutputFile.xml",
prompt: "Please specify the output file path. Existing file behavior is determined by the execution engine you have specified.",
value: outputPath
});
args[outputPathPos] = outputPath;
}
// call out to the execution engine
disposable = window.setStatusBarMessage("Executing XQuery Script...");
args = args.map<string>((value: string) => {
return value
.replace("$(script)", editor.document.uri.fsPath)
.replace("$(input)", inputFile.fsPath)
.replace("$(project)", workspace.rootPath);
});
try {
await ChildProcess.spawn(executable, args);
}
catch (error) {
if (error.message.search(/[Ll]ine:?\s*\d+/gm) > -1) {
const match: RegExpExecArray = /[Ll]ine:?\s*\d+/gm.exec(error.message);
const line: number = (Number.parseInt(match[0].replace(/([Ll]ine:?\s*)|\s/, "")) - 1);
const selection: string = await window.showErrorMessage(error.message, `Go to Line ${line}`);
if (selection === `Go to Line ${line}`) {
editor.revealRange(new Range(line, 0, line, 0));
}
}
else {
window.showErrorMessage(error.message);
}
}
finally {
disposable.dispose();
}
}

View file

@ -0,0 +1 @@
export * from "./executeXQuery";

View file

@ -0,0 +1 @@
export * from "./child-process";

View file

@ -1,13 +1,19 @@
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"noImplicitAny": true,
"removeComments": true,
"target": "es6"
},
"exclude": [
"node_modules"
]
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "out",
"lib": [
"dom",
"es6"
],
"sourceMap": true,
"rootDir": "src",
"strict": true,
"strictNullChecks": false
},
"exclude": [
"node_modules",
".vscode-test"
]
}

117
tslint.json Normal file
View file

@ -0,0 +1,117 @@
{
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"deprecation": {
"severity": "warn"
},
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"rxjs",
"rxjs/Rx"
],
"import-spacing": true,
"indent": [
true,
"spaces",
4
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
165
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"double"
],
"radix": true,
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}

2088
typings/node/node.d.ts vendored

File diff suppressed because it is too large Load diff

View file

@ -1,3 +0,0 @@
declare class Thenable<T> extends Promise<T> {
// shim for vscode's Thenable<T>
}

View file

@ -1,4 +0,0 @@
declare class XmlNode extends Node {
lineNumber: number;
columnNumber: number;
}

View file

@ -1,197 +0,0 @@
// Type definitions for xpath v0.0.7
// Project: https://github.com/goto100/xpath
// Definitions by: Andrew Bradley <https://github.com/cspotcode/>
// Definitions: https://github.com/borisyankov/DefinitelyTyped
// Some documentation prose is copied from the XPath documentation at https://developer.mozilla.org.
declare module 'xpath' {
// select1 can return any of: `Node`, `boolean`, `string`, `number`.
// select and selectWithResolver can return any of the above return types or `Array<Node>`.
// For this reason, their return types are `any`.
interface SelectFn {
/**
* Evaluate an XPath expression against a DOM node. Returns the result as one of the following:
* * Array<Node>
* * Node
* * boolean
* * number
* * string
* @param xpathText
* @param contextNode
* @param single If true and the evaluation result is one or more Nodes, will return only the first Node instead of an Array<Node>
*/
(xpathText: string, contextNode: Node, single?: boolean): any;
}
var select: SelectFn;
/**
* Evaluate an xpath expression against a DOM node, returning the first result only.
* Equivalent to `select(xpathText, contextNode, true)`
* @param xpathText
* @param contextNode
*/
function select1(xpathText: string, contextNode: Node): any;
/**
* Evaluate an XPath expression against a DOM node using a given namespace resolver. Returns the result as one of the following:
* * Array<Node>
* * Node
* * boolean
* * number
* * string
* @param xpathText
* @param contextNode
* @param resolver
* @param single If true and the evaluation result is one or more Nodes, will return only the first Node instead of an Array<Node>
*/
function selectWithResolver(xpathText: string, contextNode: Node, resolver: XPathNSResolver, single?: boolean): any;
/**
* Evaluate an xpath expression against a DOM.
* @param xpathText xpath expression as a string.
* @param contextNode xpath expression is evaluated relative to this DOM node.
* @param resolver XML namespace resolver
* @param resultType
* @param result If non-null, xpath *may* reuse this XPathResult object instead of creating a new one. However, it is not required to do so.
* @return XPathResult object containing the result of the expression.
*/
function evaluate(xpathText: string, contextNode: Node, resolver: XPathNSResolver, resultType: number, result?: XPathResult): XPathResult;
/**
* Creates a `select` function that uses the given namespace prefix to URI mappings when evaluating queries.
* @param namespaceMappings an object mapping namespace prefixes to namespace URIs. Each key is a prefix; each value is a URI.
* @return a function with the same signature as `xpath.select`
*/
function useNamespaces(namespaceMappings: NamespaceMap): typeof select;
interface NamespaceMap {
[namespacePrefix: string]: string;
}
/**
* Compile an XPath expression into an XPathExpression which can be (repeatedly) evaluated against a DOM.
* @param xpathText XPath expression as a string
* @param namespaceURLMapper Namespace resolver
* @return compiled expression
*/
function createExpression(xpathText: string, namespaceURLMapper: XPathNSResolver): XPathExpression;
/**
* Create an XPathNSResolver that resolves based on the information available in the context of a DOM node.
* @param node
*/
function createNSResolver(node: Node): XPathNSResolver;
/**
* Result of evaluating an XPathExpression.
*/
class XPathResult {
/**
* A result set containing whatever type naturally results from evaluation of the expression. Note that if the result is a node-set then UNORDERED_NODE_ITERATOR_TYPE is always the resulting type.
*/
static ANY_TYPE: number;
/**
* A result containing a single number. This is useful for example, in an XPath expression using the count() function.
*/
static NUMBER_TYPE: number;
/**
* A result containing a single string.
*/
static STRING_TYPE: number;
/**
* A result containing a single boolean value. This is useful for example, in an XPath expression using the not() function.
*/
static BOOLEAN_TYPE: number;
/**
* A result node-set containing all the nodes matching the expression. The nodes may not necessarily be in the same order that they appear in the document.
*/
static UNORDERED_NODE_ITERATOR_TYPE: number;
/**
* A result node-set containing all the nodes matching the expression. The nodes in the result set are in the same order that they appear in the document.
*/
static ORDERED_NODE_ITERATOR_TYPE: number;
/**
* A result node-set containing snapshots of all the nodes matching the expression. The nodes may not necessarily be in the same order that they appear in the document.
*/
static UNORDERED_NODE_SNAPSHOT_TYPE: number;
/**
* A result node-set containing snapshots of all the nodes matching the expression. The nodes in the result set are in the same order that they appear in the document.
*/
static ORDERED_NODE_SNAPSHOT_TYPE: number;
/**
* A result node-set containing any single node that matches the expression. The node is not necessarily the first node in the document that matches the expression.
*/
static ANY_UNORDERED_NODE_TYPE: number;
/**
* A result node-set containing the first node in the document that matches the expression.
*/
static FIRST_ORDERED_NODE_TYPE: number;
/**
* Type of this result. It is one of the enumerated result types.
*/
resultType: number;
/**
* Returns the next node in this result, if this result is one of the _ITERATOR_ result types.
*/
iterateNext(): Node;
/**
* returns the result node for a given index, if this result is one of the _SNAPSHOT_ result types.
* @param index
*/
snapshotItem(index: number): Node;
/**
* Number of nodes in this result, if this result is one of the _SNAPSHOT_ result types.
*/
snapshotLength: number;
/**
* Value of this result, if it is a BOOLEAN_TYPE result.
*/
booleanValue: boolean;
/**
* Value of this result, if it is a NUMBER_TYPE result.
*/
numberValue: number;
/**
* Value of this result, if it is a STRING_TYPE result.
*/
stringValue: string;
/**
* Value of this result, if it is a FIRST_ORDERED_NODE_TYPE result.
*/
singleNodeValue: Node;
}
/**
* A compiled XPath expression, ready to be (repeatedly) evaluated against a DOM node.
*/
interface XPathExpression {
/**
* evaluate this expression against a DOM node.
* @param contextNode
* @param resultType
* @param result
*/
evaluate(contextNode: Node, resultType: number, result?: XPathResult): XPathResult;
}
/**
* Object that can resolve XML namespace prefixes to namespace URIs.
*/
interface XPathNSResolver {
/**
* Given an XML namespace prefix, returns the corresponding XML namespace URI.
* @param prefix XML namespace prefix
* @return XML namespace URI
*/
lookupNamespaceURI(prefix: string): string;
}
}