Regex Replacing & # 58; в ":" и т. д.
У меня есть куча строк, как:
"Hello, here's a test colon:. Here's a test semi-colon;"
Я хотел бы заменить это
"Hello, here's a test colon:. Here's a test semi-colon;"
И так далее для всех печатаемых значений ASCII.
В настоящее время я использую boost::regex_search
соответствовать &#(\d+);
создание строки при обработке каждого совпадения по очереди (включая добавление подстроки, не содержащей совпадений с момента последнего найденного совпадения).
Кто-нибудь может придумать лучший способ сделать это? Я открыт для методов, не являющихся регулярными выражениями, но в этом случае регулярное выражение показалось разумным подходом.
Спасибо,
Дом
12 ответов
Большим преимуществом использования регулярных выражений является работа с такими сложными случаями, как &
Замена сущности не повторяется, это один шаг. Регулярное выражение также будет довольно эффективным: два ведущих символа исправлены, поэтому он быстро пропустит все, что не начинается с &#
, Наконец, решение для регулярных выражений - одно без сюрпризов для будущих сопровождающих.
Я бы сказал, что регулярное выражение было правильным выбором.
Хотя это лучшее регулярное выражение? Вы знаете, что вам нужно две цифры, и если у вас есть 3 цифры, первая будет 1. Распечатанный ASCII в конце концов  -~
, По этой причине вы могли бы рассмотреть ?\d\d;
,
Что касается замены контента, я бы использовал базовый алгоритм, описанный для boost:: regex:: replace:
For each match // Using regex_iterator<>
Print the prefix of the match
Remove the first 2 and last character of the match (&#;)
lexical_cast the result to int, then truncate to char and append.
Print the suffix of the last match.
* Repaired SNOBOL4 Solution
* &#38; -> &
digit = '0123456789'
main line = input :f(end)
result =
swap line arb . l
+ '&#' span(digit) . n ';' rem . line :f(out)
result = result l char(n) :(swap)
out output = result line :(main)
end
Это, вероятно, принесет мне несколько отрицательных голосов, поскольку это не ответ C++, Boost или Regex, а вот решение SNOBOL. Этот работает для ASCII. Я работаю над чем-то для Unicode.
NUMS = '1234567890'
MAIN LINE = INPUT :F(END)
SWAP LINE ? '&#' SPAN(NUMS) . N ';' = CHAR( N ) :S(SWAP)
OUTPUT = LINE :(MAIN)
END
Я не знаю о поддержке регулярных выражений в boost, но проверь, есть ли у него метод replace(), который поддерживает обратные вызовы, лямбда-выражения или что-то подобное. Это обычный способ сделать это с помощью регулярных выражений на других языках, я бы сказал.
Вот реализация Python:
s = "Hello, here's a test colon:. Here's a test semi-colon;"
re.sub(r'&#(1?\d\d);', lambda match: chr(int(match.group(1))), s)
Производство:
"Hello, here's a test colon:. Here's a test semi-colon;"
Сейчас я посмотрел на boost и вижу, что в нем есть функция regex_replace. Но C++ действительно смущает меня, поэтому я не могу понять, можно ли использовать обратный вызов для замены части. Но строка, соответствующая группе (\d\d), должна быть доступна в $1, если я правильно прочитал документацию. Я бы проверил это, если бы я использовал повышение.
Структура генератора boost:: spirit parser позволяет легко создавать синтаксический анализатор, который преобразует нужные NCR.
// spirit_ncr2a.cpp
#include <iostream>
#include <string>
#include <boost/spirit/include/classic_core.hpp>
int main() {
using namespace BOOST_SPIRIT_CLASSIC_NS;
std::string line;
while (std::getline(std::cin, line)) {
assert(parse(line.begin(), line.end(),
// match "&#(\d+);" where 32 <= $1 <= 126 or any char
*(("&#" >> limit_d(32u, 126u)[uint_p][&putchar] >> ';')
| anychar_p[&putchar])).full);
putchar('\n');
}
}
- компиляции:
$ g ++ -I / path / to / boost -o spirit_ncr2a spirit_ncr2a.cpp
- бежать:
$ echo "Здравствуйте, & # 12; вот пробное двоеточие & # 58;." | spirit_ncr2a
- выход:
"Здравствуйте, & # 12; вот пробное двоеточие:."
Вот еще одна строчка Perl (см . Ответ @mrree):
- тестовый файл:
$ cat ent.txt Здравствуйте, & # 12; вот пробная кишка & # 58;. Вот тестовая точка с запятой & # 59; '& # 131;'
- однострочник:
$ perl -pe's ~ & # (1?\d\d);~ > sub{ return chr($1) if (31 <$ 1 && $ 1 <127); $ &} -> () ~ например ' ent.txt
- или используя более конкретное регулярное выражение:
$ perl -pe "s ~ & # (1(?:[01][0-9]|2[0-6])|3[2-9]|[4-9][0-9]);~chr($1)~ например " ent.txt
- оба однострочника дают одинаковый результат:
Здравствуйте, & # 12; вот тестовая толстая кишка. Вот тестовая точка с запятой; '& # 131;'
Я знаю, что пока мы не в теме, у подстановки perl есть опция 'e'. Как в выражении выражения. Например
echo "Здравствуйте, вот пробная двоеточие & # 58;. Вот пробная точка с запятой & # 59;
Дальнейшее тестирование #65; а & # 126;..def ".
| perl -we 'sub translate {my $ x = $ _ [0]; if (($ x> = 32) && ($ x <= 126))
{return sprintf ("% c", $ x); } else {return "& #". $ x. ";"; }}
while (<>) {s / & # (1?\d\d);/&translate($1)/ge; Распечатать; }"
Pretty-печать, что:
#!/usr/bin/perl -w
sub translate
{
my $x=$_[0];
if ( ($x >= 32) && ($x <= 126) )
{
return sprintf( "%c", $x );
}
else
{
return "&#" . $x . ";" ;
}
}
while (<>)
{
s/&#(1?\d\d);/&translate($1)/ge;
print;
}
Хотя Perl и есть Perl, я уверен, что есть гораздо лучший способ написать это...
Вернуться к C-коду:
Вы также можете свернуть свой собственный конечный автомат. Но это становится грязным и хлопотным, чтобы поддержать позже.
Существующие решения SNOBOL не обрабатывают случай с несколькими шаблонами должным образом, потому что существует только один "&". Следующее решение должно работать лучше:
dd = "0123456789"
ccp = "#" span(dd) $ n ";" *?(s = s char(n)) fence (*ccp | null)
rdl line = input :f(done)
repl line "&" *?(s = ) ccp = s :s(repl)
output = line :(rdl)
done
end
Я действительно думал, что хорошо разбираюсь в регулярных выражениях, но я никогда не видел, чтобы лямбды использовались в регулярных выражениях, пожалуйста, просветите меня!
В настоящее время я использую Python и решил бы это с помощью этого oneliner:
''.join([x.isdigit() and chr(int(x)) or x for x in re.split('&#(\d+);',THESTRING)])
Имеет ли это смысл?
Вот версия, основанная на boost::regex_token_iterator
, Программа заменяет десятичные числа NCR, считанные с stdin
соответствующими символами ASCII и печатает их в stdout
,
#include <cassert>
#include <iostream>
#include <string>
#include <boost/lexical_cast.hpp>
#include <boost/regex.hpp>
int main()
{
boost::regex re("&#(1(?:[01][0-9]|2[0-6])|3[2-9]|[4-9][0-9]);"); // 32..126
const int subs[] = {-1, 1}; // non-match & subexpr
boost::sregex_token_iterator end;
std::string line;
while (std::getline(std::cin, line)) {
boost::sregex_token_iterator tok(line.begin(), line.end(), re, subs);
for (bool isncr = false; tok != end; ++tok, isncr = !isncr) {
if (isncr) { // convert NCR e.g., ':' -> ':'
const int d = boost::lexical_cast<int>(*tok);
assert(32 <= d && d < 127);
std::cout << static_cast<char>(d);
}
else
std::cout << *tok; // output as is
}
std::cout << '\n';
}
}
Вот сканер NCR, созданный с помощью Flex:
/** ncr2a.y: Replace all NCRs by corresponding printable ASCII characters. */
%%
&#(1([01][0-9]|2[0-6])|3[2-9]|[4-9][0-9]); { /* accept 32..126 */
/**recursive: unput(atoi(yytext + 2)); skip '&#'; `atoi()` ignores ';' */
fputc(atoi(yytext + 2), yyout); /* non-recursive version */
}
Чтобы сделать исполняемый файл:
$ flex ncr2a.y
$ gcc -o ncr2a lex.yy.c -lfl
Пример:
$ echo "Hello,  here's a test colon:.
> Here's a test semi-colon; 'ƒ'
> &#59; <-- may be recursive" \
> | ncr2a
Он печатает для нерекурсивной версии:
Здравствуйте, & # 12; вот тестовая толстая кишка. Вот тестовая точка с запятой; '& # 131;' & # 59;<- может быть рекурсивным
И рекурсивный производит:
Здравствуйте, & # 12; вот тестовая толстая кишка. Вот тестовая точка с запятой; '& # 131;';<- может быть рекурсивным
Это один из тех случаев, когда первоначальная формулировка проблемы, по-видимому, не очень полная, но если вы действительно хотите запускать только те случаи, в которых используются символы от 32 до 126, это тривиальное изменение в решении, которое я опубликовал ранее. Обратите внимание, что мое решение также обрабатывает случай с несколькими шаблонами (хотя эта первая версия не будет обрабатывать случаи, когда некоторые из соседних шаблонов находятся в диапазоне, а другие - нет).
dd = "0123456789"
ccp = "#" span(dd) $ n *lt(n,127) *ge(n,32) ";" *?(s = s char(n))
+ fence (*ccp | null)
rdl line = input :f(done)
repl line "&" *?(s = ) ccp = s :s(repl)
output = line :(rdl)
done
end
Не было бы особенно трудно разобраться с этим случаем (например;#131;#58; также производит ";#131;:":
dd = "0123456789"
ccp = "#" (span(dd) $ n ";") $ enc
+ *?(s = s (lt(n,127) ge(n,32) char(n), char(10) enc))
+ fence (*ccp | null)
rdl line = input :f(done)
repl line "&" *?(s = ) ccp = s :s(repl)
output = replace(line,char(10),"#") :(rdl)
done
end