1 // --------------------------------------------------------------------------
\r
2 /// IWF - Interactive Website Framework. Javascript library for creating
\r
3 /// responsive thin client interfaces.
\r
5 /// Copyright (C) 2005 Brock Weaver brockweaver@users.sourceforge.net
\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
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
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
22 /// brockweaver@users.sourceforge.net
\r
23 /// 1605 NW Maple Pl
\r
24 /// Ankeny, IA 50021
\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
32 // This reduces file size by about 30%, give or take.
\r
34 //! To rip out only logging statements (commented or uncommented):
\r
36 // --------------------------------------------------------------------------
\r
38 // --------------------------------------------------------------------------
\r
41 // Javascript-based xml parser
\r
46 //! Brock Weaver - brockweaver@users.sourceforge.net
\r
47 //! v 0.2 - 2005-11-14
\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
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
68 // -----------------------------------
\r
69 // Begin: Xml Document Object
\r
70 // -----------------------------------
\r
72 function iwfXmlDoc(xml){
\r
73 if (!xml || xml.length == 0){
\r
79 iwfXmlDoc.prototype.loadXml = function(xml){
\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
92 iwfXmlDoc.prototype.toString = function(pretty){
\r
94 return this.outerXml(pretty);
\r
97 iwfXmlDoc.prototype.outerXml = function(pretty){
\r
98 if (!this.childNodes || this.childNodes.length < 1){
\r
101 var writer = new iwfWriter(true, pretty, this._html);
\r
102 return this.childNodes[0].outerXml(writer);
\r
106 // -----------------------------------
\r
107 // End: Xml Document Object
\r
108 // -----------------------------------
\r
111 // -----------------------------------
\r
112 // Begin: Xml Node Object
\r
113 // -----------------------------------
\r
116 // --------------------------------------------------------------------------
\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
129 function iwfXmlNode(xml, parent){
\r
131 this.childNodes = new Array();
\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
141 if (!xml || xml.length == 0){
\r
144 this._parseXml(xml);
\r
147 this._htmlMode = false;
\r
152 iwfXmlNode.prototype.toString = function(innerOnly){
\r
154 return this.innerXml();
\r
156 return this.outerXml();
\r
160 iwfXmlNode.prototype.outerXml = function(writer){
\r
161 return this._serialize(writer, false);
\r
164 iwfXmlNode.prototype.innerXml = function(writer){
\r
165 return this._serialize(writer, true)
\r
168 iwfXmlNode.prototype.outerHtml = function() {
\r
169 this._htmlMode = true;
\r
170 var ret = this.outerXml();
\r
171 this._htmlMode = false;
\r
175 iwfXmlNode.prototype.innerHtml = function() {
\r
176 this._htmlMode = true;
\r
177 var ret = this.innerXml();
\r
178 this._htmlMode = false;
\r
182 iwfXmlNode.prototype._serialize = function(writer, innerOnly){
\r
183 var pretty = false;
\r
184 if (typeof(writer) == 'boolean'){
\r
189 writer = new iwfWriter(true, pretty, this._htmlMode);
\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
199 writer._writeRawText(this._text);
\r
200 for(var i=0;i<this.childNodes.length;i++){
\r
201 this.childNodes[i].outerXml(writer);
\r
205 writer.writeNodeClose();
\r
208 return writer.toString();
\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
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
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
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
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
247 if (arr.length > 1){
\r
249 for(j=0;i<arr.length;i++){
\r
250 if (arr[j] == node){
\r
264 iwfXmlNode.prototype.addAttribute = function(name, val, quotesToUse){
\r
266 // assume apostrophes
\r
270 this.attributeQuotes.push(quotesToUse);
\r
271 this.attributeNames.push(name);
\r
272 this.attributes.push(val);
\r
277 iwfXmlNode.prototype.addChildNode = function(node, nodeName){
\r
278 if (nodeName && nodeName.length > 0 && nodeName.indexOf("#") == 0){
\r
280 node = new iwfXmlNode('', this);
\r
281 node.nodeName = nodeName;
\r
285 // push it onto the childNodes array
\r
286 this.childNodes.push(node);
\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
295 if (node.nodeName.indexOf("#") == 0){
\r
296 // is a special node -- duplicate names may exist...
\r
299 // push it onto the childNodes array
\r
300 this.childNodes.push(node);
\r
304 if (!this.childNodes[node.nodeName]){
\r
305 // no other child node with this name exists yet.
\r
307 // make a new array off of our object
\r
308 this[node.nodeName] = new Array();
\r
311 // push it onto our object's array
\r
312 this[node.nodeName].push(node);
\r
314 // push it onto the childNodes array
\r
315 this.childNodes.push(node);
\r
320 iwfXmlNode.prototype.getText = function(){
\r
322 if (this.nodeName.indexOf('#') == 0){
\r
325 for(var i=0;i<this.childNodes.length;i++){
\r
326 txt += this.childNodes[i].getText();
\r
331 iwfXmlNode.prototype._parseXml = function(xml){
\r
333 var remainingXml = xml;
\r
335 while(remainingXml.length > 0){
\r
336 var type = this._determineNodeType(remainingXml);
\r
339 remainingXml = this._parseName(remainingXml);
\r
340 remainingXml = this._parseAtts(remainingXml);
\r
342 this._parent.addChildNode(this, null);
\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
349 return remainingXml;
\r
354 return this._parseText(remainingXml);
\r
357 return this._parseClosing(remainingXml);
\r
363 // return this._parsePI(remainingXml);
\r
364 remainingXml = this._parsePI(remainingXml);
\r
368 return this._parseComment(remainingXml);
\r
371 return this._parseCDATA(remainingXml);
\r
374 remainingXml = this._parseWhitespace(remainingXml);
\r
376 return remainingXml;
\r
381 iwfLog('IWF Xml Parsing Error: undefined type of ' + type + ' returned for xml starting with "' + remainingXml + '"', true);
\r
389 iwfXmlNode.prototype._determineNodeType = function(xml){
\r
392 if (!xml || xml.length == 0){
\r
396 var trimmed = this.ltrim(xml);
\r
398 var firstTrimmedLt = trimmed.indexOf("<");
\r
400 switch(firstTrimmedLt){
\r
402 // this is either insignificant whitespace or text
\r
403 if (trimmed.length == 0){
\r
404 return 'whitespace';
\r
409 // this is either an open node or insignificant whitespace.
\r
410 var firstLt = xml.indexOf("<");
\r
412 return 'whitespace'
\r
414 switch(trimmed.charAt(1)){
\r
418 if (trimmed.substr(0,4) == '<!--'){
\r
420 } else if (trimmed.substr(0,9) == '<![CDATA[') {
\r
423 return 'unknown: ' + trimmed.substr(0,10);
\r
433 // this is a text node
\r
438 iwfXmlNode.prototype._parseName = function(xml){
\r
439 // we know xml starts with <.
\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
446 if (firstGt == -1){
\r
447 iwfLog("IWF Xml Parsing Error: Bad xml; no > found for an open node.", true);
\r
451 // see if it's an empty node...
\r
452 if (xml.charAt(firstGt-1) == '/'){
\r
453 this._isEmpty = true;
\r
456 // see if there is a possibility that atts exist
\r
457 if (firstSpace > firstGt || firstSpace == -1){
\r
458 if (this._isEmpty){
\r
460 this.nodeName = xml.substr(1,firstGt-2);
\r
463 this.nodeName = xml.substr(1,firstGt-1);
\r
465 // return starting at > so parseAtts knows there are no atts.
\r
466 return xml.substr(firstGt);
\r
471 this.nodeName = xml.substr(1,firstSpace-1);
\r
474 // eat everything up to the space, return the rest
\r
475 return xml.substr(firstSpace);
\r
480 iwfXmlNode.prototype._parseAtts = function(xml){
\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
488 } else if (firstGt == 0){
\r
490 return xml.substr(firstGt+1);
\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
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
503 re = /\s*?([^=]*?)=(['"])(.*?)(\2)/;
\r
504 matches = re.exec(attxml);
\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
513 iwfXmlNode.prototype._parseChildren = function(xml){
\r
514 // we are at the > which closes the open node.
\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
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
525 // note we don't cut off the close node here, as the _parseXml function will do this for us.
\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
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
545 var result = xml.substr(firstLt+1);
\r
551 iwfXmlNode.prototype._parseText = function(xml){
\r
552 return this._parsePoundNode(xml, "#text", "", "<", false);
\r
555 iwfXmlNode.prototype._parsePI = function(xml){
\r
556 var result = this._eatXml(xml, "?>", true);
\r
560 iwfXmlNode.prototype._parseComment = function(xml){
\r
561 return this._parsePoundNode(xml, "#comment", "<!--", "-->", true);
\r
564 iwfXmlNode.prototype._parseCDATA = function(xml){
\r
565 return this._parsePoundNode(xml, "#cdata", "<![CDATA[", "]]>", true);
\r
568 iwfXmlNode.prototype._parseWhitespace = function(xml){
\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
574 // whitespace is insignificant otherwise
\r
575 result = this._eatXml(xml, "<", false);
\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
584 iwfLog("IWF Xml Parsing Error: Bad xml: " + nodeName + " does not have ending " + endMoniker, true);
\r
587 var len = beginMoniker.length;
\r
588 var s = xml.substr(len, end - len);
\r
590 this._parent.addChildNode(s, nodeName);
\r
592 var result = xml.substr(end + (eatMoniker ? endMoniker.length : 0));
\r
597 iwfXmlNode.prototype._eatXml = function(xml, moniker, eatMoniker){
\r
598 var pos = xml.indexOf(moniker);
\r
601 pos += moniker.length;
\r
604 return xml.substr(pos);
\r
608 iwfXmlNode.prototype.trim = function(s){
\r
609 return s.replace(/^\s*|\s*$/g,"");
\r
612 iwfXmlNode.prototype.ltrim = function(s){
\r
613 return s.replace(/^\s*/g,"");
\r
616 iwfXmlNode.prototype.rtrim = function(s){
\r
617 return s.replace(/\s*$/g,"");
\r
620 // -----------------------------------
\r
621 // End: Xml Node Object
\r
622 // -----------------------------------
\r
630 // -----------------------------------
\r
631 // Begin: Xml Writer Object
\r
632 // -----------------------------------
\r
635 // --------------------------------------------------------------------------
\r
638 //! Brock Weaver - brockweaver@users.sourceforge.net
\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
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
653 this._nodeStack = new Array();
\r
654 this._nodeOpened = false;
\r
655 this._prettyPrint = prettyPrint;
\r
656 this._htmlMode = htmlMode
\r
659 iwfWriter.prototype.writePretty = function(lfBeforeTabbing){
\r
660 if (this._prettyPrint){
\r
661 if (lfBeforeTabbing){
\r
662 this.writeRaw('\n');
\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
672 this.writeRaw(tabs.substr(0,this._nodeStack.length));
\r
676 iwfWriter.prototype.writeNode = function(name, txt){
\r
677 this.writeNodeOpen(name);
\r
678 this.writeText(txt);
\r
679 this.writeNodeClose();
\r
682 iwfWriter.prototype.writeNodeOpen = function(name){
\r
683 if (this._nodeOpened){
\r
684 this.writeRaw(">");
\r
686 this._nodeOpened = false;
\r
688 this.writePretty(true);
\r
692 this.writeRaw('<?');
\r
698 this.writeRaw('<![CDATA[');
\r
701 this.writeRaw('<!--');
\r
704 this.writeRaw("<" + name);
\r
705 this._nodeOpened = true;
\r
708 this._nodeStack.push(name);
\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
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
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
730 // Like I said, I'm getting lazy :)
\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(/"/gi, '\\"').replace(/'/gi, ''').replace(/>/gi, '>');
\r
740 // we're using quotes to delimit html
\r
741 attval = attval.replace(/'/gi, "\\'").replace(/"/gi, '"').replace(/>/gi, '>');
\r
744 attval = iwfXmlEncode(attval);
\r
747 this.writeRaw(" " + name + "=" + quotes + attval + quotes);
\r
750 iwfWriter.prototype.writeAtt = function(name, val, quotes){
\r
751 this.writeAttribute(name, val, quotes);
\r
754 iwfWriter.prototype._writeRawText = function(txt){
\r
755 if (!txt || txt.length == 0){
\r
756 // no text to write. do nothing.
\r
759 if (this._nodeOpened){
\r
760 this.writeRaw(">");
\r
761 this._nodeOpened = false;
\r
764 this.writeRaw(txt);
\r
768 iwfWriter.prototype.writeText = function(txt){
\r
769 if (!txt || txt.length == 0){
\r
770 // no text to write. do nothing.
\r
773 if (this._nodeOpened){
\r
774 this.writeRaw(">");
\r
775 this._nodeOpened = false;
\r
776 this.writePretty(true);
\r
779 this.writeRaw(iwfXmlEncode(txt));
\r
783 iwfWriter.prototype.writeNodeClose = function(){
\r
784 if (this._nodeStack.length > 0){
\r
785 var name = this._nodeStack.pop();
\r
787 if (!this._nodeOpened && name != '#text'){
\r
788 this.writePretty(true);
\r
793 this.writeRaw("?>");
\r
799 this.writeRaw("]]>");
\r
802 this.writeRaw("-->");
\r
805 if (this._nodeOpened){
\r
806 //! hack for <script /> and <div /> needing to be <script></script> or <div></div>
\r
810 this.writeRaw("></" + name + ">");
\r
813 this.writeRaw("/>");;
\r
817 this.writeRaw("</" + name + ">");
\r
821 this._nodeOpened = false;
\r
825 iwfWriter.prototype.writeRaw = function(xml){
\r
826 this._buffer += xml;
\r
829 iwfWriter.prototype.toString = function(){
\r
830 return this._buffer;
\r
833 iwfWriter.prototype.clear = function(){
\r
834 this.buffer = new String();
\r
837 // -----------------------------------
\r
838 // End: Xml Writer Object
\r
839 // -----------------------------------
\r