Regex Замена: на ":" и т. Д. - PullRequest
4 голосов
/ 09 января 2009

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

"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 ]

9 голосов
/ 09 января 2009

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

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

Хотя это лучшее регулярное выражение? Вы знаете, что вам нужно две цифры, и если у вас есть 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.
3 голосов
/ 09 января 2009
* 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
3 голосов
/ 09 января 2009

Это, вероятно, принесет мне несколько отрицательных голосов, поскольку это не ответ 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 голос
/ 21 января 2009

boost :: spirit Фреймворк генератора парсеров позволяет легко создавать парсер, который преобразует желаемое NCR s.

// 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, &#12; here's a test colon&#58;." | spirit_ncr2a
  • выход:
    "Hello, &#12; here's a test colon:." 
1 голос
/ 10 января 2009

Вот еще одна строка Perl (см. @ mrree's answer ):

  • тестовый файл:
$ cat ent.txt 
Hello, &#12; here's a test colon&#58;. 
Here's a test semi-colon&#59; '&#131;'
  • однострочник:
$ 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, &#12; here's a test colon:. 
Here's a test semi-colon; '&#131;'
1 голос
/ 10 января 2009

Знаете, пока мы здесь не по теме, у подстановки perl есть опция 'e'. Как и в , оцените выражение . Э.Г.

echo "Здравствуйте, вот пробная двоеточие: вот пробная точка с запятой;
Дальнейшая проверка & # 65 ;. abc. ~ .Def."
| perl -we 'sub translate {my $ x = $ _ [0]; if (($ x> = 32) && ($ x <= 126)) <br>{return sprintf ("% c", $ x); } else {return "& #". $ x. ";"; }}
while (<>) {s / & # (1? \ D \ d); / & translate ($ 1) / ge; Распечатать; } '

Красотка, которая:

#!/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:

Вы также можете запустить свой собственный конечный автомат. Но это становится грязным и хлопотным, чтобы поддержать позже.

1 голос
/ 10 января 2009

Существующие решения 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
1 голос
/ 09 января 2009

Я не знаю о поддержке регулярных выражений в 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, если я правильно прочитал документацию. Я бы проверил это, если бы я использовал повышение.

0 голосов
/ 21 января 2009

Вот версия, основанная на 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';
  }
}
0 голосов
/ 18 января 2009

Это один из тех случаев, когда первоначальное постановка задачи, по-видимому, не очень полно, кажется, но если вы действительно хотите запускать только в случаях, которые производят символы между 32 и 126, это тривиальное изменение в решении I опубликовано ранее. Обратите внимание, что мое решение также обрабатывает случай с несколькими шаблонами (хотя эта первая версия не будет обрабатывать случаи, когда некоторые из соседних шаблонов находятся в диапазоне, а другие - нет).

      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
...