/[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.26 - (show annotations) (download)
Sun Jan 6 04:35:18 2008 UTC (18 years, 3 months ago) by wakaba
Branch: MAIN
Changes since 1.25: +2 -2 lines
Wrong operator:)

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', and 'text-indent'.
1423
1424 my $sign = 1;
1425 if ($t->{type} == MINUS_TOKEN) {
1426 $t = $tt->get_next_token;
1427 $sign = -1;
1428 }
1429 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
1430
1431 if ($t->{type} == DIMENSION_TOKEN) {
1432 my $value = $t->{number} * $sign;
1433 my $unit = lc $t->{value}; ## TODO: case
1434 $t = $tt->get_next_token;
1435 if ($length_unit->{$unit} and ($allow_negative or $value >= 0)) {
1436 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1437 }
1438 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1439 my $value = $t->{number} * $sign;
1440 $t = $tt->get_next_token;
1441 return ($t, {$prop_name => ['PERCENTAGE', $value]})
1442 if $allow_negative or $value >= 0;
1443 } elsif ($t->{type} == NUMBER_TOKEN and
1444 ($self->{unitless_px} or $t->{number} == 0)) {
1445 my $value = $t->{number} * $sign;
1446 $t = $tt->get_next_token;
1447 return ($t, {$prop_name => ['DIMENSION', $value, 'px']})
1448 if $allow_negative or $value >= 0;
1449 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1450 my $value = lc $t->{value}; ## TODO: case
1451 $t = $tt->get_next_token;
1452 if ($Prop->{$prop_name}->{keyword}->{$value}) {
1453 return ($t, {$prop_name => ['KEYWORD', $value]});
1454 } elsif ($value eq 'inherit') {
1455 return ($t, {$prop_name => ['INHERIT']});
1456 }
1457 }
1458
1459 $onerror->(type => 'syntax error:'.$prop_name,
1460 level => $self->{must_level},
1461 token => $t);
1462 return ($t, undef);
1463 },
1464 allow_negative => 1,
1465 keyword => {auto => 1},
1466 serialize => $default_serializer,
1467 initial => ['DIMENSION', 0, 'px'],
1468 #inherited => 0,
1469 compute => $compute_length,
1470 };
1471 $Attr->{margin_top} = $Prop->{'margin-top'};
1472 $Key->{margin_top} = $Prop->{'margin-top'};
1473
1474 $Prop->{'margin-bottom'} = {
1475 css => 'margin-bottom',
1476 dom => 'margin_bottom',
1477 key => 'margin_bottom',
1478 parse => $Prop->{'margin-top'}->{parse},
1479 allow_negative => 1,
1480 keyword => {auto => 1},
1481 serialize => $default_serializer,
1482 initial => ['DIMENSION', 0, 'px'],
1483 #inherited => 0,
1484 compute => $compute_length,
1485 };
1486 $Attr->{margin_bottom} = $Prop->{'margin-bottom'};
1487 $Key->{margin_bottom} = $Prop->{'margin-bottom'};
1488
1489 $Prop->{'margin-right'} = {
1490 css => 'margin-right',
1491 dom => 'margin_right',
1492 key => 'margin_right',
1493 parse => $Prop->{'margin-top'}->{parse},
1494 allow_negative => 1,
1495 keyword => {auto => 1},
1496 serialize => $default_serializer,
1497 initial => ['DIMENSION', 0, 'px'],
1498 #inherited => 0,
1499 compute => $compute_length,
1500 };
1501 $Attr->{margin_right} = $Prop->{'margin-right'};
1502 $Key->{margin_right} = $Prop->{'margin-right'};
1503
1504 $Prop->{'margin-left'} = {
1505 css => 'margin-left',
1506 dom => 'margin_left',
1507 key => 'margin_left',
1508 parse => $Prop->{'margin-top'}->{parse},
1509 allow_negative => 1,
1510 keyword => {auto => 1},
1511 serialize => $default_serializer,
1512 initial => ['DIMENSION', 0, 'px'],
1513 #inherited => 0,
1514 compute => $compute_length,
1515 };
1516 $Attr->{margin_left} = $Prop->{'margin-left'};
1517 $Key->{margin_left} = $Prop->{'margin-left'};
1518
1519 $Prop->{top} = {
1520 css => 'top',
1521 dom => 'top',
1522 key => 'top',
1523 parse => $Prop->{'margin-top'}->{parse},
1524 allow_negative => 1,
1525 keyword => {auto => 1},
1526 serialize => $default_serializer,
1527 initial => ['KEYWORD', 'auto'],
1528 #inherited => 0,
1529 compute_multiple => sub {
1530 my ($self, $element, $eid, $prop_name) = @_;
1531
1532 my $pos_value = $self->get_computed_value ($element, 'position');
1533 if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
1534 if ($pos_value->[1] eq 'static') {
1535 $self->{computed_value}->{$eid}->{top} = ['KEYWORD', 'auto'];
1536 $self->{computed_value}->{$eid}->{bottom} = ['KEYWORD', 'auto'];
1537 return;
1538 } elsif ($pos_value->[1] eq 'relative') {
1539 my $top_specified = $self->get_specified_value_no_inherit
1540 ($element, 'top');
1541 if (defined $top_specified and
1542 ($top_specified->[0] eq 'DIMENSION' or
1543 $top_specified->[0] eq 'PERCENTAGE')) {
1544 my $tv = $self->{computed_value}->{$eid}->{top}
1545 = $compute_length->($self, $element, 'top', $top_specified);
1546 $self->{computed_value}->{$eid}->{bottom}
1547 = [$tv->[0], -$tv->[1], $tv->[2]];
1548 } else { # top: auto
1549 my $bottom_specified = $self->get_specified_value_no_inherit
1550 ($element, 'bottom');
1551 if (defined $bottom_specified and
1552 ($bottom_specified->[0] eq 'DIMENSION' or
1553 $bottom_specified->[0] eq 'PERCENTAGE')) {
1554 my $tv = $self->{computed_value}->{$eid}->{bottom}
1555 = $compute_length->($self, $element, 'bottom',
1556 $bottom_specified);
1557 $self->{computed_value}->{$eid}->{top}
1558 = [$tv->[0], -$tv->[1], $tv->[2]];
1559 } else { # bottom: auto
1560 $self->{computed_value}->{$eid}->{top} = ['DIMENSION', 0, 'px'];
1561 $self->{computed_value}->{$eid}->{bottom} = ['DIMENSION', 0, 'px'];
1562 }
1563 }
1564 return;
1565 }
1566 }
1567
1568 my $top_specified = $self->get_specified_value_no_inherit
1569 ($element, 'top');
1570 $self->{computed_value}->{$eid}->{top}
1571 = $compute_length->($self, $element, 'top', $top_specified);
1572 my $bottom_specified = $self->get_specified_value_no_inherit
1573 ($element, 'bottom');
1574 $self->{computed_value}->{$eid}->{bottom}
1575 = $compute_length->($self, $element, 'bottom', $bottom_specified);
1576 },
1577 };
1578 $Attr->{top} = $Prop->{top};
1579 $Key->{top} = $Prop->{top};
1580
1581 $Prop->{bottom} = {
1582 css => 'bottom',
1583 dom => 'bottom',
1584 key => 'bottom',
1585 parse => $Prop->{'margin-top'}->{parse},
1586 allow_negative => 1,
1587 keyword => {auto => 1},
1588 serialize => $default_serializer,
1589 initial => ['KEYWORD', 'auto'],
1590 #inherited => 0,
1591 compute_multiple => $Prop->{top}->{compute_multiple},
1592 };
1593 $Attr->{bottom} = $Prop->{bottom};
1594 $Key->{bottom} = $Prop->{bottom};
1595
1596 $Prop->{left} = {
1597 css => 'left',
1598 dom => 'left',
1599 key => 'left',
1600 parse => $Prop->{'margin-top'}->{parse},
1601 allow_negative => 1,
1602 keyword => {auto => 1},
1603 serialize => $default_serializer,
1604 initial => ['KEYWORD', 'auto'],
1605 #inherited => 0,
1606 compute_multiple => sub {
1607 my ($self, $element, $eid, $prop_name) = @_;
1608
1609 my $pos_value = $self->get_computed_value ($element, 'position');
1610 if (defined $pos_value and $pos_value->[0] eq 'KEYWORD') {
1611 if ($pos_value->[1] eq 'static') {
1612 $self->{computed_value}->{$eid}->{left} = ['KEYWORD', 'auto'];
1613 $self->{computed_value}->{$eid}->{right} = ['KEYWORD', 'auto'];
1614 return;
1615 } elsif ($pos_value->[1] eq 'relative') {
1616 my $left_specified = $self->get_specified_value_no_inherit
1617 ($element, 'left');
1618 if (defined $left_specified and
1619 ($left_specified->[0] eq 'DIMENSION' or
1620 $left_specified->[0] eq 'PERCENTAGE')) {
1621 my $right_specified = $self->get_specified_value_no_inherit
1622 ($element, 'right');
1623 if (defined $right_specified and
1624 ($right_specified->[0] eq 'DIMENSION' or
1625 $right_specified->[0] eq 'PERCENTAGE')) {
1626 my $direction = $self->get_computed_value ($element, 'direction');
1627 if (defined $direction and $direction->[0] eq 'KEYWORD' and
1628 $direction->[0] eq 'ltr') {
1629 my $tv = $self->{computed_value}->{$eid}->{left}
1630 = $compute_length->($self, $element, 'left',
1631 $left_specified);
1632 $self->{computed_value}->{$eid}->{right}
1633 = [$tv->[0], -$tv->[1], $tv->[2]];
1634 } else {
1635 my $tv = $self->{computed_value}->{$eid}->{right}
1636 = $compute_length->($self, $element, 'right',
1637 $right_specified);
1638 $self->{computed_value}->{$eid}->{left}
1639 = [$tv->[0], -$tv->[1], $tv->[2]];
1640 }
1641 } else {
1642 my $tv = $self->{computed_value}->{$eid}->{left}
1643 = $compute_length->($self, $element, 'left', $left_specified);
1644 $self->{computed_value}->{$eid}->{right}
1645 = [$tv->[0], -$tv->[1], $tv->[2]];
1646 }
1647 } else { # left: auto
1648 my $right_specified = $self->get_specified_value_no_inherit
1649 ($element, 'right');
1650 if (defined $right_specified and
1651 ($right_specified->[0] eq 'DIMENSION' or
1652 $right_specified->[0] eq 'PERCENTAGE')) {
1653 my $tv = $self->{computed_value}->{$eid}->{right}
1654 = $compute_length->($self, $element, 'right',
1655 $right_specified);
1656 $self->{computed_value}->{$eid}->{left}
1657 = [$tv->[0], -$tv->[1], $tv->[2]];
1658 } else { # right: auto
1659 $self->{computed_value}->{$eid}->{left} = ['DIMENSION', 0, 'px'];
1660 $self->{computed_value}->{$eid}->{right} = ['DIMENSION', 0, 'px'];
1661 }
1662 }
1663 return;
1664 }
1665 }
1666
1667 my $left_specified = $self->get_specified_value_no_inherit
1668 ($element, 'left');
1669 $self->{computed_value}->{$eid}->{left}
1670 = $compute_length->($self, $element, 'left', $left_specified);
1671 my $right_specified = $self->get_specified_value_no_inherit
1672 ($element, 'right');
1673 $self->{computed_value}->{$eid}->{right}
1674 = $compute_length->($self, $element, 'right', $right_specified);
1675 },
1676 };
1677 $Attr->{left} = $Prop->{left};
1678 $Key->{left} = $Prop->{left};
1679
1680 $Prop->{right} = {
1681 css => 'right',
1682 dom => 'right',
1683 key => 'right',
1684 parse => $Prop->{'margin-top'}->{parse},
1685 serialize => $default_serializer,
1686 initial => ['KEYWORD', 'auto'],
1687 #inherited => 0,
1688 compute_multiple => $Prop->{left}->{compute_multiple},
1689 };
1690 $Attr->{right} = $Prop->{right};
1691 $Key->{right} = $Prop->{right};
1692
1693 $Prop->{width} = {
1694 css => 'width',
1695 dom => 'width',
1696 key => 'width',
1697 parse => $Prop->{'margin-top'}->{parse},
1698 #allow_negative => 0,
1699 keyword => {auto => 1},
1700 serialize => $default_serializer,
1701 initial => ['KEYWORD', 'auto'],
1702 #inherited => 0,
1703 compute => $compute_length,
1704 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/width> for
1705 ## browser compatibility issues.
1706 };
1707 $Attr->{width} = $Prop->{width};
1708 $Key->{width} = $Prop->{width};
1709
1710 $Prop->{'min-width'} = {
1711 css => 'min-width',
1712 dom => 'min_width',
1713 key => 'min_width',
1714 parse => $Prop->{'margin-top'}->{parse},
1715 #allow_negative => 0,
1716 #keyword => {},
1717 serialize => $default_serializer,
1718 initial => ['DIMENSION', 0, 'px'],
1719 #inherited => 0,
1720 compute => $compute_length,
1721 };
1722 $Attr->{min_width} = $Prop->{'min-width'};
1723 $Key->{min_width} = $Prop->{'min-width'};
1724
1725 $Prop->{'max-width'} = {
1726 css => 'max-width',
1727 dom => 'max_width',
1728 key => 'max_width',
1729 parse => $Prop->{'margin-top'}->{parse},
1730 #allow_negative => 0,
1731 keyword => {none => 1},
1732 serialize => $default_serializer,
1733 initial => ['KEYWORD', 'none'],
1734 #inherited => 0,
1735 compute => $compute_length,
1736 };
1737 $Attr->{max_width} = $Prop->{'max-width'};
1738 $Key->{max_width} = $Prop->{'max-width'};
1739
1740 $Prop->{height} = {
1741 css => 'height',
1742 dom => 'height',
1743 key => 'height',
1744 parse => $Prop->{'margin-top'}->{parse},
1745 #allow_negative => 0,
1746 keyword => {auto => 1},
1747 serialize => $default_serializer,
1748 initial => ['KEYWORD', 'auto'],
1749 #inherited => 0,
1750 compute => $compute_length,
1751 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/height> for
1752 ## browser compatibility issues.
1753 };
1754 $Attr->{height} = $Prop->{height};
1755 $Key->{height} = $Prop->{height};
1756
1757 $Prop->{'min-height'} = {
1758 css => 'min-height',
1759 dom => 'min_height',
1760 key => 'min_height',
1761 parse => $Prop->{'margin-top'}->{parse},
1762 #allow_negative => 0,
1763 #keyword => {},
1764 serialize => $default_serializer,
1765 initial => ['DIMENSION', 0, 'px'],
1766 #inherited => 0,
1767 compute => $compute_length,
1768 };
1769 $Attr->{min_height} = $Prop->{'min-height'};
1770 $Key->{min_height} = $Prop->{'min-height'};
1771
1772 $Prop->{'max-height'} = {
1773 css => 'max-height',
1774 dom => 'max_height',
1775 key => 'max_height',
1776 parse => $Prop->{'margin-top'}->{parse},
1777 #allow_negative => 0,
1778 keyword => {none => 1},
1779 serialize => $default_serializer,
1780 initial => ['KEYWORD', 'none'],
1781 #inherited => 0,
1782 compute => $compute_length,
1783 };
1784 $Attr->{max_height} = $Prop->{'max-height'};
1785 $Key->{max_height} = $Prop->{'max-height'};
1786
1787 $Prop->{'line-height'} = {
1788 css => 'line-height',
1789 dom => 'line_height',
1790 key => 'line_height',
1791 parse => sub {
1792 my ($self, $prop_name, $tt, $t, $onerror) = @_;
1793
1794 ## NOTE: Similar to 'margin-top', but different handling
1795 ## for unitless numbers.
1796
1797 my $sign = 1;
1798 if ($t->{type} == MINUS_TOKEN) {
1799 $t = $tt->get_next_token;
1800 $sign = -1;
1801 }
1802 my $allow_negative = $Prop->{$prop_name}->{allow_negative};
1803
1804 if ($t->{type} == DIMENSION_TOKEN) {
1805 my $value = $t->{number} * $sign;
1806 my $unit = lc $t->{value}; ## TODO: case
1807 $t = $tt->get_next_token;
1808 if ($length_unit->{$unit} and $value >= 0) {
1809 return ($t, {$prop_name => ['DIMENSION', $value, $unit]});
1810 }
1811 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
1812 my $value = $t->{number} * $sign;
1813 $t = $tt->get_next_token;
1814 return ($t, {$prop_name => ['PERCENTAGE', $value]})
1815 if $value >= 0;
1816 } elsif ($t->{type} == NUMBER_TOKEN) {
1817 my $value = $t->{number} * $sign;
1818 $t = $tt->get_next_token;
1819 return ($t, {$prop_name => ['NUMBER', $value]}) if $value >= 0;
1820 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
1821 my $value = lc $t->{value}; ## TODO: case
1822 $t = $tt->get_next_token;
1823 if ($value eq 'normal') {
1824 return ($t, {$prop_name => ['KEYWORD', $value]});
1825 } elsif ($value eq 'inherit') {
1826 return ($t, {$prop_name => ['INHERIT']});
1827 }
1828 }
1829
1830 $onerror->(type => 'syntax error:'.$prop_name,
1831 level => $self->{must_level},
1832 token => $t);
1833 return ($t, undef);
1834 },
1835 serialize => $default_serializer,
1836 initial => ['KEYWORD', 'normal'],
1837 inherited => 1,
1838 compute => $compute_length,
1839 };
1840 $Attr->{line_height} = $Prop->{'line-height'};
1841 $Key->{line_height} = $Prop->{'line-height'};
1842
1843 $Prop->{'vertical-align'} = {
1844 css => 'vertical-align',
1845 dom => 'vertical_align',
1846 key => 'vertical_align',
1847 parse => $Prop->{'margin-top'}->{parse},
1848 allow_negative => 1,
1849 keyword => {
1850 baseline => 1, sub => 1, super => 1, top => 1, 'text-top' => 1,
1851 middle => 1, bottom => 1, 'text-bottom' => 1,
1852 },
1853 ## NOTE: Currently, we don't support option to select subset of keywords
1854 ## supported by application (i.e.
1855 ## $parser->{prop_value}->{'line-height'->{$keyword}). Should we support
1856 ## it?
1857 serialize => $default_serializer,
1858 initial => ['KEYWORD', 'baseline'],
1859 #inherited => 0,
1860 compute => $compute_length,
1861 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/vertical-align> for
1862 ## browser compatibility issues.
1863 };
1864 $Attr->{vertical_align} = $Prop->{'vertical-align'};
1865 $Key->{vertical_align} = $Prop->{'vertical-align'};
1866
1867 $Prop->{'text-indent'} = {
1868 css => 'text-indent',
1869 dom => 'text_indent',
1870 key => 'text_indent',
1871 parse => $Prop->{'margin-top'}->{parse},
1872 allow_negative => 1,
1873 keyword => {},
1874 serialize => $default_serializer,
1875 initial => ['DIMENSION', 0, 'px'],
1876 inherited => 1,
1877 compute => $compute_length,
1878 };
1879 $Attr->{text_indent} = $Prop->{'text-indent'};
1880 $Key->{text_indent} = $Prop->{'text-indent'};
1881
1882 $Prop->{'padding-top'} = {
1883 css => 'padding-top',
1884 dom => 'padding_top',
1885 key => 'padding_top',
1886 parse => $Prop->{'margin-top'}->{parse},
1887 #allow_negative => 0,
1888 #keyword => {},
1889 serialize => $default_serializer,
1890 initial => ['DIMENSION', 0, 'px'],
1891 #inherited => 0,
1892 compute => $compute_length,
1893 };
1894 $Attr->{padding_top} = $Prop->{'padding-top'};
1895 $Key->{padding_top} = $Prop->{'padding-top'};
1896
1897 $Prop->{'padding-bottom'} = {
1898 css => 'padding-bottom',
1899 dom => 'padding_bottom',
1900 key => 'padding_bottom',
1901 parse => $Prop->{'padding-top'}->{parse},
1902 #allow_negative => 0,
1903 #keyword => {},
1904 serialize => $default_serializer,
1905 initial => ['DIMENSION', 0, 'px'],
1906 #inherited => 0,
1907 compute => $compute_length,
1908 };
1909 $Attr->{padding_bottom} = $Prop->{'padding-bottom'};
1910 $Key->{padding_bottom} = $Prop->{'padding-bottom'};
1911
1912 $Prop->{'padding-right'} = {
1913 css => 'padding-right',
1914 dom => 'padding_right',
1915 key => 'padding_right',
1916 parse => $Prop->{'padding-top'}->{parse},
1917 #allow_negative => 0,
1918 #keyword => {},
1919 serialize => $default_serializer,
1920 initial => ['DIMENSION', 0, 'px'],
1921 #inherited => 0,
1922 compute => $compute_length,
1923 };
1924 $Attr->{padding_right} = $Prop->{'padding-right'};
1925 $Key->{padding_right} = $Prop->{'padding-right'};
1926
1927 $Prop->{'padding-left'} = {
1928 css => 'padding-left',
1929 dom => 'padding_left',
1930 key => 'padding_left',
1931 parse => $Prop->{'padding-top'}->{parse},
1932 #allow_negative => 0,
1933 #keyword => {},
1934 serialize => $default_serializer,
1935 initial => ['DIMENSION', 0, 'px'],
1936 #inherited => 0,
1937 compute => $compute_length,
1938 };
1939 $Attr->{padding_left} = $Prop->{'padding-left'};
1940 $Key->{padding_left} = $Prop->{'padding-left'};
1941
1942 $Prop->{'border-top-width'} = {
1943 css => 'border-top-width',
1944 dom => 'border_top_width',
1945 key => 'border_top_width',
1946 parse => $Prop->{'margin-top'}->{parse},
1947 #allow_negative => 0,
1948 keyword => {thin => 1, medium => 1, thick => 1},
1949 serialize => $default_serializer,
1950 initial => ['KEYWORD', 'medium'],
1951 #inherited => 0,
1952 compute => sub {
1953 my ($self, $element, $prop_name, $specified_value) = @_;
1954
1955 ## NOTE: Used for 'border-top-width', 'border-right-width',
1956 ## 'border-bottom-width', 'border-right-width', and
1957 ## 'outline-width'.
1958
1959 my $style_prop = $prop_name;
1960 $style_prop =~ s/width/style/;
1961 my $style = $self->get_computed_value ($element, $style_prop);
1962 if (defined $style and $style->[0] eq 'KEYWORD' and
1963 ($style->[1] eq 'none' or $style->[1] eq 'hidden')) {
1964 return ['DIMENSION', 0, 'px'];
1965 }
1966
1967 my $value = $compute_length->(@_);
1968 if (defined $value and $value->[0] eq 'KEYWORD') {
1969 if ($value->[1] eq 'thin') {
1970 return ['DIMENSION', 1, 'px']; ## Firefox/Opera
1971 } elsif ($value->[1] eq 'medium') {
1972 return ['DIMENSION', 3, 'px']; ## Firefox/Opera
1973 } elsif ($value->[1] eq 'thick') {
1974 return ['DIMENSION', 5, 'px']; ## Firefox
1975 }
1976 }
1977 return $value;
1978 },
1979 ## NOTE: CSS3 will allow <percentage> as an option in <border-width>.
1980 ## Opera 9 has already implemented it.
1981 };
1982 $Attr->{border_top_width} = $Prop->{'border-top-width'};
1983 $Key->{border_top_width} = $Prop->{'border-top-width'};
1984
1985 $Prop->{'border-right-width'} = {
1986 css => 'border-right-width',
1987 dom => 'border_right_width',
1988 key => 'border_right_width',
1989 parse => $Prop->{'border-top-width'}->{parse},
1990 #allow_negative => 0,
1991 keyword => {thin => 1, medium => 1, thick => 1},
1992 serialize => $default_serializer,
1993 initial => ['KEYWORD', 'medium'],
1994 #inherited => 0,
1995 compute => $Prop->{'border-top-width'}->{compute},
1996 };
1997 $Attr->{border_right_width} = $Prop->{'border-right-width'};
1998 $Key->{border_right_width} = $Prop->{'border-right-width'};
1999
2000 $Prop->{'border-bottom-width'} = {
2001 css => 'border-bottom-width',
2002 dom => 'border_bottom_width',
2003 key => 'border_bottom_width',
2004 parse => $Prop->{'border-top-width'}->{parse},
2005 #allow_negative => 0,
2006 keyword => {thin => 1, medium => 1, thick => 1},
2007 serialize => $default_serializer,
2008 initial => ['KEYWORD', 'medium'],
2009 #inherited => 0,
2010 compute => $Prop->{'border-top-width'}->{compute},
2011 };
2012 $Attr->{border_bottom_width} = $Prop->{'border-bottom-width'};
2013 $Key->{border_bottom_width} = $Prop->{'border-bottom-width'};
2014
2015 $Prop->{'border-left-width'} = {
2016 css => 'border-left-width',
2017 dom => 'border_left_width',
2018 key => 'border_left_width',
2019 parse => $Prop->{'border-top-width'}->{parse},
2020 #allow_negative => 0,
2021 keyword => {thin => 1, medium => 1, thick => 1},
2022 serialize => $default_serializer,
2023 initial => ['KEYWORD', 'medium'],
2024 #inherited => 0,
2025 compute => $Prop->{'border-top-width'}->{compute},
2026 };
2027 $Attr->{border_left_width} = $Prop->{'border-left-width'};
2028 $Key->{border_left_width} = $Prop->{'border-left-width'};
2029
2030 $Prop->{'outline-width'} = {
2031 css => 'outline-width',
2032 dom => 'outline_width',
2033 key => 'outline_width',
2034 parse => $Prop->{'border-top-width'}->{parse},
2035 #allow_negative => 0,
2036 keyword => {thin => 1, medium => 1, thick => 1},
2037 serialize => $default_serializer,
2038 initial => ['KEYWORD', 'medium'],
2039 #inherited => 0,
2040 compute => $Prop->{'border-top-width'}->{compute},
2041 };
2042 $Attr->{outline_width} = $Prop->{'outline-width'};
2043 $Key->{outline_width} = $Prop->{'outline-width'};
2044
2045 $Prop->{'font-weight'} = {
2046 css => 'font-weight',
2047 dom => 'font_weight',
2048 key => 'font_weight',
2049 parse => sub {
2050 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2051
2052 if ($t->{type} == NUMBER_TOKEN) {
2053 ## ISSUE: See <http://suika.fam.cx/gate/2005/sw/font-weight> for
2054 ## browser compatibility issue.
2055 my $value = $t->{number};
2056 $t = $tt->get_next_token;
2057 if ($value % 100 == 0 and 100 <= $value and $value <= 900) {
2058 return ($t, {$prop_name => ['WEIGHT', $value, 0]});
2059 }
2060 } elsif ($t->{type} == IDENT_TOKEN) {
2061 my $value = lc $t->{value}; ## TODO: case
2062 $t = $tt->get_next_token;
2063 if ({
2064 normal => 1, bold => 1, bolder => 1, lighter => 1,
2065 }->{$value}) {
2066 return ($t, {$prop_name => ['KEYWORD', $value]});
2067 } elsif ($value eq 'inherit') {
2068 return ($t, {$prop_name => ['INHERIT']});
2069 }
2070 }
2071
2072 $onerror->(type => 'syntax error:'.$prop_name,
2073 level => $self->{must_level},
2074 token => $t);
2075 return ($t, undef);
2076 },
2077 serialize => $default_serializer,
2078 initial => ['KEYWORD', 'normal'],
2079 inherited => 1,
2080 compute => sub {
2081 my ($self, $element, $prop_name, $specified_value) = @_;
2082
2083 if (defined $specified_value and $specified_value->[0] eq 'KEYWORD') {
2084 if ($specified_value->[1] eq 'normal') {
2085 return ['WEIGHT', 400, 0];
2086 } elsif ($specified_value->[1] eq 'bold') {
2087 return ['WEIGHT', 700, 0];
2088 } elsif ($specified_value->[1] eq 'bolder') {
2089 my $parent_element = $element->manakai_parent_element;
2090 if (defined $parent_element) {
2091 my $parent_value = $self->get_cascaded_value
2092 ($parent_element, $prop_name); ## NOTE: What Firefox does.
2093 return ['WEIGHT', $parent_value->[1], $parent_value->[2] + 1];
2094 } else {
2095 return ['WEIGHT', 400, 1];
2096 }
2097 } elsif ($specified_value->[1] eq 'lighter') {
2098 my $parent_element = $element->manakai_parent_element;
2099 if (defined $parent_element) {
2100 my $parent_value = $self->get_cascaded_value
2101 ($parent_element, $prop_name); ## NOTE: What Firefox does.
2102 return ['WEIGHT', $parent_value->[1], $parent_value->[2] - 1];
2103 } else {
2104 return ['WEIGHT', 400, 1];
2105 }
2106 }
2107 #} elsif (defined $specified_value and $specified_value->[0] eq 'WEIGHT') {
2108 #
2109 }
2110
2111 return $specified_value;
2112 },
2113 };
2114 $Attr->{font_weight} = $Prop->{'font-weight'};
2115 $Key->{font_weight} = $Prop->{'font-weight'};
2116
2117 my $uri_or_none_parser = sub {
2118 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2119
2120 if ($t->{type} == URI_TOKEN) {
2121 my $value = $t->{value};
2122 $t = $tt->get_next_token;
2123 return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
2124 } elsif ($t->{type} == IDENT_TOKEN) {
2125 my $value = lc $t->{value}; ## TODO: case
2126 $t = $tt->get_next_token;
2127 if ($value eq 'none') {
2128 ## NOTE: |none| is the default value and therefore it must be
2129 ## supported anyway.
2130 return ($t, {$prop_name => ["KEYWORD", 'none']});
2131 } elsif ($value eq 'inherit') {
2132 return ($t, {$prop_name => ['INHERIT']});
2133 }
2134 ## NOTE: None of Firefox2, WinIE6, and Opera9 support this case.
2135 #} elsif ($t->{type} == URI_INVALID_TOKEN) {
2136 # my $value = $t->{value};
2137 # $t = $tt->get_next_token;
2138 # if ($t->{type} == EOF_TOKEN) {
2139 # $onerror->(type => 'syntax error:eof:'.$prop_name,
2140 # level => $self->{must_level},
2141 # token => $t);
2142 #
2143 # return ($t, {$prop_name => ['URI', $value, \($self->{base_uri})]});
2144 # }
2145 }
2146
2147 $onerror->(type => 'syntax error:'.$prop_name,
2148 level => $self->{must_level},
2149 token => $t);
2150 return ($t, undef);
2151 }; # $uri_or_none_parser
2152
2153 my $compute_uri_or_none = sub {
2154 my ($self, $element, $prop_name, $specified_value) = @_;
2155
2156 if (defined $specified_value and
2157 $specified_value->[0] eq 'URI' and
2158 defined $specified_value->[2]) {
2159 require Message::DOM::DOMImplementation;
2160 return ['URI',
2161 Message::DOM::DOMImplementation->create_uri_reference
2162 ($specified_value->[1])
2163 ->get_absolute_reference (${$specified_value->[2]})
2164 ->get_uri_reference,
2165 $specified_value->[2]];
2166 }
2167
2168 return $specified_value;
2169 }; # $compute_uri_or_none
2170
2171 $Prop->{'list-style-image'} = {
2172 css => 'list-style-image',
2173 dom => 'list_style_image',
2174 key => 'list_style_image',
2175 parse => $uri_or_none_parser,
2176 serialize => $default_serializer,
2177 initial => ['KEYWORD', 'none'],
2178 inherited => 1,
2179 compute => $compute_uri_or_none,
2180 };
2181 $Attr->{list_style_image} = $Prop->{'list-style-image'};
2182 $Key->{list_style_image} = $Prop->{'list-style-image'};
2183
2184 $Prop->{'background-image'} = {
2185 css => 'background-image',
2186 dom => 'background_image',
2187 key => 'background_image',
2188 parse => $uri_or_none_parser,
2189 serialize => $default_serializer,
2190 initial => ['KEYWORD', 'none'],
2191 #inherited => 0,
2192 compute => $compute_uri_or_none,
2193 };
2194 $Attr->{background_image} = $Prop->{'background-image'};
2195 $Key->{background_image} = $Prop->{'background-image'};
2196
2197 my $border_style_keyword = {
2198 none => 1, hidden => 1, dotted => 1, dashed => 1, solid => 1,
2199 double => 1, groove => 1, ridge => 1, inset => 1, outset => 1,
2200 };
2201
2202 $Prop->{'border-top-style'} = {
2203 css => 'border-top-style',
2204 dom => 'border_top_style',
2205 key => 'border_top_style',
2206 parse => $one_keyword_parser,
2207 serialize => $default_serializer,
2208 keyword => $border_style_keyword,
2209 initial => ["KEYWORD", "none"],
2210 #inherited => 0,
2211 compute => $compute_as_specified,
2212 };
2213 $Attr->{border_top_style} = $Prop->{'border-top-style'};
2214 $Key->{border_top_style} = $Prop->{'border-top-style'};
2215
2216 $Prop->{'border-right-style'} = {
2217 css => 'border-right-style',
2218 dom => 'border_right_style',
2219 key => 'border_right_style',
2220 parse => $one_keyword_parser,
2221 serialize => $default_serializer,
2222 keyword => $border_style_keyword,
2223 initial => ["KEYWORD", "none"],
2224 #inherited => 0,
2225 compute => $compute_as_specified,
2226 };
2227 $Attr->{border_right_style} = $Prop->{'border-right-style'};
2228 $Key->{border_right_style} = $Prop->{'border-right-style'};
2229
2230 $Prop->{'border-bottom-style'} = {
2231 css => 'border-bottom-style',
2232 dom => 'border_bottom_style',
2233 key => 'border_bottom_style',
2234 parse => $one_keyword_parser,
2235 serialize => $default_serializer,
2236 keyword => $border_style_keyword,
2237 initial => ["KEYWORD", "none"],
2238 #inherited => 0,
2239 compute => $compute_as_specified,
2240 };
2241 $Attr->{border_bottom_style} = $Prop->{'border-bottom-style'};
2242 $Key->{border_bottom_style} = $Prop->{'border-bottom-style'};
2243
2244 $Prop->{'border-left-style'} = {
2245 css => 'border-left-style',
2246 dom => 'border_left_style',
2247 key => 'border_left_style',
2248 parse => $one_keyword_parser,
2249 serialize => $default_serializer,
2250 keyword => $border_style_keyword,
2251 initial => ["KEYWORD", "none"],
2252 #inherited => 0,
2253 compute => $compute_as_specified,
2254 };
2255 $Attr->{border_left_style} = $Prop->{'border-left-style'};
2256 $Key->{border_left_style} = $Prop->{'border-left-style'};
2257
2258 $Prop->{'outline-style'} = {
2259 css => 'outline-style',
2260 dom => 'outline_style',
2261 key => 'outline_style',
2262 parse => $one_keyword_parser,
2263 serialize => $default_serializer,
2264 keyword => {%$border_style_keyword},
2265 initial => ['KEYWORD', 'none'],
2266 #inherited => 0,
2267 compute => $compute_as_specified,
2268 };
2269 $Attr->{outline_style} = $Prop->{'outline-style'};
2270 $Key->{outline_style} = $Prop->{'outline-style'};
2271 delete $Prop->{'outline-style'}->{keyword}->{hidden};
2272
2273 $Prop->{'font-family'} = {
2274 css => 'font-family',
2275 dom => 'font_family',
2276 key => 'font_family',
2277 parse => sub {
2278 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2279
2280 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/font-family> for
2281 ## how chaotic browsers are!
2282
2283 my @prop_value;
2284
2285 my $font_name = '';
2286 my $may_be_generic = 1;
2287 my $may_be_inherit = 1;
2288 my $has_s = 0;
2289 F: {
2290 if ($t->{type} == IDENT_TOKEN) {
2291 undef $may_be_inherit if $has_s or length $font_name;
2292 undef $may_be_generic if $has_s or length $font_name;
2293 $font_name .= ' ' if $has_s;
2294 $font_name .= $t->{value};
2295 undef $has_s;
2296 $t = $tt->get_next_token;
2297 } elsif ($t->{type} == STRING_TOKEN) {
2298 $font_name .= ' ' if $has_s;
2299 $font_name .= $t->{value};
2300 undef $may_be_inherit;
2301 undef $may_be_generic;
2302 undef $has_s;
2303 $t = $tt->get_next_token;
2304 } elsif ($t->{type} == COMMA_TOKEN) {
2305 if ($may_be_generic and
2306 {
2307 serif => 1, 'sans-serif' => 1, cursive => 1,
2308 fantasy => 1, monospace => 1, '-manakai-default' => 1,
2309 }->{lc $font_name}) { ## TODO: case
2310 push @prop_value, ['KEYWORD', $font_name];
2311 } elsif (not $may_be_generic or length $font_name) {
2312 push @prop_value, ["STRING", $font_name];
2313 }
2314 undef $may_be_inherit;
2315 $may_be_generic = 1;
2316 undef $has_s;
2317 $font_name = '';
2318 $t = $tt->get_next_token;
2319 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2320 } elsif ($t->{type} == S_TOKEN) {
2321 $has_s = 1;
2322 $t = $tt->get_next_token;
2323 } else {
2324 if ($may_be_generic and
2325 {
2326 serif => 1, 'sans-serif' => 1, cursive => 1,
2327 fantasy => 1, monospace => 1, '-manakai-default' => 1,
2328 }->{lc $font_name}) { ## TODO: case
2329 push @prop_value, ['KEYWORD', $font_name];
2330 } elsif (not $may_be_generic or length $font_name) {
2331 push @prop_value, ['STRING', $font_name];
2332 } else {
2333 $onerror->(type => 'syntax error:'.$prop_name,
2334 level => $self->{must_level},
2335 token => $t);
2336 return ($t, undef);
2337 }
2338 last F;
2339 }
2340 redo F;
2341 } # F
2342
2343 if ($may_be_inherit and
2344 @prop_value == 1 and
2345 $prop_value[0]->[0] eq 'STRING' and
2346 lc $prop_value[0]->[1] eq 'inherit') { ## TODO: case
2347 return ($t, {$prop_name => ['INHERIT']});
2348 } else {
2349 unshift @prop_value, 'FONT';
2350 return ($t, {$prop_name => \@prop_value});
2351 }
2352 },
2353 serialize => sub {
2354 my ($self, $prop_name, $value) = @_;
2355
2356 if ($value->[0] eq 'FONT') {
2357 return join ', ', map {
2358 if ($_->[0] eq 'STRING') {
2359 '"'.$_->[1].'"'; ## NOTE: This is what Firefox does.
2360 } elsif ($_->[0] eq 'KEYWORD') {
2361 $_->[1]; ## NOTE: This is what Firefox does.
2362 } else {
2363 ## NOTE: This should be an error.
2364 '""';
2365 }
2366 } @$value[1..$#$value];
2367 } elsif ($value->[0] eq 'INHERIT') {
2368 return 'inherit';
2369 } else {
2370 return undef;
2371 }
2372 },
2373 initial => ['FONT', ['KEYWORD', '-manakai-default']],
2374 inherited => 1,
2375 compute => $compute_as_specified,
2376 };
2377 $Attr->{font_family} = $Prop->{'font-family'};
2378 $Key->{font_family} = $Prop->{'font-family'};
2379
2380 $Prop->{cursor} = {
2381 css => 'cursor',
2382 dom => 'cursor',
2383 key => 'cursor',
2384 parse => sub {
2385 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2386
2387 ## NOTE: See <http://suika.fam.cx/gate/2005/sw/cursor> for browser
2388 ## compatibility issues.
2389
2390 my @prop_value = ('CURSOR');
2391
2392 F: {
2393 if ($t->{type} == IDENT_TOKEN) {
2394 my $v = lc $t->{value}; ## TODO: case
2395 $t = $tt->get_next_token;
2396 if ($Prop->{$prop_name}->{keyword}->{$v}) {
2397 push @prop_value, ['KEYWORD', $v];
2398 last F;
2399 } elsif ($v eq 'inherit' and @prop_value == 1) {
2400 return ($t, {$prop_name => ['INHERIT']});
2401 } else {
2402 $onerror->(type => 'syntax error:'.$prop_name,
2403 level => $self->{must_level},
2404 token => $t);
2405 return ($t, undef);
2406 }
2407 } elsif ($t->{type} == URI_TOKEN) {
2408 push @prop_value, ['URI', $t->{value}, \($self->{base_uri})];
2409 $t = $tt->get_next_token;
2410 } else {
2411 $onerror->(type => 'syntax error:'.$prop_name,
2412 level => $self->{must_level},
2413 token => $t);
2414 return ($t, undef);
2415 }
2416
2417 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2418 if ($t->{type} == COMMA_TOKEN) {
2419 $t = $tt->get_next_token;
2420 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2421 redo F;
2422 }
2423 } # F
2424
2425 return ($t, {$prop_name => \@prop_value});
2426 },
2427 serialize => sub {
2428 my ($self, $prop_name, $value) = @_;
2429
2430 if ($value->[0] eq 'CURSOR') {
2431 return join ', ', map {
2432 if ($_->[0] eq 'URI') {
2433 'url('.$_->[1].')'; ## NOTE: This is what Firefox does.
2434 } elsif ($_->[0] eq 'KEYWORD') {
2435 $_->[1];
2436 } else {
2437 ## NOTE: This should be an error.
2438 '""';
2439 }
2440 } @$value[1..$#$value];
2441 } elsif ($value->[0] eq 'INHERIT') {
2442 return 'inherit';
2443 } else {
2444 return undef;
2445 }
2446 },
2447 keyword => {
2448 auto => 1, crosshair => 1, default => 1, pointer => 1, move => 1,
2449 'e-resize' => 1, 'ne-resize' => 1, 'nw-resize' => 1, 'n-resize' => 1,
2450 'n-resize' => 1, 'se-resize' => 1, 'sw-resize' => 1, 's-resize' => 1,
2451 'w-resize' => 1, text => 1, wait => 1, help => 1, progress => 1,
2452 },
2453 initial => ['CURSOR', ['KEYWORD', 'auto']],
2454 inherited => 1,
2455 compute => sub {
2456 my ($self, $element, $prop_name, $specified_value) = @_;
2457
2458 if (defined $specified_value and $specified_value->[0] eq 'CURSOR') {
2459 my @new_value = ('CURSOR');
2460 for my $value (@$specified_value[1..$#$specified_value]) {
2461 if ($value->[0] eq 'URI') {
2462 if (defined $value->[2]) {
2463 require Message::DOM::DOMImplementation;
2464 push @new_value, ['URI',
2465 Message::DOM::DOMImplementation
2466 ->create_uri_reference ($value->[1])
2467 ->get_absolute_reference (${$value->[2]})
2468 ->get_uri_reference,
2469 $value->[2]];
2470 } else {
2471 push @new_value, $value;
2472 }
2473 } else {
2474 push @new_value, $value;
2475 }
2476 }
2477 return \@new_value;
2478 }
2479
2480 return $specified_value;
2481 },
2482 };
2483 $Attr->{cursor} = $Prop->{cursor};
2484 $Key->{cursor} = $Prop->{cursor};
2485
2486 $Prop->{'border-style'} = {
2487 css => 'border-style',
2488 dom => 'border_style',
2489 parse => sub {
2490 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2491
2492 my %prop_value;
2493 if ($t->{type} == IDENT_TOKEN) {
2494 my $prop_value = lc $t->{value}; ## TODO: case folding
2495 $t = $tt->get_next_token;
2496 if ($border_style_keyword->{$prop_value} and
2497 $self->{prop_value}->{'border-top-style'}->{$prop_value}) {
2498 $prop_value{'border-top-style'} = ["KEYWORD", $prop_value];
2499 } elsif ($prop_value eq 'inherit') {
2500 $prop_value{'border-top-style'} = ["INHERIT"];
2501 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
2502 $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
2503 $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2504 return ($t, \%prop_value);
2505 } else {
2506 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2507 level => $self->{must_level},
2508 token => $t);
2509 return ($t, undef);
2510 }
2511 $prop_value{'border-right-style'} = $prop_value{'border-top-style'};
2512 $prop_value{'border-bottom-style'} = $prop_value{'border-top-style'};
2513 $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2514 } else {
2515 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2516 level => $self->{must_level},
2517 token => $t);
2518 return ($t, undef);
2519 }
2520
2521 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2522 if ($t->{type} == IDENT_TOKEN) {
2523 my $prop_value = lc $t->{value}; ## TODO: case folding
2524 $t = $tt->get_next_token;
2525 if ($border_style_keyword->{$prop_value} and
2526 $self->{prop_value}->{'border-right-style'}->{$prop_value}) {
2527 $prop_value{'border-right-style'} = ["KEYWORD", $prop_value];
2528 } else {
2529 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2530 level => $self->{must_level},
2531 token => $t);
2532 return ($t, undef);
2533 }
2534 $prop_value{'border-left-style'} = $prop_value{'border-right-style'};
2535
2536 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2537 if ($t->{type} == IDENT_TOKEN) {
2538 my $prop_value = lc $t->{value}; ## TODO: case folding
2539 $t = $tt->get_next_token;
2540 if ($border_style_keyword->{$prop_value} and
2541 $self->{prop_value}->{'border-bottom-style'}->{$prop_value}) {
2542 $prop_value{'border-bottom-style'} = ["KEYWORD", $prop_value];
2543 } else {
2544 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2545 level => $self->{must_level},
2546 token => $t);
2547 return ($t, undef);
2548 }
2549
2550 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2551 if ($t->{type} == IDENT_TOKEN) {
2552 my $prop_value = lc $t->{value}; ## TODO: case folding
2553 $t = $tt->get_next_token;
2554 if ($border_style_keyword->{$prop_value} and
2555 $self->{prop_value}->{'border-left-style'}->{$prop_value}) {
2556 $prop_value{'border-left-style'} = ["KEYWORD", $prop_value];
2557 } else {
2558 $onerror->(type => 'syntax error:keyword:'.$prop_name,
2559 level => $self->{must_level},
2560 token => $t);
2561 return ($t, undef);
2562 }
2563 }
2564 }
2565 }
2566
2567 return ($t, \%prop_value);
2568 },
2569 serialize => sub {
2570 my ($self, $prop_name, $value) = @_;
2571
2572 local $Error::Depth = $Error::Depth + 1;
2573 my @v;
2574 push @v, $self->border_top_style;
2575 return undef unless defined $v[-1];
2576 push @v, $self->border_right_style;
2577 return undef unless defined $v[-1];
2578 push @v, $self->border_bottom_style;
2579 return undef unless defined $v[-1];
2580 push @v, $self->border_left_style;
2581 return undef unless defined $v[-1];
2582
2583 pop @v if $v[1] eq $v[3];
2584 pop @v if $v[0] eq $v[2];
2585 pop @v if $v[0] eq $v[1];
2586 return join ' ', @v;
2587 },
2588 };
2589 $Attr->{border_style} = $Prop->{'border-style'};
2590
2591 $Prop->{margin} = {
2592 css => 'margin',
2593 dom => 'margin',
2594 parse => sub {
2595 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2596
2597 my %prop_value;
2598
2599 my $sign = 1;
2600 if ($t->{type} == MINUS_TOKEN) {
2601 $t = $tt->get_next_token;
2602 $sign = -1;
2603 }
2604
2605 if ($t->{type} == DIMENSION_TOKEN) {
2606 my $value = $t->{number} * $sign;
2607 my $unit = lc $t->{value}; ## TODO: case
2608 $t = $tt->get_next_token;
2609 if ($length_unit->{$unit}) {
2610 $prop_value{'margin-top'} = ['DIMENSION', $value, $unit];
2611 } else {
2612 $onerror->(type => 'syntax error:'.$prop_name,
2613 level => $self->{must_level},
2614 token => $t);
2615 return ($t, undef);
2616 }
2617 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2618 my $value = $t->{number} * $sign;
2619 $t = $tt->get_next_token;
2620 $prop_value{'margin-top'} = ['PERCENTAGE', $value];
2621 } elsif ($t->{type} == NUMBER_TOKEN and
2622 ($self->{unitless_px} or $t->{number} == 0)) {
2623 my $value = $t->{number} * $sign;
2624 $t = $tt->get_next_token;
2625 $prop_value{'margin-top'} = ['DIMENSION', $value, 'px'];
2626 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2627 my $prop_value = lc $t->{value}; ## TODO: case folding
2628 $t = $tt->get_next_token;
2629 if ($prop_value eq 'auto') {
2630 $prop_value{'margin-top'} = ['KEYWORD', $prop_value];
2631 } elsif ($prop_value eq 'inherit') {
2632 $prop_value{'margin-top'} = ['INHERIT'];
2633 $prop_value{'margin-right'} = $prop_value{'margin-top'};
2634 $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
2635 $prop_value{'margin-left'} = $prop_value{'margin-right'};
2636 return ($t, \%prop_value);
2637 } else {
2638 $onerror->(type => 'syntax error:'.$prop_name,
2639 level => $self->{must_level},
2640 token => $t);
2641 return ($t, undef);
2642 }
2643 } else {
2644 $onerror->(type => 'syntax error:'.$prop_name,
2645 level => $self->{must_level},
2646 token => $t);
2647 return ($t, undef);
2648 }
2649 $prop_value{'margin-right'} = $prop_value{'margin-top'};
2650 $prop_value{'margin-bottom'} = $prop_value{'margin-top'};
2651 $prop_value{'margin-left'} = $prop_value{'margin-right'};
2652
2653 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2654 $sign = 1;
2655 if ($t->{type} == MINUS_TOKEN) {
2656 $t = $tt->get_next_token;
2657 $sign = -1;
2658 }
2659
2660 if ($t->{type} == DIMENSION_TOKEN) {
2661 my $value = $t->{number} * $sign;
2662 my $unit = lc $t->{value}; ## TODO: case
2663 $t = $tt->get_next_token;
2664 if ($length_unit->{$unit}) {
2665 $prop_value{'margin-right'} = ['DIMENSION', $value, $unit];
2666 } else {
2667 $onerror->(type => 'syntax error:'.$prop_name,
2668 level => $self->{must_level},
2669 token => $t);
2670 return ($t, undef);
2671 }
2672 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2673 my $value = $t->{number} * $sign;
2674 $t = $tt->get_next_token;
2675 $prop_value{'margin-right'} = ['PERCENTAGE', $value];
2676 } elsif ($t->{type} == NUMBER_TOKEN and
2677 ($self->{unitless_px} or $t->{number} == 0)) {
2678 my $value = $t->{number} * $sign;
2679 $t = $tt->get_next_token;
2680 $prop_value{'margin-right'} = ['DIMENSION', $value, 'px'];
2681 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2682 my $prop_value = lc $t->{value}; ## TODO: case folding
2683 $t = $tt->get_next_token;
2684 if ($prop_value eq 'auto') {
2685 $prop_value{'margin-right'} = ['KEYWORD', $prop_value];
2686 } else {
2687 $onerror->(type => 'syntax error:'.$prop_name,
2688 level => $self->{must_level},
2689 token => $t);
2690 return ($t, undef);
2691 }
2692 } else {
2693 if ($sign < 0) {
2694 $onerror->(type => 'syntax error:'.$prop_name,
2695 level => $self->{must_level},
2696 token => $t);
2697 return ($t, undef);
2698 }
2699 return ($t, \%prop_value);
2700 }
2701 $prop_value{'margin-left'} = $prop_value{'margin-right'};
2702
2703 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2704 $sign = 1;
2705 if ($t->{type} == MINUS_TOKEN) {
2706 $t = $tt->get_next_token;
2707 $sign = -1;
2708 }
2709
2710 if ($t->{type} == DIMENSION_TOKEN) {
2711 my $value = $t->{number} * $sign;
2712 my $unit = lc $t->{value}; ## TODO: case
2713 $t = $tt->get_next_token;
2714 if ($length_unit->{$unit}) {
2715 $prop_value{'margin-bottom'} = ['DIMENSION', $value, $unit];
2716 } else {
2717 $onerror->(type => 'syntax error:'.$prop_name,
2718 level => $self->{must_level},
2719 token => $t);
2720 return ($t, undef);
2721 }
2722 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2723 my $value = $t->{number} * $sign;
2724 $t = $tt->get_next_token;
2725 $prop_value{'margin-bottom'} = ['PERCENTAGE', $value];
2726 } elsif ($t->{type} == NUMBER_TOKEN and
2727 ($self->{unitless_px} or $t->{number} == 0)) {
2728 my $value = $t->{number} * $sign;
2729 $t = $tt->get_next_token;
2730 $prop_value{'margin-bottom'} = ['DIMENSION', $value, 'px'];
2731 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2732 my $prop_value = lc $t->{value}; ## TODO: case folding
2733 $t = $tt->get_next_token;
2734 if ($prop_value eq 'auto') {
2735 $prop_value{'margin-bottom'} = ['KEYWORD', $prop_value];
2736 } else {
2737 $onerror->(type => 'syntax error:'.$prop_name,
2738 level => $self->{must_level},
2739 token => $t);
2740 return ($t, undef);
2741 }
2742 } else {
2743 if ($sign < 0) {
2744 $onerror->(type => 'syntax error:'.$prop_name,
2745 level => $self->{must_level},
2746 token => $t);
2747 return ($t, undef);
2748 }
2749 return ($t, \%prop_value);
2750 }
2751
2752 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2753 $sign = 1;
2754 if ($t->{type} == MINUS_TOKEN) {
2755 $t = $tt->get_next_token;
2756 $sign = -1;
2757 }
2758
2759 if ($t->{type} == DIMENSION_TOKEN) {
2760 my $value = $t->{number} * $sign;
2761 my $unit = lc $t->{value}; ## TODO: case
2762 $t = $tt->get_next_token;
2763 if ($length_unit->{$unit}) {
2764 $prop_value{'margin-left'} = ['DIMENSION', $value, $unit];
2765 } else {
2766 $onerror->(type => 'syntax error:'.$prop_name,
2767 level => $self->{must_level},
2768 token => $t);
2769 return ($t, undef);
2770 }
2771 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2772 my $value = $t->{number} * $sign;
2773 $t = $tt->get_next_token;
2774 $prop_value{'margin-left'} = ['PERCENTAGE', $value];
2775 } elsif ($t->{type} == NUMBER_TOKEN and
2776 ($self->{unitless_px} or $t->{number} == 0)) {
2777 my $value = $t->{number} * $sign;
2778 $t = $tt->get_next_token;
2779 $prop_value{'margin-left'} = ['DIMENSION', $value, 'px'];
2780 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2781 my $prop_value = lc $t->{value}; ## TODO: case folding
2782 $t = $tt->get_next_token;
2783 if ($prop_value eq 'auto') {
2784 $prop_value{'margin-left'} = ['KEYWORD', $prop_value];
2785 } else {
2786 $onerror->(type => 'syntax error:'.$prop_name,
2787 level => $self->{must_level},
2788 token => $t);
2789 return ($t, undef);
2790 }
2791 } else {
2792 if ($sign < 0) {
2793 $onerror->(type => 'syntax error:'.$prop_name,
2794 level => $self->{must_level},
2795 token => $t);
2796 return ($t, undef);
2797 }
2798 return ($t, \%prop_value);
2799 }
2800
2801 return ($t, \%prop_value);
2802 },
2803 serialize => sub {
2804 my ($self, $prop_name, $value) = @_;
2805
2806 local $Error::Depth = $Error::Depth + 1;
2807 my @v;
2808 push @v, $self->margin_top;
2809 return undef unless defined $v[-1];
2810 push @v, $self->margin_right;
2811 return undef unless defined $v[-1];
2812 push @v, $self->margin_bottom;
2813 return undef unless defined $v[-1];
2814 push @v, $self->margin_left;
2815 return undef unless defined $v[-1];
2816
2817 pop @v if $v[1] eq $v[3];
2818 pop @v if $v[0] eq $v[2];
2819 pop @v if $v[0] eq $v[1];
2820 return join ' ', @v;
2821 },
2822 };
2823 $Attr->{margin} = $Prop->{margin};
2824
2825 $Prop->{padding} = {
2826 css => 'padding',
2827 dom => 'padding',
2828 parse => sub {
2829 my ($self, $prop_name, $tt, $t, $onerror) = @_;
2830
2831 my %prop_value;
2832
2833 my $sign = 1;
2834 if ($t->{type} == MINUS_TOKEN) {
2835 $t = $tt->get_next_token;
2836 $sign = -1;
2837 }
2838
2839 if ($t->{type} == DIMENSION_TOKEN) {
2840 my $value = $t->{number} * $sign;
2841 my $unit = lc $t->{value}; ## TODO: case
2842 $t = $tt->get_next_token;
2843 if ($length_unit->{$unit} and $value >= 0) {
2844 $prop_value{'padding-top'} = ['DIMENSION', $value, $unit];
2845 } else {
2846 $onerror->(type => 'syntax error:'.$prop_name,
2847 level => $self->{must_level},
2848 token => $t);
2849 return ($t, undef);
2850 }
2851 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2852 my $value = $t->{number} * $sign;
2853 $t = $tt->get_next_token;
2854 $prop_value{'padding-top'} = ['PERCENTAGE', $value];
2855 unless ($value >= 0) {
2856 $onerror->(type => 'syntax error:'.$prop_name,
2857 level => $self->{must_level},
2858 token => $t);
2859 return ($t, undef);
2860 }
2861 } elsif ($t->{type} == NUMBER_TOKEN and
2862 ($self->{unitless_px} or $t->{number} == 0)) {
2863 my $value = $t->{number} * $sign;
2864 $t = $tt->get_next_token;
2865 $prop_value{'padding-top'} = ['DIMENSION', $value, 'px'];
2866 unless ($value >= 0) {
2867 $onerror->(type => 'syntax error:'.$prop_name,
2868 level => $self->{must_level},
2869 token => $t);
2870 return ($t, undef);
2871 }
2872 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
2873 my $prop_value = lc $t->{value}; ## TODO: case folding
2874 $t = $tt->get_next_token;
2875 if ($prop_value eq 'inherit') {
2876 $prop_value{'padding-top'} = ['INHERIT'];
2877 $prop_value{'padding-right'} = $prop_value{'padding-top'};
2878 $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
2879 $prop_value{'padding-left'} = $prop_value{'padding-right'};
2880 return ($t, \%prop_value);
2881 } else {
2882 $onerror->(type => 'syntax error:'.$prop_name,
2883 level => $self->{must_level},
2884 token => $t);
2885 return ($t, undef);
2886 }
2887 } else {
2888 $onerror->(type => 'syntax error:'.$prop_name,
2889 level => $self->{must_level},
2890 token => $t);
2891 return ($t, undef);
2892 }
2893 $prop_value{'padding-right'} = $prop_value{'padding-top'};
2894 $prop_value{'padding-bottom'} = $prop_value{'padding-top'};
2895 $prop_value{'padding-left'} = $prop_value{'padding-right'};
2896
2897 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2898 $sign = 1;
2899 if ($t->{type} == MINUS_TOKEN) {
2900 $t = $tt->get_next_token;
2901 $sign = -1;
2902 }
2903
2904 if ($t->{type} == DIMENSION_TOKEN) {
2905 my $value = $t->{number} * $sign;
2906 my $unit = lc $t->{value}; ## TODO: case
2907 $t = $tt->get_next_token;
2908 if ($length_unit->{$unit} and $value >= 0) {
2909 $prop_value{'padding-right'} = ['DIMENSION', $value, $unit];
2910 } else {
2911 $onerror->(type => 'syntax error:'.$prop_name,
2912 level => $self->{must_level},
2913 token => $t);
2914 return ($t, undef);
2915 }
2916 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2917 my $value = $t->{number} * $sign;
2918 $t = $tt->get_next_token;
2919 $prop_value{'padding-right'} = ['PERCENTAGE', $value];
2920 unless ($value >= 0) {
2921 $onerror->(type => 'syntax error:'.$prop_name,
2922 level => $self->{must_level},
2923 token => $t);
2924 return ($t, undef);
2925 }
2926 } elsif ($t->{type} == NUMBER_TOKEN and
2927 ($self->{unitless_px} or $t->{number} == 0)) {
2928 my $value = $t->{number} * $sign;
2929 $t = $tt->get_next_token;
2930 $prop_value{'padding-right'} = ['DIMENSION', $value, 'px'];
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 } else {
2938 if ($sign < 0) {
2939 $onerror->(type => 'syntax error:'.$prop_name,
2940 level => $self->{must_level},
2941 token => $t);
2942 return ($t, undef);
2943 }
2944 return ($t, \%prop_value);
2945 }
2946 $prop_value{'padding-left'} = $prop_value{'padding-right'};
2947
2948 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2949 $sign = 1;
2950 if ($t->{type} == MINUS_TOKEN) {
2951 $t = $tt->get_next_token;
2952 $sign = -1;
2953 }
2954
2955 if ($t->{type} == DIMENSION_TOKEN) {
2956 my $value = $t->{number} * $sign;
2957 my $unit = lc $t->{value}; ## TODO: case
2958 $t = $tt->get_next_token;
2959 if ($length_unit->{$unit} and $value >= 0) {
2960 $prop_value{'padding-bottom'} = ['DIMENSION', $value, $unit];
2961 } else {
2962 $onerror->(type => 'syntax error:'.$prop_name,
2963 level => $self->{must_level},
2964 token => $t);
2965 return ($t, undef);
2966 }
2967 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
2968 my $value = $t->{number} * $sign;
2969 $t = $tt->get_next_token;
2970 $prop_value{'padding-bottom'} = ['PERCENTAGE', $value];
2971 unless ($value >= 0) {
2972 $onerror->(type => 'syntax error:'.$prop_name,
2973 level => $self->{must_level},
2974 token => $t);
2975 return ($t, undef);
2976 }
2977 } elsif ($t->{type} == NUMBER_TOKEN and
2978 ($self->{unitless_px} or $t->{number} == 0)) {
2979 my $value = $t->{number} * $sign;
2980 $t = $tt->get_next_token;
2981 $prop_value{'padding-bottom'} = ['DIMENSION', $value, 'px'];
2982 unless ($value >= 0) {
2983 $onerror->(type => 'syntax error:'.$prop_name,
2984 level => $self->{must_level},
2985 token => $t);
2986 return ($t, undef);
2987 }
2988 } else {
2989 if ($sign < 0) {
2990 $onerror->(type => 'syntax error:'.$prop_name,
2991 level => $self->{must_level},
2992 token => $t);
2993 return ($t, undef);
2994 }
2995 return ($t, \%prop_value);
2996 }
2997
2998 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
2999 $sign = 1;
3000 if ($t->{type} == MINUS_TOKEN) {
3001 $t = $tt->get_next_token;
3002 $sign = -1;
3003 }
3004
3005 if ($t->{type} == DIMENSION_TOKEN) {
3006 my $value = $t->{number} * $sign;
3007 my $unit = lc $t->{value}; ## TODO: case
3008 $t = $tt->get_next_token;
3009 if ($length_unit->{$unit} and $value >= 0) {
3010 $prop_value{'padding-left'} = ['DIMENSION', $value, $unit];
3011 } else {
3012 $onerror->(type => 'syntax error:'.$prop_name,
3013 level => $self->{must_level},
3014 token => $t);
3015 return ($t, undef);
3016 }
3017 } elsif ($t->{type} == PERCENTAGE_TOKEN) {
3018 my $value = $t->{number} * $sign;
3019 $t = $tt->get_next_token;
3020 $prop_value{'padding-left'} = ['PERCENTAGE', $value];
3021 unless ($value >= 0) {
3022 $onerror->(type => 'syntax error:'.$prop_name,
3023 level => $self->{must_level},
3024 token => $t);
3025 return ($t, undef);
3026 }
3027 } elsif ($t->{type} == NUMBER_TOKEN and
3028 ($self->{unitless_px} or $t->{number} == 0)) {
3029 my $value = $t->{number} * $sign;
3030 $t = $tt->get_next_token;
3031 $prop_value{'padding-left'} = ['DIMENSION', $value, 'px'];
3032 unless ($value >= 0) {
3033 $onerror->(type => 'syntax error:'.$prop_name,
3034 level => $self->{must_level},
3035 token => $t);
3036 return ($t, undef);
3037 }
3038 } else {
3039 if ($sign < 0) {
3040 $onerror->(type => 'syntax error:'.$prop_name,
3041 level => $self->{must_level},
3042 token => $t);
3043 return ($t, undef);
3044 }
3045 return ($t, \%prop_value);
3046 }
3047
3048 return ($t, \%prop_value);
3049 },
3050 serialize => sub {
3051 my ($self, $prop_name, $value) = @_;
3052
3053 local $Error::Depth = $Error::Depth + 1;
3054 my @v;
3055 push @v, $self->padding_top;
3056 return undef unless defined $v[-1];
3057 push @v, $self->padding_right;
3058 return undef unless defined $v[-1];
3059 push @v, $self->padding_bottom;
3060 return undef unless defined $v[-1];
3061 push @v, $self->padding_left;
3062 return undef unless defined $v[-1];
3063
3064 pop @v if $v[1] eq $v[3];
3065 pop @v if $v[0] eq $v[2];
3066 pop @v if $v[0] eq $v[1];
3067 return join ' ', @v;
3068 },
3069 };
3070 $Attr->{padding} = $Prop->{padding};
3071
3072 $Prop->{'border-spacing'} = {
3073 css => 'border-spacing',
3074 dom => 'border_spacing',
3075 parse => sub {
3076 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3077
3078 my %prop_value;
3079
3080 my $sign = 1;
3081 if ($t->{type} == MINUS_TOKEN) {
3082 $t = $tt->get_next_token;
3083 $sign = -1;
3084 }
3085
3086 if ($t->{type} == DIMENSION_TOKEN) {
3087 my $value = $t->{number} * $sign;
3088 my $unit = lc $t->{value}; ## TODO: case
3089 $t = $tt->get_next_token;
3090 if ($length_unit->{$unit} and $value >= 0) {
3091 $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, $unit];
3092 } else {
3093 $onerror->(type => 'syntax error:'.$prop_name,
3094 level => $self->{must_level},
3095 token => $t);
3096 return ($t, undef);
3097 }
3098 } elsif ($t->{type} == NUMBER_TOKEN and
3099 ($self->{unitless_px} or $t->{number} == 0)) {
3100 my $value = $t->{number} * $sign;
3101 $t = $tt->get_next_token;
3102 $prop_value{'-manakai-border-spacing-x'} = ['DIMENSION', $value, 'px'];
3103 unless ($value >= 0) {
3104 $onerror->(type => 'syntax error:'.$prop_name,
3105 level => $self->{must_level},
3106 token => $t);
3107 return ($t, undef);
3108 }
3109 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3110 my $prop_value = lc $t->{value}; ## TODO: case folding
3111 $t = $tt->get_next_token;
3112 if ($prop_value eq 'inherit') {
3113 $prop_value{'-manakai-border-spacing-x'} = ['INHERIT'];
3114 $prop_value{'-manakai-border-spacing-y'}
3115 = $prop_value{'-manakai-border-spacing-x'};
3116 return ($t, \%prop_value);
3117 } else {
3118 $onerror->(type => 'syntax error:'.$prop_name,
3119 level => $self->{must_level},
3120 token => $t);
3121 return ($t, undef);
3122 }
3123 } else {
3124 $onerror->(type => 'syntax error:'.$prop_name,
3125 level => $self->{must_level},
3126 token => $t);
3127 return ($t, undef);
3128 }
3129 $prop_value{'-manakai-border-spacing-y'}
3130 = $prop_value{'-manakai-border-spacing-x'};
3131
3132 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3133 $sign = 1;
3134 if ($t->{type} == MINUS_TOKEN) {
3135 $t = $tt->get_next_token;
3136 $sign = -1;
3137 }
3138
3139 if ($t->{type} == DIMENSION_TOKEN) {
3140 my $value = $t->{number} * $sign;
3141 my $unit = lc $t->{value}; ## TODO: case
3142 $t = $tt->get_next_token;
3143 if ($length_unit->{$unit} and $value >= 0) {
3144 $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, $unit];
3145 } else {
3146 $onerror->(type => 'syntax error:'.$prop_name,
3147 level => $self->{must_level},
3148 token => $t);
3149 return ($t, undef);
3150 }
3151 } elsif ($t->{type} == NUMBER_TOKEN and
3152 ($self->{unitless_px} or $t->{number} == 0)) {
3153 my $value = $t->{number} * $sign;
3154 $t = $tt->get_next_token;
3155 $prop_value{'-manakai-border-spacing-y'} = ['DIMENSION', $value, 'px'];
3156 unless ($value >= 0) {
3157 $onerror->(type => 'syntax error:'.$prop_name,
3158 level => $self->{must_level},
3159 token => $t);
3160 return ($t, undef);
3161 }
3162 } else {
3163 if ($sign < 0) {
3164 $onerror->(type => 'syntax error:'.$prop_name,
3165 level => $self->{must_level},
3166 token => $t);
3167 return ($t, undef);
3168 }
3169 return ($t, \%prop_value);
3170 }
3171
3172 return ($t, \%prop_value);
3173 },
3174 serialize => sub {
3175 my ($self, $prop_name, $value) = @_;
3176
3177 local $Error::Depth = $Error::Depth + 1;
3178 my @v;
3179 push @v, $self->_manakai_border_spacing_x;
3180 return undef unless defined $v[-1];
3181 push @v, $self->_manakai_border_spacing_y;
3182 return undef unless defined $v[-1];
3183
3184 pop @v if $v[0] eq $v[1];
3185 return join ' ', @v;
3186 },
3187 serialize_multiple => $Prop->{'-manakai-border-spacing-x'}
3188 ->{serialize_multiple},
3189 };
3190 $Attr->{border_spacing} = $Prop->{'border-spacing'};
3191
3192 $Prop->{'border-width'} = {
3193 css => 'border-width',
3194 dom => 'border_width',
3195 parse => sub {
3196 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3197
3198 my %prop_value;
3199
3200 my $sign = 1;
3201 if ($t->{type} == MINUS_TOKEN) {
3202 $t = $tt->get_next_token;
3203 $sign = -1;
3204 }
3205
3206 if ($t->{type} == DIMENSION_TOKEN) {
3207 my $value = $t->{number} * $sign;
3208 my $unit = lc $t->{value}; ## TODO: case
3209 $t = $tt->get_next_token;
3210 if ($length_unit->{$unit} and $value >= 0) {
3211 $prop_value{'border-top-width'} = ['DIMENSION', $value, $unit];
3212 } else {
3213 $onerror->(type => 'syntax error:'.$prop_name,
3214 level => $self->{must_level},
3215 token => $t);
3216 return ($t, undef);
3217 }
3218 } elsif ($t->{type} == NUMBER_TOKEN and
3219 ($self->{unitless_px} or $t->{number} == 0)) {
3220 my $value = $t->{number} * $sign;
3221 $t = $tt->get_next_token;
3222 $prop_value{'border-top-width'} = ['DIMENSION', $value, 'px'];
3223 unless ($value >= 0) {
3224 $onerror->(type => 'syntax error:'.$prop_name,
3225 level => $self->{must_level},
3226 token => $t);
3227 return ($t, undef);
3228 }
3229 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3230 my $prop_value = lc $t->{value}; ## TODO: case folding
3231 $t = $tt->get_next_token;
3232 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3233 $prop_value{'border-top-width'} = ['KEYWORD', $prop_value];
3234 } elsif ($prop_value eq 'inherit') {
3235 $prop_value{'border-top-width'} = ['INHERIT'];
3236 $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
3237 $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
3238 $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
3239 return ($t, \%prop_value);
3240 } else {
3241 $onerror->(type => 'syntax error:'.$prop_name,
3242 level => $self->{must_level},
3243 token => $t);
3244 return ($t, undef);
3245 }
3246 } else {
3247 $onerror->(type => 'syntax error:'.$prop_name,
3248 level => $self->{must_level},
3249 token => $t);
3250 return ($t, undef);
3251 }
3252 $prop_value{'border-right-width'} = $prop_value{'border-top-width'};
3253 $prop_value{'border-bottom-width'} = $prop_value{'border-top-width'};
3254 $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
3255
3256 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3257 $sign = 1;
3258 if ($t->{type} == MINUS_TOKEN) {
3259 $t = $tt->get_next_token;
3260 $sign = -1;
3261 }
3262
3263 if ($t->{type} == DIMENSION_TOKEN) {
3264 my $value = $t->{number} * $sign;
3265 my $unit = lc $t->{value}; ## TODO: case
3266 $t = $tt->get_next_token;
3267 if ($length_unit->{$unit} and $value >= 0) {
3268 $prop_value{'border-right-width'} = ['DIMENSION', $value, $unit];
3269 } else {
3270 $onerror->(type => 'syntax error:'.$prop_name,
3271 level => $self->{must_level},
3272 token => $t);
3273 return ($t, undef);
3274 }
3275 } elsif ($t->{type} == NUMBER_TOKEN and
3276 ($self->{unitless_px} or $t->{number} == 0)) {
3277 my $value = $t->{number} * $sign;
3278 $t = $tt->get_next_token;
3279 $prop_value{'border-right-width'} = ['DIMENSION', $value, 'px'];
3280 unless ($value >= 0) {
3281 $onerror->(type => 'syntax error:'.$prop_name,
3282 level => $self->{must_level},
3283 token => $t);
3284 return ($t, undef);
3285 }
3286 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3287 my $prop_value = lc $t->{value}; ## TODO: case
3288 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3289 $prop_value{'border-right-width'} = ['KEYWORD', $prop_value];
3290 $t = $tt->get_next_token;
3291 }
3292 } else {
3293 if ($sign < 0) {
3294 $onerror->(type => 'syntax error:'.$prop_name,
3295 level => $self->{must_level},
3296 token => $t);
3297 return ($t, undef);
3298 }
3299 return ($t, \%prop_value);
3300 }
3301 $prop_value{'border-left-width'} = $prop_value{'border-right-width'};
3302
3303 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3304 $sign = 1;
3305 if ($t->{type} == MINUS_TOKEN) {
3306 $t = $tt->get_next_token;
3307 $sign = -1;
3308 }
3309
3310 if ($t->{type} == DIMENSION_TOKEN) {
3311 my $value = $t->{number} * $sign;
3312 my $unit = lc $t->{value}; ## TODO: case
3313 $t = $tt->get_next_token;
3314 if ($length_unit->{$unit} and $value >= 0) {
3315 $prop_value{'border-bottom-width'} = ['DIMENSION', $value, $unit];
3316 } else {
3317 $onerror->(type => 'syntax error:'.$prop_name,
3318 level => $self->{must_level},
3319 token => $t);
3320 return ($t, undef);
3321 }
3322 } elsif ($t->{type} == NUMBER_TOKEN and
3323 ($self->{unitless_px} or $t->{number} == 0)) {
3324 my $value = $t->{number} * $sign;
3325 $t = $tt->get_next_token;
3326 $prop_value{'border-bottom-width'} = ['DIMENSION', $value, 'px'];
3327 unless ($value >= 0) {
3328 $onerror->(type => 'syntax error:'.$prop_name,
3329 level => $self->{must_level},
3330 token => $t);
3331 return ($t, undef);
3332 }
3333 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3334 my $prop_value = lc $t->{value}; ## TODO: case
3335 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3336 $prop_value{'border-bottom-width'} = ['KEYWORD', $prop_value];
3337 $t = $tt->get_next_token;
3338 }
3339 } else {
3340 if ($sign < 0) {
3341 $onerror->(type => 'syntax error:'.$prop_name,
3342 level => $self->{must_level},
3343 token => $t);
3344 return ($t, undef);
3345 }
3346 return ($t, \%prop_value);
3347 }
3348
3349 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3350 $sign = 1;
3351 if ($t->{type} == MINUS_TOKEN) {
3352 $t = $tt->get_next_token;
3353 $sign = -1;
3354 }
3355
3356 if ($t->{type} == DIMENSION_TOKEN) {
3357 my $value = $t->{number} * $sign;
3358 my $unit = lc $t->{value}; ## TODO: case
3359 $t = $tt->get_next_token;
3360 if ($length_unit->{$unit} and $value >= 0) {
3361 $prop_value{'border-left-width'} = ['DIMENSION', $value, $unit];
3362 } else {
3363 $onerror->(type => 'syntax error:'.$prop_name,
3364 level => $self->{must_level},
3365 token => $t);
3366 return ($t, undef);
3367 }
3368 } elsif ($t->{type} == NUMBER_TOKEN and
3369 ($self->{unitless_px} or $t->{number} == 0)) {
3370 my $value = $t->{number} * $sign;
3371 $t = $tt->get_next_token;
3372 $prop_value{'border-left-width'} = ['DIMENSION', $value, 'px'];
3373 unless ($value >= 0) {
3374 $onerror->(type => 'syntax error:'.$prop_name,
3375 level => $self->{must_level},
3376 token => $t);
3377 return ($t, undef);
3378 }
3379 } elsif ($sign > 0 and $t->{type} == IDENT_TOKEN) {
3380 my $prop_value = lc $t->{value}; ## TODO: case
3381 if ({thin => 1, medium => 1, thick => 1}->{$prop_value}) {
3382 $prop_value{'border-left-width'} = ['KEYWORD', $prop_value];
3383 $t = $tt->get_next_token;
3384 }
3385 } else {
3386 if ($sign < 0) {
3387 $onerror->(type => 'syntax error:'.$prop_name,
3388 level => $self->{must_level},
3389 token => $t);
3390 return ($t, undef);
3391 }
3392 return ($t, \%prop_value);
3393 }
3394
3395 return ($t, \%prop_value);
3396 },
3397 serialize => sub {
3398 my ($self, $prop_name, $value) = @_;
3399
3400 local $Error::Depth = $Error::Depth + 1;
3401 my @v;
3402 push @v, $self->border_top_width;
3403 return undef unless defined $v[-1];
3404 push @v, $self->border_right_width;
3405 return undef unless defined $v[-1];
3406 push @v, $self->border_bottom_width;
3407 return undef unless defined $v[-1];
3408 push @v, $self->border_left_width;
3409 return undef unless defined $v[-1];
3410
3411 pop @v if $v[1] eq $v[3];
3412 pop @v if $v[0] eq $v[2];
3413 pop @v if $v[0] eq $v[1];
3414 return join ' ', @v;
3415 },
3416 };
3417 $Attr->{border_width} = $Prop->{'border-width'};
3418
3419 $Prop->{'list-style'} = {
3420 css => 'list-style',
3421 dom => 'list_style',
3422 parse => sub {
3423 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3424
3425 my %prop_value;
3426 my $none = 0;
3427
3428 F: for my $f (1..3) {
3429 if ($t->{type} == IDENT_TOKEN) {
3430 my $prop_value = lc $t->{value}; ## TODO: case folding
3431 $t = $tt->get_next_token;
3432
3433 if ($prop_value eq 'none') {
3434 $none++;
3435 } elsif ($Prop->{'list-style-type'}->{keyword}->{$prop_value}) {
3436 if (exists $prop_value{'list-style-type'}) {
3437 $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3438 $prop_name,
3439 level => $self->{must_level},
3440 token => $t);
3441 return ($t, undef);
3442 } else {
3443 $prop_value{'list-style-type'} = ['KEYWORD', $prop_value];
3444 }
3445 } elsif ($Prop->{'list-style-position'}->{keyword}->{$prop_value}) {
3446 if (exists $prop_value{'list-style-position'}) {
3447 $onerror->(type => q[syntax error:duplicate:'list-style-position':].
3448 $prop_name,
3449 level => $self->{must_level},
3450 token => $t);
3451 return ($t, undef);
3452 }
3453
3454 $prop_value{'list-style-position'} = ['KEYWORD', $prop_value];
3455 } elsif ($f == 1 and $prop_value eq 'inherit') {
3456 $prop_value{'list-style-type'} = ["INHERIT"];
3457 $prop_value{'list-style-position'} = ["INHERIT"];
3458 $prop_value{'list-style-image'} = ["INHERIT"];
3459 last F;
3460 } else {
3461 if ($f == 1) {
3462 $onerror->(type => 'syntax error:'.$prop_name,
3463 level => $self->{must_level},
3464 token => $t);
3465 return ($t, undef);
3466 } else {
3467 last F;
3468 }
3469 }
3470 } elsif ($t->{type} == URI_TOKEN) {
3471 if (exists $prop_value{'list-style-image'}) {
3472 $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3473 $prop_name,
3474 level => $self->{must_level},
3475 token => $t);
3476 return ($t, undef);
3477 }
3478
3479 $prop_value{'list-style-image'}
3480 = ['URI', $t->{value}, \($self->{base_uri})];
3481 $t = $tt->get_next_token;
3482 } else {
3483 if ($f == 1) {
3484 $onerror->(type => 'syntax error:keyword:'.$prop_name,
3485 level => $self->{must_level},
3486 token => $t);
3487 return ($t, undef);
3488 } else {
3489 last F;
3490 }
3491 }
3492
3493 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3494 } # F
3495 ## NOTE: No browser support |list-style: url(xxx|{EOF}.
3496
3497 if ($none == 1) {
3498 if (exists $prop_value{'list-style-type'}) {
3499 if (exists $prop_value{'list-style-image'}) {
3500 $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3501 $prop_name,
3502 level => $self->{must_level},
3503 token => $t);
3504 return ($t, undef);
3505 } else {
3506 $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
3507 }
3508 } else {
3509 $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
3510 $prop_value{'list-style-image'} = ['KEYWORD', 'none']
3511 unless exists $prop_value{'list-style-image'};
3512 }
3513 } elsif ($none == 2) {
3514 if (exists $prop_value{'list-style-type'}) {
3515 $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3516 $prop_name,
3517 level => $self->{must_level},
3518 token => $t);
3519 return ($t, undef);
3520 }
3521 if (exists $prop_value{'list-style-image'}) {
3522 $onerror->(type => q[syntax error:duplicate:'list-style-image':].
3523 $prop_name,
3524 level => $self->{must_level},
3525 token => $t);
3526 return ($t, undef);
3527 }
3528
3529 $prop_value{'list-style-type'} = ['KEYWORD', 'none'];
3530 $prop_value{'list-style-image'} = ['KEYWORD', 'none'];
3531 } elsif ($none == 3) {
3532 $onerror->(type => q[syntax error:duplicate:'list-style-type':].
3533 $prop_name,
3534 level => $self->{must_level},
3535 token => $t);
3536 return ($t, undef);
3537 }
3538
3539 for (qw/list-style-type list-style-position list-style-image/) {
3540 $prop_value{$_} = $Prop->{$_}->{initial} unless exists $prop_value{$_};
3541 }
3542
3543 return ($t, \%prop_value);
3544 },
3545 serialize => sub {
3546 my ($self, $prop_name, $value) = @_;
3547
3548 local $Error::Depth = $Error::Depth + 1;
3549 return $self->list_style_type . ' ' . $self->list_style_position .
3550 ' ' . $self->list_style_image;
3551 },
3552 };
3553 $Attr->{list_style} = $Prop->{'list-style'};
3554
3555 ## NOTE: Future version of the implementation will change the way to
3556 ## store the parsed value to support CSS 3 properties.
3557 $Prop->{'text-decoration'} = {
3558 css => 'text-decoration',
3559 dom => 'text_decoration',
3560 key => 'text_decoration',
3561 parse => sub {
3562 my ($self, $prop_name, $tt, $t, $onerror) = @_;
3563
3564 my $value = ['DECORATION']; # , underline, overline, line-through, blink
3565
3566 if ($t->{type} == IDENT_TOKEN) {
3567 my $v = lc $t->{value}; ## TODO: case
3568 $t = $tt->get_next_token;
3569 if ($v eq 'inherit') {
3570 return ($t, {$prop_name => ['INHERIT']});
3571 } elsif ($v eq 'none') {
3572 return ($t, {$prop_name => $value});
3573 } elsif ($v eq 'underline' and
3574 $self->{prop_value}->{$prop_name}->{$v}) {
3575 $value->[1] = 1;
3576 } elsif ($v eq 'overline' and
3577 $self->{prop_value}->{$prop_name}->{$v}) {
3578 $value->[2] = 1;
3579 } elsif ($v eq 'line-through' and
3580 $self->{prop_value}->{$prop_name}->{$v}) {
3581 $value->[3] = 1;
3582 } elsif ($v eq 'blink' and
3583 $self->{prop_value}->{$prop_name}->{$v}) {
3584 $value->[4] = 1;
3585 } else {
3586 $onerror->(type => 'syntax error:'.$prop_name,
3587 level => $self->{must_level},
3588 token => $t);
3589 return ($t, undef);
3590 }
3591 }
3592
3593 F: {
3594 $t = $tt->get_next_token while $t->{type} == S_TOKEN;
3595 last F unless $t->{type} == IDENT_TOKEN;
3596
3597 my $v = lc $t->{value}; ## TODO: case
3598 $t = $tt->get_next_token;
3599 if ($v eq 'underline' and
3600 $self->{prop_value}->{$prop_name}->{$v}) {
3601 $value->[1] = 1;
3602 } elsif ($v eq 'overline' and
3603 $self->{prop_value}->{$prop_name}->{$v}) {
3604 $value->[1] = 2;
3605 } elsif ($v eq 'line-through' and
3606 $self->{prop_value}->{$prop_name}->{$v}) {
3607 $value->[1] = 3;
3608 } elsif ($v eq 'blink' and
3609 $self->{prop_value}->{$prop_name}->{$v}) {
3610 $value->[1] = 4;
3611 } else {
3612 last F;
3613 }
3614
3615 redo F;
3616 } # F
3617
3618 return ($t, {$prop_name => $value});
3619 },
3620 serialize => $default_serializer,
3621 initial => ["KEYWORD", "none"],
3622 #inherited => 0,
3623 compute => $compute_as_specified,
3624 };
3625 $Attr->{text_decoration} = $Prop->{'text-decoration'};
3626 $Key->{text_decoration} = $Prop->{'text-decoration'};
3627
3628 1;
3629 ## $Date: 2008/01/06 04:30:59 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24