Chromium Code Reviews| 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); |
| + }; |
| +}); |