/[suikacvs]/messaging/manakai/lib/Message/Markup/XML/Parser.pm
Suika

Contents of /messaging/manakai/lib/Message/Markup/XML/Parser.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.1 - (hide annotations) (download)
Sat May 24 04:52:19 2003 UTC (23 years, 2 months ago) by wakaba
Branch: MAIN
Parser: New

1 wakaba 1.1
2     =head1 NAME
3    
4     SuikaWiki::Markup::XML::Parser --- SuikaWiki: Simple XML parser
5    
6     =head1 DESCRIPTION
7    
8     This is a simple XML parser intended to be used with SuikaWiki::Markup::XML.
9     After parsing of the XML document, this module returns a SuikaWiki::Markup::XML
10     object so that you can handle XML document with that module (and other modules
11     implementing same interface).
12    
13     This module is part of SuikaWiki.
14    
15     =cut
16    
17     package SuikaWiki::Markup::XML::Parser;
18     use strict;
19     our $VERSION = do{my @r=(q$Revision: 1.2 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
20     use Char::Class::XML qw!InXML_NameStartChar InXMLNameChar InXML_NCNameStartChar InXMLNCNameChar!;
21     use SuikaWiki::Markup::XML;
22    
23     my %NS = (
24     SGML => 'urn:x-suika-fam-cx:markup:sgml:',
25     internal_ns_invalid => 'http://suika.fam.cx/~wakaba/-temp/2003/05/17/unknown-namespace#',
26     internal_attr_duplicate => 'http://suika.fam.cx/~wakaba/-temp/2003/05/17/invalid-attr#',
27     );
28    
29     =head1 METHODS
30    
31     WARNING: This module is under construction. Interface of this module is not yet fixed.
32    
33     =cut
34    
35     my %xml_re;
36     # [1] document = prolog element *Misc
37     # [2] Char = %x09 / %x0A / %x0D / U+0020-U+D7FF / U+E000-U+FFFD / U+10000-U+10FFFF ;; 1.0
38     # [2] Char = %x09 / %x0A / %x0D / %x20-7E / U+0085 / U+00A0-U+D7FF / U+E000-U+FFFD
39     # / U+10000-U+10FFFF ;; 1.1
40     # [3] s = 1*(%x20 / %x09 / %x0D / %x0A)
41     $xml_re{s} = qr/[\x09\x0D\x0A\x20]+/s;
42     # [4] NameChar = Letter / Digit / "." / "-" / "_" / ":" / CombiningChar / Extender ;; 1.0
43     # [4] NameStartChar = ":" / ALPHA / "_" / U+00C0-U+02FF / U+0370-U+037D
44     # / U+037F-U+1FFF / U+200C-U+200D / U+2070-U+218F
45     # / U+2C00-U+2FEF / U+3001-U+D7FF / U+F900-U+EFFFF ;; 1.1
46     # [4a] NameChar = NameStartChar / "-" / "." / DIGIT / U+00B7 / U+0300-U+036F
47     # / U+203F-U+2040 ;; 1.1
48     # $xml_re{NameChar} = qr/[A-Za-z0-9._:-]|[^\x00-\x7F]/;
49     $xml_re{NameChar} = qr/\p{InXMLNameChar}/;
50     # [5] Name = (Letter / "_" / ":") *NameChar ;; 1.0
51     # [5] Name = NameStartChar *NameChar ;; 1.1
52     # $xml_re{Name} = qr/(?:[A-Z_:]|[^\x00-\x7F])(?:$xml_re{NameChar})*/;
53     $xml_re{Name} = qr/\p{InXML_NameStartChar}\p{InXMLNameChar}*/;
54     # [6] Names = Name *(S Name) ;; 1.0 FE & 1.0 FE-errata (2000-09-27) & 1.0 SE
55     # [6] Names = Name *(%x20 Name) ;; 1.0 FE-errata (2000-04-09) & 1.0 SE-errata
56     $xml_re{Names} = qr/$xml_re{Name}(?:$xml_re{s}$xml_re{Name})*/s;
57     # [7] Nmtoken = 1*NameChar
58     #$xml_re{Nmtoken} = qr/(?:$xml_re{NameChar})+/;
59     $xml_re{Nmtoken} = qr/\p{InXMLNameChar}+/;
60     # [8] Nmtokens = Nmtoken *(S Nmtoken) ;; 1.0 FE & 1.0 FE-errata (2000-09-27) & 1.0 SE
61     # [8] Nmtokens = Nmtoken *(%x20 Nmtoken) ;; 1.0 FE-errata (2000-04-09) & 1.0 SE-errata
62     $xml_re{Nmtokens} = qr/$xml_re{Nmtoken}(?:$xml_re{s}$xml_re{Nmtoken})*/s;
63     # [9] EntityValue = <"> *((Char - ("%" / "&" / <">)) / PEReference / Reference) <">
64     # / "'" *((Char - ("%" / "&" / "'")) / PEReference / Reference) "'"
65     # [10] AttValue = <"> *((Char - ("<" / "&" / <">)) / Reference) <">
66     # / "'" *((Char - ("<" / "&" / "'")) / Reference) "'"
67     $xml_re{__AttValue_simple} = qr/"[^"]*"|'[^']*'/s;
68     # [11] SystemLiteral = <"> *(Char - <">) <"> / "'" *(Char - "'") "'"
69     $xml_re{SystemLiteral} = qr/"[^"]*"|'[^']*'/;
70     # [12] PublicLiteral = <"> *PubidChar <"> / "'" *(PubidChar - "'") "'"
71     # [13] PubidChar = %x20 / %x0D / %x0A / ALPHA / DIGIT / "-" / "'" / "(" / ")"
72     # / "+" / "," / "." / "/" / ":" / "=" / "?" / ";" / "!" / "*"
73     # / "#" / "@" / "$" / "_" / "%"
74     $xml_re{PubidChar} = qr[[\x0D\x0A\x20!\x24#%'()+*,./0-9:;=?\x40A-Z_a-z-]];
75     $xml_re{__non_PubidChar} = qr[[^\x0D\x0A\x20!\x24#%'()+*,./0-9:;=?\x40A-Z_a-z-]];
76     $xml_re{__PubidChar2} = qr[[\x0D\x0A\x20!\x24#%()+*,./0-9:;=?\x40A-Z_a-z-]];
77     $xml_re{PublicLiteral} = qr/"(?:$xml_re{PubidChar})*"|'(?:$xml_re{PubidChar2})*'/;
78     # [14] CharData = *(Char - ("<" / "&")) - (*(Char - ("<" / "&")) "]]>" *(Char - ("<" / "&")))
79     $xml_re{CharData} = qr/(?:(?!\]\]>)[^<&])*/s;
80     $xml_re{__CharDataP} = qr/(?:(?!\]\]>)[^<&])+/s;
81     # [15] Comment = "<!--" *((Char - "-") / ("-" (Char - "-")))) "-->"
82     $xml_re{Comment_M} = qr/<!--((?:(?!--).)*)-->/s;
83     # [16] PI = "<?" PITarget [S (*Char - (*Char "?>" *Char))] "?>"
84     $xml_re{PI_M} = qr/<\?($xml_re{Name})(?:$xml_re{s}((?:(?!\?>).)*))?\?>/s;
85     # [17] PITarget = Name - "xml"
86     # [18] CDSect = CDStart CData CDEnd
87     # [19] CDStart = '<![CDATA['
88     # [20] CDATA = (*Char - (*Char "]]>" *Char))
89     # [21] CDEnd = "]]>"
90     $xml_re{CDSect_M} = qr/<!\[CDATA\[((?:(?!\]\]>).)*)\]\]>/s;
91     # [22] prolog = [XMLDecl] *Misc [doctypedecl *Misc]
92     # [23] XMLDecl = '<?xml' VersionInfo [EncodingDecl] [SDDecl] [S] "?>"
93     # [24] VersionInfo = S 'version' Eq ("'" VersionNum "'" / <"> VersionNum <">)
94     # [25] Eq = [S] "=" [S]
95     # [26] VersionNum = 1*(ALPHA / DIGIT / "_" / "." / ":" / "-") ;; 1.0 FE & 1.0 SE
96     # [26] VersionNum = "1.0" ;; 1.0 SE-errata
97     # [26] VersionNum = "1.1" ;; 1.1
98     # [27] Misc = Comment / PI / S
99     # [28] doctypedecl = '<!DOCTYPE' S Name [S ExternalID] [S]
100     # ["[" *(markupdecl / PEReference / S) "]" [S]] ">" ;; 1.0 FE
101     # [28] doctypedecl = '<!DOCTYPE' S Name [S ExternalID] [S] ["[" *(markupdecl / DeclSep) "]" [S]] ">"
102     # ;; 1.0 FE-errata & 1.0 SE
103     # [28] doctypedecl = '<!DOCTYPE' S Name [S ExternalID] [S] ["[" intSubset "]" [S]] ">"
104     # ;; 1.0 SE-errata
105     $xml_re{__doctypedecl_start_simple_M} = qr/(<!DOCTYPE$xml_re{s})($xml_re{Name})($xml_re{s}SYSTEM$xml_re{s}$xml_re{__AttValue_simple}|$xml_re{s}PUBLIC$xml_re{s}$xml_re{__AttValue_simple}$xml_re{s}$xml_re{__AttValue_simple})?/s;
106     # [28a] DeclSep = PEReference / S ;; 1.0 FE-errata & 1.0 SE
107     # [28b] intSubset = *(markupdecl / DeclSep) ;; 1.0 SE-errata
108     # [29] markupdecl = elementdecl / AttlistDecl / EntityDecl / NotationDecl / PI / Comment
109     # [30] extSubset = [TextDecl] extSubsetDecl
110     # [31] extSubsetDecl = *(markupdecl / conditionalSect / PEReference / S) ;; 1.0 FE
111     # [31] extSubsetDecl = *(markupdecl / conditionalSect / DeclSep) ;; 1.0 FE-errata & 1.0 SE
112     # [32] SDDecl = S 'standalone' Eq ("'" ('yes' / 'no') "'" / <"> ('yes' / 'no') <">)
113     # [33] LanguageID = Langcode *("-" Subcode) ;; 1.0 FE (removed by 1.0 SE)
114     # [34] Langcode = ISO639Cocde / IanaCode / UserCode ;; 1.0 FE (ditto)
115     # [35] ISO639Code = 2ALPHA ;; 1.0 FE (ditto)
116     # [36] IanaCode = "i-" 1*ALPHA ;; 1.0 FE (ditto)
117     # [37] UserCode = "x-" 1*ALPHA ;; 1.0 FE (ditto)
118     # [38] Subcode = 1*ALPHA ;; 1.0 FE (ditto)
119     # [39] element = EmptyElemTag / STag content ETag
120     # [40] STag = "<" Name *(S Attribute) [S] ">"
121     # [41] Attribute = Name Eq AttValue
122     $xml_re{Attribute} = qr/$xml_re{Name}(?:$xml_re{s})?=(?:$xml_re{s})?$xml_re{__AttValue_simple}/s;
123     $xml_re{Attribute_M} = qr/($xml_re{Name})(?:$xml_re{s})?=(?:$xml_re{s})?($xml_re{__AttValue_simple})/s;
124     $xml_re{STag} = qr/<$xml_re{Name}(?:$xml_re{s}$xml_re{Attribute})*(?:$xml_re{s})?>/s;
125     # [42] ETag = "</" Name [S] ">"
126     $xml_re{ETag_M} = qr!</($xml_re{Name})(?:$xml_re{s})?>!s;
127     # [43] content = *(element / CharData / Reference / CDSect / PI / Comment) ;; 1.0 FE
128     # [43] content = [CharData] *((element / Reference / CDSect / PI / Comment) [CharData])
129     # ;; 1.0 FE-errata & 1.0 SE
130     # [44] EmptyElemTag = "<" Name *(S Attribute) [S] "/>"
131     $xml_re{__STag_or_EmptyElemTag} = qr!<$xml_re{Name}(?:$xml_re{s}$xml_re{Attribute})*(?:$xml_re{s})?/?>!s;
132     $xml_re{__STag_or_EmptyElemTag_simple} = qr!<$xml_re{ame}(?:$xml_re{s}|$xml_re{Name}|$xml_re{__AttValue_simple}|=)*/?>!s;
133     # [45] elementdecl = '<!ELEMENT' S Name S contentspec [S] ">"
134     # [46] contentspec = 'EMPTY' / 'ANY' / Mixed / children
135     $xml_re{__contentspec_simple} = qr/(?:$xml_re{Name}|\#PCDATA|[()|,?*+]|$xml_re{s})/s;
136     # [47] children = (choice / seq) ["?" / "*" / "+"]
137     # [48] cp = (Name / choice / seq) ["?" / "*" / "+"]
138     # [49] choice = "(" [S] cp *([S] "|" [S] cp) [S] ")" ;; 1.0 FE
139     # [49] choice = "(" [S] cp 1*([S] "|" [S] cp) [S] ")" ;; 1.0 FE-errata & 1.0 SE
140     # [50] seq = "(" [S] cp *([S] "," [S] cp) [S] ")"
141     # [51] Mixed = "(" [S] '#PCDATA' *([S] "|" [S] Name) [S] ")*"
142     # / "(" [S] '#PCDATA' [S] ")"
143     #$xml_re{seq} = qr/\($xml_re{cp}(?:(?:$xml_re{s})?,(?:$xml_re{s})?$xml_re{cp})*(?:$xml_re{s})?\)/;
144     #$xml_re{cp} = qr/(?:$xml_re{Name}|$xml_re{choice}|$xml_re{seq})[?*+]/;
145     #$xml_re{choice} = qr/\($xml_re{cp}(?:(?:$xml_re{s})?\|(?:$xml_re{s})?$xml_re{cp})+(?:$xml_re{s})?\)/;
146     #$xml_re{children} = qr/(?:$xml_re{choice}|$xml_re{seq})[?*+]/;
147     #$xml_re{Mixed} = qr/(?:\((?:$xml_re{s})?\#PCDATA(?:$xml_re{s})?(?:(?:$xml_re{s})?|(?:$xml_re{s})?$xml_re{Name})*(?:$xml_re{s})?\)|\((?:$xml_re{s})?\#PCDATA(?:$xml_re{s})?\))/;
148     #$xml_re{contentspec} = qr/(?:EMPTY|ANY|$xml_re{Mixed}|$xml_re{children})/;
149     # [52] AttlistDecl = '<!ATTLIST' S Name *AttDef [S] ">"
150     # [53] AttDef = S Name S AttType S DefaultDecl
151     # [54] AttType = StringType / TokenizedType / EnumeratedType
152     # [55] StringType = 'CDATA'
153     # [56] TokenizedType = 'ID' / 'IDREF' / 'IDREFS' / 'ENTITY' / 'ENTITIES' / 'NMTOKEN' / 'NMTOKENS'
154     # [57] EnumeratedType = NotationType / Enumeration
155     # [58] NotationType = 'NOTATION' S "(" [S] Name *([S] "|" [S] Name) [S] ")"
156     # [59] Enumeration = "(" [S] Nmtoken *([S] "|" [S] Nmtoken) [S} ")"
157     # [60] DefaultDecl = '#REQUIRED' / '#IMPLIED' / ['#FIXED' S] AttValue
158     # [61] conditionalSect = includeSect / ignoreSect
159     # [62] includeSect = "<![" [S} 'INCLUDE' [S] "[" extSubsetDecl "]]>"
160     # [63] ignoreSect = "<![" [S] 'IGNORE' [S] "[" *ignoreSectContents "]]>"
161     # [64] ignoreSectContents = Ignore *("<![" ignoreSectContents "]]>" Ignore)
162     # [65] Ignore = *Char - (*Char ("<![" / "]]>") *Char)
163     # [66] CharRef = '&#' 1*DIGIT ";" / '&#x' 1*HEXDIGIT ";"
164     $xml_re{CharRef} = qr/&#[0-9]+;|&#x[0-9A-Fa-f]+;/;
165     # [67] Reference = EntityRef / CharRef
166     # [68] EntityRef = "&" Name ";"
167     $xml_re{EntityRef} = qr/&$xml_re{Name};/;
168     $xml_re{EntityRef_M} = qr/&($xml_re{Name});/;
169     $xml_re{Reference} = qr/$xml_re{EntityRef}|$xml_re{CharRef}/;
170     $xml_re{AttValue} = qr/"(?:$xml_re{Reference}|[^&<"])*"|'(?:$xml_re{Reference}|[^&<'])*'/s;
171     # [69] PEReference = "%" Name ";"
172     $xml_re{PEReference} = qr/%(?:$xml_re{Name});/;
173     $xml_re{PEReference_M} = qr/%($xml_re{Name});/;
174     $xml_re{__elementdecl_simple} = qr/<!ELEMENT(?:$xml_re{s}|$xml_re{PEReference}|$xml_re{Name}|\#PCDATA|[()|,?*+])+>/s;
175     $xml_re{__AttlistDecl_simple} = qr/<!ATTLIST(?:$xml_re{PEReference}|$xml_re{Name}|\#$xml_re{Name}|$xml_re{s}|$xml_re{__AttValue_simple})*>/s;
176     # [70] EntityDecl = GEDecl / PEDecl
177     $xml_re{__EntityDecl_simple} = qr/<!ENTITY$xml_re{s}(?:%$xml_re{s})?(?:$xml_re{__AttValue_simple}|$xml_re{Name}|$xml_re{PEReference}|$xml_re{s})*>/s;
178     # [71] GEDecl = '<!ENTITY' S Name S EntityDef [S] ">"
179     # [72] PEDecl = '<!ENTITY' S "%" S Name S PEDef [S] ">"
180     # [73] EntityDef = EntityValue / ExternalID [NDataDecl]
181     # [74] PEDef = EntityValue / ExternalID
182     # [75] ExternalID = 'SYSTEM' S SystemLiteral / 'PUBLIC' S PublicLiteral S SystemLiteral
183     # [76] NDataDecl = S 'NDATA' S Name
184     # [77] TextDecl = '?xml' [VersionInfo] EncodingDecl [S] "?>"
185     # [78] extParsedEnt = [TextDecl] content
186     # [79] extPE ;; 1.0 FE (removed by errata)
187     # [80] EncodingDecl = S 'encoding' Eq (<"> EncName <"> / "'" EncName "'")
188     # [81] EncName = ALPHA *(ALPHA / DIGIT / "." / "_" / "-")
189     # [82] NotationDecl = '<!NOTATION' S Name S (ExternalID / PublicID) [S] ">"
190     $xml_re{__NotationDecl_simple} = qr/<!NOTATION$xml_re{s}(?:$xml_re{__AttValue_simple}|$xml_re{Name}|$xml_re{PEReference}|$xml_re{s})*>/s;
191     # [83] PublicID = 'PUBLIC' S PubidLiteral
192     # [84] Letter = BaseChar / Ideographic
193     # [85] Basechar = ...
194     # [86] Ideographic = ...
195     # [87] CombiningChar = ...
196     # [88] Digit = ...
197     # [89] Extender = U+00B7 / U+02D0 / U+02D1 / U+0387 / U+0640 / U+0E46 / U+0EC6
198     # / U+3005 / U+3031-U+3035 / U+309D / U+309E / U+30FC-U+30FE
199     ## [84]-[89] removed by 1.1
200    
201     # [XMLNames]
202     # [1] NSAttName = PrefixedAttName / DefaultAttName
203     # [2] PrefixedAttName = 'xmlns:' NCName
204     # [3] DefaultAttName = 'xmlns'
205     # [4] NCName = (Letter / "_") *NCNameChar ;; 1.0
206     # [4] NCName = NCNameStartChar *NCNameChar ;; 1.1
207     # [5] NCNameChar = Letter / Digit / "." / "-" / "_" / CombiningChar / Extender ;; 1.0
208     # [5] NCNameChar = NameChar - ":" ;; 1.1
209     # [5a] NCNameStartChar = NameStartChar - ":" ;; 1.1
210     # [6] QName = [Prefix ":"] LocalPart ;; 1.0
211     # [6] QName = PrefixedName / UnprefixedName ;; 1.1
212     # [6a] PrefixedName = Prefix ":" LocalPart ;; 1.1
213     # [6b] UnprefixedName = LocalPart ;; 1.1
214     # [7] Prefix = NCName
215     # [8] LocalPart = NCName
216     # [9] STag = "<" QName *(S Attribute) [S] ">"
217     $xml_re{__NCSTag} = qr/<$xml_re{QName}(?:$xml_re{s}$xml_re{Attribute})*(?:$xml_re{s})?>/s;
218     # [10] ETag = "</" QName [S] ">"
219     $xml_re{__NCETag} = qr!</$xml_re{QName}(?:$xml_re{s})?>!s;
220     # [11] EmptyElemTag = "<" QName *(S Attribute) [S] "/>"
221     # [12] Attribute = NSAttName Eq AttValue / QName Eq AttValue
222     # [13] doctypedecl = '<!DOCTYPE' S QName [S ExternalID] [S]
223     # ["[" *(markupdecl / PEReference / S) "]" [S]] ">"
224     # [14] elementdecl = '<!ELEMENT' S QName S contentspec [S] ">"
225     # [15] cp = (QName / choice / sep) ["?" / "*" / "+"]
226     # [16] Mixed = "(" [S] '#PCDATA' *([S] "|" [S] QName) [S] ")*" / "(" [S] '#PCDATA' [S] ")"
227     # [17] AttlistDecl = '<!ATTLIST' S QName *AttDef [S] ">"
228     # [18] AttDef = S (QName / NSAttName) S AttType S DefaultDecl
229     # [19] Name = NameStartChar *NameChar ;; 1.1 draft
230     # [20] NameChar = {XML1.1}.NameChar ;; 1.1 draft
231     # [21] NameStartChar = {XML1.1}.NameStartChar ;; 1.1 draft
232    
233    
234     sub new ($) {
235     bless {}, shift;
236     }
237    
238     sub parse_text ($$;$) {
239     my $self = shift;
240     my $s = shift;
241     my $o = shift || {line => 0, pos => 0, entity_type => ''};
242     my $r = SuikaWiki::Markup::XML->new (type => '#document');
243     my $c = $r;
244     if ($s =~ /([^\x09\x0A\x0D\x20-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}])/) {
245     my $o = {line => 0, pos => 0};
246     _count_lp ($`, $o);
247     $self->_raise_error ($o, type => 'SYNTAX_LEGAL_CHARACTER', t => ord ($1));
248     ## NOTE: Even if there are more than one non-XML-Char, error is raised only one time.
249     }
250     while ($s) {
251     my $is_dtd = ($o->{entity_type} eq 'dtd_external_subset'
252     || $o->{entity_type} eq 'dtd_internal_subset');
253     my $can_be_dtd = ($o->{entity_type} eq 'external_parsed_entity');
254     my $is_ext_entity = ($o->{entity_type} eq 'dtd_external_subset'
255     || $o->{entity_type} eq 'general_external_parsed_entity'
256     || $o->{entity_type} eq 'external_parsed_entity');
257     if ($s =~ s/^($xml_re{__STag_or_EmptyElemTag_simple})//s) {
258     my ($stag) = ($1);
259     if ($is_dtd) {
260     $self->_raise_error ($o, p => $c, type => 'SYNTAX_INVALID_POSITION', t => $stag);
261     } elsif ($c->node_type eq '#document') {
262     if ($o->{entity_type} eq 'document_entity' && $self->_is_brother_of_root_element ($c)) {
263     $self->_raise_error ($o, p => $c, type => 'SYNTAX_DATA_OUT_OF_ROOT_ELEMENT', t => $stag);
264     } elsif (!$o->{entity_type} || $can_be_dtd) {
265     $o->{entity_type} = 'general_external_parsed_entity';
266     }
267     }
268     ## Parse element type name
269     $stag =~ s/^<($xml_re{Name})//s;
270     my ($prefix, $lname) = _ns_parse_qname ($1); ## TODO: invalid qname valid name
271     $c = $c->append_new_node (local_name => $lname);
272     $c->flag (p_original_qname => $1);
273     $c->flag (p_o_start => $self->_make_clone_of ($o));
274     _count_lp ($&, $o);
275     ## Parse attributes
276     $stag =~ s/>$//;
277     my $etag = 0;
278     if ($stag =~ s!/$!!) { # empty element
279     $c->option (use_EmptyElemTag => 1);
280     $etag = 1;
281     }
282     $self->_parse_attribute_spec_list ($c, $stag, $o) if $stag;
283     _count_lp (($etag ? '/>' : '>'), $o);
284     ## Parse element type name (namespace)
285     my $uri = $c->defined_namespace_prefix ($prefix || '');
286     if ($uri) {
287     $c->namespace_uri ($uri);
288     } elsif (!$prefix) { ## Default NS
289     #$c->namespace_uri ('');
290     $c->define_new_namespace ('' => '');
291     } else {
292     my $o_etypename = $self->_make_clone_of ($c->flag ('p_o_start'));
293     $o_etypename->{pos}++; # '<'
294     $self->_raise_error ($o_etypename, type => 'NC_PREFIX_NOT_DEFINED', t => $prefix);
295     my $uri = $NS{internal_ns_invalid}.$self->_uri_escape ($prefix);
296     $c->namespace_uri ($uri);
297     $c->define_new_namespace ($prefix => $uri);
298     }
299     ## Ending the element if EmptyElemTag
300     if ($etag) {
301     $c = $c->{parent};
302     }
303     } elsif ($s =~ s/^$xml_re{ETag_M}//s) {
304     if (!$is_dtd) {
305     my $ename = $1;
306     if ($ename eq $c->flag ('p_original_qname') || $ename eq $c->qname) {
307     $c = $c->{parent};
308     } else { ## Element type name does not match
309     my $o_etn = $self->_make_clone_of ($o);
310     $o_etn->{pos} += 2;
311     $self->_raise_error ($o_etn, p => $c, type => 'WFC_ELEMENT_TYPE_MATCH', t => [$ename, $c->qname]);
312     }
313     } else {
314     $self->_raise_error ($o, type => 'SYNTAX_INVALID_POSITION', t => $&);
315     }
316     _count_lp ($&, $o);
317     } elsif (($is_dtd || $can_be_dtd) && $s =~ s/^$xml_re{PEReference_M}//s) {
318     if ($can_be_dtd) {
319     $o->{entity_type} = 'dtd_external_subset'; $can_be_dtd = 0;
320     }
321     $c->append_new_node (type => '#reference', local_name => $1, ## BUG: %in:valid;
322     namespace_uri => 'urn:x-suika-fam-cx:markup:sgml:entity:parameter');
323     _count_lp ($1.'%;', $o);
324     } elsif (($is_dtd || $can_be_dtd || $c->node_type eq '#document') && ($s =~ s/^$xml_re{s}//s)) {
325     $c->append_text ($&);
326     _count_lp ($&, $o);
327     } elsif (!$is_dtd && ($s =~ s/^$xml_re{__CharDataP}//s)) {
328     if ($c->node_type eq '#document') {
329     if ($o->{entity_type} eq 'document_entity') {
330     $self->_raise_error ($o, p => $c, type => 'SYNTAX_DATA_OUT_OF_ROOT_ELEMENT', t => $&);
331     } elsif (!$o->{entity_type} || $can_be_dtd) {
332     $o->{entity_type} = 'general_external_parsed_entity'; $can_be_dtd = 0;
333     }
334     }
335     $c->append_text ($&);
336     _count_lp ($&, $o);
337     } elsif ($s =~ s/^($xml_re{Reference})//s) {
338     if ($is_dtd) {
339     $self->_raise_error ($o, p => $c, type => 'SYNTAX_INVALID_POSITION', t => $1);
340     } elsif ($c->node_type eq '#document') {
341     if ($self->_is_brother_of_root_element ($c)) {
342     if ($o->{entity_type} eq 'document_entity') {
343     $self->_raise_error ($o, p => $c, type => 'SYNTAX_DATA_OUT_OF_ROOT_ELEMENT', t => $1);
344     } elsif (!$o->{entity_type} || $can_be_dtd) {
345     $o->{entity_type} = 'general_external_parsed_entity'; $can_be_dtd = 0;
346     }
347     }
348     }
349     $self->_parse_reference ($c, $1, $o);
350     } elsif ($s =~ s/^$xml_re{CDSect_M}//s) {
351     if ($is_dtd) {
352     $self->_raise_error ($o, p => $c, type => 'SYNTAX_INVALID_POSITION', t => '<![');
353     } elsif ($c->node_type eq '#document') {
354     if ($o->{entity_type} eq 'document_entity' && $self->_is_brother_of_root_element ($c)) {
355     $self->_raise_error ($o, p => $c, type => 'SYNTAX_DATA_OUT_OF_ROOT_ELEMENT', t => '<![');
356     } elsif (!$o->{entity_type} || $can_be_dtd) {
357     $o->{entity_type} = 'general_external_parsed_entity'; $can_be_dtd = 0;
358     }
359     }
360     $c->append_new_node (type => '#section', local_name => 'CDATA', value => $1);
361     _count_lp ($&, $o);
362     } elsif ($s =~ s/^$xml_re{Comment_M}//s) {
363     $c->append_new_node (type => '#comment', value => $1);
364     _count_lp ($&, $o);
365     } elsif ($s =~ s/^($xml_re{__EntityDecl_simple})//s) {
366     if ($can_be_dtd) {
367     $o->{entity_type} = 'dtd_external_subset'; $can_be_dtd = 0;
368     } elsif (!$is_dtd) {
369     $self->_raise_error ($o, p => $c, type => 'SYNTAX_INVALID_POSITION', t => '<!ENTITY');
370     }
371     $self->_parse_entity_declaration ($1, $c, $o);
372     } elsif ($s =~ s/^($xml_re{__elementdecl_simple})//s) {
373     if ($can_be_dtd) {
374     $o->{entity_type} = 'dtd_external_subset'; $can_be_dtd = 0;
375     } elsif (!$is_dtd) {
376     $self->_raise_error ($o, p => $c, type => 'SYNTAX_INVALID_POSITION', t => '<!ELEMENT');
377     }
378     $self->_parse_element_declaration ($1, $c, $o);
379     } elsif ($s =~ s/^($xml_re{__AttlistDecl_simple})//s) {
380     if ($can_be_dtd) {
381     $o->{entity_type} = 'dtd_external_subset'; $can_be_dtd = 0;
382     } elsif (!$is_dtd) {
383     $self->_raise_error ($o, p => $c, type => 'SYNTAX_INVALID_POSITION', t => '<!ATTLIST');
384     }
385     $self->_parse_attlist_declaration ($1, $c, $o);
386     } elsif ($s =~ s/^$xml_re{__doctypedecl_start_simple_M}//s) {
387     my ($d, $name, $extid, $all) = ($1, $2, $3, $&);
388     if ($is_dtd || $is_ext_entity) {
389     $self->_raise_error ($o, p => $c, type => 'SYNTAX_INVALID_POSITION', t => '<!DOCTYPE');
390     } elsif ($c->node_type ne '#document') {
391     $self->_raise_error ($o, p => $c, type => 'SYNTAX_INVALID_DOCTYPE_POSITION', t => '<!DOCTYPE');
392     } else {
393     for (@{$c->{node}}) {
394     ## Root element or DOCTYPE declaration has already appeared
395     if ($_->{type} eq '#element' || $_->{type} eq '#declaration') {
396     $self->_raise_error ($o, p => $c, type => 'SYNTAX_INVALID_DOCTYPE_POSITION', t => '<!DOCTYPE');
397     last;
398     }
399     }
400     }
401     my $D = $c->append_new_node (type => '#declaration', namespace_uri => $NS{SGML}.'doctype');
402     $D->flag (p_o_start => $self->_make_clone_of ($o));
403     $D->set_attribute (qname => $name);
404     _count_lp ($d.$name, $o);
405     if ($extid =~ s/^$xml_re{s}SYSTEM$xml_re{s}//s) {
406     $D->set_attribute (SYSTEM => substr ($extid, 1, length ($extid) - 2));
407     _count_lp ($extid, $o);
408     } elsif ($extid =~ s/^($xml_re{s}PUBLIC$xml_re{s})($xml_re{__AttValue_simple})//s) {
409     _count_lp ($1.'"', $o);
410     my $pubid = substr ($2, 1, length ($2) - 2);
411     if ($pubid =~ /^($xml_re{PubidChar})*($xml_re{__non_PubidChar})/s) {
412     _count_lp ($1, $o);
413     _raise_fatal_error ($o, desc => 'INVALID_PUBID_CHAR', t => $2);
414     } else {
415     $D->set_attribute (PUBLIC => $pubid);
416     _count_lp ($pubid.'"'.$extid, $o);
417     }
418     $extid =~ s/^$xml_re{s}//s;
419     $D->set_attribute (SYSTEM => substr ($extid, 1, length ($extid) - 2));
420     }
421     _count_lp ($1, $o) if $s =~ s/^($xml_re{s})//s;
422     if ($s =~ s/^\[//s) {
423     _count_lp ('[', $o);
424     $o->{entity_type} = 'dtd_internal_subset';
425     $c = $D;
426     } elsif ($s =~ s/^>//s) {
427     _count_lp ('>', $o);
428     $o->{entity_type} = 'document_entity';
429     } else {
430     _raise_fatal_error ($o, desc => 'INVALID_DECLARE_SYNTAX', t => substr ($s, 0, 1));
431     substr ($s, 0, 1) = '';
432     $o->{entity_type} = 'document_entity';
433     }
434     } elsif (($o->{entity_type} eq 'dtd_internal_subset') && $s =~ s/^\](?:$xml_re{s})?>//s) {
435     $o->{entity_type} = 'document_entity';
436     _count_lp ($&, $o);
437     $c = $c->{parent};
438     } elsif ($s =~ s/^($xml_re{__NotationDecl_simple})//s) {
439     if ($can_be_dtd) {
440     $o->{entity_type} = 'dtd_external_subset'; $can_be_dtd = 0;
441     } elsif (!$is_dtd) {
442     $self->_raise_error ($o, p => $c, type => 'SYNTAX_INVALID_POSITION', t => '<!NOTATION');
443     }
444     $self->_parse_entity_declaration ($1, $c, $o);
445     } elsif ($s =~ s/^$xml_re{PI_M}//s) {
446     my ($target, $data, $all) = ($1, $2, $&);
447     if ($target eq 'xml') {
448     if ($c->node_type eq '#document' && $c->count == 0) {
449     _count_lp ('<?xml', $o);
450     if (length ($data)) {
451     $self->_parse_xml_declaration ($c->append_new_node (type => '#pi', local_name => 'xml'), $data, $o);
452     } else {
453     $self->_raise_error ($o, type => 'SYNTAX_XML_DECLARE_NO_ATTR');
454     }
455     _count_lp ('?>', $o);
456     } else {
457     _raise_fatal_error ($o, desc => 'INVALID_XML_DECLARE');
458     }
459     } else {
460     $c->append_new_node (type => '#pi', local_name => $1, value => $2);
461     _count_lp ($all, $o);
462     }
463     } else {
464     $self->_raise_error ($o, p => $c, type => 'SYNTAX_INVALID_CHAR', t => substr ($s, 0, 1));
465     substr ($s, 0, 1) = '';
466     }
467     }
468     while (1) {
469     if (ref $c->{parent}) {
470     if ($c->node_type eq '#element') {
471     $self->_raise_error ($o, type => 'SYNTAX_END_TAG_NOT_FOUND', t => $c);
472     } else {
473     $self->_raise_error ($o, type => 'SYNTAX_END_OF_MARKUP_NOT_FOUND', t => $c);
474     }
475     $c = $c->{parent};
476     } else {
477     last;
478     }
479     }
480     wantarray ? ($r, $o) : $r;
481     }
482    
483     sub _uri_escape ($$) {
484     shift;
485     my $s = shift; ## TODO: support utf8 flag
486     $s =~ s/([^0-9A-Za-z_.-])/sprintf '%%%02X', ord $1/ge;
487     $s;
488     }
489    
490     sub _make_clone_of ($$) {
491     my ($self, $mother) = @_;
492     if (ref $mother eq 'HASH') {
493     my $child = {};
494     for (keys %$mother) {
495     $child->{$_} = $mother->{$_}; ## BUG: recursive!
496     }
497     return $child;
498     } else {
499     ## BUG: not supported
500     }
501     }
502    
503     sub _count_lp ($$) {
504     my ($s, $o) = @_;
505     $s =~ s/[^\x0A\x0D]*(?:\x0D\x0A?|\x0A)/$o->{line}++;$o->{pos}=0;''/ges;
506     $o->{pos} += length $s;
507     }
508     sub _ns_parse_qname ($) {
509     my $qname = shift;
510     if ($qname =~ /:/) {
511     return split /:/, $qname, 2;
512     } else {
513     return (undef, $qname);
514     }
515     }
516     sub _parse_attribute_spec_list ($$$$) {
517     my ($self, $c, $attrs, $o) = @_;
518     my @attrs;
519     my (%defined_attr, %defined_ns_attr);
520     my $no_s = 0;
521     while ($attrs) {
522     if (!$no_s && $attrs =~ s/^$xml_re{Attribute_M}//s) {
523     my ($qname, $qvalue) = ($1, $2);
524     my ($prefix, $name) = _ns_parse_qname ($qname);
525     push @attrs, {prefix => $prefix, lname => $name, qvalue => $qvalue, qname => $qname,
526     o => {line => $o->{line}, pos => ($o->{pos} + length ($&) - length ($qvalue))},
527     o_attr_start => {line => $o->{line}, pos => $o->{pos}}};
528     _count_lp ($&, $o);
529     $no_s = 1;
530     } elsif ($attrs =~ s/^($xml_re{s})//s) {
531     _count_lp ($1, $o);
532     $no_s = 0;
533     } else {
534     $self->_raise_error ($o, type => 'SYNTAX_INVALID_CHAR', t => substr ($attrs, 0, 1));
535     substr ($attrs, 0, 1) = '';
536     $no_s = 1;
537     }
538     }
539     for (grep {($_->{prefix} eq 'xmlns') || (!$_->{prefix} && ($_->{lname} eq 'xmlns'))} @attrs) {
540     my $value = SuikaWiki::Markup::XML->new (type => '#text');
541     $self->_parse_attribute_value ($value, $_->{qvalue}, $_->{o});
542     if (!$defined_attr{$_->{qname}}) {
543     if ($_->{prefix} eq 'xmlns') {
544     $c->define_new_namespace ($_->{lname} => $value); # BUG: Reference
545     } else {
546     $c->define_new_namespace ('' => $value); # BUG: Reference
547     }
548     $defined_attr{$_->{qname}} = 1;
549     } else { ## Already defined
550     $self->_raise_error ($_->{o_attr_start}, type => 'WFC_UNIQUE_ATT_SPEC', t => $_->{qname});
551     my $attr;
552     if ($_->{prefix} eq 'xmlns') {
553     $attr = $c->set_attribute ($_->{lname} => '', namespace_uri => $NS{internal_attr_duplicate}.'xmlns');
554     } else { ## BUG: xmlns="" (1.1)
555     $attr = $c->set_attribute (xmlns => '', namespace_uri => $NS{internal_attr_duplicate});
556     }
557     $attr->append_node ($value);
558     }
559     }
560     for (grep {($_->{prefix} ne 'xmlns') && !(!$_->{prefix} && ($_->{lname} eq 'xmlns'))} @attrs) {
561     my $attr;
562     my $uri; $uri = $c->defined_namespace_prefix ($_->{prefix}) if $_->{prefix};
563     if ($defined_attr{$_->{qname}}) { ## Already-defined-attr is found
564     $self->_raise_error ($_->{o_attr_start}, type => 'WFC_UNIQUE_ATT_SPEC', t => $_->{qname});
565     $uri = $NS{internal_attr_duplicate} . $defined_attr{$_->{qname}};
566     $_->{prefix} = 'dup.' . $defined_attr{$_->{qname}} . ($_->{prefix} ? '.'.$_->{prefix}:'');
567     $c->define_new_namespace ($_->{prefix} => $uri);
568     $defined_attr{$_->{qname}}++;
569     } elsif (defined $uri && $defined_ns_attr{$_->{lname}.':'.$uri}) {
570     ## ns:attr="a" ns2:attr="b" xmlns:ns="ns" xmlns:ns2="ns"
571     $self->_raise_error ($_->{o_attr_start}, type => 'NC_unique_att_spec', t => $_->{qname});
572     my $i = $defined_ns_attr{$_->{lname}.':'.$uri}++;
573     $uri = $NS{internal_attr_duplicate} . 'ns.' . $i;
574     $_->{prefix} = 'dup.ns.' . $i . ($_->{prefix} ? '.'.$_->{prefix}:'');
575     $c->define_new_namespace ($_->{prefix} => $uri);
576     } else {
577     $defined_attr{$_->{qname}} = 1;
578     $defined_ns_attr{$_->{lname}.':'.$uri} = 1;
579     }
580     if ($_->{prefix}) {
581     if (defined $uri) {
582     $attr = $c->set_attribute ($_->{lname} => '', namespace_uri => $uri);
583     } else {
584     $self->_raise_error ($o, type => 'NC_PREFIX_NOT_DEFINED', t => $_->{prefix});
585     my $uri = $NS{internal_ns_invalid}.$self->_uri_escape ($_->{prefix});
586     $attr = $c->set_attribute ($_->{lname} => '', namespace_uri => $uri);
587     $c->define_new_namespace ($_->{prefix} => $uri);
588     }
589     } else {
590     $attr = $c->set_attribute ($_->{lname} => '');
591     }
592     $self->_parse_attribute_value ($attr, $_->{qvalue}, $_->{o}) if $attr;
593     }
594     }
595     sub _parse_attribute_value ($$$$) {
596     my ($self, $attr, $qvalue, $o) = @_;
597     $qvalue = substr ($qvalue, 1, length ($qvalue) - 2);
598     _count_lp ('"', $o);
599     while ($qvalue) {
600     if ($qvalue =~ s/^($xml_re{Reference})//) {
601     $self->_parse_reference ($attr, $1);
602     _count_lp ($1, $o);
603     } elsif ($qvalue =~ s/^([^&<]+)//) {
604     $attr->append_text ($1);
605     _count_lp ($1, $o);
606     } else {
607     $self->_raise_error ($o, type => 'SYNTAX_INVALID_CHAR', t => substr ($qvalue, 0, 1));
608     substr ($qvalue, 0, 1) = '';
609     }
610     }
611     _count_lp ('"', $o);
612     }
613     sub _parse_entity_value ($$$$) {
614     my ($self, $attr, $qvalue, $o) = @_;
615     $qvalue = substr ($qvalue, 1, length ($qvalue) - 2);
616     _count_lp ('"', $o);
617     while ($qvalue) {
618     if ($qvalue =~ s/^($xml_re{Reference})//) {
619     $self->_parse_reference ($attr, $1, $o);
620     } elsif ($qvalue =~ s/^$xml_re{PEReference_M}//) {
621     if ($o->{entity_type} eq 'dtd_internal_subset') {
622     $self->_raise_error ($o, type => 'WFC_PE_IN_INTERNAL_SUBSET', t => $1);
623     }
624     $attr->append_new_node (type => '#reference', local_name => $1,
625     namespace_uri => 'urn:x-suika-fam-cx:markup:sgml:entity:parameter');
626     _count_lp ($1.'%;', $o);
627     } elsif ($qvalue =~ s/^([^%&]+)//) {
628     $attr->append_text ($1);
629     _count_lp ($1, $o);
630     } else {
631     $self->_raise_error ($o, type => 'SYNTAX_INVALID_CHAR', t => substr ($qvalue, 0, 1));
632     substr ($qvalue, 0, 1) = '';
633     }
634     }
635     _count_lp ('"', $o);
636     }
637     sub _parse_parametered_markup_declaration ($$$$) {
638     my ($self, $attr, $qvalue, $o) = @_;
639     while ($qvalue) {
640     if ($qvalue =~ s/^$xml_re{PEReference_M}//) {
641     if ($o->{entity_type} eq 'dtd_internal_subset') {
642     $self->_raise_error ($o, type => 'WFC_PE_IN_INTERNAL_SUBSET', t => $1);
643     }
644     $attr->append_new_node (type => '#reference', local_name => $1,
645     namespace_uri => 'urn:x-suika-fam-cx:markup:sgml:entity:parameter');
646     _count_lp ($1.'%;', $o);
647     } elsif ($qvalue =~ s/^($xml_re{__AttValue_simple})//s) {
648     $attr->append_new_node (type => '#xml', value => $1); ## TODO: parse and check PEref
649     _count_lp ($1, $o);
650     } elsif ($qvalue =~ s/^(\#?$xml_re{Name})//) {
651     $attr->append_text ($1);
652     _count_lp ($1, $o);
653     } elsif ($qvalue =~ s/^($xml_re{s})//s) {
654     $attr->append_text ($1);
655     _count_lp ($1, $o);
656     } else {
657     $self->_raise_error ($o, type => 'SYNTAX_INVALID_CHAR', t => substr ($qvalue, 0, 1));
658     substr ($qvalue, 0, 1) = '';
659     }
660     }
661     }
662     sub _parse_reference ($$$$) {
663     my ($self, $c, $ref, $o) = @_;
664     if ($ref =~ /$xml_re{EntityRef_M}/) { ## BUG: QName
665     $c->append_new_node (type => '#reference', local_name => $1,
666     namespace_uri => 'urn:x-suika-fam-cx:markup:sgml:entity');
667     } elsif ($ref =~ /x([0-9A-Fa-f]+)/) {
668     my $ch = hex $1;
669     if (($ch < 0x20 && $ch != 0x09 && $ch != 0x0A && $ch != 0x0D)
670     || (0xD7FF < $ch && $ch < 0xE000) || $ch == 0xFFFE || $ch == 0xFFFF
671     || $ch > 0x10FFFF) {
672     $self->_raise_error ($o, type => 'WFC_LEGAL_CHARACTER', t => $ch);
673     }
674     $c->append_new_node (type => '#reference', value => $ch,
675     namespace_uri => 'urn:x-suika-fam-cx:markup:sgml:char:ref:hex');
676     } elsif ($ref =~ /([0-9]+)/) {
677     my $ch = $1;
678     if (($ch < 0x20 && $ch != 0x09 && $ch != 0x0A && $ch != 0x0D)
679     || (0xD7FF < $ch && $ch < 0xE000) || $ch == 0xFFFE || $ch == 0xFF
680     || $ch > 0x10FFFF) {
681     $self->_raise_error ($o, type => 'WFC_LEGAL_CHARACTER', t => $ch);
682     }
683     $c->append_new_node (type => '#reference', value => $ch,
684     namespace_uri => 'urn:x-suika-fam-cx:markup:sgml:char:ref');
685     } else {
686     $self->_raise_error ($o, type => 'UNKNOWN', t => $ref);
687     }
688     _count_lp ($ref, $o);
689     }
690     sub _parse_xml_declaration ($$$$) {
691     my ($self, $c, $attrs, $o) = @_;
692     my $stage = 0; # 0: <?xml, 1: version="", 2: encoding="", 3: standalone="", 4: ?>
693     $attrs = ' ' . $attrs;
694     while ($attrs) {
695     if ($attrs =~ s/^$xml_re{s}version(?:$xml_re{s})?=(?:$xml_re{s})?("[A-Za-z0-9_.:-]+"|'[A-Za-z0-9_.:-]+')//s) {
696     my $version = substr ($1, 1, length ($1) - 2);
697     if ($stage > 0) {
698     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE', t => 'version');
699     }
700     if ($version eq '1.0') {
701     $c->set_attribute (version => $version);
702     } else {
703     _raise_fatal_error ($o, desc => 'UNSUPPORTED_XML_VERSION', t => $version);
704     }
705     _count_lp ($&, $o); $stage++;
706     } elsif ($attrs =~ s/^$xml_re{s}encoding(?:$xml_re{s})?=(?:$xml_re{s})?("[A-Za-z0-9_.-]+"|'[A-Za-z0-9_.:-]+')//s) {
707     if ($stage > 2) {
708     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE', t => 'encoding');
709     } elsif ($stage == 0) { ## No version pseudo-attr
710     if ($o->{entity_type} eq 'document_entity') {
711     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE_NO_ATTR', t => 'version');
712     $c->set_attribute (version => '1.0');
713     } else {
714     $o->{entity_type} = 'external_parsed_entity';
715     }
716     }
717     $c->set_attribute (encoding => substr ($1, 1, length ($1) - 2));
718     _count_lp ($&, $o); $stage = 2;
719     } elsif ($attrs =~ s/^$xml_re{s}standalone(?:$xml_re{s})?=(?:$xml_re{s})?("(?:yes|no)"|'(?:yes|no)')//s) {
720     if ($stage == 0 || $stage > 3 || $o->{entity_type} eq 'external_parsed_entity'
721     || $o->{entity_type} eq 'dtd_external_subset'
722     || $o->{entity_type} eq 'general_external_parsed_entity') {
723     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE_NO_ATTR', t => 'standalone');
724     }
725     $c->set_attribute (standalone => (substr ($1, 1, 1) eq 'y' ? 'yes' : 'no'));
726     _count_lp ($&, $o); $stage = 3;
727     } elsif ($attrs =~ s/^($xml_re{s})//s) {
728     my $s = $1;
729     if ($stage == 0) {
730     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE_NO_ATTR');
731     $c->set_attribute (version => '1.0');
732     }
733     _count_lp ($s, $o); $stage = 4;
734     } else {
735     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE', t => $attrs);
736     _count_lp ($attrs, $o); undef $attrs;
737     }
738     } # while
739     if ($stage == 0) {
740     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE_NO_ATTR');
741     $c->set_attribute (version => '1.0');
742     }
743     }
744     sub _parse_entity_declaration ($$$$) {
745     my ($self, $all, $c, $o) = @_;
746     my ($e, $p) = (undef, 0);
747     $e = $c->append_new_node (type => '#declaration');
748     ## Entity/notation type
749     if ($all =~ s/^<!ENTITY$xml_re{s}(%$xml_re{s})?//s) {
750     $p = $1;
751     if ($p) {
752     $e->namespace_uri ('urn:x-suika-fam-cx:markup:sgml:entity:parameter');
753     } else {
754     $e->namespace_uri ('urn:x-suika-fam-cx:markup:sgml:entity');
755     }
756     _count_lp ($&, $o);
757     } elsif ($all =~ s/^<!NOTATION$xml_re{s}//s) {
758     $e->namespace_uri ('urn:x-suika-fam-cx:markup:sgml:notation');
759     _count_lp ($&, $o); $p = 'n';
760     }
761     ## Entity/notation name
762     if ($all =~ s/^($xml_re{Name})//s) {
763     $e->local_name ($1);
764     _count_lp ($1, $o);
765     }
766     ## EntityValue (ENTITY only)
767     if ($p ne 'n' && ($all =~ s/^$xml_re{s}($xml_re{__AttValue_simple})//s)) {
768     _count_lp (length ($&) - length ($1), $o);
769     $self->_parse_entity_value ($e->set_attribute ('value'), $1, $o);
770     ## System ID
771     } elsif ($all =~ s/^$xml_re{s}SYSTEM$xml_re{s}($xml_re{SystemLiteral})//s) {
772     $e->set_attribute (SYSTEM => substr ($1, 1, length ($1) - 2));
773     _count_lp ($&, $o); ## TODO: PEref
774     ## Public ID + System ID
775     } elsif ($all =~ s/^$xml_re{s}PUBLIC$xml_re{s}($xml_re{PublicLiteral})$xml_re{s}($xml_re{SystemLiteral})//s) {
776     $e->set_attribute (PUBLIC => substr ($1, 1, length ($1) - 2));
777     $e->set_attribute (SYSTEM => substr ($2, 1, length ($1) - 2));
778     _count_lp ($&, $o);
779     ## Public ID (NOTATION only)
780     } elsif ($p eq 'n' && ($all =~ s/^$xml_re{s}PUBLIC$xml_re{s}($xml_re{PublicLiteral})//s)) {
781     $e->set_attribute (PUBLIC => substr ($1, 1, length ($1) - 2));
782     _count_lp ($&, $o);
783     ## Including parameter entity reference
784     } elsif ($all =~ s/^(?:$xml_re{s})?(?:$xml_re{PEReference}|$xml_re{Name}|$xml_re{__AttValue_simple})(?:$xml_re{s}|$xml_re{PEReference}|$xml_re{Name}|$xml_re{__AttValue_simple})*//s) {
785     $self->_parse_parametered_markup_declaration ($e, $&, $o);
786     } else {
787     _raise_fatal_error ($o, desc => 'INVALID_DECLARE_SYNTAX', t => $all);
788     }
789     ## NDATA (General entity only)
790     if (!$p && $all =~ s/^$xml_re{s}NDATA$xml_re{s}($xml_re{Name})//s) {
791     $e->set_attribute (NDATA => $1);
792     _count_lp ($&, $o);
793     }
794     if ($all =~ s/^((?:$xml_re{s})?>)$//s) {
795     _count_lp ($1, $o);
796     } else {
797     _raise_fatal_error ($o, desc => 'INVALID_DECLARE_SYNTAX', t => $all);
798     }
799     }
800    
801     sub _parse_element_declaration ($$$$) {
802     my ($self, $all, $c, $o) = (@_);
803     my $e = undef;
804     $e = $c->append_new_node (type => '#declaration',
805     namespace_uri => 'urn:x-suika-fam-cx:markup:sgml:element');
806     $all =~ s/^<!ELEMENT//s;
807     _count_lp ('<!ELEMENT', $o);
808     ## Element type name
809     if ($all =~ s/^$xml_re{s}($xml_re{Name})//s) {
810     $e->set_attribute (qname => $1);
811     _count_lp ($&, $o);
812     }
813     ## contentspec / PEReference
814     if ($all =~ s/^(?:$xml_re{s})?(?:$xml_re{PEReference}|$xml_re{Name}|\#PCDATA|\()(?:$xml_re{s}|$xml_re{PEReference}|$xml_re{Name}|\#PCDATA|[()|,+*?])*//s) {
815     $e->append_new_node (type => '#xml', value => $&); # TODO: temporary
816     _count_lp ($&, $o);
817     }
818     if ($all =~ s/^((?:$xml_re{s})?>)$//s) {
819     _count_lp ($1, $o);
820     } else {
821     _raise_fatal_error ($o, desc => 'INVALID_DECLARE_SYNTAX', t => $all);
822     }
823     }
824    
825    
826     sub _parse_attlist_declaration ($$$$) {
827     my ($self, $all, $c, $o) = (@_);
828     my $e = undef;
829     $e = $c->append_new_node (type => '#declaration', local_name => 'ATTLIST');
830     $all =~ s/^<!ATTLIST//s;
831     _count_lp ('<!ATTLIST', $o);
832     ## Element type name
833     if ($all =~ s/^$xml_re{s}($xml_re{Name})//s) {
834     $e->target_name ($1);
835     _count_lp ($&, $o);
836     }
837     ## Definition
838     if ($all =~ s/^(?:$xml_re{PEReference}|$xml_re{Name}|\#$xml_re{Name}|$xml_re{s}|$xml_re{__AttValue_simple})+//s) {
839     $e->append_new_node (type => '#xml', value => $&); # TODO: temporary
840     _count_lp ($&, $o);
841     }
842     if ($all =~ s/^((?:$xml_re{s})?>)$//s) {
843     _count_lp ($1, $o);
844     } else {
845     _raise_fatal_error ($o, desc => 'INVALID_DECLARE_SYNTAX', t => $all);
846     }
847     }
848    
849     ## TODO: remove this function. this function is already obsoleted.
850     sub _raise_fatal_error ($%) {
851     require Carp;
852     my ($o, %o) = @_;
853     $o{desc} = {
854     INVALID_CHAR => 'Invalid character (%s) at this context',
855     INVALID_XML_DECLARE => 'XML declaration must be at the top of the entity',
856     NONDECLARED_NS_PREFIX => 'Undeclared namespace prefix (%s) is used',
857     NONMATCH_ETAG => 'End tag (element type name = '.$o{_end_qname}.') does not match with start tag (element type name = '.$o{_start_qname}.')',
858     NOT_ALLOWED_HERE => 'This type of markup (%s) cannot appear here',
859     NOT_ALLOWED_THIS_MODE => 'This type of markup (%s) cannot be used '.({
860     document_entity => 'out of DTD',
861     dtd_internal_subset => 'in internal subset of DTD',
862     dtd_external_subset => 'in external parsed entity (external subset of DTD)',
863     }->{$o->{entity_type}||'document_entity'}||'in '.$o->{entity_type}),
864     UNKNOWN => 'Unknown error (%s)',
865     }->{$o{desc}} || $o{desc};
866     $o{desc} .= ' (%s)' if length $o{t} && $o{desc} !~ /%s/;
867     Carp::croak ("Line $o->{line}, position $o->{pos}: ".sprintf $o{desc}, $o{t});
868     }
869    
870     sub _is_brother_of_root_element ($$) {
871     my ($self, $c) = @_;
872     for (@{$c->child_nodes}) {
873     if ($_->node_type eq '#element') {
874     return 1;
875     }
876     }
877     return 0;
878     }
879    
880     my %_Error = (
881     ## Syntax errors
882     SYNTAX_DATA_OUT_OF_ROOT_ELEMENT => {
883     description => 'Invalid data or markup out of root element',
884     level => 'wfc',
885     },
886     SYNTAX_END_OF_MARKUP_NOT_FOUND => {
887     description => sub {
888     my ($o, $err) = @_;
889     my $o_start = $err->{t}->flag ('p_o_start');
890     my $r = $err->{t}->qname;
891     $r = sprintf 'line %d, position %d%s', $o_start->{line},
892     $o_start->{pos}, ($r ? '; '.$r : '') if ref $o_start;
893     $r ? $r = '; '.$r : 0;
894     $err->{t} = $err->{t}->node_type;
895     'Markup is not closed (%s'.$r.')';
896     },
897     level => 'wfc',
898     },
899     SYNTAX_END_TAG_NOT_FOUND => {
900     description => sub {
901     my ($o, $err) = @_;
902     my $o_start = $err->{t}->flag ('p_o_start');
903     my $r = '';
904     $r = sprintf 'line %d, position %d%s', $o_start->{line},
905     $o_start->{pos}, ($r ? '; '.$r : '') if ref $o_start;
906     $r ? $r = '; '.$r : 0;
907     $err->{t} = $err->{t}->qname;
908     'End tag of element (type = %s'.$r.') not found';
909     },
910     level => 'wfc',
911     },
912     SYNTAX_INVALID_CHAR => {
913     description => 'Invalid character (%s) at this context',
914     level => 'wfc',
915     },
916     SYNTAX_INVALID => {
917     description => 'This type of markup (%s) cannot appear here',
918     level => 'wfc',
919     },
920     SYNTAX_INVALID_DOCTYPE_POSITION => {
921     description => 'DOCTYPE declaration must be between xml declaration and the root element',
922     level => 'wfc',
923     },
924     SYNTAX_INVALID_POSITION => {
925     description => sub {
926     my ($o) = shift;
927     'This type of markup (%s) cannot be used '.({
928     document_entity => 'out of DTD',
929     dtd_internal_subset => 'in the internal subset of DTD',
930     dtd_external_subset => 'in the external subset of DTD',
931     external_parsed_entity => 'in the external parsed entity',
932     general_external_parsed_entity => 'in the general external parsed entity',
933     }->{$o->{entity_type}||'document_entity'}||'in '.$o->{entity_type})},
934     level => 'wfc',
935     },
936     SYNTAX_LEGAL_CHARACTER => {
937     description => sub {
938     my $r = sprintf 'The character U-%08X is not a legal XML Char',
939     $_[1]->{t};
940     $_[1]->{t} = undef;
941     $r;
942     },
943     level => 'wfc',
944     },
945     SYNTAX_XML_DECLARE => {
946     description => 'Syntax of XML (or text) declaration is invalid',
947     level => 'wfc',
948     },
949     SYNTAX_XML_DECLARE_NO_ATTR => {
950     description => 'XML (or text) declaration does not have pseudo attribute',
951     level => 'wfc',
952     },
953     SYNTAX_XML_DECLARE_POSITION => {
954     description => 'XML declaration must be at the top of the entity',
955     level => 'wfc',
956     },
957     ## Well-formedness error
958     WFC_ELEMENT_TYPE_MATCH => {
959     description => 'End tag (type = %s) does not match with start tag (type = %s)',
960     level => 'wfc',
961     },
962     WFC_LEGAL_CHARACTER => {
963     description => sub {
964     my $r = sprintf 'The character referred (U-%08X) is not a legal XML Char',
965     $_[1]->{t};
966     $_[1]->{t} = undef;
967     $r;
968     },
969     level => 'wfc',
970     },
971     WFC_NO_LT_IN_ATTRIBUTE_VALUE => {
972     description => 'Replacement text of entity reference in an attribute value literal cannot contain LESS-THAN SIGN (<) itself',
973     level => 'wfc',
974     },
975     WFC_PE_IN_INTERNAL_SUBSET => {
976     description => 'Parameter entity (%s) cannot be referred in markup declaration in internal subset of DTD',
977     level => 'wfc',
978     },
979     WFC_UNIQUE_ATT_SPEC => {
980     description => 'Dupulicate attribute specification',
981     level => 'wfc',
982     },
983     ## Validity error
984     ## Namespace well-formedness error
985     NC_PREFIX_NOT_DEFINED => {
986     description => 'Undeclared namespace prefix (%s) is used',
987     level => 'nc',
988     },
989     ## Namespace validity error
990     ## Misc
991     UNKNOWN => {
992     description => 'Unknown error',
993     level => 'wfc',
994     },
995     );
996     ## TODO: error handling should be customizable (hookable) by user of this module
997     sub _raise_error ($$%) {
998     my ($self, $o, %err) = @_;
999     my $error_type = $_Error{$err{type}} || $_Error{UNKNOWN};
1000     my $error_msg = ref $error_type->{description} ? &{$error_type->{description}} ($o, \%err)
1001     : $error_type->{description};
1002     my @err_msg;
1003     ref $err{t} eq 'ARRAY' ? @err_msg = @{$err{t}} : defined $err{t} ? @err_msg = $err{t} : undef;
1004     $error_msg .= ' (%s)' if scalar (@err_msg) && ($error_msg !~ /%s/);
1005     $error_msg = sprintf $error_msg, @err_msg;
1006     require Carp;
1007     Carp::carp ("Line $o->{line}, position $o->{pos}: ".$error_msg);
1008     #Carp::croak ("Line $o->{line}, position $o->{pos}: ".$error_msg);
1009     }
1010    
1011     =head1 LICENSE
1012    
1013     Copyright 2003 Wakaba <[email protected]>
1014    
1015     This program is free software; you can redistribute it and/or
1016     modify it under the same terms as Perl itself.
1017    
1018     =cut
1019    
1020     1; # $Date: 2003/05/10 05:58:06 $

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24