File Coverage

blib/lib/Bio/Phylo/Parsers/Nexus.pm
Criterion Covered Total %
statement 558 677 82.4
branch 195 282 69.1
condition 114 195 58.4
subroutine 45 53 84.9
pod n/a
total 912 1207 75.5


line stmt bran cond sub pod time code
1             package Bio::Phylo::Parsers::Nexus;
2 6     6   32 use strict;
  6         10  
  6         155  
3 6     6   27 use base 'Bio::Phylo::Parsers::Abstract';
  6         9  
  6         1496  
4 6     6   34 use Bio::Phylo::Factory;
  6         10  
  6         21  
5 6     6   24 use Bio::Phylo::IO 'parse';
  6         12  
  6         240  
6 6     6   26 use Bio::Phylo::Util::CONSTANT qw':objecttypes looks_like_instance';
  6         12  
  6         1061  
7 6     6   34 use Bio::Phylo::Util::Exceptions 'throw';
  6         9  
  6         2711  
8              
9             # TODO: handle mixed? distances, splits, bipartitions
10             my $TAXA = _TAXA_;
11             my $MATRIX = _MATRIX_;
12              
13             # useful regular expressions
14             my $COMMENT = qr|^\[|; # crude, only checks first char, use after tokenizing!
15             my $QUOTES_OR_BRACKETS =
16             qr/[\[\]'"]/mox; # catch all for opening/closing square brackets and quotes
17             my $OPENING_QUOTE_OR_BRACKET =
18             qr/^(.*?)([\['"].*)$/mox; # capturing regex for opening sq. br. & q.
19              
20             # this is a dispatch table whose sub references are invoked
21             # during parsing. the keys match the tokens upon which the
22             # respective subs are called. Underscored (private) fields are for parsing
23             # context. The fields of this table comprise the default state of the
24             # parser object.
25             my %defaults = (
26             '_lines' => undef,
27             '_current' => undef,
28             '_previous' => undef,
29             '_begin' => undef,
30             '_ntax' => undef,
31             '_nchar' => undef,
32             '_gap' => undef,
33             '_missing' => undef,
34             '_i' => undef,
35             '_tree' => undef,
36             '_trees' => undef,
37             '_treename' => undef,
38             '_treestart' => undef,
39             '_row' => undef,
40             '_matrixtype' => undef,
41             '_found' => 0,
42             '_linemode' => 0,
43             '_taxlabels' => [],
44             '_tokens' => [],
45             '_context' => [],
46             '_translate' => [],
47             '_symbols' => [],
48             '_charlabels' => [],
49             '_statelabels' => [],
50             '_charstatelabels' => [],
51             '_tmpstatelabels' => [],
52             '_comments' => [],
53             '_treenames' => [],
54             '_matrixrowlabels' => [],
55             '_matrix' => {},
56             '_charset' => {},
57             '_taxset' => {},
58             'begin' => \&_begin,
59             'taxa' => \&_taxa,
60             'title' => \&_title,
61             'dimensions' => \&_dimensions,
62             'ntax' => \&_ntax,
63             'taxlabels' => \&_taxlabels,
64             'blockid' => \&_blockid,
65             'data' => \&_data,
66             'characters' => \&_characters,
67             'codons' => \&_codons,
68             'nchar' => \&_nchar,
69             'format' => \&_format,
70             'datatype' => \&_datatype,
71             'matchchar' => \&_matchchar,
72             'gap' => \&_gap,
73             'missing' => \&_missing,
74             'charlabels' => \&_charlabels,
75             'statelabels' => \&_statelabels,
76             'charstatelabels' => \&_charstatelabels,
77             'symbols' => \&_symbols,
78             'items' => \&_items,
79             'matrix' => \&_matrix,
80             'charset' => \&_charset,
81             'taxset' => \&_taxset,
82             'trees' => \&_trees,
83             'translate' => \&_translate,
84             'tree' => \&_tree,
85             'utree' => \&_tree,
86             'end' => \&_end,
87             'endblock' => \&_end,
88             '#nexus' => \&_nexus,
89             'link' => \&_link,
90             ';' => \&_semicolon,
91             'interleave' => \&_interleave,
92             );
93              
94             =head1 NAME
95              
96             Bio::Phylo::Parsers::Nexus - Parser used by Bio::Phylo::IO, no serviceable parts inside
97              
98             =head1 DESCRIPTION
99              
100             This module parses nexus files. It is called by the L<Bio::Phylo::IO> module,
101             there is no direct usage. The parser can handle files and strings with multiple
102             tree, taxon, and characters blocks whose links are defined using Mesquite's
103             "TITLE = 'some_name'" and "LINK TAXA = 'some_name'" tokens.
104              
105             The parser returns a reference to an array containing one or more taxa, trees
106             and matrices objects. Nexus comments are stripped, private nexus blocks (and the
107             'assumptions' block) are skipped. It currently doesn't handle 'mixed' data.
108              
109             =begin comment
110              
111             Type : Constructor
112             Title : _new
113             Usage : my $nexus = Bio::Phylo::Parsers::Nexus->_new;
114             Function: Initializes a Bio::Phylo::Parsers::Nexus object.
115             Returns : A Bio::Phylo::Parsers::Nexus object.
116             Args : none.
117              
118             =end comment
119              
120             =cut
121              
122             sub _process_defaults {
123 15     15   27 my $self = shift;
124 15         183 for my $key ( keys %defaults ) {
125 990 100       1504 if ( looks_like_instance( $defaults{$key}, 'ARRAY' ) ) {
    100          
126 180         378 $self->{$key} = [];
127             }
128             elsif ( looks_like_instance( $defaults{$key}, 'HASH' ) ) {
129 45         144 $self->{$key} = {};
130             }
131             else {
132 765         1319 $self->{$key} = $defaults{$key};
133             }
134             }
135 15         58 return $self;
136             }
137              
138             =begin comment
139              
140             Type : Wrapper
141             Title : _from_handle(\*FH)
142             Usage : $nexus->_from_handle(\*FH);
143             Function: Does all the parser magic, from a file handle
144             Returns : ARRAY
145             Args : \*FH = file handle
146              
147             =end comment
148              
149             =cut
150              
151             sub _parse {
152 15     15   25 my $self = shift;
153 15         48 $self->_process_defaults;
154 15         61 $self->_logger->info("going to parse nexus data");
155 15         56 $self->{'_lines'} = $self->_stringify(@_);
156 15         49 $self->{'_tokens'} = $self->_tokenize( $self->{'_lines'} );
157              
158             # iterate over tokens, dispatch methods from %{ $self } table
159             # This is the meat of the parsing, from here everything else is called.
160 15         52 $self->_logger->info("tokenized and split data, going to parse blocks");
161 15         26 my $i = 0;
162 15         25 my $private_block;
163 15         35 my $token_queue = [ undef, undef, undef ];
164 6     6   39 no strict 'refs';
  6         8  
  6         32976  
165 15         25 TOKEN_LINE: for my $token_line ( @{ $self->{'_tokens'} } ) {
  15         39  
166 313 100       576 if ( not $self->{'_linemode'} ) {
    50          
167 265         305 RAW_TOKEN: for my $raw_token ( @{$token_line} ) {
  265         401  
168 1060 100       3578 if ( $raw_token =~ qr/^\[/ ) {
169 10         20 push @{ $self->{'_comments'} }, $raw_token;
  10         19  
170 10         28 next RAW_TOKEN;
171             }
172 1050         1943 my $lower_case_token = lc($raw_token);
173 1050         1562 push @$token_queue, $lower_case_token;
174 1050         1185 shift @$token_queue;
175 1050 100 66     3347 if ( exists $self->{$lower_case_token} and not $private_block )
    100 66        
176             {
177 415 50       849 if ( ref $self->{$lower_case_token} eq 'CODE' ) {
178 415         621 $self->{'_previous'} = $self->{'_current'};
179 415         522 $self->{'_current'} = $lower_case_token;
180              
181             # pull code ref from dispatch table
182 415         511 my $c = $self->{$lower_case_token};
183              
184             # invoke as object method
185 415         919 $self->$c($raw_token);
186 414         826 next RAW_TOKEN;
187             }
188             }
189             elsif ( $self->{'_current'} and not $private_block ) {
190 634         916 my $c = $self->{ $self->{'_current'} };
191 634         1101 $self->$c($raw_token);
192 634         1057 next RAW_TOKEN;
193             }
194              
195             # $self->{'_begin'} is switched 'on' by &_begin(), and 'off'
196             # again by any one of the appropriate subsequent tokens, i.e.
197             # taxa, data, characters and trees
198 1 0 33     4 if ( $self->{'_begin'}
      33        
199             and not exists $self->{$lower_case_token}
200             and not $private_block )
201             {
202 0         0 $private_block = $raw_token;
203 0         0 next RAW_TOKEN;
204             }
205              
206             # jump over private block content
207 1 50 33     3 if ( $private_block
      33        
208             and $token_queue->[-2] eq 'end'
209             and $token_queue->[-1] eq ';' )
210             {
211 0         0 $private_block = 0;
212 0         0 $self->_logger->info(
213             "Skipped private $private_block block");
214 0         0 next RAW_TOKEN;
215             }
216             else {
217 1         2 next RAW_TOKEN;
218             }
219             }
220             }
221             elsif ( $self->{'_linemode'} ) {
222 48         76 my $c = $self->{ $self->{'_current'} };
223 48         59 push @{$token_queue}, $token_line;
  48         74  
224 48         67 shift @$token_queue;
225 48         100 $self->$c($token_line);
226 47         102 next TOKEN_LINE;
227             }
228             }
229 13         51 return $self->_post_process(@_);
230             }
231              
232             # makes array reference of strings, one string per line, from input
233             # file handle or string;
234             sub _stringify {
235 15     15   26 my $self = shift;
236 15         39 $self->_logger->info("going to split nexus data on lines");
237 15         31 my %opts = @_;
238 15         25 my @lines;
239 15         50 my $handle = $self->_handle;
240 15         84 while (<$handle>) {
241 435         591 my $line = $_;
242 435         1423 push @lines, grep { /\S/ } split( /\n|\r|\r\n/, $line );
  379         1056  
243 435         856 $self->_logger->debug("read line: $line");
244             }
245 15         52 return \@lines;
246             }
247              
248             =begin comment
249              
250             Type : Method
251             Title : _tokenize()
252             Usage : $nexus->_tokenize($lines);
253             Function: Tokenizes lines in $lines array ref
254             Returns : Two dimensional ARRAY
255             Args : An array ref of lines (e.g. read from an input file);
256             Comments: This method accepts an array ref holding lines that may contain
257             single quotes, double quotes or square brackets. Line breaks and
258             spaces inside these quoted/bracketed fragments are ignored, otherwise
259             it is split, e.g.:
260              
261             [
262             [ '#NEXUS' ],
263             [ 'BEGIN TAXA; [taxablock comment]' ],
264             [ 'DIMENSIONS NTAX=3;' ],
265             [ 'TAXLABELS "Taxon \' A" \'Taxon B\' TAXON[comment]C' ],
266             ...etc...
267             ]
268              
269             becomes:
270             [
271             [ '#NEXUS' ],
272             [
273             'BEGIN',
274             'TAXA',
275             ';',
276             '[taxablock comment]'
277             ],
278             [
279             'DIMENSIONS',
280             'NTAX',
281             '=',
282             '3',
283             ';'
284             ],
285             [
286             'TAXLABELS',
287             '"Taxon \' A"',
288             '\'Taxon B\'',
289             'TAXON',
290             '[comment]',
291             'C'
292             ],
293             ...etc...
294             ]
295              
296              
297             =end comment
298              
299             =cut
300              
301             sub _tokenize {
302 15     15   111 my ( $self, $lines ) = @_;
303 15         46 $self->_logger->info("going to split lines on tokens");
304 15         40 my ( $extract, $INSIDE_QUOTE, $continue ) = ( '', 0, 0 );
305 15         71 my ( @tokens, @split );
306 15         78 my $CLOSING_BRACKET_MIDLINE = qr/^.*?(\])(.*)$/mox;
307 15         47 my $CONTEXT_QB_AT_START = qr/^([\['"])(.*)$/mox;
308 15         45 my $CONTEXT_CLOSER;
309             my $QuoteContext; # either " ' or [
310 15         0 my $QuoteStartLine;
311 15         29 my $LineCount = 0;
312 15         62 my %CLOSE_CHAR = (
313             '"' => '"',
314             "'" => "'",
315             '[' => ']',
316             );
317 15         197 my %INVERSE_CLOSE_CHAR = (
318             '"' => '"',
319             "'" => "'",
320             ']' => '[',
321             ')' => '(',
322             );
323              
324             # tokenize
325 15         24 LINE: for my $line ( @{$lines} ) {
  15         37  
326 379         467 $LineCount++;
327 379         993 TOKEN: while ( $line =~ /\S/ ) {
328              
329             # line in file has no quoting/bracketing characters, and
330             # is no extension of a quoted/bracketed fragment starting
331             # on a previous line
332 408 100 100     2272 if ( $line !~ $QUOTES_OR_BRACKETS && !$INSIDE_QUOTE ) {
    100 100        
    100 66        
    100 66        
    50 33        
333 332 100       500 if ($continue) {
334 20         25 push @{ $tokens[-1] }, $line;
  20         49  
335 20         36 $continue = 0;
336             }
337             else {
338 312         598 push @tokens, [$line];
339             }
340 332         417 my $logline = join( ' ', @{ $tokens[-1] } );
  332         604  
341 332         477 chomp($logline);
342 332         610 $self->_logger->debug("Tokenized line $LineCount: $logline");
343 332         591 next LINE;
344             }
345              
346             # line in file has opening quoting/bracketing characters, and
347             # is no extension of a quoted/bracketed fragment starting
348             # on a previous line
349             elsif ( $line =~ $OPENING_QUOTE_OR_BRACKET && !$INSIDE_QUOTE ) {
350 20         80 my ( $start, $quoted ) = ( $1, $2 );
351 20         46 push @tokens, [$start];
352 20         38 $line = $quoted;
353 20         25 $extract = $quoted;
354 20         28 $INSIDE_QUOTE++;
355 20         27 $continue = 1;
356 20         49 $QuoteContext = substr( $quoted, 0, 1 );
357 20         49 $self->_logger->debug("Line $LineCount contains $QuoteContext");
358 20         32 $QuoteStartLine = $LineCount;
359 20         199 $CONTEXT_QB_AT_START = qr/^(\Q$QuoteContext\E)(.*)$/;
360 20         43 my $context_closer = $CLOSE_CHAR{$QuoteContext};
361 20         163 $CONTEXT_CLOSER = qr/^(.*?)(\Q$context_closer\E)(.*)$/;
362 20         77 next TOKEN;
363             }
364              
365             # line in file has no quoting/bracketing characters, and
366             # is an extension of a quoted/bracketed fragment starting
367             # on a previous line
368             elsif ( $line !~ $CONTEXT_CLOSER && $INSIDE_QUOTE ) {
369 30         72 $self->_logger->debug(
370             "Line $LineCount extends quote or comment");
371 30         52 $extract .= $line;
372 30         51 next LINE;
373             }
374             elsif ( $line =~ $CONTEXT_QB_AT_START && $INSIDE_QUOTE ) {
375 20         85 my ( $q, $remainder ) = ( $1, $1 . $2 );
376 20 100 100     84 if ( $q eq '"' || $q eq "'" ) {
    50          
377 9 50       113 if ( $remainder =~ m/^($q[^$q]*?$q)(.*)$/ ) {
    0          
378 9         29 $self->_logger->debug(
379             "Line $LineCount closes $INVERSE_CLOSE_CHAR{$q} with $q"
380             );
381 9         13 push @{ $tokens[-1] }, ($1);
  9         32  
382 9         17 $line = $2;
383 9         11 $INSIDE_QUOTE--;
384 9         39 next TOKEN;
385             }
386             elsif ( $remainder =~ m/^$q[^$q]*$/ ) {
387 0         0 $extract .= $line;
388 0         0 $continue = 1;
389 0         0 next LINE;
390             }
391             }
392             elsif ( $q eq '[' ) {
393 11         32 for my $i ( 1 .. length($line) ) {
394 830 100       1151 $INSIDE_QUOTE++ if substr( $line, $i, 1 ) eq '[';
395 830 100 66     1761 if ( $i and !$INSIDE_QUOTE ) {
396 8         12 push @{ $tokens[-1] }, substr( $line, 0, $i );
  8         31  
397 8         20 my $logqc = substr( $line, ( $i - 1 ), 1 );
398 8         25 $self->_logger->debug(
399             "Line $LineCount closes $INVERSE_CLOSE_CHAR{$logqc} with $logqc"
400             );
401 8         17 $line = substr( $line, $i );
402 8         24 next TOKEN;
403             }
404 822 100       1238 $INSIDE_QUOTE-- if substr( $line, $i, 1 ) eq ']';
405             }
406 3         6 $extract = $line;
407 3         7 $continue = 1;
408 3         7 next LINE;
409             }
410             }
411             elsif ( $line =~ $CONTEXT_CLOSER && $INSIDE_QUOTE ) {
412 6         23 my ( $start, $q, $remainder ) = ( $1, $2, $3 );
413 6         18 $self->_logger->debug(
414             "Line $LineCount closes $INVERSE_CLOSE_CHAR{$q} with $q");
415 6 50       19 $start = $extract . $start if $continue;
416 6 50 33     28 if ( $q eq '"' or $q eq "'" ) {
    50          
417 0         0 push @{ $tokens[-1] }, $start;
  0         0  
418 0         0 $line = $remainder;
419 0         0 next TOKEN;
420             }
421             elsif ( $q eq ']' ) {
422 6         13 for my $i ( 0 .. length($line) ) {
423 414 100       562 $INSIDE_QUOTE++ if substr( $line, $i, 1 ) eq '[';
424 414 100 100     861 if ( $i and !$INSIDE_QUOTE ) {
425 3         5 my $segment = substr( $line, 0, $i );
426 3 50       8 if ($continue) {
427 3         3 push @{ $tokens[-1] }, $extract . $segment;
  3         12  
428             }
429             else {
430 0         0 push @{ $tokens[-1] }, $segment;
  0         0  
431             }
432 3         7 $line = substr( $line, $i );
433 3         8 next TOKEN;
434             }
435 411 100       598 $INSIDE_QUOTE-- if substr( $line, $i, 1 ) eq ']';
436             }
437 3 50       7 if ($continue) {
438 3         6 $extract .= $line;
439             }
440             else {
441 0         0 $extract = $line;
442             }
443 3         6 $continue = 1;
444 3         5 next LINE;
445             }
446             }
447             }
448             }
449              
450             # an exception here means that an opening quote symbol " ' [
451             # ($QuoteContext) was encountered at input file/string line $QuoteStartLine.
452             # This can happen if any of these symbols is used in an illegal
453             # way, e.g. by using double quotes as gap symbols in matrices.
454 15 50       43 if ($INSIDE_QUOTE) {
455 0         0 throw 'BadArgs' =>
456             "Unbalanced $QuoteContext starting at line $QuoteStartLine";
457             }
458              
459             # final split: non-quoted/bracketed fragments are split on whitespace,
460             # others are preserved verbatim
461             $self->_logger->info(
462 15         53 "going to split non-quoted/commented fragments on whitespace");
463 15         33 foreach my $line (@tokens) {
464 332         372 my @line;
465 332         450 foreach my $word (@$line) {
466 372 100       988 if ( $word !~ $QUOTES_OR_BRACKETS ) {
467 352         1424 $word =~ s/(=|;|,)/ $1 /g;
468 352         1242 push @line, grep { /\S/ } split /\s+/, $word;
  1470         3021  
469             }
470             else {
471 20         46 push @line, $word;
472             }
473             }
474 332         638 push @split, \@line;
475             }
476 15         139 return \@split;
477             }
478              
479             # link matrices and forests to taxa
480             sub _post_process {
481 13     13   23 my $self = shift;
482 13         27 my $taxa = [];
483 13         21 foreach my $block ( @{ $self->{'_context'} } ) {
  13         37  
484 26 100 33     63 if ( $block->_type == $TAXA ) {
    50          
485 13         21 push @{$taxa}, $block;
  13         33  
486             }
487             elsif ( $block->_type != $TAXA and $block->can('set_taxa') ) {
488 13 50 33     101 if ( $taxa->[-1]
      33        
489             and $taxa->[-1]->can('_type') == $TAXA
490             and not $block->get_taxa )
491             {
492 0         0 $block->set_taxa( $taxa->[-1] ); # XXX exception here?
493             }
494             }
495             }
496 13         27 my $blocks = $self->{'_context'};
497              
498             # initialize object, note we have to
499             # force data type references to be empty
500 13         23 @{$taxa} = ();
  13         33  
501 13         195 for my $key ( keys %defaults ) {
502 858 100       1352 if ( looks_like_instance( $defaults{$key}, 'ARRAY' ) ) {
    100          
503 156         380 $self->{$key} = [];
504             }
505             elsif ( looks_like_instance( $defaults{$key}, 'HASH' ) ) {
506 39         101 $self->{$key} = {};
507             }
508             else {
509 663         1061 $self->{$key} = $defaults{$key};
510             }
511             }
512 13         45 return @{$blocks};
  13         65  
513             }
514              
515             =begin comment
516              
517             The following subs are called by the dispatch table stored in the object when
518             their respective tokens are encountered.
519              
520             =end comment
521              
522             =cut
523              
524             sub _nexus {
525 14     14   22 my $self = shift;
526 14 50       44 if ( uc( $_[0] ) eq '#NEXUS' ) {
527 14         40 $self->_logger->info("found nexus token");
528             }
529             }
530              
531             sub _begin {
532 43     43   59 my $self = shift;
533 43         65 $self->{'_begin'} = 1;
534             }
535              
536             sub _taxa {
537 18     18   30 my $self = shift;
538 18 100       39 if ( $self->{'_begin'} ) {
539 13         49 my $taxa = $self->_factory->create_taxa;
540 13         26 push @{ $self->{'_context'} }, $taxa;
  13         35  
541 13         40 $self->_logger->info("starting taxa block");
542 13         29 $self->{'_begin'} = 0;
543             }
544             else {
545 5         7 $self->{'_current'} = 'link'; # because of 'link taxa = blah' construct
546             }
547             }
548              
549             sub _charset {
550 17     17   20 my $self = shift;
551 17         22 my $token = shift;
552            
553             # first thing after the CHARSET token is the set name
554 17 100 100     113 if ( $token !~ /CHARSET/i && ! $self->{'_charset'}->{'name'} ) {
    100 100        
    100 100        
    100          
555 2         8 $self->{'_charset'}->{'name'} = $token;
556 2         7 $self->{'_charset'}->{'range'} = [];
557             }
558            
559             # then there might be a mesquite-style matrix reference, e.g. (CHARACTERS = matrix_name)
560             elsif ( $token =~ m/^\(/ ) {
561 1         2 $self->{'_charset'}->{'matrix'} = '';
562             }
563             elsif ( defined $self->{'_charset'}->{'matrix'} && ! $self->{'_charset'}->{'matrix'} && $token !~ /(?:\(?CHARACTERS|=)/i ) {
564 1         5 $token =~ s/\)$//;
565 1         3 $self->{'_charset'}->{'matrix'} = $token;
566             }
567            
568             # then come the indices
569             elsif ( $token =~ /(?:\d+|-)/ ) {
570 8         11 push @{ $self->{'_charset'}->{'range'} }, $token;
  8         16  
571             }
572             }
573              
574             sub _taxset {
575 19     19   22 my $self = shift;
576 19         22 my $token = shift;
577            
578             # first thing after the TAXSET token is the set name
579 19 100 100     107 if ( $token !~ /TAXSET/i && ! $self->{'_taxset'}->{'name'} ) {
    100 100        
    100 100        
    100          
580 2         5 $self->{'_taxset'}->{'name'} = $token;
581 2         5 $self->{'_taxset'}->{'range'} = [];
582             }
583            
584             # then there might be a mesquite-style taxa reference, e.g. (TAXA = matrix_name)
585             elsif ( $token =~ m/^\(/ ) {
586 1         3 $self->{'_taxset'}->{'taxa'} = '';
587             }
588             elsif ( defined $self->{'_taxset'}->{'taxa'} && ! $self->{'_taxset'}->{'taxa'} && $token !~ /(?:\(?TAXA|=)/ ) {
589 1         4 $token =~ s/\)$//;
590 1         3 $self->{'_taxset'}->{'taxa'} = $token;
591             }
592            
593             # then come the indices
594             elsif ( $token =~ /(?:\d+|-)/ ) {
595 10         15 push @{ $self->{'_taxset'}->{'range'} }, $token;
  10         17  
596             }
597             }
598              
599             sub _interleave {
600 0     0   0 my $self = shift;
601 0         0 my $token = shift;
602 0         0 $self->_logger->info("perhaps we'll need to parse interleaved");
603 0 0 0     0 if ( defined $token and uc($token) eq 'NO' ) {
604 0         0 $self->_logger->info("no, we don't need to parse interleaved");
605             }
606             }
607              
608             sub _title {
609 25     25   37 my $self = shift;
610 25         31 my $token = shift;
611 25 100 66     103 if ( defined $token and uc($token) ne 'TITLE' ) {
612 11         20 my $title = $token;
613 11 50       28 if ( not $self->_current->get_name ) {
614 11         31 $self->_current->set_name($title);
615 11         32 $self->_logger->info("block has title '$title'");
616             }
617             }
618             }
619              
620             sub _link {
621 2     2   3 my $self = shift;
622 2         3 my $token = shift;
623 2 50 33     14 if ( defined $token and $token !~ m/^(?:LINK|TAXA|=)$/i ) {
624 0         0 my $link = $token;
625 0 0       0 if ( not $self->_current->get_taxa ) {
626 0         0 foreach my $block ( @{ $self->{'_context'} } ) {
  0         0  
627 0 0 0     0 if ( $block->get_name and $block->get_name eq $link ) {
628 0         0 $self->_current->set_taxa($block);
629 0         0 last;
630             }
631             }
632             $self->_logger->info(
633 0         0 "block links to taxa block with title '$link'");
634             }
635             }
636             }
637              
638       22     sub _dimensions {
639              
640             #my $self = shift;
641             }
642              
643             sub _ntax {
644 39     39   44 my $self = shift;
645 39 100 66     177 if ( defined $_[0] and $_[0] =~ m/^\d+$/ ) {
646 13         27 $self->{'_ntax'} = shift;
647 13         22 my $ntax = $self->{'_ntax'};
648 13         43 $self->_logger->info("number of taxa: $ntax");
649             }
650             }
651              
652             sub _taxlabels {
653 72     72   96 my $self = shift;
654 72 100 66     236 if ( defined $_[0] and uc( $_[0] ) ne 'TAXLABELS' ) {
    50 33        
655 59         76 my $taxon = shift;
656 59         109 $self->_logger->debug("taxon: $taxon");
657 59         75 push @{ $self->{'_taxlabels'} }, $taxon;
  59         109  
658             }
659             elsif ( defined $_[0] and uc( $_[0] ) eq 'TAXLABELS' ) {
660             $self->_current->set_generic(
661 13         40 'nexus_comments' => $self->{'_comments'} );
662 13         27 $self->{'_comments'} = [];
663 13         34 $self->_logger->info("starting taxlabels");
664             }
665             }
666              
667             sub _blockid {
668 0     0   0 my $self = shift;
669 0 0 0     0 if ( defined $_[0] and uc( $_[0] ) ne 'BLOCKID' ) {
670 0         0 my $blockid = shift;
671 0         0 $self->_logger->debug("blockid: $blockid");
672 0         0 $self->_current->set_generic( 'blockid' => $blockid );
673             }
674             }
675              
676             sub _data {
677 0     0   0 my $self = shift;
678 0 0       0 if ( $self->{'_begin'} ) {
679 0         0 $self->{'_begin'} = 0;
680 0         0 push @{ $self->{'_context'} }, $self->_factory->create_matrix;
  0         0  
681 0         0 $self->_logger->info("starting data block");
682             }
683             }
684              
685             sub _characters {
686 9     9   14 my $self = shift;
687 9 50       29 if ( $self->{'_begin'} ) {
688 9         22 $self->{'_begin'} = 0;
689 9         19 push @{ $self->{'_context'} }, $self->_factory->create_matrix;
  9         33  
690 9         36 $self->_logger->info("starting characters block");
691             }
692             }
693              
694             sub _nchar {
695 27     27   35 my $self = shift;
696 27 100 66     121 if ( defined $_[0] and $_[0] =~ m/^\d+$/ ) {
697 9         22 $self->{'_nchar'} = shift;
698 9         24 my $nchar = $self->{'_nchar'};
699 9         27 $self->_logger->info("number of characters: $nchar");
700             }
701             }
702              
703       9     sub _format {
704              
705             #my $self = shift;
706             }
707              
708             sub _datatype {
709 27     27   33 my $self = shift;
710 27 100 66     124 if ( defined $_[0] and $_[0] !~ m/^(?:DATATYPE|=)/i ) {
711 9         17 my $datatype = shift;
712 9         23 $self->_current->set_type($datatype);
713 9         27 $self->_logger->info("datatype: $datatype");
714             }
715             }
716              
717             sub _matchchar {
718 0     0   0 my $self = shift;
719 0 0 0     0 if ( defined $_[0] and $_[0] !~ m/^(?:MATCHCHAR|=)/i ) {
720 0         0 my $matchchar = shift;
721 0         0 $self->_current->set_matchchar($matchchar);
722 0         0 $self->_logger->info("matchchar: $matchchar");
723             }
724             }
725              
726       0     sub _items {
727              
728             #my $self = shift;
729             }
730              
731             sub _gap {
732 27     27   36 my $self = shift;
733 27 100 66     128 if ( $_[0] !~ m/^(?:GAP|=)/i and !$self->{'_gap'} ) {
734 9         19 $self->{'_gap'} = shift;
735 9         18 my $gap = $self->{'_gap'};
736 9         21 $self->_current->set_gap($gap);
737 9         27 $self->_logger->info("gap character: $gap");
738 9         29 undef $self->{'_gap'};
739             }
740             }
741              
742             sub _missing {
743 27     27   37 my $self = shift;
744 27 100 66     120 if ( $_[0] !~ m/^(?:MISSING|=)/i and !$self->{'_missing'} ) {
745 9         19 $self->{'_missing'} = shift;
746 9         19 my $missing = $self->{'_missing'};
747 9         23 $self->_current->set_missing($missing);
748 9         23 $self->_logger->info("missing character: $missing");
749 9         21 undef $self->{'_missing'};
750             }
751             }
752              
753             sub _symbols {
754 15     15   22 my $self = shift;
755 15 100 66     100 if ( $_[0] !~ m/^(?:SYMBOLS|=)$/i and $_[0] =~ m/^"?(.+)"?$/ ) {
756 5         20 my $sym = $1;
757 5         16 $sym =~ s/"//g;
758 5         33 my @syms = grep { /\S+/ } split /\s+/, $sym;
  17         49  
759 5         11 push @{ $self->{'_symbols'} }, @syms;
  5         15  
760 5         16 $self->_logger->debug("recorded character state symbols '@syms'");
761             }
762             }
763              
764             sub _charlabels {
765 0     0   0 my $self = shift;
766 0 0 0     0 if ( defined $_[0] and uc $_[0] ne 'CHARLABELS' ) {
767 0         0 push @{ $self->{'_charlabels'} }, shift;
  0         0  
768             }
769             }
770              
771             sub _charstatelabels {
772 18     18   21 my $self = shift;
773 18         19 my $token = shift;
774 18         32 $self->_logger->debug($token);
775 18 100 66     51 if ( defined $token and uc $token ne 'CHARSTATELABELS' ) {
776 16         20 push @{ $self->{'_charstatelabels'} }, $token;
  16         32  
777             }
778             }
779              
780             sub _statelabels {
781 0     0   0 my $self = shift;
782 0         0 my $token = shift;
783 0 0 0     0 if ( defined $token and uc $token ne 'STATELABELS' ) {
784 0 0       0 if ( $token eq ',' ) {
785 0         0 my $tmpstatelabels = $self->{'_tmpstatelabels'};
786 0         0 my $index = shift @{$tmpstatelabels};
  0         0  
787 0         0 $self->{'_statelabels'}->[ $index - 1 ] = $tmpstatelabels;
788 0         0 $self->{'_tmpstatelabels'} = [];
789             }
790             else {
791 0         0 push @{ $self->{'_tmpstatelabels'} }, $token;
  0         0  
792             }
793             }
794             }
795              
796             # for data type, character labels, state labels
797             sub _add_matrix_metadata {
798 57     57   67 my $self = shift;
799 57         109 $self->_logger->info("adding matrix metadata");
800 57 100       123 if ( not defined $self->{'_matrixtype'} ) {
801 9         35 $self->{'_matrixtype'} = $self->_current->get_type;
802 9 50       15 if ( @{ $self->{'_charlabels'} } ) {
  9         27  
803 0         0 $self->_current->set_charlabels( $self->{'_charlabels'} );
804 0         0 $self->_logger->debug("adding character labels");
805             }
806 9 50       17 if ( @{ $self->{'_statelabels'} } ) {
  9         24  
807 0         0 $self->_current->set_statelabels( $self->{'_statelabels'} );
808 0         0 $self->_logger->debug("adding state labels");
809             }
810 9 50       17 if ( my @symbols = @{ $self->{'_symbols'} } ) {
  9         31  
811 0         0 $self->_logger->debug("updating state lookup table");
812 0         0 my $to = $self->_current->get_type_object;
813 0         0 my $lookup = $to->get_lookup;
814 0 0       0 if ($lookup) {
815 0         0 for my $sym (@symbols) {
816 0 0       0 if ( not exists $lookup->{$sym} ) {
817 0         0 $lookup->{$sym} = [$sym];
818             }
819             }
820             }
821             }
822             }
823 57         75 return $self;
824             }
825              
826             sub _add_tokens_to_row {
827 48     48   84 my ( $self, $tokens ) = @_;
828 48         58 my $rowname;
829 48         58 for my $token ( @{$tokens} ) {
  48         92  
830 90         168 $self->_logger->debug("token: $token");
831 90 100       177 last if $token eq ';';
832              
833             # mesquite sometimes writes multiline (but not interleaved)
834             # matrix rows (harrumph).
835 81 100 66     475 if ( not defined $rowname and $token !~ $COMMENT ) {
    50 33        
836 39         56 my $taxa;
837 39 50       75 if ( $taxa = $self->_current->get_taxa ) {
    50          
838 0 0       0 if ( my $taxon = $taxa->get_by_name($token) ) {
839 0         0 $rowname = $token;
840             }
841             else {
842 0         0 $rowname = $self->{'_matrixrowlabels'}->[-1];
843             }
844             }
845             elsif ( $taxa = $self->_find_last_seen_taxa_block ) {
846 39 50       97 if ( my $taxon = $taxa->get_by_name($token) ) {
847 39         64 $rowname = $token;
848             }
849             else {
850 0         0 $rowname = $self->{'_matrixrowlabels'}->[-1];
851             }
852             }
853             else {
854 0         0 $rowname = $token;
855             }
856 39 50       94 if ( not exists $self->{'_matrix'}->{$rowname} ) {
857 39         94 $self->{'_matrix'}->{$rowname} = [];
858 39         49 push @{ $self->{'_matrixrowlabels'} }, $rowname;
  39         92  
859             }
860             }
861             elsif ( defined $rowname and $token !~ $COMMENT ) {
862 42         78 my $row = $self->{'_matrix'}->{$rowname};
863 42 100       80 if ( $self->{'_matrixtype'} =~ m/^continuous$/i ) {
864 6         7 push @{$row}, split( /\s+/, $token );
  6         19  
865             }
866             else {
867 36         43 push @{$row}, split( //, $token );
  36         195  
868             }
869             }
870             }
871             }
872              
873             sub _find_last_seen_taxa_block {
874 85     85   111 my $self = shift;
875 85         118 my $name = shift;
876 85         106 for ( my $i = $#{ $self->{'_context'} } ; $i >= 0 ; $i-- ) {
  85         229  
877 180 100       453 if ( $self->{'_context'}->[$i]->_type == $TAXA ) {
878 84 100       136 if ( $name ) {
879 2 100       5 if ( $self->{'_context'}->[$i]->get_name eq $name ) {
880 1         3 return $self->{'_context'}->[$i];
881             }
882             }
883             else {
884 82         196 return $self->{'_context'}->[$i];
885             }
886             }
887             }
888 2         8 return;
889             }
890              
891             sub _find_last_seen_matrix {
892 4     4   7 my $self = shift;
893 4         7 my $name = shift;
894 4         11 for ( my $i = $#{ $self->{'_context'} } ; $i >= 0 ; $i-- ) {
  4         22  
895 5 50       19 if ( $self->{'_context'}->[$i]->_type == $MATRIX ) {
896 5 100       11 if ( $name ) {
897 2 100       5 if ( $self->{'_context'}->[$i]->get_name eq $name ) {
898 1         3 return $self->{'_context'}->[$i];
899             }
900             }
901             else {
902 3         7 return $self->{'_context'}->[$i];
903             }
904             }
905             }
906 0         0 return;
907             }
908              
909             sub _set_taxon {
910 74     74   129 my ( $self, $obj, $taxa ) = @_;
911              
912             # first case: a taxon by $obj's name already exists
913 74 50       163 if ( my $taxon = $taxa->get_by_name( $obj->get_name ) ) {
914 74         180 $obj->set_taxon($taxon);
915 74         130 return $self;
916             }
917              
918             # second case: no taxon by $obj's name exists yet
919             else {
920 0         0 my $taxon = $self->_factory->create_taxon( '-name' => $obj->get_name );
921 0         0 $taxa->insert($taxon);
922 0         0 $obj->set_taxon($taxon);
923 0         0 return $self;
924             }
925             }
926              
927             sub _resolve_taxon {
928 132     132   195 my ( $self, $obj ) = @_;
929 132         199 my $container = $self->_current;
930              
931             # first case: the object is actually already
932             # linked to a taxon
933 132 100       255 if ( my $taxon = $obj->get_taxon ) {
934 88         153 return $self;
935             }
936              
937             # second case: the container is already linked
938             # to a taxa block, but the object isn't
939 44 100       139 if ( my $taxa = $container->get_taxa ) {
940 30         70 $self->_set_taxon( $obj, $taxa );
941             }
942              
943             # third case: the container isn't explicitly linked,
944             # but a taxa block has been seen
945 44 100       106 if ( my $taxa = $self->_find_last_seen_taxa_block ) {
946 42         170 $container->set_taxa($taxa);
947 42         95 $self->_set_taxon( $obj, $taxa );
948             }
949              
950             # final case: no taxa block exists
951             else {
952 2         10 my $taxa = $container->make_taxa;
953 2         4 pop @{ $self->{'_context'} };
  2         7  
954 2         5 push @{ $self->{'_context'} }, $taxa, $container;
  2         5  
955 2         10 $self->_set_taxon( $obj, $taxa );
956             }
957             }
958              
959             sub _resolve_ambig {
960 39     39   77 my ( $self, $datum, $chars ) = @_;
961 39         115 my %brackets = (
962             '(' => ')',
963             '{' => '}',
964             );
965 39         90 my $to = $datum->get_type_object;
966 39         56 my @resolved;
967 39         51 my $in_set = 0;
968 39         62 my @set;
969             my $close;
970 39         48 for my $c ( @{$chars} ) {
  39         77  
971              
972 228 50 33     557 if ( not $in_set and not exists $brackets{$c} ) {
    0 0        
    0 0        
    0 0        
973 228 50       467 push @resolved, $c if defined $c;
974             }
975             elsif ( not $in_set and exists $brackets{$c} ) {
976 0         0 $in_set++;
977 0         0 $close = $brackets{$c};
978             }
979             elsif ( $in_set and $c ne $close ) {
980 0         0 push @set, $c;
981             }
982             elsif ( $in_set and $c eq $close ) {
983 0         0 push @resolved, $to->get_symbol_for_states(@set);
984 0         0 @set = ();
985 0         0 $in_set = 0;
986 0         0 $close = undef;
987             }
988             }
989 39         114 return \@resolved;
990             }
991              
992             sub _codons {
993 0     0   0 my $self = shift;
994 0         0 $self->_logger->info(shift);
995 0 0       0 if ( $self->{'_begin'} ) {
996 0         0 $self->{'_begin'} = 0;
997             }
998             }
999              
1000             sub _matrix {
1001 57     57   79 my $self = shift;
1002 57         68 my $token = shift;
1003 57         132 $self->_add_matrix_metadata;
1004              
1005             # first token: 'MATRIX', i.e. we're just starting to parse
1006             # the actual matrix. Here we need to switch to "linemode",
1007             # so that subsequently tokens will be array references (all
1008             # the tokens on a line). This is so that we can handle
1009             # interleaved matrices, which unfortunately need line breaks
1010             # in them.
1011 57 100 66     120 if ( not looks_like_instance( $token, 'ARRAY' ) and uc($token) eq 'MATRIX' )
    100 66        
    50 33        
1012             {
1013 9         20 $self->{'_linemode'} = 1;
1014 9         25 $self->_logger->info("starting matrix");
1015 9         14 return;
1016             }
1017              
1018             # a row inside the matrix, after adding tokens to row, nothing
1019             # else to do
1020             elsif ( looks_like_instance( $token, 'ARRAY' )
1021 90         263 and not grep { /^;$/ } @{$token} )
  48         93  
1022             {
1023 39         101 $self->_add_tokens_to_row($token);
1024 39         90 $self->_logger->info("adding tokens to row");
1025 39         69 return;
1026             }
1027              
1028             # the last row of the matrix, after adding tokens to row,
1029             # instantiate & populate datum objects, link against taxa
1030             # objects
1031             elsif ( looks_like_instance( $token, 'ARRAY' )
1032 9         45 and grep { /^;$/ } @{$token} )
  9         30  
1033             {
1034 9         54 $self->_add_tokens_to_row($token);
1035              
1036             # link to taxa
1037 9         18 for my $row ( @{ $self->{'_matrixrowlabels'} } ) {
  9         25  
1038              
1039             # create new datum
1040 39         100 my $datum = $self->_factory->create_datum(
1041             '-type_object' => $self->_current->get_type_object,
1042             '-name' => $row,
1043             );
1044             my $char =
1045 39         141 $self->_resolve_ambig( $datum, $self->{'_matrix'}->{$row} );
1046 39         121 $datum->set_char($char);
1047              
1048             # insert new datum in matrix
1049 39         86 $self->_current->insert($datum);
1050              
1051             # link to taxon
1052 39         105 $self->_resolve_taxon($datum);
1053 39         105 my ( $length, $seq ) = ( $datum->get_length, $datum->get_char );
1054 39         130 $self->_logger->info("parsed $length characters for ${row}: $seq");
1055             }
1056              
1057             # Let's avoid these!
1058 9 100 33     26 if ( $self->_current->get_nchar != $self->{'_nchar'} ) {
    50          
1059             my ( $obs, $exp ) =
1060 1         3 ( $self->_current->get_nchar, $self->{'_nchar'} );
1061 1         6 _bad_format("Observed and expected nchar mismatch: $obs vs. $exp");
1062             }
1063              
1064             # ntax is only defined for "data" blocks (which have ntax token),
1065             # not for "characters" blocks (which should match up with taxa block)
1066             elsif ( defined $self->{'_ntax'}
1067             and $self->_current->get_ntax != $self->{'_ntax'} )
1068             {
1069 0         0 my ( $obs, $exp ) = ( $self->_current->get_ntax, $self->{'_ntax'} );
1070 0         0 _bad_format("Observed and expected ntax mismatch: $obs vs. $exp");
1071             }
1072              
1073             # XXX matrix clean up here
1074 8         18 $self->{'_ntax'} = undef;
1075 8         16 $self->{'_nchar'} = undef;
1076 8         16 $self->{'_matrixtype'} = undef;
1077 8         40 $self->{'_matrix'} = {};
1078 8         19 $self->{'_matrixrowlabels'} = [];
1079 8         19 $self->{'_linemode'} = 0;
1080             }
1081             }
1082              
1083             sub _bad_format {
1084 2     2   11 throw 'BadFormat' => shift;
1085             }
1086 408     408   1265 sub _current { shift->{'_context'}->[-1] }
1087              
1088             sub _trees {
1089 5     5   10 my $self = shift;
1090 5 50       23 if ( $self->{'_begin'} ) {
1091 5         10 $self->{'_begin'} = 0;
1092 5         11 $self->{'_trees'} = '';
1093 5         10 $self->{'_treenames'} = [];
1094 5         9 push @{ $self->{'_context'} }, $self->_factory->create_forest;
  5         18  
1095 5         19 $self->_logger->info("starting trees block");
1096             }
1097             }
1098              
1099             sub _translate {
1100 171     171   207 my $self = shift;
1101 171         228 my $i = $self->{'_i'};
1102 171 100 100     343 if ( $i && $i == 1 )
1103             { # actually, $i can be 0 according to BayesPhylogenies translation table
1104 5         18 $self->_logger->info("starting translation table");
1105             }
1106 171 100 100     781 if ( !defined($i) && $_[0] =~ m/^\d+$/ ) {
    100 66        
      33        
      33        
1107 57         94 $self->{'_i'} = shift;
1108 57         122 $self->{'_translate'}->[ $self->{'_i'} ] = undef;
1109             }
1110             elsif (defined($i)
1111             && exists $self->{'_translate'}->[$i]
1112             && !defined $self->{'_translate'}->[$i]
1113             && $_[0] ne ';' )
1114             {
1115 57         92 $self->{'_translate'}->[$i] = $_[0];
1116 57         118 $self->_logger->debug("Translation: $i => $_[0]");
1117 57         99 $self->{'_i'} = undef;
1118             }
1119             }
1120              
1121             sub _tree {
1122 212     212   240 my $self = shift;
1123 212 100 100     411 if ( not $self->{'_treename'} and $_[0] !~ m/^(U?TREE|\*)$/i ) {
1124 13         22 $self->{'_treename'} = $_[0];
1125             }
1126 212 100 66     359 if ( $_[0] eq '=' and not $self->{'_treestart'} ) {
1127 13         21 $self->{'_treestart'} = 1;
1128             }
1129 212 100 100     487 if ( $_[0] ne '=' and $self->{'_treestart'} ) {
1130 173         244 $self->{'_tree'} .= $_[0];
1131             }
1132              
1133             # tr/// returns # of replacements, hence can be used to check
1134             # tree description is balanced
1135 212 100 100     842 if ( $self->{'_treestart'}
      100        
1136             and $self->{'_tree'}
1137             and $self->{'_tree'} =~ tr/(/(/ == $self->{'_tree'} =~ tr/)/)/ )
1138             {
1139 13         18 my $translated = $self->{'_tree'};
1140 13         20 my $translate = $self->{'_translate'};
1141 13 100       27 my $start =
1142             exists $translate->[0]
1143             ? 0
1144             : 1; # BayesPhylogenies starts translation table w. 0
1145 13         18 for my $i ( $start .. $#{$translate} ) {
  13         42  
1146 93         1998 $translated =~ s/(\(|,)$i(,|\)|:)/$1$translate->[$i]$2/;
1147             }
1148             my ( $logtreename, $logtree ) =
1149 13         45 ( $self->{'_treename'}, $self->{'_tree'} );
1150 13         38 $self->_logger->info("tree: $logtreename string: $logtree");
1151 13         37 $self->{'_trees'} .= $translated . ';';
1152 13         23 push @{ $self->{'_treenames'} }, $self->{'_treename'};
  13         28  
1153              
1154             # XXX tree cleanup here
1155 13         21 $self->{'_treestart'} = 0;
1156 13         14 $self->{'_tree'} = undef;
1157 13         30 $self->{'_treename'} = undef;
1158             }
1159             }
1160              
1161             sub _end {
1162 33     33   51 my $self = shift;
1163 33         72 $self->{'_translate'} = [];
1164 33 100 100     150 if ( uc $self->{'_previous'} eq ';' and $self->{'_trees'} ) {
1165 5         17 my $forest = $self->_current;
1166             my $trees = parse(
1167             '-format' => 'newick',
1168 5         36 '-string' => $self->{'_trees'},
1169             '-as_project' => 0
1170             );
1171 5         38 for my $tree ( @{ $trees->get_entities } ) {
  5         18  
1172 13         30 $forest->insert($tree);
1173             }
1174              
1175             # set tree names
1176 5         11 for my $i ( 0 .. $#{ $self->{'_treenames'} } ) {
  5         22  
1177 13         43 $forest->get_by_index($i)->set_name( $self->{'_treenames'}->[$i] );
1178             }
1179              
1180             # link tips to taxa
1181 5         10 for my $tree ( @{ $forest->get_entities } ) {
  5         15  
1182 13         20 for my $tip ( @{ $tree->get_terminals } ) {
  13         43  
1183 93         149 $self->_resolve_taxon($tip);
1184             }
1185             }
1186              
1187             # XXX trees cleanup here
1188 5         15 $self->{'_trees'} = '';
1189 5         42 $self->{'_treenames'} = [];
1190             }
1191             }
1192              
1193             sub _semicolon {
1194 189     189   245 my $self = shift;
1195 189 50       1093 if ( uc $self->{'_previous'} eq 'MATRIX' ) {
    100          
    100          
    100          
    100          
    100          
    50          
    50          
1196 0         0 $self->{'_matrixtype'} = undef;
1197 0         0 $self->{'_matrix'} = {};
1198 0         0 $self->{'_charlabels'} = [];
1199 0         0 $self->{'_statelabels'} = [];
1200 0         0 $self->{'_linemode'} = 0;
1201 0 0       0 if ( not $self->_current->get_ntax ) {
1202 0         0 my $taxon = {};
1203 0         0 foreach my $row ( @{ $self->_current->get_entities } ) {
  0         0  
1204 0         0 $taxon->{ $row->get_taxon }++;
1205             }
1206 0         0 my $ntax = scalar keys %{$taxon};
  0         0  
1207             }
1208             }
1209            
1210             # finalize character set
1211             elsif ( uc $self->{'_previous'} eq 'CHARSET' ) {
1212 2         9 my $matrix = $self->_find_last_seen_matrix( $self->{'_charset'}->{'matrix'} );
1213 2         8 my $characters = $matrix->get_characters;
1214 2         7 my $set = $self->_factory->create_set( '-name' => $self->{'_charset'}->{'name'} );
1215 2         15 $characters->add_set($set);
1216 2         4 my $range = $self->{'_charset'}->{'range'};
1217 2         3 my @range;
1218 2 50       7 if ( ref($range) eq 'ARRAY' ) {
1219 2         4 while ( @{ $range } ) {
  6         12  
1220 4         7 my $index = shift @{ $range };
  4         6  
1221 4 100 66     20 if ( $range->[0] && $range->[0] eq '-' ) {
1222 2         4 shift @{ $range };
  2         2  
1223 2         4 my $end = shift @{ $range };
  2         4  
1224 2         8 push @range, ( $index - 1 ) .. ( $end - 1 );
1225             }
1226             else {
1227 2         5 push @range, ( $index - 1 );
1228             }
1229             }
1230 2         5 for my $i ( @range ) {
1231 6         19 my $character = $characters->get_by_index($i);
1232 6 50       12 if ( $character ) {
1233 6         15 $characters->add_to_set($character,$set);
1234             }
1235             else {
1236 0         0 throw 'API' => "No character at index $i";
1237             }
1238             }
1239             }
1240 2         8 $self->{'_charset'} = {};
1241             }
1242            
1243             # finalize character state labels
1244             elsif ( uc $self->{'_previous'} eq 'CHARSTATELABELS' ) {
1245 2         7 my $matrix = $self->_find_last_seen_matrix;
1246 2         5 my @labels = @{ $self->{'_charstatelabels'} };
  2         8  
1247 2 100       10 if ( $matrix->get_type =~ m/continuous/i ) {
1248 1         3 my @charlabels;
1249 1         2 my $charnum = 1;
1250 1         4 while (@labels) {
1251            
1252             # expecting an index at the beginning of the statement
1253 2         3 my $index = shift @labels;
1254 2 50       5 $index != $charnum && _bad_format( "Expecting character number $charnum, observed $index in CHARSTATELABELS" );
1255            
1256             # then the character label
1257 2         4 push @charlabels, shift @labels;
1258            
1259             # then a comma
1260 2 100       5 if ( @labels ) {
1261 1 50       3 $labels[0] eq ',' ? shift @labels : _bad_format( "Expecting , observed $labels[0] in CHARSTATELABELS" );
1262             }
1263 2         4 $charnum++;
1264             }
1265 1         5 $matrix->set_charlabels(\@charlabels);
1266 1         10 $self->{'_charstatelabels'} = [];
1267             }
1268             else {
1269 1         2 my ( @charlabels, @statelabels );
1270 1         2 my $charnum = 1;
1271 1         4 while (@labels) {
1272            
1273             # expecting an index at the beginning of the statement
1274 2         4 my $index = shift @labels;
1275 2 50       5 $index != $charnum && _bad_format( "Expecting character number $charnum, observed $index in CHARSTATELABELS" );
1276            
1277             # then the character label
1278 2         4 push @charlabels, shift @labels;
1279            
1280             # then a forward slash
1281 2         3 my $slash = shift @labels;
1282 2 50       8 $slash ne '/' && _bad_format( "Expecting /, observed $slash in CHARSTATELABELS" );
1283            
1284             # then a list of state labels
1285 2         3 my @stateset;
1286 2   100     13 push @stateset, shift @labels while(@labels and $labels[0] ne ',');
1287 2         3 push @statelabels, \@stateset;
1288            
1289             # then a comma
1290 2 100       5 if ( @labels ) {
1291 1 50       3 $labels[0] eq ',' ? shift @labels : _bad_format( "Expecting , observed $labels[0] in CHARSTATELABELS" );
1292             }
1293 2         5 $charnum++;
1294             }
1295 1         16 $matrix->set_charlabels(\@charlabels);
1296 1         4 $matrix->set_statelabels(\@statelabels);
1297 1         4 $self->{'_charstatelabels'} = [];
1298             }
1299             }
1300            
1301             # finalize taxon set
1302             elsif ( uc $self->{'_previous'} eq 'TAXSET' ) {
1303 2         6 my $taxa = $self->_find_last_seen_taxa_block( $self->{'_taxset'}->{'taxa'} );
1304 2         7 my $set = $self->_factory->create_set( '-name' => $self->{'_taxset'}->{'name'} );
1305 2         13 $taxa->add_set($set);
1306 2         3 my $range = $self->{'_taxset'}->{'range'};
1307 2         3 my @range;
1308 2         4 while ( @{ $range } ) {
  8         21  
1309 6         8 my $index = shift @{ $range };
  6         9  
1310 6 100 100     19 if ( $range->[0] && $range->[0] eq '-' ) {
1311 2         3 shift @{ $range };
  2         3  
1312 2         3 my $end = shift @{ $range };
  2         4  
1313 2         6 push @range, ( $index - 1 ) .. ( $end - 1 );
1314             }
1315             else {
1316 4         6 push @range, ( $index - 1 );
1317             }
1318             }
1319 2         5 for my $i ( @range ) {
1320 8         21 my $taxon = $taxa->get_by_index($i);
1321 8 50       13 if ( $taxon ) {
1322 8         17 $taxa->add_to_set($taxon,$set);
1323             }
1324             else {
1325 0         0 _bad_format( "No taxon at index $i" );
1326             }
1327             }
1328 2         8 $self->{'_taxset'} = {};
1329             }
1330            
1331             # finalize taxa labels
1332             elsif ( uc $self->{'_previous'} eq 'TAXLABELS' ) {
1333 13         24 foreach my $name ( @{ $self->{'_taxlabels'} } ) {
  13         28  
1334 59         136 my $taxon = $self->_factory->create_taxon( '-name' => $name );
1335 59         144 $self->_current->insert($taxon);
1336             }
1337 13 100       33 if ( $self->_current->get_ntax != $self->{'_ntax'} ) {
1338             _bad_format(
1339             sprintf(
1340             'Mismatch between observed and expected ntax: %d vs %d',
1341 1         3 $self->_current->get_ntax, $self->{'_ntax'}
1342             )
1343             );
1344             }
1345              
1346             # XXX taxa cleanup here
1347 12         24 $self->{'_ntax'} = undef;
1348 12         33 $self->{'_taxlabels'} = [];
1349             }
1350            
1351             # finalize symbols list
1352             elsif ( uc $self->{'_previous'} eq 'SYMBOLS' ) {
1353 5         11 my $logsymbols = join( ' ', @{ $self->{'_symbols'} } );
  5         15  
1354 5         18 $self->_logger->info("symbols: $logsymbols");
1355 5         14 $self->{'_symbols'} = [];
1356             }
1357            
1358             # finalize character labels
1359             elsif ( uc $self->{'_previous'} eq 'CHARLABELS' ) {
1360 0 0         if ( @{ $self->{'_charlabels'} } ) {
  0            
1361 0           my $logcharlabels = join( ' ', @{ $self->{'_charlabels'} } );
  0            
1362 0           $self->_logger->info("charlabels: $logcharlabels");
1363             }
1364             }
1365            
1366             # finalize state labels
1367             elsif ( uc $self->{'_previous'} eq 'STATELABELS' ) {
1368 0 0         if ( @{ $self->{'_statelabels'} } ) {
  0            
1369 0           my $logstatelabels = join( ' ', @{ $self->{'_statelabels'} } );
  0            
1370 0           $self->_logger->info("statelabels: $logstatelabels");
1371             }
1372             }
1373             }
1374              
1375             # podinherit_insert_token
1376              
1377             =head1 SEE ALSO
1378              
1379             There is a mailing list at L<https://groups.google.com/forum/#!forum/bio-phylo>
1380             for any user or developer questions and discussions.
1381              
1382             =over
1383              
1384             =item L<Bio::Phylo::IO>
1385              
1386             The nexus parser is called by the L<Bio::Phylo::IO> object. Look there for
1387             examples of file parsing and manipulation.
1388              
1389             =item L<Bio::Phylo::Manual>
1390              
1391             Also see the manual: L<Bio::Phylo::Manual> and L<http://rutgervos.blogspot.com>.
1392              
1393             =back
1394              
1395             =head1 CITATION
1396              
1397             If you use Bio::Phylo in published research, please cite it:
1398              
1399             B<Rutger A Vos>, B<Jason Caravas>, B<Klaas Hartmann>, B<Mark A Jensen>
1400             and B<Chase Miller>, 2011. Bio::Phylo - phyloinformatic analysis using Perl.
1401             I<BMC Bioinformatics> B<12>:63.
1402             L<http://dx.doi.org/10.1186/1471-2105-12-63>
1403              
1404             =cut
1405              
1406             1;