Escape more strings: formspecs, item descriptions, infotexts...

Also, change the escape character to the more standard \x1b
Thus, it can be used in the future for translation or colored text,
for example.
This commit is contained in:
Ekdohibs 2016-04-04 18:31:00 +02:00 committed by Craig Robbins
parent 21079cc8eb
commit 48939df9a5
8 changed files with 105 additions and 83 deletions

@ -679,8 +679,8 @@ ChatBackend::~ChatBackend()
void ChatBackend::addMessage(std::wstring name, std::wstring text) void ChatBackend::addMessage(std::wstring name, std::wstring text)
{ {
name = removeChatEscapes(name); name = unescape_enriched(name);
text = removeChatEscapes(text); text = unescape_enriched(text);
// Note: A message may consist of multiple lines, for example the MOTD. // Note: A message may consist of multiple lines, for example the MOTD.
WStrfnd fnd(text); WStrfnd fnd(text);

@ -3731,7 +3731,7 @@ void Game::handlePointingAtNode(GameRunData *runData,
NodeMetadata *meta = map.getNodeMetadata(nodepos); NodeMetadata *meta = map.getNodeMetadata(nodepos);
if (meta) { if (meta) {
infotext = utf8_to_wide(meta->getString("infotext")); infotext = unescape_enriched(utf8_to_wide(meta->getString("infotext")));
} else { } else {
MapNode n = map.getNodeNoEx(nodepos); MapNode n = map.getNodeNoEx(nodepos);
@ -3807,13 +3807,15 @@ void Game::handlePointingAtObject(GameRunData *runData,
const v3f &player_position, const v3f &player_position,
bool show_debug) bool show_debug)
{ {
infotext = utf8_to_wide(runData->selected_object->infoText()); infotext = unescape_enriched(
utf8_to_wide(runData->selected_object->infoText()));
if (show_debug) { if (show_debug) {
if (infotext != L"") { if (infotext != L"") {
infotext += L"\n"; infotext += L"\n";
} }
infotext += utf8_to_wide(runData->selected_object->debugInfoText()); infotext += unescape_enriched(utf8_to_wide(
runData->selected_object->debugInfoText()));
} }
if (input->getLeftState()) { if (input->getLeftState()) {

@ -609,8 +609,6 @@ void GUIFormSpecMenu::parseButton(parserData* data,std::string element,
if(!data->explicit_size) if(!data->explicit_size)
warningstream<<"invalid use of button without a size[] element"<<std::endl; warningstream<<"invalid use of button without a size[] element"<<std::endl;
label = unescape_string(label);
std::wstring wlabel = utf8_to_wide(label); std::wstring wlabel = utf8_to_wide(label);
FieldSpec spec( FieldSpec spec(
@ -733,7 +731,6 @@ void GUIFormSpecMenu::parseTable(parserData* data,std::string element)
geom.X = stof(v_geom[0]) * (float)spacing.X; geom.X = stof(v_geom[0]) * (float)spacing.X;
geom.Y = stof(v_geom[1]) * (float)spacing.Y; geom.Y = stof(v_geom[1]) * (float)spacing.Y;
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
FieldSpec spec( FieldSpec spec(
@ -746,7 +743,7 @@ void GUIFormSpecMenu::parseTable(parserData* data,std::string element)
spec.ftype = f_Table; spec.ftype = f_Table;
for (unsigned int i = 0; i < items.size(); ++i) { for (unsigned int i = 0; i < items.size(); ++i) {
items[i] = unescape_string(items[i]); items[i] = unescape_string(unescape_enriched(items[i]));
} }
//now really show table //now really show table
@ -818,7 +815,7 @@ void GUIFormSpecMenu::parseTextList(parserData* data,std::string element)
spec.ftype = f_Table; spec.ftype = f_Table;
for (unsigned int i = 0; i < items.size(); ++i) { for (unsigned int i = 0; i < items.size(); ++i) {
items[i] = unescape_string(items[i]); items[i] = unescape_string(unescape_enriched(items[i]));
} }
//now really show list //now really show list
@ -889,7 +886,8 @@ void GUIFormSpecMenu::parseDropDown(parserData* data,std::string element)
} }
for (unsigned int i=0; i < items.size(); i++) { for (unsigned int i=0; i < items.size(); i++) {
e->addItem(utf8_to_wide(items[i]).c_str()); e->addItem(unescape_string(unescape_enriched(
utf8_to_wide(items[i]))).c_str());
} }
if (str_initial_selection != "") if (str_initial_selection != "")
@ -930,8 +928,6 @@ void GUIFormSpecMenu::parsePwdField(parserData* data,std::string element)
core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y); core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
label = unescape_string(label);
std::wstring wlabel = utf8_to_wide(label); std::wstring wlabel = utf8_to_wide(label);
FieldSpec spec( FieldSpec spec(
@ -995,8 +991,6 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data,
if(m_form_src) if(m_form_src)
default_val = m_form_src->resolveText(default_val); default_val = m_form_src->resolveText(default_val);
default_val = unescape_string(default_val);
label = unescape_string(label);
std::wstring wlabel = utf8_to_wide(label); std::wstring wlabel = utf8_to_wide(label);
@ -1094,9 +1088,6 @@ void GUIFormSpecMenu::parseTextArea(parserData* data,
default_val = m_form_src->resolveText(default_val); default_val = m_form_src->resolveText(default_val);
default_val = unescape_string(default_val);
label = unescape_string(label);
std::wstring wlabel = utf8_to_wide(label); std::wstring wlabel = utf8_to_wide(label);
FieldSpec spec( FieldSpec spec(
@ -1197,7 +1188,6 @@ void GUIFormSpecMenu::parseLabel(parserData* data,std::string element)
if(!data->explicit_size) if(!data->explicit_size)
warningstream<<"invalid use of label without a size[] element"<<std::endl; warningstream<<"invalid use of label without a size[] element"<<std::endl;
text = unescape_string(text);
std::vector<std::string> lines = split(text, '\n'); std::vector<std::string> lines = split(text, '\n');
for (unsigned int i = 0; i != lines.size(); i++) { for (unsigned int i = 0; i != lines.size(); i++) {
@ -1243,7 +1233,8 @@ void GUIFormSpecMenu::parseVertLabel(parserData* data,std::string element)
((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION)))
{ {
std::vector<std::string> v_pos = split(parts[0],','); std::vector<std::string> v_pos = split(parts[0],',');
std::wstring text = utf8_to_wide(unescape_string(parts[1])); std::wstring text = unescape_string(
unescape_enriched(utf8_to_wide(parts[1])));
MY_CHECKPOS("vertlabel",1); MY_CHECKPOS("vertlabel",1);
@ -1330,7 +1321,6 @@ void GUIFormSpecMenu::parseImageButton(parserData* data,std::string element,
image_name = unescape_string(image_name); image_name = unescape_string(image_name);
pressed_image_name = unescape_string(pressed_image_name); pressed_image_name = unescape_string(pressed_image_name);
label = unescape_string(label);
std::wstring wlabel = utf8_to_wide(label); std::wstring wlabel = utf8_to_wide(label);
@ -1430,7 +1420,8 @@ void GUIFormSpecMenu::parseTabHeader(parserData* data,std::string element)
e->setNotClipped(true); e->setNotClipped(true);
for (unsigned int i = 0; i < buttons.size(); i++) { for (unsigned int i = 0; i < buttons.size(); i++) {
e->addTab(utf8_to_wide(buttons[i]).c_str(), -1); e->addTab(unescape_string(unescape_enriched(
utf8_to_wide(buttons[i]))).c_str(), -1);
} }
if ((tab_index >= 0) && if ((tab_index >= 0) &&
@ -1489,7 +1480,6 @@ void GUIFormSpecMenu::parseItemImageButton(parserData* data,std::string element)
m_default_tooltip_bgcolor, m_default_tooltip_bgcolor,
m_default_tooltip_color); m_default_tooltip_color);
label = unescape_string(label);
FieldSpec spec( FieldSpec spec(
name, name,
utf8_to_wide(label), utf8_to_wide(label),
@ -1604,14 +1594,14 @@ void GUIFormSpecMenu::parseTooltip(parserData* data, std::string element)
std::vector<std::string> parts = split(element,';'); std::vector<std::string> parts = split(element,';');
if (parts.size() == 2) { if (parts.size() == 2) {
std::string name = parts[0]; std::string name = parts[0];
m_tooltips[name] = TooltipSpec(unescape_string(parts[1]), m_tooltips[name] = TooltipSpec(parts[1],
m_default_tooltip_bgcolor, m_default_tooltip_color); m_default_tooltip_bgcolor, m_default_tooltip_color);
return; return;
} else if (parts.size() == 4) { } else if (parts.size() == 4) {
std::string name = parts[0]; std::string name = parts[0];
video::SColor tmp_color1, tmp_color2; video::SColor tmp_color1, tmp_color2;
if ( parseColorString(parts[2], tmp_color1, false) && parseColorString(parts[3], tmp_color2, false) ) { if ( parseColorString(parts[2], tmp_color1, false) && parseColorString(parts[3], tmp_color2, false) ) {
m_tooltips[name] = TooltipSpec(unescape_string(parts[1]), m_tooltips[name] = TooltipSpec(parts[1],
tmp_color1, tmp_color2); tmp_color1, tmp_color2);
return; return;
} }
@ -2242,16 +2232,18 @@ void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase,
} }
// Draw tooltip // Draw tooltip
std::string tooltip_text = ""; std::wstring tooltip_text = L"";
if (hovering && !m_selected_item) if (hovering && !m_selected_item) {
tooltip_text = item.getDefinition(m_gamedef->idef()).description; tooltip_text = utf8_to_wide(item.getDefinition(m_gamedef->idef()).description);
if (tooltip_text != "") { tooltip_text = unescape_enriched(tooltip_text);
std::vector<std::string> tt_rows = str_split(tooltip_text, '\n'); }
if (tooltip_text != L"") {
std::vector<std::wstring> tt_rows = str_split(tooltip_text, L'\n');
m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor); m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor);
m_tooltip_element->setOverrideColor(m_default_tooltip_color); m_tooltip_element->setOverrideColor(m_default_tooltip_color);
m_tooltip_element->setVisible(true); m_tooltip_element->setVisible(true);
this->bringToFront(m_tooltip_element); this->bringToFront(m_tooltip_element);
m_tooltip_element->setText(utf8_to_wide(tooltip_text).c_str()); m_tooltip_element->setText(tooltip_text.c_str());
s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height; s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height;
s32 tooltip_height = m_tooltip_element->getTextHeight() + 5; s32 tooltip_height = m_tooltip_element->getTextHeight() + 5;
v2u32 screenSize = driver->getScreenSize(); v2u32 screenSize = driver->getScreenSize();
@ -2504,7 +2496,7 @@ void GUIFormSpecMenu::drawMenu()
u32 delta = 0; u32 delta = 0;
if (id == -1) { if (id == -1) {
m_old_tooltip_id = id; m_old_tooltip_id = id;
m_old_tooltip = ""; m_old_tooltip = L"";
} else { } else {
if (id == m_old_tooltip_id) { if (id == m_old_tooltip_id) {
delta = porting::getDeltaMs(m_hovered_time, getTimeMs()); delta = porting::getDeltaMs(m_hovered_time, getTimeMs());
@ -2517,11 +2509,11 @@ void GUIFormSpecMenu::drawMenu()
if (id != -1 && delta >= m_tooltip_show_delay) { if (id != -1 && delta >= m_tooltip_show_delay) {
for(std::vector<FieldSpec>::iterator iter = m_fields.begin(); for(std::vector<FieldSpec>::iterator iter = m_fields.begin();
iter != m_fields.end(); ++iter) { iter != m_fields.end(); ++iter) {
if ( (iter->fid == id) && (m_tooltips[iter->fname].tooltip != "") ){ if (iter->fid == id && m_tooltips[iter->fname].tooltip != L"") {
if (m_old_tooltip != m_tooltips[iter->fname].tooltip) { if (m_old_tooltip != m_tooltips[iter->fname].tooltip) {
m_old_tooltip = m_tooltips[iter->fname].tooltip; m_old_tooltip = m_tooltips[iter->fname].tooltip;
m_tooltip_element->setText(utf8_to_wide(m_tooltips[iter->fname].tooltip).c_str()); m_tooltip_element->setText(m_tooltips[iter->fname].tooltip.c_str());
std::vector<std::string> tt_rows = str_split(m_tooltips[iter->fname].tooltip, '\n'); std::vector<std::wstring> tt_rows = str_split(m_tooltips[iter->fname].tooltip, L'\n');
s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height; s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height;
s32 tooltip_height = m_tooltip_element->getTextHeight() * tt_rows.size() + 5; s32 tooltip_height = m_tooltip_element->getTextHeight() * tt_rows.size() + 5;
int tooltip_offset_x = m_btn_height; int tooltip_offset_x = m_btn_height;
@ -2875,7 +2867,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
core::position2d<s32>(x, y)); core::position2d<s32>(x, y));
if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
m_old_tooltip_id = -1; m_old_tooltip_id = -1;
m_old_tooltip = ""; m_old_tooltip = L"";
} }
if (!isChild(hovered,this)) { if (!isChild(hovered,this)) {
if (DoubleClickDetection(event)) { if (DoubleClickDetection(event)) {

@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "modalMenu.h" #include "modalMenu.h"
#include "guiTable.h" #include "guiTable.h"
#include "network/networkprotocol.h" #include "network/networkprotocol.h"
#include "util/string.h"
class IGameDef; class IGameDef;
class InventoryManager; class InventoryManager;
@ -191,18 +192,26 @@ class GUIFormSpecMenu : public GUIModalMenu
bool scale; bool scale;
}; };
/* The responsibility of unescaping the strings has been shifted
* from the formspec parsing methods to the draw methods.
* There still are a few exceptions:
* - Vertical label, because it modifies the string by inserting
* '\n' between each character,
* - Tab header, because it gives the string immediately to
* Irrlicht and we can't unescape it later.
*/
struct FieldSpec struct FieldSpec
{ {
FieldSpec() FieldSpec()
{ {
} }
FieldSpec(const std::string &name, const std::wstring &label, FieldSpec(const std::string &name, const std::wstring &label,
const std::wstring &fdeflt, int id) : const std::wstring &default_text, int id) :
fname(name), fname(name),
flabel(label),
fdefault(fdeflt),
fid(id) fid(id)
{ {
flabel = unescape_string(unescape_enriched(label));
fdefault = unescape_string(unescape_enriched(default_text));
send = false; send = false;
ftype = f_Unknown; ftype = f_Unknown;
is_exit = false; is_exit = false;
@ -235,12 +244,12 @@ class GUIFormSpecMenu : public GUIModalMenu
} }
TooltipSpec(std::string a_tooltip, irr::video::SColor a_bgcolor, TooltipSpec(std::string a_tooltip, irr::video::SColor a_bgcolor,
irr::video::SColor a_color): irr::video::SColor a_color):
tooltip(a_tooltip),
bgcolor(a_bgcolor), bgcolor(a_bgcolor),
color(a_color) color(a_color)
{ {
tooltip = unescape_string(unescape_enriched(utf8_to_wide(a_tooltip)));
} }
std::string tooltip; std::wstring tooltip;
irr::video::SColor bgcolor; irr::video::SColor bgcolor;
irr::video::SColor color; irr::video::SColor color;
}; };
@ -252,18 +261,18 @@ class GUIFormSpecMenu : public GUIModalMenu
} }
StaticTextSpec(const std::wstring &a_text, StaticTextSpec(const std::wstring &a_text,
const core::rect<s32> &a_rect): const core::rect<s32> &a_rect):
text(a_text),
rect(a_rect), rect(a_rect),
parent_button(NULL) parent_button(NULL)
{ {
text = unescape_string(unescape_enriched(a_text));
} }
StaticTextSpec(const std::wstring &a_text, StaticTextSpec(const std::wstring &a_text,
const core::rect<s32> &a_rect, const core::rect<s32> &a_rect,
gui::IGUIButton *a_parent_button): gui::IGUIButton *a_parent_button):
text(a_text),
rect(a_rect), rect(a_rect),
parent_button(a_parent_button) parent_button(a_parent_button)
{ {
text = unescape_string(unescape_enriched(a_text));
} }
std::wstring text; std::wstring text;
core::rect<s32> rect; core::rect<s32> rect;
@ -406,7 +415,7 @@ protected:
u32 m_tooltip_show_delay; u32 m_tooltip_show_delay;
s32 m_hovered_time; s32 m_hovered_time;
s32 m_old_tooltip_id; s32 m_old_tooltip_id;
std::string m_old_tooltip; std::wstring m_old_tooltip;
bool m_rmouse_auto_place; bool m_rmouse_auto_place;

@ -22,6 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "hud.h" #include "hud.h"
#include "settings.h" #include "settings.h"
#include "util/numeric.h" #include "util/numeric.h"
#include "util/string.h"
#include "log.h" #include "log.h"
#include "gamedef.h" #include "gamedef.h"
#include "itemdef.h" #include "itemdef.h"
@ -319,7 +320,7 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
(e->number >> 8) & 0xFF, (e->number >> 8) & 0xFF,
(e->number >> 0) & 0xFF); (e->number >> 0) & 0xFF);
core::rect<s32> size(0, 0, e->scale.X, text_height * e->scale.Y); core::rect<s32> size(0, 0, e->scale.X, text_height * e->scale.Y);
std::wstring text = utf8_to_wide(e->text); std::wstring text = unescape_enriched(utf8_to_wide(e->text));
core::dimension2d<u32> textsize = font->getDimension(text.c_str()); core::dimension2d<u32> textsize = font->getDimension(text.c_str());
v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2), v2s32 offset((e->align.X - 1.0) * (textsize.Width / 2),
(e->align.Y - 1.0) * (textsize.Height / 2)); (e->align.Y - 1.0) * (textsize.Height / 2));
@ -355,11 +356,11 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
(e->number >> 8) & 0xFF, (e->number >> 8) & 0xFF,
(e->number >> 0) & 0xFF); (e->number >> 0) & 0xFF);
core::rect<s32> size(0, 0, 200, 2 * text_height); core::rect<s32> size(0, 0, 200, 2 * text_height);
std::wstring text = utf8_to_wide(e->name); std::wstring text = unescape_enriched(utf8_to_wide(e->name));
font->draw(text.c_str(), size + pos, color); font->draw(text.c_str(), size + pos, color);
std::ostringstream os; std::ostringstream os;
os << distance << e->text; os << distance << e->text;
text = utf8_to_wide(os.str()); text = unescape_enriched(utf8_to_wide(os.str()));
pos.Y += text_height; pos.Y += text_height;
font->draw(text.c_str(), size + pos, color); font->draw(text.c_str(), size + pos, color);
break; } break; }

@ -45,6 +45,7 @@ public:
void testStringAllowed(); void testStringAllowed();
void testAsciiPrintableHelper(); void testAsciiPrintableHelper();
void testUTF8(); void testUTF8();
void testRemoveEscapes();
void testWrapRows(); void testWrapRows();
void testIsNumber(); void testIsNumber();
void testIsPowerOfTwo(); void testIsPowerOfTwo();
@ -71,6 +72,7 @@ void TestUtilities::runTests(IGameDef *gamedef)
TEST(testStringAllowed); TEST(testStringAllowed);
TEST(testAsciiPrintableHelper); TEST(testAsciiPrintableHelper);
TEST(testUTF8); TEST(testUTF8);
TEST(testRemoveEscapes);
TEST(testWrapRows); TEST(testWrapRows);
TEST(testIsNumber); TEST(testIsNumber);
TEST(testIsPowerOfTwo); TEST(testIsPowerOfTwo);
@ -253,6 +255,23 @@ void TestUtilities::testUTF8()
== "the shovel dug a crumbly node!"); == "the shovel dug a crumbly node!");
} }
void TestUtilities::testRemoveEscapes()
{
UASSERT(unescape_enriched<wchar_t>(
L"abc\x1bXdef") == L"abcdef");
UASSERT(unescape_enriched<wchar_t>(
L"abc\x1b(escaped)def") == L"abcdef");
UASSERT(unescape_enriched<wchar_t>(
L"abc\x1b((escaped with parenthesis\\))def") == L"abcdef");
UASSERT(unescape_enriched<wchar_t>(
L"abc\x1b(incomplete") == L"abc");
UASSERT(unescape_enriched<wchar_t>(
L"escape at the end\x1b") == L"escape at the end");
// Nested escapes not supported
UASSERT(unescape_enriched<wchar_t>(
L"abc\x1b(outer \x1b(inner escape)escape)def") == L"abcescape)def");
}
void TestUtilities::testWrapRows() void TestUtilities::testWrapRows()
{ {
UASSERT(wrap_rows("12345678",4) == "1234\n5678"); UASSERT(wrap_rows("12345678",4) == "1234\n5678");

@ -729,33 +729,6 @@ static bool parseNamedColorString(const std::string &value, video::SColor &color
return true; return true;
} }
std::wstring removeChatEscapes(const std::wstring &s) {
std::wstring output;
size_t i = 0;
while (i < s.length()) {
if (s[i] == L'\v') {
++i;
if (i == s.length()) continue;
if (s[i] == L'(') {
++i;
while (i < s.length() && s[i] != L')') {
if (s[i] == L'\\') {
++i;
}
++i;
}
++i;
} else {
++i;
}
continue;
}
output += s[i];
++i;
}
return output;
}
void str_replace(std::string &str, char from, char to) void str_replace(std::string &str, char from, char to)
{ {
std::replace(str.begin(), str.end(), from, to); std::replace(str.begin(), str.end(), from, to);

@ -386,14 +386,6 @@ inline void str_replace(std::string &str, const std::string &pattern,
} }
} }
/**
* Remove all chat escape sequences in \p s.
*
* @param s The string in which to remove escape sequences.
* @return \p s, with escape sequences removed.
*/
std::wstring removeChatEscapes(const std::wstring &s);
/** /**
* Replace all occurrences of the character \p from in \p str with \p to. * Replace all occurrences of the character \p from in \p str with \p to.
* *
@ -476,7 +468,7 @@ inline std::string wrap_rows(const std::string &from,
* Removes backslashes from an escaped string (FormSpec strings) * Removes backslashes from an escaped string (FormSpec strings)
*/ */
template <typename T> template <typename T>
inline std::basic_string<T> unescape_string(std::basic_string<T> &s) inline std::basic_string<T> unescape_string(const std::basic_string<T> &s)
{ {
std::basic_string<T> res; std::basic_string<T> res;
@ -492,6 +484,40 @@ inline std::basic_string<T> unescape_string(std::basic_string<T> &s)
return res; return res;
} }
/**
* Remove all escape sequences in \p s.
*
* @param s The string in which to remove escape sequences.
* @return \p s, with escape sequences removed.
*/
template <typename T>
std::basic_string<T> unescape_enriched(const std::basic_string<T> &s)
{
std::basic_string<T> output;
size_t i = 0;
while (i < s.length()) {
if (s[i] == '\x1b') {
++i;
if (i == s.length()) continue;
if (s[i] == '(') {
++i;
while (i < s.length() && s[i] != ')') {
if (s[i] == '\\') {
++i;
}
++i;
}
++i;
} else {
++i;
}
continue;
}
output += s[i];
++i;
}
return output;
}
/** /**
* Checks that all characters in \p to_check are a decimal digits. * Checks that all characters in \p to_check are a decimal digits.