Serenity Operating System
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
27#include <AK/HashMap.h>
28#include <LibWeb/CSS/PropertyID.h>
29#include <LibWeb/CSS/StyleSheet.h>
30#include <LibWeb/Parser/CSSParser.h>
31#include <ctype.h>
32#include <stdio.h>
33#include <stdlib.h>
34
35#define PARSE_ASSERT(x) \
36 if (!(x)) { \
37 dbg() << "CSS PARSER ASSERTION FAILED: " << #x; \
38 dbg() << "At character# " << index << " in CSS: _" << css << "_"; \
39 ASSERT_NOT_REACHED(); \
40 }
41
42namespace Web {
43
44static Optional<Color> parse_css_color(const StringView& view)
45{
46 auto color = Color::from_string(view);
47 if (color.has_value())
48 return color;
49
50 return {};
51}
52
53static Optional<float> try_parse_float(const StringView& string)
54{
55 const char* str = string.characters_without_null_termination();
56 size_t len = string.length();
57 size_t weight = 1;
58 int exp_val = 0;
59 float value = 0.0f;
60 float fraction = 0.0f;
61 bool has_sign = false;
62 bool is_negative = false;
63 bool is_fractional = false;
64 bool is_scientific = false;
65
66 if (str[0] == '-') {
67 is_negative = true;
68 has_sign = true;
69 }
70 if (str[0] == '+') {
71 has_sign = true;
72 }
73
74 for (size_t i = has_sign; i < len; i++) {
75
76 // Looks like we're about to start working on the fractional part
77 if (str[i] == '.') {
78 is_fractional = true;
79 continue;
80 }
81
82 if (str[i] == 'e' || str[i] == 'E') {
83 if (str[i + 1] == '-' || str[i + 1] == '+')
84 exp_val = atoi(str + i + 2);
85 else
86 exp_val = atoi(str + i + 1);
87
88 is_scientific = true;
89 continue;
90 }
91
92 if (str[i] < '0' || str[i] > '9' || exp_val != 0) {
93 return {};
94 continue;
95 }
96
97 if (is_fractional) {
98 fraction *= 10;
99 fraction += str[i] - '0';
100 weight *= 10;
101 } else {
102 value = value * 10;
103 value += str[i] - '0';
104 }
105 }
106
107 fraction /= weight;
108 value += fraction;
109
110 if (is_scientific) {
111 bool divide = exp_val < 0;
112 if (divide)
113 exp_val *= -1;
114
115 for (int i = 0; i < exp_val; i++) {
116 if (divide)
117 value /= 10;
118 else
119 value *= 10;
120 }
121 }
122
123 return is_negative ? -value : value;
124}
125
126static Optional<float> parse_number(const StringView& view)
127{
128 if (view.length() >= 2 && view[view.length() - 2] == 'p' && view[view.length() - 1] == 'x')
129 return parse_number(view.substring_view(0, view.length() - 2));
130
131 return try_parse_float(view);
132}
133
134NonnullRefPtr<StyleValue> parse_css_value(const StringView& string)
135{
136 auto number = parse_number(string);
137 if (number.has_value())
138 return LengthStyleValue::create(Length(number.value(), Length::Type::Absolute));
139 if (string == "inherit")
140 return InheritStyleValue::create();
141 if (string == "initial")
142 return InitialStyleValue::create();
143 if (string == "auto")
144 return LengthStyleValue::create(Length());
145
146 auto color = parse_css_color(string);
147 if (color.has_value())
148 return ColorStyleValue::create(color.value());
149
150 if (string == "-libhtml-link")
151 return IdentifierStyleValue::create(CSS::ValueID::VendorSpecificLink);
152
153 return StringStyleValue::create(string);
154}
155
156RefPtr<LengthStyleValue> parse_line_width(const StringView& part)
157{
158 NonnullRefPtr<StyleValue> value = parse_css_value(part);
159 if (value->is_length())
160 return static_ptr_cast<LengthStyleValue>(value);
161 return nullptr;
162}
163
164RefPtr<ColorStyleValue> parse_color(const StringView& part)
165{
166 NonnullRefPtr<StyleValue> value = parse_css_value(part);
167 if (value->is_color())
168 return static_ptr_cast<ColorStyleValue>(value);
169 return nullptr;
170}
171
172RefPtr<StringStyleValue> parse_line_style(const StringView& part)
173{
174 NonnullRefPtr<StyleValue> parsed_value = parse_css_value(part);
175 if (!parsed_value->is_string())
176 return nullptr;
177 auto value = static_ptr_cast<StringStyleValue>(parsed_value);
178 if (value->to_string() == "dotted")
179 return value;
180 if (value->to_string() == "dashed")
181 return value;
182 if (value->to_string() == "solid")
183 return value;
184 if (value->to_string() == "double")
185 return value;
186 if (value->to_string() == "groove")
187 return value;
188 if (value->to_string() == "ridge")
189 return value;
190 return nullptr;
191}
192
193class CSSParser {
194public:
195 CSSParser(const StringView& input)
196 : css(input)
197 {
198 }
199
200 bool next_is(const char* str) const
201 {
202 size_t len = strlen(str);
203 for (size_t i = 0; i < len; ++i) {
204 if (peek(i) != str[i])
205 return false;
206 }
207 return true;
208 }
209
210 char peek(size_t offset = 0) const
211 {
212 if ((index + offset) < css.length())
213 return css[index + offset];
214 return 0;
215 }
216
217 char consume_specific(char ch)
218 {
219 if (peek() != ch) {
220 dbg() << "peek() != '" << ch << "'";
221 }
222 PARSE_ASSERT(peek() == ch);
223 PARSE_ASSERT(index < css.length());
224 ++index;
225 return ch;
226 }
227
228 char consume_one()
229 {
230 PARSE_ASSERT(index < css.length());
231 return css[index++];
232 };
233
234 bool consume_whitespace_or_comments()
235 {
236 size_t original_index = index;
237 bool in_comment = false;
238 for (; index < css.length(); ++index) {
239 char ch = peek();
240 if (isspace(ch))
241 continue;
242 if (!in_comment && ch == '/' && peek(1) == '*') {
243 in_comment = true;
244 ++index;
245 continue;
246 }
247 if (in_comment && ch == '*' && peek(1) == '/') {
248 in_comment = false;
249 ++index;
250 continue;
251 }
252 if (in_comment)
253 continue;
254 break;
255 }
256 return original_index != index;
257 }
258
259 bool is_valid_selector_char(char ch) const
260 {
261 return isalnum(ch) || ch == '-' || ch == '_' || ch == '(' || ch == ')' || ch == '@';
262 }
263
264 bool is_combinator(char ch) const
265 {
266 return ch == '~' || ch == '>' || ch == '+';
267 }
268
269 Optional<Selector::SimpleSelector> parse_simple_selector()
270 {
271 if (consume_whitespace_or_comments())
272 return {};
273
274 if (!peek() || peek() == '{' || peek() == ',' || is_combinator(peek()))
275 return {};
276
277 Selector::SimpleSelector::Type type;
278
279 if (peek() == '*') {
280 type = Selector::SimpleSelector::Type::Universal;
281 consume_one();
282 return Selector::SimpleSelector {
283 type,
284 Selector::SimpleSelector::PseudoClass::None,
285 String(),
286 Selector::SimpleSelector::AttributeMatchType::None,
287 String(),
288 String()
289 };
290 }
291
292 if (peek() == '.') {
293 type = Selector::SimpleSelector::Type::Class;
294 consume_one();
295 } else if (peek() == '#') {
296 type = Selector::SimpleSelector::Type::Id;
297 consume_one();
298 } else if (isalpha(peek())) {
299 type = Selector::SimpleSelector::Type::TagName;
300 } else {
301 type = Selector::SimpleSelector::Type::Universal;
302 }
303
304 if (type != Selector::SimpleSelector::Type::Universal) {
305 while (is_valid_selector_char(peek()))
306 buffer.append(consume_one());
307 PARSE_ASSERT(!buffer.is_null());
308 }
309
310 Selector::SimpleSelector simple_selector {
311 type,
312 Selector::SimpleSelector::PseudoClass::None,
313 String::copy(buffer),
314 Selector::SimpleSelector::AttributeMatchType::None,
315 String(),
316 String()
317 };
318 buffer.clear();
319
320 if (peek() == '[') {
321 Selector::SimpleSelector::AttributeMatchType attribute_match_type = Selector::SimpleSelector::AttributeMatchType::HasAttribute;
322 String attribute_name;
323 String attribute_value;
324 bool in_value = false;
325 consume_specific('[');
326 char expected_end_of_attribute_selector = ']';
327 while (peek() != expected_end_of_attribute_selector) {
328 char ch = consume_one();
329 if (ch == '=') {
330 attribute_match_type = Selector::SimpleSelector::AttributeMatchType::ExactValueMatch;
331 attribute_name = String::copy(buffer);
332 buffer.clear();
333 in_value = true;
334 consume_whitespace_or_comments();
335 if (peek() == '\'') {
336 expected_end_of_attribute_selector = '\'';
337 consume_one();
338 } else if (peek() == '"') {
339 expected_end_of_attribute_selector = '"';
340 consume_one();
341 }
342 continue;
343 }
344 buffer.append(ch);
345 }
346 if (in_value)
347 attribute_value = String::copy(buffer);
348 else
349 attribute_name = String::copy(buffer);
350 buffer.clear();
351 simple_selector.attribute_match_type = attribute_match_type;
352 simple_selector.attribute_name = attribute_name;
353 simple_selector.attribute_value = attribute_value;
354 if (expected_end_of_attribute_selector != ']')
355 consume_specific(expected_end_of_attribute_selector);
356 consume_whitespace_or_comments();
357 consume_specific(']');
358 }
359
360 if (peek() == ':') {
361 // FIXME: Implement pseudo elements.
362 [[maybe_unused]] bool is_pseudo_element = false;
363 consume_one();
364 if (peek() == ':') {
365 is_pseudo_element = true;
366 consume_one();
367 }
368 if (next_is("not")) {
369 buffer.append(consume_one());
370 buffer.append(consume_one());
371 buffer.append(consume_one());
372 buffer.append(consume_specific('('));
373 while (peek() != ')')
374 buffer.append(consume_one());
375 buffer.append(consume_specific(')'));
376 } else {
377 while (is_valid_selector_char(peek()))
378 buffer.append(consume_one());
379 }
380
381 auto pseudo_name = String::copy(buffer);
382 buffer.clear();
383
384 if (pseudo_name == "link")
385 simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Link;
386 else if (pseudo_name == "hover")
387 simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Hover;
388 else if (pseudo_name == "first-child")
389 simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::FirstChild;
390 else if (pseudo_name == "last-child")
391 simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::LastChild;
392 else if (pseudo_name == "only-child")
393 simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::OnlyChild;
394 else if (pseudo_name == "empty")
395 simple_selector.pseudo_class = Selector::SimpleSelector::PseudoClass::Empty;
396 }
397
398 return simple_selector;
399 }
400
401 Optional<Selector::ComplexSelector> parse_complex_selector()
402 {
403 auto relation = Selector::ComplexSelector::Relation::Descendant;
404
405 if (peek() == '{' || peek() == ',')
406 return {};
407
408 if (is_combinator(peek())) {
409 switch (peek()) {
410 case '>':
411 relation = Selector::ComplexSelector::Relation::ImmediateChild;
412 break;
413 case '+':
414 relation = Selector::ComplexSelector::Relation::AdjacentSibling;
415 break;
416 case '~':
417 relation = Selector::ComplexSelector::Relation::GeneralSibling;
418 break;
419 }
420 consume_one();
421 consume_whitespace_or_comments();
422 }
423
424 consume_whitespace_or_comments();
425
426 Vector<Selector::SimpleSelector> simple_selectors;
427 for (;;) {
428 auto component = parse_simple_selector();
429 if (!component.has_value())
430 break;
431 simple_selectors.append(component.value());
432 // If this assert triggers, we're most likely up to no good.
433 PARSE_ASSERT(simple_selectors.size() < 100);
434 }
435
436 return Selector::ComplexSelector { relation, move(simple_selectors) };
437 }
438
439 void parse_selector()
440 {
441 Vector<Selector::ComplexSelector> complex_selectors;
442
443 for (;;) {
444 auto complex_selector = parse_complex_selector();
445 if (complex_selector.has_value())
446 complex_selectors.append(complex_selector.value());
447 consume_whitespace_or_comments();
448 if (!peek() || peek() == ',' || peek() == '{')
449 break;
450 }
451
452 if (complex_selectors.is_empty())
453 return;
454 complex_selectors.first().relation = Selector::ComplexSelector::Relation::None;
455
456 current_rule.selectors.append(Selector(move(complex_selectors)));
457 }
458
459 Optional<Selector> parse_individual_selector()
460 {
461 parse_selector();
462 if (current_rule.selectors.is_empty())
463 return {};
464 return current_rule.selectors.last();
465 }
466
467 void parse_selector_list()
468 {
469 for (;;) {
470 parse_selector();
471 consume_whitespace_or_comments();
472 if (peek() == ',') {
473 consume_one();
474 continue;
475 }
476 if (peek() == '{')
477 break;
478 }
479 }
480
481 bool is_valid_property_name_char(char ch) const
482 {
483 return ch && !isspace(ch) && ch != ':';
484 }
485
486 bool is_valid_property_value_char(char ch) const
487 {
488 return ch && ch != '!' && ch != ';' && ch != '}';
489 }
490
491 struct ValueAndImportant {
492 String value;
493 bool important { false };
494 };
495
496 ValueAndImportant consume_css_value()
497 {
498 buffer.clear();
499
500 int paren_nesting_level = 0;
501 bool important = false;
502
503 for (;;) {
504 char ch = peek();
505 if (ch == '(') {
506 ++paren_nesting_level;
507 buffer.append(consume_one());
508 continue;
509 }
510 if (ch == ')') {
511 PARSE_ASSERT(paren_nesting_level > 0);
512 --paren_nesting_level;
513 buffer.append(consume_one());
514 continue;
515 }
516 if (paren_nesting_level > 0) {
517 buffer.append(consume_one());
518 continue;
519 }
520 if (next_is("!important")) {
521 consume_specific('!');
522 consume_specific('i');
523 consume_specific('m');
524 consume_specific('p');
525 consume_specific('o');
526 consume_specific('r');
527 consume_specific('t');
528 consume_specific('a');
529 consume_specific('n');
530 consume_specific('t');
531 important = true;
532 continue;
533 }
534 if (next_is("/*")) {
535 consume_whitespace_or_comments();
536 continue;
537 }
538 if (!ch)
539 break;
540 if (ch == '}')
541 break;
542 if (ch == ';')
543 break;
544 buffer.append(consume_one());
545 }
546
547 // Remove trailing whitespace.
548 while (!buffer.is_empty() && isspace(buffer.last()))
549 buffer.take_last();
550
551 auto string = String::copy(buffer);
552 buffer.clear();
553
554 return { string, important };
555 }
556
557 Optional<StyleProperty> parse_property()
558 {
559 consume_whitespace_or_comments();
560 if (peek() == ';') {
561 consume_one();
562 return {};
563 }
564 if (peek() == '}')
565 return {};
566 buffer.clear();
567 while (is_valid_property_name_char(peek()))
568 buffer.append(consume_one());
569 auto property_name = String::copy(buffer);
570 buffer.clear();
571 consume_whitespace_or_comments();
572 consume_specific(':');
573 consume_whitespace_or_comments();
574
575 auto [property_value, important] = consume_css_value();
576
577 consume_whitespace_or_comments();
578
579 if (peek() && peek() != '}')
580 consume_specific(';');
581
582 auto property_id = CSS::property_id_from_string(property_name);
583 return StyleProperty { property_id, parse_css_value(property_value), important };
584 }
585
586 void parse_declaration()
587 {
588 for (;;) {
589 auto property = parse_property();
590 if (property.has_value())
591 current_rule.properties.append(property.value());
592 consume_whitespace_or_comments();
593 if (peek() == '}')
594 break;
595 }
596 }
597
598 void parse_rule()
599 {
600 consume_whitespace_or_comments();
601 if (index >= css.length())
602 return;
603
604 // FIXME: We ignore @-rules for now.
605 if (peek() == '@') {
606 while (peek() != '{')
607 consume_one();
608 int level = 0;
609 for (;;) {
610 auto ch = consume_one();
611 if (ch == '{') {
612 ++level;
613 } else if (ch == '}') {
614 --level;
615 if (level == 0)
616 break;
617 }
618 }
619 consume_whitespace_or_comments();
620 return;
621 }
622
623 parse_selector_list();
624 consume_specific('{');
625 parse_declaration();
626 consume_specific('}');
627 rules.append(StyleRule::create(move(current_rule.selectors), StyleDeclaration::create(move(current_rule.properties))));
628 consume_whitespace_or_comments();
629 }
630
631 RefPtr<StyleSheet> parse_sheet()
632 {
633 while (index < css.length()) {
634 parse_rule();
635 }
636
637 return StyleSheet::create(move(rules));
638 }
639
640 RefPtr<StyleDeclaration> parse_standalone_declaration()
641 {
642 consume_whitespace_or_comments();
643 for (;;) {
644 auto property = parse_property();
645 if (property.has_value())
646 current_rule.properties.append(property.value());
647 consume_whitespace_or_comments();
648 if (!peek())
649 break;
650 }
651 return StyleDeclaration::create(move(current_rule.properties));
652 }
653
654private:
655 NonnullRefPtrVector<StyleRule> rules;
656
657 struct CurrentRule {
658 Vector<Selector> selectors;
659 Vector<StyleProperty> properties;
660 };
661
662 CurrentRule current_rule;
663 Vector<char> buffer;
664
665 size_t index = 0;
666
667 StringView css;
668};
669
670Optional<Selector> parse_selector(const StringView& selector_text)
671{
672 CSSParser parser(selector_text);
673 return parser.parse_individual_selector();
674}
675
676RefPtr<StyleSheet> parse_css(const StringView& css)
677{
678 CSSParser parser(css);
679 return parser.parse_sheet();
680}
681
682RefPtr<StyleDeclaration> parse_css_declaration(const StringView& css)
683{
684 CSSParser parser(css);
685 return parser.parse_standalone_declaration();
686}
687
688}