Rename AtomicString to AtomString
[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 AtomStringImpl* 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     String resolvedURLString = resolveURLIfNeeded(element, attribute.value());
296     UChar quoteChar = '"';
297     if (WTF::protocolIsJavaScript(resolvedURLString)) {
298         // minimal escaping for javascript urls
299         if (resolvedURLString.contains('"')) {
300             if (resolvedURLString.contains('\''))
301                 resolvedURLString.replaceWithLiteral('"', "&quot;");
302             else
303                 quoteChar = '\'';
304         }
305         result.append(quoteChar);
306         result.append(resolvedURLString);
307         result.append(quoteChar);
308         return;
309     }
310
311     // FIXME: This does not fully match other browsers. Firefox percent-escapes non-ASCII characters for innerHTML.
312     result.append(quoteChar);
313     appendAttributeValue(result, resolvedURLString, false);
314     result.append(quoteChar);
315 }
316
317 bool MarkupAccumulator::shouldAddNamespaceElement(const Element& element)
318 {
319     // Don't add namespace attribute if it is already defined for this elem.
320     const AtomString& prefix = element.prefix();
321     if (prefix.isEmpty())
322         return !element.hasAttribute(xmlnsAtom());
323
324     static NeverDestroyed<String> xmlnsWithColon(MAKE_STATIC_STRING_IMPL("xmlns:"));
325     return !element.hasAttribute(xmlnsWithColon.get() + prefix);
326 }
327
328 bool MarkupAccumulator::shouldAddNamespaceAttribute(const Attribute& attribute, Namespaces& namespaces)
329 {
330     namespaces.checkConsistency();
331
332     // Don't add namespace attributes twice
333     // HTML Parser will create xmlns attributes without namespace for HTML elements, allow those as well.
334     if (attribute.name().localName() == xmlnsAtom() && (attribute.namespaceURI().isEmpty() || attribute.namespaceURI() == XMLNSNames::xmlnsNamespaceURI)) {
335         namespaces.set(emptyAtom().impl(), attribute.value().impl());
336         return false;
337     }
338
339     QualifiedName xmlnsPrefixAttr(xmlnsAtom(), attribute.localName(), XMLNSNames::xmlnsNamespaceURI);
340     if (attribute.name() == xmlnsPrefixAttr) {
341         namespaces.set(attribute.localName().impl(), attribute.value().impl());
342         namespaces.set(attribute.value().impl(), attribute.localName().impl());
343         return false;
344     }
345
346     return true;
347 }
348
349 void MarkupAccumulator::appendNamespace(StringBuilder& result, const AtomString& prefix, const AtomString& namespaceURI, Namespaces& namespaces, bool allowEmptyDefaultNS)
350 {
351     namespaces.checkConsistency();
352     if (namespaceURI.isEmpty()) {
353         // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-xhtml-syntax.html#xml-fragment-serialization-algorithm
354         if (allowEmptyDefaultNS && namespaces.get(emptyAtom().impl())) {
355             result.append(' ');
356             result.append(xmlnsAtom().string());
357             result.appendLiteral("=\"\"");
358         }
359         return;
360     }
361
362     // Use emptyAtom()s's impl() for both null and empty strings since the HashMap can't handle 0 as a key
363     AtomStringImpl* pre = prefix.isEmpty() ? emptyAtom().impl() : prefix.impl();
364     AtomStringImpl* foundNS = namespaces.get(pre);
365     if (foundNS != namespaceURI.impl()) {
366         namespaces.set(pre, namespaceURI.impl());
367         // Add namespace to prefix pair so we can do constraint checking later.
368         if (inXMLFragmentSerialization() && !prefix.isEmpty())
369             namespaces.set(namespaceURI.impl(), pre);
370         // Make sure xml prefix and namespace are always known to uphold the constraints listed at http://www.w3.org/TR/xml-names11/#xmlReserved.
371         if (namespaceURI.impl() == XMLNames::xmlNamespaceURI->impl())
372             return;
373         result.append(' ');
374         result.append(xmlnsAtom().string());
375         if (!prefix.isEmpty()) {
376             result.append(':');
377             result.append(prefix);
378         }
379
380         result.append('=');
381         result.append('"');
382         appendAttributeValue(result, namespaceURI, false);
383         result.append('"');
384     }
385 }
386
387 EntityMask MarkupAccumulator::entityMaskForText(const Text& text) const
388 {
389     if (!text.document().isHTMLDocument())
390         return EntityMaskInPCDATA;
391
392     const QualifiedName* parentName = nullptr;
393     if (text.parentElement())
394         parentName = &text.parentElement()->tagQName();
395
396     if (parentName && (*parentName == scriptTag || *parentName == styleTag || *parentName == xmpTag))
397         return EntityMaskInCDATA;
398     return EntityMaskInHTMLPCDATA;
399 }
400
401 void MarkupAccumulator::appendText(StringBuilder& result, const Text& text)
402 {
403     const String& textData = text.data();
404     appendCharactersReplacingEntities(result, textData, 0, textData.length(), entityMaskForText(text));
405 }
406
407 static void appendComment(StringBuilder& result, const String& comment)
408 {
409     // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->".
410     result.appendLiteral("<!--");
411     result.append(comment);
412     result.appendLiteral("-->");
413 }
414
415 void MarkupAccumulator::appendXMLDeclaration(StringBuilder& result, const Document& document)
416 {
417     if (!document.hasXMLDeclaration())
418         return;
419
420     result.appendLiteral("<?xml version=\"");
421     result.append(document.xmlVersion());
422     const String& encoding = document.xmlEncoding();
423     if (!encoding.isEmpty()) {
424         result.appendLiteral("\" encoding=\"");
425         result.append(encoding);
426     }
427     if (document.xmlStandaloneStatus() != Document::StandaloneStatus::Unspecified) {
428         result.appendLiteral("\" standalone=\"");
429         if (document.xmlStandalone())
430             result.appendLiteral("yes");
431         else
432             result.appendLiteral("no");
433     }
434
435     result.appendLiteral("\"?>");
436 }
437
438 void MarkupAccumulator::appendDocumentType(StringBuilder& result, const DocumentType& documentType)
439 {
440     if (documentType.name().isEmpty())
441         return;
442
443     result.appendLiteral("<!DOCTYPE ");
444     result.append(documentType.name());
445     if (!documentType.publicId().isEmpty()) {
446         result.appendLiteral(" PUBLIC \"");
447         result.append(documentType.publicId());
448         result.append('"');
449         if (!documentType.systemId().isEmpty()) {
450             result.append(' ');
451             result.append('"');
452             result.append(documentType.systemId());
453             result.append('"');
454         }
455     } else if (!documentType.systemId().isEmpty()) {
456         result.appendLiteral(" SYSTEM \"");
457         result.append(documentType.systemId());
458         result.append('"');
459     }
460     result.append('>');
461 }
462
463 void MarkupAccumulator::appendProcessingInstruction(StringBuilder& result, const String& target, const String& data)
464 {
465     // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>".
466     result.append('<');
467     result.append('?');
468     result.append(target);
469     result.append(' ');
470     result.append(data);
471     result.append('?');
472     result.append('>');
473 }
474
475 void MarkupAccumulator::appendStartTag(StringBuilder& result, const Element& element, Namespaces* namespaces)
476 {
477     appendOpenTag(result, element, namespaces);
478
479     if (element.hasAttributes()) {
480         for (const Attribute& attribute : element.attributesIterator())
481             appendAttribute(result, element, attribute, namespaces);
482     }
483
484     // Give an opportunity to subclasses to add their own attributes.
485     appendCustomAttributes(result, element, namespaces);
486
487     appendCloseTag(result, element);
488 }
489
490 void MarkupAccumulator::appendOpenTag(StringBuilder& result, const Element& element, Namespaces* namespaces)
491 {
492     result.append('<');
493     if (inXMLFragmentSerialization() && namespaces && element.prefix().isEmpty()) {
494         // According to http://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#normalizeDocumentAlgo we now should create
495         // a default namespace declaration to make this namespace well-formed. However, http://www.w3.org/TR/xml-names11/#xmlReserved states
496         // "The prefix xml MUST NOT be declared as the default namespace.", so use the xml prefix explicitly.
497         if (element.namespaceURI() == XMLNames::xmlNamespaceURI) {
498             result.append(xmlAtom());
499             result.append(':');
500         }
501     }
502     result.append(element.nodeNamePreservingCase());
503     if ((inXMLFragmentSerialization() || !element.document().isHTMLDocument()) && namespaces && shouldAddNamespaceElement(element))
504         appendNamespace(result, element.prefix(), element.namespaceURI(), *namespaces, inXMLFragmentSerialization());
505 }
506
507 void MarkupAccumulator::appendCloseTag(StringBuilder& result, const Element& element)
508 {
509     if (shouldSelfClose(element, m_serializationSyntax)) {
510         if (element.isHTMLElement())
511             result.append(' '); // XHTML 1.0 <-> HTML compatibility.
512         result.append('/');
513     }
514     result.append('>');
515 }
516
517 static inline bool attributeIsInSerializedNamespace(const Attribute& attribute)
518 {
519     return attribute.namespaceURI() == XMLNames::xmlNamespaceURI
520         || attribute.namespaceURI() == XLinkNames::xlinkNamespaceURI
521         || attribute.namespaceURI() == XMLNSNames::xmlnsNamespaceURI;
522 }
523
524 void MarkupAccumulator::generateUniquePrefix(QualifiedName& prefixedName, const Namespaces& namespaces)
525 {
526     // http://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#normalizeDocumentAlgo
527     // Find a prefix following the pattern "NS" + index (starting at 1) and make sure this
528     // prefix is not declared in the current scope.
529     StringBuilder builder;
530     do {
531         builder.clear();
532         builder.appendLiteral("NS");
533         builder.appendNumber(++m_prefixLevel);
534         const AtomString& name = builder.toAtomString();
535         if (!namespaces.get(name.impl())) {
536             prefixedName.setPrefix(name);
537             return;
538         }
539     } while (true);
540 }
541
542 void MarkupAccumulator::appendAttribute(StringBuilder& result, const Element& element, const Attribute& attribute, Namespaces* namespaces)
543 {
544     bool isSerializingHTML = element.document().isHTMLDocument() && !inXMLFragmentSerialization();
545
546     result.append(' ');
547
548     QualifiedName prefixedName = attribute.name();
549     if (isSerializingHTML && !attributeIsInSerializedNamespace(attribute))
550         result.append(attribute.name().localName());
551     else {
552         if (!attribute.namespaceURI().isEmpty()) {
553             if (attribute.namespaceURI() == XMLNames::xmlNamespaceURI) {
554                 // Always use xml as prefix if the namespace is the XML namespace.
555                 prefixedName.setPrefix(xmlAtom());
556             } else {
557                 AtomStringImpl* foundNS = namespaces && attribute.prefix().impl() ? namespaces->get(attribute.prefix().impl()) : 0;
558                 bool prefixIsAlreadyMappedToOtherNS = foundNS && foundNS != attribute.namespaceURI().impl();
559                 if (attribute.prefix().isEmpty() || !foundNS || prefixIsAlreadyMappedToOtherNS) {
560                     if (AtomStringImpl* prefix = namespaces ? namespaces->get(attribute.namespaceURI().impl()) : 0)
561                         prefixedName.setPrefix(AtomString(prefix));
562                     else {
563                         bool shouldBeDeclaredUsingAppendNamespace = !attribute.prefix().isEmpty() && !foundNS;
564                         if (!shouldBeDeclaredUsingAppendNamespace && attribute.localName() != xmlnsAtom() && namespaces)
565                             generateUniquePrefix(prefixedName, *namespaces);
566                     }
567                 }
568             }
569         }
570         result.append(prefixedName.toString());
571     }
572
573     result.append('=');
574
575     if (element.isURLAttribute(attribute))
576         appendQuotedURLAttributeValue(result, element, attribute);
577     else {
578         result.append('"');
579         appendAttributeValue(result, attribute.value(), isSerializingHTML);
580         result.append('"');
581     }
582
583     if (!isSerializingHTML && namespaces && shouldAddNamespaceAttribute(attribute, *namespaces))
584         appendNamespace(result, prefixedName.prefix(), prefixedName.namespaceURI(), *namespaces);
585 }
586
587 void MarkupAccumulator::appendCDATASection(StringBuilder& result, const String& section)
588 {
589     // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>".
590     result.appendLiteral("<![CDATA[");
591     result.append(section);
592     result.appendLiteral("]]>");
593 }
594
595 void MarkupAccumulator::appendNonElementNode(StringBuilder& result, const Node& node, Namespaces* namespaces)
596 {
597     if (namespaces)
598         namespaces->checkConsistency();
599
600     switch (node.nodeType()) {
601     case Node::TEXT_NODE:
602         appendText(result, downcast<Text>(node));
603         break;
604     case Node::COMMENT_NODE:
605         appendComment(result, downcast<Comment>(node).data());
606         break;
607     case Node::DOCUMENT_NODE:
608         appendXMLDeclaration(result, downcast<Document>(node));
609         break;
610     case Node::DOCUMENT_FRAGMENT_NODE:
611         break;
612     case Node::DOCUMENT_TYPE_NODE:
613         appendDocumentType(result, downcast<DocumentType>(node));
614         break;
615     case Node::PROCESSING_INSTRUCTION_NODE:
616         appendProcessingInstruction(result, downcast<ProcessingInstruction>(node).target(), downcast<ProcessingInstruction>(node).data());
617         break;
618     case Node::ELEMENT_NODE:
619         ASSERT_NOT_REACHED();
620         break;
621     case Node::CDATA_SECTION_NODE:
622         appendCDATASection(result, downcast<CDATASection>(node).data());
623         break;
624     case Node::ATTRIBUTE_NODE:
625         ASSERT_NOT_REACHED();
626         break;
627     }
628 }
629
630 }