Reviewed by harrison
[WebKit-https.git] / WebCore / khtml / editing / htmlediting.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 "htmlediting.h"
27
28 #include "css_computedstyle.h"
29 #include "css_value.h"
30 #include "css_valueimpl.h"
31 #include "cssparser.h"
32 #include "cssproperties.h"
33 #include "dom_docimpl.h"
34 #include "dom_elementimpl.h"
35 #include "dom_nodeimpl.h"
36 #include "dom_position.h"
37 #include "dom_stringimpl.h"
38 #include "dom_textimpl.h"
39 #include "dom2_range.h"
40 #include "dom2_rangeimpl.h"
41 #include "html_elementimpl.h"
42 #include "html_imageimpl.h"
43 #include "html_interchange.h"
44 #include "htmlnames.h"
45 #include "khtml_part.h"
46 #include "khtml_part.h"
47 #include "khtmlview.h"
48 #include "qcolor.h"
49 #include "qptrlist.h"
50 #include "render_object.h"
51 #include "render_style.h"
52 #include "render_text.h"
53 #include "visible_position.h"
54 #include "visible_text.h"
55 #include "visible_units.h"
56
57 using namespace DOM::HTMLNames;
58
59 using DOM::AttrImpl;
60 using DOM::CSSComputedStyleDeclarationImpl;
61 using DOM::CSSMutableStyleDeclarationImpl;
62 using DOM::CSSParser;
63 using DOM::CSSPrimitiveValue;
64 using DOM::CSSPrimitiveValueImpl;
65 using DOM::CSSProperty;
66 using DOM::CSSStyleDeclarationImpl;
67 using DOM::CSSValue;
68 using DOM::CSSValueImpl;
69 using DOM::DocumentFragmentImpl;
70 using DOM::DocumentImpl;
71 using DOM::DOMString;
72 using DOM::DOMStringImpl;
73 using DOM::DoNotUpdateLayout;
74 using DOM::EditingTextImpl;
75 using DOM::ElementImpl;
76 using DOM::HTMLElementImpl;
77 using DOM::HTMLImageElementImpl;
78 using DOM::NamedAttrMapImpl;
79 using DOM::NodeImpl;
80 using DOM::NodeListImpl;
81 using DOM::Position;
82 using DOM::RangeImpl;
83 using DOM::TextImpl;
84 using DOM::TreeWalkerImpl;
85
86 #if APPLE_CHANGES
87 #include <kxmlcore/Assertions.h>
88 #include "KWQLogging.h"
89 #include "KWQKHTMLPart.h"
90 #include "KWQRegExp.h"
91 #else
92 #define ASSERT(assertion) ((void)0)
93 #define ASSERT_WITH_MESSAGE(assertion, formatAndArgs...) ((void)0)
94 #define ASSERT_NOT_REACHED() ((void)0)
95 #define LOG(channel, formatAndArgs...) ((void)0)
96 #define ERROR(formatAndArgs...) ((void)0)
97 #define ASSERT(assertion) assert(assertion)
98 #endif
99
100 namespace khtml {
101
102 void rebalanceWhitespaceInTextNode(NodeImpl *node, unsigned int start, unsigned int length)
103 {
104     static QRegExp nonRegularWhitespace("[\xa0\n]");
105     static QString twoSpaces("  ");
106     static QString nbsp("\xa0");
107     static QString space(" ");
108      
109     ASSERT(node->isTextNode());
110     TextImpl *textNode = static_cast<TextImpl *>(node);
111     DOMString text = textNode->data();
112     ASSERT(length <= text.length() && start + length <= text.length());
113     
114     QString substring = text.substring(start, length).qstring();
115
116     substring.replace(nonRegularWhitespace, space);
117     
118     // The sequence should alternate between spaces and nbsps, always ending in a regular space.
119     // Note: This pattern doesn't mimic TextEdit editing behavior on other clients that don't
120     // support our -khtml-nbsp-mode: space, but it comes close.
121     static QString pattern("\xa0 ");
122     int end = length - 1; 
123     int i = substring.findRev(twoSpaces, end);
124     while (i >= 0) {
125         substring.replace(i , 2, pattern);
126         i = substring.findRev(twoSpaces, i);
127     }
128     
129     // Rendering will collapse any regular whitespace at the start or end of a line.  To prevent this, we use
130     // a nbsp at the start and end of a text node.  This is sometimes unnecessary,  E.G. <a>link</a> text
131     if (start == 0 && substring[0] == ' ')
132         substring.replace(0, 1, nbsp);
133     if (start + length == text.length() && substring[end] == ' ')
134         substring.replace(end, 1, nbsp);
135     
136     text.remove(start, length);
137     text.insert(DOMString(substring), start);
138 }
139
140 bool isTableStructureNode(const NodeImpl *node)
141 {
142     RenderObject *r = node->renderer();
143     return (r && (r->isTableCell() || r->isTableRow() || r->isTableSection() || r->isTableCol()));
144 }
145
146 DOMString &nonBreakingSpaceString()
147 {
148     static DOMString nonBreakingSpaceString = QString(QChar(NON_BREAKING_SPACE));
149     return nonBreakingSpaceString;
150 }
151
152 void derefNodesInList(QPtrList<NodeImpl> &list)
153 {
154     for (QPtrListIterator<NodeImpl> it(list); it.current(); ++it)
155         it.current()->deref();
156 }
157
158 static int maxRangeOffset(NodeImpl *n)
159 {
160     if (DOM::offsetInCharacters(n->nodeType()))
161         return n->maxOffset();
162
163     if (n->isElementNode())
164         return n->childNodeCount();
165
166     return 1;
167 }
168
169 bool isSpecialElement(const NodeImpl *n)
170 {
171     if (!n)
172         return false;
173         
174     if (!n->isHTMLElement())
175         return false;
176
177     if (n->isLink())
178         return true;
179
180     if (n->hasTagName(ulTag) || n->hasTagName(olTag) || n->hasTagName(dlTag))
181         return true;
182
183     RenderObject *renderer = n->renderer();
184     if (!renderer)
185         return false;
186         
187     if (renderer->style()->display() == TABLE || renderer->style()->display() == INLINE_TABLE)
188         return true;
189
190     if (renderer->style()->isFloating())
191         return true;
192
193     if (renderer->style()->position() != STATIC)
194         return true;
195         
196     return false;
197 }
198
199 bool isFirstVisiblePositionInSpecialElement(const Position& pos)
200 {
201     VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
202
203     for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
204         if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
205             return false;
206         if (n->rootEditableElement() == NULL)
207             return false;
208         if (isSpecialElement(n))
209             return true;
210     }
211
212     return false;
213 }
214
215 static Position positionBeforeNode(NodeImpl *node)
216 {
217     return Position(node->parentNode(), node->nodeIndex());
218 }
219
220 Position positionBeforeContainingSpecialElement(const Position& pos)
221 {
222     ASSERT(isFirstVisiblePositionInSpecialElement(pos));
223
224     VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
225     
226     NodeImpl *outermostSpecialElement = NULL;
227
228     for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
229         if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
230             break;
231         if (n->rootEditableElement() == NULL)
232             break;
233         if (isSpecialElement(n))
234             outermostSpecialElement = n;
235     }
236     
237     ASSERT(outermostSpecialElement);
238
239     Position result = positionBeforeNode(outermostSpecialElement);
240     if (result.isNull() || !result.node()->rootEditableElement())
241         return pos;
242     
243     return result;
244 }
245
246 bool isLastVisiblePositionInSpecialElement(const Position& pos)
247 {
248     // make sure to get a range-compliant version of the position
249     Position rangePos = VisiblePosition(pos, DOWNSTREAM).position();
250
251     VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM);
252
253     for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) {
254         if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos)
255             return false;
256         if (n->rootEditableElement() == NULL)
257             return false;
258         if (isSpecialElement(n))
259             return true;
260     }
261
262     return false;
263 }
264
265 static Position positionAfterNode(NodeImpl *node)
266 {
267     return Position(node->parentNode(), node->nodeIndex() + 1);
268 }
269
270 Position positionAfterContainingSpecialElement(const Position& pos)
271 {
272     ASSERT(isLastVisiblePositionInSpecialElement(pos));
273
274     // make sure to get a range-compliant version of the position
275     Position rangePos = VisiblePosition(pos, DOWNSTREAM).position();
276
277     VisiblePosition vPos = VisiblePosition(rangePos, DOWNSTREAM);
278
279     NodeImpl *outermostSpecialElement = NULL;
280
281     for (NodeImpl *n = rangePos.node(); n; n = n->parentNode()) {
282         if (VisiblePosition(n, maxRangeOffset(n), DOWNSTREAM) != vPos)
283             break;
284         if (n->rootEditableElement() == NULL)
285             break;
286         if (isSpecialElement(n))
287             outermostSpecialElement = n;
288     }
289     
290     ASSERT(outermostSpecialElement);
291
292     Position result = positionAfterNode(outermostSpecialElement);
293     if (result.isNull() || !result.node()->rootEditableElement())
294         return pos;
295     
296     return result;
297 }
298
299 Position positionOutsideContainingSpecialElement(const Position &pos)
300 {
301     if (isFirstVisiblePositionInSpecialElement(pos)) {
302         return positionBeforeContainingSpecialElement(pos);
303     } else if (isLastVisiblePositionInSpecialElement(pos)) {
304         return positionAfterContainingSpecialElement(pos);
305     }
306
307     return pos;
308 }
309
310 ElementImpl *createDefaultParagraphElement(DocumentImpl *document)
311 {
312     // We would need this margin-zeroing code back if we ever return to using <p> elements for default paragraphs.
313     // static const DOMString defaultParagraphStyle("margin-top: 0; margin-bottom: 0");    
314     int exceptionCode = 0;
315     ElementImpl *element = document->createElementNS(xhtmlNamespaceURI, "div", exceptionCode);
316     ASSERT(exceptionCode == 0);
317     return element;
318 }
319
320 ElementImpl *createBreakElement(DocumentImpl *document)
321 {
322     int exceptionCode = 0;
323     ElementImpl *breakNode = document->createElementNS(xhtmlNamespaceURI, "br", exceptionCode);
324     ASSERT(exceptionCode == 0);
325     return breakNode;
326 }
327
328 bool isTabSpanNode(const NodeImpl *node)
329 {
330     return (node && node->isElementNode() && static_cast<const ElementImpl *>(node)->getAttribute("class") == AppleTabSpanClass);
331 }
332
333 bool isTabSpanTextNode(const NodeImpl *node)
334 {
335     return (node && node->parentNode() && isTabSpanNode(node->parentNode()));
336 }
337
338 Position positionBeforeTabSpan(const Position& pos)
339 {
340     NodeImpl *node = pos.node();
341     if (isTabSpanTextNode(node))
342         node = node->parent();
343     else if (!isTabSpanNode(node))
344         return pos;
345     
346     return Position(node->parentNode(), node->nodeIndex());
347 }
348
349 ElementImpl *createTabSpanElement(DocumentImpl *document, NodeImpl *tabTextNode)
350 {
351     // make the span to hold the tab
352     int exceptionCode = 0;
353     ElementImpl *spanElement = document->createElementNS(xhtmlNamespaceURI, "span", exceptionCode);
354     assert(exceptionCode == 0);
355     spanElement->setAttribute(classAttr, AppleTabSpanClass);
356     spanElement->setAttribute(styleAttr, "white-space:pre");
357
358     // add tab text to that span
359     if (!tabTextNode)
360         tabTextNode = document->createEditingTextNode("\t");
361     spanElement->appendChild(tabTextNode, exceptionCode);
362     assert(exceptionCode == 0);
363
364     return spanElement;
365 }
366
367 ElementImpl *createTabSpanElement(DocumentImpl *document, QString *tabText)
368 {
369     return createTabSpanElement(document, document->createTextNode(*tabText));
370 }
371
372 bool isNodeRendered(const NodeImpl *node)
373 {
374     if (!node)
375         return false;
376
377     RenderObject *renderer = node->renderer();
378     if (!renderer)
379         return false;
380
381     return renderer->style()->visibility() == VISIBLE;
382 }
383
384 NodeImpl *nearestMailBlockquote(const NodeImpl *node)
385 {
386     for (NodeImpl *n = const_cast<NodeImpl *>(node); n; n = n->parentNode()) {
387         if (isMailBlockquote(n))
388             return n;
389     }
390     return 0;
391 }
392
393 bool isMailBlockquote(const NodeImpl *node)
394 {
395     if (!node || !node->renderer() || !node->isElementNode() && !node->hasTagName(blockquoteTag))
396         return false;
397         
398     return static_cast<const ElementImpl *>(node)->getAttribute("type") == "cite";
399 }
400
401 } // namespace khtml