HTMLTableElement.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /*
  2. * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021, Adam Hodgen <ant1441@gmail.com>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibWeb/CSS/Parser/Parser.h>
  8. #include <LibWeb/DOM/ElementFactory.h>
  9. #include <LibWeb/DOM/HTMLCollection.h>
  10. #include <LibWeb/HTML/HTMLTableColElement.h>
  11. #include <LibWeb/HTML/HTMLTableElement.h>
  12. #include <LibWeb/HTML/HTMLTableRowElement.h>
  13. #include <LibWeb/Namespace.h>
  14. namespace Web::HTML {
  15. HTMLTableElement::HTMLTableElement(DOM::Document& document, DOM::QualifiedName qualified_name)
  16. : HTMLElement(document, move(qualified_name))
  17. {
  18. }
  19. HTMLTableElement::~HTMLTableElement() = default;
  20. void HTMLTableElement::apply_presentational_hints(CSS::StyleProperties& style) const
  21. {
  22. for_each_attribute([&](auto& name, auto& value) {
  23. if (name == HTML::AttributeNames::width) {
  24. if (auto parsed_value = parse_html_length(document(), value))
  25. style.set_property(CSS::PropertyID::Width, parsed_value.release_nonnull());
  26. return;
  27. }
  28. if (name == HTML::AttributeNames::height) {
  29. if (auto parsed_value = parse_html_length(document(), value))
  30. style.set_property(CSS::PropertyID::Height, parsed_value.release_nonnull());
  31. return;
  32. }
  33. if (name == HTML::AttributeNames::bgcolor) {
  34. auto color = Color::from_string(value);
  35. if (color.has_value())
  36. style.set_property(CSS::PropertyID::BackgroundColor, CSS::ColorStyleValue::create(color.value()));
  37. return;
  38. }
  39. });
  40. }
  41. RefPtr<HTMLTableCaptionElement> HTMLTableElement::caption()
  42. {
  43. return first_child_of_type<HTMLTableCaptionElement>();
  44. }
  45. void HTMLTableElement::set_caption(HTMLTableCaptionElement* caption)
  46. {
  47. // FIXME: This is not always the case, but this function is currently written in a way that assumes non-null.
  48. VERIFY(caption);
  49. // FIXME: The spec requires deleting the current caption if caption is null
  50. // Currently the wrapper generator doesn't send us a nullable value
  51. delete_caption();
  52. pre_insert(*caption, first_child());
  53. }
  54. NonnullRefPtr<HTMLTableCaptionElement> HTMLTableElement::create_caption()
  55. {
  56. auto maybe_caption = caption();
  57. if (maybe_caption) {
  58. return *maybe_caption;
  59. }
  60. auto caption = DOM::create_element(document(), TagNames::caption, Namespace::HTML);
  61. pre_insert(caption, first_child());
  62. return static_ptr_cast<HTMLTableCaptionElement>(caption);
  63. }
  64. void HTMLTableElement::delete_caption()
  65. {
  66. auto maybe_caption = caption();
  67. if (maybe_caption) {
  68. maybe_caption->remove(false);
  69. }
  70. }
  71. RefPtr<HTMLTableSectionElement> HTMLTableElement::t_head()
  72. {
  73. for (auto* child = first_child(); child; child = child->next_sibling()) {
  74. if (is<HTMLTableSectionElement>(*child)) {
  75. auto table_section_element = &verify_cast<HTMLTableSectionElement>(*child);
  76. if (table_section_element->local_name() == TagNames::thead)
  77. return table_section_element;
  78. }
  79. }
  80. return nullptr;
  81. }
  82. DOM::ExceptionOr<void> HTMLTableElement::set_t_head(HTMLTableSectionElement* thead)
  83. {
  84. // FIXME: This is not always the case, but this function is currently written in a way that assumes non-null.
  85. VERIFY(thead);
  86. if (thead->local_name() != TagNames::thead)
  87. return DOM::HierarchyRequestError::create("Element is not thead");
  88. // FIXME: The spec requires deleting the current thead if thead is null
  89. // Currently the wrapper generator doesn't send us a nullable value
  90. delete_t_head();
  91. // We insert the new thead after any <caption> or <colgroup> elements
  92. DOM::Node* child_to_append_after = nullptr;
  93. for (auto* child = first_child(); child; child = child->next_sibling()) {
  94. if (!is<HTMLElement>(*child))
  95. continue;
  96. if (is<HTMLTableCaptionElement>(*child))
  97. continue;
  98. if (is<HTMLTableColElement>(*child)) {
  99. auto table_col_element = &verify_cast<HTMLTableColElement>(*child);
  100. if (table_col_element->local_name() == TagNames::colgroup)
  101. continue;
  102. }
  103. // We have found an element which is not a <caption> or <colgroup>, we'll insert before this
  104. child_to_append_after = child;
  105. break;
  106. }
  107. pre_insert(*thead, child_to_append_after);
  108. return {};
  109. }
  110. NonnullRefPtr<HTMLTableSectionElement> HTMLTableElement::create_t_head()
  111. {
  112. auto maybe_thead = t_head();
  113. if (maybe_thead)
  114. return *maybe_thead;
  115. auto thead = DOM::create_element(document(), TagNames::thead, Namespace::HTML);
  116. // We insert the new thead after any <caption> or <colgroup> elements
  117. DOM::Node* child_to_append_after = nullptr;
  118. for (auto* child = first_child(); child; child = child->next_sibling()) {
  119. if (!is<HTMLElement>(*child))
  120. continue;
  121. if (is<HTMLTableCaptionElement>(*child))
  122. continue;
  123. if (is<HTMLTableColElement>(*child)) {
  124. auto table_col_element = &verify_cast<HTMLTableColElement>(*child);
  125. if (table_col_element->local_name() == TagNames::colgroup)
  126. continue;
  127. }
  128. // We have found an element which is not a <caption> or <colgroup>, we'll insert before this
  129. child_to_append_after = child;
  130. break;
  131. }
  132. pre_insert(thead, child_to_append_after);
  133. return static_ptr_cast<HTMLTableSectionElement>(thead);
  134. }
  135. void HTMLTableElement::delete_t_head()
  136. {
  137. auto maybe_thead = t_head();
  138. if (maybe_thead) {
  139. maybe_thead->remove(false);
  140. }
  141. }
  142. RefPtr<HTMLTableSectionElement> HTMLTableElement::t_foot()
  143. {
  144. for (auto* child = first_child(); child; child = child->next_sibling()) {
  145. if (is<HTMLTableSectionElement>(*child)) {
  146. auto table_section_element = &verify_cast<HTMLTableSectionElement>(*child);
  147. if (table_section_element->local_name() == TagNames::tfoot)
  148. return table_section_element;
  149. }
  150. }
  151. return nullptr;
  152. }
  153. DOM::ExceptionOr<void> HTMLTableElement::set_t_foot(HTMLTableSectionElement* tfoot)
  154. {
  155. // FIXME: This is not always the case, but this function is currently written in a way that assumes non-null.
  156. VERIFY(tfoot);
  157. if (tfoot->local_name() != TagNames::tfoot)
  158. return DOM::HierarchyRequestError::create("Element is not tfoot");
  159. // FIXME: The spec requires deleting the current tfoot if tfoot is null
  160. // Currently the wrapper generator doesn't send us a nullable value
  161. delete_t_foot();
  162. // We insert the new tfoot at the end of the table
  163. append_child(*tfoot);
  164. return {};
  165. }
  166. NonnullRefPtr<HTMLTableSectionElement> HTMLTableElement::create_t_foot()
  167. {
  168. auto maybe_tfoot = t_foot();
  169. if (maybe_tfoot)
  170. return *maybe_tfoot;
  171. auto tfoot = DOM::create_element(document(), TagNames::tfoot, Namespace::HTML);
  172. append_child(tfoot);
  173. return static_ptr_cast<HTMLTableSectionElement>(tfoot);
  174. }
  175. void HTMLTableElement::delete_t_foot()
  176. {
  177. auto maybe_tfoot = t_foot();
  178. if (maybe_tfoot) {
  179. maybe_tfoot->remove(false);
  180. }
  181. }
  182. NonnullRefPtr<DOM::HTMLCollection> HTMLTableElement::t_bodies()
  183. {
  184. return DOM::HTMLCollection::create(*this, [](DOM::Element const& element) {
  185. return element.local_name() == TagNames::tbody;
  186. });
  187. }
  188. NonnullRefPtr<HTMLTableSectionElement> HTMLTableElement::create_t_body()
  189. {
  190. auto tbody = DOM::create_element(document(), TagNames::tbody, Namespace::HTML);
  191. // We insert the new tbody after the last <tbody> element
  192. DOM::Node* child_to_append_after = nullptr;
  193. for (auto* child = last_child(); child; child = child->previous_sibling()) {
  194. if (!is<HTMLElement>(*child))
  195. continue;
  196. if (is<HTMLTableSectionElement>(*child)) {
  197. auto table_section_element = &verify_cast<HTMLTableSectionElement>(*child);
  198. if (table_section_element->local_name() == TagNames::tbody) {
  199. // We have found an element which is a <tbody> we'll insert after this
  200. child_to_append_after = child->next_sibling();
  201. break;
  202. }
  203. }
  204. }
  205. pre_insert(tbody, child_to_append_after);
  206. return static_ptr_cast<HTMLTableSectionElement>(tbody);
  207. }
  208. NonnullRefPtr<DOM::HTMLCollection> HTMLTableElement::rows()
  209. {
  210. HTMLTableElement* table_node = this;
  211. // FIXME: The elements in the collection must be ordered such that those elements whose parent is a thead are
  212. // included first, in tree order, followed by those elements whose parent is either a table or tbody
  213. // element, again in tree order, followed finally by those elements whose parent is a tfoot element,
  214. // still in tree order.
  215. // How do you sort HTMLCollection?
  216. return DOM::HTMLCollection::create(*this, [table_node](DOM::Element const& element) {
  217. // Only match TR elements which are:
  218. // * children of the table element
  219. // * children of the thead, tbody, or tfoot elements that are themselves children of the table element
  220. if (!is<HTMLTableRowElement>(element)) {
  221. return false;
  222. }
  223. if (element.parent_element() == table_node)
  224. return true;
  225. if (element.parent_element() && (element.parent_element()->local_name() == TagNames::thead || element.parent_element()->local_name() == TagNames::tbody || element.parent_element()->local_name() == TagNames::tfoot)
  226. && element.parent()->parent() == table_node) {
  227. return true;
  228. }
  229. return false;
  230. });
  231. }
  232. DOM::ExceptionOr<NonnullRefPtr<HTMLTableRowElement>> HTMLTableElement::insert_row(long index)
  233. {
  234. auto rows = this->rows();
  235. auto rows_length = rows->length();
  236. if (index < -1 || index > (long)rows_length) {
  237. return DOM::IndexSizeError::create("Index is negative or greater than the number of rows");
  238. }
  239. auto tr = static_ptr_cast<HTMLTableRowElement>(DOM::create_element(document(), TagNames::tr, Namespace::HTML));
  240. if (rows_length == 0 && !has_child_of_type<HTMLTableRowElement>()) {
  241. auto tbody = DOM::create_element(document(), TagNames::tbody, Namespace::HTML);
  242. tbody->append_child(tr);
  243. append_child(tbody);
  244. } else if (rows_length == 0) {
  245. auto tbody = last_child_of_type<HTMLTableRowElement>();
  246. tbody->append_child(tr);
  247. } else if (index == -1 || index == (long)rows_length) {
  248. auto parent_of_last_tr = rows->item(rows_length - 1)->parent_element();
  249. parent_of_last_tr->append_child(tr);
  250. } else {
  251. rows->item(index)->parent_element()->insert_before(tr, rows->item(index));
  252. }
  253. return tr;
  254. }
  255. // https://html.spec.whatwg.org/multipage/tables.html#dom-table-deleterow
  256. DOM::ExceptionOr<void> HTMLTableElement::delete_row(long index)
  257. {
  258. auto rows = this->rows();
  259. auto rows_length = rows->length();
  260. // 1. If index is less than −1 or greater than or equal to the number of elements in the rows collection, then throw an "IndexSizeError" DOMException.
  261. if (index < -1 || index >= (long)rows_length)
  262. return DOM::IndexSizeError::create("Index is negative or greater than or equal to the number of rows");
  263. // 2. If index is −1, then remove the last element in the rows collection from its parent, or do nothing if the rows collection is empty.
  264. if (index == -1) {
  265. if (rows_length == 0)
  266. return {};
  267. auto row_to_remove = rows->item(rows_length - 1);
  268. row_to_remove->remove(false);
  269. return {};
  270. }
  271. // 3. Otherwise, remove the indexth element in the rows collection from its parent.
  272. auto row_to_remove = rows->item(index);
  273. row_to_remove->remove(false);
  274. return {};
  275. }
  276. }