vNext Rewrite
This commit cleans up and reorganizes the code base, as well as fixes some issues. The intent of the vNext branch is to make the extension more conducive to an open-source environment.
This commit is contained in:
parent
1b9caa3265
commit
eb43a467c8
16 changed files with 617 additions and 192 deletions
52
CONTRIBUTING.md
Normal file
52
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,52 @@
|
|||
# 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!
|
||||
|
||||
## Style Guide
|
||||
In short, favor clean, maintainable code over code that "just works".
|
||||
|
||||
### Imports
|
||||
To keep *.ts files from getting too cluttered, use a namespace alias when pulling in more than 4 or 5 objects. As a general rule, always import the `vscode` namespace using the `vsc` alias for consistency.
|
||||
|
||||
**Favor This**
|
||||
|
||||
`import * as abc from 'abclib';`
|
||||
|
||||
**Over This**
|
||||
|
||||
`import { SomeType, AnotherType, BigClassWithCrazyName, IwantThisToo, VeryAppealingClass, Gimmee } from 'abclib';`
|
||||
|
||||
### 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 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 that might be used by providers.
|
||||
|
||||
### 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.
|
||||
Otherwise, if a command can be implemented in just a few lines, that code can be placed here.
|
||||
|
||||
### 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.
|
|
@ -2,9 +2,5 @@
|
|||
This extension provides additional XML support to [VS Code](https://code.visualstudio.com).
|
||||
|
||||
## Features
|
||||
* XML Formatting (Indent and Linearize)
|
||||
* XPath Evaluation
|
||||
|
||||
## Roadmap
|
||||
* XPath Builder
|
||||
* View XML as Tree
|
||||
* XML Formatting (Indent and Minify)
|
||||
* XPath Evaluation ([Wiki](https://github.com/DotJoshJohnson/vscode-xml/wiki/How-To:-Evaluate-XPath-Queries))
|
25
package.json
25
package.json
|
@ -26,9 +26,10 @@
|
|||
"node": "*"
|
||||
},
|
||||
"categories": [
|
||||
"Languages", "Other"
|
||||
"Languages",
|
||||
"Other"
|
||||
],
|
||||
"main": "./src/main",
|
||||
"main": "./src/Extension",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
|
@ -36,11 +37,11 @@
|
|||
"title": "XML Tools: Format XML"
|
||||
},
|
||||
{
|
||||
"command": "xmltools.linearizeXml",
|
||||
"title": "XML Tools: Linearize XML"
|
||||
"command": "xmlTools.minifyXml",
|
||||
"title": "XML Tools: Minify XML"
|
||||
},
|
||||
{
|
||||
"command": "xmltools.evaluateXPath",
|
||||
"command": "xmlTools.evaluateXPath",
|
||||
"title": "XML Tools: Evaluate XPath"
|
||||
}
|
||||
],
|
||||
|
@ -52,17 +53,22 @@
|
|||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Remember the last XPath query used."
|
||||
},
|
||||
"xmlTools.removeCommentsOnMinify": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Remove XML comments when XML is minified."
|
||||
}
|
||||
}
|
||||
},
|
||||
"keybindings": [
|
||||
{
|
||||
"key": "ctrl+shift+alt+b",
|
||||
"command": "xmltools.formatXml"
|
||||
"command": "xmlTools.formatXml"
|
||||
},
|
||||
{
|
||||
"key": "ctrl+shift+alt+x",
|
||||
"command": "xmltools.evaluateXPath"
|
||||
"command": "xmlTools.evaluateXPath"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -74,12 +80,7 @@
|
|||
"typescript": "^1.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": "^5.1.0",
|
||||
"request": "^2.67.0",
|
||||
"xmldom": "^0.1.19",
|
||||
"xpath": "^0.0.9"
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "node ./node_modules/vscode/bin/compile -watch -p ./"
|
||||
}
|
||||
}
|
33
src/Commands.ts
Normal file
33
src/Commands.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
'use strict';
|
||||
|
||||
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';
|
||||
|
||||
const CFG_SECTION: string = 'xmlTools';
|
||||
const CFG_REMOVE_COMMENTS: string = 'removeCommentsOnMinify';
|
||||
|
||||
export class TextEditorCommands {
|
||||
static formatXml(editor: vsc.TextEditor, edit: vsc.TextEditorEdit): void {
|
||||
// alias for editor.action.format
|
||||
vsc.commands.executeCommand('editor.action.format');
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
29
src/Extension.ts
Normal file
29
src/Extension.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
'use strict';
|
||||
|
||||
import * as vsc from 'vscode';
|
||||
import { TextEditorCommands } from './Commands';
|
||||
import { XmlDocumentFormattingEditProvider, XmlRangeFormattingEditProvider } from './providers/Formatting';
|
||||
|
||||
export var GlobalState: vsc.Memento;
|
||||
export var WorkspaceState: vsc.Memento;
|
||||
|
||||
const LANG_XML: string = 'xml';
|
||||
|
||||
export function activate(ctx: vsc.ExtensionContext) {
|
||||
// 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.formatXml', TextEditorCommands.formatXml),
|
||||
vsc.commands.registerTextEditorCommand('xmlTools.evaluateXPath', TextEditorCommands.evaluateXPath)
|
||||
);
|
||||
|
||||
// register language feature providers
|
||||
ctx.subscriptions.push(
|
||||
vsc.languages.registerDocumentFormattingEditProvider(LANG_XML, new XmlDocumentFormattingEditProvider()),
|
||||
vsc.languages.registerDocumentRangeFormattingEditProvider(LANG_XML, new XmlRangeFormattingEditProvider())
|
||||
);
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import {
|
||||
Range,
|
||||
TextEdit,
|
||||
TextEditor,
|
||||
TextDocument,
|
||||
TextEditorEdit,
|
||||
CancellationToken,
|
||||
FormattingOptions,
|
||||
DocumentFormattingEditProvider,
|
||||
DocumentRangeFormattingEditProvider
|
||||
} from 'vscode';
|
||||
|
||||
import { getRangeForDocument } from '../utils/RangeUtils';
|
||||
|
||||
export function linearizeXml(editor: TextEditor, edit: TextEditorEdit): void {
|
||||
let current = editor.document.getText();
|
||||
let range = getRangeForDocument(editor.document);
|
||||
|
||||
edit.replace(range, _linearizeXml(current));
|
||||
}
|
||||
|
||||
export class XmlDocumentFormattingProvider implements DocumentFormattingEditProvider {
|
||||
provideDocumentFormattingEdits(document: TextDocument, options: FormattingOptions, token: CancellationToken): TextEdit[] {
|
||||
let current = document.getText();
|
||||
let range = getRangeForDocument(document);
|
||||
|
||||
return [TextEdit.replace(range, _formatXml(current, options))];
|
||||
}
|
||||
}
|
||||
|
||||
export class XmlRangeFormattingProvider implements DocumentRangeFormattingEditProvider {
|
||||
provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): TextEdit[] {
|
||||
let current = document.getText(range);
|
||||
|
||||
return [TextEdit.replace(range, _formatXml(current, options))];
|
||||
}
|
||||
}
|
||||
|
||||
function _strRepeat(source: string, count: number): string {
|
||||
let output = '';
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
output += source;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function _linearizeXml(xml: string): string {
|
||||
return xml.replace(/>\s{0,}</g, '><');
|
||||
}
|
||||
|
||||
function _formatXml(xml: string, options: FormattingOptions): string {
|
||||
let tab = _strRepeat(' ', options.tabSize);
|
||||
let output = '';
|
||||
let level = 0;
|
||||
|
||||
// linearize the xml first for a consistent starting point
|
||||
xml = _linearizeXml(xml);
|
||||
|
||||
// put each tag on its own line
|
||||
xml = xml.replace(/></g, '>\n<');
|
||||
|
||||
// iterate over each line and plug in tabs
|
||||
let tokens = xml.split('\n');
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
let line = tokens[i];
|
||||
|
||||
// start tags
|
||||
let startMatch = /<[\w\d]+[^\/]*>/.exec(line);
|
||||
if (startMatch !== null && startMatch[0] == line) {
|
||||
output += _strRepeat(tab, level++) + line + '\n';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// close tags
|
||||
let closeMatch = /<\s*\/\s*[\w\d]+>/.exec(line);
|
||||
if (closeMatch !== null && closeMatch[0] == line) {
|
||||
output += _strRepeat(tab, --level) + line + '\n';
|
||||
}
|
||||
|
||||
// one-liners (items that do not affect level)
|
||||
else {
|
||||
output += _strRepeat(tab, level) + line + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { window, TextEditor, TextEditorEdit, OutputChannel, ViewColumn, workspace } from 'vscode';
|
||||
|
||||
let xpath = require('xpath');
|
||||
let dom = require('xmldom').DOMParser;
|
||||
let resultChannel: OutputChannel = null;
|
||||
|
||||
export var lastXPath: string;
|
||||
|
||||
export function evaluateXPath(editor: TextEditor, edit: TextEditorEdit): void {
|
||||
let isPersistant = workspace.getConfiguration().has('xmlTools.persistXPathQuery') && workspace.getConfiguration('xmlTools').get<boolean>('persistXPathQuery') === true
|
||||
|
||||
window.showInputBox({
|
||||
placeHolder: 'XPath Query',
|
||||
prompt: 'Please enter an XPath query to evaluate.',
|
||||
value: isPersistant ? lastXPath : ''
|
||||
|
||||
}).then((query) => {
|
||||
if (query === undefined) return;
|
||||
|
||||
let xml = editor.document.getText();
|
||||
let doc = new dom().parseFromString(xml);
|
||||
|
||||
try {
|
||||
var nodes = xpath.select(query, doc);
|
||||
}
|
||||
|
||||
catch (ex) {
|
||||
window.showErrorMessage(ex);
|
||||
return;
|
||||
}
|
||||
|
||||
lastXPath = query;
|
||||
|
||||
if (nodes === null || nodes === undefined || nodes.length == 0) {
|
||||
window.showInformationMessage('Your XPath query returned no results.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (resultChannel === null) resultChannel = window.createOutputChannel('XPath Evaluation Results');
|
||||
resultChannel.clear();
|
||||
|
||||
nodes.forEach((node) => {
|
||||
resultChannel.appendLine(`${node.localName}: ${node.firstChild.data}`);
|
||||
});
|
||||
|
||||
resultChannel.show(ViewColumn.Three);
|
||||
});
|
||||
}
|
20
src/main.ts
20
src/main.ts
|
@ -1,20 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { commands, languages, ExtensionContext } from 'vscode';
|
||||
import { linearizeXml, XmlDocumentFormattingProvider, XmlRangeFormattingProvider } from './features/xmlFormatting';
|
||||
import { evaluateXPath } from './features/xmlXPathEngine';
|
||||
|
||||
export function activate(ctx: ExtensionContext) {
|
||||
// register palette commands
|
||||
ctx.subscriptions.push(commands.registerTextEditorCommand('xmltools.linearizeXml', linearizeXml));
|
||||
ctx.subscriptions.push(commands.registerTextEditorCommand('xmltools.evaluateXPath', evaluateXPath));
|
||||
|
||||
// alias for editor.action.format
|
||||
ctx.subscriptions.push(commands.registerTextEditorCommand('xmlTools.formatXml', () => {
|
||||
commands.executeCommand('editor.action.format');
|
||||
}));
|
||||
|
||||
// register formatting providers
|
||||
ctx.subscriptions.push(languages.registerDocumentFormattingEditProvider('xml', new XmlDocumentFormattingProvider()));
|
||||
ctx.subscriptions.push(languages.registerDocumentRangeFormattingEditProvider('xml', new XmlRangeFormattingProvider()));
|
||||
}
|
24
src/providers/Formatting.ts
Normal file
24
src/providers/Formatting.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
'use strict';
|
||||
|
||||
import * as vsc from 'vscode';
|
||||
import { RangeUtil } from '../utils/RangeUtil';
|
||||
import { XmlFormatter } from '../services/XmlFormatter';
|
||||
|
||||
export class XmlDocumentFormattingEditProvider implements vsc.DocumentFormattingEditProvider {
|
||||
provideDocumentFormattingEdits(document: vsc.TextDocument, options: vsc.FormattingOptions): vsc.TextEdit[] {
|
||||
let range = RangeUtil.getRangeForDocument(document);
|
||||
let formatter = new XmlFormatter(options.insertSpaces, options.tabSize);
|
||||
let xml = formatter.format(document.getText());
|
||||
|
||||
return [ vsc.TextEdit.replace(range, xml) ];
|
||||
}
|
||||
}
|
||||
|
||||
export class XmlRangeFormattingEditProvider implements vsc.DocumentRangeFormattingEditProvider {
|
||||
provideDocumentRangeFormattingEdits(document: vsc.TextDocument, range: vsc.Range, options: vsc.FormattingOptions): vsc.TextEdit[] {
|
||||
let formatter = new XmlFormatter(options.insertSpaces, options.tabSize);
|
||||
let xml = formatter.format(document.getText());
|
||||
|
||||
return [ vsc.TextEdit.replace(range, xml) ];
|
||||
}
|
||||
}
|
107
src/providers/XPath.ts
Normal file
107
src/providers/XPath.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
'use strict';
|
||||
|
||||
import * as vsc from 'vscode';
|
||||
import * as ext from '../Extension';
|
||||
import { XPathEvaluator } from '../services/XPathEvaluator';
|
||||
|
||||
const CFG_SECTION: string = 'xmlTools';
|
||||
const CFG_PERSIST_QUERY: string = 'persistXPathQuery';
|
||||
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) {
|
||||
|
||||
// run the query
|
||||
let xml: string = editor.document.getText();
|
||||
let nodes: Node[] = XPathEvaluator.evaluate(query, xml);
|
||||
|
||||
// show the results to the user
|
||||
let outputChannel: vsc.OutputChannel = vsc.window.createOutputChannel(OUTPUT_CHANNEL);
|
||||
outputChannel.clear();
|
||||
|
||||
outputChannel.appendLine(`XPath Query: ${query}`);
|
||||
outputChannel.append('');
|
||||
|
||||
nodes.forEach((node: Node) => {
|
||||
outputChannel.appendLine(`${node.localName}: ${node.textContent}`);
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
23
src/services/XPathEvaluator.ts
Normal file
23
src/services/XPathEvaluator.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
import * as xpath from 'xpath';
|
||||
|
||||
let DOMParser = require('xmldom').DOMParser;
|
||||
|
||||
export class XPathEvaluator {
|
||||
static evaluate(query: string, xml: string): Node[] {
|
||||
let nodes: Node[] = new Array<Node>();
|
||||
|
||||
let xdoc: Document = new DOMParser().parseFromString(xml, 'text/xml');
|
||||
let resolver: xpath.XPathNSResolver = xpath.createNSResolver(xdoc);
|
||||
let expression: xpath.XPathExpression = xpath.createExpression(query, resolver);
|
||||
let result: xpath.XPathResult = expression.evaluate(xdoc, xpath.XPathResult.ORDERED_NODE_ITERATOR_TYPE);
|
||||
|
||||
let node: Node;
|
||||
while (node = result.iterateNext()) {
|
||||
nodes.push(node);
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
}
|
122
src/services/XmlFormatter.ts
Normal file
122
src/services/XmlFormatter.ts
Normal file
|
@ -0,0 +1,122 @@
|
|||
'use strict';
|
||||
|
||||
// Based on pretty-data (https://github.com/vkiryukhin/pretty-data)
|
||||
export class XmlFormatter {
|
||||
constructor(preferSpaces?: boolean, tabSize?: number, newLine?: string) {
|
||||
if (typeof preferSpaces === 'undefined') {
|
||||
preferSpaces = false;
|
||||
}
|
||||
|
||||
tabSize = tabSize || 4;
|
||||
newLine = newLine || '\n';
|
||||
|
||||
this.newLine = newLine || '\n';
|
||||
this.indentPattern = (preferSpaces) ? ' '.repeat(tabSize) : '\t';
|
||||
}
|
||||
|
||||
newLine: string;
|
||||
indentPattern: string;
|
||||
|
||||
format(xml: string): string {
|
||||
xml = this.minify(xml, false);
|
||||
|
||||
let parts: string[] = xml
|
||||
.replace(/</g,"~::~<")
|
||||
.replace(/xmlns\:/g,"~::~xmlns:")
|
||||
.replace(/xmlns\=/g,"~::~xmlns=")
|
||||
.split('~::~');
|
||||
|
||||
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) {
|
||||
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 (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 = (removeComments) ? xml.replace(/\<![ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t]*)\>/g, '') : xml;
|
||||
xml = xml.replace(/>\s{0,}</g, '><');
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
private _getIndent(level: number, trailingValue?: string): string {
|
||||
trailingValue = trailingValue || '';
|
||||
|
||||
return `${this.newLine}${this.indentPattern.repeat(level)}${trailingValue}`;
|
||||
}
|
||||
}
|
13
src/utils/RangeUtil.ts
Normal file
13
src/utils/RangeUtil.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
'use strict';
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { TextDocument, Range, Position } from 'vscode';
|
||||
|
||||
export function getRangeForDocument(document: TextDocument): Range {
|
||||
let lastLineIndex = (document.lineCount - 1);
|
||||
let range = new Range(new Position(0, 0), new Position(lastLineIndex, Number.MAX_VALUE));
|
||||
|
||||
range = document.validateRange(range);
|
||||
|
||||
return range;
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"removeComments": true,
|
||||
"target": "es6"
|
||||
},
|
||||
|
||||
|
|
197
typings/xpath/xpath.d.ts
vendored
Normal file
197
typings/xpath/xpath.d.ts
vendored
Normal file
|
@ -0,0 +1,197 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue