otsdaq  3.09.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  //check for extra escaping of the quotes
358  // a quote is escaped only when preceded by an odd number of backslashes
359  {
360  unsigned int backslashCount = 0;
361  for(unsigned int j = i; j > 0 && inString[j - 1] == '\\'; --j)
362  ++backslashCount;
363 
364  if(backslashCount % 2 == 1)
365  {
366  //then this is an escaped quote, so remove the escape character and skip
367  inString.erase(i - 1, 1); // erase escape character
368  --i; // step back so next char to check is correct
369  }
370  }
371 
372  inString.insert(i,
373  (inString[i] == '\'')
374  ? "&apos"
375  : "&quot"); // insert HTML name before quotes
376  inString.replace(i + 5, 1, 1, ';'); // replace special character with ;
377  i += 5; // skip to next char to check
378  //__COUT__ << inString << std::endl;
379  }
380  else if(inString[i] == '&')
381  {
382  inString.insert(i, "&amp"); // insert HTML name before special character
383  inString.replace(i + 4, 1, 1, ';'); // replace special character with ;
384  i += 4; // skip to next char to check
385  }
386  else if(inString[i] == '<' || inString[i] == '>')
387  {
388  if(!forHtml)
389  {
390  inString.insert(
391  i,
392  (inString[i] == '<')
393  ? "&lt"
394  : "&gt"); // insert HTML name before special character
395  inString.replace(
396  i + 3, 1, 1, ';'); // replace special character with ;
397  i += 3; // skip to next char to check
398  }
399  else //double escape
400  {
401  inString.insert(
402  i,
403  (inString[i] == '<')
404  ? "&amp;lt"
405  : "&amp;gt"); // insert HTML name before special character
406  inString.replace(
407  i + 7, 1, 1, ';'); // replace special character with ;
408  i += 7; // skip to next char to check
409  }
410  }
411  else if(inString[i] >= char(161) &&
412  inString[i] <= char(255)) // printable special characters
413  {
414  sprintf(htmlTmp, "&#%3.3d", inString[i]);
415  inString.insert(i, std::string(htmlTmp)); // insert html number sequence
416  inString.replace(i + 5, 1, 1, ';'); // replace special character with ;
417  i += 5; // skip to next char to check
418  }
419 
420  __COUTS__(TLVL_EscapeString) << inString << std::endl;
421 
422  ws = i; // last non white space char
423  }
424  else if(allowWhiteSpace) // keep white space if allowed
425  {
426  if(i - 1 == ws)
427  continue; // dont do anything for first white space
428 
429  // for second white space add 2, and 1 from then
430  if(0 && i - 2 == ws)
431  {
432  inString.insert(i, "&#160;"); // insert html space
433  i += 6; // skip to point at space again
434  }
435  inString.insert(i, "&#160"); // insert html space
436  inString.replace(i + 5, 1, 1, ';'); // replace special character with ;
437  i += 5; // skip to next char to check
438  // ws = i;
439  }
440 
441  __COUTS__(TLVL_EscapeString) << inString.size() << " " << ws << std::endl;
442 
443  // inString.substr(0,ws+1);
444 
445  __COUTS__(TLVL_EscapeString) << inString.size() << " " << inString << std::endl;
446 
447  if(allowWhiteSpace) // keep all white space
448  return inString;
449  // else trim trailing white space
450 
451  if(ws == (unsigned int)-1)
452  return ""; // empty std::string since all white space
453  return inString.substr(0, ws + 1); // trim right white space
454 } // end escapeString()
455 
456 //==============================================================================
461 std::string StringMacros::escapeJSONStringEntities(const std::string& str)
462 {
463  unsigned int sz = str.size();
464  if(!sz)
465  return ""; // empty string, returns empty string
466 
467  std::string retStr = "";
468  retStr.reserve(str.size() * 2); // reserve roughly right size
469  for(unsigned int i = 0; i < sz; ++i)
470  {
471  switch(str[i])
472  {
473  case '\n':
474  retStr += "\\n";
475  break;
476  case '"':
477  retStr += "\\\"";
478  break;
479  case '\t':
480  retStr += "\\t";
481  break;
482  case '\r':
483  retStr += "\\r";
484  break;
485  case '\\':
486  retStr += "\\\\";
487  break;
488  default:
489  retStr += str[i];
490  }
491  }
492  return retStr;
493 } //end escapeJSONStringEntities
494 
495 //==============================================================================
499 std::string StringMacros::restoreJSONStringEntities(const std::string& str)
500 {
501  unsigned int sz = str.size();
502  if(!sz)
503  return ""; // empty string, returns empty string
504 
505  std::string retStr = "";
506  retStr.reserve(str.size()); // reserve roughly right size
507  unsigned int i = 0;
508  for(; i < sz - 1; ++i)
509  {
510  if(str[i] == '\\') // if 2 char escape sequence, replace with char
511  switch(str[i + 1])
512  {
513  case 'n':
514  retStr += '\n';
515  ++i;
516  break;
517  case '"':
518  retStr += '"';
519  ++i;
520  break;
521  case 't':
522  retStr += '\t';
523  ++i;
524  break;
525  case 'r':
526  retStr += '\r';
527  ++i;
528  break;
529  case '\\':
530  retStr += '\\';
531  ++i;
532  break;
533  default:
534  retStr += str[i];
535  }
536  else
537  retStr += str[i];
538  }
539  if(i == sz - 1)
540  retStr += str[sz - 1]; // output last character (which can't escape anything)
541 
542  return retStr;
543 } // end restoreJSONStringEntities()
544 
545 //==============================================================================
548 const std::string& StringMacros::trim(std::string& s)
549 {
550  // remove leading whitespace
551  s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
552  return !std::isspace(ch);
553  }));
554 
555  // remove trailing whitespace
556  s.erase(std::find_if(
557  s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); })
558  .base(),
559  s.end());
560 
561  return s;
562 } // end trim()
563 
564 //==============================================================================
574 std::string StringMacros::convertEnvironmentVariables(const std::string& data)
575 {
576  size_t begin = data.find("$");
577  if(begin != std::string::npos && begin + 1 < data.size())
578  {
579  size_t end;
580  std::string envVariable;
581  std::string converted = data; // make copy to modify
582  bool usedBraces = false; // track if braces were used
583 
584  while(begin && begin != std::string::npos &&
585  converted[begin - 1] ==
586  '\\') //do not convert environment variables with escaped \$
587  {
588  converted.replace(begin - 1, 1, "");
589  begin = converted.find("$", begin + 1); //find next
590  if(begin == std::string::npos)
591  {
592  __COUTS__(TLVL_EnvSub)
593  << "Only found escaped $'s that will not be converted: " << converted
594  << __E__;
595  return converted;
596  }
597  }
598 
599  // check if using $(( )) arithmetic expansion syntax
600  // First expand any $-based variables (including OTS system variables),
601  // then evaluate the arithmetic expression using getNumber() (requires explicit $ for variables, e.g., $A-$B)
602  if(begin + 2 < data.size() && data[begin + 1] == '(' && data[begin + 2] == '(')
603  {
604  end = data.find("))", begin + 3);
605  if(end == std::string::npos)
606  {
607  __SS__ << "Arithmetic expansion '$((...)),' at pos " << begin
608  << " in value, is missing closing '))'! Here was the value: "
609  << data << __E__;
610  __SS_THROW__;
611  }
612 
613  std::string expression = data.substr(begin + 3, end - begin - 3);
614  __COUTVS__(TLVL_EnvMath, expression);
615 
616  // Expand $VAR and ${OTS.*.*} inside the expression
617  expression = convertEnvironmentVariables(expression);
618  __COUTVS__(TLVL_EnvMath, expression);
619 
620  int64_t result;
621 
622  bool isNumber = getNumber(expression, result);
623  if(!isNumber)
624  {
625  __SS__ << "Arithmetic expansion '$((...)),' at pos " << begin
626  << " in value, does not evaluate to a number! Here was the value: "
627  << data << __E__;
628  __SS_THROW__;
629  }
630 
631  __COUTS__(TLVL_EnvMath) << "Arithmetic result: " << result << __E__;
632 
633  // proceed recursively, replacing $((...)) with the result
635  converted.replace(begin, end - begin + 2, std::to_string(result)));
636  }
637  else if(data[begin + 1] == '{') // check if using ${NAME} syntax
638  {
639  end = data.find("}", begin + 2);
640  envVariable = data.substr(begin + 2, end - begin - 2);
641  ++end; // replace the closing } too!
642  usedBraces = true;
643  }
644  else // else using $NAME syntax
645  {
646  // end is first non environment variable character
647  for(end = begin + 1; end < data.size(); ++end)
648  if(!((data[end] >= '0' && data[end] <= '9') ||
649  (data[end] >= 'A' && data[end] <= 'Z') ||
650  (data[end] >= 'a' && data[end] <= 'z') || data[end] == '-' ||
651  data[end] == '_' || data[end] == '.' || data[end] == ':'))
652  break; // found end
653  envVariable = data.substr(begin + 1, end - begin - 1);
654  usedBraces = false;
655  }
656  __COUTVS__(TLVL_EnvSub, data);
657  __COUTVS__(TLVL_EnvSub, envVariable);
658  if(usedBraces && envVariable.starts_with("OTS."))
659  {
660  __COUTS__(TLVL_EnvSub) << "OTS system variable detected!" << __E__;
661  auto sysVarSplit = StringMacros::getVectorFromString(envVariable, {'.'});
662  __COUTVS__(TLVL_EnvSub, StringMacros::vectorToString(sysVarSplit));
663 
664  if(sysVarSplit.size() != 3 ||
665  systemVariables_.find(sysVarSplit[1]) == systemVariables_.end() ||
666  systemVariables_.at(sysVarSplit[1]).find(sysVarSplit[2]) ==
667  systemVariables_.at(sysVarSplit[1]).end())
668  {
669  __SS__
670  << "System variable ${" << envVariable
671  << "} is not valid or was not found!"
672  << "\n\n"
673  << "If you were trying to access an ots System Variable, the correct "
674  "syntax is "
675  << "${OTS.<variable>.<property>}, e.g. "
676  "${OTS.ActiveStateMachine.name}"
677  << "\n\n"
678  << "If you were trying to insert an arithmetic operation, the "
679  "correct "
680  "syntax is $((4 - 3)) or $(($ENVVAR1 - $ENVVAR2))"
681  << "\n\n"
682  << "Available system variables:" << __E__;
683 
684  // Print all available system variables
685  for(const auto& varPair : systemVariables_)
686  {
687  ss << "\n OTS." << varPair.first << ".*";
688  for(const auto& propPair : varPair.second)
689  ss << "\n - OTS." << varPair.first << "." << propPair.first;
690  }
691  ss << __E__;
692  __SS_THROW__;
693  }
694  //else successful
695  // proceed recursively
696  return convertEnvironmentVariables(converted.replace(
697  begin,
698  end - begin,
699  systemVariables_.at(sysVarSplit[1]).at(sysVarSplit[2])));
700  }
701  else
702  {
703  char* envResult = nullptr;
704  try
705  {
706  envResult = __ENV__(envVariable.c_str());
707  }
708  catch(const std::runtime_error& e)
709  {
710  __SS__
711  << ("The environmental variable '" + envVariable +
712  "' is not set! Please make sure you set it before continuing!" +
713  "\n\n" +
714  "If you were trying to access an ots System Variable, the "
715  "correct syntax is " +
716  "${OTS.<variable>.<property>}, e.g. "
717  "${OTS.ActiveStateMachine.name}")
718  << __E__;
719  ss << "\n" << e.what() << __E__;
720  __SS_ONLY_THROW__;
721  }
722 
723  // proceed recursively
725  converted.replace(begin, end - begin, envResult));
726  }
727  }
728  // else no environment variables found in string
729  __COUTS__(TLVL_EnvSub) << "Result: " << data << __E__;
730  return data;
731 } //end convertEnvironmentVariables()
732 
733 //==============================================================================
738 bool StringMacros::isNumber(const std::string& s)
739 {
740  // extract set of potential numbers and operators
741  std::vector<std::string> numbers;
742  std::vector<char> ops;
743 
744  if(!s.size())
745  return false;
746 
748  s,
749  numbers,
750  /*delimiter*/ std::set<char>({'+', '-', '*', '/'}),
751  /*whitespace*/ std::set<char>({' ', '\t', '\n', '\r'}),
752  &ops);
753 
754  //__COUTV__(StringMacros::vectorToString(numbers));
755  //__COUTV__(StringMacros::vectorToString(ops));
756 
757  for(const auto& number : numbers)
758  {
759  if(number.size() == 0)
760  continue; // skip empty numbers
761 
762  if(number.find("0x") == 0) // indicates hex
763  {
764  //__COUT__ << "0x found" << std::endl;
765  for(unsigned int i = 2; i < number.size(); ++i)
766  {
767  if(!((number[i] >= '0' && number[i] <= '9') ||
768  (number[i] >= 'A' && number[i] <= 'F') ||
769  (number[i] >= 'a' && number[i] <= 'f')))
770  {
771  //__COUT__ << "prob " << number[i] << std::endl;
772  return false;
773  }
774  }
775  // return std::regex_match(number.substr(2), std::regex("^[0-90-9a-fA-F]+"));
776  }
777  else if(number[0] == 'b') // indicates binary
778  {
779  //__COUT__ << "b found" << std::endl;
780 
781  for(unsigned int i = 1; i < number.size(); ++i)
782  {
783  if(!((number[i] >= '0' && number[i] <= '1')))
784  {
785  //__COUT__ << "prob " << number[i] << std::endl;
786  return false;
787  }
788  }
789  }
790  else
791  {
792  //__COUT__ << "base 10 " << std::endl;
793  for(unsigned int i = 0; i < number.size(); ++i)
794  if(!((number[i] >= '0' && number[i] <= '9') || number[i] == '.' ||
795  number[i] == '+' || number[i] == '-'))
796  return false;
797  // Note: std::regex crashes in unresolvable ways (says Ryan.. also, stop using
798  // libraries) return std::regex_match(s,
799  // std::regex("^(\\-|\\+)?[0-9]*(\\.[0-9]+)?"));
800  }
801  }
802 
803  //__COUT__ << "yes " << std::endl;
804 
805  // all numbers are numbers
806  return true;
807 } // end isNumber()
808 
809 //==============================================================================
815 std::string StringMacros::getNumberType(const std::string& s)
816 {
817  // extract set of potential numbers and operators
818  std::vector<std::string> numbers;
819  std::vector<char> ops;
820 
821  bool hasDecimal = false;
822 
824  s,
825  numbers,
826  /*delimiter*/ std::set<char>({'+', '-', '*', '/'}),
827  /*whitespace*/ std::set<char>({' ', '\t', '\n', '\r'}),
828  &ops);
829 
830  //__COUTV__(StringMacros::vectorToString(numbers));
831  //__COUTV__(StringMacros::vectorToString(ops));
832 
833  for(const auto& number : numbers)
834  {
835  if(number.size() == 0)
836  continue; // skip empty numbers
837 
838  if(number.find("0x") == 0) // indicates hex
839  {
840  //__COUT__ << "0x found" << std::endl;
841  for(unsigned int i = 2; i < number.size(); ++i)
842  {
843  if(!((number[i] >= '0' && number[i] <= '9') ||
844  (number[i] >= 'A' && number[i] <= 'F') ||
845  (number[i] >= 'a' && number[i] <= 'f')))
846  {
847  //__COUT__ << "prob " << number[i] << std::endl;
848  return "nan";
849  }
850  }
851  // return std::regex_match(number.substr(2), std::regex("^[0-90-9a-fA-F]+"));
852  }
853  else if(number[0] == 'b') // indicates binary
854  {
855  //__COUT__ << "b found" << std::endl;
856 
857  for(unsigned int i = 1; i < number.size(); ++i)
858  {
859  if(!((number[i] >= '0' && number[i] <= '1')))
860  {
861  //__COUT__ << "prob " << number[i] << std::endl;
862  return "nan";
863  }
864  }
865  }
866  else
867  {
868  //__COUT__ << "base 10 " << std::endl;
869  for(unsigned int i = 0; i < number.size(); ++i)
870  if(!((number[i] >= '0' && number[i] <= '9') || number[i] == '.' ||
871  number[i] == '+' || number[i] == '-'))
872  return "nan";
873  else if(number[i] == '.')
874  hasDecimal = true;
875  // Note: std::regex crashes in unresolvable ways (says Ryan.. also, stop using
876  // libraries) return std::regex_match(s,
877  // std::regex("^(\\-|\\+)?[0-9]*(\\.[0-9]+)?"));
878  }
879  }
880 
881  //__COUT__ << "yes " << std::endl;
882 
883  // all numbers are numbers
884  if(hasDecimal)
885  return "double";
886  return "unsigned long long";
887 } // end getNumberType()
888 
889 //==============================================================================
890 // static template function
895 bool StringMacros::getNumber(const std::string& s, bool& retValue)
896 {
897  if(s.size() < 1)
898  {
899  __COUT_ERR__ << "Invalid empty bool string " << s << __E__;
900  return false;
901  }
902 
903  // check true case
904  if(s.find("1") != std::string::npos || s == "true" || s == "True" || s == "TRUE")
905  {
906  retValue = true;
907  return true;
908  }
909 
910  // check false case
911  if(s.find("0") != std::string::npos || s == "false" || s == "False" || s == "FALSE")
912  {
913  retValue = false;
914  return true;
915  }
916 
917  __COUT_ERR__ << "Invalid bool string " << s << __E__;
918  return false;
919 
920 } // end static getNumber<bool>
921 
922 //==============================================================================
926 std::string StringMacros::getTimestampString(const std::string& linuxTimeInSeconds)
927 {
928  time_t timestamp(strtol(linuxTimeInSeconds.c_str(), 0, 10));
929  return getTimestampString(timestamp);
930 } // end getTimestampString()
931 
932 //==============================================================================
936 std::string StringMacros::getTimestampString(const time_t linuxTimeInSeconds)
937 {
938  return ots::TimestampString().get(linuxTimeInSeconds);
939 } // end getTimestampString()
940 
941 //==============================================================================
945 {
946  //e.g., used by CoreSupervisorBase::getStatusProgressDetail(void)
947 
948  std::stringstream ss;
949  int days = t / 60 / 60 / 24;
950  if(days > 0)
951  {
952  ss << days << " day" << (days > 1 ? "s" : "") << ", ";
953  t -= days * 60 * 60 * 24;
954  }
955 
956  //HH:MM:SS
957  ss << std::setw(2) << std::setfill('0') << (t / 60 / 60) << ":" << std::setw(2)
958  << std::setfill('0') << ((t % (60 * 60)) / 60) << ":" << std::setw(2)
959  << std::setfill('0') << (t % 60);
960  return ss.str();
961 } //end getTimeDurationString()
962 
963 //==============================================================================
967  const std::string& value, bool doConvertEnvironmentVariables)
968 try
969 {
970  return doConvertEnvironmentVariables
972  : value;
973 }
974 catch(const std::runtime_error& e)
975 {
976  __SS__ << "Failed to validate value for default string data type. " << __E__
977  << e.what() << __E__;
978  __SS_THROW__;
979 }
980 
981 //==============================================================================
985 void StringMacros::getSetFromString(const std::string& inputString,
986  std::set<std::string>& setToReturn,
987  const std::set<char>& delimiter,
988  const std::set<char>& whitespace)
989 {
990  unsigned int i = 0;
991  unsigned int j = 0;
992 
993  // go through the full string extracting elements
994  // add each found element to set
995  for(; j < inputString.size(); ++j)
996  if((whitespace.find(inputString[j]) !=
997  whitespace.end() || // ignore leading white space or delimiter
998  delimiter.find(inputString[j]) != delimiter.end()) &&
999  i == j)
1000  ++i;
1001  else if((whitespace.find(inputString[j]) !=
1002  whitespace
1003  .end() || // trailing white space or delimiter indicates end
1004  delimiter.find(inputString[j]) != delimiter.end()) &&
1005  i != j) // assume end of element
1006  {
1007  //__COUT__ << "Set element found: " <<
1008  // inputString.substr(i,j-i) << std::endl;
1009 
1010  setToReturn.emplace(inputString.substr(i, j - i));
1011 
1012  // setup i and j for next find
1013  i = j + 1;
1014  }
1015 
1016  if(i != j) // last element check (for case when no concluding ' ' or delimiter)
1017  setToReturn.emplace(inputString.substr(i, j - i));
1018 } // end getSetFromString()
1019 
1020 //==============================================================================
1032 void StringMacros::getVectorFromString(const std::string& inputString,
1033  std::vector<std::string>& listToReturn,
1034  const std::set<char>& delimiter,
1035  const std::set<char>& whitespace,
1036  std::vector<char>* listOfDelimiters,
1037  bool decodeURIComponents)
1038 {
1039  unsigned int i = 0;
1040  unsigned int j = 0;
1041  unsigned int c = 0;
1042  std::set<char>::iterator delimeterSearchIt;
1043  char lastDelimiter = 0;
1044  bool isDelimiter;
1045  // bool foundLeadingDelimiter = false;
1046 
1047  //__COUT__ << inputString << __E__;
1048  //__COUTV__(inputString.length());
1049 
1050  // go through the full string extracting elements
1051  // add each found element to set
1052  for(; c < inputString.size(); ++c)
1053  {
1054  //__COUT__ << (char)inputString[c] << __E__;
1055 
1056  delimeterSearchIt = delimiter.find(inputString[c]);
1057  isDelimiter = delimeterSearchIt != delimiter.end();
1058 
1059  //__COUT__ << (char)inputString[c] << " " << isDelimiter <<
1060  //__E__;//char)lastDelimiter << __E__;
1061 
1062  if(whitespace.find(inputString[c]) !=
1063  whitespace.end() // ignore leading white space
1064  && i == j)
1065  {
1066  ++i;
1067  ++j;
1068  // if(isDelimiter)
1069  // foundLeadingDelimiter = true;
1070  }
1071  else if(whitespace.find(inputString[c]) != whitespace.end() &&
1072  i != j) // trailing white space, assume possible end of element
1073  {
1074  // do not change j or i
1075  }
1076  else if(isDelimiter) // delimiter is end of element
1077  {
1078  //__COUT__ << "Set element found: " <<
1079  // inputString.substr(i,j-i) << std::endl;
1080 
1081  if(listOfDelimiters && listToReturn.size()) // || foundLeadingDelimiter))
1082  // //accept leading delimiter
1083  // (especially for case of
1084  // leading negative in math
1085  // parsing)
1086  {
1087  //__COUTV__(lastDelimiter);
1088  listOfDelimiters->push_back(lastDelimiter);
1089  }
1090  listToReturn.push_back(decodeURIComponents ? StringMacros::decodeURIComponent(
1091  inputString.substr(i, j - i))
1092  : inputString.substr(i, j - i));
1093 
1094  // setup i and j for next find
1095  i = c + 1;
1096  j = c + 1;
1097  }
1098  else // part of element, so move j, not i
1099  j = c + 1;
1100 
1101  if(isDelimiter)
1102  lastDelimiter = *delimeterSearchIt;
1103  //__COUTV__(lastDelimiter);
1104  }
1105 
1106  if(1) // i != j) //last element check (for case when no concluding ' ' or delimiter)
1107  {
1108  //__COUT__ << "Last element found: " <<
1109  // inputString.substr(i,j-i) << std::endl;
1110 
1111  if(listOfDelimiters && listToReturn.size()) // || foundLeadingDelimiter))
1112  // //accept leading delimiter
1113  // (especially for case of leading
1114  // negative in math parsing)
1115  {
1116  //__COUTV__(lastDelimiter);
1117  listOfDelimiters->push_back(lastDelimiter);
1118  }
1119  listToReturn.push_back(decodeURIComponents ? StringMacros::decodeURIComponent(
1120  inputString.substr(i, j - i))
1121  : inputString.substr(i, j - i));
1122  }
1123 
1124  // assert that there is one less delimiter than values
1125  if(listOfDelimiters && listToReturn.size() - 1 != listOfDelimiters->size() &&
1126  listToReturn.size() != listOfDelimiters->size())
1127  {
1128  __SS__ << "There is a mismatch in delimiters to entries (should be equal or one "
1129  "less delimiter): "
1130  << listOfDelimiters->size() << " vs " << listToReturn.size() << __E__
1131  << "Entries: " << StringMacros::vectorToString(listToReturn) << __E__
1132  << "Delimiters: " << StringMacros::vectorToString(*listOfDelimiters)
1133  << __E__;
1134  __SS_THROW__;
1135  }
1136 
1137 } // end getVectorFromString()
1138 
1139 //==============================================================================
1151 std::vector<std::string> StringMacros::getVectorFromString(
1152  const std::string& inputString,
1153  const std::set<char>& delimiter,
1154  const std::set<char>& whitespace,
1155  std::vector<char>* listOfDelimiters,
1156  bool decodeURIComponents)
1157 {
1158  std::vector<std::string> listToReturn;
1159 
1161  listToReturn,
1162  delimiter,
1163  whitespace,
1164  listOfDelimiters,
1165  decodeURIComponents);
1166  return listToReturn;
1167 } // end getVectorFromString()
1168 
1169 //==============================================================================
1173 void StringMacros::getMapFromString(const std::string& inputString,
1174  std::map<std::string, std::string>& mapToReturn,
1175  const std::set<char>& pairPairDelimiter,
1176  const std::set<char>& nameValueDelimiter,
1177  const std::set<char>& whitespace)
1178 try
1179 {
1180  unsigned int i = 0;
1181  unsigned int j = 0;
1182  std::string name;
1183  bool needValue = false;
1184 
1185  // go through the full string extracting map pairs
1186  // add each found pair to map
1187  for(; j < inputString.size(); ++j)
1188  if(!needValue) // finding name
1189  {
1190  if((whitespace.find(inputString[j]) !=
1191  whitespace.end() || // ignore leading white space or delimiter
1192  pairPairDelimiter.find(inputString[j]) != pairPairDelimiter.end()) &&
1193  i == j)
1194  ++i;
1195  else if((whitespace.find(inputString[j]) !=
1196  whitespace
1197  .end() || // trailing white space or delimiter indicates end
1198  nameValueDelimiter.find(inputString[j]) !=
1199  nameValueDelimiter.end()) &&
1200  i != j) // assume end of map name
1201  {
1202  //__COUT__ << "Map name found: " <<
1203  // inputString.substr(i,j-i) << std::endl;
1204 
1205  name = inputString.substr(i, j - i); // save name, for concluding pair
1206 
1207  needValue = true; // need value now
1208 
1209  // setup i and j for next find
1210  i = j + 1;
1211  }
1212  }
1213  else // finding value
1214  {
1215  if((whitespace.find(inputString[j]) !=
1216  whitespace.end() || // ignore leading white space or delimiter
1217  nameValueDelimiter.find(inputString[j]) != nameValueDelimiter.end()) &&
1218  i == j)
1219  ++i;
1220  else if(whitespace.find(inputString[j]) !=
1221  whitespace
1222  .end() || // trailing white space or delimiter indicates end
1223  pairPairDelimiter.find(inputString[j]) !=
1224  pairPairDelimiter.end()) // &&
1225  // i != j) // assume end of value name
1226  {
1227  //__COUT__ << "Map value found: " <<
1228  // inputString.substr(i,j-i) << std::endl;
1229 
1230  auto /*pair<it,success>*/ emplaceReturn =
1231  mapToReturn.emplace(std::pair<std::string, std::string>(
1232  name,
1234  inputString.substr(i, j - i)) // value
1235  ));
1236 
1237  if(!emplaceReturn.second)
1238  {
1239  __COUT__ << "Ignoring repetitive value ('"
1240  << inputString.substr(i, j - i)
1241  << "') and keeping current value ('"
1242  << emplaceReturn.first->second << "'). " << __E__;
1243  }
1244 
1245  needValue = false; // need name now
1246 
1247  // setup i and j for next find
1248  i = j + 1;
1249  }
1250  }
1251 
1252  if(i != j) // last value (for case when no concluding ' ' or delimiter)
1253  {
1254  auto /*pair<it,success>*/ emplaceReturn =
1255  mapToReturn.emplace(std::pair<std::string, std::string>(
1256  name,
1258  inputString.substr(i, j - i)) // value
1259  ));
1260 
1261  if(!emplaceReturn.second)
1262  {
1263  __COUT__ << "Ignoring repetitive value ('" << inputString.substr(i, j - i)
1264  << "') and keeping current value ('" << emplaceReturn.first->second
1265  << "'). " << __E__;
1266  }
1267  }
1268 } // end getMapFromString()
1269 catch(const std::runtime_error& e)
1270 {
1271  __SS__ << "Error while extracting a map from the string '" << inputString
1272  << "'... is it a valid map?" << __E__ << e.what() << __E__;
1273  __SS_THROW__;
1274 }
1275 
1276 //==============================================================================
1278 std::string StringMacros::mapToString(const std::map<std::string, uint8_t>& mapToReturn,
1279  const std::string& primaryDelimeter,
1280  const std::string& secondaryDelimeter)
1281 {
1282  std::stringstream ss;
1283  bool first = true;
1284  for(auto& mapPair : mapToReturn)
1285  {
1286  if(first)
1287  first = false;
1288  else
1289  ss << primaryDelimeter;
1290  ss << mapPair.first << secondaryDelimeter << (unsigned int)mapPair.second;
1291  }
1292  return ss.str();
1293 } // end mapToString()
1294 
1295 //==============================================================================
1297 std::string StringMacros::setToString(const std::set<uint8_t>& setToReturn,
1298  const std::string& delimeter)
1299 {
1300  std::stringstream ss;
1301  bool first = true;
1302  for(auto& setValue : setToReturn)
1303  {
1304  if(first)
1305  first = false;
1306  else
1307  ss << delimeter;
1308  ss << (unsigned int)setValue;
1309  }
1310  return ss.str();
1311 } // end setToString()
1312 
1313 //==============================================================================
1315 std::string StringMacros::vectorToString(const std::vector<uint8_t>& setToReturn,
1316  const std::string& delimeter)
1317 {
1318  std::stringstream ss;
1319  bool first = true;
1320  if(delimeter == "\n")
1321  ss << "\n"; //add initial new line if new line delimiting
1322  for(auto& setValue : setToReturn)
1323  {
1324  if(first)
1325  first = false;
1326  else
1327  ss << delimeter;
1328  ss << (unsigned int)setValue;
1329  }
1330  return ss.str();
1331 } // end vectorToString()
1332 
1333 //==============================================================================
1343 bool StringMacros::extractCommonChunks(const std::vector<std::string>& haystack,
1344  std::vector<std::string>& commonChunksToReturn,
1345  std::vector<std::string>& wildcardStringsToReturn,
1346  unsigned int& fixedWildcardLength)
1347 {
1348  fixedWildcardLength = 0; // init to default
1349  __COUTTV__(StringMacros::vectorToString(haystack));
1350 
1351  // Steps:
1352  // - find start and end common chunks first in haystack strings
1353  // - use start and end to determine if there is more than one *
1354  // - decide if fixed width was specified (based on prepended 0s to numbers)
1355  // - search for more instances of * value
1356  //
1357  //
1358  // // Note: lambda recursive function to find chunks
1359  // std::function<void(
1360  // const std::vector<std::string>&,
1361  // const std::string&,
1362  // const unsigned int, const int)> localRecurse =
1363  // [&specialFolders, &specialMapTypes, &retMap, &localRecurse](
1364  // const std::vector<std::string>& haystack,
1365  // const std::string& offsetPath,
1366  // const unsigned int depth,
1367  // const int specialIndex)
1368  // {
1369  //
1370  // //__COUTV__(path);
1371  // //__COUTV__(depth);
1372  // }
1373  std::pair<unsigned int /*lo*/, unsigned int /*hi*/> wildcardBounds(
1374  std::make_pair(-1, 0)); // initialize to illegal wildcard
1375 
1376  // look for starting matching segment
1377  for(unsigned int n = 1; n < haystack.size(); ++n)
1378  for(unsigned int i = 0, j = 0;
1379  i < haystack[0].length() && j < haystack[n].length();
1380  ++i, ++j)
1381  {
1382  if(i < wildcardBounds.first)
1383  {
1384  if(haystack[0][i] != haystack[n][j])
1385  {
1386  wildcardBounds.first = i; // found lo side of wildcard
1387  break;
1388  }
1389  }
1390  else
1391  break;
1392  }
1393  __COUTS__(3) << "Low side = " << wildcardBounds.first << " "
1394  << haystack[0].substr(0, wildcardBounds.first) << __E__;
1395 
1396  // look for end matching segment
1397  for(unsigned int n = 1; n < haystack.size(); ++n)
1398  for(int i = haystack[0].length() - 1, j = haystack[n].length() - 1;
1399  i >= (int)wildcardBounds.first && j >= (int)wildcardBounds.first;
1400  --i, --j)
1401  {
1402  if(i > (int)wildcardBounds.second) // looking for hi side
1403  {
1404  if(haystack[0][i] != haystack[n][j])
1405  {
1406  wildcardBounds.second = i + 1; // found hi side of wildcard
1407  break;
1408  }
1409  }
1410  else
1411  break;
1412  }
1413 
1414  __COUTS__(3) << "High side = " << wildcardBounds.second << " "
1415  << haystack[0].substr(wildcardBounds.second) << __E__;
1416 
1417  // add first common chunk
1418  commonChunksToReturn.push_back(haystack[0].substr(0, wildcardBounds.first));
1419 
1420  if(wildcardBounds.first != (unsigned int)-1) // potentially more chunks if not end
1421  {
1422  // - use start and end to determine if there is more than one *
1423  for(int i = (wildcardBounds.first + wildcardBounds.second) / 2 + 1;
1424  i < (int)wildcardBounds.second;
1425  ++i)
1426  if(haystack[0][wildcardBounds.first] == haystack[0][i] &&
1427  haystack[0].substr(wildcardBounds.first, wildcardBounds.second - i) ==
1428  haystack[0].substr(i, wildcardBounds.second - i))
1429  {
1430  std::string multiWildcardString =
1431  haystack[0].substr(i, wildcardBounds.second - i);
1432  __COUT__ << "Potential multi-wildcard found: " << multiWildcardString
1433  << " at position i=" << i << __E__;
1434 
1435  std::vector<unsigned int /*lo index*/> wildCardInstances;
1436  // add front one now, and back one later
1437  wildCardInstances.push_back(wildcardBounds.first);
1438 
1439  unsigned int offset =
1440  wildCardInstances[0] + multiWildcardString.size() + 1;
1441  std::string middleString = haystack[0].substr(offset, (i - 1) - offset);
1442  __COUTV__(middleString);
1443 
1444  // search for more wildcard instances in new common area
1445  size_t k;
1446  while((k = middleString.find(multiWildcardString)) != std::string::npos)
1447  {
1448  __COUT__ << "Multi-wildcard found at " << k << __E__;
1449 
1450  wildCardInstances.push_back(offset + k);
1451 
1452  middleString =
1453  middleString.substr(k + multiWildcardString.size() + 1);
1454  offset += k + multiWildcardString.size() + 1;
1455  __COUTV__(middleString);
1456  }
1457 
1458  // add back one last
1459  wildCardInstances.push_back(i);
1460 
1461  for(unsigned int w = 0; w < wildCardInstances.size() - 1; ++w)
1462  {
1463  __COUTV__(wildCardInstances[w]);
1464  __COUTV__(wildCardInstances[w + 1]);
1465  __COUTV__(wildCardInstances.size());
1466  commonChunksToReturn.push_back(haystack[0].substr(
1467  wildCardInstances[w] + multiWildcardString.size(),
1468  wildCardInstances[w + 1] -
1469  (wildCardInstances[w] + multiWildcardString.size())));
1470  }
1471  }
1472 
1473  __COUTTV__(StringMacros::vectorToString(commonChunksToReturn));
1474  //confirm valid multi-commonChunksToReturn for all haystack entries (only first can be certain at this point)
1475  for(unsigned int c = 1; c < commonChunksToReturn.size(); ++c)
1476  {
1477  __COUT__ << "Checking [" << c << "]: " << commonChunksToReturn[c] << __E__;
1478  for(unsigned int n = 1; n < haystack.size(); ++n)
1479  {
1480  __COUT__ << "Checking chunks work with haystack [" << n
1481  << "]: " << haystack[n] << __E__;
1482  __COUTV__(commonChunksToReturn[0].size());
1483  std::string wildCardValue = haystack[n].substr(
1484  commonChunksToReturn[0].size(),
1485  haystack[n].find(commonChunksToReturn[1],
1486  commonChunksToReturn[0].size() + 1) -
1487  commonChunksToReturn[0].size());
1488  __COUTTV__(wildCardValue);
1489 
1490  std::string builtString = "";
1491  for(unsigned int cc = 0; cc < commonChunksToReturn.size(); ++cc)
1492  builtString += commonChunksToReturn[cc] + wildCardValue;
1493  __COUTTV__(builtString);
1494  __COUTTV__(wildCardValue);
1495 
1496  if(haystack[n].find(builtString) != 0)
1497  {
1498  __COUT__ << "Dropping common chunk " << commonChunksToReturn[c]
1499  << ", built '" << builtString << "' not found in "
1500  << haystack[n] << __E__;
1501  commonChunksToReturn.erase(commonChunksToReturn.begin() + c);
1502  --c; //rewind
1503  break; //check next chunk
1504  }
1505  else
1506  __COUTT__ << "Found built '" << builtString << "' in " << haystack[n]
1507  << __E__;
1508  } //end haystack loop
1509  } //end common chunk loop
1510 
1511  __COUTTV__(StringMacros::vectorToString(commonChunksToReturn));
1512  __COUTTV__(commonChunksToReturn[0].size());
1513 
1514  __COUTTV__(fixedWildcardLength);
1515  // check if all common chunks END in 0 to add fixed length
1516  for(unsigned int i = 0; i < commonChunksToReturn[0].size(); ++i)
1517  if(commonChunksToReturn[0][commonChunksToReturn[0].size() - 1 - i] == '0')
1518  {
1519  ++fixedWildcardLength;
1520  __COUTT__ << "Trying for added fixed length +1 to " << fixedWildcardLength
1521  << __E__;
1522  }
1523  else
1524  break;
1525 
1526  // bool allHave0 = true;
1527  for(unsigned int c = 0; c < commonChunksToReturn.size(); ++c)
1528  {
1529  unsigned int cnt = 0;
1530  for(unsigned int i = 0; i < commonChunksToReturn[c].size(); ++i)
1531  if(commonChunksToReturn[c][commonChunksToReturn[c].size() - 1 - i] == '0')
1532  ++cnt;
1533  else
1534  break;
1535 
1536  if(fixedWildcardLength < cnt)
1537  fixedWildcardLength = cnt;
1538  else if(fixedWildcardLength > cnt)
1539  {
1540  __SS__ << "Invalid fixed length found, please simplify indexing between "
1541  "these common chunks: "
1542  << StringMacros::vectorToString(commonChunksToReturn) << __E__;
1543  __SS_THROW__;
1544  }
1545  }
1546  __COUTTV__(fixedWildcardLength);
1547 
1548  if(fixedWildcardLength) // take trailing 0s out of common chunks
1549  for(unsigned int c = 0; c < commonChunksToReturn.size(); ++c)
1550  commonChunksToReturn[c] = commonChunksToReturn[c].substr(
1551  0, commonChunksToReturn[c].size() - fixedWildcardLength);
1552 
1553  // add last common chunk
1554  commonChunksToReturn.push_back(haystack[0].substr(wildcardBounds.second));
1555  } // end handling more chunks
1556  __COUTTV__(StringMacros::vectorToString(commonChunksToReturn));
1557 
1558  // now determine wildcard strings
1559  size_t k;
1560  unsigned int i;
1561  unsigned int ioff = fixedWildcardLength;
1562  bool wildcardsNeeded = false;
1563  bool someLeadingZeros = false;
1564  bool allWildcardsSameSize = true;
1565 
1566  for(unsigned int n = 0; n < haystack.size(); ++n)
1567  {
1568  std::string wildcard = "";
1569  k = 0;
1570  i = ioff + commonChunksToReturn[0].size();
1571 
1572  if(commonChunksToReturn.size() == 1) // just get end
1573  wildcard = haystack[n].substr(i);
1574  else
1575  for(unsigned int c = 1; c < commonChunksToReturn.size(); ++c)
1576  {
1577  if(c == commonChunksToReturn.size() - 1) // for last, do reverse find
1578  k = haystack[n].rfind(commonChunksToReturn[c]);
1579  else
1580  k = haystack[n].find(commonChunksToReturn[c], i + 1);
1581 
1582  if(wildcard == "")
1583  {
1584  // set wildcard for first time
1585  __COUTVS__(3, i);
1586  __COUTVS__(3, k);
1587  __COUTVS__(3, k - i);
1588 
1589  wildcard = haystack[n].substr(i, k - i);
1590  if(fixedWildcardLength && n == 0)
1591  fixedWildcardLength += wildcard.size();
1592 
1593  __COUTS__(3) << "name[" << n << "] = " << wildcard << " fixed @ "
1594  << fixedWildcardLength << __E__;
1595 
1596  break;
1597  }
1598  else if(0 /*skip validation in favor of speed*/ &&
1599  wildcard != haystack[n].substr(i, k - i))
1600  {
1601  __SS__ << "Invalid wildcard! for name[" << n << "] = " << haystack[n]
1602  << " - the extraction algorithm is confused, please simplify "
1603  "your naming convention."
1604  << __E__;
1605  __SS_THROW__;
1606  }
1607 
1608  i = k;
1609  } // end commonChunksToReturn loop
1610 
1611  if(wildcard.size())
1612  {
1613  wildcardsNeeded = true;
1614 
1615  //track if need for leading 0s in wildcards
1616  if(wildcard[0] == '0' && !fixedWildcardLength)
1617  {
1618  someLeadingZeros = true;
1619  if(wildcardStringsToReturn.size() &&
1620  wildcard.size() != wildcardStringsToReturn[0].size())
1621  allWildcardsSameSize = false;
1622  }
1623  }
1624  wildcardStringsToReturn.push_back(wildcard);
1625 
1626  } // end name loop
1627 
1628  __COUTTV__(StringMacros::vectorToString(commonChunksToReturn));
1629  __COUTTV__(StringMacros::vectorToString(wildcardStringsToReturn));
1630 
1631  if(someLeadingZeros && allWildcardsSameSize)
1632  {
1633  __COUTTV__(fixedWildcardLength); //should be 0 in this case
1634  fixedWildcardLength = wildcardStringsToReturn[0].size();
1635  __COUT__ << "Enforce wildcard size of " << fixedWildcardLength << __E__;
1636  }
1637 
1638  if(wildcardStringsToReturn.size() != haystack.size())
1639  {
1640  __SS__ << "There was a problem during common chunk extraction!" << __E__;
1641  __SS_THROW__;
1642  }
1643 
1644  return wildcardsNeeded;
1645 
1646 } // end extractCommonChunks()
1647 
1648 //==============================================================================
1653  const std::string& rhs) const
1654 {
1655  //__COUTV__(lhs);
1656  //__COUTV__(rhs);
1657  // return true if lhs < rhs (lhs will be ordered first)
1658 
1659  for(unsigned int i = 0; i < lhs.size() && i < rhs.size(); ++i)
1660  {
1661  //__COUT__ << i << "\t" << lhs[i] << "\t" << rhs[i] << __E__;
1662  if((lhs[i] >= 'A' && lhs[i] <= 'Z' && rhs[i] >= 'A' && rhs[i] <= 'Z') ||
1663  (lhs[i] >= 'a' && lhs[i] <= 'z' && rhs[i] >= 'a' && rhs[i] <= 'z'))
1664  { // same case
1665  if(lhs[i] == rhs[i])
1666  continue;
1667  return (lhs[i] < rhs[i]);
1668  //{ retVal = false; break;} //return false;
1669  }
1670  else if(lhs[i] >= 'A' && lhs[i] <= 'Z') // rhs lower case
1671  {
1672  if(lhs[i] + 32 == rhs[i]) // lower case is higher by 32
1673  return false; // in tie return lower case first
1674  return (lhs[i] + 32 < rhs[i]);
1675  }
1676  else if(rhs[i] >= 'A' && rhs[i] <= 'Z')
1677  {
1678  if(lhs[i] == rhs[i] + 32) // lower case is higher by 32
1679  return true; // in tie return lower case first
1680  return (lhs[i] < rhs[i] + 32);
1681  }
1682  else // not letters case (should only be for numbers)
1683  {
1684  if(lhs[i] == rhs[i])
1685  continue;
1686  return (lhs[i] < rhs[i]);
1687  }
1688  } // end case insensitive compare loop
1689 
1690  // lhs and rhs are equivalent to character[i], so return false if rhs.size() was the limit reached
1691  return lhs.size() < rhs.size();
1692 } // end IgnoreCaseCompareStruct::operator() comparison handler
1693 
1694 //==============================================================================
1697 std::string StringMacros::exec(const char* cmd)
1698 {
1699  __COUTTV__(cmd);
1700 
1701  std::array<char, 128> buffer;
1702  std::string result;
1703 
1704  // For capturing both stdout and stderr, we need to redirect stderr to stdout
1705  // This is done by appending " 2>&1" to the command
1706  std::string cmdWithRedirect = std::string(cmd) + " 2>&1";
1707  std::shared_ptr<FILE> pipe(popen(cmdWithRedirect.c_str(), "r"), pclose);
1708  if(!pipe)
1709  __THROW__("popen() failed!");
1710 
1711  // Read all output (both stdout and stderr)
1712  while(!feof(pipe.get()))
1713  {
1714  if(fgets(buffer.data(), 128, pipe.get()) != nullptr)
1715  result += buffer.data();
1716  }
1717 
1718  __COUTTV__(result);
1719  return result;
1720 } // end exec()
1721 
1722 // #include <iostream>
1723 #include <fstream> /* for ifstream */
1724 // #include <sstream>
1725 // #include <string>
1726 // #include <cstdlib>
1727 //==============================================================================
1728 uintptr_t find_library_base(const std::string& libname)
1729 {
1730  std::ifstream maps("/proc/self/maps");
1731  std::string line;
1732 
1733  while(std::getline(maps, line))
1734  {
1735  if(line.find(libname) != std::string::npos &&
1736  line.find("r-xp") != std::string::npos)
1737  {
1738  uintptr_t base;
1739  std::stringstream ss(line);
1740  ss >> std::hex >> base;
1741  return base;
1742  }
1743  }
1744  return 0;
1745 } //end find_library_base()
1746 
1747 //==============================================================================
1748 void resolve_stack_entry(const std::string& so_path,
1749  const std::string& real_name,
1750  const std::string& offset_begin, // e.g. "+0x249d"
1751  const std::string& offset_end // e.g. "[0x7f5518fa28fd]"
1752 )
1753 {
1754  // Extract runtime address from a string like "[0x....]".
1755  // Be defensive: validate delimiters before parsing to avoid exceptions.
1756  const std::size_t pos0x = offset_end.find("0x");
1757  if(pos0x == std::string::npos)
1758  {
1759  __COUTS__(52) << "resolve_stack_entry: could not find \"0x\" in offset_end: '"
1760  << offset_end << "'" << __E__;
1761  return;
1762  }
1763 
1764  const std::size_t posBracket = offset_end.find(']', pos0x);
1765  if(posBracket == std::string::npos || posBracket < pos0x + 3)
1766  {
1767  __COUTS__(52) << "resolve_stack_entry: could not find closing ']' with at least "
1768  "one hex digit after \"0x\" "
1769  "in offset_end: '"
1770  << offset_end << "'" << __E__;
1771  return;
1772  }
1773 
1774  const std::string addr_str = offset_end.substr(pos0x, posBracket - pos0x);
1775 
1776  uintptr_t runtime_addr = 0;
1777  try
1778  {
1779  runtime_addr = std::stoull(addr_str, nullptr, 16);
1780  }
1781  catch(const std::exception& e)
1782  {
1783  __COUTS__(52) << "resolve_stack_entry: failed to parse runtime address from '"
1784  << addr_str << "': " << e.what() << __E__;
1785  return;
1786  }
1787 
1788  std::string so_name = so_path.substr(so_path.find_last_of('/') + 1);
1789 
1790  uintptr_t base = find_library_base(so_name);
1791  if(!base)
1792  {
1793  std::cerr << "Could not find base for " << so_name << "\n";
1794  return;
1795  }
1796 
1797  uintptr_t file_addr = runtime_addr - base;
1798 
1799  std::ostringstream cmd;
1800  cmd << "addr2line -f -C -e " << so_path << " 0x" << std::hex << file_addr;
1801 
1802  __COUT__ << "\nResolving:\n"
1803  << so_path << " : " << real_name << offset_begin << " [" << std::hex
1804  << runtime_addr << "]\n\n";
1805 
1806  std::string result = StringMacros::exec(cmd.str().c_str());
1807  __COUTV__(result);
1808 } //end resolve_stack_entry()
1809 
1810 //==============================================================================
1814 #include <cxxabi.h> //for abi::__cxa_demangle
1815 #include <execinfo.h> //for back trace of stack
1816 // #include "TUnixSystem.h"
1818 {
1819  __SS__ << "ots::stackTrace:\n";
1820 
1821  void* array[10];
1822  size_t size;
1823 
1824  // get void*'s for all entries on the stack
1825  size = backtrace(array, 10);
1826  // backtrace_symbols_fd(array, size, STDERR_FILENO);
1827 
1828  // https://stackoverflow.com/questions/77005/how-to-automatically-generate-a-stacktrace-when-my-program-crashes
1829  char** messages = backtrace_symbols(array, size);
1830 
1831  // skip first stack frame (points here)
1832  // char syscom[256];
1833  for(unsigned int i = 1; i < size && messages != NULL; ++i)
1834  {
1835  // mangled name needs to be converted to get nice name and line number
1836  // line number not working... FIXME
1837 
1838  // sprintf(syscom,"addr2line %p -e %s",
1839  // array[i],
1840  // messages[i]); //last parameter is the name of this app
1841  // ss << StringMacros::exec(syscom) << __E__;
1842  // system(syscom);
1843 
1844  // continue;
1845 
1846  char *mangled_name = 0, *offset_begin = 0, *offset_end = 0;
1847 
1848  // find parentheses and +address offset surrounding mangled name
1849  for(char* p = messages[i]; *p; ++p)
1850  {
1851  if(*p == '(')
1852  {
1853  mangled_name = p;
1854  }
1855  else if(*p == '+')
1856  {
1857  offset_begin = p;
1858  }
1859  else if(*p == ')')
1860  {
1861  offset_end = p;
1862  break;
1863  }
1864  }
1865 
1866  // if the line could be processed, attempt to demangle the symbol
1867  if(mangled_name && offset_begin && offset_end && mangled_name < offset_begin)
1868  {
1869  *mangled_name++ = '\0';
1870  *offset_begin++ = '\0';
1871  *offset_end++ = '\0';
1872 
1873  int status;
1874  char* real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);
1875 
1876  // if demangling is successful, output the demangled function name
1877  if(status == 0)
1878  {
1879  ss << "[" << i << "] " << messages[i] << " : " << real_name << "+"
1880  << offset_begin << offset_end << std::endl;
1881  // Too slow to resolve lines (stackTrace getting called too much)!
1882  // resolve_stack_entry(messages[i],real_name,offset_begin,offset_end);
1883  }
1884  // otherwise, output the mangled function name
1885  else
1886  {
1887  ss << "[" << i << "] " << messages[i] << " : " << mangled_name << "+"
1888  << offset_begin << offset_end << std::endl;
1889  }
1890  free(real_name);
1891  }
1892  // otherwise, print the whole line
1893  else
1894  {
1895  ss << "[" << i << "] " << messages[i] << std::endl;
1896  }
1897  }
1898  ss << std::endl;
1899  ss << std::endl;
1900 
1901  free(messages);
1902 
1903  // call ROOT's stack trace to get line numbers of ALL threads
1904  // gSystem->StackTrace();
1905 
1906  return ss.str();
1907 } // end stackTrace
1908 
1909 //==============================================================================
1915  const std::string& location,
1916  const unsigned int& line)
1917 {
1918  char* environmentVariablePtr = getenv(name);
1919  if(!environmentVariablePtr)
1920  {
1921  __SS__ << "Environment variable '$" << name << "' not defined at " << location
1922  << ":" << line << __E__;
1923  ss << "\n\n" << StringMacros::stackTrace() << __E__;
1924  __SS_ONLY_THROW__;
1925  }
1926  return environmentVariablePtr;
1927 } // end otsGetEnvironmentVarable()
1928 
1929 //=========================================================================
1932 std::string StringMacros::extractXmlField(const std::string& xml,
1933  const std::string& field,
1934  uint32_t occurrence,
1935  size_t after,
1936  size_t* returnFindPos /* = nullptr */,
1937  const std::string& valueField /* = "value=" */,
1938  const std::string& quoteType /* = "'" */)
1939 {
1940  if(returnFindPos)
1941  *returnFindPos = std::string::npos;
1942 
1943  __COUTVS__(41, xml);
1944 
1945  size_t lo, findpos = after, hi;
1946  for(uint32_t i = 0; i <= occurrence; ++i)
1947  {
1948  bool anyFound = false;
1949  while((findpos =
1950  xml.find("<" + field, //allow for immediate closing of xml tag with >
1951  findpos)) != std::string::npos &&
1952  findpos + 1 + field.size() < xml.size())
1953  {
1954  __COUTS__(40) << "find: ---- '<" << field << " findpos=" << findpos
1955  << "findpos " << findpos << " " << xml[findpos] << " "
1956  << xml[findpos + 1 + field.size()] << " "
1957  << (int)xml[findpos + 1 + field.size()] << __E__;
1958 
1959  findpos +=
1960  1 +
1961  field
1962  .size(); //to point to closing white space and advance for next forward search
1963 
1964  //verify white space after the field
1965  if((quoteType == ">" && xml[findpos] == '>') || xml[findpos] == ' ' ||
1966  xml[findpos] == '\n' || xml[findpos] == '\t')
1967  {
1968  anyFound = true; //flag
1969  break;
1970  }
1971  }
1972 
1973  if(!anyFound)
1974  {
1975  __COUTS__(40) << "Field '" << field << "' not found" << __E__;
1976  return "";
1977  }
1978  }
1979 
1980  lo = xml.find(valueField + quoteType, findpos) + valueField.size() + quoteType.size();
1981 
1982  if(TTEST(40) && quoteType.size())
1983  {
1984  __COUTS__(40) << "Neighbors of field '" << field << "' and value '" << valueField
1985  << "' w/quote = " << quoteType << __E__;
1986  for(size_t i = lo - valueField.size(); i < lo + 10 && i < xml.size(); ++i)
1987  __COUTS__(40) << "xml[" << i << "] " << xml[i] << " vs " << quoteType << " ? "
1988  << (int)xml[i] << " vs " << (int)quoteType[0] << __E__;
1989  }
1990 
1991  if((hi = xml.find(
1992  quoteType == ">" ? "<" : quoteType, //if xml tag, change closing direction
1993  lo)) == std::string::npos)
1994  {
1995  __COUTS__(40) << "Value closing not found" << __E__;
1996  return "";
1997  }
1998 
1999  if(returnFindPos)
2000  *returnFindPos = findpos - (1 + field.size()); //remove offset that was added
2001 
2002  __COUTS__(40) << "after: " << after << ", findpos: " << findpos << ", hi/lo: " << hi
2003  << "/" << lo << ", size: " << xml.size() << __E__;
2004  __COUTVS__(40, xml.substr(lo, hi - lo));
2005  return xml.substr(lo, hi - lo);
2006 } //end extractXmlField()
2007 
2008 //=========================================================================
2011 std::string StringMacros::rextractXmlField(const std::string& xml,
2012  const std::string& field,
2013  uint32_t occurrence,
2014  size_t before,
2015  size_t* returnFindPos /* = nullptr */,
2016  const std::string& valueField /* = "value=" */,
2017  const std::string& quoteType /* = "'" */)
2018 {
2019  if(returnFindPos)
2020  *returnFindPos = std::string::npos;
2021 
2022  __COUTVS__(41, xml);
2023 
2024  size_t lo = 0, hi, findpos = before;
2025  for(uint32_t i = 0; i <= occurrence; ++i)
2026  {
2027  bool anyFound = false;
2028  while((findpos =
2029  xml.rfind("<" + field, //allow for immediate closing of xml tag with >
2030  findpos)) != std::string::npos &&
2031  findpos + 1 + field.size() < xml.size())
2032  {
2033  __COUTS__(40) << "rfind: ---- '<" << field << " findpos=" << findpos << " "
2034  << xml[findpos] << " " << xml[findpos + 1 + field.size()] << " "
2035  << (int)xml[findpos + 1 + field.size()] << __E__;
2036 
2037  findpos += 1 + field.size();
2038 
2039  //verify white space after the field
2040  if((quoteType == ">" && xml[findpos] == '>') || xml[findpos] == ' ' ||
2041  xml[findpos] == '\n' || xml[findpos] == '\t')
2042  {
2043  anyFound = true; //flag
2044  break;
2045  }
2046  else
2047  findpos -= 1 + field.size() + 1; //for next reverse search
2048  }
2049  if(!anyFound)
2050  {
2051  __COUTS__(40) << "Field '" << field << "' not found" << __E__;
2052  return "";
2053  }
2054  }
2055 
2056  lo = xml.find(valueField + quoteType, findpos) + valueField.size() + quoteType.size();
2057 
2058  if(TTEST(40) && quoteType.size())
2059  {
2060  __COUTS__(40) << "Neighbors?" << __E__;
2061  for(size_t i = findpos; i < lo + 10 && i < xml.size(); ++i)
2062  __COUTS__(40) << "xml[" << i << "] " << xml[i] << " vs " << quoteType << " ? "
2063  << (int)xml[i] << " vs " << (int)quoteType[0] << __E__;
2064  }
2065 
2066  if((hi = xml.find(
2067  quoteType == ">" ? "<" : quoteType, //if xml tag, change closing direction
2068  lo)) == std::string::npos)
2069  {
2070  __COUTS__(40) << "Value closing not found" << __E__;
2071  return "";
2072  }
2073 
2074  if(returnFindPos)
2075  *returnFindPos =
2076  findpos - (1 + field.size()); //return found position of "< + field"
2077 
2078  __COUTS__(40) << "before: " << before << ", findpos: " << findpos << ", hi/lo: " << hi
2079  << "/" << lo << ", size: " << xml.size() << __E__;
2080  __COUTVS__(40, xml.substr(lo, hi - lo));
2081  return xml.substr(lo, hi - lo);
2082 } //end rextractXmlField()
2083 
2084 //=========================================================================
2087 void StringMacros::coutSplit(const std::string& str,
2088  uint8_t lvl /* = 0 */,
2089  const std::set<char>& delimiter /* = {',', '\n', ';'} */)
2090 {
2091  auto splitArr =
2092  StringMacros::getVectorFromString(str, delimiter, {} /* whitespace */);
2093  __COUTV__(splitArr.size());
2094  __COUTVS__(lvl, splitArr.size());
2095  for(const auto& split : splitArr)
2096  __COUTS__(lvl) << split;
2097 } //end coutSplit()
2098 
2099 #ifdef __GNUG__
2100 #include <cxxabi.h>
2101 #include <cstdlib>
2102 #include <memory>
2103 
2104 //==============================================================================
2106 std::string StringMacros::demangleTypeName(const char* name)
2107 {
2108  int status = -4; // some arbitrary value to eliminate the compiler warning
2109 
2110  // enable c++11 by passing the flag -std=c++11 to g++
2111  std::unique_ptr<char, void (*)(void*)> res{
2112  abi::__cxa_demangle(name, NULL, NULL, &status), std::free};
2113 
2114  return (status == 0) ? res.get() : name;
2115 } // end demangleTypeName()
2116 
2117 #else // does nothing if not g++
2118 //==============================================================================
2121 std::string StringMacros::demangleTypeName(const char* name) { return name; }
2122 #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)