2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #include "CDATASection.h"
30 #include "CharacterNames.h"
32 #include "CSSComputedStyleDeclaration.h"
33 #include "CSSPrimitiveValue.h"
34 #include "CSSProperty.h"
35 #include "CSSPropertyNames.h"
37 #include "CSSRuleList.h"
38 #include "CSSStyleRule.h"
39 #include "CSSStyleSelector.h"
41 #include "CSSValueKeywords.h"
42 #include "DeleteButtonController.h"
44 #include "DocumentFragment.h"
45 #include "DocumentType.h"
48 #include "HTMLElement.h"
49 #include "HTMLNames.h"
50 #include "InlineTextBox.h"
52 #include "ProcessingInstruction.h"
53 #include "QualifiedName.h"
55 #include "VisibleSelection.h"
56 #include "TextIterator.h"
57 #include "htmlediting.h"
58 #include "visible_units.h"
59 #include <wtf/StdLibExtras.h>
65 using namespace HTMLNames;
67 static inline bool shouldSelfClose(const Node *node);
69 class AttributeChange {
72 : m_name(nullAtom, nullAtom, nullAtom)
76 AttributeChange(PassRefPtr<Element> element, const QualifiedName& name, const String& value)
77 : m_element(element), m_name(name), m_value(value)
83 m_element->setAttribute(m_name, m_value);
87 RefPtr<Element> m_element;
92 static void appendAttributeValue(Vector<UChar>& result, const String& attr, bool escapeNBSP)
94 const UChar* uchars = attr.characters();
95 unsigned len = attr.length();
96 unsigned lastCopiedFrom = 0;
98 DEFINE_STATIC_LOCAL(const String, ampEntity, ("&"));
99 DEFINE_STATIC_LOCAL(const String, gtEntity, (">"));
100 DEFINE_STATIC_LOCAL(const String, ltEntity, ("<"));
101 DEFINE_STATIC_LOCAL(const String, quotEntity, ("""));
102 DEFINE_STATIC_LOCAL(const String, nbspEntity, (" "));
104 for (unsigned i = 0; i < len; ++i) {
108 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
109 append(result, ampEntity);
110 lastCopiedFrom = i + 1;
113 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
114 append(result, ltEntity);
115 lastCopiedFrom = i + 1;
118 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
119 append(result, gtEntity);
120 lastCopiedFrom = i + 1;
123 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
124 append(result, quotEntity);
125 lastCopiedFrom = i + 1;
129 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
130 append(result, nbspEntity);
131 lastCopiedFrom = i + 1;
137 result.append(uchars + lastCopiedFrom, len - lastCopiedFrom);
140 static void appendEscapedContent(Vector<UChar>& result, pair<const UChar*, size_t> range, bool escapeNBSP)
142 const UChar* uchars = range.first;
143 unsigned len = range.second;
144 unsigned lastCopiedFrom = 0;
146 DEFINE_STATIC_LOCAL(const String, ampEntity, ("&"));
147 DEFINE_STATIC_LOCAL(const String, gtEntity, (">"));
148 DEFINE_STATIC_LOCAL(const String, ltEntity, ("<"));
149 DEFINE_STATIC_LOCAL(const String, nbspEntity, (" "));
151 for (unsigned i = 0; i < len; ++i) {
155 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
156 append(result, ampEntity);
157 lastCopiedFrom = i + 1;
160 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
161 append(result, ltEntity);
162 lastCopiedFrom = i + 1;
165 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
166 append(result, gtEntity);
167 lastCopiedFrom = i + 1;
171 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
172 append(result, nbspEntity);
173 lastCopiedFrom = i + 1;
179 result.append(uchars + lastCopiedFrom, len - lastCopiedFrom);
182 static String escapeContentText(const String& in, bool escapeNBSP)
184 Vector<UChar> buffer;
185 appendEscapedContent(buffer, make_pair(in.characters(), in.length()), escapeNBSP);
186 return String::adopt(buffer);
189 static void appendQuotedURLAttributeValue(Vector<UChar>& result, const String& urlString)
191 UChar quoteChar = '\"';
192 String strippedURLString = urlString.stripWhiteSpace();
193 if (protocolIs(strippedURLString, "javascript")) {
194 // minimal escaping for javascript urls
195 if (strippedURLString.contains('"')) {
196 if (strippedURLString.contains('\''))
197 strippedURLString.replace('\"', """);
201 result.append(quoteChar);
202 append(result, strippedURLString);
203 result.append(quoteChar);
207 // FIXME: This does not fully match other browsers. Firefox percent-escapes non-ASCII characters for innerHTML.
208 result.append(quoteChar);
209 appendAttributeValue(result, urlString, false);
210 result.append(quoteChar);
213 static String stringValueForRange(const Node* node, const Range* range)
216 return node->nodeValue();
218 String str = node->nodeValue();
220 if (node == range->endContainer(ec))
221 str.truncate(range->endOffset(ec));
222 if (node == range->startContainer(ec))
223 str.remove(0, range->startOffset(ec));
227 static inline pair<const UChar*, size_t> ucharRange(const Node *node, const Range *range)
229 String str = node->nodeValue();
230 const UChar* characters = str.characters();
231 size_t length = str.length();
235 if (node == range->endContainer(ec))
236 length = range->endOffset(ec);
237 if (node == range->startContainer(ec)) {
238 size_t start = range->startOffset(ec);
244 return make_pair(characters, length);
247 static inline void appendUCharRange(Vector<UChar>& result, const pair<const UChar*, size_t> range)
249 result.append(range.first, range.second);
252 static String renderedText(const Node* node, const Range* range)
254 if (!node->isTextNode())
258 const Text* textNode = static_cast<const Text*>(node);
259 unsigned startOffset = 0;
260 unsigned endOffset = textNode->length();
262 if (range && node == range->startContainer(ec))
263 startOffset = range->startOffset(ec);
264 if (range && node == range->endContainer(ec))
265 endOffset = range->endOffset(ec);
267 Position start(const_cast<Node*>(node), startOffset);
268 Position end(const_cast<Node*>(node), endOffset);
269 return plainText(Range::create(node->document(), start, end).get());
272 static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesForElement(Element* element, bool authorOnly = true)
274 RefPtr<CSSMutableStyleDeclaration> style = CSSMutableStyleDeclaration::create();
275 RefPtr<CSSRuleList> matchedRules = element->document()->styleSelector()->styleRulesForElement(element, authorOnly);
277 for (unsigned i = 0; i < matchedRules->length(); i++) {
278 if (matchedRules->item(i)->type() == CSSRule::STYLE_RULE) {
279 RefPtr<CSSMutableStyleDeclaration> s = static_cast<CSSStyleRule*>(matchedRules->item(i))->style();
280 style->merge(s.get(), true);
285 return style.release();
288 static void removeEnclosingMailBlockquoteStyle(CSSMutableStyleDeclaration* style, Node* node)
290 Node* blockquote = nearestMailBlockquote(node);
291 if (!blockquote || !blockquote->parentNode())
294 RefPtr<CSSMutableStyleDeclaration> parentStyle = Position(blockquote->parentNode(), 0).computedStyle()->copyInheritableProperties();
295 RefPtr<CSSMutableStyleDeclaration> blockquoteStyle = Position(blockquote, 0).computedStyle()->copyInheritableProperties();
296 parentStyle->diff(blockquoteStyle.get());
297 blockquoteStyle->diff(style);
300 static void removeDefaultStyles(CSSMutableStyleDeclaration* style, Document* document)
302 if (!document || !document->documentElement())
305 RefPtr<CSSMutableStyleDeclaration> documentStyle = computedStyle(document->documentElement())->copyInheritableProperties();
306 documentStyle->diff(style);
309 static bool shouldAddNamespaceElem(const Element* elem)
311 // Don't add namespace attribute if it is already defined for this elem.
312 const AtomicString& prefix = elem->prefix();
313 AtomicString attr = !prefix.isEmpty() ? "xmlns:" + prefix : "xmlns";
314 return !elem->hasAttribute(attr);
317 static bool shouldAddNamespaceAttr(const Attribute* attr, HashMap<AtomicStringImpl*, AtomicStringImpl*>& namespaces)
319 // Don't add namespace attributes twice
320 DEFINE_STATIC_LOCAL(const AtomicString, xmlnsURI, ("http://www.w3.org/2000/xmlns/"));
321 DEFINE_STATIC_LOCAL(const QualifiedName, xmlnsAttr, (nullAtom, "xmlns", xmlnsURI));
322 if (attr->name() == xmlnsAttr) {
323 namespaces.set(emptyAtom.impl(), attr->value().impl());
327 QualifiedName xmlnsPrefixAttr("xmlns", attr->localName(), xmlnsURI);
328 if (attr->name() == xmlnsPrefixAttr) {
329 namespaces.set(attr->localName().impl(), attr->value().impl());
336 static void appendNamespace(Vector<UChar>& result, const AtomicString& prefix, const AtomicString& ns, HashMap<AtomicStringImpl*, AtomicStringImpl*>& namespaces)
341 // Use emptyAtoms's impl() for both null and empty strings since the HashMap can't handle 0 as a key
342 AtomicStringImpl* pre = prefix.isEmpty() ? emptyAtom.impl() : prefix.impl();
343 AtomicStringImpl* foundNS = namespaces.get(pre);
344 if (foundNS != ns.impl()) {
345 namespaces.set(pre, ns.impl());
346 DEFINE_STATIC_LOCAL(const String, xmlns, ("xmlns"));
348 append(result, xmlns);
349 if (!prefix.isEmpty()) {
351 append(result, prefix);
356 appendAttributeValue(result, ns, false);
361 static void appendDocumentType(Vector<UChar>& result, const DocumentType* n)
363 if (n->name().isEmpty())
366 append(result, "<!DOCTYPE ");
367 append(result, n->name());
368 if (!n->publicId().isEmpty()) {
369 append(result, " PUBLIC \"");
370 append(result, n->publicId());
371 append(result, "\"");
372 if (!n->systemId().isEmpty()) {
373 append(result, " \"");
374 append(result, n->systemId());
375 append(result, "\"");
377 } else if (!n->systemId().isEmpty()) {
378 append(result, " SYSTEM \"");
379 append(result, n->systemId());
380 append(result, "\"");
382 if (!n->internalSubset().isEmpty()) {
383 append(result, " [");
384 append(result, n->internalSubset());
390 static void appendStartMarkup(Vector<UChar>& result, const Node *node, const Range *range, EAnnotateForInterchange annotate, bool convertBlocksToInlines = false, HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0)
392 bool documentIsHTML = node->document()->isHTMLDocument();
393 switch (node->nodeType()) {
394 case Node::TEXT_NODE: {
395 if (Node* parent = node->parentNode()) {
396 if (parent->hasTagName(scriptTag)
397 || parent->hasTagName(styleTag)
398 || parent->hasTagName(textareaTag)
399 || parent->hasTagName(xmpTag)) {
400 appendUCharRange(result, ucharRange(node, range));
405 appendEscapedContent(result, ucharRange(node, range), documentIsHTML);
409 bool useRenderedText = !enclosingNodeWithTag(Position(const_cast<Node*>(node), 0), selectTag);
410 String markup = escapeContentText(useRenderedText ? renderedText(node, range) : stringValueForRange(node, range), false);
412 markup = convertHTMLTextToInterchangeFormat(markup, static_cast<const Text*>(node));
413 append(result, markup);
416 case Node::COMMENT_NODE:
417 // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->".
418 append(result, "<!--");
419 append(result, static_cast<const Comment*>(node)->nodeValue());
420 append(result, "-->");
422 case Node::DOCUMENT_NODE:
423 case Node::DOCUMENT_FRAGMENT_NODE:
425 case Node::DOCUMENT_TYPE_NODE:
426 appendDocumentType(result, static_cast<const DocumentType*>(node));
428 case Node::PROCESSING_INSTRUCTION_NODE: {
429 // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>".
430 const ProcessingInstruction* n = static_cast<const ProcessingInstruction*>(node);
431 append(result, "<?");
432 append(result, n->target());
434 append(result, n->data());
435 append(result, "?>");
438 case Node::ELEMENT_NODE: {
440 const Element* el = static_cast<const Element*>(node);
441 bool convert = convertBlocksToInlines && isBlock(const_cast<Node*>(node));
442 append(result, el->nodeNamePreservingCase());
443 NamedAttrMap *attrs = el->attributes();
444 unsigned length = attrs->length();
445 if (!documentIsHTML && namespaces && shouldAddNamespaceElem(el))
446 appendNamespace(result, el->prefix(), el->namespaceURI(), *namespaces);
448 for (unsigned int i = 0; i < length; i++) {
449 Attribute *attr = attrs->attributeItem(i);
450 // We'll handle the style attribute separately, below.
451 if (attr->name() == styleAttr && el->isHTMLElement() && (annotate || convert))
456 append(result, attr->name().localName());
458 append(result, attr->name().toString());
462 if (el->isURLAttribute(attr))
463 appendQuotedURLAttributeValue(result, attr->value());
466 appendAttributeValue(result, attr->value(), documentIsHTML);
470 if (!documentIsHTML && namespaces && shouldAddNamespaceAttr(attr, *namespaces))
471 appendNamespace(result, attr->prefix(), attr->namespaceURI(), *namespaces);
474 if (el->isHTMLElement() && (annotate || convert)) {
475 Element* element = const_cast<Element*>(el);
476 RefPtr<CSSMutableStyleDeclaration> style = static_cast<HTMLElement*>(element)->getInlineStyleDecl()->copy();
478 RefPtr<CSSMutableStyleDeclaration> styleFromMatchedRules = styleFromMatchedRulesForElement(const_cast<Element*>(el));
479 // Styles from the inline style declaration, held in the variable "style", take precedence
480 // over those from matched rules.
481 styleFromMatchedRules->merge(style.get());
482 style = styleFromMatchedRules;
484 RefPtr<CSSComputedStyleDeclaration> computedStyleForElement = computedStyle(element);
485 RefPtr<CSSMutableStyleDeclaration> fromComputedStyle = CSSMutableStyleDeclaration::create();
488 CSSMutableStyleDeclaration::const_iterator end = style->end();
489 for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
490 const CSSProperty& property = *it;
491 CSSValue* value = property.value();
492 // The property value, if it's a percentage, may not reflect the actual computed value.
493 // For example: style="height: 1%; overflow: visible;" in quirksmode
494 // FIXME: There are others like this, see <rdar://problem/5195123> Slashdot copy/paste fidelity problem
495 if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE)
496 if (static_cast<CSSPrimitiveValue*>(value)->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
497 if (RefPtr<CSSValue> computedPropertyValue = computedStyleForElement->getPropertyCSSValue(property.id()))
498 fromComputedStyle->addParsedProperty(CSSProperty(property.id(), computedPropertyValue));
502 style->merge(fromComputedStyle.get());
505 style->setProperty(CSSPropertyDisplay, CSSValueInline, true);
506 if (style->length() > 0) {
507 DEFINE_STATIC_LOCAL(const String, stylePrefix, (" style=\""));
508 append(result, stylePrefix);
509 appendAttributeValue(result, style->cssText(), documentIsHTML);
514 if (shouldSelfClose(el)) {
515 if (el->isHTMLElement())
516 result.append(' '); // XHTML 1.0 <-> HTML compatibility.
522 case Node::CDATA_SECTION_NODE: {
523 // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>".
524 const CDATASection* n = static_cast<const CDATASection*>(node);
525 append(result, "<![CDATA[");
526 append(result, n->data());
527 append(result, "]]>");
530 case Node::ATTRIBUTE_NODE:
531 case Node::ENTITY_NODE:
532 case Node::ENTITY_REFERENCE_NODE:
533 case Node::NOTATION_NODE:
534 case Node::XPATH_NAMESPACE_NODE:
535 ASSERT_NOT_REACHED();
540 static String getStartMarkup(const Node *node, const Range *range, EAnnotateForInterchange annotate, bool convertBlocksToInlines = false, HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0)
542 Vector<UChar> result;
543 appendStartMarkup(result, node, range, annotate, convertBlocksToInlines, namespaces);
544 return String::adopt(result);
547 static inline bool doesHTMLForbidEndTag(const Node *node)
549 if (node->isHTMLElement()) {
550 const HTMLElement* htmlElt = static_cast<const HTMLElement*>(node);
551 return (htmlElt->endTagRequirement() == TagStatusForbidden);
556 // Rules of self-closure
557 // 1. No elements in HTML documents use the self-closing syntax.
558 // 2. Elements w/ children never self-close because they use a separate end tag.
559 // 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag.
560 // 4. Other elements self-close.
561 static inline bool shouldSelfClose(const Node *node)
563 if (node->document()->isHTMLDocument())
565 if (node->hasChildNodes())
567 if (node->isHTMLElement() && !doesHTMLForbidEndTag(node))
572 static void appendEndMarkup(Vector<UChar>& result, const Node* node)
574 if (!node->isElementNode() || shouldSelfClose(node) || (!node->hasChildNodes() && doesHTMLForbidEndTag(node)))
579 append(result, static_cast<const Element*>(node)->nodeNamePreservingCase());
583 static String getEndMarkup(const Node *node)
585 Vector<UChar> result;
586 appendEndMarkup(result, node);
587 return String::adopt(result);
590 class MarkupAccumulator {
592 MarkupAccumulator(Node* nodeToSkip, Vector<Node*>* nodes)
593 : m_nodeToSkip(nodeToSkip)
598 void appendMarkup(Node* startNode, EChildrenOnly, const HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0);
600 String takeResult() { return String::adopt(m_result); }
603 Vector<UChar> m_result;
605 Vector<Node*>* m_nodes;
608 // FIXME: Would be nice to do this in a non-recursive way.
609 void MarkupAccumulator::appendMarkup(Node* startNode, EChildrenOnly childrenOnly, const HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces)
611 if (startNode == m_nodeToSkip)
614 HashMap<AtomicStringImpl*, AtomicStringImpl*> namespaceHash;
616 namespaceHash = *namespaces;
621 m_nodes->append(startNode);
622 appendStartMarkup(m_result, startNode, 0, DoNotAnnotateForInterchange, false, &namespaceHash);
626 if (!(startNode->document()->isHTMLDocument() && doesHTMLForbidEndTag(startNode))) {
627 for (Node* current = startNode->firstChild(); current; current = current->nextSibling())
628 appendMarkup(current, IncludeNode, &namespaceHash);
633 appendEndMarkup(m_result, startNode);
636 static void completeURLs(Node* node, const String& baseURL)
638 Vector<AttributeChange> changes;
640 KURL parsedBaseURL(baseURL);
642 Node* end = node->traverseNextSibling();
643 for (Node* n = node; n != end; n = n->traverseNextNode()) {
644 if (n->isElementNode()) {
645 Element* e = static_cast<Element*>(n);
646 NamedAttrMap* attrs = e->attributes();
647 unsigned length = attrs->length();
648 for (unsigned i = 0; i < length; i++) {
649 Attribute* attr = attrs->attributeItem(i);
650 if (e->isURLAttribute(attr))
651 changes.append(AttributeChange(e, attr->name(), KURL(parsedBaseURL, attr->value()).string()));
656 size_t numChanges = changes.size();
657 for (size_t i = 0; i < numChanges; ++i)
661 static bool needInterchangeNewlineAfter(const VisiblePosition& v)
663 VisiblePosition next = v.next();
664 Node* upstreamNode = next.deepEquivalent().upstream().node();
665 Node* downstreamNode = v.deepEquivalent().downstream().node();
666 // Add an interchange newline if a paragraph break is selected and a br won't already be added to the markup to represent it.
667 return isEndOfParagraph(v) && isStartOfParagraph(next) && !(upstreamNode->hasTagName(brTag) && upstreamNode == downstreamNode);
670 static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesAndInlineDecl(const Node* node)
672 if (!node->isHTMLElement())
675 // FIXME: Having to const_cast here is ugly, but it is quite a bit of work to untangle
676 // the non-const-ness of styleFromMatchedRulesForElement.
677 HTMLElement* element = const_cast<HTMLElement*>(static_cast<const HTMLElement*>(node));
678 RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesForElement(element);
679 RefPtr<CSSMutableStyleDeclaration> inlineStyleDecl = element->getInlineStyleDecl();
680 style->merge(inlineStyleDecl.get());
681 return style.release();
684 static bool propertyMissingOrEqualToNone(CSSMutableStyleDeclaration* style, int propertyID)
688 RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID);
691 if (!value->isPrimitiveValue())
693 return static_cast<CSSPrimitiveValue*>(value.get())->getIdent() == CSSValueNone;
696 static bool elementHasTextDecorationProperty(const Node* node)
698 RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesAndInlineDecl(node);
701 return !propertyMissingOrEqualToNone(style.get(), CSSPropertyTextDecoration);
704 static String joinMarkups(const Vector<String>& preMarkups, const Vector<String>& postMarkups)
708 size_t preCount = preMarkups.size();
709 for (size_t i = 0; i < preCount; ++i)
710 length += preMarkups[i].length();
712 size_t postCount = postMarkups.size();
713 for (size_t i = 0; i < postCount; ++i)
714 length += postMarkups[i].length();
716 Vector<UChar> result;
717 result.reserveInitialCapacity(length);
719 for (size_t i = preCount; i > 0; --i)
720 append(result, preMarkups[i - 1]);
722 for (size_t i = 0; i < postCount; ++i)
723 append(result, postMarkups[i]);
725 return String::adopt(result);
728 static bool isSpecialAncestorBlock(Node* node)
730 if (!node || !isBlock(node))
733 return node->hasTagName(listingTag) ||
734 node->hasTagName(olTag) ||
735 node->hasTagName(preTag) ||
736 node->hasTagName(tableTag) ||
737 node->hasTagName(ulTag) ||
738 node->hasTagName(xmpTag) ||
739 node->hasTagName(h1Tag) ||
740 node->hasTagName(h2Tag) ||
741 node->hasTagName(h3Tag) ||
742 node->hasTagName(h4Tag) ||
743 node->hasTagName(h5Tag);
746 // FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange?
747 // FIXME: At least, annotation and style info should probably not be included in range.markupString()
748 String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterchange annotate, bool convertBlocksToInlines)
750 DEFINE_STATIC_LOCAL(const String, interchangeNewlineString, ("<br class=\"" AppleInterchangeNewline "\">"));
755 Document* document = range->ownerDocument();
759 bool documentIsHTML = document->isHTMLDocument();
761 // Disable the delete button so it's elements are not serialized into the markup,
762 // but make sure neither endpoint is inside the delete user interface.
763 Frame* frame = document->frame();
764 DeleteButtonController* deleteButton = frame ? frame->editor()->deleteButtonController() : 0;
765 RefPtr<Range> updatedRange = avoidIntersectionWithNode(range, deleteButton ? deleteButton->containerElement() : 0);
770 deleteButton->disable();
772 ExceptionCode ec = 0;
773 bool collapsed = updatedRange->collapsed(ec);
777 Node* commonAncestor = updatedRange->commonAncestorContainer(ec);
782 document->updateLayoutIgnorePendingStylesheets();
784 Vector<String> markups;
785 Vector<String> preMarkups;
786 Node* pastEnd = updatedRange->pastLastNode();
787 Node* lastClosed = 0;
788 Vector<Node*> ancestorsToClose;
790 Node* startNode = updatedRange->firstNode();
791 VisiblePosition visibleStart(updatedRange->startPosition(), VP_DEFAULT_AFFINITY);
792 VisiblePosition visibleEnd(updatedRange->endPosition(), VP_DEFAULT_AFFINITY);
793 if (annotate && needInterchangeNewlineAfter(visibleStart)) {
794 if (visibleStart == visibleEnd.previous()) {
796 deleteButton->enable();
797 return interchangeNewlineString;
800 markups.append(interchangeNewlineString);
801 startNode = visibleStart.next().deepEquivalent().node();
805 for (Node* n = startNode; n != pastEnd; n = next) {
807 // According to <rdar://problem/5730668>, it is possible for n to blow past pastEnd and become null here. This
808 // shouldn't be possible. This null check will prevent crashes (but create too much markup) and the ASSERT will
809 // hopefully lead us to understanding the problem.
814 next = n->traverseNextNode();
815 bool skipDescendants = false;
816 bool addMarkupForNode = true;
818 if (!n->renderer() && !enclosingNodeWithTag(Position(n, 0), selectTag)) {
819 skipDescendants = true;
820 addMarkupForNode = false;
821 next = n->traverseNextSibling();
822 // Don't skip over pastEnd.
823 if (pastEnd && pastEnd->isDescendantOf(n))
827 if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd)
828 // Don't write out empty block containers that aren't fully selected.
831 // Add the node to the markup.
832 if (addMarkupForNode) {
833 markups.append(getStartMarkup(n, updatedRange.get(), annotate));
838 if (n->firstChild() == 0 || skipDescendants) {
839 // Node has no children, or we are skipping it's descendants, add its close tag now.
840 if (addMarkupForNode) {
841 markups.append(getEndMarkup(n));
845 // Check if the node is the last leaf of a tree.
846 if (!n->nextSibling() || next == pastEnd) {
847 if (!ancestorsToClose.isEmpty()) {
848 // Close up the ancestors.
850 Node *ancestor = ancestorsToClose.last();
851 if (next != pastEnd && next->isDescendantOf(ancestor))
853 // Not at the end of the range, close ancestors up to sibling of next node.
854 markups.append(getEndMarkup(ancestor));
855 lastClosed = ancestor;
856 ancestorsToClose.removeLast();
857 } while (!ancestorsToClose.isEmpty());
860 // Surround the currently accumulated markup with markup for ancestors we never opened as we leave the subtree(s) rooted at those ancestors.
861 Node* nextParent = next ? next->parentNode() : 0;
862 if (next != pastEnd && n != nextParent) {
863 Node* lastAncestorClosedOrSelf = n->isDescendantOf(lastClosed) ? lastClosed : n;
864 for (Node *parent = lastAncestorClosedOrSelf->parent(); parent != 0 && parent != nextParent; parent = parent->parentNode()) {
865 // All ancestors that aren't in the ancestorsToClose list should either be a) unrendered:
866 if (!parent->renderer())
868 // or b) ancestors that we never encountered during a pre-order traversal starting at startNode:
869 ASSERT(startNode->isDescendantOf(parent));
870 preMarkups.append(getStartMarkup(parent, updatedRange.get(), annotate));
871 markups.append(getEndMarkup(parent));
873 nodes->append(parent);
878 } else if (addMarkupForNode && !skipDescendants)
879 // We added markup for this node, and we're descending into it. Set it to close eventually.
880 ancestorsToClose.append(n);
883 // Include ancestors that aren't completely inside the range but are required to retain
884 // the structure and appearance of the copied markup.
885 Node* specialCommonAncestor = 0;
886 Node* commonAncestorBlock = commonAncestor ? enclosingBlock(commonAncestor) : 0;
887 if (annotate && commonAncestorBlock) {
888 if (commonAncestorBlock->hasTagName(tbodyTag) || commonAncestorBlock->hasTagName(trTag)) {
889 Node* table = commonAncestorBlock->parentNode();
890 while (table && !table->hasTagName(tableTag))
891 table = table->parentNode();
893 specialCommonAncestor = table;
894 } else if (isSpecialAncestorBlock(commonAncestorBlock))
895 specialCommonAncestor = commonAncestorBlock;
898 // Retain the Mail quote level by including all ancestor mail block quotes.
899 if (lastClosed && annotate) {
900 for (Node *ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode())
901 if (isMailBlockquote(ancestor))
902 specialCommonAncestor = ancestor;
905 Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor;
906 if (checkAncestor->renderer()) {
907 RefPtr<CSSMutableStyleDeclaration> checkAncestorStyle = computedStyle(checkAncestor)->copyInheritableProperties();
908 if (!propertyMissingOrEqualToNone(checkAncestorStyle.get(), CSSPropertyWebkitTextDecorationsInEffect))
909 specialCommonAncestor = enclosingNodeOfType(Position(checkAncestor, 0), &elementHasTextDecorationProperty);
912 // If a single tab is selected, commonAncestor will be a text node inside a tab span.
913 // If two or more tabs are selected, commonAncestor will be the tab span.
914 // In either case, if there is a specialCommonAncestor already, it will necessarily be above
915 // any tab span that needs to be included.
916 if (!specialCommonAncestor && isTabSpanTextNode(commonAncestor))
917 specialCommonAncestor = commonAncestor->parentNode();
918 if (!specialCommonAncestor && isTabSpanNode(commonAncestor))
919 specialCommonAncestor = commonAncestor;
921 if (Node *enclosingAnchor = enclosingNodeWithTag(Position(specialCommonAncestor ? specialCommonAncestor : commonAncestor, 0), aTag))
922 specialCommonAncestor = enclosingAnchor;
924 Node* body = enclosingNodeWithTag(Position(commonAncestor, 0), bodyTag);
925 // FIXME: Only include markup for a fully selected root (and ancestors of lastClosed up to that root) if
926 // there are styles/attributes on those nodes that need to be included to preserve the appearance of the copied markup.
927 // FIXME: Do this for all fully selected blocks, not just the body.
928 Node* fullySelectedRoot = body && *VisibleSelection::selectionFromContentsOfNode(body).toNormalizedRange() == *updatedRange ? body : 0;
929 if (annotate && fullySelectedRoot)
930 specialCommonAncestor = fullySelectedRoot;
932 if (specialCommonAncestor && lastClosed) {
933 // Also include all of the ancestors of lastClosed up to this special ancestor.
934 for (Node* ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
935 if (ancestor == fullySelectedRoot && !convertBlocksToInlines) {
936 RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesAndInlineDecl(fullySelectedRoot);
938 // Bring the background attribute over, but not as an attribute because a background attribute on a div
939 // appears to have no effect.
940 if (!style->getPropertyCSSValue(CSSPropertyBackgroundImage) && static_cast<Element*>(fullySelectedRoot)->hasAttribute(backgroundAttr))
941 style->setProperty(CSSPropertyBackgroundImage, "url('" + static_cast<Element*>(fullySelectedRoot)->getAttribute(backgroundAttr) + "')");
943 if (style->length()) {
944 Vector<UChar> openTag;
945 DEFINE_STATIC_LOCAL(const String, divStyle, ("<div style=\""));
946 append(openTag, divStyle);
947 appendAttributeValue(openTag, style->cssText(), documentIsHTML);
948 openTag.append('\"');
950 preMarkups.append(String::adopt(openTag));
952 DEFINE_STATIC_LOCAL(const String, divCloseTag, ("</div>"));
953 markups.append(divCloseTag);
956 preMarkups.append(getStartMarkup(ancestor, updatedRange.get(), annotate, convertBlocksToInlines));
957 markups.append(getEndMarkup(ancestor));
960 nodes->append(ancestor);
962 lastClosed = ancestor;
964 if (ancestor == specialCommonAncestor)
969 DEFINE_STATIC_LOCAL(const String, styleSpanOpen, ("<span class=\"" AppleStyleSpanClass "\" style=\""));
970 DEFINE_STATIC_LOCAL(const String, styleSpanClose, ("</span>"));
972 // Add a wrapper span with the styles that all of the nodes in the markup inherit.
973 Node* parentOfLastClosed = lastClosed ? lastClosed->parentNode() : 0;
974 if (parentOfLastClosed && parentOfLastClosed->renderer()) {
975 RefPtr<CSSMutableStyleDeclaration> style = computedStyle(parentOfLastClosed)->copyInheritableProperties();
977 // Styles that Mail blockquotes contribute should only be placed on the Mail blockquote, to help
978 // us differentiate those styles from ones that the user has applied. This helps us
979 // get the color of content pasted into blockquotes right.
980 removeEnclosingMailBlockquoteStyle(style.get(), parentOfLastClosed);
982 // Document default styles will be added on another wrapper span.
983 removeDefaultStyles(style.get(), document);
985 // Since we are converting blocks to inlines, remove any inherited block properties that are in the style.
986 // This cuts out meaningless properties and prevents properties from magically affecting blocks later
987 // if the style is cloned for a new block element during a future editing operation.
988 if (convertBlocksToInlines)
989 style->removeBlockProperties();
991 if (style->length() > 0) {
992 Vector<UChar> openTag;
993 append(openTag, styleSpanOpen);
994 appendAttributeValue(openTag, style->cssText(), documentIsHTML);
995 openTag.append('\"');
997 preMarkups.append(String::adopt(openTag));
999 markups.append(styleSpanClose);
1003 if (lastClosed && lastClosed != document->documentElement()) {
1004 // Add a style span with the document's default styles. We add these in a separate
1005 // span so that at paste time we can differentiate between document defaults and user
1007 RefPtr<CSSMutableStyleDeclaration> defaultStyle = computedStyle(document->documentElement())->copyInheritableProperties();
1009 if (defaultStyle->length() > 0) {
1010 Vector<UChar> openTag;
1011 append(openTag, styleSpanOpen);
1012 appendAttributeValue(openTag, defaultStyle->cssText(), documentIsHTML);
1013 openTag.append('\"');
1014 openTag.append('>');
1015 preMarkups.append(String::adopt(openTag));
1016 markups.append(styleSpanClose);
1020 // FIXME: The interchange newline should be placed in the block that it's in, not after all of the content, unconditionally.
1021 if (annotate && needInterchangeNewlineAfter(visibleEnd.previous()))
1022 markups.append(interchangeNewlineString);
1025 deleteButton->enable();
1027 return joinMarkups(preMarkups, markups);
1030 PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const String& markup, const String& baseURL)
1032 ASSERT(document->documentElement()->isHTMLElement());
1033 // FIXME: What if the document element is not an HTML element?
1034 HTMLElement *element = static_cast<HTMLElement*>(document->documentElement());
1036 RefPtr<DocumentFragment> fragment = element->createContextualFragment(markup);
1038 if (fragment && !baseURL.isEmpty() && baseURL != blankURL() && baseURL != document->baseURL())
1039 completeURLs(fragment.get(), baseURL);
1041 return fragment.release();
1044 String createMarkup(const Node* node, EChildrenOnly childrenOnly, Vector<Node*>* nodes)
1049 HTMLElement* deleteButtonContainerElement = 0;
1050 if (Frame* frame = node->document()->frame()) {
1051 deleteButtonContainerElement = frame->editor()->deleteButtonController()->containerElement();
1052 if (node->isDescendantOf(deleteButtonContainerElement))
1056 MarkupAccumulator accumulator(deleteButtonContainerElement, nodes);
1057 accumulator.appendMarkup(const_cast<Node*>(node), childrenOnly);
1058 return accumulator.takeResult();
1061 static void fillContainerFromString(ContainerNode* paragraph, const String& string)
1063 Document* document = paragraph->document();
1065 ExceptionCode ec = 0;
1066 if (string.isEmpty()) {
1067 paragraph->appendChild(createBlockPlaceholderElement(document), ec);
1072 ASSERT(string.find('\n') == -1);
1074 Vector<String> tabList;
1075 string.split('\t', true, tabList);
1076 String tabText = "";
1078 size_t numEntries = tabList.size();
1079 for (size_t i = 0; i < numEntries; ++i) {
1080 const String& s = tabList[i];
1082 // append the non-tab textual part
1084 if (!tabText.isEmpty()) {
1085 paragraph->appendChild(createTabSpanElement(document, tabText), ec);
1089 RefPtr<Node> textNode = document->createTextNode(stringWithRebalancedWhitespace(s, first, i + 1 == numEntries));
1090 paragraph->appendChild(textNode.release(), ec);
1094 // there is a tab after every entry, except the last entry
1095 // (if the last character is a tab, the list gets an extra empty entry)
1096 if (i + 1 != numEntries)
1097 tabText.append('\t');
1098 else if (!tabText.isEmpty()) {
1099 paragraph->appendChild(createTabSpanElement(document, tabText), ec);
1107 PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String& text)
1112 Node* styleNode = context->firstNode();
1114 styleNode = context->startPosition().node();
1119 Document* document = styleNode->document();
1120 RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
1123 return fragment.release();
1125 String string = text;
1126 string.replace("\r\n", "\n");
1127 string.replace('\r', '\n');
1129 ExceptionCode ec = 0;
1130 RenderObject* renderer = styleNode->renderer();
1131 if (renderer && renderer->style()->preserveNewline()) {
1132 fragment->appendChild(document->createTextNode(string), ec);
1134 if (string.endsWith("\n")) {
1135 RefPtr<Element> element = createBreakElement(document);
1136 element->setAttribute(classAttr, AppleInterchangeNewline);
1137 fragment->appendChild(element.release(), ec);
1140 return fragment.release();
1143 // A string with no newlines gets added inline, rather than being put into a paragraph.
1144 if (string.find('\n') == -1) {
1145 fillContainerFromString(fragment.get(), string);
1146 return fragment.release();
1149 // Break string into paragraphs. Extra line breaks turn into empty paragraphs.
1150 Node* blockNode = enclosingBlock(context->firstNode());
1151 Element* block = static_cast<Element*>(blockNode);
1152 bool useClonesOfEnclosingBlock = blockNode
1153 && blockNode->isElementNode()
1154 && !block->hasTagName(bodyTag)
1155 && !block->hasTagName(htmlTag)
1156 && block != editableRootForPosition(context->startPosition());
1158 Vector<String> list;
1159 string.split('\n', true, list); // true gets us empty strings in the list
1160 size_t numLines = list.size();
1161 for (size_t i = 0; i < numLines; ++i) {
1162 const String& s = list[i];
1164 RefPtr<Element> element;
1165 if (s.isEmpty() && i + 1 == numLines) {
1166 // For last line, use the "magic BR" rather than a P.
1167 element = createBreakElement(document);
1168 element->setAttribute(classAttr, AppleInterchangeNewline);
1170 if (useClonesOfEnclosingBlock)
1171 element = block->cloneElementWithoutChildren();
1173 element = createDefaultParagraphElement(document);
1174 fillContainerFromString(element.get(), s);
1176 fragment->appendChild(element.release(), ec);
1179 return fragment.release();
1182 PassRefPtr<DocumentFragment> createFragmentFromNodes(Document *document, const Vector<Node*>& nodes)
1187 // disable the delete button so it's elements are not serialized into the markup
1188 if (document->frame())
1189 document->frame()->editor()->deleteButtonController()->disable();
1191 RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
1193 ExceptionCode ec = 0;
1194 size_t size = nodes.size();
1195 for (size_t i = 0; i < size; ++i) {
1196 RefPtr<Element> element = createDefaultParagraphElement(document);
1197 element->appendChild(nodes[i], ec);
1199 fragment->appendChild(element.release(), ec);
1203 if (document->frame())
1204 document->frame()->editor()->deleteButtonController()->enable();
1206 return fragment.release();
1209 String createFullMarkup(const Node* node)
1214 Document* document = node->document();
1218 Frame* frame = document->frame();
1222 // FIXME: This is never "for interchange". Is that right?
1223 String markupString = createMarkup(node, IncludeNode, 0);
1224 Node::NodeType nodeType = node->nodeType();
1225 if (nodeType != Node::DOCUMENT_NODE && nodeType != Node::DOCUMENT_TYPE_NODE)
1226 markupString = frame->documentTypeString() + markupString;
1228 return markupString;
1231 String createFullMarkup(const Range* range)
1236 Node* node = range->startContainer();
1240 Document* document = node->document();
1244 Frame* frame = document->frame();
1248 // FIXME: This is always "for interchange". Is that right? See the previous method.
1249 return frame->documentTypeString() + createMarkup(range, 0, AnnotateForInterchange);