/[suikacvs]/markup/html/whatpm/Whatpm/CSS/Parser.pm
Suika

Contents of /markup/html/whatpm/Whatpm/CSS/Parser.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.74 - (show annotations) (download)
Wed Sep 17 07:19:30 2008 UTC (16 years, 9 months ago) by wakaba
Branch: MAIN
CVS Tags: HEAD
Changes since 1.73: +6 -1 lines
++ ChangeLog	17 Sep 2008 07:19:23 -0000
	* readme.en.html: Add link to the CSS extension document.

2008-09-17  Wakaba  <wakaba@suika.fam.cx>

1 package Whatpm::CSS::Parser;
2 use strict;
3 use Whatpm::CSS::Tokenizer qw(:token);
4 require Whatpm::CSS::SelectorsParser;
5 require Whatpm::CSS::MediaQueryParser;
6
7 sub new ($) {
8 my $self = bless {
9 level => {
10 must => 'm',
11 uncertain => 'u',
12 },
13 lookup_namespace_uri => sub { undef },
14 }, shift;
15 # $self->{base_uri}
16 # $self->{unitless_px} = 1/0
17 # $self->{hashless_rgb} = 1/0
18
19 ## Default error handler
20 $self->{onerror} = sub {
21 my %opt = @_;
22 require Carp;
23 Carp::carp
24 (sprintf 'Document <%s>: Line %d column %d (token %s): %s%s',
25 defined $opt{uri} ? ${$opt{uri}} : 'thisdocument:/',
26 $opt{token}->{line},
27 $opt{token}->{column},
28 Whatpm::CSS::Tokenizer->serialize_token ($opt{token}),
29 $opt{type},
30 defined $opt{value} ? " (value $opt{value})" : '');
31 };
32
33 ## Media-dependent RGB color range clipper
34 $self->{clip_color} = sub {
35 shift; #my $self = shift;
36 my $value = shift;
37 if (defined $value and $value->[0] eq 'RGBA') {
38 my ($r, $g, $b) = @$value[1, 2, 3];
39 $r = 0 if $r < 0; $r = 255 if $r > 255;
40 $g = 0 if $g < 0; $g = 255 if $g > 255;
41 $b = 0 if $b < 0; $b = 255 if $b > 255;
42 return ['RGBA', $r, $g, $b, $value->[4]];
43 }
44 return $value;
45 };
46
47 ## System dependent font expander
48 $self->{get_system_font} = sub {
49 #my ($self, $normalized_system_font_name, $font_properties) = @_;
50 ## Modify $font_properties hash (except for 'font-family' property).
51 return $_[2];
52 };
53
54 return $self;
55 } # new
56
57 sub BEFORE_STATEMENT_STATE () { 0 }
58 sub BEFORE_DECLARATION_STATE () { 1 }
59 sub IGNORED_STATEMENT_STATE () { 2 }
60 sub IGNORED_DECLARATION_STATE () { 3 }
61
62 our $Prop; ## By CSS property name
63 our $Attr; ## By CSSOM attribute name
64 our $Key; ## By internal key
65
66 sub init ($) {
67 my $self = shift;
68 delete $self->{style_sheet};
69 delete $self->{unitless_px};
70 delete $self->{hashless_rgb};
71 delete $self->{href};
72 delete $self->{base_uri};
73 } # init
74
75 sub parse_char_string ($$) {
76 my $self = $_[0];
77
78 my $s = $_[1];
79 pos ($s) = 0;
80 my $line = 1;
81 my $column = 0;
82
83 my $tt = Whatpm::CSS::Tokenizer->new;
84 my $onerror = $tt->{onerror} = $self->{onerror};
85 $tt->{get_char} = sub ($) {
86 if (pos $s < length $s) {
87 my $c = ord substr $s, pos ($s)++, 1;
88 if ($c == 0x000A) {
89 $line++;
90 $column = 0;
91 } elsif ($c == 0x000D) {
92 unless (substr ($s, pos ($s), 1) eq "\x0A") {
93 $line++;
94 $column = 0;
95 } else {
96 $column++;
97 }
98 } else {
99 $column++;
100 }
101 $_[0]->{line} = $line;
102 $_[0]->{column} = $column;
103 return $c;
104 } else {
105 $_[0]->{column} = $column + 1; ## Set the same number always.
106 return -1;
107 }
108 }; # $tt->{get_char}
109 $tt->init;
110
111 my $sp = Whatpm::CSS::SelectorsParser->new;
112 $sp->{onerror} = $self->{onerror};
113 $sp->{level} = $self->{level};
114 $sp->{pseudo_element} = $self->{pseudo_element};
115 $sp->{pseudo_class} = $self->{pseudo_class};
116
117 my $mp = Whatpm::CSS::MediaQueryParser->new;
118 $mp->{onerror} = $self->{onerror};
119 $mp->{level} = $self->{level};
120
121 my $nsmap = {prefix_to_uri => {}, uri_to_prefixes => {}};
122 # $nsmap->{prefix_to_uri}->{p/""} = uri/undef
123 # $nsmap->{uri_to_prefixes}->{uri} = ["p|"/"",...]/undef
124 # $nsmap->{has_namespace} = 1/0
125 $self->{lookup_namespace_uri} =
126 $sp->{lookup_namespace_uri} = sub { ## TODO: case
127 return $nsmap->{prefix_to_uri}->{lc $_[0]}; # $_[0] is '' (default) or prefix
128 }; # $sp->{lookup_namespace_uri}
129
130 require Message::DOM::CSSStyleSheet;
131 require Message::DOM::CSSRule;
132 require Message::DOM::CSSStyleDeclaration;
133
134 $self->{base_uri} = $self->{href} unless defined $self->{base_uri};
135 $sp->{href} = $self->{href};
136
137 my $state = BEFORE_STATEMENT_STATE;
138 my $t = $tt->get_next_token;
139
140 my $open_rules = [[]];
141 my $current_rules = $open_rules->[-1];
142 my $parent_rules = [];
143 my $current_decls;
144 my $closing_tokens = [];
145 my $charset_allowed = 1;
146 my $namespace_allowed = 1;
147 my $import_allowed = 1;
148 my $media_allowed = 1;
149
150 my $ss = $self->{style_sheet} ||= Message::DOM::CSSStyleSheet->____new
151 (css_rules => $open_rules->[0],
152 ## TODO: href
153 ## TODO: owner_node
154 ## TODO: media
155 type => 'text/css'); ## TODO: OK?
156 $$ss->{manakai_base_uri} = $self->{base_uri};
157 $$ss->{_parser} = $self;
158 $$ss->{_nsmap} = $nsmap;
159
160 S: {
161 if ($state == BEFORE_STATEMENT_STATE) {
162 $t = $tt->get_next_token
163 while $t->{type} == S_TOKEN or
164 $t->{type} == CDO_TOKEN or
165 $t->{type} == CDC_TOKEN;
166
167 if ($t->{type} == ATKEYWORD_TOKEN) {
168 my $at_rule_name = lc $t->{value}; ## TODO: case
169 if ($at_rule_name eq 'namespace') {
170 $t = $tt->get_next_token;
171 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
172
173 my $prefix;
174 if ($t->{type} == IDENT_TOKEN) {
175 $prefix = lc $t->{value};
176 ## TODO: case (Unicode lowercase)
177
178 $t = $tt->get_next_token;
179 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
180 }
181
182 if ($t->{type} == STRING_TOKEN or $t->{type} == URI_TOKEN) {
183 my $uri = $t->{value};
184
185 $t = $tt->get_next_token;
186 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
187
188 ## ISSUE: On handling of empty namespace URI, Firefox 2 and
189 ## Opera 9 work differently (See SuikaWiki:namespace).
190 ## TODO: We need to check what we do once it is specced.
191
192 if ($t->{type} == SEMICOLON_TOKEN) {
193 if ($namespace_allowed) {
194 my $p = $prefix;
195 $nsmap->{has_namespace} = 1;
196 if (defined $prefix) {
197 $nsmap->{prefix_to_uri}->{$prefix} = $uri;
198 $p .= '|';
199 } else {
200 $nsmap->{prefix_to_uri}->{''} = $uri;
201 $p = '';
202 }
203 for my $u (keys %{$nsmap->{uri_to_prefixes}}) {
204 next if $u eq $uri;
205 my $list = $nsmap->{uri_to_prefixes}->{$u};
206 next unless $list;
207 for (reverse 0..$#$list) {
208 splice @$list, $_, 1, () if $list->[$_] eq $p;
209 }
210 }
211 push @{$nsmap->{uri_to_prefixes}->{$uri} ||= []}, $p;
212 push @$current_rules,
213 Message::DOM::CSSNamespaceRule->____new ($prefix, $uri);
214 undef $charset_allowed;
215 undef $import_allowed;
216 } else {
217 $onerror->(type => 'at-rule not allowed',
218 text => 'namespace',
219 level => $self->{level}->{must},
220 uri => \$self->{href},
221 token => $t);
222 }
223
224 $t = $tt->get_next_token;
225 ## Stay in the state.
226 redo S;
227 } else {
228 #
229 }
230 } else {
231 #
232 }
233
234 $onerror->(type => 'at-rule syntax error',
235 text => 'namespace',
236 level => $self->{level}->{must},
237 uri => \$self->{href},
238 token => $t);
239 #
240 } elsif ($at_rule_name eq 'import') {
241 if ($import_allowed) {
242 $t = $tt->get_next_token;
243 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
244 my $mq = [];
245 if ($t->{type} == STRING_TOKEN or $t->{type} == URI_TOKEN) {
246 my $uri = $t->{value};
247 $t = $tt->get_next_token;
248 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
249 if ($t->{type} == IDENT_TOKEN or
250 $t->{type} == DIMENSION_TOKEN or
251 $t->{type} == NUMBER_TOKEN or
252 $t->{type} == LPAREN_TOKEN) {
253 ($t, $mq) = $mp->_parse_mq_with_tokenizer ($t, $tt);
254 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
255 }
256 if ($mq and $t->{type} == SEMICOLON_TOKEN) {
257 ## TODO: error or warning
258 ## TODO: White space definition
259 $uri =~ s/^[\x09\x0A\x0D\x20]+//;
260 $uri =~ s/[\x09\x0A\x0D\x20]+\z//;
261
262 my $imported = Message::DOM::CSSStyleSheet->____new
263 (css_rules => [],
264 ## TODO: href
265 parent_style_sheet => $ss,
266 ## TODO: media
267 type => 'text/css', ## TODO: OK?
268 _parser => $self, _nsmap => {});
269
270 my $rule = Message::DOM::CSSImportRule->____new
271 ($uri, \($self->{base_uri}), $mq, $ss);
272
273 $$imported->{owner_rule} = $rule;
274 Scalar::Util::weaken ($$imported->{owner_rule});
275 Scalar::Util::weaken ($$imported->{parent_style_sheet});
276
277 push @$current_rules, $rule;
278
279 undef $charset_allowed;
280
281 $t = $tt->get_next_token;
282 ## Stay in the state.
283 redo S;
284 }
285 }
286
287 $onerror->(type => 'at-rule syntax error',
288 text => 'import',
289 level => $self->{level}->{must},
290 uri => \$self->{href},
291 token => $t)
292 if defined $mq; ## NOTE: Otherwise, already raised in MQ parser
293
294 #
295 } else {
296 $onerror->(type => 'at-rule not allowed',
297 text => 'import',
298 level => $self->{level}->{must},
299 uri => \$self->{href},
300 token => $t);
301
302 #
303 }
304 } elsif ($at_rule_name eq 'media') {
305 if ($media_allowed) {
306 $t = $tt->get_next_token;
307 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
308
309 my $q;
310 ($t, $q) = $mp->_parse_mq_with_tokenizer ($t, $tt);
311 if ($q) {
312 if ($t->{type} == LBRACE_TOKEN) {
313 undef $charset_allowed;
314 undef $namespace_allowed;
315 undef $import_allowed;
316 undef $media_allowed;
317 my $rule = Message::DOM::CSSMediaRule->____new
318 ($q, my $v = []);
319 push @$current_rules, $rule;
320 push @$parent_rules, $rule;
321 push @$open_rules, $current_rules = $v;
322 $t = $tt->get_next_token;
323 ## Stay in the state.
324 redo S;
325 } else {
326 $onerror->(type => 'at-rule syntax error',
327 text => 'media',
328 level => $self->{level}->{must},
329 uri => \$self->{href},
330 token => $t);
331 }
332
333 #
334 }
335
336 #
337 } else { ## Nested @media rule
338 $onerror->(type => 'at-rule not allowed',
339 text => 'media',
340 level => $self->{level}->{must},
341 uri => \$self->{href},
342 token => $t);
343
344 #
345 }
346 } elsif ($at_rule_name eq 'charset') {
347 if ($charset_allowed) {
348 $t = $tt->get_next_token;
349 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
350
351 if ($t->{type} == STRING_TOKEN) {
352 my $encoding = $t->{value};
353
354 $t = $tt->get_next_token;
355 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
356
357 if ($t->{type} == SEMICOLON_TOKEN) {
358 push @$current_rules,
359 Message::DOM::CSSCharsetRule->____new ($encoding);
360 undef $charset_allowed;
361
362 ## TODO: Detect the conformance errors for @charset...
363
364 $t = $tt->get_next_token;
365 ## Stay in the state.
366 redo S;
367 } else {
368 #
369 }
370 } else {
371 #
372 }
373
374 $onerror->(type => 'at-rule syntax error',
375 text => 'charset',
376 level => $self->{level}->{must},
377 uri => \$self->{href},
378 token => $t);
379 #
380 } else {
381 $onerror->(type => 'at-rule not allowed',
382 text => 'charset',
383 level => $self->{level}->{must},
384 uri => \$self->{href},
385 token => $t);
386 #
387 }
388 } else {
389 $onerror->(type => 'unknown at-rule',
390 level => $self->{level}->{uncertain},
391 uri => \$self->{href},
392 token => $t,
393 value => $t->{value});
394 }
395
396 ## Reprocess.
397 #$t = $tt->get_next_token;
398 $state = IGNORED_STATEMENT_STATE;
399 redo S;
400 } elsif (@$open_rules > 1 and $t->{type} == RBRACE_TOKEN) {
401 pop @$open_rules;
402 $media_allowed = 1;
403 $current_rules = $open_rules->[-1];
404 ## Stay in the state.
405 $t = $tt->get_next_token;
406 redo S;
407 } elsif ($t->{type} == EOF_TOKEN) {
408 if (@$open_rules > 1) {
409 $onerror->(type => 'block not closed',
410 level => $self->{level}->{must},
411 uri => \$self->{href},
412 token => $t);
413 }
414
415 last S;
416 } else {
417 undef $charset_allowed;
418 undef $namespace_allowed;
419 undef $import_allowed;
420
421 ($t, my $selectors) = $sp->_parse_selectors_with_tokenizer
422 ($tt, LBRACE_TOKEN, $t);
423
424 $t = $tt->get_next_token
425 while $t->{type} != LBRACE_TOKEN and $t->{type} != EOF_TOKEN;
426
427 if ($t->{type} == LBRACE_TOKEN) {
428 $current_decls = Message::DOM::CSSStyleDeclaration->____new;
429 my $rs = Message::DOM::CSSStyleRule->____new
430 ($selectors, $current_decls);
431 push @{$current_rules}, $rs if defined $selectors;
432
433 $state = BEFORE_DECLARATION_STATE;
434 $t = $tt->get_next_token;
435 redo S;
436 } else {
437 $onerror->(type => 'no declaration block',
438 level => $self->{level}->{must},
439 uri => \$self->{href},
440 token => $t);
441
442 ## Stay in the state.
443 $t = $tt->get_next_token;
444 redo S;
445 }
446 }
447 } elsif ($state == BEFORE_DECLARATION_STATE) {
448 ## NOTE: DELIM? in declaration will be removed:
449 ## <http://csswg.inkedblade.net/spec/css2.1?s=declaration%20delim#issue-2>.
450
451 my $prop_def;
452 my $prop_value;
453 my $prop_flag = '';
454 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
455 if ($t->{type} == IDENT_TOKEN) { # property
456 my $prop_name = lc $t->{value}; ## TODO: case folding
457 my $prop_name_t = $t;
458 $t = $tt->get_next_token;
459 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
460 if ($t->{type} == COLON_TOKEN) {
461 $t = $tt->get_next_token;
462 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
463
464 $prop_def = $Prop->{$prop_name};
465 if ($prop_def and $self->{prop}->{$prop_name}) {
466 ($t, $prop_value)
467 = $prop_def->{parse}->($self, $prop_name, $tt, $t, $onerror);
468 if ($prop_value) {
469 ## NOTE: {parse} don't have to consume trailing spaces.
470 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
471
472 if ($t->{type} == EXCLAMATION_TOKEN) {
473 $t = $tt->get_next_token;
474 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
475 if ($t->{type} == IDENT_TOKEN and
476 lc $t->{value} eq 'important') { ## TODO: case folding
477 $prop_flag = 'important';
478
479 $t = $tt->get_next_token;
480 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
481
482 #
483 } else {
484 $onerror->(type => 'priority syntax error',
485 level => $self->{level}->{must},
486 uri => \$self->{href},
487 token => $t);
488
489 ## Reprocess.
490 $state = IGNORED_DECLARATION_STATE;
491 redo S;
492 }
493 }
494
495 #
496 } else {
497 ## Syntax error.
498
499 ## Reprocess.
500 $state = IGNORED_DECLARATION_STATE;
501 redo S;
502 }
503 } else {
504 $onerror->(type => 'unknown property',
505 level => $self->{level}->{uncertain},
506 token => $prop_name_t, value => $prop_name,
507 uri => \$self->{href});
508
509 #
510 $state = IGNORED_DECLARATION_STATE;
511 redo S;
512 }
513 } else {
514 $onerror->(type => 'no property colon',
515 level => $self->{level}->{must},
516 uri => \$self->{href},
517 token => $t);
518
519 #
520 $state = IGNORED_DECLARATION_STATE;
521 redo S;
522 }
523 }
524
525 if ($t->{type} == RBRACE_TOKEN) {
526 $t = $tt->get_next_token;
527 $state = BEFORE_STATEMENT_STATE;
528 #redo S;
529 } elsif ($t->{type} == SEMICOLON_TOKEN) {
530 $t = $tt->get_next_token;
531 ## Stay in the state.
532 #redo S;
533 } elsif ($t->{type} == EOF_TOKEN) {
534 $onerror->(type => 'block not closed',
535 level => $self->{level}->{must},
536 uri => \$self->{href},
537 token => $t);
538 ## Reprocess.
539 $state = BEFORE_STATEMENT_STATE;
540 #redo S;
541 } else {
542 if ($prop_value) {
543 $onerror->(type => 'no property semicolon',
544 level => $self->{level}->{must},
545 uri => \$self->{href},
546 token => $t);
547 } else {
548 $onerror->(type => 'no property name',
549 level => $self->{level}->{must},
550 uri => \$self->{href},
551 token => $t);
552 }
553
554 #
555 $state = IGNORED_DECLARATION_STATE;
556 redo S;
557 }
558
559 my $important = ($prop_flag eq 'important');
560 for my $set_prop_name (keys %{$prop_value or {}}) {
561 my $set_prop_def = $Prop->{$set_prop_name};
562 $$current_decls->{$set_prop_def->{key}}
563 = [$prop_value->{$set_prop_name}, $prop_flag]
564 if $important or
565 not $$current_decls->{$set_prop_def->{key}} or
566 $$current_decls->{$set_prop_def->{key}}->[1] ne 'important';
567 }
568 redo S;
569 } elsif ($state == IGNORED_STATEMENT_STATE or
570 $state == IGNORED_DECLARATION_STATE) {
571 if (@$closing_tokens) { ## Something is yet in opening state.
572 if ($t->{type} == EOF_TOKEN) {
573 @$closing_tokens = ();
574 ## Reprocess.
575 $state = $state == IGNORED_STATEMENT_STATE
576 ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
577 redo S;
578 } elsif ($t->{type} == $closing_tokens->[-1]) {
579 pop @$closing_tokens;
580 if (@$closing_tokens == 0 and
581 $t->{type} == RBRACE_TOKEN and
582 $state == IGNORED_STATEMENT_STATE) {
583 $t = $tt->get_next_token;
584 $state = BEFORE_STATEMENT_STATE;
585 redo S;
586 } else {
587 $t = $tt->get_next_token;
588 ## Stay in the state.
589 redo S;
590 }
591 } elsif ({
592 RBRACE_TOKEN, 1,
593 #RBRACKET_TOKEN, 1,
594 #RPAREN_TOKEN, 1,
595 SEMICOLON_TOKEN, 1,
596 }->{$t->{type}}) {
597 $t = $tt->get_next_token;
598 ## Stay in the state.
599 #
600 } else {
601 #
602 }
603 } else {
604 if ($t->{type} == SEMICOLON_TOKEN) {
605 $t = $tt->get_next_token;
606 $state = $state == IGNORED_STATEMENT_STATE
607 ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
608 redo S;
609 } elsif ($t->{type} == RBRACE_TOKEN) {
610 if ($state == IGNORED_DECLARATION_STATE) {
611 $t = $tt->get_next_token;
612 $state = BEFORE_STATEMENT_STATE;
613 redo S;
614 } else {
615 ## NOTE: Maybe this state cannot be reached.
616 $t = $tt->get_next_token;
617 ## Stay in the state.
618 redo S;
619 }
620 } elsif ($t->{type} == EOF_TOKEN) {
621 ## Reprocess.
622 $state = $state == IGNORED_STATEMENT_STATE
623 ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
624 redo S;
625 #} elsif ($t->{type} == RBRACKET_TOKEN or $t->{type} == RPAREN_TOKEN) {
626 # $t = $tt->get_next_token;
627 # ## Stay in the state.
628 # #
629 } else {
630 #
631 }
632 }
633
634 while (not {
635 EOF_TOKEN, 1,
636 RBRACE_TOKEN, 1,
637 ## NOTE: ']' and ')' are disabled for browser compatibility.
638 #RBRACKET_TOKEN, 1,
639 #RPAREN_TOKEN, 1,
640 SEMICOLON_TOKEN, 1,
641 }->{$t->{type}}) {
642 if ($t->{type} == LBRACE_TOKEN) {
643 push @$closing_tokens, RBRACE_TOKEN;
644 #} elsif ($t->{type} == LBRACKET_TOKEN) {
645 # push @$closing_tokens, RBRACKET_TOKEN;
646 #} elsif ($t->{type} == LPAREN_TOKEN or $t->{type} == FUNCTION_TOKEN) {
647 # push @$closing_tokens, RPAREN_TOKEN;
648 }
649
650 $t = $tt->get_next_token;
651 }
652
653 #
654 ## Stay in the state.
655 redo S;
656 } else {
657 die "$0: parse_char_string: Unknown state: $state";
658 }
659 } # S
660
661 for (@{$$ss->{css_rules}}) {
662 $$_->{parent_style_sheet} = $ss;
663 Scalar::Util::weaken ($$_->{parent_style_sheet});
664 }
665 for my $parent_rule (@$parent_rules) {
666 for (@{$$parent_rule->{css_rules}}) {
667 $$_->{parent_rule} = $parent_rule;
668 Scalar::Util::weaken ($$_->{parent_rule});
669 }
670 }
671
672 return $ss;
673 } # parse_char_string
674
675 ## TODO: Test <style>'s base URI change and url()
676
677 sub parse_char_string_as_inline ($$) {
678 my $self = $_[0];
679
680 my $s = $_[1];
681 pos ($s) = 0;
682 my $line = 1;
683 my $column = 0;
684
685 my $tt = Whatpm::CSS::Tokenizer->new;
686 my $onerror = $tt->{onerror} = $self->{onerror};
687 $tt->{get_char} = sub ($) {
688 if (pos $s < length $s) {
689 my $c = ord substr $s, pos ($s)++, 1;
690 if ($c == 0x000A) {
691 $line++;
692 $column = 0;
693 } elsif ($c == 0x000D) {
694 unless (substr ($s, pos ($s), 1) eq "\x0A") {
695 $line++;
696 $column = 0;
697 } else {
698 $column++;
699 }
700 } else {
701 $column++;
702 }
703 $_[0]->{line} = $line;
704 $_[0]->{column} = $column;
705 return $c;
706 } else {
707 $_[0]->{column} = $column + 1; ## Set the same number always.
708 return -1;
709 }
710 }; # $tt->{get_char}
711 $tt->init;
712
713 $self->{lookup_namespace_uri} = sub { ## TODO: case
714 return undef; ## TODO: get from an external source
715 }; # $sp->{lookup_namespace_uri}
716
717 require Message::DOM::CSSStyleDeclaration;
718
719 $self->{base_uri} = $self->{href} unless defined $self->{base_uri};
720
721 my $state = BEFORE_DECLARATION_STATE;
722 my $t = $tt->get_next_token;
723
724 my $current_decls = Message::DOM::CSSStyleDeclaration->____new;
725 my $closing_tokens = [];
726
727 ## TODO: ...
728 # $$ss->{manakai_base_uri} = $self->{base_uri};
729 # $$ss->{_parser} = $self;
730 # $$ss->{_nsmap} = $nsmap;
731
732 S: {
733 if ($state == BEFORE_DECLARATION_STATE) {
734 ## NOTE: DELIM? in declaration will be removed:
735 ## <http://csswg.inkedblade.net/spec/css2.1?s=declaration%20delim#issue-2>.
736
737 my $prop_def;
738 my $prop_value;
739 my $prop_flag = '';
740 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
741 if ($t->{type} == IDENT_TOKEN) { # property
742 my $prop_name = lc $t->{value}; ## TODO: case folding
743 my $prop_name_t = $t;
744 $t = $tt->get_next_token;
745 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
746 if ($t->{type} == COLON_TOKEN) {
747 $t = $tt->get_next_token;
748 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
749
750 $prop_def = $Prop->{$prop_name};
751 if ($prop_def and $self->{prop}->{$prop_name}) {
752 ($t, $prop_value)
753 = $prop_def->{parse}->($self, $prop_name, $tt, $t, $onerror);
754 if ($prop_value) {
755 ## NOTE: {parse} don't have to consume trailing spaces.
756 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
757
758 if ($t->{type} == EXCLAMATION_TOKEN) {
759 $t = $tt->get_next_token;
760 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
761 if ($t->{type} == IDENT_TOKEN and
762 lc $t->{value} eq 'important') { ## TODO: case folding
763 $prop_flag = 'important';
764
765 $t = $tt->get_next_token;
766 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
767
768 #
769 } else {
770 $onerror->(type => 'priority syntax error',
771 level => $self->{level}->{must},
772 uri => \$self->{href},
773 token => $t);
774
775 ## Reprocess.
776 $state = IGNORED_DECLARATION_STATE;
777 redo S;
778 }
779 }
780
781 #
782 } else {
783 ## Syntax error.
784
785 ## Reprocess.
786 $state = IGNORED_DECLARATION_STATE;
787 redo S;
788 }
789 } else {
790 $onerror->(type => 'unknown property',
791 level => $self->{level}->{uncertain},
792 token => $prop_name_t, value => $prop_name,
793 uri => \$self->{href});
794
795 #
796 $state = IGNORED_DECLARATION_STATE;
797 redo S;
798 }
799 } else {
800 $onerror->(type => 'no property colon',
801 level => $self->{level}->{must},
802 uri => \$self->{href},
803 token => $t);
804
805 #
806 $state = IGNORED_DECLARATION_STATE;
807 redo S;
808 }
809 }
810
811 ## NOTE: Unlike the main parser, |RBRACE_TOKEN| does not close
812 ## the block here.
813 if ($t->{type} == SEMICOLON_TOKEN) {
814 $t = $tt->get_next_token;
815 ## Stay in the state.
816 #redo S;
817 } elsif ($t->{type} == EOF_TOKEN) {
818 ## NOTE: Unlike the main parser, no error is raised here and
819 ## exits the parser.
820 #last S;
821 } else {
822 if ($prop_value) {
823 $onerror->(type => 'no property semicolon',
824 level => $self->{level}->{must},
825 uri => \$self->{href},
826 token => $t);
827 } else {
828 $onerror->(type => 'no property name',
829 level => $self->{level}->{must},
830 uri => \$self->{href},
831 token => $t);
832 }
833
834 ## Reprocess.
835 $state = IGNORED_DECLARATION_STATE;
836 redo S;
837 }
838
839 my $important = ($prop_flag eq 'important');
840 for my $set_prop_name (keys %{$prop_value or {}}) {
841 my $set_prop_def = $Prop->{$set_prop_name};
842 $$current_decls->{$set_prop_def->{key}}
843 = [$prop_value->{$set_prop_name}, $prop_flag]
844 if $important or
845 not $$current_decls->{$set_prop_def->{key}} or
846 $$current_decls->{$set_prop_def->{key}}->[1] ne 'important';
847 }
848 last S if $t->{type} == EOF_TOKEN; # "color: red{EOF}" (w/ or w/o ";")
849 redo S;
850 } elsif ($state == IGNORED_DECLARATION_STATE) {
851 ## NOTE: Difference from the main parser is that support for the
852 ## |IGNORED_STATEMENT_STATE| cases is removed.
853 if (@$closing_tokens) { ## Something is yet in opening state.
854 if ($t->{type} == EOF_TOKEN) {
855 @$closing_tokens = ();
856 ## Reprocess.
857 $state = BEFORE_DECLARATION_STATE;
858 redo S;
859 } elsif ($t->{type} == $closing_tokens->[-1]) {
860 pop @$closing_tokens;
861 $t = $tt->get_next_token;
862 ## Stay in the state.
863 redo S;
864 } elsif ({
865 RBRACE_TOKEN, 1,
866 #RBRACKET_TOKEN, 1,
867 #RPAREN_TOKEN, 1,
868 SEMICOLON_TOKEN, 1,
869 }->{$t->{type}}) {
870 $t = $tt->get_next_token;
871 ## Stay in the state.
872 #
873 } else {
874 #
875 }
876 } else {
877 ## NOTE: Unlike the main parser, |RBRACE_TOKEN| does not close
878 ## the block here.
879 if ($t->{type} == SEMICOLON_TOKEN) {
880 $t = $tt->get_next_token;
881 $state = BEFORE_DECLARATION_STATE;
882 redo S;
883 } elsif ($t->{type} == EOF_TOKEN) {
884 ## Reprocess.
885 $state = $state == IGNORED_STATEMENT_STATE
886 ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
887 redo S;
888 #} elsif ($t->{type} == RBRACKET_TOKEN or $t->{type} == RPAREN_TOKEN) {
889 # $t = $tt->get_next_token;
890 # ## Stay in the state.
891 # #
892 } else {
893 #
894 }
895 }
896
897 while (not {
898 EOF_TOKEN, 1,
899 #RBRACE_TOKEN, 1, ## NOTE: Difference from the main parser.
900 ## NOTE: ']' and ')' are disabled for browser compatibility.
901 #RBRACKET_TOKEN, 1,
902 #RPAREN_TOKEN, 1,
903 SEMICOLON_TOKEN, 1,
904 }->{$t->{type}}) {
905 if ($t->{type} == LBRACE_TOKEN) {
906 push @$closing_tokens, RBRACE_TOKEN;
907 #} elsif ($t->{type} == LBRACKET_TOKEN) {
908 # push @$closing_tokens, RBRACKET_TOKEN;
909 #} elsif ($t->{type} == LPAREN_TOKEN or $t->{type} == FUNCTION_TOKEN) {
910 # push @$closing_tokens, RPAREN_TOKEN;
911 }
912
913 $t = $tt->get_next_token;
914 }
915
916 ## Reprocess.
917 ## Stay in the state.
918 redo S;
919 } else {
920 die "$0: parse_char_string: Unknown state: $state";
921 }
922 } # S
923
924 ## TODO: CSSStyleDeclaration attributes ...
925
926 return $current_decls;
927 } # parse_char_string_as_inline
928
929 ## TODO: We need test script for the method above.
930
931 my $compute_as_specified = sub ($$$$) {
932 #my ($self, $element, $prop_name, $specified_value) = @_;
933 return $_[3];
934 }; # $compute_as_specified
935
936 my $x11_colors = {
937 'aliceblue' => [0xf0, 0xf8, 0xff],
938 'antiquewhite' => [0xfa, 0xeb, 0xd7],
939 'aqua' => [0x00, 0xff, 0xff],
940 'aquamarine' => [0x7f, 0xff, 0xd4],
941 'azure' => [0xf0, 0xff, 0xff],
942 'beige' => [0xf5, 0xf5, 0xdc],
943 'bisque' => [0xff, 0xe4, 0xc4],
944 'black' => [0x00, 0x00, 0x00],
945 'blanchedalmond' => [0xff, 0xeb, 0xcd],
946 'blue' => [0x00, 0x00, 0xff],
947 'blueviolet' => [0x8a, 0x2b, 0xe2],
948 'brown' => [0xa5, 0x2a, 0x2a],
949 'burlywood' => [0xde, 0xb8, 0x87],
950 'cadetblue' => [0x5f, 0x9e, 0xa0],
951 'chartreuse' => [0x7f, 0xff, 0x00],
952 'chocolate' => [0xd2, 0x69, 0x1e],
953 'coral' => [0xff, 0x7f, 0x50],
954 'cornflowerblue' => [0x64, 0x95, 0xed],
955 'cornsilk' => [0xff, 0xf8, 0xdc],
956 'crimson' => [0xdc, 0x14, 0x3c],
957 'cyan' => [0x00, 0xff, 0xff],
958 'darkblue' => [0x00, 0x00, 0x8b],
959 'darkcyan' => [0x00, 0x8b, 0x8b],
960 'darkgoldenrod' => [0xb8, 0x86, 0x0b],
961 'darkgray' => [0xa9, 0xa9, 0xa9],
962 'darkgreen' => [0x00, 0x64, 0x00],
963 'darkgrey' => [0xa9, 0xa9, 0xa9],
964 'darkkhaki' => [0xbd, 0xb7, 0x6b],
965 'darkmagenta' => [0x8b, 0x00, 0x8b],
966 'darkolivegreen' => [0x55, 0x6b, 0x2f],
967 'darkorange' => [0xff, 0x8c, 0x00],
968 'darkorchid' => [0x99, 0x32, 0xcc],
969 'darkred' => [0x8b, 0x00, 0x00],
970 'darksalmon' => [0xe9, 0x96, 0x7a],
971 'darkseagreen' => [0x8f, 0xbc, 0x8f],
972 'darkslateblue' => [0x48, 0x3d, 0x8b],
973 'darkslategray' => [0x2f, 0x4f, 0x4f],
974 'darkslategrey' => [0x2f, 0x4f, 0x4f],
975 'darkturquoise' => [0x00, 0xce, 0xd1],
976 'darkviolet' => [0x94, 0x00, 0xd3],
977 'deeppink' => [0xff, 0x14, 0x93],
978 'deepskyblue' => [0x00, 0xbf, 0xff],
979 'dimgray' => [0x69, 0x69, 0x69],
980 'dimgrey' => [0x69, 0x69, 0x69],
981 'dodgerblue' => [0x1e, 0x90, 0xff],
982 'firebrick' => [0xb2, 0x22, 0x22],
983 'floralwhite' => [0xff, 0xfa, 0xf0],
984 'forestgreen' => [0x22, 0x8b, 0x22],
985 'fuchsia' => [0xff, 0x00, 0xff],
986 'gainsboro' => [0xdc, 0xdc, 0xdc],
987 'ghostwhite' => [0xf8, 0xf8, 0xff],
988 'gold' => [0xff, 0xd7, 0x00],
989 'goldenrod' => [0xda, 0xa5, 0x20],
990 'gray' => [0x80, 0x80, 0x80],
991 'green' => [0x00, 0x80, 0x00],
992 'greenyellow' => [0xad, 0xff, 0x2f],
993 'grey' => [0x80, 0x80, 0x80],
994 'honeydew' => [0xf0, 0xff, 0xf0],
995 'hotpink' => [0xff, 0x69, 0xb4],
996 'indianred' => [0xcd, 0x5c, 0x5c],
997 'indigo' => [0x4b, 0x00, 0x82],
998 'ivory' => [0xff, 0xff, 0xf0],
999 'khaki' => [0xf0, 0xe6, 0x8c],
1000 'lavender' => [0xe6, 0xe6, 0xfa],
1001 'lavenderblush' => [0xff, 0xf0, 0xf5],
1002 'lawngreen' => [0x7c, 0xfc, 0x00],
1003 'lemonchiffon' => [0xff, 0xfa, 0xcd],
1004 'lightblue' => [0xad, 0xd8, 0xe6],
1005 'lightcoral' => [0xf0, 0x80, 0x80],
1006 'lightcyan' => [0xe0, 0xff, 0xff],
1007 'lightgoldenrodyellow' => [0xfa, 0xfa, 0xd2],
1008 'lightgray' => [0xd3, 0xd3, 0xd3],
1009 'lightgreen' => [0x90, 0xee, 0x90],
1010 'lightgrey' => [0xd3, 0xd3, 0xd3],
1011 'lightpink' => [0xff, 0xb6, 0xc1],
1012 'lightsalmon' => [0xff, 0xa0, 0x7a],
1013 'lightseagreen' => [0x20, 0xb2, 0xaa],
1014 'lightskyblue' => [0x87, 0xce, 0xfa],
1015 'lightslategray' => [0x77, 0x88, 0x99],
1016 'lightslategrey' => [0x77, 0x88, 0x99],
1017 'lightsteelblue' => [0xb0, 0xc4, 0xde],
1018 'lightyellow' => [0xff, 0xff, 0xe0],
1019 'lime' => [0x00, 0xff, 0x00],
1020 'limegreen' => [0x32, 0xcd, 0x32],
1021 'linen' => [0xfa, 0xf0, 0xe6],
1022 'magenta' => [0xff, 0x00, 0xff],
1023 'maroon' => [0x80, 0x00, 0x00],
1024 'mediumaquamarine' => [0x66, 0xcd, 0xaa],
1025 'mediumblue' => [0x00, 0x00, 0xcd],
1026 'mediumorchid' => [0xba, 0x55, 0xd3],
1027 'mediumpurple' => [0x93, 0x70, 0xdb],
1028 'mediumseagreen' => [0x3c, 0xb3, 0x71],
1029 'mediumslateblue' => [0x7b, 0x68, 0xee],
1030 'mediumspringgreen' => [0x00, 0xfa, 0x9a],
1031 'mediumturquoise' => [0x48, 0xd1, 0xcc],
1032 'mediumvioletred' => [0xc7, 0x15, 0x85],
1033 'midnightblue' => [0x19, 0x19, 0x70],
1034 'mintcream' => [0xf5, 0xff, 0xfa],
1035 'mistyrose' => [0xff, 0xe4, 0xe1],
1036 'moccasin' => [0xff, 0xe4, 0xb5],
1037 'navajowhite' => [0xff, 0xde, 0xad],
1038 'navy' => [0x00, 0x00, 0x80],
1039 'oldlace' => [0xfd, 0xf5, 0xe6],
1040 'olive' => [0x80, 0x80, 0x00],
1041 'olivedrab' => [0x6b, 0x8e, 0x23],
1042 'orange' => [0xff, 0xa5, 0x00],
1043 'orangered' => [0xff, 0x45, 0x00],
1044 'orchid' => [0xda, 0x70, 0xd6],
1045 'palegoldenrod' => [0xee, 0xe8, 0xaa],
1046 'palegreen' => [0x98, 0xfb, 0x98],
1047 'paleturquoise' => [0xaf, 0xee, 0xee],
1048 'palevioletred' => [0xdb, 0x70, 0x93],
1049 'papayawhip' => [0xff, 0xef, 0xd5],
1050 'peachpuff' => [0xff, 0xda, 0xb9],
1051 'peru' => [0xcd, 0x85, 0x3f],
1052 'pink' => [0xff, 0xc0, 0xcb],
1053 'plum' => [0xdd, 0xa0, 0xdd],
1054 'powderblue' => [0xb0, 0xe0, 0xe6],
1055 'purple' => [0x80, 0x00, 0x80],
1056 'red' => [0xff, 0x00, 0x00],
1057 'rosybrown' => [0xbc, 0x8f, 0x8f],
1058 'royalblue' => [0x41, 0x69, 0xe1],
1059 'saddlebrown' => [0x8b, 0x45, 0x13],
1060 'salmon' => [0xfa, 0x80, 0x72],
1061 'sandybrown' => [0xf4, 0xa4, 0x60],
1062 'seagreen' => [0x2e, 0x8b, 0x57],
1063 'seashell' => [0xff, 0xf5, 0xee],
1064 'sienna' => [0xa0, 0x52, 0x2d],
1065 'silver' => [0xc0, 0xc0, 0xc0],
1066 'skyblue' => [0x87, 0xce, 0xeb],
1067 'slateblue' => [0x6a, 0x5a, 0xcd],
1068 'slategray' => [0x70, 0x80, 0x90],
1069 'slategrey' => [0x70, 0x80, 0x90],
1070 'snow' => [0xff, 0xfa, 0xfa],
1071 'springgreen' => [0x00, 0xff, 0x7f],
1072 'steelblue' => [0x46, 0x82, 0xb4],
1073 'tan' => [0xd2, 0xb4, 0x8c],
1074 'teal' => [0x00, 0x80, 0x80],
1075 'thistle' => [0xd8, 0xbf, 0xd8],
1076 'tomato' => [0xff, 0x63, 0x47],
1077 'turquoise' => [0x40, 0xe0, 0xd0],
1078 'violet' => [0xee, 0x82, 0xee],
1079 'wheat' => [0xf5, 0xde, 0xb3],
1080 'white' => [0xff, 0xff, 0xff],
1081 'whitesmoke' => [0xf5, 0xf5, 0xf5],
1082 'yellow' => [0xff, 0xff, 0x00],
1083 'yellowgreen' => [0x9a, 0xcd, 0x32],
1084 }; # $x11_colors
1085
1086 my $system_colors = {
1087 activeborder => 1, activecaption => 1, appworkspace => 1, background => 1,
1088 buttonface => 1, buttonhighlight => 1, buttonshadow => 1, buttontext => 1,
1089 captiontext => 1, graytext => 1, highlight => 1, highlighttext => 1,
1090 inactiveborder => 1, inactivecaption => 1, inactivecaptiontext => 1,
1091 infobackground => 1, infotext => 1, menu => 1, menutext => 1,
1092 scrollbar => 1, threeddarkshadow => 1, threedface => 1, threedhighlight => 1,
1093 threedlightshadow => 1, threedshadow => 1, window => 1, windowframe => 1,
1094 windowtext => 1,
1095 }; # $system_colors
1096
1097 my $parse_color = sub {
1098 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1099
1100 ## See
1101 ## <http://suika.fam.cx/gate/2005/sw/%3Ccolor%3E>,
1102 ## <http://suika.fam.cx/gate/2005/sw/rgb>,
1103 ## <http://suika.fam.cx/gate/2005/sw/-moz-rgba>,
1104 ## <http://suika.fam.cx/gate/2005/sw/hsl>,
1105 ## <http://suika.fam.cx/gate/2005/sw/-moz-hsla>, and
1106 ## <http://suika.fam.cx/gate/2005/sw/color>
1107 ## for browser compatibility issue.
1108
1109 ## NOTE: Implementing CSS3 Color CR (2003), except for attr(),
1110 ## rgba(), and hsla().
1111 ## NOTE: rgb(...{EOF} is not supported (only Opera does).
1112
1113 if ($t->{type} == IDENT_TOKEN) {
1114 my $value = lc $t->{value}; ## TODO: case
1115 if ($x11_colors->{$value} or
1116 $system_colors->{$value}) {
1117 ## NOTE: "For systems that do not have a corresponding value, the
1118 ## specified value should be mapped to the nearest system value, or to
1119 ## a default color." [CSS 2.1].
1120 $t = $tt->get_next_token;
1121 return ($t, {$prop_name => ['KEYWORD', $value]});
1122 } elsif ({
1123 transparent => 1, ## For 'background-color' in CSS2.1, everywhre in CSS3.
1124 flavor => 1, ## CSS3.
1125 invert => 1, ## For 'outline-color' in CSS2.1.
1126 '-moz-use-text-color' => 1, ## For <border-color> in Gecko.
1127 '-manakai-default' => 1, ## CSS2.1 initial for 'color'
1128 '-manakai-invert-or-currentcolor' => 1, ## CSS2.1 initial4'outline-color'
1129 }->{$value} and $self->{prop_value}->{$prop_name}->{$value}) {
1130 $t = $tt->get_next_token;
1131 return ($t, {$prop_name => ['KEYWORD', $value]});
1132 } elsif ($value eq 'currentcolor' or $value eq '-moz-use-text-color') {
1133 ## NOTE: '-manakai-invert-or-currentcolor' is not allowed in 'color'.
1134 $t = $tt->get_next_token;
1135 if ($prop_name eq 'color') {
1136 return ($t, {$prop_name => ['INHERIT']});
1137 } else {
1138 return ($t, {$prop_name => ['KEYWORD', $value]});
1139 }
1140 } elsif ($value eq 'inherit') {
1141 $t = $tt->get_next_token;
1142 return ($t, {$prop_name => ['INHERIT']});
1143 }
1144 }
1145
1146 if ($t->{type} == HASH_TOKEN or
1147 ($self->{hashless_rgb} and {
1148 IDENT_TOKEN, 1,
1149 NUMBER_TOKEN, 1,
1150 DIMENSION_TOKEN, 1,
1151 }->{$t->{type}})) {
1152 my $v = lc (defined $t->{number} ? $t->{number} : '' . $t->{value}); ## TODO: case
1153 if ($v =~ /\A([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})\z/) {
1154 $t = $tt->get_next_token;
1155 return ($t, {$prop_name => ['RGBA', hex $1, hex $2, hex $3, 1]});
1156 } elsif ($v =~ /\A([0-9a-f])([0-9a-f])([0-9a-f])\z/) {
1157 $t = $tt->get_next_token;
1158 return ($t, {$prop_name => ['RGBA', hex $1.$1, hex $2.$2,
1159 hex $3.$3, 1]});
1160 }
1161 }
1162
1163 if ($t->{type} == FUNCTION_TOKEN) {
1164 my $func = lc $t->{value}; ## TODO: case
1165 if ($func eq 'rgb') {
1166 $t = $tt->get_next_token;
1167 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1168 my $sign = 1;
1169 if ($t->{type} == MINUS_TOKEN) {
1170 $sign = -1;
1171 $t = $tt->get_next_token;
1172 } elsif ($t->{type} == PLUS_TOKEN) {
1173 $t = $tt->get_next_token;
1174 }
1175 if ($t->{type} == NUMBER_TOKEN) {
1176 my $r = $t->{number} * $sign;
1177 $t = $tt->get_next_token;
1178 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1179 if ($t->{type} == COMMA_TOKEN) {
1180 $t = $tt->get_next_token;
1181 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1182 $sign = 1;
1183 if ($t->{type} == MINUS_TOKEN) {
1184 $sign = -1;
1185 $t = $tt->get_next_token;
1186 } elsif ($t->{type} == PLUS_TOKEN) {
1187 $t = $tt->get_next_token;
1188 }
1189 if ($t->{type} == NUMBER_TOKEN) {
1190 my $g = $t->{number} * $sign;
1191 $t = $tt->get_next_token;
1192 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1193 if ($t->{type} == COMMA_TOKEN) {
1194 $t = $tt->get_next_token;
1195 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1196 $sign = 1;
1197 if ($t->{type} == MINUS_TOKEN) {
1198 $sign = -1;
1199 $t = $tt->get_next_token;
1200 } elsif ($t->{type} == PLUS_TOKEN) {
1201 $t = $tt->get_next_token;
1202 }
1203 if ($t->{type} == NUMBER_TOKEN) {
1204 my $b = $t->{number} * $sign;
1205 $t = $tt->get_next_token;
1206 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1207 if ($t->{type} == RPAREN_TOKEN) {
1208 $t = $tt->get_next_token;
1209 return ($t,
1210 {$prop_name =>
1211 $self->{clip_color}->($self,
1212 ['RGBA', $r, $g, $b, 1])});
1213 }
1214 }
1215 }
1216 }
1217 }
1218 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1219 my $r = $t->{number} * 255 / 100 * $sign;
1220 $t = $tt->get_next_token;
1221 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1222 if ($t->{type} == COMMA_TOKEN) {
1223 $t = $tt->get_next_token;
1224 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1225 $sign = 1;
1226 if ($t->{type} == MINUS_TOKEN) {
1227 $sign = -1;
1228 $t = $tt->get_next_token;
1229 } elsif ($t->{type} == PLUS_TOKEN) {
1230 $t = $tt->get_next_token;
1231 }
1232 if ($t->{type} == PERCENTAGE_TOKEN) {
1233 my $g = $t->{number} * 255 / 100 * $sign;
1234 $t = $tt->get_next_token;
1235 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1236 if ($t->{type} == COMMA_TOKEN) {
1237 $t = $tt->get_next_token;
1238 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1239 $sign = 1;
1240 if ($t->{type} == MINUS_TOKEN) {
1241 $sign = -1;
1242 $t = $tt->get_next_token;
1243 } elsif ($t->{type} == PLUS_TOKEN) {
1244 $t = $tt->get_next_token;
1245 }
1246 if ($t->{type} == PERCENTAGE_TOKEN) {
1247 my $b = $t->{number} * 255 / 100 * $sign;
1248 $t = $tt->get_next_token;
1249 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1250 if ($t->{type} == RPAREN_TOKEN) {
1251 $t = $tt->get_next_token;
1252 return ($t,
1253 {$prop_name =>
1254 $self->{clip_color}->($self,
1255 ['RGBA', $r, $g, $b, 1])});
1256 }
1257 }
1258 }
1259 }
1260 }
1261 }
1262 } elsif ($func eq 'hsl') {
1263 $t = $tt->get_next_token;
1264 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1265 my $sign = 1;
1266 if ($t->{type} == MINUS_TOKEN) {
1267 $sign = -1;
1268 $t = $tt->get_next_token;
1269 } elsif ($t->{type} == PLUS_TOKEN) {
1270 $t = $tt->get_next_token;
1271 }
1272 if ($t->{type} == NUMBER_TOKEN) {
1273 my $h = (((($t->{number} * $sign) % 360) + 360) % 360) / 360;
1274 $t = $tt->get_next_token;
1275 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1276 if ($t->{type} == COMMA_TOKEN) {
1277 $t = $tt->get_next_token;
1278 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1279 $sign = 1;
1280 if ($t->{type} == MINUS_TOKEN) {
1281 $sign = -1;
1282 $t = $tt->get_next_token;
1283 } elsif ($t->{type} == PLUS_TOKEN) {
1284 $t = $tt->get_next_token;
1285 }
1286 if ($t->{type} == PERCENTAGE_TOKEN) {
1287 my $s = $t->{number} * $sign / 100;
1288 $s = 0 if $s < 0;
1289 $s = 1 if $s > 1;
1290 $t = $tt->get_next_token;
1291 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1292 if ($t->{type} == COMMA_TOKEN) {
1293 $t = $tt->get_next_token;
1294 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1295 $sign = 1;
1296 if ($t->{type} == MINUS_TOKEN) {
1297 $sign = -1;
1298 $t = $tt->get_next_token;
1299 } elsif ($t->{type} == PLUS_TOKEN) {
1300 $t = $tt->get_next_token;
1301 }
1302 if ($t->{type} == PERCENTAGE_TOKEN) {
1303 my $l = $t->{number} * $sign / 100;
1304 $l = 0 if $l < 0;
1305 $l = 1 if $l > 1;
1306 $t = $tt->get_next_token;
1307 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
1308 if ($t->{type} == RPAREN_TOKEN) {
1309 my $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
1310 my $m1 = $l * 2 - $m2;
1311 my $hue2rgb = sub ($$$) {
1312 my ($m1, $m2, $h) = @_;
1313 $h++ if $h < 0;
1314 $h-- if $h > 1;
1315 return $m1 + ($m2 - $m1) * $h * 6 if $h * 6 < 1;
1316 return $m2 if $h * 2 < 1;
1317 return $m1 + ($m2 - $m1) * (2/3 - $h) * 6 if $h * 3 < 2;
1318 return $m1;
1319 };
1320 $t = $tt->get_next_token;
1321 return ($t,
1322 {$prop_name =>
1323 $self->{clip_color}
1324 ->($self,
1325 ['RGBA',
1326 $hue2rgb->($m1, $m2, $h + 1/3) * 255,
1327 $hue2rgb->($m1, $m2, $h) * 255,
1328 $hue2rgb->($m1, $m2, $h - 1/3) * 255, 1])});
1329 }
1330 }
1331 }
1332 }
1333 }
1334 }
1335 }
1336 }
1337
1338 $onerror->(type => 'CSS syntax error', text => 'color',
1339 level => $self->{level}->{must},
1340 uri => \$self->{href},
1341 token => $t);
1342
1343 return ($t, undef);
1344 }; # $parse_color
1345
1346 $Prop->{color} = {
1347 css => 'color',
1348 dom => 'color',
1349 key => 'color',
1350 parse => $parse_color,
1351 initial => ['KEYWORD', '-manakai-default'],
1352 inherited => 1,
1353 compute => sub ($$$$) {
1354 my ($self, $element, $prop_name, $specified_value) = @_;
1355
1356 if (defined $specified_value) {
1357 if ($specified_value->[0] eq 'KEYWORD') {
1358 if ($x11_colors->{$specified_value->[1]}) {
1359 return ['RGBA', @{$x11_colors->{$specified_value->[1]}}, 1];
1360 } elsif ($specified_value->[1] eq 'transparent') {
1361 return ['RGBA', 0, 0, 0, 0];
1362 } elsif ($specified_value->[1] eq 'currentcolor' or
1363 $specified_value->[1] eq '-moz-use-text-color' or
1364 ($specified_value->[1] eq '-manakai-invert-or-currentcolor'and
1365 not $self->{has_invert})) {
1366 unless ($prop_name eq 'color') {
1367 return $self->get_computed_value ($element, 'color');
1368 } else {
1369 ## NOTE: This is an error, since it should have been
1370 ## converted to 'inherit' at parse time.
1371 ## NOTE: 'color: -manakai-invert-or-currentcolor' is not allowed.
1372 return ['KEYWORD', '-manakai-default'];
1373 }
1374 } elsif ($specified_value->[1] eq '-manakai-invert-or-currentcolor') {
1375 return ['KEYWORD', 'invert'];
1376 }
1377 }
1378 }
1379
1380 return $specified_value;
1381 },
1382 };
1383 $Attr->{color} = $Prop->{color};
1384 $Key->{color} = $Prop->{color};
1385
1386 $Prop->{'background-color'} = {
1387 css => 'background-color',
1388 dom => 'background_color',
1389 key => 'background_color',
1390 parse => $parse_color,
1391 serialize_multiple => sub {
1392 my $self = shift;
1393
1394 my $r = {};
1395 my $has_all;
1396
1397 my $x = $self->background_position_x;
1398 my $y = $self->background_position_y;
1399 my $xi = $self->get_property_priority ('background-position-x');
1400 my $yi = $self->get_property_priority ('background-position-y');
1401 if (length $x) {
1402 if (length $y) {
1403 if ($xi eq $yi) {
1404 if ($x eq 'inherit') {
1405 if ($y eq 'inherit') {
1406 $r->{'background-position'} = ['inherit', $xi];
1407 $has_all = 1;
1408 } else {
1409 $r->{'background-position-x'} = [$x, $xi];
1410 $r->{'background-position-y'} = [$y, $yi];
1411 }
1412 } elsif ($y eq 'inherit') {
1413 $r->{'background-position-x'} = [$x, $xi];
1414 $r->{'background-position-y'} = [$y, $yi];
1415 } else {
1416 $r->{'background-position'} = [$x . ' ' . $y, $xi];
1417 $has_all = 1;
1418 }
1419 } else {
1420 $r->{'background-position-x'} = [$x, $xi];
1421 $r->{'background-position-y'} = [$y, $yi];
1422 }
1423 } else {
1424 $r->{'background-position-x'} = [$x, $xi];
1425 }
1426 } else {
1427 if (length $y) {
1428 $r->{'background-position-y'} = [$y, $yi];
1429 } else {
1430 #
1431 }
1432 }
1433
1434 for my $prop (qw/color image repeat attachment/) {
1435 my $prop_name = 'background_'.$prop;
1436 my $value = $self->$prop_name;
1437 if (length $value) {
1438 my $i = $self->get_property_priority ('background-'.$prop);
1439 undef $has_all unless $xi eq $i;
1440 $r->{'background-'.$prop} = [$value, $i];
1441 } else {
1442 undef $has_all;
1443 }
1444 }
1445
1446 if ($has_all) {
1447 my @v;
1448 push @v, $r->{'background-color'}
1449 unless $r->{'background-color'}->[0] eq 'transparent';
1450 push @v, $r->{'background-image'}
1451 unless $r->{'background-image'}->[0] eq 'none';
1452 push @v, $r->{'background-repeat'}
1453 unless $r->{'background-repeat'}->[0] eq 'repeat';
1454 push @v, $r->{'background-attachment'}
1455 unless $r->{'background-attachment'}->[0] eq 'scroll';
1456 push @v, $r->{'background-position'}
1457 unless $r->{'background-position'}->[0] eq '0% 0%';
1458 if (@v) {
1459 my $inherit = 0;
1460 for (@v) {
1461 $inherit++ if $_->[0] eq 'inherit';
1462 }
1463 if ($inherit == 5) {
1464 return {background => ['inherit', $xi]};
1465 } elsif ($inherit) {
1466 return $r;
1467 } else {
1468 return {background => [(join ' ', map {$_->[0]} @v), $xi]};
1469 }
1470 } else {
1471 return {background => ['transparent none repeat scroll 0% 0%', $xi]};
1472 }
1473 } else {
1474 return $r;
1475 }
1476 },
1477 initial => ['KEYWORD', 'transparent'],
1478 #inherited => 0,
1479 compute => $Prop->{color}->{compute},
1480 };
1481 $Attr->{background_color} = $Prop->{'background-color'};
1482 $Key->{background_color} = $Prop->{'background-color'};
1483
1484 $Prop->{'border-top-color'} = {
1485 css => 'border-top-color',
1486 dom => 'border_top_color',
1487 key => 'border_top_color',
1488 parse => $parse_color,
1489 serialize_multiple => sub {
1490 my $self = shift;
1491 ## NOTE: This algorithm returns the same result as that of Firefox 2
1492 ## in many case, but not always.
1493 my $r = {
1494 'border-top-color' => [$self->border_top_color,
1495 $self->get_property_priority
1496 ('border-top-color')],
1497 'border-top-style' => [$self->border_top_style,
1498 $self->get_property_priority
1499 ('border-top-style')],
1500 'border-top-width' => [$self->border_top_width,
1501 $self->get_property_priority
1502 ('border-top-width')],
1503 'border-right-color' => [$self->border_right_color,
1504 $self->get_property_priority
1505 ('border-right-color')],
1506 'border-right-style' => [$self->border_right_style,
1507 $self->get_property_priority
1508 ('border-right-style')],
1509 'border-right-width' => [$self->border_right_width,
1510 $self->get_property_priority
1511 ('border-right-width')],
1512 'border-bottom-color' => [$self->border_bottom_color,
1513 $self->get_property_priority
1514 ('border-bottom-color')],
1515 'border-bottom-style' => [$self->border_bottom_style,
1516 $self->get_property_priority
1517 ('border-bottom-style')],
1518 'border-bottom-width' => [$self->border_bottom_width,
1519 $self->get_property_priority
1520 ('border-bottom-width')],
1521 'border-left-color' => [$self->border_left_color,
1522 $self->get_property_priority
1523 ('border-leftcolor')],
1524 'border-left-style' => [$self->border_left_style,
1525 $self->get_property_priority
1526 ('border-left-style')],
1527 'border-left-width' => [$self->border_left_width,
1528 $self->get_property_priority
1529 ('border-left-width')],
1530 };
1531 my $i = 0;
1532 for my $prop (qw/border-top border-right border-bottom border-left/) {
1533 if (length $r->{$prop.'-color'}->[0] and
1534 length $r->{$prop.'-style'}->[0] and
1535 length $r->{$prop.'-width'}->[0] and
1536 $r->{$prop.'-color'}->[1] eq $r->{$prop.'-style'}->[1] and
1537 $r->{$prop.'-color'}->[1] eq $r->{$prop.'-width'}->[1]) {
1538 my $inherit = 0;
1539 $inherit++ if $r->{$prop.'-color'}->[0] eq 'inherit';
1540 $inherit++ if $r->{$prop.'-style'}->[0] eq 'inherit';
1541 $inherit++ if $r->{$prop.'-width'}->[0] eq 'inherit';
1542 if ($inherit == 3) {
1543 $r->{$prop} = $r->{$prop.'-color'};
1544 } elsif ($inherit) {
1545 next;
1546 } else {
1547 $r->{$prop} = [$r->{$prop.'-width'}->[0] . ' ' .
1548 $r->{$prop.'-style'}->[0] . ' ' .
1549 $r->{$prop.'-color'}->[0],
1550 $r->{$prop.'-color'}->[1]];
1551 }
1552 delete $r->{$prop.'-width'};
1553 delete $r->{$prop.'-style'};
1554 delete $r->{$prop.'-color'};
1555 $i++;
1556 }
1557 }
1558 if ($i == 4 and
1559 $r->{'border-top'}->[0] eq $r->{'border-right'}->[0] and
1560 $r->{'border-right'}->[0] eq $r->{'border-bottom'}->[0] and
1561 $r->{'border-bottom'}->[0] eq $r->{'border-left'}->[0] and
1562 $r->{'border-top'}->[1] eq $r->{'border-right'}->[1] and
1563 $r->{'border-right'}->[1] eq $r->{'border-bottom'}->[1] and
1564 $r->{'border-bottom'}->[1] eq $r->{'border-left'}->[1]) {
1565 return {border => $r->{'border-top'}};
1566 }
1567
1568 unless ($i) {
1569 for my $prop (qw/color style width/) {
1570 if (defined $r->{'border-top-'.$prop} and
1571 defined $r->{'border-bottom-'.$prop} and
1572 defined $r->{'border-right-'.$prop} and
1573 defined $r->{'border-left-'.$prop} and
1574 length $r->{'border-top-'.$prop}->[0] and
1575 length $r->{'border-bottom-'.$prop}->[0] and
1576 length $r->{'border-right-'.$prop}->[0] and
1577 length $r->{'border-left-'.$prop}->[0] and
1578 $r->{'border-top-'.$prop}->[1]
1579 eq $r->{'border-bottom-'.$prop}->[1] and
1580 $r->{'border-top-'.$prop}->[1]
1581 eq $r->{'border-right-'.$prop}->[1] and
1582 $r->{'border-top-'.$prop}->[1]
1583 eq $r->{'border-left-'.$prop}->[1]) {
1584 my @v = ($r->{'border-top-'.$prop},
1585 $r->{'border-right-'.$prop},
1586 $r->{'border-bottom-'.$prop},
1587 $r->{'border-left-'.$prop});
1588 my $inherit = 0;
1589 for (@v) {
1590 $inherit++ if $_->[0] eq 'inherit';
1591 }
1592 if ($inherit == 4) {
1593 $r->{'border-'.$prop}
1594 = ['inherit', $r->{'border-top-'.$prop}->[1]];
1595 } elsif ($inherit) {
1596 next;
1597 } else {
1598 pop @v
1599 if $r->{'border-right-'.$prop}->[0]
1600 eq $r->{'border-left-'.$prop}->[0];
1601 pop @v
1602 if $r->{'border-bottom-'.$prop}->[0]
1603 eq $r->{'border-top-'.$prop}->[0];
1604 pop @v
1605 if $r->{'border-right-'.$prop}->[0]
1606 eq $r->{'border-top-'.$prop}->[0];
1607 $r->{'border-'.$prop} = [(join ' ', map {$_->[0]} @v),
1608 $r->{'border-top-'.$prop}->[1]];
1609 }
1610 delete $r->{'border-top-'.$prop};
1611 delete $r->{'border-bottom-'.$prop};
1612 delete $r->{'border-right-'.$prop};
1613 delete $r->{'border-left-'.$prop};
1614 }
1615 }
1616 }
1617
1618 delete $r->{$_} for grep {not length $r->{$_}->[0]} keys %$r;
1619 return $r;
1620 },
1621 initial => ['KEYWORD', 'currentcolor'],
1622 #inherited => 0,
1623 compute => $Prop->{color}->{compute},
1624 };
1625 $Attr->{border_top_color} = $Prop->{'border-top-color'};
1626 $Key->{border_top_color} = $Prop->{'border-top-color'};
1627
1628 $Prop->{'border-right-color'} = {
1629 css => 'border-right-color',
1630 dom => 'border_right_color',
1631 key => 'border_right_color',
1632 parse => $parse_color,
1633 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
1634 initial => ['KEYWORD', 'currentcolor'],
1635 #inherited => 0,
1636 compute => $Prop->{color}->{compute},
1637 };
1638 $Attr->{border_right_color} = $Prop->{'border-right-color'};
1639 $Key->{border_right_color} = $Prop->{'border-right-color'};
1640
1641 $Prop->{'border-bottom-color'} = {
1642 css => 'border-bottom-color',
1643 dom => 'border_bottom_color',
1644 key => 'border_bottom_color',
1645 parse => $parse_color,
1646 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
1647 initial => ['KEYWORD', 'currentcolor'],
1648 #inherited => 0,
1649 compute => $Prop->{color}->{compute},
1650 };
1651 $Attr->{border_bottom_color} = $Prop->{'border-bottom-color'};
1652 $Key->{border_bottom_color} = $Prop->{'border-bottom-color'};
1653
1654 $Prop->{'border-left-color'} = {
1655 css => 'border-left-color',
1656 dom => 'border_left_color',
1657 key => 'border_left_color',
1658 parse => $parse_color,
1659 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
1660 initial => ['KEYWORD', 'currentcolor'],
1661 #inherited => 0,
1662 compute => $Prop->{color}->{compute},
1663 };
1664 $Attr->{border_left_color} = $Prop->{'border-left-color'};
1665 $Key->{border_left_color} = $Prop->{'border-left-color'};
1666
1667 $Prop->{'outline-color'} = {
1668 css => 'outline-color',
1669 dom => 'outline_color',
1670 key => 'outline_color',
1671 parse => $parse_color,
1672 serialize_multiple => sub {
1673 my $self = shift;
1674 my $oc = $self->outline_color;
1675 my $os = $self->outline_style;
1676 my $ow = $self->outline_width;
1677 my $r = {};
1678 if (length $oc and length $os and length $ow) {
1679 $r->{outline} = [$ow . ' ' . $os . ' ' . $oc];
1680 } else {
1681 $r->{'outline-color'} = [$oc] if length $oc;
1682 $r->{'outline-style'} = [$os] if length $os;
1683 $r->{'outline-width'} = [$ow] if length $ow;
1684 }
1685 return $r;
1686 },
1687 initial => ['KEYWORD', '-manakai-invert-or-currentcolor'],
1688 #inherited => 0,
1689 compute => $Prop->{color}->{compute},
1690 };
1691 $Attr->{outline_color} = $Prop->{'outline-color'};
1692 $Key->{outline_color} = $Prop->{'outline-color'};
1693
1694 my $one_keyword_parser = sub {
1695 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1696
1697 if ($t->{type} == IDENT_TOKEN) {
1698 my $prop_value = lc $t->{value}; ## TODO: case folding
1699 if ($Prop->{$prop_name}->{keyword}->{$prop_value} and
1700 $self->{prop_value}->{$prop_name}->{$prop_value}) {
1701 $t = $tt->get_next_token;
1702 return ($t, {$prop_name => ["KEYWORD", $prop_value]});
1703 } elsif (my $v = $Prop->{$prop_name}->{keyword_replace}->{$prop_value}) {
1704 if ($self->{prop_value}->{$prop_name}->{$v}) {
1705 $t = $tt->get_next_token;
1706 return ($t, {$prop_name => ["KEYWORD", $v]});
1707 }
1708 } elsif ($prop_value eq 'inherit') {
1709 $t = $tt->get_next_token;
1710 return ($t, {$prop_name => ['INHERIT']});
1711 }
1712 }
1713
1714 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
1715 level => $self->{level}->{must},
1716 uri => \$self->{href},
1717 token => $t);
1718 return ($t, undef);
1719 };
1720
1721 $Prop->{display} = {
1722 css => 'display',
1723 dom => 'display',
1724 key => 'display',
1725 parse => $one_keyword_parser,
1726 keyword => {
1727 ## CSS 2.1
1728 block => 1, inline => 1, 'inline-block' => 1, 'inline-table' => 1,
1729 'list-item' => 1, none => 1,
1730 table => 1, 'table-caption' => 1, 'table-cell' => 1, 'table-column' => 1,
1731 'table-column-group' => 1, 'table-header-group' => 1,
1732 'table-footer-group' => 1, 'table-row' => 1, 'table-row-group' => 1,
1733 ## CSS 2.0
1734 compact => 1, marker => 1,
1735 },
1736 initial => ["KEYWORD", "inline"],
1737 #inherited => 0,
1738 compute => sub {
1739 my ($self, $element, $prop_name, $specified_value) = @_;
1740 ## NOTE: CSS 2.1 Section 9.7.
1741
1742 ## WARNING: |compute| for 'float' property invoke this CODE
1743 ## in some case. Careless modification might cause a infinite loop.
1744
1745 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1746 if ($specified_value->[1] eq 'none') {
1747 ## Case 1 [CSS 2.1]
1748 return $specified_value;
1749 } else {
1750 my $position = $self->get_computed_value ($element, 'position');
1751 if ($position->[0] eq 'KEYWORD' and
1752 ($position->[1] eq 'absolute' or
1753 $position->[1] eq 'fixed')) {
1754 ## Case 2 [CSS 2.1]
1755 #
1756 } else {
1757 my $float = $self->get_computed_value ($element, 'float');
1758 if ($float->[0] eq 'KEYWORD' and $float->[1] ne 'none') {
1759 ## Caes 3 [CSS 2.1]
1760 #
1761 } elsif (not defined $element->manakai_parent_element) {
1762 ## Case 4 [CSS 2.1]
1763 #
1764 } elsif ($specified_value->[1] eq 'marker') {
1765 ## TODO: If ::after or ::before, then 'marker'. Otherwise,
1766 return ['KEYWORD', 'inline'];
1767 } else {
1768 ## Case 5 [CSS 2.1]
1769 return $specified_value;
1770 }
1771 }
1772
1773 return ["KEYWORD",
1774 {
1775 'inline-table' => 'table',
1776 inline => 'block',
1777 'run-in' => 'block',
1778 'table-row-group' => 'block',
1779 'table-column' => 'block',
1780 'table-column-group' => 'block',
1781 'table-header-group' => 'block',
1782 'table-footer-group' => 'block',
1783 'table-row' => 'block',
1784 'table-cell' => 'block',
1785 'table-caption' => 'block',
1786 'inline-block' => 'block',
1787
1788 ## NOTE: Not in CSS 2.1, but maybe...
1789 compact => 'block',
1790 marker => 'block',
1791 }->{$specified_value->[1]} || $specified_value->[1]];
1792 }
1793 } else {
1794 return $specified_value; ## Maybe an error of the implementation.
1795 }
1796 },
1797 };
1798 $Attr->{display} = $Prop->{display};
1799 $Key->{display} = $Prop->{display};
1800
1801 $Prop->{position} = {
1802 css => 'position',
1803 dom => 'position',
1804 key => 'position',
1805 parse => $one_keyword_parser,
1806 keyword => {
1807 static => 1, relative => 1, absolute => 1, fixed => 1,
1808 },
1809 initial => ["KEYWORD", "static"],
1810 #inherited => 0,
1811 compute => $compute_as_specified,
1812 };
1813 $Attr->{position} = $Prop->{position};
1814 $Key->{position} = $Prop->{position};
1815
1816 $Prop->{float} = {
1817 css => 'float',
1818 dom => 'css_float',
1819 key => 'float',
1820 parse => $one_keyword_parser,
1821 keyword => {
1822 left => 1, right => 1, none => 1,
1823 },
1824 initial => ["KEYWORD", "none"],
1825 #inherited => 0,
1826 compute => sub {
1827 my ($self, $element, $prop_name, $specified_value) = @_;
1828 ## NOTE: CSS 2.1 Section 9.7.
1829
1830 ## WARNING: |compute| for 'display' property invoke this CODE
1831 ## in some case. Careless modification might cause a infinite loop.
1832
1833 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1834 if ($specified_value->[1] eq 'none') {
1835 ## Case 1 [CSS 2.1]
1836 return $specified_value;
1837 } else {
1838 my $position = $self->get_computed_value ($element, 'position');
1839 if ($position->[0] eq 'KEYWORD' and
1840 ($position->[1] eq 'absolute' or
1841 $position->[1] eq 'fixed')) {
1842 ## Case 2 [CSS 2.1]
1843 return ["KEYWORD", "none"];
1844 }
1845 }
1846 }
1847
1848 ## ISSUE: CSS 2.1 section 9.7 and 9.5.1 ('float' definition) disagree
1849 ## on computed value of 'float' property.
1850
1851 ## Case 3, 4, and 5 [CSS 2.1]
1852 return $specified_value;
1853 },
1854 };
1855 $Attr->{css_float} = $Prop->{float};
1856 $Attr->{style_float} = $Prop->{float}; ## NOTE: IEism
1857 $Key->{float} = $Prop->{float};
1858
1859 $Prop->{clear} = {
1860 css => 'clear',
1861 dom => 'clear',
1862 key => 'clear',
1863 parse => $one_keyword_parser,
1864 keyword => {
1865 left => 1, right => 1, none => 1, both => 1,
1866 },
1867 initial => ["KEYWORD", "none"],
1868 #inherited => 0,
1869 compute => $compute_as_specified,
1870 };
1871 $Attr->{clear} = $Prop->{clear};
1872 $Key->{clear} = $Prop->{clear};
1873
1874 $Prop->{direction} = {
1875 css => 'direction',
1876 dom => 'direction',
1877 key => 'direction',
1878 parse => $one_keyword_parser,
1879 keyword => {
1880 ltr => 1, rtl => 1,
1881 },
1882 initial => ["KEYWORD", "ltr"],
1883 inherited => 1,
1884 compute => $compute_as_specified,
1885 };
1886 $Attr->{direction} = $Prop->{direction};
1887 $Key->{direction} = $Prop->{direction};
1888
1889 $Prop->{'unicode-bidi'} = {
1890 css => 'unicode-bidi',
1891 dom => 'unicode_bidi',
1892 key => 'unicode_bidi',
1893 parse => $one_keyword_parser,
1894 keyword => {
1895 normal => 1, embed => 1, 'bidi-override' => 1,
1896 },
1897 initial => ["KEYWORD", "normal"],
1898 #inherited => 0,
1899 compute => $compute_as_specified,
1900 };
1901 $Attr->{unicode_bidi} = $Prop->{'unicode-bidi'};
1902 $Key->{unicode_bidi} = $Prop->{'unicode-bidi'};
1903
1904 $Prop->{'overflow-x'} = {
1905 css => 'overflow-x',
1906 dom => 'overflow_x',
1907 key => 'overflow_x',
1908 parse => $one_keyword_parser,
1909 serialize_multiple => sub {
1910 my $self = shift;
1911
1912 my $x = $self->overflow_x;
1913 my $xi = $self->get_property_priority ('overflow-x');
1914 my $y = $self->overflow_y;
1915 my $yi = $self->get_property_priority ('overflow-y');
1916
1917 if (length $x) {
1918 if (length $y) {
1919 if ($x eq $y and $xi eq $yi) {
1920 return {overflow => [$x, $xi]};
1921 } else {
1922 return {'overflow-x' => [$x, $xi], 'overflow-y' => [$y, $yi]};
1923 }
1924 } else {
1925 return {'overflow-x' => [$x, $xi]};
1926 }
1927 } else {
1928 if (length $y) {
1929 return {'overflow-y' => [$y, $yi]};
1930 } else {
1931 return {};
1932 }
1933 }
1934 },
1935 keyword => {
1936 visible => 1, hidden => 1, scroll => 1, auto => 1,
1937 '-moz-hidden-unscrollable' => 1, '-webkit-marquee' => 1,
1938 },
1939 initial => ["KEYWORD", "visible"],
1940 #inherited => 0,
1941 compute => $compute_as_specified,
1942 };
1943 $Attr->{overflow_x} = $Prop->{'overflow-x'};
1944 $Key->{overflow_x} = $Prop->{'overflow-x'};
1945
1946 $Prop->{'overflow-y'} = {
1947 css => 'overflow-y',
1948 dom => 'overflow_y',
1949 key => 'overflow_y',
1950 parse => $one_keyword_parser,
1951 serialize_multiple => $Prop->{'overflow-x'}->{serialize_multiple},
1952 keyword => $Prop->{'overflow-x'}->{keyword},
1953 initial => ["KEYWORD", "visible"],
1954 #inherited => 0,
1955 compute => $compute_as_specified,
1956 };
1957 $Attr->{overflow_y} = $Prop->{'overflow-y'};
1958 $Key->{overflow_y} = $Prop->{'overflow-y'};
1959
1960 $Prop->{overflow} = {
1961 css => 'overflow',
1962 dom => 'overflow',
1963 parse => sub {
1964 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1965 my ($t, $pv) = $one_keyword_parser->($self, $prop_name, $tt, $t, $onerror);
1966 if (defined $pv) {
1967 return ($t, {'overflow-x' => $pv->{overflow},
1968 'overflow-y' => $pv->{overflow}});
1969 } else {
1970 return ($t, $pv);
1971 }
1972 },
1973 keyword => $Prop->{'overflow-x'}->{keyword},
1974 serialize_multiple => $Prop->{'overflow-x'}->{serialize_multiple},
1975 };
1976 $Attr->{overflow} = $Prop->{overflow};
1977
1978 $Prop->{visibility} = {
1979 css => 'visibility',
1980 dom => 'visibility',
1981 key => 'visibility',
1982 parse => $one_keyword_parser,
1983 keyword => {
1984 visible => 1, hidden => 1, collapse => 1,
1985 },
1986 initial => ["KEYWORD", "visible"],
1987 #inherited => 0,
1988 compute => $compute_as_specified,
1989 };
1990 $Attr->{visibility} = $Prop->{visibility};
1991 $Key->{visibility} = $Prop->{visibility};
1992
1993 $Prop->{'list-style-type'} = {
1994 css => 'list-style-type',
1995 dom => 'list_style_type',
1996 key => 'list_style_type',
1997 parse => $one_keyword_parser,
1998 keyword => {
1999 ## CSS 2.1
2000 qw/
2001 disc 1 circle 1 square 1 decimal 1 decimal-leading-zero 1
2002 lower-roman 1 upper-roman 1 lower-greek 1 lower-latin 1
2003 upper-latin 1 armenian 1 georgian 1 lower-alpha 1 upper-alpha 1
2004 none 1
2005 /,
2006 ## CSS 2.0
2007 hebrew => 1, 'cjk-ideographic' => 1, hiragana => 1, katakana => 1,
2008 'hiragana-iroha' => 1, 'katakana-iroha' => 1,
2009 },
2010 initial => ["KEYWORD", 'disc'],
2011 inherited => 1,
2012 compute => $compute_as_specified,
2013 };
2014 $Attr->{list_style_type} = $Prop->{'list-style-type'};
2015 $Key->{list_style_type} = $Prop->{'list-style-type'};
2016
2017 $Prop->{'list-style-position'} = {
2018 css => 'list-style-position',
2019 dom => 'list_style_position',
2020 key => 'list_style_position',
2021 parse => $one_keyword_parser,
2022 keyword => {
2023 inside => 1, outside => 1,
2024 },
2025 initial => ["KEYWORD", 'outside'],
2026 inherited => 1,
2027 compute => $compute_as_specified,
2028 };
2029 $Attr->{list_style_position} = $Prop->{'list-style-position'};
2030 $Key->{list_style_position} = $Prop->{'list-style-position'};
2031
2032 $Prop->{'page-break-before'} = {
2033 css => 'page-break-before',
2034 dom => 'page_break_before',
2035 key => 'page_break_before',
2036 parse => $one_keyword_parser,
2037 keyword => {
2038 auto => 1, always => 1, avoid => 1, left => 1, right => 1,
2039 },
2040 initial => ["KEYWORD", 'auto'],
2041 #inherited => 0,
2042 compute => $compute_as_specified,
2043 };
2044 $Attr->{page_break_before} = $Prop->{'page-break-before'};
2045 $Key->{page_break_before} = $Prop->{'page-break-before'};
2046
2047 $Prop->{'page-break-after'} = {
2048 css => 'page-break-after',
2049 dom => 'page_break_after',
2050 key => 'page_break_after',
2051 parse => $one_keyword_parser,
2052 keyword => {
2053 auto => 1, always => 1, avoid => 1, left => 1, right => 1,
2054 },
2055 initial => ["KEYWORD", 'auto'],
2056 #inherited => 0,
2057 compute => $compute_as_specified,
2058 };
2059 $Attr->{page_break_after} = $Prop->{'page-break-after'};
2060 $Key->{page_break_after} = $Prop->{'page-break-after'};
2061
2062 $Prop->{'page-break-inside'} = {
2063 css => 'page-break-inside',
2064 dom => 'page_break_inside',
2065 key => 'page_break_inside',
2066 parse => $one_keyword_parser,
2067 keyword => {
2068 auto => 1, avoid => 1,
2069 },
2070 initial => ["KEYWORD", 'auto'],
2071 inherited => 1,
2072 compute => $compute_as_specified,
2073 };
2074 $Attr->{page_break_inside} = $Prop->{'page-break-inside'};
2075 $Key->{page_break_inside} = $Prop->{'page-break-inside'};
2076
2077 $Prop->{'background-repeat'} = {
2078 css => 'background-repeat',
2079 dom => 'background_repeat',
2080 key => 'background_repeat',
2081 parse => $one_keyword_parser,
2082 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
2083 keyword => {
2084 repeat => 1, 'repeat-x' => 1, 'repeat-y' => 1, 'no-repeat' => 1,
2085 },
2086 initial => ["KEYWORD", 'repeat'],
2087 #inherited => 0,
2088 compute => $compute_as_specified,
2089 };
2090 $Attr->{background_repeat} = $Prop->{'background-repeat'};
2091 $Key->{backgroud_repeat} = $Prop->{'background-repeat'};
2092
2093 $Prop->{'background-attachment'} = {
2094 css => 'background-attachment',
2095 dom => 'background_attachment',
2096 key => 'background_attachment',
2097 parse => $one_keyword_parser,
2098 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
2099 keyword => {
2100 scroll => 1, fixed => 1,
2101 },
2102 initial => ["KEYWORD", 'scroll'],
2103 #inherited => 0,
2104 compute => $compute_as_specified,
2105 };
2106 $Attr->{background_attachment} = $Prop->{'background-attachment'};
2107 $Key->{backgroud_attachment} = $Prop->{'background-attachment'};
2108
2109 $Prop->{'font-style'} = {
2110 css => 'font-style',
2111 dom => 'font_style',
2112 key => 'font_style',
2113 parse => $one_keyword_parser,
2114 keyword => {
2115 normal => 1, italic => 1, oblique => 1,
2116 },
2117 initial => ["KEYWORD", 'normal'],
2118 inherited => 1,
2119 compute => $compute_as_specified,
2120 };
2121 $Attr->{font_style} = $Prop->{'font-style'};
2122 $Key->{font_style} = $Prop->{'font-style'};
2123
2124 $Prop->{'font-variant'} = {
2125 css => 'font-variant',
2126 dom => 'font_variant',
2127 key => 'font_variant',
2128 parse => $one_keyword_parser,
2129 keyword => {
2130 normal => 1, 'small-caps' => 1,
2131 },
2132 initial => ["KEYWORD", 'normal'],
2133 inherited => 1,
2134 compute => $compute_as_specified,
2135 };
2136 $Attr->{font_variant} = $Prop->{'font-variant'};
2137 $Key->{font_variant} = $Prop->{'font-variant'};
2138
2139 $Prop->{'text-align'} = {
2140 css => 'text-align',
2141 dom => 'text_align',
2142 key => 'text_align',
2143 parse => $one_keyword_parser,
2144 keyword => {
2145 left => 1, right => 1, center => 1, justify => 1, ## CSS 2
2146 begin => 1, end => 1, ## CSS 3
2147 },
2148 initial => ["KEYWORD", 'begin'],
2149 inherited => 1,
2150 compute => $compute_as_specified,
2151 };
2152 $Attr->{text_align} = $Prop->{'text-align'};
2153 $Key->{text_align} = $Prop->{'text-align'};
2154
2155 $Prop->{'text-transform'} = {
2156 css => 'text-transform',
2157 dom => 'text_transform',
2158 key => 'text_transform',
2159 parse => $one_keyword_parser,
2160 keyword => {
2161 capitalize => 1, uppercase => 1, lowercase => 1, none => 1,
2162 },
2163 initial => ["KEYWORD", 'none'],
2164 inherited => 1,
2165 compute => $compute_as_specified,
2166 };
2167 $Attr->{text_transform} = $Prop->{'text-transform'};
2168 $Key->{text_transform} = $Prop->{'text-transform'};
2169
2170 $Prop->{'white-space'} = {
2171 css => 'white-space',
2172 dom => 'white_space',
2173 key => 'white_space',
2174 parse => $one_keyword_parser,
2175 keyword => {
2176 normal => 1, pre => 1, nowrap => 1, 'pre-wrap' => 1, 'pre-line' => 1,
2177 },
2178 keyword_replace => {
2179 '-moz-pre-wrap' => 'pre-wrap', '-o-pre-wrap' => 'pre-wrap',
2180 },
2181 initial => ["KEYWORD", 'normal'],
2182 inherited => 1,
2183 compute => $compute_as_specified,
2184 };
2185 $Attr->{white_space} = $Prop->{'white-space'};
2186 $Key->{white_space} = $Prop->{'white-space'};
2187
2188 $Prop->{'caption-side'} = {
2189 css => 'caption-side',
2190 dom => 'caption_side',
2191 key => 'caption_side',
2192 parse => $one_keyword_parser,
2193 keyword => {
2194 ## CSS 2.1
2195 top => 1, bottom => 1,
2196 ## CSS 2
2197 left => 1, right => 1,
2198 },
2199 initial => ['KEYWORD', 'top'],
2200 inherited => 1,
2201 compute => $compute_as_specified,
2202 };
2203 $Attr->{caption_side} = $Prop->{'caption-side'};
2204 $Key->{caption_side} = $Prop->{'caption-side'};
2205
2206 $Prop->{'table-layout'} = {
2207 css => 'table-layout',
2208 dom => 'table_layout',
2209 key => 'table_layout',
2210 parse => $one_keyword_parser,
2211 keyword => {
2212 auto => 1, fixed => 1,
2213 },
2214 initial => ['KEYWORD', 'auto'],
2215 #inherited => 0,
2216 compute => $compute_as_specified,
2217 };
2218 $Attr->{table_layout} = $Prop->{'table-layout'};
2219 $Key->{table_layout} = $Prop->{'table-layout'};
2220
2221 $Prop->{'border-collapse'} = {
2222 css => 'border-collapse',
2223 dom => 'border_collapse',
2224 key => 'border_collapse',
2225 parse => $one_keyword_parser,
2226 keyword => {
2227 collapse => 1, separate => 1,
2228 },
2229 initial => ['KEYWORD', 'separate'],
2230 inherited => 1,
2231 compute => $compute_as_specified,
2232 };
2233 $Attr->{border_collapse} = $Prop->{'border-collapse'};
2234 $Key->{border_collapse} = $Prop->{'border-collapse'};
2235
2236 $Prop->{'empty-cells'} = {
2237 css => 'empty-cells',
2238 dom => 'empty_cells',
2239 key => 'empty_cells',
2240 parse => $one_keyword_parser,
2241 keyword => {
2242 show => 1, hide => 1,
2243 },
2244 initial => ['KEYWORD', 'show'],
2245 inherited => 1,
2246 compute => $compute_as_specified,
2247 };
2248 $Attr->{empty_cells} = $Prop->{'empty-cells'};
2249 $Key->{empty_cells} = $Prop->{'empty-cells'};
2250
2251 $Prop->{'z-index'} = {
2252 css => 'z-index',
2253 dom => 'z_index',
2254 key => 'z_index',
2255 parse => sub {
2256 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2257
2258 my $has_sign;
2259 my $sign = 1;
2260 if ($t->{type} == MINUS_TOKEN) {
2261 $sign = -1;
2262 $has_sign = 1;
2263 $t = $tt->get_next_token;
2264 } elsif ($t->{type} == PLUS_TOKEN) {
2265 $has_sign = 1;
2266 $t = $tt->get_next_token;
2267 }
2268
2269 if ($t->{type} == NUMBER_TOKEN) {
2270 ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/z-index> for
2271 ## browser compatibility issue.
2272 my $value = $t->{number};
2273 $t = $tt->get_next_token;
2274 return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
2275 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2276 my $value = lc $t->{value}; ## TODO: case
2277 if ($value eq 'auto') {
2278 ## NOTE: |z-index| is the default value and therefore it must be
2279 ## supported anyway.
2280 $t = $tt->get_next_token;
2281 return ($t, {$prop_name => ["KEYWORD", 'auto']});
2282 } elsif ($value eq 'inherit') {
2283 $t = $tt->get_next_token;
2284 return ($t, {$prop_name => ['INHERIT']});
2285 }
2286 }
2287
2288 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
2289 level => $self->{level}->{must},
2290 uri => \$self->{href},
2291 token => $t);
2292 return ($t, undef);
2293 },
2294 initial => ['KEYWORD', 'auto'],
2295 #inherited => 0,
2296 compute => $compute_as_specified,
2297 };
2298 $Attr->{z_index} = $Prop->{'z-index'};
2299 $Key->{z_index} = $Prop->{'z-index'};
2300
2301 $Prop->{'font-size-adjust'} = {
2302 css => 'font-size-adjust',
2303 dom => 'font_size_adjust',
2304 key => 'font_size_adjust',
2305 parse => sub {
2306 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2307
2308 my $has_sign;
2309 my $sign = 1;
2310 if ($t->{type} == MINUS_TOKEN) {
2311 $sign = -1;
2312 $has_sign = 1;
2313 $t = $tt->get_next_token;
2314 } elsif ($t->{type} == PLUS_TOKEN) {
2315 $has_sign = 1;
2316 $t = $tt->get_next_token;
2317 }
2318
2319 if ($t->{type} == NUMBER_TOKEN) {
2320 my $value = $t->{number};
2321 $t = $tt->get_next_token;
2322 return ($t, {$prop_name => ["NUMBER", $sign * $value]});
2323 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2324 my $value = lc $t->{value}; ## TODO: case
2325 if ($value eq 'none') {
2326 $t = $tt->get_next_token;
2327 return ($t, {$prop_name => ["KEYWORD", $value]});
2328 } elsif ($value eq 'inherit') {
2329 $t = $tt->get_next_token;
2330 return ($t, {$prop_name => ['INHERIT']});
2331 }
2332 }
2333
2334 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
2335 level => $self->{level}->{must},
2336 uri => \$self->{href},
2337 token => $t);
2338 return ($t, undef);
2339 },
2340 initial => ['KEYWORD', 'none'],
2341 inherited => 1,
2342 compute => $compute_as_specified,
2343 };
2344 $Attr->{font_size_adjust} = $Prop->{'font-size-adjust'};
2345 $Key->{font_size_adjust} = $Prop->{'font-size-adjust'};
2346
2347 $Prop->{orphans} = {
2348 css => 'orphans',
2349 dom => 'orphans',
2350 key => 'orphans',
2351 parse => sub {
2352 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2353
2354 my $has_sign;
2355 my $sign = 1;
2356 if ($t->{type} == MINUS_TOKEN) {
2357 $t = $tt->get_next_token;
2358 $has_sign = 1;
2359 $sign = -1;
2360 } elsif ($t->{type} == PLUS_TOKEN) {
2361 $t = $tt->get_next_token;
2362 $has_sign = 1;
2363 }
2364
2365 if ($t->{type} == NUMBER_TOKEN) {
2366 ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/orphans> and
2367 ## <http://suika.fam.cx/gate/2005/sw/widows> for
2368 ## browser compatibility issue.
2369 my $value = $t->{number};
2370 $t = $tt->get_next_token;
2371 return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
2372 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2373 my $value = lc $t->{value}; ## TODO: case
2374 if ($value eq 'inherit') {
2375 $t = $tt->get_next_token;
2376 return ($t, {$prop_name => ['INHERIT']});
2377 }
2378 }
2379
2380 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
2381 level => $self->{level}->{must},
2382 uri => \$self->{href},
2383 token => $t);
2384 return ($t, undef);
2385 },
2386 initial => ['NUMBER', 2],
2387 inherited => 1,
2388 compute => $compute_as_specified,
2389 };
2390 $Attr->{orphans} = $Prop->{orphans};
2391 $Key->{orphans} = $Prop->{orphans};
2392
2393 $Prop->{widows} = {
2394 css => 'widows',
2395 dom => 'widows',
2396 key => 'widows',
2397 parse => $Prop->{orphans}->{parse},
2398 initial => ['NUMBER', 2],
2399 inherited => 1,
2400 compute => $compute_as_specified,
2401 };
2402 $Attr->{widows} = $Prop->{widows};
2403 $Key->{widows} = $Prop->{widows};
2404
2405 $Prop->{opacity} = {
2406 css => 'opacity',
2407 dom => 'opacity',
2408 key => 'opacity',
2409 parse => sub {
2410 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2411
2412 my $has_sign;
2413 my $sign = 1;
2414 if ($t->{type} == MINUS_TOKEN) {
2415 $t = $tt->get_next_token;
2416 $has_sign = 1;
2417 $sign = -1;
2418 } elsif ($t->{type} == PLUS_TOKEN) {
2419 $t = $tt->get_next_token;
2420 $has_sign = 1;
2421 }
2422
2423 if ($t->{type} == NUMBER_TOKEN) {
2424 ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/opacity> for
2425 ## browser compatibility issue.
2426 my $value = $t->{number};
2427 $t = $tt->get_next_token;
2428 return ($t, {$prop_name => ["NUMBER", $sign * $value]});
2429 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2430 my $value = lc $t->{value}; ## TODO: case
2431 if ($value eq 'inherit') {
2432 $t = $tt->get_next_token;
2433 return ($t, {$prop_name => ['INHERIT']});
2434 }
2435 }
2436
2437 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
2438 level => $self->{level}->{must},
2439 uri => \$self->{href},
2440 token => $t);
2441 return ($t, undef);
2442 },
2443 initial => ['NUMBER', 2],
2444 inherited => 1,
2445 compute => sub {
2446 my ($self, $element, $prop_name, $specified_value) = @_;
2447
2448 if (defined $specified_value) {
2449 if ($specified_value->[0] eq 'NUMBER') {
2450 if ($specified_value->[1] < 0) {
2451 return ['NUMBER', 0];
2452 } elsif ($specified_value->[1] > 1) {
2453 return ['NUMBER', 1];
2454 }
2455 }
2456 }
2457
2458 return $specified_value;
2459 },
2460 serialize_multiple => sub {
2461 ## NOTE: This CODE is necessary to avoid two 'opacity' properties
2462 ## are outputed in |cssText| (for 'opacity' and for '-moz-opacity').
2463 return {opacity => [shift->opacity]},
2464 },
2465 };
2466 $Attr->{opacity} = $Prop->{opacity};
2467 $Key->{opacity} = $Prop->{opacity};
2468
2469 $Prop->{'-moz-opacity'} = $Prop->{opacity};
2470 $Attr->{_moz_opacity} = $Attr->{opacity};
2471
2472 my $length_unit = {
2473 em => 1, ex => 1, px => 1,
2474 in => 1, cm => 1, mm => 1, pt => 1, pc => 1,
2475 };
2476
2477 my $length_percentage_keyword_parser = sub ($$$$$) {
2478 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2479
2480 ## NOTE: Allowed keyword must have true value for $self->{prop_value}->{$_}.
2481
2482 my $sign = 1;
2483 my $has_sign;
2484 if ($t->{type} == MINUS_TOKEN) {
2485 $t = $tt->get_next_token;
2486 $has_sign = 1;
2487 $sign = -1;
2488 } elsif ($t->{type} == PLUS_TOKEN) {
2489 $t = $tt->get_next_token;
2490 $has_sign = 1;
2491 }
2492 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
2493
2494 if ($t->{type} == DIMENSION_TOKEN) {
2495 my $value = $t->{number} * $sign;
2496 my $unit = lc $t->{value}; ## TODO: case
2497 if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
2498 $t = $tt->get_next_token;
2499 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
2500 }
2501 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2502 my $value = $t->{number} * $sign;
2503 if ($allow_negative or $value >= 0) {
2504 $t = $tt->get_next_token;
2505 return ($t, {$prop_name => ['PERCENTAGE', $value]});
2506 }
2507 } elsif ($t->{type} == NUMBER_TOKEN and
2508 ($self->{unitless_px} or $t->{number} == 0)) {
2509 my $value = $t->{number} * $sign;
2510 if ($allow_negative or $value >=0) {
2511 $t = $tt->get_next_token;
2512 return ($t, {$prop_name => ['DIMENSION', $value, 'px']});
2513 }
2514 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2515 my $value = lc $t->{value}; ## TODO: case
2516 if ($Prop->{$prop_name}->{keyword}->{$value}) {
2517 if ($Prop->{$prop_name}->{keyword}->{$value} == 1 or
2518 $self->{prop_value}->{$prop_name}->{$value}) {
2519 $t = $tt->get_next_token;
2520 return ($t, {$prop_name => ['KEYWORD', $value]});
2521 }
2522 } elsif ($value eq 'inherit') {
2523 $t = $tt->get_next_token;
2524 return ($t, {$prop_name => ['INHERIT']});
2525 }
2526 ## NOTE: In the "else" case, don't procede the |$t| pointer
2527 ## for the support of 'border-top' property (and similar ones).
2528 }
2529
2530 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
2531 level => $self->{level}->{must},
2532 uri => \$self->{href},
2533 token => $t);
2534 return ($t, undef);
2535 }; # $length_percentage_keyword_parser
2536
2537 my $length_keyword_parser = sub {
2538 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2539
2540 my $has_sign;
2541 my $sign = 1;
2542 if ($t->{type} == MINUS_TOKEN) {
2543 $t = $tt->get_next_token;
2544 $has_sign = 1;
2545 $sign = -1;
2546 } elsif ($t->{type} == PLUS_TOKEN) {
2547 $t = $tt->get_next_token;
2548 $has_sign = 1;
2549 }
2550 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
2551
2552 if ($t->{type} == DIMENSION_TOKEN) {
2553 my $value = $t->{number} * $sign;
2554 my $unit = lc $t->{value}; ## TODO: case
2555 if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
2556 $t = $tt->get_next_token;
2557 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
2558 }
2559 } elsif ($t->{type} == NUMBER_TOKEN and
2560 ($self->{unitless_px} or $t->{number} == 0)) {
2561 my $value = $t->{number} * $sign;
2562 if ($allow_negative or $value >= 0) {
2563 $t = $tt->get_next_token;
2564 return ($t, {$prop_name => ['DIMENSION', $value, 'px']});
2565 }
2566 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
2567 my $value = lc $t->{value}; ## TODO: case
2568 if ($Prop->{$prop_name}->{keyword}->{$value}) {
2569 $t = $tt->get_next_token;
2570 return ($t, {$prop_name => ['KEYWORD', $value]});
2571 } elsif ($value eq 'inherit') {
2572 $t = $tt->get_next_token;
2573 return ($t, {$prop_name => ['INHERIT']});
2574 }
2575 }
2576
2577 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
2578 level => $self->{level}->{must},
2579 uri => \$self->{href},
2580 token => $t);
2581 return ($t, undef);
2582 }; # $length_keyword_parser
2583
2584 $Prop->{'font-size'} = {
2585 css => 'font-size',
2586 dom => 'font_size',
2587 key => 'font_size',
2588 parse => $length_percentage_keyword_parser,
2589 #allow_negative => 0,
2590 keyword => {
2591 'xx-small' => 1, 'x-small' => 1, small => 1, medium => 1,
2592 large => 1, 'x-large' => 1, 'xx-large' => 1,
2593 '-manakai-xxx-large' => 1, '-webkit-xxx-large' => 1,
2594 larger => 1, smaller => 1,
2595 },
2596 initial => ['KEYWORD', 'medium'],
2597 inherited => 1,
2598 compute => sub {
2599 my ($self, $element, $prop_name, $specified_value) = @_;
2600
2601 if (defined $specified_value) {
2602 if ($specified_value->[0] eq 'DIMENSION') {
2603 my $unit = $specified_value->[2];
2604 my $value = $specified_value->[1];
2605
2606 if ($unit eq 'em' or $unit eq 'ex') {
2607 $value *= 0.5 if $unit eq 'ex';
2608 ## TODO: Preferred way to determine the |ex| size is defined
2609 ## in CSS 2.1.
2610
2611 my $parent_element = $element->manakai_parent_element;
2612 if (defined $parent_element) {
2613 $value *= $self->get_computed_value ($parent_element, $prop_name)
2614 ->[1];
2615 } else {
2616 $value *= $self->{font_size}->[3]; # medium
2617 }
2618 $unit = 'px';
2619 } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
2620 ($value *= 12, $unit = 'pc') if $unit eq 'pc';
2621 ($value /= 72, $unit = 'in') if $unit eq 'pt';
2622 ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
2623 ($value *= 10, $unit = 'mm') if $unit eq 'cm';
2624 ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
2625 }
2626 ## else: consistency error
2627
2628 return ['DIMENSION', $value, $unit];
2629 } elsif ($specified_value->[0] eq 'PERCENTAGE') {
2630 my $parent_element = $element->manakai_parent_element;
2631 my $parent_cv;
2632 if (defined $parent_element) {
2633 $parent_cv = $self->get_computed_value
2634 ($parent_element, $prop_name);
2635 } else {
2636 $parent_cv = [undef, $self->{font_size}->[3]];
2637 }
2638 return ['DIMENSION', $parent_cv->[1] * $specified_value->[1] / 100,
2639 'px'];
2640 } elsif ($specified_value->[0] eq 'KEYWORD') {
2641 if ($specified_value->[1] eq 'larger') {
2642 my $parent_element = $element->manakai_parent_element;
2643 if (defined $parent_element) {
2644 my $parent_cv = $self->get_computed_value
2645 ($parent_element, $prop_name);
2646 return ['DIMENSION',
2647 $self->{get_larger_font_size}->($self, $parent_cv->[1]),
2648 'px'];
2649 } else { ## 'larger' relative to 'medium', initial of 'font-size'
2650 return ['DIMENSION', $self->{font_size}->[4], 'px'];
2651 }
2652 } elsif ($specified_value->[1] eq 'smaller') {
2653 my $parent_element = $element->manakai_parent_element;
2654 if (defined $parent_element) {
2655 my $parent_cv = $self->get_computed_value
2656 ($parent_element, $prop_name);
2657 return ['DIMENSION',
2658 $self->{get_smaller_font_size}->($self, $parent_cv->[1]),
2659 'px'];
2660 } else { ## 'smaller' relative to 'medium', initial of 'font-size'
2661 return ['DIMENSION', $self->{font_size}->[2], 'px'];
2662 }
2663 } else {
2664 ## TODO: different computation in quirks mode?
2665 return ['DIMENSION', $self->{font_size}->[{
2666 'xx-small' => 0,
2667 'x-small' => 1,
2668 small => 2,
2669 medium => 3,
2670 large => 4,
2671 'x-large' => 5,
2672 'xx-large' => 6,
2673 '-manakai-xxx-large' => 7,
2674 '-webkit-xxx-large' => 7,
2675 }->{$specified_value->[1]}], 'px'];
2676 }
2677 }
2678 }
2679
2680 ## TODO: Should we convert '-manakai-xxx-large' to '-webkit-xxx-large'
2681 ## at the parse time?
2682
2683 return $specified_value;
2684 },
2685 };
2686 $Attr->{font_size} = $Prop->{'font-size'};
2687 $Key->{font_size} = $Prop->{'font-size'};
2688
2689 my $compute_length = sub {
2690 my ($self, $element, $prop_name, $specified_value) = @_;
2691
2692 if (defined $specified_value) {
2693 if ($specified_value->[0] eq 'DIMENSION') {
2694 my $unit = $specified_value->[2];
2695 my $value = $specified_value->[1];
2696
2697 if ($unit eq 'em' or $unit eq 'ex') {
2698 $value *= 0.5 if $unit eq 'ex';
2699 ## TODO: Preferred way to determine the |ex| size is defined
2700 ## in CSS 2.1.
2701
2702 $value *= $self->get_computed_value ($element, 'font-size')->[1];
2703 $unit = 'px';
2704 } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
2705 ($value *= 12, $unit = 'pc') if $unit eq 'pc';
2706 ($value /= 72, $unit = 'in') if $unit eq 'pt';
2707 ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
2708 ($value *= 10, $unit = 'mm') if $unit eq 'cm';
2709 ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
2710 }
2711
2712 return ['DIMENSION', $value, $unit];
2713 }
2714 }
2715
2716 return $specified_value;
2717 }; # $compute_length
2718
2719 $Prop->{'letter-spacing'} = {
2720 css => 'letter-spacing',
2721 dom => 'letter_spacing',
2722 key => 'letter_spacing',
2723 parse => $length_keyword_parser,
2724 allow_negative => 1,
2725 keyword => {normal => 1},
2726 initial => ['KEYWORD', 'normal'],
2727 inherited => 1,
2728 compute => $compute_length,
2729 };
2730 $Attr->{letter_spacing} = $Prop->{'letter-spacing'};
2731 $Key->{letter_spacing} = $Prop->{'letter-spacing'};
2732
2733 $Prop->{'word-spacing'} = {
2734 css => 'word-spacing',
2735 dom => 'word_spacing',
2736 key => 'word_spacing',
2737 parse => $length_keyword_parser,
2738 allow_negative => 1,
2739 keyword => {normal => 1},
2740 initial => ['KEYWORD', 'normal'],
2741 inherited => 1,
2742 compute => $compute_length,
2743 };
2744 $Attr->{word_spacing} = $Prop->{'word-spacing'};
2745 $Key->{word_spacing} = $Prop->{'word-spacing'};
2746
2747 $Prop->{'-manakai-border-spacing-x'} = {
2748 css => '-manakai-border-spacing-x',
2749 dom => '_manakai_border_spacing_x',
2750 key => 'border_spacing_x',
2751 parse => $length_keyword_parser,
2752 #allow_negative => 0,
2753 #keyword => {},
2754 serialize_multiple => sub {
2755 my $self = shift;
2756
2757 my $x = $self->_manakai_border_spacing_x;
2758 my $y = $self->_manakai_border_spacing_y;
2759 my $xi = $self->get_property_priority ('-manakai-border-spacing-x');
2760 my $yi = $self->get_property_priority ('-manakai-border-spacing-y');
2761 if (length $x) {
2762 if (length $y) {
2763 if ($xi eq $yi) {
2764 if ($x eq $y) {
2765 return {'border-spacing' => [$x, $xi]};
2766 } else {
2767 if ($x eq 'inherit' or $y eq 'inherit') {
2768 return {'-manakai-border-spacing-x' => [$x, $xi],
2769 '-manakai-border-spacing-y' => [$y, $yi]};
2770 } else {
2771 return {'border-spacing' => [$x . ' ' . $y, $xi]};
2772 }
2773 }
2774 } else {
2775 return {'-manakai-border-spacing-x' => [$x, $xi],
2776 '-manakai-border-spacing-y' => [$y, $yi]};
2777 }
2778 } else {
2779 return {'-manakai-border-spacing-x' => [$x, $xi]};
2780 }
2781 } else {
2782 if (length $y) {
2783 return {'-manakai-border-spacing-y' => [$y, $yi]};
2784 } else {
2785 return {};
2786 }
2787 }
2788 },
2789 initial => ['DIMENSION', 0, 'px'],
2790 inherited => 1,
2791 compute => $compute_length,
2792 };
2793 $Attr->{_manakai_border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
2794 $Key->{border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
2795
2796 $Prop->{'-manakai-border-spacing-y'} = {
2797 css => '-manakai-border-spacing-y',
2798 dom => '_manakai_border_spacing_y',
2799 key => 'border_spacing_y',
2800 parse => $length_keyword_parser,
2801 #allow_negative => 0,
2802 #keyword => {},
2803 serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
2804 ->{serialize_multiple},
2805 initial => ['DIMENSION', 0, 'px'],
2806 inherited => 1,
2807 compute => $compute_length,
2808 };
2809 $Attr->{_manakai_border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
2810 $Key->{border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
2811
2812 $Attr->{marker_offset} =
2813 $Key->{marker_offset} =
2814 $Prop->{'marker-offset'} = {
2815 css => 'marker-offset',
2816 dom => 'marker_offset',
2817 key => 'marker_offset',
2818 parse => $length_keyword_parser,
2819 allow_negative => 1,
2820 keyword => {auto => 1},
2821 initial => ['KEYWORD', 'auto'],
2822 #inherited => 0,
2823 compute => $compute_length,
2824 };
2825
2826 $Prop->{'margin-top'} = {
2827 css => 'margin-top',
2828 dom => 'margin_top',
2829 key => 'margin_top',
2830 parse => $length_percentage_keyword_parser,
2831 allow_negative => 1,
2832 keyword => {auto => 1},
2833 serialize_multiple => sub {
2834 my $self = shift;
2835
2836 ## NOTE: Same as |serialize_multiple| of 'padding-top'.
2837
2838 my $use_shorthand = 1;
2839 my $t = $self->margin_top;
2840 undef $use_shorthand unless length $t;
2841 my $t_i = $self->get_property_priority ('margin-top');
2842 my $r = $self->margin_right;
2843 undef $use_shorthand
2844 if not length $r or
2845 ($r eq 'inherit' and $t ne 'inherit') or
2846 ($t eq 'inherit' and $r ne 'inherit');
2847 my $r_i = $self->get_property_priority ('margin-right');
2848 undef $use_shorthand unless $r_i eq $t_i;
2849 my $b = $self->margin_bottom;
2850 undef $use_shorthand
2851 if not length $b or
2852 ($b eq 'inherit' and $t ne 'inherit') or
2853 ($t eq 'inherit' and $b ne 'inherit');
2854 my $b_i = $self->get_property_priority ('margin-bottom');
2855 undef $use_shorthand unless $b_i eq $t_i;
2856 my $l = $self->margin_left;
2857 undef $use_shorthand
2858 if not length $l or
2859 ($l eq 'inherit' and $t ne 'inherit') or
2860 ($t eq 'inherit' and $l ne 'inherit');
2861 my $l_i = $self->get_property_priority ('margin-left');
2862 undef $use_shorthand unless $l_i eq $t_i;
2863
2864 if ($use_shorthand) {
2865 $b .= ' ' . $l if $r ne $l;
2866 $r .= ' ' . $b if $t ne $b;
2867 $t .= ' ' . $r if $t ne $r;
2868 return {margin => [$t, $t_i]};
2869 } else {
2870 my $v = {};
2871 if (length $t) {
2872 $v->{'margin-top'} = [$t, $t_i];
2873 }
2874 if (length $r) {
2875 $v->{'margin-right'} = [$r, $r_i];
2876 }
2877 if (length $b) {
2878 $v->{'margin-bottom'} = [$b, $b_i];
2879 }
2880 if (length $l) {
2881 $v->{'margin-left'} = [$l, $l_i];
2882 }
2883 return $v;
2884 }
2885 },
2886 initial => ['DIMENSION', 0, 'px'],
2887 #inherited => 0,
2888 compute => $compute_length,
2889 };
2890 $Attr->{margin_top} = $Prop->{'margin-top'};
2891 $Key->{margin_top} = $Prop->{'margin-top'};
2892
2893 $Prop->{'margin-bottom'} = {
2894 css => 'margin-bottom',
2895 dom => 'margin_bottom',
2896 key => 'margin_bottom',
2897 parse => $Prop->{'margin-top'}->{parse},
2898 allow_negative => 1,
2899 keyword => {auto => 1},
2900 serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
2901 initial => ['DIMENSION', 0, 'px'],
2902 #inherited => 0,
2903 compute => $compute_length,
2904 };
2905 $Attr->{margin_bottom} = $Prop->{'margin-bottom'};
2906 $Key->{margin_bottom} = $Prop->{'margin-bottom'};
2907
2908 $Prop->{'margin-right'} = {
2909 css => 'margin-right',
2910 dom => 'margin_right',
2911 key => 'margin_right',
2912 parse => $Prop->{'margin-top'}->{parse},
2913 allow_negative => 1,
2914 keyword => {auto => 1},
2915 serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
2916 initial => ['DIMENSION', 0, 'px'],
2917 #inherited => 0,
2918 compute => $compute_length,
2919 };
2920 $Attr->{margin_right} = $Prop->{'margin-right'};
2921 $Key->{margin_right} = $Prop->{'margin-right'};
2922
2923 $Prop->{'margin-left'} = {
2924 css => 'margin-left',
2925 dom => 'margin_left',
2926 key => 'margin_left',
2927 parse => $Prop->{'margin-top'}->{parse},
2928 allow_negative => 1,
2929 keyword => {auto => 1},
2930 serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
2931 initial => ['DIMENSION', 0, 'px'],
2932 #inherited => 0,
2933 compute => $compute_length,
2934 };
2935 $Attr->{margin_left} = $Prop->{'margin-left'};
2936 $Key->{margin_left} = $Prop->{'margin-left'};
2937
2938 $Prop->{top} = {
2939 css => 'top',
2940 dom => 'top',
2941 key => 'top',
2942 parse => $Prop->{'margin-top'}->{parse},
2943 allow_negative => 1,
2944 keyword => {auto => 1},
2945 initial => ['KEYWORD', 'auto'],
2946 #inherited => 0,
2947 compute_multiple => sub {
2948 my ($self, $element, $eid, $prop_name) = @_;
2949
2950 my $pos_value = $self->get_computed_value ($element, 'position');
2951 if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
2952 if ($pos_value->[1] eq 'static') {
2953 $self->{computed_value}->{$eid}->{top} = ['KEYWORD', 'auto'];
2954 $self->{computed_value}->{$eid}->{bottom} = ['KEYWORD', 'auto'];
2955 return;
2956 } elsif ($pos_value->[1] eq 'relative') {
2957 my $top_specified = $self->get_specified_value_no_inherit
2958 ($element, 'top');
2959 if (defined $top_specified and
2960 ($top_specified->[0] eq 'DIMENSION' or
2961 $top_specified->[0] eq 'PERCENTAGE')) {
2962 my $tv = $self->{computed_value}->{$eid}->{top}
2963 = $compute_length->($self, $element, 'top', $top_specified);
2964 $self->{computed_value}->{$eid}->{bottom}
2965 = [$tv->[0], -$tv->[1], $tv->[2]];
2966 } else { # top: auto
2967 my $bottom_specified = $self->get_specified_value_no_inherit
2968 ($element, 'bottom');
2969 if (defined $bottom_specified and
2970 ($bottom_specified->[0] eq 'DIMENSION' or
2971 $bottom_specified->[0] eq 'PERCENTAGE')) {
2972 my $tv = $self->{computed_value}->{$eid}->{bottom}
2973 = $compute_length->($self, $element, 'bottom',
2974 $bottom_specified);
2975 $self->{computed_value}->{$eid}->{top}
2976 = [$tv->[0], -$tv->[1], $tv->[2]];
2977 } else { # bottom: auto
2978 $self->{computed_value}->{$eid}->{top} = ['DIMENSION', 0, 'px'];
2979 $self->{computed_value}->{$eid}->{bottom} = ['DIMENSION', 0, 'px'];
2980 }
2981 }
2982 return;
2983 }
2984 }
2985
2986 my $top_specified = $self->get_specified_value_no_inherit
2987 ($element, 'top');
2988 $self->{computed_value}->{$eid}->{top}
2989 = $compute_length->($self, $element, 'top', $top_specified);
2990 my $bottom_specified = $self->get_specified_value_no_inherit
2991 ($element, 'bottom');
2992 $self->{computed_value}->{$eid}->{bottom}
2993 = $compute_length->($self, $element, 'bottom', $bottom_specified);
2994 },
2995 };
2996 $Attr->{top} = $Prop->{top};
2997 $Key->{top} = $Prop->{top};
2998
2999 $Prop->{bottom} = {
3000 css => 'bottom',
3001 dom => 'bottom',
3002 key => 'bottom',
3003 parse => $Prop->{'margin-top'}->{parse},
3004 allow_negative => 1,
3005 keyword => {auto => 1},
3006 initial => ['KEYWORD', 'auto'],
3007 #inherited => 0,
3008 compute_multiple => $Prop->{top}->{compute_multiple},
3009 };
3010 $Attr->{bottom} = $Prop->{bottom};
3011 $Key->{bottom} = $Prop->{bottom};
3012
3013 $Prop->{left} = {
3014 css => 'left',
3015 dom => 'left',
3016 key => 'left',
3017 parse => $Prop->{'margin-top'}->{parse},
3018 allow_negative => 1,
3019 keyword => {auto => 1},
3020 initial => ['KEYWORD', 'auto'],
3021 #inherited => 0,
3022 compute_multiple => sub {
3023 my ($self, $element, $eid, $prop_name) = @_;
3024
3025 my $pos_value = $self->get_computed_value ($element, 'position');
3026 if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
3027 if ($pos_value->[1] eq 'static') {
3028 $self->{computed_value}->{$eid}->{left} = ['KEYWORD', 'auto'];
3029 $self->{computed_value}->{$eid}->{right} = ['KEYWORD', 'auto'];
3030 return;
3031 } elsif ($pos_value->[1] eq 'relative') {
3032 my $left_specified = $self->get_specified_value_no_inherit
3033 ($element, 'left');
3034 if (defined $left_specified and
3035 ($left_specified->[0] eq 'DIMENSION' or
3036 $left_specified->[0] eq 'PERCENTAGE')) {
3037 my $right_specified = $self->get_specified_value_no_inherit
3038 ($element, 'right');
3039 if (defined $right_specified and
3040 ($right_specified->[0] eq 'DIMENSION' or
3041 $right_specified->[0] eq 'PERCENTAGE')) {
3042 my $direction = $self->get_computed_value ($element, 'direction');
3043 if (defined $direction and $direction->[0] eq 'KEYWORD' and
3044 $direction->[0] eq 'ltr') {
3045 my $tv = $self->{computed_value}->{$eid}->{left}
3046 = $compute_length->($self, $element, 'left',
3047 $left_specified);
3048 $self->{computed_value}->{$eid}->{right}
3049 = [$tv->[0], -$tv->[1], $tv->[2]];
3050 } else {
3051 my $tv = $self->{computed_value}->{$eid}->{right}
3052 = $compute_length->($self, $element, 'right',
3053 $right_specified);
3054 $self->{computed_value}->{$eid}->{left}
3055 = [$tv->[0], -$tv->[1], $tv->[2]];
3056 }
3057 } else {
3058 my $tv = $self->{computed_value}->{$eid}->{left}
3059 = $compute_length->($self, $element, 'left', $left_specified);
3060 $self->{computed_value}->{$eid}->{right}
3061 = [$tv->[0], -$tv->[1], $tv->[2]];
3062 }
3063 } else { # left: auto
3064 my $right_specified = $self->get_specified_value_no_inherit
3065 ($element, 'right');
3066 if (defined $right_specified and
3067 ($right_specified->[0] eq 'DIMENSION' or
3068 $right_specified->[0] eq 'PERCENTAGE')) {
3069 my $tv = $self->{computed_value}->{$eid}->{right}
3070 = $compute_length->($self, $element, 'right',
3071 $right_specified);
3072 $self->{computed_value}->{$eid}->{left}
3073 = [$tv->[0], -$tv->[1], $tv->[2]];
3074 } else { # right: auto
3075 $self->{computed_value}->{$eid}->{left} = ['DIMENSION', 0, 'px'];
3076 $self->{computed_value}->{$eid}->{right} = ['DIMENSION', 0, 'px'];
3077 }
3078 }
3079 return;
3080 }
3081 }
3082
3083 my $left_specified = $self->get_specified_value_no_inherit
3084 ($element, 'left');
3085 $self->{computed_value}->{$eid}->{left}
3086 = $compute_length->($self, $element, 'left', $left_specified);
3087 my $right_specified = $self->get_specified_value_no_inherit
3088 ($element, 'right');
3089 $self->{computed_value}->{$eid}->{right}
3090 = $compute_length->($self, $element, 'right', $right_specified);
3091 },
3092 };
3093 $Attr->{left} = $Prop->{left};
3094 $Key->{left} = $Prop->{left};
3095
3096 $Prop->{right} = {
3097 css => 'right',
3098 dom => 'right',
3099 key => 'right',
3100 parse => $Prop->{'margin-top'}->{parse},
3101 allow_negative => 1,
3102 keyword => {auto => 1},
3103 initial => ['KEYWORD', 'auto'],
3104 #inherited => 0,
3105 compute_multiple => $Prop->{left}->{compute_multiple},
3106 };
3107 $Attr->{right} = $Prop->{right};
3108 $Key->{right} = $Prop->{right};
3109
3110 $Prop->{width} = {
3111 css => 'width',
3112 dom => 'width',
3113 key => 'width',
3114 parse => $Prop->{'margin-top'}->{parse},
3115 #allow_negative => 0,
3116 keyword => {
3117 auto => 1,
3118
3119 ## Firefox 3
3120 '-moz-max-content' => 2, '-moz-min-content' => 2,
3121 '-moz-available' => 2, '-moz-fit-content' => 2,
3122 ## NOTE: By "2", it represents that the parser must be configured
3123 ## to allow these values.
3124 },
3125 initial => ['KEYWORD', 'auto'],
3126 #inherited => 0,
3127 compute => $compute_length,
3128 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/width> for
3129 ## browser compatibility issues.
3130 };
3131 $Attr->{width} = $Prop->{width};
3132 $Key->{width} = $Prop->{width};
3133
3134 $Prop->{'min-width'} = {
3135 css => 'min-width',
3136 dom => 'min_width',
3137 key => 'min_width',
3138 parse => $Prop->{'margin-top'}->{parse},
3139 #allow_negative => 0,
3140 keyword => {
3141 ## Firefox 3
3142 '-moz-max-content' => 2, '-moz-min-content' => 2,
3143 '-moz-available' => 2, '-moz-fit-content' => 2,
3144 },
3145 initial => ['DIMENSION', 0, 'px'],
3146 #inherited => 0,
3147 compute => $compute_length,
3148 };
3149 $Attr->{min_width} = $Prop->{'min-width'};
3150 $Key->{min_width} = $Prop->{'min-width'};
3151
3152 $Prop->{'max-width'} = {
3153 css => 'max-width',
3154 dom => 'max_width',
3155 key => 'max_width',
3156 parse => $Prop->{'margin-top'}->{parse},
3157 #allow_negative => 0,
3158 keyword => {
3159 none => 1,
3160
3161 ## Firefox 3
3162 '-moz-max-content' => 2, '-moz-min-content' => 2,
3163 '-moz-available' => 2, '-moz-fit-content' => 2,
3164 },
3165 initial => ['KEYWORD', 'none'],
3166 #inherited => 0,
3167 compute => $compute_length,
3168 };
3169 $Attr->{max_width} = $Prop->{'max-width'};
3170 $Key->{max_width} = $Prop->{'max-width'};
3171
3172 $Prop->{height} = {
3173 css => 'height',
3174 dom => 'height',
3175 key => 'height',
3176 parse => $Prop->{'margin-top'}->{parse},
3177 #allow_negative => 0,
3178 keyword => {auto => 1},
3179 initial => ['KEYWORD', 'auto'],
3180 #inherited => 0,
3181 compute => $compute_length,
3182 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/height> for
3183 ## browser compatibility issues.
3184 };
3185 $Attr->{height} = $Prop->{height};
3186 $Key->{height} = $Prop->{height};
3187
3188 $Prop->{'min-height'} = {
3189 css => 'min-height',
3190 dom => 'min_height',
3191 key => 'min_height',
3192 parse => $Prop->{'margin-top'}->{parse},
3193 #allow_negative => 0,
3194 #keyword => {},
3195 initial => ['DIMENSION', 0, 'px'],
3196 #inherited => 0,
3197 compute => $compute_length,
3198 };
3199 $Attr->{min_height} = $Prop->{'min-height'};
3200 $Key->{min_height} = $Prop->{'min-height'};
3201
3202 $Prop->{'max-height'} = {
3203 css => 'max-height',
3204 dom => 'max_height',
3205 key => 'max_height',
3206 parse => $Prop->{'margin-top'}->{parse},
3207 #allow_negative => 0,
3208 keyword => {none => 1},
3209 initial => ['KEYWORD', 'none'],
3210 #inherited => 0,
3211 compute => $compute_length,
3212 };
3213 $Attr->{max_height} = $Prop->{'max-height'};
3214 $Key->{max_height} = $Prop->{'max-height'};
3215
3216 $Prop->{'line-height'} = {
3217 css => 'line-height',
3218 dom => 'line_height',
3219 key => 'line_height',
3220 parse => sub {
3221 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3222
3223 ## NOTE: Similar to 'margin-top', but different handling
3224 ## for unitless numbers.
3225
3226 my $has_sign;
3227 my $sign = 1;
3228 if ($t->{type} == MINUS_TOKEN) {
3229 $t = $tt->get_next_token;
3230 $sign = -1;
3231 $has_sign = 1;
3232 } elsif ($t->{type} == PLUS_TOKEN) {
3233 $t = $tt->get_next_token;
3234 $has_sign = 1;
3235 }
3236
3237 if ($t->{type} == DIMENSION_TOKEN) {
3238 my $value = $t->{number} * $sign;
3239 my $unit = lc $t->{value}; ## TODO: case
3240 if ($length_unit->{$unit} and $value >= 0) {
3241 $t = $tt->get_next_token;
3242 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
3243 }
3244 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3245 my $value = $t->{number} * $sign;
3246 if ($value >= 0) {
3247 $t = $tt->get_next_token;
3248 return ($t, {$prop_name => ['PERCENTAGE', $value]});
3249 }
3250 } elsif ($t->{type} == NUMBER_TOKEN) {
3251 my $value = $t->{number} * $sign;
3252 if ($value >= 0) {
3253 $t = $tt->get_next_token;
3254 return ($t, {$prop_name => ['NUMBER', $value]});
3255 }
3256 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
3257 my $value = lc $t->{value}; ## TODO: case
3258 if ($value eq 'normal') {
3259 $t = $tt->get_next_token;
3260 return ($t, {$prop_name => ['KEYWORD', $value]});
3261 } elsif ($value eq 'inherit') {
3262 $t = $tt->get_next_token;
3263 return ($t, {$prop_name => ['INHERIT']});
3264 }
3265 }
3266
3267 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3268 level => $self->{level}->{must},
3269 uri => \$self->{href},
3270 token => $t);
3271 return ($t, undef);
3272 },
3273 initial => ['KEYWORD', 'normal'],
3274 inherited => 1,
3275 compute => $compute_length,
3276 };
3277 $Attr->{line_height} = $Prop->{'line-height'};
3278 $Key->{line_height} = $Prop->{'line-height'};
3279
3280 $Prop->{'vertical-align'} = {
3281 css => 'vertical-align',
3282 dom => 'vertical_align',
3283 key => 'vertical_align',
3284 parse => $Prop->{'margin-top'}->{parse},
3285 allow_negative => 1,
3286 keyword => {
3287 baseline => 1, sub => 1, super => 1, top => 1, 'text-top' => 1,
3288 middle => 1, bottom => 1, 'text-bottom' => 1,
3289 },
3290 ## NOTE: Currently, we don't support option to select subset of keywords
3291 ## supported by application (i.e.
3292 ## $parser->{prop_value}->{'line-height'->{$keyword}). Should we support
3293 ## it?
3294 initial => ['KEYWORD', 'baseline'],
3295 #inherited => 0,
3296 compute => $compute_length,
3297 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/vertical-align> for
3298 ## browser compatibility issues.
3299 };
3300 $Attr->{vertical_align} = $Prop->{'vertical-align'};
3301 $Key->{vertical_align} = $Prop->{'vertical-align'};
3302
3303 $Prop->{'text-indent'} = {
3304 css => 'text-indent',
3305 dom => 'text_indent',
3306 key => 'text_indent',
3307 parse => $Prop->{'margin-top'}->{parse},
3308 allow_negative => 1,
3309 keyword => {},
3310 initial => ['DIMENSION', 0, 'px'],
3311 inherited => 1,
3312 compute => $compute_length,
3313 };
3314 $Attr->{text_indent} = $Prop->{'text-indent'};
3315 $Key->{text_indent} = $Prop->{'text-indent'};
3316
3317 $Prop->{'background-position-x'} = {
3318 css => 'background-position-x',
3319 dom => 'background_position_x',
3320 key => 'background_position_x',
3321 parse => $Prop->{'margin-top'}->{parse},
3322 allow_negative => 1,
3323 keyword => {left => 1, center => 1, right => 1},
3324 initial => ['PERCENTAGE', 0],
3325 #inherited => 0,
3326 compute => sub {
3327 my ($self, $element, $prop_name, $specified_value) = @_;
3328
3329 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
3330 my $v = {
3331 left => 0, center => 50, right => 100, top => 0, bottom => 100,
3332 }->{$specified_value->[1]};
3333 if (defined $v) {
3334 return ['PERCENTAGE', $v];
3335 } else {
3336 return $specified_value;
3337 }
3338 } else {
3339 return $compute_length->(@_);
3340 }
3341 },
3342 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
3343 };
3344 $Attr->{background_position_x} = $Prop->{'background-position-x'};
3345 $Key->{background_position_x} = $Prop->{'background-position-x'};
3346
3347 $Prop->{'background-position-y'} = {
3348 css => 'background-position-y',
3349 dom => 'background_position_y',
3350 key => 'background_position_y',
3351 parse => $Prop->{'margin-top'}->{parse},
3352 allow_negative => 1,
3353 keyword => {top => 1, center => 1, bottom => 1},
3354 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
3355 initial => ['PERCENTAGE', 0],
3356 #inherited => 0,
3357 compute => $Prop->{'background-position-x'}->{compute},
3358 };
3359 $Attr->{background_position_y} = $Prop->{'background-position-y'};
3360 $Key->{background_position_y} = $Prop->{'background-position-y'};
3361
3362 $Prop->{'padding-top'} = {
3363 css => 'padding-top',
3364 dom => 'padding_top',
3365 key => 'padding_top',
3366 parse => $Prop->{'margin-top'}->{parse},
3367 #allow_negative => 0,
3368 #keyword => {},
3369 serialize_multiple => sub {
3370 my $self = shift;
3371
3372 ## NOTE: Same as |serialize_multiple| of 'margin-top'.
3373
3374 my $use_shorthand = 1;
3375 my $t = $self->padding_top;
3376 undef $use_shorthand unless length $t;
3377 my $t_i = $self->get_property_priority ('padding-top');
3378 my $r = $self->padding_right;
3379 undef $use_shorthand
3380 if not length $r or
3381 ($r eq 'inherit' and $t ne 'inherit') or
3382 ($t eq 'inherit' and $r ne 'inherit');
3383 my $r_i = $self->get_property_priority ('padding-right');
3384 undef $use_shorthand unless $r_i eq $t_i;
3385 my $b = $self->padding_bottom;
3386 undef $use_shorthand
3387 if not length $b or
3388 ($b eq 'inherit' and $t ne 'inherit') or
3389 ($t eq 'inherit' and $b ne 'inherit');
3390 my $b_i = $self->get_property_priority ('padding-bottom');
3391 undef $use_shorthand unless $b_i eq $t_i;
3392 my $l = $self->padding_left;
3393 undef $use_shorthand
3394 if not length $l or
3395 ($l eq 'inherit' and $t ne 'inherit') or
3396 ($t eq 'inherit' and $l ne 'inherit');
3397 my $l_i = $self->get_property_priority ('padding-left');
3398 undef $use_shorthand unless $l_i eq $t_i;
3399
3400 if ($use_shorthand) {
3401 $b .= ' ' . $l if $r ne $l;
3402 $r .= ' ' . $b if $t ne $b;
3403 $t .= ' ' . $r if $t ne $r;
3404 return {padding => [$t, $t_i]};
3405 } else {
3406 my $v = {};
3407 if (length $t) {
3408 $v->{'padding-top'} = [$t, $t_i];
3409 }
3410 if (length $r) {
3411 $v->{'padding-right'} = [$r, $r_i];
3412 }
3413 if (length $b) {
3414 $v->{'padding-bottom'} = [$b, $b_i];
3415 }
3416 if (length $l) {
3417 $v->{'padding-left'} = [$l, $l_i];
3418 }
3419 return $v;
3420 }
3421 },
3422 initial => ['DIMENSION', 0, 'px'],
3423 #inherited => 0,
3424 compute => $compute_length,
3425 };
3426 $Attr->{padding_top} = $Prop->{'padding-top'};
3427 $Key->{padding_top} = $Prop->{'padding-top'};
3428
3429 $Prop->{'padding-bottom'} = {
3430 css => 'padding-bottom',
3431 dom => 'padding_bottom',
3432 key => 'padding_bottom',
3433 parse => $Prop->{'padding-top'}->{parse},
3434 #allow_negative => 0,
3435 #keyword => {},
3436 serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
3437 initial => ['DIMENSION', 0, 'px'],
3438 #inherited => 0,
3439 compute => $compute_length,
3440 };
3441 $Attr->{padding_bottom} = $Prop->{'padding-bottom'};
3442 $Key->{padding_bottom} = $Prop->{'padding-bottom'};
3443
3444 $Prop->{'padding-right'} = {
3445 css => 'padding-right',
3446 dom => 'padding_right',
3447 key => 'padding_right',
3448 parse => $Prop->{'padding-top'}->{parse},
3449 #allow_negative => 0,
3450 #keyword => {},
3451 serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
3452 initial => ['DIMENSION', 0, 'px'],
3453 #inherited => 0,
3454 compute => $compute_length,
3455 };
3456 $Attr->{padding_right} = $Prop->{'padding-right'};
3457 $Key->{padding_right} = $Prop->{'padding-right'};
3458
3459 $Prop->{'padding-left'} = {
3460 css => 'padding-left',
3461 dom => 'padding_left',
3462 key => 'padding_left',
3463 parse => $Prop->{'padding-top'}->{parse},
3464 #allow_negative => 0,
3465 #keyword => {},
3466 serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
3467 initial => ['DIMENSION', 0, 'px'],
3468 #inherited => 0,
3469 compute => $compute_length,
3470 };
3471 $Attr->{padding_left} = $Prop->{'padding-left'};
3472 $Key->{padding_left} = $Prop->{'padding-left'};
3473
3474 $Prop->{'border-top-width'} = {
3475 css => 'border-top-width',
3476 dom => 'border_top_width',
3477 key => 'border_top_width',
3478 parse => $Prop->{'margin-top'}->{parse},
3479 #allow_negative => 0,
3480 keyword => {thin => 1, medium => 1, thick => 1},
3481 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3482 initial => ['KEYWORD', 'medium'],
3483 #inherited => 0,
3484 compute => sub {
3485 my ($self, $element, $prop_name, $specified_value) = @_;
3486
3487 ## NOTE: Used for 'border-top-width', 'border-right-width',
3488 ## 'border-bottom-width', 'border-right-width', and
3489 ## 'outline-width'.
3490
3491 my $style_prop = $prop_name;
3492 $style_prop =~ s/width/style/;
3493 my $style = $self->get_computed_value ($element, $style_prop);
3494 if (defined $style and $style->[0] eq 'KEYWORD' and
3495 ($style->[1] eq 'none' or $style->[1] eq 'hidden')) {
3496 return ['DIMENSION', 0, 'px'];
3497 }
3498
3499 my $value = $compute_length->(@_);
3500 if (defined $value and $value->[0] eq 'KEYWORD') {
3501 if ($value->[1] eq 'thin') {
3502 return ['DIMENSION', 1, 'px']; ## Firefox/Opera
3503 } elsif ($value->[1] eq 'medium') {
3504 return ['DIMENSION', 3, 'px']; ## Firefox/Opera
3505 } elsif ($value->[1] eq 'thick') {
3506 return ['DIMENSION', 5, 'px']; ## Firefox
3507 }
3508 }
3509 return $value;
3510 },
3511 ## NOTE: CSS3 will allow <percentage> as an option in <border-width>.
3512 ## Opera 9 has already implemented it.
3513 };
3514 $Attr->{border_top_width} = $Prop->{'border-top-width'};
3515 $Key->{border_top_width} = $Prop->{'border-top-width'};
3516
3517 $Prop->{'border-right-width'} = {
3518 css => 'border-right-width',
3519 dom => 'border_right_width',
3520 key => 'border_right_width',
3521 parse => $Prop->{'border-top-width'}->{parse},
3522 #allow_negative => 0,
3523 keyword => {thin => 1, medium => 1, thick => 1},
3524 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3525 initial => ['KEYWORD', 'medium'],
3526 #inherited => 0,
3527 compute => $Prop->{'border-top-width'}->{compute},
3528 };
3529 $Attr->{border_right_width} = $Prop->{'border-right-width'};
3530 $Key->{border_right_width} = $Prop->{'border-right-width'};
3531
3532 $Prop->{'border-bottom-width'} = {
3533 css => 'border-bottom-width',
3534 dom => 'border_bottom_width',
3535 key => 'border_bottom_width',
3536 parse => $Prop->{'border-top-width'}->{parse},
3537 #allow_negative => 0,
3538 keyword => {thin => 1, medium => 1, thick => 1},
3539 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3540 initial => ['KEYWORD', 'medium'],
3541 #inherited => 0,
3542 compute => $Prop->{'border-top-width'}->{compute},
3543 };
3544 $Attr->{border_bottom_width} = $Prop->{'border-bottom-width'};
3545 $Key->{border_bottom_width} = $Prop->{'border-bottom-width'};
3546
3547 $Prop->{'border-left-width'} = {
3548 css => 'border-left-width',
3549 dom => 'border_left_width',
3550 key => 'border_left_width',
3551 parse => $Prop->{'border-top-width'}->{parse},
3552 #allow_negative => 0,
3553 keyword => {thin => 1, medium => 1, thick => 1},
3554 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3555 initial => ['KEYWORD', 'medium'],
3556 #inherited => 0,
3557 compute => $Prop->{'border-top-width'}->{compute},
3558 };
3559 $Attr->{border_left_width} = $Prop->{'border-left-width'};
3560 $Key->{border_left_width} = $Prop->{'border-left-width'};
3561
3562 $Prop->{'outline-width'} = {
3563 css => 'outline-width',
3564 dom => 'outline_width',
3565 key => 'outline_width',
3566 parse => $Prop->{'border-top-width'}->{parse},
3567 #allow_negative => 0,
3568 keyword => {thin => 1, medium => 1, thick => 1},
3569 serialize_multiple => $Prop->{'outline-color'}->{serialize_multiple},
3570 initial => ['KEYWORD', 'medium'],
3571 #inherited => 0,
3572 compute => $Prop->{'border-top-width'}->{compute},
3573 };
3574 $Attr->{outline_width} = $Prop->{'outline-width'};
3575 $Key->{outline_width} = $Prop->{'outline-width'};
3576
3577 $Prop->{'font-weight'} = {
3578 css => 'font-weight',
3579 dom => 'font_weight',
3580 key => 'font_weight',
3581 parse => sub {
3582 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3583
3584 my $has_sign;
3585 if ($t->{type} == PLUS_TOKEN) {
3586 $has_sign = 1;
3587 $t = $tt->get_next_token;
3588 }
3589
3590 if ($t->{type} == NUMBER_TOKEN) {
3591 ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/font-weight> for
3592 ## browser compatibility issue.
3593 my $value = $t->{number};
3594 $t = $tt->get_next_token;
3595 if ($value % 100 == 0 and 100 <= $value and $value <= 900) {
3596 return ($t, {$prop_name => ['WEIGHT', $value, 0]});
3597 }
3598 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
3599 my $value = lc $t->{value}; ## TODO: case
3600 if ({
3601 normal => 1, bold => 1, bolder => 1, lighter => 1,
3602 }->{$value}) {
3603 $t = $tt->get_next_token;
3604 return ($t, {$prop_name => ['KEYWORD', $value]});
3605 } elsif ($value eq 'inherit') {
3606 $t = $tt->get_next_token;
3607 return ($t, {$prop_name => ['INHERIT']});
3608 }
3609 }
3610
3611 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3612 level => $self->{level}->{must},
3613 uri => \$self->{href},
3614 token => $t);
3615 return ($t, undef);
3616 },
3617 initial => ['KEYWORD', 'normal'],
3618 inherited => 1,
3619 compute => sub {
3620 my ($self, $element, $prop_name, $specified_value) = @_;
3621
3622 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
3623 if ($specified_value->[1] eq 'normal') {
3624 return ['WEIGHT', 400, 0];
3625 } elsif ($specified_value->[1] eq 'bold') {
3626 return ['WEIGHT', 700, 0];
3627 } elsif ($specified_value->[1] eq 'bolder') {
3628 my $parent_element = $element->manakai_parent_element;
3629 if (defined $parent_element) {
3630 my $parent_value = $self->get_cascaded_value
3631 ($parent_element, $prop_name); ## NOTE: What Firefox does.
3632 return ['WEIGHT', $parent_value->[1], $parent_value->[2] + 1];
3633 } else {
3634 return ['WEIGHT', 400, 1];
3635 }
3636 } elsif ($specified_value->[1] eq 'lighter') {
3637 my $parent_element = $element->manakai_parent_element;
3638 if (defined $parent_element) {
3639 my $parent_value = $self->get_cascaded_value
3640 ($parent_element, $prop_name); ## NOTE: What Firefox does.
3641 return ['WEIGHT', $parent_value->[1], $parent_value->[2] - 1];
3642 } else {
3643 return ['WEIGHT', 400, 1];
3644 }
3645 }
3646 #} elsif (defined $specified_value and $specified_value->[0] eq 'WEIGHT') {
3647 #
3648 }
3649
3650 return $specified_value;
3651 },
3652 };
3653 $Attr->{font_weight} = $Prop->{'font-weight'};
3654 $Key->{font_weight} = $Prop->{'font-weight'};
3655
3656 my $uri_or_none_parser = sub {
3657 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3658
3659 if ($t->{type} == URI_TOKEN) {
3660 my $value = $t->{value};
3661 $t = $tt->get_next_token;
3662 return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
3663 } elsif ($t->{type} == IDENT_TOKEN) {
3664 my $value = lc $t->{value}; ## TODO: case
3665 $t = $tt->get_next_token;
3666 if ($value eq 'none') {
3667 ## NOTE: |none| is the default value and therefore it must be
3668 ## supported anyway.
3669 return ($t, {$prop_name => ["KEYWORD", 'none']});
3670 } elsif ($value eq 'inherit') {
3671 return ($t, {$prop_name => ['INHERIT']});
3672 }
3673 ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
3674 #} elsif ($t->{type} == URI_INVALID_TOKEN) {
3675 # my $value = $t->{value};
3676 # $t = $tt->get_next_token;
3677 # if ($t->{type} == EOF_TOKEN) {
3678 # $onerror->(type => 'uri not closed',
3679 # level => $self->{level}->{must},
3680 # uri => \$self->{href},
3681 # token => $t);
3682 #
3683 # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
3684 # }
3685 }
3686
3687 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
3688 level => $self->{level}->{must},
3689 uri => \$self->{href},
3690 token => $t);
3691 return ($t, undef);
3692 }; # $uri_or_none_parser
3693
3694 my $compute_uri_or_none = sub {
3695 my ($self, $element, $prop_name, $specified_value) = @_;
3696
3697 if (defined $specified_value and
3698 $specified_value->[0] eq 'URI' and
3699 defined $specified_value->[2]) {
3700 require Message::DOM::DOMImplementation;
3701 return ['URI',
3702 Message::DOM::DOMImplementation->create_uri_reference
3703 ($specified_value->[1])
3704 ->get_absolute_reference (${$specified_value->[2]})
3705 ->get_uri_reference,
3706 $specified_value->[2]];
3707 }
3708
3709 return $specified_value;
3710 }; # $compute_uri_or_none
3711
3712 $Prop->{'list-style-image'} = {
3713 css => 'list-style-image',
3714 dom => 'list_style_image',
3715 key => 'list_style_image',
3716 parse => $uri_or_none_parser,
3717 initial => ['KEYWORD', 'none'],
3718 inherited => 1,
3719 compute => $compute_uri_or_none,
3720 };
3721 $Attr->{list_style_image} = $Prop->{'list-style-image'};
3722 $Key->{list_style_image} = $Prop->{'list-style-image'};
3723
3724 $Prop->{'background-image'} = {
3725 css => 'background-image',
3726 dom => 'background_image',
3727 key => 'background_image',
3728 parse => $uri_or_none_parser,
3729 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
3730 initial => ['KEYWORD', 'none'],
3731 #inherited => 0,
3732 compute => $compute_uri_or_none,
3733 };
3734 $Attr->{background_image} = $Prop->{'background-image'};
3735 $Key->{background_image} = $Prop->{'background-image'};
3736
3737 $Attr->{font_stretch} =
3738 $Key->{font_stretch} =
3739 $Prop->{'font-stretch'} = {
3740 css => 'font-stretch',
3741 dom => 'font_stretch',
3742 key => 'font_stretch',
3743 parse => $one_keyword_parser,
3744 keyword => {
3745 qw/normal 1 wider 1 narrower 1 ultra-condensed 1 extra-condensed 1
3746 condensed 1 semi-condensed 1 semi-expanded 1 expanded 1
3747 extra-expanded 1 ultra-expanded 1/,
3748 },
3749 initial => ["KEYWORD", 'normal'],
3750 inherited => 1,
3751 compute => sub {
3752 my ($self, $element, $prop_name, $specified_value) = @_;
3753
3754 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
3755 if ($specified_value->[1] eq 'wider') {
3756 my $parent = $element->manakai_parent_element;
3757 if ($parent) {
3758 my $computed = $self->get_computed_value ($parent, $prop_name);
3759 if (defined $computed and $computed->[0] eq 'KEYWORD') {
3760 return ['KEYWORD', {
3761 'ultra-condensed' => 'extra-condensed',
3762 'extra-condensed' => 'condensed',
3763 'condensed' => 'semi-condensed',
3764 'semi-condensed' => 'normal',
3765 'normal' => 'semi-expanded',
3766 'semi-expanded' => 'expanded',
3767 'expanded' => 'extra-expanded',
3768 'extra-expanded' => 'ultra-expanded',
3769 'ultra-expanded' => 'ultra-expanded',
3770 }->{$computed->[1]} || $computed->[1]];
3771 } else { ## This is an implementation error.
3772 #
3773 }
3774 } else {
3775 return ['KEYWORD', 'semi-expanded'];
3776 }
3777 } elsif ($specified_value->[1] eq 'narrower') {
3778 my $parent = $element->manakai_parent_element;
3779 if ($parent) {
3780 my $computed = $self->get_computed_value ($parent, $prop_name);
3781 if (defined $computed and $computed->[0] eq 'KEYWORD') {
3782 return ['KEYWORD', {
3783 'ultra-condensed' => 'ultra-condensed',
3784 'extra-condensed' => 'ultra-condensed',
3785 'condensed' => 'extra-condensed',
3786 'semi-condensed' => 'condensed',
3787 'normal' => 'semi-condensed',
3788 'semi-expanded' => 'normal',
3789 'expanded' => 'semi-expanded',
3790 'extra-expanded' => 'expanded',
3791 'ultra-expanded' => 'extra-expanded',
3792 }->{$computed->[1]} || $computed->[1]];
3793 } else { ## This is an implementation error.
3794 #
3795 }
3796 } else {
3797 return ['KEYWORD', 'semi-condensed'];
3798 }
3799 }
3800 }
3801
3802 return $specified_value;
3803 },
3804 };
3805
3806 $Attr->{writing_mode} =
3807 $Key->{writing_mode} =
3808 $Prop->{'writing-mode'} = {
3809 css => 'writing-mode',
3810 dom => 'writing_mode',
3811 key => 'writing_mode',
3812 parse => $one_keyword_parser,
3813 keyword => {
3814 'lr' => 1, 'lr-tb' => 1,
3815 'rl' => 1, 'rl-tb' => 1,
3816 'tb' => 1, 'tb-rl' => 1,
3817 },
3818 initial => ['KEYWORD', 'lr-tb'],
3819 inherited => 1,
3820 compute => sub {
3821 my ($self, $element, $prop_name, $specified_value) = @_;
3822
3823 ## ISSUE: Not defined by any standard.
3824
3825 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
3826 if ($specified_value->[1] eq 'lr') {
3827 return ['KEYWORD', 'lr-tb'];
3828 } elsif ($specified_value->[1] eq 'rl') {
3829 return ['KEYWORD', 'rl-tb'];
3830 } elsif ($specified_value->[1] eq 'tb') {
3831 return ['KEYWORD', 'tb-rl'];
3832 }
3833 }
3834
3835 return $specified_value;
3836 },
3837 };
3838
3839 $Attr->{text_anchor} =
3840 $Key->{text_anchor} =
3841 $Prop->{'text-anchor'} = {
3842 css => 'text-anchor',
3843 dom => 'text_anchor', ## TODO: manakai extension. Documentation.
3844 key => 'text_anchor',
3845 parse => $one_keyword_parser,
3846 keyword => {
3847 start => 1, middle => 1, end => 1,
3848 },
3849 initial => ['KEYWORD', 'start'],
3850 inherited => 1,
3851 compute => $compute_as_specified,
3852 };
3853
3854 $Attr->{dominant_baseline} =
3855 $Key->{dominant_baseline} =
3856 $Prop->{'dominant-baseline'} = {
3857 css => 'dominant-baseline',
3858 dom => 'dominant_baseline', ## TODO: manakai extension. Documentation.
3859 key => 'dominant_baseline',
3860 parse => $one_keyword_parser,
3861 keyword => {
3862 qw/auto 1 use-script 1 no-change 1 reset-size 1 ideographic 1 alphabetic 1
3863 hanging 1 mathematical 1 central 1 middle 1 text-after-edge 1
3864 text-before-edge 1/
3865 },
3866 initial => ['KEYWORD', 'auto'],
3867 inherited => 0,
3868 compute => $compute_as_specified,
3869 };
3870
3871 $Attr->{alignment_baseline} =
3872 $Key->{alignment_baseline} =
3873 $Prop->{'alignment-baseline'} = {
3874 css => 'alignment-baseline',
3875 dom => 'alignment_baseline', ## TODO: manakai extension. Documentation.
3876 key => 'alignment_baseline',
3877 parse => $one_keyword_parser,
3878 keyword => {
3879 qw/auto 1 baseline 1 before-edge 1 text-before-edge 1 middle 1 central 1
3880 after-edge 1 text-after-edge 1 ideographic 1 alphabetic 1 hanging 1
3881 mathematical 1/
3882 },
3883 initial => ['KEYWORD', 'auto'],
3884 inherited => 0,
3885 compute => $compute_as_specified,
3886 };
3887
3888 my $border_style_keyword = {
3889 none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
3890 double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
3891 };
3892
3893 $Prop->{'border-top-style'} = {
3894 css => 'border-top-style',
3895 dom => 'border_top_style',
3896 key => 'border_top_style',
3897 parse => $one_keyword_parser,
3898 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3899 keyword => $border_style_keyword,
3900 initial => ["KEYWORD", "none"],
3901 #inherited => 0,
3902 compute => $compute_as_specified,
3903 };
3904 $Attr->{border_top_style} = $Prop->{'border-top-style'};
3905 $Key->{border_top_style} = $Prop->{'border-top-style'};
3906
3907 $Prop->{'border-right-style'} = {
3908 css => 'border-right-style',
3909 dom => 'border_right_style',
3910 key => 'border_right_style',
3911 parse => $one_keyword_parser,
3912 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3913 keyword => $border_style_keyword,
3914 initial => ["KEYWORD", "none"],
3915 #inherited => 0,
3916 compute => $compute_as_specified,
3917 };
3918 $Attr->{border_right_style} = $Prop->{'border-right-style'};
3919 $Key->{border_right_style} = $Prop->{'border-right-style'};
3920
3921 $Prop->{'border-bottom-style'} = {
3922 css => 'border-bottom-style',
3923 dom => 'border_bottom_style',
3924 key => 'border_bottom_style',
3925 parse => $one_keyword_parser,
3926 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3927 keyword => $border_style_keyword,
3928 initial => ["KEYWORD", "none"],
3929 #inherited => 0,
3930 compute => $compute_as_specified,
3931 };
3932 $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
3933 $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
3934
3935 $Prop->{'border-left-style'} = {
3936 css => 'border-left-style',
3937 dom => 'border_left_style',
3938 key => 'border_left_style',
3939 parse => $one_keyword_parser,
3940 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
3941 keyword => $border_style_keyword,
3942 initial => ["KEYWORD", "none"],
3943 #inherited => 0,
3944 compute => $compute_as_specified,
3945 };
3946 $Attr->{border_left_style} = $Prop->{'border-left-style'};
3947 $Key->{border_left_style} = $Prop->{'border-left-style'};
3948
3949 $Prop->{'outline-style'} = {
3950 css => 'outline-style',
3951 dom => 'outline_style',
3952 key => 'outline_style',
3953 parse => $one_keyword_parser,
3954 serialize_multiple => $Prop->{'outline-color'}->{serialize_multiple},
3955 keyword => {%$border_style_keyword},
3956 initial => ['KEYWORD', 'none'],
3957 #inherited => 0,
3958 compute => $compute_as_specified,
3959 };
3960 $Attr->{outline_style} = $Prop->{'outline-style'};
3961 $Key->{outline_style} = $Prop->{'outline-style'};
3962 delete $Prop->{'outline-style'}->{keyword}->{hidden};
3963
3964 my $generic_font_keywords = {
3965 serif => 1, 'sans-serif' => 1, cursive => 1,
3966 fantasy => 1, monospace => 1, '-manakai-default' => 1,
3967 '-manakai-caption' => 1, '-manakai-icon' => 1,
3968 '-manakai-menu' => 1, '-manakai-message-box' => 1,
3969 '-manakai-small-caption' => 1, '-manakai-status-bar' => 1,
3970 };
3971 ## NOTE: "All five generic font families are defined to exist in all CSS
3972 ## implementations (they need not necessarily map to five distinct actual
3973 ## fonts)." [CSS 2.1].
3974 ## NOTE: "If no font with the indicated characteristics exists on a given
3975 ## platform, the user agent should either intelligently substitute (e.g., a
3976 ## smaller version of the 'caption' font might be used for the 'small-caption'
3977 ## font), or substitute a user agent default font." [CSS 2.1].
3978
3979 $Prop->{'font-family'} = {
3980 css => 'font-family',
3981 dom => 'font_family',
3982 key => 'font_family',
3983 parse => sub {
3984 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3985
3986 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/font-family> for
3987 ## how chaotic browsers are!
3988
3989 ## NOTE: Opera 9 allows NUMBER and DIMENSION as part of
3990 ## <font-family>, while Firefox 2 does not.
3991
3992 my @prop_value;
3993
3994 my $font_name = '';
3995 my $may_be_generic = 1;
3996 my $may_be_inherit = ($prop_name ne 'font');
3997 my $has_s = 0;
3998 F: {
3999 if ($t->{type} == IDENT_TOKEN) {
4000 undef $may_be_inherit if $has_s or length $font_name;
4001 undef $may_be_generic if $has_s or length $font_name;
4002 $font_name .= ' ' if $has_s;
4003 $font_name .= $t->{value};
4004 undef $has_s;
4005 $t = $tt->get_next_token;
4006 } elsif ($t->{type} == STRING_TOKEN) {
4007 $font_name .= ' ' if $has_s;
4008 $font_name .= $t->{value};
4009 undef $may_be_inherit;
4010 undef $may_be_generic;
4011 undef $has_s;
4012 $t = $tt->get_next_token;
4013 } elsif ($t->{type} == COMMA_TOKEN) { ## TODO: case
4014 if ($may_be_generic and $generic_font_keywords->{lc $font_name}) {
4015 push @prop_value, ['KEYWORD', $font_name];
4016 } elsif (not $may_be_generic or length $font_name) {
4017 push @prop_value, ["STRING", $font_name];
4018 }
4019 undef $may_be_inherit;
4020 $may_be_generic = 1;
4021 undef $has_s;
4022 $font_name = '';
4023 $t = $tt->get_next_token;
4024 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4025 } elsif ($t->{type} == S_TOKEN) {
4026 $has_s = 1;
4027 $t = $tt->get_next_token;
4028 } else {
4029 if ($may_be_generic and $generic_font_keywords->{lc $font_name}) {
4030 push @prop_value, ['KEYWORD', $font_name]; ## TODO: case
4031 } elsif (not $may_be_generic or length $font_name) {
4032 push @prop_value, ['STRING', $font_name];
4033 } else {
4034 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4035 level => $self->{level}->{must},
4036 uri => \$self->{href},
4037 token => $t);
4038 return ($t, undef);
4039 }
4040 last F;
4041 }
4042 redo F;
4043 } # F
4044
4045 if ($may_be_inherit and
4046 @prop_value == 1 and
4047 $prop_value[0]->[0] eq 'STRING' and
4048 lc $prop_value[0]->[1] eq 'inherit') { ## TODO: case
4049 return ($t, {$prop_name => ['INHERIT']});
4050 } else {
4051 unshift @prop_value, 'FONT';
4052 return ($t, {$prop_name => \@prop_value});
4053 }
4054 },
4055 initial => ['FONT', ['KEYWORD', '-manakai-default']],
4056 inherited => 1,
4057 compute => $compute_as_specified,
4058 };
4059 $Attr->{font_family} = $Prop->{'font-family'};
4060 $Key->{font_family} = $Prop->{'font-family'};
4061
4062 $Prop->{cursor} = {
4063 css => 'cursor',
4064 dom => 'cursor',
4065 key => 'cursor',
4066 parse => sub {
4067 my ($self, $prop_name, $tt, $t, $onerror) = @_;
4068
4069 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/cursor> for browser
4070 ## compatibility issues.
4071
4072 my @prop_value = ('CURSOR');
4073
4074 F: {
4075 if ($t->{type} == IDENT_TOKEN) {
4076 my $v = lc $t->{value}; ## TODO: case
4077 if ($Prop->{$prop_name}->{keyword}->{$v}) {
4078 push @prop_value, ['KEYWORD', $v];
4079 $t = $tt->get_next_token;
4080 last F;
4081 } elsif ($v eq 'hand' and
4082 $Prop->{$prop_name}->{keyword}->{pointer}) {
4083 ## TODO: add test
4084 $onerror->(type => 'CSS cursor hand',
4085 level => $self->{level}->{must}, # not valid <'cursor'>
4086 uri => \$self->{href},
4087 token => $t);
4088 push @prop_value, ['KEYWORD', 'pointer'];
4089 $t = $tt->get_next_token;
4090 last F;
4091 } elsif ($v eq 'inherit' and @prop_value == 1) {
4092 $t = $tt->get_next_token;
4093 return ($t, {$prop_name => ['INHERIT']});
4094 } else {
4095 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4096 level => $self->{level}->{must},
4097 uri => \$self->{href},
4098 token => $t);
4099 return ($t, undef);
4100 }
4101 } elsif ($t->{type} == URI_TOKEN) {
4102 push @prop_value, ['URI', $t->{value}, \($self->{base_uri})];
4103 $t = $tt->get_next_token;
4104 } else {
4105 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4106 level => $self->{level}->{must},
4107 uri => \$self->{href},
4108 token => $t);
4109 return ($t, undef);
4110 }
4111
4112 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4113 if ($t->{type} == COMMA_TOKEN) {
4114 $t = $tt->get_next_token;
4115 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4116 redo F;
4117 }
4118 } # F
4119
4120 return ($t, {$prop_name => \@prop_value});
4121 },
4122 keyword => {
4123 auto => 1, crosshair => 1, default => 1, pointer => 1, move => 1,
4124 'e-resize' => 1, 'ne-resize' => 1, 'nw-resize' => 1, 'n-resize' => 1,
4125 'n-resize' => 1, 'se-resize' => 1, 'sw-resize' => 1, 's-resize' => 1,
4126 'w-resize' => 1, text => 1, wait => 1, help => 1, progress => 1,
4127 },
4128 initial => ['CURSOR', ['KEYWORD', 'auto']],
4129 inherited => 1,
4130 compute => sub {
4131 my ($self, $element, $prop_name, $specified_value) = @_;
4132
4133 if (defined $specified_value and $specified_value->[0] eq 'CURSOR') {
4134 my @new_value = ('CURSOR');
4135 for my $value (@$specified_value[1..$#$specified_value]) {
4136 if ($value->[0] eq 'URI') {
4137 if (defined $value->[2]) {
4138 require Message::DOM::DOMImplementation;
4139 push @new_value, ['URI',
4140 Message::DOM::DOMImplementation
4141 ->create_uri_reference ($value->[1])
4142 ->get_absolute_reference (${$value->[2]})
4143 ->get_uri_reference,
4144 $value->[2]];
4145 } else {
4146 push @new_value, $value;
4147 }
4148 } else {
4149 push @new_value, $value;
4150 }
4151 }
4152 return \@new_value;
4153 }
4154
4155 return $specified_value;
4156 },
4157 };
4158 $Attr->{cursor} = $Prop->{cursor};
4159 $Key->{cursor} = $Prop->{cursor};
4160
4161 $Prop->{'border-style'} = {
4162 css => 'border-style',
4163 dom => 'border_style',
4164 parse => sub {
4165 my ($self, $prop_name, $tt, $t, $onerror) = @_;
4166
4167 my %prop_value;
4168 if ($t->{type} == IDENT_TOKEN) {
4169 my $prop_value = lc $t->{value}; ## TODO: case folding
4170 $t = $tt->get_next_token;
4171 if ($border_style_keyword->{$prop_value} and
4172 $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
4173 $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
4174 } elsif ($prop_value eq 'inherit') {
4175 $prop_value{'border-top-style'} = ["INHERIT"];
4176 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
4177 $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
4178 $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
4179 return ($t, \%prop_value);
4180 } else {
4181 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4182 level => $self->{level}->{must},
4183 uri => \$self->{href},
4184 token => $t);
4185 return ($t, undef);
4186 }
4187 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
4188 $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
4189 $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
4190 } else {
4191 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4192 level => $self->{level}->{must},
4193 uri => \$self->{href},
4194 token => $t);
4195 return ($t, undef);
4196 }
4197
4198 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4199 if ($t->{type} == IDENT_TOKEN) {
4200 my $prop_value = lc $t->{value}; ## TODO: case folding
4201 $t = $tt->get_next_token;
4202 if ($border_style_keyword->{$prop_value} and
4203 $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
4204 $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
4205 } else {
4206 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4207 level => $self->{level}->{must},
4208 uri => \$self->{href},
4209 token => $t);
4210 return ($t, undef);
4211 }
4212 $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
4213
4214 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4215 if ($t->{type} == IDENT_TOKEN) {
4216 my $prop_value = lc $t->{value}; ## TODO: case folding
4217 $t = $tt->get_next_token;
4218 if ($border_style_keyword->{$prop_value} and
4219 $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
4220 $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
4221 } else {
4222 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4223 level => $self->{level}->{must},
4224 uri => \$self->{href},
4225 token => $t);
4226 return ($t, undef);
4227 }
4228
4229 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4230 if ($t->{type} == IDENT_TOKEN) {
4231 my $prop_value = lc $t->{value}; ## TODO: case folding
4232 $t = $tt->get_next_token;
4233 if ($border_style_keyword->{$prop_value} and
4234 $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
4235 $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
4236 } else {
4237 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4238 level => $self->{level}->{must},
4239 uri => \$self->{href},
4240 token => $t);
4241 return ($t, undef);
4242 }
4243 }
4244 }
4245 }
4246
4247 return ($t, \%prop_value);
4248 },
4249 serialize_shorthand => sub {
4250 my $self = shift;
4251
4252 my @v;
4253 push @v, $self->border_top_style;
4254 my $i = $self->get_property_priority ('border-top-style');
4255 return {} unless length $v[-1];
4256 push @v, $self->border_right_style;
4257 return {} unless length $v[-1];
4258 return {} unless $i eq $self->get_property_priority ('border-right-style');
4259 push @v, $self->border_bottom_style;
4260 return {} unless length $v[-1];
4261 return {} unless $i eq $self->get_property_priority ('border-bottom-style');
4262 push @v, $self->border_left_style;
4263 return {} unless length $v[-1];
4264 return {} unless $i eq $self->get_property_priority ('border-left-style');
4265
4266 my $v = 0;
4267 for (0..3) {
4268 $v++ if $v[$_] eq 'inherit';
4269 }
4270 if ($v == 4) {
4271 return {'border-style' => ['inherit', $i]};
4272 } elsif ($v) {
4273 return {};
4274 }
4275
4276 pop @v if $v[1] eq $v[3];
4277 pop @v if $v[0] eq $v[2];
4278 pop @v if $v[0] eq $v[1];
4279 return {'border-style' => [(join ' ', @v), $i]};
4280 },
4281 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4282 };
4283 $Attr->{border_style} = $Prop->{'border-style'};
4284
4285 $Prop->{'border-color'} = {
4286 css => 'border-color',
4287 dom => 'border_color',
4288 parse => sub {
4289 my ($self, $prop_name, $tt, $t, $onerror) = @_;
4290
4291 my %prop_value;
4292 ($t, my $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
4293 if (not defined $pv) {
4294 return ($t, undef);
4295 }
4296 $prop_value{'border-top-color'} = $pv->{'border-color'};
4297 $prop_value{'border-bottom-color'} = $prop_value{'border-top-color'};
4298 $prop_value{'border-right-color'} = $prop_value{'border-top-color'};
4299 $prop_value{'border-left-color'}= $prop_value{'border-right-color'};
4300 if ($prop_value{'border-top-color'}->[0] eq 'INHERIT') {
4301 return ($t, \%prop_value);
4302 }
4303
4304 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4305 if ({
4306 IDENT_TOKEN, 1,
4307 HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
4308 FUNCTION_TOKEN, 1,
4309 }->{$t->{type}}) {
4310 ($t, $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
4311 if (not defined $pv) {
4312 return ($t, undef);
4313 } elsif ($pv->{'border-color'}->[0] eq 'INHERIT') {
4314 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4315 level => $self->{level}->{must},
4316 uri => \$self->{href},
4317 token => $t);
4318 return ($t, undef);
4319 }
4320 $prop_value{'border-right-color'} = $pv->{'border-color'};
4321 $prop_value{'border-left-color'}= $prop_value{'border-right-color'};
4322
4323 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4324 if ({
4325 IDENT_TOKEN, 1,
4326 HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
4327 FUNCTION_TOKEN, 1,
4328 }->{$t->{type}}) {
4329 ($t, $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
4330 if (not defined $pv) {
4331 return ($t, undef);
4332 } elsif ($pv->{'border-color'}->[0] eq 'INHERIT') {
4333 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4334 level => $self->{level}->{must},
4335 uri => \$self->{href},
4336 token => $t);
4337 return ($t, undef);
4338 }
4339 $prop_value{'border-bottom-color'} = $pv->{'border-color'};
4340
4341 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4342 if ({
4343 IDENT_TOKEN, 1,
4344 HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
4345 FUNCTION_TOKEN, 1,
4346 }->{$t->{type}}) {
4347 ($t, $pv) = $parse_color->($self, 'border-color', $tt, $t, $onerror);
4348 if (not defined $pv) {
4349 return ($t, undef);
4350 } elsif ($pv->{'border-color'}->[0] eq 'INHERIT') {
4351 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4352 level => $self->{level}->{must},
4353 uri => \$self->{href},
4354 token => $t);
4355 return ($t, undef);
4356 }
4357 $prop_value{'border-left-color'} = $pv->{'border-color'};
4358 }
4359 }
4360 }
4361
4362 return ($t, \%prop_value);
4363 },
4364 serialize_shorthand => sub {
4365 my $self = shift;
4366
4367 my @v;
4368 push @v, $self->border_top_color;
4369 my $i = $self->get_property_priority ('border-top-color');
4370 return {} unless length $v[-1];
4371 push @v, $self->border_right_color;
4372 return {} unless length $v[-1];
4373 return {} unless $i eq $self->get_property_priority ('border-right-color');
4374 push @v, $self->border_bottom_color;
4375 return {} unless length $v[-1];
4376 return {} unless $i eq $self->get_property_priority ('border-bottom-color');
4377 push @v, $self->border_left_color;
4378 return {} unless length $v[-1];
4379 return {} unless $i eq $self->get_property_priority ('border-left-color');
4380
4381 my $v = 0;
4382 for (0..3) {
4383 $v++ if $v[$_] eq 'inherit';
4384 }
4385 if ($v == 4) {
4386 return {'border-color' => ['inherit', $i]};
4387 } elsif ($v) {
4388 return {};
4389 }
4390
4391 pop @v if $v[1] eq $v[3];
4392 pop @v if $v[0] eq $v[2];
4393 pop @v if $v[0] eq $v[1];
4394 return {'border-color' => [(join ' ', @v), $i]};
4395 },
4396 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4397 };
4398 $Attr->{border_color} = $Prop->{'border-color'};
4399
4400 $Prop->{'border-top'} = {
4401 css => 'border-top',
4402 dom => 'border_top',
4403 parse => sub {
4404 my ($self, $prop_name, $tt, $t, $onerror) = @_;
4405
4406 ## TODO: Need to be rewritten.
4407
4408 my %prop_value;
4409 my $pv;
4410 ## NOTE: Since $onerror is disabled for three invocations below,
4411 ## some informative warning messages (if they are added someday) will not
4412 ## be reported.
4413 ($t, $pv) = $parse_color->($self, $prop_name.'-color', $tt, $t, sub {});
4414 if (defined $pv) {
4415 if ($pv->{$prop_name.'-color'}->[0] eq 'INHERIT') {
4416 return ($t, {$prop_name.'-color' => ['INHERIT'],
4417 $prop_name.'-style' => ['INHERIT'],
4418 $prop_name.'-width' => ['INHERIT']});
4419 } else {
4420 $prop_value{$prop_name.'-color'} = $pv->{$prop_name.'-color'};
4421 }
4422 } else {
4423 ($t, $pv) = $Prop->{'border-top-width'}->{parse}
4424 ->($self, $prop_name.'-width', $tt, $t, sub {});
4425 if (defined $pv) {
4426 $prop_value{$prop_name.'-width'} = $pv->{$prop_name.'-width'};
4427 } else {
4428 ($t, $pv) = $Prop->{'border-top-style'}->{parse}
4429 ->($self, $prop_name.'-style', $tt, $t, sub {});
4430 if (defined $pv) {
4431 $prop_value{$prop_name.'-style'} = $pv->{$prop_name.'-style'};
4432 } else {
4433 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4434 level => $self->{level}->{must},
4435 uri => \$self->{href},
4436 token => $t);
4437 return ($t, undef);
4438 }
4439 }
4440 }
4441
4442 for (1..2) {
4443 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4444 if ($t->{type} == IDENT_TOKEN) {
4445 my $prop_value = lc $t->{value}; ## TODO: case
4446 if ($border_style_keyword->{$prop_value} and
4447 $self->{prop_value}->{'border-top-style'}->{$prop_value} and
4448 not defined $prop_value{$prop_name.'-style'}) {
4449 $prop_value{$prop_name.'-style'} = ['KEYWORD', $prop_value];
4450 $t = $tt->get_next_token;
4451 next;
4452 } elsif ({thin => 1, medium => 1, thick => 1}->{$prop_value} and
4453 not defined $prop_value{$prop_name.'-width'}) {
4454 $prop_value{$prop_name.'-width'} = ['KEYWORD', $prop_value];
4455 $t = $tt->get_next_token;
4456 next;
4457 }
4458 }
4459
4460 undef $pv;
4461 ($t, $pv) = $parse_color->($self, $prop_name.'-color', $tt, $t, $onerror)
4462 if not defined $prop_value{$prop_name.'-color'} and
4463 {
4464 IDENT_TOKEN, 1,
4465 HASH_TOKEN, 1, NUMBER_TOKEN, 1, DIMENSION_TOKEN, 1,
4466 FUNCTION_TOKEN, 1,
4467 }->{$t->{type}};
4468 if (defined $pv) {
4469 if ($pv->{$prop_name.'-color'}->[0] eq 'INHERIT') {
4470 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4471 level => $self->{level}->{must},
4472 uri => \$self->{href},
4473 token => $t);
4474 } else {
4475 $prop_value{$prop_name.'-color'} = $pv->{$prop_name.'-color'};
4476 }
4477 } else {
4478 undef $pv;
4479 ($t, $pv) = $Prop->{'border-top-width'}->{parse}
4480 ->($self, $prop_name.'-width',
4481 $tt, $t, $onerror)
4482 if not defined $prop_value{$prop_name.'-width'} and
4483 {
4484 DIMENSION_TOKEN, 1,
4485 NUMBER_TOKEN, 1,
4486 IDENT_TOKEN, 1,
4487 MINUS_TOKEN, 1,
4488 }->{$t->{type}};
4489 if (defined $pv) {
4490 if ($pv->{$prop_name.'-width'}->[0] eq 'INHERIT') {
4491 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4492 level => $self->{level}->{must},
4493 uri => \$self->{href},
4494 token => $t);
4495 } else {
4496 $prop_value{$prop_name.'-width'} = $pv->{$prop_name.'-width'};
4497 }
4498 } else {
4499 last;
4500 }
4501 }
4502 }
4503
4504 $prop_value{$prop_name.'-color'}
4505 ||= $Prop->{$prop_name.'-color'}->{initial};
4506 $prop_value{$prop_name.'-width'}
4507 ||= $Prop->{$prop_name.'-width'}->{initial};
4508 $prop_value{$prop_name.'-style'}
4509 ||= $Prop->{$prop_name.'-style'}->{initial};
4510
4511 return ($t, \%prop_value);
4512 },
4513 serialize_shorthand => sub {
4514 my $self = shift;
4515
4516 my $w = $self->border_top_width;
4517 return {} unless length $w;
4518 my $i = $self->get_property_priority ('border-top-width');
4519 my $s = $self->border_top_style;
4520 return {} unless length $s;
4521 return {} unless $i eq $self->get_property_priority ('border-top-style');
4522 my $c = $self->border_top_color;
4523 return {} unless length $c;
4524 return {} unless $i eq $self->get_property_priority ('border-top-color');
4525
4526 my $v = 0;
4527 $v++ if $w eq 'inherit';
4528 $v++ if $s eq 'inherit';
4529 $v++ if $c eq 'inherit';
4530 if ($v == 3) {
4531 return {'border-top' => ['inherit', $i]};
4532 } elsif ($v) {
4533 return {};
4534 }
4535
4536 return {'border-top' => [$w . ' ' . $s . ' ' . $c, $i]};
4537 },
4538 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4539 };
4540 $Attr->{border_top} = $Prop->{'border-top'};
4541
4542 $Prop->{'border-right'} = {
4543 css => 'border-right',
4544 dom => 'border_right',
4545 parse => $Prop->{'border-top'}->{parse},
4546 serialize_shorthand => sub {
4547 my $self = shift;
4548
4549 my $w = $self->border_right_width;
4550 return {} unless length $w;
4551 my $i = $self->get_property_priority ('border-right-width');
4552 my $s = $self->border_right_style;
4553 return {} unless length $s;
4554 return {} unless $i eq $self->get_property_priority ('border-right-style');
4555 my $c = $self->border_right_color;
4556 return {} unless length $c;
4557 return {} unless $i eq $self->get_property_priority ('border-right-color');
4558
4559 my $v = 0;
4560 $v++ if $w eq 'inherit';
4561 $v++ if $s eq 'inherit';
4562 $v++ if $c eq 'inherit';
4563 if ($v == 3) {
4564 return {'border-right' => ['inherit', $i]};
4565 } elsif ($v) {
4566 return {};
4567 }
4568
4569 return {'border-right' => [$w . ' ' . $s . ' ' . $c, $i]};
4570 },
4571 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4572 };
4573 $Attr->{border_right} = $Prop->{'border-right'};
4574
4575 $Prop->{'border-bottom'} = {
4576 css => 'border-bottom',
4577 dom => 'border_bottom',
4578 parse => $Prop->{'border-top'}->{parse},
4579 serialize_shorthand => sub {
4580 my $self = shift;
4581
4582 my $w = $self->border_bottom_width;
4583 return {} unless length $w;
4584 my $i = $self->get_property_priority ('border-bottom-width');
4585 my $s = $self->border_bottom_style;
4586 return {} unless length $s;
4587 return {} unless $i eq $self->get_property_priority ('border-bottom-style');
4588 my $c = $self->border_bottom_color;
4589 return {} unless length $c;
4590 return {} unless $i eq $self->get_property_priority ('border-bottom-color');
4591
4592 my $v = 0;
4593 $v++ if $w eq 'inherit';
4594 $v++ if $s eq 'inherit';
4595 $v++ if $c eq 'inherit';
4596 if ($v == 3) {
4597 return {'border-bottom' => ['inherit', $i]};
4598 } elsif ($v) {
4599 return {};
4600 }
4601
4602 return {'border-bottom' => [$w . ' ' . $s . ' ' . $c, $i]};
4603 },
4604 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4605 };
4606 $Attr->{border_bottom} = $Prop->{'border-bottom'};
4607
4608 $Prop->{'border-left'} = {
4609 css => 'border-left',
4610 dom => 'border_left',
4611 parse => $Prop->{'border-top'}->{parse},
4612 serialize_shorthand => sub {
4613 my $self = shift;
4614
4615 my $w = $self->border_left_width;
4616 return {} unless length $w;
4617 my $i = $self->get_property_priority ('border-left-width');
4618 my $s = $self->border_left_style;
4619 return {} unless length $s;
4620 return {} unless $i eq $self->get_property_priority ('border-left-style');
4621 my $c = $self->border_left_color;
4622 return {} unless length $c;
4623 return {} unless $i eq $self->get_property_priority ('border-left-color');
4624
4625 my $v = 0;
4626 $v++ if $w eq 'inherit';
4627 $v++ if $s eq 'inherit';
4628 $v++ if $c eq 'inherit';
4629 if ($v == 3) {
4630 return {'border-left' => ['inherit', $i]};
4631 } elsif ($v) {
4632 return {};
4633 }
4634
4635 return {'border-left' => [$w . ' ' . $s . ' ' . $c, $i]};
4636 },
4637 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4638 };
4639 $Attr->{border_left} = $Prop->{'border-left'};
4640
4641 ## TODO: -moz-outline -> outline
4642
4643 $Prop->{outline} = {
4644 css => 'outline',
4645 dom => 'outline',
4646 parse => $Prop->{'border-top'}->{parse},
4647 serialize_multiple => $Prop->{'outline-color'}->{serialize_multiple},
4648 };
4649 $Attr->{outline} = $Prop->{outline};
4650
4651 $Prop->{border} = {
4652 css => 'border',
4653 dom => 'border',
4654 parse => sub {
4655 my ($self, $prop_name, $tt, $t, $onerror) = @_;
4656 my $prop_value;
4657 ($t, $prop_value) = $Prop->{'border-top'}->{parse}
4658 ->($self, 'border-top', $tt, $t, $onerror);
4659 return ($t, undef) unless defined $prop_value;
4660
4661 for (qw/border-right border-bottom border-left/) {
4662 $prop_value->{$_.'-color'} = $prop_value->{'border-top-color'}
4663 if defined $prop_value->{'border-top-color'};
4664 $prop_value->{$_.'-style'} = $prop_value->{'border-top-style'}
4665 if defined $prop_value->{'border-top-style'};
4666 $prop_value->{$_.'-width'} = $prop_value->{'border-top-width'}
4667 if defined $prop_value->{'border-top-width'};
4668 }
4669 return ($t, $prop_value);
4670 },
4671 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
4672 };
4673 $Attr->{border} = $Prop->{border};
4674
4675 $Prop->{margin} = {
4676 css => 'margin',
4677 dom => 'margin',
4678 parse => sub {
4679 my ($self, $prop_name, $tt, $t, $onerror) = @_;
4680
4681 my %prop_value;
4682
4683 my $sign = 1;
4684 my $has_sign;
4685 if ($t->{type} == MINUS_TOKEN) {
4686 $t = $tt->get_next_token;
4687 $has_sign = 1;
4688 $sign = -1;
4689 } elsif ($t->{type} == PLUS_TOKEN) {
4690 $t = $tt->get_next_token;
4691 $has_sign = 1;
4692 }
4693
4694 if ($t->{type} == DIMENSION_TOKEN) {
4695 my $value = $t->{number} * $sign;
4696 my $unit = lc $t->{value}; ## TODO: case
4697 $t = $tt->get_next_token;
4698 if ($length_unit->{$unit}) {
4699 $prop_value{'margin-top'} = ['DIMENSION', $value, $unit];
4700 } else {
4701 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4702 level => $self->{level}->{must},
4703 uri => \$self->{href},
4704 token => $t);
4705 return ($t, undef);
4706 }
4707 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4708 my $value = $t->{number} * $sign;
4709 $t = $tt->get_next_token;
4710 $prop_value{'margin-top'} = ['PERCENTAGE', $value];
4711 } elsif ($t->{type} == NUMBER_TOKEN and
4712 ($self->{unitless_px} or $t->{number} == 0)) {
4713 my $value = $t->{number} * $sign;
4714 $t = $tt->get_next_token;
4715 $prop_value{'margin-top'} = ['DIMENSION', $value, 'px'];
4716 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4717 my $prop_value = lc $t->{value}; ## TODO: case folding
4718 $t = $tt->get_next_token;
4719 if ($prop_value eq 'auto') {
4720 $prop_value{'margin-top'} = ['KEYWORD', $prop_value];
4721 } elsif ($prop_value eq 'inherit') {
4722 $prop_value{'margin-top'} = ['INHERIT'];
4723 $prop_value{'margin-right'} = $prop_value{'margin-top'};
4724 $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
4725 $prop_value{'margin-left'} = $prop_value{'margin-right'};
4726 return ($t, \%prop_value);
4727 } else {
4728 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4729 level => $self->{level}->{must},
4730 uri => \$self->{href},
4731 token => $t);
4732 return ($t, undef);
4733 }
4734 } else {
4735 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4736 level => $self->{level}->{must},
4737 uri => \$self->{href},
4738 token => $t);
4739 return ($t, undef);
4740 }
4741 $prop_value{'margin-right'} = $prop_value{'margin-top'};
4742 $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
4743 $prop_value{'margin-left'} = $prop_value{'margin-right'};
4744
4745 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4746 undef $has_sign;
4747 $sign = 1;
4748 if ($t->{type} == MINUS_TOKEN) {
4749 $t = $tt->get_next_token;
4750 $has_sign = 1;
4751 $sign = -1;
4752 } elsif ($t->{type} == PLUS_TOKEN) {
4753 $t = $tt->get_next_token;
4754 $has_sign = 1;
4755 }
4756
4757 if ($t->{type} == DIMENSION_TOKEN) {
4758 my $value = $t->{number} * $sign;
4759 my $unit = lc $t->{value}; ## TODO: case
4760 $t = $tt->get_next_token;
4761 if ($length_unit->{$unit}) {
4762 $prop_value{'margin-right'} = ['DIMENSION', $value, $unit];
4763 } else {
4764 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4765 level => $self->{level}->{must},
4766 uri => \$self->{href},
4767 token => $t);
4768 return ($t, undef);
4769 }
4770 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4771 my $value = $t->{number} * $sign;
4772 $t = $tt->get_next_token;
4773 $prop_value{'margin-right'} = ['PERCENTAGE', $value];
4774 } elsif ($t->{type} == NUMBER_TOKEN and
4775 ($self->{unitless_px} or $t->{number} == 0)) {
4776 my $value = $t->{number} * $sign;
4777 $t = $tt->get_next_token;
4778 $prop_value{'margin-right'} = ['DIMENSION', $value, 'px'];
4779 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4780 my $prop_value = lc $t->{value}; ## TODO: case folding
4781 $t = $tt->get_next_token;
4782 if ($prop_value eq 'auto') {
4783 $prop_value{'margin-right'} = ['KEYWORD', $prop_value];
4784 } else {
4785 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4786 level => $self->{level}->{must},
4787 uri => \$self->{href},
4788 token => $t);
4789 return ($t, undef);
4790 }
4791 } else {
4792 if ($has_sign) {
4793 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4794 level => $self->{level}->{must},
4795 uri => \$self->{href},
4796 token => $t);
4797 return ($t, undef);
4798 }
4799 return ($t, \%prop_value);
4800 }
4801 $prop_value{'margin-left'} = $prop_value{'margin-right'};
4802
4803 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4804 undef $has_sign;
4805 $sign = 1;
4806 if ($t->{type} == MINUS_TOKEN) {
4807 $t = $tt->get_next_token;
4808 $has_sign = 1;
4809 $sign = -1;
4810 } elsif ($t->{type} == PLUS_TOKEN) {
4811 $t = $tt->get_next_token;
4812 $has_sign = 1;
4813 }
4814
4815 if ($t->{type} == DIMENSION_TOKEN) {
4816 my $value = $t->{number} * $sign;
4817 my $unit = lc $t->{value}; ## TODO: case
4818 $t = $tt->get_next_token;
4819 if ($length_unit->{$unit}) {
4820 $prop_value{'margin-bottom'} = ['DIMENSION', $value, $unit];
4821 } else {
4822 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4823 level => $self->{level}->{must},
4824 uri => \$self->{href},
4825 token => $t);
4826 return ($t, undef);
4827 }
4828 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4829 my $value = $t->{number} * $sign;
4830 $t = $tt->get_next_token;
4831 $prop_value{'margin-bottom'} = ['PERCENTAGE', $value];
4832 } elsif ($t->{type} == NUMBER_TOKEN and
4833 ($self->{unitless_px} or $t->{number} == 0)) {
4834 my $value = $t->{number} * $sign;
4835 $t = $tt->get_next_token;
4836 $prop_value{'margin-bottom'} = ['DIMENSION', $value, 'px'];
4837 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4838 my $prop_value = lc $t->{value}; ## TODO: case folding
4839 $t = $tt->get_next_token;
4840 if ($prop_value eq 'auto') {
4841 $prop_value{'margin-bottom'} = ['KEYWORD', $prop_value];
4842 } else {
4843 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4844 level => $self->{level}->{must},
4845 uri => \$self->{href},
4846 token => $t);
4847 return ($t, undef);
4848 }
4849 } else {
4850 if ($has_sign) {
4851 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4852 level => $self->{level}->{must},
4853 uri => \$self->{href},
4854 token => $t);
4855 return ($t, undef);
4856 }
4857 return ($t, \%prop_value);
4858 }
4859
4860 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
4861 undef $has_sign;
4862 $sign = 1;
4863 if ($t->{type} == MINUS_TOKEN) {
4864 $t = $tt->get_next_token;
4865 $has_sign = 1;
4866 $sign = -1;
4867 } elsif ($t->{type} == PLUS_TOKEN) {
4868 $t = $tt->get_next_token;
4869 $has_sign = 1;
4870 }
4871
4872 if ($t->{type} == DIMENSION_TOKEN) {
4873 my $value = $t->{number} * $sign;
4874 my $unit = lc $t->{value}; ## TODO: case
4875 $t = $tt->get_next_token;
4876 if ($length_unit->{$unit}) {
4877 $prop_value{'margin-left'} = ['DIMENSION', $value, $unit];
4878 } else {
4879 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4880 level => $self->{level}->{must},
4881 uri => \$self->{href},
4882 token => $t);
4883 return ($t, undef);
4884 }
4885 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4886 my $value = $t->{number} * $sign;
4887 $t = $tt->get_next_token;
4888 $prop_value{'margin-left'} = ['PERCENTAGE', $value];
4889 } elsif ($t->{type} == NUMBER_TOKEN and
4890 ($self->{unitless_px} or $t->{number} == 0)) {
4891 my $value = $t->{number} * $sign;
4892 $t = $tt->get_next_token;
4893 $prop_value{'margin-left'} = ['DIMENSION', $value, 'px'];
4894 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
4895 my $prop_value = lc $t->{value}; ## TODO: case folding
4896 $t = $tt->get_next_token;
4897 if ($prop_value eq 'auto') {
4898 $prop_value{'margin-left'} = ['KEYWORD', $prop_value];
4899 } else {
4900 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4901 level => $self->{level}->{must},
4902 uri => \$self->{href},
4903 token => $t);
4904 return ($t, undef);
4905 }
4906 } else {
4907 if ($has_sign) {
4908 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4909 level => $self->{level}->{must},
4910 uri => \$self->{href},
4911 token => $t);
4912 return ($t, undef);
4913 }
4914 return ($t, \%prop_value);
4915 }
4916
4917 return ($t, \%prop_value);
4918 },
4919 serialize_multiple => $Prop->{'margin-top'}->{serialize_multiple},
4920 };
4921 $Attr->{margin} = $Prop->{margin};
4922
4923 $Prop->{padding} = {
4924 css => 'padding',
4925 dom => 'padding',
4926 parse => sub {
4927 my ($self, $prop_name, $tt, $t, $onerror) = @_;
4928
4929 my %prop_value;
4930
4931 my $sign = 1;
4932 if ($t->{type} == MINUS_TOKEN) {
4933 $t = $tt->get_next_token;
4934 $sign = -1;
4935 }
4936
4937 if ($t->{type} == DIMENSION_TOKEN) {
4938 my $value = $t->{number} * $sign;
4939 my $unit = lc $t->{value}; ## TODO: case
4940 $t = $tt->get_next_token;
4941 if ($length_unit->{$unit} and $value >= 0) {
4942 $prop_value{'padding-top'} = ['DIMENSION', $value, $unit];
4943 } else {
4944 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4945 level => $self->{level}->{must},
4946 uri => \$self->{href},
4947 token => $t);
4948 return ($t, undef);
4949 }
4950 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
4951 my $value = $t->{number} * $sign;
4952 $t = $tt->get_next_token;
4953 $prop_value{'padding-top'} = ['PERCENTAGE', $value];
4954 unless ($value >= 0) {
4955 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4956 level => $self->{level}->{must},
4957 uri => \$self->{href},
4958 token => $t);
4959 return ($t, undef);
4960 }
4961 } elsif ($t->{type} == NUMBER_TOKEN and
4962 ($self->{unitless_px} or $t->{number} == 0)) {
4963 my $value = $t->{number} * $sign;
4964 $t = $tt->get_next_token;
4965 $prop_value{'padding-top'} = ['DIMENSION', $value, 'px'];
4966 unless ($value >= 0) {
4967 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4968 level => $self->{level}->{must},
4969 uri => \$self->{href},
4970 token => $t);
4971 return ($t, undef);
4972 }
4973 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
4974 my $prop_value = lc $t->{value}; ## TODO: case folding
4975 $t = $tt->get_next_token;
4976 if ($prop_value eq 'inherit') {
4977 $prop_value{'padding-top'} = ['INHERIT'];
4978 $prop_value{'padding-right'} = $prop_value{'padding-top'};
4979 $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
4980 $prop_value{'padding-left'} = $prop_value{'padding-right'};
4981 return ($t, \%prop_value);
4982 } else {
4983 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4984 level => $self->{level}->{must},
4985 uri => \$self->{href},
4986 token => $t);
4987 return ($t, undef);
4988 }
4989 } else {
4990 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
4991 level => $self->{level}->{must},
4992 uri => \$self->{href},
4993 token => $t);
4994 return ($t, undef);
4995 }
4996 $prop_value{'padding-right'} = $prop_value{'padding-top'};
4997 $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
4998 $prop_value{'padding-left'} = $prop_value{'padding-right'};
4999
5000 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5001 $sign = 1;
5002 if ($t->{type} == MINUS_TOKEN) {
5003 $t = $tt->get_next_token;
5004 $sign = -1;
5005 }
5006
5007 if ($t->{type} == DIMENSION_TOKEN) {
5008 my $value = $t->{number} * $sign;
5009 my $unit = lc $t->{value}; ## TODO: case
5010 $t = $tt->get_next_token;
5011 if ($length_unit->{$unit} and $value >= 0) {
5012 $prop_value{'padding-right'} = ['DIMENSION', $value, $unit];
5013 } else {
5014 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5015 level => $self->{level}->{must},
5016 uri => \$self->{href},
5017 token => $t);
5018 return ($t, undef);
5019 }
5020 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5021 my $value = $t->{number} * $sign;
5022 $t = $tt->get_next_token;
5023 $prop_value{'padding-right'} = ['PERCENTAGE', $value];
5024 unless ($value >= 0) {
5025 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5026 level => $self->{level}->{must},
5027 uri => \$self->{href},
5028 token => $t);
5029 return ($t, undef);
5030 }
5031 } elsif ($t->{type} == NUMBER_TOKEN and
5032 ($self->{unitless_px} or $t->{number} == 0)) {
5033 my $value = $t->{number} * $sign;
5034 $t = $tt->get_next_token;
5035 $prop_value{'padding-right'} = ['DIMENSION', $value, 'px'];
5036 unless ($value >= 0) {
5037 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5038 level => $self->{level}->{must},
5039 uri => \$self->{href},
5040 token => $t);
5041 return ($t, undef);
5042 }
5043 } else {
5044 if ($sign < 0) {
5045 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5046 level => $self->{level}->{must},
5047 uri => \$self->{href},
5048 token => $t);
5049 return ($t, undef);
5050 }
5051 return ($t, \%prop_value);
5052 }
5053 $prop_value{'padding-left'} = $prop_value{'padding-right'};
5054
5055 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5056 $sign = 1;
5057 if ($t->{type} == MINUS_TOKEN) {
5058 $t = $tt->get_next_token;
5059 $sign = -1;
5060 }
5061
5062 if ($t->{type} == DIMENSION_TOKEN) {
5063 my $value = $t->{number} * $sign;
5064 my $unit = lc $t->{value}; ## TODO: case
5065 $t = $tt->get_next_token;
5066 if ($length_unit->{$unit} and $value >= 0) {
5067 $prop_value{'padding-bottom'} = ['DIMENSION', $value, $unit];
5068 } else {
5069 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5070 level => $self->{level}->{must},
5071 uri => \$self->{href},
5072 token => $t);
5073 return ($t, undef);
5074 }
5075 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5076 my $value = $t->{number} * $sign;
5077 $t = $tt->get_next_token;
5078 $prop_value{'padding-bottom'} = ['PERCENTAGE', $value];
5079 unless ($value >= 0) {
5080 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5081 level => $self->{level}->{must},
5082 uri => \$self->{href},
5083 token => $t);
5084 return ($t, undef);
5085 }
5086 } elsif ($t->{type} == NUMBER_TOKEN and
5087 ($self->{unitless_px} or $t->{number} == 0)) {
5088 my $value = $t->{number} * $sign;
5089 $t = $tt->get_next_token;
5090 $prop_value{'padding-bottom'} = ['DIMENSION', $value, 'px'];
5091 unless ($value >= 0) {
5092 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5093 level => $self->{level}->{must},
5094 uri => \$self->{href},
5095 token => $t);
5096 return ($t, undef);
5097 }
5098 } else {
5099 if ($sign < 0) {
5100 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5101 level => $self->{level}->{must},
5102 uri => \$self->{href},
5103 token => $t);
5104 return ($t, undef);
5105 }
5106 return ($t, \%prop_value);
5107 }
5108
5109 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5110 $sign = 1;
5111 if ($t->{type} == MINUS_TOKEN) {
5112 $t = $tt->get_next_token;
5113 $sign = -1;
5114 }
5115
5116 if ($t->{type} == DIMENSION_TOKEN) {
5117 my $value = $t->{number} * $sign;
5118 my $unit = lc $t->{value}; ## TODO: case
5119 $t = $tt->get_next_token;
5120 if ($length_unit->{$unit} and $value >= 0) {
5121 $prop_value{'padding-left'} = ['DIMENSION', $value, $unit];
5122 } else {
5123 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5124 level => $self->{level}->{must},
5125 uri => \$self->{href},
5126 token => $t);
5127 return ($t, undef);
5128 }
5129 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5130 my $value = $t->{number} * $sign;
5131 $t = $tt->get_next_token;
5132 $prop_value{'padding-left'} = ['PERCENTAGE', $value];
5133 unless ($value >= 0) {
5134 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5135 level => $self->{level}->{must},
5136 uri => \$self->{href},
5137 token => $t);
5138 return ($t, undef);
5139 }
5140 } elsif ($t->{type} == NUMBER_TOKEN and
5141 ($self->{unitless_px} or $t->{number} == 0)) {
5142 my $value = $t->{number} * $sign;
5143 $t = $tt->get_next_token;
5144 $prop_value{'padding-left'} = ['DIMENSION', $value, 'px'];
5145 unless ($value >= 0) {
5146 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5147 level => $self->{level}->{must},
5148 uri => \$self->{href},
5149 token => $t);
5150 return ($t, undef);
5151 }
5152 } else {
5153 if ($sign < 0) {
5154 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5155 level => $self->{level}->{must},
5156 uri => \$self->{href},
5157 token => $t);
5158 return ($t, undef);
5159 }
5160 return ($t, \%prop_value);
5161 }
5162
5163 return ($t, \%prop_value);
5164 },
5165 serialize_multiple => $Prop->{'padding-top'}->{serialize_multiple},
5166 };
5167 $Attr->{padding} = $Prop->{padding};
5168
5169 $Prop->{'border-spacing'} = {
5170 css => 'border-spacing',
5171 dom => 'border_spacing',
5172 parse => sub {
5173 my ($self, $prop_name, $tt, $t, $onerror) = @_;
5174
5175 my %prop_value;
5176 my $has_sign;
5177 my $sign = 1;
5178 if ($t->{type} == MINUS_TOKEN) {
5179 $t = $tt->get_next_token;
5180 $has_sign = 1;
5181 $sign = -1;
5182 } elsif ($t->{type} == PLUS_TOKEN) {
5183 $t = $tt->get_next_token;
5184 $has_sign = 1;
5185 }
5186
5187 if ($t->{type} == DIMENSION_TOKEN) {
5188 my $value = $t->{number} * $sign;
5189 my $unit = lc $t->{value}; ## TODO: case
5190 if ($length_unit->{$unit} and $value >= 0) {
5191 $t = $tt->get_next_token;
5192 $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, $unit];
5193 } else {
5194 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5195 level => $self->{level}->{must},
5196 uri => \$self->{href},
5197 token => $t);
5198 return ($t, undef);
5199 }
5200 } elsif ($t->{type} == NUMBER_TOKEN and
5201 ($self->{unitless_px} or $t->{number} == 0)) {
5202 my $value = $t->{number} * $sign;
5203 $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, 'px'];
5204 if ($value >= 0) {
5205 $t = $tt->get_next_token;
5206 } else {
5207 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5208 level => $self->{level}->{must},
5209 uri => \$self->{href},
5210 token => $t);
5211 return ($t, undef);
5212 }
5213 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5214 my $prop_value = lc $t->{value}; ## TODO: case folding
5215 if ($prop_value eq 'inherit') {
5216 $t = $tt->get_next_token;
5217 $prop_value{'-manakai-border-spacing-x'} = ['INHERIT'];
5218 $prop_value{'-manakai-border-spacing-y'}
5219 = $prop_value{'-manakai-border-spacing-x'};
5220 return ($t, \%prop_value);
5221 } else {
5222 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5223 level => $self->{level}->{must},
5224 uri => \$self->{href},
5225 token => $t);
5226 return ($t, undef);
5227 }
5228 } else {
5229 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5230 level => $self->{level}->{must},
5231 uri => \$self->{href},
5232 token => $t);
5233 return ($t, undef);
5234 }
5235 $prop_value{'-manakai-border-spacing-y'}
5236 = $prop_value{'-manakai-border-spacing-x'};
5237
5238 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5239 undef $has_sign;
5240 $sign = 1;
5241 if ($t->{type} == MINUS_TOKEN) {
5242 $t = $tt->get_next_token;
5243 $has_sign = 1;
5244 $sign = -1;
5245 } elsif ($t->{type} == PLUS_TOKEN) {
5246 $t = $tt->get_next_token;
5247 $has_sign = 1;
5248 }
5249
5250 if ($t->{type} == DIMENSION_TOKEN) {
5251 my $value = $t->{number} * $sign;
5252 my $unit = lc $t->{value}; ## TODO: case
5253 if ($length_unit->{$unit} and $value >= 0) {
5254 $t = $tt->get_next_token;
5255 $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, $unit];
5256 } else {
5257 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5258 level => $self->{level}->{must},
5259 uri => \$self->{href},
5260 token => $t);
5261 return ($t, undef);
5262 }
5263 } elsif ($t->{type} == NUMBER_TOKEN and
5264 ($self->{unitless_px} or $t->{number} == 0)) {
5265 my $value = $t->{number} * $sign;
5266 $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, 'px'];
5267 if ($value >= 0) {
5268 $t = $tt->get_next_token;
5269 } else {
5270 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5271 level => $self->{level}->{must},
5272 uri => \$self->{href},
5273 token => $t);
5274 return ($t, undef);
5275 }
5276 } else {
5277 if ($has_sign) {
5278 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5279 level => $self->{level}->{must},
5280 uri => \$self->{href},
5281 token => $t);
5282 return ($t, undef);
5283 }
5284 return ($t, \%prop_value);
5285 }
5286
5287 return ($t, \%prop_value);
5288 },
5289 serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
5290 ->{serialize_multiple},
5291 };
5292 $Attr->{border_spacing} = $Prop->{'border-spacing'};
5293
5294 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/background-position> for
5295 ## browser compatibility problems.
5296 $Prop->{'background-position'} = {
5297 css => 'background-position',
5298 dom => 'background_position',
5299 parse => sub {
5300 my ($self, $prop_name, $tt, $t, $onerror) = @_;
5301
5302 my %prop_value;
5303
5304 my $sign = 1;
5305 my $has_sign;
5306 if ($t->{type} == MINUS_TOKEN) {
5307 $t = $tt->get_next_token;
5308 $has_sign = 1;
5309 $sign = -1;
5310 } elsif ($t->{type} == PLUS_TOKEN) {
5311 $t = $tt->get_next_token;
5312 $has_sign = 1;
5313 }
5314
5315 if ($t->{type} == DIMENSION_TOKEN) {
5316 my $value = $t->{number} * $sign;
5317 my $unit = lc $t->{value}; ## TODO: case
5318 if ($length_unit->{$unit}) {
5319 $t = $tt->get_next_token;
5320 $prop_value{'background-position-x'} = ['DIMENSION', $value, $unit];
5321 $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5322 } else {
5323 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5324 level => $self->{level}->{must},
5325 uri => \$self->{href},
5326 token => $t);
5327 return ($t, undef);
5328 }
5329 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5330 my $value = $t->{number} * $sign;
5331 $t = $tt->get_next_token;
5332 $prop_value{'background-position-x'} = ['PERCENTAGE', $value];
5333 $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5334 } elsif ($t->{type} == NUMBER_TOKEN and
5335 ($self->{unitless_px} or $t->{number} == 0)) {
5336 my $value = $t->{number} * $sign;
5337 $t = $tt->get_next_token;
5338 $prop_value{'background-position-x'} = ['DIMENSION', $value, 'px'];
5339 $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5340 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5341 my $prop_value = lc $t->{value}; ## TODO: case folding
5342 if ($prop_value eq 'left' or $prop_value eq 'right') {
5343 $t = $tt->get_next_token;
5344 $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
5345 $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5346 } elsif ($prop_value eq 'center') {
5347 $t = $tt->get_next_token;
5348 $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
5349
5350 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5351 if ($t->{type} == IDENT_TOKEN) {
5352 my $prop_value = lc $t->{value}; ## TODO: case folding
5353 if ($prop_value eq 'left' or $prop_value eq 'right') {
5354 $prop_value{'background-position-y'}
5355 = $prop_value{'background-position-x'};
5356 $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
5357 $t = $tt->get_next_token;
5358 return ($t, \%prop_value);
5359 }
5360 } else {
5361 $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5362 }
5363 } elsif ($prop_value eq 'top' or $prop_value eq 'bottom') {
5364 $t = $tt->get_next_token;
5365 $prop_value{'background-position-y'} = ['KEYWORD', $prop_value];
5366
5367 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5368 if ($t->{type} == IDENT_TOKEN) {
5369 my $prop_value = lc $t->{value}; ## TODO: case folding
5370 if ({left => 1, center => 1, right => 1}->{$prop_value}) {
5371 $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
5372 $t = $tt->get_next_token;
5373 return ($t, \%prop_value);
5374 }
5375 }
5376 $prop_value{'background-position-x'} = ['KEYWORD', 'center'];
5377 return ($t, \%prop_value);
5378 } elsif ($prop_value eq 'inherit') {
5379 $t = $tt->get_next_token;
5380 $prop_value{'background-position-x'} = ['INHERIT'];
5381 $prop_value{'background-position-y'} = ['INHERIT'];
5382 return ($t, \%prop_value);
5383 } else {
5384 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5385 level => $self->{level}->{must},
5386 uri => \$self->{href},
5387 token => $t);
5388 return ($t, undef);
5389 }
5390 } else {
5391 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5392 level => $self->{level}->{must},
5393 uri => \$self->{href},
5394 token => $t);
5395 return ($t, undef);
5396 }
5397
5398 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5399 undef $has_sign;
5400 $sign = 1;
5401 if ($t->{type} == MINUS_TOKEN) {
5402 $t = $tt->get_next_token;
5403 $has_sign = 1;
5404 $sign = -1;
5405 } elsif ($t->{type} == PLUS_TOKEN) {
5406 $t = $tt->get_next_token;
5407 $has_sign = 1;
5408 }
5409
5410 if ($t->{type} == DIMENSION_TOKEN) {
5411 my $value = $t->{number} * $sign;
5412 my $unit = lc $t->{value}; ## TODO: case
5413 if ($length_unit->{$unit}) {
5414 $t = $tt->get_next_token;
5415 $prop_value{'background-position-y'} = ['DIMENSION', $value, $unit];
5416 } else {
5417 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5418 level => $self->{level}->{must},
5419 uri => \$self->{href},
5420 token => $t);
5421 return ($t, undef);
5422 }
5423 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5424 my $value = $t->{number} * $sign;
5425 $t = $tt->get_next_token;
5426 $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
5427 } elsif ($t->{type} == NUMBER_TOKEN and
5428 ($self->{unitless_px} or $t->{number} == 0)) {
5429 my $value = $t->{number} * $sign;
5430 $t = $tt->get_next_token;
5431 $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
5432 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5433 my $value = lc $t->{value}; ## TODO: case
5434 if ({top => 1, center => 1, bottom => 1}->{$value}) {
5435 $prop_value{'background-position-y'} = ['KEYWORD', $value];
5436 $t = $tt->get_next_token;
5437 }
5438 } else {
5439 if ($has_sign) {
5440 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5441 level => $self->{level}->{must},
5442 uri => \$self->{href},
5443 token => $t);
5444 return ($t, undef);
5445 }
5446 return ($t, \%prop_value);
5447 }
5448
5449 return ($t, \%prop_value);
5450 },
5451 serialize_shorthand => sub {
5452 my $self = shift;
5453
5454 my $r = {};
5455
5456 my $x = $self->background_position_x;
5457 my $y = $self->background_position_y;
5458 my $xi = $self->get_property_priority ('background-position-x');
5459 my $yi = $self->get_property_priority ('background-position-y');
5460 if (length $x) {
5461 if (length $y) {
5462 if ($xi eq $yi) {
5463 if ($x eq 'inherit') {
5464 if ($y eq 'inherit') {
5465 $r->{'background-position'} = ['inherit', $xi];
5466 } else {
5467 $r->{'background-position-x'} = [$x, $xi];
5468 $r->{'background-position-y'} = [$y, $yi];
5469 }
5470 } elsif ($y eq 'inherit') {
5471 $r->{'background-position-x'} = [$x, $xi];
5472 $r->{'background-position-y'} = [$y, $yi];
5473 } else {
5474 $r->{'background-position'} = [$x . ' ' . $y, $xi];
5475 }
5476 } else {
5477 $r->{'background-position-x'} = [$x, $xi];
5478 $r->{'background-position-y'} = [$y, $yi];
5479 }
5480 } else {
5481 $r->{'background-position-x'} = [$x, $xi];
5482 }
5483 } else {
5484 if (length $y) {
5485 $r->{'background-position-y'} = [$y, $yi];
5486 } else {
5487 #
5488 }
5489 }
5490
5491 return $r;
5492 },
5493 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
5494 };
5495 $Attr->{background_position} = $Prop->{'background-position'};
5496
5497 $Prop->{background} = {
5498 css => 'background',
5499 dom => 'background',
5500 parse => sub {
5501 my ($self, $prop_name, $tt, $t, $onerror) = @_;
5502 my %prop_value;
5503 B: for (1..5) {
5504 my $has_sign;
5505 my $sign = 1;
5506 if ($t->{type} == MINUS_TOKEN) {
5507 $sign = -1;
5508 $has_sign = 1;
5509 $t = $tt->get_next_token;
5510 } elsif ($t->{type} == PLUS_TOKEN) {
5511 $has_sign = 1;
5512 $t = $tt->get_next_token;
5513 }
5514
5515 if (not $has_sign and $t->{type} == IDENT_TOKEN) {
5516 my $value = lc $t->{value}; ## TODO: case
5517 if ($Prop->{'background-repeat'}->{keyword}->{$value} and
5518 $self->{prop_value}->{'background-repeat'}->{$value} and
5519 not defined $prop_value{'background-repeat'}) {
5520 $prop_value{'background-repeat'} = ['KEYWORD', $value];
5521 $t = $tt->get_next_token;
5522 } elsif ($Prop->{'background-attachment'}->{keyword}->{$value} and
5523 $self->{prop_value}->{'background-attachment'}->{$value} and
5524 not defined $prop_value{'background-attachment'}) {
5525 $prop_value{'background-attachment'} = ['KEYWORD', $value];
5526 $t = $tt->get_next_token;
5527 } elsif ($value eq 'none' and
5528 not defined $prop_value{'background-image'}) {
5529 $prop_value{'background-image'} = ['KEYWORD', $value];
5530 $t = $tt->get_next_token;
5531 } elsif ({left => 1, center => 1, right => 1}->{$value} and
5532 not defined $prop_value{'background-position-x'}) {
5533 $prop_value{'background-position-x'} = ['KEYWORD', $value];
5534 $t = $tt->get_next_token;
5535 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5536 my $sign = 1;
5537 my $has_sign;
5538 if ($t->{type} == MINUS_TOKEN) {
5539 $sign = -1;
5540 $has_sign = 1;
5541 $t = $tt->get_next_token;
5542 } elsif ($t->{type} == PLUS_TOKEN) {
5543 $has_sign = 1;
5544 $t = $tt->get_next_token;
5545 }
5546 if (not $has_sign and $t->{type} == IDENT_TOKEN) {
5547 my $value = lc $t->{value}; ## TODO: case
5548 if ({top => 1, bottom => 1, center => 1}->{$value}) {
5549 $prop_value{'background-position-y'} = ['KEYWORD', $value];
5550 $t = $tt->get_next_token;
5551 } elsif ($prop_value{'background-position-x'}->[1] eq 'center' and
5552 $value eq 'left' or $value eq 'right') {
5553 $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5554 $prop_value{'background-position-x'} = ['KEYWORD', $value];
5555 $t = $tt->get_next_token;
5556 } else {
5557 $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5558 }
5559 } elsif ($t->{type} == DIMENSION_TOKEN) {
5560 my $value = $t->{number} * $sign;
5561 my $unit = lc $t->{value}; ## TODO: case
5562 $t = $tt->get_next_token;
5563 if ($length_unit->{$unit}) {
5564 $prop_value{'background-position-y'}
5565 = ['DIMENSION', $value, $unit];
5566 } else {
5567 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5568 level => $self->{level}->{must},
5569 uri => \$self->{href},
5570 token => $t);
5571 return ($t, undef);
5572 }
5573 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5574 my $value = $t->{number} * $sign;
5575 $t = $tt->get_next_token;
5576 $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
5577 } elsif ($t->{type} == NUMBER_TOKEN and
5578 ($self->{unitless_px} or $t->{number} == 0)) {
5579 my $value = $t->{number} * $sign;
5580 $t = $tt->get_next_token;
5581 $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
5582 } elsif ($has_sign) {
5583 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5584 level => $self->{level}->{must},
5585 uri => \$self->{href},
5586 token => $t);
5587 return ($t, undef);
5588 } else {
5589 $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
5590 }
5591 } elsif (($value eq 'top' or $value eq 'bottom') and
5592 not defined $prop_value{'background-position-y'}) {
5593 $prop_value{'background-position-y'} = ['KEYWORD', $value];
5594 $t = $tt->get_next_token;
5595 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5596 if ($t->{type} == IDENT_TOKEN and ## TODO: case
5597 {
5598 left => 1, center => 1, right => 1,
5599 }->{my $value = lc $t->{value}}) {
5600 $prop_value{'background-position-x'} = ['KEYWORD', $value];
5601 $t = $tt->get_next_token;
5602 } else {
5603 $prop_value{'background-position-x'} = ['KEYWORD', 'center'];
5604 }
5605 } elsif ($value eq 'inherit' and not keys %prop_value) {
5606 $prop_value{'background-color'} =
5607 $prop_value{'background-image'} =
5608 $prop_value{'background-repeat'} =
5609 $prop_value{'background-attachment'} =
5610 $prop_value{'background-position-x'} =
5611 $prop_value{'background-position-y'} = ['INHERIT'];
5612 $t = $tt->get_next_token;
5613 return ($t, \%prop_value);
5614 } elsif (not defined $prop_value{'background-color'} or
5615 not keys %prop_value) {
5616 ($t, my $pv) = $parse_color->($self, 'background', $tt, $t,
5617 $onerror);
5618 if (defined $pv) {
5619 $prop_value{'background-color'} = $pv->{background};
5620 } else {
5621 ## NOTE: An error should already be raiased.
5622 return ($t, undef);
5623 }
5624 }
5625 } elsif (($t->{type} == DIMENSION_TOKEN or
5626 $t->{type} == PERCENTAGE_TOKEN or
5627 ($t->{type} == NUMBER_TOKEN and
5628 ($t->{unitless_px} or $t->{number} == 0))) and
5629 not defined $prop_value{'background-position-x'}) {
5630 if ($t->{type} == DIMENSION_TOKEN) {
5631 my $value = $t->{number} * $sign;
5632 my $unit = lc $t->{value}; ## TODO: case
5633 $t = $tt->get_next_token;
5634 if ($length_unit->{$unit}) {
5635 $prop_value{'background-position-x'}
5636 = ['DIMENSION', $value, $unit];
5637 } else {
5638 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5639 level => $self->{level}->{must},
5640 uri => \$self->{href},
5641 token => $t);
5642 return ($t, undef);
5643 }
5644 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5645 my $value = $t->{number} * $sign;
5646 $t = $tt->get_next_token;
5647 $prop_value{'background-position-x'} = ['PERCENTAGE', $value];
5648 } elsif ($t->{type} == NUMBER_TOKEN and
5649 ($self->{unitless_px} or $t->{number} == 0)) {
5650 my $value = $t->{number} * $sign;
5651 $t = $tt->get_next_token;
5652 $prop_value{'background-position-x'} = ['DIMENSION', $value, 'px'];
5653 } else {
5654 ## NOTE: Should not be happened.
5655 last B;
5656 }
5657
5658 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5659 if ($t->{type} == MINUS_TOKEN) {
5660 $sign = -1;
5661 $has_sign = 1;
5662 $t = $tt->get_next_token;
5663 } elsif ($t->{type} == PLUS_TOKEN) {
5664 $has_sign = 1;
5665 $t = $tt->get_next_token;
5666 } else {
5667 undef $has_sign;
5668 $sign = 1;
5669 }
5670
5671 if ($t->{type} == DIMENSION_TOKEN) {
5672 my $value = $t->{number} * $sign;
5673 my $unit = lc $t->{value}; ## TODO: case
5674 $t = $tt->get_next_token;
5675 if ($length_unit->{$unit}) {
5676 $prop_value{'background-position-y'}
5677 = ['DIMENSION', $value, $unit];
5678 } else {
5679 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5680 level => $self->{level}->{must},
5681 uri => \$self->{href},
5682 token => $t);
5683 return ($t, undef);
5684 }
5685 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
5686 my $value = $t->{number} * $sign;
5687 $t = $tt->get_next_token;
5688 $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
5689 } elsif ($t->{type} == NUMBER_TOKEN and
5690 ($self->{unitless_px} or $t->{number} == 0)) {
5691 my $value = $t->{number} * $sign;
5692 $t = $tt->get_next_token;
5693 $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
5694 } elsif ($t->{type} == IDENT_TOKEN) {
5695 my $value = lc $t->{value}; ## TODO: case
5696 if ({top => 1, center => 1, bottom => 1}->{$value}) {
5697 $prop_value{'background-position-y'} = ['KEYWORD', $value];
5698 $t = $tt->get_next_token;
5699 } else {
5700 $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5701 }
5702 } else {
5703 $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
5704 if ($has_sign) {
5705 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5706 level => $self->{level}->{must},
5707 uri => \$self->{href},
5708 token => $t);
5709 return ($t, undef);
5710 }
5711 }
5712 } elsif (not $has_sign and
5713 $t->{type} == URI_TOKEN and
5714 not defined $prop_value{'background-image'}) {
5715 $prop_value{'background-image'}
5716 = ['URI', $t->{value}, \($self->{base_uri})];
5717 $t = $tt->get_next_token;
5718 } else {
5719 if (keys %prop_value and not $has_sign) {
5720 last B;
5721 } else {
5722 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5723 level => $self->{level}->{must},
5724 uri => \$self->{href},
5725 token => $t);
5726 return ($t, undef);
5727 }
5728 }
5729
5730 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5731 } # B
5732
5733 $prop_value{$_} ||= $Prop->{$_}->{initial}
5734 for qw/background-image background-attachment background-repeat
5735 background-color background-position-x background-position-y/;
5736
5737 return ($t, \%prop_value);
5738 },
5739 ## TODO: background: #fff does not work.
5740 serialize_multiple => $Prop->{'background-color'}->{serialize_multiple},
5741 };
5742 $Attr->{background} = $Prop->{background};
5743
5744 $Prop->{font} = {
5745 css => 'font',
5746 dom => 'font',
5747 parse => sub {
5748 my ($self, $prop_name, $tt, $t, $onerror) = @_;
5749
5750 my %prop_value;
5751
5752 A: for (1..3) {
5753 if ($t->{type} == IDENT_TOKEN) {
5754 my $value = lc $t->{value}; ## TODO: case
5755 if ($value eq 'normal') {
5756 $t = $tt->get_next_token;
5757 } elsif ($Prop->{'font-style'}->{keyword}->{$value} and
5758 $self->{prop_value}->{'font-style'}->{$value} and
5759 not defined $prop_value{'font-style'}) {
5760 $prop_value{'font-style'} = ['KEYWORD', $value];
5761 $t = $tt->get_next_token;
5762 } elsif ($Prop->{'font-variant'}->{keyword}->{$value} and
5763 $self->{prop_value}->{'font-variant'}->{$value} and
5764 not defined $prop_value{'font-variant'}) {
5765 $prop_value{'font-variant'} = ['KEYWORD', $value];
5766 $t = $tt->get_next_token;
5767 } elsif ({normal => 1, bold => 1,
5768 bolder => 1, lighter => 1}->{$value} and
5769 not defined $prop_value{'font-weight'}) {
5770 $prop_value{'font-weight'} = ['KEYWORD', $value];
5771 $t = $tt->get_next_token;
5772 } elsif ($value eq 'inherit' and 0 == keys %prop_value) {
5773 $t = $tt->get_next_token;
5774 return ($t, {'font-style' => ['INHERIT'],
5775 'font-variant' => ['INHERIT'],
5776 'font-weight' => ['INHERIT'],
5777 'font-size' => ['INHERIT'],
5778 'font-size-adjust' => ['INHERIT'],
5779 'font-stretch' => ['INHERIT'],
5780 'line-height' => ['INHERIT'],
5781 'font-family' => ['INHERIT']});
5782 } elsif ({
5783 caption => 1, icon => 1, menu => 1,
5784 'message-box' => 1, 'small-caption' => 1, 'status-bar' => 1,
5785 }->{$value} and 0 == keys %prop_value) {
5786 $t = $tt->get_next_token;
5787 return ($t, $self->{get_system_font}->($self, $value, {
5788 'font-style' => $Prop->{'font-style'}->{initial},
5789 'font-variant' => $Prop->{'font-variant'}->{initial},
5790 'font-weight' => $Prop->{'font-weight'}->{initial},
5791 'font-size' => $Prop->{'font-size'}->{initial},
5792 'font-size-adjust' => $Prop->{'font-size-adjust'}->{initial},
5793 'font-stretch' => $Prop->{'font-stretch'}->{initial},
5794 'line-height' => $Prop->{'line-height'}->{initial},
5795 'font-family' => ['FONT', ['KEYWORD', '-manakai-'.$value]],
5796 }));
5797 } else {
5798 if (keys %prop_value) {
5799 last A;
5800 } else {
5801 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5802 level => $self->{level}->{must},
5803 uri => \$self->{href},
5804 token => $t);
5805 return ($t, undef);
5806 }
5807 }
5808 } elsif ($t->{type} == NUMBER_TOKEN) {
5809 if ({100 => 1, 200 => 1, 300 => 1, 400 => 1, 500 => 1,
5810 600 => 1, 700 => 1, 800 => 1, 900 => 1}->{$t->{number}}) {
5811 $prop_value{'font-weight'} = ['WEIGHT', $t->{number}, 0];
5812 $t = $tt->get_next_token;
5813 } else {
5814 last A;
5815 }
5816 } elsif ($t->{type} == PLUS_TOKEN) {
5817 $t = $tt->get_next_token;
5818 if ($t->{type} == NUMBER_TOKEN) {
5819 if ({100 => 1, 200 => 1, 300 => 1, 400 => 1, 500 => 1,
5820 600 => 1, 700 => 1, 800 => 1, 900 => 1}->{$t->{number}}) {
5821 $prop_value{'font-weight'} = ['WEIGHT', $t->{number}, 0];
5822 $t = $tt->get_next_token;
5823 } else {
5824 ## NOTE: <'font-size'> or invalid
5825 last A;
5826 }
5827 } elsif ($t->{type} == DIMENSION_TOKEN or
5828 $t->{type} == PERCENTAGE_TOKEN) {
5829 ## NOTE: <'font-size'> or invalid
5830 last A;
5831 } else {
5832 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5833 level => $self->{level}->{must},
5834 uri => \$self->{href},
5835 token => $t);
5836 return ($t, undef);
5837 }
5838 } else {
5839 last A;
5840 }
5841
5842 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5843 } # A
5844
5845 for (qw/font-style font-variant font-weight/) {
5846 $prop_value{$_} = $Prop->{$_}->{initial} unless defined $prop_value{$_};
5847 }
5848
5849 ($t, my $pv) = $Prop->{'font-size'}->{parse}
5850 ->($self, 'font', $tt, $t, $onerror);
5851 return ($t, undef) unless defined $pv;
5852 if ($pv->{font}->[0] eq 'INHERIT') {
5853 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5854 level => $self->{level}->{must},
5855 uri => \$self->{href},
5856 token => $t);
5857 return ($t, undef);
5858 }
5859 $prop_value{'font-size'} = $pv->{font};
5860
5861 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5862 if ($t->{type} == DELIM_TOKEN and $t->{value} eq '/') {
5863 $t = $tt->get_next_token;
5864 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5865 ($t, my $pv) = $Prop->{'line-height'}->{parse}
5866 ->($self, 'font', $tt, $t, $onerror);
5867 return ($t, undef) unless defined $pv;
5868 if ($pv->{font}->[0] eq 'INHERIT') {
5869 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5870 level => $self->{level}->{must},
5871 uri => \$self->{href},
5872 token => $t);
5873 return ($t, undef);
5874 }
5875 $prop_value{'line-height'} = $pv->{font};
5876 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
5877 } else {
5878 $prop_value{'line-height'} = $Prop->{'line-height'}->{initial};
5879 }
5880
5881 undef $pv;
5882 ($t, $pv) = $Prop->{'font-family'}->{parse}
5883 ->($self, 'font', $tt, $t, $onerror);
5884 return ($t, undef) unless defined $pv;
5885 $prop_value{'font-family'} = $pv->{font};
5886
5887 $prop_value{'font-size-adjust'} = $Prop->{'font-size-adjust'}->{initial};
5888 $prop_value{'font-stretch'} = $Prop->{'font-stretch'}->{initial};
5889
5890 return ($t, \%prop_value);
5891 },
5892 serialize_shorthand => sub {
5893 my $self = shift;
5894
5895 local $Error::Depth = $Error::Depth + 1;
5896 my $style = $self->font_style;
5897 my $i = $self->get_property_priority ('font-style');
5898 return {} unless length $style;
5899 my $variant = $self->font_variant;
5900 return {} unless length $variant;
5901 return {} if $i ne $self->get_property_priority ('font-variant');
5902 my $weight = $self->font_weight;
5903 return {} unless length $weight;
5904 return {} if $i ne $self->get_property_priority ('font-weight');
5905 my $size = $self->font_size;
5906 return {} unless length $size;
5907 return {} if $i ne $self->get_property_priority ('font-size');
5908 my $height = $self->line_height;
5909 return {} unless length $height;
5910 return {} if $i ne $self->get_property_priority ('line-height');
5911 my $family = $self->font_family;
5912 return {} unless length $family;
5913 return {} if $i ne $self->get_property_priority ('font-family');
5914
5915 my $v = 0;
5916 for ($style, $variant, $weight, $size, $height, $family) {
5917 $v++ if $_ eq 'inherit';
5918 }
5919 if ($v == 6) {
5920 return {font => ['inherit', $i]};
5921 } elsif ($v) {
5922 return {};
5923 }
5924
5925 my @v;
5926 push @v, $style unless $style eq 'normal';
5927 push @v, $variant unless $variant eq 'normal';
5928 push @v, $weight unless $weight eq 'normal';
5929 push @v, $size.($height eq 'normal' ? '' : '/'.$height);
5930 push @v, $family;
5931 return {font => [(join ' ', @v), $i]};
5932 },
5933 };
5934 $Attr->{font} = $Prop->{font};
5935
5936 $Prop->{'border-width'} = {
5937 css => 'border-width',
5938 dom => 'border_width',
5939 parse => sub {
5940 my ($self, $prop_name, $tt, $t, $onerror) = @_;
5941
5942 my %prop_value;
5943
5944 my $has_sign;
5945 my $sign = 1;
5946 if ($t->{type} == MINUS_TOKEN) {
5947 $t = $tt->get_next_token;
5948 $has_sign = 1;
5949 $sign = -1;
5950 } elsif ($t->{type} == PLUS_TOKEN) {
5951 $t = $tt->get_next_token;
5952 $has_sign = 1;
5953 }
5954
5955 if ($t->{type} == DIMENSION_TOKEN) {
5956 my $value = $t->{number} * $sign;
5957 my $unit = lc $t->{value}; ## TODO: case
5958 if ($length_unit->{$unit} and $value >= 0) {
5959 $prop_value{'border-top-width'} = ['DIMENSION', $value, $unit];
5960 $t = $tt->get_next_token;
5961 } else {
5962 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5963 level => $self->{level}->{must},
5964 uri => \$self->{href},
5965 token => $t);
5966 return ($t, undef);
5967 }
5968 } elsif ($t->{type} == NUMBER_TOKEN and
5969 ($self->{unitless_px} or $t->{number} == 0)) {
5970 my $value = $t->{number} * $sign;
5971 if ($value >= 0) {
5972 $prop_value{'border-top-width'} = ['DIMENSION', $value, 'px'];
5973 $t = $tt->get_next_token;
5974 } else {
5975 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5976 level => $self->{level}->{must},
5977 uri => \$self->{href},
5978 token => $t);
5979 return ($t, undef);
5980 }
5981 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
5982 my $prop_value = lc $t->{value}; ## TODO: case folding
5983 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
5984 $t = $tt->get_next_token;
5985 $prop_value{'border-top-width'} = ['KEYWORD', $prop_value];
5986 } elsif ($prop_value eq 'inherit') {
5987 $t = $tt->get_next_token;
5988 $prop_value{'border-top-width'} = ['INHERIT'];
5989 $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
5990 $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
5991 $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
5992 return ($t, \%prop_value);
5993 } else {
5994 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
5995 level => $self->{level}->{must},
5996 uri => \$self->{href},
5997 token => $t);
5998 return ($t, undef);
5999 }
6000 } else {
6001 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6002 level => $self->{level}->{must},
6003 uri => \$self->{href},
6004 token => $t);
6005 return ($t, undef);
6006 }
6007 $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
6008 $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
6009 $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
6010
6011 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6012 if ($t->{type} == MINUS_TOKEN) {
6013 $t = $tt->get_next_token;
6014 $has_sign = 1;
6015 $sign = -1;
6016 } elsif ($t->{type} == PLUS_TOKEN) {
6017 $t = $tt->get_next_token;
6018 $has_sign = 1;
6019 $sign = 1;
6020 } else {
6021 undef $has_sign;
6022 $sign = 1;
6023 }
6024
6025 if ($t->{type} == DIMENSION_TOKEN) {
6026 my $value = $t->{number} * $sign;
6027 my $unit = lc $t->{value}; ## TODO: case
6028 if ($length_unit->{$unit} and $value >= 0) {
6029 $t = $tt->get_next_token;
6030 $prop_value{'border-right-width'} = ['DIMENSION', $value, $unit];
6031 } else {
6032 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6033 level => $self->{level}->{must},
6034 uri => \$self->{href},
6035 token => $t);
6036 return ($t, undef);
6037 }
6038 } elsif ($t->{type} == NUMBER_TOKEN and
6039 ($self->{unitless_px} or $t->{number} == 0)) {
6040 my $value = $t->{number} * $sign;
6041 if ($value >= 0) {
6042 $t = $tt->get_next_token;
6043 $prop_value{'border-right-width'} = ['DIMENSION', $value, 'px'];
6044 } else {
6045 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6046 level => $self->{level}->{must},
6047 uri => \$self->{href},
6048 token => $t);
6049 return ($t, undef);
6050 }
6051 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
6052 my $prop_value = lc $t->{value}; ## TODO: case
6053 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
6054 $prop_value{'border-right-width'} = ['KEYWORD', $prop_value];
6055 $t = $tt->get_next_token;
6056 }
6057 } else {
6058 if ($has_sign) {
6059 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6060 level => $self->{level}->{must},
6061 uri => \$self->{href},
6062 token => $t);
6063 return ($t, undef);
6064 }
6065 return ($t, \%prop_value);
6066 }
6067 $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
6068
6069 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6070 if ($t->{type} == MINUS_TOKEN) {
6071 $t = $tt->get_next_token;
6072 $has_sign = 1;
6073 $sign = -1;
6074 } elsif ($t->{type} == PLUS_TOKEN) {
6075 $t = $tt->get_next_token;
6076 $has_sign = 1;
6077 $sign = 1;
6078 } else {
6079 undef $has_sign;
6080 $sign = 1;
6081 }
6082
6083 if ($t->{type} == DIMENSION_TOKEN) {
6084 my $value = $t->{number} * $sign;
6085 my $unit = lc $t->{value}; ## TODO: case
6086 if ($length_unit->{$unit} and $value >= 0) {
6087 $t = $tt->get_next_token;
6088 $prop_value{'border-bottom-width'} = ['DIMENSION', $value, $unit];
6089 } else {
6090 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6091 level => $self->{level}->{must},
6092 uri => \$self->{href},
6093 token => $t);
6094 return ($t, undef);
6095 }
6096 } elsif ($t->{type} == NUMBER_TOKEN and
6097 ($self->{unitless_px} or $t->{number} == 0)) {
6098 my $value = $t->{number} * $sign;
6099 if ($value >= 0) {
6100 $t = $tt->get_next_token;
6101 $prop_value{'border-bottom-width'} = ['DIMENSION', $value, 'px'];
6102 } else {
6103 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6104 level => $self->{level}->{must},
6105 uri => \$self->{href},
6106 token => $t);
6107 return ($t, undef);
6108 }
6109 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
6110 my $prop_value = lc $t->{value}; ## TODO: case
6111 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
6112 $prop_value{'border-bottom-width'} = ['KEYWORD', $prop_value];
6113 $t = $tt->get_next_token;
6114 }
6115 } else {
6116 if ($has_sign) {
6117 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6118 level => $self->{level}->{must},
6119 uri => \$self->{href},
6120 token => $t);
6121 return ($t, undef);
6122 }
6123 return ($t, \%prop_value);
6124 }
6125
6126 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6127 if ($t->{type} == MINUS_TOKEN) {
6128 $t = $tt->get_next_token;
6129 $has_sign = 1;
6130 $sign = -1;
6131 } elsif ($t->{type} == PLUS_TOKEN) {
6132 $t = $tt->get_next_token;
6133 $has_sign = 1;
6134 $sign = 1;
6135 } else {
6136 undef $has_sign;
6137 $sign = 1;
6138 }
6139
6140 if ($t->{type} == DIMENSION_TOKEN) {
6141 my $value = $t->{number} * $sign;
6142 my $unit = lc $t->{value}; ## TODO: case
6143 if ($length_unit->{$unit} and $value >= 0) {
6144 $t = $tt->get_next_token;
6145 $prop_value{'border-left-width'} = ['DIMENSION', $value, $unit];
6146 } else {
6147 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6148 level => $self->{level}->{must},
6149 uri => \$self->{href},
6150 token => $t);
6151 return ($t, undef);
6152 }
6153 } elsif ($t->{type} == NUMBER_TOKEN and
6154 ($self->{unitless_px} or $t->{number} == 0)) {
6155 my $value = $t->{number} * $sign;
6156 if ($value >= 0) {
6157 $t = $tt->get_next_token;
6158 $prop_value{'border-left-width'} = ['DIMENSION', $value, 'px'];
6159 } else {
6160 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6161 level => $self->{level}->{must},
6162 uri => \$self->{href},
6163 token => $t);
6164 return ($t, undef);
6165 }
6166 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
6167 my $prop_value = lc $t->{value}; ## TODO: case
6168 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
6169 $prop_value{'border-left-width'} = ['KEYWORD', $prop_value];
6170 $t = $tt->get_next_token;
6171 }
6172 } else {
6173 if ($has_sign) {
6174 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6175 level => $self->{level}->{must},
6176 uri => \$self->{href},
6177 token => $t);
6178 return ($t, undef);
6179 }
6180 return ($t, \%prop_value);
6181 }
6182
6183 return ($t, \%prop_value);
6184 },
6185 serialize_shorthand => sub {
6186 my $self = shift;
6187
6188 my @v;
6189 push @v, $self->border_top_width;
6190 my $i = $self->get_property_priority ('border-top-width');
6191 return {} unless length $v[-1];
6192 push @v, $self->border_right_width;
6193 return {} unless length $v[-1];
6194 return {} unless $i eq $self->get_property_priority ('border-right-width');
6195 push @v, $self->border_bottom_width;
6196 return {} unless length $v[-1];
6197 return {} unless $i eq $self->get_property_priority ('border-bottom-width');
6198 push @v, $self->border_left_width;
6199 return {} unless length $v[-1];
6200 return {} unless $i eq $self->get_property_priority ('border-left-width');
6201
6202 my $v = 0;
6203 for (0..3) {
6204 $v++ if $v[$_] eq 'inherit';
6205 }
6206 if ($v == 4) {
6207 return {'border-width' => ['inherit', $i]};
6208 } elsif ($v) {
6209 return {};
6210 }
6211
6212 pop @v if $v[1] eq $v[3];
6213 pop @v if $v[0] eq $v[2];
6214 pop @v if $v[0] eq $v[1];
6215 return {'border-width' => [(join ' ', @v), $i]};
6216 },
6217 serialize_multiple => $Prop->{'border-top-color'}->{serialize_multiple},
6218 };
6219 $Attr->{border_width} = $Prop->{'border-width'};
6220
6221 $Prop->{'list-style'} = {
6222 css => 'list-style',
6223 dom => 'list_style',
6224 parse => sub {
6225 my ($self, $prop_name, $tt, $t, $onerror) = @_;
6226
6227 my %prop_value;
6228 my $none = 0;
6229
6230 F: for my $f (1..3) {
6231 if ($t->{type} == IDENT_TOKEN) {
6232 my $prop_value = lc $t->{value}; ## TODO: case folding
6233 $t = $tt->get_next_token;
6234
6235 if ($prop_value eq 'none') {
6236 $none++;
6237 } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
6238 if (exists $prop_value{'list-style-type'}) {
6239 $onerror->(type => 'CSS duplication', text => "'list-style-type'",
6240 level => $self->{level}->{must},
6241 uri => \$self->{href},
6242 token => $t);
6243 return ($t, undef);
6244 } else {
6245 $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
6246 }
6247 } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
6248 if (exists $prop_value{'list-style-position'}) {
6249 $onerror->(type => 'CSS duplication',
6250 text => "'list-style-position'",
6251 level => $self->{level}->{must},
6252 uri => \$self->{href},
6253 token => $t);
6254 return ($t, undef);
6255 }
6256
6257 $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
6258 } elsif ($f == 1 and $prop_value eq 'inherit') {
6259 $prop_value{'list-style-type'} = ["INHERIT"];
6260 $prop_value{'list-style-position'} = ["INHERIT"];
6261 $prop_value{'list-style-image'} = ["INHERIT"];
6262 last F;
6263 } else {
6264 if ($f == 1) {
6265 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6266 level => $self->{level}->{must},
6267 uri => \$self->{href},
6268 token => $t);
6269 return ($t, undef);
6270 } else {
6271 last F;
6272 }
6273 }
6274 } elsif ($t->{type} == URI_TOKEN) {
6275 if (exists $prop_value{'list-style-image'}) {
6276 $onerror->(type => 'CSS duplication', text => "'list-style-image'",
6277 uri => \$self->{href},
6278 level => $self->{level}->{must},
6279 token => $t);
6280 return ($t, undef);
6281 }
6282
6283 $prop_value{'list-style-image'}
6284 = ['URI', $t->{value}, \($self->{base_uri})];
6285 $t = $tt->get_next_token;
6286 } else {
6287 if ($f == 1) {
6288 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6289 level => $self->{level}->{must},
6290 uri => \$self->{href},
6291 token => $t);
6292 return ($t, undef);
6293 } else {
6294 last F;
6295 }
6296 }
6297
6298 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6299 } # F
6300 ## NOTE: No browser support |list-style: url(xxx|{EOF}.
6301
6302 if ($none == 1) {
6303 if (exists $prop_value{'list-style-type'}) {
6304 if (exists $prop_value{'list-style-image'}) {
6305 $onerror->(type => 'CSS duplication', text => "'list-style-image'",
6306 uri => \$self->{href},
6307 level => $self->{level}->{must},
6308 token => $t);
6309 return ($t, undef);
6310 } else {
6311 $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
6312 }
6313 } else {
6314 $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
6315 $prop_value{'list-style-image'} = ['KEYWORD', 'none']
6316 unless exists $prop_value{'list-style-image'};
6317 }
6318 } elsif ($none == 2) {
6319 if (exists $prop_value{'list-style-type'}) {
6320 $onerror->(type => 'CSS duplication', text => "'list-style-type'",
6321 uri => \$self->{href},
6322 level => $self->{level}->{must},
6323 token => $t);
6324 return ($t, undef);
6325 }
6326 if (exists $prop_value{'list-style-image'}) {
6327 $onerror->(type => 'CSS duplication', text => "'list-style-image'",
6328 uri => \$self->{href},
6329 level => $self->{level}->{must},
6330 token => $t);
6331 return ($t, undef);
6332 }
6333
6334 $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
6335 $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
6336 } elsif ($none == 3) {
6337 $onerror->(type => 'CSS duplication', text => "'list-style-type'",
6338 uri => \$self->{href},
6339 level => $self->{level}->{must},
6340 token => $t);
6341 return ($t, undef);
6342 }
6343
6344 for (qw/list-style-type list-style-position list-style-image/) {
6345 $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
6346 }
6347
6348 return ($t, \%prop_value);
6349 },
6350 ## NOTE: We don't merge longhands in |css_text| serialization,
6351 ## since no browser does.
6352 serialize_shorthand => sub {
6353 my $self = shift;
6354
6355 ## NOTE: Don't omit any value even if it is the initial value,
6356 ## since WinIE is buggy.
6357
6358 my $type = $self->list_style_type;
6359 return {} unless length $type;
6360 my $type_i = $self->get_property_priority ('list-style-type');
6361 my $image = $self->list_style_image;
6362 return {} unless length $image;
6363 my $image_i = $self->get_property_priority ('list-style-image');
6364 return {} unless $type_i eq $image_i;
6365 my $position = $self->list_style_position;
6366 return {} unless length $position;
6367 my $position_i = $self->get_property_priority ('list-style-position');
6368 return {} unless $type_i eq $position_i;
6369
6370 return {'list-style' => [$type . ' ' . $image . ' ' . $position, $type_i]};
6371 },
6372 };
6373 $Attr->{list_style} = $Prop->{'list-style'};
6374
6375 ## NOTE: Future version of the implementation will change the way to
6376 ## store the parsed value to support CSS 3 properties.
6377 $Prop->{'text-decoration'} = {
6378 css => 'text-decoration',
6379 dom => 'text_decoration',
6380 key => 'text_decoration',
6381 parse => sub {
6382 my ($self, $prop_name, $tt, $t, $onerror) = @_;
6383
6384 my $value = ['DECORATION']; # , underline, overline, line-through, blink
6385
6386 if ($t->{type} == IDENT_TOKEN) {
6387 my $v = lc $t->{value}; ## TODO: case
6388 $t = $tt->get_next_token;
6389 if ($v eq 'inherit') {
6390 return ($t, {$prop_name => ['INHERIT']});
6391 } elsif ($v eq 'none') {
6392 return ($t, {$prop_name => $value});
6393 } elsif ($v eq 'underline' and
6394 $self->{prop_value}->{$prop_name}->{$v}) {
6395 $value->[1] = 1;
6396 } elsif ($v eq 'overline' and
6397 $self->{prop_value}->{$prop_name}->{$v}) {
6398 $value->[2] = 1;
6399 } elsif ($v eq 'line-through' and
6400 $self->{prop_value}->{$prop_name}->{$v}) {
6401 $value->[3] = 1;
6402 } elsif ($v eq 'blink' and
6403 $self->{prop_value}->{$prop_name}->{$v}) {
6404 $value->[4] = 1;
6405 } else {
6406 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6407 level => $self->{level}->{must},
6408 uri => \$self->{href},
6409 token => $t);
6410 return ($t, undef);
6411 }
6412 }
6413
6414 F: {
6415 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6416 last F unless $t->{type} == IDENT_TOKEN;
6417
6418 my $v = lc $t->{value}; ## TODO: case
6419 $t = $tt->get_next_token;
6420 if ($v eq 'underline' and
6421 $self->{prop_value}->{$prop_name}->{$v}) {
6422 $value->[1] = 1;
6423 } elsif ($v eq 'overline' and
6424 $self->{prop_value}->{$prop_name}->{$v}) {
6425 $value->[1] = 2;
6426 } elsif ($v eq 'line-through' and
6427 $self->{prop_value}->{$prop_name}->{$v}) {
6428 $value->[1] = 3;
6429 } elsif ($v eq 'blink' and
6430 $self->{prop_value}->{$prop_name}->{$v}) {
6431 $value->[1] = 4;
6432 } else {
6433 last F;
6434 }
6435
6436 redo F;
6437 } # F
6438
6439 return ($t, {$prop_name => $value});
6440 },
6441 initial => ["KEYWORD", "none"],
6442 #inherited => 0,
6443 compute => $compute_as_specified,
6444 };
6445 $Attr->{text_decoration} = $Prop->{'text-decoration'};
6446 $Key->{text_decoration} = $Prop->{'text-decoration'};
6447
6448 $Attr->{quotes} =
6449 $Key->{quotes} =
6450 $Prop->{quotes} = {
6451 css => 'quotes',
6452 dom => 'quotes',
6453 key => 'quotes',
6454 parse => sub {
6455 my ($self, $prop_name, $tt, $t, $onerror) = @_;
6456
6457 my @v;
6458 A: {
6459 if ($t->{type} == STRING_TOKEN) {
6460 my $open = $t->{value};
6461 $t = $tt->get_next_token;
6462
6463 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6464 if ($t->{type} == STRING_TOKEN) {
6465 push @v, [$open, $t->{value}];
6466 $t = $tt->get_next_token;
6467 } else {
6468 last A;
6469 }
6470 } elsif (not @v and $t->{type} == IDENT_TOKEN) {
6471 my $value = lc $t->{value}; ## TODO: case
6472 if ($value eq 'none' or $value eq '-manakai-default') {
6473 $t = $tt->get_next_token;
6474 return ($t, {$prop_name => ['KEYWORD', $value]});
6475 } elsif ($value eq 'inherit') {
6476 $t = $tt->get_next_token;
6477 return ($t, {$prop_name => ['INHERIT']});
6478 } else {
6479 last A;
6480 }
6481 } else {
6482 if (@v) {
6483 return ($t, {$prop_name => ['QUOTES', \@v]});
6484 } else {
6485 last A;
6486 }
6487 }
6488
6489 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6490 redo A;
6491 }
6492
6493 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6494 level => $self->{level}->{must},
6495 uri => \$self->{href},
6496 token => $t);
6497 return ($t, undef);
6498 },
6499 initial => ['KEYWORD', '-manakai-default'],
6500 inherited => 1,
6501 compute => $compute_as_specified,
6502 };
6503
6504 $Attr->{content} =
6505 $Key->{content} =
6506 $Prop->{content} = {
6507 css => 'content',
6508 dom => 'content',
6509 key => 'content',
6510 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/content>.
6511 parse => sub {
6512 my ($self, $prop_name, $tt, $t, $onerror) = @_;
6513
6514 if ($t->{type} == IDENT_TOKEN) {
6515 my $value = lc $t->{value}; ## TODO: case
6516 if ($value eq 'normal' or $value eq 'none') {
6517 $t = $tt->get_next_token;
6518 return ($t, {$prop_name => ['KEYWORD', $value]});
6519 } elsif ($value eq 'inherit') {
6520 $t = $tt->get_next_token;
6521 return ($t, {$prop_name => ['INHERIT']});
6522 }
6523 }
6524
6525 my @v;
6526 A: {
6527 if ($t->{type} == IDENT_TOKEN) {
6528 my $value = lc $t->{value}; ## TODO: case
6529 if ({qw/open-quote 1 close-quote 1
6530 no-open-quote 1 no-close-quote 1/}->{$value} and
6531 $self->{prop}->{quotes}) {
6532 push @v, ['KEYWORD', $value];
6533 $t = $tt->get_next_token;
6534 } else {
6535 last A;
6536 }
6537 } elsif ($t->{type} == STRING_TOKEN) {
6538 push @v, ['STRING', $t->{value}];
6539 $t = $tt->get_next_token;
6540 } elsif ($t->{type} == URI_TOKEN) {
6541 push @v, ['URI', $t->{value}, \($self->{base_uri})];
6542 $t = $tt->get_next_token;
6543 } elsif ($t->{type} == FUNCTION_TOKEN) {
6544 my $name = lc $t->{value}; ## TODO: case
6545 if ($name eq 'attr') {
6546 $t = $tt->get_next_token;
6547 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6548 if ($t->{type} == IDENT_TOKEN) {
6549 my $t_pfx;
6550 my $t_ln = $t;
6551 $t = $tt->get_next_token;
6552 if ($t->{type} == VBAR_TOKEN) {
6553 $t = $tt->get_next_token;
6554 if ($t->{type} == IDENT_TOKEN) {
6555 $t_pfx = $t_ln;
6556 $t_ln = $t;
6557 $t = $tt->get_next_token;
6558 } else {
6559 last A;
6560 }
6561 }
6562
6563 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6564 if ($t->{type} == RPAREN_TOKEN) {
6565 if (defined $t_pfx) {
6566 my $pfx = $t_pfx->{value};
6567 my $uri = $self->{lookup_namespace_uri}->($pfx);
6568 unless (defined $uri) {
6569 $self->{onerror}->(type => 'namespace prefix:not declared',
6570 level => $self->{level}->{must},
6571 uri => \$self->{href},
6572 token => $t_pfx,
6573 value => $pfx);
6574 return ($t, undef);
6575 }
6576 push @v, ['ATTR', $uri, $t_ln->{value}];
6577 } else {
6578 push @v, ['ATTR', undef, $t_ln->{value}];
6579 }
6580 $t = $tt->get_next_token;
6581 } else {
6582 last A;
6583 }
6584 } elsif ($t->{type} == VBAR_TOKEN) {
6585 $t = $tt->get_next_token;
6586 my $t_ln;
6587 if ($t->{type} == IDENT_TOKEN) {
6588 $t_ln = $t;
6589 $t = $tt->get_next_token;
6590 } else {
6591 last A;
6592 }
6593
6594 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6595 if ($t->{type} == RPAREN_TOKEN) {
6596 push @v, ['ATTR', undef, $t_ln->{value}];
6597 $t = $tt->get_next_token;
6598 } else {
6599 last A;
6600 }
6601 } else {
6602 last A;
6603 }
6604 } elsif (($name eq 'counter' or $name eq 'counters') and
6605 $self->{prop}->{'counter-reset'}) {
6606 $t = $tt->get_next_token;
6607 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6608 if ($t->{type} == IDENT_TOKEN) {
6609 my $t_id = $t;
6610 my $t_str;
6611 my $type;
6612 $t = $tt->get_next_token;
6613 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6614 if ($t->{type} == COMMA_TOKEN) {
6615 $t = $tt->get_next_token;
6616 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6617 if ($name eq 'counters' and $t->{type} == STRING_TOKEN) {
6618 $t_str = $t;
6619 $t = $tt->get_next_token;
6620 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6621 if ($t->{type} == COMMA_TOKEN) {
6622 $t = $tt->get_next_token;
6623 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6624 if ($t->{type} == IDENT_TOKEN) {
6625 $type = lc $t->{value}; ## TODO: value
6626 if ($Prop->{'list-style-type'}->{keyword}->{$type}) {
6627 $t = $tt->get_next_token;
6628 } else {
6629 last A;
6630 }
6631 } else {
6632 last A;
6633 }
6634 }
6635 } elsif ($name eq 'counter' and $t->{type} == IDENT_TOKEN) {
6636 $type = lc $t->{value}; ## TODO: value
6637 if ($Prop->{'list-style-type'}->{keyword}->{$type}) {
6638 $t = $tt->get_next_token;
6639 } else {
6640 last A;
6641 }
6642 } else {
6643 last A;
6644 }
6645 } elsif ($name eq 'counters') {
6646 last A;
6647 }
6648 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6649 if ($t->{type} == RPAREN_TOKEN) {
6650 push @v, [uc $name, ## |COUNTER| or |COUNTERS|
6651 $t_id->{value},
6652 defined $t_str ? $t_str->{value} : undef,
6653 defined $type ? $type : 'decimal'];
6654 $t = $tt->get_next_token;
6655 } else {
6656 last A;
6657 }
6658 } else {
6659 last A;
6660 }
6661 } else {
6662 last A;
6663 }
6664 } else {
6665 unshift @v, 'CONTENT';
6666 return ($t, {$prop_name => \@v});
6667 }
6668
6669 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6670 redo A;
6671 } # A
6672
6673 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6674 level => $self->{level}->{must},
6675 uri => \$self->{href},
6676 token => $t);
6677 return ($t, undef);
6678 },
6679 initial => ['KEYWORD', 'normal'],
6680 #inherited => 0,
6681 compute => $compute_as_specified,
6682 ## NOTE: This is what Opera 9 does, except for 'normal' -> 'none'.
6683 ## TODO: 'normal' -> 'none' for ::before and ::after [CSS 2.1]
6684 };
6685
6686 $Attr->{counter_reset} =
6687 $Key->{counter_reset} =
6688 $Prop->{'counter-reset'} = {
6689 css => 'counter-reset',
6690 dom => 'counter_reset',
6691 key => 'counter_reset',
6692 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/counter-reset>.
6693 parse => sub {
6694 my ($self, $prop_name, $tt, $t, $onerror) = @_;
6695
6696 ## NOTE: For 'counter-increment' and 'counter-reset'.
6697
6698 my @v = ($prop_name eq 'counter-increment' ? 'ADDCOUNTER' : 'SETCOUNTER');
6699 B: {
6700 if ($t->{type} == IDENT_TOKEN) {
6701 my $value = $t->{value};
6702 my $lcvalue = lc $value; ## TODO: case
6703 last B if $lcvalue ne 'inherit' and $lcvalue ne 'none';
6704
6705 $t = $tt->get_next_token;
6706 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6707 if ($t->{type} == IDENT_TOKEN) {
6708 push @v, [$value, $prop_name eq 'counter-increment' ? 1 : 0];
6709 } elsif ($t->{type} == NUMBER_TOKEN) {
6710 push @v, [$value, int $t->{number}];
6711 $t = $tt->get_next_token;
6712 } elsif ($t->{type} == PLUS_TOKEN) {
6713 $t = $tt->get_next_token;
6714 if ($t->{type} == NUMBER_TOKEN) {
6715 push @v, [$value, int $t->{number}];
6716 $t = $tt->get_next_token;
6717 } else {
6718 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6719 level => $self->{level}->{must},
6720 uri => \$self->{href},
6721 token => $t);
6722 return ($t, undef);
6723 }
6724 } elsif ($t->{type} == MINUS_TOKEN) {
6725 $t = $tt->get_next_token;
6726 if ($t->{type} == NUMBER_TOKEN) {
6727 push @v, [$value, -int $t->{number}];
6728 $t = $tt->get_next_token;
6729 } else {
6730 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6731 level => $self->{level}->{must},
6732 uri => \$self->{href},
6733 token => $t);
6734 return ($t, undef);
6735 }
6736 } else {
6737 if ($lcvalue eq 'none') {
6738 return ($t, {$prop_name => ['KEYWORD', $lcvalue]});
6739 } elsif ($lcvalue eq 'inherit') {
6740 return ($t, {$prop_name => ['INHERIT']});
6741 } else {
6742 last B;
6743 }
6744 }
6745 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6746 } else {
6747 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6748 level => $self->{level}->{must},
6749 uri => \$self->{href},
6750 token => $t);
6751 return ($t, undef);
6752 }
6753 } # B
6754
6755 A: {
6756 if ($t->{type} == IDENT_TOKEN) {
6757 my $value = $t->{value};
6758 $t = $tt->get_next_token;
6759 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6760 if ($t->{type} == NUMBER_TOKEN) {
6761 push @v, [$value, int $t->{number}];
6762 $t = $tt->get_next_token;
6763 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6764 } elsif ($t->{type} == MINUS_TOKEN) {
6765 $t = $tt->get_next_token;
6766 if ($t->{type} == NUMBER_TOKEN) {
6767 push @v, [$value, -int $t->{number}];
6768 $t = $tt->get_next_token;
6769 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6770 } else {
6771 last A;
6772 }
6773 } elsif ($t->{type} == PLUS_TOKEN) {
6774 $t = $tt->get_next_token;
6775 if ($t->{type} == NUMBER_TOKEN) {
6776 push @v, [$value, int $t->{number}];
6777 $t = $tt->get_next_token;
6778 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6779 } else {
6780 last A;
6781 }
6782 } else {
6783 push @v, [$value, $prop_name eq 'counter-increment' ? 1 : 0];
6784 }
6785 redo A;
6786 } else {
6787 return ($t, {$prop_name => \@v});
6788 }
6789 } # A
6790
6791 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6792 level => $self->{level}->{must},
6793 uri => \$self->{href},
6794 token => $t);
6795 return ($t, undef);
6796 },
6797 initial => ['KEYWORD', 'none'],
6798 #inherited => 0,
6799 compute => $compute_as_specified,
6800 };
6801
6802 $Attr->{counter_increment} =
6803 $Key->{counter_increment} =
6804 $Prop->{'counter-increment'} = {
6805 css => 'counter-increment',
6806 dom => 'counter_increment',
6807 key => 'counter_increment',
6808 parse => $Prop->{'counter-reset'}->{parse},
6809 initial => ['KEYWORD', 'none'],
6810 #inherited => 0,
6811 compute => $compute_as_specified,
6812 };
6813
6814 $Attr->{clip} =
6815 $Key->{clip} =
6816 $Prop->{clip} = {
6817 css => 'clip',
6818 dom => 'clip',
6819 key => 'clip',
6820 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/clip>.
6821 parse => sub {
6822 my ($self, $prop_name, $tt, $t, $onerror) = @_;
6823
6824 if ($t->{type} == FUNCTION_TOKEN) {
6825 my $value = lc $t->{value}; ## TODO: case
6826 if ($value eq 'rect') {
6827 $t = $tt->get_next_token;
6828 my $prop_value = ['RECT'];
6829
6830 A: {
6831 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6832
6833 my $has_sign;
6834 my $sign = 1;
6835 if ($t->{type} == MINUS_TOKEN) {
6836 $sign = -1;
6837 $has_sign = 1;
6838 $t = $tt->get_next_token;
6839 } elsif ($t->{type} == PLUS_TOKEN) {
6840 $has_sign = 1;
6841 $t = $tt->get_next_token;
6842 }
6843 if ($t->{type} == DIMENSION_TOKEN) {
6844 my $value = $t->{number} * $sign;
6845 my $unit = lc $t->{value}; ## TODO: case
6846 if ($length_unit->{$unit}) {
6847 $t = $tt->get_next_token;
6848 push @$prop_value, ['DIMENSION', $value, $unit];
6849 } else {
6850 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6851 level => $self->{level}->{must},
6852 uri => \$self->{href},
6853 token => $t);
6854 return ($t, undef);
6855 }
6856 } elsif ($t->{type} == NUMBER_TOKEN and
6857 ($self->{unitless_px} or $t->{number} == 0)) {
6858 my $value = $t->{number} * $sign;
6859 $t = $tt->get_next_token;
6860 push @$prop_value, ['DIMENSION', $value, 'px'];
6861 } elsif (not $has_sign and $t->{type} == IDENT_TOKEN) {
6862 my $value = lc $t->{value}; ## TODO: case
6863 if ($value eq 'auto') {
6864 push @$prop_value, ['KEYWORD', 'auto'];
6865 $t = $tt->get_next_token;
6866 } else {
6867 last A;
6868 }
6869 } else {
6870 if ($has_sign) {
6871 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6872 level => $self->{level}->{must},
6873 uri => \$self->{href},
6874 token => $t);
6875 return ($t, undef);
6876 } else {
6877 last A;
6878 }
6879 }
6880
6881 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6882 if ($#$prop_value == 4) {
6883 if ($t->{type} == RPAREN_TOKEN) {
6884 $t = $tt->get_next_token;
6885 return ($t, {$prop_name => $prop_value});
6886 } else {
6887 last A;
6888 }
6889 } else {
6890 $t = $tt->get_next_token if $t->{type} == COMMA_TOKEN;
6891 redo A;
6892 }
6893 } # A
6894 }
6895 } elsif ($t->{type} == IDENT_TOKEN) {
6896 my $value = lc $t->{value}; ## TODO: case
6897 if ($value eq 'auto') {
6898 $t = $tt->get_next_token;
6899 return ($t, {$prop_name => ['KEYWORD', 'auto']});
6900 } elsif ($value eq 'inherit') {
6901 $t = $tt->get_next_token;
6902 return ($t, {$prop_name => ['INHERIT']});
6903 }
6904 }
6905
6906 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6907 level => $self->{level}->{must},
6908 uri => \$self->{href},
6909 token => $t);
6910 return ($t, undef);
6911 },
6912 initial => ['KEYWORD', 'auto'],
6913 #inherited => 0,
6914 compute => sub {
6915 my ($self, $element, $prop_name, $specified_value) = @_;
6916
6917 if (defined $specified_value and $specified_value->[0] eq 'RECT') {
6918 my $v = ['RECT'];
6919 for (@$specified_value[1..4]) {
6920 push @$v, $compute_length->($self, $element, $prop_name, $_);
6921 }
6922 return $v;
6923 }
6924
6925 return $specified_value;
6926 },
6927 };
6928
6929 $Attr->{marks} =
6930 $Key->{marks} =
6931 $Prop->{marks} = {
6932 css => 'marks',
6933 dom => 'marks',
6934 key => 'marks',
6935 parse => sub {
6936 my ($self, $prop_name, $tt, $t, $onerror) = @_;
6937
6938 if ($t->{type} == IDENT_TOKEN) {
6939 my $value = lc $t->{value}; ## TODO: case
6940 if ($value eq 'crop' and $self->{prop_value}->{$prop_name}->{$value}) {
6941 $t = $tt->get_next_token;
6942 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6943 if ($t->{type} == IDENT_TOKEN) {
6944 my $value = lc $t->{value}; ## TODO: case
6945 if ($value eq 'cross' and
6946 $self->{prop_value}->{$prop_name}->{$value}) {
6947 $t = $tt->get_next_token;
6948 return ($t, {$prop_name => ['MARKS', 1, 1]});
6949 }
6950 }
6951 return ($t, {$prop_name => ['MARKS', 1, 0]});
6952 } elsif ($value eq 'cross' and
6953 $self->{prop_value}->{$prop_name}->{$value}) {
6954 $t = $tt->get_next_token;
6955 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
6956 if ($t->{type} == IDENT_TOKEN) {
6957 my $value = lc $t->{value}; ## TODO: case
6958 if ($value eq 'crop' and
6959 $self->{prop_value}->{$prop_name}->{$value}) {
6960 $t = $tt->get_next_token;
6961 return ($t, {$prop_name => ['MARKS', 1, 1]});
6962 }
6963 }
6964 return ($t, {$prop_name => ['MARKS', 0, 1]});
6965 } elsif ($value eq 'none') {
6966 $t = $tt->get_next_token;
6967 return ($t, {$prop_name => ['MARKS']});
6968 } elsif ($value eq 'inherit') {
6969 $t = $tt->get_next_token;
6970 return ($t, {$prop_name => ['INHERIT']});
6971 }
6972 }
6973
6974 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
6975 level => $self->{level}->{must},
6976 uri => \$self->{href},
6977 token => $t);
6978 return ($t, undef);
6979 },
6980 initial => ['MARKS', 0, 0],
6981 #inherited => 0,
6982 compute => $compute_as_specified,
6983 };
6984
6985 $Attr->{size} =
6986 $Key->{size} =
6987 $Prop->{size} = {
6988 css => 'size',
6989 dom => 'size',
6990 key => 'size',
6991 parse => sub {
6992 my ($self, $prop_name, $tt, $t, $onerror) = @_;
6993
6994 if ($t->{type} == IDENT_TOKEN) {
6995 my $value = lc $t->{value}; ## TODO: case
6996 if ({
6997 auto => 1, portrait => 1, landscape => 1,
6998 }->{$value}) {
6999 $t = $tt->get_next_token;
7000 return ($t, {$prop_name => ['KEYWORD', $value]});
7001 } elsif ($value eq 'inherit') {
7002 $t = $tt->get_next_token;
7003 return ($t, {$prop_name => ['INHERIT']});
7004 }
7005 }
7006
7007 my $prop_value = ['SIZE'];
7008 A: {
7009 my $has_sign;
7010 my $sign = 1;
7011 if ($t->{type} == MINUS_TOKEN) {
7012 $has_sign = 1;
7013 $sign = -1;
7014 $t = $tt->get_next_token;
7015 } elsif ($t->{type} == PLUS_TOKEN) {
7016 $has_sign = 1;
7017 $t = $tt->get_next_token;
7018 }
7019
7020 if ($t->{type} == DIMENSION_TOKEN) {
7021 my $value = $t->{number} * $sign;
7022 my $unit = lc $t->{value}; ## TODO: case
7023 if ($length_unit->{$unit}) {
7024 $t = $tt->get_next_token;
7025 push @$prop_value, ['DIMENSION', $value, $unit];
7026 } else {
7027 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
7028 level => $self->{level}->{must},
7029 uri => \$self->{href},
7030 token => $t);
7031 return ($t, undef);
7032 }
7033 } elsif ($t->{type} == NUMBER_TOKEN and
7034 ($self->{unitless_px} or $t->{number} == 0)) {
7035 my $value = $t->{number} * $sign;
7036 $t = $tt->get_next_token;
7037 push @$prop_value, ['DIMENSION', $value, 'px'];
7038 } else {
7039 if (@$prop_value == 2) {
7040 $prop_value->[2] = $prop_value->[1];
7041 return ($t, {$prop_name => $prop_value});
7042 } else {
7043 last A;
7044 }
7045 }
7046
7047 if (@$prop_value == 3) {
7048 return ($t, {$prop_name => $prop_value});
7049 } else {
7050 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
7051 redo A;
7052 }
7053 } # A
7054
7055 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
7056 level => $self->{level}->{must},
7057 uri => \$self->{href},
7058 token => $t);
7059 return ($t, undef);
7060 },
7061 initial => ['KEYWORD', 'auto'],
7062 #inherited => 0,
7063 compute => sub {
7064 my ($self, $element, $prop_name, $specified_value) = @_;
7065
7066 if (defined $specified_value and $specified_value->[0] eq 'SIZE') {
7067 my $v = ['SIZE'];
7068 for (@$specified_value[1..2]) {
7069 push @$v, $compute_length->($self, $element, $prop_name, $_);
7070 }
7071 return $v;
7072 }
7073
7074 return $specified_value;
7075 },
7076 };
7077
7078 $Attr->{page} =
7079 $Key->{page} =
7080 $Prop->{page} = {
7081 css => 'page',
7082 dom => 'page',
7083 key => 'page',
7084 parse => sub {
7085 my ($self, $prop_name, $tt, $t, $onerror) = @_;
7086
7087 if ($t->{type} == IDENT_TOKEN) {
7088 my $value = lc $t->{value}; ## TODO: case
7089 if ($value eq 'auto') {
7090 $t = $tt->get_next_token;
7091 return ($t, {$prop_name => ['KEYWORD', 'auto']});
7092 } else {
7093 $value = $t->{value};
7094 $t = $tt->get_next_token;
7095 return ($t, {$prop_name => ['PAGE', $value]});
7096 }
7097 }
7098
7099 $onerror->(type => 'CSS syntax error', text => qq['$prop_name'],
7100 level => $self->{level}->{must},
7101 uri => \$self->{href},
7102 token => $t);
7103 return ($t, undef);
7104 },
7105 initial => ['KEYWORD', 'auto'],
7106 inherited => 1,
7107 compute => $compute_as_specified,
7108 };
7109
7110 1;
7111 ## $Date: 2008/09/17 03:55:15 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24