2b364c4ca930c6fa0bd2647c3a57e3b74620a495
[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             if (length == 0 && additionalStyle.length() > 0) {
175                 // FIXME: Handle case where additionalStyle has illegal characters in it, like "
176                 markup += " " +  styleAttr.localName().deprecatedString() + "=\"" + additionalStyle.deprecatedString() + "\"";
177             }
178             else {
179                 for (unsigned int i=0; i<length; i++) {
180                     Attribute *attr = attrs->attributeItem(i);
181                     String value = attr->value();
182                     if (attr->name() == styleAttr && additionalStyle.length() > 0)
183                         value += "; " + additionalStyle;
184                     // FIXME: Handle case where value has illegal characters in it, like "
185                     if (documentIsHTML)
186                         markup += " " + attr->name().localName().deprecatedString();
187                     else
188                         markup += " " + attr->name().toString().deprecatedString();
189                     markup += "=\"" + escapeTextForMarkup(value.deprecatedString()) + "\"";
190                 }
191             }
192             
193             if (shouldSelfClose(el)) {
194                 if (el->isHTMLElement())
195                     markup += " "; // XHTML 1.0 <-> HTML compatibility.
196                 markup += "/>";
197             } else
198                 markup += ">";
199             
200             return markup;
201         }
202         case Node::ATTRIBUTE_NODE:
203         case Node::CDATA_SECTION_NODE:
204         case Node::ENTITY_NODE:
205         case Node::ENTITY_REFERENCE_NODE:
206         case Node::NOTATION_NODE:
207         case Node::XPATH_NAMESPACE_NODE:
208             break;
209     }
210     return "";
211 }
212
213 static inline bool doesHTMLForbidEndTag(const Node *node)
214 {
215     if (node->isHTMLElement()) {
216         const HTMLElement* htmlElt = static_cast<const HTMLElement*>(node);
217         return (htmlElt->endTagRequirement() == TagStatusForbidden);
218     }
219     return false;
220 }
221
222 // Rules of self-closure
223 // 1. No elements in HTML documents use the self-closing syntax.
224 // 2. Elements w/ children never self-close because they use a separate end tag.
225 // 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag.
226 // 4. Other elements self-close.
227 static inline bool shouldSelfClose(const Node *node)
228 {
229     if (node->document()->isHTMLDocument())
230         return false;
231     if (node->hasChildNodes())
232         return false;
233     if (node->isHTMLElement() && !doesHTMLForbidEndTag(node))
234         return false;
235     return true;
236 }
237
238 static DeprecatedString endMarkup(const Node *node)
239 {
240     if (node->isElementNode() && !shouldSelfClose(node) && (node->hasChildNodes() || !doesHTMLForbidEndTag(node)))
241         return "</" + static_cast<const Element*>(node)->nodeNamePreservingCase().deprecatedString() + ">";
242     return "";
243 }
244
245 static DeprecatedString markup(Node* startNode, bool onlyIncludeChildren, bool includeSiblings, Vector<Node*> *nodes)
246 {
247     // Doesn't make sense to only include children and include siblings.
248     ASSERT(!(onlyIncludeChildren && includeSiblings));
249     DeprecatedString me = "";
250     for (Node* current = startNode; current != NULL; current = includeSiblings ? current->nextSibling() : NULL) {
251         if (!onlyIncludeChildren) {
252             if (nodes)
253                 nodes->append(current);
254             me += startMarkup(current, 0, DoNotAnnotateForInterchange, 0);
255         }
256         // print children
257         if (Node *n = current->firstChild())
258             if (!(n->document()->isHTMLDocument() && doesHTMLForbidEndTag(current)))
259                 me += markup(n, false, true, nodes);
260         
261         // Print my ending tag
262         if (!onlyIncludeChildren)
263             me += endMarkup(current);
264     }
265     return me;
266 }
267
268 static void completeURLs(Node *node, const DeprecatedString &baseURL)
269 {
270     Node *end = node->traverseNextSibling();
271     for (Node *n = node; n != end; n = n->traverseNextNode()) {
272         if (n->isElementNode()) {
273             Element *e = static_cast<Element*>(n);
274             NamedAttrMap *attrs = e->attributes();
275             unsigned length = attrs->length();
276             for (unsigned i = 0; i < length; i++) {
277                 Attribute *attr = attrs->attributeItem(i);
278                 if (e->isURLAttribute(attr))
279                     e->setAttribute(attr->name(), KURL(baseURL, attr->value().deprecatedString()).url());
280             }
281         }
282     }
283 }
284
285 // FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange? 
286 // FIXME: At least, annotation and style info should probably not be included in range.markupString()
287 DeprecatedString createMarkup(const Range *range, Vector<Node*>* nodes, EAnnotateForInterchange annotate)
288 {
289     if (!range || range->isDetached())
290         return DeprecatedString();
291
292     static const DeprecatedString interchangeNewlineString = DeprecatedString("<br class=\"") + AppleInterchangeNewline + "\">";
293
294     ExceptionCode ec = 0;
295     if (range->collapsed(ec))
296         return "";
297     ASSERT(ec == 0);
298     Node *commonAncestor = range->commonAncestorContainer(ec);
299     ASSERT(ec == 0);
300
301     Document *doc = commonAncestor->document();
302     doc->updateLayoutIgnorePendingStylesheets();
303
304     Node *commonAncestorBlock = 0;
305     if (commonAncestor)
306         commonAncestorBlock = commonAncestor->enclosingBlockFlowElement();
307     if (!commonAncestorBlock)
308         return "";
309
310     DeprecatedStringList markups;
311     Node *pastEnd = range->pastEndNode();
312     Node *lastClosed = 0;
313     Vector<Node*> ancestorsToClose;
314
315     // calculate the "default style" for this markup
316     Position pos(doc->documentElement(), 0);
317     RefPtr<CSSComputedStyleDeclaration> computedStyle = pos.computedStyle();
318     RefPtr<CSSMutableStyleDeclaration> defaultStyle = computedStyle->copyInheritableProperties();
319     
320     // FIXME: Shouldn't we do the interchangeNewLine and skip the node iff (annotate == AnnotateForInterchange)?
321     Node *startNode = range->startNode();
322     VisiblePosition visibleStart(range->startPosition(), VP_DEFAULT_AFFINITY);
323     VisiblePosition visibleEnd(range->endPosition(), VP_DEFAULT_AFFINITY);
324     if (!inSameBlock(visibleStart, visibleStart.next())) {
325         if (visibleStart == visibleEnd.previous())
326             return interchangeNewlineString;
327             
328         // FIXME: This adds a newline, but a later add of the ancestor could go before that newline (e.g. when startNode is tbody,
329         // we will later add a table, but the interchangeNewlineString will appear between the table and the tbody! for now, that
330         // does not happen with tables because of code below that moves startNode to the table when at tbody)
331         markups.append(interchangeNewlineString);
332         startNode = startNode->traverseNextNode();
333     }
334
335     // no use having a tbody without its table
336     // NOTE: need sibling check because startNode might be anonymous text (like newlines)
337     // FIXME: This would not be needed if ancestor adding applied to more than just the lastClosed
338     for (Node *n = startNode; n; n = n->nextSibling()) {
339         if (n->hasTagName(tbodyTag)) {
340             startNode = startNode->parent();
341             break;
342         }
343     }
344
345     // Iterate through the nodes of the range.
346     Node *next;
347     for (Node *n = startNode; n != pastEnd; n = next) {
348         next = n->traverseNextNode();
349
350         if (n->isBlockFlow() && next == pastEnd)
351             // Don't write out an empty block.
352             continue;
353         
354         // Add the node to the markup.
355         // FIXME: Add markup for nodes without renderers to fix <rdar://problem/4062865>. Also see the three checks below.
356         if (n->renderer()) {
357             markups.append(startMarkup(n, range, annotate, defaultStyle.get()));
358             if (nodes)
359                 nodes->append(n);
360         }
361         
362         if (n->firstChild() == 0) {
363             // Node has no children, add its close tag now.
364             if (n->renderer()) {
365                 markups.append(endMarkup(n));
366                 lastClosed = n;
367             }
368             
369             // Check if the node is the last leaf of a tree.
370             if (n->nextSibling() == 0 || next == pastEnd) {
371                 if (!ancestorsToClose.isEmpty()) {
372                     // Close up the ancestors.
373                     do {
374                         Node *ancestor = ancestorsToClose.last();
375                         if (next != pastEnd && next->isAncestor(ancestor))
376                             break;
377                         // Not at the end of the range, close ancestors up to sibling of next node.
378                         markups.append(endMarkup(ancestor));
379                         lastClosed = ancestor;
380                         ancestorsToClose.removeLast();
381                     } while (!ancestorsToClose.isEmpty());
382                 } else {
383                     if (next != pastEnd) {
384                         Node *nextParent = next->parentNode();
385                         if (n != nextParent) {
386                             for (Node *parent = n->parent(); parent != 0 && parent != nextParent; parent = parent->parentNode()) {
387                                 // All ancestors not in the ancestorsToClose list should either be a) unrendered:
388                                 if (!parent->renderer())
389                                     continue;
390                                 // or b) ancestors that we never encountered during a pre-order traversal starting at startNode:
391                                 ASSERT(startNode->isAncestor(parent));
392                                 markups.prepend(startMarkup(parent, range, annotate, defaultStyle.get()));
393                                 markups.append(endMarkup(parent));
394                                 if (nodes)
395                                     nodes->append(parent);
396                                 lastClosed = parent;
397                             }
398                         }
399                     }
400                 }
401             }
402         } else if (n->renderer())
403             // Node is an ancestor, set it to close eventually.
404             ancestorsToClose.append(n);
405     }
406     
407     Node *rangeStartNode = range->startNode();
408     int rangeStartOffset = range->startOffset(ec);
409     ASSERT(ec == 0);
410     
411     // Add ancestors up to the common ancestor block so inline ancestors such as FONT and B are part of the markup.
412     if (lastClosed) {
413         for (Node *ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
414             if (Range::compareBoundaryPoints(ancestor, 0, rangeStartNode, rangeStartOffset) >= 0) {
415                 // we have already added markup for this node
416                 continue;
417             }
418             bool breakAtEnd = false;
419             if (commonAncestorBlock == ancestor) {
420                 // Include ancestors that are required to retain the appearance of the copied markup.
421                 if (ancestor->hasTagName(listingTag)
422                         || ancestor->hasTagName(olTag)
423                         || ancestor->hasTagName(preTag)
424                         || ancestor->hasTagName(tableTag)
425                         || ancestor->hasTagName(ulTag)
426                         || ancestor->hasTagName(xmpTag)) {
427                     breakAtEnd = true;
428                 } else
429                     break;
430             }
431             markups.prepend(startMarkup(ancestor, range, annotate, defaultStyle.get()));
432             markups.append(endMarkup(ancestor));
433             if (nodes) {
434                 nodes->append(ancestor);
435             }        
436             if (breakAtEnd)
437                 break;
438         }
439     }
440
441     if (annotate) {
442         if (!inSameBlock(visibleEnd, visibleEnd.previous()))
443             markups.append(interchangeNewlineString);
444     }
445
446     // Retain the Mail quote level by including all ancestor mail block quotes.
447     for (Node *ancestor = commonAncestorBlock; ancestor; ancestor = ancestor->parentNode()) {
448         if (isMailBlockquote(ancestor)) {
449             markups.prepend(startMarkup(ancestor, range, annotate, defaultStyle.get()));
450             markups.append(endMarkup(ancestor));
451         }
452     }
453     
454     // add in the "default style" for this markup
455     // FIXME: Handle case where value has illegal characters in it, like "
456     DeprecatedString openTag = DeprecatedString("<span class=\"") + AppleStyleSpanClass + "\" style=\"" + defaultStyle->cssText().deprecatedString() + "\">";
457     markups.prepend(openTag);
458     markups.append("</span>");
459
460     return markups.join("");
461 }
462
463 PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const String& markup, const String& baseURL)
464 {
465     ASSERT(document->documentElement()->isHTMLElement());
466     // FIXME: What if the document element is not an HTML element?
467     HTMLElement *element = static_cast<HTMLElement*>(document->documentElement());
468
469     RefPtr<DocumentFragment> fragment = element->createContextualFragment(markup);
470     ASSERT(fragment);
471
472     if (!baseURL.isEmpty() && baseURL != document->baseURL())
473         completeURLs(fragment.get(), baseURL.deprecatedString());
474
475     return fragment.release();
476 }
477
478 DeprecatedString createMarkup(const Node* node, EChildrenOnly includeChildren,
479     Vector<Node*>* nodes, EAnnotateForInterchange annotate)
480 {
481     ASSERT(annotate == DoNotAnnotateForInterchange); // annotation not yet implemented for this code path
482     node->document()->updateLayoutIgnorePendingStylesheets();
483     return markup(const_cast<Node*>(node), includeChildren, false, nodes);
484 }
485
486 static void createParagraphContentsFromString(Element* paragraph, const DeprecatedString& string)
487 {
488     Document* document = paragraph->document();
489
490     ExceptionCode ec = 0;
491     if (string.isEmpty()) {
492         paragraph->appendChild(createBlockPlaceholderElement(document), ec);
493         ASSERT(ec == 0);
494         return;
495     }
496
497     assert(string.find('\n') == -1);
498
499     DeprecatedStringList tabList = DeprecatedStringList::split('\t', string, true);
500     DeprecatedString tabText = "";
501     while (!tabList.isEmpty()) {
502         DeprecatedString s = tabList.first();
503         tabList.pop_front();
504
505         // append the non-tab textual part
506         if (!s.isEmpty()) {
507             if (!tabText.isEmpty()) {
508                 paragraph->appendChild(createTabSpanElement(document, tabText), ec);
509                 ASSERT(ec == 0);
510                 tabText = "";
511             }
512             RefPtr<Node> textNode = document->createTextNode(s);
513             rebalanceWhitespaceInTextNode(textNode.get(), 0, s.length());
514             paragraph->appendChild(textNode.release(), ec);
515             ASSERT(ec == 0);
516         }
517
518         // there is a tab after every entry, except the last entry
519         // (if the last character is a tab, the list gets an extra empty entry)
520         if (!tabList.isEmpty())
521             tabText += '\t';
522         else if (!tabText.isEmpty()) {
523             paragraph->appendChild(createTabSpanElement(document, tabText), ec);
524             ASSERT(ec == 0);
525         }
526     }
527 }
528
529 PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String& text)
530 {
531     if (!context)
532         return 0;
533
534     Node* styleNode = context->startNode();
535     if (!styleNode) {
536         styleNode = context->startPosition().node();
537         if (!styleNode)
538             return 0;
539     }
540
541     Document* document = styleNode->document();
542     RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
543     
544     if (text.isEmpty())
545         return fragment.release();
546
547     DeprecatedString string = text.deprecatedString();
548     string.replace("\r\n", "\n");
549     string.replace('\r', '\n');
550
551     ExceptionCode ec = 0;
552     RenderObject* renderer = styleNode->renderer();
553     if (renderer && renderer->style()->preserveNewline()) {
554         fragment->appendChild(document->createTextNode(string), ec);
555         ASSERT(ec == 0);
556         if (string.endsWith("\n")) {
557             RefPtr<Element> element;
558             element = document->createElementNS(xhtmlNamespaceURI, "br", ec);
559             ASSERT(ec == 0);
560             element->setAttribute(classAttr, AppleInterchangeNewline);            
561             fragment->appendChild(element.release(), ec);
562             ASSERT(ec == 0);
563         }
564         return fragment.release();
565     }
566
567     // Break string into paragraphs. Extra line breaks turn into empty paragraphs.
568     DeprecatedStringList list = DeprecatedStringList::split('\n', string, true); // true gets us empty strings in the list
569     while (!list.isEmpty()) {
570         DeprecatedString s = list.first();
571         list.pop_front();
572
573         RefPtr<Element> element;
574         if (s.isEmpty() && list.isEmpty()) {
575             // For last line, use the "magic BR" rather than a P.
576             element = document->createElementNS(xhtmlNamespaceURI, "br", ec);
577             ASSERT(ec == 0);
578             element->setAttribute(classAttr, AppleInterchangeNewline);            
579         } else {
580             element = createDefaultParagraphElement(document);
581             createParagraphContentsFromString(element.get(), s);
582         }
583         fragment->appendChild(element.release(), ec);
584         ASSERT(ec == 0);
585     }
586     return fragment.release();
587 }
588
589 PassRefPtr<DocumentFragment> createFragmentFromNodes(Document *document, const Vector<Node*>& nodes)
590 {
591     if (!document)
592         return 0;
593     
594     RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
595
596     ExceptionCode ec = 0;
597     size_t size = nodes.size();
598     for (size_t i = 0; i < size; ++i) {
599         RefPtr<Element> element = createDefaultParagraphElement(document);
600         element->appendChild(nodes[i], ec);
601         ASSERT(ec == 0);
602         fragment->appendChild(element.release(), ec);
603         ASSERT(ec == 0);
604     }
605
606     return fragment.release();
607 }
608
609 }