2008-11-04 Anders Carlsson <andersca@apple.com>
[webkit/qt.git] / WebCore / editing / markup.cpp
blobb067002be196da8d44fa0fb9275f4136240836bf
1 /*
2 * Copyright (C) 2004, 2005, 2006, 2007, 2008 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
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
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.
26 #include "config.h"
27 #include "markup.h"
29 #include "CDATASection.h"
30 #include "CharacterNames.h"
31 #include "Comment.h"
32 #include "CSSComputedStyleDeclaration.h"
33 #include "CSSPrimitiveValue.h"
34 #include "CSSProperty.h"
35 #include "CSSPropertyNames.h"
36 #include "CSSRule.h"
37 #include "CSSRuleList.h"
38 #include "CSSStyleRule.h"
39 #include "CSSStyleSelector.h"
40 #include "CSSValue.h"
41 #include "CSSValueKeywords.h"
42 #include "DeleteButtonController.h"
43 #include "Document.h"
44 #include "DocumentFragment.h"
45 #include "DocumentType.h"
46 #include "Editor.h"
47 #include "Frame.h"
48 #include "HTMLElement.h"
49 #include "HTMLNames.h"
50 #include "InlineTextBox.h"
51 #include "Logging.h"
52 #include "ProcessingInstruction.h"
53 #include "QualifiedName.h"
54 #include "Range.h"
55 #include "Selection.h"
56 #include "TextIterator.h"
57 #include "htmlediting.h"
58 #include "visible_units.h"
60 using namespace std;
62 namespace WebCore {
64 using namespace HTMLNames;
66 static inline bool shouldSelfClose(const Node *node);
68 class AttributeChange {
69 public:
70 AttributeChange()
71 : m_name(nullAtom, nullAtom, nullAtom)
75 AttributeChange(PassRefPtr<Element> element, const QualifiedName& name, const String& value)
76 : m_element(element), m_name(name), m_value(value)
80 void apply()
82 m_element->setAttribute(m_name, m_value);
85 private:
86 RefPtr<Element> m_element;
87 QualifiedName m_name;
88 String m_value;
91 static void appendAttributeValue(Vector<UChar>& result, const String& attr, bool escapeNBSP)
93 const UChar* uchars = attr.characters();
94 unsigned len = attr.length();
95 unsigned lastCopiedFrom = 0;
97 static const String ampEntity("&amp;");
98 static const String gtEntity("&gt;");
99 static const String ltEntity("&lt;");
100 static const String quotEntity("&quot;");
101 static const String nbspEntity("&nbsp;");
103 for (unsigned i = 0; i < len; ++i) {
104 UChar c = uchars[i];
105 switch (c) {
106 case '&':
107 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
108 append(result, ampEntity);
109 lastCopiedFrom = i + 1;
110 break;
111 case '<':
112 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
113 append(result, ltEntity);
114 lastCopiedFrom = i + 1;
115 break;
116 case '>':
117 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
118 append(result, gtEntity);
119 lastCopiedFrom = i + 1;
120 break;
121 case '"':
122 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
123 append(result, quotEntity);
124 lastCopiedFrom = i + 1;
125 break;
126 case noBreakSpace:
127 if (escapeNBSP) {
128 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
129 append(result, nbspEntity);
130 lastCopiedFrom = i + 1;
132 break;
136 result.append(uchars + lastCopiedFrom, len - lastCopiedFrom);
139 static void appendEscapedContent(Vector<UChar>& result, pair<const UChar*, size_t> range, bool escapeNBSP)
141 const UChar* uchars = range.first;
142 unsigned len = range.second;
143 unsigned lastCopiedFrom = 0;
145 static const String ampEntity("&amp;");
146 static const String gtEntity("&gt;");
147 static const String ltEntity("&lt;");
148 static const String nbspEntity("&nbsp;");
150 for (unsigned i = 0; i < len; ++i) {
151 UChar c = uchars[i];
152 switch (c) {
153 case '&':
154 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
155 append(result, ampEntity);
156 lastCopiedFrom = i + 1;
157 break;
158 case '<':
159 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
160 append(result, ltEntity);
161 lastCopiedFrom = i + 1;
162 break;
163 case '>':
164 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
165 append(result, gtEntity);
166 lastCopiedFrom = i + 1;
167 break;
168 case noBreakSpace:
169 if (escapeNBSP) {
170 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
171 append(result, nbspEntity);
172 lastCopiedFrom = i + 1;
174 break;
178 result.append(uchars + lastCopiedFrom, len - lastCopiedFrom);
181 static String escapeContentText(const String& in, bool escapeNBSP)
183 Vector<UChar> buffer;
184 appendEscapedContent(buffer, make_pair(in.characters(), in.length()), escapeNBSP);
185 return String::adopt(buffer);
188 static void appendQuotedURLAttributeValue(Vector<UChar>& result, const String& urlString)
190 UChar quoteChar = '\"';
191 String strippedURLString = urlString.stripWhiteSpace();
192 if (protocolIs(strippedURLString, "javascript")) {
193 // minimal escaping for javascript urls
194 if (strippedURLString.contains('"')) {
195 if (strippedURLString.contains('\''))
196 strippedURLString.replace('\"', "&quot;");
197 else
198 quoteChar = '\'';
200 result.append(quoteChar);
201 append(result, strippedURLString);
202 result.append(quoteChar);
203 return;
206 // FIXME: This does not fully match other browsers. Firefox percent-escapes non-ASCII characters for innerHTML.
207 result.append(quoteChar);
208 appendAttributeValue(result, urlString, false);
209 result.append(quoteChar);
212 static String stringValueForRange(const Node* node, const Range* range)
214 if (!range)
215 return node->nodeValue();
217 String str = node->nodeValue();
218 ExceptionCode ec;
219 if (node == range->endContainer(ec))
220 str.truncate(range->endOffset(ec));
221 if (node == range->startContainer(ec))
222 str.remove(0, range->startOffset(ec));
223 return str;
226 static inline pair<const UChar*, size_t> ucharRange(const Node *node, const Range *range)
228 String str = node->nodeValue();
229 const UChar* characters = str.characters();
230 size_t length = str.length();
232 if (range) {
233 ExceptionCode ec;
234 if (node == range->endContainer(ec))
235 length = range->endOffset(ec);
236 if (node == range->startContainer(ec)) {
237 size_t start = range->startOffset(ec);
238 characters += start;
239 length -= start;
243 return make_pair(characters, length);
246 static inline void appendUCharRange(Vector<UChar>& result, const pair<const UChar*, size_t> range)
248 result.append(range.first, range.second);
251 static String renderedText(const Node* node, const Range* range)
253 if (!node->isTextNode())
254 return String();
256 ExceptionCode ec;
257 const Text* textNode = static_cast<const Text*>(node);
258 unsigned startOffset = 0;
259 unsigned endOffset = textNode->length();
261 if (range && node == range->startContainer(ec))
262 startOffset = range->startOffset(ec);
263 if (range && node == range->endContainer(ec))
264 endOffset = range->endOffset(ec);
266 Position start(const_cast<Node*>(node), startOffset);
267 Position end(const_cast<Node*>(node), endOffset);
268 return plainText(Range::create(node->document(), start, end).get());
271 static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesForElement(Element* element, bool authorOnly = true)
273 RefPtr<CSSMutableStyleDeclaration> style = CSSMutableStyleDeclaration::create();
274 RefPtr<CSSRuleList> matchedRules = element->document()->styleSelector()->styleRulesForElement(element, authorOnly);
275 if (matchedRules) {
276 for (unsigned i = 0; i < matchedRules->length(); i++) {
277 if (matchedRules->item(i)->type() == CSSRule::STYLE_RULE) {
278 RefPtr<CSSMutableStyleDeclaration> s = static_cast<CSSStyleRule*>(matchedRules->item(i))->style();
279 style->merge(s.get(), true);
284 return style.release();
287 static void removeEnclosingMailBlockquoteStyle(CSSMutableStyleDeclaration* style, Node* node)
289 Node* blockquote = nearestMailBlockquote(node);
290 if (!blockquote || !blockquote->parentNode())
291 return;
293 RefPtr<CSSMutableStyleDeclaration> parentStyle = Position(blockquote->parentNode(), 0).computedStyle()->copyInheritableProperties();
294 RefPtr<CSSMutableStyleDeclaration> blockquoteStyle = Position(blockquote, 0).computedStyle()->copyInheritableProperties();
295 parentStyle->diff(blockquoteStyle.get());
296 blockquoteStyle->diff(style);
299 static void removeDefaultStyles(CSSMutableStyleDeclaration* style, Document* document)
301 if (!document || !document->documentElement())
302 return;
304 RefPtr<CSSMutableStyleDeclaration> documentStyle = computedStyle(document->documentElement())->copyInheritableProperties();
305 documentStyle->diff(style);
308 static bool shouldAddNamespaceElem(const Element* elem)
310 // Don't add namespace attribute if it is already defined for this elem.
311 const AtomicString& prefix = elem->prefix();
312 AtomicString attr = !prefix.isEmpty() ? "xmlns:" + prefix : "xmlns";
313 return !elem->hasAttribute(attr);
316 static bool shouldAddNamespaceAttr(const Attribute* attr, HashMap<AtomicStringImpl*, AtomicStringImpl*>& namespaces)
318 // Don't add namespace attributes twice
319 static const AtomicString xmlnsURI = "http://www.w3.org/2000/xmlns/";
320 static const QualifiedName xmlnsAttr(nullAtom, "xmlns", xmlnsURI);
321 if (attr->name() == xmlnsAttr) {
322 namespaces.set(emptyAtom.impl(), attr->value().impl());
323 return false;
326 QualifiedName xmlnsPrefixAttr("xmlns", attr->localName(), xmlnsURI);
327 if (attr->name() == xmlnsPrefixAttr) {
328 namespaces.set(attr->localName().impl(), attr->value().impl());
329 return false;
332 return true;
335 static void appendNamespace(Vector<UChar>& result, const AtomicString& prefix, const AtomicString& ns, HashMap<AtomicStringImpl*, AtomicStringImpl*>& namespaces)
337 if (ns.isEmpty())
338 return;
340 // Use emptyAtoms's impl() for both null and empty strings since the HashMap can't handle 0 as a key
341 AtomicStringImpl* pre = prefix.isEmpty() ? emptyAtom.impl() : prefix.impl();
342 AtomicStringImpl* foundNS = namespaces.get(pre);
343 if (foundNS != ns.impl()) {
344 namespaces.set(pre, ns.impl());
345 static const String xmlns("xmlns");
346 result.append(' ');
347 append(result, xmlns);
348 if (!prefix.isEmpty()) {
349 result.append(':');
350 append(result, prefix);
353 result.append('=');
354 result.append('"');
355 appendAttributeValue(result, ns, false);
356 result.append('"');
360 static void appendDocumentType(Vector<UChar>& result, const DocumentType* n)
362 if (n->name().isEmpty())
363 return;
365 append(result, "<!DOCTYPE ");
366 append(result, n->name());
367 if (!n->publicId().isEmpty()) {
368 append(result, " PUBLIC \"");
369 append(result, n->publicId());
370 append(result, "\"");
371 if (!n->systemId().isEmpty()) {
372 append(result, " \"");
373 append(result, n->systemId());
374 append(result, "\"");
376 } else if (!n->systemId().isEmpty()) {
377 append(result, " SYSTEM \"");
378 append(result, n->systemId());
379 append(result, "\"");
381 if (!n->internalSubset().isEmpty()) {
382 append(result, " [");
383 append(result, n->internalSubset());
384 append(result, "]");
386 append(result, ">");
389 static void appendStartMarkup(Vector<UChar>& result, const Node *node, const Range *range, EAnnotateForInterchange annotate, bool convertBlocksToInlines = false, HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0)
391 bool documentIsHTML = node->document()->isHTMLDocument();
392 switch (node->nodeType()) {
393 case Node::TEXT_NODE: {
394 if (Node* parent = node->parentNode()) {
395 if (parent->hasTagName(scriptTag)
396 || parent->hasTagName(styleTag)
397 || parent->hasTagName(textareaTag)
398 || parent->hasTagName(xmpTag)) {
399 appendUCharRange(result, ucharRange(node, range));
400 break;
403 if (!annotate) {
404 appendEscapedContent(result, ucharRange(node, range), documentIsHTML);
405 break;
408 bool useRenderedText = !enclosingNodeWithTag(Position(const_cast<Node*>(node), 0), selectTag);
409 String markup = escapeContentText(useRenderedText ? renderedText(node, range) : stringValueForRange(node, range), false);
410 if (annotate)
411 markup = convertHTMLTextToInterchangeFormat(markup, static_cast<const Text*>(node));
412 append(result, markup);
413 break;
415 case Node::COMMENT_NODE:
416 // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->".
417 append(result, "<!--");
418 append(result, static_cast<const Comment*>(node)->nodeValue());
419 append(result, "-->");
420 break;
421 case Node::DOCUMENT_NODE:
422 case Node::DOCUMENT_FRAGMENT_NODE:
423 break;
424 case Node::DOCUMENT_TYPE_NODE:
425 appendDocumentType(result, static_cast<const DocumentType*>(node));
426 break;
427 case Node::PROCESSING_INSTRUCTION_NODE: {
428 // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>".
429 const ProcessingInstruction* n = static_cast<const ProcessingInstruction*>(node);
430 append(result, "<?");
431 append(result, n->target());
432 append(result, " ");
433 append(result, n->data());
434 append(result, "?>");
435 break;
437 case Node::ELEMENT_NODE: {
438 result.append('<');
439 const Element* el = static_cast<const Element*>(node);
440 bool convert = convertBlocksToInlines & isBlock(const_cast<Node*>(node));
441 append(result, el->nodeNamePreservingCase());
442 NamedAttrMap *attrs = el->attributes();
443 unsigned length = attrs->length();
444 if (!documentIsHTML && namespaces && shouldAddNamespaceElem(el))
445 appendNamespace(result, el->prefix(), el->namespaceURI(), *namespaces);
447 for (unsigned int i = 0; i < length; i++) {
448 Attribute *attr = attrs->attributeItem(i);
449 // We'll handle the style attribute separately, below.
450 if (attr->name() == styleAttr && el->isHTMLElement() && (annotate || convert))
451 continue;
452 result.append(' ');
454 if (documentIsHTML)
455 append(result, attr->name().localName());
456 else
457 append(result, attr->name().toString());
459 result.append('=');
461 if (el->isURLAttribute(attr))
462 appendQuotedURLAttributeValue(result, attr->value());
463 else {
464 result.append('\"');
465 appendAttributeValue(result, attr->value(), documentIsHTML);
466 result.append('\"');
469 if (!documentIsHTML && namespaces && shouldAddNamespaceAttr(attr, *namespaces))
470 appendNamespace(result, attr->prefix(), attr->namespaceURI(), *namespaces);
473 if (el->isHTMLElement() && (annotate || convert)) {
474 Element* element = const_cast<Element*>(el);
475 RefPtr<CSSMutableStyleDeclaration> style = static_cast<HTMLElement*>(element)->getInlineStyleDecl()->copy();
476 if (annotate) {
477 RefPtr<CSSMutableStyleDeclaration> styleFromMatchedRules = styleFromMatchedRulesForElement(const_cast<Element*>(el));
478 // Styles from the inline style declaration, held in the variable "style", take precedence
479 // over those from matched rules.
480 styleFromMatchedRules->merge(style.get());
481 style = styleFromMatchedRules;
483 RefPtr<CSSComputedStyleDeclaration> computedStyleForElement = computedStyle(element);
484 RefPtr<CSSMutableStyleDeclaration> fromComputedStyle = CSSMutableStyleDeclaration::create();
486 DeprecatedValueListConstIterator<CSSProperty> end;
487 for (DeprecatedValueListConstIterator<CSSProperty> it = style->valuesIterator(); it != end; ++it) {
488 const CSSProperty& property = *it;
489 CSSValue* value = property.value();
490 // The property value, if it's a percentage, may not reflect the actual computed value.
491 // For example: style="height: 1%; overflow: visible;" in quirksmode
492 // FIXME: There are others like this, see <rdar://problem/5195123> Slashdot copy/paste fidelity problem
493 if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE)
494 if (static_cast<CSSPrimitiveValue*>(value)->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
495 if (RefPtr<CSSValue> computedPropertyValue = computedStyleForElement->getPropertyCSSValue(property.id()))
496 fromComputedStyle->addParsedProperty(CSSProperty(property.id(), computedPropertyValue));
499 style->merge(fromComputedStyle.get());
501 if (convert)
502 style->setProperty(CSSPropertyDisplay, CSSValueInline, true);
503 if (style->length() > 0) {
504 static const String stylePrefix(" style=\"");
505 append(result, stylePrefix);
506 appendAttributeValue(result, style->cssText(), documentIsHTML);
507 result.append('\"');
511 if (shouldSelfClose(el)) {
512 if (el->isHTMLElement())
513 result.append(' '); // XHTML 1.0 <-> HTML compatibility.
514 result.append('/');
516 result.append('>');
517 break;
519 case Node::CDATA_SECTION_NODE: {
520 // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>".
521 const CDATASection* n = static_cast<const CDATASection*>(node);
522 append(result, "<![CDATA[");
523 append(result, n->data());
524 append(result, "]]>");
525 break;
527 case Node::ATTRIBUTE_NODE:
528 case Node::ENTITY_NODE:
529 case Node::ENTITY_REFERENCE_NODE:
530 case Node::NOTATION_NODE:
531 case Node::XPATH_NAMESPACE_NODE:
532 ASSERT_NOT_REACHED();
533 break;
537 static String getStartMarkup(const Node *node, const Range *range, EAnnotateForInterchange annotate, bool convertBlocksToInlines = false, HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0)
539 Vector<UChar> result;
540 appendStartMarkup(result, node, range, annotate, convertBlocksToInlines, namespaces);
541 return String::adopt(result);
544 static inline bool doesHTMLForbidEndTag(const Node *node)
546 if (node->isHTMLElement()) {
547 const HTMLElement* htmlElt = static_cast<const HTMLElement*>(node);
548 return (htmlElt->endTagRequirement() == TagStatusForbidden);
550 return false;
553 // Rules of self-closure
554 // 1. No elements in HTML documents use the self-closing syntax.
555 // 2. Elements w/ children never self-close because they use a separate end tag.
556 // 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag.
557 // 4. Other elements self-close.
558 static inline bool shouldSelfClose(const Node *node)
560 if (node->document()->isHTMLDocument())
561 return false;
562 if (node->hasChildNodes())
563 return false;
564 if (node->isHTMLElement() && !doesHTMLForbidEndTag(node))
565 return false;
566 return true;
569 static void appendEndMarkup(Vector<UChar>& result, const Node* node)
571 if (!node->isElementNode() || shouldSelfClose(node) || (!node->hasChildNodes() && doesHTMLForbidEndTag(node)))
572 return;
574 result.append('<');
575 result.append('/');
576 append(result, static_cast<const Element*>(node)->nodeNamePreservingCase());
577 result.append('>');
580 static String getEndMarkup(const Node *node)
582 Vector<UChar> result;
583 appendEndMarkup(result, node);
584 return String::adopt(result);
587 static void appendMarkup(Vector<UChar>& result, Node* startNode, bool onlyIncludeChildren, Vector<Node*>* nodes, const HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0)
589 HashMap<AtomicStringImpl*, AtomicStringImpl*> namespaceHash;
590 if (namespaces)
591 namespaceHash = *namespaces;
593 if (!onlyIncludeChildren) {
594 if (nodes)
595 nodes->append(startNode);
597 appendStartMarkup(result,startNode, 0, DoNotAnnotateForInterchange, false, &namespaceHash);
599 // print children
600 if (!(startNode->document()->isHTMLDocument() && doesHTMLForbidEndTag(startNode)))
601 for (Node* current = startNode->firstChild(); current; current = current->nextSibling())
602 appendMarkup(result, current, false, nodes, &namespaceHash);
604 // Print my ending tag
605 if (!onlyIncludeChildren)
606 appendEndMarkup(result, startNode);
609 static void completeURLs(Node* node, const String& baseURL)
611 Vector<AttributeChange> changes;
613 KURL parsedBaseURL(baseURL);
615 Node* end = node->traverseNextSibling();
616 for (Node* n = node; n != end; n = n->traverseNextNode()) {
617 if (n->isElementNode()) {
618 Element* e = static_cast<Element*>(n);
619 NamedAttrMap* attrs = e->attributes();
620 unsigned length = attrs->length();
621 for (unsigned i = 0; i < length; i++) {
622 Attribute* attr = attrs->attributeItem(i);
623 if (e->isURLAttribute(attr))
624 changes.append(AttributeChange(e, attr->name(), KURL(parsedBaseURL, attr->value()).string()));
629 size_t numChanges = changes.size();
630 for (size_t i = 0; i < numChanges; ++i)
631 changes[i].apply();
634 static bool needInterchangeNewlineAfter(const VisiblePosition& v)
636 VisiblePosition next = v.next();
637 Node* upstreamNode = next.deepEquivalent().upstream().node();
638 Node* downstreamNode = v.deepEquivalent().downstream().node();
639 // Add an interchange newline if a paragraph break is selected and a br won't already be added to the markup to represent it.
640 return isEndOfParagraph(v) && isStartOfParagraph(next) && !(upstreamNode->hasTagName(brTag) && upstreamNode == downstreamNode);
643 static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesAndInlineDecl(const Node* node)
645 if (!node->isHTMLElement())
646 return 0;
648 // FIXME: Having to const_cast here is ugly, but it is quite a bit of work to untangle
649 // the non-const-ness of styleFromMatchedRulesForElement.
650 HTMLElement* element = const_cast<HTMLElement*>(static_cast<const HTMLElement*>(node));
651 RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesForElement(element);
652 RefPtr<CSSMutableStyleDeclaration> inlineStyleDecl = element->getInlineStyleDecl();
653 style->merge(inlineStyleDecl.get());
654 return style.release();
657 static bool propertyMissingOrEqualToNone(CSSMutableStyleDeclaration* style, int propertyID)
659 if (!style)
660 return false;
661 RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID);
662 if (!value)
663 return true;
664 if (!value->isPrimitiveValue())
665 return false;
666 return static_cast<CSSPrimitiveValue*>(value.get())->getIdent() == CSSValueNone;
669 static bool elementHasTextDecorationProperty(const Node* node)
671 RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesAndInlineDecl(node);
672 if (!style)
673 return false;
674 return !propertyMissingOrEqualToNone(style.get(), CSSPropertyTextDecoration);
677 String joinMarkups(const Vector<String> preMarkups, const Vector<String>& postMarkups)
679 size_t length = 0;
681 size_t preCount = preMarkups.size();
682 for (size_t i = 0; i < preCount; ++i)
683 length += preMarkups[i].length();
685 size_t postCount = postMarkups.size();
686 for (size_t i = 0; i < postCount; ++i)
687 length += postMarkups[i].length();
689 Vector<UChar> result;
690 result.reserveCapacity(length);
692 for (size_t i = preCount; i > 0; --i)
693 append(result, preMarkups[i - 1]);
695 for (size_t i = 0; i < postCount; ++i)
696 append(result, postMarkups[i]);
698 return String::adopt(result);
701 // FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange?
702 // FIXME: At least, annotation and style info should probably not be included in range.markupString()
703 String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterchange annotate, bool convertBlocksToInlines)
705 static const String interchangeNewlineString = String("<br class=\"") + AppleInterchangeNewline + "\">";
707 if (!range)
708 return "";
710 Document* document = range->ownerDocument();
711 if (!document)
712 return "";
714 bool documentIsHTML = document->isHTMLDocument();
716 // Disable the delete button so it's elements are not serialized into the markup,
717 // but make sure neither endpoint is inside the delete user interface.
718 Frame* frame = document->frame();
719 DeleteButtonController* deleteButton = frame ? frame->editor()->deleteButtonController() : 0;
720 RefPtr<Range> updatedRange = avoidIntersectionWithNode(range, deleteButton ? deleteButton->containerElement() : 0);
721 if (!updatedRange)
722 return "";
724 if (deleteButton)
725 deleteButton->disable();
727 ExceptionCode ec = 0;
728 bool collapsed = updatedRange->collapsed(ec);
729 ASSERT(ec == 0);
730 if (collapsed)
731 return "";
732 Node* commonAncestor = updatedRange->commonAncestorContainer(ec);
733 ASSERT(ec == 0);
734 if (!commonAncestor)
735 return "";
737 document->updateLayoutIgnorePendingStylesheets();
739 Vector<String> markups;
740 Vector<String> preMarkups;
741 Node* pastEnd = updatedRange->pastLastNode();
742 Node* lastClosed = 0;
743 Vector<Node*> ancestorsToClose;
745 Node* startNode = updatedRange->firstNode();
746 VisiblePosition visibleStart(updatedRange->startPosition(), VP_DEFAULT_AFFINITY);
747 VisiblePosition visibleEnd(updatedRange->endPosition(), VP_DEFAULT_AFFINITY);
748 if (annotate && needInterchangeNewlineAfter(visibleStart)) {
749 if (visibleStart == visibleEnd.previous()) {
750 if (deleteButton)
751 deleteButton->enable();
752 return interchangeNewlineString;
755 markups.append(interchangeNewlineString);
756 startNode = visibleStart.next().deepEquivalent().node();
759 Node* next;
760 for (Node* n = startNode; n != pastEnd; n = next) {
762 // According to <rdar://problem/5730668>, it is possible for n to blow past pastEnd and become null here. This
763 // shouldn't be possible. This null check will prevent crashes (but create too much markup) and the ASSERT will
764 // hopefully lead us to understanding the problem.
765 ASSERT(n);
766 if (!n)
767 break;
769 next = n->traverseNextNode();
770 bool skipDescendants = false;
771 bool addMarkupForNode = true;
773 if (!n->renderer() && !enclosingNodeWithTag(Position(n, 0), selectTag)) {
774 skipDescendants = true;
775 addMarkupForNode = false;
776 next = n->traverseNextSibling();
777 // Don't skip over pastEnd.
778 if (pastEnd && pastEnd->isDescendantOf(n))
779 next = pastEnd;
782 if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd)
783 // Don't write out empty block containers that aren't fully selected.
784 continue;
786 // Add the node to the markup.
787 if (addMarkupForNode) {
788 markups.append(getStartMarkup(n, updatedRange.get(), annotate));
789 if (nodes)
790 nodes->append(n);
793 if (n->firstChild() == 0 || skipDescendants) {
794 // Node has no children, or we are skipping it's descendants, add its close tag now.
795 if (addMarkupForNode) {
796 markups.append(getEndMarkup(n));
797 lastClosed = n;
800 // Check if the node is the last leaf of a tree.
801 if (!n->nextSibling() || next == pastEnd) {
802 if (!ancestorsToClose.isEmpty()) {
803 // Close up the ancestors.
804 do {
805 Node *ancestor = ancestorsToClose.last();
806 if (next != pastEnd && next->isDescendantOf(ancestor))
807 break;
808 // Not at the end of the range, close ancestors up to sibling of next node.
809 markups.append(getEndMarkup(ancestor));
810 lastClosed = ancestor;
811 ancestorsToClose.removeLast();
812 } while (!ancestorsToClose.isEmpty());
815 // Surround the currently accumulated markup with markup for ancestors we never opened as we leave the subtree(s) rooted at those ancestors.
816 Node* nextParent = next ? next->parentNode() : 0;
817 if (next != pastEnd && n != nextParent) {
818 Node* lastAncestorClosedOrSelf = n->isDescendantOf(lastClosed) ? lastClosed : n;
819 for (Node *parent = lastAncestorClosedOrSelf->parent(); parent != 0 && parent != nextParent; parent = parent->parentNode()) {
820 // All ancestors that aren't in the ancestorsToClose list should either be a) unrendered:
821 if (!parent->renderer())
822 continue;
823 // or b) ancestors that we never encountered during a pre-order traversal starting at startNode:
824 ASSERT(startNode->isDescendantOf(parent));
825 preMarkups.append(getStartMarkup(parent, updatedRange.get(), annotate));
826 markups.append(getEndMarkup(parent));
827 if (nodes)
828 nodes->append(parent);
829 lastClosed = parent;
833 } else if (addMarkupForNode && !skipDescendants)
834 // We added markup for this node, and we're descending into it. Set it to close eventually.
835 ancestorsToClose.append(n);
838 // Include ancestors that aren't completely inside the range but are required to retain
839 // the structure and appearance of the copied markup.
840 Node* specialCommonAncestor = 0;
841 Node* commonAncestorBlock = commonAncestor ? enclosingBlock(commonAncestor) : 0;
842 if (annotate && commonAncestorBlock) {
843 if (commonAncestorBlock->hasTagName(tbodyTag) || commonAncestorBlock->hasTagName(trTag)) {
844 Node* table = commonAncestorBlock->parentNode();
845 while (table && !table->hasTagName(tableTag))
846 table = table->parentNode();
847 if (table)
848 specialCommonAncestor = table;
849 } else if (commonAncestorBlock->hasTagName(listingTag)
850 || commonAncestorBlock->hasTagName(olTag)
851 || commonAncestorBlock->hasTagName(preTag)
852 || commonAncestorBlock->hasTagName(tableTag)
853 || commonAncestorBlock->hasTagName(ulTag)
854 || commonAncestorBlock->hasTagName(xmpTag))
855 specialCommonAncestor = commonAncestorBlock;
858 bool selectedOneOrMoreParagraphs = startOfParagraph(visibleStart) != startOfParagraph(visibleEnd) ||
859 isStartOfParagraph(visibleStart) && isEndOfParagraph(visibleEnd);
861 // Retain the Mail quote level by including all ancestor mail block quotes.
862 if (lastClosed && annotate && selectedOneOrMoreParagraphs) {
863 for (Node *ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode())
864 if (isMailBlockquote(ancestor))
865 specialCommonAncestor = ancestor;
868 Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor;
869 if (checkAncestor->renderer()) {
870 RefPtr<CSSMutableStyleDeclaration> checkAncestorStyle = computedStyle(checkAncestor)->copyInheritableProperties();
871 if (!propertyMissingOrEqualToNone(checkAncestorStyle.get(), CSSPropertyWebkitTextDecorationsInEffect))
872 specialCommonAncestor = enclosingNodeOfType(Position(checkAncestor, 0), &elementHasTextDecorationProperty);
875 // If a single tab is selected, commonAncestor will be a text node inside a tab span.
876 // If two or more tabs are selected, commonAncestor will be the tab span.
877 // In either case, if there is a specialCommonAncestor already, it will necessarily be above
878 // any tab span that needs to be included.
879 if (!specialCommonAncestor && isTabSpanTextNode(commonAncestor))
880 specialCommonAncestor = commonAncestor->parentNode();
881 if (!specialCommonAncestor && isTabSpanNode(commonAncestor))
882 specialCommonAncestor = commonAncestor;
884 if (Node *enclosingAnchor = enclosingNodeWithTag(Position(specialCommonAncestor ? specialCommonAncestor : commonAncestor, 0), aTag))
885 specialCommonAncestor = enclosingAnchor;
887 Node* body = enclosingNodeWithTag(Position(commonAncestor, 0), bodyTag);
888 // FIXME: Only include markup for a fully selected root (and ancestors of lastClosed up to that root) if
889 // there are styles/attributes on those nodes that need to be included to preserve the appearance of the copied markup.
890 // FIXME: Do this for all fully selected blocks, not just the body.
891 Node* fullySelectedRoot = body && *Selection::selectionFromContentsOfNode(body).toRange() == *updatedRange ? body : 0;
892 if (annotate && fullySelectedRoot)
893 specialCommonAncestor = fullySelectedRoot;
895 if (specialCommonAncestor && lastClosed) {
896 // Also include all of the ancestors of lastClosed up to this special ancestor.
897 for (Node* ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
898 if (ancestor == fullySelectedRoot && !convertBlocksToInlines) {
899 RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesAndInlineDecl(fullySelectedRoot);
901 // Bring the background attribute over, but not as an attribute because a background attribute on a div
902 // appears to have no effect.
903 if (!style->getPropertyCSSValue(CSSPropertyBackgroundImage) && static_cast<Element*>(fullySelectedRoot)->hasAttribute(backgroundAttr))
904 style->setProperty(CSSPropertyBackgroundImage, "url('" + static_cast<Element*>(fullySelectedRoot)->getAttribute(backgroundAttr) + "')");
906 if (style->length()) {
907 Vector<UChar> openTag;
908 static const String divStyle("<div style=\"");
909 append(openTag, divStyle);
910 appendAttributeValue(openTag, style->cssText(), documentIsHTML);
911 openTag.append('\"');
912 openTag.append('>');
913 preMarkups.append(String::adopt(openTag));
915 static const String divCloseTag("</div>");
916 markups.append(divCloseTag);
918 } else {
919 preMarkups.append(getStartMarkup(ancestor, updatedRange.get(), annotate, convertBlocksToInlines));
920 markups.append(getEndMarkup(ancestor));
922 if (nodes)
923 nodes->append(ancestor);
925 lastClosed = ancestor;
927 if (ancestor == specialCommonAncestor)
928 break;
932 static const String styleSpanOpen = String("<span class=\"" AppleStyleSpanClass "\" style=\"");
933 static const String styleSpanClose("</span>");
935 // Add a wrapper span with the styles that all of the nodes in the markup inherit.
936 Node* parentOfLastClosed = lastClosed ? lastClosed->parentNode() : 0;
937 if (parentOfLastClosed && parentOfLastClosed->renderer()) {
938 RefPtr<CSSMutableStyleDeclaration> style = computedStyle(parentOfLastClosed)->copyInheritableProperties();
940 // Styles that Mail blockquotes contribute should only be placed on the Mail blockquote, to help
941 // us differentiate those styles from ones that the user has applied. This helps us
942 // get the color of content pasted into blockquotes right.
943 removeEnclosingMailBlockquoteStyle(style.get(), parentOfLastClosed);
945 // Document default styles will be added on another wrapper span.
946 removeDefaultStyles(style.get(), document);
948 // Since we are converting blocks to inlines, remove any inherited block properties that are in the style.
949 // This cuts out meaningless properties and prevents properties from magically affecting blocks later
950 // if the style is cloned for a new block element during a future editing operation.
951 if (convertBlocksToInlines)
952 style->removeBlockProperties();
954 if (style->length() > 0) {
955 Vector<UChar> openTag;
956 append(openTag, styleSpanOpen);
957 appendAttributeValue(openTag, style->cssText(), documentIsHTML);
958 openTag.append('\"');
959 openTag.append('>');
960 preMarkups.append(String::adopt(openTag));
962 markups.append(styleSpanClose);
966 if (lastClosed && lastClosed != document->documentElement()) {
967 // Add a style span with the document's default styles. We add these in a separate
968 // span so that at paste time we can differentiate between document defaults and user
969 // applied styles.
970 RefPtr<CSSMutableStyleDeclaration> defaultStyle = computedStyle(document->documentElement())->copyInheritableProperties();
972 if (defaultStyle->length() > 0) {
973 Vector<UChar> openTag;
974 append(openTag, styleSpanOpen);
975 appendAttributeValue(openTag, defaultStyle->cssText(), documentIsHTML);
976 openTag.append('\"');
977 openTag.append('>');
978 preMarkups.append(String::adopt(openTag));
979 markups.append(styleSpanClose);
983 // FIXME: The interchange newline should be placed in the block that it's in, not after all of the content, unconditionally.
984 if (annotate && needInterchangeNewlineAfter(visibleEnd.previous()))
985 markups.append(interchangeNewlineString);
987 if (deleteButton)
988 deleteButton->enable();
990 return joinMarkups(preMarkups, markups);
993 PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const String& markup, const String& baseURL)
995 ASSERT(document->documentElement()->isHTMLElement());
996 // FIXME: What if the document element is not an HTML element?
997 HTMLElement *element = static_cast<HTMLElement*>(document->documentElement());
999 RefPtr<DocumentFragment> fragment = element->createContextualFragment(markup);
1001 if (fragment && !baseURL.isEmpty() && baseURL != blankURL() && baseURL != document->baseURL())
1002 completeURLs(fragment.get(), baseURL);
1004 return fragment.release();
1007 String createMarkup(const Node* node, EChildrenOnly includeChildren, Vector<Node*>* nodes)
1009 Vector<UChar> result;
1011 if (!node)
1012 return "";
1014 Document* document = node->document();
1015 Frame* frame = document->frame();
1016 DeleteButtonController* deleteButton = frame ? frame->editor()->deleteButtonController() : 0;
1018 // disable the delete button so it's elements are not serialized into the markup
1019 if (deleteButton) {
1020 if (node->isDescendantOf(deleteButton->containerElement()))
1021 return "";
1022 deleteButton->disable();
1025 appendMarkup(result, const_cast<Node*>(node), includeChildren, nodes);
1027 if (deleteButton)
1028 deleteButton->enable();
1030 return String::adopt(result);
1033 static void fillContainerFromString(ContainerNode* paragraph, const String& string)
1035 Document* document = paragraph->document();
1037 ExceptionCode ec = 0;
1038 if (string.isEmpty()) {
1039 paragraph->appendChild(createBlockPlaceholderElement(document), ec);
1040 ASSERT(ec == 0);
1041 return;
1044 ASSERT(string.find('\n') == -1);
1046 Vector<String> tabList;
1047 string.split('\t', true, tabList);
1048 String tabText = "";
1049 bool first = true;
1050 size_t numEntries = tabList.size();
1051 for (size_t i = 0; i < numEntries; ++i) {
1052 const String& s = tabList[i];
1054 // append the non-tab textual part
1055 if (!s.isEmpty()) {
1056 if (!tabText.isEmpty()) {
1057 paragraph->appendChild(createTabSpanElement(document, tabText), ec);
1058 ASSERT(ec == 0);
1059 tabText = "";
1061 RefPtr<Node> textNode = document->createTextNode(stringWithRebalancedWhitespace(s, first, i + 1 == numEntries));
1062 paragraph->appendChild(textNode.release(), ec);
1063 ASSERT(ec == 0);
1066 // there is a tab after every entry, except the last entry
1067 // (if the last character is a tab, the list gets an extra empty entry)
1068 if (i + 1 != numEntries)
1069 tabText.append('\t');
1070 else if (!tabText.isEmpty()) {
1071 paragraph->appendChild(createTabSpanElement(document, tabText), ec);
1072 ASSERT(ec == 0);
1075 first = false;
1079 PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String& text)
1081 if (!context)
1082 return 0;
1084 Node* styleNode = context->firstNode();
1085 if (!styleNode) {
1086 styleNode = context->startPosition().node();
1087 if (!styleNode)
1088 return 0;
1091 Document* document = styleNode->document();
1092 RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
1094 if (text.isEmpty())
1095 return fragment.release();
1097 String string = text;
1098 string.replace("\r\n", "\n");
1099 string.replace('\r', '\n');
1101 ExceptionCode ec = 0;
1102 RenderObject* renderer = styleNode->renderer();
1103 if (renderer && renderer->style()->preserveNewline()) {
1104 fragment->appendChild(document->createTextNode(string), ec);
1105 ASSERT(ec == 0);
1106 if (string.endsWith("\n")) {
1107 RefPtr<Element> element;
1108 element = document->createElementNS(xhtmlNamespaceURI, "br", ec);
1109 ASSERT(ec == 0);
1110 element->setAttribute(classAttr, AppleInterchangeNewline);
1111 fragment->appendChild(element.release(), ec);
1112 ASSERT(ec == 0);
1114 return fragment.release();
1117 // A string with no newlines gets added inline, rather than being put into a paragraph.
1118 if (string.find('\n') == -1) {
1119 fillContainerFromString(fragment.get(), string);
1120 return fragment.release();
1123 // Break string into paragraphs. Extra line breaks turn into empty paragraphs.
1124 Node* block = enclosingBlock(context->firstNode());
1125 bool useClonesOfEnclosingBlock = block && !block->hasTagName(bodyTag) && !block->hasTagName(htmlTag) && block != editableRootForPosition(context->startPosition());
1127 Vector<String> list;
1128 string.split('\n', true, list); // true gets us empty strings in the list
1129 size_t numLines = list.size();
1130 for (size_t i = 0; i < numLines; ++i) {
1131 const String& s = list[i];
1133 RefPtr<Element> element;
1134 if (s.isEmpty() && i + 1 == numLines) {
1135 // For last line, use the "magic BR" rather than a P.
1136 element = document->createElementNS(xhtmlNamespaceURI, "br", ec);
1137 ASSERT(ec == 0);
1138 element->setAttribute(classAttr, AppleInterchangeNewline);
1139 } else {
1140 element = useClonesOfEnclosingBlock ? static_cast<Element*>(block->cloneNode(false).get()) : createDefaultParagraphElement(document);
1141 fillContainerFromString(element.get(), s);
1143 fragment->appendChild(element.release(), ec);
1144 ASSERT(ec == 0);
1146 return fragment.release();
1149 PassRefPtr<DocumentFragment> createFragmentFromNodes(Document *document, const Vector<Node*>& nodes)
1151 if (!document)
1152 return 0;
1154 // disable the delete button so it's elements are not serialized into the markup
1155 if (document->frame())
1156 document->frame()->editor()->deleteButtonController()->disable();
1158 RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
1160 ExceptionCode ec = 0;
1161 size_t size = nodes.size();
1162 for (size_t i = 0; i < size; ++i) {
1163 RefPtr<Element> element = createDefaultParagraphElement(document);
1164 element->appendChild(nodes[i], ec);
1165 ASSERT(ec == 0);
1166 fragment->appendChild(element.release(), ec);
1167 ASSERT(ec == 0);
1170 if (document->frame())
1171 document->frame()->editor()->deleteButtonController()->enable();
1173 return fragment.release();
1176 String createFullMarkup(const Node* node)
1178 if (!node)
1179 return String();
1181 Document* document = node->document();
1182 if (!document)
1183 return String();
1185 Frame* frame = document->frame();
1186 if (!frame)
1187 return String();
1189 // FIXME: This is never "for interchange". Is that right?
1190 String markupString = createMarkup(node, IncludeNode, 0);
1191 Node::NodeType nodeType = node->nodeType();
1192 if (nodeType != Node::DOCUMENT_NODE && nodeType != Node::DOCUMENT_TYPE_NODE)
1193 markupString = frame->documentTypeString() + markupString;
1195 return markupString;
1198 String createFullMarkup(const Range* range)
1200 if (!range)
1201 return String();
1203 Node* node = range->startContainer();
1204 if (!node)
1205 return String();
1207 Document* document = node->document();
1208 if (!document)
1209 return String();
1211 Frame* frame = document->frame();
1212 if (!frame)
1213 return String();
1215 // FIXME: This is always "for interchange". Is that right? See the previous method.
1216 return frame->documentTypeString() + createMarkup(range, 0, AnnotateForInterchange);