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/\&/\&/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/\&/\&/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/&/&/sg; 396 $cdata =~ s/</</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(), " \"%", $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;" if $text eq '&'; 787 $text = "&#60;" if $text eq '<'; 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;" if $text eq '&'; 795 $text = "&#60;" if $text eq '<'; 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