Notepad3/grepWinNP3/src/RegexReplaceFormatter.h

339 lines
13 KiB
C++

// grepWin - regex search and replace for Windows
// Copyright (C) 2011-2012, 2014-2015, 2021, 2023 - Stefan Kueng
// This program 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 2
// of the License, or (at your option) any later version.
// This program 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 this program; if not, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#pragma once
#include <string>
#include <stdio.h>
#include <algorithm>
#include <map>
#include "StringUtils.h"
#pragma warning(push)
#pragma warning(disable : 4996) // warning STL4010: Various members of std::allocator are deprecated in C++17
#include <boost/regex.hpp>
#include <boost/iostreams/device/mapped_file.hpp>
#pragma warning(pop)
class NumberReplacer
{
public:
NumberReplacer()
: leadZero(false)
, padding(0)
, start(1)
, increment(1)
{
}
bool leadZero;
int padding;
int start;
int increment;
std::wstring expression;
};
class NumberReplacerA
{
public:
NumberReplacerA()
: leadZero(false)
, padding(0)
, start(1)
, increment(1)
{
}
bool leadZero;
int padding;
int start;
int increment;
std::string expression;
};
extern std::vector<NumberReplacer> g_incVec;
extern std::vector<NumberReplacerA> g_incVecA;
class RegexReplaceFormatter
{
public:
RegexReplaceFormatter(const std::wstring& sReplace)
: m_sReplace(sReplace)
{
g_incVec.clear();
// parse for ${count0L}, ${count0L(n)}, ${count0L(n,m)}, where
// ${count}
// is replaced later with numbers starting from 1, incremented by 1
// ${count(n)}
// is replaced with numbers starting from n, incremented by 1
// ${count(n,m)}
// is replaced with numbers starting from n, incremented by m
// 0 and L are optional and specify the size of the right-aligned
// number string. If 0 is specified, zeros are used for padding, otherwise spaces.
// boost::wregex expression = boost::wregex(L"(?<!\\\\)\\$\\{count(?<leadzero>0)?(?<length>\\d+)?(\\((?<startval>[-0-9]+)\\)||\\((?<startval>[-0-9]+),(?<increment>[-0-9]+)\\))?\\}", boost::regex::normal);
boost::wregex expression = boost::wregex(L"\\$\\{count(?<leadzero>0)?(?<length>\\d+)?(\\((?<startval>[-0-9]+)\\)||\\((?<startval>[-0-9]+),(?<increment>[-0-9]+)\\))?\\}", boost::regex::normal);
boost::match_results<std::wstring::const_iterator> whatc;
std::wstring::const_iterator start = m_sReplace.begin();
std::wstring::const_iterator end = m_sReplace.end();
boost::match_flag_type flags = boost::match_default | boost::format_all;
while (boost::regex_search(start, end, whatc, expression, flags))
{
if (whatc[0].matched)
{
NumberReplacer nr;
nr.leadZero = (static_cast<std::wstring>(whatc[L"leadzero"]) == L"0");
nr.padding = _wtoi(static_cast<std::wstring>(whatc[L"length"]).c_str());
std::wstring s = static_cast<std::wstring>(whatc[L"startval"]);
if (!s.empty())
nr.start = _wtoi(s.c_str());
s = static_cast<std::wstring>(whatc[L"increment"]);
if (!s.empty())
nr.increment = _wtoi(s.c_str());
if (nr.increment == 0)
nr.increment = 1;
nr.expression = static_cast<std::wstring>(whatc[0]);
g_incVec.push_back(nr);
}
// update search position:
if (start == whatc[0].second)
{
if (start == end)
break;
++start;
}
else
start = whatc[0].second;
// update flags:
flags |= boost::match_prev_avail;
flags |= boost::match_not_bob;
}
}
void SetReplacePair(const std::wstring& s1, const std::wstring& s2)
{
m_replaceMap[s1] = s2;
}
std::wstring operator()(boost::match_results<std::wstring::const_iterator> what) const
{
std::wstring sReplace = what.format(m_sReplace);
if (!m_replaceMap.empty())
{
for (auto it = m_replaceMap.cbegin(); it != m_replaceMap.cend(); ++it)
{
auto itBegin = std::search(sReplace.begin(), sReplace.end(), it->first.begin(), it->first.end());
while (itBegin != sReplace.end())
{
if ((itBegin == sReplace.begin()) || ((*(itBegin - 1)) != '\\'))
{
auto itEnd = itBegin + it->first.size();
sReplace.replace(itBegin, itEnd, it->second);
}
else if ((*(itBegin - 1)) == '\\')
{
sReplace.erase(itBegin - 1);
};
itBegin = std::search(sReplace.begin(), sReplace.end(), it->first.begin(), it->first.end());
}
}
}
if (!g_incVec.empty())
{
for (auto it = g_incVec.begin(); it != g_incVec.end(); ++it)
{
auto itBegin = std::search(sReplace.begin(), sReplace.end(), it->expression.begin(), it->expression.end());
if (itBegin != sReplace.end())
{
if ((itBegin == sReplace.begin()) || ((*(itBegin - 1)) != '\\'))
{
auto itEnd = itBegin + it->expression.size();
wchar_t format[20] = {0};
if (it->padding)
{
if (it->leadZero)
swprintf_s(format, _countof(format), L"%%0%dd", it->padding);
else
swprintf_s(format, _countof(format), L"%%%dd", it->padding);
}
else
wcscpy_s(format, L"%d");
if (it->padding < 50)
{
// for small strings, reserve space on the stack
wchar_t buf[128] = {0};
swprintf_s(buf, _countof(buf), format, it->start);
sReplace.replace(itBegin, itEnd, buf);
}
else
{
auto s = CStringUtils::Format(format, it->start);
sReplace.replace(itBegin, itEnd, s);
}
it->start += it->increment;
}
else if ((*(itBegin - 1)) == '\\')
{
sReplace.erase(itBegin - 1);
};
}
}
}
// sReplace = boost::regex_replace(what[0].str(), sReplace, boost::match_default);
return sReplace;
}
private:
std::wstring m_sReplace;
std::map<std::wstring, std::wstring> m_replaceMap;
};
class RegexReplaceFormatterA
{
public:
RegexReplaceFormatterA(const std::string& sReplace)
: m_sReplace(sReplace)
{
g_incVec.clear();
// parse for ${count0L}, ${count0L(n)}, ${count0L(n,m)}, where
// ${count}
// is replaced later with numbers starting from 1, incremented by 1
// ${count(n)}
// is replaced with numbers starting from n, incremented by 1
// ${count(n,m)}
// is replaced with numbers starting from n, incremented by m
// 0 and L are optional and specify the size of the right-aligned
// number string. If 0 is specified, zeros are used for padding, otherwise spaces.
// boost::wregex expression = boost::wregex(L"(?<!\\\\)\\$\\{count(?<leadzero>0)?(?<length>\\d+)?(\\((?<startval>[-0-9]+)\\)||\\((?<startval>[-0-9]+),(?<increment>[-0-9]+)\\))?\\}", boost::regex::normal);
boost::regex expression = boost::regex("\\$\\{count(?<leadzero>0)?(?<length>\\d+)?(\\((?<startval>[-0-9]+)\\)||\\((?<startval>[-0-9]+),(?<increment>[-0-9]+)\\))?\\}", boost::regex::normal);
boost::match_results<std::string::const_iterator> whatc;
std::string::const_iterator start = m_sReplace.begin();
std::string::const_iterator end = m_sReplace.end();
boost::match_flag_type flags = boost::match_default | boost::format_all;
while (boost::regex_search(start, end, whatc, expression, flags))
{
if (whatc[0].matched)
{
NumberReplacerA nr;
nr.leadZero = (static_cast<std::string>(whatc["leadzero"]) == "0");
nr.padding = atoi(static_cast<std::string>(whatc["length"]).c_str());
std::string s = static_cast<std::string>(whatc["startval"]);
if (!s.empty())
nr.start = atoi(s.c_str());
s = static_cast<std::string>(whatc["increment"]);
if (!s.empty())
nr.increment = atoi(s.c_str());
if (nr.increment == 0)
nr.increment = 1;
nr.expression = static_cast<std::string>(whatc[0]);
g_incVecA.push_back(nr);
}
// update search position:
if (start == whatc[0].second)
{
if (start == end)
break;
++start;
}
else
start = whatc[0].second;
// update flags:
flags |= boost::match_prev_avail;
flags |= boost::match_not_bob;
}
}
void SetReplacePair(const std::string& s1, const std::string& s2)
{
m_replaceMap[s1] = s2;
}
template <typename It>
std::string operator()(boost::match_results<It> what) const
{
std::string sReplace = what.format(m_sReplace);
if (!m_replaceMap.empty())
{
for (auto it = m_replaceMap.cbegin(); it != m_replaceMap.cend(); ++it)
{
auto itBegin = std::search(sReplace.begin(), sReplace.end(), it->first.begin(), it->first.end());
while (itBegin != sReplace.end())
{
if ((itBegin == sReplace.begin()) || ((*(itBegin - 1)) != '\\'))
{
auto itEnd = itBegin + it->first.size();
sReplace.replace(itBegin, itEnd, it->second);
}
else if ((*(itBegin - 1)) == '\\')
{
sReplace.erase(itBegin - 1);
};
itBegin = std::search(sReplace.begin(), sReplace.end(), it->first.begin(), it->first.end());
}
}
}
if (!g_incVec.empty())
{
for (auto it = g_incVec.begin(); it != g_incVec.end(); ++it)
{
auto itBegin = std::search(sReplace.begin(), sReplace.end(), it->expression.begin(), it->expression.end());
if (itBegin != sReplace.end())
{
if ((itBegin == sReplace.begin()) || ((*(itBegin - 1)) != '\\'))
{
auto itEnd = itBegin + it->expression.size();
char format[20] = {0};
if (it->padding)
{
if (it->leadZero)
sprintf_s(format, _countof(format), "%%0%dd", it->padding);
else
sprintf_s(format, _countof(format), "%%%dd", it->padding);
}
else
strcpy_s(format, "%d");
if (it->padding < 50)
{
// for small strings, reserve space on the stack
char buf[128] = {0};
sprintf_s(buf, _countof(buf), format, it->start);
sReplace.replace(itBegin, itEnd, buf);
}
else
{
auto s = CStringUtils::Format(format, it->start);
sReplace.replace(itBegin, itEnd, s);
}
it->start += it->increment;
}
else if ((*(itBegin - 1)) == '\\')
{
sReplace.erase(itBegin - 1);
};
}
}
}
return sReplace;
}
private:
std::string m_sReplace;
std::map<std::string, std::string> m_replaceMap;
};