home *** CD-ROM | disk | FTP | other *** search
/ HTML - Publishing on the Internet / html_cdrom.iso / tools / html / windows / check / xtraclnk.pl < prev   
Perl Script  |  1995-01-20  |  23KB  |  485 lines

  1. #!/usr/local/bin/perl
  2. #xtraclnk.pl: Extracts hypertext links from HTML files; isolates text contained
  3. #             in <A> and <TITLE> elements.
  4. #
  5. # Typical use:
  6. #
  7. #   perl xtraclnk.pl [options] infiles.html > outfile
  8. #
  9. # Where options have the form "option=value", as discussed below (command line
  10. # options other than ``title='' and ``loc='' work the same way as those of the
  11. # htmlchek program in this distribution).
  12. #
  13. # Whenever xtraclnk.pl encounters an <A HREF="URL">Text</A> link in an input
  14. # file, it copies this to the output.  Whenever xtraclnk.pl encounters an
  15. # <A NAME="name">Text</A> anchor in an input file, it copies this as an
  16. # <A HREF="currentfile.html#name">Text</A> link _to_ the current input file.
  17. # Finally, the contents of a <TITLE>Text</TITLE> element are copied as an
  18. # <A HREF="currentfile.html">Text</A> link _to_ the current input file.
  19. # Each link in the ouput occupies exactly one line.
  20. #
  21. # This program was suggested by an idea of John Harper; what he had in mind,
  22. # I think, was to use this as part of a CGI script which would dynamically
  23. # construct an HTML document with links to all files with a title or anchors
  24. # that contain text matching a user-specified search pattern.  However,
  25. # xtraclnk.pl also has some value as an HTML style debugging tool: if you have
  26. # used a lot of context-dependent titles like "Intro" and meaningless anchor
  27. # text like "Click Here", this will be very apparent when you view the HTML
  28. # document (derived with xtraclnk.pl using the ``title='' option) which
  29. # contains only the text inside titles and anchors in your other HTML
  30. # documents.  This program can also be used to enforce consistency in link text:
  31. # if there is random variation between different <A HREF="...">LinkText</A>
  32. # elements which all point towards the same resource, this will be apparent
  33. # when the output of xtraclnk.pl is sorted.  Also, by looking over the sorted
  34. # output of <tt>xtraclnk.pl</tt>, it becomes relatively easy to detect mistaken
  35. # links, that point to someplace other than what was intended.
  36. #
  37. #  If you apply xtraclnk.pl to a list of filenames that are all specified
  38. # relative to the current directory then all the references to files in
  39. # subordinate directories will be expressed from the point of view of the top
  40. # directory (i.e. relative URL pathnames will have the current directory as
  41. # starting point).  Under Unix, you can use:
  42. #
  43. #   perl xtraclnk.pl `find . -name \*.html -print` > output
  44. #
  45. # Since xtraclnk.pl is a hacked-down version of the htmlchek error checker, it
  46. # is rather robust in its handling of incorrect HTML code (but it generally has
  47. # the same limitations that htmlchek does with metachar=2).  Though it is not
  48. # a general-purpose error checker like htmlchek, xtraclnk.pl does return
  49. # errormessages about HTML errors connected with its functioning (note that it
  50. # ignores all tags in  a file except <A>, <BASE>, <TITLE>, and the
  51. # ALT="..." attribute valuex of <IMG>).
  52. #
  53. # Command-line
  54. #   options:
  55. #
  56. #  dirprefix=...     A string to be prefixed to URL's in the output links, in
  57. #                 order to resolve relative URL's into absolute URL's.
  58. #                 (See the htmlchek documentation for the complexities of use.)
  59. #
  60. #  usebase=1         Take the prefix from a <BASE HREF="..."> tag in each file.
  61. #
  62. #  sugar=1           Use the Unix ``filename: linenumber:'' format in reporting
  63. #                 errors.
  64. #
  65. #  title=...         Make the output file a valid HTML document, with <br> at
  66. #                 the end of each line, and a title as specified.  Error
  67. #                 messages (if any) appear as HTML comments in the outputfile.
  68. #                 (If this title= option is not specified on the command line,
  69. #                 the output will tailored for human readability, and will not
  70. #                 really be an HTML file.)
  71. #                    Note that the output with title= will still be a HTML file
  72. #                 if you run it though the ``sort'' and ``uniq'' filters.  It
  73. #                 will also remain HTML if you run it through ``grep'' -- as
  74. #                 long as you keep the first and last lines; for example (under
  75. #                 Unix):
  76. #                         perl xtraclnk.pl title="Link Stuff" *.html > out
  77. #                         head -1 out > linkfile.html
  78. #                         egrep 'pattern' out >> linkfile.html
  79. #                         tail -1 out >> linkfile.html
  80. #
  81. #  loc=...           Whether or not to include the location (input filename and
  82. #                 linenumber) from which each output link is derived.  If you
  83. #                 don't include locations, it's hard to tell where bad links
  84. #                 came from; if you do include locations, the output will be
  85. #                 larger, and running the output though sort and uniq won't be
  86. #                 as useful for detecting inconsistent link text.
  87. #                    By default, source locations are not included in the
  88. #                 output. A value of loc=1 causes locations to be included.
  89. #                 A value of loc=hide (or anything beginning with the three
  90. #                 characters "hid...") will include locations as HTML comments,
  91. #                 if the title= option has alson been specified.
  92. #
  93. # Copyright 1994, 1995 by H. Churchyard, churchh@uts.cc.utexas.edu -- freely
  94. # redistributable.
  95. #
  96. #  Version 1.0 12/15/94
  97. #  Version 1.1 12/18/94 -- improve HTML-icity of "title=" option output, etc.
  98. #  Version 1.11 12/19/94 -- squashed minor bugs.   Was informally made
  99. # available by HTTP from uts.cc.utexas.edu.
  100. #  Version 1.2 1/9/95 -- Added loc= option, include <IMG ALT="..."> text in
  101. # links.  Included in htmlchek 4.0 release.
  102. #
  103. eval "exec /usr/local/bin/perl -S $0 $*"
  104.     if $running_under_some_shell; # This emulates #! processing on NIH machines
  105. #
  106. # Setup:
  107. #
  108. $known{'A'} = 1; $known{'IMG'} = 1; $known{'TITLE'} = 1; $known{'/A'} = 1;
  109. $known{'/TITLE'} = 1; $known{'BASE'} = 1; $pair{'A'} = 1;  $pair{'TITLE'} = 1;
  110. #
  111. &initscalrs();
  112. $usebase = 0; $dirprefix = ''; $sugar = 0; $title = ''; $loc = 0;
  113. #process any FOO=bar switches
  114. eval '$'.$1.'$2;' while $ARGV[0] =~ /^(usebase=|dirprefix=|sugar=|title=|loc=)(.*)/ && shift;
  115. $[ = 1;                 # set array base to 1
  116. $, = ' ';               # set output field separator
  117. $\ = "\n";              # set output record separator
  118. foreach $X (@ARGV) {
  119.     if ($X =~ /^[^=]+=/) {
  120.         print STDERR "Apparent misspelled or badly-placed command-line option $&";
  121.         print STDERR "Attempting to continue anyway...";}}
  122. #
  123. if ($title) {
  124.     print " <html><head><title>$title</title></head><body><h1>$title</h1>";
  125.     $E = '<br>'; $gt = '>'; $lt = '<'; $A = '<!-- '; $Z = ' -->';
  126.     if ($loc =~ /^HID/i)
  127.       {$AA = '<!-- '; $ZZ = ' -->';}
  128.     else
  129.       {$AA = ''; $ZZ = '';}}
  130. else {
  131.     $E = ''; $gt = '>'; $lt = '<'; $A = ''; $Z = ''; $AA = ''; $ZZ = '';}
  132. #
  133. # Main
  134. #
  135. $stuperlRS = $/;
  136. while (<>) {
  137.     if ($_ =~ /$stuperlRS$/o) { # strip record separator, allow for last line to
  138.         chop;}                  # be unterminated.
  139.     if (($.-$FNRbase) == 1) {
  140.         $fn = $ARGV;
  141.         # Next line is Unix-specific
  142.         $fn =~ s/^\.\///;
  143.         $nampref = ($dirprefix . $fn . '#');
  144.         $lochpref = ($dirprefix . $fn);
  145.         if ($fn =~ /.\//) {
  146.             $fromroot = $fn; $fromroot =~ s/\/[^\057]*$/\//;}
  147.         else {
  148.             $fromroot = '';}
  149.         $fromroot=($dirprefix . $fromroot);}
  150.     if ($sugar) {$S = ($fn . ': ' . ($.-$FNRbase) . ': ');}
  151.     if ($loc) {$L = ($fn . ' ' . ($.-$FNRbase));}
  152.     $lastbeg = 0; $currsrch = 1; $txtbeg = 1;
  153.     while ((((substr($_, $currsrch) =~ /[<>]/) eq 1) &&
  154.       ($RSTART = length($`)+1)) != 0) {
  155.         $currsrch = ($currsrch + $RSTART);
  156.         if (substr($_, ($currsrch - 1), 1) eq '<') {
  157.             if ($state) {
  158.                 print $A . $S . "Multiple `$lt' without `$gt' ERROR!", &crl() .
  159.                   $Z;}
  160.             else {
  161.                 if (($currsrch > length($_)) ||
  162.                   (substr($_, $currsrch, 1) =~ /^[ \t]$/)) {
  163.                     print $A . $S .
  164.                       "Whitespace after `$lt': Incorrect SGML syntax ERROR!",
  165.                       &crl() . ", Ignoring$Z";}
  166.                 else {
  167.                     if (($nestvar) && ($currsrch > ($txtbeg + 1))) {
  168.                         $line = ($line . substr($_, $txtbeg,
  169.                           ($currsrch - ($txtbeg + 1))));}
  170.                     $lastbeg = $currsrch; $state = 1;
  171.                     $lasttag = ''; $lastopt = '';}}}
  172.         else {
  173.             if (substr($_, ($currsrch - 1), 1) eq '>') {
  174.                 if ($state == 0) {
  175.                     next;}        #`>' without `<'
  176.                 else {
  177.                     &parsetag($currsrch - 1);
  178.                     if (($inquote) || ($inequal)) {
  179.                         &malft();}
  180.                     if ($optfree) {
  181.                         &misstest();}
  182.                     if (($lasttag eq 'A') && (!$wasname) && (!$washref)) {
  183.                         print $A . $S . $lt .
  184.                           "A$gt tag occurred without reference (NAME,HREF,ID) option ERROR!",
  185.                           &crl() . $Z;}
  186.                     if (($wasname > 1) || ($washref > 1)) {
  187.                         print $A . $S .
  188.                           'Multiple reference (NAME,ID;HREF) options ERROR!',
  189.                           &crl(), 'on tag', $lasttag . $Z;}
  190.                     $txtbeg = $currsrch;
  191.                     $state = 0; $continuation = 0;}}
  192.             else {
  193.                 print $A . $S . 'Internal error', &crl(), 'ignore' . $Z;}}}
  194.     if (($state == 1) || (($lastbeg == 0) && ($continuation == 1))) {
  195.         &parsetag(length($_) + 1);
  196.         $continuation = 1;}
  197.     else {
  198.         if (($nestvar) && (!$state) && ($txtbeg <= length($_))) {
  199.           $line = ($line . substr($_, $txtbeg) . ' ');}
  200.         else {
  201.           $line = ($line . ' ');}}}
  202. continue {
  203.     $FNRbase = $. if eof;}
  204. #
  205. # End-of-file routine.
  206. #
  207. if ($. > 0) {&endit()};
  208. if ($title) {print '<hr></body></html>';}
  209. #
  210. #
  211. # parsetag() communicates with main() through these global variables:
  212. # - $lastbeg (zero if no `<' ocurred on line, otherwise points to character
  213. #   immediately after the last `<' encountered).
  214. # - $state (one if unresolved `<', zero otherwise).
  215. # - $continuation (one if unresolved `<' from previous line, zero otherwise),
  216. # - $inquote (one if inside option quotes <tag opt="...">).
  217. #
  218. sub parsetag {
  219.     local($inp) = @_;
  220.     if (!$lastbeg) {
  221.         $lastbeg = 1;}
  222.     $numf = (@arr = split(' ', substr($_, $lastbeg, ($inp - $lastbeg))));
  223.     if ($numf == 0) {
  224.         if (!$continuation) {
  225.             print $A . $S . "Blank $lt$gt ERROR!", &crl() . $Z;
  226.             $state = 0;}
  227.         return;}
  228.     else {
  229.         if (!$continuation) {
  230.             $arr[1] =~ tr/a-z/A-Z/;
  231.             $lasttag = $arr[1];
  232.             if (defined $known{$arr[1]}) {
  233.                 if ($arr[1] =~ /^\//) {
  234.                     # </TAG> found
  235.                     $arr[1] =~ s/^\///;
  236.                     if (defined $pair{$arr[1]}) {
  237.                             if (($nestvar <= 0) || ($lev{$arr[1]} <= 0)) {
  238.                                 print $A . $S . 'Extraneous /' . $arr[1],
  239.                                   'tag without preceding', $arr[1], 'tag ERROR!',
  240.                                   &crl() . ', Ignoring' . $Z;}
  241.                             else {--$nestvar; --$lev{$arr[1]};
  242.                                   if ($arr[1] eq 'TITLE') {&doout($lochpref);}
  243.                                   else
  244.                                       {if ($currf[2]) {&doout($currf[2]);}
  245.                                        if ($currf[3]) {&doout($currf[3]);}}}}}
  246.                 else {
  247.                     # <TAG> found
  248.                     if ($arr[1] ne 'IMG') {$line = '';}
  249.                     ++$lev{$arr[1]};
  250.                     if (defined $pair{$arr[1]}) {
  251.                         $currf[2] = ''; $currf[3] = '';
  252.                         ++$nestvar;
  253.                         if (($lev{$arr[1]} > 1) || ($nestvar > 1)) {
  254.                             print $A . $S . 'Nesting ERROR!', &crl(),
  255.                             "on tag $arr[1]" . $Z;}}}}
  256.                 $startf = 2; $inquote = 0; $inequal = 0; $optfree = 0;
  257.                 $wasopt = 0; $wasname = 0; $washref = 0;}
  258.         else {
  259.             $startf = 1;}
  260.         # Remainder of stuff in <...> after tag word
  261.         if (defined $known{$lasttag}) {
  262.             for ($i = $startf; $i <= $numf; ++$i) {
  263.                 if ((!$inequal) && (!$inquote)) {
  264.                     if (($arr[$i] =~
  265.                       /^[^=\042]*(=\042[^\042]*\042)?$/) ||
  266.                       ($arr[$i] =~ /^[^=\042]*=(\042)?[^\042]*$/)) {
  267.                         if (($optfree) &&
  268.                           (($arr[$i] =~ /^=[^=\042][^=\042]*$/) ||
  269.                           ($arr[$i] =~ /^=\042[^\042]*\042$/))) {
  270.                             if (!$malftag) {
  271.                                 $arr[$i] =~ s/^\075//;
  272.                                 if ($arr[$i] =~ /\042/) {
  273.                                     &optvalproc($arr[$i],1);}
  274.                                 else {&optvalproc($arr[$i],0);}}
  275.                             $optfree = 0; ++$tagwarn;}
  276.                         else {
  277.                             if (($optfree) && (($arr[$i] =~ /^=\042/) ||
  278.                               ($arr[$i] eq '='))) {
  279.                                 $inequal = 1; ++$tagwarn;}
  280.                             @arr2 = split(/=/, $arr[$i], 2);
  281.                             if ($arr2[1] eq '') {
  282.                                 if (!$inequal) {
  283.                                     print $A . $S . 'Null tag option ERROR!',
  284.                                       &crl(), "on tag $lasttag" . $Z;
  285.                                 $malftag = 1;}}
  286.                             else {
  287.                                 if ($optfree) {
  288.                                     &misstest();}
  289.                                 $arr2[1] =~ tr/a-z/A-Z/;
  290.                                 $optfree = 1; ++$wasopt;
  291.                                 $malftag = 0; $optvalstr = '';
  292.                                 if ($lasttag =~ /^\//) {
  293.                                     print $A . $S . 'Option on closing tag',
  294.                                     $lasttag, 'Warning!', &crl() . $Z;}
  295.                                 else {
  296.                                     $lastopt = $arr2[1];}}
  297.                             if ($arr[$i] =~ /^[^=\042][^=\042]*=$/) {
  298.                                 $inequal = 1;}
  299.                             if ($arr[$i] =~ /[\075]/) {
  300.                                 $optvalstr = $arr[$i];
  301.                                 $optvalstr =~ s/^[^=]*=//;}
  302.                             $stuperltmp = $arr[$i];
  303.                             $Q = ($stuperltmp =~ s/\042//g);
  304.                             if ($Q == 1) {
  305.                                 $inquote = 1;}
  306.                             if (($optvalstr)&&(!$inequal)&&(!$inquote)) {
  307.                                 $optfree = 0;
  308.                                 if (!$malftag) {
  309.                                     &optvalproc($optvalstr,$Q);}}}}
  310.                     else {
  311.                         &malft();}}
  312.                 else {
  313.                     if (($inequal) && (!$inquote)) {
  314.                         ++$tagwarn;
  315.                         if ($arr[$i] =~ /\042/) {
  316.                             if ($arr[$i] =~ /^\042[^\042]*(\042)?$/) {
  317.                                 $stuperltmp = $arr[$i];
  318.                                 if (($stuperltmp =~ s/\042//g) == 2) {
  319.                                     if (!$malftag) {
  320.                                         $stuperltmp =~ s/^\075//;
  321.                                         &optvalproc($stuperltmp,1);}
  322.                                     $inequal = 0; $optfree = 0;}
  323.                                 else {
  324.                                     $optvalstr = $arr[$i];
  325.                                     $inquote = 1;}}
  326.                             else {
  327.                                 &malft();}}
  328.                         else {
  329.                             if ($arr[$i] !~ /[\075]/) {
  330.                                 if (!$malftag) {
  331.                                     &optvalproc($arr[$i],0);}
  332.                                 $inequal = 0; $optfree = 0;}
  333.                             else {
  334.                                 &malft();}}}
  335.                     else {
  336.                         if ($arr[$i] =~ /\042/) {
  337.                             $inquote = 0; $inequal = 0; $optfree = 0;
  338.                             if ($arr[$i] !~ /^[^\042]*\042$/) {
  339.                                 &malft();}
  340.                             else {
  341.                                 $optvalstr = ($optvalstr . ' ' . $arr[$i]);
  342.                                 if (!$malftag) {
  343.                                   &optvalproc($optvalstr,1);}}}
  344.                         else {
  345.                             $optvalstr = ($optvalstr . ' ' . $arr[$i]);}}}}}
  346.         return;}}
  347. #
  348. #
  349. # Return as much location information as possible in diagnostics:
  350. #
  351. # Current location:
  352. sub crl {
  353.     if (($fn)&&($fn ne '-')) {
  354.         return ('at line ' . ($.-$FNRbase) . " of file \042" . $fn . "\042");}
  355.     else {
  356.         return ('at line ' . $.);}}
  357. #
  358. # End of file location:
  359. sub ndl {
  360.     if (($fn)&&($fn ne '-')) {
  361.         return ("at END of file \042" . $fn . "\042");}
  362.     else {
  363.         return 'at END';}}
  364. #
  365. # Error message returned from numerous places in the program...
  366. #
  367. sub malft {
  368.     print $A . $S . 'Malformed tag option ERROR!', &crl(), 'on tag', $lasttag .
  369.       $Z;
  370.     $malftag = 1;}
  371. #
  372. #
  373. #Check for non-kosher null options:
  374. #
  375. sub misstest {
  376.     if ((($lasttag eq 'A') && ($lastopt eq 'NAME')) || ($lastopt eq 'HREF') ||
  377.       ($lastopt eq 'ID')) {
  378.         print $A . $S . 'Missing reference option value', &crl(),
  379.           "on tag $lasttag, option $lastopt" . $Z;}}
  380. #
  381. #
  382. sub doout {
  383.     local($href) = @_;
  384.     $line =~ s/[ \t][ \t]+/ /g; $line =~ s/\t/ /g;
  385.     $line =~ s/^ //; $line =~ s/ $//;
  386.     if ($line eq '') {
  387.       $line = '[ EMPTY ANCHOR TEXT ]';}
  388.     print "<A HREF=\042" . $href . "\042>" . $line . '</A>', $AA . $L . $ZZ . $E;}
  389. #
  390. # This subroutine receives the raw option value string, for every tag option
  391. # that does have a value.  It does some errorchecking and cleanup, and sets
  392. # the URL or name of the current anchor.
  393. #
  394. sub optvalproc {
  395.     local($val, $quoted) = @_;
  396.     $currfn = 0;
  397.     if ($quoted) {
  398.         $val =~ s/\042//g; $val =~ s/^ //; $val =~ s/ $//;}
  399.     if ($lasttag eq 'IMG') {
  400.         if (($lastopt eq 'ALT') && ($val =~ /[^ \t]/)) {
  401.             $line = ($line . " [ $val ] ");}}
  402.     elsif ($lasttag eq 'BASE') {
  403.         if (($usebase) && ($lastopt eq 'HREF')) {
  404.             if (($quoted) && ($val) && ($val ne '=') && ($val !~ /[^ ] [^ ]/)) {
  405.                 $nampref = ($val . '#'); $lochpref = $val;
  406.                 if ($val =~ /.\//) {
  407.                     $fromroot = $val;
  408.                     $fromroot =~ s/\/[^\057]*$/\//;}
  409.                 else {
  410.                     $fromroot = '';}}
  411.             else {
  412.                print $A . $S . "Bad $lt" . "BASE HREF=\042...\042$gt", &crl() .
  413.                  ', Ignoring' . $Z;}}}
  414.     else {
  415.         if ((($lasttag eq 'A') && ($lastopt eq 'NAME')) || ($lastopt eq 'ID')) {
  416.             $currfn = 2; ++$wasname;
  417.             if ($val =~ /^#/) {
  418.                 print $A . $S . "Invalid #-initial location \042" .
  419.                   $val . "\042 ERROR!", &crl(), 'on tag', $lasttag,
  420.                   'option', $lastopt . $Z;}}
  421.         else {
  422.             if ($lastopt eq 'HREF') {
  423.                 $currfn = 3; ++$washref;}}}
  424.     if ($currfn) {
  425.         if (!$quoted) {
  426.             print $A . $S . 'Unquoted reference option value Warning!', &crl(),
  427.               "on tag $lasttag, option $lastopt$Z";}
  428.         if ($val =~ /[^ ] [^ ]/) {
  429.             print $A . $S . 'Whitespace in reference option value Warning!',
  430.               &crl(), "on tag $lasttag, option $lastopt$Z";}
  431.         else {
  432.             if ($val eq '') {
  433.                 print $A . $S . 'Null reference option value ERROR!', &crl(),
  434.                   "on tag $lasttag, option $lastopt$Z";}
  435.             else {
  436.                 # Skip the residue of Malformed Tag Option cases;  OK to do
  437.                 # this, since "=" is not a valid URL;  However, a minor bug
  438.                 # is that <A NAME="="> will not be checked, and will not
  439.                 # result in any errormessage.
  440.                 if ($val ne '=') {
  441.                     if ($currfn == 2) {
  442.                         $val = ($nampref . $val);}
  443.                     else {
  444.                         if (($currfn == 3) && ($val =~ /^#/)) {
  445.                             $val = ($lochpref . $val);}
  446.                         else {
  447.                             if ($val =~ /^http:[^\057]*$/) {
  448.                                 $val =~ s/^http://;}
  449.                             if (($val !~ /^[^\057]*:/) && ($val !~ /^\//)) {
  450.                                 if ($val =~ /^~/) {
  451.                                     print $A . $S .
  452.                                       "Relative URL beginning with '~' Warning!",
  453.                                       &crl(),"on tag $lasttag option $lastopt$Z";}
  454.                                 else {
  455.                                     $val = ($fromroot . $val);}}}}
  456.                     # This monstrosity supports "../" in URL's:
  457.                     while ($val =~ /\057[^\057]*[^\057]\057\.\.\057/) {
  458.                         $val =~ s/\057[^\057]*[^\057]\057\.\.\057/\057/;}
  459.                     if (($val =~ /[:\057]\.\.\057/) || ($val =~ /^\.\.\057/)) {
  460.                         print $A . $S . "Unresolved \042../\042 in URL Warning!",
  461.                           &crl(), "on tag $lasttag option $lastopt$Z";}
  462.                     $currf[$currfn] = $val;}}}}}
  463. #
  464. #
  465. # Start each file with a clean slate.
  466. #
  467. sub initscalrs {
  468.     $state = 0; $continuation = 0; $nestvar = 0; $S = ''; $L = ''; $line = '';}
  469. #
  470. #
  471. #
  472. sub endit {
  473.     if ($sugar) {$S = ($fn . ': END: ');}
  474.     if ($continuation) {
  475.         print $A . $S . "Was awaiting a `$gt' ERROR!", &ndl() . $Z;}
  476.     foreach $X (sort(keys %pair)) {
  477.         if ($lev{$X} > 0) {
  478.             print $A . $S . "Pending unresolved $lt" .
  479.               "x$gt without $lt/x$gt ERROR!", &ndl(), 'on tag', $X . $Z;}}
  480.     #Reinitialize for next file
  481.     &initscalrs();
  482.     undef %lev;}
  483. #-=-  -=-  -=-  -=-  -=-  -=-  -=-  -=-  -=-  -=-  -=-  -=-  -=-  -=-  -=-  -=-
  484. ##EOF
  485.