9553d23b54aab8e046f6cf86eb77c3655c900579
[WebKit.git] / WebCore / khtml / editing / markup.cpp
1 /*
2  * Copyright (C) 2004 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 "markup.h"
27
28 #include "htmlediting.h"
29
30 #include "css/css_computedstyle.h"
31 #include "css/css_valueimpl.h"
32 #include "editing/visible_position.h"
33 #include "editing/visible_units.h"
34 #include "html/html_elementimpl.h"
35 #include "xml/dom_position.h"
36 #include "xml/dom2_rangeimpl.h"
37 #include "rendering/render_text.h"
38 #include "misc/htmlattrs.h"
39 #include "misc/htmltags.h"
40 #include "html/dtd.h"
41
42 using DOM::AttributeImpl;
43 using DOM::CommentImpl;
44 using DOM::CSSComputedStyleDeclarationImpl;
45 using DOM::CSSMutableStyleDeclarationImpl;
46 using DOM::DocumentFragmentImpl;
47 using DOM::DocumentImpl;
48 using DOM::DOMString;
49 using DOM::ElementImpl;
50 using DOM::FORBIDDEN;
51 using DOM::endTagRequirement;
52 using DOM::HTMLElementImpl;
53 using DOM::NamedAttrMapImpl;
54 using DOM::Node;
55 using DOM::NodeImpl;
56 using DOM::Position;
57 using DOM::RangeImpl;
58 using DOM::TextImpl;
59
60 #if APPLE_CHANGES
61 #include "KWQAssertions.h"
62 #include "KWQLogging.h"
63 #else
64 #define ASSERT(assertion) ((void)0)
65 #define ASSERT_WITH_MESSAGE(assertion, formatAndArgs...) ((void)0)
66 #define ASSERT_NOT_REACHED() ((void)0)
67 #define LOG(channel, formatAndArgs...) ((void)0)
68 #define ERROR(formatAndArgs...) ((void)0)
69 #define ASSERT(assertion) assert(assertion)
70 #endif
71
72 namespace khtml {
73
74 static QString escapeHTML(const QString &in)
75 {
76     QString s = "";
77
78     unsigned len = in.length();
79     for (unsigned i = 0; i < len; ++i) {
80         switch (in[i].latin1()) {
81             case '&':
82                 s += "&amp;";
83                 break;
84             case '<':
85                 s += "&lt;";
86                 break;
87             case '>':
88                 s += "&gt;";
89                 break;
90             default:
91                 s += in[i];
92         }
93     }
94
95     return s;
96 }
97
98 static QString stringValueForRange(const NodeImpl *node, const RangeImpl *range)
99 {
100     DOMString str = node->nodeValue().copy();
101     if (range) {
102         int exceptionCode;
103         if (node == range->endContainer(exceptionCode)) {
104             str.truncate(range->endOffset(exceptionCode));
105         }
106         if (node == range->startContainer(exceptionCode)) {
107             str.remove(0, range->startOffset(exceptionCode));
108         }
109     }
110     return str.string();
111 }
112
113 static QString renderedText(const NodeImpl *node, const RangeImpl *range)
114 {
115     RenderObject *r = node->renderer();
116     if (!r)
117         return QString();
118     
119     if (!node->isTextNode())
120         return QString();
121
122     QString result = "";
123
124     int exceptionCode;
125     const TextImpl *textNode = static_cast<const TextImpl *>(node);
126     unsigned startOffset = 0;
127     unsigned endOffset = textNode->length();
128
129     if (range && node == range->startContainer(exceptionCode))
130         startOffset = range->startOffset(exceptionCode);
131     if (range && node == range->endContainer(exceptionCode))
132         endOffset = range->endOffset(exceptionCode);
133     
134     RenderText *textRenderer = static_cast<RenderText *>(r);
135     QString str = node->nodeValue().string();
136     for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
137         unsigned start = box->m_start;
138         unsigned end = box->m_start + box->m_len;
139         if (endOffset < start)
140             break;
141         if (startOffset <= end) {
142             unsigned s = kMax(start, startOffset);
143             unsigned e = kMin(end, endOffset);
144             result.append(str.mid(s, e-s));
145             if (e == end) {
146                 // now add in collapsed-away spaces if at the end of the line
147                 InlineTextBox *nextBox = box->nextTextBox();
148                 if (nextBox && box->root() != nextBox->root()) {
149                     const char nonBreakingSpace = '\xa0';
150                     // count the number of characters between the end of the
151                     // current box and the start of the next box.
152                     int collapsedStart = e;
153                     int collapsedPastEnd = kMin((unsigned)nextBox->m_start, endOffset + 1);
154                     bool addNextNonNBSP = true;
155                     for (int i = collapsedStart; i < collapsedPastEnd; i++) {
156                         if (str[i] == nonBreakingSpace) {
157                             result.append(str[i]);
158                             addNextNonNBSP = true;
159                         }
160                         else if (addNextNonNBSP) {
161                             result.append(str[i]);
162                             addNextNonNBSP = false;
163                         }
164                     }
165                 }
166             }
167         }
168
169     }
170     
171     return result;
172 }
173
174 static QString startMarkup(const NodeImpl *node, const RangeImpl *range, EAnnotateForInterchange annotate, CSSMutableStyleDeclarationImpl *defaultStyle)
175 {
176     unsigned short type = node->nodeType();
177     switch (type) {
178         case Node::TEXT_NODE: {
179             if (node->parentNode()) {
180                 switch (node->parentNode()->id()) {
181                     case ID_PRE:
182                     case ID_SCRIPT:
183                     case ID_STYLE:
184                     case ID_TEXTAREA:
185                         return stringValueForRange(node, range);
186                 }
187             }
188             QString markup = annotate ? escapeHTML(renderedText(node, range)) : escapeHTML(stringValueForRange(node, range));            
189             if (defaultStyle) {
190                 NodeImpl *element = node->parentNode();
191                 if (element) {
192                     CSSMutableStyleDeclarationImpl *style = Position(element, 0).computedStyle()->copyInheritableProperties();
193                     style->ref();
194                     defaultStyle->diff(style);
195                     if (style->length() > 0) {
196                         // FIXME: Handle case where style->cssText() has illegal characters in it, like "
197                         QString openTag = QString("<span class=\"") + AppleStyleSpanClass + "\" style=\"" + style->cssText().string() + "\">";
198                         markup = openTag + markup + "</span>";
199                     }
200                     style->deref();
201                 }            
202             }
203             return annotate ? convertHTMLTextToInterchangeFormat(markup) : markup;
204         }
205         case Node::COMMENT_NODE:
206             return static_cast<const CommentImpl *>(node)->toString().string();
207         case Node::DOCUMENT_NODE:
208             return "";
209         default: {
210             QString markup = QChar('<') + node->nodeName().string();
211             if (type == Node::ELEMENT_NODE) {
212                 const ElementImpl *el = static_cast<const ElementImpl *>(node);
213                 DOMString additionalStyle;
214                 if (defaultStyle && el->isHTMLElement()) {
215                     CSSMutableStyleDeclarationImpl *style = Position(const_cast<ElementImpl *>(el), 0).computedStyle()->copyInheritableProperties();
216                     style->ref();
217                     defaultStyle->diff(style);
218                     if (style->length() > 0) {
219                         CSSMutableStyleDeclarationImpl *inlineStyleDecl = static_cast<const HTMLElementImpl *>(el)->inlineStyleDecl();
220                         if (inlineStyleDecl)
221                             inlineStyleDecl->diff(style);
222                         additionalStyle = style->cssText();
223                     }
224                     style->deref();
225                 }
226                 NamedAttrMapImpl *attrs = el->attributes();
227                 unsigned long length = attrs->length();
228                 if (length == 0 && additionalStyle.length() > 0) {
229                     // FIXME: Handle case where additionalStyle has illegal characters in it, like "
230                     markup += " " + node->getDocument()->attrName(ATTR_STYLE).string() + "=\"" + additionalStyle.string() + "\"";
231                 }
232                 else {
233                     for (unsigned int i=0; i<length; i++) {
234                         AttributeImpl *attr = attrs->attributeItem(i);
235                         DOMString value = attr->value();
236                         if (attr->id() == ATTR_STYLE && additionalStyle.length() > 0)
237                             value += "; " + additionalStyle;
238                         // FIXME: Handle case where value has illegal characters in it, like "
239                         markup += " " + node->getDocument()->attrName(attr->id()).string() + "=\"" + value.string() + "\"";
240                     }
241                 }
242             }
243             markup += node->isHTMLElement() ? ">" : "/>";
244             return markup;
245         }
246     }
247 }
248
249 static QString endMarkup(const NodeImpl *node)
250 {
251     if ((!node->isHTMLElement() || endTagRequirement(node->id()) != FORBIDDEN) && node->nodeType() != Node::TEXT_NODE && node->nodeType() != Node::DOCUMENT_NODE) {
252         return "</" + node->nodeName().string() + ">";
253     }
254     return "";
255 }
256
257 static QString markup(const NodeImpl *startNode, bool onlyIncludeChildren, bool includeSiblings, QPtrList<NodeImpl> *nodes)
258 {
259     // Doesn't make sense to only include children and include siblings.
260     ASSERT(!(onlyIncludeChildren && includeSiblings));
261     QString me = "";
262     for (const NodeImpl *current = startNode; current != NULL; current = includeSiblings ? current->nextSibling() : NULL) {
263         if (!onlyIncludeChildren) {
264             if (nodes) {
265                 nodes->append(current);
266             }
267             me += startMarkup(current, 0, DoNotAnnotateForInterchange, 0);
268         }        
269         if (!current->isHTMLElement() || endTagRequirement(current->id()) != FORBIDDEN) {
270             // print children
271             if (NodeImpl *n = current->firstChild()) {
272                 me += markup(n, false, true, nodes);
273             }
274             // Print my ending tag
275             if (!onlyIncludeChildren) {
276                 me += endMarkup(current);
277             }
278         }
279     }
280     return me;
281 }
282
283 static void completeURLs(NodeImpl *node, const QString &baseURL)
284 {
285     NodeImpl *end = node->traverseNextSibling();
286     for (NodeImpl *n = node; n != end; n = n->traverseNextNode()) {
287         if (n->nodeType() == Node::ELEMENT_NODE) {
288             ElementImpl *e = static_cast<ElementImpl *>(n);
289             NamedAttrMapImpl *attrs = e->attributes();
290             unsigned long length = attrs->length();
291             for (unsigned long i = 0; i < length; i++) {
292                 AttributeImpl *attr = attrs->attributeItem(i);
293                 if (e->isURLAttribute(attr)) {
294                     e->setAttribute(attr->id(), KURL(baseURL, attr->value().string()).url());
295                 }
296             }
297         }
298     }
299 }
300
301 QString createMarkup(const RangeImpl *range, QPtrList<NodeImpl> *nodes, EAnnotateForInterchange annotate)
302 {
303     if (!range || range->isDetached())
304         return QString();
305
306     static const QString interchangeNewlineString = QString("<br class=\"") + AppleInterchangeNewline + "\">";
307
308     int exceptionCode = 0;
309     NodeImpl *commonAncestor = range->commonAncestorContainer(exceptionCode);
310     ASSERT(exceptionCode == 0);
311
312     DocumentImpl *doc = commonAncestor->getDocument();
313     doc->updateLayout();
314
315     NodeImpl *commonAncestorBlock = 0;
316     if (commonAncestor != 0) {
317         commonAncestorBlock = commonAncestor->enclosingBlockFlowElement();
318     }
319     if (commonAncestorBlock == 0) {
320         return "";    
321     }
322
323     QStringList markups;
324     NodeImpl *pastEnd = range->pastEndNode();
325     NodeImpl *lastClosed = 0;
326     QPtrList<NodeImpl> ancestorsToClose;
327
328     // calculate the "default style" for this markup
329     Position pos(doc->documentElement(), 0);
330     CSSMutableStyleDeclarationImpl *defaultStyle = pos.computedStyle()->copyInheritableProperties();
331     defaultStyle->ref();
332     
333     NodeImpl *startNode = range->startNode();
334     VisiblePosition visibleStart(range->startPosition(), VP_DEFAULT_AFFINITY);
335     VisiblePosition visibleEnd(range->endPosition(), VP_DEFAULT_AFFINITY);
336     if (isEndOfBlock(visibleStart)) {
337         if (visibleStart == visibleEnd.previous())
338             return interchangeNewlineString;
339         markups.append(interchangeNewlineString);
340         startNode = startNode->traverseNextNode();
341     }
342     
343     // Iterate through the nodes of the range.
344     NodeImpl *next;
345     for (NodeImpl *n = startNode; n != pastEnd; n = next) {
346         next = n->traverseNextNode();
347
348         if (n->isBlockFlow() && next == pastEnd) {
349             // Don't write out an empty block.
350             continue;
351         }
352         
353         // Add the node to the markup.
354         if (n->renderer()) {
355             markups.append(startMarkup(n, range, annotate, defaultStyle));
356             if (nodes) {
357                 nodes->append(n);
358             }
359         }
360         
361         if (n->firstChild() == 0) {
362             // Node has no children, add its close tag now.
363             if (n->renderer()) {
364                 markups.append(endMarkup(n));
365                 lastClosed = n;
366             }
367             
368             // Check if the node is the last leaf of a tree.
369             if (n->nextSibling() == 0 || next == pastEnd) {
370                 if (!ancestorsToClose.isEmpty()) {
371                     // Close up the ancestors.
372                     while (NodeImpl *ancestor = ancestorsToClose.last()) {
373                         if (next != pastEnd && ancestor == next->parentNode()) {
374                             break;
375                         }
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                     // No ancestors to close, but need to add ancestors not in range since next node is in another tree. 
383                     if (next != pastEnd) {
384                         NodeImpl *nextParent = next->parentNode();
385                         if (n != nextParent) {
386                             for (NodeImpl *parent = n->parent(); parent != 0 && parent != nextParent; parent = parent->parentNode()) {
387                                 markups.prepend(startMarkup(parent, range, annotate, defaultStyle));
388                                 markups.append(endMarkup(parent));
389                                 if (nodes) {
390                                     nodes->append(parent);
391                                 }                            
392                                 lastClosed = parent;
393                             }
394                         }
395                     }
396                 }
397             }
398         } else if (n->renderer()) {
399             // Node is an ancestor, set it to close eventually.
400             ancestorsToClose.append(n);
401         }
402     }
403     
404     NodeImpl *rangeStartNode = range->startNode();
405     int rangeStartOffset = range->startOffset(exceptionCode);
406     ASSERT(exceptionCode == 0);
407     
408     // Add ancestors up to the common ancestor block so inline ancestors such as FONT and B are part of the markup.
409     if (lastClosed) {
410         for (NodeImpl *ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) {
411             if (RangeImpl::compareBoundaryPoints(ancestor, 0, rangeStartNode, rangeStartOffset) >= 0) {
412                 // we have already added markup for this node
413                 continue;
414             }
415             bool breakAtEnd = false;
416             if (commonAncestorBlock == ancestor) {
417                 NodeImpl::Id id = ancestor->id();
418                 // Include ancestors that are required to retain the appearance of the copied markup.
419                 if (id == ID_PRE || id == ID_TABLE || id == ID_OL || id == ID_UL) {
420                     breakAtEnd = true;
421                 } else {
422                     break;
423                 }
424             }
425             markups.prepend(startMarkup(ancestor, range, annotate, defaultStyle));
426             markups.append(endMarkup(ancestor));
427             if (nodes) {
428                 nodes->append(ancestor);
429             }        
430             if (breakAtEnd) {
431                 break;
432             }
433         }
434     }
435
436     if (annotate) {
437         if (!inSameBlock(visibleEnd, visibleEnd.previous()))
438             markups.append(interchangeNewlineString);
439     }
440
441     // Retain the Mail quote level by including all ancestor mail block quotes.
442     for (NodeImpl *ancestor = commonAncestorBlock; ancestor; ancestor = ancestor->parentNode()) {
443         if (isMailBlockquote(ancestor)) {
444             markups.prepend(startMarkup(ancestor, range, annotate, defaultStyle));
445             markups.append(endMarkup(ancestor));
446         }
447     }
448     
449     // add in the "default style" for this markup
450     // FIXME: Handle case where value has illegal characters in it, like "
451     QString openTag = QString("<span class=\"") + AppleStyleSpanClass + "\" style=\"" + defaultStyle->cssText().string() + "\">";
452     markups.prepend(openTag);
453     markups.append("</span>");
454     defaultStyle->deref();
455
456     return markups.join("");
457 }
458
459 DocumentFragmentImpl *createFragmentFromMarkup(DocumentImpl *document, const QString &markup, const QString &baseURL)
460 {
461     // FIXME: What if the document element is not an HTML element?
462     HTMLElementImpl *element = static_cast<HTMLElementImpl *>(document->documentElement());
463
464     DocumentFragmentImpl *fragment = element->createContextualFragment(markup);
465     ASSERT(fragment);
466
467     if (!baseURL.isEmpty() && baseURL != document->baseURL())
468         completeURLs(fragment, baseURL);
469
470     return fragment;
471 }
472
473 QString createMarkup(const DOM::NodeImpl *node, EChildrenOnly includeChildren,
474     QPtrList<DOM::NodeImpl> *nodes, EAnnotateForInterchange annotate)
475 {
476     ASSERT(annotate == DoNotAnnotateForInterchange); // annotation not yet implemented for this code path
477
478     // FIXME: We could take out this if statement if we had more time to test.
479     // I'm concerned that making this crash when the document is nil might be too risky a change at the moment.
480     DocumentImpl *doc = node->getDocument();
481     assert(doc);
482     if (doc)
483         doc->updateLayout();
484
485     return markup(node, includeChildren, false, nodes);
486 }
487
488 DOM::DocumentFragmentImpl *createFragmentFromText(DOM::DocumentImpl *document, const QString &text)
489 {
490     if (!document)
491         return 0;
492
493     DocumentFragmentImpl *fragment = document->createDocumentFragment();
494     fragment->ref();
495     
496     if (!text.isEmpty()) {
497         QString string = text;
498
499         // Replace tabs with four plain spaces.
500         // These spaces will get converted along with the other existing spaces below.
501         string.replace('\t', "    ");
502
503         // FIXME: Wrap the NBSP's in a span that says "converted space".
504         int offset = 0;
505         int stringLength = string.length();
506         while (1) {
507             // FIXME: This only converts plain old spaces, and does not
508             // deal with more exotic whitespace. Note that we want to 
509             // leave newlines and returns alone at this point anyway, 
510             // since those are handled specially later.
511             int spacesPos = string.find("  ", offset);
512             if (spacesPos < 0)
513                 break;
514
515             // Found two adjoining spaces.
516             // Now, lookahead to see if these two spaces are followed by:
517             //   1. another space and then a non-space
518             //   2. another space and then the end of the string
519             // If either 1 or 2 is true, replace the three spaces found with nbsp+nbsp+space, 
520             // otherwise, replace the first two spaces with nbsp+space.
521             if ((spacesPos + 3 < stringLength && string[spacesPos + 2] == ' ' && string[spacesPos + 3] != ' ')
522                     || (spacesPos + 3 == stringLength && string[spacesPos + 2] == ' ')) {
523                 string.replace(spacesPos, 3, "\xA0\xA0 ");
524             } else {
525                 string.replace(spacesPos, 2, "\xA0 ");
526             }
527             offset = spacesPos + 2;
528         }
529
530         // Break string into paragraphs. Extra line breaks turn into empty paragraphs.
531         string.replace(QString("\r\n"), "\n");
532         string.replace('\r', '\n');
533         QStringList list = QStringList::split('\n', string, true); // true gets us empty strings in the list
534         while (!list.isEmpty()) {
535             QString s = list.first();
536             list.pop_front();
537
538             int exceptionCode = 0;
539             ElementImpl *element;
540             if (s.isEmpty() && list.isEmpty()) {
541                 // For last line, use the "magic BR" rather than a P.
542                 element = document->createHTMLElement("br", exceptionCode);
543                 ASSERT(exceptionCode == 0);
544                 element->ref();
545                 element->setAttribute(ATTR_CLASS, AppleInterchangeNewline);            
546             } else {
547                 element = createDefaultParagraphElement(document);
548                 NodeImpl *paragraphContents;
549                 if (s.isEmpty()) {
550                     paragraphContents = createBlockPlaceholderElement(document);
551                 } else {
552                     paragraphContents = document->createTextNode(s);
553                     ASSERT(exceptionCode == 0);
554                 }
555                 element->ref();
556                 element->appendChild(paragraphContents, exceptionCode);
557                 ASSERT(exceptionCode == 0);
558             }
559             fragment->appendChild(element, exceptionCode);
560             ASSERT(exceptionCode == 0);
561             element->deref();
562         }
563     }
564     
565     // Trick to get the fragment back to the floating state, with 0
566     // refs but not destroyed.
567     fragment->setParent(document);
568     fragment->deref();
569     fragment->setParent(0);
570     
571     return fragment;
572 }
573
574 }