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