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 "Attribute.h"
     27 #include "CSSPropertyNames.h"
     28 #include "CSSValueKeywords.h"
     29 #include "Document.h"
     30 #include "HTMLNames.h"
     31 #include "NodeList.h"
     32 #include "RenderObject.h"
     33 #include "Text.h"
     34 #include "WMLErrorHandling.h"
     35 #include "WMLNames.h"
     36 #include <wtf/unicode/CharacterNames.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 PassRefPtr<WMLTableElement> WMLTableElement::create(const QualifiedName& tagName, Document* document)
     49 {
     50     return adoptRef(new WMLTableElement(tagName, document));
     51 }
     52 
     53 WMLTableElement::~WMLTableElement()
     54 {
     55 }
     56 
     57 bool WMLTableElement::mapToEntry(const QualifiedName& attrName, MappedAttributeEntry& result) const
     58 {
     59     if (attrName == HTMLNames::alignAttr) {
     60         result = eTable;
     61         return false;
     62     }
     63 
     64     return WMLElement::mapToEntry(attrName, result);
     65 }
     66 
     67 void WMLTableElement::parseMappedAttribute(Attribute* attr)
     68 {
     69     if (attr->name() == columnsAttr) {
     70         bool isNumber = false;
     71         m_columns = attr->value().string().toUIntStrict(&isNumber);
     72 
     73         // Spec: This required attribute specifies the number of columns for the table.
     74         // The user agent must create a table with exactly the number of columns specified
     75         // by the attribute value. It is an error to specify a value of zero ("0")
     76         if (!m_columns || !isNumber)
     77             reportWMLError(document(), WMLErrorInvalidColumnsNumberInTable);
     78     } else if (attr->name() == HTMLNames::alignAttr)
     79         m_alignment = parseValueForbiddingVariableReferences(attr->value());
     80     else
     81         WMLElement::parseMappedAttribute(attr);
     82 }
     83 
     84 void WMLTableElement::finishParsingChildren()
     85 {
     86     WMLElement::finishParsingChildren();
     87 
     88     if (!m_columns) {
     89         reportWMLError(document(), WMLErrorInvalidColumnsNumberInTable);
     90         return;
     91     }
     92 
     93     Vector<WMLElement*> rowElements = scanTableChildElements(this, trTag);
     94     if (rowElements.isEmpty())
     95         return;
     96 
     97     Vector<WMLElement*>::iterator it = rowElements.begin();
     98     Vector<WMLElement*>::iterator end = rowElements.end();
     99 
    100     for (; it != end; ++it) {
    101         WMLElement* rowElement = (*it);
    102 
    103         // Squeeze the table to fit in the desired number of columns
    104         Vector<WMLElement*> columnElements = scanTableChildElements(rowElement, tdTag);
    105         unsigned actualNumberOfColumns = columnElements.size();
    106 
    107         if (actualNumberOfColumns > m_columns) {
    108             joinSuperflousColumns(columnElements, rowElement);
    109             columnElements = scanTableChildElements(rowElement, tdTag);
    110         } else if (actualNumberOfColumns < m_columns) {
    111             padWithEmptyColumns(columnElements, rowElement);
    112             columnElements = scanTableChildElements(rowElement, tdTag);
    113         }
    114 
    115         // Layout cells according to the 'align' attribute
    116         alignCells(columnElements, rowElement);
    117     }
    118 }
    119 
    120 Vector<WMLElement*> WMLTableElement::scanTableChildElements(WMLElement* startElement, const QualifiedName& tagName) const
    121 {
    122     Vector<WMLElement*> childElements;
    123 
    124     RefPtr<NodeList> children = startElement->childNodes();
    125     if (!children)
    126         return childElements;
    127 
    128     unsigned length = children->length();
    129     for (unsigned i = 0; i < length; ++i) {
    130         Node* child = children->item(i);
    131         if (child->hasTagName(tagName))
    132             childElements.append(static_cast<WMLElement*>(child));
    133     }
    134 
    135     return childElements;
    136 }
    137 
    138 void WMLTableElement::transferAllChildrenOfElementToTargetElement(WMLElement* sourceElement, WMLElement* targetElement, unsigned startOffset) const
    139 {
    140     RefPtr<NodeList> children = sourceElement->childNodes();
    141     if (!children)
    142         return;
    143 
    144     ExceptionCode ec = 0;
    145 
    146     unsigned length = children->length();
    147     for (unsigned i = startOffset; i < length; ++i) {
    148         RefPtr<Node> clonedNode = children->item(i)->cloneNode(true);
    149         targetElement->appendChild(clonedNode.release(), ec);
    150         ASSERT(ec == 0);
    151     }
    152 }
    153 
    154 bool WMLTableElement::tryMergeAdjacentTextCells(Node* item, Node* nextItem) const
    155 {
    156     if (!item || !nextItem)
    157         return false;
    158 
    159     if (!item->isTextNode() || !nextItem->isTextNode())
    160         return false;
    161 
    162     Text* itemText = static_cast<Text*>(item);
    163     Text* nextItemText = static_cast<Text*>(nextItem);
    164 
    165     String newContent = " ";
    166     newContent += nextItemText->data();
    167 
    168     ExceptionCode ec = 0;
    169     itemText->appendData(newContent, ec);
    170     ASSERT(ec == 0);
    171 
    172     return true;
    173 }
    174 
    175 void WMLTableElement::joinSuperflousColumns(Vector<WMLElement*>& columnElements, WMLElement* rowElement) const
    176 {
    177     // Spec: If the actual number of columns in a row is greater than the value specified
    178     // by this attribute, the extra columns of the row must be aggregated into the last
    179     // column such that the row contains exactly the number of columns specified.
    180     WMLElement* lastColumn = columnElements.at(m_columns - 1);
    181     ASSERT(lastColumn);
    182 
    183     // Merge superflous columns into a single one
    184     RefPtr<WMLElement> newCell = WMLElement::create(tdTag, document());
    185     transferAllChildrenOfElementToTargetElement(lastColumn, newCell.get(), 0);
    186 
    187     ExceptionCode ec = 0;
    188     unsigned actualNumberOfColumns = columnElements.size();
    189 
    190     for (unsigned i = m_columns; i < actualNumberOfColumns; ++i) {
    191         WMLElement* columnElement = columnElements.at(i);
    192         unsigned startOffset = 0;
    193 
    194         // Spec: A single inter-word space must be inserted between two cells that are being aggregated.
    195         if (tryMergeAdjacentTextCells(newCell->lastChild(), columnElement->firstChild()))
    196             ++startOffset;
    197 
    198         transferAllChildrenOfElementToTargetElement(columnElement, newCell.get(), startOffset);
    199     }
    200 
    201     // Remove the columns, that have just been merged
    202     unsigned i = actualNumberOfColumns;
    203     for (; i > m_columns; --i) {
    204         rowElement->removeChild(columnElements.at(i - 1), ec);
    205         ASSERT(ec == 0);
    206     }
    207 
    208     // Replace the last column in the row with the new merged column
    209     rowElement->replaceChild(newCell.release(), lastColumn, ec);
    210     ASSERT(ec == 0);
    211 }
    212 
    213 void WMLTableElement::padWithEmptyColumns(Vector<WMLElement*>& columnElements, WMLElement* rowElement) const
    214 {
    215     // Spec: If the actual number of columns in a row is less than the value specified by the columns
    216     // attribute, the row must be padded with empty columns effectively as if the user agent
    217     // appended empty td elements to the row.
    218     ExceptionCode ec = 0;
    219 
    220     for (unsigned i = columnElements.size(); i < m_columns; ++i) {
    221         RefPtr<WMLElement> newCell = WMLElement::create(tdTag, document());
    222         rowElement->appendChild(newCell.release(), ec);
    223         ASSERT(ec == 0);
    224     }
    225 }
    226 
    227 void WMLTableElement::alignCells(Vector<WMLElement*>& columnElements, WMLElement* rowElement) const
    228 {
    229     // Spec: User agents should consider the current language when determining
    230     // the default alignment and the direction of the table.
    231     bool rtl = false;
    232     if (RenderObject* renderer = rowElement->renderer()) {
    233         if (RenderStyle* style = renderer->style())
    234             rtl = !style->isLeftToRightDirection();
    235     }
    236 
    237     rowElement->setAttribute(HTMLNames::alignAttr, rtl ? "right" : "left");
    238 
    239     if (m_alignment.isEmpty())
    240         return;
    241 
    242     unsigned alignLength = m_alignment.length();
    243 
    244     Vector<WMLElement*>::iterator it = columnElements.begin();
    245     Vector<WMLElement*>::iterator end = columnElements.end();
    246 
    247     for (unsigned i = 0; it != end; ++it, ++i) {
    248         if (i == alignLength)
    249             break;
    250 
    251         String alignmentValue;
    252         switch (m_alignment[i]) {
    253         case 'C':
    254             alignmentValue = "center";
    255             break;
    256         case 'L':
    257             alignmentValue = "left";
    258             break;
    259         case 'R':
    260             alignmentValue = "right";
    261             break;
    262         default:
    263             break;
    264         }
    265 
    266         if (alignmentValue.isEmpty())
    267             continue;
    268 
    269         (*it)->setAttribute(HTMLNames::alignAttr, alignmentValue);
    270     }
    271 }
    272 
    273 }
    274 
    275 #endif
    276