otsdaq  v2_04_00
TableView.cc
1 #include "otsdaq-core/TableCore/TableView.h"
2 
3 #include <cstdlib>
4 #include <iostream>
5 #include <regex>
6 #include <sstream>
7 
8 using namespace ots;
9 
10 #undef __MF_SUBJECT__
11 #define __MF_SUBJECT__ "TableView-" + tableName_
12 #undef __COUT_HDR__
13 #define __COUT_HDR__ "v" << version_ << ":" << __COUT_HDR_FL__
14 
15 const unsigned int TableView::INVALID = -1;
16 
17 //==============================================================================
18 TableView::TableView(const std::string& name)
19  : uniqueStorageIdentifier_("")
20  , tableName_(name)
21  , version_(TableVersion::INVALID)
22  , comment_("")
23  , author_("")
24  , creationTime_(time(0))
25  , lastAccessTime_(0)
26  , colUID_(INVALID)
27  , colStatus_(INVALID)
28  , colPriority_(INVALID)
29  , fillWithLooseColumnMatching_(false)
30  , sourceColumnMismatchCount_(0)
31  , sourceColumnMissingCount_(0)
32 {
33 }
34 
35 //==============================================================================
36 TableView::~TableView(void) {}
37 
38 //==============================================================================
39 // operator=
40 // Do NOT allow!... use TableView::copy
41 // copy is used to maintain consistency with version, creationTime, lastAccessTime, etc)
42 TableView& TableView::operator=(const TableView src)
43 {
44  __SS__ << "Invalid use of operator=... Should not directly copy a TableView. Please "
45  "use TableView::copy(sourceView,author,comment)";
46  __SS_THROW__;
47 }
48 
49 //==============================================================================
50 TableView& TableView::copy(const TableView& src,
51  TableVersion destinationVersion,
52  const std::string& author)
53 {
54  tableName_ = src.tableName_;
55  version_ = destinationVersion;
56  comment_ = src.comment_;
57  author_ = author; // take new author
58  // creationTime_ = time(0); //don't change creation time
59  lastAccessTime_ = time(0);
60  columnsInfo_ = src.columnsInfo_;
61  theDataView_ = src.theDataView_;
62  sourceColumnNames_ = src.sourceColumnNames_;
63  init(); // verify consistency
64  return *this;
65 }
66 
67 //==============================================================================
68 // copyRows
69 // return row offset of first row copied in
70 unsigned int TableView::copyRows(const std::string& author,
71  const TableView& src,
72  unsigned int srcOffsetRow,
73  unsigned int srcRowsToCopy,
74  unsigned int destOffsetRow,
75  bool generateUniqueDataColumns)
76 {
77  //__COUTV__(destOffsetRow);
78  //__COUTV__(srcOffsetRow);
79  //__COUTV__(srcRowsToCopy);
80 
81  unsigned int retRow = (unsigned int)-1;
82 
83  // check that column sizes match
84  if(src.getNumberOfColumns() != getNumberOfColumns())
85  {
86  __SS__ << "Error! Number of Columns of source view must match destination view."
87  << "Dimension of source is [" << src.getNumberOfColumns()
88  << "] and of destination is [" << getNumberOfColumns() << "]." << __E__;
89  __SS_THROW__;
90  }
91 
92  unsigned int srcRows = src.getNumberOfRows();
93 
94  for(unsigned int r = 0; r < srcRowsToCopy; ++r)
95  {
96  if(r + srcOffsetRow >= srcRows)
97  break; // end when no more source rows to copy (past bounds)
98 
99  destOffsetRow = addRow(author,
100  generateUniqueDataColumns /*incrementUniqueData*/,
101  "" /*baseNameAutoUID*/,
102  destOffsetRow); // add and get row created
103 
104  if(retRow == (unsigned int)-1)
105  retRow = destOffsetRow; // save row of first copied entry
106 
107  // copy data
108  for(unsigned int col = 0; col < getNumberOfColumns(); ++col)
109  if(generateUniqueDataColumns &&
110  (columnsInfo_[col].getType() == TableViewColumnInfo::TYPE_UNIQUE_DATA ||
111  columnsInfo_[col].getType() ==
112  TableViewColumnInfo::TYPE_UNIQUE_GROUP_DATA))
113  continue; // if leaving unique data, then skip copy
114  else
115  theDataView_[destOffsetRow][col] =
116  src.theDataView_[r + srcOffsetRow][col];
117 
118  // prepare for next row
119  ++destOffsetRow;
120  }
121 
122  return retRow;
123 }
124 
125 //==============================================================================
126 // init
127 // Should be called after table is filled to setup special members
128 // and verify consistency.
129 // e.g. identifying the UID column, checking unique data fields, etc.
130 //
131 // Note: this function also sanitizes yes/no, on/off, and true/false types
132 void TableView::init(void)
133 {
134  try
135  {
136  // verify column names are unique
137  // make set of names,.. and CommentDescription == COMMENT
138  std::set<std::string> colNameSet;
139  std::string capsColName, colName;
140  for(auto& colInfo : columnsInfo_)
141  {
142  colName = colInfo.getStorageName();
143  if(colName == "COMMENT_DESCRIPTION")
144  colName = "COMMENT";
145  capsColName = "";
146  for(unsigned int i = 0; i < colName.size(); ++i)
147  {
148  if(colName[i] == '_')
149  continue;
150  capsColName += colName[i];
151  }
152 
153  colNameSet.emplace(capsColName);
154  }
155 
156  if(colNameSet.size() != columnsInfo_.size())
157  {
158  __SS__ << "Table Error:\t"
159  << " Columns names must be unique! There are " << columnsInfo_.size()
160  << " columns and the unique name count is " << colNameSet.size()
161  << __E__;
162  __SS_THROW__;
163  }
164 
165  getOrInitColUID(); // setup UID column
166  try
167  {
168  getOrInitColStatus(); // setup Status column
169  }
170  catch(...)
171  {
172  } // ignore no Status column
173  try
174  {
175  getOrInitColPriority(); // setup Priority column
176  }
177  catch(...)
178  {
179  } // ignore no Priority column
180 
181  // require one comment column
182  unsigned int colPos;
183  if((colPos = findColByType(TableViewColumnInfo::TYPE_COMMENT)) != INVALID)
184  {
185  if(columnsInfo_[colPos].getName() != "CommentDescription")
186  {
187  __SS__ << "Table Error:\t" << TableViewColumnInfo::TYPE_COMMENT
188  << " data type column must have name="
189  << "CommentDescription" << __E__;
190  __SS_THROW__;
191  }
192 
193  if(findColByType(TableViewColumnInfo::TYPE_COMMENT, colPos + 1) !=
194  INVALID) // found two!
195  {
196  __SS__ << "Table Error:\t" << TableViewColumnInfo::TYPE_COMMENT
197  << " data type in column " << columnsInfo_[colPos].getName()
198  << " is repeated. This is not allowed." << __E__;
199  __SS_THROW__;
200  }
201 
202  if(colPos != getNumberOfColumns() - 3)
203  {
204  __SS__ << "Table Error:\t" << TableViewColumnInfo::TYPE_COMMENT
205  << " data type column must be 3rd to last (in column "
206  << getNumberOfColumns() - 3 << ")." << __E__;
207  __SS_THROW__;
208  }
209  }
210  else
211  {
212  __SS__ << "Table Error:\t" << TableViewColumnInfo::TYPE_COMMENT
213  << " data type column "
214  << " is missing. This is not allowed." << __E__;
215  __SS_THROW__;
216  }
217 
218  // require one author column
219  if((colPos = findColByType(TableViewColumnInfo::TYPE_AUTHOR)) != INVALID)
220  {
221  if(findColByType(TableViewColumnInfo::TYPE_AUTHOR, colPos + 1) !=
222  INVALID) // found two!
223  {
224  __SS__ << "Table Error:\t" << TableViewColumnInfo::TYPE_AUTHOR
225  << " data type in column " << columnsInfo_[colPos].getName()
226  << " is repeated. This is not allowed." << __E__;
227  __SS_THROW__;
228  }
229 
230  if(colPos != getNumberOfColumns() - 2)
231  {
232  __SS__ << "Table Error:\t" << TableViewColumnInfo::TYPE_AUTHOR
233  << " data type column must be 2nd to last (in column "
234  << getNumberOfColumns() - 2 << ")." << __E__;
235  __SS_THROW__;
236  }
237  }
238  else
239  {
240  __SS__ << "Table Error:\t" << TableViewColumnInfo::TYPE_AUTHOR
241  << " data type column "
242  << " is missing. This is not allowed." << __E__;
243  __SS_THROW__;
244  }
245 
246  // require one timestamp column
247  if((colPos = findColByType(TableViewColumnInfo::TYPE_TIMESTAMP)) != INVALID)
248  {
249  if(findColByType(TableViewColumnInfo::TYPE_TIMESTAMP, colPos + 1) !=
250  INVALID) // found two!
251  {
252  __SS__ << "Table Error:\t" << TableViewColumnInfo::TYPE_TIMESTAMP
253  << " data type in column " << columnsInfo_[colPos].getName()
254  << " is repeated. This is not allowed." << __E__;
255  __SS_THROW__;
256  }
257 
258  if(colPos != getNumberOfColumns() - 1)
259  {
260  __SS__ << "Table Error:\t" << TableViewColumnInfo::TYPE_TIMESTAMP
261  << " data type column must be last (in column "
262  << getNumberOfColumns() - 1 << ")." << __E__;
263  __COUT_ERR__ << "\n" << ss.str();
264  __SS_THROW__;
265  }
266  }
267  else
268  {
269  __SS__ << "Table Error:\t" << TableViewColumnInfo::TYPE_TIMESTAMP
270  << " data type column "
271  << " is missing. This is not allowed." << __E__;
272  __SS_THROW__;
273  }
274 
275  // check that UID is really unique ID (no repeats)
276  // and ... allow letters, numbers, dash, underscore
277  // and ... force size 1
278  std::set<std::string /*uid*/> uidSet;
279  for(unsigned int row = 0; row < getNumberOfRows(); ++row)
280  {
281  if(uidSet.find(theDataView_[row][colUID_]) != uidSet.end())
282  {
283  __SS__ << ("Entries in UID are not unique. Specifically at row=" +
284  std::to_string(row) + " value=" + theDataView_[row][colUID_])
285  << __E__;
286  __SS_THROW__;
287  }
288 
289  if(theDataView_[row][colUID_].size() == 0)
290  {
291  __SS__ << "An invalid UID '" << theDataView_[row][colUID_] << "' "
292  << " was identified. UIDs must contain at least 1 character."
293  << __E__;
294  __SS_THROW__;
295  }
296 
297  for(unsigned int i = 0; i < theDataView_[row][colUID_].size(); ++i)
298  if(!((theDataView_[row][colUID_][i] >= 'A' &&
299  theDataView_[row][colUID_][i] <= 'Z') ||
300  (theDataView_[row][colUID_][i] >= 'a' &&
301  theDataView_[row][colUID_][i] <= 'z') ||
302  (theDataView_[row][colUID_][i] >= '0' &&
303  theDataView_[row][colUID_][i] <= '9') ||
304  (theDataView_[row][colUID_][i] == '-' ||
305  theDataView_[row][colUID_][i] <= '_')))
306  {
307  __SS__ << "An invalid UID '" << theDataView_[row][colUID_] << "' "
308  << " was identified. UIDs must contain only letters, numbers,"
309  << "dashes, and underscores." << __E__;
310  __SS_THROW__;
311  }
312 
313  uidSet.insert(theDataView_[row][colUID_]);
314  }
315  if(uidSet.size() != getNumberOfRows())
316  {
317  __SS__ << "Entries in UID are not unique!"
318  << "There are " << getNumberOfRows()
319  << " records and the unique UID count is " << uidSet.size() << __E__;
320  __SS_THROW__;
321  }
322 
323  // check that any TYPE_UNIQUE_DATA columns are really unique (no repeats)
324  colPos = (unsigned int)-1;
325  while((colPos = findColByType(TableViewColumnInfo::TYPE_UNIQUE_DATA,
326  colPos + 1)) != INVALID)
327  {
328  std::set<std::string /*unique data*/> uDataSet;
329  for(unsigned int row = 0; row < getNumberOfRows(); ++row)
330  {
331  if(uDataSet.find(theDataView_[row][colPos]) != uDataSet.end())
332  {
333  __SS__ << "Entries in Unique Data column "
334  << columnsInfo_[colPos].getName()
335  << (" are not unique. Specifically at row=" +
336  std::to_string(row) +
337  " value=" + theDataView_[row][colPos])
338  << __E__;
339  __SS_THROW__;
340  }
341  uDataSet.insert(theDataView_[row][colPos]);
342  }
343  if(uDataSet.size() != getNumberOfRows())
344  {
345  __SS__ << "Entries in Unique Data column "
346  << columnsInfo_[colPos].getName() << " are not unique!"
347  << "There are " << getNumberOfRows()
348  << " records and the unique data count is " << uDataSet.size()
349  << __E__;
350  __SS_THROW__;
351  }
352  }
353 
354  // check that any TYPE_UNIQUE_GROUP_DATA columns are really unique fpr groups (no
355  // repeats)
356  colPos = (unsigned int)-1;
357  while((colPos = findColByType(TableViewColumnInfo::TYPE_UNIQUE_GROUP_DATA,
358  colPos + 1)) != INVALID)
359  {
360  // colPos is a unique group data column
361  // now, for each groupId column
362  // check that data is unique for all groups
363  for(unsigned int groupIdColPos = 0; groupIdColPos < columnsInfo_.size();
364  ++groupIdColPos)
365  if(columnsInfo_[groupIdColPos].isGroupID())
366  {
367  std::map<std::string /*group name*/,
368  std::pair<unsigned int /*memberCount*/,
369  std::set<std::string /*unique data*/> > >
370  uGroupDataSets;
371 
372  for(unsigned int row = 0; row < getNumberOfRows(); ++row)
373  {
374  auto groupIds = getSetOfGroupIDs(groupIdColPos, row);
375 
376  for(const auto& groupId : groupIds)
377  {
378  uGroupDataSets[groupId].first++; // add to member count
379 
380  if(uGroupDataSets[groupId].second.find(
381  theDataView_[row][colPos]) !=
382  uGroupDataSets[groupId].second.end())
383  {
384  __SS__ << "Entries in Unique Group Data column " << colPos
385  << ":" << columnsInfo_[colPos].getName()
386  << " are not unique for group ID '" << groupId
387  << ".' Specifically at row=" << std::to_string(row)
388  << " value=" << theDataView_[row][colPos] << __E__;
389  __SS_THROW__;
390  }
391  uGroupDataSets[groupId].second.insert(
392  theDataView_[row][colPos]);
393  }
394  }
395 
396  for(const auto& groupPair : uGroupDataSets)
397  if(uGroupDataSets[groupPair.first].second.size() !=
398  uGroupDataSets[groupPair.first].first)
399  {
400  __SS__
401  << "Entries in Unique Data column "
402  << columnsInfo_[colPos].getName()
403  << " are not unique for group '" << groupPair.first
404  << "!'"
405  << "There are " << uGroupDataSets[groupPair.first].first
406  << " records and the unique data count is "
407  << uGroupDataSets[groupPair.first].second.size() << __E__;
408  __SS_THROW__;
409  }
410  }
411  } // end TYPE_UNIQUE_GROUP_DATA check
412 
413  auto rowDefaults = getDefaultRowValues();
414 
415  // check that column types are well behaved
416  // - check that fixed choice data is one of choices
417  // - sanitize booleans
418  // - check that child link I are unique
419  // note: childLinkId refers to childLinkGroupIDs AND childLinkUIDs
420  std::set<std::string> groupIdIndexes, childLinkIndexes, childLinkIdLabels;
421  unsigned int groupIdIndexesCount = 0, childLinkIndexesCount = 0,
422  childLinkIdLabelsCount = 0;
423  bool tmpIsGroup;
424  std::pair<unsigned int /*link col*/, unsigned int /*link id col*/> tmpLinkPair;
425 
426  for(unsigned int col = 0; col < getNumberOfColumns(); ++col)
427  {
428  if(columnsInfo_[col].getType() == TableViewColumnInfo::TYPE_FIXED_CHOICE_DATA)
429  {
430  const std::vector<std::string>& theDataChoices =
431  columnsInfo_[col].getDataChoices();
432 
433  // check if arbitrary values allowed
434  if(theDataChoices.size() && theDataChoices[0] == "arbitraryBool=1")
435  continue; // arbitrary values allowed
436 
437  bool found;
438  for(unsigned int row = 0; row < getNumberOfRows(); ++row)
439  {
440  found = false;
441  // check against default value first
442  if(theDataView_[row][col] == rowDefaults[col])
443  continue; // default is always ok
444 
445  for(const auto& choice : theDataChoices)
446  {
447  if(theDataView_[row][col] == choice)
448  {
449  found = true;
450  break;
451  }
452  }
453  if(!found)
454  {
455  __SS__ << "Table Error:\t'" << theDataView_[row][col]
456  << "' in column " << columnsInfo_[col].getName()
457  << " is not a valid Fixed Choice option. "
458  << "Possible values are as follows: ";
459 
460  for(unsigned int i = 0;
461  i < columnsInfo_[col].getDataChoices().size();
462  ++i)
463  {
464  if(i)
465  ss << ", ";
466  ss << columnsInfo_[col].getDataChoices()[i];
467  }
468  ss << "." << __E__;
469  __SS_THROW__;
470  }
471  }
472  }
473  else if(columnsInfo_[col].isChildLink())
474  {
475  // check if forcing fixed choices
476 
477  const std::vector<std::string>& theDataChoices =
478  columnsInfo_[col].getDataChoices();
479 
480  // check if arbitrary values allowed
481  if(!theDataChoices.size() || theDataChoices[0] == "arbitraryBool=1")
482  continue; // arbitrary values allowed
483 
484  // skip one if arbitrary setting is embedded as first value
485  bool skipOne =
486  (theDataChoices.size() && theDataChoices[0] == "arbitraryBool=0");
487  bool hasSkipped;
488 
489  bool found;
490  for(unsigned int row = 0; row < getNumberOfRows(); ++row)
491  {
492  found = false;
493 
494  hasSkipped = false;
495  for(const auto& choice : theDataChoices)
496  {
497  if(skipOne && !hasSkipped)
498  {
499  hasSkipped = true;
500  continue;
501  }
502 
503  if(theDataView_[row][col] == choice)
504  {
505  found = true;
506  break;
507  }
508  }
509  if(!found)
510  {
511  __SS__ << "Table Error:\t'" << theDataView_[row][col]
512  << "' in column " << columnsInfo_[col].getName()
513  << " is not a valid Fixed Choice option. "
514  << "Possible values are as follows: ";
515 
516  // ss <<
517  // StringMacros::vectorToString(columnsInfo_[col].getDataChoices())
518  // << __E__;
519  for(unsigned int i = skipOne ? 1 : 0;
520  i < columnsInfo_[col].getDataChoices().size();
521  ++i)
522  {
523  if(i > (skipOne ? 1 : 0))
524  ss << ", ";
525  ss << columnsInfo_[col].getDataChoices()[i];
526  }
527  ss << "." << __E__;
528  __SS_THROW__;
529  }
530  }
531  }
532  else if(columnsInfo_[col].getType() == TableViewColumnInfo::TYPE_ON_OFF)
533  for(unsigned int row = 0; row < getNumberOfRows(); ++row)
534  {
535  if(theDataView_[row][col] == "1" || theDataView_[row][col] == "on" ||
536  theDataView_[row][col] == "On" || theDataView_[row][col] == "ON")
537  theDataView_[row][col] = TableViewColumnInfo::TYPE_VALUE_ON;
538  else if(theDataView_[row][col] == "0" ||
539  theDataView_[row][col] == "off" ||
540  theDataView_[row][col] == "Off" ||
541  theDataView_[row][col] == "OFF")
542  theDataView_[row][col] = TableViewColumnInfo::TYPE_VALUE_OFF;
543  else
544  {
545  __SS__ << "Table Error:\t" << theDataView_[row][col]
546  << " in column " << columnsInfo_[col].getName()
547  << " is not a valid Type (On/Off) std::string. Possible "
548  "values are 1, on, On, ON, 0, off, Off, OFF."
549  << __E__;
550  __SS_THROW__;
551  }
552  }
553  else if(columnsInfo_[col].getType() == TableViewColumnInfo::TYPE_TRUE_FALSE)
554  for(unsigned int row = 0; row < getNumberOfRows(); ++row)
555  {
556  if(theDataView_[row][col] == "1" ||
557  theDataView_[row][col] == "true" ||
558  theDataView_[row][col] == "True" ||
559  theDataView_[row][col] == "TRUE")
560  theDataView_[row][col] = TableViewColumnInfo::TYPE_VALUE_TRUE;
561  else if(theDataView_[row][col] == "0" ||
562  theDataView_[row][col] == "false" ||
563  theDataView_[row][col] == "False" ||
564  theDataView_[row][col] == "FALSE")
565  theDataView_[row][col] = TableViewColumnInfo::TYPE_VALUE_FALSE;
566  else
567  {
568  __SS__ << "Table Error:\t" << theDataView_[row][col]
569  << " in column " << columnsInfo_[col].getName()
570  << " is not a valid Type (True/False) std::string. "
571  "Possible values are 1, true, True, TRUE, 0, false, "
572  "False, FALSE."
573  << __E__;
574  __SS_THROW__;
575  }
576  }
577  else if(columnsInfo_[col].getType() == TableViewColumnInfo::TYPE_YES_NO)
578  for(unsigned int row = 0; row < getNumberOfRows(); ++row)
579  {
580  if(theDataView_[row][col] == "1" || theDataView_[row][col] == "yes" ||
581  theDataView_[row][col] == "Yes" || theDataView_[row][col] == "YES")
582  theDataView_[row][col] = TableViewColumnInfo::TYPE_VALUE_YES;
583  else if(theDataView_[row][col] == "0" ||
584  theDataView_[row][col] == "no" ||
585  theDataView_[row][col] == "No" ||
586  theDataView_[row][col] == "NO")
587  theDataView_[row][col] = TableViewColumnInfo::TYPE_VALUE_NO;
588  else
589  {
590  __SS__ << "Table Error:\t" << theDataView_[row][col]
591  << " in column " << columnsInfo_[col].getName()
592  << " is not a valid Type (Yes/No) std::string. Possible "
593  "values are 1, yes, Yes, YES, 0, no, No, NO."
594  << __E__;
595  __SS_THROW__;
596  }
597  }
598  else if(columnsInfo_[col].isGroupID()) // GroupID type
599  {
600  colLinkGroupIDs_[columnsInfo_[col].getChildLinkIndex()] =
601  col; // add to groupid map
602  // check uniqueness
603  groupIdIndexes.emplace(columnsInfo_[col].getChildLinkIndex());
604  ++groupIdIndexesCount;
605  }
606  else if(columnsInfo_[col].isChildLink()) // Child Link type
607  {
608  // sanitize no link to default
609  for(unsigned int row = 0; row < getNumberOfRows(); ++row)
610  if(theDataView_[row][col] == "NoLink" ||
611  theDataView_[row][col] == "No_Link" ||
612  theDataView_[row][col] == "NOLINK" ||
613  theDataView_[row][col] == "NO_LINK" ||
614  theDataView_[row][col] == "Nolink" ||
615  theDataView_[row][col] == "nolink" ||
616  theDataView_[row][col] == "noLink")
617  theDataView_[row][col] =
618  TableViewColumnInfo::DATATYPE_LINK_DEFAULT;
619 
620  // check uniqueness
621  childLinkIndexes.emplace(columnsInfo_[col].getChildLinkIndex());
622  ++childLinkIndexesCount;
623 
624  // force data type to TableViewColumnInfo::DATATYPE_STRING
625  if(columnsInfo_[col].getDataType() !=
626  TableViewColumnInfo::DATATYPE_STRING)
627  {
628  __SS__ << "Table Error:\t"
629  << "Column " << col << " with name "
630  << columnsInfo_[col].getName()
631  << " is a Child Link column and has an illegal data type of '"
632  << columnsInfo_[col].getDataType()
633  << "'. The data type for Child Link columns must be "
634  << TableViewColumnInfo::DATATYPE_STRING << __E__;
635  __SS_THROW__;
636  }
637 
638  // check for link mate (i.e. every child link needs link ID)
639  getChildLink(col, tmpIsGroup, tmpLinkPair);
640  }
641  else if(columnsInfo_[col].isChildLinkUID() || // Child Link ID type
642  columnsInfo_[col].isChildLinkGroupID())
643  {
644  // check uniqueness
645  childLinkIdLabels.emplace(columnsInfo_[col].getChildLinkIndex());
646  ++childLinkIdLabelsCount;
647 
648  // check that the Link ID is not empty, and force to default
649  for(unsigned int row = 0; row < getNumberOfRows(); ++row)
650  if(theDataView_[row][col] == "")
651  theDataView_[row][col] = rowDefaults[col];
652 
653  // check for link mate (i.e. every child link needs link ID)
654  getChildLink(col, tmpIsGroup, tmpLinkPair);
655  }
656  }
657 
658  // verify child link index uniqueness
659  if(groupIdIndexes.size() != groupIdIndexesCount)
660  {
661  __SS__ << ("GroupId Labels are not unique!") << "There are "
662  << groupIdIndexesCount << " GroupId Labels and the unique count is "
663  << groupIdIndexes.size() << __E__;
664  __SS_THROW__;
665  }
666  if(childLinkIndexes.size() != childLinkIndexesCount)
667  {
668  __SS__ << ("Child Link Labels are not unique!") << "There are "
669  << childLinkIndexesCount
670  << " Child Link Labels and the unique count is "
671  << childLinkIndexes.size() << __E__;
672  __SS_THROW__;
673  }
674  if(childLinkIdLabels.size() != childLinkIdLabelsCount)
675  {
676  __SS__ << ("Child Link ID Labels are not unique!") << "There are "
677  << childLinkIdLabelsCount
678  << " Child Link ID Labels and the unique count is "
679  << childLinkIdLabels.size() << __E__;
680  __SS_THROW__;
681  }
682  }
683  catch(...)
684  {
685  __COUT__ << "Error occured in TableView::init() for version=" << version_
686  << __E__;
687  throw;
688  }
689 } // end init()
690 
691 //==============================================================================
692 // getValue
693 // string version
694 // Note: necessary because types of std::basic_string<char> cause compiler problems if no
695 // string specific function
696 void TableView::getValue(std::string& value,
697  unsigned int row,
698  unsigned int col,
699  bool doConvertEnvironmentVariables) const
700 {
701  if(!(col < columnsInfo_.size() && row < getNumberOfRows()))
702  {
703  __SS__ << "Invalid row col requested" << __E__;
704  __SS_THROW__;
705  }
706 
707  value = validateValueForColumn(
708  theDataView_[row][col], col, doConvertEnvironmentVariables);
709 }
710 
711 //==============================================================================
712 // validateValueForColumn
713 // string version
714 // Note: necessary because types of std::basic_string<char>
715 // cause compiler problems if no string specific function
716 std::string TableView::validateValueForColumn(const std::string& value,
717  unsigned int col,
718  bool doConvertEnvironmentVariables) const
719 {
720  if(col >= columnsInfo_.size())
721  {
722  __SS__ << "Invalid col requested" << __E__;
723  __SS_THROW__;
724  }
725 
726  if(columnsInfo_[col].getDataType() == TableViewColumnInfo::DATATYPE_STRING)
727  return doConvertEnvironmentVariables
728  ? StringMacros::convertEnvironmentVariables(value)
729  : value;
730  else if(columnsInfo_[col].getDataType() == TableViewColumnInfo::DATATYPE_TIME)
731  {
732  return StringMacros::getTimestampString(
733  doConvertEnvironmentVariables
734  ? StringMacros::convertEnvironmentVariables(value)
735  : value);
736 
737  // retValue.resize(30); //known fixed size: Thu Aug 23 14:55:02 2001 CST
738  // time_t timestamp(
739  // strtol((doConvertEnvironmentVariables?StringMacros::convertEnvironmentVariables(value):value).c_str(),
740  // 0,10));
741  // struct tm tmstruct;
742  // ::localtime_r(&timestamp, &tmstruct);
743  // ::strftime(&retValue[0], 30, "%c %Z", &tmstruct);
744  // retValue.resize(strlen(retValue.c_str()));
745  }
746  else
747  {
748  __SS__ << "\tUnrecognized column data type: " << columnsInfo_[col].getDataType()
749  << " in configuration " << tableName_
750  << " at column=" << columnsInfo_[col].getName()
751  << " for getValue with type '"
752  << StringMacros::demangleTypeName(typeid(std::string).name()) << "'"
753  << __E__;
754  __SS_THROW__;
755  }
756 
757  // return retValue;
758 } // end validateValueForColumn()
759 
760 //==============================================================================
761 // getValueAsString
762 // gets the value with the proper data type and converts to string
763 // as though getValue was called.
764 std::string TableView::getValueAsString(unsigned int row,
765  unsigned int col,
766  bool doConvertEnvironmentVariables) const
767 {
768  if(!(col < columnsInfo_.size() && row < getNumberOfRows()))
769  {
770  __SS__ << ("Invalid row col requested") << __E__;
771  __SS_THROW__;
772  }
773 
774  //__COUT__ << columnsInfo_[col].getType() << " " << col << __E__;
775 
776  if(columnsInfo_[col].getType() == TableViewColumnInfo::TYPE_ON_OFF)
777  {
778  if(theDataView_[row][col] == "1" || theDataView_[row][col] == "on" ||
779  theDataView_[row][col] == "On" || theDataView_[row][col] == "ON")
780  return TableViewColumnInfo::TYPE_VALUE_ON;
781  else
782  return TableViewColumnInfo::TYPE_VALUE_OFF;
783  }
784  else if(columnsInfo_[col].getType() == TableViewColumnInfo::TYPE_TRUE_FALSE)
785  {
786  if(theDataView_[row][col] == "1" || theDataView_[row][col] == "true" ||
787  theDataView_[row][col] == "True" || theDataView_[row][col] == "TRUE")
788  return TableViewColumnInfo::TYPE_VALUE_TRUE;
789  else
790  return TableViewColumnInfo::TYPE_VALUE_FALSE;
791  }
792  else if(columnsInfo_[col].getType() == TableViewColumnInfo::TYPE_YES_NO)
793  {
794  if(theDataView_[row][col] == "1" || theDataView_[row][col] == "yes" ||
795  theDataView_[row][col] == "Yes" || theDataView_[row][col] == "YES")
796  return TableViewColumnInfo::TYPE_VALUE_YES;
797  else
798  return TableViewColumnInfo::TYPE_VALUE_NO;
799  }
800 
801  //__COUT__ << __E__;
802  return doConvertEnvironmentVariables
803  ? StringMacros::convertEnvironmentVariables(theDataView_[row][col])
804  : theDataView_[row][col];
805 }
806 
807 //==============================================================================
808 // getEscapedValueAsString
809 // gets the value with the proper data type and converts to string
810 // as though getValue was called.
811 // then escapes all special characters with slash.
812 // Note: this should be useful for values placed in double quotes, i.e. JSON.
813 std::string TableView::getEscapedValueAsString(unsigned int row,
814  unsigned int col,
815  bool doConvertEnvironmentVariables) const
816 {
817  std::string val = getValueAsString(row, col, doConvertEnvironmentVariables);
818  std::string retVal = "";
819  retVal.reserve(val.size()); // reserve roughly right size
820  for(unsigned int i = 0; i < val.size(); ++i)
821  {
822  if(val[i] == '\n')
823  retVal += "\\n";
824  else if(val[i] == '\t')
825  retVal += "\\t";
826  else if(val[i] == '\r')
827  retVal += "\\r";
828  else
829  {
830  // escaped characters need a
831  if(val[i] == '"' || val[i] == '\\')
832  retVal += '\\';
833  retVal += val[i];
834  }
835  }
836  return retVal;
837 }
838 
839 //==============================================================================
840 // setValue
841 // string version
842 void TableView::setValue(const std::string& value, unsigned int row, unsigned int col)
843 {
844  if(!(col < columnsInfo_.size() && row < getNumberOfRows()))
845  {
846  __SS__ << "Invalid row (" << row << ") col (" << col << ") requested!" << __E__;
847  __SS_THROW__;
848  }
849 
850  if(columnsInfo_[col].getDataType() == TableViewColumnInfo::DATATYPE_STRING)
851  theDataView_[row][col] = value;
852  else // dont allow TableViewColumnInfo::DATATYPE_TIME to be set as string.. force use
853  // as time_t to standardize string result
854  {
855  __SS__ << "\tUnrecognized column data type: " << columnsInfo_[col].getDataType()
856  << " in configuration " << tableName_
857  << " at column=" << columnsInfo_[col].getName()
858  << " for setValue with type '"
859  << StringMacros::demangleTypeName(typeid(value).name()) << "'" << __E__;
860  __SS_THROW__;
861  }
862 }
863 void TableView::setValue(const char* value, unsigned int row, unsigned int col)
864 {
865  setValue(std::string(value), row, col);
866 }
867 
868 //==============================================================================
869 // setValue
870 // string version
871 void TableView::setValueAsString(const std::string& value,
872  unsigned int row,
873  unsigned int col)
874 {
875  if(!(col < columnsInfo_.size() && row < getNumberOfRows()))
876  {
877  __SS__ << "Invalid row (" << row << ") col (" << col << ") requested!" << __E__;
878  __SS_THROW__;
879  }
880 
881  theDataView_[row][col] = value;
882 }
883 
884 //==============================================================================
885 // getOrInitColUID
886 // if column not found throw error
887 const unsigned int TableView::getOrInitColUID(void)
888 {
889  if(colUID_ != INVALID)
890  return colUID_;
891 
892  // if doesn't exist throw error! each view must have a UID column
893  colUID_ = findColByType(TableViewColumnInfo::TYPE_UID);
894  if(colUID_ == INVALID)
895  {
896  __COUT__ << "Column Types: " << __E__;
897  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
898  std::cout << columnsInfo_[col].getType() << "() "
899  << columnsInfo_[col].getName() << __E__;
900  __SS__ << "\tMissing UID Column in table named '" << tableName_ << "'" << __E__;
901  __SS_THROW__;
902  }
903  return colUID_;
904 }
905 //==============================================================================
906 // getColOfUID
907 // const version, so don't attempt to lookup
908 // if column not found throw error
909 const unsigned int TableView::getColUID(void) const
910 {
911  if(colUID_ != INVALID)
912  return colUID_;
913 
914  __COUT__ << "Column Types: " << __E__;
915  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
916  std::cout << columnsInfo_[col].getType() << "() " << columnsInfo_[col].getName()
917  << __E__;
918 
919  __SS__ << ("Missing UID Column in config named " + tableName_ +
920  ". (Possibly TableView was just not initialized?" +
921  "This is the const call so can not alter class members)")
922  << __E__;
923  __SS_THROW__;
924 }
925 
926 //==============================================================================
927 // getOrInitColStatus
928 // if column not found throw error
929 const unsigned int TableView::getOrInitColStatus(void)
930 {
931  if(colStatus_ != INVALID)
932  return colStatus_;
933 
934  // if doesn't exist throw error! each view must have a UID column
935  colStatus_ = findCol(TableViewColumnInfo::COL_NAME_STATUS);
936  if(colStatus_ == INVALID)
937  {
938  __SS__ << "\tMissing " << TableViewColumnInfo::COL_NAME_STATUS
939  << " Column in table named '" << tableName_ << "'" << __E__;
940  ss << "Column Types: " << __E__;
941  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
942  ss << columnsInfo_[col].getType() << "() " << columnsInfo_[col].getName()
943  << __E__;
944 
945  __SS_THROW__;
946  }
947  return colStatus_;
948 }
949 
950 //==============================================================================
951 // getOrInitColPriority
952 // if column not found throw error
953 const unsigned int TableView::getOrInitColPriority(void)
954 {
955  if(colPriority_ != INVALID)
956  return colPriority_;
957 
958  // if doesn't exist throw error! each view must have a UID column
959  colPriority_ =
960  findCol("*" + TableViewColumnInfo::COL_NAME_PRIORITY); // wild card search
961  if(colPriority_ == INVALID)
962  {
963  __SS__ << "\tMissing " << TableViewColumnInfo::COL_NAME_PRIORITY
964  << " Column in table named '" << tableName_ << "'" << __E__;
965  ss << "Column Types: " << __E__;
966  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
967  ss << columnsInfo_[col].getType() << "() " << columnsInfo_[col].getName()
968  << __E__;
969 
970  __SS_THROW__;
971  }
972  return colPriority_;
973 }
974 
975 //==============================================================================
976 // getColStatus
977 // const version, so don't attempt to lookup
978 // if column not found throw error
979 const unsigned int TableView::getColStatus(void) const
980 {
981  if(colStatus_ != INVALID)
982  return colStatus_;
983 
984  __COUT__ << "\n\nTable '" << tableName_ << "' Columns: " << __E__;
985  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
986  std::cout << "\t" << columnsInfo_[col].getType() << "() "
987  << columnsInfo_[col].getName() << __E__;
988 
989  std::cout << __E__;
990 
991  __SS__ << "Missing " << TableViewColumnInfo::COL_NAME_STATUS
992  << " Column in config named " << tableName_
993  << ". (Possibly TableView was just not initialized?"
994  << "This is the const call so can not alter class members)" << __E__;
995  __SS_THROW__;
996 }
997 
998 //==============================================================================
999 // getColPriority
1000 // const version, so don't attempt to lookup
1001 // if column not found throw error
1002 const unsigned int TableView::getColPriority(void) const
1003 {
1004  if(colPriority_ != INVALID)
1005  return colPriority_;
1006 
1007  __COUT__ << "Priority column was not found... \nColumn Types: " << __E__;
1008  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
1009  std::cout << "\t" << columnsInfo_[col].getType() << "() "
1010  << columnsInfo_[col].getName() << __E__;
1011  std::cout << __E__;
1012 
1013  __SS__ << "Missing " << TableViewColumnInfo::COL_NAME_PRIORITY
1014  << " Column in config named " << tableName_
1015  << ". (The Priority column is identified when TableView is initialized)"
1016  << __E__; // this is the const call, so can not identify the column and set
1017  // colPriority_ here
1018  __SS_THROW__;
1019 }
1020 
1021 //==============================================================================
1022 // addRowToGroup
1023 // Group entry can include | to place a record in multiple groups
1024 void TableView::addRowToGroup(
1025  const unsigned int& row,
1026  const unsigned int& col,
1027  const std::string& groupID) //,
1028  // const std::string &colDefault)
1029 {
1030  if(isEntryInGroupCol(row, col, groupID))
1031  {
1032  __SS__ << "GroupID (" << groupID << ") added to row (" << row
1033  << " is already present!" << __E__;
1034  __SS_THROW__;
1035  }
1036 
1037  // not in group, so
1038  // if no groups
1039  // set groupid
1040  // if other groups
1041  // prepend groupId |
1042  if(getDataView()[row][col] == "" ||
1043  getDataView()[row][col] == getDefaultRowValues()[col]) // colDefault)
1044  setValue(groupID, row, col);
1045  else
1046  setValue(groupID + " | " + getDataView()[row][col], row, col);
1047 
1048  //__COUT__ << getDataView()[row][col] << __E__;
1049 }
1050 
1051 //==============================================================================
1052 // removeRowFromGroup
1053 // Group entry can include | to place a record in multiple groups
1054 //
1055 // returns true if row was deleted because it had no group left
1056 bool TableView::removeRowFromGroup(const unsigned int& row,
1057  const unsigned int& col,
1058  const std::string& groupNeedle,
1059  bool deleteRowIfNoGroupLeft)
1060 {
1061  __COUT__ << "groupNeedle " << groupNeedle << __E__;
1062  std::set<std::string> groupIDList;
1063  if(!isEntryInGroupCol(row, col, groupNeedle, &groupIDList))
1064  {
1065  __SS__ << "GroupID (" << groupNeedle << ") removed from row (" << row
1066  << ") was already removed!" << __E__;
1067  __SS_THROW__;
1068  }
1069 
1070  // is in group, so
1071  // create new string based on set of groupids
1072  // but skip groupNeedle
1073 
1074  std::string newValue = "";
1075  unsigned int cnt = 0;
1076  for(const auto& groupID : groupIDList)
1077  {
1078  //__COUT__ << groupID << " " << groupNeedle << " " << newValue << __E__;
1079  if(groupID == groupNeedle)
1080  continue; // skip group to be removed
1081 
1082  if(cnt)
1083  newValue += " | ";
1084  newValue += groupID;
1085  }
1086 
1087  bool wasDeleted = false;
1088  if(deleteRowIfNoGroupLeft && newValue == "")
1089  {
1090  __COUT__ << "Delete row since it no longer part of any group." << __E__;
1091  deleteRow(row);
1092  wasDeleted = true;
1093  }
1094  else
1095  setValue(newValue, row, col);
1096 
1097  //__COUT__ << getDataView()[row][col] << __E__;
1098 
1099  return wasDeleted;
1100 }
1101 
1102 //==============================================================================
1103 // isEntryInGroup
1104 // All group link checking should use this function
1105 // so that handling is consistent
1106 //
1107 // Group entry can include | to place a record in multiple groups
1108 bool TableView::isEntryInGroup(const unsigned int& r,
1109  const std::string& childLinkIndex,
1110  const std::string& groupNeedle) const
1111 {
1112  unsigned int c = getColLinkGroupID(childLinkIndex); // column in question
1113 
1114  return isEntryInGroupCol(r, c, groupNeedle);
1115 }
1116 
1117 //==============================================================================
1118 // isEntryInGroupCol
1119 //
1120 // if *groupIDList != 0 return set of groupIDs found
1121 // useful for removing groupIDs.
1122 //
1123 // Group entry can include | to place a record in multiple groups
1124 //
1125 // Note: should mirror what happens in TableView::getSetOfGroupIDs
1126 bool TableView::isEntryInGroupCol(const unsigned int& r,
1127  const unsigned int& c,
1128  const std::string& groupNeedle,
1129  std::set<std::string>* groupIDList) const
1130 {
1131  unsigned int i = 0;
1132  unsigned int j = 0;
1133  bool found = false;
1134 
1135  //__COUT__ << "groupNeedle " << groupNeedle << __E__;
1136 
1137  // go through the full groupString extracting groups and comparing to groupNeedle
1138  for(; j < theDataView_[r][c].size(); ++j)
1139  if((theDataView_[r][c][j] == ' ' || // ignore leading white space or |
1140  theDataView_[r][c][j] == '|') &&
1141  i == j)
1142  ++i;
1143  else if((theDataView_[r][c][j] ==
1144  ' ' || // trailing white space or | indicates group
1145  theDataView_[r][c][j] == '|') &&
1146  i != j) // assume end of group name
1147  {
1148  if(groupIDList)
1149  groupIDList->emplace(theDataView_[r][c].substr(i, j - i));
1150 
1151  //__COUT__ << "Group found to compare: " <<
1152  // theDataView_[r][c].substr(i,j-i) << __E__;
1153  if(groupNeedle == theDataView_[r][c].substr(i, j - i))
1154  {
1155  if(!groupIDList) // dont return if caller is trying to get group list
1156  return true;
1157  found = true;
1158  }
1159  // if no match, setup i and j for next find
1160  i = j + 1;
1161  }
1162 
1163  if(i != j) // last group check (for case when no ' ' or '|')
1164  {
1165  if(groupIDList)
1166  groupIDList->emplace(theDataView_[r][c].substr(i, j - i));
1167 
1168  //__COUT__ << "Group found to compare: " <<
1169  // theDataView_[r][c].substr(i,j-i) << __E__;
1170  if(groupNeedle == theDataView_[r][c].substr(i, j - i))
1171  return true;
1172  }
1173 
1174  return found;
1175 }
1176 
1177 //==============================================================================
1178 // getSetOfGroupIDs
1179 // if row == -1, then considers all rows
1180 // else just that row
1181 // returns unique set of groupIds in GroupID column
1182 // associate with childLinkIndex
1183 //
1184 // Note: should mirror what happens in TableView::isEntryInGroupCol
1185 std::set<std::string> TableView::getSetOfGroupIDs(const std::string& childLinkIndex,
1186  unsigned int r) const
1187 {
1188  return getSetOfGroupIDs(getColLinkGroupID(childLinkIndex), r);
1189 }
1190 std::set<std::string> TableView::getSetOfGroupIDs(const unsigned int& c,
1191  unsigned int r) const
1192 {
1193  //__COUT__ << "GroupID col=" << (int)c << __E__;
1194 
1195  std::set<std::string> retSet;
1196 
1197  unsigned int i = 0;
1198  unsigned int j = 0;
1199 
1200  if(r != (unsigned int)-1)
1201  {
1202  if(r >= getNumberOfRows())
1203  {
1204  __SS__ << "Invalid row requested!" << __E__;
1205  __SS_THROW__;
1206  }
1207 
1208  StringMacros::getSetFromString(theDataView_[r][c], retSet);
1209  // //go through the full groupString extracting groups
1210  // //add each found groupId to set
1211  // for(;j<theDataView_[r][c].size();++j)
1212  // if((theDataView_[r][c][j] == ' ' || //ignore leading white space or |
1213  // theDataView_[r][c][j] == '|')
1214  // && i == j)
1215  // ++i;
1216  // else if((theDataView_[r][c][j] == ' ' || //trailing white space or |
1217  // indicates group theDataView_[r][c][j] == '|')
1218  // && i != j) // assume end of group name
1219  // {
1220  // //__COUT__ << "Group found: " <<
1221  // // theDataView_[r][c].substr(i,j-i) << __E__;
1222  //
1223  //
1224  // retSet.emplace(theDataView_[r][c].substr(i,j-i));
1225  //
1226  // //setup i and j for next find
1227  // i = j+1;
1228  // }
1229  //
1230  // if(i != j) //last group check (for case when no ' ' or '|')
1231  // retSet.emplace(theDataView_[r][c].substr(i,j-i));
1232  }
1233  else
1234  {
1235  // do all rows
1236  for(r = 0; r < getNumberOfRows(); ++r)
1237  {
1238  StringMacros::getSetFromString(theDataView_[r][c], retSet);
1239 
1240  // i=0;
1241  // j=0;
1242  //
1243  // //__COUT__ << (int)r << ": " << theDataView_[r][c] << __E__;
1244  //
1245  // //go through the full groupString extracting groups
1246  // //add each found groupId to set
1247  // for(;j<theDataView_[r][c].size();++j)
1248  // {
1249  // //__COUT__ << "i:" << i << " j:" << j << __E__;
1250  //
1251  // if((theDataView_[r][c][j] == ' ' || //ignore leading white
1252  // space or | theDataView_[r][c][j] == '|')
1253  // && i == j)
1254  // ++i;
1255  // else if((theDataView_[r][c][j] == ' ' || //trailing white
1256  // space or | indicates group theDataView_[r][c][j]
1257  // ==
1258  // '|')
1259  // && i != j) // assume end of group name
1260  // {
1261  // //__COUT__ << "Group found: " <<
1262  // // theDataView_[r][c].substr(i,j-i) << __E__;
1263  //
1264  // retSet.emplace(theDataView_[r][c].substr(i,j-i));
1265  //
1266  // //setup i and j for next find
1267  // i = j+1;
1268  // }
1269  // }
1270  //
1271  // if(i != j) //last group (for case when no ' ' or '|')
1272  // {
1273  // //__COUT__ << "Group found: " <<
1274  // // theDataView_[r][c].substr(i,j-i) << __E__;
1275  // retSet.emplace(theDataView_[r][c].substr(i,j-i));
1276  // }
1277  }
1278  }
1279 
1280  return retSet;
1281 }
1282 
1283 //==============================================================================
1284 // getColOfLinkGroupID
1285 // const version, if column not found throw error
1286 const unsigned int TableView::getColLinkGroupID(const std::string& childLinkIndex) const
1287 {
1288  if(!childLinkIndex.size())
1289  {
1290  __SS__ << "Empty childLinkIndex string parameter!" << __E__;
1291  __SS_THROW__;
1292  }
1293 
1294  const char* needleChildLinkIndex = &childLinkIndex[0];
1295 
1296  // allow space syntax to target a childLinkIndex from a different parentLinkIndex
1297  // e.g. "parentLinkIndex childLinkIndex"
1298  size_t spacePos = childLinkIndex.find(' ');
1299  if(spacePos != std::string::npos &&
1300  spacePos + 1 < childLinkIndex.size()) // make sure there are more characters
1301  {
1302  // found space syntax for targeting childLinkIndex
1303  needleChildLinkIndex = &childLinkIndex[spacePos + 1];
1304  }
1305 
1306  std::map<std::string, unsigned int>::const_iterator it =
1307  colLinkGroupIDs_.find(needleChildLinkIndex);
1308  if(it != // if already known, return it
1309  colLinkGroupIDs_.end())
1310  return it->second;
1311 
1312  __SS__
1313  << "Incompatible table for this group link. Table '" << tableName_
1314  << "' is missing a GroupID column with data type '"
1315  << TableViewColumnInfo::TYPE_START_GROUP_ID << "-" << needleChildLinkIndex
1316  << "'.\n\n"
1317  << "Note: you can separate the child GroupID column data type from "
1318  << "the parent GroupLink column data type; this is accomplished by using a space "
1319  << "character at the parent level - the string after the space will be treated "
1320  "as the "
1321  << "child GroupID column data type." << __E__;
1322  ss << "Existing Column GroupIDs: " << __E__;
1323  for(auto& groupIdColPair : colLinkGroupIDs_)
1324  ss << "\t" << groupIdColPair.first << " : col-" << groupIdColPair.second << __E__;
1325 
1326  ss << "Existing Column Types: " << __E__;
1327  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
1328  ss << "\t" << columnsInfo_[col].getType() << "() " << columnsInfo_[col].getName()
1329  << __E__;
1330 
1331  __SS_THROW__;
1332 } // end getColLinkGroupID()
1333 
1334 //==============================================================================
1335 unsigned int TableView::findRow(unsigned int col,
1336  const std::string& value,
1337  unsigned int offsetRow) const
1338 {
1339  for(unsigned int row = offsetRow; row < theDataView_.size(); ++row)
1340  {
1341  if(theDataView_[row][col] == value)
1342  return row;
1343  }
1344 
1345  __SS__ << "\tIn view: " << tableName_ << ", Can't find value=" << value
1346  << " in column named " << columnsInfo_[col].getName()
1347  << " with type=" << columnsInfo_[col].getType() << __E__;
1348  // Note: findRow gets purposely called by configuration GUI a lot looking for
1349  // exceptions so may not want to print out
1350  //__COUT__ << "\n" << ss.str();
1351  __SS_ONLY_THROW__;
1352 } // end findRow()
1353 
1354 //==============================================================================
1355 unsigned int TableView::findRowInGroup(unsigned int col,
1356  const std::string& value,
1357  const std::string& groupId,
1358  const std::string& childLinkIndex,
1359  unsigned int offsetRow) const
1360 {
1361  unsigned int groupIdCol = getColLinkGroupID(childLinkIndex);
1362  for(unsigned int row = offsetRow; row < theDataView_.size(); ++row)
1363  {
1364  if(theDataView_[row][col] == value && isEntryInGroupCol(row, groupIdCol, groupId))
1365  return row;
1366  }
1367 
1368  __SS__ << "\tIn view: " << tableName_ << ", Can't find in group the value=" << value
1369  << " in column named '" << columnsInfo_[col].getName()
1370  << "' with type=" << columnsInfo_[col].getType() << " and GroupID: '"
1371  << groupId << "' in column '" << groupIdCol
1372  << "' with GroupID child link index '" << childLinkIndex << "'" << __E__;
1373  // Note: findRowInGroup gets purposely called by configuration GUI a lot looking for
1374  // exceptions so may not want to print out
1375  __SS_ONLY_THROW__;
1376 } // end findRowInGroup()
1377 
1378 //==============================================================================
1379 // findCol
1380 // throws exception if column not found by name
1381 unsigned int TableView::findCol(const std::string& wildCardName) const
1382 {
1383  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
1384  if(StringMacros::wildCardMatch(wildCardName /*needle*/,
1385  columnsInfo_[col].getName() /*haystack*/))
1386  return col;
1387 
1388  __SS__ << "\tIn view: " << tableName_ << ", Can't find column named '" << wildCardName
1389  << "'" << __E__;
1390  ss << "Existing columns:\n";
1391  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
1392  ss << "\t" << columnsInfo_[col].getName() << "\n";
1393  // Note: findCol gets purposely called by configuration GUI a lot looking for
1394  // exceptions so may not want to print out
1395  __SS_ONLY_THROW__;
1396 } // end findCol()
1397 
1398 //==============================================================================
1399 // findColByType
1400 // return invalid if type not found
1401 unsigned int TableView::findColByType(const std::string& type, int startingCol) const
1402 {
1403  for(unsigned int col = startingCol; col < columnsInfo_.size(); ++col)
1404  if(columnsInfo_[col].getType() == type)
1405  return col;
1406 
1407  return INVALID;
1408 } // end findColByType()
1409 
1410 // Getters
1411 //==============================================================================
1412 const std::string& TableView::getUniqueStorageIdentifier(void) const
1413 {
1414  return uniqueStorageIdentifier_;
1415 }
1416 
1417 //==============================================================================
1418 const std::string& TableView::getTableName(void) const { return tableName_; }
1419 
1420 //==============================================================================
1421 const TableVersion& TableView::getVersion(void) const { return version_; }
1422 
1423 //==============================================================================
1424 const std::string& TableView::getComment(void) const { return comment_; }
1425 
1426 //==============================================================================
1427 const std::string& TableView::getAuthor(void) const { return author_; }
1428 
1429 //==============================================================================
1430 const time_t& TableView::getCreationTime(void) const { return creationTime_; }
1431 
1432 //==============================================================================
1433 const time_t& TableView::getLastAccessTime(void) const { return lastAccessTime_; }
1434 
1435 //==============================================================================
1436 const bool& TableView::getLooseColumnMatching(void) const
1437 {
1438  return fillWithLooseColumnMatching_;
1439 }
1440 
1441 //==============================================================================
1442 // getDataColumnSize
1443 const unsigned int TableView::getDataColumnSize(void) const
1444 {
1445  // if no data, give benefit of the doubt that phantom data has mockup column size
1446  if(!getNumberOfRows())
1447  return getNumberOfColumns();
1448  return theDataView_[0].size(); // number of columns in first row of data
1449 }
1450 
1451 //==============================================================================
1452 // getSourceColumnMismatch
1453 // The source information is only valid after modifying the table with ::fillFromJSON
1454 const unsigned int& TableView::getSourceColumnMismatch(void) const
1455 {
1456  return sourceColumnMismatchCount_;
1457 }
1458 
1459 //==============================================================================
1460 // getSourceColumnMissing
1461 // The source information is only valid after modifying the table with ::fillFromJSON
1462 const unsigned int& TableView::getSourceColumnMissing(void) const
1463 {
1464  return sourceColumnMissingCount_;
1465 }
1466 
1467 //==============================================================================
1468 // getSourceColumnNames
1469 // The source information is only valid after modifying the table with ::fillFromJSON
1470 const std::set<std::string>& TableView::getSourceColumnNames(void) const
1471 {
1472  return sourceColumnNames_;
1473 }
1474 
1475 //==============================================================================
1476 std::set<std::string> TableView::getColumnNames(void) const
1477 {
1478  std::set<std::string> retSet;
1479  for(auto& colInfo : columnsInfo_)
1480  retSet.emplace(colInfo.getName());
1481  return retSet;
1482 } // end getColumnNames()
1483 
1484 //==============================================================================
1485 std::map<std::string, unsigned int /*col*/> TableView::getColumnNamesMap(void) const
1486 {
1487  std::map<std::string, unsigned int /*col*/> retMap;
1488  unsigned int c = 0;
1489  for(auto& colInfo : columnsInfo_)
1490  retMap.emplace(std::make_pair(colInfo.getName(), c++));
1491  return retMap;
1492 } // end getColumnNamesMap()
1493 
1494 //==============================================================================
1495 std::set<std::string> TableView::getColumnStorageNames(void) const
1496 {
1497  std::set<std::string> retSet;
1498  for(auto& colInfo : columnsInfo_)
1499  retSet.emplace(colInfo.getStorageName());
1500  return retSet;
1501 }
1502 
1503 //==============================================================================
1504 std::vector<std::string> TableView::getDefaultRowValues(void) const
1505 {
1506  std::vector<std::string> retVec;
1507 
1508  // fill each col of new row with default values
1509  for(unsigned int col = 0; col < getNumberOfColumns(); ++col)
1510  {
1511  // if this is a fixed choice Link, and NO_LINK is not in list,
1512  // take first in list to avoid creating illegal rows.
1513  // NOTE: this is not a problem for standard fixed choice fields
1514  // because the default value is always required.
1515 
1516  if(columnsInfo_[col].isChildLink())
1517  {
1518  const std::vector<std::string>& theDataChoices =
1519  columnsInfo_[col].getDataChoices();
1520 
1521  // check if arbitrary values allowed
1522  if(!theDataChoices.size() || // if so, use default
1523  theDataChoices[0] == "arbitraryBool=1")
1524  retVec.push_back(columnsInfo_[col].getDefaultValue());
1525  else
1526  {
1527  bool skipOne =
1528  (theDataChoices.size() && theDataChoices[0] == "arbitraryBool=0");
1529  bool hasSkipped;
1530 
1531  // look for default value in list
1532 
1533  bool foundDefault = false;
1534  hasSkipped = false;
1535  for(const auto& choice : theDataChoices)
1536  if(skipOne && !hasSkipped)
1537  {
1538  hasSkipped = true;
1539  continue;
1540  }
1541  else if(choice == columnsInfo_[col].getDefaultValue())
1542  {
1543  foundDefault = true;
1544  break;
1545  }
1546 
1547  // use first choice if possible
1548  if(!foundDefault && theDataChoices.size() > (skipOne ? 1 : 0))
1549  retVec.push_back(theDataChoices[(skipOne ? 1 : 0)]);
1550  else // else stick with default
1551  retVec.push_back(columnsInfo_[col].getDefaultValue());
1552  }
1553  }
1554  else
1555  retVec.push_back(columnsInfo_[col].getDefaultValue());
1556  }
1557 
1558  return retVec;
1559 } // end getDefaultRowValues()
1560 
1561 //==============================================================================
1562 unsigned int TableView::getNumberOfRows(void) const { return theDataView_.size(); }
1563 
1564 //==============================================================================
1565 unsigned int TableView::getNumberOfColumns(void) const { return columnsInfo_.size(); }
1566 
1567 //==============================================================================
1568 const TableView::DataView& TableView::getDataView(void) const { return theDataView_; }
1569 
1570 //==============================================================================
1571 // TableView::DataView* TableView::getDataViewP(void)
1572 //{
1573 // return &theDataView_;
1574 //}
1575 
1576 //==============================================================================
1577 const std::vector<TableViewColumnInfo>& TableView::getColumnsInfo(void) const
1578 {
1579  return columnsInfo_;
1580 }
1581 
1582 //==============================================================================
1583 std::vector<TableViewColumnInfo>* TableView::getColumnsInfoP(void)
1584 {
1585  return &columnsInfo_;
1586 }
1587 //==============================================================================
1588 const TableViewColumnInfo& TableView::getColumnInfo(unsigned int column) const
1589 {
1590  if(column >= columnsInfo_.size())
1591  {
1592  std::stringstream errMsg;
1593  errMsg << __COUT_HDR_FL__ << "\nCan't find column " << column
1594  << "\n\n\n\nThe column info is likely missing due to incomplete "
1595  "Configuration View filling.\n\n"
1596  << __E__;
1597  __THROW__(errMsg.str().c_str());
1598  }
1599  return columnsInfo_[column];
1600 } // end getColumnInfo()
1601 
1602 // Setters
1603 //==============================================================================
1604 void TableView::setUniqueStorageIdentifier(const std::string& storageUID)
1605 {
1606  uniqueStorageIdentifier_ = storageUID;
1607 }
1608 
1609 //==============================================================================
1610 void TableView::setTableName(const std::string& name) { tableName_ = name; }
1611 
1612 //==============================================================================
1613 void TableView::setComment(const std::string& comment) { comment_ = comment; }
1614 
1615 //==============================================================================
1616 void TableView::setURIEncodedComment(const std::string& uriComment)
1617 {
1618  comment_ = StringMacros::decodeURIComponent(uriComment);
1619 }
1620 
1621 //==============================================================================
1622 void TableView::setAuthor(const std::string& author) { author_ = author; }
1623 
1624 //==============================================================================
1625 void TableView::setCreationTime(time_t t) { creationTime_ = t; }
1626 
1627 //==============================================================================
1628 void TableView::setLastAccessTime(time_t t) { lastAccessTime_ = t; }
1629 
1630 //==============================================================================
1631 void TableView::setLooseColumnMatching(bool setValue)
1632 {
1633  fillWithLooseColumnMatching_ = setValue;
1634 }
1635 
1636 //==============================================================================
1637 void TableView::reset(void)
1638 {
1639  version_ = -1;
1640  comment_ = "";
1641  author_ + "";
1642  columnsInfo_.clear();
1643  theDataView_.clear();
1644 } // end reset()
1645 
1646 //==============================================================================
1647 void TableView::print(std::ostream& out) const
1648 {
1649  out << "============================================================================="
1650  "="
1651  << __E__;
1652  out << "Print: " << tableName_ << " Version: " << version_ << " Comment: " << comment_
1653  << " Author: " << author_ << " Creation Time: " << ctime(&creationTime_) << __E__;
1654  out << "\t\tNumber of Cols " << getNumberOfColumns() << __E__;
1655  out << "\t\tNumber of Rows " << getNumberOfRows() << __E__;
1656 
1657  out << "Columns:\t";
1658  for(int i = 0; i < (int)columnsInfo_.size(); ++i)
1659  out << i << ":" << columnsInfo_[i].getName() << ":"
1660  << columnsInfo_[i].getStorageName() << ":" << columnsInfo_[i].getType() << ":"
1661  << columnsInfo_[i].getDataType() << "\t ";
1662  out << __E__;
1663 
1664  out << "Rows:" << __E__;
1665  int num;
1666  std::string val;
1667  for(int r = 0; r < (int)getNumberOfRows(); ++r)
1668  {
1669  out << (int)r << ":\t";
1670  for(int c = 0; c < (int)getNumberOfColumns(); ++c)
1671  {
1672  out << (int)c << ":";
1673 
1674  // if fixed choice type, print index in choice
1675  if(columnsInfo_[c].getType() == TableViewColumnInfo::TYPE_FIXED_CHOICE_DATA)
1676  {
1677  int choiceIndex = -1;
1678  std::vector<std::string> choices = columnsInfo_[c].getDataChoices();
1679  val = StringMacros::convertEnvironmentVariables(theDataView_[r][c]);
1680 
1681  if(val == columnsInfo_[c].getDefaultValue())
1682  choiceIndex = 0;
1683  else
1684  {
1685  for(int i = 0; i < (int)choices.size(); ++i)
1686  if(val == choices[i])
1687  choiceIndex = i + 1;
1688  }
1689 
1690  out << "ChoiceIndex=" << choiceIndex << ":";
1691  }
1692 
1693  out << theDataView_[r][c];
1694  // stopped using below, because it is called sometimes during debugging when
1695  // numbers are set to environment variables:
1696  // if(columnsInfo_[c].getDataType() == "NUMBER")
1697  // {
1698  // getValue(num,r,c,false);
1699  // out << num;
1700  // }
1701  // else
1702  // {
1703  // getValue(val,r,c,false);
1704  // out << val;
1705  // }
1706  out << "\t\t";
1707  }
1708  out << __E__;
1709  }
1710 } // end print()
1711 
1712 //==============================================================================
1713 void TableView::printJSON(std::ostream& out) const
1714 {
1715  out << "{\n";
1716  out << "\"NAME\" : \"" << tableName_ << "\",\n";
1717 
1718  // out << "\"VERSION\" : \"" << version_ << "\"\n";
1719 
1720  out << "\"COMMENT\" : ";
1721 
1722  // output escaped comment
1723  std::string val;
1724  val = comment_;
1725  out << "\"";
1726  for(unsigned int i = 0; i < val.size(); ++i)
1727  {
1728  if(val[i] == '\n')
1729  out << "\\n";
1730  else if(val[i] == '\t')
1731  out << "\\t";
1732  else if(val[i] == '\r')
1733  out << "\\r";
1734  else
1735  {
1736  // escaped characters need a
1737  if(val[i] == '"' || val[i] == '\\')
1738  out << '\\';
1739  out << val[i];
1740  }
1741  }
1742  out << "\",\n";
1743 
1744  out << "\"AUTHOR\" : \"" << author_ << "\",\n";
1745  out << "\"CREATION_TIME\" : " << creationTime_ << ",\n";
1746 
1747  // USELESS... out << "\"NUM_OF_COLS\" : " << getNumberOfColumns() << ",\n";
1748  // USELESS... out << "\"NUM_OF_ROWS\" : " << getNumberOfRows() << ",\n";
1749 
1750  out << "\"COL_TYPES\" : {\n";
1751  for(int c = 0; c < (int)getNumberOfColumns(); ++c)
1752  {
1753  out << "\t\t\"" << columnsInfo_[c].getStorageName() << "\" : ";
1754  out << "\"" << columnsInfo_[c].getDataType() << "\"";
1755  if(c + 1 < (int)getNumberOfColumns())
1756  out << ",";
1757  out << "\n";
1758  }
1759  out << "},\n"; // close COL_TYPES
1760 
1761  out << "\"DATA_SET\" : [\n";
1762  int num;
1763  for(int r = 0; r < (int)getNumberOfRows(); ++r)
1764  {
1765  out << "\t{\n";
1766  for(int c = 0; c < (int)getNumberOfColumns(); ++c)
1767  {
1768  out << "\t\t\"" << columnsInfo_[c].getStorageName() << "\" : ";
1769 
1770  out << "\"" << getEscapedValueAsString(r, c, false)
1771  << "\""; // do not convert env variables
1772 
1773  if(c + 1 < (int)getNumberOfColumns())
1774  out << ",";
1775  out << "\n";
1776  }
1777  out << "\t}";
1778  if(r + 1 < (int)getNumberOfRows())
1779  out << ",";
1780  out << "\n";
1781  }
1782  out << "]\n"; // close DATA_SET
1783 
1784  out << "}";
1785 } // end printJSON()
1786 
1787 //==============================================================================
1788 // restoreJSONStringEntities
1789 // returns string with literals \n \t \" \r \\ replaced with char
1790 std::string restoreJSONStringEntities(const std::string& str)
1791 {
1792  unsigned int sz = str.size();
1793  if(!sz)
1794  return ""; // empty string, returns empty string
1795 
1796  std::stringstream retStr;
1797  unsigned int i = 0;
1798  for(; i < sz - 1; ++i)
1799  {
1800  if(str[i] == '\\') // if 2 char escape sequence, replace with char
1801  switch(str[i + 1])
1802  {
1803  case 'n':
1804  retStr << '\n';
1805  ++i;
1806  break;
1807  case '"':
1808  retStr << '"';
1809  ++i;
1810  break;
1811  case 't':
1812  retStr << '\t';
1813  ++i;
1814  break;
1815  case 'r':
1816  retStr << '\r';
1817  ++i;
1818  break;
1819  case '\\':
1820  retStr << '\\';
1821  ++i;
1822  break;
1823  default:
1824  retStr << str[i];
1825  }
1826  else
1827  retStr << str[i];
1828  }
1829  if(i == sz - 1)
1830  retStr << str[sz - 1]; // output last character (which can't escape anything)
1831 
1832  return retStr.str();
1833 } // end restoreJSONStringEntities()
1834 
1835 //==============================================================================
1836 // fillFromJSON
1837 // Clears and fills the view from the JSON string.
1838 // Returns -1 on failure
1839 //
1840 // first level keys:
1841 // NAME
1842 // DATA_SET
1843 int TableView::fillFromJSON(const std::string& json)
1844 {
1845  std::vector<std::string> keys;
1846  keys.push_back("NAME");
1847  keys.push_back("COMMENT");
1848  keys.push_back("AUTHOR");
1849  keys.push_back("CREATION_TIME");
1850  // keys.push_back ("COL_TYPES");
1851  keys.push_back("DATA_SET");
1852  enum
1853  {
1854  CV_JSON_FILL_NAME,
1855  CV_JSON_FILL_COMMENT,
1856  CV_JSON_FILL_AUTHOR,
1857  CV_JSON_FILL_CREATION_TIME,
1858  // CV_JSON_FILL_COL_TYPES,
1859  CV_JSON_FILL_DATA_SET
1860  };
1861 
1862  //__COUTV__(json);
1863 
1864  sourceColumnMismatchCount_ = 0;
1865  sourceColumnMissingCount_ = 0;
1866  sourceColumnNames_.clear(); // reset
1867  unsigned int colFoundCount = 0;
1868  unsigned int i = 0;
1869  unsigned int row = -1;
1870  unsigned int colSpeedup = 0;
1871  unsigned int startString, startNumber, endNumber = -1;
1872  unsigned int bracketCount = 0;
1873  unsigned int sqBracketCount = 0;
1874  bool inQuotes = 0;
1875  bool newString = 0;
1876  bool newValue = 0;
1877  bool isDataArray = 0;
1878  bool keyIsMatch, keyIsComment;
1879  unsigned int keyIsMatchIndex, keyIsMatchStorageIndex, keyIsMatchCommentIndex;
1880  const std::string COMMENT_ALT_KEY = "COMMENT";
1881 
1882  std::string extractedString = "", currKey = "", currVal = "";
1883  unsigned int currDepth;
1884 
1885  std::vector<std::string> jsonPath;
1886  std::vector<char> jsonPathType; // indicator of type in jsonPath: { [ K
1887  char lastPopType = '_'; // either: _ { [ K
1888  // _ indicates reset pop (this happens when a new {obj} starts)
1889  unsigned int matchedKey = -1;
1890  unsigned int lastCol = -1;
1891 
1892  // find all depth 1 matching keys
1893  for(; i < json.size(); ++i)
1894  {
1895  switch(json[i])
1896  {
1897  case '"':
1898  if(i - 1 < json.size() && // ignore if escaped
1899  json[i - 1] == '\\')
1900  break;
1901 
1902  inQuotes = !inQuotes; // toggle in quotes if not escaped
1903  if(inQuotes)
1904  startString = i;
1905  else
1906  {
1907  extractedString = restoreJSONStringEntities(
1908  json.substr(startString + 1, i - startString - 1));
1909  newString = 1; // have new string!
1910  }
1911  break;
1912  case ':':
1913  if(inQuotes)
1914  break; // skip if in quote
1915 
1916  // must be a json object level to have a key
1917  if(jsonPathType[jsonPathType.size() - 1] != '{' ||
1918  !newString) // and must have a string for key
1919  {
1920  __COUT__ << "Invalid ':' position" << __E__;
1921  return -1;
1922  }
1923 
1924  // valid, so take key
1925  jsonPathType.push_back('K');
1926  jsonPath.push_back(extractedString);
1927  startNumber = i;
1928  newString = 0; // clear flag
1929  endNumber = -1; // reset end number index
1930  break;
1931 
1932  // if(isKey ||
1933  // isDataArray)
1934  // {
1935  // std::cout << "Invalid ':' position" << __E__;
1936  // return -1;
1937  // }
1938  // isKey = 1; //new value is a key
1939  // newValue = 1;
1940  // startNumber = i;
1941  // break;
1942  case ',':
1943  if(inQuotes)
1944  break; // skip if in quote
1945  if(lastPopType == '{') // don't need value again of nested object
1946  {
1947  // check if the nested object was the value to a key, if so, pop key
1948  if(jsonPathType[jsonPathType.size() - 1] == 'K')
1949  {
1950  lastPopType = 'K';
1951  jsonPath.pop_back();
1952  jsonPathType.pop_back();
1953  }
1954  break; // skip , handling if {obj} just ended
1955  }
1956 
1957  if(newString)
1958  currVal = extractedString;
1959  else // number value
1960  {
1961  if(endNumber == (unsigned int)-1 || // take i as end number if needed
1962  endNumber <= startNumber)
1963  endNumber = i;
1964  // extract number value
1965  if(endNumber <= startNumber) // empty data, could be {}
1966  currVal = "";
1967  else
1968  currVal = json.substr(startNumber + 1, endNumber - startNumber - 1);
1969  }
1970 
1971  currDepth = bracketCount;
1972 
1973  if(jsonPathType[jsonPathType.size() - 1] == 'K') // this is the value to key
1974  {
1975  currKey = jsonPath[jsonPathType.size() - 1];
1976  newValue = 1; // new value to consider!
1977 
1978  // pop key
1979  lastPopType = 'K';
1980  jsonPath.pop_back();
1981  jsonPathType.pop_back();
1982  }
1983  else if(jsonPathType[jsonPathType.size() - 1] ==
1984  '[') // this is a value in array
1985  {
1986  // key is last key
1987  for(unsigned int k = jsonPathType.size() - 2; k < jsonPathType.size();
1988  --k)
1989  if(jsonPathType[k] == 'K')
1990  {
1991  currKey = jsonPath[k];
1992  break;
1993  }
1994  else if(k == 0)
1995  {
1996  __COUT__ << "Invalid array position" << __E__;
1997  return -1;
1998  }
1999 
2000  newValue = 1; // new value to consider!
2001  isDataArray = 1;
2002  }
2003  else // { is an error
2004  {
2005  __COUT__ << "Invalid ',' position" << __E__;
2006  return -1;
2007  }
2008 
2009  startNumber = i;
2010  break;
2011 
2012  case '{':
2013  if(inQuotes)
2014  break; // skip if in quote
2015  lastPopType = '_'; // reset because of new object
2016  jsonPathType.push_back('{');
2017  jsonPath.push_back("{");
2018  ++bracketCount;
2019  break;
2020 
2021  // ++bracketCount;
2022  // isDataArray = 0;
2023  // isKey = 0;
2024  // endingObject = 0;
2025  // break;
2026  case '}':
2027  if(inQuotes)
2028  break; // skip if in quote
2029 
2030  if(lastPopType != '{' && // don't need value again of nested object
2031  jsonPathType[jsonPathType.size() - 1] == 'K') // this is the value to key
2032  {
2033  currDepth = bracketCount;
2034  currKey = jsonPath[jsonPathType.size() - 1];
2035  if(newString)
2036  currVal = extractedString;
2037  else // number value
2038  {
2039  if(endNumber == (unsigned int)-1 || // take i as end number if needed
2040  endNumber <= startNumber)
2041  endNumber = i;
2042  // extract val
2043  if(endNumber <= startNumber) // empty data, could be {}
2044  currVal = "";
2045  else
2046  currVal =
2047  json.substr(startNumber + 1, endNumber - startNumber - 1);
2048  }
2049  newValue = 1; // new value to consider!
2050  // pop key
2051  jsonPath.pop_back();
2052  jsonPathType.pop_back();
2053  }
2054  // pop {
2055  if(jsonPathType[jsonPathType.size() - 1] != '{')
2056  {
2057  __COUT__ << "Invalid '}' position" << __E__;
2058  return -1;
2059  }
2060  lastPopType = '{';
2061  jsonPath.pop_back();
2062  jsonPathType.pop_back();
2063  --bracketCount;
2064  break;
2065  case '[':
2066  if(inQuotes)
2067  break; // skip if in quote
2068  jsonPathType.push_back('[');
2069  jsonPath.push_back("[");
2070  ++sqBracketCount;
2071  startNumber = i;
2072  break;
2073  case ']':
2074  if(inQuotes)
2075  break; // skip if in quote
2076 
2077  // must be an array at this level (in order to close it)
2078  if(jsonPathType[jsonPathType.size() - 1] != '[')
2079  {
2080  __COUT__ << "Invalid ']' position" << __E__;
2081  return -1;
2082  }
2083 
2084  currDepth = bracketCount;
2085 
2086  // This is an array value
2087  if(newString)
2088  currVal = extractedString;
2089  else // number value
2090  {
2091  if(endNumber == (unsigned int)-1 || // take i as end number if needed
2092  endNumber <= startNumber)
2093  endNumber = i;
2094  // extract val
2095  if(endNumber <= startNumber) // empty data, could be {}
2096  currVal = "";
2097  else
2098  currVal = json.substr(startNumber + 1, endNumber - startNumber - 1);
2099  }
2100  isDataArray = 1;
2101 
2102  // key is last key
2103  for(unsigned int k = jsonPathType.size() - 2; k < jsonPathType.size(); --k)
2104  if(jsonPathType[k] == 'K')
2105  {
2106  currKey = jsonPath[k];
2107  break;
2108  }
2109  else if(k == 0)
2110  {
2111  __COUT__ << "Invalid array position" << __E__;
2112  return -1;
2113  }
2114 
2115  // pop [
2116  if(jsonPathType[jsonPathType.size() - 1] != '[')
2117  {
2118  __COUT__ << "Invalid ']' position" << __E__;
2119  return -1;
2120  }
2121  lastPopType = '[';
2122  jsonPath.pop_back();
2123  jsonPathType.pop_back();
2124  --sqBracketCount;
2125  break;
2126  case ' ': // white space handling for numbers
2127  case '\t':
2128  case '\n':
2129  case '\r':
2130  if(inQuotes)
2131  break; // skip if in quote
2132  if(startNumber != (unsigned int)-1 && endNumber == (unsigned int)-1)
2133  endNumber = i;
2134  startNumber = i;
2135  break;
2136  default:;
2137  }
2138 
2139  if(0) // for debugging
2140  {
2141  std::cout << i << ":\t" << json[i] << " - ";
2142 
2143  std::cout << "ExtKey=";
2144  for(unsigned int k = 0; k < jsonPath.size(); ++k)
2145  std::cout << jsonPath[k] << "/";
2146  std::cout << " - ";
2147  std::cout << lastPopType << " ";
2148  std::cout << bracketCount << " ";
2149  std::cout << sqBracketCount << " ";
2150  std::cout << inQuotes << " ";
2151  std::cout << newValue << "-";
2152  std::cout << currKey << "-{" << currDepth << "}:";
2153  std::cout << currVal << " ";
2154  std::cout << startNumber << "-";
2155  std::cout << endNumber << " ";
2156  std::cout << "\n";
2157  }
2158 
2159  // continue;
2160 
2161  // handle a new completed value
2162  if(newValue)
2163  {
2164  // std::cout << "ExtKey=";
2165  // for(unsigned int k=0;k<jsonPath.size();++k)
2166  // std::cout << jsonPath[k] << "/";
2167  //
2168  // std::cout << currKey << "-{" << currDepth << "}: - ";
2169  //
2170  // if(isDataArray)
2171  // std::cout << "Array:: ";
2172  // if(newString)
2173  // std::cout << "New String:: ";
2174  // else
2175  // std::cout << "New Number:: ";
2176  //
2177  // std::cout << currVal << "\n";
2178 
2179  // extract only what we care about
2180  // for TableView only care about matching depth 1
2181 
2182  // handle matching depth 1 keys
2183 
2184  matchedKey = -1; // init to unfound
2185  for(unsigned int k = 0; k < keys.size(); ++k)
2186  if((currDepth == 1 && keys[k] == currKey) ||
2187  (currDepth > 1 && keys[k] == jsonPath[1]))
2188  matchedKey = k;
2189 
2190  if(matchedKey != (unsigned int)-1)
2191  {
2192  // std::cout << "New Data for:: key[" << matchedKey << "]-"
2193  //<< keys[matchedKey] << "\n";
2194 
2195  switch(matchedKey)
2196  {
2197  case CV_JSON_FILL_NAME:
2198  if(currDepth == 1)
2199  setTableName(currVal);
2200  break;
2201  case CV_JSON_FILL_COMMENT:
2202  if(currDepth == 1)
2203  setComment(currVal);
2204  break;
2205  case CV_JSON_FILL_AUTHOR:
2206  if(currDepth == 1)
2207  setAuthor(currVal);
2208  break;
2209  case CV_JSON_FILL_CREATION_TIME:
2210  if(currDepth == 1)
2211  setCreationTime(strtol(currVal.c_str(), 0, 10));
2212  break;
2213  // case CV_JSON_FILL_COL_TYPES:
2214  //
2215  // break;
2216  case CV_JSON_FILL_DATA_SET:
2217  // std::cout << "CV_JSON_FILL_DATA_SET New Data for::
2218  //"
2219  //<< matchedKey << "]-" << keys[matchedKey]
2220  //<<
2221  //"/" << currDepth << ".../" << currKey <<
2222  //"\n";
2223 
2224  if(currDepth == 2) // second level depth
2225  {
2226  // if matches first column name.. then add new row
2227  // else add to current row
2228  unsigned int col, ccnt = 0;
2229  unsigned int noc = getNumberOfColumns();
2230  for(; ccnt < noc; ++ccnt)
2231  {
2232  // use colSpeedup to change the first column we search
2233  // for each iteration.. since we expect the data to
2234  // be arranged in column order
2235 
2236  if(fillWithLooseColumnMatching_)
2237  {
2238  // loose column matching makes no attempt to
2239  // match the column names
2240  // just assumes the data is in the correct order
2241 
2242  col = colSpeedup;
2243 
2244  // auto matched
2245  if(col <= lastCol) // add row (use lastCol in case new
2246  // column-0 was added
2247  row = addRow();
2248  lastCol = col;
2249  if(getNumberOfRows() == 1) // only for first row
2250  sourceColumnNames_.emplace(currKey);
2251 
2252  // add value to row and column
2253 
2254  if(row >= getNumberOfRows())
2255  {
2256  __SS__ << "Invalid row"
2257  << __E__; // should be impossible?
2258  std::cout << ss.str();
2259  __SS_THROW__;
2260  return -1;
2261  }
2262 
2263  theDataView_[row][col] =
2264  currVal; // THERE IS NO CHECK FOR WHAT IS READ FROM
2265  // THE DATABASE. IT SHOULD BE ALREADY
2266  // CONSISTENT
2267  break;
2268  }
2269  else
2270  {
2271  col = (ccnt + colSpeedup) % noc;
2272 
2273  // match key by ignoring '_'
2274  // also accept COMMENT == COMMENT_DESCRIPTION
2275  // (this is for backwards compatibility..)
2276  keyIsMatch = true;
2277  keyIsComment = true;
2278  for(keyIsMatchIndex = 0,
2279  keyIsMatchStorageIndex = 0,
2280  keyIsMatchCommentIndex = 0;
2281  keyIsMatchIndex < currKey.size();
2282  ++keyIsMatchIndex)
2283  {
2284  if(columnsInfo_[col]
2285  .getStorageName()[keyIsMatchStorageIndex] ==
2286  '_')
2287  ++keyIsMatchStorageIndex; // skip to next storage
2288  // character
2289  if(currKey[keyIsMatchIndex] == '_')
2290  continue; // skip to next character
2291 
2292  // match to storage name
2293  if(keyIsMatchStorageIndex >=
2294  columnsInfo_[col].getStorageName().size() ||
2295  currKey[keyIsMatchIndex] !=
2296  columnsInfo_[col]
2297  .getStorageName()[keyIsMatchStorageIndex])
2298  {
2299  // size mismatch or character mismatch
2300  keyIsMatch = false;
2301  if(!keyIsComment)
2302  break;
2303  }
2304 
2305  // check also if alternate comment is matched
2306  if(keyIsComment &&
2307  keyIsMatchCommentIndex < COMMENT_ALT_KEY.size())
2308  {
2309  if(currKey[keyIsMatchIndex] !=
2310  COMMENT_ALT_KEY[keyIsMatchCommentIndex])
2311  {
2312  // character mismatch with COMMENT
2313  keyIsComment = false;
2314  }
2315  }
2316 
2317  ++keyIsMatchStorageIndex; // go to next character
2318  }
2319 
2320  if(keyIsMatch ||
2321  keyIsComment) // currKey ==
2322  // columnsInfo_[c].getStorageName())
2323  {
2324  // matched
2325  if(col <= lastCol) // add row (use lastCol in case
2326  // new column-0 was added
2327  {
2328  if(getNumberOfRows()) // skip first time
2329  sourceColumnMissingCount_ +=
2330  getNumberOfColumns() - colFoundCount;
2331 
2332  colFoundCount = 0; // reset column found count
2333  row = addRow();
2334  }
2335  lastCol = col;
2336  ++colFoundCount;
2337 
2338  if(getNumberOfRows() == 1) // only for first row
2339  sourceColumnNames_.emplace(currKey);
2340 
2341  // add value to row and column
2342 
2343  if(row >= getNumberOfRows())
2344  {
2345  __SS__ << "Invalid row"
2346  << __E__; // should be impossible?!
2347  __COUT__ << "\n" << ss.str();
2348  __SS_THROW__;
2349  return -1; // never gets here
2350  }
2351 
2352  theDataView_[row][col] = currVal;
2353  break;
2354  }
2355  }
2356  }
2357 
2358  colSpeedup =
2359  (colSpeedup + 1) %
2360  noc; // short cut to proper column hopefully in next search
2361 
2362  if(ccnt >= getNumberOfColumns())
2363  {
2364  __SS__
2365  << "\n\nInvalid column in JSON source data: " << currKey
2366  << " not found in column names of table named "
2367  << getTableName() << "."
2368  << __E__; // input data doesn't match config description
2369  __COUT__ << "\n" << ss.str();
2370  // CHANGED on 11/10/2016
2371  // to.. try just not populating data instead of error
2372  ++sourceColumnMismatchCount_; // but count errors
2373  if(getNumberOfRows() == 1) // only for first row
2374  sourceColumnNames_.emplace(currKey);
2375 
2376  //__SS_THROW__;
2377  __COUT_WARN__
2378  << "Ignoring error, and not populating missing column."
2379  << __E__;
2380  }
2381  }
2382  break;
2383  default:; // unknown match?
2384  }
2385  }
2386 
2387  // clean up handling of new value
2388 
2389  newString = 0; // toggle flag
2390  newValue = 0; // toggle flag
2391  isDataArray = 0;
2392  endNumber = -1; // reset end number index
2393  }
2394 
2395  // if(i>200) break; //185
2396  }
2397 
2398  //__COUT__ << "Done!" << __E__;
2399 
2400  // print();
2401 
2402  return 0; // success
2403 } // end fillFromJSON()
2404 
2405 //==============================================================================
2406 bool TableView::isURIEncodedCommentTheSame(const std::string& comment) const
2407 {
2408  std::string compareStr = StringMacros::decodeURIComponent(comment);
2409  return comment_ == compareStr;
2410 }
2411 //
2413 // bool TableView::isValueTheSame(const std::string &valueStr,
2414 // unsigned int r, unsigned int c) const
2415 //{
2416 // __COUT__ << "valueStr " << valueStr << __E__;
2417 //
2418 // if(!(c < columnsInfo_.size() && r < getNumberOfRows()))
2419 // {
2420 // __SS__ << "Invalid row (" << (int)r << ") col (" << (int)c << ") requested!" <<
2421 //__E__;
2422 // __SS_THROW__;
2423 // }
2424 //
2425 // __COUT__ << "originalValueStr " << theDataView_[r][c] << __E__;
2426 //
2427 // if(columnsInfo_[c].getDataType() == TableViewColumnInfo::DATATYPE_TIME)
2428 // {
2429 // time_t valueTime(strtol(valueStr.c_str(),0,10));
2430 // time_t originalValueTime;
2431 // getValue(originalValueTime,r,c);
2432 // __COUT__ << "time_t valueStr " << valueTime << __E__;
2433 // __COUT__ << "time_t originalValueStr " << originalValueTime << __E__;
2434 // return valueTime == originalValueTime;
2435 // }
2436 // else
2437 // {
2438 // return valueStr == theDataView_[r][c];
2439 // }
2440 //}
2441 
2442 //==============================================================================
2443 // fillFromCSV
2444 // Fills the view from the CSV string.
2445 //
2446 // Note: converts all %## to the ascii character, # is hex nibble
2447 // (e.g. '%' must be represented as "%25")
2448 //
2449 // dataOffset := starting destination row
2450 //
2451 // while there are row entries in the data.. replace
2452 // data range from [dataOffset, dataOffset+chunkSize-1]
2453 // ... note if less rows, this means rows were deleted
2454 // ... if more, then rows were added.
2455 //
2456 // ',' next cell delimiter
2457 // ';' next row delimiter
2458 //
2459 //
2460 // if author == "", do nothing special for author and timestamp column
2461 // if author != "", assign author for any row that has been modified, and assign now as
2462 // timestamp
2463 //
2464 // Returns -1 if data was same and pre-existing content
2465 // Returns 1 if data was same, but columns are different
2466 // otherwise 0
2467 //
2468 int TableView::fillFromCSV(const std::string& data,
2469  const int& dataOffset,
2470  const std::string& author)
2471 {
2472  int retVal = 0;
2473 
2474  int r = dataOffset;
2475  int c = 0;
2476 
2477  int i = 0; // use to parse data std::string
2478  int j = data.find(',', i); // find next cell delimiter
2479  int k = data.find(';', i); // find next row delimiter
2480 
2481  bool rowWasModified;
2482  unsigned int countRowsModified = 0;
2483  int authorCol = findColByType(TableViewColumnInfo::TYPE_AUTHOR);
2484  int timestampCol = findColByType(TableViewColumnInfo::TYPE_TIMESTAMP);
2485  // std::string valueStr, tmpTimeStr, originalValueStr;
2486 
2487  while(k != (int)(std::string::npos))
2488  {
2489  rowWasModified = false;
2490  if(r >= (int)getNumberOfRows())
2491  {
2492  addRow();
2493  //__COUT__ << "Row added" << __E__;
2494  rowWasModified = true;
2495  }
2496 
2497  while(j < k && j != (int)(std::string::npos))
2498  {
2499  //__COUT__ << "Col " << (int)c << __E__;
2500 
2501  // skip last 2 columns
2502  if(c >= (int)getNumberOfColumns() - 2)
2503  {
2504  i = j + 1;
2505  j = data.find(',', i); // find next cell delimiter
2506  ++c;
2507  continue;
2508  }
2509 
2510  if(setURIEncodedValue(data.substr(i, j - i), r, c))
2511  rowWasModified = true;
2512 
2513  i = j + 1;
2514  j = data.find(',', i); // find next cell delimiter
2515  ++c;
2516  }
2517 
2518  // if row was modified, assign author and timestamp
2519  if(author != "" && rowWasModified)
2520  {
2521  __COUT__ << "Row=" << (int)r << " was modified!" << __E__;
2522  setValue(author, r, authorCol);
2523  setValue(time(0), r, timestampCol);
2524  }
2525 
2526  if(rowWasModified)
2527  ++countRowsModified;
2528 
2529  ++r;
2530  c = 0;
2531 
2532  i = k + 1;
2533  j = data.find(',', i); // find next cell delimiter
2534  k = data.find(';', i); // find new row delimiter
2535  }
2536 
2537  // delete excess rows
2538  while(r < (int)getNumberOfRows())
2539  {
2540  deleteRow(r);
2541  __COUT__ << "Row deleted: " << (int)r << __E__;
2542  ++countRowsModified;
2543  }
2544 
2545  __COUT_INFO__ << "countRowsModified=" << countRowsModified << __E__;
2546 
2547  if(!countRowsModified)
2548  {
2549  // check that source columns match storage name
2550  // otherwise allow same data...
2551 
2552  bool match = getColumnStorageNames().size() == getSourceColumnNames().size();
2553  if(match)
2554  {
2555  for(auto& destColName : getColumnStorageNames())
2556  if(getSourceColumnNames().find(destColName) ==
2557  getSourceColumnNames().end())
2558  {
2559  __COUT__ << "Found column name mismach for '" << destColName
2560  << "'... So allowing same data!" << __E__;
2561 
2562  match = false;
2563  break;
2564  }
2565  }
2566  // if still a match, do not allow!
2567  if(match)
2568  {
2569  __SS__ << "No rows were modified! No reason to fill a view with same content."
2570  << __E__;
2571  __COUT__ << "\n" << ss.str();
2572  return -1;
2573  }
2574  // else mark with retVal
2575  retVal = 1;
2576  } // end same check
2577 
2578  // print(); //for debugging
2579 
2580  // setup sourceColumnNames_ to be correct
2581  sourceColumnNames_.clear();
2582  for(unsigned int i = 0; i < getNumberOfColumns(); ++i)
2583  sourceColumnNames_.emplace(getColumnsInfo()[i].getStorageName());
2584 
2585  init(); // verify new table (throws runtime_errors)
2586 
2587  // printout for debugging
2588  // __SS__ << "\n";
2589  // print(ss);
2590  // __COUT__ << "\n" << ss.str() << __E__;
2591 
2592  return retVal;
2593 } // end fillFromCSV()
2594 
2595 //==============================================================================
2596 // setURIEncodedValue
2597 // converts all %## to the ascii character
2598 // returns true if value was different than original value
2599 //
2600 //
2601 // if author == "", do nothing special for author and timestamp column
2602 // if author != "", assign author for any row that has been modified, and assign now as
2603 // timestamp
2604 bool TableView::setURIEncodedValue(const std::string& value,
2605  const unsigned int& r,
2606  const unsigned int& c,
2607  const std::string& author)
2608 {
2609  if(!(c < columnsInfo_.size() && r < getNumberOfRows()))
2610  {
2611  __SS__ << "Invalid row (" << (int)r << ") col (" << (int)c << ") requested!"
2612  << "Number of Rows = " << getNumberOfRows()
2613  << "Number of Columns = " << columnsInfo_.size() << __E__;
2614  print(ss);
2615  __SS_THROW__;
2616  }
2617 
2618  std::string valueStr = StringMacros::decodeURIComponent(value);
2619  std::string originalValueStr =
2620  getValueAsString(r, c, false); // do not convert env variables
2621 
2622  //__COUT__ << "valueStr " << valueStr << __E__;
2623  //__COUT__ << "originalValueStr " << originalValueStr << __E__;
2624 
2625  if(columnsInfo_[c].getDataType() == TableViewColumnInfo::DATATYPE_NUMBER)
2626  {
2627  // check if valid number
2628  std::string convertedString = StringMacros::convertEnvironmentVariables(valueStr);
2629  // do not check here, let init check
2630  // if this is a link to valid number, then this is an improper check.
2631  // if(!StringMacros::isNumber(convertedString))
2632  // {
2633  // __SS__ << "\tIn configuration " << tableName_
2634  // << " at column=" << columnsInfo_[c].getName() << " the value
2635  // set
2636  //("
2637  // << convertedString << ")"
2638  // << " is not a number! Please fix it or change the column
2639  // type..."
2640  // << __E__;
2641  // __SS_THROW__;
2642  // }
2643  theDataView_[r][c] = valueStr;
2644  }
2645  else if(columnsInfo_[c].getDataType() == TableViewColumnInfo::DATATYPE_TIME)
2646  {
2647  // valueStr = StringMacros::decodeURIComponent(data.substr(i,j-i));
2648  //
2649  // getValue(tmpTimeStr,r,c);
2650  // if(valueStr != tmpTimeStr)//theDataView_[r][c])
2651  // {
2652  // __COUT__ << "valueStr=" << valueStr <<
2653  // " theDataView_[r][c]=" << tmpTimeStr << __E__;
2654  // rowWasModified = true;
2655  // }
2656 
2657  setValue(time_t(strtol(valueStr.c_str(), 0, 10)), r, c);
2658  }
2659  else
2660  theDataView_[r][c] = valueStr;
2661 
2662  bool rowWasModified =
2663  (originalValueStr !=
2664  getValueAsString(r, c, false)); // do not convert env variables
2665 
2666  // if row was modified, assign author and timestamp
2667  if(author != "" && rowWasModified)
2668  {
2669  __COUT__ << "Row=" << (int)r << " was modified!" << __E__;
2670  int authorCol = findColByType(TableViewColumnInfo::TYPE_AUTHOR);
2671  int timestampCol = findColByType(TableViewColumnInfo::TYPE_TIMESTAMP);
2672  setValue(author, r, authorCol);
2673  setValue(time(0), r, timestampCol);
2674  }
2675 
2676  return rowWasModified;
2677 } // end setURIEncodedValue()
2678 
2679 //==============================================================================
2680 void TableView::resizeDataView(unsigned int nRows, unsigned int nCols)
2681 {
2682  // FIXME This maybe should disappear but I am using it in ConfigurationHandler
2683  // still...
2684  theDataView_.resize(nRows, std::vector<std::string>(nCols));
2685 }
2686 
2687 //==============================================================================
2688 // addRow
2689 // returns index of added row, always is last row
2690 // return -1 on failure (throw error)
2691 //
2692 // if baseNameAutoUID != "", creates a UID based on this base name
2693 // and increments and appends an integer relative to the previous last row
2694 unsigned int TableView::addRow(const std::string& author,
2695  bool incrementUniqueData,
2696  std::string baseNameAutoUID,
2697  unsigned int rowToAdd)
2698 {
2699  // default to last row
2700  if(rowToAdd == (unsigned int)-1)
2701  rowToAdd = getNumberOfRows();
2702 
2703  theDataView_.resize(getNumberOfRows() + 1,
2704  std::vector<std::string>(getNumberOfColumns()));
2705 
2706  // shift data down the table if necessary
2707  for(unsigned int r = getNumberOfRows() - 2; r >= rowToAdd; --r)
2708  {
2709  if(r == (unsigned int)-1)
2710  break; // quit wrap around case
2711  for(unsigned int col = 0; col < getNumberOfColumns(); ++col)
2712  theDataView_[r + 1][col] = theDataView_[r][col];
2713  }
2714 
2715  std::vector<std::string> defaultRowValues = getDefaultRowValues();
2716 
2717  char indexString[1000];
2718  std::string tmpString, baseString;
2719  bool foundAny;
2720  unsigned int index;
2721  unsigned int maxUniqueData;
2722  std::string numString;
2723 
2724  // fill each col of new row with default values
2725  // if a row is a unique data row, increment last row in attempt to make a legal
2726  // column
2727  for(unsigned int col = 0; col < getNumberOfColumns(); ++col)
2728  {
2729  // __COUT__ << col << " " << columnsInfo_[col].getType() << " == " <<
2730  // TableViewColumnInfo::TYPE_UNIQUE_DATA << __E__;
2731 
2732  // baseNameAutoUID indicates to attempt to make row unique
2733  // add index to max number
2734  if(incrementUniqueData &&
2735  (col == getColUID() ||
2736  (getNumberOfRows() > 1 &&
2737  (columnsInfo_[col].getType() == TableViewColumnInfo::TYPE_UNIQUE_DATA ||
2738  columnsInfo_[col].getType() ==
2739  TableViewColumnInfo::TYPE_UNIQUE_GROUP_DATA))))
2740  {
2741  // __COUT__ << "Current unique data entry is data[" << rowToAdd
2742  // << "][" << col << "] = '" << theDataView_[rowToAdd][col]
2743  //<<
2744  //"'"
2745  // << __E__;
2746 
2747  maxUniqueData = 0;
2748  tmpString = "";
2749  baseString = "";
2750 
2751  // find max in rows
2752 
2753  // this->print();
2754 
2755  for(unsigned int r = 0; r < getNumberOfRows(); ++r)
2756  {
2757  if(r == rowToAdd)
2758  continue; // skip row to add
2759 
2760  // find last non numeric character
2761 
2762  foundAny = false;
2763  tmpString = theDataView_[r][col];
2764 
2765  //__COUT__ << "tmpString " << tmpString << __E__;
2766 
2767  for(index = tmpString.length() - 1; index < tmpString.length(); --index)
2768  {
2769  //__COUT__ << index << " tmpString[index] " << tmpString[index] <<
2770  //__E__;
2771  if(!(tmpString[index] >= '0' && tmpString[index] <= '9'))
2772  break; // if not numeric, break
2773  foundAny = true;
2774  }
2775 
2776  //__COUT__ << "index " << index << __E__;
2777 
2778  if(tmpString.length() && foundAny) // then found a numeric substring
2779  {
2780  // create numeric substring
2781  numString = tmpString.substr(index + 1);
2782  tmpString = tmpString.substr(0, index + 1);
2783 
2784  //__COUT__ << "Found unique data base string '" <<
2785  // tmpString << "' and number string '" << numString <<
2786  // "' in last record '" << theDataView_[r][col] << "'" << __E__;
2787 
2788  // extract number
2789  sscanf(numString.c_str(), "%u", &index);
2790 
2791  if(index > maxUniqueData)
2792  {
2793  maxUniqueData = index;
2794  baseString = tmpString;
2795  }
2796  }
2797  }
2798 
2799  ++maxUniqueData; // increment
2800 
2801  sprintf(indexString, "%u", maxUniqueData);
2802  //__COUT__ << "indexString " << indexString << __E__;
2803 
2804  //__COUT__ << "baseNameAutoUID " << baseNameAutoUID << __E__;
2805  if(col == getColUID())
2806  {
2807  // handle UID case
2808  if(baseNameAutoUID != "")
2809  theDataView_[rowToAdd][col] = baseNameAutoUID + indexString;
2810  else
2811  theDataView_[rowToAdd][col] = baseString + indexString;
2812  }
2813  else
2814  theDataView_[rowToAdd][col] = baseString + indexString;
2815 
2816  __COUT__ << "New unique data entry is data[" << rowToAdd << "][" << col
2817  << "] = '" << theDataView_[rowToAdd][col] << "'" << __E__;
2818 
2819  // this->print();
2820  }
2821  else
2822  theDataView_[rowToAdd][col] = defaultRowValues[col];
2823  }
2824 
2825  if(author != "")
2826  {
2827  __COUT__ << "Row=" << rowToAdd << " was created!" << __E__;
2828  int authorCol = findColByType(TableViewColumnInfo::TYPE_AUTHOR);
2829  int timestampCol = findColByType(TableViewColumnInfo::TYPE_TIMESTAMP);
2830  setValue(author, rowToAdd, authorCol);
2831  setValue(time(0), rowToAdd, timestampCol);
2832  }
2833 
2834  return rowToAdd;
2835 } // end addRow()
2836 
2837 //==============================================================================
2838 // deleteRow
2839 // throws exception on failure
2840 void TableView::deleteRow(int r)
2841 {
2842  if(r >= (int)getNumberOfRows())
2843  {
2844  // out of bounds
2845  __SS__ << "Row " << (int)r
2846  << " is out of bounds (Row Count = " << getNumberOfRows()
2847  << ") and can not be deleted." << __E__;
2848  __SS_THROW__;
2849  }
2850 
2851  theDataView_.erase(theDataView_.begin() + r);
2852 } // end deleteRow()
2853 
2854 //==============================================================================
2855 // getChildLink ~
2856 // find the pair of columns associated with a child link.
2857 //
2858 // c := a member column of the pair
2859 //
2860 // returns:
2861 // isGroup := indicates pair found is a group link
2862 // linkPair := pair of columns that are part of the link
2863 //
2864 // a unique link is defined by two columns: TYPE_START_CHILD_LINK,
2865 // TYPE_START_CHILD_LINK_UID
2866 // a group link is defined by two columns: TYPE_START_CHILD_LINK,
2867 // TYPE_START_CHILD_LINK_GROUP_ID
2868 //
2869 // returns true if column is member of a group or unique link.
2870 const bool TableView::getChildLink(
2871  const unsigned int& c,
2872  bool& isGroup,
2873  std::pair<unsigned int /*link col*/, unsigned int /*link id col*/>& linkPair) const
2874 {
2875  if(!(c < columnsInfo_.size()))
2876  {
2877  __SS__ << "Invalid col (" << (int)c << ") requested!" << __E__;
2878  __SS_THROW__;
2879  }
2880 
2881  //__COUT__ << "getChildLink for col: " << (int)c << "-" <<
2882  // columnsInfo_[c].getType() << "-" << columnsInfo_[c].getName() << __E__;
2883 
2884  // check if column is a child link UID
2885  if((isGroup = columnsInfo_[c].isChildLinkGroupID()) ||
2886  columnsInfo_[c].isChildLinkUID())
2887  {
2888  // must be part of unique link, (or invalid table?)
2889  //__COUT__ << "col: " << (int)c << __E__;
2890  linkPair.second = c;
2891  std::string index = columnsInfo_[c].getChildLinkIndex();
2892 
2893  //__COUT__ << "index: " << index << __E__;
2894 
2895  // find pair link
2896  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
2897  {
2898  //__COUT__ << "try: " << col << "-" << columnsInfo_[col].getType() << "-" <<
2899  // columnsInfo_[col].getName() << __E__;
2900  if(col == c)
2901  continue; // skip column c that we know
2902  else if(columnsInfo_[col].isChildLink() &&
2903  index == columnsInfo_[col].getChildLinkIndex())
2904  {
2905  // found match!
2906  //__COUT__ << "getChildLink Found match for col: " << (int)c << " at " <<
2907  // col << __E__;
2908  linkPair.first = col;
2909  return true;
2910  }
2911  }
2912 
2913  // if here then invalid table!
2914  __SS__ << "\tIn view: " << tableName_
2915  << ", Can't find complete child link for column name "
2916  << columnsInfo_[c].getName() << __E__;
2917  __SS_THROW__;
2918  }
2919 
2920  if(!columnsInfo_[c].isChildLink())
2921  return false; // cant be unique link
2922 
2923  // this is child link, so find pair link uid or gid column
2924  linkPair.first = c;
2925  std::string index = columnsInfo_[c].getChildLinkIndex();
2926 
2927  //__COUT__ << "index: " << index << __E__;
2928 
2929  // find pair link
2930  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
2931  {
2932  //__COUT__ << "try: " << col << "-" << columnsInfo_[col].getType() << "-" <<
2933  // columnsInfo_[col].getName() << __E__;
2934  if(col == c)
2935  continue; // skip column c that we know
2936  // __COUT__ << "try: " << col << "-" << columnsInfo_[col].getType() <<
2937  // "-" << columnsInfo_[col].getName() <<
2938  // "-u" << columnsInfo_[col].isChildLinkUID() <<
2939  // "-g" << columnsInfo_[col].isChildLinkGroupID() << __E__;
2940  //
2941  // if(columnsInfo_[col].isChildLinkUID())
2942  // __COUT__ << "-L" << columnsInfo_[col].getChildLinkIndex() << __E__;
2943  //
2944  // if(columnsInfo_[col].isChildLinkGroupID())
2945  // __COUT__ << "-L" << columnsInfo_[col].getChildLinkIndex() << __E__;
2946 
2947  if(((columnsInfo_[col].isChildLinkUID() && !(isGroup = false)) ||
2948  (columnsInfo_[col].isChildLinkGroupID() && (isGroup = true))) &&
2949  index == columnsInfo_[col].getChildLinkIndex())
2950  {
2951  // found match!
2952  //__COUT__ << "getChildLink Found match for col: " << (int)c << " at " << col
2953  //<< __E__;
2954  linkPair.second = col;
2955  return true;
2956  }
2957  }
2958 
2959  // if here then invalid table!
2960  __SS__ << "\tIn view: " << tableName_
2961  << ", Can't find complete child link id for column name "
2962  << columnsInfo_[c].getName() << __E__;
2963  __SS_THROW__;
2964 } // end getChildLink()