Added footnotes support!
[project-aon.git] / scripts / gblint.pl
1 #!/usr/bin/perl -Tw
2 #
3 # Each new section id requires adding it to the list (e.g. improved
4 # disciplines).
5 #
6 ###############################################################################
7 use strict;
8
9 my $endOfDTD = 0;
10
11 my %sectionDocLookup = (
12   '_unknown' => '_unknown',
13   'toc'      => 'toc',
14   'title'    => 'title',
15   'dedicate' => 'dedicate',
16   'acknwldg' => 'acknwldg',
17   'credits'  => 'acknwldg',
18   'coming'   => 'coming',
19   'tssf'     => 'tssf',
20   'gamerulz' => 'gamerulz',
21   'discplnz' => 'discplnz',
22   'camflage' => 'discplnz',
23   'hunting'  => 'discplnz',
24   'sixthsns' => 'discplnz',
25   'tracking' => 'discplnz',
26   'healing'  => 'discplnz',
27   'wepnskll' => 'discplnz',
28   'mndshld'  => 'discplnz',
29   'mndblst'  => 'discplnz',
30   'anmlknsp' => 'discplnz',
31   'mindomtr' => 'discplnz',
32   'mksumary' => 'discplnz',
33   'anmlctrl' => 'discplnz',
34   'curing'   => 'discplnz',
35   'invsblty' => 'discplnz',
36   'psisurge' => 'discplnz',
37   'psiscrn'  => 'discplnz',
38   'dvnation' => 'discplnz',
39   'wpnmstry' => 'discplnz',
40   'anmlmstr' => 'discplnz',
41   'deliver'  => 'discplnz',
42   'assimila' => 'discplnz',
43   'hntmstry' => 'discplnz',
44   'pthmnshp' => 'discplnz',
45   'kaisurge' => 'discplnz',
46   'kaiscrn'  => 'discplnz',
47   'nexus'    => 'discplnz',
48   'gnosis'   => 'discplnz',
49   'magi'     => 'discplnz',
50   'kalchemy' => 'discplnz',
51   'powers'   => 'powers',
52   'lessmcks' => 'powers',
53   'alchemy'  => 'powers',
54   'sorcery'  => 'powers',
55   'enchant'  => 'powers',
56   'elementl' => 'powers',
57   'prophecy' => 'powers',
58   'psycmncy' => 'powers',
59   'evcation' => 'powers',
60   'highmcks' => 'powers',
61   'thamtrgy' => 'powers',
62   'telergy'  => 'powers',
63   'physirgy' => 'powers',
64   'theurgy'  => 'powers',
65   'visionry' => 'powers',
66   'necrmncy' => 'powers',
67   'staff'    => 'powers',
68   'moonston' => 'powers',
69   'equipmnt' => 'equipmnt',
70   'howcarry' => 'equipmnt',
71   'howmuch'  => 'equipmnt',
72   'howuse'   => 'equipmnt',
73   'cmbtrulz' => 'cmbtrulz',
74   'evasion'  => 'cmbtrulz',
75   'lorecrcl' => 'lorecrcl',
76   'lcbonus'  => 'lorecrcl',
77   'levels'   => 'levels',
78   'primate'  => 'levels',
79   'tutelary' => 'levels',
80   'mentora'  => 'levels',
81   'scion'    => 'levels',
82   'archmstr' => 'levels',
83   'prncpln'  => 'levels',
84   'imprvdsc' => 'imprvdsc',
85   'guardian' => 'imprvdsc',
86   'sunkght'  => 'imprvdsc',
87   'sunlord'  => 'imprvdsc',
88   'kaiwisdm' => 'kaiwisdm',
89   'sage'     => 'sage',
90   'numbered' => 'numbered',
91   'passing'  => 'passing',
92   'part1'    => 'part1',
93   'part2'    => 'part2',
94   'map'      => 'map',
95   'action'   => 'action',
96   'crsumary' => 'crsumary',
97   'smevazn'  => 'crsumary',
98   'crtable'  => 'crtable',
99   'random'   => 'random',
100   'errata'   => 'errata',
101   'errintro' => 'errata',
102   'errerr'   => 'errata',
103   'footnotz' => 'footnotz',
104   'illstrat' => 'illstrat',
105   'primill'  => 'illstrat',
106   'secill'   => 'illstrat',
107   'license'  => 'license',
108   'lic-pre'  => 'license',
109   'lic-1'    => 'license',
110   'lic-1-0'  => 'license',
111   'lic-1-1'  => 'license',
112   'lic-1-2'  => 'license',
113   'lic-1-3'  => 'license',
114   'lic-1-4'  => 'license',
115   'lic-1-5'  => 'license',
116   'lic-1-6'  => 'license',
117   'lic-1-7'  => 'license',
118   'lic-2'    => 'license',
119   'lic-2-0'  => 'license',
120   'lic-2-1'  => 'license',
121   'lic-2-2'  => 'license',
122   'lic-2-3'  => 'license',
123   'lic-2-4'  => 'license',
124   'lic-2-5'  => 'license',
125   'lic-3'    => 'license',
126   'lic-3-0'  => 'license',
127   'lic-3-1'  => 'license',
128   'lic-4'    => 'license',
129   'lic-4-0'  => 'license',
130   'lic-5'    => 'license',
131   'lic-5-0'  => 'license',
132   'lic-6'    => 'license',
133   'lic-6-0'  => 'license',
134   'lic-6-1'  => 'license',
135 );
136
137 my $errorCount = 0;
138 my $maxErrorCount = 0;
139 my $skipLines = 0;
140 my $initials = "??";
141 my $useCorr = 0;
142
143 while( $#ARGV > -1 && $ARGV[ 0 ] =~ /^-/ ) {
144   if( $ARGV[ 0 ] eq "-e" && $#ARGV > 0 ) {
145     shift @ARGV;
146     $maxErrorCount = shift @ARGV;
147   }
148   elsif( $ARGV[ 0 ] eq "-s" && $#ARGV > 0 ) {
149     shift @ARGV;
150     $skipLines = shift @ARGV;
151   }
152   elsif( $ARGV[ 0 ] eq "-i" && $#ARGV > 0 ) {
153     shift @ARGV;
154     $initials = shift @ARGV;
155   }
156   elsif( $ARGV[ 0 ] eq "--use-corr" ) {
157     shift @ARGV;
158     $useCorr = 1;
159   }
160 }
161
162 my $lineNumber = 1;
163 my $currentSection = "_unknown";
164
165 while( my $line = <> ) {
166   my @section = ( $line =~ /<section[^>]+id="([^"]*)"/g );
167   if( $#section > 0 ) { die( "Multiple sections begin at line $lineNumber\n" ); }
168   elsif( $#section == 0 ) {
169     if( $section[ 0 ] =~ /^sect[[:digit:]]+$/ ) {
170       $currentSection = $section[ 0 ];
171     }
172     else {
173       $currentSection = $sectionDocLookup{$section[ 0 ]};
174     }
175   }
176
177   if( $skipLines >= $lineNumber ) {
178     ++$lineNumber;
179     next;
180   }
181
182   ##### Unescaped Characters
183   if( $line =~ /[\200-\377]/ ) {
184     if( $line =~ /\221/ ) { &printError( "ne", $currentSection, $lineNumber, "unescaped left single quotation mark(s)", "\221", "<quote>...</quote> or \&apos;" ); }
185     if( $line =~ /\222/ ) { &printError( "ne", $currentSection, $lineNumber, "unescaped right single quotation mark(s)", "\222", "<quote>...</quote> or \&apos;" ); }
186     if( $line =~ /\223/ ) { &printError( "ne", $currentSection, $lineNumber, "unescaped left double quotation mark(s)", "\223", "<quote>...</quote>" ); }
187     if( $line =~ /\224/ ) { &printError( "ne", $currentSection, $lineNumber, "unescaped right double quotation mark(s)", "\224", "<quote>...</quote>" ); }
188     if( $line =~ /\226/ ) { &printError( "ne", $currentSection, $lineNumber, "unescaped endash(es)", "\226", "&endash;" ); }
189     if( $line =~ /\227/ ) { &printError( "ne", $currentSection, $lineNumber, "unescaped emdash(es)", "\227", "&emdash;" ); }
190     if( $line =~ /([ \200-\220 \225 \230-\377 ])/gx ) {
191       &printError( "ne", $currentSection, $lineNumber, "unescaped non-ASCII character(s); first found only", "$1" );
192     }
193 }
194   if( $line =~ /'/ ) { &printError( "ne", $currentSection, $lineNumber, "unescaped apostrophe(s)", "'", "\&apos; or <quote>...</quote>" ); }
195   if( $line =~ /`/ ) { &printError( "ne", $currentSection, $lineNumber, "backtick(s)", "`", "\&apos; or <quote>...</quote>" ); }
196
197   # tab
198
199   if( $line =~ /\t/ ) { &printError( "ne", $currentSection, $lineNumber, "TAB character found; convert to equivalent SPACEs" ); }
200
201   # ampersand
202   if( $line =~ /\&\s/ ) { &printError( "ne", $currentSection, $lineNumber, "possible malformed ampersand or escape sequence", "&", "&ampersand;" ); }
203
204   # emdash
205   if( $line =~ /\s-\s/ ) { &printError( "ne", $currentSection, $lineNumber, "probable malformed emdash", " - ", "\&emdash;" ); }
206   if( $line =~ /(?<!\!)--(?!>)/ ) { &printError( "ne", $currentSection, $lineNumber, "probable malformed emdash", "--", "\&emdash;" ); }
207
208   # endash
209   if( $line =~ /([0-9])-([0-9]+)(?![^<]+>)/ ) { &printError( "ne", $currentSection, $lineNumber, "possible malformed endash", "$1-$2", "$1\&endash;$2" ); }
210
211   # ellipsis
212   if( $line =~ /(\.\s*\.(\s*\.)?)/ ) { &printError( "ne", $currentSection, $lineNumber, "possible malformed ellipsis", "$1", "\&ellips; or \&lellips;" ); }
213   if( $line =~ /(\&ellips;)([^<[:space:]])/ ) { &printError( "ne", $currentSection, $lineNumber, "\&ellips; without space afterwards", "$1$2", "\&ellips; $2" ); }
214   if( $line =~ /([[:space:]]\&ellips;)/ ) { &printError( "ne", $currentSection, $lineNumber, "\&ellips; with preceding space", "$1", "\&ellips;" ); }
215   if( $line =~ /([^>])(\&lellips;)/ ) { &printError( "ne", $currentSection, $lineNumber, "possible \&lellips; used in place of \&ellips;", "$1$2", "$1\&ellips;" ); }
216   if( $line =~ /(>\&ellips;)/ ) { &printError( "ne", $currentSection, $lineNumber, "possible \&ellips; used in place of \&lellips;", "$1", ">\&lellips;" ); }
217
218   # thinspace
219   if( $line =~ m{(</?quote>)\1} ) { &printError( "ne", $currentSection, $lineNumber, "probable candidate for thinspace", "$1$1", "$1\&thinspace;$1" ); }
220   if( $line =~ m{(<quote>)(\&apos;)} || $line =~ m{(\&apos;)(</quote>)} ) { &printError( "ne", $currentSection, $lineNumber, "probable canidate for thinspace", "$1$2", "$1\&thinspace;$2" ); }
221
222   # blankline
223   if( $line =~ /(__+)/ ) { &printError( "ne", $currentSection, $lineNumber, "probable candidate for blankline", "$1", "\&blankline;" ); }
224
225   # percent
226   #  It should be safe to assume that there will be a "]>" at the end of
227   #  internal DTD subset. Previous to the end of the internal DTD subset
228   #  "%" has special meaning and shouldn't be detected.
229   if( $line =~ /]>/ ) { $endOfDTD = 1; }
230   if( $endOfDTD && $line =~ /\%/ ) { &printError( "ne", $currentSection, $lineNumber, "possible candidate for percent", "\%", "\&percent;" ); }
231
232   ##### OCR Errors
233
234   if( $line =~ m{([^.?!:);>]</((p)|(choice))>)} ) { &printError( "??", $currentSection, $lineNumber, "possible missing punctuation", "$1" ); }
235   if( $line =~ /((?<![iIeE]\.[eg])[.?!]\s+[a-z])/ ) { &printError( "??", $currentSection, $lineNumber, "possible bad initial capitalization", "$1" ); }
236   if( $line =~ /([a-zA-Z][0-9][a-zA-Z])/ ) { &printError( "??", $currentSection, $lineNumber, "probable replacement of number for letter", "$1" ); }
237   if( $line =~ />[^<]*-[[:space:]]/ ) { &printError( "??", $currentSection, $lineNumber, "possible retained end-of-line hyphen(s)" ); }
238
239   ##### Obsolete Markup
240
241   if( $line =~ /\&lsquot;/ ) { &printError( "ne", $currentSection, $lineNumber, "probable obsolete markup", "\&lsquot;", "<quote>" ); }
242   if( $line =~ /\&rsquot;/ ) { &printError( "ne", $currentSection, $lineNumber, "probable obsolete markup", "\&rsquot;", "</quote>" ); }
243   if( $line =~ /\&ldquot;/ ) { &printError( "ne", $currentSection, $lineNumber, "probable obsolete markup", "\&ldquot;", "<quote>" ); }
244   if( $line =~ /\&rdquot;/ ) { &printError( "ne", $currentSection, $lineNumber, "probable obsolete markup", "\&rdquot;", "</quote>" ); }
245   if( $line =~ /\&quot;/ ) { &printError( "ne", $currentSection, $lineNumber, "possible obsolete markup", "\&quot;", "<quote> or </quote>" ); }
246   if( $line =~ /(\&link.[^;]+;)/ ) { &printError( "ne", $currentSection, $lineNumber, "probable obsolete markup", "$1", "use <bookref.../> instead" ); }
247   if( $line =~ /\&([^[:space:]]+);/ ) {
248     unless( $1 =~ /^(?:link|inclusion)/ ) {
249       &printError( "ne", $currentSection, $lineNumber, "probable obsolete markup", "\&$1\;", "<ch.$1/>" );
250     }
251   }
252   if( $line =~ /(<a([^>]*) class="footnote"(.*?)>)/ )  { &printError( "ne", $currentSection, $lineNumber, "obsolete markup", "$1", "<footref$2$3>" ); }
253
254   ##### Character Attributes
255   if( $line =~ /[^>]((CLOSE\s+)?COMBAT\sSKILL)/ || $line =~ /((CLOSE\s+)?COMBAT\sSKILL)[^<]/ ) {
256     &printError( "ne", $currentSection, $lineNumber, "possible missing markup", "$1", "<typ class=\"attribute\">$1</typ>" );
257   }
258   if( $line =~ /[^>](ENDURANCE)/ || $line =~ /(ENDURANCE)[^<]/ ) {
259     &printError( "ne", $currentSection, $lineNumber, "possible missing markup", "ENDURANCE", "<typ class=\"attribute\">ENDURANCE</typ>" );
260   }
261   if( $line =~ /[^>](WILLPOWER)/ || $line =~ /(WILLPOWER)[^<]/ ) {
262     &printError( "ne", $currentSection, $lineNumber, "possible missing markup", "WILLPOWER", "<typ class=\"attribute\">WILLPOWER</typ>" );
263   }
264
265   ##### Links
266   if( $line =~ /[^>](random[[:space:]]+number[[:space:]]+table)/i ) {
267     &printError( "ne", $currentSection, $lineNumber, "possible missing markup", "$1", "<a idref=\"random\">$1</a>" );
268   }
269   if( $line =~ /[^>](action[[:space:]]+charts?)/i ) {
270     &printError( "ne", $currentSection, $lineNumber, "possible missing markup", "$1", "<a idref=\"action\">$1</a>" );
271   }
272
273   ##### Others
274   if( $line =~ m{<!--(?!/?ERRTAG)} ) { &printError( "ne", $currentSection, $lineNumber, "XML comment found (check for editor comments)" ); }
275   if( $line =~ /([[:upper:]]{5,})/ &&
276       $line !~ /<signpost>/ &&
277       $1 ne "ENDURANCE" &&
278       $1 ne "COMBAT" &&
279       $1 ne "WILLPOWER" &&
280       $1 ne "CLOSE" &&
281       $1 ne "XVIII" &&
282       $1 ne "DOCTYPE" &&
283       $1 ne "ENTITY" &&
284       $1 ne "ERRTAG" ) { &printError( "ne", $currentSection, $lineNumber, "possible <signpost> needed", "$1", "<signpost>$1</signpost>" ); }
285
286   #####
287   ++$lineNumber;
288 }
289
290 unless( $endOfDTD || $skipLines > 0 ) { print "End of document reached without finding end of internal DTD subset \"]>\".\n"; }
291
292 ################################################################################
293
294 sub printError {
295   my ($type, $section, $line, $message, $original, $corrected) = @_;
296   my $report = "";
297
298   if( $useCorr ) {
299     $report = "($type) $section: ";
300     if( defined $original ) { $report .= "$original "; }
301     if( defined $corrected ) { $report .= "-> $corrected "; }
302     $report .= "[$initials: $message <line $line>]\n";
303   }
304   else {
305     $report = "line $line ($section): $message";
306     if( defined $original ) { $report .= " \"$original\""; }
307     if( defined $corrected ) { $report .= " ($corrected)"; }
308     $report .= "\n";
309   }
310
311   print $report;
312
313   ++$errorCount;
314   if( $maxErrorCount > 0 && $errorCount > $maxErrorCount ) { die "Maximum number of errors ($maxErrorCount) exceeded. Quitting.\n"; }
315 }