1# -*- Perl -*-
2
3package SGML::DTDParse::DTD;
4
5use strict;
6use vars qw($VERSION $CVS);
7
8$VERSION = do { my @r=(q$Revision: 2.2 $=~/\d+/g); sprintf "%d."."%03d"x$#r,@r };
9$CVS = '$Id: DTD.pm,v 2.2 2005/07/16 03:21:35 ehood Exp $ ';
10
11use Text::DelimMatch;
12use SGML::DTDParse;
13use SGML::DTDParse::Catalog;
14use SGML::DTDParse::Tokenizer;
15use SGML::DTDParse::ContentModel;
16use SGML::DTDParse::Util qw(entify);
17
18my $DTDVERSION = "1.0";
19my $DTDPUBID = "-//Norman Walsh//DTD DTDParse V2.0//EN";
20my $DTDSYSID = "dtd.dtd";
21my $debug = 0;
22
23{
24    package SGML::DTDParse::DTD::ENTITY;
25
26    sub new {
27	my($type, $dtd, $entity, $etype, $pub, $sys, $text) = @_;
28	my $class = ref($type) || $type;
29	my $self = {};
30
31	$text = $dtd->fix_entityrefs($text);
32
33	if ($dtd->{'XML'} && ($pub && !$sys)) {
34	    $dtd->status("External entity declaration without system "
35			 . "identifer found in XML DTD. "
36			 . "This isn't an XML DTD.", 1);
37	    $dtd->{'XML'} = 0;
38	}
39
40	$self->{'DTD'} = $dtd;
41	$self->{'NAME'} = $entity;
42	$self->{'TYPE'} = $etype;
43	$self->{'NOTATION'} = "";
44	$self->{'PUBLIC'} = $pub;
45	$self->{'SYSTEM'} = $sys;
46	$self->{'TEXT'} = $text;
47
48	if ($etype =~ /^ndata (\S+)$/i) {
49	    $self->{'TYPE'} = 'ndata';
50	    $self->{'NOTATION'} = $1;
51	}
52
53	if ($etype =~ /^cdata (\S+)$/i) {
54	    $self->{'TYPE'} = 'cdata';
55	    $self->{'NOTATION'} = $1;
56	}
57
58	bless $self, $class;
59    }
60
61    sub name {
62	my $self = shift;
63	my $value = shift;
64	$self->{'NAME'} = $value if defined($value);
65	return $self->{'NAME'};
66    }
67
68    sub type {
69	my $self = shift;
70	my $value = shift;
71	$self->{'TYPE'} = $value if defined($value);
72	return $self->{'TYPE'};
73    }
74
75    sub notation {
76	my $self = shift;
77	my $value = shift;
78	$self->{'NOTATION'} = $value if defined($value);
79	return $self->{'NOTATION'};
80    }
81
82    sub public {
83	my $self = shift;
84	my $value = shift;
85	$self->{'PUBLIC'} = $value if defined($value);
86	return $self->{'PUBLIC'};
87    }
88
89    sub system {
90	my $self = shift;
91	my $value = shift;
92	$self->{'SYSTEM'} = $value if defined($value);
93	return $self->{'SYSTEM'};
94    }
95
96    sub text {
97	my $self = shift;
98	my $value = shift;
99	$self->{'TEXT'} = $value if defined($value);
100	return $self->{'TEXT'};
101    }
102
103    sub xml {
104	my $self = shift;
105	my $xml = "";
106
107	$xml .= "<entity name=\"" . $self->name() . "\"\n";
108	$xml .= "        type=\"" . $self->type() . "\"\n";
109	$xml .= "        notation=\"" . $self->notation() . "\"\n"
110	    if $self->notation();
111
112	if ($self->public() || $self->system()) {
113	    $xml .= "        public=\"" . $self->public() . "\"\n"
114		if $self->public();
115	    $xml .= "        system=\"" . $self->system() . "\"\n"
116		if $self->system();
117	    $xml .= "/>\n";
118	} else {
119	    my $text = $self->{'DTD'}->expand_entities($self->text());
120	    $text =~ s/\&/\&amp;/sg;
121
122	    $xml .= ">\n";
123	    $xml .= "<text-expanded>$text</text-expanded>\n";
124
125	    if ($self->{'DTD'}->{'UNEXPANDED_CONTENT'}) {
126		$text = $self->text();
127		$text =~ s/\&/\&amp;/sg;
128		$xml .= "<text>$text</text>\n";
129	    }
130
131	    $xml .= "</entity>\n";
132	}
133
134	return $xml;
135    }
136}
137
138{
139    package SGML::DTDParse::DTD::ELEMENT;
140
141    sub new {
142	my($type, $dtd, $element, $stagm, $etagm, $cm, $incl, $excl) = @_;
143	my $class = ref($type) || $type;
144	my $self = {};
145
146	$cm = $dtd->fix_entityrefs($cm);
147	$incl = $dtd->fix_entityrefs($incl);
148	$excl = $dtd->fix_entityrefs($excl);
149
150	if ($dtd->{'XML'} && ($cm eq 'CDATA')) {
151	    $dtd->status("CDATA declared element content found in XML DTD. "
152			 . "This isn't an XML DTD.", 1);
153	    $dtd->{'XML'} = 0;
154	}
155
156	if ($dtd->{'XML'} && ($stagm || $etagm)) {
157	    $dtd->status("Tag minimization found in XML DTD. "
158			 . "This isn't an XML DTD.", 1);
159	    $dtd->{'XML'} = 0;
160	}
161
162	$self->{'DTD'} = $dtd;
163	$self->{'NAME'} = $element;
164	$self->{'STAGM'} = $stagm;
165	$self->{'ETAGM'} = $etagm;
166	$self->{'CONMDL'} = $cm;
167	$self->{'INCL'} = $incl;
168	$self->{'EXCL'} = $excl;
169
170	bless $self, $class;
171    }
172
173    sub name {
174	my $self = shift;
175	my $value = shift;
176	$self->{'NAME'} = $value if defined($value);
177	return $self->{'NAME'};
178    }
179
180    sub type {
181	return "element";
182    }
183
184    sub starttag_min {
185	my $self = shift;
186	my $value = shift;
187	$self->{'STAGM'} = $value if defined($value);
188	return $self->{'STAGM'};
189    }
190
191    sub endtag_min {
192	my $self = shift;
193	my $value = shift;
194	$self->{'ETAGM'} = $value if defined($value);
195	return $self->{'ETAGM'};
196    }
197
198    sub content_model {
199	my $self = shift;
200	my $value = shift;
201	$self->{'CONMDL'} = $value if defined($value);
202	return $self->{'CONMDL'};
203    }
204
205    sub inclusions {
206	my $self = shift;
207	my $value = shift;
208	$self->{'INCL'} = $value if defined($value);
209	return $self->{'INCL'};
210    }
211
212    sub exclusions {
213	my $self = shift;
214	my $value = shift;
215	$self->{'EXCL'} = $value if defined($value);
216	return $self->{'EXCL'};
217    }
218
219    sub xml_content_model {
220	my $self = shift;
221	my $wrapper = shift;
222	my $model = shift;
223	my $expand = shift;
224	my $xml  = "";
225	my ($text, $cmtok, $cm);
226
227#	$text = $model;
228#	$text =~ s/\%/\&/sg;
229	# $xml = "<$wrapper text=\"$text\">\n";
230	$xml = "<$wrapper>\n";
231
232	$text = $expand ? $self->{'DTD'}->expand_entities($model) : $model;
233	$cmtok = new SGML::DTDParse::Tokenizer $text;
234	$cm = new SGML::DTDParse::ContentModel $cmtok;
235
236	$xml .= $cm->xml();
237
238	$xml .= "</$wrapper>\n";
239
240	return $xml;
241    }
242
243    sub xml {
244	my $self = shift;
245	my $xml = "";
246	my($text, $cmtok, $cm, $type);
247
248	$text = $self->content_model();
249	$text = $self->{'DTD'}->expand_entities($text);
250	$cmtok = new SGML::DTDParse::Tokenizer $text;
251	$cm = new SGML::DTDParse::ContentModel $cmtok;
252
253	$type = $cm->type();
254
255	$xml .= "<element name=\"" . $self->name() . "\"";
256	$xml .= " stagm=\"" . $self->starttag_min() . "\""
257	    if $self->starttag_min();
258	$xml .= " etagm=\"" . $self->endtag_min() . "\""
259	    if $self->endtag_min();
260	$xml .= "\n";
261	$xml .= "         content-type=\"$type\"";
262	$xml .= ">\n";
263
264	$xml .= $self->xml_content_model('content-model-expanded',
265					 $self->content_model(), 1);
266
267	if ($self->{'DTD'}->{'UNEXPANDED_CONTENT'}) {
268	    $xml .= $self->xml_content_model('content-model',
269					     $self->content_model(), 0);
270	}
271
272	if ($self->inclusions()) {
273	    $xml .= $self->xml_content_model('inclusions',
274					     $self->inclusions(), 1);
275	}
276
277	if ($self->exclusions()) {
278	    $xml .= $self->xml_content_model('exclusions',
279					     $self->exclusions(), 1);
280	}
281
282	$xml .= "</element>\n";
283
284	return $xml;
285    }
286}
287
288{
289    package SGML::DTDParse::DTD::ATTLIST;
290
291    sub new {
292	my $type = shift;
293	my $dtd = shift;
294	my $attlist = shift;
295	my $attdecl = shift;
296	my(@attrs) = @_;
297	my $class = ref($type) || $type;
298	my $self = {};
299
300	$self->{'DTD'} = $dtd;
301	$self->{'NAME'} = $attlist;
302	$self->{'TYPE'} = {};
303	$self->{'VALS'} = {};
304	$self->{'DEFV'} = {};
305	$self->{'DECL'} = $attdecl;
306
307	while (@attrs) {
308	    my $name     = shift @attrs;
309	    my $values   = shift @attrs;
310	    my $attrtype = shift @attrs;
311	    my $defval   = shift @attrs;
312
313	    $self->{'TYPE'}->{$name} = $attrtype;
314	    $self->{'VALS'}->{$name} = $values;
315	    $self->{'DEFV'}->{$name} = $defval;
316	}
317
318	bless $self, $class;
319    }
320
321    sub append {
322	my $self = shift;
323	my $dtd = shift;
324	my $attlist = shift;
325	my $attdecl = shift;
326	my(@attrs) = @_;
327
328	while (@attrs) {
329	    my $name     = shift @attrs;
330	    my $values   = shift @attrs;
331	    my $attrtype = shift @attrs;
332	    my $defval   = shift @attrs;
333
334	    $self->{'TYPE'}->{$name} = $attrtype;
335	    $self->{'VALS'}->{$name} = $values;
336	    $self->{'DEFV'}->{$name} = $defval;
337	}
338    }
339
340    sub name {
341	my $self = shift;
342	my $value = shift;
343	$self->{'NAME'} = $value if defined($value);
344	return $self->{'NAME'};
345    }
346
347    sub type {
348	return "attlist";
349    }
350
351    sub text {
352	my $self = shift;
353	return $self->{'DECL'};
354    }
355
356    sub attribute_list {
357	my $self = shift;
358	my(@attr) = keys %{$self->{'TYPE'}};
359	return @attr;
360    }
361
362    sub attribute_type {
363	my $self = shift;
364	my $attr = shift;
365	my $value = shift;
366	$self->{'TYPE'}->{$attr} = $value if defined($value);
367	return $self->{'TYPE'}->{$attr};
368    }
369
370    sub attribute_values {
371	my $self = shift;
372	my $attr = shift;
373	my $value = shift;
374	$self->{'VALS'}->{$attr} = $value if defined($value);
375	return $self->{'VALS'}->{$attr};
376    }
377
378    sub attribute_default {
379	my $self = shift;
380	my $attr = shift;
381	my $value = shift;
382	$self->{'DEFV'}->{$attr} = $value if defined($value);
383	return $self->{'DEFV'}->{$attr};
384    }
385
386    sub xml {
387	my $self = shift;
388	my $xml = "";
389	my(@attr) = $self->attribute_list();
390	my($attr, $text);
391
392	$xml .= "<attlist name=\"" . $self->name() . "\">\n";
393
394	my $cdata = $self->{'DECL'};
395	$cdata =~ s/&/&amp;/sg;
396	$cdata =~ s/</&lt;/sg;
397
398	$xml .= "<attdecl>$cdata</attdecl>\n";
399
400	foreach $attr (@attr) {
401	    $xml .= "<attribute name=\"$attr\"\n";
402
403	    $text = $self->attribute_type($attr);
404	    # $text =~ s/\%/\&/sg;
405	    $xml .= "           type=\"$text\"\n";
406
407	    $text = $self->attribute_values($attr);
408	    # $text =~ s/\%/\&/sg;
409
410	    my $enumtype = undef;
411	    if ($text =~ /^NOTATION \(/) {
412		$enumtype = "notation";
413		$text = "(" . $'; # '
414	    }
415
416	    if ($text =~ /^\(/) {
417		$enumtype = "yes" if !defined($enumtype);
418		$xml .= "           enumeration=\"$enumtype\"\n";
419		$text =~ s/[\(\)\|]/ /g;
420		$text =~ s/\s+/ /g;
421		$text =~ s/^\s*//;
422		$text =~ s/\s*$//;
423	    }
424
425	    $xml .= "           value=\"$text\"\n";
426
427	    $text = $self->attribute_default($attr);
428	    # $text =~ s/\%/\&/sg;
429	    $xml .= "           default=\"$text\"/>\n";
430	}
431
432	$xml .= "</attlist>\n";
433
434	return $xml;
435    }
436}
437
438{
439    package SGML::DTDParse::DTD::NOTATION;
440
441    sub new {
442	my($type, $dtd, $notation, $pub, $sys, $text) = @_;
443	my $class = ref($type) || $type;
444	my $self = {};
445
446	$self->{'DTD'} = $dtd;
447	$self->{'NAME'} = $notation;
448	$self->{'PUBLIC'} = $pub;
449	$self->{'SYSTEM'} = $sys;
450
451	bless $self, $class;
452    }
453
454    sub name {
455	my $self = shift;
456	my $value = shift;
457	$self->{'NAME'} = $value if defined($value);
458	return $self->{'NAME'};
459    }
460
461    sub type {
462	return "notation";
463    }
464
465    sub public {
466	my $self = shift;
467	my $value = shift;
468	$self->{'PUBLIC'} = $value if defined($value);
469	return $self->{'PUBLIC'};
470    }
471
472    sub system {
473	my $self = shift;
474	my $value = shift;
475	$self->{'SYSTEM'} = $value if defined($value);
476	return $self->{'SYSTEM'};
477    }
478
479    sub xml {
480	my $self = shift;
481	my $xml = "";
482
483	$xml .= "<notation name=\"" . $self->name() . "\"\n";
484
485	$xml .= "        public=\"" . $self->public() . "\"\n"
486	    if $self->public();
487
488	if (!$self->public() || $self->system()) {
489	    $xml .= "        system=\"" . $self->system() . "\"\n";
490	}
491
492	$xml .= "/>\n";
493
494	return $xml;
495    }
496}
497
498sub new {
499    my $type = shift;
500    my %param = @_;
501    my $class = ref($type) || $type;
502    my $self = bless {}, $class;
503    my $cat = new SGML::DTDParse::Catalog (%param);
504
505    $self->{'LASTMSGLEN'} = 0;
506    $self->{'NEWLINE'} = 0;
507    $self->{'CAT'} = $cat;
508    $self->{'PENT'} = {};
509    $self->{'DECLS'} = [];
510    $self->{'DECLS'}->[0] = 0;
511    $self->{'PENTDECL'} = [];
512    $self->{'PENTDECL'}->[0] = 0;
513    $self->{'GENT'} = {};
514    $self->{'GENTDECL'} = [];
515    $self->{'GENTDECL'}->[0] = 0;
516    $self->{'ELEM'} = {};
517    $self->{'ATTR'} = {};
518    $self->{'NOTN'} = {};
519    $self->{'VERBOSE'} = $param{'Verbose'} || $param{'Debug'};
520    $self->debug($param{'Debug'});
521    $self->{'TITLE'} = $param{'Title'};
522    $self->{'UNEXPANDED_CONTENT'}
523      = $param{'UnexpandedContent'} ? 1 : 0;
524    $self->{'SOURCE_DTD'} = $param{'SourceDtd'};
525    $self->{'PUBLIC_ID'} = $param{'PublicId'};
526    $self->{'SYSTEM_ID'} = $param{'SystemId'};
527    $self->{'DECLARATION'} = $param{'Declaration'};
528    $self->{'XML'} = $param{'Xml'};
529    $self->{'NAMECASE_GEN'} = $param{'NamecaseGeneral'};
530    $self->{'NAMECASE_ENT'} = $param{'NamecaseEntity'};
531
532    # There's a deficiency in the way this code is written. The entity
533    # boundaries are lost as entities are loaded, so there's no way to
534    # keep track of the correct "current directory" for resolving
535    # relative system identifiers. To work around this problem, the list
536    # of all directories accessed is kept in a path, and that path is
537    # searched for relative system identifiers. This could produce the
538    # wrong results, but it doesn't seem very likely. A proper solution
539    # may be implemented in the future.
540    $self->{'SEARCHPATH'} = ();
541
542    delete($self->{'DTD'}); # This isn't supposed to exist yet.
543
544    return $self;
545}
546
547sub parse {
548    my $self = shift;
549    my $dtd = shift;
550    my $dtd_fh = \*STDIN;
551    local $_;
552
553    die "Error: Already parsed " . $self->{'DTD'} . "\n" if $self->{'DTD'};
554
555    if (!$dtd) {
556	if ($self->{'SYSTEM_ID'}) {
557	    $dtd = $self->{'CAT'}->system_map($self->{'SYSTEM_ID'});
558	} elsif ($self->{'PUBLIC_ID'}) {
559	    $dtd = $self->{'CAT'}->public_map($self->{'PUBLIC_ID'});
560	}
561    }
562
563    if (!$dtd) {
564	$self->status('Reading DTD from stdin...', 1);
565	$self->{'DTD'} = '<osfd>0';
566    } else {
567	$self->{'DTD'} = $dtd;
568    }
569    if (!$self->{'SYSTEM_ID'}) {
570	$self->{'SYSTEM_ID'} = $self->{'DTD'};
571    }
572
573    my $decl = $self->{'DECLARATION'};
574
575    if (!$decl) {
576	if ($self->{'PUBLIC_ID'}) {
577	    $decl = $self->{'CAT'}->declaration($self->{'PUBLIC_ID'});
578	} else {
579	    my $pubid = $self->{'CAT'}->reverse_public_map($dtd);
580	    $decl = $self->{'CAT'}->declaration($pubid);
581	}
582    }
583
584    if ($self->{'PUBLIC_ID'}) {
585	$self->status('Public ID: ' . $self->{'PUBLIC_ID'}, 1);
586    } else {
587	$self->status('Public ID: unknown', 1);
588    }
589
590    $self->status('System ID: ' . $self->{'SYSTEM_ID'}, 1);
591
592    if ($decl) {
593	$self->{'DECLARATION'} = $decl;
594	$self->status("SGML declaration: $decl", 1);
595	my($xml, $namecase, $entitycase) = $self->parse_decl($decl);
596	$self->{'XML'} = $xml;
597	$self->{'NAMECASE_GEN'} = $namecase;
598	$self->{'NAMECASE_ENT'} = $entitycase;
599    } else {
600	$self->status("SGML declaration: unknown, using defaults for xml and namecase", 1);
601    }
602
603    if ($dtd) {
604	use Symbol;
605	$dtd_fh = gensym;
606	open($dtd_fh, $dtd) || die qq{Error: Unable to open "$dtd": $!\n};
607    }
608    {
609	# slurp up entire file
610	local $/;
611	$_ = <$dtd_fh>;
612    }
613    close ($dtd_fh)  if $dtd;
614
615    $self->add_to_searchpath($dtd || '.');
616
617    my ($tok, $rest) = $self->next_token($_);
618    while ($tok) {
619	if ($tok =~ /<!ENTITY/is) {
620	    $rest = $self->parse_entity($rest);
621	} elsif ($tok =~ /<!ELEMENT/is) {
622	    $rest = $self->parse_element($rest);
623	} elsif ($tok =~ /<!ATTLIST/is) {
624	    $rest = $self->parse_attlist($rest);
625	} elsif ($tok =~ /<!NOTATION/is) {
626	    $rest = $self->parse_notation($rest);
627	} elsif ($tok =~ /<!\[/) {
628	    $rest = $self->parse_markedsection($rest);
629	} else {
630	    die "Error: Unexpected declaration: $tok\n";
631	}
632
633	($tok, $rest) = $self->next_token($rest);
634    }
635
636    $self->status("Parse complete.\n");
637
638    return $self;
639}
640
641sub parseCatalog {
642    my $self = shift;
643    my $catalog = shift;
644
645    $self->{'CAT'}->parse($catalog);
646}
647
648sub verbose {
649    my $self = shift;
650    my $val = shift;
651    my $verb = $self->{'VERBOSE'};
652
653    $self->{'VERBOSE'} = $val if defined($val);
654
655    return $verb;
656}
657
658sub debug {
659    my $self = shift;
660    my $val = shift;
661    my $dbg = $debug;
662
663    if (defined($val)) {
664	$debug = $val;
665        if (ref($self)) {
666            $self->{'DEBUG'} = $debug;
667        }
668    }
669    return $dbg;
670}
671
672# ======================================================================
673
674sub add_entity {
675    my($self, $name, $type, $public, $system, $text) = @_;
676    my $entity = new SGML::DTDParse::DTD::ENTITY $self, $name, $type, $public, $system, $text;
677    my $count;
678
679    if ($type eq 'param') {
680	return if exists($self->{'PENT'}->{$name});
681	$count = $self->{'PENTDECL'}->[0] + 1;
682	$self->{'PENT'}->{$name} = $count;
683	$self->{'PENTDECL'}->[0] = $count;
684	$self->{'PENTDECL'}->[$count] = $entity;
685
686	$count = $self->{'DECLS'}->[0] + 1;
687	$self->{'DECLS'}->[0] = $count;
688	$self->{'DECLS'}->[$count] = $entity;
689    } else {
690	return if exists($self->{'GENT'}->{$name});
691	$count = $self->{'GENTDECL'}->[0] + 1;
692	$self->{'GENT'}->{$name} = $count;
693	$self->{'GENTDECL'}->[0] = $count;
694	$self->{'GENTDECL'}->[$count] = $entity;
695
696	$count = $self->{'DECLS'}->[0] + 1;
697	$self->{'DECLS'}->[0] = $count;
698	$self->{'DECLS'}->[$count] = $entity;
699    }
700}
701
702sub pent {
703    my $self = shift;
704    my $name = shift;
705    my $count = $self->{'PENT'}->{$name};
706
707    return undef if !$count;
708
709    return $self->{'PENTDECL'}->[$count];
710}
711
712sub gent {
713    my $self = shift;
714    my $name = shift;
715    my $count = $self->{'GENT'}->{$name};
716
717    return undef if !$count;
718
719    return $self->{'GENTDECL'}->[$count];
720}
721
722sub declaration_count {
723    my $self = shift;
724    return $self->{'DECLS'}->[0];
725}
726
727sub declarations {
728    my $self = shift;
729    my @decls = @{$self->{'DECLS'}};
730    shift @decls;
731    return @decls;
732}
733
734# ======================================================================
735
736sub xml_elements {
737    my $self = shift;
738    my $fh   = shift;
739    my %output = ();
740
741    foreach $_ (keys %{$self->{'NOTN'}}) {
742	print $fh $self->{'NOTN'}->{$_}->xml(), "\n";
743    }
744
745    foreach $_ (keys %{$self->{'PENT'}}) {
746	print $fh $self->pent($_)->xml(), "\n";
747    }
748
749    foreach $_ (keys %{$self->{'GENT'}}) {
750	print $fh $self->gent($_)->xml(), "\n";
751    }
752
753    foreach $_ (keys %{$self->{'ELEM'}}) {
754	print $fh $self->{'ELEM'}->{$_}->xml(), "\n";
755	print $fh $self->{'ATTR'}->{$_}->xml(), "\n"
756	    if exists ($self->{'ATTR'}->{$_});
757	$output{$_} = 1;
758    }
759
760    foreach $_ (keys %{$self->{'ATTR'}}) {
761	print $fh $self->{'ATTR'}->{$_}->xml(), "\n" if !$output{$_};
762    }
763}
764
765sub xml {
766    my $self = shift;
767    my $fh = shift;
768    my $count;
769
770    print $fh "<!DOCTYPE dtd PUBLIC \"$DTDPUBID\"\n";
771    print $fh "              \"$DTDSYSID\" [\n";
772
773#    for ($count = 1; $count <= $self->{'PENTDECL'}->[0]; $count++) {
774#	my($pent) = $self->{'PENTDECL'}->[$count];
775#	next if $pent->system() || $pent->public();
776#	print $fh "<!ENTITY ", $pent->name(), " \"&#37;", $pent->name(), ";\">\n";
777#    }
778
779    for ($count = 1; $count <= $self->{'GENTDECL'}->[0]; $count++) {
780	my $gent = $self->{'GENTDECL'}->[$count];
781
782	if ($gent->type() ne 'sdata') {
783	    my $name = $gent->name();
784	    my $text = $gent->text();
785
786	    $text = "&#38;#38;" if $text eq '&#38;';
787	    $text = "&#38;#60;" if $text eq '&#60;';
788
789	    print $fh "<!ENTITY $name \"$text\">\n";
790	} elsif ($gent->type() ne 'pi') {
791	    my $name = $gent->name();
792	    my $text = $gent->text();
793
794	    $text = "&#38;#38;" if $text eq '&#38;';
795	    $text = "&#38;#60;" if $text eq '&#60;';
796
797	    print $fh "<!ENTITY $name \"$text\">\n";
798	}
799    }
800
801    print $fh "]>\n";
802    print $fh "<dtd version='$DTDVERSION'\n";
803    print $fh "     unexpanded='", $self->{'UNEXPANDED_CONTENT'}, "'\n";
804    print $fh "     title=\"", entify($self->{'TITLE'}), "\"\n";
805    print $fh "     namecase-general=\"", $self->{'NAMECASE_GEN'}, "\"\n";
806    print $fh "     namecase-entity=\"", $self->{'NAMECASE_ENT'}, "\"\n";
807    print $fh "     xml=\"", $self->{'XML'}, "\"\n";
808    print $fh "     system-id=\"", entify($self->{'SYSTEM_ID'}), "\"\n";
809    print $fh "     public-id=\"", entify($self->{'PUBLIC_ID'}), "\"\n";
810    print $fh "     declaration=\"", $self->{'DECLARATION'}, "\"\n";
811    print $fh "     created-by=\"DTDParse V$SGML::DTDParse::VERSION\"\n";
812    print $fh "     created-on=\"", scalar(localtime()), "\"\n";
813    print $fh ">\n";
814
815    $self->xml_elements($fh);
816    print $fh "</dtd>\n";
817}
818
819# ======================================================================
820
821sub parse_entity {
822    my $self = shift;
823    my $dtd = shift;
824    my($type, $name) = ('gen', undef);
825    my($public, $system, $text) = ("", "", "");
826    my($tok);
827
828    ($tok, $dtd) = $self->next_token($dtd);
829
830    if ($tok eq '%') {
831	$type = 'param';
832	($tok, $dtd) = $self->next_token($dtd);
833    }
834
835    $name = $tok;
836
837    $tok = $self->peek_token($dtd);
838
839    if ($tok =~ /^[\"\']/) {
840	# we're looking at text...
841	($text, $dtd) = $self->next_token($dtd);
842	$text = $self->trim_quotes($text);
843    } else {
844	($tok, $dtd) = $self->next_token($dtd);
845
846	if ($tok =~ /public/i) {
847	    ($public, $dtd) = $self->next_token($dtd);
848	    $public = $self->trim_quotes($public);
849	    $tok = $self->peek_token($dtd);
850	    if ($tok ne '>') {
851		($system, $dtd) = $self->next_token($dtd);
852		$system = $self->trim_quotes($system);
853	    }
854	} elsif ($tok =~ /system/i) {
855	    ($system, $dtd) = $self->next_token($dtd);
856	    $system = $self->trim_quotes($system);
857	} elsif ($tok =~ /^sdata$/i) {
858	    $type = 'sdata';
859	    ($text, $dtd) = $self->next_token($dtd);
860	    $text = $self->trim_quotes($text);
861	} elsif ($tok =~ /^pi$/i) {
862	    $type = 'pi';
863	    ($text, $dtd) = $self->next_token($dtd);
864	    $text = $self->trim_quotes($text);
865	} elsif ($tok =~ /^cdata$/i) {
866	    $type = 'cdata';
867	    ($text, $dtd) = $self->next_token($dtd);
868	    $text = $self->trim_quotes($text);
869	} else {
870	    die "Error: Unexpected declared entity type ($name): $tok\n";
871	}
872    }
873
874    ($tok, $dtd) = $self->next_token($dtd);
875
876    if ($tok =~ /ndata/i) {
877	($tok, $dtd) = $self->next_token($dtd);
878	# now $tok contains the notation name
879	$type = "ndata $tok";
880	($tok, $dtd) = $self->next_token($dtd);
881	# now $tok should contain the token after the notation
882    } elsif ($tok =~ /cdata/i) {
883	($tok, $dtd) = $self->next_token($dtd);
884	# now $tok contains the notation name
885	$type = "cdata $tok";
886	($tok, $dtd) = $self->next_token($dtd);
887	# now $tok should contain the token after the notation
888    }
889
890    if ($tok ne '>') {
891	print "[[", substr($dtd, 0, 100), "]]\n";
892	die "Error: Unexpected token in ENTITY declaration: $tok\n";
893    }
894
895    print STDERR "ENT: $type $name (P: $public) (S: $system) [$text]\n" if $debug>1;
896
897    $self->status("Entity $name");
898
899    $self->add_entity($name, $type, $public, $system, $text);
900
901    return $dtd;
902}
903
904sub parse_element {
905    my $self = shift;
906    my $dtd = shift;
907    my(@names) = ();
908    my($stagm, $etagm) = ('', '');
909    my $mc = new Text::DelimMatch '\(', '\)[\?\+\*\,]*';
910    my($tok, $cm, $expand, $rest);
911    my($incl, $excl, $name);
912
913    ($tok, $dtd) = $self->next_token($dtd);
914
915    if ($tok =~ /^\(/) {
916	my($pre, $namegrp, $ntok, $rest);
917	($pre, $namegrp, $dtd) = $mc->match($tok . $dtd);
918
919	($ntok, $rest) = $self->next_token($namegrp);
920	while ($ntok) {
921	    if ($ntok =~ /[\|\(\)]/) {
922		# nop
923	    } else {
924		push (@names, $ntok);
925	    }
926	    ($ntok, $rest) = $self->next_token($rest);
927	}
928    } else {
929	push (@names, $tok);
930    }
931
932    # we need to look ahead a little bit here so that we can handle
933    # the case where the start/end tag minimization flags are in
934    # a parameter entity without accidentally expanding parameter
935    # entities in the content model...
936
937    ($tok, $dtd) = $self->next_token($dtd, 1);
938
939    if ($tok =~ /^\%/) {
940	# check to see what this is...
941	($expand, $rest) = $self->next_token($tok);
942
943	if ($expand =~ /^[\-o]/is) {
944	    $stagm = $expand;
945	    $dtd = $rest . $dtd;
946	    ($etagm, $dtd) = $self->next_token($dtd);
947	} else {
948  	    $dtd = $tok . $dtd  if $expand =~ /\S/;
949	}
950    } elsif ($tok =~ /^[\-o]/is) {
951	$stagm = $tok;
952	($etagm, $dtd) = $self->next_token($dtd);
953    } else {
954	$dtd = $tok . $dtd;
955    }
956
957    # ok, now $dtd begins with the content model...
958    ($tok, $dtd) = $self->next_token($dtd, 1);
959
960    if ($tok eq '(') {
961	my($pre, $match);
962	($pre, $match, $dtd) = $mc->match($tok . $dtd);
963	$cm = $match;
964    } else {
965	$cm = $tok;
966    }
967
968    ($tok, $dtd) = $self->next_token($dtd);
969
970    if ($tok eq '-') {
971	my($pre, $match);
972	($pre, $match, $dtd) = $mc->match($tok . $dtd);
973	$excl = $match;
974	($tok, $dtd) = $self->next_token($dtd);
975    }
976
977    if ($tok eq '+') {
978	my($pre, $match);
979	($pre, $match, $dtd) = $mc->match($tok . $dtd);
980	$incl = $match;
981	($tok, $dtd) = $self->next_token($dtd);
982    }
983
984    if ($tok ne '>') {
985	die "Error: Unexpected token in ELEMENT declaration: $tok\n";
986    }
987
988    foreach $name (@names) {
989	$self->status("Element $name");
990
991	if (exists($self->{'ELEM'}->{$name})) {
992	    warn "Warning: Duplicate element declaration for $name ignored.\n";
993	} else {
994	    my $elem = new SGML::DTDParse::DTD::ELEMENT $self, $name, $stagm,$etagm, $cm, $incl, $excl;
995
996	    $self->{'ELEM'}->{$name} = $elem;
997
998	    my $count = $self->{'DECLS'}->[0] + 1;
999	    $self->{'DECLS'}->[0] = $count;
1000	    $self->{'DECLS'}->[$count] = $elem;
1001	}
1002
1003	print STDERR "ELEM: $name = $cm -($excl) +($incl)\n" if $debug>1;
1004    }
1005
1006    return $dtd;
1007}
1008
1009sub parse_attlist {
1010    my $self = shift;
1011    my $dtd = shift;
1012    my(@names) = ();
1013    my $mc = new Text::DelimMatch '\(', '\)[\?\+\*\,]*';
1014    my(@attr) = ();
1015    my($name, $values, $defval, $type, $tok, $notation_hack);
1016
1017    # name   is name
1018    # values is CDATA or an enumeration (for example)
1019    # defval is a default value
1020    # type   is #IMPLIED, #FIXED, #REQUIRED, etc.
1021
1022    ($tok, $dtd) = $self->next_token($dtd);
1023
1024    if ($tok =~ /^\(/) {
1025	my($pre, $namegrp, $ntok, $rest);
1026	($pre, $namegrp, $dtd) = $mc->match($tok . $dtd);
1027
1028	($ntok, $rest) = $self->next_token($namegrp);
1029	while ($ntok) {
1030	    if ($ntok =~ /[\|\(\)]/) {
1031		# nop
1032	    } else {
1033		push (@names, $ntok);
1034	    }
1035	    ($ntok, $rest) = $self->next_token($rest);
1036	}
1037    } else {
1038	push (@names, $tok);
1039    }
1040
1041    print STDERR "\nATTLIST ", join(" ", @names), "\n" if $debug > 2;
1042
1043    # now we're looking at the attribute declarations...
1044
1045    # first grab the whole darn thing, unexpanded...
1046    # this is a tad iffy, perhaps, but I think it always works...
1047    $dtd =~ /^(.*?)>/is;
1048    my $attdecl = $1;
1049
1050    # then we can look at the expanded thing...
1051    ($tok, $dtd) = $self->next_token($dtd);
1052    while ($tok ne '>') {
1053	$name = $tok;
1054	($values, $dtd) = $self->next_token($dtd);
1055
1056	$defval = "";
1057	$type = "";
1058
1059	print STDERR "$name\n" if $debug > 2;
1060
1061	$notation_hack = "";
1062	if ($values =~ /^notation$/i) {
1063	    if ($self->peek_token($dtd)) {
1064		$notation_hack = "NOTATION ";
1065		($values, $dtd) = $self->next_token($dtd);
1066	    }
1067	}
1068
1069	if ($values eq '(') {
1070	    my(@enum) = ();
1071	    my($pre, $enum, $ntok, $rest);
1072
1073	    ($pre, $enum, $dtd) = $mc->match($values . $dtd);
1074	    ($ntok, $rest) = $self->next_token($enum);
1075	    print STDERR "\$rest = $rest\n"  if $debug>4;
1076	    while ($ntok ne '') {
1077		print STDERR "\$ntok = $ntok\n"  if $debug>4;
1078		if ($ntok =~ /[,\|\(\)]/) {
1079		    # nop
1080		} else {
1081		    print STDERR "Adding to \@enum: $ntok\n"  if $debug>4;
1082		    push (@enum, $ntok);
1083		}
1084		($ntok, $rest) = $self->next_token($rest);
1085	    }
1086
1087	    $values = $notation_hack . '(' . join("|", @enum) . ')';
1088	}
1089
1090	print STDERR "\t$values\n" if $debug > 2;
1091
1092	($type, $dtd) = $self->next_token($dtd);
1093
1094	print STDERR "\t$type\n" if $debug > 2;
1095
1096	if ($type =~ /\#FIXED/i) {
1097	    ($defval, $dtd) = $self->next_token($dtd);
1098	    $defval = $self->trim_quotes($defval) if $defval =~ /^[\"\']/;
1099	} elsif ($type !~ /^\#/) {
1100	    $defval = $type;
1101	    $defval = $self->trim_quotes($defval) if $defval =~ /^[\"\']/;
1102	    $type = "";
1103	}
1104
1105	print STDERR "\t$defval\n" if $debug > 2;
1106
1107	push (@attr, $name, $values, $type, $defval);
1108
1109	($tok, $dtd) = $self->next_token($dtd);
1110    }
1111
1112    foreach $name (@names) {
1113	$self->status("Attlist $name");
1114
1115	if (exists($self->{'ATTR'}->{$name})) {
1116	    my $attlist = $self->{'ATTR'}->{$name};
1117	    $attlist->append($self, $name, $attdecl, @attr);
1118	    warn ": duplicate attlist declaration for $name appended.\n";
1119	} else {
1120	    my $attlist = new SGML::DTDParse::DTD::ATTLIST $self, $name, $attdecl, @attr;
1121	    $self->{'ATTR'}->{$name} = $attlist;
1122
1123	    my $count = $self->{'DECLS'}->[0] + 1;
1124	    $self->{'DECLS'}->[0] = $count;
1125	    $self->{'DECLS'}->[$count] = $attlist;
1126	}
1127    }
1128
1129    return $dtd;
1130}
1131
1132sub parse_notation {
1133    my $self = shift;
1134    my $dtd = shift;
1135    my $name = undef;
1136    my($public, $system, $text) = ("", "", "");
1137    my($tok);
1138
1139    ($name, $dtd) = $self->next_token($dtd);
1140    ($tok, $dtd) = $self->next_token($dtd);
1141
1142    if ($tok =~ /public/i) {
1143	($public, $dtd) = $self->next_token($dtd);
1144	$public = $self->trim_quotes($public);
1145
1146	$tok = $self->peek_token($dtd);
1147	if ($tok ne '>') {
1148	    ($system, $dtd) = $self->next_token($dtd);
1149	    $system = $self->trim_quotes($system);
1150	}
1151    } elsif ($tok =~ /system/i) {
1152	$tok = $self->peek_token($dtd);
1153	if ($tok eq '>') {
1154	    $system = "";
1155	} else {
1156	    ($system, $dtd) = $self->next_token($dtd);
1157	    $system = $self->trim_quotes($system);
1158	}
1159    } else {
1160	$text = $self->trim_quotes($tok);
1161    }
1162
1163    ($tok, $dtd) = $self->next_token($dtd);
1164
1165    if ($tok ne '>') {
1166	die "Error: Unexpected token in NOTATION declaration: $tok\n";
1167    }
1168
1169    print STDERR "NOT: $name (P: $public) (S: $system) [$text]\n" if $debug > 1;
1170
1171    $self->status("Notation $name");
1172
1173    if (exists($self->{'NOTN'}->{$name})) {
1174	warn "Warning: Duplicate notation declaration for $name ignored.\n";
1175    } else {
1176	my $notation = new SGML::DTDParse::DTD::NOTATION $self, $name, $public, $system, $text;
1177
1178	$self->{'NOTN'}->{$name} = $notation;
1179
1180	my $count = $self->{'DECLS'}->[0] + 1;
1181	$self->{'DECLS'}->[0] = $count;
1182	$self->{'DECLS'}->[$count] = $notation;
1183    }
1184
1185    return $dtd;
1186}
1187
1188sub parse_markedsection {
1189    my $self = shift;
1190    my $dtd = shift;
1191    my $mc = new Text::DelimMatch '<!\[.*?\[', '\]\]\>';
1192    my($tok, $pre, $match, $ms);
1193
1194    ($tok, $dtd) = $self->next_token($dtd);
1195
1196    ($pre, $ms, $dtd) = $mc->match("<![$tok" . $dtd);
1197
1198    if ($tok =~ /^include$/i) {
1199	$ms =~ /^<!\[.*?\[(.*)\]\]\>$/s;
1200	$dtd = $1 . $dtd;
1201    }
1202
1203    return $dtd;
1204}
1205
1206sub peek_token {
1207    my $self = shift;
1208    my $dtd = shift;
1209    my $return_peref = shift;
1210    my $tok;
1211
1212    ($tok, $dtd) = $self->next_token($dtd, $return_peref);
1213
1214    return $tok;
1215}
1216
1217sub next_token {
1218    my $self = shift;
1219    my $dtd = shift;
1220    my $return_peref = shift;
1221
1222    $dtd =~ s/^\s*//sg;
1223
1224    if ($dtd =~ /^<!--.*?-->/s) {
1225	# comment declaration
1226	return $self->next_token($'); # '
1227    }
1228
1229    if ($dtd =~ /^--.*?--/s) {
1230	# comment
1231	return $self->next_token($'); # '
1232    }
1233
1234    if ($dtd =~ /^<\?.*?>/s) {
1235	# processing instruction
1236	return $self->next_token($'); # '
1237    }
1238
1239    if ($dtd =~ /^<!\[/s) {
1240	# beginning of a marked section
1241	print STDERR "TOK: [$&]\n" if $debug > 3;
1242	return ($&, $'); # '
1243    }
1244
1245    if ($dtd =~ /^[\(\)\-\+\|\&\,\>]/) {
1246	# beginning of a model group, or incl., or excl., or end decl
1247	print STDERR "TOK: [$&]\n" if $debug > 3;
1248	return ($&, $'); # '
1249    }
1250
1251    if ($dtd =~ /^[\"\']/) {
1252	# quoted string
1253	$dtd =~ /^(([\"\'])(.*?)\2)/s;
1254	print STDERR "TOK: [$1]\n" if $debug > 3;
1255	return ($&, $'); # '
1256    }
1257
1258    if ($dtd =~ /^\%([a-zA-Z0-9\_\-\.]+);?/) {
1259	# peref
1260	print STDERR "TOK: [$1]\n" if $debug > 3;
1261	if ($return_peref) {
1262	    return ("%$1;", $'); # '
1263	} else {
1264	    my $repltext = $self->entity_repl($1);
1265	    $dtd = $repltext . $'; # '
1266	    return $self->next_token($dtd);
1267	}
1268    }
1269
1270    if ($dtd =~ /^([^\s\|\&\,\(\)\[\]\>\%]+)/s) {
1271	# next non-space sequence
1272	print STDERR "TOK: [$1]\n" if $debug > 3;
1273	return ($1, $'); # '
1274    }
1275
1276    if ($dtd =~ /^(\%)/s) {
1277	# lone % (for param entity declarations)
1278	print STDERR "TOK: [$1]\n" if $debug > 3;
1279	return ($1, $');
1280    }
1281
1282    print STDERR "TOK: <<none>>\n" if $debug > 3;
1283    return (undef, $dtd);
1284}
1285
1286sub entity_repl {
1287    my $self = shift;
1288    my $name = shift;
1289    my $entity = $self->pent($name);
1290    local(*F, $_);
1291
1292    die "Error: %$name; undeclared.\n" if !$entity;
1293
1294    if ($entity->{'PUBLIC'} || $entity->{'SYSTEM'}) {
1295	my $id = "";
1296	my $filename = "";
1297
1298	if ($entity->{'PUBLIC'}) {
1299	    $id = $entity->{'PUBLIC'};
1300	    $filename = $self->{'CAT'}->public_map($id);
1301	}
1302
1303	if (!$filename && $entity->{'SYSTEM'}) {
1304	    $id = $entity->{'SYSTEM'};
1305	    $filename = $self->{'CAT'}->system_map($id);
1306	}
1307
1308	if (!defined($filename)) {
1309	    die "%Error: $name; ($id): not found in catalog.\n";
1310	}
1311
1312	if ($self->debug()) {
1313	    $self->status("Loading $id\n\t($filename)", 1);
1314	} else {
1315	    $self->status("Loading $id", 1);
1316	}
1317
1318	$filename = $self->resolve_relativesystem($filename);
1319
1320	$self->add_to_searchpath($filename);
1321
1322	open (F, $filename) ||
1323	    die qq{\n%Error: $name;: Unable to open "$filename": $! \n};
1324	{
1325	    local $/;
1326	    $_ = <F>;
1327	}
1328	close (F);
1329	return $_;
1330    } else {
1331	return $entity->{'TEXT'};
1332    }
1333}
1334
1335sub trim_quotes {
1336    my $self = shift;
1337    my $text = shift;
1338
1339    if ($text =~ /^\"(.*)\"$/s) {
1340	$text = $1;
1341    } elsif ($text =~ /^\'(.*)\'$/s) {
1342	$text = $1;
1343    } else {
1344	die "Error: Unexpected text: $text\n";
1345    }
1346
1347    return $text;
1348}
1349
1350sub fix_entityrefs {
1351    my $self = shift;
1352    my $text = shift;
1353
1354    if ($text ne "") {
1355	my $value = "";
1356
1357	# make sure all entity references end in semi-colons
1358	while ($text =~ /^(.*?)([\&\%]\#?[-.:_a-z0-9]+;?)(.*)$/si) {
1359	    my $entref = $2;
1360	    $value .= $1;
1361	    $text = $3;
1362
1363	    if ($entref =~ /\;$/s) {
1364		$value .= $entref;
1365	    } else {
1366		$value .= $entref . ";";
1367	    }
1368	}
1369
1370	$text = $value . $text;
1371    }
1372
1373    return $text;
1374}
1375
1376sub expand_entities {
1377    my $self = shift;
1378    my $text = shift;
1379
1380    while ($text =~ /\%(.*?);/) {
1381	my $pre = $`;
1382	my $pename = $1;
1383	my $post = $'; # '
1384
1385	$text = $pre . $self->entity_repl($pename) . $post;
1386    }
1387
1388    return $text;
1389}
1390
1391sub parse_decl {
1392    my $self = shift;
1393    my $decl = shift;
1394    local (*F, $_);
1395    my $xml = 0;
1396    my $namecase_gen = 1;
1397    my $namecase_ent = 0;
1398
1399    if (!open (F, $decl)) {
1400	$self->status(qq{Warning: Failed to load declaration "$decl": $!}, 1);
1401	return ($xml, $namecase_gen, $namecase_ent);
1402    }
1403
1404    {
1405	local $/;
1406	$_ = <F>;
1407    }
1408    close (F);
1409
1410#    <!SGML -- SGML Declaration for valid XML documents --
1411#     "ISO 8879:1986 (WWW)"
1412
1413    s/--.*?--//gs; # get rid of comments
1414    if (!/<!SGML/) {
1415	return ($xml, $namecase_gen, $namecase_ent);
1416    }
1417
1418    if (/<!SGML\s*\"([^\"]+\(WWW\))\"/is) {
1419	# this is XML
1420	return (1, 0, 0);
1421    }
1422
1423    if (/namecase\s+/is) {
1424	$_ = $'; # '
1425	my @words = split(/\s+/is, $_);
1426	my $done = 0;
1427
1428	while (!$done) {
1429	    my $word = shift @words;
1430
1431	    if ($word =~ /^general$/i) {
1432		$word = shift @words;
1433		$namecase_gen = ($word =~ /^yes$/i);
1434	    } elsif ($word =~ /^entity$/i) {
1435		$word = shift @words;
1436		$namecase_ent = ($word =~ /^yes$/i);
1437	    } else {
1438		$done = 1;
1439	    }
1440	}
1441    } else {
1442	print "No namecase declaration???\n";
1443    }
1444
1445    return ($xml, $namecase_gen, $namecase_ent);
1446}
1447
1448sub add_to_searchpath {
1449    my $self = shift;
1450    my $file = shift;
1451    my $searchpath = ".";
1452    my $found = 0;
1453
1454    $file =~ s/\\/\//sg;
1455    $searchpath = $1 if $file =~ /^(.*)\/[^\/]+$/;
1456
1457    foreach my $path (@{$self->{'SEARCHPATH'}}) {
1458	$found = 1 if $path eq $searchpath;
1459    }
1460
1461    push (@{$self->{'SEARCHPATH'}}, $searchpath)
1462	if !$found && $searchpath;
1463}
1464
1465sub resolve_relativesystem {
1466    my $self = shift;
1467    my $system = shift;
1468    my $found = 0;
1469    my $resolved = $system;
1470
1471    return $system if ($system =~ /^\//) || ($system =~ /^[a-z]:[\\\/]/);
1472
1473    foreach my $path (@{$self->{'SEARCHPATH'}}) {
1474	if (-f "$path/$system") {
1475	    $found = 1;
1476	    $resolved = "$path/$system";
1477	    last;
1478	}
1479    }
1480
1481    if ($found) {
1482	$self->add_to_searchpath($resolved);
1483    } else {
1484	$self->status("Could not resolve relative path: $system", 1);
1485    }
1486
1487    return $resolved;
1488}
1489
1490sub status {
1491    my $self = shift;
1492    my $msg = shift;
1493    my $persist = shift;
1494
1495    return if !$self->verbose();
1496
1497    if ($self->debug() || $self->{'NEWLINE'}) {
1498	print STDERR "\n";
1499    } else {
1500	print STDERR "\r";
1501	print STDERR " " x $self->{'LASTMSGLEN'};
1502	print STDERR "\r";
1503    }
1504
1505    print STDERR $msg;
1506
1507    $self->{'LASTMSGLEN'} = length($msg);
1508    $self->{'NEWLINE'} = $persist || (length($msg) > 79);
1509}
1510
15111;
1512
1513__END__
1514
1515=head1 NAME
1516
1517SGML::DTDParse::DTD - Parse an SGML or XML DTD.
1518
1519=head1 SYNOPSIS
1520
1521  use SGML::DTDParse::DTD;
1522
1523  $dtd = SGML::DTDParse::DTD->new( %options );
1524  $dtd->parse($dtd_file);
1525  $dtd->xml($file_handle);
1526
1527=head1 DESCRIPTION
1528
1529B<SGML::DTDParse::DTD> is the main module for parsing a DTD.  Normally,
1530this module is not used directly with the program L<dtdparse|dtdparse>
1531being the prefered usage model for parsing a DTD.
1532
1533=head1 CONSTRUCTOR METHODS
1534
1535TODO.
1536
1537=head1 METHODS
1538
1539TODO.
1540
1541=head1 SEE ALSO
1542
1543L<dtdparse|dtdparse>
1544
1545See L<SGML::DTDParse|SGML::DTDParse> for an overview of the DTDParse package.
1546
1547=head1 PREREQUISITES
1548
1549B<Text::DelimMatch>
1550
1551=head1 AVAILABILITY
1552
1553E<lt>I<http://dtdparse.sourceforge.net/>E<gt>
1554
1555=head1 AUTHORS
1556
1557Originally developed by Norman Walsh, E<lt>ndw@nwalsh.comE<gt>.
1558
1559Earl Hood E<lt>earl@earlhood.comE<gt> picked up support and
1560maintenance.
1561
1562=head1 COPYRIGHT AND LICENSE
1563
1564See L<SGML::DTDParse|SGML::DTDParse> for copyright and license information.
1565
1566