186e7a1164bfe21bd834d57de65f411bd5b0bbff
[WebKit-https.git] / WebCore / khtml / editing / markup.cpp
1 /*
2  * Copyright (C) 2004 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 "markup.h"
27
28 #include "htmlediting.h"
29
30 #include "css/css_computedstyle.h"
31 #include "css/css_valueimpl.h"
32 #include "editing/visible_position.h"
33 #include "editing/visible_units.h"
34 #include "html/html_elementimpl.h"
35 #include "xml/dom_position.h"
36 #include "xml/dom2_rangeimpl.h"
37 #include "rendering/render_text.h"
38 #include "htmlnames.h"
39
40 using namespace DOM::HTMLNames;
41
42 using DOM::AttributeImpl;
43 using DOM::CommentImpl;
44 using DOM::CSSComputedStyleDeclarationImpl;
45 using DOM::CSSMutableStyleDeclarationImpl;
46 using DOM::DocumentFragmentImpl;
47 using DOM::DocumentImpl;
48 using DOM::DOMString;
49 using DOM::ElementImpl;
50 using DOM::TagStatusForbidden;
51 using DOM::HTMLElementImpl;
52 using DOM::NamedAttrMapImpl;
53 using DOM::Node;
54 using DOM::NodeImpl;
55 using DOM::Position;
56 using DOM::RangeImpl;
57 using DOM::TextImpl;
58
59 #if APPLE_CHANGES
60 #include <kxmlcore/Assertions.h>
61 #include "KWQLogging.h"
62 #else
63 #define ASSERT(assertion) ((void)0)
64 #define ASSERT_WITH_MESSAGE(assertion, formatAndArgs...) ((void)0)
65 #define ASSERT_NOT_REACHED() ((void)0)
66 #define LOG(channel, formatAndArgs...) ((void)0)
67 #define ERROR(formatAndArgs...) ((void)0)
68 #define ASSERT(assertion) assert(assertion)
69 #endif
70
71 namespace khtml {
72
73 static QString escapeHTML(const QString &in)
74 {
75     QString s = "";
76
77     unsigned len = in.length();
78     for (unsigned i = 0; i < len; ++i) {
79         switch (in[i].latin1()) {
80             case '&':
81                 s += "&amp;";
82                 break;
83             case '<':
84                 s += "&lt;";
85                 break;
86             case '>':
87                 s += "&gt;";
88                 break;
89             default:
90                 s += in[i];
91         }
92     }
93
94     return s;
95 }
96
97 static QString stringValueForRange(const NodeImpl *node, const RangeImpl *range)
98 {
99     DOMString str = node->nodeValue().copy();
100     if (range) {
101         int exceptionCode;
102         if (node == range->endContainer(exceptionCode)) {
103             str.truncate(range->endOffset(exceptionCode));
104         }
105         if (node == range->startContainer(exceptionCode)) {
106             str.remove(0, range->startOffset(exceptionCode));
107         }
108     }
109     return str.qstring();
110 }
111
112 static QString renderedText(const NodeImpl *node, const RangeImpl *range)
113 {
114     RenderObject *r = node->renderer();
115     if (!r)
116         return QString();
117     
118     if (!node->isTextNode())
119         return QString();
120
121     QString result = "";
122
123     int exceptionCode;
124     const TextImpl *textNode = static_cast<const TextImpl *>(node);
125     unsigned startOffset = 0;
126     unsigned endOffset = textNode->length();
127
128     if (range && node == range->startContainer(exceptionCode))
129         startOffset = range->startOffset(exceptionCode);
130     if (range && node == range->endContainer(exceptionCode))
131         endOffset = range->endOffset(exceptionCode);
132     
133     RenderText *textRenderer = static_cast<RenderText *>(r);
134     QString str = node->nodeValue().qstring();
135     for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
136         unsigned start = box->m_start;
137         unsigned end = box->m_start + box->m_len;
138         if (endOffset < start)
139             break;
140         if (startOffset <= end) {
141             unsigned s = kMax(start, startOffset);
142             unsigned e = kMin(end, endOffset);
143             result.append(str.mid(s, e-s));
144             if (e == end) {
145                 // now add in collapsed-away spaces if at the end of the line
146                 InlineTextBox *nextBox = box->nextTextBox();
147                 if (nextBox && box->root() != nextBox->root()) {
148                     const char nonBreakingSpace = '\xa0';
149                     // count the number of characters between the end of the
150                     // current box and the start of the next box.
151                     int collapsedStart = e;
152                     int collapsedPastEnd = kMin((unsigned)nextBox->m_start, endOffset + 1);
153                     bool addNextNonNBSP = true;
154                     for (int i = collapsedStart; i < collapsedPastEnd; i++) {
155                         if (str[i] == nonBreakingSpace) {
156                             result.append(str[i]);
157                             addNextNonNBSP = true;
158                         }
159                         else if (addNextNonNBSP) {
160                             result.append(str[i]);
161                             addNextNonNBSP = false;
162                         }
163                     }
164                 }
165             }
166         }
167
168     }
169     
170     return result;
171 }
172
173 static QString startMarkup(const NodeImpl *node, const RangeImpl *range, EAnnotateForInterchange annotate, CSSMutableStyleDeclarationImpl *defaultStyle)
174 {
175     unsigned short type = node->nodeType();
176     switch (type) {
177         case Node::TEXT_NODE: {
178             if (node->parentNode()) {
179                 if (node->parentNode()->hasTagName(preTag) ||
180                     node->parentNode()->hasTagName(scriptTag) ||
181                     node->parentNode()->hasTagName(styleTag) ||
182                     node->parentNode()->hasTagName(textareaTag))
183                     return stringValueForRange(node, range);
184             }
185             QString markup = annotate ? escapeHTML(renderedText(node, range)) : escapeHTML(stringValueForRange(node, range));            
186             if (defaultStyle) {
187                 NodeImpl *element = node->parentNode();
188                 if (element) {
189                     CSSComputedStyleDeclarationImpl *computedStyle = Position(element, 0).computedStyle();
190                     computedStyle->ref();
191                     CSSMutableStyleDeclarationImpl *style = computedStyle->copyInheritableProperties();
192                     computedStyle->deref();
193                     style->ref();
194                     defaultStyle->diff(style);
195                     if (style->length() > 0) {
196                         // FIXME: Handle case where style->cssText() has illegal characters in it, like "
197                         QString openTag = QString("<span class=\"") + AppleStyleSpanClass + "\" style=\"" + style->cssText().qstring() + "\">";
198                         markup = openTag + markup + "</span>";
199                     }
200                     style->deref();
201                 }            
202             }
203             return annotate ? convertHTMLTextToInterchangeFormat(markup) : markup;
204         }
205         case Node::COMMENT_NODE:
206             return static_cast<const CommentImpl *>(node)->toString().qstring();
207         case Node::DOCUMENT_NODE:
208             return "";
209         default: {
210             QString markup = QChar('<') + node->nodeName().qstring();
211             if (type == Node::ELEMENT_NODE) {
212                 const ElementImpl *el = static_cast<const ElementImpl *>(node);
213                 DOMString additionalStyle;
214                 if (defaultStyle && el->isHTMLElement()) {
215                     CSSComputedStyleDeclarationImpl *computedStyle = Position(const_cast<ElementImpl *>(el), 0).computedStyle();
216                     computedStyle->ref();
217                     CSSMutableStyleDeclarationImpl *style = computedStyle->copyInheritableProperties();
218                     computedStyle->deref();
219                     style->ref();
220                     defaultStyle->diff(style);
221                     if (style->length() > 0) {
222                         CSSMutableStyleDeclarationImpl *inlineStyleDecl = static_cast<const HTMLElementImpl *>(el)->inlineStyleDecl();
223                         if (inlineStyleDecl)
224                             inlineStyleDecl->diff(style);
225                         additionalStyle = style->cssText();
226                     }
227                     style->deref();
228                 }
229                 NamedAttrMapImpl *attrs = el->attributes();
230                 unsigned length = attrs->length();
231                 if (length == 0 && additionalStyle.length() > 0) {
232                     // FIXME: Handle case where additionalStyle has illegal characters in it, like "
233                     markup += " " +  styleAttr.localName().qstring() + "=\"" + additionalStyle.qstring() + "\"";
234                 }
235                 else {
236                     for (unsigned int i=0; i<length; i++) {
237                         AttributeImpl *attr = attrs->attributeItem(i);
238                         DOMString value = attr->value();
239                         if (attr->name() == styleAttr && additionalStyle.length() > 0)
240                             value += "; " + additionalStyle;
241                         // FIXME: Handle case where value has illegal characters in it, like "
242                         // FIXME: Namespaces! XML! Ack!
243                         markup += " " + attr->name().localName().qstring() + "=\"" + value.qstring() + "\"";
244                     }
245                 }
246             }
247             markup += node->isHTMLElement() ? ">" : "/>";
248             return markup;
249         }
250     }
251 }
252
253 static QString endMarkup(const NodeImpl *node)
254 {
255     bool hasEndTag = node->isElementNode();
256     if (node->isHTMLElement()) {
257         const HTMLElementImpl* htmlElt = static_cast<const HTMLElementImpl*>(node);
258         hasEndTag = (htmlElt->endTagRequirement() != TagStatusForbidden);
259     }
260     if (hasEndTag)
261         return "</" + node->nodeName().qstring() + ">";
262     return "";
263 }
264
265 static QString markup(const NodeImpl *startNode, bool onlyIncludeChildren, bool includeSiblings, QPtrList<NodeImpl> *nodes)
266 {
267     // Doesn't make sense to only include children and include siblings.
268     ASSERT(!(onlyIncludeChildren && includeSiblings));
269     QString me = "";
270     for (const NodeImpl *current = startNode; current != NULL; current = includeSiblings ? current->nextSibling() : NULL) {
271         if (!onlyIncludeChildren) {
272             if (nodes) {
273                 nodes->append(current);
274             }
275             me += startMarkup(current, 0, DoNotAnnotateForInterchange, 0);
276         }
277         
278         bool container = true;
279         if (current->isHTMLElement()) {
280             const HTMLElementImpl* h = static_cast<const HTMLElementImpl*>(current);
281             container = h->endTagRequirement() != TagStatusForbidden;
282         }
283         if (container) {
284             // print children
285             if (NodeImpl *n = current->firstChild()) {
286                 me += markup(n, false, true, nodes);
287             }
288             // Print my ending tag
289             if (!onlyIncludeChildren) {
290                 me += endMarkup(current);
291             }
292         }
293     }
294     return me;
295 }
296
297 static void completeURLs(NodeImpl *node, const QString &baseURL)
298 {
299     NodeImpl *end = node->traverseNextSibling();
300     for (NodeImpl *n = node; n != end; n = n->traverseNextNode()) {
301         if (n->nodeType() == Node::ELEMENT_NODE) {
302             ElementImpl *e = static_cast<ElementImpl *>(n);
303             NamedAttrMapImpl *attrs = e->attributes();
304             unsigned length = attrs->length();
305             for (unsigned i = 0; i < length; i++) {
306                 AttributeImpl *attr = attrs->attributeItem(i);
307                 if (e->isURLAttribute(attr))
308                     e->setAttribute(attr->name(), KURL(baseURL, attr->value().qstring()).url());
309             }
310         }
311     }
312 }
313
314 QString createMarkup(const RangeImpl *range, QPtrList<NodeImpl> *nodes, EAnnotateForInterchange annotate)
315 {
316     if (!range || range->isDetached())
317         return QString();
318
319     static const QString interchangeNewlineString = QString("<br class=\"") + AppleInterchangeNewline + "\">";
320
321     int exceptionCode = 0;
322     NodeImpl *commonAncestor = range->commonAncestorContainer(exceptionCode);
323     ASSERT(exceptionCode == 0);
324
325     DocumentImpl *doc = commonAncestor->getDocument();
326     doc->updateLayout();
327
328     NodeImpl *commonAncestorBlock = 0;
329     if (commonAncestor != 0) {
330         commonAncestorBlock = commonAncestor->enclosingBlockFlowElement();
331     }
332     if (commonAncestorBlock == 0) {
333         return "";    
334     }
335
336     QStringList markups;
337     NodeImpl *pastEnd = range->pastEndNode();
338     NodeImpl *lastClosed = 0;
339     QPtrList<NodeImpl> ancestorsToClose;
340
341     // calculate the "default style" for this markup
342     Position pos(doc->documentElement(), 0);
343     CSSComputedStyleDeclarationImpl *computedStyle = pos.computedStyle();
344     computedStyle->ref();
345     CSSMutableStyleDeclarationImpl *defaultStyle = computedStyle->copyInheritableProperties();
346     computedStyle->deref();
347     defaultStyle->ref();
348     
349     NodeImpl *startNode = range->startNode();
350     VisiblePosition visibleStart(range->startPosition(), VP_DEFAULT_AFFINITY);
351     VisiblePosition visibleEnd(range->endPosition(), VP_DEFAULT_AFFINITY);
352     if (!inSameBlock(visibleStart, visibleStart.next())) {
353         if (visibleStart == visibleEnd.previous()) {
354             defaultStyle->deref();
355             return interchangeNewlineString;
356         }
357         markups.append(interchangeNewlineString);
358         startNode = startNode->traverseNextNode();
359     }
360     
361     // Iterate through the nodes of the range.
362     NodeImpl *next;
363     for (NodeImpl *n = startNode; n != pastEnd; n = next) {
364         next = n->traverseNextNode();
365
366         if (n->isBlockFlow() && next == pastEnd) {
367             // Don't write out an empty block.
368             continue;
369         }
370         
371         // Add the node to the markup.
372         if (n->renderer()) {
373             markups.append(startMarkup(n, range, annotate, defaultStyle));
374             if (nodes) {
375                 nodes->append(n);
376             }
377         }
378         
379         if (n->firstChild() == 0) {
380             // Node has no children, add its close tag now.
381             if (n->renderer()) {
382                 markups.append(endMarkup(n));
383                 lastClosed = n;
384             }
385             
386             // Check if the node is the last leaf of a tree.
387             if (n->nextSibling() == 0 || next == pastEnd) {
388                 if (!ancestorsToClose.isEmpty()) {
389                     // Close up the ancestors.
390                     while (NodeImpl *ancestor = ancestorsToClose.last()) {
391                         if (next != pastEnd && ancestor == next->parentNode()) {
392                             break;
393                         }
394                         // Not at the end of the range, close ancestors up to sibling of next node.
395                         markups.append(endMarkup(ancestor));
396                         lastClosed = ancestor;
397                         ancestorsToClose.removeLast();
398                     }
399                 } else {
400                     // No ancestors to close, but need to add ancestors not in range since next node is in another tree. 
401                     if (next != pastEnd) {
402                         NodeImpl *nextParent = next->parentNode();
403                         if (n != nextParent) {
404                             for (NodeImpl *parent = n->parent(); parent != 0 && parent != nextParent; parent = parent->parentNode()) {
405                                 markups.prepend(startMarkup(parent, range, annotate, defaultStyle));
406                                 markups.append(endMarkup(parent));
407                                 if (nodes) {
408                                     nodes->append(parent);
409                                 }                            
410                                 lastClosed = parent;
411                             }
412                         }
413                     }
414                 }
415             }
416         } else if (n->renderer()) {
417             // Node is an ancestor, set it to close eventually.
418             ancestorsToClose.append(n);
419         }
420     }
421     
422     NodeImpl *rangeStartNode = range->startNode();
423     int rangeStartOffset = range->startOffset(exceptionCode);
424     ASSERT(exceptionCode == 0);
425     
426     // Add ancestors up to the common ancestor block so inline ancestors such as FONT and B are part of the markup.
427     if (lastClosed) {
428         for (NodeImpl *ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
429             if (RangeImpl::compareBoundaryPoints(ancestor, 0, rangeStartNode, rangeStartOffset) >= 0) {
430                 // we have already added markup for this node
431                 continue;
432             }
433             bool breakAtEnd = false;
434             if (commonAncestorBlock == ancestor) {
435                 // Include ancestors that are required to retain the appearance of the copied markup.
436                 if (ancestor->hasTagName(preTag) || ancestor->hasTagName(tableTag) ||
437                     ancestor->hasTagName(olTag) || ancestor->hasTagName(ulTag)) {
438                     breakAtEnd = true;
439                 } else {
440                     break;
441                 }
442             }
443             markups.prepend(startMarkup(ancestor, range, annotate, defaultStyle));
444             markups.append(endMarkup(ancestor));
445             if (nodes) {
446                 nodes->append(ancestor);
447             }        
448             if (breakAtEnd) {
449                 break;
450             }
451         }
452     }
453
454     if (annotate) {
455         if (!inSameBlock(visibleEnd, visibleEnd.previous()))
456             markups.append(interchangeNewlineString);
457     }
458
459     // Retain the Mail quote level by including all ancestor mail block quotes.
460     for (NodeImpl *ancestor = commonAncestorBlock; ancestor; ancestor = ancestor->parentNode()) {
461         if (isMailBlockquote(ancestor)) {
462             markups.prepend(startMarkup(ancestor, range, annotate, defaultStyle));
463             markups.append(endMarkup(ancestor));
464         }
465     }
466     
467     // add in the "default style" for this markup
468     // FIXME: Handle case where value has illegal characters in it, like "
469     QString openTag = QString("<span class=\"") + AppleStyleSpanClass + "\" style=\"" + defaultStyle->cssText().qstring() + "\">";
470     markups.prepend(openTag);
471     markups.append("</span>");
472     defaultStyle->deref();
473
474     return markups.join("");
475 }
476
477 DocumentFragmentImpl *createFragmentFromMarkup(DocumentImpl *document, const QString &markup, const QString &baseURL)
478 {
479     // FIXME: What if the document element is not an HTML element?
480     HTMLElementImpl *element = static_cast<HTMLElementImpl *>(document->documentElement());
481
482     DocumentFragmentImpl *fragment = element->createContextualFragment(markup);
483     ASSERT(fragment);
484
485     if (!baseURL.isEmpty() && baseURL != document->baseURL())
486         completeURLs(fragment, baseURL);
487
488     return fragment;
489 }
490
491 QString createMarkup(const DOM::NodeImpl *node, EChildrenOnly includeChildren,
492     QPtrList<DOM::NodeImpl> *nodes, EAnnotateForInterchange annotate)
493 {
494     ASSERT(annotate == DoNotAnnotateForInterchange); // annotation not yet implemented for this code path
495
496     // FIXME: We could take out this if statement if we had more time to test.
497     // I'm concerned that making this crash when the document is nil might be too risky a change at the moment.
498     DocumentImpl *doc = node->getDocument();
499     assert(doc);
500     if (doc)
501         doc->updateLayout();
502
503     return markup(node, includeChildren, false, nodes);
504 }
505
506 static void createParagraphContentsFromString(DOM::DocumentImpl *document, ElementImpl *paragraph, const QString &string)
507 {
508     int exceptionCode = 0;
509     if (string.isEmpty()) {
510         NodeImpl *placeHolder = createBlockPlaceholderElement(document);
511         paragraph->appendChild(placeHolder, exceptionCode);
512         ASSERT(exceptionCode == 0);
513         return;
514     }
515
516     assert(string.find('\n') == -1);
517
518     QStringList tabList = QStringList::split('\t', string, true);
519     QString tabText = "";
520     while (!tabList.isEmpty()) {
521         QString s = tabList.first();
522         tabList.pop_front();
523
524         // append the non-tab textual part
525         if (!s.isEmpty()) {
526             if (tabText != "") {
527                 paragraph->appendChild(createTabSpanElement(document, &tabText), exceptionCode);
528                 ASSERT(exceptionCode == 0);
529                 tabText = "";
530             }
531             NodeImpl *textNode = document->createTextNode(s);
532             paragraph->appendChild(textNode, exceptionCode);
533             ASSERT(exceptionCode == 0);
534         }
535
536         // there is a tab after every entry, except the last entry
537         // (if the last character is a tab, the list gets an extra empty entry)
538         if (!tabList.isEmpty()) {
539 //#ifdef COALESCE_TAB_SPANS
540 #if 1
541             tabText += '\t';
542 #else
543             paragraph->appendChild(createTabSpanElement(document), exceptionCode);
544             ASSERT(exceptionCode == 0);
545 #endif
546         } else if (tabText != "") {
547             paragraph->appendChild(createTabSpanElement(document, &tabText), exceptionCode);
548             ASSERT(exceptionCode == 0);
549         }
550     }
551 }
552
553 DOM::DocumentFragmentImpl *createFragmentFromText(DOM::DocumentImpl *document, const QString &text)
554 {
555     if (!document)
556         return 0;
557
558     DocumentFragmentImpl *fragment = document->createDocumentFragment();
559     fragment->ref();
560     
561     if (!text.isEmpty()) {
562         QString string = text;
563
564         // FIXME: Wrap the NBSP's in a span that says "converted space".
565         int offset = 0;
566         int stringLength = string.length();
567         while (1) {
568             // FIXME: This only converts plain old spaces, and does not
569             // deal with more exotic whitespace. Note that we want to 
570             // leave newlines and returns alone at this point anyway, 
571             // since those are handled specially later.
572             int spacesPos = string.find("  ", offset);
573             if (spacesPos < 0)
574                 break;
575
576             // Found two adjoining spaces.
577             // Now, lookahead to see if these two spaces are followed by:
578             //   1. another space and then a non-space
579             //   2. another space and then the end of the string
580             // If either 1 or 2 is true, replace the three spaces found with nbsp+nbsp+space, 
581             // otherwise, replace the first two spaces with nbsp+space.
582             if ((spacesPos + 3 < stringLength && string[spacesPos + 2] == ' ' && string[spacesPos + 3] != ' ')
583                     || (spacesPos + 3 == stringLength && string[spacesPos + 2] == ' ')) {
584                 string.replace(spacesPos, 3, "\xA0\xA0 ");
585             } else {
586                 string.replace(spacesPos, 2, "\xA0 ");
587             }
588             offset = spacesPos + 2;
589         }
590
591         // Break string into paragraphs. Extra line breaks turn into empty paragraphs.
592         string.replace(QString("\r\n"), "\n");
593         string.replace('\r', '\n');
594         QStringList list = QStringList::split('\n', string, true); // true gets us empty strings in the list
595         while (!list.isEmpty()) {
596             QString s = list.first();
597             list.pop_front();
598
599             int exceptionCode = 0;
600             ElementImpl *element;
601             if (s.isEmpty() && list.isEmpty()) {
602                 // For last line, use the "magic BR" rather than a P.
603                 element = document->createElementNS(xhtmlNamespaceURI, "br", exceptionCode);
604                 ASSERT(exceptionCode == 0);
605                 element->ref();
606                 element->setAttribute(classAttr, AppleInterchangeNewline);            
607             } else {
608                 element = createDefaultParagraphElement(document);
609                 element->ref();
610                 createParagraphContentsFromString(document, element, s);
611             }
612             fragment->appendChild(element, exceptionCode);
613             ASSERT(exceptionCode == 0);
614             element->deref();
615         }
616     }
617     
618     // Trick to get the fragment back to the floating state, with 0
619     // refs but not destroyed.
620     fragment->setParent(document);
621     fragment->deref();
622     fragment->setParent(0);
623     
624     return fragment;
625 }
626
627 }