HTMLTableElement.cpp 11 KB

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