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