Reviewed by Mitz.
[WebKit-https.git] / WebCore / editing / markup.cpp
index fffa6e6..438da6d 100644 (file)
 #include "config.h"
 #include "markup.h"
 
+#include "CDATASection.h"
 #include "CSSComputedStyleDeclaration.h"
+#include "CSSPropertyNames.h"
+#include "CSSRule.h"
+#include "CSSRuleList.h"
+#include "CSSStyleRule.h"
+#include "cssstyleselector.h"
 #include "Comment.h"
+#include "DeleteButtonController.h"
+#include "DeprecatedStringList.h"
 #include "Document.h"
 #include "DocumentFragment.h"
 #include "DocumentType.h"
+#include "Editor.h"
+#include "Frame.h"
 #include "HTMLElement.h"
 #include "HTMLNames.h"
 #include "InlineTextBox.h"
@@ -38,6 +48,7 @@
 #include "Logging.h"
 #include "ProcessingInstruction.h"
 #include "Range.h"
+#include "Selection.h"
 #include "htmlediting.h"
 #include "visible_units.h"
 #include "TextIterator.h"
@@ -50,7 +61,7 @@ using namespace HTMLNames;
 
 static inline bool shouldSelfClose(const Node *node);
 
-static DeprecatedString escapeTextForMarkup(const DeprecatedString &in)
+static DeprecatedString escapeTextForMarkup(const DeprecatedString &in, bool isAttributeValue)
 {
     DeprecatedString s = "";
 
@@ -66,6 +77,12 @@ static DeprecatedString escapeTextForMarkup(const DeprecatedString &in)
             case '>':
                 s += ">";
                 break;
+            case '"':
+                if (isAttributeValue) {
+                    s += """;
+                    break;
+                }
+                // fall through
             default:
                 s += in[i];
         }
@@ -108,6 +125,34 @@ static DeprecatedString renderedText(const Node *node, const Range *range)
     return plainText(&r);
 }
 
+static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesForElement(Element* element, bool authorOnly = true)
+{
+    RefPtr<CSSMutableStyleDeclaration> style = new CSSMutableStyleDeclaration();
+    RefPtr<CSSRuleList> matchedRules = element->document()->styleSelector()->styleRulesForElement(element, authorOnly);
+    if (matchedRules) {
+        for (unsigned i = 0; i < matchedRules->length(); i++) {
+            if (matchedRules->item(i)->type() == CSSRule::STYLE_RULE) {
+                RefPtr<CSSMutableStyleDeclaration> s = static_cast<CSSStyleRule*>(matchedRules->item(i))->style();
+                style->merge(s.get(), true);
+            }
+        }
+    }
+    
+    return style.release();
+}
+
+static void removeEnclosingMailBlockquoteStyle(CSSMutableStyleDeclaration* style, Node* node)
+{
+    Node* blockquote = nearestMailBlockquote(node);
+    if (!blockquote || !blockquote->parentNode())
+        return;
+            
+    RefPtr<CSSMutableStyleDeclaration> parentStyle = Position(blockquote->parentNode(), 0).computedStyle()->copyInheritableProperties();
+    RefPtr<CSSMutableStyleDeclaration> blockquoteStyle = Position(blockquote, 0).computedStyle()->copyInheritableProperties();
+    parentStyle->diff(blockquoteStyle.get());
+    blockquoteStyle->diff(style);
+}
+
 static DeprecatedString startMarkup(const Node *node, const Range *range, EAnnotateForInterchange annotate, CSSMutableStyleDeclaration *defaultStyle)
 {
     bool documentIsHTML = node->document()->isHTMLDocument();
@@ -122,12 +167,19 @@ static DeprecatedString startMarkup(const Node *node, const Range *range, EAnnot
                         || parent->hasTagName(xmpTag))
                     return stringValueForRange(node, range).deprecatedString();
             }
-            DeprecatedString markup = annotate ? escapeTextForMarkup(renderedText(node, range)) : escapeTextForMarkup(stringValueForRange(node, range).deprecatedString());            
+            bool useRenderedText = annotate && !enclosingNodeWithTag(const_cast<Node*>(node), selectTag);
+            
+            DeprecatedString markup = useRenderedText ? escapeTextForMarkup(renderedText(node, range), false) : escapeTextForMarkup(stringValueForRange(node, range).deprecatedString(), false);
             if (defaultStyle) {
                 Node *element = node->parentNode();
                 if (element) {
                     RefPtr<CSSComputedStyleDeclaration> computedStyle = Position(element, 0).computedStyle();
                     RefPtr<CSSMutableStyleDeclaration> style = computedStyle->copyInheritableProperties();
+                    // Styles that Mail blockquotes contribute should only be placed on the Mail blockquote, to help
+                    // us differentiate those styles from ones that the user has applied.  This helps us
+                    // get the color of content pasted into blockquotes right.
+                    removeEnclosingMailBlockquoteStyle(style.get(), const_cast<Node*>(node));
+                    
                     defaultStyle->diff(style.get());
                     if (style->length() > 0) {
                         // FIXME: Handle case where style->cssText() has illegal characters in it, like "
@@ -154,13 +206,20 @@ static DeprecatedString startMarkup(const Node *node, const Range *range, EAnnot
         case Node::PROCESSING_INSTRUCTION_NODE:
             return static_cast<const ProcessingInstruction*>(node)->toString().deprecatedString();
         case Node::ELEMENT_NODE: {
-            DeprecatedString markup = QChar('<');
+            DeprecatedString markup = DeprecatedChar('<');
             const Element* el = static_cast<const Element*>(node);
             markup += el->nodeNamePreservingCase().deprecatedString();
             String additionalStyle;
-            if (defaultStyle && el->isHTMLElement()) {
+            if (defaultStyle && el->isHTMLElement() && !isMailBlockquote(node)) {
                 RefPtr<CSSComputedStyleDeclaration> computedStyle = Position(const_cast<Element*>(el), 0).computedStyle();
                 RefPtr<CSSMutableStyleDeclaration> style = computedStyle->copyInheritableProperties();
+                style->merge(styleFromMatchedRulesForElement(const_cast<Element*>(el)).get());
+                
+                // Styles that Mail blockquotes contribute should only be placed on the Mail blockquote, to help
+                // us differentiate those styles from ones that the user has applied.  This helps us
+                // get the color of content pasted into blockquotes right.
+                removeEnclosingMailBlockquoteStyle(style.get(), const_cast<Node*>(node));
+                
                 defaultStyle->diff(style.get());
                 if (style->length() > 0) {
                     CSSMutableStyleDeclaration *inlineStyleDecl = static_cast<const HTMLElement*>(el)->inlineStyleDecl();
@@ -171,25 +230,26 @@ static DeprecatedString startMarkup(const Node *node, const Range *range, EAnnot
             }
             NamedAttrMap *attrs = el->attributes();
             unsigned length = attrs->length();
-            if (length == 0 && additionalStyle.length() > 0) {
-                // FIXME: Handle case where additionalStyle has illegal characters in it, like "
-                markup += " " +  styleAttr.localName().deprecatedString() + "=\"" + additionalStyle.deprecatedString() + "\"";
-            }
-            else {
-                for (unsigned int i=0; i<length; i++) {
-                    Attribute *attr = attrs->attributeItem(i);
-                    String value = attr->value();
-                    if (attr->name() == styleAttr && additionalStyle.length() > 0)
-                        value += "; " + additionalStyle;
-                    // FIXME: Handle case where value has illegal characters in it, like "
-                    if (documentIsHTML)
-                        markup += " " + attr->name().localName().deprecatedString();
-                    else
-                        markup += " " + attr->name().toString().deprecatedString();
-                    markup += "=\"" + escapeTextForMarkup(value.deprecatedString()) + "\"";
+
+            for (unsigned int i = 0; i < length; i++) {
+                Attribute *attr = attrs->attributeItem(i);
+                String value = attr->value();
+                if (attr->name() == styleAttr && additionalStyle.length() > 0) {
+                    value += "; " + additionalStyle;
+                    additionalStyle = "";
                 }
+                // FIXME: Handle case where value has illegal characters in it, like "
+                if (documentIsHTML)
+                    markup += " " + attr->name().localName().deprecatedString();
+                else
+                    markup += " " + attr->name().toString().deprecatedString();
+                markup += "=\"" + escapeTextForMarkup(value.deprecatedString(), true) + "\"";
             }
             
+            if (additionalStyle.length() > 0)
+                // FIXME: Handle case where additionalStyle has illegal characters in it, like "
+                markup += " " +  styleAttr.localName().deprecatedString() + "=\"" + additionalStyle.deprecatedString() + "\"";
+            
             if (shouldSelfClose(el)) {
                 if (el->isHTMLElement())
                     markup += " "; // XHTML 1.0 <-> HTML compatibility.
@@ -199,8 +259,9 @@ static DeprecatedString startMarkup(const Node *node, const Range *range, EAnnot
             
             return markup;
         }
-        case Node::ATTRIBUTE_NODE:
         case Node::CDATA_SECTION_NODE:
+            return static_cast<const CDATASection*>(node)->toString().deprecatedString();
+        case Node::ATTRIBUTE_NODE:
         case Node::ENTITY_NODE:
         case Node::ENTITY_REFERENCE_NODE:
         case Node::NOTATION_NODE:
@@ -237,17 +298,17 @@ static inline bool shouldSelfClose(const Node *node)
 
 static DeprecatedString endMarkup(const Node *node)
 {
-    if (node->isElementNode() && !shouldSelfClose(node) && !doesHTMLForbidEndTag(node))
+    if (node->isElementNode() && !shouldSelfClose(node) && (node->hasChildNodes() || !doesHTMLForbidEndTag(node)))
         return "</" + static_cast<const Element*>(node)->nodeNamePreservingCase().deprecatedString() + ">";
     return "";
 }
 
-static DeprecatedString markup(const Node *startNode, bool onlyIncludeChildren, bool includeSiblings, DeprecatedPtrList<Node> *nodes)
+static DeprecatedString markup(Node* startNode, bool onlyIncludeChildren, bool includeSiblings, Vector<Node*> *nodes)
 {
     // Doesn't make sense to only include children and include siblings.
     ASSERT(!(onlyIncludeChildren && includeSiblings));
     DeprecatedString me = "";
-    for (const Node *current = startNode; current != NULL; current = includeSiblings ? current->nextSibling() : NULL) {
+    for (Node* current = startNode; current != NULL; current = includeSiblings ? current->nextSibling() : NULL) {
         if (!onlyIncludeChildren) {
             if (nodes)
                 nodes->append(current);
@@ -282,7 +343,15 @@ static void completeURLs(Node *node, const DeprecatedString &baseURL)
     }
 }
 
-DeprecatedString createMarkup(const Range *range, DeprecatedPtrList<Node> *nodes, EAnnotateForInterchange annotate)
+static bool needInterchangeNewlineAfter(const VisiblePosition& v)
+{
+    VisiblePosition next = v.next();
+    return isEndOfParagraph(v) && isStartOfParagraph(next) && !next.deepEquivalent().upstream().node()->hasTagName(brTag);
+}
+
+// FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange? 
+// FIXME: At least, annotation and style info should probably not be included in range.markupString()
+DeprecatedString createMarkup(const Range *range, Vector<Node*>* nodes, EAnnotateForInterchange annotate)
 {
     if (!range || range->isDetached())
         return DeprecatedString();
@@ -297,87 +366,113 @@ DeprecatedString createMarkup(const Range *range, DeprecatedPtrList<Node> *nodes
     ASSERT(ec == 0);
 
     Document *doc = commonAncestor->document();
+    // disable the delete button so it's elements are not serialized into the markup
+    doc->frame()->editor()->deleteButtonController()->disable();
     doc->updateLayoutIgnorePendingStylesheets();
 
     Node *commonAncestorBlock = 0;
-    if (commonAncestor)
-        commonAncestorBlock = commonAncestor->enclosingBlockFlowElement();
-    if (!commonAncestorBlock)
-        return "";
+    if (commonAncestor) {
+        commonAncestorBlock = enclosingBlock(commonAncestor);
+        if (commonAncestorBlock && commonAncestorBlock->hasTagName(tbodyTag)) {
+            Node* table = commonAncestorBlock->parentNode();
+            while (table && !table->hasTagName(tableTag))
+                table = table->parentNode();
+            if (table)
+                commonAncestorBlock = table;
+        }
+    }
 
     DeprecatedStringList markups;
     Node *pastEnd = range->pastEndNode();
     Node *lastClosed = 0;
-    DeprecatedPtrList<Node> ancestorsToClose;
+    Vector<Node*> ancestorsToClose;
 
-    // calculate the "default style" for this markup
+    // Calculate the "default style" for this markup.
     Position pos(doc->documentElement(), 0);
     RefPtr<CSSComputedStyleDeclaration> computedStyle = pos.computedStyle();
     RefPtr<CSSMutableStyleDeclaration> defaultStyle = computedStyle->copyInheritableProperties();
     
-    Node *startNode = range->startNode();
+    NodestartNode = range->startNode();
     VisiblePosition visibleStart(range->startPosition(), VP_DEFAULT_AFFINITY);
     VisiblePosition visibleEnd(range->endPosition(), VP_DEFAULT_AFFINITY);
-    if (!inSameBlock(visibleStart, visibleStart.next())) {
-        if (visibleStart == visibleEnd.previous())
+    if (annotate && needInterchangeNewlineAfter(visibleStart)) {
+        if (visibleStart == visibleEnd.previous()) {
+            doc->frame()->editor()->deleteButtonController()->enable();
             return interchangeNewlineString;
+        }
+
         markups.append(interchangeNewlineString);
-        startNode = startNode->traverseNextNode();
+        startNode = visibleStart.next().deepEquivalent().node();
     }
-    
-    // Iterate through the nodes of the range.
+
     Node *next;
     for (Node *n = startNode; n != pastEnd; n = next) {
         next = n->traverseNextNode();
+        bool skipDescendants = false;
+        bool addMarkupForNode = true;
+        
+        if (!n->renderer() && !enclosingNodeWithTag(n, selectTag)) {
+            skipDescendants = true;
+            addMarkupForNode = false;
+            next = n->traverseNextSibling();
+            // Don't skip over pastEnd.
+            if (pastEnd && pastEnd->isDescendantOf(n))
+                next = pastEnd;
+        }
 
-        if (n->isBlockFlow() && next == pastEnd)
-            // Don't write out an empty block.
+        if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd)
+            // Don't write out empty block containers that aren't fully selected.
             continue;
         
         // Add the node to the markup.
-        if (n->renderer()) {
+        if (addMarkupForNode) {
             markups.append(startMarkup(n, range, annotate, defaultStyle.get()));
             if (nodes)
                 nodes->append(n);
         }
         
-        if (n->firstChild() == 0) {
-            // Node has no children, add its close tag now.
-            if (n->renderer()) {
+        if (n->firstChild() == 0 || skipDescendants) {
+            // Node has no children, or we are skipping it's descendants, add its close tag now.
+            if (addMarkupForNode) {
                 markups.append(endMarkup(n));
                 lastClosed = n;
             }
             
             // Check if the node is the last leaf of a tree.
-            if (n->nextSibling() == 0 || next == pastEnd) {
+            if (!n->nextSibling() || next == pastEnd) {
                 if (!ancestorsToClose.isEmpty()) {
                     // Close up the ancestors.
-                    while (Node *ancestor = ancestorsToClose.last()) {
-                        if (next != pastEnd && ancestor == next->parentNode())
+                    do {
+                        Node *ancestor = ancestorsToClose.last();
+                        if (next != pastEnd && next->isDescendantOf(ancestor))
                             break;
                         // Not at the end of the range, close ancestors up to sibling of next node.
                         markups.append(endMarkup(ancestor));
                         lastClosed = ancestor;
                         ancestorsToClose.removeLast();
-                    }
-                } else {
-                    // No ancestors to close, but need to add ancestors not in range since next node is in another tree. 
-                    if (next != pastEnd) {
-                        Node *nextParent = next->parentNode();
-                        if (n != nextParent) {
-                            for (Node *parent = n->parent(); parent != 0 && parent != nextParent; parent = parent->parentNode()) {
-                                markups.prepend(startMarkup(parent, range, annotate, defaultStyle.get()));
-                                markups.append(endMarkup(parent));
-                                if (nodes)
-                                    nodes->append(parent);
-                                lastClosed = parent;
-                            }
-                        }
+                    } while (!ancestorsToClose.isEmpty());
+                }
+                
+                // Surround the currently accumulated markup with markup for ancestors we never opened as we leave the subtree(s) rooted at those ancestors.
+                Node* nextParent = next ? next->parentNode() : 0;
+                if (next != pastEnd && n != nextParent) {
+                    Node* lastAncestorClosedOrSelf = n->isDescendantOf(lastClosed) ? lastClosed : n;
+                    for (Node *parent = lastAncestorClosedOrSelf->parent(); parent != 0 && parent != nextParent; parent = parent->parentNode()) {
+                        // All ancestors that aren't in the ancestorsToClose list should either be a) unrendered:
+                        if (!parent->renderer())
+                            continue;
+                        // or b) ancestors that we never encountered during a pre-order traversal starting at startNode:
+                        ASSERT(startNode->isDescendantOf(parent));
+                        markups.prepend(startMarkup(parent, range, annotate, defaultStyle.get()));
+                        markups.append(endMarkup(parent));
+                        if (nodes)
+                            nodes->append(parent);
+                        lastClosed = parent;
                     }
                 }
             }
-        } else if (n->renderer())
-            // Node is an ancestor, set it to close eventually.
+        } else if (addMarkupForNode && !skipDescendants)
+            // We added markup for this node, and we're descending into it.  Set it to close eventually.
             ancestorsToClose.append(n);
     }
     
@@ -395,12 +490,13 @@ DeprecatedString createMarkup(const Range *range, DeprecatedPtrList<Node> *nodes
             bool breakAtEnd = false;
             if (commonAncestorBlock == ancestor) {
                 // Include ancestors that are required to retain the appearance of the copied markup.
-                if (ancestor->hasTagName(listingTag)
+                if (annotate &&
+                    (ancestor->hasTagName(listingTag)
                         || ancestor->hasTagName(olTag)
                         || ancestor->hasTagName(preTag)
                         || ancestor->hasTagName(tableTag)
                         || ancestor->hasTagName(ulTag)
-                        || ancestor->hasTagName(xmpTag)) {
+                        || ancestor->hasTagName(xmpTag))) {
                     breakAtEnd = true;
                 } else
                     break;
@@ -415,25 +511,46 @@ DeprecatedString createMarkup(const Range *range, DeprecatedPtrList<Node> *nodes
         }
     }
 
-    if (annotate) {
-        if (!inSameBlock(visibleEnd, visibleEnd.previous()))
-            markups.append(interchangeNewlineString);
-    }
+    if (annotate && needInterchangeNewlineAfter(visibleEnd.previous()))
+        markups.append(interchangeNewlineString);
 
     // Retain the Mail quote level by including all ancestor mail block quotes.
-    for (Node *ancestor = commonAncestorBlock; ancestor; ancestor = ancestor->parentNode()) {
-        if (isMailBlockquote(ancestor)) {
-            markups.prepend(startMarkup(ancestor, range, annotate, defaultStyle.get()));
-            markups.append(endMarkup(ancestor));
+    if (annotate) {
+        for (Node *ancestor = commonAncestorBlock; ancestor; ancestor = ancestor->parentNode()) {
+            if (isMailBlockquote(ancestor)) {
+                markups.prepend(startMarkup(ancestor, range, annotate, defaultStyle.get()));
+                markups.append(endMarkup(ancestor));
+            }
         }
     }
     
+    // FIXME: Do this for all fully selected blocks, not just a body.
+    Node* root = range->startPosition().node();
+    while (root && !root->hasTagName(bodyTag))
+        root = root->parentNode();
+    if (root && *Selection::selectionFromContentsOfNode(root).toRange() == *range) {
+        CSSMutableStyleDeclaration* inlineStyleDecl = static_cast<HTMLElement*>(root)->inlineStyleDecl();
+        RefPtr<CSSMutableStyleDeclaration> style = inlineStyleDecl ? inlineStyleDecl->copy() : new CSSMutableStyleDeclaration();
+        style->merge(styleFromMatchedRulesForElement(static_cast<Element*>(root)).get());
+        
+        defaultStyle->diff(style.get());
+        
+        // Bring the background attribute over, but not as an attribute because a background attribute on a div
+        // appears to have no effect.
+        if (!style->getPropertyCSSValue(CSS_PROP_BACKGROUND_IMAGE) && static_cast<Element*>(root)->hasAttribute(backgroundAttr))
+            style->setProperty(CSS_PROP_BACKGROUND_IMAGE, "url('" + static_cast<Element*>(root)->getAttribute(backgroundAttr) + "')");
+        
+        markups.prepend("<div style='" + style->cssText().deprecatedString() + "'>");
+        markups.append("</div>");
+    }
+    
     // add in the "default style" for this markup
     // FIXME: Handle case where value has illegal characters in it, like "
     DeprecatedString openTag = DeprecatedString("<span class=\"") + AppleStyleSpanClass + "\" style=\"" + defaultStyle->cssText().deprecatedString() + "\">";
     markups.prepend(openTag);
     markups.append("</span>");
 
+    doc->frame()->editor()->deleteButtonController()->enable();
     return markups.join("");
 }
 
@@ -452,16 +569,24 @@ PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const
     return fragment.release();
 }
 
-DeprecatedString createMarkup(const WebCore::Node *node, EChildrenOnly includeChildren,
-    DeprecatedPtrList<WebCore::Node> *nodes, EAnnotateForInterchange annotate)
+DeprecatedString createMarkup(const Node* node, EChildrenOnly includeChildren,
+    Vector<Node*>* nodes, EAnnotateForInterchange annotate)
 {
     ASSERT(annotate == DoNotAnnotateForInterchange); // annotation not yet implemented for this code path
+    // disable the delete button so it's elements are not serialized into the markup
+    if (node->document()->frame())
+        node->document()->frame()->editor()->deleteButtonController()->disable();
     node->document()->updateLayoutIgnorePendingStylesheets();
-    return markup(node, includeChildren, false, nodes);
+    DeprecatedString result(markup(const_cast<Node*>(node), includeChildren, false, nodes));
+    if (node->document()->frame())
+        node->document()->frame()->editor()->deleteButtonController()->enable();
+    return result;
 }
 
-static void createParagraphContentsFromString(WebCore::Document *document, Element *paragraph, const DeprecatedString &string)
+static void fillContainerFromString(ContainerNode* paragraph, const DeprecatedString& string)
 {
+    Document* document = paragraph->document();
+
     ExceptionCode ec = 0;
     if (string.isEmpty()) {
         paragraph->appendChild(createBlockPlaceholderElement(document), ec);
@@ -473,86 +598,126 @@ static void createParagraphContentsFromString(WebCore::Document *document, Eleme
 
     DeprecatedStringList tabList = DeprecatedStringList::split('\t', string, true);
     DeprecatedString tabText = "";
+    bool first = true;
     while (!tabList.isEmpty()) {
         DeprecatedString s = tabList.first();
         tabList.pop_front();
 
         // append the non-tab textual part
         if (!s.isEmpty()) {
-            if (tabText != "") {
+            if (!tabText.isEmpty()) {
                 paragraph->appendChild(createTabSpanElement(document, tabText), ec);
                 ASSERT(ec == 0);
                 tabText = "";
             }
-            RefPtr<Node> textNode = document->createTextNode(s);
-            rebalanceWhitespaceInTextNode(textNode.get(), 0, s.length());
+            RefPtr<Node> textNode = document->createTextNode(stringWithRebalancedWhitespace(s, first, tabList.isEmpty()));
             paragraph->appendChild(textNode.release(), ec);
             ASSERT(ec == 0);
         }
 
         // there is a tab after every entry, except the last entry
         // (if the last character is a tab, the list gets an extra empty entry)
-        if (!tabList.isEmpty()) {
+        if (!tabList.isEmpty())
             tabText += '\t';
-        } else if (tabText != "") {
+        else if (!tabText.isEmpty()) {
             paragraph->appendChild(createTabSpanElement(document, tabText), ec);
             ASSERT(ec == 0);
         }
+        
+        first = false;
     }
 }
 
-PassRefPtr<DocumentFragment> createFragmentFromText(Document *document, const DeprecatedString &text)
+PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String& text)
 {
-    if (!document)
+    if (!context)
         return 0;
 
+    Node* styleNode = context->startNode();
+    if (!styleNode) {
+        styleNode = context->startPosition().node();
+        if (!styleNode)
+            return 0;
+    }
+
+    Document* document = styleNode->document();
     RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
     
-    if (!text.isEmpty()) {
-        DeprecatedString string = text;
-
-        // Break string into paragraphs. Extra line breaks turn into empty paragraphs.
-        string.replace("\r\n", "\n");
-        string.replace('\r', '\n');
-        DeprecatedStringList list = DeprecatedStringList::split('\n', string, true); // true gets us empty strings in the list
-        while (!list.isEmpty()) {
-            DeprecatedString s = list.first();
-            list.pop_front();
-
-            ExceptionCode ec = 0;
+    if (text.isEmpty())
+        return fragment.release();
+
+    DeprecatedString string = text.deprecatedString();
+    string.replace("\r\n", "\n");
+    string.replace('\r', '\n');
+
+    ExceptionCode ec = 0;
+    RenderObject* renderer = styleNode->renderer();
+    if (renderer && renderer->style()->preserveNewline()) {
+        fragment->appendChild(document->createTextNode(string), ec);
+        ASSERT(ec == 0);
+        if (string.endsWith("\n")) {
             RefPtr<Element> element;
-            if (s.isEmpty() && list.isEmpty()) {
-                // For last line, use the "magic BR" rather than a P.
-                element = document->createElementNS(xhtmlNamespaceURI, "br", ec);
-                ASSERT(ec == 0);
-                element->setAttribute(classAttr, AppleInterchangeNewline);            
-            } else {
-                element = createDefaultParagraphElement(document);
-                createParagraphContentsFromString(document, element.get(), s);
-            }
-            fragment->appendChild(element.get(), ec);
+            element = document->createElementNS(xhtmlNamespaceURI, "br", ec);
+            ASSERT(ec == 0);
+            element->setAttribute(classAttr, AppleInterchangeNewline);            
+            fragment->appendChild(element.release(), ec);
             ASSERT(ec == 0);
         }
+        return fragment.release();
+    }
+
+    // A string with no newlines gets added inline, rather than being put into a paragraph.
+    if (string.find('\n') == -1) {
+        fillContainerFromString(fragment.get(), string);
+        return fragment.release();
+    }
+
+    // Break string into paragraphs. Extra line breaks turn into empty paragraphs.
+    DeprecatedStringList list = DeprecatedStringList::split('\n', string, true); // true gets us empty strings in the list
+    while (!list.isEmpty()) {
+        DeprecatedString s = list.first();
+        list.pop_front();
+
+        RefPtr<Element> element;
+        if (s.isEmpty() && list.isEmpty()) {
+            // For last line, use the "magic BR" rather than a P.
+            element = document->createElementNS(xhtmlNamespaceURI, "br", ec);
+            ASSERT(ec == 0);
+            element->setAttribute(classAttr, AppleInterchangeNewline);            
+        } else {
+            element = createDefaultParagraphElement(document);
+            fillContainerFromString(element.get(), s);
+        }
+        fragment->appendChild(element.release(), ec);
+        ASSERT(ec == 0);
     }
     return fragment.release();
 }
 
-PassRefPtr<DocumentFragment> createFragmentFromNodeList(Document *document, const DeprecatedPtrList<Node> &nodeList)
+PassRefPtr<DocumentFragment> createFragmentFromNodes(Document *document, const Vector<Node*>& nodes)
 {
     if (!document)
         return 0;
-    
+
+    // disable the delete button so it's elements are not serialized into the markup
+    if (document->frame())
+        document->frame()->editor()->deleteButtonController()->disable();
+
     RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
 
     ExceptionCode ec = 0;
-    for (DeprecatedPtrListIterator<Node> i(nodeList); i.current(); ++i) {
+    size_t size = nodes.size();
+    for (size_t i = 0; i < size; ++i) {
         RefPtr<Element> element = createDefaultParagraphElement(document);
-        element->appendChild(i.current(), ec);
+        element->appendChild(nodes[i], ec);
         ASSERT(ec == 0);
         fragment->appendChild(element.release(), ec);
         ASSERT(ec == 0);
     }
 
+    if (document->frame())
+        document->frame()->editor()->deleteButtonController()->enable();
+
     return fragment.release();
 }