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