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