CSSParser.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this
  9. * list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include <AK/HashMap.h>
  27. #include <LibWeb/CSS/PropertyID.h>
  28. #include <LibWeb/CSS/StyleSheet.h>
  29. #include <LibWeb/Parser/CSSParser.h>
  30. #include <ctype.h>
  31. #include <stdio.h>
  32. #include <stdlib.h>
  33. #define PARSE_ASSERT(x) \
  34. if (!(x)) { \
  35. dbg() << "CSS PARSER ASSERTION FAILED: " << #x; \
  36. dbg() << "At character# " << index << " in CSS: _" << css << "_"; \
  37. ASSERT_NOT_REACHED(); \
  38. }
  39. namespace Web {
  40. static Optional<Color> parse_css_color(const StringView& view)
  41. {
  42. if (view.equals_ignoring_case("transparent"))
  43. return Color::from_rgba(0x00000000);
  44. auto color = Color::from_string(view.to_string().to_lowercase());
  45. if (color.has_value())
  46. return color;
  47. return {};
  48. }
  49. static Optional<float> try_parse_float(const StringView& string)
  50. {
  51. const char* str = string.characters_without_null_termination();
  52. size_t len = string.length();
  53. size_t weight = 1;
  54. int exp_val = 0;
  55. float value = 0.0f;
  56. float fraction = 0.0f;
  57. bool has_sign = false;
  58. bool is_negative = false;
  59. bool is_fractional = false;
  60. bool is_scientific = false;
  61. if (str[0] == '-') {
  62. is_negative = true;
  63. has_sign = true;
  64. }
  65. if (str[0] == '+') {
  66. has_sign = true;
  67. }
  68. for (size_t i = has_sign; i < len; i++) {
  69. // Looks like we're about to start working on the fractional part
  70. if (str[i] == '.') {
  71. is_fractional = true;
  72. continue;
  73. }
  74. if (str[i] == 'e' || str[i] == 'E') {
  75. if (str[i + 1] == '-' || str[i + 1] == '+')
  76. exp_val = atoi(str + i + 2);
  77. else
  78. exp_val = atoi(str + i + 1);
  79. is_scientific = true;
  80. continue;
  81. }
  82. if (str[i] < '0' || str[i] > '9' || exp_val != 0) {
  83. return {};
  84. continue;
  85. }
  86. if (is_fractional) {
  87. fraction *= 10;
  88. fraction += str[i] - '0';
  89. weight *= 10;
  90. } else {
  91. value = value * 10;
  92. value += str[i] - '0';
  93. }
  94. }
  95. fraction /= weight;
  96. value += fraction;
  97. if (is_scientific) {
  98. bool divide = exp_val < 0;
  99. if (divide)
  100. exp_val *= -1;
  101. for (int i = 0; i < exp_val; i++) {
  102. if (divide)
  103. value /= 10;
  104. else
  105. value *= 10;
  106. }
  107. }
  108. return is_negative ? -value : value;
  109. }
  110. static Optional<float> parse_number(const StringView& view)
  111. {
  112. if (view.ends_with('%'))
  113. return parse_number(view.substring_view(0, view.length() - 1));
  114. // FIXME: Maybe we should have "ends_with_ignoring_case()" ?
  115. if (view.to_string().to_lowercase().ends_with("px"))
  116. return parse_number(view.substring_view(0, view.length() - 2));
  117. return try_parse_float(view);
  118. }
  119. NonnullRefPtr<StyleValue> parse_css_value(const StringView& string)
  120. {
  121. auto number = parse_number(string);
  122. if (number.has_value()) {
  123. if (string.ends_with('%'))
  124. return PercentageStyleValue::create(number.value());
  125. return LengthStyleValue::create(Length(number.value(), Length::Type::Absolute));
  126. }
  127. if (string.equals_ignoring_case("inherit"))
  128. return InheritStyleValue::create();
  129. if (string.equals_ignoring_case("initial"))
  130. return InitialStyleValue::create();
  131. if (string.equals_ignoring_case("auto"))
  132. return LengthStyleValue::create(Length());
  133. auto color = parse_css_color(string);
  134. if (color.has_value())
  135. return ColorStyleValue::create(color.value());
  136. if (string == "-libhtml-link")
  137. return IdentifierStyleValue::create(CSS::ValueID::VendorSpecificLink);
  138. return StringStyleValue::create(string);
  139. }
  140. RefPtr<LengthStyleValue> parse_line_width(const StringView& part)
  141. {
  142. NonnullRefPtr<StyleValue> value = parse_css_value(part);
  143. if (value->is_length())
  144. return static_ptr_cast<LengthStyleValue>(value);
  145. return nullptr;
  146. }
  147. RefPtr<ColorStyleValue> parse_color(const StringView& part)
  148. {
  149. NonnullRefPtr<StyleValue> value = parse_css_value(part);
  150. if (value->is_color())
  151. return static_ptr_cast<ColorStyleValue>(value);
  152. return nullptr;
  153. }
  154. RefPtr<StringStyleValue> parse_line_style(const StringView& part)
  155. {
  156. NonnullRefPtr<StyleValue> parsed_value = parse_css_value(part);
  157. if (!parsed_value->is_string())
  158. return nullptr;
  159. auto value = static_ptr_cast<StringStyleValue>(parsed_value);
  160. if (value->to_string() == "dotted")
  161. return value;
  162. if (value->to_string() == "dashed")
  163. return value;
  164. if (value->to_string() == "solid")
  165. return value;
  166. if (value->to_string() == "double")
  167. return value;
  168. if (value->to_string() == "groove")
  169. return value;
  170. if (value->to_string() == "ridge")
  171. return value;
  172. return nullptr;
  173. }
  174. class CSSParser {
  175. public:
  176. CSSParser(const StringView& input)
  177. : css(input)
  178. {
  179. }
  180. bool next_is(const char* str) const
  181. {
  182. size_t len = strlen(str);
  183. for (size_t i = 0; i < len; ++i) {
  184. if (peek(i) != str[i])
  185. return false;
  186. }
  187. return true;
  188. }
  189. char peek(size_t offset = 0) const
  190. {
  191. if ((index + offset) < css.length())
  192. return css[index + offset];
  193. return 0;
  194. }
  195. char consume_specific(char ch)
  196. {
  197. if (peek() != ch) {
  198. dbg() << "peek() != '" << ch << "'";
  199. }
  200. PARSE_ASSERT(peek() == ch);
  201. PARSE_ASSERT(index < css.length());
  202. ++index;
  203. return ch;
  204. }
  205. char consume_one()
  206. {
  207. PARSE_ASSERT(index < css.length());
  208. return css[index++];
  209. };
  210. bool consume_whitespace_or_comments()
  211. {
  212. size_t original_index = index;
  213. bool in_comment = false;
  214. for (; index < css.length(); ++index) {
  215. char ch = peek();
  216. if (isspace(ch))
  217. continue;
  218. if (!in_comment && ch == '/' && peek(1) == '*') {
  219. in_comment = true;
  220. ++index;
  221. continue;
  222. }
  223. if (in_comment && ch == '*' && peek(1) == '/') {
  224. in_comment = false;
  225. ++index;
  226. continue;
  227. }
  228. if (in_comment)
  229. continue;
  230. break;
  231. }
  232. return original_index != index;
  233. }
  234. bool is_valid_selector_char(char ch) const
  235. {
  236. return isalnum(ch) || ch == '-' || ch == '_' || ch == '(' || ch == ')' || ch == '@';
  237. }
  238. bool is_combinator(char ch) const
  239. {
  240. return ch == '~' || ch == '>' || ch == '+';
  241. }
  242. Optional<Selector::SimpleSelector> parse_simple_selector()
  243. {
  244. auto index_at_start = index;
  245. if (consume_whitespace_or_comments())
  246. return {};
  247. if (!peek() || peek() == '{' || peek() == ',' || is_combinator(peek()))
  248. return {};
  249. Selector::SimpleSelector::Type type;
  250. if (peek() == '*') {
  251. type = Selector::SimpleSelector::Type::Universal;
  252. consume_one();
  253. return Selector::SimpleSelector {
  254. type,
  255. Selector::SimpleSelector::PseudoClass::None,
  256. String(),
  257. Selector::SimpleSelector::AttributeMatchType::None,
  258. String(),
  259. String()
  260. };
  261. }
  262. if (peek() == '.') {
  263. type = Selector::SimpleSelector::Type::Class;
  264. consume_one();
  265. } else if (peek() == '#') {
  266. type = Selector::SimpleSelector::Type::Id;
  267. consume_one();
  268. } else if (isalpha(peek())) {
  269. type = Selector::SimpleSelector::Type::TagName;
  270. } else {
  271. type = Selector::SimpleSelector::Type::Universal;
  272. }
  273. if (type != Selector::SimpleSelector::Type::Universal) {
  274. while (is_valid_selector_char(peek()))
  275. buffer.append(consume_one());
  276. PARSE_ASSERT(!buffer.is_null());
  277. }
  278. Selector::SimpleSelector simple_selector {
  279. type,
  280. Selector::SimpleSelector::PseudoClass::None,
  281. String::copy(buffer),
  282. Selector::SimpleSelector::AttributeMatchType::None,
  283. String(),
  284. String()
  285. };
  286. buffer.clear();
  287. if (peek() == '[') {
  288. Selector::SimpleSelector::AttributeMatchType attribute_match_type = Selector::SimpleSelector::AttributeMatchType::HasAttribute;
  289. String attribute_name;
  290. String attribute_value;
  291. bool in_value = false;
  292. consume_specific('[');
  293. char expected_end_of_attribute_selector = ']';
  294. while (peek() != expected_end_of_attribute_selector) {
  295. char ch = consume_one();
  296. if (ch == '=') {
  297. attribute_match_type = Selector::SimpleSelector::AttributeMatchType::ExactValueMatch;
  298. attribute_name = String::copy(buffer);
  299. buffer.clear();
  300. in_value = true;
  301. consume_whitespace_or_comments();
  302. if (peek() == '\'') {
  303. expected_end_of_attribute_selector = '\'';
  304. consume_one();
  305. } else if (peek() == '"') {
  306. expected_end_of_attribute_selector = '"';
  307. consume_one();
  308. }
  309. continue;
  310. }
  311. buffer.append(ch);
  312. }
  313. if (in_value)
  314. attribute_value = String::copy(buffer);
  315. else
  316. attribute_name = String::copy(buffer);
  317. buffer.clear();
  318. simple_selector.attribute_match_type = attribute_match_type;
  319. simple_selector.attribute_name = attribute_name;
  320. simple_selector.attribute_value = attribute_value;
  321. if (expected_end_of_attribute_selector != ']')
  322. consume_specific(expected_end_of_attribute_selector);
  323. consume_whitespace_or_comments();
  324. consume_specific(']');
  325. }
  326. if (peek() == ':') {
  327. // FIXME: Implement pseudo elements.
  328. [[maybe_unused]] bool is_pseudo_element = false;
  329. consume_one();
  330. if (peek() == ':') {
  331. is_pseudo_element = true;
  332. consume_one();
  333. }
  334. if (next_is("not")) {
  335. buffer.append(consume_one());
  336. buffer.append(consume_one());
  337. buffer.append(consume_one());
  338. buffer.append(consume_specific('('));
  339. while (peek() != ')')
  340. buffer.append(consume_one());
  341. buffer.append(consume_specific(')'));
  342. } else {
  343. while (is_valid_selector_char(peek()))
  344. buffer.append(consume_one());
  345. }
  346. auto pseudo_name = String::copy(buffer);
  347. buffer.clear();
  348. // Ignore for now, otherwise we produce a "false positive" selector
  349. // and apply styles to the element itself, not its pseudo element
  350. if (is_pseudo_element)
  351. return {};
  352. if (pseudo_name.equals_ignoring_case("link"))
  353. simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Link;
  354. else if (pseudo_name.equals_ignoring_case("hover"))
  355. simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Hover;
  356. else if (pseudo_name.equals_ignoring_case("focus"))
  357. simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Focus;
  358. else if (pseudo_name.equals_ignoring_case("first-child"))
  359. simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::FirstChild;
  360. else if (pseudo_name.equals_ignoring_case("last-child"))
  361. simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::LastChild;
  362. else if (pseudo_name.equals_ignoring_case("only-child"))
  363. simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::OnlyChild;
  364. else if (pseudo_name.equals_ignoring_case("empty"))
  365. simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Empty;
  366. }
  367. if (index == index_at_start) {
  368. // We consumed nothing.
  369. return {};
  370. }
  371. return simple_selector;
  372. }
  373. Optional<Selector::ComplexSelector> parse_complex_selector()
  374. {
  375. auto relation = Selector::ComplexSelector::Relation::Descendant;
  376. if (peek() == '{' || peek() == ',')
  377. return {};
  378. if (is_combinator(peek())) {
  379. switch (peek()) {
  380. case '>':
  381. relation = Selector::ComplexSelector::Relation::ImmediateChild;
  382. break;
  383. case '+':
  384. relation = Selector::ComplexSelector::Relation::AdjacentSibling;
  385. break;
  386. case '~':
  387. relation = Selector::ComplexSelector::Relation::GeneralSibling;
  388. break;
  389. }
  390. consume_one();
  391. consume_whitespace_or_comments();
  392. }
  393. consume_whitespace_or_comments();
  394. Vector<Selector::SimpleSelector> simple_selectors;
  395. for (;;) {
  396. auto component = parse_simple_selector();
  397. if (!component.has_value())
  398. break;
  399. simple_selectors.append(component.value());
  400. // If this assert triggers, we're most likely up to no good.
  401. PARSE_ASSERT(simple_selectors.size() < 100);
  402. }
  403. if (simple_selectors.is_empty())
  404. return {};
  405. return Selector::ComplexSelector { relation, move(simple_selectors) };
  406. }
  407. void parse_selector()
  408. {
  409. Vector<Selector::ComplexSelector> complex_selectors;
  410. for (;;) {
  411. auto complex_selector = parse_complex_selector();
  412. if (complex_selector.has_value())
  413. complex_selectors.append(complex_selector.value());
  414. consume_whitespace_or_comments();
  415. if (!peek() || peek() == ',' || peek() == '{')
  416. break;
  417. }
  418. if (complex_selectors.is_empty())
  419. return;
  420. complex_selectors.first().relation = Selector::ComplexSelector::Relation::None;
  421. current_rule.selectors.append(Selector(move(complex_selectors)));
  422. }
  423. Optional<Selector> parse_individual_selector()
  424. {
  425. parse_selector();
  426. if (current_rule.selectors.is_empty())
  427. return {};
  428. return current_rule.selectors.last();
  429. }
  430. void parse_selector_list()
  431. {
  432. for (;;) {
  433. parse_selector();
  434. consume_whitespace_or_comments();
  435. if (peek() == ',') {
  436. consume_one();
  437. continue;
  438. }
  439. if (peek() == '{')
  440. break;
  441. }
  442. }
  443. bool is_valid_property_name_char(char ch) const
  444. {
  445. return ch && !isspace(ch) && ch != ':';
  446. }
  447. bool is_valid_property_value_char(char ch) const
  448. {
  449. return ch && ch != '!' && ch != ';' && ch != '}';
  450. }
  451. struct ValueAndImportant {
  452. String value;
  453. bool important { false };
  454. };
  455. ValueAndImportant consume_css_value()
  456. {
  457. buffer.clear();
  458. int paren_nesting_level = 0;
  459. bool important = false;
  460. for (;;) {
  461. char ch = peek();
  462. if (ch == '(') {
  463. ++paren_nesting_level;
  464. buffer.append(consume_one());
  465. continue;
  466. }
  467. if (ch == ')') {
  468. PARSE_ASSERT(paren_nesting_level > 0);
  469. --paren_nesting_level;
  470. buffer.append(consume_one());
  471. continue;
  472. }
  473. if (paren_nesting_level > 0) {
  474. buffer.append(consume_one());
  475. continue;
  476. }
  477. if (next_is("!important")) {
  478. consume_specific('!');
  479. consume_specific('i');
  480. consume_specific('m');
  481. consume_specific('p');
  482. consume_specific('o');
  483. consume_specific('r');
  484. consume_specific('t');
  485. consume_specific('a');
  486. consume_specific('n');
  487. consume_specific('t');
  488. important = true;
  489. continue;
  490. }
  491. if (next_is("/*")) {
  492. consume_whitespace_or_comments();
  493. continue;
  494. }
  495. if (!ch)
  496. break;
  497. if (ch == '}')
  498. break;
  499. if (ch == ';')
  500. break;
  501. buffer.append(consume_one());
  502. }
  503. // Remove trailing whitespace.
  504. while (!buffer.is_empty() && isspace(buffer.last()))
  505. buffer.take_last();
  506. auto string = String::copy(buffer);
  507. buffer.clear();
  508. return { string, important };
  509. }
  510. Optional<StyleProperty> parse_property()
  511. {
  512. consume_whitespace_or_comments();
  513. if (peek() == ';') {
  514. consume_one();
  515. return {};
  516. }
  517. if (peek() == '}')
  518. return {};
  519. buffer.clear();
  520. while (is_valid_property_name_char(peek()))
  521. buffer.append(consume_one());
  522. auto property_name = String::copy(buffer);
  523. buffer.clear();
  524. consume_whitespace_or_comments();
  525. consume_specific(':');
  526. consume_whitespace_or_comments();
  527. auto [property_value, important] = consume_css_value();
  528. consume_whitespace_or_comments();
  529. if (peek() && peek() != '}')
  530. consume_specific(';');
  531. auto property_id = CSS::property_id_from_string(property_name);
  532. return StyleProperty { property_id, parse_css_value(property_value), important };
  533. }
  534. void parse_declaration()
  535. {
  536. for (;;) {
  537. auto property = parse_property();
  538. if (property.has_value())
  539. current_rule.properties.append(property.value());
  540. consume_whitespace_or_comments();
  541. if (peek() == '}')
  542. break;
  543. }
  544. }
  545. void parse_rule()
  546. {
  547. consume_whitespace_or_comments();
  548. if (index >= css.length())
  549. return;
  550. // FIXME: We ignore @-rules for now.
  551. if (peek() == '@') {
  552. while (peek() != '{')
  553. consume_one();
  554. int level = 0;
  555. for (;;) {
  556. auto ch = consume_one();
  557. if (ch == '{') {
  558. ++level;
  559. } else if (ch == '}') {
  560. --level;
  561. if (level == 0)
  562. break;
  563. }
  564. }
  565. consume_whitespace_or_comments();
  566. return;
  567. }
  568. parse_selector_list();
  569. consume_specific('{');
  570. parse_declaration();
  571. consume_specific('}');
  572. rules.append(StyleRule::create(move(current_rule.selectors), StyleDeclaration::create(move(current_rule.properties))));
  573. consume_whitespace_or_comments();
  574. }
  575. RefPtr<StyleSheet> parse_sheet()
  576. {
  577. while (index < css.length()) {
  578. parse_rule();
  579. }
  580. return StyleSheet::create(move(rules));
  581. }
  582. RefPtr<StyleDeclaration> parse_standalone_declaration()
  583. {
  584. consume_whitespace_or_comments();
  585. for (;;) {
  586. auto property = parse_property();
  587. if (property.has_value())
  588. current_rule.properties.append(property.value());
  589. consume_whitespace_or_comments();
  590. if (!peek())
  591. break;
  592. }
  593. return StyleDeclaration::create(move(current_rule.properties));
  594. }
  595. private:
  596. NonnullRefPtrVector<StyleRule> rules;
  597. struct CurrentRule {
  598. Vector<Selector> selectors;
  599. Vector<StyleProperty> properties;
  600. };
  601. CurrentRule current_rule;
  602. Vector<char> buffer;
  603. size_t index = 0;
  604. StringView css;
  605. };
  606. Optional<Selector> parse_selector(const StringView& selector_text)
  607. {
  608. CSSParser parser(selector_text);
  609. return parser.parse_individual_selector();
  610. }
  611. RefPtr<StyleSheet> parse_css(const StringView& css)
  612. {
  613. CSSParser parser(css);
  614. return parser.parse_sheet();
  615. }
  616. RefPtr<StyleDeclaration> parse_css_declaration(const StringView& css)
  617. {
  618. CSSParser parser(css);
  619. return parser.parse_standalone_declaration();
  620. }
  621. }