/[suikacvs]/markup/html/whatpm/Whatpm/HTML.pm.src
Suika

Diff of /markup/html/whatpm/Whatpm/HTML.pm.src

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.3 by wakaba, Wed May 2 13:44:34 2007 UTC revision 1.42 by wakaba, Sat Jul 21 06:59:16 2007 UTC
# Line 2  package Whatpm::HTML; Line 2  package Whatpm::HTML;
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 an early version of an HTML parser.  ## ISSUE:
6    ## var doc = implementation.createDocument (null, null, null);
7    ## doc.write ('');
8    ## alert (doc.compatMode);
9    
10    ## ISSUE: HTML5 revision 967 says that the encoding layer MUST NOT
11    ## strip BOM and the HTML layer MUST ignore it.  Whether we can do it
12    ## is not yet clear.
13    ## "{U+FEFF}..." in UTF-16BE/UTF-16LE is three or four characters?
14    ## "{U+FEFF}..." in GB18030?
15    
16  my $permitted_slash_tag_name = {  my $permitted_slash_tag_name = {
17    base => 1,    base => 1,
# Line 18  my $permitted_slash_tag_name = { Line 27  my $permitted_slash_tag_name = {
27    input => 1,    input => 1,
28  };  };
29    
30  my $entity_char = {  my $c1_entity_char = {
31    AElig => "\x{00C6}",    0x80 => 0x20AC,
32    Aacute => "\x{00C1}",    0x81 => 0xFFFD,
33    Acirc => "\x{00C2}",    0x82 => 0x201A,
34    Agrave => "\x{00C0}",    0x83 => 0x0192,
35    Alpha => "\x{0391}",    0x84 => 0x201E,
36    Aring => "\x{00C5}",    0x85 => 0x2026,
37    Atilde => "\x{00C3}",    0x86 => 0x2020,
38    Auml => "\x{00C4}",    0x87 => 0x2021,
39    Beta => "\x{0392}",    0x88 => 0x02C6,
40    Ccedil => "\x{00C7}",    0x89 => 0x2030,
41    Chi => "\x{03A7}",    0x8A => 0x0160,
42    Dagger => "\x{2021}",    0x8B => 0x2039,
43    Delta => "\x{0394}",    0x8C => 0x0152,
44    ETH => "\x{00D0}",    0x8D => 0xFFFD,
45    Eacute => "\x{00C9}",    0x8E => 0x017D,
46    Ecirc => "\x{00CA}",    0x8F => 0xFFFD,
47    Egrave => "\x{00C8}",    0x90 => 0xFFFD,
48    Epsilon => "\x{0395}",    0x91 => 0x2018,
49    Eta => "\x{0397}",    0x92 => 0x2019,
50    Euml => "\x{00CB}",    0x93 => 0x201C,
51    Gamma => "\x{0393}",    0x94 => 0x201D,
52    Iacute => "\x{00CD}",    0x95 => 0x2022,
53    Icirc => "\x{00CE}",    0x96 => 0x2013,
54    Igrave => "\x{00CC}",    0x97 => 0x2014,
55    Iota => "\x{0399}",    0x98 => 0x02DC,
56    Iuml => "\x{00CF}",    0x99 => 0x2122,
57    Kappa => "\x{039A}",    0x9A => 0x0161,
58    Lambda => "\x{039B}",    0x9B => 0x203A,
59    Mu => "\x{039C}",    0x9C => 0x0153,
60    Ntilde => "\x{00D1}",    0x9D => 0xFFFD,
61    Nu => "\x{039D}",    0x9E => 0x017E,
62    OElig => "\x{0152}",    0x9F => 0x0178,
63    Oacute => "\x{00D3}",  }; # $c1_entity_char
   Ocirc => "\x{00D4}",  
   Ograve => "\x{00D2}",  
   Omega => "\x{03A9}",  
   Omicron => "\x{039F}",  
   Oslash => "\x{00D8}",  
   Otilde => "\x{00D5}",  
   Ouml => "\x{00D6}",  
   Phi => "\x{03A6}",  
   Pi => "\x{03A0}",  
   Prime => "\x{2033}",  
   Psi => "\x{03A8}",  
   Rho => "\x{03A1}",  
   Scaron => "\x{0160}",  
   Sigma => "\x{03A3}",  
   THORN => "\x{00DE}",  
   Tau => "\x{03A4}",  
   Theta => "\x{0398}",  
   Uacute => "\x{00DA}",  
   Ucirc => "\x{00DB}",  
   Ugrave => "\x{00D9}",  
   Upsilon => "\x{03A5}",  
   Uuml => "\x{00DC}",  
   Xi => "\x{039E}",  
   Yacute => "\x{00DD}",  
   Yuml => "\x{0178}",  
   Zeta => "\x{0396}",  
   aacute => "\x{00E1}",  
   acirc => "\x{00E2}",  
   acute => "\x{00B4}",  
   aelig => "\x{00E6}",  
   agrave => "\x{00E0}",  
   alefsym => "\x{2135}",  
   alpha => "\x{03B1}",  
   amp => "\x{0026}",  
   AMP => "\x{0026}",  
   and => "\x{2227}",  
   ang => "\x{2220}",  
   apos => "\x{0027}",  
   aring => "\x{00E5}",  
   asymp => "\x{2248}",  
   atilde => "\x{00E3}",  
   auml => "\x{00E4}",  
   bdquo => "\x{201E}",  
   beta => "\x{03B2}",  
   brvbar => "\x{00A6}",  
   bull => "\x{2022}",  
   cap => "\x{2229}",  
   ccedil => "\x{00E7}",  
   cedil => "\x{00B8}",  
   cent => "\x{00A2}",  
   chi => "\x{03C7}",  
   circ => "\x{02C6}",  
   clubs => "\x{2663}",  
   cong => "\x{2245}",  
   copy => "\x{00A9}",  
   COPY => "\x{00A9}",  
   crarr => "\x{21B5}",  
   cup => "\x{222A}",  
   curren => "\x{00A4}",  
   dArr => "\x{21D3}",  
   dagger => "\x{2020}",  
   darr => "\x{2193}",  
   deg => "\x{00B0}",  
   delta => "\x{03B4}",  
   diams => "\x{2666}",  
   divide => "\x{00F7}",  
   eacute => "\x{00E9}",  
   ecirc => "\x{00EA}",  
   egrave => "\x{00E8}",  
   empty => "\x{2205}",  
   emsp => "\x{2003}",  
   ensp => "\x{2002}",  
   epsilon => "\x{03B5}",  
   equiv => "\x{2261}",  
   eta => "\x{03B7}",  
   eth => "\x{00F0}",  
   euml => "\x{00EB}",  
   euro => "\x{20AC}",  
   exist => "\x{2203}",  
   fnof => "\x{0192}",  
   forall => "\x{2200}",  
   frac12 => "\x{00BD}",  
   frac14 => "\x{00BC}",  
   frac34 => "\x{00BE}",  
   frasl => "\x{2044}",  
   gamma => "\x{03B3}",  
   ge => "\x{2265}",  
   gt => "\x{003E}",  
   GT => "\x{003E}",  
   hArr => "\x{21D4}",  
   harr => "\x{2194}",  
   hearts => "\x{2665}",  
   hellip => "\x{2026}",  
   iacute => "\x{00ED}",  
   icirc => "\x{00EE}",  
   iexcl => "\x{00A1}",  
   igrave => "\x{00EC}",  
   image => "\x{2111}",  
   infin => "\x{221E}",  
   int => "\x{222B}",  
   iota => "\x{03B9}",  
   iquest => "\x{00BF}",  
   isin => "\x{2208}",  
   iuml => "\x{00EF}",  
   kappa => "\x{03BA}",  
   lArr => "\x{21D0}",  
   lambda => "\x{03BB}",  
   lang => "\x{2329}",  
   laquo => "\x{00AB}",  
   larr => "\x{2190}",  
   lceil => "\x{2308}",  
   ldquo => "\x{201C}",  
   le => "\x{2264}",  
   lfloor => "\x{230A}",  
   lowast => "\x{2217}",  
   loz => "\x{25CA}",  
   lrm => "\x{200E}",  
   lsaquo => "\x{2039}",  
   lsquo => "\x{2018}",  
   lt => "\x{003C}",  
   LT => "\x{003C}",  
   macr => "\x{00AF}",  
   mdash => "\x{2014}",  
   micro => "\x{00B5}",  
   middot => "\x{00B7}",  
   minus => "\x{2212}",  
   mu => "\x{03BC}",  
   nabla => "\x{2207}",  
   nbsp => "\x{00A0}",  
   ndash => "\x{2013}",  
   ne => "\x{2260}",  
   ni => "\x{220B}",  
   not => "\x{00AC}",  
   notin => "\x{2209}",  
   nsub => "\x{2284}",  
   ntilde => "\x{00F1}",  
   nu => "\x{03BD}",  
   oacute => "\x{00F3}",  
   ocirc => "\x{00F4}",  
   oelig => "\x{0153}",  
   ograve => "\x{00F2}",  
   oline => "\x{203E}",  
   omega => "\x{03C9}",  
   omicron => "\x{03BF}",  
   oplus => "\x{2295}",  
   or => "\x{2228}",  
   ordf => "\x{00AA}",  
   ordm => "\x{00BA}",  
   oslash => "\x{00F8}",  
   otilde => "\x{00F5}",  
   otimes => "\x{2297}",  
   ouml => "\x{00F6}",  
   para => "\x{00B6}",  
   part => "\x{2202}",  
   permil => "\x{2030}",  
   perp => "\x{22A5}",  
   phi => "\x{03C6}",  
   pi => "\x{03C0}",  
   piv => "\x{03D6}",  
   plusmn => "\x{00B1}",  
   pound => "\x{00A3}",  
   prime => "\x{2032}",  
   prod => "\x{220F}",  
   prop => "\x{221D}",  
   psi => "\x{03C8}",  
   quot => "\x{0022}",  
   QUOT => "\x{0022}",  
   rArr => "\x{21D2}",  
   radic => "\x{221A}",  
   rang => "\x{232A}",  
   raquo => "\x{00BB}",  
   rarr => "\x{2192}",  
   rceil => "\x{2309}",  
   rdquo => "\x{201D}",  
   real => "\x{211C}",  
   reg => "\x{00AE}",  
   REG => "\x{00AE}",  
   rfloor => "\x{230B}",  
   rho => "\x{03C1}",  
   rlm => "\x{200F}",  
   rsaquo => "\x{203A}",  
   rsquo => "\x{2019}",  
   sbquo => "\x{201A}",  
   scaron => "\x{0161}",  
   sdot => "\x{22C5}",  
   sect => "\x{00A7}",  
   shy => "\x{00AD}",  
   sigma => "\x{03C3}",  
   sigmaf => "\x{03C2}",  
   sim => "\x{223C}",  
   spades => "\x{2660}",  
   sub => "\x{2282}",  
   sube => "\x{2286}",  
   sum => "\x{2211}",  
   sup => "\x{2283}",  
   sup1 => "\x{00B9}",  
   sup2 => "\x{00B2}",  
   sup3 => "\x{00B3}",  
   supe => "\x{2287}",  
   szlig => "\x{00DF}",  
   tau => "\x{03C4}",  
   there4 => "\x{2234}",  
   theta => "\x{03B8}",  
   thetasym => "\x{03D1}",  
   thinsp => "\x{2009}",  
   thorn => "\x{00FE}",  
   tilde => "\x{02DC}",  
   times => "\x{00D7}",  
   trade => "\x{2122}",  
   uArr => "\x{21D1}",  
   uacute => "\x{00FA}",  
   uarr => "\x{2191}",  
   ucirc => "\x{00FB}",  
   ugrave => "\x{00F9}",  
   uml => "\x{00A8}",  
   upsih => "\x{03D2}",  
   upsilon => "\x{03C5}",  
   uuml => "\x{00FC}",  
   weierp => "\x{2118}",  
   xi => "\x{03BE}",  
   yacute => "\x{00FD}",  
   yen => "\x{00A5}",  
   yuml => "\x{00FF}",  
   zeta => "\x{03B6}",  
   zwj => "\x{200D}",  
   zwnj => "\x{200C}",  
 };  
64    
65  my $special_category = {  my $special_category = {
66    address => 1, area => 1, base => 1, basefont => 1, bgsound => 1,    address => 1, area => 1, base => 1, basefont => 1, bgsound => 1,
# Line 314  sub parse_string ($$$;$) { Line 96  sub parse_string ($$$;$) {
96    my $column = 0;    my $column = 0;
97    $self->{set_next_input_character} = sub {    $self->{set_next_input_character} = sub {
98      my $self = shift;      my $self = shift;
99    
100        pop @{$self->{prev_input_character}};
101        unshift @{$self->{prev_input_character}}, $self->{next_input_character};
102    
103      $self->{next_input_character} = -1 and return if $i >= length $$s;      $self->{next_input_character} = -1 and return if $i >= length $$s;
104      $self->{next_input_character} = ord substr $$s, $i++, 1;      $self->{next_input_character} = ord substr $$s, $i++, 1;
105      $column++;      $column++;
106            
107      if ($self->{next_input_character} == 0x000D) { # CR      if ($self->{next_input_character} == 0x000A) { # LF
108        if ($i >= length $$s) {        $line++;
109          #        $column = 0;
110        } else {      } elsif ($self->{next_input_character} == 0x000D) { # CR
111          my $next_char = ord substr $$s, $i++, 1;        $i++ if substr ($$s, $i, 1) eq "\x0A";
         if ($next_char == 0x000A) { # LF  
           #  
         } else {  
           push @{$self->{char}}, $next_char;  
         }  
       }  
112        $self->{next_input_character} = 0x000A; # LF # MUST        $self->{next_input_character} = 0x000A; # LF # MUST
113        $line++;        $line++;
114        $column = -1;        $column = 0;
115      } elsif ($self->{next_input_character} > 0x10FFFF) {      } elsif ($self->{next_input_character} > 0x10FFFF) {
116        $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST        $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
117      } elsif ($self->{next_input_character} == 0x0000) { # NULL      } elsif ($self->{next_input_character} == 0x0000) { # NULL
118          !!!parse-error (type => 'NULL');
119        $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST        $self->{next_input_character} = 0xFFFD; # REPLACEMENT CHARACTER # MUST
120      }      }
121    };    };
122      $self->{prev_input_character} = [-1, -1, -1];
123      $self->{next_input_character} = -1;
124    
125    my $onerror = $_[2] || sub {    my $onerror = $_[2] || sub {
126      my (%opt) = @_;      my (%opt) = @_;
# Line 367  sub new ($) { Line 150  sub new ($) {
150    return $self;    return $self;
151  } # new  } # new
152    
153    sub CM_ENTITY () { 0b001 } # & markup in data
154    sub CM_LIMITED_MARKUP () { 0b010 } # < markup in data (limited)
155    sub CM_FULL_MARKUP () { 0b100 } # < markup in data (any)
156    
157    sub PLAINTEXT_CONTENT_MODEL () { 0 }
158    sub CDATA_CONTENT_MODEL () { CM_LIMITED_MARKUP }
159    sub RCDATA_CONTENT_MODEL () { CM_ENTITY | CM_LIMITED_MARKUP }
160    sub PCDATA_CONTENT_MODEL () { CM_ENTITY | CM_FULL_MARKUP }
161    
162  ## Implementations MUST act as if state machine in the spec  ## Implementations MUST act as if state machine in the spec
163    
164  sub _initialize_tokenizer ($) {  sub _initialize_tokenizer ($) {
165    my $self = shift;    my $self = shift;
166    $self->{state} = 'data'; # MUST    $self->{state} = 'data'; # MUST
167    $self->{content_model_flag} = 'PCDATA'; # be    $self->{content_model} = PCDATA_CONTENT_MODEL; # be
168    undef $self->{current_token}; # start tag, end tag, comment, or DOCTYPE    undef $self->{current_token}; # start tag, end tag, comment, or DOCTYPE
169    undef $self->{current_attribute};    undef $self->{current_attribute};
170    undef $self->{last_emitted_start_tag_name};    undef $self->{last_emitted_start_tag_name};
# Line 381  sub _initialize_tokenizer ($) { Line 173  sub _initialize_tokenizer ($) {
173    # $self->{next_input_character}    # $self->{next_input_character}
174    !!!next-input-character;    !!!next-input-character;
175    $self->{token} = [];    $self->{token} = [];
176      # $self->{escape}
177  } # _initialize_tokenizer  } # _initialize_tokenizer
178    
179  ## A token has:  ## A token has:
180  ##   ->{type} eq 'DOCTYPE', 'start tag', 'end tag', 'comment',  ##   ->{type} eq 'DOCTYPE', 'start tag', 'end tag', 'comment',
181  ##       'character', or 'end-of-file'  ##       'character', or 'end-of-file'
182  ##   ->{name} (DOCTYPE, start tag (tagname), end tag (tagname))  ##   ->{name} (DOCTYPE, start tag (tag name), end tag (tag name))
183      ## ISSUE: the spec need s/tagname/tag name/  ##   ->{public_identifier} (DOCTYPE)
184  ##   ->{error} == 1 or 0 (DOCTYPE)  ##   ->{system_identifier} (DOCTYPE)
185    ##   ->{correct} == 1 or 0 (DOCTYPE)
186  ##   ->{attributes} isa HASH (start tag, end tag)  ##   ->{attributes} isa HASH (start tag, end tag)
187  ##   ->{data} (comment, character)  ##   ->{data} (comment, character)
188    
 ## Macros  
 ##   Macros MUST be preceded by three EXCLAMATION MARKs.  
 ##   emit ($token)  
 ##     Emits the specified token.  
   
189  ## Emitted token MUST immediately be handled by the tree construction state.  ## Emitted token MUST immediately be handled by the tree construction state.
190    
191  ## Before each step, UA MAY check to see if either one of the scripts in  ## Before each step, UA MAY check to see if either one of the scripts in
# Line 414  sub _get_next_token ($) { Line 203  sub _get_next_token ($) {
203    A: {    A: {
204      if ($self->{state} eq 'data') {      if ($self->{state} eq 'data') {
205        if ($self->{next_input_character} == 0x0026) { # &        if ($self->{next_input_character} == 0x0026) { # &
206          if ($self->{content_model_flag} eq 'PCDATA' or          if ($self->{content_model} & CM_ENTITY) { # PCDATA | RCDATA
             $self->{content_model_flag} eq 'RCDATA') {  
207            $self->{state} = 'entity data';            $self->{state} = 'entity data';
208            !!!next-input-character;            !!!next-input-character;
209            redo A;            redo A;
210          } else {          } else {
211            #            #
212          }          }
213          } elsif ($self->{next_input_character} == 0x002D) { # -
214            if ($self->{content_model} & CM_LIMITED_MARKUP) { # RCDATA | CDATA
215              unless ($self->{escape}) {
216                if ($self->{prev_input_character}->[0] == 0x002D and # -
217                    $self->{prev_input_character}->[1] == 0x0021 and # !
218                    $self->{prev_input_character}->[2] == 0x003C) { # <
219                  $self->{escape} = 1;
220                }
221              }
222            }
223            
224            #
225        } elsif ($self->{next_input_character} == 0x003C) { # <        } elsif ($self->{next_input_character} == 0x003C) { # <
226          if ($self->{content_model_flag} ne 'PLAINTEXT') {          if ($self->{content_model} & CM_FULL_MARKUP or # PCDATA
227                (($self->{content_model} & CM_LIMITED_MARKUP) and # CDATA | RCDATA
228                 not $self->{escape})) {
229            $self->{state} = 'tag open';            $self->{state} = 'tag open';
230            !!!next-input-character;            !!!next-input-character;
231            redo A;            redo A;
232          } else {          } else {
233            #            #
234          }          }
235          } elsif ($self->{next_input_character} == 0x003E) { # >
236            if ($self->{escape} and
237                ($self->{content_model} & CM_LIMITED_MARKUP)) { # RCDATA | CDATA
238              if ($self->{prev_input_character}->[0] == 0x002D and # -
239                  $self->{prev_input_character}->[1] == 0x002D) { # -
240                delete $self->{escape};
241              }
242            }
243            
244            #
245        } elsif ($self->{next_input_character} == -1) {        } elsif ($self->{next_input_character} == -1) {
246          !!!emit ({type => 'end-of-file'});          !!!emit ({type => 'end-of-file'});
247          last A; ## TODO: ok?          last A; ## TODO: ok?
# Line 446  sub _get_next_token ($) { Line 258  sub _get_next_token ($) {
258      } elsif ($self->{state} eq 'entity data') {      } elsif ($self->{state} eq 'entity data') {
259        ## (cannot happen in CDATA state)        ## (cannot happen in CDATA state)
260                
261        my $token = $self->_tokenize_attempt_to_consume_an_entity;        my $token = $self->_tokenize_attempt_to_consume_an_entity (0);
262    
263        $self->{state} = 'data';        $self->{state} = 'data';
264        # next-input-character is already done        # next-input-character is already done
# Line 459  sub _get_next_token ($) { Line 271  sub _get_next_token ($) {
271    
272        redo A;        redo A;
273      } elsif ($self->{state} eq 'tag open') {      } elsif ($self->{state} eq 'tag open') {
274        if ($self->{content_model_flag} eq 'RCDATA' or        if ($self->{content_model} & CM_LIMITED_MARKUP) { # RCDATA | CDATA
           $self->{content_model_flag} eq 'CDATA') {  
275          if ($self->{next_input_character} == 0x002F) { # /          if ($self->{next_input_character} == 0x002F) { # /
276            !!!next-input-character;            !!!next-input-character;
277            $self->{state} = 'close tag open';            $self->{state} = 'close tag open';
# Line 473  sub _get_next_token ($) { Line 284  sub _get_next_token ($) {
284    
285            redo A;            redo A;
286          }          }
287        } elsif ($self->{content_model_flag} eq 'PCDATA') {        } elsif ($self->{content_model} & CM_FULL_MARKUP) { # PCDATA
288          if ($self->{next_input_character} == 0x0021) { # !          if ($self->{next_input_character} == 0x0021) { # !
289            $self->{state} = 'markup declaration open';            $self->{state} = 'markup declaration open';
290            !!!next-input-character;            !!!next-input-character;
# Line 520  sub _get_next_token ($) { Line 331  sub _get_next_token ($) {
331            redo A;            redo A;
332          }          }
333        } else {        } else {
334          die "$0: $self->{content_model_flag}: Unknown content model flag";          die "$0: $self->{content_model} in tag open";
335        }        }
336      } elsif ($self->{state} eq 'close tag open') {      } elsif ($self->{state} eq 'close tag open') {
337        if ($self->{content_model_flag} eq 'RCDATA' or        if ($self->{content_model} & CM_LIMITED_MARKUP) { # RCDATA | CDATA
338            $self->{content_model_flag} eq 'CDATA') {          if (defined $self->{last_emitted_start_tag_name}) {
339          my @next_char;            ## NOTE: <http://krijnhoetmer.nl/irc-logs/whatwg/20070626#l-564>
340          TAGNAME: for (my $i = 0; $i < length $self->{last_emitted_start_tag_name}; $i++) {            my @next_char;
341              TAGNAME: for (my $i = 0; $i < length $self->{last_emitted_start_tag_name}; $i++) {
342                push @next_char, $self->{next_input_character};
343                my $c = ord substr ($self->{last_emitted_start_tag_name}, $i, 1);
344                my $C = 0x0061 <= $c && $c <= 0x007A ? $c - 0x0020 : $c;
345                if ($self->{next_input_character} == $c or $self->{next_input_character} == $C) {
346                  !!!next-input-character;
347                  next TAGNAME;
348                } else {
349                  $self->{next_input_character} = shift @next_char; # reconsume
350                  !!!back-next-input-character (@next_char);
351                  $self->{state} = 'data';
352    
353                  !!!emit ({type => 'character', data => '</'});
354      
355                  redo A;
356                }
357              }
358            push @next_char, $self->{next_input_character};            push @next_char, $self->{next_input_character};
359            my $c = ord substr ($self->{last_emitted_start_tag_name}, $i, 1);        
360            my $C = 0x0061 <= $c && $c <= 0x007A ? $c - 0x0020 : $c;            unless ($self->{next_input_character} == 0x0009 or # HT
361            if ($self->{next_input_character} == $c or $self->{next_input_character} == $C) {                    $self->{next_input_character} == 0x000A or # LF
362              !!!next-input-character;                    $self->{next_input_character} == 0x000B or # VT
363              next TAGNAME;                    $self->{next_input_character} == 0x000C or # FF
364            } else {                    $self->{next_input_character} == 0x0020 or # SP
365              !!!parse-error (type => 'unmatched end tag');                    $self->{next_input_character} == 0x003E or # >
366                      $self->{next_input_character} == 0x002F or # /
367                      $self->{next_input_character} == -1) {
368              $self->{next_input_character} = shift @next_char; # reconsume              $self->{next_input_character} = shift @next_char; # reconsume
369              !!!back-next-input-character (@next_char);              !!!back-next-input-character (@next_char);
370              $self->{state} = 'data';              $self->{state} = 'data';
   
371              !!!emit ({type => 'character', data => '</'});              !!!emit ({type => 'character', data => '</'});
   
372              redo A;              redo A;
373              } else {
374                $self->{next_input_character} = shift @next_char;
375                !!!back-next-input-character (@next_char);
376                # and consume...
377            }            }
378          }          } else {
379          push @next_char, $self->{next_input_character};            ## No start tag token has ever been emitted
380                  # next-input-character is already done
         unless ($self->{next_input_character} == 0x0009 or # HT  
                 $self->{next_input_character} == 0x000A or # LF  
                 $self->{next_input_character} == 0x000B or # VT  
                 $self->{next_input_character} == 0x000C or # FF  
                 $self->{next_input_character} == 0x0020 or # SP  
                 $self->{next_input_character} == 0x003E or # >  
                 $self->{next_input_character} == 0x002F or # /  
                 $self->{next_input_character} == 0x003C or # <  
                 $self->{next_input_character} == -1) {  
           !!!parse-error (type => 'unmatched end tag');  
           $self->{next_input_character} = shift @next_char; # reconsume  
           !!!back-next-input-character (@next_char);  
381            $self->{state} = 'data';            $self->{state} = 'data';
   
382            !!!emit ({type => 'character', data => '</'});            !!!emit ({type => 'character', data => '</'});
   
383            redo A;            redo A;
         } else {  
           $self->{next_input_character} = shift @next_char;  
           !!!back-next-input-character (@next_char);  
           # and consume...  
384          }          }
385        }        }
386                
# Line 614  sub _get_next_token ($) { Line 428  sub _get_next_token ($) {
428          redo A;          redo A;
429        } elsif ($self->{next_input_character} == 0x003E) { # >        } elsif ($self->{next_input_character} == 0x003E) { # >
430          if ($self->{current_token}->{type} eq 'start tag') {          if ($self->{current_token}->{type} eq 'start tag') {
431              $self->{current_token}->{first_start_tag}
432                  = not defined $self->{last_emitted_start_tag_name};
433            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
434          } elsif ($self->{current_token}->{type} eq 'end tag') {          } elsif ($self->{current_token}->{type} eq 'end tag') {
435            $self->{content_model_flag} = 'PCDATA'; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
436            if ($self->{current_token}->{attributes}) {            if ($self->{current_token}->{attributes}) {
437              !!!parse-error (type => 'end tag attribute');              !!!parse-error (type => 'end tag attribute');
438            }            }
# Line 627  sub _get_next_token ($) { Line 443  sub _get_next_token ($) {
443          !!!next-input-character;          !!!next-input-character;
444    
445          !!!emit ($self->{current_token}); # start tag or end tag          !!!emit ($self->{current_token}); # start tag or end tag
         undef $self->{current_token};  
446    
447          redo A;          redo A;
448        } elsif (0x0041 <= $self->{next_input_character} and        } elsif (0x0041 <= $self->{next_input_character} and
# Line 637  sub _get_next_token ($) { Line 452  sub _get_next_token ($) {
452          ## Stay in this state          ## Stay in this state
453          !!!next-input-character;          !!!next-input-character;
454          redo A;          redo A;
455        } elsif ($self->{next_input_character} == 0x003C or # <        } elsif ($self->{next_input_character} == -1) {
                $self->{next_input_character} == -1) {  
456          !!!parse-error (type => 'unclosed tag');          !!!parse-error (type => 'unclosed tag');
457          if ($self->{current_token}->{type} eq 'start tag') {          if ($self->{current_token}->{type} eq 'start tag') {
458              $self->{current_token}->{first_start_tag}
459                  = not defined $self->{last_emitted_start_tag_name};
460            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};            $self->{last_emitted_start_tag_name} = $self->{current_token}->{tag_name};
461          } elsif ($self->{current_token}->{type} eq 'end tag') {          } elsif ($self->{current_token}->{type} eq 'end tag') {
462            $self->{content_model_flag} = 'PCDATA'; # MUST            $self->{content_model} = PCDATA_CONTENT_MODEL; # MUST
463            if ($self->{current_token}-