OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 'use strict'; | |
6 | |
7 installClass('XMLSerializer', function(XMLSerializerPrototype) { | |
8 | |
9 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.
| |
10 | |
11 var _XML_NAMESPACE_URI = 'http://www.w3.org/XML/1998/namespace'; | |
12 var _XLINK_NAMESPACE_URI = 'http://www.w3.org/1999/xlink'; | |
13 var _XMLNS_NAMESPACE_URI = 'http://www.w3.org/2000/xmlns'; | |
14 var _HTML_NAMESPACE_URI = 'http://www.w3.org/1999/xhtml'; | |
15 | |
16 var _SERIALIZATION_TYPE_AS_OWNER_DOCUMENT = 0; | |
17 var _SERIALIZATION_TYPE_FORCED_XML = 1; | |
18 | |
19 function escapePCDATA(text) { | |
20 return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, ' >'); | |
21 } | |
22 | |
23 function escapeHTMLPCDATA(text) { | |
24 return escapePCDATA(text).replace(/\u00A0/g, ' '); | |
25 } | |
26 | |
27 function escapeAttributeValue(text) { | |
28 return text.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>'); | |
29 } | |
30 | |
31 function escapeHTMLAttributeValue(text) { | |
32 return text.replace(/&/g, '&').replace(/"/g, '"').replace(/\u00 A0/g, ' '); | |
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.
| |
33 } | |
34 | |
35 function validateURI(uri) { | |
36 return uri ? uri.replace(/\/$/g, '') : ''; | |
37 } | |
38 | |
39 function cloneNamespace(obj) { | |
40 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.
| |
41 for (var i in obj) | |
42 clone[i] = obj[i]; | |
43 return clone; | |
44 } | |
45 | |
46 function isTemplateElement(node) { | |
47 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.
| |
48 } | |
49 | |
50 function generatePrefix(namespaces) { | |
51 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.
| |
52 var maxValue = 0; | |
53 for (var key in namespaces) { | |
54 var match = nsRegex.exec(key); | |
55 if (match) { | |
56 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.
| |
57 if (maxValue < value) | |
58 maxValue = value; | |
59 } | |
60 } | |
61 return 'ns' + (maxValue + 1); | |
62 } | |
63 | |
64 function qualifiedTagName(node) { | |
65 if (isHTMLElement(node)) | |
66 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.
| |
67 return node.tagName; | |
68 } | |
69 | |
70 function attributeIsInSerializedNamespace(attr) { | |
71 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.
| |
72 _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.
| |
73 _XMLNS_NAMESPACE_URI:true | |
74 }; | |
75 return _SERIALIZED_NAMESPACES[validateURI(attr.namespaceURI)]; | |
76 } | |
77 | |
78 function appendNamespace(prefix, namespaceURI, namespaces) { | |
79 if (!namespaceURI) | |
80 return ''; | |
81 | |
82 var result = ''; | |
83 var lookupKey = _EMPTY; | |
84 if (prefix) | |
85 lookupKey = prefix; | |
86 if (namespaces[lookupKey] != namespaceURI) { | |
87 namespaces[lookupKey] = namespaceURI; | |
88 result += ' xmlns'; | |
89 if (prefix) | |
90 result += ':' + prefix; | |
91 result += '="' + namespaceURI + '"'; | |
92 } | |
93 return result; | |
94 } | |
95 | |
96 function shouldAddNamespaceAttribute(attr, node) { | |
97 if (!attr.namespaceURI) | |
98 return false; | |
99 if (!attr.prefix) | |
100 return true; | |
101 if (!node.hasAttribute('xmlns:' + attr.prefix)) | |
102 return true; | |
103 return false; | |
104 } | |
105 | |
106 function shouldAddNamespaceElement(node, namespaces) { | |
107 var prefix = node.prefix; | |
108 if (!node.prefix) { | |
109 if (node.hasAttribute('xmlns')) { | |
110 namespaces[_EMPTY] = node.namespaceURI; | |
111 return false; | |
112 } | |
113 return true; | |
114 } | |
115 if (!node.hasAttribute('xmlns:' + node.prefix)) | |
116 return true; | |
117 return false; | |
118 } | |
119 | |
120 function isHTMLElement(node) { | |
121 if (node.nodeType != Node.ELEMENT_NODE) | |
122 return false; | |
123 | |
124 for (var runner = node; runner; runner = runner.parentNode) { | |
125 if (runner.nodeType == Node.ELEMENT_NODE && runner.namespaceURI != n ull) | |
126 return validateURI(runner.namespaceURI) == _HTML_NAMESPACE_URI; | |
127 } | |
128 return false; | |
129 } | |
130 | |
131 function isEndTagOmissible(node) { | |
132 if (node.nodeType != Node.ELEMENT_NODE) | |
133 return false; | |
134 // This tables comes from HTMLElement::ieForbidsInsertHTML(). | |
135 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.
| |
136 'area':true, 'base':true, 'basefont':true, 'br':true, 'col':true, 'e mbed':true, | |
arv (Not doing code reviews)
2014/10/07 15:37:11
bad formatting
tasak
2014/10/08 12:04:25
Done.
| |
137 'frame':true, 'hr':true, 'image':true, 'img':true, 'input':true, 'li nk':true, | |
138 'meta':true, 'param':true, 'source':true, 'wbr':true | |
139 }; | |
140 return _END_TAG_OMISSIBLE_TAGS[node.tagName.toLowerCase()]; | |
141 } | |
142 | |
143 XMLSerializerPrototype.serializeAsHTMLDocument_ = function(node) { | |
144 if (this.serializationType_ == _SERIALIZATION_TYPE_FORCED_XML) | |
145 return false; | |
146 return node.ownerDocument && (node.ownerDocument instanceof HTMLDocument ); | |
147 } | |
arv (Not doing code reviews)
2014/10/07 15:37:11
missing ;
tasak
2014/10/08 12:04:25
Done.
| |
148 | |
149 XMLSerializerPrototype.serializeAttribute_ = function(node, attr, namespaces ) { | |
150 var documentIsHTML = this.serializeAsHTMLDocument_(node); | |
151 var result = ''; | |
152 | |
153 var attrPrefix = attr.prefix; | |
154 var attrName = attr.localName; | |
155 if (documentIsHTML && !attributeIsInSerializedNamespace(attr)) { | |
156 result += ' ' + attr.localName; | |
157 } else { | |
158 var namespaceURI = validateURI(attr.namespaceURI); | |
159 if (namespaceURI == _XMLNS_NAMESPACE_URI) { | |
160 if (!attr.prefix && attr.localName != 'xmlns') | |
161 attrPrefix = 'xmlns'; | |
162 if (namespaces) { | |
163 var lookupKey = _EMPTY; | |
164 if (attr.prefix) | |
165 lookupKey = attr.localName; | |
166 namespaces[lookupKey] = attr.value; | |
167 } | |
168 } else if (namespaceURI == _XML_NAMESPACE_URI) { | |
169 if (!attr.prefix) | |
170 attrPrefix = 'xml'; | |
171 } else { | |
172 if (namespaceURI == _XLINK_NAMESPACE_URI) { | |
173 if (!attr.prefix) | |
174 attrPrefix = 'xlink'; | |
175 } | |
176 if (namespaces && shouldAddNamespaceAttribute(attr, node)) { | |
177 if (!attrPrefix) { | |
178 for (var i in namespaces) { | |
179 if (namespaces[i] == namespaceURI && i) { | |
180 attrPrefix = i; | |
181 break; | |
182 } | |
183 } | |
184 if (!attrPrefix) | |
185 attrPrefix = generatePrefix(namespaces); | |
186 } | |
187 result += appendNamespace(attrPrefix, namespaceURI, namespac es); | |
188 } | |
189 } | |
190 if (attrPrefix) { | |
191 result += ' ' + attrPrefix + ':' + attrName; | |
192 } else { | |
193 result += ' ' + attrName; | |
194 } | |
195 } | |
196 if (attr.value) { | |
197 var attrValue; | |
198 if (documentIsHTML) { | |
199 attrValue = escapeHTMLAttributeValue(String(attr.value)); | |
200 } else { | |
201 attrValue = escapeAttributeValue(String(attr.value)); | |
202 } | |
203 result += '="' + attrValue + '"'; | |
204 } | |
205 return result; | |
206 } | |
207 | |
208 XMLSerializerPrototype.serializeAttributes_ = function(node, namespaces) { | |
209 if (!node.attributes) | |
210 return ''; | |
211 | |
212 var sink = ''; | |
213 for (var i = 0; i < node.attributes.length; ++i) { | |
214 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.
| |
215 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.
| |
216 continue; | |
217 sink += this.serializeAttribute_(node, attr, namespaces); | |
218 } | |
219 return sink; | |
220 } | |
221 | |
222 XMLSerializerPrototype.serializeOpenTag_ = function(node, namespaces) { | |
223 var sink = '<' + qualifiedTagName(node); | |
224 if (!this.serializeAsHTMLDocument_(node) && namespaces && shouldAddNames paceElement(node, namespaces)) | |
225 sink += appendNamespace(node.prefix, node.namespaceURI, namespaces); | |
226 return sink; | |
227 } | |
228 | |
229 XMLSerializerPrototype.serializeCloseTag_ = function(node, namespaces) { | |
230 var sink = ''; | |
231 if (this.shouldSelfClose_(node)) { | |
232 if (isHTMLElement(node)) | |
233 sink += ' '; | |
234 sink += '/'; | |
235 } | |
236 sink += '>'; | |
237 return sink; | |
238 } | |
239 | |
240 XMLSerializerPrototype.serializeElement_ = function(node, namespaces) { | |
241 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.
| |
242 | |
243 sink += this.serializeAttributes_(node, namespaces); | |
244 sink += this.serializeCloseTag_(node, namespaces); | |
245 return sink; | |
246 } | |
247 | |
248 XMLSerializerPrototype.serializeText_ = function(node) { | |
249 if (!node.nodeValue) | |
250 return ''; | |
251 | |
252 if (this.serializeAsHTMLDocument_(node)) { | |
253 if (node.parentNode && isHTMLElement(node.parentNode)) { | |
254 var tagName = node.parentNode.tagName.toLowerCase(); | |
255 if (tagName == 'script' || tagName == 'style' || tagName == 'xmp ') | |
256 return node.nodeValue; | |
257 } | |
258 return escapeHTMLPCDATA(node.nodeValue); | |
259 } | |
260 return escapePCDATA(node.nodeValue); | |
261 } | |
262 | |
263 XMLSerializerPrototype.serializeComment_ = function(node) { | |
264 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
| |
265 } | |
266 | |
267 XMLSerializerPrototype.serializeDocument_ = function(node) { | |
268 return this.documentXMLDeclaration_; | |
269 } | |
270 | |
271 XMLSerializerPrototype.serializeDocumentType_ = function(node) { | |
272 var s = '<!DOCTYPE ' + node.name; | |
273 if (node.publicId) { | |
274 s += ' PUBLIC "' + node.publicId + '"'; | |
275 if (node.systemId) | |
276 s += ' "' + node.systemId + '"'; | |
277 } else if (node.systemId) { | |
278 s += ' SYSTEM "' + node.systemId + '"'; | |
279 } | |
280 s += '>'; | |
281 return s; | |
282 } | |
283 | |
284 XMLSerializerPrototype.serializeProcessingInstruction_ = function(node) { | |
285 var s = '<?' + node.target; | |
286 if (node.nodeValue) | |
287 s += ' ' + node.nodeValue; | |
288 s += '?>'; | |
289 return s; | |
290 } | |
291 | |
292 XMLSerializerPrototype.serializeCDataSection_ = function(node) { | |
293 return '<![CDATA[' + node.nodeValue + ']]>'; | |
294 } | |
295 | |
296 XMLSerializerPrototype.serializeStartTag = function(node, namespaces) { | |
297 switch (node.nodeType) { | |
298 case Node.TEXT_NODE: | |
299 return this.serializeText_(node); | |
300 case Node.COMMENT_NODE: | |
301 return this.serializeComment_(node); | |
302 case Node.DOCUMENT_NODE: | |
303 return this.serializeDocument_(node); | |
304 case Node.DOCUMENT_TYPE_NODE: | |
305 return this.serializeDocumentType_(node); | |
306 case Node.PROCESSING_INSTRUCTION_NODE: | |
307 return this.serializeProcessingInstruction_(node); | |
308 case Node.ELEMENT_NODE: | |
309 return this.serializeElement_(node, namespaces); | |
310 case Node.CDATA_SECTION_NODE: | |
311 return this.serializeCDataSection_(node); | |
312 case Node.DOCUMENT_FRAGMENT_NODE: | |
313 default: | |
314 return ''; | |
315 } | |
316 } | |
317 | |
318 XMLSerializerPrototype.shouldSelfClose_ = function(node) { | |
319 if (this.serializeAsHTMLDocument_(node)) | |
320 return false; | |
321 if (node.hasChildNodes()) | |
322 return false; | |
323 if (isTemplateElement(node) && node.content && node.content.hasChildNode s()) | |
324 return false; | |
325 if (isHTMLElement(node) && !isEndTagOmissible(node)) | |
326 return false; | |
327 return true; | |
328 } | |
329 | |
330 XMLSerializerPrototype.serializeEndTag_ = function(node) { | |
331 if (this.shouldSelfClose_(node) || (!node.hasChildNodes() && isEndTagOmi ssible(node))) | |
332 return ''; | |
333 return '</' + qualifiedTagName(node) + '>'; | |
334 } | |
335 | |
336 XMLSerializerPrototype.serializeNodesWithNamespaces_ = function(node, namesp aces) { | |
337 var sink = ''; | |
338 var localNamespaces = cloneNamespace(namespaces); | |
339 sink += this.serializeStartTag(node, localNamespaces); | |
340 | |
341 if (!(this.serializeAsHTMLDocument_(node) && isEndTagOmissible(node))) { | |
342 var current = node; | |
343 if (isTemplateElement(node)) | |
344 current = node.content; | |
345 current = current.firstChild; | |
346 while (current) { | |
347 sink += this.serializeNodesWithNamespaces_(current, localNamespa ces); | |
348 current = current.nextSibling; | |
349 } | |
350 } | |
351 if (node.nodeType == Node.ELEMENT_NODE) | |
352 sink += this.serializeEndTag_(node); | |
353 return sink; | |
354 } | |
355 | |
356 XMLSerializerPrototype.serializeNodes_ = function(node) { | |
357 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.
| |
358 if (!this.serializeAsHTMLDocument_(node)) { | |
359 namespaces['xml'] = _XML_NAMESPACE_URI; | |
360 namespaces['xmlns'] = _XMLNS_NAMESPACE_URI; | |
361 } | |
362 | |
363 return this.serializeNodesWithNamespaces_(node, namespaces); | |
364 } | |
365 | |
366 XMLSerializerPrototype.serializeToStringInternal = function(root, xmlDeclara tion) { | |
367 this.serializationType_ = _SERIALIZATION_TYPE_FORCED_XML; | |
368 this.documentXMLDeclaration_ = xmlDeclaration; | |
369 return this.serializeNodes_(root); | |
370 } | |
371 }); | |
OLD | NEW |