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..d70cf16472eeeff19cfda86a8ff4e85db3eba10f |
--- /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"; |
yosin_UTC9
2014/09/03 03:35:02
Let's use one kind of quote.
I suggest to use sing
tasak
2014/09/03 09:38:00
Done.
|
+ |
+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 escape(text) { |
+ return text.replace(/&/g, '&').replace(/\u0022/g, '"').replace(/</g, '<').replace(/>/g, '>'); |
+ } |
+ |
+ function validateURI(uri) { |
+ if (!uri) |
yosin_UTC9
2014/09/03 03:35:02
nit: return uri === '' ? '' : uri.replace(/\/$/g,
tasak
2014/09/03 09:38:00
We need to consier the case that uri is null.
So
|
+ return uri; |
+ return uri.replace(/\/$/g, ''); |
+ } |
+ |
+ function cloneNamespace(obj) { |
yosin_UTC9
2014/09/03 03:35:01
This function make clone of object, not sure why w
tasak
2014/09/03 09:38:00
When we visit child nodes, one of the child nodes
|
+ var clone = {}; |
yosin_UTC9
2014/09/03 03:35:01
nit: return Object.create(obj);
tasak
2014/09/03 09:38:00
I think, this doesn't work.
|
+ for (var i in obj) |
+ clone[i] = obj[i]; |
+ return clone; |
+ } |
+ |
+ function isTemplateElement(node) { |
+ if (isHTMLElement(node) && node.tagName.toLowerCase() == 'template') |
yosin_UTC9
2014/09/03 03:35:01
nit: Just use |return| statement.
nit: We don't ne
tasak
2014/09/03 09:38:00
I don't think so. If we always compare with 'TEMPL
|
+ return true; |
+ return false; |
+ } |
+ |
+ function generatePrefix(namespaces) { |
+ var _MAX_RETRY = 10; |
+ for (var count = 0; count < _MAX_RETRY; ++count) { |
+ var candidate = 'ns' + Date.now(); |
yosin_UTC9
2014/09/03 03:35:01
We should have closed variable, rather than using
tasak
2014/09/03 09:38:00
I tried another approach.
|
+ if (!namespaces[candidate]) |
+ return candidate; |
+ } |
+ return null; |
+ } |
+ |
+ function qualifiedTagName(node) { |
+ var tagName = node.tagName.toLowerCase(); |
yosin_UTC9
2014/09/03 03:35:02
not obvious what you want to accomplish.
Can we us
tasak
2014/09/03 09:38:00
I updated.
If a given node is HTMLElement, we shou
|
+ if (tagName != node.tagName && node.tagName.toUpperCase() != node.tagName) |
+ tagName = node.tagName; |
+ return tagName; |
+ } |
+ |
+ function attributeIsInSerializedNamespace(attr) { |
+ var namespaceURI = validateURI(attr.namespaceURI); |
+ if (namespaceURI == _XML_NAMESPACE_URI || |
yosin_UTC9
2014/09/03 03:35:02
We should have |Set| which member is well know nam
tasak
2014/09/03 09:38:00
Done.
|
+ namespaceURI == _XLINK_NAMESPACE_URI || |
+ namespaceURI == _XMLNS_NAMESPACE_URI) |
+ return true; |
+ return false; |
+ } |
+ |
+ 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) { |
yosin_UTC9
2014/09/03 03:35:01
nit: No need to have "{}"
tasak
2014/09/03 09:38:00
Done.
|
+ 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; |
+ |
+ while (node) { |
yosin_UTC9
2014/09/03 03:35:01
nit: it is better to use for statement
for (var r
tasak
2014/09/03 09:38:00
Done.
|
+ if (node.nodeType == Node.ELEMENT_NODE) { |
+ var namespaceURI = validateURI(node.namespaceURI); |
+ if (namespaceURI) |
+ return namespaceURI == _HTML_NAMESPACE_URI; |
+ } |
+ node = node.parentNode; |
+ } |
+ return false; |
+ } |
+ |
+ function elementCannotHaveEndTag(node) { |
yosin_UTC9
2014/09/03 03:35:01
nit: HTML5 use term "Tag ommission in text/html"
h
tasak
2014/09/03 09:38:00
Done.
|
+ if (node.nodeType != Node.ELEMENT_NODE) |
+ return false; |
+ var _IE_FORBIDS_TAG = {'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}; |
+ var tagName = node.tagName.toLowerCase(); |
yosin_UTC9
2014/09/03 03:35:01
nit: To avoid string object construction, we shoul
tasak
2014/09/03 09:38:00
I found layout test failure if using upper case le
|
+ if (_IE_FORBIDS_TAG[tagName]) |
+ return true; |
+ return false; |
+ } |
+ |
+ XMLSerializerPrototype.serializeAsHTMLDocument = function(node) { |
yosin_UTC9
2014/09/03 03:35:01
I suggest to use
Object.defineProperties(XMLSerial
tasak
2014/09/03 09:38:00
I'm not sure whether we allow users to see interna
yosin_UTC9
2014/09/04 00:58:44
When you use this JS file in standalone environmen
|
+ if (this.serialization_type_ == _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) |
+ result += '="' + escape(String(attr.value)) + '"'; |
+ return result; |
+ } |
+ |
+ XMLSerializerPrototype.serializeAttributes = function(node, namespaces) { |
+ var attributes = node.attributes; |
+ if (!attributes) |
+ return ''; |
+ |
+ var sink = ''; |
+ var attrs = []; |
+ for (var index = 0; index < attributes.length; ++index) |
+ attrs.push(attributes[index]); |
+ |
+ var serializer = this; |
+ attrs.sort(function(a, b) { |
+ return a.name <= b.name ? -1 : 0; |
+ }).forEach(function(attr) { |
+ if (attr.name.indexOf('_moz') == 0) |
+ return; |
+ sink += serializer.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) { |
+ return escape(node.nodeValue); |
+ } |
+ |
+ XMLSerializerPrototype.serializeComment = function(node) { |
+ return '<!--' + node.nodeValue + '-->'; |
+ } |
+ |
+ XMLSerializerPrototype.serializeDocument = function(node) { |
+ if (!node.hasXMLDeclaration) |
+ return ''; |
+ |
+ var s = '<?xml'; |
+ if (node.xmlVersion) |
+ s += ' version="' + node.xmlVersion + '"'; |
+ if (node.xmlEncoding) |
+ s += ' encoding="' + node.xmlEncoding + '"'; |
+ if (node.isXMLStandaloneSpecified) { |
+ s += ' standalone="' |
+ if (node.xmlStandalone) { |
+ s += 'yes'; |
+ } else { |
+ s += 'no'; |
+ } |
+ s += '"'; |
+ } |
+ s += '?>'; |
+ return s; |
+ } |
+ |
+ XMLSerializerPrototype.serializeDocumentType = function(node) { |
+ var s = '<!DOCTYPE ' + node.name; |
+ if (node.publicId) { |
+ s += ' PUBLIC "' + node.publicId + '"'; |
+ if (node.systemId) |
+ s += ' "' + 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) { |
+ 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) && !elementCannotHaveEndTag(node)) |
+ return false; |
+ return true; |
+ } |
+ |
+ XMLSerializerPrototype.serializeEndTag = function(node) { |
+ if (this.shouldSelfClose(node) || (!node.hasChildNodes() && elementCannotHaveEndTag(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) && elementCannotHaveEndTag(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; |
+ |
+ return this.serializeNodesWithNamespaces(node, namespaces); |
+ } |
+ |
+ XMLSerializerPrototype.serializeToString = function(root) { |
+ if (!root) |
+ throw new TypeError("Invalid node value."); |
+ |
+ this.serialization_type_ = _SERIALIZATION_TYPE_FORCED_XML; |
+ return this.serializeNodes(root); |
+ } |
+}); |