2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
20 //-----------------------------------------------------------------------
22 // File: StringUtils.cpp
24 // Purpose: ATL split string utility
25 // Author: Paul J. Weiss
27 // Modified to use J O'Leary's CStdString class by kraqh3d
29 //------------------------------------------------------------------------
32 #include "StringUtils.h"
33 #include "utils/RegExp.h"
34 #include "utils/fstrcmp.h"
41 #define FORMAT_BLOCK_SIZE 2048 // # of bytes to increment per try
45 const char* ADDON_GUID_RE = "^(\\{){0,1}[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}(\\}){0,1}$";
47 /* empty string for use in returns by ref */
48 const CStdString StringUtils::EmptyString = "";
49 const std::string StringUtils::Empty = "";
50 CStdString StringUtils::m_lastUUID = "";
52 string StringUtils::Format(const char *fmt, ...)
56 string str = FormatV(fmt, args);
62 string StringUtils::FormatV(const char *fmt, va_list args)
67 int size = FORMAT_BLOCK_SIZE;
70 char *cstr = reinterpret_cast<char*>(malloc(sizeof(char) * size));
76 va_copy(argCopy, args);
78 int nActual = vsnprintf(cstr, size, fmt, argCopy);
81 if (nActual > -1 && nActual < size) // We got a valid result
83 string str(cstr, nActual);
87 if (nActual > -1) // Exactly what we will need (glibc 2.1)
89 else // Let's try to double the size (glibc 2.0)
92 char *new_cstr = reinterpret_cast<char*>(realloc(cstr, sizeof(char) * size));
106 wstring StringUtils::Format(const wchar_t *fmt, ...)
110 wstring str = FormatV(fmt, args);
116 wstring StringUtils::FormatV(const wchar_t *fmt, va_list args)
121 int size = FORMAT_BLOCK_SIZE;
124 wchar_t *cstr = reinterpret_cast<wchar_t*>(malloc(sizeof(wchar_t) * size));
130 va_copy(argCopy, args);
132 int nActual = vswprintf(cstr, size, fmt, argCopy);
135 if (nActual > -1 && nActual < size) // We got a valid result
137 wstring str(cstr, nActual);
141 if (nActual > -1) // Exactly what we will need (glibc 2.1)
143 else // Let's try to double the size (glibc 2.0)
146 wchar_t *new_cstr = reinterpret_cast<wchar_t*>(realloc(cstr, sizeof(wchar_t) * size));
147 if (new_cstr == NULL)
159 void StringUtils::ToUpper(string &str)
161 transform(str.begin(), str.end(), str.begin(), ::toupper);
164 void StringUtils::ToUpper(wstring &str)
166 transform(str.begin(), str.end(), str.begin(), ::towupper);
169 void StringUtils::ToLower(string &str)
171 transform(str.begin(), str.end(), str.begin(), ::tolower);
174 void StringUtils::ToLower(wstring &str)
176 transform(str.begin(), str.end(), str.begin(), ::towlower);
179 bool StringUtils::EqualsNoCase(const std::string &str1, const std::string &str2)
181 return EqualsNoCase(str1.c_str(), str2.c_str());
184 bool StringUtils::EqualsNoCase(const std::string &str1, const char *s2)
186 return EqualsNoCase(str1.c_str(), s2);
189 bool StringUtils::EqualsNoCase(const char *s1, const char *s2)
191 char c2; // we need only one char outside the loop
194 const char c1 = *s1++; // const local variable should help compiler to optimize
196 if (c1 != c2 && ::tolower(c1) != ::tolower(c2)) // This includes the possibility that one of the characters is the null-terminator, which implies a string mismatch.
198 } while (c2 != '\0'); // At this point, we know c1 == c2, so there's no need to test them both.
202 int StringUtils::CompareNoCase(const std::string &str1, const std::string &str2)
204 return CompareNoCase(str1.c_str(), str2.c_str());
207 int StringUtils::CompareNoCase(const char *s1, const char *s2)
209 char c2; // we need only one char outside the loop
212 const char c1 = *s1++; // const local variable should help compiler to optimize
214 if (c1 != c2 && ::tolower(c1) != ::tolower(c2)) // This includes the possibility that one of the characters is the null-terminator, which implies a string mismatch.
215 return ::tolower(c1) - ::tolower(c2);
216 } while (c2 != '\0'); // At this point, we know c1 == c2, so there's no need to test them both.
220 string StringUtils::Left(const string &str, size_t count)
222 count = max((size_t)0, min(count, str.size()));
223 return str.substr(0, count);
226 string StringUtils::Mid(const string &str, size_t first, size_t count /* = string::npos */)
228 if (first + count > str.size())
229 count = str.size() - first;
231 if (first > str.size())
234 ASSERT(first + count <= str.size());
236 return str.substr(first, count);
239 string StringUtils::Right(const string &str, size_t count)
241 count = max((size_t)0, min(count, str.size()));
242 return str.substr(str.size() - count);
245 std::string& StringUtils::Trim(std::string &str)
248 return TrimRight(str);
251 std::string& StringUtils::Trim(std::string &str, const char* const chars)
253 TrimLeft(str, chars);
254 return TrimRight(str, chars);
257 // hack to ensure that std::string::iterator will be dereferenced as _unsigned_ char
258 // without this hack "TrimX" functions failed on Win32 with UTF-8 strings
259 static int isspace_c(char c)
261 return ::isspace((unsigned char)c);
264 std::string& StringUtils::TrimLeft(std::string &str)
266 str.erase(str.begin(), ::find_if(str.begin(), str.end(), ::not1(::ptr_fun(isspace_c))));
270 std::string& StringUtils::TrimLeft(std::string &str, const char* const chars)
272 size_t nidx = str.find_first_not_of(chars);
277 std::string& StringUtils::TrimRight(std::string &str)
279 str.erase(::find_if(str.rbegin(), str.rend(), ::not1(::ptr_fun(isspace_c))).base(), str.end());
283 std::string& StringUtils::TrimRight(std::string &str, const char* const chars)
285 size_t nidx = str.find_last_not_of(chars);
286 str.erase(str.npos == nidx ? 0 : ++nidx);
290 std::string& StringUtils::RemoveDuplicatedSpacesAndTabs(std::string& str)
292 std::string::iterator it = str.begin();
293 bool onSpace = false;
294 while(it != str.end())
317 int StringUtils::Replace(string &str, char oldChar, char newChar)
319 int replacedChars = 0;
320 for (string::iterator it = str.begin(); it != str.end(); it++)
329 return replacedChars;
332 int StringUtils::Replace(std::string &str, const std::string &oldStr, const std::string &newStr)
337 int replacedChars = 0;
340 while (index < str.size() && (index = str.find(oldStr, index)) != string::npos)
342 str.replace(index, oldStr.size(), newStr);
343 index += newStr.size();
347 return replacedChars;
350 int StringUtils::Replace(std::wstring &str, const std::wstring &oldStr, const std::wstring &newStr)
355 int replacedChars = 0;
358 while (index < str.size() && (index = str.find(oldStr, index)) != string::npos)
360 str.replace(index, oldStr.size(), newStr);
361 index += newStr.size();
365 return replacedChars;
368 bool StringUtils::StartsWith(const std::string &str1, const std::string &str2)
370 return str1.compare(0, str2.size(), str2) == 0;
373 bool StringUtils::StartsWith(const std::string &str1, const char *s2)
375 return StartsWith(str1.c_str(), s2);
378 bool StringUtils::StartsWith(const char *s1, const char *s2)
390 bool StringUtils::StartsWithNoCase(const std::string &str1, const std::string &str2)
392 return StartsWithNoCase(str1.c_str(), str2.c_str());
395 bool StringUtils::StartsWithNoCase(const std::string &str1, const char *s2)
397 return StartsWithNoCase(str1.c_str(), s2);
400 bool StringUtils::StartsWithNoCase(const char *s1, const char *s2)
404 if (::tolower(*s1) != ::tolower(*s2))
412 bool StringUtils::EndsWith(const std::string &str1, const std::string &str2)
414 if (str1.size() < str2.size())
416 return str1.compare(str1.size() - str2.size(), str2.size(), str2) == 0;
419 bool StringUtils::EndsWith(const std::string &str1, const char *s2)
421 size_t len2 = strlen(s2);
422 if (str1.size() < len2)
424 return str1.compare(str1.size() - len2, len2, s2) == 0;
427 bool StringUtils::EndsWithNoCase(const std::string &str1, const std::string &str2)
429 if (str1.size() < str2.size())
431 const char *s1 = str1.c_str() + str1.size() - str2.size();
432 const char *s2 = str2.c_str();
435 if (::tolower(*s1) != ::tolower(*s2))
443 bool StringUtils::EndsWithNoCase(const std::string &str1, const char *s2)
445 size_t len2 = strlen(s2);
446 if (str1.size() < len2)
448 const char *s1 = str1.c_str() + str1.size() - len2;
451 if (::tolower(*s1) != ::tolower(*s2))
459 void StringUtils::JoinString(const CStdStringArray &strings, const CStdString& delimiter, CStdString& result)
462 for(CStdStringArray::const_iterator it = strings.begin(); it != strings.end(); it++ )
463 result += (*it) + delimiter;
466 result.erase(result.size()-delimiter.size(), delimiter.size());
469 CStdString StringUtils::JoinString(const CStdStringArray &strings, const CStdString& delimiter)
472 JoinString(strings, delimiter, result);
476 CStdString StringUtils::Join(const vector<string> &strings, const CStdString& delimiter)
478 CStdStringArray strArray;
479 for (unsigned int index = 0; index < strings.size(); index++)
480 strArray.push_back(strings.at(index));
482 return JoinString(strArray, delimiter);
485 // Splits the string input into pieces delimited by delimiter.
486 // if 2 delimiters are in a row, it will include the empty string between them.
487 // added MaxStrings parameter to restrict the number of returned substrings (like perl and python)
488 int StringUtils::SplitString(const CStdString& input, const CStdString& delimiter, CStdStringArray &results, unsigned int iMaxStrings /* = 0 */)
490 size_t iPos = std::string::npos;
491 size_t newPos = std::string::npos;
492 size_t sizeS2 = delimiter.size();
493 size_t isize = input.size();
497 vector<unsigned int> positions;
499 newPos = input.find(delimiter, 0);
501 if (newPos == std::string::npos)
503 results.push_back(input);
507 while (newPos != std::string::npos)
509 positions.push_back(newPos);
511 newPos = input.find(delimiter, iPos + sizeS2);
514 // numFound is the number of delimiters which is one less
515 // than the number of substrings
516 unsigned int numFound = positions.size();
517 if (iMaxStrings > 0 && numFound >= iMaxStrings)
518 numFound = iMaxStrings - 1;
520 for ( unsigned int i = 0; i <= numFound; i++ )
528 s = input.substr(i, positions[i]);
532 size_t offset = positions[i - 1] + sizeS2;
533 if ( offset < isize )
536 s = input.substr(offset);
538 s = input.substr( positions[i - 1] + sizeS2,
539 positions[i] - positions[i - 1] - sizeS2 );
542 results.push_back(s);
544 // return the number of substrings
545 return results.size();
548 CStdStringArray StringUtils::SplitString(const CStdString& input, const CStdString& delimiter, unsigned int iMaxStrings /* = 0 */)
550 CStdStringArray result;
551 SplitString(input, delimiter, result, iMaxStrings);
555 vector<string> StringUtils::Split(const std::string& input, const std::string& delimiter, unsigned int iMaxStrings /* = 0 */)
557 CStdStringArray result;
558 SplitString(input, delimiter, result, iMaxStrings);
560 vector<string> strArray;
561 for (unsigned int index = 0; index < result.size(); index++)
562 strArray.push_back(result.at(index));
567 // returns the number of occurrences of strFind in strInput.
568 int StringUtils::FindNumber(const CStdString& strInput, const CStdString &strFind)
570 size_t pos = strInput.find(strFind, 0);
572 while (pos != std::string::npos)
575 pos = strInput.find(strFind, pos + 1);
580 // Compares separately the numeric and alphabetic parts of a string.
581 // returns negative if left < right, positive if left > right
582 // and 0 if they are identical (essentially calculates left - right)
583 int64_t StringUtils::AlphaNumericCompare(const wchar_t *left, const wchar_t *right)
585 wchar_t *l = (wchar_t *)left;
586 wchar_t *r = (wchar_t *)right;
590 const collate<wchar_t>& coll = use_facet< collate<wchar_t> >( locale() );
592 while (*l != 0 && *r != 0)
594 // check if we have a numerical value
595 if (*l >= L'0' && *l <= L'9' && *r >= L'0' && *r <= L'9')
599 while (*ld >= L'0' && *ld <= L'9' && ld < l + 15)
600 { // compare only up to 15 digits
606 while (*rd >= L'0' && *rd <= L'9' && rd < r + 15)
607 { // compare only up to 15 digits
609 rnum += *rd++ - L'0';
611 // do we have numbers?
613 { // yes - and they're different!
620 // do case less comparison
622 if (lc >= L'A' && lc <= L'Z')
625 if (rc >= L'A' && rc <= L'Z')
628 // ok, do a normal comparison, taking current locale into account. Add special case stuff (eg '(' characters)) in here later
629 if ((cmp_res = coll.compare(&lc, &lc + 1, &rc, &rc + 1)) != 0)
643 return 0; // files are the same
646 int StringUtils::DateStringToYYYYMMDD(const CStdString &dateString)
648 CStdStringArray days;
649 int splitCount = StringUtils::SplitString(dateString, "-", days);
651 return atoi(days[0].c_str());
652 else if (splitCount == 2)
653 return atoi(days[0].c_str())*100+atoi(days[1].c_str());
654 else if (splitCount == 3)
655 return atoi(days[0].c_str())*10000+atoi(days[1].c_str())*100+atoi(days[2].c_str());
660 long StringUtils::TimeStringToSeconds(const CStdString &timeString)
662 CStdString strCopy(timeString);
663 StringUtils::Trim(strCopy);
664 if(StringUtils::EndsWithNoCase(strCopy, " min"))
666 // this is imdb format of "XXX min"
667 return 60 * atoi(strCopy.c_str());
671 CStdStringArray secs;
672 StringUtils::SplitString(strCopy, ":", secs);
674 for (unsigned int i = 0; i < 3 && i < secs.size(); i++)
677 timeInSecs += atoi(secs[i]);
683 CStdString StringUtils::SecondsToTimeString(long lSeconds, TIME_FORMAT format)
685 int hh = lSeconds / 3600;
686 lSeconds = lSeconds % 3600;
687 int mm = lSeconds / 60;
688 int ss = lSeconds % 60;
690 if (format == TIME_FORMAT_GUESS)
691 format = (hh >= 1) ? TIME_FORMAT_HH_MM_SS : TIME_FORMAT_MM_SS;
693 if (format & TIME_FORMAT_HH)
694 strHMS += StringUtils::Format("%02.2i", hh);
695 else if (format & TIME_FORMAT_H)
696 strHMS += StringUtils::Format("%i", hh);
697 if (format & TIME_FORMAT_MM)
698 strHMS += StringUtils::Format(strHMS.empty() ? "%02.2i" : ":%02.2i", mm);
699 if (format & TIME_FORMAT_SS)
700 strHMS += StringUtils::Format(strHMS.empty() ? "%02.2i" : ":%02.2i", ss);
704 bool StringUtils::IsNaturalNumber(const CStdString& str)
707 // allow whitespace,digits,whitespace
708 while (i < str.size() && isspace((unsigned char) str[i]))
710 while (i < str.size() && isdigit((unsigned char) str[i]))
714 while (i < str.size() && isspace((unsigned char) str[i]))
716 return i == str.size() && n > 0;
719 bool StringUtils::IsInteger(const CStdString& str)
722 // allow whitespace,-,digits,whitespace
723 while (i < str.size() && isspace((unsigned char) str[i]))
725 if (i < str.size() && str[i] == '-')
727 while (i < str.size() && isdigit((unsigned char) str[i]))
731 while (i < str.size() && isspace((unsigned char) str[i]))
733 return i == str.size() && n > 0;
736 int StringUtils::asciidigitvalue(char chr)
738 if (!isasciidigit(chr))
744 int StringUtils::asciixdigitvalue(char chr)
746 int v = asciidigitvalue(chr);
749 if (chr >= 'a' && chr <= 'f')
750 return chr - 'a' + 10;
751 if (chr >= 'A' && chr <= 'F')
752 return chr - 'A' + 10;
758 void StringUtils::RemoveCRLF(CStdString& strLine)
760 StringUtils::TrimRight(strLine, "\n\r");
763 CStdString StringUtils::SizeToString(int64_t size)
766 const char prefixes[] = {' ','k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'};
768 double s = (double)size;
769 while (i < sizeof(prefixes)/sizeof(prefixes[0]) && s >= 1000.0)
776 strLabel = StringUtils::Format("%.0lf %cB ", s, prefixes[i]);
778 strLabel = StringUtils::Format("%.1lf %cB", s, prefixes[i]);
780 strLabel = StringUtils::Format("%.2lf %cB", s, prefixes[i]);
785 // return -1 if not, else return the utf8 char length.
786 int IsUTF8Letter(const unsigned char *str)
789 // unicode -> utf8 table: http://www.utf8-chartable.de/
790 // latin characters in unicode: http://en.wikipedia.org/wiki/Latin_characters_in_Unicode
791 unsigned char ch = str[0];
794 if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
798 unsigned char ch2 = str[1];
801 // check latin 1 letter table: http://en.wikipedia.org/wiki/C1_Controls_and_Latin-1_Supplement
802 if (ch == 0xC3 && ch2 >= 0x80 && ch2 <= 0xBF && ch2 != 0x97 && ch2 != 0xB7)
804 // check latin extended A table: http://en.wikipedia.org/wiki/Latin_Extended-A
805 if (ch >= 0xC4 && ch <= 0xC7 && ch2 >= 0x80 && ch2 <= 0xBF)
807 // check latin extended B table: http://en.wikipedia.org/wiki/Latin_Extended-B
808 // and International Phonetic Alphabet: http://en.wikipedia.org/wiki/IPA_Extensions_(Unicode_block)
809 if (((ch == 0xC8 || ch == 0xC9) && ch2 >= 0x80 && ch2 <= 0xBF)
810 || (ch == 0xCA && ch2 >= 0x80 && ch2 <= 0xAF))
815 size_t StringUtils::FindWords(const char *str, const char *wordLowerCase)
817 // NOTE: This assumes word is lowercase!
818 unsigned char *s = (unsigned char *)str;
821 // start with a compare
822 unsigned char *c = s;
823 unsigned char *w = (unsigned char *)wordLowerCase;
825 while (same && *c && *w)
827 unsigned char lc = *c++;
828 if (lc >= 'A' && lc <= 'Z')
831 if (lc != *w++) // different
834 if (same && *w == 0) // only the same if word has been exhausted
835 return (const char *)s - str;
837 // otherwise, skip current word (composed by latin letters) or number
839 if (*s >= '0' && *s <= '9')
842 while (*s >= '0' && *s <= '9') ++s;
844 else if ((l = IsUTF8Letter(s)) > 0)
847 while ((l = IsUTF8Letter(s)) > 0) s += l;
851 while (*s && *s == ' ') s++;
853 // and repeat until we're done
856 return CStdString::npos;
859 // assumes it is called from after the first open bracket is found
860 int StringUtils::FindEndBracket(const CStdString &str, char opener, char closer, int startPos)
863 for (unsigned int i = startPos; i < str.size(); i++)
865 if (str[i] == opener)
867 else if (str[i] == closer)
875 return (int)CStdString::npos;
878 void StringUtils::WordToDigits(CStdString &word)
880 static const char word_to_letter[] = "22233344455566677778889999";
881 StringUtils::ToLower(word);
882 for (unsigned int i = 0; i < word.size(); ++i)
883 { // NB: This assumes ascii, which probably needs extending at some point.
884 char letter = word[i];
885 if ((letter >= 'a' && letter <= 'z')) // assume contiguous letter range
887 word[i] = word_to_letter[letter-'a'];
889 else if (letter < '0' || letter > '9') // We want to keep 0-9!
891 word[i] = ' '; // replace everything else with a space
896 CStdString StringUtils::CreateUUID()
898 /* This function generate a DCE 1.1, ISO/IEC 11578:1996 and IETF RFC-4122
899 * Version 4 conform local unique UUID based upon random number generation.
902 char *pUuidStr = UuidStrTmp;
905 static bool m_uuidInitialized = false;
906 if (!m_uuidInitialized)
908 /* use current time as the seed for rand()*/
910 m_uuidInitialized = true;
913 /*Data1 - 8 characters.*/
914 for(i = 0; i < 8; i++, pUuidStr++)
915 ((*pUuidStr = (rand() % 16)) < 10) ? *pUuidStr += 48 : *pUuidStr += 55;
917 /*Data2 - 4 characters.*/
919 for(i = 0; i < 4; i++, pUuidStr++)
920 ((*pUuidStr = (rand() % 16)) < 10) ? *pUuidStr += 48 : *pUuidStr += 55;
922 /*Data3 - 4 characters.*/
924 for(i = 0; i < 4; i++, pUuidStr++)
925 ((*pUuidStr = (rand() % 16)) < 10) ? *pUuidStr += 48 : *pUuidStr += 55;
927 /*Data4 - 4 characters.*/
929 for(i = 0; i < 4; i++, pUuidStr++)
930 ((*pUuidStr = (rand() % 16)) < 10) ? *pUuidStr += 48 : *pUuidStr += 55;
932 /*Data5 - 12 characters.*/
934 for(i = 0; i < 12; i++, pUuidStr++)
935 ((*pUuidStr = (rand() % 16)) < 10) ? *pUuidStr += 48 : *pUuidStr += 55;
939 m_lastUUID = UuidStrTmp;
943 bool StringUtils::ValidateUUID(const CStdString &uuid)
946 guidRE.RegComp(ADDON_GUID_RE);
947 return (guidRE.RegFind(uuid.c_str()) == 0);
950 double StringUtils::CompareFuzzy(const CStdString &left, const CStdString &right)
952 return (0.5 + fstrcmp(left.c_str(), right.c_str(), 0.0) * (left.length() + right.length())) / 2.0;
955 int StringUtils::FindBestMatch(const CStdString &str, const CStdStringArray &strings, double &matchscore)
961 for (CStdStringArray::const_iterator it = strings.begin(); it != strings.end(); it++, i++)
963 int maxlength = max(str.length(), it->length());
964 double score = StringUtils::CompareFuzzy(str, *it) / maxlength;
965 if (score > matchscore)
974 bool StringUtils::ContainsKeyword(const CStdString &str, const CStdStringArray &keywords)
976 for (CStdStringArray::const_iterator it = keywords.begin(); it != keywords.end(); it++)
978 if (str.find(*it) != str.npos)
984 size_t StringUtils::utf8_strlen(const char *s)
989 if ((*s++ & 0xC0) != 0x80)
995 std::string StringUtils::Paramify(const std::string ¶m)
997 std::string result = param;
999 StringUtils::Replace(result, "\\", "\\\\");
1000 // escape double quotes
1001 StringUtils::Replace(result, "\"", "\\\"");
1003 // add double quotes around the whole string
1004 return "\"" + result + "\"";
1007 void StringUtils::Tokenize(const std::string& input, std::vector<std::string>& tokens, const std::string& delimiters)
1009 // Tokenize ripped from http://www.linuxselfhelp.com/HOWTO/C++Programming-HOWTO-7.html
1010 // Skip delimiters at beginning.
1011 string::size_type lastPos = input.find_first_not_of(delimiters, 0);
1012 // Find first "non-delimiter".
1013 string::size_type pos = input.find_first_of(delimiters, lastPos);
1015 while (string::npos != pos || string::npos != lastPos)
1017 // Found a token, add it to the vector.
1018 tokens.push_back(input.substr(lastPos, pos - lastPos));
1019 // Skip delimiters. Note the "not_of"
1020 lastPos = input.find_first_not_of(delimiters, pos);
1021 // Find next "non-delimiter"
1022 pos = input.find_first_of(delimiters, lastPos);