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