source: branches/profile/SL/TREE_WRITE/TreeWrite.cxx

Last change on this file was 12138, checked in by westram, 11 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.2 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : TreeWrite.cxx                                     //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include <TreeWrite.h>
12#include <arbdbt.h>
13#include <arb_strbuf.h>
14#include <xml.hxx>
15
16using namespace std;
17
18#define tree_assert(cond) arb_assert(cond)
19
20inline void replace_by_underscore(char *str, const char *toReplace) {
21    char *unwanted = strpbrk(str, toReplace);
22    while (unwanted) {
23        unwanted[0] = '_';
24        unwanted    = strpbrk(unwanted+1, toReplace);
25    }
26}
27
28inline bool isQuoteChar(char c) { return c == '"' || c == '\''; }
29inline bool whole_label_quoted(const char *label, size_t length) { return isQuoteChar(label[0]) && label[0] == label[length-1]; }
30
31static void export_tree_label(const char *label, FILE *out, TREE_node_quoting qmode) {
32    // writes a label into the Newick file
33    // label is quoted if necessary
34    // label may be an internal_node_label, a leaf_label or a root_label
35    tree_assert(label);
36
37    const char *disallowed_chars = " \t\'\"()[]:;,"; // '(' is first problem_char
38    const char *problem_chars    = disallowed_chars+4;
39    tree_assert(problem_chars[0] == '(');
40
41    bool need_quotes  = strpbrk(label, disallowed_chars) != NULL;
42    char used_quote   = 0;
43    bool force_quotes = (qmode & TREE_FORCE_QUOTES);
44
45    if (force_quotes || need_quotes) {
46        if      (qmode&TREE_SINGLE_QUOTES) used_quote = '\'';
47        else if (qmode&TREE_DOUBLE_QUOTES) used_quote = '\"';
48    }
49
50    char *fixed_label;
51    {
52        size_t label_length = strlen(label);
53        fixed_label         = GB_strduplen(label, label_length);
54
55        if (whole_label_quoted(fixed_label, label_length)) {
56            // if whole label is quoted -> remove quotes
57            memmove(fixed_label, fixed_label+1, label_length-2);
58            fixed_label[label_length-2] = 0;
59        }
60    }
61
62    if (used_quote) {
63        // replace all problematic characters if requested
64        bool force_replace = (qmode & TREE_FORCE_REPLACE);
65        if (force_replace) replace_by_underscore(fixed_label, problem_chars);
66
67        // replace used quote by an '_' if it appears inside label
68        char used_quote_as_string[] = { used_quote, 0 };
69        replace_by_underscore(fixed_label, used_quote_as_string);
70
71        if (!force_quotes) {
72            need_quotes = strpbrk(fixed_label, disallowed_chars) != NULL;
73            if (!need_quotes) used_quote = 0; // @@@ fails if both quote-types are used in one name
74        }
75    }
76    else {
77        // unquoted label - always replace all problematic characters by '_'
78        replace_by_underscore(fixed_label, disallowed_chars);
79    }
80
81    if (used_quote) fputc(used_quote, out);
82    fputs(fixed_label, out);
83    if (used_quote) fputc(used_quote, out);
84
85    free(fixed_label);
86}
87
88
89
90// documentation of the Newick Format is in ../SOURCE_TOOLS/docs/newick_doc.html
91
92inline void indentTo(int indent, FILE *out) {
93    for (int i = 0; i < indent; i++) {
94        putc(' ', out);
95        putc(' ', out);
96    }
97}
98
99static const char *export_tree_node_print(GBDATA *gb_main, FILE *out, GBT_TREE *tree, const char *tree_name,
100                                          bool pretty, int indent,
101                                          const TREE_node_text_gen *node_gen, bool save_branchlengths,
102                                          bool save_bootstraps, bool save_groupnames, TREE_node_quoting qmode)
103{
104    const char *error = 0;
105
106    if (pretty) indentTo(indent, out);
107
108    if (tree->is_leaf) {
109        const char *label;
110        if (node_gen) label = node_gen->gen(gb_main, tree->gb_node, NDS_OUTPUT_LEAFTEXT, tree, tree_name);
111        else          label = tree->name;
112
113        export_tree_label(label, out, qmode);
114    }
115    else {
116        if (pretty) fputs("(\n", out);
117        else        putc('(', out);
118
119        error = export_tree_node_print(gb_main, out, tree->leftson, tree_name, pretty, indent+1, node_gen, save_branchlengths, save_bootstraps, save_groupnames, qmode);
120        if (save_branchlengths) fprintf(out, ":%.5f", tree->leftlen);
121        fputs(",\n", out);
122
123        if (error) return error;
124
125        error = export_tree_node_print(gb_main, out, tree->rightson, tree_name, pretty, indent+1, node_gen, save_branchlengths, save_bootstraps, save_groupnames, qmode);
126        if (save_branchlengths) fprintf(out, ":%.5f", tree->rightlen);
127        fputc('\n', out);
128
129        if (pretty) indentTo(indent, out);
130        fputc(')', out);
131
132        char *bootstrap = 0;
133        if (save_bootstraps) {
134            double value;
135            switch (tree->parse_bootstrap(value)) {
136                case REMARK_BOOTSTRAP: bootstrap = GBS_global_string_copy("%i", int(value+0.5)); break;
137                case REMARK_OTHER:     bootstrap = strdup(tree->get_remark()); break;
138                case REMARK_NONE:      break;
139            }
140        }
141
142        const char *group = (tree->name && save_groupnames) ? tree->name : 0;
143        const char *label = 0;
144        if (group) {
145            if (bootstrap) label = GBS_global_string("%s:%s", bootstrap, group);
146            else           label = group;
147        }
148        else if (bootstrap) label = bootstrap;
149
150        if (label) export_tree_label(label, out, qmode);
151
152        free(bootstrap);
153    }
154
155    return error;
156}
157
158inline string buildNodeIdentifier(const string& parent_id, int& son_counter) {
159    ++son_counter;
160    if (parent_id.empty()) return GBS_global_string("n_%i", son_counter);
161    return GBS_global_string("%s.%i", parent_id.c_str(), son_counter);
162}
163
164static const char *export_tree_node_print_xml(GBDATA *gb_main, GBT_TREE *tree, double my_length, const char *tree_name,
165                                              const TREE_node_text_gen *node_gen, bool skip_folded, const string& parent_id, int& parent_son_counter) {
166    const char *error = 0;
167
168    if (tree->is_leaf) {
169        XML_Tag item_tag("ITEM");
170
171        item_tag.add_attribute("name", buildNodeIdentifier(parent_id, parent_son_counter));
172
173        item_tag.add_attribute("itemname",
174                               node_gen
175                               ? node_gen->gen(gb_main, tree->gb_node, NDS_OUTPUT_LEAFTEXT, tree, tree_name)
176                               : tree->name);
177
178        item_tag.add_attribute("length", GBS_global_string("%.5f", my_length));
179    }
180    else {
181        char *bootstrap = 0;
182        {
183            double value;
184            switch (tree->parse_bootstrap(value)) {
185                case REMARK_BOOTSTRAP: bootstrap = GBS_global_string_copy("%i", int(value+0.5)); break;
186                case REMARK_OTHER:     break; // @@@ other branch-remarks are currently not saved into xml format
187                case REMARK_NONE:      break;
188            }
189        }
190
191        bool  folded    = false;
192        char *groupname = 0;
193        if (tree->name) {
194            const char *buf;
195
196            if (node_gen) buf = node_gen->gen(gb_main, tree->gb_node, NDS_OUTPUT_LEAFTEXT, tree, tree_name);
197            else          buf = tree->name;
198
199            tree_assert(buf);
200            groupname = strdup(buf);
201
202            GBDATA *gb_grouped = GB_entry(tree->gb_node, "grouped");
203            if (gb_grouped) {
204                folded = GB_read_byte(gb_grouped);
205            }
206        }
207
208        if (my_length || bootstrap || groupname) {
209            bool hide_this_group = skip_folded && folded; // hide folded groups only if skip_folded is true
210
211            XML_Tag branch_tag(hide_this_group ? "FOLDED_GROUP" : "BRANCH");
212            string  my_id = buildNodeIdentifier(parent_id, parent_son_counter);
213
214            branch_tag.add_attribute("name", my_id);
215
216            if (my_length) {
217                branch_tag.add_attribute("length", GBS_global_string("%.5f", my_length));
218            }
219            if (bootstrap) {
220                branch_tag.add_attribute("bootstrap", bootstrap);
221                freenull(bootstrap);
222            }
223            if (groupname) {
224                branch_tag.add_attribute("groupname", groupname);
225                freenull(groupname);
226                if (folded) branch_tag.add_attribute("folded", "1");
227            }
228            else {
229                tree_assert(!folded);
230            }
231
232            if (hide_this_group) {
233                branch_tag.add_attribute("items_in_group", GBT_count_leafs(tree));
234            }
235            else {
236                int my_son_counter = 0;
237                if (!error) error  = export_tree_node_print_xml(gb_main, tree->leftson, tree->leftlen, tree_name, node_gen, skip_folded, my_id, my_son_counter);
238                if (!error) error  = export_tree_node_print_xml(gb_main, tree->rightson, tree->rightlen, tree_name, node_gen, skip_folded, my_id, my_son_counter);
239            }
240        }
241        else {
242            if (!error) error = export_tree_node_print_xml(gb_main, tree->leftson, tree->leftlen, tree_name, node_gen, skip_folded, parent_id, parent_son_counter);
243            if (!error) error = export_tree_node_print_xml(gb_main, tree->rightson, tree->rightlen, tree_name, node_gen, skip_folded, parent_id, parent_son_counter);
244        }
245    }
246
247    return error;
248}
249
250GB_ERROR TREE_write_XML(GBDATA *gb_main, const char *db_name, const char *tree_name, const TREE_node_text_gen *node_gen, bool skip_folded, const char *path) {
251    GB_ERROR  error  = 0;
252    FILE     *output = fopen(path, "w");
253
254    if (!output) error = GB_export_errorf("file '%s' could not be opened for writing", path);
255    else {
256        GB_transaction ta(gb_main);
257
258        GBT_TREE *tree   = GBT_read_tree(gb_main, tree_name, GBT_TREE_NodeFactory());
259        if (!tree) error = GB_await_error();
260        else {
261            error = GBT_link_tree(tree, gb_main, true, 0, 0);
262            if (!error && node_gen) node_gen->init(gb_main);
263
264            if (!error) {
265                GBDATA *tree_cont   = GBT_find_tree(gb_main, tree_name);
266                GBDATA *tree_remark = GB_entry(tree_cont, "remark");
267
268                XML_Document xml_doc("ARB_TREE", "arb_tree.dtd", output);
269
270                xml_doc.add_attribute("database", db_name);
271                xml_doc.add_attribute("treename", tree_name);
272                xml_doc.add_attribute("export_date", GB_date_string());
273
274                if (tree_remark) {
275                    char *remark = GB_read_string(tree_remark);
276                    XML_Tag  remark_tag("COMMENT");
277                    XML_Text remark_text(remark);
278                    free(remark);
279                }
280
281                int my_son_counter = 0;
282                error              = export_tree_node_print_xml(gb_main, tree, 0.0, tree_name, node_gen, skip_folded, "", my_son_counter);
283            }
284        }
285        fclose(output);
286    }
287
288    return error;
289}
290
291static char *complete_newick_comment(const char *comment) {
292    // ensure that all '[' in 'comment' are closed by corresponding ']' by inserting additional brackets
293
294    int            openBrackets = 0;
295    GBS_strstruct *out          = GBS_stropen(strlen(comment)*1.1);
296
297    for (int o = 0; comment[o]; ++o) {
298        switch (comment[o]) {
299            case '[':
300                openBrackets++;
301                break;
302            case ']':
303                if (openBrackets == 0) {
304                    GBS_chrcat(out, '[');           // insert one
305                }
306                else {
307                    openBrackets--;
308                }
309                break;
310
311            default:
312                break;
313        }
314        GBS_chrcat(out, comment[o]);
315    }
316
317    while (openBrackets>0) {
318        GBS_chrcat(out, ']');                       // insert one
319        openBrackets--;
320    }
321
322    tree_assert(openBrackets == 0);
323
324    return GBS_strclose(out);
325}
326
327GB_ERROR TREE_write_Newick(GBDATA *gb_main, const char *tree_name, const TREE_node_text_gen *node_gen, bool save_branchlengths, bool save_bootstraps, bool save_groupnames, bool pretty, TREE_node_quoting quoteMode, const char *path)
328{
329    GB_ERROR  error  = 0;
330    FILE     *output = fopen(path, "w");
331
332    if (!output) error = GBS_global_string("file '%s' could not be opened for writing", path);
333    else {
334        GB_transaction ta(gb_main);
335
336        GBT_TREE *tree   = GBT_read_tree(gb_main, tree_name, GBT_TREE_NodeFactory());
337        if (!tree) error = GB_await_error();
338        else {
339            error = GBT_link_tree(tree, gb_main, true, 0, 0);
340            if (!error && node_gen) node_gen->init(gb_main);
341
342            if (!error) {
343                char   *remark      = 0;
344                GBDATA *tree_cont   = GBT_find_tree(gb_main, tree_name);
345                GBDATA *tree_remark = GB_entry(tree_cont, "remark");
346
347                if (tree_remark) {
348                    remark = GB_read_string(tree_remark);
349                }
350                {
351                    const char *saved_to = GBS_global_string("%s saved to %s", tree_name, path);
352                    freeset(remark, GBS_log_dated_action_to(remark, saved_to));
353                }
354
355                if (remark) {
356                    char *wellformed = complete_newick_comment(remark);
357
358                    tree_assert(wellformed);
359
360                    fputc('[', output); fputs(wellformed, output); fputs("]\n", output);
361                    free(wellformed);
362                }
363                free(remark);
364                if (!error) {
365                    error = export_tree_node_print(gb_main, output, tree, tree_name, pretty, 0, node_gen,  save_branchlengths,  save_bootstraps,  save_groupnames, quoteMode);
366                }
367            }
368
369            delete tree;
370        }
371
372        fprintf(output, ";\n");
373        fclose(output);
374    }
375
376    return error;
377}
378
379// --------------------------------------------------------------------------------
380
381static void export_tree_node_print_remove(char *str) {
382    int i = 0;
383    while (char c = str[i]) {
384        if (c == '\'' || c == '\"') str[i] = '.';
385        i++;
386    }
387}
388
389static void export_tree_rek(GBT_TREE *tree, FILE *out, bool export_branchlens, bool dquot) {
390    if (tree->is_leaf) {
391        export_tree_node_print_remove(tree->name);
392        fprintf(out,
393                dquot ? " \"%s\" " : " '%s' ",
394                tree->name);
395    }
396    else {
397        fputc('(', out);
398        export_tree_rek(tree->leftson,  out, export_branchlens, dquot); if (export_branchlens) fprintf(out, ":%.5f,", tree->leftlen);
399        export_tree_rek(tree->rightson, out, export_branchlens, dquot); if (export_branchlens) fprintf(out, ":%.5f",  tree->rightlen);
400        fputc(')', out);
401
402        if (tree->name) {
403            export_tree_node_print_remove(tree->name);
404            fprintf(out,
405                    dquot ? "\"%s\"" : "'%s'",
406                    tree->name);
407        }
408    }
409}
410
411#if defined(WARN_TODO)
412#warning maybe replace TREE_export_tree by TREE_write_Newick
413// need some additional parameters (no comment, trifurcation)
414#endif
415
416GB_ERROR TREE_export_tree(GBDATA *, FILE *out, GBT_TREE *tree, bool triple_root, bool export_branchlens, bool dquot) {
417    if (triple_root) {
418        GBT_TREE *one, *two, *three;
419        if (tree->is_leaf) {
420            return GB_export_error("Tree is two small, minimum 3 nodes");
421        }
422        if (tree->leftson->is_leaf && tree->rightson->is_leaf) {
423            return GB_export_error("Tree is two small, minimum 3 nodes");
424        }
425        if (tree->leftson->is_leaf) {
426            one = tree->leftson;
427            two = tree->rightson->leftson;
428            three = tree->rightson->rightson;
429        }
430        else {
431            one = tree->leftson->leftson;
432            two = tree->leftson->rightson;
433            three = tree->rightson;
434        }
435        fputc('(', out);
436        export_tree_rek(one,   out, export_branchlens, dquot); if (export_branchlens) fprintf(out, ":%.5f", 1.0); fputc(',', out);
437        export_tree_rek(two,   out, export_branchlens, dquot); if (export_branchlens) fprintf(out, ":%.5f", 1.0); fputc(',', out);
438        export_tree_rek(three, out, export_branchlens, dquot); if (export_branchlens) fprintf(out, ":%.5f", 1.0);
439        fputc(')', out);
440    }
441    else {
442        export_tree_rek(tree, out, export_branchlens, dquot);
443    }
444    return 0;
445}
Note: See TracBrowser for help on using the repository browser.