File Coverage

GO/Parsers/base_parser.pm
Criterion Covered Total %
statement 85 247 34.4
branch 17 72 23.6
condition 5 23 21.7
subroutine 22 36 61.1
pod 6 27 22.2
total 135 405 33.3


line stmt bran cond sub pod time code
1             # $Id: base_parser.pm,v 1.18 2008/03/13 05:16:40 cmungall Exp $
2             #
3             #
4             # see also - http://www.geneontology.org
5             # - http://www.godatabase.org/dev
6             #
7             # You may distribute this module under the same terms as perl itself
8              
9             package GO::Parsers::base_parser;
10              
11             =head1 NAME
12              
13             GO::Parsers::base_parser - base class for parsers
14              
15             =head1 SYNOPSIS
16              
17             do not use this class directly; use GO::Parser
18              
19             =cut
20              
21             =head1 DESCRIPTION
22              
23             =head1 AUTHOR
24              
25             =cut
26              
27 17     17   116 use Carp;
  17         37  
  17         1125  
28 17     17   96 use FileHandle;
  17         35  
  17         132  
29 17     17   8121 use Digest::MD5 qw(md5_hex);
  17         35  
  17         942  
30 17     17   92 use GO::Parser;
  17         32  
  17         551  
31 17     17   17648 use Data::Stag qw(:all);
  17         605570  
  17         19032  
32 17     17   212 use base qw(Data::Stag::BaseGenerator Exporter);
  17         40  
  17         57723  
33 17     17   78745 use strict qw(subs vars refs);
  17         43  
  17         56263  
34              
35             # Exceptions
36              
37             sub throw {
38 0     0 0 0 my $self = shift;
39 0         0 confess("@_");
40             }
41              
42             sub warn {
43 0     0 0 0 my $self = shift;
44 0         0 warn("@_");
45             }
46              
47             sub messages {
48 26     26 0 60 my $self = shift;
49 26 50       887 $self->{_messages} = shift if @_;
50 26         75 return $self->{_messages};
51             }
52              
53             *error_list = \&messages;
54              
55             sub message {
56 0     0 0 0 my $self = shift;
57 0         0 my $msg = shift;
58 0         0 CORE::warn 'deprecated';
59 0         0 $self->parse_err($msg);
60             }
61              
62             =head2 show_messages
63              
64             Usage -
65             Returns -
66             Args -
67              
68             =cut
69              
70             sub show_messages {
71 0     0 1 0 my $self = shift;
72 0         0 my $fh = shift;
73 0 0       0 $fh = \*STDERR unless $fh;
74 0 0       0 foreach my $e (@{$self->error_list || []}) {
  0         0  
75 0   0     0 printf $fh "\n===\n Line:%s [%s]\n%s\n %s\n\n", $e->{line_no} || "", $e->{file} || "", $e->{line} || "", $e->{msg} || "";
      0        
      0        
      0        
76             }
77             }
78              
79             sub init {
80 26     26 0 2134 my $self = shift;
81              
82 26         204 $self->messages([]);
83 26         263 $self->acc2name_h({});
84 26         76 $self;
85             }
86              
87             sub parsed_ontology {
88 16     16 0 39 my $self = shift;
89 16 50       124 $self->{parsed_ontology} = shift if @_;
90 16         95 return $self->{parsed_ontology};
91             }
92              
93             =head2 normalize_files
94              
95             Usage - @files = $parser->normalize_files(@files)
96             Returns -
97             Args -
98              
99             takes a list of filenames/paths, "glob"s them, uncompresses any compressed files and returns the new file list
100              
101             =cut
102              
103             sub normalize_files {
104 0     0 1 0 my $self = shift;
105 0         0 my $dtype;
106 0         0 my @files = map {glob $_} @_;
  0         0  
107 0         0 my @errors = ();
108 0         0 my @nfiles = ();
109            
110             # uncompress any compressed files
111 0         0 foreach my $fn (@files) {
112 0 0       0 if ($fn =~ /\.gz$/) {
113 0         0 my $nfn = $fn;
114 0         0 $nfn =~ s/\.gz$//;
115 0         0 my $cmd = "gzip -dc $fn > $nfn";
116             #print STDERR "Running $cmd\n";
117 0         0 my $err = system("$cmd");
118 0 0       0 if ($err) {
119 0         0 push(@errors,
120             "can't uncompress $fn");
121 0         0 next;
122             }
123 0         0 $fn = $nfn;
124             }
125 0 0       0 if ($fn =~ /\.Z$/) {
126 0         0 my $nfn = $fn;
127 0         0 $nfn =~ s/\.Z$//;
128 0         0 my $cmd = "zcat $fn > $nfn";
129 0         0 print STDERR "Running $cmd\n";
130 0         0 my $err = system("$cmd");
131 0 0       0 if ($err) {
132 0         0 push(@errors,
133             "can't uncompress $fn");
134 0         0 next;
135             }
136 0         0 $fn = $nfn;
137             }
138 0         0 push(@nfiles, $fn);
139             }
140 0         0 my %done = ();
141 0         0 @files = grep { my $d = !$done{$_}; $done{$_} = 1; $d } @nfiles;
  0         0  
  0         0  
  0         0  
142 0         0 return @files;
143             }
144              
145             sub fire_source_event {
146 18     18 0 40 my $self = shift;
147 18   50     84 my $file = shift || die "need to pass file argument";
148 18         126 my @fileparts = split(/\//, $file);
149 18         645 my @stat = stat($file);
150 18         54 my $mtime = $stat[9];
151 18         85 my $parsetime = time;
152 18         164 my $md5 = md5_hex($fileparts[-1]);
153 18         334 $self->event(source => [
154             [source_id => $file ],
155             [source_type => 'file'],
156             [source_fullpath => $file ],
157             [source_path => $fileparts[-1] ],
158             [source_md5 => $md5],
159             [source_mtime => $mtime ],
160             [source_parsetime => $parsetime ],
161             ]
162             );
163 18         13482 return;
164             }
165              
166             sub parse_assocs {
167 0     0 0 0 my $self = shift;
168 0         0 my $fn = shift;
169 0         0 $self->dtype('go_assoc');
170 0         0 my $p = GO::Parser->get_parser_impl('go_assoc');
171 0         0 %$p = %$self;
172 0         0 $p->parse($fn);
173 0         0 return;
174             }
175              
176             sub parse_to_graph {
177 1     1 0 10 my $self = shift;
178 1         8 my $h = GO::Parser->create_handler('obj');
179 1         28 $self->handler($h);
180 1         81 $self->parse(@_);
181 1         92 return $h->graph;
182             }
183              
184             sub set_type {
185 0     0 0 0 my ($self, $fmt) = @_;
186 0         0 $self->dtype($fmt);
187 0         0 my $p = GO::Parser->get_parser_impl($fmt);
188 0         0 bless $self, ref($p);
189 0         0 return;
190             }
191             sub dtype {
192 20     20 0 47 my $self = shift;
193 20 50       93 $self->{_dtype} = shift if @_;
194 20         60 return $self->{_dtype};
195             }
196              
197             sub parse_file {
198 0     0 0 0 my ($self, $file, $dtype) = @_;
199              
200 0         0 $self->dtype($dtype);
201 0         0 $self->parse($file);
202             }
203              
204             sub xslt {
205 20     20 0 47 my $self = shift;
206 20 50       82 $self->{_xslt} = shift if @_;
207 20         286 return $self->{_xslt};
208             }
209              
210             sub force_namespace {
211 40     40 0 73 my $self = shift;
212 40 50       122 $self->{_force_namespace} = shift if @_;
213 40         358 return $self->{_force_namespace};
214             }
215              
216             sub replace_underscore {
217 16     16 0 39 my $self = shift;
218 16 50       59 $self->{_replace_underscore} = shift if @_;
219 16         63 return $self->{_replace_underscore};
220             }
221              
222             # EXPERIMENTAL: cache objects
223             sub use_cache {
224 40     40 0 90 my $self = shift;
225 40 100       194 $self->{_use_cache} = shift if @_;
226 40         152 return $self->{_use_cache};
227             }
228              
229             # EXPERIMENTAL: returns subroutine
230             # sub maps name to cached name
231             sub file_to_cache_sub {
232 0     0 0 0 my $self = shift;
233 0         0 my $lite = $self->litemode;
234 0 0       0 my $suffix = $lite ? ".lcache" : ".cache";
235 0 0       0 $self->{_file_to_cache_sub} = shift if @_;
236             return $self->{_file_to_cache_sub} ||
237             sub {
238 0     0   0 my $f = shift;
239 0         0 $f =~ s/\.\w+$//;
240 0         0 $f .= $suffix;
241 0         0 return $f;
242 0   0     0 };
243             }
244              
245              
246             sub cached_obj_file {
247 0     0 0 0 my $self = shift;
248 0         0 return $self->file_to_cache_sub->(@_);
249             }
250              
251             sub parse {
252 20     20 1 29215 my ($self, @files) = @_;
253              
254 20         220 my $dtype = $self->dtype;
255 20         64 foreach my $file (@files) {
256              
257 20         158 $file = $self->download_file_if_required($file);
258              
259 20         225 $self->file($file);
260             #printf STDERR "parsing: $file %d\n", $self->use_cache;
261              
262 20 50       209 if ($self->use_cache) {
263 0         0 my $cached_obj_file = $self->cached_obj_file($file);
264 0         0 my $reparse;
265 0 0       0 if (-f $cached_obj_file) {
266 0         0 my @stat1 = lstat($file);
267 0         0 my @stat2 = lstat($cached_obj_file);
268 0         0 my $t1 = $stat1[9];
269 0         0 my $t2 = $stat2[9];
270 0 0       0 if ($t1 >= $t2) {
271 0         0 $reparse = 1;
272             }
273             else {
274 0         0 $reparse = 0;
275             }
276             }
277             else {
278 0         0 $reparse = 1;
279             }
280              
281 0 0       0 if ($reparse) {
282             # make/remake cache
283 0         0 my $hclass = "GO::Handlers::obj_storable";
284 0         0 $self->load_module($hclass);
285 0         0 my $cache_handler =
286             $hclass->new;
287 0         0 $self->use_cache(0);
288 0         0 my $orig_handler = $self->handler;
289 0         0 $self->handler($cache_handler);
290 0         0 $cache_handler->file($cached_obj_file);
291 0         0 $self->parse($file);
292 0         0 my $g = $cache_handler->graph;
293 0         0 $self->use_cache(1);
294 0         0 my $p2 = GO::Parser->new({
295             format=>'GO::Parsers::obj_emitter'});
296 0         0 $p2->handler($orig_handler);
297             # this is the only state we need to copy across
298 0 0       0 if ($self->can('xslt')) {
299 0         0 $p2->xslt($self->xslt);
300             }
301 0         0 $p2->emit_graph($g);
302             }
303             else {
304             # use cache
305 0         0 my $p2 = GO::Parser->new({format=>'obj_storable'});
306 0         0 $p2->handler($self->handler);
307             # this is the only state we need to copy across
308 0 0       0 if ($self->can('xslt')) {
309 0         0 $p2->xslt($self->xslt);
310             }
311 0         0 $p2->parse_file($cached_obj_file);
312             }
313 0         0 next;
314             }
315              
316             # check for XSL transform
317 20 50 33     354 if ($self->can('xslt') && $self->xslt) {
318 0         0 my $xsl = $self->xslt;
319 0         0 my $xslt_file = $xsl;
320              
321 0 0       0 if (!-f $xslt_file) {
322             # if GO_ROOT is set then this specifies the location of the xslt dir
323             # if it is not set, assume we are using an installed version of go-perl,
324             # in which case, the xslts will be located along with the perl modules
325 0         0 my $GO_ROOT = $ENV{GO_ROOT};
326 0 0       0 if ($GO_ROOT) {
327             # env var takes precedence;
328             # developers should use this
329 0         0 $xslt_file = "$GO_ROOT/xml/xsl/$xsl.xsl";
330             }
331            
332             # default location is with perl modules
333 0 0 0     0 if (!$xslt_file || !-f $xslt_file) {
334             # user-mode; xsl will be wherever the GO modules are installed
335 0         0 require "GO/Parser.pm";
336 0         0 my $dir = $INC{'GO/Parser.pm'};
337 0         0 $dir =~ s/Parser\.pm/xsl/;
338 0         0 $xslt_file = "$dir/$xsl.xsl";
339             }
340             }
341 0 0       0 if (!-f $xslt_file) {
342 0         0 $self->throw("No such file: $xslt_file OR $xsl");
343             }
344              
345             # first parse input file to intermediate xml
346 0         0 my $file1 = _make_temp_filename($file, "-1.xml");
347 0         0 my $handler = $self->handler;
348 0         0 my $outhandler1 =
349             Data::Stag->getformathandler("xml");
350 0         0 $outhandler1->file($file1);
351 0         0 $self->handler($outhandler1);
352 0         0 $self->SUPER::parse($file);
353 0         0 $self->handler->finish;
354              
355             # transform intermediate xml using XSLT
356 0         0 my $file2 = _make_temp_filename($file, "-2.xml");
357             # $results contains the post-xslt XML doc;
358             # we either want to write this directly, or
359             # pass it to a handler
360              
361 0 0       0 if ($handler->isa("Data::Stag::XMLWriter")) {
362             # WRITE DIRECTLY:
363             # if the final goal is XML, then write this
364             # directly
365 0 0       0 if ($handler->file) {
366             # $ss->output_file($results,$handler->file);
367 0         0 xsltproc($xslt_file,$file1,$handler->file);
368             } else {
369 0         0 my $fh = $handler->fh;
370 0 0       0 if (!$fh) {
371 0         0 $fh = \*STDOUT;
372 0         0 xsltproc($xslt_file,$file1);
373             }
374             else {
375 0         0 xsltproc($xslt_file,$file1,$file2);
376 0   0     0 my $infh = FileHandle->new($file2) || die "cannot open $file2";
377 0         0 while (<$infh>) {print $fh $_}
  0         0  
378 0         0 unlink($file2);
379             }
380             #$ss->output_fh($results,$handler->fh);
381             }
382             } else {
383             # PASS TO HANDLER:
384             # we need to do another transform, in perl.
385             #
386             # write $results of stylesheet transform
387             #$ss->output_file($results,$file2);
388 0         0 xsltproc($xslt_file,$file1,$file2);
389            
390             # clear memory
391             #$ss=undef;
392             #$xslt=undef;
393             #$results=undef;
394              
395             # we pass the final XML to the handler
396 0         0 my $load_parser = new GO::Parser ({format=>'obo_xml'});
397 0         0 $load_parser->handler($handler);
398 0         0 $load_parser->errhandler($self->errhandler);
399 0         0 $load_parser->parse($file2);
400 0         0 unlink($file2);
401             }
402              
403             # restore to previous state
404 0         0 $self->handler($handler);
405             } else {
406             # no XSL transform - perform parse as normal
407             # (use Data::Stag superclass)
408 20         183 $self->SUPER::parse($file);
409             }
410             }
411             }
412              
413             # applies XSLT and removes input file
414             sub xsltproc {
415 0     0 0 0 my ($xf,$inf,$outf) = @_;
416 0         0 my $cmd = "xsltproc $xf $inf";
417 0 0       0 if ($outf) {
418 0         0 $cmd .= " > $outf";
419             }
420 0         0 my $err = system($cmd);
421 0         0 unlink($inf);
422 0 0       0 if ($err) {
423 0         0 confess("problem running: $cmd");
424             }
425 0         0 return;
426             }
427              
428             sub _make_temp_filename {
429 0     0   0 my ($base, $suffix) = @_;
430 0         0 $base =~ s/.*\///;
431 0         0 return "TMP.$$.$base$suffix";
432             }
433              
434             sub download_file_if_required {
435 20     20 0 44 my $self = shift;
436 20         43 my $f = shift;
437 20 50       122 if ($f =~ /^http:/) {
438 0         0 my $tmpf = _make_temp_filename($f,'.obo');
439 0         0 system("wget -O $tmpf $f");
440 0         0 return $tmpf;
441             }
442             else {
443 20         71 return $f;
444             }
445             }
446              
447             =head2 litemode
448              
449             Usage - $p->litemode(1)
450             Returns -
451             Args - bool
452              
453             when set, parser will only throw the following events:
454              
455             id|name|is_a|relationship|namespace
456              
457             (optimisation for fast parsing)
458              
459             =cut
460              
461             sub litemode {
462 10     10 1 28 my $self = shift;
463 10 50       44 $self->{_litemode} = shift if @_;
464 10         33 return $self->{_litemode};
465             }
466              
467             =head2 acc2name_h
468              
469             Usage - $n = $p->acc2name_h->{'GO:0003673'}
470             Returns - hashref
471             Args - hashref [optional]
472              
473             gets/sets a hash mapping IDs to names
474              
475             this will be automatically set by an ontology parser
476              
477             a non-ontology parser will use this index to verify the parsed data
478             (see $p->acc_not_found($id), below)
479              
480             =cut
481              
482             sub acc2name_h {
483 1747     1747 1 2724 my $self = shift;
484 1747 100       3867 $self->{_acc2name_h} = shift if @_;
485 1747 50       4524 $self->{_acc2name_h} = {}
486             unless $self->{_acc2name_h};
487 1747         14472 return $self->{_acc2name_h};
488             }
489              
490              
491             =head2 acc_not_found
492              
493             Usage - if ($p->acc_not_found($go_id)) { warn("$go_id not known") }
494             Returns - bool
495             Args - acc string
496              
497             uses acc2name_h - if this hash mapping has been created AND the acc is
498             not in the hash, THEN it is considered not found
499              
500             This is useful for non-ontology parsers (xref_parser, go_assoc_parser)
501             to check whether a referenced ID is actually present in the ontology
502              
503             note that if acc2name_h has not been created, then accs cannot be
504             considered not-found, and this will always return 0/false
505              
506             =cut
507              
508             sub acc_not_found {
509 918     918 1 1191 my $self = shift;
510 918         1145 my $acc = shift;
511 918         1823 my $h = $self->acc2name_h;
512 918 100 100     2974 if (scalar(keys %$h) && !$h->{$acc}) {
513 1         6 return 1;
514             }
515 917         2749 return 0;
516             }
517              
518             sub dtd {
519 0     0 0   undef;
520             }
521              
522             1;