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