File Coverage

blib/lib/Bio/NEXUS/Import.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Bio::NEXUS::Import;
2              
3 4     4   134175 use warnings;
  4         12  
  4         138  
4 4     4   20 use strict;
  4         11  
  4         122  
5 4     4   20 use Carp;
  4         11  
  4         294  
6              
7 4     4   5358 use Bio::NEXUS;
  0            
  0            
8             use Bio::NEXUS::Functions;
9              
10             use base 'Bio::NEXUS';
11              
12             use version; our $VERSION = qv('0.2.0');
13              
14             sub new {
15             my ( $class, $filename, $fileformat, $verbose ) = @_;
16             my $self = {};
17             bless $self, $class;
18             $self->{'supported_file_formats'} = {
19             'phylip' => {
20             'PHYLIP_DIST_SQUARE' => 1,
21             'PHYLIP_DIST_LOWER' => 1,
22             'PHYLIP_DIST_SQUARE_BLANK' => 1,
23             'PHYLIP_DIST_LOWER_BLANK' => 1,
24             'PHYLIP_DIST_UPPER' => 1,
25             'PHYLIP_SEQ_INTERLEAVED' => 1,
26             'PHYLIP_SEQ_SEQUENTIAL' => 1,
27             'PHYLIP_SEQ_INTERLEAVED_BLANK' => 1,
28             'PHYLIP_SEQ_SEQUENTIAL_BLANK' => 1,
29             },
30             'nexus' => { 'NEXUS' => 1 },
31             };
32             if ( defined $filename ) {
33             $self->import_file( $filename, $fileformat, $verbose );
34             $self->set_name($filename);
35             }
36             return $self;
37             }
38              
39             sub _say {
40             my ( $self, $msg ) = @_;
41             print "$msg\n" or croak q{Can't write to Terminal};
42             return;
43             }
44              
45             sub import_file {
46             my ( $self, $filename, $fileformat, $verbose ) = @_;
47             if ( !-e $filename ) {
48             croak "ERROR: $filename is not a valid filename\n";
49             }
50             my @filecontent = split /\n/xms,
51             $self->_load_file(
52             { 'format' => 'filename',
53             'param' => $filename,
54             'verbose' => $verbose,
55             }
56             );
57             if ( !defined $fileformat ) {
58             if ($verbose) {
59             $self->_say("Trying to detect format of $self->{filename}");
60             }
61             $fileformat = $self->_detect_fileformat( \@filecontent );
62             if ($verbose) {
63             $self->_say("$fileformat detected");
64             }
65             }
66             my $sff = $self->{'supported_file_formats'};
67             if ( defined $sff->{'phylip'}->{$fileformat} ) {
68             $self->_import_phylip(
69             { 'filecontent' => \@filecontent,
70             'param' => $filename,
71             'verbose' => $verbose,
72             'fileformat' => $fileformat,
73             }
74             );
75             }
76             elsif ( defined $sff->{'nexus'}->{$fileformat} ) {
77             $self->read_file( $filename, $verbose );
78             }
79             else {
80             croak "ERROR: $fileformat is not supported.\n";
81             }
82             return;
83             }
84              
85             sub _detect_fileformat {
86             my ( $self, $filecontent ) = @_;
87             if ( $filecontent->[0] =~ m{\A \s* (\d+)\s+(\d+) \s* \z}xms ) {
88             if ( $filecontent->[2] =~ m{\A [\sAGCTU]+ \z }xmsi ) {
89             return 'PHYLIP_SEQ_SEQUENTIAL';
90             }
91             else {
92             return 'PHYLIP_SEQ_INTERLEAVED';
93             }
94             }
95             elsif ( $filecontent->[0] =~ m{\A \s* (\d+) \s* \z}xms ) {
96             my $number_taxa = $1;
97             my @fields = split( /\s+/, $filecontent->[1] );
98             if ( length $filecontent->[1] <= 10
99             || scalar(@fields) == 1 )
100             {
101             for my $i ( 1 .. ( scalar( @{$filecontent} ) - 1 ) ) {
102             my @fields2 = split( /\s+/, $filecontent->[$i] );
103             if ( scalar @fields2 != $i ) {
104             return 'PHYLIP_DIST_LOWER';
105             }
106             }
107             return 'PHYLIP_DIST_LOWER_BLANK';
108             }
109             else {
110             for my $i ( 1 .. ( scalar( @{$filecontent} ) - 1 ) ) {
111             my @fields2 = split( /\s+/, $filecontent->[$i] );
112             if ( scalar @fields2 != $number_taxa + 1 ) {
113             return 'PHYLIP_DIST_SQUARE';
114             }
115             }
116             return 'PHYLIP_DIST_SQUARE_BLANK';
117             }
118             }
119             elsif ( $filecontent->[0] =~ m{\A \s* \#NEXUS \s* \z}xms ) {
120             return 'NEXUS';
121             }
122             else {
123             croak("ERROR: Could not detect file format.\n");
124             }
125             }
126              
127             sub _load_file {
128             my ( $self, $args ) = @_;
129             $args->{'format'} ||= 'string';
130             $args->{'param'} ||= q{};
131             my $verbose = $args->{'verbose'} || 0;
132             my $file;
133             my $filename;
134              
135             if ( lc $args->{'format'} eq 'string' ) {
136             $file = $args->{'param'};
137             }
138             else {
139             $filename = $args->{'param'};
140             $file = _slurp($filename);
141             }
142              
143             # Read entire file into scalar $import_file
144             if ($verbose) {
145             $self->_say('Reading file...');
146             }
147             $self->{'filename'} = $filename;
148             return $file;
149             }
150              
151             sub _import_phylip {
152             my ( $self, $args ) = @_;
153              
154             my $filename = $self->{'filename'};
155              
156             $args->{'fileformat'} ||= '_dist_square';
157             my $ff = $args->{'fileformat'};
158             $ff = lc $ff;
159             my $verbose = $args->{'verbose'} || 0;
160             my $line_number = 0;
161             my $taxon_started = 0;
162             my $taxon_id = -1;
163             my ( $number_taxa, $number_chars, @taxdata, @taxlabels );
164             LINE:
165              
166             for my $line ( @{ $args->{'filecontent'} } ) {
167             $line_number++;
168              
169             #remove newline, leading and trailing whitespaces
170             chomp $line;
171             $line =~ s{\s+ \z}{}xms;
172              
173             next LINE if $line eq q{};
174              
175             if ( $line_number == 1 ) {
176              
177             if ( $ff =~ m{dist}xms ) {
178             ($number_taxa) = $line =~ m{\A \s* (\d+) \s* \z}xms;
179             }
180             else {
181              
182             # sequence data has the number of characters in the first line
183             ( $number_taxa, $number_chars )
184             = $line =~ m{\A \s* (\d+)\s+(\d+) \s* \z}xms;
185             if ( !defined $number_chars ) {
186             croak(
187             "ERROR: First line must contain number of characters.\n"
188             );
189             }
190             }
191             if ( !defined $number_taxa ) {
192             croak("ERROR: First line must contain number of taxa.\n");
193             }
194             next LINE;
195             }
196             if ( !$taxon_started ) {
197             $taxon_id++;
198              
199             my ( $label, $data );
200              
201             if ( $ff =~ m{blank\z}xms ) {
202             ( $label, $data ) = $line =~ m{ \A (.*?)\s+(.*) \z }xms;
203             }
204             else {
205              
206             # first 10 chars are the labels
207             ( $label, $data ) = $line =~ m{ \A (.{10})(.*) \z }xms;
208             }
209              
210             # undefined? then we have only one label, no data
211             # for example in the first row of a lower distmatrix
212             if ( !defined $label ) {
213             $label = $line;
214             $data = q{};
215             }
216              
217             #remove leading and trailing whitespaces
218             $label =~ s{\A \s+}{}xms;
219             $label =~ s{\s+ \z}{}xms;
220              
221             $label =~ s{-|\s}{_}xms;
222              
223             $data =~ s{\A \s+}{}xms;
224             my @taxondata = split /\s+/xms, $data;
225              
226             $taxdata[$taxon_id] = [@taxondata];
227             push @taxlabels, $label;
228             }
229             else {
230             my @taxondata = @{ $taxdata[$taxon_id] };
231             $line =~ s{\A \s+}{}xms;
232             push @taxondata, ( split /\s+/xms, $line );
233             $taxdata[$taxon_id] = [@taxondata];
234             }
235              
236             if ( $ff =~ m{dist}xms ) {
237              
238             # how many tab/space seperated items do we expect?
239             my $number_items_in_row;
240             if ( $ff =~ m{_dist_square}xms ) {
241             $number_items_in_row = $number_taxa;
242             }
243             elsif ( $ff =~ m{_dist_lower}xms ) {
244             $number_items_in_row = $taxon_id;
245             }
246             elsif ( $ff =~ m{_dist_upper}xms ) {
247             $number_items_in_row = $number_taxa - ( $taxon_id + 1 );
248             }
249              
250             if ( scalar( @{ $taxdata[$taxon_id] } ) < $number_items_in_row ) {
251             $taxon_started = 1;
252             }
253             else {
254             $taxon_started = 0;
255             }
256             }
257             else {
258             my $seq = join q{}, @{ $taxdata[$taxon_id] };
259             if ( $ff =~ m{_seq_seq}xms ) {
260             if ( length $seq < $number_chars ) {
261             $taxon_started = 1;
262             }
263             else {
264             $taxon_started = 0;
265             }
266             }
267              
268             next LINE if $ff =~ m{_seq_seq}xms;
269              
270             # interleaved
271             if ( scalar(@taxlabels) == $number_taxa ) {
272             if ( $taxon_id >= ( $number_taxa - 1 ) ) {
273             $taxon_id = 0;
274             }
275             else {
276             $taxon_id++;
277             }
278             $taxon_started = 1;
279             }
280             }
281             }
282             croak "ERROR: Could not parse $filename. Number taxa not correct.\n"
283             if scalar(@taxlabels) != $number_taxa;
284              
285             $self->_create_nexus_obj( $ff, \@taxlabels, \@taxdata, $number_taxa );
286              
287             if ($verbose) {
288             $self->say('File import complete.');
289             }
290             return $self;
291             }
292              
293             sub _create_nexus_obj {
294             my ( $self, $ff, $taxlabels_ref, $taxdata_ref, $number_taxa ) = @_;
295              
296             my $taxa_block = Bio::NEXUS::TaxaBlock->new('taxa');
297             $taxa_block->set_taxlabels($taxlabels_ref);
298             $self->add_block($taxa_block);
299              
300             if ( $ff =~ m{dist}xms ) {
301             my $distances_block = Bio::NEXUS::DistancesBlock->new('distances');
302             $distances_block->set_ntax( scalar @{$taxlabels_ref} );
303             $distances_block->set_taxlabels($taxlabels_ref);
304             $distances_block->set_format(
305             { triangle => 'lower', diagonal => 1, labels => 1 } );
306             my $matrix;
307             for my $i ( 0 .. $distances_block->get_ntax - 1 ) {
308             for my $j ( 0 .. $distances_block->get_ntax - 1 ) {
309             my $dist;
310             if ( defined $taxdata_ref->[$i]->[$j] ) {
311             $dist = $taxdata_ref->[$i]->[$j];
312             }
313             else {
314             $dist = $taxdata_ref->[$j]->[$i];
315              
316             # diag. entries:
317             if ( !defined $dist ) {
318             $dist = 0;
319             }
320             }
321             $matrix->{ $taxlabels_ref->[$i] }{ $taxlabels_ref->[$j] }
322             = $dist;
323             }
324             }
325             $distances_block->{matrix} = $matrix;
326              
327             # $distances_block->_write_matrix();
328              
329             $self->add_block($distances_block);
330             }
331             else {
332             my $chars_block = Bio::NEXUS::CharactersBlock->new('characters');
333             my %taxa;
334             for my $i ( 0 .. $number_taxa - 1 ) {
335             $taxa{ $taxlabels_ref->[$i] } = join q{}, @{ $taxdata_ref->[$i] };
336             }
337              
338             my (@otus);
339              
340             for my $name ( @{$taxlabels_ref} ) {
341             my $seq = $taxa{$name};
342             push @otus,
343             Bio::NEXUS::TaxUnit->new( $name, [ split //xms, $seq ] );
344             }
345              
346             my $otuset = $chars_block->get_otuset();
347             $otuset->set_otus( \@otus );
348             $chars_block->set_taxlabels( $otuset->get_otu_names() );
349              
350             $self->add_block($chars_block);
351             }
352             return;
353             }
354              
355             1; # Magic true value required at end of module
356             __END__