CSSParser.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  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. #define PARSE_ERROR() \
  40. do { \
  41. dbg() << "CSS parse error"; \
  42. } while (0)
  43. namespace Web {
  44. static CSS::ValueID value_id_for_palette_string(const StringView& string)
  45. {
  46. if (string == "desktop-background")
  47. return CSS::ValueID::VendorSpecificPaletteDesktopBackground;
  48. else if (string == "active-window-border1")
  49. return CSS::ValueID::VendorSpecificPaletteActiveWindowBorder1;
  50. else if (string == "active-window-border2")
  51. return CSS::ValueID::VendorSpecificPaletteActiveWindowBorder2;
  52. else if (string == "active-window-title")
  53. return CSS::ValueID::VendorSpecificPaletteActiveWindowTitle;
  54. else if (string == "inactive-window-border1")
  55. return CSS::ValueID::VendorSpecificPaletteInactiveWindowBorder1;
  56. else if (string == "inactive-window-border2")
  57. return CSS::ValueID::VendorSpecificPaletteInactiveWindowBorder2;
  58. else if (string == "inactive-window-title")
  59. return CSS::ValueID::VendorSpecificPaletteInactiveWindowTitle;
  60. else if (string == "moving-window-border1")
  61. return CSS::ValueID::VendorSpecificPaletteMovingWindowBorder1;
  62. else if (string == "moving-window-border2")
  63. return CSS::ValueID::VendorSpecificPaletteMovingWindowBorder2;
  64. else if (string == "moving-window-title")
  65. return CSS::ValueID::VendorSpecificPaletteMovingWindowTitle;
  66. else if (string == "highlight-window-border1")
  67. return CSS::ValueID::VendorSpecificPaletteHighlightWindowBorder1;
  68. else if (string == "highlight-window-border2")
  69. return CSS::ValueID::VendorSpecificPaletteHighlightWindowBorder2;
  70. else if (string == "highlight-window-title")
  71. return CSS::ValueID::VendorSpecificPaletteHighlightWindowTitle;
  72. else if (string == "menu-stripe")
  73. return CSS::ValueID::VendorSpecificPaletteMenuStripe;
  74. else if (string == "menu-base")
  75. return CSS::ValueID::VendorSpecificPaletteMenuBase;
  76. else if (string == "menu-base-text")
  77. return CSS::ValueID::VendorSpecificPaletteMenuBaseText;
  78. else if (string == "menu-selection")
  79. return CSS::ValueID::VendorSpecificPaletteMenuSelection;
  80. else if (string == "menu-selection-text")
  81. return CSS::ValueID::VendorSpecificPaletteMenuSelectionText;
  82. else if (string == "window")
  83. return CSS::ValueID::VendorSpecificPaletteWindow;
  84. else if (string == "window-text")
  85. return CSS::ValueID::VendorSpecificPaletteWindowText;
  86. else if (string == "button")
  87. return CSS::ValueID::VendorSpecificPaletteButton;
  88. else if (string == "button-text")
  89. return CSS::ValueID::VendorSpecificPaletteButtonText;
  90. else if (string == "base")
  91. return CSS::ValueID::VendorSpecificPaletteBase;
  92. else if (string == "base-text")
  93. return CSS::ValueID::VendorSpecificPaletteBaseText;
  94. else if (string == "threed-highlight")
  95. return CSS::ValueID::VendorSpecificPaletteThreedHighlight;
  96. else if (string == "threed-shadow1")
  97. return CSS::ValueID::VendorSpecificPaletteThreedShadow1;
  98. else if (string == "threed-shadow2")
  99. return CSS::ValueID::VendorSpecificPaletteThreedShadow2;
  100. else if (string == "hover-highlight")
  101. return CSS::ValueID::VendorSpecificPaletteHoverHighlight;
  102. else if (string == "selection")
  103. return CSS::ValueID::VendorSpecificPaletteSelection;
  104. else if (string == "selection-text")
  105. return CSS::ValueID::VendorSpecificPaletteSelectionText;
  106. else if (string == "inactive-selection")
  107. return CSS::ValueID::VendorSpecificPaletteInactiveSelection;
  108. else if (string == "inactive-selection-text")
  109. return CSS::ValueID::VendorSpecificPaletteInactiveSelectionText;
  110. else if (string == "rubber-band-fill")
  111. return CSS::ValueID::VendorSpecificPaletteRubberBandFill;
  112. else if (string == "rubber-band-border")
  113. return CSS::ValueID::VendorSpecificPaletteRubberBandBorder;
  114. else if (string == "link")
  115. return CSS::ValueID::VendorSpecificPaletteLink;
  116. else if (string == "active-link")
  117. return CSS::ValueID::VendorSpecificPaletteActiveLink;
  118. else if (string == "visited-link")
  119. return CSS::ValueID::VendorSpecificPaletteVisitedLink;
  120. else if (string == "ruler")
  121. return CSS::ValueID::VendorSpecificPaletteRuler;
  122. else if (string == "ruler-border")
  123. return CSS::ValueID::VendorSpecificPaletteRulerBorder;
  124. else if (string == "ruler-active-text")
  125. return CSS::ValueID::VendorSpecificPaletteRulerActiveText;
  126. else if (string == "ruler-inactive-text")
  127. return CSS::ValueID::VendorSpecificPaletteRulerInactiveText;
  128. else if (string == "text-cursor")
  129. return CSS::ValueID::VendorSpecificPaletteTextCursor;
  130. else if (string == "focus-outline")
  131. return CSS::ValueID::VendorSpecificPaletteFocusOutline;
  132. else if (string == "syntax-comment")
  133. return CSS::ValueID::VendorSpecificPaletteSyntaxComment;
  134. else if (string == "syntax-number")
  135. return CSS::ValueID::VendorSpecificPaletteSyntaxNumber;
  136. else if (string == "syntax-string")
  137. return CSS::ValueID::VendorSpecificPaletteSyntaxString;
  138. else if (string == "syntax-type")
  139. return CSS::ValueID::VendorSpecificPaletteSyntaxType;
  140. else if (string == "syntax-punctuation")
  141. return CSS::ValueID::VendorSpecificPaletteSyntaxPunctuation;
  142. else if (string == "syntax-operator")
  143. return CSS::ValueID::VendorSpecificPaletteSyntaxOperator;
  144. else if (string == "syntax-keyword")
  145. return CSS::ValueID::VendorSpecificPaletteSyntaxKeyword;
  146. else if (string == "syntax-control-keyword")
  147. return CSS::ValueID::VendorSpecificPaletteSyntaxControlKeyword;
  148. else if (string == "syntax-identifier")
  149. return CSS::ValueID::VendorSpecificPaletteSyntaxIdentifier;
  150. else if (string == "syntax-preprocessor-statement")
  151. return CSS::ValueID::VendorSpecificPaletteSyntaxPreprocessorStatement;
  152. else if (string == "syntax-preprocessor-value")
  153. return CSS::ValueID::VendorSpecificPaletteSyntaxPreprocessorValue;
  154. else
  155. return CSS::ValueID::Invalid;
  156. }
  157. static Optional<Color> parse_css_color(const StringView& view)
  158. {
  159. if (view.equals_ignoring_case("transparent"))
  160. return Color::from_rgba(0x00000000);
  161. auto color = Color::from_string(view.to_string().to_lowercase());
  162. if (color.has_value())
  163. return color;
  164. return {};
  165. }
  166. static Optional<float> try_parse_float(const StringView& string)
  167. {
  168. const char* str = string.characters_without_null_termination();
  169. size_t len = string.length();
  170. size_t weight = 1;
  171. int exp_val = 0;
  172. float value = 0.0f;
  173. float fraction = 0.0f;
  174. bool has_sign = false;
  175. bool is_negative = false;
  176. bool is_fractional = false;
  177. bool is_scientific = false;
  178. if (str[0] == '-') {
  179. is_negative = true;
  180. has_sign = true;
  181. }
  182. if (str[0] == '+') {
  183. has_sign = true;
  184. }
  185. for (size_t i = has_sign; i < len; i++) {
  186. // Looks like we're about to start working on the fractional part
  187. if (str[i] == '.') {
  188. is_fractional = true;
  189. continue;
  190. }
  191. if (str[i] == 'e' || str[i] == 'E') {
  192. if (str[i + 1] == '-' || str[i + 1] == '+')
  193. exp_val = atoi(str + i + 2);
  194. else
  195. exp_val = atoi(str + i + 1);
  196. is_scientific = true;
  197. continue;
  198. }
  199. if (str[i] < '0' || str[i] > '9' || exp_val != 0) {
  200. return {};
  201. continue;
  202. }
  203. if (is_fractional) {
  204. fraction *= 10;
  205. fraction += str[i] - '0';
  206. weight *= 10;
  207. } else {
  208. value = value * 10;
  209. value += str[i] - '0';
  210. }
  211. }
  212. fraction /= weight;
  213. value += fraction;
  214. if (is_scientific) {
  215. bool divide = exp_val < 0;
  216. if (divide)
  217. exp_val *= -1;
  218. for (int i = 0; i < exp_val; i++) {
  219. if (divide)
  220. value /= 10;
  221. else
  222. value *= 10;
  223. }
  224. }
  225. return is_negative ? -value : value;
  226. }
  227. static Optional<float> parse_number(const StringView& view)
  228. {
  229. if (view.ends_with('%'))
  230. return parse_number(view.substring_view(0, view.length() - 1));
  231. // FIXME: Maybe we should have "ends_with_ignoring_case()" ?
  232. if (view.to_string().to_lowercase().ends_with("px"))
  233. return parse_number(view.substring_view(0, view.length() - 2));
  234. if (view.to_string().to_lowercase().ends_with("rem"))
  235. return parse_number(view.substring_view(0, view.length() - 3));
  236. if (view.to_string().to_lowercase().ends_with("em"))
  237. return parse_number(view.substring_view(0, view.length() - 2));
  238. return try_parse_float(view);
  239. }
  240. NonnullRefPtr<StyleValue> parse_css_value(const StringView& string)
  241. {
  242. auto number = parse_number(string);
  243. if (number.has_value()) {
  244. if (string.ends_with('%'))
  245. return PercentageStyleValue::create(number.value());
  246. if (string.ends_with("em"))
  247. return LengthStyleValue::create(Length(number.value(), Length::Type::Em));
  248. if (string.ends_with("rem"))
  249. return LengthStyleValue::create(Length(number.value(), Length::Type::Rem));
  250. return LengthStyleValue::create(Length(number.value(), Length::Type::Px));
  251. }
  252. if (string.equals_ignoring_case("inherit"))
  253. return InheritStyleValue::create();
  254. if (string.equals_ignoring_case("initial"))
  255. return InitialStyleValue::create();
  256. if (string.equals_ignoring_case("auto"))
  257. return LengthStyleValue::create(Length());
  258. auto color = parse_css_color(string);
  259. if (color.has_value())
  260. return ColorStyleValue::create(color.value());
  261. if (string == "-libweb-link")
  262. return IdentifierStyleValue::create(CSS::ValueID::VendorSpecificLink);
  263. else if (string.starts_with("-libweb-palette-")) {
  264. auto value_id = value_id_for_palette_string(string.substring_view(16, string.length() - 16));
  265. return IdentifierStyleValue::create(value_id);
  266. }
  267. return StringStyleValue::create(string);
  268. }
  269. RefPtr<LengthStyleValue> parse_line_width(const StringView& part)
  270. {
  271. NonnullRefPtr<StyleValue> value = parse_css_value(part);
  272. if (value->is_length())
  273. return static_ptr_cast<LengthStyleValue>(value);
  274. return nullptr;
  275. }
  276. RefPtr<ColorStyleValue> parse_color(const StringView& part)
  277. {
  278. NonnullRefPtr<StyleValue> value = parse_css_value(part);
  279. if (value->is_color())
  280. return static_ptr_cast<ColorStyleValue>(value);
  281. return nullptr;
  282. }
  283. RefPtr<StringStyleValue> parse_line_style(const StringView& part)
  284. {
  285. NonnullRefPtr<StyleValue> parsed_value = parse_css_value(part);
  286. if (!parsed_value->is_string())
  287. return nullptr;
  288. auto value = static_ptr_cast<StringStyleValue>(parsed_value);
  289. if (value->to_string() == "dotted")
  290. return value;
  291. if (value->to_string() == "dashed")
  292. return value;
  293. if (value->to_string() == "solid")
  294. return value;
  295. if (value->to_string() == "double")
  296. return value;
  297. if (value->to_string() == "groove")
  298. return value;
  299. if (value->to_string() == "ridge")
  300. return value;
  301. return nullptr;
  302. }
  303. class CSSParser {
  304. public:
  305. CSSParser(const StringView& input)
  306. : css(input)
  307. {
  308. }
  309. bool next_is(const char* str) const
  310. {
  311. size_t len = strlen(str);
  312. for (size_t i = 0; i < len; ++i) {
  313. if (peek(i) != str[i])
  314. return false;
  315. }
  316. return true;
  317. }
  318. char peek(size_t offset = 0) const
  319. {
  320. if ((index + offset) < css.length())
  321. return css[index + offset];
  322. return 0;
  323. }
  324. char consume_specific(char ch)
  325. {
  326. if (peek() != ch) {
  327. dbg() << "peek() != '" << ch << "'";
  328. }
  329. if (peek() != ch) {
  330. PARSE_ERROR();
  331. }
  332. PARSE_ASSERT(index < css.length());
  333. ++index;
  334. return ch;
  335. }
  336. char consume_one()
  337. {
  338. PARSE_ASSERT(index < css.length());
  339. return css[index++];
  340. };
  341. bool consume_whitespace_or_comments()
  342. {
  343. size_t original_index = index;
  344. bool in_comment = false;
  345. for (; index < css.length(); ++index) {
  346. char ch = peek();
  347. if (isspace(ch))
  348. continue;
  349. if (!in_comment && ch == '/' && peek(1) == '*') {
  350. in_comment = true;
  351. ++index;
  352. continue;
  353. }
  354. if (in_comment && ch == '*' && peek(1) == '/') {
  355. in_comment = false;
  356. ++index;
  357. continue;
  358. }
  359. if (in_comment)
  360. continue;
  361. break;
  362. }
  363. return original_index != index;
  364. }
  365. bool is_valid_selector_char(char ch) const
  366. {
  367. return isalnum(ch) || ch == '-' || ch == '_' || ch == '(' || ch == ')' || ch == '@';
  368. }
  369. bool is_combinator(char ch) const
  370. {
  371. return ch == '~' || ch == '>' || ch == '+';
  372. }
  373. Optional<Selector::SimpleSelector> parse_simple_selector()
  374. {
  375. auto index_at_start = index;
  376. if (consume_whitespace_or_comments())
  377. return {};
  378. if (!peek() || peek() == '{' || peek() == ',' || is_combinator(peek()))
  379. return {};
  380. Selector::SimpleSelector::Type type;
  381. if (peek() == '*') {
  382. type = Selector::SimpleSelector::Type::Universal;
  383. consume_one();
  384. return Selector::SimpleSelector {
  385. type,
  386. Selector::SimpleSelector::PseudoClass::None,
  387. String(),
  388. Selector::SimpleSelector::AttributeMatchType::None,
  389. String(),
  390. String()
  391. };
  392. }
  393. if (peek() == '.') {
  394. type = Selector::SimpleSelector::Type::Class;
  395. consume_one();
  396. } else if (peek() == '#') {
  397. type = Selector::SimpleSelector::Type::Id;
  398. consume_one();
  399. } else if (isalpha(peek())) {
  400. type = Selector::SimpleSelector::Type::TagName;
  401. } else {
  402. type = Selector::SimpleSelector::Type::Universal;
  403. }
  404. if (type != Selector::SimpleSelector::Type::Universal) {
  405. while (is_valid_selector_char(peek()))
  406. buffer.append(consume_one());
  407. PARSE_ASSERT(!buffer.is_null());
  408. }
  409. Selector::SimpleSelector simple_selector {
  410. type,
  411. Selector::SimpleSelector::PseudoClass::None,
  412. String::copy(buffer),
  413. Selector::SimpleSelector::AttributeMatchType::None,
  414. String(),
  415. String()
  416. };
  417. buffer.clear();
  418. if (peek() == '[') {
  419. Selector::SimpleSelector::AttributeMatchType attribute_match_type = Selector::SimpleSelector::AttributeMatchType::HasAttribute;
  420. String attribute_name;
  421. String attribute_value;
  422. bool in_value = false;
  423. consume_specific('[');
  424. char expected_end_of_attribute_selector = ']';
  425. while (peek() != expected_end_of_attribute_selector) {
  426. char ch = consume_one();
  427. if (ch == '=' || (ch == '~' && peek() == '=')) {
  428. if (ch == '=') {
  429. attribute_match_type = Selector::SimpleSelector::AttributeMatchType::ExactValueMatch;
  430. } else if (ch == '~') {
  431. consume_one();
  432. attribute_match_type = Selector::SimpleSelector::AttributeMatchType::Contains;
  433. }
  434. attribute_name = String::copy(buffer);
  435. buffer.clear();
  436. in_value = true;
  437. consume_whitespace_or_comments();
  438. if (peek() == '\'') {
  439. expected_end_of_attribute_selector = '\'';
  440. consume_one();
  441. } else if (peek() == '"') {
  442. expected_end_of_attribute_selector = '"';
  443. consume_one();
  444. }
  445. continue;
  446. }
  447. // FIXME: This is a hack that will go away when we replace this with a big boy CSS parser.
  448. if (ch == '\\')
  449. ch = consume_one();
  450. buffer.append(ch);
  451. }
  452. if (in_value)
  453. attribute_value = String::copy(buffer);
  454. else
  455. attribute_name = String::copy(buffer);
  456. buffer.clear();
  457. simple_selector.attribute_match_type = attribute_match_type;
  458. simple_selector.attribute_name = attribute_name;
  459. simple_selector.attribute_value = attribute_value;
  460. if (expected_end_of_attribute_selector != ']')
  461. consume_specific(expected_end_of_attribute_selector);
  462. consume_whitespace_or_comments();
  463. consume_specific(']');
  464. }
  465. if (peek() == ':') {
  466. // FIXME: Implement pseudo elements.
  467. [[maybe_unused]] bool is_pseudo_element = false;
  468. consume_one();
  469. if (peek() == ':') {
  470. is_pseudo_element = true;
  471. consume_one();
  472. }
  473. if (next_is("not")) {
  474. buffer.append(consume_one());
  475. buffer.append(consume_one());
  476. buffer.append(consume_one());
  477. buffer.append(consume_specific('('));
  478. while (peek() != ')')
  479. buffer.append(consume_one());
  480. buffer.append(consume_specific(')'));
  481. } else {
  482. while (is_valid_selector_char(peek()))
  483. buffer.append(consume_one());
  484. }
  485. auto pseudo_name = String::copy(buffer);
  486. buffer.clear();
  487. // Ignore for now, otherwise we produce a "false positive" selector
  488. // and apply styles to the element itself, not its pseudo element
  489. if (is_pseudo_element)
  490. return {};
  491. if (pseudo_name.equals_ignoring_case("link"))
  492. simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Link;
  493. else if (pseudo_name.equals_ignoring_case("hover"))
  494. simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Hover;
  495. else if (pseudo_name.equals_ignoring_case("focus"))
  496. simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Focus;
  497. else if (pseudo_name.equals_ignoring_case("first-child"))
  498. simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::FirstChild;
  499. else if (pseudo_name.equals_ignoring_case("last-child"))
  500. simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::LastChild;
  501. else if (pseudo_name.equals_ignoring_case("only-child"))
  502. simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::OnlyChild;
  503. else if (pseudo_name.equals_ignoring_case("empty"))
  504. simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Empty;
  505. else if (pseudo_name.equals_ignoring_case("root"))
  506. simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Root;
  507. }
  508. if (index == index_at_start) {
  509. // We consumed nothing.
  510. return {};
  511. }
  512. return simple_selector;
  513. }
  514. Optional<Selector::ComplexSelector> parse_complex_selector()
  515. {
  516. auto relation = Selector::ComplexSelector::Relation::Descendant;
  517. if (peek() == '{' || peek() == ',')
  518. return {};
  519. if (is_combinator(peek())) {
  520. switch (peek()) {
  521. case '>':
  522. relation = Selector::ComplexSelector::Relation::ImmediateChild;
  523. break;
  524. case '+':
  525. relation = Selector::ComplexSelector::Relation::AdjacentSibling;
  526. break;
  527. case '~':
  528. relation = Selector::ComplexSelector::Relation::GeneralSibling;
  529. break;
  530. }
  531. consume_one();
  532. consume_whitespace_or_comments();
  533. }
  534. consume_whitespace_or_comments();
  535. Vector<Selector::SimpleSelector> simple_selectors;
  536. for (;;) {
  537. auto component = parse_simple_selector();
  538. if (!component.has_value())
  539. break;
  540. simple_selectors.append(component.value());
  541. // If this assert triggers, we're most likely up to no good.
  542. PARSE_ASSERT(simple_selectors.size() < 100);
  543. }
  544. if (simple_selectors.is_empty())
  545. return {};
  546. return Selector::ComplexSelector { relation, move(simple_selectors) };
  547. }
  548. void parse_selector()
  549. {
  550. Vector<Selector::ComplexSelector> complex_selectors;
  551. for (;;) {
  552. auto index_before = index;
  553. auto complex_selector = parse_complex_selector();
  554. if (complex_selector.has_value())
  555. complex_selectors.append(complex_selector.value());
  556. consume_whitespace_or_comments();
  557. if (!peek() || peek() == ',' || peek() == '{')
  558. break;
  559. // HACK: If we didn't move forward, just let go.
  560. if (index == index_before)
  561. break;
  562. }
  563. if (complex_selectors.is_empty())
  564. return;
  565. complex_selectors.first().relation = Selector::ComplexSelector::Relation::None;
  566. current_rule.selectors.append(Selector(move(complex_selectors)));
  567. }
  568. Optional<Selector> parse_individual_selector()
  569. {
  570. parse_selector();
  571. if (current_rule.selectors.is_empty())
  572. return {};
  573. return current_rule.selectors.last();
  574. }
  575. void parse_selector_list()
  576. {
  577. for (;;) {
  578. auto index_before = index;
  579. parse_selector();
  580. consume_whitespace_or_comments();
  581. if (peek() == ',') {
  582. consume_one();
  583. continue;
  584. }
  585. if (peek() == '{')
  586. break;
  587. // HACK: If we didn't move forward, just let go.
  588. if (index_before == index)
  589. break;
  590. }
  591. }
  592. bool is_valid_property_name_char(char ch) const
  593. {
  594. return ch && !isspace(ch) && ch != ':';
  595. }
  596. bool is_valid_property_value_char(char ch) const
  597. {
  598. return ch && ch != '!' && ch != ';' && ch != '}';
  599. }
  600. struct ValueAndImportant {
  601. String value;
  602. bool important { false };
  603. };
  604. ValueAndImportant consume_css_value()
  605. {
  606. buffer.clear();
  607. int paren_nesting_level = 0;
  608. bool important = false;
  609. for (;;) {
  610. char ch = peek();
  611. if (ch == '(') {
  612. ++paren_nesting_level;
  613. buffer.append(consume_one());
  614. continue;
  615. }
  616. if (ch == ')') {
  617. PARSE_ASSERT(paren_nesting_level > 0);
  618. --paren_nesting_level;
  619. buffer.append(consume_one());
  620. continue;
  621. }
  622. if (paren_nesting_level > 0) {
  623. buffer.append(consume_one());
  624. continue;
  625. }
  626. if (next_is("!important")) {
  627. consume_specific('!');
  628. consume_specific('i');
  629. consume_specific('m');
  630. consume_specific('p');
  631. consume_specific('o');
  632. consume_specific('r');
  633. consume_specific('t');
  634. consume_specific('a');
  635. consume_specific('n');
  636. consume_specific('t');
  637. important = true;
  638. continue;
  639. }
  640. if (next_is("/*")) {
  641. consume_whitespace_or_comments();
  642. continue;
  643. }
  644. if (!ch)
  645. break;
  646. if (ch == '}')
  647. break;
  648. if (ch == ';')
  649. break;
  650. buffer.append(consume_one());
  651. }
  652. // Remove trailing whitespace.
  653. while (!buffer.is_empty() && isspace(buffer.last()))
  654. buffer.take_last();
  655. auto string = String::copy(buffer);
  656. buffer.clear();
  657. return { string, important };
  658. }
  659. Optional<StyleProperty> parse_property()
  660. {
  661. consume_whitespace_or_comments();
  662. if (peek() == ';') {
  663. consume_one();
  664. return {};
  665. }
  666. if (peek() == '}')
  667. return {};
  668. buffer.clear();
  669. while (is_valid_property_name_char(peek()))
  670. buffer.append(consume_one());
  671. auto property_name = String::copy(buffer);
  672. buffer.clear();
  673. consume_whitespace_or_comments();
  674. consume_specific(':');
  675. consume_whitespace_or_comments();
  676. auto [property_value, important] = consume_css_value();
  677. consume_whitespace_or_comments();
  678. if (peek() && peek() != '}')
  679. consume_specific(';');
  680. auto property_id = CSS::property_id_from_string(property_name);
  681. return StyleProperty { property_id, parse_css_value(property_value), important };
  682. }
  683. void parse_declaration()
  684. {
  685. for (;;) {
  686. auto property = parse_property();
  687. if (property.has_value())
  688. current_rule.properties.append(property.value());
  689. consume_whitespace_or_comments();
  690. if (peek() == '}')
  691. break;
  692. }
  693. }
  694. void parse_rule()
  695. {
  696. consume_whitespace_or_comments();
  697. if (index >= css.length())
  698. return;
  699. // FIXME: We ignore @-rules for now.
  700. if (peek() == '@') {
  701. while (peek() != '{')
  702. consume_one();
  703. int level = 0;
  704. for (;;) {
  705. auto ch = consume_one();
  706. if (ch == '{') {
  707. ++level;
  708. } else if (ch == '}') {
  709. --level;
  710. if (level == 0)
  711. break;
  712. }
  713. }
  714. consume_whitespace_or_comments();
  715. return;
  716. }
  717. parse_selector_list();
  718. consume_specific('{');
  719. parse_declaration();
  720. consume_specific('}');
  721. rules.append(StyleRule::create(move(current_rule.selectors), StyleDeclaration::create(move(current_rule.properties))));
  722. consume_whitespace_or_comments();
  723. }
  724. RefPtr<StyleSheet> parse_sheet()
  725. {
  726. while (index < css.length()) {
  727. parse_rule();
  728. }
  729. return StyleSheet::create(move(rules));
  730. }
  731. RefPtr<StyleDeclaration> parse_standalone_declaration()
  732. {
  733. consume_whitespace_or_comments();
  734. for (;;) {
  735. auto property = parse_property();
  736. if (property.has_value())
  737. current_rule.properties.append(property.value());
  738. consume_whitespace_or_comments();
  739. if (!peek())
  740. break;
  741. }
  742. return StyleDeclaration::create(move(current_rule.properties));
  743. }
  744. private:
  745. NonnullRefPtrVector<StyleRule> rules;
  746. struct CurrentRule {
  747. Vector<Selector> selectors;
  748. Vector<StyleProperty> properties;
  749. };
  750. CurrentRule current_rule;
  751. Vector<char> buffer;
  752. size_t index = 0;
  753. StringView css;
  754. };
  755. Optional<Selector> parse_selector(const StringView& selector_text)
  756. {
  757. CSSParser parser(selector_text);
  758. return parser.parse_individual_selector();
  759. }
  760. RefPtr<StyleSheet> parse_css(const StringView& css)
  761. {
  762. CSSParser parser(css);
  763. return parser.parse_sheet();
  764. }
  765. RefPtr<StyleDeclaration> parse_css_declaration(const StringView& css)
  766. {
  767. CSSParser parser(css);
  768. return parser.parse_standalone_declaration();
  769. }
  770. }