1 /**
  2  * @fileoverview A class representing a pilot to traverse the 
  3  * tokens of a XML tree.
  4  */
  5 
  6 goog.provide('xrx.pilot');
  7 
  8 
  9 
 10 goog.require('xrx.label');
 11 goog.require('xrx.stream');
 12 goog.require('xrx.token');
 13 goog.require('xrx.token.Attribute');
 14 goog.require('xrx.token.AttrName');
 15 goog.require('xrx.token.AttrValue');
 16 goog.require('xrx.token.EmptyTag');
 17 goog.require('xrx.token.EndTag');
 18 goog.require('xrx.token.Namespace');
 19 goog.require('xrx.token.NotTag');
 20 goog.require('xrx.token.NsPrefix');
 21 goog.require('xrx.token.NsUri');
 22 goog.require('xrx.token.Root');
 23 goog.require('xrx.token.StartEmptyTag');
 24 goog.require('xrx.token.StartTag');
 25 goog.require('xrx.token.Tag');
 26 goog.require('xrx.token.TagName');
 27 
 28 
 29 
 30 /**
 31  * Constructs a new pilot. A pilot is able to traverse a XML tree
 32  * in forward and backward direction.
 33  * 
 34  * @param {!string} xml A XML string. 
 35  * @constructor
 36  */
 37 xrx.pilot = function(xml) {
 38 
 39 
 40   /**
 41    * Initialize the XML stream.
 42    * 
 43    * @type {xrx.stream}
 44    * @private
 45    */
 46   this.stream_ = new xrx.stream(xml);
 47 
 48 
 49 
 50   /**
 51    * Path lastly used to traverse the XML tree 
 52    * (for debugging only).
 53    * 
 54    * @param {}
 55    * @private
 56    */
 57   this.currentPath_;
 58 };
 59 
 60 
 61 xrx.pilot.prototype.stream = function() {
 62   return this.stream_;
 63 };
 64 
 65 
 66 /**
 67  * Returns the path lastly used to traverse the XML tree 
 68  * (for debugging only).
 69  * @return {?}
 70  */
 71 xrx.pilot.prototype.currentPath = function() {
 72   return this.currentPath_;
 73 };
 74 
 75 
 76 
 77 /**
 78  * Returns a token string or the content of the XML stream.
 79  * @return {!string}
 80  */
 81 xrx.pilot.prototype.xml = function(token) {
 82   return token === undefined ? this.stream_.xml() : 
 83     this.stream_.xml().substr(token.offset(), token.length());
 84 };
 85 
 86 
 87 
 88 /**
 89  * Stops the XML stream.
 90  * @private
 91  */
 92 xrx.pilot.prototype.stop = function() {
 93   this.stream_.stop();
 94 };
 95 
 96 
 97 
 98 /**
 99  * Forward tree traversing.
100  * 
101  * @param {?} target The target token.
102  */
103 xrx.pilot.prototype.forward = function(context, target) {
104   var tok, startAt = 0;
105   var pilot = this;
106   var label; 
107   if(context === null) {
108     label = new xrx.label();
109   } else {
110     label = context.label().clone();
111     label.precedingSibling();
112     startAt = context.offset();
113   }
114   var lastTag = context ? xrx.token.NOT_TAG : xrx.token.UNDEFINED;
115 
116   pilot.stream_.rowStartTag = function(offset, length1, length2) {
117     lastTag === xrx.token.UNDEFINED || lastTag === xrx.token.START_TAG ? label.child() : label.nextSibling();
118     
119     var tmp;
120     if (target.type() === xrx.token.NOT_TAG) {
121       tmp = label.clone();
122       tmp.push0();
123     }
124 
125     if (target.compare(xrx.token.START_TAG, label)) {
126       tok = new xrx.token.StartTag(label.clone());
127       tok.offset(offset);
128       tok.length(length1);
129       pilot.stop();
130     } else if (target.compare(xrx.token.NOT_TAG, tmp)) {
131       tok = new xrx.token.NotTag(tmp);
132       tok.offset(offset + length1);
133       tok.length(length2 - length1);
134       pilot.stop();
135     } else {}
136     lastTag = xrx.token.START_TAG;
137   };
138   
139   pilot.stream_.rowEndTag = function(offset, length1, length2) {
140     lastTag === xrx.token.START_TAG ? null : label.parent();
141     if (target.compare(xrx.token.END_TAG, label)) {
142       tok = new xrx.token.EndTag(label.clone());
143       tok.offset(offset);
144       tok.length(length1);
145       pilot.stop();
146     } else if (target.compare(xrx.token.NOT_TAG, label)) {
147       tok = new xrx.token.NotTag(label);
148       tok.offset(offset + length1);
149       tok.length(length2 - length1);
150       pilot.stop();
151     } else {}
152     lastTag = xrx.token.END_TAG;
153   };
154   
155   pilot.stream_.rowEmptyTag = function(offset, length1, length2) {
156     lastTag === xrx.token.UNDEFINED || lastTag === xrx.token.START_TAG ? label.child() : label.nextSibling();    
157     if (target.compare(xrx.token.EMPTY_TAG, label)) {
158       tok = new xrx.token.EmptyTag(label.clone());
159       tok.offset(offset);
160       tok.length(length1);
161       pilot.stop();
162     } else if (target.compare(xrx.token.NOT_TAG, label)) {
163       tok = new xrx.token.NotTag(label);
164       tok.offset(offset + length1);
165       tok.length(length2 - length1);
166       pilot.stop();
167     } else {}
168     lastTag = xrx.token.END_TAG;
169   };
170   
171   pilot.stream_.forward(startAt);
172 
173   return tok;
174 };
175 
176 
177 
178 /**
179  * Backward tree traversing.
180  * 
181  * @param {?} target The target token.
182  */
183 xrx.pilot.prototype.backward = function(context, target) {
184   var tok, startAt = 0;
185   var pilot = this;
186   var label; 
187   if (context === null) {
188     label = new xrx.label();
189   } else {
190     label = context.label().clone();
191     startAt = context.offset();
192   }
193   var lastTag = xrx.token.START_TAG;
194   
195   pilot.stream_.rowStartTag = function(offset, length1, length2) {
196     lastTag === xrx.token.END_TAG ? null : label.parent();
197     if (target.compare(xrx.token.START_TAG, label)) {
198       tok = new xrx.token.StartTag(label.clone());
199       tok.offset(offset);
200       tok.length(length1);
201       pilot.stop();
202     }
203     lastTag = xrx.token.START_TAG;
204   };
205   
206   pilot.stream_.rowEndTag = function(offset, length1, length2) {
207     lastTag === xrx.token.UNDEFINED || lastTag === xrx.token.END_TAG ?
208         label.child() : label.precedingSibling();
209     if (target.compare(xrx.token.END_TAG, label)) {
210       tok = new xrx.token.EndTag(label.clone());
211       tok.offset(offset);
212       tok.length(length1);
213       pilot.stop();
214     }
215     lastTag = xrx.token.END_TAG;
216   };
217   
218   pilot.stream_.rowEmptyTag = function(offset, length1, length2) {
219     lastTag === xrx.token.UNDEFINED || lastTag === xrx.token.END_TAG ?
220         label.child() : label.precedingSibling();
221     if (target.compare(xrx.token.EMPTY_TAG, label)) {
222       tok = new xrx.token.EmptyTag(label.clone());
223       tok.offset(offset);
224       tok.length(length1);
225       pilot.stop();
226     }
227     lastTag = xrx.token.START_TAG;
228   };
229   
230   pilot.stream_.rowNotTag = function(offset, length1, length2) {
231     // not used for backward streams
232   };
233 
234   pilot.stream_.backward(startAt);
235 
236   return tok;
237 };
238 
239 
240 
241 /**
242  * Returns the joint parent of two tags.
243  * 
244  * @private
245  * @param {!xrx.token.StartEmptyTag} tag The overloaded tag.
246  * @return {!xrx.token.StartEmptyTag}
247  */
248 xrx.pilot.prototype.jointParent = function(context, tag) {
249   var label = context.label().jointParent(tag.label());
250 
251   return new xrx.token.StartEmptyTag(label);
252 };
253 
254 
255 
256 /**
257  * Helper function for xrx.pilot.prototype.path.
258  * 
259  * @private
260  * @param {!xrx.token.StartEmptyTag} tag The target tag.
261  * @return {!xrx.token.StartEmptyTag}
262  */
263 xrx.pilot.prototype.onLocation = function(tag) {
264   return tag;
265 };
266 
267 
268 /**
269  * Helper function for xrx.pilot.prototype.path.
270  * 
271  * @private
272  * @param {!xrx.token.StartEmptyTag} tag The target tag.
273  * @return {!xrx.token.StartEmptyTag}
274  */
275 xrx.pilot.prototype.zigzag = function(context, tag) {
276 
277   // we first traverse to the joint parent tag
278   var jointParent = this.backward(context, this.jointParent(context, tag));
279   
280   // then we've (a) already found the target tag or (b) we 
281   // stream forward. We can use xrx.pilot.path short hand for 
282   // (a) and (b).
283   return this.path(jointParent, tag);
284 };
285 
286 
287 /**
288  * 
289  */
290 xrx.pilot.prototype.path = function(context, tag) {
291 
292   if (context === null) {
293     return this.forward(context, tag);
294   } else if (context.sameAs(tag)) {
295     // context is already at the right place?
296     // => return tag
297     this.currentPath_ = 'xrx.pilot.prototype.onLocation';
298     return this.onLocation(tag);
299   } else if (context.isBefore(tag)) {
300     // context is placed before the target tag?
301     // => stream forward
302     this.currentPath_ = 'xrx.pilot.prototype.forward';
303     return this.forward(context, tag);
304   } else {
305     // context is placed after the target tag?
306     // => zigzag course
307     var zigzag = this.zigzag(context, tag);
308     this.currentPath_ = 'xrx.pilot.prototype.zigzag';
309     return zigzag;
310   }
311 };
312 
313 
314 
315 /**
316  * Returns the location of a token.
317  * @return {?}
318  */
319 xrx.pilot.prototype.location = function(opt_context, target) {
320   return this.update(opt_context, target);
321 };
322 
323 
324 
325 /**
326  * Updates a token and returns its location.
327  */
328 xrx.pilot.prototype.update = function(context, target, update) {
329   var token;
330   
331   switch(target.type()) {
332   // primary tokens
333   case xrx.token.START_TAG:
334     token = this.startTag(context, target, update);
335     break;
336   case xrx.token.END_TAG:
337     token = this.endTag(context, target, update);
338     break;
339   case xrx.token.EMPTY_TAG:
340     token = this.emptyTag(context, target, update);
341     break;
342   case xrx.token.NOT_TAG:
343     token = this.notTag(context, target, update);
344     break;
345   // secondary tokens
346   case xrx.token.TAG_NAME:
347     throw Error('Not supported. Use function xrx.pilot.tagName instead.');
348     break;
349   case xrx.token.ATTRIBUTE:
350     token = this.attribute(context, target, update);
351     break;
352   case xrx.token.ATTR_NAME:
353     token = this.attrName(context, target, update);
354     break;
355   case xrx.token.ATTR_VALUE:
356     token = this.attrValue(context, target, update);
357     break;
358   // generic tokens
359   case xrx.token.START_EMPTY_TAG:
360     token = this.startEmptyTag(context, target, update);
361     break;
362   // TODO: complex tokens 
363   default:
364     throw Error('Unknown type.');
365     break;
366   }
367   return token;
368 };
369 
370 
371 
372 /**
373  * 
374  */
375 xrx.pilot.prototype.startTag = function(context, target, opt_update) {
376   var startTag = this.path(context, target);
377 
378   return startTag;
379 };
380 
381 
382 
383 /**
384  * 
385  */
386 xrx.pilot.prototype.endTag = function(context, target, opt_update) {
387   var endTag = this.path(context, target);
388 
389   return endTag;
390 };
391 
392 
393 
394 /**
395  * 
396  */
397 xrx.pilot.prototype.emptyTag = function(context, target, opt_update) {
398   var emptyTag = this.path(context, target);
399 
400   return emptyTag;
401 };
402 
403 
404 /**
405  * Shared utility function for all secondary tokens.
406  * @private
407  */
408 xrx.pilot.prototype.secondaryToken_ = function(context, target, streamFunc, tokenConstr) {
409 
410   
411   return token;
412 };
413 
414 
415 
416 /**
417  * Get the location and optionally update a xrx.token.TagName.
418  * 
419  * @param {?} context
420  * @param {(!xrx.token.StartTag|!xrx.token.EndTag|!xrx.token.EmptyTag
421  * |!xrx.token.StartEmptyTag|!xrx.token.Tag)} target The tag to which 
422  * the tag-name belongs. 
423  * @param {?string} opt_update The new tag-name.
424  * @return {!xrx.token.TagName}
425  */
426 xrx.pilot.prototype.tagName = function(context, target, opt_update) {
427   var pos = this.stream_.pos();
428   var tag = this.path(context, target);
429   var xml = this.stream_.xml().substr(tag.offset(), tag.length());
430   var location = this.stream_.tagName(xml);
431   var tagName = new xrx.token.TagName(tag.label().clone(), 
432       location.offset + tag.offset(), location.length);
433   
434   // reset the stream reader position, important!
435   this.stream_.pos(pos);
436 
437   return !opt_update ? tagName : 
438       xrx.update.tagName(this.stream_, tagName, opt_update);
439 };
440 
441 
442 
443 /**
444  * Get the location and optionally update a xrx.token.Attribute.
445  * 
446  * @param {?} context
447  * @param {!xrx.token.Attribute} target The attribute token.
448  * @param {?string} opt_update The new attribute.
449  * @return {!xrx.token.Attribute}
450  */
451 xrx.pilot.prototype.attribute = function(context, target, opt_update) {
452   var pos = this.stream_.pos();
453   var tag = this.path(context, target.tag());
454   var xml = this.stream_.xml().substr(tag.offset(), tag.length());
455   var location = this.stream_.attribute(xml, target.label().last());
456   var attribute = new xrx.token.Attribute(target.label().clone(), 
457       location.offset + tag.offset(), location.length);
458   
459   // reset the stream reader position, important!
460   this.stream_.pos(pos);
461   
462   return !opt_update ? attribute :
463       xrx.update.attribute(this.stream_, attribute, opt_update);
464 };
465 
466 
467 
468 /**
469  * Get the location and optionally update a xrx.token.AttrName.
470  * 
471  * @param {?} context
472  * @param {!xrx.token.AttrName} target The attribute-name token.
473  * @param {?string} opt_update The new attribute-name.
474  * @return {!xrx.token.AttrName}
475  */
476 xrx.pilot.prototype.attrName = function(context, target, opt_update) {
477   var pos = this.stream_.pos();
478   var tag = this.path(context, target.tag());
479   var xml = this.stream_.xml().substr(tag.offset(), tag.length());
480   var location = this.stream_.attrName(xml, target.label().last());
481   var attrName = new xrx.token.AttrName(target.label().clone(), 
482       location.offset + tag.offset(), location.length);
483   
484   // reset the stream reader position, important!
485   this.stream_.pos(pos);
486 
487   return opt_update === undefined ? attrName :
488       xrx.update.attrName(this.stream_, attrName, opt_update);  
489 };
490 
491 
492 
493 /**
494  * Get the location and optionally update a xrx.token.AttrValue.
495  * 
496  * @param {?} context
497  * @param {!xrx.token.AttrValue} target The attribute-value token.
498  * @param {?string} opt_update The new attribute-value.
499  * @return {!xrx.token.AttrValue}
500  */
501 xrx.pilot.prototype.attrValue = function(context, target, opt_update) {
502   var pos = this.stream_.pos();
503   var tag = this.path(context, target.tag());
504   var xml = this.stream_.xml().substr(tag.offset(), tag.length());
505   var location = this.stream_.attrValue(xml, target.label().last());
506   var attrValue = new xrx.token.AttrValue(target.label().clone(), 
507       location.offset + tag.offset(), location.length);
508   
509   // reset the stream reader position, important!
510   this.stream_.pos(pos);  
511 
512   return opt_update === undefined ? attrValue :
513       xrx.update.attrValue(this.stream_, attrValue, opt_update);
514 };
515 
516 
517 
518 /**
519  * Get the location and optionally update a xrx.token.NotTag.
520  * 
521  * @param {?} context
522  * @param {!xrx.token.NotTag} target The not-tag token.
523  * @param {?string} opt_update The new not-tag.
524  * @return {!xrx.token.NotTag}
525  */
526 xrx.pilot.prototype.notTag = function(context, target, opt_update) {
527   var notTag = this.path(context, target);
528 
529   return opt_update === undefined ? notTag :
530       xrx.update.notTag(this.stream_, notTag, opt_update);
531 };
532 
533 
534 
535 xrx.pilot.prototype.startEmptyTag = function(context, target, opt_update) {
536   var startEmptyTag = this.path(context, target);
537 
538   return opt_update === undefined ? startEmptyTag :
539       xrx.update.startEmptyTag(this.stream_, startEmptyTag, opt_update);
540 };
541 
542 
543 
544 xrx.pilot.prototype.tag = function(context, target, opt_update) {
545   
546 };
547 
548 
549 /**
550  * Get or update an array of xrx.token.Attribute.
551  * 
552  * @param {?} context
553  * @param {!xrx.token.StartEmptyTag} target The start-empty tag.
554  * @param {?Array.<xrx.token.Attribute>} opt_update Array of new attribute tokens.
555  * @return {!Array.<xrx.token.Attribute>}
556  */
557 xrx.pilot.prototype.attributes = function(context, target, opt_update) {
558   var pos = this.stream_.pos();
559   var tag = this.path(context, target);
560   var xml = this.stream_.xml().substr(tag.offset(), tag.length());
561   var locations = this.stream_.attributes(xml);
562   var attributes = [];
563   var label = target.label().clone();
564   label.child();
565   for (var pos in locations) {
566     var location = locations[pos];
567     attributes.push(new xrx.token.Attribute(label.clone(),
568         location.offset + tag.offset(), location.length));
569     label.nextSibling();
570   }
571   
572   // reset the stream reader position, important!
573   this.stream_.pos(pos); 
574   console.log(attributes);
575   return opt_update === undefined ? attributes : 
576       xrx.update.attributes(this.stream_, attributes, opt_update);
577 };
578