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
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
create(const QualifiedName & tagName,Document * document)48 PassRefPtr<WMLTableElement> WMLTableElement::create(const QualifiedName& tagName, Document* document)
49 {
50 return adoptRef(new WMLTableElement(tagName, document));
51 }
52
~WMLTableElement()53 WMLTableElement::~WMLTableElement()
54 {
55 }
56
mapToEntry(const QualifiedName & attrName,MappedAttributeEntry & result) const57 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
parseMappedAttribute(Attribute * attr)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
finishParsingChildren()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
scanTableChildElements(WMLElement * startElement,const QualifiedName & tagName) const120 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
transferAllChildrenOfElementToTargetElement(WMLElement * sourceElement,WMLElement * targetElement,unsigned startOffset) const138 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
tryMergeAdjacentTextCells(Node * item,Node * nextItem) const154 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
joinSuperflousColumns(Vector<WMLElement * > & columnElements,WMLElement * rowElement) const175 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
padWithEmptyColumns(Vector<WMLElement * > & columnElements,WMLElement * rowElement) const213 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
alignCells(Vector<WMLElement * > & columnElements,WMLElement * rowElement) const227 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