Add toHTMLTableSectionElement() functions, and use it
[WebKit-https.git] / Source / WebCore / html / HTMLTableElement.cpp
1 /*
2  * Copyright (C) 1997 Martin Jones (mjones@kde.org)
3  *           (C) 1997 Torben Weis (weis@kde.org)
4  *           (C) 1998 Waldo Bastian (bastian@kde.org)
5  *           (C) 1999 Lars Knoll (knoll@kde.org)
6  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
7  * Copyright (C) 2003, 2004, 2005, 2006, 2008, 2010, 2011 Apple Inc. All rights reserved.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public License
20  * along with this library; see the file COPYING.LIB.  If not, write to
21  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  */
24
25 #include "config.h"
26 #include "HTMLTableElement.h"
27
28 #include "Attribute.h"
29 #include "CSSImageValue.h"
30 #include "CSSPropertyNames.h"
31 #include "CSSValueKeywords.h"
32 #include "CSSValuePool.h"
33 #include "ExceptionCode.h"
34 #include "ExceptionCodePlaceholder.h"
35 #include "HTMLNames.h"
36 #include "HTMLParserIdioms.h"
37 #include "HTMLTableCaptionElement.h"
38 #include "HTMLTableRowElement.h"
39 #include "HTMLTableRowsCollection.h"
40 #include "HTMLTableSectionElement.h"
41 #include "RenderTable.h"
42 #include "StyleProperties.h"
43 #include <wtf/Ref.h>
44
45 namespace WebCore {
46
47 using namespace HTMLNames;
48
49 HTMLTableElement::HTMLTableElement(const QualifiedName& tagName, Document& document)
50     : HTMLElement(tagName, document)
51     , m_borderAttr(false)
52     , m_borderColorAttr(false)
53     , m_frameAttr(false)
54     , m_rulesAttr(UnsetRules)
55     , m_padding(1)
56 {
57     ASSERT(hasTagName(tableTag));
58 }
59
60 PassRefPtr<HTMLTableElement> HTMLTableElement::create(Document& document)
61 {
62     return adoptRef(new HTMLTableElement(tableTag, document));
63 }
64
65 PassRefPtr<HTMLTableElement> HTMLTableElement::create(const QualifiedName& tagName, Document& document)
66 {
67     return adoptRef(new HTMLTableElement(tagName, document));
68 }
69
70 HTMLTableCaptionElement* HTMLTableElement::caption() const
71 {
72     for (Node* child = firstChild(); child; child = child->nextSibling()) {
73         if (child->hasTagName(captionTag))
74             return toHTMLTableCaptionElement(child);
75     }
76     return 0;
77 }
78
79 void HTMLTableElement::setCaption(PassRefPtr<HTMLTableCaptionElement> newCaption, ExceptionCode& ec)
80 {
81     deleteCaption();
82     insertBefore(newCaption, firstChild(), ec);
83 }
84
85 HTMLTableSectionElement* HTMLTableElement::tHead() const
86 {
87     for (Node* child = firstChild(); child; child = child->nextSibling()) {
88         if (child->hasTagName(theadTag))
89             return toHTMLTableSectionElement(child);
90     }
91     return 0;
92 }
93
94 void HTMLTableElement::setTHead(PassRefPtr<HTMLTableSectionElement> newHead, ExceptionCode& ec)
95 {
96     deleteTHead();
97
98     Node* child;
99     for (child = firstChild(); child; child = child->nextSibling())
100         if (child->isElementNode() && !child->hasTagName(captionTag) && !child->hasTagName(colgroupTag))
101             break;
102
103     insertBefore(newHead, child, ec);
104 }
105
106 HTMLTableSectionElement* HTMLTableElement::tFoot() const
107 {
108     for (Node* child = firstChild(); child; child = child->nextSibling()) {
109         if (child->hasTagName(tfootTag))
110             return toHTMLTableSectionElement(child);
111     }
112     return 0;
113 }
114
115 void HTMLTableElement::setTFoot(PassRefPtr<HTMLTableSectionElement> newFoot, ExceptionCode& ec)
116 {
117     deleteTFoot();
118
119     Node* child;
120     for (child = firstChild(); child; child = child->nextSibling())
121         if (child->isElementNode() && !child->hasTagName(captionTag) && !child->hasTagName(colgroupTag) && !child->hasTagName(theadTag))
122             break;
123
124     insertBefore(newFoot, child, ec);
125 }
126
127 PassRefPtr<HTMLElement> HTMLTableElement::createTHead()
128 {
129     if (HTMLTableSectionElement* existingHead = tHead())
130         return existingHead;
131     RefPtr<HTMLTableSectionElement> head = HTMLTableSectionElement::create(theadTag, document());
132     setTHead(head, IGNORE_EXCEPTION);
133     return head.release();
134 }
135
136 void HTMLTableElement::deleteTHead()
137 {
138     removeChild(tHead(), IGNORE_EXCEPTION);
139 }
140
141 PassRefPtr<HTMLElement> HTMLTableElement::createTFoot()
142 {
143     if (HTMLTableSectionElement* existingFoot = tFoot())
144         return existingFoot;
145     RefPtr<HTMLTableSectionElement> foot = HTMLTableSectionElement::create(tfootTag, document());
146     setTFoot(foot, IGNORE_EXCEPTION);
147     return foot.release();
148 }
149
150 void HTMLTableElement::deleteTFoot()
151 {
152     removeChild(tFoot(), IGNORE_EXCEPTION);
153 }
154
155 PassRefPtr<HTMLElement> HTMLTableElement::createTBody()
156 {
157     RefPtr<HTMLTableSectionElement> body = HTMLTableSectionElement::create(tbodyTag, document());
158     Node* referenceElement = lastBody() ? lastBody()->nextSibling() : 0;
159     insertBefore(body, referenceElement, ASSERT_NO_EXCEPTION);
160     return body.release();
161 }
162
163 PassRefPtr<HTMLElement> HTMLTableElement::createCaption()
164 {
165     if (HTMLTableCaptionElement* existingCaption = caption())
166         return existingCaption;
167     RefPtr<HTMLTableCaptionElement> caption = HTMLTableCaptionElement::create(captionTag, document());
168     setCaption(caption, IGNORE_EXCEPTION);
169     return caption.release();
170 }
171
172 void HTMLTableElement::deleteCaption()
173 {
174     removeChild(caption(), IGNORE_EXCEPTION);
175 }
176
177 HTMLTableSectionElement* HTMLTableElement::lastBody() const
178 {
179     for (Node* child = lastChild(); child; child = child->previousSibling()) {
180         if (child->hasTagName(tbodyTag))
181             return toHTMLTableSectionElement(child);
182     }
183     return 0;
184 }
185
186 PassRefPtr<HTMLElement> HTMLTableElement::insertRow(int index, ExceptionCode& ec)
187 {
188     if (index < -1) {
189         ec = INDEX_SIZE_ERR;
190         return 0;
191     }
192
193     Ref<HTMLTableElement> protectFromMutationEvents(*this);
194
195     RefPtr<HTMLTableRowElement> lastRow = 0;
196     RefPtr<HTMLTableRowElement> row = 0;
197     if (index == -1)
198         lastRow = HTMLTableRowsCollection::lastRow(this);
199     else {
200         for (int i = 0; i <= index; ++i) {
201             row = HTMLTableRowsCollection::rowAfter(this, lastRow.get());
202             if (!row) {
203                 if (i != index) {
204                     ec = INDEX_SIZE_ERR;
205                     return 0;
206                 }
207                 break;
208             }
209             lastRow = row;
210         }
211     }
212
213     RefPtr<ContainerNode> parent;
214     if (lastRow)
215         parent = row ? row->parentNode() : lastRow->parentNode();
216     else {
217         parent = lastBody();
218         if (!parent) {
219             RefPtr<HTMLTableSectionElement> newBody = HTMLTableSectionElement::create(tbodyTag, document());
220             RefPtr<HTMLTableRowElement> newRow = HTMLTableRowElement::create(document());
221             newBody->appendChild(newRow, ec);
222             appendChild(newBody.release(), ec);
223             return newRow.release();
224         }
225     }
226
227     RefPtr<HTMLTableRowElement> newRow = HTMLTableRowElement::create(document());
228     parent->insertBefore(newRow, row.get(), ec);
229     return newRow.release();
230 }
231
232 void HTMLTableElement::deleteRow(int index, ExceptionCode& ec)
233 {
234     HTMLTableRowElement* row = 0;
235     if (index == -1)
236         row = HTMLTableRowsCollection::lastRow(this);
237     else {
238         for (int i = 0; i <= index; ++i) {
239             row = HTMLTableRowsCollection::rowAfter(this, row);
240             if (!row)
241                 break;
242         }
243     }
244     if (!row) {
245         ec = INDEX_SIZE_ERR;
246         return;
247     }
248     row->remove(ec);
249 }
250
251 static inline bool isTableCellAncestor(Node* n)
252 {
253     return n->hasTagName(theadTag) || n->hasTagName(tbodyTag) ||
254            n->hasTagName(tfootTag) || n->hasTagName(trTag) ||
255            n->hasTagName(thTag);
256 }
257
258 static bool setTableCellsChanged(Node* n)
259 {
260     ASSERT(n);
261     bool cellChanged = false;
262
263     if (n->hasTagName(tdTag))
264         cellChanged = true;
265     else if (isTableCellAncestor(n)) {
266         for (Node* child = n->firstChild(); child; child = child->nextSibling())
267             cellChanged |= setTableCellsChanged(child);
268     }
269
270     if (cellChanged)
271        n->setNeedsStyleRecalc();
272
273     return cellChanged;
274 }
275
276 static bool getBordersFromFrameAttributeValue(const AtomicString& value, bool& borderTop, bool& borderRight, bool& borderBottom, bool& borderLeft)
277 {
278     borderTop = false;
279     borderRight = false;
280     borderBottom = false;
281     borderLeft = false;
282
283     if (equalIgnoringCase(value, "above"))
284         borderTop = true;
285     else if (equalIgnoringCase(value, "below"))
286         borderBottom = true;
287     else if (equalIgnoringCase(value, "hsides"))
288         borderTop = borderBottom = true;
289     else if (equalIgnoringCase(value, "vsides"))
290         borderLeft = borderRight = true;
291     else if (equalIgnoringCase(value, "lhs"))
292         borderLeft = true;
293     else if (equalIgnoringCase(value, "rhs"))
294         borderRight = true;
295     else if (equalIgnoringCase(value, "box") || equalIgnoringCase(value, "border"))
296         borderTop = borderBottom = borderLeft = borderRight = true;
297     else if (!equalIgnoringCase(value, "void"))
298         return false;
299     return true;
300 }
301
302 void HTMLTableElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
303 {
304     if (name == widthAttr)
305         addHTMLLengthToStyle(style, CSSPropertyWidth, value);
306     else if (name == heightAttr)
307         addHTMLLengthToStyle(style, CSSPropertyHeight, value);
308     else if (name == borderAttr) 
309         addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderWidth, parseBorderWidthAttribute(value), CSSPrimitiveValue::CSS_PX);
310     else if (name == bordercolorAttr) {
311         if (!value.isEmpty())
312             addHTMLColorToStyle(style, CSSPropertyBorderColor, value);
313     } else if (name == bgcolorAttr)
314         addHTMLColorToStyle(style, CSSPropertyBackgroundColor, value);
315     else if (name == backgroundAttr) {
316         String url = stripLeadingAndTrailingHTMLSpaces(value);
317         if (!url.isEmpty())
318             style.setProperty(CSSProperty(CSSPropertyBackgroundImage, CSSImageValue::create(document().completeURL(url).string())));
319     } else if (name == valignAttr) {
320         if (!value.isEmpty())
321             addPropertyToPresentationAttributeStyle(style, CSSPropertyVerticalAlign, value);
322     } else if (name == cellspacingAttr) {
323         if (!value.isEmpty())
324             addHTMLLengthToStyle(style, CSSPropertyBorderSpacing, value);
325     } else if (name == vspaceAttr) {
326         addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
327         addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
328     } else if (name == hspaceAttr) {
329         addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
330         addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
331     } else if (name == alignAttr) {
332         if (!value.isEmpty()) {
333             if (equalIgnoringCase(value, "center")) {
334                 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitMarginStart, CSSValueAuto);
335                 addPropertyToPresentationAttributeStyle(style, CSSPropertyWebkitMarginEnd, CSSValueAuto);
336             } else
337                 addPropertyToPresentationAttributeStyle(style, CSSPropertyFloat, value);
338         }
339     } else if (name == rulesAttr) {
340         // The presence of a valid rules attribute causes border collapsing to be enabled.
341         if (m_rulesAttr != UnsetRules)
342             addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderCollapse, CSSValueCollapse);
343     } else if (name == frameAttr) {
344         bool borderTop;
345         bool borderRight;
346         bool borderBottom;
347         bool borderLeft;
348         if (getBordersFromFrameAttributeValue(value, borderTop, borderRight, borderBottom, borderLeft)) {
349             addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderWidth, CSSValueThin);
350             addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderTopStyle, borderTop ? CSSValueSolid : CSSValueHidden);
351             addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderBottomStyle, borderBottom ? CSSValueSolid : CSSValueHidden);
352             addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderLeftStyle, borderLeft ? CSSValueSolid : CSSValueHidden);
353             addPropertyToPresentationAttributeStyle(style, CSSPropertyBorderRightStyle, borderRight ? CSSValueSolid : CSSValueHidden);
354         }
355     } else
356         HTMLElement::collectStyleForPresentationAttribute(name, value, style);
357 }
358
359 bool HTMLTableElement::isPresentationAttribute(const QualifiedName& name) const
360 {
361     if (name == widthAttr || name == heightAttr || name == bgcolorAttr || name == backgroundAttr || name == valignAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr || name == cellspacingAttr || name == borderAttr || name == bordercolorAttr || name == frameAttr || name == rulesAttr)
362         return true;
363     return HTMLElement::isPresentationAttribute(name);
364 }
365
366 void HTMLTableElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
367 {
368     CellBorders bordersBefore = cellBorders();
369     unsigned short oldPadding = m_padding;
370
371     if (name == borderAttr)  {
372         // FIXME: This attribute is a mess.
373         m_borderAttr = parseBorderWidthAttribute(value);
374     } else if (name == bordercolorAttr) {
375         m_borderColorAttr = !value.isEmpty();
376     } else if (name == frameAttr) {
377         // FIXME: This attribute is a mess.
378         bool borderTop;
379         bool borderRight;
380         bool borderBottom;
381         bool borderLeft;
382         m_frameAttr = getBordersFromFrameAttributeValue(value, borderTop, borderRight, borderBottom, borderLeft);
383     } else if (name == rulesAttr) {
384         m_rulesAttr = UnsetRules;
385         if (equalIgnoringCase(value, "none"))
386             m_rulesAttr = NoneRules;
387         else if (equalIgnoringCase(value, "groups"))
388             m_rulesAttr = GroupsRules;
389         else if (equalIgnoringCase(value, "rows"))
390             m_rulesAttr = RowsRules;
391         else if (equalIgnoringCase(value, "cols"))
392             m_rulesAttr = ColsRules;
393         else if (equalIgnoringCase(value, "all"))
394             m_rulesAttr = AllRules;
395     } else if (name == cellpaddingAttr) {
396         if (!value.isEmpty())
397             m_padding = std::max(0, value.toInt());
398         else
399             m_padding = 1;
400     } else if (name == colsAttr) {
401         // ###
402     } else
403         HTMLElement::parseAttribute(name, value);
404
405     if (bordersBefore != cellBorders() || oldPadding != m_padding) {
406         m_sharedCellStyle = 0;
407         bool cellChanged = false;
408         for (Node* child = firstChild(); child; child = child->nextSibling())
409             cellChanged |= setTableCellsChanged(child);
410         if (cellChanged)
411             setNeedsStyleRecalc();
412     }
413 }
414
415 static StyleProperties* leakBorderStyle(CSSValueID value)
416 {
417     RefPtr<MutableStyleProperties> style = MutableStyleProperties::create();
418     style->setProperty(CSSPropertyBorderTopStyle, value);
419     style->setProperty(CSSPropertyBorderBottomStyle, value);
420     style->setProperty(CSSPropertyBorderLeftStyle, value);
421     style->setProperty(CSSPropertyBorderRightStyle, value);
422     return style.release().leakRef();
423 }
424
425 const StyleProperties* HTMLTableElement::additionalPresentationAttributeStyle()
426 {
427     if (m_frameAttr)
428         return 0;
429     
430     if (!m_borderAttr && !m_borderColorAttr) {
431         // Setting the border to 'hidden' allows it to win over any border
432         // set on the table's cells during border-conflict resolution.
433         if (m_rulesAttr != UnsetRules) {
434             static StyleProperties* solidBorderStyle = leakBorderStyle(CSSValueHidden);
435             return solidBorderStyle;
436         }
437         return 0;
438     }
439
440     if (m_borderColorAttr) {
441         static StyleProperties* solidBorderStyle = leakBorderStyle(CSSValueSolid);
442         return solidBorderStyle;
443     }
444     static StyleProperties* outsetBorderStyle = leakBorderStyle(CSSValueOutset);
445     return outsetBorderStyle;
446 }
447
448 HTMLTableElement::CellBorders HTMLTableElement::cellBorders() const
449 {
450     switch (m_rulesAttr) {
451         case NoneRules:
452         case GroupsRules:
453             return NoBorders;
454         case AllRules:
455             return SolidBorders;
456         case ColsRules:
457             return SolidBordersColsOnly;
458         case RowsRules:
459             return SolidBordersRowsOnly;
460         case UnsetRules:
461             if (!m_borderAttr)
462                 return NoBorders;
463             if (m_borderColorAttr)
464                 return SolidBorders;
465             return InsetBorders;
466     }
467     ASSERT_NOT_REACHED();
468     return NoBorders;
469 }
470
471 PassRefPtr<StyleProperties> HTMLTableElement::createSharedCellStyle()
472 {
473     RefPtr<MutableStyleProperties> style = MutableStyleProperties::create();
474
475     switch (cellBorders()) {
476     case SolidBordersColsOnly:
477         style->setProperty(CSSPropertyBorderLeftWidth, CSSValueThin);
478         style->setProperty(CSSPropertyBorderRightWidth, CSSValueThin);
479         style->setProperty(CSSPropertyBorderLeftStyle, CSSValueSolid);
480         style->setProperty(CSSPropertyBorderRightStyle, CSSValueSolid);
481         style->setProperty(CSSPropertyBorderColor, cssValuePool().createInheritedValue());
482         break;
483     case SolidBordersRowsOnly:
484         style->setProperty(CSSPropertyBorderTopWidth, CSSValueThin);
485         style->setProperty(CSSPropertyBorderBottomWidth, CSSValueThin);
486         style->setProperty(CSSPropertyBorderTopStyle, CSSValueSolid);
487         style->setProperty(CSSPropertyBorderBottomStyle, CSSValueSolid);
488         style->setProperty(CSSPropertyBorderColor, cssValuePool().createInheritedValue());
489         break;
490     case SolidBorders:
491         style->setProperty(CSSPropertyBorderWidth, cssValuePool().createValue(1, CSSPrimitiveValue::CSS_PX));
492         style->setProperty(CSSPropertyBorderStyle, cssValuePool().createIdentifierValue(CSSValueSolid));
493         style->setProperty(CSSPropertyBorderColor, cssValuePool().createInheritedValue());
494         break;
495     case InsetBorders:
496         style->setProperty(CSSPropertyBorderWidth, cssValuePool().createValue(1, CSSPrimitiveValue::CSS_PX));
497         style->setProperty(CSSPropertyBorderStyle, cssValuePool().createIdentifierValue(CSSValueInset));
498         style->setProperty(CSSPropertyBorderColor, cssValuePool().createInheritedValue());
499         break;
500     case NoBorders:
501         // If 'rules=none' then allow any borders set at cell level to take effect. 
502         break;
503     }
504
505     if (m_padding)
506         style->setProperty(CSSPropertyPadding, cssValuePool().createValue(m_padding, CSSPrimitiveValue::CSS_PX));
507
508     return style.release();
509 }
510
511 const StyleProperties* HTMLTableElement::additionalCellStyle()
512 {
513     if (!m_sharedCellStyle)
514         m_sharedCellStyle = createSharedCellStyle();
515     return m_sharedCellStyle.get();
516 }
517
518 static StyleProperties* leakGroupBorderStyle(int rows)
519 {
520     RefPtr<MutableStyleProperties> style = MutableStyleProperties::create();
521     if (rows) {
522         style->setProperty(CSSPropertyBorderTopWidth, CSSValueThin);
523         style->setProperty(CSSPropertyBorderBottomWidth, CSSValueThin);
524         style->setProperty(CSSPropertyBorderTopStyle, CSSValueSolid);
525         style->setProperty(CSSPropertyBorderBottomStyle, CSSValueSolid);
526     } else {
527         style->setProperty(CSSPropertyBorderLeftWidth, CSSValueThin);
528         style->setProperty(CSSPropertyBorderRightWidth, CSSValueThin);
529         style->setProperty(CSSPropertyBorderLeftStyle, CSSValueSolid);
530         style->setProperty(CSSPropertyBorderRightStyle, CSSValueSolid);
531     }
532     return style.release().leakRef();
533 }
534
535 const StyleProperties* HTMLTableElement::additionalGroupStyle(bool rows)
536 {
537     if (m_rulesAttr != GroupsRules)
538         return 0;
539
540     if (rows) {
541         static StyleProperties* rowBorderStyle = leakGroupBorderStyle(true);
542         return rowBorderStyle;
543     }
544     static StyleProperties* columnBorderStyle = leakGroupBorderStyle(false);
545     return columnBorderStyle;
546 }
547
548 bool HTMLTableElement::isURLAttribute(const Attribute& attribute) const
549 {
550     return attribute.name() == backgroundAttr || HTMLElement::isURLAttribute(attribute);
551 }
552
553 PassRefPtr<HTMLCollection> HTMLTableElement::rows()
554 {
555     return ensureCachedHTMLCollection(TableRows);
556 }
557
558 PassRefPtr<HTMLCollection> HTMLTableElement::tBodies()
559 {
560     return ensureCachedHTMLCollection(TableTBodies);
561 }
562
563 String HTMLTableElement::rules() const
564 {
565     return getAttribute(rulesAttr);
566 }
567
568 String HTMLTableElement::summary() const
569 {
570     return getAttribute(summaryAttr);
571 }
572
573 void HTMLTableElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const
574 {
575     HTMLElement::addSubresourceAttributeURLs(urls);
576
577     addSubresourceURL(urls, document().completeURL(getAttribute(backgroundAttr)));
578 }
579
580 }