1 /**
  2  * Copyright (C) 2012 KO GmbH <copyright@kogmbh.com>
  3  *
  4  * @licstart
  5  * This file is part of WebODF.
  6  *
  7  * WebODF is free software: you can redistribute it and/or modify it
  8  * under the terms of the GNU Affero General Public License (GNU AGPL)
  9  * as published by the Free Software Foundation, either version 3 of
 10  * the License, or (at your option) any later version.
 11  *
 12  * WebODF is distributed in the hope that it will be useful, but
 13  * WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  * GNU Affero General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU Affero General Public License
 18  * along with WebODF.  If not, see <http://www.gnu.org/licenses/>.
 19  * @licend
 20  *
 21  * @source: http://www.webodf.org/
 22  * @source: https://github.com/kogmbh/WebODF/
 23  */
 24 
 25 /*global Node, NodeFilter, xmldom, runtime*/
 26 
 27 /*jslint sub: true, emptyblock: true*/
 28 if (typeof Object.create !== 'function') {
 29     /**
 30      * @param {!Object} o
 31      * @return {!Object}
 32      */
 33     Object['create'] = function (o) {
 34         "use strict";
 35         /**
 36          * @constructor
 37          */
 38         var F = function () {};
 39         F.prototype = o;
 40         return new F();
 41     };
 42 }
 43 /*jslint emptyblock: false*/
 44 
 45 /**
 46  * Partial implementation of LSSerializer
 47  * @constructor
 48  */
 49 xmldom.LSSerializer = function LSSerializer() {
 50     "use strict";
 51     var self = this;
 52 
 53     /**
 54      * @constructor
 55      * @param {!Object.<string,string>} nsmap
 56      */
 57     function Namespaces(nsmap) {
 58         /**
 59          * @param {!Object.<string,string>} map
 60          * @return {!Object.<string,string>}
 61          */
 62         function invertMap(map) {
 63             var m = {},
 64                 /**@type{string}*/
 65                 i;
 66             for (i in map) {
 67                 if (map.hasOwnProperty(i)) {
 68                     m[map[i]] = i;
 69                 }
 70             }
 71             return m;
 72         }
 73         var /**@type{!Object.<string,string>}*/
 74             current = nsmap || {},
 75             /**@type{!Object.<string,string>}*/
 76             currentrev = invertMap(nsmap),
 77             /**@type{!Array.<!Object.<string,string>>}*/
 78             levels = [ current ],
 79             /**@type{!Array.<!Object.<string,string>>}*/
 80             levelsrev = [ currentrev ],
 81             /**@type{number}*/
 82             level = 0;
 83         this.push = function () {
 84             level += 1;
 85             current = levels[level] = Object.create(current);
 86             currentrev = levelsrev[level] = Object.create(currentrev);
 87         };
 88         this.pop = function () {
 89             levels.pop();
 90             levelsrev.pop();
 91             level -= 1;
 92             current = levels[level];
 93             currentrev = levelsrev[level];
 94         };
 95         /**
 96          * @return {!Object.<string,string>} nsmap
 97          */
 98         this.getLocalNamespaceDefinitions = function () {
 99             return currentrev;
100         };
101         /**
102          * @param {!Node} node
103          * @return {!string}
104          */
105         this.getQName = function (node) {
106             var ns = node.namespaceURI,
107                 i = 0,
108                 p;
109             if (!ns) {
110                 return node.localName;
111             }
112             p = currentrev[ns];
113             if (p) {
114                 return p + ":" + node.localName;
115             }
116             do {
117                 if (p || !node.prefix) {
118                     p = "ns" + i;
119                     i += 1;
120                 } else {
121                     p = node.prefix;
122                 }
123                 if (current[p] === ns) {
124                     break;
125                 }
126                 if (!current[p]) {
127                     current[p] = ns;
128                     currentrev[ns] = p;
129                     break;
130                 }
131                 p = null;
132             } while (p === null);
133             return p + ":" + node.localName;
134         };
135     }
136     /**
137      * Escape characters within document content
138      * Follows basic guidelines specified at http://xerces.apache.org/xerces2-j/javadocs/api/org/w3c/dom/ls/LSSerializer.html
139      * @param {string} value
140      * @return {string}
141      */
142     function escapeContent(value) {
143         return value.replace(/&/g, "&")
144             .replace(/</g, "<")
145             .replace(/>/g, ">")
146             .replace(/'/g, "'")
147             .replace(/"/g, """);
148     }
149     /**
150      * @param {!string} qname
151      * @param {!Attr} attr
152      * @return {!string}
153      */
154     function serializeAttribute(qname, attr) {
155         var escapedValue = typeof attr.value === 'string'
156                            ? escapeContent(attr.value)
157                            : attr.value,
158             /**@type{!string}*/
159             s = qname + "=\"" + escapedValue + "\"";
160         return s;
161     }
162     /**
163      * @param {!Namespaces} ns
164      * @param {!string} qname
165      * @param {!Node} element
166      * @return {!string}
167      */
168     function startElement(ns, qname, element) {
169         var /**@type{!string}*/ s = "",
170             /**@const*/
171             atts = /**@type{!NamedNodeMap}*/(element.attributes),
172             /**@const
173  *             @type{!number}*/
174             length,
175             /**@type{!number}*/
176             i,
177             /**@type{!Attr}*/
178             attr,
179             /**@type{!string}*/
180             attstr = "",
181             /**@type{!number}*/
182             accept,
183             /**@type{!string}*/
184             prefix,
185             nsmap;
186         s += "<" + qname;
187         length = atts.length;
188         for (i = 0; i < length; i += 1) {
189             attr = /**@type{!Attr}*/(atts.item(i));
190             if (attr.namespaceURI !== "http://www.w3.org/2000/xmlns/") {
191                 accept = self.filter
192                          ? self.filter.acceptNode(attr)
193                          : NodeFilter.FILTER_ACCEPT;
194                 if (accept === NodeFilter.FILTER_ACCEPT) {
195                     attstr += " " + serializeAttribute(ns.getQName(attr),
196                         attr);
197                 }
198             }
199         }
200         nsmap = ns.getLocalNamespaceDefinitions();
201         for (i in nsmap) {
202             if (nsmap.hasOwnProperty(i)) {
203                 prefix = nsmap[i];
204                 if (!prefix) {
205                     s += " xmlns=\"" + i + "\"";
206                 } else if (prefix !== "xmlns") {
207                     s += " xmlns:" + nsmap[i] + "=\"" + i + "\"";
208                 }
209             }
210         }
211         s += attstr + ">";
212         return s;
213     }
214     /**
215      * @param {!Namespaces} ns
216      * @param {!Node} node
217      * @return {!string}
218      */
219     function serializeNode(ns, node) {
220         var /**@type{!string}*/
221             s = "",
222             /**@const
223  *             @type{!number}*/
224             accept = (self.filter) ? self.filter.acceptNode(node) : NodeFilter.FILTER_ACCEPT,
225             /**@type{Node}*/
226             child,
227             /**@const
228  *             @type{string}*/
229             qname;
230         if (accept === NodeFilter.FILTER_ACCEPT
231                 && node.nodeType === Node.ELEMENT_NODE) {
232             ns.push();
233             qname = ns.getQName(node);
234             s += startElement(ns, qname, node);
235         }
236         if (accept === NodeFilter.FILTER_ACCEPT
237                 || accept === NodeFilter.FILTER_SKIP) {
238             child = node.firstChild;
239             while (child) {
240                 s += serializeNode(ns, child);
241                 child = child.nextSibling;
242             }
243             if (node.nodeValue) {
244                 s += escapeContent(node.nodeValue);
245             }
246         }
247         if (qname) {
248             s += "</" + qname + ">";
249             ns.pop();
250         }
251         return s;
252     }
253     /**
254      * @type {xmldom.LSSerializerFilter}
255      */
256     this.filter = null;
257     /**
258      * @param {?Node} node
259      * @param {!Object.<string,string>} nsmap
260      * @return {!string}
261      */
262     this.writeToString = function (node, nsmap) {
263         if (!node) {
264             return "";
265         }
266         var ns = new Namespaces(nsmap);
267         return serializeNode(ns, node);
268     };
269 };
270