Improve accuracy of version extraction

In particular, where multiple versions exist in a string, prefer those
prefixed by "version" over others.
This commit is contained in:
Oliver Hamlet
2015-12-07 18:54:36 +00:00
parent 835d2ba463
commit f1aec275a6
3 changed files with 58 additions and 40 deletions
+37 -39
View File
@@ -43,53 +43,51 @@
namespace loot {
using namespace std;
const regex Version::versionRegex(
// The R"()" raw string literal format means the outermost brackets in
// the C strings below are not part of the regex.
// There are three separate version string matchers below:
// 1. Timestamp with forwardslash date separators.
// 2. Subset of Pseudosem-supported versions.
// 3. Single-number versions that are at the start of the search string
/* The string below matches timestamps that use forwardslashes for date
separators. However, Pseudosem v1.0.1 will only compare the first
two digits as it does not recognise forwardslashes as separators. */
const std::string dateRegex = R"((\d{1,2}/\d{1,2}/\d{1,4} \d{1,2}:\d{1,2}:\d{1,2}))";
R"((?:)"
// The string below matches timestamps that use forwardslashes for date
// separators. However, Pseudosem v1.0.1 will only compare the first
// two digits as it does not recognise forwardslashes as separators.
R"((\d{1,2}/\d{1,2}/\d{1,4} \d{1,2}:\d{1,2}:\d{1,2}))"
R"(|)"
// The string below only allows matches that have one of the following
// conditions:
// * are at the start of the search string
// * are preceded by a "v"
// * are preceded by a whitespace character
R"((?:^|v|\s))"
// The string below matches the range of version strings supported by
// Pseudosem v1.0.1, excluding space separators, as they make version
// extraction from inside sentences very tricky and have not been
// seen "in the wild".
/* The string below matches the range of version strings supported by
Pseudosem v1.0.1, excluding space separators, as they make version
extraction from inside sentences very tricky and have not been
seen "in the wild". */
const std::string pseudosemVersionRegex =
R"((\d+(?:\.\d+)+(?:[-._:]?[A-Za-z0-9]+)*))"
// The string below prevents numbers followed by a comma from matching.
R"((?!,))"
R"(|)"
// The string below matches a number containing one or more digits
// found at the start of the search string.
R"(^(\d+))"
R"())"
,
regex::ECMAScript | regex::icase);
// The string below prevents version numbers followed by a comma from
// matching.
R"((?!,))";
/* There are a few different version formats that can appear in strings
together, and in order to extract the correct one, they must be searched
for in order of priority. */
const vector<regex> Version::versionRegexes({
regex(dateRegex, regex::ECMAScript | regex::icase),
regex(R"(version:?\s)" +
pseudosemVersionRegex, regex::ECMAScript | regex::icase),
regex(R"((?:^|v|\s))" +
pseudosemVersionRegex, regex::ECMAScript | regex::icase),
regex(
/* The string below matches a number containing one or more digits
found at the start of the search string or preceded by 'v'. */
R"((?:^|v)(\d+))", regex::ECMAScript | regex::icase),
});
Version::Version() {}
Version::Version(const std::string& ver) {
smatch what;
if (regex_search(ver, what, versionRegex)) {
for (auto it = next(begin(what)); it != end(what); ++it) {
if (it->str().empty())
continue;
for (const auto& versionRegex : versionRegexes) {
if (regex_search(ver, what, versionRegex)) {
for (auto it = next(begin(what)); it != end(what); ++it) {
if (it->str().empty())
continue;
//Use the first non-empty sub-match.
verString = *it;
boost::trim(verString);
//Use the first non-empty sub-match.
verString = *it;
boost::trim(verString);
return;
}
}
}
}
+1 -1
View File
@@ -47,7 +47,7 @@ namespace loot {
bool operator != (const Version&) const;
private:
std::string verString;
static const std::regex versionRegex;
static const std::vector<std::regex> versionRegexes;
};
}
+20
View File
@@ -144,6 +144,26 @@ namespace loot {
EXPECT_EQ("", Version(testText).AsString());
}
TEST(Version, shouldPreferVersionPrefixedNumbersOverVersionsInSentence) {
// Found in <http://www.nexusmods.com/skyrim/mods/47327>
std::string testText("Requires Skyrim patch 1.9.32.0.8 or greater.\n"
"Requires Unofficial Skyrim Legendary Edition Patch 3.0.0 or greater.\n"
"Version 2.0.0");
EXPECT_EQ("2.0.0", Version(testText).AsString());
}
TEST(Version, shouldExtractSingleDigitVersionPrecededByV) {
// Found in <http://www.nexusmods.com/skyrim/mods/19733>
std::string testText("Immersive Armors v8 Main Plugin");
EXPECT_EQ("8", Version(testText).AsString());
}
TEST(Version, shouldPreferVersionPrefixedNumbersOverVPrefixedNumber) {
// Found in <http://www.nexusmods.com/skyrim/mods/43773>
std::string testText("Compatibility patch for AOS v2.5 and True Storms v1.5 (or later),\nPatch Version: 1.0");
EXPECT_EQ("1.0", Version(testText).AsString());
}
TEST(Version, GreaterThan) {
Version version1, version2;
EXPECT_FALSE(version1 > version2);