Comparing version 0.0.27 to 0.0.28
@@ -34,7 +34,7 @@ # Function Resolvers | ||
var aboutPi = evaluator.evaluateNumber({ | ||
functions: { | ||
'squareRoot': function (c, value) { | ||
return Math.sqrt(value.numberValue()); | ||
} | ||
} | ||
functions: { | ||
'squareRoot': function (c, value) { | ||
return Math.sqrt(value.numberValue()); | ||
} | ||
} | ||
}); | ||
@@ -53,9 +53,9 @@ ``` | ||
var aboutPi = evaluator.evaluateNumber({ | ||
functions: function (name, namespace) { | ||
functions: function (name, namespace) { | ||
if (name === 'squareRoot' && namespace === 'http://sample.org/math/') { | ||
return function (c, value) { | ||
return Math.sqrt(value.numberValue()); | ||
}; | ||
return function (c, value) { | ||
return Math.sqrt(value.numberValue()); | ||
}; | ||
} | ||
}, | ||
}, | ||
namespaces: { | ||
@@ -77,10 +77,10 @@ math: 'http://sample.org/math/' | ||
var aboutPi = evaluator.evaluateNumber({ | ||
functions: { | ||
functions: { | ||
getFunction: function (name, namespace) { | ||
if (name === 'squareRoot' && namespace === 'http://sample.org/math/') { | ||
return function (c, value) { | ||
return Math.sqrt(value.numberValue()); | ||
}; | ||
return function (c, value) { | ||
return Math.sqrt(value.numberValue()); | ||
}; | ||
} | ||
} | ||
} | ||
}, | ||
@@ -87,0 +87,0 @@ namespaces: { |
@@ -5,11 +5,11 @@ # `XPathEvaluator` | ||
Each of these methods takes an optional `options` object, which can contain any of the following properties: | ||
Each of these methods takes an optional `options` object, which can contain any of the following properties. See the links for each item for further details: | ||
`namespaces` - a namespace resolver. See the [documentation page](namespace%20resolvers.md) for details. | ||
- `namespaces` - a [namespace resolver](namespace%20resolvers.md) | ||
`variables` - a variable resolver. See the [documentation page](variable%20resolvers.md) for details. | ||
- `variables` - a [variable resolver](variable%20resolvers.md) | ||
`functions` - a function resolver. See the [documentation page](function%20resolvers.md) for details. | ||
- `functions` - a [function resolver](function%20resolvers.md) | ||
`node` - the context node for evaluating the expression | ||
- `node` - the context node for evaluating the expression | ||
@@ -60,5 +60,5 @@ Example usage: | ||
Evaluates the XPath expression and the first node in the resulting node set, in document order. Returns `undefined` | ||
Evaluates the XPath expression and the first node in the resulting node set, in document order. Returns `undefined` if the resulting node set is empty. | ||
This is only valid for expressions that evaluate to a node set. | ||
{ | ||
"name": "xpath", | ||
"version": "0.0.27", | ||
"version": "0.0.28", | ||
"description": "DOM 3 XPath implemention and helper for node.js.", | ||
@@ -21,3 +21,2 @@ "engines": { | ||
"devDependencies": { | ||
"nodeunit": ">=0.6.4", | ||
"xmldom": "^0.1.19" | ||
@@ -27,3 +26,3 @@ }, | ||
"scripts": { | ||
"test": "./node_modules/.bin/nodeunit test.js" | ||
"test": "mocha" | ||
}, | ||
@@ -30,0 +29,0 @@ "repository": { |
1858
test.js
@@ -1,1092 +0,1022 @@ | ||
var xpath = require('./xpath.js') | ||
, dom = require('xmldom').DOMParser | ||
, assert = require('assert'); | ||
const xpath = require('./xpath.js'); | ||
const dom = require('xmldom').DOMParser; | ||
const assert = require('assert'); | ||
var xhtmlNs = 'http://www.w3.org/1999/xhtml'; | ||
module.exports = { | ||
'api': function(test) { | ||
assert.ok(xpath.evaluate, 'evaluate api ok.'); | ||
assert.ok(xpath.select, 'select api ok.'); | ||
assert.ok(xpath.parse, 'parse api ok.'); | ||
test.done(); | ||
}, | ||
describe('xpath', () => { | ||
describe('api', () => { | ||
it('should contain the correct methods', () => { | ||
assert.ok(xpath.evaluate, 'evaluate api ok.'); | ||
assert.ok(xpath.select, 'select api ok.'); | ||
assert.ok(xpath.parse, 'parse api ok.'); | ||
}); | ||
'evaluate': function(test) { | ||
var xml = '<book><title>Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var nodes = xpath.evaluate('//title', doc, null, xpath.XPathResult.ANY_TYPE, null).nodes; | ||
assert.equal('title', nodes[0].localName); | ||
assert.equal('Harry Potter', nodes[0].firstChild.data); | ||
assert.equal('<title>Harry Potter</title>', nodes[0].toString()); | ||
test.done(); | ||
}, | ||
it('should support .evaluate()', () => { | ||
var xml = '<book><title>Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var nodes = xpath.evaluate('//title', doc, null, xpath.XPathResult.ANY_TYPE, null).nodes; | ||
'select': function(test) { | ||
var xml = '<?book title="Harry Potter"?><?series title="Harry Potter"?><?series books="7"?><book><!-- This is a great book --><title>Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var nodes = xpath.select('//title', doc); | ||
assert.equal('title', nodes[0].localName); | ||
assert.equal('Harry Potter', nodes[0].firstChild.data); | ||
assert.equal('<title>Harry Potter</title>', nodes[0].toString()); | ||
var nodes2 = xpath.select('//node()', doc); | ||
assert.equal(7, nodes2.length); | ||
assert.equal('title', nodes[0].localName); | ||
assert.equal('Harry Potter', nodes[0].firstChild.data); | ||
assert.equal('<title>Harry Potter</title>', nodes[0].toString()); | ||
}); | ||
var pis = xpath.select("/processing-instruction('series')", doc); | ||
assert.equal(2, pis.length); | ||
assert.equal('books="7"', pis[1].data); | ||
test.done(); | ||
}, | ||
it('should support .select()', () => { | ||
var xml = '<?book title="Harry Potter"?><?series title="Harry Potter"?><?series books="7"?><book><!-- This is a great book --><title>Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var nodes = xpath.select('//title', doc); | ||
assert.equal('title', nodes[0].localName); | ||
assert.equal('Harry Potter', nodes[0].firstChild.data); | ||
assert.equal('<title>Harry Potter</title>', nodes[0].toString()); | ||
'select single node': function(test) { | ||
var xml = '<book><title>Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var nodes2 = xpath.select('//node()', doc); | ||
assert.equal(7, nodes2.length); | ||
assert.equal('title', xpath.select('//title[1]', doc)[0].localName); | ||
var pis = xpath.select("/processing-instruction('series')", doc); | ||
assert.equal(2, pis.length); | ||
assert.equal('books="7"', pis[1].data); | ||
}); | ||
}); | ||
test.done(); | ||
}, | ||
describe('parsing', () => { | ||
it('should detect unterminated string literals', () => { | ||
function testUnterminated(path) { | ||
assert.throws(function () { | ||
xpath.evaluate('"hello'); | ||
}, function (err) { | ||
return err.message.indexOf('Unterminated') !== -1; | ||
}); | ||
} | ||
'select text node': function (test) { | ||
var xml = '<book><title>Harry</title><title>Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
testUnterminated('"Hello'); | ||
testUnterminated("'Hello"); | ||
testUnterminated('self::text() = "\""'); | ||
testUnterminated('"\""'); | ||
}); | ||
}); | ||
assert.deepEqual('book', xpath.select('local-name(/book)', doc)); | ||
assert.deepEqual('Harry,Potter', xpath.select('//title/text()', doc).toString()); | ||
describe('.select()', () => { | ||
it('should select single nodes', () => { | ||
var xml = '<book><title>Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
test.done(); | ||
}, | ||
assert.equal('title', xpath.select('//title[1]', doc)[0].localName); | ||
}); | ||
'select number value': function(test) { | ||
var xml = '<book><title>Harry</title><title>Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
it('should select text nodes', () => { | ||
var xml = '<book><title>Harry</title><title>Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
assert.deepEqual(2, xpath.select('count(//title)', doc)); | ||
assert.deepEqual('book', xpath.select('local-name(/book)', doc)); | ||
assert.deepEqual('Harry,Potter', xpath.select('//title/text()', doc).toString()); | ||
}); | ||
test.done(); | ||
}, | ||
it('should select number values', () => { | ||
var xml = '<book><title>Harry</title><title>Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
'select xpath with namespaces': function (test) { | ||
var xml = '<book><title xmlns="myns">Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
assert.deepEqual(2, xpath.select('count(//title)', doc)); | ||
}); | ||
var nodes = xpath.select('//*[local-name(.)="title" and namespace-uri(.)="myns"]', doc); | ||
assert.equal('title', nodes[0].localName); | ||
assert.equal('myns', nodes[0].namespaceURI) ; | ||
it('should select with namespaces', () => { | ||
var xml = '<book><title xmlns="myns">Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var nodes2 = xpath.select('/*/title', doc); | ||
assert.equal(0, nodes2.length); | ||
test.done(); | ||
}, | ||
var nodes = xpath.select('//*[local-name(.)="title" and namespace-uri(.)="myns"]', doc); | ||
assert.equal('title', nodes[0].localName); | ||
assert.equal('myns', nodes[0].namespaceURI); | ||
'select xpath with namespaces, using a resolver': function (test) { | ||
var xml = '<book xmlns:testns="http://example.com/test" xmlns:otherns="http://example.com/other"><otherns:title>Narnia</otherns:title><testns:title>Harry Potter</testns:title><testns:field testns:type="author">JKR</testns:field></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var nodes2 = xpath.select('/*/title', doc); | ||
var resolver = { | ||
mappings: { | ||
'testns': 'http://example.com/test' | ||
}, | ||
lookupNamespaceURI: function(prefix) { | ||
return this.mappings[prefix]; | ||
} | ||
} | ||
assert.equal(0, nodes2.length); | ||
}); | ||
var nodes = xpath.selectWithResolver('//testns:title/text()', doc, resolver); | ||
assert.equal('Harry Potter', xpath.selectWithResolver('//testns:title/text()', doc, resolver)[0].nodeValue); | ||
assert.equal('JKR', xpath.selectWithResolver('//testns:field[@testns:type="author"]/text()', doc, resolver)[0].nodeValue); | ||
var nodes2 = xpath.selectWithResolver('/*/testns:*', doc, resolver); | ||
assert.equal(2, nodes2.length); | ||
it('should select with namespaces, using a resolver', () => { | ||
var xml = '<book xmlns:testns="http://example.com/test" xmlns:otherns="http://example.com/other"><otherns:title>Narnia</otherns:title><testns:title>Harry Potter</testns:title><testns:field testns:type="author">JKR</testns:field></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
test.done(); | ||
}, | ||
var resolver = { | ||
mappings: { | ||
'testns': 'http://example.com/test' | ||
}, | ||
lookupNamespaceURI: function (prefix) { | ||
return this.mappings[prefix]; | ||
} | ||
}; | ||
'select xpath with default namespace, using a resolver': function (test) { | ||
var xml = '<book xmlns="http://example.com/test"><title>Harry Potter</title><field type="author">JKR</field></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var nodes = xpath.selectWithResolver('//testns:title/text()', doc, resolver); | ||
assert.equal('Harry Potter', nodes[0].nodeValue); | ||
var resolver = { | ||
mappings: { | ||
'testns': 'http://example.com/test' | ||
}, | ||
lookupNamespaceURI: function(prefix) { | ||
return this.mappings[prefix]; | ||
} | ||
} | ||
assert.equal('JKR', xpath.selectWithResolver('//testns:field[@testns:type="author"]/text()', doc, resolver)[0].nodeValue); | ||
var nodes = xpath.selectWithResolver('//testns:title/text()', doc, resolver); | ||
assert.equal('Harry Potter', xpath.selectWithResolver('//testns:title/text()', doc, resolver)[0].nodeValue); | ||
assert.equal('JKR', xpath.selectWithResolver('//testns:field[@type="author"]/text()', doc, resolver)[0].nodeValue); | ||
var nodes2 = xpath.selectWithResolver('/*/testns:*', doc, resolver); | ||
test.done(); | ||
}, | ||
assert.equal(2, nodes2.length); | ||
}); | ||
'select xpath with namespaces, prefixes different in xml and xpath, using a resolver': function (test) { | ||
var xml = '<book xmlns:testns="http://example.com/test"><testns:title>Harry Potter</testns:title><testns:field testns:type="author">JKR</testns:field></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
it('should select from xml with a default namespace, using a resolver', () => { | ||
var xml = '<book xmlns="http://example.com/test"><title>Harry Potter</title><field type="author">JKR</field></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var resolver = { | ||
mappings: { | ||
'ns': 'http://example.com/test' | ||
}, | ||
lookupNamespaceURI: function(prefix) { | ||
return this.mappings[prefix]; | ||
} | ||
} | ||
var resolver = { | ||
mappings: { | ||
'testns': 'http://example.com/test' | ||
}, | ||
lookupNamespaceURI: function (prefix) { | ||
return this.mappings[prefix]; | ||
} | ||
} | ||
var nodes = xpath.selectWithResolver('//ns:title/text()', doc, resolver); | ||
assert.equal('Harry Potter', xpath.selectWithResolver('//ns:title/text()', doc, resolver)[0].nodeValue); | ||
assert.equal('JKR', xpath.selectWithResolver('//ns:field[@ns:type="author"]/text()', doc, resolver)[0].nodeValue); | ||
var nodes = xpath.selectWithResolver('//testns:title/text()', doc, resolver); | ||
assert.equal('Harry Potter', xpath.selectWithResolver('//testns:title/text()', doc, resolver)[0].nodeValue); | ||
assert.equal('JKR', xpath.selectWithResolver('//testns:field[@type="author"]/text()', doc, resolver)[0].nodeValue); | ||
}); | ||
test.done(); | ||
}, | ||
it('should select with namespaces, prefixes different in xml and xpath, using a resolver', () => { | ||
var xml = '<book xmlns:testns="http://example.com/test"><testns:title>Harry Potter</testns:title><testns:field testns:type="author">JKR</testns:field></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
'select xpath with namespaces, using namespace mappings': function (test) { | ||
var xml = '<book xmlns:testns="http://example.com/test"><testns:title>Harry Potter</testns:title><testns:field testns:type="author">JKR</testns:field></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var select = xpath.useNamespaces({'testns': 'http://example.com/test'}); | ||
var resolver = { | ||
mappings: { | ||
'ns': 'http://example.com/test' | ||
}, | ||
lookupNamespaceURI: function (prefix) { | ||
return this.mappings[prefix]; | ||
} | ||
} | ||
assert.equal('Harry Potter', select('//testns:title/text()', doc)[0].nodeValue); | ||
assert.equal('JKR', select('//testns:field[@testns:type="author"]/text()', doc)[0].nodeValue); | ||
var nodes = xpath.selectWithResolver('//ns:title/text()', doc, resolver); | ||
assert.equal('Harry Potter', nodes[0].nodeValue); | ||
test.done(); | ||
}, | ||
assert.equal('JKR', xpath.selectWithResolver('//ns:field[@ns:type="author"]/text()', doc, resolver)[0].nodeValue); | ||
}); | ||
it('should select with namespaces, using namespace mappings', () => { | ||
var xml = '<book xmlns:testns="http://example.com/test"><testns:title>Harry Potter</testns:title><testns:field testns:type="author">JKR</testns:field></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var select = xpath.useNamespaces({ 'testns': 'http://example.com/test' }); | ||
'select attribute': function (test) { | ||
var xml = '<author name="J. K. Rowling"></author>'; | ||
var doc = new dom().parseFromString(xml); | ||
assert.equal('Harry Potter', select('//testns:title/text()', doc)[0].nodeValue); | ||
assert.equal('JKR', select('//testns:field[@testns:type="author"]/text()', doc)[0].nodeValue); | ||
}); | ||
var author = xpath.select1('/author/@name', doc).value; | ||
assert.equal('J. K. Rowling', author); | ||
it('should select attributes', () => { | ||
var xml = '<author name="J. K. Rowling"></author>'; | ||
var doc = new dom().parseFromString(xml); | ||
test.done(); | ||
} | ||
var author = xpath.select1('/author/@name', doc).value; | ||
assert.equal('J. K. Rowling', author); | ||
}); | ||
}); | ||
,'select with multiple predicates': function (test) { | ||
var xml = '<characters><character name="Snape" sex="M" age="50" /><character name="McGonnagal" sex="F" age="65" /><character name="Harry" sex="M" age="14" /></characters>'; | ||
var doc = new dom().parseFromString(xml); | ||
var characters = xpath.select('/*/character[@sex = "M"][@age > 40]/@name', doc); | ||
assert.equal(1, characters.length); | ||
assert.equal('Snape', characters[0].textContent); | ||
test.done(); | ||
} | ||
// https://github.com/goto100/xpath/issues/37 | ||
,'select multiple attributes': function (test) { | ||
var xml = '<authors><author name="J. K. Rowling" /><author name="Saeed Akl" /></authors>'; | ||
var doc = new dom().parseFromString(xml); | ||
describe('selection', () => { | ||
it('should select with multiple predicates', () => { | ||
var xml = '<characters><character name="Snape" sex="M" age="50" /><character name="McGonnagal" sex="F" age="65" /><character name="Harry" sex="M" age="14" /></characters>'; | ||
var doc = new dom().parseFromString(xml); | ||
var authors = xpath.select('/authors/author/@name', doc); | ||
assert.equal(2, authors.length); | ||
assert.equal('J. K. Rowling', authors[0].value); | ||
var characters = xpath.select('/*/character[@sex = "M"][@age > 40]/@name', doc); | ||
// https://github.com/goto100/xpath/issues/41 | ||
doc = new dom().parseFromString('<chapters><chapter v="1"/><chapter v="2"/><chapter v="3"/></chapters>'); | ||
var nodes = xpath.select("/chapters/chapter/@v", doc); | ||
var values = nodes.map(function(n) { return n.value; }); | ||
assert.equal(1, characters.length); | ||
assert.equal('Snape', characters[0].textContent); | ||
}); | ||
assert.equal(3, values.length); | ||
assert.equal("1", values[0]); | ||
assert.equal("2", values[1]); | ||
assert.equal("3", values[2]); | ||
// https://github.com/goto100/xpath/issues/37 | ||
it('should select multiple attributes', () => { | ||
var xml = '<authors><author name="J. K. Rowling" /><author name="Saeed Akl" /></authors>'; | ||
var doc = new dom().parseFromString(xml); | ||
test.done(); | ||
} | ||
var authors = xpath.select('/authors/author/@name', doc); | ||
assert.equal(2, authors.length); | ||
assert.equal('J. K. Rowling', authors[0].value); | ||
,'XPathException acts like Error': function (test) { | ||
try { | ||
xpath.evaluate('1', null, null, null); | ||
assert.fail(null, null, 'evaluate() should throw exception'); | ||
} catch (e) { | ||
assert.ok('code' in e, 'must have a code'); | ||
assert.ok('stack' in e, 'must have a stack'); | ||
} | ||
test.done(); | ||
}, | ||
'string() with no arguments': function (test) { | ||
var doc = new dom().parseFromString('<book>Harry Potter</book>'); | ||
var rootElement = xpath.select1('/book', doc); | ||
assert.ok(rootElement, 'rootElement is null'); | ||
assert.equal('Harry Potter', xpath.select1('string()', doc)); | ||
test.done(); | ||
}, | ||
'string value of document fragment': function (test) { | ||
var doc = new dom().parseFromString('<n />'); | ||
var docFragment = doc.createDocumentFragment(); | ||
// https://github.com/goto100/xpath/issues/41 | ||
doc = new dom().parseFromString('<chapters><chapter v="1"/><chapter v="2"/><chapter v="3"/></chapters>'); | ||
var nodes = xpath.select("/chapters/chapter/@v", doc); | ||
var values = nodes.map(function (n) { return n.value; }); | ||
var el = doc.createElement("book"); | ||
docFragment.appendChild(el); | ||
var testValue = "Harry Potter"; | ||
el.appendChild(doc.createTextNode(testValue)); | ||
assert.equal(3, values.length); | ||
assert.equal("1", values[0]); | ||
assert.equal("2", values[1]); | ||
assert.equal("3", values[2]); | ||
}); | ||
assert.equal(testValue, xpath.select1("string()", docFragment)); | ||
test.done(); | ||
}, | ||
it('should compare string values of numbers with numbers', () => { | ||
assert.ok(xpath.select1('"000" = 0'), '000'); | ||
assert.ok(xpath.select1('"45.0" = 45'), '45'); | ||
}); | ||
'compare string of a number with a number': function (test) { | ||
assert.ok(xpath.select1('"000" = 0'), '000'); | ||
assert.ok(xpath.select1('"45.0" = 45'), '45'); | ||
test.done(); | ||
}, | ||
it('should correctly compare strings with booleans', () => { | ||
// string should downcast to boolean | ||
assert.equal(false, xpath.select1('"false" = false()'), '"false" = false()'); | ||
assert.equal(true, xpath.select1('"a" = true()'), '"a" = true()'); | ||
assert.equal(true, xpath.select1('"" = false()'), '"" = false()'); | ||
}); | ||
'string(boolean) is a string': function (test) { | ||
assert.equal('string', typeof xpath.select1('string(true())')); | ||
assert.equal('string', typeof xpath.select1('string(false())')); | ||
assert.equal('string', typeof xpath.select1('string(1 = 2)')); | ||
assert.ok(xpath.select1('"true" = string(true())'), '"true" = string(true())'); | ||
test.done(); | ||
}, | ||
'string should downcast to boolean': function (test) { | ||
assert.equal(false, xpath.select1('"false" = false()'), '"false" = false()'); | ||
assert.equal(true, xpath.select1('"a" = true()'), '"a" = true()'); | ||
assert.equal(true, xpath.select1('"" = false()'), '"" = false()'); | ||
test.done(); | ||
}, | ||
'string(number) is a string': function (test) { | ||
assert.equal('string', typeof xpath.select1('string(45)')); | ||
assert.ok(xpath.select1('"45" = string(45)'), '"45" = string(45)'); | ||
test.done(); | ||
}, | ||
'correct string to number conversion': function (test) { | ||
assert.equal(45.2, xpath.select1('number("45.200")')); | ||
assert.equal(55.0, xpath.select1('number("000055")')); | ||
assert.equal(65.0, xpath.select1('number(" 65 ")')); | ||
it('should evaluate local-name() and name() on processing instructions', () => { | ||
var xml = '<?book-record added="2015-01-16" author="J.K. Rowling" ?><book>Harry Potter</book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var expectedName = 'book-record'; | ||
var localName = xpath.select('local-name(/processing-instruction())', doc); | ||
var name = xpath.select('name(/processing-instruction())', doc); | ||
assert.equal(true, xpath.select1('"" != 0'), '"" != 0'); | ||
assert.equal(false, xpath.select1('"" = 0'), '"" = 0'); | ||
assert.equal(false, xpath.select1('0 = ""'), '0 = ""'); | ||
assert.equal(false, xpath.select1('0 = " "'), '0 = " "'); | ||
assert.deepEqual(expectedName, localName, 'local-name() - "' + expectedName + '" !== "' + localName + '"'); | ||
assert.deepEqual(expectedName, name, 'name() - "' + expectedName + '" !== "' + name + '"'); | ||
}); | ||
assert.ok(Number.isNaN(xpath.select('number("")')), 'number("")'); | ||
assert.ok(Number.isNaN(xpath.select('number("45.8g")')), 'number("45.8g")'); | ||
assert.ok(Number.isNaN(xpath.select('number("2e9")')), 'number("2e9")'); | ||
assert.ok(Number.isNaN(xpath.select('number("+33")')), 'number("+33")'); | ||
test.done(); | ||
} | ||
,'correct number to string conversion': function (test) { | ||
assert.equal('0.0000000000000000000000005250000000000001', xpath.parse('0.525 div 1000000 div 1000000 div 1000000 div 1000000').evaluateString()); | ||
assert.equal('525000000000000000000000', xpath.parse('0.525 * 1000000 * 1000000 * 1000000 * 1000000').evaluateString()); | ||
test.done(); | ||
} | ||
,'local-name() and name() of processing instruction': function (test) { | ||
var xml = '<?book-record added="2015-01-16" author="J.K. Rowling" ?><book>Harry Potter</book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var expectedName = 'book-record'; | ||
var localName = xpath.select('local-name(/processing-instruction())', doc); | ||
var name = xpath.select('name(/processing-instruction())', doc); | ||
assert.deepEqual(expectedName, localName, 'local-name() - "' + expectedName + '" !== "' + localName + '"'); | ||
assert.deepEqual(expectedName, name, 'name() - "' + expectedName + '" !== "' + name + '"'); | ||
test.done(); | ||
}, | ||
it('should support substring-after()', () => { | ||
var xml = '<classmate>Hermione</classmate>'; | ||
var doc = new dom().parseFromString(xml); | ||
'evaluate substring-after': function (test) { | ||
var xml = '<classmate>Hermione</classmate>'; | ||
var doc = new dom().parseFromString(xml); | ||
var part = xpath.select('substring-after(/classmate, "Her")', doc); | ||
assert.deepEqual('mione', part); | ||
test.done(); | ||
} | ||
,'parsed expression with no options': function (test) { | ||
var parsed = xpath.parse('5 + 7'); | ||
assert.equal(typeof parsed, "object", "parse() should return an object"); | ||
assert.equal(typeof parsed.evaluate, "function", "parsed.evaluate should be a function"); | ||
assert.equal(typeof parsed.evaluateNumber, "function", "parsed.evaluateNumber should be a function"); | ||
assert.equal(parsed.evaluateNumber(), 12); | ||
var part = xpath.select('substring-after(/classmate, "Her")', doc); | ||
assert.deepEqual('mione', part); | ||
}); | ||
// evaluating twice should yield the same result | ||
assert.equal(parsed.evaluateNumber(), 12); | ||
test.done(); | ||
} | ||
,'select1() on parsed expression': function (test) { | ||
var xml = '<book><title>Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var parsed = xpath.parse('/*/title'); | ||
assert.equal(typeof parsed, 'object', 'parse() should return an object'); | ||
assert.equal(typeof parsed.select1, 'function', 'parsed.select1 should be a function'); | ||
var single = parsed.select1({ node: doc }); | ||
assert.equal('title', single.localName); | ||
assert.equal('Harry Potter', single.firstChild.data); | ||
assert.equal('<title>Harry Potter</title>', single.toString()); | ||
test.done(); | ||
} | ||
it('should support preceding:: on document fragments', () => { | ||
var doc = new dom().parseFromString('<n />'), | ||
df = doc.createDocumentFragment(), | ||
root = doc.createElement('book'); | ||
,'select() on parsed expression': function (test) { | ||
var xml = '<book><title>Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var parsed = xpath.parse('/*/title'); | ||
assert.equal(typeof parsed, 'object', 'parse() should return an object'); | ||
assert.equal(typeof parsed.select, 'function', 'parsed.select should be a function'); | ||
var nodes = parsed.select({ node: doc }); | ||
assert.ok(nodes, 'parsed.select() should return a value'); | ||
assert.equal(1, nodes.length); | ||
assert.equal('title', nodes[0].localName); | ||
assert.equal('Harry Potter', nodes[0].firstChild.data); | ||
assert.equal('<title>Harry Potter</title>', nodes[0].toString()); | ||
test.done(); | ||
} | ||
df.appendChild(root); | ||
,'evaluateString(), and evaluateNumber() on parsed expression with node': function (test) { | ||
var xml = '<book><title>Harry Potter</title><numVolumes>7</numVolumes></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var parsed = xpath.parse('/*/numVolumes'); | ||
assert.equal(typeof parsed, 'object', 'parse() should return an object'); | ||
assert.equal(typeof parsed.evaluateString, 'function', 'parsed.evaluateString should be a function'); | ||
assert.equal('7', parsed.evaluateString({ node: doc })); | ||
assert.equal(typeof parsed.evaluateBoolean, 'function', 'parsed.evaluateBoolean should be a function'); | ||
assert.equal(true, parsed.evaluateBoolean({ node: doc })); | ||
for (var i = 0; i < 10; i += 1) { | ||
root.appendChild(doc.createElement('chapter')); | ||
} | ||
assert.equal(typeof parsed.evaluateNumber, 'function', 'parsed.evaluateNumber should be a function'); | ||
assert.equal(7, parsed.evaluateNumber({ node: doc })); | ||
var chapter = xpath.select1("book/chapter[5]", df); | ||
test.done(); | ||
} | ||
,'evaluateBoolean() on parsed empty node set and boolean expressions': function (test) { | ||
var xml = '<book><title>Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var context = { node: doc }; | ||
function evaluate(path) { | ||
return xpath.parse(path).evaluateBoolean({ node: doc }); | ||
} | ||
assert.ok(chapter, 'chapter'); | ||
assert.equal(false, evaluate('/*/myrtle'), 'boolean value of empty node set should be false'); | ||
assert.equal(true, evaluate('not(/*/myrtle)'), 'not() of empty nodeset should be true'); | ||
assert.equal(xpath.select("count(preceding::chapter)", chapter), 4); | ||
}); | ||
assert.equal(true, evaluate('/*/title'), 'boolean value of non-empty nodeset should be true'); | ||
assert.equal(true, evaluate('/*/title = "Harry Potter"'), 'title equals Harry Potter'); | ||
it('should allow getting sorted and unsorted arrays from nodesets', () => { | ||
const doc = new dom().parseFromString('<book><character>Harry</character><character>Ron</character><character>Hermione</character></book>'); | ||
const path = xpath.parse("/*/*[3] | /*/*[2] | /*/*[1]"); | ||
const nset = path.evaluateNodeSet({ node: doc }); | ||
const sorted = nset.toArray(); | ||
const unsorted = nset.toUnsortedArray(); | ||
assert.equal(false, evaluate('/*/title != "Harry Potter"'), 'title != Harry Potter should be false'); | ||
assert.equal(sorted.length, 3); | ||
assert.equal(unsorted.length, 3); | ||
assert.equal(false, evaluate('/*/title = "Percy Jackson"'), 'title should not equal Percy Jackson'); | ||
test.done(); | ||
} | ||
,'namespaces with parsed expression': function (test) { | ||
var xml = '<characters xmlns:ps="http://philosophers-stone.com" xmlns:cs="http://chamber-secrets.com">' + | ||
'<ps:character>Quirrell</ps:character><ps:character>Fluffy</ps:character>' + | ||
'<cs:character>Myrtle</cs:character><cs:character>Tom Riddle</cs:character>' + | ||
'</characters>'; | ||
var doc = new dom().parseFromString(xml); | ||
assert.equal(sorted[0].textContent, 'Harry'); | ||
assert.equal(sorted[1].textContent, 'Ron'); | ||
assert.equal(sorted[2].textContent, 'Hermione'); | ||
var expr = xpath.parse('/characters/c:character'); | ||
var countExpr = xpath.parse('count(/characters/c:character)'); | ||
var csns = 'http://chamber-secrets.com'; | ||
function resolve(prefix) { | ||
if (prefix === 'c') { | ||
return csns; | ||
assert.notEqual(sorted[0], unsorted[0], "first nodeset element equal"); | ||
}); | ||
it('should compare nodesets to nodesets (=)', () => { | ||
var xml = '<school><houses>' + | ||
'<house name="Gryffindor"><student>Harry</student><student>Hermione</student></house>' + | ||
'<house name="Slytherin"><student>Draco</student><student>Crabbe</student></house>' + | ||
'<house name="Ravenclaw"><student>Luna</student><student>Cho</student></house>' + | ||
'</houses>' + | ||
'<honorStudents><student>Hermione</student><student>Luna</student></honorStudents></school>'; | ||
var doc = new dom().parseFromString(xml); | ||
var houses = xpath.parse('/school/houses/house[student = /school/honorStudents/student]').select({ node: doc }); | ||
assert.equal(houses.length, 2); | ||
var houseNames = houses.map(function (node) { return node.getAttribute('name'); }).sort(); | ||
assert.equal(houseNames[0], 'Gryffindor'); | ||
assert.equal(houseNames[1], 'Ravenclaw'); | ||
}); | ||
it('should compare nodesets to nodesets (>=)', () => { | ||
var xml = '<school><houses>' + | ||
'<house name="Gryffindor"><student level="5">Harry</student><student level="9">Hermione</student></house>' + | ||
'<house name="Slytherin"><student level="1">Goyle</student><student level="1">Crabbe</student></house>' + | ||
'<house name="Ravenclaw"><student level="4">Luna</student><student level="3">Cho</student></house>' + | ||
'</houses>' + | ||
'<courses><course minLevel="9">DADA</course><course minLevel="4">Charms</course></courses>' + | ||
'</school>'; | ||
var doc = new dom().parseFromString(xml); | ||
var houses = xpath.parse('/school/houses/house[student/@level >= /school/courses/course/@minLevel]').select({ node: doc }); | ||
assert.equal(houses.length, 2); | ||
var houseNames = houses.map(function (node) { return node.getAttribute('name'); }).sort(); | ||
assert.equal(houseNames[0], 'Gryffindor'); | ||
assert.equal(houseNames[1], 'Ravenclaw'); | ||
}); | ||
it('should support various inequality expressions on nodesets', () => { | ||
var xml = "<books><book num='1' title='PS' /><book num='2' title='CoS' /><book num='3' title='PoA' /><book num='4' title='GoF' /><book num='5' title='OotP' /><book num='6' title='HBP' /><book num='7' title='DH' /></books>"; | ||
var doc = new dom().parseFromString(xml); | ||
var options = { node: doc, variables: { theNumber: 3, theString: '3', theBoolean: true } }; | ||
var numberPaths = [ | ||
'/books/book[$theNumber <= @num]', | ||
'/books/book[$theNumber < @num]', | ||
'/books/book[$theNumber >= @num]', | ||
'/books/book[$theNumber > @num]' | ||
]; | ||
var stringPaths = [ | ||
'/books/book[$theString <= @num]', | ||
'/books/book[$theString < @num]', | ||
'/books/book[$theString >= @num]', | ||
'/books/book[$theString > @num]' | ||
]; | ||
var booleanPaths = [ | ||
'/books/book[$theBoolean <= @num]', | ||
'/books/book[$theBoolean < @num]', | ||
'/books/book[$theBoolean >= @num]', | ||
'/books/book[$theBoolean > @num]' | ||
]; | ||
var lhsPaths = [ | ||
'/books/book[@num <= $theNumber]', | ||
'/books/book[@num < $theNumber]' | ||
]; | ||
function countNodes(paths) { | ||
return paths | ||
.map(xpath.parse) | ||
.map(function (path) { return path.select(options) }) | ||
.map(function (arr) { return arr.length; }); | ||
} | ||
} | ||
function testContext(context, description) { | ||
try { | ||
var value = expr.evaluateString(context); | ||
var count = countExpr.evaluateNumber(context); | ||
assert.equal('Myrtle', value, description + ' - string value - ' + value); | ||
assert.equal(2, count, description + ' map - count - ' + count); | ||
} catch(e) { | ||
e.message = description + ': ' + (e.message || ''); | ||
throw e; | ||
assert.deepEqual(countNodes(numberPaths), [5, 4, 3, 2], 'numbers'); | ||
assert.deepEqual(countNodes(stringPaths), [5, 4, 3, 2], 'strings'); | ||
assert.deepEqual(countNodes(booleanPaths), [7, 6, 1, 0], 'numbers'); | ||
assert.deepEqual(countNodes(lhsPaths), [3, 2], 'lhs'); | ||
}); | ||
it('should correctly evaluate context position', () => { | ||
var doc = new dom().parseFromString("<books><book><chapter>The boy who lived</chapter><chapter>The vanishing glass</chapter></book><book><chapter>The worst birthday</chapter><chapter>Dobby's warning</chapter><chapter>The burrow</chapter></book></books>"); | ||
var chapters = xpath.parse('/books/book/chapter[2]').select({ node: doc }); | ||
assert.equal(2, chapters.length); | ||
assert.equal('The vanishing glass', chapters[0].textContent); | ||
assert.equal("Dobby's warning", chapters[1].textContent); | ||
var lastChapters = xpath.parse('/books/book/chapter[last()]').select({ node: doc }); | ||
assert.equal(2, lastChapters.length); | ||
assert.equal('The vanishing glass', lastChapters[0].textContent); | ||
assert.equal("The burrow", lastChapters[1].textContent); | ||
var secondChapter = xpath.parse('(/books/book/chapter)[2]').select({ node: doc }); | ||
assert.equal(1, secondChapter.length); | ||
assert.equal('The vanishing glass', chapters[0].textContent); | ||
var lastChapter = xpath.parse('(/books/book/chapter)[last()]').select({ node: doc }); | ||
assert.equal(1, lastChapter.length); | ||
assert.equal("The burrow", lastChapter[0].textContent); | ||
}); | ||
}); | ||
describe('string()', () => { | ||
it('should work with no arguments', () => { | ||
var doc = new dom().parseFromString('<book>Harry Potter</book>'); | ||
var rootElement = xpath.select1('/book', doc); | ||
assert.ok(rootElement, 'rootElement is null'); | ||
assert.equal('Harry Potter', xpath.select1('string()', doc)); | ||
}); | ||
it('should work on document fragments', () => { | ||
var doc = new dom().parseFromString('<n />'); | ||
var docFragment = doc.createDocumentFragment(); | ||
var el = doc.createElement("book"); | ||
docFragment.appendChild(el); | ||
var testValue = "Harry Potter"; | ||
el.appendChild(doc.createTextNode(testValue)); | ||
assert.equal(testValue, xpath.select1("string()", docFragment)); | ||
}); | ||
it('should work correctly on boolean values', () => { | ||
assert.equal('string', typeof xpath.select1('string(true())')); | ||
assert.equal('string', typeof xpath.select1('string(false())')); | ||
assert.equal('string', typeof xpath.select1('string(1 = 2)')); | ||
assert.ok(xpath.select1('"true" = string(true())'), '"true" = string(true())'); | ||
}); | ||
it('should work correctly on numbers', () => { | ||
assert.equal('string', typeof xpath.select1('string(45)')); | ||
assert.ok(xpath.select1('"45" = string(45)'), '"45" = string(45)'); | ||
}); | ||
}); | ||
describe('type conversion', () => { | ||
it('should convert strings to numbers correctly', () => { | ||
assert.equal(45.2, xpath.select1('number("45.200")')); | ||
assert.equal(55.0, xpath.select1('number("000055")')); | ||
assert.equal(65.0, xpath.select1('number(" 65 ")')); | ||
assert.equal(true, xpath.select1('"" != 0'), '"" != 0'); | ||
assert.equal(false, xpath.select1('"" = 0'), '"" = 0'); | ||
assert.equal(false, xpath.select1('0 = ""'), '0 = ""'); | ||
assert.equal(false, xpath.select1('0 = " "'), '0 = " "'); | ||
assert.ok(Number.isNaN(xpath.select('number("")')), 'number("")'); | ||
assert.ok(Number.isNaN(xpath.select('number("45.8g")')), 'number("45.8g")'); | ||
assert.ok(Number.isNaN(xpath.select('number("2e9")')), 'number("2e9")'); | ||
assert.ok(Number.isNaN(xpath.select('number("+33")')), 'number("+33")'); | ||
}); | ||
it('should convert numbers to strings correctly', () => { | ||
assert.equal('0.0000000000000000000000005250000000000001', xpath.parse('0.525 div 1000000 div 1000000 div 1000000 div 1000000').evaluateString()); | ||
assert.equal('525000000000000000000000', xpath.parse('0.525 * 1000000 * 1000000 * 1000000 * 1000000').evaluateString()); | ||
}); | ||
it('should provide correct string value for cdata sections', () => { | ||
const xml = "<people><person><![CDATA[Harry Potter]]></person><person>Ron <![CDATA[Weasley]]></person></people>"; | ||
const doc = new dom().parseFromString(xml); | ||
const person1 = xpath.parse("/people/person").evaluateString({ node: doc }); | ||
const person2 = xpath.parse("/people/person/text()").evaluateString({ node: doc }); | ||
const person3 = xpath.select("string(/people/person/text())", doc); | ||
const person4 = xpath.parse("/people/person[2]").evaluateString({ node: doc }); | ||
assert.equal(person1, 'Harry Potter'); | ||
assert.equal(person2, 'Harry Potter'); | ||
assert.equal(person3, 'Harry Potter'); | ||
assert.equal(person4, 'Ron Weasley'); | ||
}); | ||
it('should convert various node types to string values', () => { | ||
var xml = "<book xmlns:hp='http://harry'><!-- This describes the Harry Potter Book --><?author name='J.K. Rowling' ?><title lang='en'><![CDATA[Harry Potter & the Philosopher's Stone]]></title><character>Harry Potter</character></book>", | ||
doc = new dom().parseFromString(xml), | ||
allText = xpath.parse('.').evaluateString({ node: doc }), | ||
ns = xpath.parse('*/namespace::*[name() = "hp"]').evaluateString({ node: doc }), | ||
title = xpath.parse('*/title').evaluateString({ node: doc }), | ||
child = xpath.parse('*/*').evaluateString({ node: doc }), | ||
titleLang = xpath.parse('*/*/@lang').evaluateString({ node: doc }), | ||
pi = xpath.parse('*/processing-instruction()').evaluateString({ node: doc }), | ||
comment = xpath.parse('*/comment()').evaluateString({ node: doc }); | ||
assert.equal(allText, "Harry Potter & the Philosopher's StoneHarry Potter"); | ||
assert.equal(ns, 'http://harry'); | ||
assert.equal(title, "Harry Potter & the Philosopher's Stone"); | ||
assert.equal(child, "Harry Potter & the Philosopher's Stone"); | ||
assert.equal(titleLang, 'en'); | ||
assert.equal(pi.trim(), "name='J.K. Rowling'"); | ||
assert.equal(comment, ' This describes the Harry Potter Book '); | ||
}); | ||
it('should convert booleans to numbers correctly', () => { | ||
var num = xpath.parse('"a" = "b"').evaluateNumber(); | ||
assert.equal(num, 0); | ||
var str = xpath.select('substring("expelliarmus", 1, "a" = "a")'); | ||
assert.equal(str, 'e'); | ||
}); | ||
}); | ||
describe('parsed expressions', () => { | ||
it('should work with no options', () => { | ||
var parsed = xpath.parse('5 + 7'); | ||
assert.equal(typeof parsed, "object", "parse() should return an object"); | ||
assert.equal(typeof parsed.evaluate, "function", "parsed.evaluate should be a function"); | ||
assert.equal(typeof parsed.evaluateNumber, "function", "parsed.evaluateNumber should be a function"); | ||
assert.equal(parsed.evaluateNumber(), 12); | ||
// evaluating twice should yield the same result | ||
assert.equal(parsed.evaluateNumber(), 12); | ||
}); | ||
it('should support select1()', () => { | ||
var xml = '<book><title>Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var parsed = xpath.parse('/*/title'); | ||
assert.equal(typeof parsed, 'object', 'parse() should return an object'); | ||
assert.equal(typeof parsed.select1, 'function', 'parsed.select1 should be a function'); | ||
var single = parsed.select1({ node: doc }); | ||
assert.equal('title', single.localName); | ||
assert.equal('Harry Potter', single.firstChild.data); | ||
assert.equal('<title>Harry Potter</title>', single.toString()); | ||
}); | ||
it('should support select()', () => { | ||
var xml = '<book><title>Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var parsed = xpath.parse('/*/title'); | ||
assert.equal(typeof parsed, 'object', 'parse() should return an object'); | ||
assert.equal(typeof parsed.select, 'function', 'parsed.select should be a function'); | ||
var nodes = parsed.select({ node: doc }); | ||
assert.ok(nodes, 'parsed.select() should return a value'); | ||
assert.equal(1, nodes.length); | ||
assert.equal('title', nodes[0].localName); | ||
assert.equal('Harry Potter', nodes[0].firstChild.data); | ||
assert.equal('<title>Harry Potter</title>', nodes[0].toString()); | ||
}); | ||
it('should support .evaluateString() and .evaluateNumber()', () => { | ||
var xml = '<book><title>Harry Potter</title><numVolumes>7</numVolumes></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var parsed = xpath.parse('/*/numVolumes'); | ||
assert.equal(typeof parsed, 'object', 'parse() should return an object'); | ||
assert.equal(typeof parsed.evaluateString, 'function', 'parsed.evaluateString should be a function'); | ||
assert.equal('7', parsed.evaluateString({ node: doc })); | ||
assert.equal(typeof parsed.evaluateBoolean, 'function', 'parsed.evaluateBoolean should be a function'); | ||
assert.equal(true, parsed.evaluateBoolean({ node: doc })); | ||
assert.equal(typeof parsed.evaluateNumber, 'function', 'parsed.evaluateNumber should be a function'); | ||
assert.equal(7, parsed.evaluateNumber({ node: doc })); | ||
}); | ||
it('should support .evaluateBoolean()', () => { | ||
var xml = '<book><title>Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var context = { node: doc }; | ||
function evaluate(path) { | ||
return xpath.parse(path).evaluateBoolean(context); | ||
} | ||
} | ||
testContext({ | ||
node: doc, | ||
namespaces: { | ||
c: csns | ||
assert.equal(false, evaluate('/*/myrtle'), 'boolean value of empty node set should be false'); | ||
assert.equal(true, evaluate('not(/*/myrtle)'), 'not() of empty nodeset should be true'); | ||
assert.equal(true, evaluate('/*/title'), 'boolean value of non-empty nodeset should be true'); | ||
assert.equal(true, evaluate('/*/title = "Harry Potter"'), 'title equals Harry Potter'); | ||
assert.equal(false, evaluate('/*/title != "Harry Potter"'), 'title != Harry Potter should be false'); | ||
assert.equal(false, evaluate('/*/title = "Percy Jackson"'), 'title should not equal Percy Jackson'); | ||
}); | ||
it('should support namespaces', () => { | ||
var xml = '<characters xmlns:ps="http://philosophers-stone.com" xmlns:cs="http://chamber-secrets.com">' + | ||
'<ps:character>Quirrell</ps:character><ps:character>Fluffy</ps:character>' + | ||
'<cs:character>Myrtle</cs:character><cs:character>Tom Riddle</cs:character>' + | ||
'</characters>'; | ||
var doc = new dom().parseFromString(xml); | ||
var expr = xpath.parse('/characters/c:character'); | ||
var countExpr = xpath.parse('count(/characters/c:character)'); | ||
var csns = 'http://chamber-secrets.com'; | ||
function resolve(prefix) { | ||
if (prefix === 'c') { | ||
return csns; | ||
} | ||
} | ||
}, 'Namespace map'); | ||
testContext({ | ||
node: doc, | ||
namespaces: resolve | ||
}, 'Namespace function'); | ||
testContext({ | ||
node: doc, | ||
namespaces: { | ||
getNamespace: resolve | ||
function testContext(context, description) { | ||
try { | ||
var value = expr.evaluateString(context); | ||
var count = countExpr.evaluateNumber(context); | ||
assert.equal('Myrtle', value, description + ' - string value - ' + value); | ||
assert.equal(2, count, description + ' map - count - ' + count); | ||
} catch (e) { | ||
e.message = description + ': ' + (e.message || ''); | ||
throw e; | ||
} | ||
} | ||
}, 'Namespace object'); | ||
test.done(); | ||
} | ||
,'custom functions': function (test) { | ||
var xml = '<book><title>Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var parsed = xpath.parse('concat(double(/*/title), " is cool")'); | ||
function doubleString(context, value) { | ||
assert.equal(2, arguments.length); | ||
var str = value.stringValue(); | ||
return str + str; | ||
} | ||
function functions(name, namespace) { | ||
if(name === 'double') { | ||
return doubleString; | ||
testContext({ | ||
node: doc, | ||
namespaces: { | ||
c: csns | ||
} | ||
}, 'Namespace map'); | ||
testContext({ | ||
node: doc, | ||
namespaces: resolve | ||
}, 'Namespace function'); | ||
testContext({ | ||
node: doc, | ||
namespaces: { | ||
getNamespace: resolve | ||
} | ||
}, 'Namespace object'); | ||
}); | ||
it('should support custom functions', () => { | ||
var xml = '<book><title>Harry Potter</title></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var parsed = xpath.parse('concat(double(/*/title), " is cool")'); | ||
function doubleString(context, value) { | ||
assert.equal(2, arguments.length); | ||
var str = value.stringValue(); | ||
return str + str; | ||
} | ||
return null; | ||
} | ||
function testContext(context, description) { | ||
try{ | ||
var actual = parsed.evaluateString(context); | ||
var expected = 'Harry PotterHarry Potter is cool'; | ||
assert.equal(expected, actual, description + ' - ' + expected + ' != ' + actual); | ||
} catch (e) { | ||
e.message = description + ": " + (e.message || ''); | ||
throw e; | ||
function functions(name, namespace) { | ||
if (name === 'double') { | ||
return doubleString; | ||
} | ||
return null; | ||
} | ||
} | ||
testContext({ | ||
node: doc, | ||
functions: functions | ||
}, 'Functions function'); | ||
testContext({ | ||
node: doc, | ||
functions: { | ||
getFunction: functions | ||
function testContext(context, description) { | ||
try { | ||
var actual = parsed.evaluateString(context); | ||
var expected = 'Harry PotterHarry Potter is cool'; | ||
assert.equal(expected, actual, description + ' - ' + expected + ' != ' + actual); | ||
} catch (e) { | ||
e.message = description + ": " + (e.message || ''); | ||
throw e; | ||
} | ||
} | ||
}, 'Functions object'); | ||
testContext({ | ||
node: doc, | ||
functions: { | ||
double: doubleString | ||
} | ||
}, 'Functions map'); | ||
test.done(); | ||
} | ||
,'custom function namespaces': function (test) { | ||
var xml = '<book><title>Harry Potter</title><friend>Ron</friend><friend>Hermione</friend><friend>Neville</friend></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var parsed = xpath.parse('concat(hp:double(/*/title), " is 2 cool ", hp:square(2), " school")'); | ||
var hpns = 'http://harry-potter.com'; | ||
var namespaces = { | ||
hp: hpns | ||
}; | ||
var context = { | ||
node: doc, | ||
namespaces: { | ||
testContext({ | ||
node: doc, | ||
functions: functions | ||
}, 'Functions function'); | ||
testContext({ | ||
node: doc, | ||
functions: { | ||
getFunction: functions | ||
} | ||
}, 'Functions object'); | ||
testContext({ | ||
node: doc, | ||
functions: { | ||
double: doubleString | ||
} | ||
}, 'Functions map'); | ||
}); | ||
it('should support custom functions in namespaces', () => { | ||
var xml = '<book><title>Harry Potter</title><friend>Ron</friend><friend>Hermione</friend><friend>Neville</friend></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var parsed = xpath.parse('concat(hp:double(/*/title), " is 2 cool ", hp:square(2), " school")'); | ||
var hpns = 'http://harry-potter.com'; | ||
var namespaces = { | ||
hp: hpns | ||
}, | ||
functions: function (name, namespace) { | ||
if (namespace === hpns) { | ||
switch (name) { | ||
case "double": | ||
return function (context, value) { | ||
assert.equal(2, arguments.length); | ||
var str = value.stringValue(); | ||
return str + str; | ||
}; | ||
case "square": | ||
return function (context, value) { | ||
var num = value.numberValue(); | ||
return num * num; | ||
}; | ||
}; | ||
case "xor": | ||
return function (context, l, r) { | ||
assert.equal(3, arguments.length); | ||
var lbool = l.booleanValue(); | ||
var rbool = r.booleanValue(); | ||
return (lbool || rbool) && !(lbool && rbool); | ||
}; | ||
var context = { | ||
node: doc, | ||
namespaces: { | ||
hp: hpns | ||
}, | ||
functions: function (name, namespace) { | ||
if (namespace === hpns) { | ||
switch (name) { | ||
case "double": | ||
return function (context, value) { | ||
assert.equal(2, arguments.length); | ||
var str = value.stringValue(); | ||
return str + str; | ||
}; | ||
case "square": | ||
return function (context, value) { | ||
var num = value.numberValue(); | ||
return num * num; | ||
}; | ||
case "second": | ||
return function (context, nodes) { | ||
var nodesArr = nodes.toArray(); | ||
var second = nodesArr[1]; | ||
return second ? [second] : []; | ||
}; | ||
case "xor": | ||
return function (context, l, r) { | ||
assert.equal(3, arguments.length); | ||
var lbool = l.booleanValue(); | ||
var rbool = r.booleanValue(); | ||
return (lbool || rbool) && !(lbool && rbool); | ||
}; | ||
case "second": | ||
return function (context, nodes) { | ||
var nodesArr = nodes.toArray(); | ||
var second = nodesArr[1]; | ||
return second ? [second] : []; | ||
}; | ||
} | ||
} | ||
return null; | ||
} | ||
return null; | ||
} | ||
}; | ||
assert.equal('Harry PotterHarry Potter is 2 cool 4 school', parsed.evaluateString(context)); | ||
}; | ||
assert.equal(false, xpath.parse('hp:xor(false(), false())').evaluateBoolean(context)); | ||
assert.equal(true, xpath.parse('hp:xor(false(), true())').evaluateBoolean(context)); | ||
assert.equal(true, xpath.parse('hp:xor(true(), false())').evaluateBoolean(context)); | ||
assert.equal(false, xpath.parse('hp:xor(true(), true())').evaluateBoolean(context)); | ||
assert.equal('Harry PotterHarry Potter is 2 cool 4 school', parsed.evaluateString(context)); | ||
assert.equal('Hermione', xpath.parse('hp:second(/*/friend)').evaluateString(context)); | ||
assert.equal(1, xpath.parse('count(hp:second(/*/friend))').evaluateNumber(context)); | ||
assert.equal(0, xpath.parse('count(hp:second(/*/friendz))').evaluateNumber(context)); | ||
test.done(); | ||
} | ||
,'xpath variables': function (test) { | ||
var xml = '<book><title>Harry Potter</title><volumes>7</volumes></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var variables = { | ||
title: 'Harry Potter', | ||
notTitle: 'Percy Jackson', | ||
houses: 4 | ||
}; | ||
function variableFunction(name) { | ||
return variables[name]; | ||
} | ||
function testContext(context, description) { | ||
try{ | ||
assert.equal(true, xpath.parse('$title = /*/title').evaluateBoolean(context)); | ||
assert.equal(false, xpath.parse('$notTitle = /*/title').evaluateBoolean(context)); | ||
assert.equal(11, xpath.parse('$houses + /*/volumes').evaluateNumber(context)); | ||
} catch (e) { | ||
e.message = description + ": " + (e.message || ''); | ||
throw e; | ||
} | ||
} | ||
assert.equal(false, xpath.parse('hp:xor(false(), false())').evaluateBoolean(context)); | ||
assert.equal(true, xpath.parse('hp:xor(false(), true())').evaluateBoolean(context)); | ||
assert.equal(true, xpath.parse('hp:xor(true(), false())').evaluateBoolean(context)); | ||
assert.equal(false, xpath.parse('hp:xor(true(), true())').evaluateBoolean(context)); | ||
testContext({ | ||
node: doc, | ||
variables: variableFunction | ||
}, 'Variables function'); | ||
assert.equal('Hermione', xpath.parse('hp:second(/*/friend)').evaluateString(context)); | ||
assert.equal(1, xpath.parse('count(hp:second(/*/friend))').evaluateNumber(context)); | ||
assert.equal(0, xpath.parse('count(hp:second(/*/friendz))').evaluateNumber(context)); | ||
}); | ||
testContext({ | ||
node: doc, | ||
variables: { | ||
getVariable: variableFunction | ||
it('should support xpath variables', () => { | ||
var xml = '<book><title>Harry Potter</title><volumes>7</volumes></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var variables = { | ||
title: 'Harry Potter', | ||
notTitle: 'Percy Jackson', | ||
houses: 4 | ||
}; | ||
function variableFunction(name) { | ||
return variables[name]; | ||
} | ||
}, 'Variables object'); | ||
testContext({ | ||
node: doc, | ||
variables: variables | ||
}, 'Variables map'); | ||
test.done(); | ||
} | ||
,'xpath variable namespaces': function (test) { | ||
var xml = '<book><title>Harry Potter</title><volumes>7</volumes></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var hpns = 'http://harry-potter.com'; | ||
var context = { | ||
node: doc, | ||
namespaces: { | ||
hp: hpns | ||
}, | ||
variables: function(name, namespace) { | ||
if (namespace === hpns) { | ||
switch (name) { | ||
case 'title': return 'Harry Potter'; | ||
case 'houses': return 4; | ||
case 'false': return false; | ||
case 'falseStr': return 'false'; | ||
} | ||
} else if (namespace === '') { | ||
switch (name) { | ||
case 'title': return 'World'; | ||
} | ||
function testContext(context, description) { | ||
try { | ||
assert.equal(true, xpath.parse('$title = /*/title').evaluateBoolean(context)); | ||
assert.equal(false, xpath.parse('$notTitle = /*/title').evaluateBoolean(context)); | ||
assert.equal(11, xpath.parse('$houses + /*/volumes').evaluateNumber(context)); | ||
} catch (e) { | ||
e.message = description + ": " + (e.message || ''); | ||
throw e; | ||
} | ||
return null; | ||
} | ||
}; | ||
assert.equal(true, xpath.parse('$hp:title = /*/title').evaluateBoolean(context)); | ||
assert.equal(false, xpath.parse('$title = /*/title').evaluateBoolean(context)); | ||
assert.equal('World', xpath.parse('$title').evaluateString(context)); | ||
assert.equal(false, xpath.parse('$hp:false').evaluateBoolean(context)); | ||
assert.notEqual(false, xpath.parse('$hp:falseStr').evaluateBoolean(context)); | ||
assert.throws(function () { | ||
xpath.parse('$hp:hello').evaluateString(context); | ||
}, function (err) { | ||
return err.message === 'Undeclared variable: $hp:hello'; | ||
testContext({ | ||
node: doc, | ||
variables: variableFunction | ||
}, 'Variables function'); | ||
testContext({ | ||
node: doc, | ||
variables: { | ||
getVariable: variableFunction | ||
} | ||
}, 'Variables object'); | ||
testContext({ | ||
node: doc, | ||
variables: variables | ||
}, 'Variables map'); | ||
}); | ||
test.done(); | ||
} | ||
,"detect unterminated string literals": function (test) { | ||
function testUnterminated(path) { | ||
it('should support variables with namespaces', () => { | ||
var xml = '<book><title>Harry Potter</title><volumes>7</volumes></book>'; | ||
var doc = new dom().parseFromString(xml); | ||
var hpns = 'http://harry-potter.com'; | ||
var context = { | ||
node: doc, | ||
namespaces: { | ||
hp: hpns | ||
}, | ||
variables: function (name, namespace) { | ||
if (namespace === hpns) { | ||
switch (name) { | ||
case 'title': return 'Harry Potter'; | ||
case 'houses': return 4; | ||
case 'false': return false; | ||
case 'falseStr': return 'false'; | ||
} | ||
} else if (namespace === '') { | ||
switch (name) { | ||
case 'title': return 'World'; | ||
} | ||
} | ||
return null; | ||
} | ||
}; | ||
assert.equal(true, xpath.parse('$hp:title = /*/title').evaluateBoolean(context)); | ||
assert.equal(false, xpath.parse('$title = /*/title').evaluateBoolean(context)); | ||
assert.equal('World', xpath.parse('$title').evaluateString(context)); | ||
assert.equal(false, xpath.parse('$hp:false').evaluateBoolean(context)); | ||
assert.notEqual(false, xpath.parse('$hp:falseStr').evaluateBoolean(context)); | ||
assert.throws(function () { | ||
xpath.evaluate('"hello'); | ||
xpath.parse('$hp:hello').evaluateString(context); | ||
}, function (err) { | ||
return err.message.indexOf('Unterminated') !== -1; | ||
return err.message === 'Undeclared variable: $hp:hello'; | ||
}); | ||
} | ||
testUnterminated('"Hello'); | ||
testUnterminated("'Hello"); | ||
testUnterminated('self::text() = "\""'); | ||
testUnterminated('"\""'); | ||
}); | ||
test.done(); | ||
} | ||
,"string value for CDATA sections": function (test) { | ||
var xml = "<people><person><![CDATA[Harry Potter]]></person><person>Ron <![CDATA[Weasley]]></person></people>", | ||
doc = new dom().parseFromString(xml), | ||
person1 = xpath.parse("/people/person").evaluateString({ node: doc }), | ||
person2 = xpath.parse("/people/person/text()").evaluateString({ node: doc }), | ||
person3 = xpath.select("string(/people/person/text())", doc); | ||
person4 = xpath.parse("/people/person[2]").evaluateString({ node: doc }); | ||
it('should support .toString()', () => { | ||
var parser = new xpath.XPathParser(); | ||
assert.equal(person1, 'Harry Potter'); | ||
assert.equal(person2, 'Harry Potter'); | ||
assert.equal(person3, 'Harry Potter'); | ||
assert.equal(person4, 'Ron Weasley'); | ||
test.done(); | ||
} | ||
,"string value of various node types": function (test) { | ||
var xml = "<book xmlns:hp='http://harry'><!-- This describes the Harry Potter Book --><?author name='J.K. Rowling' ?><title lang='en'><![CDATA[Harry Potter & the Philosopher's Stone]]></title><character>Harry Potter</character></book>", | ||
doc = new dom().parseFromString(xml), | ||
allText = xpath.parse('.').evaluateString({ node: doc }), | ||
ns = xpath.parse('*/namespace::*[name() = "hp"]').evaluateString({ node: doc }), | ||
title = xpath.parse('*/title').evaluateString({ node: doc }), | ||
child = xpath.parse('*/*').evaluateString({ node: doc }), | ||
titleLang = xpath.parse('*/*/@lang').evaluateString({ node: doc }), | ||
pi = xpath.parse('*/processing-instruction()').evaluateString({ node: doc }), | ||
comment = xpath.parse('*/comment()').evaluateString({ node: doc }); | ||
assert.equal(allText, "Harry Potter & the Philosopher's StoneHarry Potter"); | ||
assert.equal(ns, 'http://harry'); | ||
assert.equal(title, "Harry Potter & the Philosopher's Stone"); | ||
assert.equal(child, "Harry Potter & the Philosopher's Stone"); | ||
assert.equal(titleLang, 'en'); | ||
assert.equal(pi.trim(), "name='J.K. Rowling'"); | ||
assert.equal(comment, ' This describes the Harry Potter Book '); | ||
test.done(); | ||
} | ||
,"exposes custom types": function (test) { | ||
assert.ok(xpath.XPath, "xpath.XPath"); | ||
assert.ok(xpath.XPathParser, "xpath.XPathParser"); | ||
assert.ok(xpath.XPathResult, "xpath.XPathResult"); | ||
assert.ok(xpath.Step, "xpath.Step"); | ||
assert.ok(xpath.NodeTest, "xpath.NodeTest"); | ||
assert.ok(xpath.BarOperation, "xpath.BarOperation"); | ||
assert.ok(xpath.NamespaceResolver, "xpath.NamespaceResolver"); | ||
assert.ok(xpath.FunctionResolver, "xpath.FunctionResolver"); | ||
assert.ok(xpath.VariableResolver, "xpath.VariableResolver"); | ||
assert.ok(xpath.Utilities, "xpath.Utilities"); | ||
assert.ok(xpath.XPathContext, "xpath.XPathContext"); | ||
assert.ok(xpath.XNodeSet, "xpath.XNodeSet"); | ||
assert.ok(xpath.XBoolean, "xpath.XBoolean"); | ||
assert.ok(xpath.XString, "xpath.XString"); | ||
assert.ok(xpath.XNumber, "xpath.XNumber"); | ||
test.done(); | ||
} | ||
,"work with nodes created using DOM1 createElement()": function (test) { | ||
var doc = new dom().parseFromString('<book />'); | ||
doc.documentElement.appendChild(doc.createElement('characters')); | ||
assert.ok(xpath.select1('/book/characters', doc)); | ||
assert.equal(xpath.select1('local-name(/book/characters)', doc), 'characters'); | ||
var simpleStep = parser.parse('my:book'); | ||
test.done(); | ||
} | ||
,"preceding:: axis works on document fragments": function (test) { | ||
var doc = new dom().parseFromString('<n />'), | ||
df = doc.createDocumentFragment(), | ||
root = doc.createElement('book'); | ||
df.appendChild(root); | ||
for (var i = 0; i < 10; i += 1) { | ||
root.appendChild(doc.createElement('chapter')); | ||
} | ||
var chapter = xpath.select1("book/chapter[5]", df); | ||
assert.ok(chapter, 'chapter'); | ||
assert.equal(xpath.select("count(preceding::chapter)", chapter), 4); | ||
test.done(); | ||
} | ||
,"node set sorted and unsorted arrays": function (test) { | ||
var doc = new dom().parseFromString('<book><character>Harry</character><character>Ron</character><character>Hermione</character></book>'), | ||
path = xpath.parse("/*/*[3] | /*/*[2] | /*/*[1]") | ||
nset = path.evaluateNodeSet({ node: doc }), | ||
sorted = nset.toArray(), | ||
unsorted = nset.toUnsortedArray(); | ||
assert.equal(sorted.length, 3); | ||
assert.equal(unsorted.length, 3); | ||
assert.equal(sorted[0].textContent, 'Harry'); | ||
assert.equal(sorted[1].textContent, 'Ron'); | ||
assert.equal(sorted[2].textContent, 'Hermione'); | ||
assert.notEqual(sorted[0], unsorted[0], "first nodeset element equal"); | ||
test.done(); | ||
} | ||
,'meaningful error for invalid function': function(test) { | ||
var path = xpath.parse('invalidFunc()'); | ||
assert.equal(simpleStep.toString(), 'child::my:book'); | ||
assert.throws(function () { | ||
path.evaluateString(); | ||
}, function (err) { | ||
return err.message.indexOf('invalidFunc') !== -1; | ||
var precedingSib = parser.parse('preceding-sibling::my:chapter'); | ||
assert.equal(precedingSib.toString(), 'preceding-sibling::my:chapter'); | ||
var withPredicates = parser.parse('book[number > 3][contains(title, "and the")]'); | ||
assert.equal(withPredicates.toString(), "child::book[(child::number > 3)][contains(child::title, 'and the')]"); | ||
var parenthesisWithPredicate = parser.parse('(/books/book/chapter)[7]'); | ||
assert.equal(parenthesisWithPredicate.toString(), '(/child::books/child::book/child::chapter)[7]'); | ||
var charactersOver20 = parser.parse('heroes[age > 20] | villains[age > 20]'); | ||
assert.equal(charactersOver20.toString(), 'child::heroes[(child::age > 20)] | child::villains[(child::age > 20)]'); | ||
}); | ||
var path2 = xpath.parse('funcs:invalidFunc()'); | ||
assert.throws(function () { | ||
path2.evaluateString({ | ||
namespaces: { | ||
funcs: 'myfunctions' | ||
} | ||
}); | ||
}, function (err) { | ||
return err.message.indexOf('invalidFunc') !== -1; | ||
}); | ||
describe('html-mode support', () => { | ||
it('should allow null namespaces for nodes with no prefix', () => { | ||
var markup = `<html> | ||
<head></head> | ||
<body> | ||
<p>Hi Ron!</p> | ||
<my:p xmlns:my="http://www.example.com/my">Hi Draco!</p> | ||
<p>Hi Hermione!</p> | ||
</body> | ||
</html>`; | ||
var docHtml = new dom().parseFromString(markup, 'text/html'); | ||
var noPrefixPath = xpath.parse('/html/body/p[2]'); | ||
var greetings1 = noPrefixPath.select({ node: docHtml, allowAnyNamespaceForNoPrefix: false }); | ||
assert.equal(0, greetings1.length); | ||
var allowAnyNamespaceOptions = { node: docHtml, allowAnyNamespaceForNoPrefix: true }; | ||
// if allowAnyNamespaceForNoPrefix specified, allow using prefix-less node tests to match nodes with no prefix | ||
var greetings2 = noPrefixPath.select(allowAnyNamespaceOptions); | ||
assert.equal(1, greetings2.length); | ||
assert.equal('Hi Hermione!', greetings2[0].textContent); | ||
var allGreetings = xpath.parse('/html/body/p').select(allowAnyNamespaceOptions); | ||
assert.equal(2, allGreetings.length); | ||
var nsm = { html: xhtmlNs, other: 'http://www.example.com/other' }; | ||
var prefixPath = xpath.parse('/html:html/body/html:p'); | ||
var optionsWithNamespaces = { node: docHtml, allowAnyNamespaceForNoPrefix: true, namespaces: nsm }; | ||
// if the path uses prefixes, they have to match | ||
var greetings3 = prefixPath.select(optionsWithNamespaces); | ||
assert.equal(2, greetings3.length); | ||
var badPrefixPath = xpath.parse('/html:html/other:body/html:p'); | ||
var greetings4 = badPrefixPath.select(optionsWithNamespaces); | ||
assert.strictEqual(0, greetings4.length); | ||
}); | ||
test.done(); | ||
} | ||
// https://github.com/goto100/xpath/issues/32 | ||
,'supports contains() function on attributes': function (test) { | ||
var doc = new dom().parseFromString("<books><book title='Harry Potter and the Philosopher\"s Stone' /><book title='Harry Potter and the Chamber of Secrets' /></books>"), | ||
andTheBooks = xpath.select("/books/book[contains(@title, ' ')]", doc), | ||
secretBooks = xpath.select("/books/book[contains(@title, 'Secrets')]", doc); | ||
assert.equal(andTheBooks.length, 2); | ||
assert.equal(secretBooks.length, 1); | ||
test.done(); | ||
} | ||
,'compare multiple nodes to multiple nodes (equals)': function (test) { | ||
var xml = '<school><houses>' + | ||
'<house name="Gryffindor"><student>Harry</student><student>Hermione</student></house>' + | ||
'<house name="Slytherin"><student>Draco</student><student>Crabbe</student></house>' + | ||
'<house name="Ravenclaw"><student>Luna</student><student>Cho</student></house>' + | ||
'</houses>' + | ||
'<honorStudents><student>Hermione</student><student>Luna</student></honorStudents></school>'; | ||
it('should support the isHtml option', () => { | ||
var markup = '<html><head></head><body><p>Hi Ron!</p><my:p xmlns:my="http://www.example.com/my">Hi Draco!</p><p>Hi Hermione!</p></body></html>'; | ||
var docHtml = new dom().parseFromString(markup, 'text/html'); | ||
var doc = new dom().parseFromString(xml); | ||
var houses = xpath.parse('/school/houses/house[student = /school/honorStudents/student]').select({ node: doc }); | ||
var ns = { h: xhtmlNs }; | ||
assert.equal(houses.length, 2); | ||
// allow matching on unprefixed nodes | ||
var greetings1 = xpath.parse('/html/body/p').select({ node: docHtml, isHtml: true }); | ||
var houseNames = houses.map(function (node) { return node.getAttribute('name'); }).sort(); | ||
assert.equal(2, greetings1.length); | ||
assert.equal(houseNames[0], 'Gryffindor'); | ||
assert.equal(houseNames[1], 'Ravenclaw'); | ||
test.done(); | ||
} | ||
// allow case insensitive match | ||
var greetings2 = xpath.parse('/h:html/h:bOdY/h:p').select({ node: docHtml, namespaces: ns, isHtml: true }); | ||
,'compare multiple nodes to multiple nodes (gte)': function (test) { | ||
var xml = '<school><houses>' + | ||
'<house name="Gryffindor"><student level="5">Harry</student><student level="9">Hermione</student></house>' + | ||
'<house name="Slytherin"><student level="1">Goyle</student><student level="1">Crabbe</student></house>' + | ||
'<house name="Ravenclaw"><student level="4">Luna</student><student level="3">Cho</student></house>' + | ||
'</houses>' + | ||
'<courses><course minLevel="9">DADA</course><course minLevel="4">Charms</course></courses>' + | ||
'</school>'; | ||
assert.equal(2, greetings2.length); | ||
var doc = new dom().parseFromString(xml); | ||
var houses = xpath.parse('/school/houses/house[student/@level >= /school/courses/course/@minLevel]').select({ node: doc }); | ||
// non-html mode: allow select if case and namespaces match | ||
var greetings3 = xpath.parse('/h:html/h:body/h:p').select({ node: docHtml, namespaces: ns }); | ||
assert.equal(houses.length, 2); | ||
assert.equal(2, greetings3.length); | ||
var houseNames = houses.map(function (node) { return node.getAttribute('name'); }).sort(); | ||
// non-html mode: require namespaces | ||
var greetings4 = xpath.parse('/html/body/p').select({ node: docHtml, namespaces: ns }); | ||
assert.equal(houseNames[0], 'Gryffindor'); | ||
assert.equal(houseNames[1], 'Ravenclaw'); | ||
test.done(); | ||
} | ||
assert.equal(0, greetings4.length); | ||
,'inequality comparisons with nodesets': function (test) { | ||
var xml = "<books><book num='1' title='PS' /><book num='2' title='CoS' /><book num='3' title='PoA' /><book num='4' title='GoF' /><book num='5' title='OotP' /><book num='6' title='HBP' /><book num='7' title='DH' /></books>"; | ||
var doc = new dom().parseFromString(xml); | ||
// non-html mode: require case to match | ||
var greetings5 = xpath.parse('/h:html/h:bOdY/h:p').select({ node: docHtml, namespaces: ns }); | ||
var options = { node: doc, variables: { theNumber: 3, theString: '3', theBoolean: true }}; | ||
assert.equal(0, greetings5.length); | ||
}); | ||
}); | ||
var numberPaths = [ | ||
'/books/book[$theNumber <= @num]', | ||
'/books/book[$theNumber < @num]', | ||
'/books/book[$theNumber >= @num]', | ||
'/books/book[$theNumber > @num]' | ||
]; | ||
describe('functions', () => { | ||
it('should provide a meaningful error for invalid functions', () => { | ||
var path = xpath.parse('invalidFunc()'); | ||
var stringPaths = [ | ||
'/books/book[$theString <= @num]', | ||
'/books/book[$theString < @num]', | ||
'/books/book[$theString >= @num]', | ||
'/books/book[$theString > @num]' | ||
]; | ||
assert.throws(function () { | ||
path.evaluateString(); | ||
}, function (err) { | ||
return err.message.indexOf('invalidFunc') !== -1; | ||
}); | ||
var booleanPaths = [ | ||
'/books/book[$theBoolean <= @num]', | ||
'/books/book[$theBoolean < @num]', | ||
'/books/book[$theBoolean >= @num]', | ||
'/books/book[$theBoolean > @num]' | ||
]; | ||
var path2 = xpath.parse('funcs:invalidFunc()'); | ||
var lhsPaths = [ | ||
'/books/book[@num <= $theNumber]', | ||
'/books/book[@num < $theNumber]' | ||
]; | ||
assert.throws(function () { | ||
path2.evaluateString({ | ||
namespaces: { | ||
funcs: 'myfunctions' | ||
} | ||
}); | ||
}, function (err) { | ||
return err.message.indexOf('invalidFunc') !== -1; | ||
}); | ||
}); | ||
function countNodes(paths){ | ||
return paths | ||
.map(xpath.parse) | ||
.map(function (path) { return path.select(options) }) | ||
.map(function (arr) { return arr.length; }); | ||
} | ||
assert.deepEqual(countNodes(numberPaths), [5, 4, 3, 2], 'numbers'); | ||
assert.deepEqual(countNodes(stringPaths), [5, 4, 3, 2], 'strings'); | ||
assert.deepEqual(countNodes(booleanPaths), [7, 6, 1, 0], 'numbers'); | ||
assert.deepEqual(countNodes(lhsPaths), [3, 2], 'lhs'); | ||
test.done(); | ||
} | ||
// https://github.com/goto100/xpath/issues/32 | ||
it('should support the contains() function on attributes', () => { | ||
var doc = new dom().parseFromString("<books><book title='Harry Potter and the Philosopher\"s Stone' /><book title='Harry Potter and the Chamber of Secrets' /></books>"), | ||
andTheBooks = xpath.select("/books/book[contains(@title, ' ')]", doc), | ||
secretBooks = xpath.select("/books/book[contains(@title, 'Secrets')]", doc); | ||
,'error when evaluating boolean as number': function (test) { | ||
var num = xpath.parse('"a" = "b"').evaluateNumber(); | ||
assert.equal(num, 0); | ||
var str = xpath.select('substring("expelliarmus", 1, "a" = "a")'); | ||
assert.equal(str, 'e'); | ||
test.done(); | ||
} | ||
,'string values of parsed expressions': function (test) { | ||
var parser = new xpath.XPathParser(); | ||
assert.equal(andTheBooks.length, 2); | ||
assert.equal(secretBooks.length, 1); | ||
}); | ||
var simpleStep = parser.parse('my:book'); | ||
it('should support builtin functions', () => { | ||
var translated = xpath.parse('translate("hello", "lhho", "yHb")').evaluateString(); | ||
assert.equal(simpleStep.toString(), 'child::my:book'); | ||
assert.equal('Heyy', translated); | ||
var precedingSib = parser.parse('preceding-sibling::my:chapter'); | ||
var characters = new dom().parseFromString('<characters><character>Harry</character><character>Ron</character><character>Hermione</character></characters>'); | ||
assert.equal(precedingSib.toString(), 'preceding-sibling::my:chapter'); | ||
var firstTwo = xpath.parse('/characters/character[position() <= 2]').select({ node: characters }); | ||
var withPredicates = parser.parse('book[number > 3][contains(title, "and the")]'); | ||
assert.equal(2, firstTwo.length); | ||
assert.equal('Harry', firstTwo[0].textContent); | ||
assert.equal('Ron', firstTwo[1].textContent); | ||
assert.equal(withPredicates.toString(), "child::book[(child::number > 3)][contains(child::title, 'and the')]"); | ||
var last = xpath.parse('/characters/character[last()]').select({ node: characters }); | ||
var parenthesisWithPredicate = parser.parse('(/books/book/chapter)[7]'); | ||
assert.equal(parenthesisWithPredicate.toString(), '(/child::books/child::book/child::chapter)[7]'); | ||
assert.equal(1, last.length); | ||
assert.equal('Hermione', last[0].textContent); | ||
}); | ||
}); | ||
var charactersOver20 = parser.parse('heroes[age > 20] | villains[age > 20]'); | ||
assert.equal(charactersOver20.toString(), 'child::heroes[(child::age > 20)] | child::villains[(child::age > 20)]'); | ||
test.done(); | ||
} | ||
,'context position should work correctly': function (test) { | ||
var doc = new dom().parseFromString("<books><book><chapter>The boy who lived</chapter><chapter>The vanishing glass</chapter></book><book><chapter>The worst birthday</chapter><chapter>Dobby's warning</chapter><chapter>The burrow</chapter></book></books>"); | ||
var chapters = xpath.parse('/books/book/chapter[2]').select({ node: doc }); | ||
assert.equal(2, chapters.length); | ||
assert.equal('The vanishing glass', chapters[0].textContent); | ||
assert.equal("Dobby's warning", chapters[1].textContent); | ||
describe('miscellaneous', () => { | ||
it('should create XPathExceptions that act like Errors', () => { | ||
try { | ||
xpath.evaluate('1', null, null, null); | ||
assert.fail(null, null, 'evaluate() should throw exception'); | ||
} catch (e) { | ||
assert.ok('code' in e, 'must have a code'); | ||
assert.ok('stack' in e, 'must have a stack'); | ||
} | ||
}); | ||
var lastChapters = xpath.parse('/books/book/chapter[last()]').select({ node: doc }); | ||
assert.equal(2, lastChapters.length); | ||
assert.equal('The vanishing glass', lastChapters[0].textContent); | ||
assert.equal("The burrow", lastChapters[1].textContent); | ||
it('should expose custom types', () => { | ||
assert.ok(xpath.XPath, "xpath.XPath"); | ||
assert.ok(xpath.XPathParser, "xpath.XPathParser"); | ||
assert.ok(xpath.XPathResult, "xpath.XPathResult"); | ||
var secondChapter = xpath.parse('(/books/book/chapter)[2]').select({ node: doc }); | ||
assert.equal(1, secondChapter.length); | ||
assert.equal('The vanishing glass', chapters[0].textContent); | ||
assert.ok(xpath.Step, "xpath.Step"); | ||
assert.ok(xpath.NodeTest, "xpath.NodeTest"); | ||
assert.ok(xpath.BarOperation, "xpath.BarOperation"); | ||
var lastChapter = xpath.parse('(/books/book/chapter)[last()]').select({ node: doc }); | ||
assert.equal(1, lastChapter.length); | ||
assert.equal("The burrow", lastChapter[0].textContent); | ||
assert.ok(xpath.NamespaceResolver, "xpath.NamespaceResolver"); | ||
assert.ok(xpath.FunctionResolver, "xpath.FunctionResolver"); | ||
assert.ok(xpath.VariableResolver, "xpath.VariableResolver"); | ||
test.done(); | ||
} | ||
,'should allow null namespaces for null prefixes': function (test) { | ||
var markup = '<html><head></head><body><p>Hi Ron!</p><my:p xmlns:my="http://www.example.com/my">Hi Draco!</p><p>Hi Hermione!</p></body></html>'; | ||
var docHtml = new dom().parseFromString(markup, 'text/html'); | ||
var noPrefixPath = xpath.parse('/html/body/p[2]'); | ||
var greetings1 = noPrefixPath.select({ node: docHtml, allowAnyNamespaceForNoPrefix: false }); | ||
assert.equal(0, greetings1.length); | ||
var allowAnyNamespaceOptions = { node: docHtml, allowAnyNamespaceForNoPrefix: true }; | ||
// if allowAnyNamespaceForNoPrefix specified, allow using prefix-less node tests to match nodes with no prefix | ||
var greetings2 = noPrefixPath.select(allowAnyNamespaceOptions); | ||
assert.equal(1, greetings2.length); | ||
assert.equal('Hi Hermione!', greetings2[0].textContent); | ||
assert.ok(xpath.Utilities, "xpath.Utilities"); | ||
var allGreetings = xpath.parse('/html/body/p').select(allowAnyNamespaceOptions); | ||
assert.equal(2, allGreetings.length); | ||
var nsm = { html: xhtmlNs, other: 'http://www.example.com/other' }; | ||
var prefixPath = xpath.parse('/html:html/body/html:p'); | ||
var optionsWithNamespaces = { node: docHtml, allowAnyNamespaceForNoPrefix: true, namespaces: nsm }; | ||
// if the path uses prefixes, they have to match | ||
var greetings3 = prefixPath.select(optionsWithNamespaces); | ||
assert.equal(2, greetings3.length); | ||
var badPrefixPath = xpath.parse('/html:html/other:body/html:p'); | ||
var greetings4 = badPrefixPath.select(optionsWithNamespaces); | ||
test.done(); | ||
} | ||
,'support isHtml option' : function (test){ | ||
var markup = '<html><head></head><body><p>Hi Ron!</p><my:p xmlns:my="http://www.example.com/my">Hi Draco!</p><p>Hi Hermione!</p></body></html>'; | ||
var docHtml = new dom().parseFromString(markup, 'text/html'); | ||
var ns = { h: xhtmlNs }; | ||
// allow matching on unprefixed nodes | ||
var greetings1 = xpath.parse('/html/body/p').select({ node: docHtml, isHtml: true }); | ||
assert.equal(2, greetings1.length); | ||
// allow case insensitive match | ||
var greetings2 = xpath.parse('/h:html/h:bOdY/h:p').select({ node: docHtml, namespaces: ns, isHtml: true }); | ||
assert.equal(2, greetings2.length); | ||
// non-html mode: allow select if case and namespaces match | ||
var greetings3 = xpath.parse('/h:html/h:body/h:p').select({ node: docHtml, namespaces: ns }); | ||
assert.ok(xpath.XPathContext, "xpath.XPathContext"); | ||
assert.ok(xpath.XNodeSet, "xpath.XNodeSet"); | ||
assert.ok(xpath.XBoolean, "xpath.XBoolean"); | ||
assert.ok(xpath.XString, "xpath.XString"); | ||
assert.ok(xpath.XNumber, "xpath.XNumber"); | ||
}); | ||
assert.equal(2, greetings3.length); | ||
// non-html mode: require namespaces | ||
var greetings4 = xpath.parse('/html/body/p').select({ node: docHtml, namespaces: ns }); | ||
it('should work with nodes created using DOM1 createElement()', () => { | ||
var doc = new dom().parseFromString('<book />'); | ||
assert.equal(0, greetings4.length); | ||
// non-html mode: require case to match | ||
var greetings5 = xpath.parse('/h:html/h:bOdY/h:p').select({ node: docHtml, namespaces: ns }); | ||
doc.documentElement.appendChild(doc.createElement('characters')); | ||
assert.equal(0, greetings5.length); | ||
test.done(); | ||
} | ||
,"builtin functions": function (test) { | ||
var translated = xpath.parse('translate("hello", "lhho", "yHb")').evaluateString(); | ||
assert.equal('Heyy', translated); | ||
var characters = new dom().parseFromString('<characters><character>Harry</character><character>Ron</character><character>Hermione</character></characters>'); | ||
var firstTwo = xpath.parse('/characters/character[position() <= 2]').select({ node: characters }); | ||
assert.equal(2, firstTwo.length); | ||
assert.equal('Harry', firstTwo[0].textContent); | ||
assert.equal('Ron', firstTwo[1].textContent); | ||
assert.ok(xpath.select1('/book/characters', doc)); | ||
var last = xpath.parse('/characters/character[last()]').select({ node: characters }); | ||
assert.equal(1, last.length); | ||
assert.equal('Hermione', last[0].textContent); | ||
test.done(); | ||
} | ||
} | ||
assert.equal(xpath.select1('local-name(/book/characters)', doc), 'characters'); | ||
}); | ||
}); | ||
}); |
@@ -0,9 +1,11 @@ | ||
/// <reference lib="dom" /> | ||
type SelectedValue = Node | Attr | string | number | boolean; | ||
interface XPathSelect { | ||
(expression: string, node?: Node): Array<SelectedValue>; | ||
(expression: string, node: Node, single: true): SelectedValue; | ||
(expression: string, node: Node, single: true): SelectedValue | undefined; | ||
} | ||
export var select: XPathSelect; | ||
export function select1(expression: string, node?: Node): SelectedValue; | ||
export function evaluate(expression: string, contextNode: Node, resolver: XPathNSResolver, type: number, result: XPathResult): XPathResult; | ||
export function select1(expression: string, node?: Node): SelectedValue | undefined; | ||
export function evaluate(expression: string, contextNode: Node, resolver: XPathNSResolver | null, type: number, result: XPathResult | null): XPathResult; | ||
export function useNamespaces(namespaceMap: { [name: string]: string }): XPathSelect; |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2598880
1
18
9245
1
1