Skip to content

Commit

Permalink
Add isHtml option for parsed expressions. Fixes #27
Browse files Browse the repository at this point in the history
  • Loading branch information
JLRishe committed Nov 25, 2017
1 parent 2933bb9 commit cae87df
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 18 deletions.
47 changes: 42 additions & 5 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ var xpath = require('./xpath.js')
, dom = require('xmldom').DOMParser
, assert = require('assert');

var xhtmlNs = 'http://www.w3.org/1999/xhtml';

module.exports = {
'api': function(test) {
assert.ok(xpath.evaluate, 'evaluate api ok.');
Expand Down Expand Up @@ -995,15 +997,16 @@ module.exports = {
}

,'should allow null namespaces for null prefixes': function (test) {
var doc = new dom().parseFromString('<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>', 'text/html');
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: doc });
var greetings1 = noPrefixPath.select({ node: docHtml, allowAnyNamespaceForNoPrefix: false });

assert.equal(0, greetings1.length);

var allowAnyNamespaceOptions = { node: doc, allowAnyNamespaceForNoPrefix: true };
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);
Expand All @@ -1015,10 +1018,10 @@ module.exports = {

assert.equal(2, allGreetings.length);

var nsm = { html: 'http://www.w3.org/1999/xhtml', other: 'http://www.example.com/other' };
var nsm = { html: xhtmlNs, other: 'http://www.example.com/other' };

var prefixPath = xpath.parse('/html:html/body/html:p');
var optionsWithNamespaces = { node: doc, allowAnyNamespaceForNoPrefix: true, namespaces: nsm };
var optionsWithNamespaces = { node: docHtml, allowAnyNamespaceForNoPrefix: true, namespaces: nsm };

// if the path uses prefixes, they have to match
var greetings3 = prefixPath.select(optionsWithNamespaces);
Expand All @@ -1031,4 +1034,38 @@ module.exports = {

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.equal(2, greetings3.length);

// non-html mode: require namespaces
var greetings4 = xpath.parse('/html/body/p').select({ node: docHtml, namespaces: ns });

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

assert.equal(0, greetings5.length);

test.done();
}
}
53 changes: 40 additions & 13 deletions xpath.js
Original file line number Diff line number Diff line change
Expand Up @@ -1300,22 +1300,26 @@ XPath.prototype.toString = function() {
return this.expression.toString();
};

function setIfUnset(obj, prop, value) {
if (!(prop in obj)) {
obj[prop] = value;
}
}

XPath.prototype.evaluate = function(c) {
c.contextNode = c.expressionContextNode;
c.contextSize = 1;
c.contextPosition = 1;
c.caseInsensitive = false;
if (c.contextNode != null) {
var doc = c.contextNode;
if (doc.nodeType != 9 /*Node.DOCUMENT_NODE*/) {
doc = doc.ownerDocument;
}
try {
c.caseInsensitive = doc.implementation.hasFeature("HTML", "2.0");
} catch (e) {
c.caseInsensitive = true;
}

// [2017-11-25] Removed usage of .implementation.hasFeature() since it does
// not reliably detect HTML DOMs (always returns false in xmldom and true in browsers)
if (c.isHtml) {
setIfUnset(c, 'caseInsensitive', true);
setIfUnset(c, 'allowAnyNamespaceForNoPrefix', true);
}

setIfUnset(c, 'caseInsensitive', false);

return this.expression.evaluate(c);
};

Expand Down Expand Up @@ -4295,8 +4299,26 @@ function XPathExpression(e, r, p) {
this.context.namespaceResolver = new XPathNSResolverWrapper(r);
}

XPathExpression.getOwnerDocument = function (n) {
return n.nodeType === 9 /*Node.DOCUMENT_NODE*/ ? n : n.ownerDocument;
}

XPathExpression.detectHtmlDom = function (n) {
if (!n) { return false; }

var doc = XPathExpression.getOwnerDocument(n);

try {
return doc.implementation.hasFeature("HTML", "2.0");
} catch (e) {
return true;
}
}

XPathExpression.prototype.evaluate = function(n, t, res) {
this.context.expressionContextNode = n;
this.context.caseInsensitive = XPathExpression.detectHtmlDom(n);

var result = this.xpath.evaluate(this.context);
return new XPathResult(result, t);
}
Expand Down Expand Up @@ -4607,6 +4629,10 @@ installDOM3XPathSupport(exports, new XPathParser());

return defaultVariableResolver;
}

function copyIfPresent(prop, dest, source) {
if (prop in source) { dest[prop] = source[prop]; }
}

function makeContext(options) {
var context = new XPathContext();
Expand All @@ -4615,8 +4641,9 @@ installDOM3XPathSupport(exports, new XPathParser());
context.namespaceResolver = makeNSResolver(options.namespaces);
context.functionResolver = makeFunctionResolver(options.functions);
context.variableResolver = makeVariableResolver(options.variables);
context.expressionContextNode = options.node;
context.allowAnyNamespaceForNoPrefix = options.allowAnyNamespaceForNoPrefix;
context.expressionContextNode = options.node;
copyIfPresent('allowAnyNamespaceForNoPrefix', context, options);
copyIfPresent('isHtml', context, options);
} else {
context.namespaceResolver = defaultNSResolver;
}
Expand Down

0 comments on commit cae87df

Please sign in to comment.