1 | #include "sass.hpp" |
2 | #include "sass.h" |
3 | #include "ast.hpp" |
4 | #include "util.hpp" |
5 | #include "util_string.hpp" |
6 | #include "lexer.hpp" |
7 | #include "prelexer.hpp" |
8 | #include "constants.hpp" |
9 | #include "utf8/checked.h" |
10 | |
11 | #include <cmath> |
12 | #include <stdint.h> |
13 | #if defined(_MSC_VER) && _MSC_VER >= 1800 && _MSC_VER < 1900 && defined(_M_X64) |
14 | #include <mutex> |
15 | #endif |
16 | |
17 | namespace Sass { |
18 | |
19 | double round(double val, size_t precision) |
20 | { |
21 | // Disable FMA3-optimized implementation when compiling with VS2013 for x64 targets |
22 | // See https://github.com/sass/node-sass/issues/1854 for details |
23 | // FIXME: Remove this workaround when we switch to VS2015+ |
24 | #if defined(_MSC_VER) && _MSC_VER >= 1800 && _MSC_VER < 1900 && defined(_M_X64) |
25 | static std::once_flag flag; |
26 | std::call_once(flag, []() { _set_FMA3_enable(0); }); |
27 | #endif |
28 | |
29 | // https://github.com/sass/sass/commit/4e3e1d5684cc29073a507578fc977434ff488c93 |
30 | if (std::fmod(x: val, y: 1) - 0.5 > - std::pow(x: 0.1, y: precision + 1)) return std::ceil(x: val); |
31 | else if (std::fmod(x: val, y: 1) - 0.5 > std::pow(x: 0.1, y: precision)) return std::floor(x: val); |
32 | // work around some compiler issue |
33 | // cygwin has it not defined in std |
34 | using namespace std; |
35 | return ::round(x: val); |
36 | } |
37 | |
38 | /* Locale unspecific atof function. */ |
39 | double sass_strtod(const char *str) |
40 | { |
41 | char separator = *(localeconv()->decimal_point); |
42 | if(separator != '.'){ |
43 | // The current locale specifies another |
44 | // separator. convert the separator to the |
45 | // one understood by the locale if needed |
46 | const char *found = strchr(s: str, c: '.'); |
47 | if(found != NULL){ |
48 | // substitution is required. perform the substitution on a copy |
49 | // of the string. This is slower but it is thread safe. |
50 | char *copy = sass_copy_c_string(str); |
51 | *(copy + (found - str)) = separator; |
52 | double res = strtod(nptr: copy, NULL); |
53 | free(ptr: copy); |
54 | return res; |
55 | } |
56 | } |
57 | |
58 | return strtod(nptr: str, NULL); |
59 | } |
60 | |
61 | // helper for safe access to c_ctx |
62 | const char* safe_str (const char* str, const char* alt) { |
63 | return str == NULL ? alt : str; |
64 | } |
65 | |
66 | void free_string_array(char ** arr) { |
67 | if(!arr) |
68 | return; |
69 | |
70 | char **it = arr; |
71 | while (it && (*it)) { |
72 | free(ptr: *it); |
73 | ++it; |
74 | } |
75 | |
76 | free(ptr: arr); |
77 | } |
78 | |
79 | char **copy_strings(const sass::vector<sass::string>& strings, char*** array, int skip) { |
80 | int num = static_cast<int>(strings.size()) - skip; |
81 | char** arr = (char**) calloc(nmemb: num + 1, size: sizeof(char*)); |
82 | if (arr == 0) |
83 | return *array = (char **)NULL; |
84 | |
85 | for(int i = 0; i < num; i++) { |
86 | arr[i] = (char*) malloc(size: sizeof(char) * (strings[i + skip].size() + 1)); |
87 | if (arr[i] == 0) { |
88 | free_string_array(arr); |
89 | return *array = (char **)NULL; |
90 | } |
91 | std::copy(first: strings[i + skip].begin(), last: strings[i + skip].end(), result: arr[i]); |
92 | arr[i][strings[i + skip].size()] = '\0'; |
93 | } |
94 | |
95 | arr[num] = 0; |
96 | return *array = arr; |
97 | } |
98 | |
99 | // read css string (handle multiline DELIM) |
100 | sass::string read_css_string(const sass::string& str, bool css) |
101 | { |
102 | if (!css) return str; |
103 | sass::string out("" ); |
104 | bool esc = false; |
105 | for (auto i : str) { |
106 | if (i == '\\') { |
107 | esc = ! esc; |
108 | } else if (esc && i == '\r') { |
109 | continue; |
110 | } else if (esc && i == '\n') { |
111 | out.resize (n: out.size () - 1); |
112 | esc = false; |
113 | continue; |
114 | } else { |
115 | esc = false; |
116 | } |
117 | out.push_back(c: i); |
118 | } |
119 | // happens when parsing does not correctly skip |
120 | // over escaped sequences for ie. interpolations |
121 | // one example: foo\#{interpolate} |
122 | // if (esc) out += '\\'; |
123 | return out; |
124 | } |
125 | |
126 | // double escape all escape sequences |
127 | // keep unescaped quotes and backslashes |
128 | sass::string evacuate_escapes(const sass::string& str) |
129 | { |
130 | sass::string out("" ); |
131 | bool esc = false; |
132 | for (auto i : str) { |
133 | if (i == '\\' && !esc) { |
134 | out += '\\'; |
135 | out += '\\'; |
136 | esc = true; |
137 | } else if (esc && i == '"') { |
138 | out += '\\'; |
139 | out += i; |
140 | esc = false; |
141 | } else if (esc && i == '\'') { |
142 | out += '\\'; |
143 | out += i; |
144 | esc = false; |
145 | } else if (esc && i == '\\') { |
146 | out += '\\'; |
147 | out += i; |
148 | esc = false; |
149 | } else { |
150 | esc = false; |
151 | out += i; |
152 | } |
153 | } |
154 | // happens when parsing does not correctly skip |
155 | // over escaped sequences for ie. interpolations |
156 | // one example: foo\#{interpolate} |
157 | // if (esc) out += '\\'; |
158 | return out; |
159 | } |
160 | |
161 | // bell characters are replaced with spaces |
162 | void newline_to_space(sass::string& str) |
163 | { |
164 | std::replace(first: str.begin(), last: str.end(), old_value: '\n', new_value: ' '); |
165 | } |
166 | |
167 | // 1. Removes whitespace after newlines. |
168 | // 2. Replaces newlines with spaces. |
169 | // |
170 | // This method only considers LF and CRLF as newlines. |
171 | sass::string string_to_output(const sass::string& str) |
172 | { |
173 | sass::string result; |
174 | result.reserve(res_arg: str.size()); |
175 | std::size_t pos = 0; |
176 | while (true) { |
177 | const std::size_t newline = str.find_first_of(s: "\n\r" , pos: pos); |
178 | if (newline == sass::string::npos) break; |
179 | result.append(str: str, pos: pos, n: newline - pos); |
180 | if (str[newline] == '\r') { |
181 | if (str[newline + 1] == '\n') { |
182 | pos = newline + 2; |
183 | } else { |
184 | // CR without LF: append as-is and continue. |
185 | result += '\r'; |
186 | pos = newline + 1; |
187 | continue; |
188 | } |
189 | } else { |
190 | pos = newline + 1; |
191 | } |
192 | result += ' '; |
193 | const std::size_t non_space = str.find_first_not_of(s: " \f\n\r\t\v" , pos: pos); |
194 | if (non_space != sass::string::npos) { |
195 | pos = non_space; |
196 | } |
197 | } |
198 | result.append(str: str, pos: pos, n: sass::string::npos); |
199 | return result; |
200 | } |
201 | |
202 | sass::string escape_string(const sass::string& str) |
203 | { |
204 | sass::string out; |
205 | out.reserve(res_arg: str.size()); |
206 | for (char c : str) { |
207 | switch (c) { |
208 | case '\n': |
209 | out.append(s: "\\n" ); |
210 | break; |
211 | case '\r': |
212 | out.append(s: "\\r" ); |
213 | break; |
214 | case '\f': |
215 | out.append(s: "\\f" ); |
216 | break; |
217 | default: |
218 | out += c; |
219 | } |
220 | } |
221 | return out; |
222 | } |
223 | |
224 | sass::string (const sass::string& text) |
225 | { |
226 | sass::string str = "" ; |
227 | size_t has = 0; |
228 | char prev = 0; |
229 | bool clean = false; |
230 | for (auto i : text) { |
231 | if (clean) { |
232 | if (i == '\n') { has = 0; } |
233 | else if (i == '\t') { ++ has; } |
234 | else if (i == ' ') { ++ has; } |
235 | else if (i == '*') {} |
236 | else { |
237 | clean = false; |
238 | str += ' '; |
239 | if (prev == '*' && i == '/') str += "*/" ; |
240 | else str += i; |
241 | } |
242 | } else if (i == '\n') { |
243 | clean = true; |
244 | } else { |
245 | str += i; |
246 | } |
247 | prev = i; |
248 | } |
249 | if (has) return str; |
250 | else return text; |
251 | } |
252 | |
253 | // find best quote_mark by detecting if the string contains any single |
254 | // or double quotes. When a single quote is found, we not we want a double |
255 | // quote as quote_mark. Otherwise we check if the string cotains any double |
256 | // quotes, which will trigger the use of single quotes as best quote_mark. |
257 | char detect_best_quotemark(const char* s, char qm) |
258 | { |
259 | // ensure valid fallback quote_mark |
260 | char quote_mark = qm && qm != '*' ? qm : '"'; |
261 | while (*s) { |
262 | // force double quotes as soon |
263 | // as one single quote is found |
264 | if (*s == '\'') { return '"'; } |
265 | // a single does not force quote_mark |
266 | // maybe we see a double quote later |
267 | else if (*s == '"') { quote_mark = '\''; } |
268 | ++ s; |
269 | } |
270 | return quote_mark; |
271 | } |
272 | |
273 | sass::string read_hex_escapes(const sass::string& s) |
274 | { |
275 | |
276 | sass::string result; |
277 | bool skipped = false; |
278 | |
279 | for (size_t i = 0, L = s.length(); i < L; ++i) { |
280 | |
281 | // implement the same strange ruby sass behavior |
282 | // an escape sequence can also mean a unicode char |
283 | if (s[i] == '\\' && !skipped) { |
284 | |
285 | // remember |
286 | skipped = true; |
287 | |
288 | // escape length |
289 | size_t len = 1; |
290 | |
291 | // parse as many sequence chars as possible |
292 | // ToDo: Check if ruby aborts after possible max |
293 | while (i + len < L && s[i + len] && Util::ascii_isxdigit(c: static_cast<unsigned char>(s[i + len]))) ++ len; |
294 | |
295 | if (len > 1) { |
296 | |
297 | // convert the extracted hex string to code point value |
298 | // ToDo: Maybe we could do this without creating a substring |
299 | uint32_t cp = strtol(nptr: s.substr (pos: i + 1, n: len - 1).c_str(), NULL, base: 16); |
300 | |
301 | if (s[i + len] == ' ') ++ len; |
302 | |
303 | // assert invalid code points |
304 | if (cp == 0) cp = 0xFFFD; |
305 | // replace bell character |
306 | // if (cp == '\n') cp = 32; |
307 | |
308 | // use a very simple approach to convert via utf8 lib |
309 | // maybe there is a more elegant way; maybe we shoud |
310 | // convert the whole output from string to a stream!? |
311 | // allocate memory for utf8 char and convert to utf8 |
312 | unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, result: u); |
313 | for(size_t m = 0; m < 5 && u[m]; m++) result.push_back(c: u[m]); |
314 | |
315 | // skip some more chars? |
316 | i += len - 1; skipped = false; |
317 | |
318 | } |
319 | |
320 | else { |
321 | |
322 | skipped = false; |
323 | |
324 | result.push_back(c: s[i]); |
325 | |
326 | } |
327 | |
328 | } |
329 | |
330 | else { |
331 | |
332 | result.push_back(c: s[i]); |
333 | |
334 | } |
335 | |
336 | } |
337 | |
338 | return result; |
339 | |
340 | } |
341 | |
342 | sass::string unquote(const sass::string& s, char* qd, bool keep_utf8_sequences, bool strict) |
343 | { |
344 | |
345 | // not enough room for quotes |
346 | // no possibility to unquote |
347 | if (s.length() < 2) return s; |
348 | |
349 | char q; |
350 | bool skipped = false; |
351 | |
352 | // this is no guarantee that the unquoting will work |
353 | // what about whitespace before/after the quote_mark? |
354 | if (*s.begin() == '"' && *s.rbegin() == '"') q = '"'; |
355 | else if (*s.begin() == '\'' && *s.rbegin() == '\'') q = '\''; |
356 | else return s; |
357 | |
358 | sass::string unq; |
359 | unq.reserve(res_arg: s.length()-2); |
360 | |
361 | for (size_t i = 1, L = s.length() - 1; i < L; ++i) { |
362 | |
363 | // implement the same strange ruby sass behavior |
364 | // an escape sequence can also mean a unicode char |
365 | if (s[i] == '\\' && !skipped) { |
366 | // remember |
367 | skipped = true; |
368 | |
369 | // skip it |
370 | // ++ i; |
371 | |
372 | // if (i == L) break; |
373 | |
374 | // escape length |
375 | size_t len = 1; |
376 | |
377 | // parse as many sequence chars as possible |
378 | // ToDo: Check if ruby aborts after possible max |
379 | while (i + len < L && s[i + len] && Util::ascii_isxdigit(c: static_cast<unsigned char>(s[i + len]))) ++ len; |
380 | |
381 | // hex string? |
382 | if (keep_utf8_sequences) { |
383 | unq.push_back(c: s[i]); |
384 | } else if (len > 1) { |
385 | |
386 | // convert the extracted hex string to code point value |
387 | // ToDo: Maybe we could do this without creating a substring |
388 | uint32_t cp = strtol(nptr: s.substr (pos: i + 1, n: len - 1).c_str(), NULL, base: 16); |
389 | |
390 | if (s[i + len] == ' ') ++ len; |
391 | |
392 | // assert invalid code points |
393 | if (cp == 0) cp = 0xFFFD; |
394 | // replace bell character |
395 | // if (cp == '\n') cp = 32; |
396 | |
397 | // use a very simple approach to convert via utf8 lib |
398 | // maybe there is a more elegant way; maybe we shoud |
399 | // convert the whole output from string to a stream!? |
400 | // allocate memory for utf8 char and convert to utf8 |
401 | unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, result: u); |
402 | for(size_t m = 0; m < 5 && u[m]; m++) unq.push_back(c: u[m]); |
403 | |
404 | // skip some more chars? |
405 | i += len - 1; skipped = false; |
406 | |
407 | } |
408 | |
409 | |
410 | } |
411 | // check for unexpected delimiter |
412 | // be strict and throw error back |
413 | // else if (!skipped && q == s[i]) { |
414 | // // don't be that strict |
415 | // return s; |
416 | // // this basically always means an internal error and not users fault |
417 | // error("Unescaped delimiter in string to unquote found. [" + s + "]", SourceSpan("[UNQUOTE]")); |
418 | // } |
419 | else { |
420 | if (strict && !skipped) { |
421 | if (s[i] == q) return s; |
422 | } |
423 | skipped = false; |
424 | unq.push_back(c: s[i]); |
425 | } |
426 | |
427 | } |
428 | if (skipped) { return s; } |
429 | if (qd) *qd = q; |
430 | return unq; |
431 | |
432 | } |
433 | |
434 | sass::string quote(const sass::string& s, char q) |
435 | { |
436 | |
437 | // autodetect with fallback to given quote |
438 | q = detect_best_quotemark(s: s.c_str(), qm: q); |
439 | |
440 | // return an empty quoted string |
441 | if (s.empty()) return sass::string(2, q ? q : '"'); |
442 | |
443 | sass::string quoted; |
444 | quoted.reserve(res_arg: s.length()+2); |
445 | quoted.push_back(c: q); |
446 | |
447 | const char* it = s.c_str(); |
448 | const char* end = it + strlen(s: it) + 1; |
449 | while (*it && it < end) { |
450 | const char* now = it; |
451 | |
452 | if (*it == q) { |
453 | quoted.push_back(c: '\\'); |
454 | } else if (*it == '\\') { |
455 | quoted.push_back(c: '\\'); |
456 | } |
457 | |
458 | int cp = utf8::next(it, end); |
459 | |
460 | // in case of \r, check if the next in sequence |
461 | // is \n and then advance the iterator and skip \r |
462 | if (cp == '\r' && it < end && utf8::peek_next(it, end) == '\n') { |
463 | cp = utf8::next(it, end); |
464 | } |
465 | |
466 | if (cp == '\n') { |
467 | quoted.push_back(c: '\\'); |
468 | quoted.push_back(c: 'a'); |
469 | // we hope we can remove this flag once we figure out |
470 | // why ruby sass has these different output behaviors |
471 | // gsub(/\n(?![a-fA-F0-9\s])/, "\\a").gsub("\n", "\\a ") |
472 | using namespace Prelexer; |
473 | if (alternatives < |
474 | Prelexer::char_range<'a', 'f'>, |
475 | Prelexer::char_range<'A', 'F'>, |
476 | Prelexer::char_range<'0', '9'>, |
477 | space |
478 | >(src: it) != NULL) { |
479 | quoted.push_back(c: ' '); |
480 | } |
481 | } else if (cp < 127) { |
482 | quoted.push_back(c: (char) cp); |
483 | } else { |
484 | while (now < it) { |
485 | quoted.push_back(c: *now); |
486 | ++ now; |
487 | } |
488 | } |
489 | } |
490 | |
491 | quoted.push_back(c: q); |
492 | return quoted; |
493 | } |
494 | |
495 | bool is_hex_doublet(double n) |
496 | { |
497 | return n == 0x00 || n == 0x11 || n == 0x22 || n == 0x33 || |
498 | n == 0x44 || n == 0x55 || n == 0x66 || n == 0x77 || |
499 | n == 0x88 || n == 0x99 || n == 0xAA || n == 0xBB || |
500 | n == 0xCC || n == 0xDD || n == 0xEE || n == 0xFF ; |
501 | } |
502 | |
503 | bool is_color_doublet(double r, double g, double b) |
504 | { |
505 | return is_hex_doublet(n: r) && is_hex_doublet(n: g) && is_hex_doublet(n: b); |
506 | } |
507 | |
508 | bool peek_linefeed(const char* start) |
509 | { |
510 | using namespace Prelexer; |
511 | using namespace Constants; |
512 | return sequence < |
513 | zero_plus < |
514 | alternatives < |
515 | exactly <' '>, |
516 | exactly <'\t'>, |
517 | line_comment, |
518 | block_comment, |
519 | delimited_by < |
520 | slash_star, |
521 | star_slash, |
522 | false |
523 | > |
524 | > |
525 | >, |
526 | re_linebreak |
527 | >(src: start) != 0; |
528 | } |
529 | |
530 | namespace Util { |
531 | |
532 | bool isPrintable(StyleRule* r, Sass_Output_Style style) { |
533 | if (r == NULL) { |
534 | return false; |
535 | } |
536 | |
537 | Block_Obj b = r->block(); |
538 | |
539 | SelectorList* sl = r->selector(); |
540 | bool hasSelectors = sl ? sl->length() > 0 : false; |
541 | |
542 | if (!hasSelectors) { |
543 | return false; |
544 | } |
545 | |
546 | bool hasDeclarations = false; |
547 | bool hasPrintableChildBlocks = false; |
548 | for (size_t i = 0, L = b->length(); i < L; ++i) { |
549 | Statement_Obj stm = b->at(i); |
550 | if (Cast<AtRule>(ptr: stm)) { |
551 | return true; |
552 | } else if (Declaration* d = Cast<Declaration>(ptr: stm)) { |
553 | return isPrintable(d, style); |
554 | } else if (ParentStatement* p = Cast<ParentStatement>(ptr: stm)) { |
555 | Block_Obj pChildBlock = p->block(); |
556 | if (isPrintable(b: pChildBlock, style)) { |
557 | hasPrintableChildBlocks = true; |
558 | } |
559 | } else if (Comment* c = Cast<Comment>(ptr: stm)) { |
560 | // keep for uncompressed |
561 | if (style != COMPRESSED) { |
562 | hasDeclarations = true; |
563 | } |
564 | // output style compressed |
565 | if (c->is_important()) { |
566 | hasDeclarations = c->is_important(); |
567 | } |
568 | } else { |
569 | hasDeclarations = true; |
570 | } |
571 | |
572 | if (hasDeclarations || hasPrintableChildBlocks) { |
573 | return true; |
574 | } |
575 | } |
576 | |
577 | return false; |
578 | } |
579 | |
580 | bool isPrintable(String_Constant* s, Sass_Output_Style style) |
581 | { |
582 | return ! s->value().empty(); |
583 | } |
584 | |
585 | bool isPrintable(String_Quoted* s, Sass_Output_Style style) |
586 | { |
587 | return true; |
588 | } |
589 | |
590 | bool isPrintable(Declaration* d, Sass_Output_Style style) |
591 | { |
592 | ExpressionObj val = d->value(); |
593 | if (String_Quoted_Obj sq = Cast<String_Quoted>(ptr: val)) return isPrintable(s: sq.ptr(), style); |
594 | if (String_Constant_Obj sc = Cast<String_Constant>(ptr: val)) return isPrintable(s: sc.ptr(), style); |
595 | return true; |
596 | } |
597 | |
598 | bool isPrintable(SupportsRule* f, Sass_Output_Style style) { |
599 | if (f == NULL) { |
600 | return false; |
601 | } |
602 | |
603 | Block_Obj b = f->block(); |
604 | |
605 | bool hasDeclarations = false; |
606 | bool hasPrintableChildBlocks = false; |
607 | for (size_t i = 0, L = b->length(); i < L; ++i) { |
608 | Statement_Obj stm = b->at(i); |
609 | if (Cast<Declaration>(ptr: stm) || Cast<AtRule>(ptr: stm)) { |
610 | hasDeclarations = true; |
611 | } |
612 | else if (ParentStatement* b = Cast<ParentStatement>(ptr: stm)) { |
613 | Block_Obj pChildBlock = b->block(); |
614 | if (!b->is_invisible()) { |
615 | if (isPrintable(b: pChildBlock, style)) { |
616 | hasPrintableChildBlocks = true; |
617 | } |
618 | } |
619 | } |
620 | |
621 | if (hasDeclarations || hasPrintableChildBlocks) { |
622 | return true; |
623 | } |
624 | } |
625 | |
626 | return false; |
627 | } |
628 | |
629 | bool isPrintable(CssMediaRule* m, Sass_Output_Style style) |
630 | { |
631 | if (m == nullptr) return false; |
632 | Block_Obj b = m->block(); |
633 | if (b == nullptr) return false; |
634 | if (m->empty()) return false; |
635 | for (size_t i = 0, L = b->length(); i < L; ++i) { |
636 | Statement_Obj stm = b->at(i); |
637 | if (Cast<AtRule>(ptr: stm)) return true; |
638 | else if (Cast<Declaration>(ptr: stm)) return true; |
639 | else if (Comment* c = Cast<Comment>(ptr: stm)) { |
640 | if (isPrintable(b: c, style)) { |
641 | return true; |
642 | } |
643 | } |
644 | else if (StyleRule* r = Cast<StyleRule>(ptr: stm)) { |
645 | if (isPrintable(r, style)) { |
646 | return true; |
647 | } |
648 | } |
649 | else if (SupportsRule* f = Cast<SupportsRule>(ptr: stm)) { |
650 | if (isPrintable(f, style)) { |
651 | return true; |
652 | } |
653 | } |
654 | else if (CssMediaRule* mb = Cast<CssMediaRule>(ptr: stm)) { |
655 | if (isPrintable(m: mb, style)) { |
656 | return true; |
657 | } |
658 | } |
659 | else if (ParentStatement* b = Cast<ParentStatement>(ptr: stm)) { |
660 | if (isPrintable(b: b->block(), style)) { |
661 | return true; |
662 | } |
663 | } |
664 | } |
665 | return false; |
666 | } |
667 | |
668 | bool (Comment* c, Sass_Output_Style style) |
669 | { |
670 | // keep for uncompressed |
671 | if (style != COMPRESSED) { |
672 | return true; |
673 | } |
674 | // output style compressed |
675 | if (c->is_important()) { |
676 | return true; |
677 | } |
678 | // not printable |
679 | return false; |
680 | }; |
681 | |
682 | bool isPrintable(Block_Obj b, Sass_Output_Style style) { |
683 | if (!b) { |
684 | return false; |
685 | } |
686 | |
687 | for (size_t i = 0, L = b->length(); i < L; ++i) { |
688 | Statement_Obj stm = b->at(i); |
689 | if (Cast<Declaration>(ptr: stm) || Cast<AtRule>(ptr: stm)) { |
690 | return true; |
691 | } |
692 | else if (Comment* c = Cast<Comment>(ptr: stm)) { |
693 | if (isPrintable(c, style)) { |
694 | return true; |
695 | } |
696 | } |
697 | else if (StyleRule* r = Cast<StyleRule>(ptr: stm)) { |
698 | if (isPrintable(r, style)) { |
699 | return true; |
700 | } |
701 | } |
702 | else if (SupportsRule* f = Cast<SupportsRule>(ptr: stm)) { |
703 | if (isPrintable(f, style)) { |
704 | return true; |
705 | } |
706 | } |
707 | else if (CssMediaRule * m = Cast<CssMediaRule>(ptr: stm)) { |
708 | if (isPrintable(m, style)) { |
709 | return true; |
710 | } |
711 | } |
712 | else if (ParentStatement* b = Cast<ParentStatement>(ptr: stm)) { |
713 | if (isPrintable(b: b->block(), style)) { |
714 | return true; |
715 | } |
716 | } |
717 | } |
718 | |
719 | return false; |
720 | } |
721 | |
722 | } |
723 | } |
724 | |