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..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); |
| + } |
| +}); |