File Coverage

blib/lib/RandomJungle/File/XML.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package RandomJungle::File::XML;
2              
3             =head1 NAME
4              
5             RandomJungle::File::XML - Low level access to the data in the RandomJungle XML output file
6              
7             =cut
8              
9 6     6   1931 use strict;
  6         13  
  6         251  
10 6     6   35 use warnings;
  6         12  
  6         245  
11              
12 6     6   32 use Carp;
  6         11  
  6         437  
13 6     6   37 use Data::Dumper;
  6         13  
  6         271  
14 6     6   902 use DBM::Deep;
  6         9756  
  6         34  
15 6     6   923 use Devel::StackTrace;
  6         3872  
  6         131  
16 6     6   6802 use XML::Twig;
  0            
  0            
17              
18             =head1 VERSION
19              
20             Version 0.06
21              
22             =cut
23              
24             our $VERSION = 0.06;
25             our $ERROR; # used if new() fails
26              
27             =head1 SYNOPSIS
28              
29             RandomJungle::File::XML provides access to the data contained within RandomJungle's XML output file.
30             See RandomJungle::Jungle and RandomJungle::Tree for higher-level methods.
31              
32             use RandomJungle::File::XML;
33              
34             my $xml = RandomJungle::File::XML->new( filename => $xmlfile ) || die $RandomJungle::File::XML::ERROR;
35             $xml->parse || die $xml->err_str;
36              
37             my $file = $xml->get_filename; # returns the filename of the XML file
38             my $href = $xml->get_RJ_input_params; # all the input params that were used for RJ
39             my $aref = $xml->get_tree_ids; # sorted numerically
40             my $href = $xml->get_tree_data; # data for all trees (not RJ::Tree objects)
41             my $href = $xml->get_tree_data( tree_id => $id ) || warn $xml->err_str;
42              
43             my $href = $xml->get_data; # for debugging only; returns raw data structs
44              
45             # Error handling
46             $xml->set_err( 'Something went boom' );
47             my $msg = $xml->err_str;
48             my $trace = $xml->err_trace;
49              
50             =cut
51              
52             #*********************************************************************
53             # Public Methods
54             #*********************************************************************
55              
56             =head1 METHODS
57              
58             =head2 new()
59              
60             Creates and returns a new RandomJungle::File::XML object:
61              
62             my $xml = RandomJungle::File::XML->new( filename => $xmlfile );
63              
64             The 'filename' parameter is required. Sets $ERROR and returns undef on failure.
65              
66             =cut
67              
68             sub new
69             {
70             # Returns RJ::File::XML object on success
71             # Returns undef on failure (e.g., 'filename' param not set)
72             my ( $class, %args ) = @_;
73              
74             my $obj = {};
75             bless $obj, $class;
76             $obj->_init( %args ) || return; # $ERROR is set by _init()
77              
78             return $obj;
79             }
80              
81             =head2 parse()
82              
83             Parses the XML file specified in new():
84              
85             my $retval = $xml->parse;
86              
87             Returns a true value on success. Sets err_str and returns undef on failure.
88              
89             =cut
90              
91             sub parse
92             {
93             # Returns true on success
94             # Sets err_str and returns undef on failure (parse error)
95             my ( $self ) = @_;
96              
97             my $twig = XML::Twig->new(
98             twig_handlers =>
99             {
100             'tree' => sub { $self->_tree( @_ ) },
101             'option' => sub { $self->_option( @_ ) },
102             },
103             );
104              
105             my $retval = $twig->safe_parsefile( $self->{xml_file}{filename} ); # does not die on error (but returns 0)
106              
107             if( ! $retval )
108             {
109             $self->set_err( "Error parsing XML file: $@" );
110             return;
111             }
112              
113             $self->{twig} = $twig;
114              
115             return 1;
116             }
117              
118             =head2 get_filename()
119              
120             Returns the name of the XML file specified in new():
121              
122             my $file = $xml->get_filename;
123              
124             =cut
125              
126             sub get_filename
127             {
128             my ( $self ) = @_;
129             return $self->{xml_file}{filename};
130             }
131              
132             =head2 get_RJ_input_params()
133              
134             Returns a href containing the input parameters that were used when Random Jungle was run:
135              
136             my $href = $xml->get_RJ_input_params; # $href->{$param_name} = $param_value;
137              
138             This method calls parse() if it has not already been called.
139              
140             =cut
141              
142             sub get_RJ_input_params
143             {
144             # Returns an href of all the input params that were used for RJ
145             # calls parse() internally if not already called, so subject to that method's behavior on failure
146             my ( $self ) = @_;
147              
148             if( ! defined $self->{options} )
149             {
150             $self->parse || return;
151             }
152              
153             return $self->{options};
154             }
155              
156             =head2 get_tree_ids()
157              
158             Returns an aref of tree IDs (sorted numerically):
159              
160             my $aref = $xml->get_tree_ids;
161              
162             This method calls parse() if it has not already been called.
163              
164             =cut
165              
166             sub get_tree_ids
167             {
168             # Returns an aref of tree IDs (sorted numerically)
169             # calls parse() internally if not already called, so subject to that method's behavior on failure
170             my ( $self ) = @_;
171              
172             if( ! defined $self->{options} )
173             {
174             $self->parse || return;
175             }
176              
177             my @ids = sort { $a <=> $b } ( keys %{ $self->{tree_data} } );
178              
179             return \@ids;
180             }
181              
182             =head2 get_tree_data()
183              
184             Returns an href of tree records (not RandomJungle::Tree objects):
185              
186             my $href = $xml->get_tree_data; # data for all trees
187             my $href = $xml->get_tree_data( tree_id => $id );
188              
189             If called without parameters, records for all trees will be returned.
190             The tree_id parameter can be used to get a single record (returns undef if $id is invalid).
191              
192             This method calls parse() if it has not already been called.
193              
194             $href has the following structure:
195             $tree_id
196             id => $tree_id,
197             var_id_str => varID string from XML, e.g., '((490,967,1102,...))'
198             values_str => values string from XML, e.g., '(((0)),((0)),((1)),...)'
199             branches_str => branches string from XML, e.g., '((1,370),(2,209),(3,160),...)'
200              
201             $href is suitable for passing to RandomJungle::Tree->new().
202              
203             =cut
204              
205             sub get_tree_data
206             {
207             # Returns an href of tree records (not RJ::Tree objects)
208             # Default is all trees, can specify tree_id => $id to get a single tree
209             # Sets err_str and returns undef if tree_id is specified but invalid
210             # calls parse() internally if not already called, so subject to that method's behavior on failure
211             my ( $self, %params ) = @_;
212              
213             if( ! defined $self->{tree_data} )
214             {
215             $self->parse || return;
216             }
217              
218             if( exists $params{tree_id} )
219             {
220             if( defined $params{tree_id} )
221             {
222             if( ! exists $self->{tree_data}{ $params{tree_id} } )
223             {
224             $self->set_err( "Invalid tree ID: $params{tree_id}" );
225             return;
226             }
227              
228             my $href = $self->{tree_data}{ $params{tree_id} }; # single record
229             return { $params{tree_id} => $href }; # maintain same struct as for all records
230             }
231             else
232             {
233             $self->set_err( 'Tree ID not specified' );
234             return; # specified tree_id option without a value
235             }
236             }
237              
238             return $self->{tree_data}; # all records
239             }
240              
241             =head2 get_data()
242              
243             Returns the data structures contained in $self:
244              
245             my $href = $xml->get_data;
246              
247             This method is for debugging only and should not be used in production code.
248              
249             =cut
250              
251             sub get_data
252             {
253             # for debugging only
254             my ( $self ) = @_;
255              
256             if( ! defined $self->{tree_data} )
257             {
258             $self->parse || return;
259             }
260              
261             # don't pass the Twig object (big)
262             my %h = (
263             xml_file => $self->{xml_file},
264             options => $self->{options},
265             tree_data => $self->{tree_data},
266             );
267              
268             return \%h;
269             }
270              
271             =head2 set_err()
272              
273             Sets the error message (provided as a parameter) and creates a stack trace:
274              
275             $xml->set_err( 'Something went boom' );
276              
277             =cut
278              
279             sub set_err
280             {
281             my ( $self, $errstr ) = @_;
282              
283             $self->{err_str} = $errstr || '';
284             $self->{err_trace} = Devel::StackTrace->new;
285             }
286              
287             =head2 err_str()
288              
289             Returns the last error message that was set:
290              
291             my $msg = $xml->err_str;
292              
293             =cut
294              
295             sub err_str
296             {
297             my ( $self ) = @_;
298              
299             return $self->{err_str};
300             }
301              
302             =head2 err_trace()
303              
304             Returns a backtrace for the last error that was encountered:
305              
306             my $trace = $xml->err_trace;
307              
308             =cut
309              
310             sub err_trace
311             {
312             my ( $self ) = @_;
313              
314             return $self->{err_trace}->as_string;
315             }
316              
317             #*********************************************************************
318             # Private Methods and Routines
319             #*********************************************************************
320              
321             sub _init
322             {
323             # sets $ERROR and returns undef if $args{filename} is not defined or file does not exist
324             my ( $self, %args ) = @_;
325              
326             @{ $self }{ keys %args } = values %args;
327              
328             if( ! defined $self->{filename} )
329             {
330             $ERROR = "'filename' is not defined";
331             return;
332             }
333             elsif( ! -e $self->{filename} )
334             {
335             $ERROR = "$self->{filename} does not exist";
336             return;
337             }
338              
339             $self->{xml_file}{filename} = $self->{filename};
340             }
341              
342             #***** XML::Twig handlers *****
343              
344             sub _tree
345             {
346             # this sub could probably use some error checking on the $elt method calls
347             my ( $self, $twig, $elt ) = @_;
348              
349             my $tree_id = $elt->att( 'id' );
350              
351             my %t = (
352             id => $tree_id,
353             var_id_str => $elt->first_child_text( 'variable[@name="varID"]' ),
354             values_str => $elt->first_child_text( 'variable[@name="values"]' ),
355             branches_str => $elt->first_child_text( 'variable[@name="branches"]' ),
356             );
357              
358             # store raw data only; no longer convert to ::Tree objects to save memory and simplify saving
359             # the data to a db
360             $self->{tree_data}{$tree_id} = \%t;
361              
362             $twig->purge; # added Feb 7, 2011 to try to free memory (didn't test!)
363             }
364              
365             sub _option
366             {
367             # this sub could probably use some error checking on the $elt method calls
368             my ( $self, $twig, $elt ) = @_;
369              
370             my $opt_name = $elt->att( 'id' );
371             my $opt_value = $elt->text;
372              
373             $self->{options}{$opt_name} = $opt_value;
374             }
375              
376             =head1 SEE ALSO
377              
378             RandomJungle::Jungle, RandomJungle::Tree, RandomJungle::Tree::Node,
379             RandomJungle::XML, RandomJungle::OOB, RandomJungle::RAW,
380             RandomJungle::DB, RandomJungle::Classification_DB
381              
382             =head1 AUTHOR
383              
384             Robert R. Freimuth
385              
386             =head1 COPYRIGHT
387              
388             Copyright (c) 2011 Mayo Foundation for Medical Education and Research. All rights reserved.
389              
390             This program is free software; you can redistribute it and/or modify
391             it under the same terms as Perl itself.
392              
393             The full text of the license can be found in the
394             LICENSE file included with this module.
395              
396             =cut
397              
398             #*********************************************************************
399             # Guts
400             #*********************************************************************
401              
402             =begin guts
403              
404             Guts:
405              
406             $self
407             xml_file
408             filename => $filename
409             options
410             $name => $value (input parameters for RJ)
411             twig => XML::Twig object
412             tree_data
413             $tree_id
414             id => $tree_id,
415             var_id_str => varID string from XML, e.g., '((490,967,1102,...))'
416             values_str => values string from XML, e.g., '(((0)),((0)),((1)),...)'
417             branches_str => branches string from XML, e.g., '((1,370),(2,209),(3,160),...)'
418             err_str => $errstr
419             err_trace => Devel::StackTrace object
420              
421             =cut
422              
423             1;
424