Вопрос

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

"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+);, создавая строку по мере обработки каждого совпадения по очереди (включая добавление подстроки, не содержащей совпадений с момента последнего найденного мной совпадения).

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

Спасибо,

Дом

Это было полезно?

Решение

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

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

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

Другие советы

Это, вероятно, принесет мне несколько отрицательных голосов, поскольку это не ответ на С++, повышение или регулярное выражение, а вот решение SNOBOL.Этот работает для ASCII.Я работаю над чем-то для Unicode.

        NUMS = '1234567890'
MAIN    LINE = INPUT                                :F(END)
SWAP    LINE ?  '&#' SPAN(NUMS) . N ';' = CHAR( N ) :S(SWAP)
        OUTPUT = LINE                               :(MAIN)
END
* 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

Существующие решения 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

Я не знаю о поддержке регулярных выражений в 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.

Знаете, раз уж мы здесь не по теме, у замены Perl есть опция «e».Как в оценить выражение.Например.

echo "Здравствуйте, вот тестовое двоеточие:.Вот тестовая точка с запятой;
Дальнейшее тестирование A.abc.~.def."
| perl -мы 'sub переводим { my $x=$_[0];if ( ($x >= 32) && ($x <= 126) )
{ 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:

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

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

  • тестовый файл:
$ 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;'

повышение::дух Фреймворк генератора парсеров позволяет легко создать парсер, который преобразует желаемые НКРс.

// 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:." 

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

В настоящее время я использую Python и решил бы эту проблему с помощью этого однострочного текста:

''.join([x.isdigit() and chr(int(x)) or x for x in re.split('&#(\d+);',THESTRING)])

Имеет ли это смысл?

Вот сканер NCR, созданный с использованием Гибкий:

/** 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

Он печатает для нерекурсивной версии:

Hello, &#12; here's a test colon:.
Here's a test semi-colon; '&#131;'
&#59; <-- may be recursive

И рекурсивный производит:

Hello, &#12; here's a test colon:.
Here's a test semi-colon; '&#131;'
; <-- 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.Программа заменяет десятичные числа НКРпрочитал из 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';
  }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top