diff --git a/vrs/helpers/Strings.cpp b/vrs/helpers/Strings.cpp index 4910decb..3c1fb292 100644 --- a/vrs/helpers/Strings.cpp +++ b/vrs/helpers/Strings.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -62,6 +63,56 @@ bool endsWith(const string& text, const string& suffix) { text.c_str() + text.length() - suffix.length(), suffix.c_str(), suffix.length()) == 0; } +inline bool isdigit(char c) { + return std::isdigit(static_cast(c)); +} + +static uint32_t lastDigitIndex(const char* str, uint32_t index) { + while (isdigit(str[index + 1])) { + index++; + } + return index; +} + +inline char paddedChar(const char* str, uint32_t pos, uint32_t pad, uint32_t index) { + return index < pad ? '0' : str[pos + index - pad]; +} + +#define LEFT_C (left[left_p]) +#define RIGHT_C (right[right_p]) + +bool beforeFileName(const char* left, const char* right) { + uint32_t leftPos = 0; + uint32_t rightPos = 0; + bool bothDigits = false; + while ((bothDigits = (isdigit(left[leftPos]) && isdigit(right[rightPos]))) || + (left[leftPos] == right[rightPos] && left[leftPos] != 0)) { + if (bothDigits) { + uint32_t leftDigitLength = lastDigitIndex(left, leftPos) - leftPos; + uint32_t rightDigitLength = lastDigitIndex(right, rightPos) - rightPos; + uint32_t leftPad = + leftDigitLength < rightDigitLength ? rightDigitLength - leftDigitLength : 0; + uint32_t rightPad = + rightDigitLength < leftDigitLength ? leftDigitLength - rightDigitLength : 0; + uint32_t lastDigitIndex = max(leftDigitLength, rightDigitLength); + for (uint32_t digitIndex = 0; digitIndex <= lastDigitIndex; digitIndex++) { + char lc = paddedChar(left, leftPos, leftPad, digitIndex); + char rc = paddedChar(right, rightPos, rightPad, digitIndex); + if (lc != rc) { + return lc < rc; + } + } + leftPos += leftDigitLength; + rightPos += rightDigitLength; + } + leftPos++, rightPos++; + } + if (left[leftPos] == 0) { + return right[rightPos] != 0; + } + return left[leftPos] < right[rightPos]; +} + string humanReadableFileSize(int64_t bytes) { const int64_t unit = 1024; if (bytes < unit) { diff --git a/vrs/helpers/Strings.h b/vrs/helpers/Strings.h index ca9acfa7..cf21352d 100644 --- a/vrs/helpers/Strings.h +++ b/vrs/helpers/Strings.h @@ -18,7 +18,6 @@ #include -#include #include #include #include @@ -53,6 +52,17 @@ inline int strncasecmp(const char* first, const char* second, size_t size) { } #endif +/// Compare strings, as you'd expect in a modern desktop OS (Explorer/Finder), treating digit +/// sections as numbers, so that "image1.png" is before "image02.png", and "image010.png" is the +/// same as "image00010.png". +/// Note: This is not a total order, since beforeFileName("image1.png", "image01.png") and +/// beforeFileName("image01.png", "image1.png") are both false! +bool beforeFileName(const char* left, const char* right); + +inline bool beforefileName(const std::string& left, const std::string& right) { + return beforeFileName(left.c_str(), right.c_str()); +} + /// Returns a copy of the string from which all the characters in whiteChars /// at the beginning or at the end of the string have been removed. /// @param text: some utf8 text string to trim diff --git a/vrs/helpers/test/StringsTest.cpp b/vrs/helpers/test/StringsTest.cpp index 02584145..035dff82 100644 --- a/vrs/helpers/test/StringsTest.cpp +++ b/vrs/helpers/test/StringsTest.cpp @@ -323,3 +323,65 @@ TEST_F(StringsHelpersTester, splitTest) { helpers::split(str, 'l', actualTokens, true, " "); EXPECT_EQ(actualTokens, expectedTokens); } + +#define CHECK_BEFORE(a, b) \ + EXPECT_TRUE(helpers::beforeFileName(a, b)); \ + EXPECT_FALSE(helpers::beforeFileName(b, a)); + +#define CHECK_SAME(a, b) \ + EXPECT_FALSE(helpers::beforeFileName(a, b)); \ + EXPECT_FALSE(helpers::beforeFileName(b, a)); + +#define CHECK_BEFORE_SELF(a) EXPECT_FALSE(helpers::beforeFileName(a, a)) + +TEST_F(StringsHelpersTester, beforeFileNameTest) { + helpers::beforeFileName("part0image10.png", "part0000image011.png"); + + CHECK_BEFORE_SELF(""); + CHECK_BEFORE_SELF("a"); + CHECK_BEFORE_SELF("abcd"); + CHECK_BEFORE_SELF("abcd000z"); + + CHECK_BEFORE("", "a"); + CHECK_BEFORE("", "0"); + CHECK_BEFORE("00", "001"); + CHECK_BEFORE("00", "0a"); + CHECK_BEFORE("10", "011"); + + CHECK_SAME("0", "00"); + CHECK_SAME("0", "0000000"); + CHECK_SAME("10", "0010"); + CHECK_SAME("123", "123"); + CHECK_SAME("123", "0123"); + CHECK_SAME("0123", "00000000123"); + CHECK_SAME("image0123section3z", "image000123section003z"); + CHECK_SAME("02image0123section3z", "2image0123section03z"); + + CHECK_SAME("image10.png", "image10.png"); + CHECK_SAME("image010.png", "image10.png"); + CHECK_SAME("image0010.png", "image10.png"); + CHECK_SAME("image010.png", "image000010.png"); + + CHECK_BEFORE("image10a", "image10b"); + CHECK_BEFORE("image010a", "image10b"); + CHECK_BEFORE("image010a", "image0010b"); + + CHECK_BEFORE("image10.png", "image11.png"); + CHECK_BEFORE("image010.png", "image11.png"); + CHECK_BEFORE("image10.png", "image011.png"); + CHECK_BEFORE("image90.png", "image0110.png"); + CHECK_BEFORE("image90.png", "image0190.png"); + CHECK_BEFORE("image19.png", "image90.png"); + CHECK_BEFORE("image019.png", "image90.png"); + CHECK_BEFORE("image019.png", "image0090.png"); + CHECK_BEFORE("image1901.png", "image19010.png"); + + CHECK_BEFORE("part0image10.png", "part0image11.png"); + CHECK_BEFORE("part00image010.png", "part0image11.png"); + CHECK_BEFORE("part0image10.png", "part0000image011.png"); + CHECK_BEFORE("part0image90.png", "part000image0110.png"); + CHECK_BEFORE("part0image90.png", "part0image0190.png"); + CHECK_BEFORE("part0image19.png", "part00image90.png"); + CHECK_BEFORE("part0image019.png", "part0image90.png"); + CHECK_BEFORE("part0image019.png", "part0image0090.png"); +}