Move URL from WebCore to WTF
[WebKit-https.git] / Source / WebCore / editing / MarkupAccumulator.cpp
1 /*
2  * Copyright (C) 2004-2017 Apple Inc. All rights reserved.
3  * Copyright (C) 2009, 2010 Google Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "MarkupAccumulator.h"
29
30 #include "CDATASection.h"
31 #include "Comment.h"
32 #include "DocumentFragment.h"
33 #include "DocumentType.h"
34 #include "Editor.h"
35 #include "HTMLElement.h"
36 #include "HTMLNames.h"
37 #include "HTMLTemplateElement.h"
38 #include <wtf/URL.h>
39 #include "ProcessingInstruction.h"
40 #include "XLinkNames.h"
41 #include "XMLNSNames.h"
42 #include "XMLNames.h"
43 #include <wtf/NeverDestroyed.h>
44 #include <wtf/unicode/CharacterNames.h>
45
46 namespace WebCore {
47
48 using namespace HTMLNames;
49
50 struct EntityDescription {
51     const char* characters;
52     unsigned char length;
53     unsigned char mask;
54 };
55
56 static const EntityDescription entitySubstitutionList[] = {
57     { "", 0 , 0 },
58     { "&amp;", 5 , EntityAmp },
59     { "&lt;", 4, EntityLt },
60     { "&gt;", 4, EntityGt },
61     { "&quot;", 6, EntityQuot },
62     { "&nbsp;", 6, EntityNbsp },
63 };
64
65 enum EntitySubstitutionIndex {
66     EntitySubstitutionNullIndex = 0,
67     EntitySubstitutionAmpIndex = 1,
68     EntitySubstitutionLtIndex = 2,
69     EntitySubstitutionGtIndex = 3,
70     EntitySubstitutionQuotIndex = 4,
71     EntitySubstitutionNbspIndex = 5,
72 };
73
74 static const unsigned maximumEscapedentityCharacter = noBreakSpace;
75 static const uint8_t entityMap[maximumEscapedentityCharacter + 1] = {
76     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
77     EntitySubstitutionQuotIndex, // '"'.
78     0, 0, 0,
79     EntitySubstitutionAmpIndex, // '&'.
80     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
81     EntitySubstitutionLtIndex, // '<'.
82     0,
83     EntitySubstitutionGtIndex, // '>'.
84     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
85     EntitySubstitutionNbspIndex // noBreakSpace.
86 };
87
88 static bool elementCannotHaveEndTag(const Node& node)
89 {
90     if (!is<HTMLElement>(node))
91         return false;
92
93     // From https://html.spec.whatwg.org/#serialising-html-fragments:
94     // If current node is an area, base, basefont, bgsound, br, col, embed, frame, hr, img,
95     // input, keygen, link, meta, param, source, track or wbr element, then continue on to
96     // the next child node at this point.
97     static const AtomicStringImpl* const localNames[] = {
98         areaTag->localName().impl(),
99         baseTag->localName().impl(),
100         basefontTag->localName().impl(),
101         bgsoundTag->localName().impl(),
102         brTag->localName().impl(),
103         colTag->localName().impl(),
104         embedTag->localName().impl(),
105         frameTag->localName().impl(),
106         hrTag->localName().impl(),
107         imgTag->localName().impl(),
108         inputTag->localName().impl(),
109         keygenTag->localName().impl(),
110         linkTag->localName().impl(),
111         metaTag->localName().impl(),
112         paramTag->localName().impl(),
113         sourceTag->localName().impl(),
114         trackTag->localName().impl(),
115         wbrTag->localName().impl()
116     };
117
118     auto* const elementName = downcast<HTMLElement>(node).localName().impl();
119     for (auto* name : localNames) {
120         if (name == elementName)
121             return true;
122     }
123
124     return false;
125 }
126
127 // Rules of self-closure
128 // 1. No elements in HTML documents use the self-closing syntax.
129 // 2. Elements w/ children never self-close because they use a separate end tag.
130 // 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag.
131 // 4. Other elements self-close.
132 static bool shouldSelfClose(const Element& element, SerializationSyntax syntax)
133 {
134     if (syntax != SerializationSyntax::XML && element.document().isHTMLDocument())
135         return false;
136     if (element.hasChildNodes())
137         return false;
138     if (element.isHTMLElement() && !elementCannotHaveEndTag(element))
139         return false;
140     return true;
141 }
142
143 template<typename CharacterType>
144 static inline void appendCharactersReplacingEntitiesInternal(StringBuilder& result, const String& source, unsigned offset, unsigned length, EntityMask entityMask)
145 {
146     const CharacterType* text = source.characters<CharacterType>() + offset;
147
148     size_t positionAfterLastEntity = 0;
149     for (size_t i = 0; i < length; ++i) {
150         CharacterType character = text[i];
151         uint8_t substitution = character < WTF_ARRAY_LENGTH(entityMap) ? entityMap[character] : static_cast<uint8_t>(EntitySubstitutionNullIndex);
152         if (UNLIKELY(substitution != EntitySubstitutionNullIndex) && entitySubstitutionList[substitution].mask & entityMask) {
153             result.append(text + positionAfterLastEntity, i - positionAfterLastEntity);
154             result.append(entitySubstitutionList[substitution].characters, entitySubstitutionList[substitution].length);
155             positionAfterLastEntity = i + 1;
156         }
157     }
158     result.append(text + positionAfterLastEntity, length - positionAfterLastEntity);
159 }
160
161 void MarkupAccumulator::appendCharactersReplacingEntities(StringBuilder& result, const String& source, unsigned offset, unsigned length, EntityMask entityMask)
162 {
163     if (!(offset + length))
164         return;
165
166     ASSERT(offset + length <= source.length());
167
168     if (source.is8Bit())
169         appendCharactersReplacingEntitiesInternal<LChar>(result, source, offset, length, entityMask);
170     else
171         appendCharactersReplacingEntitiesInternal<UChar>(result, source, offset, length, entityMask);
172 }
173
174 MarkupAccumulator::MarkupAccumulator(Vector<Node*>* nodes, ResolveURLs resolveURLs, SerializationSyntax serializationSyntax)
175     : m_nodes(nodes)
176     , m_resolveURLs(resolveURLs)
177     , m_serializationSyntax(serializationSyntax)
178 {
179 }
180
181 MarkupAccumulator::~MarkupAccumulator() = default;
182
183 String MarkupAccumulator::serializeNodes(Node& targetNode, SerializedNodes root, Vector<QualifiedName>* tagNamesToSkip)
184 {
185     serializeNodesWithNamespaces(targetNode, root, 0, tagNamesToSkip);
186     return m_markup.toString();
187 }
188
189 void MarkupAccumulator::serializeNodesWithNamespaces(Node& targetNode, SerializedNodes root, const Namespaces* namespaces, Vector<QualifiedName>* tagNamesToSkip)
190 {
191     if (tagNamesToSkip && is<Element>(targetNode)) {
192         for (auto& name : *tagNamesToSkip) {
193             if (downcast<Element>(targetNode).hasTagName(name))
194                 return;
195         }
196     }
197
198     Namespaces namespaceHash;
199     if (namespaces)
200         namespaceHash = *namespaces;
201     else if (inXMLFragmentSerialization()) {
202         // Make sure xml prefix and namespace are always known to uphold the constraints listed at http://www.w3.org/TR/xml-names11/#xmlReserved.
203         namespaceHash.set(xmlAtom().impl(), XMLNames::xmlNamespaceURI->impl());
204         namespaceHash.set(XMLNames::xmlNamespaceURI->impl(), xmlAtom().impl());
205     }
206
207     if (root == SerializedNodes::SubtreeIncludingNode)
208         startAppendingNode(targetNode, &namespaceHash);
209
210     if (targetNode.document().isHTMLDocument() && elementCannotHaveEndTag(targetNode))
211         return;
212
213     Node* current = targetNode.hasTagName(templateTag) ? downcast<HTMLTemplateElement>(targetNode).content().firstChild() : targetNode.firstChild();
214     for ( ; current; current = current->nextSibling())
215         serializeNodesWithNamespaces(*current, SerializedNodes::SubtreeIncludingNode, &namespaceHash, tagNamesToSkip);
216
217     if (root == SerializedNodes::SubtreeIncludingNode)
218         endAppendingNode(targetNode);
219 }
220
221 String MarkupAccumulator::resolveURLIfNeeded(const Element& element, const String& urlString) const
222 {
223     switch (m_resolveURLs) {
224     case ResolveURLs::Yes:
225         return element.document().completeURL(urlString).string();
226
227     case ResolveURLs::YesExcludingLocalFileURLsForPrivacy:
228         if (!element.document().url().isLocalFile())
229             return element.document().completeURL(urlString).string();
230         break;
231
232     case ResolveURLs::No:
233         break;
234     }
235     return urlString;
236 }
237
238 void MarkupAccumulator::appendString(const String& string)
239 {
240     m_markup.append(string);
241 }
242
243 void MarkupAccumulator::appendStringView(StringView view)
244 {
245     m_markup.append(view);
246 }
247
248 void MarkupAccumulator::startAppendingNode(const Node& node, Namespaces* namespaces)
249 {
250     if (is<Element>(node))
251         appendStartTag(m_markup, downcast<Element>(node), namespaces);
252     else
253         appendNonElementNode(m_markup, node, namespaces);
254
255     if (m_nodes)
256         m_nodes->append(const_cast<Node*>(&node));
257 }
258
259 void MarkupAccumulator::appendEndTag(StringBuilder& result, const Element& element)
260 {
261     if (shouldSelfClose(element, m_serializationSyntax) || (!element.hasChildNodes() && elementCannotHaveEndTag(element)))
262         return;
263     result.append('<');
264     result.append('/');
265     result.append(element.nodeNamePreservingCase());
266     result.append('>');
267 }
268
269 size_t MarkupAccumulator::totalLength(const Vector<String>& strings)
270 {
271     size_t length = 0;
272     for (auto& string : strings)
273         length += string.length();
274     return length;
275 }
276
277 void MarkupAccumulator::concatenateMarkup(StringBuilder& result)
278 {
279     result.append(m_markup);
280 }
281
282 void MarkupAccumulator::appendAttributeValue(StringBuilder& result, const String& attribute, bool isSerializingHTML)
283 {
284     appendCharactersReplacingEntities(result, attribute, 0, attribute.length(),
285         isSerializingHTML ? EntityMaskInHTMLAttributeValue : EntityMaskInAttributeValue);
286 }
287
288 void MarkupAccumulator::appendCustomAttributes(StringBuilder&, const Element&, Namespaces*)
289 {
290 }
291
292 void MarkupAccumulator::appendQuotedURLAttributeValue(StringBuilder& result, const Element& element, const Attribute& attribute)
293 {
294     ASSERT(element.isURLAttribute(attribute));
295     const String resolvedURLString = resolveURLIfNeeded(element, attribute.value());
296     UChar quoteChar = '"';
297     String strippedURLString = resolvedURLString.stripWhiteSpace();
298     if (WTF::protocolIsJavaScript(strippedURLString)) {
299         // minimal escaping for javascript urls
300         if (strippedURLString.contains('"')) {
301             if (strippedURLString.contains('\''))
302                 strippedURLString.replaceWithLiteral('"', "&quot;");
303             else
304                 quoteChar = '\'';
305         }
306         result.append(quoteChar);
307         result.append(strippedURLString);
308         result.append(quoteChar);
309         return;
310     }
311
312     // FIXME: This does not fully match other browsers. Firefox percent-escapes non-ASCII characters for innerHTML.
313     result.append(quoteChar);
314     appendAttributeValue(result, resolvedURLString, false);
315     result.append(quoteChar);
316 }
317
318 bool MarkupAccumulator::shouldAddNamespaceElement(const Element& element)
319 {
320     // Don't add namespace attribute if it is already defined for this elem.
321     const AtomicString& prefix = element.prefix();
322     if (prefix.isEmpty())
323         return !element.hasAttribute(xmlnsAtom());
324
325     static NeverDestroyed<String> xmlnsWithColon(MAKE_STATIC_STRING_IMPL("xmlns:"));
326     return !element.hasAttribute(xmlnsWithColon.get() + prefix);
327 }
328
329 bool MarkupAccumulator::shouldAddNamespaceAttribute(const Attribute& attribute, Namespaces& namespaces)
330 {
331     namespaces.checkConsistency();
332
333     // Don't add namespace attributes twice
334     // HTML Parser will create xmlns attributes without namespace for HTML elements, allow those as well.
335     if (attribute.name().localName() == xmlnsAtom() && (attribute.namespaceURI().isEmpty() || attribute.namespaceURI() == XMLNSNames::xmlnsNamespaceURI)) {
336         namespaces.set(emptyAtom().impl(), attribute.value().impl());
337         return false;
338     }
339
340     QualifiedName xmlnsPrefixAttr(xmlnsAtom(), attribute.localName(), XMLNSNames::xmlnsNamespaceURI);
341     if (attribute.name() == xmlnsPrefixAttr) {
342         namespaces.set(attribute.localName().impl(), attribute.value().impl());
343         namespaces.set(attribute.value().impl(), attribute.localName().impl());
344         return false;
345     }
346
347     return true;
348 }
349
350 void MarkupAccumulator::appendNamespace(StringBuilder& result, const AtomicString& prefix, const AtomicString& namespaceURI, Namespaces& namespaces, bool allowEmptyDefaultNS)
351 {
352     namespaces.checkConsistency();
353     if (namespaceURI.isEmpty()) {
354         // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-xhtml-syntax.html#xml-fragment-serialization-algorithm
355         if (allowEmptyDefaultNS && namespaces.get(emptyAtom().impl())) {
356             result.append(' ');
357             result.append(xmlnsAtom().string());
358             result.appendLiteral("=\"\"");
359         }
360         return;
361     }
362
363     // Use emptyAtom()s's impl() for both null and empty strings since the HashMap can't handle 0 as a key
364     AtomicStringImpl* pre = prefix.isEmpty() ? emptyAtom().impl() : prefix.impl();
365     AtomicStringImpl* foundNS = namespaces.get(pre);
366     if (foundNS != namespaceURI.impl()) {
367         namespaces.set(pre, namespaceURI.impl());
368         // Add namespace to prefix pair so we can do constraint checking later.
369         if (inXMLFragmentSerialization() && !prefix.isEmpty())
370             namespaces.set(namespaceURI.impl(), pre);
371         // Make sure xml prefix and namespace are always known to uphold the constraints listed at http://www.w3.org/TR/xml-names11/#xmlReserved.
372         if (namespaceURI.impl() == XMLNames::xmlNamespaceURI->impl())
373             return;
374         result.append(' ');
375         result.append(xmlnsAtom().string());
376         if (!prefix.isEmpty()) {
377             result.append(':');
378             result.append(prefix);
379         }
380
381         result.append('=');
382         result.append('"');
383         appendAttributeValue(result, namespaceURI, false);
384         result.append('"');
385     }
386 }
387
388 EntityMask MarkupAccumulator::entityMaskForText(const Text& text) const
389 {
390     if (!text.document().isHTMLDocument())
391         return EntityMaskInPCDATA;
392
393     const QualifiedName* parentName = nullptr;
394     if (text.parentElement())
395         parentName = &text.parentElement()->tagQName();
396
397     if (parentName && (*parentName == scriptTag || *parentName == styleTag || *parentName == xmpTag))
398         return EntityMaskInCDATA;
399     return EntityMaskInHTMLPCDATA;
400 }
401
402 void MarkupAccumulator::appendText(StringBuilder& result, const Text& text)
403 {
404     const String& textData = text.data();
405     appendCharactersReplacingEntities(result, textData, 0, textData.length(), entityMaskForText(text));
406 }
407
408 static void appendComment(StringBuilder& result, const String& comment)
409 {
410     // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->".
411     result.appendLiteral("<!--");
412     result.append(comment);
413     result.appendLiteral("-->");
414 }
415
416 void MarkupAccumulator::appendXMLDeclaration(StringBuilder& result, const Document& document)
417 {
418     if (!document.hasXMLDeclaration())
419         return;
420
421     result.appendLiteral("<?xml version=\"");
422     result.append(document.xmlVersion());
423     const String& encoding = document.xmlEncoding();
424     if (!encoding.isEmpty()) {
425         result.appendLiteral("\" encoding=\"");
426         result.append(encoding);
427     }
428     if (document.xmlStandaloneStatus() != Document::StandaloneStatus::Unspecified) {
429         result.appendLiteral("\" standalone=\"");
430         if (document.xmlStandalone())
431             result.appendLiteral("yes");
432         else
433             result.appendLiteral("no");
434     }
435
436     result.appendLiteral("\"?>");
437 }
438
439 void MarkupAccumulator::appendDocumentType(StringBuilder& result, const DocumentType& documentType)
440 {
441     if (documentType.name().isEmpty())
442         return;
443
444     result.appendLiteral("<!DOCTYPE ");
445     result.append(documentType.name());
446     if (!documentType.publicId().isEmpty()) {
447         result.appendLiteral(" PUBLIC \"");
448         result.append(documentType.publicId());
449         result.append('"');
450         if (!documentType.systemId().isEmpty()) {
451             result.append(' ');
452             result.append('"');
453             result.append(documentType.systemId());
454             result.append('"');
455         }
456     } else if (!documentType.systemId().isEmpty()) {
457         result.appendLiteral(" SYSTEM \"");
458         result.append(documentType.systemId());
459         result.append('"');
460     }
461     result.append('>');
462 }
463
464 void MarkupAccumulator::appendProcessingInstruction(StringBuilder& result, const String& target, const String& data)
465 {
466     // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>".
467     result.append('<');
468     result.append('?');
469     result.append(target);
470     result.append(' ');
471     result.append(data);
472     result.append('?');
473     result.append('>');
474 }
475
476 void MarkupAccumulator::appendStartTag(StringBuilder& result, const Element& element, Namespaces* namespaces)
477 {
478     appendOpenTag(result, element, namespaces);
479
480     if (element.hasAttributes()) {
481         for (const Attribute& attribute : element.attributesIterator())
482             appendAttribute(result, element, attribute, namespaces);
483     }
484
485     // Give an opportunity to subclasses to add their own attributes.
486     appendCustomAttributes(result, element, namespaces);
487
488     appendCloseTag(result, element);
489 }
490
491 void MarkupAccumulator::appendOpenTag(StringBuilder& result, const Element& element, Namespaces* namespaces)
492 {
493     result.append('<');
494     if (inXMLFragmentSerialization() && namespaces && element.prefix().isEmpty()) {
495         // According to http://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#normalizeDocumentAlgo we now should create
496         // a default namespace declaration to make this namespace well-formed. However, http://www.w3.org/TR/xml-names11/#xmlReserved states
497         // "The prefix xml MUST NOT be declared as the default namespace.", so use the xml prefix explicitly.
498         if (element.namespaceURI() == XMLNames::xmlNamespaceURI) {
499             result.append(xmlAtom());
500             result.append(':');
501         }
502     }
503     result.append(element.nodeNamePreservingCase());
504     if ((inXMLFragmentSerialization() || !element.document().isHTMLDocument()) && namespaces && shouldAddNamespaceElement(element))
505         appendNamespace(result, element.prefix(), element.namespaceURI(), *namespaces, inXMLFragmentSerialization());
506 }
507
508 void MarkupAccumulator::appendCloseTag(StringBuilder& result, const Element& element)
509 {
510     if (shouldSelfClose(element, m_serializationSyntax)) {
511         if (element.isHTMLElement())
512             result.append(' '); // XHTML 1.0 <-> HTML compatibility.
513         result.append('/');
514     }
515     result.append('>');
516 }
517
518 static inline bool attributeIsInSerializedNamespace(const Attribute& attribute)
519 {
520     return attribute.namespaceURI() == XMLNames::xmlNamespaceURI
521         || attribute.namespaceURI() == XLinkNames::xlinkNamespaceURI
522         || attribute.namespaceURI() == XMLNSNames::xmlnsNamespaceURI;
523 }
524
525 void MarkupAccumulator::generateUniquePrefix(QualifiedName& prefixedName, const Namespaces& namespaces)
526 {
527     // http://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#normalizeDocumentAlgo
528     // Find a prefix following the pattern "NS" + index (starting at 1) and make sure this
529     // prefix is not declared in the current scope.
530     StringBuilder builder;
531     do {
532         builder.clear();
533         builder.appendLiteral("NS");
534         builder.appendNumber(++m_prefixLevel);
535         const AtomicString& name = builder.toAtomicString();
536         if (!namespaces.get(name.impl())) {
537             prefixedName.setPrefix(name);
538             return;
539         }
540     } while (true);
541 }
542
543 void MarkupAccumulator::appendAttribute(StringBuilder& result, const Element& element, const Attribute& attribute, Namespaces* namespaces)
544 {
545     bool isSerializingHTML = element.document().isHTMLDocument() && !inXMLFragmentSerialization();
546
547     result.append(' ');
548
549     QualifiedName prefixedName = attribute.name();
550     if (isSerializingHTML && !attributeIsInSerializedNamespace(attribute))
551         result.append(attribute.name().localName());
552     else {
553         if (!attribute.namespaceURI().isEmpty()) {
554             if (attribute.namespaceURI() == XMLNames::xmlNamespaceURI) {
555                 // Always use xml as prefix if the namespace is the XML namespace.
556                 prefixedName.setPrefix(xmlAtom());
557             } else {
558                 AtomicStringImpl* foundNS = namespaces && attribute.prefix().impl() ? namespaces->get(attribute.prefix().impl()) : 0;
559                 bool prefixIsAlreadyMappedToOtherNS = foundNS && foundNS != attribute.namespaceURI().impl();
560                 if (attribute.prefix().isEmpty() || !foundNS || prefixIsAlreadyMappedToOtherNS) {
561                     if (AtomicStringImpl* prefix = namespaces ? namespaces->get(attribute.namespaceURI().impl()) : 0)
562                         prefixedName.setPrefix(AtomicString(prefix));
563                     else {
564                         bool shouldBeDeclaredUsingAppendNamespace = !attribute.prefix().isEmpty() && !foundNS;
565                         if (!shouldBeDeclaredUsingAppendNamespace && attribute.localName() != xmlnsAtom() && namespaces)
566                             generateUniquePrefix(prefixedName, *namespaces);
567                     }
568                 }
569             }
570         }
571         result.append(prefixedName.toString());
572     }
573
574     result.append('=');
575
576     if (element.isURLAttribute(attribute))
577         appendQuotedURLAttributeValue(result, element, attribute);
578     else {
579         result.append('"');
580         appendAttributeValue(result, attribute.value(), isSerializingHTML);
581         result.append('"');
582     }
583
584     if (!isSerializingHTML && namespaces && shouldAddNamespaceAttribute(attribute, *namespaces))
585         appendNamespace(result, prefixedName.prefix(), prefixedName.namespaceURI(), *namespaces);
586 }
587
588 void MarkupAccumulator::appendCDATASection(StringBuilder& result, const String& section)
589 {
590     // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>".
591     result.appendLiteral("<![CDATA[");
592     result.append(section);
593     result.appendLiteral("]]>");
594 }
595
596 void MarkupAccumulator::appendNonElementNode(StringBuilder& result, const Node& node, Namespaces* namespaces)
597 {
598     if (namespaces)
599         namespaces->checkConsistency();
600
601     switch (node.nodeType()) {
602     case Node::TEXT_NODE:
603         appendText(result, downcast<Text>(node));
604         break;
605     case Node::COMMENT_NODE:
606         appendComment(result, downcast<Comment>(node).data());
607         break;
608     case Node::DOCUMENT_NODE:
609         appendXMLDeclaration(result, downcast<Document>(node));
610         break;
611     case Node::DOCUMENT_FRAGMENT_NODE:
612         break;
613     case Node::DOCUMENT_TYPE_NODE:
614         appendDocumentType(result, downcast<DocumentType>(node));
615         break;
616     case Node::PROCESSING_INSTRUCTION_NODE:
617         appendProcessingInstruction(result, downcast<ProcessingInstruction>(node).target(), downcast<ProcessingInstruction>(node).data());
618         break;
619     case Node::ELEMENT_NODE:
620         ASSERT_NOT_REACHED();
621         break;
622     case Node::CDATA_SECTION_NODE:
623         appendCDATASection(result, downcast<CDATASection>(node).data());
624         break;
625     case Node::ATTRIBUTE_NODE:
626         ASSERT_NOT_REACHED();
627         break;
628     }
629 }
630
631 }