diff options
author | Steve Block <steveblock@google.com> | 2011-05-06 11:45:16 +0100 |
---|---|---|
committer | Steve Block <steveblock@google.com> | 2011-05-12 13:44:10 +0100 |
commit | cad810f21b803229eb11403f9209855525a25d57 (patch) | |
tree | 29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /WebCore/editing/markup.cpp | |
parent | 121b0cf4517156d0ac5111caf9830c51b69bae8f (diff) | |
download | external_webkit-cad810f21b803229eb11403f9209855525a25d57.zip external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2 |
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'WebCore/editing/markup.cpp')
-rw-r--r-- | WebCore/editing/markup.cpp | 915 |
1 files changed, 0 insertions, 915 deletions
diff --git a/WebCore/editing/markup.cpp b/WebCore/editing/markup.cpp deleted file mode 100644 index 4cbdcce..0000000 --- a/WebCore/editing/markup.cpp +++ /dev/null @@ -1,915 +0,0 @@ -/* - * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. - * - * 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. - */ - -#include "config.h" -#include "markup.h" - -#include "CDATASection.h" -#include "CharacterNames.h" -#include "CSSComputedStyleDeclaration.h" -#include "CSSMutableStyleDeclaration.h" -#include "CSSPrimitiveValue.h" -#include "CSSProperty.h" -#include "CSSPropertyNames.h" -#include "CSSRule.h" -#include "CSSRuleList.h" -#include "CSSStyleRule.h" -#include "CSSStyleSelector.h" -#include "CSSValue.h" -#include "CSSValueKeywords.h" -#include "DeleteButtonController.h" -#include "DocumentFragment.h" -#include "DocumentType.h" -#include "Editor.h" -#include "Frame.h" -#include "HTMLBodyElement.h" -#include "HTMLElement.h" -#include "HTMLNames.h" -#include "KURL.h" -#include "MarkupAccumulator.h" -#include "Range.h" -#include "TextIterator.h" -#include "VisibleSelection.h" -#include "XMLNSNames.h" -#include "htmlediting.h" -#include "visible_units.h" -#include <wtf/StdLibExtras.h> - -using namespace std; - -namespace WebCore { - -using namespace HTMLNames; - -static bool propertyMissingOrEqualToNone(CSSStyleDeclaration*, int propertyID); - -class AttributeChange { -public: - AttributeChange() - : m_name(nullAtom, nullAtom, nullAtom) - { - } - - AttributeChange(PassRefPtr<Element> element, const QualifiedName& name, const String& value) - : m_element(element), m_name(name), m_value(value) - { - } - - void apply() - { - m_element->setAttribute(m_name, m_value); - } - -private: - RefPtr<Element> m_element; - QualifiedName m_name; - String m_value; -}; - -static void completeURLs(Node* node, const String& baseURL) -{ - Vector<AttributeChange> changes; - - KURL parsedBaseURL(ParsedURLString, baseURL); - - Node* end = node->traverseNextSibling(); - for (Node* n = node; n != end; n = n->traverseNextNode()) { - if (n->isElementNode()) { - Element* e = static_cast<Element*>(n); - NamedNodeMap* attributes = e->attributes(); - unsigned length = attributes->length(); - for (unsigned i = 0; i < length; i++) { - Attribute* attribute = attributes->attributeItem(i); - if (e->isURLAttribute(attribute)) - changes.append(AttributeChange(e, attribute->name(), KURL(parsedBaseURL, attribute->value()).string())); - } - } - } - - size_t numChanges = changes.size(); - for (size_t i = 0; i < numChanges; ++i) - changes[i].apply(); -} - -class StyledMarkupAccumulator : public MarkupAccumulator { -public: - enum RangeFullySelectsNode { DoesFullySelectNode, DoesNotFullySelectNode }; - - StyledMarkupAccumulator(Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs, EAnnotateForInterchange shouldAnnotate, const Range* range) - : MarkupAccumulator(nodes, shouldResolveURLs, range) - , m_shouldAnnotate(shouldAnnotate) - { - } - - Node* serializeNodes(Node* startNode, Node* pastEnd); - void appendString(const String& s) { return MarkupAccumulator::appendString(s); } - void wrapWithNode(Node*, bool convertBlocksToInlines = false, RangeFullySelectsNode = DoesFullySelectNode); - void wrapWithStyleNode(CSSStyleDeclaration*, Document*, bool isBlock = false); - String takeResults(); - -private: - virtual void appendText(Vector<UChar>& out, Text*); - String renderedText(const Node*, const Range*); - String stringValueForRange(const Node*, const Range*); - void removeExteriorStyles(CSSMutableStyleDeclaration*); - void appendElement(Vector<UChar>& out, Element* element, bool addDisplayInline, RangeFullySelectsNode); - void appendElement(Vector<UChar>& out, Element* element, Namespaces*) { appendElement(out, element, false, DoesFullySelectNode); } - - bool shouldAnnotate() { return m_shouldAnnotate == AnnotateForInterchange; } - - Vector<String> m_reversedPrecedingMarkup; - const EAnnotateForInterchange m_shouldAnnotate; -}; - -void StyledMarkupAccumulator::wrapWithNode(Node* node, bool convertBlocksToInlines, RangeFullySelectsNode rangeFullySelectsNode) -{ - Vector<UChar> markup; - if (node->isElementNode()) - appendElement(markup, static_cast<Element*>(node), convertBlocksToInlines && isBlock(const_cast<Node*>(node)), rangeFullySelectsNode); - else - appendStartMarkup(markup, node, 0); - m_reversedPrecedingMarkup.append(String::adopt(markup)); - appendEndTag(node); - if (m_nodes) - m_nodes->append(node); -} - -void StyledMarkupAccumulator::wrapWithStyleNode(CSSStyleDeclaration* style, Document* document, bool isBlock) -{ - // All text-decoration-related elements should have been treated as special ancestors - // If we ever hit this ASSERT, we should export StyleChange in ApplyStyleCommand and use it here - ASSERT(propertyMissingOrEqualToNone(style, CSSPropertyTextDecoration) && propertyMissingOrEqualToNone(style, CSSPropertyWebkitTextDecorationsInEffect)); - DEFINE_STATIC_LOCAL(const String, divStyle, ("<div style=\"")); - DEFINE_STATIC_LOCAL(const String, divClose, ("</div>")); - DEFINE_STATIC_LOCAL(const String, styleSpanOpen, ("<span class=\"" AppleStyleSpanClass "\" style=\"")); - DEFINE_STATIC_LOCAL(const String, styleSpanClose, ("</span>")); - Vector<UChar> openTag; - append(openTag, isBlock ? divStyle : styleSpanOpen); - appendAttributeValue(openTag, style->cssText(), document->isHTMLDocument()); - openTag.append('\"'); - openTag.append('>'); - m_reversedPrecedingMarkup.append(String::adopt(openTag)); - appendString(isBlock ? divClose : styleSpanClose); -} - -String StyledMarkupAccumulator::takeResults() -{ - Vector<UChar> result; - result.reserveInitialCapacity(totalLength(m_reversedPrecedingMarkup) + length()); - - for (size_t i = m_reversedPrecedingMarkup.size(); i > 0; --i) - append(result, m_reversedPrecedingMarkup[i - 1]); - - concatenateMarkup(result); - - return String::adopt(result); -} - -void StyledMarkupAccumulator::appendText(Vector<UChar>& out, Text* text) -{ - if (!shouldAnnotate() || (text->parentElement() && text->parentElement()->tagQName() == textareaTag)) { - MarkupAccumulator::appendText(out, text); - return; - } - - bool useRenderedText = !enclosingNodeWithTag(Position(text, 0), selectTag); - String content = useRenderedText ? renderedText(text, m_range) : stringValueForRange(text, m_range); - Vector<UChar> buffer; - appendCharactersReplacingEntities(buffer, content.characters(), content.length(), EntityMaskInPCDATA); - append(out, convertHTMLTextToInterchangeFormat(String::adopt(buffer), text)); -} - -String StyledMarkupAccumulator::renderedText(const Node* node, const Range* range) -{ - if (!node->isTextNode()) - return String(); - - ExceptionCode ec; - const Text* textNode = static_cast<const Text*>(node); - unsigned startOffset = 0; - unsigned endOffset = textNode->length(); - - if (range && node == range->startContainer(ec)) - startOffset = range->startOffset(ec); - if (range && node == range->endContainer(ec)) - endOffset = range->endOffset(ec); - - Position start(const_cast<Node*>(node), startOffset); - Position end(const_cast<Node*>(node), endOffset); - return plainText(Range::create(node->document(), start, end).get()); -} - -String StyledMarkupAccumulator::stringValueForRange(const Node* node, const Range* range) -{ - if (!range) - return node->nodeValue(); - - String str = node->nodeValue(); - ExceptionCode ec; - if (node == range->endContainer(ec)) - str.truncate(range->endOffset(ec)); - if (node == range->startContainer(ec)) - str.remove(0, range->startOffset(ec)); - return str; -} - -static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesForElement(Element* element, bool authorOnly = true) -{ - RefPtr<CSSMutableStyleDeclaration> style = CSSMutableStyleDeclaration::create(); - 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(); -} - -void StyledMarkupAccumulator::appendElement(Vector<UChar>& out, Element* element, bool addDisplayInline, RangeFullySelectsNode rangeFullySelectsNode) -{ - bool documentIsHTML = element->document()->isHTMLDocument(); - appendOpenTag(out, element, 0); - - NamedNodeMap* attributes = element->attributes(); - unsigned length = attributes->length(); - for (unsigned int i = 0; i < length; i++) { - Attribute* attribute = attributes->attributeItem(i); - // We'll handle the style attribute separately, below. - if (attribute->name() == styleAttr && element->isHTMLElement() && (shouldAnnotate() || addDisplayInline)) - continue; - appendAttribute(out, element, *attribute, 0); - } - - if (element->isHTMLElement() && (shouldAnnotate() || addDisplayInline)) { - RefPtr<CSSMutableStyleDeclaration> style = static_cast<HTMLElement*>(element)->getInlineStyleDecl()->copy(); - if (shouldAnnotate()) { - RefPtr<CSSMutableStyleDeclaration> styleFromMatchedRules = styleFromMatchedRulesForElement(const_cast<Element*>(element)); - // Styles from the inline style declaration, held in the variable "style", take precedence - // over those from matched rules. - styleFromMatchedRules->merge(style.get()); - style = styleFromMatchedRules; - - RefPtr<CSSComputedStyleDeclaration> computedStyleForElement = computedStyle(element); - RefPtr<CSSMutableStyleDeclaration> fromComputedStyle = CSSMutableStyleDeclaration::create(); - - { - CSSMutableStyleDeclaration::const_iterator end = style->end(); - for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) { - const CSSProperty& property = *it; - CSSValue* value = property.value(); - // The property value, if it's a percentage, may not reflect the actual computed value. - // For example: style="height: 1%; overflow: visible;" in quirksmode - // FIXME: There are others like this, see <rdar://problem/5195123> Slashdot copy/paste fidelity problem - if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) - if (static_cast<CSSPrimitiveValue*>(value)->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE) - if (RefPtr<CSSValue> computedPropertyValue = computedStyleForElement->getPropertyCSSValue(property.id())) - fromComputedStyle->addParsedProperty(CSSProperty(property.id(), computedPropertyValue)); - } - } - style->merge(fromComputedStyle.get()); - } - if (addDisplayInline) - style->setProperty(CSSPropertyDisplay, CSSValueInline, true); - // If the node is not fully selected by the range, then we don't want to keep styles that affect its relationship to the nodes around it - // only the ones that affect it and the nodes within it. - if (rangeFullySelectsNode == DoesNotFullySelectNode) - removeExteriorStyles(style.get()); - if (style->length() > 0) { - DEFINE_STATIC_LOCAL(const String, stylePrefix, (" style=\"")); - append(out, stylePrefix); - appendAttributeValue(out, style->cssText(), documentIsHTML); - out.append('\"'); - } - } - - appendCloseTag(out, element); -} - -void StyledMarkupAccumulator::removeExteriorStyles(CSSMutableStyleDeclaration* style) -{ - style->removeProperty(CSSPropertyFloat); -} - -Node* StyledMarkupAccumulator::serializeNodes(Node* startNode, Node* pastEnd) -{ - Vector<Node*> ancestorsToClose; - Node* next; - Node* lastClosed = 0; - for (Node* n = startNode; n != pastEnd; n = next) { - // According to <rdar://problem/5730668>, it is possible for n to blow - // past pastEnd and become null here. This shouldn't be possible. - // This null check will prevent crashes (but create too much markup) - // and the ASSERT will hopefully lead us to understanding the problem. - ASSERT(n); - if (!n) - break; - - next = n->traverseNextNode(); - bool openedTag = false; - - if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd) - // Don't write out empty block containers that aren't fully selected. - continue; - - if (!n->renderer() && !enclosingNodeWithTag(Position(n, 0), selectTag)) { - next = n->traverseNextSibling(); - // Don't skip over pastEnd. - if (pastEnd && pastEnd->isDescendantOf(n)) - next = pastEnd; - } else { - // Add the node to the markup if we're not skipping the descendants - appendStartTag(n); - - // If node has no children, close the tag now. - if (!n->childNodeCount()) { - appendEndTag(n); - lastClosed = n; - } else { - openedTag = true; - ancestorsToClose.append(n); - } - } - - // If we didn't insert open tag and there's no more siblings or we're at the end of the traversal, take care of ancestors. - // FIXME: What happens if we just inserted open tag and reached the end? - if (!openedTag && (!n->nextSibling() || next == pastEnd)) { - // Close up the ancestors. - while (!ancestorsToClose.isEmpty()) { - 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. - appendEndTag(ancestor); - lastClosed = ancestor; - ancestorsToClose.removeLast(); - } - - // Surround the currently accumulated markup with markup for ancestors we never opened as we leave the subtree(s) rooted at those ancestors. - ContainerNode* nextParent = next ? next->parentNode() : 0; - if (next != pastEnd && n != nextParent) { - Node* lastAncestorClosedOrSelf = n->isDescendantOf(lastClosed) ? lastClosed : n; - for (ContainerNode* parent = lastAncestorClosedOrSelf->parentNode(); parent && 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)); - wrapWithNode(parent); - lastClosed = parent; - } - } - } - } - - return lastClosed; -} - -static Node* ancestorToRetainStructureAndAppearance(Node* commonAncestor) -{ - Node* commonAncestorBlock = enclosingBlock(commonAncestor); - - if (!commonAncestorBlock) - return 0; - - if (commonAncestorBlock->hasTagName(tbodyTag) || commonAncestorBlock->hasTagName(trTag)) { - ContainerNode* table = commonAncestorBlock->parentNode(); - while (table && !table->hasTagName(tableTag)) - table = table->parentNode(); - - return table; - } - - if (commonAncestorBlock->hasTagName(listingTag) - || commonAncestorBlock->hasTagName(olTag) - || commonAncestorBlock->hasTagName(preTag) - || commonAncestorBlock->hasTagName(tableTag) - || commonAncestorBlock->hasTagName(ulTag) - || commonAncestorBlock->hasTagName(xmpTag) - || commonAncestorBlock->hasTagName(h1Tag) - || commonAncestorBlock->hasTagName(h2Tag) - || commonAncestorBlock->hasTagName(h3Tag) - || commonAncestorBlock->hasTagName(h4Tag) - || commonAncestorBlock->hasTagName(h5Tag)) - return commonAncestorBlock; - - return 0; -} - -static bool propertyMissingOrEqualToNone(CSSStyleDeclaration* style, int propertyID) -{ - if (!style) - return false; - RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID); - if (!value) - return true; - if (!value->isPrimitiveValue()) - return false; - return static_cast<CSSPrimitiveValue*>(value.get())->getIdent() == CSSValueNone; -} - -static bool needInterchangeNewlineAfter(const VisiblePosition& v) -{ - VisiblePosition next = v.next(); - Node* upstreamNode = next.deepEquivalent().upstream().node(); - Node* downstreamNode = v.deepEquivalent().downstream().node(); - // Add an interchange newline if a paragraph break is selected and a br won't already be added to the markup to represent it. - return isEndOfParagraph(v) && isStartOfParagraph(next) && !(upstreamNode->hasTagName(brTag) && upstreamNode == downstreamNode); -} - -static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesAndInlineDecl(const Node* node) -{ - if (!node->isHTMLElement()) - return 0; - - // FIXME: Having to const_cast here is ugly, but it is quite a bit of work to untangle - // the non-const-ness of styleFromMatchedRulesForElement. - HTMLElement* element = const_cast<HTMLElement*>(static_cast<const HTMLElement*>(node)); - RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesForElement(element); - RefPtr<CSSMutableStyleDeclaration> inlineStyleDecl = element->getInlineStyleDecl(); - style->merge(inlineStyleDecl.get()); - return style.release(); -} - -static bool isElementPresentational(const Node* node) -{ - if (node->hasTagName(uTag) || node->hasTagName(sTag) || node->hasTagName(strikeTag) - || node->hasTagName(iTag) || node->hasTagName(emTag) || node->hasTagName(bTag) || node->hasTagName(strongTag)) - return true; - RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesAndInlineDecl(node); - if (!style) - return false; - return !propertyMissingOrEqualToNone(style.get(), CSSPropertyTextDecoration); -} - -static bool shouldIncludeWrapperForFullySelectedRoot(Node* fullySelectedRoot, CSSMutableStyleDeclaration* style) -{ - if (fullySelectedRoot->isElementNode() && static_cast<Element*>(fullySelectedRoot)->hasAttribute(backgroundAttr)) - return true; - - return style->getPropertyCSSValue(CSSPropertyBackgroundImage) || style->getPropertyCSSValue(CSSPropertyBackgroundColor); -} - -static Node* highestAncestorToWrapMarkup(const Range* range, Node* fullySelectedRoot, EAnnotateForInterchange shouldAnnotate) -{ - ExceptionCode ec; - Node* commonAncestor = range->commonAncestorContainer(ec); - ASSERT(commonAncestor); - Node* specialCommonAncestor = 0; - if (shouldAnnotate == AnnotateForInterchange) { - // Include ancestors that aren't completely inside the range but are required to retain - // the structure and appearance of the copied markup. - specialCommonAncestor = ancestorToRetainStructureAndAppearance(commonAncestor); - - // Retain the Mail quote level by including all ancestor mail block quotes. - for (Node* ancestor = range->firstNode(); ancestor; ancestor = ancestor->parentNode()) { - if (isMailBlockquote(ancestor)) - specialCommonAncestor = ancestor; - } - } - - Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor; - if (checkAncestor->renderer()) { - Node* newSpecialCommonAncestor = highestEnclosingNodeOfType(Position(checkAncestor, 0), &isElementPresentational); - if (newSpecialCommonAncestor) - specialCommonAncestor = newSpecialCommonAncestor; - } - - // If a single tab is selected, commonAncestor will be a text node inside a tab span. - // If two or more tabs are selected, commonAncestor will be the tab span. - // In either case, if there is a specialCommonAncestor already, it will necessarily be above - // any tab span that needs to be included. - if (!specialCommonAncestor && isTabSpanTextNode(commonAncestor)) - specialCommonAncestor = commonAncestor->parentNode(); - if (!specialCommonAncestor && isTabSpanNode(commonAncestor)) - specialCommonAncestor = commonAncestor; - - if (Node *enclosingAnchor = enclosingNodeWithTag(Position(specialCommonAncestor ? specialCommonAncestor : commonAncestor, 0), aTag)) - specialCommonAncestor = enclosingAnchor; - - if (shouldAnnotate == AnnotateForInterchange && fullySelectedRoot) { - RefPtr<CSSMutableStyleDeclaration> fullySelectedRootStyle = styleFromMatchedRulesAndInlineDecl(fullySelectedRoot); - if (shouldIncludeWrapperForFullySelectedRoot(fullySelectedRoot, fullySelectedRootStyle.get())) - specialCommonAncestor = fullySelectedRoot; - } - return specialCommonAncestor; -} - -// 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() -String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterchange shouldAnnotate, bool convertBlocksToInlines, EAbsoluteURLs shouldResolveURLs) -{ - DEFINE_STATIC_LOCAL(const String, interchangeNewlineString, ("<br class=\"" AppleInterchangeNewline "\">")); - - if (!range) - return ""; - - Document* document = range->ownerDocument(); - if (!document) - return ""; - - // Disable the delete button so it's elements are not serialized into the markup, - // but make sure neither endpoint is inside the delete user interface. - Frame* frame = document->frame(); - DeleteButtonController* deleteButton = frame ? frame->editor()->deleteButtonController() : 0; - RefPtr<Range> updatedRange = avoidIntersectionWithNode(range, deleteButton ? deleteButton->containerElement() : 0); - if (!updatedRange) - return ""; - - if (deleteButton) - deleteButton->disable(); - - ExceptionCode ec = 0; - bool collapsed = updatedRange->collapsed(ec); - ASSERT(!ec); - if (collapsed) - return ""; - Node* commonAncestor = updatedRange->commonAncestorContainer(ec); - ASSERT(!ec); - if (!commonAncestor) - return ""; - - document->updateLayoutIgnorePendingStylesheets(); - - StyledMarkupAccumulator accumulator(nodes, shouldResolveURLs, shouldAnnotate, updatedRange.get()); - Node* pastEnd = updatedRange->pastLastNode(); - - Node* startNode = updatedRange->firstNode(); - VisiblePosition visibleStart(updatedRange->startPosition(), VP_DEFAULT_AFFINITY); - VisiblePosition visibleEnd(updatedRange->endPosition(), VP_DEFAULT_AFFINITY); - if (shouldAnnotate == AnnotateForInterchange && needInterchangeNewlineAfter(visibleStart)) { - if (visibleStart == visibleEnd.previous()) { - if (deleteButton) - deleteButton->enable(); - return interchangeNewlineString; - } - - accumulator.appendString(interchangeNewlineString); - startNode = visibleStart.next().deepEquivalent().node(); - - if (pastEnd && Range::compareBoundaryPoints(startNode, 0, pastEnd, 0) >= 0) { - if (deleteButton) - deleteButton->enable(); - return interchangeNewlineString; - } - } - - Node* body = enclosingNodeWithTag(Position(commonAncestor, 0), bodyTag); - Node* fullySelectedRoot = 0; - // FIXME: Do this for all fully selected blocks, not just the body. - if (body && areRangesEqual(VisibleSelection::selectionFromContentsOfNode(body).toNormalizedRange().get(), range)) - fullySelectedRoot = body; - - Node* specialCommonAncestor = highestAncestorToWrapMarkup(updatedRange.get(), fullySelectedRoot, shouldAnnotate); - - Node* lastClosed = accumulator.serializeNodes(startNode, pastEnd); - - if (specialCommonAncestor && lastClosed) { - // Also include all of the ancestors of lastClosed up to this special ancestor. - for (ContainerNode* ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) { - if (ancestor == fullySelectedRoot && !convertBlocksToInlines) { - RefPtr<CSSMutableStyleDeclaration> fullySelectedRootStyle = styleFromMatchedRulesAndInlineDecl(fullySelectedRoot); - - // Bring the background attribute over, but not as an attribute because a background attribute on a div - // appears to have no effect. - if (!fullySelectedRootStyle->getPropertyCSSValue(CSSPropertyBackgroundImage) && static_cast<Element*>(fullySelectedRoot)->hasAttribute(backgroundAttr)) - fullySelectedRootStyle->setProperty(CSSPropertyBackgroundImage, "url('" + static_cast<Element*>(fullySelectedRoot)->getAttribute(backgroundAttr) + "')"); - - if (fullySelectedRootStyle->length()) { - // Reset the CSS properties to avoid an assertion error in addStyleMarkup(). - // This assertion is caused at least when we select all text of a <body> element whose - // 'text-decoration' property is "inherit", and copy it. - if (!propertyMissingOrEqualToNone(fullySelectedRootStyle.get(), CSSPropertyTextDecoration)) - fullySelectedRootStyle->setProperty(CSSPropertyTextDecoration, CSSValueNone); - if (!propertyMissingOrEqualToNone(fullySelectedRootStyle.get(), CSSPropertyWebkitTextDecorationsInEffect)) - fullySelectedRootStyle->setProperty(CSSPropertyWebkitTextDecorationsInEffect, CSSValueNone); - accumulator.wrapWithStyleNode(fullySelectedRootStyle.get(), document, true); - } - } else { - // Since this node and all the other ancestors are not in the selection we want to set RangeFullySelectsNode to DoesNotFullySelectNode - // so that styles that affect the exterior of the node are not included. - accumulator.wrapWithNode(ancestor, convertBlocksToInlines, StyledMarkupAccumulator::DoesNotFullySelectNode); - } - if (nodes) - nodes->append(ancestor); - - lastClosed = ancestor; - - if (ancestor == specialCommonAncestor) - break; - } - } - - // Add a wrapper span with the styles that all of the nodes in the markup inherit. - ContainerNode* parentOfLastClosed = lastClosed ? lastClosed->parentNode() : 0; - if (parentOfLastClosed && parentOfLastClosed->renderer()) { - RefPtr<EditingStyle> style = EditingStyle::create(parentOfLastClosed); - - // 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. - style->removeStyleAddedByNode(nearestMailBlockquote(parentOfLastClosed)); - - // Document default styles will be added on another wrapper span. - if (document && document->documentElement()) - style->prepareToApplyAt(firstPositionInNode(document->documentElement())); - - // Since we are converting blocks to inlines, remove any inherited block properties that are in the style. - // This cuts out meaningless properties and prevents properties from magically affecting blocks later - // if the style is cloned for a new block element during a future editing operation. - if (convertBlocksToInlines) - style->removeBlockProperties(); - - if (!style->isEmpty()) - accumulator.wrapWithStyleNode(style->style(), document); - } - - if (lastClosed && lastClosed != document->documentElement()) { - // Add a style span with the document's default styles. We add these in a separate - // span so that at paste time we can differentiate between document defaults and user - // applied styles. - RefPtr<EditingStyle> defaultStyle = EditingStyle::create(document->documentElement()); - if (!defaultStyle->isEmpty()) - accumulator.wrapWithStyleNode(defaultStyle->style(), document); - } - - // FIXME: The interchange newline should be placed in the block that it's in, not after all of the content, unconditionally. - if (shouldAnnotate == AnnotateForInterchange && needInterchangeNewlineAfter(visibleEnd.previous())) - accumulator.appendString(interchangeNewlineString); - - if (deleteButton) - deleteButton->enable(); - - return accumulator.takeResults(); -} - -PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const String& markup, const String& baseURL, FragmentScriptingPermission scriptingPermission) -{ - // We use a fake body element here to trick the HTML parser to using the - // InBody insertion mode. Really, all this code is wrong and need to be - // changed not to use deprecatedCreateContextualFragment. - RefPtr<HTMLBodyElement> fakeBody = HTMLBodyElement::create(document); - // FIXME: This should not use deprecatedCreateContextualFragment - RefPtr<DocumentFragment> fragment = fakeBody->deprecatedCreateContextualFragment(markup, scriptingPermission); - - if (fragment && !baseURL.isEmpty() && baseURL != blankURL() && baseURL != document->baseURL()) - completeURLs(fragment.get(), baseURL); - - return fragment.release(); -} - -String createMarkup(const Node* node, EChildrenOnly childrenOnly, Vector<Node*>* nodes, EAbsoluteURLs shouldResolveURLs) -{ - if (!node) - return ""; - - HTMLElement* deleteButtonContainerElement = 0; - if (Frame* frame = node->document()->frame()) { - deleteButtonContainerElement = frame->editor()->deleteButtonController()->containerElement(); - if (node->isDescendantOf(deleteButtonContainerElement)) - return ""; - } - - MarkupAccumulator accumulator(nodes, shouldResolveURLs); - return accumulator.serializeNodes(const_cast<Node*>(node), deleteButtonContainerElement, childrenOnly); -} - -static void fillContainerFromString(ContainerNode* paragraph, const String& string) -{ - Document* document = paragraph->document(); - - ExceptionCode ec = 0; - if (string.isEmpty()) { - paragraph->appendChild(createBlockPlaceholderElement(document), ec); - ASSERT(!ec); - return; - } - - ASSERT(string.find('\n') == notFound); - - Vector<String> tabList; - string.split('\t', true, tabList); - String tabText = ""; - bool first = true; - size_t numEntries = tabList.size(); - for (size_t i = 0; i < numEntries; ++i) { - const String& s = tabList[i]; - - // append the non-tab textual part - if (!s.isEmpty()) { - if (!tabText.isEmpty()) { - paragraph->appendChild(createTabSpanElement(document, tabText), ec); - ASSERT(!ec); - tabText = ""; - } - RefPtr<Node> textNode = document->createTextNode(stringWithRebalancedWhitespace(s, first, i + 1 == numEntries)); - paragraph->appendChild(textNode.release(), ec); - ASSERT(!ec); - } - - // 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 (i + 1 != numEntries) - tabText.append('\t'); - else if (!tabText.isEmpty()) { - paragraph->appendChild(createTabSpanElement(document, tabText), ec); - ASSERT(!ec); - } - - first = false; - } -} - -bool isPlainTextMarkup(Node *node) -{ - if (!node->isElementNode() || !node->hasTagName(divTag) || static_cast<Element*>(node)->attributes()->length()) - return false; - - if (node->childNodeCount() == 1 && (node->firstChild()->isTextNode() || (node->firstChild()->firstChild()))) - return true; - - return (node->childNodeCount() == 2 && isTabSpanTextNode(node->firstChild()->firstChild()) && node->firstChild()->nextSibling()->isTextNode()); -} - -PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String& text) -{ - if (!context) - return 0; - - Node* styleNode = context->firstNode(); - if (!styleNode) { - styleNode = context->startPosition().node(); - if (!styleNode) - return 0; - } - - Document* document = styleNode->document(); - RefPtr<DocumentFragment> fragment = document->createDocumentFragment(); - - if (text.isEmpty()) - return fragment.release(); - - String string = text; - 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); - if (string.endsWith("\n")) { - RefPtr<Element> element = createBreakElement(document); - element->setAttribute(classAttr, AppleInterchangeNewline); - fragment->appendChild(element.release(), ec); - ASSERT(!ec); - } - return fragment.release(); - } - - // A string with no newlines gets added inline, rather than being put into a paragraph. - if (string.find('\n') == notFound) { - fillContainerFromString(fragment.get(), string); - return fragment.release(); - } - - // Break string into paragraphs. Extra line breaks turn into empty paragraphs. - Node* blockNode = enclosingBlock(context->firstNode()); - Element* block = static_cast<Element*>(blockNode); - bool useClonesOfEnclosingBlock = blockNode - && blockNode->isElementNode() - && !block->hasTagName(bodyTag) - && !block->hasTagName(htmlTag) - && block != editableRootForPosition(context->startPosition()); - - Vector<String> list; - string.split('\n', true, list); // true gets us empty strings in the list - size_t numLines = list.size(); - for (size_t i = 0; i < numLines; ++i) { - const String& s = list[i]; - - RefPtr<Element> element; - if (s.isEmpty() && i + 1 == numLines) { - // For last line, use the "magic BR" rather than a P. - element = createBreakElement(document); - element->setAttribute(classAttr, AppleInterchangeNewline); - } else { - if (useClonesOfEnclosingBlock) - element = block->cloneElementWithoutChildren(); - else - element = createDefaultParagraphElement(document); - fillContainerFromString(element.get(), s); - } - fragment->appendChild(element.release(), ec); - ASSERT(!ec); - } - return fragment.release(); -} - -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; - size_t size = nodes.size(); - for (size_t i = 0; i < size; ++i) { - RefPtr<Element> element = createDefaultParagraphElement(document); - element->appendChild(nodes[i], ec); - ASSERT(!ec); - fragment->appendChild(element.release(), ec); - ASSERT(!ec); - } - - if (document->frame()) - document->frame()->editor()->deleteButtonController()->enable(); - - return fragment.release(); -} - -String createFullMarkup(const Node* node) -{ - if (!node) - return String(); - - Document* document = node->document(); - if (!document) - return String(); - - Frame* frame = document->frame(); - if (!frame) - return String(); - - // FIXME: This is never "for interchange". Is that right? - String markupString = createMarkup(node, IncludeNode, 0); - Node::NodeType nodeType = node->nodeType(); - if (nodeType != Node::DOCUMENT_NODE && nodeType != Node::DOCUMENT_TYPE_NODE) - markupString = frame->documentTypeString() + markupString; - - return markupString; -} - -String createFullMarkup(const Range* range) -{ - if (!range) - return String(); - - Node* node = range->startContainer(); - if (!node) - return String(); - - Document* document = node->document(); - if (!document) - return String(); - - Frame* frame = document->frame(); - if (!frame) - return String(); - - // FIXME: This is always "for interchange". Is that right? See the previous method. - return frame->documentTypeString() + createMarkup(range, 0, AnnotateForInterchange); -} - -String urlToMarkup(const KURL& url, const String& title) -{ - Vector<UChar> markup; - append(markup, "<a href=\""); - append(markup, url.string()); - append(markup, "\">"); - appendCharactersReplacingEntities(markup, title.characters(), title.length(), EntityMaskInPCDATA); - append(markup, "</a>"); - return String::adopt(markup); -} - -} |