From 04f7a8c466758fda7bbcf3fc67d903dc44cef835 Mon Sep 17 00:00:00 2001 From: Tracy Ward Date: Sat, 12 Oct 2019 11:41:39 -0400 Subject: [PATCH] Rewrite BG/FG changes parsing (#1916) * Handle commas and equals in BG/FG change tags Rewrote parsing of BGCHANGES and FGCHANGES tags to be able to handle people putting commas and equals in the filenames. For the value positions which can be filenames, check to see if we can recognize any filename from the song directory followed by a comma, equal, or end of string. If we can, use that. If not, just treat it the same as before, looking for the next comma or equal. --- src/NotesLoaderSM.cpp | 190 +++++++++++++++++++++++++++++++++++++---- src/NotesLoaderSM.h | 9 +- src/NotesLoaderSMA.cpp | 10 +-- src/NotesLoaderSSC.cpp | 10 +-- 4 files changed, 192 insertions(+), 27 deletions(-) diff --git a/src/NotesLoaderSM.cpp b/src/NotesLoaderSM.cpp index cd6ca59d90..8f2c219912 100644 --- a/src/NotesLoaderSM.cpp +++ b/src/NotesLoaderSM.cpp @@ -166,14 +166,14 @@ void SMSetBGChanges(SMSongTagInfo& info) } void SMSetFGChanges(SMSongTagInfo& info) { - vector aFGChangeExpressions; - split((*info.params)[1], ",", aFGChangeExpressions); + std::vector > aFGChanges; + info.loader->ParseBGChangesString((*info.params)[1], aFGChanges, info.song->GetSongDir()); - for(unsigned int b = 0; b < aFGChangeExpressions.size(); ++b) + for (const auto &b : aFGChanges) { BackgroundChange change; - if(info.loader->LoadFromBGChangesString(change, aFGChangeExpressions[b])) - info.song->AddForegroundChange(change); + if (info.loader->LoadFromBGChangesVector(change, b)) + info.song->AddForegroundChange(change); } } void SMSetKeysounds(SMSongTagInfo& info) @@ -356,14 +356,14 @@ void SMLoader::ProcessBGChanges( Song &out, const RString &sValueName, const RSt } else { - vector aBGChangeExpressions; - split( sParam, ",", aBGChangeExpressions ); - - for( unsigned b=0; b > aBGChanges; + ParseBGChangesString(sParam, aBGChanges, out.GetSongDir()); + + for (const auto &b : aBGChanges) { BackgroundChange change; - if( LoadFromBGChangesString( change, aBGChangeExpressions[b] ) ) - out.AddBackgroundChange( iLayer, change ); + if(LoadFromBGChangesVector( change, b)) + out.AddBackgroundChange(iLayer, change); } } } @@ -937,11 +937,8 @@ void SMLoader::ProcessFakes( TimingData &out, const RString line, const int rows } } -bool SMLoader::LoadFromBGChangesString( BackgroundChange &change, const RString &sBGChangeExpression ) +bool SMLoader::LoadFromBGChangesVector( BackgroundChange &change, std::vector aBGChangeValues ) { - vector aBGChangeValues; - split( sBGChangeExpression, "=", aBGChangeValues, false ); - aBGChangeValues.resize( min((int)aBGChangeValues.size(),11) ); switch( aBGChangeValues.size() ) @@ -1354,6 +1351,169 @@ void SMLoader::TidyUpData( Song &song, bool bFromCache ) } } +std::vector SMLoader::GetSongDirFiles(const RString &sSongDir) +{ + if (!m_SongDirFiles.empty()) + return m_SongDirFiles; + + ASSERT(!sSongDir.empty()); + + std::vector vsDirs; + vsDirs.push_back(sSongDir); + + while (!vsDirs.empty()) + { + RString d = vsDirs.back(); + vsDirs.pop_back(); + + std::vector vsFiles; + GetDirListing(d+"*", vsFiles, false, true); + + for (const RString& f : vsFiles) + { + if (IsADirectory(f)) + vsDirs.push_back(f+"/"); + + m_SongDirFiles.push_back(f.substr(sSongDir.size())); + } + } + + return m_SongDirFiles; +} + +void SMLoader::ParseBGChangesString(const RString& _sChanges, std::vector > &vvsAddTo, const RString& sSongDir) +{ + // short circuit: empty string + if (_sChanges.empty()) + return; + + // strip newlines (basically operates as both split and join at the same time) + RString sChanges; + size_t start = 0; + do { + size_t pos = _sChanges.find_first_of("\r\n", start); + if (RString::npos == pos) + pos = _sChanges.size(); + + if (pos - start > 0) { + if ((start == 0) && (pos == _sChanges.size())) + sChanges = _sChanges; + else + sChanges += _sChanges.substr(start, pos - start); + } + start = pos + 1; + } while (start <= _sChanges.size()); + + // after removing newlines, do we have anything? + if (sChanges.empty()) + return; + + // get the list of possible files/directories for the file parameters + std::vector vsFiles = GetSongDirFiles(sSongDir); + + start = 0; + int pnum = 0; + do { + switch (pnum) { + // parameters 1 and 7 can be files or folder names + case 1: + case 7: + { + // see if one of the files in the song directory are listed. + RString found; + for (const auto& f : vsFiles) + { + // there aren't enough characters for this to match + if ((sChanges.size() - start) < f.size()) + continue; + + // the string itself matches + if (f.EqualsNoCase(sChanges.substr(start, f.size()).c_str())) + { + size_t nextpos = start + f.size(); + + // is this name followed by end-of-string, equals, or comma? + if ((nextpos == sChanges.size()) || (sChanges[nextpos] == '=') || (sChanges[nextpos] == ',')) + { + found = f; + break; + } + } + } + // yes. use that as this parameter, even if it has commas or equals signs in it + if (!found.empty()) + { + vvsAddTo.back().push_back(found); + start += found.size(); + // the next character should be a comma or equals. skip it + if (start < sChanges.size()) + { + if (sChanges[start] == '=') + ++pnum; + else + { + ASSERT(sChanges[start] == ','); + pnum = 0; + } + start += 1; + } + // move to the next parameter + break; + } + // deliberate fall-through if not found. treat it as a normal string like before + } + // everything else should be safe + default: + if(0 == pnum) vvsAddTo.push_back(std::vector()); // first value of this set. create our vector + + { + size_t eqpos = sChanges.find('=', start); + size_t compos = sChanges.find(',', start); + + if ((eqpos == RString::npos) && (compos == RString::npos)) + { + // neither = nor , were found in the remainder of the string. consume the rest of the string. + vvsAddTo.back().push_back(sChanges.substr(start)); + start = sChanges.size() + 1; + } + else if ((eqpos != RString::npos) && (compos != RString::npos)) + { + // both were found. which came first? + if (eqpos < compos) + { + // equals. consume value and move to next value + vvsAddTo.back().push_back(sChanges.substr(start, eqpos - start)); + start = eqpos + 1; + ++pnum; + } + else + { + // comma. consume value and move to next set + vvsAddTo.back().push_back(sChanges.substr(start, compos - start)); + start = compos + 1; + pnum = 0; + } + } + else if (eqpos != RString::npos) + { + // found only equals. consume and move on. + vvsAddTo.back().push_back(sChanges.substr(start, eqpos - start)); + start = eqpos + 1; + ++pnum; + } + else + { + // only foudn comma. consume and move on. + vvsAddTo.back().push_back(sChanges.substr(start, compos - start)); + start = compos + 1; + pnum = 0; + } + break; + } + } + } while (start <= sChanges.size()); +} + /* * (c) 2001-2004 Chris Danford, Glenn Maynard * All rights reserved. diff --git a/src/NotesLoaderSM.h b/src/NotesLoaderSM.h index 9d6ea557da..72de0e15ce 100644 --- a/src/NotesLoaderSM.h +++ b/src/NotesLoaderSM.h @@ -65,8 +65,7 @@ struct SMLoader virtual bool LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong, Song *givenSong=nullptr ); virtual bool LoadEditFromBuffer( const RString &sBuffer, const RString &sEditFilePath, ProfileSlot slot, Song *givenSong=nullptr ); virtual bool LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong, Song *givenSong=nullptr ); - virtual bool LoadFromBGChangesString(BackgroundChange &change, - const RString &sBGChangeExpression ); + virtual bool LoadFromBGChangesVector(BackgroundChange &change, std::vector aBGChangeValues); /** * @brief Parse BPM Changes data from a string. @@ -153,6 +152,8 @@ struct SMLoader virtual void ProcessBGChanges( Song &out, const RString &sValueName, const RString &sPath, const RString &sParam ); + + virtual void ParseBGChangesString(const RString& _sChanges, std::vector > &vvsAddTo, const RString &sSongDir); /** * @brief Put the attacks in the attacks string. @@ -199,6 +200,8 @@ protected: * @return the file extension. */ RString GetFileExtension() const { return fileExt; } + std::vector GetSongDirFiles(const RString &sSongDir); + public: // SetSongTitle and GetSongTitle changed to public to allow the functions // used by the parser helper to access them. -Kyz @@ -217,6 +220,8 @@ private: const RString fileExt; /** @brief The song title that is being processed. */ RString songTitle; + + std::vector m_SongDirFiles; }; #endif diff --git a/src/NotesLoaderSMA.cpp b/src/NotesLoaderSMA.cpp index 1179163d7c..f60090ed90 100644 --- a/src/NotesLoaderSMA.cpp +++ b/src/NotesLoaderSMA.cpp @@ -344,14 +344,14 @@ bool SMALoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach else if( sValueName=="FGCHANGES" ) { - vector aFGChangeExpressions; - split( sParams[1], ",", aFGChangeExpressions ); + std::vector > aFGChanges; + ParseBGChangesString(sParams[1], aFGChanges, out.GetSongDir()); - for( unsigned b=0; b aFGChangeExpressions; - split((*info.params)[1], ",", aFGChangeExpressions); + std::vector > aFGChanges; + info.loader->ParseBGChangesString((*info.params)[1], aFGChanges, info.song->GetSongDir()); - for(size_t b = 0; b < aFGChangeExpressions.size(); ++b) + for (const auto &b : aFGChanges) { BackgroundChange change; - if(info.loader->LoadFromBGChangesString(change, aFGChangeExpressions[b])) - { info.song->AddForegroundChange(change); } + if (info.loader->LoadFromBGChangesVector(change, b)) + info.song->AddForegroundChange(change); } } void SetKeysounds(SongTagInfo& info)