f9682a4455ff487dc600a924c8d5a0a6ed90c43e
[WebKit-https.git] / Source / WebCore / editing / MarkupAccumulator.cpp
1 /*
2  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2012 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 "URL.h"
39 #include "ProcessingInstruction.h"
40 #include "XLinkNames.h"
41 #include "XMLNSNames.h"
42 #include "XMLNames.h"
43 #include <wtf/unicode/CharacterNames.h>
44
45 namespace WebCore {
46
47 using namespace HTMLNames;
48
49 struct EntityDescription {
50     const char* characters;
51     unsigned char length;
52     unsigned char mask;
53 };
54
55 static const EntityDescription entitySubstitutionList[] = {
56     { "", 0 , 0 },
57     { "&amp;", 5 , EntityAmp },
58     { "&lt;", 4, EntityLt },
59     { "&gt;", 4, EntityGt },
60     { "&quot;", 6, EntityQuot },
61     { "&nbsp;", 6, EntityNbsp },
62 };
63
64 enum EntitySubstitutionIndex {
65     EntitySubstitutionNullIndex = 0,
66     EntitySubstitutionAmpIndex = 1,
67     EntitySubstitutionLtIndex = 2,
68     EntitySubstitutionGtIndex = 3,
69     EntitySubstitutionQuotIndex = 4,
70     EntitySubstitutionNbspIndex = 5,
71 };
72
73 static const unsigned maximumEscapedentityCharacter = noBreakSpace;
74 static const uint8_t entityMap[maximumEscapedentityCharacter + 1] = {
75     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,
76     EntitySubstitutionQuotIndex, // '"'.
77     0, 0, 0,
78     EntitySubstitutionAmpIndex, // '&'.
79     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
80     EntitySubstitutionLtIndex, // '<'.
81     0,
82     EntitySubstitutionGtIndex, // '>'.
83     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,
84     EntitySubstitutionNbspIndex // noBreakSpace.
85 };
86
87 template<typename CharacterType>
88 static inline void appendCharactersReplacingEntitiesInternal(StringBuilder& result, const String& source, unsigned offset, unsigned length, EntityMask entityMask)
89 {
90     const CharacterType* text = source.characters<CharacterType>() + offset;
91
92     size_t positionAfterLastEntity = 0;
93     for (size_t i = 0; i < length; ++i) {
94         CharacterType character = text[i];
95         uint8_t substitution = character < WTF_ARRAY_LENGTH(entityMap) ? entityMap[character] : static_cast<uint8_t>(EntitySubstitutionNullIndex);
96         if (UNLIKELY(substitution != EntitySubstitutionNullIndex) && entitySubstitutionList[substitution].mask & entityMask) {
97             result.append(text + positionAfterLastEntity, i - positionAfterLastEntity);
98             result.append(entitySubstitutionList[substitution].characters, entitySubstitutionList[substitution].length);
99             positionAfterLastEntity = i + 1;
100         }
101     }
102     result.append(text + positionAfterLastEntity, length - positionAfterLastEntity);
103 }
104
105 void MarkupAccumulator::appendCharactersReplacingEntities(StringBuilder& result, const String& source, unsigned offset, unsigned length, EntityMask entityMask)
106 {
107     if (!(offset + length))
108         return;
109
110     ASSERT(offset + length <= source.length());
111
112     if (source.is8Bit())
113         appendCharactersReplacingEntitiesInternal<LChar>(result, source, offset, length, entityMask);
114     else
115         appendCharactersReplacingEntitiesInternal<UChar>(result, source, offset, length, entityMask);
116 }
117
118 MarkupAccumulator::MarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs resolveUrlsMethod, const Range* range, EFragmentSerialization fragmentSerialization)
119     : m_nodes(nodes)
120     , m_range(range)
121     , m_resolveURLsMethod(resolveUrlsMethod)
122     , m_fragmentSerialization(fragmentSerialization)
123     , m_prefixLevel(0)
124 {
125 }
126
127 MarkupAccumulator::~MarkupAccumulator()
128 {
129 }
130
131 String MarkupAccumulator::serializeNodes(Node& targetNode, EChildrenOnly childrenOnly, Vector<QualifiedName>* tagNamesToSkip)
132 {
133     serializeNodesWithNamespaces(targetNode, childrenOnly, 0, tagNamesToSkip);
134     return m_markup.toString();
135 }
136
137 void MarkupAccumulator::serializeNodesWithNamespaces(Node& targetNode, EChildrenOnly childrenOnly, const Namespaces* namespaces, Vector<QualifiedName>* tagNamesToSkip)
138 {
139     if (tagNamesToSkip && is<Element>(targetNode)) {
140         for (auto& name : *tagNamesToSkip) {
141             if (downcast<Element>(targetNode).hasTagName(name))
142                 return;
143         }
144     }
145
146     Namespaces namespaceHash;
147     if (namespaces)
148         namespaceHash = *namespaces;
149     else if (inXMLFragmentSerialization()) {
150         // Make sure xml prefix and namespace are always known to uphold the constraints listed at http://www.w3.org/TR/xml-names11/#xmlReserved.
151         namespaceHash.set(xmlAtom.impl(), XMLNames::xmlNamespaceURI.impl());
152         namespaceHash.set(XMLNames::xmlNamespaceURI.impl(), xmlAtom.impl());
153     }
154
155     if (!childrenOnly)
156         appendStartTag(targetNode, &namespaceHash);
157
158     if (!(targetNode.document().isHTMLDocument() && elementCannotHaveEndTag(targetNode))) {
159 #if ENABLE(TEMPLATE_ELEMENT)
160         Node* current = targetNode.hasTagName(templateTag) ? downcast<HTMLTemplateElement>(targetNode).content()->firstChild() : targetNode.firstChild();
161 #else
162         Node* current = targetNode.firstChild();
163 #endif
164         for ( ; current; current = current->nextSibling())
165             serializeNodesWithNamespaces(*current, IncludeNode, &namespaceHash, tagNamesToSkip);
166     }
167
168     if (!childrenOnly)
169         appendEndTag(targetNode);
170 }
171
172 String MarkupAccumulator::resolveURLIfNeeded(const Element& element, const String& urlString) const
173 {
174     switch (m_resolveURLsMethod) {
175     case ResolveAllURLs:
176         return element.document().completeURL(urlString).string();
177
178     case ResolveNonLocalURLs:
179         if (!element.document().url().isLocalFile())
180             return element.document().completeURL(urlString).string();
181         break;
182
183     case DoNotResolveURLs:
184         break;
185     }
186     return urlString;
187 }
188
189 void MarkupAccumulator::appendString(const String& string)
190 {
191     m_markup.append(string);
192 }
193
194 void MarkupAccumulator::appendStartTag(const Node& node, Namespaces* namespaces)
195 {
196     appendStartMarkup(m_markup, node, namespaces);
197     if (m_nodes)
198         m_nodes->append(const_cast<Node*>(&node));
199 }
200
201 void MarkupAccumulator::appendEndTag(const Element& element)
202 {
203     appendEndMarkup(m_markup, element);
204 }
205
206 size_t MarkupAccumulator::totalLength(const Vector<String>& strings)
207 {
208     size_t length = 0;
209     for (auto& string : strings)
210         length += string.length();
211     return length;
212 }
213
214 void MarkupAccumulator::concatenateMarkup(StringBuilder& result)
215 {
216     result.append(m_markup);
217 }
218
219 void MarkupAccumulator::appendAttributeValue(StringBuilder& result, const String& attribute, bool documentIsHTML)
220 {
221     appendCharactersReplacingEntities(result, attribute, 0, attribute.length(),
222         documentIsHTML ? EntityMaskInHTMLAttributeValue : EntityMaskInAttributeValue);
223 }
224
225 void MarkupAccumulator::appendCustomAttributes(StringBuilder&, const Element&, Namespaces*)
226 {
227 }
228
229 void MarkupAccumulator::appendQuotedURLAttributeValue(StringBuilder& result, const Element& element, const Attribute& attribute)
230 {
231     ASSERT(element.isURLAttribute(attribute));
232     const String resolvedURLString = resolveURLIfNeeded(element, attribute.value());
233     UChar quoteChar = '"';
234     String strippedURLString = resolvedURLString.stripWhiteSpace();
235     if (protocolIsJavaScript(strippedURLString)) {
236         // minimal escaping for javascript urls
237         if (strippedURLString.contains('"')) {
238             if (strippedURLString.contains('\''))
239                 strippedURLString.replaceWithLiteral('"', "&quot;");
240             else
241                 quoteChar = '\'';
242         }
243         result.append(quoteChar);
244         result.append(strippedURLString);
245         result.append(quoteChar);
246         return;
247     }
248
249     // FIXME: This does not fully match other browsers. Firefox percent-escapes non-ASCII characters for innerHTML.
250     result.append(quoteChar);
251     appendAttributeValue(result, resolvedURLString, false);
252     result.append(quoteChar);
253 }
254
255 bool MarkupAccumulator::shouldAddNamespaceElement(const Element& element)
256 {
257     // Don't add namespace attribute if it is already defined for this elem.
258     const AtomicString& prefix = element.prefix();
259     if (prefix.isEmpty())
260         return !element.hasAttribute(xmlnsAtom);
261
262     DEPRECATED_DEFINE_STATIC_LOCAL(String, xmlnsWithColon, (ASCIILiteral("xmlns:")));
263     return !element.hasAttribute(xmlnsWithColon + prefix);
264 }
265
266 bool MarkupAccumulator::shouldAddNamespaceAttribute(const Attribute& attribute, Namespaces& namespaces)
267 {
268     namespaces.checkConsistency();
269
270     // Don't add namespace attributes twice
271     // HTML Parser will create xmlns attributes without namespace for HTML elements, allow those as well.
272     if (attribute.name().localName() == xmlnsAtom && (attribute.namespaceURI().isEmpty() || attribute.namespaceURI() == XMLNSNames::xmlnsNamespaceURI)) {
273         namespaces.set(emptyAtom.impl(), attribute.value().impl());
274         return false;
275     }
276
277     QualifiedName xmlnsPrefixAttr(xmlnsAtom, attribute.localName(), XMLNSNames::xmlnsNamespaceURI);
278     if (attribute.name() == xmlnsPrefixAttr) {
279         namespaces.set(attribute.localName().impl(), attribute.value().impl());
280         namespaces.set(attribute.value().impl(), attribute.localName().impl());
281         return false;
282     }
283
284     return true;
285 }
286
287 void MarkupAccumulator::appendNamespace(StringBuilder& result, const AtomicString& prefix, const AtomicString& namespaceURI, Namespaces& namespaces, bool allowEmptyDefaultNS)
288 {
289     namespaces.checkConsistency();
290     if (namespaceURI.isEmpty()) {
291         // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-xhtml-syntax.html#xml-fragment-serialization-algorithm
292         if (allowEmptyDefaultNS && namespaces.get(emptyAtom.impl())) {
293             result.append(' ');
294             result.append(xmlnsAtom.string());
295             result.appendLiteral("=\"\"");
296         }
297         return;
298     }
299
300     // Use emptyAtoms's impl() for both null and empty strings since the HashMap can't handle 0 as a key
301     AtomicStringImpl* pre = prefix.isEmpty() ? emptyAtom.impl() : prefix.impl();
302     AtomicStringImpl* foundNS = namespaces.get(pre);
303     if (foundNS != namespaceURI.impl()) {
304         namespaces.set(pre, namespaceURI.impl());
305         // Add namespace to prefix pair so we can do constraint checking later.
306         if (inXMLFragmentSerialization() && !prefix.isEmpty())
307             namespaces.set(namespaceURI.impl(), pre);
308         // Make sure xml prefix and namespace are always known to uphold the constraints listed at http://www.w3.org/TR/xml-names11/#xmlReserved.
309         if (namespaceURI.impl() == XMLNames::xmlNamespaceURI.impl())
310             return;
311         result.append(' ');
312         result.append(xmlnsAtom.string());
313         if (!prefix.isEmpty()) {
314             result.append(':');
315             result.append(prefix);
316         }
317
318         result.append('=');
319         result.append('"');
320         appendAttributeValue(result, namespaceURI, false);
321         result.append('"');
322     }
323 }
324
325 EntityMask MarkupAccumulator::entityMaskForText(const Text& text) const
326 {
327     if (!text.document().isHTMLDocument())
328         return EntityMaskInPCDATA;
329
330     const QualifiedName* parentName = nullptr;
331     if (text.parentElement())
332         parentName = &text.parentElement()->tagQName();
333
334     if (parentName && (*parentName == scriptTag || *parentName == styleTag || *parentName == xmpTag))
335         return EntityMaskInCDATA;
336     return EntityMaskInHTMLPCDATA;
337 }
338
339 void MarkupAccumulator::appendText(StringBuilder& result, const Text& text)
340 {
341     const String& textData = text.data();
342     unsigned start = 0;
343     unsigned length = textData.length();
344
345     if (m_range) {
346         if (&text == &m_range->endContainer())
347             length = m_range->endOffset();
348         if (&text == &m_range->startContainer()) {
349             start = m_range->startOffset();
350             length -= start;
351         }
352     }
353
354     appendCharactersReplacingEntities(result, textData, start, length, entityMaskForText(text));
355 }
356
357 static void appendComment(StringBuilder& result, const String& comment)
358 {
359     // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->".
360     result.appendLiteral("<!--");
361     result.append(comment);
362     result.appendLiteral("-->");
363 }
364
365 void MarkupAccumulator::appendXMLDeclaration(StringBuilder& result, const Document& document)
366 {
367     if (!document.hasXMLDeclaration())
368         return;
369
370     result.appendLiteral("<?xml version=\"");
371     result.append(document.xmlVersion());
372     const String& encoding = document.xmlEncoding();
373     if (!encoding.isEmpty()) {
374         result.appendLiteral("\" encoding=\"");
375         result.append(encoding);
376     }
377     if (document.xmlStandaloneStatus() != Document::StandaloneUnspecified) {
378         result.appendLiteral("\" standalone=\"");
379         if (document.xmlStandalone())
380             result.appendLiteral("yes");
381         else
382             result.appendLiteral("no");
383     }
384
385     result.appendLiteral("\"?>");
386 }
387
388 void MarkupAccumulator::appendDocumentType(StringBuilder& result, const DocumentType& documentType)
389 {
390     if (documentType.name().isEmpty())
391         return;
392
393     result.appendLiteral("<!DOCTYPE ");
394     result.append(documentType.name());
395     if (!documentType.publicId().isNull()) {
396         result.appendLiteral(" PUBLIC \"");
397         result.append(documentType.publicId());
398         result.append('"');
399         if (!documentType.systemId().isNull()) {
400             result.append(' ');
401             result.append('"');
402             result.append(documentType.systemId());
403             result.append('"');
404         }
405     } else if (!documentType.systemId().isNull()) {
406         result.appendLiteral(" SYSTEM \"");
407         result.append(documentType.systemId());
408         result.append('"');
409     }
410     if (!documentType.internalSubset().isNull()) {
411         result.append(' ');
412         result.append('[');
413         result.append(documentType.internalSubset());
414         result.append(']');
415     }
416     result.append('>');
417 }
418
419 void MarkupAccumulator::appendProcessingInstruction(StringBuilder& result, const String& target, const String& data)
420 {
421     // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>".
422     result.append('<');
423     result.append('?');
424     result.append(target);
425     result.append(' ');
426     result.append(data);
427     result.append('?');
428     result.append('>');
429 }
430
431 void MarkupAccumulator::appendElement(StringBuilder& result, const Element& element, Namespaces* namespaces)
432 {
433     appendOpenTag(result, element, namespaces);
434
435     if (element.hasAttributes()) {
436         for (const Attribute& attribute : element.attributesIterator())
437             appendAttribute(result, element, attribute, namespaces);
438     }
439
440     // Give an opportunity to subclasses to add their own attributes.
441     appendCustomAttributes(result, element, namespaces);
442
443     appendCloseTag(result, element);
444 }
445
446 void MarkupAccumulator::appendOpenTag(StringBuilder& result, const Element& element, Namespaces* namespaces)
447 {
448     result.append('<');
449     if (inXMLFragmentSerialization() && namespaces && element.prefix().isEmpty()) {
450         // According to http://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#normalizeDocumentAlgo we now should create
451         // a default namespace declaration to make this namespace well-formed. However, http://www.w3.org/TR/xml-names11/#xmlReserved states
452         // "The prefix xml MUST NOT be declared as the default namespace.", so use the xml prefix explicitly.
453         if (element.namespaceURI() == XMLNames::xmlNamespaceURI) {
454             result.append(xmlAtom);
455             result.append(':');
456         }
457     }
458     result.append(element.nodeNamePreservingCase());
459     if ((inXMLFragmentSerialization() || !element.document().isHTMLDocument()) && namespaces && shouldAddNamespaceElement(element))
460         appendNamespace(result, element.prefix(), element.namespaceURI(), *namespaces, inXMLFragmentSerialization());
461 }
462
463 void MarkupAccumulator::appendCloseTag(StringBuilder& result, const Element& element)
464 {
465     if (shouldSelfClose(element)) {
466         if (element.isHTMLElement())
467             result.append(' '); // XHTML 1.0 <-> HTML compatibility.
468         result.append('/');
469     }
470     result.append('>');
471 }
472
473 static inline bool attributeIsInSerializedNamespace(const Attribute& attribute)
474 {
475     return attribute.namespaceURI() == XMLNames::xmlNamespaceURI
476         || attribute.namespaceURI() == XLinkNames::xlinkNamespaceURI
477         || attribute.namespaceURI() == XMLNSNames::xmlnsNamespaceURI;
478 }
479
480 void MarkupAccumulator::generateUniquePrefix(QualifiedName& prefixedName, const Namespaces& namespaces)
481 {
482     // http://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#normalizeDocumentAlgo
483     // Find a prefix following the pattern "NS" + index (starting at 1) and make sure this
484     // prefix is not declared in the current scope.
485     StringBuilder builder;
486     do {
487         builder.clear();
488         builder.appendLiteral("NS");
489         builder.appendNumber(++m_prefixLevel);
490         const AtomicString& name = builder.toAtomicString();
491         if (!namespaces.get(name.impl())) {
492             prefixedName.setPrefix(name);
493             return;
494         }
495     } while (true);
496 }
497
498 void MarkupAccumulator::appendAttribute(StringBuilder& result, const Element& element, const Attribute& attribute, Namespaces* namespaces)
499 {
500     bool documentIsHTML = element.document().isHTMLDocument();
501
502     result.append(' ');
503
504     QualifiedName prefixedName = attribute.name();
505     if (documentIsHTML && !attributeIsInSerializedNamespace(attribute))
506         result.append(attribute.name().localName());
507     else {
508         if (!attribute.namespaceURI().isEmpty()) {
509             AtomicStringImpl* foundNS = namespaces && attribute.prefix().impl() ? namespaces->get(attribute.prefix().impl()) : 0;
510             bool prefixIsAlreadyMappedToOtherNS = foundNS && foundNS != attribute.namespaceURI().impl();
511             if (attribute.prefix().isEmpty() || !foundNS || prefixIsAlreadyMappedToOtherNS) {
512                 if (AtomicStringImpl* prefix = namespaces ? namespaces->get(attribute.namespaceURI().impl()) : 0)
513                     prefixedName.setPrefix(AtomicString(prefix));
514                 else {
515                     bool shouldBeDeclaredUsingAppendNamespace = !attribute.prefix().isEmpty() && !foundNS;
516                     if (!shouldBeDeclaredUsingAppendNamespace && attribute.localName() != xmlnsAtom && namespaces)
517                         generateUniquePrefix(prefixedName, *namespaces);
518                 }
519             }
520         }
521         result.append(prefixedName.toString());
522     }
523
524     result.append('=');
525
526     if (element.isURLAttribute(attribute))
527         appendQuotedURLAttributeValue(result, element, attribute);
528     else {
529         result.append('"');
530         appendAttributeValue(result, attribute.value(), documentIsHTML);
531         result.append('"');
532     }
533
534     if ((inXMLFragmentSerialization() || !documentIsHTML) && namespaces && shouldAddNamespaceAttribute(attribute, *namespaces))
535         appendNamespace(result, prefixedName.prefix(), prefixedName.namespaceURI(), *namespaces);
536 }
537
538 void MarkupAccumulator::appendCDATASection(StringBuilder& result, const String& section)
539 {
540     // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>".
541     result.appendLiteral("<![CDATA[");
542     result.append(section);
543     result.appendLiteral("]]>");
544 }
545
546 void MarkupAccumulator::appendStartMarkup(StringBuilder& result, const Node& node, Namespaces* namespaces)
547 {
548     if (namespaces)
549         namespaces->checkConsistency();
550
551     switch (node.nodeType()) {
552     case Node::TEXT_NODE:
553         appendText(result, downcast<Text>(node));
554         break;
555     case Node::COMMENT_NODE:
556         appendComment(result, downcast<Comment>(node).data());
557         break;
558     case Node::DOCUMENT_NODE:
559         appendXMLDeclaration(result, downcast<Document>(node));
560         break;
561     case Node::DOCUMENT_FRAGMENT_NODE:
562         break;
563     case Node::DOCUMENT_TYPE_NODE:
564         appendDocumentType(result, downcast<DocumentType>(node));
565         break;
566     case Node::PROCESSING_INSTRUCTION_NODE:
567         appendProcessingInstruction(result, downcast<ProcessingInstruction>(node).target(), downcast<ProcessingInstruction>(node).data());
568         break;
569     case Node::ELEMENT_NODE:
570         appendElement(result, downcast<Element>(node), namespaces);
571         break;
572     case Node::CDATA_SECTION_NODE:
573         appendCDATASection(result, downcast<CDATASection>(node).data());
574         break;
575     case Node::ATTRIBUTE_NODE:
576         ASSERT_NOT_REACHED();
577         break;
578     }
579 }
580
581 // Rules of self-closure
582 // 1. No elements in HTML documents use the self-closing syntax.
583 // 2. Elements w/ children never self-close because they use a separate end tag.
584 // 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag.
585 // 4. Other elements self-close.
586 bool MarkupAccumulator::shouldSelfClose(const Element& element)
587 {
588     if (!inXMLFragmentSerialization() && element.document().isHTMLDocument())
589         return false;
590     if (element.hasChildNodes())
591         return false;
592     if (element.isHTMLElement() && !elementCannotHaveEndTag(element))
593         return false;
594     return true;
595 }
596
597 bool MarkupAccumulator::elementCannotHaveEndTag(const Node& node)
598 {
599     if (!is<HTMLElement>(node))
600         return false;
601
602     // FIXME: ieForbidsInsertHTML may not be the right function to call here
603     // ieForbidsInsertHTML is used to disallow setting innerHTML/outerHTML
604     // or createContextualFragment.  It does not necessarily align with
605     // which elements should be serialized w/o end tags.
606     return downcast<HTMLElement>(node).ieForbidsInsertHTML();
607 }
608
609 void MarkupAccumulator::appendEndMarkup(StringBuilder& result, const Element& element)
610 {
611     if (shouldSelfClose(element) || (!element.hasChildNodes() && elementCannotHaveEndTag(element)))
612         return;
613
614     result.append('<');
615     result.append('/');
616     result.append(element.nodeNamePreservingCase());
617     result.append('>');
618 }
619
620 }