LayoutTests:
[WebKit-https.git] / WebCore / editing / markup.cpp
1 /*
2  * Copyright (C) 2004, 2005, 2006 Apple Computer, 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 "CSSComputedStyleDeclaration.h"
30 #include "Comment.h"
31 #include "Document.h"
32 #include "DocumentFragment.h"
33 #include "DocumentType.h"
34 #include "HTMLElement.h"
35 #include "HTMLNames.h"
36 #include "InlineTextBox.h"
37 #include "KURL.h"
38 #include "Logging.h"
39 #include "ProcessingInstruction.h"
40 #include "Range.h"
41 #include "htmlediting.h"
42 #include "visible_units.h"
43 #include "TextIterator.h"
44
45 using namespace std;
46
47 namespace WebCore {
48
49 using namespace HTMLNames;
50
51 static inline bool shouldSelfClose(const Node *node);
52
53 static DeprecatedString escapeTextForMarkup(const DeprecatedString &in)
54 {
55     DeprecatedString s = "";
56
57     unsigned len = in.length();
58     for (unsigned i = 0; i < len; ++i) {
59         switch (in[i].unicode()) {
60             case '&':
61                 s += "&amp;";
62                 break;
63             case '<':
64                 s += "&lt;";
65                 break;
66             case '>':
67                 s += "&gt;";
68                 break;
69             default:
70                 s += in[i];
71         }
72     }
73
74     return s;
75 }
76
77 static String stringValueForRange(const Node *node, const Range *range)
78 {
79     String str = node->nodeValue().copy();
80     if (range) {
81         ExceptionCode ec;
82         if (node == range->endContainer(ec))
83             str.truncate(range->endOffset(ec));
84         if (node == range->startContainer(ec))
85             str.remove(0, range->startOffset(ec));
86     }
87     return str;
88 }
89
90 static DeprecatedString renderedText(const Node *node, const Range *range)
91 {
92     if (!node->isTextNode())
93         return DeprecatedString();
94
95     ExceptionCode ec;
96     const Text* textNode = static_cast<const Text*>(node);
97     unsigned startOffset = 0;
98     unsigned endOffset = textNode->length();
99
100     if (range && node == range->startContainer(ec))
101         startOffset = range->startOffset(ec);
102     if (range && node == range->endContainer(ec))
103         endOffset = range->endOffset(ec);
104     
105     Position start(const_cast<Node*>(node), startOffset);
106     Position end(const_cast<Node*>(node), endOffset);
107     Range r(node->document(), start, end);
108     return plainText(&r);
109 }
110
111 static DeprecatedString startMarkup(const Node *node, const Range *range, EAnnotateForInterchange annotate, CSSMutableStyleDeclaration *defaultStyle)
112 {
113     bool documentIsHTML = node->document()->isHTMLDocument();
114     switch (node->nodeType()) {
115         case Node::TEXT_NODE: {
116             if (Node* parent = node->parentNode()) {
117                 if (parent->hasTagName(listingTag)
118                         || parent->hasTagName(preTag)
119                         || parent->hasTagName(scriptTag)
120                         || parent->hasTagName(styleTag)
121                         || parent->hasTagName(textareaTag)
122                         || parent->hasTagName(xmpTag))
123                     return stringValueForRange(node, range).deprecatedString();
124             }
125             DeprecatedString markup = annotate ? escapeTextForMarkup(renderedText(node, range)) : escapeTextForMarkup(stringValueForRange(node, range).deprecatedString());            
126             if (defaultStyle) {
127                 Node *element = node->parentNode();
128                 if (element) {
129                     RefPtr<CSSComputedStyleDeclaration> computedStyle = Position(element, 0).computedStyle();
130                     RefPtr<CSSMutableStyleDeclaration> style = computedStyle->copyInheritableProperties();
131                     defaultStyle->diff(style.get());
132                     if (style->length() > 0) {
133                         // FIXME: Handle case where style->cssText() has illegal characters in it, like "
134                         DeprecatedString openTag = DeprecatedString("<span class=\"") + AppleStyleSpanClass + "\" style=\"" + style->cssText().deprecatedString() + "\">";
135                         markup = openTag + markup + "</span>";
136                     }
137                 }            
138             }
139             return annotate ? convertHTMLTextToInterchangeFormat(markup) : markup;
140         }
141         case Node::COMMENT_NODE:
142             return static_cast<const Comment*>(node)->toString().deprecatedString();
143         case Node::DOCUMENT_NODE: {
144             // Documents do not normally contain a docType as a child node, force it to print here instead.
145             const DocumentType* docType = static_cast<const Document*>(node)->doctype();
146             if (docType)
147                 return docType->toString().deprecatedString();
148             return "";
149         }
150         case Node::DOCUMENT_FRAGMENT_NODE:
151             return "";
152         case Node::DOCUMENT_TYPE_NODE:
153             return static_cast<const DocumentType*>(node)->toString().deprecatedString();
154         case Node::PROCESSING_INSTRUCTION_NODE:
155             return static_cast<const ProcessingInstruction*>(node)->toString().deprecatedString();
156         case Node::ELEMENT_NODE: {
157             DeprecatedString markup = DeprecatedChar('<');
158             const Element* el = static_cast<const Element*>(node);
159             markup += el->nodeNamePreservingCase().deprecatedString();
160             String additionalStyle;
161             if (defaultStyle && el->isHTMLElement()) {
162                 RefPtr<CSSComputedStyleDeclaration> computedStyle = Position(const_cast<Element*>(el), 0).computedStyle();
163                 RefPtr<CSSMutableStyleDeclaration> style = computedStyle->copyInheritableProperties();
164                 defaultStyle->diff(style.get());
165                 if (style->length() > 0) {
166                     CSSMutableStyleDeclaration *inlineStyleDecl = static_cast<const HTMLElement*>(el)->inlineStyleDecl();
167                     if (inlineStyleDecl)
168                         inlineStyleDecl->diff(style.get());
169                     additionalStyle = style->cssText();
170                 }
171             }
172             NamedAttrMap *attrs = el->attributes();
173             unsigned length = attrs->length();
174             if (length == 0 && additionalStyle.length() > 0) {
175                 // FIXME: Handle case where additionalStyle has illegal characters in it, like "
176                 markup += " " +  styleAttr.localName().deprecatedString() + "=\"" + additionalStyle.deprecatedString() + "\"";
177             }
178             else {
179                 for (unsigned int i=0; i<length; i++) {
180                     Attribute *attr = attrs->attributeItem(i);
181                     String value = attr->value();
182                     if (attr->name() == styleAttr && additionalStyle.length() > 0)
183                         value += "; " + additionalStyle;
184                     // FIXME: Handle case where value has illegal characters in it, like "
185                     if (documentIsHTML)
186                         markup += " " + attr->name().localName().deprecatedString();
187                     else
188                         markup += " " + attr->name().toString().deprecatedString();
189                     markup += "=\"" + escapeTextForMarkup(value.deprecatedString()) + "\"";
190                 }
191             }
192             
193             if (shouldSelfClose(el)) {
194                 if (el->isHTMLElement())
195                     markup += " "; // XHTML 1.0 <-> HTML compatibility.
196                 markup += "/>";
197             } else
198                 markup += ">";
199             
200             return markup;
201         }
202         case Node::ATTRIBUTE_NODE:
203         case Node::CDATA_SECTION_NODE:
204         case Node::ENTITY_NODE:
205         case Node::ENTITY_REFERENCE_NODE:
206         case Node::NOTATION_NODE:
207         case Node::XPATH_NAMESPACE_NODE:
208             break;
209     }
210     return "";
211 }
212
213 static inline bool doesHTMLForbidEndTag(const Node *node)
214 {
215     if (node->isHTMLElement()) {
216         const HTMLElement* htmlElt = static_cast<const HTMLElement*>(node);
217         return (htmlElt->endTagRequirement() == TagStatusForbidden);
218     }
219     return false;
220 }
221
222 // Rules of self-closure
223 // 1. No elements in HTML documents use the self-closing syntax.
224 // 2. Elements w/ children never self-close because they use a separate end tag.
225 // 3. HTML elements which do not have a "forbidden" end tag will close with a separate end tag.
226 // 4. Other elements self-close.
227 static inline bool shouldSelfClose(const Node *node)
228 {
229     if (node->document()->isHTMLDocument())
230         return false;
231     if (node->hasChildNodes())
232         return false;
233     if (node->isHTMLElement() && !doesHTMLForbidEndTag(node))
234         return false;
235     return true;
236 }
237
238 static DeprecatedString endMarkup(const Node *node)
239 {
240     if (node->isElementNode() && !shouldSelfClose(node) && !doesHTMLForbidEndTag(node))
241         return "</" + static_cast<const Element*>(node)->nodeNamePreservingCase().deprecatedString() + ">";
242     return "";
243 }
244
245 static DeprecatedString markup(const Node *startNode, bool onlyIncludeChildren, bool includeSiblings, DeprecatedPtrList<Node> *nodes)
246 {
247     // Doesn't make sense to only include children and include siblings.
248     ASSERT(!(onlyIncludeChildren && includeSiblings));
249     DeprecatedString me = "";
250     for (const Node *current = startNode; current != NULL; current = includeSiblings ? current->nextSibling() : NULL) {
251         if (!onlyIncludeChildren) {
252             if (nodes)
253                 nodes->append(current);
254             me += startMarkup(current, 0, DoNotAnnotateForInterchange, 0);
255         }
256         // print children
257         if (Node *n = current->firstChild())
258             if (!(n->document()->isHTMLDocument() && doesHTMLForbidEndTag(current)))
259                 me += markup(n, false, true, nodes);
260         
261         // Print my ending tag
262         if (!onlyIncludeChildren)
263             me += endMarkup(current);
264     }
265     return me;
266 }
267
268 static void completeURLs(Node *node, const DeprecatedString &baseURL)
269 {
270     Node *end = node->traverseNextSibling();
271     for (Node *n = node; n != end; n = n->traverseNextNode()) {
272         if (n->isElementNode()) {
273             Element *e = static_cast<Element*>(n);
274             NamedAttrMap *attrs = e->attributes();
275             unsigned length = attrs->length();
276             for (unsigned i = 0; i < length; i++) {
277                 Attribute *attr = attrs->attributeItem(i);
278                 if (e->isURLAttribute(attr))
279                     e->setAttribute(attr->name(), KURL(baseURL, attr->value().deprecatedString()).url());
280             }
281         }
282     }
283 }
284
285 // FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange? 
286 // FIXME: At least, annotation and style info should probably not be included in range.markupString()
287 DeprecatedString createMarkup(const Range *range, DeprecatedPtrList<Node> *nodes, EAnnotateForInterchange annotate)
288 {
289     if (!range || range->isDetached())
290         return DeprecatedString();
291
292     static const DeprecatedString interchangeNewlineString = DeprecatedString("<br class=\"") + AppleInterchangeNewline + "\">";
293
294     ExceptionCode ec = 0;
295     if (range->collapsed(ec))
296         return "";
297     ASSERT(ec == 0);
298     Node *commonAncestor = range->commonAncestorContainer(ec);
299     ASSERT(ec == 0);
300
301     Document *doc = commonAncestor->document();
302     doc->updateLayoutIgnorePendingStylesheets();
303
304     Node *commonAncestorBlock = 0;
305     if (commonAncestor)
306         commonAncestorBlock = commonAncestor->enclosingBlockFlowElement();
307     if (!commonAncestorBlock)
308         return "";
309
310     DeprecatedStringList markups;
311     Node *pastEnd = range->pastEndNode();
312     Node *lastClosed = 0;
313     DeprecatedPtrList<Node> ancestorsToClose;
314
315     // calculate the "default style" for this markup
316     Position pos(doc->documentElement(), 0);
317     RefPtr<CSSComputedStyleDeclaration> computedStyle = pos.computedStyle();
318     RefPtr<CSSMutableStyleDeclaration> defaultStyle = computedStyle->copyInheritableProperties();
319     
320     // FIXME: Shouldn't we do the interchangeNewLine and skip the node iff (annotate == AnnotateForInterchange)?
321     Node *startNode = range->startNode();
322     VisiblePosition visibleStart(range->startPosition(), VP_DEFAULT_AFFINITY);
323     VisiblePosition visibleEnd(range->endPosition(), VP_DEFAULT_AFFINITY);
324     if (!inSameBlock(visibleStart, visibleStart.next())) {
325         if (visibleStart == visibleEnd.previous())
326             return interchangeNewlineString;
327             
328         // FIXME: This adds a newline, but a later add of the ancestor could go before that newline (e.g. when startNode is tbody,
329         // we will later add a table, but the interchangeNewlineString will appear between the table and the tbody! for now, that
330         // does not happen with tables because of code below that moves startNode to the table when at tbody)
331         markups.append(interchangeNewlineString);
332         startNode = startNode->traverseNextNode();
333     }
334
335     // no use having a tbody without its table
336     // NOTE: need sibling check because startNode might be anonymous text (like newlines)
337     // FIXME: This would not be needed if ancestor adding applied to more than just the lastClosed
338     for (Node *n = startNode; n; n = n->nextSibling()) {
339         if (n->hasTagName(tbodyTag)) {
340             startNode = startNode->parent();
341             break;
342         }
343     }
344
345     // Iterate through the nodes of the range.
346     Node *next;
347     for (Node *n = startNode; n != pastEnd; n = next) {
348         next = n->traverseNextNode();
349
350         if (n->isBlockFlow() && next == pastEnd)
351             // Don't write out an empty block.
352             continue;
353         
354         // Add the node to the markup.
355         // FIXME: Add markup for nodes without renderers to fix <rdar://problem/4062865>. Also see the three checks below.
356         if (n->renderer()) {
357             markups.append(startMarkup(n, range, annotate, defaultStyle.get()));
358             if (nodes)
359                 nodes->append(n);
360         }
361         
362         if (n->firstChild() == 0) {
363             // Node has no children, add its close tag now.
364             if (n->renderer()) {
365                 markups.append(endMarkup(n));
366                 lastClosed = n;
367             }
368             
369             // Check if the node is the last leaf of a tree.
370             if (n->nextSibling() == 0 || next == pastEnd) {
371                 if (!ancestorsToClose.isEmpty()) {
372                     // Close up the ancestors.
373                     while (Node *ancestor = ancestorsToClose.last()) {
374                         if (next != pastEnd && next->isAncestor(ancestor))
375                             break;
376                         // Not at the end of the range, close ancestors up to sibling of next node.
377                         markups.append(endMarkup(ancestor));
378                         lastClosed = ancestor;
379                         ancestorsToClose.removeLast();
380                     }
381                 } else {
382                     if (next != pastEnd) {
383                         Node *nextParent = next->parentNode();
384                         if (n != nextParent) {
385                             for (Node *parent = n->parent(); parent != 0 && parent != nextParent; parent = parent->parentNode()) {
386                                 // All ancestors not in the ancestorsToClose list should either be a) unrendered:
387                                 if (!parent->renderer())
388                                     continue;
389                                 // or b) ancestors that we never encountered during a pre-order traversal starting at startNode:
390                                 ASSERT(startNode->isAncestor(parent));
391                                 markups.prepend(startMarkup(parent, range, annotate, defaultStyle.get()));
392                                 markups.append(endMarkup(parent));
393                                 if (nodes)
394                                     nodes->append(parent);
395                                 lastClosed = parent;
396                             }
397                         }
398                     }
399                 }
400             }
401         } else if (n->renderer())
402             // Node is an ancestor, set it to close eventually.
403             ancestorsToClose.append(n);
404     }
405     
406     Node *rangeStartNode = range->startNode();
407     int rangeStartOffset = range->startOffset(ec);
408     ASSERT(ec == 0);
409     
410     // Add ancestors up to the common ancestor block so inline ancestors such as FONT and B are part of the markup.
411     if (lastClosed) {
412         for (Node *ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
413             if (Range::compareBoundaryPoints(ancestor, 0, rangeStartNode, rangeStartOffset) >= 0) {
414                 // we have already added markup for this node
415                 continue;
416             }
417             bool breakAtEnd = false;
418             if (commonAncestorBlock == ancestor) {
419                 // Include ancestors that are required to retain the appearance of the copied markup.
420                 if (ancestor->hasTagName(listingTag)
421                         || ancestor->hasTagName(olTag)
422                         || ancestor->hasTagName(preTag)
423                         || ancestor->hasTagName(tableTag)
424                         || ancestor->hasTagName(ulTag)
425                         || ancestor->hasTagName(xmpTag)) {
426                     breakAtEnd = true;
427                 } else
428                     break;
429             }
430             markups.prepend(startMarkup(ancestor, range, annotate, defaultStyle.get()));
431             markups.append(endMarkup(ancestor));
432             if (nodes) {
433                 nodes->append(ancestor);
434             }        
435             if (breakAtEnd)
436                 break;
437         }
438     }
439
440     if (annotate) {
441         if (!inSameBlock(visibleEnd, visibleEnd.previous()))
442             markups.append(interchangeNewlineString);
443     }
444
445     // Retain the Mail quote level by including all ancestor mail block quotes.
446     for (Node *ancestor = commonAncestorBlock; ancestor; ancestor = ancestor->parentNode()) {
447         if (isMailBlockquote(ancestor)) {
448             markups.prepend(startMarkup(ancestor, range, annotate, defaultStyle.get()));
449             markups.append(endMarkup(ancestor));
450         }
451     }
452     
453     // add in the "default style" for this markup
454     // FIXME: Handle case where value has illegal characters in it, like "
455     DeprecatedString openTag = DeprecatedString("<span class=\"") + AppleStyleSpanClass + "\" style=\"" + defaultStyle->cssText().deprecatedString() + "\">";
456     markups.prepend(openTag);
457     markups.append("</span>");
458
459     return markups.join("");
460 }
461
462 PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const String& markup, const String& baseURL)
463 {
464     ASSERT(document->documentElement()->isHTMLElement());
465     // FIXME: What if the document element is not an HTML element?
466     HTMLElement *element = static_cast<HTMLElement*>(document->documentElement());
467
468     RefPtr<DocumentFragment> fragment = element->createContextualFragment(markup);
469     ASSERT(fragment);
470
471     if (!baseURL.isEmpty() && baseURL != document->baseURL())
472         completeURLs(fragment.get(), baseURL.deprecatedString());
473
474     return fragment.release();
475 }
476
477 DeprecatedString createMarkup(const Node *node, EChildrenOnly includeChildren,
478     DeprecatedPtrList<Node> *nodes, EAnnotateForInterchange annotate)
479 {
480     ASSERT(annotate == DoNotAnnotateForInterchange); // annotation not yet implemented for this code path
481     node->document()->updateLayoutIgnorePendingStylesheets();
482     return markup(node, includeChildren, false, nodes);
483 }
484
485 static void createParagraphContentsFromString(Document *document, Element *paragraph, const DeprecatedString &string)
486 {
487     ExceptionCode ec = 0;
488     if (string.isEmpty()) {
489         paragraph->appendChild(createBlockPlaceholderElement(document), ec);
490         ASSERT(ec == 0);
491         return;
492     }
493
494     assert(string.find('\n') == -1);
495
496     DeprecatedStringList tabList = DeprecatedStringList::split('\t', string, true);
497     DeprecatedString tabText = "";
498     while (!tabList.isEmpty()) {
499         DeprecatedString s = tabList.first();
500         tabList.pop_front();
501
502         // append the non-tab textual part
503         if (!s.isEmpty()) {
504             if (tabText != "") {
505                 paragraph->appendChild(createTabSpanElement(document, tabText), ec);
506                 ASSERT(ec == 0);
507                 tabText = "";
508             }
509             RefPtr<Node> textNode = document->createTextNode(s);
510             rebalanceWhitespaceInTextNode(textNode.get(), 0, s.length());
511             paragraph->appendChild(textNode.release(), ec);
512             ASSERT(ec == 0);
513         }
514
515         // there is a tab after every entry, except the last entry
516         // (if the last character is a tab, the list gets an extra empty entry)
517         if (!tabList.isEmpty()) {
518             tabText += '\t';
519         } else if (tabText != "") {
520             paragraph->appendChild(createTabSpanElement(document, tabText), ec);
521             ASSERT(ec == 0);
522         }
523     }
524 }
525
526 PassRefPtr<DocumentFragment> createFragmentFromText(Document *document, const DeprecatedString &text)
527 {
528     if (!document)
529         return 0;
530
531     RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
532     
533     if (!text.isEmpty()) {
534         DeprecatedString string = text;
535
536         // Break string into paragraphs. Extra line breaks turn into empty paragraphs.
537         string.replace("\r\n", "\n");
538         string.replace('\r', '\n');
539         DeprecatedStringList list = DeprecatedStringList::split('\n', string, true); // true gets us empty strings in the list
540         while (!list.isEmpty()) {
541             DeprecatedString s = list.first();
542             list.pop_front();
543
544             ExceptionCode ec = 0;
545             RefPtr<Element> element;
546             if (s.isEmpty() && list.isEmpty()) {
547                 // For last line, use the "magic BR" rather than a P.
548                 element = document->createElementNS(xhtmlNamespaceURI, "br", ec);
549                 ASSERT(ec == 0);
550                 element->setAttribute(classAttr, AppleInterchangeNewline);            
551             } else {
552                 element = createDefaultParagraphElement(document);
553                 createParagraphContentsFromString(document, element.get(), s);
554             }
555             fragment->appendChild(element.get(), ec);
556             ASSERT(ec == 0);
557         }
558     }
559     return fragment.release();
560 }
561
562 PassRefPtr<DocumentFragment> createFragmentFromNodeList(Document *document, const DeprecatedPtrList<Node> &nodeList)
563 {
564     if (!document)
565         return 0;
566     
567     RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
568
569     ExceptionCode ec = 0;
570     for (DeprecatedPtrListIterator<Node> i(nodeList); i.current(); ++i) {
571         RefPtr<Element> element = createDefaultParagraphElement(document);
572         element->appendChild(i.current(), ec);
573         ASSERT(ec == 0);
574         fragment->appendChild(element.release(), ec);
575         ASSERT(ec == 0);
576     }
577
578     return fragment.release();
579 }
580
581 }