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