File Coverage

blib/lib/XML/SAX/PurePerl/DTDDecls.pm
Criterion Covered Total %
statement 6 308 1.9
branch 0 142 0.0
condition 0 35 0.0
subroutine 2 29 6.9
pod 0 27 0.0
total 8 541 1.4


line stmt bran cond sub pod time code
1             # $Id$
2              
3             package XML::SAX::PurePerl;
4              
5 14     14   84 use strict;
  14         23  
  14         430  
6 14     14   65 use XML::SAX::PurePerl::Productions qw($SingleChar);
  14         23  
  14         41898  
7              
8             sub elementdecl {
9 0     0 0   my ($self, $reader) = @_;
10            
11 0           my $data = $reader->data(9);
12 0 0         return 0 unless $data =~ /^
13 0           $reader->move_along(9);
14            
15 0 0         $self->skip_whitespace($reader) ||
16             $self->parser_error("No whitespace after ELEMENT declaration", $reader);
17            
18 0           my $name = $self->Name($reader);
19            
20 0 0         $self->skip_whitespace($reader) ||
21             $self->parser_error("No whitespace after ELEMENT's name", $reader);
22            
23 0           $self->contentspec($reader, $name);
24            
25 0           $self->skip_whitespace($reader);
26            
27 0 0         $reader->match('>') or $self->parser_error("Closing angle bracket not found on ELEMENT declaration", $reader);
28            
29 0           return 1;
30             }
31              
32             sub contentspec {
33 0     0 0   my ($self, $reader, $name) = @_;
34            
35 0           my $data = $reader->data(5);
36            
37 0           my $model;
38 0 0         if ($data =~ /^EMPTY/) {
    0          
39 0           $reader->move_along(5);
40 0           $model = 'EMPTY';
41             }
42             elsif ($data =~ /^ANY/) {
43 0           $reader->move_along(3);
44 0           $model = 'ANY';
45             }
46             else {
47 0           $model = $self->Mixed_or_children($reader);
48             }
49              
50 0 0         if ($model) {
51             # call SAX callback now.
52 0           $self->element_decl({Name => $name, Model => $model});
53 0           return 1;
54             }
55            
56 0           $self->parser_error("contentspec not found in ELEMENT declaration", $reader);
57             }
58              
59             sub Mixed_or_children {
60 0     0 0   my ($self, $reader) = @_;
61              
62 0           my $data = $reader->data(8);
63 0 0         $data =~ /^\(/ or return; # $self->parser_error("No opening bracket in Mixed or children", $reader);
64            
65 0 0         if ($data =~ /^\(\s*\#PCDATA/) {
66 0           $reader->match('(');
67 0           $self->skip_whitespace($reader);
68 0           $reader->move_along(7);
69 0           my $model = $self->Mixed($reader);
70 0           return $model;
71             }
72              
73             # not matched - must be Children
74 0           return $self->children($reader);
75             }
76              
77             # Mixed ::= ( '(' S* PCDATA ( S* '|' S* QName )* S* ')' '*' )
78             # | ( '(' S* PCDATA S* ')' )
79             sub Mixed {
80 0     0 0   my ($self, $reader) = @_;
81              
82             # Mixed_or_children already matched '(' S* '#PCDATA'
83              
84 0           my $model = '(#PCDATA';
85            
86 0           $self->skip_whitespace($reader);
87              
88 0           my %seen;
89            
90 0           while (1) {
91 0 0         last unless $reader->match('|');
92 0           $self->skip_whitespace($reader);
93              
94 0   0       my $name = $self->Name($reader) ||
95             $self->parser_error("No 'Name' after Mixed content '|'", $reader);
96              
97 0 0         if ($seen{$name}) {
98 0           $self->parser_error("Element '$name' has already appeared in this group", $reader);
99             }
100 0           $seen{$name}++;
101              
102 0           $model .= "|$name";
103            
104 0           $self->skip_whitespace($reader);
105             }
106            
107 0 0         $reader->match(')') || $self->parser_error("no closing bracket on mixed content", $reader);
108              
109 0           $model .= ")";
110              
111 0 0         if ($reader->match('*')) {
112 0           $model .= "*";
113             }
114            
115 0           return $model;
116             }
117              
118             # [[47]] Children ::= ChoiceOrSeq Cardinality?
119             # [[48]] Cp ::= ( QName | ChoiceOrSeq ) Cardinality?
120             # ChoiceOrSeq ::= '(' S* Cp ( Choice | Seq )? S* ')'
121             # [[49]] Choice ::= ( S* '|' S* Cp )+
122             # [[50]] Seq ::= ( S* ',' S* Cp )+
123             # // Children ::= (Choice | Seq) Cardinality?
124             # // Cp ::= ( QName | Choice | Seq) Cardinality?
125             # // Choice ::= '(' S* Cp ( S* '|' S* Cp )+ S* ')'
126             # // Seq ::= '(' S* Cp ( S* ',' S* Cp )* S* ')'
127             # [[51]] Mixed ::= ( '(' S* PCDATA ( S* '|' S* QName )* S* ')' MixedCardinality )
128             # | ( '(' S* PCDATA S* ')' )
129             # Cardinality ::= '?' | '+' | '*'
130             # MixedCardinality ::= '*'
131             sub children {
132 0     0 0   my ($self, $reader) = @_;
133            
134 0           return $self->ChoiceOrSeq($reader) . $self->Cardinality($reader);
135             }
136              
137             sub ChoiceOrSeq {
138 0     0 0   my ($self, $reader) = @_;
139            
140 0 0         $reader->match('(') or $self->parser_error("choice/seq contains no opening bracket", $reader);
141            
142 0           my $model = '(';
143            
144 0           $self->skip_whitespace($reader);
145              
146 0           $model .= $self->Cp($reader);
147            
148 0 0         if (my $choice = $self->Choice($reader)) {
149 0           $model .= $choice;
150             }
151             else {
152 0           $model .= $self->Seq($reader);
153             }
154              
155 0           $self->skip_whitespace($reader);
156              
157 0 0         $reader->match(')') or $self->parser_error("choice/seq contains no closing bracket", $reader);
158              
159 0           $model .= ')';
160            
161 0           return $model;
162             }
163              
164             sub Cardinality {
165 0     0 0   my ($self, $reader) = @_;
166             # cardinality is always optional
167 0           my $data = $reader->data;
168 0 0         if ($data =~ /^([\?\+\*])/) {
169 0           $reader->move_along(1);
170 0           return $1;
171             }
172 0           return '';
173             }
174              
175             sub Cp {
176 0     0 0   my ($self, $reader) = @_;
177              
178 0           my $model;
179             my $name = eval
180 0           {
181 0 0         if (my $name = $self->Name($reader)) {
182 0           return $name . $self->Cardinality($reader);
183             }
184             };
185 0 0         return $name if defined $name;
186 0           return $self->ChoiceOrSeq($reader) . $self->Cardinality($reader);
187             }
188              
189             sub Choice {
190 0     0 0   my ($self, $reader) = @_;
191            
192 0           my $model = '';
193 0           $self->skip_whitespace($reader);
194            
195 0           while ($reader->match('|')) {
196 0           $self->skip_whitespace($reader);
197 0           $model .= '|';
198 0           $model .= $self->Cp($reader);
199 0           $self->skip_whitespace($reader);
200             }
201              
202 0           return $model;
203             }
204              
205             sub Seq {
206 0     0 0   my ($self, $reader) = @_;
207            
208 0           my $model = '';
209 0           $self->skip_whitespace($reader);
210            
211 0           while ($reader->match(',')) {
212 0           $self->skip_whitespace($reader);
213 0           my $cp = $self->Cp($reader);
214 0 0         if ($cp) {
215 0           $model .= ',';
216 0           $model .= $cp;
217             }
218 0           $self->skip_whitespace($reader);
219             }
220              
221 0           return $model;
222             }
223              
224             sub AttlistDecl {
225 0     0 0   my ($self, $reader) = @_;
226            
227 0           my $data = $reader->data(9);
228 0 0         if ($data =~ /^
229             # It's an attlist
230            
231 0           $reader->move_along(9);
232            
233 0 0         $self->skip_whitespace($reader) ||
234             $self->parser_error("No whitespace after ATTLIST declaration", $reader);
235 0           my $name = $self->Name($reader);
236              
237 0           $self->AttDefList($reader, $name);
238              
239 0           $self->skip_whitespace($reader);
240            
241 0 0         $reader->match('>') or $self->parser_error("Closing angle bracket not found on ATTLIST declaration", $reader);
242            
243 0           return 1;
244             }
245            
246 0           return 0;
247             }
248              
249             sub AttDefList {
250 0     0 0   my ($self, $reader, $name) = @_;
251              
252 0           1 while $self->AttDef($reader, $name);
253             }
254              
255             sub AttDef {
256 0     0 0   my ($self, $reader, $el_name) = @_;
257              
258 0 0         $self->skip_whitespace($reader) || return 0;
259 0   0       my $att_name = $self->Name($reader) || return 0;
260 0 0         $self->skip_whitespace($reader) ||
261             $self->parser_error("No whitespace after Name in attribute definition", $reader);
262 0           my $att_type = $self->AttType($reader);
263              
264 0 0         $self->skip_whitespace($reader) ||
265             $self->parser_error("No whitespace after AttType in attribute definition", $reader);
266 0           my ($mode, $value) = $self->DefaultDecl($reader);
267            
268             # fire SAX event here!
269 0           $self->attribute_decl({
270             eName => $el_name,
271             aName => $att_name,
272             Type => $att_type,
273             Mode => $mode,
274             Value => $value,
275             });
276 0           return 1;
277             }
278              
279             sub AttType {
280 0     0 0   my ($self, $reader) = @_;
281              
282 0   0       return $self->StringType($reader) ||
283             $self->TokenizedType($reader) ||
284             $self->EnumeratedType($reader) ||
285             $self->parser_error("Can't match AttType", $reader);
286             }
287              
288             sub StringType {
289 0     0 0   my ($self, $reader) = @_;
290            
291 0           my $data = $reader->data(5);
292 0 0         return unless $data =~ /^CDATA/;
293 0           $reader->move_along(5);
294 0           return 'CDATA';
295             }
296              
297             sub TokenizedType {
298 0     0 0   my ($self, $reader) = @_;
299            
300 0           my $data = $reader->data(8);
301 0 0         if ($data =~ /^(IDREFS?|ID|ENTITIES|ENTITY|NMTOKENS?)/) {
302 0           $reader->move_along(length($1));
303 0           return $1;
304             }
305 0           return;
306             }
307              
308             sub EnumeratedType {
309 0     0 0   my ($self, $reader) = @_;
310 0   0       return $self->NotationType($reader) || $self->Enumeration($reader);
311             }
312              
313             sub NotationType {
314 0     0 0   my ($self, $reader) = @_;
315            
316 0           my $data = $reader->data(8);
317 0 0         return unless $data =~ /^NOTATION/;
318 0           $reader->move_along(8);
319            
320 0 0         $self->skip_whitespace($reader) ||
321             $self->parser_error("No whitespace after NOTATION", $reader);
322 0 0         $reader->match('(') or $self->parser_error("No opening bracket in notation section", $reader);
323            
324 0           $self->skip_whitespace($reader);
325 0           my $model = 'NOTATION (';
326 0   0       my $name = $self->Name($reader) ||
327             $self->parser_error("No name in notation section", $reader);
328 0           $model .= $name;
329 0           $self->skip_whitespace($reader);
330 0           $data = $reader->data;
331 0           while ($data =~ /^\|/) {
332 0           $reader->move_along(1);
333 0           $model .= '|';
334 0           $self->skip_whitespace($reader);
335 0   0       my $name = $self->Name($reader) ||
336             $self->parser_error("No name in notation section", $reader);
337 0           $model .= $name;
338 0           $self->skip_whitespace($reader);
339 0           $data = $reader->data;
340             }
341 0 0         $data =~ /^\)/ or $self->parser_error("No closing bracket in notation section", $reader);
342 0           $reader->move_along(1);
343            
344 0           $model .= ')';
345              
346 0           return $model;
347             }
348              
349             sub Enumeration {
350 0     0 0   my ($self, $reader) = @_;
351            
352 0 0         return unless $reader->match('(');
353            
354 0           $self->skip_whitespace($reader);
355 0           my $model = '(';
356 0   0       my $nmtoken = $self->Nmtoken($reader) ||
357             $self->parser_error("No Nmtoken in enumerated declaration", $reader);
358 0           $model .= $nmtoken;
359 0           $self->skip_whitespace($reader);
360 0           my $data = $reader->data;
361 0           while ($data =~ /^\|/) {
362 0           $model .= '|';
363 0           $reader->move_along(1);
364 0           $self->skip_whitespace($reader);
365 0   0       my $nmtoken = $self->Nmtoken($reader) ||
366             $self->parser_error("No Nmtoken in enumerated declaration", $reader);
367 0           $model .= $nmtoken;
368 0           $self->skip_whitespace($reader);
369 0           $data = $reader->data;
370             }
371 0 0         $data =~ /^\)/ or $self->parser_error("No closing bracket in enumerated declaration", $reader);
372 0           $reader->move_along(1);
373            
374 0           $model .= ')';
375              
376 0           return $model;
377             }
378              
379             sub Nmtoken {
380 0     0 0   my ($self, $reader) = @_;
381 0           return $self->Name($reader);
382             }
383              
384             sub DefaultDecl {
385 0     0 0   my ($self, $reader) = @_;
386            
387 0           my $data = $reader->data(9);
388 0 0         if ($data =~ /^(\#REQUIRED|\#IMPLIED)/) {
389 0           $reader->move_along(length($1));
390 0           return $1;
391             }
392 0           my $model = '';
393 0 0         if ($data =~ /^\#FIXED/) {
394 0           $reader->move_along(6);
395 0 0         $self->skip_whitespace($reader) || $self->parser_error(
396             "no whitespace after FIXED specifier", $reader);
397 0           my $value = $self->AttValue($reader);
398 0           return "#FIXED", $value;
399             }
400 0           my $value = $self->AttValue($reader);
401 0           return undef, $value;
402             }
403              
404             sub EntityDecl {
405 0     0 0   my ($self, $reader) = @_;
406            
407 0           my $data = $reader->data(8);
408 0 0         return 0 unless $data =~ /^
409 0           $reader->move_along(8);
410            
411 0 0         $self->skip_whitespace($reader) || $self->parser_error(
412             "No whitespace after ENTITY declaration", $reader);
413            
414 0 0         $self->PEDecl($reader) || $self->GEDecl($reader);
415            
416 0           $self->skip_whitespace($reader);
417            
418 0 0         $reader->match('>') or $self->parser_error("No closing '>' in entity definition", $reader);
419            
420 0           return 1;
421             }
422              
423             sub GEDecl {
424 0     0 0   my ($self, $reader) = @_;
425              
426 0   0       my $name = $self->Name($reader) || $self->parser_error("No entity name given", $reader);
427 0 0         $self->skip_whitespace($reader) || $self->parser_error("No whitespace after entity name", $reader);
428              
429             # TODO: ExternalID calls lexhandler method. Wrong place for it.
430 0           my $value;
431 0 0         if ($value = $self->ExternalID($reader)) {
432 0           $value .= $self->NDataDecl($reader);
433             }
434             else {
435 0           $value = $self->EntityValue($reader);
436             }
437              
438 0 0         if ($self->{ParseOptions}{entities}{$name}) {
439 0           warn("entity $name already exists\n");
440             } else {
441 0           $self->{ParseOptions}{entities}{$name} = 1;
442 0           $self->{ParseOptions}{expanded_entity}{$name} = $value; # ???
443             }
444             # do callback?
445 0           return 1;
446             }
447              
448             sub PEDecl {
449 0     0 0   my ($self, $reader) = @_;
450            
451 0 0         return 0 unless $reader->match('%');
452              
453 0 0         $self->skip_whitespace($reader) || $self->parser_error("No whitespace after parameter entity marker", $reader);
454 0   0       my $name = $self->Name($reader) || $self->parser_error("No parameter entity name given", $reader);
455 0 0         $self->skip_whitespace($reader) || $self->parser_error("No whitespace after parameter entity name", $reader);
456 0   0       my $value = $self->ExternalID($reader) ||
457             $self->EntityValue($reader) ||
458             $self->parser_error("PE is not a value or an external resource", $reader);
459             # do callback?
460 0           return 1;
461             }
462              
463             my $quotre = qr/[^%&\"]/;
464             my $aposre = qr/[^%&\']/;
465              
466             sub EntityValue {
467 0     0 0   my ($self, $reader) = @_;
468            
469 0           my $data = $reader->data;
470 0           my $quote = '"';
471 0           my $re = $quotre;
472 0 0         if ($data !~ /^"/) {
473 0 0         $data =~ /^'/ or $self->parser_error("Not a quote character", $reader);
474 0           $quote = "'";
475 0           $re = $aposre;
476             }
477 0           $reader->move_along(1);
478            
479 0           my $value = '';
480            
481 0           while (1) {
482 0           my $data = $reader->data;
483              
484 0 0         $self->parser_error("EOF found while reading entity value", $reader)
485             unless length($data);
486            
487 0 0         if ($data =~ /^($re+)/) {
    0          
    0          
    0          
488 0           my $match = $1;
489 0           $value .= $match;
490 0           $reader->move_along(length($match));
491             }
492             elsif ($reader->match('&')) {
493             # if it's a char ref, expand now:
494 0 0         if ($reader->match('#')) {
495 0           my $char;
496 0           my $ref = '';
497 0 0         if ($reader->match('x')) {
498 0           my $data = $reader->data;
499 0           while (1) {
500 0 0         $self->parser_error("EOF looking for reference end", $reader)
501             unless length($data);
502 0 0         if ($data !~ /^([0-9a-fA-F]*)/) {
503 0           last;
504             }
505 0           $ref .= $1;
506 0           $reader->move_along(length($1));
507 0 0         if (length($1) == length($data)) {
508 0           $data = $reader->data;
509             }
510             else {
511 0           last;
512             }
513             }
514 0           $char = chr_ref(hex($ref));
515 0           $ref = "x$ref";
516             }
517             else {
518 0           my $data = $reader->data;
519 0           while (1) {
520 0 0         $self->parser_error("EOF looking for reference end", $reader)
521             unless length($data);
522 0 0         if ($data !~ /^([0-9]*)/) {
523 0           last;
524             }
525 0           $ref .= $1;
526 0           $reader->move_along(length($1));
527 0 0         if (length($1) == length($data)) {
528 0           $data = $reader->data;
529             }
530             else {
531 0           last;
532             }
533             }
534 0           $char = chr($ref);
535             }
536 0 0         $reader->match(';') ||
537             $self->parser_error("No semi-colon found after character reference", $reader);
538 0 0         if ($char !~ $SingleChar) { # match a single character
539 0           $self->parser_error("Character reference '&#$ref;' refers to an illegal XML character ($char)", $reader);
540             }
541 0           $value .= $char;
542             }
543             else {
544             # entity refs in entities get expanded later, so don't parse now.
545 0           $value .= '&';
546             }
547             }
548             elsif ($reader->match('%')) {
549 0           $value .= $self->PEReference($reader);
550             }
551             elsif ($reader->match($quote)) {
552             # end of attrib
553 0           last;
554             }
555             else {
556 0           $self->parser_error("Invalid character in attribute value: " . substr($reader->data, 0, 1), $reader);
557             }
558             }
559            
560 0           return $value;
561             }
562              
563             sub NDataDecl {
564 0     0 0   my ($self, $reader) = @_;
565 0 0         $self->skip_whitespace($reader) || return '';
566 0           my $data = $reader->data(5);
567 0 0         return '' unless $data =~ /^NDATA/;
568 0           $reader->move_along(5);
569 0 0         $self->skip_whitespace($reader) || $self->parser_error("No whitespace after NDATA declaration", $reader);
570 0   0       my $name = $self->Name($reader) || $self->parser_error("NDATA declaration lacks a proper Name", $reader);
571 0           return " NDATA $name";
572             }
573              
574             sub NotationDecl {
575 0     0 0   my ($self, $reader) = @_;
576            
577 0           my $data = $reader->data(10);
578 0 0         return 0 unless $data =~ /^
579 0           $reader->move_along(10);
580 0 0         $self->skip_whitespace($reader) ||
581             $self->parser_error("No whitespace after NOTATION declaration", $reader);
582 0           $data = $reader->data;
583 0           my $value = '';
584 0           while(1) {
585 0 0         $self->parser_error("EOF found while looking for end of NotationDecl", $reader)
586             unless length($data);
587            
588 0 0         if ($data =~ /^([^>]*)>/) {
589 0           $value .= $1;
590 0           $reader->move_along(length($1) + 1);
591 0           $self->notation_decl({Name => "FIXME", SystemId => "FIXME", PublicId => "FIXME" });
592 0           last;
593             }
594             else {
595 0           $value .= $data;
596 0           $reader->move_along(length($data));
597 0           $data = $reader->data;
598             }
599             }
600 0           return 1;
601             }
602              
603             1;