otsdaq  v2_03_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__ << "Column Types: " << __E__;
985  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
986  std::cout << columnsInfo_[col].getType() << "() " << columnsInfo_[col].getName()
987  << __E__;
988 
989  __SS__ << "Missing " << TableViewColumnInfo::COL_NAME_STATUS
990  << " Column in config named " << tableName_
991  << ". (Possibly TableView was just not initialized?"
992  << "This is the const call so can not alter class members)" << __E__;
993  __SS_THROW__;
994 }
995 
996 //==============================================================================
997 // getColPriority
998 // const version, so don't attempt to lookup
999 // if column not found throw error
1000 const unsigned int TableView::getColPriority(void) const
1001 {
1002  if(colPriority_ != INVALID)
1003  return colPriority_;
1004 
1005  __COUT__ << "Column Types: " << __E__;
1006  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
1007  std::cout << columnsInfo_[col].getType() << "() " << columnsInfo_[col].getName()
1008  << __E__;
1009 
1010  __SS__ << "Missing " << TableViewColumnInfo::COL_NAME_PRIORITY
1011  << " Column in config named " << tableName_
1012  << ". (The Priority column is identified when TableView is initialized)"
1013  << __E__; // this is the const call, so can not identify the column and set
1014  // colPriority_ here
1015  __SS_THROW__;
1016 }
1017 
1018 //==============================================================================
1019 // addRowToGroup
1020 // Group entry can include | to place a record in multiple groups
1021 void TableView::addRowToGroup(
1022  const unsigned int& row,
1023  const unsigned int& col,
1024  const std::string& groupID) //,
1025  // const std::string &colDefault)
1026 {
1027  if(isEntryInGroupCol(row, col, groupID))
1028  {
1029  __SS__ << "GroupID (" << groupID << ") added to row (" << row
1030  << " is already present!" << __E__;
1031  __SS_THROW__;
1032  }
1033 
1034  // not in group, so
1035  // if no groups
1036  // set groupid
1037  // if other groups
1038  // prepend groupId |
1039  if(getDataView()[row][col] == "" ||
1040  getDataView()[row][col] == getDefaultRowValues()[col]) // colDefault)
1041  setValue(groupID, row, col);
1042  else
1043  setValue(groupID + " | " + getDataView()[row][col], row, col);
1044 
1045  //__COUT__ << getDataView()[row][col] << __E__;
1046 }
1047 
1048 //==============================================================================
1049 // removeRowFromGroup
1050 // Group entry can include | to place a record in multiple groups
1051 //
1052 // returns true if row was deleted because it had no group left
1053 bool TableView::removeRowFromGroup(const unsigned int& row,
1054  const unsigned int& col,
1055  const std::string& groupNeedle,
1056  bool deleteRowIfNoGroupLeft)
1057 {
1058  __COUT__ << "groupNeedle " << groupNeedle << __E__;
1059  std::set<std::string> groupIDList;
1060  if(!isEntryInGroupCol(row, col, groupNeedle, &groupIDList))
1061  {
1062  __SS__ << "GroupID (" << groupNeedle << ") removed from row (" << row
1063  << ") was already removed!" << __E__;
1064  __SS_THROW__;
1065  }
1066 
1067  // is in group, so
1068  // create new string based on set of groupids
1069  // but skip groupNeedle
1070 
1071  std::string newValue = "";
1072  unsigned int cnt = 0;
1073  for(const auto& groupID : groupIDList)
1074  {
1075  //__COUT__ << groupID << " " << groupNeedle << " " << newValue << __E__;
1076  if(groupID == groupNeedle)
1077  continue; // skip group to be removed
1078 
1079  if(cnt)
1080  newValue += " | ";
1081  newValue += groupID;
1082  }
1083 
1084  bool wasDeleted = false;
1085  if(deleteRowIfNoGroupLeft && newValue == "")
1086  {
1087  __COUT__ << "Delete row since it no longer part of any group." << __E__;
1088  deleteRow(row);
1089  wasDeleted = true;
1090  }
1091  else
1092  setValue(newValue, row, col);
1093 
1094  //__COUT__ << getDataView()[row][col] << __E__;
1095 
1096  return wasDeleted;
1097 }
1098 
1099 //==============================================================================
1100 // isEntryInGroup
1101 // All group link checking should use this function
1102 // so that handling is consistent
1103 //
1104 // Group entry can include | to place a record in multiple groups
1105 bool TableView::isEntryInGroup(const unsigned int& r,
1106  const std::string& childLinkIndex,
1107  const std::string& groupNeedle) const
1108 {
1109  unsigned int c = getColLinkGroupID(childLinkIndex); // column in question
1110 
1111  return isEntryInGroupCol(r, c, groupNeedle);
1112 }
1113 
1114 //==============================================================================
1115 // isEntryInGroupCol
1116 //
1117 // if *groupIDList != 0 return set of groupIDs found
1118 // useful for removing groupIDs.
1119 //
1120 // Group entry can include | to place a record in multiple groups
1121 //
1122 // Note: should mirror what happens in TableView::getSetOfGroupIDs
1123 bool TableView::isEntryInGroupCol(const unsigned int& r,
1124  const unsigned int& c,
1125  const std::string& groupNeedle,
1126  std::set<std::string>* groupIDList) const
1127 {
1128  unsigned int i = 0;
1129  unsigned int j = 0;
1130  bool found = false;
1131 
1132  //__COUT__ << "groupNeedle " << groupNeedle << __E__;
1133 
1134  // go through the full groupString extracting groups and comparing to groupNeedle
1135  for(; j < theDataView_[r][c].size(); ++j)
1136  if((theDataView_[r][c][j] == ' ' || // ignore leading white space or |
1137  theDataView_[r][c][j] == '|') &&
1138  i == j)
1139  ++i;
1140  else if((theDataView_[r][c][j] ==
1141  ' ' || // trailing white space or | indicates group
1142  theDataView_[r][c][j] == '|') &&
1143  i != j) // assume end of group name
1144  {
1145  if(groupIDList)
1146  groupIDList->emplace(theDataView_[r][c].substr(i, j - i));
1147 
1148  //__COUT__ << "Group found to compare: " <<
1149  // theDataView_[r][c].substr(i,j-i) << __E__;
1150  if(groupNeedle == theDataView_[r][c].substr(i, j - i))
1151  {
1152  if(!groupIDList) // dont return if caller is trying to get group list
1153  return true;
1154  found = true;
1155  }
1156  // if no match, setup i and j for next find
1157  i = j + 1;
1158  }
1159 
1160  if(i != j) // last group check (for case when no ' ' or '|')
1161  {
1162  if(groupIDList)
1163  groupIDList->emplace(theDataView_[r][c].substr(i, j - i));
1164 
1165  //__COUT__ << "Group found to compare: " <<
1166  // theDataView_[r][c].substr(i,j-i) << __E__;
1167  if(groupNeedle == theDataView_[r][c].substr(i, j - i))
1168  return true;
1169  }
1170 
1171  return found;
1172 }
1173 
1174 //==============================================================================
1175 // getSetOfGroupIDs
1176 // if row == -1, then considers all rows
1177 // else just that row
1178 // returns unique set of groupIds in GroupID column
1179 // associate with childLinkIndex
1180 //
1181 // Note: should mirror what happens in TableView::isEntryInGroupCol
1182 std::set<std::string> TableView::getSetOfGroupIDs(const std::string& childLinkIndex,
1183  unsigned int r) const
1184 {
1185  return getSetOfGroupIDs(getColLinkGroupID(childLinkIndex), r);
1186 }
1187 std::set<std::string> TableView::getSetOfGroupIDs(const unsigned int& c,
1188  unsigned int r) const
1189 {
1190  //__COUT__ << "GroupID col=" << (int)c << __E__;
1191 
1192  std::set<std::string> retSet;
1193 
1194  unsigned int i = 0;
1195  unsigned int j = 0;
1196 
1197  if(r != (unsigned int)-1)
1198  {
1199  if(r >= getNumberOfRows())
1200  {
1201  __SS__ << "Invalid row requested!" << __E__;
1202  __SS_THROW__;
1203  }
1204 
1205  StringMacros::getSetFromString(theDataView_[r][c], retSet);
1206  // //go through the full groupString extracting groups
1207  // //add each found groupId to set
1208  // for(;j<theDataView_[r][c].size();++j)
1209  // if((theDataView_[r][c][j] == ' ' || //ignore leading white space or |
1210  // theDataView_[r][c][j] == '|')
1211  // && i == j)
1212  // ++i;
1213  // else if((theDataView_[r][c][j] == ' ' || //trailing white space or |
1214  // indicates group theDataView_[r][c][j] == '|')
1215  // && i != j) // assume end of group name
1216  // {
1217  // //__COUT__ << "Group found: " <<
1218  // // theDataView_[r][c].substr(i,j-i) << __E__;
1219  //
1220  //
1221  // retSet.emplace(theDataView_[r][c].substr(i,j-i));
1222  //
1223  // //setup i and j for next find
1224  // i = j+1;
1225  // }
1226  //
1227  // if(i != j) //last group check (for case when no ' ' or '|')
1228  // retSet.emplace(theDataView_[r][c].substr(i,j-i));
1229  }
1230  else
1231  {
1232  // do all rows
1233  for(r = 0; r < getNumberOfRows(); ++r)
1234  {
1235  StringMacros::getSetFromString(theDataView_[r][c], retSet);
1236 
1237  // i=0;
1238  // j=0;
1239  //
1240  // //__COUT__ << (int)r << ": " << theDataView_[r][c] << __E__;
1241  //
1242  // //go through the full groupString extracting groups
1243  // //add each found groupId to set
1244  // for(;j<theDataView_[r][c].size();++j)
1245  // {
1246  // //__COUT__ << "i:" << i << " j:" << j << __E__;
1247  //
1248  // if((theDataView_[r][c][j] == ' ' || //ignore leading white
1249  // space or | theDataView_[r][c][j] == '|')
1250  // && i == j)
1251  // ++i;
1252  // else if((theDataView_[r][c][j] == ' ' || //trailing white
1253  // space or | indicates group theDataView_[r][c][j]
1254  // ==
1255  // '|')
1256  // && i != j) // assume end of group name
1257  // {
1258  // //__COUT__ << "Group found: " <<
1259  // // theDataView_[r][c].substr(i,j-i) << __E__;
1260  //
1261  // retSet.emplace(theDataView_[r][c].substr(i,j-i));
1262  //
1263  // //setup i and j for next find
1264  // i = j+1;
1265  // }
1266  // }
1267  //
1268  // if(i != j) //last group (for case when no ' ' or '|')
1269  // {
1270  // //__COUT__ << "Group found: " <<
1271  // // theDataView_[r][c].substr(i,j-i) << __E__;
1272  // retSet.emplace(theDataView_[r][c].substr(i,j-i));
1273  // }
1274  }
1275  }
1276 
1277  return retSet;
1278 }
1279 
1280 //==============================================================================
1281 // getColOfLinkGroupID
1282 // const version, if column not found throw error
1283 const unsigned int TableView::getColLinkGroupID(const std::string& childLinkIndex) const
1284 {
1285  if(!childLinkIndex.size())
1286  {
1287  __SS__ << "Empty childLinkIndex string parameter!" << __E__;
1288  __SS_THROW__;
1289  }
1290 
1291  const char* needleChildLinkIndex = &childLinkIndex[0];
1292 
1293  // allow space syntax to target a childLinkIndex from a different parentLinkIndex
1294  // e.g. "parentLinkIndex childLinkIndex"
1295  size_t spacePos = childLinkIndex.find(' ');
1296  if(spacePos != std::string::npos &&
1297  spacePos + 1 < childLinkIndex.size()) // make sure there are more characters
1298  {
1299  // found space syntax for targeting childLinkIndex
1300  needleChildLinkIndex = &childLinkIndex[spacePos + 1];
1301  }
1302 
1303  std::map<std::string, unsigned int>::const_iterator it =
1304  colLinkGroupIDs_.find(needleChildLinkIndex);
1305  if(it != // if already known, return it
1306  colLinkGroupIDs_.end())
1307  return it->second;
1308 
1309  __COUT__ << "Existing Column GroupIDs: " << __E__;
1310  for(auto& groupIdColPair : colLinkGroupIDs_)
1311  std::cout << "\t" << groupIdColPair.first << " : col-" << groupIdColPair.second
1312  << __E__;
1313 
1314  __COUT__ << "Existing Column Types: " << __E__;
1315  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
1316  std::cout << "\t" << columnsInfo_[col].getType() << "() "
1317  << columnsInfo_[col].getName() << __E__;
1318 
1319  __SS__
1320  << "Incompatible table for this group link. Table '" << tableName_
1321  << "' is missing a GroupID column with data type '"
1322  << TableViewColumnInfo::TYPE_START_GROUP_ID << "-" << needleChildLinkIndex
1323  << "'.\n\n"
1324  << "Note: you can separate the child GroupID column data type from "
1325  << "the parent GroupLink column data type; this is accomplished by using a space "
1326  << "character at the parent level - the string after the space will be treated "
1327  "as the "
1328  << "child GroupID column data type." << __E__;
1329  __SS_THROW__;
1330 } // end getColLinkGroupID()
1331 
1332 //==============================================================================
1333 unsigned int TableView::findRow(unsigned int col,
1334  const std::string& value,
1335  unsigned int offsetRow) const
1336 {
1337  for(unsigned int row = offsetRow; row < theDataView_.size(); ++row)
1338  {
1339  if(theDataView_[row][col] == value)
1340  return row;
1341  }
1342 
1343  __SS__ << "\tIn view: " << tableName_ << ", Can't find value=" << value
1344  << " in column named " << columnsInfo_[col].getName()
1345  << " with type=" << columnsInfo_[col].getType() << __E__;
1346  // Note: findRow gets purposely called by configuration GUI a lot looking for
1347  // exceptions so may not want to print out
1348  //__COUT__ << "\n" << ss.str();
1349  __SS_ONLY_THROW__;
1350 } // end findRow()
1351 
1352 //==============================================================================
1353 unsigned int TableView::findRowInGroup(unsigned int col,
1354  const std::string& value,
1355  const std::string& groupId,
1356  const std::string& childLinkIndex,
1357  unsigned int offsetRow) const
1358 {
1359  unsigned int groupIdCol = getColLinkGroupID(childLinkIndex);
1360  for(unsigned int row = offsetRow; row < theDataView_.size(); ++row)
1361  {
1362  if(theDataView_[row][col] == value && isEntryInGroupCol(row, groupIdCol, groupId))
1363  return row;
1364  }
1365 
1366  __SS__ << "\tIn view: " << tableName_ << ", Can't find in group the value=" << value
1367  << " in column named '" << columnsInfo_[col].getName()
1368  << "' with type=" << columnsInfo_[col].getType() << " and GroupID: '"
1369  << groupId << "' in column '" << groupIdCol
1370  << "' with GroupID child link index '" << childLinkIndex << "'" << __E__;
1371  // Note: findRowInGroup gets purposely called by configuration GUI a lot looking for
1372  // exceptions so may not want to print out
1373  __SS_ONLY_THROW__;
1374 } // end findRowInGroup()
1375 
1376 //==============================================================================
1377 // findCol
1378 // throws exception if column not found by name
1379 unsigned int TableView::findCol(const std::string& wildCardName) const
1380 {
1381  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
1382  if(StringMacros::wildCardMatch(wildCardName /*needle*/,
1383  columnsInfo_[col].getName() /*haystack*/))
1384  return col;
1385 
1386  __SS__ << "\tIn view: " << tableName_ << ", Can't find column named '" << wildCardName
1387  << "'" << __E__;
1388  ss << "Existing columns:\n";
1389  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
1390  ss << "\t" << columnsInfo_[col].getName() << "\n";
1391  // Note: findCol gets purposely called by configuration GUI a lot looking for
1392  // exceptions so may not want to print out
1393  __SS_ONLY_THROW__;
1394 } // end findCol()
1395 
1396 //==============================================================================
1397 // findColByType
1398 // return invalid if type not found
1399 unsigned int TableView::findColByType(const std::string& type, int startingCol) const
1400 {
1401  for(unsigned int col = startingCol; col < columnsInfo_.size(); ++col)
1402  if(columnsInfo_[col].getType() == type)
1403  return col;
1404 
1405  return INVALID;
1406 } // end findColByType()
1407 
1408 // Getters
1409 //==============================================================================
1410 const std::string& TableView::getUniqueStorageIdentifier(void) const
1411 {
1412  return uniqueStorageIdentifier_;
1413 }
1414 
1415 //==============================================================================
1416 const std::string& TableView::getTableName(void) const { return tableName_; }
1417 
1418 //==============================================================================
1419 const TableVersion& TableView::getVersion(void) const { return version_; }
1420 
1421 //==============================================================================
1422 const std::string& TableView::getComment(void) const { return comment_; }
1423 
1424 //==============================================================================
1425 const std::string& TableView::getAuthor(void) const { return author_; }
1426 
1427 //==============================================================================
1428 const time_t& TableView::getCreationTime(void) const { return creationTime_; }
1429 
1430 //==============================================================================
1431 const time_t& TableView::getLastAccessTime(void) const { return lastAccessTime_; }
1432 
1433 //==============================================================================
1434 const bool& TableView::getLooseColumnMatching(void) const
1435 {
1436  return fillWithLooseColumnMatching_;
1437 }
1438 
1439 //==============================================================================
1440 // getDataColumnSize
1441 const unsigned int TableView::getDataColumnSize(void) const
1442 {
1443  // if no data, give benefit of the doubt that phantom data has mockup column size
1444  if(!getNumberOfRows())
1445  return getNumberOfColumns();
1446  return theDataView_[0].size(); // number of columns in first row of data
1447 }
1448 
1449 //==============================================================================
1450 // getSourceColumnMismatch
1451 // The source information is only valid after modifying the table with ::fillFromJSON
1452 const unsigned int& TableView::getSourceColumnMismatch(void) const
1453 {
1454  return sourceColumnMismatchCount_;
1455 }
1456 
1457 //==============================================================================
1458 // getSourceColumnMissing
1459 // The source information is only valid after modifying the table with ::fillFromJSON
1460 const unsigned int& TableView::getSourceColumnMissing(void) const
1461 {
1462  return sourceColumnMissingCount_;
1463 }
1464 
1465 //==============================================================================
1466 // getSourceColumnNames
1467 // The source information is only valid after modifying the table with ::fillFromJSON
1468 const std::set<std::string>& TableView::getSourceColumnNames(void) const
1469 {
1470  return sourceColumnNames_;
1471 }
1472 
1473 //==============================================================================
1474 std::set<std::string> TableView::getColumnNames(void) const
1475 {
1476  std::set<std::string> retSet;
1477  for(auto& colInfo : columnsInfo_)
1478  retSet.emplace(colInfo.getName());
1479  return retSet;
1480 }
1481 
1482 //==============================================================================
1483 std::set<std::string> TableView::getColumnStorageNames(void) const
1484 {
1485  std::set<std::string> retSet;
1486  for(auto& colInfo : columnsInfo_)
1487  retSet.emplace(colInfo.getStorageName());
1488  return retSet;
1489 }
1490 
1491 //==============================================================================
1492 std::vector<std::string> TableView::getDefaultRowValues(void) const
1493 {
1494  std::vector<std::string> retVec;
1495 
1496  // fill each col of new row with default values
1497  for(unsigned int col = 0; col < getNumberOfColumns(); ++col)
1498  {
1499  // if this is a fixed choice Link, and NO_LINK is not in list,
1500  // take first in list to avoid creating illegal rows.
1501  // NOTE: this is not a problem for standard fixed choice fields
1502  // because the default value is always required.
1503 
1504  if(columnsInfo_[col].isChildLink())
1505  {
1506  const std::vector<std::string>& theDataChoices =
1507  columnsInfo_[col].getDataChoices();
1508 
1509  // check if arbitrary values allowed
1510  if(!theDataChoices.size() || // if so, use default
1511  theDataChoices[0] == "arbitraryBool=1")
1512  retVec.push_back(columnsInfo_[col].getDefaultValue());
1513  else
1514  {
1515  bool skipOne =
1516  (theDataChoices.size() && theDataChoices[0] == "arbitraryBool=0");
1517  bool hasSkipped;
1518 
1519  // look for default value in list
1520 
1521  bool foundDefault = false;
1522  hasSkipped = false;
1523  for(const auto& choice : theDataChoices)
1524  if(skipOne && !hasSkipped)
1525  {
1526  hasSkipped = true;
1527  continue;
1528  }
1529  else if(choice == columnsInfo_[col].getDefaultValue())
1530  {
1531  foundDefault = true;
1532  break;
1533  }
1534 
1535  // use first choice if possible
1536  if(!foundDefault && theDataChoices.size() > (skipOne ? 1 : 0))
1537  retVec.push_back(theDataChoices[(skipOne ? 1 : 0)]);
1538  else // else stick with default
1539  retVec.push_back(columnsInfo_[col].getDefaultValue());
1540  }
1541  }
1542  else
1543  retVec.push_back(columnsInfo_[col].getDefaultValue());
1544  }
1545 
1546  return retVec;
1547 } // end getDefaultRowValues()
1548 
1549 //==============================================================================
1550 unsigned int TableView::getNumberOfRows(void) const { return theDataView_.size(); }
1551 
1552 //==============================================================================
1553 unsigned int TableView::getNumberOfColumns(void) const { return columnsInfo_.size(); }
1554 
1555 //==============================================================================
1556 const TableView::DataView& TableView::getDataView(void) const { return theDataView_; }
1557 
1558 //==============================================================================
1559 // TableView::DataView* TableView::getDataViewP(void)
1560 //{
1561 // return &theDataView_;
1562 //}
1563 
1564 //==============================================================================
1565 const std::vector<TableViewColumnInfo>& TableView::getColumnsInfo(void) const
1566 {
1567  return columnsInfo_;
1568 }
1569 
1570 //==============================================================================
1571 std::vector<TableViewColumnInfo>* TableView::getColumnsInfoP(void)
1572 {
1573  return &columnsInfo_;
1574 }
1575 //==============================================================================
1576 const TableViewColumnInfo& TableView::getColumnInfo(unsigned int column) const
1577 {
1578  if(column >= columnsInfo_.size())
1579  {
1580  std::stringstream errMsg;
1581  errMsg << __COUT_HDR_FL__ << "\nCan't find column " << column
1582  << "\n\n\n\nThe column info is likely missing due to incomplete "
1583  "Configuration View filling.\n\n"
1584  << __E__;
1585  __THROW__(errMsg.str().c_str());
1586  }
1587  return columnsInfo_[column];
1588 } // end getColumnInfo()
1589 
1590 // Setters
1591 //==============================================================================
1592 void TableView::setUniqueStorageIdentifier(const std::string& storageUID)
1593 {
1594  uniqueStorageIdentifier_ = storageUID;
1595 }
1596 
1597 //==============================================================================
1598 void TableView::setTableName(const std::string& name) { tableName_ = name; }
1599 
1600 //==============================================================================
1601 void TableView::setComment(const std::string& comment) { comment_ = comment; }
1602 
1603 //==============================================================================
1604 void TableView::setURIEncodedComment(const std::string& uriComment)
1605 {
1606  comment_ = StringMacros::decodeURIComponent(uriComment);
1607 }
1608 
1609 //==============================================================================
1610 void TableView::setAuthor(const std::string& author) { author_ = author; }
1611 
1612 //==============================================================================
1613 void TableView::setCreationTime(time_t t) { creationTime_ = t; }
1614 
1615 //==============================================================================
1616 void TableView::setLastAccessTime(time_t t) { lastAccessTime_ = t; }
1617 
1618 //==============================================================================
1619 void TableView::setLooseColumnMatching(bool setValue)
1620 {
1621  fillWithLooseColumnMatching_ = setValue;
1622 }
1623 
1624 //==============================================================================
1625 void TableView::reset(void)
1626 {
1627  version_ = -1;
1628  comment_ = "";
1629  author_ + "";
1630  columnsInfo_.clear();
1631  theDataView_.clear();
1632 } // end reset()
1633 
1634 //==============================================================================
1635 void TableView::print(std::ostream& out) const
1636 {
1637  out << "============================================================================="
1638  "="
1639  << __E__;
1640  out << "Print: " << tableName_ << " Version: " << version_ << " Comment: " << comment_
1641  << " Author: " << author_ << " Creation Time: " << ctime(&creationTime_) << __E__;
1642  out << "\t\tNumber of Cols " << getNumberOfColumns() << __E__;
1643  out << "\t\tNumber of Rows " << getNumberOfRows() << __E__;
1644 
1645  out << "Columns:\t";
1646  for(int i = 0; i < (int)columnsInfo_.size(); ++i)
1647  out << i << ":" << columnsInfo_[i].getName() << ":"
1648  << columnsInfo_[i].getStorageName() << ":" << columnsInfo_[i].getType() << ":"
1649  << columnsInfo_[i].getDataType() << "\t ";
1650  out << __E__;
1651 
1652  out << "Rows:" << __E__;
1653  int num;
1654  std::string val;
1655  for(int r = 0; r < (int)getNumberOfRows(); ++r)
1656  {
1657  out << (int)r << ":\t";
1658  for(int c = 0; c < (int)getNumberOfColumns(); ++c)
1659  {
1660  out << (int)c << ":";
1661 
1662  // if fixed choice type, print index in choice
1663  if(columnsInfo_[c].getType() == TableViewColumnInfo::TYPE_FIXED_CHOICE_DATA)
1664  {
1665  int choiceIndex = -1;
1666  std::vector<std::string> choices = columnsInfo_[c].getDataChoices();
1667  val = StringMacros::convertEnvironmentVariables(theDataView_[r][c]);
1668 
1669  if(val == columnsInfo_[c].getDefaultValue())
1670  choiceIndex = 0;
1671  else
1672  {
1673  for(int i = 0; i < (int)choices.size(); ++i)
1674  if(val == choices[i])
1675  choiceIndex = i + 1;
1676  }
1677 
1678  out << "ChoiceIndex=" << choiceIndex << ":";
1679  }
1680 
1681  out << theDataView_[r][c];
1682  // stopped using below, because it is called sometimes during debugging when
1683  // numbers are set to environment variables:
1684  // if(columnsInfo_[c].getDataType() == "NUMBER")
1685  // {
1686  // getValue(num,r,c,false);
1687  // out << num;
1688  // }
1689  // else
1690  // {
1691  // getValue(val,r,c,false);
1692  // out << val;
1693  // }
1694  out << "\t\t";
1695  }
1696  out << __E__;
1697  }
1698 } // end print()
1699 
1700 //==============================================================================
1701 void TableView::printJSON(std::ostream& out) const
1702 {
1703  out << "{\n";
1704  out << "\"NAME\" : \"" << tableName_ << "\",\n";
1705 
1706  // out << "\"VERSION\" : \"" << version_ << "\"\n";
1707 
1708  out << "\"COMMENT\" : ";
1709 
1710  // output escaped comment
1711  std::string val;
1712  val = comment_;
1713  out << "\"";
1714  for(unsigned int i = 0; i < val.size(); ++i)
1715  {
1716  if(val[i] == '\n')
1717  out << "\\n";
1718  else if(val[i] == '\t')
1719  out << "\\t";
1720  else if(val[i] == '\r')
1721  out << "\\r";
1722  else
1723  {
1724  // escaped characters need a
1725  if(val[i] == '"' || val[i] == '\\')
1726  out << '\\';
1727  out << val[i];
1728  }
1729  }
1730  out << "\",\n";
1731 
1732  out << "\"AUTHOR\" : \"" << author_ << "\",\n";
1733  out << "\"CREATION_TIME\" : " << creationTime_ << ",\n";
1734 
1735  // USELESS... out << "\"NUM_OF_COLS\" : " << getNumberOfColumns() << ",\n";
1736  // USELESS... out << "\"NUM_OF_ROWS\" : " << getNumberOfRows() << ",\n";
1737 
1738  out << "\"COL_TYPES\" : {\n";
1739  for(int c = 0; c < (int)getNumberOfColumns(); ++c)
1740  {
1741  out << "\t\t\"" << columnsInfo_[c].getStorageName() << "\" : ";
1742  out << "\"" << columnsInfo_[c].getDataType() << "\"";
1743  if(c + 1 < (int)getNumberOfColumns())
1744  out << ",";
1745  out << "\n";
1746  }
1747  out << "},\n"; // close COL_TYPES
1748 
1749  out << "\"DATA_SET\" : [\n";
1750  int num;
1751  for(int r = 0; r < (int)getNumberOfRows(); ++r)
1752  {
1753  out << "\t{\n";
1754  for(int c = 0; c < (int)getNumberOfColumns(); ++c)
1755  {
1756  out << "\t\t\"" << columnsInfo_[c].getStorageName() << "\" : ";
1757 
1758  out << "\"" << getEscapedValueAsString(r, c, false)
1759  << "\""; // do not convert env variables
1760 
1761  if(c + 1 < (int)getNumberOfColumns())
1762  out << ",";
1763  out << "\n";
1764  }
1765  out << "\t}";
1766  if(r + 1 < (int)getNumberOfRows())
1767  out << ",";
1768  out << "\n";
1769  }
1770  out << "]\n"; // close DATA_SET
1771 
1772  out << "}";
1773 } // end printJSON()
1774 
1775 //==============================================================================
1776 // restoreJSONStringEntities
1777 // returns string with literals \n \t \" \r \\ replaced with char
1778 std::string restoreJSONStringEntities(const std::string& str)
1779 {
1780  unsigned int sz = str.size();
1781  if(!sz)
1782  return ""; // empty string, returns empty string
1783 
1784  std::stringstream retStr;
1785  unsigned int i = 0;
1786  for(; i < sz - 1; ++i)
1787  {
1788  if(str[i] == '\\') // if 2 char escape sequence, replace with char
1789  switch(str[i + 1])
1790  {
1791  case 'n':
1792  retStr << '\n';
1793  ++i;
1794  break;
1795  case '"':
1796  retStr << '"';
1797  ++i;
1798  break;
1799  case 't':
1800  retStr << '\t';
1801  ++i;
1802  break;
1803  case 'r':
1804  retStr << '\r';
1805  ++i;
1806  break;
1807  case '\\':
1808  retStr << '\\';
1809  ++i;
1810  break;
1811  default:
1812  retStr << str[i];
1813  }
1814  else
1815  retStr << str[i];
1816  }
1817  if(i == sz - 1)
1818  retStr << str[sz - 1]; // output last character (which can't escape anything)
1819 
1820  return retStr.str();
1821 } // end restoreJSONStringEntities()
1822 
1823 //==============================================================================
1824 // fillFromJSON
1825 // Clears and fills the view from the JSON string.
1826 // Returns -1 on failure
1827 //
1828 // first level keys:
1829 // NAME
1830 // DATA_SET
1831 int TableView::fillFromJSON(const std::string& json)
1832 {
1833  std::vector<std::string> keys;
1834  keys.push_back("NAME");
1835  keys.push_back("COMMENT");
1836  keys.push_back("AUTHOR");
1837  keys.push_back("CREATION_TIME");
1838  // keys.push_back ("COL_TYPES");
1839  keys.push_back("DATA_SET");
1840  enum
1841  {
1842  CV_JSON_FILL_NAME,
1843  CV_JSON_FILL_COMMENT,
1844  CV_JSON_FILL_AUTHOR,
1845  CV_JSON_FILL_CREATION_TIME,
1846  // CV_JSON_FILL_COL_TYPES,
1847  CV_JSON_FILL_DATA_SET
1848  };
1849 
1850  //__COUTV__(json);
1851 
1852  sourceColumnMismatchCount_ = 0;
1853  sourceColumnMissingCount_ = 0;
1854  sourceColumnNames_.clear(); // reset
1855  unsigned int colFoundCount = 0;
1856  unsigned int i = 0;
1857  unsigned int row = -1;
1858  unsigned int colSpeedup = 0;
1859  unsigned int startString, startNumber, endNumber = -1;
1860  unsigned int bracketCount = 0;
1861  unsigned int sqBracketCount = 0;
1862  bool inQuotes = 0;
1863  bool newString = 0;
1864  bool newValue = 0;
1865  bool isDataArray = 0;
1866  bool keyIsMatch, keyIsComment;
1867  unsigned int keyIsMatchIndex, keyIsMatchStorageIndex, keyIsMatchCommentIndex;
1868  const std::string COMMENT_ALT_KEY = "COMMENT";
1869 
1870  std::string extractedString = "", currKey = "", currVal = "";
1871  unsigned int currDepth;
1872 
1873  std::vector<std::string> jsonPath;
1874  std::vector<char> jsonPathType; // indicator of type in jsonPath: { [ K
1875  char lastPopType = '_'; // either: _ { [ K
1876  // _ indicates reset pop (this happens when a new {obj} starts)
1877  unsigned int matchedKey = -1;
1878  unsigned int lastCol = -1;
1879 
1880  // find all depth 1 matching keys
1881  for(; i < json.size(); ++i)
1882  {
1883  switch(json[i])
1884  {
1885  case '"':
1886  if(i - 1 < json.size() && // ignore if escaped
1887  json[i - 1] == '\\')
1888  break;
1889 
1890  inQuotes = !inQuotes; // toggle in quotes if not escaped
1891  if(inQuotes)
1892  startString = i;
1893  else
1894  {
1895  extractedString = restoreJSONStringEntities(
1896  json.substr(startString + 1, i - startString - 1));
1897  newString = 1; // have new string!
1898  }
1899  break;
1900  case ':':
1901  if(inQuotes)
1902  break; // skip if in quote
1903 
1904  // must be a json object level to have a key
1905  if(jsonPathType[jsonPathType.size() - 1] != '{' ||
1906  !newString) // and must have a string for key
1907  {
1908  __COUT__ << "Invalid ':' position" << __E__;
1909  return -1;
1910  }
1911 
1912  // valid, so take key
1913  jsonPathType.push_back('K');
1914  jsonPath.push_back(extractedString);
1915  startNumber = i;
1916  newString = 0; // clear flag
1917  endNumber = -1; // reset end number index
1918  break;
1919 
1920  // if(isKey ||
1921  // isDataArray)
1922  // {
1923  // std::cout << "Invalid ':' position" << __E__;
1924  // return -1;
1925  // }
1926  // isKey = 1; //new value is a key
1927  // newValue = 1;
1928  // startNumber = i;
1929  // break;
1930  case ',':
1931  if(inQuotes)
1932  break; // skip if in quote
1933  if(lastPopType == '{') // don't need value again of nested object
1934  {
1935  // check if the nested object was the value to a key, if so, pop key
1936  if(jsonPathType[jsonPathType.size() - 1] == 'K')
1937  {
1938  lastPopType = 'K';
1939  jsonPath.pop_back();
1940  jsonPathType.pop_back();
1941  }
1942  break; // skip , handling if {obj} just ended
1943  }
1944 
1945  if(newString)
1946  currVal = extractedString;
1947  else // number value
1948  {
1949  if(endNumber == (unsigned int)-1 || // take i as end number if needed
1950  endNumber <= startNumber)
1951  endNumber = i;
1952  // extract number value
1953  if(endNumber <= startNumber) // empty data, could be {}
1954  currVal = "";
1955  else
1956  currVal = json.substr(startNumber + 1, endNumber - startNumber - 1);
1957  }
1958 
1959  currDepth = bracketCount;
1960 
1961  if(jsonPathType[jsonPathType.size() - 1] == 'K') // this is the value to key
1962  {
1963  currKey = jsonPath[jsonPathType.size() - 1];
1964  newValue = 1; // new value to consider!
1965 
1966  // pop key
1967  lastPopType = 'K';
1968  jsonPath.pop_back();
1969  jsonPathType.pop_back();
1970  }
1971  else if(jsonPathType[jsonPathType.size() - 1] ==
1972  '[') // this is a value in array
1973  {
1974  // key is last key
1975  for(unsigned int k = jsonPathType.size() - 2; k < jsonPathType.size();
1976  --k)
1977  if(jsonPathType[k] == 'K')
1978  {
1979  currKey = jsonPath[k];
1980  break;
1981  }
1982  else if(k == 0)
1983  {
1984  __COUT__ << "Invalid array position" << __E__;
1985  return -1;
1986  }
1987 
1988  newValue = 1; // new value to consider!
1989  isDataArray = 1;
1990  }
1991  else // { is an error
1992  {
1993  __COUT__ << "Invalid ',' position" << __E__;
1994  return -1;
1995  }
1996 
1997  startNumber = i;
1998  break;
1999 
2000  case '{':
2001  if(inQuotes)
2002  break; // skip if in quote
2003  lastPopType = '_'; // reset because of new object
2004  jsonPathType.push_back('{');
2005  jsonPath.push_back("{");
2006  ++bracketCount;
2007  break;
2008 
2009  // ++bracketCount;
2010  // isDataArray = 0;
2011  // isKey = 0;
2012  // endingObject = 0;
2013  // break;
2014  case '}':
2015  if(inQuotes)
2016  break; // skip if in quote
2017 
2018  if(lastPopType != '{' && // don't need value again of nested object
2019  jsonPathType[jsonPathType.size() - 1] == 'K') // this is the value to key
2020  {
2021  currDepth = bracketCount;
2022  currKey = jsonPath[jsonPathType.size() - 1];
2023  if(newString)
2024  currVal = extractedString;
2025  else // number value
2026  {
2027  if(endNumber == (unsigned int)-1 || // take i as end number if needed
2028  endNumber <= startNumber)
2029  endNumber = i;
2030  // extract val
2031  if(endNumber <= startNumber) // empty data, could be {}
2032  currVal = "";
2033  else
2034  currVal =
2035  json.substr(startNumber + 1, endNumber - startNumber - 1);
2036  }
2037  newValue = 1; // new value to consider!
2038  // pop key
2039  jsonPath.pop_back();
2040  jsonPathType.pop_back();
2041  }
2042  // pop {
2043  if(jsonPathType[jsonPathType.size() - 1] != '{')
2044  {
2045  __COUT__ << "Invalid '}' position" << __E__;
2046  return -1;
2047  }
2048  lastPopType = '{';
2049  jsonPath.pop_back();
2050  jsonPathType.pop_back();
2051  --bracketCount;
2052  break;
2053  case '[':
2054  if(inQuotes)
2055  break; // skip if in quote
2056  jsonPathType.push_back('[');
2057  jsonPath.push_back("[");
2058  ++sqBracketCount;
2059  startNumber = i;
2060  break;
2061  case ']':
2062  if(inQuotes)
2063  break; // skip if in quote
2064 
2065  // must be an array at this level (in order to close it)
2066  if(jsonPathType[jsonPathType.size() - 1] != '[')
2067  {
2068  __COUT__ << "Invalid ']' position" << __E__;
2069  return -1;
2070  }
2071 
2072  currDepth = bracketCount;
2073 
2074  // This is an array value
2075  if(newString)
2076  currVal = extractedString;
2077  else // number value
2078  {
2079  if(endNumber == (unsigned int)-1 || // take i as end number if needed
2080  endNumber <= startNumber)
2081  endNumber = i;
2082  // extract val
2083  if(endNumber <= startNumber) // empty data, could be {}
2084  currVal = "";
2085  else
2086  currVal = json.substr(startNumber + 1, endNumber - startNumber - 1);
2087  }
2088  isDataArray = 1;
2089 
2090  // key is last key
2091  for(unsigned int k = jsonPathType.size() - 2; k < jsonPathType.size(); --k)
2092  if(jsonPathType[k] == 'K')
2093  {
2094  currKey = jsonPath[k];
2095  break;
2096  }
2097  else if(k == 0)
2098  {
2099  __COUT__ << "Invalid array position" << __E__;
2100  return -1;
2101  }
2102 
2103  // pop [
2104  if(jsonPathType[jsonPathType.size() - 1] != '[')
2105  {
2106  __COUT__ << "Invalid ']' position" << __E__;
2107  return -1;
2108  }
2109  lastPopType = '[';
2110  jsonPath.pop_back();
2111  jsonPathType.pop_back();
2112  --sqBracketCount;
2113  break;
2114  case ' ': // white space handling for numbers
2115  case '\t':
2116  case '\n':
2117  case '\r':
2118  if(inQuotes)
2119  break; // skip if in quote
2120  if(startNumber != (unsigned int)-1 && endNumber == (unsigned int)-1)
2121  endNumber = i;
2122  startNumber = i;
2123  break;
2124  default:;
2125  }
2126 
2127  if(0) // for debugging
2128  {
2129  std::cout << i << ":\t" << json[i] << " - ";
2130 
2131  std::cout << "ExtKey=";
2132  for(unsigned int k = 0; k < jsonPath.size(); ++k)
2133  std::cout << jsonPath[k] << "/";
2134  std::cout << " - ";
2135  std::cout << lastPopType << " ";
2136  std::cout << bracketCount << " ";
2137  std::cout << sqBracketCount << " ";
2138  std::cout << inQuotes << " ";
2139  std::cout << newValue << "-";
2140  std::cout << currKey << "-{" << currDepth << "}:";
2141  std::cout << currVal << " ";
2142  std::cout << startNumber << "-";
2143  std::cout << endNumber << " ";
2144  std::cout << "\n";
2145  }
2146 
2147  // continue;
2148 
2149  // handle a new completed value
2150  if(newValue)
2151  {
2152  // std::cout << "ExtKey=";
2153  // for(unsigned int k=0;k<jsonPath.size();++k)
2154  // std::cout << jsonPath[k] << "/";
2155  //
2156  // std::cout << currKey << "-{" << currDepth << "}: - ";
2157  //
2158  // if(isDataArray)
2159  // std::cout << "Array:: ";
2160  // if(newString)
2161  // std::cout << "New String:: ";
2162  // else
2163  // std::cout << "New Number:: ";
2164  //
2165  // std::cout << currVal << "\n";
2166 
2167  // extract only what we care about
2168  // for TableView only care about matching depth 1
2169 
2170  // handle matching depth 1 keys
2171 
2172  matchedKey = -1; // init to unfound
2173  for(unsigned int k = 0; k < keys.size(); ++k)
2174  if((currDepth == 1 && keys[k] == currKey) ||
2175  (currDepth > 1 && keys[k] == jsonPath[1]))
2176  matchedKey = k;
2177 
2178  if(matchedKey != (unsigned int)-1)
2179  {
2180  // std::cout << "New Data for:: key[" << matchedKey << "]-"
2181  //<< keys[matchedKey] << "\n";
2182 
2183  switch(matchedKey)
2184  {
2185  case CV_JSON_FILL_NAME:
2186  if(currDepth == 1)
2187  setTableName(currVal);
2188  break;
2189  case CV_JSON_FILL_COMMENT:
2190  if(currDepth == 1)
2191  setComment(currVal);
2192  break;
2193  case CV_JSON_FILL_AUTHOR:
2194  if(currDepth == 1)
2195  setAuthor(currVal);
2196  break;
2197  case CV_JSON_FILL_CREATION_TIME:
2198  if(currDepth == 1)
2199  setCreationTime(strtol(currVal.c_str(), 0, 10));
2200  break;
2201  // case CV_JSON_FILL_COL_TYPES:
2202  //
2203  // break;
2204  case CV_JSON_FILL_DATA_SET:
2205  // std::cout << "CV_JSON_FILL_DATA_SET New Data for::
2206  //"
2207  //<< matchedKey << "]-" << keys[matchedKey]
2208  //<<
2209  //"/" << currDepth << ".../" << currKey <<
2210  //"\n";
2211 
2212  if(currDepth == 2) // second level depth
2213  {
2214  // if matches first column name.. then add new row
2215  // else add to current row
2216  unsigned int col, ccnt = 0;
2217  unsigned int noc = getNumberOfColumns();
2218  for(; ccnt < noc; ++ccnt)
2219  {
2220  // use colSpeedup to change the first column we search
2221  // for each iteration.. since we expect the data to
2222  // be arranged in column order
2223 
2224  if(fillWithLooseColumnMatching_)
2225  {
2226  // loose column matching makes no attempt to
2227  // match the column names
2228  // just assumes the data is in the correct order
2229 
2230  col = colSpeedup;
2231 
2232  // auto matched
2233  if(col <= lastCol) // add row (use lastCol in case new
2234  // column-0 was added
2235  row = addRow();
2236  lastCol = col;
2237  if(getNumberOfRows() == 1) // only for first row
2238  sourceColumnNames_.emplace(currKey);
2239 
2240  // add value to row and column
2241 
2242  if(row >= getNumberOfRows())
2243  {
2244  __SS__ << "Invalid row"
2245  << __E__; // should be impossible?
2246  std::cout << ss.str();
2247  __SS_THROW__;
2248  return -1;
2249  }
2250 
2251  theDataView_[row][col] =
2252  currVal; // THERE IS NO CHECK FOR WHAT IS READ FROM
2253  // THE DATABASE. IT SHOULD BE ALREADY
2254  // CONSISTENT
2255  break;
2256  }
2257  else
2258  {
2259  col = (ccnt + colSpeedup) % noc;
2260 
2261  // match key by ignoring '_'
2262  // also accept COMMENT == COMMENT_DESCRIPTION
2263  // (this is for backwards compatibility..)
2264  keyIsMatch = true;
2265  keyIsComment = true;
2266  for(keyIsMatchIndex = 0,
2267  keyIsMatchStorageIndex = 0,
2268  keyIsMatchCommentIndex = 0;
2269  keyIsMatchIndex < currKey.size();
2270  ++keyIsMatchIndex)
2271  {
2272  if(columnsInfo_[col]
2273  .getStorageName()[keyIsMatchStorageIndex] ==
2274  '_')
2275  ++keyIsMatchStorageIndex; // skip to next storage
2276  // character
2277  if(currKey[keyIsMatchIndex] == '_')
2278  continue; // skip to next character
2279 
2280  // match to storage name
2281  if(keyIsMatchStorageIndex >=
2282  columnsInfo_[col].getStorageName().size() ||
2283  currKey[keyIsMatchIndex] !=
2284  columnsInfo_[col]
2285  .getStorageName()[keyIsMatchStorageIndex])
2286  {
2287  // size mismatch or character mismatch
2288  keyIsMatch = false;
2289  if(!keyIsComment)
2290  break;
2291  }
2292 
2293  // check also if alternate comment is matched
2294  if(keyIsComment &&
2295  keyIsMatchCommentIndex < COMMENT_ALT_KEY.size())
2296  {
2297  if(currKey[keyIsMatchIndex] !=
2298  COMMENT_ALT_KEY[keyIsMatchCommentIndex])
2299  {
2300  // character mismatch with COMMENT
2301  keyIsComment = false;
2302  }
2303  }
2304 
2305  ++keyIsMatchStorageIndex; // go to next character
2306  }
2307 
2308  if(keyIsMatch ||
2309  keyIsComment) // currKey ==
2310  // columnsInfo_[c].getStorageName())
2311  {
2312  // matched
2313  if(col <= lastCol) // add row (use lastCol in case
2314  // new column-0 was added
2315  {
2316  if(getNumberOfRows()) // skip first time
2317  sourceColumnMissingCount_ +=
2318  getNumberOfColumns() - colFoundCount;
2319 
2320  colFoundCount = 0; // reset column found count
2321  row = addRow();
2322  }
2323  lastCol = col;
2324  ++colFoundCount;
2325 
2326  if(getNumberOfRows() == 1) // only for first row
2327  sourceColumnNames_.emplace(currKey);
2328 
2329  // add value to row and column
2330 
2331  if(row >= getNumberOfRows())
2332  {
2333  __SS__ << "Invalid row"
2334  << __E__; // should be impossible?!
2335  __COUT__ << "\n" << ss.str();
2336  __SS_THROW__;
2337  return -1; // never gets here
2338  }
2339 
2340  theDataView_[row][col] = currVal;
2341  break;
2342  }
2343  }
2344  }
2345 
2346  colSpeedup =
2347  (colSpeedup + 1) %
2348  noc; // short cut to proper column hopefully in next search
2349 
2350  if(ccnt >= getNumberOfColumns())
2351  {
2352  __SS__
2353  << "\n\nInvalid column in JSON source data: " << currKey
2354  << " not found in column names of table named "
2355  << getTableName() << "."
2356  << __E__; // input data doesn't match config description
2357  __COUT__ << "\n" << ss.str();
2358  // CHANGED on 11/10/2016
2359  // to.. try just not populating data instead of error
2360  ++sourceColumnMismatchCount_; // but count errors
2361  if(getNumberOfRows() == 1) // only for first row
2362  sourceColumnNames_.emplace(currKey);
2363 
2364  //__SS_THROW__;
2365  __COUT_WARN__
2366  << "Ignoring error, and not populating missing column."
2367  << __E__;
2368  }
2369  }
2370  break;
2371  default:; // unknown match?
2372  }
2373  }
2374 
2375  // clean up handling of new value
2376 
2377  newString = 0; // toggle flag
2378  newValue = 0; // toggle flag
2379  isDataArray = 0;
2380  endNumber = -1; // reset end number index
2381  }
2382 
2383  // if(i>200) break; //185
2384  }
2385 
2386  //__COUT__ << "Done!" << __E__;
2387 
2388  // print();
2389 
2390  return 0; // success
2391 } // end fillFromJSON()
2392 
2393 //==============================================================================
2394 bool TableView::isURIEncodedCommentTheSame(const std::string& comment) const
2395 {
2396  std::string compareStr = StringMacros::decodeURIComponent(comment);
2397  return comment_ == compareStr;
2398 }
2399 //
2401 // bool TableView::isValueTheSame(const std::string &valueStr,
2402 // unsigned int r, unsigned int c) const
2403 //{
2404 // __COUT__ << "valueStr " << valueStr << __E__;
2405 //
2406 // if(!(c < columnsInfo_.size() && r < getNumberOfRows()))
2407 // {
2408 // __SS__ << "Invalid row (" << (int)r << ") col (" << (int)c << ") requested!" <<
2409 //__E__;
2410 // __SS_THROW__;
2411 // }
2412 //
2413 // __COUT__ << "originalValueStr " << theDataView_[r][c] << __E__;
2414 //
2415 // if(columnsInfo_[c].getDataType() == TableViewColumnInfo::DATATYPE_TIME)
2416 // {
2417 // time_t valueTime(strtol(valueStr.c_str(),0,10));
2418 // time_t originalValueTime;
2419 // getValue(originalValueTime,r,c);
2420 // __COUT__ << "time_t valueStr " << valueTime << __E__;
2421 // __COUT__ << "time_t originalValueStr " << originalValueTime << __E__;
2422 // return valueTime == originalValueTime;
2423 // }
2424 // else
2425 // {
2426 // return valueStr == theDataView_[r][c];
2427 // }
2428 //}
2429 
2430 //==============================================================================
2431 // fillFromCSV
2432 // Fills the view from the CSV string.
2433 //
2434 // Note: converts all %## to the ascii character, # is hex nibble
2435 // (e.g. '%' must be represented as "%25")
2436 //
2437 // dataOffset := starting destination row
2438 //
2439 // while there are row entries in the data.. replace
2440 // data range from [dataOffset, dataOffset+chunkSize-1]
2441 // ... note if less rows, this means rows were deleted
2442 // ... if more, then rows were added.
2443 //
2444 // ',' next cell delimiter
2445 // ';' next row delimiter
2446 //
2447 //
2448 // if author == "", do nothing special for author and timestamp column
2449 // if author != "", assign author for any row that has been modified, and assign now as
2450 // timestamp
2451 //
2452 // Returns -1 if data was same and pre-existing content
2453 // Returns 1 if data was same, but columns are different
2454 // otherwise 0
2455 //
2456 int TableView::fillFromCSV(const std::string& data,
2457  const int& dataOffset,
2458  const std::string& author)
2459 {
2460  int retVal = 0;
2461 
2462  int r = dataOffset;
2463  int c = 0;
2464 
2465  int i = 0; // use to parse data std::string
2466  int j = data.find(',', i); // find next cell delimiter
2467  int k = data.find(';', i); // find next row delimiter
2468 
2469  bool rowWasModified;
2470  unsigned int countRowsModified = 0;
2471  int authorCol = findColByType(TableViewColumnInfo::TYPE_AUTHOR);
2472  int timestampCol = findColByType(TableViewColumnInfo::TYPE_TIMESTAMP);
2473  // std::string valueStr, tmpTimeStr, originalValueStr;
2474 
2475  while(k != (int)(std::string::npos))
2476  {
2477  rowWasModified = false;
2478  if(r >= (int)getNumberOfRows())
2479  {
2480  addRow();
2481  //__COUT__ << "Row added" << __E__;
2482  rowWasModified = true;
2483  }
2484 
2485  while(j < k && j != (int)(std::string::npos))
2486  {
2487  //__COUT__ << "Col " << (int)c << __E__;
2488 
2489  // skip last 2 columns
2490  if(c >= (int)getNumberOfColumns() - 2)
2491  {
2492  i = j + 1;
2493  j = data.find(',', i); // find next cell delimiter
2494  ++c;
2495  continue;
2496  }
2497 
2498  if(setURIEncodedValue(data.substr(i, j - i), r, c))
2499  rowWasModified = true;
2500 
2501  i = j + 1;
2502  j = data.find(',', i); // find next cell delimiter
2503  ++c;
2504  }
2505 
2506  // if row was modified, assign author and timestamp
2507  if(author != "" && rowWasModified)
2508  {
2509  __COUT__ << "Row=" << (int)r << " was modified!" << __E__;
2510  setValue(author, r, authorCol);
2511  setValue(time(0), r, timestampCol);
2512  }
2513 
2514  if(rowWasModified)
2515  ++countRowsModified;
2516 
2517  ++r;
2518  c = 0;
2519 
2520  i = k + 1;
2521  j = data.find(',', i); // find next cell delimiter
2522  k = data.find(';', i); // find new row delimiter
2523  }
2524 
2525  // delete excess rows
2526  while(r < (int)getNumberOfRows())
2527  {
2528  deleteRow(r);
2529  __COUT__ << "Row deleted: " << (int)r << __E__;
2530  ++countRowsModified;
2531  }
2532 
2533  __COUT_INFO__ << "countRowsModified=" << countRowsModified << __E__;
2534 
2535  if(!countRowsModified)
2536  {
2537  // check that source columns match storage name
2538  // otherwise allow same data...
2539 
2540  bool match = getColumnStorageNames().size() == getSourceColumnNames().size();
2541  if(match)
2542  {
2543  for(auto& destColName : getColumnStorageNames())
2544  if(getSourceColumnNames().find(destColName) ==
2545  getSourceColumnNames().end())
2546  {
2547  __COUT__ << "Found column name mismach for '" << destColName
2548  << "'... So allowing same data!" << __E__;
2549 
2550  match = false;
2551  break;
2552  }
2553  }
2554  // if still a match, do not allow!
2555  if(match)
2556  {
2557  __SS__ << "No rows were modified! No reason to fill a view with same content."
2558  << __E__;
2559  __COUT__ << "\n" << ss.str();
2560  return -1;
2561  }
2562  // else mark with retVal
2563  retVal = 1;
2564  } // end same check
2565 
2566  // print(); //for debugging
2567 
2568  // setup sourceColumnNames_ to be correct
2569  sourceColumnNames_.clear();
2570  for(unsigned int i = 0; i < getNumberOfColumns(); ++i)
2571  sourceColumnNames_.emplace(getColumnsInfo()[i].getStorageName());
2572 
2573  init(); // verify new table (throws runtime_errors)
2574 
2575  // printout for debugging
2576  // __SS__ << "\n";
2577  // print(ss);
2578  // __COUT__ << "\n" << ss.str() << __E__;
2579 
2580  return retVal;
2581 } // end fillFromCSV()
2582 
2583 //==============================================================================
2584 // setURIEncodedValue
2585 // converts all %## to the ascii character
2586 // returns true if value was different than original value
2587 //
2588 //
2589 // if author == "", do nothing special for author and timestamp column
2590 // if author != "", assign author for any row that has been modified, and assign now as
2591 // timestamp
2592 bool TableView::setURIEncodedValue(const std::string& value,
2593  const unsigned int& r,
2594  const unsigned int& c,
2595  const std::string& author)
2596 {
2597  if(!(c < columnsInfo_.size() && r < getNumberOfRows()))
2598  {
2599  __SS__ << "Invalid row (" << (int)r << ") col (" << (int)c << ") requested!"
2600  << "Number of Rows = " << getNumberOfRows()
2601  << "Number of Columns = " << columnsInfo_.size() << __E__;
2602  print(ss);
2603  __SS_THROW__;
2604  }
2605 
2606  std::string valueStr = StringMacros::decodeURIComponent(value);
2607  std::string originalValueStr =
2608  getValueAsString(r, c, false); // do not convert env variables
2609 
2610  //__COUT__ << "valueStr " << valueStr << __E__;
2611  //__COUT__ << "originalValueStr " << originalValueStr << __E__;
2612 
2613  if(columnsInfo_[c].getDataType() == TableViewColumnInfo::DATATYPE_NUMBER)
2614  {
2615  // check if valid number
2616  std::string convertedString = StringMacros::convertEnvironmentVariables(valueStr);
2617  // do not check here, let init check
2618  // if this is a link to valid number, then this is an improper check.
2619  // if(!StringMacros::isNumber(convertedString))
2620  // {
2621  // __SS__ << "\tIn configuration " << tableName_
2622  // << " at column=" << columnsInfo_[c].getName() << " the value
2623  //set
2624  //("
2625  // << convertedString << ")"
2626  // << " is not a number! Please fix it or change the column
2627  // type..."
2628  // << __E__;
2629  // __SS_THROW__;
2630  // }
2631  theDataView_[r][c] = valueStr;
2632  }
2633  else if(columnsInfo_[c].getDataType() == TableViewColumnInfo::DATATYPE_TIME)
2634  {
2635  // valueStr = StringMacros::decodeURIComponent(data.substr(i,j-i));
2636  //
2637  // getValue(tmpTimeStr,r,c);
2638  // if(valueStr != tmpTimeStr)//theDataView_[r][c])
2639  // {
2640  // __COUT__ << "valueStr=" << valueStr <<
2641  // " theDataView_[r][c]=" << tmpTimeStr << __E__;
2642  // rowWasModified = true;
2643  // }
2644 
2645  setValue(time_t(strtol(valueStr.c_str(), 0, 10)), r, c);
2646  }
2647  else
2648  theDataView_[r][c] = valueStr;
2649 
2650  bool rowWasModified =
2651  (originalValueStr !=
2652  getValueAsString(r, c, false)); // do not convert env variables
2653 
2654  // if row was modified, assign author and timestamp
2655  if(author != "" && rowWasModified)
2656  {
2657  __COUT__ << "Row=" << (int)r << " was modified!" << __E__;
2658  int authorCol = findColByType(TableViewColumnInfo::TYPE_AUTHOR);
2659  int timestampCol = findColByType(TableViewColumnInfo::TYPE_TIMESTAMP);
2660  setValue(author, r, authorCol);
2661  setValue(time(0), r, timestampCol);
2662  }
2663 
2664  return rowWasModified;
2665 } // end setURIEncodedValue()
2666 
2667 //==============================================================================
2668 void TableView::resizeDataView(unsigned int nRows, unsigned int nCols)
2669 {
2670  // FIXME This maybe should disappear but I am using it in ConfigurationHandler
2671  // still...
2672  theDataView_.resize(nRows, std::vector<std::string>(nCols));
2673 }
2674 
2675 //==============================================================================
2676 // addRow
2677 // returns index of added row, always is last row
2678 // return -1 on failure (throw error)
2679 //
2680 // if baseNameAutoUID != "", creates a UID based on this base name
2681 // and increments and appends an integer relative to the previous last row
2682 unsigned int TableView::addRow(const std::string& author,
2683  bool incrementUniqueData,
2684  std::string baseNameAutoUID,
2685  unsigned int rowToAdd)
2686 {
2687  // default to last row
2688  if(rowToAdd == (unsigned int)-1)
2689  rowToAdd = getNumberOfRows();
2690 
2691  theDataView_.resize(getNumberOfRows() + 1,
2692  std::vector<std::string>(getNumberOfColumns()));
2693 
2694  // shift data down the table if necessary
2695  for(unsigned int r = getNumberOfRows() - 2; r >= rowToAdd; --r)
2696  {
2697  if(r == (unsigned int)-1)
2698  break; // quit wrap around case
2699  for(unsigned int col = 0; col < getNumberOfColumns(); ++col)
2700  theDataView_[r + 1][col] = theDataView_[r][col];
2701  }
2702 
2703  std::vector<std::string> defaultRowValues = getDefaultRowValues();
2704 
2705  char indexString[1000];
2706  std::string tmpString, baseString;
2707  bool foundAny;
2708  unsigned int index;
2709  unsigned int maxUniqueData;
2710  std::string numString;
2711 
2712  // fill each col of new row with default values
2713  // if a row is a unique data row, increment last row in attempt to make a legal
2714  // column
2715  for(unsigned int col = 0; col < getNumberOfColumns(); ++col)
2716  {
2717  // __COUT__ << col << " " << columnsInfo_[col].getType() << " == " <<
2718  // TableViewColumnInfo::TYPE_UNIQUE_DATA << __E__;
2719 
2720  // baseNameAutoUID indicates to attempt to make row unique
2721  // add index to max number
2722  if(incrementUniqueData &&
2723  (col == getColUID() ||
2724  (getNumberOfRows() > 1 &&
2725  (columnsInfo_[col].getType() == TableViewColumnInfo::TYPE_UNIQUE_DATA ||
2726  columnsInfo_[col].getType() ==
2727  TableViewColumnInfo::TYPE_UNIQUE_GROUP_DATA))))
2728  {
2729  // __COUT__ << "Current unique data entry is data[" << rowToAdd
2730  // << "][" << col << "] = '" << theDataView_[rowToAdd][col]
2731  //<<
2732  //"'"
2733  // << __E__;
2734 
2735  maxUniqueData = 0;
2736  tmpString = "";
2737  baseString = "";
2738 
2739  // find max in rows
2740 
2741  // this->print();
2742 
2743  for(unsigned int r = 0; r < getNumberOfRows(); ++r)
2744  {
2745  if(r == rowToAdd)
2746  continue; // skip row to add
2747 
2748  // find last non numeric character
2749 
2750  foundAny = false;
2751  tmpString = theDataView_[r][col];
2752 
2753  //__COUT__ << "tmpString " << tmpString << __E__;
2754 
2755  for(index = tmpString.length() - 1; index < tmpString.length(); --index)
2756  {
2757  //__COUT__ << index << " tmpString[index] " << tmpString[index] <<
2758  //__E__;
2759  if(!(tmpString[index] >= '0' && tmpString[index] <= '9'))
2760  break; // if not numeric, break
2761  foundAny = true;
2762  }
2763 
2764  //__COUT__ << "index " << index << __E__;
2765 
2766  if(tmpString.length() && foundAny) // then found a numeric substring
2767  {
2768  // create numeric substring
2769  numString = tmpString.substr(index + 1);
2770  tmpString = tmpString.substr(0, index + 1);
2771 
2772  //__COUT__ << "Found unique data base string '" <<
2773  // tmpString << "' and number string '" << numString <<
2774  // "' in last record '" << theDataView_[r][col] << "'" << __E__;
2775 
2776  // extract number
2777  sscanf(numString.c_str(), "%u", &index);
2778 
2779  if(index > maxUniqueData)
2780  {
2781  maxUniqueData = index;
2782  baseString = tmpString;
2783  }
2784  }
2785  }
2786 
2787  ++maxUniqueData; // increment
2788 
2789  sprintf(indexString, "%u", maxUniqueData);
2790  //__COUT__ << "indexString " << indexString << __E__;
2791 
2792  //__COUT__ << "baseNameAutoUID " << baseNameAutoUID << __E__;
2793  if(col == getColUID())
2794  {
2795  // handle UID case
2796  if(baseNameAutoUID != "")
2797  theDataView_[rowToAdd][col] = baseNameAutoUID + indexString;
2798  else
2799  theDataView_[rowToAdd][col] = baseString + indexString;
2800  }
2801  else
2802  theDataView_[rowToAdd][col] = baseString + indexString;
2803 
2804  __COUT__ << "New unique data entry is data[" << rowToAdd << "][" << col
2805  << "] = '" << theDataView_[rowToAdd][col] << "'" << __E__;
2806 
2807  // this->print();
2808  }
2809  else
2810  theDataView_[rowToAdd][col] = defaultRowValues[col];
2811  }
2812 
2813  if(author != "")
2814  {
2815  __COUT__ << "Row=" << rowToAdd << " was created!" << __E__;
2816  int authorCol = findColByType(TableViewColumnInfo::TYPE_AUTHOR);
2817  int timestampCol = findColByType(TableViewColumnInfo::TYPE_TIMESTAMP);
2818  setValue(author, rowToAdd, authorCol);
2819  setValue(time(0), rowToAdd, timestampCol);
2820  }
2821 
2822  return rowToAdd;
2823 } // end addRow()
2824 
2825 //==============================================================================
2826 // deleteRow
2827 // throws exception on failure
2828 void TableView::deleteRow(int r)
2829 {
2830  if(r >= (int)getNumberOfRows())
2831  {
2832  // out of bounds
2833  __SS__ << "Row " << (int)r
2834  << " is out of bounds (Row Count = " << getNumberOfRows()
2835  << ") and can not be deleted." << __E__;
2836  __SS_THROW__;
2837  }
2838 
2839  theDataView_.erase(theDataView_.begin() + r);
2840 } // end deleteRow()
2841 
2842 //==============================================================================
2843 // getChildLink ~
2844 // find the pair of columns associated with a child link.
2845 //
2846 // c := a member column of the pair
2847 //
2848 // returns:
2849 // isGroup := indicates pair found is a group link
2850 // linkPair := pair of columns that are part of the link
2851 //
2852 // a unique link is defined by two columns: TYPE_START_CHILD_LINK,
2853 // TYPE_START_CHILD_LINK_UID
2854 // a group link is defined by two columns: TYPE_START_CHILD_LINK,
2855 // TYPE_START_CHILD_LINK_GROUP_ID
2856 //
2857 // returns true if column is member of a group or unique link.
2858 const bool TableView::getChildLink(
2859  const unsigned int& c,
2860  bool& isGroup,
2861  std::pair<unsigned int /*link col*/, unsigned int /*link id col*/>& linkPair) const
2862 {
2863  if(!(c < columnsInfo_.size()))
2864  {
2865  __SS__ << "Invalid col (" << (int)c << ") requested!" << __E__;
2866  __SS_THROW__;
2867  }
2868 
2869  //__COUT__ << "getChildLink for col: " << (int)c << "-" <<
2870  // columnsInfo_[c].getType() << "-" << columnsInfo_[c].getName() << __E__;
2871 
2872  // check if column is a child link UID
2873  if((isGroup = columnsInfo_[c].isChildLinkGroupID()) ||
2874  columnsInfo_[c].isChildLinkUID())
2875  {
2876  // must be part of unique link, (or invalid table?)
2877  //__COUT__ << "col: " << (int)c << __E__;
2878  linkPair.second = c;
2879  std::string index = columnsInfo_[c].getChildLinkIndex();
2880 
2881  //__COUT__ << "index: " << index << __E__;
2882 
2883  // find pair link
2884  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
2885  {
2886  //__COUT__ << "try: " << col << "-" << columnsInfo_[col].getType() << "-" <<
2887  // columnsInfo_[col].getName() << __E__;
2888  if(col == c)
2889  continue; // skip column c that we know
2890  else if(columnsInfo_[col].isChildLink() &&
2891  index == columnsInfo_[col].getChildLinkIndex())
2892  {
2893  // found match!
2894  //__COUT__ << "getChildLink Found match for col: " << (int)c << " at " <<
2895  // col << __E__;
2896  linkPair.first = col;
2897  return true;
2898  }
2899  }
2900 
2901  // if here then invalid table!
2902  __SS__ << "\tIn view: " << tableName_
2903  << ", Can't find complete child link for column name "
2904  << columnsInfo_[c].getName() << __E__;
2905  __SS_THROW__;
2906  }
2907 
2908  if(!columnsInfo_[c].isChildLink())
2909  return false; // cant be unique link
2910 
2911  // this is child link, so find pair link uid or gid column
2912  linkPair.first = c;
2913  std::string index = columnsInfo_[c].getChildLinkIndex();
2914 
2915  //__COUT__ << "index: " << index << __E__;
2916 
2917  // find pair link
2918  for(unsigned int col = 0; col < columnsInfo_.size(); ++col)
2919  {
2920  //__COUT__ << "try: " << col << "-" << columnsInfo_[col].getType() << "-" <<
2921  // columnsInfo_[col].getName() << __E__;
2922  if(col == c)
2923  continue; // skip column c that we know
2924  // __COUT__ << "try: " << col << "-" << columnsInfo_[col].getType() <<
2925  // "-" << columnsInfo_[col].getName() <<
2926  // "-u" << columnsInfo_[col].isChildLinkUID() <<
2927  // "-g" << columnsInfo_[col].isChildLinkGroupID() << __E__;
2928  //
2929  // if(columnsInfo_[col].isChildLinkUID())
2930  // __COUT__ << "-L" << columnsInfo_[col].getChildLinkIndex() << __E__;
2931  //
2932  // if(columnsInfo_[col].isChildLinkGroupID())
2933  // __COUT__ << "-L" << columnsInfo_[col].getChildLinkIndex() << __E__;
2934 
2935  if(((columnsInfo_[col].isChildLinkUID() && !(isGroup = false)) ||
2936  (columnsInfo_[col].isChildLinkGroupID() && (isGroup = true))) &&
2937  index == columnsInfo_[col].getChildLinkIndex())
2938  {
2939  // found match!
2940  //__COUT__ << "getChildLink Found match for col: " << (int)c << " at " << col
2941  //<< __E__;
2942  linkPair.second = col;
2943  return true;
2944  }
2945  }
2946 
2947  // if here then invalid table!
2948  __SS__ << "\tIn view: " << tableName_
2949  << ", Can't find complete child link id for column name "
2950  << columnsInfo_[c].getName() << __E__;
2951  __SS_THROW__;
2952 } // end getChildLink()