/[suikacvs]/markup/html/whatpm/t/CSS-Parser-1.t
Suika

Contents of /markup/html/whatpm/t/CSS-Parser-1.t

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.23 - (show annotations) (download) (as text)
Mon Sep 15 14:34:24 2008 UTC (16 years, 10 months ago) by wakaba
Branch: MAIN
CVS Tags: HEAD
Changes since 1.22: +5 -0 lines
File MIME type: application/x-troff
++ whatpm/t/ChangeLog	15 Sep 2008 14:34:19 -0000
2008-09-15  Wakaba  <wakaba@suika.fam.cx>

	* css-visual.dat: New test data for Firefox3's new 'width'
	values.

++ whatpm/Whatpm/CSS/ChangeLog	15 Sep 2008 14:34:00 -0000
2008-09-15  Wakaba  <wakaba@suika.fam.cx>

	* Parser.pm: Support for Firefox3's new 'width' keywords.

1 #!/usr/bin/perl
2 use strict;
3
4 use lib qw[/home/wakaba/work/manakai2/lib]; ## TODO: ...
5
6 use Test;
7
8 BEGIN { plan tests => 2210 }
9
10 require Whatpm::CSS::Parser;
11 require Message::DOM::Window;
12
13 require Message::DOM::DOMImplementation;
14 my $dom = Message::DOM::DOMImplementation->new;
15
16 my $DefaultComputed;
17 my $DefaultComputedText;
18
19 for my $file_name (map {"t/$_"} qw(
20 css-1.dat
21 css-2.dat
22 css-3.dat
23 css-4.dat
24 css-visual.dat
25 css-generated.dat
26 css-paged.dat
27 css-text.dat
28 css-font.dat
29 css-table.dat
30 css-interactive.dat
31 )) {
32 print "# $file_name\n";
33 open my $file, '<:utf8', $file_name or die "$0: $file_name: $!";
34
35 my $all_test = {document => {}, test => []};
36 my $test;
37 my $mode = 'data';
38 my $doc_id;
39 my $selectors;
40 while (<$file>) {
41 s/\x0D\x0A/\x0A/;
42 if (/^#data$/) {
43 undef $test;
44 $test->{data} = '';
45 push @{$all_test->{test}}, $test;
46 $mode = 'data';
47 } elsif (/#(csstext|cssom)$/) {
48 $test->{$1} = '';
49 $mode = $1;
50 } elsif (/#(computed(?>text)?) (\S+) (.+)$/) {
51 $test->{$1}->{$doc_id = $2}->{$selectors = $3} = '';
52 $mode = $1;
53 } elsif (/^#html (\S+)$/) {
54 undef $test;
55 $test->{format} = 'html';
56 $test->{data} = '';
57 $all_test->{document}->{$1} = $test;
58 $mode = 'data';
59 } elsif (/^#errors$/) {
60 $test->{errors} = [];
61 $mode = 'errors';
62 $test->{data} =~ s/\x0D?\x0A\z//;
63 } elsif (/^#option q$/) {
64 $test->{option}->{parse_mode} = 'q';
65 } elsif (defined $test->{data} and /^$/) {
66 undef $test;
67 } else {
68 if ({data => 1, cssom => 1, csstext => 1}->{$mode}) {
69 $test->{$mode} .= $_;
70 } elsif ($mode eq 'computed' or $mode eq 'computedtext') {
71 $test->{$mode}->{$doc_id}->{$selectors} .= $_;
72 } elsif ($mode eq 'errors') {
73 tr/\x0D\x0A//d;
74 push @{$test->{errors}}, $_;
75 } else {
76 die "Line $.: $_";
77 }
78 }
79 }
80
81 for my $data (values %{$all_test->{document}}) {
82 if ($data->{format} eq 'html') {
83 my $doc = $dom->create_document;
84 $doc->manakai_is_html (1);
85 $doc->inner_html ($data->{data});
86 $data->{document} = $doc;
87 } else {
88 die "Test data format $data->{format} is not supported";
89 }
90 }
91
92 for my $test (@{$all_test->{test}}) {
93 my ($p, $css_options) = get_parser ($test->{option}->{parse_mode});
94
95 my @actual_error;
96 $p->{onerror} = sub {
97 my (%opt) = @_;
98 my $uri = ${$opt{uri}};
99 $uri =~ s[^thismessage:/][];
100 push @actual_error, join ';',
101 $uri, $opt{token}->{line}, $opt{token}->{column},
102 $opt{level},
103 $opt{type} .
104 (defined $opt{text} ? ';'.$opt{text} : '');
105 };
106
107 my $ss = $p->parse_char_string ($test->{data});
108
109 ok ((join "\n", @actual_error), (join "\n", @{$test->{errors} or []}),
110 "#result ($test->{data})");
111
112 if (defined $test->{cssom}) {
113 my $actual = serialize_cssom ($ss);
114 ok $actual, $test->{cssom}, "#cssom ($test->{data})";
115 }
116
117 if (defined $test->{csstext}) {
118 my $actual = $ss->css_text;
119 ok $actual, $test->{csstext}, "#csstext ($test->{data})";
120 }
121
122 for my $doc_id (keys %{$test->{computed} or {}}) {
123 for my $selectors (keys %{$test->{computed}->{$doc_id}}) {
124 my ($window, $style) = get_computed_style
125 ($all_test, $doc_id, $selectors, $dom, $css_options, $ss);
126 ## NOTE: $window is the root object, so that we must keep it
127 ## referenced in this block.
128
129 my $actual = serialize_style ($style, '');
130 my $expected = $DefaultComputed;
131 my $diff = $test->{computed}->{$doc_id}->{$selectors};
132 ($actual, $expected) = apply_diff ($actual, $expected, $diff);
133 ok $actual, $expected,
134 "#computed $doc_id $selectors ($test->{data})";
135 }
136 }
137
138 for my $doc_id (keys %{$test->{computedtext} or {}}) {
139 for my $selectors (keys %{$test->{computedtext}->{$doc_id}}) {
140 my ($window, $style) = get_computed_style
141 ($all_test, $doc_id, $selectors, $dom, $css_options, $ss);
142 ## NOTE: $window is the root object, so that we must keep it
143 ## referenced in this block.
144
145 my $actual = $style->css_text;
146 my $expected = $DefaultComputedText;
147 my $diff = $test->{computedtext}->{$doc_id}->{$selectors};
148 ($actual, $expected) = apply_diff ($actual, $expected, $diff);
149 "#computedtext $doc_id $selectors ($test->{data})";
150 ok $actual, $expected,
151 "#computedtext $doc_id $selectors ($test->{data})";
152 }
153 }
154 }
155 }
156
157 my @longhand;
158 my @shorthand;
159 BEGIN {
160 @longhand = qw/
161 alignment-baseline
162 background-attachment background-color background-image
163 background-position-x background-position-y
164 background-repeat border-bottom-color
165 border-bottom-style border-bottom-width border-collapse
166 border-left-color
167 border-left-style border-left-width border-right-color
168 border-right-style border-right-width
169 -manakai-border-spacing-x -manakai-border-spacing-y
170 border-top-color border-top-style border-top-width bottom
171 caption-side clear clip color content counter-increment counter-reset
172 cursor direction display dominant-baseline empty-cells float
173 font-family font-size font-size-adjust font-stretch
174 font-style font-variant font-weight height left
175 letter-spacing line-height
176 list-style-image list-style-position list-style-type
177 margin-bottom margin-left margin-right margin-top marker-offset
178 marks max-height max-width min-height min-width opacity -moz-opacity
179 orphans outline-color outline-style outline-width overflow-x overflow-y
180 padding-bottom padding-left padding-right padding-top
181 page page-break-after page-break-before page-break-inside
182 position quotes right size table-layout
183 text-align text-anchor text-decoration text-indent text-transform
184 top unicode-bidi vertical-align visibility white-space width widows
185 word-spacing writing-mode z-index
186 /;
187 @shorthand = qw/
188 background background-position
189 border border-color border-style border-width border-spacing
190 border-top border-right border-bottom border-left
191 font list-style margin outline overflow padding
192 /;
193 $DefaultComputedText = q[ alignment-baseline: auto;
194 border-spacing: 0px;
195 background: transparent none repeat scroll 0% 0%;
196 border: 0px none -manakai-default;
197 border-collapse: separate;
198 bottom: auto;
199 caption-side: top;
200 clear: none;
201 clip: auto;
202 color: -manakai-default;
203 content: normal;
204 counter-increment: none;
205 counter-reset: none;
206 cursor: auto;
207 direction: ltr;
208 display: inline;
209 dominant-baseline: auto;
210 empty-cells: show;
211 float: none;
212 font-family: -manakai-default;
213 font-size: 16px;
214 font-size-adjust: none;
215 font-stretch: normal;
216 font-style: normal;
217 font-variant: normal;
218 font-weight: 400;
219 height: auto;
220 left: auto;
221 letter-spacing: normal;
222 line-height: normal;
223 list-style-image: none;
224 list-style-position: outside;
225 list-style-type: disc;
226 margin: 0px;
227 marker-offset: auto;
228 marks: none;
229 max-height: none;
230 max-width: none;
231 min-height: 0px;
232 min-width: 0px;
233 opacity: 1;
234 orphans: 2;
235 outline: 0px none invert;
236 overflow: visible;
237 padding: 0px;
238 page: auto;
239 page-break-after: auto;
240 page-break-before: auto;
241 page-break-inside: auto;
242 position: static;
243 quotes: -manakai-default;
244 right: auto;
245 size: auto;
246 table-layout: auto;
247 text-align: begin;
248 text-anchor: start;
249 text-decoration: none;
250 text-indent: 0px;
251 text-transform: none;
252 top: auto;
253 unicode-bidi: normal;
254 vertical-align: baseline;
255 visibility: visible;
256 white-space: normal;
257 widows: 2;
258 width: auto;
259 word-spacing: normal;
260 writing-mode: lr-tb;
261 z-index: auto;
262 ];
263 $DefaultComputed = $DefaultComputedText;
264 $DefaultComputed =~ s/^ /| /gm;
265 $DefaultComputed =~ s/;$//gm;
266 $DefaultComputed .= q[| -manakai-border-spacing-x: 0px
267 | -manakai-border-spacing-y: 0px
268 | -moz-opacity: 1
269 | background-attachment: scroll
270 | background-color: transparent
271 | background-image: none
272 | background-position: 0% 0%
273 | background-position-x: 0%
274 | background-position-y: 0%
275 | background-repeat: repeat
276 | border-top: 0px none -manakai-default
277 | border-right: 0px none -manakai-default
278 | border-bottom: 0px none -manakai-default
279 | border-left: 0px none -manakai-default
280 | border-bottom-color: -manakai-default
281 | border-bottom-style: none
282 | border-bottom-width: 0px
283 | border-left-color: -manakai-default
284 | border-left-style: none
285 | border-left-width: 0px
286 | border-right-color: -manakai-default
287 | border-right-style: none
288 | border-right-width: 0px
289 | border-top-color: -manakai-default
290 | border-top-style: none
291 | border-top-width: 0px
292 | border-color: -manakai-default
293 | border-style: none
294 | border-width: 0px
295 | float: none
296 | font: 400 16px -manakai-default
297 | list-style: disc none outside
298 | margin-top: 0px
299 | margin-right: 0px
300 | margin-bottom: 0px
301 | margin-left: 0px
302 | outline-color: invert
303 | outline-style: none
304 | outline-width: 0px
305 | overflow-x: visible
306 | overflow-y: visible
307 | padding-bottom: 0px
308 | padding-left: 0px
309 | padding-right: 0px
310 | padding-top: 0px];
311 }
312
313 sub get_parser ($) {
314 my $parse_mode = shift;
315
316 my $p = Whatpm::CSS::Parser->new;
317
318 $p->{prop}->{$_} = 1 for (@longhand, @shorthand);
319 $p->{prop_value}->{display}->{$_} = 1 for qw/
320 block clip inline inline-block inline-table list-item none
321 table table-caption table-cell table-column table-column-group
322 table-header-group table-footer-group table-row table-row-group
323 compact marker
324 /;
325 $p->{prop_value}->{position}->{$_} = 1 for qw/
326 absolute fixed relative static
327 /;
328 for (qw/-moz-max-content -moz-min-content -moz-fit-content -moz-available/) {
329 $p->{prop_value}->{width}->{$_} = 1;
330 $p->{prop_value}->{'min-width'}->{$_} = 1;
331 $p->{prop_value}->{'max-width'}->{$_} = 1;
332 }
333 $p->{prop_value}->{float}->{$_} = 1 for qw/
334 left right none
335 /;
336 $p->{prop_value}->{clear}->{$_} = 1 for qw/
337 left right none both
338 /;
339 $p->{prop_value}->{direction}->{ltr} = 1;
340 $p->{prop_value}->{direction}->{rtl} = 1;
341 $p->{prop_value}->{marks}->{crop} = 1;
342 $p->{prop_value}->{marks}->{cross} = 1;
343 $p->{prop_value}->{'unicode-bidi'}->{$_} = 1 for qw/
344 normal bidi-override embed
345 /;
346 for my $prop_name (qw/overflow overflow-x overflow-y/) {
347 $p->{prop_value}->{$prop_name}->{$_} = 1 for qw/
348 visible hidden scroll auto -webkit-marquee -moz-hidden-unscrollable
349 /;
350 }
351 $p->{prop_value}->{visibility}->{$_} = 1 for qw/
352 visible hidden collapse
353 /;
354 $p->{prop_value}->{'list-style-type'}->{$_} = 1 for qw/
355 disc circle square decimal decimal-leading-zero
356 lower-roman upper-roman lower-greek lower-latin
357 upper-latin armenian georgian lower-alpha upper-alpha none
358 hebrew cjk-ideographic hiragana katakana hiragana-iroha
359 katakana-iroha
360 /;
361 $p->{prop_value}->{'list-style-position'}->{outside} = 1;
362 $p->{prop_value}->{'list-style-position'}->{inside} = 1;
363 $p->{prop_value}->{'page-break-before'}->{$_} = 1 for qw/
364 auto always avoid left right
365 /;
366 $p->{prop_value}->{'page-break-after'}->{$_} = 1 for qw/
367 auto always avoid left right
368 /;
369 $p->{prop_value}->{'page-break-inside'}->{auto} = 1;
370 $p->{prop_value}->{'page-break-inside'}->{avoid} = 1;
371 $p->{prop_value}->{'background-repeat'}->{$_} = 1 for qw/
372 repeat repeat-x repeat-y no-repeat
373 /;
374 $p->{prop_value}->{'background-attachment'}->{scroll} = 1;
375 $p->{prop_value}->{'background-attachment'}->{fixed} = 1;
376 $p->{prop_value}->{'font-size'}->{$_} = 1 for qw/
377 xx-small x-small small medium large x-large xx-large
378 -manakai-xxx-large -webkit-xxx-large
379 larger smaller
380 /;
381 $p->{prop_value}->{'font-style'}->{normal} = 1;
382 $p->{prop_value}->{'font-style'}->{italic} = 1;
383 $p->{prop_value}->{'font-style'}->{oblique} = 1;
384 $p->{prop_value}->{'font-variant'}->{normal} = 1;
385 $p->{prop_value}->{'font-variant'}->{'small-caps'} = 1;
386 $p->{prop_value}->{'font-stretch'}->{$_} = 1 for
387 qw/normal wider narrower ultra-condensed extra-condensed
388 condensed semi-condensed semi-expanded expanded
389 extra-expanded ultra-expanded/;
390 $p->{prop_value}->{'text-align'}->{$_} = 1 for qw/
391 left right center justify begin end
392 /;
393 $p->{prop_value}->{'text-transform'}->{$_} = 1 for qw/
394 capitalize uppercase lowercase none
395 /;
396 $p->{prop_value}->{'white-space'}->{$_} = 1 for qw/
397 normal pre nowrap pre-line pre-wrap -moz-pre-wrap
398 /;
399 $p->{prop_value}->{'writing-mode'}->{$_} = 1 for qw/
400 lr rl tb lr-tb rl-tb tb-rl
401 /;
402 $p->{prop_value}->{'text-anchor'}->{$_} = 1 for qw/
403 start middle end
404 /;
405 $p->{prop_value}->{'dominant-baseline'}->{$_} = 1 for qw/
406 auto use-script no-change reset-size ideographic alphabetic
407 hanging mathematical central middle text-after-edge text-before-edge
408 /;
409 $p->{prop_value}->{'alignment-baseline'}->{$_} = 1 for qw/
410 auto baseline before-edge text-before-edge middle central
411 after-edge text-after-edge ideographic alphabetic hanging
412 mathematical
413 /;
414 $p->{prop_value}->{'text-decoration'}->{$_} = 1 for qw/
415 none blink underline overline line-through
416 /;
417 $p->{prop_value}->{'caption-side'}->{$_} = 1 for qw/
418 top bottom left right
419 /;
420 $p->{prop_value}->{'table-layout'}->{auto} = 1;
421 $p->{prop_value}->{'table-layout'}->{fixed} = 1;
422 $p->{prop_value}->{'border-collapse'}->{collapse} = 1;
423 $p->{prop_value}->{'border-collapse'}->{separate} = 1;
424 $p->{prop_value}->{'empty-cells'}->{show} = 1;
425 $p->{prop_value}->{'empty-cells'}->{hide} = 1;
426 $p->{prop_value}->{cursor}->{$_} = 1 for qw/
427 auto crosshair default pointer move e-resize ne-resize nw-resize n-resize
428 se-resize sw-resize s-resize w-resize text wait help progress
429 /;
430 for my $prop (qw/border-top-style border-left-style
431 border-bottom-style border-right-style outline-style/) {
432 $p->{prop_value}->{$prop}->{$_} = 1 for qw/
433 none hidden dotted dashed solid double groove ridge inset outset
434 /;
435 }
436 for my $prop (qw/color background-color
437 border-bottom-color border-left-color border-right-color
438 border-top-color border-color/) {
439 $p->{prop_value}->{$prop}->{transparent} = 1;
440 $p->{prop_value}->{$prop}->{flavor} = 1;
441 $p->{prop_value}->{$prop}->{'-manakai-default'} = 1;
442 }
443 $p->{prop_value}->{'outline-color'}->{invert} = 1;
444 $p->{prop_value}->{'outline-color'}->{'-manakai-invert-or-currentcolor'} = 1;
445 $p->{pseudo_class}->{$_} = 1 for qw/
446 active checked disabled empty enabled first-child first-of-type
447 focus hover indeterminate last-child last-of-type link only-child
448 only-of-type root target visited
449 lang nth-child nth-last-child nth-of-type nth-last-of-type not
450 -manakai-contains -manakai-current
451 /;
452 $p->{pseudo_element}->{$_} = 1 for qw/
453 after before first-letter first-line
454 /;
455
456 my $css_options = {
457 prop => $p->{prop},
458 prop_value => $p->{prop_value},
459 pseudo_class => $p->{pseudo_class},
460 pseudo_element => $p->{pseudo_element},
461 };
462
463 $p->init;
464 if ($parse_mode eq 'q') {
465 $p->{unitless_px} = 1;
466 $p->{hashless_color} = 1;
467 }
468 $p->{href} = 'thismessage:/';
469
470 return ($p, $css_options);
471 } # get_parser
472
473 sub serialize_cssom ($) {
474 my $ss = shift;
475
476 if (defined $ss) {
477 if ($ss->isa ('Message::IF::CSSStyleSheet')) {
478 my $v = '';
479 for my $rule (@{$ss->css_rules}) {
480 my $indent = '';
481 $v .= serialize_rule ($rule, $indent);
482 }
483 return $v;
484 } else {
485 return '(' . (ref $ss) . ')';
486 }
487 } else {
488 return '(undef)';
489 }
490 } # serialize_cssom
491
492 sub serialize_rule ($$) {
493 my ($rule, $indent) = @_;
494 my $v = '';
495 if ($rule->type == $rule->STYLE_RULE) {
496 $v .= '| ' . $indent . '<' . $rule->selector_text . ">\n";
497 $v .= serialize_style ($rule->style, $indent . ' ');
498 } elsif ($rule->type == $rule->MEDIA_RULE) {
499 $v .= '| ' . $indent . '@media ' . $rule->media . "\n";
500 $v .= serialize_rule ($_, $indent . ' ') for @{$rule->css_rules};
501 } elsif ($rule->type == $rule->NAMESPACE_RULE) {
502 $v .= '| ' . $indent . '@namespace ';
503 my $prefix = $rule->prefix;
504 $v .= $prefix . ': ' if length $prefix;
505 $v .= '<' . $rule->namespace_uri . ">\n";
506 } elsif ($rule->type == $rule->IMPORT_RULE) {
507 $v .= '| ' . $indent . '@import <' . $rule->href . '> ' . $rule->media;
508 $v .= "\n";
509 } elsif ($rule->type == $rule->CHARSET_RULE) {
510 $v .= '| ' . $indent . '@charset ' . $rule->encoding . "\n";
511 } else {
512 die "Rule type @{[$rule->type]} is not supported";
513 }
514 return $v;
515 } # serialize_rule
516
517 sub get_computed_style ($$$$$$) {
518 my ($all_test, $doc_id, $selectors, $dom, $css_options, $ss) = @_;
519
520 my $doc = $all_test->{document}->{$doc_id}->{document};
521 unless ($doc) {
522 die "Test document $doc_id is not defined";
523 }
524
525 my $element = $doc->query_selector ($selectors);
526 unless ($element) {
527 die "Element $selectors not found in document $doc_id";
528 }
529
530 my $window = Message::DOM::Window->___new ($dom);
531 $window->___set_css_options ($css_options);
532 $window->___set_user_style_sheets ([$ss]);
533 $window->set_document ($doc);
534
535 my $style = $element->manakai_computed_style;
536 return ($window, $style);
537 } # get_computed_style
538
539 sub serialize_style ($$) {
540 my ($style, $indent) = @_;
541
542 ## TODO: check @$style
543
544 my @v;
545 for (map {get_dom_names ($_)} @shorthand, @longhand) {
546 my $dom = $_->[1];
547 push @v, [$_->[0], $dom, $style->$dom,
548 $style->get_property_priority ($_->[0])];
549 $v[-1]->[3] = ' !' . $v[-1]->[3] if length $v[-1]->[3];
550 }
551 return join '', map {"| $indent$_->[0]: $_->[2]$_->[3]\n"}
552 sort {$a->[0] cmp $b->[0]} grep {length $_->[2]} @v;
553 } # serialize_style
554
555 sub get_dom_names ($) {
556 my $dom_name = $_[0];
557 if ($_[0] eq 'float') {
558 return ([$_[0] => 'css_float'], [$_[0] => 'style_float']);
559 }
560
561 $dom_name =~ tr/-/_/;
562 return ([$_[0] => $dom_name]);
563 } # get_dom_names
564
565 sub apply_diff ($$$) {
566 my ($actual, $expected, $diff) = @_;
567 my @actual = split /[\x0D\x0A]+/, $actual;
568 my @expected = split /[\x0D\x0A]+/, $expected;
569 my @diff = split /[\x0D\x0A]+/, $diff;
570 for (@diff) {
571 if (s/^-//) {
572 push @actual, $_;
573 } elsif (s/^\+//) {
574 push @expected, $_;
575 } else {
576 die "Invalid diff line: $_";
577 }
578 }
579 $actual = join "\n", sort {$a cmp $b} @actual;
580 $expected = join "\n", sort {$a cmp $b} @expected;
581 ($actual, $expected);
582 } # apply_diff

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24