WIP: G-code find & replace: Support for non-regular expression,

whole word and case insensitive search.
This commit is contained in:
Vojtech Bubnik 2022-01-06 16:41:54 +01:00 committed by YuSanka
parent add1e994fa
commit d4fd95bd4a
4 changed files with 88 additions and 17 deletions

View File

@ -2849,14 +2849,11 @@ void GCode::GCodeOutputStream::close()
void GCode::GCodeOutputStream::write(const char *what)
{
if (what != nullptr) {
const char* gcode = what;
// writes string to file
fwrite(gcode, 1, ::strlen(gcode), this->f);
//FIXME don't allocate a string, maybe process a batch of lines?
if (m_find_replace)
m_processor.process_buffer(m_find_replace->process_layer(std::string(gcode)));
else
m_processor.process_buffer(std::string(gcode));
std::string gcode(m_find_replace ? m_find_replace->process_layer(what) : what);
// writes string to file
fwrite(gcode.c_str(), 1, gcode.size(), this->f);
m_processor.process_buffer(gcode);
}
}

View File

@ -1,6 +1,8 @@
#include "FindReplace.hpp"
#include "../Utils.hpp"
#include <cctype> // isalpha
namespace Slic3r {
GCodeFindReplace::GCodeFindReplace(const PrintConfig &print_config)
@ -12,13 +14,24 @@ GCodeFindReplace::GCodeFindReplace(const PrintConfig &print_config)
m_substitutions.reserve(subst.size() / 3);
for (size_t i = 0; i < subst.size(); i += 3) {
boost::regex pattern;
Substitution out;
try {
pattern.assign(subst[i], boost::regex::optimize); // boost::regex::icase
out.plain_pattern = subst[i];
out.format = subst[i + 1];
const std::string &params = subst[i + 2];
out.regexp = strchr(params.c_str(), 'r') != nullptr || strchr(params.c_str(), 'R') != nullptr;
out.case_insensitive = strchr(params.c_str(), 'i') != nullptr || strchr(params.c_str(), 'I') != nullptr;
out.whole_word = strchr(params.c_str(), 'w') != nullptr || strchr(params.c_str(), 'W') != nullptr;
if (out.regexp)
out.regexp_pattern.assign(
out.whole_word ?
std::string("\b") + out.plain_pattern + "\b" :
out.plain_pattern,
(out.case_insensitive ? boost::regex::icase : 0) | boost::regex::optimize);
} catch (const std::exception &ex) {
throw RuntimeError(std::string("Invalid gcode_substitutions parameter, failed to compile regular expression: ") + ex.what());
}
m_substitutions.push_back({ std::move(pattern), subst[i + 1] });
m_substitutions.emplace_back(std::move(out));
}
}
@ -49,6 +62,29 @@ private:
std::string *m_data;
};
template<typename FindFn>
static void find_and_replace_whole_word(std::string &inout, const std::string &match, const std::string &replace, FindFn find_fn)
{
if (! match.empty() && inout.size() >= match.size() && match != replace) {
std::string out;
auto [i, j] = find_fn(inout, 0, match);
size_t k = 0;
for (; i != std::string::npos; std::tie(i, j) = find_fn(inout, i, match)) {
if ((i == 0 || ! std::isalnum(inout[i - 1])) && (j == inout.size() || ! std::isalnum(inout[j]))) {
out.reserve(inout.size());
out.append(inout, k, i - k);
out.append(replace);
i = k = j;
} else
i += match.size();
}
if (k > 0) {
out.append(inout, k, inout.size() - k);
inout.swap(out);
}
}
}
std::string GCodeFindReplace::process_layer(const std::string &ain)
{
std::string out;
@ -57,11 +93,39 @@ std::string GCodeFindReplace::process_layer(const std::string &ain)
temp.reserve(in->size());
for (const Substitution &substitution : m_substitutions) {
temp.clear();
temp.reserve(in->size());
boost::regex_replace(ToStringIterator(temp), in->begin(), in->end(),
substitution.pattern, substitution.format, boost::match_default | boost::match_not_dot_newline | boost::match_not_dot_null | boost::format_all);
std::swap(out, temp);
if (substitution.regexp) {
temp.clear();
temp.reserve(in->size());
boost::regex_replace(ToStringIterator(temp), in->begin(), in->end(),
substitution.regexp_pattern, substitution.format, boost::match_default | boost::match_not_dot_newline | boost::match_not_dot_null | boost::format_all);
std::swap(out, temp);
} else {
if (in == &ain)
out = ain;
// Plain substitution
if (substitution.case_insensitive) {
if (substitution.whole_word)
find_and_replace_whole_word(out, substitution.plain_pattern, substitution.format,
[](const std::string &str, size_t start_pos, const std::string &match) {
auto begin = str.begin() + start_pos;
auto res = boost::ifind_first(
boost::iterator_range<std::string::const_iterator>(begin, str.end()),
boost::iterator_range<std::string::const_iterator>(match.begin(), match.end()));
return res ? std::make_pair(size_t(res.begin() - begin), size_t(res.end() - begin)) : std::make_pair(std::string::npos, std::string::npos);
});
else
boost::ireplace_all(out, substitution.plain_pattern, substitution.format);
} else {
if (substitution.whole_word)
find_and_replace_whole_word(out, substitution.plain_pattern, substitution.format,
[](const std::string &str, size_t start_pos, const std::string &match) {
size_t pos = str.find(match, start_pos);
return std::make_pair(pos, pos + (pos == std::string::npos ? 0 : match.size()));
});
else
boost::replace_all(out, substitution.plain_pattern, substitution.format);
}
}
in = &out;
}

View File

@ -15,8 +15,13 @@ public:
private:
struct Substitution {
boost::regex pattern;
std::string format;
std::string plain_pattern;
boost::regex regexp_pattern;
std::string format;
bool regexp { false };
bool case_insensitive { false };
bool whole_word { false };
};
std::vector<Substitution> m_substitutions;
};

View File

@ -641,6 +641,11 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionBool, gcode_comments))
((ConfigOptionEnum<GCodeFlavor>, gcode_flavor))
((ConfigOptionBool, gcode_label_objects))
// Triples of strings: "search pattern", "replace with pattern", "attribs"
// where "attribs" are one of:
// r - regular expression
// i - case insensitive
// w - whole word
((ConfigOptionStrings, gcode_substitutions))
((ConfigOptionString, layer_gcode))
((ConfigOptionFloat, max_print_speed))