/[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.35 - (show annotations) (download)
Mon Jan 14 11:21:22 2008 UTC (18 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.34: +3 -3 lines
++ whatpm/Whatpm/CSS/ChangeLog	14 Jan 2008 11:21:19 -0000
	* Cascade.pm (get_cascaded_value): No longer have to test
	whether priority is defined.

	* Parser.pm (parse_char_string): Set an empty string as the priority
	if no priority was specified.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24