• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
WMLTableElement(const QualifiedName & tagName,Document * doc)42 WMLTableElement::WMLTableElement(const QualifiedName& tagName, Document* doc)
43     : WMLElement(tagName, doc)
44     , m_columns(0)
45 {
46 }
47 
~WMLTableElement()48 WMLTableElement::~WMLTableElement()
49 {
50 }
51 
mapToEntry(const QualifiedName & attrName,MappedAttributeEntry & result) const52 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 
parseMappedAttribute(MappedAttribute * attr)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 
finishParsingChildren()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 
scanTableChildElements(WMLElement * startElement,const QualifiedName & tagName) const115 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 
transferAllChildrenOfElementToTargetElement(WMLElement * sourceElement,WMLElement * targetElement,unsigned startOffset) const133 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 
tryMergeAdjacentTextCells(Node * item,Node * nextItem) const149 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 
joinSuperflousColumns(Vector<WMLElement * > & columnElements,WMLElement * rowElement) const170 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 = new WMLElement(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 
padWithEmptyColumns(Vector<WMLElement * > & columnElements,WMLElement * rowElement) const208 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 = new WMLElement(tdTag, document());
217         rowElement->appendChild(newCell.release(), ec);
218         ASSERT(ec == 0);
219     }
220 }
221 
alignCells(Vector<WMLElement * > & columnElements,WMLElement * rowElement) const222 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