michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: var EXPORTED_SYMBOLS = ["ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath", michael@0: "Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib", michael@0: ]; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils); michael@0: var strings = {}; Cu.import('resource://mozmill/stdlib/strings.js', strings); michael@0: var arrays = {}; Cu.import('resource://mozmill/stdlib/arrays.js', arrays); michael@0: var json2 = {}; Cu.import('resource://mozmill/stdlib/json2.js', json2); michael@0: var withs = {}; Cu.import('resource://mozmill/stdlib/withs.js', withs); michael@0: var dom = {}; Cu.import('resource://mozmill/stdlib/dom.js', dom); michael@0: var objects = {}; Cu.import('resource://mozmill/stdlib/objects.js', objects); michael@0: michael@0: var countQuotes = function (str) { michael@0: var count = 0; michael@0: var i = 0; michael@0: michael@0: while (i < str.length) { michael@0: i = str.indexOf('"', i); michael@0: if (i != -1) { michael@0: count++; michael@0: i++; michael@0: } else { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return count; michael@0: }; michael@0: michael@0: /** michael@0: * smartSplit() michael@0: * michael@0: * Takes a lookup string as input and returns michael@0: * a list of each node in the string michael@0: */ michael@0: var smartSplit = function (str) { michael@0: // Ensure we have an even number of quotes michael@0: if (countQuotes(str) % 2 != 0) { michael@0: throw new Error ("Invalid Lookup Expression"); michael@0: } michael@0: michael@0: /** michael@0: * This regex matches a single "node" in a lookup string. michael@0: * In otherwords, it matches the part between the two '/'s michael@0: * michael@0: * Regex Explanation: michael@0: * \/ - start matching at the first forward slash michael@0: * ([^\/"]*"[^"]*")* - match as many pairs of quotes as possible until we hit a slash (ignore slashes inside quotes) michael@0: * [^\/]* - match the remainder of text outside of last quote but before next slash michael@0: */ michael@0: var re = /\/([^\/"]*"[^"]*")*[^\/]*/g michael@0: var ret = [] michael@0: var match = re.exec(str); michael@0: michael@0: while (match != null) { michael@0: ret.push(match[0].replace(/^\//, "")); michael@0: match = re.exec(str); michael@0: } michael@0: michael@0: return ret; michael@0: }; michael@0: michael@0: /** michael@0: * defaultDocuments() michael@0: * michael@0: * Returns a list of default documents in which to search for elements michael@0: * if no document is provided michael@0: */ michael@0: function defaultDocuments() { michael@0: var win = Services.wm.getMostRecentWindow("navigator:browser"); michael@0: michael@0: return [ michael@0: win.document, michael@0: utils.getBrowserObject(win).selectedBrowser.contentWindow.document michael@0: ]; michael@0: }; michael@0: michael@0: /** michael@0: * nodeSearch() michael@0: * michael@0: * Takes an optional document, callback and locator string michael@0: * Returns a handle to the located element or null michael@0: */ michael@0: function nodeSearch(doc, func, string) { michael@0: if (doc != undefined) { michael@0: var documents = [doc]; michael@0: } else { michael@0: var documents = defaultDocuments(); michael@0: } michael@0: michael@0: var e = null; michael@0: var element = null; michael@0: michael@0: //inline function to recursively find the element in the DOM, cross frame. michael@0: var search = function (win, func, string) { michael@0: if (win == null) { michael@0: return; michael@0: } michael@0: michael@0: //do the lookup in the current window michael@0: element = func.call(win, string); michael@0: michael@0: if (!element || (element.length == 0)) { michael@0: var frames = win.frames; michael@0: for (var i = 0; i < frames.length; i++) { michael@0: search(frames[i], func, string); michael@0: } michael@0: } else { michael@0: e = element; michael@0: } michael@0: }; michael@0: michael@0: for (var i = 0; i < documents.length; ++i) { michael@0: var win = documents[i].defaultView; michael@0: search(win, func, string); michael@0: if (e) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return e; michael@0: }; michael@0: michael@0: /** michael@0: * Selector() michael@0: * michael@0: * Finds an element by selector string michael@0: */ michael@0: function Selector(_document, selector, index) { michael@0: if (selector == undefined) { michael@0: throw new Error('Selector constructor did not recieve enough arguments.'); michael@0: } michael@0: michael@0: this.selector = selector; michael@0: michael@0: this.getNodeForDocument = function (s) { michael@0: return this.document.querySelectorAll(s); michael@0: }; michael@0: michael@0: var nodes = nodeSearch(_document, this.getNodeForDocument, this.selector); michael@0: michael@0: return nodes ? nodes[index || 0] : null; michael@0: }; michael@0: michael@0: /** michael@0: * ID() michael@0: * michael@0: * Finds an element by ID michael@0: */ michael@0: function ID(_document, nodeID) { michael@0: if (nodeID == undefined) { michael@0: throw new Error('ID constructor did not recieve enough arguments.'); michael@0: } michael@0: michael@0: this.getNodeForDocument = function (nodeID) { michael@0: return this.document.getElementById(nodeID); michael@0: }; michael@0: michael@0: return nodeSearch(_document, this.getNodeForDocument, nodeID); michael@0: }; michael@0: michael@0: /** michael@0: * Link() michael@0: * michael@0: * Finds a link by innerHTML michael@0: */ michael@0: function Link(_document, linkName) { michael@0: if (linkName == undefined) { michael@0: throw new Error('Link constructor did not recieve enough arguments.'); michael@0: } michael@0: michael@0: this.getNodeForDocument = function (linkName) { michael@0: var getText = function (el) { michael@0: var text = ""; michael@0: michael@0: if (el.nodeType == 3) { //textNode michael@0: if (el.data != undefined) { michael@0: text = el.data; michael@0: } else { michael@0: text = el.innerHTML; michael@0: } michael@0: michael@0: text = text.replace(/n|r|t/g, " "); michael@0: } michael@0: else if (el.nodeType == 1) { //elementNode michael@0: for (var i = 0; i < el.childNodes.length; i++) { michael@0: var child = el.childNodes.item(i); michael@0: text += getText(child); michael@0: } michael@0: michael@0: if (el.tagName == "P" || el.tagName == "BR" || michael@0: el.tagName == "HR" || el.tagName == "DIV") { michael@0: text += "\n"; michael@0: } michael@0: } michael@0: michael@0: return text; michael@0: }; michael@0: michael@0: //sometimes the windows won't have this function michael@0: try { michael@0: var links = this.document.getElementsByTagName('a'); michael@0: } catch (e) { michael@0: // ADD LOG LINE mresults.write('Error: '+ e, 'lightred'); michael@0: } michael@0: michael@0: for (var i = 0; i < links.length; i++) { michael@0: var el = links[i]; michael@0: //if (getText(el).indexOf(this.linkName) != -1) { michael@0: if (el.innerHTML.indexOf(linkName) != -1) { michael@0: return el; michael@0: } michael@0: } michael@0: michael@0: return null; michael@0: }; michael@0: michael@0: return nodeSearch(_document, this.getNodeForDocument, linkName); michael@0: }; michael@0: michael@0: /** michael@0: * XPath() michael@0: * michael@0: * Finds an element by XPath michael@0: */ michael@0: function XPath(_document, expr) { michael@0: if (expr == undefined) { michael@0: throw new Error('XPath constructor did not recieve enough arguments.'); michael@0: } michael@0: michael@0: this.getNodeForDocument = function (s) { michael@0: var aNode = this.document; michael@0: var aExpr = s; michael@0: var xpe = null; michael@0: michael@0: if (this.document.defaultView == null) { michael@0: xpe = new getMethodInWindows('XPathEvaluator')(); michael@0: } else { michael@0: xpe = new this.document.defaultView.XPathEvaluator(); michael@0: } michael@0: michael@0: var nsResolver = xpe.createNSResolver(aNode.ownerDocument == null ? aNode.documentElement michael@0: : aNode.ownerDocument.documentElement); michael@0: var result = xpe.evaluate(aExpr, aNode, nsResolver, 0, null); michael@0: var found = []; michael@0: var res; michael@0: michael@0: while (res = result.iterateNext()) { michael@0: found.push(res); michael@0: } michael@0: michael@0: return found[0]; michael@0: }; michael@0: michael@0: return nodeSearch(_document, this.getNodeForDocument, expr); michael@0: }; michael@0: michael@0: /** michael@0: * Name() michael@0: * michael@0: * Finds an element by Name michael@0: */ michael@0: function Name(_document, nName) { michael@0: if (nName == undefined) { michael@0: throw new Error('Name constructor did not recieve enough arguments.'); michael@0: } michael@0: michael@0: this.getNodeForDocument = function (s) { michael@0: try{ michael@0: var els = this.document.getElementsByName(s); michael@0: if (els.length > 0) { michael@0: return els[0]; michael@0: } michael@0: } catch (e) { michael@0: } michael@0: michael@0: return null; michael@0: }; michael@0: michael@0: return nodeSearch(_document, this.getNodeForDocument, nName); michael@0: }; michael@0: michael@0: michael@0: var _returnResult = function (results) { michael@0: if (results.length == 0) { michael@0: return null michael@0: } michael@0: else if (results.length == 1) { michael@0: return results[0]; michael@0: } else { michael@0: return results; michael@0: } michael@0: } michael@0: michael@0: var _forChildren = function (element, name, value) { michael@0: var results = []; michael@0: var nodes = [e for each (e in element.childNodes) if (e)] michael@0: michael@0: for (var i in nodes) { michael@0: var n = nodes[i]; michael@0: if (n[name] == value) { michael@0: results.push(n); michael@0: } michael@0: } michael@0: michael@0: return results; michael@0: } michael@0: michael@0: var _forAnonChildren = function (_document, element, name, value) { michael@0: var results = []; michael@0: var nodes = [e for each (e in _document.getAnoymousNodes(element)) if (e)]; michael@0: michael@0: for (var i in nodes ) { michael@0: var n = nodes[i]; michael@0: if (n[name] == value) { michael@0: results.push(n); michael@0: } michael@0: } michael@0: michael@0: return results; michael@0: } michael@0: michael@0: var _byID = function (_document, parent, value) { michael@0: return _returnResult(_forChildren(parent, 'id', value)); michael@0: } michael@0: michael@0: var _byName = function (_document, parent, value) { michael@0: return _returnResult(_forChildren(parent, 'tagName', value)); michael@0: } michael@0: michael@0: var _byAttrib = function (parent, attributes) { michael@0: var results = []; michael@0: var nodes = parent.childNodes; michael@0: michael@0: for (var i in nodes) { michael@0: var n = nodes[i]; michael@0: requirementPass = 0; michael@0: requirementLength = 0; michael@0: michael@0: for (var a in attributes) { michael@0: requirementLength++; michael@0: try { michael@0: if (n.getAttribute(a) == attributes[a]) { michael@0: requirementPass++; michael@0: } michael@0: } catch (e) { michael@0: // Workaround any bugs in custom attribute crap in XUL elements michael@0: } michael@0: } michael@0: michael@0: if (requirementPass == requirementLength) { michael@0: results.push(n); michael@0: } michael@0: } michael@0: michael@0: return _returnResult(results) michael@0: } michael@0: michael@0: var _byAnonAttrib = function (_document, parent, attributes) { michael@0: var results = []; michael@0: michael@0: if (objects.getLength(attributes) == 1) { michael@0: for (var i in attributes) { michael@0: var k = i; michael@0: var v = attributes[i]; michael@0: } michael@0: michael@0: var result = _document.getAnonymousElementByAttribute(parent, k, v); michael@0: if (result) { michael@0: return result; michael@0: } michael@0: } michael@0: michael@0: var nodes = [n for each (n in _document.getAnonymousNodes(parent)) if (n.getAttribute)]; michael@0: michael@0: function resultsForNodes (nodes) { michael@0: for (var i in nodes) { michael@0: var n = nodes[i]; michael@0: requirementPass = 0; michael@0: requirementLength = 0; michael@0: michael@0: for (var a in attributes) { michael@0: requirementLength++; michael@0: if (n.getAttribute(a) == attributes[a]) { michael@0: requirementPass++; michael@0: } michael@0: } michael@0: michael@0: if (requirementPass == requirementLength) { michael@0: results.push(n); michael@0: } michael@0: } michael@0: } michael@0: michael@0: resultsForNodes(nodes); michael@0: if (results.length == 0) { michael@0: resultsForNodes([n for each (n in parent.childNodes) if (n != undefined && n.getAttribute)]) michael@0: } michael@0: michael@0: return _returnResult(results) michael@0: } michael@0: michael@0: var _byIndex = function (_document, parent, i) { michael@0: if (parent instanceof Array) { michael@0: return parent[i]; michael@0: } michael@0: michael@0: return parent.childNodes[i]; michael@0: } michael@0: michael@0: var _anonByName = function (_document, parent, value) { michael@0: return _returnResult(_forAnonChildren(_document, parent, 'tagName', value)); michael@0: } michael@0: michael@0: var _anonByAttrib = function (_document, parent, value) { michael@0: return _byAnonAttrib(_document, parent, value); michael@0: } michael@0: michael@0: var _anonByIndex = function (_document, parent, i) { michael@0: return _document.getAnonymousNodes(parent)[i]; michael@0: } michael@0: michael@0: /** michael@0: * Lookup() michael@0: * michael@0: * Finds an element by Lookup expression michael@0: */ michael@0: function Lookup(_document, expression) { michael@0: if (expression == undefined) { michael@0: throw new Error('Lookup constructor did not recieve enough arguments.'); michael@0: } michael@0: michael@0: var expSplit = [e for each (e in smartSplit(expression) ) if (e != '')]; michael@0: expSplit.unshift(_document); michael@0: michael@0: var nCases = {'id':_byID, 'name':_byName, 'attrib':_byAttrib, 'index':_byIndex}; michael@0: var aCases = {'name':_anonByName, 'attrib':_anonByAttrib, 'index':_anonByIndex}; michael@0: michael@0: /** michael@0: * Reduces the lookup expression michael@0: * @param {Object} parentNode michael@0: * Parent node (previousValue of the formerly executed reduce callback) michael@0: * @param {String} exp michael@0: * Lookup expression for the parents child node michael@0: * michael@0: * @returns {Object} Node found by the given expression michael@0: */ michael@0: var reduceLookup = function (parentNode, exp) { michael@0: // Abort in case the parent node was not found michael@0: if (!parentNode) { michael@0: return false; michael@0: } michael@0: michael@0: // Handle case where only index is provided michael@0: var cases = nCases; michael@0: michael@0: // Handle ending index before any of the expression gets mangled michael@0: if (withs.endsWith(exp, ']')) { michael@0: var expIndex = json2.JSON.parse(strings.vslice(exp, '[', ']')); michael@0: } michael@0: michael@0: // Handle anon michael@0: if (withs.startsWith(exp, 'anon')) { michael@0: exp = strings.vslice(exp, '(', ')'); michael@0: cases = aCases; michael@0: } michael@0: michael@0: if (withs.startsWith(exp, '[')) { michael@0: try { michael@0: var obj = json2.JSON.parse(strings.vslice(exp, '[', ']')); michael@0: } catch (e) { michael@0: throw new SyntaxError(e + '. String to be parsed was || ' + michael@0: strings.vslice(exp, '[', ']') + ' ||'); michael@0: } michael@0: michael@0: var r = cases['index'](_document, parentNode, obj); michael@0: if (r == null) { michael@0: throw new SyntaxError('Expression "' + exp + michael@0: '" returned null. Anonymous == ' + (cases == aCases)); michael@0: } michael@0: michael@0: return r; michael@0: } michael@0: michael@0: for (var c in cases) { michael@0: if (withs.startsWith(exp, c)) { michael@0: try { michael@0: var obj = json2.JSON.parse(strings.vslice(exp, '(', ')')) michael@0: } catch (e) { michael@0: throw new SyntaxError(e + '. String to be parsed was || ' + michael@0: strings.vslice(exp, '(', ')') + ' ||'); michael@0: } michael@0: var result = cases[c](_document, parentNode, obj); michael@0: } michael@0: } michael@0: michael@0: if (!result) { michael@0: if (withs.startsWith(exp, '{')) { michael@0: try { michael@0: var obj = json2.JSON.parse(exp); michael@0: } catch (e) { michael@0: throw new SyntaxError(e + '. String to be parsed was || ' + exp + ' ||'); michael@0: } michael@0: michael@0: if (cases == aCases) { michael@0: var result = _anonByAttrib(_document, parentNode, obj); michael@0: } else { michael@0: var result = _byAttrib(parentNode, obj); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Final return michael@0: if (expIndex) { michael@0: // TODO: Check length and raise error michael@0: return result[expIndex]; michael@0: } else { michael@0: // TODO: Check length and raise error michael@0: return result; michael@0: } michael@0: michael@0: // Maybe we should cause an exception here michael@0: return false; michael@0: }; michael@0: michael@0: return expSplit.reduce(reduceLookup); michael@0: };