Escape special characters for like sql searches
This commit is contained in:
@@ -251,7 +251,7 @@ guessMediaFileFormat(const std::filesystem::path& file)
|
||||
if (formats.size() > 1)
|
||||
LMS_LOG(AV, INFO) << "File '" << file.string() << "' reported several formats: '" << format->name << "'";
|
||||
|
||||
std::vector<std::string> mimeTypes;
|
||||
std::vector<std::string_view> mimeTypes;
|
||||
if (format->mime_type)
|
||||
mimeTypes = StringUtils::splitString(format->mime_type, ",");
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ add_library(lmsdatabase SHARED
|
||||
impl/Track.cpp
|
||||
impl/TrackBookmark.cpp
|
||||
impl/User.cpp
|
||||
impl/Utils.cpp
|
||||
)
|
||||
|
||||
target_include_directories(lmsdatabase INTERFACE
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include "database/User.hpp"
|
||||
#include "utils/Logger.hpp"
|
||||
#include "SqlQuery.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Database
|
||||
{
|
||||
@@ -83,7 +83,7 @@ Wt::Dbo::Query<T>
|
||||
createQuery(Session& session,
|
||||
const std::string& queryStr,
|
||||
const std::set<IdType>& clusterIds,
|
||||
const std::vector<std::string>& keywords,
|
||||
const std::vector<std::string_view>& keywords,
|
||||
std::optional<TrackArtistLinkType> linkType)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
@@ -100,16 +100,16 @@ createQuery(Session& session,
|
||||
std::vector<std::string> clauses;
|
||||
std::vector<std::string> sortClauses;
|
||||
|
||||
for (const std::string& keyword : keywords)
|
||||
for (std::string_view keyword : keywords)
|
||||
{
|
||||
clauses.push_back("a.name LIKE ?");
|
||||
query.bind("%%" + keyword + "%%");
|
||||
clauses.push_back("a.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'");
|
||||
query.bind("%%" + escapeLikeKeyword(keyword) + "%%");
|
||||
}
|
||||
|
||||
for (const std::string& keyword : keywords)
|
||||
for (std::string_view keyword : keywords)
|
||||
{
|
||||
sortClauses.push_back("a.sort_name LIKE ?");
|
||||
query.bind("%%" + keyword + "%%");
|
||||
sortClauses.push_back("a.sort_name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'");
|
||||
query.bind("%%" + escapeLikeKeyword(keyword) + "%%");
|
||||
}
|
||||
|
||||
query.where("(" + StringUtils::joinStrings(clauses, " AND ") + ") OR (" + StringUtils::joinStrings(sortClauses, " AND ") + ")");
|
||||
@@ -267,7 +267,7 @@ Artist::getByClusters(Session& session, const std::set<IdType>& clusters, SortMe
|
||||
std::vector<Artist::pointer>
|
||||
Artist::getByFilter(Session& session,
|
||||
const std::set<IdType>& clusters,
|
||||
const std::vector<std::string>& keywords,
|
||||
const std::vector<std::string_view>& keywords,
|
||||
std::optional<TrackArtistLinkType> linkType,
|
||||
SortMethod sortMethod,
|
||||
std::optional<Range> range,
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "database/User.hpp"
|
||||
#include "utils/Logger.hpp"
|
||||
#include "SqlQuery.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Database
|
||||
{
|
||||
@@ -38,14 +39,14 @@ Wt::Dbo::Query<T>
|
||||
createQuery(Session& session,
|
||||
const std::string& queryStr,
|
||||
const std::set<IdType>& clusterIds,
|
||||
const std::vector<std::string>& keywords)
|
||||
const std::vector<std::string_view>& keywords)
|
||||
{
|
||||
|
||||
auto query {session.getDboSession().query<T>(queryStr)};
|
||||
query.join("track t ON t.release_id = r.id");
|
||||
|
||||
for (const std::string& keyword : keywords)
|
||||
query.where("r.name LIKE ?").bind("%%" + keyword + "%%");
|
||||
for (std::string_view keyword : keywords)
|
||||
query.where("r.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%%" + escapeLikeKeyword(keyword) + "%%");
|
||||
|
||||
if (!clusterIds.empty())
|
||||
{
|
||||
@@ -300,7 +301,7 @@ Release::getByClusters(Session& session, const std::set<IdType>& clusters)
|
||||
std::vector<Release::pointer>
|
||||
Release::getByFilter(Session& session,
|
||||
const std::set<IdType>& clusterIds,
|
||||
const std::vector<std::string>& keywords,
|
||||
const std::vector<std::string_view>& keywords,
|
||||
std::optional<Range> range,
|
||||
bool& moreResults)
|
||||
{
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
#include "SqlQuery.hpp"
|
||||
#include "StringViewTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Database {
|
||||
|
||||
@@ -40,14 +41,14 @@ Wt::Dbo::Query<T>
|
||||
createQuery(Session& session,
|
||||
const std::string& queryStr,
|
||||
const std::set<IdType>& clusterIds,
|
||||
const std::vector<std::string>& keywords)
|
||||
const std::vector<std::string_view>& keywords)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
|
||||
auto query {session.getDboSession().query<T>(queryStr)};
|
||||
|
||||
for (const std::string& keyword : keywords)
|
||||
query.where("t.name LIKE ?").bind("%%" + keyword + "%%");
|
||||
for (std::string_view keyword : keywords)
|
||||
query.where("t.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%%" + escapeLikeKeyword(keyword) + "%%");
|
||||
|
||||
if (!clusterIds.empty())
|
||||
{
|
||||
@@ -333,7 +334,7 @@ Track::hasTrackFeatures() const
|
||||
std::vector<Track::pointer>
|
||||
Track::getByFilter(Session& session,
|
||||
const std::set<IdType>& clusterIds,
|
||||
const std::vector<std::string>& keywords,
|
||||
const std::vector<std::string_view>& keywords,
|
||||
std::optional<Range> range,
|
||||
bool& moreResults)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include "utils/String.hpp"
|
||||
|
||||
namespace Database
|
||||
{
|
||||
std::string
|
||||
escapeLikeKeyword(std::string_view keyword)
|
||||
{
|
||||
return StringUtils::escapeString(keyword, "*_", escapeChar);
|
||||
}
|
||||
|
||||
} // namespace Database
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace Database
|
||||
{
|
||||
#define ESCAPE_CHAR_STR "\\"
|
||||
static constexpr char escapeChar {'\\'};
|
||||
std::string escapeLikeKeyword(std::string_view keywords);
|
||||
|
||||
} // namespace Database
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
@@ -69,7 +70,7 @@ class Artist : public Wt::Dbo::Dbo<Artist>
|
||||
);
|
||||
static std::vector<pointer> getByFilter(Session& session,
|
||||
const std::set<IdType>& clusters, // if non empty, at least one artist that belongs to these clusters
|
||||
const std::vector<std::string>& keywords, // if non empty, name must match all of these keywords (name + sort name fields)
|
||||
const std::vector<std::string_view>& keywords, // if non empty, name must match all of these keywords (name + sort name fields)
|
||||
std::optional<TrackArtistLinkType> linkType, // if set, only artists that have produced at least one track with this link type
|
||||
SortMethod sortMethod,
|
||||
std::optional<Range> range,
|
||||
|
||||
@@ -66,7 +66,7 @@ class Release : public Wt::Dbo::Dbo<Release>
|
||||
static std::vector<pointer> getByClusters(Session& session, const std::set<IdType>& clusters);
|
||||
static std::vector<pointer> getByFilter(Session& session,
|
||||
const std::set<IdType>& clusters, // if non empty, at least one release that belongs to these clusters
|
||||
const std::vector<std::string>& keywords, // if non empty, name must match all of these keywords
|
||||
const std::vector<std::string_view>& keywords, // if non empty, name must match all of these keywords
|
||||
std::optional<Range> range,
|
||||
bool& moreExpected);
|
||||
static std::vector<IdType> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
|
||||
|
||||
@@ -71,7 +71,7 @@ class Track : public Wt::Dbo::Dbo<Track>
|
||||
const std::set<IdType>& clusters); // tracks that belong to these clusters
|
||||
static std::vector<pointer> getByFilter(Session& session,
|
||||
const std::set<IdType>& clusters, // if non empty, tracks that belong to these clusters
|
||||
const std::vector<std::string>& keywords, // if non empty, name must match all of these keywords
|
||||
const std::vector<std::string_view>& keywords, // if non empty, name must match all of these keywords
|
||||
std::optional<Range> range,
|
||||
bool& moreExpected);
|
||||
static std::vector<pointer> getByNameAndReleaseName(Session& session, std::string_view trackName, std::string_view releaseName);
|
||||
|
||||
@@ -49,10 +49,10 @@ findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initiali
|
||||
if (!str)
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<std::string> strUuids = StringUtils::splitString(*str, "/");
|
||||
const std::vector<std::string_view> strUuids {StringUtils::splitString(*str, "/")};
|
||||
std::vector<UUID> res;
|
||||
|
||||
for (const std::string& strUuid : strUuids)
|
||||
for (std::string_view strUuid : strUuids)
|
||||
{
|
||||
std::optional<UUID> uuid {UUID::fromString(strUuid)};
|
||||
if (!uuid)
|
||||
@@ -101,7 +101,7 @@ getArtists(const Av::IAudioFile::MetadataMap& metadataMap)
|
||||
{
|
||||
std::vector<Artist> artists;
|
||||
|
||||
std::vector<std::string> artistNames;
|
||||
std::vector<std::string_view> artistNames;
|
||||
if (metadataMap.find("ARTISTS") != metadataMap.end())
|
||||
{
|
||||
artistNames = StringUtils::splitString(metadataMap.find("ARTISTS")->second, "/;");
|
||||
@@ -161,8 +161,7 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
|
||||
else if (tag == "TRACK")
|
||||
{
|
||||
// Expecting 'Number/Total'
|
||||
std::vector<std::string> strings {StringUtils::splitString(value, "/") };
|
||||
|
||||
const std::vector<std::string_view> strings {StringUtils::splitString(value, "/") };
|
||||
if (strings.size() > 0)
|
||||
{
|
||||
track.trackNumber = StringUtils::readAs<std::size_t>(strings[0]);
|
||||
@@ -174,8 +173,7 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
|
||||
else if (tag == "DISC")
|
||||
{
|
||||
// Expecting 'Number/Total'
|
||||
std::vector<std::string> strings {StringUtils::splitString(value, "/")};
|
||||
|
||||
const std::vector<std::string_view> strings {StringUtils::splitString(value, "/")};
|
||||
if (strings.size() > 0)
|
||||
{
|
||||
track.discNumber = StringUtils::readAs<std::size_t>(strings[0]);
|
||||
@@ -217,10 +215,16 @@ AvFormatParser::parse(const std::filesystem::path& p, bool debug)
|
||||
}
|
||||
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
||||
{
|
||||
std::vector<std::string> clusterNames {StringUtils::splitString(value, "/,;")};
|
||||
const std::vector<std::string_view> clusterNames {StringUtils::splitString(value, "/,;")};
|
||||
|
||||
if (!clusterNames.empty())
|
||||
track.clusters[tag] = std::set<std::string>{clusterNames.begin(), clusterNames.end()};
|
||||
{
|
||||
std::set<std::string> values;
|
||||
std::transform(std::cbegin(clusterNames), std::cend(clusterNames),
|
||||
std::inserter(values, std::begin(values)),
|
||||
[](std::string_view clusterName) { return std::string {clusterName}; });
|
||||
track.clusters[tag] = std::move(values);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -78,12 +78,12 @@ getPropertyValuesAs(const TagLib::PropertyMap& properties, const std::string& ke
|
||||
|
||||
static
|
||||
std::vector<std::string>
|
||||
splitAndTrimString(const std::string& str, const std::string& delimiters)
|
||||
splitAndTrimString(const std::string& str, std::string_view delimiters)
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
|
||||
std::vector<std::string> strings {StringUtils::splitString(str, delimiters)};
|
||||
for (const std::string& s : strings)
|
||||
std::vector<std::string_view> strings {StringUtils::splitString(str, delimiters)};
|
||||
for (std::string_view s : strings)
|
||||
res.emplace_back(StringUtils::stringTrim(s));
|
||||
|
||||
return res;
|
||||
@@ -203,8 +203,7 @@ TagLibParser::processTag(Track& track, const std::string& tag, const TagLib::Str
|
||||
else if (tag == "DISCNUMBER")
|
||||
{
|
||||
// Expecting 'Number/Total'
|
||||
std::vector<std::string> strings {StringUtils::splitString(value, "/")};
|
||||
|
||||
std::vector<std::string_view> strings {StringUtils::splitString(value, "/")};
|
||||
if (!strings.empty())
|
||||
{
|
||||
track.discNumber = StringUtils::readAs<std::size_t>(strings[0]);
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace StringUtils
|
||||
{
|
||||
template<>
|
||||
std::optional<std::chrono::seconds>
|
||||
readAs(const std::string& str)
|
||||
readAs(std::string_view str)
|
||||
{
|
||||
std::optional<std::chrono::seconds> res;
|
||||
|
||||
|
||||
@@ -23,14 +23,14 @@ namespace StringUtils
|
||||
{
|
||||
template<>
|
||||
std::optional<API::Subsonic::Id>
|
||||
readAs(const std::string& str)
|
||||
readAs(std::string_view str)
|
||||
{
|
||||
return API::Subsonic::IdFromString(str);
|
||||
}
|
||||
|
||||
template<>
|
||||
std::optional<bool>
|
||||
readAs(const std::string& str)
|
||||
readAs(std::string_view str)
|
||||
{
|
||||
if (str == "true")
|
||||
return true;
|
||||
|
||||
@@ -90,10 +90,10 @@ namespace StringUtils
|
||||
{
|
||||
template<>
|
||||
std::optional<API::Subsonic::Id>
|
||||
readAs(const std::string& str);
|
||||
readAs(std::string_view str);
|
||||
|
||||
template<>
|
||||
std::optional<bool>
|
||||
readAs(const std::string& str);
|
||||
readAs(std::string_view str);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,12 +28,12 @@ namespace API::Subsonic
|
||||
{
|
||||
|
||||
std::optional<Id>
|
||||
IdFromString(const std::string& id)
|
||||
IdFromString(const std::string_view id)
|
||||
{
|
||||
if (id == "root")
|
||||
return Id {Id::Type::Root};
|
||||
|
||||
std::vector<std::string> values {StringUtils::splitString(id, "-")};
|
||||
std::vector<std::string_view> values {StringUtils::splitString(id, "-")};
|
||||
if (values.size() != 2)
|
||||
return std::nullopt;
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ struct Id
|
||||
Database::IdType value {};
|
||||
};
|
||||
|
||||
std::optional<Id> IdFromString(const std::string& id);
|
||||
std::optional<Id> IdFromString(std::string_view id);
|
||||
std::string IdToString(const Id& id);
|
||||
|
||||
} // namespace API::Subsonic
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace StringUtils
|
||||
{
|
||||
template<>
|
||||
std::optional<API::Subsonic::ClientVersion>
|
||||
readAs(const std::string& str)
|
||||
readAs(std::string_view str)
|
||||
{
|
||||
// Expects "X.Y.Z"
|
||||
const auto numbers {StringUtils::splitString(str, ".")};
|
||||
@@ -1454,7 +1454,7 @@ handleSearchRequestCommon(RequestContext& context, bool id3)
|
||||
// Mandatory params
|
||||
std::string query {getMandatoryParameterAs<std::string>(context.parameters, "query")};
|
||||
|
||||
std::vector<std::string> keywords {StringUtils::splitString(query, " ")};
|
||||
std::vector<std::string_view> keywords {StringUtils::splitString(query, " ")};
|
||||
|
||||
// Optional params
|
||||
std::size_t artistCount {getParameterAs<std::size_t>(context.parameters, "artistCount").value_or(20)};
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "utils/String.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -57,13 +58,13 @@ readList(const std::string& str, const std::string& separators, std::list<std::s
|
||||
|
||||
template<>
|
||||
std::optional<std::string>
|
||||
readAs(const std::string& str)
|
||||
readAs(std::string_view str)
|
||||
{
|
||||
return str;
|
||||
return std::string {str};
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
splitString(const std::string& string, const std::string& separators)
|
||||
splitStringCopy(std::string_view string, std::string_view separators)
|
||||
{
|
||||
std::string str {stringTrim(string, separators)};
|
||||
|
||||
@@ -73,6 +74,29 @@ splitString(const std::string& string, const std::string& separators)
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<std::string_view>
|
||||
splitString(std::string_view str, std::string_view separators)
|
||||
{
|
||||
std::vector<std::string_view> res;
|
||||
|
||||
std::string_view::size_type strBegin {};
|
||||
|
||||
while ((strBegin = str.find_first_not_of(separators, strBegin)) != std::string_view::npos)
|
||||
{
|
||||
auto strEnd {str.find_first_of(separators, strBegin + 1)};
|
||||
if (strEnd == std::string_view::npos)
|
||||
{
|
||||
res.push_back(str.substr(strBegin, str.size() - strBegin));
|
||||
break;
|
||||
}
|
||||
|
||||
res.push_back(str.substr(strBegin, strEnd - strBegin));
|
||||
strBegin = strEnd + 1;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string
|
||||
joinStrings(const std::vector<std::string>& strings, const std::string& delimiter)
|
||||
{
|
||||
@@ -80,22 +104,22 @@ joinStrings(const std::vector<std::string>& strings, const std::string& delimite
|
||||
}
|
||||
|
||||
std::string
|
||||
stringTrim(const std::string& str, const std::string& whitespace)
|
||||
stringTrim(std::string_view str, std::string_view whitespaces)
|
||||
{
|
||||
const auto strBegin = str.find_first_not_of(whitespace);
|
||||
if (strBegin == std::string::npos)
|
||||
const auto strBegin = str.find_first_not_of(whitespaces);
|
||||
if (strBegin == std::string_view::npos)
|
||||
return ""; // no content
|
||||
|
||||
const auto strEnd = str.find_last_not_of(whitespace);
|
||||
const auto strEnd = str.find_last_not_of(whitespaces);
|
||||
const auto strRange = strEnd - strBegin + 1;
|
||||
|
||||
return str.substr(strBegin, strRange);
|
||||
return std::string {str.substr(strBegin, strRange)};
|
||||
}
|
||||
|
||||
std::string
|
||||
stringTrimEnd(const std::string& str, const std::string& whitespace)
|
||||
stringTrimEnd(std::string_view str, std::string_view whitespaces)
|
||||
{
|
||||
return str.substr(0, str.find_last_not_of(whitespace)+1);
|
||||
return std::string {str.substr(0, str.find_last_not_of(whitespaces) + 1)};
|
||||
}
|
||||
|
||||
std::string
|
||||
@@ -185,6 +209,23 @@ jsEscape(const std::string& str)
|
||||
return escaped;
|
||||
}
|
||||
|
||||
std::string
|
||||
escapeString(std::string_view str, std::string_view charsToEscape, char escapeChar)
|
||||
{
|
||||
std::string res;
|
||||
res.reserve(str.size());
|
||||
|
||||
for (const char c : str)
|
||||
{
|
||||
if (std::any_of(std::cbegin(charsToEscape), std::cend(charsToEscape), [c](char charToEscape) { return c == charToEscape; }))
|
||||
res += escapeChar;
|
||||
|
||||
res += c;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
stringEndsWith(const std::string& str, const std::string& ending)
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace StringUtils
|
||||
{
|
||||
template <>
|
||||
std::optional<UUID>
|
||||
readAs(const std::string& str)
|
||||
readAs(std::string_view str)
|
||||
{
|
||||
return UUID::fromString(str);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -31,16 +32,19 @@
|
||||
namespace StringUtils {
|
||||
|
||||
std::vector<std::string>
|
||||
splitString(const std::string& string, const std::string& separators);
|
||||
splitStringCopy(std::string_view string, std::string_view separators);
|
||||
|
||||
std::vector<std::string_view>
|
||||
splitString(std::string_view string, std::string_view separators);
|
||||
|
||||
std::string
|
||||
joinStrings(const std::vector<std::string>& strings, const std::string& delimiter);
|
||||
|
||||
std::string
|
||||
stringTrim(const std::string& str, const std::string& whitespaces = " \t");
|
||||
stringTrim(std::string_view str, std::string_view whitespaces = " \t");
|
||||
|
||||
std::string
|
||||
stringTrimEnd(const std::string& str, const std::string& whitespaces = " \t");
|
||||
stringTrimEnd(std::string_view str, std::string_view whitespaces = " \t");
|
||||
|
||||
std::string
|
||||
stringToLower(std::string_view str);
|
||||
@@ -55,11 +59,11 @@ std::string
|
||||
bufferToString(const std::vector<unsigned char>& data);
|
||||
|
||||
template<typename T>
|
||||
std::optional<T> readAs(const std::string& str)
|
||||
std::optional<T> readAs(std::string_view str)
|
||||
{
|
||||
T res;
|
||||
|
||||
std::istringstream iss ( str );
|
||||
std::istringstream iss {std::string {str}};
|
||||
iss >> res;
|
||||
if (iss.fail())
|
||||
return std::nullopt;
|
||||
@@ -69,15 +73,17 @@ std::optional<T> readAs(const std::string& str)
|
||||
|
||||
template<>
|
||||
std::optional<std::string>
|
||||
readAs(const std::string& str);
|
||||
readAs(std::string_view str);
|
||||
|
||||
[[nodiscard]]
|
||||
std::string
|
||||
replaceInString(const std::string& str, const std::string& from, const std::string& to);
|
||||
|
||||
std::string
|
||||
jsEscape(const std::string& str);
|
||||
|
||||
std::string
|
||||
escapeString(std::string_view str, std::string_view charsToEscape, char escapeChar);
|
||||
|
||||
bool
|
||||
stringEndsWith(const std::string& str, const std::string& ending);
|
||||
|
||||
|
||||
@@ -42,6 +42,6 @@ namespace StringUtils
|
||||
{
|
||||
template <>
|
||||
std::optional<UUID>
|
||||
readAs(const std::string& str);
|
||||
readAs(std::string_view str);
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
|
||||
if (recommendationEngineTypeRow)
|
||||
scanSettings.modify()->setRecommendationEngineType(_recommendationEngineTypeModel->getValue(*recommendationEngineTypeRow));
|
||||
|
||||
auto clusterTypes {StringUtils::splitString(valueText(TagsField).toUTF8(), " ")};
|
||||
auto clusterTypes {StringUtils::splitStringCopy(valueText(TagsField).toUTF8(), " ")};
|
||||
scanSettings.modify()->setClusterTypes(LmsApp->getDbSession(), std::set<std::string>(clusterTypes.begin(), clusterTypes.end()));
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,8 @@ namespace UserInterface
|
||||
void
|
||||
SearchView::refreshView(const Wt::WString& searchText)
|
||||
{
|
||||
_keywords = StringUtils::splitString(searchText.toUTF8(), " ");
|
||||
_searchValue = searchText.toUTF8();
|
||||
_keywords = StringUtils::splitString(_searchValue, " ");
|
||||
refreshView();
|
||||
}
|
||||
|
||||
@@ -73,6 +74,7 @@ namespace UserInterface
|
||||
SearchView::searchArtists()
|
||||
{
|
||||
bool more;
|
||||
|
||||
const auto artists {Database::Artist::getByFilter(LmsApp->getDbSession(),
|
||||
_filters->getClusterIds(),
|
||||
_keywords,
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <Wt/WTemplate.h>
|
||||
@@ -46,7 +47,8 @@ class SearchView : public Wt::WTemplate
|
||||
void searchTracks();
|
||||
|
||||
Filters* _filters {};
|
||||
std::vector<std::string> _keywords;
|
||||
std::string _searchValue;
|
||||
std::vector<std::string_view> _keywords;
|
||||
};
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace StringUtils
|
||||
{
|
||||
template <>
|
||||
std::optional<Database::AudioFormat>
|
||||
readAs(const std::string& str)
|
||||
readAs(std::string_view str)
|
||||
{
|
||||
|
||||
auto encodedFormat {readAs<int>(str)};
|
||||
|
||||
@@ -26,6 +26,74 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "utils/RecursiveSharedMutex.hpp"
|
||||
#include "utils/String.hpp"
|
||||
|
||||
|
||||
void
|
||||
testStrings()
|
||||
{
|
||||
{
|
||||
const std::string test{"a"};
|
||||
|
||||
const std::vector<std::string_view> strings {StringUtils::splitString(test, "")};
|
||||
assert(strings.size() == 1);
|
||||
assert(strings.front() == "a");
|
||||
}
|
||||
|
||||
{
|
||||
const std::string test{"a b"};
|
||||
|
||||
const std::vector<std::string_view> strings {StringUtils::splitString(test, "|")};
|
||||
assert(strings.size() == 1);
|
||||
assert(strings.front() == "a b");
|
||||
}
|
||||
|
||||
{
|
||||
const std::string test{" a"};
|
||||
|
||||
const std::vector<std::string_view> strings {StringUtils::splitString(test, " ")};
|
||||
assert(strings.size() == 1);
|
||||
assert(strings.front() == "a");
|
||||
}
|
||||
|
||||
{
|
||||
const std::string test{"a "};
|
||||
|
||||
const std::vector<std::string_view> strings {StringUtils::splitString(test, " ")};
|
||||
assert(strings.size() == 1);
|
||||
assert(strings.front() == "a");
|
||||
}
|
||||
|
||||
{
|
||||
const std::string test{"a b"};
|
||||
|
||||
const std::vector<std::string_view> strings {StringUtils::splitString(test, " ")};
|
||||
assert(strings.size() == 2);
|
||||
assert(strings.front() == "a");
|
||||
assert(strings.back() == "b");
|
||||
}
|
||||
|
||||
{
|
||||
const std::string test{"a b,c|defgh "};
|
||||
|
||||
const std::vector<std::string_view> strings {StringUtils::splitString(test, " ,|")};
|
||||
assert(strings.size() == 4);
|
||||
assert(strings[0] == "a");
|
||||
assert(strings[1] == "b");
|
||||
assert(strings[2] == "c");
|
||||
assert(strings[3] == "defgh");
|
||||
}
|
||||
|
||||
{
|
||||
assert(StringUtils::escapeString("", "*", ' ') == "");
|
||||
assert(StringUtils::escapeString("", "", ' ') == "");
|
||||
assert(StringUtils::escapeString("a", "", ' ') == "a");
|
||||
assert(StringUtils::escapeString("*", "*", '_') == "_*");
|
||||
assert(StringUtils::escapeString("*a*", "*", '_') == "_*a_*");
|
||||
assert(StringUtils::escapeString("*a|", "*|", '_') == "_*a_|");
|
||||
assert(StringUtils::escapeString("**||", "*|", '_') == "_*_*_|_|");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSharedMutex()
|
||||
@@ -109,6 +177,7 @@ int main()
|
||||
{
|
||||
try
|
||||
{
|
||||
testStrings();
|
||||
testSharedMutex();
|
||||
}
|
||||
catch (std::exception& e)
|
||||
|
||||
Reference in New Issue
Block a user