otsdaq  3.06.00
StringMacros.cc
1 #include "otsdaq/Macros/StringMacros.h"
2 
3 #include <algorithm> // for find_if
4 #include <array>
5 #include <cstdint> // for uintptr_t
6 
7 using namespace ots;
8 
9 std::map<std::string /* system variable */,
10  std::map<std::string /* property */, std::string /* value */>>
11  StringMacros::systemVariables_;
12 const std::string StringMacros::TBD = "To-be-defined";
13 
14 #define TLVL_EscapeString 30 // = TLVL_DEBUG + 30
15 #define TLVL_EnvMath 49 // = TLVL_DEBUG + 49
16 #define TLVL_EnvSub 50 // = TLVL_DEBUG + 50
17 
18 //==============================================================================
30 bool StringMacros::wildCardMatch(const std::string& needle,
31  const std::string& haystack,
32  unsigned int* priorityIndex)
33 try
34 {
35  __COUTT__ << "\t\t wildCardMatch: " << needle << " =in= " << haystack << " ??? "
36  << std::endl;
37 
38  // empty needle
39  if(needle.size() == 0)
40  {
41  if(priorityIndex)
42  *priorityIndex = 1; // consider an exact match, to stop higher level loops
43  return true; // if empty needle, always "found"
44  }
45 
46  // only wildcard
47  if(needle == "*")
48  {
49  if(priorityIndex)
50  *priorityIndex = 5; // only wildcard, is lowest priority
51  return true; // if empty needle, always "found"
52  }
53 
54  // no wildcards
55  if(needle == haystack)
56  {
57  if(priorityIndex)
58  *priorityIndex = 1; // an exact match
59  return true;
60  }
61 
62  const bool hasWildcard = (needle.find('*') != std::string::npos);
63  if(!hasWildcard)
64  {
65  if(priorityIndex)
66  *priorityIndex = 0; // no wildcard and not exact => no match
67  return false;
68  }
69 
70  // trailing wildcard
71  if(needle[needle.size() - 1] == '*' &&
72  needle.substr(0, needle.size() - 1) == haystack.substr(0, needle.size() - 1))
73  {
74  if(priorityIndex)
75  *priorityIndex = 2; // trailing wildcard match
76  return true;
77  }
78 
79  // leading wildcard
80  if(needle[0] == '*' &&
81  needle.substr(1) == haystack.substr(haystack.size() - (needle.size() - 1)))
82  {
83  if(priorityIndex)
84  *priorityIndex = 3; // leading wildcard match
85  return true;
86  }
87 
88  // generic wildcard matching with '*' anywhere in needle
89  // '*' matches any sequence (including empty)
90  std::size_t patternPos = 0;
91  std::size_t textPos = 0;
92  std::size_t lastStarPattern = std::string::npos;
93  std::size_t lastStarTextPos = std::string::npos;
94  while(textPos < haystack.size())
95  {
96  if(patternPos < needle.size() && needle[patternPos] == haystack[textPos])
97  {
98  ++patternPos;
99  ++textPos;
100  }
101  else if(patternPos < needle.size() && needle[patternPos] == '*')
102  {
103  lastStarPattern = patternPos++;
104  lastStarTextPos = textPos;
105  }
106  else if(lastStarPattern != std::string::npos)
107  {
108  patternPos = lastStarPattern + 1;
109  textPos = ++lastStarTextPos;
110  }
111  else
112  {
113  if(priorityIndex)
114  *priorityIndex = 0; // no match
115  return false;
116  }
117  }
118 
119  while(patternPos < needle.size() && needle[patternPos] == '*')
120  ++patternPos;
121 
122  if(patternPos == needle.size())
123  {
124  if(priorityIndex)
125  *priorityIndex = 4; // wildcard match
126  return true;
127  }
128 
129  // else no match
130  if(priorityIndex)
131  *priorityIndex = 0; // no match
132  return false;
133 } //end wildCardMatch()
134 catch(...)
135 {
136  if(priorityIndex)
137  *priorityIndex = 0; // no match
138  return false; // if out of range
139 } //end wildCardMatch() catch
140 
141 //==============================================================================
145 bool StringMacros::inWildCardSet(const std::string& needle,
146  const std::set<std::string>& haystack)
147 {
148  for(const auto& haystackString : haystack)
149  {
150  // use wildcard match, flip needle parameter.. because we want haystack to have the wildcards
151  if(haystackString.size() && haystackString[0] == '!')
152  {
153  //treat as inverted
154  if(!StringMacros::wildCardMatch(haystackString.substr(1), needle))
155  return true;
156  }
157  else if(StringMacros::wildCardMatch(haystackString, needle))
158  return true;
159  }
160  return false;
161 }
162 
163 //==============================================================================
166 std::string StringMacros::decodeURIComponent(const std::string& data)
167 {
168  std::string decodeURIString(data.size(), 0); // init to same size
169  unsigned int j = 0;
170  for(unsigned int i = 0; i < data.size(); ++i, ++j)
171  {
172  if(data[i] == '%')
173  {
174  // high order hex nibble digit
175  if(data[i + 1] > '9') // then ABCDEF
176  decodeURIString[j] += (data[i + 1] - 55) * 16;
177  else
178  decodeURIString[j] += (data[i + 1] - 48) * 16;
179 
180  // low order hex nibble digit
181  if(data[i + 2] > '9') // then ABCDEF
182  decodeURIString[j] += (data[i + 2] - 55);
183  else
184  decodeURIString[j] += (data[i + 2] - 48);
185 
186  i += 2; // skip to next char
187  }
188  else
189  decodeURIString[j] = data[i];
190  }
191  decodeURIString.resize(j);
192  return decodeURIString;
193 } // end decodeURIComponent()
194 
195 //==============================================================================
196 std::string StringMacros::encodeURIComponent(const std::string& sourceStr)
197 {
198  std::string retStr = "";
199  char encodeStr[4];
200  for(const auto& c : sourceStr)
201  if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
202  retStr += c;
203  else
204  {
205  sprintf(encodeStr, "%%%2.2X", (uint8_t)c);
206  retStr += encodeStr;
207  }
208  return retStr;
209 } // end encodeURIComponent()
210 
211 //==============================================================================
213 void StringMacros::sanitizeForSQL(std::string& str)
214 {
215  std::map<char, std::string> replacements = {
216  {'\'', "''"}, // Single quote becomes two single quotes
217  {'\\', "\\\\"} //, // Backslash becomes double backslash
218  // {';', "\\;"}, // Semicolon can be escaped (optional)
219  // {'-', "\\-"}, // Dash for comments (optional, context-specific)
220  };
221 
222  size_t pos = 0;
223  while(pos < str.size())
224  {
225  auto it = replacements.find(str[pos]);
226  if(it != replacements.end())
227  {
228  str.replace(pos, 1, it->second);
229  pos += it->second.size(); // Advance past the replacement
230  }
231  else
232  {
233  ++pos;
234  }
235  }
236 } //end sanitizeForSQL
237 
238 //==============================================================================
249 std::string StringMacros::escapeString(std::string inString,
250  bool allowWhiteSpace /* = false */,
251  bool forHtml /* = false */)
252 {
253  unsigned int ws = -1;
254  char htmlTmp[10];
255 
256  __COUTVS__(TLVL_EscapeString, allowWhiteSpace);
257  __COUTVS__(TLVL_EscapeString, forHtml);
258 
259  for(unsigned int i = 0; i < inString.length(); i++)
260  if(inString[i] != ' ')
261  {
262  __COUTS__(TLVL_EscapeString)
263  << i << ". " << inString[i] << ":" << (int)inString[i] << std::endl;
264 
265  // remove new lines and unprintable characters
266  if(inString[i] == '\r' || inString[i] == '\n' || // remove new line chars
267  inString[i] == '\t' || // remove tabs
268  inString[i] < 32 || // remove un-printable characters (they mess up xml
269  // interpretation)
270  (inString[i] > char(126) &&
271  inString[i] < char(161))) // this is aggravated by the bug in
272  // MFextensions (though Eric says he fixed on
273  // 8/24/2016) Note: greater than 255 should be
274  // impossible if by byte (but there are html
275  // chracters in 300s and 8000s)
276  {
277  //handle UTF-8 encoded characters
278  if(i + 2 < inString.size() && inString[i] == char(0xE2) &&
279  inString[i + 1] == char(0x80) &&
280  inString[i + 2] ==
281  char(0x93)) // longer dash endash is 3-bytes 0xE2 0x80 0x93
282  {
283  //encode "--" as &#8211;
284  inString.insert(i,
285  "&#82"); // insert HTML name before special character
286  inString.replace(
287  i + 4, 1, 1, '1'); // replace special character-0 with s
288  inString.replace(
289  i + 5, 1, 1, '1'); // replace special character-1 with h
290  inString.replace(
291  i + 6, 1, 1, ';'); // replace special character-2 with ;
292  i += 7; // skip to next char to check
293  ws = i; // last non white space char
294  --i;
295  continue;
296  }
297 
298  if(inString[i] == '\n') // maintain new lines and tabs
299  {
300  if(allowWhiteSpace)
301  {
302  sprintf(htmlTmp, "&#%3.3d", inString[i]);
303  inString.insert(
304  i, std::string(htmlTmp)); // insert html str sequence
305  inString.replace(
306  i + 5, 1, 1, ';'); // replace special character with ;
307  i += 6; // skip to next char to check
308  --i;
309  }
310  else // translate to ' '
311  inString[i] = ' ';
312  }
313  else if(inString[i] == '\t') // maintain new lines and tabs
314  {
315  if(allowWhiteSpace)
316  {
317  if(0)
318  {
319  // tab = 8 spaces
320  sprintf(htmlTmp,
321  "&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160");
322  inString.insert(
323  i, std::string(htmlTmp)); // insert html str sequence
324  inString.replace(
325  i + 47, 1, 1, ';'); // replace special character with ;
326  i += 48; // skip to next char to check
327  --i;
328  }
329  else // tab = 0x09
330  {
331  sprintf(htmlTmp, "&#009");
332  inString.insert(
333  i, std::string(htmlTmp)); // insert html str sequence
334  inString.replace(
335  i + 5, 1, 1, ';'); // replace special character with ;
336  i += 6; // skip to next char to check
337  --i;
338  }
339  }
340  else // translate to ' '
341  inString[i] = ' ';
342  }
343  else
344  {
345  inString.erase(i, 1); // erase character
346  --i; // step back so next char to check is correct
347  }
348  __COUTS__(31) << inString << std::endl;
349  continue;
350  }
351 
352  __COUTS__(31) << inString << std::endl;
353 
354  // replace special characters
355  if(inString[i] == '\"' || inString[i] == '\'')
356  {
357  inString.insert(i,
358  (inString[i] == '\'')
359  ? "&apos"
360  : "&quot"); // insert HTML name before quotes
361  inString.replace(i + 5, 1, 1, ';'); // replace special character with ;
362  i += 5; // skip to next char to check
363  //__COUT__ << inString << std::endl;
364  }
365  else if(inString[i] == '&')
366  {
367  inString.insert(i, "&amp"); // insert HTML name before special character
368  inString.replace(i + 4, 1, 1, ';'); // replace special character with ;
369  i += 4; // skip to next char to check
370  }
371  else if(inString[i] == '<' || inString[i] == '>')
372  {
373  if(!forHtml)
374  {
375  inString.insert(
376  i,
377  (inString[i] == '<')
378  ? "&lt"
379  : "&gt"); // insert HTML name before special character
380  inString.replace(
381  i + 3, 1, 1, ';'); // replace special character with ;
382  i += 3; // skip to next char to check
383  }
384  else //double escape
385  {
386  inString.insert(
387  i,
388  (inString[i] == '<')
389  ? "&amp;lt"
390  : "&amp;gt"); // insert HTML name before special character
391  inString.replace(
392  i + 7, 1, 1, ';'); // replace special character with ;
393  i += 7; // skip to next char to check
394  }
395  }
396  else if(inString[i] >= char(161) &&
397  inString[i] <= char(255)) // printable special characters
398  {
399  sprintf(htmlTmp, "&#%3.3d", inString[i]);
400  inString.insert(i, std::string(htmlTmp)); // insert html number sequence
401  inString.replace(i + 5, 1, 1, ';'); // replace special character with ;
402  i += 5; // skip to next char to check
403  }
404 
405  __COUTS__(TLVL_EscapeString) << inString << std::endl;
406 
407  ws = i; // last non white space char
408  }
409  else if(allowWhiteSpace) // keep white space if allowed
410  {
411  if(i - 1 == ws)
412  continue; // dont do anything for first white space
413 
414  // for second white space add 2, and 1 from then
415  if(0 && i - 2 == ws)
416  {
417  inString.insert(i, "&#160;"); // insert html space
418  i += 6; // skip to point at space again
419  }
420  inString.insert(i, "&#160"); // insert html space
421  inString.replace(i + 5, 1, 1, ';'); // replace special character with ;
422  i += 5; // skip to next char to check
423  // ws = i;
424  }
425 
426  __COUTS__(TLVL_EscapeString) << inString.size() << " " << ws << std::endl;
427 
428  // inString.substr(0,ws+1);
429 
430  __COUTS__(TLVL_EscapeString) << inString.size() << " " << inString << std::endl;
431 
432  if(allowWhiteSpace) // keep all white space
433  return inString;
434  // else trim trailing white space
435 
436  if(ws == (unsigned int)-1)
437  return ""; // empty std::string since all white space
438  return inString.substr(0, ws + 1); // trim right white space
439 } // end escapeString()
440 
441 //==============================================================================
446 std::string StringMacros::escapeJSONStringEntities(const std::string& str)
447 {
448  unsigned int sz = str.size();
449  if(!sz)
450  return ""; // empty string, returns empty string
451 
452  std::string retStr = "";
453  retStr.reserve(str.size() * 2); // reserve roughly right size
454  for(unsigned int i = 0; i < sz; ++i)
455  {
456  switch(str[i])
457  {
458  case '\n':
459  retStr += "\\n";
460  break;
461  case '"':
462  retStr += "\\\"";
463  break;
464  case '\t':
465  retStr += "\\t";
466  break;
467  case '\r':
468  retStr += "\\r";
469  break;
470  case '\\':
471  retStr += "\\\\";
472  break;
473  default:
474  retStr += str[i];
475  }
476  }
477  return retStr;
478 } //end escapeJSONStringEntities
479 
480 //==============================================================================
484 std::string StringMacros::restoreJSONStringEntities(const std::string& str)
485 {
486  unsigned int sz = str.size();
487  if(!sz)
488  return ""; // empty string, returns empty string
489 
490  std::string retStr = "";
491  retStr.reserve(str.size()); // reserve roughly right size
492  unsigned int i = 0;
493  for(; i < sz - 1; ++i)
494  {
495  if(str[i] == '\\') // if 2 char escape sequence, replace with char
496  switch(str[i + 1])
497  {
498  case 'n':
499  retStr += '\n';
500  ++i;
501  break;
502  case '"':
503  retStr += '"';
504  ++i;
505  break;
506  case 't':
507  retStr += '\t';
508  ++i;
509  break;
510  case 'r':
511  retStr += '\r';
512  ++i;
513  break;
514  case '\\':
515  retStr += '\\';
516  ++i;
517  break;
518  default:
519  retStr += str[i];
520  }
521  else
522  retStr += str[i];
523  }
524  if(i == sz - 1)
525  retStr += str[sz - 1]; // output last character (which can't escape anything)
526 
527  return retStr;
528 } // end restoreJSONStringEntities()
529 
530 //==============================================================================
533 const std::string& StringMacros::trim(std::string& s)
534 {
535  // remove leading whitespace
536  s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
537  return !std::isspace(ch);
538  }));
539 
540  // remove trailing whitespace
541  s.erase(std::find_if(
542  s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); })
543  .base(),
544  s.end());
545 
546  return s;
547 } // end trim()
548 
549 //==============================================================================
559 std::string StringMacros::convertEnvironmentVariables(const std::string& data)
560 {
561  size_t begin = data.find("$");
562  if(begin != std::string::npos && begin + 1 < data.size())
563  {
564  size_t end;
565  std::string envVariable;
566  std::string converted = data; // make copy to modify
567  bool usedBraces = false; // track if braces were used
568 
569  while(begin && begin != std::string::npos &&
570  converted[begin - 1] ==
571  '\\') //do not convert environment variables with escaped \$
572  {
573  converted.replace(begin - 1, 1, "");
574  begin = converted.find("$", begin + 1); //find next
575  if(begin == std::string::npos)
576  {
577  __COUTS__(TLVL_EnvSub)
578  << "Only found escaped $'s that will not be converted: " << converted
579  << __E__;
580  return converted;
581  }
582  }
583 
584  // check if using $(( )) arithmetic expansion syntax
585  // First expand any $-based variables (including OTS system variables),
586  // then evaluate the arithmetic expression using getNumber() (requires explicit $ for variables, e.g., $A-$B)
587  if(begin + 2 < data.size() && data[begin + 1] == '(' && data[begin + 2] == '(')
588  {
589  end = data.find("))", begin + 3);
590  if(end == std::string::npos)
591  {
592  __SS__ << "Arithmetic expansion '$((...)),' at pos " << begin
593  << " in value, is missing closing '))'! Here was the value: "
594  << data << __E__;
595  __SS_THROW__;
596  }
597 
598  std::string expression = data.substr(begin + 3, end - begin - 3);
599  __COUTVS__(TLVL_EnvMath, expression);
600 
601  // Expand $VAR and ${OTS.*.*} inside the expression
602  expression = convertEnvironmentVariables(expression);
603  __COUTVS__(TLVL_EnvMath, expression);
604 
605  int64_t result;
606 
607  bool isNumber = getNumber(expression, result);
608  if(!isNumber)
609  {
610  __SS__ << "Arithmetic expansion '$((...)),' at pos " << begin
611  << " in value, does not evaluate to a number! Here was the value: "
612  << data << __E__;
613  __SS_THROW__;
614  }
615 
616  __COUTS__(TLVL_EnvMath) << "Arithmetic result: " << result << __E__;
617 
618  // proceed recursively, replacing $((...)) with the result
620  converted.replace(begin, end - begin + 2, std::to_string(result)));
621  }
622  else if(data[begin + 1] == '{') // check if using ${NAME} syntax
623  {
624  end = data.find("}", begin + 2);
625  envVariable = data.substr(begin + 2, end - begin - 2);
626  ++end; // replace the closing } too!
627  usedBraces = true;
628  }
629  else // else using $NAME syntax
630  {
631  // end is first non environment variable character
632  for(end = begin + 1; end < data.size(); ++end)
633  if(!((data[end] >= '0' && data[end] <= '9') ||
634  (data[end] >= 'A' && data[end] <= 'Z') ||
635  (data[end] >= 'a' && data[end] <= 'z') || data[end] == '-' ||
636  data[end] == '_' || data[end] == '.' || data[end] == ':'))
637  break; // found end
638  envVariable = data.substr(begin + 1, end - begin - 1);
639  usedBraces = false;
640  }
641  __COUTVS__(TLVL_EnvSub, data);
642  __COUTVS__(TLVL_EnvSub, envVariable);
643  if(usedBraces && envVariable.starts_with("OTS."))
644  {
645  __COUTS__(TLVL_EnvSub) << "OTS system variable detected!" << __E__;
646  auto sysVarSplit = StringMacros::getVectorFromString(envVariable, {'.'});
647  __COUTVS__(TLVL_EnvSub, StringMacros::vectorToString(sysVarSplit));
648 
649  if(sysVarSplit.size() != 3 ||
650  systemVariables_.find(sysVarSplit[1]) == systemVariables_.end() ||
651  systemVariables_.at(sysVarSplit[1]).find(sysVarSplit[2]) ==
652  systemVariables_.at(sysVarSplit[1]).end())
653  {
654  __SS__
655  << "System variable ${" << envVariable
656  << "} is not valid or was not found!"
657  << "\n\n"
658  << "If you were trying to access an ots System Variable, the correct "
659  "syntax is "
660  << "${OTS.<variable>.<property>}, e.g. "
661  "${OTS.ActiveStateMachine.name}"
662  << "\n\n"
663  << "If you were trying to insert an arithmetic operation, the "
664  "correct "
665  "syntax is $((4 - 3)) or $(($ENVVAR1 - $ENVVAR2))"
666  << "\n\n"
667  << "Available system variables:" << __E__;
668 
669  // Print all available system variables
670  for(const auto& varPair : systemVariables_)
671  {
672  ss << "\n OTS." << varPair.first << ".*";
673  for(const auto& propPair : varPair.second)
674  ss << "\n - OTS." << varPair.first << "." << propPair.first;
675  }
676  ss << __E__;
677  __SS_THROW__;
678  }
679  //else successful
680  // proceed recursively
681  return convertEnvironmentVariables(converted.replace(
682  begin,
683  end - begin,
684  systemVariables_.at(sysVarSplit[1]).at(sysVarSplit[2])));
685  }
686  else
687  {
688  char* envResult = nullptr;
689  try
690  {
691  envResult = __ENV__(envVariable.c_str());
692  }
693  catch(const std::runtime_error& e)
694  {
695  __SS__
696  << ("The environmental variable '" + envVariable +
697  "' is not set! Please make sure you set it before continuing!" +
698  "\n\n" +
699  "If you were trying to access an ots System Variable, the "
700  "correct syntax is " +
701  "${OTS.<variable>.<property>}, e.g. "
702  "${OTS.ActiveStateMachine.name}")
703  << __E__;
704  ss << "\n" << e.what() << __E__;
705  __SS_ONLY_THROW__;
706  }
707 
708  // proceed recursively
710  converted.replace(begin, end - begin, envResult));
711  }
712  }
713  // else no environment variables found in string
714  __COUTS__(TLVL_EnvSub) << "Result: " << data << __E__;
715  return data;
716 } //end convertEnvironmentVariables()
717 
718 //==============================================================================
723 bool StringMacros::isNumber(const std::string& s)
724 {
725  // extract set of potential numbers and operators
726  std::vector<std::string> numbers;
727  std::vector<char> ops;
728 
729  if(!s.size())
730  return false;
731 
733  s,
734  numbers,
735  /*delimiter*/ std::set<char>({'+', '-', '*', '/'}),
736  /*whitespace*/ std::set<char>({' ', '\t', '\n', '\r'}),
737  &ops);
738 
739  //__COUTV__(StringMacros::vectorToString(numbers));
740  //__COUTV__(StringMacros::vectorToString(ops));
741 
742  for(const auto& number : numbers)
743  {
744  if(number.size() == 0)
745  continue; // skip empty numbers
746 
747  if(number.find("0x") == 0) // indicates hex
748  {
749  //__COUT__ << "0x found" << std::endl;
750  for(unsigned int i = 2; i < number.size(); ++i)
751  {
752  if(!((number[i] >= '0' && number[i] <= '9') ||
753  (number[i] >= 'A' && number[i] <= 'F') ||
754  (number[i] >= 'a' && number[i] <= 'f')))
755  {
756  //__COUT__ << "prob " << number[i] << std::endl;
757  return false;
758  }
759  }
760  // return std::regex_match(number.substr(2), std::regex("^[0-90-9a-fA-F]+"));
761  }
762  else if(number[0] == 'b') // indicates binary
763  {
764  //__COUT__ << "b found" << std::endl;
765 
766  for(unsigned int i = 1; i < number.size(); ++i)
767  {
768  if(!((number[i] >= '0' && number[i] <= '1')))
769  {
770  //__COUT__ << "prob " << number[i] << std::endl;
771  return false;
772  }
773  }
774  }
775  else
776  {
777  //__COUT__ << "base 10 " << std::endl;
778  for(unsigned int i = 0; i < number.size(); ++i)
779  if(!((number[i] >= '0' && number[i] <= '9') || number[i] == '.' ||
780  number[i] == '+' || number[i] == '-'))
781  return false;
782  // Note: std::regex crashes in unresolvable ways (says Ryan.. also, stop using
783  // libraries) return std::regex_match(s,
784  // std::regex("^(\\-|\\+)?[0-9]*(\\.[0-9]+)?"));
785  }
786  }
787 
788  //__COUT__ << "yes " << std::endl;
789 
790  // all numbers are numbers
791  return true;
792 } // end isNumber()
793 
794 //==============================================================================
800 std::string StringMacros::getNumberType(const std::string& s)
801 {
802  // extract set of potential numbers and operators
803  std::vector<std::string> numbers;
804  std::vector<char> ops;
805 
806  bool hasDecimal = false;
807 
809  s,
810  numbers,
811  /*delimiter*/ std::set<char>({'+', '-', '*', '/'}),
812  /*whitespace*/ std::set<char>({' ', '\t', '\n', '\r'}),
813  &ops);
814 
815  //__COUTV__(StringMacros::vectorToString(numbers));
816  //__COUTV__(StringMacros::vectorToString(ops));
817 
818  for(const auto& number : numbers)
819  {
820  if(number.size() == 0)
821  continue; // skip empty numbers
822 
823  if(number.find("0x") == 0) // indicates hex
824  {
825  //__COUT__ << "0x found" << std::endl;
826  for(unsigned int i = 2; i < number.size(); ++i)
827  {
828  if(!((number[i] >= '0' && number[i] <= '9') ||
829  (number[i] >= 'A' && number[i] <= 'F') ||
830  (number[i] >= 'a' && number[i] <= 'f')))
831  {
832  //__COUT__ << "prob " << number[i] << std::endl;
833  return "nan";
834  }
835  }
836  // return std::regex_match(number.substr(2), std::regex("^[0-90-9a-fA-F]+"));
837  }
838  else if(number[0] == 'b') // indicates binary
839  {
840  //__COUT__ << "b found" << std::endl;
841 
842  for(unsigned int i = 1; i < number.size(); ++i)
843  {
844  if(!((number[i] >= '0' && number[i] <= '1')))
845  {
846  //__COUT__ << "prob " << number[i] << std::endl;
847  return "nan";
848  }
849  }
850  }
851  else
852  {
853  //__COUT__ << "base 10 " << std::endl;
854  for(unsigned int i = 0; i < number.size(); ++i)
855  if(!((number[i] >= '0' && number[i] <= '9') || number[i] == '.' ||
856  number[i] == '+' || number[i] == '-'))
857  return "nan";
858  else if(number[i] == '.')
859  hasDecimal = true;
860  // Note: std::regex crashes in unresolvable ways (says Ryan.. also, stop using
861  // libraries) return std::regex_match(s,
862  // std::regex("^(\\-|\\+)?[0-9]*(\\.[0-9]+)?"));
863  }
864  }
865 
866  //__COUT__ << "yes " << std::endl;
867 
868  // all numbers are numbers
869  if(hasDecimal)
870  return "double";
871  return "unsigned long long";
872 } // end getNumberType()
873 
874 //==============================================================================
875 // static template function
880 bool StringMacros::getNumber(const std::string& s, bool& retValue)
881 {
882  if(s.size() < 1)
883  {
884  __COUT_ERR__ << "Invalid empty bool string " << s << __E__;
885  return false;
886  }
887 
888  // check true case
889  if(s.find("1") != std::string::npos || s == "true" || s == "True" || s == "TRUE")
890  {
891  retValue = true;
892  return true;
893  }
894 
895  // check false case
896  if(s.find("0") != std::string::npos || s == "false" || s == "False" || s == "FALSE")
897  {
898  retValue = false;
899  return true;
900  }
901 
902  __COUT_ERR__ << "Invalid bool string " << s << __E__;
903  return false;
904 
905 } // end static getNumber<bool>
906 
907 //==============================================================================
911 std::string StringMacros::getTimestampString(const std::string& linuxTimeInSeconds)
912 {
913  time_t timestamp(strtol(linuxTimeInSeconds.c_str(), 0, 10));
914  return getTimestampString(timestamp);
915 } // end getTimestampString()
916 
917 //==============================================================================
921 std::string StringMacros::getTimestampString(const time_t linuxTimeInSeconds)
922 {
923  return ots::TimestampString().get(linuxTimeInSeconds);
924 } // end getTimestampString()
925 
926 //==============================================================================
930 {
931  //e.g., used by CoreSupervisorBase::getStatusProgressDetail(void)
932 
933  std::stringstream ss;
934  int days = t / 60 / 60 / 24;
935  if(days > 0)
936  {
937  ss << days << " day" << (days > 1 ? "s" : "") << ", ";
938  t -= days * 60 * 60 * 24;
939  }
940 
941  //HH:MM:SS
942  ss << std::setw(2) << std::setfill('0') << (t / 60 / 60) << ":" << std::setw(2)
943  << std::setfill('0') << ((t % (60 * 60)) / 60) << ":" << std::setw(2)
944  << std::setfill('0') << (t % 60);
945  return ss.str();
946 } //end getTimeDurationString()
947 
948 //==============================================================================
952  const std::string& value, bool doConvertEnvironmentVariables)
953 try
954 {
955  return doConvertEnvironmentVariables
957  : value;
958 }
959 catch(const std::runtime_error& e)
960 {
961  __SS__ << "Failed to validate value for default string data type. " << __E__
962  << e.what() << __E__;
963  __SS_THROW__;
964 }
965 
966 //==============================================================================
970 void StringMacros::getSetFromString(const std::string& inputString,
971  std::set<std::string>& setToReturn,
972  const std::set<char>& delimiter,
973  const std::set<char>& whitespace)
974 {
975  unsigned int i = 0;
976  unsigned int j = 0;
977 
978  // go through the full string extracting elements
979  // add each found element to set
980  for(; j < inputString.size(); ++j)
981  if((whitespace.find(inputString[j]) !=
982  whitespace.end() || // ignore leading white space or delimiter
983  delimiter.find(inputString[j]) != delimiter.end()) &&
984  i == j)
985  ++i;
986  else if((whitespace.find(inputString[j]) !=
987  whitespace
988  .end() || // trailing white space or delimiter indicates end
989  delimiter.find(inputString[j]) != delimiter.end()) &&
990  i != j) // assume end of element
991  {
992  //__COUT__ << "Set element found: " <<
993  // inputString.substr(i,j-i) << std::endl;
994 
995  setToReturn.emplace(inputString.substr(i, j - i));
996 
997  // setup i and j for next find
998  i = j + 1;
999  }
1000 
1001  if(i != j) // last element check (for case when no concluding ' ' or delimiter)
1002  setToReturn.emplace(inputString.substr(i, j - i));
1003 } // end getSetFromString()
1004 
1005 //==============================================================================
1017 void StringMacros::getVectorFromString(const std::string& inputString,
1018  std::vector<std::string>& listToReturn,
1019  const std::set<char>& delimiter,
1020  const std::set<char>& whitespace,
1021  std::vector<char>* listOfDelimiters,
1022  bool decodeURIComponents)
1023 {
1024  unsigned int i = 0;
1025  unsigned int j = 0;
1026  unsigned int c = 0;
1027  std::set<char>::iterator delimeterSearchIt;
1028  char lastDelimiter = 0;
1029  bool isDelimiter;
1030  // bool foundLeadingDelimiter = false;
1031 
1032  //__COUT__ << inputString << __E__;
1033  //__COUTV__(inputString.length());
1034 
1035  // go through the full string extracting elements
1036  // add each found element to set
1037  for(; c < inputString.size(); ++c)
1038  {
1039  //__COUT__ << (char)inputString[c] << __E__;
1040 
1041  delimeterSearchIt = delimiter.find(inputString[c]);
1042  isDelimiter = delimeterSearchIt != delimiter.end();
1043 
1044  //__COUT__ << (char)inputString[c] << " " << isDelimiter <<
1045  //__E__;//char)lastDelimiter << __E__;
1046 
1047  if(whitespace.find(inputString[c]) !=
1048  whitespace.end() // ignore leading white space
1049  && i == j)
1050  {
1051  ++i;
1052  ++j;
1053  // if(isDelimiter)
1054  // foundLeadingDelimiter = true;
1055  }
1056  else if(whitespace.find(inputString[c]) != whitespace.end() &&
1057  i != j) // trailing white space, assume possible end of element
1058  {
1059  // do not change j or i
1060  }
1061  else if(isDelimiter) // delimiter is end of element
1062  {
1063  //__COUT__ << "Set element found: " <<
1064  // inputString.substr(i,j-i) << std::endl;
1065 
1066  if(listOfDelimiters && listToReturn.size()) // || foundLeadingDelimiter))
1067  // //accept leading delimiter
1068  // (especially for case of
1069  // leading negative in math
1070  // parsing)
1071  {
1072  //__COUTV__(lastDelimiter);
1073  listOfDelimiters->push_back(lastDelimiter);
1074  }
1075  listToReturn.push_back(decodeURIComponents ? StringMacros::decodeURIComponent(
1076  inputString.substr(i, j - i))
1077  : inputString.substr(i, j - i));
1078 
1079  // setup i and j for next find
1080  i = c + 1;
1081  j = c + 1;
1082  }
1083  else // part of element, so move j, not i
1084  j = c + 1;
1085 
1086  if(isDelimiter)
1087  lastDelimiter = *delimeterSearchIt;
1088  //__COUTV__(lastDelimiter);
1089  }
1090 
1091  if(1) // i != j) //last element check (for case when no concluding ' ' or delimiter)
1092  {
1093  //__COUT__ << "Last element found: " <<
1094  // inputString.substr(i,j-i) << std::endl;
1095 
1096  if(listOfDelimiters && listToReturn.size()) // || foundLeadingDelimiter))
1097  // //accept leading delimiter
1098  // (especially for case of leading
1099  // negative in math parsing)
1100  {
1101  //__COUTV__(lastDelimiter);
1102  listOfDelimiters->push_back(lastDelimiter);
1103  }
1104  listToReturn.push_back(decodeURIComponents ? StringMacros::decodeURIComponent(
1105  inputString.substr(i, j - i))
1106  : inputString.substr(i, j - i));
1107  }
1108 
1109  // assert that there is one less delimiter than values
1110  if(listOfDelimiters && listToReturn.size() - 1 != listOfDelimiters->size() &&
1111  listToReturn.size() != listOfDelimiters->size())
1112  {
1113  __SS__ << "There is a mismatch in delimiters to entries (should be equal or one "
1114  "less delimiter): "
1115  << listOfDelimiters->size() << " vs " << listToReturn.size() << __E__
1116  << "Entries: " << StringMacros::vectorToString(listToReturn) << __E__
1117  << "Delimiters: " << StringMacros::vectorToString(*listOfDelimiters)
1118  << __E__;
1119  __SS_THROW__;
1120  }
1121 
1122 } // end getVectorFromString()
1123 
1124 //==============================================================================
1136 std::vector<std::string> StringMacros::getVectorFromString(
1137  const std::string& inputString,
1138  const std::set<char>& delimiter,
1139  const std::set<char>& whitespace,
1140  std::vector<char>* listOfDelimiters,
1141  bool decodeURIComponents)
1142 {
1143  std::vector<std::string> listToReturn;
1144 
1146  listToReturn,
1147  delimiter,
1148  whitespace,
1149  listOfDelimiters,
1150  decodeURIComponents);
1151  return listToReturn;
1152 } // end getVectorFromString()
1153 
1154 //==============================================================================
1158 void StringMacros::getMapFromString(const std::string& inputString,
1159  std::map<std::string, std::string>& mapToReturn,
1160  const std::set<char>& pairPairDelimiter,
1161  const std::set<char>& nameValueDelimiter,
1162  const std::set<char>& whitespace)
1163 try
1164 {
1165  unsigned int i = 0;
1166  unsigned int j = 0;
1167  std::string name;
1168  bool needValue = false;
1169 
1170  // go through the full string extracting map pairs
1171  // add each found pair to map
1172  for(; j < inputString.size(); ++j)
1173  if(!needValue) // finding name
1174  {
1175  if((whitespace.find(inputString[j]) !=
1176  whitespace.end() || // ignore leading white space or delimiter
1177  pairPairDelimiter.find(inputString[j]) != pairPairDelimiter.end()) &&
1178  i == j)
1179  ++i;
1180  else if((whitespace.find(inputString[j]) !=
1181  whitespace
1182  .end() || // trailing white space or delimiter indicates end
1183  nameValueDelimiter.find(inputString[j]) !=
1184  nameValueDelimiter.end()) &&
1185  i != j) // assume end of map name
1186  {
1187  //__COUT__ << "Map name found: " <<
1188  // inputString.substr(i,j-i) << std::endl;
1189 
1190  name = inputString.substr(i, j - i); // save name, for concluding pair
1191 
1192  needValue = true; // need value now
1193 
1194  // setup i and j for next find
1195  i = j + 1;
1196  }
1197  }
1198  else // finding value
1199  {
1200  if((whitespace.find(inputString[j]) !=
1201  whitespace.end() || // ignore leading white space or delimiter
1202  nameValueDelimiter.find(inputString[j]) != nameValueDelimiter.end()) &&
1203  i == j)
1204  ++i;
1205  else if(whitespace.find(inputString[j]) !=
1206  whitespace
1207  .end() || // trailing white space or delimiter indicates end
1208  pairPairDelimiter.find(inputString[j]) !=
1209  pairPairDelimiter.end()) // &&
1210  // i != j) // assume end of value name
1211  {
1212  //__COUT__ << "Map value found: " <<
1213  // inputString.substr(i,j-i) << std::endl;
1214 
1215  auto /*pair<it,success>*/ emplaceReturn =
1216  mapToReturn.emplace(std::pair<std::string, std::string>(
1217  name,
1219  inputString.substr(i, j - i)) // value
1220  ));
1221 
1222  if(!emplaceReturn.second)
1223  {
1224  __COUT__ << "Ignoring repetitive value ('"
1225  << inputString.substr(i, j - i)
1226  << "') and keeping current value ('"
1227  << emplaceReturn.first->second << "'). " << __E__;
1228  }
1229 
1230  needValue = false; // need name now
1231 
1232  // setup i and j for next find
1233  i = j + 1;
1234  }
1235  }
1236 
1237  if(i != j) // last value (for case when no concluding ' ' or delimiter)
1238  {
1239  auto /*pair<it,success>*/ emplaceReturn =
1240  mapToReturn.emplace(std::pair<std::string, std::string>(
1241  name,
1243  inputString.substr(i, j - i)) // value
1244  ));
1245 
1246  if(!emplaceReturn.second)
1247  {
1248  __COUT__ << "Ignoring repetitive value ('" << inputString.substr(i, j - i)
1249  << "') and keeping current value ('" << emplaceReturn.first->second
1250  << "'). " << __E__;
1251  }
1252  }
1253 } // end getMapFromString()
1254 catch(const std::runtime_error& e)
1255 {
1256  __SS__ << "Error while extracting a map from the string '" << inputString
1257  << "'... is it a valid map?" << __E__ << e.what() << __E__;
1258  __SS_THROW__;
1259 }
1260 
1261 //==============================================================================
1263 std::string StringMacros::mapToString(const std::map<std::string, uint8_t>& mapToReturn,
1264  const std::string& primaryDelimeter,
1265  const std::string& secondaryDelimeter)
1266 {
1267  std::stringstream ss;
1268  bool first = true;
1269  for(auto& mapPair : mapToReturn)
1270  {
1271  if(first)
1272  first = false;
1273  else
1274  ss << primaryDelimeter;
1275  ss << mapPair.first << secondaryDelimeter << (unsigned int)mapPair.second;
1276  }
1277  return ss.str();
1278 } // end mapToString()
1279 
1280 //==============================================================================
1282 std::string StringMacros::setToString(const std::set<uint8_t>& setToReturn,
1283  const std::string& delimeter)
1284 {
1285  std::stringstream ss;
1286  bool first = true;
1287  for(auto& setValue : setToReturn)
1288  {
1289  if(first)
1290  first = false;
1291  else
1292  ss << delimeter;
1293  ss << (unsigned int)setValue;
1294  }
1295  return ss.str();
1296 } // end setToString()
1297 
1298 //==============================================================================
1300 std::string StringMacros::vectorToString(const std::vector<uint8_t>& setToReturn,
1301  const std::string& delimeter)
1302 {
1303  std::stringstream ss;
1304  bool first = true;
1305  if(delimeter == "\n")
1306  ss << "\n"; //add initial new line if new line delimiting
1307  for(auto& setValue : setToReturn)
1308  {
1309  if(first)
1310  first = false;
1311  else
1312  ss << delimeter;
1313  ss << (unsigned int)setValue;
1314  }
1315  return ss.str();
1316 } // end vectorToString()
1317 
1318 //==============================================================================
1328 bool StringMacros::extractCommonChunks(const std::vector<std::string>& haystack,
1329  std::vector<std::string>& commonChunksToReturn,
1330  std::vector<std::string>& wildcardStringsToReturn,
1331  unsigned int& fixedWildcardLength)
1332 {
1333  fixedWildcardLength = 0; // init to default
1334  __COUTTV__(StringMacros::vectorToString(haystack));
1335 
1336  // Steps:
1337  // - find start and end common chunks first in haystack strings
1338  // - use start and end to determine if there is more than one *
1339  // - decide if fixed width was specified (based on prepended 0s to numbers)
1340  // - search for more instances of * value
1341  //
1342  //
1343  // // Note: lambda recursive function to find chunks
1344  // std::function<void(
1345  // const std::vector<std::string>&,
1346  // const std::string&,
1347  // const unsigned int, const int)> localRecurse =
1348  // [&specialFolders, &specialMapTypes, &retMap, &localRecurse](
1349  // const std::vector<std::string>& haystack,
1350  // const std::string& offsetPath,
1351  // const unsigned int depth,
1352  // const int specialIndex)
1353  // {
1354  //
1355  // //__COUTV__(path);
1356  // //__COUTV__(depth);
1357  // }
1358  std::pair<unsigned int /*lo*/, unsigned int /*hi*/> wildcardBounds(
1359  std::make_pair(-1, 0)); // initialize to illegal wildcard
1360 
1361  // look for starting matching segment
1362  for(unsigned int n = 1; n < haystack.size(); ++n)
1363  for(unsigned int i = 0, j = 0;
1364  i < haystack[0].length() && j < haystack[n].length();
1365  ++i, ++j)
1366  {
1367  if(i < wildcardBounds.first)
1368  {
1369  if(haystack[0][i] != haystack[n][j])
1370  {
1371  wildcardBounds.first = i; // found lo side of wildcard
1372  break;
1373  }
1374  }
1375  else
1376  break;
1377  }
1378  __COUTS__(3) << "Low side = " << wildcardBounds.first << " "
1379  << haystack[0].substr(0, wildcardBounds.first) << __E__;
1380 
1381  // look for end matching segment
1382  for(unsigned int n = 1; n < haystack.size(); ++n)
1383  for(int i = haystack[0].length() - 1, j = haystack[n].length() - 1;
1384  i >= (int)wildcardBounds.first && j >= (int)wildcardBounds.first;
1385  --i, --j)
1386  {
1387  if(i > (int)wildcardBounds.second) // looking for hi side
1388  {
1389  if(haystack[0][i] != haystack[n][j])
1390  {
1391  wildcardBounds.second = i + 1; // found hi side of wildcard
1392  break;
1393  }
1394  }
1395  else
1396  break;
1397  }
1398 
1399  __COUTS__(3) << "High side = " << wildcardBounds.second << " "
1400  << haystack[0].substr(wildcardBounds.second) << __E__;
1401 
1402  // add first common chunk
1403  commonChunksToReturn.push_back(haystack[0].substr(0, wildcardBounds.first));
1404 
1405  if(wildcardBounds.first != (unsigned int)-1) // potentially more chunks if not end
1406  {
1407  // - use start and end to determine if there is more than one *
1408  for(int i = (wildcardBounds.first + wildcardBounds.second) / 2 + 1;
1409  i < (int)wildcardBounds.second;
1410  ++i)
1411  if(haystack[0][wildcardBounds.first] == haystack[0][i] &&
1412  haystack[0].substr(wildcardBounds.first, wildcardBounds.second - i) ==
1413  haystack[0].substr(i, wildcardBounds.second - i))
1414  {
1415  std::string multiWildcardString =
1416  haystack[0].substr(i, wildcardBounds.second - i);
1417  __COUT__ << "Potential multi-wildcard found: " << multiWildcardString
1418  << " at position i=" << i << __E__;
1419 
1420  std::vector<unsigned int /*lo index*/> wildCardInstances;
1421  // add front one now, and back one later
1422  wildCardInstances.push_back(wildcardBounds.first);
1423 
1424  unsigned int offset =
1425  wildCardInstances[0] + multiWildcardString.size() + 1;
1426  std::string middleString = haystack[0].substr(offset, (i - 1) - offset);
1427  __COUTV__(middleString);
1428 
1429  // search for more wildcard instances in new common area
1430  size_t k;
1431  while((k = middleString.find(multiWildcardString)) != std::string::npos)
1432  {
1433  __COUT__ << "Multi-wildcard found at " << k << __E__;
1434 
1435  wildCardInstances.push_back(offset + k);
1436 
1437  middleString =
1438  middleString.substr(k + multiWildcardString.size() + 1);
1439  offset += k + multiWildcardString.size() + 1;
1440  __COUTV__(middleString);
1441  }
1442 
1443  // add back one last
1444  wildCardInstances.push_back(i);
1445 
1446  for(unsigned int w = 0; w < wildCardInstances.size() - 1; ++w)
1447  {
1448  __COUTV__(wildCardInstances[w]);
1449  __COUTV__(wildCardInstances[w + 1]);
1450  __COUTV__(wildCardInstances.size());
1451  commonChunksToReturn.push_back(haystack[0].substr(
1452  wildCardInstances[w] + multiWildcardString.size(),
1453  wildCardInstances[w + 1] -
1454  (wildCardInstances[w] + multiWildcardString.size())));
1455  }
1456  }
1457 
1458  __COUTTV__(StringMacros::vectorToString(commonChunksToReturn));
1459  //confirm valid multi-commonChunksToReturn for all haystack entries (only first can be certain at this point)
1460  for(unsigned int c = 1; c < commonChunksToReturn.size(); ++c)
1461  {
1462  __COUT__ << "Checking [" << c << "]: " << commonChunksToReturn[c] << __E__;
1463  for(unsigned int n = 1; n < haystack.size(); ++n)
1464  {
1465  __COUT__ << "Checking chunks work with haystack [" << n
1466  << "]: " << haystack[n] << __E__;
1467  __COUTV__(commonChunksToReturn[0].size());
1468  std::string wildCardValue = haystack[n].substr(
1469  commonChunksToReturn[0].size(),
1470  haystack[n].find(commonChunksToReturn[1],
1471  commonChunksToReturn[0].size() + 1) -
1472  commonChunksToReturn[0].size());
1473  __COUTTV__(wildCardValue);
1474 
1475  std::string builtString = "";
1476  for(unsigned int cc = 0; cc < commonChunksToReturn.size(); ++cc)
1477  builtString += commonChunksToReturn[cc] + wildCardValue;
1478  __COUTTV__(builtString);
1479  __COUTTV__(wildCardValue);
1480 
1481  if(haystack[n].find(builtString) != 0)
1482  {
1483  __COUT__ << "Dropping common chunk " << commonChunksToReturn[c]
1484  << ", built '" << builtString << "' not found in "
1485  << haystack[n] << __E__;
1486  commonChunksToReturn.erase(commonChunksToReturn.begin() + c);
1487  --c; //rewind
1488  break; //check next chunk
1489  }
1490  else
1491  __COUTT__ << "Found built '" << builtString << "' in " << haystack[n]
1492  << __E__;
1493  } //end haystack loop
1494  } //end common chunk loop
1495 
1496  __COUTTV__(StringMacros::vectorToString(commonChunksToReturn));
1497  __COUTTV__(commonChunksToReturn[0].size());
1498 
1499  __COUTTV__(fixedWildcardLength);
1500  // check if all common chunks END in 0 to add fixed length
1501  for(unsigned int i = 0; i < commonChunksToReturn[0].size(); ++i)
1502  if(commonChunksToReturn[0][commonChunksToReturn[0].size() - 1 - i] == '0')
1503  {
1504  ++fixedWildcardLength;
1505  __COUTT__ << "Trying for added fixed length +1 to " << fixedWildcardLength
1506  << __E__;
1507  }
1508  else
1509  break;
1510 
1511  // bool allHave0 = true;
1512  for(unsigned int c = 0; c < commonChunksToReturn.size(); ++c)
1513  {
1514  unsigned int cnt = 0;
1515  for(unsigned int i = 0; i < commonChunksToReturn[c].size(); ++i)
1516  if(commonChunksToReturn[c][commonChunksToReturn[c].size() - 1 - i] == '0')
1517  ++cnt;
1518  else
1519  break;
1520 
1521  if(fixedWildcardLength < cnt)
1522  fixedWildcardLength = cnt;
1523  else if(fixedWildcardLength > cnt)
1524  {
1525  __SS__ << "Invalid fixed length found, please simplify indexing between "
1526  "these common chunks: "
1527  << StringMacros::vectorToString(commonChunksToReturn) << __E__;
1528  __SS_THROW__;
1529  }
1530  }
1531  __COUTTV__(fixedWildcardLength);
1532 
1533  if(fixedWildcardLength) // take trailing 0s out of common chunks
1534  for(unsigned int c = 0; c < commonChunksToReturn.size(); ++c)
1535  commonChunksToReturn[c] = commonChunksToReturn[c].substr(
1536  0, commonChunksToReturn[c].size() - fixedWildcardLength);
1537 
1538  // add last common chunk
1539  commonChunksToReturn.push_back(haystack[0].substr(wildcardBounds.second));
1540  } // end handling more chunks
1541  __COUTTV__(StringMacros::vectorToString(commonChunksToReturn));
1542 
1543  // now determine wildcard strings
1544  size_t k;
1545  unsigned int i;
1546  unsigned int ioff = fixedWildcardLength;
1547  bool wildcardsNeeded = false;
1548  bool someLeadingZeros = false;
1549  bool allWildcardsSameSize = true;
1550 
1551  for(unsigned int n = 0; n < haystack.size(); ++n)
1552  {
1553  std::string wildcard = "";
1554  k = 0;
1555  i = ioff + commonChunksToReturn[0].size();
1556 
1557  if(commonChunksToReturn.size() == 1) // just get end
1558  wildcard = haystack[n].substr(i);
1559  else
1560  for(unsigned int c = 1; c < commonChunksToReturn.size(); ++c)
1561  {
1562  if(c == commonChunksToReturn.size() - 1) // for last, do reverse find
1563  k = haystack[n].rfind(commonChunksToReturn[c]);
1564  else
1565  k = haystack[n].find(commonChunksToReturn[c], i + 1);
1566 
1567  if(wildcard == "")
1568  {
1569  // set wildcard for first time
1570  __COUTVS__(3, i);
1571  __COUTVS__(3, k);
1572  __COUTVS__(3, k - i);
1573 
1574  wildcard = haystack[n].substr(i, k - i);
1575  if(fixedWildcardLength && n == 0)
1576  fixedWildcardLength += wildcard.size();
1577 
1578  __COUTS__(3) << "name[" << n << "] = " << wildcard << " fixed @ "
1579  << fixedWildcardLength << __E__;
1580 
1581  break;
1582  }
1583  else if(0 /*skip validation in favor of speed*/ &&
1584  wildcard != haystack[n].substr(i, k - i))
1585  {
1586  __SS__ << "Invalid wildcard! for name[" << n << "] = " << haystack[n]
1587  << " - the extraction algorithm is confused, please simplify "
1588  "your naming convention."
1589  << __E__;
1590  __SS_THROW__;
1591  }
1592 
1593  i = k;
1594  } // end commonChunksToReturn loop
1595 
1596  if(wildcard.size())
1597  {
1598  wildcardsNeeded = true;
1599 
1600  //track if need for leading 0s in wildcards
1601  if(wildcard[0] == '0' && !fixedWildcardLength)
1602  {
1603  someLeadingZeros = true;
1604  if(wildcardStringsToReturn.size() &&
1605  wildcard.size() != wildcardStringsToReturn[0].size())
1606  allWildcardsSameSize = false;
1607  }
1608  }
1609  wildcardStringsToReturn.push_back(wildcard);
1610 
1611  } // end name loop
1612 
1613  __COUTTV__(StringMacros::vectorToString(commonChunksToReturn));
1614  __COUTTV__(StringMacros::vectorToString(wildcardStringsToReturn));
1615 
1616  if(someLeadingZeros && allWildcardsSameSize)
1617  {
1618  __COUTTV__(fixedWildcardLength); //should be 0 in this case
1619  fixedWildcardLength = wildcardStringsToReturn[0].size();
1620  __COUT__ << "Enforce wildcard size of " << fixedWildcardLength << __E__;
1621  }
1622 
1623  if(wildcardStringsToReturn.size() != haystack.size())
1624  {
1625  __SS__ << "There was a problem during common chunk extraction!" << __E__;
1626  __SS_THROW__;
1627  }
1628 
1629  return wildcardsNeeded;
1630 
1631 } // end extractCommonChunks()
1632 
1633 //==============================================================================
1638  const std::string& rhs) const
1639 {
1640  //__COUTV__(lhs);
1641  //__COUTV__(rhs);
1642  // return true if lhs < rhs (lhs will be ordered first)
1643 
1644  for(unsigned int i = 0; i < lhs.size() && i < rhs.size(); ++i)
1645  {
1646  //__COUT__ << i << "\t" << lhs[i] << "\t" << rhs[i] << __E__;
1647  if((lhs[i] >= 'A' && lhs[i] <= 'Z' && rhs[i] >= 'A' && rhs[i] <= 'Z') ||
1648  (lhs[i] >= 'a' && lhs[i] <= 'z' && rhs[i] >= 'a' && rhs[i] <= 'z'))
1649  { // same case
1650  if(lhs[i] == rhs[i])
1651  continue;
1652  return (lhs[i] < rhs[i]);
1653  //{ retVal = false; break;} //return false;
1654  }
1655  else if(lhs[i] >= 'A' && lhs[i] <= 'Z') // rhs lower case
1656  {
1657  if(lhs[i] + 32 == rhs[i]) // lower case is higher by 32
1658  return false; // in tie return lower case first
1659  return (lhs[i] + 32 < rhs[i]);
1660  }
1661  else if(rhs[i] >= 'A' && rhs[i] <= 'Z')
1662  {
1663  if(lhs[i] == rhs[i] + 32) // lower case is higher by 32
1664  return true; // in tie return lower case first
1665  return (lhs[i] < rhs[i] + 32);
1666  }
1667  else // not letters case (should only be for numbers)
1668  {
1669  if(lhs[i] == rhs[i])
1670  continue;
1671  return (lhs[i] < rhs[i]);
1672  }
1673  } // end case insensitive compare loop
1674 
1675  // lhs and rhs are equivalent to character[i], so return false if rhs.size() was the limit reached
1676  return lhs.size() < rhs.size();
1677 } // end IgnoreCaseCompareStruct::operator() comparison handler
1678 
1679 //==============================================================================
1682 std::string StringMacros::exec(const char* cmd)
1683 {
1684  __COUTTV__(cmd);
1685 
1686  std::array<char, 128> buffer;
1687  std::string result;
1688 
1689  // For capturing both stdout and stderr, we need to redirect stderr to stdout
1690  // This is done by appending " 2>&1" to the command
1691  std::string cmdWithRedirect = std::string(cmd) + " 2>&1";
1692  std::shared_ptr<FILE> pipe(popen(cmdWithRedirect.c_str(), "r"), pclose);
1693  if(!pipe)
1694  __THROW__("popen() failed!");
1695 
1696  // Read all output (both stdout and stderr)
1697  while(!feof(pipe.get()))
1698  {
1699  if(fgets(buffer.data(), 128, pipe.get()) != nullptr)
1700  result += buffer.data();
1701  }
1702 
1703  __COUTTV__(result);
1704  return result;
1705 } // end exec()
1706 
1707 // #include <iostream>
1708 #include <fstream> /* for ifstream */
1709 // #include <sstream>
1710 // #include <string>
1711 // #include <cstdlib>
1712 //==============================================================================
1713 uintptr_t find_library_base(const std::string& libname)
1714 {
1715  std::ifstream maps("/proc/self/maps");
1716  std::string line;
1717 
1718  while(std::getline(maps, line))
1719  {
1720  if(line.find(libname) != std::string::npos &&
1721  line.find("r-xp") != std::string::npos)
1722  {
1723  uintptr_t base;
1724  std::stringstream ss(line);
1725  ss >> std::hex >> base;
1726  return base;
1727  }
1728  }
1729  return 0;
1730 } //end find_library_base()
1731 
1732 //==============================================================================
1733 void resolve_stack_entry(const std::string& so_path,
1734  const std::string& real_name,
1735  const std::string& offset_begin, // e.g. "+0x249d"
1736  const std::string& offset_end // e.g. "[0x7f5518fa28fd]"
1737 )
1738 {
1739  // Extract runtime address from a string like "[0x....]".
1740  // Be defensive: validate delimiters before parsing to avoid exceptions.
1741  const std::size_t pos0x = offset_end.find("0x");
1742  if(pos0x == std::string::npos)
1743  {
1744  __COUTS__(52) << "resolve_stack_entry: could not find \"0x\" in offset_end: '"
1745  << offset_end << "'" << __E__;
1746  return;
1747  }
1748 
1749  const std::size_t posBracket = offset_end.find(']', pos0x);
1750  if(posBracket == std::string::npos || posBracket < pos0x + 3)
1751  {
1752  __COUTS__(52) << "resolve_stack_entry: could not find closing ']' with at least "
1753  "one hex digit after \"0x\" "
1754  "in offset_end: '"
1755  << offset_end << "'" << __E__;
1756  return;
1757  }
1758 
1759  const std::string addr_str = offset_end.substr(pos0x, posBracket - pos0x);
1760 
1761  uintptr_t runtime_addr = 0;
1762  try
1763  {
1764  runtime_addr = std::stoull(addr_str, nullptr, 16);
1765  }
1766  catch(const std::exception& e)
1767  {
1768  __COUTS__(52) << "resolve_stack_entry: failed to parse runtime address from '"
1769  << addr_str << "': " << e.what() << __E__;
1770  return;
1771  }
1772 
1773  std::string so_name = so_path.substr(so_path.find_last_of('/') + 1);
1774 
1775  uintptr_t base = find_library_base(so_name);
1776  if(!base)
1777  {
1778  std::cerr << "Could not find base for " << so_name << "\n";
1779  return;
1780  }
1781 
1782  uintptr_t file_addr = runtime_addr - base;
1783 
1784  std::ostringstream cmd;
1785  cmd << "addr2line -f -C -e " << so_path << " 0x" << std::hex << file_addr;
1786 
1787  __COUT__ << "\nResolving:\n"
1788  << so_path << " : " << real_name << offset_begin << " [" << std::hex
1789  << runtime_addr << "]\n\n";
1790 
1791  std::string result = StringMacros::exec(cmd.str().c_str());
1792  __COUTV__(result);
1793 } //end resolve_stack_entry()
1794 
1795 //==============================================================================
1799 #include <cxxabi.h> //for abi::__cxa_demangle
1800 #include <execinfo.h> //for back trace of stack
1801 // #include "TUnixSystem.h"
1803 {
1804  __SS__ << "ots::stackTrace:\n";
1805 
1806  void* array[10];
1807  size_t size;
1808 
1809  // get void*'s for all entries on the stack
1810  size = backtrace(array, 10);
1811  // backtrace_symbols_fd(array, size, STDERR_FILENO);
1812 
1813  // https://stackoverflow.com/questions/77005/how-to-automatically-generate-a-stacktrace-when-my-program-crashes
1814  char** messages = backtrace_symbols(array, size);
1815 
1816  // skip first stack frame (points here)
1817  // char syscom[256];
1818  for(unsigned int i = 1; i < size && messages != NULL; ++i)
1819  {
1820  // mangled name needs to be converted to get nice name and line number
1821  // line number not working... FIXME
1822 
1823  // sprintf(syscom,"addr2line %p -e %s",
1824  // array[i],
1825  // messages[i]); //last parameter is the name of this app
1826  // ss << StringMacros::exec(syscom) << __E__;
1827  // system(syscom);
1828 
1829  // continue;
1830 
1831  char *mangled_name = 0, *offset_begin = 0, *offset_end = 0;
1832 
1833  // find parentheses and +address offset surrounding mangled name
1834  for(char* p = messages[i]; *p; ++p)
1835  {
1836  if(*p == '(')
1837  {
1838  mangled_name = p;
1839  }
1840  else if(*p == '+')
1841  {
1842  offset_begin = p;
1843  }
1844  else if(*p == ')')
1845  {
1846  offset_end = p;
1847  break;
1848  }
1849  }
1850 
1851  // if the line could be processed, attempt to demangle the symbol
1852  if(mangled_name && offset_begin && offset_end && mangled_name < offset_begin)
1853  {
1854  *mangled_name++ = '\0';
1855  *offset_begin++ = '\0';
1856  *offset_end++ = '\0';
1857 
1858  int status;
1859  char* real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);
1860 
1861  // if demangling is successful, output the demangled function name
1862  if(status == 0)
1863  {
1864  ss << "[" << i << "] " << messages[i] << " : " << real_name << "+"
1865  << offset_begin << offset_end << std::endl;
1866  // Too slow to resolve lines (stackTrace getting called too much)!
1867  // resolve_stack_entry(messages[i],real_name,offset_begin,offset_end);
1868  }
1869  // otherwise, output the mangled function name
1870  else
1871  {
1872  ss << "[" << i << "] " << messages[i] << " : " << mangled_name << "+"
1873  << offset_begin << offset_end << std::endl;
1874  }
1875  free(real_name);
1876  }
1877  // otherwise, print the whole line
1878  else
1879  {
1880  ss << "[" << i << "] " << messages[i] << std::endl;
1881  }
1882  }
1883  ss << std::endl;
1884  ss << std::endl;
1885 
1886  free(messages);
1887 
1888  // call ROOT's stack trace to get line numbers of ALL threads
1889  // gSystem->StackTrace();
1890 
1891  return ss.str();
1892 } // end stackTrace
1893 
1894 //==============================================================================
1900  const std::string& location,
1901  const unsigned int& line)
1902 {
1903  char* environmentVariablePtr = getenv(name);
1904  if(!environmentVariablePtr)
1905  {
1906  __SS__ << "Environment variable '$" << name << "' not defined at " << location
1907  << ":" << line << __E__;
1908  ss << "\n\n" << StringMacros::stackTrace() << __E__;
1909  __SS_ONLY_THROW__;
1910  }
1911  return environmentVariablePtr;
1912 } // end otsGetEnvironmentVarable()
1913 
1914 //=========================================================================
1917 std::string StringMacros::extractXmlField(const std::string& xml,
1918  const std::string& field,
1919  uint32_t occurrence,
1920  size_t after,
1921  size_t* returnFindPos /* = nullptr */,
1922  const std::string& valueField /* = "value=" */,
1923  const std::string& quoteType /* = "'" */)
1924 {
1925  if(returnFindPos)
1926  *returnFindPos = std::string::npos;
1927 
1928  __COUTVS__(41, xml);
1929 
1930  size_t lo, findpos = after, hi;
1931  for(uint32_t i = 0; i <= occurrence; ++i)
1932  {
1933  bool anyFound = false;
1934  while((findpos =
1935  xml.find("<" + field, //allow for immediate closing of xml tag with >
1936  findpos)) != std::string::npos &&
1937  findpos + 1 + field.size() < xml.size())
1938  {
1939  __COUTS__(40) << "find: ---- '<" << field << " findpos=" << findpos
1940  << "findpos " << findpos << " " << xml[findpos] << " "
1941  << xml[findpos + 1 + field.size()] << " "
1942  << (int)xml[findpos + 1 + field.size()] << __E__;
1943 
1944  findpos +=
1945  1 +
1946  field
1947  .size(); //to point to closing white space and advance for next forward search
1948 
1949  //verify white space after the field
1950  if((quoteType == ">" && xml[findpos] == '>') || xml[findpos] == ' ' ||
1951  xml[findpos] == '\n' || xml[findpos] == '\t')
1952  {
1953  anyFound = true; //flag
1954  break;
1955  }
1956  }
1957 
1958  if(!anyFound)
1959  {
1960  __COUTS__(40) << "Field '" << field << "' not found" << __E__;
1961  return "";
1962  }
1963  }
1964 
1965  lo = xml.find(valueField + quoteType, findpos) + valueField.size() + quoteType.size();
1966 
1967  if(TTEST(40) && quoteType.size())
1968  {
1969  __COUTS__(40) << "Neighbors of field '" << field << "' and value '" << valueField
1970  << "' w/quote = " << quoteType << __E__;
1971  for(size_t i = lo - valueField.size(); i < lo + 10 && i < xml.size(); ++i)
1972  __COUTS__(40) << "xml[" << i << "] " << xml[i] << " vs " << quoteType << " ? "
1973  << (int)xml[i] << " vs " << (int)quoteType[0] << __E__;
1974  }
1975 
1976  if((hi = xml.find(
1977  quoteType == ">" ? "<" : quoteType, //if xml tag, change closing direction
1978  lo)) == std::string::npos)
1979  {
1980  __COUTS__(40) << "Value closing not found" << __E__;
1981  return "";
1982  }
1983 
1984  if(returnFindPos)
1985  *returnFindPos = findpos - (1 + field.size()); //remove offset that was added
1986 
1987  __COUTS__(40) << "after: " << after << ", findpos: " << findpos << ", hi/lo: " << hi
1988  << "/" << lo << ", size: " << xml.size() << __E__;
1989  __COUTVS__(40, xml.substr(lo, hi - lo));
1990  return xml.substr(lo, hi - lo);
1991 } //end extractXmlField()
1992 
1993 //=========================================================================
1996 std::string StringMacros::rextractXmlField(const std::string& xml,
1997  const std::string& field,
1998  uint32_t occurrence,
1999  size_t before,
2000  size_t* returnFindPos /* = nullptr */,
2001  const std::string& valueField /* = "value=" */,
2002  const std::string& quoteType /* = "'" */)
2003 {
2004  if(returnFindPos)
2005  *returnFindPos = std::string::npos;
2006 
2007  __COUTVS__(41, xml);
2008 
2009  size_t lo = 0, hi, findpos = before;
2010  for(uint32_t i = 0; i <= occurrence; ++i)
2011  {
2012  bool anyFound = false;
2013  while((findpos =
2014  xml.rfind("<" + field, //allow for immediate closing of xml tag with >
2015  findpos)) != std::string::npos &&
2016  findpos + 1 + field.size() < xml.size())
2017  {
2018  __COUTS__(40) << "rfind: ---- '<" << field << " findpos=" << findpos << " "
2019  << xml[findpos] << " " << xml[findpos + 1 + field.size()] << " "
2020  << (int)xml[findpos + 1 + field.size()] << __E__;
2021 
2022  findpos += 1 + field.size();
2023 
2024  //verify white space after the field
2025  if((quoteType == ">" && xml[findpos] == '>') || xml[findpos] == ' ' ||
2026  xml[findpos] == '\n' || xml[findpos] == '\t')
2027  {
2028  anyFound = true; //flag
2029  break;
2030  }
2031  else
2032  findpos -= 1 + field.size() + 1; //for next reverse search
2033  }
2034  if(!anyFound)
2035  {
2036  __COUTS__(40) << "Field '" << field << "' not found" << __E__;
2037  return "";
2038  }
2039  }
2040 
2041  lo = xml.find(valueField + quoteType, findpos) + valueField.size() + quoteType.size();
2042 
2043  if(TTEST(40) && quoteType.size())
2044  {
2045  __COUTS__(40) << "Neighbors?" << __E__;
2046  for(size_t i = findpos; i < lo + 10 && i < xml.size(); ++i)
2047  __COUTS__(40) << "xml[" << i << "] " << xml[i] << " vs " << quoteType << " ? "
2048  << (int)xml[i] << " vs " << (int)quoteType[0] << __E__;
2049  }
2050 
2051  if((hi = xml.find(
2052  quoteType == ">" ? "<" : quoteType, //if xml tag, change closing direction
2053  lo)) == std::string::npos)
2054  {
2055  __COUTS__(40) << "Value closing not found" << __E__;
2056  return "";
2057  }
2058 
2059  if(returnFindPos)
2060  *returnFindPos =
2061  findpos - (1 + field.size()); //return found position of "< + field"
2062 
2063  __COUTS__(40) << "before: " << before << ", findpos: " << findpos << ", hi/lo: " << hi
2064  << "/" << lo << ", size: " << xml.size() << __E__;
2065  __COUTVS__(40, xml.substr(lo, hi - lo));
2066  return xml.substr(lo, hi - lo);
2067 } //end rextractXmlField()
2068 
2069 //=========================================================================
2072 void StringMacros::coutSplit(const std::string& str,
2073  uint8_t lvl /* = 0 */,
2074  const std::set<char>& delimiter /* = {',', '\n', ';'} */)
2075 {
2076  auto splitArr =
2077  StringMacros::getVectorFromString(str, delimiter, {} /* whitespace */);
2078  __COUTV__(splitArr.size());
2079  __COUTVS__(lvl, splitArr.size());
2080  for(const auto& split : splitArr)
2081  __COUTS__(lvl) << split;
2082 } //end coutSplit()
2083 
2084 #ifdef __GNUG__
2085 #include <cxxabi.h>
2086 #include <cstdlib>
2087 #include <memory>
2088 
2089 //==============================================================================
2091 std::string StringMacros::demangleTypeName(const char* name)
2092 {
2093  int status = -4; // some arbitrary value to eliminate the compiler warning
2094 
2095  // enable c++11 by passing the flag -std=c++11 to g++
2096  std::unique_ptr<char, void (*)(void*)> res{
2097  abi::__cxa_demangle(name, NULL, NULL, &status), std::free};
2098 
2099  return (status == 0) ? res.get() : name;
2100 } // end demangleTypeName()
2101 
2102 #else // does nothing if not g++
2103 //==============================================================================
2106 std::string StringMacros::demangleTypeName(const char* name) { return name; }
2107 #endif
defines used also by OtsConfigurationWizardSupervisor
bool operator()(const std::string &lhs, const std::string &rhs) const
<get string in order ignoring letter case
static std::string getTimestampString(const std::string &linuxTimeInSeconds)
static const std::string & trim(std::string &s)
static std::string extractXmlField(const std::string &xml, const std::string &field, uint32_t occurrence, size_t after, size_t *returnFindPos=nullptr, const std::string &valueField="value=", const std::string &quoteType="'")
static void getVectorFromString(const std::string &inputString, std::vector< std::string > &listToReturn, const std::set< char > &delimiter={',', '|', '&'}, const std::set< char > &whitespace={' ', '\t', '\n', '\r'}, std::vector< char > *listOfDelimiters=0, bool decodeURIComponents=false)
static std::string exec(const char *cmd)
static void getSetFromString(const std::string &inputString, std::set< std::string > &setToReturn, const std::set< char > &delimiter={',', '|', '&'}, const std::set< char > &whitespace={' ', '\t', '\n', '\r'})
static std::string setToString(const std::set< T > &setToReturn, const std::string &delimeter=", ")
setToString ~
static std::string escapeString(std::string inString, bool allowWhiteSpace=false, bool forHtml=false)
static T validateValueForDefaultStringDataType(const std::string &value, bool doConvertEnvironmentVariables=true)
static char * otsGetEnvironmentVarable(const char *name, const std::string &location, const unsigned int &line)
static void sanitizeForSQL(std::string &data)
StringMacros::sanitizeForSQL.
static void coutSplit(const std::string &str, uint8_t traceLevel=TLVL_DEBUG, const std::set< char > &delimiter={',', '\n', ';'})
static std::string vectorToString(const std::vector< T > &setToReturn, const std::string &delimeter=", ")
vectorToString ~
static std::string convertEnvironmentVariables(const std::string &data)
static std::string getNumberType(const std::string &stringToCheck)
Note: before call consider use of stringToCheck = StringMacros::convertEnvironmentVariables(stringToC...
static std::string escapeJSONStringEntities(const std::string &str)
static std::string demangleTypeName(const char *name)
static std::string rextractXmlField(const std::string &xml, const std::string &field, uint32_t occurrence, size_t before, size_t *returnFindPos=nullptr, const std::string &valueField="value=", const std::string &quoteType="'")
static bool extractCommonChunks(const std::vector< std::string > &haystack, std::vector< std::string > &commonChunksToReturn, std::vector< std::string > &wildcardStrings, unsigned int &fixedWildcardLength)
static bool inWildCardSet(const std::string &needle, const std::set< std::string > &haystack)
static bool isNumber(const std::string &stringToCheck)
Note: before call consider use of stringToCheck = StringMacros::convertEnvironmentVariables(stringToC...
static std::string mapToString(const std::map< std::string, T > &mapToReturn, const std::string &primaryDelimeter=", ", const std::string &secondaryDelimeter=": ")
static void getMapFromString(const std::string &inputString, std::map< S, T > &mapToReturn, const std::set< char > &pairPairDelimiter={',', '|', '&'}, const std::set< char > &nameValueDelimiter={'=', ':'}, const std::set< char > &whitespace={' ', '\t', '\n', '\r'})
getMapFromString ~
static std::string restoreJSONStringEntities(const std::string &str)
static std::string getTimeDurationString(const time_t durationInSeconds=time(0))
static bool wildCardMatch(const std::string &needle, const std::string &haystack, unsigned int *priorityIndex=0)
Definition: StringMacros.cc:30
static std::string decodeURIComponent(const std::string &data)
static std::string stackTrace(void)
static bool getNumber(const std::string &s, T &retValue)