inprogress
[jalview.git] / forester / java / src / org / forester / io / parsers / nexus / NexusPhylogeniesParser.java
1 // $Id:
2 // FORESTER -- software libraries and applications
3 // for evolutionary biology research and applications.
4 //
5 // Copyright (C) 2008-2009 Christian M. Zmasek
6 // Copyright (C) 2008-2009 Burnham Institute for Medical Research
7 // All rights reserved
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public
11 // License as published by the Free Software Foundation; either
12 // version 2.1 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 // Lesser General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 //
23 // Contact: phylosoft @ gmail . com
24 // WWW: https://sites.google.com/site/cmzmasek/home/software/forester
25
26 package org.forester.io.parsers.nexus;
27
28 import java.io.BufferedReader;
29 import java.io.FileNotFoundException;
30 import java.io.IOException;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37
38 import org.forester.archaeopteryx.Constants;
39 import org.forester.io.parsers.IteratingPhylogenyParser;
40 import org.forester.io.parsers.PhylogenyParser;
41 import org.forester.io.parsers.nhx.NHXFormatException;
42 import org.forester.io.parsers.nhx.NHXParser;
43 import org.forester.io.parsers.nhx.NHXParser.TAXONOMY_EXTRACTION;
44 import org.forester.io.parsers.util.ParserUtils;
45 import org.forester.io.parsers.util.PhylogenyParserException;
46 import org.forester.phylogeny.Phylogeny;
47 import org.forester.phylogeny.PhylogenyNode;
48 import org.forester.phylogeny.iterators.PhylogenyNodeIterator;
49 import org.forester.util.ForesterUtil;
50
51 public final class NexusPhylogeniesParser implements IteratingPhylogenyParser, PhylogenyParser {
52
53     final private static String  begin_trees               = NexusConstants.BEGIN_TREES.toLowerCase();
54     final private static String  end                       = NexusConstants.END.toLowerCase();
55     final private static String  endblock                  = "endblock";
56     final private static Pattern ROOTEDNESS_PATTERN        = Pattern.compile( ".+=\\s*\\[&([R|U])\\].*" );
57     final private static String  taxlabels                 = NexusConstants.TAXLABELS.toLowerCase();
58     final private static Pattern TITLE_PATTERN             = Pattern.compile( "TITLE.?\\s+([^;]+)",
59                                                                               Pattern.CASE_INSENSITIVE );
60     final private static String  translate                 = NexusConstants.TRANSLATE.toLowerCase();
61     final private static String  tree                      = NexusConstants.TREE.toLowerCase();
62     final private static Pattern TREE_NAME_PATTERN         = Pattern.compile( "\\s*.?Tree\\s+(.+?)\\s*=.+",
63                                                                               Pattern.CASE_INSENSITIVE );
64     final private static String  utree                     = NexusConstants.UTREE.toLowerCase();
65     private BufferedReader       _br;
66     private boolean              _ignore_quotes_in_nh_data = Constants.NH_PARSING_IGNORE_QUOTES_DEFAULT;
67     private boolean              _in_taxalabels;
68     private boolean              _in_translate;
69     private boolean              _in_tree;
70     private boolean              _in_trees_block;
71     private boolean              _is_rooted;
72     private String               _name;
73     private Phylogeny            _next;
74     private Object               _nexus_source;
75     private StringBuilder        _nh;
76     private boolean              _replace_underscores      = NHXParser.REPLACE_UNDERSCORES_DEFAULT;
77     private boolean              _rooted_info_present;
78     private List<String>         _taxlabels;
79     private TAXONOMY_EXTRACTION  _taxonomy_extraction      = TAXONOMY_EXTRACTION.NO;
80     private String               _title;
81     private Map<String, String>  _translate_map;
82     private StringBuilder        _translate_sb;
83
84     @Override
85     public String getName() {
86         return "Nexus Phylogenies Parser";
87     }
88
89     @Override
90     public final boolean hasNext() {
91         return _next != null;
92     }
93
94     @Override
95     public final Phylogeny next() throws NHXFormatException, IOException {
96         final Phylogeny phy = _next;
97         getNext();
98         return phy;
99     }
100
101     @Override
102     public final Phylogeny[] parse() throws IOException {
103         final List<Phylogeny> l = new ArrayList<Phylogeny>();
104         while ( hasNext() ) {
105             l.add( next() );
106         }
107         final Phylogeny[] p = new Phylogeny[ l.size() ];
108         for( int i = 0; i < l.size(); ++i ) {
109             p[ i ] = l.get( i );
110         }
111         reset();
112         return p;
113     }
114
115     @Override
116     public final void reset() throws FileNotFoundException, IOException {
117         _taxlabels = new ArrayList<String>();
118         _translate_map = new HashMap<String, String>();
119         _nh = new StringBuilder();
120         _name = "";
121         _title = "";
122         _translate_sb = null;
123         _next = null;
124         _in_trees_block = false;
125         _in_taxalabels = false;
126         _in_translate = false;
127         _in_tree = false;
128         _rooted_info_present = false;
129         _is_rooted = false;
130         _br = ParserUtils.createReader( _nexus_source );
131         getNext();
132     }
133
134     public final void setIgnoreQuotes( final boolean ignore_quotes_in_nh_data ) {
135         _ignore_quotes_in_nh_data = ignore_quotes_in_nh_data;
136     }
137
138     public final void setReplaceUnderscores( final boolean replace_underscores ) {
139         _replace_underscores = replace_underscores;
140     }
141
142     @Override
143     public final void setSource( final Object nexus_source ) throws PhylogenyParserException, IOException {
144         if ( nexus_source == null ) {
145             throw new PhylogenyParserException( "attempt to parse null object" );
146         }
147         _nexus_source = nexus_source;
148         reset();
149     }
150
151     public final void setTaxonomyExtraction( final TAXONOMY_EXTRACTION taxonomy_extraction ) {
152         _taxonomy_extraction = taxonomy_extraction;
153     }
154
155     private final void createPhylogeny( final String title,
156                                         final String name,
157                                         final StringBuilder nhx,
158                                         final boolean rooted_info_present,
159                                         final boolean is_rooted ) throws IOException {
160         _next = null;
161         final NHXParser pars = new NHXParser();
162         pars.setTaxonomyExtraction( _taxonomy_extraction );
163         pars.setReplaceUnderscores( _replace_underscores );
164         pars.setIgnoreQuotes( _ignore_quotes_in_nh_data );
165         if ( rooted_info_present ) {
166             pars.setGuessRootedness( false );
167         }
168         pars.setSource( nhx );
169         final Phylogeny p = pars.next();
170         if ( p == null ) {
171             throw new PhylogenyParserException( "failed to create phylogeny" );
172         }
173         String myname = null;
174         if ( !ForesterUtil.isEmpty( title ) && !ForesterUtil.isEmpty( name ) ) {
175             myname = title.replace( '_', ' ' ).trim() + " (" + name.trim() + ")";
176         }
177         else if ( !ForesterUtil.isEmpty( title ) ) {
178             myname = title.replace( '_', ' ' ).trim();
179         }
180         else if ( !ForesterUtil.isEmpty( name ) ) {
181             myname = name.trim();
182         }
183         if ( !ForesterUtil.isEmpty( myname ) ) {
184             p.setName( myname );
185         }
186         if ( rooted_info_present ) {
187             p.setRooted( is_rooted );
188         }
189         if ( ( _taxlabels.size() > 0 ) || ( _translate_map.size() > 0 ) ) {
190             final PhylogenyNodeIterator it = p.iteratorExternalForward();
191             while ( it.hasNext() ) {
192                 final PhylogenyNode node = it.next();
193                 if ( ( _translate_map.size() > 0 ) && _translate_map.containsKey( node.getName() ) ) {
194                     node.setName( _translate_map.get( node.getName() ).replaceAll( "['\"]+", "" ) );
195                 }
196                 else if ( _taxlabels.size() > 0 ) {
197                     int i = -1;
198                     try {
199                         i = Integer.parseInt( node.getName() );
200                     }
201                     catch ( final NumberFormatException e ) {
202                         // Ignore.
203                     }
204                     if ( i > 0 ) {
205                         node.setName( _taxlabels.get( i - 1 ).replaceAll( "['\"]+", "" ) );
206                     }
207                 }
208                 if ( !_replace_underscores && ( ( _taxonomy_extraction != TAXONOMY_EXTRACTION.NO ) ) ) {
209                     ParserUtils.extractTaxonomyDataFromNodeName( node, _taxonomy_extraction );
210                 }
211                 else if ( _replace_underscores ) {
212                     if ( !ForesterUtil.isEmpty( node.getName() ) ) {
213                         node.setName( node.getName().replace( '_', ' ' ).trim() );
214                     }
215                 }
216             }
217         }
218         _next = p;
219     }
220
221     private final void getNext() throws IOException, NHXFormatException {
222         _next = null;
223         String line;
224         while ( ( line = _br.readLine() ) != null ) {
225             line = line.trim();
226             if ( ( line.length() > 0 ) && !line.startsWith( "#" ) && !line.startsWith( ">" ) ) {
227                 line = ForesterUtil.collapseWhiteSpace( line );
228                 line = removeWhiteSpaceBeforeSemicolon( line );
229                 final String line_lc = line.toLowerCase();
230                 if ( line_lc.startsWith( begin_trees ) ) {
231                     _in_trees_block = true;
232                     _in_taxalabels = false;
233                     _in_translate = false;
234                     _title = "";
235                 }
236                 else if ( line_lc.startsWith( taxlabels ) ) {
237                     _in_trees_block = false;
238                     _in_taxalabels = true;
239                     _in_translate = false;
240                 }
241                 else if ( line_lc.startsWith( translate ) ) {
242                     _translate_sb = new StringBuilder();
243                     _in_taxalabels = false;
244                     _in_translate = true;
245                 }
246                 else if ( _in_trees_block ) {
247                     if ( line_lc.startsWith( "title" ) ) {
248                         final Matcher title_m = TITLE_PATTERN.matcher( line );
249                         if ( title_m.lookingAt() ) {
250                             _title = title_m.group( 1 );
251                         }
252                     }
253                     else if ( line_lc.startsWith( "link" ) ) {
254                     }
255                     else if ( line_lc.startsWith( end ) || line_lc.startsWith( endblock ) ) {
256                         _in_trees_block = false;
257                         _in_tree = false;
258                         _in_translate = false;
259                         if ( _nh.length() > 0 ) {
260                             createPhylogeny( _title, _name, _nh, _rooted_info_present, _is_rooted );
261                             _nh = new StringBuilder();
262                             _name = "";
263                             _rooted_info_present = false;
264                             _is_rooted = false;
265                             if ( _next != null ) {
266                                 return;
267                             }
268                         }
269                     }
270                     else if ( line_lc.startsWith( tree ) || ( line_lc.startsWith( utree ) ) ) {
271                         boolean might = false;
272                         if ( _nh.length() > 0 ) {
273                             might = true;
274                             createPhylogeny( _title, _name, _nh, _rooted_info_present, _is_rooted );
275                             _nh = new StringBuilder();
276                             _name = "";
277                             _rooted_info_present = false;
278                             _is_rooted = false;
279                         }
280                         _in_tree = true;
281                         _nh.append( line.substring( line.indexOf( '=' ) ) );
282                         final Matcher name_matcher = TREE_NAME_PATTERN.matcher( line );
283                         if ( name_matcher.matches() ) {
284                             _name = name_matcher.group( 1 );
285                             _name = _name.replaceAll( "['\"]+", "" );
286                         }
287                         final Matcher rootedness_matcher = ROOTEDNESS_PATTERN.matcher( line );
288                         if ( rootedness_matcher.matches() ) {
289                             final String s = rootedness_matcher.group( 1 );
290                             line = line.replaceAll( "\\[\\&.\\]", "" );
291                             _rooted_info_present = true;
292                             if ( s.toUpperCase().equals( "R" ) ) {
293                                 _is_rooted = true;
294                             }
295                         }
296                         if ( might && ( _next != null ) ) {
297                             return;
298                         }
299                     }
300                     else if ( _in_tree && !_in_translate ) {
301                         _nh.append( line );
302                     }
303                     if ( !line_lc.startsWith( "title" ) && !line_lc.startsWith( "link" ) && !_in_translate
304                             && !line_lc.startsWith( end ) && !line_lc.startsWith( endblock ) && line_lc.endsWith( ";" ) ) {
305                         _in_tree = false;
306                         _in_translate = false;
307                         createPhylogeny( _title, _name, _nh, _rooted_info_present, _is_rooted );
308                         _nh = new StringBuilder();
309                         _name = "";
310                         _rooted_info_present = false;
311                         _is_rooted = false;
312                         if ( _next != null ) {
313                             return;
314                         }
315                     }
316                 }
317                 if ( _in_taxalabels ) {
318                     if ( line_lc.startsWith( end ) || line_lc.startsWith( endblock ) ) {
319                         _in_taxalabels = false;
320                     }
321                     else {
322                         final String[] labels = line.split( "\\s+" );
323                         for( String label : labels ) {
324                             if ( !label.toLowerCase().equals( taxlabels ) ) {
325                                 if ( label.endsWith( ";" ) ) {
326                                     _in_taxalabels = false;
327                                     label = label.substring( 0, label.length() - 1 );
328                                 }
329                                 if ( label.length() > 0 ) {
330                                     _taxlabels.add( label );
331                                 }
332                             }
333                         }
334                     }
335                 }
336                 if ( _in_translate ) {
337                     if ( line_lc.startsWith( end ) || line_lc.startsWith( endblock ) ) {
338                         _in_translate = false;
339                     }
340                     else {
341                         _translate_sb.append( " " );
342                         _translate_sb.append( line.trim() );
343                         if ( line.endsWith( ";" ) ) {
344                             _in_translate = false;
345                             setTranslateKeyValuePairs( _translate_sb );
346                         }
347                     }
348                 }
349             }
350         }
351         if ( _nh.length() > 0 ) {
352             createPhylogeny( _title, _name, _nh, _rooted_info_present, _is_rooted );
353             if ( _next != null ) {
354                 return;
355             }
356         }
357     }
358
359     private final void setTranslateKeyValuePairs( final StringBuilder translate_sb ) throws IOException {
360         String s = translate_sb.toString().trim();
361         if ( s.endsWith( ";" ) ) {
362             s = s.substring( 0, s.length() - 1 ).trim();
363         }
364         for( final String pair : s.split( "," ) ) {
365             final String[] kv = pair.trim().split( "\\s+" );
366             if ( ( kv.length < 2 ) || ( kv.length > 3 ) ) {
367                 throw new IOException( "ill-formatted translate values: " + pair );
368             }
369             if ( ( kv.length == 3 ) && !kv[ 0 ].toLowerCase().trim().equals( translate ) ) {
370                 throw new IOException( "ill-formatted translate values: " + pair );
371             }
372             String key = "";
373             String value = "";
374             if ( kv.length == 3 ) {
375                 key = kv[ 1 ];
376                 value = kv[ 2 ];
377             }
378             else {
379                 key = kv[ 0 ];
380                 value = kv[ 1 ];
381             }
382             if ( value.endsWith( ";" ) ) {
383                 value = value.substring( 0, value.length() - 1 );
384             }
385             _translate_map.put( key, value );
386         }
387     }
388
389     private final static String removeWhiteSpaceBeforeSemicolon( final String s ) {
390         return s.replaceAll( "\\s+;", ";" );
391     }
392 }