| Index: third_party/WebKit/WebCore/editing/markup.cpp
|
| ===================================================================
|
| --- third_party/WebKit/WebCore/editing/markup.cpp (revision 9391)
|
| +++ third_party/WebKit/WebCore/editing/markup.cpp (working copy)
|
| @@ -1,1226 +1,1239 @@
|
| -/*
|
| - * Copyright (C) 2004, 2005, 2006, 2007, 2008 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 "Comment.h"
|
| -#include "CSSComputedStyleDeclaration.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 "Document.h"
|
| -#include "DocumentFragment.h"
|
| -#include "DocumentType.h"
|
| -#include "Editor.h"
|
| -#include "Frame.h"
|
| -#include "HTMLElement.h"
|
| -#include "HTMLNames.h"
|
| -#include "InlineTextBox.h"
|
| -#include "Logging.h"
|
| -#include "ProcessingInstruction.h"
|
| -#include "QualifiedName.h"
|
| -#include "Range.h"
|
| -#include "Selection.h"
|
| -#include "TextIterator.h"
|
| -#include "htmlediting.h"
|
| -#include "visible_units.h"
|
| -#include <wtf/StdLibExtras.h>
|
| -
|
| -using namespace std;
|
| -
|
| -namespace WebCore {
|
| -
|
| -using namespace HTMLNames;
|
| -
|
| -static inline bool shouldSelfClose(const Node *node);
|
| -
|
| -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 appendAttributeValue(Vector<UChar>& result, const String& attr, bool escapeNBSP)
|
| -{
|
| - const UChar* uchars = attr.characters();
|
| - unsigned len = attr.length();
|
| - unsigned lastCopiedFrom = 0;
|
| -
|
| - DEFINE_STATIC_LOCAL(const String, ampEntity, ("&"));
|
| - DEFINE_STATIC_LOCAL(const String, gtEntity, (">"));
|
| - DEFINE_STATIC_LOCAL(const String, ltEntity, ("<"));
|
| - DEFINE_STATIC_LOCAL(const String, quotEntity, ("""));
|
| - DEFINE_STATIC_LOCAL(const String, nbspEntity, (" "));
|
| -
|
| - for (unsigned i = 0; i < len; ++i) {
|
| - UChar c = uchars[i];
|
| - switch (c) {
|
| - case '&':
|
| - result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| - append(result, ampEntity);
|
| - lastCopiedFrom = i + 1;
|
| - break;
|
| - case '<':
|
| - result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| - append(result, ltEntity);
|
| - lastCopiedFrom = i + 1;
|
| - break;
|
| - case '>':
|
| - result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| - append(result, gtEntity);
|
| - lastCopiedFrom = i + 1;
|
| - break;
|
| - case '"':
|
| - result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| - append(result, quotEntity);
|
| - lastCopiedFrom = i + 1;
|
| - break;
|
| - case noBreakSpace:
|
| - if (escapeNBSP) {
|
| - result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| - append(result, nbspEntity);
|
| - lastCopiedFrom = i + 1;
|
| - }
|
| - break;
|
| - }
|
| - }
|
| -
|
| - result.append(uchars + lastCopiedFrom, len - lastCopiedFrom);
|
| -}
|
| -
|
| -static void appendEscapedContent(Vector<UChar>& result, pair<const UChar*, size_t> range, bool escapeNBSP)
|
| -{
|
| - const UChar* uchars = range.first;
|
| - unsigned len = range.second;
|
| - unsigned lastCopiedFrom = 0;
|
| -
|
| - DEFINE_STATIC_LOCAL(const String, ampEntity, ("&"));
|
| - DEFINE_STATIC_LOCAL(const String, gtEntity, (">"));
|
| - DEFINE_STATIC_LOCAL(const String, ltEntity, ("<"));
|
| - DEFINE_STATIC_LOCAL(const String, nbspEntity, (" "));
|
| -
|
| - for (unsigned i = 0; i < len; ++i) {
|
| - UChar c = uchars[i];
|
| - switch (c) {
|
| - case '&':
|
| - result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| - append(result, ampEntity);
|
| - lastCopiedFrom = i + 1;
|
| - break;
|
| - case '<':
|
| - result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| - append(result, ltEntity);
|
| - lastCopiedFrom = i + 1;
|
| - break;
|
| - case '>':
|
| - result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| - append(result, gtEntity);
|
| - lastCopiedFrom = i + 1;
|
| - break;
|
| - case noBreakSpace:
|
| - if (escapeNBSP) {
|
| - result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| - append(result, nbspEntity);
|
| - lastCopiedFrom = i + 1;
|
| - }
|
| - break;
|
| - }
|
| - }
|
| -
|
| - result.append(uchars + lastCopiedFrom, len - lastCopiedFrom);
|
| -}
|
| -
|
| -static String escapeContentText(const String& in, bool escapeNBSP)
|
| -{
|
| - Vector<UChar> buffer;
|
| - appendEscapedContent(buffer, make_pair(in.characters(), in.length()), escapeNBSP);
|
| - return String::adopt(buffer);
|
| -}
|
| -
|
| -static void appendQuotedURLAttributeValue(Vector<UChar>& result, const String& urlString)
|
| -{
|
| - UChar quoteChar = '\"';
|
| - String strippedURLString = urlString.stripWhiteSpace();
|
| - if (protocolIs(strippedURLString, "javascript")) {
|
| - // minimal escaping for javascript urls
|
| - if (strippedURLString.contains('"')) {
|
| - if (strippedURLString.contains('\''))
|
| - strippedURLString.replace('\"', """);
|
| - else
|
| - quoteChar = '\'';
|
| - }
|
| - result.append(quoteChar);
|
| - append(result, strippedURLString);
|
| - result.append(quoteChar);
|
| - return;
|
| - }
|
| -
|
| - // FIXME: This does not fully match other browsers. Firefox percent-escapes non-ASCII characters for innerHTML.
|
| - result.append(quoteChar);
|
| - appendAttributeValue(result, urlString, false);
|
| - result.append(quoteChar);
|
| -}
|
| -
|
| -static String 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 inline pair<const UChar*, size_t> ucharRange(const Node *node, const Range *range)
|
| -{
|
| - String str = node->nodeValue();
|
| - const UChar* characters = str.characters();
|
| - size_t length = str.length();
|
| -
|
| - if (range) {
|
| - ExceptionCode ec;
|
| - if (node == range->endContainer(ec))
|
| - length = range->endOffset(ec);
|
| - if (node == range->startContainer(ec)) {
|
| - size_t start = range->startOffset(ec);
|
| - characters += start;
|
| - length -= start;
|
| - }
|
| - }
|
| -
|
| - return make_pair(characters, length);
|
| -}
|
| -
|
| -static inline void appendUCharRange(Vector<UChar>& result, const pair<const UChar*, size_t> range)
|
| -{
|
| - result.append(range.first, range.second);
|
| -}
|
| -
|
| -static String 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());
|
| -}
|
| -
|
| -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();
|
| -}
|
| -
|
| -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 void removeDefaultStyles(CSSMutableStyleDeclaration* style, Document* document)
|
| -{
|
| - if (!document || !document->documentElement())
|
| - return;
|
| -
|
| - RefPtr<CSSMutableStyleDeclaration> documentStyle = computedStyle(document->documentElement())->copyInheritableProperties();
|
| - documentStyle->diff(style);
|
| -}
|
| -
|
| -static bool shouldAddNamespaceElem(const Element* elem)
|
| -{
|
| - // Don't add namespace attribute if it is already defined for this elem.
|
| - const AtomicString& prefix = elem->prefix();
|
| - AtomicString attr = !prefix.isEmpty() ? "xmlns:" + prefix : "xmlns";
|
| - return !elem->hasAttribute(attr);
|
| -}
|
| -
|
| -static bool shouldAddNamespaceAttr(const Attribute* attr, HashMap<AtomicStringImpl*, AtomicStringImpl*>& namespaces)
|
| -{
|
| - // Don't add namespace attributes twice
|
| - DEFINE_STATIC_LOCAL(const AtomicString, xmlnsURI, ("http://www.w3.org/2000/xmlns/"));
|
| - DEFINE_STATIC_LOCAL(const QualifiedName, xmlnsAttr, (nullAtom, "xmlns", xmlnsURI));
|
| - if (attr->name() == xmlnsAttr) {
|
| - namespaces.set(emptyAtom.impl(), attr->value().impl());
|
| - return false;
|
| - }
|
| -
|
| - QualifiedName xmlnsPrefixAttr("xmlns", attr->localName(), xmlnsURI);
|
| - if (attr->name() == xmlnsPrefixAttr) {
|
| - namespaces.set(attr->localName().impl(), attr->value().impl());
|
| - return false;
|
| - }
|
| -
|
| - return true;
|
| -}
|
| -
|
| -static void appendNamespace(Vector<UChar>& result, const AtomicString& prefix, const AtomicString& ns, HashMap<AtomicStringImpl*, AtomicStringImpl*>& namespaces)
|
| -{
|
| - if (ns.isEmpty())
|
| - return;
|
| -
|
| - // Use emptyAtoms's impl() for both null and empty strings since the HashMap can't handle 0 as a key
|
| - AtomicStringImpl* pre = prefix.isEmpty() ? emptyAtom.impl() : prefix.impl();
|
| - AtomicStringImpl* foundNS = namespaces.get(pre);
|
| - if (foundNS != ns.impl()) {
|
| - namespaces.set(pre, ns.impl());
|
| - DEFINE_STATIC_LOCAL(const String, xmlns, ("xmlns"));
|
| - result.append(' ');
|
| - append(result, xmlns);
|
| - if (!prefix.isEmpty()) {
|
| - result.append(':');
|
| - append(result, prefix);
|
| - }
|
| -
|
| - result.append('=');
|
| - result.append('"');
|
| - appendAttributeValue(result, ns, false);
|
| - result.append('"');
|
| - }
|
| -}
|
| -
|
| -static void appendDocumentType(Vector<UChar>& result, const DocumentType* n)
|
| -{
|
| - if (n->name().isEmpty())
|
| - return;
|
| -
|
| - append(result, "<!DOCTYPE ");
|
| - append(result, n->name());
|
| - if (!n->publicId().isEmpty()) {
|
| - append(result, " PUBLIC \"");
|
| - append(result, n->publicId());
|
| - append(result, "\"");
|
| - if (!n->systemId().isEmpty()) {
|
| - append(result, " \"");
|
| - append(result, n->systemId());
|
| - append(result, "\"");
|
| - }
|
| - } else if (!n->systemId().isEmpty()) {
|
| - append(result, " SYSTEM \"");
|
| - append(result, n->systemId());
|
| - append(result, "\"");
|
| - }
|
| - if (!n->internalSubset().isEmpty()) {
|
| - append(result, " [");
|
| - append(result, n->internalSubset());
|
| - append(result, "]");
|
| - }
|
| - append(result, ">");
|
| -}
|
| -
|
| -static void appendStartMarkup(Vector<UChar>& result, const Node *node, const Range *range, EAnnotateForInterchange annotate, bool convertBlocksToInlines = false, HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0)
|
| -{
|
| - bool documentIsHTML = node->document()->isHTMLDocument();
|
| - switch (node->nodeType()) {
|
| - case Node::TEXT_NODE: {
|
| - if (Node* parent = node->parentNode()) {
|
| - if (parent->hasTagName(scriptTag)
|
| - || parent->hasTagName(styleTag)
|
| - || parent->hasTagName(textareaTag)
|
| - || parent->hasTagName(xmpTag)) {
|
| - appendUCharRange(result, ucharRange(node, range));
|
| - break;
|
| - }
|
| - }
|
| - if (!annotate) {
|
| - appendEscapedContent(result, ucharRange(node, range), documentIsHTML);
|
| - break;
|
| - }
|
| -
|
| - bool useRenderedText = !enclosingNodeWithTag(Position(const_cast<Node*>(node), 0), selectTag);
|
| - String markup = escapeContentText(useRenderedText ? renderedText(node, range) : stringValueForRange(node, range), false);
|
| - if (annotate)
|
| - markup = convertHTMLTextToInterchangeFormat(markup, static_cast<const Text*>(node));
|
| - append(result, markup);
|
| - break;
|
| - }
|
| - case Node::COMMENT_NODE:
|
| - // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->".
|
| - append(result, "<!--");
|
| - append(result, static_cast<const Comment*>(node)->nodeValue());
|
| - append(result, "-->");
|
| - break;
|
| - case Node::DOCUMENT_NODE:
|
| - case Node::DOCUMENT_FRAGMENT_NODE:
|
| - break;
|
| - case Node::DOCUMENT_TYPE_NODE:
|
| - appendDocumentType(result, static_cast<const DocumentType*>(node));
|
| - break;
|
| - case Node::PROCESSING_INSTRUCTION_NODE: {
|
| - // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>".
|
| - const ProcessingInstruction* n = static_cast<const ProcessingInstruction*>(node);
|
| - append(result, "<?");
|
| - append(result, n->target());
|
| - append(result, " ");
|
| - append(result, n->data());
|
| - append(result, "?>");
|
| - break;
|
| - }
|
| - case Node::ELEMENT_NODE: {
|
| - result.append('<');
|
| - const Element* el = static_cast<const Element*>(node);
|
| - bool convert = convertBlocksToInlines & isBlock(const_cast<Node*>(node));
|
| - append(result, el->nodeNamePreservingCase());
|
| - NamedAttrMap *attrs = el->attributes();
|
| - unsigned length = attrs->length();
|
| - if (!documentIsHTML && namespaces && shouldAddNamespaceElem(el))
|
| - appendNamespace(result, el->prefix(), el->namespaceURI(), *namespaces);
|
| -
|
| - for (unsigned int i = 0; i < length; i++) {
|
| - Attribute *attr = attrs->attributeItem(i);
|
| - // We'll handle the style attribute separately, below.
|
| - if (attr->name() == styleAttr && el->isHTMLElement() && (annotate || convert))
|
| - continue;
|
| - result.append(' ');
|
| -
|
| - if (documentIsHTML)
|
| - append(result, attr->name().localName());
|
| - else
|
| - append(result, attr->name().toString());
|
| -
|
| - result.append('=');
|
| -
|
| - if (el->isURLAttribute(attr))
|
| - appendQuotedURLAttributeValue(result, attr->value());
|
| - else {
|
| - result.append('\"');
|
| - appendAttributeValue(result, attr->value(), documentIsHTML);
|
| - result.append('\"');
|
| - }
|
| -
|
| - if (!documentIsHTML && namespaces && shouldAddNamespaceAttr(attr, *namespaces))
|
| - appendNamespace(result, attr->prefix(), attr->namespaceURI(), *namespaces);
|
| - }
|
| -
|
| - if (el->isHTMLElement() && (annotate || convert)) {
|
| - Element* element = const_cast<Element*>(el);
|
| - RefPtr<CSSMutableStyleDeclaration> style = static_cast<HTMLElement*>(element)->getInlineStyleDecl()->copy();
|
| - if (annotate) {
|
| - RefPtr<CSSMutableStyleDeclaration> styleFromMatchedRules = styleFromMatchedRulesForElement(const_cast<Element*>(el));
|
| - // 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 (convert)
|
| - style->setProperty(CSSPropertyDisplay, CSSValueInline, true);
|
| - if (style->length() > 0) {
|
| - DEFINE_STATIC_LOCAL(const String, stylePrefix, (" style=\""));
|
| - append(result, stylePrefix);
|
| - appendAttributeValue(result, style->cssText(), documentIsHTML);
|
| - result.append('\"');
|
| - }
|
| - }
|
| -
|
| - if (shouldSelfClose(el)) {
|
| - if (el->isHTMLElement())
|
| - result.append(' '); // XHTML 1.0 <-> HTML compatibility.
|
| - result.append('/');
|
| - }
|
| - result.append('>');
|
| - break;
|
| - }
|
| - case Node::CDATA_SECTION_NODE: {
|
| - // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>".
|
| - const CDATASection* n = static_cast<const CDATASection*>(node);
|
| - append(result, "<![CDATA[");
|
| - append(result, n->data());
|
| - append(result, "]]>");
|
| - break;
|
| - }
|
| - case Node::ATTRIBUTE_NODE:
|
| - case Node::ENTITY_NODE:
|
| - case Node::ENTITY_REFERENCE_NODE:
|
| - case Node::NOTATION_NODE:
|
| - case Node::XPATH_NAMESPACE_NODE:
|
| - ASSERT_NOT_REACHED();
|
| - break;
|
| - }
|
| -}
|
| -
|
| -static String getStartMarkup(const Node *node, const Range *range, EAnnotateForInterchange annotate, bool convertBlocksToInlines = false, HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0)
|
| -{
|
| - Vector<UChar> result;
|
| - appendStartMarkup(result, node, range, annotate, convertBlocksToInlines, namespaces);
|
| - return String::adopt(result);
|
| -}
|
| -
|
| -static inline bool doesHTMLForbidEndTag(const Node *node)
|
| -{
|
| - if (node->isHTMLElement()) {
|
| - const HTMLElement* htmlElt = static_cast<const HTMLElement*>(node);
|
| - return (htmlElt->endTagRequirement() == TagStatusForbidden);
|
| - }
|
| - return false;
|
| -}
|
| -
|
| -// Rules of self-closure
|
| -// 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.
|
| -static inline bool shouldSelfClose(const Node *node)
|
| -{
|
| - if (node->document()->isHTMLDocument())
|
| - return false;
|
| - if (node->hasChildNodes())
|
| - return false;
|
| - if (node->isHTMLElement() && !doesHTMLForbidEndTag(node))
|
| - return false;
|
| - return true;
|
| -}
|
| -
|
| -static void appendEndMarkup(Vector<UChar>& result, const Node* node)
|
| -{
|
| - if (!node->isElementNode() || shouldSelfClose(node) || (!node->hasChildNodes() && doesHTMLForbidEndTag(node)))
|
| - return;
|
| -
|
| - result.append('<');
|
| - result.append('/');
|
| - append(result, static_cast<const Element*>(node)->nodeNamePreservingCase());
|
| - result.append('>');
|
| -}
|
| -
|
| -static String getEndMarkup(const Node *node)
|
| -{
|
| - Vector<UChar> result;
|
| - appendEndMarkup(result, node);
|
| - return String::adopt(result);
|
| -}
|
| -
|
| -static void appendMarkup(Vector<UChar>& result, Node* startNode, bool onlyIncludeChildren, Vector<Node*>* nodes, const HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0)
|
| -{
|
| - HashMap<AtomicStringImpl*, AtomicStringImpl*> namespaceHash;
|
| - if (namespaces)
|
| - namespaceHash = *namespaces;
|
| -
|
| - if (!onlyIncludeChildren) {
|
| - if (nodes)
|
| - nodes->append(startNode);
|
| -
|
| - appendStartMarkup(result,startNode, 0, DoNotAnnotateForInterchange, false, &namespaceHash);
|
| - }
|
| - // print children
|
| - if (!(startNode->document()->isHTMLDocument() && doesHTMLForbidEndTag(startNode)))
|
| - for (Node* current = startNode->firstChild(); current; current = current->nextSibling())
|
| - appendMarkup(result, current, false, nodes, &namespaceHash);
|
| -
|
| - // Print my ending tag
|
| - if (!onlyIncludeChildren)
|
| - appendEndMarkup(result, startNode);
|
| -}
|
| -
|
| -static void completeURLs(Node* node, const String& baseURL)
|
| -{
|
| - Vector<AttributeChange> changes;
|
| -
|
| - KURL parsedBaseURL(baseURL);
|
| -
|
| - Node* end = node->traverseNextSibling();
|
| - for (Node* n = node; n != end; n = n->traverseNextNode()) {
|
| - if (n->isElementNode()) {
|
| - Element* e = static_cast<Element*>(n);
|
| - NamedAttrMap* attrs = e->attributes();
|
| - unsigned length = attrs->length();
|
| - for (unsigned i = 0; i < length; i++) {
|
| - Attribute* attr = attrs->attributeItem(i);
|
| - if (e->isURLAttribute(attr))
|
| - changes.append(AttributeChange(e, attr->name(), KURL(parsedBaseURL, attr->value()).string()));
|
| - }
|
| - }
|
| - }
|
| -
|
| - size_t numChanges = changes.size();
|
| - for (size_t i = 0; i < numChanges; ++i)
|
| - changes[i].apply();
|
| -}
|
| -
|
| -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 propertyMissingOrEqualToNone(CSSMutableStyleDeclaration* 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 elementHasTextDecorationProperty(const Node* node)
|
| -{
|
| - RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesAndInlineDecl(node);
|
| - if (!style)
|
| - return false;
|
| - return !propertyMissingOrEqualToNone(style.get(), CSSPropertyTextDecoration);
|
| -}
|
| -
|
| -static String joinMarkups(const Vector<String>& preMarkups, const Vector<String>& postMarkups)
|
| -{
|
| - size_t length = 0;
|
| -
|
| - size_t preCount = preMarkups.size();
|
| - for (size_t i = 0; i < preCount; ++i)
|
| - length += preMarkups[i].length();
|
| -
|
| - size_t postCount = postMarkups.size();
|
| - for (size_t i = 0; i < postCount; ++i)
|
| - length += postMarkups[i].length();
|
| -
|
| - Vector<UChar> result;
|
| - result.reserveInitialCapacity(length);
|
| -
|
| - for (size_t i = preCount; i > 0; --i)
|
| - append(result, preMarkups[i - 1]);
|
| -
|
| - for (size_t i = 0; i < postCount; ++i)
|
| - append(result, postMarkups[i]);
|
| -
|
| - return String::adopt(result);
|
| -}
|
| -
|
| -// 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 annotate, bool convertBlocksToInlines)
|
| -{
|
| - DEFINE_STATIC_LOCAL(const String, interchangeNewlineString, ("<br class=\"" AppleInterchangeNewline "\">"));
|
| -
|
| - if (!range)
|
| - return "";
|
| -
|
| - Document* document = range->ownerDocument();
|
| - if (!document)
|
| - return "";
|
| -
|
| - bool documentIsHTML = document->isHTMLDocument();
|
| -
|
| - // 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 == 0);
|
| - if (collapsed)
|
| - return "";
|
| - Node* commonAncestor = updatedRange->commonAncestorContainer(ec);
|
| - ASSERT(ec == 0);
|
| - if (!commonAncestor)
|
| - return "";
|
| -
|
| - document->updateLayoutIgnorePendingStylesheets();
|
| -
|
| - Vector<String> markups;
|
| - Vector<String> preMarkups;
|
| - Node* pastEnd = updatedRange->pastLastNode();
|
| - Node* lastClosed = 0;
|
| - Vector<Node*> ancestorsToClose;
|
| -
|
| - Node* startNode = updatedRange->firstNode();
|
| - VisiblePosition visibleStart(updatedRange->startPosition(), VP_DEFAULT_AFFINITY);
|
| - VisiblePosition visibleEnd(updatedRange->endPosition(), VP_DEFAULT_AFFINITY);
|
| - if (annotate && needInterchangeNewlineAfter(visibleStart)) {
|
| - if (visibleStart == visibleEnd.previous()) {
|
| - if (deleteButton)
|
| - deleteButton->enable();
|
| - return interchangeNewlineString;
|
| - }
|
| -
|
| - markups.append(interchangeNewlineString);
|
| - startNode = visibleStart.next().deepEquivalent().node();
|
| - }
|
| -
|
| - Node* next;
|
| - 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 skipDescendants = false;
|
| - bool addMarkupForNode = true;
|
| -
|
| - if (!n->renderer() && !enclosingNodeWithTag(Position(n, 0), selectTag)) {
|
| - skipDescendants = true;
|
| - addMarkupForNode = false;
|
| - next = n->traverseNextSibling();
|
| - // Don't skip over pastEnd.
|
| - if (pastEnd && pastEnd->isDescendantOf(n))
|
| - next = pastEnd;
|
| - }
|
| -
|
| - 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 (addMarkupForNode) {
|
| - markups.append(getStartMarkup(n, updatedRange.get(), annotate));
|
| - if (nodes)
|
| - nodes->append(n);
|
| - }
|
| -
|
| - 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(getEndMarkup(n));
|
| - lastClosed = n;
|
| - }
|
| -
|
| - // Check if the node is the last leaf of a tree.
|
| - if (!n->nextSibling() || next == pastEnd) {
|
| - if (!ancestorsToClose.isEmpty()) {
|
| - // Close up the ancestors.
|
| - 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(getEndMarkup(ancestor));
|
| - lastClosed = ancestor;
|
| - ancestorsToClose.removeLast();
|
| - } 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));
|
| - preMarkups.append(getStartMarkup(parent, updatedRange.get(), annotate));
|
| - markups.append(getEndMarkup(parent));
|
| - if (nodes)
|
| - nodes->append(parent);
|
| - lastClosed = parent;
|
| - }
|
| - }
|
| - }
|
| - } else if (addMarkupForNode && !skipDescendants)
|
| - // We added markup for this node, and we're descending into it. Set it to close eventually.
|
| - ancestorsToClose.append(n);
|
| - }
|
| -
|
| - // Include ancestors that aren't completely inside the range but are required to retain
|
| - // the structure and appearance of the copied markup.
|
| - Node* specialCommonAncestor = 0;
|
| - Node* commonAncestorBlock = commonAncestor ? enclosingBlock(commonAncestor) : 0;
|
| - if (annotate && commonAncestorBlock) {
|
| - if (commonAncestorBlock->hasTagName(tbodyTag) || commonAncestorBlock->hasTagName(trTag)) {
|
| - Node* table = commonAncestorBlock->parentNode();
|
| - while (table && !table->hasTagName(tableTag))
|
| - table = table->parentNode();
|
| - if (table)
|
| - specialCommonAncestor = table;
|
| - } else if (commonAncestorBlock->hasTagName(listingTag)
|
| - || commonAncestorBlock->hasTagName(olTag)
|
| - || commonAncestorBlock->hasTagName(preTag)
|
| - || commonAncestorBlock->hasTagName(tableTag)
|
| - || commonAncestorBlock->hasTagName(ulTag)
|
| - || commonAncestorBlock->hasTagName(xmpTag))
|
| - specialCommonAncestor = commonAncestorBlock;
|
| - }
|
| -
|
| - // Retain the Mail quote level by including all ancestor mail block quotes.
|
| - if (lastClosed && annotate) {
|
| - for (Node *ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode())
|
| - if (isMailBlockquote(ancestor))
|
| - specialCommonAncestor = ancestor;
|
| - }
|
| -
|
| - Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor;
|
| - if (checkAncestor->renderer()) {
|
| - RefPtr<CSSMutableStyleDeclaration> checkAncestorStyle = computedStyle(checkAncestor)->copyInheritableProperties();
|
| - if (!propertyMissingOrEqualToNone(checkAncestorStyle.get(), CSSPropertyWebkitTextDecorationsInEffect))
|
| - specialCommonAncestor = enclosingNodeOfType(Position(checkAncestor, 0), &elementHasTextDecorationProperty);
|
| - }
|
| -
|
| - // 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;
|
| -
|
| - Node* body = enclosingNodeWithTag(Position(commonAncestor, 0), bodyTag);
|
| - // FIXME: Only include markup for a fully selected root (and ancestors of lastClosed up to that root) if
|
| - // there are styles/attributes on those nodes that need to be included to preserve the appearance of the copied markup.
|
| - // FIXME: Do this for all fully selected blocks, not just the body.
|
| - Node* fullySelectedRoot = body && *Selection::selectionFromContentsOfNode(body).toRange() == *updatedRange ? body : 0;
|
| - if (annotate && fullySelectedRoot)
|
| - specialCommonAncestor = fullySelectedRoot;
|
| -
|
| - if (specialCommonAncestor && lastClosed) {
|
| - // Also include all of the ancestors of lastClosed up to this special ancestor.
|
| - for (Node* ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
|
| - if (ancestor == fullySelectedRoot && !convertBlocksToInlines) {
|
| - RefPtr<CSSMutableStyleDeclaration> style = 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 (!style->getPropertyCSSValue(CSSPropertyBackgroundImage) && static_cast<Element*>(fullySelectedRoot)->hasAttribute(backgroundAttr))
|
| - style->setProperty(CSSPropertyBackgroundImage, "url('" + static_cast<Element*>(fullySelectedRoot)->getAttribute(backgroundAttr) + "')");
|
| -
|
| - if (style->length()) {
|
| - Vector<UChar> openTag;
|
| - DEFINE_STATIC_LOCAL(const String, divStyle, ("<div style=\""));
|
| - append(openTag, divStyle);
|
| - appendAttributeValue(openTag, style->cssText(), documentIsHTML);
|
| - openTag.append('\"');
|
| - openTag.append('>');
|
| - preMarkups.append(String::adopt(openTag));
|
| -
|
| - DEFINE_STATIC_LOCAL(const String, divCloseTag, ("</div>"));
|
| - markups.append(divCloseTag);
|
| - }
|
| - } else {
|
| - preMarkups.append(getStartMarkup(ancestor, updatedRange.get(), annotate, convertBlocksToInlines));
|
| - markups.append(getEndMarkup(ancestor));
|
| - }
|
| - if (nodes)
|
| - nodes->append(ancestor);
|
| -
|
| - lastClosed = ancestor;
|
| -
|
| - if (ancestor == specialCommonAncestor)
|
| - break;
|
| - }
|
| - }
|
| -
|
| - DEFINE_STATIC_LOCAL(const String, styleSpanOpen, ("<span class=\"" AppleStyleSpanClass "\" style=\""));
|
| - DEFINE_STATIC_LOCAL(const String, styleSpanClose, ("</span>"));
|
| -
|
| - // Add a wrapper span with the styles that all of the nodes in the markup inherit.
|
| - Node* parentOfLastClosed = lastClosed ? lastClosed->parentNode() : 0;
|
| - if (parentOfLastClosed && parentOfLastClosed->renderer()) {
|
| - RefPtr<CSSMutableStyleDeclaration> style = computedStyle(parentOfLastClosed)->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(), parentOfLastClosed);
|
| -
|
| - // Document default styles will be added on another wrapper span.
|
| - removeDefaultStyles(style.get(), document);
|
| -
|
| - // 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->length() > 0) {
|
| - Vector<UChar> openTag;
|
| - append(openTag, styleSpanOpen);
|
| - appendAttributeValue(openTag, style->cssText(), documentIsHTML);
|
| - openTag.append('\"');
|
| - openTag.append('>');
|
| - preMarkups.append(String::adopt(openTag));
|
| -
|
| - markups.append(styleSpanClose);
|
| - }
|
| - }
|
| -
|
| - 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<CSSMutableStyleDeclaration> defaultStyle = computedStyle(document->documentElement())->copyInheritableProperties();
|
| -
|
| - if (defaultStyle->length() > 0) {
|
| - Vector<UChar> openTag;
|
| - append(openTag, styleSpanOpen);
|
| - appendAttributeValue(openTag, defaultStyle->cssText(), documentIsHTML);
|
| - openTag.append('\"');
|
| - openTag.append('>');
|
| - preMarkups.append(String::adopt(openTag));
|
| - markups.append(styleSpanClose);
|
| - }
|
| - }
|
| -
|
| - // FIXME: The interchange newline should be placed in the block that it's in, not after all of the content, unconditionally.
|
| - if (annotate && needInterchangeNewlineAfter(visibleEnd.previous()))
|
| - markups.append(interchangeNewlineString);
|
| -
|
| - if (deleteButton)
|
| - deleteButton->enable();
|
| -
|
| - return joinMarkups(preMarkups, markups);
|
| -}
|
| -
|
| -PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const String& markup, const String& baseURL)
|
| -{
|
| - ASSERT(document->documentElement()->isHTMLElement());
|
| - // FIXME: What if the document element is not an HTML element?
|
| - HTMLElement *element = static_cast<HTMLElement*>(document->documentElement());
|
| -
|
| - RefPtr<DocumentFragment> fragment = element->createContextualFragment(markup);
|
| -
|
| - if (fragment && !baseURL.isEmpty() && baseURL != blankURL() && baseURL != document->baseURL())
|
| - completeURLs(fragment.get(), baseURL);
|
| -
|
| - return fragment.release();
|
| -}
|
| -
|
| -String createMarkup(const Node* node, EChildrenOnly includeChildren, Vector<Node*>* nodes)
|
| -{
|
| - Vector<UChar> result;
|
| -
|
| - if (!node)
|
| - return "";
|
| -
|
| - Document* document = node->document();
|
| - Frame* frame = document->frame();
|
| - DeleteButtonController* deleteButton = frame ? frame->editor()->deleteButtonController() : 0;
|
| -
|
| - // disable the delete button so it's elements are not serialized into the markup
|
| - if (deleteButton) {
|
| - if (node->isDescendantOf(deleteButton->containerElement()))
|
| - return "";
|
| - deleteButton->disable();
|
| - }
|
| -
|
| - appendMarkup(result, const_cast<Node*>(node), includeChildren, nodes);
|
| -
|
| - if (deleteButton)
|
| - deleteButton->enable();
|
| -
|
| - return String::adopt(result);
|
| -}
|
| -
|
| -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 == 0);
|
| - return;
|
| - }
|
| -
|
| - ASSERT(string.find('\n') == -1);
|
| -
|
| - 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 == 0);
|
| - tabText = "";
|
| - }
|
| - RefPtr<Node> textNode = document->createTextNode(stringWithRebalancedWhitespace(s, first, i + 1 == numEntries));
|
| - 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 (i + 1 != numEntries)
|
| - tabText.append('\t');
|
| - else if (!tabText.isEmpty()) {
|
| - paragraph->appendChild(createTabSpanElement(document, tabText), ec);
|
| - ASSERT(ec == 0);
|
| - }
|
| -
|
| - first = false;
|
| - }
|
| -}
|
| -
|
| -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 == 0);
|
| - if (string.endsWith("\n")) {
|
| - RefPtr<Element> element = createBreakElement(document);
|
| - 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.
|
| - 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->cloneElement();
|
| - else
|
| - element = createDefaultParagraphElement(document);
|
| - fillContainerFromString(element.get(), s);
|
| - }
|
| - fragment->appendChild(element.release(), ec);
|
| - ASSERT(ec == 0);
|
| - }
|
| - 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 == 0);
|
| - fragment->appendChild(element.release(), ec);
|
| - ASSERT(ec == 0);
|
| - }
|
| -
|
| - 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);
|
| -}
|
| -
|
| -}
|
| -
|
| -
|
| +/*
|
| + * Copyright (C) 2004, 2005, 2006, 2007, 2008 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 "Comment.h"
|
| +#include "CSSComputedStyleDeclaration.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 "Document.h"
|
| +#include "DocumentFragment.h"
|
| +#include "DocumentType.h"
|
| +#include "Editor.h"
|
| +#include "Frame.h"
|
| +#include "HTMLElement.h"
|
| +#include "HTMLNames.h"
|
| +#include "InlineTextBox.h"
|
| +#include "Logging.h"
|
| +#include "ProcessingInstruction.h"
|
| +#include "QualifiedName.h"
|
| +#include "Range.h"
|
| +#include "Selection.h"
|
| +#include "TextIterator.h"
|
| +#include "htmlediting.h"
|
| +#include "visible_units.h"
|
| +#include <wtf/StdLibExtras.h>
|
| +
|
| +using namespace std;
|
| +
|
| +namespace WebCore {
|
| +
|
| +using namespace HTMLNames;
|
| +
|
| +static inline bool shouldSelfClose(const Node *node);
|
| +
|
| +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 appendAttributeValue(Vector<UChar>& result, const String& attr, bool escapeNBSP)
|
| +{
|
| + const UChar* uchars = attr.characters();
|
| + unsigned len = attr.length();
|
| + unsigned lastCopiedFrom = 0;
|
| +
|
| + DEFINE_STATIC_LOCAL(const String, ampEntity, ("&"));
|
| + DEFINE_STATIC_LOCAL(const String, gtEntity, (">"));
|
| + DEFINE_STATIC_LOCAL(const String, ltEntity, ("<"));
|
| + DEFINE_STATIC_LOCAL(const String, quotEntity, ("""));
|
| + DEFINE_STATIC_LOCAL(const String, nbspEntity, (" "));
|
| +
|
| + for (unsigned i = 0; i < len; ++i) {
|
| + UChar c = uchars[i];
|
| + switch (c) {
|
| + case '&':
|
| + result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| + append(result, ampEntity);
|
| + lastCopiedFrom = i + 1;
|
| + break;
|
| + case '<':
|
| + result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| + append(result, ltEntity);
|
| + lastCopiedFrom = i + 1;
|
| + break;
|
| + case '>':
|
| + result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| + append(result, gtEntity);
|
| + lastCopiedFrom = i + 1;
|
| + break;
|
| + case '"':
|
| + result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| + append(result, quotEntity);
|
| + lastCopiedFrom = i + 1;
|
| + break;
|
| + case noBreakSpace:
|
| + if (escapeNBSP) {
|
| + result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| + append(result, nbspEntity);
|
| + lastCopiedFrom = i + 1;
|
| + }
|
| + break;
|
| + }
|
| + }
|
| +
|
| + result.append(uchars + lastCopiedFrom, len - lastCopiedFrom);
|
| +}
|
| +
|
| +static void appendEscapedContent(Vector<UChar>& result, pair<const UChar*, size_t> range, bool escapeNBSP)
|
| +{
|
| + const UChar* uchars = range.first;
|
| + unsigned len = range.second;
|
| + unsigned lastCopiedFrom = 0;
|
| +
|
| + DEFINE_STATIC_LOCAL(const String, ampEntity, ("&"));
|
| + DEFINE_STATIC_LOCAL(const String, gtEntity, (">"));
|
| + DEFINE_STATIC_LOCAL(const String, ltEntity, ("<"));
|
| + DEFINE_STATIC_LOCAL(const String, nbspEntity, (" "));
|
| +
|
| + for (unsigned i = 0; i < len; ++i) {
|
| + UChar c = uchars[i];
|
| + switch (c) {
|
| + case '&':
|
| + result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| + append(result, ampEntity);
|
| + lastCopiedFrom = i + 1;
|
| + break;
|
| + case '<':
|
| + result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| + append(result, ltEntity);
|
| + lastCopiedFrom = i + 1;
|
| + break;
|
| + case '>':
|
| + result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| + append(result, gtEntity);
|
| + lastCopiedFrom = i + 1;
|
| + break;
|
| + case noBreakSpace:
|
| + if (escapeNBSP) {
|
| + result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
|
| + append(result, nbspEntity);
|
| + lastCopiedFrom = i + 1;
|
| + }
|
| + break;
|
| + }
|
| + }
|
| +
|
| + result.append(uchars + lastCopiedFrom, len - lastCopiedFrom);
|
| +}
|
| +
|
| +static String escapeContentText(const String& in, bool escapeNBSP)
|
| +{
|
| + Vector<UChar> buffer;
|
| + appendEscapedContent(buffer, make_pair(in.characters(), in.length()), escapeNBSP);
|
| + return String::adopt(buffer);
|
| +}
|
| +
|
| +static void appendQuotedURLAttributeValue(Vector<UChar>& result, const String& urlString)
|
| +{
|
| + UChar quoteChar = '\"';
|
| + String strippedURLString = urlString.stripWhiteSpace();
|
| + if (protocolIs(strippedURLString, "javascript")) {
|
| + // minimal escaping for javascript urls
|
| + if (strippedURLString.contains('"')) {
|
| + if (strippedURLString.contains('\''))
|
| + strippedURLString.replace('\"', """);
|
| + else
|
| + quoteChar = '\'';
|
| + }
|
| + result.append(quoteChar);
|
| + append(result, strippedURLString);
|
| + result.append(quoteChar);
|
| + return;
|
| + }
|
| +
|
| + // FIXME: This does not fully match other browsers. Firefox percent-escapes non-ASCII characters for innerHTML.
|
| + result.append(quoteChar);
|
| + appendAttributeValue(result, urlString, false);
|
| + result.append(quoteChar);
|
| +}
|
| +
|
| +static String 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 inline pair<const UChar*, size_t> ucharRange(const Node *node, const Range *range)
|
| +{
|
| + String str = node->nodeValue();
|
| + const UChar* characters = str.characters();
|
| + size_t length = str.length();
|
| +
|
| + if (range) {
|
| + ExceptionCode ec;
|
| + if (node == range->endContainer(ec))
|
| + length = range->endOffset(ec);
|
| + if (node == range->startContainer(ec)) {
|
| + size_t start = range->startOffset(ec);
|
| + characters += start;
|
| + length -= start;
|
| + }
|
| + }
|
| +
|
| + return make_pair(characters, length);
|
| +}
|
| +
|
| +static inline void appendUCharRange(Vector<UChar>& result, const pair<const UChar*, size_t> range)
|
| +{
|
| + result.append(range.first, range.second);
|
| +}
|
| +
|
| +static String 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());
|
| +}
|
| +
|
| +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();
|
| +}
|
| +
|
| +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 void removeDefaultStyles(CSSMutableStyleDeclaration* style, Document* document)
|
| +{
|
| + if (!document || !document->documentElement())
|
| + return;
|
| +
|
| + RefPtr<CSSMutableStyleDeclaration> documentStyle = computedStyle(document->documentElement())->copyInheritableProperties();
|
| + documentStyle->diff(style);
|
| +}
|
| +
|
| +static bool shouldAddNamespaceElem(const Element* elem)
|
| +{
|
| + // Don't add namespace attribute if it is already defined for this elem.
|
| + const AtomicString& prefix = elem->prefix();
|
| + AtomicString attr = !prefix.isEmpty() ? "xmlns:" + prefix : "xmlns";
|
| + return !elem->hasAttribute(attr);
|
| +}
|
| +
|
| +static bool shouldAddNamespaceAttr(const Attribute* attr, HashMap<AtomicStringImpl*, AtomicStringImpl*>& namespaces)
|
| +{
|
| + // Don't add namespace attributes twice
|
| + DEFINE_STATIC_LOCAL(const AtomicString, xmlnsURI, ("http://www.w3.org/2000/xmlns/"));
|
| + DEFINE_STATIC_LOCAL(const QualifiedName, xmlnsAttr, (nullAtom, "xmlns", xmlnsURI));
|
| + if (attr->name() == xmlnsAttr) {
|
| + namespaces.set(emptyAtom.impl(), attr->value().impl());
|
| + return false;
|
| + }
|
| +
|
| + QualifiedName xmlnsPrefixAttr("xmlns", attr->localName(), xmlnsURI);
|
| + if (attr->name() == xmlnsPrefixAttr) {
|
| + namespaces.set(attr->localName().impl(), attr->value().impl());
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +static void appendNamespace(Vector<UChar>& result, const AtomicString& prefix, const AtomicString& ns, HashMap<AtomicStringImpl*, AtomicStringImpl*>& namespaces)
|
| +{
|
| + if (ns.isEmpty())
|
| + return;
|
| +
|
| + // Use emptyAtoms's impl() for both null and empty strings since the HashMap can't handle 0 as a key
|
| + AtomicStringImpl* pre = prefix.isEmpty() ? emptyAtom.impl() : prefix.impl();
|
| + AtomicStringImpl* foundNS = namespaces.get(pre);
|
| + if (foundNS != ns.impl()) {
|
| + namespaces.set(pre, ns.impl());
|
| + DEFINE_STATIC_LOCAL(const String, xmlns, ("xmlns"));
|
| + result.append(' ');
|
| + append(result, xmlns);
|
| + if (!prefix.isEmpty()) {
|
| + result.append(':');
|
| + append(result, prefix);
|
| + }
|
| +
|
| + result.append('=');
|
| + result.append('"');
|
| + appendAttributeValue(result, ns, false);
|
| + result.append('"');
|
| + }
|
| +}
|
| +
|
| +static void appendDocumentType(Vector<UChar>& result, const DocumentType* n)
|
| +{
|
| + if (n->name().isEmpty())
|
| + return;
|
| +
|
| + append(result, "<!DOCTYPE ");
|
| + append(result, n->name());
|
| + if (!n->publicId().isEmpty()) {
|
| + append(result, " PUBLIC \"");
|
| + append(result, n->publicId());
|
| + append(result, "\"");
|
| + if (!n->systemId().isEmpty()) {
|
| + append(result, " \"");
|
| + append(result, n->systemId());
|
| + append(result, "\"");
|
| + }
|
| + } else if (!n->systemId().isEmpty()) {
|
| + append(result, " SYSTEM \"");
|
| + append(result, n->systemId());
|
| + append(result, "\"");
|
| + }
|
| + if (!n->internalSubset().isEmpty()) {
|
| + append(result, " [");
|
| + append(result, n->internalSubset());
|
| + append(result, "]");
|
| + }
|
| + append(result, ">");
|
| +}
|
| +
|
| +static void appendStartMarkup(Vector<UChar>& result, const Node *node, const Range *range, EAnnotateForInterchange annotate, bool convertBlocksToInlines = false, HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0)
|
| +{
|
| + bool documentIsHTML = node->document()->isHTMLDocument();
|
| + switch (node->nodeType()) {
|
| + case Node::TEXT_NODE: {
|
| + if (Node* parent = node->parentNode()) {
|
| + if (parent->hasTagName(scriptTag)
|
| + || parent->hasTagName(styleTag)
|
| + || parent->hasTagName(textareaTag)
|
| + || parent->hasTagName(xmpTag)) {
|
| + appendUCharRange(result, ucharRange(node, range));
|
| + break;
|
| + }
|
| + }
|
| + if (!annotate) {
|
| + appendEscapedContent(result, ucharRange(node, range), documentIsHTML);
|
| + break;
|
| + }
|
| +
|
| + bool useRenderedText = !enclosingNodeWithTag(Position(const_cast<Node*>(node), 0), selectTag);
|
| + String markup = escapeContentText(useRenderedText ? renderedText(node, range) : stringValueForRange(node, range), false);
|
| + if (annotate)
|
| + markup = convertHTMLTextToInterchangeFormat(markup, static_cast<const Text*>(node));
|
| + append(result, markup);
|
| + break;
|
| + }
|
| + case Node::COMMENT_NODE:
|
| + // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->".
|
| + append(result, "<!--");
|
| + append(result, static_cast<const Comment*>(node)->nodeValue());
|
| + append(result, "-->");
|
| + break;
|
| + case Node::DOCUMENT_NODE:
|
| + case Node::DOCUMENT_FRAGMENT_NODE:
|
| + break;
|
| + case Node::DOCUMENT_TYPE_NODE:
|
| + appendDocumentType(result, static_cast<const DocumentType*>(node));
|
| + break;
|
| + case Node::PROCESSING_INSTRUCTION_NODE: {
|
| + // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>".
|
| + const ProcessingInstruction* n = static_cast<const ProcessingInstruction*>(node);
|
| + append(result, "<?");
|
| + append(result, n->target());
|
| + append(result, " ");
|
| + append(result, n->data());
|
| + append(result, "?>");
|
| + break;
|
| + }
|
| + case Node::ELEMENT_NODE: {
|
| + result.append('<');
|
| + const Element* el = static_cast<const Element*>(node);
|
| + bool convert = convertBlocksToInlines & isBlock(const_cast<Node*>(node));
|
| + append(result, el->nodeNamePreservingCase());
|
| + NamedAttrMap *attrs = el->attributes();
|
| + unsigned length = attrs->length();
|
| + if (!documentIsHTML && namespaces && shouldAddNamespaceElem(el))
|
| + appendNamespace(result, el->prefix(), el->namespaceURI(), *namespaces);
|
| +
|
| + for (unsigned int i = 0; i < length; i++) {
|
| + Attribute *attr = attrs->attributeItem(i);
|
| + // We'll handle the style attribute separately, below.
|
| + if (attr->name() == styleAttr && el->isHTMLElement() && (annotate || convert))
|
| + continue;
|
| + result.append(' ');
|
| +
|
| + if (documentIsHTML)
|
| + append(result, attr->name().localName());
|
| + else
|
| + append(result, attr->name().toString());
|
| +
|
| + result.append('=');
|
| +
|
| + if (el->isURLAttribute(attr))
|
| + appendQuotedURLAttributeValue(result, attr->value());
|
| + else {
|
| + result.append('\"');
|
| + appendAttributeValue(result, attr->value(), documentIsHTML);
|
| + result.append('\"');
|
| + }
|
| +
|
| + if (!documentIsHTML && namespaces && shouldAddNamespaceAttr(attr, *namespaces))
|
| + appendNamespace(result, attr->prefix(), attr->namespaceURI(), *namespaces);
|
| + }
|
| +
|
| + if (el->isHTMLElement() && (annotate || convert)) {
|
| + Element* element = const_cast<Element*>(el);
|
| + RefPtr<CSSMutableStyleDeclaration> style = static_cast<HTMLElement*>(element)->getInlineStyleDecl()->copy();
|
| + if (annotate) {
|
| + RefPtr<CSSMutableStyleDeclaration> styleFromMatchedRules = styleFromMatchedRulesForElement(const_cast<Element*>(el));
|
| + // 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 (convert)
|
| + style->setProperty(CSSPropertyDisplay, CSSValueInline, true);
|
| + if (style->length() > 0) {
|
| + DEFINE_STATIC_LOCAL(const String, stylePrefix, (" style=\""));
|
| + append(result, stylePrefix);
|
| + appendAttributeValue(result, style->cssText(), documentIsHTML);
|
| + result.append('\"');
|
| + }
|
| + }
|
| +
|
| + if (shouldSelfClose(el)) {
|
| + if (el->isHTMLElement())
|
| + result.append(' '); // XHTML 1.0 <-> HTML compatibility.
|
| + result.append('/');
|
| + }
|
| + result.append('>');
|
| + break;
|
| + }
|
| + case Node::CDATA_SECTION_NODE: {
|
| + // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>".
|
| + const CDATASection* n = static_cast<const CDATASection*>(node);
|
| + append(result, "<![CDATA[");
|
| + append(result, n->data());
|
| + append(result, "]]>");
|
| + break;
|
| + }
|
| + case Node::ATTRIBUTE_NODE:
|
| + case Node::ENTITY_NODE:
|
| + case Node::ENTITY_REFERENCE_NODE:
|
| + case Node::NOTATION_NODE:
|
| + case Node::XPATH_NAMESPACE_NODE:
|
| + ASSERT_NOT_REACHED();
|
| + break;
|
| + }
|
| +}
|
| +
|
| +static String getStartMarkup(const Node *node, const Range *range, EAnnotateForInterchange annotate, bool convertBlocksToInlines = false, HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0)
|
| +{
|
| + Vector<UChar> result;
|
| + appendStartMarkup(result, node, range, annotate, convertBlocksToInlines, namespaces);
|
| + return String::adopt(result);
|
| +}
|
| +
|
| +static inline bool doesHTMLForbidEndTag(const Node *node)
|
| +{
|
| + if (node->isHTMLElement()) {
|
| + const HTMLElement* htmlElt = static_cast<const HTMLElement*>(node);
|
| + return (htmlElt->endTagRequirement() == TagStatusForbidden);
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +// Rules of self-closure
|
| +// 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.
|
| +static inline bool shouldSelfClose(const Node *node)
|
| +{
|
| + if (node->document()->isHTMLDocument())
|
| + return false;
|
| + if (node->hasChildNodes())
|
| + return false;
|
| + if (node->isHTMLElement() && !doesHTMLForbidEndTag(node))
|
| + return false;
|
| + return true;
|
| +}
|
| +
|
| +static void appendEndMarkup(Vector<UChar>& result, const Node* node)
|
| +{
|
| + if (!node->isElementNode() || shouldSelfClose(node) || (!node->hasChildNodes() && doesHTMLForbidEndTag(node)))
|
| + return;
|
| +
|
| + result.append('<');
|
| + result.append('/');
|
| + append(result, static_cast<const Element*>(node)->nodeNamePreservingCase());
|
| + result.append('>');
|
| +}
|
| +
|
| +static String getEndMarkup(const Node *node)
|
| +{
|
| + Vector<UChar> result;
|
| + appendEndMarkup(result, node);
|
| + return String::adopt(result);
|
| +}
|
| +
|
| +static void appendMarkup(Vector<UChar>& result, Node* startNode, bool onlyIncludeChildren, Vector<Node*>* nodes, const HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0)
|
| +{
|
| + HashMap<AtomicStringImpl*, AtomicStringImpl*> namespaceHash;
|
| + if (namespaces)
|
| + namespaceHash = *namespaces;
|
| +
|
| + if (!onlyIncludeChildren) {
|
| + if (nodes)
|
| + nodes->append(startNode);
|
| +
|
| + appendStartMarkup(result,startNode, 0, DoNotAnnotateForInterchange, false, &namespaceHash);
|
| + }
|
| + // print children
|
| + if (!(startNode->document()->isHTMLDocument() && doesHTMLForbidEndTag(startNode)))
|
| + for (Node* current = startNode->firstChild(); current; current = current->nextSibling())
|
| + appendMarkup(result, current, false, nodes, &namespaceHash);
|
| +
|
| + // Print my ending tag
|
| + if (!onlyIncludeChildren)
|
| + appendEndMarkup(result, startNode);
|
| +}
|
| +
|
| +static void completeURLs(Node* node, const String& baseURL)
|
| +{
|
| + Vector<AttributeChange> changes;
|
| +
|
| + KURL parsedBaseURL(baseURL);
|
| +
|
| + Node* end = node->traverseNextSibling();
|
| + for (Node* n = node; n != end; n = n->traverseNextNode()) {
|
| + if (n->isElementNode()) {
|
| + Element* e = static_cast<Element*>(n);
|
| + NamedAttrMap* attrs = e->attributes();
|
| + unsigned length = attrs->length();
|
| + for (unsigned i = 0; i < length; i++) {
|
| + Attribute* attr = attrs->attributeItem(i);
|
| + if (e->isURLAttribute(attr))
|
| + changes.append(AttributeChange(e, attr->name(), KURL(parsedBaseURL, attr->value()).string()));
|
| + }
|
| + }
|
| + }
|
| +
|
| + size_t numChanges = changes.size();
|
| + for (size_t i = 0; i < numChanges; ++i)
|
| + changes[i].apply();
|
| +}
|
| +
|
| +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 propertyMissingOrEqualToNone(CSSMutableStyleDeclaration* 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 elementHasTextDecorationProperty(const Node* node)
|
| +{
|
| + RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesAndInlineDecl(node);
|
| + if (!style)
|
| + return false;
|
| + return !propertyMissingOrEqualToNone(style.get(), CSSPropertyTextDecoration);
|
| +}
|
| +
|
| +static String joinMarkups(const Vector<String>& preMarkups, const Vector<String>& postMarkups)
|
| +{
|
| + size_t length = 0;
|
| +
|
| + size_t preCount = preMarkups.size();
|
| + for (size_t i = 0; i < preCount; ++i)
|
| + length += preMarkups[i].length();
|
| +
|
| + size_t postCount = postMarkups.size();
|
| + for (size_t i = 0; i < postCount; ++i)
|
| + length += postMarkups[i].length();
|
| +
|
| + Vector<UChar> result;
|
| + result.reserveInitialCapacity(length);
|
| +
|
| + for (size_t i = preCount; i > 0; --i)
|
| + append(result, preMarkups[i - 1]);
|
| +
|
| + for (size_t i = 0; i < postCount; ++i)
|
| + append(result, postMarkups[i]);
|
| +
|
| + return String::adopt(result);
|
| +}
|
| +
|
| +bool isSpecialAncestorBlock(Node* node)
|
| +{
|
| + if (!node || !isBlock(node))
|
| + return false;
|
| +
|
| + return node->hasTagName(listingTag) ||
|
| + node->hasTagName(olTag) ||
|
| + node->hasTagName(preTag) ||
|
| + node->hasTagName(tableTag) ||
|
| + node->hasTagName(ulTag) ||
|
| + node->hasTagName(xmpTag) ||
|
| + node->hasTagName(h1Tag) ||
|
| + node->hasTagName(h2Tag) ||
|
| + node->hasTagName(h3Tag) ||
|
| + node->hasTagName(h4Tag) ||
|
| + node->hasTagName(h5Tag);
|
| +}
|
| +
|
| +// 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 annotate, bool convertBlocksToInlines)
|
| +{
|
| + DEFINE_STATIC_LOCAL(const String, interchangeNewlineString, ("<br class=\"" AppleInterchangeNewline "\">"));
|
| +
|
| + if (!range)
|
| + return "";
|
| +
|
| + Document* document = range->ownerDocument();
|
| + if (!document)
|
| + return "";
|
| +
|
| + bool documentIsHTML = document->isHTMLDocument();
|
| +
|
| + // 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 == 0);
|
| + if (collapsed)
|
| + return "";
|
| + Node* commonAncestor = updatedRange->commonAncestorContainer(ec);
|
| + ASSERT(ec == 0);
|
| + if (!commonAncestor)
|
| + return "";
|
| +
|
| + document->updateLayoutIgnorePendingStylesheets();
|
| +
|
| + Vector<String> markups;
|
| + Vector<String> preMarkups;
|
| + Node* pastEnd = updatedRange->pastLastNode();
|
| + Node* lastClosed = 0;
|
| + Vector<Node*> ancestorsToClose;
|
| +
|
| + Node* startNode = updatedRange->firstNode();
|
| + VisiblePosition visibleStart(updatedRange->startPosition(), VP_DEFAULT_AFFINITY);
|
| + VisiblePosition visibleEnd(updatedRange->endPosition(), VP_DEFAULT_AFFINITY);
|
| + if (annotate && needInterchangeNewlineAfter(visibleStart)) {
|
| + if (visibleStart == visibleEnd.previous()) {
|
| + if (deleteButton)
|
| + deleteButton->enable();
|
| + return interchangeNewlineString;
|
| + }
|
| +
|
| + markups.append(interchangeNewlineString);
|
| + startNode = visibleStart.next().deepEquivalent().node();
|
| + }
|
| +
|
| + Node* next;
|
| + 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 skipDescendants = false;
|
| + bool addMarkupForNode = true;
|
| +
|
| + if (!n->renderer() && !enclosingNodeWithTag(Position(n, 0), selectTag)) {
|
| + skipDescendants = true;
|
| + addMarkupForNode = false;
|
| + next = n->traverseNextSibling();
|
| + // Don't skip over pastEnd.
|
| + if (pastEnd && pastEnd->isDescendantOf(n))
|
| + next = pastEnd;
|
| + }
|
| +
|
| + 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 (addMarkupForNode) {
|
| + markups.append(getStartMarkup(n, updatedRange.get(), annotate));
|
| + if (nodes)
|
| + nodes->append(n);
|
| + }
|
| +
|
| + 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(getEndMarkup(n));
|
| + lastClosed = n;
|
| + }
|
| +
|
| + // Check if the node is the last leaf of a tree.
|
| + if (!n->nextSibling() || next == pastEnd) {
|
| + if (!ancestorsToClose.isEmpty()) {
|
| + // Close up the ancestors.
|
| + 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(getEndMarkup(ancestor));
|
| + lastClosed = ancestor;
|
| + ancestorsToClose.removeLast();
|
| + } 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));
|
| + preMarkups.append(getStartMarkup(parent, updatedRange.get(), annotate));
|
| + markups.append(getEndMarkup(parent));
|
| + if (nodes)
|
| + nodes->append(parent);
|
| + lastClosed = parent;
|
| + }
|
| + }
|
| + }
|
| + } else if (addMarkupForNode && !skipDescendants)
|
| + // We added markup for this node, and we're descending into it. Set it to close eventually.
|
| + ancestorsToClose.append(n);
|
| + }
|
| +
|
| + // Include ancestors that aren't completely inside the range but are required to retain
|
| + // the structure and appearance of the copied markup.
|
| + Node* specialCommonAncestor = 0;
|
| + Node* commonAncestorBlock = commonAncestor ? enclosingBlock(commonAncestor) : 0;
|
| + if (annotate && commonAncestorBlock) {
|
| + if (commonAncestorBlock->hasTagName(tbodyTag) || commonAncestorBlock->hasTagName(trTag)) {
|
| + Node* table = commonAncestorBlock->parentNode();
|
| + while (table && !table->hasTagName(tableTag))
|
| + table = table->parentNode();
|
| + if (table)
|
| + specialCommonAncestor = table;
|
| + } else if (isSpecialAncestorBlock(commonAncestorBlock))
|
| + specialCommonAncestor = commonAncestorBlock;
|
| + }
|
| +
|
| + // Retain the Mail quote level by including all ancestor mail block quotes.
|
| + if (lastClosed && annotate) {
|
| + for (Node *ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode())
|
| + if (isMailBlockquote(ancestor))
|
| + specialCommonAncestor = ancestor;
|
| + }
|
| +
|
| + Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor;
|
| + if (checkAncestor->renderer()) {
|
| + RefPtr<CSSMutableStyleDeclaration> checkAncestorStyle = computedStyle(checkAncestor)->copyInheritableProperties();
|
| + if (!propertyMissingOrEqualToNone(checkAncestorStyle.get(), CSSPropertyWebkitTextDecorationsInEffect))
|
| + specialCommonAncestor = enclosingNodeOfType(Position(checkAncestor, 0), &elementHasTextDecorationProperty);
|
| + }
|
| +
|
| + // 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;
|
| +
|
| + Node* body = enclosingNodeWithTag(Position(commonAncestor, 0), bodyTag);
|
| + // FIXME: Only include markup for a fully selected root (and ancestors of lastClosed up to that root) if
|
| + // there are styles/attributes on those nodes that need to be included to preserve the appearance of the copied markup.
|
| + // FIXME: Do this for all fully selected blocks, not just the body.
|
| + Node* fullySelectedRoot = body && *Selection::selectionFromContentsOfNode(body).toNormalizedRange() == *updatedRange ? body : 0;
|
| + if (annotate && fullySelectedRoot)
|
| + specialCommonAncestor = fullySelectedRoot;
|
| +
|
| + if (specialCommonAncestor && lastClosed) {
|
| + // Also include all of the ancestors of lastClosed up to this special ancestor.
|
| + for (Node* ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
|
| + if (ancestor == fullySelectedRoot && !convertBlocksToInlines) {
|
| + RefPtr<CSSMutableStyleDeclaration> style = 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 (!style->getPropertyCSSValue(CSSPropertyBackgroundImage) && static_cast<Element*>(fullySelectedRoot)->hasAttribute(backgroundAttr))
|
| + style->setProperty(CSSPropertyBackgroundImage, "url('" + static_cast<Element*>(fullySelectedRoot)->getAttribute(backgroundAttr) + "')");
|
| +
|
| + if (style->length()) {
|
| + Vector<UChar> openTag;
|
| + DEFINE_STATIC_LOCAL(const String, divStyle, ("<div style=\""));
|
| + append(openTag, divStyle);
|
| + appendAttributeValue(openTag, style->cssText(), documentIsHTML);
|
| + openTag.append('\"');
|
| + openTag.append('>');
|
| + preMarkups.append(String::adopt(openTag));
|
| +
|
| + DEFINE_STATIC_LOCAL(const String, divCloseTag, ("</div>"));
|
| + markups.append(divCloseTag);
|
| + }
|
| + } else {
|
| + preMarkups.append(getStartMarkup(ancestor, updatedRange.get(), annotate, convertBlocksToInlines));
|
| + markups.append(getEndMarkup(ancestor));
|
| + }
|
| + if (nodes)
|
| + nodes->append(ancestor);
|
| +
|
| + lastClosed = ancestor;
|
| +
|
| + if (ancestor == specialCommonAncestor)
|
| + break;
|
| + }
|
| + }
|
| +
|
| + DEFINE_STATIC_LOCAL(const String, styleSpanOpen, ("<span class=\"" AppleStyleSpanClass "\" style=\""));
|
| + DEFINE_STATIC_LOCAL(const String, styleSpanClose, ("</span>"));
|
| +
|
| + // Add a wrapper span with the styles that all of the nodes in the markup inherit.
|
| + Node* parentOfLastClosed = lastClosed ? lastClosed->parentNode() : 0;
|
| + if (parentOfLastClosed && parentOfLastClosed->renderer()) {
|
| + RefPtr<CSSMutableStyleDeclaration> style = computedStyle(parentOfLastClosed)->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(), parentOfLastClosed);
|
| +
|
| + // Document default styles will be added on another wrapper span.
|
| + removeDefaultStyles(style.get(), document);
|
| +
|
| + // 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->length() > 0) {
|
| + Vector<UChar> openTag;
|
| + append(openTag, styleSpanOpen);
|
| + appendAttributeValue(openTag, style->cssText(), documentIsHTML);
|
| + openTag.append('\"');
|
| + openTag.append('>');
|
| + preMarkups.append(String::adopt(openTag));
|
| +
|
| + markups.append(styleSpanClose);
|
| + }
|
| + }
|
| +
|
| + 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<CSSMutableStyleDeclaration> defaultStyle = computedStyle(document->documentElement())->copyInheritableProperties();
|
| +
|
| + if (defaultStyle->length() > 0) {
|
| + Vector<UChar> openTag;
|
| + append(openTag, styleSpanOpen);
|
| + appendAttributeValue(openTag, defaultStyle->cssText(), documentIsHTML);
|
| + openTag.append('\"');
|
| + openTag.append('>');
|
| + preMarkups.append(String::adopt(openTag));
|
| + markups.append(styleSpanClose);
|
| + }
|
| + }
|
| +
|
| + // FIXME: The interchange newline should be placed in the block that it's in, not after all of the content, unconditionally.
|
| + if (annotate && needInterchangeNewlineAfter(visibleEnd.previous()))
|
| + markups.append(interchangeNewlineString);
|
| +
|
| + if (deleteButton)
|
| + deleteButton->enable();
|
| +
|
| + return joinMarkups(preMarkups, markups);
|
| +}
|
| +
|
| +PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const String& markup, const String& baseURL)
|
| +{
|
| + ASSERT(document->documentElement()->isHTMLElement());
|
| + // FIXME: What if the document element is not an HTML element?
|
| + HTMLElement *element = static_cast<HTMLElement*>(document->documentElement());
|
| +
|
| + RefPtr<DocumentFragment> fragment = element->createContextualFragment(markup);
|
| +
|
| + if (fragment && !baseURL.isEmpty() && baseURL != blankURL() && baseURL != document->baseURL())
|
| + completeURLs(fragment.get(), baseURL);
|
| +
|
| + return fragment.release();
|
| +}
|
| +
|
| +String createMarkup(const Node* node, EChildrenOnly includeChildren, Vector<Node*>* nodes)
|
| +{
|
| + Vector<UChar> result;
|
| +
|
| + if (!node)
|
| + return "";
|
| +
|
| + Document* document = node->document();
|
| + Frame* frame = document->frame();
|
| + DeleteButtonController* deleteButton = frame ? frame->editor()->deleteButtonController() : 0;
|
| +
|
| + // disable the delete button so it's elements are not serialized into the markup
|
| + if (deleteButton) {
|
| + if (node->isDescendantOf(deleteButton->containerElement()))
|
| + return "";
|
| + deleteButton->disable();
|
| + }
|
| +
|
| + appendMarkup(result, const_cast<Node*>(node), includeChildren, nodes);
|
| +
|
| + if (deleteButton)
|
| + deleteButton->enable();
|
| +
|
| + return String::adopt(result);
|
| +}
|
| +
|
| +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 == 0);
|
| + return;
|
| + }
|
| +
|
| + ASSERT(string.find('\n') == -1);
|
| +
|
| + 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 == 0);
|
| + tabText = "";
|
| + }
|
| + RefPtr<Node> textNode = document->createTextNode(stringWithRebalancedWhitespace(s, first, i + 1 == numEntries));
|
| + 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 (i + 1 != numEntries)
|
| + tabText.append('\t');
|
| + else if (!tabText.isEmpty()) {
|
| + paragraph->appendChild(createTabSpanElement(document, tabText), ec);
|
| + ASSERT(ec == 0);
|
| + }
|
| +
|
| + first = false;
|
| + }
|
| +}
|
| +
|
| +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 == 0);
|
| + if (string.endsWith("\n")) {
|
| + RefPtr<Element> element = createBreakElement(document);
|
| + 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.
|
| + 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->cloneElement();
|
| + else
|
| + element = createDefaultParagraphElement(document);
|
| + fillContainerFromString(element.get(), s);
|
| + }
|
| + fragment->appendChild(element.release(), ec);
|
| + ASSERT(ec == 0);
|
| + }
|
| + 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 == 0);
|
| + fragment->appendChild(element.release(), ec);
|
| + ASSERT(ec == 0);
|
| + }
|
| +
|
| + 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);
|
| +}
|
| +
|
| +}
|
| +
|
| +
|
|
|