source: orange/source/orange/tabdelim.cpp @ 9431:7fc716597428

Revision 9431:7fc716597428, 35.5 KB checked in by matija <matija.polajnar@…>, 2 years ago (diff)

Support for writing multiclass tab files.

Line 
1/*
2    This file is part of Orange.
3   
4    Copyright 1996-2010 Faculty of Computer and Information Science, University of Ljubljana
5    Contact: janez.demsar@fri.uni-lj.si
6
7    Orange is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation, either version 3 of the License, or
10    (at your option) any later version.
11
12    Orange is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with Orange.  If not, see <http://www.gnu.org/licenses/>.
19*/
20
21
22#include <string>
23#include <vector>
24#include <list>
25#include <map>
26
27#include <math.h>
28#include "stladdon.hpp"
29#include "strings.hpp"
30#include "getarg.hpp"
31
32#include "values.hpp"
33#include "vars.hpp"
34#include "stringvars.hpp"
35#include "pythonvars.hpp"
36#include "domain.hpp"
37#include "examples.hpp"
38
39#include "tabdelim.ppp"
40
41int readTabAtom(TFileExampleIteratorData &fei, vector<string> &atoms, bool escapeSpaces=true, bool csv = false, bool allowEmpty=false);
42bool atomsEmpty(const vector<string> &atoms);
43
44
45const TTabDelimExampleGenerator::TIdentifierDeclaration TTabDelimExampleGenerator::typeIdentifiers[] =
46 {{"discrete", 0, TValue::INTVAR},      {"d", 0, TValue::INTVAR},
47  {"continuous", 0, TValue::FLOATVAR},  {"c", 0, TValue::FLOATVAR},
48  {"string", 0, STRINGVAR},             {"s", 0, STRINGVAR},
49  {"python", 0, PYTHONVAR},             {"python:", 7, PYTHONVAR},
50  {NULL, 0}};
51
52
53TTabDelimExampleGenerator::TTabDelimExampleGenerator(const TTabDelimExampleGenerator &old)
54: TFileExampleGenerator(old),
55  attributeTypes(mlnew TIntList(old.attributeTypes.getReference())),
56  classPos(old.classPos),
57  headerLines(old.headerLines),
58  csv(old.csv)
59{}
60
61
62TTabDelimExampleGenerator::TTabDelimExampleGenerator(const string &afname, bool autoDetect, bool acsv, const int createNewOn, vector<int> &status, vector<pair<int, int> > &metaStatus, const char *aDK, const char *aDC, bool noCodedDiscrete, bool noClass)
63: TFileExampleGenerator(afname, PDomain()),
64  attributeTypes(mlnew TIntList()),
65  DK(aDK ? strcpy((char *)malloc(strlen(aDK)+1), aDK) : NULL),
66  DC(aDC ? strcpy((char *)malloc(strlen(aDC)+1), aDC) : NULL),
67  classPos(-1),
68  headerLines(0),
69  csv(acsv)
70{ 
71  // domain needs to be initialized after attributeTypes, classPos, headerLines
72  domain = readDomain(afname, autoDetect, createNewOn, status, metaStatus, noCodedDiscrete, noClass);
73
74  TFileExampleIteratorData fei(afname);
75 
76  vector<string> atoms;
77  for (int i = headerLines; !feof(fei.file) && i--; )
78    // read one line (not counting comment lines, but the flag line may be empty)
79    while(!feof(fei.file) && (readTabAtom(fei, atoms, true, csv, (headerLines==3) && !i) == -1));
80
81  startDataPos = ftell(fei.file);
82  startDataLine = fei.line;
83}
84
85
86TTabDelimExampleGenerator::~TTabDelimExampleGenerator()
87{
88  if (DK)
89    free(DK);
90
91  if (DC)
92    free(DC);
93}
94
95bool TTabDelimExampleGenerator::readExample(TFileExampleIteratorData &fei, TExample &exam)
96{
97  vector<string> atoms;
98  // read lines until eof or a non-empty line
99  while(!feof(fei.file) && ((readTabAtom(fei, atoms, true, csv)>0) || atomsEmpty(atoms))) {
100    vector<string>::iterator ii(atoms.begin()), ie(atoms.end());
101    while ((ii!=ie) && !(*ii).length())
102      ii++;
103    if (ii==ie)
104      atoms.clear();
105    else
106      break;
107  }
108 
109  if (!atoms.size())
110    return false;
111
112  // Add an appropriate number of empty atoms, if needed
113  while (atoms.size()<attributeTypes->size())
114    atoms.push_back(string(""));
115  _ASSERT(exam.domain==domain);
116
117  exam.removeMetas();
118
119  TExample::iterator ei(exam.begin());
120  TVarList::iterator vi(domain->attributes->begin());
121  vector<string>::iterator ai(atoms.begin());
122  TIntList::iterator si(attributeTypes->begin()), se(attributeTypes->end());
123  TIntList::iterator cb, cp, ce;
124  if (classPoses) {
125      cb = cp = classPoses->begin();
126      ce = classPoses->end();
127  }
128  int pos=0;
129  for (; (si!=se); pos++, si++, ai++) {
130    if (*si) { // if attribute is not to be skipped and is not a basket
131      string valstr;
132
133      // Check for don't care
134      valstr = *ai;
135      if (!valstr.length() || (valstr == "NA") || (valstr == ".") || (DC && (valstr == DC)))
136        valstr = "?";
137      else if ((valstr == "*") || (DK && (valstr == DK)))
138        valstr = "~";
139
140      try {
141        if (*si==-1)
142          if (pos==classPos) { // if this is class value
143            TValue cval;
144            domain->classVar->filestr2val(valstr, cval, exam);
145            exam.setClass(cval);
146          }
147          else if (classPoses && (cp != ce) && (pos == *cp)) {
148              const int ind = cp - cb;
149              domain->classVars->at(ind)->filestr2val(valstr, exam.values_end[ind], exam);
150              cp++;
151          }
152          else { // if this is a normal value
153            (*vi++)->filestr2val(valstr, *ei++, exam);
154          }
155        else { // if this is a meta value
156          TMetaDescriptor *md = domain->metas[*si];
157          _ASSERT(md!=NULL);
158          TValue mval;
159          md->variable->filestr2val(valstr, mval, exam);
160
161          exam.setMeta(*si, mval);
162        }
163      }
164      catch (mlexception &err) {
165        raiseError("file '%s', line '%i': %s", fei.filename.c_str(), fei.line, err.what());
166      }
167    }
168
169    // the attribute is marked to be skipped, but may also be a basket
170    else { 
171      if (pos == basketPos) {
172        TSplits splits;
173        split(*ai, splits);
174        ITERATE(TSplits, si, splits)
175          basketFeeder->addItem(exam, string(si->first, si->second), fei.line);
176      }
177    }
178  }
179
180  if (pos==classPos) // if class is the last value in the line, it is set here
181    domain->classVar->filestr2val(ai==atoms.end() ? "?" : *(ai++), exam[domain->variables->size()-1], exam);
182  /* I'm not sure that this is needed; this code is a mess but I don't wish to
183     waste time studying it since we are moving to 3.0 */
184  else if (classPoses && (cp != ce) && (pos == *cp)) {
185    const int ind = cp - cb;
186    domain->classVars->at(ind)->filestr2val(ai==atoms.end() ? "?" : *(ai++), exam.values_end[ind], exam);
187  }
188
189  while ((ai!=atoms.end()) && !(*ai).length()) ai++; // line must be empty from now on
190
191  if (ai!=atoms.end()) {
192    vector<string>::iterator ii=atoms.begin();
193    string s=*ii;
194    while(++ii!=atoms.end()) s+=" "+*ii;
195    raiseError("example of invalid length (%s)", s.c_str());
196  }
197
198  return true;
199}
200
201
202char *TTabDelimExampleGenerator::mayBeTabFile(const string &stem)
203{
204  vector<string> varNames, atoms;
205  vector<string>::const_iterator vi, ai, ei;
206
207  TFileExampleIteratorData fei(stem);
208
209  // if there is no names line, it is not .tab
210  while(!feof(fei.file) && (readTabAtom(fei, varNames, true, csv)==-1));
211  if (varNames.empty()) {
212    char *res = mlnew char[128];
213    res = strcpy(res, "empty file");
214    return res;
215  }
216
217  // if any name contains the correct hash formatting it is not tab-delim it's more likely .txt
218  for(vi = varNames.begin(), ei = varNames.end(); vi!=ei; vi++) {
219    const char *c = (*vi).c_str();
220    if ((*c=='m') || (*c=='c') || (*c=='i'))
221      c++;
222    if (   ((*c=='D') || (*c=='C') || (*c=='S'))
223        && (c[1]=='#')) {
224      char *res= mlnew char[128 + (*vi).size()];
225      sprintf(res, "attribute name '%s' looks suspicious", (*vi).c_str());
226      return res;
227    }
228  }
229
230  // if there is no var types line, it is not .tab
231  while(!feof(fei.file) && (readTabAtom(fei, atoms, true, csv)==-1));
232  if (atoms.empty()) {
233    char *res = mlnew char[128];
234    res = strcpy(res, "no line with attribute types");
235    return res;
236  }
237
238  if (atoms.size() != varNames.size())
239    raiseError("the number of attribute types does not match the number of attributes");
240
241  // Each atom must be either 'd', 'c' or 's', or contain a space
242  for(vi = varNames.begin(), ai = atoms.begin(), ei = atoms.end(); ai != ei; ai++, vi++) {
243    const char *c = (*ai).c_str();
244    if (!*c) {
245      char *res= mlnew char[128 + (*vi).size()];
246      sprintf(res, "empty type entry for attribute '%s'", (*vi).c_str());
247      return res;
248    }
249
250    if (!strcmp("basket", c))
251      continue;
252
253    const TIdentifierDeclaration *tid = typeIdentifiers;
254    for(; tid->identifier && (tid->matchRoot ? strncmp(tid->identifier, c, tid->matchRoot) : strcmp(tid->identifier, c)); tid++);
255    if (tid->identifier)
256      continue;
257
258    for(; *c && (*c!=' '); c++);
259      if (!*c) {
260        char *res= mlnew char[128 + (*vi).size() + (*ai).size()];
261        sprintf(res, "attribute '%s' is defined as having only one value ('%s')", (*vi).c_str(), (*ai).c_str());
262        return res;
263      }
264  }
265
266  // if there is no flags line, it is not .tab
267  while(!feof(fei.file) && (readTabAtom(fei, atoms, true, csv, true)==-1));
268  if (feof(fei.file)) {
269    char *res = mlnew char[128];
270    res = strcpy(res, "file has only two lines");
271    return res;
272  }
273
274  if (atoms.size() > varNames.size())
275    raiseError("the number of attribute options is greater than the number of attributes");
276
277  // Check flags
278  for(vi = varNames.begin(), ai = atoms.begin(), ei = atoms.end(); ai != ei; ai++, vi++) {
279    TProgArguments args("dc: ordered", *ai, false, true);
280
281/*  Not any more: now they go into the Variable's dictionary
282
283    if (args.unrecognized.size()) {
284      char *res= mlnew char[128 + (*vi).size()];
285      sprintf(res, "unrecognized options at attribute '%s'", (*vi).c_str());
286      return res;
287    }
288*/
289    if (args.direct.size()) {
290      if (args.direct.size()>1) {
291        char *res= mlnew char[128 + (*vi).size()];
292        sprintf(res, "too many direct options at attribute '%s'", (*vi).c_str());
293        return res;
294      }
295
296      static const char *legalDirects[] = {"s", "skip","i", "ignore", "c", "class", "multiclass", "m", "meta", NULL};
297      string &direct = args.direct.front();
298      const char **lc = legalDirects;
299      while(*lc && strcmp(*lc, direct.c_str()))
300        lc++;
301      if (!*lc) {
302        char *res= mlnew char[128 + (*vi).size() + (*ai).size()];
303        sprintf(res, "unrecognized option ('%s') at attribute '%s'", (*ai).c_str(), (*vi).c_str());
304        return res;
305      }
306    }
307  }
308
309  return NULL;
310}
311
312PDomain TTabDelimExampleGenerator::readDomain(const string &stem, const bool autoDetect, const int createNewOn, vector<int> &status, vector<pair<int, int> > &metaStatus, bool noCodedDiscrete, bool noClass)
313{ 
314  // non-NULL when this cannot be tab file (reason given as result)
315  // NULL if this seems a valid tab file
316  char *isNotTab = mayBeTabFile(stem);
317
318  TDomainDepot::TAttributeDescriptions descriptions;
319 
320  if (autoDetect) {
321    if (!isNotTab)
322      raiseWarning("'%s' is being loaded as .txt, but could be .tab file", stem.c_str());
323    readTxtHeader(stem, descriptions);
324  }
325  else {
326    if (isNotTab)
327      raiseWarning("'%s' is being loaded as .tab, but looks more like .txt file\n(%s)", stem.c_str(), isNotTab);
328    readTabHeader(stem, descriptions);
329  }
330
331  if (isNotTab)
332    mldelete isNotTab;
333
334  scanAttributeValues(stem, descriptions);
335 
336  TIntList::iterator ati(attributeTypes->begin());
337  TDomainDepot::TPAttributeDescriptions attributeDescriptions, metaDescriptions, classDescriptions;
338  int ind = 0, lastRegular = -1;
339  TIntList::const_iterator cp, ce;
340  if (classPoses) {
341      cp = classPoses->begin();
342      ce = classPoses->end();
343  }
344
345  for(TDomainDepot::TAttributeDescriptions::iterator adi(descriptions.begin()), ade(descriptions.end()); adi != ade; adi++, ati++, ind++) {
346    if (!*ati)
347      continue;
348     
349    if (adi->varType == -1) {
350      switch (detectAttributeType(*adi, noCodedDiscrete)) {
351        case 0:
352        case 2:
353          adi->varType = TValue::INTVAR;
354          break;
355         
356        case 1:
357          adi->varType = TValue::FLOATVAR;
358          break;
359
360        case 4:
361          adi->varType = STRINGVAR;
362          *ati = 1;
363          break;
364
365        default:
366          raiseWarning("cannot determine type for attribute '%s'; the attribute will be ignored", adi->name.c_str());
367          *ati = 0;
368          continue;
369        }
370    }
371   
372    if (*ati == 1)
373      metaDescriptions.push_back(&*adi);
374    else if (classPoses && (cp != ce) && (*cp == ind)) {
375        classDescriptions.push_back(&*adi);
376        cp++;
377    }
378    else if ((classPos != ind) && (basketPos != ind)) {
379      attributeDescriptions.push_back(&*adi);
380      lastRegular = ind;
381    }
382  }
383 
384  if (classPos > -1)
385    attributeDescriptions.push_back(&descriptions[classPos]);
386  else if (autoDetect && !noClass)
387    classPos = lastRegular;
388   
389  if (basketPos >= 0)
390//    basketFeeder = mlnew TBasketFeeder(sourceDomain, createNewOn == TVariable::OK, false);
391    basketFeeder = mlnew TBasketFeeder(PDomain(), createNewOn == TVariable::OK, false);
392   
393/*  if (sourceDomain) {
394    if (!domainDepot_tab.checkDomain(sourceDomain.AS(TDomain), &attributeDescriptions, classPos >= 0, NULL))
395      raiseError("given domain does not match the file");
396
397    if (basketFeeder)
398      basketFeeder->domain = sourceDomain;
399    return sourceDomain;
400  }
401*/
402  PDomain newDomain = domainDepot.prepareDomain(&attributeDescriptions, classPos>-1, &classDescriptions, &metaDescriptions, createNewOn, status, metaStatus);
403
404  vector<pair<int, int> >::const_iterator mid(metaStatus.begin());
405  PITERATE(TIntList, ii, attributeTypes)
406    if (*ii == 1)
407      *ii = mid++ ->first;
408
409  if (basketFeeder)
410    basketFeeder->domain = newDomain;
411
412  return newDomain;
413}
414
415
416
417int TTabDelimExampleGenerator::detectAttributeType(TDomainDepot::TAttributeDescription &desc, const bool noCodedDiscrete)
418{
419  char numTest[64];
420
421  int status = 3;  //  3 - not encountered any values, 2 - can be coded discrete, 1 - can be float, 0 - must be nominal
422                   //  4 (set later) - string value
423  typedef map<string, int> msi;
424  ITERATE(msi, vli, desc.values) {
425
426    if (vli->first.length() > 63) {
427      status = 0;
428      break;
429    }
430   
431    const char *ceni = vli->first.c_str();
432    if (   !*ceni
433        || !ceni[1] && ((*ceni=='?') || (*ceni=='.') || (*ceni=='~') || (*ceni=='*'))
434        || !strcmp(ceni, "NA") || (DC && !strcmp(ceni, DC)) || (DK && !strcmp(ceni, DK)))
435      continue;
436   
437    if (status == 3)
438      status = 2;
439
440    if ((status == 2) && (ceni[1] || (*ceni<'0') || (*ceni>'9')))
441      status = noCodedDiscrete ? 2 : 1;
442     
443    if (status == 1) {
444      strcpy(numTest, ceni);
445      for(char *sc = numTest; *sc; sc++)
446        if (*sc == ',')
447          *sc = '.';
448
449      char *eptr;
450      strtod(numTest, &eptr);
451      while (*eptr==32)
452        eptr++;
453      if (*eptr) {
454        status = 0;
455        break;
456      }
457    }
458  }
459 
460  /* Check whether this is a string attribute:
461     - has more than 20 values
462     - less than half of the values appear more than once */
463  if ((status==0) && (desc.values.size() > 20)) {
464      int more2 = 0;
465      for(map<string, int>::const_iterator dvi(desc.values.begin()), dve(desc.values.end()); dvi != dve; dvi++) {
466        if (dvi->second > 1)
467          more2++;
468      }
469      if (more2*2 < desc.values.size()) {
470        status = 4;
471      }
472  }
473  return status;
474}
475
476
477
478
479/* These are the rules for determining the attribute types.
480
481   There are three ways to determine a type.
482
483   1. By header prefixes to attribute names.
484      The prefix is formed by [cmi][DCS]#
485      c, m and i mean class attribute, meta attribute and ignore,
486      respectively.
487      D, C and S mean discrete, continuous and string attributes.
488
489
490!!! NOT TRUE:
491
492   2. By knownVars.
493      If the type is not determined from header row (either because
494      there was no prefix or it only contained c, m or i)
495      knownVars is checked for the attribute with the same name.
496      If found, the attribute from knownVars will be used.
497
498   3. From the data.
499      These attributes can be either continuous or discrete.
500      The file is parsed and values for each attribute are checked.
501      Values denoting undefined values ('?', '.', '~', '*', 'NA' and
502      empty strings) are ignored.
503      If all values can be parsed as numbers, the attribute is continuous.
504      An exception to this rule are attributes with values 0, 1, 2, ..., 9.
505      These are treated as discrete (the assumption is that those number
506      are just codes for otherwise discrete values).
507*/
508
509
510void TTabDelimExampleGenerator::scanAttributeValues(const string &stem, TDomainDepot::TAttributeDescriptions &desc)
511{
512  TFileExampleIteratorData fei(stem);
513
514  vector<string> atoms;
515  vector<string>::const_iterator ai, ae;
516  TDomainDepot::TAttributeDescriptions::iterator di, db(desc.begin()), de(desc.end());
517  TIntList::const_iterator ati, atb(attributeTypes->begin());
518
519  for (int i = headerLines; !feof(fei.file) && i--; )
520    while(!feof(fei.file) && (readTabAtom(fei, atoms, true, csv, (headerLines==3) && !i) == -1));
521
522  while (!feof(fei.file)) {
523    if (readTabAtom(fei, atoms, true, csv) <= 0)
524      continue;
525   
526    for(di = db, ati = atb, ai = atoms.begin(), ae = atoms.end(); (di != de) && (ai != ae); di++, ai++, ati++) {
527      if (!*atb)
528        continue;
529   
530      //skip the attribute if it is a FLOATVAR or STRINGVAR
531      if ((di->varType != TValue::FLOATVAR) && (di->varType != STRINGVAR)) {
532
533        const char *ceni = ai->c_str();
534        if (   !*ceni
535            || !ceni[1] && ((*ceni=='?') || (*ceni=='.') || (*ceni=='~') || (*ceni=='*'))
536            || (*ai == "NA") || (DC && (*ai == DC)) || (DK && (*ai == DK)))
537           continue;
538
539        map<string, int>::iterator vf = di->values.lower_bound(*ai);
540        if ((vf != di->values.end()) && (vf->first == *ai)) {
541          vf->second++;
542        }
543        else {
544          di->values.insert(vf, make_pair(*ai, 1));
545        }
546      }
547    }
548  }
549}
550
551
552void TTabDelimExampleGenerator::readTxtHeader(const string &stem, TDomainDepot::TAttributeDescriptions &descs)
553{ 
554  TFileExampleIteratorData fei(stem);
555
556  vector<string> varNames;
557  while(!feof(fei.file) && (readTabAtom(fei, varNames, true, csv)==-1));
558  if (varNames.empty())
559    ::raiseError("unexpected end of file '%s' while searching for attribute names", fei.filename.c_str());
560
561  headerLines = 1;
562  classPos = -1;
563  basketPos = -1;
564  attributeTypes = mlnew TIntList(varNames.size(), -1);
565  TIntList::iterator attributeType(attributeTypes->begin());
566  vector<string>::const_iterator ni(varNames.begin()), ne(varNames.end());
567  int ind = 0;
568 
569  for(; ni != ne; ni++, ind++, attributeType++) {
570    /* Parses the header line
571       - sets *ni to a real name (without prefix)
572       - sets varType to TValue::varType or -1 if the type is not specified and -2 if it's a basket
573       - sets classPos/basketPos to the current position, if the attribute is class/basket attribute
574         (and reports an error if there is more than one such attribute)
575       - to attributeTypes, appends -1 for ordinary atributes, 1 for metas and 0 for ignored or baskets*/
576    int varType = -1; // varType, or -1 for unnown, -2 for basket
577
578    const char *cptr = (*ni).c_str();
579    if (*cptr && (cptr[1]=='#') || (cptr[2] == '#')) {
580      if (*cptr == 'm') {
581        *attributeType = 1;
582        cptr++;
583      }
584      else if (*cptr == 'i') {
585        *attributeType = 0;
586        cptr++;
587      }
588      else if (*cptr == 'c') {
589        if (classPos>-1)
590          ::raiseError("more than one attribute marked as class");
591        else
592          classPos = ind;
593        cptr++;
594      }
595     
596      // we may have encountered a m, i or c, so cptr points to the second character,
597      // or it can still point to the first
598      if (*cptr == 'D') {
599        varType = TValue::INTVAR;
600        cptr++;
601      }
602      else if (*cptr == 'C') {
603        varType = TValue::FLOATVAR;
604        cptr++;
605      }
606      else if (*cptr == 'S') {
607        varType = STRINGVAR;
608        cptr++;
609      }
610      else if (*cptr == 'B') {
611        varType = -2;
612        if ((*attributeType != -1) || (classPos == ind))
613          ::raiseError("flag 'B' is incompatible with 'i', 'm' and 'c'");
614        *attributeType = 0;
615        if (basketPos > -1)
616          ::raiseError("more than one basket attribute");
617        else
618          basketPos = ind;
619        cptr++;
620      }
621     
622      if (*cptr != '#')     
623        ::raiseError("unrecognized flags in attribute name '%s'", cptr);
624      cptr++;
625    }
626
627    descs.push_back(TDomainDepot::TAttributeDescription(cptr, varType));
628  }
629}
630
631
632
633void TTabDelimExampleGenerator::readTabHeader(const string &stem, TDomainDepot::TAttributeDescriptions &descs)
634{
635  classPos = -1;
636  classPoses = mlnew TIntList;
637  basketPos = -1;
638  headerLines = 3;
639
640  TFileExampleIteratorData fei(stem);
641 
642  vector<string> varNames, varTypes, varFlags;
643 
644  while(!feof(fei.file) && (readTabAtom(fei, varNames, true, csv) == -1));
645  if (varNames.empty())
646    ::raiseError("empty file");
647
648  while(!feof(fei.file) && (readTabAtom(fei, varTypes, false, csv) == -1));
649  if (varTypes.empty())
650    ::raiseError("cannot read types of attributes");
651
652  while(!feof(fei.file) && (readTabAtom(fei, varFlags, true, csv, true) == -1));
653
654  if (varNames.size() != varTypes.size())
655    ::raiseError("mismatching number of attributes and their types.");
656  if (varNames.size() < varFlags.size())
657    ::raiseError("too many flags (third line too long)");
658  while (varFlags.size() < varNames.size())
659    varFlags.push_back("");
660
661  attributeTypes = mlnew TIntList(varNames.size(), -1);
662
663  vector<string>::iterator vni(varNames.begin()), vne(varNames.end());
664  vector<string>::iterator ti(varTypes.begin());
665  vector<string>::iterator fi(varFlags.begin()), fe(varFlags.end());
666  TIntList::iterator attributeType(attributeTypes->begin());
667  int ind = 0;
668 
669  for(; vni!=vne; fi++, vni++, ti++, attributeType++, ind++) {
670 
671    descs.push_back(TDomainDepot::TAttributeDescription(*vni, 0));
672    TDomainDepot::TAttributeDescription &desc = descs.back();
673
674    bool ordered = false;
675
676    if (fi!=fe) {
677      TProgArguments args("dc: ordered", *fi, false, true);
678
679      if (args.direct.size()) {
680     
681        if (args.direct.size()>1)
682          ::raiseError("invalid flags for attribute '%s'", (*vni).c_str());
683         
684        string direct = args.direct.front();
685        if ((direct=="s") || (direct=="skip") || (direct=="i") || (direct=="ignore"))
686          *attributeType = 0;
687
688        else if ((direct=="c") || (direct=="class")) {
689          if (classPos != -1)
690            ::raiseError("multiple attributes are specified as class attribute ('%s' and '%s')", (*vni).c_str(), (*vni).c_str());
691          classPos = ind;
692        }
693       
694        else if (direct=="multiclass") {
695          classPoses->push_back(ind);
696        }
697       
698        else if ((direct=="m") || (direct=="meta"))
699          *attributeType = 1;
700      }
701
702      ITERATE(TMultiStringParameters, mi, args.options)
703        if ((*mi).first == "dc")
704          raiseWarning("argument -dc is not supported any more");
705
706      ordered = args.exists("ordered");
707
708      desc.userFlags = args.unrecognized;
709    }
710   
711    if (!strcmp((*ti).c_str(), "basket")) {
712      if (basketPos > -1)
713        ::raiseError("multiple basket attributes are defined");
714      if (ordered || (classPos == ind) || (*attributeType != -1))
715        ::raiseError("'basket' flag is incompatible with other flags");
716      basketPos = ind;
717      *attributeType = 0;
718    }
719
720    if (!*attributeType)
721      continue;
722
723    if (!(*ti).length())
724      ::raiseError("type for attribute '%s' is missing", (*vni).c_str());
725
726    const TIdentifierDeclaration *tid = typeIdentifiers;
727    for(; tid->identifier; tid++)
728      if (!(tid->matchRoot ? strncmp(tid->identifier, (*ti).c_str(), tid->matchRoot)
729                           : strcmp(tid->identifier, (*ti).c_str()))) {
730        desc.varType = tid->varType;
731        desc.typeDeclaration = *ti;
732        break;
733      }
734     
735    if (!tid->identifier) {
736      desc.varType = TValue::INTVAR;
737
738      string vals;
739      ITERATE(string, ci, *ti) {
740        if (*ci==' ') {
741          if (vals.length())
742            desc.addValue(vals);
743          vals="";
744        }
745        else {
746          if ((*ci=='\\') && (ci[1]==' ')) {
747            vals += ' ';
748            ci++;
749          }
750          else
751            vals += *ci;
752        }
753      }
754
755      if (vals.length())
756        desc.addValue(vals);
757    }
758  }
759
760  if (!classPoses->size())
761      classPoses = PIntList();
762}
763
764
765bool atomsEmpty(const vector<string> &atoms)
766{ const_ITERATE(vector<string>, ai, atoms)
767    if ((*ai).length())
768      return false;
769  return true;
770}
771
772
773int trimAtomsList(vector<string> &atoms)
774{
775  if (!atoms.size())
776    return 0;
777
778  vector<string>::iterator ei(atoms.end()-1), bi(atoms.begin());
779  for(; !(*ei).length() && ei!=bi; ei--);
780  if (!(*ei).length())
781    atoms.clear();
782  else
783    atoms.erase(++ei, atoms.end());
784  return atoms.size();
785}
786
787/*  Reads a list of atoms from a line of tab or comma delimited file. Atom consists of any characters
788    except \n, \r and \t (and ',' if csv=true). Multiple spaces are replaced by a single space. Atoms
789    are separated by \t or ',' if csv=true. Lines end with \n or \r. Lines which begin with | are ignored.
790   
791    Returns number of atoms, -1 for comment line and -2 for EOF
792    */
793int readTabAtom(TFileExampleIteratorData &fei, vector<string> &atoms, bool escapeSpaces, bool csv, bool allowEmpty)
794{
795  atoms.clear();
796
797  if (!fei.file)
798    raiseErrorWho("TabDelimExampleGenerator", "file not opened");
799
800  if (feof(fei.file))
801    return -2;
802
803  fei.line++;
804
805  char c, c2;
806  int col = 0;
807  string atom;
808  for(;;) {
809    c = fgetc(fei.file);
810
811    if (c==(char)EOF)
812      break;
813    if (!col && (c=='|')) {
814      for (c=fgetc(fei.file); (c!='\r') && (c!='\n') && (c!=(char)EOF); c=fgetc(fei.file));
815      return -1;
816    }
817
818    col++;
819
820    switch(c) {
821      case '\r':
822      case '\n':
823        c2 = fgetc(fei.file);
824        if ((c2!='\r') && (c2!='\n') || (c2 == c))
825          ungetc(c2, fei.file);
826        if (atom.length() || atoms.size())
827          atoms.push_back(trim(atom));  // end of line
828        if (allowEmpty || atoms.size())
829          return trimAtomsList(atoms);
830        break;
831
832      case '\t':
833        atoms.push_back(trim(atom));
834        atom.clear();
835        break;
836
837      case ',':
838        if (csv) {
839          atoms.push_back(trim(atom));
840          atom.clear();
841          break;
842        }
843        // else fallthrough
844
845      case ' ':
846        atom += c;
847        break;
848
849      case '\\':
850        if (escapeSpaces) {
851          c = fgetc(fei.file);
852          if (c != ' ')
853            atom += '\\';
854        }
855
856      default:
857        // trim left
858        if ((c>=' ') || (c<0))
859          atom += c;
860    };
861  }
862 
863  if (ferror(fei.file))
864    raiseErrorWho("TabDelimExampleGenerator", "error while reading line %i of file '%s'", fei.line, fei.filename.c_str());
865
866  if (atom.length() || atoms.size())
867    atoms.push_back(csv ? trim(atom) : atom);
868
869  return trimAtomsList(atoms);
870}
871
872
873
874
875// ********* Output ********* //
876
877
878#define PUTDELIM { if (ho) putc(delim, file); else ho = true; }
879
880void tabDelim_writeExample(FILE *file, const TExample &ex, char delim)
881{ 
882}
883
884
885inline const char *checkCtrl(const char *c) {
886  for(const char *cc = c; *cc; cc++)
887    if ((const unsigned char)(*cc) < 32)
888      raiseErrorWho("write", "string '%s' cannot be written to a file since it contains invalid characters", c);
889  return c;
890}
891
892void tabDelim_writeExamples(FILE *file, PExampleGenerator rg, char delim, const char *DK, const char *DC)
893{ 
894  const TDomain domain = rg->domain.getReference();
895  TVarList::const_iterator vb(domain.variables->begin()), vi, ve(domain.variables->end());
896  TVarList::const_iterator cvb(domain.classVars->begin()), cve(domain.classVars->end());
897
898  PEITERATE(ex, rg) {
899    TExample::const_iterator ri((*ex).begin());
900    string st;
901    bool ho = false;
902
903    for(vi=vb; vi!=cve; vi++, ri++) {
904      if (vi==ve) {
905        vi = cvb;
906        if (vi==cve) break;
907      }
908      PUTDELIM;
909      if (DK && ((*ri).valueType == valueDK))
910        fprintf(file, DK);
911      else if (DC && ((*ri).valueType == valueDC))
912        fprintf(file, DC);
913      else {
914        (*vi)->val2filestr(*ri, st, *ex);
915        fprintf(file, checkCtrl(st.c_str()));
916      }
917    }
918
919    TMetaVector::const_iterator mb((*ex).domain->metas.begin()), mi, me((*ex).domain->metas.end());
920
921    for(mi = mb; mi != me; mi++) {
922      if (!(*mi).optional) {
923        PUTDELIM;
924        if (DK && ((*ri).valueType == valueDK))
925          fprintf(file, DK);
926        else if (DC && ((*ri).valueType == valueDC))
927          fprintf(file, DC);
928        else {
929          (*mi).variable->val2filestr((*ex)[(*mi).id], st, *ex);
930          fprintf(file, "%s", checkCtrl(st.c_str()));
931        }
932      }
933    }
934   
935    bool first = true;
936    for(mi = mb; mi != me; mi++) {
937      if ((*mi).optional) {
938        const TVariable &var = (*mi).variable.getReference();
939        if ((var.varType == TValue::FLOATVAR) && (*ex).hasMeta((*mi).id)) {
940          const TValue &mval = (*ex).getMeta((*mi).id);
941          if (!mval.isSpecial()) {
942            if (first) {
943              PUTDELIM;
944              first = false;
945            }
946            else
947              fprintf(file, " ");
948
949            if (mval.floatV == 1.0)
950              fprintf(file, checkCtrl(var.get_name().c_str()));
951            else {
952              var.val2filestr(mval, st, *ex);
953              fprintf(file, "%s=%s", checkCtrl(var.get_name().c_str()), checkCtrl(st.c_str()));
954            }
955          }
956        }
957      }
958    }
959    fprintf(file, "\n");
960  }
961}
962
963string escSpaces(const string &s)
964{ string res;
965  const_ITERATE(string, si, s)
966    if (*si==' ')
967      res += "\\ ";
968    else
969      res += *si;
970  return res;
971}
972
973extern TOrangeType PyOrPythonVariable_Type;
974
975void printVarType(FILE *file, PVariable var, bool listDiscreteValues)
976{
977  TEnumVariable *enumv = var.AS(TEnumVariable);
978  if (enumv) {
979    TValue val;
980    string sval;
981    if (!enumv->firstValue(val) || !listDiscreteValues)
982      fprintf(file, "d");
983    else {
984      enumv->val2str(val, sval); 
985      fprintf(file, checkCtrl(escSpaces(sval).c_str()));
986      while(enumv->nextValue(val)) {
987        enumv->val2str(val, sval);
988        fprintf(file, " %s", checkCtrl(escSpaces(sval).c_str()));
989      }
990    }
991  }
992  else if (var.is_derived_from(TFloatVariable))
993    fprintf(file, "continuous");
994  else if (var.is_derived_from(TStringVariable))
995    fprintf(file, "string");
996  else if (var.is_derived_from(TPythonVariable)) {
997    if (var.counter->ob_type == (PyTypeObject *)&PyOrPythonVariable_Type)
998      fprintf(file, "python");
999    else {
1000      PyObject *pyclassname = PyObject_GetAttrString((PyObject *)(var.counter)->ob_type, "__name__");
1001      fprintf(file, "python:%s", checkCtrl(PyString_AsString(pyclassname)));
1002      Py_DECREF(pyclassname);
1003    }
1004  } 
1005  else
1006    raiseErrorWho("tabDelim_writeDomain", "tabDelim format supports only discrete, continuous and string variables");
1007}
1008
1009
1010void tabDelim_printAttributes(FILE *file, PVariable var, bool needsSpace) {
1011  TPyOrange *bvar = (TPyOrange *)(var.counter);
1012  PyObject *attrdict = bvar->orange_dict ? PyDict_GetItemString(bvar->orange_dict, "attributes") : NULL;
1013  if (attrdict) {
1014    PyObject *key, *value;
1015    Py_ssize_t pos = 0;
1016    while (PyDict_Next(attrdict, &pos, &key, &value)) {
1017      if (PyString_Check(key))
1018        Py_INCREF(key);
1019      else
1020        key = PyObject_Repr(key);
1021      if (PyString_Check(value))
1022        Py_INCREF(value);
1023      else
1024        value = PyObject_Repr(value);
1025      fprintf(file, (pos>1) || needsSpace ? " %s=%s" : "%s=%s", PyString_AsString(key), PyString_AsString(value));
1026      Py_DECREF(value);
1027      Py_DECREF(key);
1028    }
1029  }
1030}
1031
1032void tabDelim_writeDomainWithoutDetection(FILE *file, PDomain dom, char delim, bool listDiscreteValues)
1033{ 
1034  TVarList::const_iterator vi, vb(dom->variables->begin()), ve(dom->variables->end());
1035  TVarList::const_iterator cvi, cvb(dom->classVars->begin()), cve(dom->classVars->end());
1036  TMetaVector::const_iterator mi, mb(dom->metas.begin()), me(dom->metas.end());
1037
1038  bool ho = false;
1039  bool hasOptionalFloats = false;
1040
1041  // First line: attribute names
1042  for(vi = vb; vi!=ve; vi++) {
1043    PUTDELIM;
1044    fprintf(file, "%s", checkCtrl((*vi)->get_name().c_str()));
1045  }
1046  for(cvi = cvb; cvi!=cve; cvi++) {
1047    PUTDELIM;
1048    fprintf(file, "%s", checkCtrl((*cvi)->get_name().c_str()));
1049  }
1050  for(mi = mb; mi!=me; mi++) {
1051    if (mi->optional) {
1052      if ((*mi).variable->varType == TValue::FLOATVAR)
1053        hasOptionalFloats = true;
1054    }
1055    else {
1056      PUTDELIM;
1057      fprintf(file, "%s", checkCtrl((*mi).variable->get_name().c_str()));
1058    }
1059  }
1060
1061  if (hasOptionalFloats) {
1062    PUTDELIM;
1063    fprintf(file, "__basket_foo");
1064  }
1065
1066  fprintf(file, "\n");
1067
1068 
1069  // Second line: types
1070  ho = false;
1071  for(vi = vb; vi!=ve; vi++) {
1072    PUTDELIM;
1073    printVarType(file, *vi, listDiscreteValues);
1074  }
1075  for(cvi = cvb; cvi!=cve; cvi++) {
1076    PUTDELIM;
1077    printVarType(file, *cvi, listDiscreteValues);
1078  }
1079  for(mi = mb; mi!=me; mi++) {
1080    if (mi->optional)
1081      continue;
1082    PUTDELIM;
1083    printVarType(file, (*mi).variable, listDiscreteValues);
1084  }
1085
1086  if (hasOptionalFloats) {
1087    PUTDELIM;
1088    fprintf(file, "basket");
1089  }
1090
1091  fprintf(file, "\n");
1092
1093
1094  // Third line: "meta" and "-ordered"
1095  ho = false;
1096  for(vb = vi = dom->attributes->begin(), ve = dom->attributes->end(); vi!=ve; vi++) {
1097    PUTDELIM;
1098    bool isOrdered = ((*vi)->varType == TValue::INTVAR) && (*vi)->ordered;
1099    if (isOrdered)
1100      fprintf(file, "-ordered");
1101    tabDelim_printAttributes(file, *vi, isOrdered);
1102  }
1103  if (dom->classVar) {
1104    PUTDELIM;
1105    fprintf(file, "class");
1106    tabDelim_printAttributes(file, dom->classVar, true);
1107  }
1108  for(cvi = cvb; cvi!=cve; cvi++) {
1109    PUTDELIM;
1110    fprintf(file, "multiclass");
1111    tabDelim_printAttributes(file, *cvi, true);
1112  }
1113  for(mi = mb; mi!=me; mi++) {
1114    if (mi->optional)
1115      continue;
1116    PUTDELIM;
1117    fprintf(file, "meta");
1118    if (((*mi).variable->varType == TValue::INTVAR) && (*mi).variable->ordered)
1119      fprintf(file, " -ordered");
1120    tabDelim_printAttributes(file, (*mi).variable, true);
1121 }
1122
1123 if (hasOptionalFloats)
1124   PUTDELIM;
1125
1126 fprintf(file, "\n");
1127}
1128
1129
1130/* If discrete value can be mistakenly read as continuous, we need to add the prefix.
1131   This needs to be checked. */
1132bool tabDelim_checkNeedsD(PVariable var)
1133{
1134  bool floated = false;
1135  TEnumVariable *enumv = var.AS(TEnumVariable);
1136  if (enumv) {
1137    TValue val;
1138    string sval;
1139    char svalc[65];
1140
1141    if (!enumv->firstValue(val))
1142      return true;
1143   
1144    do {
1145      enumv->val2str(val, sval);
1146      if (sval.size()>63)
1147        return false;
1148
1149      if ((sval.size()==1) && (sval[0]>='0') && (sval[0]<='9'))
1150        continue;
1151
1152      // Convert commas into dots
1153      char *sc = svalc;
1154      ITERATE(string, si, sval) {
1155        *(sc++) = *si==',' ? '.' : *si;
1156        *sc = 0;
1157
1158        char *eptr;
1159        strtod(svalc, &eptr);
1160        if (*eptr)
1161          return false;
1162        else
1163          floated = true;
1164      }
1165    } while (enumv->nextValue(val));
1166  }
1167 
1168  // All values were either one digit or successfully interpreted as continuous
1169  // We need to return true if there were some that were not one-digit...
1170  return floated;
1171}
1172
1173
1174void tabDelim_writeDomainWithDetection(FILE *file, PDomain dom, char delim)
1175{
1176  bool ho = false;
1177  const_PITERATE(TVarList, vi, dom->attributes) {
1178    PUTDELIM;
1179    fprintf(file, "%s%s", (tabDelim_checkNeedsD(*vi) ? "D#" : ""), checkCtrl((*vi)->get_name().c_str()));
1180  }
1181 
1182  if (dom->classVar) {
1183    PUTDELIM;
1184    fprintf(file, "%s%s", (tabDelim_checkNeedsD(dom->classVar) ? "cD#" : "c#"), checkCtrl(dom->classVar->get_name().c_str()));
1185  }
1186
1187
1188  bool hasOptionalFloats = false;
1189
1190  const_ITERATE(TMetaVector, mi, dom->metas) {
1191    if (mi->optional) {
1192      if ((*mi).variable->varType == TValue::FLOATVAR)
1193        hasOptionalFloats = true;
1194    }
1195    else {
1196      PUTDELIM;
1197      fprintf(file, "%s%s", (tabDelim_checkNeedsD((*mi).variable) ? "mD#" : "m#"), checkCtrl((*mi).variable->get_name().c_str()));
1198    }
1199  }
1200
1201  if (hasOptionalFloats) {
1202    PUTDELIM;
1203    fprintf(file, "B#__basket_foo");
1204  }
1205
1206  fprintf(file, "\n");
1207}
1208
1209
1210void tabDelim_writeDomain(FILE *file, PDomain dom, bool autodetect, char delim, bool listDiscreteValues)
1211{ if (autodetect)
1212    tabDelim_writeDomainWithDetection(file, dom, delim);
1213  else 
1214    tabDelim_writeDomainWithoutDetection(file, dom, delim, listDiscreteValues);
1215}
Note: See TracBrowser for help on using the repository browser.