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