/[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.53 - (show annotations) (download)
Sun Jan 27 08:09:12 2008 UTC (18 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.52: +44 -32 lines
++ whatpm/t/ChangeLog	27 Jan 2008 08:09:01 -0000
	* CSS-Parser-1.t: Files |css-table.dat| and |css-interactive.dat|
	are added.

	* css-table.dat: New test file.

	* css-interactive.dat: New test file.

	* css-font.dat: New test data for 'font-size' are added.

	* css-text.dat: New test data for 'text-indent', 'letter-spacing',
	and 'word-spacing' are added.

2008-01-27  Wakaba  <wakaba@suika.fam.cx>

++ whatpm/Whatpm/CSS/ChangeLog	27 Jan 2008 08:07:41 -0000
	* Parser.pm ('letter-spacing' parse): Support for '+'.
	('border-spacing' serialize_multiple): Revised taking into
	account 'import' and 'inherit'.
	('border-spacing' parse): Support for '+'.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24