/[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.21 - (show annotations) (download)
Thu Jan 3 13:51:41 2008 UTC (18 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.20: +172 -1 lines
++ whatpm/Whatpm/CSS/ChangeLog	3 Jan 2008 13:51:32 -0000
	* Cascade.pm (get_specified_value_no_inherit): New function.
	(get_computed_value): New way to get computed value,
	the |{compute_multiple}| code, is now supported.

	* Parser.pm (top, bottom, left, right): Implemented.

2008-01-03  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
13 return $self;
14 } # new
15
16 sub BEFORE_STATEMENT_STATE () { 0 }
17 sub BEFORE_DECLARATION_STATE () { 1 }
18 sub IGNORED_STATEMENT_STATE () { 2 }
19 sub IGNORED_DECLARATION_STATE () { 3 }
20
21 our $Prop; ## By CSS property name
22 our $Attr; ## By CSSOM attribute name
23 our $Key; ## By internal key
24
25 sub parse_char_string ($$) {
26 my $self = $_[0];
27
28 my $s = $_[1];
29 pos ($s) = 0;
30 my $line = 1;
31 my $column = 0;
32
33 my $_onerror = $self->{onerror};
34 my $onerror = sub {
35 $_onerror->(@_, line => $line, column => $column);
36 };
37
38 my $tt = Whatpm::CSS::Tokenizer->new;
39 $tt->{onerror} = $onerror;
40 $tt->{get_char} = sub {
41 if (pos $s < length $s) {
42 my $c = ord substr $s, pos ($s)++, 1;
43 if ($c == 0x000A) {
44 $line++;
45 $column = 0;
46 } elsif ($c == 0x000D) {
47 unless (substr ($s, pos ($s), 1) eq "\x0A") {
48 $line++;
49 $column = 0;
50 } else {
51 $column++;
52 }
53 } else {
54 $column++;
55 }
56 return $c;
57 } else {
58 return -1;
59 }
60 }; # $tt->{get_char}
61 $tt->init;
62
63 my $sp = Whatpm::CSS::SelectorsParser->new;
64 $sp->{onerror} = $onerror;
65 $sp->{must_level} = $self->{must_level};
66 $sp->{pseudo_element} = $self->{pseudo_element};
67 $sp->{pseudo_class} = $self->{pseudo_class};
68
69 my $nsmap = {};
70 $sp->{lookup_namespace_uri} = sub {
71 return $nsmap->{$_[0]}; # $_[0] is '' (default namespace) or prefix
72 }; # $sp->{lookup_namespace_uri}
73
74 ## TODO: Supported pseudo classes and elements...
75
76 require Message::DOM::CSSStyleSheet;
77 require Message::DOM::CSSRule;
78 require Message::DOM::CSSStyleDeclaration;
79
80 $self->{base_uri} = $self->{href} unless defined $self->{base_uri};
81
82 my $state = BEFORE_STATEMENT_STATE;
83 my $t = $tt->get_next_token;
84
85 my $open_rules = [[]];
86 my $current_rules = $open_rules->[-1];
87 my $current_decls;
88 my $closing_tokens = [];
89 my $charset_allowed = 1;
90 my $namespace_allowed = 1;
91
92 S: {
93 if ($state == BEFORE_STATEMENT_STATE) {
94 $t = $tt->get_next_token
95 while $t->{type} == S_TOKEN or
96 $t->{type} == CDO_TOKEN or
97 $t->{type} == CDC_TOKEN;
98
99 if ($t->{type} == ATKEYWORD_TOKEN) {
100 if (lc $t->{value} eq 'namespace') { ## TODO: case folding
101 $t = $tt->get_next_token;
102 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
103
104 my $prefix;
105 if ($t->{type} == IDENT_TOKEN) {
106 $prefix = lc $t->{value};
107 ## TODO: Unicode lowercase
108
109 $t = $tt->get_next_token;
110 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
111 }
112
113 if ($t->{type} == STRING_TOKEN or $t->{type} == URI_TOKEN) {
114 my $uri = $t->{value};
115
116 $t = $tt->get_next_token;
117 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
118
119 ## ISSUE: On handling of empty namespace URI, Firefox 2 and
120 ## Opera 9 work differently (See SuikaWiki:namespace).
121 ## TODO: We need to check what we do once it is specced.
122
123 if ($t->{type} == SEMICOLON_TOKEN) {
124 if ($namespace_allowed) {
125 $nsmap->{defined $prefix ? $prefix : ''} = $uri;
126 push @$current_rules,
127 Message::DOM::CSSNamespaceRule->____new ($prefix, $uri);
128 undef $charset_allowed;
129 } else {
130 $onerror->(type => 'at:namespace:not allowed',
131 level => $self->{must_level},
132 token => $t);
133 }
134
135 $t = $tt->get_next_token;
136 ## Stay in the state.
137 redo S;
138 } else {
139 #
140 }
141 } else {
142 #
143 }
144
145 $onerror->(type => 'syntax error:at:namespace',
146 level => $self->{must_level},
147 token => $t);
148 #
149 } elsif (lc $t->{value} eq 'charset') { ## TODO: case folding
150 $t = $tt->get_next_token;
151 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
152
153 if ($t->{type} == STRING_TOKEN) {
154 my $encoding = $t->{value};
155
156 $t = $tt->get_next_token;
157 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
158
159 if ($t->{type} == SEMICOLON_TOKEN) {
160 if ($charset_allowed) {
161 push @$current_rules,
162 Message::DOM::CSSCharsetRule->____new ($encoding);
163 undef $charset_allowed;
164 } else {
165 $onerror->(type => 'at:charset:not allowed',
166 level => $self->{must_level},
167 token => $t);
168 }
169
170 ## TODO: Detect the conformance errors for @charset...
171
172 $t = $tt->get_next_token;
173 ## Stay in the state.
174 redo S;
175 } else {
176 #
177 }
178 } else {
179 #
180 }
181
182 $onerror->(type => 'syntax error:at:charset',
183 level => $self->{must_level},
184 token => $t);
185 #
186 ## NOTE: When adding support for new at-rule, insert code
187 ## "undef $charset_allowed" and "undef $namespace_token" as
188 ## appropriate.
189 } else {
190 $onerror->(type => 'not supported:at:'.$t->{value},
191 level => $self->{unsupported_level},
192 token => $t);
193 }
194
195 $t = $tt->get_next_token;
196 $state = IGNORED_STATEMENT_STATE;
197 redo S;
198 } elsif (@$open_rules > 1 and $t->{type} == RBRACE_TOKEN) {
199 pop @$open_rules;
200 ## Stay in the state.
201 $t = $tt->get_next_token;
202 redo S;
203 } elsif ($t->{type} == EOF_TOKEN) {
204 if (@$open_rules > 1) {
205 $onerror->(type => 'syntax error:block not closed',
206 level => $self->{must_level},
207 token => $t);
208 }
209
210 last S;
211 } else {
212 undef $charset_allowed;
213 undef $namespace_allowed;
214
215 ($t, my $selectors) = $sp->_parse_selectors_with_tokenizer
216 ($tt, LBRACE_TOKEN, $t);
217
218 $t = $tt->get_next_token
219 while $t->{type} != LBRACE_TOKEN and $t->{type} != EOF_TOKEN;
220
221 if ($t->{type} == LBRACE_TOKEN) {
222 $current_decls = Message::DOM::CSSStyleDeclaration->____new;
223 my $rs = Message::DOM::CSSStyleRule->____new
224 ($selectors, $current_decls);
225 push @{$current_rules}, $rs if defined $selectors;
226
227 $state = BEFORE_DECLARATION_STATE;
228 $t = $tt->get_next_token;
229 redo S;
230 } else {
231 $onerror->(type => 'syntax error:after selectors',
232 level => $self->{must_level},
233 token => $t);
234
235 ## Stay in the state.
236 $t = $tt->get_next_token;
237 redo S;
238 }
239 }
240 } elsif ($state == BEFORE_DECLARATION_STATE) {
241 ## NOTE: DELIM? in declaration will be removed:
242 ## <http://csswg.inkedblade.net/spec/css2.1?s=declaration%20delim#issue-2>.
243
244 my $prop_def;
245 my $prop_value;
246 my $prop_flag;
247 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
248 if ($t->{type} == IDENT_TOKEN) { # property
249 my $prop_name = lc $t->{value}; ## TODO: case folding
250 $t = $tt->get_next_token;
251 if ($t->{type} == COLON_TOKEN) {
252 $t = $tt->get_next_token;
253 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
254
255 $prop_def = $Prop->{$prop_name};
256 if ($prop_def and $self->{prop}->{$prop_name}) {
257 ($t, $prop_value)
258 = $prop_def->{parse}->($self, $prop_name, $tt, $t, $onerror);
259 if ($prop_value) {
260 ## NOTE: {parse} don't have to consume trailing spaces.
261 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
262
263 if ($t->{type} == EXCLAMATION_TOKEN) {
264 $t = $tt->get_next_token;
265 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
266 if ($t->{type} == IDENT_TOKEN and
267 lc $t->{value} eq 'important') { ## TODO: case folding
268 $prop_flag = 'important';
269
270 $t = $tt->get_next_token;
271 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
272
273 #
274 } else {
275 $onerror->(type => 'syntax error:important',
276 level => $self->{must_level},
277 token => $t);
278
279 ## Reprocess.
280 $state = IGNORED_DECLARATION_STATE;
281 redo S;
282 }
283 }
284
285 #
286 } else {
287 ## Syntax error.
288
289 ## Reprocess.
290 $state = IGNORED_DECLARATION_STATE;
291 redo S;
292 }
293 } else {
294 $onerror->(type => 'not supported:property',
295 level => $self->{unsupported_level},
296 token => $t, value => $prop_name);
297
298 #
299 $state = IGNORED_DECLARATION_STATE;
300 redo S;
301 }
302 } else {
303 $onerror->(type => 'syntax error:property colon',
304 level => $self->{must_level},
305 token => $t);
306
307 #
308 $state = IGNORED_DECLARATION_STATE;
309 redo S;
310 }
311 }
312
313 if ($t->{type} == RBRACE_TOKEN) {
314 $t = $tt->get_next_token;
315 $state = BEFORE_STATEMENT_STATE;
316 #redo S;
317 } elsif ($t->{type} == SEMICOLON_TOKEN) {
318 $t = $tt->get_next_token;
319 ## Stay in the state.
320 #redo S;
321 } elsif ($t->{type} == EOF_TOKEN) {
322 $onerror->(type => 'syntax error:ruleset not closed',
323 level => $self->{must_level},
324 token => $t);
325 ## Reprocess.
326 $state = BEFORE_STATEMENT_STATE;
327 #redo S;
328 } else {
329 if ($prop_value) {
330 $onerror->(type => 'syntax error:property semicolon',
331 level => $self->{must_level},
332 token => $t);
333 } else {
334 $onerror->(type => 'syntax error:property name',
335 level => $self->{must_level},
336 token => $t);
337 }
338
339 #
340 $state = IGNORED_DECLARATION_STATE;
341 redo S;
342 }
343
344 my $important = (defined $prop_flag and $prop_flag eq 'important');
345 for my $set_prop_name (keys %{$prop_value or {}}) {
346 my $set_prop_def = $Prop->{$set_prop_name};
347 $$current_decls->{$set_prop_def->{key}}
348 = [$prop_value->{$set_prop_name}, $prop_flag]
349 if $important or
350 not $$current_decls->{$set_prop_def->{key}} or
351 not defined $$current_decls->{$set_prop_def->{key}}->[1];
352 }
353 redo S;
354 } elsif ($state == IGNORED_STATEMENT_STATE or
355 $state == IGNORED_DECLARATION_STATE) {
356 if (@$closing_tokens) { ## Something is yet in opening state.
357 if ($t->{type} == EOF_TOKEN) {
358 @$closing_tokens = ();
359 ## Reprocess.
360 $state = $state == IGNORED_STATEMENT_STATE
361 ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
362 redo S;
363 } elsif ($t->{type} == $closing_tokens->[-1]) {
364 pop @$closing_tokens;
365 if (@$closing_tokens == 0 and
366 $t->{type} == RBRACE_TOKEN and
367 $state == IGNORED_STATEMENT_STATE) {
368 $t = $tt->get_next_token;
369 $state = BEFORE_STATEMENT_STATE;
370 redo S;
371 } else {
372 $t = $tt->get_next_token;
373 ## Stay in the state.
374 redo S;
375 }
376 } else {
377 #
378 }
379 } else {
380 if ($t->{type} == SEMICOLON_TOKEN) {
381 $t = $tt->get_next_token;
382 $state = $state == IGNORED_STATEMENT_STATE
383 ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
384 redo S;
385 } elsif ($state == IGNORED_DECLARATION_STATE and
386 $t->{type} == RBRACE_TOKEN) {
387 $t = $tt->get_next_token;
388 $state = BEFORE_STATEMENT_STATE;
389 redo S;
390 } elsif ($t->{type} == EOF_TOKEN) {
391 ## Reprocess.
392 $state = $state == IGNORED_STATEMENT_STATE
393 ? BEFORE_STATEMENT_STATE : BEFORE_DECLARATION_STATE;
394 redo S;
395 } else {
396 #
397 }
398 }
399
400 while (not {
401 EOF_TOKEN, 1,
402 RBRACE_TOKEN, 1,
403 RBRACKET_TOKEN, 1,
404 RPAREN_TOKEN, 1,
405 SEMICOLON_TOKEN, 1,
406 }->{$t->{type}}) {
407 if ($t->{type} == LBRACE_TOKEN) {
408 push @$closing_tokens, RBRACE_TOKEN;
409 } elsif ($t->{type} == LBRACKET_TOKEN) {
410 push @$closing_tokens, RBRACKET_TOKEN;
411 } elsif ($t->{type} == LPAREN_TOKEN or $t->{type} == FUNCTION_TOKEN) {
412 push @$closing_tokens, RPAREN_TOKEN;
413 }
414
415 $t = $tt->get_next_token;
416 }
417
418 #
419 ## Stay in the state.
420 redo S;
421 } else {
422 die "$0: parse_char_string: Unknown state: $state";
423 }
424 } # S
425
426 my $ss = Message::DOM::CSSStyleSheet->____new
427 (manakai_base_uri => $self->{base_uri},
428 css_rules => $open_rules->[0],
429 ## TODO: href
430 ## TODO: owner_node
431 ## TODO: media
432 type => 'text/css', ## TODO: OK?
433 _parser => $self);
434 return $ss;
435 } # parse_char_string
436
437 my $compute_as_specified = sub ($$$$) {
438 #my ($self, $element, $prop_name, $specified_value) = @_;
439 return $_[3];
440 }; # $compute_as_specified
441
442 my $default_serializer = sub {
443 my ($self, $prop_name, $value) = @_;
444 if ($value->[0] eq 'NUMBER' or $value->[0] eq 'WEIGHT') {
445 ## TODO: What we currently do for 'font-weight' is different from
446 ## any browser for lighter/bolder cases. We need to fix this, but
447 ## how?
448 return $value->[1]; ## TODO: big or small number cases?
449 } elsif ($value->[0] eq 'DIMENSION') {
450 return $value->[1] . $value->[2]; ## NOTE: This is what browsers do.
451 } elsif ($value->[0] eq 'KEYWORD') {
452 return $value->[1];
453 } elsif ($value->[0] eq 'URI') {
454 ## NOTE: This is what browsers do.
455 return 'url('.$value->[1].')';
456 } elsif ($value->[0] eq 'INHERIT') {
457 return 'inherit';
458 } elsif ($value->[0] eq 'DECORATION') {
459 my @v = ();
460 push @v, 'underline' if $value->[1];
461 push @v, 'overline' if $value->[2];
462 push @v, 'line-through' if $value->[3];
463 push @v, 'blink' if $value->[4];
464 return 'none' unless @v;
465 return join ' ', @v;
466 } else {
467 return undef;
468 }
469 }; # $default_serializer
470
471 $Prop->{color} = {
472 css => 'color',
473 dom => 'color',
474 key => 'color',
475 parse => sub {
476 my ($self, $prop_name, $tt, $t, $onerror) = @_;
477
478 if ($t->{type} == IDENT_TOKEN) {
479 if (lc $t->{value} eq 'blue') { ## TODO: case folding
480 $t = $tt->get_next_token;
481 return ($t, {$prop_name => ["RGBA", 0, 0, 255, 1]});
482 } else {
483 #
484 }
485 } else {
486 #
487 }
488
489 $onerror->(type => 'syntax error:color',
490 level => $self->{must_level},
491 token => $t);
492
493 return ($t, undef);
494 },
495 serialize => sub {
496 my ($self, $prop_name, $value) = @_;
497 if ($value->[0] eq 'RGBA') { ## TODO: %d? %f?
498 return sprintf 'rgba(%d, %d, %d, %f)', @$value[1, 2, 3, 4];
499 } else {
500 return undef;
501 }
502 },
503 initial => ["KEYWORD", "-manakai-initial-color"], ## NOTE: UA-dependent in CSS 2.1.
504 inherited => 1,
505 compute => $compute_as_specified,
506 };
507 $Attr->{color} = $Prop->{color};
508 $Key->{color} = $Prop->{color};
509
510 my $one_keyword_parser = sub {
511 my ($self, $prop_name, $tt, $t, $onerror) = @_;
512
513 if ($t->{type} == IDENT_TOKEN) {
514 my $prop_value = lc $t->{value}; ## TODO: case folding
515 $t = $tt->get_next_token;
516 if ($Prop->{$prop_name}->{keyword}->{$prop_value} and
517 $self->{prop_value}->{$prop_name}->{$prop_value}) {
518 return ($t, {$prop_name => ["KEYWORD", $prop_value]});
519 } elsif ($prop_value eq 'inherit') {
520 return ($t, {$prop_name => ['INHERIT']});
521 }
522 }
523
524 $onerror->(type => 'syntax error:keyword:'.$prop_name,
525 level => $self->{must_level},
526 token => $t);
527 return ($t, undef);
528 };
529
530 $Prop->{display} = {
531 css => 'display',
532 dom => 'display',
533 key => 'display',
534 parse => $one_keyword_parser,
535 serialize => $default_serializer,
536 keyword => {
537 block => 1, inline => 1, 'inline-block' => 1, 'inline-table' => 1,
538 'list-item' => 1, none => 1,
539 table => 1, 'table-caption' => 1, 'table-cell' => 1, 'table-column' => 1,
540 'table-column-group' => 1, 'table-header-group' => 1,
541 'table-footer-group' => 1, 'table-row' => 1, 'table-row-group' => 1,
542 },
543 initial => ["KEYWORD", "inline"],
544 #inherited => 0,
545 compute => sub {
546 my ($self, $element, $prop_name, $specified_value) = @_;
547 ## NOTE: CSS 2.1 Section 9.7.
548
549 ## WARNING: |compute| for 'float' property invoke this CODE
550 ## in some case. Careless modification might cause a infinite loop.
551
552 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
553 if ($specified_value->[1] eq 'none') {
554 ## Case 1 [CSS 2.1]
555 return $specified_value;
556 } else {
557 my $position = $self->get_computed_value ($element, 'position');
558 if ($position->[0] eq 'KEYWORD' and
559 ($position->[1] eq 'absolute' or
560 $position->[1] eq 'fixed')) {
561 ## Case 2 [CSS 2.1]
562 #
563 } else {
564 my $float = $self->get_computed_value ($element, 'float');
565 if ($float->[0] eq 'KEYWORD' and $float->[1] ne 'none') {
566 ## Caes 3 [CSS 2.1]
567 #
568 } elsif (not defined $element->manakai_parent_element) {
569 ## Case 4 [CSS 2.1]
570 #
571 } else {
572 ## Case 5 [CSS 2.1]
573 return $specified_value;
574 }
575 }
576
577 return ["KEYWORD",
578 {
579 'inline-table' => 'table',
580 inline => 'block',
581 'run-in' => 'block',
582 'table-row-group' => 'block',
583 'table-column' => 'block',
584 'table-column-group' => 'block',
585 'table-header-group' => 'block',
586 'table-footer-group' => 'block',
587 'table-row' => 'block',
588 'table-cell' => 'block',
589 'table-caption' => 'block',
590 'inline-block' => 'block',
591 }->{$specified_value->[1]} || $specified_value->[1]];
592 }
593 } else {
594 return $specified_value; ## Maybe an error of the implementation.
595 }
596 },
597 };
598 $Attr->{display} = $Prop->{display};
599 $Key->{display} = $Prop->{display};
600
601 $Prop->{position} = {
602 css => 'position',
603 dom => 'position',
604 key => 'position',
605 parse => $one_keyword_parser,
606 serialize => $default_serializer,
607 keyword => {
608 static => 1, relative => 1, absolute => 1, fixed => 1,
609 },
610 initial => ["KEYWORD", "static"],
611 #inherited => 0,
612 compute => $compute_as_specified,
613 };
614 $Attr->{position} = $Prop->{position};
615 $Key->{position} = $Prop->{position};
616
617 $Prop->{float} = {
618 css => 'float',
619 dom => 'css_float',
620 key => 'float',
621 parse => $one_keyword_parser,
622 serialize => $default_serializer,
623 keyword => {
624 left => 1, right => 1, none => 1,
625 },
626 initial => ["KEYWORD", "none"],
627 #inherited => 0,
628 compute => sub {
629 my ($self, $element, $prop_name, $specified_value) = @_;
630 ## NOTE: CSS 2.1 Section 9.7.
631
632 ## WARNING: |compute| for 'display' property invoke this CODE
633 ## in some case. Careless modification might cause a infinite loop.
634
635 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
636 if ($specified_value->[1] eq 'none') {
637 ## Case 1 [CSS 2.1]
638 return $specified_value;
639 } else {
640 my $position = $self->get_computed_value ($element, 'position');
641 if ($position->[0] eq 'KEYWORD' and
642 ($position->[1] eq 'absolute' or
643 $position->[1] eq 'fixed')) {
644 ## Case 2 [CSS 2.1]
645 return ["KEYWORD", "none"];
646 }
647 }
648 }
649
650 ## ISSUE: CSS 2.1 section 9.7 and 9.5.1 ('float' definition) disagree
651 ## on computed value of 'float' property.
652
653 ## Case 3, 4, and 5 [CSS 2.1]
654 return $specified_value;
655 },
656 };
657 $Attr->{css_float} = $Prop->{float};
658 $Attr->{style_float} = $Prop->{float}; ## NOTE: IEism
659 $Key->{float} = $Prop->{float};
660
661 $Prop->{clear} = {
662 css => 'clear',
663 dom => 'clear',
664 key => 'clear',
665 parse => $one_keyword_parser,
666 serialize => $default_serializer,
667 keyword => {
668 left => 1, right => 1, none => 1, both => 1,
669 },
670 initial => ["KEYWORD", "none"],
671 #inherited => 0,
672 compute => $compute_as_specified,
673 };
674 $Attr->{clear} = $Prop->{clear};
675 $Key->{clear} = $Prop->{clear};
676
677 $Prop->{direction} = {
678 css => 'direction',
679 dom => 'direction',
680 key => 'direction',
681 parse => $one_keyword_parser,
682 serialize => $default_serializer,
683 keyword => {
684 ltr => 1, rtl => 1,
685 },
686 initial => ["KEYWORD", "ltr"],
687 inherited => 1,
688 compute => $compute_as_specified,
689 };
690 $Attr->{direction} = $Prop->{direction};
691 $Key->{direction} = $Prop->{direction};
692
693 $Prop->{'unicode-bidi'} = {
694 css => 'unicode-bidi',
695 dom => 'unicode_bidi',
696 key => 'unicode_bidi',
697 parse => $one_keyword_parser,
698 serialize => $default_serializer,
699 keyword => {
700 normal => 1, embed => 1, 'bidi-override' => 1,
701 },
702 initial => ["KEYWORD", "normal"],
703 #inherited => 0,
704 compute => $compute_as_specified,
705 };
706 $Attr->{unicode_bidi} = $Prop->{'unicode-bidi'};
707 $Key->{unicode_bidi} = $Prop->{'unicode-bidi'};
708
709 $Prop->{overflow} = {
710 css => 'overflow',
711 dom => 'overflow',
712 key => 'overflow',
713 parse => $one_keyword_parser,
714 serialize => $default_serializer,
715 keyword => {
716 visible => 1, hidden => 1, scroll => 1, auto => 1,
717 },
718 initial => ["KEYWORD", "visible"],
719 #inherited => 0,
720 compute => $compute_as_specified,
721 };
722 $Attr->{overflow} = $Prop->{overflow};
723 $Key->{overflow} = $Prop->{overflow};
724
725 $Prop->{visibility} = {
726 css => 'visibility',
727 dom => 'visibility',
728 key => 'visibility',
729 parse => $one_keyword_parser,
730 serialize => $default_serializer,
731 keyword => {
732 visible => 1, hidden => 1, collapse => 1,
733 },
734 initial => ["KEYWORD", "visible"],
735 #inherited => 0,
736 compute => $compute_as_specified,
737 };
738 $Attr->{visibility} = $Prop->{visibility};
739 $Key->{visibility} = $Prop->{visibility};
740
741 $Prop->{'list-style-type'} = {
742 css => 'list-style-type',
743 dom => 'list_style_type',
744 key => 'list_style_type',
745 parse => $one_keyword_parser,
746 serialize => $default_serializer,
747 keyword => {
748 qw/
749 disc 1 circle 1 square 1 decimal 1 decimal-leading-zero 1
750 lower-roman 1 upper-roman 1 lower-greek 1 lower-latin 1
751 upper-latin 1 armenian 1 georgian 1 lower-alpha 1 upper-alpha 1
752 none 1
753 /,
754 },
755 initial => ["KEYWORD", 'disc'],
756 inherited => 1,
757 compute => $compute_as_specified,
758 };
759 $Attr->{list_style_type} = $Prop->{'list-style-type'};
760 $Key->{list_style_type} = $Prop->{'list-style-type'};
761
762 $Prop->{'list-style-position'} = {
763 css => 'list-style-position',
764 dom => 'list_style_position',
765 key => 'list_style_position',
766 parse => $one_keyword_parser,
767 serialize => $default_serializer,
768 keyword => {
769 inside => 1, outside => 1,
770 },
771 initial => ["KEYWORD", 'outside'],
772 inherited => 1,
773 compute => $compute_as_specified,
774 };
775 $Attr->{list_style_position} = $Prop->{'list-style-position'};
776 $Key->{list_style_position} = $Prop->{'list-style-position'};
777
778 $Prop->{'page-break-before'} = {
779 css => 'page-break-before',
780 dom => 'page_break_before',
781 key => 'page_break_before',
782 parse => $one_keyword_parser,
783 serialize => $default_serializer,
784 keyword => {
785 auto => 1, always => 1, avoid => 1, left => 1, right => 1,
786 },
787 initial => ["KEYWORD", 'auto'],
788 #inherited => 0,
789 compute => $compute_as_specified,
790 };
791 $Attr->{page_break_before} = $Prop->{'page-break-before'};
792 $Key->{page_break_before} = $Prop->{'page-break-before'};
793
794 $Prop->{'page-break-after'} = {
795 css => 'page-break-after',
796 dom => 'page_break_after',
797 key => 'page_break_after',
798 parse => $one_keyword_parser,
799 serialize => $default_serializer,
800 keyword => {
801 auto => 1, always => 1, avoid => 1, left => 1, right => 1,
802 },
803 initial => ["KEYWORD", 'auto'],
804 #inherited => 0,
805 compute => $compute_as_specified,
806 };
807 $Attr->{page_break_after} = $Prop->{'page-break-after'};
808 $Key->{page_break_after} = $Prop->{'page-break-after'};
809
810 $Prop->{'page-break-inside'} = {
811 css => 'page-break-inside',
812 dom => 'page_break_inside',
813 key => 'page_break_inside',
814 parse => $one_keyword_parser,
815 serialize => $default_serializer,
816 keyword => {
817 auto => 1, avoid => 1,
818 },
819 initial => ["KEYWORD", 'auto'],
820 inherited => 1,
821 compute => $compute_as_specified,
822 };
823 $Attr->{page_break_inside} = $Prop->{'page-break-inside'};
824 $Key->{page_break_inside} = $Prop->{'page-break-inside'};
825
826 $Prop->{'background-repeat'} = {
827 css => 'background-repeat',
828 dom => 'background_repeat',
829 key => 'background_repeat',
830 parse => $one_keyword_parser,
831 serialize => $default_serializer,
832 keyword => {
833 repeat => 1, 'repeat-x' => 1, 'repeat-y' => 1, 'no-repeat' => 1,
834 },
835 initial => ["KEYWORD", 'repeat'],
836 #inherited => 0,
837 compute => $compute_as_specified,
838 };
839 $Attr->{background_repeat} = $Prop->{'background-repeat'};
840 $Key->{backgroud_repeat} = $Prop->{'background-repeat'};
841
842 $Prop->{'background-attachment'} = {
843 css => 'background-attachment',
844 dom => 'background_attachment',
845 key => 'background_attachment',
846 parse => $one_keyword_parser,
847 serialize => $default_serializer,
848 keyword => {
849 scroll => 1, fixed => 1,
850 },
851 initial => ["KEYWORD", 'scroll'],
852 #inherited => 0,
853 compute => $compute_as_specified,
854 };
855 $Attr->{background_attachment} = $Prop->{'background-attachment'};
856 $Key->{backgroud_attachment} = $Prop->{'background-attachment'};
857
858 $Prop->{'font-style'} = {
859 css => 'font-style',
860 dom => 'font_style',
861 key => 'font_style',
862 parse => $one_keyword_parser,
863 serialize => $default_serializer,
864 keyword => {
865 normal => 1, italic => 1, oblique => 1,
866 },
867 initial => ["KEYWORD", 'normal'],
868 inherited => 1,
869 compute => $compute_as_specified,
870 };
871 $Attr->{font_style} = $Prop->{'font-style'};
872 $Key->{font_style} = $Prop->{'font-style'};
873
874 $Prop->{'font-variant'} = {
875 css => 'font-variant',
876 dom => 'font_variant',
877 key => 'font_variant',
878 parse => $one_keyword_parser,
879 serialize => $default_serializer,
880 keyword => {
881 normal => 1, 'small-caps' => 1,
882 },
883 initial => ["KEYWORD", 'normal'],
884 inherited => 1,
885 compute => $compute_as_specified,
886 };
887 $Attr->{font_variant} = $Prop->{'font-variant'};
888 $Key->{font_variant} = $Prop->{'font-variant'};
889
890 $Prop->{'text-align'} = {
891 css => 'text-align',
892 dom => 'text_align',
893 key => 'text_align',
894 parse => $one_keyword_parser,
895 serialize => $default_serializer,
896 keyword => {
897 left => 1, right => 1, center => 1, justify => 1, ## CSS 2
898 begin => 1, end => 1, ## CSS 3
899 },
900 initial => ["KEYWORD", 'begin'],
901 inherited => 1,
902 compute => $compute_as_specified,
903 };
904 $Attr->{text_align} = $Prop->{'text-align'};
905 $Key->{text_align} = $Prop->{'text-align'};
906
907 $Prop->{'text-transform'} = {
908 css => 'text-transform',
909 dom => 'text_transform',
910 key => 'text_transform',
911 parse => $one_keyword_parser,
912 serialize => $default_serializer,
913 keyword => {
914 capitalize => 1, uppercase => 1, lowercase => 1, none => 1,
915 },
916 initial => ["KEYWORD", 'none'],
917 inherited => 1,
918 compute => $compute_as_specified,
919 };
920 $Attr->{text_transform} = $Prop->{'text-transform'};
921 $Key->{text_transform} = $Prop->{'text-transform'};
922
923 $Prop->{'white-space'} = {
924 css => 'white-space',
925 dom => 'white_space',
926 key => 'white_space',
927 parse => $one_keyword_parser,
928 serialize => $default_serializer,
929 keyword => {
930 normal => 1, pre => 1, nowrap => 1, 'pre-wrap' => 1, 'pre-line' => 1,
931 },
932 initial => ["KEYWORD", 'normal'],
933 inherited => 1,
934 compute => $compute_as_specified,
935 };
936 $Attr->{white_space} = $Prop->{'white-space'};
937 $Key->{white_space} = $Prop->{'white-space'};
938
939 $Prop->{'caption-side'} = {
940 css => 'caption-side',
941 dom => 'caption_side',
942 key => 'caption_side',
943 parse => $one_keyword_parser,
944 serialize => $default_serializer,
945 keyword => {
946 top => 1, bottom => 1,
947 },
948 initial => ['KEYWORD', 'top'],
949 inherited => 1,
950 compute => $compute_as_specified,
951 };
952 $Attr->{caption_side} = $Prop->{'caption-side'};
953 $Key->{caption_side} = $Prop->{'caption-side'};
954
955 $Prop->{'table-layout'} = {
956 css => 'table-layout',
957 dom => 'table_layout',
958 key => 'table_layout',
959 parse => $one_keyword_parser,
960 serialize => $default_serializer,
961 keyword => {
962 auto => 1, fixed => 1,
963 },
964 initial => ['KEYWORD', 'auto'],
965 #inherited => 0,
966 compute => $compute_as_specified,
967 };
968 $Attr->{table_layout} = $Prop->{'table-layout'};
969 $Key->{table_layout} = $Prop->{'table-layout'};
970
971 $Prop->{'border-collapse'} = {
972 css => 'border-collapse',
973 dom => 'border_collapse',
974 key => 'border_collapse',
975 parse => $one_keyword_parser,
976 serialize => $default_serializer,
977 keyword => {
978 collapse => 1, separate => 1,
979 },
980 initial => ['KEYWORD', 'separate'],
981 inherited => 1,
982 compute => $compute_as_specified,
983 };
984 $Attr->{border_collapse} = $Prop->{'border-collapse'};
985 $Key->{border_collapse} = $Prop->{'border-collapse'};
986
987 $Prop->{'empty-cells'} = {
988 css => 'empty-cells',
989 dom => 'empty_cells',
990 key => 'empty_cells',
991 parse => $one_keyword_parser,
992 serialize => $default_serializer,
993 keyword => {
994 show => 1, hide => 1,
995 },
996 initial => ['KEYWORD', 'show'],
997 inherited => 1,
998 compute => $compute_as_specified,
999 };
1000 $Attr->{empty_cells} = $Prop->{'empty-cells'};
1001 $Key->{empty_cells} = $Prop->{'empty-cells'};
1002
1003 $Prop->{'z-index'} = {
1004 css => 'z-index',
1005 dom => 'z_index',
1006 key => 'z_index',
1007 parse => sub {
1008 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1009
1010 my $sign = 1;
1011 if ($t->{type} == MINUS_TOKEN) {
1012 $sign = -1;
1013 $t = $tt->get_next_token;
1014 }
1015
1016 if ($t->{type} == NUMBER_TOKEN) {
1017 ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/z-index> for
1018 ## browser compatibility issue.
1019 my $value = $t->{number};
1020 $t = $tt->get_next_token;
1021 return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
1022 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1023 my $value = lc $t->{value}; ## TODO: case
1024 $t = $tt->get_next_token;
1025 if ($value eq 'auto') {
1026 ## NOTE: |z-index| is the default value and therefore it must be
1027 ## supported anyway.
1028 return ($t, {$prop_name => ["KEYWORD", 'auto']});
1029 } elsif ($value eq 'inherit') {
1030 return ($t, {$prop_name => ['INHERIT']});
1031 }
1032 }
1033
1034 $onerror->(type => 'syntax error:'.$prop_name,
1035 level => $self->{must_level},
1036 token => $t);
1037 return ($t, undef);
1038 },
1039 serialize => $default_serializer,
1040 initial => ['KEYWORD', 'auto'],
1041 #inherited => 0,
1042 compute => $compute_as_specified,
1043 };
1044 $Attr->{z_index} = $Prop->{'z-index'};
1045 $Key->{z_index} = $Prop->{'z-index'};
1046
1047 $Prop->{orphans} = {
1048 css => 'orphans',
1049 dom => 'orphans',
1050 key => 'orphans',
1051 parse => sub {
1052 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1053
1054 my $sign = 1;
1055 if ($t->{type} == MINUS_TOKEN) {
1056 $t = $tt->get_next_token;
1057 $sign = -1;
1058 }
1059
1060 if ($t->{type} == NUMBER_TOKEN) {
1061 ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/orphans> and
1062 ## <http://suika.fam.cx/gate/2005/sw/widows> for
1063 ## browser compatibility issue.
1064 my $value = $t->{number};
1065 $t = $tt->get_next_token;
1066 return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
1067 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1068 my $value = lc $t->{value}; ## TODO: case
1069 $t = $tt->get_next_token;
1070 if ($value eq 'inherit') {
1071 return ($t, {$prop_name => ['INHERIT']});
1072 }
1073 }
1074
1075 $onerror->(type => 'syntax error:'.$prop_name,
1076 level => $self->{must_level},
1077 token => $t);
1078 return ($t, undef);
1079 },
1080 serialize => $default_serializer,
1081 initial => ['NUMBER', 2],
1082 inherited => 1,
1083 compute => $compute_as_specified,
1084 };
1085 $Attr->{orphans} = $Prop->{orphans};
1086 $Key->{orphans} = $Prop->{orphans};
1087
1088 $Prop->{widows} = {
1089 css => 'widows',
1090 dom => 'widows',
1091 key => 'widows',
1092 parse => $Prop->{orphans}->{parse},
1093 serialize => $default_serializer,
1094 initial => ['NUMBER', 2],
1095 inherited => 1,
1096 compute => $compute_as_specified,
1097 };
1098 $Attr->{widows} = $Prop->{widows};
1099 $Key->{widows} = $Prop->{widows};
1100
1101 my $length_unit = {
1102 em => 1, ex => 1, px => 1,
1103 in => 1, cm => 1, mm => 1, pt => 1, pc => 1,
1104 };
1105
1106 $Prop->{'font-size'} = {
1107 css => 'font-size',
1108 dom => 'font_size',
1109 key => 'font_size',
1110 parse => sub {
1111 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1112
1113 my $sign = 1;
1114 if ($t->{type} == MINUS_TOKEN) {
1115 $t = $tt->get_next_token;
1116 $sign = -1;
1117 }
1118
1119 if ($t->{type} == DIMENSION_TOKEN) {
1120 my $value = $t->{number} * $sign;
1121 my $unit = lc $t->{value}; ## TODO: case
1122 $t = $tt->get_next_token;
1123 if ($length_unit->{$unit} and $value >= 0) {
1124 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1125 }
1126 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1127 my $value = $t->{number} * $sign;
1128 $t = $tt->get_next_token;
1129 return ($t, {$prop_name => ['PERCENTAGE', $value]}) if $value >= 0;
1130 } elsif ($t->{type} == NUMBER_TOKEN and
1131 ($self->{unitless_px} or $t->{number} == 0)) {
1132 my $value = $t->{number} * $sign;
1133 $t = $tt->get_next_token;
1134 return ($t, {$prop_name => ['DIMENSION', $value, 'px']}) if $value >= 0;
1135 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1136 my $value = lc $t->{value}; ## TODO: case
1137 $t = $tt->get_next_token;
1138 if ({
1139 'xx-small' => 1, 'x-small' => 1, small => 1, medium => 1,
1140 large => 1, 'x-large' => 1, 'xx-large' => 1,
1141 '-manakai-xxx-large' => 1, # -webkit-xxx-large
1142 larger => 1, smaller => 1,
1143 }->{$value}) {
1144 return ($t, {$prop_name => ['KEYWORD', $value]});
1145 } elsif ($value eq 'inherit') {
1146 return ($t, {$prop_name => ['INHERIT']});
1147 }
1148 }
1149
1150 $onerror->(type => 'syntax error:'.$prop_name,
1151 level => $self->{must_level},
1152 token => $t);
1153 return ($t, undef);
1154 },
1155 serialize => $default_serializer,
1156 initial => ['KEYWORD', 'medium'],
1157 inherited => 1,
1158 compute => sub {
1159 my ($self, $element, $prop_name, $specified_value) = @_;
1160
1161 if (defined $specified_value) {
1162 if ($specified_value->[0] eq 'DIMENSION') {
1163 my $unit = $specified_value->[2];
1164 my $value = $specified_value->[1];
1165
1166 if ($unit eq 'em' or $unit eq 'ex') {
1167 $value *= 0.5 if $unit eq 'ex';
1168 ## TODO: Preferred way to determine the |ex| size is defined
1169 ## in CSS 2.1.
1170
1171 my $parent_element = $element->manakai_parent_element;
1172 if (defined $parent_element) {
1173 $value *= $self->get_computed_value ($parent_element, $prop_name)
1174 ->[1];
1175 } else {
1176 $value *= $self->{font_size}->[3]; # medium
1177 }
1178 $unit = 'px';
1179 } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
1180 ($value *= 12, $unit = 'pc') if $unit eq 'pc';
1181 ($value /= 72, $unit = 'in') if $unit eq 'pt';
1182 ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
1183 ($value *= 10, $unit = 'mm') if $unit eq 'cm';
1184 ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
1185 }
1186 ## else: consistency error
1187
1188 return ['DIMENSION', $value, $unit];
1189 } elsif ($specified_value->[0] eq 'PERCENTAGE') {
1190 my $parent_element = $element->manakai_parent_element;
1191 my $parent_cv;
1192 if (defined $parent_element) {
1193 $parent_cv = $self->get_computed_value
1194 ($parent_element, $prop_name);
1195 } else {
1196 $parent_cv = [undef, $self->{font_size}->[3]];
1197 }
1198 return ['DIMENSION', $parent_cv->[1] * $specified_value->[1] / 100,
1199 'px'];
1200 } elsif ($specified_value->[0] eq 'KEYWORD') {
1201 if ($specified_value->[1] eq 'larger') {
1202 my $parent_element = $element->manakai_parent_element;
1203 if (defined $parent_element) {
1204 my $parent_cv = $self->get_computed_value
1205 ($parent_element, $prop_name);
1206 return ['DIMENSION',
1207 $self->{get_larger_font_size}->($self, $parent_cv->[1]),
1208 'px'];
1209 } else { ## 'larger' relative to 'medium', initial of 'font-size'
1210 return ['DIMENSION', $self->{font_size}->[4], 'px'];
1211 }
1212 } elsif ($specified_value->[1] eq 'smaller') {
1213 my $parent_element = $element->manakai_parent_element;
1214 if (defined $parent_element) {
1215 my $parent_cv = $self->get_computed_value
1216 ($parent_element, $prop_name);
1217 return ['DIMENSION',
1218 $self->{get_smaller_font_size}->($self, $parent_cv->[1]),
1219 'px'];
1220 } else { ## 'smaller' relative to 'medium', initial of 'font-size'
1221 return ['DIMENSION', $self->{font_size}->[2], 'px'];
1222 }
1223 } else {
1224 return ['DIMENSION', $self->{font_size}->[{
1225 'xx-small' => 0,
1226 'x-small' => 1,
1227 small => 2,
1228 medium => 3,
1229 large => 4,
1230 'x-large' => 5,
1231 'xx-large' => 6,
1232 '-manakai-xxx-large' => 7,
1233 }->{$specified_value->[1]}], 'px'];
1234 }
1235 }
1236 }
1237
1238 return $specified_value;
1239 },
1240 };
1241 $Attr->{font_size} = $Prop->{'font-size'};
1242 $Key->{font_size} = $Prop->{'font-size'};
1243
1244 my $compute_length = sub {
1245 my ($self, $element, $prop_name, $specified_value) = @_;
1246
1247 if (defined $specified_value) {
1248 if ($specified_value->[0] eq 'DIMENSION') {
1249 my $unit = $specified_value->[2];
1250 my $value = $specified_value->[1];
1251
1252 if ($unit eq 'em' or $unit eq 'ex') {
1253 $value *= 0.5 if $unit eq 'ex';
1254 ## TODO: Preferred way to determine the |ex| size is defined
1255 ## in CSS 2.1.
1256
1257 $value *= $self->get_computed_value ($element, 'font-size')->[1];
1258 $unit = 'px';
1259 } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
1260 ($value *= 12, $unit = 'pc') if $unit eq 'pc';
1261 ($value /= 72, $unit = 'in') if $unit eq 'pt';
1262 ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
1263 ($value *= 10, $unit = 'mm') if $unit eq 'cm';
1264 ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
1265 }
1266
1267 return ['DIMENSION', $value, $unit];
1268 }
1269 }
1270
1271 return $specified_value;
1272 }; # $compute_length
1273
1274 $Prop->{'margin-top'} = {
1275 css => 'margin-top',
1276 dom => 'margin_top',
1277 key => 'margin_top',
1278 parse => sub {
1279 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1280
1281 ## NOTE: Used for 'margin-top', 'margin-right', 'margin-bottom',
1282 ## 'margin-left', 'top', 'right', 'bottom', and 'left'.
1283
1284 my $sign = 1;
1285 if ($t->{type} == MINUS_TOKEN) {
1286 $t = $tt->get_next_token;
1287 $sign = -1;
1288 }
1289
1290 if ($t->{type} == DIMENSION_TOKEN) {
1291 my $value = $t->{number} * $sign;
1292 my $unit = lc $t->{value}; ## TODO: case
1293 $t = $tt->get_next_token;
1294 if ($length_unit->{$unit}) {
1295 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1296 }
1297 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1298 my $value = $t->{number} * $sign;
1299 $t = $tt->get_next_token;
1300 return ($t, {$prop_name => ['PERCENTAGE', $value]});
1301 } elsif ($t->{type} == NUMBER_TOKEN and
1302 ($self->{unitless_px} or $t->{number} == 0)) {
1303 my $value = $t->{number} * $sign;
1304 $t = $tt->get_next_token;
1305 return ($t, {$prop_name => ['DIMENSION', $value, 'px']});
1306 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1307 my $value = lc $t->{value}; ## TODO: case
1308 $t = $tt->get_next_token;
1309 if ($value eq 'auto') {
1310 return ($t, {$prop_name => ['KEYWORD', $value]});
1311 } elsif ($value eq 'inherit') {
1312 return ($t, {$prop_name => ['INHERIT']});
1313 }
1314 }
1315
1316 $onerror->(type => 'syntax error:'.$prop_name,
1317 level => $self->{must_level},
1318 token => $t);
1319 return ($t, undef);
1320 },
1321 serialize => $default_serializer,
1322 initial => ['DIMENSION', 0, 'px'],
1323 #inherited => 0,
1324 compute => $compute_length,
1325 };
1326 $Attr->{margin_top} = $Prop->{'margin-top'};
1327 $Key->{margin_top} = $Prop->{'margin-top'};
1328
1329 $Prop->{'margin-bottom'} = {
1330 css => 'margin-bottom',
1331 dom => 'margin_bottom',
1332 key => 'margin_bottom',
1333 parse => $Prop->{'margin-top'}->{parse},
1334 serialize => $default_serializer,
1335 initial => ['DIMENSION', 0, 'px'],
1336 #inherited => 0,
1337 compute => $compute_length,
1338 };
1339 $Attr->{margin_bottom} = $Prop->{'margin-bottom'};
1340 $Key->{margin_bottom} = $Prop->{'margin-bottom'};
1341
1342 $Prop->{'margin-right'} = {
1343 css => 'margin-right',
1344 dom => 'margin_right',
1345 key => 'margin_right',
1346 parse => $Prop->{'margin-top'}->{parse},
1347 serialize => $default_serializer,
1348 initial => ['DIMENSION', 0, 'px'],
1349 #inherited => 0,
1350 compute => $compute_length,
1351 };
1352 $Attr->{margin_right} = $Prop->{'margin-right'};
1353 $Key->{margin_right} = $Prop->{'margin-right'};
1354
1355 $Prop->{'margin-left'} = {
1356 css => 'margin-left',
1357 dom => 'margin_left',
1358 key => 'margin_left',
1359 parse => $Prop->{'margin-top'}->{parse},
1360 serialize => $default_serializer,
1361 initial => ['DIMENSION', 0, 'px'],
1362 #inherited => 0,
1363 compute => $compute_length,
1364 };
1365 $Attr->{margin_left} = $Prop->{'margin-left'};
1366 $Key->{margin_left} = $Prop->{'margin-left'};
1367
1368 $Prop->{top} = {
1369 css => 'top',
1370 dom => 'top',
1371 key => 'top',
1372 parse => $Prop->{'margin-top'}->{parse},
1373 serialize => $default_serializer,
1374 initial => ['KEYWORD', 'auto'],
1375 #inherited => 0,
1376 compute_multiple => sub {
1377 my ($self, $element, $eid, $prop_name) = @_;
1378
1379 my $pos_value = $self->get_computed_value ($element, 'position');
1380 if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
1381 if ($pos_value->[1] eq 'static') {
1382 $self->{computed_value}->{$eid}->{top} = ['KEYWORD', 'auto'];
1383 $self->{computed_value}->{$eid}->{bottom} = ['KEYWORD', 'auto'];
1384 return;
1385 } elsif ($pos_value->[1] eq 'relative') {
1386 my $top_specified = $self->get_specified_value_no_inherit
1387 ($element, 'top');
1388 if (defined $top_specified and
1389 ($top_specified->[0] eq 'DIMENSION' or
1390 $top_specified->[0] eq 'PERCENTAGE')) {
1391 my $tv = $self->{computed_value}->{$eid}->{top}
1392 = $compute_length->($self, $element, 'top', $top_specified);
1393 $self->{computed_value}->{$eid}->{bottom}
1394 = [$tv->[0], -$tv->[1], $tv->[2]];
1395 } else { # top: auto
1396 my $bottom_specified = $self->get_specified_value_no_inherit
1397 ($element, 'bottom');
1398 if (defined $bottom_specified and
1399 ($bottom_specified->[0] eq 'DIMENSION' or
1400 $bottom_specified->[0] eq 'PERCENTAGE')) {
1401 my $tv = $self->{computed_value}->{$eid}->{bottom}
1402 = $compute_length->($self, $element, 'bottom',
1403 $bottom_specified);
1404 $self->{computed_value}->{$eid}->{top}
1405 = [$tv->[0], -$tv->[1], $tv->[2]];
1406 } else { # bottom: auto
1407 $self->{computed_value}->{$eid}->{top} = ['DIMENSION', 0, 'px'];
1408 $self->{computed_value}->{$eid}->{bottom} = ['DIMENSION', 0, 'px'];
1409 }
1410 }
1411 return;
1412 }
1413 }
1414
1415 my $top_specified = $self->get_specified_value_no_inherit
1416 ($element, 'top');
1417 $self->{computed_value}->{$eid}->{top}
1418 = $compute_length->($self, $element, 'top', $top_specified);
1419 my $bottom_specified = $self->get_specified_value_no_inherit
1420 ($element, 'bottom');
1421 $self->{computed_value}->{$eid}->{bottom}
1422 = $compute_length->($self, $element, 'bottom', $bottom_specified);
1423 },
1424 };
1425 $Attr->{top} = $Prop->{top};
1426 $Key->{top} = $Prop->{top};
1427
1428 $Prop->{bottom} = {
1429 css => 'bottom',
1430 dom => 'bottom',
1431 key => 'bottom',
1432 parse => $Prop->{'margin-top'}->{parse},
1433 serialize => $default_serializer,
1434 initial => ['KEYWORD', 'auto'],
1435 #inherited => 0,
1436 compute_multiple => $Prop->{top}->{compute_multiple},
1437 };
1438 $Attr->{bottom} = $Prop->{bottom};
1439 $Key->{bottom} = $Prop->{bottom};
1440
1441 $Prop->{left} = {
1442 css => 'left',
1443 dom => 'left',
1444 key => 'left',
1445 parse => $Prop->{'margin-top'}->{parse},
1446 serialize => $default_serializer,
1447 initial => ['KEYWORD', 'auto'],
1448 #inherited => 0,
1449 compute_multiple => sub {
1450 my ($self, $element, $eid, $prop_name) = @_;
1451
1452 my $pos_value = $self->get_computed_value ($element, 'position');
1453 if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
1454 if ($pos_value->[1] eq 'static') {
1455 $self->{computed_value}->{$eid}->{left} = ['KEYWORD', 'auto'];
1456 $self->{computed_value}->{$eid}->{right} = ['KEYWORD', 'auto'];
1457 return;
1458 } elsif ($pos_value->[1] eq 'relative') {
1459 my $left_specified = $self->get_specified_value_no_inherit
1460 ($element, 'left');
1461 if (defined $left_specified and
1462 ($left_specified->[0] eq 'DIMENSION' or
1463 $left_specified->[0] eq 'PERCENTAGE')) {
1464 my $right_specified = $self->get_specified_value_no_inherit
1465 ($element, 'right');
1466 if (defined $right_specified and
1467 ($right_specified->[0] eq 'DIMENSION' or
1468 $right_specified->[0] eq 'PERCENTAGE')) {
1469 my $direction = $self->get_computed_value ($element, 'direction');
1470 if (defined $direction and $direction->[0] eq 'KEYWORD' and
1471 $direction->[0] eq 'ltr') {
1472 my $tv = $self->{computed_value}->{$eid}->{left}
1473 = $compute_length->($self, $element, 'left',
1474 $left_specified);
1475 $self->{computed_value}->{$eid}->{right}
1476 = [$tv->[0], -$tv->[1], $tv->[2]];
1477 } else {
1478 my $tv = $self->{computed_value}->{$eid}->{right}
1479 = $compute_length->($self, $element, 'right',
1480 $right_specified);
1481 $self->{computed_value}->{$eid}->{left}
1482 = [$tv->[0], -$tv->[1], $tv->[2]];
1483 }
1484 } else {
1485 my $tv = $self->{computed_value}->{$eid}->{left}
1486 = $compute_length->($self, $element, 'left', $left_specified);
1487 $self->{computed_value}->{$eid}->{right}
1488 = [$tv->[0], -$tv->[1], $tv->[2]];
1489 }
1490 } else { # left: auto
1491 my $right_specified = $self->get_specified_value_no_inherit
1492 ($element, 'right');
1493 if (defined $right_specified and
1494 ($right_specified->[0] eq 'DIMENSION' or
1495 $right_specified->[0] eq 'PERCENTAGE')) {
1496 my $tv = $self->{computed_value}->{$eid}->{right}
1497 = $compute_length->($self, $element, 'right',
1498 $right_specified);
1499 $self->{computed_value}->{$eid}->{left}
1500 = [$tv->[0], -$tv->[1], $tv->[2]];
1501 } else { # right: auto
1502 $self->{computed_value}->{$eid}->{left} = ['DIMENSION', 0, 'px'];
1503 $self->{computed_value}->{$eid}->{right} = ['DIMENSION', 0, 'px'];
1504 }
1505 }
1506 return;
1507 }
1508 }
1509
1510 my $left_specified = $self->get_specified_value_no_inherit
1511 ($element, 'left');
1512 $self->{computed_value}->{$eid}->{left}
1513 = $compute_length->($self, $element, 'left', $left_specified);
1514 my $right_specified = $self->get_specified_value_no_inherit
1515 ($element, 'right');
1516 $self->{computed_value}->{$eid}->{right}
1517 = $compute_length->($self, $element, 'right', $right_specified);
1518 },
1519 };
1520 $Attr->{left} = $Prop->{left};
1521 $Key->{left} = $Prop->{left};
1522
1523 $Prop->{right} = {
1524 css => 'right',
1525 dom => 'right',
1526 key => 'right',
1527 parse => $Prop->{'margin-top'}->{parse},
1528 serialize => $default_serializer,
1529 initial => ['KEYWORD', 'auto'],
1530 #inherited => 0,
1531 compute_multiple => $Prop->{left}->{compute_multiple},
1532 };
1533 $Attr->{right} = $Prop->{right};
1534 $Key->{right} = $Prop->{right};
1535
1536 $Prop->{'padding-top'} = {
1537 css => 'padding-top',
1538 dom => 'padding_top',
1539 key => 'padding_top',
1540 parse => sub {
1541 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1542
1543 my $sign = 1;
1544 if ($t->{type} == MINUS_TOKEN) {
1545 $t = $tt->get_next_token;
1546 $sign = -1;
1547 }
1548
1549 if ($t->{type} == DIMENSION_TOKEN) {
1550 my $value = $t->{number} * $sign;
1551 my $unit = lc $t->{value}; ## TODO: case
1552 $t = $tt->get_next_token;
1553 if ($length_unit->{$unit} and $value >= 0) {
1554 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1555 }
1556 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1557 my $value = $t->{number} * $sign;
1558 $t = $tt->get_next_token;
1559 return ($t, {$prop_name => ['PERCENTAGE', $value]}) if $value >= 0;
1560 } elsif ($t->{type} == NUMBER_TOKEN and
1561 ($self->{unitless_px} or $t->{number} == 0)) {
1562 my $value = $t->{number} * $sign;
1563 $t = $tt->get_next_token;
1564 return ($t, {$prop_name => ['DIMENSION', $value, 'px']}) if $value >= 0;
1565 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1566 my $value = lc $t->{value}; ## TODO: case
1567 $t = $tt->get_next_token;
1568 if ($value eq 'inherit') {
1569 return ($t, {$prop_name => ['INHERIT']});
1570 }
1571 }
1572
1573 $onerror->(type => 'syntax error:'.$prop_name,
1574 level => $self->{must_level},
1575 token => $t);
1576 return ($t, undef);
1577 },
1578 serialize => $default_serializer,
1579 initial => ['DIMENSION', 0, 'px'],
1580 #inherited => 0,
1581 compute => $compute_length,
1582 };
1583 $Attr->{padding_top} = $Prop->{'padding-top'};
1584 $Key->{padding_top} = $Prop->{'padding-top'};
1585
1586 $Prop->{'padding-bottom'} = {
1587 css => 'padding-bottom',
1588 dom => 'padding_bottom',
1589 key => 'padding_bottom',
1590 parse => $Prop->{'padding-top'}->{parse},
1591 serialize => $default_serializer,
1592 initial => ['DIMENSION', 0, 'px'],
1593 #inherited => 0,
1594 compute => $compute_length,
1595 };
1596 $Attr->{padding_bottom} = $Prop->{'padding-bottom'};
1597 $Key->{padding_bottom} = $Prop->{'padding-bottom'};
1598
1599 $Prop->{'padding-right'} = {
1600 css => 'padding-right',
1601 dom => 'padding_right',
1602 key => 'padding_right',
1603 parse => $Prop->{'padding-top'}->{parse},
1604 serialize => $default_serializer,
1605 initial => ['DIMENSION', 0, 'px'],
1606 #inherited => 0,
1607 compute => $compute_length,
1608 };
1609 $Attr->{padding_right} = $Prop->{'padding-right'};
1610 $Key->{padding_right} = $Prop->{'padding-right'};
1611
1612 $Prop->{'padding-left'} = {
1613 css => 'padding-left',
1614 dom => 'padding_left',
1615 key => 'padding_left',
1616 parse => $Prop->{'padding-top'}->{parse},
1617 serialize => $default_serializer,
1618 initial => ['DIMENSION', 0, 'px'],
1619 #inherited => 0,
1620 compute => $compute_length,
1621 };
1622 $Attr->{padding_left} = $Prop->{'padding-left'};
1623 $Key->{padding_left} = $Prop->{'padding-left'};
1624
1625 $Prop->{'border-top-width'} = {
1626 css => 'border-top-width',
1627 dom => 'border_top_width',
1628 key => 'border_top_width',
1629 parse => sub {
1630 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1631
1632 my $sign = 1;
1633 if ($t->{type} == MINUS_TOKEN) {
1634 $t = $tt->get_next_token;
1635 $sign = -1;
1636 }
1637
1638 if ($t->{type} == DIMENSION_TOKEN) {
1639 my $value = $t->{number} * $sign;
1640 my $unit = lc $t->{value}; ## TODO: case
1641 $t = $tt->get_next_token;
1642 if ($length_unit->{$unit} and $value >= 0) {
1643 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1644 }
1645 } elsif ($t->{type} == NUMBER_TOKEN and
1646 ($self->{unitless_px} or $t->{number} == 0)) {
1647 my $value = $t->{number} * $sign;
1648 $t = $tt->get_next_token;
1649 return ($t, {$prop_name => ['DIMENSION', $value, 'px']}) if $value >= 0;
1650 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1651 my $value = lc $t->{value}; ## TODO: case
1652 $t = $tt->get_next_token;
1653 if ({thin => 1, medium => 1, thick => 1}->{$value}) {
1654 return ($t, {$prop_name => ['KEYWORD', $value]});
1655 } elsif ($value eq 'inherit') {
1656 return ($t, {$prop_name => ['INHERIT']});
1657 }
1658 }
1659
1660 $onerror->(type => 'syntax error:'.$prop_name,
1661 level => $self->{must_level},
1662 token => $t);
1663 return ($t, undef);
1664 },
1665 serialize => $default_serializer,
1666 initial => ['KEYWORD', 'medium'],
1667 #inherited => 0,
1668 compute => sub {
1669 my ($self, $element, $prop_name, $specified_value) = @_;
1670
1671 my $style_prop = $prop_name;
1672 $style_prop =~ s/width/style/;
1673 my $style = $self->get_computed_value ($element, $style_prop);
1674 if (defined $style and $style->[0] eq 'KEYWORD' and
1675 ($style->[1] eq 'none' or $style->[1] eq 'hidden')) {
1676 return ['DIMENSION', 0, 'px'];
1677 }
1678
1679 my $value = $compute_length->(@_);
1680 if (defined $value and $value->[0] eq 'KEYWORD') {
1681 if ($value->[1] eq 'thin') {
1682 return ['DIMENSION', 1, 'px']; ## Firefox/Opera
1683 } elsif ($value->[1] eq 'medium') {
1684 return ['DIMENSION', 3, 'px']; ## Firefox/Opera
1685 } elsif ($value->[1] eq 'thick') {
1686 return ['DIMENSION', 5, 'px']; ## Firefox
1687 }
1688 }
1689 return $value;
1690 },
1691 };
1692 $Attr->{border_top_width} = $Prop->{'border-top-width'};
1693 $Key->{border_top_width} = $Prop->{'border-top-width'};
1694
1695 $Prop->{'border-right-width'} = {
1696 css => 'border-right-width',
1697 dom => 'border_right_width',
1698 key => 'border_right_width',
1699 parse => $Prop->{'border-top-width'}->{parse},
1700 serialize => $default_serializer,
1701 initial => ['KEYWORD', 'medium'],
1702 #inherited => 0,
1703 compute => $Prop->{'border-top-width'}->{compute},
1704 };
1705 $Attr->{border_right_width} = $Prop->{'border-right-width'};
1706 $Key->{border_right_width} = $Prop->{'border-right-width'};
1707
1708 $Prop->{'border-bottom-width'} = {
1709 css => 'border-bottom-width',
1710 dom => 'border_bottom_width',
1711 key => 'border_bottom_width',
1712 parse => $Prop->{'border-top-width'}->{parse},
1713 serialize => $default_serializer,
1714 initial => ['KEYWORD', 'medium'],
1715 #inherited => 0,
1716 compute => $Prop->{'border-top-width'}->{compute},
1717 };
1718 $Attr->{border_bottom_width} = $Prop->{'border-bottom-width'};
1719 $Key->{border_bottom_width} = $Prop->{'border-bottom-width'};
1720
1721 $Prop->{'border-left-width'} = {
1722 css => 'border-left-width',
1723 dom => 'border_left_width',
1724 key => 'border_left_width',
1725 parse => $Prop->{'border-top-width'}->{parse},
1726 serialize => $default_serializer,
1727 initial => ['KEYWORD', 'medium'],
1728 #inherited => 0,
1729 compute => $Prop->{'border-top-width'}->{compute},
1730 };
1731 $Attr->{border_left_width} = $Prop->{'border-left-width'};
1732 $Key->{border_left_width} = $Prop->{'border-left-width'};
1733
1734 $Prop->{'font-weight'} = {
1735 css => 'font-weight',
1736 dom => 'font_weight',
1737 key => 'font_weight',
1738 parse => sub {
1739 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1740
1741 if ($t->{type} == NUMBER_TOKEN) {
1742 ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/font-weight> for
1743 ## browser compatibility issue.
1744 my $value = $t->{number};
1745 $t = $tt->get_next_token;
1746 if ($value % 100 == 0 and 100 <= $value and $value <= 900) {
1747 return ($t, {$prop_name => ['WEIGHT', $value, 0]});
1748 }
1749 } elsif ($t->{type} == IDENT_TOKEN) {
1750 my $value = lc $t->{value}; ## TODO: case
1751 $t = $tt->get_next_token;
1752 if ({
1753 normal => 1, bold => 1, bolder => 1, lighter => 1,
1754 }->{$value}) {
1755 return ($t, {$prop_name => ['KEYWORD', $value]});
1756 } elsif ($value eq 'inherit') {
1757 return ($t, {$prop_name => ['INHERIT']});
1758 }
1759 }
1760
1761 $onerror->(type => 'syntax error:'.$prop_name,
1762 level => $self->{must_level},
1763 token => $t);
1764 return ($t, undef);
1765 },
1766 serialize => $default_serializer,
1767 initial => ['KEYWORD', 'normal'],
1768 inherited => 1,
1769 compute => sub {
1770 my ($self, $element, $prop_name, $specified_value) = @_;
1771
1772 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1773 if ($specified_value->[1] eq 'normal') {
1774 return ['WEIGHT', 400, 0];
1775 } elsif ($specified_value->[1] eq 'bold') {
1776 return ['WEIGHT', 700, 0];
1777 } elsif ($specified_value->[1] eq 'bolder') {
1778 my $parent_element = $element->manakai_parent_element;
1779 if (defined $parent_element) {
1780 my $parent_value = $self->get_cascaded_value
1781 ($parent_element, $prop_name); ## NOTE: What Firefox does.
1782 return ['WEIGHT', $parent_value->[1], $parent_value->[2] + 1];
1783 } else {
1784 return ['WEIGHT', 400, 1];
1785 }
1786 } elsif ($specified_value->[1] eq 'lighter') {
1787 my $parent_element = $element->manakai_parent_element;
1788 if (defined $parent_element) {
1789 my $parent_value = $self->get_cascaded_value
1790 ($parent_element, $prop_name); ## NOTE: What Firefox does.
1791 return ['WEIGHT', $parent_value->[1], $parent_value->[2] - 1];
1792 } else {
1793 return ['WEIGHT', 400, 1];
1794 }
1795 }
1796 #} elsif (defined $specified_value and $specified_value->[0] eq 'WEIGHT') {
1797 #
1798 }
1799
1800 return $specified_value;
1801 },
1802 };
1803 $Attr->{font_weight} = $Prop->{'font-weight'};
1804 $Key->{font_weight} = $Prop->{'font-weight'};
1805
1806 my $uri_or_none_parser = sub {
1807 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1808
1809 if ($t->{type} == URI_TOKEN) {
1810 my $value = $t->{value};
1811 $t = $tt->get_next_token;
1812 return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
1813 } elsif ($t->{type} == IDENT_TOKEN) {
1814 my $value = lc $t->{value}; ## TODO: case
1815 $t = $tt->get_next_token;
1816 if ($value eq 'none') {
1817 ## NOTE: |none| is the default value and therefore it must be
1818 ## supported anyway.
1819 return ($t, {$prop_name => ["KEYWORD", 'none']});
1820 } elsif ($value eq 'inherit') {
1821 return ($t, {$prop_name => ['INHERIT']});
1822 }
1823 ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
1824 #} elsif ($t->{type} == URI_INVALID_TOKEN) {
1825 # my $value = $t->{value};
1826 # $t = $tt->get_next_token;
1827 # if ($t->{type} == EOF_TOKEN) {
1828 # $onerror->(type => 'syntax error:eof:'.$prop_name,
1829 # level => $self->{must_level},
1830 # token => $t);
1831 #
1832 # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
1833 # }
1834 }
1835
1836 $onerror->(type => 'syntax error:'.$prop_name,
1837 level => $self->{must_level},
1838 token => $t);
1839 return ($t, undef);
1840 }; # $uri_or_none_parser
1841
1842 my $compute_uri_or_none = sub {
1843 my ($self, $element, $prop_name, $specified_value) = @_;
1844
1845 if (defined $specified_value and
1846 $specified_value->[0] eq 'URI' and
1847 defined $specified_value->[2]) {
1848 require Message::DOM::DOMImplementation;
1849 return ['URI',
1850 Message::DOM::DOMImplementation->create_uri_reference
1851 ($specified_value->[1])
1852 ->get_absolute_reference (${$specified_value->[2]})
1853 ->get_uri_reference,
1854 $specified_value->[2]];
1855 }
1856
1857 return $specified_value;
1858 }; # $compute_uri_or_none
1859
1860 $Prop->{'list-style-image'} = {
1861 css => 'list-style-image',
1862 dom => 'list_style_image',
1863 key => 'list_style_image',
1864 parse => $uri_or_none_parser,
1865 serialize => $default_serializer,
1866 initial => ['KEYWORD', 'none'],
1867 inherited => 1,
1868 compute => $compute_uri_or_none,
1869 };
1870 $Attr->{list_style_image} = $Prop->{'list-style-image'};
1871 $Key->{list_style_image} = $Prop->{'list-style-image'};
1872
1873 $Prop->{'background-image'} = {
1874 css => 'background-image',
1875 dom => 'background_image',
1876 key => 'background_image',
1877 parse => $uri_or_none_parser,
1878 serialize => $default_serializer,
1879 initial => ['KEYWORD', 'none'],
1880 #inherited => 0,
1881 compute => $compute_uri_or_none,
1882 };
1883 $Attr->{background_image} = $Prop->{'background-image'};
1884 $Key->{background_image} = $Prop->{'background-image'};
1885
1886 my $border_style_keyword = {
1887 none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
1888 double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
1889 };
1890
1891 $Prop->{'border-top-style'} = {
1892 css => 'border-top-style',
1893 dom => 'border_top_style',
1894 key => 'border_top_style',
1895 parse => $one_keyword_parser,
1896 serialize => $default_serializer,
1897 keyword => $border_style_keyword,
1898 initial => ["KEYWORD", "none"],
1899 #inherited => 0,
1900 compute => $compute_as_specified,
1901 };
1902 $Attr->{border_top_style} = $Prop->{'border-top-style'};
1903 $Key->{border_top_style} = $Prop->{'border-top-style'};
1904
1905 $Prop->{'border-right-style'} = {
1906 css => 'border-right-style',
1907 dom => 'border_right_style',
1908 key => 'border_right_style',
1909 parse => $one_keyword_parser,
1910 serialize => $default_serializer,
1911 keyword => $border_style_keyword,
1912 initial => ["KEYWORD", "none"],
1913 #inherited => 0,
1914 compute => $compute_as_specified,
1915 };
1916 $Attr->{border_right_style} = $Prop->{'border-right-style'};
1917 $Key->{border_right_style} = $Prop->{'border-right-style'};
1918
1919 $Prop->{'border-bottom-style'} = {
1920 css => 'border-bottom-style',
1921 dom => 'border_bottom_style',
1922 key => 'border_bottom_style',
1923 parse => $one_keyword_parser,
1924 serialize => $default_serializer,
1925 keyword => $border_style_keyword,
1926 initial => ["KEYWORD", "none"],
1927 #inherited => 0,
1928 compute => $compute_as_specified,
1929 };
1930 $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
1931 $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
1932
1933 $Prop->{'border-left-style'} = {
1934 css => 'border-left-style',
1935 dom => 'border_left_style',
1936 key => 'border_left_style',
1937 parse => $one_keyword_parser,
1938 serialize => $default_serializer,
1939 keyword => $border_style_keyword,
1940 initial => ["KEYWORD", "none"],
1941 #inherited => 0,
1942 compute => $compute_as_specified,
1943 };
1944 $Attr->{border_left_style} = $Prop->{'border-left-style'};
1945 $Key->{border_left_style} = $Prop->{'border-left-style'};
1946
1947 $Prop->{'outline-style'} = {
1948 css => 'outline-style',
1949 dom => 'outline_style',
1950 key => 'outline_style',
1951 parse => $one_keyword_parser,
1952 serialize => $default_serializer,
1953 keyword => $border_style_keyword,
1954 initial => ['KEYWORD', 'none'],
1955 #inherited => 0,
1956 compute => $compute_as_specified,
1957 };
1958 $Attr->{outline_style} = $Prop->{'outline-style'};
1959 $Key->{outline_style} = $Prop->{'outline-style'};
1960
1961 $Prop->{'font-family'} = {
1962 css => 'font-family',
1963 dom => 'font_family',
1964 key => 'font_family',
1965 parse => sub {
1966 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1967
1968 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/font-family> for
1969 ## how chaotic browsers are!
1970
1971 my @prop_value;
1972
1973 my $font_name = '';
1974 my $may_be_generic = 1;
1975 my $may_be_inherit = 1;
1976 my $has_s = 0;
1977 F: {
1978 if ($t->{type} == IDENT_TOKEN) {
1979 undef $may_be_inherit if $has_s or length $font_name;
1980 undef $may_be_generic if $has_s or length $font_name;
1981 $font_name .= ' ' if $has_s;
1982 $font_name .= $t->{value};
1983 undef $has_s;
1984 $t = $tt->get_next_token;
1985 } elsif ($t->{type} == STRING_TOKEN) {
1986 $font_name .= ' ' if $has_s;
1987 $font_name .= $t->{value};
1988 undef $may_be_inherit;
1989 undef $may_be_generic;
1990 undef $has_s;
1991 $t = $tt->get_next_token;
1992 } elsif ($t->{type} == COMMA_TOKEN) {
1993 if ($may_be_generic and
1994 {
1995 serif => 1, 'sans-serif' => 1, cursive => 1,
1996 fantasy => 1, monospace => 1, '-manakai-default' => 1,
1997 }->{lc $font_name}) { ## TODO: case
1998 push @prop_value, ['KEYWORD', $font_name];
1999 } elsif (not $may_be_generic or length $font_name) {
2000 push @prop_value, ["STRING", $font_name];
2001 }
2002 undef $may_be_inherit;
2003 $may_be_generic = 1;
2004 undef $has_s;
2005 $font_name = '';
2006 $t = $tt->get_next_token;
2007 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2008 } elsif ($t->{type} == S_TOKEN) {
2009 $has_s = 1;
2010 $t = $tt->get_next_token;
2011 } else {
2012 if ($may_be_generic and
2013 {
2014 serif => 1, 'sans-serif' => 1, cursive => 1,
2015 fantasy => 1, monospace => 1, '-manakai-default' => 1,
2016 }->{lc $font_name}) { ## TODO: case
2017 push @prop_value, ['KEYWORD', $font_name];
2018 } elsif (not $may_be_generic or length $font_name) {
2019 push @prop_value, ['STRING', $font_name];
2020 } else {
2021 $onerror->(type => 'syntax error:'.$prop_name,
2022 level => $self->{must_level},
2023 token => $t);
2024 return ($t, undef);
2025 }
2026 last F;
2027 }
2028 redo F;
2029 } # F
2030
2031 if ($may_be_inherit and
2032 @prop_value == 1 and
2033 $prop_value[0]->[0] eq 'STRING' and
2034 lc $prop_value[0]->[1] eq 'inherit') { ## TODO: case
2035 return ($t, {$prop_name => ['INHERIT']});
2036 } else {
2037 unshift @prop_value, 'FONT';
2038 return ($t, {$prop_name => \@prop_value});
2039 }
2040 },
2041 serialize => sub {
2042 my ($self, $prop_name, $value) = @_;
2043
2044 if ($value->[0] eq 'FONT') {
2045 return join ', ', map {
2046 if ($_->[0] eq 'STRING') {
2047 '"'.$_->[1].'"'; ## NOTE: This is what Firefox does.
2048 } elsif ($_->[0] eq 'KEYWORD') {
2049 $_->[1]; ## NOTE: This is what Firefox does.
2050 } else {
2051 ## NOTE: This should be an error.
2052 '""';
2053 }
2054 } @$value[1..$#$value];
2055 } elsif ($value->[0] eq 'INHERIT') {
2056 return 'inherit';
2057 } else {
2058 return undef;
2059 }
2060 },
2061 initial => ['FONT', ['KEYWORD', '-manakai-default']],
2062 inherited => 1,
2063 compute => $compute_as_specified,
2064 };
2065 $Attr->{font_family} = $Prop->{'font-family'};
2066 $Key->{font_family} = $Prop->{'font-family'};
2067
2068 $Prop->{cursor} = {
2069 css => 'cursor',
2070 dom => 'cursor',
2071 key => 'cursor',
2072 parse => sub {
2073 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2074
2075 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/cursor> for browser
2076 ## compatibility issues.
2077
2078 my @prop_value = ('CURSOR');
2079
2080 F: {
2081 if ($t->{type} == IDENT_TOKEN) {
2082 my $v = lc $t->{value}; ## TODO: case
2083 $t = $tt->get_next_token;
2084 if ($Prop->{$prop_name}->{keyword}->{$v}) {
2085 push @prop_value, ['KEYWORD', $v];
2086 last F;
2087 } elsif ($v eq 'inherit' and @prop_value == 1) {
2088 return ($t, {$prop_name => ['INHERIT']});
2089 } else {
2090 $onerror->(type => 'syntax error:'.$prop_name,
2091 level => $self->{must_level},
2092 token => $t);
2093 return ($t, undef);
2094 }
2095 } elsif ($t->{type} == URI_TOKEN) {
2096 push @prop_value, ['URI', $t->{value}, \($self->{base_uri})];
2097 $t = $tt->get_next_token;
2098 } else {
2099 $onerror->(type => 'syntax error:'.$prop_name,
2100 level => $self->{must_level},
2101 token => $t);
2102 return ($t, undef);
2103 }
2104
2105 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2106 if ($t->{type} == COMMA_TOKEN) {
2107 $t = $tt->get_next_token;
2108 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2109 redo F;
2110 }
2111 } # F
2112
2113 return ($t, {$prop_name => \@prop_value});
2114 },
2115 serialize => sub {
2116 my ($self, $prop_name, $value) = @_;
2117
2118 if ($value->[0] eq 'CURSOR') {
2119 return join ', ', map {
2120 if ($_->[0] eq 'URI') {
2121 'url('.$_->[1].')'; ## NOTE: This is what Firefox does.
2122 } elsif ($_->[0] eq 'KEYWORD') {
2123 $_->[1];
2124 } else {
2125 ## NOTE: This should be an error.
2126 '""';
2127 }
2128 } @$value[1..$#$value];
2129 } elsif ($value->[0] eq 'INHERIT') {
2130 return 'inherit';
2131 } else {
2132 return undef;
2133 }
2134 },
2135 keyword => {
2136 auto => 1, crosshair => 1, default => 1, pointer => 1, move => 1,
2137 'e-resize' => 1, 'ne-resize' => 1, 'nw-resize' => 1, 'n-resize' => 1,
2138 'n-resize' => 1, 'se-resize' => 1, 'sw-resize' => 1, 's-resize' => 1,
2139 'w-resize' => 1, text => 1, wait => 1, help => 1, progress => 1,
2140 },
2141 initial => ['CURSOR', ['KEYWORD', 'auto']],
2142 inherited => 1,
2143 compute => sub {
2144 my ($self, $element, $prop_name, $specified_value) = @_;
2145
2146 if (defined $specified_value and $specified_value->[0] eq 'CURSOR') {
2147 my @new_value = ('CURSOR');
2148 for my $value (@$specified_value[1..$#$specified_value]) {
2149 if ($value->[0] eq 'URI') {
2150 if (defined $value->[2]) {
2151 require Message::DOM::DOMImplementation;
2152 push @new_value, ['URI',
2153 Message::DOM::DOMImplementation
2154 ->create_uri_reference ($value->[1])
2155 ->get_absolute_reference (${$value->[2]})
2156 ->get_uri_reference,
2157 $value->[2]];
2158 } else {
2159 push @new_value, $value;
2160 }
2161 } else {
2162 push @new_value, $value;
2163 }
2164 }
2165 return \@new_value;
2166 }
2167
2168 return $specified_value;
2169 },
2170 };
2171 $Attr->{cursor} = $Prop->{cursor};
2172 $Key->{cursor} = $Prop->{cursor};
2173
2174 $Prop->{'border-style'} = {
2175 css => 'border-style',
2176 dom => 'border_style',
2177 parse => sub {
2178 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2179
2180 my %prop_value;
2181 if ($t->{type} == IDENT_TOKEN) {
2182 my $prop_value = lc $t->{value}; ## TODO: case folding
2183 $t = $tt->get_next_token;
2184 if ($border_style_keyword->{$prop_value} and
2185 $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
2186 $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
2187 } elsif ($prop_value eq 'inherit') {
2188 $prop_value{'border-top-style'} = ["INHERIT"];
2189 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
2190 $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
2191 $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2192 return ($t, \%prop_value);
2193 } else {
2194 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2195 level => $self->{must_level},
2196 token => $t);
2197 return ($t, undef);
2198 }
2199 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
2200 $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
2201 $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2202 } else {
2203 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2204 level => $self->{must_level},
2205 token => $t);
2206 return ($t, undef);
2207 }
2208
2209 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2210 if ($t->{type} == IDENT_TOKEN) {
2211 my $prop_value = lc $t->{value}; ## TODO: case folding
2212 $t = $tt->get_next_token;
2213 if ($border_style_keyword->{$prop_value} and
2214 $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
2215 $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
2216 } else {
2217 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2218 level => $self->{must_level},
2219 token => $t);
2220 return ($t, undef);
2221 }
2222 $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2223
2224 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2225 if ($t->{type} == IDENT_TOKEN) {
2226 my $prop_value = lc $t->{value}; ## TODO: case folding
2227 $t = $tt->get_next_token;
2228 if ($border_style_keyword->{$prop_value} and
2229 $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
2230 $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
2231 } else {
2232 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2233 level => $self->{must_level},
2234 token => $t);
2235 return ($t, undef);
2236 }
2237
2238 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2239 if ($t->{type} == IDENT_TOKEN) {
2240 my $prop_value = lc $t->{value}; ## TODO: case folding
2241 $t = $tt->get_next_token;
2242 if ($border_style_keyword->{$prop_value} and
2243 $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
2244 $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
2245 } else {
2246 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2247 level => $self->{must_level},
2248 token => $t);
2249 return ($t, undef);
2250 }
2251 }
2252 }
2253 }
2254
2255 return ($t, \%prop_value);
2256 },
2257 serialize => sub {
2258 my ($self, $prop_name, $value) = @_;
2259
2260 local $Error::Depth = $Error::Depth + 1;
2261 my @v;
2262 push @v, $self->border_top_style;
2263 return undef unless defined $v[-1];
2264 push @v, $self->border_right_style;
2265 return undef unless defined $v[-1];
2266 push @v, $self->border_bottom_style;
2267 return undef unless defined $v[-1];
2268 push @v, $self->border_left_style;
2269 return undef unless defined $v[-1];
2270
2271 pop @v if $v[1] eq $v[3];
2272 pop @v if $v[0] eq $v[2];
2273 pop @v if $v[0] eq $v[1];
2274 return join ' ', @v;
2275 },
2276 };
2277 $Attr->{border_style} = $Prop->{'border-style'};
2278
2279 $Prop->{margin} = {
2280 css => 'margin',
2281 dom => 'margin',
2282 parse => sub {
2283 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2284
2285 my %prop_value;
2286
2287 my $sign = 1;
2288 if ($t->{type} == MINUS_TOKEN) {
2289 $t = $tt->get_next_token;
2290 $sign = -1;
2291 }
2292
2293 if ($t->{type} == DIMENSION_TOKEN) {
2294 my $value = $t->{number} * $sign;
2295 my $unit = lc $t->{value}; ## TODO: case
2296 $t = $tt->get_next_token;
2297 if ($length_unit->{$unit}) {
2298 $prop_value{'margin-top'} = ['DIMENSION', $value, $unit];
2299 } else {
2300 $onerror->(type => 'syntax error:'.$prop_name,
2301 level => $self->{must_level},
2302 token => $t);
2303 return ($t, undef);
2304 }
2305 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2306 my $value = $t->{number} * $sign;
2307 $t = $tt->get_next_token;
2308 $prop_value{'margin-top'} = ['PERCENTAGE', $value];
2309 } elsif ($t->{type} == NUMBER_TOKEN and
2310 ($self->{unitless_px} or $t->{number} == 0)) {
2311 my $value = $t->{number} * $sign;
2312 $t = $tt->get_next_token;
2313 $prop_value{'margin-top'} = ['DIMENSION', $value, 'px'];
2314 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2315 my $prop_value = lc $t->{value}; ## TODO: case folding
2316 $t = $tt->get_next_token;
2317 if ($prop_value eq 'auto') {
2318 $prop_value{'margin-top'} = ['KEYWORD', $prop_value];
2319 } elsif ($prop_value eq 'inherit') {
2320 $prop_value{'margin-top'} = ['INHERIT'];
2321 $prop_value{'margin-right'} = $prop_value{'margin-top'};
2322 $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
2323 $prop_value{'margin-left'} = $prop_value{'margin-right'};
2324 return ($t, \%prop_value);
2325 } else {
2326 $onerror->(type => 'syntax error:'.$prop_name,
2327 level => $self->{must_level},
2328 token => $t);
2329 return ($t, undef);
2330 }
2331 } else {
2332 $onerror->(type => 'syntax error:'.$prop_name,
2333 level => $self->{must_level},
2334 token => $t);
2335 return ($t, undef);
2336 }
2337 $prop_value{'margin-right'} = $prop_value{'margin-top'};
2338 $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
2339 $prop_value{'margin-left'} = $prop_value{'margin-right'};
2340
2341 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2342 $sign = 1;
2343 if ($t->{type} == MINUS_TOKEN) {
2344 $t = $tt->get_next_token;
2345 $sign = -1;
2346 }
2347
2348 if ($t->{type} == DIMENSION_TOKEN) {
2349 my $value = $t->{number} * $sign;
2350 my $unit = lc $t->{value}; ## TODO: case
2351 $t = $tt->get_next_token;
2352 if ($length_unit->{$unit}) {
2353 $prop_value{'margin-right'} = ['DIMENSION', $value, $unit];
2354 } else {
2355 $onerror->(type => 'syntax error:'.$prop_name,
2356 level => $self->{must_level},
2357 token => $t);
2358 return ($t, undef);
2359 }
2360 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2361 my $value = $t->{number} * $sign;
2362 $t = $tt->get_next_token;
2363 $prop_value{'margin-right'} = ['PERCENTAGE', $value];
2364 } elsif ($t->{type} == NUMBER_TOKEN and
2365 ($self->{unitless_px} or $t->{number} == 0)) {
2366 my $value = $t->{number} * $sign;
2367 $t = $tt->get_next_token;
2368 $prop_value{'margin-right'} = ['DIMENSION', $value, 'px'];
2369 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2370 my $prop_value = lc $t->{value}; ## TODO: case folding
2371 $t = $tt->get_next_token;
2372 if ($prop_value eq 'auto') {
2373 $prop_value{'margin-right'} = ['KEYWORD', $prop_value];
2374 } else {
2375 $onerror->(type => 'syntax error:'.$prop_name,
2376 level => $self->{must_level},
2377 token => $t);
2378 return ($t, undef);
2379 }
2380 } else {
2381 return ($t, \%prop_value);
2382 }
2383 $prop_value{'margin-left'} = $prop_value{'margin-right'};
2384
2385 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2386 $sign = 1;
2387 if ($t->{type} == MINUS_TOKEN) {
2388 $t = $tt->get_next_token;
2389 $sign = -1;
2390 }
2391
2392 if ($t->{type} == DIMENSION_TOKEN) {
2393 my $value = $t->{number} * $sign;
2394 my $unit = lc $t->{value}; ## TODO: case
2395 $t = $tt->get_next_token;
2396 if ($length_unit->{$unit}) {
2397 $prop_value{'margin-bottom'} = ['DIMENSION', $value, $unit];
2398 } else {
2399 $onerror->(type => 'syntax error:'.$prop_name,
2400 level => $self->{must_level},
2401 token => $t);
2402 return ($t, undef);
2403 }
2404 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2405 my $value = $t->{number} * $sign;
2406 $t = $tt->get_next_token;
2407 $prop_value{'margin-bottom'} = ['PERCENTAGE', $value];
2408 } elsif ($t->{type} == NUMBER_TOKEN and
2409 ($self->{unitless_px} or $t->{number} == 0)) {
2410 my $value = $t->{number} * $sign;
2411 $t = $tt->get_next_token;
2412 $prop_value{'margin-bottom'} = ['DIMENSION', $value, 'px'];
2413 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2414 my $prop_value = lc $t->{value}; ## TODO: case folding
2415 $t = $tt->get_next_token;
2416 if ($prop_value eq 'auto') {
2417 $prop_value{'margin-bottom'} = ['KEYWORD', $prop_value];
2418 } else {
2419 $onerror->(type => 'syntax error:'.$prop_name,
2420 level => $self->{must_level},
2421 token => $t);
2422 return ($t, undef);
2423 }
2424 } else {
2425 return ($t, \%prop_value);
2426 }
2427
2428 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2429 $sign = 1;
2430 if ($t->{type} == MINUS_TOKEN) {
2431 $t = $tt->get_next_token;
2432 $sign = -1;
2433 }
2434
2435 if ($t->{type} == DIMENSION_TOKEN) {
2436 my $value = $t->{number} * $sign;
2437 my $unit = lc $t->{value}; ## TODO: case
2438 $t = $tt->get_next_token;
2439 if ($length_unit->{$unit}) {
2440 $prop_value{'margin-left'} = ['DIMENSION', $value, $unit];
2441 } else {
2442 $onerror->(type => 'syntax error:'.$prop_name,
2443 level => $self->{must_level},
2444 token => $t);
2445 return ($t, undef);
2446 }
2447 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2448 my $value = $t->{number} * $sign;
2449 $t = $tt->get_next_token;
2450 $prop_value{'margin-left'} = ['PERCENTAGE', $value];
2451 } elsif ($t->{type} == NUMBER_TOKEN and
2452 ($self->{unitless_px} or $t->{number} == 0)) {
2453 my $value = $t->{number} * $sign;
2454 $t = $tt->get_next_token;
2455 $prop_value{'margin-left'} = ['DIMENSION', $value, 'px'];
2456 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2457 my $prop_value = lc $t->{value}; ## TODO: case folding
2458 $t = $tt->get_next_token;
2459 if ($prop_value eq 'auto') {
2460 $prop_value{'margin-left'} = ['KEYWORD', $prop_value];
2461 } else {
2462 $onerror->(type => 'syntax error:'.$prop_name,
2463 level => $self->{must_level},
2464 token => $t);
2465 return ($t, undef);
2466 }
2467 } else {
2468 return ($t, \%prop_value);
2469 }
2470
2471 return ($t, \%prop_value);
2472 },
2473 serialize => sub {
2474 my ($self, $prop_name, $value) = @_;
2475
2476 local $Error::Depth = $Error::Depth + 1;
2477 my @v;
2478 push @v, $self->margin_top;
2479 return undef unless defined $v[-1];
2480 push @v, $self->margin_right;
2481 return undef unless defined $v[-1];
2482 push @v, $self->margin_bottom;
2483 return undef unless defined $v[-1];
2484 push @v, $self->margin_left;
2485 return undef unless defined $v[-1];
2486
2487 pop @v if $v[1] eq $v[3];
2488 pop @v if $v[0] eq $v[2];
2489 pop @v if $v[0] eq $v[1];
2490 return join ' ', @v;
2491 },
2492 };
2493 $Attr->{margin} = $Prop->{margin};
2494
2495 $Prop->{padding} = {
2496 css => 'padding',
2497 dom => 'padding',
2498 parse => sub {
2499 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2500
2501 my %prop_value;
2502
2503 my $sign = 1;
2504 if ($t->{type} == MINUS_TOKEN) {
2505 $t = $tt->get_next_token;
2506 $sign = -1;
2507 }
2508
2509 if ($t->{type} == DIMENSION_TOKEN) {
2510 my $value = $t->{number} * $sign;
2511 my $unit = lc $t->{value}; ## TODO: case
2512 $t = $tt->get_next_token;
2513 if ($length_unit->{$unit} and $value >= 0) {
2514 $prop_value{'padding-top'} = ['DIMENSION', $value, $unit];
2515 } else {
2516 $onerror->(type => 'syntax error:'.$prop_name,
2517 level => $self->{must_level},
2518 token => $t);
2519 return ($t, undef);
2520 }
2521 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2522 my $value = $t->{number} * $sign;
2523 $t = $tt->get_next_token;
2524 $prop_value{'padding-top'} = ['PERCENTAGE', $value];
2525 unless ($value >= 0) {
2526 $onerror->(type => 'syntax error:'.$prop_name,
2527 level => $self->{must_level},
2528 token => $t);
2529 return ($t, undef);
2530 }
2531 } elsif ($t->{type} == NUMBER_TOKEN and
2532 ($self->{unitless_px} or $t->{number} == 0)) {
2533 my $value = $t->{number} * $sign;
2534 $t = $tt->get_next_token;
2535 $prop_value{'padding-top'} = ['DIMENSION', $value, 'px'];
2536 unless ($value >= 0) {
2537 $onerror->(type => 'syntax error:'.$prop_name,
2538 level => $self->{must_level},
2539 token => $t);
2540 return ($t, undef);
2541 }
2542 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2543 my $prop_value = lc $t->{value}; ## TODO: case folding
2544 $t = $tt->get_next_token;
2545 if ($prop_value eq 'inherit') {
2546 $prop_value{'padding-top'} = ['INHERIT'];
2547 $prop_value{'padding-right'} = $prop_value{'padding-top'};
2548 $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
2549 $prop_value{'padding-left'} = $prop_value{'padding-right'};
2550 return ($t, \%prop_value);
2551 } else {
2552 $onerror->(type => 'syntax error:'.$prop_name,
2553 level => $self->{must_level},
2554 token => $t);
2555 return ($t, undef);
2556 }
2557 } else {
2558 $onerror->(type => 'syntax error:'.$prop_name,
2559 level => $self->{must_level},
2560 token => $t);
2561 return ($t, undef);
2562 }
2563 $prop_value{'padding-right'} = $prop_value{'padding-top'};
2564 $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
2565 $prop_value{'padding-left'} = $prop_value{'padding-right'};
2566
2567 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2568 $sign = 1;
2569 if ($t->{type} == MINUS_TOKEN) {
2570 $t = $tt->get_next_token;
2571 $sign = -1;
2572 }
2573
2574 if ($t->{type} == DIMENSION_TOKEN) {
2575 my $value = $t->{number} * $sign;
2576 my $unit = lc $t->{value}; ## TODO: case
2577 $t = $tt->get_next_token;
2578 if ($length_unit->{$unit} and $value >= 0) {
2579 $prop_value{'padding-right'} = ['DIMENSION', $value, $unit];
2580 } else {
2581 $onerror->(type => 'syntax error:'.$prop_name,
2582 level => $self->{must_level},
2583 token => $t);
2584 return ($t, undef);
2585 }
2586 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2587 my $value = $t->{number} * $sign;
2588 $t = $tt->get_next_token;
2589 $prop_value{'padding-right'} = ['PERCENTAGE', $value];
2590 unless ($value >= 0) {
2591 $onerror->(type => 'syntax error:'.$prop_name,
2592 level => $self->{must_level},
2593 token => $t);
2594 return ($t, undef);
2595 }
2596 } elsif ($t->{type} == NUMBER_TOKEN and
2597 ($self->{unitless_px} or $t->{number} == 0)) {
2598 my $value = $t->{number} * $sign;
2599 $t = $tt->get_next_token;
2600 $prop_value{'padding-right'} = ['DIMENSION', $value, 'px'];
2601 unless ($value >= 0) {
2602 $onerror->(type => 'syntax error:'.$prop_name,
2603 level => $self->{must_level},
2604 token => $t);
2605 return ($t, undef);
2606 }
2607 } else {
2608 return ($t, \%prop_value);
2609 }
2610 $prop_value{'padding-left'} = $prop_value{'padding-right'};
2611
2612 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2613 $sign = 1;
2614 if ($t->{type} == MINUS_TOKEN) {
2615 $t = $tt->get_next_token;
2616 $sign = -1;
2617 }
2618
2619 if ($t->{type} == DIMENSION_TOKEN) {
2620 my $value = $t->{number} * $sign;
2621 my $unit = lc $t->{value}; ## TODO: case
2622 $t = $tt->get_next_token;
2623 if ($length_unit->{$unit} and $value >= 0) {
2624 $prop_value{'padding-bottom'} = ['DIMENSION', $value, $unit];
2625 } else {
2626 $onerror->(type => 'syntax error:'.$prop_name,
2627 level => $self->{must_level},
2628 token => $t);
2629 return ($t, undef);
2630 }
2631 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2632 my $value = $t->{number} * $sign;
2633 $t = $tt->get_next_token;
2634 $prop_value{'padding-bottom'} = ['PERCENTAGE', $value];
2635 unless ($value >= 0) {
2636 $onerror->(type => 'syntax error:'.$prop_name,
2637 level => $self->{must_level},
2638 token => $t);
2639 return ($t, undef);
2640 }
2641 } elsif ($t->{type} == NUMBER_TOKEN and
2642 ($self->{unitless_px} or $t->{number} == 0)) {
2643 my $value = $t->{number} * $sign;
2644 $t = $tt->get_next_token;
2645 $prop_value{'padding-bottom'} = ['DIMENSION', $value, 'px'];
2646 unless ($value >= 0) {
2647 $onerror->(type => 'syntax error:'.$prop_name,
2648 level => $self->{must_level},
2649 token => $t);
2650 return ($t, undef);
2651 }
2652 } else {
2653 return ($t, \%prop_value);
2654 }
2655
2656 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2657 $sign = 1;
2658 if ($t->{type} == MINUS_TOKEN) {
2659 $t = $tt->get_next_token;
2660 $sign = -1;
2661 }
2662
2663 if ($t->{type} == DIMENSION_TOKEN) {
2664 my $value = $t->{number} * $sign;
2665 my $unit = lc $t->{value}; ## TODO: case
2666 $t = $tt->get_next_token;
2667 if ($length_unit->{$unit} and $value >= 0) {
2668 $prop_value{'padding-left'} = ['DIMENSION', $value, $unit];
2669 } else {
2670 $onerror->(type => 'syntax error:'.$prop_name,
2671 level => $self->{must_level},
2672 token => $t);
2673 return ($t, undef);
2674 }
2675 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2676 my $value = $t->{number} * $sign;
2677 $t = $tt->get_next_token;
2678 $prop_value{'padding-left'} = ['PERCENTAGE', $value];
2679 unless ($value >= 0) {
2680 $onerror->(type => 'syntax error:'.$prop_name,
2681 level => $self->{must_level},
2682 token => $t);
2683 return ($t, undef);
2684 }
2685 } elsif ($t->{type} == NUMBER_TOKEN and
2686 ($self->{unitless_px} or $t->{number} == 0)) {
2687 my $value = $t->{number} * $sign;
2688 $t = $tt->get_next_token;
2689 $prop_value{'padding-left'} = ['DIMENSION', $value, 'px'];
2690 unless ($value >= 0) {
2691 $onerror->(type => 'syntax error:'.$prop_name,
2692 level => $self->{must_level},
2693 token => $t);
2694 return ($t, undef);
2695 }
2696 } else {
2697 return ($t, \%prop_value);
2698 }
2699
2700 return ($t, \%prop_value);
2701 },
2702 serialize => sub {
2703 my ($self, $prop_name, $value) = @_;
2704
2705 local $Error::Depth = $Error::Depth + 1;
2706 my @v;
2707 push @v, $self->padding_top;
2708 return undef unless defined $v[-1];
2709 push @v, $self->padding_right;
2710 return undef unless defined $v[-1];
2711 push @v, $self->padding_bottom;
2712 return undef unless defined $v[-1];
2713 push @v, $self->padding_left;
2714 return undef unless defined $v[-1];
2715
2716 pop @v if $v[1] eq $v[3];
2717 pop @v if $v[0] eq $v[2];
2718 pop @v if $v[0] eq $v[1];
2719 return join ' ', @v;
2720 },
2721 };
2722 $Attr->{padding} = $Prop->{padding};
2723
2724 $Prop->{'border-width'} = {
2725 css => 'border-width',
2726 dom => 'border_width',
2727 parse => sub {
2728 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2729
2730 my %prop_value;
2731
2732 my $sign = 1;
2733 if ($t->{type} == MINUS_TOKEN) {
2734 $t = $tt->get_next_token;
2735 $sign = -1;
2736 }
2737
2738 if ($t->{type} == DIMENSION_TOKEN) {
2739 my $value = $t->{number} * $sign;
2740 my $unit = lc $t->{value}; ## TODO: case
2741 $t = $tt->get_next_token;
2742 if ($length_unit->{$unit} and $value >= 0) {
2743 $prop_value{'border-top-width'} = ['DIMENSION', $value, $unit];
2744 } else {
2745 $onerror->(type => 'syntax error:'.$prop_name,
2746 level => $self->{must_level},
2747 token => $t);
2748 return ($t, undef);
2749 }
2750 } elsif ($t->{type} == NUMBER_TOKEN and
2751 ($self->{unitless_px} or $t->{number} == 0)) {
2752 my $value = $t->{number} * $sign;
2753 $t = $tt->get_next_token;
2754 $prop_value{'border-top-width'} = ['DIMENSION', $value, 'px'];
2755 unless ($value >= 0) {
2756 $onerror->(type => 'syntax error:'.$prop_name,
2757 level => $self->{must_level},
2758 token => $t);
2759 return ($t, undef);
2760 }
2761 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2762 my $prop_value = lc $t->{value}; ## TODO: case folding
2763 $t = $tt->get_next_token;
2764 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
2765 $prop_value{'border-top-width'} = ['KEYWORD', $prop_value];
2766 } elsif ($prop_value eq 'inherit') {
2767 $prop_value{'border-top-width'} = ['INHERIT'];
2768 $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
2769 $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
2770 $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
2771 return ($t, \%prop_value);
2772 } else {
2773 $onerror->(type => 'syntax error:'.$prop_name,
2774 level => $self->{must_level},
2775 token => $t);
2776 return ($t, undef);
2777 }
2778 } else {
2779 $onerror->(type => 'syntax error:'.$prop_name,
2780 level => $self->{must_level},
2781 token => $t);
2782 return ($t, undef);
2783 }
2784 $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
2785 $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
2786 $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
2787
2788 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2789 $sign = 1;
2790 if ($t->{type} == MINUS_TOKEN) {
2791 $t = $tt->get_next_token;
2792 $sign = -1;
2793 }
2794
2795 if ($t->{type} == DIMENSION_TOKEN) {
2796 my $value = $t->{number} * $sign;
2797 my $unit = lc $t->{value}; ## TODO: case
2798 $t = $tt->get_next_token;
2799 if ($length_unit->{$unit} and $value >= 0) {
2800 $prop_value{'border-right-width'} = ['DIMENSION', $value, $unit];
2801 } else {
2802 $onerror->(type => 'syntax error:'.$prop_name,
2803 level => $self->{must_level},
2804 token => $t);
2805 return ($t, undef);
2806 }
2807 } elsif ($t->{type} == NUMBER_TOKEN and
2808 ($self->{unitless_px} or $t->{number} == 0)) {
2809 my $value = $t->{number} * $sign;
2810 $t = $tt->get_next_token;
2811 $prop_value{'border-right-width'} = ['DIMENSION', $value, 'px'];
2812 unless ($value >= 0) {
2813 $onerror->(type => 'syntax error:'.$prop_name,
2814 level => $self->{must_level},
2815 token => $t);
2816 return ($t, undef);
2817 }
2818 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2819 my $prop_value = lc $t->{value}; ## TODO: case
2820 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
2821 $prop_value{'border-right-width'} = ['KEYWORD', $prop_value];
2822 $t = $tt->get_next_token;
2823 }
2824 } else {
2825 return ($t, \%prop_value);
2826 }
2827 $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
2828
2829 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2830 $sign = 1;
2831 if ($t->{type} == MINUS_TOKEN) {
2832 $t = $tt->get_next_token;
2833 $sign = -1;
2834 }
2835
2836 if ($t->{type} == DIMENSION_TOKEN) {
2837 my $value = $t->{number} * $sign;
2838 my $unit = lc $t->{value}; ## TODO: case
2839 $t = $tt->get_next_token;
2840 if ($length_unit->{$unit} and $value >= 0) {
2841 $prop_value{'border-bottom-width'} = ['DIMENSION', $value, $unit];
2842 } else {
2843 $onerror->(type => 'syntax error:'.$prop_name,
2844 level => $self->{must_level},
2845 token => $t);
2846 return ($t, undef);
2847 }
2848 } elsif ($t->{type} == NUMBER_TOKEN and
2849 ($self->{unitless_px} or $t->{number} == 0)) {
2850 my $value = $t->{number} * $sign;
2851 $t = $tt->get_next_token;
2852 $prop_value{'border-bottom-width'} = ['DIMENSION', $value, 'px'];
2853 unless ($value >= 0) {
2854 $onerror->(type => 'syntax error:'.$prop_name,
2855 level => $self->{must_level},
2856 token => $t);
2857 return ($t, undef);
2858 }
2859 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2860 my $prop_value = lc $t->{value}; ## TODO: case
2861 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
2862 $prop_value{'border-bottom-width'} = ['KEYWORD', $prop_value];
2863 $t = $tt->get_next_token;
2864 }
2865 } else {
2866 return ($t, \%prop_value);
2867 }
2868
2869 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2870 $sign = 1;
2871 if ($t->{type} == MINUS_TOKEN) {
2872 $t = $tt->get_next_token;
2873 $sign = -1;
2874 }
2875
2876 if ($t->{type} == DIMENSION_TOKEN) {
2877 my $value = $t->{number} * $sign;
2878 my $unit = lc $t->{value}; ## TODO: case
2879 $t = $tt->get_next_token;
2880 if ($length_unit->{$unit} and $value >= 0) {
2881 $prop_value{'border-left-width'} = ['DIMENSION', $value, $unit];
2882 } else {
2883 $onerror->(type => 'syntax error:'.$prop_name,
2884 level => $self->{must_level},
2885 token => $t);
2886 return ($t, undef);
2887 }
2888 } elsif ($t->{type} == NUMBER_TOKEN and
2889 ($self->{unitless_px} or $t->{number} == 0)) {
2890 my $value = $t->{number} * $sign;
2891 $t = $tt->get_next_token;
2892 $prop_value{'border-left-width'} = ['DIMENSION', $value, 'px'];
2893 unless ($value >= 0) {
2894 $onerror->(type => 'syntax error:'.$prop_name,
2895 level => $self->{must_level},
2896 token => $t);
2897 return ($t, undef);
2898 }
2899 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2900 my $prop_value = lc $t->{value}; ## TODO: case
2901 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
2902 $prop_value{'border-left-width'} = ['KEYWORD', $prop_value];
2903 $t = $tt->get_next_token;
2904 }
2905 } else {
2906 return ($t, \%prop_value);
2907 }
2908
2909 return ($t, \%prop_value);
2910 },
2911 serialize => sub {
2912 my ($self, $prop_name, $value) = @_;
2913
2914 local $Error::Depth = $Error::Depth + 1;
2915 my @v;
2916 push @v, $self->padding_top;
2917 return undef unless defined $v[-1];
2918 push @v, $self->padding_right;
2919 return undef unless defined $v[-1];
2920 push @v, $self->padding_bottom;
2921 return undef unless defined $v[-1];
2922 push @v, $self->padding_left;
2923 return undef unless defined $v[-1];
2924
2925 pop @v if $v[1] eq $v[3];
2926 pop @v if $v[0] eq $v[2];
2927 pop @v if $v[0] eq $v[1];
2928 return join ' ', @v;
2929 },
2930 };
2931 $Attr->{padding} = $Prop->{padding};
2932
2933 $Prop->{'list-style'} = {
2934 css => 'list-style',
2935 dom => 'list_style',
2936 parse => sub {
2937 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2938
2939 my %prop_value;
2940 my $none = 0;
2941
2942 F: for my $f (1..3) {
2943 if ($t->{type} == IDENT_TOKEN) {
2944 my $prop_value = lc $t->{value}; ## TODO: case folding
2945 $t = $tt->get_next_token;
2946
2947 if ($prop_value eq 'none') {
2948 $none++;
2949 } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
2950 if (exists $prop_value{'list-style-type'}) {
2951 $onerror->(type => q[syntax error:duplicate:'list-style-type':].
2952 $prop_name,
2953 level => $self->{must_level},
2954 token => $t);
2955 return ($t, undef);
2956 } else {
2957 $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
2958 }
2959 } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
2960 if (exists $prop_value{'list-style-position'}) {
2961 $onerror->(type => q[syntax error:duplicate:'list-style-position':].
2962 $prop_name,
2963 level => $self->{must_level},
2964 token => $t);
2965 return ($t, undef);
2966 }
2967
2968 $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
2969 } elsif ($f == 1 and $prop_value eq 'inherit') {
2970 $prop_value{'list-style-type'} = ["INHERIT"];
2971 $prop_value{'list-style-position'} = ["INHERIT"];
2972 $prop_value{'list-style-image'} = ["INHERIT"];
2973 last F;
2974 } else {
2975 if ($f == 1) {
2976 $onerror->(type => 'syntax error:'.$prop_name,
2977 level => $self->{must_level},
2978 token => $t);
2979 return ($t, undef);
2980 } else {
2981 last F;
2982 }
2983 }
2984 } elsif ($t->{type} == URI_TOKEN) {
2985 if (exists $prop_value{'list-style-image'}) {
2986 $onerror->(type => q[syntax error:duplicate:'list-style-image':].
2987 $prop_name,
2988 level => $self->{must_level},
2989 token => $t);
2990 return ($t, undef);
2991 }
2992
2993 $prop_value{'list-style-image'}
2994 = ['URI', $t->{value}, \($self->{base_uri})];
2995 $t = $tt->get_next_token;
2996 } else {
2997 if ($f == 1) {
2998 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2999 level => $self->{must_level},
3000 token => $t);
3001 return ($t, undef);
3002 } else {
3003 last F;
3004 }
3005 }
3006
3007 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3008 } # F
3009 ## NOTE: No browser support |list-style: url(xxx|{EOF}.
3010
3011 if ($none == 1) {
3012 if (exists $prop_value{'list-style-type'}) {
3013 if (exists $prop_value{'list-style-image'}) {
3014 $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3015 $prop_name,
3016 level => $self->{must_level},
3017 token => $t);
3018 return ($t, undef);
3019 } else {
3020 $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
3021 }
3022 } else {
3023 $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
3024 $prop_value{'list-style-image'} = ['KEYWORD', 'none']
3025 unless exists $prop_value{'list-style-image'};
3026 }
3027 } elsif ($none == 2) {
3028 if (exists $prop_value{'list-style-type'}) {
3029 $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3030 $prop_name,
3031 level => $self->{must_level},
3032 token => $t);
3033 return ($t, undef);
3034 }
3035 if (exists $prop_value{'list-style-image'}) {
3036 $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3037 $prop_name,
3038 level => $self->{must_level},
3039 token => $t);
3040 return ($t, undef);
3041 }
3042
3043 $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
3044 $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
3045 } elsif ($none == 3) {
3046 $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3047 $prop_name,
3048 level => $self->{must_level},
3049 token => $t);
3050 return ($t, undef);
3051 }
3052
3053 for (qw/list-style-type list-style-position list-style-image/) {
3054 $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
3055 }
3056
3057 return ($t, \%prop_value);
3058 },
3059 serialize => sub {
3060 my ($self, $prop_name, $value) = @_;
3061
3062 local $Error::Depth = $Error::Depth + 1;
3063 return $self->list_style_type . ' ' . $self->list_style_position .
3064 ' ' . $self->list_style_image;
3065 },
3066 };
3067 $Attr->{list_style} = $Prop->{'list-style'};
3068
3069 ## NOTE: Future version of the implementation will change the way to
3070 ## store the parsed value to support CSS 3 properties.
3071 $Prop->{'text-decoration'} = {
3072 css => 'text-decoration',
3073 dom => 'text_decoration',
3074 key => 'text_decoration',
3075 parse => sub {
3076 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3077
3078 my $value = ['DECORATION']; # , underline, overline, line-through, blink
3079
3080 if ($t->{type} == IDENT_TOKEN) {
3081 my $v = lc $t->{value}; ## TODO: case
3082 $t = $tt->get_next_token;
3083 if ($v eq 'inherit') {
3084 return ($t, {$prop_name => ['INHERIT']});
3085 } elsif ($v eq 'none') {
3086 return ($t, {$prop_name => $value});
3087 } elsif ($v eq 'underline' and
3088 $self->{prop_value}->{$prop_name}->{$v}) {
3089 $value->[1] = 1;
3090 } elsif ($v eq 'overline' and
3091 $self->{prop_value}->{$prop_name}->{$v}) {
3092 $value->[2] = 1;
3093 } elsif ($v eq 'line-through' and
3094 $self->{prop_value}->{$prop_name}->{$v}) {
3095 $value->[3] = 1;
3096 } elsif ($v eq 'blink' and
3097 $self->{prop_value}->{$prop_name}->{$v}) {
3098 $value->[4] = 1;
3099 } else {
3100 $onerror->(type => 'syntax error:'.$prop_name,
3101 level => $self->{must_level},
3102 token => $t);
3103 return ($t, undef);
3104 }
3105 }
3106
3107 F: {
3108 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3109 last F unless $t->{type} == IDENT_TOKEN;
3110
3111 my $v = lc $t->{value}; ## TODO: case
3112 $t = $tt->get_next_token;
3113 if ($v eq 'underline' and
3114 $self->{prop_value}->{$prop_name}->{$v}) {
3115 $value->[1] = 1;
3116 } elsif ($v eq 'overline' and
3117 $self->{prop_value}->{$prop_name}->{$v}) {
3118 $value->[1] = 2;
3119 } elsif ($v eq 'line-through' and
3120 $self->{prop_value}->{$prop_name}->{$v}) {
3121 $value->[1] = 3;
3122 } elsif ($v eq 'blink' and
3123 $self->{prop_value}->{$prop_name}->{$v}) {
3124 $value->[1] = 4;
3125 } else {
3126 last F;
3127 }
3128
3129 redo F;
3130 } # F
3131
3132 return ($t, {$prop_name => $value});
3133 },
3134 serialize => $default_serializer,
3135 initial => ["KEYWORD", "none"],
3136 #inherited => 0,
3137 compute => $compute_as_specified,
3138 };
3139 $Attr->{text_decoration} = $Prop->{'text-decoration'};
3140 $Key->{text_decoration} = $Prop->{'text-decoration'};
3141
3142 1;
3143 ## $Date: 2008/01/03 12:23:46 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24