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

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

Спасибо,

Дом

Число в объектах НЕ является ASCII. Это номер кодовой точки Unicode, который может находиться вне диапазона 0–255.

Kornel 09.01.2009 16:33

... в этом случае мы, вероятно, можем оставить его нетронутым. (Кстати, диапазон печатаемых ASCII - 32-126)

MSalters 09.01.2009 16:47

Я не удивлюсь, если уже существует библиотека (возможно, даже как часть повышения) для преобразования XML-сущностей в их эквиваленты utf-8 / utf-16.

Powerlord 09.01.2009 18:32

Если есть, хорошо знать, что формальное имя этих сущностей - это числовая символьная ссылка (NCR). См. en.wikipedia.org/wiki/Numeric_character_reference - PEZ

PEZ 09.01.2009 18:50
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
4
5 919
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

Я не знаю о поддержке регулярных выражений в boost, но проверьте, есть ли у него метод replace (), который поддерживает обратные вызовы, лямбды или что-то в этом роде. Я бы сказал, что это обычный способ сделать это с регулярными выражениями на других языках.

Вот реализация Python:

s = "Hello, here's a test colon:. 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, если я правильно прочитал документы о повышении. Я бы проверил это, если бы использовал наддув.

Я добавил "1?" в регулярное выражение.

jfs 10.01.2009 16:42

lamdba .. не прав. Он заменяет непечатаемые символы ASCII, например, '& # 07'. Примечание. c можно распечатать в формате ASCII, если 31 <ord (c) <127 (для документов html).

jfs 10.01.2009 19:36

Да, в основном это был пример подхода.

PEZ 10.01.2009 19:56

Чтобы исправить код, не затрагивая лямбда, вы можете использовать более ограничительное регулярное выражение, например, r '& # (3 [2-9] | [4-9] \ d | 1 (?: [01] \ d | 2 [0 -6])); ' См. stackoverflow.com/questions/428013/…

jfs 12.01.2009 18:54

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

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

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

Это имеет какое-либо значение?

Если вы стремитесь к содержательности (питости?), Вы можете убрать скобки []. К вашему сведению.

recursive 09.01.2009 18:26

Я добавил к своему ответу пример Python, если вам все еще интересно. Вместо лямбды вы можете вызвать именованную / обычную функцию.

PEZ 10.01.2009 03:06

Ваш ответ имеет смысл, но я думаю, что проще сделать re.sub ().

PEZ 10.01.2009 03:07
Ответ принят как подходящий

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

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

Но разве это лучшее регулярное выражение? Вы знаете, что вам нужны две цифры, и если у вас есть 3 цифры, первая будет 1. Печатный ASCII - это, в конце концов, &#32;-&#126;. По этой причине вы можете рассмотреть &#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.

Хорошее предложение по & # 1? \ D \ d - спасибо (и +1). Можете ли вы придумать способ выполнить замену с помощью регулярного выражения?

Dominic Rodger 09.01.2009 17:27

Спасибо - алгоритм, который вы указали, примерно соответствует тому, что я сделал (хотя я использовал & # (1? \ D \ d);, чтобы разрешить мне доступ к числовому значению без удаления символов). Выполнение полученного алгоритма 100000 раз над строкой из 320 символов с 20 заменяемыми значениями занимает 10 секунд. Хороший!

Dominic Rodger 09.01.2009 19:56

О, если скорость имеет значение, злоупотребляйте тем фактом, что вы уже подтвердили свой ввод. Результатом будет ((match [5] == ';')? (Match [3] * 10 + match [4]): (100 + match [4] * 10 + match [5])) -'0 '* 11

MSalters 14.01.2009 16:54

Это, вероятно, принесет мне несколько голосов против, поскольку это не ответ 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

PEZ 09.01.2009 20:07

Это не так. Он будет декодировать "& # 38; # 65;" на "A" вместо "& # 65;"

Andru Luvisi 09.01.2009 20:46

@Glomek прав, я думаю, что правильный код может быть SWAP LINE? REM '& #' SPAN (NUMS). N ';' = СИМВОЛ (N): S (SWAP)

user3458 09.01.2009 21:01

Это вообще не меняет входную строку.

Andru Luvisi 09.01.2009 21:31
* 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

"& & # 65;" => «A» вместо «& A»

Andru Luvisi 09.01.2009 21:56

Я пытался сделать его более эффективным - явно не сработало: попробуйте «arb» вместо «break ('&')»

FredW 10.01.2009 00:28

Я внес изменения в ваш пост. Пока я не нашел ввода, который ломает эту версию.

Andru Luvisi 10.01.2009 00:39

+1 - Я полностью впечатлен тем, что программисты SNOBOL все еще существуют - я люблю этот язык, но не использовал его более 30 лет.

Ken Paul 10.01.2009 00:57

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

одинарная кавычка после # должна быть двойной кавычкой. Извините.

Gordon Peterson 10.01.2009 00:55

Это преобразует "& # 38; # 38;" в "&&", а не "& # 38;" как это должно. Он также преобразует "& # 65 & # 59;" на «А», а не на «& # 65;» как и должно, и он не работает с кодами в конце строки, если & fullscan не включен.

Andru Luvisi 10.01.2009 03:26

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

echo "Hello, here's a test colon&#58;. Here's a test semi-colon&#59;
Further test &#38;#65;. abc.&#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; 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/…

jfs 10.01.2009 19:37

JF: Ммм, спасибо. Мне было интересно, почему код Perl больше не запускается. Я подумал, что, должно быть, закодировал сон, когда использовал одинарные кавычки вместо двойных кавычек в этом эхо. (Сейчас это исправлено.)

Mr.Ree 11.01.2009 08:26

Для корректного отображения примеров замените в разметке &# на &amp;#. Кстати, одинарные кавычки отлично работают с echo.

jfs 12.01.2009 18:48

"& amp;": я не понимал, что SO неправильно переводит "&". Я добавил несколько символов '\\', чтобы избежать неправильной интерпретации других символов. Одиночные / двойные кавычки по-разному влияют на TCSH / BASH. В данном конкретном случае текст заключен в одинарные кавычки. echo 'вот a' vs echo "вот a".

Mr.Ree 13.01.2009 01:52

Вот еще один однострочник 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;'

Очень умный! Я впечатлен! Один раз совет: вы можете использовать '|' скорее, чем '!' в качестве символа-разделителя s //, если вы планируете запускать это в командной строке в CSH / TCSH. (! является особенным даже внутри одинарных кавычек.)

Mr.Ree 10.01.2009 23:18

@mrree: Я заменил '!' пользователем '~'.

jfs 11.01.2009 03:50

Вот сканер 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

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

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. Программа заменяет десятичные 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';
  }
}

Структура генератора парсера 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, &#12; here's a test colon&#58;." | spirit_ncr2a
  • выход:
    "Hello, &#12; here's a test colon:." 

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