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