source: tags/arb-6.0-rc1/SL/NDS/nds.cxx

Last change on this file was 11817, checked in by epruesse, 10 years ago

don't crash if NDS cannot read a DB string (partial/hotfix for #481)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.2 KB
Line 
1// ================================================================ //
2//                                                                  //
3//   File      : AWT_nds.cxx                                        //
4//   Purpose   :                                                    //
5//                                                                  //
6//   Institute of Microbiology (Technical University Munich)        //
7//   http://www.arb-home.de/                                        //
8//                                                                  //
9// ================================================================ //
10
11#include "nds.h"
12#include <awt_config_manager.hxx>
13#include <awt_sel_boxes.hxx>
14
15#include <aw_awar.hxx>
16#include <aw_file.hxx>
17#include <aw_msg.hxx>
18#include <aw_root.hxx>
19#include <aw_select.hxx>
20#include <arbdbt.h>
21#include <items.h>
22#include <arb_msg_fwd.h>
23
24
25#define nds_assert(cond) arb_assert(cond)
26
27#define NDS_PER_PAGE 10         // number of NDS definitions on each config-page
28#define NDS_PAGES     6         // how many config-pages (each has NDS_PER_PAGE definitions)
29
30#define NDS_COUNT (NDS_PER_PAGE*NDS_PAGES)    // overall number of NDS definitions
31
32#define AWAR_NDS_PAGE "tmp/viewkeys/page"
33
34#if defined(DEBUG)
35#define NDS_STRING_SIZE 200
36#else
37#define NDS_STRING_SIZE 4000
38#endif // DEBUG
39
40struct NodeTextBuilder {
41    char  buf[NDS_STRING_SIZE]; // buffer used to generate output
42    char *bp;
43    int   space_left;
44
45    long count;
46    int  show_errors;           // how many errors to show
47
48    long  lengths[NDS_COUNT];   // length of generated string
49    char *dkeys[NDS_COUNT];     // database field name (may be empty)
50    bool  rek[NDS_COUNT];       // 1->key is hierarchical (e.g. 'ali_16s/data')
51    char *parsing[NDS_COUNT];   // ACI/SRT program
52    bool  at_group[NDS_COUNT];  // whether string shall appear at group NDS entries
53    bool  at_leaf[NDS_COUNT];   // whether string shall appear at leaf NDS entries
54
55    void init_buffer() {
56        bp         = buf;
57        space_left = NDS_STRING_SIZE-1;
58    }
59    char *get_buffer() {
60        bp[0] = 0;
61        return buf;
62    }
63
64    void insert_overflow_warning() {
65        nds_assert(space_left >= 0); // <0 means 'already warned'
66        while (space_left) {
67            *bp++ = ' ';
68            space_left--;
69        }
70
71        static const char *warning  = "..<truncated>";
72        static int         warn_len = 0;
73        if (!warn_len) warn_len = strlen(warning);
74
75        strcpy(buf+(NDS_STRING_SIZE-warn_len-1), warning);
76        space_left = -1;
77    }
78
79    void append(char c) {
80        if (space_left >= 1) {
81            *bp++ = c;
82            space_left--;
83        }
84        else if (!space_left) {
85            insert_overflow_warning();
86        }
87    }
88    void append(const char *str, int length = -1) {
89        nds_assert(str);
90        if (length == -1) length = strlen(str);
91        nds_assert(int(strlen(str)) == length);
92
93        if (space_left >= length) {
94            strcpy(bp, str);
95            bp         += length;
96            space_left -= length;
97        }
98        else { // space_left < length
99            if (space_left >= 0) {
100                if (space_left>0) {
101                    memcpy(bp, str, space_left);
102                    bp         += space_left;
103                    space_left  = 0;
104                }
105                insert_overflow_warning();
106            }
107        }
108    }
109
110    __ATTR__FORMAT(2) void appendf(const char *format, ...) { FORWARD_FORMATTED(append, format); }
111};
112static NodeTextBuilder *awt_nds_ms = 0;
113
114inline const char *viewkeyAwarName(int i, const char *name) {
115    nds_assert(i >= 0 && i < NDS_PER_PAGE);
116    return GBS_global_string("tmp/viewkeys/viewkey_%i/%s", i, name);
117}
118
119inline AW_awar *viewkeyAwar(AW_root *aw_root, AW_default awdef, int i, const char *name, bool string_awar) {
120    const char *awar_name = viewkeyAwarName(i, name);
121    AW_awar    *awar      = 0;
122    if (string_awar) awar = aw_root->awar_string(awar_name, "", awdef);
123    else        awar      = aw_root->awar_int(awar_name, 0, awdef);
124    return awar;
125}
126
127static void map_viewkey(AW_root *aw_root, AW_default awdef, int i, GBDATA *gb_viewkey) {
128    // maps one NDS key data to one line of the config window
129
130    GBDATA *gb_key_text = GB_entry(gb_viewkey, "key_text");
131    GBDATA *gb_pars     = GB_entry(gb_viewkey, "pars");
132    GBDATA *gb_len1     = GB_entry(gb_viewkey, "len1");
133    GBDATA *gb_group    = GB_entry(gb_viewkey, "group");
134    GBDATA *gb_leaf     = GB_entry(gb_viewkey, "leaf");
135
136    nds_assert(gb_key_text);
137    nds_assert(gb_pars);
138    nds_assert(gb_len1);
139    nds_assert(gb_group);
140    nds_assert(gb_leaf);
141
142    AW_awar *Awar;
143    Awar = viewkeyAwar(aw_root, awdef, i, "key_text", true); Awar->map(gb_key_text);
144    Awar = viewkeyAwar(aw_root, awdef, i, "pars",     true); Awar->map(gb_pars);
145    Awar = viewkeyAwar(aw_root, awdef, i, "len1",     false); Awar->map(gb_len1); Awar->set_minmax(0, 1000000);
146    Awar = viewkeyAwar(aw_root, awdef, i, "group",    false); Awar->map(gb_group);
147    Awar = viewkeyAwar(aw_root, awdef, i, "leaf",     false); Awar->map(gb_leaf);
148}
149
150static void map_viewkeys(AW_root *aw_root, /*AW_default*/ GBDATA *awdef, GBDATA *gb_main) {
151    // map visible viewkeys to internal db entries
152    static bool  initialized        = false;
153    AW_awar     *awar_selected_page = 0;
154
155    if (!initialized) {
156        awar_selected_page = aw_root->awar_int(AWAR_NDS_PAGE, 0);
157        awar_selected_page->add_callback(makeRootCallback(map_viewkeys, awdef, gb_main)); // bind to self
158        initialized = true;
159    }
160    else {
161        awar_selected_page = aw_root->awar(AWAR_NDS_PAGE);
162    }
163
164    int page = awar_selected_page->read_int();
165#if defined(DEBUG)
166    printf("map_viewkeys to page %i\n", page);
167#endif // DEBUG
168    if (page<NDS_PAGES) {
169        GB_transaction ta(gb_main);
170
171        GBDATA *gb_arb_presets = GB_search(gb_main, "arb_presets", GB_CREATE_CONTAINER);
172        GBDATA *gb_viewkey     = 0;
173
174        int i1 = page*NDS_PER_PAGE;
175        int i2 = i1+NDS_PER_PAGE-1;
176
177        for (int i = 0; i <= i2; i++) {
178            gb_viewkey = !gb_viewkey ? GB_entry(gb_arb_presets, "viewkey") : GB_nextEntry(gb_viewkey);
179            nds_assert(i<NDS_COUNT);
180            nds_assert(gb_viewkey);
181            if (i >= i1) map_viewkey(aw_root, awdef, i-i1, gb_viewkey);
182        }
183    }
184}
185
186void create_nds_vars(AW_root *aw_root, AW_default awdef, GBDATA *gb_main) {
187    GB_push_transaction(gb_main);
188
189    GBDATA *gb_viewkey     = 0;
190    GBDATA *gb_arb_presets = GB_search(gb_main, "arb_presets", GB_CREATE_CONTAINER);
191
192    for (int i = 0; i<NDS_COUNT; ++i) {
193        gb_viewkey = !gb_viewkey ? GB_entry(gb_arb_presets, "viewkey") : GB_nextEntry(gb_viewkey);
194
195        if (!gb_viewkey) gb_viewkey = GB_create_container(gb_arb_presets, "viewkey");
196
197        {
198            int  group             = 0;
199            int  leaf              = 0;
200            bool was_group_name    = false;
201            int  default_len       = 30;
202
203            GBDATA *gb_key_text = GB_entry(gb_viewkey, "key_text");
204            if (!gb_key_text) {
205                gb_key_text        = GB_create(gb_viewkey, "key_text", GB_STRING);
206                const char *wanted = "";
207                switch (i) {
208                    case 0: wanted = "name"; default_len = 12; leaf = 1; break;
209                    case 1: wanted = "full_name"; leaf = 1; break;
210                    case 2: wanted = ""; was_group_name = true; break;
211                    case 3: wanted = "acc"; default_len = 20; leaf = 1; break;
212                    case 4: wanted = "date"; break;
213                }
214                GB_write_string(gb_key_text, wanted);
215            }
216
217            if (strcmp(GB_read_char_pntr(gb_key_text), "group_name") == 0) {
218                GB_write_string(gb_key_text, "");
219                was_group_name = true; // means: change group/leaf + add 'taxonomy(1)' to ACI
220            }
221
222            GB_searchOrCreate_int(gb_viewkey, "len1", default_len);
223            GBDATA *gb_pars = GB_searchOrCreate_string(gb_viewkey, "pars", "");
224
225            if (was_group_name) {
226                group = 1;
227                leaf  = 0;
228
229                const char *pars = GB_read_char_pntr(gb_pars);
230
231                if (pars[0] == 0) pars        = "taxonomy(1)"; // empty ACI/SRT
232                else if (pars[0] == ':') pars = GBS_global_string("taxonomy(1)|%s", pars); // was an SRT -> unsure what to do
233                else if (pars[0] == '|') pars = GBS_global_string("taxonomy(1)%s", pars); // was an ACI -> prefix taxonomy
234                else pars                     = GBS_global_string("taxonomy(1)|%s", pars); // other ACIs -> same
235
236                GB_write_string(gb_pars, pars);
237            }
238
239            {
240                GBDATA *gb_flag1 = GB_entry(gb_viewkey, "flag1");
241                if (gb_flag1) {
242                    if (GB_read_int(gb_flag1)) { // obsolete
243                        leaf = 1;
244                    }
245                    GB_ERROR error = GB_delete(gb_flag1);
246                    if (error) aw_message(error);
247                }
248            }
249
250            {
251                GBDATA *gb_inherit = GB_entry(gb_viewkey, "inherit");
252                if (gb_inherit) { // 'inherit' is old NDS style -> convert & delete
253                    if (was_group_name && GB_read_int(gb_inherit)) leaf = 1;
254                    GB_ERROR error = GB_delete(gb_inherit);
255                    if (error) aw_message(error);
256                }
257            }
258
259            GB_searchOrCreate_int(gb_viewkey, "group", group);
260            GB_searchOrCreate_int(gb_viewkey, "leaf", leaf);
261        }
262    }
263
264    aw_root->awar_string(AWAR_KEY_SELECT, "", awdef);
265    GB_pop_transaction(gb_main);
266
267    map_viewkeys(aw_root, awdef, gb_main); // call once
268}
269
270static void awt_pre_to_view(AW_root *aw_root) {
271    char *str = aw_root->awar(AWAR_SELECT_ACISRT_PRE)->read_string();
272    char *brk = strchr(str, '#');
273    if (brk) {
274        *(brk++) = 0;
275        aw_root->awar(AWAR_SELECT_ACISRT)->write_string(brk);
276    }
277    else {
278        aw_root->awar(AWAR_SELECT_ACISRT)->write_string(str);
279    }
280    free(str);
281}
282void AWT_popup_select_srtaci_window(AW_window *aww, const char *acisrt_awarname) {
283    static AW_window *win = 0;
284
285    if (!win) {
286        AW_root *aw_root = aww->get_root();
287        aw_root->awar_string(AWAR_SELECT_ACISRT);
288        aw_root->awar_string(AWAR_SELECT_ACISRT_PRE);
289        aw_root->awar(AWAR_SELECT_ACISRT)->map(acisrt_awarname);
290
291        AW_window_simple *aws = new AW_window_simple;
292        aws->init(aw_root, "SRT_ACI_SELECT", "SRT_ACI_SELECT");
293        aws->load_xfig("awt/srt_select.fig");
294        aws->button_length(13);
295
296        aws->callback(AW_POPDOWN);
297        aws->at("close");
298        aws->create_button("CLOSE", "CLOSE", "C");
299
300        aws->callback(makeHelpCallback("acisrt.hlp"));
301        aws->at("help");
302        aws->create_button("HELP", "HELP", "H");
303
304        aws->at("box");
305        AW_selection_list *programs = aws->create_selection_list(AWAR_SELECT_ACISRT_PRE);
306        GB_ERROR error;
307        {
308            StorableSelectionList storable_sellist(TypedSelectionList("sellst", programs, "SRT/ACI scripts", "srt_aci"));
309            error = storable_sellist.load(GB_path_in_ARBLIB("sellists/srt_aci*.sellst"), false);
310        }
311        if (error) aw_message(error);
312
313        aws->at("field");
314        aws->create_text_field(AWAR_SELECT_ACISRT);
315
316        aw_root->awar(AWAR_SELECT_ACISRT_PRE)->add_callback(awt_pre_to_view);
317        awt_pre_to_view(aw_root);
318        win =  (AW_window*)aws;
319    }
320    win->activate();
321}
322
323static void nds_init_config(AWT_config_definition& cdef) {
324    for (int i = 0; i<NDS_PER_PAGE; ++i) {
325        cdef.add(viewkeyAwarName(i, "leaf"), "leaf", i);
326        cdef.add(viewkeyAwarName(i, "group"), "group", i);
327        cdef.add(viewkeyAwarName(i, "key_text"), "key_text", i);
328        cdef.add(viewkeyAwarName(i, "len1"), "len1", i);
329        cdef.add(viewkeyAwarName(i, "pars"), "pars", i);
330    }
331}
332
333static char *nds_store_config(AW_window *aww, AW_CL, AW_CL) {
334    AWT_config_definition cdef(aww->get_root());
335    nds_init_config(cdef);
336    return cdef.read();
337}
338
339static void nds_restore_config(AW_window *aww, const char *stored, AW_CL, AW_CL) {
340    AWT_config_definition cdef(aww->get_root());
341    nds_init_config(cdef);
342
343    AWT_config parsedCfg(stored);
344    if (parsedCfg.has_entry("inherit0")) {
345        aw_message("Converting stored config to new NDS format -- consider saving it again.");
346        // Note: The conversion applied here is also done in create_nds_vars()
347
348        GB_ERROR error = 0;
349
350        for (int i = 0; !error && i<NDS_COUNT; ++i) {
351            bool was_group_name = false;
352            {
353                const char *key_text_key = GBS_global_string("key_text%i", i);
354                const char *key_text     = parsedCfg.get_entry(key_text_key);
355                if (strcmp(key_text, "group_name") == 0) {
356                    was_group_name = true;
357                    parsedCfg.set_entry(key_text_key, "");
358                }
359            }
360
361            bool leaf    = false;
362            bool group   = false;
363            int  inherit = 0;
364
365            {
366                const char *inherit_key   = GBS_global_string("inherit%i", i);
367                const char *inherit_value = parsedCfg.get_entry(inherit_key);
368
369                if (inherit_value) {
370                    inherit = atoi(inherit_value);
371                    parsedCfg.delete_entry(inherit_key);
372                }
373                else {
374                    error = GB_export_errorf("Expected entry '%s' in saved config", inherit_key);
375                }
376            }
377
378            if (was_group_name) {
379                if (!error) {
380                    leaf  = inherit;
381                    group = true;
382
383                    char       *aci_key = GBS_global_string_copy("pars%i", i);
384                    const char *aci     = parsedCfg.get_entry(aci_key);
385                    char       *new_aci = 0;
386
387                    if      (aci[0] == 0)   { new_aci = strdup("taxonomy(1)"); }
388                    else if (aci[0] == '|') { new_aci = GBS_global_string_copy("taxonomy(1)%s", aci); }
389                    else                    { new_aci = GBS_global_string_copy("taxonomy(1)|%s", aci); }
390
391                    parsedCfg.set_entry(aci_key, new_aci);
392
393                    free(new_aci);
394                    free(aci_key);
395                }
396            }
397            else {
398                leaf = true;
399            }
400
401            if (!error) {
402                const char *flag1_key   = GBS_global_string("active%i", i);
403                const char *flag1_value = parsedCfg.get_entry(flag1_key);
404                if (flag1_value) {
405                    int flag1 = atoi(flag1_value);
406                    if (flag1 == 0) { leaf = group = false; }
407                    parsedCfg.delete_entry(flag1_key);
408                }
409                else {
410                    error = GB_export_errorf("Expected entry '%s' in saved config", flag1_key);
411                }
412            }
413
414            if (!error) {
415                const char *leaf_key  = GBS_global_string("leaf%i", i);
416                parsedCfg.set_entry(leaf_key, GBS_global_string("%i", int(leaf)));
417                const char *group_key = GBS_global_string("group%i", i);
418                parsedCfg.set_entry(group_key, GBS_global_string("%i", int(group)));
419            }
420        }
421
422        if (!error) {
423            char *converted_cfg_str = parsedCfg.config_string();
424            cdef.write(converted_cfg_str);
425            free(converted_cfg_str);
426        }
427        else {
428            aw_message(error);
429        }
430    }
431    else {
432        cdef.write(stored);
433    }
434}
435
436AW_window *AWT_create_nds_window(AW_root *aw_root, GBDATA *gb_main) {
437    static AW_window_simple *aws = 0;
438    if (!aws) {
439        aws = new AW_window_simple;
440        aws->init(aw_root, "NDS_PROPS", "NDS");
441        aws->load_xfig("awt/nds.fig");
442        aws->auto_space(10, 5);
443
444        aws->callback(AW_POPDOWN);
445        aws->at("close");
446        aws->create_button("CLOSE", "CLOSE", "C");
447
448        aws->at("help");
449        aws->callback(makeHelpCallback("props_nds.hlp"));
450        aws->create_button("HELP", "HELP", "H");
451
452        aws->at("page");
453        aws->create_option_menu(AWAR_NDS_PAGE);
454        for (int p = 0; p < NDS_PAGES; p++) {
455            const char *text = GBS_global_string("Entries %i - %i", p*NDS_PER_PAGE+1, (p+1)*NDS_PER_PAGE);
456            aws->insert_option(text, "", p);
457        }
458        aws->update_option_menu();
459
460        AWT_insert_config_manager(aws, AW_ROOT_DEFAULT, "nds", nds_store_config, nds_restore_config, 0, 0);
461
462        // --------------------
463
464        aws->button_length(13);
465        int dummy, closey;
466        aws->at_newline();
467        aws->get_at_position(&dummy, &closey);
468
469        aws->create_button(0, "K");
470
471        aws->at_newline();
472
473        int leafx, groupx, fieldselectx, fieldx, columnx, srtx, srtux;
474
475        aws->auto_space(10, 0);
476
477        int i;
478        for (i=0; i<NDS_PER_PAGE; i++) {
479            aws->get_at_position(&leafx, &dummy);
480            aws->create_toggle(viewkeyAwarName(i, "leaf"));
481
482            aws->get_at_position(&groupx, &dummy);
483            aws->create_toggle(viewkeyAwarName(i, "group"));
484
485            {
486                const char *awar_name = strdup(viewkeyAwarName(i, "key_text"));
487
488                aws->button_length(20);
489                aws->get_at_position(&fieldx, &dummy);
490                aws->create_input_field(awar_name, 15);
491
492                aws->button_length(0);
493                aws->callback(makeWindowCallback(popup_select_species_field_window, awar_name, gb_main)); // awar_name belongs to cbs now
494                aws->get_at_position(&fieldselectx, &dummy);
495
496                char *button_id = GBS_global_string_copy("SELECT_NDS_%i", i+1);
497                aws->create_button(button_id, "N");
498                free(button_id);
499            }
500
501            aws->get_at_position(&columnx, &dummy);
502            aws->create_input_field(viewkeyAwarName(i, "len1"), 4);
503
504            {
505                char *awar_name = strdup(viewkeyAwarName(i, "pars"));
506
507                aws->get_at_position(&srtx, &dummy);
508                aws->button_length(0);
509                aws->callback(makeWindowCallback(AWT_popup_select_srtaci_window, awar_name)); // awar_name belongs to cbs now
510                {
511                    char *button_id = GBS_global_string_copy("SELECT_SRTACI_%i", i+1);
512                    aws->create_button(button_id, "S");
513                    free(button_id);
514                }
515
516                aws->get_at_position(&srtux, &dummy);
517                aws->at_set_to(true, false, -7, 30);
518                aws->create_input_field(awar_name, 40);
519            }
520
521            aws->at_unset_to();
522            aws->at_newline();
523        }
524
525        aws->at(leafx, closey);
526
527        aws->at_x(leafx);
528        aws->create_button(0, "LEAF");
529        aws->at_x(groupx);
530        aws->create_button(0, "GRP.");
531
532        aws->at_x(fieldx);
533        aws->create_button(0, "FIELD");
534
535        aws->at_x(fieldselectx);
536        aws->create_button(0, "SEL");
537
538        aws->at_x(columnx);
539        aws->create_button(0, "WIDTH");
540
541        aws->at_x(srtx);
542        aws->create_button(0, "SRT");
543
544        aws->at_x(srtux);
545        aws->create_button(0, "ACI/SRT PROGRAM");
546    }
547
548    return aws;
549}
550
551
552
553void make_node_text_init(GBDATA *gb_main) {
554    GBDATA *gbz, *gbe;
555    int     count;
556
557    if (!awt_nds_ms) awt_nds_ms = (NodeTextBuilder *) GB_calloc(sizeof(NodeTextBuilder), 1);
558
559    GBDATA *gb_arb_presets = GB_search(gb_main, "arb_presets", GB_CREATE_CONTAINER);
560    count                  = 0;
561
562    for (gbz = GB_entry(gb_arb_presets, "viewkey"); gbz; gbz  = GB_nextEntry(gbz)) {
563        // toggle set ?
564        bool at_leaf  = *GBT_read_int(gbz, "leaf");
565        bool at_group = *GBT_read_int(gbz, "group");
566
567
568        if (at_leaf || at_group) {
569            freeset(awt_nds_ms->dkeys[count], GB_read_string(GB_entry(gbz, "key_text")));
570
571            awt_nds_ms->rek[count]      = (GB_first_non_key_char(awt_nds_ms->dkeys[count]) != 0);
572            awt_nds_ms->lengths[count]  = *GBT_read_int(gbz, "len1");
573            awt_nds_ms->at_leaf[count]  = at_leaf;
574            awt_nds_ms->at_group[count] = at_group;
575
576            gbe = GB_entry(gbz, "pars");
577            freenull(awt_nds_ms->parsing[count]);
578            if (gbe && GB_read_string_count(gbe)>1) awt_nds_ms->parsing[count] = GB_read_string(gbe);
579            count++;
580        }
581    }
582    awt_nds_ms->show_errors = 10;
583    awt_nds_ms->count       = count;
584}
585
586static char *quoted_if_containing_separator(const char *text, char separator) {
587    bool contains_separator = strchr(text, separator) != NULL;
588    if (!contains_separator) return NULL;
589    return GBS_global_string_copy("\"%s\"", text);
590}
591
592const char *make_node_text_nds(GBDATA *gb_main, GBDATA * gbd, NDS_Type mode, GBT_TREE *species, const char *tree_name) {
593    awt_nds_ms->init_buffer();
594
595    if (!gbd) {
596        if (!species) return "<internal error: no tree-node, no db-entry>";
597        if (!species->name) return "<internal error: species w/o name>";
598        awt_nds_ms->appendf("<%s>", species->name); // zombie
599    }
600    else {
601        bool field_was_printed = false;
602        bool is_leaf           = species ? species->is_leaf : true;
603
604        for (int i = 0; i < awt_nds_ms->count; i++) {
605            if (is_leaf) { if (!awt_nds_ms->at_leaf[i]) continue; }
606            else         { if (!awt_nds_ms->at_group[i]) continue; }
607
608            char *str        = 0;     // the generated string
609            bool  apply_aci  = false; // whether aci shall be applied
610            bool  align_left = true;  // otherwise align right
611
612            {
613                const char *field_output = "";
614                const char *field_name   = awt_nds_ms->dkeys[i];
615
616                if (field_name[0] == 0) { // empty field_name -> only do ACI/SRT
617                    apply_aci = true;
618                }
619                else { // non-empty field_name
620                    GBDATA *gbe;
621                    if (awt_nds_ms->rek[i]) {       // hierarchical key
622                        gbe = GB_search(gbd, awt_nds_ms->dkeys[i], GB_FIND);
623                    }
624                    else {              // flat entry
625                        gbe = GB_entry(gbd, awt_nds_ms->dkeys[i]);
626                    }
627                    // silently ignore missing fields (and leave apply_aci false!)
628                    if (gbe) {
629                        apply_aci = true;
630                        switch (GB_read_type(gbe)) {
631                            case GB_INT: field_output  = GBS_global_string("%li", GB_read_int(gbe)); align_left = false; break;
632                            case GB_BYTE: field_output = GBS_global_string("%i", GB_read_byte(gbe)); align_left = false; break;
633
634                            case GB_FLOAT: {
635                                const char *format = "%5.4f";
636                                if (mode == NDS_OUTPUT_TAB_SEPARATED) { // '.' -> ','
637                                    char *dotted  = GBS_global_string_copy(format, GB_read_float(gbe));
638                                    char *dot     = strchr(dotted, '.');
639                                    if (dot) *dot = ',';
640                                    field_output  = GBS_static_string(dotted);
641                                    free(dotted);
642                                }
643                                else {
644                                    field_output = GBS_global_string(format, GB_read_float(gbe));
645                                }
646                                align_left = false;
647                                break;
648                            }
649                            case GB_STRING:
650                                field_output = GB_read_char_pntr(gbe);
651                                if (!field_output) field_output="<read error>";
652                                break;
653
654                            default: {
655                                char *as_string = GB_read_as_string(gbe);
656                                field_output    = GBS_static_string(as_string);
657                                free(as_string);
658                            }
659                        }
660                    }
661                }
662                str = strdup(field_output);
663            }
664
665            // apply ACI/SRT program
666
667            GB_ERROR error = 0;
668            if (apply_aci) {
669                const char *aci_srt = awt_nds_ms->parsing[i];
670                if (aci_srt) {
671                    char *aci_result = GB_command_interpreter(gb_main, str, aci_srt, gbd, tree_name);
672                    if (!aci_result) aci_result = GBS_global_string_copy("<error: %s>", GB_await_error());
673                    freeset(str, aci_result);
674                }
675            }
676
677            NDS_mask_nonprintable_chars(str);
678
679            // quote string, if it contains separator
680            {
681                char *quoted = NULL;
682                switch (mode) {
683                    case NDS_OUTPUT_COMMA_SEPARATED:
684                        quoted = quoted_if_containing_separator(str, ',');
685                        break;
686                       
687                    case NDS_OUTPUT_TAB_SEPARATED:
688                        quoted = quoted_if_containing_separator(str, '\t');
689                        break;
690
691                    case NDS_OUTPUT_LEAFTEXT:
692                    case NDS_OUTPUT_SPACE_PADDED:
693                        break;
694                }
695
696                if (quoted) freeset(str, quoted);
697            }
698
699
700            bool skip_display = (mode == NDS_OUTPUT_LEAFTEXT && str[0] == 0);
701            if (!skip_display) {
702                switch (mode) {
703                    case NDS_OUTPUT_LEAFTEXT:
704                        if (!field_was_printed) break; // no comma no space if nothing printed yet
705                        awt_nds_ms->append(','); // separate single fields by comma in compressed mode
706                        // fall-through
707                    case NDS_OUTPUT_SPACE_PADDED:
708                        awt_nds_ms->append(' '); // print at least one space if not using tabs
709                        break;
710
711                    case NDS_OUTPUT_COMMA_SEPARATED:
712                        if (i != 0) awt_nds_ms->append(','); // CSV output for star-calc/excel/...
713                        break;
714
715                    case NDS_OUTPUT_TAB_SEPARATED:
716                        if (i != 0) awt_nds_ms->append('\t'); // tabbed output for star-calc/excel/...
717                        break;
718                }
719
720                field_was_printed = true;
721
722                int str_len = strlen(str);
723                switch (mode) {
724                    case NDS_OUTPUT_TAB_SEPARATED:
725                    case NDS_OUTPUT_COMMA_SEPARATED:
726                        awt_nds_ms->append(str, str_len);
727                        break;
728
729                    case NDS_OUTPUT_LEAFTEXT:
730                    case NDS_OUTPUT_SPACE_PADDED: {
731                        int nds_len = awt_nds_ms->lengths[i];
732                        if (str_len>nds_len) { // string is too long -> shorten
733                            str[nds_len] = 0;
734                            str_len      = nds_len;
735                        }
736
737                        if (mode == NDS_OUTPUT_SPACE_PADDED) { // may need alignment
738                            const char *spaced = GBS_global_string((align_left ? "%-*s" : "%*s"), nds_len, str);
739                            awt_nds_ms->append(spaced, nds_len);
740                        }
741                        else {
742                            nds_assert(mode == NDS_OUTPUT_LEAFTEXT);
743                            awt_nds_ms->append(str, str_len);
744                        }
745                    }
746                }
747            }
748
749            // show first XXX errors
750            if (error && awt_nds_ms->show_errors>0) {
751                awt_nds_ms->show_errors--;
752                aw_message(error);
753            }
754
755            free(str);
756        }
757    }
758
759    return awt_nds_ms->get_buffer();
760}
761
762static const char *createReplaceTable() {
763    static char replaceTable[256];
764
765    for (int i = 0; i<32; ++i)   replaceTable[i] = '?';
766    for (int i = 32; i<256; ++i) replaceTable[i] = i;
767
768    const char LFREP = '#';
769
770    replaceTable['\n'] = LFREP;
771    replaceTable['\r'] = LFREP;
772    replaceTable['\t'] = ' ';
773
774    return replaceTable;
775}
776
777char *NDS_mask_nonprintable_chars(char * const str) {
778    // mask nonprintable characters in result of NDS.
779    //
780    // modifies and returns 'str'
781    //
782    // background: gtk renders LFs as such (i.e. renders multiple lines),
783    //             motif printed a small box (i.e. rendered all text in one line)
784
785    static const char *replaceTable = createReplaceTable();
786    for (int i = 0; str[i]; ++i) {
787        str[i] = replaceTable[safeCharIndex(str[i])];
788    }
789    return str;
790}
791
792// --------------------------------------------------------------------------------
793
794#ifdef UNIT_TESTS
795#ifndef TEST_UNIT_H
796#include <test_unit.h>
797#endif
798
799#define TEST_EXPECT_MASK_NONPRINTABLE(i,o) do { \
800        char *masked = strdup(i);               \
801        NDS_mask_nonprintable_chars(masked);    \
802        TEST_EXPECT_EQUAL(masked,o);            \
803        free(masked);                           \
804    } while (0)
805
806void TEST_mask_nds() {
807    TEST_EXPECT_MASK_NONPRINTABLE("plain text",     "plain text");
808    TEST_EXPECT_MASK_NONPRINTABLE("with\nLF",       "with#LF");
809    TEST_EXPECT_MASK_NONPRINTABLE("with\rLF",       "with#LF");
810    TEST_EXPECT_MASK_NONPRINTABLE("with\r\nLF",     "with##LF");
811    TEST_EXPECT_MASK_NONPRINTABLE("tab\tseparated", "tab separated");
812    TEST_EXPECT_MASK_NONPRINTABLE("\t\n\t\n",       " # #");
813}
814
815#define TEST_EXPECT_NDS_EQUALS(specName,format,expected_NDS) do {                           \
816        GBDATA *gb_species  = GBT_find_species(gb_main, specName);                          \
817        TEST_REJECT_NULL(gb_species);                                                       \
818                                                                                            \
819        const char *nds = make_node_text_nds(gb_main, gb_species, format, NULL, NULL);      \
820        TEST_EXPECT_EQUAL(nds, expected_NDS);                                               \
821    } while(0)
822
823#define TEST_EXPECT_NDS_EQUALS__BROKEN(specName,format,expected_NDS) do {                   \
824        GBDATA *gb_species  = GBT_find_species(gb_main, specName);                          \
825        TEST_REJECT_NULL(gb_species);                                                       \
826                                                                                            \
827        const char *nds = make_node_text_nds(gb_main, gb_species, format, NULL, NULL);      \
828        TEST_EXPECT_EQUAL__BROKEN(nds, expected_NDS);                                       \
829    } while(0)
830
831void TEST_nds() {
832    GB_shell    shell;
833    const char *testDB  = "display/nds.arb"; // NDS definitions are in ../../UNIT_TESTER/run/display/nds.arb@arb_presets
834    GBDATA     *gb_main = GB_open(testDB, "r");
835
836    TEST_REJECT_NULL(gb_main);
837
838    {
839        GB_transaction ta(gb_main);
840        make_node_text_init(gb_main);
841
842        TEST_EXPECT_NDS_EQUALS("MycChlor", NDS_OUTPUT_LEAFTEXT,        "'MycChlor', Mycobacterium #phenolicus, acc=X79094");
843        TEST_EXPECT_NDS_EQUALS("MycChlor", NDS_OUTPUT_SPACE_PADDED,    " 'MycChlor'   Mycobacterium #phenolicus      acc=X79094          ");
844        TEST_EXPECT_NDS_EQUALS("MycChlor", NDS_OUTPUT_TAB_SEPARATED,   "'MycChlor'\tMycobacterium #phenolicus\tacc=X79094");
845        TEST_EXPECT_NDS_EQUALS("MycChlor", NDS_OUTPUT_COMMA_SEPARATED, "'MycChlor',Mycobacterium #phenolicus,acc=X79094");
846
847        TEST_EXPECT_NDS_EQUALS("ActUtahe", NDS_OUTPUT_LEAFTEXT,        "'ActUtahe', Act;ino planes uta,hen.sis#, acc=X80823");
848        TEST_EXPECT_NDS_EQUALS("ActUtahe", NDS_OUTPUT_SPACE_PADDED,    " 'ActUtahe'   Act;ino planes uta,hen.sis#    acc=X80823          ");
849        TEST_EXPECT_NDS_EQUALS("ActUtahe", NDS_OUTPUT_TAB_SEPARATED,   "'ActUtahe'\tAct;ino planes uta,hen.sis#\tacc=X80823");
850        TEST_EXPECT_NDS_EQUALS("ActUtahe", NDS_OUTPUT_COMMA_SEPARATED, "'ActUtahe',\"Act;ino planes uta,hen.sis#\",acc=X80823");     // quote 2nd value (cause it contains a comma)
851
852        TEST_EXPECT_NDS_EQUALS("StpGrise", NDS_OUTPUT_LEAFTEXT,        "'StpGrise', Strepto s griseus, acc=M76388 X55435 X6");       // acc truncated!
853        TEST_EXPECT_NDS_EQUALS("StpGrise", NDS_OUTPUT_SPACE_PADDED,    " 'StpGrise'   Strepto s griseus              acc=M76388 X55435 X6");
854        TEST_EXPECT_NDS_EQUALS("StpGrise", NDS_OUTPUT_TAB_SEPARATED,   "'StpGrise'\tStrepto s griseus\tacc=M76388 X55435 X61478");   // acc not truncated here
855        TEST_EXPECT_NDS_EQUALS("StpGrise", NDS_OUTPUT_COMMA_SEPARATED, "'StpGrise',Strepto s griseus,acc=M76388 X55435 X61478");
856    }
857
858    GB_close(gb_main);
859}
860
861#endif // UNIT_TESTS
862
863// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.