File Coverage

blib/lib/XML/ParseDTD.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             XML::ParseDTD - parses a XML DTD and provides methods to access the
4             information stored in the DTD.
5              
6             =cut
7              
8             ######################################################################
9              
10             package XML::ParseDTD;
11             require 5.004;
12              
13             # Copyright (c) 2003, Moritz Sinn. This module is free software;
14             # you can redistribute it and/or modify it under the terms of the
15             # GNU GENERAL PUBLIC LICENSE, see COPYING for more information.
16              
17 1     1   38042 use strict;
  1         3  
  1         39  
18 1     1   5 use vars qw($VERSION);
  1         1  
  1         65  
19             $VERSION = '0.1.4';
20              
21             ######################################################################
22              
23             =head1 DEPENDENCIES
24              
25             =head2 Perl Version
26              
27             5.004
28              
29             =head2 Standard Modules
30              
31             Carp 1.01
32              
33             =head2 Nonstandard Modules
34              
35             LWP::UserAgent 0.01
36             Cache::Cache 1.02
37              
38             =cut
39              
40             ######################################################################
41              
42 1     1   1454 use Switch;
  0            
  0            
43             use Carp;
44             use LWP::UserAgent;
45             use Cache::SharedMemoryCache;
46              
47             ######################################################################
48              
49             =head1 SYNOPSIS
50              
51             use XML::ParseDTD;
52             $dtd = XML::ParseDTD->new($dtd);
53             $bool = $dtd->child_allowed($tag, $childtag);
54             $bool = $dtd->child_list_allowed($tag, @childtags);
55             $bool = $dtd->attr_allowed($tag, $attribute);
56             $bool = $dtd->attr_list_allowed($tag, @attributes);
57             $bool = $dtd->is_empty($tag);
58             $bool = $dtd->is_defined($tag);
59             $bool = $dtd->is_fixed($tag, $attribute);
60             $bool = $dtd->attr_value_allowed($tag, $attribute, $value);
61             $bool = $dtd->attr_list_value_allowed($tag, \%attribute_value);
62             @tags = $dtd->get_document_tags();
63             $regexp = $dtd->get_child_regexp($tag);
64             @attributes = $dtd->get_attributes($tag);
65             @req_attributes = $dtd->get_req_attributes($tag);
66             $value = $dtd->get_allowed_attr_values($tag, $attribute);
67             $default_value = $dtd->get_attr_def_value($tag, $attribute);
68             $dtd->clear_cache();
69             $errormessage = $dtd->errstr;
70             $errornumber = $dtd->err;
71              
72             =head1 DESCRIPTION
73              
74             ParseDTD.pm is a Perl 5 object class which provides methods to access
75             the information stored in a XML DTD.
76              
77             This module basically tells you which tags are known by the dtd, which
78             child tags a certain tag might have, which tags are defined as a empty
79             tag, which attributes a certain tag might have, which values are
80             allowed for a certain attribute, which attributes are required, which
81             attributes are fixed, which attributes have which default value
82             ... well i would say it tells you all except the entity definitions
83             (they're on the ToDo list) that is defined in the dtd (at least all
84             that i know of, but i'm not so much into that topic, so please make me
85             aware if i missed something). All this information can be accessed in
86             2 diffrent ways: 1. you can simply get it 2. you can pass certain data
87             and the module then tells you whether thats ok or not.
88              
89             This package uses Cache::SharedMemoryCache to cache every parsed DTD,
90             so next time the data structure representing the dtd can be just
91             taken out of memory. Thus the dtd is not refetched and not parsed
92             again which saves quite some time and work. You can easily modify the
93             module so that is uses Cache::FileCache if you prefer, but i think
94             SharedMemory is faster.
95              
96             Everytime the constructor is called it first checks whether the given
97             dtd is already in memory, if so it compares the I date
98             to the date stored in memory and then decides whether it should
99             refetch it or not. If the dtd lays on the local filesystem this
100             operation doesn't produce any reasonable overhead, but if the dtd is
101             fetched out of the internet it might make sense to not check the
102             I header every time. You can configure how often it
103             should be checked, by default it is checked averaged every third
104             time. But since most dtds don't change it is mostly save to not check
105             it at all.
106              
107             Internally the parsed DTD data is simply stored in 6 hash
108             structures. Because of this and because of the caching the module
109             should be very fast.
110              
111             =head1 USING XML::ParseDTD
112              
113             =head2 The Constructor
114              
115             =head3 new ($dtd_url, [ %conf ])
116              
117             This method is the constructor. The first argument must be the path to
118             a xml dtd, it should be a valid URL using the file or http
119             protocol. Here are some examples:
120              
121             =over
122              
123             =item
124              
125             http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
126              
127             =item
128              
129             /home/moritz/xhtml1-strict.dtd
130              
131             =item
132              
133             file://home/moritz/xhtml1-strict.dtd
134              
135             =back
136              
137             The configuration hash can be used to influence the modules
138             behaviour. The options known are:
139              
140             =over
141              
142             =item
143              
144             B - configures how often the I header should
145             be checked if the http protocol is used. The Default is I<3> that
146             means that averaged it is checked every third time (dtd is refetched
147             and reparsed if it was modified meanwhile). Setting it to 1 will
148             force the module to always check the I header, setting
149             it to -1 will force it to never check the header (which is recommend
150             if performance is important and its more or less sure that the dtd
151             will not be changed).
152              
153             B - Identifier for the datastructure which is saved to and
154             taken from the cache. Because this module uses the shared memory for
155             caching, it is important that is identifier is really unique, else it
156             would probably overwrite some data of another program. By default the
157             Identifier is I. I is allways
158             added to the value of this option to distinguish the dtds.
159              
160             B - The value of this option is simply passed to
161             LWP::UserAgent as timeout value. Please see the documentation of
162             LWP::UserAgent for more information. The default is
163             I<30>. LWP::UserAgent is used to fetch dtds with the http protocol and
164             to get their I header to know whether they have been
165             modified.
166              
167             B - The value of this option is passed to Cache::Cache
168             for setting the time when the cache will be expired and thus has to be
169             rewritten. By default this is I. For possible values please
170             read the documentation of Cache::Cache.
171              
172             =back
173              
174             B: You shouldn't set any option to I<0> since it will not be interpreted, that means the default setting will be used instead.
175              
176             =cut
177              
178             ######################################################################
179              
180             sub new {
181             my $checklm = 3;
182             my $memkey = 'XML::ParseDTD';
183             my $timeout = 30;
184             my $cache_expire = 'never';
185             my ($class, $dtd, %conf) = @_;
186             #file:// or without protocol is the same (local filesystem)
187             $dtd =~ s/^file:\/\///;
188             my $cache = new Cache::SharedMemoryCache;
189             #attach dtd path to key
190             $memkey = ($conf{memkey}||$memkey).'/'.$dtd;
191             $cache->purge();
192             my $self = $cache->get($memkey);
193             if(!defined($self) || !_validate($dtd,$self,$conf{checklm}||$checklm,$conf{timeout}||$timeout)) {
194             $self = _load($dtd,$conf{timeout}||$timeout);
195             $cache->set($memkey,$self,$conf{cache_expire}||$cache_expire);
196             }
197             $self = bless($self, ref($class) || $class);
198             $self->{cache} = $cache;
199             return $self;
200             }
201              
202             ######################################################################
203              
204             =head2 Check Methods
205              
206             =head3 child_allowed ($tag, $childtag)
207              
208             Checks whether the given tag can contain the given childtag.
209              
210             Returns 1 (true) or 0 (false).
211              
212             =cut
213              
214             ######################################################################
215              
216             sub child_allowed {
217             my($self,$tag,$child) = @_;
218             $self->_set_errstr(1,$tag) and return 0 unless($self->{'Element'}->{$tag});
219             return 1 if (eval("'" . $self->{'Element'}->{$tag} . "'" . "=~ m/\($child,\)/"));
220             $self->_set_errstr(4,$child,$tag);
221             return 0;
222             }
223              
224             ######################################################################
225              
226             =head3 child_list_allowed ($tag, @childtags)
227              
228             Checks whether its ok if the given tag contains the given childtags in
229             the given order. This means that the method will return ails if a
230             certain tag is not allowed, a required tag is not given or the order
231             is not allowed.
232              
233             Returns 1 (true) or 0 (false).
234              
235             =cut
236              
237             ######################################################################
238              
239             sub child_list_allowed {
240             my($self,$tag,@childs) = @_;
241             $self->_set_errstr(1,$tag) and return 0 unless($self->{'Element'}->{$tag});
242             local $_ = join(',', @childs);
243             $_ .= ',';
244             return 1 if(eval('/' . $self->{'Element'}->{$tag} . '/'));
245             chop();
246             $self->_set_errstr(5, $_, $tag);
247             return 0;
248             }
249              
250             ######################################################################
251              
252             =head3 attr_allowed ($tag, $attribute)
253              
254             Checks whether the given attribute is allowed for the given tag.
255              
256             Returns 1 (true) or 0 (false).
257              
258             =cut
259              
260             ######################################################################
261              
262             sub attr_allowed {
263             my($self,$tag,$attr) = @_;
264             $self->_set_errstr(1,$tag) and return 0 unless($self->{'Element'}->{$tag});
265             $self->_set_errstr(2,$tag) and return 0 unless(defined($self->{'Attr'}->{$tag}));
266             return 1 if($self->{'Attr'}->{$tag}->{$attr});
267             $self->_set_errstr(3,$tag,$attr);
268             return 0;
269             }
270              
271             ######################################################################
272              
273             =head3 attr_list_allowed ($tag, @attributes)
274              
275             Checks whether its ok if the given tag has set given attributes. This
276             means that the method will return fails if a certain attribute is not
277             allowed or a required attribute is not given.
278              
279             Returns 1 (true) or 0 (false).
280              
281             =cut
282              
283             ######################################################################
284              
285             sub attr_list_allowed {
286             my($self,$tag,@attrs) = @_;
287             $self->_set_errstr(1,$tag) and return 0 unless($self->{'Element'}->{$tag});
288             $self->_set_errstr(2,$tag) and return 0 unless(defined($self->{'Attr'}->{$tag}));
289             my %req;
290             %req = %{$self->{'ReqAtt'}->{$tag}} if(defined($self->{'ReqAtt'}->{$tag}));
291             foreach $_ (@attrs) {
292             $self->_set_errstr(3,$_,$tag) and return 0 unless(defined($self->{'Attr'}->{$tag}->{$_}));
293             delete $req{$_} if(defined($req{$_}));
294             }
295             return 1 unless(scalar keys(%req));
296             $self->_set_errstr(6,join(',', keys(%req)),$tag);
297             return 0;
298             }
299              
300             ######################################################################
301              
302             =head3 is_empty ($tag)
303              
304             Checks whether the given tag is a empty tag, that means whether it
305             can't contain any elements or data.
306              
307             Returns 1 (true) or 0 (false).
308              
309             =cut
310              
311             ######################################################################
312              
313             sub is_empty {
314             my($self,$tag) = @_;
315             return 1 if($self->{'Empty'}->{$tag});
316             $self->_set_errstr(8, $tag);
317             return 0;
318             }
319              
320             ######################################################################
321              
322             =head3 is_any ($tag)
323              
324             Checks whether the given tag has content model I.
325              
326             Returns 1 (true) or 0 (false).
327              
328             =cut
329              
330             ######################################################################
331              
332             sub is_any {
333             my($self,$tag) = @_;
334             return 1 if($self->{'Any'}->{$tag});
335             $self->_set_errstr(11, $tag);
336             return 0;
337             }
338              
339             ######################################################################
340              
341             =head3 is_defined ($tag)
342              
343             Checks whether the given tag is defined in the dtd, that means whether
344             it is allowed in the document.
345              
346             Returns 1 (true) or 0 (false).
347              
348             =cut
349              
350             ######################################################################
351              
352             sub is_defined {
353             my($self,$tag) = @_;
354             return 1 if(defined($self->{Element}->{$tag}));
355             $self->_set_errstr(1, $tag);
356             return 0;
357             }
358              
359             ######################################################################
360              
361             =head3 is_fixed ($tag, $attribute)
362              
363             Checks whether the given attribute for the given tag is a fixed
364             attribute, that means if its value is predefined by the dtd.
365              
366             If so, you can use C to get the predefined
367             value.
368              
369             Returns 1 (true) or 0 (false)
370              
371             =cut
372              
373             ######################################################################
374              
375             sub is_fixed {
376             my($self,$tag,$attr) = @_;
377             return 0 unless($self->attr_allowed($tag,$attr));
378             return 1 if($self->{FixAtt}->{$tag}->{$attr});
379             $self->_set_errstr(9, $attr, $tag);
380             return 0;
381             }
382              
383             ######################################################################
384              
385             =head3 attr_value_allowed ($tag, $attribute, $value)
386              
387             Checks whether the given attribute for the given tag might be set to
388             the given value.
389              
390             Returns 1 (true) or 0 (false).
391              
392             =cut
393              
394             ######################################################################
395              
396             sub attr_value_allowed {
397             my($self,$tag,$attr,$value) = @_;
398             return 0 unless($self->attr_allowed($tag,$attr));
399             for(ref($self->{'Attr'}->{$tag}->{$attr})) {
400             m/HASH/ && do {
401             $self->_set_errstr(7,$value,$attr,$tag) && return 0 unless($self->{'Attr'}->{$tag}->{$attr}->{$value});
402             last;
403             };
404             m/^$/ && do {
405             $self->_set_errstr(7,$value,$attr,$tag) && return 0 unless($self->{'Attr'}->{$tag}->{$attr} == $value);
406             last;
407             };
408             m/ARRAY/ && do {
409             my $rex=$self->{Attr}->{$tag}->{$attr}->[1];
410             $self->_set_errstr(7,$value,$attr,$tag) && return 0 unless($value =~ m/$rex/);
411             last;
412             };
413             }
414             return 1;
415             }
416              
417             ######################################################################
418              
419             =head3 attr_list_value_allowed ($tag, \%attribute_value)
420              
421             Calls C for the attribute names, if everything is
422             fine it calls C for each value.
423              
424             Returns 1 (true) or 0 (false).
425              
426             =cut
427              
428             ######################################################################
429              
430             sub attr_list_value_allowed {
431             my($self,$tag,$attr_value) = @_;
432             croak "2. argument must be HASHREF" unless(ref($attr_value) eq 'HASH');
433             return 0 unless($self->attr_list_allowed($tag,keys(%$attr_value)));
434             foreach $_ (keys(%$attr_value)) {
435             return 0 unless($self->attr_value_allowed($tag,$_,$attr_value->{$_}));
436             }
437             return 1;
438             }
439              
440             ######################################################################
441              
442             =head2 Get Methods
443              
444             =head3 get_document_tags
445              
446             Returns a list of all tags which are defined in the dtd, that means
447             which are allowed in the document.
448              
449             =cut
450              
451             ######################################################################
452              
453             sub get_document_tags {
454             my $self = shift;
455             return keys(%{$self->{Element}});
456             }
457              
458             ######################################################################
459              
460             =head3 get_child_regexp ($tag)
461              
462             Returns the regular expression, which defines which combinations of
463             child elements are valid for the given tag, as a string.
464              
465             =cut
466              
467             ######################################################################
468              
469             sub get_child_regexp {
470             my($self,$tag) = @_;
471             return undef unless($self->is_defined($tag));
472             return $self->{'Element'}->{$tag};
473             }
474              
475             ######################################################################
476              
477             =head3 get_attributes ($tag)
478              
479             Returns a list of all attributes which are allowed for the given tag.
480              
481             =cut
482              
483             ######################################################################
484              
485             sub get_attributes {
486             my($self,$tag) = @_;
487             return undef unless($self->is_defined($tag));
488             return keys(%{$self->{Attr}->{$tag}}) if(defined($self->{Attr}->{$tag}));
489             return ();
490             }
491              
492             ######################################################################
493              
494             =head3 get_req_attributes ($tag)
495              
496             Returns a list of all required attributes for the given tag.
497              
498             =cut
499              
500             ######################################################################
501              
502             sub get_req_attributes {
503             my($self,$tag) = @_;
504             return undef unless($self->is_defined($tag));
505             return keys(%{$self->{ReqAtt}->{$tag}}) if(defined($self->{ReqAtt}->{$tag}));
506             return ();
507             }
508              
509             ######################################################################
510              
511             =head3 get_allowed_attr_values ($tag,$attribute)
512              
513             Returns the allowed values for the given attribute for the given tag.
514              
515             If only one certain string is allowed to be set as value, this string
516             is returned. If the value must be one string out of a list of
517             strings, a reference to this list is returned. If the value must be
518             of a certain datatype such as PCDATA, ID or NMTOKEN, a reference to a
519             hash with only one element is returned. The key is the name of the
520             datatype and the value is a regular expression string which describes
521             the datatype.
522              
523             undef is returned if nothing is defined as attribute value, that
524             normally means that the attribute is not known for the given tag, but
525             you can call C to get more information.
526              
527             =cut
528              
529             ######################################################################
530              
531             sub get_allowed_attr_values {
532             my($self,$tag,$attr) = @_;
533              
534             return undef unless($self->is_defined($tag));
535             return undef unless($self->attr_allowed($tag,$attr));
536              
537             if(defined($self->{Attr}->{$tag}->{$attr})) {
538             if(ref($self->{Attr}->{$tag}->{$attr}) eq 'HASH') {
539             return [keys(%{$self->{Attr}->{$tag}->{$attr}})];
540             }
541             elsif(ref($self->{Attr}->{$tag}->{$attr}) eq 'ARRAY') {
542             return {$self->{Attr}->{$tag}->{$attr}->[0] => $self->{Attr}->{$tag}->{$attr}->[1]};
543             }
544             else {
545             return $self->{Attr}->{$tag}->{$attr};
546             }
547             }
548             #this should never be the case since $self->{Attr}->{$tag}->{$attr} should always be defined if the attribute is allowed
549             return undef;
550             }
551              
552             ######################################################################
553              
554             =head3 get_attr_def_value ($tag,$attribute)
555              
556             Returns the default value defined for the given attribute of the given
557             tag. In most cases no default value is defined, that means that undef
558             is returned. But undef is also returned if the tag does not exist or
559             if the attribute is not allowed for the given tag. To get more
560             information why undef was returned, you should call C.
561              
562             =cut
563              
564             ######################################################################
565              
566             sub get_attr_def_value {
567             my($self,$tag,$attr) = @_;
568              
569             return undef unless($self->is_defined($tag));
570             return undef unless($self->attr_allowed($tag,$attr));
571              
572             return $self->{DefAtt}->{$tag}->{$attr} if(defined($self->{DefAtt}->{$tag}->{$attr}));
573             $self->_set_errstr(10,$attr,$tag);
574             return undef;
575             }
576              
577             ######################################################################
578              
579             =head2 Other Methods
580              
581             =head3 clear_cache ()
582              
583             Clears the cache, that means that the dtd will be refetched and
584             reparsed next time.
585              
586             =cut
587              
588             ######################################################################
589              
590             sub clear_cache {
591             my $self = shift;
592             $self->{cache}->clear();
593             }
594              
595             ######################################################################
596              
597             =head3 errstr ()
598              
599             Returns the message of the last occured error.
600              
601             =cut
602              
603             ######################################################################
604              
605             sub errstr {
606             my($self) = @_;
607             return $self->{errstr};
608             }
609              
610             ######################################################################
611              
612             =head3 err ()
613              
614             Returns the number of the last occured error.
615              
616             =cut
617              
618             ######################################################################
619              
620             sub err {
621             my($self) = @_;
622             return $self->{err};
623             }
624              
625              
626             ######################################################################
627             # INTERNAL MEHTODS #
628             ######################################################################
629              
630             sub _set_errstr {
631             my($self, $err) = (shift, shift);
632             $self->{errstr} = _get_errstr($err,@_);
633             $self->{err} = $err;
634             }
635              
636             sub _get_errstr {
637             my $err = shift;
638             my $msg;
639             for ($err) {
640             $msg = /^1$/ && sprintf("Unkown tag '%s'", @_)
641             || /^2$/ && sprintf("'%s' has no attributes", @_)
642             || /^3$/ && sprintf("Attribute '%s' not allowed for '%s'", @_)
643             || /^4$/ && sprintf("'%s' is not allowed in '%s'", @_)
644             || /^5$/ && sprintf("Child list '%s' not allowed for '%s'", @_)
645             || /^6$/ && sprintf('Required Attribute(s) "%s" for "%s" not defined', @_)
646             || /^7$/ && sprintf('Value "%s" not allowed for attribute "%s" of "%s"', @_)
647             || /^8$/ && sprintf('"%s" is not a empty tag', @_)
648             || /^9$/ && sprintf('Attribute "%s" for "%s" is not fixed', @_)
649             || /^10$/ && sprintf('No default value defined for attribute "%s" of "%s"', @_)
650             || /^11$/ && sprintf('Content model of tag "%s" is NOT "ANY"', @_)
651             || '';
652             }
653             return $msg;
654             }
655              
656              
657             #this method fetches and parses the dtd
658             sub _load {
659             my ($dtd,$timeout) = @_;
660             my %pdtd = (
661             'Element' => {},
662             'Empty' => {},
663             'Any' => {},
664             'Attr' => {},
665             'ReqAtt' => {},
666             'FixAtt' => {},
667             'DefAtt' => {},
668             );
669             my $DTD;
670             if($dtd =~ m/^(?!file)([A-za-z]+):\/\//i) {
671             my $ua = LWP::UserAgent->new(timeout => $timeout);
672             local $_;
673             $_ = $ua->get($dtd);
674             $DTD = $_->content;
675             $pdtd{lmod} = $_->last_modified;
676             }
677             else {
678             open DTD, "<$dtd" or die "Cannot open file $dtd : $!\n";
679             {
680             local $/;
681             $DTD = ;
682             }
683             close DTD;
684             $pdtd{lmod} = (stat($dtd))[9];
685             }
686              
687             $DTD =~ s///gs;
688            
689             my %IntEntity;
690             while($DTD =~ s///os) {
691             $IntEntity{$1} = $2;
692             }
693            
694             my $entity;
695             foreach $_ (keys(%IntEntity)) {
696             #$IntEntity{$_} =~ s/%(\S+);/$IntEntity{$1}/gs;
697             while($IntEntity{$_} =~ s/%(\S+);/$IntEntity{$1}/s) {}
698             }
699            
700             #$DTD =~ s/%(\S+);/$IntEntity{$1}/gs;
701             while($DTD =~ s/%(\S+);/$IntEntity{$1}/s) {}
702              
703             while($DTD =~ s/]*\)(\*|\+)?)|(EMPTY)|(ANY))\s*>//s) {
704             if(!$4) {
705             $_ = $1;
706             $pdtd{'Element'}->{$_} = $2;
707             $pdtd{'Element'}->{$_} =~ s/\s*//gs;
708             $pdtd{'Element'}->{$_} =~ s/([a-zA-Z0-9#]+)(?!(,|[a-zA-Z0-9#]))/$1,/gs;
709             $pdtd{'Element'}->{$_} =~ s/([a-zA-Z0-9#]+,)/($1)/gs;
710             $pdtd{'Element'}->{$_} =~ s/([^a-zA-Z0-9#]{1}),/$1/gs;
711             }
712             else {
713             if($4 eq 'EMPTY') {
714             $pdtd{'Element'}->{$1} = 1;
715             $pdtd{'Empty'}->{$1} = 1;
716             }
717             elsif($4 eq 'ANY') {
718             $pdtd{'Element'}->{$1} = '.*';
719             $pdtd{'Any'}->{$1} = 1;
720             }
721             }
722             }
723            
724             my $elem;
725             while($DTD =~ s/]*)>//s) {
726             $elem = $1;
727             $pdtd{'Attr'}->{$elem} = {};
728             $_ = $2;
729             my ($attr,$type,$some,$default);
730             while(s/\s*(\S+)\s*((?:\([^\(\)]+\))|(?:[^\(\) \t\n]+))\s*(\S+)?\s*((?:"|')\S+(?:'|"))?\s*//s) {
731             ($attr,$type,$some,$default) = ($1,$2,$3,$4);
732             for($type) {
733             #/^ID(REF)?$/ && do { $pdtd{'Attr'}->{$elem}->{$attr} = \&XML::ParseDTD::_check_id; last; };
734             /^ID(REF)?$/ && do { $pdtd{'Attr'}->{$elem}->{$attr} = ['ID', '^[A-Za-z_]{1}[A-Za-z0-9_:.-]*$']; last; };
735             #/^IDREFS$/ && do { $pdtd{'Attr'}->{$elem}->{$attr} = \&XML::ParseDTD::_check_idrefs; last; };
736             /^IDREFS$/ && do { $pdtd{'Attr'}->{$elem}->{$attr} = ['IDREFS', '^[A-Za-z_]{1}[A-Za-z0-9_:. -]*$']; last; };
737             #/^CDATA$/ && do { $pdtd{'Attr'}->{$elem}->{$attr} = \&XML::ParseDTD::_check_cdata; last; };
738             /^CDATA$/ && do { $pdtd{'Attr'}->{$elem}->{$attr} = ['CDATA', '.*']; last; };
739             #/^PCDATA$/ && do { $pdtd{'Attr'}->{$elem}->{$attr} = \&XML::ParseDTD::_check_pcdata; last; };
740             /^PCDATA$/ && do { $pdtd{'Attr'}->{$elem}->{$attr} = ['PCDATA', '.*']; last; };
741             #/^NMTOKEN$/ && do { $pdtd{'Attr'}->{$elem}->{$attr} = \&XML::ParseDTD::_check_nmtoken; last; };
742             /^NMTOKEN$/ && do { $pdtd{'Attr'}->{$elem}->{$attr} = ['NMTOKEN', '^[A-Za-z0-9_:.-]{1}\S*$']; last; };
743             /^\((.*)\)$/s && do {
744             $_ = $1;
745             s/\s//gs;
746             my @allowed = split(/\|/s, $_);
747             if(@allowed > 1) {
748             $pdtd{'Attr'}->{$elem}->{$attr} = {};
749             foreach my $value (@allowed) {
750             $pdtd{'Attr'}->{$elem}->{$attr}->{$value} = 1;
751             }
752             }
753             else {
754             $pdtd{'Attr'}->{$elem}->{$attr} = $allowed[0];
755             }
756             last;
757             };
758             }
759             for($some) {
760             /#IMPLIED/ && do { last; };
761             /#REQUIRED/ && do { $pdtd{'ReqAtt'}->{$elem}->{$attr} = 1; last; };
762             /#FIXED/ && do { $pdtd{'FixAtt'}->{$elem}->{$attr} = 1; last; };
763             ($pdtd{'DefAtt'}->{$elem}->{$attr} = $some) =~ s/("|')//g if($some);
764             }
765             ($pdtd{'DefAtt'}->{$elem}->{$attr} = $default) =~ s/("|')//g if($default);
766             }
767             }
768             return \%pdtd;
769             }
770              
771             #this method proves whether the dtd is already cached and if so if it should be refetched (and reparsed)
772             sub _validate {
773             my ($dtd,$rec,$checklm,$timeout) = @_;
774             my $lmod;
775             if($dtd =~ m/^([A-za-z]+):\/\//i) {
776             $lmod = ($checklm < 0 || int(rand($checklm))) ? $rec->{lmod} : LWP::UserAgent->new(timeout => $timeout)->head($dtd)->last_modified;
777             }
778             else {
779             $lmod = (stat($dtd))[9];
780             }
781             return ($lmod == $rec->{lmod}) ? 1 : 0;
782             }
783              
784             ######################################################################
785             return 1;
786             __END__