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

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