У меня есть несколько строк вроде:
"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+);, создавая строку, поскольку я обрабатываю каждое совпадение по очереди (включая добавление подстроки, не содержащей совпадений с момента последнего найденного совпадения).
Кто-нибудь может придумать лучший способ сделать это? Я открыт для методов, не являющихся регулярными выражениями, но в данном случае регулярное выражение казалось разумным подходом.
Спасибо,
Дом
... в этом случае мы, вероятно, можем оставить его нетронутым. (Кстати, диапазон печатаемых ASCII - 32-126)
Я не удивлюсь, если уже существует библиотека (возможно, даже как часть повышения) для преобразования XML-сущностей в их эквиваленты utf-8 / utf-16.
Если есть, хорошо знать, что формальное имя этих сущностей - это числовая символьная ссылка (NCR). См. en.wikipedia.org/wiki/Numeric_character_reference - PEZ





Я не знаю о поддержке регулярных выражений в 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, если я правильно прочитал документы о повышении. Я бы проверил это, если бы использовал наддув.
Я добавил "1?" в регулярное выражение.
lamdba .. не прав. Он заменяет непечатаемые символы ASCII, например, '& # 07'. Примечание. c можно распечатать в формате ASCII, если 31 <ord (c) <127 (для документов html).
Да, в основном это был пример подхода.
Чтобы исправить код, не затрагивая лямбда, вы можете использовать более ограничительное регулярное выражение, например, r '& # (3 [2-9] | [4-9] \ d | 1 (?: [01] \ d | 2 [0 -6])); ' См. stackoverflow.com/questions/428013/…
Я действительно думал, что неплохо разбираюсь в регулярных выражениях, но я никогда не видел, чтобы лямбды использовались в регулярных выражениях, пожалуйста, просветите меня!
В настоящее время я использую python и решил бы это с помощью этого oneliner:
''.join([x.isdigit() and chr(int(x)) or x for x in re.split('&#(\d+);',THESTRING)])
Это имеет какое-либо значение?
Если вы стремитесь к содержательности (питости?), Вы можете убрать скобки []. К вашему сведению.
Я добавил к своему ответу пример Python, если вам все еще интересно. Вместо лямбды вы можете вызвать именованную / обычную функцию.
Ваш ответ имеет смысл, но я думаю, что проще сделать re.sub ().
Большим преимуществом использования регулярного выражения является возможность справиться с такими сложными случаями, как замена сущности &#38; не итеративно, это один шаг. Регулярное выражение также будет довольно эффективным: два ведущих символа фиксированы, поэтому он быстро пропустит все, что не начинается с &#. Наконец, решение с регулярными выражениями не преподнесет много сюрпризов для будущих сопровождающих.
Я бы сказал, что регулярное выражение было правильным выбором.
Но разве это лучшее регулярное выражение? Вы знаете, что вам нужны две цифры, и если у вас есть 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.
Хорошее предложение по & # 1? \ D \ d - спасибо (и +1). Можете ли вы придумать способ выполнить замену с помощью регулярного выражения?
Спасибо - алгоритм, который вы указали, примерно соответствует тому, что я сделал (хотя я использовал & # (1? \ D \ d);, чтобы разрешить мне доступ к числовому значению без удаления символов). Выполнение полученного алгоритма 100000 раз над строкой из 320 символов с 20 заменяемыми значениями занимает 10 секунд. Хороший!
О, если скорость имеет значение, злоупотребляйте тем фактом, что вы уже подтвердили свой ввод. Результатом будет ((match [5] == ';')? (Match [3] * 10 + match [4]): (100 + match [4] * 10 + match [5])) -'0 '* 11
Это, вероятно, принесет мне несколько голосов против, поскольку это не ответ 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
Я впечатлен. Не могу понять этот код вообще и теперь должен проверить СНОБОЛ. +1
Это не так. Он будет декодировать "& # 38; # 65;" на "A" вместо "& # 65;"
@Glomek прав, я думаю, что правильный код может быть SWAP LINE? REM '& #' SPAN (NUMS). N ';' = СИМВОЛ (N): S (SWAP)
Это вообще не меняет входную строку.
* 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
"& & # 65;" => «A» вместо «& A»
Я пытался сделать его более эффективным - явно не сработало: попробуйте «arb» вместо «break ('&')»
Я внес изменения в ваш пост. Пока я не нашел ввода, который ломает эту версию.
+1 - Я полностью впечатлен тем, что программисты SNOBOL все еще существуют - я люблю этот язык, но не использовал его более 30 лет.
Существующие решения 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
одинарная кавычка после # должна быть двойной кавычкой. Извините.
Это преобразует "& # 38; # 38;" в "&&", а не "& # 38;" как это должно. Он также преобразует "& # 65 & # 59;" на «А», а не на «& # 65;» как и должно, и он не работает с кодами в конце строки, если & fullscan не включен.
Я знаю, раз уж мы здесь не по теме, у замены perl есть опция 'e'. Как в оценить выражение. Например.
echo "Hello, here's a test colon:. Here's a test semi-colon;
Further test &#65;. abc.~.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; print; }'
Довольно печально, что:
#!/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:
Вы также можете запустить свой собственный конечный автомат. Но позже это становится запутанным и проблематичным.
@mrree: Я опубликовал еще одну однострочную версию Perl stackoverflow.com/questions/428013/…
JF: Ммм, спасибо. Мне было интересно, почему код Perl больше не запускается. Я подумал, что, должно быть, закодировал сон, когда использовал одинарные кавычки вместо двойных кавычек в этом эхо. (Сейчас это исправлено.)
Для корректного отображения примеров замените в разметке &# на &#. Кстати, одинарные кавычки отлично работают с echo.
"& amp;": я не понимал, что SO неправильно переводит "&". Я добавил несколько символов '\\', чтобы избежать неправильной интерпретации других символов. Одиночные / двойные кавычки по-разному влияют на TCSH / BASH. В данном конкретном случае текст заключен в одинарные кавычки. echo 'вот a' vs echo "вот a".
Вот еще один однострочник Perl (см. @ mrree ответ):
$ cat ent.txt Hello,  here's a test colon:. Here's a test semi-colon; 'ƒ'
$ perl -pe's~(1?\d\d);~
> sub{ return chr($1) if (31 < $1 && $1 < 127); $& }->()~eg' ent.txt
$ perl -pe"s~(1(?:[01][0-9]|2[0-6])|3[2-9]|[4-9][0-9]);~chr($1)~eg" ent.txt
Hello,  here's a test colon:. Here's a test semi-colon; 'ƒ'
Очень умный! Я впечатлен! Один раз совет: вы можете использовать '|' скорее, чем '!' в качестве символа-разделителя s //, если вы планируете запускать это в командной строке в CSH / TCSH. (! является особенным даже внутри одинарных кавычек.)
@mrree: Я заменил '!' пользователем '~'.
Вот сканер 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
Он печатает для нерекурсивной версии:
Hello,  here's a test colon:. Here's a test semi-colon; 'ƒ' ; <-- may be recursive
А рекурсивный дает:
Hello,  here's a test colon:. Here's a test semi-colon; 'ƒ' ; <-- may be recursive
Это один из тех случаев, когда исходная формулировка проблемы, по-видимому, не очень полная, но если вы действительно хотите запускать только те случаи, которые производят символы от 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
Вот версия на основе 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';
}
}
Структура генератора парсера boost :: spirit позволяет легко создать парсер, который преобразует желаемые 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 "Hello,  here's a test colon:." | spirit_ncr2a
"Hello,  here's a test colon:."
Число в объектах НЕ является ASCII. Это номер кодовой точки Unicode, который может находиться вне диапазона 0–255.