1 /*
2 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3 *
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.
12 *
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.
24 */
25
26 #include "config.h"
27 #include "markup.h"
28
29 #include "CDATASection.h"
30 #include "CharacterNames.h"
31 #include "Comment.h"
32 #include "CSSComputedStyleDeclaration.h"
33 #include "CSSMutableStyleDeclaration.h"
34 #include "CSSPrimitiveValue.h"
35 #include "CSSProperty.h"
36 #include "CSSPropertyNames.h"
37 #include "CSSRule.h"
38 #include "CSSRuleList.h"
39 #include "CSSStyleRule.h"
40 #include "CSSStyleSelector.h"
41 #include "CSSValue.h"
42 #include "CSSValueKeywords.h"
43 #include "DeleteButtonController.h"
44 #include "Document.h"
45 #include "DocumentFragment.h"
46 #include "DocumentType.h"
47 #include "Editor.h"
48 #include "Frame.h"
49 #include "HTMLElement.h"
50 #include "HTMLNames.h"
51 #include "InlineTextBox.h"
52 #include "Logging.h"
53 #include "ProcessingInstruction.h"
54 #include "QualifiedName.h"
55 #include "Range.h"
56 #include "VisibleSelection.h"
57 #include "TextIterator.h"
58 #include "htmlediting.h"
59 #include "visible_units.h"
60 #include <wtf/StdLibExtras.h>
61 #include "ApplyStyleCommand.h"
62
63 using namespace std;
64
65 namespace WebCore {
66
67 using namespace HTMLNames;
68
69 static inline bool shouldSelfClose(const Node *node);
70
71 class AttributeChange {
72 public:
AttributeChange()73 AttributeChange()
74 : m_name(nullAtom, nullAtom, nullAtom)
75 {
76 }
77
AttributeChange(PassRefPtr<Element> element,const QualifiedName & name,const String & value)78 AttributeChange(PassRefPtr<Element> element, const QualifiedName& name, const String& value)
79 : m_element(element), m_name(name), m_value(value)
80 {
81 }
82
apply()83 void apply()
84 {
85 m_element->setAttribute(m_name, m_value);
86 }
87
88 private:
89 RefPtr<Element> m_element;
90 QualifiedName m_name;
91 String m_value;
92 };
93
appendAttributeValue(Vector<UChar> & result,const String & attr,bool escapeNBSP)94 static void appendAttributeValue(Vector<UChar>& result, const String& attr, bool escapeNBSP)
95 {
96 const UChar* uchars = attr.characters();
97 unsigned len = attr.length();
98 unsigned lastCopiedFrom = 0;
99
100 DEFINE_STATIC_LOCAL(const String, ampEntity, ("&"));
101 DEFINE_STATIC_LOCAL(const String, gtEntity, (">"));
102 DEFINE_STATIC_LOCAL(const String, ltEntity, ("<"));
103 DEFINE_STATIC_LOCAL(const String, quotEntity, ("""));
104 DEFINE_STATIC_LOCAL(const String, nbspEntity, (" "));
105
106 for (unsigned i = 0; i < len; ++i) {
107 UChar c = uchars[i];
108 switch (c) {
109 case '&':
110 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
111 append(result, ampEntity);
112 lastCopiedFrom = i + 1;
113 break;
114 case '<':
115 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
116 append(result, ltEntity);
117 lastCopiedFrom = i + 1;
118 break;
119 case '>':
120 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
121 append(result, gtEntity);
122 lastCopiedFrom = i + 1;
123 break;
124 case '"':
125 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
126 append(result, quotEntity);
127 lastCopiedFrom = i + 1;
128 break;
129 case noBreakSpace:
130 if (escapeNBSP) {
131 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
132 append(result, nbspEntity);
133 lastCopiedFrom = i + 1;
134 }
135 break;
136 }
137 }
138
139 result.append(uchars + lastCopiedFrom, len - lastCopiedFrom);
140 }
141
appendEscapedContent(Vector<UChar> & result,pair<const UChar *,size_t> range,bool escapeNBSP)142 static void appendEscapedContent(Vector<UChar>& result, pair<const UChar*, size_t> range, bool escapeNBSP)
143 {
144 const UChar* uchars = range.first;
145 unsigned len = range.second;
146 unsigned lastCopiedFrom = 0;
147
148 DEFINE_STATIC_LOCAL(const String, ampEntity, ("&"));
149 DEFINE_STATIC_LOCAL(const String, gtEntity, (">"));
150 DEFINE_STATIC_LOCAL(const String, ltEntity, ("<"));
151 DEFINE_STATIC_LOCAL(const String, nbspEntity, (" "));
152
153 for (unsigned i = 0; i < len; ++i) {
154 UChar c = uchars[i];
155 switch (c) {
156 case '&':
157 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
158 append(result, ampEntity);
159 lastCopiedFrom = i + 1;
160 break;
161 case '<':
162 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
163 append(result, ltEntity);
164 lastCopiedFrom = i + 1;
165 break;
166 case '>':
167 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
168 append(result, gtEntity);
169 lastCopiedFrom = i + 1;
170 break;
171 case noBreakSpace:
172 if (escapeNBSP) {
173 result.append(uchars + lastCopiedFrom, i - lastCopiedFrom);
174 append(result, nbspEntity);
175 lastCopiedFrom = i + 1;
176 }
177 break;
178 }
179 }
180
181 result.append(uchars + lastCopiedFrom, len - lastCopiedFrom);
182 }
183
escapeContentText(const String & in,bool escapeNBSP)184 static String escapeContentText(const String& in, bool escapeNBSP)
185 {
186 Vector<UChar> buffer;
187 appendEscapedContent(buffer, make_pair(in.characters(), in.length()), escapeNBSP);
188 return String::adopt(buffer);
189 }
190
appendQuotedURLAttributeValue(Vector<UChar> & result,const String & urlString)191 static void appendQuotedURLAttributeValue(Vector<UChar>& result, const String& urlString)
192 {
193 UChar quoteChar = '\"';
194 String strippedURLString = urlString.stripWhiteSpace();
195 if (protocolIsJavaScript(strippedURLString)) {
196 // minimal escaping for javascript urls
197 if (strippedURLString.contains('"')) {
198 if (strippedURLString.contains('\''))
199 strippedURLString.replace('\"', """);
200 else
201 quoteChar = '\'';
202 }
203 result.append(quoteChar);
204 append(result, strippedURLString);
205 result.append(quoteChar);
206 return;
207 }
208
209 // FIXME: This does not fully match other browsers. Firefox percent-escapes non-ASCII characters for innerHTML.
210 result.append(quoteChar);
211 appendAttributeValue(result, urlString, false);
212 result.append(quoteChar);
213 }
214
stringValueForRange(const Node * node,const Range * range)215 static String stringValueForRange(const Node* node, const Range* range)
216 {
217 if (!range)
218 return node->nodeValue();
219
220 String str = node->nodeValue();
221 ExceptionCode ec;
222 if (node == range->endContainer(ec))
223 str.truncate(range->endOffset(ec));
224 if (node == range->startContainer(ec))
225 str.remove(0, range->startOffset(ec));
226 return str;
227 }
228
ucharRange(const Node * node,const Range * range)229 static inline pair<const UChar*, size_t> ucharRange(const Node *node, const Range *range)
230 {
231 String str = node->nodeValue();
232 const UChar* characters = str.characters();
233 size_t length = str.length();
234
235 if (range) {
236 ExceptionCode ec;
237 if (node == range->endContainer(ec))
238 length = range->endOffset(ec);
239 if (node == range->startContainer(ec)) {
240 size_t start = range->startOffset(ec);
241 characters += start;
242 length -= start;
243 }
244 }
245
246 return make_pair(characters, length);
247 }
248
appendUCharRange(Vector<UChar> & result,const pair<const UChar *,size_t> range)249 static inline void appendUCharRange(Vector<UChar>& result, const pair<const UChar*, size_t> range)
250 {
251 result.append(range.first, range.second);
252 }
253
renderedText(const Node * node,const Range * range)254 static String renderedText(const Node* node, const Range* range)
255 {
256 if (!node->isTextNode())
257 return String();
258
259 ExceptionCode ec;
260 const Text* textNode = static_cast<const Text*>(node);
261 unsigned startOffset = 0;
262 unsigned endOffset = textNode->length();
263
264 if (range && node == range->startContainer(ec))
265 startOffset = range->startOffset(ec);
266 if (range && node == range->endContainer(ec))
267 endOffset = range->endOffset(ec);
268
269 Position start(const_cast<Node*>(node), startOffset);
270 Position end(const_cast<Node*>(node), endOffset);
271 return plainText(Range::create(node->document(), start, end).get());
272 }
273
styleFromMatchedRulesForElement(Element * element,bool authorOnly=true)274 static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesForElement(Element* element, bool authorOnly = true)
275 {
276 RefPtr<CSSMutableStyleDeclaration> style = CSSMutableStyleDeclaration::create();
277 RefPtr<CSSRuleList> matchedRules = element->document()->styleSelector()->styleRulesForElement(element, authorOnly);
278 if (matchedRules) {
279 for (unsigned i = 0; i < matchedRules->length(); i++) {
280 if (matchedRules->item(i)->type() == CSSRule::STYLE_RULE) {
281 RefPtr<CSSMutableStyleDeclaration> s = static_cast<CSSStyleRule*>(matchedRules->item(i))->style();
282 style->merge(s.get(), true);
283 }
284 }
285 }
286
287 return style.release();
288 }
289
removeEnclosingMailBlockquoteStyle(CSSMutableStyleDeclaration * style,Node * node)290 static void removeEnclosingMailBlockquoteStyle(CSSMutableStyleDeclaration* style, Node* node)
291 {
292 Node* blockquote = nearestMailBlockquote(node);
293 if (!blockquote || !blockquote->parentNode())
294 return;
295
296 removeStylesAddedByNode(style, blockquote);
297 }
298
removeDefaultStyles(CSSMutableStyleDeclaration * style,Document * document)299 static void removeDefaultStyles(CSSMutableStyleDeclaration* style, Document* document)
300 {
301 if (!document || !document->documentElement())
302 return;
303
304 prepareEditingStyleToApplyAt(style, Position(document->documentElement(), 0));
305 }
306
shouldAddNamespaceElem(const Element * elem)307 static bool shouldAddNamespaceElem(const Element* elem)
308 {
309 // Don't add namespace attribute if it is already defined for this elem.
310 const AtomicString& prefix = elem->prefix();
311 AtomicString attr = !prefix.isEmpty() ? "xmlns:" + prefix : "xmlns";
312 return !elem->hasAttribute(attr);
313 }
314
shouldAddNamespaceAttr(const Attribute * attr,HashMap<AtomicStringImpl *,AtomicStringImpl * > & namespaces)315 static bool shouldAddNamespaceAttr(const Attribute* attr, HashMap<AtomicStringImpl*, AtomicStringImpl*>& namespaces)
316 {
317 // Don't add namespace attributes twice
318 DEFINE_STATIC_LOCAL(const AtomicString, xmlnsURI, ("http://www.w3.org/2000/xmlns/"));
319 DEFINE_STATIC_LOCAL(const QualifiedName, xmlnsAttr, (nullAtom, "xmlns", xmlnsURI));
320 if (attr->name() == xmlnsAttr) {
321 namespaces.set(emptyAtom.impl(), attr->value().impl());
322 return false;
323 }
324
325 QualifiedName xmlnsPrefixAttr("xmlns", attr->localName(), xmlnsURI);
326 if (attr->name() == xmlnsPrefixAttr) {
327 namespaces.set(attr->localName().impl(), attr->value().impl());
328 return false;
329 }
330
331 return true;
332 }
333
appendNamespace(Vector<UChar> & result,const AtomicString & prefix,const AtomicString & ns,HashMap<AtomicStringImpl *,AtomicStringImpl * > & namespaces)334 static void appendNamespace(Vector<UChar>& result, const AtomicString& prefix, const AtomicString& ns, HashMap<AtomicStringImpl*, AtomicStringImpl*>& namespaces)
335 {
336 if (ns.isEmpty())
337 return;
338
339 // Use emptyAtoms's impl() for both null and empty strings since the HashMap can't handle 0 as a key
340 AtomicStringImpl* pre = prefix.isEmpty() ? emptyAtom.impl() : prefix.impl();
341 AtomicStringImpl* foundNS = namespaces.get(pre);
342 if (foundNS != ns.impl()) {
343 namespaces.set(pre, ns.impl());
344 DEFINE_STATIC_LOCAL(const String, xmlns, ("xmlns"));
345 result.append(' ');
346 append(result, xmlns);
347 if (!prefix.isEmpty()) {
348 result.append(':');
349 append(result, prefix);
350 }
351
352 result.append('=');
353 result.append('"');
354 appendAttributeValue(result, ns, false);
355 result.append('"');
356 }
357 }
358
appendDocumentType(Vector<UChar> & result,const DocumentType * n)359 static void appendDocumentType(Vector<UChar>& result, const DocumentType* n)
360 {
361 if (n->name().isEmpty())
362 return;
363
364 append(result, "<!DOCTYPE ");
365 append(result, n->name());
366 if (!n->publicId().isEmpty()) {
367 append(result, " PUBLIC \"");
368 append(result, n->publicId());
369 append(result, "\"");
370 if (!n->systemId().isEmpty()) {
371 append(result, " \"");
372 append(result, n->systemId());
373 append(result, "\"");
374 }
375 } else if (!n->systemId().isEmpty()) {
376 append(result, " SYSTEM \"");
377 append(result, n->systemId());
378 append(result, "\"");
379 }
380 if (!n->internalSubset().isEmpty()) {
381 append(result, " [");
382 append(result, n->internalSubset());
383 append(result, "]");
384 }
385 append(result, ">");
386 }
387
removeExteriorStyles(CSSMutableStyleDeclaration * style)388 static void removeExteriorStyles(CSSMutableStyleDeclaration* style)
389 {
390 style->removeProperty(CSSPropertyFloat);
391 }
392
393 enum RangeFullySelectsNode { DoesFullySelectNode, DoesNotFullySelectNode };
394
appendStartMarkup(Vector<UChar> & result,const Node * node,const Range * range,EAnnotateForInterchange annotate,bool convertBlocksToInlines=false,HashMap<AtomicStringImpl *,AtomicStringImpl * > * namespaces=0,RangeFullySelectsNode rangeFullySelectsNode=DoesFullySelectNode)395 static void appendStartMarkup(Vector<UChar>& result, const Node* node, const Range* range, EAnnotateForInterchange annotate, bool convertBlocksToInlines = false, HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0, RangeFullySelectsNode rangeFullySelectsNode = DoesFullySelectNode)
396 {
397 bool documentIsHTML = node->document()->isHTMLDocument();
398 switch (node->nodeType()) {
399 case Node::TEXT_NODE: {
400 if (Node* parent = node->parentNode()) {
401 if (parent->hasTagName(scriptTag)
402 || parent->hasTagName(styleTag)
403 || parent->hasTagName(textareaTag)
404 || parent->hasTagName(xmpTag)) {
405 appendUCharRange(result, ucharRange(node, range));
406 break;
407 }
408 }
409 if (!annotate) {
410 appendEscapedContent(result, ucharRange(node, range), documentIsHTML);
411 break;
412 }
413
414 bool useRenderedText = !enclosingNodeWithTag(Position(const_cast<Node*>(node), 0), selectTag);
415 String markup = escapeContentText(useRenderedText ? renderedText(node, range) : stringValueForRange(node, range), false);
416 markup = convertHTMLTextToInterchangeFormat(markup, static_cast<const Text*>(node));
417 append(result, markup);
418 break;
419 }
420 case Node::COMMENT_NODE:
421 // FIXME: Comment content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "-->".
422 append(result, "<!--");
423 append(result, static_cast<const Comment*>(node)->nodeValue());
424 append(result, "-->");
425 break;
426 case Node::DOCUMENT_NODE:
427 case Node::DOCUMENT_FRAGMENT_NODE:
428 break;
429 case Node::DOCUMENT_TYPE_NODE:
430 appendDocumentType(result, static_cast<const DocumentType*>(node));
431 break;
432 case Node::PROCESSING_INSTRUCTION_NODE: {
433 // FIXME: PI data is not escaped, but XMLSerializer (and possibly other callers) this should raise an exception if it includes "?>".
434 const ProcessingInstruction* n = static_cast<const ProcessingInstruction*>(node);
435 append(result, "<?");
436 append(result, n->target());
437 append(result, " ");
438 append(result, n->data());
439 append(result, "?>");
440 break;
441 }
442 case Node::ELEMENT_NODE: {
443 result.append('<');
444 const Element* el = static_cast<const Element*>(node);
445 bool convert = convertBlocksToInlines && isBlock(const_cast<Node*>(node));
446 append(result, el->nodeNamePreservingCase());
447 NamedNodeMap *attrs = el->attributes();
448 unsigned length = attrs->length();
449 if (!documentIsHTML && namespaces && shouldAddNamespaceElem(el))
450 appendNamespace(result, el->prefix(), el->namespaceURI(), *namespaces);
451
452 for (unsigned int i = 0; i < length; i++) {
453 Attribute *attr = attrs->attributeItem(i);
454 // We'll handle the style attribute separately, below.
455 if (attr->name() == styleAttr && el->isHTMLElement() && (annotate || convert))
456 continue;
457 result.append(' ');
458
459 if (documentIsHTML)
460 append(result, attr->name().localName());
461 else
462 append(result, attr->name().toString());
463
464 result.append('=');
465
466 if (el->isURLAttribute(attr))
467 appendQuotedURLAttributeValue(result, attr->value());
468 else {
469 result.append('\"');
470 appendAttributeValue(result, attr->value(), documentIsHTML);
471 result.append('\"');
472 }
473
474 if (!documentIsHTML && namespaces && shouldAddNamespaceAttr(attr, *namespaces))
475 appendNamespace(result, attr->prefix(), attr->namespaceURI(), *namespaces);
476 }
477
478 if (el->isHTMLElement() && (annotate || convert)) {
479 Element* element = const_cast<Element*>(el);
480 RefPtr<CSSMutableStyleDeclaration> style = static_cast<HTMLElement*>(element)->getInlineStyleDecl()->copy();
481 if (annotate) {
482 RefPtr<CSSMutableStyleDeclaration> styleFromMatchedRules = styleFromMatchedRulesForElement(const_cast<Element*>(el));
483 // Styles from the inline style declaration, held in the variable "style", take precedence
484 // over those from matched rules.
485 styleFromMatchedRules->merge(style.get());
486 style = styleFromMatchedRules;
487
488 RefPtr<CSSComputedStyleDeclaration> computedStyleForElement = computedStyle(element);
489 RefPtr<CSSMutableStyleDeclaration> fromComputedStyle = CSSMutableStyleDeclaration::create();
490
491 {
492 CSSMutableStyleDeclaration::const_iterator end = style->end();
493 for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) {
494 const CSSProperty& property = *it;
495 CSSValue* value = property.value();
496 // The property value, if it's a percentage, may not reflect the actual computed value.
497 // For example: style="height: 1%; overflow: visible;" in quirksmode
498 // FIXME: There are others like this, see <rdar://problem/5195123> Slashdot copy/paste fidelity problem
499 if (value->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE)
500 if (static_cast<CSSPrimitiveValue*>(value)->primitiveType() == CSSPrimitiveValue::CSS_PERCENTAGE)
501 if (RefPtr<CSSValue> computedPropertyValue = computedStyleForElement->getPropertyCSSValue(property.id()))
502 fromComputedStyle->addParsedProperty(CSSProperty(property.id(), computedPropertyValue));
503 }
504 }
505
506 style->merge(fromComputedStyle.get());
507 }
508 if (convert)
509 style->setProperty(CSSPropertyDisplay, CSSValueInline, true);
510 // If the node is not fully selected by the range, then we don't want to keep styles that affect its relationship to the nodes around it
511 // only the ones that affect it and the nodes within it.
512 if (rangeFullySelectsNode == DoesNotFullySelectNode)
513 removeExteriorStyles(style.get());
514 if (style->length() > 0) {
515 DEFINE_STATIC_LOCAL(const String, stylePrefix, (" style=\""));
516 append(result, stylePrefix);
517 appendAttributeValue(result, style->cssText(), documentIsHTML);
518 result.append('\"');
519 }
520 }
521
522 if (shouldSelfClose(el)) {
523 if (el->isHTMLElement())
524 result.append(' '); // XHTML 1.0 <-> HTML compatibility.
525 result.append('/');
526 }
527 result.append('>');
528 break;
529 }
530 case Node::CDATA_SECTION_NODE: {
531 // FIXME: CDATA content is not escaped, but XMLSerializer (and possibly other callers) should raise an exception if it includes "]]>".
532 const CDATASection* n = static_cast<const CDATASection*>(node);
533 append(result, "<![CDATA[");
534 append(result, n->data());
535 append(result, "]]>");
536 break;
537 }
538 case Node::ATTRIBUTE_NODE:
539 case Node::ENTITY_NODE:
540 case Node::ENTITY_REFERENCE_NODE:
541 case Node::NOTATION_NODE:
542 case Node::XPATH_NAMESPACE_NODE:
543 ASSERT_NOT_REACHED();
544 break;
545 }
546 }
547
getStartMarkup(const Node * node,const Range * range,EAnnotateForInterchange annotate,bool convertBlocksToInlines=false,HashMap<AtomicStringImpl *,AtomicStringImpl * > * namespaces=0,RangeFullySelectsNode rangeFullySelectsNode=DoesFullySelectNode)548 static String getStartMarkup(const Node* node, const Range* range, EAnnotateForInterchange annotate, bool convertBlocksToInlines = false, HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0, RangeFullySelectsNode rangeFullySelectsNode = DoesFullySelectNode)
549 {
550 Vector<UChar> result;
551 appendStartMarkup(result, node, range, annotate, convertBlocksToInlines, namespaces, rangeFullySelectsNode);
552 return String::adopt(result);
553 }
554
doesHTMLForbidEndTag(const Node * node)555 static inline bool doesHTMLForbidEndTag(const Node *node)
556 {
557 if (node->isHTMLElement()) {
558 const HTMLElement* htmlElt = static_cast<const HTMLElement*>(node);
559 return (htmlElt->endTagRequirement() == TagStatusForbidden);
560 }
561 return false;
562 }
563
564 // Rules of self-closure
565 // 1. No elements in HTML documents use the self-closing syntax.
566 // 2. Elements w/ children never self-close because they use a separate end tag.
567 // 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag.
568 // 4. Other elements self-close.
shouldSelfClose(const Node * node)569 static inline bool shouldSelfClose(const Node *node)
570 {
571 if (node->document()->isHTMLDocument())
572 return false;
573 if (node->hasChildNodes())
574 return false;
575 if (node->isHTMLElement() && !doesHTMLForbidEndTag(node))
576 return false;
577 return true;
578 }
579
appendEndMarkup(Vector<UChar> & result,const Node * node)580 static void appendEndMarkup(Vector<UChar>& result, const Node* node)
581 {
582 if (!node->isElementNode() || shouldSelfClose(node) || (!node->hasChildNodes() && doesHTMLForbidEndTag(node)))
583 return;
584
585 result.append('<');
586 result.append('/');
587 append(result, static_cast<const Element*>(node)->nodeNamePreservingCase());
588 result.append('>');
589 }
590
getEndMarkup(const Node * node)591 static String getEndMarkup(const Node *node)
592 {
593 Vector<UChar> result;
594 appendEndMarkup(result, node);
595 return String::adopt(result);
596 }
597
598 class MarkupAccumulator {
599 public:
MarkupAccumulator(Node * nodeToSkip,Vector<Node * > * nodes)600 MarkupAccumulator(Node* nodeToSkip, Vector<Node*>* nodes)
601 : m_nodeToSkip(nodeToSkip)
602 , m_nodes(nodes)
603 {
604 }
605
606 void appendMarkup(Node* startNode, EChildrenOnly, const HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0);
607
takeResult()608 String takeResult() { return String::adopt(m_result); }
609
610 private:
611 Vector<UChar> m_result;
612 Node* m_nodeToSkip;
613 Vector<Node*>* m_nodes;
614 };
615
616 // FIXME: Would be nice to do this in a non-recursive way.
appendMarkup(Node * startNode,EChildrenOnly childrenOnly,const HashMap<AtomicStringImpl *,AtomicStringImpl * > * namespaces)617 void MarkupAccumulator::appendMarkup(Node* startNode, EChildrenOnly childrenOnly, const HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces)
618 {
619 if (startNode == m_nodeToSkip)
620 return;
621
622 HashMap<AtomicStringImpl*, AtomicStringImpl*> namespaceHash;
623 if (namespaces)
624 namespaceHash = *namespaces;
625
626 // start tag
627 if (!childrenOnly) {
628 if (m_nodes)
629 m_nodes->append(startNode);
630 appendStartMarkup(m_result, startNode, 0, DoNotAnnotateForInterchange, false, &namespaceHash);
631 }
632
633 // children
634 if (!(startNode->document()->isHTMLDocument() && doesHTMLForbidEndTag(startNode))) {
635 for (Node* current = startNode->firstChild(); current; current = current->nextSibling())
636 appendMarkup(current, IncludeNode, &namespaceHash);
637 }
638
639 // end tag
640 if (!childrenOnly)
641 appendEndMarkup(m_result, startNode);
642 }
643
completeURLs(Node * node,const String & baseURL)644 static void completeURLs(Node* node, const String& baseURL)
645 {
646 Vector<AttributeChange> changes;
647
648 KURL parsedBaseURL(baseURL);
649
650 Node* end = node->traverseNextSibling();
651 for (Node* n = node; n != end; n = n->traverseNextNode()) {
652 if (n->isElementNode()) {
653 Element* e = static_cast<Element*>(n);
654 NamedNodeMap* attrs = e->attributes();
655 unsigned length = attrs->length();
656 for (unsigned i = 0; i < length; i++) {
657 Attribute* attr = attrs->attributeItem(i);
658 if (e->isURLAttribute(attr))
659 changes.append(AttributeChange(e, attr->name(), KURL(parsedBaseURL, attr->value()).string()));
660 }
661 }
662 }
663
664 size_t numChanges = changes.size();
665 for (size_t i = 0; i < numChanges; ++i)
666 changes[i].apply();
667 }
668
needInterchangeNewlineAfter(const VisiblePosition & v)669 static bool needInterchangeNewlineAfter(const VisiblePosition& v)
670 {
671 VisiblePosition next = v.next();
672 Node* upstreamNode = next.deepEquivalent().upstream().node();
673 Node* downstreamNode = v.deepEquivalent().downstream().node();
674 // Add an interchange newline if a paragraph break is selected and a br won't already be added to the markup to represent it.
675 return isEndOfParagraph(v) && isStartOfParagraph(next) && !(upstreamNode->hasTagName(brTag) && upstreamNode == downstreamNode);
676 }
677
styleFromMatchedRulesAndInlineDecl(const Node * node)678 static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesAndInlineDecl(const Node* node)
679 {
680 if (!node->isHTMLElement())
681 return 0;
682
683 // FIXME: Having to const_cast here is ugly, but it is quite a bit of work to untangle
684 // the non-const-ness of styleFromMatchedRulesForElement.
685 HTMLElement* element = const_cast<HTMLElement*>(static_cast<const HTMLElement*>(node));
686 RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesForElement(element);
687 RefPtr<CSSMutableStyleDeclaration> inlineStyleDecl = element->getInlineStyleDecl();
688 style->merge(inlineStyleDecl.get());
689 return style.release();
690 }
691
propertyMissingOrEqualToNone(CSSStyleDeclaration * style,int propertyID)692 static bool propertyMissingOrEqualToNone(CSSStyleDeclaration* style, int propertyID)
693 {
694 if (!style)
695 return false;
696 RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID);
697 if (!value)
698 return true;
699 if (!value->isPrimitiveValue())
700 return false;
701 return static_cast<CSSPrimitiveValue*>(value.get())->getIdent() == CSSValueNone;
702 }
703
isElementPresentational(const Node * node)704 static bool isElementPresentational(const Node* node)
705 {
706 if (node->hasTagName(uTag) || node->hasTagName(sTag) || node->hasTagName(strikeTag) ||
707 node->hasTagName(iTag) || node->hasTagName(emTag) || node->hasTagName(bTag) || node->hasTagName(strongTag))
708 return true;
709 RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesAndInlineDecl(node);
710 if (!style)
711 return false;
712 return !propertyMissingOrEqualToNone(style.get(), CSSPropertyTextDecoration);
713 }
714
joinMarkups(const Vector<String> & preMarkups,const Vector<String> & postMarkups)715 static String joinMarkups(const Vector<String>& preMarkups, const Vector<String>& postMarkups)
716 {
717 size_t length = 0;
718
719 size_t preCount = preMarkups.size();
720 for (size_t i = 0; i < preCount; ++i)
721 length += preMarkups[i].length();
722
723 size_t postCount = postMarkups.size();
724 for (size_t i = 0; i < postCount; ++i)
725 length += postMarkups[i].length();
726
727 Vector<UChar> result;
728 result.reserveInitialCapacity(length);
729
730 for (size_t i = preCount; i > 0; --i)
731 append(result, preMarkups[i - 1]);
732
733 for (size_t i = 0; i < postCount; ++i)
734 append(result, postMarkups[i]);
735
736 return String::adopt(result);
737 }
738
isSpecialAncestorBlock(Node * node)739 static bool isSpecialAncestorBlock(Node* node)
740 {
741 if (!node || !isBlock(node))
742 return false;
743
744 return node->hasTagName(listingTag) ||
745 node->hasTagName(olTag) ||
746 node->hasTagName(preTag) ||
747 node->hasTagName(tableTag) ||
748 node->hasTagName(ulTag) ||
749 node->hasTagName(xmpTag) ||
750 node->hasTagName(h1Tag) ||
751 node->hasTagName(h2Tag) ||
752 node->hasTagName(h3Tag) ||
753 node->hasTagName(h4Tag) ||
754 node->hasTagName(h5Tag);
755 }
756
shouldIncludeWrapperForFullySelectedRoot(Node * fullySelectedRoot,CSSMutableStyleDeclaration * style)757 static bool shouldIncludeWrapperForFullySelectedRoot(Node* fullySelectedRoot, CSSMutableStyleDeclaration* style)
758 {
759 if (fullySelectedRoot->isElementNode() && static_cast<Element*>(fullySelectedRoot)->hasAttribute(backgroundAttr))
760 return true;
761
762 return style->getPropertyCSSValue(CSSPropertyBackgroundImage) ||
763 style->getPropertyCSSValue(CSSPropertyBackgroundColor);
764 }
765
addStyleMarkup(Vector<String> & preMarkups,Vector<String> & postMarkups,CSSStyleDeclaration * style,Document * document,bool isBlock=false)766 static void addStyleMarkup(Vector<String>& preMarkups, Vector<String>& postMarkups, CSSStyleDeclaration* style, Document* document, bool isBlock = false)
767 {
768 // All text-decoration-related elements should have been treated as special ancestors
769 // If we ever hit this ASSERT, we should export StyleChange in ApplyStyleCommand and use it here
770 ASSERT(propertyMissingOrEqualToNone(style, CSSPropertyTextDecoration) && propertyMissingOrEqualToNone(style, CSSPropertyWebkitTextDecorationsInEffect));
771 DEFINE_STATIC_LOCAL(const String, divStyle, ("<div style=\""));
772 DEFINE_STATIC_LOCAL(const String, divClose, ("</div>"));
773 DEFINE_STATIC_LOCAL(const String, styleSpanOpen, ("<span class=\"" AppleStyleSpanClass "\" style=\""));
774 DEFINE_STATIC_LOCAL(const String, styleSpanClose, ("</span>"));
775 Vector<UChar> openTag;
776 append(openTag, isBlock ? divStyle : styleSpanOpen);
777 appendAttributeValue(openTag, style->cssText(), document->isHTMLDocument());
778 openTag.append('\"');
779 openTag.append('>');
780 preMarkups.append(String::adopt(openTag));
781
782 postMarkups.append(isBlock ? divClose : styleSpanClose);
783 }
784
785 // FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange?
786 // FIXME: At least, annotation and style info should probably not be included in range.markupString()
createMarkup(const Range * range,Vector<Node * > * nodes,EAnnotateForInterchange annotate,bool convertBlocksToInlines)787 String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterchange annotate, bool convertBlocksToInlines)
788 {
789 DEFINE_STATIC_LOCAL(const String, interchangeNewlineString, ("<br class=\"" AppleInterchangeNewline "\">"));
790
791 if (!range)
792 return "";
793
794 Document* document = range->ownerDocument();
795 if (!document)
796 return "";
797
798 // Disable the delete button so it's elements are not serialized into the markup,
799 // but make sure neither endpoint is inside the delete user interface.
800 Frame* frame = document->frame();
801 DeleteButtonController* deleteButton = frame ? frame->editor()->deleteButtonController() : 0;
802 RefPtr<Range> updatedRange = avoidIntersectionWithNode(range, deleteButton ? deleteButton->containerElement() : 0);
803 if (!updatedRange)
804 return "";
805
806 if (deleteButton)
807 deleteButton->disable();
808
809 ExceptionCode ec = 0;
810 bool collapsed = updatedRange->collapsed(ec);
811 ASSERT(ec == 0);
812 if (collapsed)
813 return "";
814 Node* commonAncestor = updatedRange->commonAncestorContainer(ec);
815 ASSERT(ec == 0);
816 if (!commonAncestor)
817 return "";
818
819 document->updateLayoutIgnorePendingStylesheets();
820
821 Vector<String> markups;
822 Vector<String> preMarkups;
823 Node* pastEnd = updatedRange->pastLastNode();
824 Node* lastClosed = 0;
825 Vector<Node*> ancestorsToClose;
826
827 Node* startNode = updatedRange->firstNode();
828 VisiblePosition visibleStart(updatedRange->startPosition(), VP_DEFAULT_AFFINITY);
829 VisiblePosition visibleEnd(updatedRange->endPosition(), VP_DEFAULT_AFFINITY);
830 if (annotate && needInterchangeNewlineAfter(visibleStart)) {
831 if (visibleStart == visibleEnd.previous()) {
832 if (deleteButton)
833 deleteButton->enable();
834 return interchangeNewlineString;
835 }
836
837 markups.append(interchangeNewlineString);
838 startNode = visibleStart.next().deepEquivalent().node();
839
840 if (pastEnd && Range::compareBoundaryPoints(startNode, 0, pastEnd, 0) >= 0) {
841 if (deleteButton)
842 deleteButton->enable();
843 return interchangeNewlineString;
844 }
845 }
846
847 Node* next;
848 for (Node* n = startNode; n != pastEnd; n = next) {
849 // According to <rdar://problem/5730668>, it is possible for n to blow
850 // past pastEnd and become null here. This shouldn't be possible.
851 // This null check will prevent crashes (but create too much markup)
852 // and the ASSERT will hopefully lead us to understanding the problem.
853 ASSERT(n);
854 if (!n)
855 break;
856
857 next = n->traverseNextNode();
858 bool skipDescendants = false;
859 bool addMarkupForNode = true;
860
861 if (!n->renderer() && !enclosingNodeWithTag(Position(n, 0), selectTag)) {
862 skipDescendants = true;
863 addMarkupForNode = false;
864 next = n->traverseNextSibling();
865 // Don't skip over pastEnd.
866 if (pastEnd && pastEnd->isDescendantOf(n))
867 next = pastEnd;
868 }
869
870 if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd)
871 // Don't write out empty block containers that aren't fully selected.
872 continue;
873
874 // Add the node to the markup.
875 if (addMarkupForNode) {
876 markups.append(getStartMarkup(n, updatedRange.get(), annotate));
877 if (nodes)
878 nodes->append(n);
879 }
880
881 if (n->firstChild() == 0 || skipDescendants) {
882 // Node has no children, or we are skipping it's descendants, add its close tag now.
883 if (addMarkupForNode) {
884 markups.append(getEndMarkup(n));
885 lastClosed = n;
886 }
887
888 // Check if the node is the last leaf of a tree.
889 if (!n->nextSibling() || next == pastEnd) {
890 if (!ancestorsToClose.isEmpty()) {
891 // Close up the ancestors.
892 do {
893 Node *ancestor = ancestorsToClose.last();
894 if (next != pastEnd && next->isDescendantOf(ancestor))
895 break;
896 // Not at the end of the range, close ancestors up to sibling of next node.
897 markups.append(getEndMarkup(ancestor));
898 lastClosed = ancestor;
899 ancestorsToClose.removeLast();
900 } while (!ancestorsToClose.isEmpty());
901 }
902
903 // Surround the currently accumulated markup with markup for ancestors we never opened as we leave the subtree(s) rooted at those ancestors.
904 Node* nextParent = next ? next->parentNode() : 0;
905 if (next != pastEnd && n != nextParent) {
906 Node* lastAncestorClosedOrSelf = n->isDescendantOf(lastClosed) ? lastClosed : n;
907 for (Node *parent = lastAncestorClosedOrSelf->parent(); parent != 0 && parent != nextParent; parent = parent->parentNode()) {
908 // All ancestors that aren't in the ancestorsToClose list should either be a) unrendered:
909 if (!parent->renderer())
910 continue;
911 // or b) ancestors that we never encountered during a pre-order traversal starting at startNode:
912 ASSERT(startNode->isDescendantOf(parent));
913 preMarkups.append(getStartMarkup(parent, updatedRange.get(), annotate));
914 markups.append(getEndMarkup(parent));
915 if (nodes)
916 nodes->append(parent);
917 lastClosed = parent;
918 }
919 }
920 }
921 } else if (addMarkupForNode && !skipDescendants)
922 // We added markup for this node, and we're descending into it. Set it to close eventually.
923 ancestorsToClose.append(n);
924 }
925
926 // Include ancestors that aren't completely inside the range but are required to retain
927 // the structure and appearance of the copied markup.
928 Node* specialCommonAncestor = 0;
929 Node* commonAncestorBlock = commonAncestor ? enclosingBlock(commonAncestor) : 0;
930 if (annotate && commonAncestorBlock) {
931 if (commonAncestorBlock->hasTagName(tbodyTag) || commonAncestorBlock->hasTagName(trTag)) {
932 Node* table = commonAncestorBlock->parentNode();
933 while (table && !table->hasTagName(tableTag))
934 table = table->parentNode();
935 if (table)
936 specialCommonAncestor = table;
937 } else if (isSpecialAncestorBlock(commonAncestorBlock))
938 specialCommonAncestor = commonAncestorBlock;
939 }
940
941 // Retain the Mail quote level by including all ancestor mail block quotes.
942 if (lastClosed && annotate) {
943 for (Node *ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode())
944 if (isMailBlockquote(ancestor))
945 specialCommonAncestor = ancestor;
946 }
947
948 Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor;
949 if (checkAncestor->renderer()) {
950 Node* newSpecialCommonAncestor = highestEnclosingNodeOfType(Position(checkAncestor, 0), &isElementPresentational);
951 if (newSpecialCommonAncestor)
952 specialCommonAncestor = newSpecialCommonAncestor;
953 }
954
955 // If a single tab is selected, commonAncestor will be a text node inside a tab span.
956 // If two or more tabs are selected, commonAncestor will be the tab span.
957 // In either case, if there is a specialCommonAncestor already, it will necessarily be above
958 // any tab span that needs to be included.
959 if (!specialCommonAncestor && isTabSpanTextNode(commonAncestor))
960 specialCommonAncestor = commonAncestor->parentNode();
961 if (!specialCommonAncestor && isTabSpanNode(commonAncestor))
962 specialCommonAncestor = commonAncestor;
963
964 if (Node *enclosingAnchor = enclosingNodeWithTag(Position(specialCommonAncestor ? specialCommonAncestor : commonAncestor, 0), aTag))
965 specialCommonAncestor = enclosingAnchor;
966
967 Node* body = enclosingNodeWithTag(Position(commonAncestor, 0), bodyTag);
968 // FIXME: Do this for all fully selected blocks, not just the body.
969 Node* fullySelectedRoot = body && *VisibleSelection::selectionFromContentsOfNode(body).toNormalizedRange() == *updatedRange ? body : 0;
970 RefPtr<CSSMutableStyleDeclaration> fullySelectedRootStyle = fullySelectedRoot ? styleFromMatchedRulesAndInlineDecl(fullySelectedRoot) : 0;
971 if (annotate && fullySelectedRoot) {
972 if (shouldIncludeWrapperForFullySelectedRoot(fullySelectedRoot, fullySelectedRootStyle.get()))
973 specialCommonAncestor = fullySelectedRoot;
974 }
975
976 if (specialCommonAncestor && lastClosed) {
977 // Also include all of the ancestors of lastClosed up to this special ancestor.
978 for (Node* ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
979 if (ancestor == fullySelectedRoot && !convertBlocksToInlines) {
980
981 // Bring the background attribute over, but not as an attribute because a background attribute on a div
982 // appears to have no effect.
983 if (!fullySelectedRootStyle->getPropertyCSSValue(CSSPropertyBackgroundImage) && static_cast<Element*>(fullySelectedRoot)->hasAttribute(backgroundAttr))
984 fullySelectedRootStyle->setProperty(CSSPropertyBackgroundImage, "url('" + static_cast<Element*>(fullySelectedRoot)->getAttribute(backgroundAttr) + "')");
985
986 if (fullySelectedRootStyle->length())
987 addStyleMarkup(preMarkups, markups, fullySelectedRootStyle.get(), document, true);
988 } else {
989 // Since this node and all the other ancestors are not in the selection we want to set RangeFullySelectsNode to DoesNotFullySelectNode
990 // so that styles that affect the exterior of the node are not included.
991 preMarkups.append(getStartMarkup(ancestor, updatedRange.get(), annotate, convertBlocksToInlines, 0, DoesNotFullySelectNode));
992 markups.append(getEndMarkup(ancestor));
993 }
994 if (nodes)
995 nodes->append(ancestor);
996
997 lastClosed = ancestor;
998
999 if (ancestor == specialCommonAncestor)
1000 break;
1001 }
1002 }
1003
1004 // Add a wrapper span with the styles that all of the nodes in the markup inherit.
1005 Node* parentOfLastClosed = lastClosed ? lastClosed->parentNode() : 0;
1006 if (parentOfLastClosed && parentOfLastClosed->renderer()) {
1007 RefPtr<CSSMutableStyleDeclaration> style = editingStyleAtPosition(Position(parentOfLastClosed, 0));
1008
1009 // Styles that Mail blockquotes contribute should only be placed on the Mail blockquote, to help
1010 // us differentiate those styles from ones that the user has applied. This helps us
1011 // get the color of content pasted into blockquotes right.
1012 removeEnclosingMailBlockquoteStyle(style.get(), parentOfLastClosed);
1013
1014 // Document default styles will be added on another wrapper span.
1015 removeDefaultStyles(style.get(), document);
1016
1017 // Since we are converting blocks to inlines, remove any inherited block properties that are in the style.
1018 // This cuts out meaningless properties and prevents properties from magically affecting blocks later
1019 // if the style is cloned for a new block element during a future editing operation.
1020 if (convertBlocksToInlines)
1021 style->removeBlockProperties();
1022
1023 if (style->length() > 0)
1024 addStyleMarkup(preMarkups, markups, style.get(), document);
1025 }
1026
1027 if (lastClosed && lastClosed != document->documentElement()) {
1028 // Add a style span with the document's default styles. We add these in a separate
1029 // span so that at paste time we can differentiate between document defaults and user
1030 // applied styles.
1031 RefPtr<CSSMutableStyleDeclaration> defaultStyle = editingStyleAtPosition(Position(document->documentElement(), 0));
1032
1033 if (defaultStyle->length() > 0)
1034 addStyleMarkup(preMarkups, markups, defaultStyle.get(), document);
1035 }
1036
1037 // FIXME: The interchange newline should be placed in the block that it's in, not after all of the content, unconditionally.
1038 if (annotate && needInterchangeNewlineAfter(visibleEnd.previous()))
1039 markups.append(interchangeNewlineString);
1040
1041 if (deleteButton)
1042 deleteButton->enable();
1043
1044 return joinMarkups(preMarkups, markups);
1045 }
1046
createFragmentFromMarkup(Document * document,const String & markup,const String & baseURL)1047 PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const String& markup, const String& baseURL)
1048 {
1049 ASSERT(document->documentElement()->isHTMLElement());
1050 // FIXME: What if the document element is not an HTML element?
1051 HTMLElement *element = static_cast<HTMLElement*>(document->documentElement());
1052
1053 RefPtr<DocumentFragment> fragment = element->createContextualFragment(markup);
1054
1055 if (fragment && !baseURL.isEmpty() && baseURL != blankURL() && baseURL != document->baseURL())
1056 completeURLs(fragment.get(), baseURL);
1057
1058 return fragment.release();
1059 }
1060
createMarkup(const Node * node,EChildrenOnly childrenOnly,Vector<Node * > * nodes)1061 String createMarkup(const Node* node, EChildrenOnly childrenOnly, Vector<Node*>* nodes)
1062 {
1063 if (!node)
1064 return "";
1065
1066 HTMLElement* deleteButtonContainerElement = 0;
1067 if (Frame* frame = node->document()->frame()) {
1068 deleteButtonContainerElement = frame->editor()->deleteButtonController()->containerElement();
1069 if (node->isDescendantOf(deleteButtonContainerElement))
1070 return "";
1071 }
1072
1073 MarkupAccumulator accumulator(deleteButtonContainerElement, nodes);
1074 accumulator.appendMarkup(const_cast<Node*>(node), childrenOnly);
1075 return accumulator.takeResult();
1076 }
1077
fillContainerFromString(ContainerNode * paragraph,const String & string)1078 static void fillContainerFromString(ContainerNode* paragraph, const String& string)
1079 {
1080 Document* document = paragraph->document();
1081
1082 ExceptionCode ec = 0;
1083 if (string.isEmpty()) {
1084 paragraph->appendChild(createBlockPlaceholderElement(document), ec);
1085 ASSERT(ec == 0);
1086 return;
1087 }
1088
1089 ASSERT(string.find('\n') == -1);
1090
1091 Vector<String> tabList;
1092 string.split('\t', true, tabList);
1093 String tabText = "";
1094 bool first = true;
1095 size_t numEntries = tabList.size();
1096 for (size_t i = 0; i < numEntries; ++i) {
1097 const String& s = tabList[i];
1098
1099 // append the non-tab textual part
1100 if (!s.isEmpty()) {
1101 if (!tabText.isEmpty()) {
1102 paragraph->appendChild(createTabSpanElement(document, tabText), ec);
1103 ASSERT(ec == 0);
1104 tabText = "";
1105 }
1106 RefPtr<Node> textNode = document->createTextNode(stringWithRebalancedWhitespace(s, first, i + 1 == numEntries));
1107 paragraph->appendChild(textNode.release(), ec);
1108 ASSERT(ec == 0);
1109 }
1110
1111 // there is a tab after every entry, except the last entry
1112 // (if the last character is a tab, the list gets an extra empty entry)
1113 if (i + 1 != numEntries)
1114 tabText.append('\t');
1115 else if (!tabText.isEmpty()) {
1116 paragraph->appendChild(createTabSpanElement(document, tabText), ec);
1117 ASSERT(ec == 0);
1118 }
1119
1120 first = false;
1121 }
1122 }
1123
createFragmentFromText(Range * context,const String & text)1124 PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String& text)
1125 {
1126 if (!context)
1127 return 0;
1128
1129 Node* styleNode = context->firstNode();
1130 if (!styleNode) {
1131 styleNode = context->startPosition().node();
1132 if (!styleNode)
1133 return 0;
1134 }
1135
1136 Document* document = styleNode->document();
1137 RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
1138
1139 if (text.isEmpty())
1140 return fragment.release();
1141
1142 String string = text;
1143 string.replace("\r\n", "\n");
1144 string.replace('\r', '\n');
1145
1146 ExceptionCode ec = 0;
1147 RenderObject* renderer = styleNode->renderer();
1148 if (renderer && renderer->style()->preserveNewline()) {
1149 fragment->appendChild(document->createTextNode(string), ec);
1150 ASSERT(ec == 0);
1151 if (string.endsWith("\n")) {
1152 RefPtr<Element> element = createBreakElement(document);
1153 element->setAttribute(classAttr, AppleInterchangeNewline);
1154 fragment->appendChild(element.release(), ec);
1155 ASSERT(ec == 0);
1156 }
1157 return fragment.release();
1158 }
1159
1160 // A string with no newlines gets added inline, rather than being put into a paragraph.
1161 if (string.find('\n') == -1) {
1162 fillContainerFromString(fragment.get(), string);
1163 return fragment.release();
1164 }
1165
1166 // Break string into paragraphs. Extra line breaks turn into empty paragraphs.
1167 Node* blockNode = enclosingBlock(context->firstNode());
1168 Element* block = static_cast<Element*>(blockNode);
1169 bool useClonesOfEnclosingBlock = blockNode
1170 && blockNode->isElementNode()
1171 && !block->hasTagName(bodyTag)
1172 && !block->hasTagName(htmlTag)
1173 && block != editableRootForPosition(context->startPosition());
1174
1175 Vector<String> list;
1176 string.split('\n', true, list); // true gets us empty strings in the list
1177 size_t numLines = list.size();
1178 for (size_t i = 0; i < numLines; ++i) {
1179 const String& s = list[i];
1180
1181 RefPtr<Element> element;
1182 if (s.isEmpty() && i + 1 == numLines) {
1183 // For last line, use the "magic BR" rather than a P.
1184 element = createBreakElement(document);
1185 element->setAttribute(classAttr, AppleInterchangeNewline);
1186 } else {
1187 if (useClonesOfEnclosingBlock)
1188 element = block->cloneElementWithoutChildren();
1189 else
1190 element = createDefaultParagraphElement(document);
1191 fillContainerFromString(element.get(), s);
1192 }
1193 fragment->appendChild(element.release(), ec);
1194 ASSERT(ec == 0);
1195 }
1196 return fragment.release();
1197 }
1198
createFragmentFromNodes(Document * document,const Vector<Node * > & nodes)1199 PassRefPtr<DocumentFragment> createFragmentFromNodes(Document *document, const Vector<Node*>& nodes)
1200 {
1201 if (!document)
1202 return 0;
1203
1204 // disable the delete button so it's elements are not serialized into the markup
1205 if (document->frame())
1206 document->frame()->editor()->deleteButtonController()->disable();
1207
1208 RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
1209
1210 ExceptionCode ec = 0;
1211 size_t size = nodes.size();
1212 for (size_t i = 0; i < size; ++i) {
1213 RefPtr<Element> element = createDefaultParagraphElement(document);
1214 element->appendChild(nodes[i], ec);
1215 ASSERT(ec == 0);
1216 fragment->appendChild(element.release(), ec);
1217 ASSERT(ec == 0);
1218 }
1219
1220 if (document->frame())
1221 document->frame()->editor()->deleteButtonController()->enable();
1222
1223 return fragment.release();
1224 }
1225
createFullMarkup(const Node * node)1226 String createFullMarkup(const Node* node)
1227 {
1228 if (!node)
1229 return String();
1230
1231 Document* document = node->document();
1232 if (!document)
1233 return String();
1234
1235 Frame* frame = document->frame();
1236 if (!frame)
1237 return String();
1238
1239 // FIXME: This is never "for interchange". Is that right?
1240 String markupString = createMarkup(node, IncludeNode, 0);
1241 Node::NodeType nodeType = node->nodeType();
1242 if (nodeType != Node::DOCUMENT_NODE && nodeType != Node::DOCUMENT_TYPE_NODE)
1243 markupString = frame->documentTypeString() + markupString;
1244
1245 return markupString;
1246 }
1247
createFullMarkup(const Range * range)1248 String createFullMarkup(const Range* range)
1249 {
1250 if (!range)
1251 return String();
1252
1253 Node* node = range->startContainer();
1254 if (!node)
1255 return String();
1256
1257 Document* document = node->document();
1258 if (!document)
1259 return String();
1260
1261 Frame* frame = document->frame();
1262 if (!frame)
1263 return String();
1264
1265 // FIXME: This is always "for interchange". Is that right? See the previous method.
1266 return frame->documentTypeString() + createMarkup(range, 0, AnnotateForInterchange);
1267 }
1268
1269 }
1270