|
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 "CharacterNames.h" |
|
30 #include "Document.h" |
|
31 #include "HTMLNames.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(Attribute* 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 |