markup.cpp 29.5 KB
Newer Older
darin's avatar
darin committed
1
/*
darin's avatar
darin committed
2
 * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.  All rights reserved.
darin's avatar
darin committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

mjs's avatar
mjs committed
26
#include "config.h"
darin's avatar
darin committed
27 28
#include "markup.h"

darin's avatar
darin committed
29
#include "CSSComputedStyleDeclaration.h"
justing's avatar
justing committed
30
#include "CSSPropertyNames.h"
justing's avatar
justing committed
31 32 33 34
#include "CSSRule.h"
#include "CSSRuleList.h"
#include "CSSStyleRule.h"
#include "cssstyleselector.h"
darin's avatar
darin committed
35
#include "Comment.h"
thatcher's avatar
thatcher committed
36
#include "DeleteButtonController.h"
37
#include "DeprecatedStringList.h"
darin's avatar
darin committed
38
#include "Document.h"
darin's avatar
darin committed
39
#include "DocumentFragment.h"
darin's avatar
darin committed
40
#include "DocumentType.h"
thatcher's avatar
thatcher committed
41 42
#include "Editor.h"
#include "Frame.h"
darin's avatar
darin committed
43
#include "HTMLElement.h"
darin's avatar
darin committed
44
#include "HTMLNames.h"
darin's avatar
darin committed
45
#include "InlineTextBox.h"
46
#include "KURL.h"
47
#include "Logging.h"
48
#include "ProcessingInstruction.h"
darin's avatar
darin committed
49
#include "Range.h"
justing's avatar
justing committed
50
#include "Selection.h"
darin's avatar
darin committed
51 52
#include "htmlediting.h"
#include "visible_units.h"
justing's avatar
justing committed
53
#include "TextIterator.h"
kocienda's avatar
kocienda committed
54

darin's avatar
darin committed
55 56
using namespace std;

darin's avatar
darin committed
57 58 59
namespace WebCore {

using namespace HTMLNames;
darin's avatar
darin committed
60

darin's avatar
darin committed
61
static inline bool shouldSelfClose(const Node *node);
eseidel's avatar
eseidel committed
62

darin's avatar
darin committed
63
static DeprecatedString escapeTextForMarkup(const DeprecatedString &in)
darin's avatar
darin committed
64
{
darin's avatar
darin committed
65
    DeprecatedString s = "";
darin's avatar
darin committed
66 67 68

    unsigned len = in.length();
    for (unsigned i = 0; i < len; ++i) {
darin's avatar
darin committed
69
        switch (in[i].unicode()) {
darin's avatar
darin committed
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
            case '&':
                s += "&amp;";
                break;
            case '<':
                s += "&lt;";
                break;
            case '>':
                s += "&gt;";
                break;
            default:
                s += in[i];
        }
    }

    return s;
}

darin's avatar
darin committed
87
static String stringValueForRange(const Node *node, const Range *range)
darin's avatar
darin committed
88
{
darin's avatar
darin committed
89
    String str = node->nodeValue().copy();
darin's avatar
darin committed
90
    if (range) {
darin's avatar
darin committed
91 92 93 94 95
        ExceptionCode ec;
        if (node == range->endContainer(ec))
            str.truncate(range->endOffset(ec));
        if (node == range->startContainer(ec))
            str.remove(0, range->startOffset(ec));
darin's avatar
darin committed
96
    }
eseidel's avatar
eseidel committed
97
    return str;
darin's avatar
darin committed
98 99
}

darin's avatar
darin committed
100
static DeprecatedString renderedText(const Node *node, const Range *range)
darin's avatar
darin committed
101 102
{
    if (!node->isTextNode())
darin's avatar
darin committed
103
        return DeprecatedString();
darin's avatar
darin committed
104

darin's avatar
darin committed
105
    ExceptionCode ec;
justing's avatar
justing committed
106
    const Text* textNode = static_cast<const Text*>(node);
darin's avatar
darin committed
107 108 109
    unsigned startOffset = 0;
    unsigned endOffset = textNode->length();

darin's avatar
darin committed
110 111 112 113
    if (range && node == range->startContainer(ec))
        startOffset = range->startOffset(ec);
    if (range && node == range->endContainer(ec))
        endOffset = range->endOffset(ec);
darin's avatar
darin committed
114
    
justing's avatar
justing committed
115 116 117 118
    Position start(const_cast<Node*>(node), startOffset);
    Position end(const_cast<Node*>(node), endOffset);
    Range r(node->document(), start, end);
    return plainText(&r);
darin's avatar
darin committed
119 120
}

justing's avatar
justing committed
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
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);
}

darin's avatar
darin committed
149
static DeprecatedString startMarkup(const Node *node, const Range *range, EAnnotateForInterchange annotate, CSSMutableStyleDeclaration *defaultStyle)
darin's avatar
darin committed
150
{
ggaren's avatar
ggaren committed
151
    bool documentIsHTML = node->document()->isHTMLDocument();
darin's avatar
darin committed
152
    switch (node->nodeType()) {
darin's avatar
darin committed
153
        case Node::TEXT_NODE: {
darin's avatar
darin committed
154 155 156 157 158
            if (Node* parent = node->parentNode()) {
                if (parent->hasTagName(listingTag)
                        || parent->hasTagName(preTag)
                        || parent->hasTagName(scriptTag)
                        || parent->hasTagName(styleTag)
harrison's avatar
harrison committed
159 160
                        || parent->hasTagName(textareaTag)
                        || parent->hasTagName(xmpTag))
darin's avatar
darin committed
161
                    return stringValueForRange(node, range).deprecatedString();
darin's avatar
darin committed
162
            }
justing's avatar
justing committed
163 164 165
            bool useRenderedText = annotate && !enclosingNodeWithTag(const_cast<Node*>(node), selectTag);
            
            DeprecatedString markup = useRenderedText ? escapeTextForMarkup(renderedText(node, range)) : escapeTextForMarkup(stringValueForRange(node, range).deprecatedString());            
kocienda's avatar
kocienda committed
166
            if (defaultStyle) {
darin's avatar
darin committed
167
                Node *element = node->parentNode();
kocienda's avatar
kocienda committed
168
                if (element) {
darin's avatar
darin committed
169 170
                    RefPtr<CSSComputedStyleDeclaration> computedStyle = Position(element, 0).computedStyle();
                    RefPtr<CSSMutableStyleDeclaration> style = computedStyle->copyInheritableProperties();
justing's avatar
justing committed
171 172 173 174 175
                    // 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));
                    
eseidel's avatar
eseidel committed
176
                    defaultStyle->diff(style.get());
kocienda's avatar
kocienda committed
177
                    if (style->length() > 0) {
darin's avatar
darin committed
178
                        // FIXME: Handle case where style->cssText() has illegal characters in it, like "
darin's avatar
darin committed
179
                        DeprecatedString openTag = DeprecatedString("<span class=\"") + AppleStyleSpanClass + "\" style=\"" + style->cssText().deprecatedString() + "\">";
kocienda's avatar
kocienda committed
180 181 182 183 184
                        markup = openTag + markup + "</span>";
                    }
                }            
            }
            return annotate ? convertHTMLTextToInterchangeFormat(markup) : markup;
darin's avatar
darin committed
185
        }
darin's avatar
darin committed
186
        case Node::COMMENT_NODE:
187
            return static_cast<const Comment*>(node)->toString().deprecatedString();
darin's avatar
darin committed
188
        case Node::DOCUMENT_NODE: {
189
            // Documents do not normally contain a docType as a child node, force it to print here instead.
darin's avatar
darin committed
190
            const DocumentType* docType = static_cast<const Document*>(node)->doctype();
191
            if (docType)
darin's avatar
darin committed
192
                return docType->toString().deprecatedString();
193 194
            return "";
        }
darin's avatar
darin committed
195
        case Node::DOCUMENT_FRAGMENT_NODE:
darin's avatar
darin committed
196
            return "";
darin's avatar
darin committed
197 198 199
        case Node::DOCUMENT_TYPE_NODE:
            return static_cast<const DocumentType*>(node)->toString().deprecatedString();
        case Node::PROCESSING_INSTRUCTION_NODE:
200
            return static_cast<const ProcessingInstruction*>(node)->toString().deprecatedString();
darin's avatar
darin committed
201
        case Node::ELEMENT_NODE: {
darin's avatar
darin committed
202
            DeprecatedString markup = DeprecatedChar('<');
darin's avatar
darin committed
203
            const Element* el = static_cast<const Element*>(node);
lweintraub's avatar
lweintraub committed
204
            markup += el->nodeNamePreservingCase().deprecatedString();
darin's avatar
darin committed
205
            String additionalStyle;
justing's avatar
justing committed
206
            if (defaultStyle && el->isHTMLElement() && !isMailBlockquote(node)) {
207
                RefPtr<CSSComputedStyleDeclaration> computedStyle = Position(const_cast<Element*>(el), 0).computedStyle();
darin's avatar
darin committed
208
                RefPtr<CSSMutableStyleDeclaration> style = computedStyle->copyInheritableProperties();
justing's avatar
justing committed
209 210 211 212 213 214 215
                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));
                
darin's avatar
darin committed
216 217
                defaultStyle->diff(style.get());
                if (style->length() > 0) {
218
                    CSSMutableStyleDeclaration *inlineStyleDecl = static_cast<const HTMLElement*>(el)->inlineStyleDecl();
darin's avatar
darin committed
219 220 221
                    if (inlineStyleDecl)
                        inlineStyleDecl->diff(style.get());
                    additionalStyle = style->cssText();
kocienda's avatar
kocienda committed
222
                }
darin's avatar
darin committed
223 224 225
            }
            NamedAttrMap *attrs = el->attributes();
            unsigned length = attrs->length();
justing's avatar
justing committed
226 227 228 229 230 231 232

            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 = "";
darin's avatar
darin committed
233
                }
justing's avatar
justing committed
234 235 236 237 238 239
                // 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()) + "\"";
darin's avatar
darin committed
240
            }
eseidel's avatar
eseidel committed
241
            
justing's avatar
justing committed
242 243 244 245
            if (additionalStyle.length() > 0)
                // FIXME: Handle case where additionalStyle has illegal characters in it, like "
                markup += " " +  styleAttr.localName().deprecatedString() + "=\"" + additionalStyle.deprecatedString() + "\"";
            
darin's avatar
darin committed
246 247
            if (shouldSelfClose(el)) {
                if (el->isHTMLElement())
eseidel's avatar
eseidel committed
248 249 250 251 252
                    markup += " "; // XHTML 1.0 <-> HTML compatibility.
                markup += "/>";
            } else
                markup += ">";
            
darin's avatar
darin committed
253 254
            return markup;
        }
darin's avatar
darin committed
255 256 257 258 259
        case Node::ATTRIBUTE_NODE:
        case Node::CDATA_SECTION_NODE:
        case Node::ENTITY_NODE:
        case Node::ENTITY_REFERENCE_NODE:
        case Node::NOTATION_NODE:
260
        case Node::XPATH_NAMESPACE_NODE:
darin's avatar
darin committed
261
            break;
darin's avatar
darin committed
262
    }
263
    return "";
darin's avatar
darin committed
264 265
}

darin's avatar
darin committed
266
static inline bool doesHTMLForbidEndTag(const Node *node)
darin's avatar
darin committed
267
{
268
    if (node->isHTMLElement()) {
darin's avatar
darin committed
269
        const HTMLElement* htmlElt = static_cast<const HTMLElement*>(node);
eseidel's avatar
eseidel committed
270
        return (htmlElt->endTagRequirement() == TagStatusForbidden);
darin's avatar
darin committed
271
    }
eseidel's avatar
eseidel committed
272 273 274 275
    return false;
}

// Rules of self-closure
darin's avatar
darin committed
276 277 278 279
// 1. No elements in HTML documents use the self-closing syntax.
// 2. Elements w/ children never self-close because they use a separate end tag.
// 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag.
// 4. Other elements self-close.
darin's avatar
darin committed
280
static inline bool shouldSelfClose(const Node *node)
eseidel's avatar
eseidel committed
281
{
ggaren's avatar
ggaren committed
282
    if (node->document()->isHTMLDocument())
eseidel's avatar
eseidel committed
283
        return false;
darin's avatar
darin committed
284 285 286 287 288
    if (node->hasChildNodes())
        return false;
    if (node->isHTMLElement() && !doesHTMLForbidEndTag(node))
        return false;
    return true;
eseidel's avatar
eseidel committed
289 290
}

darin's avatar
darin committed
291
static DeprecatedString endMarkup(const Node *node)
eseidel's avatar
eseidel committed
292
{
ap's avatar
ap committed
293
    if (node->isElementNode() && !shouldSelfClose(node) && (node->hasChildNodes() || !doesHTMLForbidEndTag(node)))
lweintraub's avatar
lweintraub committed
294
        return "</" + static_cast<const Element*>(node)->nodeNamePreservingCase().deprecatedString() + ">";
darin's avatar
darin committed
295 296 297
    return "";
}

darin's avatar
darin committed
298
static DeprecatedString markup(Node* startNode, bool onlyIncludeChildren, bool includeSiblings, Vector<Node*> *nodes)
darin's avatar
darin committed
299 300
{
    // Doesn't make sense to only include children and include siblings.
kocienda's avatar
kocienda committed
301
    ASSERT(!(onlyIncludeChildren && includeSiblings));
darin's avatar
darin committed
302
    DeprecatedString me = "";
darin's avatar
darin committed
303
    for (Node* current = startNode; current != NULL; current = includeSiblings ? current->nextSibling() : NULL) {
darin's avatar
darin committed
304
        if (!onlyIncludeChildren) {
eseidel's avatar
eseidel committed
305
            if (nodes)
darin's avatar
darin committed
306
                nodes->append(current);
kocienda's avatar
kocienda committed
307
            me += startMarkup(current, 0, DoNotAnnotateForInterchange, 0);
308
        }
eseidel's avatar
eseidel committed
309
        // print children
darin's avatar
darin committed
310
        if (Node *n = current->firstChild())
ggaren's avatar
ggaren committed
311
            if (!(n->document()->isHTMLDocument() && doesHTMLForbidEndTag(current)))
darin's avatar
darin committed
312
                me += markup(n, false, true, nodes);
eseidel's avatar
eseidel committed
313 314 315 316
        
        // Print my ending tag
        if (!onlyIncludeChildren)
            me += endMarkup(current);
darin's avatar
darin committed
317 318 319 320
    }
    return me;
}

darin's avatar
darin committed
321
static void completeURLs(Node *node, const DeprecatedString &baseURL)
darin's avatar
darin committed
322
{
darin's avatar
darin committed
323 324
    Node *end = node->traverseNextSibling();
    for (Node *n = node; n != end; n = n->traverseNextNode()) {
darin's avatar
darin committed
325
        if (n->isElementNode()) {
326
            Element *e = static_cast<Element*>(n);
darin's avatar
darin committed
327
            NamedAttrMap *attrs = e->attributes();
adele's avatar
adele committed
328 329
            unsigned length = attrs->length();
            for (unsigned i = 0; i < length; i++) {
darin's avatar
darin committed
330
                Attribute *attr = attrs->attributeItem(i);
331
                if (e->isURLAttribute(attr))
darin's avatar
darin committed
332
                    e->setAttribute(attr->name(), KURL(baseURL, attr->value().deprecatedString()).url());
darin's avatar
darin committed
333 334 335 336 337
            }
        }
    }
}

justing's avatar
justing committed
338 339 340 341 342 343
static bool needInterchangeNewlineAfter(const VisiblePosition& v)
{
    VisiblePosition next = v.next();
    return isEndOfParagraph(v) && isStartOfParagraph(next) && !next.deepEquivalent().upstream().node()->hasTagName(brTag);
}

harrison's avatar
harrison committed
344 345
// 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()
darin's avatar
darin committed
346
DeprecatedString createMarkup(const Range *range, Vector<Node*>* nodes, EAnnotateForInterchange annotate)
darin's avatar
darin committed
347 348
{
    if (!range || range->isDetached())
darin's avatar
darin committed
349
        return DeprecatedString();
darin's avatar
darin committed
350

darin's avatar
darin committed
351
    static const DeprecatedString interchangeNewlineString = DeprecatedString("<br class=\"") + AppleInterchangeNewline + "\">";
kocienda's avatar
kocienda committed
352

darin's avatar
darin committed
353
    ExceptionCode ec = 0;
justing's avatar
justing committed
354 355 356
    if (range->collapsed(ec))
        return "";
    ASSERT(ec == 0);
darin's avatar
darin committed
357
    Node *commonAncestor = range->commonAncestorContainer(ec);
darin's avatar
darin committed
358
    ASSERT(ec == 0);
darin's avatar
darin committed
359

ggaren's avatar
ggaren committed
360
    Document *doc = commonAncestor->document();
thatcher's avatar
thatcher committed
361 362
    // disable the delete button so it's elements are not serialized into the markup
    doc->frame()->editor()->deleteButtonController()->disable();
darin's avatar
darin committed
363
    doc->updateLayoutIgnorePendingStylesheets();
darin's avatar
darin committed
364

darin's avatar
darin committed
365
    Node *commonAncestorBlock = 0;
justing's avatar
justing committed
366 367 368 369 370 371 372 373 374 375
    if (commonAncestor) {
        commonAncestorBlock = enclosingBlock(commonAncestor);
        if (commonAncestorBlock->hasTagName(tbodyTag)) {
            Node* table = commonAncestorBlock->parentNode();
            while (table && !table->hasTagName(tableTag))
                table = table->parentNode();
            if (table)
                commonAncestorBlock = table;
        }
    }
thatcher's avatar
thatcher committed
376 377 378

    if (!commonAncestorBlock) {
        doc->frame()->editor()->deleteButtonController()->enable();
379
        return "";
thatcher's avatar
thatcher committed
380
    }
darin's avatar
darin committed
381

darin's avatar
darin committed
382 383 384
    DeprecatedStringList markups;
    Node *pastEnd = range->pastEndNode();
    Node *lastClosed = 0;
darin's avatar
darin committed
385
    Vector<Node*> ancestorsToClose;
kocienda's avatar
kocienda committed
386

justing's avatar
justing committed
387
    // Calculate the "default style" for this markup.
darin's avatar
darin committed
388
    Position pos(doc->documentElement(), 0);
darin's avatar
darin committed
389 390
    RefPtr<CSSComputedStyleDeclaration> computedStyle = pos.computedStyle();
    RefPtr<CSSMutableStyleDeclaration> defaultStyle = computedStyle->copyInheritableProperties();
darin's avatar
darin committed
391
    
justing's avatar
justing committed
392
    Node* startNode = range->startNode();
kocienda's avatar
kocienda committed
393 394
    VisiblePosition visibleStart(range->startPosition(), VP_DEFAULT_AFFINITY);
    VisiblePosition visibleEnd(range->endPosition(), VP_DEFAULT_AFFINITY);
justing's avatar
justing committed
395
    if (annotate && needInterchangeNewlineAfter(visibleStart)) {
thatcher's avatar
thatcher committed
396 397
        if (visibleStart == visibleEnd.previous()) {
            doc->frame()->editor()->deleteButtonController()->enable();
kocienda's avatar
kocienda committed
398
            return interchangeNewlineString;
thatcher's avatar
thatcher committed
399 400
        }

harrison's avatar
harrison committed
401
        markups.append(interchangeNewlineString);
justing's avatar
justing committed
402
        startNode = visibleStart.next().deepEquivalent().node();
harrison's avatar
harrison committed
403
    }
harrison's avatar
harrison committed
404

darin's avatar
darin committed
405 406
    Node *next;
    for (Node *n = startNode; n != pastEnd; n = next) {
darin's avatar
darin committed
407
        next = n->traverseNextNode();
justing's avatar
justing committed
408 409 410 411 412 413 414 415 416 417 418
        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;
        }
darin's avatar
darin committed
419

justing's avatar
justing committed
420 421
        if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd)
            // Don't write out empty block containers that aren't fully selected.
darin's avatar
darin committed
422 423 424
            continue;
        
        // Add the node to the markup.
justing's avatar
justing committed
425
        if (addMarkupForNode) {
eseidel's avatar
eseidel committed
426
            markups.append(startMarkup(n, range, annotate, defaultStyle.get()));
427
            if (nodes)
kocienda's avatar
kocienda committed
428
                nodes->append(n);
darin's avatar
darin committed
429 430
        }
        
justing's avatar
justing committed
431 432 433
        if (n->firstChild() == 0 || skipDescendants) {
            // Node has no children, or we are skipping it's descendants, add its close tag now.
            if (addMarkupForNode) {
kocienda's avatar
kocienda committed
434 435 436
                markups.append(endMarkup(n));
                lastClosed = n;
            }
darin's avatar
darin committed
437 438
            
            // Check if the node is the last leaf of a tree.
justing's avatar
justing committed
439
            if (!n->nextSibling() || next == pastEnd) {
darin's avatar
darin committed
440 441
                if (!ancestorsToClose.isEmpty()) {
                    // Close up the ancestors.
darin's avatar
darin committed
442 443
                    do {
                        Node *ancestor = ancestorsToClose.last();
justing's avatar
justing committed
444
                        if (next != pastEnd && next->isDescendantOf(ancestor))
darin's avatar
darin committed
445 446 447 448 449
                            break;
                        // Not at the end of the range, close ancestors up to sibling of next node.
                        markups.append(endMarkup(ancestor));
                        lastClosed = ancestor;
                        ancestorsToClose.removeLast();
darin's avatar
darin committed
450
                    } while (!ancestorsToClose.isEmpty());
justing's avatar
justing committed
451 452 453 454 455
                }
                
                // 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) {
justing's avatar
justing committed
456
                    Node* lastAncestorClosedOrSelf = n->isDescendantOf(lastClosed) ? lastClosed : n;
justing's avatar
justing committed
457 458 459 460 461
                    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:
justing's avatar
justing committed
462
                        ASSERT(startNode->isDescendantOf(parent));
justing's avatar
justing committed
463 464 465 466 467
                        markups.prepend(startMarkup(parent, range, annotate, defaultStyle.get()));
                        markups.append(endMarkup(parent));
                        if (nodes)
                            nodes->append(parent);
                        lastClosed = parent;
darin's avatar
darin committed
468 469 470
                    }
                }
            }
justing's avatar
justing committed
471 472
        } else if (addMarkupForNode && !skipDescendants)
            // We added markup for this node, and we're descending into it.  Set it to close eventually.
darin's avatar
darin committed
473 474 475
            ancestorsToClose.append(n);
    }
    
harrison's avatar
harrison committed
476 477 478 479
    Node *rangeStartNode = range->startNode();
    int rangeStartOffset = range->startOffset(ec);
    ASSERT(ec == 0);
    
darin's avatar
darin committed
480
    // Add ancestors up to the common ancestor block so inline ancestors such as FONT and B are part of the markup.
darin's avatar
darin committed
481
    if (lastClosed) {
darin's avatar
darin committed
482 483
        for (Node *ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
            if (Range::compareBoundaryPoints(ancestor, 0, rangeStartNode, rangeStartOffset) >= 0) {
darin's avatar
darin committed
484 485 486 487 488
                // we have already added markup for this node
                continue;
            }
            bool breakAtEnd = false;
            if (commonAncestorBlock == ancestor) {
489
                // Include ancestors that are required to retain the appearance of the copied markup.
justing's avatar
justing committed
490 491
                if (annotate &&
                    (ancestor->hasTagName(listingTag)
darin's avatar
darin committed
492 493 494
                        || ancestor->hasTagName(olTag)
                        || ancestor->hasTagName(preTag)
                        || ancestor->hasTagName(tableTag)
harrison's avatar
harrison committed
495
                        || ancestor->hasTagName(ulTag)
justing's avatar
justing committed
496
                        || ancestor->hasTagName(xmpTag))) {
darin's avatar
darin committed
497
                    breakAtEnd = true;
498
                } else
darin's avatar
darin committed
499 500
                    break;
            }
eseidel's avatar
eseidel committed
501
            markups.prepend(startMarkup(ancestor, range, annotate, defaultStyle.get()));
darin's avatar
darin committed
502 503 504 505
            markups.append(endMarkup(ancestor));
            if (nodes) {
                nodes->append(ancestor);
            }        
506
            if (breakAtEnd)
darin's avatar
darin committed
507 508 509 510
                break;
        }
    }

justing's avatar
justing committed
511 512
    if (annotate && needInterchangeNewlineAfter(visibleEnd.previous()))
        markups.append(interchangeNewlineString);
darin's avatar
darin committed
513

514
    // Retain the Mail quote level by including all ancestor mail block quotes.
justing's avatar
justing committed
515 516 517 518 519 520
    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));
            }
521 522 523
        }
    }
    
justing's avatar
justing committed
524 525 526 527 528 529 530
    // 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();
justing's avatar
justing committed
531 532
        style->merge(styleFromMatchedRulesForElement(static_cast<Element*>(root)).get());
        
justing's avatar
justing committed
533 534 535 536 537 538 539 540 541 542 543
        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>");
    }
    
kocienda's avatar
kocienda committed
544
    // add in the "default style" for this markup
darin's avatar
darin committed
545
    // FIXME: Handle case where value has illegal characters in it, like "
darin's avatar
darin committed
546
    DeprecatedString openTag = DeprecatedString("<span class=\"") + AppleStyleSpanClass + "\" style=\"" + defaultStyle->cssText().deprecatedString() + "\">";
kocienda's avatar
kocienda committed
547 548
    markups.prepend(openTag);
    markups.append("</span>");
kocienda's avatar
kocienda committed
549

thatcher's avatar
thatcher committed
550
    doc->frame()->editor()->deleteButtonController()->enable();
darin's avatar
darin committed
551 552 553
    return markups.join("");
}

554
PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const String& markup, const String& baseURL)
darin's avatar
darin committed
555
{
eseidel's avatar
eseidel committed
556
    ASSERT(document->documentElement()->isHTMLElement());
darin's avatar
darin committed
557
    // FIXME: What if the document element is not an HTML element?
558
    HTMLElement *element = static_cast<HTMLElement*>(document->documentElement());
darin's avatar
darin committed
559

darin's avatar
darin committed
560
    RefPtr<DocumentFragment> fragment = element->createContextualFragment(markup);
kocienda's avatar
kocienda committed
561
    ASSERT(fragment);
darin's avatar
darin committed
562 563

    if (!baseURL.isEmpty() && baseURL != document->baseURL())
564
        completeURLs(fragment.get(), baseURL.deprecatedString());
darin's avatar
darin committed
565

darin's avatar
darin committed
566
    return fragment.release();
darin's avatar
darin committed
567 568
}

darin's avatar
darin committed
569 570
DeprecatedString createMarkup(const Node* node, EChildrenOnly includeChildren,
    Vector<Node*>* nodes, EAnnotateForInterchange annotate)
darin's avatar
darin committed
571
{
kocienda's avatar
kocienda committed
572
    ASSERT(annotate == DoNotAnnotateForInterchange); // annotation not yet implemented for this code path
thatcher's avatar
thatcher committed
573
    // disable the delete button so it's elements are not serialized into the markup
andersca's avatar
andersca committed
574 575
    if (node->document()->frame())
        node->document()->frame()->editor()->deleteButtonController()->disable();
ggaren's avatar
ggaren committed
576
    node->document()->updateLayoutIgnorePendingStylesheets();
thatcher's avatar
thatcher committed
577
    DeprecatedString result(markup(const_cast<Node*>(node), includeChildren, false, nodes));
andersca's avatar
andersca committed
578 579
    if (node->document()->frame())
        node->document()->frame()->editor()->deleteButtonController()->enable();
thatcher's avatar
thatcher committed
580
    return result;
darin's avatar
darin committed
581 582
}

harrison's avatar
harrison committed
583
static void fillContainerFromString(ContainerNode* paragraph, const DeprecatedString& string)
584
{
darin's avatar
darin committed
585 586
    Document* document = paragraph->document();

darin's avatar
darin committed
587
    ExceptionCode ec = 0;
588
    if (string.isEmpty()) {
darin's avatar
darin committed
589 590
        paragraph->appendChild(createBlockPlaceholderElement(document), ec);
        ASSERT(ec == 0);
591 592 593 594 595
        return;
    }

    assert(string.find('\n') == -1);

darin's avatar
darin committed
596 597
    DeprecatedStringList tabList = DeprecatedStringList::split('\t', string, true);
    DeprecatedString tabText = "";
598
    while (!tabList.isEmpty()) {
darin's avatar
darin committed
599
        DeprecatedString s = tabList.first();
600 601 602 603
        tabList.pop_front();

        // append the non-tab textual part
        if (!s.isEmpty()) {
darin's avatar
darin committed
604
            if (!tabText.isEmpty()) {
darin's avatar
darin committed
605 606
                paragraph->appendChild(createTabSpanElement(document, tabText), ec);
                ASSERT(ec == 0);
607 608
                tabText = "";
            }
darin's avatar
darin committed
609
            RefPtr<Node> textNode = document->createTextNode(s);
darin's avatar
darin committed
610
            rebalanceWhitespaceInTextNode(textNode.get(), 0, s.length());
darin's avatar
darin committed
611 612
            paragraph->appendChild(textNode.release(), ec);
            ASSERT(ec == 0);
613 614 615 616
        }

        // 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)
darin's avatar
darin committed
617
        if (!tabList.isEmpty())
618
            tabText += '\t';
darin's avatar
darin committed
619
        else if (!tabText.isEmpty()) {
darin's avatar
darin committed
620 621
            paragraph->appendChild(createTabSpanElement(document, tabText), ec);
            ASSERT(ec == 0);
622 623 624 625
        }
    }
}

darin's avatar
darin committed
626
PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String& text)
darin's avatar
darin committed
627
{
darin's avatar
darin committed
628
    if (!context)
darin's avatar
darin committed
629 630
        return 0;

darin's avatar
darin committed
631 632 633 634 635 636 637 638
    Node* styleNode = context->startNode();
    if (!styleNode) {
        styleNode = context->startPosition().node();
        if (!styleNode)
            return 0;
    }

    Document* document = styleNode->document();
darin's avatar
darin committed
639
    RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
640
    
darin's avatar
darin committed
641 642 643 644 645 646 647 648 649 650 651 652 653
    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")) {
darin's avatar
darin committed
654
            RefPtr<Element> element;
darin's avatar
darin committed
655 656 657 658
            element = document->createElementNS(xhtmlNamespaceURI, "br", ec);
            ASSERT(ec == 0);
            element->setAttribute(classAttr, AppleInterchangeNewline);            
            fragment->appendChild(element.release(), ec);
darin's avatar
darin committed
659
            ASSERT(ec == 0);
darin's avatar
darin committed
660
        }
darin's avatar
darin committed
661 662 663
        return fragment.release();
    }

harrison's avatar
harrison committed
664 665 666 667 668 669
    // 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();
    }

darin's avatar
darin committed
670 671 672 673 674 675 676 677 678 679 680 681 682 683
    // 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);
harrison's avatar
harrison committed
684
            fillContainerFromString(element.get(), s);
darin's avatar
darin committed
685 686 687
        }
        fragment->appendChild(element.release(), ec);
        ASSERT(ec == 0);
darin's avatar
darin committed
688
    }
darin's avatar
darin committed
689
    return fragment.release();
darin's avatar
darin committed
690 691
}

darin's avatar
darin committed
692
PassRefPtr<DocumentFragment> createFragmentFromNodes(Document *document, const Vector<Node*>& nodes)
eseidel's avatar
eseidel committed
693 694 695
{
    if (!document)
        return 0;
thatcher's avatar
thatcher committed
696 697 698 699 700

    // disable the delete button so it's elements are not serialized into the markup
    if (document->frame())
        document->frame()->editor()->deleteButtonController()->disable();

darin's avatar
darin committed
701
    RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
darin's avatar
darin committed
702

darin's avatar
darin committed
703
    ExceptionCode ec = 0;
darin's avatar
darin committed
704 705
    size_t size = nodes.size();
    for (size_t i = 0; i < size; ++i) {
darin's avatar
darin committed
706
        RefPtr<Element> element = createDefaultParagraphElement(document);
darin's avatar
darin committed
707
        element->appendChild(nodes[i], ec);
darin's avatar
darin committed
708 709 710
        ASSERT(ec == 0);
        fragment->appendChild(element.release(), ec);
        ASSERT(ec == 0);
eseidel's avatar
eseidel committed
711
    }
darin's avatar
darin committed
712

thatcher's avatar
thatcher committed
713 714 715
    if (document->frame())
        document->frame()->editor()->deleteButtonController()->enable();

darin's avatar
darin committed
716
    return fragment.release();
eseidel's avatar
eseidel committed
717 718
}

darin's avatar
darin committed
719
}