1/*
2 * Copyright (C) 2005 Apple 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 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 INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "BreakBlockquoteCommand.h"
28
29#include "HTMLElement.h"
30#include "HTMLNames.h"
31#include "NodeTraversal.h"
32#include "RenderListItem.h"
33#include "Text.h"
34#include "htmlediting.h"
35
36namespace WebCore {
37
38using namespace HTMLNames;
39
40BreakBlockquoteCommand::BreakBlockquoteCommand(Document& document)
41    : CompositeEditCommand(document)
42{
43}
44
45void BreakBlockquoteCommand::doApply()
46{
47    if (endingSelection().isNone())
48        return;
49
50    // Delete the current selection.
51    if (endingSelection().isRange())
52        deleteSelection(false, false);
53
54    // This is a scenario that should never happen, but we want to
55    // make sure we don't dereference a null pointer below.
56
57    ASSERT(!endingSelection().isNone());
58
59    if (endingSelection().isNone())
60        return;
61
62    VisiblePosition visiblePos = endingSelection().visibleStart();
63
64    // pos is a position equivalent to the caret.  We use downstream() so that pos will
65    // be in the first node that we need to move (there are a few exceptions to this, see below).
66    Position pos = endingSelection().start().downstream();
67
68    // Find the top-most blockquote from the start.
69    Node* topBlockquote = highestEnclosingNodeOfType(pos, isMailBlockquote);
70    if (!topBlockquote || !topBlockquote->parentNode() || !topBlockquote->isElementNode())
71        return;
72
73    RefPtr<Element> breakNode = createBreakElement(document());
74
75    bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockquote);
76
77    // If the position is at the beginning of the top quoted content, we don't need to break the quote.
78    // Instead, insert the break before the blockquote, unless the position is as the end of the the quoted content.
79    if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPosInNode) {
80        insertNodeBefore(breakNode.get(), topBlockquote);
81        setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional()));
82        rebalanceWhitespace();
83        return;
84    }
85
86    // Insert a break after the top blockquote.
87    insertNodeAfter(breakNode.get(), topBlockquote);
88
89    // If we're inserting the break at the end of the quoted content, we don't need to break the quote.
90    if (isLastVisPosInNode) {
91        setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional()));
92        rebalanceWhitespace();
93        return;
94    }
95
96    // Don't move a line break just after the caret.  Doing so would create an extra, empty paragraph
97    // in the new blockquote.
98    if (lineBreakExistsAtVisiblePosition(visiblePos))
99        pos = pos.next();
100
101    // Adjust the position so we don't split at the beginning of a quote.
102    while (isFirstVisiblePositionInNode(VisiblePosition(pos), enclosingNodeOfType(pos, isMailBlockquote)))
103        pos = pos.previous();
104
105    // startNode is the first node that we need to move to the new blockquote.
106    Node* startNode = pos.deprecatedNode();
107
108    // Split at pos if in the middle of a text node.
109    if (startNode->isTextNode()) {
110        Text* textNode = toText(startNode);
111        if ((unsigned)pos.deprecatedEditingOffset() >= textNode->length()) {
112            startNode = NodeTraversal::next(startNode);
113            ASSERT(startNode);
114        } else if (pos.deprecatedEditingOffset() > 0)
115            splitTextNode(textNode, pos.deprecatedEditingOffset());
116    } else if (pos.deprecatedEditingOffset() > 0) {
117        Node* childAtOffset = startNode->childNode(pos.deprecatedEditingOffset());
118        startNode = childAtOffset ? childAtOffset : NodeTraversal::next(startNode);
119        ASSERT(startNode);
120    }
121
122    // If there's nothing inside topBlockquote to move, we're finished.
123    if (!startNode->isDescendantOf(topBlockquote)) {
124        setEndingSelection(VisibleSelection(VisiblePosition(firstPositionInOrBeforeNode(startNode)), endingSelection().isDirectional()));
125        return;
126    }
127
128    // Build up list of ancestors in between the start node and the top blockquote.
129    Vector<RefPtr<Element>> ancestors;
130    for (Element* node = startNode->parentElement(); node && node != topBlockquote; node = node->parentElement())
131        ancestors.append(node);
132
133    // Insert a clone of the top blockquote after the break.
134    RefPtr<Element> clonedBlockquote = toElement(topBlockquote)->cloneElementWithoutChildren();
135    insertNodeAfter(clonedBlockquote.get(), breakNode.get());
136
137    // Clone startNode's ancestors into the cloned blockquote.
138    // On exiting this loop, clonedAncestor is the lowest ancestor
139    // that was cloned (i.e. the clone of either ancestors.last()
140    // or clonedBlockquote if ancestors is empty).
141    RefPtr<Element> clonedAncestor = clonedBlockquote;
142    for (size_t i = ancestors.size(); i != 0; --i) {
143        RefPtr<Element> clonedChild = ancestors[i - 1]->cloneElementWithoutChildren();
144        // Preserve list item numbering in cloned lists.
145        if (clonedChild->isElementNode() && clonedChild->hasTagName(olTag)) {
146            Node* listChildNode = i > 1 ? ancestors[i - 2].get() : startNode;
147            // The first child of the cloned list might not be a list item element,
148            // find the first one so that we know where to start numbering.
149            while (listChildNode && !listChildNode->hasTagName(liTag))
150                listChildNode = listChildNode->nextSibling();
151            if (listChildNode && listChildNode->renderer() && listChildNode->renderer()->isListItem())
152                setNodeAttribute(clonedChild, startAttr, AtomicString::number(toRenderListItem(listChildNode->renderer())->value()));
153        }
154
155        appendNode(clonedChild.get(), clonedAncestor.get());
156        clonedAncestor = clonedChild;
157    }
158
159    moveRemainingSiblingsToNewParent(startNode, 0, clonedAncestor);
160
161    if (!ancestors.isEmpty()) {
162        // Split the tree up the ancestor chain until the topBlockquote
163        // Throughout this loop, clonedParent is the clone of ancestor's parent.
164        // This is so we can clone ancestor's siblings and place the clones
165        // into the clone corresponding to the ancestor's parent.
166        RefPtr<Element> ancestor;
167        RefPtr<Element> clonedParent;
168        for (ancestor = ancestors.first(), clonedParent = clonedAncestor->parentElement();
169             ancestor && ancestor != topBlockquote;
170             ancestor = ancestor->parentElement(), clonedParent = clonedParent->parentElement())
171            moveRemainingSiblingsToNewParent(ancestor->nextSibling(), 0, clonedParent);
172
173        // If the startNode's original parent is now empty, remove it
174        Node* originalParent = ancestors.first().get();
175        if (!originalParent->hasChildNodes())
176            removeNode(originalParent);
177    }
178
179    // Make sure the cloned block quote renders.
180    addBlockPlaceholderIfNeeded(clonedBlockquote.get());
181
182    // Put the selection right before the break.
183    setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional()));
184    rebalanceWhitespace();
185}
186
187} // namespace WebCore
188