1 |
// ----------------------------------------------------------------------------- |
2 |
// IWF - Interactive Website Framework. Javascript library for creating |
3 |
// responsive thin client interfaces. |
4 |
// |
5 |
// Copyright (C) 2005 Brock Weaver brockweaver@gmail.com |
6 |
// |
7 |
// This library is free software; you can redistribute it and/or modify |
8 |
// it under the terms of the GNU Lesser General Public License as published |
9 |
// by the Free Software Foundation; either version 2.1 of the License, or |
10 |
// (at your option) any later version. |
11 |
// |
12 |
// This library is distributed in the hope that it will be useful, but |
13 |
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
14 |
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
15 |
// License for more details. |
16 |
// |
17 |
// You should have received a copy of the GNU Lesser General Public License |
18 |
// along with this library; if not, write to the Free Software Foundation, |
19 |
// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
20 |
// |
21 |
// Brock Weaver |
22 |
// brockweaver@gmail.com |
23 |
// 1605 NW Maple Pl |
24 |
// Ankeny, IA 50021 |
25 |
// ----------------------------------------------------------------------------- |
26 |
|
27 |
// -------------------------------------------------------------------------- |
28 |
// iwfxml.js |
29 |
// |
30 |
// Javascript-based xml parser |
31 |
// |
32 |
// Dependencies: |
33 |
// iwfcore.js |
34 |
// |
35 |
// Brock Weaver - brockweaver@sourceforge.net - iwf.sourceforge.net |
36 |
// v 0.1 - 2005-06-05 |
37 |
// Initial release. |
38 |
// -------------------------------------------------------------------------- |
39 |
// This class is meant to ease the burden of parsing xml. |
40 |
// Xml is parsed into sets of arrays, in a hierarchical format. |
41 |
// Each node is an array (of length 1, if applicable) |
42 |
// The root node is an exception, as it is simply a node and not an array, |
43 |
// as there is always only a single root node. |
44 |
// Each attribute is a property of the current element in the node array. |
45 |
// Useful for loading xml from server into a nice, easy-to-use format |
46 |
// -------------------------------------------------------------------------- |
47 |
|
48 |
if (!window.iwfGetById){ |
49 |
iwfLog("IWF Dependency Error: iwfxml.js is dependent upon iwfcore.js, so you *must* reference that file first.\n\nExample:\n\n<script type='text/javascript' src='iwfcore.js'></script>\n<script type='text/javascript' src='iwfxml.js'></script>", true); |
50 |
} |
51 |
|
52 |
|
53 |
// ----------------------------------- |
54 |
// Begin: Xml Document Object |
55 |
// ----------------------------------- |
56 |
|
57 |
function iwfXmlDoc(xml){ |
58 |
if (!xml || xml.length == 0){ |
59 |
return; |
60 |
} |
61 |
this.loadXml(xml); |
62 |
} |
63 |
|
64 |
iwfXmlDoc.prototype.loadXml = function(xml){ |
65 |
|
66 |
// note this root node is not an array, as there will always |
67 |
// be exactly 1 root. |
68 |
var node = new iwfXmlNode(xml); |
69 |
this.childNodes = new Array(); |
70 |
this.childNodes.push(node); |
71 |
this[node.nodeName] = node; |
72 |
|
73 |
this._html = false; |
74 |
|
75 |
} |
76 |
|
77 |
iwfXmlDoc.prototype.toString = function(pretty){ |
78 |
this._html = false; |
79 |
return this.outerXml(pretty); |
80 |
} |
81 |
|
82 |
iwfXmlDoc.prototype.outerXml = function(pretty){ |
83 |
if (!this.childNodes || this.childNodes.length < 1){ |
84 |
return null; |
85 |
} else { |
86 |
var writer = new iwfWriter(true, pretty, this._html); |
87 |
return this.childNodes[0].outerXml(writer); |
88 |
} |
89 |
} |
90 |
|
91 |
// ----------------------------------- |
92 |
// End: Xml Document Object |
93 |
// ----------------------------------- |
94 |
|
95 |
|
96 |
// ----------------------------------- |
97 |
// Begin: Xml Node Object |
98 |
// ----------------------------------- |
99 |
|
100 |
|
101 |
// -------------------------------------------------------------------------- |
102 |
// iwfXmlNode |
103 |
// |
104 |
// Brock Weaver - brockweaver@gmail.com |
105 |
// v 0.1 - 2005-06-05 |
106 |
// Initial release. |
107 |
// -------------------------------------------------------------------------- |
108 |
// This class is used to represent a single xml node. |
109 |
// All xml is kept except processing instructions. |
110 |
// There are issues with "<" and ">" chars embedded within |
111 |
// the xml as text. This is technically not valid xml, so |
112 |
// it is technically not a problem I guess. |
113 |
|
114 |
function iwfXmlNode(xml, parent){ |
115 |
|
116 |
this.childNodes = new Array(); |
117 |
this._text = ''; |
118 |
this.attributes = new Array(); |
119 |
this.attributeNames = new Array(); |
120 |
this.attributeQuotes = new Array(); |
121 |
this.nodeName = 'UNKNOWN'; |
122 |
this._parent = parent; |
123 |
this._isEmpty = false; |
124 |
|
125 |
|
126 |
if (!xml || xml.length == 0){ |
127 |
return; |
128 |
} else { |
129 |
this._parseXml(xml); |
130 |
} |
131 |
|
132 |
this._htmlMode = false; |
133 |
|
134 |
|
135 |
} |
136 |
|
137 |
iwfXmlNode.prototype.toString = function(innerOnly){ |
138 |
if (innerOnly){ |
139 |
return this.innerXml(); |
140 |
} else { |
141 |
return this.outerXml(); |
142 |
} |
143 |
} |
144 |
|
145 |
iwfXmlNode.prototype.outerXml = function(writer){ |
146 |
return this._serialize(writer, false); |
147 |
} |
148 |
|
149 |
iwfXmlNode.prototype.innerXml = function(writer){ |
150 |
return this._serialize(writer, true) |
151 |
} |
152 |
|
153 |
iwfXmlNode.prototype.outerHtml = function() { |
154 |
this._htmlMode = true; |
155 |
var ret = this.outerXml(); |
156 |
this._htmlMode = false; |
157 |
return ret; |
158 |
} |
159 |
|
160 |
iwfXmlNode.prototype.innerHtml = function() { |
161 |
this._htmlMode = true; |
162 |
var ret = this.innerXml(); |
163 |
this._htmlMode = false; |
164 |
return ret; |
165 |
} |
166 |
|
167 |
iwfXmlNode.prototype._serialize = function(writer, innerOnly){ |
168 |
var pretty = false; |
169 |
if (typeof(writer) == 'boolean'){ |
170 |
pretty = writer; |
171 |
writer = false; |
172 |
} |
173 |
if (!writer){ |
174 |
writer = new iwfWriter(true, pretty, this._htmlMode); |
175 |
} |
176 |
|
177 |
if (!innerOnly){ |
178 |
writer.writeNodeOpen(this.nodeName); |
179 |
for(var i=0;i<this.attributes.length;i++){ |
180 |
writer.writeAttribute(this.attributeNames[i], this.attributes[i], this.attributeQuotes[i]); |
181 |
} |
182 |
} |
183 |
|
184 |
writer._writeRawText(this._text); |
185 |
for(var i=0;i<this.childNodes.length;i++){ |
186 |
this.childNodes[i].outerXml(writer); |
187 |
} |
188 |
|
189 |
if (!innerOnly){ |
190 |
writer.writeNodeClose(); |
191 |
} |
192 |
|
193 |
return writer.toString(); |
194 |
} |
195 |
|
196 |
iwfXmlNode.prototype.removeAttribute = function(name){ |
197 |
for(var i=0;i<this.attributeNames.length;i++){ |
198 |
if (this.attributeNames[i] == name){ |
199 |
|
200 |
// found the attribute. splice it out of all the arrays. |
201 |
this.attributeQuotes.splice(j, 1); |
202 |
this.attributeNames.splice(j, 1); |
203 |
this.attributes.splice(j, 1); |
204 |
// remove from our object and jump out of the loop |
205 |
delete this[name]; |
206 |
break; |
207 |
|
208 |
|
209 |
// // found the attribute. move all ones following it down one |
210 |
// for(var j=i;j<this.attributeNames.length-1;j++){ |
211 |
// this.attributeQuotes[j] = this.attributeQuotes[j+1]; |
212 |
// this.attributeNames[j] = this.attributeNames[j+1]; |
213 |
// this.attributes[j] = this.attributes[j+1]; |
214 |
// } |
215 |
// // pop off the last one, as it's now a duplicate |
216 |
// this.attributeQuotes.pop(); |
217 |
// this.attributeNames.pop(); |
218 |
// this.attributes.pop(); |
219 |
// delete this[name]; |
220 |
// break; |
221 |
} |
222 |
} |
223 |
} |
224 |
|
225 |
iwfXmlNode.prototype.removeChildNode = function(node){ |
226 |
for(var i=0;i<this.childNodes.length;i++){ |
227 |
if (node == this.childNodes[i]){ |
228 |
// remove from childNodes array |
229 |
this.childNodes.splice(k, 1); |
230 |
var arr = this[node.nodeName]; |
231 |
if (arr){ |
232 |
if (arr.length > 1){ |
233 |
var j = 0; |
234 |
for(j=0;i<arr.length;i++){ |
235 |
if (arr[j] == node){ |
236 |
arr.splice(j, 1); |
237 |
break; |
238 |
} |
239 |
} |
240 |
} else { |
241 |
delete arr; |
242 |
} |
243 |
} |
244 |
break; |
245 |
} |
246 |
} |
247 |
} |
248 |
|
249 |
iwfXmlNode.prototype.addAttribute = function(name, val, quotesToUse){ |
250 |
if (!quotesToUse){ |
251 |
// assume apostrophes |
252 |
quotesToUse = "'"; |
253 |
} |
254 |
if (!this[name]){ |
255 |
this.attributeQuotes.push(quotesToUse); |
256 |
this.attributeNames.push(name); |
257 |
this.attributes.push(val); |
258 |
} |
259 |
this[name] = val; |
260 |
} |
261 |
|
262 |
iwfXmlNode.prototype.addChildNode = function(node, nodeName){ |
263 |
if (nodeName && nodeName.length > 0 && nodeName.indexOf("#") == 0){ |
264 |
var txt = node; |
265 |
node = new iwfXmlNode('', this); |
266 |
node.nodeName = nodeName; |
267 |
node._text = txt; |
268 |
|
269 |
|
270 |
// push it onto the childNodes array |
271 |
this.childNodes.push(node); |
272 |
|
273 |
} else { |
274 |
|
275 |
// make a node object out of it if they gave us a string... |
276 |
if (typeof(node) == 'string'){ |
277 |
node = new iwfXmlNode(node, this); |
278 |
} |
279 |
|
280 |
if (node.nodeName.indexOf("#") == 0){ |
281 |
// is a special node -- duplicate names may exist... |
282 |
node._text = txt; |
283 |
|
284 |
// push it onto the childNodes array |
285 |
this.childNodes.push(node); |
286 |
|
287 |
} else { |
288 |
|
289 |
if (!this.childNodes[node.nodeName]){ |
290 |
// no other child node with this name exists yet. |
291 |
|
292 |
// make a new array off of our object |
293 |
this[node.nodeName] = new Array(); |
294 |
} |
295 |
|
296 |
// push it onto our object's array |
297 |
this[node.nodeName].push(node); |
298 |
|
299 |
// push it onto the childNodes array |
300 |
this.childNodes.push(node); |
301 |
} |
302 |
} |
303 |
} |
304 |
|
305 |
iwfXmlNode.prototype.getText = function(){ |
306 |
var txt = ''; |
307 |
if (this.nodeName.indexOf('#') == 0){ |
308 |
txt = this._text; |
309 |
} |
310 |
for(var i=0;i<this.childNodes.length;i++){ |
311 |
txt += this.childNodes[i].getText(); |
312 |
} |
313 |
return txt; |
314 |
} |
315 |
|
316 |
iwfXmlNode.prototype._parseXml = function(xml){ |
317 |
|
318 |
var remainingXml = xml; |
319 |
|
320 |
while(remainingXml.length > 0){ |
321 |
var type = this._determineNodeType(remainingXml); |
322 |
switch(type){ |
323 |
case 'open': |
324 |
remainingXml = this._parseName(remainingXml); |
325 |
remainingXml = this._parseAtts(remainingXml); |
326 |
if (this._parent){ |
327 |
this._parent.addChildNode(this, null); |
328 |
} |
329 |
|
330 |
if (!this._isEmpty){ |
331 |
remainingXml = this._parseChildren(remainingXml); |
332 |
// we still need to parse out the close node, so don't jump out yet! |
333 |
} else { |
334 |
return remainingXml; |
335 |
} |
336 |
break; |
337 |
|
338 |
case 'text': |
339 |
return this._parseText(remainingXml); |
340 |
|
341 |
case 'close': |
342 |
return this._parseClosing(remainingXml); |
343 |
|
344 |
case 'end': |
345 |
return ''; |
346 |
|
347 |
case 'pi': |
348 |
// return this._parsePI(remainingXml); |
349 |
remainingXml = this._parsePI(remainingXml); |
350 |
break; |
351 |
|
352 |
case 'comment': |
353 |
return this._parseComment(remainingXml); |
354 |
|
355 |
case 'cdata': |
356 |
return this._parseCDATA(remainingXml); |
357 |
|
358 |
case 'whitespace': |
359 |
remainingXml = this._parseWhitespace(remainingXml); |
360 |
if (this._parent){ |
361 |
return remainingXml; |
362 |
} |
363 |
break; |
364 |
|
365 |
default: |
366 |
iwfLog('IWF Xml Parsing Error: undefined type of ' + type + ' returned for xml starting with "' + remainingXml + '"', true); |
367 |
break; |
368 |
} |
369 |
|
370 |
} |
371 |
|
372 |
} |
373 |
|
374 |
iwfXmlNode.prototype._determineNodeType = function(xml){ |
375 |
|
376 |
|
377 |
if (!xml || xml.length == 0){ |
378 |
return 'end'; |
379 |
} |
380 |
|
381 |
var trimmed = this.ltrim(xml); |
382 |
|
383 |
var firstTrimmedLt = trimmed.indexOf("<"); |
384 |
|
385 |
switch(firstTrimmedLt){ |
386 |
case -1: |
387 |
// this is either insignificant whitespace or text |
388 |
if (trimmed.length == 0){ |
389 |
return 'whitespace'; |
390 |
} else { |
391 |
return 'text'; |
392 |
} |
393 |
case 0: |
394 |
// this is either an open node or insignificant whitespace. |
395 |
var firstLt = xml.indexOf("<"); |
396 |
if (firstLt > 0){ |
397 |
return 'whitespace' |
398 |
} else { |
399 |
switch(trimmed.charAt(1)){ |
400 |
case '?': |
401 |
return 'pi'; |
402 |
case '!': |
403 |
if (trimmed.substr(0,4) == '<!--'){ |
404 |
return 'comment'; |
405 |
} else if (trimmed.substr(0,9) == '<![CDATA[') { |
406 |
return 'cdata'; |
407 |
} else { |
408 |
return 'unknown: ' + trimmed.substr(0,10); |
409 |
} |
410 |
case '/': |
411 |
return 'close'; |
412 |
default: |
413 |
return 'open'; |
414 |
} |
415 |
} |
416 |
|
417 |
default: |
418 |
// this is a text node |
419 |
return 'text'; |
420 |
} |
421 |
} |
422 |
|
423 |
iwfXmlNode.prototype._parseName = function(xml){ |
424 |
// we know xml starts with <. |
425 |
|
426 |
var firstSpace = xml.indexOf(" "); |
427 |
var firstApos = xml.indexOf("'"); |
428 |
var firstQuote = xml.indexOf('"'); |
429 |
var firstGt = xml.indexOf(">"); |
430 |
|
431 |
if (firstGt == -1){ |
432 |
iwfLog("IWF Xml Parsing Error: Bad xml; no > found for an open node.", true); |
433 |
return ''; |
434 |
} |
435 |
|
436 |
// see if it's an empty node... |
437 |
if (xml.charAt(firstGt-1) == '/'){ |
438 |
this._isEmpty = true; |
439 |
} |
440 |
|
441 |
// see if there is a possibility that atts exist |
442 |
if (firstSpace > firstGt || firstSpace == -1){ |
443 |
if (this._isEmpty){ |
444 |
// <h1/> |
445 |
this.nodeName = xml.substr(1,firstGt-2); |
446 |
} else { |
447 |
// <h1> |
448 |
this.nodeName = xml.substr(1,firstGt-1); |
449 |
} |
450 |
// return starting at > so parseAtts knows there are no atts. |
451 |
return xml.substr(firstGt); |
452 |
} else { |
453 |
// <h1 > |
454 |
// <h1 att='val'> |
455 |
// <h1 att='val'/> |
456 |
this.nodeName = xml.substr(1,firstSpace-1); |
457 |
|
458 |
|
459 |
// eat everything up to the space, return the rest |
460 |
return xml.substr(firstSpace); |
461 |
} |
462 |
|
463 |
} |
464 |
|
465 |
iwfXmlNode.prototype._parseAtts = function(xml){ |
466 |
|
467 |
|
468 |
xml = this.ltrim(xml); |
469 |
var firstGt = xml.indexOf(">"); |
470 |
if (firstGt == -1){ |
471 |
iwfLog("IWF Xml Parsing Error: Bad xml; no > found when parsing atts for " + this.nodeName, true); |
472 |
return ''; |
473 |
} else if (firstGt == 0){ |
474 |
// no atts. |
475 |
return xml.substr(firstGt+1); |
476 |
} else { |
477 |
// at least one att exists. |
478 |
var attxml = xml.substr(0, firstGt); |
479 |
var re = /\s*?([^=]*?)=(['"])(.*?)(\2)/; |
480 |
var matches= re.exec(attxml) |
481 |
while(matches){ |
482 |
attxml = this.ltrim(attxml.substr(matches[0].length)); |
483 |
var attname = this.ltrim(matches[1]); |
484 |
var attval = matches[3]; |
485 |
var quotesToUse = matches[2]; |
486 |
this.addAttribute(attname, attval, quotesToUse); |
487 |
|
488 |
re = /\s*?([^=]*?)=(['"])(.*?)(\2)/; |
489 |
matches = re.exec(attxml); |
490 |
} |
491 |
|
492 |
|
493 |
// return everything after the end of the att list and the > which closes the start of the node. |
494 |
return xml.substr(firstGt+1); |
495 |
} |
496 |
} |
497 |
|
498 |
iwfXmlNode.prototype._parseChildren = function(xml){ |
499 |
// we are at the > which closes the open node. |
500 |
|
501 |
if (xml && xml.length > 0){ |
502 |
var endnode = "</" + this.nodeName + ">"; |
503 |
while (xml && xml.length > 0 && xml.indexOf(endnode) > 0){ |
504 |
|
505 |
// do not pass the xml to the constructor, as that may cause an infinite loop. |
506 |
var childNode = new iwfXmlNode('', this); |
507 |
xml = childNode._parseXml(xml); |
508 |
} |
509 |
|
510 |
// note we don't cut off the close node here, as the _parseXml function will do this for us. |
511 |
|
512 |
} |
513 |
|
514 |
return xml; |
515 |
} |
516 |
|
517 |
|
518 |
|
519 |
iwfXmlNode.prototype._parseClosing = function(xml){ |
520 |
var firstGt = xml.indexOf("</"); |
521 |
if (firstGt == -1){ |
522 |
iwfLog('IWF Xml Parsing Error: Bad xml; no </' + this.nodeName + ' found', true); |
523 |
return ''; |
524 |
} else { |
525 |
var firstLt = xml.indexOf(">",firstGt); |
526 |
if (firstLt == -1){ |
527 |
iwfLog('IWF Xml Parsing Error: Bad xml; no > found after </' + this.nodeName, true); |
528 |
return ''; |
529 |
} else { |
530 |
var result = xml.substr(firstLt+1); |
531 |
return result; |
532 |
} |
533 |
} |
534 |
} |
535 |
|
536 |
iwfXmlNode.prototype._parseText = function(xml){ |
537 |
return this._parsePoundNode(xml, "#text", "", "<", false); |
538 |
} |
539 |
|
540 |
iwfXmlNode.prototype._parsePI = function(xml){ |
541 |
var result = this._eatXml(xml, "?>", true); |
542 |
return result; |
543 |
} |
544 |
|
545 |
iwfXmlNode.prototype._parseComment = function(xml){ |
546 |
return this._parsePoundNode(xml, "#comment", "<!--", "-->", true); |
547 |
} |
548 |
|
549 |
iwfXmlNode.prototype._parseCDATA = function(xml){ |
550 |
return this._parsePoundNode(xml, "#cdata", "<![CDATA[", "]]>", true); |
551 |
} |
552 |
|
553 |
iwfXmlNode.prototype._parseWhitespace = function(xml){ |
554 |
var result = ''; |
555 |
if (this._parent && this._parent.nodeName.toLowerCase() == 'pre'){ |
556 |
// hack for supporting HTML's <pre> node. ugly, I know :) |
557 |
result = this._parsePoundNode(xml, "#text", "", "<", false); |
558 |
} else { |
559 |
// whitespace is insignificant otherwise |
560 |
result = this._eatXml(xml, "<", false); |
561 |
} |
562 |
return result; |
563 |
} |
564 |
|
565 |
iwfXmlNode.prototype._parsePoundNode = function(xml, nodeName, beginMoniker, endMoniker, eatMoniker){ |
566 |
// simply slurp everything up until the first endMoniker, starting after the beginMoniker |
567 |
var end = xml.indexOf(endMoniker); |
568 |
if (end == -1){ |
569 |
iwfLog("IWF Xml Parsing Error: Bad xml: " + nodeName + " does not have ending " + endMoniker, true); |
570 |
return ''; |
571 |
} else { |
572 |
var len = beginMoniker.length; |
573 |
var s = xml.substr(len, end - len); |
574 |
if (this._parent){ |
575 |
this._parent.addChildNode(s, nodeName); |
576 |
} |
577 |
var result = xml.substr(end + (eatMoniker ? endMoniker.length : 0)); |
578 |
return result; |
579 |
} |
580 |
} |
581 |
|
582 |
iwfXmlNode.prototype._eatXml = function(xml, moniker, eatMoniker){ |
583 |
var pos = xml.indexOf(moniker); |
584 |
|
585 |
if (eatMoniker){ |
586 |
pos += moniker.length; |
587 |
} |
588 |
|
589 |
return xml.substr(pos); |
590 |
|
591 |
} |
592 |
|
593 |
iwfXmlNode.prototype.trim = function(s){ |
594 |
return s.replace(/^\s*|\s*$/g,""); |
595 |
} |
596 |
|
597 |
iwfXmlNode.prototype.ltrim = function(s){ |
598 |
return s.replace(/^\s*/g,""); |
599 |
} |
600 |
|
601 |
iwfXmlNode.prototype.rtrim = function(s){ |
602 |
return s.replace(/\s*$/g,""); |
603 |
} |
604 |
|
605 |
// ----------------------------------- |
606 |
// End: Xml Node Object |
607 |
// ----------------------------------- |
608 |
|
609 |
|
610 |
|
611 |
|
612 |
|
613 |
|
614 |
|
615 |
// ----------------------------------- |
616 |
// Begin: Xml Writer Object |
617 |
// ----------------------------------- |
618 |
|
619 |
|
620 |
// -------------------------------------------------------------------------- |
621 |
// iwfWriter |
622 |
// |
623 |
// Brock Weaver - brockweaver@gmail.com |
624 |
// |
625 |
// v 0.1 - 2005-06-05 |
626 |
// Initial release. |
627 |
// -------------------------------------------------------------------------- |
628 |
// This class is meant to ease the creation of xml strings. |
629 |
// Note it is not a fully-compliant xml generator. |
630 |
// -------------------------------------------------------------------------- |
631 |
|
632 |
|
633 |
function iwfWriter(suppressProcessingInstruction, prettyPrint, htmlMode) { |
634 |
this._buffer = new String(); |
635 |
if (!suppressProcessingInstruction){ |
636 |
this.writeRaw("<?xml version='1.0' ?>"); |
637 |
} |
638 |
this._nodeStack = new Array(); |
639 |
this._nodeOpened = false; |
640 |
this._prettyPrint = prettyPrint; |
641 |
this._htmlMode = htmlMode |
642 |
} |
643 |
|
644 |
iwfWriter.prototype.writePretty = function(lfBeforeTabbing){ |
645 |
if (this._prettyPrint){ |
646 |
if (lfBeforeTabbing){ |
647 |
this.writeRaw('\n'); |
648 |
} |
649 |
|
650 |
// assumption is most xml won't have a maximum node depth exceeding 30... |
651 |
var tabs = '\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t'; |
652 |
while (tabs.length < this._nodeStack.length){ |
653 |
// but some xml might exceed node depth of 30, so keep tacking on another 30 tabs until we have enough... |
654 |
// I know this is an awkward way of doing it, but I wanted to avoid looping and concatenating for "most" xml... |
655 |
tabs += tabs; |
656 |
} |
657 |
this.writeRaw(tabs.substr(0,this._nodeStack.length)); |
658 |
} |
659 |
} |
660 |
|
661 |
iwfWriter.prototype.writeNode = function(name, txt){ |
662 |
this.writeNodeOpen(name); |
663 |
this.writeText(txt); |
664 |
this.writeNodeClose(); |
665 |
} |
666 |
|
667 |
iwfWriter.prototype.writeNodeOpen = function(name){ |
668 |
if (this._nodeOpened){ |
669 |
this.writeRaw(">"); |
670 |
} |
671 |
this._nodeOpened = false; |
672 |
|
673 |
this.writePretty(true); |
674 |
|
675 |
switch(name){ |
676 |
case '#pi': |
677 |
this.writeRaw('<?'); |
678 |
break; |
679 |
case '#text': |
680 |
// do nothing |
681 |
break; |
682 |
case '#cdata': |
683 |
this.writeRaw('<![CDATA['); |
684 |
break; |
685 |
case '#comment': |
686 |
this.writeRaw('<!--'); |
687 |
break; |
688 |
default: |
689 |
this.writeRaw("<" + name); |
690 |
this._nodeOpened = true; |
691 |
break; |
692 |
} |
693 |
this._nodeStack.push(name); |
694 |
} |
695 |
|
696 |
iwfWriter.prototype.writeAttribute = function(name, val, quotes){ |
697 |
if (!this._nodeOpened){ |
698 |
iwfLog("IWF Xml Parsing Error: Appending attribute '" + name + |
699 |
"' when no open node exists!", true); |
700 |
} |
701 |
var attval = val; |
702 |
|
703 |
|
704 |
if (!quotes){ |
705 |
quotes = "'"; |
706 |
} |
707 |
|
708 |
// browsers handle html attributes in a special way: |
709 |
// only the character that matches the html delimiter for the attribute should be xml-escaped. |
710 |
// the OTHER quote character (' or ", whichever) needs to be javascript-escaped, |
711 |
// |
712 |
// the iwfXmlNode class also needs the > char escaped to be functional -- I need to fix that! |
713 |
// But for right now, this hack gets things working at least. |
714 |
// |
715 |
// Like I said, I'm getting lazy :) |
716 |
// |
717 |
|
718 |
if (this._htmlMode){ |
719 |
// we're kicking out html, so xml escape only the quote character that matches the delimiter and the > sign. |
720 |
// We also need to javascript-escape the quote char that DOES NOT match the delimiter but is xml-escaped. |
721 |
if (quotes == "'"){ |
722 |
// we're using apostrophes to delimit html. |
723 |
attval = attval.replace(/"/gi, '\\"').replace(/'/gi, ''').replace(/>/gi, '>'); |
724 |
} else { |
725 |
// we're using quotes to delimit html |
726 |
attval = attval.replace(/'/gi, "\\'").replace(/"/gi, '"').replace(/>/gi, '>'); |
727 |
// attval = attval.replace(/&/gi, '&').replace(/"/gi, '"').replace(/</gi, '<').replace(/>/gi, '>'); |
728 |
} |
729 |
} else { |
730 |
attval = iwfXmlEncode(attval); |
731 |
} |
732 |
|
733 |
this.writeRaw(" " + name + "=" + quotes + attval + quotes); |
734 |
} |
735 |
|
736 |
iwfWriter.prototype.writeAtt = function(name, val, quotes){ |
737 |
this.writeAttribute(name, val, quotes); |
738 |
} |
739 |
|
740 |
iwfWriter.prototype._writeRawText = function(txt){ |
741 |
if (!txt || txt.length == 0){ |
742 |
// no text to write. do nothing. |
743 |
return; |
744 |
} else { |
745 |
if (this._nodeOpened){ |
746 |
this.writeRaw(">"); |
747 |
this._nodeOpened = false; |
748 |
} |
749 |
|
750 |
this.writeRaw(txt); |
751 |
} |
752 |
} |
753 |
|
754 |
iwfWriter.prototype.writeText = function(txt){ |
755 |
if (!txt || txt.length == 0){ |
756 |
// no text to write. do nothing. |
757 |
return; |
758 |
} else { |
759 |
if (this._nodeOpened){ |
760 |
this.writeRaw(">"); |
761 |
this._nodeOpened = false; |
762 |
this.writePretty(true); |
763 |
} |
764 |
|
765 |
this.writeRaw(iwfXmlEncode(txt)); |
766 |
} |
767 |
} |
768 |
|
769 |
iwfWriter.prototype.writeNodeClose = function(){ |
770 |
if (this._nodeStack.length > 0){ |
771 |
var name = this._nodeStack.pop(); |
772 |
|
773 |
if (!this._nodeOpened && name != '#text'){ |
774 |
this.writePretty(true); |
775 |
} |
776 |
|
777 |
switch(name){ |
778 |
case '#pi': |
779 |
this.writeRaw("?>"); |
780 |
break; |
781 |
case '#text': |
782 |
// do nothing |
783 |
break; |
784 |
case '#cdata': |
785 |
this.writeRaw("]]>"); |
786 |
break; |
787 |
case '#comment': |
788 |
this.writeRaw("-->"); |
789 |
break; |
790 |
default: |
791 |
if (this._nodeOpened){ |
792 |
// hack for <script /> needing to be <script></script> |
793 |
switch(name){ |
794 |
case 'script': |
795 |
this.writeRaw("></" + name + ">"); |
796 |
break; |
797 |
default: |
798 |
this.writeRaw("/>");; |
799 |
break; |
800 |
} |
801 |
} else { |
802 |
this.writeRaw("</" + name + ">"); |
803 |
} |
804 |
break; |
805 |
} |
806 |
this._nodeOpened = false; |
807 |
} |
808 |
} |
809 |
|
810 |
iwfWriter.prototype.writeRaw = function(xml){ |
811 |
this._buffer += xml; |
812 |
} |
813 |
|
814 |
iwfWriter.prototype.toString = function(){ |
815 |
return this._buffer; |
816 |
} |
817 |
|
818 |
iwfWriter.prototype.clear = function(){ |
819 |
this.buffer = new String(); |
820 |
} |
821 |
|
822 |
// ----------------------------------- |
823 |
// End: Xml Writer Object |
824 |
// ----------------------------------- |
825 |
|