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