Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 'use strict'; | |
| 6 | |
| 7 installClass('XMLSerializer', function(XMLSerializerPrototype) { | |
| 8 | |
| 9 var EMPTY = ''; | |
| 10 | |
| 11 var XML_NAMESPACE_URI = 'http://www.w3.org/XML/1998/namespace'; | |
| 12 var XLINK_NAMESPACE_URI = 'http://www.w3.org/1999/xlink'; | |
| 13 var XMLNS_NAMESPACE_URI = 'http://www.w3.org/2000/xmlns'; | |
| 14 var HTML_NAMESPACE_URI = 'http://www.w3.org/1999/xhtml'; | |
| 15 | |
| 16 var SERIALIZATION_TYPE_AS_OWNER_DOCUMENT = 0; | |
| 17 var SERIALIZATION_TYPE_FORCED_XML = 1; | |
| 18 | |
| 19 function escapePCDATA(text) { | |
| 20 return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, ' >'); | |
| 21 } | |
| 22 | |
| 23 function escapeHTMLPCDATA(text) { | |
| 24 return escapePCDATA(text).replace(/\u00A0/g, ' '); | |
| 25 } | |
| 26 | |
| 27 function escapeAttributeValue(text) { | |
| 28 return text.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>'); | |
| 29 } | |
| 30 | |
| 31 function escapeHTMLAttributeValue(text) { | |
| 32 text.replace(/&|"|\u00A0/g, function(c) { | |
| 33 switch (c) { | |
| 34 case '&': return '&'; | |
| 35 case '"': return '"'; | |
| 36 case '\u00A0': return ' '; | |
| 37 } | |
| 38 }); | |
| 39 } | |
| 40 | |
| 41 function validateURI(uri) { | |
| 42 return uri ? uri.replace(/\/$/g, '') : ''; | |
| 43 } | |
| 44 | |
| 45 function cloneNamespace(obj) { | |
| 46 var clone = Object.create(null); | |
| 47 for (var i in obj) | |
| 48 clone[i] = obj[i]; | |
| 49 return clone; | |
| 50 } | |
| 51 | |
| 52 function isTemplateElement(node) { | |
| 53 return isHTMLElement(node) && node.localName == 'template'; | |
| 54 } | |
| 55 | |
| 56 function generatePrefix(namespaces) { | |
| 57 var nsRegex = /^ns([0-9]+)$/g; | |
| 58 var maxValue = 0; | |
| 59 for (var key in namespaces) { | |
| 60 var match = nsRegex.exec(key); | |
| 61 if (match) { | |
| 62 var value = parseInt(match[1], 10); | |
| 63 if (maxValue < value) | |
| 64 maxValue = value; | |
| 65 } | |
| 66 } | |
| 67 return 'ns' + (maxValue + 1); | |
| 68 } | |
| 69 | |
| 70 function qualifiedTagName(node) { | |
| 71 return isHTMLElement(node) ? node.localName : node.tagName; | |
| 72 } | |
| 73 | |
| 74 var SERIALIZED_NAMESPACES = { | |
| 75 XML_NAMESPACE_URI: true, | |
| 76 XLINK_NAMESPACE_URI: true, | |
| 77 XMLNS_NAMESPACE_URI: true | |
| 78 }; | |
| 79 | |
| 80 function attributeIsInSerializedNamespace(attr) { | |
| 81 return SERIALIZED_NAMESPACES[validateURI(attr.namespaceURI)]; | |
| 82 } | |
| 83 | |
| 84 function appendNamespace(prefix, namespaceURI, namespaces) { | |
| 85 if (!namespaceURI) | |
| 86 return ''; | |
| 87 | |
| 88 var result = ''; | |
| 89 var lookupKey = EMPTY; | |
| 90 if (prefix) | |
| 91 lookupKey = prefix; | |
| 92 if (namespaces[lookupKey] != namespaceURI) { | |
| 93 namespaces[lookupKey] = namespaceURI; | |
| 94 result += ' xmlns'; | |
| 95 if (prefix) | |
| 96 result += ':' + prefix; | |
| 97 result += '="' + namespaceURI + '"'; | |
| 98 } | |
| 99 return result; | |
| 100 } | |
| 101 | |
| 102 function shouldAddNamespaceAttribute(attr, node) { | |
| 103 if (!attr.namespaceURI) | |
| 104 return false; | |
| 105 if (!attr.prefix) | |
| 106 return true; | |
| 107 if (!node.hasAttribute('xmlns:' + attr.prefix)) | |
| 108 return true; | |
| 109 return false; | |
| 110 } | |
| 111 | |
| 112 function shouldAddNamespaceElement(node, namespaces) { | |
| 113 var prefix = node.prefix; | |
| 114 if (!node.prefix) { | |
| 115 if (node.hasAttribute('xmlns')) { | |
| 116 namespaces[EMPTY] = node.namespaceURI; | |
| 117 return false; | |
| 118 } | |
| 119 return true; | |
| 120 } | |
| 121 if (!node.hasAttribute('xmlns:' + node.prefix)) | |
| 122 return true; | |
| 123 return false; | |
| 124 } | |
| 125 | |
| 126 function isHTMLElement(node) { | |
| 127 if (node.nodeType != Node.ELEMENT_NODE) | |
| 128 return false; | |
| 129 | |
| 130 for (var runner = node; runner; runner = runner.parentNode) { | |
| 131 if (runner.nodeType == Node.ELEMENT_NODE && runner.namespaceURI != n ull) | |
| 132 return validateURI(runner.namespaceURI) == HTML_NAMESPACE_URI; | |
| 133 } | |
| 134 return false; | |
| 135 } | |
| 136 | |
| 137 // This tables comes from HTMLElement::ieForbidsInsertHTML(). | |
| 138 var END_TAG_OMISSIBLE_TAGS = { | |
| 139 'area': true, 'base': true, 'basefont': true, 'br': true, | |
| 140 'col': true, 'embed': true, 'frame': true, 'hr': true, | |
| 141 'image': true, 'img': true, 'input': true, 'link': true, | |
| 142 'meta': true, 'param': true, 'source': true, 'wbr': true | |
| 143 }; | |
| 144 | |
| 145 function isEndTagOmissible(node) { | |
| 146 if (node.nodeType != Node.ELEMENT_NODE) | |
| 147 return false; | |
| 148 return END_TAG_OMISSIBLE_TAGS[node.localName]; | |
| 149 } | |
| 150 | |
| 151 XMLSerializerPrototype.serializeAsHTMLDocument_ = function(node) { | |
| 152 if (this.serializationType_ == SERIALIZATION_TYPE_FORCED_XML) | |
| 153 return false; | |
| 154 return node.ownerDocument && (node.ownerDocument instanceof HTMLDocument ); | |
| 155 }; | |
| 156 | |
| 157 XMLSerializerPrototype.serializeAttribute_ = function(node, attr, namespaces ) { | |
| 158 var documentIsHTML = this.serializeAsHTMLDocument_(node); | |
| 159 var result = ''; | |
| 160 | |
| 161 var attrPrefix = attr.prefix; | |
| 162 var attrName = attr.localName; | |
| 163 if (documentIsHTML && !attributeIsInSerializedNamespace(attr)) { | |
| 164 result += ' ' + attr.localName; | |
| 165 } else { | |
| 166 var namespaceURI = validateURI(attr.namespaceURI); | |
| 167 if (namespaceURI == XMLNS_NAMESPACE_URI) { | |
| 168 if (!attr.prefix && attr.localName != 'xmlns') | |
| 169 attrPrefix = 'xmlns'; | |
| 170 if (namespaces) { | |
| 171 var lookupKey = EMPTY; | |
| 172 if (attr.prefix) | |
| 173 lookupKey = attr.localName; | |
| 174 namespaces[lookupKey] = attr.value; | |
| 175 } | |
| 176 } else if (namespaceURI == XML_NAMESPACE_URI) { | |
| 177 if (!attr.prefix) | |
| 178 attrPrefix = 'xml'; | |
| 179 } else { | |
| 180 if (namespaceURI == XLINK_NAMESPACE_URI) { | |
| 181 if (!attr.prefix) | |
| 182 attrPrefix = 'xlink'; | |
| 183 } | |
| 184 if (namespaces && shouldAddNamespaceAttribute(attr, node)) { | |
| 185 if (!attrPrefix) { | |
| 186 for (var i in namespaces) { | |
| 187 if (namespaces[i] == namespaceURI && i) { | |
| 188 attrPrefix = i; | |
| 189 break; | |
| 190 } | |
| 191 } | |
| 192 if (!attrPrefix) | |
| 193 attrPrefix = generatePrefix(namespaces); | |
| 194 } | |
| 195 result += appendNamespace(attrPrefix, namespaceURI, namespac es); | |
| 196 } | |
| 197 } | |
| 198 if (attrPrefix) { | |
| 199 result += ' ' + attrPrefix + ':' + attrName; | |
| 200 } else { | |
| 201 result += ' ' + attrName; | |
| 202 } | |
| 203 } | |
| 204 if (attr.value) { | |
| 205 var attrValue; | |
| 206 if (documentIsHTML) { | |
| 207 attrValue = escapeHTMLAttributeValue(String(attr.value)); | |
| 208 } else { | |
| 209 attrValue = escapeAttributeValue(String(attr.value)); | |
| 210 } | |
| 211 result += '="' + attrValue + '"'; | |
| 212 } | |
| 213 return result; | |
| 214 }; | |
| 215 | |
| 216 XMLSerializerPrototype.serializeAttributes_ = function(node, namespaces) { | |
| 217 if (!node.attributes) | |
| 218 return ''; | |
| 219 | |
| 220 var sink = ''; | |
| 221 for (var i = 0; i < node.attributes.length; ++i) | |
| 222 sink += this.serializeAttribute_(node, node.attributes[i], namespace s); | |
| 223 return sink; | |
| 224 }; | |
| 225 | |
| 226 XMLSerializerPrototype.serializeOpenTag_ = function(node, namespaces) { | |
| 227 var sink = '<' + qualifiedTagName(node); | |
| 228 if (!this.serializeAsHTMLDocument_(node) && namespaces && shouldAddNames paceElement(node, namespaces)) | |
| 229 sink += appendNamespace(node.prefix, node.namespaceURI, namespaces); | |
| 230 return sink; | |
| 231 }; | |
| 232 | |
| 233 XMLSerializerPrototype.serializeCloseTag_ = function(node, namespaces) { | |
| 234 var sink = ''; | |
| 235 if (this.shouldSelfClose_(node)) { | |
| 236 if (isHTMLElement(node)) | |
| 237 sink += ' '; | |
| 238 sink += '/'; | |
| 239 } | |
| 240 sink += '>'; | |
| 241 return sink; | |
| 242 }; | |
| 243 | |
| 244 XMLSerializerPrototype.serializeElement_ = function(node, namespaces) { | |
| 245 return this.serializeOpenTag_(node, namespaces) + | |
| 246 this.serializeAttributes_(node, namespaces) + | |
| 247 this.serializeCloseTag_(node, namespaces); | |
| 248 }; | |
| 249 | |
| 250 XMLSerializerPrototype.serializeText_ = function(node) { | |
| 251 if (!node.nodeValue) | |
| 252 return ''; | |
| 253 | |
| 254 if (this.serializeAsHTMLDocument_(node)) { | |
| 255 if (node.parentNode && isHTMLElement(node.parentNode)) { | |
| 256 var tagName = node.parentNode.localName; | |
| 257 if (tagName == 'script' || tagName == 'style' || tagName == 'xmp ') | |
| 258 return node.nodeValue; | |
| 259 } | |
| 260 return escapeHTMLPCDATA(node.nodeValue); | |
| 261 } | |
| 262 return escapePCDATA(node.nodeValue); | |
| 263 }; | |
| 264 | |
| 265 XMLSerializerPrototype.serializeComment_ = function(node) { | |
| 266 return '<!--' + node.nodeValue + '-->'; | |
| 267 }; | |
| 268 | |
| 269 XMLSerializerPrototype.serializeDocument_ = function(node) { | |
| 270 return this.documentXMLDeclaration_; | |
| 271 }; | |
| 272 | |
| 273 XMLSerializerPrototype.serializeDocumentType_ = function(node) { | |
| 274 var s = '<!DOCTYPE ' + node.name; | |
| 275 if (node.publicId) { | |
| 276 s += ' PUBLIC "' + node.publicId + '"'; | |
| 277 if (node.systemId) | |
| 278 s += ' "' + node.systemId + '"'; | |
| 279 } else if (node.systemId) { | |
| 280 s += ' SYSTEM "' + node.systemId + '"'; | |
| 281 } | |
| 282 s += '>'; | |
| 283 return s; | |
| 284 }; | |
| 285 | |
| 286 XMLSerializerPrototype.serializeProcessingInstruction_ = function(node) { | |
| 287 var s = '<?' + node.target; | |
| 288 if (node.nodeValue) | |
| 289 s += ' ' + node.nodeValue; | |
| 290 s += '?>'; | |
| 291 return s; | |
| 292 }; | |
| 293 | |
| 294 XMLSerializerPrototype.serializeCDataSection_ = function(node) { | |
| 295 return '<![CDATA[' + node.nodeValue + ']]>'; | |
| 296 }; | |
| 297 | |
| 298 XMLSerializerPrototype.serializeStartTag = function(node, namespaces) { | |
| 299 switch (node.nodeType) { | |
| 300 case Node.TEXT_NODE: | |
| 301 return this.serializeText_(node); | |
| 302 case Node.COMMENT_NODE: | |
| 303 return this.serializeComment_(node); | |
| 304 case Node.DOCUMENT_NODE: | |
| 305 return this.serializeDocument_(node); | |
| 306 case Node.DOCUMENT_TYPE_NODE: | |
| 307 return this.serializeDocumentType_(node); | |
| 308 case Node.PROCESSING_INSTRUCTION_NODE: | |
| 309 return this.serializeProcessingInstruction_(node); | |
| 310 case Node.ELEMENT_NODE: | |
| 311 return this.serializeElement_(node, namespaces); | |
| 312 case Node.CDATA_SECTION_NODE: | |
| 313 return this.serializeCDataSection_(node); | |
| 314 case Node.DOCUMENT_FRAGMENT_NODE: | |
| 315 default: | |
| 316 return ''; | |
| 317 } | |
| 318 }; | |
| 319 | |
| 320 XMLSerializerPrototype.shouldSelfClose_ = function(node) { | |
| 321 if (this.serializeAsHTMLDocument_(node)) | |
| 322 return false; | |
| 323 if (node.hasChildNodes()) | |
| 324 return false; | |
| 325 if (isTemplateElement(node) && node.content && node.content.hasChildNode s()) | |
| 326 return false; | |
| 327 if (isHTMLElement(node) && !isEndTagOmissible(node)) | |
| 328 return false; | |
| 329 return true; | |
| 330 }; | |
| 331 | |
| 332 XMLSerializerPrototype.serializeEndTag_ = function(node) { | |
| 333 if (this.shouldSelfClose_(node) || (!node.hasChildNodes() && isEndTagOmi ssible(node))) | |
| 334 return ''; | |
| 335 return '</' + qualifiedTagName(node) + '>'; | |
| 336 }; | |
| 337 | |
| 338 XMLSerializerPrototype.serializeNodesWithNamespaces_ = function(node, namesp aces) { | |
| 339 var sink = ''; | |
| 340 var localNamespaces = cloneNamespace(namespaces); | |
| 341 sink += this.serializeStartTag(node, localNamespaces); | |
| 342 | |
| 343 if (!(this.serializeAsHTMLDocument_(node) && isEndTagOmissible(node))) { | |
| 344 var current = node; | |
| 345 if (isTemplateElement(node)) | |
| 346 current = node.content; | |
| 347 current = current.firstChild; | |
| 348 while (current) { | |
| 349 sink += this.serializeNodesWithNamespaces_(current, localNamespa ces); | |
| 350 current = current.nextSibling; | |
| 351 } | |
| 352 } | |
| 353 if (node.nodeType == Node.ELEMENT_NODE) | |
| 354 sink += this.serializeEndTag_(node); | |
| 355 return sink; | |
| 356 }; | |
| 357 | |
| 358 XMLSerializerPrototype.serializeNodes_ = function(node) { | |
| 359 var namespaces = Object.create(null); | |
| 360 if (!this.serializeAsHTMLDocument_(node)) { | |
| 361 namespaces['xml'] = XML_NAMESPACE_URI; | |
| 362 namespaces['xmlns'] = XMLNS_NAMESPACE_URI; | |
| 363 } | |
| 364 | |
| 365 return this.serializeNodesWithNamespaces_(node, namespaces); | |
| 366 }; | |
| 367 | |
| 368 XMLSerializerPrototype.serializeToStringForPrivateScript = function(root, xm lDeclaration) { | |
|
tasak
2014/10/08 12:04:26
Done.
| |
| 369 this.serializationType_ = SERIALIZATION_TYPE_FORCED_XML; | |
| 370 this.documentXMLDeclaration_ = xmlDeclaration; | |
| 371 return this.serializeNodes_(root); | |
| 372 }; | |
| 373 }); | |
| OLD | NEW |