envjs 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. data/README +4 -1
  2. data/lib/envjs/env.js +3815 -133
  3. data/lib/envjs/runtime.rb +3 -3
  4. metadata +2 -2
data/lib/envjs/env.js CHANGED
@@ -85,6 +85,7 @@
85
85
  $env.$master = $master;
86
86
  var $inner = this.$inner;
87
87
  delete this.$inner;
88
+ $inner.$envx = $env;
88
89
  $env.init_window.call($inner,$inner,options);
89
90
  };
90
91
 
@@ -130,20 +131,12 @@ $env.location = function(path, base){
130
131
  return s;
131
132
  }else if(base){
132
133
  base = Ruby.URI.parse(base);
133
- if ( path[0] == "/" ) {
134
- base.path = path;
135
- base = base + "";
136
- } else {
137
- // debug("bb", base);
138
- // base = base + Ruby.URI.parse(path);
139
- b = Ruby.eval("lambda { |a,b| a+b; }");
140
- base = b(base,path);
141
- // base.path = base.path.substring(0, base.path.lastIndexOf('/'));
142
- // base.path = base.path + '/' + path;
143
- base = base + "";
144
- // debug("bbb", base);
145
- }
134
+ path = Ruby.URI.parse(path);
135
+ b = Ruby.eval("lambda { |a,b| a+b; }");
136
+ base = b(base,path);
137
+ base = base + "";
146
138
  var result = base;
139
+ // print("ZZ",result);
147
140
  // ? This path only used for files?
148
141
  if ( result.substring(0,6) == "file:/" && result[6] != "/" ) {
149
142
  result = "file://" + result.substring(5,result.length);
@@ -161,7 +154,12 @@ $env.location = function(path, base){
161
154
  base.href &&
162
155
  (base.href.length > 0) ) {
163
156
  base = base.href.substring(0, base.href.lastIndexOf('/'));
164
- var result = base + '/' + path;
157
+ var result;
158
+ if ( base[base.length-1] == "/" ) {
159
+ result = base + path;
160
+ } else {
161
+ result = base + '/' + path;
162
+ }
165
163
  if ( result.substring(0,6) == "file:/" && result[6] != "/" ) {
166
164
  result = "file://" + result.substring(5,result.length);
167
165
  }
@@ -174,7 +172,7 @@ $env.location = function(path, base){
174
172
  }
175
173
  };
176
174
 
177
- $env.connection = function(xhr, responseHandler, data){
175
+ $env.connection = $master.connection || function(xhr, responseHandler, data){
178
176
  var url = Ruby.URI.parse(xhr.url);
179
177
  var connection;
180
178
  var resp;
@@ -503,14 +501,15 @@ $env.__eval__ = function(script,scope){
503
501
  }
504
502
  };
505
503
 
506
- $env.newwindow = function(openingWindow, parentArg, url, outer){
504
+ $env.newwindow = function(openingWindow, parentArg, url, outer,xhr_options){
507
505
  // print(location);
508
506
  // print("url",url,window.location,openingWindow);
509
507
  // print("parent",parentArg);
510
508
  var options = {
511
509
  opener: openingWindow,
512
510
  parent: parentArg,
513
- url: $env.location(url)
511
+ url: $env.location(url),
512
+ xhr: xhr_options
514
513
  };
515
514
 
516
515
  // print("$w",$w);
@@ -526,12 +525,13 @@ $env.newwindow = function(openingWindow, parentArg, url, outer){
526
525
  return proxy;
527
526
  };
528
527
 
529
- $env.reload = function(oldWindowProxy, url){
528
+ $env.reload = function(oldWindowProxy, url,options){
530
529
  // print("reload",window,oldWindowProxy,url);
531
530
  $env.newwindow( oldWindowProxy.opener,
532
- oldWindowProxy.parent,
533
- url,
534
- oldWindowProxy );
531
+ oldWindowProxy.parent,
532
+ url,
533
+ oldWindowProxy,
534
+ options );
535
535
  };
536
536
 
537
537
  $env.sleep = function(n){Ruby.sleep(n/1000.);};
@@ -1120,10 +1120,10 @@ $env.unload = function(windowToUnload){
1120
1120
  };
1121
1121
 
1122
1122
 
1123
- $env.load = function(url){
1123
+ $env.load = function(url,xhr_options){
1124
1124
  $location = $env.location(url);
1125
1125
  __setHistory__($location);
1126
- $w.document.load($location);
1126
+ $w.document.load($location,xhr_options);
1127
1127
  };
1128
1128
 
1129
1129
 
@@ -1554,10 +1554,12 @@ __extend__(DOMNamedNodeMap.prototype, {
1554
1554
  throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
1555
1555
  } else {
1556
1556
  this[itemIndex] = arg; // over-write existing NamedNode
1557
+ this[arg.name.toLowerCase()] = arg;
1557
1558
  }
1558
1559
  }else {
1559
1560
  // add new NamedNode
1560
1561
  Array.prototype.push.apply(this, [arg]);
1562
+ this[arg.name.toLowerCase()] = arg;
1561
1563
  }
1562
1564
  arg.ownerElement = this.parentNode;
1563
1565
 
@@ -5262,11 +5264,10 @@ __extend__(DOMDocument.prototype, {
5262
5264
  this._parseComplete = true;
5263
5265
  return this;
5264
5266
  },
5265
- load: function(url){
5267
+ load: function(url,xhr_options){
5266
5268
  $debug("Loading url into DOM Document: "+ url + " - (Asynch? "+$w.document.async+")");
5267
5269
  var scripts, _this = this;
5268
5270
  var xhr;
5269
- // print("KK",url,url =="about:blank");
5270
5271
  if (url == "about:blank"){
5271
5272
  xhr = ({
5272
5273
  open: function(){},
@@ -5306,7 +5307,11 @@ __extend__(DOMDocument.prototype, {
5306
5307
  } else {
5307
5308
  xhr = new XMLHttpRequest();
5308
5309
  }
5309
- xhr.open("GET", url, $w.document.async);
5310
+ xhr_options = xhr_options || {};
5311
+ var method = (xhr_options.method || "GET").toUpperCase();
5312
+ xhr.open(method, url, $w.document.async);
5313
+ // FIXME: not all XHRs have this right now
5314
+ xhr.setRequestHeader && xhr.setRequestHeader('Content-Type', xhr_options["Content-Type"] || 'application/x-www-form-urlencoded');
5310
5315
  xhr.onreadystatechange = function(){
5311
5316
  if (xhr.status != 200) {
5312
5317
  $warn("Could not retrieve XHR content from " + url + ": status code " + xhr.status);
@@ -5358,7 +5363,7 @@ __extend__(DOMDocument.prototype, {
5358
5363
  }
5359
5364
 
5360
5365
  };
5361
- xhr.send();
5366
+ xhr.send(xhr_options["data"]);
5362
5367
  },
5363
5368
  createEvent : function(eventType){
5364
5369
  var event;
@@ -5553,9 +5558,9 @@ __extend__(DOMDocument.prototype, {
5553
5558
  * @seealso
5554
5559
  * Document.evaluate
5555
5560
  */
5556
- /*evaluate: function(xpathText, contextNode, nsuriMapper, resultType, result){
5557
- return new XPathExpression().evaluate();
5558
- },*/
5561
+ evaluate: function(xpathText, contextNode, nsuriMapper, resultType, result){
5562
+ return new XPathExpression(xpathText, contextNode, nsuriMapper, resultType, result).evaluate();
5563
+ },
5559
5564
  getElementById : function(elementId) {
5560
5565
  var retNode = null,
5561
5566
  node;
@@ -6516,7 +6521,7 @@ HTMLInputCommon.prototype = new HTMLElement;
6516
6521
  __extend__(HTMLInputCommon.prototype, {
6517
6522
  get form(){
6518
6523
  var parent = this.parentNode;
6519
- while(parent.nodeName.toLowerCase() != 'form'){
6524
+ while(parent && parent.nodeName.toLowerCase() != 'form'){
6520
6525
  parent = parent.parentNode;
6521
6526
  }
6522
6527
  return parent;
@@ -6585,6 +6590,30 @@ __extend__(HTMLTypeValueInputs.prototype, {
6585
6590
  this.setAttribute('value',newValue);
6586
6591
  },
6587
6592
  setAttribute: function(name, value){
6593
+ if (this.type == "radio" && name == "checked" && value && this.name) {
6594
+ // HTMLElement.prototype.setAttribute.apply(this, ["checked", "checked"]);
6595
+ // return;
6596
+ var parent = this.parentNode;
6597
+ while(parent != document) {
6598
+ if(parent.tagName == "FORM") {
6599
+ break;
6600
+ }
6601
+ parent = parent.parentNode;
6602
+ }
6603
+ if(parent.tagName == "FORM") {
6604
+ var xpath = './/input[@type="radio" and @name="'+this.name+'"]';
6605
+ var nodes =
6606
+ document.evaluate(xpath,parent, null, XPathResult.ANY_TYPE,null );
6607
+ while(( node = nodes.iterateNext() )) {
6608
+ // FIX? events when we short circuit like this?
6609
+ if (node === this) {
6610
+ HTMLElement.prototype.setAttribute.call(node, "checked", "checked");
6611
+ } else {
6612
+ HTMLElement.prototype.removeAttribute.call(node, "checked");
6613
+ }
6614
+ }
6615
+ }
6616
+ }
6588
6617
  if(name == 'value' && !this.defaultValue){
6589
6618
  this.defaultValue = value;
6590
6619
  }
@@ -6622,6 +6651,12 @@ __extend__(HTMLInputAreaCommon.prototype, {
6622
6651
  });
6623
6652
 
6624
6653
  $w.HTMLInputAreaCommon = HTMLInputAreaCommon;
6654
+
6655
+ // Local Variables:
6656
+ // espresso-indent-level:4
6657
+ // c-basic-offset:4
6658
+ // tab-width:4
6659
+ // End:
6625
6660
  $debug("Defining HTMLAnchorElement");
6626
6661
  /*
6627
6662
  * HTMLAnchorElement - DOM Level 2
@@ -7175,7 +7210,400 @@ __extend__(HTMLFormElement.prototype,{
7175
7210
  }
7176
7211
  });
7177
7212
 
7178
- $w.HTMLFormElement = HTMLFormElement;$debug("Defining HTMLFrameElement");
7213
+ $w.HTMLFormElement = HTMLFormElement;
7214
+
7215
+ /**
7216
+ * Form Submissions
7217
+ *
7218
+ * This code is borrow largely from jquery.params and jquery.form.js
7219
+ *
7220
+ * formToArray() gathers form element data into an array of objects that can
7221
+ * be passed to any of the following ajax functions: $.get, $.post, or load.
7222
+ * Each object in the array has both a 'name' and 'value' property. An example of
7223
+ * an array for a simple login form might be:
7224
+ *
7225
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
7226
+ *
7227
+ * It is this array that is passed to pre-submit callback functions provided to the
7228
+ * ajaxSubmit() and ajaxForm() methods.
7229
+ *
7230
+ * The semantic argument can be used to force form serialization in semantic order.
7231
+ * This is normally true anyway, unless the form contains input elements of type='image'.
7232
+ * If your form must be submitted with name/value pairs in semantic order and your form
7233
+ * contains an input of type='image" then pass true for this arg, otherwise pass false
7234
+ * (or nothing) to avoid the overhead for this logic.
7235
+ *
7236
+ *
7237
+ * @name formToArray
7238
+ * @param semantic true if serialization must maintain strict semantic ordering of elements (slower)
7239
+ * @type Array<Object>
7240
+ */
7241
+ var __formToArray__ = function(form, semantic, boundary) {
7242
+ var array = [],
7243
+ elements = semantic ? form.getElementsByTagName('*') : form.elements,
7244
+ element,
7245
+ i,j,imax, jmax,
7246
+ name,
7247
+ value;
7248
+
7249
+ if (!elements)
7250
+ return array;
7251
+
7252
+ imax = elements.length;
7253
+ for(i=0; i < imax; i++) {
7254
+ element = elements[i];
7255
+ name = element.name;
7256
+ if (!name)
7257
+ continue;
7258
+
7259
+ if (semantic && form.clk && element.type == "image") {
7260
+ // handle image inputs on the fly when semantic == true
7261
+ if(!element.disabled && form.clk == element) {
7262
+ if (form.clk_x) {
7263
+ array.push({
7264
+ name: name+'.x',
7265
+ value: form.clk_x
7266
+ });
7267
+ }
7268
+ if (form.clk_y) {
7269
+ array.push({
7270
+ name: name+'.y',
7271
+ value: form.clk_y
7272
+ });
7273
+ }
7274
+ }
7275
+ continue;
7276
+ }
7277
+
7278
+ value = __fieldValue__(element, true);
7279
+ if (value && value.constructor == Array) {
7280
+ jmax = value.length;
7281
+ for(j=0; j < jmax; j++){
7282
+ // FIX: handle uploads with the same name
7283
+ array.push({name: name, value: value[j]});
7284
+ }
7285
+ } else if (value !== null && typeof value != 'undefined'){
7286
+ array.push({name: name, value: value});
7287
+ if(element.type == "file") {
7288
+ var v = array[array.length-1];
7289
+ v.filename = element.value || null;
7290
+ }
7291
+ }
7292
+ }
7293
+
7294
+ if (!semantic && form.clk) {
7295
+ // input type=='image' are not found in elements array! handle them here
7296
+ elements = form.getElementsByTagName("input");
7297
+ imax = imax=elements.length;
7298
+ for(i=0; i < imax; i++) {
7299
+ element = elements[i];
7300
+ name = element.name;
7301
+ if(name && !element.disabled && element.type == "image" && form.clk == element)
7302
+ if (form.clk_x)
7303
+ array.push({name: name+'.x', value: form.clk_x});
7304
+ if (form.clk_y)
7305
+ array.push({name: name+'.y', value: form.clk_y});
7306
+ }
7307
+ }
7308
+ return array;
7309
+ };
7310
+
7311
+
7312
+ /**
7313
+ * Serializes form data into a 'submittable' string. This method will return a string
7314
+ * in the format: name1=value1&amp;name2=value2
7315
+ *
7316
+ * The semantic argument can be used to force form serialization in semantic order.
7317
+ * If your form must be submitted with name/value pairs in semantic order then pass
7318
+ * true for this arg, otherwise pass false (or nothing) to avoid the overhead for
7319
+ * this logic (which can be significant for very large forms).
7320
+ *
7321
+ *
7322
+ * @name formSerialize
7323
+ * @param semantic true if serialization must maintain strict semantic ordering of elements (slower)
7324
+ * @type String
7325
+ */
7326
+ var __formSerialize__ = function(form, semantic,boundary) {
7327
+ //hand off to param for proper encoding
7328
+ v = __param__(__formToArray__(form, semantic,boundary),boundary);
7329
+ // print("v",v);
7330
+ return v;
7331
+ };
7332
+
7333
+
7334
+ /**
7335
+ * Serializes all field elements inputs Array into a query string.
7336
+ * This method will return a string in the format: name1=value1&amp;name2=value2
7337
+ *
7338
+ * The successful argument controls whether or not serialization is limited to
7339
+ * 'successful' controls (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
7340
+ * The default value of the successful argument is true.
7341
+ *
7342
+ *
7343
+ * @name fieldSerialize
7344
+ * @param successful true if only successful controls should be serialized (default is true)
7345
+ * @type String
7346
+ */
7347
+ var __fieldSerialize__ = function(inputs, successful) {
7348
+ var array = [],
7349
+ input,
7350
+ name,
7351
+ value,
7352
+ i,j, imax, jmax;
7353
+
7354
+ imax = inputs.length;
7355
+ for(i=0; i<imax; i++){
7356
+ input = inputs[i];
7357
+ name = input.name;
7358
+ if (!name)
7359
+ return;
7360
+ value = __fieldValue__(input, successful);
7361
+ if (value && value.constructor == Array) {
7362
+ jmax = value.length;
7363
+ for (j=0; j < max; j++){
7364
+ array.push({
7365
+ name: name,
7366
+ value: value[j]
7367
+ });
7368
+ }
7369
+ }else if (value !== null && typeof value != 'undefined'){
7370
+ array.push({
7371
+ name: input.name,
7372
+ value: value
7373
+ });
7374
+ }
7375
+ };
7376
+ //hand off for proper encoding
7377
+ return __param__(array);
7378
+ };
7379
+
7380
+
7381
+ /**
7382
+ * Returns the value(s) of the element in the matched set. For example, consider the following form:
7383
+ *
7384
+ *
7385
+ * The successful argument controls whether or not the field element must be 'successful'
7386
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
7387
+ * The default value of the successful argument is true. If this value is false the value(s)
7388
+ * for each element is returned.
7389
+ *
7390
+ * Note: This method *always* returns an array. If no valid value can be determined the
7391
+ * array will be empty, otherwise it will contain one or more values.
7392
+ *
7393
+ *
7394
+ * @name fieldValue
7395
+ * @param Boolean successful true if only the values for successful controls
7396
+ * should be returned (default is true)
7397
+ * @type Array<String>
7398
+ */
7399
+ var __fieldValues__ = function(inputs, successful) {
7400
+ var i,
7401
+ imax = inputs.length,
7402
+ element,
7403
+ values = [],
7404
+ value;
7405
+ for (i=0; i < imax; i++) {
7406
+ element = inputs[i];
7407
+ value = __fieldValue__(element, successful);
7408
+ if (value === null || typeof value == 'undefined' ||
7409
+ (value.constructor == Array && !value.length))
7410
+ continue;
7411
+ value.constructor == Array ?
7412
+ Array.prototype.push(values, value) :
7413
+ values.push(value);
7414
+ }
7415
+ return values;
7416
+ };
7417
+
7418
+ /**
7419
+ * Returns the value of the field element.
7420
+ *
7421
+ * The successful argument controls whether or not the field element must be 'successful'
7422
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
7423
+ * The default value of the successful argument is true. If the given element is not
7424
+ * successful and the successful arg is not false then the returned value will be null.
7425
+ *
7426
+ * Note: If the successful flag is true (default) but the element is not successful, the return will be null
7427
+ * Note: The value returned for a successful select-multiple element will always be an array.
7428
+ * Note: If the element has no value the return value will be undefined.
7429
+ *
7430
+ * @name fieldValue
7431
+ * @param Element el The DOM element for which the value will be returned
7432
+ * @param Boolean successful true if value returned must be for a successful controls (default is true)
7433
+ * @type String or Array<String> or null or undefined
7434
+ */
7435
+ var __fieldValue__ = function(element, successful) {
7436
+ var name = element.name,
7437
+ type = element.type,
7438
+ tag = element.tagName.toLowerCase(),
7439
+ index,
7440
+ array,
7441
+ options,
7442
+ option,
7443
+ one,
7444
+ i, imax,
7445
+ value;
7446
+ if (typeof successful == 'undefined') successful = true;
7447
+
7448
+ // NOTE: changed to default to first selected: could do this when building the DOM?
7449
+ // Probably, but a little unclear ...
7450
+
7451
+ if (successful && (!name || element.disabled || type == 'reset' || type == 'button' ||
7452
+ (type == 'checkbox' || type == 'radio') && !element.checked ||
7453
+ (type == 'submit' || type == 'image') &&
7454
+ element.form && element.form.clk != element || (false && tag == 'select' &&
7455
+ element.selectedIndex == -1)))
7456
+ return null;
7457
+
7458
+ if (tag == 'select') {
7459
+ index = element.selectedIndex;
7460
+ if (index < 0)
7461
+ // return null;
7462
+ index = 0;
7463
+ array = [];
7464
+ options = element.options;
7465
+ one = (type == 'select-one');
7466
+ imax = (one ? index+1 : options.length);
7467
+ i = (one ? index : 0);
7468
+ for( i; i < imax; i++) {
7469
+ option = options[i];
7470
+ if (option && option.selected) {
7471
+ value = option.value;
7472
+ if (one)
7473
+ return value;
7474
+ array.push(value);
7475
+ }
7476
+ }
7477
+ if (array.length === 0) {
7478
+ if (element.options[0]) {
7479
+ array.push( element.options[0].value );
7480
+ }
7481
+ }
7482
+ return array;
7483
+ }
7484
+
7485
+ // print("**",tag,type,element.value,element.innerText);
7486
+
7487
+ if (type == "file") {
7488
+ return Ruby.File.basename(element.value);
7489
+ }
7490
+
7491
+ if (tag == "textarea") {
7492
+ return element.innerText;
7493
+ }
7494
+
7495
+ return element.value;
7496
+ };
7497
+
7498
+
7499
+ /**
7500
+ * Clears the form data. Takes the following actions on the form's input fields:
7501
+ * - input text fields will have their 'value' property set to the empty string
7502
+ * - select elements will have their 'selectedIndex' property set to -1
7503
+ * - checkbox and radio inputs will have their 'checked' property set to false
7504
+ * - inputs of type submit, button, reset, and hidden will *not* be effected
7505
+ * - button elements will *not* be effected
7506
+ *
7507
+ *
7508
+ * @name clearForm
7509
+ */
7510
+ var __clearForm__ = function(form) {
7511
+ var i,
7512
+ j, jmax,
7513
+ elements,
7514
+ resetable = ['input','select','textarea'];
7515
+ for(i=0; i<resetable.lenth; i++){
7516
+ elements = form.getElementsByTagName(resetable[i]);
7517
+ jmax = elements.length;
7518
+ for(j=0;j<jmax;j++){
7519
+ __clearField__(elements[j]);
7520
+ }
7521
+ }
7522
+ };
7523
+
7524
+ /**
7525
+ * Clears the selected form element. Takes the following actions on the element:
7526
+ * - input text fields will have their 'value' property set to the empty string
7527
+ * - select elements will have their 'selectedIndex' property set to -1
7528
+ * - checkbox and radio inputs will have their 'checked' property set to false
7529
+ * - inputs of type submit, button, reset, and hidden will *not* be effected
7530
+ * - button elements will *not* be effected
7531
+ *
7532
+ * @name clearFields
7533
+ */
7534
+ var __clearField__ = function(element) {
7535
+ var type = element.type,
7536
+ tag = element.tagName.toLowerCase();
7537
+ if (type == 'text' || type == 'password' || tag == 'textarea')
7538
+ element.value = '';
7539
+ else if (type == 'checkbox' || type == 'radio')
7540
+ element.checked = false;
7541
+ else if (tag == 'select')
7542
+ element.selectedIndex = -1;
7543
+ };
7544
+
7545
+
7546
+ // Serialize an array of key/values into a query string
7547
+ var __param__= function( array, boundary ) {
7548
+ var serialized = [];
7549
+ if(boundary) {
7550
+ for(i=0; i<array.length; i++){
7551
+ if (array[i].filename === null) {
7552
+ continue;
7553
+ }
7554
+ serialized.push( "--"+boundary + "\r\n" );
7555
+ var fn = array[i].filename ? '; filename="'+array[i].value+'"' : "";
7556
+ if (fn) {
7557
+ var mime_type = "text/plain";
7558
+ var transfer_encoding;
7559
+ if (array[i].filename.match(/\.jpe?g$/)) {
7560
+ mime_type = "image/jpeg";
7561
+ transfer_encoding = "base64";
7562
+ }
7563
+ var content;
7564
+ if (transfer_encoding === "base64") {
7565
+ Ruby.require("base64");
7566
+ content = Ruby.eval("lambda { |fn| Base64.encode64(File.read(fn)) }").call(array[i].filename);
7567
+ } else {
7568
+ content = Ruby.File.read(array[i].filename);
7569
+ }
7570
+ // FIX: better mime types
7571
+ array[i].value = [ "Content-Type: "+mime_type ];
7572
+ if(transfer_encoding) {
7573
+ array[i].value.push("Content-Transfer-Encoding: "+transfer_encoding);
7574
+ }
7575
+ array[i].value.push("Content-Length: "+content.length);
7576
+ array[i].value.push("");
7577
+ array[i].value.push(content);
7578
+ array[i].value = array[i].value.join("\r\n");
7579
+ }
7580
+ serialized.push('Content-Disposition: form-data; name="'+array[i].name+'"'+fn+'\r\n');
7581
+ serialized.push(array[i].value);
7582
+ serialized.push( "\r\n" );
7583
+ }
7584
+ serialized.push( "--"+boundary + "--\r\n" );
7585
+ var v = serialized.join("");
7586
+ // print("vvvv",v);
7587
+ return v;
7588
+ } else {
7589
+ // Serialize the key/values
7590
+ for(i=0; i<array.length; i++){
7591
+ serialized[ serialized.length ] =
7592
+ encodeURIComponent(array[i].name) + '=' +
7593
+ encodeURIComponent(array[i].value);
7594
+ }
7595
+
7596
+ // Return the resulting serialization
7597
+ return serialized.join("&").replace(/%20/g, "+");
7598
+ }
7599
+ };
7600
+
7601
+ // Local Variables:
7602
+ // espresso-indent-level:4
7603
+ // c-basic-offset:4
7604
+ // tab-width:4
7605
+ // End:
7606
+ $debug("Defining HTMLFrameElement");
7179
7607
  /*
7180
7608
  * HTMLFrameElement - DOM Level 2
7181
7609
  */
@@ -7741,7 +8169,7 @@ __extend__(HTMLOptGroupElement.prototype, {
7741
8169
  },
7742
8170
  set label(value){
7743
8171
  this.setAttribute('label',value);
7744
- },
8172
+ }
7745
8173
  });
7746
8174
 
7747
8175
  $w.HTMLOptGroupElement = HTMLOptGroupElement; $debug("Defining HTMLOptionElement");
@@ -7754,6 +8182,39 @@ var HTMLOptionElement = function(ownerDocument) {
7754
8182
  };
7755
8183
  HTMLOptionElement.prototype = new HTMLInputCommon;
7756
8184
  __extend__(HTMLOptionElement.prototype, {
8185
+ setAttribute: function(name, value){
8186
+ if (name != "selected") {
8187
+ HTMLInputCommon.prototype.setAttribute.apply(this, arguments);
8188
+ } else {
8189
+ if(this.defaultSelected===null && this.selected!==null){
8190
+ this.defaultSelected = this.selected;
8191
+ }
8192
+ var selectedValue = (value ? 'selected' : '');
8193
+ if (this.getAttribute('selected') == selectedValue) {
8194
+ // prevent inifinite loops (option's selected modifies
8195
+ // select's value which modifies option's selected)
8196
+ return;
8197
+ }
8198
+ HTMLInputCommon.prototype.setAttribute.call(this, 'selected', selectedValue);
8199
+ if (value) {
8200
+ // set select's value to this option's value (this also
8201
+ // unselects previously selected value)
8202
+ this.parentNode.value = this.value;
8203
+ } else {
8204
+ // if no other option is selected, select the first option in the select
8205
+ var i, anythingSelected;
8206
+ for (i=0; i<this.parentNode.options.length; i++) {
8207
+ if (this.parentNode.options[i].selected) {
8208
+ anythingSelected = true;
8209
+ break;
8210
+ }
8211
+ }
8212
+ if (!anythingSelected) {
8213
+ this.parentNode.value = this.parentNode.options[0].value;
8214
+ }
8215
+ }
8216
+ }
8217
+ },
7757
8218
  get defaultSelected(){
7758
8219
  return this.getAttribute('defaultSelected');
7759
8220
  },
@@ -7778,34 +8239,7 @@ __extend__(HTMLOptionElement.prototype, {
7778
8239
  return (this.getAttribute('selected')=='selected');
7779
8240
  },
7780
8241
  set selected(value){
7781
- if(this.defaultSelected===null && this.selected!==null){
7782
- this.defaultSelected = this.selected;
7783
- }
7784
- var selectedValue = (value ? 'selected' : '');
7785
- if (this.getAttribute('selected') == selectedValue) {
7786
- // prevent inifinite loops (option's selected modifies
7787
- // select's value which modifies option's selected)
7788
- return;
7789
- }
7790
- this.setAttribute('selected', selectedValue);
7791
- if (value) {
7792
- // set select's value to this option's value (this also
7793
- // unselects previously selected value)
7794
- this.parentNode.value = this.value;
7795
- } else {
7796
- // if no other option is selected, select the first option in the select
7797
- var i, anythingSelected;
7798
- for (i=0; i<this.parentNode.options.length; i++) {
7799
- if (this.parentNode.options[i].selected) {
7800
- anythingSelected = true;
7801
- break;
7802
- }
7803
- }
7804
- if (!anythingSelected) {
7805
- this.parentNode.value = this.parentNode.options[0].value;
7806
- }
7807
- }
7808
-
8242
+ this.setAttribute('selected',value);
7809
8243
  },
7810
8244
  get text(){
7811
8245
  return ((this.nodeValue === null) || (this.nodeValue ===undefined)) ?
@@ -7823,6 +8257,12 @@ __extend__(HTMLOptionElement.prototype, {
7823
8257
  });
7824
8258
 
7825
8259
  $w.HTMLOptionElement = HTMLOptionElement;
8260
+
8261
+ // Local Variables:
8262
+ // espresso-indent-level:4
8263
+ // c-basic-offset:4
8264
+ // tab-width:4
8265
+ // End:
7826
8266
  $debug("Defining HTMLParamElement");
7827
8267
  /*
7828
8268
  * HTMLParamElement - DOM Level 2
@@ -7856,7 +8296,7 @@ __extend__(HTMLParamElement.prototype, {
7856
8296
  },
7857
8297
  set valueType(value){
7858
8298
  this.setAttribute('valuetype',value);
7859
- },
8299
+ }
7860
8300
  });
7861
8301
 
7862
8302
  $w.HTMLParamElement = HTMLParamElement;
@@ -7963,6 +8403,10 @@ __extend__(HTMLSelectElement.prototype, {
7963
8403
  if (index !== undefined) {
7964
8404
  this.setAttribute('value', newValue);
7965
8405
  this.selectedIndex = index;
8406
+
8407
+ var event = document.createEvent();
8408
+ event.initEvent("change");
8409
+ this.dispatchEvent( event );
7966
8410
  }
7967
8411
  },
7968
8412
  get value() {
@@ -8749,7 +9193,156 @@ __extend__(XMLSerializer.prototype, {
8749
9193
  serializeToString: function(node){
8750
9194
  return node.xml;
8751
9195
  }
8752
- });/**
9196
+ });// Copyright 2006 Google Inc.
9197
+ // All Rights Reserved
9198
+ //
9199
+ // Defines regular expression patterns to extract XML tokens from string.
9200
+ // See <http://www.w3.org/TR/REC-xml/#sec-common-syn>,
9201
+ // <http://www.w3.org/TR/xml11/#sec-common-syn> and
9202
+ // <http://www.w3.org/TR/REC-xml-names/#NT-NCName> for the specifications.
9203
+ //
9204
+ // Author: Junji Takagi <jtakagi@google.com>
9205
+
9206
+ // Detect whether RegExp supports Unicode characters or not.
9207
+
9208
+ var REGEXP_UNICODE = function() {
9209
+ var tests = [' ', '\u0120', -1, // Konquerer 3.4.0 fails here.
9210
+ '!', '\u0120', -1,
9211
+ '\u0120', '\u0120', 0,
9212
+ '\u0121', '\u0120', -1,
9213
+ '\u0121', '\u0120|\u0121', 0,
9214
+ '\u0122', '\u0120|\u0121', -1,
9215
+ '\u0120', '[\u0120]', 0, // Safari 2.0.3 fails here.
9216
+ '\u0121', '[\u0120]', -1,
9217
+ '\u0121', '[\u0120\u0121]', 0, // Safari 2.0.3 fails here.
9218
+ '\u0122', '[\u0120\u0121]', -1,
9219
+ '\u0121', '[\u0120-\u0121]', 0, // Safari 2.0.3 fails here.
9220
+ '\u0122', '[\u0120-\u0121]', -1];
9221
+ for (var i = 0; i < tests.length; i += 3) {
9222
+ if (tests[i].search(new RegExp(tests[i + 1])) != tests[i + 2]) {
9223
+ return false;
9224
+ }
9225
+ }
9226
+ return true;
9227
+ }();
9228
+
9229
+ // Common tokens in XML 1.0 and XML 1.1.
9230
+
9231
+ var XML_S = '[ \t\r\n]+';
9232
+ var XML_EQ = '(' + XML_S + ')?=(' + XML_S + ')?';
9233
+ var XML_CHAR_REF = '&#[0-9]+;|&#x[0-9a-fA-F]+;';
9234
+
9235
+ // XML 1.0 tokens.
9236
+
9237
+ var XML10_VERSION_INFO = XML_S + 'version' + XML_EQ + '("1\\.0"|' + "'1\\.0')";
9238
+ var XML10_BASE_CHAR = (REGEXP_UNICODE) ?
9239
+ '\u0041-\u005a\u0061-\u007a\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff' +
9240
+ '\u0100-\u0131\u0134-\u013e\u0141-\u0148\u014a-\u017e\u0180-\u01c3' +
9241
+ '\u01cd-\u01f0\u01f4-\u01f5\u01fa-\u0217\u0250-\u02a8\u02bb-\u02c1\u0386' +
9242
+ '\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03ce\u03d0-\u03d6\u03da\u03dc' +
9243
+ '\u03de\u03e0\u03e2-\u03f3\u0401-\u040c\u040e-\u044f\u0451-\u045c' +
9244
+ '\u045e-\u0481\u0490-\u04c4\u04c7-\u04c8\u04cb-\u04cc\u04d0-\u04eb' +
9245
+ '\u04ee-\u04f5\u04f8-\u04f9\u0531-\u0556\u0559\u0561-\u0586\u05d0-\u05ea' +
9246
+ '\u05f0-\u05f2\u0621-\u063a\u0641-\u064a\u0671-\u06b7\u06ba-\u06be' +
9247
+ '\u06c0-\u06ce\u06d0-\u06d3\u06d5\u06e5-\u06e6\u0905-\u0939\u093d' +
9248
+ '\u0958-\u0961\u0985-\u098c\u098f-\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2' +
9249
+ '\u09b6-\u09b9\u09dc-\u09dd\u09df-\u09e1\u09f0-\u09f1\u0a05-\u0a0a' +
9250
+ '\u0a0f-\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32-\u0a33\u0a35-\u0a36' +
9251
+ '\u0a38-\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8b\u0a8d' +
9252
+ '\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2-\u0ab3\u0ab5-\u0ab9' +
9253
+ '\u0abd\u0ae0\u0b05-\u0b0c\u0b0f-\u0b10\u0b13-\u0b28\u0b2a-\u0b30' +
9254
+ '\u0b32-\u0b33\u0b36-\u0b39\u0b3d\u0b5c-\u0b5d\u0b5f-\u0b61\u0b85-\u0b8a' +
9255
+ '\u0b8e-\u0b90\u0b92-\u0b95\u0b99-\u0b9a\u0b9c\u0b9e-\u0b9f\u0ba3-\u0ba4' +
9256
+ '\u0ba8-\u0baa\u0bae-\u0bb5\u0bb7-\u0bb9\u0c05-\u0c0c\u0c0e-\u0c10' +
9257
+ '\u0c12-\u0c28\u0c2a-\u0c33\u0c35-\u0c39\u0c60-\u0c61\u0c85-\u0c8c' +
9258
+ '\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cde\u0ce0-\u0ce1' +
9259
+ '\u0d05-\u0d0c\u0d0e-\u0d10\u0d12-\u0d28\u0d2a-\u0d39\u0d60-\u0d61' +
9260
+ '\u0e01-\u0e2e\u0e30\u0e32-\u0e33\u0e40-\u0e45\u0e81-\u0e82\u0e84' +
9261
+ '\u0e87-\u0e88\u0e8a\u0e8d\u0e94-\u0e97\u0e99-\u0e9f\u0ea1-\u0ea3\u0ea5' +
9262
+ '\u0ea7\u0eaa-\u0eab\u0ead-\u0eae\u0eb0\u0eb2-\u0eb3\u0ebd\u0ec0-\u0ec4' +
9263
+ '\u0f40-\u0f47\u0f49-\u0f69\u10a0-\u10c5\u10d0-\u10f6\u1100\u1102-\u1103' +
9264
+ '\u1105-\u1107\u1109\u110b-\u110c\u110e-\u1112\u113c\u113e\u1140\u114c' +
9265
+ '\u114e\u1150\u1154-\u1155\u1159\u115f-\u1161\u1163\u1165\u1167\u1169' +
9266
+ '\u116d-\u116e\u1172-\u1173\u1175\u119e\u11a8\u11ab\u11ae-\u11af' +
9267
+ '\u11b7-\u11b8\u11ba\u11bc-\u11c2\u11eb\u11f0\u11f9\u1e00-\u1e9b' +
9268
+ '\u1ea0-\u1ef9\u1f00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d' +
9269
+ '\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc' +
9270
+ '\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec' +
9271
+ '\u1ff2-\u1ff4\u1ff6-\u1ffc\u2126\u212a-\u212b\u212e\u2180-\u2182' +
9272
+ '\u3041-\u3094\u30a1-\u30fa\u3105-\u312c\uac00-\ud7a3' :
9273
+ 'A-Za-z';
9274
+ var XML10_IDEOGRAPHIC = (REGEXP_UNICODE) ?
9275
+ '\u4e00-\u9fa5\u3007\u3021-\u3029' :
9276
+ '';
9277
+ var XML10_COMBINING_CHAR = (REGEXP_UNICODE) ?
9278
+ '\u0300-\u0345\u0360-\u0361\u0483-\u0486\u0591-\u05a1\u05a3-\u05b9' +
9279
+ '\u05bb-\u05bd\u05bf\u05c1-\u05c2\u05c4\u064b-\u0652\u0670\u06d6-\u06dc' +
9280
+ '\u06dd-\u06df\u06e0-\u06e4\u06e7-\u06e8\u06ea-\u06ed\u0901-\u0903\u093c' +
9281
+ '\u093e-\u094c\u094d\u0951-\u0954\u0962-\u0963\u0981-\u0983\u09bc\u09be' +
9282
+ '\u09bf\u09c0-\u09c4\u09c7-\u09c8\u09cb-\u09cd\u09d7\u09e2-\u09e3\u0a02' +
9283
+ '\u0a3c\u0a3e\u0a3f\u0a40-\u0a42\u0a47-\u0a48\u0a4b-\u0a4d\u0a70-\u0a71' +
9284
+ '\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0b01-\u0b03' +
9285
+ '\u0b3c\u0b3e-\u0b43\u0b47-\u0b48\u0b4b-\u0b4d\u0b56-\u0b57\u0b82-\u0b83' +
9286
+ '\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0c01-\u0c03\u0c3e-\u0c44' +
9287
+ '\u0c46-\u0c48\u0c4a-\u0c4d\u0c55-\u0c56\u0c82-\u0c83\u0cbe-\u0cc4' +
9288
+ '\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5-\u0cd6\u0d02-\u0d03\u0d3e-\u0d43' +
9289
+ '\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1' +
9290
+ '\u0eb4-\u0eb9\u0ebb-\u0ebc\u0ec8-\u0ecd\u0f18-\u0f19\u0f35\u0f37\u0f39' +
9291
+ '\u0f3e\u0f3f\u0f71-\u0f84\u0f86-\u0f8b\u0f90-\u0f95\u0f97\u0f99-\u0fad' +
9292
+ '\u0fb1-\u0fb7\u0fb9\u20d0-\u20dc\u20e1\u302a-\u302f\u3099\u309a' :
9293
+ '';
9294
+ var XML10_DIGIT = (REGEXP_UNICODE) ?
9295
+ '\u0030-\u0039\u0660-\u0669\u06f0-\u06f9\u0966-\u096f\u09e6-\u09ef' +
9296
+ '\u0a66-\u0a6f\u0ae6-\u0aef\u0b66-\u0b6f\u0be7-\u0bef\u0c66-\u0c6f' +
9297
+ '\u0ce6-\u0cef\u0d66-\u0d6f\u0e50-\u0e59\u0ed0-\u0ed9\u0f20-\u0f29' :
9298
+ '0-9';
9299
+ var XML10_EXTENDER = (REGEXP_UNICODE) ?
9300
+ '\u00b7\u02d0\u02d1\u0387\u0640\u0e46\u0ec6\u3005\u3031-\u3035' +
9301
+ '\u309d-\u309e\u30fc-\u30fe' :
9302
+ '';
9303
+ var XML10_LETTER = XML10_BASE_CHAR + XML10_IDEOGRAPHIC;
9304
+ var XML10_NAME_CHAR = XML10_LETTER + XML10_DIGIT + '\\._:' +
9305
+ XML10_COMBINING_CHAR + XML10_EXTENDER + '-';
9306
+ var XML10_NAME = '[' + XML10_LETTER + '_:][' + XML10_NAME_CHAR + ']*';
9307
+
9308
+ var XML10_ENTITY_REF = '&' + XML10_NAME + ';';
9309
+ var XML10_REFERENCE = XML10_ENTITY_REF + '|' + XML_CHAR_REF;
9310
+ var XML10_ATT_VALUE = '"(([^<&"]|' + XML10_REFERENCE + ')*)"|' +
9311
+ "'(([^<&']|" + XML10_REFERENCE + ")*)'";
9312
+ var XML10_ATTRIBUTE =
9313
+ '(' + XML10_NAME + ')' + XML_EQ + '(' + XML10_ATT_VALUE + ')';
9314
+
9315
+ // XML 1.1 tokens.
9316
+ // TODO(jtakagi): NameStartChar also includes \u10000-\ueffff.
9317
+ // ECMAScript Language Specifiction defines UnicodeEscapeSequence as
9318
+ // "\u HexDigit HexDigit HexDigit HexDigit" and we may need to use
9319
+ // surrogate pairs, but any browser doesn't support surrogate paris in
9320
+ // character classes of regular expression, so avoid including them for now.
9321
+
9322
+ var XML11_VERSION_INFO = XML_S + 'version' + XML_EQ + '("1\\.1"|' + "'1\\.1')";
9323
+ var XML11_NAME_START_CHAR = (REGEXP_UNICODE) ?
9324
+ ':A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff\u0370-\u037d' +
9325
+ '\u037f-\u1fff\u200c-\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff' +
9326
+ '\uf900-\ufdcf\ufdf0-\ufffd' :
9327
+ ':A-Z_a-z';
9328
+ var XML11_NAME_CHAR = XML11_NAME_START_CHAR +
9329
+ ((REGEXP_UNICODE) ? '\\.0-9\u00b7\u0300-\u036f\u203f-\u2040-' : '\\.0-9-');
9330
+ var XML11_NAME = '[' + XML11_NAME_START_CHAR + '][' + XML11_NAME_CHAR + ']*';
9331
+
9332
+ var XML11_ENTITY_REF = '&' + XML11_NAME + ';';
9333
+ var XML11_REFERENCE = XML11_ENTITY_REF + '|' + XML_CHAR_REF;
9334
+ var XML11_ATT_VALUE = '"(([^<&"]|' + XML11_REFERENCE + ')*)"|' +
9335
+ "'(([^<&']|" + XML11_REFERENCE + ")*)'";
9336
+ var XML11_ATTRIBUTE =
9337
+ '(' + XML11_NAME + ')' + XML_EQ + '(' + XML11_ATT_VALUE + ')';
9338
+
9339
+ // XML Namespace tokens.
9340
+ // Used in XML parser and XPath parser.
9341
+
9342
+ var XML_NC_NAME_CHAR = XML10_LETTER + XML10_DIGIT + '\\._' +
9343
+ XML10_COMBINING_CHAR + XML10_EXTENDER + '-';
9344
+ var XML_NC_NAME = '[' + XML10_LETTER + '_][' + XML_NC_NAME_CHAR + ']*';
9345
+ /**
8753
9346
  * @author thatcher
8754
9347
  */
8755
9348
  $debug("Defining XPathExpression");
@@ -8760,11 +9353,23 @@ $w.__defineGetter__("XPathExpression", function(){
8760
9353
  return XPathExpression;
8761
9354
  });
8762
9355
 
8763
- var XPathExpression = function() {};
9356
+ var XPathExpression =
9357
+ function(xpathText, contextNode, nsuriMapper, resultType, result) {
9358
+ if(nsuriMapper != null) {
9359
+ throw new Error("nsuriMapper not implemented");
9360
+ }
9361
+ if(result != null) {
9362
+ throw new Error("result not implemented");
9363
+ }
9364
+ if(resultType!=XPathResult.ANY_TYPE) {
9365
+ throw new Error("result type not implemented");
9366
+ }
9367
+ var context = new ExprContext(contextNode);
9368
+ this.result = xpathParse(xpathText).evaluate(context);
9369
+ };
8764
9370
  __extend__(XPathExpression.prototype, {
8765
9371
  evaluate: function(){
8766
- //TODO for now just return an empty XPathResult
8767
- return new XPathResult();
9372
+ return new XPathResult(this.result);
8768
9373
  }
8769
9374
  });/**
8770
9375
  * @author thatcher
@@ -8777,9 +9382,9 @@ $w.__defineGetter__("XPathResult", function(){
8777
9382
  return XPathResult;
8778
9383
  });
8779
9384
 
8780
- var XPathResult = function() {
8781
- this.snapshotLength = 0;
8782
- this.stringValue = '';
9385
+ var XPathResult = function(impl) {
9386
+ this.current = 0;
9387
+ this.impl = impl;
8783
9388
  };
8784
9389
 
8785
9390
  __extend__( XPathResult, {
@@ -8797,88 +9402,3097 @@ __extend__( XPathResult, {
8797
9402
 
8798
9403
  __extend__(XPathResult.prototype, {
8799
9404
  get booleanValue(){
8800
- //TODO
9405
+ this.impl.booleanValue();
9406
+ },
9407
+ get stringValue(){
9408
+ this.impl.stringValue();
8801
9409
  },
9410
+ /*
8802
9411
  get invalidIteration(){
9412
+ throw new Error("implement invalidIteration");
8803
9413
  //TODO
8804
9414
  },
9415
+ */
8805
9416
  get numberValue(){
8806
- //TODO
9417
+ this.impl.numberValue();
8807
9418
  },
9419
+ /*
8808
9420
  get resultType(){
9421
+ throw new Error("implement resultType");
8809
9422
  //TODO
8810
9423
  },
9424
+ */
8811
9425
  get singleNodeValue(){
8812
- //TODO
8813
- },
8814
- iterateNext: function(){
8815
- //TODO
9426
+ return this.impl.nodeSetValue()[0];
8816
9427
  },
9428
+ /*
8817
9429
  snapshotItem: function(index){
9430
+ throw new Error("implement snapshotItem");
8818
9431
  //TODO
9432
+ },
9433
+ */
9434
+ iterateNext: function(){
9435
+ return this.impl.nodeSetValue()[this.current++];
8819
9436
  }
8820
9437
  });
8821
9438
 
8822
- /**
8823
- * @author thatcher
8824
- */
9439
+ // ENVJS changes:
9440
+ // DOM_ => DOMNode.
9441
+ // case insensitive test on node names
8825
9442
 
8826
- $w.__defineGetter__("XSLTProcessor", function(){
8827
- return new XSLTProcessor(arguments);
8828
- });
9443
+ // Copyright 2005 Google Inc.
9444
+ // All Rights Reserved
9445
+ //
9446
+ // An XPath parser and evaluator written in JavaScript. The
9447
+ // implementation is complete except for functions handling
9448
+ // namespaces.
9449
+ //
9450
+ // Reference: [XPATH] XPath Specification
9451
+ // <http://www.w3.org/TR/1999/REC-xpath-19991116>.
9452
+ //
9453
+ //
9454
+ // The API of the parser has several parts:
9455
+ //
9456
+ // 1. The parser function xpathParse() that takes a string and returns
9457
+ // an expession object.
9458
+ //
9459
+ // 2. The expression object that has an evaluate() method to evaluate the
9460
+ // XPath expression it represents. (It is actually a hierarchy of
9461
+ // objects that resembles the parse tree, but an application will call
9462
+ // evaluate() only on the top node of this hierarchy.)
9463
+ //
9464
+ // 3. The context object that is passed as an argument to the evaluate()
9465
+ // method, which represents the DOM context in which the expression is
9466
+ // evaluated.
9467
+ //
9468
+ // 4. The value object that is returned from evaluate() and represents
9469
+ // values of the different types that are defined by XPath (number,
9470
+ // string, boolean, and node-set), and allows to convert between them.
9471
+ //
9472
+ // These parts are near the top of the file, the functions and data
9473
+ // that are used internally follow after them.
9474
+ //
9475
+ //
9476
+ // Author: Steffen Meschkat <mesch@google.com>
8829
9477
 
8830
- var XSLTProcessor = function() {
8831
- this.__stylesheet__ = null;
8832
- };
8833
- __extend__(XSLTProcessor.prototype, {
8834
- clearParameters: function(){
8835
- //TODO
8836
- },
8837
- getParameter: function(nsuri, name){
8838
- //TODO
8839
- },
8840
- importStyleSheet: function(stylesheet){
8841
- this.__stylesheet__ = stylesheet;
8842
- },
8843
- removeParameter: function(nsuri, name){
8844
- //TODO
8845
- },
8846
- reset: function(){
8847
- //TODO
8848
- },
8849
- setParameter: function(nsuri, name, value){
8850
- //TODO
8851
- },
8852
- transformToDocument: function(sourceNode){
8853
- return xsltProcess(sourceNode, this.__stylesheet__);
8854
- },
8855
- transformToFragment: function(sourceNode, ownerDocument){
8856
- return xsltProcess(sourceNode, this.__stylesheet__).childNodes;
9478
+
9479
+ // The entry point for the parser.
9480
+ //
9481
+ // @param expr a string that contains an XPath expression.
9482
+ // @return an expression object that can be evaluated with an
9483
+ // expression context.
9484
+
9485
+ function xpathParse(expr) {
9486
+ xpathLog('parse ' + expr);
9487
+ xpathParseInit();
9488
+
9489
+ var cached = xpathCacheLookup(expr);
9490
+ if (cached) {
9491
+ xpathLog(' ... cached');
9492
+ return cached;
9493
+ }
9494
+
9495
+ // Optimize for a few common cases: simple attribute node tests
9496
+ // (@id), simple element node tests (page), variable references
9497
+ // ($address), numbers (4), multi-step path expressions where each
9498
+ // step is a plain element node test
9499
+ // (page/overlay/locations/location).
9500
+
9501
+ if (expr.match(/^(\$|@)?\w+$/i)) {
9502
+ var ret = makeSimpleExpr(expr);
9503
+ xpathParseCache[expr] = ret;
9504
+ xpathLog(' ... simple');
9505
+ return ret;
9506
+ }
9507
+
9508
+ if (expr.match(/^\w+(\/\w+)*$/i)) {
9509
+ var ret = makeSimpleExpr2(expr);
9510
+ xpathParseCache[expr] = ret;
9511
+ xpathLog(' ... simple 2');
9512
+ return ret;
9513
+ }
9514
+
9515
+ var cachekey = expr; // expr is modified during parse
9516
+
9517
+ var stack = [];
9518
+ var ahead = null;
9519
+ var previous = null;
9520
+ var done = false;
9521
+
9522
+ var parse_count = 0;
9523
+ var lexer_count = 0;
9524
+ var reduce_count = 0;
9525
+
9526
+ while (!done) {
9527
+ parse_count++;
9528
+ expr = expr.replace(/^\s*/, '');
9529
+ previous = ahead;
9530
+ ahead = null;
9531
+
9532
+ var rule = null;
9533
+ var match = '';
9534
+ for (var i = 0; i < xpathTokenRules.length; ++i) {
9535
+ var result = xpathTokenRules[i].re.exec(expr);
9536
+ lexer_count++;
9537
+ if (result && result.length > 0 && result[0].length > match.length) {
9538
+ rule = xpathTokenRules[i];
9539
+ match = result[0];
9540
+ break;
9541
+ }
8857
9542
  }
8858
- });$debug("Defining Event");
8859
- /*
8860
- * event.js
8861
- */
8862
- var Event = function(options){
8863
- options={};
8864
- __extend__(this,{
8865
- CAPTURING_PHASE : 1,
8866
- AT_TARGET : 2,
8867
- BUBBLING_PHASE : 3
8868
- });
8869
- $debug("Creating new Event");
8870
- var $bubbles = options.bubbles?options.bubbles:true,
8871
- $cancelable = options.cancelable?options.cancelable:true,
8872
- $currentTarget = options.currentTarget?options.currentTarget:null,
8873
- $eventPhase = options.eventPhase?options.eventPhase:Event.CAPTURING_PHASE,
8874
- $target = options.target?options.target:null,
8875
- $timestamp = options.timestamp?options.timestamp:new Date().getTime().toString(),
8876
- $type = options.type?options.type:"";
8877
- return __extend__(this,{
8878
- get bubbles(){return $bubbles;},
8879
- get cancelable(){return $cancelable;},
8880
- get currentTarget(){return $currentTarget;},
8881
- get eventPhase(){return $eventPhase;},
9543
+
9544
+ // Special case: allow operator keywords to be element and
9545
+ // variable names.
9546
+
9547
+ // NOTE(mesch): The parser resolves conflicts by looking ahead,
9548
+ // and this is the only case where we look back to
9549
+ // disambiguate. So this is indeed something different, and
9550
+ // looking back is usually done in the lexer (via states in the
9551
+ // general case, called "start conditions" in flex(1)). Also,the
9552
+ // conflict resolution in the parser is not as robust as it could
9553
+ // be, so I'd like to keep as much off the parser as possible (all
9554
+ // these precedence values should be computed from the grammar
9555
+ // rules and possibly associativity declarations, as in bison(1),
9556
+ // and not explicitly set.
9557
+
9558
+ if (rule &&
9559
+ (rule == TOK_DIV ||
9560
+ rule == TOK_MOD ||
9561
+ rule == TOK_AND ||
9562
+ rule == TOK_OR) &&
9563
+ (!previous ||
9564
+ previous.tag == TOK_AT ||
9565
+ previous.tag == TOK_DSLASH ||
9566
+ previous.tag == TOK_SLASH ||
9567
+ previous.tag == TOK_AXIS ||
9568
+ previous.tag == TOK_DOLLAR)) {
9569
+ rule = TOK_QNAME;
9570
+ }
9571
+
9572
+ if (rule) {
9573
+ expr = expr.substr(match.length);
9574
+ xpathLog('token: ' + match + ' -- ' + rule.label);
9575
+ ahead = {
9576
+ tag: rule,
9577
+ match: match,
9578
+ prec: rule.prec ? rule.prec : 0, // || 0 is removed by the compiler
9579
+ expr: makeTokenExpr(match)
9580
+ };
9581
+
9582
+ } else {
9583
+ xpathLog('DONE');
9584
+ done = true;
9585
+ }
9586
+
9587
+ while (xpathReduce(stack, ahead)) {
9588
+ reduce_count++;
9589
+ xpathLog('stack: ' + stackToString(stack));
9590
+ }
9591
+ }
9592
+
9593
+ xpathLog('stack: ' + stackToString(stack));
9594
+
9595
+ // DGF any valid XPath should "reduce" to a single Expr token
9596
+ if (stack.length != 1) {
9597
+ throw 'XPath parse error ' + cachekey + ':\n' + stackToString(stack);
9598
+ }
9599
+
9600
+ var result = stack[0].expr;
9601
+ xpathParseCache[cachekey] = result;
9602
+
9603
+ xpathLog('XPath parse: ' + parse_count + ' / ' +
9604
+ lexer_count + ' / ' + reduce_count);
9605
+
9606
+ return result;
9607
+ }
9608
+
9609
+ var xpathParseCache = {};
9610
+
9611
+ function xpathCacheLookup(expr) {
9612
+ return xpathParseCache[expr];
9613
+ }
9614
+
9615
+ /*DGF xpathReduce is where the magic happens in this parser.
9616
+ Skim down to the bottom of this file to find the table of
9617
+ grammatical rules and precedence numbers, "The productions of the grammar".
9618
+
9619
+ The idea here
9620
+ is that we want to take a stack of tokens and apply
9621
+ grammatical rules to them, "reducing" them to higher-level
9622
+ tokens. Ultimately, any valid XPath should reduce to exactly one
9623
+ "Expr" token.
9624
+
9625
+ Reduce too early or too late and you'll have two tokens that can't reduce
9626
+ to single Expr. For example, you may hastily reduce a qname that
9627
+ should name a function, incorrectly treating it as a tag name.
9628
+ Or you may reduce too late, accidentally reducing the last part of the
9629
+ XPath into a top-level "Expr" that won't reduce with earlier parts of
9630
+ the XPath.
9631
+
9632
+ A "cand" is a grammatical rule candidate, with a given precedence
9633
+ number. "ahead" is the upcoming token, which also has a precedence
9634
+ number. If the token has a higher precedence number than
9635
+ the rule candidate, we'll "shift" the token onto the token stack,
9636
+ instead of immediately applying the rule candidate.
9637
+
9638
+ Some tokens have left associativity, in which case we shift when they
9639
+ have LOWER precedence than the candidate.
9640
+ */
9641
+ function xpathReduce(stack, ahead) {
9642
+ var cand = null;
9643
+
9644
+ if (stack.length > 0) {
9645
+ var top = stack[stack.length-1];
9646
+ var ruleset = xpathRules[top.tag.key];
9647
+
9648
+ if (ruleset) {
9649
+ for (var i = 0; i < ruleset.length; ++i) {
9650
+ var rule = ruleset[i];
9651
+ var match = xpathMatchStack(stack, rule[1]);
9652
+ if (match.length) {
9653
+ cand = {
9654
+ tag: rule[0],
9655
+ rule: rule,
9656
+ match: match
9657
+ };
9658
+ cand.prec = xpathGrammarPrecedence(cand);
9659
+ break;
9660
+ }
9661
+ }
9662
+ }
9663
+ }
9664
+
9665
+ var ret;
9666
+ if (cand && (!ahead || cand.prec > ahead.prec ||
9667
+ (ahead.tag.left && cand.prec >= ahead.prec))) {
9668
+ for (var i = 0; i < cand.match.matchlength; ++i) {
9669
+ stack.pop();
9670
+ }
9671
+
9672
+ xpathLog('reduce ' + cand.tag.label + ' ' + cand.prec +
9673
+ ' ahead ' + (ahead ? ahead.tag.label + ' ' + ahead.prec +
9674
+ (ahead.tag.left ? ' left' : '')
9675
+ : ' none '));
9676
+
9677
+ var matchexpr = mapExpr(cand.match, function(m) { return m.expr; });
9678
+ xpathLog('going to apply ' + cand.rule[3].toString());
9679
+ cand.expr = cand.rule[3].apply(null, matchexpr);
9680
+
9681
+ stack.push(cand);
9682
+ ret = true;
9683
+
9684
+ } else {
9685
+ if (ahead) {
9686
+ xpathLog('shift ' + ahead.tag.label + ' ' + ahead.prec +
9687
+ (ahead.tag.left ? ' left' : '') +
9688
+ ' over ' + (cand ? cand.tag.label + ' ' +
9689
+ cand.prec : ' none'));
9690
+ stack.push(ahead);
9691
+ }
9692
+ ret = false;
9693
+ }
9694
+ return ret;
9695
+ }
9696
+
9697
+ function xpathMatchStack(stack, pattern) {
9698
+
9699
+ // NOTE(mesch): The stack matches for variable cardinality are
9700
+ // greedy but don't do backtracking. This would be an issue only
9701
+ // with rules of the form A* A, i.e. with an element with variable
9702
+ // cardinality followed by the same element. Since that doesn't
9703
+ // occur in the grammar at hand, all matches on the stack are
9704
+ // unambiguous.
9705
+
9706
+ var S = stack.length;
9707
+ var P = pattern.length;
9708
+ var p, s;
9709
+ var match = [];
9710
+ match.matchlength = 0;
9711
+ var ds = 0;
9712
+ for (p = P - 1, s = S - 1; p >= 0 && s >= 0; --p, s -= ds) {
9713
+ ds = 0;
9714
+ var qmatch = [];
9715
+ if (pattern[p] == Q_MM) {
9716
+ p -= 1;
9717
+ match.push(qmatch);
9718
+ while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {
9719
+ qmatch.push(stack[s - ds]);
9720
+ ds += 1;
9721
+ match.matchlength += 1;
9722
+ }
9723
+
9724
+ } else if (pattern[p] == Q_01) {
9725
+ p -= 1;
9726
+ match.push(qmatch);
9727
+ while (s - ds >= 0 && ds < 2 && stack[s - ds].tag == pattern[p]) {
9728
+ qmatch.push(stack[s - ds]);
9729
+ ds += 1;
9730
+ match.matchlength += 1;
9731
+ }
9732
+
9733
+ } else if (pattern[p] == Q_1M) {
9734
+ p -= 1;
9735
+ match.push(qmatch);
9736
+ if (stack[s].tag == pattern[p]) {
9737
+ while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {
9738
+ qmatch.push(stack[s - ds]);
9739
+ ds += 1;
9740
+ match.matchlength += 1;
9741
+ }
9742
+ } else {
9743
+ return [];
9744
+ }
9745
+
9746
+ } else if (stack[s].tag == pattern[p]) {
9747
+ match.push(stack[s]);
9748
+ ds += 1;
9749
+ match.matchlength += 1;
9750
+
9751
+ } else {
9752
+ return [];
9753
+ }
9754
+
9755
+ reverseInplace(qmatch);
9756
+ qmatch.expr = mapExpr(qmatch, function(m) { return m.expr; });
9757
+ }
9758
+
9759
+ reverseInplace(match);
9760
+
9761
+ if (p == -1) {
9762
+ return match;
9763
+
9764
+ } else {
9765
+ return [];
9766
+ }
9767
+ }
9768
+
9769
+ function xpathTokenPrecedence(tag) {
9770
+ return tag.prec || 2;
9771
+ }
9772
+
9773
+ function xpathGrammarPrecedence(frame) {
9774
+ var ret = 0;
9775
+
9776
+ if (frame.rule) { /* normal reduce */
9777
+ if (frame.rule.length >= 3 && frame.rule[2] >= 0) {
9778
+ ret = frame.rule[2];
9779
+
9780
+ } else {
9781
+ for (var i = 0; i < frame.rule[1].length; ++i) {
9782
+ var p = xpathTokenPrecedence(frame.rule[1][i]);
9783
+ ret = Math.max(ret, p);
9784
+ }
9785
+ }
9786
+ } else if (frame.tag) { /* TOKEN match */
9787
+ ret = xpathTokenPrecedence(frame.tag);
9788
+
9789
+ } else if (frame.length) { /* Q_ match */
9790
+ for (var j = 0; j < frame.length; ++j) {
9791
+ var p = xpathGrammarPrecedence(frame[j]);
9792
+ ret = Math.max(ret, p);
9793
+ }
9794
+ }
9795
+
9796
+ return ret;
9797
+ }
9798
+
9799
+ function stackToString(stack) {
9800
+ var ret = '';
9801
+ for (var i = 0; i < stack.length; ++i) {
9802
+ if (ret) {
9803
+ ret += '\n';
9804
+ }
9805
+ ret += stack[i].tag.label;
9806
+ }
9807
+ return ret;
9808
+ }
9809
+
9810
+
9811
+ // XPath expression evaluation context. An XPath context consists of a
9812
+ // DOM node, a list of DOM nodes that contains this node, a number
9813
+ // that represents the position of the single node in the list, and a
9814
+ // current set of variable bindings. (See XPath spec.)
9815
+ //
9816
+ // The interface of the expression context:
9817
+ //
9818
+ // Constructor -- gets the node, its position, the node set it
9819
+ // belongs to, and a parent context as arguments. The parent context
9820
+ // is used to implement scoping rules for variables: if a variable
9821
+ // is not found in the current context, it is looked for in the
9822
+ // parent context, recursively. Except for node, all arguments have
9823
+ // default values: default position is 0, default node set is the
9824
+ // set that contains only the node, and the default parent is null.
9825
+ //
9826
+ // Notice that position starts at 0 at the outside interface;
9827
+ // inside XPath expressions this shows up as position()=1.
9828
+ //
9829
+ // clone() -- creates a new context with the current context as
9830
+ // parent. If passed as argument to clone(), the new context has a
9831
+ // different node, position, or node set. What is not passed is
9832
+ // inherited from the cloned context.
9833
+ //
9834
+ // setVariable(name, expr) -- binds given XPath expression to the
9835
+ // name.
9836
+ //
9837
+ // getVariable(name) -- what the name says.
9838
+ //
9839
+ // setNode(position) -- sets the context to the node at the given
9840
+ // position. Needed to implement scoping rules for variables in
9841
+ // XPath. (A variable is visible to all subsequent siblings, not
9842
+ // only to its children.)
9843
+ //
9844
+ // set/isCaseInsensitive -- specifies whether node name tests should
9845
+ // be case sensitive. If you're executing xpaths against a regular
9846
+ // HTML DOM, you probably don't want case-sensitivity, because
9847
+ // browsers tend to disagree about whether elements & attributes
9848
+ // should be upper/lower case. If you're running xpaths in an
9849
+ // XSLT instance, you probably DO want case sensitivity, as per the
9850
+ // XSL spec.
9851
+ //
9852
+ // set/isReturnOnFirstMatch -- whether XPath evaluation should quit as soon
9853
+ // as a result is found. This is an optimization that might make sense if you
9854
+ // only care about the first result.
9855
+ //
9856
+ // set/isIgnoreNonElementNodesForNTA -- whether to ignore non-element nodes
9857
+ // when evaluating the "node()" any node test. While technically this is
9858
+ // contrary to the XPath spec, practically it can enhance performance
9859
+ // significantly, and makes sense if you a) use "node()" when you mean "*",
9860
+ // and b) use "//" when you mean "/descendant::*/".
9861
+
9862
+ function ExprContext(node, opt_position, opt_nodelist, opt_parent,
9863
+ opt_caseInsensitive, opt_ignoreAttributesWithoutValue,
9864
+ opt_returnOnFirstMatch, opt_ignoreNonElementNodesForNTA)
9865
+ {
9866
+ this.node = node;
9867
+ this.position = opt_position || 0;
9868
+ this.nodelist = opt_nodelist || [ node ];
9869
+ this.variables = {};
9870
+ this.parent = opt_parent || null;
9871
+ this.caseInsensitive = opt_caseInsensitive || false;
9872
+ this.ignoreAttributesWithoutValue = opt_ignoreAttributesWithoutValue || false;
9873
+ this.returnOnFirstMatch = opt_returnOnFirstMatch || false;
9874
+ this.ignoreNonElementNodesForNTA = opt_ignoreNonElementNodesForNTA || false;
9875
+ if (opt_parent) {
9876
+ this.root = opt_parent.root;
9877
+ } else if (this.node.nodeType == DOMNode.DOCUMENT_NODE) {
9878
+ // NOTE(mesch): DOM Spec stipulates that the ownerDocument of a
9879
+ // document is null. Our root, however is the document that we are
9880
+ // processing, so the initial context is created from its document
9881
+ // node, which case we must handle here explcitly.
9882
+ this.root = node;
9883
+ } else {
9884
+ this.root = node.ownerDocument;
9885
+ }
9886
+ }
9887
+
9888
+ ExprContext.prototype.clone = function(opt_node, opt_position, opt_nodelist) {
9889
+ return new ExprContext(
9890
+ opt_node || this.node,
9891
+ typeof opt_position != 'undefined' ? opt_position : this.position,
9892
+ opt_nodelist || this.nodelist, this, this.caseInsensitive,
9893
+ this.ignoreAttributesWithoutValue, this.returnOnFirstMatch,
9894
+ this.ignoreNonElementNodesForNTA);
9895
+ };
9896
+
9897
+ ExprContext.prototype.setVariable = function(name, value) {
9898
+ if (value instanceof StringValue || value instanceof BooleanValue ||
9899
+ value instanceof NumberValue || value instanceof NodeSetValue) {
9900
+ this.variables[name] = value;
9901
+ return;
9902
+ }
9903
+ if ('true' === value) {
9904
+ this.variables[name] = new BooleanValue(true);
9905
+ } else if ('false' === value) {
9906
+ this.variables[name] = new BooleanValue(false);
9907
+ } else if (TOK_NUMBER.re.test(value)) {
9908
+ this.variables[name] = new NumberValue(value);
9909
+ } else {
9910
+ // DGF What if it's null?
9911
+ this.variables[name] = new StringValue(value);
9912
+ }
9913
+ };
9914
+
9915
+ ExprContext.prototype.getVariable = function(name) {
9916
+ if (typeof this.variables[name] != 'undefined') {
9917
+ return this.variables[name];
9918
+
9919
+ } else if (this.parent) {
9920
+ return this.parent.getVariable(name);
9921
+
9922
+ } else {
9923
+ return null;
9924
+ }
9925
+ };
9926
+
9927
+ ExprContext.prototype.setNode = function(position) {
9928
+ this.node = this.nodelist[position];
9929
+ this.position = position;
9930
+ };
9931
+
9932
+ ExprContext.prototype.contextSize = function() {
9933
+ return this.nodelist.length;
9934
+ };
9935
+
9936
+ ExprContext.prototype.isCaseInsensitive = function() {
9937
+ return this.caseInsensitive;
9938
+ };
9939
+
9940
+ ExprContext.prototype.setCaseInsensitive = function(caseInsensitive) {
9941
+ return this.caseInsensitive = caseInsensitive;
9942
+ };
9943
+
9944
+ ExprContext.prototype.isIgnoreAttributesWithoutValue = function() {
9945
+ return this.ignoreAttributesWithoutValue;
9946
+ };
9947
+
9948
+ ExprContext.prototype.setIgnoreAttributesWithoutValue = function(ignore) {
9949
+ return this.ignoreAttributesWithoutValue = ignore;
9950
+ };
9951
+
9952
+ ExprContext.prototype.isReturnOnFirstMatch = function() {
9953
+ return this.returnOnFirstMatch;
9954
+ };
9955
+
9956
+ ExprContext.prototype.setReturnOnFirstMatch = function(returnOnFirstMatch) {
9957
+ return this.returnOnFirstMatch = returnOnFirstMatch;
9958
+ };
9959
+
9960
+ ExprContext.prototype.isIgnoreNonElementNodesForNTA = function() {
9961
+ return this.ignoreNonElementNodesForNTA;
9962
+ };
9963
+
9964
+ ExprContext.prototype.setIgnoreNonElementNodesForNTA = function(ignoreNonElementNodesForNTA) {
9965
+ return this.ignoreNonElementNodesForNTA = ignoreNonElementNodesForNTA;
9966
+ };
9967
+
9968
+ // XPath expression values. They are what XPath expressions evaluate
9969
+ // to. Strangely, the different value types are not specified in the
9970
+ // XPath syntax, but only in the semantics, so they don't show up as
9971
+ // nonterminals in the grammar. Yet, some expressions are required to
9972
+ // evaluate to particular types, and not every type can be coerced
9973
+ // into every other type. Although the types of XPath values are
9974
+ // similar to the types present in JavaScript, the type coercion rules
9975
+ // are a bit peculiar, so we explicitly model XPath types instead of
9976
+ // mapping them onto JavaScript types. (See XPath spec.)
9977
+ //
9978
+ // The four types are:
9979
+ //
9980
+ // StringValue
9981
+ //
9982
+ // NumberValue
9983
+ //
9984
+ // BooleanValue
9985
+ //
9986
+ // NodeSetValue
9987
+ //
9988
+ // The common interface of the value classes consists of methods that
9989
+ // implement the XPath type coercion rules:
9990
+ //
9991
+ // stringValue() -- returns the value as a JavaScript String,
9992
+ //
9993
+ // numberValue() -- returns the value as a JavaScript Number,
9994
+ //
9995
+ // booleanValue() -- returns the value as a JavaScript Boolean,
9996
+ //
9997
+ // nodeSetValue() -- returns the value as a JavaScript Array of DOM
9998
+ // Node objects.
9999
+ //
10000
+
10001
+ function StringValue(value) {
10002
+ this.value = value;
10003
+ this.type = 'string';
10004
+ }
10005
+
10006
+ StringValue.prototype.stringValue = function() {
10007
+ return this.value;
10008
+ }
10009
+
10010
+ StringValue.prototype.booleanValue = function() {
10011
+ return this.value.length > 0;
10012
+ }
10013
+
10014
+ StringValue.prototype.numberValue = function() {
10015
+ return this.value - 0;
10016
+ }
10017
+
10018
+ StringValue.prototype.nodeSetValue = function() {
10019
+ throw this;
10020
+ }
10021
+
10022
+ function BooleanValue(value) {
10023
+ this.value = value;
10024
+ this.type = 'boolean';
10025
+ }
10026
+
10027
+ BooleanValue.prototype.stringValue = function() {
10028
+ return '' + this.value;
10029
+ }
10030
+
10031
+ BooleanValue.prototype.booleanValue = function() {
10032
+ return this.value;
10033
+ }
10034
+
10035
+ BooleanValue.prototype.numberValue = function() {
10036
+ return this.value ? 1 : 0;
10037
+ }
10038
+
10039
+ BooleanValue.prototype.nodeSetValue = function() {
10040
+ throw this;
10041
+ }
10042
+
10043
+ function NumberValue(value) {
10044
+ this.value = value;
10045
+ this.type = 'number';
10046
+ }
10047
+
10048
+ NumberValue.prototype.stringValue = function() {
10049
+ return '' + this.value;
10050
+ }
10051
+
10052
+ NumberValue.prototype.booleanValue = function() {
10053
+ return !!this.value;
10054
+ }
10055
+
10056
+ NumberValue.prototype.numberValue = function() {
10057
+ return this.value - 0;
10058
+ }
10059
+
10060
+ NumberValue.prototype.nodeSetValue = function() {
10061
+ throw this;
10062
+ }
10063
+
10064
+ function NodeSetValue(value) {
10065
+ this.value = value;
10066
+ this.type = 'node-set';
10067
+ }
10068
+
10069
+ NodeSetValue.prototype.stringValue = function() {
10070
+ if (this.value.length == 0) {
10071
+ return '';
10072
+ } else {
10073
+ return xmlValue(this.value[0]);
10074
+ }
10075
+ }
10076
+
10077
+ NodeSetValue.prototype.booleanValue = function() {
10078
+ return this.value.length > 0;
10079
+ }
10080
+
10081
+ NodeSetValue.prototype.numberValue = function() {
10082
+ return this.stringValue() - 0;
10083
+ }
10084
+
10085
+ NodeSetValue.prototype.nodeSetValue = function() {
10086
+ return this.value;
10087
+ };
10088
+
10089
+ // XPath expressions. They are used as nodes in the parse tree and
10090
+ // possess an evaluate() method to compute an XPath value given an XPath
10091
+ // context. Expressions are returned from the parser. Teh set of
10092
+ // expression classes closely mirrors the set of non terminal symbols
10093
+ // in the grammar. Every non trivial nonterminal symbol has a
10094
+ // corresponding expression class.
10095
+ //
10096
+ // The common expression interface consists of the following methods:
10097
+ //
10098
+ // evaluate(context) -- evaluates the expression, returns a value.
10099
+ //
10100
+ // toString() -- returns the XPath text representation of the
10101
+ // expression (defined in xsltdebug.js).
10102
+ //
10103
+ // parseTree(indent) -- returns a parse tree representation of the
10104
+ // expression (defined in xsltdebug.js).
10105
+
10106
+ function TokenExpr(m) {
10107
+ this.value = m;
10108
+ }
10109
+
10110
+ TokenExpr.prototype.evaluate = function() {
10111
+ return new StringValue(this.value);
10112
+ };
10113
+
10114
+ function LocationExpr() {
10115
+ this.absolute = false;
10116
+ this.steps = [];
10117
+ }
10118
+
10119
+ LocationExpr.prototype.appendStep = function(s) {
10120
+ var combinedStep = this._combineSteps(this.steps[this.steps.length-1], s);
10121
+ if (combinedStep) {
10122
+ this.steps[this.steps.length-1] = combinedStep;
10123
+ } else {
10124
+ this.steps.push(s);
10125
+ }
10126
+ }
10127
+
10128
+ LocationExpr.prototype.prependStep = function(s) {
10129
+ var combinedStep = this._combineSteps(s, this.steps[0]);
10130
+ if (combinedStep) {
10131
+ this.steps[0] = combinedStep;
10132
+ } else {
10133
+ this.steps.unshift(s);
10134
+ }
10135
+ };
10136
+
10137
+ // DGF try to combine two steps into one step (perf enhancement)
10138
+ LocationExpr.prototype._combineSteps = function(prevStep, nextStep) {
10139
+ if (!prevStep) return null;
10140
+ if (!nextStep) return null;
10141
+ var hasPredicates = (prevStep.predicates && prevStep.predicates.length > 0);
10142
+ if (prevStep.nodetest instanceof NodeTestAny && !hasPredicates) {
10143
+ // maybe suitable to be combined
10144
+ if (prevStep.axis == xpathAxis.DESCENDANT_OR_SELF) {
10145
+ if (nextStep.axis == xpathAxis.CHILD) {
10146
+ // HBC - commenting out, because this is not a valid reduction
10147
+ //nextStep.axis = xpathAxis.DESCENDANT;
10148
+ //return nextStep;
10149
+ } else if (nextStep.axis == xpathAxis.SELF) {
10150
+ nextStep.axis = xpathAxis.DESCENDANT_OR_SELF;
10151
+ return nextStep;
10152
+ }
10153
+ } else if (prevStep.axis == xpathAxis.DESCENDANT) {
10154
+ if (nextStep.axis == xpathAxis.SELF) {
10155
+ nextStep.axis = xpathAxis.DESCENDANT;
10156
+ return nextStep;
10157
+ }
10158
+ }
10159
+ }
10160
+ return null;
10161
+ }
10162
+
10163
+ LocationExpr.prototype.evaluate = function(ctx) {
10164
+ var start;
10165
+ if (this.absolute) {
10166
+ start = ctx.root;
10167
+
10168
+ } else {
10169
+ start = ctx.node;
10170
+ }
10171
+
10172
+ var nodes = [];
10173
+ xPathStep(nodes, this.steps, 0, start, ctx);
10174
+ return new NodeSetValue(nodes);
10175
+ };
10176
+
10177
+ function xPathStep(nodes, steps, step, input, ctx) {
10178
+ var s = steps[step];
10179
+ var ctx2 = ctx.clone(input);
10180
+
10181
+ if (ctx.returnOnFirstMatch && !s.hasPositionalPredicate) {
10182
+ var nodelist = s.evaluate(ctx2).nodeSetValue();
10183
+ // the predicates were not processed in the last evaluate(), so that we can
10184
+ // process them here with the returnOnFirstMatch optimization. We do a
10185
+ // depth-first grab at any nodes that pass the predicate tests. There is no
10186
+ // way to optimize when predicates contain positional selectors, including
10187
+ // indexes or uses of the last() or position() functions, because they
10188
+ // typically require the entire nodelist for context. Process without
10189
+ // optimization if we encounter such selectors.
10190
+ var nLength = nodelist.length;
10191
+ var pLength = s.predicate.length;
10192
+ nodelistLoop:
10193
+ for (var i = 0; i < nLength; ++i) {
10194
+ var n = nodelist[i];
10195
+ for (var j = 0; j < pLength; ++j) {
10196
+ if (!s.predicate[j].evaluate(ctx.clone(n, i, nodelist)).booleanValue()) {
10197
+ continue nodelistLoop;
10198
+ }
10199
+ }
10200
+ // n survived the predicate tests!
10201
+ if (step == steps.length - 1) {
10202
+ nodes.push(n);
10203
+ }
10204
+ else {
10205
+ xPathStep(nodes, steps, step + 1, n, ctx);
10206
+ }
10207
+ if (nodes.length > 0) {
10208
+ break;
10209
+ }
10210
+ }
10211
+ }
10212
+ else {
10213
+ // set returnOnFirstMatch to false for the cloned ExprContext, because
10214
+ // behavior in StepExpr.prototype.evaluate is driven off its value. Note
10215
+ // that the original context may still have true for this value.
10216
+ ctx2.returnOnFirstMatch = false;
10217
+ var nodelist = s.evaluate(ctx2).nodeSetValue();
10218
+ for (var i = 0; i < nodelist.length; ++i) {
10219
+ if (step == steps.length - 1) {
10220
+ nodes.push(nodelist[i]);
10221
+ } else {
10222
+ xPathStep(nodes, steps, step + 1, nodelist[i], ctx);
10223
+ }
10224
+ }
10225
+ }
10226
+ }
10227
+
10228
+ function StepExpr(axis, nodetest, opt_predicate) {
10229
+ this.axis = axis;
10230
+ this.nodetest = nodetest;
10231
+ this.predicate = opt_predicate || [];
10232
+ this.hasPositionalPredicate = false;
10233
+ for (var i = 0; i < this.predicate.length; ++i) {
10234
+ if (predicateExprHasPositionalSelector(this.predicate[i].expr)) {
10235
+ this.hasPositionalPredicate = true;
10236
+ break;
10237
+ }
10238
+ }
10239
+ }
10240
+
10241
+ StepExpr.prototype.appendPredicate = function(p) {
10242
+ this.predicate.push(p);
10243
+ if (!this.hasPositionalPredicate) {
10244
+ this.hasPositionalPredicate = predicateExprHasPositionalSelector(p.expr);
10245
+ }
10246
+ }
10247
+
10248
+ StepExpr.prototype.evaluate = function(ctx) {
10249
+ var input = ctx.node;
10250
+ var nodelist = [];
10251
+ var skipNodeTest = false;
10252
+
10253
+ if (this.nodetest instanceof NodeTestAny) {
10254
+ skipNodeTest = true;
10255
+ }
10256
+
10257
+ // NOTE(mesch): When this was a switch() statement, it didn't work
10258
+ // in Safari/2.0. Not sure why though; it resulted in the JavaScript
10259
+ // console output "undefined" (without any line number or so).
10260
+
10261
+ if (this.axis == xpathAxis.ANCESTOR_OR_SELF) {
10262
+ nodelist.push(input);
10263
+ for (var n = input.parentNode; n; n = n.parentNode) {
10264
+ nodelist.push(n);
10265
+ }
10266
+
10267
+ } else if (this.axis == xpathAxis.ANCESTOR) {
10268
+ for (var n = input.parentNode; n; n = n.parentNode) {
10269
+ nodelist.push(n);
10270
+ }
10271
+
10272
+ } else if (this.axis == xpathAxis.ATTRIBUTE) {
10273
+ if (this.nodetest.name != undefined) {
10274
+ // single-attribute step
10275
+ if (input.attributes) {
10276
+ if (input.attributes instanceof Array) {
10277
+ // probably evaluating on document created by xmlParse()
10278
+ copyArray(nodelist, input.attributes);
10279
+ }
10280
+ else {
10281
+ if (this.nodetest.name == 'style') {
10282
+ var value = input.getAttribute('style');
10283
+ if (value && typeof(value) != 'string') {
10284
+ // this is the case where indexing into the attributes array
10285
+ // doesn't give us the attribute node in IE - we create our own
10286
+ // node instead
10287
+ nodelist.push(XNode.create(DOMNode.ATTRIBUTE_NODE, 'style',
10288
+ value.cssText, document));
10289
+ }
10290
+ else {
10291
+ nodelist.push(input.attributes[this.nodetest.name]);
10292
+ }
10293
+ }
10294
+ else {
10295
+ nodelist.push(input.attributes[this.nodetest.name]);
10296
+ }
10297
+ }
10298
+ }
10299
+ }
10300
+ else {
10301
+ // all-attributes step
10302
+ if (ctx.ignoreAttributesWithoutValue) {
10303
+ copyArrayIgnoringAttributesWithoutValue(nodelist, input.attributes);
10304
+ }
10305
+ else {
10306
+ copyArray(nodelist, input.attributes);
10307
+ }
10308
+ }
10309
+
10310
+ } else if (this.axis == xpathAxis.CHILD) {
10311
+ copyArray(nodelist, input.childNodes);
10312
+
10313
+ } else if (this.axis == xpathAxis.DESCENDANT_OR_SELF) {
10314
+ if (this.nodetest.evaluate(ctx).booleanValue()) {
10315
+ nodelist.push(input);
10316
+ }
10317
+ var tagName = xpathExtractTagNameFromNodeTest(this.nodetest, ctx.ignoreNonElementNodesForNTA);
10318
+ xpathCollectDescendants(nodelist, input, tagName);
10319
+ if (tagName) skipNodeTest = true;
10320
+
10321
+ } else if (this.axis == xpathAxis.DESCENDANT) {
10322
+ var tagName = xpathExtractTagNameFromNodeTest(this.nodetest, ctx.ignoreNonElementNodesForNTA);
10323
+ xpathCollectDescendants(nodelist, input, tagName);
10324
+ if (tagName) skipNodeTest = true;
10325
+
10326
+ } else if (this.axis == xpathAxis.FOLLOWING) {
10327
+ for (var n = input; n; n = n.parentNode) {
10328
+ for (var nn = n.nextSibling; nn; nn = nn.nextSibling) {
10329
+ nodelist.push(nn);
10330
+ xpathCollectDescendants(nodelist, nn);
10331
+ }
10332
+ }
10333
+
10334
+ } else if (this.axis == xpathAxis.FOLLOWING_SIBLING) {
10335
+ for (var n = input.nextSibling; n; n = n.nextSibling) {
10336
+ nodelist.push(n);
10337
+ }
10338
+
10339
+ } else if (this.axis == xpathAxis.NAMESPACE) {
10340
+ alert('not implemented: axis namespace');
10341
+
10342
+ } else if (this.axis == xpathAxis.PARENT) {
10343
+ if (input.parentNode) {
10344
+ nodelist.push(input.parentNode);
10345
+ }
10346
+
10347
+ } else if (this.axis == xpathAxis.PRECEDING) {
10348
+ for (var n = input; n; n = n.parentNode) {
10349
+ for (var nn = n.previousSibling; nn; nn = nn.previousSibling) {
10350
+ nodelist.push(nn);
10351
+ xpathCollectDescendantsReverse(nodelist, nn);
10352
+ }
10353
+ }
10354
+
10355
+ } else if (this.axis == xpathAxis.PRECEDING_SIBLING) {
10356
+ for (var n = input.previousSibling; n; n = n.previousSibling) {
10357
+ nodelist.push(n);
10358
+ }
10359
+
10360
+ } else if (this.axis == xpathAxis.SELF) {
10361
+ nodelist.push(input);
10362
+
10363
+ } else {
10364
+ throw 'ERROR -- NO SUCH AXIS: ' + this.axis;
10365
+ }
10366
+
10367
+ if (!skipNodeTest) {
10368
+ // process node test
10369
+ var nodelist0 = nodelist;
10370
+ nodelist = [];
10371
+ for (var i = 0; i < nodelist0.length; ++i) {
10372
+ var n = nodelist0[i];
10373
+ if (this.nodetest.evaluate(ctx.clone(n, i, nodelist0)).booleanValue()) {
10374
+ nodelist.push(n);
10375
+ }
10376
+ }
10377
+ }
10378
+
10379
+ // process predicates
10380
+ if (!ctx.returnOnFirstMatch) {
10381
+ for (var i = 0; i < this.predicate.length; ++i) {
10382
+ var nodelist0 = nodelist;
10383
+ nodelist = [];
10384
+ for (var ii = 0; ii < nodelist0.length; ++ii) {
10385
+ var n = nodelist0[ii];
10386
+ if (this.predicate[i].evaluate(ctx.clone(n, ii, nodelist0)).booleanValue()) {
10387
+ nodelist.push(n);
10388
+ }
10389
+ }
10390
+ }
10391
+ }
10392
+
10393
+ return new NodeSetValue(nodelist);
10394
+ };
10395
+
10396
+ function NodeTestAny() {
10397
+ this.value = new BooleanValue(true);
10398
+ }
10399
+
10400
+ NodeTestAny.prototype.evaluate = function(ctx) {
10401
+ return this.value;
10402
+ };
10403
+
10404
+ function NodeTestElementOrAttribute() {}
10405
+
10406
+ NodeTestElementOrAttribute.prototype.evaluate = function(ctx) {
10407
+ return new BooleanValue(
10408
+ ctx.node.nodeType == DOMNode.ELEMENT_NODE ||
10409
+ ctx.node.nodeType == DOMNode.ATTRIBUTE_NODE);
10410
+ }
10411
+
10412
+ function NodeTestText() {}
10413
+
10414
+ NodeTestText.prototype.evaluate = function(ctx) {
10415
+ return new BooleanValue(ctx.node.nodeType == DOMNode.TEXT_NODE);
10416
+ }
10417
+
10418
+ function NodeTestComment() {}
10419
+
10420
+ NodeTestComment.prototype.evaluate = function(ctx) {
10421
+ return new BooleanValue(ctx.node.nodeType == DOMNode.COMMENT_NODE);
10422
+ }
10423
+
10424
+ function NodeTestPI(target) {
10425
+ this.target = target;
10426
+ }
10427
+
10428
+ NodeTestPI.prototype.evaluate = function(ctx) {
10429
+ return new
10430
+ BooleanValue(ctx.node.nodeType == DOMNode.PROCESSING_INSTRUCTION_NODE &&
10431
+ (!this.target || ctx.node.nodeName == this.target));
10432
+ }
10433
+
10434
+ function NodeTestNC(nsprefix) {
10435
+ this.regex = new RegExp("^" + nsprefix + ":");
10436
+ this.nsprefix = nsprefix;
10437
+ }
10438
+
10439
+ NodeTestNC.prototype.evaluate = function(ctx) {
10440
+ var n = ctx.node;
10441
+ return new BooleanValue(this.regex.match(n.nodeName));
10442
+ }
10443
+
10444
+ function NodeTestName(name) {
10445
+ this.name = name;
10446
+ this.re = new RegExp('^' + name + '$', "i");
10447
+ }
10448
+
10449
+ NodeTestName.prototype.evaluate = function(ctx) {
10450
+ var n = ctx.node;
10451
+ if (ctx.caseInsensitive || n instanceof HTMLElement) {
10452
+ if (n.nodeName.length != this.name.length) return new BooleanValue(false);
10453
+ return new BooleanValue(this.re.test(n.nodeName));
10454
+ } else {
10455
+ return new BooleanValue(n.nodeName == this.name);
10456
+ }
10457
+ }
10458
+
10459
+ function PredicateExpr(expr) {
10460
+ this.expr = expr;
10461
+ }
10462
+
10463
+ PredicateExpr.prototype.evaluate = function(ctx) {
10464
+ var v = this.expr.evaluate(ctx);
10465
+ if (v.type == 'number') {
10466
+ // NOTE(mesch): Internally, position is represented starting with
10467
+ // 0, however in XPath position starts with 1. See functions
10468
+ // position() and last().
10469
+ return new BooleanValue(ctx.position == v.numberValue() - 1);
10470
+ } else {
10471
+ return new BooleanValue(v.booleanValue());
10472
+ }
10473
+ };
10474
+
10475
+ function FunctionCallExpr(name) {
10476
+ this.name = name;
10477
+ this.args = [];
10478
+ }
10479
+
10480
+ FunctionCallExpr.prototype.appendArg = function(arg) {
10481
+ this.args.push(arg);
10482
+ };
10483
+
10484
+ FunctionCallExpr.prototype.evaluate = function(ctx) {
10485
+ var fn = '' + this.name.value;
10486
+ var f = this.xpathfunctions[fn];
10487
+ if (f) {
10488
+ return f.call(this, ctx);
10489
+ } else {
10490
+ xpathLog('XPath NO SUCH FUNCTION ' + fn);
10491
+ return new BooleanValue(false);
10492
+ }
10493
+ };
10494
+
10495
+ FunctionCallExpr.prototype.xpathfunctions = {
10496
+ 'last': function(ctx) {
10497
+ assert(this.args.length == 0);
10498
+ // NOTE(mesch): XPath position starts at 1.
10499
+ return new NumberValue(ctx.contextSize());
10500
+ },
10501
+
10502
+ 'position': function(ctx) {
10503
+ assert(this.args.length == 0);
10504
+ // NOTE(mesch): XPath position starts at 1.
10505
+ return new NumberValue(ctx.position + 1);
10506
+ },
10507
+
10508
+ 'count': function(ctx) {
10509
+ assert(this.args.length == 1);
10510
+ var v = this.args[0].evaluate(ctx);
10511
+ return new NumberValue(v.nodeSetValue().length);
10512
+ },
10513
+
10514
+ 'id': function(ctx) {
10515
+ assert(this.args.length == 1);
10516
+ var e = this.args[0].evaluate(ctx);
10517
+ var ret = [];
10518
+ var ids;
10519
+ if (e.type == 'node-set') {
10520
+ ids = [];
10521
+ var en = e.nodeSetValue();
10522
+ for (var i = 0; i < en.length; ++i) {
10523
+ var v = xmlValue(en[i]).split(/\s+/);
10524
+ for (var ii = 0; ii < v.length; ++ii) {
10525
+ ids.push(v[ii]);
10526
+ }
10527
+ }
10528
+ } else {
10529
+ ids = e.stringValue().split(/\s+/);
10530
+ }
10531
+ var d = ctx.root;
10532
+ for (var i = 0; i < ids.length; ++i) {
10533
+ var n = d.getElementById(ids[i]);
10534
+ if (n) {
10535
+ ret.push(n);
10536
+ }
10537
+ }
10538
+ return new NodeSetValue(ret);
10539
+ },
10540
+
10541
+ 'local-name': function(ctx) {
10542
+ alert('not implmented yet: XPath function local-name()');
10543
+ },
10544
+
10545
+ 'namespace-uri': function(ctx) {
10546
+ alert('not implmented yet: XPath function namespace-uri()');
10547
+ },
10548
+
10549
+ 'name': function(ctx) {
10550
+ assert(this.args.length == 1 || this.args.length == 0);
10551
+ var n;
10552
+ if (this.args.length == 0) {
10553
+ n = [ ctx.node ];
10554
+ } else {
10555
+ n = this.args[0].evaluate(ctx).nodeSetValue();
10556
+ }
10557
+
10558
+ if (n.length == 0) {
10559
+ return new StringValue('');
10560
+ } else {
10561
+ return new StringValue(n[0].nodeName);
10562
+ }
10563
+ },
10564
+
10565
+ 'string': function(ctx) {
10566
+ assert(this.args.length == 1 || this.args.length == 0);
10567
+ if (this.args.length == 0) {
10568
+ return new StringValue(new NodeSetValue([ ctx.node ]).stringValue());
10569
+ } else {
10570
+ return new StringValue(this.args[0].evaluate(ctx).stringValue());
10571
+ }
10572
+ },
10573
+
10574
+ 'concat': function(ctx) {
10575
+ var ret = '';
10576
+ for (var i = 0; i < this.args.length; ++i) {
10577
+ ret += this.args[i].evaluate(ctx).stringValue();
10578
+ }
10579
+ return new StringValue(ret);
10580
+ },
10581
+
10582
+ 'starts-with': function(ctx) {
10583
+ assert(this.args.length == 2);
10584
+ var s0 = this.args[0].evaluate(ctx).stringValue();
10585
+ var s1 = this.args[1].evaluate(ctx).stringValue();
10586
+ return new BooleanValue(s0.indexOf(s1) == 0);
10587
+ },
10588
+
10589
+ 'ends-with': function(ctx) {
10590
+ assert(this.args.length == 2);
10591
+ var s0 = this.args[0].evaluate(ctx).stringValue();
10592
+ var s1 = this.args[1].evaluate(ctx).stringValue();
10593
+ var re = new RegExp(RegExp.escape(s1) + '$');
10594
+ return new BooleanValue(re.test(s0));
10595
+ },
10596
+
10597
+ 'contains': function(ctx) {
10598
+ assert(this.args.length == 2);
10599
+ var s0 = this.args[0].evaluate(ctx).stringValue();
10600
+ var s1 = this.args[1].evaluate(ctx).stringValue();
10601
+ return new BooleanValue(s0.indexOf(s1) != -1);
10602
+ },
10603
+
10604
+ 'substring-before': function(ctx) {
10605
+ assert(this.args.length == 2);
10606
+ var s0 = this.args[0].evaluate(ctx).stringValue();
10607
+ var s1 = this.args[1].evaluate(ctx).stringValue();
10608
+ var i = s0.indexOf(s1);
10609
+ var ret;
10610
+ if (i == -1) {
10611
+ ret = '';
10612
+ } else {
10613
+ ret = s0.substr(0,i);
10614
+ }
10615
+ return new StringValue(ret);
10616
+ },
10617
+
10618
+ 'substring-after': function(ctx) {
10619
+ assert(this.args.length == 2);
10620
+ var s0 = this.args[0].evaluate(ctx).stringValue();
10621
+ var s1 = this.args[1].evaluate(ctx).stringValue();
10622
+ var i = s0.indexOf(s1);
10623
+ var ret;
10624
+ if (i == -1) {
10625
+ ret = '';
10626
+ } else {
10627
+ ret = s0.substr(i + s1.length);
10628
+ }
10629
+ return new StringValue(ret);
10630
+ },
10631
+
10632
+ 'substring': function(ctx) {
10633
+ // NOTE: XPath defines the position of the first character in a
10634
+ // string to be 1, in JavaScript this is 0 ([XPATH] Section 4.2).
10635
+ assert(this.args.length == 2 || this.args.length == 3);
10636
+ var s0 = this.args[0].evaluate(ctx).stringValue();
10637
+ var s1 = this.args[1].evaluate(ctx).numberValue();
10638
+ var ret;
10639
+ if (this.args.length == 2) {
10640
+ var i1 = Math.max(0, Math.round(s1) - 1);
10641
+ ret = s0.substr(i1);
10642
+
10643
+ } else {
10644
+ var s2 = this.args[2].evaluate(ctx).numberValue();
10645
+ var i0 = Math.round(s1) - 1;
10646
+ var i1 = Math.max(0, i0);
10647
+ var i2 = Math.round(s2) - Math.max(0, -i0);
10648
+ ret = s0.substr(i1, i2);
10649
+ }
10650
+ return new StringValue(ret);
10651
+ },
10652
+
10653
+ 'string-length': function(ctx) {
10654
+ var s;
10655
+ if (this.args.length > 0) {
10656
+ s = this.args[0].evaluate(ctx).stringValue();
10657
+ } else {
10658
+ s = new NodeSetValue([ ctx.node ]).stringValue();
10659
+ }
10660
+ return new NumberValue(s.length);
10661
+ },
10662
+
10663
+ 'normalize-space': function(ctx) {
10664
+ var s;
10665
+ if (this.args.length > 0) {
10666
+ s = this.args[0].evaluate(ctx).stringValue();
10667
+ } else {
10668
+ s = new NodeSetValue([ ctx.node ]).stringValue();
10669
+ }
10670
+ s = s.replace(/^\s*/,'').replace(/\s*$/,'').replace(/\s+/g, ' ');
10671
+ return new StringValue(s);
10672
+ },
10673
+
10674
+ 'translate': function(ctx) {
10675
+ assert(this.args.length == 3);
10676
+ var s0 = this.args[0].evaluate(ctx).stringValue();
10677
+ var s1 = this.args[1].evaluate(ctx).stringValue();
10678
+ var s2 = this.args[2].evaluate(ctx).stringValue();
10679
+
10680
+ for (var i = 0; i < s1.length; ++i) {
10681
+ s0 = s0.replace(new RegExp(s1.charAt(i), 'g'), s2.charAt(i));
10682
+ }
10683
+ return new StringValue(s0);
10684
+ },
10685
+
10686
+ 'matches': function(ctx) {
10687
+ assert(this.args.length >= 2);
10688
+ var s0 = this.args[0].evaluate(ctx).stringValue();
10689
+ var s1 = this.args[1].evaluate(ctx).stringValue();
10690
+ if (this.args.length > 2) {
10691
+ var s2 = this.args[2].evaluate(ctx).stringValue();
10692
+ if (/[^mi]/.test(s2)) {
10693
+ throw 'Invalid regular expression syntax: ' + s2;
10694
+ }
10695
+ }
10696
+
10697
+ try {
10698
+ var re = new RegExp(s1, s2);
10699
+ }
10700
+ catch (e) {
10701
+ throw 'Invalid matches argument: ' + s1;
10702
+ }
10703
+ return new BooleanValue(re.test(s0));
10704
+ },
10705
+
10706
+ 'boolean': function(ctx) {
10707
+ assert(this.args.length == 1);
10708
+ return new BooleanValue(this.args[0].evaluate(ctx).booleanValue());
10709
+ },
10710
+
10711
+ 'not': function(ctx) {
10712
+ assert(this.args.length == 1);
10713
+ var ret = !this.args[0].evaluate(ctx).booleanValue();
10714
+ return new BooleanValue(ret);
10715
+ },
10716
+
10717
+ 'true': function(ctx) {
10718
+ assert(this.args.length == 0);
10719
+ return new BooleanValue(true);
10720
+ },
10721
+
10722
+ 'false': function(ctx) {
10723
+ assert(this.args.length == 0);
10724
+ return new BooleanValue(false);
10725
+ },
10726
+
10727
+ 'lang': function(ctx) {
10728
+ assert(this.args.length == 1);
10729
+ var lang = this.args[0].evaluate(ctx).stringValue();
10730
+ var xmllang;
10731
+ var n = ctx.node;
10732
+ while (n && n != n.parentNode /* just in case ... */) {
10733
+ xmllang = n.getAttribute('xml:lang');
10734
+ if (xmllang) {
10735
+ break;
10736
+ }
10737
+ n = n.parentNode;
10738
+ }
10739
+ if (!xmllang) {
10740
+ return new BooleanValue(false);
10741
+ } else {
10742
+ var re = new RegExp('^' + lang + '$', 'i');
10743
+ return new BooleanValue(xmllang.match(re) ||
10744
+ xmllang.replace(/_.*$/,'').match(re));
10745
+ }
10746
+ },
10747
+
10748
+ 'number': function(ctx) {
10749
+ assert(this.args.length == 1 || this.args.length == 0);
10750
+
10751
+ if (this.args.length == 1) {
10752
+ return new NumberValue(this.args[0].evaluate(ctx).numberValue());
10753
+ } else {
10754
+ return new NumberValue(new NodeSetValue([ ctx.node ]).numberValue());
10755
+ }
10756
+ },
10757
+
10758
+ 'sum': function(ctx) {
10759
+ assert(this.args.length == 1);
10760
+ var n = this.args[0].evaluate(ctx).nodeSetValue();
10761
+ var sum = 0;
10762
+ for (var i = 0; i < n.length; ++i) {
10763
+ sum += xmlValue(n[i]) - 0;
10764
+ }
10765
+ return new NumberValue(sum);
10766
+ },
10767
+
10768
+ 'floor': function(ctx) {
10769
+ assert(this.args.length == 1);
10770
+ var num = this.args[0].evaluate(ctx).numberValue();
10771
+ return new NumberValue(Math.floor(num));
10772
+ },
10773
+
10774
+ 'ceiling': function(ctx) {
10775
+ assert(this.args.length == 1);
10776
+ var num = this.args[0].evaluate(ctx).numberValue();
10777
+ return new NumberValue(Math.ceil(num));
10778
+ },
10779
+
10780
+ 'round': function(ctx) {
10781
+ assert(this.args.length == 1);
10782
+ var num = this.args[0].evaluate(ctx).numberValue();
10783
+ return new NumberValue(Math.round(num));
10784
+ },
10785
+
10786
+ // TODO(mesch): The following functions are custom. There is a
10787
+ // standard that defines how to add functions, which should be
10788
+ // applied here.
10789
+
10790
+ 'ext-join': function(ctx) {
10791
+ assert(this.args.length == 2);
10792
+ var nodes = this.args[0].evaluate(ctx).nodeSetValue();
10793
+ var delim = this.args[1].evaluate(ctx).stringValue();
10794
+ var ret = '';
10795
+ for (var i = 0; i < nodes.length; ++i) {
10796
+ if (ret) {
10797
+ ret += delim;
10798
+ }
10799
+ ret += xmlValue(nodes[i]);
10800
+ }
10801
+ return new StringValue(ret);
10802
+ },
10803
+
10804
+ // ext-if() evaluates and returns its second argument, if the
10805
+ // boolean value of its first argument is true, otherwise it
10806
+ // evaluates and returns its third argument.
10807
+
10808
+ 'ext-if': function(ctx) {
10809
+ assert(this.args.length == 3);
10810
+ if (this.args[0].evaluate(ctx).booleanValue()) {
10811
+ return this.args[1].evaluate(ctx);
10812
+ } else {
10813
+ return this.args[2].evaluate(ctx);
10814
+ }
10815
+ },
10816
+
10817
+ // ext-cardinal() evaluates its single argument as a number, and
10818
+ // returns the current node that many times. It can be used in the
10819
+ // select attribute to iterate over an integer range.
10820
+
10821
+ 'ext-cardinal': function(ctx) {
10822
+ assert(this.args.length >= 1);
10823
+ var c = this.args[0].evaluate(ctx).numberValue();
10824
+ var ret = [];
10825
+ for (var i = 0; i < c; ++i) {
10826
+ ret.push(ctx.node);
10827
+ }
10828
+ return new NodeSetValue(ret);
10829
+ }
10830
+ };
10831
+
10832
+ function UnionExpr(expr1, expr2) {
10833
+ this.expr1 = expr1;
10834
+ this.expr2 = expr2;
10835
+ }
10836
+
10837
+ UnionExpr.prototype.evaluate = function(ctx) {
10838
+ var nodes1 = this.expr1.evaluate(ctx).nodeSetValue();
10839
+ var nodes2 = this.expr2.evaluate(ctx).nodeSetValue();
10840
+ var I1 = nodes1.length;
10841
+ for (var i2 = 0; i2 < nodes2.length; ++i2) {
10842
+ var n = nodes2[i2];
10843
+ var inBoth = false;
10844
+ for (var i1 = 0; i1 < I1; ++i1) {
10845
+ if (nodes1[i1] == n) {
10846
+ inBoth = true;
10847
+ i1 = I1; // break inner loop
10848
+ }
10849
+ }
10850
+ if (!inBoth) {
10851
+ nodes1.push(n);
10852
+ }
10853
+ }
10854
+ return new NodeSetValue(nodes1);
10855
+ };
10856
+
10857
+ function PathExpr(filter, rel) {
10858
+ this.filter = filter;
10859
+ this.rel = rel;
10860
+ }
10861
+
10862
+ PathExpr.prototype.evaluate = function(ctx) {
10863
+ var nodes = this.filter.evaluate(ctx).nodeSetValue();
10864
+ var nodes1 = [];
10865
+ if (ctx.returnOnFirstMatch) {
10866
+ for (var i = 0; i < nodes.length; ++i) {
10867
+ nodes1 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue();
10868
+ if (nodes1.length > 0) {
10869
+ break;
10870
+ }
10871
+ }
10872
+ return new NodeSetValue(nodes1);
10873
+ }
10874
+ else {
10875
+ for (var i = 0; i < nodes.length; ++i) {
10876
+ var nodes0 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue();
10877
+ for (var ii = 0; ii < nodes0.length; ++ii) {
10878
+ nodes1.push(nodes0[ii]);
10879
+ }
10880
+ }
10881
+ return new NodeSetValue(nodes1);
10882
+ }
10883
+ };
10884
+
10885
+ function FilterExpr(expr, predicate) {
10886
+ this.expr = expr;
10887
+ this.predicate = predicate;
10888
+ }
10889
+
10890
+ FilterExpr.prototype.evaluate = function(ctx) {
10891
+ // the filter expression should be evaluated in its entirety with no
10892
+ // optimization, as we can't backtrack to it after having moved on to
10893
+ // evaluating the relative location path. See the testReturnOnFirstMatch
10894
+ // unit test.
10895
+ var flag = ctx.returnOnFirstMatch;
10896
+ ctx.setReturnOnFirstMatch(false);
10897
+ var nodes = this.expr.evaluate(ctx).nodeSetValue();
10898
+ ctx.setReturnOnFirstMatch(flag);
10899
+
10900
+ for (var i = 0; i < this.predicate.length; ++i) {
10901
+ var nodes0 = nodes;
10902
+ nodes = [];
10903
+ for (var j = 0; j < nodes0.length; ++j) {
10904
+ var n = nodes0[j];
10905
+ if (this.predicate[i].evaluate(ctx.clone(n, j, nodes0)).booleanValue()) {
10906
+ nodes.push(n);
10907
+ }
10908
+ }
10909
+ }
10910
+
10911
+ return new NodeSetValue(nodes);
10912
+ }
10913
+
10914
+ function UnaryMinusExpr(expr) {
10915
+ this.expr = expr;
10916
+ }
10917
+
10918
+ UnaryMinusExpr.prototype.evaluate = function(ctx) {
10919
+ return new NumberValue(-this.expr.evaluate(ctx).numberValue());
10920
+ };
10921
+
10922
+ function BinaryExpr(expr1, op, expr2) {
10923
+ this.expr1 = expr1;
10924
+ this.expr2 = expr2;
10925
+ this.op = op;
10926
+ }
10927
+
10928
+ BinaryExpr.prototype.evaluate = function(ctx) {
10929
+ var ret;
10930
+ switch (this.op.value) {
10931
+ case 'or':
10932
+ ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() ||
10933
+ this.expr2.evaluate(ctx).booleanValue());
10934
+ break;
10935
+
10936
+ case 'and':
10937
+ ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() &&
10938
+ this.expr2.evaluate(ctx).booleanValue());
10939
+ break;
10940
+
10941
+ case '+':
10942
+ ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() +
10943
+ this.expr2.evaluate(ctx).numberValue());
10944
+ break;
10945
+
10946
+ case '-':
10947
+ ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() -
10948
+ this.expr2.evaluate(ctx).numberValue());
10949
+ break;
10950
+
10951
+ case '*':
10952
+ ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() *
10953
+ this.expr2.evaluate(ctx).numberValue());
10954
+ break;
10955
+
10956
+ case 'mod':
10957
+ ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() %
10958
+ this.expr2.evaluate(ctx).numberValue());
10959
+ break;
10960
+
10961
+ case 'div':
10962
+ ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() /
10963
+ this.expr2.evaluate(ctx).numberValue());
10964
+ break;
10965
+
10966
+ case '=':
10967
+ ret = this.compare(ctx, function(x1, x2) { return x1 == x2; });
10968
+ break;
10969
+
10970
+ case '!=':
10971
+ ret = this.compare(ctx, function(x1, x2) { return x1 != x2; });
10972
+ break;
10973
+
10974
+ case '<':
10975
+ ret = this.compare(ctx, function(x1, x2) { return x1 < x2; });
10976
+ break;
10977
+
10978
+ case '<=':
10979
+ ret = this.compare(ctx, function(x1, x2) { return x1 <= x2; });
10980
+ break;
10981
+
10982
+ case '>':
10983
+ ret = this.compare(ctx, function(x1, x2) { return x1 > x2; });
10984
+ break;
10985
+
10986
+ case '>=':
10987
+ ret = this.compare(ctx, function(x1, x2) { return x1 >= x2; });
10988
+ break;
10989
+
10990
+ default:
10991
+ alert('BinaryExpr.evaluate: ' + this.op.value);
10992
+ }
10993
+ return ret;
10994
+ };
10995
+
10996
+ BinaryExpr.prototype.compare = function(ctx, cmp) {
10997
+ var v1 = this.expr1.evaluate(ctx);
10998
+ var v2 = this.expr2.evaluate(ctx);
10999
+
11000
+ var ret;
11001
+ if (v1.type == 'node-set' && v2.type == 'node-set') {
11002
+ var n1 = v1.nodeSetValue();
11003
+ var n2 = v2.nodeSetValue();
11004
+ ret = false;
11005
+ for (var i1 = 0; i1 < n1.length; ++i1) {
11006
+ for (var i2 = 0; i2 < n2.length; ++i2) {
11007
+ if (cmp(xmlValue(n1[i1]), xmlValue(n2[i2]))) {
11008
+ ret = true;
11009
+ // Break outer loop. Labels confuse the jscompiler and we
11010
+ // don't use them.
11011
+ i2 = n2.length;
11012
+ i1 = n1.length;
11013
+ }
11014
+ }
11015
+ }
11016
+
11017
+ } else if (v1.type == 'node-set' || v2.type == 'node-set') {
11018
+
11019
+ if (v1.type == 'number') {
11020
+ var s = v1.numberValue();
11021
+ var n = v2.nodeSetValue();
11022
+
11023
+ ret = false;
11024
+ for (var i = 0; i < n.length; ++i) {
11025
+ var nn = xmlValue(n[i]) - 0;
11026
+ if (cmp(s, nn)) {
11027
+ ret = true;
11028
+ break;
11029
+ }
11030
+ }
11031
+
11032
+ } else if (v2.type == 'number') {
11033
+ var n = v1.nodeSetValue();
11034
+ var s = v2.numberValue();
11035
+
11036
+ ret = false;
11037
+ for (var i = 0; i < n.length; ++i) {
11038
+ var nn = xmlValue(n[i]) - 0;
11039
+ if (cmp(nn, s)) {
11040
+ ret = true;
11041
+ break;
11042
+ }
11043
+ }
11044
+
11045
+ } else if (v1.type == 'string') {
11046
+ var s = v1.stringValue();
11047
+ var n = v2.nodeSetValue();
11048
+
11049
+ ret = false;
11050
+ for (var i = 0; i < n.length; ++i) {
11051
+ var nn = xmlValue(n[i]);
11052
+ if (cmp(s, nn)) {
11053
+ ret = true;
11054
+ break;
11055
+ }
11056
+ }
11057
+
11058
+ } else if (v2.type == 'string') {
11059
+ var n = v1.nodeSetValue();
11060
+ var s = v2.stringValue();
11061
+
11062
+ ret = false;
11063
+ for (var i = 0; i < n.length; ++i) {
11064
+ var nn = xmlValue(n[i]);
11065
+ if (cmp(nn, s)) {
11066
+ ret = true;
11067
+ break;
11068
+ }
11069
+ }
11070
+
11071
+ } else {
11072
+ ret = cmp(v1.booleanValue(), v2.booleanValue());
11073
+ }
11074
+
11075
+ } else if (v1.type == 'boolean' || v2.type == 'boolean') {
11076
+ ret = cmp(v1.booleanValue(), v2.booleanValue());
11077
+
11078
+ } else if (v1.type == 'number' || v2.type == 'number') {
11079
+ ret = cmp(v1.numberValue(), v2.numberValue());
11080
+
11081
+ } else {
11082
+ ret = cmp(v1.stringValue(), v2.stringValue());
11083
+ }
11084
+
11085
+ return new BooleanValue(ret);
11086
+ }
11087
+
11088
+ function LiteralExpr(value) {
11089
+ this.value = value;
11090
+ }
11091
+
11092
+ LiteralExpr.prototype.evaluate = function(ctx) {
11093
+ return new StringValue(this.value);
11094
+ };
11095
+
11096
+ function NumberExpr(value) {
11097
+ this.value = value;
11098
+ }
11099
+
11100
+ NumberExpr.prototype.evaluate = function(ctx) {
11101
+ return new NumberValue(this.value);
11102
+ };
11103
+
11104
+ function VariableExpr(name) {
11105
+ this.name = name;
11106
+ }
11107
+
11108
+ VariableExpr.prototype.evaluate = function(ctx) {
11109
+ return ctx.getVariable(this.name);
11110
+ }
11111
+
11112
+ // Factory functions for semantic values (i.e. Expressions) of the
11113
+ // productions in the grammar. When a production is matched to reduce
11114
+ // the current parse state stack, the function is called with the
11115
+ // semantic values of the matched elements as arguments, and returns
11116
+ // another semantic value. The semantic value is a node of the parse
11117
+ // tree, an expression object with an evaluate() method that evaluates the
11118
+ // expression in an actual context. These factory functions are used
11119
+ // in the specification of the grammar rules, below.
11120
+
11121
+ function makeTokenExpr(m) {
11122
+ return new TokenExpr(m);
11123
+ }
11124
+
11125
+ function passExpr(e) {
11126
+ return e;
11127
+ }
11128
+
11129
+ function makeLocationExpr1(slash, rel) {
11130
+ rel.absolute = true;
11131
+ return rel;
11132
+ }
11133
+
11134
+ function makeLocationExpr2(dslash, rel) {
11135
+ rel.absolute = true;
11136
+ rel.prependStep(makeAbbrevStep(dslash.value));
11137
+ return rel;
11138
+ }
11139
+
11140
+ function makeLocationExpr3(slash) {
11141
+ var ret = new LocationExpr();
11142
+ ret.appendStep(makeAbbrevStep('.'));
11143
+ ret.absolute = true;
11144
+ return ret;
11145
+ }
11146
+
11147
+ function makeLocationExpr4(dslash) {
11148
+ var ret = new LocationExpr();
11149
+ ret.absolute = true;
11150
+ ret.appendStep(makeAbbrevStep(dslash.value));
11151
+ return ret;
11152
+ }
11153
+
11154
+ function makeLocationExpr5(step) {
11155
+ var ret = new LocationExpr();
11156
+ ret.appendStep(step);
11157
+ return ret;
11158
+ }
11159
+
11160
+ function makeLocationExpr6(rel, slash, step) {
11161
+ rel.appendStep(step);
11162
+ return rel;
11163
+ }
11164
+
11165
+ function makeLocationExpr7(rel, dslash, step) {
11166
+ rel.appendStep(makeAbbrevStep(dslash.value));
11167
+ rel.appendStep(step);
11168
+ return rel;
11169
+ }
11170
+
11171
+ function makeStepExpr1(dot) {
11172
+ return makeAbbrevStep(dot.value);
11173
+ }
11174
+
11175
+ function makeStepExpr2(ddot) {
11176
+ return makeAbbrevStep(ddot.value);
11177
+ }
11178
+
11179
+ function makeStepExpr3(axisname, axis, nodetest) {
11180
+ return new StepExpr(axisname.value, nodetest);
11181
+ }
11182
+
11183
+ function makeStepExpr4(at, nodetest) {
11184
+ return new StepExpr('attribute', nodetest);
11185
+ }
11186
+
11187
+ function makeStepExpr5(nodetest) {
11188
+ return new StepExpr('child', nodetest);
11189
+ }
11190
+
11191
+ function makeStepExpr6(step, predicate) {
11192
+ step.appendPredicate(predicate);
11193
+ return step;
11194
+ }
11195
+
11196
+ function makeAbbrevStep(abbrev) {
11197
+ switch (abbrev) {
11198
+ case '//':
11199
+ return new StepExpr('descendant-or-self', new NodeTestAny);
11200
+
11201
+ case '.':
11202
+ return new StepExpr('self', new NodeTestAny);
11203
+
11204
+ case '..':
11205
+ return new StepExpr('parent', new NodeTestAny);
11206
+ }
11207
+ }
11208
+
11209
+ function makeNodeTestExpr1(asterisk) {
11210
+ return new NodeTestElementOrAttribute;
11211
+ }
11212
+
11213
+ function makeNodeTestExpr2(ncname, colon, asterisk) {
11214
+ return new NodeTestNC(ncname.value);
11215
+ }
11216
+
11217
+ function makeNodeTestExpr3(qname) {
11218
+ return new NodeTestName(qname.value);
11219
+ }
11220
+
11221
+ function makeNodeTestExpr4(typeo, parenc) {
11222
+ var type = typeo.value.replace(/\s*\($/, '');
11223
+ switch(type) {
11224
+ case 'node':
11225
+ return new NodeTestAny;
11226
+
11227
+ case 'text':
11228
+ return new NodeTestText;
11229
+
11230
+ case 'comment':
11231
+ return new NodeTestComment;
11232
+
11233
+ case 'processing-instruction':
11234
+ return new NodeTestPI('');
11235
+ }
11236
+ }
11237
+
11238
+ function makeNodeTestExpr5(typeo, target, parenc) {
11239
+ var type = typeo.replace(/\s*\($/, '');
11240
+ if (type != 'processing-instruction') {
11241
+ throw type;
11242
+ }
11243
+ return new NodeTestPI(target.value);
11244
+ }
11245
+
11246
+ function makePredicateExpr(pareno, expr, parenc) {
11247
+ return new PredicateExpr(expr);
11248
+ }
11249
+
11250
+ function makePrimaryExpr(pareno, expr, parenc) {
11251
+ return expr;
11252
+ }
11253
+
11254
+ function makeFunctionCallExpr1(name, pareno, parenc) {
11255
+ return new FunctionCallExpr(name);
11256
+ }
11257
+
11258
+ function makeFunctionCallExpr2(name, pareno, arg1, args, parenc) {
11259
+ var ret = new FunctionCallExpr(name);
11260
+ ret.appendArg(arg1);
11261
+ for (var i = 0; i < args.length; ++i) {
11262
+ ret.appendArg(args[i]);
11263
+ }
11264
+ return ret;
11265
+ }
11266
+
11267
+ function makeArgumentExpr(comma, expr) {
11268
+ return expr;
11269
+ }
11270
+
11271
+ function makeUnionExpr(expr1, pipe, expr2) {
11272
+ return new UnionExpr(expr1, expr2);
11273
+ }
11274
+
11275
+ function makePathExpr1(filter, slash, rel) {
11276
+ return new PathExpr(filter, rel);
11277
+ }
11278
+
11279
+ function makePathExpr2(filter, dslash, rel) {
11280
+ rel.prependStep(makeAbbrevStep(dslash.value));
11281
+ return new PathExpr(filter, rel);
11282
+ }
11283
+
11284
+ function makeFilterExpr(expr, predicates) {
11285
+ if (predicates.length > 0) {
11286
+ return new FilterExpr(expr, predicates);
11287
+ } else {
11288
+ return expr;
11289
+ }
11290
+ }
11291
+
11292
+ function makeUnaryMinusExpr(minus, expr) {
11293
+ return new UnaryMinusExpr(expr);
11294
+ }
11295
+
11296
+ function makeBinaryExpr(expr1, op, expr2) {
11297
+ return new BinaryExpr(expr1, op, expr2);
11298
+ }
11299
+
11300
+ function makeLiteralExpr(token) {
11301
+ // remove quotes from the parsed value:
11302
+ var value = token.value.substring(1, token.value.length - 1);
11303
+ return new LiteralExpr(value);
11304
+ }
11305
+
11306
+ function makeNumberExpr(token) {
11307
+ return new NumberExpr(token.value);
11308
+ }
11309
+
11310
+ function makeVariableReference(dollar, name) {
11311
+ return new VariableExpr(name.value);
11312
+ }
11313
+
11314
+ // Used before parsing for optimization of common simple cases. See
11315
+ // the begin of xpathParse() for which they are.
11316
+ function makeSimpleExpr(expr) {
11317
+ if (expr.charAt(0) == '$') {
11318
+ return new VariableExpr(expr.substr(1));
11319
+ } else if (expr.charAt(0) == '@') {
11320
+ var a = new NodeTestName(expr.substr(1));
11321
+ var b = new StepExpr('attribute', a);
11322
+ var c = new LocationExpr();
11323
+ c.appendStep(b);
11324
+ return c;
11325
+ } else if (expr.match(/^[0-9]+$/)) {
11326
+ return new NumberExpr(expr);
11327
+ } else {
11328
+ var a = new NodeTestName(expr);
11329
+ var b = new StepExpr('child', a);
11330
+ var c = new LocationExpr();
11331
+ c.appendStep(b);
11332
+ return c;
11333
+ }
11334
+ }
11335
+
11336
+ function makeSimpleExpr2(expr) {
11337
+ var steps = stringSplit(expr, '/');
11338
+ var c = new LocationExpr();
11339
+ for (var i = 0; i < steps.length; ++i) {
11340
+ var a = new NodeTestName(steps[i]);
11341
+ var b = new StepExpr('child', a);
11342
+ c.appendStep(b);
11343
+ }
11344
+ return c;
11345
+ }
11346
+
11347
+ // The axes of XPath expressions.
11348
+
11349
+ var xpathAxis = {
11350
+ ANCESTOR_OR_SELF: 'ancestor-or-self',
11351
+ ANCESTOR: 'ancestor',
11352
+ ATTRIBUTE: 'attribute',
11353
+ CHILD: 'child',
11354
+ DESCENDANT_OR_SELF: 'descendant-or-self',
11355
+ DESCENDANT: 'descendant',
11356
+ FOLLOWING_SIBLING: 'following-sibling',
11357
+ FOLLOWING: 'following',
11358
+ NAMESPACE: 'namespace',
11359
+ PARENT: 'parent',
11360
+ PRECEDING_SIBLING: 'preceding-sibling',
11361
+ PRECEDING: 'preceding',
11362
+ SELF: 'self'
11363
+ };
11364
+
11365
+ var xpathAxesRe = [
11366
+ xpathAxis.ANCESTOR_OR_SELF,
11367
+ xpathAxis.ANCESTOR,
11368
+ xpathAxis.ATTRIBUTE,
11369
+ xpathAxis.CHILD,
11370
+ xpathAxis.DESCENDANT_OR_SELF,
11371
+ xpathAxis.DESCENDANT,
11372
+ xpathAxis.FOLLOWING_SIBLING,
11373
+ xpathAxis.FOLLOWING,
11374
+ xpathAxis.NAMESPACE,
11375
+ xpathAxis.PARENT,
11376
+ xpathAxis.PRECEDING_SIBLING,
11377
+ xpathAxis.PRECEDING,
11378
+ xpathAxis.SELF
11379
+ ].join('|');
11380
+
11381
+
11382
+ // The tokens of the language. The label property is just used for
11383
+ // generating debug output. The prec property is the precedence used
11384
+ // for shift/reduce resolution. Default precedence is 0 as a lookahead
11385
+ // token and 2 on the stack. TODO(mesch): this is certainly not
11386
+ // necessary and too complicated. Simplify this!
11387
+
11388
+ // NOTE: tabular formatting is the big exception, but here it should
11389
+ // be OK.
11390
+
11391
+ var TOK_PIPE = { label: "|", prec: 17, re: new RegExp("^\\|") };
11392
+ var TOK_DSLASH = { label: "//", prec: 19, re: new RegExp("^//") };
11393
+ var TOK_SLASH = { label: "/", prec: 30, re: new RegExp("^/") };
11394
+ var TOK_AXIS = { label: "::", prec: 20, re: new RegExp("^::") };
11395
+ var TOK_COLON = { label: ":", prec: 1000, re: new RegExp("^:") };
11396
+ var TOK_AXISNAME = { label: "[axis]", re: new RegExp('^(' + xpathAxesRe + ')') };
11397
+ var TOK_PARENO = { label: "(", prec: 34, re: new RegExp("^\\(") };
11398
+ var TOK_PARENC = { label: ")", re: new RegExp("^\\)") };
11399
+ var TOK_DDOT = { label: "..", prec: 34, re: new RegExp("^\\.\\.") };
11400
+ var TOK_DOT = { label: ".", prec: 34, re: new RegExp("^\\.") };
11401
+ var TOK_AT = { label: "@", prec: 34, re: new RegExp("^@") };
11402
+
11403
+ var TOK_COMMA = { label: ",", re: new RegExp("^,") };
11404
+
11405
+ var TOK_OR = { label: "or", prec: 10, re: new RegExp("^or\\b") };
11406
+ var TOK_AND = { label: "and", prec: 11, re: new RegExp("^and\\b") };
11407
+ var TOK_EQ = { label: "=", prec: 12, re: new RegExp("^=") };
11408
+ var TOK_NEQ = { label: "!=", prec: 12, re: new RegExp("^!=") };
11409
+ var TOK_GE = { label: ">=", prec: 13, re: new RegExp("^>=") };
11410
+ var TOK_GT = { label: ">", prec: 13, re: new RegExp("^>") };
11411
+ var TOK_LE = { label: "<=", prec: 13, re: new RegExp("^<=") };
11412
+ var TOK_LT = { label: "<", prec: 13, re: new RegExp("^<") };
11413
+ var TOK_PLUS = { label: "+", prec: 14, re: new RegExp("^\\+"), left: true };
11414
+ var TOK_MINUS = { label: "-", prec: 14, re: new RegExp("^\\-"), left: true };
11415
+ var TOK_DIV = { label: "div", prec: 15, re: new RegExp("^div\\b"), left: true };
11416
+ var TOK_MOD = { label: "mod", prec: 15, re: new RegExp("^mod\\b"), left: true };
11417
+
11418
+ var TOK_BRACKO = { label: "[", prec: 32, re: new RegExp("^\\[") };
11419
+ var TOK_BRACKC = { label: "]", re: new RegExp("^\\]") };
11420
+ var TOK_DOLLAR = { label: "$", re: new RegExp("^\\$") };
11421
+
11422
+ var TOK_NCNAME = { label: "[ncname]", re: new RegExp('^' + XML_NC_NAME) };
11423
+
11424
+ var TOK_ASTERISK = { label: "*", prec: 15, re: new RegExp("^\\*"), left: true };
11425
+ var TOK_LITERALQ = { label: "[litq]", prec: 20, re: new RegExp("^'[^\\']*'") };
11426
+ var TOK_LITERALQQ = {
11427
+ label: "[litqq]",
11428
+ prec: 20,
11429
+ re: new RegExp('^"[^\\"]*"')
11430
+ };
11431
+
11432
+ var TOK_NUMBER = {
11433
+ label: "[number]",
11434
+ prec: 35,
11435
+ re: new RegExp('^\\d+(\\.\\d*)?') };
11436
+
11437
+ var TOK_QNAME = {
11438
+ label: "[qname]",
11439
+ re: new RegExp('^(' + XML_NC_NAME + ':)?' + XML_NC_NAME)
11440
+ };
11441
+
11442
+ var TOK_NODEO = {
11443
+ label: "[nodetest-start]",
11444
+ re: new RegExp('^(processing-instruction|comment|text|node)\\(')
11445
+ };
11446
+
11447
+ // The table of the tokens of our grammar, used by the lexer: first
11448
+ // column the tag, second column a regexp to recognize it in the
11449
+ // input, third column the precedence of the token, fourth column a
11450
+ // factory function for the semantic value of the token.
11451
+ //
11452
+ // NOTE: order of this list is important, because the first match
11453
+ // counts. Cf. DDOT and DOT, and AXIS and COLON.
11454
+
11455
+ var xpathTokenRules = [
11456
+ TOK_DSLASH,
11457
+ TOK_SLASH,
11458
+ TOK_DDOT,
11459
+ TOK_DOT,
11460
+ TOK_AXIS,
11461
+ TOK_COLON,
11462
+ TOK_AXISNAME,
11463
+ TOK_NODEO,
11464
+ TOK_PARENO,
11465
+ TOK_PARENC,
11466
+ TOK_BRACKO,
11467
+ TOK_BRACKC,
11468
+ TOK_AT,
11469
+ TOK_COMMA,
11470
+ TOK_OR,
11471
+ TOK_AND,
11472
+ TOK_NEQ,
11473
+ TOK_EQ,
11474
+ TOK_GE,
11475
+ TOK_GT,
11476
+ TOK_LE,
11477
+ TOK_LT,
11478
+ TOK_PLUS,
11479
+ TOK_MINUS,
11480
+ TOK_ASTERISK,
11481
+ TOK_PIPE,
11482
+ TOK_MOD,
11483
+ TOK_DIV,
11484
+ TOK_LITERALQ,
11485
+ TOK_LITERALQQ,
11486
+ TOK_NUMBER,
11487
+ TOK_QNAME,
11488
+ TOK_NCNAME,
11489
+ TOK_DOLLAR
11490
+ ];
11491
+
11492
+ // All the nonterminals of the grammar. The nonterminal objects are
11493
+ // identified by object identity; the labels are used in the debug
11494
+ // output only.
11495
+ var XPathLocationPath = { label: "LocationPath" };
11496
+ var XPathRelativeLocationPath = { label: "RelativeLocationPath" };
11497
+ var XPathAbsoluteLocationPath = { label: "AbsoluteLocationPath" };
11498
+ var XPathStep = { label: "Step" };
11499
+ var XPathNodeTest = { label: "NodeTest" };
11500
+ var XPathPredicate = { label: "Predicate" };
11501
+ var XPathLiteral = { label: "Literal" };
11502
+ var XPathExpr = { label: "Expr" };
11503
+ var XPathPrimaryExpr = { label: "PrimaryExpr" };
11504
+ var XPathVariableReference = { label: "Variablereference" };
11505
+ var XPathNumber = { label: "Number" };
11506
+ var XPathFunctionCall = { label: "FunctionCall" };
11507
+ var XPathArgumentRemainder = { label: "ArgumentRemainder" };
11508
+ var XPathPathExpr = { label: "PathExpr" };
11509
+ var XPathUnionExpr = { label: "UnionExpr" };
11510
+ var XPathFilterExpr = { label: "FilterExpr" };
11511
+ var XPathDigits = { label: "Digits" };
11512
+
11513
+ var xpathNonTerminals = [
11514
+ XPathLocationPath,
11515
+ XPathRelativeLocationPath,
11516
+ XPathAbsoluteLocationPath,
11517
+ XPathStep,
11518
+ XPathNodeTest,
11519
+ XPathPredicate,
11520
+ XPathLiteral,
11521
+ XPathExpr,
11522
+ XPathPrimaryExpr,
11523
+ XPathVariableReference,
11524
+ XPathNumber,
11525
+ XPathFunctionCall,
11526
+ XPathArgumentRemainder,
11527
+ XPathPathExpr,
11528
+ XPathUnionExpr,
11529
+ XPathFilterExpr,
11530
+ XPathDigits
11531
+ ];
11532
+
11533
+ // Quantifiers that are used in the productions of the grammar.
11534
+ var Q_01 = { label: "?" };
11535
+ var Q_MM = { label: "*" };
11536
+ var Q_1M = { label: "+" };
11537
+
11538
+ // Tag for left associativity (right assoc is implied by undefined).
11539
+ var ASSOC_LEFT = true;
11540
+
11541
+ // The productions of the grammar. Columns of the table:
11542
+ //
11543
+ // - target nonterminal,
11544
+ // - pattern,
11545
+ // - precedence,
11546
+ // - semantic value factory
11547
+ //
11548
+ // The semantic value factory is a function that receives parse tree
11549
+ // nodes from the stack frames of the matched symbols as arguments and
11550
+ // returns an a node of the parse tree. The node is stored in the top
11551
+ // stack frame along with the target object of the rule. The node in
11552
+ // the parse tree is an expression object that has an evaluate() method
11553
+ // and thus evaluates XPath expressions.
11554
+ //
11555
+ // The precedence is used to decide between reducing and shifting by
11556
+ // comparing the precendence of the rule that is candidate for
11557
+ // reducing with the precedence of the look ahead token. Precedence of
11558
+ // -1 means that the precedence of the tokens in the pattern is used
11559
+ // instead. TODO: It shouldn't be necessary to explicitly assign
11560
+ // precedences to rules.
11561
+
11562
+ // DGF As it stands, these precedences are purely empirical; we're
11563
+ // not sure they can be made to be consistent at all.
11564
+
11565
+ var xpathGrammarRules =
11566
+ [
11567
+ [ XPathLocationPath, [ XPathRelativeLocationPath ], 18,
11568
+ passExpr ],
11569
+ [ XPathLocationPath, [ XPathAbsoluteLocationPath ], 18,
11570
+ passExpr ],
11571
+
11572
+ [ XPathAbsoluteLocationPath, [ TOK_SLASH, XPathRelativeLocationPath ], 18,
11573
+ makeLocationExpr1 ],
11574
+ [ XPathAbsoluteLocationPath, [ TOK_DSLASH, XPathRelativeLocationPath ], 18,
11575
+ makeLocationExpr2 ],
11576
+
11577
+ [ XPathAbsoluteLocationPath, [ TOK_SLASH ], 0,
11578
+ makeLocationExpr3 ],
11579
+ [ XPathAbsoluteLocationPath, [ TOK_DSLASH ], 0,
11580
+ makeLocationExpr4 ],
11581
+
11582
+ [ XPathRelativeLocationPath, [ XPathStep ], 31,
11583
+ makeLocationExpr5 ],
11584
+ [ XPathRelativeLocationPath,
11585
+ [ XPathRelativeLocationPath, TOK_SLASH, XPathStep ], 31,
11586
+ makeLocationExpr6 ],
11587
+ [ XPathRelativeLocationPath,
11588
+ [ XPathRelativeLocationPath, TOK_DSLASH, XPathStep ], 31,
11589
+ makeLocationExpr7 ],
11590
+
11591
+ [ XPathStep, [ TOK_DOT ], 33,
11592
+ makeStepExpr1 ],
11593
+ [ XPathStep, [ TOK_DDOT ], 33,
11594
+ makeStepExpr2 ],
11595
+ [ XPathStep,
11596
+ [ TOK_AXISNAME, TOK_AXIS, XPathNodeTest ], 33,
11597
+ makeStepExpr3 ],
11598
+ [ XPathStep, [ TOK_AT, XPathNodeTest ], 33,
11599
+ makeStepExpr4 ],
11600
+ [ XPathStep, [ XPathNodeTest ], 33,
11601
+ makeStepExpr5 ],
11602
+ [ XPathStep, [ XPathStep, XPathPredicate ], 33,
11603
+ makeStepExpr6 ],
11604
+
11605
+ [ XPathNodeTest, [ TOK_ASTERISK ], 33,
11606
+ makeNodeTestExpr1 ],
11607
+ [ XPathNodeTest, [ TOK_NCNAME, TOK_COLON, TOK_ASTERISK ], 33,
11608
+ makeNodeTestExpr2 ],
11609
+ [ XPathNodeTest, [ TOK_QNAME ], 33,
11610
+ makeNodeTestExpr3 ],
11611
+ [ XPathNodeTest, [ TOK_NODEO, TOK_PARENC ], 33,
11612
+ makeNodeTestExpr4 ],
11613
+ [ XPathNodeTest, [ TOK_NODEO, XPathLiteral, TOK_PARENC ], 33,
11614
+ makeNodeTestExpr5 ],
11615
+
11616
+ [ XPathPredicate, [ TOK_BRACKO, XPathExpr, TOK_BRACKC ], 33,
11617
+ makePredicateExpr ],
11618
+
11619
+ [ XPathPrimaryExpr, [ XPathVariableReference ], 33,
11620
+ passExpr ],
11621
+ [ XPathPrimaryExpr, [ TOK_PARENO, XPathExpr, TOK_PARENC ], 33,
11622
+ makePrimaryExpr ],
11623
+ [ XPathPrimaryExpr, [ XPathLiteral ], 30,
11624
+ passExpr ],
11625
+ [ XPathPrimaryExpr, [ XPathNumber ], 30,
11626
+ passExpr ],
11627
+ [ XPathPrimaryExpr, [ XPathFunctionCall ], 31,
11628
+ passExpr ],
11629
+
11630
+ [ XPathFunctionCall, [ TOK_QNAME, TOK_PARENO, TOK_PARENC ], -1,
11631
+ makeFunctionCallExpr1 ],
11632
+ [ XPathFunctionCall,
11633
+ [ TOK_QNAME, TOK_PARENO, XPathExpr, XPathArgumentRemainder, Q_MM,
11634
+ TOK_PARENC ], -1,
11635
+ makeFunctionCallExpr2 ],
11636
+ [ XPathArgumentRemainder, [ TOK_COMMA, XPathExpr ], -1,
11637
+ makeArgumentExpr ],
11638
+
11639
+ [ XPathUnionExpr, [ XPathPathExpr ], 20,
11640
+ passExpr ],
11641
+ [ XPathUnionExpr, [ XPathUnionExpr, TOK_PIPE, XPathPathExpr ], 20,
11642
+ makeUnionExpr ],
11643
+
11644
+ [ XPathPathExpr, [ XPathLocationPath ], 20,
11645
+ passExpr ],
11646
+ [ XPathPathExpr, [ XPathFilterExpr ], 19,
11647
+ passExpr ],
11648
+ [ XPathPathExpr,
11649
+ [ XPathFilterExpr, TOK_SLASH, XPathRelativeLocationPath ], 19,
11650
+ makePathExpr1 ],
11651
+ [ XPathPathExpr,
11652
+ [ XPathFilterExpr, TOK_DSLASH, XPathRelativeLocationPath ], 19,
11653
+ makePathExpr2 ],
11654
+
11655
+ [ XPathFilterExpr, [ XPathPrimaryExpr, XPathPredicate, Q_MM ], 31,
11656
+ makeFilterExpr ],
11657
+
11658
+ [ XPathExpr, [ XPathPrimaryExpr ], 16,
11659
+ passExpr ],
11660
+ [ XPathExpr, [ XPathUnionExpr ], 16,
11661
+ passExpr ],
11662
+
11663
+ [ XPathExpr, [ TOK_MINUS, XPathExpr ], -1,
11664
+ makeUnaryMinusExpr ],
11665
+
11666
+ [ XPathExpr, [ XPathExpr, TOK_OR, XPathExpr ], -1,
11667
+ makeBinaryExpr ],
11668
+ [ XPathExpr, [ XPathExpr, TOK_AND, XPathExpr ], -1,
11669
+ makeBinaryExpr ],
11670
+
11671
+ [ XPathExpr, [ XPathExpr, TOK_EQ, XPathExpr ], -1,
11672
+ makeBinaryExpr ],
11673
+ [ XPathExpr, [ XPathExpr, TOK_NEQ, XPathExpr ], -1,
11674
+ makeBinaryExpr ],
11675
+
11676
+ [ XPathExpr, [ XPathExpr, TOK_LT, XPathExpr ], -1,
11677
+ makeBinaryExpr ],
11678
+ [ XPathExpr, [ XPathExpr, TOK_LE, XPathExpr ], -1,
11679
+ makeBinaryExpr ],
11680
+ [ XPathExpr, [ XPathExpr, TOK_GT, XPathExpr ], -1,
11681
+ makeBinaryExpr ],
11682
+ [ XPathExpr, [ XPathExpr, TOK_GE, XPathExpr ], -1,
11683
+ makeBinaryExpr ],
11684
+
11685
+ [ XPathExpr, [ XPathExpr, TOK_PLUS, XPathExpr ], -1,
11686
+ makeBinaryExpr, ASSOC_LEFT ],
11687
+ [ XPathExpr, [ XPathExpr, TOK_MINUS, XPathExpr ], -1,
11688
+ makeBinaryExpr, ASSOC_LEFT ],
11689
+
11690
+ [ XPathExpr, [ XPathExpr, TOK_ASTERISK, XPathExpr ], -1,
11691
+ makeBinaryExpr, ASSOC_LEFT ],
11692
+ [ XPathExpr, [ XPathExpr, TOK_DIV, XPathExpr ], -1,
11693
+ makeBinaryExpr, ASSOC_LEFT ],
11694
+ [ XPathExpr, [ XPathExpr, TOK_MOD, XPathExpr ], -1,
11695
+ makeBinaryExpr, ASSOC_LEFT ],
11696
+
11697
+ [ XPathLiteral, [ TOK_LITERALQ ], -1,
11698
+ makeLiteralExpr ],
11699
+ [ XPathLiteral, [ TOK_LITERALQQ ], -1,
11700
+ makeLiteralExpr ],
11701
+
11702
+ [ XPathNumber, [ TOK_NUMBER ], -1,
11703
+ makeNumberExpr ],
11704
+
11705
+ [ XPathVariableReference, [ TOK_DOLLAR, TOK_QNAME ], 200,
11706
+ makeVariableReference ]
11707
+ ];
11708
+
11709
+ // That function computes some optimizations of the above data
11710
+ // structures and will be called right here. It merely takes the
11711
+ // counter variables out of the global scope.
11712
+
11713
+ var xpathRules = [];
11714
+
11715
+ function xpathParseInit() {
11716
+ if (xpathRules.length) {
11717
+ return;
11718
+ }
11719
+
11720
+ // Some simple optimizations for the xpath expression parser: sort
11721
+ // grammar rules descending by length, so that the longest match is
11722
+ // first found.
11723
+
11724
+ xpathGrammarRules.sort(function(a,b) {
11725
+ var la = a[1].length;
11726
+ var lb = b[1].length;
11727
+ if (la < lb) {
11728
+ return 1;
11729
+ } else if (la > lb) {
11730
+ return -1;
11731
+ } else {
11732
+ return 0;
11733
+ }
11734
+ });
11735
+
11736
+ var k = 1;
11737
+ for (var i = 0; i < xpathNonTerminals.length; ++i) {
11738
+ xpathNonTerminals[i].key = k++;
11739
+ }
11740
+
11741
+ for (i = 0; i < xpathTokenRules.length; ++i) {
11742
+ xpathTokenRules[i].key = k++;
11743
+ }
11744
+
11745
+ xpathLog('XPath parse INIT: ' + k + ' rules');
11746
+
11747
+ // Another slight optimization: sort the rules into bins according
11748
+ // to the last element (observing quantifiers), so we can restrict
11749
+ // the match against the stack to the subest of rules that match the
11750
+ // top of the stack.
11751
+ //
11752
+ // TODO(mesch): What we actually want is to compute states as in
11753
+ // bison, so that we don't have to do any explicit and iterated
11754
+ // match against the stack.
11755
+
11756
+ function push_(array, position, element) {
11757
+ if (!array[position]) {
11758
+ array[position] = [];
11759
+ }
11760
+ array[position].push(element);
11761
+ }
11762
+
11763
+ for (i = 0; i < xpathGrammarRules.length; ++i) {
11764
+ var rule = xpathGrammarRules[i];
11765
+ var pattern = rule[1];
11766
+
11767
+ for (var j = pattern.length - 1; j >= 0; --j) {
11768
+ if (pattern[j] == Q_1M) {
11769
+ push_(xpathRules, pattern[j-1].key, rule);
11770
+ break;
11771
+
11772
+ } else if (pattern[j] == Q_MM || pattern[j] == Q_01) {
11773
+ push_(xpathRules, pattern[j-1].key, rule);
11774
+ --j;
11775
+
11776
+ } else {
11777
+ push_(xpathRules, pattern[j].key, rule);
11778
+ break;
11779
+ }
11780
+ }
11781
+ }
11782
+
11783
+ xpathLog('XPath parse INIT: ' + xpathRules.length + ' rule bins');
11784
+
11785
+ var sum = 0;
11786
+ mapExec(xpathRules, function(i) {
11787
+ if (i) {
11788
+ sum += i.length;
11789
+ }
11790
+ });
11791
+
11792
+ xpathLog('XPath parse INIT: ' + (sum / xpathRules.length) +
11793
+ ' average bin size');
11794
+ }
11795
+
11796
+ // Local utility functions that are used by the lexer or parser.
11797
+
11798
+ function xpathCollectDescendants(nodelist, node, opt_tagName) {
11799
+ if (opt_tagName && node.getElementsByTagName) {
11800
+ copyArray(nodelist, node.getElementsByTagName(opt_tagName));
11801
+ return;
11802
+ }
11803
+ for (var n = node.firstChild; n; n = n.nextSibling) {
11804
+ nodelist.push(n);
11805
+ xpathCollectDescendants(nodelist, n);
11806
+ }
11807
+ }
11808
+
11809
+ /**
11810
+ * DGF - extract a tag name suitable for getElementsByTagName
11811
+ *
11812
+ * @param nodetest the node test
11813
+ * @param ignoreNonElementNodesForNTA if true, the node list returned when
11814
+ * evaluating "node()" will not contain
11815
+ * non-element nodes. This can boost
11816
+ * performance. This is false by default.
11817
+ */
11818
+ function xpathExtractTagNameFromNodeTest(nodetest, ignoreNonElementNodesForNTA) {
11819
+ if (nodetest instanceof NodeTestName) {
11820
+ return nodetest.name;
11821
+ }
11822
+ if ((ignoreNonElementNodesForNTA && nodetest instanceof NodeTestAny) ||
11823
+ nodetest instanceof NodeTestElementOrAttribute) {
11824
+ return "*";
11825
+ }
11826
+ }
11827
+
11828
+ function xpathCollectDescendantsReverse(nodelist, node) {
11829
+ for (var n = node.lastChild; n; n = n.previousSibling) {
11830
+ nodelist.push(n);
11831
+ xpathCollectDescendantsReverse(nodelist, n);
11832
+ }
11833
+ }
11834
+
11835
+
11836
+ // The entry point for the library: match an expression against a DOM
11837
+ // node. Returns an XPath value.
11838
+ function xpathDomEval(expr, node) {
11839
+ var expr1 = xpathParse(expr);
11840
+ var ret = expr1.evaluate(new ExprContext(node));
11841
+ return ret;
11842
+ }
11843
+
11844
+ // Utility function to sort a list of nodes. Used by xsltSort() and
11845
+ // nxslSelect().
11846
+ function xpathSort(input, sort) {
11847
+ if (sort.length == 0) {
11848
+ return;
11849
+ }
11850
+
11851
+ var sortlist = [];
11852
+
11853
+ for (var i = 0; i < input.contextSize(); ++i) {
11854
+ var node = input.nodelist[i];
11855
+ var sortitem = { node: node, key: [] };
11856
+ var context = input.clone(node, 0, [ node ]);
11857
+
11858
+ for (var j = 0; j < sort.length; ++j) {
11859
+ var s = sort[j];
11860
+ var value = s.expr.evaluate(context);
11861
+
11862
+ var evalue;
11863
+ if (s.type == 'text') {
11864
+ evalue = value.stringValue();
11865
+ } else if (s.type == 'number') {
11866
+ evalue = value.numberValue();
11867
+ }
11868
+ sortitem.key.push({ value: evalue, order: s.order });
11869
+ }
11870
+
11871
+ // Make the sort stable by adding a lowest priority sort by
11872
+ // id. This is very convenient and furthermore required by the
11873
+ // spec ([XSLT] - Section 10 Sorting).
11874
+ sortitem.key.push({ value: i, order: 'ascending' });
11875
+
11876
+ sortlist.push(sortitem);
11877
+ }
11878
+
11879
+ sortlist.sort(xpathSortByKey);
11880
+
11881
+ var nodes = [];
11882
+ for (var i = 0; i < sortlist.length; ++i) {
11883
+ nodes.push(sortlist[i].node);
11884
+ }
11885
+ input.nodelist = nodes;
11886
+ input.setNode(0);
11887
+ }
11888
+
11889
+
11890
+ // Sorts by all order criteria defined. According to the JavaScript
11891
+ // spec ([ECMA] Section 11.8.5), the compare operators compare strings
11892
+ // as strings and numbers as numbers.
11893
+ //
11894
+ // NOTE: In browsers which do not follow the spec, this breaks only in
11895
+ // the case that numbers should be sorted as strings, which is very
11896
+ // uncommon.
11897
+ function xpathSortByKey(v1, v2) {
11898
+ // NOTE: Sort key vectors of different length never occur in
11899
+ // xsltSort.
11900
+
11901
+ for (var i = 0; i < v1.key.length; ++i) {
11902
+ var o = v1.key[i].order == 'descending' ? -1 : 1;
11903
+ if (v1.key[i].value > v2.key[i].value) {
11904
+ return +1 * o;
11905
+ } else if (v1.key[i].value < v2.key[i].value) {
11906
+ return -1 * o;
11907
+ }
11908
+ }
11909
+
11910
+ return 0;
11911
+ }
11912
+
11913
+
11914
+ // Parses and then evaluates the given XPath expression in the given
11915
+ // input context. Notice that parsed xpath expressions are cached.
11916
+ function xpathEval(select, context) {
11917
+ var expr = xpathParse(select);
11918
+ var ret = expr.evaluate(context);
11919
+ return ret;
11920
+ }
11921
+ // Copyright 2005 Google
11922
+ //
11923
+ // Author: Steffen Meschkat <mesch@google.com>
11924
+ //
11925
+ // Miscellaneous utility and placeholder functions.
11926
+
11927
+ // Dummy implmentation for the logging functions. Replace by something
11928
+ // useful when you want to debug.
11929
+ function xpathLog(msg) {print(msg)};
11930
+ function xpathLog(msg) {/*print(msg)*/};
11931
+ function xsltLog(msg) {};
11932
+ function xsltLogXml(msg) {};
11933
+
11934
+ /* ENVJS CHANGE */
11935
+ var ajaxsltIsIE6 = false; // navigator.appVersion.match(/MSIE 6.0/);
11936
+
11937
+ // Throws an exception if false.
11938
+ function assert(b) {
11939
+ if (!b) {
11940
+ throw "Assertion failed";
11941
+ }
11942
+ }
11943
+
11944
+ // Splits a string s at all occurrences of character c. This is like
11945
+ // the split() method of the string object, but IE omits empty
11946
+ // strings, which violates the invariant (s.split(x).join(x) == s).
11947
+ function stringSplit(s, c) {
11948
+ var a = s.indexOf(c);
11949
+ if (a == -1) {
11950
+ return [ s ];
11951
+ }
11952
+ var parts = [];
11953
+ parts.push(s.substr(0,a));
11954
+ while (a != -1) {
11955
+ var a1 = s.indexOf(c, a + 1);
11956
+ if (a1 != -1) {
11957
+ parts.push(s.substr(a + 1, a1 - a - 1));
11958
+ } else {
11959
+ parts.push(s.substr(a + 1));
11960
+ }
11961
+ a = a1;
11962
+ }
11963
+ return parts;
11964
+ }
11965
+
11966
+ // The following function does what document.importNode(node, true)
11967
+ // would do for us here; however that method is broken in Safari/1.3,
11968
+ // so we have to emulate it.
11969
+ function xmlImportNode(doc, node) {
11970
+ if (node.nodeType == DOMNode.TEXT_NODE) {
11971
+ return domCreateTextNode(doc, node.nodeValue);
11972
+
11973
+ } else if (node.nodeType == DOMNode.CDATA_SECTION_NODE) {
11974
+ return domCreateCDATASection(doc, node.nodeValue);
11975
+
11976
+ } else if (node.nodeType == DOMNode.ELEMENT_NODE) {
11977
+ var newNode = domCreateElement(doc, node.nodeName);
11978
+ for (var i = 0; i < node.attributes.length; ++i) {
11979
+ var an = node.attributes[i];
11980
+ var name = an.nodeName;
11981
+ var value = an.nodeValue;
11982
+ domSetAttribute(newNode, name, value);
11983
+ }
11984
+
11985
+ for (var c = node.firstChild; c; c = c.nextSibling) {
11986
+ var cn = arguments.callee(doc, c);
11987
+ domAppendChild(newNode, cn);
11988
+ }
11989
+
11990
+ return newNode;
11991
+
11992
+ } else {
11993
+ return domCreateComment(doc, node.nodeName);
11994
+ }
11995
+ }
11996
+
11997
+ // A set data structure. It can also be used as a map (i.e. the keys
11998
+ // can have values other than 1), but we don't call it map because it
11999
+ // would be ambiguous in this context. Also, the map is iterable, so
12000
+ // we can use it to replace for-in loops over core javascript Objects.
12001
+ // For-in iteration breaks when Object.prototype is modified, which
12002
+ // some clients of the maps API do.
12003
+ //
12004
+ // NOTE(mesch): The set keys by the string value of its element, NOT
12005
+ // by the typed value. In particular, objects can't be used as keys.
12006
+ //
12007
+ // @constructor
12008
+ function Set() {
12009
+ this.keys = [];
12010
+ }
12011
+
12012
+ Set.prototype.size = function() {
12013
+ return this.keys.length;
12014
+ }
12015
+
12016
+ // Adds the entry to the set, ignoring if it is present.
12017
+ Set.prototype.add = function(key, opt_value) {
12018
+ var value = opt_value || 1;
12019
+ if (!this.contains(key)) {
12020
+ this[':' + key] = value;
12021
+ this.keys.push(key);
12022
+ }
12023
+ }
12024
+
12025
+ // Sets the entry in the set, adding if it is not yet present.
12026
+ Set.prototype.set = function(key, opt_value) {
12027
+ var value = opt_value || 1;
12028
+ if (!this.contains(key)) {
12029
+ this[':' + key] = value;
12030
+ this.keys.push(key);
12031
+ } else {
12032
+ this[':' + key] = value;
12033
+ }
12034
+ }
12035
+
12036
+ // Increments the key's value by 1. This works around the fact that
12037
+ // numbers are always passed by value, never by reference, so that we
12038
+ // can't increment the value returned by get(), or the iterator
12039
+ // argument. Sets the key's value to 1 if it doesn't exist yet.
12040
+ Set.prototype.inc = function(key) {
12041
+ if (!this.contains(key)) {
12042
+ this[':' + key] = 1;
12043
+ this.keys.push(key);
12044
+ } else {
12045
+ this[':' + key]++;
12046
+ }
12047
+ }
12048
+
12049
+ Set.prototype.get = function(key) {
12050
+ if (this.contains(key)) {
12051
+ return this[':' + key];
12052
+ } else {
12053
+ var undefined;
12054
+ return undefined;
12055
+ }
12056
+ }
12057
+
12058
+ // Removes the entry from the set.
12059
+ Set.prototype.remove = function(key) {
12060
+ if (this.contains(key)) {
12061
+ delete this[':' + key];
12062
+ removeFromArray(this.keys, key, true);
12063
+ }
12064
+ }
12065
+
12066
+ // Tests if an entry is in the set.
12067
+ Set.prototype.contains = function(entry) {
12068
+ return typeof this[':' + entry] != 'undefined';
12069
+ }
12070
+
12071
+ // Gets a list of values in the set.
12072
+ Set.prototype.items = function() {
12073
+ var list = [];
12074
+ for (var i = 0; i < this.keys.length; ++i) {
12075
+ var k = this.keys[i];
12076
+ var v = this[':' + k];
12077
+ list.push(v);
12078
+ }
12079
+ return list;
12080
+ }
12081
+
12082
+
12083
+ // Invokes function f for every key value pair in the set as a method
12084
+ // of the set.
12085
+ Set.prototype.map = function(f) {
12086
+ for (var i = 0; i < this.keys.length; ++i) {
12087
+ var k = this.keys[i];
12088
+ f.call(this, k, this[':' + k]);
12089
+ }
12090
+ }
12091
+
12092
+ Set.prototype.clear = function() {
12093
+ for (var i = 0; i < this.keys.length; ++i) {
12094
+ delete this[':' + this.keys[i]];
12095
+ }
12096
+ this.keys.length = 0;
12097
+ }
12098
+
12099
+
12100
+ // Applies the given function to each element of the array, preserving
12101
+ // this, and passing the index.
12102
+ function mapExec(array, func) {
12103
+ for (var i = 0; i < array.length; ++i) {
12104
+ func.call(this, array[i], i);
12105
+ }
12106
+ }
12107
+
12108
+ // Returns an array that contains the return value of the given
12109
+ // function applied to every element of the input array.
12110
+ function mapExpr(array, func) {
12111
+ var ret = [];
12112
+ for (var i = 0; i < array.length; ++i) {
12113
+ ret.push(func(array[i]));
12114
+ }
12115
+ return ret;
12116
+ };
12117
+
12118
+ // Reverses the given array in place.
12119
+ function reverseInplace(array) {
12120
+ for (var i = 0; i < array.length / 2; ++i) {
12121
+ var h = array[i];
12122
+ var ii = array.length - i - 1;
12123
+ array[i] = array[ii];
12124
+ array[ii] = h;
12125
+ }
12126
+ }
12127
+
12128
+ // Removes value from array. Returns the number of instances of value
12129
+ // that were removed from array.
12130
+ function removeFromArray(array, value, opt_notype) {
12131
+ var shift = 0;
12132
+ for (var i = 0; i < array.length; ++i) {
12133
+ if (array[i] === value || (opt_notype && array[i] == value)) {
12134
+ array.splice(i--, 1);
12135
+ shift++;
12136
+ }
12137
+ }
12138
+ return shift;
12139
+ }
12140
+
12141
+ // Shallow-copies an array to the end of another array
12142
+ // Basically Array.concat, but works with other non-array collections
12143
+ function copyArray(dst, src) {
12144
+ if (!src) return;
12145
+ var dstLength = dst.length;
12146
+ for (var i = src.length - 1; i >= 0; --i) {
12147
+ dst[i+dstLength] = src[i];
12148
+ }
12149
+ }
12150
+
12151
+ /**
12152
+ * This is an optimization for copying attribute lists in IE. IE includes many
12153
+ * extraneous properties in its DOM attribute lists, which take require
12154
+ * significant extra processing when evaluating attribute steps. With this
12155
+ * function, we ignore any such attributes that has an empty string value.
12156
+ */
12157
+ function copyArrayIgnoringAttributesWithoutValue(dst, src)
12158
+ {
12159
+ if (!src) return;
12160
+ for (var i = src.length - 1; i >= 0; --i) {
12161
+ // this test will pass so long as the attribute has a non-empty string
12162
+ // value, even if that value is "false", "0", "undefined", etc.
12163
+ if (src[i].nodeValue) {
12164
+ dst.push(src[i]);
12165
+ }
12166
+ }
12167
+ }
12168
+
12169
+ // Returns the text value of a node; for nodes without children this
12170
+ // is the nodeValue, for nodes with children this is the concatenation
12171
+ // of the value of all children. Browser-specific optimizations are used by
12172
+ // default; they can be disabled by passing "true" in as the second parameter.
12173
+ function xmlValue(node, disallowBrowserSpecificOptimization) {
12174
+ if (!node) {
12175
+ return '';
12176
+ }
12177
+
12178
+ var ret = '';
12179
+ if (node.nodeType == DOMNode.TEXT_NODE ||
12180
+ node.nodeType == DOMNode.CDATA_SECTION_NODE) {
12181
+ ret += node.nodeValue;
12182
+
12183
+ } else if (node.nodeType == DOMNode.ATTRIBUTE_NODE) {
12184
+ if (ajaxsltIsIE6) {
12185
+ ret += xmlValueIE6Hack(node);
12186
+ } else {
12187
+ ret += node.nodeValue;
12188
+ }
12189
+ } else if (node.nodeType == DOMNode.ELEMENT_NODE ||
12190
+ node.nodeType == DOMNode.DOCUMENT_NODE ||
12191
+ node.nodeType == DOMNode.DOCUMENT_FRAGMENT_NODE) {
12192
+ if (!disallowBrowserSpecificOptimization) {
12193
+ // IE, Safari, Opera, and friends
12194
+ var innerText = node.innerText;
12195
+ if (innerText != undefined) {
12196
+ return innerText;
12197
+ }
12198
+ // Firefox
12199
+ var textContent = node.textContent;
12200
+ if (textContent != undefined) {
12201
+ return textContent;
12202
+ }
12203
+ }
12204
+ // pobrecito!
12205
+ var len = node.childNodes.length;
12206
+ for (var i = 0; i < len; ++i) {
12207
+ ret += arguments.callee(node.childNodes[i]);
12208
+ }
12209
+ }
12210
+ return ret;
12211
+ }
12212
+
12213
+ function xmlValueIE6Hack(node) {
12214
+ // Issue 19, IE6 mangles href attribute when it's a javascript: url
12215
+ var nodeName = node.nodeName;
12216
+ var nodeValue = node.nodeValue;
12217
+ if (nodeName.length != 4) return nodeValue;
12218
+ if (!/^href$/i.test(nodeName)) return nodeValue;
12219
+ if (!/^javascript:/.test(nodeValue)) return nodeValue;
12220
+ return unescape(nodeValue);
12221
+ }
12222
+
12223
+ // Returns the representation of a node as XML text.
12224
+ function xmlText(node, opt_cdata) {
12225
+ var buf = [];
12226
+ xmlTextR(node, buf, opt_cdata);
12227
+ return buf.join('');
12228
+ }
12229
+
12230
+ function xmlTextR(node, buf, cdata) {
12231
+ if (node.nodeType == DOMNode.TEXT_NODE) {
12232
+ buf.push(xmlEscapeText(node.nodeValue));
12233
+
12234
+ } else if (node.nodeType == DOMNode.CDATA_SECTION_NODE) {
12235
+ if (cdata) {
12236
+ buf.push(node.nodeValue);
12237
+ } else {
12238
+ buf.push('<![CDATA[' + node.nodeValue + ']]>');
12239
+ }
12240
+
12241
+ } else if (node.nodeType == DOMNode.COMMENT_NODE) {
12242
+ buf.push('<!--' + node.nodeValue + '-->');
12243
+
12244
+ } else if (node.nodeType == DOMNode.ELEMENT_NODE) {
12245
+ buf.push('<' + xmlFullNodeName(node));
12246
+ for (var i = 0; i < node.attributes.length; ++i) {
12247
+ var a = node.attributes[i];
12248
+ if (a && a.nodeName && a.nodeValue) {
12249
+ buf.push(' ' + xmlFullNodeName(a) + '="' +
12250
+ xmlEscapeAttr(a.nodeValue) + '"');
12251
+ }
12252
+ }
12253
+
12254
+ if (node.childNodes.length == 0) {
12255
+ buf.push('/>');
12256
+ } else {
12257
+ buf.push('>');
12258
+ for (var i = 0; i < node.childNodes.length; ++i) {
12259
+ arguments.callee(node.childNodes[i], buf, cdata);
12260
+ }
12261
+ buf.push('</' + xmlFullNodeName(node) + '>');
12262
+ }
12263
+
12264
+ } else if (node.nodeType == DOMNode.DOCUMENT_NODE ||
12265
+ node.nodeType == DOMNode.DOCUMENT_FRAGMENT_NODE) {
12266
+ for (var i = 0; i < node.childNodes.length; ++i) {
12267
+ arguments.callee(node.childNodes[i], buf, cdata);
12268
+ }
12269
+ }
12270
+ }
12271
+
12272
+ function xmlFullNodeName(n) {
12273
+ if (n.prefix && n.nodeName.indexOf(n.prefix + ':') != 0) {
12274
+ return n.prefix + ':' + n.nodeName;
12275
+ } else {
12276
+ return n.nodeName;
12277
+ }
12278
+ }
12279
+
12280
+ // Escape XML special markup chracters: tag delimiter < > and entity
12281
+ // reference start delimiter &. The escaped string can be used in XML
12282
+ // text portions (i.e. between tags).
12283
+ function xmlEscapeText(s) {
12284
+ return ('' + s).replace(/&/g, '&amp;').replace(/</g, '&lt;').
12285
+ replace(/>/g, '&gt;');
12286
+ }
12287
+
12288
+ // Escape XML special markup characters: tag delimiter < > entity
12289
+ // reference start delimiter & and quotes ". The escaped string can be
12290
+ // used in double quoted XML attribute value portions (i.e. in
12291
+ // attributes within start tags).
12292
+ function xmlEscapeAttr(s) {
12293
+ return xmlEscapeText(s).replace(/\"/g, '&quot;');
12294
+ }
12295
+
12296
+ // Escape markup in XML text, but don't touch entity references. The
12297
+ // escaped string can be used as XML text (i.e. between tags).
12298
+ function xmlEscapeTags(s) {
12299
+ return s.replace(/</g, '&lt;').replace(/>/g, '&gt;');
12300
+ }
12301
+
12302
+ /**
12303
+ * Wrapper function to access the owner document uniformly for document
12304
+ * and other nodes: for the document node, the owner document is the
12305
+ * node itself, for all others it's the ownerDocument property.
12306
+ *
12307
+ * @param {Node} node
12308
+ * @return {Document}
12309
+ */
12310
+ function xmlOwnerDocument(node) {
12311
+ if (node.nodeType == DOMNode.DOCUMENT_NODE) {
12312
+ return node;
12313
+ } else {
12314
+ return node.ownerDocument;
12315
+ }
12316
+ }
12317
+
12318
+ // Wrapper around DOM methods so we can condense their invocations.
12319
+ function domGetAttribute(node, name) {
12320
+ return node.getAttribute(name);
12321
+ }
12322
+
12323
+ function domSetAttribute(node, name, value) {
12324
+ return node.setAttribute(name, value);
12325
+ }
12326
+
12327
+ function domRemoveAttribute(node, name) {
12328
+ return node.removeAttribute(name);
12329
+ }
12330
+
12331
+ function domAppendChild(node, child) {
12332
+ return node.appendChild(child);
12333
+ }
12334
+
12335
+ function domRemoveChild(node, child) {
12336
+ return node.removeChild(child);
12337
+ }
12338
+
12339
+ function domReplaceChild(node, newChild, oldChild) {
12340
+ return node.replaceChild(newChild, oldChild);
12341
+ }
12342
+
12343
+ function domInsertBefore(node, newChild, oldChild) {
12344
+ return node.insertBefore(newChild, oldChild);
12345
+ }
12346
+
12347
+ function domRemoveNode(node) {
12348
+ return domRemoveChild(node.parentNode, node);
12349
+ }
12350
+
12351
+ function domCreateTextNode(doc, text) {
12352
+ return doc.createTextNode(text);
12353
+ }
12354
+
12355
+ function domCreateElement(doc, name) {
12356
+ return doc.createElement(name);
12357
+ }
12358
+
12359
+ function domCreateAttribute(doc, name) {
12360
+ return doc.createAttribute(name);
12361
+ }
12362
+
12363
+ function domCreateCDATASection(doc, data) {
12364
+ return doc.createCDATASection(data);
12365
+ }
12366
+
12367
+ function domCreateComment(doc, text) {
12368
+ return doc.createComment(text);
12369
+ }
12370
+
12371
+ function domCreateDocumentFragment(doc) {
12372
+ return doc.createDocumentFragment();
12373
+ }
12374
+
12375
+ function domGetElementById(doc, id) {
12376
+ return doc.getElementById(id);
12377
+ }
12378
+
12379
+ // Same for window methods.
12380
+ function windowSetInterval(win, fun, time) {
12381
+ return win.setInterval(fun, time);
12382
+ }
12383
+
12384
+ function windowClearInterval(win, id) {
12385
+ return win.clearInterval(id);
12386
+ }
12387
+
12388
+ /**
12389
+ * Escape the special regular expression characters when the regular expression
12390
+ * is specified as a string.
12391
+ *
12392
+ * Based on: http://simonwillison.net/2006/Jan/20/escape/
12393
+ */
12394
+ RegExp.escape = (function() {
12395
+ var specials = [
12396
+ '/', '.', '*', '+', '?', '|', '^', '$',
12397
+ '(', ')', '[', ']', '{', '}', '\\'
12398
+ ];
12399
+
12400
+ var sRE = new RegExp(
12401
+ '(\\' + specials.join('|\\') + ')', 'g'
12402
+ );
12403
+
12404
+ return function(text) {
12405
+ return text.replace(sRE, '\\$1');
12406
+ }
12407
+ })();
12408
+
12409
+ /**
12410
+ * Determines whether a predicate expression contains a "positional selector".
12411
+ * A positional selector filters nodes from the nodelist input based on their
12412
+ * position within that list. When such selectors are encountered, the
12413
+ * evaluation of the predicate cannot be depth-first, because the positional
12414
+ * selector may be based on the result of evaluating predicates that precede
12415
+ * it.
12416
+ */
12417
+ function predicateExprHasPositionalSelector(expr, isRecursiveCall) {
12418
+ if (!expr) {
12419
+ return false;
12420
+ }
12421
+ if (!isRecursiveCall && exprReturnsNumberValue(expr)) {
12422
+ // this is a "proximity position"-based predicate
12423
+ return true;
12424
+ }
12425
+ if (expr instanceof FunctionCallExpr) {
12426
+ var value = expr.name.value;
12427
+ return (value == 'last' || value == 'position');
12428
+ }
12429
+ if (expr instanceof BinaryExpr) {
12430
+ return (
12431
+ predicateExprHasPositionalSelector(expr.expr1, true) ||
12432
+ predicateExprHasPositionalSelector(expr.expr2, true));
12433
+ }
12434
+ return false;
12435
+ }
12436
+
12437
+ function exprReturnsNumberValue(expr) {
12438
+ if (expr instanceof FunctionCallExpr) {
12439
+ var isMember = {
12440
+ last: true
12441
+ , position: true
12442
+ , count: true
12443
+ , 'string-length': true
12444
+ , number: true
12445
+ , sum: true
12446
+ , floor: true
12447
+ , ceiling: true
12448
+ , round: true
12449
+ };
12450
+ return isMember[expr.name.value];
12451
+ }
12452
+ else if (expr instanceof UnaryMinusExpr) {
12453
+ return true;
12454
+ }
12455
+ else if (expr instanceof BinaryExpr) {
12456
+ var isMember = {
12457
+ '+': true
12458
+ , '-': true
12459
+ , '*': true
12460
+ , mod: true
12461
+ , div: true
12462
+ };
12463
+ return isMember[expr.op.value];
12464
+ }
12465
+ else if (expr instanceof NumberExpr) {
12466
+ return true;
12467
+ }
12468
+ return false;
12469
+ }
12470
+
12471
+
12472
+ $debug("Defining Event");
12473
+ /*
12474
+ * event.js
12475
+ */
12476
+ var Event = function(options){
12477
+ options={};
12478
+ __extend__(this,{
12479
+ CAPTURING_PHASE : 1,
12480
+ AT_TARGET : 2,
12481
+ BUBBLING_PHASE : 3
12482
+ });
12483
+ $debug("Creating new Event");
12484
+ var $bubbles = options.bubbles?options.bubbles:true,
12485
+ $cancelable = options.cancelable?options.cancelable:true,
12486
+ $currentTarget = options.currentTarget?options.currentTarget:null,
12487
+ $eventPhase = options.eventPhase?options.eventPhase:Event.CAPTURING_PHASE,
12488
+ $target = options.target?options.target:null,
12489
+ $timestamp = options.timestamp?options.timestamp:new Date().getTime().toString(),
12490
+ $type = options.type?options.type:"";
12491
+ return __extend__(this,{
12492
+ get bubbles(){return $bubbles;},
12493
+ get cancelable(){return $cancelable;},
12494
+ get currentTarget(){return $currentTarget;},
12495
+ get eventPhase(){return $eventPhase;},
8882
12496
  get target(){return $target;},
8883
12497
  set target(target){ $target = target;},
8884
12498
  get timestamp(){return $timestamp;},
@@ -9280,6 +12894,14 @@ $debug("Initializing Window Location.");
9280
12894
  var $location = '';
9281
12895
 
9282
12896
  $w.__defineSetter__("location", function(url){
12897
+ if (url[0] === "#") {
12898
+ return;
12899
+ }
12900
+ var now = window.location.href.replace(/^file:\/\//,"").replace(/#.*/,"");
12901
+ var to = $env.location(url,window.location.href != "about:blank" ? window.location.href: undefined);
12902
+ if (to.indexOf(now)===0 && to[now.length]==="#") {
12903
+ return;
12904
+ }
9283
12905
  if( !$location || $location == "about:blank" ) {
9284
12906
  // $w.__loadAWindowsDocument__(url);
9285
12907
  $env.load(url);
@@ -9734,6 +13356,7 @@ $w.removeEventListener = function(type, fn){
9734
13356
 
9735
13357
 
9736
13358
  function __dispatchEvent__(target, event, bubbles){
13359
+ try{
9737
13360
  //$debug("dispatching event " + event.type);
9738
13361
 
9739
13362
  //the window scope defines the $event object, for IE(^^^) compatibility;
@@ -9766,6 +13389,55 @@ function __dispatchEvent__(target, event, bubbles){
9766
13389
  //$debug('calling event handler on'+event.type+' on target '+target);
9767
13390
  target["on" + event.type](event);
9768
13391
  }
13392
+
13393
+ // SMP FIX: cancel/stop prop
13394
+
13395
+ // print(event.type,target);
13396
+
13397
+ if ( event.type == "click" && target instanceof HTMLAnchorElement && target.href ) {
13398
+ window.location = target.href;
13399
+ }
13400
+
13401
+ if ( event.type == "click" &&
13402
+ target.form &&
13403
+ ( target instanceof HTMLInputElement || target instanceof HTMLTypeValueInputs ) &&
13404
+ ( ( target.tagName === "INPUT" &&
13405
+ (target.type === "submit" ||
13406
+ target.type === "image" ) ) ||
13407
+ ( target.tagName === "BUTTON" &&
13408
+ ( !target.type ||
13409
+ target.type === "submit" ) ) ) ) {
13410
+ target.form.clk = target;
13411
+ try{
13412
+ __submit__(target.form);
13413
+ }catch(e){print("oops",e);print(e.stack);};
13414
+ delete target.form.clk;
13415
+ }
13416
+
13417
+ // print(event.type,target.type,target.constructor+"");
13418
+ if ( event.type == "click" && target instanceof HTMLInputElement && target.type == "checkbox" ) {
13419
+ target.checked = target.checked ? "" : "checked";
13420
+ }
13421
+
13422
+ if ((event.type == "submit") && target instanceof HTMLFormElement) {
13423
+ $env.unload($w);
13424
+ var proxy = $w.window;
13425
+ var data;
13426
+ var boundary;
13427
+ if (target.enctype === "multipart/form-data") {
13428
+ boundary = (new Date).getTime();
13429
+ }
13430
+ data = __formSerialize__(target,undefined,boundary);
13431
+ var options = {method: target.method || "get", data: data};
13432
+ if (boundary) {
13433
+ options["Content-Type"] = "multipart/form-data; boundary="+boundary;
13434
+ } else {
13435
+ options["Content-Type"] = 'application/x-www-form-urlencoded';
13436
+ }
13437
+ var action = target.action || window.location.href;
13438
+ $env.reload(proxy, action, options );
13439
+ }
13440
+
9769
13441
  }else{
9770
13442
  //$debug("non target: " + event.target + " \n this->"+target);
9771
13443
  }
@@ -9773,6 +13445,10 @@ function __dispatchEvent__(target, event, bubbles){
9773
13445
  //$debug('bubbling to parentNode '+target.parentNode);
9774
13446
  __dispatchEvent__(target.parentNode, event, bubbles);
9775
13447
  }
13448
+ }catch(e){
13449
+ print("oops",e);
13450
+ print(e.stack);
13451
+ }
9776
13452
  };
9777
13453
 
9778
13454
  $w.dispatchEvent = function(event, bubbles){
@@ -9839,7 +13515,9 @@ XMLHttpRequest.prototype = {
9839
13515
  var _this = this;
9840
13516
 
9841
13517
  function makeRequest(){
13518
+ // print("MR",$env.connection);
9842
13519
  $env.connection(_this, function(){
13520
+ // print("MC");
9843
13521
  if (_this.$continueProcessing){
9844
13522
  var responseXML = null;
9845
13523
  _this.__defineGetter__("responseXML", function(){
@@ -9874,6 +13552,8 @@ XMLHttpRequest.prototype = {
9874
13552
  _this.onreadystatechange();
9875
13553
  }
9876
13554
 
13555
+ try{
13556
+ // print("pk");
9877
13557
  if (this.async){
9878
13558
  $debug("XHR sending asynch;");
9879
13559
  $env.runAsync(makeRequest);
@@ -9881,6 +13561,8 @@ XMLHttpRequest.prototype = {
9881
13561
  $debug("XHR sending synch;");
9882
13562
  makeRequest();
9883
13563
  }
13564
+ }catch(e){print("oopsy",e);}
13565
+
9884
13566
  },
9885
13567
  abort: function(){
9886
13568
  this.$continueProcessing = false;
@@ -21652,7 +25334,7 @@ Html5Parser();
21652
25334
  Envjs.evaluate = $env.$master.evaluate;
21653
25335
 
21654
25336
  // $w.__loadAWindowsDocument__(options.url || "about:blank");
21655
- $env.load(options.url || "about:blank");
25337
+ $env.load(options.url || "about:blank", options.xhr);
21656
25338
  };
21657
25339
 
21658
25340
  return $env;