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