ARB
TreeRead.cxx
Go to the documentation of this file.
1 // ============================================================ //
2 // //
3 // File : TreeRead.cxx //
4 // Purpose : load tree from file //
5 // //
6 // Institute of Microbiology (Technical University Munich) //
7 // www.arb-home.de //
8 // //
9 // ============================================================ //
10 
11 #include "TreeRead.h"
12 #include <TreeNode.h>
13 
14 #include <arb_msg_fwd.h>
15 #include <arb_strbuf.h>
16 #include <arb_strarray.h>
17 #include <arb_file.h>
18 #include <arb_defs.h>
19 #include <algorithm>
20 
21 #define tree_assert(cond) arb_assert(cond)
22 
23 /*!******************************************************************************************
24  load a tree from file system
25 ********************************************************************************************/
26 
27 // --------------------
28 // TreeReader
29 
30 class TreeReader : virtual Noncopyable {
31  enum tr_lfmode { LF_UNKNOWN, LF_N, LF_R, LF_NR, LF_RN, };
32 
33  int unnamed_counter;
34  char *tree_file_name;
35  FILE *in;
36  int last_character; // may be EOF
37  int line_cnt;
38 
39  GBS_strstruct tree_comment;
40  GBT_LEN max_found_branchlen;
41  double max_found_bootstrap;
42  tr_lfmode lfmode;
43 
44  char *warnings;
45 
46  TreeRoot *troot;
47 
48 #define MAX_READER_WARN 100 // maximum warnings show for each kind of separately counted warnings
49 
50  struct Count {
51  int dropped_leaf_groups;
52  int dropped_leaf_remarks;
53  int dropped_redefined_groups;
54  int dropped_redefined_remarks;
55  Count() : dropped_leaf_groups(0), dropped_leaf_remarks(0), dropped_redefined_groups(0), dropped_redefined_remarks(0) {}
56 
57  bool warnings_suppressed() const { return
58  dropped_leaf_groups>MAX_READER_WARN || dropped_redefined_groups>MAX_READER_WARN ||
59  dropped_leaf_remarks>MAX_READER_WARN || dropped_redefined_remarks>MAX_READER_WARN;
60  }
61  } count;
62 
63  void setError(const char *message);
64  void setErrorAt(const char *message);
65  void setExpectedError(const char *expected);
66 
67  int get_char();
68  int read_char();
69  int read_tree_char(); // extracts comments and ignores whitespace outside comments
70 
71  char *content_ahead(size_t how_many, bool show_eof);
72 
73  void drop_tree_char(char expected);
74 
75  void setBranchName_acceptingBootstrap(TreeNode *node, char*& name);
76 
77  // The eat-functions below assume that the "current" character
78  // has already been read into 'last_character':
79  void eat_white();
80  __ATTR__USERESULT bool eat_number(GBT_LEN& result);
81  char *eat_quoted_string();
82  bool eat_and_set_name_and_length(TreeNode *node, GBT_LEN& len);
83 
84  char *unnamedNodeName() { return GBS_global_string_copy("unnamed%i", ++unnamed_counter); }
85 
86  TreeNode *load_subtree(GBT_LEN& nodeLen);
87  TreeNode *load_named_node(GBT_LEN& nodeLen);
88 
89 public:
90 
91  TreeReader(FILE *input, const char *file_name, TreeRoot *troot_);
92  ~TreeReader();
93 
95  GBT_LEN rootNodeLen = DEFAULT_BRANCH_LENGTH_MARKER; // ignored dummy
96  TreeNode *tree = load_named_node(rootNodeLen);
97 
98  if (!error) {
99  if (rootNodeLen != DEFAULT_BRANCH_LENGTH_MARKER && rootNodeLen != 0.0) {
100  add_warning("Length specified for root-node has been ignored");
101  }
102  if (!tree->is_leaf()) { // handle special cases: tree "();" and "(a);"(?)
103  const char *remark = tree->get_remark(); // remark of root node
104  if (remark) {
105  add_warningf("Remark specified for root-node ('%s') has been ignored", remark);
106  tree->remove_remark();
107  }
108  }
109 
110  // check for unexpected input
111  if (last_character == ';') read_tree_char(); // accepts ';'
112  if (last_character != EOF) {
113  char *unused_input = content_ahead(30, false);
114  add_warningf("Unexpected input-data after tree: '%s'", unused_input);
115  free(unused_input);
116  }
117 
119  }
120  return tree;
121  }
122 
124 
125  void add_warning(const char *msg) {
126  if (warnings) freeset(warnings, GBS_global_string_copy("%s\n%s", warnings, msg));
127  else warnings = GBS_global_string_copy("Warning(s): %s", msg);
128  }
129  __ATTR__FORMAT(2) void add_warningf(const char *format, ...) { FORWARD_FORMATTED(add_warning, format); }
130 
132  GB_ERROR get_warnings() const { return warnings; } // valid until TreeReader is destroyed
133 
134  char *takeComment() {
135  // can only be called once (further calls will return NULp)
136  return tree_comment.release();
137  }
138 
139  double get_max_found_bootstrap() const { return max_found_bootstrap; }
140  GBT_LEN get_max_found_branchlen() const { return max_found_branchlen; }
141 };
142 
143 TreeReader::TreeReader(FILE *input, const char *file_name, TreeRoot *troot_)
144  : unnamed_counter(0),
145  tree_file_name(strdup(file_name)),
146  in(input),
147  last_character(0),
148  line_cnt(1),
149  tree_comment(2048),
150  max_found_branchlen(-1),
151  max_found_bootstrap(-1),
152  lfmode(LF_UNKNOWN),
153  warnings(NULp),
154  troot(troot_),
155  error(NULp)
156 {
157  read_tree_char();
158 }
159 
161  free(warnings);
162  free(tree_file_name);
163 }
164 
165 void TreeReader::setError(const char *message) {
166  tree_assert(!error);
167  error = GBS_global_string("Error reading %s:%i: %s",
168  tree_file_name, line_cnt, message);
169 }
170 char *TreeReader::content_ahead(size_t how_many, bool show_eof) {
171  char show[how_many+1+4]; // 4 = oversize of '<EOF>'
172  size_t i;
173  for (i = 0; i<how_many; ++i) {
174  show[i] = last_character;
175  if (show[i] == EOF) {
176  if (show_eof) {
177  strcpy(show+i, "<EOF>");
178  i += 5;
179  }
180  break;
181  }
182  read_char();
183  }
184  show[i] = 0;
185  return strdup(show);
186 }
187 
188 void TreeReader::setErrorAt(const char *message) {
189  if (last_character == EOF) {
190  setError(GBS_global_string("%s while end-of-file was reached", message));
191  }
192  else {
193  char *show = content_ahead(30, true);
194  setError(GBS_global_string("%s while looking at '%s'", message, show));
195  free(show);
196  }
197 }
198 
199 void TreeReader::setExpectedError(const char *expected) {
200  setErrorAt(GBS_global_string("Expected %s", expected));
201 }
202 
203 int TreeReader::get_char() {
204  // reads character from stream
205  // - converts linefeeds for DOS- and MAC-textfiles
206  // - increments line_cnt
207 
208  int c = getc(in);
209  int inc = 0;
210 
211  if (c == '\n') {
212  switch (lfmode) {
213  case LF_UNKNOWN: lfmode = LF_N; inc = 1; break;
214  case LF_N: inc = 1; break;
215  case LF_R: lfmode = LF_RN; c = get_char(); break;
216  case LF_NR: c = get_char(); break;
217  case LF_RN: inc = 1; break;
218  }
219  }
220  else if (c == '\r') {
221  switch (lfmode) {
222  case LF_UNKNOWN: lfmode = LF_R; inc = 1; break;
223  case LF_R: inc = 1; break;
224  case LF_N: lfmode = LF_NR; c = get_char(); break;
225  case LF_RN: c = get_char(); break;
226  case LF_NR: inc = 1; break;
227  }
228  if (c == '\r') c = '\n'; // never report '\r'
229  }
230  if (inc) line_cnt++;
231 
232  return c;
233 }
234 
235 int TreeReader::read_tree_char() {
236  // reads over tree comment(s) and whitespace.
237  // tree comments are stored inside TreeReader
238 
239  bool done = false;
240  int c = ' ';
241 
242  while (!done && !error) {
243  c = get_char();
244  if (c == ' ' || c == '\t' || c == '\n') ; // skip
245  else if (c == '[') { // collect tree comment(s)
246  int openBrackets = 1;
247  if (tree_comment.get_position()) {
248  tree_comment.put('\n'); // not first comment -> add new line
249  }
250 
251  while (openBrackets && !error) {
252  c = get_char();
253  switch (c) {
254  case EOF:
255  setError("Reached end of file while reading comment");
256  break;
257  case ']':
258  openBrackets--;
259  if (openBrackets) tree_comment.put(c); // write all but last closing brackets
260  break;
261  case '[':
262  openBrackets++;
263  // fall-through
264  default:
265  tree_comment.put(c);
266  break;
267  }
268  }
269  }
270  else done = true;
271  }
272 
273  last_character = c;
274  return c;
275 }
276 
277 int TreeReader::read_char() {
278  int c = get_char();
279  last_character = c;
280  return c;
281 }
282 
283 void TreeReader::eat_white() {
284  int c = last_character;
285  while ((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t')) {
286  c = read_char();
287  }
288 }
289 
290 bool TreeReader::eat_number(GBT_LEN& result) {
291  char strng[256];
292  char *s = strng;
293  int c = last_character;
294 
295  while (((c<='9') && (c>='0')) || (c=='.') || (c=='-') || (c=='+') || (c=='e') || (c=='E')) {
296  *(s++) = c;
297  c = read_char();
298  }
299  *s = 0;
300 
301  GB_ERROR failed_to_convert = GB_safe_atof(strng, &result);
302  if (failed_to_convert) {
303  setErrorAt(failed_to_convert);
304  return false;
305  }
306 
307  eat_white();
308 
309  bool consumed_some_length = strng[0];
310  return consumed_some_length;
311 }
312 
313 char *TreeReader::eat_quoted_string() {
323  const int MAX_NAME_LEN = 1000;
324 
325  char buffer[MAX_NAME_LEN+2];
326  char *s = buffer;
327  int c = last_character;
328 
329 #define NAME_TOO_LONG ((s-buffer)>MAX_NAME_LEN)
330 
331  if (c == '\'' || c == '"') {
332  char found_quote = c;
333 
334  c = read_char();
335  while (c!=EOF && c!=found_quote) {
336  *(s++) = c;
337  if (NAME_TOO_LONG) { c = 0; break; }
338  c = read_char();
339  }
340  if (c == found_quote) c = read_tree_char();
341  }
342  else {
343 #if 0
344  // previous behavior: skip prefixes matching PRE '_* *'
345  // (reason unknown; behavior exists since [2])
346  // conflicts with replacement of problematic character done in ../TREE_WRITE/TreeWrite.cxx@replace_by_underscore
347  // -> disabled
348  while (c == '_') c = read_tree_char();
349  while (c == ' ') c = read_tree_char();
350 #endif
351  while (c!=':' && c!=EOF && c!=',' && c!=';' && c != ')') {
352  *(s++) = c;
353  if (NAME_TOO_LONG) break;
354  c = read_tree_char();
355  }
356  }
357  *s = 0;
358  if (NAME_TOO_LONG) {
359  setError(GBS_global_string("Name '%s' is longer than %i bytes", buffer, MAX_NAME_LEN));
360  return NULp;
361  }
362  return strdup(buffer);
363 }
364 
365 inline void append_leaf_redef_details(GBS_strstruct& summary, int droppedAtLeaf, int droppedRedefined) {
366  if (droppedAtLeaf) {
367  summary.putlong(droppedAtLeaf);
368  summary.cat(" at leafs");
369  }
370  if (droppedAtLeaf && droppedRedefined) {
371  summary.cat(" + ");
372  }
373  if (droppedRedefined) {
374  summary.putlong(droppedRedefined);
375  summary.cat(" redefined");
376  }
377 }
378 
380  if (count.warnings_suppressed()) {
381  GBS_strstruct summary(200);
382  summary.cat("Drop summary: ");
383 
384  bool dropped_remarks = count.dropped_leaf_remarks || count.dropped_redefined_remarks;
385  bool dropped_groups = count.dropped_leaf_groups || count.dropped_redefined_groups;
386 
387  if (dropped_groups) {
388  summary.cat("groups: ");
389  append_leaf_redef_details(summary, count.dropped_leaf_groups, count.dropped_redefined_groups);
390  }
391  if (dropped_groups && dropped_remarks) {
392  summary.cat(", ");
393  }
394  if (dropped_remarks) {
395  summary.cat("support-values/remarks: ");
396  append_leaf_redef_details(summary, count.dropped_leaf_remarks, count.dropped_redefined_remarks);
397  }
398  summary.put('.');
399 
400  add_warning(summary.get_data());
401  }
402 }
403 
404 void TreeReader::setBranchName_acceptingBootstrap(TreeNode *node, char*& name) {
405  // store groupname and/or bootstrap value.
406  //
407  // ARBs extended newick format allows 4 kinds of node-names:
408  // 'groupname'
409  // 'bootstrap'
410  // 'bootstrap:groupname' (needs to be quoted)
411  // 'remark:groupname' (needs to be quoted)
412  //
413  // where
414  // 'bootstrap' is sth interpretable as double (optionally followed by '%')
415  // 'groupname' is sth not interpretable as double
416  //
417  // If a groupname is detected, it is stored in node->name
418  // If a bootstrap or remark is detected, it is stored in node->remark_branch
419  //
420  // Bootstrap values will be scaled up by factor 100.
421  // Wrong scale-ups (to 10000) will be corrected by calling TREE_scale() after the whole tree has been loaded.
422  //
423  // Note: this method may be called with leafs, if the input data contains "(leafname)groupname" or similar.
424  // In this case it gets called for 'groupname' (not for 'leafname').
425 
426  char *group = NULp;
427  {
428  double bootstrap;
429  const char *group_part = name;
430  char *remark = NULp;
431  bool is_bootstrap = parse_treelabel(group_part, bootstrap, remark);
432 
433  if (is_bootstrap || remark) {
434  tree_assert(contradicted(is_bootstrap, remark));
435 
436  if (node->is_leaf()) {
437  if (count.dropped_leaf_remarks<MAX_READER_WARN) {
438  if (is_bootstrap) {
439  add_warningf("Dropped support-value ('%g') specified for a single-node-subtree", bootstrap);
440  }
441  else {
442  add_warningf("Dropped remark ('%s') specified for a single-node-subtree", remark);
443  }
444 
445  if (++count.dropped_leaf_remarks == MAX_READER_WARN) {
446  add_warning("[Note: further warnings of this type will be suppressed]");
447  }
448  }
449  }
450  else {
451  const char *existingRemark = node->get_remark();
452  if (existingRemark) {
453  if (count.dropped_redefined_remarks<MAX_READER_WARN) {
454  if (is_bootstrap) {
455  add_warningf("Dropped support-value redefinition '%g' (edge already labeled as '%s')", bootstrap, existingRemark);
456  }
457  else {
458  add_warningf("Dropped remark redefinition '%s' (edge already labeled as '%s')", remark, existingRemark);
459  }
460  if (++count.dropped_redefined_remarks == MAX_READER_WARN) {
461  add_warning("[Note: further warnings of this type will be suppressed]");
462  }
463  freenull(remark);
464  }
465  }
466  else {
467  if (is_bootstrap) {
468  bootstrap = bootstrap*100.0; // needed if bootstrap values are between 0.0 and 1.0 (downscaling is done later)
469  if (bootstrap > max_found_bootstrap) { max_found_bootstrap = bootstrap; }
470  node->set_bootstrap(bootstrap);
471  }
472  else {
473  node->set_remark(remark);
474  }
475  }
476  }
477  }
478 
479  if (group_part) {
480  if (group_part == name) reassign(group, name); // use whole input as groupname
481  else group = strdup(group_part);
482  }
483 
484  freenull(name);
485  freenull(remark);
486  }
487 
488  if (group) {
489  if (!group[0]) { // empty group name (e.g. "3%:" or "remark:")
490  freenull(group);
491  }
492  else if (node->name) {
493  if (node->is_leaf()) {
494  if (count.dropped_leaf_groups<MAX_READER_WARN) {
495  add_warningf("Dropped group name specified for a single-node-subtree ('%s')", group);
496  if (++count.dropped_leaf_groups == MAX_READER_WARN) {
497  add_warning("[Note: further warnings of this type will be suppressed]");
498  }
499  }
500  freenull(group);
501  }
502  else {
503  if (count.dropped_redefined_groups<MAX_READER_WARN) {
504  add_warningf("Dropped group name redefinition '%s' (furcation already named '%s')",
505  group, node->name);
506  if (++count.dropped_redefined_groups == MAX_READER_WARN) {
507  add_warning("[Note: further warnings of this type will be suppressed]");
508  }
509  }
510  freenull(group);
511  }
512  }
513  else {
514  reassign(node->name, group);
515  }
516 
517  tree_assert(implicated(node->name, node->name[0]));
518 
519  if (node->name) {
520  GB_ERROR badNameError = GBT_check_valid_group_name(node->name);
521  if (badNameError) setErrorAt(badNameError);
522  }
523  }
524 }
525 
526 void TreeReader::drop_tree_char(char expected) {
527  if (last_character != expected) {
528  setExpectedError(GBS_global_string("'%c'", expected));
529  }
530  read_tree_char();
531 }
532 
533 bool TreeReader::eat_and_set_name_and_length(TreeNode *node, GBT_LEN& nodeLen) {
534  // reads optional branch-name and -length
535  //
536  // if 'nodeLen' contains DEFAULT_BRANCH_LENGTH_MARKER, it gets overwritten with any found length-specification
537  // otherwise found length is added to 'nodeLen'
538  //
539  // sets the branch-name of 'node', if a name is found (e.g. sth like "(...)'name':0.5")
540  //
541  // returns true if successful, false otherwise (TreeReader::error is set then)
542 
543  bool done = false;
544  bool length_consumed = false;
545 
546  while (!done && !error) {
547  switch (last_character) {
548  case ';':
549  case ',':
550  case ')':
551  done = true;
552  break;
553  case ':':
554  if (!error && length_consumed) setErrorAt("Unexpected ':' (already read a branchlength)");
555  if (!error) drop_tree_char(':');
556  if (!error) {
557  GBT_LEN foundlen;
558  if (eat_number(foundlen)) {
559  if (is_marked_as_default_len(nodeLen)) {
560  nodeLen = foundlen;
561  }
562  else {
563  tree_assert(node->is_leaf()); // should only happen when a single leaf in parenthesis was read
564  nodeLen += foundlen; // sum leaf and node lengths
565  }
566  max_found_branchlen = std::max(max_found_branchlen, nodeLen);
567  }
568  else {
569  // accept (don't superseede) errors set by eat_number()
570  if (!error) setExpectedError("valid length");
571  }
572  }
573  length_consumed = true;
574  break;
575 
576  case EOF:
577  done = true;
578  break;
579 
580  default: {
581  char *branchName = eat_quoted_string();
582  tree_assert(contradicted(branchName, error));
583  if (branchName) {
584  if (branchName[0]) setBranchName_acceptingBootstrap(node, branchName);
585  else freenull(branchName);
586  }
587  break;
588  }
589  }
590  }
591 
592  return !error;
593 }
594 
595 static TreeNode *createLinkedTreeNode(const TreeRoot& nodeMaker, TreeNode *left, GBT_LEN leftlen, TreeNode *right, GBT_LEN rightlen) { // @@@ move into class GBT_tree (as ctor) - or better move into TreeNodeFactory
596  TreeNode *node = nodeMaker.makeNode();
597 
598  node->leftson = left;
599  node->leftlen = leftlen;
600  node->rightson = right;
601  node->rightlen = rightlen;
602 
603  left->father = node;
604  right->father = node;
605 
606  return node;
607 }
608 
609 TreeNode *TreeReader::load_named_node(GBT_LEN& nodeLen) {
610  // reads a node or subtree.
611  // a single node is expected to have a name (or will be auto-named)
612  // subtrees may have a name (groupname)
613  TreeNode *node = NULp;
614 
615  if (last_character == '(') {
616  node = load_subtree(nodeLen);
617  }
618  else { // single node
619  eat_white();
620  char *name = eat_quoted_string();
621  tree_assert(contradicted(name, error));
622  if (name) {
623  if (!name[0]) freeset(name, unnamedNodeName());
624 
625  node = troot->makeNode();
626  node->name = name;
627  node->markAsLeaf();
628  }
629  }
630  if (node && !error) {
631  if (!eat_and_set_name_and_length(node, nodeLen)) {
632  node->forget_origin();
633  destroy(node, troot);
634  node = NULp;
635  }
636  }
637  tree_assert(contradicted(node, error));
638  tree_assert(!node || !node->is_leaf() || node->name); // leafs need to be named here
639  return node;
640 }
641 
642 
643 TreeNode *TreeReader::load_subtree(GBT_LEN& nodeLen) {
644  // loads a subtree (i.e. expects parenthesis around one or several nodes)
645  //
646  // 'nodeLen' normally is set to DEFAULT_BRANCH_LENGTH_MARKER
647  // or to length of single node (if parenthesis contain only one node)
648  //
649  // length and/or name behind '(...)' are not parsed (has to be done by caller).
650  //
651  // if subtree contains a single node (or a single other subtree), 'name'+'remark_branch' are
652  // already set, when load_subtree() returns - otherwise they are NULp.
653 
654  TreeNode *node = NULp;
655 
656  drop_tree_char('(');
657 
659  TreeNode *left = load_named_node(leftLen);
660 
661  if (left) {
662  switch (last_character) {
663  case ')': // single node
664  nodeLen = leftLen;
665  node = left;
666  left = NULp;
667  break;
668 
669  case ',': {
671  TreeNode *right = NULp;
672 
673  while (last_character == ',' && !error) {
674  if (right) { // multi-branch
675  TreeNode *pair = createLinkedTreeNode(*troot, left, leftLen, right, rightLen);
676 
677  left = pair; leftLen = 0;
678  right = NULp; rightLen = DEFAULT_BRANCH_LENGTH_MARKER;
679  }
680 
681  drop_tree_char(',');
682  if (!error) {
683  right = load_named_node(rightLen);
684  }
685  }
686 
687  if (!error) {
688  if (last_character == ')') {
689  node = createLinkedTreeNode(*troot, left, leftLen, right, rightLen);
691 
692  left = NULp;
693  right = NULp;
694  }
695  else {
696  setExpectedError("one of ',)'");
697  }
698  }
699 
700  if (right) {
701  right->forget_origin();
702  destroy(right, troot);
703  }
704  if (error && node) {
705  node->forget_origin();
706  destroy(node, troot);
707  node = NULp;
708  }
709 
710  break;
711  }
712 
713  default:
714  setExpectedError("one of ',)'");
715  break;
716  }
717  if (left) {
718  left->forget_origin();
719  destroy(left, troot);
720  }
721  }
722 
723  if (!error) drop_tree_char(')');
724 
725  tree_assert(contradicted(node, error));
726  return node;
727 }
728 
730 static void warningToConsumingReader(const char *msg) {
731  if (consumingReader) consumingReader->add_warning(msg);
732 }
733 
734 TreeNode *TREE_load(const char *path, TreeRoot *troot, char **commentPtr, bool allow_length_scaling, char **warningPtr) {
735  /* Load a newick compatible tree from file 'path',
736  if commentPtr is specified -> set it to a malloc copy of all concatenated comments found in tree file
737  if warningPtr is specified -> set it to a malloc copy of any warnings occurring during tree-load (e.g. autoscale- or informational warnings)
738  */
739 
740  TreeNode *tree = NULp;
741  FILE *input = fopen(path, "rt");
742  GB_ERROR error = NULp;
743  bool own_root = true;
744 
745  if (!input) {
746  error = GBS_global_string("No such file: %s", path);
747  }
748  else {
749  const char *name_only = strrchr(path, '/');
750  if (name_only) ++name_only;
751  else name_only = path;
752 
753  TreeReader reader(input, name_only, troot);
754  if (!reader.error) {
755  tree = reader.load();
756  if (tree) own_root = false;
757  }
758  fclose(input);
759 
760  if (reader.error) error = reader.error;
761  else if (tree && tree->is_leaf()) error = "tree is too small (need at least 2 species)";
762 
763  if (error) {
764  destroy(tree);
765  tree = NULp;
766  }
767 
768  if (tree) {
769  double bootstrap_scale = 1.0;
770  double branchlen_scale = 1.0;
771 
772  if (reader.get_max_found_bootstrap() >= 101.0) { // bootstrap values were given in percent
773  bootstrap_scale = 0.01;
774  reader.add_warningf("Auto-scaling bootstrap values by factor %.2f (max. found bootstrap was %5.2f)",
775  bootstrap_scale, reader.get_max_found_bootstrap());
776  }
777  if (reader.get_max_found_branchlen() >= 1.1) { // assume branchlengths have range [0;100]
778  if (allow_length_scaling) {
779  branchlen_scale = 0.01;
780  reader.add_warningf("Auto-scaling branchlengths by factor %.2f (max. found branchlength = %.2f)\n"
781  "(use ARB/Tree/Modify branches/Scale branchlengths with factor %.2f to undo auto-scaling)",
782  branchlen_scale, reader.get_max_found_branchlen(), 1.0/branchlen_scale);
783  }
784  }
785 
786  {
787  // scale bootstraps and branchlengths:
788  LocallyModify<TreeReader*> assign_consumer(consumingReader, &reader);
789  TREE_scale(tree, branchlen_scale, bootstrap_scale, warningToConsumingReader);
790  }
791 
792  // correct root remark(s)
793  if (!tree->is_leaf()) {
794  tree_assert(tree == tree->get_root_node());
795  // @@@ correction could be done in parse_bootstrap + just call it once here: ../../ARBDB/TreeNode.cxx@CORR_ROOT_REM
796  TreeNode *left_son = tree->get_leftson();
797  TreeNode *right_son = tree->get_rightson();
798 
799  if (!left_son->is_leaf() && !right_son->is_leaf()) {
800  const char *left_rem = left_son->get_remark();
801  const char *right_rem = right_son->get_remark();
802 
803  if (left_rem != right_rem) { // not identical
804  TreeNode *son_with_remark = left_rem ? left_son : right_son;
805  TreeNode *other_son = left_rem ? right_son : left_son;
806 
807  tree_assert(son_with_remark->is_inner_node_with_remark());
808  other_son->use_as_remark(son_with_remark->get_remark_ptr());
809 
810  left_rem = left_son->get_remark();
811  right_rem = right_son->get_remark();
812  }
813  }
815  }
816 
817  if (warningPtr) {
819  const char *wmsg = reader.get_warnings();
820  if (wmsg) *warningPtr = strdup(wmsg);
821  }
822 
823  if (commentPtr) {
824  char *comment = reader.takeComment();
825 
826  const char *loaded_from = GBS_global_string("Loaded from %s", path);
827  freeset(comment, GBS_log_action_to(comment, loaded_from, true));
828 
829  // @@@ append warnings?
830 
831  tree_assert(!*commentPtr);
832  *commentPtr = comment;
833  }
834  }
835  }
836 
837  tree_assert(tree||error);
838  if (error) {
839  GB_export_errorf("Import tree: %s", error);
840  tree_assert(!tree);
841  if (own_root) troot->delete_by_node();
842  }
843 
844  return tree;
845 }
846 
847 GB_ERROR TREE_load_to_db(GBDATA *gb_main, const char *treefile, const char *tree_name, const LabelTranslator& translator) {
848  GB_ERROR error = NULp;
849 
850  char *warnings = NULp;
851  char *tree_comment = NULp;
852 
853  TreeNode *tree = TREE_load(treefile, new SimpleRoot, &tree_comment, true, &warnings);
854 
855  if (!tree) error = GB_await_error();
856  else {
857  if (warnings) GBT_message(gb_main, warnings);
858 
859  error = TREE_translate_labels(gb_main, tree, translator);
860 
861  if (!error) {
862  GB_transaction ta(gb_main);
863  error = GBT_write_tree_with_remark(gb_main, tree_name, tree, tree_comment);
864  error = ta.close(error);
865  }
866 
867  destroy(tree);
868  }
869 
870  free(warnings);
871  free(tree_comment);
872 
873  return error;
874 }
875 
876 // --------------------------------------------------------------------------------
877 
878 #ifdef UNIT_TESTS
879 #ifndef TEST_UNIT_H
880 #include <test_unit.h>
881 #endif
882 
883 static arb_test::match_expectation parsing_label_succeeds(const char *label, bool result_expected, const double bootstrap_expected, const char *remark_expected, const char *groupname_expected) {
884  using namespace arb_test;
885  expectation_group expected;
886 
887  const double UNCHANGED_VALUE = -666.66;
888 
889  double bootstrap = UNCHANGED_VALUE;
890  char *remark = (char*)"xxx";
891  const char *groupname = label;
892 
893  bool result = parse_treelabel(groupname, bootstrap, remark);
894 
895  expected.add(that(result).is_equal_to(result_expected));
896  expected.add(that(groupname).is_equal_to(groupname_expected));
897 
898  const double EPSILON = 0.000001;
899  tree_assert(!epsilon_similar(EPSILON)(bootstrap_expected, UNCHANGED_VALUE));
900 
901  expected.add(that(bootstrap).fulfills(epsilon_similar(EPSILON), bootstrap_expected));
902  expected.add(that(remark).is_equal_to(remark_expected));
903 
904  freenull(remark);
905 
906  return all().ofgroup(expected);
907 }
908 
909 const double ZERO = 0.0;
910 
911 #define TEST_PARSES_LABEL_AS_EMPTY(label) TEST_EXPECTATION(parsing_label_succeeds(label, false, ZERO, NULp, NULp))
912 #define TEST_PARSES_LABEL_AS_PLAIN_GROUP(label,group) TEST_EXPECTATION(parsing_label_succeeds(label, false, ZERO, NULp, group))
913 #define TEST_PARSES_LABEL_AS_PLAIN_BS(label,bs) TEST_EXPECTATION(parsing_label_succeeds(label, true, bs, NULp, NULp))
914 #define TEST_PARSES_LABEL_AS_PLAIN_REM(label,rem) TEST_EXPECTATION(parsing_label_succeeds(label, false, ZERO, rem, NULp))
915 #define TEST_PARSES_LABEL_AS_BS_AND_GROUP(label,bs,group) TEST_EXPECTATION(parsing_label_succeeds(label, true, bs, NULp, group))
916 #define TEST_PARSES_LABEL_AS_REM_AND_GROUP(label,rem,group) TEST_EXPECTATION(parsing_label_succeeds(label, false, ZERO, rem, group))
917 
918 #define TEST_PARSES_LABEL_AS_PLAIN_REM__BROKEN(label,rem) TEST_EXPECTATION__BROKEN_SIMPLE(parsing_label_succeeds(label, false, ZERO, rem, NULp))
919 #define TEST_PARSES_LABEL_AS_PLAIN_GROUP__BROKEN(label,group) TEST_EXPECTATION__BROKEN_SIMPLE(parsing_label_succeeds(label, false, ZERO, NULp, group))
920 #define TEST_PARSES_LABEL_AS_PLAIN_BS__BROKEN(label,bs) TEST_EXPECTATION__BROKEN_SIMPLE(parsing_label_succeeds(label, true, bs, NULp, NULp))
921 
922 #define TEST_PARSES_LABEL_AS_REM_AND_GROUP__BROKEN(label,rem_wanted,group_wanted,rem_got,group_got) \
923  TEST_EXPECTATION__BROKEN(parsing_label_succeeds(label, false, ZERO, rem_wanted, group_wanted), \
924  parsing_label_succeeds(label, false, ZERO, rem_got, group_got))
925 
926 void TEST_treelabel_parser() {
927  TEST_PARSES_LABEL_AS_PLAIN_GROUP("group", "group");
928  TEST_PARSES_LABEL_AS_PLAIN_GROUP("77x", "77x");
929 
930  TEST_PARSES_LABEL_AS_PLAIN_BS("33%", 0.33);
931  TEST_PARSES_LABEL_AS_PLAIN_BS("0.123", 0.123);
932  TEST_PARSES_LABEL_AS_PLAIN_BS("0.456", 0.456);
933  TEST_PARSES_LABEL_AS_PLAIN_BS("93.27", 93.27);
934  TEST_PARSES_LABEL_AS_PLAIN_BS("93.27%", 0.9327);
935  TEST_PARSES_LABEL_AS_PLAIN_BS("93.27618%", 0.932762);
936 
937  TEST_PARSES_LABEL_AS_BS_AND_GROUP ("24%:group", 0.24, "group");
938  TEST_PARSES_LABEL_AS_REM_AND_GROUP("remark:group", "remark", "group");
939  TEST_PARSES_LABEL_AS_REM_AND_GROUP("remark:456group", "remark", "456group");
940 
941  TEST_PARSES_LABEL_AS_REM_AND_GROUP("123remark:group", "123remark", "group");
942  TEST_PARSES_LABEL_AS_REM_AND_GROUP("123remark:456group", "123remark", "456group");
943 
944  TEST_PARSES_LABEL_AS_REM_AND_GROUP("a:b:c", "a", "b:c"); // group name is invalid (but not reported here); reported in testcase .@REMGRPBAD
945 
946  // test empty group and/or remark:
947  TEST_PARSES_LABEL_AS_PLAIN_REM("remark:", "remark");
948  TEST_PARSES_LABEL_AS_PLAIN_REM("12.789remark:", "12.789remark");
949 
950  TEST_PARSES_LABEL_AS_PLAIN_BS("93.27%:", 0.9327);
951  TEST_PARSES_LABEL_AS_PLAIN_BS("14%:", 0.14);
952  TEST_PARSES_LABEL_AS_PLAIN_BS("81:", 81.0);
953 
954  TEST_PARSES_LABEL_AS_PLAIN_GROUP(":group", "group");
955 
956  TEST_PARSES_LABEL_AS_EMPTY(":");
957  TEST_PARSES_LABEL_AS_EMPTY("");
958 }
959 
960 static TreeNode *loadFromFileContaining(const char *treeString, char **warningsPtr) {
961  const char *filename = "trees/tmp.tree";
962  FILE *out = fopen(filename, "wt");
963  TreeNode *tree = NULp;
964 
965  if (out) {
966  fputs(treeString, out);
967  fclose(out);
968  tree = TREE_load(filename, new SimpleRoot, NULp, false, warningsPtr);
969  }
970  else {
971  GB_export_IO_error("save tree", filename);
972  }
973 
974  return tree;
975 }
976 
977 static arb_test::match_expectation loading_tree_failed_with(TreeNode *tree, const char *errpart) {
978  using namespace arb_test;
979  expectation_group expected;
980 
981  expected.add(that(tree).is_equal_to_NULL());
982  expected.add(that(GB_have_error()).is_equal_to(true));
983  if (GB_have_error()) {
984  expected.add(that(GB_await_error()).does_contain(errpart));
985  }
986  return all().ofgroup(expected);
987 }
988 
989 static arb_test::match_expectation loading_tree_succeeds(TreeNode *tree, const char *newick_expected, NewickFormat format) {
990  using namespace arb_test;
991  expectation_group expected;
992 
993  expected.add(that(tree).does_differ_from_NULL());
994  expected.add(that(GB_get_error()).is_equal_to_NULL());
995  if (!GB_have_error() && tree) {
996  expected.add(that(tree->get_root_node()->has_valid_root_remarks()).is_equal_to(true));
997  char *newick = GBT_tree_2_newick(tree, format, false);
998  expected.add(that(newick).is_equal_to(newick_expected));
999  free(newick);
1000  }
1001  return all().ofgroup(expected);
1002 }
1003 
1004 #define TEST_EXPECT_TREELOAD_FAILED_WITH(tree,errpart) TEST_EXPECTATION(loading_tree_failed_with(tree, errpart))
1005 #define TEST_EXPECT_TREELOAD_FAILED_WITH__BROKEN(tree,errpart) TEST_EXPECTATION__BROKEN(loading_tree_failed_with(tree, errpart))
1006 
1007 #define TEST_EXPECT_TREELOAD(tree,newick) TEST_EXPECTATION(loading_tree_succeeds(tree,newick,expectedFormat))
1008 #define TEST_EXPECT_TREELOAD__BROKEN(tree,newick) TEST_EXPECTATION__BROKEN(loading_tree_succeeds(tree,newick,expectedFormat))
1009 
1010 #define TEST_EXPECT_TREEFILE_FAILS_WITH(name,errpart) do { \
1011  TreeNode *tree = TREE_load(name, new SimpleRoot, NULp, false, NULp); \
1012  TEST_EXPECT_TREELOAD_FAILED_WITH(tree, errpart); \
1013  } while(0)
1014 
1015 #define TEST_EXPECT_TREESTRING_FAILS_WITH(treeString,errpart) do { \
1016  TreeNode *tree = loadFromFileContaining(treeString, NULp); \
1017  TEST_EXPECT_TREELOAD_FAILED_WITH(tree, errpart); \
1018  } while(0)
1019 
1020 // argument 'newick' is vs regression only!
1021 #define TEST_EXPECT_TREESTRING_FAILS_WITH__BROKEN(treeString,errpart,newick) do { \
1022  char *warnings = NULp; \
1023  TreeNode *tree = loadFromFileContaining(treeString, &warnings); \
1024  TEST_EXPECT_TREELOAD_FAILED_WITH__BROKEN(tree, errpart); \
1025  TEST_EXPECT_TREELOAD(tree, newick); \
1026  TEST_EXPECT_NULL(warnings); \
1027  delete tree; \
1028  free(warnings); \
1029  } while(0)
1030 
1031 #define TEST_EXPECT_TREESTRING_OK(treeString,newick) do { \
1032  char *warnings = NULp; \
1033  TreeNode *tree = loadFromFileContaining(treeString, &warnings); \
1034  TEST_EXPECT_TREELOAD(tree, newick); \
1035  TEST_EXPECT_NULL(warnings); \
1036  destroy(tree); \
1037  free(warnings); \
1038  } while(0)
1039 
1040 static arb_test::match_expectation reports_these_warnings(const char *report, const char *expectedWarnings) {
1041  // both arguments are multiple lines joined with \n (line-amount: report==expectedWarnings)
1042  ConstStrArray reportParts;
1043  ConstStrArray warningParts;
1044 
1045  GBT_split_string(reportParts, report, "\n", SPLIT_DROPEMPTY);
1046  GBT_split_string(warningParts, expectedWarnings, "\n", SPLIT_DROPEMPTY);
1047 
1048  using namespace arb_test;
1049  expectation_group expected;
1050 
1051  size_t reportCount = reportParts.size();
1052  size_t warningCount = warningParts.size();
1053 
1054  expected.add(that(reportCount).is_more_or_equal(warningCount));
1055 
1056  bool reportMatched[reportCount];
1057  bool warningMatched[warningCount];
1058 
1059  for (int w = 0; warningParts[w]; ++w) {
1060  const char *warningPart = warningParts[w];
1061  warningMatched[w] = false;
1062 
1063  for (int r = 0; reportParts[r]; ++r) {
1064  if (!w) reportMatched[r] = false;
1065  else if (reportMatched[r]) continue;
1066  const char *reportPart = reportParts[r];
1067  if (strstr(reportPart, warningPart)) {
1068  expected.add(that(reportPart).does_contain(warningPart));
1069  reportMatched[r] = true;
1070  warningMatched[w] = true;
1071  }
1072  }
1073  }
1074 
1075  for (int w = 0; warningParts[w]; ++w) if (!warningMatched[w]) expected.add(that(warningParts[w]).is_equal_to("<unreported>"));
1076  for (int r = 0; reportParts[r]; ++r) if (!reportMatched[r]) expected.add(that(reportParts[r]).is_equal_to("<reported, but not expected>"));
1077 
1078  return all().ofgroup(expected);
1079 }
1080 
1081 #define TEST_EXPECT_HAVE_WARNINGS(warningsReported, expectedWarnParts) do { \
1082  TEST_REJECT_NULL(warnings); \
1083  TEST_EXPECTATION(reports_these_warnings(warningsReported, expectedWarnParts)); \
1084  } while (0)
1085 
1086 #define TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS(treeString,newick,expectedWarnParts) do { \
1087  char *warnings = NULp; \
1088  TreeNode *tree = loadFromFileContaining(treeString, &warnings); \
1089  TEST_EXPECT_TREELOAD(tree, newick); \
1090  TEST_EXPECT_HAVE_WARNINGS(warnings, expectedWarnParts); \
1091  destroy(tree); \
1092  free(warnings); \
1093  } while(0)
1094 
1095 #define TEST_EXPECT_TREESTRING_OK__BROKEN(treeString,newick) do { \
1096  TreeNode *tree = loadFromFileContaining(treeString, NULp); \
1097  TEST_EXPECT_TREELOAD__BROKEN(tree, newick); \
1098  } while(0)
1099 
1100 #define LF "\n"
1101 
1102 void TEST_load_tree() {
1103  // just are few tests covering most of this module.
1104  // more load tests are in ../../TOOLS/arb_test.cxx@TEST_SLOW_arb_read_tree
1105 
1106  NewickFormat expectedFormat = nSIMPLE;
1107 
1108  // simple succeeding tree load
1109  {
1110  char *comment = NULp;
1111  TreeNode *tree = TREE_load("trees/test.tree", new SimpleRoot, &comment, false, NULp);
1112  // -> ../../UNIT_TESTER/run/trees/test.tree
1113 
1114  TEST_EXPECT_TREELOAD(tree, "(((s1,s2),(s3,s 4)),(s5,s-6));");
1115  if (tree) {
1116  TEST_REJECT_NULL(comment);
1117  TEST_EXPECT_CONTAINS(comment,
1118  // comment part from treefile:
1119  "tree covering most of tree reader code\n"
1120  "comment contains [extra brackets] inside comment\n");
1121  TEST_EXPECT_CONTAINS(comment,
1122  // comment as appended by load:
1123  ": Loaded from trees/test.tree\n");
1124  }
1125  free(comment);
1126  destroy(tree);
1127  }
1128 
1129  // detailed load tests (checking branchlengths and nodenames)
1130  {
1131  const char *treestring[] = {
1132  "(node1,node2)rootgroup;", // [0] tree with a named root
1133  "(node1:0.00,(node2, node3:0.57)):0;", // [1] test tree lengths (esp. length zero)
1134  "(((((a))single)), ((b, c)17%:0.2));", // [2] test single-node-subtree name-conflict
1135 
1136  "((a,b)17,(c,d)33.3,(e,f)12.5:0.2);", // [3] test bootstraps
1137  "((a,b)G,(c,d)H,(e,f)I:0.2);", // [4] test groupnames w/o bootstraps
1138  "((a,b)'17:G',(c,d)'33.3:H',(e,f)'12.5:I':0.2);", // [5] test groupnames with bootstraps
1139  "((a,b)17G,(c,d)33.3H,(e,f)12.5I:0.2)", // [6] test groupnames + bootstraps w/o separator -> interpreted as groupname
1140 
1141  "((a,b)'17%:G',(c,d)'33.3%:H',(e,f)'12.5%:I':0.2);", // [7] test bootstraps with percent spec
1142  "((a,b)'0.17:G',(c,d)'0.333:H',(e,f)'0.125:I':0.2);", // [8] test bootstraps in range [0..1]
1143  };
1144 
1145  const char *EXPECTED_NEWICK_3TO8 = "(((a,b),(c,d)),(e,f));";
1146 
1147  const char *expected_newick[] = {
1148  "(node1,node2);",
1149  "(node1,(node2,node3));",
1150  "(a,(b,c));",
1151 
1152  EXPECTED_NEWICK_3TO8,
1153  EXPECTED_NEWICK_3TO8,
1154  EXPECTED_NEWICK_3TO8,
1155  EXPECTED_NEWICK_3TO8,
1156 
1157  EXPECTED_NEWICK_3TO8,
1158  EXPECTED_NEWICK_3TO8,
1159  };
1160  const char *expected_warnings[] = {
1161  NULp,
1162  NULp,
1163  "Dropped group name specified for a single-node-subtree" LF "Ignored invalid bootstrap '17%' at root edge leading to a leaf",
1164 
1165  "Auto-scaling bootstrap values by factor 0.01" LF "Using comment '1250%' on both sides of root-edge",
1166  NULp,
1167  "Auto-scaling bootstrap values by factor 0.01" LF "Using comment '1250%' on both sides of root-edge",
1168  NULp,
1169 
1170  "Using comment '13%' on both sides of root-edge", // no auto-scaling shall occur here (bootstraps are already specified as percent)
1171  "Using comment '13%' on both sides of root-edge", // no auto-scaling shall occur here (bootstraps are in [0..1])
1172  };
1173 
1174  STATIC_ASSERT(ARRAY_ELEMS(expected_newick) == ARRAY_ELEMS(treestring));
1175  STATIC_ASSERT(ARRAY_ELEMS(expected_warnings) == ARRAY_ELEMS(treestring));
1176 
1177  for (size_t i = 0; i<ARRAY_ELEMS(treestring); ++i) {
1178  TEST_ANNOTATE(GBS_global_string("for tree #%zu = '%s'", i, treestring[i]));
1179  char *warnings = NULp;
1180  TreeNode *tree = loadFromFileContaining(treestring[i], &warnings);
1181  TEST_EXPECT_TREELOAD(tree, expected_newick[i]);
1182  switch (i) {
1183  case 0:
1184  TEST_EXPECT_EQUAL(tree->name, "rootgroup");
1185  break;
1186  case 1:
1187  TEST_EXPECT_EQUAL(tree->leftlen, 0);
1189  TEST_EXPECT_EQUAL(tree->rightson->rightlen, 0.57);
1190  break;
1191  case 2:
1192  TEST_EXPECT_NULL(tree->rightson->name);
1194  TEST_EXPECT_EQUAL(tree->rightlen, 0.2);
1196  break;
1197 
1198  case 3:
1199  case 4:
1200  case 5:
1201  case 6:
1202  case 7:
1203  case 8:
1204  // check bootstraps
1205  switch (i) {
1206  case 4:
1207  case 6:
1212  break;
1213  case 3:
1214  case 5:
1215  case 7:
1216  case 8:
1217  // test bootstraps with percent-specifications are parsed correctly
1218  TEST_EXPECT_EQUAL(tree->leftson->leftson->get_remark(), "17%");
1219  TEST_EXPECT_EQUAL(tree->leftson->rightson->get_remark(), "33%");
1220  TEST_EXPECT_EQUAL(tree->rightson->get_remark(), "13%");
1221  TEST_EXPECT_EQUAL(tree->leftson->get_remark(), "13%");
1222  break;
1223  default:
1224  TEST_REJECT(true); // unhandled tree
1225  break;
1226  }
1227 
1228  // check node-names
1229  TEST_EXPECT_NULL(tree->name);
1230  TEST_EXPECT_NULL(tree->leftson->name);
1231  switch (i) {
1232  case 6:
1233  // check un-separated digits are treated as strange names
1234  // (previously these were accepted as bootstraps)
1235  TEST_EXPECT_EQUAL(tree->leftson->leftson->name, "17G");
1236  TEST_EXPECT_EQUAL(tree->leftson->rightson->name, "33.3H");
1237  TEST_EXPECT_EQUAL(tree->rightson->name, "12.5I");
1238  break;
1239  case 4:
1240  case 5:
1241  case 8:
1242  case 7:
1243  TEST_EXPECT_EQUAL(tree->leftson->leftson->name, "G");
1244  TEST_EXPECT_EQUAL(tree->leftson->rightson->name, "H");
1245  TEST_EXPECT_EQUAL(tree->rightson->name, "I");
1246  break;
1247  case 3:
1250  TEST_EXPECT_NULL(tree->rightson->name);
1251  break;
1252  default:
1253  TEST_REJECT(true); // unhandled tree
1254  break;
1255  }
1256 
1257  // expect_no_lengths:
1258  TEST_EXPECT_EQUAL(tree->leftlen, 0); // multifurcation
1261  TEST_EXPECT_EQUAL(tree->rightlen, 0.2);
1262  break;
1263 
1264  default:
1265  TEST_REJECT(true); // unhandled tree
1266  break;
1267  }
1268  if (expected_warnings[i]) {
1269  TEST_EXPECT_HAVE_WARNINGS(warnings, expected_warnings[i]);
1270  }
1271  else {
1272  TEST_EXPECT_NULL(warnings);
1273  }
1274  free(warnings);
1275  destroy(tree);
1276  }
1277 
1278  TEST_ANNOTATE(NULp);
1279  }
1280 
1281  expectedFormat = nSIMPLE;
1282 
1283  // test valid trees with strange behavior
1284  TEST_EXPECT_TREESTRING_OK("(,);", "(unnamed1,unnamed2);"); // tree with 2 unamed species (weird, but ok)
1285  TEST_EXPECT_TREESTRING_OK("((a,)); ", "(a,unnamed1);");
1286  TEST_EXPECT_TREESTRING_OK("((,b)); ", "(unnamed1,b);");
1287 
1288  TEST_EXPECT_TREESTRING_OK("( a, (b,(c),d), (e,(f)) );", "((a,((b,c),d)),(e,f));");
1289  TEST_EXPECT_TREESTRING_OK("(((((a)))), ((b, c)));", "(a,(b,c));");
1290 
1291  // test group names:
1292  expectedFormat = NewickFormat(nGROUP|nREMARK);
1293 
1294  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("( (a), ((((b),(c),(d))'rem:')'duprem:group')dupgroup, ((e),(f)) );",
1295  "((a,((b,c),d)'rem:group'),(e,f));",
1296  "Dropped remark redefinition 'duprem' (edge already labeled as 'rem')" LF
1297  "Dropped group name redefinition 'dupgroup' (furcation already named 'group')");
1298 
1299  // test limits where boostraps can/cannot occur
1300  expectedFormat = nREMARK;
1301 
1302  TEST_EXPECT_TREESTRING_OK ("(a,b);", "(a,b);");
1303  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("(a,b)17%;", "(a,b);", "Remark specified for root-node ('17%') has been ignored");
1304 
1305  TEST_EXPECT_TREESTRING_OK ("((a,b),c);", "((a,b),c);");
1306  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("((a,b)23%,c);", "((a,b),c);", "Ignored invalid bootstrap '23%' at root edge leading to a leaf");
1307  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("(c,(a,b)23%);", "(c,(a,b));", "Ignored invalid bootstrap '23%' at root edge leading to a leaf");
1308  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("(c,(a,b)23%)99%;", "(c,(a,b));",
1309  "Remark specified for root-node ('99%') has been ignored" LF
1310  "Ignored invalid bootstrap '23%' at root edge leading to a leaf");
1311 
1312  TEST_EXPECT_TREESTRING_OK ("((a,b),(c,d));", "((a,b),(c,d));");
1313  // test conflicting bootstraps specified for root-edge:
1314  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("((a,b)17%,(c,d)39%);", "((a,b)'28%',(c,d)'28%');", "Root-edge has conflicting support values '17%' and '39%' -> using average '28%'");
1315  TEST_EXPECT_TREESTRING_OK ("((a,b)0.333,(c,d)0.334);", "((a,b)'33%',(c,d)'33%');"); // difference between support values is below 1% -> assume numeric error + correct silently
1316  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("((a,b)0.321,(c,d)0.345);", "((a,b)'34%',(c,d)'34%');", "Root-edge has conflicting support values '32%' and '35%' -> using average '34%'"); // unintuitive. this is caused by order of auto-scaling, rounding and root-correction
1317 
1318  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("((a,b)17%,(c,d));", "((a,b)'17%',(c,d)'17%');", "Using comment '17%' on both sides of root-edge");
1319  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("((a,b),(c,d)17%);", "((a,b)'17%',(c,d)'17%');", "Using comment '17%' on both sides of root-edge");
1320  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("((a,b)17%,(c,d)100%);", "((a,b)'17%',(c,d)'17%');", "Using comment '17%' on both sides of root-edge"); // dropping 100% is a bit surprising
1321  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("((a,b)100%,(c,d)17%);", "((a,b)'17%',(c,d)'17%');", "Using comment '17%' on both sides of root-edge"); // dito
1322  TEST_EXPECT_TREESTRING_OK ("((a,b)100%,(c,d)100%);","((a,b),(c,d));");
1323 
1324  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("((a,(b,c))32%,d);", "((a,(b,c)),d);", "Ignored invalid bootstrap '32%' at root edge leading to a leaf");
1325  TEST_EXPECT_TREESTRING_OK ("((a,(b,c)41%),d);", "((a,(b,c)'41%'),d);"); // ok. BS located at sole inner branch between (b,c) and (a,d)
1326 
1327  // test various invalid information at single-node subtrees:
1328  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("(((a)41%,(b,c)),d);", "((a,(b,c)),d);", "Dropped support-value ('0.41') specified for a single-node-subtree");
1329  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("(((b,c),(a)0.41),d);", "(((b,c),a),d);", "Dropped support-value ('0.41') specified for a single-node-subtree");
1330 
1331  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("(((b,c),(a)name),d);", "(((b,c),a),d);", "Dropped group name specified for a single-node-subtree ('name')");
1332  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("(((a)'name',(b,c)),d);", "((a,(b,c)),d);", "Dropped group name specified for a single-node-subtree ('name')");
1333  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("(((b,c),(a)'35%:name'),d);", "(((b,c),a),d);", "Dropped group name specified for a single-node-subtree ('name')" LF "Dropped support-value ('0.35') specified for a single-node-subtree");
1334  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("(((a)'remark:name',(b,c)),d);", "((a,(b,c)),d);", "Dropped group name specified for a single-node-subtree ('name')" LF "Dropped remark ('remark') specified for a single-node-subtree");
1335  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("(((b,c),(a)'remark:'),d);", "(((b,c),a),d);", "Dropped remark ('remark') specified for a single-node-subtree");
1336 
1337  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("((a, b)25)20;", "(a,b);",
1338  "Dropped support-value redefinition '20' (edge already labeled as '2500%')" LF
1339  "Remark specified for root-node ('2500%') has been ignored" LF
1340  "Auto-scaling bootstrap values by factor 0.01 (max. found bootstrap was 2500.00)");
1341  expectedFormat = nALL;
1342 
1343  // check some branchlengths:
1344  TEST_EXPECT_TREESTRING_OK("((a:0,b:5):-.9,(c:.3, d:-.0001));", "((a:0,b:5):-0.9,(c:.3,d:0):.1);");
1345  TEST_EXPECT_TREESTRING_OK("(((a,b),(c,d)),((e,f),(g,h)));", "(((a:.1,b:.1):.1,(c:.1,d:.1):.1):.1,((e:.1,f:.1):.1,(g:.1,h:.1):.1):.1);");
1346 
1347  TEST_EXPECT_TREESTRING_OK("(((a,b)'0.25:G':.6,(c,d)'17%':.5),((e,f)'r:g':.2,(g,h)g:.3));",
1348  "(((a:.1,b:.1)'25%:G':.6,(c:.1,d:.1)'17%':.5):.1,((e:.1,f:.1)'r:g':.2,(g:.1,h:.1)'g':.3):.1);");
1349 
1350  TEST_EXPECT_TREESTRING_OK("(:.2,:2);", "(unnamed1:.2,unnamed2:2);"); // tree with 2 unamed species but length (weird, but ok)
1351 
1352  // test unacceptable trees
1353  expectedFormat = nSIMPLE;
1354  {
1355  const char *tooSmallTree[] = {
1356  "();",
1357  "()",
1358  ";",
1359  "",
1360  "(one)",
1361  "((((()))));",
1362  "(((((one)))));",
1363  };
1364 
1365  for (size_t i = 0; i<ARRAY_ELEMS(tooSmallTree); ++i) {
1366  TEST_ANNOTATE(GBS_global_string("for tree #%zu = '%s'", i, tooSmallTree[i]));
1367  TreeNode *tree = loadFromFileContaining(tooSmallTree[i], NULp);
1368  TEST_EXPECT_TREELOAD_FAILED_WITH(tree, "tree is too small");
1369  }
1370  TEST_ANNOTATE(NULp);
1371  }
1372 
1373  // test invalid trees
1374  TEST_EXPECT_TREESTRING_FAILS_WITH("(;);", "Expected one of ',)'");
1375 
1376  TEST_EXPECT_TREESTRING_FAILS_WITH("(17", "Expected one of ',)' while end-of-file was reached");
1377  TEST_EXPECT_TREESTRING_FAILS_WITH("((((", "Expected one of ',)' while end-of-file was reached");
1378  TEST_EXPECT_TREESTRING_FAILS_WITH("(a, 'b", "Expected one of ',)' while end-of-file was reached");
1379  TEST_EXPECT_TREESTRING_FAILS_WITH("((a, ", "Expected one of ',)' while end-of-file was reached");
1380  TEST_EXPECT_TREESTRING_FAILS_WITH("((a,'b ", "Expected one of ',)' while end-of-file was reached");
1381 
1382  TEST_EXPECT_TREESTRING_FAILS_WITH("(a, b:5::::", "Unexpected ':' (already read a branchlength) while looking at '::::<EOF>'");
1383  TEST_EXPECT_TREESTRING_FAILS_WITH("(a, b:5:c:d", "Unexpected ':' (already read a branchlength) while looking at ':c:d<EOF>'");
1384  TEST_EXPECT_TREESTRING_FAILS_WITH("(a, b:5:c:d)", "Unexpected ':' (already read a branchlength) while looking at ':c:d)<EOF>'");
1385 
1386  TEST_EXPECT_TREESTRING_FAILS_WITH("[unclosed\ncomment", "while reading comment");
1387  TEST_EXPECT_TREESTRING_FAILS_WITH("[unclosed\ncomment [ bla ]", "while reading comment");
1388 
1389  TEST_EXPECT_TREESTRING_FAILS_WITH("(a, b:d)", "Expected valid length while looking at 'd)<EOF>'");
1390 
1391  TEST_EXPECT_TREESTRING_FAILS_WITH("((a:0,b:.),c:.3);", "cannot convert '.' to float while looking at '),c:.3);<EOF>'");
1392 
1393  expectedFormat = nALL;
1394 
1395  {
1396 #pragma GCC diagnostic push
1397 #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
1398 #define FAILS_WITH(err) NULp, NULp, err
1399  struct NodeInfo {
1400  const char *input;
1401  const char *left;
1402  const char *right;
1403  const char *failsWith; // only checks failure (warnings are ignored here)
1404  } labelTest[] = {
1405  // no/empty node-info
1406  { "", ":.1" }, // .1 is the default length used as fallback
1407  { "''", ":.1" },
1408  { "\"\"", ":.1" },
1409 
1410  { "grp", ":.1", "'grp':.1" }, // only group -> stored at right son of root
1411  { ":2", ":.1", ":2" }, // only length
1412  { "'rem:'", "'rem:':.1" }, // only remark
1413 
1414  { "17.2", "'17%':.1" }, // only bootstrap -> stored at both sons of root
1415  { "17.2%", "'17%':.1" }, // only bootstrap -> stored at both sons of root
1416  { "32.7", "'33%':.1" }, // only bootstrap -> stored at both sons of root
1417  { "32.7%", "'33%':.1" }, // only bootstrap -> stored at both sons of root
1418  { "0.572", "'57%':.1" }, // only bootstrap -> stored at both sons of root
1419  { "0.725", "'73%':.1" }, // only bootstrap -> stored at both sons of root
1420 
1421 
1422  { "grp:2", ":.1", "'grp':2" }, // group+length -> stored at right son of root
1423  { "'grp':2", ":.1", "'grp':2" }, // quoted group+length -> stored at right son of root
1424  { "'rem:':2", "'rem:':.1", "'rem:':2" }, // remark+length
1425  { "0.725:2", "'73%':.1", "'73%':2" }, // bootstrap+length -> only bootstrap is stored at both sons of root!
1426  { "'0.725':2", "'73%':.1", "'73%':2" }, // quoted bootstrap+length -> only bootstrap is stored at both sons of root!
1427 
1428  { "'rem:grp'", "'rem:':.1", "'rem:grp':.1" }, // remark+group -> stored at right son of root
1429  { "'3%:grp'", "'3%':.1", "'3%:grp':.1" }, // bootstrap+group -> only bootstrap is stored at both sons of root!
1430 
1431  { "'3%:grp':2", "'3%':.1", "'3%:grp':2" }, // bootstrap+group+length -> only bootstrap is stored at both sons of root!
1432  { "':grp':2", ":.1", "'grp':2" }, // empty remark/bootstrap+group+length
1433  { "'3%:':2", "'3%':.1", "'3%':2" }, // bootstrap+empty group+length -> only bootstrap is stored at both sons of root!
1434  { "':':2", ":.1", ":2" }, // ignores empty bootstrap+empty group; only group+length stored at right son
1435  { "':'", ":.1" }, // ignores empty bootstrap+empty group
1436 
1437  // custom remarks to display 'shalrt / ufboot' bootstraps
1438  { "'95/80:group'", "'95/80:':.1", "'95/80:group':.1" },
1439  { "'95/80:'", "'95/80:':.1" },
1440  { "'98.6 / 89.3:group'", "'98.6 / 89.3:':.1", "'98.6 / 89.3:group':.1" },
1441  { "'98.6 / 89.3:'", "'98.6 / 89.3:':.1" },
1442  { "'98.6% / 89.3%:group'", "'98.6% / 89.3%:':.1", "'98.6% / 89.3%:group':.1" },
1443  { "'98.6% / 89.3%:'", "'98.6% / 89.3%:':.1", },
1444 
1445  // wanted failures:
1446  { "grp:", FAILS_WITH("Expected valid length while looking at ');<EOF>'") },
1447  { "2%:", FAILS_WITH("Expected valid length while looking at ');<EOF>'") },
1448  { ".2:", FAILS_WITH("Expected valid length while looking at ');<EOF>'") },
1449  { "'rem:grp':", FAILS_WITH("Expected valid length while looking at ');<EOF>'") },
1450  { "'rem:':", FAILS_WITH("Expected valid length while looking at ');<EOF>'") },
1451  { "'':", FAILS_WITH("Expected valid length while looking at ');<EOF>'") },
1452 
1453  { "'2%:4%'", FAILS_WITH("Invalid group name '4%' (would be misinterpreted as plain inner node with a support value of 4% if re-imported from newick file) while looking at ');<EOF>'") },
1454  { "'2:4'", FAILS_WITH("Invalid group name '4' (would be misinterpreted as plain inner node with a support value of 4% if re-imported from newick file) while looking at ');<EOF>'") },
1455  { "'.2:.4'", FAILS_WITH("Invalid group name '.4' (would be misinterpreted as plain inner node with a support value of 40% if re-imported from newick file) while looking at ');<EOF>'") },
1456 
1457  { "'rem:grp:bad'", FAILS_WITH("Invalid group name 'grp:bad' (would be misinterpreted as group named 'bad' with remark 'grp' if re-imported from newick file) while looking at ');<EOF>'") }, // .@REMGRPBAD
1458 
1459  // unwanted failures:
1460  { "'.55:'", "'55%':.1" },
1461  { "'66%:'", "'66%':.1" },
1462  { "'.77:':.2", "'77%':.1", "'77%':.2" },
1463  { "'88%:':.3", "'88%':.1", "'88%':.3" },
1464  };
1465 #undef FAILS_WITH
1466 #pragma GCC diagnostic pop
1467 
1468  for (size_t i = 0; i<ARRAY_ELEMS(labelTest); ++i) {
1469  const NodeInfo& nodeInfo = labelTest[i];
1470  TEST_ANNOTATE(GBS_global_string("for label #%zu = <%s>", i, nodeInfo.input));
1471 
1472  char *treeString = GBS_global_string_copy("((a,b),(c,d)%s);", nodeInfo.input);
1473  TreeNode *tree = loadFromFileContaining(treeString, NULp);
1474 
1475  if (nodeInfo.failsWith) {
1476  TEST_EXPECT_TREELOAD_FAILED_WITH(tree, nodeInfo.failsWith);
1477  }
1478  else {
1479  const char *leftNodeInfo = nodeInfo.left;
1480  const char *rightNodeInfo = nodeInfo.right ? nodeInfo.right : leftNodeInfo;
1481 
1482  if (nodeInfo.right && strcmp(leftNodeInfo, rightNodeInfo) == 0) {
1483  const char *UNWANTED_CONDITION = GBS_global_string("Only specify one, if both node-infos contain the same string: <%s>", leftNodeInfo);
1484  TEST_EXPECT_NULL(UNWANTED_CONDITION);
1485  }
1486 
1487  char *newick = GBS_global_string_copy("((a:.1,b:.1)%s,(c:.1,d:.1)%s);", leftNodeInfo, rightNodeInfo);
1488  TEST_EXPECT_TREELOAD(tree, newick);
1489  free(newick);
1490  destroy(tree);
1491  }
1492  free(treeString);
1493  }
1494  TEST_ANNOTATE(NULp);
1495  }
1496 
1497  expectedFormat = nALL;
1498 
1499  // userland trees:
1500  TEST_EXPECT_TREESTRING_OK("((((A:1,B:1)'95/80:INT1':1,C:2)'99/98:INT2':0.5),(D:1,E:1)'30/40:INT3':3)'100/100';", // tree from Donovan
1501  "(((A:1,B:1)'95/80:INT1':1,C:2)'99/98:INT2':.5,(D:1,E:1)'99/98:INT3':3)'100/100';"); // @@@ should warn that remark at right son of root gets overwritten
1502 
1503  TEST_EXPECT_TREESTRING_OK("((((A:1,B:1)'95.7% / 80.3%:':1,C:2)'99.9 / 98.5:':0.5)g1,(D:1,E:1)'30.4/40.8:g2':3);", // tree from Donovan, slightly corrected: root-comment removed; mixed comments and remarks, use various remark formats
1504  "(((A:1,B:1)'95.7% / 80.3%:':1,C:2)'99.9 / 98.5:g1':.5,(D:1,E:1)'99.9 / 98.5:g2':3);"); // @@@ should warn that remark at right son of root gets overwritten
1505 
1506  // questionable accepted trees / check warnings
1507  expectedFormat = nSIMPLE;
1508  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("(a,b):0.5", "(a,b);", "Length specified for root-node has been ignored");
1509  TEST_EXPECT_TREESTRING_OK_WITH_WARNINGS("(a, b))", "(a,b);", "Unexpected input-data after tree: ')'");
1510 
1511 
1512  // questionable leaf names:
1513  TEST_EXPECT_TREESTRING_OK("(a*,b%);", "(a*,b%);"); // really accept such names?
1514  TEST_EXPECT_TREESTRING_OK("(a, b:5)", "(a,b);");
1515  TEST_EXPECT_TREESTRING_OK("(c,(b,'a:b:':.2));", "(c,(b,a:b:));"); // leaf-name is 'a:b:' here :-/
1516 
1517  // check file errors
1518  TEST_EXPECT_TREEFILE_FAILS_WITH("trees/nosuch.tree", "No such file");
1519  TEST_EXPECT_TREEFILE_FAILS_WITH("trees/corrupted.tree", "Error reading");
1520 
1521  TEST_EXPECT_ZERO_OR_SHOW_ERRNO(GB_unlink("trees/tmp.tree")); // cleanup
1522 }
1523 
1524 #endif // UNIT_TESTS
1525 
1526 // --------------------------------------------------------------------------------
GB_ERROR GB_get_error()
Definition: arb_msg.cxx:333
GB_ERROR get_warnings() const
Definition: TreeRead.cxx:132
void set_bootstrap(double bootstrap)
Definition: TreeNode.cxx:841
const char * GB_ERROR
Definition: arb_core.h:25
string result
#define MAX_NAME_LEN
group_matcher all()
Definition: test_unit.h:1011
size_t size() const
Definition: arb_strarray.h:85
AliDataPtr format(AliDataPtr data, const size_t wanted_len, GB_ERROR &error)
Definition: insdel.cxx:615
GB_ERROR GBT_write_tree_with_remark(GBDATA *gb_main, const char *tree_name, TreeNode *tree, const char *remark)
Definition: adtree.cxx:599
#define implicated(hypothesis, conclusion)
Definition: arb_assert.h:289
const TreeNode * get_root_node() const
Definition: TreeNode.h:475
static void warningToConsumingReader(const char *msg)
Definition: TreeRead.cxx:730
char * takeComment()
Definition: TreeRead.cxx:134
char * GBT_tree_2_newick(const TreeNode *tree, NewickFormat format, bool compact)
Definition: adtree.cxx:1495
GB_ERROR error
Definition: TreeRead.cxx:123
void forget_origin()
Definition: TreeNode.h:466
bool is_marked_as_default_len(GBT_LEN len)
Definition: TreeRead.h:23
GB_ERROR GB_export_IO_error(const char *action, const char *filename)
Definition: arb_msg.cxx:318
#define DEFAULT_BRANCH_LENGTH
Definition: arbdbt.h:18
const char * GBS_global_string(const char *templat,...)
Definition: arb_msg.cxx:203
#define FORWARD_FORMATTED(receiver, format)
Definition: arb_msg_fwd.h:19
bool GB_have_error()
Definition: arb_msg.cxx:338
char * release()
Definition: arb_strbuf.h:129
void cat(const char *from)
Definition: arb_strbuf.h:204
int GB_unlink(const char *path)
Definition: arb_file.cxx:188
void append_leaf_redef_details(GBS_strstruct &summary, int droppedAtLeaf, int droppedRedefined)
Definition: TreeRead.cxx:365
void use_as_remark(const SmartCharPtr &newRemark)
Definition: TreeNode.h:367
#define ARRAY_ELEMS(array)
Definition: arb_defs.h:19
char buffer[MESSAGE_BUFFERSIZE]
Definition: seq_search.cxx:34
GBT_LEN leftlen
Definition: TreeNode.h:224
TreeNode * rightson
Definition: TreeNode.h:223
void TREE_scale(TreeNode *tree, double length_scale, double bootstrap_scale, WarningConsumer warn)
Definition: TreeTools.cxx:14
double get_max_found_bootstrap() const
Definition: TreeRead.cxx:139
__ATTR__FORMAT(2) void add_warningf(const char *format
bool has_valid_root_remarks() const
Definition: TreeNode.cxx:914
static TreeReader * consumingReader
Definition: TreeRead.cxx:729
#define DEFAULT_BRANCH_LENGTH_MARKER
Definition: TreeRead.h:22
const double EPSILON
Definition: aw_position.hxx:73
void putlong(long l)
Definition: arb_strbuf.h:245
#define TEST_EXPECT_CONTAINS(str, part)
Definition: test_unit.h:1316
GB_ERROR GB_await_error()
Definition: arb_msg.cxx:342
#define MAX_READER_WARN
Definition: TreeRead.cxx:48
#define tree_assert(cond)
Definition: TreeRead.cxx:21
#define is_equal_to_NULL()
Definition: test_unit.h:1028
virtual TreeNode * makeNode() const =0
TreeReader(FILE *input, const char *file_name, TreeRoot *troot_)
Definition: TreeRead.cxx:143
static int group[MAXN+1]
Definition: ClustalV.cxx:65
void message(char *errortext)
#define TEST_REJECT(cond)
Definition: test_unit.h:1330
#define TEST_REJECT_NULL(n)
Definition: test_unit.h:1325
TreeNode * father
Definition: TreeNode.h:223
static void error(const char *msg)
Definition: mkptypes.cxx:96
TreeNode * TREE_load(const char *path, TreeRoot *troot, char **commentPtr, bool allow_length_scaling, char **warningPtr)
Definition: TreeRead.cxx:734
expectation_group & add(const expectation &e)
Definition: test_unit.h:812
#define that(thing)
Definition: test_unit.h:1043
bool parse_treelabel(const char *&label, double &bootstrap, char *&remark)
Definition: TreeNode.h:149
#define TEST_EXPECT_ZERO_OR_SHOW_ERRNO(iocond)
Definition: test_unit.h:1090
static SearchTree * tree[SEARCH_PATTERNS]
Definition: ED4_search.cxx:629
TreeNode * load()
Definition: TreeRead.cxx:94
NewickFormat
Definition: arbdb_base.h:68
NOT4PERL GB_ERROR GB_safe_atof(const char *str, float *res)
Definition: arbdb.cxx:173
TreeNode * leftson
Definition: TreeNode.h:223
char * GBS_log_action_to(const char *comment, const char *action, bool stamp)
Definition: adstring.cxx:981
#define does_differ_from_NULL()
Definition: test_unit.h:1029
GBT_LEN rightlen
Definition: TreeNode.h:224
#define is_equal_to(val)
Definition: test_unit.h:1025
void remove_remark()
Definition: TreeNode.h:376
GBT_LEN get_max_found_branchlen() const
Definition: TreeRead.cxx:140
#define does_contain(val)
Definition: test_unit.h:1040
fputs(TRACE_PREFIX, stderr)
GB_ERROR GB_export_errorf(const char *templat,...)
Definition: arb_msg.cxx:262
GB_ERROR TREE_translate_labels(GBDATA *gb_main, TreeNode *tree, const LabelTranslator &translator)
bool is_leaf() const
Definition: TreeNode.h:263
#define TEST_EXPECT_NULL(n)
Definition: test_unit.h:1322
static list< LineAttachedMessage > warnings
GB_ERROR close(GB_ERROR error)
Definition: arbdbpp.cxx:35
bool is_inner_node_with_remark() const
Definition: TreeNode.h:366
#define fulfills(pred, arg)
Definition: test_unit.h:1037
GB_ERROR TREE_load_to_db(GBDATA *gb_main, const char *treefile, const char *tree_name, const LabelTranslator &translator)
Definition: TreeRead.cxx:847
const char * name_only(const char *fullpath)
Definition: AWTI_import.cxx:46
#define __ATTR__USERESULT
Definition: attributes.h:58
char * name
Definition: TreeNode.h:226
void announce_tree_constructed()
Definition: TreeNode.h:455
static TreeNode * createLinkedTreeNode(const TreeRoot &nodeMaker, TreeNode *left, GBT_LEN leftlen, TreeNode *right, GBT_LEN rightlen)
Definition: TreeRead.cxx:595
void GBT_message(GBDATA *gb_main, const char *msg)
Definition: adtools.cxx:238
void add_drop_summary_warning_if_suppressed()
Definition: TreeRead.cxx:379
void set_remark(const char *newRemark)
Definition: TreeNode.h:372
float GBT_LEN
Definition: arbdb_base.h:34
#define NULp
Definition: cxxforward.h:116
void add_warning(const char *msg)
Definition: TreeRead.cxx:125
void GBT_split_string(ConstStrArray &dest, const char *namelist, const char *separator, SplitMode mode)
Definition: arb_strarray.h:223
void markAsLeaf()
Definition: TreeNode.h:264
const char * get_data() const
Definition: arb_strbuf.h:120
#define is_more_or_equal(val)
Definition: test_unit.h:1035
GB_transaction ta(gb_var)
GB_ERROR GBT_check_valid_group_name(const char *new_group_name)
Definition: adtree.cxx:230
void destroy(TreeNode *that)
Definition: TreeNode.h:667
GBDATA * gb_main
Definition: adname.cxx:32
const char * get_remark() const
Definition: TreeNode.h:357
const SmartCharPtr & get_remark_ptr() const
Definition: TreeNode.h:362
void delete_by_node()
Definition: TreeNode.h:106
#define STATIC_ASSERT(const_expression)
Definition: static_assert.h:37
#define NAME_TOO_LONG
#define TEST_EXPECT_EQUAL(expr, want)
Definition: test_unit.h:1294
size_t get_position() const
Definition: arb_strbuf.h:112
char * GBS_global_string_copy(const char *templat,...)
Definition: arb_msg.cxx:194
const char * label
void put(char c)
Definition: arb_strbuf.h:179
#define max(a, b)
Definition: f2c.h:154
GB_write_int const char s
Definition: AW_awar.cxx:154