Regex Replacing & # 58; в ":" и т. д.

У меня есть куча строк, как:

"Hello, here's a test colon:. Here's a test semi-colon&#59;"

Я хотел бы заменить это

"Hello, here's a test colon:. Here's a test semi-colon;"

И так далее для всех печатаемых значений ASCII.

В настоящее время я использую boost::regex_search соответствовать &#(\d+); создание строки при обработке каждого совпадения по очереди (включая добавление подстроки, не содержащей совпадений с момента последнего найденного совпадения).

Кто-нибудь может придумать лучший способ сделать это? Я открыт для методов, не являющихся регулярными выражениями, но в этом случае регулярное выражение показалось разумным подходом.

Спасибо,

Дом

12 ответов

Решение

Большим преимуществом использования регулярных выражений является работа с такими сложными случаями, как & Замена сущности не повторяется, это один шаг. Регулярное выражение также будет довольно эффективным: два ведущих символа исправлены, поэтому он быстро пропустит все, что не начинается с &#, Наконец, решение для регулярных выражений - одно без сюрпризов для будущих сопровождающих.

Я бы сказал, что регулярное выражение было правильным выбором.

Хотя это лучшее регулярное выражение? Вы знаете, что вам нужно две цифры, и если у вас есть 3 цифры, первая будет 1. Распечатанный ASCII в конце концов  -~, По этой причине вы могли бы рассмотреть &#1?\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;#38; -> &#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&#58;. Here's a test semi-colon&#59;"
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., '&#58;' -> ':'
        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, &#12; here's a test colon&#58;. 
> Here's a test semi-colon&#59; '&#131;'
> &#38;#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
Другие вопросы по тегам