Wed, 31 Dec 2014 07:53:36 +0100
Correct small whitespace inconsistency, lost while renaming variables.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
5 var EXPORTED_SYMBOLS = ["ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath",
6 "Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib",
7 ];
9 const Cc = Components.classes;
10 const Ci = Components.interfaces;
11 const Cu = Components.utils;
13 Cu.import("resource://gre/modules/Services.jsm");
15 var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
16 var strings = {}; Cu.import('resource://mozmill/stdlib/strings.js', strings);
17 var arrays = {}; Cu.import('resource://mozmill/stdlib/arrays.js', arrays);
18 var json2 = {}; Cu.import('resource://mozmill/stdlib/json2.js', json2);
19 var withs = {}; Cu.import('resource://mozmill/stdlib/withs.js', withs);
20 var dom = {}; Cu.import('resource://mozmill/stdlib/dom.js', dom);
21 var objects = {}; Cu.import('resource://mozmill/stdlib/objects.js', objects);
23 var countQuotes = function (str) {
24 var count = 0;
25 var i = 0;
27 while (i < str.length) {
28 i = str.indexOf('"', i);
29 if (i != -1) {
30 count++;
31 i++;
32 } else {
33 break;
34 }
35 }
37 return count;
38 };
40 /**
41 * smartSplit()
42 *
43 * Takes a lookup string as input and returns
44 * a list of each node in the string
45 */
46 var smartSplit = function (str) {
47 // Ensure we have an even number of quotes
48 if (countQuotes(str) % 2 != 0) {
49 throw new Error ("Invalid Lookup Expression");
50 }
52 /**
53 * This regex matches a single "node" in a lookup string.
54 * In otherwords, it matches the part between the two '/'s
55 *
56 * Regex Explanation:
57 * \/ - start matching at the first forward slash
58 * ([^\/"]*"[^"]*")* - match as many pairs of quotes as possible until we hit a slash (ignore slashes inside quotes)
59 * [^\/]* - match the remainder of text outside of last quote but before next slash
60 */
61 var re = /\/([^\/"]*"[^"]*")*[^\/]*/g
62 var ret = []
63 var match = re.exec(str);
65 while (match != null) {
66 ret.push(match[0].replace(/^\//, ""));
67 match = re.exec(str);
68 }
70 return ret;
71 };
73 /**
74 * defaultDocuments()
75 *
76 * Returns a list of default documents in which to search for elements
77 * if no document is provided
78 */
79 function defaultDocuments() {
80 var win = Services.wm.getMostRecentWindow("navigator:browser");
82 return [
83 win.document,
84 utils.getBrowserObject(win).selectedBrowser.contentWindow.document
85 ];
86 };
88 /**
89 * nodeSearch()
90 *
91 * Takes an optional document, callback and locator string
92 * Returns a handle to the located element or null
93 */
94 function nodeSearch(doc, func, string) {
95 if (doc != undefined) {
96 var documents = [doc];
97 } else {
98 var documents = defaultDocuments();
99 }
101 var e = null;
102 var element = null;
104 //inline function to recursively find the element in the DOM, cross frame.
105 var search = function (win, func, string) {
106 if (win == null) {
107 return;
108 }
110 //do the lookup in the current window
111 element = func.call(win, string);
113 if (!element || (element.length == 0)) {
114 var frames = win.frames;
115 for (var i = 0; i < frames.length; i++) {
116 search(frames[i], func, string);
117 }
118 } else {
119 e = element;
120 }
121 };
123 for (var i = 0; i < documents.length; ++i) {
124 var win = documents[i].defaultView;
125 search(win, func, string);
126 if (e) {
127 break;
128 }
129 }
131 return e;
132 };
134 /**
135 * Selector()
136 *
137 * Finds an element by selector string
138 */
139 function Selector(_document, selector, index) {
140 if (selector == undefined) {
141 throw new Error('Selector constructor did not recieve enough arguments.');
142 }
144 this.selector = selector;
146 this.getNodeForDocument = function (s) {
147 return this.document.querySelectorAll(s);
148 };
150 var nodes = nodeSearch(_document, this.getNodeForDocument, this.selector);
152 return nodes ? nodes[index || 0] : null;
153 };
155 /**
156 * ID()
157 *
158 * Finds an element by ID
159 */
160 function ID(_document, nodeID) {
161 if (nodeID == undefined) {
162 throw new Error('ID constructor did not recieve enough arguments.');
163 }
165 this.getNodeForDocument = function (nodeID) {
166 return this.document.getElementById(nodeID);
167 };
169 return nodeSearch(_document, this.getNodeForDocument, nodeID);
170 };
172 /**
173 * Link()
174 *
175 * Finds a link by innerHTML
176 */
177 function Link(_document, linkName) {
178 if (linkName == undefined) {
179 throw new Error('Link constructor did not recieve enough arguments.');
180 }
182 this.getNodeForDocument = function (linkName) {
183 var getText = function (el) {
184 var text = "";
186 if (el.nodeType == 3) { //textNode
187 if (el.data != undefined) {
188 text = el.data;
189 } else {
190 text = el.innerHTML;
191 }
193 text = text.replace(/n|r|t/g, " ");
194 }
195 else if (el.nodeType == 1) { //elementNode
196 for (var i = 0; i < el.childNodes.length; i++) {
197 var child = el.childNodes.item(i);
198 text += getText(child);
199 }
201 if (el.tagName == "P" || el.tagName == "BR" ||
202 el.tagName == "HR" || el.tagName == "DIV") {
203 text += "\n";
204 }
205 }
207 return text;
208 };
210 //sometimes the windows won't have this function
211 try {
212 var links = this.document.getElementsByTagName('a');
213 } catch (e) {
214 // ADD LOG LINE mresults.write('Error: '+ e, 'lightred');
215 }
217 for (var i = 0; i < links.length; i++) {
218 var el = links[i];
219 //if (getText(el).indexOf(this.linkName) != -1) {
220 if (el.innerHTML.indexOf(linkName) != -1) {
221 return el;
222 }
223 }
225 return null;
226 };
228 return nodeSearch(_document, this.getNodeForDocument, linkName);
229 };
231 /**
232 * XPath()
233 *
234 * Finds an element by XPath
235 */
236 function XPath(_document, expr) {
237 if (expr == undefined) {
238 throw new Error('XPath constructor did not recieve enough arguments.');
239 }
241 this.getNodeForDocument = function (s) {
242 var aNode = this.document;
243 var aExpr = s;
244 var xpe = null;
246 if (this.document.defaultView == null) {
247 xpe = new getMethodInWindows('XPathEvaluator')();
248 } else {
249 xpe = new this.document.defaultView.XPathEvaluator();
250 }
252 var nsResolver = xpe.createNSResolver(aNode.ownerDocument == null ? aNode.documentElement
253 : aNode.ownerDocument.documentElement);
254 var result = xpe.evaluate(aExpr, aNode, nsResolver, 0, null);
255 var found = [];
256 var res;
258 while (res = result.iterateNext()) {
259 found.push(res);
260 }
262 return found[0];
263 };
265 return nodeSearch(_document, this.getNodeForDocument, expr);
266 };
268 /**
269 * Name()
270 *
271 * Finds an element by Name
272 */
273 function Name(_document, nName) {
274 if (nName == undefined) {
275 throw new Error('Name constructor did not recieve enough arguments.');
276 }
278 this.getNodeForDocument = function (s) {
279 try{
280 var els = this.document.getElementsByName(s);
281 if (els.length > 0) {
282 return els[0];
283 }
284 } catch (e) {
285 }
287 return null;
288 };
290 return nodeSearch(_document, this.getNodeForDocument, nName);
291 };
294 var _returnResult = function (results) {
295 if (results.length == 0) {
296 return null
297 }
298 else if (results.length == 1) {
299 return results[0];
300 } else {
301 return results;
302 }
303 }
305 var _forChildren = function (element, name, value) {
306 var results = [];
307 var nodes = [e for each (e in element.childNodes) if (e)]
309 for (var i in nodes) {
310 var n = nodes[i];
311 if (n[name] == value) {
312 results.push(n);
313 }
314 }
316 return results;
317 }
319 var _forAnonChildren = function (_document, element, name, value) {
320 var results = [];
321 var nodes = [e for each (e in _document.getAnoymousNodes(element)) if (e)];
323 for (var i in nodes ) {
324 var n = nodes[i];
325 if (n[name] == value) {
326 results.push(n);
327 }
328 }
330 return results;
331 }
333 var _byID = function (_document, parent, value) {
334 return _returnResult(_forChildren(parent, 'id', value));
335 }
337 var _byName = function (_document, parent, value) {
338 return _returnResult(_forChildren(parent, 'tagName', value));
339 }
341 var _byAttrib = function (parent, attributes) {
342 var results = [];
343 var nodes = parent.childNodes;
345 for (var i in nodes) {
346 var n = nodes[i];
347 requirementPass = 0;
348 requirementLength = 0;
350 for (var a in attributes) {
351 requirementLength++;
352 try {
353 if (n.getAttribute(a) == attributes[a]) {
354 requirementPass++;
355 }
356 } catch (e) {
357 // Workaround any bugs in custom attribute crap in XUL elements
358 }
359 }
361 if (requirementPass == requirementLength) {
362 results.push(n);
363 }
364 }
366 return _returnResult(results)
367 }
369 var _byAnonAttrib = function (_document, parent, attributes) {
370 var results = [];
372 if (objects.getLength(attributes) == 1) {
373 for (var i in attributes) {
374 var k = i;
375 var v = attributes[i];
376 }
378 var result = _document.getAnonymousElementByAttribute(parent, k, v);
379 if (result) {
380 return result;
381 }
382 }
384 var nodes = [n for each (n in _document.getAnonymousNodes(parent)) if (n.getAttribute)];
386 function resultsForNodes (nodes) {
387 for (var i in nodes) {
388 var n = nodes[i];
389 requirementPass = 0;
390 requirementLength = 0;
392 for (var a in attributes) {
393 requirementLength++;
394 if (n.getAttribute(a) == attributes[a]) {
395 requirementPass++;
396 }
397 }
399 if (requirementPass == requirementLength) {
400 results.push(n);
401 }
402 }
403 }
405 resultsForNodes(nodes);
406 if (results.length == 0) {
407 resultsForNodes([n for each (n in parent.childNodes) if (n != undefined && n.getAttribute)])
408 }
410 return _returnResult(results)
411 }
413 var _byIndex = function (_document, parent, i) {
414 if (parent instanceof Array) {
415 return parent[i];
416 }
418 return parent.childNodes[i];
419 }
421 var _anonByName = function (_document, parent, value) {
422 return _returnResult(_forAnonChildren(_document, parent, 'tagName', value));
423 }
425 var _anonByAttrib = function (_document, parent, value) {
426 return _byAnonAttrib(_document, parent, value);
427 }
429 var _anonByIndex = function (_document, parent, i) {
430 return _document.getAnonymousNodes(parent)[i];
431 }
433 /**
434 * Lookup()
435 *
436 * Finds an element by Lookup expression
437 */
438 function Lookup(_document, expression) {
439 if (expression == undefined) {
440 throw new Error('Lookup constructor did not recieve enough arguments.');
441 }
443 var expSplit = [e for each (e in smartSplit(expression) ) if (e != '')];
444 expSplit.unshift(_document);
446 var nCases = {'id':_byID, 'name':_byName, 'attrib':_byAttrib, 'index':_byIndex};
447 var aCases = {'name':_anonByName, 'attrib':_anonByAttrib, 'index':_anonByIndex};
449 /**
450 * Reduces the lookup expression
451 * @param {Object} parentNode
452 * Parent node (previousValue of the formerly executed reduce callback)
453 * @param {String} exp
454 * Lookup expression for the parents child node
455 *
456 * @returns {Object} Node found by the given expression
457 */
458 var reduceLookup = function (parentNode, exp) {
459 // Abort in case the parent node was not found
460 if (!parentNode) {
461 return false;
462 }
464 // Handle case where only index is provided
465 var cases = nCases;
467 // Handle ending index before any of the expression gets mangled
468 if (withs.endsWith(exp, ']')) {
469 var expIndex = json2.JSON.parse(strings.vslice(exp, '[', ']'));
470 }
472 // Handle anon
473 if (withs.startsWith(exp, 'anon')) {
474 exp = strings.vslice(exp, '(', ')');
475 cases = aCases;
476 }
478 if (withs.startsWith(exp, '[')) {
479 try {
480 var obj = json2.JSON.parse(strings.vslice(exp, '[', ']'));
481 } catch (e) {
482 throw new SyntaxError(e + '. String to be parsed was || ' +
483 strings.vslice(exp, '[', ']') + ' ||');
484 }
486 var r = cases['index'](_document, parentNode, obj);
487 if (r == null) {
488 throw new SyntaxError('Expression "' + exp +
489 '" returned null. Anonymous == ' + (cases == aCases));
490 }
492 return r;
493 }
495 for (var c in cases) {
496 if (withs.startsWith(exp, c)) {
497 try {
498 var obj = json2.JSON.parse(strings.vslice(exp, '(', ')'))
499 } catch (e) {
500 throw new SyntaxError(e + '. String to be parsed was || ' +
501 strings.vslice(exp, '(', ')') + ' ||');
502 }
503 var result = cases[c](_document, parentNode, obj);
504 }
505 }
507 if (!result) {
508 if (withs.startsWith(exp, '{')) {
509 try {
510 var obj = json2.JSON.parse(exp);
511 } catch (e) {
512 throw new SyntaxError(e + '. String to be parsed was || ' + exp + ' ||');
513 }
515 if (cases == aCases) {
516 var result = _anonByAttrib(_document, parentNode, obj);
517 } else {
518 var result = _byAttrib(parentNode, obj);
519 }
520 }
521 }
523 // Final return
524 if (expIndex) {
525 // TODO: Check length and raise error
526 return result[expIndex];
527 } else {
528 // TODO: Check length and raise error
529 return result;
530 }
532 // Maybe we should cause an exception here
533 return false;
534 };
536 return expSplit.reduce(reduceLookup);
537 };