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