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
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 "Selection.h"
56 #include "TextIterator.h"
57 #include "htmlediting.h"
58 #include "visible_units.h"
64 using namespace HTMLNames
;
66 static inline bool shouldSelfClose(const Node
*node
);
68 class 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
)
82 m_element
->setAttribute(m_name
, m_value
);
86 RefPtr
<Element
> m_element
;
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("&");
98 static const String
gtEntity(">");
99 static const String
ltEntity("<");
100 static const String
quotEntity(""");
101 static const String
nbspEntity(" ");
103 for (unsigned i
= 0; i
< len
; ++i
) {
107 result
.append(uchars
+ lastCopiedFrom
, i
- lastCopiedFrom
);
108 append(result
, ampEntity
);
109 lastCopiedFrom
= i
+ 1;
112 result
.append(uchars
+ lastCopiedFrom
, i
- lastCopiedFrom
);
113 append(result
, ltEntity
);
114 lastCopiedFrom
= i
+ 1;
117 result
.append(uchars
+ lastCopiedFrom
, i
- lastCopiedFrom
);
118 append(result
, gtEntity
);
119 lastCopiedFrom
= i
+ 1;
122 result
.append(uchars
+ lastCopiedFrom
, i
- lastCopiedFrom
);
123 append(result
, quotEntity
);
124 lastCopiedFrom
= i
+ 1;
128 result
.append(uchars
+ lastCopiedFrom
, i
- lastCopiedFrom
);
129 append(result
, nbspEntity
);
130 lastCopiedFrom
= i
+ 1;
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("&");
146 static const String
gtEntity(">");
147 static const String
ltEntity("<");
148 static const String
nbspEntity(" ");
150 for (unsigned i
= 0; i
< len
; ++i
) {
154 result
.append(uchars
+ lastCopiedFrom
, i
- lastCopiedFrom
);
155 append(result
, ampEntity
);
156 lastCopiedFrom
= i
+ 1;
159 result
.append(uchars
+ lastCopiedFrom
, i
- lastCopiedFrom
);
160 append(result
, ltEntity
);
161 lastCopiedFrom
= i
+ 1;
164 result
.append(uchars
+ lastCopiedFrom
, i
- lastCopiedFrom
);
165 append(result
, gtEntity
);
166 lastCopiedFrom
= i
+ 1;
170 result
.append(uchars
+ lastCopiedFrom
, i
- lastCopiedFrom
);
171 append(result
, nbspEntity
);
172 lastCopiedFrom
= i
+ 1;
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('\"', """);
200 result
.append(quoteChar
);
201 append(result
, strippedURLString
);
202 result
.append(quoteChar
);
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
)
215 return node
->nodeValue();
217 String str
= node
->nodeValue();
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
));
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();
234 if (node
== range
->endContainer(ec
))
235 length
= range
->endOffset(ec
);
236 if (node
== range
->startContainer(ec
)) {
237 size_t start
= range
->startOffset(ec
);
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())
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
);
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())
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())
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());
326 QualifiedName
xmlnsPrefixAttr("xmlns", attr
->localName(), xmlnsURI
);
327 if (attr
->name() == xmlnsPrefixAttr
) {
328 namespaces
.set(attr
->localName().impl(), attr
->value().impl());
335 static void appendNamespace(Vector
<UChar
>& result
, const AtomicString
& prefix
, const AtomicString
& ns
, HashMap
<AtomicStringImpl
*, AtomicStringImpl
*>& namespaces
)
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");
347 append(result
, xmlns
);
348 if (!prefix
.isEmpty()) {
350 append(result
, prefix
);
355 appendAttributeValue(result
, ns
, false);
360 static void appendDocumentType(Vector
<UChar
>& result
, const DocumentType
* n
)
362 if (n
->name().isEmpty())
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());
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
));
404 appendEscapedContent(result
, ucharRange(node
, range
), documentIsHTML
);
408 bool useRenderedText
= !enclosingNodeWithTag(Position(const_cast<Node
*>(node
), 0), selectTag
);
409 String markup
= escapeContentText(useRenderedText
? renderedText(node
, range
) : stringValueForRange(node
, range
), false);
411 markup
= convertHTMLTextToInterchangeFormat(markup
, static_cast<const Text
*>(node
));
412 append(result
, markup
);
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
, "-->");
421 case Node::DOCUMENT_NODE
:
422 case Node::DOCUMENT_FRAGMENT_NODE
:
424 case Node::DOCUMENT_TYPE_NODE
:
425 appendDocumentType(result
, static_cast<const DocumentType
*>(node
));
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());
433 append(result
, n
->data());
434 append(result
, "?>");
437 case Node::ELEMENT_NODE
: {
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
))
455 append(result
, attr
->name().localName());
457 append(result
, attr
->name().toString());
461 if (el
->isURLAttribute(attr
))
462 appendQuotedURLAttributeValue(result
, attr
->value());
465 appendAttributeValue(result
, attr
->value(), documentIsHTML
);
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();
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());
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
);
511 if (shouldSelfClose(el
)) {
512 if (el
->isHTMLElement())
513 result
.append(' '); // XHTML 1.0 <-> HTML compatibility.
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
, "]]>");
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();
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
);
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())
562 if (node
->hasChildNodes())
564 if (node
->isHTMLElement() && !doesHTMLForbidEndTag(node
))
569 static void appendEndMarkup(Vector
<UChar
>& result
, const Node
* node
)
571 if (!node
->isElementNode() || shouldSelfClose(node
) || (!node
->hasChildNodes() && doesHTMLForbidEndTag(node
)))
576 append(result
, static_cast<const Element
*>(node
)->nodeNamePreservingCase());
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
;
591 namespaceHash
= *namespaces
;
593 if (!onlyIncludeChildren
) {
595 nodes
->append(startNode
);
597 appendStartMarkup(result
,startNode
, 0, DoNotAnnotateForInterchange
, false, &namespaceHash
);
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
)
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())
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
)
661 RefPtr
<CSSValue
> value
= style
->getPropertyCSSValue(propertyID
);
664 if (!value
->isPrimitiveValue())
666 return static_cast<CSSPrimitiveValue
*>(value
.get())->getIdent() == CSSValueNone
;
669 static bool elementHasTextDecorationProperty(const Node
* node
)
671 RefPtr
<CSSMutableStyleDeclaration
> style
= styleFromMatchedRulesAndInlineDecl(node
);
674 return !propertyMissingOrEqualToNone(style
.get(), CSSPropertyTextDecoration
);
677 String
joinMarkups(const Vector
<String
> preMarkups
, const Vector
<String
>& postMarkups
)
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
+ "\">";
710 Document
* document
= range
->ownerDocument();
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);
725 deleteButton
->disable();
727 ExceptionCode ec
= 0;
728 bool collapsed
= updatedRange
->collapsed(ec
);
732 Node
* commonAncestor
= updatedRange
->commonAncestorContainer(ec
);
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()) {
751 deleteButton
->enable();
752 return interchangeNewlineString
;
755 markups
.append(interchangeNewlineString
);
756 startNode
= visibleStart
.next().deepEquivalent().node();
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.
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
))
782 if (isBlock(n
) && canHaveChildrenForEditing(n
) && next
== pastEnd
)
783 // Don't write out empty block containers that aren't fully selected.
786 // Add the node to the markup.
787 if (addMarkupForNode
) {
788 markups
.append(getStartMarkup(n
, updatedRange
.get(), annotate
));
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
));
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.
805 Node
*ancestor
= ancestorsToClose
.last();
806 if (next
!= pastEnd
&& next
->isDescendantOf(ancestor
))
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())
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
));
828 nodes
->append(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();
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('\"');
913 preMarkups
.append(String::adopt(openTag
));
915 static const String
divCloseTag("</div>");
916 markups
.append(divCloseTag
);
919 preMarkups
.append(getStartMarkup(ancestor
, updatedRange
.get(), annotate
, convertBlocksToInlines
));
920 markups
.append(getEndMarkup(ancestor
));
923 nodes
->append(ancestor
);
925 lastClosed
= ancestor
;
927 if (ancestor
== specialCommonAncestor
)
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('\"');
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
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('\"');
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
);
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
;
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
1020 if (node
->isDescendantOf(deleteButton
->containerElement()))
1022 deleteButton
->disable();
1025 appendMarkup(result
, const_cast<Node
*>(node
), includeChildren
, nodes
);
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
);
1044 ASSERT(string
.find('\n') == -1);
1046 Vector
<String
> tabList
;
1047 string
.split('\t', true, tabList
);
1048 String tabText
= "";
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
1056 if (!tabText
.isEmpty()) {
1057 paragraph
->appendChild(createTabSpanElement(document
, tabText
), ec
);
1061 RefPtr
<Node
> textNode
= document
->createTextNode(stringWithRebalancedWhitespace(s
, first
, i
+ 1 == numEntries
));
1062 paragraph
->appendChild(textNode
.release(), ec
);
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
);
1079 PassRefPtr
<DocumentFragment
> createFragmentFromText(Range
* context
, const String
& text
)
1084 Node
* styleNode
= context
->firstNode();
1086 styleNode
= context
->startPosition().node();
1091 Document
* document
= styleNode
->document();
1092 RefPtr
<DocumentFragment
> fragment
= document
->createDocumentFragment();
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
);
1106 if (string
.endsWith("\n")) {
1107 RefPtr
<Element
> element
;
1108 element
= document
->createElementNS(xhtmlNamespaceURI
, "br", ec
);
1110 element
->setAttribute(classAttr
, AppleInterchangeNewline
);
1111 fragment
->appendChild(element
.release(), ec
);
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
);
1138 element
->setAttribute(classAttr
, AppleInterchangeNewline
);
1140 element
= useClonesOfEnclosingBlock
? static_cast<Element
*>(block
->cloneNode(false).get()) : createDefaultParagraphElement(document
);
1141 fillContainerFromString(element
.get(), s
);
1143 fragment
->appendChild(element
.release(), ec
);
1146 return fragment
.release();
1149 PassRefPtr
<DocumentFragment
> createFragmentFromNodes(Document
*document
, const Vector
<Node
*>& nodes
)
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
);
1166 fragment
->appendChild(element
.release(), ec
);
1170 if (document
->frame())
1171 document
->frame()->editor()->deleteButtonController()->enable();
1173 return fragment
.release();
1176 String
createFullMarkup(const Node
* node
)
1181 Document
* document
= node
->document();
1185 Frame
* frame
= document
->frame();
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
)
1203 Node
* node
= range
->startContainer();
1207 Document
* document
= node
->document();
1211 Frame
* frame
= document
->frame();
1215 // FIXME: This is always "for interchange". Is that right? See the previous method.
1216 return frame
->documentTypeString() + createMarkup(range
, 0, AnnotateForInterchange
);