1
|
|
|
// Copyright 2005 Google |
2
|
|
|
// |
3
|
|
|
// Author: Steffen Meschkat <[email protected]> |
4
|
|
|
// |
5
|
|
|
// Miscellaneous utility and placeholder functions. |
6
|
|
|
|
7
|
|
|
// Dummy implmentation for the logging functions. Replace by something |
8
|
|
|
// useful when you want to debug. |
9
|
|
|
function xpathLog(msg) {}; |
|
|
|
|
10
|
|
|
function xsltLog(msg) {}; |
|
|
|
|
11
|
|
|
function xsltLogXml(msg) {}; |
|
|
|
|
12
|
|
|
|
13
|
|
|
var ajaxsltIsIE6 = navigator.appVersion.match(/MSIE 6.0/); |
14
|
|
|
|
15
|
|
|
// Throws an exception if false. |
16
|
|
|
function assert(b) { |
17
|
|
|
if (!b) { |
18
|
|
|
throw "Assertion failed"; |
19
|
|
|
} |
20
|
|
|
} |
21
|
|
|
|
22
|
|
|
// Splits a string s at all occurrences of character c. This is like |
23
|
|
|
// the split() method of the string object, but IE omits empty |
24
|
|
|
// strings, which violates the invariant (s.split(x).join(x) == s). |
25
|
|
|
function stringSplit(s, c) { |
26
|
|
|
var a = s.indexOf(c); |
27
|
|
|
if (a == -1) { |
28
|
|
|
return [ s ]; |
29
|
|
|
} |
30
|
|
|
var parts = []; |
31
|
|
|
parts.push(s.substr(0,a)); |
32
|
|
|
while (a != -1) { |
33
|
|
|
var a1 = s.indexOf(c, a + 1); |
34
|
|
|
if (a1 != -1) { |
35
|
|
|
parts.push(s.substr(a + 1, a1 - a - 1)); |
36
|
|
|
} else { |
37
|
|
|
parts.push(s.substr(a + 1)); |
38
|
|
|
} |
39
|
|
|
a = a1; |
40
|
|
|
} |
41
|
|
|
return parts; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
// The following function does what document.importNode(node, true) |
45
|
|
|
// would do for us here; however that method is broken in Safari/1.3, |
46
|
|
|
// so we have to emulate it. |
47
|
|
|
function xmlImportNode(doc, node) { |
48
|
|
|
if (node.nodeType == DOM_TEXT_NODE) { |
49
|
|
|
return domCreateTextNode(doc, node.nodeValue); |
50
|
|
|
|
51
|
|
|
} else if (node.nodeType == DOM_CDATA_SECTION_NODE) { |
52
|
|
|
return domCreateCDATASection(doc, node.nodeValue); |
53
|
|
|
|
54
|
|
|
} else if (node.nodeType == DOM_ELEMENT_NODE) { |
55
|
|
|
var newNode = domCreateElement(doc, node.nodeName); |
56
|
|
|
for (var i = 0; i < node.attributes.length; ++i) { |
57
|
|
|
var an = node.attributes[i]; |
58
|
|
|
var name = an.nodeName; |
59
|
|
|
var value = an.nodeValue; |
60
|
|
|
domSetAttribute(newNode, name, value); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
for (var c = node.firstChild; c; c = c.nextSibling) { |
64
|
|
|
var cn = arguments.callee(doc, c); |
|
|
|
|
65
|
|
|
domAppendChild(newNode, cn); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
return newNode; |
69
|
|
|
|
70
|
|
|
} else { |
71
|
|
|
return domCreateComment(doc, node.nodeName); |
72
|
|
|
} |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
// A set data structure. It can also be used as a map (i.e. the keys |
76
|
|
|
// can have values other than 1), but we don't call it map because it |
77
|
|
|
// would be ambiguous in this context. Also, the map is iterable, so |
78
|
|
|
// we can use it to replace for-in loops over core javascript Objects. |
79
|
|
|
// For-in iteration breaks when Object.prototype is modified, which |
80
|
|
|
// some clients of the maps API do. |
81
|
|
|
// |
82
|
|
|
// NOTE(mesch): The set keys by the string value of its element, NOT |
83
|
|
|
// by the typed value. In particular, objects can't be used as keys. |
84
|
|
|
// |
85
|
|
|
// @constructor |
86
|
|
|
function Set() { |
87
|
|
|
this.keys = []; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
Set.prototype.size = function() { |
|
|
|
|
91
|
|
|
return this.keys.length; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
// Adds the entry to the set, ignoring if it is present. |
95
|
|
|
Set.prototype.add = function(key, opt_value) { |
|
|
|
|
96
|
|
|
var value = opt_value || 1; |
97
|
|
|
if (!this.contains(key)) { |
98
|
|
|
this[':' + key] = value; |
99
|
|
|
this.keys.push(key); |
100
|
|
|
} |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
// Sets the entry in the set, adding if it is not yet present. |
104
|
|
|
Set.prototype.set = function(key, opt_value) { |
|
|
|
|
105
|
|
|
var value = opt_value || 1; |
106
|
|
|
if (!this.contains(key)) { |
107
|
|
|
this[':' + key] = value; |
108
|
|
|
this.keys.push(key); |
109
|
|
|
} else { |
110
|
|
|
this[':' + key] = value; |
111
|
|
|
} |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
// Increments the key's value by 1. This works around the fact that |
115
|
|
|
// numbers are always passed by value, never by reference, so that we |
116
|
|
|
// can't increment the value returned by get(), or the iterator |
117
|
|
|
// argument. Sets the key's value to 1 if it doesn't exist yet. |
118
|
|
|
Set.prototype.inc = function(key) { |
|
|
|
|
119
|
|
|
if (!this.contains(key)) { |
120
|
|
|
this[':' + key] = 1; |
121
|
|
|
this.keys.push(key); |
122
|
|
|
} else { |
123
|
|
|
this[':' + key]++; |
124
|
|
|
} |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
Set.prototype.get = function(key) { |
|
|
|
|
128
|
|
|
if (this.contains(key)) { |
129
|
|
|
return this[':' + key]; |
130
|
|
|
} else { |
131
|
|
|
var undefined; |
132
|
|
|
return undefined; |
|
|
|
|
133
|
|
|
} |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
// Removes the entry from the set. |
137
|
|
|
Set.prototype.remove = function(key) { |
|
|
|
|
138
|
|
|
if (this.contains(key)) { |
139
|
|
|
delete this[':' + key]; |
140
|
|
|
removeFromArray(this.keys, key, true); |
141
|
|
|
} |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
// Tests if an entry is in the set. |
145
|
|
|
Set.prototype.contains = function(entry) { |
|
|
|
|
146
|
|
|
return typeof this[':' + entry] != 'undefined'; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
// Gets a list of values in the set. |
150
|
|
|
Set.prototype.items = function() { |
|
|
|
|
151
|
|
|
var list = []; |
152
|
|
|
for (var i = 0; i < this.keys.length; ++i) { |
153
|
|
|
var k = this.keys[i]; |
154
|
|
|
var v = this[':' + k]; |
155
|
|
|
list.push(v); |
156
|
|
|
} |
157
|
|
|
return list; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
|
161
|
|
|
// Invokes function f for every key value pair in the set as a method |
162
|
|
|
// of the set. |
163
|
|
|
Set.prototype.map = function(f) { |
|
|
|
|
164
|
|
|
for (var i = 0; i < this.keys.length; ++i) { |
165
|
|
|
var k = this.keys[i]; |
166
|
|
|
f.call(this, k, this[':' + k]); |
167
|
|
|
} |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
Set.prototype.clear = function() { |
|
|
|
|
171
|
|
|
for (var i = 0; i < this.keys.length; ++i) { |
172
|
|
|
delete this[':' + this.keys[i]]; |
173
|
|
|
} |
174
|
|
|
this.keys.length = 0; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
|
178
|
|
|
// Applies the given function to each element of the array, preserving |
179
|
|
|
// this, and passing the index. |
180
|
|
|
function mapExec(array, func) { |
181
|
|
|
for (var i = 0; i < array.length; ++i) { |
182
|
|
|
func.call(this, array[i], i); |
183
|
|
|
} |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
// Returns an array that contains the return value of the given |
187
|
|
|
// function applied to every element of the input array. |
188
|
|
|
function mapExpr(array, func) { |
189
|
|
|
var ret = []; |
190
|
|
|
for (var i = 0; i < array.length; ++i) { |
191
|
|
|
ret.push(func(array[i])); |
192
|
|
|
} |
193
|
|
|
return ret; |
194
|
|
|
}; |
195
|
|
|
|
196
|
|
|
// Reverses the given array in place. |
197
|
|
|
function reverseInplace(array) { |
198
|
|
|
for (var i = 0; i < array.length / 2; ++i) { |
199
|
|
|
var h = array[i]; |
200
|
|
|
var ii = array.length - i - 1; |
201
|
|
|
array[i] = array[ii]; |
202
|
|
|
array[ii] = h; |
203
|
|
|
} |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
// Removes value from array. Returns the number of instances of value |
207
|
|
|
// that were removed from array. |
208
|
|
|
function removeFromArray(array, value, opt_notype) { |
209
|
|
|
var shift = 0; |
210
|
|
|
for (var i = 0; i < array.length; ++i) { |
211
|
|
|
if (array[i] === value || (opt_notype && array[i] == value)) { |
212
|
|
|
array.splice(i--, 1); |
213
|
|
|
shift++; |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
return shift; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
// Shallow-copies an array to the end of another array |
220
|
|
|
// Basically Array.concat, but works with other non-array collections |
221
|
|
|
function copyArray(dst, src) { |
222
|
|
|
if (!src) return; |
223
|
|
|
var dstLength = dst.length; |
224
|
|
|
for (var i = src.length - 1; i >= 0; --i) { |
225
|
|
|
dst[i+dstLength] = src[i]; |
226
|
|
|
} |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* This is an optimization for copying attribute lists in IE. IE includes many |
231
|
|
|
* extraneous properties in its DOM attribute lists, which take require |
232
|
|
|
* significant extra processing when evaluating attribute steps. With this |
233
|
|
|
* function, we ignore any such attributes that has an empty string value. |
234
|
|
|
*/ |
235
|
|
|
function copyArrayIgnoringAttributesWithoutValue(dst, src) |
236
|
|
|
{ |
237
|
|
|
if (!src) return; |
238
|
|
|
for (var i = src.length - 1; i >= 0; --i) { |
239
|
|
|
// this test will pass so long as the attribute has a non-empty string |
240
|
|
|
// value, even if that value is "false", "0", "undefined", etc. |
241
|
|
|
if (src[i].nodeValue) { |
242
|
|
|
dst.push(src[i]); |
243
|
|
|
} |
244
|
|
|
} |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
// Returns the text value of a node; for nodes without children this |
248
|
|
|
// is the nodeValue, for nodes with children this is the concatenation |
249
|
|
|
// of the value of all children. Browser-specific optimizations are used by |
250
|
|
|
// default; they can be disabled by passing "true" in as the second parameter. |
251
|
|
|
function xmlValue(node, disallowBrowserSpecificOptimization) { |
252
|
|
|
if (!node) { |
253
|
|
|
return ''; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
var ret = ''; |
257
|
|
|
if (node.nodeType == DOM_TEXT_NODE || |
258
|
|
|
node.nodeType == DOM_CDATA_SECTION_NODE) { |
259
|
|
|
ret += node.nodeValue; |
260
|
|
|
|
261
|
|
|
} else if (node.nodeType == DOM_ATTRIBUTE_NODE) { |
262
|
|
|
if (ajaxsltIsIE6) { |
263
|
|
|
ret += xmlValueIE6Hack(node); |
264
|
|
|
} else { |
265
|
|
|
ret += node.nodeValue; |
266
|
|
|
} |
267
|
|
|
} else if (node.nodeType == DOM_ELEMENT_NODE || |
268
|
|
|
node.nodeType == DOM_DOCUMENT_NODE || |
269
|
|
|
node.nodeType == DOM_DOCUMENT_FRAGMENT_NODE) { |
270
|
|
|
if (!disallowBrowserSpecificOptimization) { |
271
|
|
|
// IE, Safari, Opera, and friends |
272
|
|
|
var innerText = node.innerText; |
273
|
|
|
if (innerText != undefined) { |
274
|
|
|
return innerText; |
275
|
|
|
} |
276
|
|
|
// Firefox |
277
|
|
|
var textContent = node.textContent; |
278
|
|
|
if (textContent != undefined) { |
279
|
|
|
return textContent; |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
// pobrecito! |
283
|
|
|
var len = node.childNodes.length; |
284
|
|
|
for (var i = 0; i < len; ++i) { |
285
|
|
|
ret += arguments.callee(node.childNodes[i]); |
|
|
|
|
286
|
|
|
} |
287
|
|
|
} |
288
|
|
|
return ret; |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
function xmlValueIE6Hack(node) { |
292
|
|
|
// Issue 19, IE6 mangles href attribute when it's a javascript: url |
293
|
|
|
var nodeName = node.nodeName; |
294
|
|
|
var nodeValue = node.nodeValue; |
295
|
|
|
if (nodeName.length != 4) return nodeValue; |
296
|
|
|
if (!/^href$/i.test(nodeName)) return nodeValue; |
297
|
|
|
if (!/^javascript:/.test(nodeValue)) return nodeValue; |
298
|
|
|
return unescape(nodeValue); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
// Returns the representation of a node as XML text. |
302
|
|
|
function xmlText(node, opt_cdata) { |
303
|
|
|
var buf = []; |
304
|
|
|
xmlTextR(node, buf, opt_cdata); |
305
|
|
|
return buf.join(''); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
function xmlTextR(node, buf, cdata) { |
309
|
|
|
if (node.nodeType == DOM_TEXT_NODE) { |
310
|
|
|
buf.push(xmlEscapeText(node.nodeValue)); |
311
|
|
|
|
312
|
|
|
} else if (node.nodeType == DOM_CDATA_SECTION_NODE) { |
313
|
|
|
if (cdata) { |
314
|
|
|
buf.push(node.nodeValue); |
315
|
|
|
} else { |
316
|
|
|
buf.push('<![CDATA[' + node.nodeValue + ']]>'); |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
} else if (node.nodeType == DOM_COMMENT_NODE) { |
320
|
|
|
buf.push('<!--' + node.nodeValue + '-->'); |
321
|
|
|
|
322
|
|
|
} else if (node.nodeType == DOM_ELEMENT_NODE) { |
323
|
|
|
buf.push('<' + xmlFullNodeName(node)); |
324
|
|
|
for (var i = 0; i < node.attributes.length; ++i) { |
325
|
|
|
var a = node.attributes[i]; |
326
|
|
|
if (a && a.nodeName && a.nodeValue) { |
327
|
|
|
buf.push(' ' + xmlFullNodeName(a) + '="' + |
328
|
|
|
xmlEscapeAttr(a.nodeValue) + '"'); |
329
|
|
|
} |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
if (node.childNodes.length == 0) { |
333
|
|
|
buf.push('/>'); |
334
|
|
|
} else { |
335
|
|
|
buf.push('>'); |
336
|
|
|
for (var i = 0; i < node.childNodes.length; ++i) { |
|
|
|
|
337
|
|
|
arguments.callee(node.childNodes[i], buf, cdata); |
|
|
|
|
338
|
|
|
} |
339
|
|
|
buf.push('</' + xmlFullNodeName(node) + '>'); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
} else if (node.nodeType == DOM_DOCUMENT_NODE || |
343
|
|
|
node.nodeType == DOM_DOCUMENT_FRAGMENT_NODE) { |
344
|
|
|
for (var i = 0; i < node.childNodes.length; ++i) { |
|
|
|
|
345
|
|
|
arguments.callee(node.childNodes[i], buf, cdata); |
|
|
|
|
346
|
|
|
} |
347
|
|
|
} |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
function xmlFullNodeName(n) { |
351
|
|
|
if (n.prefix && n.nodeName.indexOf(n.prefix + ':') != 0) { |
352
|
|
|
return n.prefix + ':' + n.nodeName; |
353
|
|
|
} else { |
354
|
|
|
return n.nodeName; |
355
|
|
|
} |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
// Escape XML special markup chracters: tag delimiter < > and entity |
359
|
|
|
// reference start delimiter &. The escaped string can be used in XML |
360
|
|
|
// text portions (i.e. between tags). |
361
|
|
|
function xmlEscapeText(s) { |
362
|
|
|
return ('' + s).replace(/&/g, '&').replace(/</g, '<'). |
363
|
|
|
replace(/>/g, '>'); |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
// Escape XML special markup characters: tag delimiter < > entity |
367
|
|
|
// reference start delimiter & and quotes ". The escaped string can be |
368
|
|
|
// used in double quoted XML attribute value portions (i.e. in |
369
|
|
|
// attributes within start tags). |
370
|
|
|
function xmlEscapeAttr(s) { |
371
|
|
|
return xmlEscapeText(s).replace(/\"/g, '"'); |
372
|
|
|
} |
373
|
|
|
|
374
|
|
|
// Escape markup in XML text, but don't touch entity references. The |
375
|
|
|
// escaped string can be used as XML text (i.e. between tags). |
376
|
|
|
function xmlEscapeTags(s) { |
377
|
|
|
return s.replace(/</g, '<').replace(/>/g, '>'); |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
/** |
381
|
|
|
* Wrapper function to access the owner document uniformly for document |
382
|
|
|
* and other nodes: for the document node, the owner document is the |
383
|
|
|
* node itself, for all others it's the ownerDocument property. |
384
|
|
|
* |
385
|
|
|
* @param {Node} node |
386
|
|
|
* @return {Document} |
387
|
|
|
*/ |
388
|
|
|
function xmlOwnerDocument(node) { |
389
|
|
|
if (node.nodeType == DOM_DOCUMENT_NODE) { |
390
|
|
|
return node; |
391
|
|
|
} else { |
392
|
|
|
return node.ownerDocument; |
393
|
|
|
} |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
// Wrapper around DOM methods so we can condense their invocations. |
397
|
|
|
function domGetAttribute(node, name) { |
398
|
|
|
return node.getAttribute(name); |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
function domSetAttribute(node, name, value) { |
402
|
|
|
return node.setAttribute(name, value); |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
function domRemoveAttribute(node, name) { |
406
|
|
|
return node.removeAttribute(name); |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
function domAppendChild(node, child) { |
410
|
|
|
return node.appendChild(child); |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
function domRemoveChild(node, child) { |
414
|
|
|
return node.removeChild(child); |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
function domReplaceChild(node, newChild, oldChild) { |
418
|
|
|
return node.replaceChild(newChild, oldChild); |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
function domInsertBefore(node, newChild, oldChild) { |
422
|
|
|
return node.insertBefore(newChild, oldChild); |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
function domRemoveNode(node) { |
426
|
|
|
return domRemoveChild(node.parentNode, node); |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
function domCreateTextNode(doc, text) { |
430
|
|
|
return doc.createTextNode(text); |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
function domCreateElement(doc, name) { |
434
|
|
|
return doc.createElement(name); |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
function domCreateAttribute(doc, name) { |
438
|
|
|
return doc.createAttribute(name); |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
function domCreateCDATASection(doc, data) { |
442
|
|
|
return doc.createCDATASection(data); |
443
|
|
|
} |
444
|
|
|
|
445
|
|
|
function domCreateComment(doc, text) { |
446
|
|
|
return doc.createComment(text); |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
function domCreateDocumentFragment(doc) { |
450
|
|
|
return doc.createDocumentFragment(); |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
function domGetElementById(doc, id) { |
454
|
|
|
return doc.getElementById(id); |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
// Same for window methods. |
458
|
|
|
function windowSetInterval(win, fun, time) { |
459
|
|
|
return win.setInterval(fun, time); |
460
|
|
|
} |
461
|
|
|
|
462
|
|
|
function windowClearInterval(win, id) { |
463
|
|
|
return win.clearInterval(id); |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
/** |
467
|
|
|
* Escape the special regular expression characters when the regular expression |
468
|
|
|
* is specified as a string. |
469
|
|
|
* |
470
|
|
|
* Based on: http://simonwillison.net/2006/Jan/20/escape/ |
471
|
|
|
*/ |
472
|
|
|
RegExp.escape = (function() { |
|
|
|
|
473
|
|
|
var specials = [ |
474
|
|
|
'/', '.', '*', '+', '?', '|', '^', '$', |
475
|
|
|
'(', ')', '[', ']', '{', '}', '\\' |
476
|
|
|
]; |
477
|
|
|
|
478
|
|
|
var sRE = new RegExp( |
479
|
|
|
'(\\' + specials.join('|\\') + ')', 'g' |
480
|
|
|
); |
481
|
|
|
|
482
|
|
|
return function(text) { |
483
|
|
|
return text.replace(sRE, '\\$1'); |
484
|
|
|
} |
485
|
|
|
})(); |
486
|
|
|
|
487
|
|
|
/** |
488
|
|
|
* Determines whether a predicate expression contains a "positional selector". |
489
|
|
|
* A positional selector filters nodes from the nodelist input based on their |
490
|
|
|
* position within that list. When such selectors are encountered, the |
491
|
|
|
* evaluation of the predicate cannot be depth-first, because the positional |
492
|
|
|
* selector may be based on the result of evaluating predicates that precede |
493
|
|
|
* it. |
494
|
|
|
*/ |
495
|
|
|
function predicateExprHasPositionalSelector(expr, isRecursiveCall) { |
496
|
|
|
if (!expr) { |
497
|
|
|
return false; |
498
|
|
|
} |
499
|
|
|
if (!isRecursiveCall && exprReturnsNumberValue(expr)) { |
500
|
|
|
// this is a "proximity position"-based predicate |
501
|
|
|
return true; |
502
|
|
|
} |
503
|
|
|
if (expr instanceof FunctionCallExpr) { |
504
|
|
|
var value = expr.name.value; |
505
|
|
|
return (value == 'last' || value == 'position'); |
506
|
|
|
} |
507
|
|
|
if (expr instanceof BinaryExpr) { |
508
|
|
|
return ( |
509
|
|
|
predicateExprHasPositionalSelector(expr.expr1, true) || |
510
|
|
|
predicateExprHasPositionalSelector(expr.expr2, true)); |
511
|
|
|
} |
512
|
|
|
return false; |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
function exprReturnsNumberValue(expr) { |
516
|
|
|
if (expr instanceof FunctionCallExpr) { |
517
|
|
|
var isMember = { |
518
|
|
|
last: true |
519
|
|
|
, position: true |
520
|
|
|
, count: true |
521
|
|
|
, 'string-length': true |
522
|
|
|
, number: true |
523
|
|
|
, sum: true |
524
|
|
|
, floor: true |
525
|
|
|
, ceiling: true |
526
|
|
|
, round: true |
527
|
|
|
}; |
528
|
|
|
return isMember[expr.name.value]; |
529
|
|
|
} |
530
|
|
|
else if (expr instanceof UnaryMinusExpr) { |
531
|
|
|
return true; |
532
|
|
|
} |
533
|
|
|
else if (expr instanceof BinaryExpr) { |
534
|
|
|
var isMember = { |
|
|
|
|
535
|
|
|
'+': true |
536
|
|
|
, '-': true |
537
|
|
|
, '*': true |
538
|
|
|
, mod: true |
539
|
|
|
, div: true |
540
|
|
|
}; |
541
|
|
|
return isMember[expr.op.value]; |
542
|
|
|
} |
543
|
|
|
else if (expr instanceof NumberExpr) { |
544
|
|
|
return true; |
545
|
|
|
} |
546
|
|
|
return false; |
547
|
|
|
} |
548
|
|
|
|
549
|
|
|
|
550
|
|
|
|
This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.