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..4fdef031f3592338835e9e92548d843b9fcac4b3 |
--- /dev/null |
+++ b/Source/core/xml/XMLSerializer.js |
@@ -0,0 +1,373 @@ |
+// 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) { |
+ text.replace(/&|"|\u00A0/g, function(c) { |
+ switch (c) { |
+ case '&': return '&'; |
+ case '"': return '"'; |
+ case '\u00A0': return ' '; |
+ } |
+ }); |
+ } |
+ |
+ function validateURI(uri) { |
+ return uri ? uri.replace(/\/$/g, '') : ''; |
+ } |
+ |
+ function cloneNamespace(obj) { |
+ var clone = Object.create(null); |
+ for (var i in obj) |
+ clone[i] = obj[i]; |
+ return clone; |
+ } |
+ |
+ function isTemplateElement(node) { |
+ return isHTMLElement(node) && node.localName == 'template'; |
+ } |
+ |
+ function generatePrefix(namespaces) { |
+ var nsRegex = /^ns([0-9]+)$/g; |
+ var maxValue = 0; |
+ for (var key in namespaces) { |
+ var match = nsRegex.exec(key); |
+ if (match) { |
+ var value = parseInt(match[1], 10); |
+ if (maxValue < value) |
+ maxValue = value; |
+ } |
+ } |
+ return 'ns' + (maxValue + 1); |
+ } |
+ |
+ function qualifiedTagName(node) { |
+ return isHTMLElement(node) ? node.localName : node.tagName; |
+ } |
+ |
+ var SERIALIZED_NAMESPACES = { |
+ XML_NAMESPACE_URI: true, |
+ XLINK_NAMESPACE_URI: true, |
+ XMLNS_NAMESPACE_URI: true |
+ }; |
+ |
+ function attributeIsInSerializedNamespace(attr) { |
+ 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; |
+ } |
+ |
+ // 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 |
+ }; |
+ |
+ function isEndTagOmissible(node) { |
+ if (node.nodeType != Node.ELEMENT_NODE) |
+ return false; |
+ return END_TAG_OMISSIBLE_TAGS[node.localName]; |
+ } |
+ |
+ 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) |
+ sink += this.serializeAttribute_(node, node.attributes[i], 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) { |
+ return this.serializeOpenTag_(node, namespaces) + |
+ this.serializeAttributes_(node, namespaces) + |
+ this.serializeCloseTag_(node, namespaces); |
+ }; |
+ |
+ XMLSerializerPrototype.serializeText_ = function(node) { |
+ if (!node.nodeValue) |
+ return ''; |
+ |
+ if (this.serializeAsHTMLDocument_(node)) { |
+ if (node.parentNode && isHTMLElement(node.parentNode)) { |
+ var tagName = node.parentNode.localName; |
+ 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) { |
+ switch (node.nodeType) { |
+ case Node.TEXT_NODE: |
+ return this.serializeText_(node); |
+ case Node.COMMENT_NODE: |
+ return this.serializeComment_(node); |
+ case Node.DOCUMENT_NODE: |
+ return this.serializeDocument_(node); |
+ case Node.DOCUMENT_TYPE_NODE: |
+ return this.serializeDocumentType_(node); |
+ case Node.PROCESSING_INSTRUCTION_NODE: |
+ return this.serializeProcessingInstruction_(node); |
+ case Node.ELEMENT_NODE: |
+ return this.serializeElement_(node, namespaces); |
+ case Node.CDATA_SECTION_NODE: |
+ return this.serializeCDataSection_(node); |
+ case Node.DOCUMENT_FRAGMENT_NODE: |
+ default: |
+ 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 = Object.create(null); |
+ if (!this.serializeAsHTMLDocument_(node)) { |
+ namespaces['xml'] = XML_NAMESPACE_URI; |
+ namespaces['xmlns'] = XMLNS_NAMESPACE_URI; |
+ } |
+ |
+ return this.serializeNodesWithNamespaces_(node, namespaces); |
+ }; |
+ |
+ XMLSerializerPrototype.serializeToStringForPrivateScript = function(root, xmlDeclaration) { |
tasak
2014/10/08 12:04:26
Done.
|
+ this.serializationType_ = SERIALIZATION_TYPE_FORCED_XML; |
+ this.documentXMLDeclaration_ = xmlDeclaration; |
+ return this.serializeNodes_(root); |
+ }; |
+}); |