1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-12-01 05:49:55 +00:00
TiddlyWiki5/editions/tw2/source/tiddlywiki/tests/js/xpath.js
2012-11-16 21:20:27 +00:00

627 lines
16 KiB
JavaScript
Executable File

/*
javascript-xpath, an implementation of DOM Level 3 XPath for Internet Explorer 5+
Copyright (C) 2004 Dimitri Glazkov Modified 2006 Mehdi Hassan
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
*/
// Mozilla, Firefox, Safari etc have support by default, we don't have an implementation for the rest
if (!(XPathResult.NUMBER_TYPE + XPathResult.NUMBER_TYPE))
{
window._content = window;
var _tb_windowEvents = new Array();
window.addEventListener = function(event, handler, flag) {
_tb_windowEvents[event] = handler;
};
function _tb_postProcess() {
if (_tb_windowEvents["load"]) {
_tb_windowEvents["load"]();
}
}
var extern;
if (window.dialogArguments && window.dialogArguments.external) {
extern = window.dialogArguments.external;
} else if (window.external) {
extern = window.external;
}
// XPathException
// An Error object will be thrown, this is just a handler to instantiate that object
var XPathException = new _XPathExceptionHandler();
function _XPathExceptionHandler()
{
this.INVALID_EXPRESSION_ERR = 51;
this.TYPE_ERR = 52;
this.NOT_IMPLEMENTED_ERR = -1;
this.RUNTIME_ERR = -2;
this.ThrowNotImplemented = function(message)
{
ThrowError(this.NOT_IMPLEMENTED_ERR, "This functionality is not implemented.", message);
};
this.ThrowInvalidExpression = function(message)
{
ThrowError(this.INVALID_EXPRESSION_ERR, "Invalid expression", message);
};
this.ThrowType = function(message)
{
ThrowError(this.TYPE_ERR, "Type error", message);
};
this.Throw = function(message)
{
ThrowError(this.RUNTIME_ERR, "Run-time error", message);
};
function ThrowError(code, description, message)
{
var error = new Error(code, "DOM-L3-XPath : " + description + (message ? ", \"" + message + "\"": ""));
error.code = code;
error.name = "XPathException";
throw error;
}
}
// DOMException
// An Error object will be thrown, this is just a handler to instantiate that object
var DOMException = new _DOMExceptionHandler();
function _DOMExceptionHandler()
{
this.ThrowInvalidState = function(message)
{
ThrowError(13, "The state of the object is no longer valid", message);
};
function ThrowError(code, description, message)
{
var error = new Error(code, "DOM : " + description + (message ? ", \"" + message + "\"": ""));
error.code = code;
error.name = "DOMException";
throw error;
}
}
// XPathEvaluator
// implemented as document object methods
// XPathExpression createExpression(String expression, XPathNSResolver resolver)
document.createExpression = function
(
expression, // String
resolver // XPathNSResolver
)
{
// returns XPathExpression object
return new XPathExpression(expression, resolver);
};
// XPathNSResolver createNSResolver(nodeResolver)
document.createNSResolver = function
(
nodeResolver // Node
)
{
// returns XPathNSResolver
return new XPathNSResolver(nodeResolver);
};
// XPathResult evaluate(String expresison, Node contextNode, XPathNSResolver resolver, Number type, XPathResult result)
document.evaluate = function
(
expression, // String
contextNode, // Node
resolver, // XPathNSResolver
type, // Number
result // XPathResult
)
// can raise XPathException, DOMException
{
// return XPathResult
return document.createExpression(expression, resolver).evaluate(contextNode, type, result);
};
// XPathExpression
function XPathExpression
(
expression, // String
resolver // XPathNSResolver
)
{
this.expressionString = expression;
this.resolver = resolver;
// XPathResult evaluate(Node contextNode, Number type, XPathResult result)
this.evaluate = function
(
contextNode, // Node
type, // Number
result // XPathResult
)
// raises XPathException, DOMException
{
// return XPathResult
return (result && result.constructor == XPathResult ? result.initialize(this, contextNode, resolver, type) : new XPathResult(this, contextNode, resolver, type));
};
this.toString = function()
{
return "[XPathExpression]";
};
}
// XPathNSResolver
function XPathNSResolver(node)
{
this.node = node;
// String lookupNamespaceURI(String prefix)
this.lookupNamespaceURI = function
(
prefix // String
)
{
XPathException.ThrowNotImplemented();
// return String
return null;
};
this.toString = function()
{
return "[XPathNSResolver]";
};
}
// XPathResult
XPathResult.ANY_TYPE = 0;
XPathResult.NUMBER_TYPE = 1;
XPathResult.STRING_TYPE = 2;
XPathResult.BOOLEAN_TYPE = 3;
XPathResult.UNORDERED_NODE_ITERATOR_TYPE = 4;
XPathResult.ORDERED_NODE_ITERATOR_TYPE = 5;
XPathResult.UNORDERED_SNAPSHOT_TYPE = 6;
XPathResult.ORDERED_SNAPSHOT_TYPE = 7;
XPathResult.ANY_UNORDERED_NODE_TYPE = 8;
XPathResult.FIRST_ORDERED_NODE_TYPE = 9;
function XPathResult
(
expression, // XPathExpression
contextNode, // Node
resolver, // XPathNSResolver
type // Number
)
{
this.initialize = function(expression, contextNode, resolver, type)
{
this._domResult = null;
this._expression = expression;
this._contextNode = contextNode;
this._resolver = resolver;
if (type)
{
this.resultType = type;
this._isIterator = (type == XPathResult.UNORDERED_NODE_ITERATOR_TYPE ||
type == XPathResult.ORDERED_NODE_ITERATOR_TYPE ||
type == XPathResult.ANY_TYPE);
this._isSnapshot = (type == XPathResult.UNORDERED_SNAPSHOT_TYPE || type == XPathResult.ORDERED_SNAPSHOT_TYPE);
this._isNodeSet = type > XPathResult.BOOLEAN_TYPE;
}
else
{
this.resultType = XPathResult.ANY_TYPE;
this._isIterator = true;
this._isSnapshot = false;
this._isNodeSet = true;
}
return this;
};
this.initialize(expression, contextNode, resolver, type);
this.getInvalidIteratorState = function()
{
return documentChangeDetected() || !this._isIterator;
};
this.getSnapshotLength = function()
// raises XPathException
{
if (!this._isSnapshot)
{
XPathException.ThrowType("Snapshot is not an expected result type");
}
activateResult(this);
// return Number
return this._domResult.length;
};
// Node iterateNext()
this.iterateNext = function()
// raises XPathException, DOMException
{
if (!this._isIterator)
{
XPathException.ThrowType("Iterator is not an expected result type");
}
activateResult(this);
if (documentChangeDetected())
{
DOMException.ThrowInvalidState("iterateNext");
}
// return Node
return getNextNode(this);
};
// Node snapshotItem(Number index)
this.snapshotItem = function(index)
// raises XPathException
{
if (!this._isSnapshot)
{
XPathException.ThrowType("Snapshot is not an expected result type");
}
// return Node
return getItemNode(this, index);
};
this.toString = function()
{
return "[XPathResult]";
};
// returns string value of the result, if result type is STRING_TYPE
// otherwise throws an XPathException
this.getStringValue = function()
{
if (this.resultType != XPathResult.STRING_TYPE)
{
XPathException.ThrowType("The expression can not be converted to return String");
}
return getNodeText(this);
};
// returns number value of the result, if the result is NUMBER_TYPE
// otherwise throws an XPathException
this.getNumberValue = function()
{
if (this.resultType != XPathResult.NUMBER_TYPE)
{
XPathException.ThrowType("The expression can not be converted to return Number");
}
var number = parseInt(getNodeText(this));
if (isNaN(number))
{
XPathException.ThrowType("The result can not be converted to Number");
}
return number;
};
// returns boolean value of the result, if the result is BOOLEAN_TYPE
// otherwise throws an XPathException
this.getBooleanValue = function()
{
if (this.resultType != XPathResult.BOOLEAN_TYPE)
{
XPathException.ThrowType("The expression can not be converted to return Boolean");
}
var
text = getNodeText(this);
bool = (text ? text.toLowerCase() : null);
if (bool == "false" || bool == "true")
{
return bool;
}
XPathException.ThrowType("The result can not be converted to Boolean");
};
// returns single node, if the result is ANY_UNORDERED_NODE_TYPE or FIRST_ORDERED_NODE_TYPE
// otherwise throws an XPathException
this.getSingleNodeValue = function()
{
if (this.resultType != XPathResult.ANY_UNORDERED_NODE_TYPE &&
this.resultType != XPathResult.FIRST_ORDERED_NODE_TYPE)
{
XPathException.ThrowType("The expression can not be converted to return single Node value");
}
return getSingleNode(this);
};
function documentChangeDetected()
{
return document._XPathMsxmlDocumentHelper.documentChangeDetected();
}
function getNodeText(result)
{
activateResult(result);
return result._textResult;
// return ((node = getSingleNode(result)) ? (node.nodeType == 1 ? node.innerText : node.nodeValue) : null);
}
function findNode(result, current)
{
switch(current.nodeType)
{
case 1: // NODE_ELEMENT
var id = current.attributes.getNamedItem("id");
if (id)
{
return document.getElementById(id.value);
}
XPathException.Throw("unable to locate element in XML tree");
case 2: // NODE_ATTRIBUTE
var id = current.selectSingleNode("..").attributes.getNamedItem("id");
if (id)
{
var node = document.getElementById(id.text);
if (node)
{
return node.attributes.getNamedItem(current.nodeName);
}
}
XPathException.Throw("unable to locate attribute in XML tree");
case 3: // NODE_TEXT
var id = current.selectSingleNode("..").attributes.getNamedItem("id");
if (id)
{
var node = document.getElementById(id.value);
if (node)
{
for(child in node.childNodes)
{
if (child.nodeType == 3 && child.nodeValue == current.nodeValue)
{
return child;
}
}
}
}
XPathException.Throw("unable to locate text in XML tree");
}
XPathException.Throw("unknown node type");
}
function activateResult(result)
{
if (!result._domResult)
{
try
{
var expression = result._expression.expressionString;
// adjust expression if contextNode is not a document
if (result._contextNode != document && expression.indexOf("//") != 0)
{
expression = "//*[@id = '" + result._contextNode.id + "']" +
(expression.indexOf("/") == 0 ? "" : "/") + expression;
}
if (result._isNodeSet)
{
result._domResult = document._XPathMsxmlDocumentHelper.getDom().selectNodes(expression);
}
else
{
result._domResult = true;
result._textResult = document._XPathMsxmlDocumentHelper.getTextResult(expression);
}
}
catch(error)
{
alert(error.description);
XPathException.ThrowInvalidExpression(error.description);
}
}
}
function getSingleNode(result)
{
var node = getItemNode(result, 0);
result._domResult = null;
return node;
}
function getItemNode(result, index)
{
activateResult(result);
var current = result._domResult.item(index);
return (current ? findNode(result, current) : null);
}
function getNextNode(result)
{
var current = result._domResult.nextNode;
if (current)
{
return findNode(result, current);
}
result._domResult = null;
return null;
}
}
document.reloadDom = function()
{
document._XPathMsxmlDocumentHelper.reset();
};
document._XPathMsxmlDocumentHelper = new _XPathMsxmlDocumentHelper();
function _XPathMsxmlDocumentHelper()
{
this.getDom = function()
{
activateDom(this);
return this.dom;
};
this.getXml = function()
{
activateDom(this);
return this.dom.xml;
};
this.getTextResult = function(expression)
{
expression = expression.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "\"");
var xslText = "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">" +
"<xsl:output method=\"text\"/><xsl:template match=\"*\"><xsl:value-of select=\"" + expression + "\"/>" +
"</xsl:template></xsl:stylesheet>";
var xsl = new ActiveXObject("Msxml2.DOMDocument");
xsl.loadXML(xslText);
try
{
var result = this.getDom().transformNode(xsl);
}
catch(error)
{
alert("Error: " + error.description);
}
return result;
};
this.reset = function()
{
this.dom = null;
};
function onPropertyChangeEventHandler()
{
document._propertyChangeDetected = true;
};
this.documentChangeDetected = function()
{
return (document.ignoreDocumentChanges ? false : this._currentElementCount != document.all.length || document._propertyChangeDetected);
};
function activateDom(helper)
{
if (!helper.dom)
{
var dom = new ActiveXObject("Msxml2.DOMDocument");
dom.async = false;
dom.resolveExternals = false;
loadDocument(dom, helper);
helper.dom = dom;
helper._currentElementCount = document.all.length;
document._propertyChangeDetected = false;
}
else
{
if (helper.documentChangeDetected())
{
var dom = helper.dom;
dom.load("");
loadDocument(dom, helper);
helper._currentElementCount = document.all.length;
document._propertyChangeDetected = false;
}
}
}
function loadDocument(dom, helper)
{
return loadNode(dom, dom, document.body, helper);
}
function loadNode(dom, domParentNode, node, helper)
{
if (node.nodeType == 3)
{
domParentNode.appendChild(dom.createTextNode(node.nodeValue));
}
else
{
var domNode = dom.createElement(node.nodeName.toLowerCase());
if (!node.id)
{
node.id = node.uniqueID;
}
domParentNode.appendChild(domNode);
loadAttributes(dom, domNode, node);
var length = node.childNodes.length;
for(var i = 0; i < length; i ++ )
{
loadNode(dom, domNode, node.childNodes[i], helper);
}
node.attachEvent("onpropertychange", onPropertyChangeEventHandler);
}
}
function loadAttributes(dom, domParentNode, node)
{
for (var i = 0; i < node.attributes.length; i ++ )
{
var attribute = node.attributes[i];
var attributeValue = attribute.nodeValue;
if (attributeValue && attribute.specified)
{
var domAttribute = dom.createAttribute(attribute.nodeName);
domAttribute.value = attributeValue;
domParentNode.setAttributeNode(domAttribute);
}
}
}
}
}
else
{
document.reloadDom = function() {};
XPathResult.prototype.getStringValue = function()
{
return this.stringValue;
};
XPathResult.prototype.getNumberValue = function()
{
return this.numberValue;
};
XPathResult.prototype.getBooleanValue = function()
{
return this.booleanValue;
};
XPathResult.prototype.getSingleNodeValue = function()
{
return this.singleNodeValue;
};
XPathResult.prototype.getInvalidIteratorState = function()
{
return this.invalidIteratorState;
};
XPathResult.prototype.getSnapshotLength = function()
{
return this.snapshotLength;
};
XPathResult.prototype.getResultType = function()
{
return this.resultType;
};
}