File Coverage

blib/lib/HTML/GMUCK.pm
Criterion Covered Total %
statement 16 260 6.1
branch 1 140 0.7
condition 0 91 0.0
subroutine 5 27 18.5
pod 14 14 100.0
total 36 532 6.7


line stmt bran cond sub pod time code
1             package HTML::GMUCK;
2              
3             # $Id: GMUCK.pm,v 1.24 2007/04/01 20:26:55 scop Exp $
4              
5 1     1   18442 use strict;
  1         3  
  1         45  
6              
7             require 5.006;
8              
9 1         159 use vars qw($VERSION $Tag_End $Tag_Start $Non_Tag_End
10             $URI_Attrs $End_Omit $All_Elems
11             $Min_Elems $Compat_Elems $Min_Attrs $MIME_Type @MIME_Attrs
12             %Req_Attrs $All_Attrs $Depr_Elems @Depr_Attrs @Int_Attrs
13 1     1   5 @Length_Attrs @Fixed_Attrs);
  1         8  
14              
15 1     1   4 use Carp qw(carp);
  1         11  
  1         53  
16              
17 1     1   5 no warnings 'utf8';
  1         1  
  1         110  
18              
19             BEGIN
20             {
21              
22 1     1   8 $VERSION = sprintf("%d.%02d", q$Revision: 1.24 $ =~ /(\d+)\.(\d+)/);
23              
24             # --- Preload regexps.
25              
26 1         8 my $tmp = '';
27 1         1 my %tmp = ();
28              
29 1 50       2686 if (! do 'HTML/GMUCK/regexps.pl') {
30 0   0       my $err = $! || $@;
31 0           die "Error reading HTML/GMUCK/regexps.pl: $err";
32             }
33              
34             }
35              
36             # ----- Constructors -------------------------------------------------------- #
37              
38             sub new
39             {
40 0     0 1   my ($class, %attr) = @_;
41              
42 0   0       my $this = bless({
43             _mode => undef,
44             _xml => undef,
45             _xhtml => undef,
46             _html => undef,
47             _tab_width => undef,
48             _num_errors => undef,
49             _num_warnings => undef,
50             _quote => undef,
51             _min_attrs => undef,
52             },
53             (ref($class) || $class));
54              
55 0           my $tab_width = delete($attr{tab_width});
56 0 0         $tab_width = 4 unless defined($tab_width);
57 0 0         $this->tab_width($tab_width) or $this->tab_width(4);
58              
59 0           my $mode = delete($attr{mode});
60 0 0         $mode = 'XHTML' unless defined($mode);
61 0 0         $this->mode($mode) or $this->mode('XHTML');
62              
63 0           my $quote = delete($attr{quote});
64 0 0         $this->quote(defined($quote) ? $quote : '"');
65              
66 0           $this->min_attributes(delete($attr{min_attributes}));
67              
68 0           $this->reset();
69              
70 0 0         if (my @unknown = keys(%attr)) {
71 0           carp("** Unrecognized attributes: " . join(',', sort(@unknown)));
72             }
73              
74 0           return $this;
75             }
76              
77             # ---------- Check: deprecated ---------------------------------------------- #
78              
79 0     0 1   sub deprecated { return shift->_wrap('_deprecated', @_);}
80              
81             sub _deprecated
82             {
83 0     0     my ($this, $line) = @_;
84 0           my @errors = ();
85              
86 0           while ($line =~ /\b(document\.location)\b/go) {
87 0           push(@errors, { col => $this->_pos($line, pos($line) - length($1)),
88             type => 'W',
89             mesg =>
90             'document.location is deprecated, use window.location ' .
91             'instead',
92             },
93             );
94             }
95              
96             # ---
97              
98 0 0         return @errors unless $this->{_html};
99              
100             # Optimization.
101 0 0         return @errors unless $line =~ $Tag_Start;
102              
103             # ---
104              
105 0           while ($line =~ /
106             <
107             (\/?)
108             (
109             ($Depr_Elems)
110             (?:$|$Tag_End|\s)
111             )
112             /giox) {
113 0 0         push(@errors, { col => $this->_pos($line, pos($line) - length($2)),
114             elem => $3,
115             mesg => 'deprecated element' . ($1 ? ' end' : ''),
116             type => 'W',
117             },
118             );
119             }
120              
121             # ---
122              
123 0           foreach my $re (@Depr_Attrs) {
124              
125 0           while ($line =~ /$re/g) {
126 0           my ($m, $elem, $attr) = ($1, $2, $3);
127 0 0         if ($attr) {
128 0           push(@errors, { col => $this->_pos($line, pos($line) - length($m)),
129             elem => $elem,
130             attr => $attr,
131             type => 'W',
132             mesg => 'deprecated attribute for this element',
133             },
134             );
135             }
136             }
137             }
138              
139 0           return @errors;
140             }
141              
142             # ----- Check: attributes --------------------------------------------------- #
143              
144 0     0 1   sub attributes { return shift->_wrap('_attributes', @_); }
145              
146             sub _attributes
147             {
148 0     0     my ($this, $line) = @_;
149 0 0         return () unless $this->{_html};
150              
151 0           my @errors = ();
152              
153             # ---
154              
155 0 0         my $type = $this->{_xhtml} ? 'E' : 'W';
156              
157             # BUG: Does not catch non-lowercase minimized attributes, like CHECKED.
158 0           while ($line =~ /
159             (?:^\s*|(?<=[\w\"\'])\s+)
160             (
161             ($All_Attrs)
162             =
163             (.\S?) # Would like to see ['"], possibly backslashed.
164             )
165             /giox) {
166              
167 0           my ($pos, $att, $q) = (pos($line) - length($1), $2, $3);
168              
169 0 0         if ($att ne lc($att)) {
170 0           push(@errors, { col => $this->_pos($line, $pos),
171             attr => $att,
172             type => $type,
173             mesg => 'non-lowercase attribute',
174             },
175             );
176             }
177              
178 0 0         if (my $tq = $this->{_quote}) {
179 0           my $pos = $this->_pos($line, $pos + length($att) + 1);
180 0 0         if ($q =~ /\\?([\"\'])/o) {
181 0 0         if ($1 ne $tq) {
182 0           push(@errors, { col => $pos,
183             type => 'W',
184             attr => $att,
185             mesg => "quote attribute values with $tq",
186             },
187             );
188             }
189             } else {
190 0           push(@errors, { col => $pos,
191             attr => $att,
192             type => 'W',
193             mesg => 'unquoted value',
194             },
195             );
196             }
197             }
198             }
199              
200             # ---
201              
202             # Optimization.
203 0 0         return @errors unless $line =~ /$Tag_Start\w../o;
204              
205             # ---
206              
207 0           foreach my $re (@Int_Attrs) {
208              
209 0           my $msg = 'value should be an integer: "%s"';
210              
211 0           while ($line =~ /$re/g) {
212 0           my ($m, $el, $att, $q, $val) = ($1, $2, $3, $4, $5);
213 0           my $lel = lc($el);
214 0           my $latt = lc($att);
215              
216 0 0 0       if ($val !~ /^\d+$/o &&
217             $val !~ /[\\\$\(\[]/o # bogus protection
218             ) {
219              
220             # Special case: img->border only in HTML 4
221 0 0 0       next if ($this->{_xhtml} && $lel eq 'img' && $latt eq 'border');
      0        
222              
223 0           push(@errors, { col => $this->_pos($line, pos($line) - length($m)),
224             type => 'E',
225             mesg => sprintf($msg, $val),
226             elem => $el,
227             attr => $att,
228             },
229             );
230             }
231             }
232             }
233              
234             # ---
235              
236 0           foreach my $re (@Length_Attrs) {
237              
238 0           my $msg = 'value should be an integer or a percentage: "%s"';
239              
240 0           while ($line =~ /$re/g) {
241              
242 0           my ($m, $el, $att, $q, $val) = ($1, $2, $3, $4, $5);
243              
244 0 0 0       if ($val !~ /^\d+%?$/o &&
245             $val !~ /[\\\$\(\[]/o # bogus protection
246             ) {
247              
248 0           push(@errors, { col => $this->_pos($line, pos($line) - length($m)),
249             type => 'E',
250             mesg => sprintf($msg, $val),
251             elem => $el,
252             attr => $att,
253             },
254             );
255             }
256             }
257             }
258              
259             # ---
260              
261 0           foreach (@Fixed_Attrs) {
262              
263 0           my ($re, $vre, $vals) = @$_;
264 0 0         $vre = $this->{_xml} ? qr/$vre/ : qr/$vre/i;
265 0           my $msg = 'invalid value: "%s", should be %s"%s"';
266              
267 0           while ($line =~ /$re/g) {
268              
269 0           my ($m, $el, $att, $q, $val) = ($1, $2, $3, $4, $5);
270              
271 0 0 0       if ($val !~ $vre &&
272             $val !~ /[\\\$\(\[]/o # bogus protection
273             ) {
274              
275 0           my $latt = lc($att);
276 0           my $lel = lc($el);
277              
278             # Special case: html->xmlns and pre,script,style->xml:space XHTML-only
279 0 0 0       next if (! $this->{_xhtml} &&
      0        
280             (($lel eq 'html' && $latt eq 'xmlns') ||
281             ($latt eq 'xml:space' && $lel =~ /^(pre|s(cript|tyle))$/o)));
282              
283 0 0         push(@errors, { col => $this->_pos($line, pos($line) - length($m)),
284             type => 'E',
285             mesg => sprintf($msg, $val,
286             ($vals =~ /\|/o) ? 'one of ' : '',
287             $vals),
288             elem => $el,
289             attr => $att,
290             },
291             );
292             }
293             }
294             }
295              
296             # ---
297              
298             #
299             # Note that minimized attributes are forbidden only in XHTML, but it
300             # is legal to have them in HTML too.
301             #
302             # Not doing this check inside <>'s would result in too much bogus.
303             #
304 0 0         if ($this->{_min_attrs}) {
305 0           while ($line =~ /
306             <
307             $Non_Tag_End+?
308             \s
309             (
310             ($Min_Attrs)
311             ([=\s]|$Tag_End)
312             )
313             /giox) {
314 0           my ($m, $attr, $eq) = ($1, $2, $3);
315 0 0         if ($eq ne '=') {
316 0           push(@errors, { col => $this->_pos($line, pos($line) - length($m)),
317             attr => $attr,
318             type => $type,
319             mesg => 'minimized attribute',
320             },
321             );
322             }
323             }
324             }
325              
326             # ---
327              
328 0           while (my ($attr, $re) = each(%Req_Attrs)) {
329              
330 0           my $msg = 'missing required attribute: "%s"';
331              
332             # Parens: 1: for pos(), 2:element, 3: attribute (or undef if not found)
333 0           while ($line =~ /$re/g) {
334              
335 0           my ($m, $el, $att) = ($1, $2, $3);
336              
337 0 0         if (! $att) {
338              
339 0           my $lel = lc($el);
340              
341             # Special case: @name not required for input/@type's submit and reset
342 0 0 0       next if ($lel eq 'input' && $attr eq 'name' &&
      0        
343             # TODO: this is crap
344             $line =~ /\stype=(\\?[\"\'])?(submi|rese)t\b/io);
345              
346             # Special case: map/@id required only in XHTML 1.0+
347 0 0 0       next if ($lel eq 'map' && $attr eq 'id' && ! $this->{_xhtml});
      0        
348              
349 0           push(@errors, { col => $this->_pos($line, pos($line) - length($m)),
350             type => 'E',
351             mesg => sprintf($msg, $attr),
352             elem => $el,
353             },
354             );
355             }
356             }
357             }
358              
359 0           return @errors;
360             }
361              
362             # ----- Check: MIME types --------------------------------------------------- #
363              
364 0     0 1   sub mime_types { return shift->_wrap('_mime_types', @_); }
365              
366             sub _mime_types
367             {
368 0     0     my ($this, $line) = @_;
369 0 0         return () unless $this->{_html};
370              
371             # Optimization. " 372 0 0         return () unless $line =~ /$Tag_Start.{6}/o;
373              
374 0           my @errors = ();
375 0           my $msg = 'bad media type: "%s"';
376 0           my $jsmsg =
377             'not recommended media type: "%s", see RFC 4329 (and also CAVEATS in the HTML::GMUCK manual page)';
378              
379 0           foreach my $re (@MIME_Attrs) {
380              
381 0           while ($line =~ /$re/g) {
382              
383 0           my ($elem, $attr, $m, $mtype) = ($1, $2, $4, $5);
384 0           my $pos = $this->_pos($line, pos($line) - length($m));
385              
386 0 0 0       if ($mtype !~ $MIME_Type) {
    0 0        
387 0           push(@errors, { col => $pos,
388             type => 'E',
389             elem => $elem,
390             attr => $attr,
391             mesg => sprintf($msg, $mtype),
392             },
393             );
394             } elsif (lc($elem) eq 'script' &&
395             $mtype =~ /(ecm|jav)ascript/io &&
396             lc($mtype) !~ '^application/(ecm|jav)ascript$') {
397 0           push(@errors, { col => $pos,
398             type => 'W',
399             elem => $elem,
400             attr => $attr,
401             mesg => sprintf($jsmsg, $mtype),
402             },
403             );
404             }
405             }
406             }
407              
408 0           return @errors;
409             }
410              
411             # ----- Check: elements ----------------------------------------------------- #
412              
413 0     0 1   sub elements { return shift->_wrap('_elements', @_); }
414              
415             sub _elements
416             {
417 0     0     my ($this, $line) = @_;
418 0 0         return () unless $this->{_html};
419              
420 0           my @errors = ();
421              
422             # ---
423              
424 0 0         my $type = $this->{_xhtml} ? 'E' : 'W';
425 0           my $msg = 'non-lowercase element%s';
426              
427 0           while ($line =~ /
428             <
429             (\/?)
430             (
431             ($All_Elems)
432             (\s|$Tag_End|\Z) # \Z) because $) would screw my indentation :)
433             )
434             /giox) {
435 0           my ($slash, $pos, $elem) = ($1, pos($line) - length($2), $3);
436 0 0         if ($elem ne lc($elem)) {
437 0 0         push(@errors, { col => $this->_pos($line, $pos),
438             type => $type,
439             elem => $elem,
440             mesg => sprintf($msg, ($slash ? ' end' : '')),
441             },
442             );
443             }
444             }
445              
446             # ---
447              
448 0           $msg = 'missing end tag';
449              
450 0           while ($line =~ /
451             <
452             (
453             ($End_Omit)
454             .*?
455             $Tag_End
456             [^<]*
457             <
458             (.?)
459             ($End_Omit)
460             )
461             /giox) {
462 0           my ($m, $start, $slash, $end) = ($1, $2, $3, $4);
463 0 0 0       if ((lc($start) eq lc($end) && $slash ne '/') ||
      0        
464             # TODO: this needs tuning. See t/002endtag.t, line 6.
465             (lc($start) ne lc($end))) {
466 0           push(@errors, { col => $this->_pos($line, pos($line) - length($m)),
467             mesg => $msg,
468             elem => $start,
469             type => 'W',
470             },
471             );
472             }
473             }
474              
475             # ---
476              
477             # We also allow a backslashed "/", they're common in eg. Perl regexps.
478             # Consider
479             # $foo =~ s/bar/baz
/;
480 0           while ($line =~ /
481             < # TODO: Do we really need to see a known
482             ($All_Elems) # element here?
483             .*?
484             (\s?\\?\/?($Tag_End))
485             /giox) {
486 0           my ($el, $end, $m) = ($1, $2);
487 0           my $pos = $this->_pos($line, pos($line) - length($3));
488 0 0         if ($end =~ m|/>$|o) {
489 0 0 0       if ($this->{_xhtml} &&
    0 0        
      0        
490             $el !~ /^$Compat_Elems$/io && # These don't apply here, see later.
491             $end !~ m|\s\\?/|o) {
492 0           push(@errors, { col => $pos,
493             type => 'W',
494             mesg => 'use space before "/>" for compatibility',
495             elem => $el,
496             },
497             );
498             } elsif (! $this->{_xml} && $end =~ m|/>$|o) {
499 0           push(@errors, { col => $pos,
500             type => 'E',
501             mesg => 'element end "/>" is allowed in X(HT)ML only',
502             elem => $el,
503             },
504             );
505             }
506             }
507             }
508              
509             # ---
510              
511             # Check for missing " />".
512 0 0         if ($this->{_xhtml}) {
513              
514 0           while ($line =~ /
515             <
516             ($Min_Elems)
517             .*?
518             (\/?$Tag_End)
519             /giox) {
520 0           my ($el, $end) = ($1, $2);
521 0 0         if ($end ne '/>') {
522 0           push(@errors, { col => $this->_pos($line, pos($line) - length($end)),
523             elem => $el,
524             mesg => 'missing " />"',
525             type => 'E',
526             },
527             );
528             }
529             }
530              
531 0           while ($line =~ /
532             <
533             ($Compat_Elems)
534             .*?
535             (\s?.?$Tag_End)
536             /giox) {
537 0           my ($el, $end) = ($1, $2);
538 0           $msg = 'use "<%s>" instead of <%s for compatibility';
539 0 0         if ($end =~ m|(\s?/>)$|o) {
540 0           my $e = lc($el);
541 0           push(@errors, { col => $this->_pos($line, pos($line) - length($end)),
542             elem => $el,
543             mesg => sprintf($msg, $e, $e, $e . $1),
544             type => 'W',
545             },
546             );
547             }
548             }
549             }
550              
551 0           return @errors;
552             }
553              
554             # ----- Check: entities ----------------------------------------------------- #
555              
556             # Check for unterminated entities in URIs (usually & instead of &).
557 0     0 1   sub entities { return shift->_wrap('_entities', @_);}
558              
559             sub _entities
560             {
561 0     0     my ($this, $line) = @_;
562 0 0         return () unless $this->{_html};
563              
564             # Optimization. "src=&" is the shortest we know of.
565 0 0         return () unless $line =~ /\w{3}=./;
566              
567 0           my @errors = ();
568 0           my $msg = 'unterminated entity: %s';
569              
570 0           while ($line =~ /
571             (?:^|\s)
572             ($URI_Attrs)
573             =
574             (
575             (.+?)
576             (?:
577             (?
578             \s # A space terminates here.
579             (?!%\]) # Protect Template Toolkit's " %]".
580             |
581             $Tag_End
582             )
583             )
584             /giox) {
585              
586 0           my ($attr, $pos, $val) = ($1, pos($line) - length($2), $3);
587              
588 0           while ($val =~ /(&([^;]*?))[=\"\'\#\s]/go) {
589 0           push(@errors, { col =>
590             $this->_pos($line, $pos + pos($val) - length($2) - 1),
591             type => 'E',
592             mesg => sprintf($msg, $1),
593             attr => $attr,
594             },
595             );
596             }
597             }
598              
599 0           return @errors;
600             }
601              
602             # ----- Check: DOCTYPE ------------------------------------------------------ #
603              
604             # Check for doctype declaration errors.
605 0     0 1   sub doctype { return shift->_wrap('_doctype', @_); }
606              
607             sub _doctype
608             {
609 0     0     my ($this, $line) = @_;
610 0           my @errors = ();
611              
612 0           while ($line =~ /)/gio) {
613 0           my ($pos, $dt, $rest) = (pos($line) - length($1), $2, $3);
614 0 0         if ($dt ne "DOCTYPE") {
615 0           push(@errors, { col => $this->_pos($line, $pos),
616             type => 'E',
617             mesg => "DOCTYPE must be uppercase: $dt",
618             },
619             );
620              
621 0           $pos = pos($line) - length($rest) - 1;
622              
623 0 0 0       if ($this->{_html} &&
624             (my ($p1, $html, $t) = ($rest =~ /^((html)\s+)(\w+)?/io))) {
625              
626             # TODO: better message, maybe this should not be XHTML-only.
627 0 0 0       if ($this->{_xhtml} && $html ne 'html') {
628 0           my $msg = "\"html\" in DOCTYPE should be lowercase in XHTML: $html";
629 0           push(@errors, { col => $this->_pos($line, $pos),
630             type => 'W',
631             mesg => $msg,
632             },
633             );
634             }
635              
636 0           $pos += length($p1);
637              
638 0 0         if ($t =~ /^(PUBLIC|SYSTEM)$/io) {
639 0 0         if ($t ne uc($t)) {
640 0           my $msg = uc($t) . " must be uppercase: \"$t\"";
641 0           push(@errors, { col => $this->_pos($line, $pos),
642             type => 'E',
643             mesg => $msg,
644             },
645             );
646              
647 0 0 0       if ($this->{_xml} && uc($t) eq 'PUBLIC') {
648             # TODO: In XML, you can't declare public ID without
649             # system ID. Check this.
650             }
651             }
652             } else {
653 0           my $msg = "PUBLIC or SYSTEM should follow root element name: \"$t\"";
654 0           push(@errors, { col => $this->_pos($line, $pos),
655             type => 'W',
656             mesg => $msg,
657             },
658             );
659             }
660             }
661             }
662             }
663              
664 0           return @errors;
665             }
666              
667              
668             # ---------- Accessors and mutators ----------------------------------------- #
669              
670             sub mode
671             {
672 0     0 1   my ($this, $mode) = @_;
673 0 0         if ($mode) {
674 0           my $was_xml = $this->{_xml};
675 0 0         if ($mode eq 'HTML') {
    0          
    0          
676 0           $this->{_xhtml} = 0;
677 0           $this->{_xml} = 0;
678 0           $this->{_html} = 1;
679 0           $this->{_mode} = $mode;
680             } elsif ($mode eq 'XML') {
681 0           $this->{_xhtml} = 0;
682 0           $this->{_xml} = 1;
683 0           $this->{_html} = 0;
684 0           $this->{_mode} = $mode;
685 0 0         $this->quote('"') unless $was_xml;
686             } elsif ($mode eq 'XHTML') {
687 0           $this->{_xhtml} = 1;
688 0           $this->{_xml} = 1;
689 0           $this->{_html} = 1;
690 0           $this->{_mode} = $mode;
691 0 0         $this->quote('"') unless $was_xml;
692             } else {
693 0           carp("** Mode must be one of XHTML, HTML, XML (resetting to XHTML)");
694 0           $this->mode('XHTML');
695             }
696             }
697 0           return $this->{_mode};
698             }
699              
700             sub tab_width
701             {
702 0     0 1   my ($this, $tw) = @_;
703 0 0         if (defined($tw)) {
704 0 0         if ($tw > 0) {
705 0           $this->{_tab_width} = sprintf("%.0f", $tw); # Uh. Integers please.
706             } else {
707 0           carp("** TAB width must be > 0");
708             }
709             }
710 0           return $this->{_tab_width};
711             }
712              
713             sub min_attributes
714             {
715 0     0 1   my ($this, $minattr) = @_;
716 0 0         if (defined($minattr)) {
717 0 0 0       if (! $minattr && $this->{_xml}) {
718 0           carp("** Will not disable minimized attribute checks in " .
719             $this->mode() . " mode");
720             } else {
721 0           $this->{_min_attrs} = $minattr;
722             }
723             }
724 0           return $this->{_min_attrs};
725             }
726              
727             sub stats
728             {
729 0     0 1   my $this = shift;
730 0           return ($this->{_num_errors}, $this->{_num_warnings});
731             }
732              
733             sub reset
734             {
735 0     0 1   my $this = shift;
736 0           my ($e, $w) = $this->stats();
737 0           $this->{_num_errors} = 0;
738 0           $this->{_num_warnings} = 0;
739 0           return ($e, $w);
740             }
741              
742             sub quote
743             {
744 0     0 1   my ($this, $q) = @_;
745 0 0         if (defined($q)) {
746             # We always allow " and ', and empty when non-xml, refuse others.
747 0   0       my $is_ok = ($q eq '"' || $q eq "'" );
748 0   0       $is_ok ||= (! $this->{_xml} && ! length($q));
      0        
749 0 0         if ($is_ok) {
750 0           $this->{_quote} = $q;
751             } else {
752 0   0       carp("** Refusing to set quote to ", ($q || '[none]'),
753             " when in " . $this->mode() . " mode");
754             }
755             }
756 0           return $this->{_quote};
757             }
758              
759             sub full_version
760             {
761 0     0 1   return "HTML::GMUCK $VERSION";
762             }
763              
764             # ---------- Utility methods ------------------------------------------------ #
765              
766             sub _pos
767             {
768 0     0     my ($this, $line, $pos) = @_;
769 0 0 0       $pos = 0 unless (defined($pos) && $pos > 0);
770 0 0 0       if ($this->{_tab_width} > 1 && $pos > 0) {
771 0           my $pre = substr($line, 0, $pos);
772 0           while ($pre =~ /\t/g) {
773 0           $pos += $this->{_tab_width} - 1;
774             }
775             }
776 0           return $pos;
777             }
778              
779             sub _wrap
780             {
781 0     0     my ($this, $method, @lines) = @_;
782 0           my @errors = ();
783 0           my $ln = 0;
784              
785 0           for (my $ln = 0; $ln < scalar(@lines); $ln++) {
786 0           foreach my $err ($this->$method($lines[$ln])) {
787 0           $err->{line} = $ln;
788 0 0         if (! $err->{mesg}) {
789 0           $err->{mesg} = "no error message, looks like you found a bug";
790 0           carp("** " . ucfirst($err->{mesg}));
791             }
792 0   0       $err->{col} ||= 0;
793 0 0         if (! $err->{type}) {
794 0           carp("** No error type, looks like you found a bug");
795 0           $err->{type} = '?';
796             }
797 0           push(@errors, $err);
798 0 0         if ($err->{type} eq 'W') {
799 0           $this->{_num_warnings}++;
800             } else {
801 0           $this->{_num_errors}++;
802             }
803             }
804             }
805              
806 0           return @errors;
807             }
808              
809             1;