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