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