199 lines
6.4 KiB
C++
199 lines
6.4 KiB
C++
// *****************************************************************************
|
|
// * This file is part of the FreeFileSync project. It is distributed under *
|
|
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
|
|
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
|
|
// *****************************************************************************
|
|
|
|
#include "multi_rename.h"
|
|
#include <zen/string_tools.h>
|
|
|
|
using namespace zen;
|
|
using namespace fff;
|
|
|
|
|
|
namespace
|
|
{
|
|
std::wstring_view findLongestSubstring(const std::vector<std::wstring_view>& strings)
|
|
{
|
|
if (strings.empty())
|
|
return {};
|
|
|
|
const std::wstring_view strMin = *std::min_element(strings.begin(), strings.end(),
|
|
/**/[](const std::wstring_view lhs, const std::wstring_view rhs) { return lhs.size() < rhs.size(); });
|
|
|
|
for (size_t sz = strMin.size(); sz > 0; --sz) //iterate over size, descending
|
|
for (size_t i = 0; i + sz <= strMin.size(); ++i)
|
|
{
|
|
const std::wstring_view substr(strMin.data() + i, sz);
|
|
//perf: duplicate substrings, especially für size = 1?
|
|
|
|
const bool isCommon = [&]
|
|
{
|
|
for (const std::wstring_view str : strings)
|
|
if (str.data() != strMin.data()) //sufficient check: an extension of strMin necessarily contains "substr" anyway
|
|
if (!contains(str, substr))
|
|
return false;
|
|
return true;
|
|
}();
|
|
|
|
if (isCommon)
|
|
return substr; //*first* occuring substring of maximum size
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
|
|
struct StringPart
|
|
{
|
|
std::vector<std::wstring_view> diff; //may be empty, but only at beginning (or between filename/extension parts)
|
|
std::wstring_view common; //may be empty, but only at end (dito)
|
|
};
|
|
|
|
std::vector<StringPart> getStringParts(std::vector<std::wstring_view>&& strings)
|
|
{
|
|
std::wstring_view substr = findLongestSubstring(strings);
|
|
if (!substr.empty())
|
|
{
|
|
std::vector<std::wstring_view> head;
|
|
std::vector<std::wstring_view> tail;
|
|
|
|
for (const std::wstring_view str : strings)
|
|
{
|
|
head.push_back(beforeFirst(str, substr, IfNotFoundReturn::none));
|
|
tail.push_back(afterFirst (str, substr, IfNotFoundReturn::none));
|
|
}
|
|
|
|
std::vector<StringPart> np = getStringParts(std::move(head));
|
|
assert(np.empty() || np.back().common.empty()); //otherwise we could construct an even longer substring!
|
|
|
|
if (np.empty())
|
|
np.push_back({{}, substr});
|
|
else
|
|
np.back().common = substr;
|
|
|
|
const std::vector<StringPart> npTail = getStringParts(std::move(tail));
|
|
assert(npTail.empty() || !npTail.front().diff.empty()); //otherwise we could construct an even longer substring!
|
|
|
|
append(np, npTail);
|
|
return np;
|
|
}
|
|
else
|
|
{
|
|
if (std::all_of(strings.begin(), strings.end(), [](const std::wstring_view str) { return str.empty(); }))
|
|
/**/return {};
|
|
|
|
return {{std::move(strings), {}}};
|
|
}
|
|
}
|
|
|
|
|
|
constexpr wchar_t placeholders[] = //http://xahlee.info/comp/unicode_circled_numbers.html
|
|
{
|
|
//L'\u24FF', //⓿ <- rendered bigger than the rest (same for ⓫) on Centos Linux
|
|
L'\u2776', //❶
|
|
L'\u2777', //❷
|
|
L'\u2778', //❸
|
|
L'\u2779', //❹
|
|
L'\u277A', //❺
|
|
L'\u277B', //❻
|
|
L'\u277C', //❼
|
|
L'\u277D', //❽
|
|
L'\u277E', //❾
|
|
L'\u277F', //❿ -> last one is special: represents "all the rest"
|
|
};
|
|
|
|
|
|
inline
|
|
size_t getPlaceholderIndex(wchar_t c)
|
|
{
|
|
static_assert(std::size(placeholders) == 10);
|
|
if (placeholders[0] <= c && c <= placeholders[9])
|
|
return static_cast<size_t>(c - placeholders[0]);
|
|
|
|
return static_cast<size_t>(-1);
|
|
}
|
|
}
|
|
|
|
|
|
bool fff::isRenamePlaceholderChar(wchar_t c) { return getPlaceholderIndex(c) < std::size(placeholders); }
|
|
|
|
|
|
struct fff::RenameBuf
|
|
{
|
|
explicit RenameBuf(const std::vector<std::wstring>& s) : strings(s)
|
|
{
|
|
//file extensions deserve special treatment: https://freefilesync.org/forum/viewtopic.php?t=11943#p46453
|
|
std::vector<std::wstring_view> names;
|
|
std::vector<std::wstring_view> extensions; //including "."
|
|
for (const std::wstring& fileName : strings)
|
|
{
|
|
auto it = findLast(fileName.begin(), fileName.end(), L'.');
|
|
names. push_back(makeStringView(fileName.begin(), it));
|
|
extensions.push_back(makeStringView(it, fileName.end()));
|
|
}
|
|
|
|
parts = getStringParts(std::move(names));
|
|
append(parts, getStringParts(std::move(extensions)));
|
|
}
|
|
|
|
std::vector<std::wstring> strings;
|
|
std::vector<StringPart> parts;
|
|
};
|
|
|
|
|
|
//e.g. "Season ❶, Episode ❷ - ❸.avi"
|
|
std::pair<std::wstring /*phrase*/, SharedRef<const RenameBuf>> fff::getPlaceholderPhrase(const std::vector<std::wstring>& strings)
|
|
{
|
|
auto renameBuf = makeSharedRef<const RenameBuf>(strings);
|
|
|
|
std::wstring phrase;
|
|
size_t placeIdx = 0;
|
|
|
|
for (const StringPart& p : renameBuf.ref().parts)
|
|
{
|
|
if (!p.diff.empty())
|
|
{
|
|
phrase += placeholders[placeIdx++];
|
|
|
|
if (placeIdx >= std::size(placeholders))
|
|
break; //represent "all the rest" with last placeholder
|
|
}
|
|
phrase += p.common; //TODO: what if common part incidentally contains placeholder character!?
|
|
}
|
|
return {phrase, renameBuf};
|
|
}
|
|
|
|
|
|
const std::vector<std::wstring> fff::resolvePlaceholderPhrase(const std::wstring_view phrase, const RenameBuf& buf)
|
|
{
|
|
std::vector<const std::vector<std::wstring_view>*> diffByIdx;
|
|
|
|
for (const StringPart& p : buf.parts)
|
|
if (!p.diff.empty())
|
|
diffByIdx.push_back(&p.diff), assert(p.diff.size() == buf.strings.size());
|
|
|
|
std::vector<std::wstring> output;
|
|
|
|
for (size_t i = 0; i < buf.strings.size(); ++i)
|
|
{
|
|
std::wstring resolved;
|
|
|
|
for (const wchar_t c : phrase)
|
|
if (const size_t placeIdx = getPlaceholderIndex(c);
|
|
placeIdx < diffByIdx.size())
|
|
{
|
|
if (placeIdx == std::size(placeholders) - 1) //last placeholder represents "all the rest"
|
|
resolved.append((*diffByIdx[placeIdx])[i].data(), buf.strings[i].data() + buf.strings[i].size());
|
|
else
|
|
resolved += (*diffByIdx[placeIdx])[i];
|
|
}
|
|
else
|
|
resolved += c;
|
|
|
|
output.push_back(std::move(resolved));
|
|
}
|
|
|
|
return output;
|
|
}
|