From f170c10b6ce6b135834056dcbf02b2bcdb0a9b27 Mon Sep 17 00:00:00 2001 From: Joe Hildebrand Date: Mon, 8 May 2023 10:34:54 -0600 Subject: [PATCH] Lots of minor xpath futzing --- README.md | 2 +- cli/bin/ewq.js | 7 +- docs/AttlistDecl.html | 16 +- docs/Attribute.html | 21 +- docs/AttributeDecl.html | 19 +- docs/CdataSection.html | 14 +- docs/Comment.html | 19 +- docs/DoctypeDecl.html | 14 +- docs/Document.html | 25 ++- docs/DomParser.html | 2 +- docs/Element.html | 24 +-- docs/ElementDecl.html | 19 +- docs/EntityDecl.html | 19 +- docs/Namespace.html | 19 +- docs/Namespace_Namespace.html | 4 +- docs/Node.html | 19 +- docs/NotationDecl.html | 19 +- docs/ParentNode.html | 12 +- docs/ProcessingInstruction.html | 19 +- docs/Text.html | 21 +- docs/XPath.html | 6 +- docs/XPathSyntaxError.html | 2 +- docs/XmlDeclaration.html | 19 +- docs/dom.js.html | 33 +-- docs/domParser.js.html | 2 +- docs/external-ConstructorParameters.html | 4 +- docs/external-Exclude.html | 4 +- docs/external-Extract.html | 4 +- docs/external-InstanceType.html | 4 +- docs/external-NonNullable.html | 4 +- docs/external-Omit.html | 4 +- docs/external-OmitThisParameter.html | 4 +- docs/external-Parameters.html | 4 +- docs/external-Partial.html | 4 +- docs/external-Pick.html | 4 +- docs/external-Readonly.html | 4 +- docs/external-Record.html | 4 +- docs/external-Required.html | 4 +- docs/external-ReturnType.html | 4 +- docs/external-ThisParameterType.html | 4 +- docs/external-ThisType.html | 4 +- docs/global.html | 254 ++++++++++++++++++++++- docs/index.html | 16 +- docs/xpath.js.html | 62 +++++- lib/dom.js | 4 +- lib/xpath.js | 49 ++++- lib/xpathPattern3.js | 162 +++++++-------- lib/xpathPattern3.pegjs | 14 +- test/xpath.ava.js | 21 ++ types/dom.d.ts | 4 +- types/xpath.d.ts | 19 +- 51 files changed, 714 insertions(+), 331 deletions(-) diff --git a/README.md b/README.md index 5f4b869..2c18a17 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ console.log(doc.root.toString()) console.log(doc.get('//bar')) ``` -Note that there is an implementation of XPath3 included. It is not +Note that there is an implementation of XPath3 included. It is not in any way feature-complete yet, but it has a bunch of useful features already. Full [documentation](https://hildjj.github.io/expat-wasm-dom/) is available. diff --git a/cli/bin/ewq.js b/cli/bin/ewq.js index 8837392..5a7430c 100755 --- a/cli/bin/ewq.js +++ b/cli/bin/ewq.js @@ -2,6 +2,7 @@ /* eslint-disable no-console */ import {Command} from 'commander' +import {dom} from 'expat-wasm-dom' import {parse} from '../lib/entity.js' import {sharedOptions} from '../lib/shared.js' @@ -21,7 +22,11 @@ async function main() { const doc = await parse(arg, opts) const res = doc.get(xpath) for (const r of res) { - console.log(r.toString({colors: process.stdout.isTTY, depth: Infinity})) + if (r instanceof dom.Node) { + console.log(r.toString({colors: process.stdout.isTTY, depth: Infinity})) + } else { + console.log(r) + } } } } diff --git a/docs/AttlistDecl.html b/docs/AttlistDecl.html index bc183e8..cc96fcf 100644 --- a/docs/AttlistDecl.html +++ b/docs/AttlistDecl.html @@ -29,7 +29,7 @@ @@ -84,7 +84,7 @@

new Attlis
Source:
@@ -348,7 +348,7 @@

addSource:
@@ -513,7 +513,7 @@

Source:
@@ -623,7 +623,7 @@

Source:
@@ -733,7 +733,7 @@

Source:
@@ -942,7 +942,7 @@

textSource:
@@ -1121,7 +1121,7 @@

toStringSource:
diff --git a/docs/Attribute.html b/docs/Attribute.html index d14fbdd..2d3cf6a 100644 --- a/docs/Attribute.html +++ b/docs/Attribute.html @@ -29,7 +29,7 @@ @@ -84,7 +84,7 @@

new Attribut
Source:
@@ -279,7 +279,7 @@

Source:
@@ -348,7 +348,7 @@

Source:
@@ -421,7 +421,7 @@

Methods

-

first(pattern, contextopt) → {string|Node|null}

+

first(pattern, contextopt) → {string|Node|number|null}

@@ -437,7 +437,7 @@

firstSource:
@@ -634,6 +634,9 @@
Returns:
Node | +number +| + null @@ -667,7 +670,7 @@

getSource:
@@ -890,7 +893,7 @@

textSource:
@@ -1068,7 +1071,7 @@

toStringSource:
diff --git a/docs/AttributeDecl.html b/docs/AttributeDecl.html index 2ea1df5..ad7bbc8 100644 --- a/docs/AttributeDecl.html +++ b/docs/AttributeDecl.html @@ -29,7 +29,7 @@ @@ -84,7 +84,7 @@

new Attr
Source:
@@ -331,7 +331,7 @@

Source:
@@ -400,7 +400,7 @@

Source:
@@ -473,7 +473,7 @@

Methods

-

first(pattern, contextopt) → {string|Node|null}

+

first(pattern, contextopt) → {string|Node|number|null}

@@ -489,7 +489,7 @@

firstSource:
@@ -686,6 +686,9 @@
Returns:
Node | +number +| + null @@ -719,7 +722,7 @@

getSource:
@@ -1120,7 +1123,7 @@

toStringSource:
diff --git a/docs/CdataSection.html b/docs/CdataSection.html index b9de7a5..2112996 100644 --- a/docs/CdataSection.html +++ b/docs/CdataSection.html @@ -29,7 +29,7 @@ @@ -81,7 +81,7 @@

new Cdata
Source:
@@ -443,7 +443,7 @@

Source:
@@ -553,7 +553,7 @@

Source:
@@ -663,7 +663,7 @@

Source:
@@ -872,7 +872,7 @@

textSource:
@@ -1051,7 +1051,7 @@

toStringSource:
diff --git a/docs/Comment.html b/docs/Comment.html index ad8631f..e2c2607 100644 --- a/docs/Comment.html +++ b/docs/Comment.html @@ -29,7 +29,7 @@ @@ -84,7 +84,7 @@

new CommentSource:
@@ -252,7 +252,7 @@

Source:
@@ -321,7 +321,7 @@

Source:
@@ -394,7 +394,7 @@

Methods

-

first(pattern, contextopt) → {string|Node|null}

+

first(pattern, contextopt) → {string|Node|number|null}

@@ -410,7 +410,7 @@

firstSource:
@@ -607,6 +607,9 @@
Returns:
Node | +number +| + null @@ -640,7 +643,7 @@

getSource:
@@ -1041,7 +1044,7 @@

toStringSource:
diff --git a/docs/DoctypeDecl.html b/docs/DoctypeDecl.html index 2e71106..7e925a4 100644 --- a/docs/DoctypeDecl.html +++ b/docs/DoctypeDecl.html @@ -29,7 +29,7 @@ @@ -84,7 +84,7 @@

new Doctyp
Source:
@@ -622,7 +622,7 @@

Source:
@@ -732,7 +732,7 @@

Source:
@@ -842,7 +842,7 @@

Source:
@@ -1051,7 +1051,7 @@

textSource:
@@ -1230,7 +1230,7 @@

toStringSource:
diff --git a/docs/Document.html b/docs/Document.html index 137c51d..b8bbe43 100644 --- a/docs/Document.html +++ b/docs/Document.html @@ -29,7 +29,7 @@ @@ -84,7 +84,7 @@

new Document<
Source:
@@ -278,7 +278,7 @@

Source:
@@ -520,7 +520,7 @@

Source:
@@ -630,7 +630,7 @@

Source:
@@ -740,7 +740,7 @@

Source:
@@ -931,7 +931,7 @@
Returns:
-

first(pattern, contextopt) → {string|Node|null}

+

first(pattern, contextopt) → {string|number|Node|null}

@@ -947,7 +947,7 @@

firstSource:
@@ -1136,6 +1136,9 @@
Returns:
string | +number +| + Node | @@ -1172,7 +1175,7 @@

getSource:
@@ -1392,7 +1395,7 @@

textSource:
@@ -1571,7 +1574,7 @@

toStringSource:
diff --git a/docs/DomParser.html b/docs/DomParser.html index 00e68b7..20c628e 100644 --- a/docs/DomParser.html +++ b/docs/DomParser.html @@ -29,7 +29,7 @@ diff --git a/docs/Element.html b/docs/Element.html index 4b8c258..c5e6107 100644 --- a/docs/Element.html +++ b/docs/Element.html @@ -29,7 +29,7 @@ @@ -84,7 +84,7 @@

new ElementSource:
@@ -439,7 +439,7 @@

_attribute<
Source:
@@ -809,7 +809,7 @@

attrSource:
@@ -1017,7 +1017,7 @@

Source:
@@ -1127,7 +1127,7 @@

Source:
@@ -1236,7 +1236,7 @@

elementSource:
@@ -1443,7 +1443,7 @@

Source:
@@ -1650,7 +1650,7 @@

remove
Source:
@@ -1814,7 +1814,7 @@

setAttrib
Source:
@@ -2001,7 +2001,7 @@

textSource:
@@ -2180,7 +2180,7 @@

toStringSource:
diff --git a/docs/ElementDecl.html b/docs/ElementDecl.html index 480b9e2..98a3ecc 100644 --- a/docs/ElementDecl.html +++ b/docs/ElementDecl.html @@ -29,7 +29,7 @@ @@ -84,7 +84,7 @@

new Elemen
Source:
@@ -283,7 +283,7 @@

Source:
@@ -352,7 +352,7 @@

Source:
@@ -425,7 +425,7 @@

Methods

-

first(pattern, contextopt) → {string|Node|null}

+

first(pattern, contextopt) → {string|Node|number|null}

@@ -441,7 +441,7 @@

firstSource:
@@ -638,6 +638,9 @@
Returns:
Node | +number +| + null @@ -671,7 +674,7 @@

getSource:
@@ -1072,7 +1075,7 @@

toStringSource:
diff --git a/docs/EntityDecl.html b/docs/EntityDecl.html index 800d021..e8269ba 100644 --- a/docs/EntityDecl.html +++ b/docs/EntityDecl.html @@ -29,7 +29,7 @@ @@ -84,7 +84,7 @@

new EntityD
Source:
@@ -403,7 +403,7 @@

Source:
@@ -472,7 +472,7 @@

Source:
@@ -545,7 +545,7 @@

Methods

-

first(pattern, contextopt) → {string|Node|null}

+

first(pattern, contextopt) → {string|Node|number|null}

@@ -561,7 +561,7 @@

firstSource:
@@ -758,6 +758,9 @@
Returns:
Node | +number +| + null @@ -791,7 +794,7 @@

getSource:
@@ -1192,7 +1195,7 @@

toStringSource:
diff --git a/docs/Namespace.html b/docs/Namespace.html index d31c9fc..ac7e014 100644 --- a/docs/Namespace.html +++ b/docs/Namespace.html @@ -29,7 +29,7 @@ @@ -80,7 +80,7 @@

new Namespac
Source:
@@ -198,7 +198,7 @@

Source:
@@ -267,7 +267,7 @@

Source:
@@ -340,7 +340,7 @@

Methods

-

first(pattern, contextopt) → {string|Node|null}

+

first(pattern, contextopt) → {string|Node|number|null}

@@ -356,7 +356,7 @@

firstSource:
@@ -553,6 +553,9 @@
Returns:
Node | +number +| + null @@ -586,7 +589,7 @@

getSource:
@@ -987,7 +990,7 @@

toStringSource:
diff --git a/docs/Namespace_Namespace.html b/docs/Namespace_Namespace.html index 1d852c4..00c516e 100644 --- a/docs/Namespace_Namespace.html +++ b/docs/Namespace_Namespace.html @@ -29,7 +29,7 @@ @@ -82,7 +82,7 @@

new Namespac
Source:
diff --git a/docs/Node.html b/docs/Node.html index 9783b3a..fe732d7 100644 --- a/docs/Node.html +++ b/docs/Node.html @@ -29,7 +29,7 @@ @@ -84,7 +84,7 @@

Source:
@@ -191,7 +191,7 @@

Source:
@@ -255,7 +255,7 @@

Source:
@@ -323,7 +323,7 @@

Methods

-

first(pattern, contextopt) → {string|Node|null}

+

first(pattern, contextopt) → {string|Node|number|null}

@@ -339,7 +339,7 @@

firstSource:
@@ -531,6 +531,9 @@
Returns:
Node | +number +| + null @@ -564,7 +567,7 @@

getSource:
@@ -955,7 +958,7 @@

Source:
diff --git a/docs/NotationDecl.html b/docs/NotationDecl.html index f6335c2..b6b1492 100644 --- a/docs/NotationDecl.html +++ b/docs/NotationDecl.html @@ -29,7 +29,7 @@ @@ -84,7 +84,7 @@

new Notat
Source:
@@ -371,7 +371,7 @@

Source:
@@ -440,7 +440,7 @@

Source:
@@ -513,7 +513,7 @@

Methods

-

first(pattern, contextopt) → {string|Node|null}

+

first(pattern, contextopt) → {string|Node|number|null}

@@ -529,7 +529,7 @@

firstSource:
@@ -726,6 +726,9 @@
Returns:
Node | +number +| + null @@ -759,7 +762,7 @@

getSource:
@@ -1160,7 +1163,7 @@

toStringSource:
diff --git a/docs/ParentNode.html b/docs/ParentNode.html index abc4e9c..5ff15c9 100644 --- a/docs/ParentNode.html +++ b/docs/ParentNode.html @@ -29,7 +29,7 @@ @@ -434,7 +434,7 @@

Source:
@@ -539,7 +539,7 @@

Source:
@@ -644,7 +644,7 @@

Source:
@@ -848,7 +848,7 @@

textSource:
@@ -1023,7 +1023,7 @@

toStringSource:
diff --git a/docs/ProcessingInstruction.html b/docs/ProcessingInstruction.html index d14517f..e68d396 100644 --- a/docs/ProcessingInstruction.html +++ b/docs/ProcessingInstruction.html @@ -29,7 +29,7 @@ @@ -85,7 +85,7 @@

Source:
@@ -304,7 +304,7 @@

Source:
@@ -373,7 +373,7 @@

Source:
@@ -446,7 +446,7 @@

Methods

-

first(pattern, contextopt) → {string|Node|null}

+

first(pattern, contextopt) → {string|Node|number|null}

@@ -462,7 +462,7 @@

firstSource:
@@ -659,6 +659,9 @@
Returns:
Node | +number +| + null @@ -692,7 +695,7 @@

getSource:
@@ -1093,7 +1096,7 @@

toStringSource:
diff --git a/docs/Text.html b/docs/Text.html index 3f26579..5b2c022 100644 --- a/docs/Text.html +++ b/docs/Text.html @@ -29,7 +29,7 @@ @@ -84,7 +84,7 @@

new TextSource:
@@ -252,7 +252,7 @@

Source:
@@ -321,7 +321,7 @@

Source:
@@ -394,7 +394,7 @@

Methods

-

first(pattern, contextopt) → {string|Node|null}

+

first(pattern, contextopt) → {string|Node|number|null}

@@ -410,7 +410,7 @@

firstSource:
@@ -607,6 +607,9 @@
Returns:
Node | +number +| + null @@ -640,7 +643,7 @@

getSource:
@@ -863,7 +866,7 @@

textSource:
@@ -1042,7 +1045,7 @@

toStringSource:
diff --git a/docs/XPath.html b/docs/XPath.html index f3a5fa0..811c6ea 100644 --- a/docs/XPath.html +++ b/docs/XPath.html @@ -29,7 +29,7 @@ @@ -84,7 +84,7 @@

new XPathSource:
@@ -305,7 +305,7 @@

executeSource:
diff --git a/docs/XPathSyntaxError.html b/docs/XPathSyntaxError.html index 2756ab4..9a5ddb6 100644 --- a/docs/XPathSyntaxError.html +++ b/docs/XPathSyntaxError.html @@ -29,7 +29,7 @@ diff --git a/docs/XmlDeclaration.html b/docs/XmlDeclaration.html index ff7d49d..b082e95 100644 --- a/docs/XmlDeclaration.html +++ b/docs/XmlDeclaration.html @@ -29,7 +29,7 @@ @@ -84,7 +84,7 @@

new Xml
Source:
@@ -357,7 +357,7 @@

Source:
@@ -426,7 +426,7 @@

Source:
@@ -499,7 +499,7 @@

Methods

-

first(pattern, contextopt) → {string|Node|null}

+

first(pattern, contextopt) → {string|Node|number|null}

@@ -515,7 +515,7 @@

firstSource:
@@ -712,6 +712,9 @@
Returns:
Node | +number +| + null @@ -745,7 +748,7 @@

getSource:
@@ -1146,7 +1149,7 @@

toStringSource:
diff --git a/docs/dom.js.html b/docs/dom.js.html index c7963f6..ee6cc5e 100644 --- a/docs/dom.js.html +++ b/docs/dom.js.html @@ -29,7 +29,7 @@ @@ -45,7 +45,8 @@

dom.js

-
import {XPath} from './xpath.js'
+            
/* eslint-disable no-use-before-define */
+import {XPath} from './xpath.js'
 import util from 'util'
 
 const TOSTRING_OPTS = {
@@ -215,7 +216,7 @@ 

dom.js

* @param {string|XPath} pattern The pattern to match * @param {Node} [context=this] The context node. Uses this node as context * if none is provided. - * @returns {string|Node|null} The first match if one exists + * @returns {string|Node|number|null} The first match if one exists */ first(pattern, context) { const res = this.get(pattern, context) @@ -238,7 +239,6 @@

dom.js

cur = cur.parent } - // eslint-disable-next-line no-use-before-define if (cur instanceof Document) { return cur } @@ -255,7 +255,7 @@

dom.js

* @returns {string} The escaped string */ static escape(str, attrib = false) { - const re = attrib ? /[<&"]/g : /[<&]/g + const re = attrib ? /[<&"']/g : /[<&]/g const ret = str.replace(re, c => { switch (c) { case '<': return '&lt;' @@ -308,6 +308,17 @@

dom.js

* @returns {Node} The added node */ add(node) { + if ( + (node instanceof Text) && + (this.children.length > 0) && + (this.children[this.children.length - 1] instanceof Text) + ) { + const prev = this.children[this.children.length - 1] + // @ts-ignore + prev.txt += node.txt + return prev + } + node.parent = this this.children.push(node) return node @@ -360,7 +371,6 @@

dom.js

*/ *descendantElements() { for (const c of this.children) { - // eslint-disable-next-line no-use-before-define if (c instanceof Element) { yield c yield *c.descendantElements() @@ -380,7 +390,6 @@

dom.js

*/ *elements(local, ns) { for (const c of this.children) { - // eslint-disable-next-line no-use-before-define if ((c instanceof Element) && (!ns || (ns === c.name.ns)) && (!local || (local === c.name.local))) { @@ -431,7 +440,6 @@

dom.js

*/ get root() { return /** @type {Element|null} */ ( - // eslint-disable-next-line no-use-before-define this.children.find(c => c instanceof Element) ) } @@ -455,7 +463,7 @@

dom.js

* @param {string|XPath} pattern The pattern to match * @param {Node} [context=this.root] The context node. Uses the root element * as context if none is provided. - * @returns {string|Node|null} The first match if one exists + * @returns {string|number|Node|null} The first match if one exists */ first(pattern, context) { return super.first(pattern, context || this.root || undefined) @@ -490,9 +498,7 @@

dom.js

constructor(name, attribs = [], ns = []) { super() this.name = (typeof name === 'string') ? {local: name} : name - // eslint-disable-next-line no-use-before-define this.att = attribs.map(([n, v]) => new Attribute(n, v)) - // eslint-disable-next-line no-use-before-define this.ns = ns.map(([prefix, uri]) => new Namespace(prefix, uri)) } @@ -539,7 +545,6 @@

dom.js

if (a) { a.value = value } else { - // eslint-disable-next-line no-use-before-define a = new Attribute(name, value) this.att.push(a) } @@ -597,7 +602,7 @@

dom.js

if (newText == null) { return super.text() } - // eslint-disable-next-line no-use-before-define + const t = new Text(newText) t.parent = this this.children = [t] @@ -768,7 +773,6 @@

dom.js

* @returns {string} The node, converted to a string */ toString(options = TOSTRING_OPTS) { - // eslint-disable-next-line no-use-before-define if (this.parent instanceof CdataSection) { // TODO: escape "]]>" ? // replace ]]> by ]]]><![CDATA[]> @@ -1236,7 +1240,6 @@

dom.js

* @returns {Node} The added node */ add(node) { - // eslint-disable-next-line no-use-before-define if (node instanceof AttributeDecl || !this.parent) { return super.add(node) } diff --git a/docs/domParser.js.html b/docs/domParser.js.html index 99788e7..902eb33 100644 --- a/docs/domParser.js.html +++ b/docs/domParser.js.html @@ -29,7 +29,7 @@ diff --git a/docs/external-ConstructorParameters.html b/docs/external-ConstructorParameters.html index fb02c20..81e767b 100644 --- a/docs/external-ConstructorParameters.html +++ b/docs/external-ConstructorParameters.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/external-Exclude.html b/docs/external-Exclude.html index 97ead99..8e770f4 100644 --- a/docs/external-Exclude.html +++ b/docs/external-Exclude.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/external-Extract.html b/docs/external-Extract.html index 2e02b13..3e1f1aa 100644 --- a/docs/external-Extract.html +++ b/docs/external-Extract.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/external-InstanceType.html b/docs/external-InstanceType.html index 1d59e02..333baa9 100644 --- a/docs/external-InstanceType.html +++ b/docs/external-InstanceType.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/external-NonNullable.html b/docs/external-NonNullable.html index 6d8564e..961e211 100644 --- a/docs/external-NonNullable.html +++ b/docs/external-NonNullable.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/external-Omit.html b/docs/external-Omit.html index b2d7f7d..72bee42 100644 --- a/docs/external-Omit.html +++ b/docs/external-Omit.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/external-OmitThisParameter.html b/docs/external-OmitThisParameter.html index cbe9954..21aa78e 100644 --- a/docs/external-OmitThisParameter.html +++ b/docs/external-OmitThisParameter.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/external-Parameters.html b/docs/external-Parameters.html index 7ed36b7..fd689a0 100644 --- a/docs/external-Parameters.html +++ b/docs/external-Parameters.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/external-Partial.html b/docs/external-Partial.html index b74b7a1..9efbec7 100644 --- a/docs/external-Partial.html +++ b/docs/external-Partial.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/external-Pick.html b/docs/external-Pick.html index 4bbcf29..dea41fe 100644 --- a/docs/external-Pick.html +++ b/docs/external-Pick.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/external-Readonly.html b/docs/external-Readonly.html index 14c37b2..3dc5544 100644 --- a/docs/external-Readonly.html +++ b/docs/external-Readonly.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/external-Record.html b/docs/external-Record.html index 08c9654..3f284cd 100644 --- a/docs/external-Record.html +++ b/docs/external-Record.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/external-Required.html b/docs/external-Required.html index 138d6b0..2b6b442 100644 --- a/docs/external-Required.html +++ b/docs/external-Required.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/external-ReturnType.html b/docs/external-ReturnType.html index 7fc26d1..19460b3 100644 --- a/docs/external-ReturnType.html +++ b/docs/external-ReturnType.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/external-ThisParameterType.html b/docs/external-ThisParameterType.html index 52ed1d4..5e217e2 100644 --- a/docs/external-ThisParameterType.html +++ b/docs/external-ThisParameterType.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/external-ThisType.html b/docs/external-ThisType.html index c21b17e..f8120f9 100644 --- a/docs/external-ThisType.html +++ b/docs/external-ThisType.html @@ -29,7 +29,7 @@ @@ -68,7 +68,7 @@

Source:
diff --git a/docs/global.html b/docs/global.html index a1ed230..8bb7caa 100644 --- a/docs/global.html +++ b/docs/global.html @@ -29,7 +29,7 @@ @@ -121,6 +121,81 @@

+

Members

+ + + +

(constant) XPathFunctions :Record.<string, XPathFun>

+ + + + + +
+ + + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
Type:
+ + + + + + + + + @@ -298,7 +373,7 @@

MaybeStylized

Source:
@@ -369,7 +444,7 @@

MaybeStylizedSeparated

Source:
@@ -443,7 +518,7 @@

Model

Source:
@@ -974,7 +1049,7 @@

Pieces

Source:
@@ -1397,7 +1472,7 @@

Separated

Source:
@@ -1543,7 +1618,7 @@

StylizeSource:
@@ -1795,6 +1870,169 @@
Type:
+ + + +

XPathFun(…params) → {XPathResult}

+ + + + + + +
+ + + +
Source:
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
params + + +XPathResult + + + + + + + + + + + <repeatable>
+ +
+ + + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +XPathResult + + + +
+
+ + + + + + +

XPathResult

@@ -1852,7 +2090,7 @@
Type:
  • -Array.<(string|dom.Node)> +Array.<(string|number|dom.Node)> diff --git a/docs/index.html b/docs/index.html index ae6f452..bb8aa60 100644 --- a/docs/index.html +++ b/docs/index.html @@ -29,7 +29,7 @@ @@ -60,16 +60,24 @@

    -

    A simple DOM for expat-wasm.

    +

    expat-wasm-dom

    +

    A Document Object Model for expat-wasm.

    To install:

    npm install --save expat-wasm-dom
     

    To use:

    import DomParser from 'expat-wasm-dom'
    -const doc = DomParser.parse('<foo/>')
    +const doc = DomParser.parse('<foo><bar/></foo>')
     console.log(doc.root.toString())
    +console.log(doc.get('//bar'))
     
    -

    Docs coming soon.

    +

    Note that there is an implementation of XPath3 included. It is not in any way +feature-complete yet, but it has a bunch of useful features already.

    +

    Full documentation is available.

    +

    A Command Line Interface is available in expat-wasm-dom-cli.

    +

    -- +Tests +codecov

    diff --git a/docs/xpath.js.html b/docs/xpath.js.html index e9bac15..721edf0 100644 --- a/docs/xpath.js.html +++ b/docs/xpath.js.html @@ -29,7 +29,7 @@ @@ -91,9 +91,24 @@

    xpath.js

    } /** - * @typedef {Array<string|dom.Node>} XPathResult + * @typedef {Array<string|number|dom.Node>} XPathResult */ +/** + * @callback XPathFun + * @param {...XPathResult} params + * @return {XPathResult} + */ + +/** + * @type {Record<string,XPathFun>} + */ +const XPathFunctions = { + count(context) { + return [context.length] + }, +} + /** * An XPath expression for querying an XML document */ @@ -224,12 +239,11 @@

    xpath.js

    */ // eslint-disable-next-line class-methods-use-this _nameTestWildcard(num, context, star) { - /* c8 ignore next */ if (star !== '*') { throw new Error(`Unimplemented wildcard: "${star}"`) } if (Array.isArray(context)) { - return context + return context.filter(e => e instanceof dom.Element) } else if (context instanceof dom.Attribute) { return [context] } else if (context instanceof dom.Element) { @@ -338,6 +352,9 @@

    xpath.js

    */ // eslint-disable-next-line class-methods-use-this _textTest(num, context) { + if (typeof context === 'string') { + return [context] + } if (typeof context.text !== 'function') { throw new Error('Cannot get text from this node') } @@ -390,7 +407,7 @@

    xpath.js

    } else if (Array.isArray(expr)) { return (this._eval([context], expr).length > 0) ? [context] : [] } - /* istanbul ignore next */ + throw new Error(`Unimplemented predicate in "${this.pattern}": "${util.inspect(expr)}"`) } @@ -399,7 +416,7 @@

    xpath.js

    * * @param {dom.Node} context * @param {Functor} expr - * @returns {string|dom.Node|undefined} + * @returns {string|number|dom.Node|undefined} * @private */ _expr(context, expr) { @@ -437,6 +454,12 @@

    xpath.js

    return [true] } break + case '!=': + case 'ne': + if (eLeft !== eRight) { + return [true] + } + break default: throw new Error(`Unimplemented op: "${op}"`) } @@ -477,9 +500,7 @@

    xpath.js

    */ // eslint-disable-next-line class-methods-use-this _comment(num, context) { - if (context instanceof dom.Comment) { - return [context] - } else if (Array.isArray(context)) { + if (Array.isArray(context)) { return context.filter(n => n instanceof dom.Comment) } else if (context instanceof dom.ParentNode) { return context.children.filter(n => n instanceof dom.Comment) @@ -503,6 +524,29 @@

    xpath.js

    ) } + /** + * Execute functions. + * + * @param {number} num + * @param {dom.Node} context + * @param {string} fn + * @param {Functor[]} params + * @returns {XPathResult} + * @private + */ + _fn(num, context, fn, params) { + const fun = XPathFunctions[fn] + if (typeof fun !== 'function') { + throw new Error(`Unimplemented function: "${fn}"`) + } + + const eparams = params.map( + v => this._eval([context], v) + ) + + return fun.apply(this, eparams) + } + /** * Some operation that hasn't been implemented yet. * diff --git a/lib/dom.js b/lib/dom.js index 8ecc32c..b5a8b95 100644 --- a/lib/dom.js +++ b/lib/dom.js @@ -169,7 +169,7 @@ export class Node { * @param {string|XPath} pattern The pattern to match * @param {Node} [context=this] The context node. Uses this node as context * if none is provided. - * @returns {string|Node|null} The first match if one exists + * @returns {string|Node|number|null} The first match if one exists */ first(pattern, context) { const res = this.get(pattern, context) @@ -416,7 +416,7 @@ export class Document extends ParentNode { * @param {string|XPath} pattern The pattern to match * @param {Node} [context=this.root] The context node. Uses the root element * as context if none is provided. - * @returns {string|Node|null} The first match if one exists + * @returns {string|number|Node|null} The first match if one exists */ first(pattern, context) { return super.first(pattern, context || this.root || undefined) diff --git a/lib/xpath.js b/lib/xpath.js index 77760c9..2513e5a 100644 --- a/lib/xpath.js +++ b/lib/xpath.js @@ -44,9 +44,24 @@ export class XPathSyntaxError extends Error { } /** - * @typedef {Array} XPathResult + * @typedef {Array} XPathResult */ +/** + * @callback XPathFun + * @param {...XPathResult} params + * @return {XPathResult} + */ + +/** + * @type {Record} + */ +const XPathFunctions = { + count(context) { + return [context.length] + }, +} + /** * An XPath expression for querying an XML document */ @@ -177,12 +192,11 @@ export class XPath { */ // eslint-disable-next-line class-methods-use-this _nameTestWildcard(num, context, star) { - /* c8 ignore next */ if (star !== '*') { throw new Error(`Unimplemented wildcard: "${star}"`) } if (Array.isArray(context)) { - return context + return context.filter(e => e instanceof dom.Element) } else if (context instanceof dom.Attribute) { return [context] } else if (context instanceof dom.Element) { @@ -355,7 +369,7 @@ export class XPath { * * @param {dom.Node} context * @param {Functor} expr - * @returns {string|dom.Node|undefined} + * @returns {string|number|dom.Node|undefined} * @private */ _expr(context, expr) { @@ -439,9 +453,7 @@ export class XPath { */ // eslint-disable-next-line class-methods-use-this _comment(num, context) { - if (context instanceof dom.Comment) { - return [context] - } else if (Array.isArray(context)) { + if (Array.isArray(context)) { return context.filter(n => n instanceof dom.Comment) } else if (context instanceof dom.ParentNode) { return context.children.filter(n => n instanceof dom.Comment) @@ -465,6 +477,29 @@ export class XPath { ) } + /** + * Execute functions. + * + * @param {number} num + * @param {dom.Node} context + * @param {string} fn + * @param {Functor[]} params + * @returns {XPathResult} + * @private + */ + _fn(num, context, fn, params) { + const fun = XPathFunctions[fn] + if (typeof fun !== 'function') { + throw new Error(`Unimplemented function: "${fn}"`) + } + + const eparams = params.map( + v => this._eval([context], v) + ) + + return fun.apply(this, eparams) + } + /** * Some operation that hasn't been implemented yet. * diff --git a/lib/xpathPattern3.js b/lib/xpathPattern3.js index bde2bed..94909a1 100644 --- a/lib/xpathPattern3.js +++ b/lib/xpathPattern3.js @@ -495,8 +495,8 @@ function peg$parse(input, options) { var peg$f23 = function(s) { return bind(s[0], s[1]) }; var peg$f24 = function(a) { return '_' + slugify(a[0]) }; var peg$f25 = function() { return bind('_parent') }; - var peg$f26 = function(q) { return bind('_nameTest', q) }; - var peg$f27 = function(w) { return bind('_nameTestWildcard', w) }; + var peg$f26 = function(w) { return bind('_nameTestWildcard', w) }; + var peg$f27 = function(q) { return bind('_nameTest', q) }; var peg$f28 = function(head, tail) { return tail.length ? bind('_postfix', head, tail) : head }; var peg$f29 = function(e) { return bind('_pred', e) }; var peg$f30 = function() { return bind('_dot') }; @@ -2466,49 +2466,31 @@ function peg$parse(input, options) { } function peg$parseNodeComp() { - var s0, s1; + var s0; - s0 = peg$currPos; if (input.substr(peg$currPos, 2) === peg$c49) { - s1 = peg$c49; + s0 = peg$c49; peg$currPos += 2; } else { - s1 = peg$FAILED; + s0 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e50); } } - if (s1 !== peg$FAILED) { - s0 = input.substring(s0, peg$currPos); - } else { - s0 = s1; - } if (s0 === peg$FAILED) { - s0 = peg$currPos; if (input.substr(peg$currPos, 2) === peg$c50) { - s1 = peg$c50; + s0 = peg$c50; peg$currPos += 2; } else { - s1 = peg$FAILED; + s0 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e51); } } - if (s1 !== peg$FAILED) { - s0 = input.substring(s0, peg$currPos); - } else { - s0 = s1; - } if (s0 === peg$FAILED) { - s0 = peg$currPos; if (input.substr(peg$currPos, 2) === peg$c51) { - s1 = peg$c51; + s0 = peg$c51; peg$currPos += 2; } else { - s1 = peg$FAILED; + s0 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e52); } } - if (s1 !== peg$FAILED) { - s0 = input.substring(s0, peg$currPos); - } else { - s0 = s1; - } } } @@ -2731,9 +2713,9 @@ function peg$parse(input, options) { function peg$parseStepExpr() { var s0; - s0 = peg$parseAxisStep(); + s0 = peg$parsePostfixExpr(); if (s0 === peg$FAILED) { - s0 = peg$parsePostfixExpr(); + s0 = peg$parseAxisStep(); } return s0; @@ -3281,7 +3263,7 @@ function peg$parse(input, options) { var s0, s1; s0 = peg$currPos; - s1 = peg$parseEQName(); + s1 = peg$parseWildcard(); if (s1 !== peg$FAILED) { peg$savedPos = s0; s1 = peg$f26(s1); @@ -3289,7 +3271,7 @@ function peg$parse(input, options) { s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - s1 = peg$parseWildcard(); + s1 = peg$parseEQName(); if (s1 !== peg$FAILED) { peg$savedPos = s0; s1 = peg$f27(s1); @@ -3448,7 +3430,7 @@ function peg$parse(input, options) { } function peg$parseArgumentList() { - var s0, s1, s2, s3, s4, s5, s6, s7; + var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 40) { @@ -3460,73 +3442,50 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { s2 = peg$currPos; - s3 = peg$parseArgument(); - if (s3 !== peg$FAILED) { - s4 = []; - s5 = peg$currPos; + s3 = []; + s4 = peg$parseArgument(); + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$currPos; if (input.charCodeAt(peg$currPos) === 44) { - s6 = peg$c0; + s5 = peg$c0; peg$currPos++; } else { - s6 = peg$FAILED; + s5 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e0); } } - if (s6 !== peg$FAILED) { - s7 = peg$parseArgument(); - if (s7 !== peg$FAILED) { - s6 = [s6, s7]; - s5 = s6; + if (s5 !== peg$FAILED) { + s5 = peg$parseArgument(); + if (s5 === peg$FAILED) { + peg$currPos = s4; + s4 = peg$FAILED; } else { - peg$currPos = s5; - s5 = peg$FAILED; + s4 = s5; } } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - while (s5 !== peg$FAILED) { - s4.push(s5); - s5 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 44) { - s6 = peg$c0; - peg$currPos++; - } else { - s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e0); } - } - if (s6 !== peg$FAILED) { - s7 = peg$parseArgument(); - if (s7 !== peg$FAILED) { - s6 = [s6, s7]; - s5 = s6; - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } - } else { - peg$currPos = s5; - s5 = peg$FAILED; - } + s4 = s5; } - s3 = [s3, s4]; - s2 = s3; - } else { + } + if (s3.length < 1) { peg$currPos = s2; s2 = peg$FAILED; - } - if (s2 === peg$FAILED) { - s2 = null; - } - if (input.charCodeAt(peg$currPos) === 41) { - s3 = peg$c14; - peg$currPos++; } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e15); } + s2 = s3; } - if (s3 !== peg$FAILED) { - s1 = [s1, s2, s3]; - s0 = s1; + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 41) { + s3 = peg$c14; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e15); } + } + if (s3 !== peg$FAILED) { + s0 = s2; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } } else { peg$currPos = s0; s0 = peg$FAILED; @@ -3707,7 +3666,7 @@ function peg$parse(input, options) { } function peg$parseContextItemExpr() { - var s0, s1; + var s0, s1, s2, s3; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 46) { @@ -3718,10 +3677,33 @@ function peg$parse(input, options) { if (peg$silentFails === 0) { peg$fail(peg$e76); } } if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$f30(); + s2 = peg$currPos; + peg$silentFails++; + if (input.charCodeAt(peg$currPos) === 46) { + s3 = peg$c74; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e76); } + } + peg$silentFails--; + if (s3 === peg$FAILED) { + s2 = undefined; + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f30(); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; } - s0 = s1; return s0; } diff --git a/lib/xpathPattern3.pegjs b/lib/xpathPattern3.pegjs index d0491c6..98197c9 100644 --- a/lib/xpathPattern3.pegjs +++ b/lib/xpathPattern3.pegjs @@ -200,8 +200,8 @@ Slash / $"/" StepExpr - = AxisStep - / PostfixExpr + = PostfixExpr + / AxisStep AxisStep = step:(ReverseStep / ForwardStep) predicates:PredicateList @@ -247,8 +247,9 @@ NodeTest / NameTest NameTest - = q:EQName { return bind('_nameTest', q) } - / w:Wildcard { return bind('_nameTestWildcard', w) } + = w:Wildcard { return bind('_nameTestWildcard', w) } + / q:EQName { return bind('_nameTest', q) } + Wildcard = "*" @@ -261,7 +262,7 @@ PostfixExpr { return tail.length ? bind('_postfix', head, tail) : head } ArgumentList - = "(" (Argument ("," Argument)*)? ")" + = "(" @Argument|1.., ","| ")" PredicateList = Predicate* @@ -297,7 +298,7 @@ ParenthesizedExpr = "(" Expr? ")" ContextItemExpr - = "." { return bind('_dot') } + = "." !"." { return bind('_dot') } FunctionCall = fn:EQName ! {return reservedFnName(fn)} params:ArgumentList @@ -541,6 +542,7 @@ StringLiteral URIQualifiedName = BracedURILiteral NCName /* ws: explicit */ + BracedURILiteral = "Q" "{" uri:([^{}]*) "}" { return bind('_bracedURI', uri) } diff --git a/test/xpath.ava.js b/test/xpath.ava.js index 858864b..23e443d 100644 --- a/test/xpath.ava.js +++ b/test/xpath.ava.js @@ -99,11 +99,25 @@ test('error', t => { const doc = xml`` doc.get('/foo/unimplemented()') }) + + t.throws(() => { + xml``.get('Q{foo}*') + }) + + t.throws(() => { + // Will break when child axis implemented + xml``.get('child::*') + }) + t.throws(() => { + xml``.get('count(/)/text()') + }) }) test('xpath edges', t => { const doc = xml`baz` t.deepEqual(doc.get('/bar/comment()/*'), []) + t.deepEqual(doc.get('/bar/@a/comment()'), []) + t.deepEqual(doc.get('/bar/@a/comment()/text()'), []) t.throws(() => doc.get('/bar/comment()//*')) t.is(doc.first('/bar/text()'), 'baz') t.is(doc.first('/bar/text()/text()'), 'baz') @@ -113,3 +127,10 @@ test('unimplemented', t => { const doc = xml`baz` t.throws(() => doc.get('/bar[@a ge "c"]')) }) + +test('functions', t => { + const doc = xml`` + t.throws(() => doc.get('unimpl(/)')) + t.deepEqual(doc.get('count(baz)'), [2]) + t.deepEqual(doc.get('count(count(baz))'), [1]) +}) diff --git a/types/dom.d.ts b/types/dom.d.ts index d6f5894..f17cc5f 100644 --- a/types/dom.d.ts +++ b/types/dom.d.ts @@ -59,9 +59,9 @@ export class Node { * @param {string|XPath} pattern The pattern to match * @param {Node} [context=this] The context node. Uses this node as context * if none is provided. - * @returns {string|Node|null} The first match if one exists + * @returns {string|Node|number|null} The first match if one exists */ - first(pattern: string | XPath, context?: Node | undefined): string | Node | null; + first(pattern: string | XPath, context?: Node | undefined): string | Node | number | null; /** * Find the Document that this node is in, if it is in a Document. * diff --git a/types/xpath.d.ts b/types/xpath.d.ts index eb1a18a..81bd207 100644 --- a/types/xpath.d.ts +++ b/types/xpath.d.ts @@ -23,9 +23,6 @@ export class XPathSyntaxError extends Error { */ [util.inspect.custom](depth: number, options: util.InspectOptionsStylized): string; } -/** - * @typedef {Array} XPathResult - */ /** * An XPath expression for querying an XML document */ @@ -185,7 +182,7 @@ export class XPath { * * @param {dom.Node} context * @param {Functor} expr - * @returns {string|dom.Node|undefined} + * @returns {string|number|dom.Node|undefined} * @private */ private _expr; @@ -229,6 +226,17 @@ export class XPath { * @private */ private _comma; + /** + * Execute functions. + * + * @param {number} num + * @param {dom.Node} context + * @param {string} fn + * @param {Functor[]} params + * @returns {XPathResult} + * @private + */ + private _fn; /** * Some operation that hasn't been implemented yet. * @@ -240,6 +248,7 @@ export class XPath { */ private _unimplemented; } -export type XPathResult = Array; +export type XPathResult = Array; +export type XPathFun = (...params: XPathResult[]) => XPathResult; import util from 'util'; import * as dom from './dom.js';