fffa6e650e536f9cf2e735f6b143b2a6353b0f65
[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 = QChar('<');
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 DeprecatedString createMarkup(const Range *range, DeprecatedPtrList<Node> *nodes, EAnnotateForInterchange annotate)
286 {
287     if (!range || range->isDetached())
288         return DeprecatedString();
289
290     static const DeprecatedString interchangeNewlineString = DeprecatedString("<br class=\"") + AppleInterchangeNewline + "\">";
291
292     ExceptionCode ec = 0;
293     if (range->collapsed(ec))
294         return "";
295     ASSERT(ec == 0);
296     Node *commonAncestor = range->commonAncestorContainer(ec);
297     ASSERT(ec == 0);
298
299     Document *doc = commonAncestor->document();
300     doc->updateLayoutIgnorePendingStylesheets();
301
302     Node *commonAncestorBlock = 0;
303     if (commonAncestor)
304         commonAncestorBlock = commonAncestor->enclosingBlockFlowElement();
305     if (!commonAncestorBlock)
306         return "";
307
308     DeprecatedStringList markups;
309     Node *pastEnd = range->pastEndNode();
310     Node *lastClosed = 0;
311     DeprecatedPtrList<Node> ancestorsToClose;
312
313     // calculate the "default style" for this markup
314     Position pos(doc->documentElement(), 0);
315     RefPtr<CSSComputedStyleDeclaration> computedStyle = pos.computedStyle();
316     RefPtr<CSSMutableStyleDeclaration> defaultStyle = computedStyle->copyInheritableProperties();
317     
318     Node *startNode = range->startNode();
319     VisiblePosition visibleStart(range->startPosition(), VP_DEFAULT_AFFINITY);
320     VisiblePosition visibleEnd(range->endPosition(), VP_DEFAULT_AFFINITY);
321     if (!inSameBlock(visibleStart, visibleStart.next())) {
322         if (visibleStart == visibleEnd.previous())
323             return interchangeNewlineString;
324         markups.append(interchangeNewlineString);
325         startNode = startNode->traverseNextNode();
326     }
327     
328     // Iterate through the nodes of the range.
329     Node *next;
330     for (Node *n = startNode; n != pastEnd; n = next) {
331         next = n->traverseNextNode();
332
333         if (n->isBlockFlow() && next == pastEnd)
334             // Don't write out an empty block.
335             continue;
336         
337         // Add the node to the markup.
338         if (n->renderer()) {
339             markups.append(startMarkup(n, range, annotate, defaultStyle.get()));
340             if (nodes)
341                 nodes->append(n);
342         }
343         
344         if (n->firstChild() == 0) {
345             // Node has no children, add its close tag now.
346             if (n->renderer()) {
347                 markups.append(endMarkup(n));
348                 lastClosed = n;
349             }
350             
351             // Check if the node is the last leaf of a tree.
352             if (n->nextSibling() == 0 || next == pastEnd) {
353                 if (!ancestorsToClose.isEmpty()) {
354                     // Close up the ancestors.
355                     while (Node *ancestor = ancestorsToClose.last()) {
356                         if (next != pastEnd && ancestor == next->parentNode())
357                             break;
358                         // Not at the end of the range, close ancestors up to sibling of next node.
359                         markups.append(endMarkup(ancestor));
360                         lastClosed = ancestor;
361                         ancestorsToClose.removeLast();
362                     }
363                 } else {
364                     // No ancestors to close, but need to add ancestors not in range since next node is in another tree. 
365                     if (next != pastEnd) {
366                         Node *nextParent = next->parentNode();
367                         if (n != nextParent) {
368                             for (Node *parent = n->parent(); parent != 0 && parent != nextParent; parent = parent->parentNode()) {
369                                 markups.prepend(startMarkup(parent, range, annotate, defaultStyle.get()));
370                                 markups.append(endMarkup(parent));
371                                 if (nodes)
372                                     nodes->append(parent);
373                                 lastClosed = parent;
374                             }
375                         }
376                     }
377                 }
378             }
379         } else if (n->renderer())
380             // Node is an ancestor, set it to close eventually.
381             ancestorsToClose.append(n);
382     }
383     
384     Node *rangeStartNode = range->startNode();
385     int rangeStartOffset = range->startOffset(ec);
386     ASSERT(ec == 0);
387     
388     // Add ancestors up to the common ancestor block so inline ancestors such as FONT and B are part of the markup.
389     if (lastClosed) {
390         for (Node *ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
391             if (Range::compareBoundaryPoints(ancestor, 0, rangeStartNode, rangeStartOffset) >= 0) {
392                 // we have already added markup for this node
393                 continue;
394             }
395             bool breakAtEnd = false;
396             if (commonAncestorBlock == ancestor) {
397                 // Include ancestors that are required to retain the appearance of the copied markup.
398                 if (ancestor->hasTagName(listingTag)
399                         || ancestor->hasTagName(olTag)
400                         || ancestor->hasTagName(preTag)
401                         || ancestor->hasTagName(tableTag)
402                         || ancestor->hasTagName(ulTag)
403                         || ancestor->hasTagName(xmpTag)) {
404                     breakAtEnd = true;
405                 } else
406                     break;
407             }
408             markups.prepend(startMarkup(ancestor, range, annotate, defaultStyle.get()));
409             markups.append(endMarkup(ancestor));
410             if (nodes) {
411                 nodes->append(ancestor);
412             }        
413             if (breakAtEnd)
414                 break;
415         }
416     }
417
418     if (annotate) {
419         if (!inSameBlock(visibleEnd, visibleEnd.previous()))
420             markups.append(interchangeNewlineString);
421     }
422
423     // Retain the Mail quote level by including all ancestor mail block quotes.
424     for (Node *ancestor = commonAncestorBlock; ancestor; ancestor = ancestor->parentNode()) {
425         if (isMailBlockquote(ancestor)) {
426             markups.prepend(startMarkup(ancestor, range, annotate, defaultStyle.get()));
427             markups.append(endMarkup(ancestor));
428         }
429     }
430     
431     // add in the "default style" for this markup
432     // FIXME: Handle case where value has illegal characters in it, like "
433     DeprecatedString openTag = DeprecatedString("<span class=\"") + AppleStyleSpanClass + "\" style=\"" + defaultStyle->cssText().deprecatedString() + "\">";
434     markups.prepend(openTag);
435     markups.append("</span>");
436
437     return markups.join("");
438 }
439
440 PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const String& markup, const String& baseURL)
441 {
442     ASSERT(document->documentElement()->isHTMLElement());
443     // FIXME: What if the document element is not an HTML element?
444     HTMLElement *element = static_cast<HTMLElement*>(document->documentElement());
445
446     RefPtr<DocumentFragment> fragment = element->createContextualFragment(markup);
447     ASSERT(fragment);
448
449     if (!baseURL.isEmpty() && baseURL != document->baseURL())
450         completeURLs(fragment.get(), baseURL.deprecatedString());
451
452     return fragment.release();
453 }
454
455 DeprecatedString createMarkup(const WebCore::Node *node, EChildrenOnly includeChildren,
456     DeprecatedPtrList<WebCore::Node> *nodes, EAnnotateForInterchange annotate)
457 {
458     ASSERT(annotate == DoNotAnnotateForInterchange); // annotation not yet implemented for this code path
459     node->document()->updateLayoutIgnorePendingStylesheets();
460     return markup(node, includeChildren, false, nodes);
461 }
462
463 static void createParagraphContentsFromString(WebCore::Document *document, Element *paragraph, const DeprecatedString &string)
464 {
465     ExceptionCode ec = 0;
466     if (string.isEmpty()) {
467         paragraph->appendChild(createBlockPlaceholderElement(document), ec);
468         ASSERT(ec == 0);
469         return;
470     }
471
472     assert(string.find('\n') == -1);
473
474     DeprecatedStringList tabList = DeprecatedStringList::split('\t', string, true);
475     DeprecatedString tabText = "";
476     while (!tabList.isEmpty()) {
477         DeprecatedString s = tabList.first();
478         tabList.pop_front();
479
480         // append the non-tab textual part
481         if (!s.isEmpty()) {
482             if (tabText != "") {
483                 paragraph->appendChild(createTabSpanElement(document, tabText), ec);
484                 ASSERT(ec == 0);
485                 tabText = "";
486             }
487             RefPtr<Node> textNode = document->createTextNode(s);
488             rebalanceWhitespaceInTextNode(textNode.get(), 0, s.length());
489             paragraph->appendChild(textNode.release(), ec);
490             ASSERT(ec == 0);
491         }
492
493         // there is a tab after every entry, except the last entry
494         // (if the last character is a tab, the list gets an extra empty entry)
495         if (!tabList.isEmpty()) {
496             tabText += '\t';
497         } else if (tabText != "") {
498             paragraph->appendChild(createTabSpanElement(document, tabText), ec);
499             ASSERT(ec == 0);
500         }
501     }
502 }
503
504 PassRefPtr<DocumentFragment> createFragmentFromText(Document *document, const DeprecatedString &text)
505 {
506     if (!document)
507         return 0;
508
509     RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
510     
511     if (!text.isEmpty()) {
512         DeprecatedString string = text;
513
514         // Break string into paragraphs. Extra line breaks turn into empty paragraphs.
515         string.replace("\r\n", "\n");
516         string.replace('\r', '\n');
517         DeprecatedStringList list = DeprecatedStringList::split('\n', string, true); // true gets us empty strings in the list
518         while (!list.isEmpty()) {
519             DeprecatedString s = list.first();
520             list.pop_front();
521
522             ExceptionCode ec = 0;
523             RefPtr<Element> element;
524             if (s.isEmpty() && list.isEmpty()) {
525                 // For last line, use the "magic BR" rather than a P.
526                 element = document->createElementNS(xhtmlNamespaceURI, "br", ec);
527                 ASSERT(ec == 0);
528                 element->setAttribute(classAttr, AppleInterchangeNewline);            
529             } else {
530                 element = createDefaultParagraphElement(document);
531                 createParagraphContentsFromString(document, element.get(), s);
532             }
533             fragment->appendChild(element.get(), ec);
534             ASSERT(ec == 0);
535         }
536     }
537     return fragment.release();
538 }
539
540 PassRefPtr<DocumentFragment> createFragmentFromNodeList(Document *document, const DeprecatedPtrList<Node> &nodeList)
541 {
542     if (!document)
543         return 0;
544     
545     RefPtr<DocumentFragment> fragment = document->createDocumentFragment();
546
547     ExceptionCode ec = 0;
548     for (DeprecatedPtrListIterator<Node> i(nodeList); i.current(); ++i) {
549         RefPtr<Element> element = createDefaultParagraphElement(document);
550         element->appendChild(i.current(), ec);
551         ASSERT(ec == 0);
552         fragment->appendChild(element.release(), ec);
553         ASSERT(ec == 0);
554     }
555
556     return fragment.release();
557 }
558
559 }