db1f394990f94c05a124d356e6dd6c29d71f210f
[WebKit-https.git] / WebCore / editing / markup.cpp
1 /*
2  * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "markup.h"
28
29 #include "CSSComputedStyleDeclaration.h"
30 #include "Comment.h"
31 #include "Document.h"
32 #include "DocumentFragment.h"
33 #include "DocumentType.h"
34 #include "HTMLElement.h"
35 #include "HTMLNames.h"
36 #include "InlineTextBox.h"
37 #include "KURL.h"
38 #include "Logging.h"
39 #include "ProcessingInstruction.h"
40 #include "Range.h"
41 #include "htmlediting.h"
42 #include "visible_units.h"
43 #include "TextIterator.h"
44
45 using namespace std;
46
47 namespace WebCore {
48
49 using namespace HTMLNames;
50
51 static inline bool shouldSelfClose(const Node *node);
52
53 static DeprecatedString escapeTextForMarkup(const DeprecatedString &in)
54 {
55     DeprecatedString s = "";
56
57     unsigned len = in.length();
58     for (unsigned i = 0; i < len; ++i) {
59         switch (in[i].unicode()) {
60             case '&':
61                 s += "&amp;";
62                 break;
63             case '<':
64                 s += "&lt;";
65                 break;
66             case '>':
67                 s += "&gt;";
68                 break;
69             default:
70                 s += in[i];
71         }
72     }
73
74     return s;
75 }
76
77 static String stringValueForRange(const Node *node, const Range *range)
78 {
79     String str = node->nodeValue().copy();
80     if (range) {
81         ExceptionCode ec;
82         if (node == range->endContainer(ec))
83             str.truncate(range->endOffset(ec));
84         if (node == range->startContainer(ec))
85             str.remove(0, range->startOffset(ec));
86     }
87     return str;
88 }
89
90 static DeprecatedString renderedText(const Node *node, const Range *range)
91 {
92     if (!node->isTextNode())
93         return DeprecatedString();
94
95     ExceptionCode ec;
96     const Text* textNode = static_cast<const Text*>(node);
97     unsigned startOffset = 0;
98     unsigned endOffset = textNode->length();
99
100     if (range && node == range->startContainer(ec))
101         startOffset = range->startOffset(ec);
102     if (range && node == range->endContainer(ec))
103         endOffset = range->endOffset(ec);
104     
105     Position start(const_cast<Node*>(node), startOffset);
106     Position end(const_cast<Node*>(node), endOffset);
107     Range r(node->document(), start, end);
108     return plainText(&r);
109 }
110
111 static DeprecatedString startMarkup(const Node *node, const Range *range, EAnnotateForInterchange annotate, CSSMutableStyleDeclaration *defaultStyle)
112 {
113     bool documentIsHTML = node->document()->isHTMLDocument();
114     switch (node->nodeType()) {
115         case Node::TEXT_NODE: {
116             if (Node* parent = node->parentNode()) {
117                 if (parent->hasTagName(listingTag)
118                         || parent->hasTagName(preTag)
119                         || parent->hasTagName(scriptTag)
120                         || parent->hasTagName(styleTag)
121                         || parent->hasTagName(textareaTag)
122                         || parent->hasTagName(xmpTag))
123                     return stringValueForRange(node, range).deprecatedString();
124             }
125             DeprecatedString markup = annotate ? escapeTextForMarkup(renderedText(node, range)) : escapeTextForMarkup(stringValueForRange(node, range).deprecatedString());            
126             if (defaultStyle) {
127                 Node *element = node->parentNode();
128                 if (element) {
129                     RefPtr<CSSComputedStyleDeclaration> computedStyle = Position(element, 0).computedStyle();
130                     RefPtr<CSSMutableStyleDeclaration> style = computedStyle->copyInheritableProperties();
131                     defaultStyle->diff(style.get());
132                     if (style->length() > 0) {
133                         // FIXME: Handle case where style->cssText() has illegal characters in it, like "
134                         DeprecatedString openTag = DeprecatedString("<span class=\"") + AppleStyleSpanClass + "\" style=\"" + style->cssText().deprecatedString() + "\">";
135                         markup = openTag + markup + "</span>";
136                     }
137                 }            
138             }
139             return annotate ? convertHTMLTextToInterchangeFormat(markup) : markup;
140         }
141         case Node::COMMENT_NODE:
142             return static_cast<const Comment*>(node)->toString().deprecatedString();
143         case Node::DOCUMENT_NODE: {
144             // Documents do not normally contain a docType as a child node, force it to print here instead.
145             const DocumentType* docType = static_cast<const Document*>(node)->doctype();
146             if (docType)
147                 return docType->toString().deprecatedString();
148             return "";
149         }
150         case Node::DOCUMENT_FRAGMENT_NODE:
151             return "";
152         case Node::DOCUMENT_TYPE_NODE:
153             return static_cast<const DocumentType*>(node)->toString().deprecatedString();
154         case Node::PROCESSING_INSTRUCTION_NODE:
155             return static_cast<const ProcessingInstruction*>(node)->toString().deprecatedString();
156         case Node::ELEMENT_NODE: {
157             DeprecatedString markup = DeprecatedChar('<');
158             const Element* el = static_cast<const Element*>(node);
159             markup += el->nodeNamePreservingCase().deprecatedString();
160             String additionalStyle;
161             if (defaultStyle && el->isHTMLElement()) {
162                 RefPtr<CSSComputedStyleDeclaration> computedStyle = Position(const_cast<Element*>(el), 0).computedStyle();
163                 RefPtr<CSSMutableStyleDeclaration> style = computedStyle->copyInheritableProperties();
164                 defaultStyle->diff(style.get());
165                 if (style->length() > 0) {
166                     CSSMutableStyleDeclaration *inlineStyleDecl = static_cast<const HTMLElement*>(el)->inlineStyleDecl();
167                     if (inlineStyleDecl)
168                         inlineStyleDecl->diff(style.get());
169                     additionalStyle = style->cssText();
170                 }
171             }
172             NamedAttrMap *attrs = el->attributes();
173             unsigned length = attrs->length();
174
175             for (unsigned int i = 0; i < length; i++) {
176                 Attribute *attr = attrs->attributeItem(i);
177                 String value = attr->value();
178                 if (attr->name() == styleAttr && additionalStyle.length() > 0) {
179                     value += "; " + additionalStyle;
180                     additionalStyle = "";
181                 }
182                 // FIXME: Handle case where value has illegal characters in it, like "
183                 if (documentIsHTML)
184                     markup += " " + attr->name().localName().deprecatedString();
185                 else
186                     markup += " " + attr->name().toString().deprecatedString();
187                 markup += "=\"" + escapeTextForMarkup(value.deprecatedString()) + "\"";
188             }
189             
190             if (additionalStyle.length() > 0)
191                 // FIXME: Handle case where additionalStyle has illegal characters in it, like "
192                 markup += " " +  styleAttr.localName().deprecatedString() + "=\"" + additionalStyle.deprecatedString() + "\"";
193             
194             if (shouldSelfClose(el)) {
195                 if (el->isHTMLElement())
196                     markup += " "; // XHTML 1.0 <-> HTML compatibility.
197                 markup += "/>";
198             } else
199                 markup += ">";
200             
201             return markup;
202         }
203         case Node::ATTRIBUTE_NODE:
204         case Node::CDATA_SECTION_NODE:
205         case Node::ENTITY_NODE:
206         case Node::ENTITY_REFERENCE_NODE:
207         case Node::NOTATION_NODE:
208         case Node::XPATH_NAMESPACE_NODE:
209             break;
210     }
211     return "";
212 }
213
214 static inline bool doesHTMLForbidEndTag(const Node *node)
215 {
216     if (node->isHTMLElement()) {
217         const HTMLElement* htmlElt = static_cast<const HTMLElement*>(node);
218         return (htmlElt->endTagRequirement() == TagStatusForbidden);
219     }
220     return false;
221 }
222
223 // Rules of self-closure
224 // 1. No elements in HTML documents use the self-closing syntax.
225 // 2. Elements w/ children never self-close because they use a separate end tag.
226 // 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag.
227 // 4. Other elements self-close.
228 static inline bool shouldSelfClose(const Node *node)
229 {
230     if (node->document()->isHTMLDocument())
231         return false;
232     if (node->hasChildNodes())
233         return false;
234     if (node->isHTMLElement() && !doesHTMLForbidEndTag(node))
235         return false;
236     return true;
237 }
238
239 static DeprecatedString endMarkup(const Node *node)
240 {
241     if (node->isElementNode() && !shouldSelfClose(node) && (node->hasChildNodes() || !doesHTMLForbidEndTag(node)))
242         return "</" + static_cast<const Element*>(node)->nodeNamePreservingCase().deprecatedString() + ">";
243     return "";
244 }
245
246 static DeprecatedString markup(Node* startNode, bool onlyIncludeChildren, bool includeSiblings, Vector<Node*> *nodes)
247 {
248     // Doesn't make sense to only include children and include siblings.
249     ASSERT(!(onlyIncludeChildren && includeSiblings));
250     DeprecatedString me = "";
251     for (Node* current = startNode; current != NULL; current = includeSiblings ? current->nextSibling() : NULL) {
252         if (!onlyIncludeChildren) {
253             if (nodes)
254                 nodes->append(current);
255             me += startMarkup(current, 0, DoNotAnnotateForInterchange, 0);
256         }
257         // print children
258         if (Node *n = current->firstChild())
259             if (!(n->document()->isHTMLDocument() && doesHTMLForbidEndTag(current)))
260                 me += markup(n, false, true, nodes);
261         
262         // Print my ending tag
263         if (!onlyIncludeChildren)
264             me += endMarkup(current);
265     }
266     return me;
267 }
268
269 static void completeURLs(Node *node, const DeprecatedString &baseURL)
270 {
271     Node *end = node->traverseNextSibling();
272     for (Node *n = node; n != end; n = n->traverseNextNode()) {
273         if (n->isElementNode()) {
274             Element *e = static_cast<Element*>(n);
275             NamedAttrMap *attrs = e->attributes();
276             unsigned length = attrs->length();
277             for (unsigned i = 0; i < length; i++) {
278                 Attribute *attr = attrs->attributeItem(i);
279                 if (e->isURLAttribute(attr))
280                     e->setAttribute(attr->name(), KURL(baseURL, attr->value().deprecatedString()).url());
281             }
282         }
283     }
284 }
285
286 // FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange? 
287 // FIXME: At least, annotation and style info should probably not be included in range.markupString()
288 DeprecatedString createMarkup(const Range *range, Vector<Node*>* nodes, EAnnotateForInterchange annotate)
289 {
290     if (!range || range->isDetached())
291         return DeprecatedString();
292
293     static const DeprecatedString interchangeNewlineString = DeprecatedString("<br class=\"") + AppleInterchangeNewline + "\">";
294
295     ExceptionCode ec = 0;
296     if (range->collapsed(ec))
297         return "";
298     ASSERT(ec == 0);
299     Node *commonAncestor = range->commonAncestorContainer(ec);
300     ASSERT(ec == 0);
301
302     Document *doc = commonAncestor->document();
303     doc->updateLayoutIgnorePendingStylesheets();
304
305     Node *commonAncestorBlock = 0;
306     if (commonAncestor)
307         commonAncestorBlock = commonAncestor->enclosingBlockFlowElement();
308     if (!commonAncestorBlock)
309         return "";
310
311     DeprecatedStringList markups;
312     Node *pastEnd = range->pastEndNode();
313     Node *lastClosed = 0;
314     Vector<Node*> ancestorsToClose;
315
316     // calculate the "default style" for this markup
317     Position pos(doc->documentElement(), 0);
318     RefPtr<CSSComputedStyleDeclaration> computedStyle = pos.computedStyle();
319     RefPtr<CSSMutableStyleDeclaration> defaultStyle = computedStyle->copyInheritableProperties();
320     
321     // FIXME: Shouldn't we do the interchangeNewLine and skip the node iff (annotate == AnnotateForInterchange)?
322     Node *startNode = range->startNode();
323     VisiblePosition visibleStart(range->startPosition(), VP_DEFAULT_AFFINITY);
324     VisiblePosition visibleEnd(range->endPosition(), VP_DEFAULT_AFFINITY);
325     if (!inSameBlock(visibleStart, visibleStart.next())) {
326         if (visibleStart == visibleEnd.previous())
327             return interchangeNewlineString;
328             
329         // FIXME: This adds a newline, but a later add of the ancestor could go before that newline (e.g. when startNode is tbody,
330         // we will later add a table, but the interchangeNewlineString will appear between the table and the tbody! for now, that
331         // does not happen with tables because of code below that moves startNode to the table when at tbody)
332         markups.append(interchangeNewlineString);
333         startNode = startNode->traverseNextNode();
334     }
335
336     // no use having a tbody without its table
337     // NOTE: need sibling check because startNode might be anonymous text (like newlines)
338     // FIXME: This would not be needed if ancestor adding applied to more than just the lastClosed
339     for (Node *n = startNode; n; n = n->nextSibling()) {
340         if (n->hasTagName(tbodyTag)) {
341             startNode = startNode->parent();
342             break;
343         }
344     }
345
346     // Iterate through the nodes of the range.
347     Node *next;
348     for (Node *n = startNode; n != pastEnd; n = next) {
349         next = n->traverseNextNode();
350
351         if (n->isBlockFlow() && next == pastEnd)
352             // Don't write out an empty block.
353             continue;
354         
355         // Add the node to the markup.
356         // FIXME: Add markup for nodes without renderers to fix <rdar://problem/4062865>. Also see the three checks below.
357         if (n->renderer()) {
358             markups.append(startMarkup(n, range, annotate, defaultStyle.get()));
359             if (nodes)
360                 nodes->append(n);
361         }
362         
363         if (n->firstChild() == 0) {
364             // Node has no children, add its close tag now.
365             if (n->renderer()) {
366                 markups.append(endMarkup(n));
367                 lastClosed = n;
368             }
369             
370             // Check if the node is the last leaf of a tree.
371             if (n->nextSibling() == 0 || next == pastEnd) {
372                 if (!ancestorsToClose.isEmpty()) {
373                     // Close up the ancestors.
374                     do {
375                         Node *ancestor = ancestorsToClose.last();
376                         if (next != pastEnd && next->isAncestor(ancestor))
377                             break;
378                         // Not at the end of the range, close ancestors up to sibling of next node.
379                         markups.append(endMarkup(ancestor));
380                         lastClosed = ancestor;
381                         ancestorsToClose.removeLast();
382                     } while (!ancestorsToClose.isEmpty());
383                 } else {
384                     if (next != pastEnd) {
385                         Node *nextParent = next->parentNode();
386                         if (n != nextParent) {
387                             for (Node *parent = n->parent(); parent != 0 && parent != nextParent; parent = parent->parentNode()) {
388                                 // All ancestors not in the ancestorsToClose list should either be a) unrendered:
389                                 if (!parent->renderer())
390                                     continue;
391                                 // or b) ancestors that we never encountered during a pre-order traversal starting at startNode:
392                                 ASSERT(startNode->isAncestor(parent));
393                                 markups.prepend(startMarkup(parent, range, annotate, defaultStyle.get()));
394                                 markups.append(endMarkup(parent));
395                                 if (nodes)
396                                     nodes->append(parent);
397                                 lastClosed = parent;
398                             }
399                         }
400                     }
401                 }
402             }
403         } else if (n->renderer())
404             // Node is an ancestor, set it to close eventually.
405             ancestorsToClose.append(n);
406     }
407     
408     Node *rangeStartNode = range->startNode();
409     int rangeStartOffset = range->startOffset(ec);
410     ASSERT(ec == 0);
411     
412     // Add ancestors up to the common ancestor block so inline ancestors such as FONT and B are part of the markup.
413     if (lastClosed) {
414         for (Node *ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
415             if (Range::compareBoundaryPoints(ancestor, 0, rangeStartNode, rangeStartOffset) >= 0) {
416                 // we have already added markup for this node
417                 continue;
418             }
419             bool breakAtEnd = false;
420             if (commonAncestorBlock == ancestor) {
421                 // Include ancestors that are required to retain the appearance of the copied markup.
422                 if (ancestor->hasTagName(listingTag)
423                         || ancestor->hasTagName(olTag)
424                         || ancestor->hasTagName(preTag)
425                         || ancestor->hasTagName(tableTag)
426                         || ancestor->hasTagName(ulTag)
427                         || ancestor->hasTagName(xmpTag)) {
428                     breakAtEnd = true;
429                 } else
430                     break;
431             }
432             markups.prepend(startMarkup(ancestor, range, annotate, defaultStyle.get()));
433             markups.append(endMarkup(ancestor));
434             if (nodes) {
435                 nodes->append(ancestor);
436             }        
437             if (breakAtEnd)
438                 break;
439         }
440     }
441
442     if (annotate) {
443         if (!inSameBlock(visibleEnd, visibleEnd.previous()))
444             markups.append(interchangeNewlineString);
445     }
446
447     // Retain the Mail quote level by including all ancestor mail block quotes.
448     for (Node *ancestor = commonAncestorBlock; ancestor; ancestor = ancestor->parentNode()) {
449         if (isMailBlockquote(ancestor)) {
450             markups.prepend(startMarkup(ancestor, range, annotate, defaultStyle.get()));
451             markups.append(endMarkup(ancestor));
452         }
453     }
454     
455     // add in the "default style" for this markup
456     // FIXME: Handle case where value has illegal characters in it, like "
457     DeprecatedString openTag = DeprecatedString("<span class=\"") + AppleStyleSpanClass + "\" style=\"" + defaultStyle->cssText().deprecatedString() + "\">";
458     markups.prepend(openTag);
459     markups.append("</span>");
460
461     return markups.join("");
462 }
463
464 PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const String& markup, const String& baseURL)
465 {
466     ASSERT(document->documentElement()->isHTMLElement());
467     // FIXME: What if the document element is not an HTML element?
468     HTMLElement *element = static_cast<HTMLElement*>(document->documentElement());
469
470     RefPtr<DocumentFragment> fragment = element->createContextualFragment(markup);
471     ASSERT(fragment);
472
473     if (!baseURL.isEmpty() && baseURL != document->baseURL())
474         completeURLs(fragment.get(), baseURL.deprecatedString());
475
476     return fragment.release();
477 }
478
479 DeprecatedString createMarkup(const Node* node, EChildrenOnly includeChildren,
480     Vector<Node*>* nodes, EAnnotateForInterchange annotate)
481 {
482     ASSERT(annotate == DoNotAnnotateForInterchange); // annotation not yet implemented for this code path
483     node->document()->updateLayoutIgnorePendingStylesheets();
484     return markup(const_cast<Node*>(node), includeChildren, false, nodes);
485 }
486
487 static void createParagraphContentsFromString(Element* paragraph, const DeprecatedString& string)
488 {
489     Document* document = paragraph->document();
490
491     ExceptionCode ec = 0;
492     if (string.isEmpty()) {
493         paragraph->appendChild(createBlockPlaceholderElement(document), ec);
494         ASSERT(ec == 0);
495         return;
496     }
497
498     assert(string.find('\n') == -1);
499
500     DeprecatedStringList tabList = DeprecatedStringList::split('\t', string, true);
501     DeprecatedString tabText = "";
502     while (!tabList.isEmpty()) {
503         DeprecatedString s = tabList.first();
504         tabList.pop_front();
505
506         // append the non-tab textual part
507         if (!s.isEmpty()) {
508             if (!tabText.isEmpty()) {
509                 paragraph->appendChild(createTabSpanElement(document, tabText), ec);
510                 ASSERT(ec == 0);
511                 tabText = "";
512             }
513             RefPtr<Node> textNode = document->createTextNode(s);
514             rebalanceWhitespaceInTextNode(textNode.get(), 0, s.length());
515             paragraph->appendChild(textNode.release(), ec);
516             ASSERT(ec == 0);
517         }
518
519         // there is a tab after every entry, except the last entry
520         // (if the last character is a tab, the list gets an extra empty entry)
521         if (!tabList.isEmpty())
522             tabText += '\t';
523         else if (!tabText.isEmpty()) {
524             paragraph->appendChild(createTabSpanElement(document, tabText), ec);
525             ASSERT(ec == 0);
526         }
527     }
528 }
529
530 PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String& text)
531 {
532     if (!context)
533         return 0;
534
535     Node* styleNode = context->startNode();
536     if (!styleNode) {
537         styleNode = context->startPosition().node();
538         if (!styleNode)
539             return 0;
540     }
541
542     Document* document = styleNode->document();
543     RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
544     
545     if (text.isEmpty())
546         return fragment.release();
547
548     DeprecatedString string = text.deprecatedString();
549     string.replace("\r\n", "\n");
550     string.replace('\r', '\n');
551
552     ExceptionCode ec = 0;
553     RenderObject* renderer = styleNode->renderer();
554     if (renderer && renderer->style()->preserveNewline()) {
555         fragment->appendChild(document->createTextNode(string), ec);
556         ASSERT(ec == 0);
557         if (string.endsWith("\n")) {
558             RefPtr<Element> element;
559             element = document->createElementNS(xhtmlNamespaceURI, "br", ec);
560             ASSERT(ec == 0);
561             element->setAttribute(classAttr, AppleInterchangeNewline);            
562             fragment->appendChild(element.release(), ec);
563             ASSERT(ec == 0);
564         }
565         return fragment.release();
566     }
567
568     // Break string into paragraphs. Extra line breaks turn into empty paragraphs.
569     DeprecatedStringList list = DeprecatedStringList::split('\n', string, true); // true gets us empty strings in the list
570     while (!list.isEmpty()) {
571         DeprecatedString s = list.first();
572         list.pop_front();
573
574         RefPtr<Element> element;
575         if (s.isEmpty() && list.isEmpty()) {
576             // For last line, use the "magic BR" rather than a P.
577             element = document->createElementNS(xhtmlNamespaceURI, "br", ec);
578             ASSERT(ec == 0);
579             element->setAttribute(classAttr, AppleInterchangeNewline);            
580         } else {
581             element = createDefaultParagraphElement(document);
582             createParagraphContentsFromString(element.get(), s);
583         }
584         fragment->appendChild(element.release(), ec);
585         ASSERT(ec == 0);
586     }
587     return fragment.release();
588 }
589
590 PassRefPtr<DocumentFragment> createFragmentFromNodes(Document *document, const Vector<Node*>& nodes)
591 {
592     if (!document)
593         return 0;
594     
595     RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
596
597     ExceptionCode ec = 0;
598     size_t size = nodes.size();
599     for (size_t i = 0; i < size; ++i) {
600         RefPtr<Element> element = createDefaultParagraphElement(document);
601         element->appendChild(nodes[i], ec);
602         ASSERT(ec == 0);
603         fragment->appendChild(element.release(), ec);
604         ASSERT(ec == 0);
605     }
606
607     return fragment.release();
608 }
609
610 }