/[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.27 - (show annotations) (download)
Sun Jan 6 10:33:01 2008 UTC (18 years, 4 months ago) by wakaba
Branch: MAIN
Changes since 1.26: +214 -2 lines
++ whatpm/Whatpm/CSS/ChangeLog	6 Jan 2008 10:32:49 -0000
	* Parser.pm (background-position, background-position-x,
	background-position-y): Implemented.

2008-01-06  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 'PERCENTAGE') {
452 return $value->[1] . '%';
453 } elsif ($value->[0] eq 'KEYWORD') {
454 return $value->[1];
455 } elsif ($value->[0] eq 'URI') {
456 ## NOTE: This is what browsers do.
457 return 'url('.$value->[1].')';
458 } elsif ($value->[0] eq 'INHERIT') {
459 return 'inherit';
460 } elsif ($value->[0] eq 'DECORATION') {
461 my @v = ();
462 push @v, 'underline' if $value->[1];
463 push @v, 'overline' if $value->[2];
464 push @v, 'line-through' if $value->[3];
465 push @v, 'blink' if $value->[4];
466 return 'none' unless @v;
467 return join ' ', @v;
468 } else {
469 return undef;
470 }
471 }; # $default_serializer
472
473 $Prop->{color} = {
474 css => 'color',
475 dom => 'color',
476 key => 'color',
477 parse => sub {
478 my ($self, $prop_name, $tt, $t, $onerror) = @_;
479
480 if ($t->{type} == IDENT_TOKEN) {
481 if (lc $t->{value} eq 'blue') { ## TODO: case folding
482 $t = $tt->get_next_token;
483 return ($t, {$prop_name => ["RGBA", 0, 0, 255, 1]});
484 } else {
485 #
486 }
487 } else {
488 #
489 }
490
491 $onerror->(type => 'syntax error:color',
492 level => $self->{must_level},
493 token => $t);
494
495 return ($t, undef);
496 },
497 serialize => sub {
498 my ($self, $prop_name, $value) = @_;
499 if ($value->[0] eq 'RGBA') { ## TODO: %d? %f?
500 return sprintf 'rgba(%d, %d, %d, %f)', @$value[1, 2, 3, 4];
501 } else {
502 return undef;
503 }
504 },
505 initial => ["KEYWORD", "-manakai-initial-color"], ## NOTE: UA-dependent in CSS 2.1.
506 inherited => 1,
507 compute => $compute_as_specified,
508 };
509 $Attr->{color} = $Prop->{color};
510 $Key->{color} = $Prop->{color};
511
512 my $one_keyword_parser = sub {
513 my ($self, $prop_name, $tt, $t, $onerror) = @_;
514
515 if ($t->{type} == IDENT_TOKEN) {
516 my $prop_value = lc $t->{value}; ## TODO: case folding
517 $t = $tt->get_next_token;
518 if ($Prop->{$prop_name}->{keyword}->{$prop_value} and
519 $self->{prop_value}->{$prop_name}->{$prop_value}) {
520 return ($t, {$prop_name => ["KEYWORD", $prop_value]});
521 } elsif ($prop_value eq 'inherit') {
522 return ($t, {$prop_name => ['INHERIT']});
523 }
524 }
525
526 $onerror->(type => 'syntax error:keyword:'.$prop_name,
527 level => $self->{must_level},
528 token => $t);
529 return ($t, undef);
530 };
531
532 $Prop->{display} = {
533 css => 'display',
534 dom => 'display',
535 key => 'display',
536 parse => $one_keyword_parser,
537 serialize => $default_serializer,
538 keyword => {
539 block => 1, inline => 1, 'inline-block' => 1, 'inline-table' => 1,
540 'list-item' => 1, none => 1,
541 table => 1, 'table-caption' => 1, 'table-cell' => 1, 'table-column' => 1,
542 'table-column-group' => 1, 'table-header-group' => 1,
543 'table-footer-group' => 1, 'table-row' => 1, 'table-row-group' => 1,
544 },
545 initial => ["KEYWORD", "inline"],
546 #inherited => 0,
547 compute => sub {
548 my ($self, $element, $prop_name, $specified_value) = @_;
549 ## NOTE: CSS 2.1 Section 9.7.
550
551 ## WARNING: |compute| for 'float' property invoke this CODE
552 ## in some case. Careless modification might cause a infinite loop.
553
554 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
555 if ($specified_value->[1] eq 'none') {
556 ## Case 1 [CSS 2.1]
557 return $specified_value;
558 } else {
559 my $position = $self->get_computed_value ($element, 'position');
560 if ($position->[0] eq 'KEYWORD' and
561 ($position->[1] eq 'absolute' or
562 $position->[1] eq 'fixed')) {
563 ## Case 2 [CSS 2.1]
564 #
565 } else {
566 my $float = $self->get_computed_value ($element, 'float');
567 if ($float->[0] eq 'KEYWORD' and $float->[1] ne 'none') {
568 ## Caes 3 [CSS 2.1]
569 #
570 } elsif (not defined $element->manakai_parent_element) {
571 ## Case 4 [CSS 2.1]
572 #
573 } else {
574 ## Case 5 [CSS 2.1]
575 return $specified_value;
576 }
577 }
578
579 return ["KEYWORD",
580 {
581 'inline-table' => 'table',
582 inline => 'block',
583 'run-in' => 'block',
584 'table-row-group' => 'block',
585 'table-column' => 'block',
586 'table-column-group' => 'block',
587 'table-header-group' => 'block',
588 'table-footer-group' => 'block',
589 'table-row' => 'block',
590 'table-cell' => 'block',
591 'table-caption' => 'block',
592 'inline-block' => 'block',
593 }->{$specified_value->[1]} || $specified_value->[1]];
594 }
595 } else {
596 return $specified_value; ## Maybe an error of the implementation.
597 }
598 },
599 };
600 $Attr->{display} = $Prop->{display};
601 $Key->{display} = $Prop->{display};
602
603 $Prop->{position} = {
604 css => 'position',
605 dom => 'position',
606 key => 'position',
607 parse => $one_keyword_parser,
608 serialize => $default_serializer,
609 keyword => {
610 static => 1, relative => 1, absolute => 1, fixed => 1,
611 },
612 initial => ["KEYWORD", "static"],
613 #inherited => 0,
614 compute => $compute_as_specified,
615 };
616 $Attr->{position} = $Prop->{position};
617 $Key->{position} = $Prop->{position};
618
619 $Prop->{float} = {
620 css => 'float',
621 dom => 'css_float',
622 key => 'float',
623 parse => $one_keyword_parser,
624 serialize => $default_serializer,
625 keyword => {
626 left => 1, right => 1, none => 1,
627 },
628 initial => ["KEYWORD", "none"],
629 #inherited => 0,
630 compute => sub {
631 my ($self, $element, $prop_name, $specified_value) = @_;
632 ## NOTE: CSS 2.1 Section 9.7.
633
634 ## WARNING: |compute| for 'display' property invoke this CODE
635 ## in some case. Careless modification might cause a infinite loop.
636
637 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
638 if ($specified_value->[1] eq 'none') {
639 ## Case 1 [CSS 2.1]
640 return $specified_value;
641 } else {
642 my $position = $self->get_computed_value ($element, 'position');
643 if ($position->[0] eq 'KEYWORD' and
644 ($position->[1] eq 'absolute' or
645 $position->[1] eq 'fixed')) {
646 ## Case 2 [CSS 2.1]
647 return ["KEYWORD", "none"];
648 }
649 }
650 }
651
652 ## ISSUE: CSS 2.1 section 9.7 and 9.5.1 ('float' definition) disagree
653 ## on computed value of 'float' property.
654
655 ## Case 3, 4, and 5 [CSS 2.1]
656 return $specified_value;
657 },
658 };
659 $Attr->{css_float} = $Prop->{float};
660 $Attr->{style_float} = $Prop->{float}; ## NOTE: IEism
661 $Key->{float} = $Prop->{float};
662
663 $Prop->{clear} = {
664 css => 'clear',
665 dom => 'clear',
666 key => 'clear',
667 parse => $one_keyword_parser,
668 serialize => $default_serializer,
669 keyword => {
670 left => 1, right => 1, none => 1, both => 1,
671 },
672 initial => ["KEYWORD", "none"],
673 #inherited => 0,
674 compute => $compute_as_specified,
675 };
676 $Attr->{clear} = $Prop->{clear};
677 $Key->{clear} = $Prop->{clear};
678
679 $Prop->{direction} = {
680 css => 'direction',
681 dom => 'direction',
682 key => 'direction',
683 parse => $one_keyword_parser,
684 serialize => $default_serializer,
685 keyword => {
686 ltr => 1, rtl => 1,
687 },
688 initial => ["KEYWORD", "ltr"],
689 inherited => 1,
690 compute => $compute_as_specified,
691 };
692 $Attr->{direction} = $Prop->{direction};
693 $Key->{direction} = $Prop->{direction};
694
695 $Prop->{'unicode-bidi'} = {
696 css => 'unicode-bidi',
697 dom => 'unicode_bidi',
698 key => 'unicode_bidi',
699 parse => $one_keyword_parser,
700 serialize => $default_serializer,
701 keyword => {
702 normal => 1, embed => 1, 'bidi-override' => 1,
703 },
704 initial => ["KEYWORD", "normal"],
705 #inherited => 0,
706 compute => $compute_as_specified,
707 };
708 $Attr->{unicode_bidi} = $Prop->{'unicode-bidi'};
709 $Key->{unicode_bidi} = $Prop->{'unicode-bidi'};
710
711 $Prop->{overflow} = {
712 css => 'overflow',
713 dom => 'overflow',
714 key => 'overflow',
715 parse => $one_keyword_parser,
716 serialize => $default_serializer,
717 keyword => {
718 visible => 1, hidden => 1, scroll => 1, auto => 1,
719 },
720 initial => ["KEYWORD", "visible"],
721 #inherited => 0,
722 compute => $compute_as_specified,
723 };
724 $Attr->{overflow} = $Prop->{overflow};
725 $Key->{overflow} = $Prop->{overflow};
726
727 $Prop->{visibility} = {
728 css => 'visibility',
729 dom => 'visibility',
730 key => 'visibility',
731 parse => $one_keyword_parser,
732 serialize => $default_serializer,
733 keyword => {
734 visible => 1, hidden => 1, collapse => 1,
735 },
736 initial => ["KEYWORD", "visible"],
737 #inherited => 0,
738 compute => $compute_as_specified,
739 };
740 $Attr->{visibility} = $Prop->{visibility};
741 $Key->{visibility} = $Prop->{visibility};
742
743 $Prop->{'list-style-type'} = {
744 css => 'list-style-type',
745 dom => 'list_style_type',
746 key => 'list_style_type',
747 parse => $one_keyword_parser,
748 serialize => $default_serializer,
749 keyword => {
750 qw/
751 disc 1 circle 1 square 1 decimal 1 decimal-leading-zero 1
752 lower-roman 1 upper-roman 1 lower-greek 1 lower-latin 1
753 upper-latin 1 armenian 1 georgian 1 lower-alpha 1 upper-alpha 1
754 none 1
755 /,
756 },
757 initial => ["KEYWORD", 'disc'],
758 inherited => 1,
759 compute => $compute_as_specified,
760 };
761 $Attr->{list_style_type} = $Prop->{'list-style-type'};
762 $Key->{list_style_type} = $Prop->{'list-style-type'};
763
764 $Prop->{'list-style-position'} = {
765 css => 'list-style-position',
766 dom => 'list_style_position',
767 key => 'list_style_position',
768 parse => $one_keyword_parser,
769 serialize => $default_serializer,
770 keyword => {
771 inside => 1, outside => 1,
772 },
773 initial => ["KEYWORD", 'outside'],
774 inherited => 1,
775 compute => $compute_as_specified,
776 };
777 $Attr->{list_style_position} = $Prop->{'list-style-position'};
778 $Key->{list_style_position} = $Prop->{'list-style-position'};
779
780 $Prop->{'page-break-before'} = {
781 css => 'page-break-before',
782 dom => 'page_break_before',
783 key => 'page_break_before',
784 parse => $one_keyword_parser,
785 serialize => $default_serializer,
786 keyword => {
787 auto => 1, always => 1, avoid => 1, left => 1, right => 1,
788 },
789 initial => ["KEYWORD", 'auto'],
790 #inherited => 0,
791 compute => $compute_as_specified,
792 };
793 $Attr->{page_break_before} = $Prop->{'page-break-before'};
794 $Key->{page_break_before} = $Prop->{'page-break-before'};
795
796 $Prop->{'page-break-after'} = {
797 css => 'page-break-after',
798 dom => 'page_break_after',
799 key => 'page_break_after',
800 parse => $one_keyword_parser,
801 serialize => $default_serializer,
802 keyword => {
803 auto => 1, always => 1, avoid => 1, left => 1, right => 1,
804 },
805 initial => ["KEYWORD", 'auto'],
806 #inherited => 0,
807 compute => $compute_as_specified,
808 };
809 $Attr->{page_break_after} = $Prop->{'page-break-after'};
810 $Key->{page_break_after} = $Prop->{'page-break-after'};
811
812 $Prop->{'page-break-inside'} = {
813 css => 'page-break-inside',
814 dom => 'page_break_inside',
815 key => 'page_break_inside',
816 parse => $one_keyword_parser,
817 serialize => $default_serializer,
818 keyword => {
819 auto => 1, avoid => 1,
820 },
821 initial => ["KEYWORD", 'auto'],
822 inherited => 1,
823 compute => $compute_as_specified,
824 };
825 $Attr->{page_break_inside} = $Prop->{'page-break-inside'};
826 $Key->{page_break_inside} = $Prop->{'page-break-inside'};
827
828 $Prop->{'background-repeat'} = {
829 css => 'background-repeat',
830 dom => 'background_repeat',
831 key => 'background_repeat',
832 parse => $one_keyword_parser,
833 serialize => $default_serializer,
834 keyword => {
835 repeat => 1, 'repeat-x' => 1, 'repeat-y' => 1, 'no-repeat' => 1,
836 },
837 initial => ["KEYWORD", 'repeat'],
838 #inherited => 0,
839 compute => $compute_as_specified,
840 };
841 $Attr->{background_repeat} = $Prop->{'background-repeat'};
842 $Key->{backgroud_repeat} = $Prop->{'background-repeat'};
843
844 $Prop->{'background-attachment'} = {
845 css => 'background-attachment',
846 dom => 'background_attachment',
847 key => 'background_attachment',
848 parse => $one_keyword_parser,
849 serialize => $default_serializer,
850 keyword => {
851 scroll => 1, fixed => 1,
852 },
853 initial => ["KEYWORD", 'scroll'],
854 #inherited => 0,
855 compute => $compute_as_specified,
856 };
857 $Attr->{background_attachment} = $Prop->{'background-attachment'};
858 $Key->{backgroud_attachment} = $Prop->{'background-attachment'};
859
860 $Prop->{'font-style'} = {
861 css => 'font-style',
862 dom => 'font_style',
863 key => 'font_style',
864 parse => $one_keyword_parser,
865 serialize => $default_serializer,
866 keyword => {
867 normal => 1, italic => 1, oblique => 1,
868 },
869 initial => ["KEYWORD", 'normal'],
870 inherited => 1,
871 compute => $compute_as_specified,
872 };
873 $Attr->{font_style} = $Prop->{'font-style'};
874 $Key->{font_style} = $Prop->{'font-style'};
875
876 $Prop->{'font-variant'} = {
877 css => 'font-variant',
878 dom => 'font_variant',
879 key => 'font_variant',
880 parse => $one_keyword_parser,
881 serialize => $default_serializer,
882 keyword => {
883 normal => 1, 'small-caps' => 1,
884 },
885 initial => ["KEYWORD", 'normal'],
886 inherited => 1,
887 compute => $compute_as_specified,
888 };
889 $Attr->{font_variant} = $Prop->{'font-variant'};
890 $Key->{font_variant} = $Prop->{'font-variant'};
891
892 $Prop->{'text-align'} = {
893 css => 'text-align',
894 dom => 'text_align',
895 key => 'text_align',
896 parse => $one_keyword_parser,
897 serialize => $default_serializer,
898 keyword => {
899 left => 1, right => 1, center => 1, justify => 1, ## CSS 2
900 begin => 1, end => 1, ## CSS 3
901 },
902 initial => ["KEYWORD", 'begin'],
903 inherited => 1,
904 compute => $compute_as_specified,
905 };
906 $Attr->{text_align} = $Prop->{'text-align'};
907 $Key->{text_align} = $Prop->{'text-align'};
908
909 $Prop->{'text-transform'} = {
910 css => 'text-transform',
911 dom => 'text_transform',
912 key => 'text_transform',
913 parse => $one_keyword_parser,
914 serialize => $default_serializer,
915 keyword => {
916 capitalize => 1, uppercase => 1, lowercase => 1, none => 1,
917 },
918 initial => ["KEYWORD", 'none'],
919 inherited => 1,
920 compute => $compute_as_specified,
921 };
922 $Attr->{text_transform} = $Prop->{'text-transform'};
923 $Key->{text_transform} = $Prop->{'text-transform'};
924
925 $Prop->{'white-space'} = {
926 css => 'white-space',
927 dom => 'white_space',
928 key => 'white_space',
929 parse => $one_keyword_parser,
930 serialize => $default_serializer,
931 keyword => {
932 normal => 1, pre => 1, nowrap => 1, 'pre-wrap' => 1, 'pre-line' => 1,
933 },
934 initial => ["KEYWORD", 'normal'],
935 inherited => 1,
936 compute => $compute_as_specified,
937 };
938 $Attr->{white_space} = $Prop->{'white-space'};
939 $Key->{white_space} = $Prop->{'white-space'};
940
941 $Prop->{'caption-side'} = {
942 css => 'caption-side',
943 dom => 'caption_side',
944 key => 'caption_side',
945 parse => $one_keyword_parser,
946 serialize => $default_serializer,
947 keyword => {
948 top => 1, bottom => 1,
949 },
950 initial => ['KEYWORD', 'top'],
951 inherited => 1,
952 compute => $compute_as_specified,
953 };
954 $Attr->{caption_side} = $Prop->{'caption-side'};
955 $Key->{caption_side} = $Prop->{'caption-side'};
956
957 $Prop->{'table-layout'} = {
958 css => 'table-layout',
959 dom => 'table_layout',
960 key => 'table_layout',
961 parse => $one_keyword_parser,
962 serialize => $default_serializer,
963 keyword => {
964 auto => 1, fixed => 1,
965 },
966 initial => ['KEYWORD', 'auto'],
967 #inherited => 0,
968 compute => $compute_as_specified,
969 };
970 $Attr->{table_layout} = $Prop->{'table-layout'};
971 $Key->{table_layout} = $Prop->{'table-layout'};
972
973 $Prop->{'border-collapse'} = {
974 css => 'border-collapse',
975 dom => 'border_collapse',
976 key => 'border_collapse',
977 parse => $one_keyword_parser,
978 serialize => $default_serializer,
979 keyword => {
980 collapse => 1, separate => 1,
981 },
982 initial => ['KEYWORD', 'separate'],
983 inherited => 1,
984 compute => $compute_as_specified,
985 };
986 $Attr->{border_collapse} = $Prop->{'border-collapse'};
987 $Key->{border_collapse} = $Prop->{'border-collapse'};
988
989 $Prop->{'empty-cells'} = {
990 css => 'empty-cells',
991 dom => 'empty_cells',
992 key => 'empty_cells',
993 parse => $one_keyword_parser,
994 serialize => $default_serializer,
995 keyword => {
996 show => 1, hide => 1,
997 },
998 initial => ['KEYWORD', 'show'],
999 inherited => 1,
1000 compute => $compute_as_specified,
1001 };
1002 $Attr->{empty_cells} = $Prop->{'empty-cells'};
1003 $Key->{empty_cells} = $Prop->{'empty-cells'};
1004
1005 $Prop->{'z-index'} = {
1006 css => 'z-index',
1007 dom => 'z_index',
1008 key => 'z_index',
1009 parse => sub {
1010 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1011
1012 my $sign = 1;
1013 if ($t->{type} == MINUS_TOKEN) {
1014 $sign = -1;
1015 $t = $tt->get_next_token;
1016 }
1017
1018 if ($t->{type} == NUMBER_TOKEN) {
1019 ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/z-index> for
1020 ## browser compatibility issue.
1021 my $value = $t->{number};
1022 $t = $tt->get_next_token;
1023 return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
1024 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1025 my $value = lc $t->{value}; ## TODO: case
1026 $t = $tt->get_next_token;
1027 if ($value eq 'auto') {
1028 ## NOTE: |z-index| is the default value and therefore it must be
1029 ## supported anyway.
1030 return ($t, {$prop_name => ["KEYWORD", 'auto']});
1031 } elsif ($value eq 'inherit') {
1032 return ($t, {$prop_name => ['INHERIT']});
1033 }
1034 }
1035
1036 $onerror->(type => 'syntax error:'.$prop_name,
1037 level => $self->{must_level},
1038 token => $t);
1039 return ($t, undef);
1040 },
1041 serialize => $default_serializer,
1042 initial => ['KEYWORD', 'auto'],
1043 #inherited => 0,
1044 compute => $compute_as_specified,
1045 };
1046 $Attr->{z_index} = $Prop->{'z-index'};
1047 $Key->{z_index} = $Prop->{'z-index'};
1048
1049 $Prop->{orphans} = {
1050 css => 'orphans',
1051 dom => 'orphans',
1052 key => 'orphans',
1053 parse => sub {
1054 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1055
1056 my $sign = 1;
1057 if ($t->{type} == MINUS_TOKEN) {
1058 $t = $tt->get_next_token;
1059 $sign = -1;
1060 }
1061
1062 if ($t->{type} == NUMBER_TOKEN) {
1063 ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/orphans> and
1064 ## <http://suika.fam.cx/gate/2005/sw/widows> for
1065 ## browser compatibility issue.
1066 my $value = $t->{number};
1067 $t = $tt->get_next_token;
1068 return ($t, {$prop_name => ["NUMBER", $sign * int ($value / 1)]});
1069 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1070 my $value = lc $t->{value}; ## TODO: case
1071 $t = $tt->get_next_token;
1072 if ($value eq 'inherit') {
1073 return ($t, {$prop_name => ['INHERIT']});
1074 }
1075 }
1076
1077 $onerror->(type => 'syntax error:'.$prop_name,
1078 level => $self->{must_level},
1079 token => $t);
1080 return ($t, undef);
1081 },
1082 serialize => $default_serializer,
1083 initial => ['NUMBER', 2],
1084 inherited => 1,
1085 compute => $compute_as_specified,
1086 };
1087 $Attr->{orphans} = $Prop->{orphans};
1088 $Key->{orphans} = $Prop->{orphans};
1089
1090 $Prop->{widows} = {
1091 css => 'widows',
1092 dom => 'widows',
1093 key => 'widows',
1094 parse => $Prop->{orphans}->{parse},
1095 serialize => $default_serializer,
1096 initial => ['NUMBER', 2],
1097 inherited => 1,
1098 compute => $compute_as_specified,
1099 };
1100 $Attr->{widows} = $Prop->{widows};
1101 $Key->{widows} = $Prop->{widows};
1102
1103 my $length_unit = {
1104 em => 1, ex => 1, px => 1,
1105 in => 1, cm => 1, mm => 1, pt => 1, pc => 1,
1106 };
1107
1108 $Prop->{'font-size'} = {
1109 css => 'font-size',
1110 dom => 'font_size',
1111 key => 'font_size',
1112 parse => sub {
1113 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1114
1115 my $sign = 1;
1116 if ($t->{type} == MINUS_TOKEN) {
1117 $t = $tt->get_next_token;
1118 $sign = -1;
1119 }
1120
1121 if ($t->{type} == DIMENSION_TOKEN) {
1122 my $value = $t->{number} * $sign;
1123 my $unit = lc $t->{value}; ## TODO: case
1124 $t = $tt->get_next_token;
1125 if ($length_unit->{$unit} and $value >= 0) {
1126 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1127 }
1128 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1129 my $value = $t->{number} * $sign;
1130 $t = $tt->get_next_token;
1131 return ($t, {$prop_name => ['PERCENTAGE', $value]}) if $value >= 0;
1132 } elsif ($t->{type} == NUMBER_TOKEN and
1133 ($self->{unitless_px} or $t->{number} == 0)) {
1134 my $value = $t->{number} * $sign;
1135 $t = $tt->get_next_token;
1136 return ($t, {$prop_name => ['DIMENSION', $value, 'px']}) if $value >= 0;
1137 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1138 my $value = lc $t->{value}; ## TODO: case
1139 $t = $tt->get_next_token;
1140 if ({
1141 'xx-small' => 1, 'x-small' => 1, small => 1, medium => 1,
1142 large => 1, 'x-large' => 1, 'xx-large' => 1,
1143 '-manakai-xxx-large' => 1, # -webkit-xxx-large
1144 larger => 1, smaller => 1,
1145 }->{$value}) {
1146 return ($t, {$prop_name => ['KEYWORD', $value]});
1147 } elsif ($value eq 'inherit') {
1148 return ($t, {$prop_name => ['INHERIT']});
1149 }
1150 }
1151
1152 $onerror->(type => 'syntax error:'.$prop_name,
1153 level => $self->{must_level},
1154 token => $t);
1155 return ($t, undef);
1156 },
1157 serialize => $default_serializer,
1158 initial => ['KEYWORD', 'medium'],
1159 inherited => 1,
1160 compute => sub {
1161 my ($self, $element, $prop_name, $specified_value) = @_;
1162
1163 if (defined $specified_value) {
1164 if ($specified_value->[0] eq 'DIMENSION') {
1165 my $unit = $specified_value->[2];
1166 my $value = $specified_value->[1];
1167
1168 if ($unit eq 'em' or $unit eq 'ex') {
1169 $value *= 0.5 if $unit eq 'ex';
1170 ## TODO: Preferred way to determine the |ex| size is defined
1171 ## in CSS 2.1.
1172
1173 my $parent_element = $element->manakai_parent_element;
1174 if (defined $parent_element) {
1175 $value *= $self->get_computed_value ($parent_element, $prop_name)
1176 ->[1];
1177 } else {
1178 $value *= $self->{font_size}->[3]; # medium
1179 }
1180 $unit = 'px';
1181 } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
1182 ($value *= 12, $unit = 'pc') if $unit eq 'pc';
1183 ($value /= 72, $unit = 'in') if $unit eq 'pt';
1184 ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
1185 ($value *= 10, $unit = 'mm') if $unit eq 'cm';
1186 ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
1187 }
1188 ## else: consistency error
1189
1190 return ['DIMENSION', $value, $unit];
1191 } elsif ($specified_value->[0] eq 'PERCENTAGE') {
1192 my $parent_element = $element->manakai_parent_element;
1193 my $parent_cv;
1194 if (defined $parent_element) {
1195 $parent_cv = $self->get_computed_value
1196 ($parent_element, $prop_name);
1197 } else {
1198 $parent_cv = [undef, $self->{font_size}->[3]];
1199 }
1200 return ['DIMENSION', $parent_cv->[1] * $specified_value->[1] / 100,
1201 'px'];
1202 } elsif ($specified_value->[0] eq 'KEYWORD') {
1203 if ($specified_value->[1] eq 'larger') {
1204 my $parent_element = $element->manakai_parent_element;
1205 if (defined $parent_element) {
1206 my $parent_cv = $self->get_computed_value
1207 ($parent_element, $prop_name);
1208 return ['DIMENSION',
1209 $self->{get_larger_font_size}->($self, $parent_cv->[1]),
1210 'px'];
1211 } else { ## 'larger' relative to 'medium', initial of 'font-size'
1212 return ['DIMENSION', $self->{font_size}->[4], 'px'];
1213 }
1214 } elsif ($specified_value->[1] eq 'smaller') {
1215 my $parent_element = $element->manakai_parent_element;
1216 if (defined $parent_element) {
1217 my $parent_cv = $self->get_computed_value
1218 ($parent_element, $prop_name);
1219 return ['DIMENSION',
1220 $self->{get_smaller_font_size}->($self, $parent_cv->[1]),
1221 'px'];
1222 } else { ## 'smaller' relative to 'medium', initial of 'font-size'
1223 return ['DIMENSION', $self->{font_size}->[2], 'px'];
1224 }
1225 } else {
1226 return ['DIMENSION', $self->{font_size}->[{
1227 'xx-small' => 0,
1228 'x-small' => 1,
1229 small => 2,
1230 medium => 3,
1231 large => 4,
1232 'x-large' => 5,
1233 'xx-large' => 6,
1234 '-manakai-xxx-large' => 7,
1235 }->{$specified_value->[1]}], 'px'];
1236 }
1237 }
1238 }
1239
1240 return $specified_value;
1241 },
1242 };
1243 $Attr->{font_size} = $Prop->{'font-size'};
1244 $Key->{font_size} = $Prop->{'font-size'};
1245
1246 my $compute_length = sub {
1247 my ($self, $element, $prop_name, $specified_value) = @_;
1248
1249 if (defined $specified_value) {
1250 if ($specified_value->[0] eq 'DIMENSION') {
1251 my $unit = $specified_value->[2];
1252 my $value = $specified_value->[1];
1253
1254 if ($unit eq 'em' or $unit eq 'ex') {
1255 $value *= 0.5 if $unit eq 'ex';
1256 ## TODO: Preferred way to determine the |ex| size is defined
1257 ## in CSS 2.1.
1258
1259 $value *= $self->get_computed_value ($element, 'font-size')->[1];
1260 $unit = 'px';
1261 } elsif ({in => 1, cm => 1, mm => 1, pt => 1, pc => 1}->{$unit}) {
1262 ($value *= 12, $unit = 'pc') if $unit eq 'pc';
1263 ($value /= 72, $unit = 'in') if $unit eq 'pt';
1264 ($value *= 2.54, $unit = 'cm') if $unit eq 'in';
1265 ($value *= 10, $unit = 'mm') if $unit eq 'cm';
1266 ($value /= 0.26, $unit = 'px') if $unit eq 'mm';
1267 }
1268
1269 return ['DIMENSION', $value, $unit];
1270 }
1271 }
1272
1273 return $specified_value;
1274 }; # $compute_length
1275
1276 $Prop->{'letter-spacing'} = {
1277 css => 'letter-spacing',
1278 dom => 'letter_spacing',
1279 key => 'letter_spacing',
1280 parse => sub {
1281 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1282
1283 ## NOTE: Used also for 'word-spacing', '-manakai-border-spacing-x',
1284 ## and '-manakai-border-spacing-y'.
1285
1286 my $sign = 1;
1287 if ($t->{type} == MINUS_TOKEN) {
1288 $t = $tt->get_next_token;
1289 $sign = -1;
1290 }
1291 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
1292
1293 if ($t->{type} == DIMENSION_TOKEN) {
1294 my $value = $t->{number} * $sign;
1295 my $unit = lc $t->{value}; ## TODO: case
1296 $t = $tt->get_next_token;
1297 if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
1298 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1299 }
1300 } elsif ($t->{type} == NUMBER_TOKEN and
1301 ($self->{unitless_px} or $t->{number} == 0)) {
1302 my $value = $t->{number} * $sign;
1303 $t = $tt->get_next_token;
1304 return ($t, {$prop_name => ['DIMENSION', $value, 'px']})
1305 if $allow_negative or $value >= 0;
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 ($Prop->{$prop_name}->{keyword}->{$value}) {
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 allow_negative => 1,
1322 keyword => {normal => 1},
1323 serialize => $default_serializer,
1324 initial => ['KEYWORD', 'normal'],
1325 inherited => 1,
1326 compute => $compute_length,
1327 };
1328 $Attr->{letter_spacing} = $Prop->{'letter-spacing'};
1329 $Key->{letter_spacing} = $Prop->{'letter-spacing'};
1330
1331 $Prop->{'word-spacing'} = {
1332 css => 'word-spacing',
1333 dom => 'word_spacing',
1334 key => 'word_spacing',
1335 parse => $Prop->{'letter-spacing'}->{parse},
1336 allow_negative => 1,
1337 keyword => {normal => 1},
1338 serialize => $default_serializer,
1339 initial => ['KEYWORD', 'normal'],
1340 inherited => 1,
1341 compute => $compute_length,
1342 };
1343 $Attr->{word_spacing} = $Prop->{'word-spacing'};
1344 $Key->{word_spacing} = $Prop->{'word-spacing'};
1345
1346 $Prop->{'-manakai-border-spacing-x'} = {
1347 css => '-manakai-border-spacing-x',
1348 dom => '_manakai_border_spacing_x',
1349 key => 'border_spacing_x',
1350 parse => $Prop->{'letter-spacing'}->{parse},
1351 #allow_negative => 0,
1352 #keyword => {},
1353 serialize => $default_serializer,
1354 serialize_multiple => sub {
1355 my $self = shift;
1356
1357 local $Error::Depth = $Error::Depth + 1;
1358 my $x = $self->_manakai_border_spacing_x;
1359 my $y = $self->_manakai_border_spacing_y;
1360 my $xi = $self->get_property_priority ('-manakai-border-spacing-x');
1361 my $yi = $self->get_property_priority ('-manakai-border-spacing-y');
1362 $xi = ' !' . $xi if length $xi;
1363 $yi = ' !' . $yi if length $yi;
1364 if (defined $x) {
1365 if (defined $y) {
1366 if ($xi eq $yi) {
1367 if ($x eq $y) {
1368 return {'border-spacing' => $x . $xi};
1369 } else {
1370 return {'border-spacing' => $x . ' ' . $y . $xi};
1371 }
1372 } else {
1373 return {'-manakai-border-spacing-x' => $x . $xi,
1374 '-manakai-border-spacing-y' => $y . $yi};
1375 }
1376 } else {
1377 return {'-manakai-border-spacing-x' => $x . $xi};
1378 }
1379 } else {
1380 if (defined $y) {
1381 return {'-manakai-border-spacing-y' => $y . $yi};
1382 } else {
1383 return {};
1384 }
1385 }
1386 },
1387 initial => ['DIMENSION', 0, 'px'],
1388 inherited => 1,
1389 compute => $compute_length,
1390 };
1391 $Attr->{_manakai_border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
1392 $Key->{border_spacing_x} = $Prop->{'-manakai-border-spacing-x'};
1393
1394 $Prop->{'-manakai-border-spacing-y'} = {
1395 css => '-manakai-border-spacing-y',
1396 dom => '_manakai_border_spacing_y',
1397 key => 'border_spacing_y',
1398 parse => $Prop->{'letter-spacing'}->{parse},
1399 #allow_negative => 0,
1400 #keyword => {},
1401 serialize => $default_serializer,
1402 serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
1403 ->{serialize_multiple},
1404 initial => ['DIMENSION', 0, 'px'],
1405 inherited => 1,
1406 compute => $compute_length,
1407 };
1408 $Attr->{_manakai_border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
1409 $Key->{border_spacing_y} = $Prop->{'-manakai-border-spacing-y'};
1410
1411 $Prop->{'margin-top'} = {
1412 css => 'margin-top',
1413 dom => 'margin_top',
1414 key => 'margin_top',
1415 parse => sub {
1416 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1417
1418 ## NOTE: Used for 'margin-top', 'margin-right', 'margin-bottom',
1419 ## 'margin-left', 'top', 'right', 'bottom', 'left', 'padding-top',
1420 ## 'padding-right', 'padding-bottom', 'padding-left',
1421 ## 'border-top-width', 'border-right-width', 'border-bottom-width',
1422 ## 'border-left-width', 'text-indent', 'background-position-x',
1423 ## and 'background-position-y'.
1424
1425 my $sign = 1;
1426 if ($t->{type} == MINUS_TOKEN) {
1427 $t = $tt->get_next_token;
1428 $sign = -1;
1429 }
1430 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
1431
1432 if ($t->{type} == DIMENSION_TOKEN) {
1433 my $value = $t->{number} * $sign;
1434 my $unit = lc $t->{value}; ## TODO: case
1435 $t = $tt->get_next_token;
1436 if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
1437 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1438 }
1439 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1440 my $value = $t->{number} * $sign;
1441 $t = $tt->get_next_token;
1442 return ($t, {$prop_name => ['PERCENTAGE', $value]})
1443 if $allow_negative or $value >= 0;
1444 } elsif ($t->{type} == NUMBER_TOKEN and
1445 ($self->{unitless_px} or $t->{number} == 0)) {
1446 my $value = $t->{number} * $sign;
1447 $t = $tt->get_next_token;
1448 return ($t, {$prop_name => ['DIMENSION', $value, 'px']})
1449 if $allow_negative or $value >= 0;
1450 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1451 my $value = lc $t->{value}; ## TODO: case
1452 $t = $tt->get_next_token;
1453 if ($Prop->{$prop_name}->{keyword}->{$value}) {
1454 return ($t, {$prop_name => ['KEYWORD', $value]});
1455 } elsif ($value eq 'inherit') {
1456 return ($t, {$prop_name => ['INHERIT']});
1457 }
1458 }
1459
1460 $onerror->(type => 'syntax error:'.$prop_name,
1461 level => $self->{must_level},
1462 token => $t);
1463 return ($t, undef);
1464 },
1465 allow_negative => 1,
1466 keyword => {auto => 1},
1467 serialize => $default_serializer,
1468 initial => ['DIMENSION', 0, 'px'],
1469 #inherited => 0,
1470 compute => $compute_length,
1471 };
1472 $Attr->{margin_top} = $Prop->{'margin-top'};
1473 $Key->{margin_top} = $Prop->{'margin-top'};
1474
1475 $Prop->{'margin-bottom'} = {
1476 css => 'margin-bottom',
1477 dom => 'margin_bottom',
1478 key => 'margin_bottom',
1479 parse => $Prop->{'margin-top'}->{parse},
1480 allow_negative => 1,
1481 keyword => {auto => 1},
1482 serialize => $default_serializer,
1483 initial => ['DIMENSION', 0, 'px'],
1484 #inherited => 0,
1485 compute => $compute_length,
1486 };
1487 $Attr->{margin_bottom} = $Prop->{'margin-bottom'};
1488 $Key->{margin_bottom} = $Prop->{'margin-bottom'};
1489
1490 $Prop->{'margin-right'} = {
1491 css => 'margin-right',
1492 dom => 'margin_right',
1493 key => 'margin_right',
1494 parse => $Prop->{'margin-top'}->{parse},
1495 allow_negative => 1,
1496 keyword => {auto => 1},
1497 serialize => $default_serializer,
1498 initial => ['DIMENSION', 0, 'px'],
1499 #inherited => 0,
1500 compute => $compute_length,
1501 };
1502 $Attr->{margin_right} = $Prop->{'margin-right'};
1503 $Key->{margin_right} = $Prop->{'margin-right'};
1504
1505 $Prop->{'margin-left'} = {
1506 css => 'margin-left',
1507 dom => 'margin_left',
1508 key => 'margin_left',
1509 parse => $Prop->{'margin-top'}->{parse},
1510 allow_negative => 1,
1511 keyword => {auto => 1},
1512 serialize => $default_serializer,
1513 initial => ['DIMENSION', 0, 'px'],
1514 #inherited => 0,
1515 compute => $compute_length,
1516 };
1517 $Attr->{margin_left} = $Prop->{'margin-left'};
1518 $Key->{margin_left} = $Prop->{'margin-left'};
1519
1520 $Prop->{top} = {
1521 css => 'top',
1522 dom => 'top',
1523 key => 'top',
1524 parse => $Prop->{'margin-top'}->{parse},
1525 allow_negative => 1,
1526 keyword => {auto => 1},
1527 serialize => $default_serializer,
1528 initial => ['KEYWORD', 'auto'],
1529 #inherited => 0,
1530 compute_multiple => sub {
1531 my ($self, $element, $eid, $prop_name) = @_;
1532
1533 my $pos_value = $self->get_computed_value ($element, 'position');
1534 if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
1535 if ($pos_value->[1] eq 'static') {
1536 $self->{computed_value}->{$eid}->{top} = ['KEYWORD', 'auto'];
1537 $self->{computed_value}->{$eid}->{bottom} = ['KEYWORD', 'auto'];
1538 return;
1539 } elsif ($pos_value->[1] eq 'relative') {
1540 my $top_specified = $self->get_specified_value_no_inherit
1541 ($element, 'top');
1542 if (defined $top_specified and
1543 ($top_specified->[0] eq 'DIMENSION' or
1544 $top_specified->[0] eq 'PERCENTAGE')) {
1545 my $tv = $self->{computed_value}->{$eid}->{top}
1546 = $compute_length->($self, $element, 'top', $top_specified);
1547 $self->{computed_value}->{$eid}->{bottom}
1548 = [$tv->[0], -$tv->[1], $tv->[2]];
1549 } else { # top: auto
1550 my $bottom_specified = $self->get_specified_value_no_inherit
1551 ($element, 'bottom');
1552 if (defined $bottom_specified and
1553 ($bottom_specified->[0] eq 'DIMENSION' or
1554 $bottom_specified->[0] eq 'PERCENTAGE')) {
1555 my $tv = $self->{computed_value}->{$eid}->{bottom}
1556 = $compute_length->($self, $element, 'bottom',
1557 $bottom_specified);
1558 $self->{computed_value}->{$eid}->{top}
1559 = [$tv->[0], -$tv->[1], $tv->[2]];
1560 } else { # bottom: auto
1561 $self->{computed_value}->{$eid}->{top} = ['DIMENSION', 0, 'px'];
1562 $self->{computed_value}->{$eid}->{bottom} = ['DIMENSION', 0, 'px'];
1563 }
1564 }
1565 return;
1566 }
1567 }
1568
1569 my $top_specified = $self->get_specified_value_no_inherit
1570 ($element, 'top');
1571 $self->{computed_value}->{$eid}->{top}
1572 = $compute_length->($self, $element, 'top', $top_specified);
1573 my $bottom_specified = $self->get_specified_value_no_inherit
1574 ($element, 'bottom');
1575 $self->{computed_value}->{$eid}->{bottom}
1576 = $compute_length->($self, $element, 'bottom', $bottom_specified);
1577 },
1578 };
1579 $Attr->{top} = $Prop->{top};
1580 $Key->{top} = $Prop->{top};
1581
1582 $Prop->{bottom} = {
1583 css => 'bottom',
1584 dom => 'bottom',
1585 key => 'bottom',
1586 parse => $Prop->{'margin-top'}->{parse},
1587 allow_negative => 1,
1588 keyword => {auto => 1},
1589 serialize => $default_serializer,
1590 initial => ['KEYWORD', 'auto'],
1591 #inherited => 0,
1592 compute_multiple => $Prop->{top}->{compute_multiple},
1593 };
1594 $Attr->{bottom} = $Prop->{bottom};
1595 $Key->{bottom} = $Prop->{bottom};
1596
1597 $Prop->{left} = {
1598 css => 'left',
1599 dom => 'left',
1600 key => 'left',
1601 parse => $Prop->{'margin-top'}->{parse},
1602 allow_negative => 1,
1603 keyword => {auto => 1},
1604 serialize => $default_serializer,
1605 initial => ['KEYWORD', 'auto'],
1606 #inherited => 0,
1607 compute_multiple => sub {
1608 my ($self, $element, $eid, $prop_name) = @_;
1609
1610 my $pos_value = $self->get_computed_value ($element, 'position');
1611 if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
1612 if ($pos_value->[1] eq 'static') {
1613 $self->{computed_value}->{$eid}->{left} = ['KEYWORD', 'auto'];
1614 $self->{computed_value}->{$eid}->{right} = ['KEYWORD', 'auto'];
1615 return;
1616 } elsif ($pos_value->[1] eq 'relative') {
1617 my $left_specified = $self->get_specified_value_no_inherit
1618 ($element, 'left');
1619 if (defined $left_specified and
1620 ($left_specified->[0] eq 'DIMENSION' or
1621 $left_specified->[0] eq 'PERCENTAGE')) {
1622 my $right_specified = $self->get_specified_value_no_inherit
1623 ($element, 'right');
1624 if (defined $right_specified and
1625 ($right_specified->[0] eq 'DIMENSION' or
1626 $right_specified->[0] eq 'PERCENTAGE')) {
1627 my $direction = $self->get_computed_value ($element, 'direction');
1628 if (defined $direction and $direction->[0] eq 'KEYWORD' and
1629 $direction->[0] eq 'ltr') {
1630 my $tv = $self->{computed_value}->{$eid}->{left}
1631 = $compute_length->($self, $element, 'left',
1632 $left_specified);
1633 $self->{computed_value}->{$eid}->{right}
1634 = [$tv->[0], -$tv->[1], $tv->[2]];
1635 } else {
1636 my $tv = $self->{computed_value}->{$eid}->{right}
1637 = $compute_length->($self, $element, 'right',
1638 $right_specified);
1639 $self->{computed_value}->{$eid}->{left}
1640 = [$tv->[0], -$tv->[1], $tv->[2]];
1641 }
1642 } else {
1643 my $tv = $self->{computed_value}->{$eid}->{left}
1644 = $compute_length->($self, $element, 'left', $left_specified);
1645 $self->{computed_value}->{$eid}->{right}
1646 = [$tv->[0], -$tv->[1], $tv->[2]];
1647 }
1648 } else { # left: auto
1649 my $right_specified = $self->get_specified_value_no_inherit
1650 ($element, 'right');
1651 if (defined $right_specified and
1652 ($right_specified->[0] eq 'DIMENSION' or
1653 $right_specified->[0] eq 'PERCENTAGE')) {
1654 my $tv = $self->{computed_value}->{$eid}->{right}
1655 = $compute_length->($self, $element, 'right',
1656 $right_specified);
1657 $self->{computed_value}->{$eid}->{left}
1658 = [$tv->[0], -$tv->[1], $tv->[2]];
1659 } else { # right: auto
1660 $self->{computed_value}->{$eid}->{left} = ['DIMENSION', 0, 'px'];
1661 $self->{computed_value}->{$eid}->{right} = ['DIMENSION', 0, 'px'];
1662 }
1663 }
1664 return;
1665 }
1666 }
1667
1668 my $left_specified = $self->get_specified_value_no_inherit
1669 ($element, 'left');
1670 $self->{computed_value}->{$eid}->{left}
1671 = $compute_length->($self, $element, 'left', $left_specified);
1672 my $right_specified = $self->get_specified_value_no_inherit
1673 ($element, 'right');
1674 $self->{computed_value}->{$eid}->{right}
1675 = $compute_length->($self, $element, 'right', $right_specified);
1676 },
1677 };
1678 $Attr->{left} = $Prop->{left};
1679 $Key->{left} = $Prop->{left};
1680
1681 $Prop->{right} = {
1682 css => 'right',
1683 dom => 'right',
1684 key => 'right',
1685 parse => $Prop->{'margin-top'}->{parse},
1686 serialize => $default_serializer,
1687 initial => ['KEYWORD', 'auto'],
1688 #inherited => 0,
1689 compute_multiple => $Prop->{left}->{compute_multiple},
1690 };
1691 $Attr->{right} = $Prop->{right};
1692 $Key->{right} = $Prop->{right};
1693
1694 $Prop->{width} = {
1695 css => 'width',
1696 dom => 'width',
1697 key => 'width',
1698 parse => $Prop->{'margin-top'}->{parse},
1699 #allow_negative => 0,
1700 keyword => {auto => 1},
1701 serialize => $default_serializer,
1702 initial => ['KEYWORD', 'auto'],
1703 #inherited => 0,
1704 compute => $compute_length,
1705 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/width> for
1706 ## browser compatibility issues.
1707 };
1708 $Attr->{width} = $Prop->{width};
1709 $Key->{width} = $Prop->{width};
1710
1711 $Prop->{'min-width'} = {
1712 css => 'min-width',
1713 dom => 'min_width',
1714 key => 'min_width',
1715 parse => $Prop->{'margin-top'}->{parse},
1716 #allow_negative => 0,
1717 #keyword => {},
1718 serialize => $default_serializer,
1719 initial => ['DIMENSION', 0, 'px'],
1720 #inherited => 0,
1721 compute => $compute_length,
1722 };
1723 $Attr->{min_width} = $Prop->{'min-width'};
1724 $Key->{min_width} = $Prop->{'min-width'};
1725
1726 $Prop->{'max-width'} = {
1727 css => 'max-width',
1728 dom => 'max_width',
1729 key => 'max_width',
1730 parse => $Prop->{'margin-top'}->{parse},
1731 #allow_negative => 0,
1732 keyword => {none => 1},
1733 serialize => $default_serializer,
1734 initial => ['KEYWORD', 'none'],
1735 #inherited => 0,
1736 compute => $compute_length,
1737 };
1738 $Attr->{max_width} = $Prop->{'max-width'};
1739 $Key->{max_width} = $Prop->{'max-width'};
1740
1741 $Prop->{height} = {
1742 css => 'height',
1743 dom => 'height',
1744 key => 'height',
1745 parse => $Prop->{'margin-top'}->{parse},
1746 #allow_negative => 0,
1747 keyword => {auto => 1},
1748 serialize => $default_serializer,
1749 initial => ['KEYWORD', 'auto'],
1750 #inherited => 0,
1751 compute => $compute_length,
1752 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/height> for
1753 ## browser compatibility issues.
1754 };
1755 $Attr->{height} = $Prop->{height};
1756 $Key->{height} = $Prop->{height};
1757
1758 $Prop->{'min-height'} = {
1759 css => 'min-height',
1760 dom => 'min_height',
1761 key => 'min_height',
1762 parse => $Prop->{'margin-top'}->{parse},
1763 #allow_negative => 0,
1764 #keyword => {},
1765 serialize => $default_serializer,
1766 initial => ['DIMENSION', 0, 'px'],
1767 #inherited => 0,
1768 compute => $compute_length,
1769 };
1770 $Attr->{min_height} = $Prop->{'min-height'};
1771 $Key->{min_height} = $Prop->{'min-height'};
1772
1773 $Prop->{'max-height'} = {
1774 css => 'max-height',
1775 dom => 'max_height',
1776 key => 'max_height',
1777 parse => $Prop->{'margin-top'}->{parse},
1778 #allow_negative => 0,
1779 keyword => {none => 1},
1780 serialize => $default_serializer,
1781 initial => ['KEYWORD', 'none'],
1782 #inherited => 0,
1783 compute => $compute_length,
1784 };
1785 $Attr->{max_height} = $Prop->{'max-height'};
1786 $Key->{max_height} = $Prop->{'max-height'};
1787
1788 $Prop->{'line-height'} = {
1789 css => 'line-height',
1790 dom => 'line_height',
1791 key => 'line_height',
1792 parse => sub {
1793 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1794
1795 ## NOTE: Similar to 'margin-top', but different handling
1796 ## for unitless numbers.
1797
1798 my $sign = 1;
1799 if ($t->{type} == MINUS_TOKEN) {
1800 $t = $tt->get_next_token;
1801 $sign = -1;
1802 }
1803 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
1804
1805 if ($t->{type} == DIMENSION_TOKEN) {
1806 my $value = $t->{number} * $sign;
1807 my $unit = lc $t->{value}; ## TODO: case
1808 $t = $tt->get_next_token;
1809 if ($length_unit->{$unit} and $value >= 0) {
1810 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1811 }
1812 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1813 my $value = $t->{number} * $sign;
1814 $t = $tt->get_next_token;
1815 return ($t, {$prop_name => ['PERCENTAGE', $value]})
1816 if $value >= 0;
1817 } elsif ($t->{type} == NUMBER_TOKEN) {
1818 my $value = $t->{number} * $sign;
1819 $t = $tt->get_next_token;
1820 return ($t, {$prop_name => ['NUMBER', $value]}) if $value >= 0;
1821 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1822 my $value = lc $t->{value}; ## TODO: case
1823 $t = $tt->get_next_token;
1824 if ($value eq 'normal') {
1825 return ($t, {$prop_name => ['KEYWORD', $value]});
1826 } elsif ($value eq 'inherit') {
1827 return ($t, {$prop_name => ['INHERIT']});
1828 }
1829 }
1830
1831 $onerror->(type => 'syntax error:'.$prop_name,
1832 level => $self->{must_level},
1833 token => $t);
1834 return ($t, undef);
1835 },
1836 serialize => $default_serializer,
1837 initial => ['KEYWORD', 'normal'],
1838 inherited => 1,
1839 compute => $compute_length,
1840 };
1841 $Attr->{line_height} = $Prop->{'line-height'};
1842 $Key->{line_height} = $Prop->{'line-height'};
1843
1844 $Prop->{'vertical-align'} = {
1845 css => 'vertical-align',
1846 dom => 'vertical_align',
1847 key => 'vertical_align',
1848 parse => $Prop->{'margin-top'}->{parse},
1849 allow_negative => 1,
1850 keyword => {
1851 baseline => 1, sub => 1, super => 1, top => 1, 'text-top' => 1,
1852 middle => 1, bottom => 1, 'text-bottom' => 1,
1853 },
1854 ## NOTE: Currently, we don't support option to select subset of keywords
1855 ## supported by application (i.e.
1856 ## $parser->{prop_value}->{'line-height'->{$keyword}). Should we support
1857 ## it?
1858 serialize => $default_serializer,
1859 initial => ['KEYWORD', 'baseline'],
1860 #inherited => 0,
1861 compute => $compute_length,
1862 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/vertical-align> for
1863 ## browser compatibility issues.
1864 };
1865 $Attr->{vertical_align} = $Prop->{'vertical-align'};
1866 $Key->{vertical_align} = $Prop->{'vertical-align'};
1867
1868 $Prop->{'text-indent'} = {
1869 css => 'text-indent',
1870 dom => 'text_indent',
1871 key => 'text_indent',
1872 parse => $Prop->{'margin-top'}->{parse},
1873 allow_negative => 1,
1874 keyword => {},
1875 serialize => $default_serializer,
1876 initial => ['DIMENSION', 0, 'px'],
1877 inherited => 1,
1878 compute => $compute_length,
1879 };
1880 $Attr->{text_indent} = $Prop->{'text-indent'};
1881 $Key->{text_indent} = $Prop->{'text-indent'};
1882
1883 $Prop->{'background-position-x'} = {
1884 css => 'background-position-x',
1885 dom => 'background_position_x',
1886 key => 'background_position_x',
1887 parse => $Prop->{'margin-top'}->{parse},
1888 allow_negative => 1,
1889 keyword => {left => 1, center => 1, right => 1},
1890 serialize => $default_serializer,
1891 initial => ['PERCENTAGE', 0],
1892 #inherited => 0,
1893 compute => sub {
1894 my ($self, $element, $prop_name, $specified_value) = @_;
1895
1896 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
1897 my $v = {
1898 left => 0, center => 50, right => 100, top => 0, bottom => 100,
1899 }->{$specified_value->[1]};
1900 if (defined $v) {
1901 return ['PERCENTAGE', $v];
1902 } else {
1903 return $specified_value;
1904 }
1905 } else {
1906 return $compute_length->(@_);
1907 }
1908 },
1909 serialize_multiple => sub {
1910 my $self = shift;
1911
1912 local $Error::Depth = $Error::Depth + 1;
1913 my $x = $self->background_position_x;
1914 my $y = $self->background_position_y;
1915 my $xi = $self->get_property_priority ('background-position-x');
1916 my $yi = $self->get_property_priority ('background-position-y');
1917 $xi = ' !' . $xi if length $xi;
1918 $yi = ' !' . $yi if length $yi;
1919 if (defined $x) {
1920 if (defined $y) {
1921 if ($xi eq $yi) {
1922 return {'background-position' => $x . ' ' . $y . $xi};
1923 } else {
1924 return {'background-position-x' => $x . $xi,
1925 'background-position-y' => $y . $yi};
1926 }
1927 } else {
1928 return {'background-position-x' => $x . $xi};
1929 }
1930 } else {
1931 if (defined $y) {
1932 return {'background-position-y' => $y . $yi};
1933 } else {
1934 return {};
1935 }
1936 }
1937 },
1938 };
1939 $Attr->{background_position_x} = $Prop->{'background-position-x'};
1940 $Key->{background_position_x} = $Prop->{'background-position-x'};
1941
1942 $Prop->{'background-position-y'} = {
1943 css => 'background-position-y',
1944 dom => 'background_position_y',
1945 key => 'background_position_y',
1946 parse => $Prop->{'margin-top'}->{parse},
1947 allow_negative => 1,
1948 keyword => {top => 1, center => 1, bottom => 1},
1949 serialize => $default_serializer,
1950 serialize_multiple => $Prop->{'background-position-x'}->{serialize_multiple},
1951 initial => ['PERCENTAGE', 0],
1952 #inherited => 0,
1953 compute => $Prop->{'background-position-x'}->{compute},
1954 };
1955 $Attr->{background_position_y} = $Prop->{'background-position-y'};
1956 $Key->{background_position_y} = $Prop->{'background-position-y'};
1957
1958 $Prop->{'padding-top'} = {
1959 css => 'padding-top',
1960 dom => 'padding_top',
1961 key => 'padding_top',
1962 parse => $Prop->{'margin-top'}->{parse},
1963 #allow_negative => 0,
1964 #keyword => {},
1965 serialize => $default_serializer,
1966 initial => ['DIMENSION', 0, 'px'],
1967 #inherited => 0,
1968 compute => $compute_length,
1969 };
1970 $Attr->{padding_top} = $Prop->{'padding-top'};
1971 $Key->{padding_top} = $Prop->{'padding-top'};
1972
1973 $Prop->{'padding-bottom'} = {
1974 css => 'padding-bottom',
1975 dom => 'padding_bottom',
1976 key => 'padding_bottom',
1977 parse => $Prop->{'padding-top'}->{parse},
1978 #allow_negative => 0,
1979 #keyword => {},
1980 serialize => $default_serializer,
1981 initial => ['DIMENSION', 0, 'px'],
1982 #inherited => 0,
1983 compute => $compute_length,
1984 };
1985 $Attr->{padding_bottom} = $Prop->{'padding-bottom'};
1986 $Key->{padding_bottom} = $Prop->{'padding-bottom'};
1987
1988 $Prop->{'padding-right'} = {
1989 css => 'padding-right',
1990 dom => 'padding_right',
1991 key => 'padding_right',
1992 parse => $Prop->{'padding-top'}->{parse},
1993 #allow_negative => 0,
1994 #keyword => {},
1995 serialize => $default_serializer,
1996 initial => ['DIMENSION', 0, 'px'],
1997 #inherited => 0,
1998 compute => $compute_length,
1999 };
2000 $Attr->{padding_right} = $Prop->{'padding-right'};
2001 $Key->{padding_right} = $Prop->{'padding-right'};
2002
2003 $Prop->{'padding-left'} = {
2004 css => 'padding-left',
2005 dom => 'padding_left',
2006 key => 'padding_left',
2007 parse => $Prop->{'padding-top'}->{parse},
2008 #allow_negative => 0,
2009 #keyword => {},
2010 serialize => $default_serializer,
2011 initial => ['DIMENSION', 0, 'px'],
2012 #inherited => 0,
2013 compute => $compute_length,
2014 };
2015 $Attr->{padding_left} = $Prop->{'padding-left'};
2016 $Key->{padding_left} = $Prop->{'padding-left'};
2017
2018 $Prop->{'border-top-width'} = {
2019 css => 'border-top-width',
2020 dom => 'border_top_width',
2021 key => 'border_top_width',
2022 parse => $Prop->{'margin-top'}->{parse},
2023 #allow_negative => 0,
2024 keyword => {thin => 1, medium => 1, thick => 1},
2025 serialize => $default_serializer,
2026 initial => ['KEYWORD', 'medium'],
2027 #inherited => 0,
2028 compute => sub {
2029 my ($self, $element, $prop_name, $specified_value) = @_;
2030
2031 ## NOTE: Used for 'border-top-width', 'border-right-width',
2032 ## 'border-bottom-width', 'border-right-width', and
2033 ## 'outline-width'.
2034
2035 my $style_prop = $prop_name;
2036 $style_prop =~ s/width/style/;
2037 my $style = $self->get_computed_value ($element, $style_prop);
2038 if (defined $style and $style->[0] eq 'KEYWORD' and
2039 ($style->[1] eq 'none' or $style->[1] eq 'hidden')) {
2040 return ['DIMENSION', 0, 'px'];
2041 }
2042
2043 my $value = $compute_length->(@_);
2044 if (defined $value and $value->[0] eq 'KEYWORD') {
2045 if ($value->[1] eq 'thin') {
2046 return ['DIMENSION', 1, 'px']; ## Firefox/Opera
2047 } elsif ($value->[1] eq 'medium') {
2048 return ['DIMENSION', 3, 'px']; ## Firefox/Opera
2049 } elsif ($value->[1] eq 'thick') {
2050 return ['DIMENSION', 5, 'px']; ## Firefox
2051 }
2052 }
2053 return $value;
2054 },
2055 ## NOTE: CSS3 will allow <percentage> as an option in <border-width>.
2056 ## Opera 9 has already implemented it.
2057 };
2058 $Attr->{border_top_width} = $Prop->{'border-top-width'};
2059 $Key->{border_top_width} = $Prop->{'border-top-width'};
2060
2061 $Prop->{'border-right-width'} = {
2062 css => 'border-right-width',
2063 dom => 'border_right_width',
2064 key => 'border_right_width',
2065 parse => $Prop->{'border-top-width'}->{parse},
2066 #allow_negative => 0,
2067 keyword => {thin => 1, medium => 1, thick => 1},
2068 serialize => $default_serializer,
2069 initial => ['KEYWORD', 'medium'],
2070 #inherited => 0,
2071 compute => $Prop->{'border-top-width'}->{compute},
2072 };
2073 $Attr->{border_right_width} = $Prop->{'border-right-width'};
2074 $Key->{border_right_width} = $Prop->{'border-right-width'};
2075
2076 $Prop->{'border-bottom-width'} = {
2077 css => 'border-bottom-width',
2078 dom => 'border_bottom_width',
2079 key => 'border_bottom_width',
2080 parse => $Prop->{'border-top-width'}->{parse},
2081 #allow_negative => 0,
2082 keyword => {thin => 1, medium => 1, thick => 1},
2083 serialize => $default_serializer,
2084 initial => ['KEYWORD', 'medium'],
2085 #inherited => 0,
2086 compute => $Prop->{'border-top-width'}->{compute},
2087 };
2088 $Attr->{border_bottom_width} = $Prop->{'border-bottom-width'};
2089 $Key->{border_bottom_width} = $Prop->{'border-bottom-width'};
2090
2091 $Prop->{'border-left-width'} = {
2092 css => 'border-left-width',
2093 dom => 'border_left_width',
2094 key => 'border_left_width',
2095 parse => $Prop->{'border-top-width'}->{parse},
2096 #allow_negative => 0,
2097 keyword => {thin => 1, medium => 1, thick => 1},
2098 serialize => $default_serializer,
2099 initial => ['KEYWORD', 'medium'],
2100 #inherited => 0,
2101 compute => $Prop->{'border-top-width'}->{compute},
2102 };
2103 $Attr->{border_left_width} = $Prop->{'border-left-width'};
2104 $Key->{border_left_width} = $Prop->{'border-left-width'};
2105
2106 $Prop->{'outline-width'} = {
2107 css => 'outline-width',
2108 dom => 'outline_width',
2109 key => 'outline_width',
2110 parse => $Prop->{'border-top-width'}->{parse},
2111 #allow_negative => 0,
2112 keyword => {thin => 1, medium => 1, thick => 1},
2113 serialize => $default_serializer,
2114 initial => ['KEYWORD', 'medium'],
2115 #inherited => 0,
2116 compute => $Prop->{'border-top-width'}->{compute},
2117 };
2118 $Attr->{outline_width} = $Prop->{'outline-width'};
2119 $Key->{outline_width} = $Prop->{'outline-width'};
2120
2121 $Prop->{'font-weight'} = {
2122 css => 'font-weight',
2123 dom => 'font_weight',
2124 key => 'font_weight',
2125 parse => sub {
2126 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2127
2128 if ($t->{type} == NUMBER_TOKEN) {
2129 ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/font-weight> for
2130 ## browser compatibility issue.
2131 my $value = $t->{number};
2132 $t = $tt->get_next_token;
2133 if ($value % 100 == 0 and 100 <= $value and $value <= 900) {
2134 return ($t, {$prop_name => ['WEIGHT', $value, 0]});
2135 }
2136 } elsif ($t->{type} == IDENT_TOKEN) {
2137 my $value = lc $t->{value}; ## TODO: case
2138 $t = $tt->get_next_token;
2139 if ({
2140 normal => 1, bold => 1, bolder => 1, lighter => 1,
2141 }->{$value}) {
2142 return ($t, {$prop_name => ['KEYWORD', $value]});
2143 } elsif ($value eq 'inherit') {
2144 return ($t, {$prop_name => ['INHERIT']});
2145 }
2146 }
2147
2148 $onerror->(type => 'syntax error:'.$prop_name,
2149 level => $self->{must_level},
2150 token => $t);
2151 return ($t, undef);
2152 },
2153 serialize => $default_serializer,
2154 initial => ['KEYWORD', 'normal'],
2155 inherited => 1,
2156 compute => sub {
2157 my ($self, $element, $prop_name, $specified_value) = @_;
2158
2159 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
2160 if ($specified_value->[1] eq 'normal') {
2161 return ['WEIGHT', 400, 0];
2162 } elsif ($specified_value->[1] eq 'bold') {
2163 return ['WEIGHT', 700, 0];
2164 } elsif ($specified_value->[1] eq 'bolder') {
2165 my $parent_element = $element->manakai_parent_element;
2166 if (defined $parent_element) {
2167 my $parent_value = $self->get_cascaded_value
2168 ($parent_element, $prop_name); ## NOTE: What Firefox does.
2169 return ['WEIGHT', $parent_value->[1], $parent_value->[2] + 1];
2170 } else {
2171 return ['WEIGHT', 400, 1];
2172 }
2173 } elsif ($specified_value->[1] eq 'lighter') {
2174 my $parent_element = $element->manakai_parent_element;
2175 if (defined $parent_element) {
2176 my $parent_value = $self->get_cascaded_value
2177 ($parent_element, $prop_name); ## NOTE: What Firefox does.
2178 return ['WEIGHT', $parent_value->[1], $parent_value->[2] - 1];
2179 } else {
2180 return ['WEIGHT', 400, 1];
2181 }
2182 }
2183 #} elsif (defined $specified_value and $specified_value->[0] eq 'WEIGHT') {
2184 #
2185 }
2186
2187 return $specified_value;
2188 },
2189 };
2190 $Attr->{font_weight} = $Prop->{'font-weight'};
2191 $Key->{font_weight} = $Prop->{'font-weight'};
2192
2193 my $uri_or_none_parser = sub {
2194 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2195
2196 if ($t->{type} == URI_TOKEN) {
2197 my $value = $t->{value};
2198 $t = $tt->get_next_token;
2199 return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
2200 } elsif ($t->{type} == IDENT_TOKEN) {
2201 my $value = lc $t->{value}; ## TODO: case
2202 $t = $tt->get_next_token;
2203 if ($value eq 'none') {
2204 ## NOTE: |none| is the default value and therefore it must be
2205 ## supported anyway.
2206 return ($t, {$prop_name => ["KEYWORD", 'none']});
2207 } elsif ($value eq 'inherit') {
2208 return ($t, {$prop_name => ['INHERIT']});
2209 }
2210 ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
2211 #} elsif ($t->{type} == URI_INVALID_TOKEN) {
2212 # my $value = $t->{value};
2213 # $t = $tt->get_next_token;
2214 # if ($t->{type} == EOF_TOKEN) {
2215 # $onerror->(type => 'syntax error:eof:'.$prop_name,
2216 # level => $self->{must_level},
2217 # token => $t);
2218 #
2219 # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
2220 # }
2221 }
2222
2223 $onerror->(type => 'syntax error:'.$prop_name,
2224 level => $self->{must_level},
2225 token => $t);
2226 return ($t, undef);
2227 }; # $uri_or_none_parser
2228
2229 my $compute_uri_or_none = sub {
2230 my ($self, $element, $prop_name, $specified_value) = @_;
2231
2232 if (defined $specified_value and
2233 $specified_value->[0] eq 'URI' and
2234 defined $specified_value->[2]) {
2235 require Message::DOM::DOMImplementation;
2236 return ['URI',
2237 Message::DOM::DOMImplementation->create_uri_reference
2238 ($specified_value->[1])
2239 ->get_absolute_reference (${$specified_value->[2]})
2240 ->get_uri_reference,
2241 $specified_value->[2]];
2242 }
2243
2244 return $specified_value;
2245 }; # $compute_uri_or_none
2246
2247 $Prop->{'list-style-image'} = {
2248 css => 'list-style-image',
2249 dom => 'list_style_image',
2250 key => 'list_style_image',
2251 parse => $uri_or_none_parser,
2252 serialize => $default_serializer,
2253 initial => ['KEYWORD', 'none'],
2254 inherited => 1,
2255 compute => $compute_uri_or_none,
2256 };
2257 $Attr->{list_style_image} = $Prop->{'list-style-image'};
2258 $Key->{list_style_image} = $Prop->{'list-style-image'};
2259
2260 $Prop->{'background-image'} = {
2261 css => 'background-image',
2262 dom => 'background_image',
2263 key => 'background_image',
2264 parse => $uri_or_none_parser,
2265 serialize => $default_serializer,
2266 initial => ['KEYWORD', 'none'],
2267 #inherited => 0,
2268 compute => $compute_uri_or_none,
2269 };
2270 $Attr->{background_image} = $Prop->{'background-image'};
2271 $Key->{background_image} = $Prop->{'background-image'};
2272
2273 my $border_style_keyword = {
2274 none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
2275 double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
2276 };
2277
2278 $Prop->{'border-top-style'} = {
2279 css => 'border-top-style',
2280 dom => 'border_top_style',
2281 key => 'border_top_style',
2282 parse => $one_keyword_parser,
2283 serialize => $default_serializer,
2284 keyword => $border_style_keyword,
2285 initial => ["KEYWORD", "none"],
2286 #inherited => 0,
2287 compute => $compute_as_specified,
2288 };
2289 $Attr->{border_top_style} = $Prop->{'border-top-style'};
2290 $Key->{border_top_style} = $Prop->{'border-top-style'};
2291
2292 $Prop->{'border-right-style'} = {
2293 css => 'border-right-style',
2294 dom => 'border_right_style',
2295 key => 'border_right_style',
2296 parse => $one_keyword_parser,
2297 serialize => $default_serializer,
2298 keyword => $border_style_keyword,
2299 initial => ["KEYWORD", "none"],
2300 #inherited => 0,
2301 compute => $compute_as_specified,
2302 };
2303 $Attr->{border_right_style} = $Prop->{'border-right-style'};
2304 $Key->{border_right_style} = $Prop->{'border-right-style'};
2305
2306 $Prop->{'border-bottom-style'} = {
2307 css => 'border-bottom-style',
2308 dom => 'border_bottom_style',
2309 key => 'border_bottom_style',
2310 parse => $one_keyword_parser,
2311 serialize => $default_serializer,
2312 keyword => $border_style_keyword,
2313 initial => ["KEYWORD", "none"],
2314 #inherited => 0,
2315 compute => $compute_as_specified,
2316 };
2317 $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
2318 $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
2319
2320 $Prop->{'border-left-style'} = {
2321 css => 'border-left-style',
2322 dom => 'border_left_style',
2323 key => 'border_left_style',
2324 parse => $one_keyword_parser,
2325 serialize => $default_serializer,
2326 keyword => $border_style_keyword,
2327 initial => ["KEYWORD", "none"],
2328 #inherited => 0,
2329 compute => $compute_as_specified,
2330 };
2331 $Attr->{border_left_style} = $Prop->{'border-left-style'};
2332 $Key->{border_left_style} = $Prop->{'border-left-style'};
2333
2334 $Prop->{'outline-style'} = {
2335 css => 'outline-style',
2336 dom => 'outline_style',
2337 key => 'outline_style',
2338 parse => $one_keyword_parser,
2339 serialize => $default_serializer,
2340 keyword => {%$border_style_keyword},
2341 initial => ['KEYWORD', 'none'],
2342 #inherited => 0,
2343 compute => $compute_as_specified,
2344 };
2345 $Attr->{outline_style} = $Prop->{'outline-style'};
2346 $Key->{outline_style} = $Prop->{'outline-style'};
2347 delete $Prop->{'outline-style'}->{keyword}->{hidden};
2348
2349 $Prop->{'font-family'} = {
2350 css => 'font-family',
2351 dom => 'font_family',
2352 key => 'font_family',
2353 parse => sub {
2354 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2355
2356 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/font-family> for
2357 ## how chaotic browsers are!
2358
2359 my @prop_value;
2360
2361 my $font_name = '';
2362 my $may_be_generic = 1;
2363 my $may_be_inherit = 1;
2364 my $has_s = 0;
2365 F: {
2366 if ($t->{type} == IDENT_TOKEN) {
2367 undef $may_be_inherit if $has_s or length $font_name;
2368 undef $may_be_generic if $has_s or length $font_name;
2369 $font_name .= ' ' if $has_s;
2370 $font_name .= $t->{value};
2371 undef $has_s;
2372 $t = $tt->get_next_token;
2373 } elsif ($t->{type} == STRING_TOKEN) {
2374 $font_name .= ' ' if $has_s;
2375 $font_name .= $t->{value};
2376 undef $may_be_inherit;
2377 undef $may_be_generic;
2378 undef $has_s;
2379 $t = $tt->get_next_token;
2380 } elsif ($t->{type} == COMMA_TOKEN) {
2381 if ($may_be_generic and
2382 {
2383 serif => 1, 'sans-serif' => 1, cursive => 1,
2384 fantasy => 1, monospace => 1, '-manakai-default' => 1,
2385 }->{lc $font_name}) { ## TODO: case
2386 push @prop_value, ['KEYWORD', $font_name];
2387 } elsif (not $may_be_generic or length $font_name) {
2388 push @prop_value, ["STRING", $font_name];
2389 }
2390 undef $may_be_inherit;
2391 $may_be_generic = 1;
2392 undef $has_s;
2393 $font_name = '';
2394 $t = $tt->get_next_token;
2395 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2396 } elsif ($t->{type} == S_TOKEN) {
2397 $has_s = 1;
2398 $t = $tt->get_next_token;
2399 } else {
2400 if ($may_be_generic and
2401 {
2402 serif => 1, 'sans-serif' => 1, cursive => 1,
2403 fantasy => 1, monospace => 1, '-manakai-default' => 1,
2404 }->{lc $font_name}) { ## TODO: case
2405 push @prop_value, ['KEYWORD', $font_name];
2406 } elsif (not $may_be_generic or length $font_name) {
2407 push @prop_value, ['STRING', $font_name];
2408 } else {
2409 $onerror->(type => 'syntax error:'.$prop_name,
2410 level => $self->{must_level},
2411 token => $t);
2412 return ($t, undef);
2413 }
2414 last F;
2415 }
2416 redo F;
2417 } # F
2418
2419 if ($may_be_inherit and
2420 @prop_value == 1 and
2421 $prop_value[0]->[0] eq 'STRING' and
2422 lc $prop_value[0]->[1] eq 'inherit') { ## TODO: case
2423 return ($t, {$prop_name => ['INHERIT']});
2424 } else {
2425 unshift @prop_value, 'FONT';
2426 return ($t, {$prop_name => \@prop_value});
2427 }
2428 },
2429 serialize => sub {
2430 my ($self, $prop_name, $value) = @_;
2431
2432 if ($value->[0] eq 'FONT') {
2433 return join ', ', map {
2434 if ($_->[0] eq 'STRING') {
2435 '"'.$_->[1].'"'; ## NOTE: This is what Firefox does.
2436 } elsif ($_->[0] eq 'KEYWORD') {
2437 $_->[1]; ## NOTE: This is what Firefox does.
2438 } else {
2439 ## NOTE: This should be an error.
2440 '""';
2441 }
2442 } @$value[1..$#$value];
2443 } elsif ($value->[0] eq 'INHERIT') {
2444 return 'inherit';
2445 } else {
2446 return undef;
2447 }
2448 },
2449 initial => ['FONT', ['KEYWORD', '-manakai-default']],
2450 inherited => 1,
2451 compute => $compute_as_specified,
2452 };
2453 $Attr->{font_family} = $Prop->{'font-family'};
2454 $Key->{font_family} = $Prop->{'font-family'};
2455
2456 $Prop->{cursor} = {
2457 css => 'cursor',
2458 dom => 'cursor',
2459 key => 'cursor',
2460 parse => sub {
2461 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2462
2463 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/cursor> for browser
2464 ## compatibility issues.
2465
2466 my @prop_value = ('CURSOR');
2467
2468 F: {
2469 if ($t->{type} == IDENT_TOKEN) {
2470 my $v = lc $t->{value}; ## TODO: case
2471 $t = $tt->get_next_token;
2472 if ($Prop->{$prop_name}->{keyword}->{$v}) {
2473 push @prop_value, ['KEYWORD', $v];
2474 last F;
2475 } elsif ($v eq 'inherit' and @prop_value == 1) {
2476 return ($t, {$prop_name => ['INHERIT']});
2477 } else {
2478 $onerror->(type => 'syntax error:'.$prop_name,
2479 level => $self->{must_level},
2480 token => $t);
2481 return ($t, undef);
2482 }
2483 } elsif ($t->{type} == URI_TOKEN) {
2484 push @prop_value, ['URI', $t->{value}, \($self->{base_uri})];
2485 $t = $tt->get_next_token;
2486 } else {
2487 $onerror->(type => 'syntax error:'.$prop_name,
2488 level => $self->{must_level},
2489 token => $t);
2490 return ($t, undef);
2491 }
2492
2493 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2494 if ($t->{type} == COMMA_TOKEN) {
2495 $t = $tt->get_next_token;
2496 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2497 redo F;
2498 }
2499 } # F
2500
2501 return ($t, {$prop_name => \@prop_value});
2502 },
2503 serialize => sub {
2504 my ($self, $prop_name, $value) = @_;
2505
2506 if ($value->[0] eq 'CURSOR') {
2507 return join ', ', map {
2508 if ($_->[0] eq 'URI') {
2509 'url('.$_->[1].')'; ## NOTE: This is what Firefox does.
2510 } elsif ($_->[0] eq 'KEYWORD') {
2511 $_->[1];
2512 } else {
2513 ## NOTE: This should be an error.
2514 '""';
2515 }
2516 } @$value[1..$#$value];
2517 } elsif ($value->[0] eq 'INHERIT') {
2518 return 'inherit';
2519 } else {
2520 return undef;
2521 }
2522 },
2523 keyword => {
2524 auto => 1, crosshair => 1, default => 1, pointer => 1, move => 1,
2525 'e-resize' => 1, 'ne-resize' => 1, 'nw-resize' => 1, 'n-resize' => 1,
2526 'n-resize' => 1, 'se-resize' => 1, 'sw-resize' => 1, 's-resize' => 1,
2527 'w-resize' => 1, text => 1, wait => 1, help => 1, progress => 1,
2528 },
2529 initial => ['CURSOR', ['KEYWORD', 'auto']],
2530 inherited => 1,
2531 compute => sub {
2532 my ($self, $element, $prop_name, $specified_value) = @_;
2533
2534 if (defined $specified_value and $specified_value->[0] eq 'CURSOR') {
2535 my @new_value = ('CURSOR');
2536 for my $value (@$specified_value[1..$#$specified_value]) {
2537 if ($value->[0] eq 'URI') {
2538 if (defined $value->[2]) {
2539 require Message::DOM::DOMImplementation;
2540 push @new_value, ['URI',
2541 Message::DOM::DOMImplementation
2542 ->create_uri_reference ($value->[1])
2543 ->get_absolute_reference (${$value->[2]})
2544 ->get_uri_reference,
2545 $value->[2]];
2546 } else {
2547 push @new_value, $value;
2548 }
2549 } else {
2550 push @new_value, $value;
2551 }
2552 }
2553 return \@new_value;
2554 }
2555
2556 return $specified_value;
2557 },
2558 };
2559 $Attr->{cursor} = $Prop->{cursor};
2560 $Key->{cursor} = $Prop->{cursor};
2561
2562 $Prop->{'border-style'} = {
2563 css => 'border-style',
2564 dom => 'border_style',
2565 parse => sub {
2566 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2567
2568 my %prop_value;
2569 if ($t->{type} == IDENT_TOKEN) {
2570 my $prop_value = lc $t->{value}; ## TODO: case folding
2571 $t = $tt->get_next_token;
2572 if ($border_style_keyword->{$prop_value} and
2573 $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
2574 $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
2575 } elsif ($prop_value eq 'inherit') {
2576 $prop_value{'border-top-style'} = ["INHERIT"];
2577 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
2578 $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
2579 $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2580 return ($t, \%prop_value);
2581 } else {
2582 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2583 level => $self->{must_level},
2584 token => $t);
2585 return ($t, undef);
2586 }
2587 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
2588 $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
2589 $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2590 } else {
2591 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2592 level => $self->{must_level},
2593 token => $t);
2594 return ($t, undef);
2595 }
2596
2597 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2598 if ($t->{type} == IDENT_TOKEN) {
2599 my $prop_value = lc $t->{value}; ## TODO: case folding
2600 $t = $tt->get_next_token;
2601 if ($border_style_keyword->{$prop_value} and
2602 $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
2603 $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
2604 } else {
2605 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2606 level => $self->{must_level},
2607 token => $t);
2608 return ($t, undef);
2609 }
2610 $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2611
2612 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2613 if ($t->{type} == IDENT_TOKEN) {
2614 my $prop_value = lc $t->{value}; ## TODO: case folding
2615 $t = $tt->get_next_token;
2616 if ($border_style_keyword->{$prop_value} and
2617 $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
2618 $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
2619 } else {
2620 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2621 level => $self->{must_level},
2622 token => $t);
2623 return ($t, undef);
2624 }
2625
2626 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2627 if ($t->{type} == IDENT_TOKEN) {
2628 my $prop_value = lc $t->{value}; ## TODO: case folding
2629 $t = $tt->get_next_token;
2630 if ($border_style_keyword->{$prop_value} and
2631 $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
2632 $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
2633 } else {
2634 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2635 level => $self->{must_level},
2636 token => $t);
2637 return ($t, undef);
2638 }
2639 }
2640 }
2641 }
2642
2643 return ($t, \%prop_value);
2644 },
2645 serialize => sub {
2646 my ($self, $prop_name, $value) = @_;
2647
2648 local $Error::Depth = $Error::Depth + 1;
2649 my @v;
2650 push @v, $self->border_top_style;
2651 return undef unless defined $v[-1];
2652 push @v, $self->border_right_style;
2653 return undef unless defined $v[-1];
2654 push @v, $self->border_bottom_style;
2655 return undef unless defined $v[-1];
2656 push @v, $self->border_left_style;
2657 return undef unless defined $v[-1];
2658
2659 pop @v if $v[1] eq $v[3];
2660 pop @v if $v[0] eq $v[2];
2661 pop @v if $v[0] eq $v[1];
2662 return join ' ', @v;
2663 },
2664 };
2665 $Attr->{border_style} = $Prop->{'border-style'};
2666
2667 $Prop->{margin} = {
2668 css => 'margin',
2669 dom => 'margin',
2670 parse => sub {
2671 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2672
2673 my %prop_value;
2674
2675 my $sign = 1;
2676 if ($t->{type} == MINUS_TOKEN) {
2677 $t = $tt->get_next_token;
2678 $sign = -1;
2679 }
2680
2681 if ($t->{type} == DIMENSION_TOKEN) {
2682 my $value = $t->{number} * $sign;
2683 my $unit = lc $t->{value}; ## TODO: case
2684 $t = $tt->get_next_token;
2685 if ($length_unit->{$unit}) {
2686 $prop_value{'margin-top'} = ['DIMENSION', $value, $unit];
2687 } else {
2688 $onerror->(type => 'syntax error:'.$prop_name,
2689 level => $self->{must_level},
2690 token => $t);
2691 return ($t, undef);
2692 }
2693 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2694 my $value = $t->{number} * $sign;
2695 $t = $tt->get_next_token;
2696 $prop_value{'margin-top'} = ['PERCENTAGE', $value];
2697 } elsif ($t->{type} == NUMBER_TOKEN and
2698 ($self->{unitless_px} or $t->{number} == 0)) {
2699 my $value = $t->{number} * $sign;
2700 $t = $tt->get_next_token;
2701 $prop_value{'margin-top'} = ['DIMENSION', $value, 'px'];
2702 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2703 my $prop_value = lc $t->{value}; ## TODO: case folding
2704 $t = $tt->get_next_token;
2705 if ($prop_value eq 'auto') {
2706 $prop_value{'margin-top'} = ['KEYWORD', $prop_value];
2707 } elsif ($prop_value eq 'inherit') {
2708 $prop_value{'margin-top'} = ['INHERIT'];
2709 $prop_value{'margin-right'} = $prop_value{'margin-top'};
2710 $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
2711 $prop_value{'margin-left'} = $prop_value{'margin-right'};
2712 return ($t, \%prop_value);
2713 } else {
2714 $onerror->(type => 'syntax error:'.$prop_name,
2715 level => $self->{must_level},
2716 token => $t);
2717 return ($t, undef);
2718 }
2719 } else {
2720 $onerror->(type => 'syntax error:'.$prop_name,
2721 level => $self->{must_level},
2722 token => $t);
2723 return ($t, undef);
2724 }
2725 $prop_value{'margin-right'} = $prop_value{'margin-top'};
2726 $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
2727 $prop_value{'margin-left'} = $prop_value{'margin-right'};
2728
2729 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2730 $sign = 1;
2731 if ($t->{type} == MINUS_TOKEN) {
2732 $t = $tt->get_next_token;
2733 $sign = -1;
2734 }
2735
2736 if ($t->{type} == DIMENSION_TOKEN) {
2737 my $value = $t->{number} * $sign;
2738 my $unit = lc $t->{value}; ## TODO: case
2739 $t = $tt->get_next_token;
2740 if ($length_unit->{$unit}) {
2741 $prop_value{'margin-right'} = ['DIMENSION', $value, $unit];
2742 } else {
2743 $onerror->(type => 'syntax error:'.$prop_name,
2744 level => $self->{must_level},
2745 token => $t);
2746 return ($t, undef);
2747 }
2748 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2749 my $value = $t->{number} * $sign;
2750 $t = $tt->get_next_token;
2751 $prop_value{'margin-right'} = ['PERCENTAGE', $value];
2752 } elsif ($t->{type} == NUMBER_TOKEN and
2753 ($self->{unitless_px} or $t->{number} == 0)) {
2754 my $value = $t->{number} * $sign;
2755 $t = $tt->get_next_token;
2756 $prop_value{'margin-right'} = ['DIMENSION', $value, 'px'];
2757 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2758 my $prop_value = lc $t->{value}; ## TODO: case folding
2759 $t = $tt->get_next_token;
2760 if ($prop_value eq 'auto') {
2761 $prop_value{'margin-right'} = ['KEYWORD', $prop_value];
2762 } else {
2763 $onerror->(type => 'syntax error:'.$prop_name,
2764 level => $self->{must_level},
2765 token => $t);
2766 return ($t, undef);
2767 }
2768 } else {
2769 if ($sign < 0) {
2770 $onerror->(type => 'syntax error:'.$prop_name,
2771 level => $self->{must_level},
2772 token => $t);
2773 return ($t, undef);
2774 }
2775 return ($t, \%prop_value);
2776 }
2777 $prop_value{'margin-left'} = $prop_value{'margin-right'};
2778
2779 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2780 $sign = 1;
2781 if ($t->{type} == MINUS_TOKEN) {
2782 $t = $tt->get_next_token;
2783 $sign = -1;
2784 }
2785
2786 if ($t->{type} == DIMENSION_TOKEN) {
2787 my $value = $t->{number} * $sign;
2788 my $unit = lc $t->{value}; ## TODO: case
2789 $t = $tt->get_next_token;
2790 if ($length_unit->{$unit}) {
2791 $prop_value{'margin-bottom'} = ['DIMENSION', $value, $unit];
2792 } else {
2793 $onerror->(type => 'syntax error:'.$prop_name,
2794 level => $self->{must_level},
2795 token => $t);
2796 return ($t, undef);
2797 }
2798 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2799 my $value = $t->{number} * $sign;
2800 $t = $tt->get_next_token;
2801 $prop_value{'margin-bottom'} = ['PERCENTAGE', $value];
2802 } elsif ($t->{type} == NUMBER_TOKEN and
2803 ($self->{unitless_px} or $t->{number} == 0)) {
2804 my $value = $t->{number} * $sign;
2805 $t = $tt->get_next_token;
2806 $prop_value{'margin-bottom'} = ['DIMENSION', $value, 'px'];
2807 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2808 my $prop_value = lc $t->{value}; ## TODO: case folding
2809 $t = $tt->get_next_token;
2810 if ($prop_value eq 'auto') {
2811 $prop_value{'margin-bottom'} = ['KEYWORD', $prop_value];
2812 } else {
2813 $onerror->(type => 'syntax error:'.$prop_name,
2814 level => $self->{must_level},
2815 token => $t);
2816 return ($t, undef);
2817 }
2818 } else {
2819 if ($sign < 0) {
2820 $onerror->(type => 'syntax error:'.$prop_name,
2821 level => $self->{must_level},
2822 token => $t);
2823 return ($t, undef);
2824 }
2825 return ($t, \%prop_value);
2826 }
2827
2828 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2829 $sign = 1;
2830 if ($t->{type} == MINUS_TOKEN) {
2831 $t = $tt->get_next_token;
2832 $sign = -1;
2833 }
2834
2835 if ($t->{type} == DIMENSION_TOKEN) {
2836 my $value = $t->{number} * $sign;
2837 my $unit = lc $t->{value}; ## TODO: case
2838 $t = $tt->get_next_token;
2839 if ($length_unit->{$unit}) {
2840 $prop_value{'margin-left'} = ['DIMENSION', $value, $unit];
2841 } else {
2842 $onerror->(type => 'syntax error:'.$prop_name,
2843 level => $self->{must_level},
2844 token => $t);
2845 return ($t, undef);
2846 }
2847 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2848 my $value = $t->{number} * $sign;
2849 $t = $tt->get_next_token;
2850 $prop_value{'margin-left'} = ['PERCENTAGE', $value];
2851 } elsif ($t->{type} == NUMBER_TOKEN and
2852 ($self->{unitless_px} or $t->{number} == 0)) {
2853 my $value = $t->{number} * $sign;
2854 $t = $tt->get_next_token;
2855 $prop_value{'margin-left'} = ['DIMENSION', $value, 'px'];
2856 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2857 my $prop_value = lc $t->{value}; ## TODO: case folding
2858 $t = $tt->get_next_token;
2859 if ($prop_value eq 'auto') {
2860 $prop_value{'margin-left'} = ['KEYWORD', $prop_value];
2861 } else {
2862 $onerror->(type => 'syntax error:'.$prop_name,
2863 level => $self->{must_level},
2864 token => $t);
2865 return ($t, undef);
2866 }
2867 } else {
2868 if ($sign < 0) {
2869 $onerror->(type => 'syntax error:'.$prop_name,
2870 level => $self->{must_level},
2871 token => $t);
2872 return ($t, undef);
2873 }
2874 return ($t, \%prop_value);
2875 }
2876
2877 return ($t, \%prop_value);
2878 },
2879 serialize => sub {
2880 my ($self, $prop_name, $value) = @_;
2881
2882 local $Error::Depth = $Error::Depth + 1;
2883 my @v;
2884 push @v, $self->margin_top;
2885 return undef unless defined $v[-1];
2886 push @v, $self->margin_right;
2887 return undef unless defined $v[-1];
2888 push @v, $self->margin_bottom;
2889 return undef unless defined $v[-1];
2890 push @v, $self->margin_left;
2891 return undef unless defined $v[-1];
2892
2893 pop @v if $v[1] eq $v[3];
2894 pop @v if $v[0] eq $v[2];
2895 pop @v if $v[0] eq $v[1];
2896 return join ' ', @v;
2897 },
2898 };
2899 $Attr->{margin} = $Prop->{margin};
2900
2901 $Prop->{padding} = {
2902 css => 'padding',
2903 dom => 'padding',
2904 parse => sub {
2905 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2906
2907 my %prop_value;
2908
2909 my $sign = 1;
2910 if ($t->{type} == MINUS_TOKEN) {
2911 $t = $tt->get_next_token;
2912 $sign = -1;
2913 }
2914
2915 if ($t->{type} == DIMENSION_TOKEN) {
2916 my $value = $t->{number} * $sign;
2917 my $unit = lc $t->{value}; ## TODO: case
2918 $t = $tt->get_next_token;
2919 if ($length_unit->{$unit} and $value >= 0) {
2920 $prop_value{'padding-top'} = ['DIMENSION', $value, $unit];
2921 } else {
2922 $onerror->(type => 'syntax error:'.$prop_name,
2923 level => $self->{must_level},
2924 token => $t);
2925 return ($t, undef);
2926 }
2927 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2928 my $value = $t->{number} * $sign;
2929 $t = $tt->get_next_token;
2930 $prop_value{'padding-top'} = ['PERCENTAGE', $value];
2931 unless ($value >= 0) {
2932 $onerror->(type => 'syntax error:'.$prop_name,
2933 level => $self->{must_level},
2934 token => $t);
2935 return ($t, undef);
2936 }
2937 } elsif ($t->{type} == NUMBER_TOKEN and
2938 ($self->{unitless_px} or $t->{number} == 0)) {
2939 my $value = $t->{number} * $sign;
2940 $t = $tt->get_next_token;
2941 $prop_value{'padding-top'} = ['DIMENSION', $value, 'px'];
2942 unless ($value >= 0) {
2943 $onerror->(type => 'syntax error:'.$prop_name,
2944 level => $self->{must_level},
2945 token => $t);
2946 return ($t, undef);
2947 }
2948 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2949 my $prop_value = lc $t->{value}; ## TODO: case folding
2950 $t = $tt->get_next_token;
2951 if ($prop_value eq 'inherit') {
2952 $prop_value{'padding-top'} = ['INHERIT'];
2953 $prop_value{'padding-right'} = $prop_value{'padding-top'};
2954 $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
2955 $prop_value{'padding-left'} = $prop_value{'padding-right'};
2956 return ($t, \%prop_value);
2957 } else {
2958 $onerror->(type => 'syntax error:'.$prop_name,
2959 level => $self->{must_level},
2960 token => $t);
2961 return ($t, undef);
2962 }
2963 } else {
2964 $onerror->(type => 'syntax error:'.$prop_name,
2965 level => $self->{must_level},
2966 token => $t);
2967 return ($t, undef);
2968 }
2969 $prop_value{'padding-right'} = $prop_value{'padding-top'};
2970 $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
2971 $prop_value{'padding-left'} = $prop_value{'padding-right'};
2972
2973 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2974 $sign = 1;
2975 if ($t->{type} == MINUS_TOKEN) {
2976 $t = $tt->get_next_token;
2977 $sign = -1;
2978 }
2979
2980 if ($t->{type} == DIMENSION_TOKEN) {
2981 my $value = $t->{number} * $sign;
2982 my $unit = lc $t->{value}; ## TODO: case
2983 $t = $tt->get_next_token;
2984 if ($length_unit->{$unit} and $value >= 0) {
2985 $prop_value{'padding-right'} = ['DIMENSION', $value, $unit];
2986 } else {
2987 $onerror->(type => 'syntax error:'.$prop_name,
2988 level => $self->{must_level},
2989 token => $t);
2990 return ($t, undef);
2991 }
2992 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2993 my $value = $t->{number} * $sign;
2994 $t = $tt->get_next_token;
2995 $prop_value{'padding-right'} = ['PERCENTAGE', $value];
2996 unless ($value >= 0) {
2997 $onerror->(type => 'syntax error:'.$prop_name,
2998 level => $self->{must_level},
2999 token => $t);
3000 return ($t, undef);
3001 }
3002 } elsif ($t->{type} == NUMBER_TOKEN and
3003 ($self->{unitless_px} or $t->{number} == 0)) {
3004 my $value = $t->{number} * $sign;
3005 $t = $tt->get_next_token;
3006 $prop_value{'padding-right'} = ['DIMENSION', $value, 'px'];
3007 unless ($value >= 0) {
3008 $onerror->(type => 'syntax error:'.$prop_name,
3009 level => $self->{must_level},
3010 token => $t);
3011 return ($t, undef);
3012 }
3013 } else {
3014 if ($sign < 0) {
3015 $onerror->(type => 'syntax error:'.$prop_name,
3016 level => $self->{must_level},
3017 token => $t);
3018 return ($t, undef);
3019 }
3020 return ($t, \%prop_value);
3021 }
3022 $prop_value{'padding-left'} = $prop_value{'padding-right'};
3023
3024 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3025 $sign = 1;
3026 if ($t->{type} == MINUS_TOKEN) {
3027 $t = $tt->get_next_token;
3028 $sign = -1;
3029 }
3030
3031 if ($t->{type} == DIMENSION_TOKEN) {
3032 my $value = $t->{number} * $sign;
3033 my $unit = lc $t->{value}; ## TODO: case
3034 $t = $tt->get_next_token;
3035 if ($length_unit->{$unit} and $value >= 0) {
3036 $prop_value{'padding-bottom'} = ['DIMENSION', $value, $unit];
3037 } else {
3038 $onerror->(type => 'syntax error:'.$prop_name,
3039 level => $self->{must_level},
3040 token => $t);
3041 return ($t, undef);
3042 }
3043 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3044 my $value = $t->{number} * $sign;
3045 $t = $tt->get_next_token;
3046 $prop_value{'padding-bottom'} = ['PERCENTAGE', $value];
3047 unless ($value >= 0) {
3048 $onerror->(type => 'syntax error:'.$prop_name,
3049 level => $self->{must_level},
3050 token => $t);
3051 return ($t, undef);
3052 }
3053 } elsif ($t->{type} == NUMBER_TOKEN and
3054 ($self->{unitless_px} or $t->{number} == 0)) {
3055 my $value = $t->{number} * $sign;
3056 $t = $tt->get_next_token;
3057 $prop_value{'padding-bottom'} = ['DIMENSION', $value, 'px'];
3058 unless ($value >= 0) {
3059 $onerror->(type => 'syntax error:'.$prop_name,
3060 level => $self->{must_level},
3061 token => $t);
3062 return ($t, undef);
3063 }
3064 } else {
3065 if ($sign < 0) {
3066 $onerror->(type => 'syntax error:'.$prop_name,
3067 level => $self->{must_level},
3068 token => $t);
3069 return ($t, undef);
3070 }
3071 return ($t, \%prop_value);
3072 }
3073
3074 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3075 $sign = 1;
3076 if ($t->{type} == MINUS_TOKEN) {
3077 $t = $tt->get_next_token;
3078 $sign = -1;
3079 }
3080
3081 if ($t->{type} == DIMENSION_TOKEN) {
3082 my $value = $t->{number} * $sign;
3083 my $unit = lc $t->{value}; ## TODO: case
3084 $t = $tt->get_next_token;
3085 if ($length_unit->{$unit} and $value >= 0) {
3086 $prop_value{'padding-left'} = ['DIMENSION', $value, $unit];
3087 } else {
3088 $onerror->(type => 'syntax error:'.$prop_name,
3089 level => $self->{must_level},
3090 token => $t);
3091 return ($t, undef);
3092 }
3093 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3094 my $value = $t->{number} * $sign;
3095 $t = $tt->get_next_token;
3096 $prop_value{'padding-left'} = ['PERCENTAGE', $value];
3097 unless ($value >= 0) {
3098 $onerror->(type => 'syntax error:'.$prop_name,
3099 level => $self->{must_level},
3100 token => $t);
3101 return ($t, undef);
3102 }
3103 } elsif ($t->{type} == NUMBER_TOKEN and
3104 ($self->{unitless_px} or $t->{number} == 0)) {
3105 my $value = $t->{number} * $sign;
3106 $t = $tt->get_next_token;
3107 $prop_value{'padding-left'} = ['DIMENSION', $value, 'px'];
3108 unless ($value >= 0) {
3109 $onerror->(type => 'syntax error:'.$prop_name,
3110 level => $self->{must_level},
3111 token => $t);
3112 return ($t, undef);
3113 }
3114 } else {
3115 if ($sign < 0) {
3116 $onerror->(type => 'syntax error:'.$prop_name,
3117 level => $self->{must_level},
3118 token => $t);
3119 return ($t, undef);
3120 }
3121 return ($t, \%prop_value);
3122 }
3123
3124 return ($t, \%prop_value);
3125 },
3126 serialize => sub {
3127 my ($self, $prop_name, $value) = @_;
3128
3129 local $Error::Depth = $Error::Depth + 1;
3130 my @v;
3131 push @v, $self->padding_top;
3132 return undef unless defined $v[-1];
3133 push @v, $self->padding_right;
3134 return undef unless defined $v[-1];
3135 push @v, $self->padding_bottom;
3136 return undef unless defined $v[-1];
3137 push @v, $self->padding_left;
3138 return undef unless defined $v[-1];
3139
3140 pop @v if $v[1] eq $v[3];
3141 pop @v if $v[0] eq $v[2];
3142 pop @v if $v[0] eq $v[1];
3143 return join ' ', @v;
3144 },
3145 };
3146 $Attr->{padding} = $Prop->{padding};
3147
3148 $Prop->{'border-spacing'} = {
3149 css => 'border-spacing',
3150 dom => 'border_spacing',
3151 parse => sub {
3152 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3153
3154 my %prop_value;
3155
3156 my $sign = 1;
3157 if ($t->{type} == MINUS_TOKEN) {
3158 $t = $tt->get_next_token;
3159 $sign = -1;
3160 }
3161
3162 if ($t->{type} == DIMENSION_TOKEN) {
3163 my $value = $t->{number} * $sign;
3164 my $unit = lc $t->{value}; ## TODO: case
3165 $t = $tt->get_next_token;
3166 if ($length_unit->{$unit} and $value >= 0) {
3167 $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, $unit];
3168 } else {
3169 $onerror->(type => 'syntax error:'.$prop_name,
3170 level => $self->{must_level},
3171 token => $t);
3172 return ($t, undef);
3173 }
3174 } elsif ($t->{type} == NUMBER_TOKEN and
3175 ($self->{unitless_px} or $t->{number} == 0)) {
3176 my $value = $t->{number} * $sign;
3177 $t = $tt->get_next_token;
3178 $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, 'px'];
3179 unless ($value >= 0) {
3180 $onerror->(type => 'syntax error:'.$prop_name,
3181 level => $self->{must_level},
3182 token => $t);
3183 return ($t, undef);
3184 }
3185 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3186 my $prop_value = lc $t->{value}; ## TODO: case folding
3187 $t = $tt->get_next_token;
3188 if ($prop_value eq 'inherit') {
3189 $prop_value{'-manakai-border-spacing-x'} = ['INHERIT'];
3190 $prop_value{'-manakai-border-spacing-y'}
3191 = $prop_value{'-manakai-border-spacing-x'};
3192 return ($t, \%prop_value);
3193 } else {
3194 $onerror->(type => 'syntax error:'.$prop_name,
3195 level => $self->{must_level},
3196 token => $t);
3197 return ($t, undef);
3198 }
3199 } else {
3200 $onerror->(type => 'syntax error:'.$prop_name,
3201 level => $self->{must_level},
3202 token => $t);
3203 return ($t, undef);
3204 }
3205 $prop_value{'-manakai-border-spacing-y'}
3206 = $prop_value{'-manakai-border-spacing-x'};
3207
3208 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3209 $sign = 1;
3210 if ($t->{type} == MINUS_TOKEN) {
3211 $t = $tt->get_next_token;
3212 $sign = -1;
3213 }
3214
3215 if ($t->{type} == DIMENSION_TOKEN) {
3216 my $value = $t->{number} * $sign;
3217 my $unit = lc $t->{value}; ## TODO: case
3218 $t = $tt->get_next_token;
3219 if ($length_unit->{$unit} and $value >= 0) {
3220 $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, $unit];
3221 } else {
3222 $onerror->(type => 'syntax error:'.$prop_name,
3223 level => $self->{must_level},
3224 token => $t);
3225 return ($t, undef);
3226 }
3227 } elsif ($t->{type} == NUMBER_TOKEN and
3228 ($self->{unitless_px} or $t->{number} == 0)) {
3229 my $value = $t->{number} * $sign;
3230 $t = $tt->get_next_token;
3231 $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, 'px'];
3232 unless ($value >= 0) {
3233 $onerror->(type => 'syntax error:'.$prop_name,
3234 level => $self->{must_level},
3235 token => $t);
3236 return ($t, undef);
3237 }
3238 } else {
3239 if ($sign < 0) {
3240 $onerror->(type => 'syntax error:'.$prop_name,
3241 level => $self->{must_level},
3242 token => $t);
3243 return ($t, undef);
3244 }
3245 return ($t, \%prop_value);
3246 }
3247
3248 return ($t, \%prop_value);
3249 },
3250 serialize => sub {
3251 my ($self, $prop_name, $value) = @_;
3252
3253 local $Error::Depth = $Error::Depth + 1;
3254 my @v;
3255 push @v, $self->_manakai_border_spacing_x;
3256 return undef unless defined $v[-1];
3257 push @v, $self->_manakai_border_spacing_y;
3258 return undef unless defined $v[-1];
3259
3260 pop @v if $v[0] eq $v[1];
3261 return join ' ', @v;
3262 },
3263 serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
3264 ->{serialize_multiple},
3265 };
3266 $Attr->{border_spacing} = $Prop->{'border-spacing'};
3267
3268 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/background-position> for
3269 ## browser compatibility problems.
3270 $Prop->{'background-position'} = {
3271 css => 'background-position',
3272 dom => 'background_position',
3273 parse => sub {
3274 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3275
3276 my %prop_value;
3277
3278 my $sign = 1;
3279 if ($t->{type} == MINUS_TOKEN) {
3280 $t = $tt->get_next_token;
3281 $sign = -1;
3282 }
3283
3284 if ($t->{type} == DIMENSION_TOKEN) {
3285 my $value = $t->{number} * $sign;
3286 my $unit = lc $t->{value}; ## TODO: case
3287 $t = $tt->get_next_token;
3288 if ($length_unit->{$unit}) {
3289 $prop_value{'background-position-x'} = ['DIMENSION', $value, $unit];
3290 $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
3291 } else {
3292 $onerror->(type => 'syntax error:'.$prop_name,
3293 level => $self->{must_level},
3294 token => $t);
3295 return ($t, undef);
3296 }
3297 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3298 my $value = $t->{number} * $sign;
3299 $t = $tt->get_next_token;
3300 $prop_value{'background-position-x'} = ['PERCENTAGE', $value];
3301 $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
3302 } elsif ($t->{type} == NUMBER_TOKEN and
3303 ($self->{unitless_px} or $t->{number} == 0)) {
3304 my $value = $t->{number} * $sign;
3305 $t = $tt->get_next_token;
3306 $prop_value{'background-position-x'} = ['DIMENSION', $value, 'px'];
3307 $prop_value{'background-position-y'} = ['PERCENTAGE', 50];
3308 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3309 my $prop_value = lc $t->{value}; ## TODO: case folding
3310 $t = $tt->get_next_token;
3311 if ({left => 1, center => 1, right => 1}->{$prop_value}) {
3312 $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
3313 $prop_value{'background-position-y'} = ['KEYWORD', 'center'];
3314 } elsif ($prop_value eq 'top' or $prop_value eq 'bottom') {
3315 $prop_value{'background-position-y'} = ['KEYWORD', $prop_value];
3316
3317 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3318 if ($t->{type} == IDENT_TOKEN) {
3319 my $prop_value = lc $t->{value}; ## TODO: case folding
3320 if ({left => 1, center => 1, right => 1}->{$prop_value}) {
3321 $prop_value{'background-position-x'} = ['KEYWORD', $prop_value];
3322 $t = $tt->get_next_token;
3323 return ($t, \%prop_value);
3324 }
3325 }
3326 $prop_value{'background-position-x'} = ['KEYWORD', 'center'];
3327 return ($t, \%prop_value);
3328 } elsif ($prop_value eq 'inherit') {
3329 $prop_value{'background-position-x'} = ['INHERIT'];
3330 $prop_value{'background-position-y'} = ['INHERIT'];
3331 return ($t, \%prop_value);
3332 } else {
3333 $onerror->(type => 'syntax error:'.$prop_name,
3334 level => $self->{must_level},
3335 token => $t);
3336 return ($t, undef);
3337 }
3338 } else {
3339 $onerror->(type => 'syntax error:'.$prop_name,
3340 level => $self->{must_level},
3341 token => $t);
3342 return ($t, undef);
3343 }
3344
3345 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3346 $sign = 1;
3347 if ($t->{type} == MINUS_TOKEN) {
3348 $t = $tt->get_next_token;
3349 $sign = -1;
3350 }
3351
3352 if ($t->{type} == DIMENSION_TOKEN) {
3353 my $value = $t->{number} * $sign;
3354 my $unit = lc $t->{value}; ## TODO: case
3355 $t = $tt->get_next_token;
3356 if ($length_unit->{$unit}) {
3357 $prop_value{'background-position-y'} = ['DIMENSION', $value, $unit];
3358 } else {
3359 $onerror->(type => 'syntax error:'.$prop_name,
3360 level => $self->{must_level},
3361 token => $t);
3362 return ($t, undef);
3363 }
3364 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3365 my $value = $t->{number} * $sign;
3366 $t = $tt->get_next_token;
3367 $prop_value{'background-position-y'} = ['PERCENTAGE', $value];
3368 } elsif ($t->{type} == NUMBER_TOKEN and $self->{unitless_px}) {
3369 my $value = $t->{number} * $sign;
3370 $t = $tt->get_next_token;
3371 $prop_value{'background-position-y'} = ['DIMENSION', $value, 'px'];
3372 } elsif ($t->{type} == IDENT_TOKEN) {
3373 my $value = lc $t->{value}; ## TODO: case
3374 if ({top => 1, center => 1, bottom => 1}->{$value}) {
3375 $prop_value{'background-position-y'} = ['KEYWORD', $value];
3376 $t = $tt->get_next_token;
3377 }
3378 } else {
3379 if ($sign < 0) {
3380 $onerror->(type => 'syntax error:'.$prop_name,
3381 level => $self->{must_level},
3382 token => $t);
3383 return ($t, undef);
3384 }
3385 return ($t, \%prop_value);
3386 }
3387
3388 return ($t, \%prop_value);
3389 },
3390 serialize => sub {
3391 my ($self, $prop_name, $value) = @_;
3392
3393 local $Error::Depth = $Error::Depth + 1;
3394 my $x = $self->background_position_x;
3395 my $y = $self->background_position_y;
3396 return $x . ' ' . $y if defined $x and defined $y;
3397 return undef;
3398 },
3399 serialize_multiple => $Prop->{'background-position-x'}
3400 ->{serialize_multiple},
3401 };
3402 $Attr->{background_position} = $Prop->{'background-position'};
3403
3404 $Prop->{'border-width'} = {
3405 css => 'border-width',
3406 dom => 'border_width',
3407 parse => sub {
3408 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3409
3410 my %prop_value;
3411
3412 my $sign = 1;
3413 if ($t->{type} == MINUS_TOKEN) {
3414 $t = $tt->get_next_token;
3415 $sign = -1;
3416 }
3417
3418 if ($t->{type} == DIMENSION_TOKEN) {
3419 my $value = $t->{number} * $sign;
3420 my $unit = lc $t->{value}; ## TODO: case
3421 $t = $tt->get_next_token;
3422 if ($length_unit->{$unit} and $value >= 0) {
3423 $prop_value{'border-top-width'} = ['DIMENSION', $value, $unit];
3424 } else {
3425 $onerror->(type => 'syntax error:'.$prop_name,
3426 level => $self->{must_level},
3427 token => $t);
3428 return ($t, undef);
3429 }
3430 } elsif ($t->{type} == NUMBER_TOKEN and
3431 ($self->{unitless_px} or $t->{number} == 0)) {
3432 my $value = $t->{number} * $sign;
3433 $t = $tt->get_next_token;
3434 $prop_value{'border-top-width'} = ['DIMENSION', $value, 'px'];
3435 unless ($value >= 0) {
3436 $onerror->(type => 'syntax error:'.$prop_name,
3437 level => $self->{must_level},
3438 token => $t);
3439 return ($t, undef);
3440 }
3441 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3442 my $prop_value = lc $t->{value}; ## TODO: case folding
3443 $t = $tt->get_next_token;
3444 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3445 $prop_value{'border-top-width'} = ['KEYWORD', $prop_value];
3446 } elsif ($prop_value eq 'inherit') {
3447 $prop_value{'border-top-width'} = ['INHERIT'];
3448 $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
3449 $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
3450 $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
3451 return ($t, \%prop_value);
3452 } else {
3453 $onerror->(type => 'syntax error:'.$prop_name,
3454 level => $self->{must_level},
3455 token => $t);
3456 return ($t, undef);
3457 }
3458 } else {
3459 $onerror->(type => 'syntax error:'.$prop_name,
3460 level => $self->{must_level},
3461 token => $t);
3462 return ($t, undef);
3463 }
3464 $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
3465 $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
3466 $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
3467
3468 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3469 $sign = 1;
3470 if ($t->{type} == MINUS_TOKEN) {
3471 $t = $tt->get_next_token;
3472 $sign = -1;
3473 }
3474
3475 if ($t->{type} == DIMENSION_TOKEN) {
3476 my $value = $t->{number} * $sign;
3477 my $unit = lc $t->{value}; ## TODO: case
3478 $t = $tt->get_next_token;
3479 if ($length_unit->{$unit} and $value >= 0) {
3480 $prop_value{'border-right-width'} = ['DIMENSION', $value, $unit];
3481 } else {
3482 $onerror->(type => 'syntax error:'.$prop_name,
3483 level => $self->{must_level},
3484 token => $t);
3485 return ($t, undef);
3486 }
3487 } elsif ($t->{type} == NUMBER_TOKEN and
3488 ($self->{unitless_px} or $t->{number} == 0)) {
3489 my $value = $t->{number} * $sign;
3490 $t = $tt->get_next_token;
3491 $prop_value{'border-right-width'} = ['DIMENSION', $value, 'px'];
3492 unless ($value >= 0) {
3493 $onerror->(type => 'syntax error:'.$prop_name,
3494 level => $self->{must_level},
3495 token => $t);
3496 return ($t, undef);
3497 }
3498 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3499 my $prop_value = lc $t->{value}; ## TODO: case
3500 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3501 $prop_value{'border-right-width'} = ['KEYWORD', $prop_value];
3502 $t = $tt->get_next_token;
3503 }
3504 } else {
3505 if ($sign < 0) {
3506 $onerror->(type => 'syntax error:'.$prop_name,
3507 level => $self->{must_level},
3508 token => $t);
3509 return ($t, undef);
3510 }
3511 return ($t, \%prop_value);
3512 }
3513 $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
3514
3515 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3516 $sign = 1;
3517 if ($t->{type} == MINUS_TOKEN) {
3518 $t = $tt->get_next_token;
3519 $sign = -1;
3520 }
3521
3522 if ($t->{type} == DIMENSION_TOKEN) {
3523 my $value = $t->{number} * $sign;
3524 my $unit = lc $t->{value}; ## TODO: case
3525 $t = $tt->get_next_token;
3526 if ($length_unit->{$unit} and $value >= 0) {
3527 $prop_value{'border-bottom-width'} = ['DIMENSION', $value, $unit];
3528 } else {
3529 $onerror->(type => 'syntax error:'.$prop_name,
3530 level => $self->{must_level},
3531 token => $t);
3532 return ($t, undef);
3533 }
3534 } elsif ($t->{type} == NUMBER_TOKEN and
3535 ($self->{unitless_px} or $t->{number} == 0)) {
3536 my $value = $t->{number} * $sign;
3537 $t = $tt->get_next_token;
3538 $prop_value{'border-bottom-width'} = ['DIMENSION', $value, 'px'];
3539 unless ($value >= 0) {
3540 $onerror->(type => 'syntax error:'.$prop_name,
3541 level => $self->{must_level},
3542 token => $t);
3543 return ($t, undef);
3544 }
3545 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3546 my $prop_value = lc $t->{value}; ## TODO: case
3547 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3548 $prop_value{'border-bottom-width'} = ['KEYWORD', $prop_value];
3549 $t = $tt->get_next_token;
3550 }
3551 } else {
3552 if ($sign < 0) {
3553 $onerror->(type => 'syntax error:'.$prop_name,
3554 level => $self->{must_level},
3555 token => $t);
3556 return ($t, undef);
3557 }
3558 return ($t, \%prop_value);
3559 }
3560
3561 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3562 $sign = 1;
3563 if ($t->{type} == MINUS_TOKEN) {
3564 $t = $tt->get_next_token;
3565 $sign = -1;
3566 }
3567
3568 if ($t->{type} == DIMENSION_TOKEN) {
3569 my $value = $t->{number} * $sign;
3570 my $unit = lc $t->{value}; ## TODO: case
3571 $t = $tt->get_next_token;
3572 if ($length_unit->{$unit} and $value >= 0) {
3573 $prop_value{'border-left-width'} = ['DIMENSION', $value, $unit];
3574 } else {
3575 $onerror->(type => 'syntax error:'.$prop_name,
3576 level => $self->{must_level},
3577 token => $t);
3578 return ($t, undef);
3579 }
3580 } elsif ($t->{type} == NUMBER_TOKEN and
3581 ($self->{unitless_px} or $t->{number} == 0)) {
3582 my $value = $t->{number} * $sign;
3583 $t = $tt->get_next_token;
3584 $prop_value{'border-left-width'} = ['DIMENSION', $value, 'px'];
3585 unless ($value >= 0) {
3586 $onerror->(type => 'syntax error:'.$prop_name,
3587 level => $self->{must_level},
3588 token => $t);
3589 return ($t, undef);
3590 }
3591 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3592 my $prop_value = lc $t->{value}; ## TODO: case
3593 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3594 $prop_value{'border-left-width'} = ['KEYWORD', $prop_value];
3595 $t = $tt->get_next_token;
3596 }
3597 } else {
3598 if ($sign < 0) {
3599 $onerror->(type => 'syntax error:'.$prop_name,
3600 level => $self->{must_level},
3601 token => $t);
3602 return ($t, undef);
3603 }
3604 return ($t, \%prop_value);
3605 }
3606
3607 return ($t, \%prop_value);
3608 },
3609 serialize => sub {
3610 my ($self, $prop_name, $value) = @_;
3611
3612 local $Error::Depth = $Error::Depth + 1;
3613 my @v;
3614 push @v, $self->border_top_width;
3615 return undef unless defined $v[-1];
3616 push @v, $self->border_right_width;
3617 return undef unless defined $v[-1];
3618 push @v, $self->border_bottom_width;
3619 return undef unless defined $v[-1];
3620 push @v, $self->border_left_width;
3621 return undef unless defined $v[-1];
3622
3623 pop @v if $v[1] eq $v[3];
3624 pop @v if $v[0] eq $v[2];
3625 pop @v if $v[0] eq $v[1];
3626 return join ' ', @v;
3627 },
3628 };
3629 $Attr->{border_width} = $Prop->{'border-width'};
3630
3631 $Prop->{'list-style'} = {
3632 css => 'list-style',
3633 dom => 'list_style',
3634 parse => sub {
3635 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3636
3637 my %prop_value;
3638 my $none = 0;
3639
3640 F: for my $f (1..3) {
3641 if ($t->{type} == IDENT_TOKEN) {
3642 my $prop_value = lc $t->{value}; ## TODO: case folding
3643 $t = $tt->get_next_token;
3644
3645 if ($prop_value eq 'none') {
3646 $none++;
3647 } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
3648 if (exists $prop_value{'list-style-type'}) {
3649 $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3650 $prop_name,
3651 level => $self->{must_level},
3652 token => $t);
3653 return ($t, undef);
3654 } else {
3655 $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
3656 }
3657 } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
3658 if (exists $prop_value{'list-style-position'}) {
3659 $onerror->(type => q[syntax error:duplicate:'list-style-position':].
3660 $prop_name,
3661 level => $self->{must_level},
3662 token => $t);
3663 return ($t, undef);
3664 }
3665
3666 $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
3667 } elsif ($f == 1 and $prop_value eq 'inherit') {
3668 $prop_value{'list-style-type'} = ["INHERIT"];
3669 $prop_value{'list-style-position'} = ["INHERIT"];
3670 $prop_value{'list-style-image'} = ["INHERIT"];
3671 last F;
3672 } else {
3673 if ($f == 1) {
3674 $onerror->(type => 'syntax error:'.$prop_name,
3675 level => $self->{must_level},
3676 token => $t);
3677 return ($t, undef);
3678 } else {
3679 last F;
3680 }
3681 }
3682 } elsif ($t->{type} == URI_TOKEN) {
3683 if (exists $prop_value{'list-style-image'}) {
3684 $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3685 $prop_name,
3686 level => $self->{must_level},
3687 token => $t);
3688 return ($t, undef);
3689 }
3690
3691 $prop_value{'list-style-image'}
3692 = ['URI', $t->{value}, \($self->{base_uri})];
3693 $t = $tt->get_next_token;
3694 } else {
3695 if ($f == 1) {
3696 $onerror->(type => 'syntax error:keyword:'.$prop_name,
3697 level => $self->{must_level},
3698 token => $t);
3699 return ($t, undef);
3700 } else {
3701 last F;
3702 }
3703 }
3704
3705 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3706 } # F
3707 ## NOTE: No browser support |list-style: url(xxx|{EOF}.
3708
3709 if ($none == 1) {
3710 if (exists $prop_value{'list-style-type'}) {
3711 if (exists $prop_value{'list-style-image'}) {
3712 $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3713 $prop_name,
3714 level => $self->{must_level},
3715 token => $t);
3716 return ($t, undef);
3717 } else {
3718 $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
3719 }
3720 } else {
3721 $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
3722 $prop_value{'list-style-image'} = ['KEYWORD', 'none']
3723 unless exists $prop_value{'list-style-image'};
3724 }
3725 } elsif ($none == 2) {
3726 if (exists $prop_value{'list-style-type'}) {
3727 $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3728 $prop_name,
3729 level => $self->{must_level},
3730 token => $t);
3731 return ($t, undef);
3732 }
3733 if (exists $prop_value{'list-style-image'}) {
3734 $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3735 $prop_name,
3736 level => $self->{must_level},
3737 token => $t);
3738 return ($t, undef);
3739 }
3740
3741 $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
3742 $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
3743 } elsif ($none == 3) {
3744 $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3745 $prop_name,
3746 level => $self->{must_level},
3747 token => $t);
3748 return ($t, undef);
3749 }
3750
3751 for (qw/list-style-type list-style-position list-style-image/) {
3752 $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
3753 }
3754
3755 return ($t, \%prop_value);
3756 },
3757 serialize => sub {
3758 my ($self, $prop_name, $value) = @_;
3759
3760 local $Error::Depth = $Error::Depth + 1;
3761 return $self->list_style_type . ' ' . $self->list_style_position .
3762 ' ' . $self->list_style_image;
3763 },
3764 };
3765 $Attr->{list_style} = $Prop->{'list-style'};
3766
3767 ## NOTE: Future version of the implementation will change the way to
3768 ## store the parsed value to support CSS 3 properties.
3769 $Prop->{'text-decoration'} = {
3770 css => 'text-decoration',
3771 dom => 'text_decoration',
3772 key => 'text_decoration',
3773 parse => sub {
3774 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3775
3776 my $value = ['DECORATION']; # , underline, overline, line-through, blink
3777
3778 if ($t->{type} == IDENT_TOKEN) {
3779 my $v = lc $t->{value}; ## TODO: case
3780 $t = $tt->get_next_token;
3781 if ($v eq 'inherit') {
3782 return ($t, {$prop_name => ['INHERIT']});
3783 } elsif ($v eq 'none') {
3784 return ($t, {$prop_name => $value});
3785 } elsif ($v eq 'underline' and
3786 $self->{prop_value}->{$prop_name}->{$v}) {
3787 $value->[1] = 1;
3788 } elsif ($v eq 'overline' and
3789 $self->{prop_value}->{$prop_name}->{$v}) {
3790 $value->[2] = 1;
3791 } elsif ($v eq 'line-through' and
3792 $self->{prop_value}->{$prop_name}->{$v}) {
3793 $value->[3] = 1;
3794 } elsif ($v eq 'blink' and
3795 $self->{prop_value}->{$prop_name}->{$v}) {
3796 $value->[4] = 1;
3797 } else {
3798 $onerror->(type => 'syntax error:'.$prop_name,
3799 level => $self->{must_level},
3800 token => $t);
3801 return ($t, undef);
3802 }
3803 }
3804
3805 F: {
3806 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3807 last F unless $t->{type} == IDENT_TOKEN;
3808
3809 my $v = lc $t->{value}; ## TODO: case
3810 $t = $tt->get_next_token;
3811 if ($v eq 'underline' and
3812 $self->{prop_value}->{$prop_name}->{$v}) {
3813 $value->[1] = 1;
3814 } elsif ($v eq 'overline' and
3815 $self->{prop_value}->{$prop_name}->{$v}) {
3816 $value->[1] = 2;
3817 } elsif ($v eq 'line-through' and
3818 $self->{prop_value}->{$prop_name}->{$v}) {
3819 $value->[1] = 3;
3820 } elsif ($v eq 'blink' and
3821 $self->{prop_value}->{$prop_name}->{$v}) {
3822 $value->[1] = 4;
3823 } else {
3824 last F;
3825 }
3826
3827 redo F;
3828 } # F
3829
3830 return ($t, {$prop_name => $value});
3831 },
3832 serialize => $default_serializer,
3833 initial => ["KEYWORD", "none"],
3834 #inherited => 0,
3835 compute => $compute_as_specified,
3836 };
3837 $Attr->{text_decoration} = $Prop->{'text-decoration'};
3838 $Key->{text_decoration} = $Prop->{'text-decoration'};
3839
3840 1;
3841 ## $Date: 2008/01/06 04:35:18 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24