| 2 |
use strict; |
use strict; |
| 3 |
our $VERSION=do{my @r=(q$Revision$=~/\d+/g);sprintf "%d."."%02d" x $#r,@r}; |
our $VERSION=do{my @r=(q$Revision$=~/\d+/g);sprintf "%d."."%02d" x $#r,@r}; |
| 4 |
|
|
| 5 |
## This is a very, very early version of an HTML parser. |
## This is an early version of an HTML parser. |
| 6 |
|
|
| 7 |
my $permitted_slash_tag_name = { |
my $permitted_slash_tag_name = { |
| 8 |
base => 1, |
base => 1, |
| 302 |
}; |
}; |
| 303 |
# $phrasing_category: all other elements |
# $phrasing_category: all other elements |
| 304 |
|
|
| 305 |
|
sub parse_string ($$$;$) { |
| 306 |
|
my $self = shift->new; |
| 307 |
|
my $s = \$_[0]; |
| 308 |
|
$self->{document} = $_[1]; |
| 309 |
|
|
| 310 |
|
my $i; |
| 311 |
|
my $i = 0; |
| 312 |
|
$self->{set_next_input_character} = sub { |
| 313 |
|
my $self = shift; |
| 314 |
|
$self->{next_input_character} = -1 and return if $i >= length $$s; |
| 315 |
|
$self->{next_input_character} = ord substr $$s, $i++, 1; |
| 316 |
|
|
| 317 |
|
if ($self->{next_input_character} == 0x000D) { # CR |
| 318 |
|
if ($i >= length $$s) { |
| 319 |
|
# |
| 320 |
|
} else { |
| 321 |
|
my $next_char = ord substr $$s, $i++, 1; |
| 322 |
|
if ($next_char == 0x000A) { # LF |
| 323 |
|
# |
| 324 |
|
} else { |
| 325 |
|
push @{$self->{char}}, $next_char; |
| 326 |
|
} |
| 327 |
|
} |
| 328 |
|
$self->{next_input_character} = 0x000A; # LF # MUST |
| 329 |
|
} elsif ($self->{next_input_character} > 0x10FFFF) { |
| 330 |
|
$self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST |
| 331 |
|
} elsif ($self->{next_input_character} == 0x0000) { # NULL |
| 332 |
|
$self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST |
| 333 |
|
} |
| 334 |
|
}; |
| 335 |
|
|
| 336 |
|
$self->{parse_error} = $_[2] || sub { |
| 337 |
|
warn "Parse error at character $i\n"; ## TODO: Report (line, column) pair |
| 338 |
|
}; |
| 339 |
|
|
| 340 |
|
$self->_initialize_tokenizer; |
| 341 |
|
$self->_initialize_tree_constructor; |
| 342 |
|
$self->_construct_tree; |
| 343 |
|
$self->_terminate_tree_constructor; |
| 344 |
|
|
| 345 |
|
return $self->{document}; |
| 346 |
|
} # parse_string |
| 347 |
|
|
| 348 |
sub new ($) { |
sub new ($) { |
| 349 |
my $class = shift; |
my $class = shift; |
| 350 |
my $self = bless {}, $class; |
my $self = bless {}, $class; |
| 489 |
## reconsume |
## reconsume |
| 490 |
$self->{state} = 'data'; |
$self->{state} = 'data'; |
| 491 |
|
|
| 492 |
return (type => 'character', data => {'/'}); |
return ({type => 'character', data => '<'}); |
| 493 |
|
|
| 494 |
redo A; |
redo A; |
| 495 |
} |
} |
| 2124 |
|
|
| 2125 |
sub _initialize_tree_constructor ($) { |
sub _initialize_tree_constructor ($) { |
| 2126 |
my $self = shift; |
my $self = shift; |
| 2127 |
require What::NanoDOM; |
## NOTE: $self->{document} MUST be specified before this method is called |
|
$self->{document} = What::NanoDOM::Document->new; |
|
| 2128 |
$self->{document}->strict_error_checking (0); |
$self->{document}->strict_error_checking (0); |
| 2129 |
## TODO: Turn mutation events off # MUST |
## TODO: Turn mutation events off # MUST |
| 2130 |
## TODO: Turn loose Document option (manakai extension) on |
## TODO: Turn loose Document option (manakai extension) on |
| 2131 |
|
## TODO: Mark the Document as an HTML document # MUST |
| 2132 |
} # _initialize_tree_constructor |
} # _initialize_tree_constructor |
| 2133 |
|
|
| 2134 |
sub _terminate_tree_constructor ($) { |
sub _terminate_tree_constructor ($) { |
| 2162 |
my $insertion_mode = 'before head'; |
my $insertion_mode = 'before head'; |
| 2163 |
|
|
| 2164 |
my $reconstruct_active_formatting_elements = sub { # MUST |
my $reconstruct_active_formatting_elements = sub { # MUST |
| 2165 |
|
my $insert = shift; |
| 2166 |
|
|
| 2167 |
## Step 1 |
## Step 1 |
| 2168 |
return unless @$active_formatting_elements; |
return unless @$active_formatting_elements; |
| 2169 |
|
|
| 2179 |
} |
} |
| 2180 |
} |
} |
| 2181 |
|
|
|
## Step 4 |
|
| 2182 |
S4: { |
S4: { |
| 2183 |
|
## Step 4 |
| 2184 |
last S4 if $active_formatting_elements->[0]->[0] eq $entry->[0]; |
last S4 if $active_formatting_elements->[0]->[0] eq $entry->[0]; |
| 2185 |
|
|
| 2186 |
## Step 5 |
## Step 5 |
| 2194 |
my $in_open_elements; |
my $in_open_elements; |
| 2195 |
OE: for (@$open_elements) { |
OE: for (@$open_elements) { |
| 2196 |
if ($entry->[0] eq $_->[0]) { |
if ($entry->[0] eq $_->[0]) { |
| 2197 |
$in_open_elements = 1; |
$in_open_elements = 1; |
| 2198 |
last OE; |
last OE; |
| 2199 |
} |
} |
| 2200 |
} |
} |
| 2201 |
if ($in_open_elements) { |
if ($in_open_elements) { |
| 2202 |
# |
# |
| 2212 |
|
|
| 2213 |
S7: { |
S7: { |
| 2214 |
## Step 8 |
## Step 8 |
| 2215 |
my $clone = $entry->[0]->clone_node (0); |
my $clone = [$entry->[0]->clone_node (0), $entry->[1]]; |
| 2216 |
|
|
| 2217 |
## Step 9 |
## Step 9 |
| 2218 |
$open_elements->[-1]->[0]->append_child ($clone); |
$insert->($clone->[0]); |
| 2219 |
push @$open_elements, [$clone, $entry->[1]]; |
push @$open_elements, $clone; |
| 2220 |
|
|
| 2221 |
## Step 10 |
## Step 10 |
| 2222 |
$active_formatting_elements->[$i] = $open_elements->[-1]; |
$active_formatting_elements->[$i] = $open_elements->[-1]; |
| 2223 |
|
|
| 2224 |
unless ($i == $#$active_formatting_elements) { |
## Step 11 |
| 2225 |
|
unless ($clone->[0] eq $active_formatting_elements->[-1]->[0]) { |
| 2226 |
## Step 7' |
## Step 7' |
| 2227 |
$i++; |
$i++; |
| 2228 |
$entry = $active_formatting_elements->[$i]; |
$entry = $active_formatting_elements->[$i]; |
| 2327 |
}; # $style_start_tag |
}; # $style_start_tag |
| 2328 |
|
|
| 2329 |
my $script_start_tag = sub { |
my $script_start_tag = sub { |
| 2330 |
my $script_el; |
my $script_el; |
| 2331 |
|
|
| 2332 |
$script_el = $self->{document}->create_element_ns |
$script_el = $self->{document}->create_element_ns |
| 2333 |
(q<http://www.w3.org/1999/xhtml>, [undef, 'script']); |
(q<http://www.w3.org/1999/xhtml>, [undef, 'script']); |
| 2334 |
|
|
| 2335 |
|
for my $attr_name (keys %{ $token->{attributes}}) { |
| 2336 |
|
$script_el->set_attribute_ns (undef, [undef, $attr_name], |
| 2337 |
|
$token->{attributes} ->{$attr_name}->{value}); |
| 2338 |
|
} |
| 2339 |
|
|
| 2340 |
## TODO: mark as "parser-inserted" |
## TODO: mark as "parser-inserted" |
| 2341 |
|
|
| 2342 |
$self->{content_model_flag} = 'CDATA'; |
$self->{content_model_flag} = 'CDATA'; |
| 2352 |
} |
} |
| 2353 |
|
|
| 2354 |
$self->{content_model_flag} = 'PCDATA'; |
$self->{content_model_flag} = 'PCDATA'; |
| 2355 |
|
|
| 2356 |
if ($token->{type} eq 'end tag' and |
if ($token->{type} eq 'end tag' and |
| 2357 |
$token->{tag_name} eq 'script') { |
$token->{tag_name} eq 'script') { |
| 2358 |
## Ignore the token |
## Ignore the token |
| 2561 |
} # FET |
} # FET |
| 2562 |
}; # $formatting_end_tag |
}; # $formatting_end_tag |
| 2563 |
|
|
| 2564 |
|
my $insert_to_current = sub { |
| 2565 |
|
$open_elements->[-1]->[0]->append_child (shift); |
| 2566 |
|
}; # $insert_to_current |
| 2567 |
|
|
| 2568 |
|
my $insert_to_foster = sub { |
| 2569 |
|
my $child = shift; |
| 2570 |
|
if ({ |
| 2571 |
|
table => 1, tbody => 1, tfoot => 1, |
| 2572 |
|
thead => 1, tr => 1, |
| 2573 |
|
}->{$open_elements->[-1]->[1]}) { |
| 2574 |
|
# MUST |
| 2575 |
|
my $foster_parent_element; |
| 2576 |
|
my $next_sibling; |
| 2577 |
|
OE: for (reverse 0..$#$open_elements) { |
| 2578 |
|
if ($open_elements->[$_]->[1] eq 'table') { |
| 2579 |
|
my $parent = $open_elements->[$_]->[0]->parent_node; |
| 2580 |
|
if (defined $parent and $parent->node_type == 1) { |
| 2581 |
|
$foster_parent_element = $parent; |
| 2582 |
|
$next_sibling = $open_elements->[$_]->[0]; |
| 2583 |
|
} else { |
| 2584 |
|
$foster_parent_element |
| 2585 |
|
= $open_elements->[$_ - 1]->[0]; |
| 2586 |
|
} |
| 2587 |
|
last OE; |
| 2588 |
|
} |
| 2589 |
|
} # OE |
| 2590 |
|
$foster_parent_element = $open_elements->[0]->[0] |
| 2591 |
|
unless defined $foster_parent_element; |
| 2592 |
|
$foster_parent_element->insert_before |
| 2593 |
|
($child, $next_sibling); |
| 2594 |
|
} else { |
| 2595 |
|
$open_elements->[-1]->[0]->append_child ($child); |
| 2596 |
|
} |
| 2597 |
|
}; # $insert_to_foster |
| 2598 |
|
|
| 2599 |
my $in_body = sub { |
my $in_body = sub { |
| 2600 |
my $insert = shift; |
my $insert = shift; |
| 2601 |
if ($token->{type} eq 'start tag') { |
if ($token->{type} eq 'start tag') { |
| 2606 |
$style_start_tag->(); |
$style_start_tag->(); |
| 2607 |
return; |
return; |
| 2608 |
} elsif ({ |
} elsif ({ |
| 2609 |
base => 1, link => 1, meta => 1, title => 1, |
base => 1, link => 1, meta => 1, |
| 2610 |
}->{$token->{tag_name}}) { |
}->{$token->{tag_name}}) { |
| 2611 |
$self->{parse_error}->(); |
$self->{parse_error}-> ($token->{tag_name}.' in body'); |
| 2612 |
## NOTE: This is an "as if in head" code clone |
## NOTE: This is an "as if in head" code clone |
| 2613 |
my $el; |
my $el; |
| 2614 |
|
|
| 2626 |
$insert->($el); |
$insert->($el); |
| 2627 |
} |
} |
| 2628 |
|
|
| 2629 |
## ISSUE: Issue on magical <base> in the spec |
$token = $self->_get_next_token; |
| 2630 |
|
return; |
| 2631 |
|
} elsif ($token->{tag_name} eq 'title') { |
| 2632 |
|
$self->{parse_error}-> ('title in body'); |
| 2633 |
|
## NOTE: There is an "as if in head" code clone |
| 2634 |
|
my $title_el; |
| 2635 |
|
|
| 2636 |
|
$title_el = $self->{document}->create_element_ns |
| 2637 |
|
(q<http://www.w3.org/1999/xhtml>, [undef, 'title']); |
| 2638 |
|
|
| 2639 |
|
for my $attr_name (keys %{ $token->{attributes}}) { |
| 2640 |
|
$title_el->set_attribute_ns (undef, [undef, $attr_name], |
| 2641 |
|
$token->{attributes} ->{$attr_name}->{value}); |
| 2642 |
|
} |
| 2643 |
|
|
| 2644 |
|
(defined $head_element ? $head_element : $open_elements->[-1]->[0]) |
| 2645 |
|
->append_child ($title_el); |
| 2646 |
|
$self->{content_model_flag} = 'RCDATA'; |
| 2647 |
|
|
| 2648 |
|
my $text = ''; |
| 2649 |
|
$token = $self->_get_next_token; |
| 2650 |
|
while ($token->{type} eq 'character') { |
| 2651 |
|
$text .= $token->{data}; |
| 2652 |
|
$token = $self->_get_next_token; |
| 2653 |
|
} |
| 2654 |
|
if (length $text) { |
| 2655 |
|
$title_el->manakai_append_text ($text); |
| 2656 |
|
} |
| 2657 |
|
|
| 2658 |
|
$self->{content_model_flag} = 'PCDATA'; |
| 2659 |
|
|
| 2660 |
|
if ($token->{type} eq 'end tag' and |
| 2661 |
|
$token->{tag_name} eq 'title') { |
| 2662 |
|
## Ignore the token |
| 2663 |
|
} else { |
| 2664 |
|
$self->{parse_error}->(); |
| 2665 |
|
## ISSUE: And ignore? |
| 2666 |
|
} |
| 2667 |
$token = $self->_get_next_token; |
$token = $self->_get_next_token; |
| 2668 |
return; |
return; |
| 2669 |
} elsif ($token->{tag_name} eq 'body') { |
} elsif ($token->{tag_name} eq 'body') { |
| 2806 |
} |
} |
| 2807 |
|
|
| 2808 |
## Step 4 |
## Step 4 |
| 2809 |
$i++; |
$i--; |
| 2810 |
$node = $open_elements->[$i]; |
$node = $open_elements->[$i]; |
| 2811 |
redo LI; |
redo LI; |
| 2812 |
} # LI |
} # LI |
| 2864 |
} |
} |
| 2865 |
|
|
| 2866 |
## Step 4 |
## Step 4 |
| 2867 |
$i++; |
$i--; |
| 2868 |
$node = $open_elements->[$i]; |
$node = $open_elements->[$i]; |
| 2869 |
redo LI; |
redo LI; |
| 2870 |
} # LI |
} # LI |
| 2986 |
AFE: for my $i (reverse 0..$#$active_formatting_elements) { |
AFE: for my $i (reverse 0..$#$active_formatting_elements) { |
| 2987 |
my $node = $active_formatting_elements->[$i]; |
my $node = $active_formatting_elements->[$i]; |
| 2988 |
if ($node->[1] eq 'a') { |
if ($node->[1] eq 'a') { |
| 2989 |
$self->{parse_error}->(); |
$self->{parse_error}-> ('a in a'); |
| 2990 |
|
|
| 2991 |
unshift @{$self->{token}}, $token; |
unshift @{$self->{token}}, $token; |
| 2992 |
$token = {type => 'end tag', tag_name => 'a'}; |
$token = {type => 'end tag', tag_name => 'a'}; |
| 2993 |
$formatting_end_tag->($token->{tag_name}); |
$formatting_end_tag->($token->{tag_name}); |
| 2994 |
|
|
| 2995 |
splice @$active_formatting_elements, $i; |
AFE2: for (reverse 0..$#$active_formatting_elements) { |
| 2996 |
|
if ($active_formatting_elements->[$_]->[0] eq $node->[0]) { |
| 2997 |
|
splice @$active_formatting_elements, $_, 1; |
| 2998 |
|
last AFE2; |
| 2999 |
|
} |
| 3000 |
|
} # AFE2 |
| 3001 |
OE: for (reverse 0..$#$open_elements) { |
OE: for (reverse 0..$#$open_elements) { |
| 3002 |
if ($open_elements->[$_]->[0] eq $node->[0]) { |
if ($open_elements->[$_]->[0] eq $node->[0]) { |
| 3003 |
splice @$open_elements, $_; |
splice @$open_elements, $_, 1; |
| 3004 |
last OE; |
last OE; |
| 3005 |
} |
} |
| 3006 |
} # OE |
} # OE |
| 3010 |
} |
} |
| 3011 |
} # AFE |
} # AFE |
| 3012 |
|
|
| 3013 |
$reconstruct_active_formatting_elements->(); |
$reconstruct_active_formatting_elements->($insert_to_current); |
| 3014 |
|
|
| 3015 |
|
|
| 3016 |
{ |
{ |
| 3037 |
nobr => 1, s => 1, small => 1, strile => 1, |
nobr => 1, s => 1, small => 1, strile => 1, |
| 3038 |
strong => 1, tt => 1, u => 1, |
strong => 1, tt => 1, u => 1, |
| 3039 |
}->{$token->{tag_name}}) { |
}->{$token->{tag_name}}) { |
| 3040 |
$reconstruct_active_formatting_elements->(); |
$reconstruct_active_formatting_elements->($insert_to_current); |
| 3041 |
|
|
| 3042 |
|
|
| 3043 |
{ |
{ |
| 3076 |
} |
} |
| 3077 |
} # INSCOPE |
} # INSCOPE |
| 3078 |
|
|
| 3079 |
$reconstruct_active_formatting_elements->(); |
$reconstruct_active_formatting_elements->($insert_to_current); |
| 3080 |
|
|
| 3081 |
|
|
| 3082 |
{ |
{ |
| 3100 |
return; |
return; |
| 3101 |
} elsif ($token->{tag_name} eq 'marquee' or |
} elsif ($token->{tag_name} eq 'marquee' or |
| 3102 |
$token->{tag_name} eq 'object') { |
$token->{tag_name} eq 'object') { |
| 3103 |
$reconstruct_active_formatting_elements->(); |
$reconstruct_active_formatting_elements->($insert_to_current); |
| 3104 |
|
|
| 3105 |
|
|
| 3106 |
{ |
{ |
| 3123 |
$token = $self->_get_next_token; |
$token = $self->_get_next_token; |
| 3124 |
return; |
return; |
| 3125 |
} elsif ($token->{tag_name} eq 'xmp') { |
} elsif ($token->{tag_name} eq 'xmp') { |
| 3126 |
$reconstruct_active_formatting_elements->(); |
$reconstruct_active_formatting_elements->($insert_to_current); |
| 3127 |
|
|
| 3128 |
|
|
| 3129 |
{ |
{ |
| 3192 |
$token->{tag_name} = 'img'; |
$token->{tag_name} = 'img'; |
| 3193 |
} |
} |
| 3194 |
|
|
| 3195 |
$reconstruct_active_formatting_elements->(); |
$reconstruct_active_formatting_elements->($insert_to_current); |
| 3196 |
|
|
| 3197 |
|
|
| 3198 |
{ |
{ |
| 3250 |
$token = $self->_get_next_token; |
$token = $self->_get_next_token; |
| 3251 |
return; |
return; |
| 3252 |
} elsif ($token->{tag_name} eq 'input') { |
} elsif ($token->{tag_name} eq 'input') { |
| 3253 |
$reconstruct_active_formatting_elements->(); |
$reconstruct_active_formatting_elements->($insert_to_current); |
| 3254 |
|
|
| 3255 |
|
|
| 3256 |
{ |
{ |
| 3289 |
{type => 'start tag', tag_name => 'p'}, |
{type => 'start tag', tag_name => 'p'}, |
| 3290 |
{type => 'start tag', tag_name => 'label'}, |
{type => 'start tag', tag_name => 'label'}, |
| 3291 |
{type => 'character', |
{type => 'character', |
| 3292 |
data => 'This is a searchable index. Insert your search keywords here: '}, # SHOULD |
data => 'This is a searchable index. Insert your search keywords here: '}, # SHOULD |
| 3293 |
## TODO: make this configurable |
## TODO: make this configurable |
| 3294 |
{type => 'start tag', tag_name => 'input', attributes => $at}, |
{type => 'start tag', tag_name => 'input', attributes => $at}, |
| 3295 |
#{type => 'character', data => ''}, # SHOULD |
#{type => 'character', data => ''}, # SHOULD |
| 3350 |
} |
} |
| 3351 |
$token = $self->_get_next_token; |
$token = $self->_get_next_token; |
| 3352 |
return; |
return; |
| 3353 |
} elsif ($token->{type} eq 'select') { |
} elsif ($token->{tag_name} eq 'select') { |
| 3354 |
$reconstruct_active_formatting_elements->(); |
$reconstruct_active_formatting_elements->($insert_to_current); |
| 3355 |
|
|
| 3356 |
|
|
| 3357 |
{ |
{ |
| 3386 |
|
|
| 3387 |
## ISSUE: An issue on HTML5 new elements in the spec. |
## ISSUE: An issue on HTML5 new elements in the spec. |
| 3388 |
} else { |
} else { |
| 3389 |
$reconstruct_active_formatting_elements->(); |
$reconstruct_active_formatting_elements->($insert_to_current); |
| 3390 |
|
|
| 3391 |
|
|
| 3392 |
{ |
{ |
| 3702 |
my $top_el = $open_elements->[0]->[0]; |
my $top_el = $open_elements->[0]->[0]; |
| 3703 |
for my $attr_name (keys %{$token->{attributes}}) { |
for my $attr_name (keys %{$token->{attributes}}) { |
| 3704 |
unless ($top_el->has_attribute_ns (undef, $attr_name)) { |
unless ($top_el->has_attribute_ns (undef, $attr_name)) { |
| 3705 |
$top_el->set_attribute_ns (undef, [undef, $attr_name], |
$top_el->set_attribute_ns |
| 3706 |
$token->{attributes}->{value}); |
(undef, [undef, $attr_name], |
| 3707 |
|
$token->{attributes}->{$attr_name}->{value}); |
| 3708 |
} |
} |
| 3709 |
} |
} |
| 3710 |
$token = $self->_get_next_token; |
$token = $self->_get_next_token; |
| 3819 |
redo B; |
redo B; |
| 3820 |
} elsif ($token->{type} eq 'start tag') { |
} elsif ($token->{type} eq 'start tag') { |
| 3821 |
if ($token->{tag_name} eq 'title') { |
if ($token->{tag_name} eq 'title') { |
| 3822 |
my $title_el; |
## NOTE: There is an "as if in head" code clone |
| 3823 |
|
my $title_el; |
| 3824 |
|
|
| 3825 |
$title_el = $self->{document}->create_element_ns |
$title_el = $self->{document}->create_element_ns |
| 3826 |
(q<http://www.w3.org/1999/xhtml>, [undef, 'title']); |
(q<http://www.w3.org/1999/xhtml>, [undef, 'title']); |
| 3827 |
|
|
| 3828 |
|
for my $attr_name (keys %{ $token->{attributes}}) { |
| 3829 |
|
$title_el->set_attribute_ns (undef, [undef, $attr_name], |
| 3830 |
|
$token->{attributes} ->{$attr_name}->{value}); |
| 3831 |
|
} |
| 3832 |
|
|
| 3833 |
(defined $head_element ? $head_element : $open_elements->[-1]->[0]) |
(defined $head_element ? $head_element : $open_elements->[-1]->[0]) |
| 3834 |
->append_child ($title_el); |
->append_child ($title_el); |
| 3835 |
$self->{content_model_flag} = 'RCDATA'; |
$self->{content_model_flag} = 'RCDATA'; |
| 3836 |
|
|
| 3837 |
my $text = ''; |
my $text = ''; |
| 3838 |
$token = $self->_get_next_token; |
$token = $self->_get_next_token; |
| 3839 |
while ($token->{type} eq 'character') { |
while ($token->{type} eq 'character') { |
| 3875 |
|
|
| 3876 |
(defined $head_element ? $head_element : $open_elements->[-1]->[0]) |
(defined $head_element ? $head_element : $open_elements->[-1]->[0]) |
| 3877 |
->append_child ($el); |
->append_child ($el); |
|
|
|
|
## ISSUE: Issue on magical <base> in the spec |
|
| 3878 |
|
|
| 3879 |
$token = $self->_get_next_token; |
$token = $self->_get_next_token; |
| 3880 |
redo B; |
redo B; |
| 4007 |
} elsif ($insertion_mode eq 'in body') { |
} elsif ($insertion_mode eq 'in body') { |
| 4008 |
if ($token->{type} eq 'character') { |
if ($token->{type} eq 'character') { |
| 4009 |
## NOTE: There is a code clone of "character in body". |
## NOTE: There is a code clone of "character in body". |
| 4010 |
$reconstruct_active_formatting_elements->(); |
$reconstruct_active_formatting_elements->($insert_to_current); |
| 4011 |
|
|
| 4012 |
$open_elements->[-1]->[0]->manakai_append_text ($token->{data}); |
$open_elements->[-1]->[0]->manakai_append_text ($token->{data}); |
| 4013 |
|
|
| 4020 |
$token = $self->_get_next_token; |
$token = $self->_get_next_token; |
| 4021 |
redo B; |
redo B; |
| 4022 |
} else { |
} else { |
| 4023 |
$in_body->(sub { |
$in_body->($insert_to_current); |
|
$open_elements->[-1]->[0]->append_child (shift); |
|
|
}); |
|
| 4024 |
redo B; |
redo B; |
| 4025 |
} |
} |
| 4026 |
} elsif ($insertion_mode eq 'in table') { |
} elsif ($insertion_mode eq 'in table') { |
| 4027 |
if ($token->{type} eq 'character') { |
if ($token->{type} eq 'character') { |
| 4028 |
$reconstruct_active_formatting_elements->(); |
## NOTE: There are "character in table" code clones. |
| 4029 |
|
if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) { |
| 4030 |
$open_elements->[-1]->[0]->manakai_append_text ($token->{data}); |
$open_elements->[-1]->[0]->manakai_append_text ($1); |
| 4031 |
|
|
| 4032 |
|
unless (length $token->{data}) { |
| 4033 |
|
$token = $self->_get_next_token; |
| 4034 |
|
redo B; |
| 4035 |
|
} |
| 4036 |
|
} |
| 4037 |
|
|
| 4038 |
|
## As if in body, but insert into foster parent element |
| 4039 |
|
## ISSUE: Spec says that "whenever a node would be inserted |
| 4040 |
|
## into the current node" while characters might not be |
| 4041 |
|
## result in a new Text node. |
| 4042 |
|
$reconstruct_active_formatting_elements->($insert_to_foster); |
| 4043 |
|
|
| 4044 |
|
if ({ |
| 4045 |
|
table => 1, tbody => 1, tfoot => 1, |
| 4046 |
|
thead => 1, tr => 1, |
| 4047 |
|
}->{$open_elements->[-1]->[1]}) { |
| 4048 |
|
# MUST |
| 4049 |
|
my $foster_parent_element; |
| 4050 |
|
my $next_sibling; |
| 4051 |
|
my $prev_sibling; |
| 4052 |
|
OE: for (reverse 0..$#$open_elements) { |
| 4053 |
|
if ($open_elements->[$_]->[1] eq 'table') { |
| 4054 |
|
my $parent = $open_elements->[$_]->[0]->parent_node; |
| 4055 |
|
if (defined $parent and $parent->node_type == 1) { |
| 4056 |
|
$foster_parent_element = $parent; |
| 4057 |
|
$next_sibling = $open_elements->[$_]->[0]; |
| 4058 |
|
$prev_sibling = $next_sibling->previous_sibling; |
| 4059 |
|
} else { |
| 4060 |
|
$foster_parent_element = $open_elements->[$_ - 1]->[0]; |
| 4061 |
|
$prev_sibling = $foster_parent_element->last_child; |
| 4062 |
|
} |
| 4063 |
|
last OE; |
| 4064 |
|
} |
| 4065 |
|
} # OE |
| 4066 |
|
$foster_parent_element = $open_elements->[0]->[0] and |
| 4067 |
|
$prev_sibling = $foster_parent_element->last_child |
| 4068 |
|
unless defined $foster_parent_element; |
| 4069 |
|
if (defined $prev_sibling and |
| 4070 |
|
$prev_sibling->node_type == 3) { |
| 4071 |
|
$prev_sibling->manakai_append_text ($token->{data}); |
| 4072 |
|
} else { |
| 4073 |
|
$foster_parent_element->insert_before |
| 4074 |
|
($self->{document}->create_text_node ($token->{data}), |
| 4075 |
|
$next_sibling); |
| 4076 |
|
} |
| 4077 |
|
} else { |
| 4078 |
|
$open_elements->[-1]->[0]->manakai_append_text ($token->{data}); |
| 4079 |
|
} |
| 4080 |
|
|
| 4081 |
$token = $self->_get_next_token; |
$token = $self->_get_next_token; |
| 4082 |
redo B; |
redo B; |
| 4083 |
} elsif ($token->{type} eq 'comment') { |
} elsif ($token->{type} eq 'comment') { |
| 4262 |
# |
# |
| 4263 |
} |
} |
| 4264 |
|
|
|
## NOTE: There are code clones of "misc in table". |
|
| 4265 |
$self->{parse_error}->(); |
$self->{parse_error}->(); |
| 4266 |
$in_body->(sub { |
$in_body->($insert_to_foster); |
|
my $child = shift; |
|
|
if ({ |
|
|
table => 1, tbody => 1, tfoot => 1, |
|
|
thead => 1, tr => 1, |
|
|
}->{$open_elements->[-1]->[1]}) { |
|
|
# MUST |
|
|
my $foster_parent_element; |
|
|
my $next_sibling; |
|
|
OE: for (reverse 0..$#$open_elements) { |
|
|
if ($open_elements->[$_]->[1] eq 'table') { |
|
|
my $parent = $open_elements->[$_]->[0]->parent_node; |
|
|
if (defined $parent and $parent->node_type == 1) { |
|
|
$foster_parent_element = $parent; |
|
|
$next_sibling = $open_elements->[$_]->[0]; |
|
|
} else { |
|
|
$foster_parent_element |
|
|
= $open_elements->[$_ - 1]->[0]; |
|
|
} |
|
|
last OE; |
|
|
} |
|
|
} # OE |
|
|
$foster_parent_element = $open_elements->[0]->[0] |
|
|
unless defined $foster_parent_element; |
|
|
$foster_parent_element->insert_before |
|
|
($child, $next_sibling); |
|
|
} else { |
|
|
$open_elements->[-1]->[0]->append_child ($child); |
|
|
} |
|
|
}); |
|
| 4267 |
redo B; |
redo B; |
| 4268 |
} elsif ($insertion_mode eq 'in caption') { |
} elsif ($insertion_mode eq 'in caption') { |
| 4269 |
if ($token->{type} eq 'character') { |
if ($token->{type} eq 'character') { |
| 4270 |
## NOTE: This is a code clone of "character in body". |
## NOTE: This is a code clone of "character in body". |
| 4271 |
$reconstruct_active_formatting_elements->(); |
$reconstruct_active_formatting_elements->($insert_to_current); |
| 4272 |
|
|
| 4273 |
$open_elements->[-1]->[0]->manakai_append_text ($token->{data}); |
$open_elements->[-1]->[0]->manakai_append_text ($token->{data}); |
| 4274 |
|
|
| 4445 |
# |
# |
| 4446 |
} |
} |
| 4447 |
|
|
| 4448 |
$in_body->(sub { |
$in_body->($insert_to_current); |
|
$open_elements->[-1]->[0]->append_child (shift); |
|
|
}); |
|
| 4449 |
redo B; |
redo B; |
| 4450 |
} elsif ($insertion_mode eq 'in column group') { |
} elsif ($insertion_mode eq 'in column group') { |
| 4451 |
if ($token->{type} eq 'character') { |
if ($token->{type} eq 'character') { |
| 4526 |
} |
} |
| 4527 |
} elsif ($insertion_mode eq 'in table body') { |
} elsif ($insertion_mode eq 'in table body') { |
| 4528 |
if ($token->{type} eq 'character') { |
if ($token->{type} eq 'character') { |
| 4529 |
## Copied from 'in table' |
## NOTE: This is a "character in table" code clone. |
| 4530 |
$reconstruct_active_formatting_elements->(); |
if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) { |
| 4531 |
|
$open_elements->[-1]->[0]->manakai_append_text ($1); |
| 4532 |
|
|
| 4533 |
|
unless (length $token->{data}) { |
| 4534 |
|
$token = $self->_get_next_token; |
| 4535 |
|
redo B; |
| 4536 |
|
} |
| 4537 |
|
} |
| 4538 |
|
|
| 4539 |
$open_elements->[-1]->[0]->manakai_append_text ($token->{data}); |
## As if in body, but insert into foster parent element |
| 4540 |
|
## ISSUE: Spec says that "whenever a node would be inserted |
| 4541 |
|
## into the current node" while characters might not be |
| 4542 |
|
## result in a new Text node. |
| 4543 |
|
$reconstruct_active_formatting_elements->($insert_to_foster); |
| 4544 |
|
|
| 4545 |
|
if ({ |
| 4546 |
|
table => 1, tbody => 1, tfoot => 1, |
| 4547 |
|
thead => 1, tr => 1, |
| 4548 |
|
}->{$open_elements->[-1]->[1]}) { |
| 4549 |
|
# MUST |
| 4550 |
|
my $foster_parent_element; |
| 4551 |
|
my $next_sibling; |
| 4552 |
|
my $prev_sibling; |
| 4553 |
|
OE: for (reverse 0..$#$open_elements) { |
| 4554 |
|
if ($open_elements->[$_]->[1] eq 'table') { |
| 4555 |
|
my $parent = $open_elements->[$_]->[0]->parent_node; |
| 4556 |
|
if (defined $parent and $parent->node_type == 1) { |
| 4557 |
|
$foster_parent_element = $parent; |
| 4558 |
|
$next_sibling = $open_elements->[$_]->[0]; |
| 4559 |
|
$prev_sibling = $next_sibling->previous_sibling; |
| 4560 |
|
} else { |
| 4561 |
|
$foster_parent_element = $open_elements->[$_ - 1]->[0]; |
| 4562 |
|
$prev_sibling = $foster_parent_element->last_child; |
| 4563 |
|
} |
| 4564 |
|
last OE; |
| 4565 |
|
} |
| 4566 |
|
} # OE |
| 4567 |
|
$foster_parent_element = $open_elements->[0]->[0] and |
| 4568 |
|
$prev_sibling = $foster_parent_element->last_child |
| 4569 |
|
unless defined $foster_parent_element; |
| 4570 |
|
if (defined $prev_sibling and |
| 4571 |
|
$prev_sibling->node_type == 3) { |
| 4572 |
|
$prev_sibling->manakai_append_text ($token->{data}); |
| 4573 |
|
} else { |
| 4574 |
|
$foster_parent_element->insert_before |
| 4575 |
|
($self->{document}->create_text_node ($token->{data}), |
| 4576 |
|
$next_sibling); |
| 4577 |
|
} |
| 4578 |
|
} else { |
| 4579 |
|
$open_elements->[-1]->[0]->manakai_append_text ($token->{data}); |
| 4580 |
|
} |
| 4581 |
|
|
| 4582 |
$token = $self->_get_next_token; |
$token = $self->_get_next_token; |
| 4583 |
redo B; |
redo B; |
| 4584 |
} elsif ($token->{type} eq 'comment') { |
} elsif ($token->{type} eq 'comment') { |
| 4824 |
} |
} |
| 4825 |
|
|
| 4826 |
## As if in table |
## As if in table |
|
## NOTE: This is a code clone of "misc in table". |
|
| 4827 |
$self->{parse_error}->(); |
$self->{parse_error}->(); |
| 4828 |
$in_body->(sub { |
$in_body->($insert_to_foster); |
|
my $child = shift; |
|
|
if ({ |
|
|
table => 1, tbody => 1, tfoot => 1, |
|
|
thead => 1, tr => 1, |
|
|
}->{$open_elements->[-1]->[1]}) { |
|
|
# MUST |
|
|
my $foster_parent_element; |
|
|
my $next_sibling; |
|
|
OE: for (reverse 0..$#$open_elements) { |
|
|
if ($open_elements->[$_]->[1] eq 'table') { |
|
|
my $parent = $open_elements->[$_]->[0]->parent_node; |
|
|
if (defined $parent and $parent->node_type == 1) { |
|
|
$foster_parent_element = $parent; |
|
|
$next_sibling = $open_elements->[$_]->[0]; |
|
|
} else { |
|
|
$foster_parent_element |
|
|
= $open_elements->[$_ - 1]->[0]; |
|
|
} |
|
|
last OE; |
|
|
} |
|
|
} # OE |
|
|
$foster_parent_element = $open_elements->[0]->[0] |
|
|
unless defined $foster_parent_element; |
|
|
$foster_parent_element->insert_before |
|
|
($child, $next_sibling); |
|
|
} else { |
|
|
$open_elements->[-1]->[0]->append_child ($child); |
|
|
} |
|
|
}); |
|
| 4829 |
redo B; |
redo B; |
| 4830 |
} elsif ($insertion_mode eq 'in row') { |
} elsif ($insertion_mode eq 'in row') { |
| 4831 |
if ($token->{type} eq 'character') { |
if ($token->{type} eq 'character') { |
| 4832 |
## Copied from 'in table' |
## NOTE: This is a "character in table" code clone. |
| 4833 |
$reconstruct_active_formatting_elements->(); |
if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) { |
| 4834 |
|
$open_elements->[-1]->[0]->manakai_append_text ($1); |
| 4835 |
$open_elements->[-1]->[0]->manakai_append_text ($token->{data}); |
|
| 4836 |
|
unless (length $token->{data}) { |
| 4837 |
|
$token = $self->_get_next_token; |
| 4838 |
|
redo B; |
| 4839 |
|
} |
| 4840 |
|
} |
| 4841 |
|
|
| 4842 |
|
## As if in body, but insert into foster parent element |
| 4843 |
|
## ISSUE: Spec says that "whenever a node would be inserted |
| 4844 |
|
## into the current node" while characters might not be |
| 4845 |
|
## result in a new Text node. |
| 4846 |
|
$reconstruct_active_formatting_elements->($insert_to_foster); |
| 4847 |
|
|
| 4848 |
|
if ({ |
| 4849 |
|
table => 1, tbody => 1, tfoot => 1, |
| 4850 |
|
thead => 1, tr => 1, |
| 4851 |
|
}->{$open_elements->[-1]->[1]}) { |
| 4852 |
|
# MUST |
| 4853 |
|
my $foster_parent_element; |
| 4854 |
|
my $next_sibling; |
| 4855 |
|
my $prev_sibling; |
| 4856 |
|
OE: for (reverse 0..$#$open_elements) { |
| 4857 |
|
if ($open_elements->[$_]->[1] eq 'table') { |
| 4858 |
|
my $parent = $open_elements->[$_]->[0]->parent_node; |
| 4859 |
|
if (defined $parent and $parent->node_type == 1) { |
| 4860 |
|
$foster_parent_element = $parent; |
| 4861 |
|
$next_sibling = $open_elements->[$_]->[0]; |
| 4862 |
|
$prev_sibling = $next_sibling->previous_sibling; |
| 4863 |
|
} else { |
| 4864 |
|
$foster_parent_element = $open_elements->[$_ - 1]->[0]; |
| 4865 |
|
$prev_sibling = $foster_parent_element->last_child; |
| 4866 |
|
} |
| 4867 |
|
last OE; |
| 4868 |
|
} |
| 4869 |
|
} # OE |
| 4870 |
|
$foster_parent_element = $open_elements->[0]->[0] and |
| 4871 |
|
$prev_sibling = $foster_parent_element->last_child |
| 4872 |
|
unless defined $foster_parent_element; |
| 4873 |
|
if (defined $prev_sibling and |
| 4874 |
|
$prev_sibling->node_type == 3) { |
| 4875 |
|
$prev_sibling->manakai_append_text ($token->{data}); |
| 4876 |
|
} else { |
| 4877 |
|
$foster_parent_element->insert_before |
| 4878 |
|
($self->{document}->create_text_node ($token->{data}), |
| 4879 |
|
$next_sibling); |
| 4880 |
|
} |
| 4881 |
|
} else { |
| 4882 |
|
$open_elements->[-1]->[0]->manakai_append_text ($token->{data}); |
| 4883 |
|
} |
| 4884 |
|
|
| 4885 |
$token = $self->_get_next_token; |
$token = $self->_get_next_token; |
| 4886 |
redo B; |
redo B; |
| 4887 |
} elsif ($token->{type} eq 'comment') { |
} elsif ($token->{type} eq 'comment') { |
| 5151 |
} |
} |
| 5152 |
|
|
| 5153 |
## As if in table |
## As if in table |
|
## NOTE: This is a code clone of "misc in table". |
|
| 5154 |
$self->{parse_error}->(); |
$self->{parse_error}->(); |
| 5155 |
$in_body->(sub { |
$in_body->($insert_to_foster); |
|
my $child = shift; |
|
|
if ({ |
|
|
table => 1, tbody => 1, tfoot => 1, |
|
|
thead => 1, tr => 1, |
|
|
}->{$open_elements->[-1]->[1]}) { |
|
|
# MUST |
|
|
my $foster_parent_element; |
|
|
my $next_sibling; |
|
|
OE: for (reverse 0..$#$open_elements) { |
|
|
if ($open_elements->[$_]->[1] eq 'table') { |
|
|
my $parent = $open_elements->[$_]->[0]->parent_node; |
|
|
if (defined $parent and $parent->node_type == 1) { |
|
|
$foster_parent_element = $parent; |
|
|
$next_sibling = $open_elements->[$_]->[0]; |
|
|
} else { |
|
|
$foster_parent_element |
|
|
= $open_elements->[$_ - 1]->[0]; |
|
|
} |
|
|
last OE; |
|
|
} |
|
|
} # OE |
|
|
$foster_parent_element = $open_elements->[0]->[0] |
|
|
unless defined $foster_parent_element; |
|
|
$foster_parent_element->insert_before |
|
|
($child, $next_sibling); |
|
|
} else { |
|
|
$open_elements->[-1]->[0]->append_child ($child); |
|
|
} |
|
|
}); |
|
| 5156 |
redo B; |
redo B; |
| 5157 |
} elsif ($insertion_mode eq 'in cell') { |
} elsif ($insertion_mode eq 'in cell') { |
| 5158 |
if ($token->{type} eq 'character') { |
if ($token->{type} eq 'character') { |
| 5159 |
## NOTE: This is a code clone of "character in body". |
## NOTE: This is a code clone of "character in body". |
| 5160 |
$reconstruct_active_formatting_elements->(); |
$reconstruct_active_formatting_elements->($insert_to_current); |
| 5161 |
|
|
| 5162 |
$open_elements->[-1]->[0]->manakai_append_text ($token->{data}); |
$open_elements->[-1]->[0]->manakai_append_text ($token->{data}); |
| 5163 |
|
|
| 5297 |
# |
# |
| 5298 |
} |
} |
| 5299 |
|
|
| 5300 |
$in_body->(sub { |
$in_body->($insert_to_current); |
|
$open_elements->[-1]->[0]->append_child (shift); |
|
|
}); |
|
| 5301 |
redo B; |
redo B; |
| 5302 |
} elsif ($insertion_mode eq 'in select') { |
} elsif ($insertion_mode eq 'in select') { |
| 5303 |
if ($token->{type} eq 'character') { |
if ($token->{type} eq 'character') { |
| 5506 |
|
|
| 5507 |
$self->{parse_error}->(); |
$self->{parse_error}->(); |
| 5508 |
## Ignore the token |
## Ignore the token |
| 5509 |
|
$token = $self->_get_next_token; |
| 5510 |
redo B; |
redo B; |
| 5511 |
} elsif ($insertion_mode eq 'after body') { |
} elsif ($insertion_mode eq 'after body') { |
| 5512 |
if ($token->{type} eq 'character') { |
if ($token->{type} eq 'character') { |
| 5513 |
if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) { |
if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) { |
| 5514 |
## As if in body |
## As if in body |
| 5515 |
$reconstruct_active_formatting_elements->(); |
$reconstruct_active_formatting_elements->($insert_to_current); |
| 5516 |
|
|
| 5517 |
$open_elements->[-1]->[0]->manakai_append_text ($token->{data}); |
$open_elements->[-1]->[0]->manakai_append_text ($token->{data}); |
| 5518 |
|
|
| 5542 |
# |
# |
| 5543 |
} |
} |
| 5544 |
|
|
| 5545 |
$self->{parse_error}->(); |
$self->{parse_error}-> ('data after body'); |
| 5546 |
$insertion_mode = 'in body'; |
$insertion_mode = 'in body'; |
| 5547 |
## reprocess |
## reprocess |
| 5548 |
redo B; |
redo B; |
| 5604 |
$token = $self->_get_next_token; |
$token = $self->_get_next_token; |
| 5605 |
redo B; |
redo B; |
| 5606 |
} elsif ($token->{tag_name} eq 'noframes') { |
} elsif ($token->{tag_name} eq 'noframes') { |
| 5607 |
$in_body->(sub { |
$in_body->($insert_to_current); |
|
$open_elements->[-1]->[0]->append_child (shift); |
|
|
}); |
|
| 5608 |
redo B; |
redo B; |
| 5609 |
} else { |
} else { |
| 5610 |
# |
# |
| 5656 |
redo B; |
redo B; |
| 5657 |
} elsif ($token->{type} eq 'start tag') { |
} elsif ($token->{type} eq 'start tag') { |
| 5658 |
if ($token->{tag_name} eq 'noframes') { |
if ($token->{tag_name} eq 'noframes') { |
| 5659 |
$in_body->(sub { |
$in_body->($insert_to_current); |
|
$open_elements->[-1]->[0]->append_child (shift); |
|
|
}); |
|
| 5660 |
redo B; |
redo B; |
| 5661 |
} else { |
} else { |
| 5662 |
# |
# |
| 5698 |
redo B; |
redo B; |
| 5699 |
} elsif ($token->{type} eq 'character') { |
} elsif ($token->{type} eq 'character') { |
| 5700 |
if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) { |
if ($token->{data} =~ s/^([\x09\x0A\x0B\x0C\x20]+)//) { |
| 5701 |
|
my $data = $1; |
| 5702 |
## As if in the main phase. |
## As if in the main phase. |
| 5703 |
## NOTE: The insertion mode in the main phase |
## NOTE: The insertion mode in the main phase |
| 5704 |
## just before the phase has been changed to the trailing |
## just before the phase has been changed to the trailing |
| 5705 |
## end phase is either "after body" or "after frameset". |
## end phase is either "after body" or "after frameset". |
| 5706 |
$reconstruct_active_formatting_elements->() |
$reconstruct_active_formatting_elements->($insert_to_current) |
| 5707 |
if $phase eq 'main'; |
if $phase eq 'main'; |
| 5708 |
|
|
| 5709 |
$open_elements->[-1]->[0]->manakai_append_text ($token->{data}); |
$open_elements->[-1]->[0]->manakai_append_text ($data); |
| 5710 |
|
|
| 5711 |
unless (length $token->{data}) { |
unless (length $token->{data}) { |
| 5712 |
$token = $self->_get_next_token; |
$token = $self->_get_next_token; |
| 5738 |
## TODO: script stuffs |
## TODO: script stuffs |
| 5739 |
} # _construct_tree |
} # _construct_tree |
| 5740 |
|
|
| 5741 |
sub inner_html ($$$) { |
sub get_inner_html ($$$) { |
| 5742 |
my ($class, $node, $on_error) = @_; |
my ($class, $node, $on_error) = @_; |
| 5743 |
|
|
| 5744 |
## Step 1 |
## Step 1 |
| 5826 |
} elsif ($nt == 5) { # entrefs |
} elsif ($nt == 5) { # entrefs |
| 5827 |
push @node, @{$child->child_nodes}; |
push @node, @{$child->child_nodes}; |
| 5828 |
} else { |
} else { |
| 5829 |
$on_error->($child); |
$on_error->($child) if defined $on_error; |
| 5830 |
} |
} |
| 5831 |
|
## ISSUE: This code does not support PIs. |
| 5832 |
} # C |
} # C |
| 5833 |
|
|
| 5834 |
## Step 3 |
## Step 3 |
| 5835 |
return \$s; |
return \$s; |
| 5836 |
} # inner_html |
} # get_inner_html |
| 5837 |
|
|
| 5838 |
1; |
1; |
| 5839 |
# $Date$ |
# $Date$ |