Home | History | Annotate | Download | only in wml
      1 /**
      2  * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
      3  *
      4  * This library is free software; you can redistribute it and/or
      5  * modify it under the terms of the GNU Library General Public
      6  * License as published by the Free Software Foundation; either
      7  * version 2 of the License, or (at your option) any later version.
      8  *
      9  * This library is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     12  * Library General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU Library General Public License
     15  * along with this library; see the file COPYING.LIB.  If not, write to
     16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     17  * Boston, MA 02110-1301, USA.
     18  *
     19  */
     20 
     21 #include "config.h"
     22 
     23 #if ENABLE(WML)
     24 #include "WMLTableElement.h"
     25 
     26 #include "CharacterNames.h"
     27 #include "CSSPropertyNames.h"
     28 #include "CSSValueKeywords.h"
     29 #include "Document.h"
     30 #include "HTMLNames.h"
     31 #include "MappedAttribute.h"
     32 #include "NodeList.h"
     33 #include "RenderObject.h"
     34 #include "Text.h"
     35 #include "WMLErrorHandling.h"
     36 #include "WMLNames.h"
     37 
     38 namespace WebCore {
     39 
     40 using namespace WMLNames;
     41 
     42 WMLTableElement::WMLTableElement(const QualifiedName& tagName, Document* doc)
     43     : WMLElement(tagName, doc)
     44     , m_columns(0)
     45 {
     46 }
     47 
     48 WMLTableElement::~WMLTableElement()
     49 {
     50 }
     51 
     52 bool WMLTableElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const
     53 {
     54     if (attrName == HTMLNames::alignAttr) {
     55         result = eTable;
     56         return false;
     57     }
     58 
     59     return WMLElement::mapToEntry(attrName, result);
     60 }
     61 
     62 void WMLTableElement::parseMappedAttribute(MappedAttribute* attr)
     63 {
     64     if (attr->name() == columnsAttr) {
     65         bool isNumber = false;
     66         m_columns = attr->value().string().toUIntStrict(&isNumber);
     67 
     68         // Spec: This required attribute specifies the number of columns for the table.
     69         // The user agent must create a table with exactly the number of columns specified
     70         // by the attribute value. It is an error to specify a value of zero ("0")
     71         if (!m_columns || !isNumber)
     72             reportWMLError(document(), WMLErrorInvalidColumnsNumberInTable);
     73     } else if (attr->name() == HTMLNames::alignAttr)
     74         m_alignment = parseValueForbiddingVariableReferences(attr->value());
     75     else
     76         WMLElement::parseMappedAttribute(attr);
     77 }
     78 
     79 void WMLTableElement::finishParsingChildren()
     80 {
     81     WMLElement::finishParsingChildren();
     82 
     83     if (!m_columns) {
     84         reportWMLError(document(), WMLErrorInvalidColumnsNumberInTable);
     85         return;
     86     }
     87 
     88     Vector<WMLElement*> rowElements = scanTableChildElements(this, trTag);
     89     if (rowElements.isEmpty())
     90         return;
     91 
     92     Vector<WMLElement*>::iterator it = rowElements.begin();
     93     Vector<WMLElement*>::iterator end = rowElements.end();
     94 
     95     for (; it != end; ++it) {
     96         WMLElement* rowElement = (*it);
     97 
     98         // Squeeze the table to fit in the desired number of columns
     99         Vector<WMLElement*> columnElements = scanTableChildElements(rowElement, tdTag);
    100         unsigned actualNumberOfColumns = columnElements.size();
    101 
    102         if (actualNumberOfColumns > m_columns) {
    103             joinSuperflousColumns(columnElements, rowElement);
    104             columnElements = scanTableChildElements(rowElement, tdTag);
    105         } else if (actualNumberOfColumns < m_columns) {
    106             padWithEmptyColumns(columnElements, rowElement);
    107             columnElements = scanTableChildElements(rowElement, tdTag);
    108         }
    109 
    110         // Layout cells according to the 'align' attribute
    111         alignCells(columnElements, rowElement);
    112     }
    113 }
    114 
    115 Vector<WMLElement*> WMLTableElement::scanTableChildElements(WMLElement* startElement, const QualifiedName& tagName) const
    116 {
    117     Vector<WMLElement*> childElements;
    118 
    119     RefPtr<NodeList> children = startElement->childNodes();
    120     if (!children)
    121         return childElements;
    122 
    123     unsigned length = children->length();
    124     for (unsigned i = 0; i < length; ++i) {
    125         Node* child = children->item(i);
    126         if (child->hasTagName(tagName))
    127             childElements.append(static_cast<WMLElement*>(child));
    128     }
    129 
    130     return childElements;
    131 }
    132 
    133 void WMLTableElement::transferAllChildrenOfElementToTargetElement(WMLElement* sourceElement, WMLElement* targetElement, unsigned startOffset) const
    134 {
    135     RefPtr<NodeList> children = sourceElement->childNodes();
    136     if (!children)
    137         return;
    138 
    139     ExceptionCode ec = 0;
    140 
    141     unsigned length = children->length();
    142     for (unsigned i = startOffset; i < length; ++i) {
    143         RefPtr<Node> clonedNode = children->item(i)->cloneNode(true);
    144         targetElement->appendChild(clonedNode.release(), ec);
    145         ASSERT(ec == 0);
    146     }
    147 }
    148 
    149 bool WMLTableElement::tryMergeAdjacentTextCells(Node* item, Node* nextItem) const
    150 {
    151     if (!item || !nextItem)
    152         return false;
    153 
    154     if (!item->isTextNode() || !nextItem->isTextNode())
    155         return false;
    156 
    157     Text* itemText = static_cast<Text*>(item);
    158     Text* nextItemText = static_cast<Text*>(nextItem);
    159 
    160     String newContent = " ";
    161     newContent += nextItemText->data();
    162 
    163     ExceptionCode ec = 0;
    164     itemText->appendData(newContent, ec);
    165     ASSERT(ec == 0);
    166 
    167     return true;
    168 }
    169 
    170 void WMLTableElement::joinSuperflousColumns(Vector<WMLElement*>& columnElements, WMLElement* rowElement) const
    171 {
    172     // Spec: If the actual number of columns in a row is greater than the value specified
    173     // by this attribute, the extra columns of the row must be aggregated into the last
    174     // column such that the row contains exactly the number of columns specified.
    175     WMLElement* lastColumn = columnElements.at(m_columns - 1);
    176     ASSERT(lastColumn);
    177 
    178     // Merge superflous columns into a single one
    179     RefPtr<WMLElement> newCell = WMLElement::create(tdTag, document());
    180     transferAllChildrenOfElementToTargetElement(lastColumn, newCell.get(), 0);
    181 
    182     ExceptionCode ec = 0;
    183     unsigned actualNumberOfColumns = columnElements.size();
    184 
    185     for (unsigned i = m_columns; i < actualNumberOfColumns; ++i) {
    186         WMLElement* columnElement = columnElements.at(i);
    187         unsigned startOffset = 0;
    188 
    189         // Spec: A single inter-word space must be inserted between two cells that are being aggregated.
    190         if (tryMergeAdjacentTextCells(newCell->lastChild(), columnElement->firstChild()))
    191             ++startOffset;
    192 
    193         transferAllChildrenOfElementToTargetElement(columnElement, newCell.get(), startOffset);
    194     }
    195 
    196     // Remove the columns, that have just been merged
    197     unsigned i = actualNumberOfColumns;
    198     for (; i > m_columns; --i) {
    199         rowElement->removeChild(columnElements.at(i - 1), ec);
    200         ASSERT(ec == 0);
    201     }
    202 
    203     // Replace the last column in the row with the new merged column
    204     rowElement->replaceChild(newCell.release(), lastColumn, ec);
    205     ASSERT(ec == 0);
    206 }
    207 
    208 void WMLTableElement::padWithEmptyColumns(Vector<WMLElement*>& columnElements, WMLElement* rowElement) const
    209 {
    210     // Spec: If the actual number of columns in a row is less than the value specified by the columns
    211     // attribute, the row must be padded with empty columns effectively as if the user agent
    212     // appended empty td elements to the row.
    213     ExceptionCode ec = 0;
    214 
    215     for (unsigned i = columnElements.size(); i < m_columns; ++i) {
    216         RefPtr<WMLElement> newCell = WMLElement::create(tdTag, document());
    217         rowElement->appendChild(newCell.release(), ec);
    218         ASSERT(ec == 0);
    219     }
    220 }
    221 
    222 void WMLTableElement::alignCells(Vector<WMLElement*>& columnElements, WMLElement* rowElement) const
    223 {
    224     // Spec: User agents should consider the current language when determining
    225     // the default alignment and the direction of the table.
    226     bool rtl = false;
    227     if (RenderObject* renderer = rowElement->renderer()) {
    228         if (RenderStyle* style = renderer->style())
    229             rtl = style->direction() == RTL;
    230     }
    231 
    232     rowElement->setAttribute(HTMLNames::alignAttr, rtl ? "right" : "left");
    233 
    234     if (m_alignment.isEmpty())
    235         return;
    236 
    237     unsigned alignLength = m_alignment.length();
    238 
    239     Vector<WMLElement*>::iterator it = columnElements.begin();
    240     Vector<WMLElement*>::iterator end = columnElements.end();
    241 
    242     for (unsigned i = 0; it != end; ++it, ++i) {
    243         if (i == alignLength)
    244             break;
    245 
    246         String alignmentValue;
    247         switch (m_alignment[i]) {
    248         case 'C':
    249             alignmentValue = "center";
    250             break;
    251         case 'L':
    252             alignmentValue = "left";
    253             break;
    254         case 'R':
    255             alignmentValue = "right";
    256             break;
    257         default:
    258             break;
    259         }
    260 
    261         if (alignmentValue.isEmpty())
    262             continue;
    263 
    264         (*it)->setAttribute(HTMLNames::alignAttr, alignmentValue);
    265     }
    266 }
    267 
    268 }
    269 
    270 #endif
    271