Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(35)

Unified Diff: Source/core/xml/XMLSerializer.js

Issue 534583002: Implemented XMLSerializer in PrivateScript. (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Created 6 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « Source/core/xml/XMLSerializer.idl ('k') | public/blink_resources.grd » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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..499d4ed082aa7b3e30cfbc5a7fe8e0d1a084e008
--- /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 = '';
arv (Not doing code reviews) 2014/10/07 15:37:10 No leading underscores for variable names.
tasak 2014/10/08 12:04:25 Done.
+
+ 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+ }
+
+ function escapeHTMLPCDATA(text) {
+ return escapePCDATA(text).replace(/\u00A0/g, '&nbsp;');
+ }
+
+ function escapeAttributeValue(text) {
+ return text.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+ }
+
+ function escapeHTMLAttributeValue(text) {
+ return text.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/\u00A0/g, '&nbsp;');
arv (Not doing code reviews) 2014/10/07 15:37:11 It is kind of sad that we have to create all these
tasak 2014/10/08 12:04:25 Done.
+ }
+
+ function validateURI(uri) {
+ return uri ? uri.replace(/\/$/g, '') : '';
+ }
+
+ function cloneNamespace(obj) {
+ var clone = {};
arv (Not doing code reviews) 2014/10/07 15:37:11 Object.create(null) or {__proto__: null} or your m
tasak 2014/10/08 12:04:25 Done.
+ for (var i in obj)
+ clone[i] = obj[i];
+ return clone;
+ }
+
+ function isTemplateElement(node) {
+ return isHTMLElement(node) && node.tagName.toLowerCase() == 'template';
arv (Not doing code reviews) 2014/10/07 15:37:11 node.localName === 'template' or node.tagName =
tasak 2014/10/08 12:04:25 Done.
+ }
+
+ function generatePrefix(namespaces) {
+ var nsRegex = new RegExp('^ns([0-9]+)$', 'g');
arv (Not doing code reviews) 2014/10/07 15:37:11 /^ns([0-9]+)$/g
tasak 2014/10/08 12:04:25 Done.
+ var maxValue = 0;
+ for (var key in namespaces) {
+ var match = nsRegex.exec(key);
+ if (match) {
+ var value = parseInt(match[1]);
arv (Not doing code reviews) 2014/10/07 15:37:11 parseInt(x, 10) or you might get octal you can al
tasak 2014/10/08 12:04:25 Done.
+ if (maxValue < value)
+ maxValue = value;
+ }
+ }
+ return 'ns' + (maxValue + 1);
+ }
+
+ function qualifiedTagName(node) {
+ if (isHTMLElement(node))
+ return node.tagName.toLowerCase();
arv (Not doing code reviews) 2014/10/07 15:37:11 localName should be lowercase already
tasak 2014/10/08 12:04:25 Done.
+ return node.tagName;
+ }
+
+ function attributeIsInSerializedNamespace(attr) {
+ var _SERIALIZED_NAMESPACES = {
arv (Not doing code reviews) 2014/10/07 15:37:11 no leading underscores
tasak 2014/10/08 12:04:26 Done.
+ _XML_NAMESPACE_URI:true, _XLINK_NAMESPACE_URI:true,
arv (Not doing code reviews) 2014/10/07 15:37:11 bad formatting
tasak 2014/10/08 12:04:25 Done.
+ _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 = {
arv (Not doing code reviews) 2014/10/07 15:37:11 move out of function?
tasak 2014/10/08 12:04:25 Done.
+ 'area':true, 'base':true, 'basefont':true, 'br':true, 'col':true, 'embed':true,
arv (Not doing code reviews) 2014/10/07 15:37:11 bad formatting
tasak 2014/10/08 12:04:25 Done.
+ '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);
+ }
arv (Not doing code reviews) 2014/10/07 15:37:11 missing ;
tasak 2014/10/08 12:04:25 Done.
+
+ 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);
arv (Not doing code reviews) 2014/10/07 15:37:11 node.attributes[i]
tasak 2014/10/08 12:04:26 Done.
+ if (attr.name.indexOf('_moz') == 0)
arv (Not doing code reviews) 2014/10/07 15:37:11 what? Is this part of the spec?
tasak 2014/10/08 12:04:26 Done.
+ 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);
arv (Not doing code reviews) 2014/10/07 15:37:10 return this.serializeOpenTag_(node, namespaces) +
tasak 2014/10/08 12:04:25 Done.
+
+ 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 + '-->';
arv (Not doing code reviews) 2014/10/07 15:37:11 escape nodeValue?
tasak 2014/10/08 12:04:25 Looking at //src/third_party/WebKit/Source/core/ed
arv (Not doing code reviews) 2014/10/08 13:38:13 This will definitely not round trip but I guess th
+ }
+
+ 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 = {};
arv (Not doing code reviews) 2014/10/07 15:37:11 Object.create(null) every time you use an object a
tasak 2014/10/08 12:04:25 Done.
+ 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);
+ }
+});
« no previous file with comments | « Source/core/xml/XMLSerializer.idl ('k') | public/blink_resources.grd » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698