Index: Source/core/xml/XMLSerializer.js |
diff --git a/Source/core/xml/XMLSerializer.js b/Source/core/xml/XMLSerializer.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f651d6c34f1c4bd171d2b1ae3fb39d90d384b45c |
--- /dev/null |
+++ b/Source/core/xml/XMLSerializer.js |
@@ -0,0 +1,371 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+'use strict'; |
+ |
+installClass('XMLSerializer', function(XMLSerializerPrototype) { |
+ |
+ var _EMPTY = ''; |
+ |
+ var _XML_NAMESPACE_URI = 'http://www.w3.org/XML/1998/namespace'; |
+ var _XLINK_NAMESPACE_URI = 'http://www.w3.org/1999/xlink'; |
+ var _XMLNS_NAMESPACE_URI = 'http://www.w3.org/2000/xmlns'; |
+ var _HTML_NAMESPACE_URI = 'http://www.w3.org/1999/xhtml'; |
+ |
+ var _SERIALIZATION_TYPE_AS_OWNER_DOCUMENT = 0; |
+ var _SERIALIZATION_TYPE_FORCED_XML = 1; |
+ |
+ function escapePCDATA(text) { |
+ return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); |
+ } |
+ |
+ function escapeHTMLPCDATA(text) { |
+ return escapePCDATA(text).replace(/\u00A0/g, ' '); |
+ } |
+ |
+ function escapeAttributeValue(text) { |
+ return text.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>'); |
+ } |
+ |
+ function escapeHTMLAttributeValue(text) { |
+ return text.replace(/&/g, '&').replace(/"/g, '"').replace(/\u00A0/g, ' '); |
+ } |
+ |
+ function validateURI(uri) { |
+ return uri ? uri.replace(/\/$/g, '') : ''; |
+ } |
+ |
+ function cloneNamespace(obj) { |
+ var clone = {}; |
+ for (var i in obj) |
+ clone[i] = obj[i]; |
+ return clone; |
+ } |
+ |
+ function isTemplateElement(node) { |
+ return isHTMLElement(node) && node.tagName.toLowerCase() == 'template'; |
+ } |
+ |
+ function generatePrefix(namespaces) { |
+ var nsRegex = new RegExp('^ns([0-9]+)$', 'g'); |
+ var maxValue = 0; |
+ for (var key in namespaces) { |
+ var match = nsRegex.exec(key); |
+ if (match) { |
+ var value = parseInt(match[1]); |
+ if (maxValue < value) |
+ maxValue = value; |
+ } |
+ } |
+ return 'ns' + (maxValue + 1); |
+ } |
+ |
+ function qualifiedTagName(node) { |
+ if (isHTMLElement(node)) |
+ return node.tagName.toLowerCase(); |
+ return node.tagName; |
+ } |
+ |
+ function attributeIsInSerializedNamespace(attr) { |
+ var _SERIALIZED_NAMESPACES = { |
+ _XML_NAMESPACE_URI:true, _XLINK_NAMESPACE_URI:true, |
+ _XMLNS_NAMESPACE_URI:true |
+ }; |
+ return _SERIALIZED_NAMESPACES[validateURI(attr.namespaceURI)]; |
+ } |
+ |
+ function appendNamespace(prefix, namespaceURI, namespaces) { |
+ if (!namespaceURI) |
+ return ''; |
+ |
+ var result = ''; |
+ var lookupKey = _EMPTY; |
+ if (prefix) |
+ lookupKey = prefix; |
+ if (namespaces[lookupKey] != namespaceURI) { |
+ namespaces[lookupKey] = namespaceURI; |
+ result += ' xmlns'; |
+ if (prefix) |
+ result += ':' + prefix; |
+ result += '="' + namespaceURI + '"'; |
+ } |
+ return result; |
+ } |
+ |
+ function shouldAddNamespaceAttribute(attr, node) { |
+ if (!attr.namespaceURI) |
+ return false; |
+ if (!attr.prefix) |
+ return true; |
+ if (!node.hasAttribute('xmlns:' + attr.prefix)) |
+ return true; |
+ return false; |
+ } |
+ |
+ function shouldAddNamespaceElement(node, namespaces) { |
+ var prefix = node.prefix; |
+ if (!node.prefix) { |
+ if (node.hasAttribute('xmlns')) { |
+ namespaces[_EMPTY] = node.namespaceURI; |
+ return false; |
+ } |
+ return true; |
+ } |
+ if (!node.hasAttribute('xmlns:' + node.prefix)) |
+ return true; |
+ return false; |
+ } |
+ |
+ function isHTMLElement(node) { |
+ if (node.nodeType != Node.ELEMENT_NODE) |
+ return false; |
+ |
+ for (var runner = node; runner; runner = runner.parentNode) { |
+ if (runner.nodeType == Node.ELEMENT_NODE && runner.namespaceURI != null) |
+ return validateURI(runner.namespaceURI) == _HTML_NAMESPACE_URI; |
+ } |
+ return false; |
+ } |
+ |
+ function isEndTagOmissible(node) { |
+ if (node.nodeType != Node.ELEMENT_NODE) |
+ return false; |
+ // This tables comes from HTMLElement::ieForbidsInsertHTML(). |
+ var _END_TAG_OMISSIBLE_TAGS = { |
+ 'area':true, 'base':true, 'basefont':true, 'br':true, 'col':true, 'embed':true, |
+ 'frame':true, 'hr':true, 'image':true, 'img':true, 'input':true, 'link':true, |
+ 'meta':true, 'param':true, 'source':true, 'wbr':true |
+ }; |
+ return _END_TAG_OMISSIBLE_TAGS[node.tagName.toLowerCase()]; |
+ } |
+ |
+ XMLSerializerPrototype.serializeAsHTMLDocument_ = function(node) { |
+ if (this.serializationType_ == _SERIALIZATION_TYPE_FORCED_XML) |
+ return false; |
+ return node.ownerDocument && (node.ownerDocument instanceof HTMLDocument); |
+ } |
+ |
+ XMLSerializerPrototype.serializeAttribute_ = function(node, attr, namespaces) { |
+ var documentIsHTML = this.serializeAsHTMLDocument_(node); |
+ var result = ''; |
+ |
+ var attrPrefix = attr.prefix; |
+ var attrName = attr.localName; |
+ if (documentIsHTML && !attributeIsInSerializedNamespace(attr)) { |
+ result += ' ' + attr.localName; |
+ } else { |
+ var namespaceURI = validateURI(attr.namespaceURI); |
+ if (namespaceURI == _XMLNS_NAMESPACE_URI) { |
+ if (!attr.prefix && attr.localName != 'xmlns') |
+ attrPrefix = 'xmlns'; |
+ if (namespaces) { |
+ var lookupKey = _EMPTY; |
+ if (attr.prefix) |
+ lookupKey = attr.localName; |
+ namespaces[lookupKey] = attr.value; |
+ } |
+ } else if (namespaceURI == _XML_NAMESPACE_URI) { |
+ if (!attr.prefix) |
+ attrPrefix = 'xml'; |
+ } else { |
+ if (namespaceURI == _XLINK_NAMESPACE_URI) { |
+ if (!attr.prefix) |
+ attrPrefix = 'xlink'; |
+ } |
+ if (namespaces && shouldAddNamespaceAttribute(attr, node)) { |
+ if (!attrPrefix) { |
+ for (var i in namespaces) { |
+ if (namespaces[i] == namespaceURI && i) { |
+ attrPrefix = i; |
+ break; |
+ } |
+ } |
+ if (!attrPrefix) |
+ attrPrefix = generatePrefix(namespaces); |
+ } |
+ result += appendNamespace(attrPrefix, namespaceURI, namespaces); |
+ } |
+ } |
+ if (attrPrefix) { |
+ result += ' ' + attrPrefix + ':' + attrName; |
+ } else { |
+ result += ' ' + attrName; |
+ } |
+ } |
+ if (attr.value) { |
+ var attrValue; |
+ if (documentIsHTML) { |
+ attrValue = escapeHTMLAttributeValue(String(attr.value)); |
+ } else { |
+ attrValue = escapeAttributeValue(String(attr.value)); |
+ } |
+ result += '="' + attrValue + '"'; |
+ } |
+ return result; |
+ } |
+ |
+ XMLSerializerPrototype.serializeAttributes_ = function(node, namespaces) { |
+ if (!node.attributes) |
+ return ''; |
+ |
+ var sink = ''; |
+ for (var i = 0; i < node.attributes.length; ++i) { |
+ var attr = node.attributes.item(i); |
+ if (attr.name.indexOf('_moz') == 0) |
+ continue; |
+ sink += this.serializeAttribute_(node, attr, namespaces); |
+ } |
+ return sink; |
+ } |
+ |
+ XMLSerializerPrototype.serializeOpenTag_ = function(node, namespaces) { |
+ var sink = '<' + qualifiedTagName(node); |
+ if (!this.serializeAsHTMLDocument_(node) && namespaces && shouldAddNamespaceElement(node, namespaces)) |
+ sink += appendNamespace(node.prefix, node.namespaceURI, namespaces); |
+ return sink; |
+ } |
+ |
+ XMLSerializerPrototype.serializeCloseTag_ = function(node, namespaces) { |
+ var sink = ''; |
+ if (this.shouldSelfClose_(node)) { |
+ if (isHTMLElement(node)) |
+ sink += ' '; |
+ sink += '/'; |
+ } |
+ sink += '>'; |
+ return sink; |
+ } |
+ |
+ XMLSerializerPrototype.serializeElement_ = function(node, namespaces) { |
+ var sink = this.serializeOpenTag_(node, namespaces); |
+ |
+ sink += this.serializeAttributes_(node, namespaces); |
+ sink += this.serializeCloseTag_(node, namespaces); |
+ return sink; |
+ } |
+ |
+ XMLSerializerPrototype.serializeText_ = function(node) { |
+ if (!node.nodeValue) |
+ return ''; |
+ |
+ if (this.serializeAsHTMLDocument_(node)) { |
+ if (node.parentNode && isHTMLElement(node.parentNode)) { |
+ var tagName = node.parentNode.tagName.toLowerCase(); |
+ if (tagName == 'script' || tagName == 'style' || tagName == 'xmp') |
+ return node.nodeValue; |
+ } |
+ return escapeHTMLPCDATA(node.nodeValue); |
+ } |
+ return escapePCDATA(node.nodeValue); |
+ } |
+ |
+ XMLSerializerPrototype.serializeComment_ = function(node) { |
+ return '<!--' + node.nodeValue + '-->'; |
+ } |
+ |
+ XMLSerializerPrototype.serializeDocument_ = function(node) { |
+ return this.documentXMLDeclaration_; |
+ } |
+ |
+ XMLSerializerPrototype.serializeDocumentType_ = function(node) { |
+ var s = '<!DOCTYPE ' + node.name; |
+ if (node.publicId) { |
+ s += ' PUBLIC "' + node.publicId + '"'; |
+ if (node.systemId) |
+ s += ' "' + node.systemId + '"'; |
+ } else if (node.systemId) { |
+ s += ' SYSTEM "' + node.systemId + '"'; |
+ } |
+ s += '>'; |
+ return s; |
+ } |
+ |
+ XMLSerializerPrototype.serializeProcessingInstruction_ = function(node) { |
+ var s = '<?' + node.target; |
+ if (node.nodeValue) |
+ s += ' ' + node.nodeValue; |
+ s += '?>'; |
+ return s; |
+ } |
+ |
+ XMLSerializerPrototype.serializeCDataSection_ = function(node) { |
+ return '<![CDATA[' + node.nodeValue + ']]>'; |
+ } |
+ |
+ XMLSerializerPrototype.serializeStartTag = function(node, namespaces) { |
+ if (node.nodeType == Node.TEXT_NODE) { |
+ return this.serializeText_(node); |
+ } else if (node.nodeType == Node.COMMENT_NODE) { |
yosin_UTC9
2014/09/04 01:10:42
nit: We don't need to have |else| after |return|.
tasak
2014/09/04 11:17:35
Done.
|
+ return this.serializeComment_(node); |
+ } else if (node.nodeType == Node.DOCUMENT_NODE) { |
+ return this.serializeDocument_(node); |
+ } else if (node.nodeType == Node.DOCUMENT_TYPE_NODE) { |
+ return this.serializeDocumentType_(node); |
+ } else if (node.nodeType == Node.DOCUMENT_FRAGMENT_NODE) { |
+ return ''; |
+ } else if (node.nodeType == Node.PROCESSING_INSTRUCTION_NODE) { |
+ return this.serializeProcessingInstruction_(node); |
+ } else if (node.nodeType == Node.ELEMENT_NODE) { |
+ return this.serializeElement_(node, namespaces); |
+ } else if (node.nodeType == Node.CDATA_SECTION_NODE) { |
+ return this.serializeCDataSection_(node); |
+ } else { |
+ return ''; |
+ } |
+ } |
+ |
+ XMLSerializerPrototype.shouldSelfClose_ = function(node) { |
+ if (this.serializeAsHTMLDocument_(node)) |
+ return false; |
+ if (node.hasChildNodes()) |
+ return false; |
+ if (isTemplateElement(node) && node.content && node.content.hasChildNodes()) |
+ return false; |
+ if (isHTMLElement(node) && !isEndTagOmissible(node)) |
+ return false; |
+ return true; |
+ } |
+ |
+ XMLSerializerPrototype.serializeEndTag_ = function(node) { |
+ if (this.shouldSelfClose_(node) || (!node.hasChildNodes() && isEndTagOmissible(node))) |
+ return ''; |
+ return '</' + qualifiedTagName(node) + '>'; |
+ } |
+ |
+ XMLSerializerPrototype.serializeNodesWithNamespaces_ = function(node, namespaces) { |
+ var sink = ''; |
+ var localNamespaces = cloneNamespace(namespaces); |
+ sink += this.serializeStartTag(node, localNamespaces); |
+ |
+ if (!(this.serializeAsHTMLDocument_(node) && isEndTagOmissible(node))) { |
+ var current = node; |
+ if (isTemplateElement(node)) |
+ current = node.content; |
+ current = current.firstChild; |
+ while (current) { |
+ sink += this.serializeNodesWithNamespaces_(current, localNamespaces); |
+ current = current.nextSibling; |
+ } |
+ } |
+ if (node.nodeType == Node.ELEMENT_NODE) |
+ sink += this.serializeEndTag_(node); |
+ return sink; |
+ } |
+ |
+ XMLSerializerPrototype.serializeNodes_ = function(node) { |
+ var namespaces = {}; |
+ if (!this.serializeAsHTMLDocument_(node)) { |
+ namespaces['xml'] = _XML_NAMESPACE_URI; |
+ namespaces['xmlns'] = _XMLNS_NAMESPACE_URI; |
+ } |
+ |
+ return this.serializeNodesWithNamespaces_(node, namespaces); |
+ } |
+ |
+ XMLSerializerPrototype.serializeToStringInternal = function(root, xmlDeclaration) { |
+ this.serializationType_ = _SERIALIZATION_TYPE_FORCED_XML; |
+ this.documentXMLDeclaration_ = xmlDeclaration; |
+ return this.serializeNodes_(root); |
+ } |
+}); |