View Javadoc
1   /*
2    * Copyright (c) 2002-2017 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package com.gargoylesoftware.htmlunit.javascript.host.html;
16  
17  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_TABLE_VALIGN_SUPPORTS_IE_VALUES;
18  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
19  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
20  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
21  import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.IE;
22  
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.List;
26  import java.util.Locale;
27  
28  import com.gargoylesoftware.htmlunit.html.DomNode;
29  import com.gargoylesoftware.htmlunit.html.HtmlElement;
30  import com.gargoylesoftware.htmlunit.html.HtmlTable;
31  import com.gargoylesoftware.htmlunit.html.HtmlTableRow;
32  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
33  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
34  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
35  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter;
36  import com.gargoylesoftware.htmlunit.javascript.configuration.JsxSetter;
37  
38  import net.sourceforge.htmlunit.corejs.javascript.Context;
39  
40  /**
41   * The JavaScript object {@code HTMLTableElement}.
42   *
43   * @author David D. Kilzer
44   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
45   * @author Daniel Gredler
46   * @author Chris Erskine
47   * @author Marc Guillemot
48   * @author Ahmed Ashour
49   * @author Ronald Brill
50   * @author Frank Danek
51   */
52  @JsxClass(domClass = HtmlTable.class)
53  public class HTMLTableElement extends RowContainer {
54  
55      private static final List<String> VALID_RULES_ = Arrays.asList("none", "groups", "rows", "cols");
56  
57      /**
58       * Creates an instance.
59       */
60      @JsxConstructor({CHROME, FF, EDGE})
61      public HTMLTableElement() {
62      }
63  
64      /**
65       * Returns the table's caption element, or {@code null} if none exists. If more than one
66       * caption is declared in the table, this method returns the first one.
67       * @return the table's caption element
68       */
69      @JsxGetter
70      public Object getCaption() {
71          final List<HtmlElement> captions = getDomNodeOrDie().getElementsByTagName("caption");
72          if (captions.isEmpty()) {
73              return null;
74          }
75          return getScriptableFor(captions.get(0));
76      }
77  
78      /**
79       * Sets the caption.
80       * @param o the caption
81       */
82      @JsxSetter
83      public void setCaption(final Object o) {
84          if (!(o instanceof HTMLTableCaptionElement)) {
85              throw Context.reportRuntimeError("Not a caption");
86          }
87  
88          // remove old caption (if any)
89          deleteCaption();
90  
91          final HTMLTableCaptionElement caption = (HTMLTableCaptionElement) o;
92          getDomNodeOrDie().appendChild(caption.getDomNodeOrDie());
93      }
94  
95      /**
96       * Returns the table's tfoot element, or {@code null} if none exists. If more than one
97       * tfoot is declared in the table, this method returns the first one.
98       * @return the table's tfoot element
99       */
100     @JsxGetter
101     public Object getTFoot() {
102         final List<HtmlElement> tfoots = getDomNodeOrDie().getElementsByTagName("tfoot");
103         if (tfoots.isEmpty()) {
104             return null;
105         }
106         return getScriptableFor(tfoots.get(0));
107     }
108 
109     /**
110      * Sets the tFoot.
111      * @param o the tFoot
112      */
113     @JsxSetter
114     public void setTFoot(final Object o) {
115         if (!(o instanceof HTMLTableSectionElement
116             && "TFOOT".equals(((HTMLTableSectionElement) o).getTagName()))) {
117             throw Context.reportRuntimeError("Not a tFoot");
118         }
119 
120         // remove old caption (if any)
121         deleteTFoot();
122 
123         final HTMLTableSectionElement tfoot = (HTMLTableSectionElement) o;
124         getDomNodeOrDie().appendChild(tfoot.getDomNodeOrDie());
125     }
126 
127     /**
128      * Returns the table's thead element, or {@code null} if none exists. If more than one
129      * thead is declared in the table, this method returns the first one.
130      * @return the table's thead element
131      */
132     @JsxGetter
133     public Object getTHead() {
134         final List<HtmlElement> theads = getDomNodeOrDie().getElementsByTagName("thead");
135         if (theads.isEmpty()) {
136             return null;
137         }
138         return getScriptableFor(theads.get(0));
139     }
140 
141     /**
142      * Sets the {@code tHead}.
143      * @param o the {@code tHead}
144      */
145     @JsxSetter
146     public void setTHead(final Object o) {
147         if (!(o instanceof HTMLTableSectionElement
148             && "THEAD".equals(((HTMLTableSectionElement) o).getTagName()))) {
149             throw Context.reportRuntimeError("Not a tHead");
150         }
151 
152         // remove old caption (if any)
153         deleteTHead();
154 
155         final HTMLTableSectionElement thead = (HTMLTableSectionElement) o;
156         getDomNodeOrDie().appendChild(thead.getDomNodeOrDie());
157     }
158 
159     /**
160      * Returns the tbody's in the table.
161      * @return the tbody's in the table
162      */
163     @JsxGetter
164     public Object getTBodies() {
165         final HtmlTable table = (HtmlTable) getDomNodeOrDie();
166         return new HTMLCollection(table, false) {
167             @Override
168             protected List<DomNode> computeElements() {
169                 return new ArrayList<>(table.getBodies());
170             }
171         };
172     }
173 
174     /**
175      * If this table does not have a caption, this method creates an empty table caption,
176      * adds it to the table and then returns it. If one or more captions already exist,
177      * this method returns the first existing caption.
178      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536381.aspx">MSDN Documentation</a>
179      * @return a newly added caption if no caption exists, or the first existing caption
180      */
181     @JsxFunction
182     public Object createCaption() {
183         return getScriptableFor(getDomNodeOrDie().appendChildIfNoneExists("caption"));
184     }
185 
186     /**
187      * If this table does not have a tfoot element, this method creates an empty tfoot
188      * element, adds it to the table and then returns it. If this table already has a
189      * tfoot element, this method returns the existing tfoot element.
190      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536402.aspx">MSDN Documentation</a>
191      * @return a newly added caption if no caption exists, or the first existing caption
192      */
193     @JsxFunction
194     public Object createTFoot() {
195         return getScriptableFor(getDomNodeOrDie().appendChildIfNoneExists("tfoot"));
196     }
197 
198     /**
199      * If this table does not have a tbody element, this method creates an empty tbody
200      * element, adds it to the table and then returns it. If this table already has a
201      * tbody element, this method returns the existing tbody element.
202      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536402.aspx">MSDN Documentation</a>
203      * @return a newly added caption if no caption exists, or the first existing caption
204      */
205     @JsxFunction
206     public Object createTBody() {
207         return getScriptableFor(getDomNodeOrDie().appendChildIfNoneExists("tbody"));
208     }
209 
210     /**
211      * If this table does not have a thead element, this method creates an empty
212      * thead element, adds it to the table and then returns it. If this table
213      * already has a thead element, this method returns the existing thead element.
214      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536403.aspx">MSDN Documentation</a>
215      * @return a newly added caption if no caption exists, or the first existing caption
216      */
217     @JsxFunction
218     public Object createTHead() {
219         return getScriptableFor(getDomNodeOrDie().appendChildIfNoneExists("thead"));
220     }
221 
222     /**
223      * Deletes this table's caption. If the table has multiple captions, this method
224      * deletes only the first caption. If this table does not have any captions, this
225      * method does nothing.
226      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536405.aspx">MSDN Documentation</a>
227      */
228     @JsxFunction
229     public void deleteCaption() {
230         getDomNodeOrDie().removeChild("caption", 0);
231     }
232 
233     /**
234      * Deletes this table's tfoot element. If the table has multiple tfoot elements, this
235      * method deletes only the first tfoot element. If this table does not have any tfoot
236      * elements, this method does nothing.
237      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536409.aspx">MSDN Documentation</a>
238      */
239     @JsxFunction
240     public void deleteTFoot() {
241         getDomNodeOrDie().removeChild("tfoot", 0);
242     }
243 
244     /**
245      * Deletes this table's thead element. If the table has multiple thead elements, this
246      * method deletes only the first thead element. If this table does not have any thead
247      * elements, this method does nothing.
248      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536410.aspx">MSDN Documentation</a>
249      */
250     @JsxFunction
251     public void deleteTHead() {
252         getDomNodeOrDie().removeChild("thead", 0);
253     }
254 
255     /**
256      * Indicates if the row belongs to this container.
257      * @param row the row to test
258      * @return {@code true} if it belongs to this container
259      */
260     @Override
261     protected boolean isContainedRow(final HtmlTableRow row) {
262         final DomNode parent = row.getParentNode(); // the tbody, thead or tfoo
263         return (parent != null) && parent.getParentNode() == getDomNodeOrDie();
264     }
265 
266     /**
267      * Handle special case where table is empty.
268      * {@inheritDoc}
269      */
270     @Override
271     public Object insertRow(final int index) {
272         // check if a tbody should be created
273         final List<?> rowContainers =
274             getDomNodeOrDie().getByXPath("//tbody | //thead | //tfoot");
275         if (rowContainers.isEmpty() || index == 0) {
276             final HtmlElement tBody = getDomNodeOrDie().appendChildIfNoneExists("tbody");
277             return ((RowContainer) getScriptableFor(tBody)).insertRow(0);
278         }
279         return super.insertRow(index);
280     }
281 
282     /**
283      * Returns the {@code width} property.
284      * @return the {@code width} property
285      */
286     @JsxGetter(propertyName = "width")
287     public String getWidth_js() {
288         return getDomNodeOrDie().getAttribute("width");
289     }
290 
291     /**
292      * Sets the {@code width} property.
293      * @param width the {@code width} property
294      */
295     @JsxSetter
296     public void setWidth(final String width) {
297         getDomNodeOrDie().setAttribute("width", width);
298     }
299 
300     /**
301      * Returns the {@code cellSpacing} property.
302      * @return the {@code cellSpacing} property
303      */
304     @JsxGetter
305     public String getCellSpacing() {
306         return getDomNodeOrDie().getAttribute("cellspacing");
307     }
308 
309     /**
310      * Sets the {@code cellSpacing} property.
311      * @param cellSpacing the {@code cellSpacing} property
312      */
313     @JsxSetter
314     public void setCellSpacing(final String cellSpacing) {
315         getDomNodeOrDie().setAttribute("cellspacing", cellSpacing);
316     }
317 
318     /**
319      * Returns the {@code cellPadding} property.
320      * @return the {@code cellPadding} property
321      */
322     @JsxGetter
323     public String getCellPadding() {
324         return getDomNodeOrDie().getAttribute("cellpadding");
325     }
326 
327     /**
328      * Sets the {@code cellPadding} property.
329      * @param cellPadding the {@code cellPadding} property
330      */
331     @JsxSetter
332     public void setCellPadding(final String cellPadding) {
333         getDomNodeOrDie().setAttribute("cellpadding", cellPadding);
334     }
335 
336     /**
337      * Gets the {@code border} property.
338      * @return the {@code border} property
339      */
340     @JsxGetter
341     public String getBorder() {
342         final String border = getDomNodeOrDie().getAttribute("border");
343         return border;
344     }
345 
346     /**
347      * Sets the {@code border} property.
348      * @param border the {@code border} property
349      */
350     @JsxSetter
351     public void setBorder(final String border) {
352         getDomNodeOrDie().setAttribute("border", border);
353     }
354 
355     /**
356      * Returns the value of the {@code bgColor} property.
357      * @return the value of the {@code bgColor} property
358      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533505.aspx">MSDN Documentation</a>
359      */
360     @JsxGetter
361     public String getBgColor() {
362         return getDomNodeOrDie().getAttribute("bgColor");
363     }
364 
365     /**
366      * Sets the value of the {@code bgColor} property.
367      * @param bgColor the value of the {@code bgColor} property
368      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533505.aspx">MSDN Documentation</a>
369      */
370     @JsxSetter
371     public void setBgColor(final String bgColor) {
372         setColorAttribute("bgColor", bgColor);
373     }
374 
375     /**
376      * Gets the {@code borderColor} property.
377      * @return the property
378      */
379     @JsxGetter(IE)
380     public String getBorderColor() {
381         return getDomNodeOrDie().getAttribute("borderColor");
382     }
383 
384     /**
385      * Sets the {@code borderColor} property.
386      * @param borderColor the new property
387      */
388     @JsxSetter(IE)
389     public void setBorderColor(final String borderColor) {
390         setColorAttribute("borderColor", borderColor);
391     }
392 
393     /**
394      * Gets the {@code borderColor} property.
395      * @return the property
396      */
397     @JsxGetter(IE)
398     public String getBorderColorDark() {
399         return getDomNodeOrDie().getAttribute("borderColorDark");
400     }
401 
402     /**
403      * Sets the {@code borderColor} property.
404      * @param borderColor the new property
405      */
406     @JsxSetter(IE)
407     public void setBorderColorDark(final String borderColor) {
408         setColorAttribute("borderColorDark", borderColor);
409     }
410 
411     /**
412      * Gets the {@code borderColor} property.
413      * @return the property
414      */
415     @JsxGetter(IE)
416     public String getBorderColorLight() {
417         return getDomNodeOrDie().getAttribute("borderColorLight");
418     }
419 
420     /**
421      * Sets the {@code borderColor} property.
422      * @param borderColor the new property
423      */
424     @JsxSetter(IE)
425     public void setBorderColorLight(final String borderColor) {
426         setColorAttribute("borderColorLight", borderColor);
427     }
428 
429     /**
430      * {@inheritDoc}
431      */
432     @Override
433     public String getInnerText() {
434         return getDomNodeOrDie().asText();
435     }
436 
437     /**
438      * {@inheritDoc}
439      */
440     @Override
441     public Object appendChild(final Object childObject) {
442         final Object appendedChild = super.appendChild(childObject);
443         getWindow().clearComputedStyles(this);
444         return appendedChild;
445     }
446 
447     /**
448      * {@inheritDoc}
449      */
450     @Override
451     public Object removeChild(final Object childObject) {
452         final Object removedChild = super.removeChild(childObject);
453         getWindow().clearComputedStyles(this);
454         return removedChild;
455     }
456 
457     /**
458      * Gets the {@code summary} property.
459      * @return the property
460      */
461     @JsxGetter
462     public String getSummary() {
463         return getDomNodeOrDie().getAttribute("summary");
464     }
465 
466     /**
467      * Sets the {@code summary} property.
468      * @param summary the new property
469      */
470     @JsxSetter
471     public void setSummary(final String summary) {
472         setAttribute("summary", summary);
473     }
474 
475     /**
476      * Gets the {@code rules} property.
477      * @return the property
478      */
479     @JsxGetter
480     public String getRules() {
481         String rules = getDomNodeOrDie().getAttribute("rules");
482         if (getBrowserVersion().hasFeature(JS_TABLE_VALIGN_SUPPORTS_IE_VALUES)
483                 && !VALID_RULES_.contains(rules)) {
484             rules = "";
485         }
486         return rules;
487     }
488 
489     /**
490      * Sets the {@code rules} property.
491      * @param rules the new property
492      */
493     @JsxSetter
494     public void setRules(String rules) {
495         if (getBrowserVersion().hasFeature(JS_TABLE_VALIGN_SUPPORTS_IE_VALUES)) {
496             rules = rules.toLowerCase(Locale.ROOT);
497             if (!rules.isEmpty() && !VALID_RULES_.contains(rules)) {
498                 throw Context.throwAsScriptRuntimeEx(new Exception("Invalid argument"));
499             }
500         }
501         setAttribute("rules", rules);
502     }
503 
504 }