/[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.66 - (show annotations) (download)
Sun Feb 10 09:38:27 2008 UTC (18 years, 2 months ago) by wakaba
Branch: MAIN
Changes since 1.65: +24 -10 lines
++ whatpm/t/ChangeLog	10 Feb 2008 09:38:21 -0000
	* CSS-Parser-1.t (get_parser): Call |init|.

2008-02-10  Wakaba  <wakaba@suika.fam.cx>

++ whatpm/Whatpm/CSS/ChangeLog	10 Feb 2008 09:37:42 -0000
	* Parser.pm (parse_char_string): Create a style sheet
	before the actual parsing (or use the style sheet created before
	the invocation to the method.
	(init): New.

2008-02-10  Wakaba  <wakaba@suika.fam.cx>

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24