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