/[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.71 - (show annotations) (download)
Mon Sep 15 14:34:24 2008 UTC (17 years, 7 months ago) by wakaba
Branch: MAIN
Changes since 1.70: +27 -6 lines
++ whatpm/t/ChangeLog	15 Sep 2008 14:34:19 -0000
2008-09-15  Wakaba  <wakaba@suika.fam.cx>

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

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

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

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24