diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 90d01b48e9..bf1db8030c 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -263,7 +263,7 @@ void CSMDoc::CollectionReferencesStage::perform(int stage, Messages& messages) std::deque& indices = mState.getSubRecords()[cellId.getRefIdString()]; // collect moved references at the end of the container - bool interior = cellId.getRefIdString()[0] != '#'; + const bool interior = !cellId.startsWith("#"); std::ostringstream stream; if (!interior) { @@ -367,7 +367,7 @@ void CSMDoc::WriteCellCollectionStage::perform(int stage, Messages& messages) || references != mState.getSubRecords().end()) { CSMWorld::Cell cellRecord = cell.get(); - bool interior = cellRecord.mId.getRefIdString()[0] != '#'; + const bool interior = !cellRecord.mId.startsWith("#"); // count new references and adjust RefNumCount accordingsly unsigned int newRefNum = cellRecord.mRefNumCounter; @@ -451,10 +451,9 @@ void CSMDoc::WritePathgridCollectionStage::perform(int stage, Messages& messages if (pathgrid.isModified() || pathgrid.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::Pathgrid record = pathgrid.get(); - std::string recordIdString = record.mId.getRefIdString(); - if (recordIdString[0] == '#') + if (record.mId.startsWith("#")) { - std::istringstream stream(recordIdString.c_str()); + std::istringstream stream(record.mId.getRefIdString()); char ignore; stream >> ignore >> record.mData.mX >> record.mData.mY; } diff --git a/apps/opencs/model/tools/gmstcheck.cpp b/apps/opencs/model/tools/gmstcheck.cpp index 8b7390387e..858bc2c3ec 100644 --- a/apps/opencs/model/tools/gmstcheck.cpp +++ b/apps/opencs/model/tools/gmstcheck.cpp @@ -47,7 +47,7 @@ void CSMTools::GmstCheckStage::perform(int stage, CSMDoc::Messages& messages) // Checking type and limits // optimization - compare it to lists based on naming convention (f-float,i-int,s-string) - if (gmstIdString[0] == 'f') + if (gmst.mId.startsWith("f")) { for (size_t i = 0; i < CSMWorld::DefaultGmsts::FloatCount; ++i) { @@ -74,7 +74,7 @@ void CSMTools::GmstCheckStage::perform(int stage, CSMDoc::Messages& messages) } } } - else if (gmstIdString[0] == 'i') + else if (gmst.mId.startsWith("i")) { for (size_t i = 0; i < CSMWorld::DefaultGmsts::IntCount; ++i) { @@ -101,7 +101,7 @@ void CSMTools::GmstCheckStage::perform(int stage, CSMDoc::Messages& messages) } } } - else if (gmstIdString[0] == 's') + else if (gmst.mId.startsWith("s")) { for (size_t i = 0; i < CSMWorld::DefaultGmsts::StringCount; ++i) { diff --git a/apps/opencs/model/world/pathgrid.cpp b/apps/opencs/model/world/pathgrid.cpp index 78191d9b58..27d2b29600 100644 --- a/apps/opencs/model/world/pathgrid.cpp +++ b/apps/opencs/model/world/pathgrid.cpp @@ -9,7 +9,7 @@ void CSMWorld::Pathgrid::load(ESM::ESMReader& esm, bool& isDeleted, const IdColl load(esm, isDeleted); // correct ID - if (!mId.empty() && mId.getRefIdString()[0] != '#' && cells.searchId(mId) == -1) + if (!mId.empty() && !mId.startsWith("#") && cells.searchId(mId) == -1) { std::ostringstream stream; stream << "#" << mData.mX << " " << mData.mY; diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 76bb7f2c29..51a4241cbf 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -243,7 +243,7 @@ namespace MWClass bool Miscellaneous::isSoulGem(const MWWorld::ConstPtr& ptr) const { - return Misc::StringUtils::ciStartsWith(ptr.getCellRef().getRefId().getRefIdString(), "misc_soulgem"); + return ptr.getCellRef().getRefId().startsWith("misc_soulgem"); } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 04dcbe7334..27ca299ac1 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1447,7 +1447,7 @@ namespace MWClass bool Npc::isClass(const MWWorld::ConstPtr& ptr, std::string_view className) const { - return Misc::StringUtils::ciEqual(ptr.get()->mBase->mClass.getRefIdString(), className); + return ptr.get()->mBase->mClass == className; } bool Npc::canSwim(const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 2e7581c469..c97ef43e7c 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -856,7 +856,7 @@ namespace MWMechanics for (const ESM::GameSetting& currentSetting : gameSettings) { // Don't bother checking this GMST if it's not a sMagicBound* one. - if (!Misc::StringUtils::ciStartsWith(currentSetting.mId.getRefIdString(), "smagicbound")) + if (!currentSetting.mId.startsWith("smagicbound")) continue; // All sMagicBound* GMST's should be of type string diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 7d2a9ec0a4..1025c21621 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -526,8 +526,7 @@ namespace MWRender addAnimSource(smodel, smodel); - if (!isWerewolf - && Misc::StringUtils::lowerCase(mNpc->mRace.getRefIdString()).find("argonian") != std::string::npos) + if (!isWerewolf && mNpc->mRace.contains("argonian")) addAnimSource("meshes\\xargonian_swimkna.nif", smodel); } else diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index d3aa2a5ca9..866fa66440 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -153,7 +153,7 @@ namespace MWWorld { std::vector results; std::copy_if(mShared.begin(), mShared.end(), std::back_inserter(results), - [prefix](const T* item) { return Misc::StringUtils::ciStartsWith(item->mId.getRefIdString(), prefix); }); + [prefix](const T* item) { return item->mId.startsWith(prefix); }); if (!results.empty()) return results[Misc::Rng::rollDice(results.size(), prng)]; return nullptr; diff --git a/apps/openmw_test_suite/misc/test_stringops.cpp b/apps/openmw_test_suite/misc/test_stringops.cpp index 7ef13b3541..2cfe53b1dd 100644 --- a/apps/openmw_test_suite/misc/test_stringops.cpp +++ b/apps/openmw_test_suite/misc/test_stringops.cpp @@ -171,6 +171,11 @@ namespace EXPECT_FALSE(ciStartsWith("foo", "foo bar")); } + TEST(MiscStringsCiStartsWith, should_be_case_insensitive) + { + EXPECT_TRUE(ciStartsWith("foo bar", "FOO")); + } + TEST(MiscStringsFormat, string_format) { std::string f = "1%s2"; @@ -192,4 +197,39 @@ namespace EXPECT_EQ(Misc::StringUtils::format(f, view.substr(1, 1)), "122"); EXPECT_EQ(Misc::StringUtils::format(f, view.substr(2)), "12"); } + + TEST(MiscStringsCiFind, should_return_zero_for_2_empty_strings) + { + EXPECT_EQ(ciFind(std::string_view(), std::string_view()), 0); + } + + TEST(MiscStringsCiFind, should_return_zero_when_looking_for_empty_string) + { + EXPECT_EQ(ciFind("foo", std::string_view()), 0); + } + + TEST(MiscStringsCiFind, should_return_npos_for_longer_substring) + { + EXPECT_EQ(ciFind("a", "aa"), std::string_view::npos); + } + + TEST(MiscStringsCiFind, should_return_zero_for_the_same_string) + { + EXPECT_EQ(ciFind("foo", "foo"), 0); + } + + TEST(MiscStringsCiFind, should_return_first_position_of_substring) + { + EXPECT_EQ(ciFind("foobar foobar", "bar"), 3); + } + + TEST(MiscStringsCiFind, should_be_case_insensitive) + { + EXPECT_EQ(ciFind("foobar", "BAR"), 3); + } + + TEST(MiscStringsCiFind, should_return_npos_for_absent_substring) + { + EXPECT_EQ(ciFind("foobar", "baz"), std::string_view::npos); + } } diff --git a/components/esm/refid.cpp b/components/esm/refid.cpp index 48a06ea6e5..27d3bb6d9d 100644 --- a/components/esm/refid.cpp +++ b/components/esm/refid.cpp @@ -44,6 +44,16 @@ namespace ESM return Misc::StringUtils::ciEqual(mId, rhs); } + bool RefId::startsWith(std::string_view prefix) const + { + return Misc::StringUtils::ciStartsWith(mId, prefix); + } + + bool RefId::contains(std::string_view subString) const + { + return Misc::StringUtils::ciFind(mId, subString) != std::string_view::npos; + } + const RefId RefId::sEmpty = {}; } diff --git a/components/esm/refid.hpp b/components/esm/refid.hpp index 9a538146db..0d297cfb0f 100644 --- a/components/esm/refid.hpp +++ b/components/esm/refid.hpp @@ -29,6 +29,10 @@ namespace ESM bool empty() const { return mId.empty(); } + bool startsWith(std::string_view prefix) const; + + bool contains(std::string_view subString) const; + bool operator==(const RefId& rhs) const; bool operator==(std::string_view rhs) const; diff --git a/components/misc/strings/algorithm.hpp b/components/misc/strings/algorithm.hpp index 885b9e872e..f34801b8d3 100644 --- a/components/misc/strings/algorithm.hpp +++ b/components/misc/strings/algorithm.hpp @@ -176,6 +176,16 @@ namespace Misc::StringUtils return; str.replace(pos, substr.size(), with); } + + inline std::string_view::size_type ciFind(std::string_view str, std::string_view substr) + { + if (str.size() < substr.size()) + return std::string_view::npos; + for (std::string_view::size_type i = 0, n = str.size() - substr.size() + 1; i < n; ++i) + if (ciEqual(str.substr(i, substr.size()), substr)) + return i; + return std::string_view::npos; + } } #endif