/[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.4 - (hide annotations) (download)
Fri Jun 27 13:05:57 2003 UTC (23 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.3: +214 -118 lines
External entity support

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 wakaba 1.4 our $VERSION = do{my @r=(q$Revision: 1.3 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
20 wakaba 1.2 use Char::Class::XML qw!InXML_NameStartChar InXMLNameChar InXML_NCNameStartChar InXMLNCNameChar InXMLChar InXML_deprecated_noncharacter InXML_unicode_xml_not_suitable!;
21 wakaba 1.1 use SuikaWiki::Markup::XML;
22 wakaba 1.2 require SuikaWiki::Markup::XML::Error;
23     *_raise_error = \&SuikaWiki::Markup::XML::Error::raise;
24 wakaba 1.1
25     my %NS = (
26     SGML => 'urn:x-suika-fam-cx:markup:sgml:',
27     internal_ns_invalid => 'http://suika.fam.cx/~wakaba/-temp/2003/05/17/unknown-namespace#',
28     internal_attr_duplicate => 'http://suika.fam.cx/~wakaba/-temp/2003/05/17/invalid-attr#',
29     );
30    
31     =head1 METHODS
32    
33     WARNING: This module is under construction. Interface of this module is not yet fixed.
34    
35     =cut
36    
37     my %xml_re;
38     # [1] document = prolog element *Misc
39     # [2] Char = %x09 / %x0A / %x0D / U+0020-U+D7FF / U+E000-U+FFFD / U+10000-U+10FFFF ;; 1.0
40     # [2] Char = %x09 / %x0A / %x0D / %x20-7E / U+0085 / U+00A0-U+D7FF / U+E000-U+FFFD
41     # / U+10000-U+10FFFF ;; 1.1
42     # [3] s = 1*(%x20 / %x09 / %x0D / %x0A)
43     $xml_re{s} = qr/[\x09\x0D\x0A\x20]+/s;
44     # [4] NameChar = Letter / Digit / "." / "-" / "_" / ":" / CombiningChar / Extender ;; 1.0
45     # [4] NameStartChar = ":" / ALPHA / "_" / U+00C0-U+02FF / U+0370-U+037D
46     # / U+037F-U+1FFF / U+200C-U+200D / U+2070-U+218F
47     # / U+2C00-U+2FEF / U+3001-U+D7FF / U+F900-U+EFFFF ;; 1.1
48     # [4a] NameChar = NameStartChar / "-" / "." / DIGIT / U+00B7 / U+0300-U+036F
49     # / U+203F-U+2040 ;; 1.1
50     # $xml_re{NameChar} = qr/[A-Za-z0-9._:-]|[^\x00-\x7F]/;
51 wakaba 1.4 #$xml_re{NameChar} = qr/\p{InXMLNameChar}/;
52 wakaba 1.1 # [5] Name = (Letter / "_" / ":") *NameChar ;; 1.0
53     # [5] Name = NameStartChar *NameChar ;; 1.1
54     # $xml_re{Name} = qr/(?:[A-Z_:]|[^\x00-\x7F])(?:$xml_re{NameChar})*/;
55     $xml_re{Name} = qr/\p{InXML_NameStartChar}\p{InXMLNameChar}*/;
56     # [6] Names = Name *(S Name) ;; 1.0 FE & 1.0 FE-errata (2000-09-27) & 1.0 SE
57     # [6] Names = Name *(%x20 Name) ;; 1.0 FE-errata (2000-04-09) & 1.0 SE-errata
58 wakaba 1.4 #$xml_re{Names} = qr/$xml_re{Name}(?:$xml_re{s}$xml_re{Name})*/s;
59 wakaba 1.1 # [7] Nmtoken = 1*NameChar
60     #$xml_re{Nmtoken} = qr/(?:$xml_re{NameChar})+/;
61 wakaba 1.4 #$xml_re{Nmtoken} = qr/\p{InXMLNameChar}+/;
62 wakaba 1.1 # [8] Nmtokens = Nmtoken *(S Nmtoken) ;; 1.0 FE & 1.0 FE-errata (2000-09-27) & 1.0 SE
63     # [8] Nmtokens = Nmtoken *(%x20 Nmtoken) ;; 1.0 FE-errata (2000-04-09) & 1.0 SE-errata
64 wakaba 1.4 #$xml_re{Nmtokens} = qr/$xml_re{Nmtoken}(?:$xml_re{s}$xml_re{Nmtoken})*/s;
65 wakaba 1.1 # [9] EntityValue = <"> *((Char - ("%" / "&" / <">)) / PEReference / Reference) <">
66     # / "'" *((Char - ("%" / "&" / "'")) / PEReference / Reference) "'"
67     # [10] AttValue = <"> *((Char - ("<" / "&" / <">)) / Reference) <">
68     # / "'" *((Char - ("<" / "&" / "'")) / Reference) "'"
69     $xml_re{__AttValue_simple} = qr/"[^"]*"|'[^']*'/s;
70     # [11] SystemLiteral = <"> *(Char - <">) <"> / "'" *(Char - "'") "'"
71 wakaba 1.4 #$xml_re{SystemLiteral} = qr/"[^"]*"|'[^']*'/;
72 wakaba 1.1 # [12] PublicLiteral = <"> *PubidChar <"> / "'" *(PubidChar - "'") "'"
73     # [13] PubidChar = %x20 / %x0D / %x0A / ALPHA / DIGIT / "-" / "'" / "(" / ")"
74     # / "+" / "," / "." / "/" / ":" / "=" / "?" / ";" / "!" / "*"
75     # / "#" / "@" / "$" / "_" / "%"
76     $xml_re{PubidChar} = qr[[\x0D\x0A\x20!\x24#%'()+*,./0-9:;=?\x40A-Z_a-z-]];
77     $xml_re{__non_PubidChar} = qr[[^\x0D\x0A\x20!\x24#%'()+*,./0-9:;=?\x40A-Z_a-z-]];
78 wakaba 1.4 #$xml_re{__PubidChar2} = qr[[\x0D\x0A\x20!\x24#%()+*,./0-9:;=?\x40A-Z_a-z-]];
79     #$xml_re{PublicLiteral} = qr/"(?:$xml_re{PubidChar})*"|'(?:$xml_re{__PubidChar2})*'/;
80 wakaba 1.1 # [14] CharData = *(Char - ("<" / "&")) - (*(Char - ("<" / "&")) "]]>" *(Char - ("<" / "&")))
81 wakaba 1.4 #$xml_re{CharData} = qr/(?:(?!\]\]>)[^<&])*/s;
82 wakaba 1.1 $xml_re{__CharDataP} = qr/(?:(?!\]\]>)[^<&])+/s;
83     # [15] Comment = "<!--" *((Char - "-") / ("-" (Char - "-")))) "-->"
84     $xml_re{Comment_M} = qr/<!--((?:(?!--).)*)-->/s;
85     # [16] PI = "<?" PITarget [S (*Char - (*Char "?>" *Char))] "?>"
86     $xml_re{PI_M} = qr/<\?($xml_re{Name})(?:$xml_re{s}((?:(?!\?>).)*))?\?>/s;
87 wakaba 1.4 $xml_re{_xml_PI_M} = qr/<\?xml(?:$xml_re{s}((?:(?!\?>).)*))?\?>/s;
88 wakaba 1.1 # [17] PITarget = Name - "xml"
89     # [18] CDSect = CDStart CData CDEnd
90     # [19] CDStart = '<![CDATA['
91     # [20] CDATA = (*Char - (*Char "]]>" *Char))
92     # [21] CDEnd = "]]>"
93     $xml_re{CDSect_M} = qr/<!\[CDATA\[((?:(?!\]\]>).)*)\]\]>/s;
94     # [22] prolog = [XMLDecl] *Misc [doctypedecl *Misc]
95     # [23] XMLDecl = '<?xml' VersionInfo [EncodingDecl] [SDDecl] [S] "?>"
96     # [24] VersionInfo = S 'version' Eq ("'" VersionNum "'" / <"> VersionNum <">)
97     # [25] Eq = [S] "=" [S]
98     # [26] VersionNum = 1*(ALPHA / DIGIT / "_" / "." / ":" / "-") ;; 1.0 FE & 1.0 SE
99     # [26] VersionNum = "1.0" ;; 1.0 SE-errata
100     # [26] VersionNum = "1.1" ;; 1.1
101     # [27] Misc = Comment / PI / S
102     # [28] doctypedecl = '<!DOCTYPE' S Name [S ExternalID] [S]
103     # ["[" *(markupdecl / PEReference / S) "]" [S]] ">" ;; 1.0 FE
104     # [28] doctypedecl = '<!DOCTYPE' S Name [S ExternalID] [S] ["[" *(markupdecl / DeclSep) "]" [S]] ">"
105     # ;; 1.0 FE-errata & 1.0 SE
106     # [28] doctypedecl = '<!DOCTYPE' S Name [S ExternalID] [S] ["[" intSubset "]" [S]] ">"
107     # ;; 1.0 SE-errata
108     $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;
109     # [28a] DeclSep = PEReference / S ;; 1.0 FE-errata & 1.0 SE
110     # [28b] intSubset = *(markupdecl / DeclSep) ;; 1.0 SE-errata
111     # [29] markupdecl = elementdecl / AttlistDecl / EntityDecl / NotationDecl / PI / Comment
112     # [30] extSubset = [TextDecl] extSubsetDecl
113     # [31] extSubsetDecl = *(markupdecl / conditionalSect / PEReference / S) ;; 1.0 FE
114     # [31] extSubsetDecl = *(markupdecl / conditionalSect / DeclSep) ;; 1.0 FE-errata & 1.0 SE
115     # [32] SDDecl = S 'standalone' Eq ("'" ('yes' / 'no') "'" / <"> ('yes' / 'no') <">)
116     # [33] LanguageID = Langcode *("-" Subcode) ;; 1.0 FE (removed by 1.0 SE)
117     # [34] Langcode = ISO639Cocde / IanaCode / UserCode ;; 1.0 FE (ditto)
118     # [35] ISO639Code = 2ALPHA ;; 1.0 FE (ditto)
119     # [36] IanaCode = "i-" 1*ALPHA ;; 1.0 FE (ditto)
120     # [37] UserCode = "x-" 1*ALPHA ;; 1.0 FE (ditto)
121     # [38] Subcode = 1*ALPHA ;; 1.0 FE (ditto)
122     # [39] element = EmptyElemTag / STag content ETag
123     # [40] STag = "<" Name *(S Attribute) [S] ">"
124     # [41] Attribute = Name Eq AttValue
125     $xml_re{Attribute} = qr/$xml_re{Name}(?:$xml_re{s})?=(?:$xml_re{s})?$xml_re{__AttValue_simple}/s;
126     $xml_re{Attribute_M} = qr/($xml_re{Name})(?:$xml_re{s})?=(?:$xml_re{s})?($xml_re{__AttValue_simple})/s;
127 wakaba 1.4 #$xml_re{STag} = qr/<$xml_re{Name}(?:$xml_re{s}$xml_re{Attribute})*(?:$xml_re{s})?>/s;
128 wakaba 1.1 # [42] ETag = "</" Name [S] ">"
129     $xml_re{ETag_M} = qr!</($xml_re{Name})(?:$xml_re{s})?>!s;
130     # [43] content = *(element / CharData / Reference / CDSect / PI / Comment) ;; 1.0 FE
131     # [43] content = [CharData] *((element / Reference / CDSect / PI / Comment) [CharData])
132     # ;; 1.0 FE-errata & 1.0 SE
133     # [44] EmptyElemTag = "<" Name *(S Attribute) [S] "/>"
134 wakaba 1.4 #$xml_re{__STag_or_EmptyElemTag} = qr!<$xml_re{Name}(?:$xml_re{s}$xml_re{Attribute})*(?:$xml_re{s})?/?>!s;
135 wakaba 1.1 $xml_re{__STag_or_EmptyElemTag_simple} = qr!<$xml_re{ame}(?:$xml_re{s}|$xml_re{Name}|$xml_re{__AttValue_simple}|=)*/?>!s;
136     # [45] elementdecl = '<!ELEMENT' S Name S contentspec [S] ">"
137     # [46] contentspec = 'EMPTY' / 'ANY' / Mixed / children
138 wakaba 1.4 #$xml_re{__contentspec_simple} = qr/(?:$xml_re{Name}|\#PCDATA|[()|,?*+]|$xml_re{s})/s;
139 wakaba 1.1 # [47] children = (choice / seq) ["?" / "*" / "+"]
140     # [48] cp = (Name / choice / seq) ["?" / "*" / "+"]
141     # [49] choice = "(" [S] cp *([S] "|" [S] cp) [S] ")" ;; 1.0 FE
142     # [49] choice = "(" [S] cp 1*([S] "|" [S] cp) [S] ")" ;; 1.0 FE-errata & 1.0 SE
143     # [50] seq = "(" [S] cp *([S] "," [S] cp) [S] ")"
144     # [51] Mixed = "(" [S] '#PCDATA' *([S] "|" [S] Name) [S] ")*"
145     # / "(" [S] '#PCDATA' [S] ")"
146     #$xml_re{seq} = qr/\($xml_re{cp}(?:(?:$xml_re{s})?,(?:$xml_re{s})?$xml_re{cp})*(?:$xml_re{s})?\)/;
147     #$xml_re{cp} = qr/(?:$xml_re{Name}|$xml_re{choice}|$xml_re{seq})[?*+]/;
148     #$xml_re{choice} = qr/\($xml_re{cp}(?:(?:$xml_re{s})?\|(?:$xml_re{s})?$xml_re{cp})+(?:$xml_re{s})?\)/;
149     #$xml_re{children} = qr/(?:$xml_re{choice}|$xml_re{seq})[?*+]/;
150     #$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})?\))/;
151     #$xml_re{contentspec} = qr/(?:EMPTY|ANY|$xml_re{Mixed}|$xml_re{children})/;
152     # [52] AttlistDecl = '<!ATTLIST' S Name *AttDef [S] ">"
153     # [53] AttDef = S Name S AttType S DefaultDecl
154     # [54] AttType = StringType / TokenizedType / EnumeratedType
155     # [55] StringType = 'CDATA'
156     # [56] TokenizedType = 'ID' / 'IDREF' / 'IDREFS' / 'ENTITY' / 'ENTITIES' / 'NMTOKEN' / 'NMTOKENS'
157     # [57] EnumeratedType = NotationType / Enumeration
158     # [58] NotationType = 'NOTATION' S "(" [S] Name *([S] "|" [S] Name) [S] ")"
159     # [59] Enumeration = "(" [S] Nmtoken *([S] "|" [S] Nmtoken) [S} ")"
160     # [60] DefaultDecl = '#REQUIRED' / '#IMPLIED' / ['#FIXED' S] AttValue
161     # [61] conditionalSect = includeSect / ignoreSect
162     # [62] includeSect = "<![" [S} 'INCLUDE' [S] "[" extSubsetDecl "]]>"
163     # [63] ignoreSect = "<![" [S] 'IGNORE' [S] "[" *ignoreSectContents "]]>"
164     # [64] ignoreSectContents = Ignore *("<![" ignoreSectContents "]]>" Ignore)
165     # [65] Ignore = *Char - (*Char ("<![" / "]]>") *Char)
166     # [66] CharRef = '&#' 1*DIGIT ";" / '&#x' 1*HEXDIGIT ";"
167     $xml_re{CharRef} = qr/&#[0-9]+;|&#x[0-9A-Fa-f]+;/;
168     # [67] Reference = EntityRef / CharRef
169     # [68] EntityRef = "&" Name ";"
170     $xml_re{EntityRef} = qr/&$xml_re{Name};/;
171     $xml_re{EntityRef_M} = qr/&($xml_re{Name});/;
172     $xml_re{Reference} = qr/$xml_re{EntityRef}|$xml_re{CharRef}/;
173 wakaba 1.4 #$xml_re{AttValue} = qr/"(?:$xml_re{Reference}|[^&<"])*"|'(?:$xml_re{Reference}|[^&<'])*'/s;
174 wakaba 1.1 # [69] PEReference = "%" Name ";"
175     $xml_re{PEReference} = qr/%(?:$xml_re{Name});/;
176     $xml_re{PEReference_M} = qr/%($xml_re{Name});/;
177     $xml_re{__elementdecl_simple} = qr/<!ELEMENT(?:$xml_re{s}|$xml_re{PEReference}|$xml_re{Name}|\#PCDATA|[()|,?*+])+>/s;
178     $xml_re{__AttlistDecl_simple} = qr/<!ATTLIST(?:$xml_re{PEReference}|$xml_re{Name}|\#$xml_re{Name}|$xml_re{s}|$xml_re{__AttValue_simple})*>/s;
179     # [70] EntityDecl = GEDecl / PEDecl
180 wakaba 1.2 $xml_re{__EntityDecl_simple} = qr/<!ENTITY(?:$xml_re{__AttValue_simple}|[^"'>])*>/s;
181 wakaba 1.1 # [71] GEDecl = '<!ENTITY' S Name S EntityDef [S] ">"
182     # [72] PEDecl = '<!ENTITY' S "%" S Name S PEDef [S] ">"
183     # [73] EntityDef = EntityValue / ExternalID [NDataDecl]
184     # [74] PEDef = EntityValue / ExternalID
185     # [75] ExternalID = 'SYSTEM' S SystemLiteral / 'PUBLIC' S PublicLiteral S SystemLiteral
186     # [76] NDataDecl = S 'NDATA' S Name
187     # [77] TextDecl = '?xml' [VersionInfo] EncodingDecl [S] "?>"
188     # [78] extParsedEnt = [TextDecl] content
189     # [79] extPE ;; 1.0 FE (removed by errata)
190     # [80] EncodingDecl = S 'encoding' Eq (<"> EncName <"> / "'" EncName "'")
191     # [81] EncName = ALPHA *(ALPHA / DIGIT / "." / "_" / "-")
192     # [82] NotationDecl = '<!NOTATION' S Name S (ExternalID / PublicID) [S] ">"
193 wakaba 1.2 $xml_re{__NotationDecl_simple} = qr/<!NOTATION(?:$xml_re{__AttValue_simple}|[^"'>])*>/s;
194 wakaba 1.1 # [83] PublicID = 'PUBLIC' S PubidLiteral
195     # [84] Letter = BaseChar / Ideographic
196     # [85] Basechar = ...
197     # [86] Ideographic = ...
198     # [87] CombiningChar = ...
199     # [88] Digit = ...
200     # [89] Extender = U+00B7 / U+02D0 / U+02D1 / U+0387 / U+0640 / U+0E46 / U+0EC6
201     # / U+3005 / U+3031-U+3035 / U+309D / U+309E / U+30FC-U+30FE
202     ## [84]-[89] removed by 1.1
203    
204     # [XMLNames]
205     # [1] NSAttName = PrefixedAttName / DefaultAttName
206     # [2] PrefixedAttName = 'xmlns:' NCName
207     # [3] DefaultAttName = 'xmlns'
208     # [4] NCName = (Letter / "_") *NCNameChar ;; 1.0
209     # [4] NCName = NCNameStartChar *NCNameChar ;; 1.1
210     # [5] NCNameChar = Letter / Digit / "." / "-" / "_" / CombiningChar / Extender ;; 1.0
211     # [5] NCNameChar = NameChar - ":" ;; 1.1
212     # [5a] NCNameStartChar = NameStartChar - ":" ;; 1.1
213     # [6] QName = [Prefix ":"] LocalPart ;; 1.0
214     # [6] QName = PrefixedName / UnprefixedName ;; 1.1
215     # [6a] PrefixedName = Prefix ":" LocalPart ;; 1.1
216     # [6b] UnprefixedName = LocalPart ;; 1.1
217     # [7] Prefix = NCName
218     # [8] LocalPart = NCName
219     # [9] STag = "<" QName *(S Attribute) [S] ">"
220 wakaba 1.4 #$xml_re{__NCSTag} = qr/<$xml_re{QName}(?:$xml_re{s}$xml_re{Attribute})*(?:$xml_re{s})?>/s;
221 wakaba 1.1 # [10] ETag = "</" QName [S] ">"
222 wakaba 1.4 #$xml_re{__NCETag} = qr!</$xml_re{QName}(?:$xml_re{s})?>!s;
223 wakaba 1.1 # [11] EmptyElemTag = "<" QName *(S Attribute) [S] "/>"
224     # [12] Attribute = NSAttName Eq AttValue / QName Eq AttValue
225     # [13] doctypedecl = '<!DOCTYPE' S QName [S ExternalID] [S]
226     # ["[" *(markupdecl / PEReference / S) "]" [S]] ">"
227     # [14] elementdecl = '<!ELEMENT' S QName S contentspec [S] ">"
228     # [15] cp = (QName / choice / sep) ["?" / "*" / "+"]
229     # [16] Mixed = "(" [S] '#PCDATA' *([S] "|" [S] QName) [S] ")*" / "(" [S] '#PCDATA' [S] ")"
230     # [17] AttlistDecl = '<!ATTLIST' S QName *AttDef [S] ">"
231     # [18] AttDef = S (QName / NSAttName) S AttType S DefaultDecl
232     # [19] Name = NameStartChar *NameChar ;; 1.1 draft
233     # [20] NameChar = {XML1.1}.NameChar ;; 1.1 draft
234     # [21] NameStartChar = {XML1.1}.NameStartChar ;; 1.1 draft
235    
236    
237 wakaba 1.4 sub new ($;%) {
238     my $self = bless {}, shift;
239     $self->{_constructor_option} = {@_};
240     $self;
241 wakaba 1.1 }
242    
243     sub parse_text ($$;$) {
244     my $self = shift;
245     my $s = shift;
246     my $o = shift || {line => 0, pos => 0, entity_type => ''};
247     my $r = SuikaWiki::Markup::XML->new (type => '#document');
248 wakaba 1.4 for (qw/flag/) {
249     $r->{$_} = $self->{_constructor_option}->{$_} || {};
250     }
251     $r->base_uri ($self->{_constructor_option}->{uri}->{base})
252     if defined $self->{_constructor_option}->{uri}->{base};
253 wakaba 1.1 my $c = $r;
254 wakaba 1.2 ## NOTE: Even if there are more than one non-XML-Char, error is raised only one time.
255     $self->_warn_char_val ($self->_make_clone_of ($o), $s);
256     if ($s =~ s/^$xml_re{PI_M}//s && $1 eq 'xml') { # <?xml?>
257     my ($data, $all) = ($2, $&);
258     _count_lp ('<?xml', $o);
259     if (length ($data)) {
260     $self->_parse_xml_declaration ($c->append_new_node (type => '#pi', local_name => 'xml'), $data, $o);
261     } else {
262     $self->_raise_error ($o, type => 'SYNTAX_XML_DECLARE_NO_ATTR');
263     }
264     _count_lp ('?>', $o);
265 wakaba 1.1 }
266 wakaba 1.2 while ($s) {
267     if ($s =~ /^$xml_re{__STag_or_EmptyElemTag_simple}/s) { # <element/>
268     $o->{entity_type} ||= 'document_entity';
269     if ($o->{entity_type} eq 'document_entity'
270     || $o->{entity_type} eq 'external_general_parsed_entity') {
271     $self->_parse_element_content ($c, \$s, $o);
272     } else {
273 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'SYNTAX_INVALID_POSITION', t => $&);
274 wakaba 1.1 }
275 wakaba 1.2 } elsif ($s =~ s/^$xml_re{s}//s) { # s
276     $c->append_text ($&);
277     _count_lp ($&, $o);
278     } elsif ($s =~ s/^$xml_re{__doctypedecl_start_simple_M}//s) { # <!DOCTYPE>
279     my ($d, $name, $extid) = ($1, $2, $3);
280     $o->{entity_type} ||= 'document_entity';
281     if ($o->{entity_type} ne 'document_entity') {
282 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'SYNTAX_INVALID_POSITION', t => '<!DOCTYPE');
283 wakaba 1.1 }
284     my $D = $c->append_new_node (type => '#declaration', namespace_uri => $NS{SGML}.'doctype');
285     $D->flag (p_o_start => $self->_make_clone_of ($o));
286     $D->set_attribute (qname => $name);
287     _count_lp ($d.$name, $o);
288     if ($extid =~ s/^$xml_re{s}SYSTEM$xml_re{s}//s) {
289     $D->set_attribute (SYSTEM => substr ($extid, 1, length ($extid) - 2));
290     _count_lp ($extid, $o);
291     } elsif ($extid =~ s/^($xml_re{s}PUBLIC$xml_re{s})($xml_re{__AttValue_simple})//s) {
292     _count_lp ($1.'"', $o);
293     my $pubid = substr ($2, 1, length ($2) - 2);
294     if ($pubid =~ /^($xml_re{PubidChar})*($xml_re{__non_PubidChar})/s) {
295     _count_lp ($1, $o);
296 wakaba 1.4 #_raise_fatal_error ($o, desc => 'INVALID_PUBID_CHAR', t => $2);
297 wakaba 1.1 } else {
298     $D->set_attribute (PUBLIC => $pubid);
299     _count_lp ($pubid.'"'.$extid, $o);
300     }
301     $extid =~ s/^$xml_re{s}//s;
302     $D->set_attribute (SYSTEM => substr ($extid, 1, length ($extid) - 2));
303     }
304     _count_lp ($1, $o) if $s =~ s/^($xml_re{s})//s;
305     if ($s =~ s/^\[//s) {
306     _count_lp ('[', $o);
307 wakaba 1.3 $self->_parse_dtd ($D, \$s, $o, return_with_dsc => 1, validate_ndata => 1);
308 wakaba 1.1 } elsif ($s =~ s/^>//s) {
309     _count_lp ('>', $o);
310     } else {
311     _raise_fatal_error ($o, desc => 'INVALID_DECLARE_SYNTAX', t => substr ($s, 0, 1));
312     substr ($s, 0, 1) = '';
313     }
314 wakaba 1.2 $self->_parse_element_content ($c, \$s, $o);
315     } elsif ($s =~ s/^$xml_re{PI_M}//s) { # <?pi?>
316     my ($target, $data, $all) = ($1, $2, $&);
317     if ($target eq 'xml') {
318 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'SYNTAX_XML_DECLARE_POSITION');
319 wakaba 1.1 } else {
320 wakaba 1.2 $c->append_new_node (type => '#pi', local_name => $1, value => $2);
321     _count_lp ($all, $o);
322 wakaba 1.1 }
323 wakaba 1.2 } elsif ($s =~ s/^$xml_re{Comment_M}//s) { # <!-- -->
324     $c->append_new_node (type => '#comment', value => $1);
325     _count_lp ($&, $o);
326     } elsif ($s =~ /^<![A-Z]/) { # <!DECLARATION>
327     $o->{entity_type} ||= 'dtd_external_subset';
328 wakaba 1.3 $self->_parse_dtd ($c, \$s, $o, validate_ndata => 1);
329 wakaba 1.2 ## TODO: ext.general parsed entity
330 wakaba 1.1 } else {
331 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'SYNTAX_INVALID_CHAR', t => substr ($s, 0, 1));
332 wakaba 1.2 substr ($s, 0, 1) = '';
333 wakaba 1.1 }
334     }
335     wantarray ? ($r, $o) : $r;
336     }
337    
338 wakaba 1.4 ## [internal] URI escaping unsafe characters
339     ## $s = $parser->_uri_escape ($s)
340 wakaba 1.1 sub _uri_escape ($$) {
341     shift;
342     my $s = shift; ## TODO: support utf8 flag
343     $s =~ s/([^0-9A-Za-z_.-])/sprintf '%%%02X', ord $1/ge;
344     $s;
345     }
346    
347 wakaba 1.4 ## [internal] Duplication of HASH reference
348     ## $ref = $parser->_make_clone_of ($ref)
349 wakaba 1.2 sub _make_clone_of ($$;%) {
350     my ($self, $mother, %o) = @_;
351 wakaba 1.1 if (ref $mother eq 'HASH') {
352     my $child = {};
353 wakaba 1.2 $o{m_vs_c}->{$mother} = $child;
354 wakaba 1.1 for (keys %$mother) {
355 wakaba 1.2 if (ref $mother->{$_}) {
356     $child->{$_} = $o{m_vs_c}->{$mother->{$_}} || $self->_make_clone_of ($mother->{$_});
357     } else {
358     $child->{$_} = $mother->{$_};
359     }
360 wakaba 1.1 }
361     return $child;
362     } else {
363     ## BUG: not supported
364     }
365     }
366    
367 wakaba 1.4 ## [internal] Count line/position
368     ## _count_lp ($string, $o)
369 wakaba 1.1 sub _count_lp ($$) {
370     my ($s, $o) = @_;
371     $s =~ s/[^\x0A\x0D]*(?:\x0D\x0A?|\x0A)/$o->{line}++;$o->{pos}=0;''/ges;
372     $o->{pos} += length $s;
373     }
374 wakaba 1.4
375     ## [internal] Split QName into prefix and NCName
376     ## ($prefix or undef, $NCName) = $parser->_ns_parse_qname ($QName)
377     sub _ns_parse_qname ($$) {
378     shift;
379 wakaba 1.1 my $qname = shift;
380     if ($qname =~ /:/) {
381     return split /:/, $qname, 2;
382     } else {
383     return (undef, $qname);
384     }
385     }
386 wakaba 1.2
387     sub _parse_element_content ($$\$$) {
388     my ($self, $c, $s, $o) = @_;
389     my $c_initial = overload::StrVal ($c);
390     my $entMan;
391     while ($$s) {
392     if ($$s =~ s/^($xml_re{__STag_or_EmptyElemTag_simple})//s) {
393     my ($stag) = ($1);
394     if ($c->node_type eq '#document'
395     && $self->_is_brother_of_root_element ($c)) {
396 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'SYNTAX_DATA_OUT_OF_ROOT_ELEMENT', t => $stag);
397 wakaba 1.2 }
398     ## Parse element type name
399     $stag =~ s/^<($xml_re{Name})//s;
400 wakaba 1.4 my ($prefix, $lname) = $self->_ns_parse_qname ($1); ## TODO: invalid qname valid name
401 wakaba 1.2 $c = $c->append_new_node (local_name => $lname);
402     $c->flag (p_original_qname => $1);
403     $c->flag (p_o_start => $self->_make_clone_of ($o));
404     _count_lp ($&, $o);
405     ## Parse attributes
406     $stag =~ s/>$//;
407     my $etag = 0;
408     if ($stag =~ s!/$!!) { # empty element
409     $c->option (use_EmptyElemTag => 1);
410     $etag = 1;
411     }
412     $self->_parse_attribute_spec_list ($c, $stag, $o) if $stag;
413     _count_lp (($etag ? '/>' : '>'), $o);
414     ## Parse element type name (namespace)
415     my $uri = $c->defined_namespace_prefix ($prefix || '');
416     if (defined $uri) {
417     $c->namespace_uri ($uri);
418     } elsif (!$prefix) { ## Default NS
419     #$c->namespace_uri ('');
420     $c->define_new_namespace ('' => '');
421     } else {
422     my $o_etypename = $self->_make_clone_of ($c->flag ('p_o_start'));
423     $o_etypename->{pos}++; # '<'
424     $self->_raise_error ($o_etypename, type => 'NC_PREFIX_NOT_DEFINED', t => $prefix);
425     my $uri = $NS{internal_ns_invalid}.$self->_uri_escape ($prefix);
426     $c->namespace_uri ($uri);
427     $c->define_new_namespace ($prefix => $uri);
428     }
429     ## Ending the element if EmptyElemTag
430     if ($etag) {
431     $c = $c->{parent};
432     }
433     } elsif ($$s =~ s/^$xml_re{ETag_M}//s) {
434     my $ename = $1;
435     if ($ename eq $c->flag ('p_original_qname') || $ename eq $c->qname) {
436     $c = $c->{parent};
437     } else { ## Element type name does not match
438     my $o_etn = $self->_make_clone_of ($o);
439     $o_etn->{pos} += 2;
440 wakaba 1.4 $self->_raise_error ($o_etn, c => $c, type => 'WFC_ELEMENT_TYPE_MATCH', t => [$ename, $c->qname]);
441 wakaba 1.2 }
442     _count_lp ($&, $o);
443     } elsif (($c->node_type eq '#document') && ($$s =~ s/^$xml_re{s}//s)) {
444     $c->append_text ($&);
445     _count_lp ($&, $o);
446     } elsif ($$s =~ s/^$xml_re{__CharDataP}//s) {
447     if ($c->node_type eq '#document') {
448 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'SYNTAX_DATA_OUT_OF_ROOT_ELEMENT', t => $&);
449 wakaba 1.2 }
450     $c->append_text ($&);
451     _count_lp ($&, $o);
452     } elsif ($$s =~ s/^($xml_re{Reference})//s) { ## &foo; | &#1234; | &#x12AB;
453     if ($c->node_type eq '#document') {
454     if ($self->_is_brother_of_root_element ($c)) {
455 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'SYNTAX_DATA_OUT_OF_ROOT_ELEMENT', t => $1);
456 wakaba 1.2 }
457     }
458     my $entity_ref = $1;
459     my $eref = $self->_parse_reference ($c, $entity_ref, $o);
460     if ($eref->{namespace_uri} !~ 'char') {
461     $entMan ||= $c->_get_entity_manager;
462 wakaba 1.4 my $entity = $entMan->get_entity ($eref);
463 wakaba 1.2 if (!$entity && {qw/&lt; 1 &gt; 1 &amp; 1 &quot; 1 &apos; 1/}->{$entity_ref}) {
464     $self->_raise_error ($o, c => $c, type => 'WARN_PREDEFINED_ENTITY_NOT_DECLARED',
465     t => $entity_ref);
466     $entity = $entMan->get_entity ($eref);
467     }
468     if (!$entity) {
469     $self->_raise_error ($o,
470     type => ($entMan->is_standalone_document_1?'WF':'V').'C_ENTITY_DECLARED',
471     t => $entity_ref);
472     } else {
473     my $o2 = $self->_make_clone_of ($o);
474     if ($o2->{__entities}->{$entity_ref}) {
475     $self->_raise_error ($o, type => 'WFC_NO_RECURSION', t => $entity_ref);
476     } else {
477 wakaba 1.4 $o2->{entity} = $entity_ref;
478 wakaba 1.2 my $entity_value = $entity->get_attribute ('value');
479     if (ref $entity_value) {
480     $o2->{__entities}->{$entity_ref} = 1;
481 wakaba 1.4 $o2->{line} = 0; $o2->{pos} = 0;
482 wakaba 1.2 my $ev = $entity_value->_entity_parameter_literal_value;
483     $self->_parse_element_content ($eref, \$ev, $o2);
484     $eref->flag (smxp__ref_expanded => 1);
485 wakaba 1.4 } else { ## External entity
486     $o2->{entity_type} = 'external_general_parsed_entity';
487     my $ext_ent = $entMan->get_external_entity ($self, $entity, $o2);
488     if ($ext_ent->{NDATA}) { ## non-parsed entity
489     $self->_raise_error ($o, type => 'WFC_PARSED_ENTITY', c => $entity, t => $entity_ref);
490     } elsif ($ext_ent->{error}->{no_data}) { ## parsed entity but can't be retrived
491     $self->_raise_error ($o, type => 'ERR_EXT_ENTITY_NOT_FOUND', c => $entity,
492     t => [$entity_ref, $o2->{uri},
493     $ext_ent->{error}->{reason_text}]);
494     } else { ## parsed entity
495     $o2->{__entities}->{$entity_ref} = 1;
496     $eref->base_uri ($ext_ent->{base_uri});
497     my $ev = $ext_ent->{text};
498     $self->_parse_element_content ($eref, \$ev, $o2);
499     $eref->flag (smxp__ref_expanded => 1);
500     }
501 wakaba 1.2 }
502     }
503     }
504     } # if &foo;
505     } elsif ($$s =~ s/^$xml_re{CDSect_M}//s) {
506     if ($c->node_type eq '#document') {
507     if ($self->_is_brother_of_root_element ($c)) {
508 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'SYNTAX_DATA_OUT_OF_ROOT_ELEMENT', t => '<![');
509 wakaba 1.2 }
510     }
511     $c->append_new_node (type => '#section', namespace_uri => $NS{SGML}.'section:cdata', value => $1);
512     _count_lp ($&, $o);
513     } elsif ($$s =~ s/^$xml_re{Comment_M}//s) {
514     $c->append_new_node (type => '#comment', value => $1);
515     _count_lp ($&, $o);
516     } elsif ($$s =~ s/^$xml_re{PI_M}//s) {
517     my ($target, $data, $all) = ($1, $2, $&);
518     if ($target eq 'xml') {
519     if ($c->node_type eq '#document' && $c->count == 0) {
520     _count_lp ('<?xml', $o);
521     if (length ($data)) {
522     $self->_parse_xml_declaration ($c->append_new_node (type => '#pi', local_name => 'xml'), $data, $o);
523     } else {
524 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'SYNTAX_XML_DECLARE_NO_ATTR');
525 wakaba 1.2 }
526     _count_lp ('?>', $o);
527     } else {
528 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'SYNTAX_XML_DECLARE_POSITION');
529 wakaba 1.2 }
530     } else {
531     $c->append_new_node (type => '#pi', local_name => $1, value => $2);
532     _count_lp ($all, $o);
533     }
534     } else {
535 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'SYNTAX_INVALID_CHAR', t => substr ($$s, 0, 1));
536 wakaba 1.2 substr ($$s, 0, 1) = '';
537     }
538     } # while $s
539     while ($c_initial ne overload::StrVal ($c)) {
540     if (ref $c->{parent}) {
541     if ($c->node_type eq '#element') {
542     $self->_raise_error ($o, type => 'SYNTAX_END_TAG_NOT_FOUND', t => $c);
543     }
544     $c = $c->{parent};
545     } else {
546     last;
547     }
548     }
549     }
550    
551     sub _parse_dtd ($$\$$;%) {
552     my ($self, $c, $s, $o, %opt) = @_;
553     my $c_initial = overload::StrVal ($c);
554     my $entMan;
555     while ($$s) {
556 wakaba 1.4 if ($$s =~ s/^($xml_re{PEReference_M})//s) {
557     my ($ref, $ename) = ($1, $2);
558 wakaba 1.2 if (index ($ename, ':') > -1) {
559 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'NS_SYNTAX_NAME_IS_NCNAME', t => $ref);
560 wakaba 1.2 }
561     $entMan ||= $c->_get_entity_manager;
562     my $entity = $entMan->get_entity ($ename, namespace_uri => $NS{SGML}.'entity:parameter');
563     my $eref = $c->append_new_node (type => '#reference', local_name => $ename,
564     namespace_uri => $NS{SGML}.'entity:parameter');
565     if (!$entity) { ## TODO: if internal subset, flag
566 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'VC_ENTITY_DECLARED', t => $ref);
567 wakaba 1.2 } else {
568     my $o2 = $self->_make_clone_of ($o);
569 wakaba 1.4 if ($o2->{__entities}->{$ref}) {
570     $self->_raise_error ($o, c => $c, type => 'WFC_NO_RECURSION', t => $ref);
571 wakaba 1.2 } else {
572 wakaba 1.4 $o2->{entity} = $ref;
573 wakaba 1.2 my $entity_value = $entity->get_attribute ('value');
574 wakaba 1.4 if (ref $entity_value) { ## Internal entity
575     $o2->{__entities}->{$ref} = 1;
576     $o2->{line} = 0; $o2->{pos} = 0;
577     my $ev = $entity_value->_entity_parameter_literal_value;
578 wakaba 1.2 $self->_parse_dtd ($eref, \$ev, $o2);
579     $eref->flag (smxp__ref_expanded => 1);
580 wakaba 1.4 } else { ## External entity
581     $o2->{entity_type} = 'external_parameter_entity';
582     my $ext_ent = $entMan->get_external_entity ($self, $entity, $o2);
583     if ($ext_ent->{NDATA}) { ## non-parsed entity
584     $self->_raise_error ($o, type => 'WFC_PARSED_ENTITY', c => $entity, t => $ref);
585     } elsif ($ext_ent->{error}->{no_data}) { ## parsed entity but can't be retrived
586     $self->_raise_error ($o, type => 'ERR_EXT_ENTITY_NOT_FOUND', c => $entity,
587     t => [$ref, $o2->{uri}, $ext_ent->{error}->{reason_text}]);
588     } else { ## parsed entity
589     $o2->{__entities}->{$ref} = 1;
590     $eref->base_uri ($ext_ent->{base_uri});
591     my $ev = $ext_ent->{text};
592     $self->_parse_dtd ($eref, \$ev, $o2);
593     $eref->flag (smxp__ref_expanded => 1);
594     }
595 wakaba 1.2 }
596     } # not recursive
597     } # entity defined
598 wakaba 1.4 _count_lp ($ref, $o);
599 wakaba 1.2 } elsif ($$s =~ s/^$xml_re{s}//s) {
600     $c->append_text ($&);
601     _count_lp ($&, $o);
602     } elsif ($$s =~ s/^$xml_re{Comment_M}//s) {
603     $c->append_new_node (type => '#comment', value => $1);
604     _count_lp ($&, $o);
605     } elsif ($$s =~ s/^($xml_re{__EntityDecl_simple})//s) {
606     $self->_parse_entity_declaration ($1, $c, $o);
607     } elsif ($$s =~ s/^($xml_re{__elementdecl_simple})//s) {
608     $self->_parse_element_declaration ($1, $c, $o);
609     } elsif ($$s =~ s/^($xml_re{__AttlistDecl_simple})//s) {
610     $self->_parse_attlist_declaration ($1, $c, $o);
611     } elsif ($opt{return_with_dsc} && $$s =~ s/^\](?:$xml_re{s})?>//s) {
612     _count_lp ($&, $o);
613     $c = $c->{parent};
614 wakaba 1.3 last;
615 wakaba 1.2 } elsif ($$s =~ s/^($xml_re{__NotationDecl_simple})//s) {
616     $self->_parse_entity_declaration ($1, $c, $o);
617     } elsif ($$s =~ s/^$xml_re{PI_M}//s) {
618     my ($target, $data, $all) = ($1, $2, $&);
619     if ($target eq 'xml') {
620     if ($c->node_type eq '#document' && $c->count == 0) {
621     _count_lp ('<?xml', $o);
622     if (length ($data)) {
623     $self->_parse_xml_declaration ($c->append_new_node (type => '#pi', local_name => 'xml'), $data, $o);
624     } else {
625     $self->_raise_error ($o, type => 'SYNTAX_XML_DECLARE_NO_ATTR');
626     }
627     _count_lp ('?>', $o);
628     } else {
629 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'SYNTAX_XML_DECLARE_POSITION');
630 wakaba 1.2 }
631     } else {
632 wakaba 1.4 my $pi = $c->append_new_node (type => '#pi', local_name => $1, value => $2);
633 wakaba 1.2 _count_lp ($all, $o);
634     }
635     } else {
636 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'SYNTAX_INVALID_CHAR', t => substr ($$s, 0, 1));
637 wakaba 1.2 substr ($$s, 0, 1) = '';
638 wakaba 1.3 _count_lp (' ', $o);
639 wakaba 1.2 ## TODO: markup section
640     }
641     } # while $$s
642     while ($c_initial ne overload::StrVal ($c)) {
643     if (ref $c->{parent}) {
644     $self->_raise_error ($o, type => 'SYNTAX_END_OF_MARKUP_NOT_FOUND', t => $c);
645     $c = $c->{parent};
646     } else {
647     last;
648     }
649 wakaba 1.3 } # while
650     if ($opt{validate_ndata}) {
651     my $l = [];
652     my $entMan = $c->_get_entity_manager;
653     $entMan->get_entities ($l, namespace_uri => $NS{SGML}.'entity');
654     my %defined;
655     for my $ent (@$l) {
656     for ($ent->get_attribute ('NDATA')) {
657     if (ref $_) {
658     my $nname = $_->inner_text;
659     if ($defined{$nname} > 0
660     || $entMan->get_entity ($nname, namespace_uri => $NS{SGML}.'notation')) {
661     $defined{$nname} = 1;
662     } else {
663     $self->_raise_error ($_->flag ('smxp__src_pos'), type => 'VC_NOTATION_DECLARED',
664     t => $nname, c => $_);
665     $defined{$nname} = -1;
666     }
667     }} # NDATA exist
668     }
669 wakaba 1.2 }
670     }
671    
672    
673 wakaba 1.1 sub _parse_attribute_spec_list ($$$$) {
674     my ($self, $c, $attrs, $o) = @_;
675     my @attrs;
676     my (%defined_attr, %defined_ns_attr);
677     my $no_s = 0;
678     while ($attrs) {
679     if (!$no_s && $attrs =~ s/^$xml_re{Attribute_M}//s) {
680     my ($qname, $qvalue) = ($1, $2);
681 wakaba 1.4 my ($prefix, $name) = $self->_ns_parse_qname ($qname);
682 wakaba 1.1 push @attrs, {prefix => $prefix, lname => $name, qvalue => $qvalue, qname => $qname,
683     o => {line => $o->{line}, pos => ($o->{pos} + length ($&) - length ($qvalue))},
684     o_attr_start => {line => $o->{line}, pos => $o->{pos}}};
685     _count_lp ($&, $o);
686     $no_s = 1;
687     } elsif ($attrs =~ s/^($xml_re{s})//s) {
688     _count_lp ($1, $o);
689     $no_s = 0;
690     } else {
691     $self->_raise_error ($o, type => 'SYNTAX_INVALID_CHAR', t => substr ($attrs, 0, 1));
692     substr ($attrs, 0, 1) = '';
693     $no_s = 1;
694     }
695     }
696     for (grep {($_->{prefix} eq 'xmlns') || (!$_->{prefix} && ($_->{lname} eq 'xmlns'))} @attrs) {
697     my $value = SuikaWiki::Markup::XML->new (type => '#text');
698 wakaba 1.2 _count_lp ('"', $_->{o});
699     my $av = substr ($_->{qvalue}, 1, length ($_->{qvalue}) - 2);
700     $self->_parse_attribute_value ($value, \$av, $_->{o});
701     _count_lp ('"', $_->{o});
702 wakaba 1.1 if (!$defined_attr{$_->{qname}}) {
703     if ($_->{prefix} eq 'xmlns') {
704     $c->define_new_namespace ($_->{lname} => $value); # BUG: Reference
705     } else {
706     $c->define_new_namespace ('' => $value); # BUG: Reference
707     }
708     $defined_attr{$_->{qname}} = 1;
709     } else { ## Already defined
710     $self->_raise_error ($_->{o_attr_start}, type => 'WFC_UNIQUE_ATT_SPEC', t => $_->{qname});
711     my $attr;
712     if ($_->{prefix} eq 'xmlns') {
713     $attr = $c->set_attribute ($_->{lname} => '', namespace_uri => $NS{internal_attr_duplicate}.'xmlns');
714     } else { ## BUG: xmlns="" (1.1)
715     $attr = $c->set_attribute (xmlns => '', namespace_uri => $NS{internal_attr_duplicate});
716     }
717     $attr->append_node ($value);
718     }
719     }
720     for (grep {($_->{prefix} ne 'xmlns') && !(!$_->{prefix} && ($_->{lname} eq 'xmlns'))} @attrs) {
721     my $attr;
722     my $uri; $uri = $c->defined_namespace_prefix ($_->{prefix}) if $_->{prefix};
723     if ($defined_attr{$_->{qname}}) { ## Already-defined-attr is found
724     $self->_raise_error ($_->{o_attr_start}, type => 'WFC_UNIQUE_ATT_SPEC', t => $_->{qname});
725     $uri = $NS{internal_attr_duplicate} . $defined_attr{$_->{qname}};
726     $_->{prefix} = 'dup.' . $defined_attr{$_->{qname}} . ($_->{prefix} ? '.'.$_->{prefix}:'');
727     $c->define_new_namespace ($_->{prefix} => $uri);
728     $defined_attr{$_->{qname}}++;
729     } elsif (defined $uri && $defined_ns_attr{$_->{lname}.':'.$uri}) {
730     ## ns:attr="a" ns2:attr="b" xmlns:ns="ns" xmlns:ns2="ns"
731     $self->_raise_error ($_->{o_attr_start}, type => 'NC_unique_att_spec', t => $_->{qname});
732     my $i = $defined_ns_attr{$_->{lname}.':'.$uri}++;
733     $uri = $NS{internal_attr_duplicate} . 'ns.' . $i;
734     $_->{prefix} = 'dup.ns.' . $i . ($_->{prefix} ? '.'.$_->{prefix}:'');
735     $c->define_new_namespace ($_->{prefix} => $uri);
736     } else {
737     $defined_attr{$_->{qname}} = 1;
738     $defined_ns_attr{$_->{lname}.':'.$uri} = 1;
739     }
740     if ($_->{prefix}) {
741     if (defined $uri) {
742     $attr = $c->set_attribute ($_->{lname} => '', namespace_uri => $uri);
743     } else {
744     $self->_raise_error ($o, type => 'NC_PREFIX_NOT_DEFINED', t => $_->{prefix});
745     my $uri = $NS{internal_ns_invalid}.$self->_uri_escape ($_->{prefix});
746     $attr = $c->set_attribute ($_->{lname} => '', namespace_uri => $uri);
747     $c->define_new_namespace ($_->{prefix} => $uri);
748     }
749     } else {
750     $attr = $c->set_attribute ($_->{lname} => '');
751     }
752 wakaba 1.2 _count_lp ('"', $_->{o});
753     my $av = substr ($_->{qvalue}, 1, length ($_->{qvalue}) - 2);
754     $self->_parse_attribute_value ($attr, \$av, $_->{o}) if $attr;
755     _count_lp ('"', $_->{o});
756 wakaba 1.1 }
757     }
758 wakaba 1.2
759     sub _parse_attribute_value ($$\$$) {
760     my ($self, $attr, $s, $o) = @_;
761     my $entMan;
762     while ($$s) {
763     if ($$s =~ s/^($xml_re{Reference})//) {
764     my $entity_ref = $1;
765     my $eref = $self->_parse_reference ($attr, $entity_ref);
766     if ($eref->{namespace_uri} !~ 'char') { ## general entity ref.
767     $entMan ||= $attr->_get_entity_manager;
768     my $entity = $entMan->get_entity ($eref, dont_use_predefined_entities => 1);
769     if (!$entity && {qw/&lt; 1 &gt; 1 &amp; 1 &quot; 1 &apos; 1/}->{$entity_ref}) {
770     $self->_raise_error ($o, c => $attr, type => 'WARN_PREDEFINED_ENTITY_NOT_DECLARED',
771     t => $entity_ref);
772     $entity = $entMan->get_entity ($eref);
773     }
774     if (!$entity) {
775     $self->_raise_error ($o,
776     type => ($entMan->is_standalone_document_1?'WF':'V').'C_ENTITY_DECLARED',
777     t => $entity_ref);
778     } else {
779     my $o2 = $self->_make_clone_of ($o);
780     if ($o2->{__entities}->{$entity_ref}) {
781     $self->_raise_error ($o, type => 'WFC_NO_RECURSION', t => $entity_ref);
782     } else {
783     my $entity_value = $entity->get_attribute ('value');
784     if (ref $entity_value) {
785     $o2->{__entities}->{$entity_ref} = 1;
786     $o2->{entity} = $entity_ref; $o2->{line} = 0; $o2->{pos} = 0;
787     my $ev = $entity_value->_entity_parameter_literal_value;
788     $self->_parse_attribute_value ($eref, \$ev, $o2);
789     $eref->flag (smxp__ref_expanded => 1);
790     } else {
791     $self->_raise_error ($o, type => 'WFC_NO_EXTERNAL_ENTITY_REFERENCE', t => $entity_ref);
792     }
793     }
794     }
795     }
796     _count_lp ($entity_ref, $o);
797     } elsif ($$s =~ s/^([^&<]+)//) {
798 wakaba 1.1 $attr->append_text ($1);
799     _count_lp ($1, $o);
800 wakaba 1.2 } elsif ($$s =~ s/^<//) {
801     $self->_raise_error ($o, type => 'WFC_NO_LE_IN_ATTRIBUTE_VALUE');
802     substr ($$s, 0, 1) = '';
803 wakaba 1.1 } else {
804 wakaba 1.2 $self->_raise_error ($o, type => 'SYNTAX_INVALID_CHAR', t => substr ($$s, 0, 10));
805     substr ($$s, 0, 1) = '';
806 wakaba 1.1 }
807     }
808     }
809 wakaba 1.2
810     sub _parse_rpdata ($$\$$) {
811     my ($self, $c, $s, $o) = @_;
812     my $entMan;
813     while ($$s) {
814     if ($$s =~ s/^($xml_re{PEReference_M})//) {
815     my ($ref, $ename) = ($1, $2);
816     if ($o->{entity_type} eq 'document_entity') {
817     $self->_raise_error ($o, type => 'WFC_PE_IN_INTERNAL_SUBSET', t => $ref);
818     }
819 wakaba 1.3 if (index ($ename, ':') > -1) {
820     $self->_raise_error ($o, type => 'NS_SYNTAX_NAME_IS_NCNAME', t => $ref);
821     }
822 wakaba 1.2 $entMan ||= $c->_get_entity_manager;
823 wakaba 1.4 my $entity = $entMan->get_entity ($ename, namespace_uri => $NS{SGML}.'entity:parameter');
824 wakaba 1.2 my $eref = $c->append_new_node (type => '#reference', local_name => $ename,
825     namespace_uri => $NS{SGML}.'entity:parameter');
826 wakaba 1.4 if (!$entity) { ## TODO: if internal subset, flag
827     $self->_raise_error ($o, c => $c, type => 'VC_ENTITY_DECLARED', t => $ref);
828 wakaba 1.2 } else {
829     my $o2 = $self->_make_clone_of ($o);
830     if ($o2->{__entities}->{$ref}) {
831     $self->_raise_error ($o, c => $c, type => 'WFC_NO_RECURSION', t => $ref);
832     } else {
833 wakaba 1.4 $o2->{entity} = $ref;
834 wakaba 1.2 my $entity_value = $entity->get_attribute ('value');
835 wakaba 1.4 if (ref $entity_value) { ## Internal entity
836 wakaba 1.2 $o2->{__entities}->{$ref} = 1;
837 wakaba 1.4 $o2->{line} = 0; $o2->{pos} = 0;
838     my $ev = $entity_value->_entity_parameter_literal_value;
839     $eref->append_text ($ev);
840     #$self->_parse_rpdata ($eref, \$ev, $o2);
841     } else { ## External entity
842     $o2->{entity_type} = 'external_parameter_entity';
843     my $ext_ent = $entMan->get_external_entity ($self, $entity, $o2);
844     if ($ext_ent->{NDATA}) { ## non-parsed entity
845     $self->_raise_error ($o, type => 'WFC_PARSED_ENTITY', c => $entity, t => $ref);
846     } elsif ($ext_ent->{error}->{no_data}) { ## parsed entity but can't be retrived
847     $self->_raise_error ($o, type => 'ERR_EXT_ENTITY_NOT_FOUND', c => $entity,
848     t => [$ref, $o2->{uri}, $ext_ent->{error}->{reason_text}]);
849     } else { ## parsed entity
850     $o2->{__entities}->{$ref} = 1;
851     #$eref->base_uri ($ext_ent->{base_uri}); ## No worth
852     my $ev = $ext_ent->{text};
853     $self->_parse_rpdata ($eref, \$ev, $o2);
854     $eref->flag (smxp__ref_expanded => 1);
855     } # external parsed entity
856     } # external entity
857     } # not recursive
858     } # entity defined
859     _count_lp ($ref, $o);
860 wakaba 1.2 } elsif ($$s =~ s/^([^%]+)//) {
861 wakaba 1.3 my $t = $1; my $r = '';
862     while ($t) {
863     if ($t =~ s/^&#(?:x([0-9A-Fa-f]+)|([0-9]+));//) {
864     my $char = chr ($1 ? hex $1 : 0+$2);
865     $self->_warn_char_val ($o, $char, ref => 1);
866     $r .= $char;
867     _count_lp ($&, $o);
868     } elsif ($t =~ s/^(&$xml_re{Name};)//) {
869     $r .= $1;
870     if (index ($1, ':') > -1) {
871     $self->_raise_error ($o, type => 'NS_SYNTAX_NAME_IS_NCNAME', c => $c, t => $1);
872     }
873     _count_lp ($1, $o);
874     } elsif ($t =~ s/^&//) {
875     $self->_raise_error ($o, type => 'SYNTAX_INVALID_CHAR', c => $c, t => '&');
876     $r .= '&';
877     _count_lp ('&', $o);
878     } elsif ($t =~ s/^([^&]+)//s) {
879     $r .= $1;
880     _count_lp ($1, $o);
881     }
882     }
883     $c->append_new_node (type => '#text', value => $r);
884 wakaba 1.1 } else {
885 wakaba 1.2 $self->_raise_error ($o, type => 'SYNTAX_INVALID_CHAR', t => substr ($$s, 0, 1));
886     substr ($$s, 0, 1) = '';
887     _count_lp (' ', $o);
888 wakaba 1.1 }
889     }
890     }
891 wakaba 1.2
892     sub _parse_markup_declaration_parameters ($$$$$) {
893     my ($self, $c, $s, $o, $params) = @_;
894     my $entMan;
895     while ($$s) {
896 wakaba 1.4 if ($$s =~ s/^($xml_re{PEReference_M})//) {
897     my ($ref, $ename) = ($1, $2);
898 wakaba 1.2 if ($o->{entity_type} eq 'document_entity') { ## Internal Subset
899 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'WFC_PE_IN_INTERNAL_SUBSET', t => $ref);
900 wakaba 1.1 }
901 wakaba 1.2 if (index ($ename, ':') > -1) {
902 wakaba 1.4 $self->_raise_error ($o, c => $c, type => 'NS_SYNTAX_NAME_IS_NCNAME', t => $ref);
903 wakaba 1.2 }
904     $entMan ||= $c->_get_entity_manager;
905     my $entity = $entMan->get_entity ($ename, namespace_uri => $NS{SGML}.'entity:parameter');
906     my $eref = $c->append_new_node (type => '#reference', local_name => $ename,
907     namespace_uri => $NS{SGML}.'entity:parameter');
908 wakaba 1.4 if (!$entity) { ## TODO: if internal subset, flag
909     $self->_raise_error ($o, c => $c, type => 'VC_ENTITY_DECLARED', t => $ref);
910 wakaba 1.2 } else {
911     my $o2 = $self->_make_clone_of ($o);
912 wakaba 1.4 if ($o2->{__entities}->{$ref}) {
913     $self->_raise_error ($o, c => $c, type => 'WFC_NO_RECURSION', t => $ref);
914 wakaba 1.2 } else {
915 wakaba 1.4 $o2->{entity} = $ref;
916 wakaba 1.2 my $entity_value = $entity->get_attribute ('value');
917 wakaba 1.4 if (ref $entity_value) { ## Internal entity
918     $o2->{__entities}->{$ref} = 1;
919     $o2->{line} = 0; $o2->{pos} = 0;
920 wakaba 1.2 push @$params, $c->append_text (' ');
921     $$params[$#$params]->flag (smxp__pmdp_type => 'S');
922     my $ev = $entity_value->_entity_parameter_literal_value;
923     my $o22 = $self->_make_clone_of ($o);
924     $$params[$#$params]->flag (smxp__src_pos => $o22);
925     $self->_parse_markup_declaration_parameters ($eref, \$ev, $o2, $params);
926     push @$params, $c->append_text (' ');
927     $$params[$#$params]->flag (smxp__pmdp_type => 'S');
928     $$params[$#$params]->flag (smxp__src_pos => $o22);
929     $eref->flag (smxp__ref_expanded => 1);
930 wakaba 1.4 } else { ## External entity
931     $o2->{entity_type} = 'external_parameter_entity';
932     my $ext_ent = $entMan->get_external_entity ($self, $entity, $o2);
933     if ($ext_ent->{NDATA}) { ## non-parsed entity
934     $self->_raise_error ($o, type => 'WFC_PARSED_ENTITY', c => $entity, t => $ref);
935     } elsif ($ext_ent->{error}->{no_data}) { ## parsed entity but can't be retrived
936     $self->_raise_error ($o, type => 'ERR_EXT_ENTITY_NOT_FOUND', c => $entity,
937     t => [$ref, $o2->{uri}, $ext_ent->{error}->{reason_text}]);
938     } else { ## parsed entity
939     $o2->{__entities}->{$ref} = 1;
940     #$eref->base_uri ($ext_ent->{base_uri}); ## No worth
941     push @$params, $c->append_text (' ');
942     $$params[$#$params]->flag (smxp__pmdp_type => 'S');
943     my $o22 = $self->_make_clone_of ($o);
944     $$params[$#$params]->flag (smxp__src_pos => $o22);
945     my $ev = $ext_ent->{text};
946     $self->_parse_markup_declaration_parameters ($eref, \$ev, $o2, $params);
947     push @$params, $c->append_text (' ');
948     $$params[$#$params]->flag (smxp__pmdp_type => 'S');
949     $$params[$#$params]->flag (smxp__src_pos => $o22);
950     $eref->flag (smxp__ref_expanded => 1);
951     } # external parsed entity
952     } # external entity
953 wakaba 1.2 } # not recursive
954     } # entity defined
955     $c->flag (smxp__defined_with_param_ref => 1);
956     _count_lp ($ename.'%;', $o);
957     } elsif ($$s =~ s/^($xml_re{__AttValue_simple})//s) {
958     my $all = $1;
959     $c->append_new_node (type => '#xml', value => substr ($all, 0, 1)); # lit(a)
960     push @$params, $c->append_new_node (type => '#xml', value => substr ($1, 1, length ($all) - 2));
961     $$params[$#$params]->flag (smxp__pmdp_type => 'literal');
962     $$params[$#$params]->flag (smxp__src_pos => $self->_make_clone_of ($o));
963     $c->append_new_node (type => '#xml', value => substr ($all, -1, 1)); # lit(a)
964     _count_lp ($all, $o);
965     } elsif ($$s =~ s/^($xml_re{Name})//) {
966     push @$params, $c->append_text ($1);
967     $$params[$#$params]->flag (smxp__pmdp_type => 'Name');
968     $$params[$#$params]->flag (smxp__src_pos => $self->_make_clone_of ($o));
969 wakaba 1.1 _count_lp ($1, $o);
970 wakaba 1.2 } elsif ($$s =~ s/^([%#(),|+?])//) {
971     push @$params, $c->append_text ($1);
972     $$params[$#$params]->flag (smxp__pmdp_type => 'delimiter');
973     $$params[$#$params]->flag (smxp__src_pos => $self->_make_clone_of ($o));
974 wakaba 1.1 _count_lp ($1, $o);
975 wakaba 1.2 } elsif ($$s =~ s/^($xml_re{s})//s) {
976     push @$params, $c->append_text ($1);
977     $$params[$#$params]->flag (smxp__pmdp_type => 'S');
978     $$params[$#$params]->flag (smxp__src_pos => $self->_make_clone_of ($o));
979 wakaba 1.1 _count_lp ($1, $o);
980     } else {
981 wakaba 1.2 $self->_raise_error ($o, type => 'SYNTAX_INVALID_CHAR', t => substr ($$s, 0, 10));
982     substr ($$s, 0, 1) = '';
983 wakaba 1.3 _count_lp (' ', $o);
984 wakaba 1.1 }
985 wakaba 1.2 } # while
986     }
987    
988     sub _warn_char_val ($$$%) {
989     my ($self, $o, $ch, %o) = @_;
990     if ($ch =~ /(\P{InXMLChar})/) {
991     _count_lp ($`, $o);
992     $self->_raise_error ($o, type => (($o{ref}?'WFC':'SYNTAX').'_LEGAL_CHARACTER'), t => ord $1);
993     } elsif ($ch =~ /(\p{InXML_deprecated_noncharacter})/) {
994     _count_lp ($`, $o);
995 wakaba 1.4 $self->_raise_error ($o, type => 'WARN_UNICODE_NONCHARACTER', t => ord $1);
996 wakaba 1.2 } elsif ($ch =~ /(\p{Compat})/) {
997     _count_lp ($`, $o);
998     $self->_raise_error ($o, type => 'WARN_UNICODE_COMPAT_CHARACTER', t => ord $1);
999     } elsif ($ch =~ /(\p{InXML_unicode_xml_not_suitable})/) {
1000     _count_lp ($`, $o);
1001     $self->_raise_error ($o, type => 'WARN_UNICODE_XML_NOT_SUITABLE_CHARACTER', t => ord $1);
1002 wakaba 1.1 }
1003     }
1004     sub _parse_reference ($$$$) {
1005     my ($self, $c, $ref, $o) = @_;
1006 wakaba 1.2 my $r;
1007 wakaba 1.1 if ($ref =~ /$xml_re{EntityRef_M}/) { ## BUG: QName
1008 wakaba 1.2 $r = $c->append_new_node (type => '#reference', local_name => $1,
1009     namespace_uri => $NS{SGML}.'entity');
1010 wakaba 1.1 } elsif ($ref =~ /x([0-9A-Fa-f]+)/) {
1011     my $ch = hex $1;
1012 wakaba 1.2 $self->_warn_char_val ($o, chr $ch, ref => 1);
1013     $r = $c->append_new_node (type => '#reference', value => $ch,
1014     namespace_uri => 'urn:x-suika-fam-cx:markup:sgml:char:ref:hex');
1015 wakaba 1.1 } elsif ($ref =~ /([0-9]+)/) {
1016 wakaba 1.2 my $ch = 0+$1;
1017     $self->_warn_char_val ($o, chr $ch, ref => 1);
1018     $r = $c->append_new_node (type => '#reference', value => $ch,
1019     namespace_uri => 'urn:x-suika-fam-cx:markup:sgml:char:ref');
1020 wakaba 1.1 } else {
1021     $self->_raise_error ($o, type => 'UNKNOWN', t => $ref);
1022     }
1023     _count_lp ($ref, $o);
1024 wakaba 1.2 $r;
1025 wakaba 1.1 }
1026 wakaba 1.4
1027     sub _parse_xml_or_text_declaration ($$\$$) {
1028     my ($self, $c, $s, $o) = @_;
1029     if ($$s =~ s/^$xml_re{_xml_PI_M}//s) {
1030     my ($data, $all) = ($1, $&);
1031     _count_lp ('<?xml', $o);
1032     if (length ($data)) {
1033     $self->_parse_xml_declaration ($c, $data, $o);
1034     } else {
1035     $self->_raise_error ($o, c => $c, type => 'SYNTAX_XML_DECLARE_NO_ATTR');
1036     }
1037     _count_lp ('?>', $o);
1038     }
1039     }
1040 wakaba 1.1 sub _parse_xml_declaration ($$$$) {
1041     my ($self, $c, $attrs, $o) = @_;
1042     my $stage = 0; # 0: <?xml, 1: version="", 2: encoding="", 3: standalone="", 4: ?>
1043     $attrs = ' ' . $attrs;
1044     while ($attrs) {
1045     if ($attrs =~ s/^$xml_re{s}version(?:$xml_re{s})?=(?:$xml_re{s})?("[A-Za-z0-9_.:-]+"|'[A-Za-z0-9_.:-]+')//s) {
1046     my $version = substr ($1, 1, length ($1) - 2);
1047     if ($stage > 0) {
1048     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE', t => 'version');
1049     }
1050 wakaba 1.4 $c->set_attribute (version => $version);
1051     ## TODO: XML 1.1 support
1052     if ($version ne '1.0') {
1053     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_UNSUPPORTED_XML_VERSION', t => $version);
1054 wakaba 1.1 }
1055     _count_lp ($&, $o); $stage++;
1056     } elsif ($attrs =~ s/^$xml_re{s}encoding(?:$xml_re{s})?=(?:$xml_re{s})?("[A-Za-z0-9_.-]+"|'[A-Za-z0-9_.:-]+')//s) {
1057     if ($stage > 2) {
1058     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE', t => 'encoding');
1059     } elsif ($stage == 0) { ## No version pseudo-attr
1060     if ($o->{entity_type} eq 'document_entity') {
1061 wakaba 1.4 $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE_NO_VERSION_ATTR');
1062 wakaba 1.1 $c->set_attribute (version => '1.0');
1063     } else {
1064 wakaba 1.4 $self->_raise_error ($o, c => $attrs, type => 'WARN_XML_DECLARE_NO_VERSION_ATTR');
1065 wakaba 1.1 $o->{entity_type} = 'external_parsed_entity';
1066     }
1067     }
1068     $c->set_attribute (encoding => substr ($1, 1, length ($1) - 2));
1069     _count_lp ($&, $o); $stage = 2;
1070     } elsif ($attrs =~ s/^$xml_re{s}standalone(?:$xml_re{s})?=(?:$xml_re{s})?("(?:yes|no)"|'(?:yes|no)')//s) {
1071 wakaba 1.4 if ($stage == 0) { ## 'version' or 'encoding' is expected
1072     if ($o->{entity_type} eq 'document_entity') { ## XML declaration
1073     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE_NO_VERSION_ATTR');
1074     $c->set_attribute (version => '1.0');
1075     } else { ## Text declaration
1076     $self->_raise_error ($o, c => $attrs, type => 'WARN_XML_DECLARE_NO_VERSION_ATTR');
1077     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE_NO_ENCODING_ATTR');
1078     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE_STANDALONE_ATTR');
1079     }
1080     } elsif ($stage > 3) {
1081     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE', t => 'standalone');
1082 wakaba 1.1 }
1083     $c->set_attribute (standalone => (substr ($1, 1, 1) eq 'y' ? 'yes' : 'no'));
1084     _count_lp ($&, $o); $stage = 3;
1085     } elsif ($attrs =~ s/^($xml_re{s})//s) {
1086     my $s = $1;
1087     if ($stage == 0) {
1088     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE_NO_ATTR');
1089     $c->set_attribute (version => '1.0');
1090     }
1091     _count_lp ($s, $o); $stage = 4;
1092     } else {
1093     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE', t => $attrs);
1094     _count_lp ($attrs, $o); undef $attrs;
1095     }
1096     } # while
1097     if ($stage == 0) {
1098     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE_NO_ATTR');
1099     $c->set_attribute (version => '1.0');
1100 wakaba 1.4 } elsif ($stage == 1 && substr ($o->{entity_type}, 'external') > -1) {
1101     $self->_raise_error ($o, c => $attrs, type => 'SYNTAX_XML_DECLARE_NO_ENCODING_ATTR');
1102 wakaba 1.1 }
1103     }
1104 wakaba 1.4
1105 wakaba 1.1 sub _parse_entity_declaration ($$$$) {
1106     my ($self, $all, $c, $o) = @_;
1107 wakaba 1.2 my $p; ## notation ? 'n' : parameter entity ? '%' : undef;
1108     my $entMan;
1109     my $e = $c->append_new_node (type => '#declaration');
1110 wakaba 1.1 ## Entity/notation type
1111 wakaba 1.4 if ($all =~ s/^<!ENTITY//) {
1112 wakaba 1.2 $e->namespace_uri ($NS{SGML}.'entity');
1113 wakaba 1.1 _count_lp ($&, $o);
1114 wakaba 1.4 } elsif ($all =~ s/^<!NOTATION//) {
1115 wakaba 1.2 $e->namespace_uri ($NS{SGML}.'notation');
1116 wakaba 1.1 _count_lp ($&, $o); $p = 'n';
1117     }
1118 wakaba 1.2 substr ($all, -1) = ''; # mdc (>)
1119     my @params;
1120     $self->_parse_markup_declaration_parameters ($e, \$all, $o, \@params);
1121     push @params, ref ($e)->new (type => '#text'); ## dummy
1122     shift @params while ($params[0]->flag ('smxp__pmdp_type') eq 'S');
1123 wakaba 1.1 ## Entity/notation name
1124 wakaba 1.2 if ($p ne 'n' && $params[0]->inner_text eq '%') { ## parameter entity
1125     shift @params; # '%'
1126     $e->namespace_uri ($NS{SGML}.'entity:parameter'); $p = '%';
1127     if ($params[0]->flag ('smxp__pmdp_type') eq 'S') {
1128     shift @params while ($params[0]->flag ('smxp__pmdp_type') eq 'S');
1129     } else { ## <!ENTITY %foo...>
1130     my $part = $params[0];
1131     $self->_raise_error (($part->flag ('smxp__src_pos')||$o), c => $e,
1132     type => 'SYNTAX_INVALID_MD', t => ($part->inner_text||'>'));
1133     }
1134     }
1135     if ($params[0]->flag ('smxp__pmdp_type') ne 'Name') {
1136     $self->_raise_error ((shift (@params)->flag ('smxp__src_pos') || $o), c => $e,
1137     type => 'SYNTAX_MD_NAME_NOT_FOUND');
1138     } else { ## Name exist
1139 wakaba 1.3 my $part = shift (@params);
1140     my $ename = $part->inner_text;
1141 wakaba 1.2 $entMan ||= $c->_get_entity_manager;
1142     if ($entMan->get_entity ($ename, namespace_uri => $e->namespace_uri,
1143     dont_use_predefined_entities => 1)) {
1144     if ($p eq 'n') {
1145 wakaba 1.3 $self->_raise_error (($part->flag ('smxp__src_pos') || $o), c => $e,
1146     type => 'VC_UNIQUE_NOTATION_NAME', t => $ename);
1147 wakaba 1.2 } else {
1148 wakaba 1.3 $self->_raise_error (($part->flag ('smxp__src_pos') || $o), c => $e,
1149     type => 'WARN_UNIQUE_'.($p eq '%' ? 'PARAMETER_' : '')
1150     .'ENTITY_NAME', t => $ename);
1151 wakaba 1.2 }
1152     }
1153 wakaba 1.3 if (index ($ename, ':') > -1) {
1154     $self->_raise_error (($part->flag ('smxp__src_pos') || $o), c => $c,
1155     type => 'NS_SYNTAX_NAME_IS_NCNAME', t => $ename);
1156     }
1157 wakaba 1.2 $e->local_name ($ename);
1158     if ($params[0]->flag ('smxp__pmdp_type') eq 'S') {
1159     shift @params while ($params[0]->flag ('smxp__pmdp_type') eq 'S');
1160     my ($extid_type_obj, $extid_type);
1161     if ($params[0]->flag ('smxp__pmdp_type') eq 'Name') { # PUBLIC or SYSTEM
1162     $extid_type_obj = shift (@params);
1163     $extid_type = $extid_type_obj->inner_text;
1164     if ($extid_type ne 'PUBLIC' && $extid_type ne 'SYSTEM') {
1165     $self->_raise_error ($extid_type_obj->flag ('smxp__src_pos'), c => $e,
1166     type => 'SYNTAX_INVALID_KEYWORD', t => $extid_type);
1167     undef $extid_type;
1168     }
1169     if ($params[0]->flag ('smxp__pmdp_type') eq 'S') {
1170     shift @params while ($params[0]->flag ('smxp__pmdp_type') eq 'S');
1171     } else { # <!ENTITY PUBLIC>
1172     $self->_raise_error ($extid_type_obj->flag ('smxp__src_pos'), c => $e,
1173     type => 'SYNTAX_INVALID_KEYWORD', t => $extid_type);
1174     }
1175     }
1176     if ($params[0]->flag ('smxp__pmdp_type') eq 'literal') {
1177     if ($extid_type eq 'SYSTEM') { ## System ID
1178 wakaba 1.4 my $sysid = shift (@params);
1179     $entMan ||= $c->_get_entity_manager;
1180     $e->set_attribute (SYSTEM => $entMan->check_system_id ($sysid->flag ('smxp__src_pos'),
1181     $sysid->inner_text));
1182 wakaba 1.2 } elsif ($extid_type eq 'PUBLIC') { ## Public ID
1183 wakaba 1.4 my $pubid = shift (@params);
1184     $entMan ||= $c->_get_entity_manager;
1185     $e->set_attribute (PUBLIC => $entMan->check_public_id ($pubid->flag ('smxp__src_pos'),
1186     $pubid->inner_text));
1187 wakaba 1.2 } elsif ($p ne 'n') { ## EntityValue (ENTITY only)
1188 wakaba 1.3 my $part = shift (@params);
1189     my $vv = $part->inner_text;
1190     my $o2 = $self->_make_clone_of ($part->flag ('smxp__src_pos')); _count_lp ('"', $o2);
1191     $self->_parse_rpdata ($e->set_attribute ('value'), \$vv, $o2);
1192     if (($p ne '%') && {qw/lt 1 gt 1 amp 1 quot 1 apos 1/}->{$ename}) {
1193     ## TODO: check when external entity
1194     my $ev = $e->get_attribute ('value')->_entity_parameter_literal_value;
1195     unless ({qw/lt|&#60; 1 gt|>&#62; 1 amp|&#38; 1 apos|&#39; 1 quot|&#34; 1
1196     lt|&#x3c; 1 gt|&#x3e; 1 amp|&#x26; 1 apos|&#x27; 1 quot|&#x22; 1
1197     gt|> 1 apos|' 1 quot|" 1
1198     /}->{$ename.'|'.lc ($ev)}) {
1199     $self->_raise_error ($part->flag ('smxp__src_pos'), c => $e,
1200 wakaba 1.4 type => 'FATAL_ERR_PREDEFINED_ENTITY', t => [$ename, $ev]);
1201 wakaba 1.3 }
1202     }
1203 wakaba 1.2 } else {
1204     $self->_raise_error (shift (@params)->flag ('smxp__src_pos'), c => $e,
1205     type => 'SYNTAX_INVALID_LITERAL');
1206     }
1207     } # literal 1
1208     if ($params[0]->flag ('smxp__pmdp_type') eq 'S') {
1209     shift @params while ($params[0]->flag ('smxp__pmdp_type') eq 'S');
1210     my $s_exist = 1;
1211     if ($params[0]->flag ('smxp__pmdp_type') eq 'literal') {
1212     $s_exist = 0;
1213     if ($extid_type eq 'PUBLIC') { ## System ID following Public ID
1214 wakaba 1.4 my $sysid = shift (@params);
1215     $entMan ||= $c->_get_entity_manager;
1216     $e->set_attribute (SYSTEM => $entMan->check_system_id ($sysid->flag ('smxp__src_pos'),
1217     $sysid->inner_text));
1218 wakaba 1.2 } else {
1219     my $param = shift @params;
1220     $self->_raise_error ($param->flag ('smxp__src_pos'), c => $e,
1221     type => 'SYNTAX_INVALID_LITERAL', t => $param);
1222     }
1223     if ($p ne 'n' && $params[0]->flag ('smxp__pmdp_type') eq 'S') {
1224     shift @params while ($params[0]->flag ('smxp__pmdp_type') eq 'S');
1225     $s_exist = 1;
1226     }
1227     } elsif ($p ne 'n' && $extid_type eq 'PUBLIC') { ## System ID required
1228     $self->_raise_error (($params[0]->flag ('smxp__src_pos')||$o), c => $e,
1229     type => 'SYNTAX_MD_SYSID_NOT_FOUND');
1230     $e->set_attribute (SYSTEM => 'http://system.identifier.invalid/');
1231     }
1232     ## NDATA (General entity only)
1233     if ($params[0]->flag ('smxp__pmdp_type') eq 'Name' && $params[0]->inner_text eq 'NDATA') {
1234     if ($p eq '%') { ## parameter entity
1235     $self->_raise_error (shift (@params)->flag ('smxp__src_pos'), c => $e,
1236     type => 'SYNTAX_PE_NDATA');
1237     } elsif (!$extid_type) { ## internal entity
1238     $self->_raise_error (shift (@params)->flag ('smxp__src_pos'), c => $e,
1239     type => 'SYNTAX_INVALID_KEYWORD', t => 'NDATA');
1240     } else {
1241     shift @params; # 'NDATA'
1242     }
1243     if ($p ne 'n' && $params[0]->flag ('smxp__pmdp_type') eq 'S') {
1244     shift @params while ($params[0]->flag ('smxp__pmdp_type') eq 'S');
1245     } else { # <!ENTITY...NDATA> or <!ENTITY...NDATA"foo">, etc.
1246     my $part = shift (@params);
1247     $self->_raise_error ($part->flag ('smxp__src_pos'), c => $e,
1248     type => 'SYNTAX_INVALID_MD', t => ($part->inner_text||'>'));
1249     }
1250     ## Notation name
1251     if ($p ne 'n' && $params[0]->flag ('smxp__pmdp_type') eq 'Name') {
1252     my $part = shift (@params);
1253     my $nname = $part->inner_text;
1254     $entMan ||= $c->_get_entity_manager;
1255 wakaba 1.3 ## Whether notation declared is validated after all of DTD is read
1256     #my $notation = $entMan->get_entity ($nname, namespace_uri => $NS{SGML}.'notation');
1257     #if (!$notation) {
1258     # $self->_raise_error ($part->flag ('smxp__src_pos'), type => 'VC_NOTATION_DECLARED',
1259     # t => $nname, c => $e);
1260     #}
1261     $e->set_attribute (NDATA => $nname)
1262     ->flag (smxp__src_pos => $part->flag ('smxp__src_pos'));
1263 wakaba 1.2 } # Notation Name
1264     } # NDATA
1265     } else { # no literal 2 nor NDATA
1266     if ($p ne 'n' && $extid_type eq 'PUBLIC') { ## System ID required
1267     $self->_raise_error (shift (@params)->flag ('smxp__src_pos'), c => $e,
1268     type => 'SYNTAX_MD_SYSID_NOT_FOUND');
1269     $e->set_attribute (SYSTEM => 'http://system.identifier.invalid/');
1270     }
1271     } # no literal 2
1272     } else { # <!ENTITY name>
1273     my $part = shift (@params);
1274     $self->_raise_error ($part->flag ('smxp__src_pos'), c => $e,
1275     type => 'SYNTAX_INVALID_MD', t => ($part->inner_text||'>'));
1276     }
1277     } # Name exist
1278     while (@params) {
1279     my $param = shift (@params);
1280     last unless $param->flag ('smxp__pmdp_type'); ## dummy parameter
1281     if ($param->flag ('smxp__pmdp_type') ne 'S') {
1282     $self->_raise_error ($param->flag ('smxp__src_pos'), c => $e,
1283     type => 'SYNTAX_INVALID_MD', t => $param->inner_text);
1284     }
1285 wakaba 1.1 }
1286 wakaba 1.3 _count_lp ('>', $o);
1287 wakaba 1.1 }
1288    
1289     sub _parse_element_declaration ($$$$) {
1290     my ($self, $all, $c, $o) = (@_);
1291     my $e = undef;
1292     $e = $c->append_new_node (type => '#declaration',
1293     namespace_uri => 'urn:x-suika-fam-cx:markup:sgml:element');
1294     $all =~ s/^<!ELEMENT//s;
1295     _count_lp ('<!ELEMENT', $o);
1296     ## Element type name
1297     if ($all =~ s/^$xml_re{s}($xml_re{Name})//s) {
1298     $e->set_attribute (qname => $1);
1299     _count_lp ($&, $o);
1300     }
1301     ## contentspec / PEReference
1302     if ($all =~ s/^(?:$xml_re{s})?(?:$xml_re{PEReference}|$xml_re{Name}|\#PCDATA|\()(?:$xml_re{s}|$xml_re{PEReference}|$xml_re{Name}|\#PCDATA|[()|,+*?])*//s) {
1303     $e->append_new_node (type => '#xml', value => $&); # TODO: temporary
1304     _count_lp ($&, $o);
1305     }
1306     if ($all =~ s/^((?:$xml_re{s})?>)$//s) {
1307     _count_lp ($1, $o);
1308     } else {
1309     _raise_fatal_error ($o, desc => 'INVALID_DECLARE_SYNTAX', t => $all);
1310     }
1311     }
1312    
1313    
1314     sub _parse_attlist_declaration ($$$$) {
1315     my ($self, $all, $c, $o) = (@_);
1316     my $e = undef;
1317     $e = $c->append_new_node (type => '#declaration', local_name => 'ATTLIST');
1318     $all =~ s/^<!ATTLIST//s;
1319     _count_lp ('<!ATTLIST', $o);
1320     ## Element type name
1321     if ($all =~ s/^$xml_re{s}($xml_re{Name})//s) {
1322     $e->target_name ($1);
1323     _count_lp ($&, $o);
1324     }
1325     ## Definition
1326     if ($all =~ s/^(?:$xml_re{PEReference}|$xml_re{Name}|\#$xml_re{Name}|$xml_re{s}|$xml_re{__AttValue_simple})+//s) {
1327     $e->append_new_node (type => '#xml', value => $&); # TODO: temporary
1328     _count_lp ($&, $o);
1329     }
1330     if ($all =~ s/^((?:$xml_re{s})?>)$//s) {
1331     _count_lp ($1, $o);
1332     } else {
1333     _raise_fatal_error ($o, desc => 'INVALID_DECLARE_SYNTAX', t => $all);
1334     }
1335     }
1336    
1337     ## TODO: remove this function. this function is already obsoleted.
1338     sub _raise_fatal_error ($%) {
1339     require Carp;
1340     my ($o, %o) = @_;
1341     $o{desc} .= ' (%s)' if length $o{t} && $o{desc} !~ /%s/;
1342     Carp::croak ("Line $o->{line}, position $o->{pos}: ".sprintf $o{desc}, $o{t});
1343     }
1344    
1345     sub _is_brother_of_root_element ($$) {
1346     my ($self, $c) = @_;
1347     for (@{$c->child_nodes}) {
1348     if ($_->node_type eq '#element') {
1349     return 1;
1350     }
1351     }
1352     return 0;
1353     }
1354    
1355     =head1 LICENSE
1356    
1357     Copyright 2003 Wakaba <[email protected]>
1358    
1359     This program is free software; you can redistribute it and/or
1360     modify it under the same terms as Perl itself.
1361    
1362     =cut
1363    
1364 wakaba 1.4 1; # $Date: 2003/06/17 12:25:07 $

[email protected]
ViewVC Help
Powered by ViewVC 1.1.24