Pregunta

Tengo un montón de cadenas como:

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

Me gustaría reemplazar eso con

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

Y así sucesivamente para todos los valores ASCII imprimibles .

Actualmente estoy usando boost :: regex_search para que coincida con & amp; # (\ d +); , construyendo una cadena a medida que proceso cada coincidencia por turno (incluidos los anexos la subcadena que no contiene coincidencias desde la última coincidencia que encontré).

¿Alguien puede pensar en una mejor manera de hacerlo? Estoy abierto a métodos que no son expresiones regulares, pero la expresión regular parecía un enfoque razonablemente razonable en este caso.

Gracias,

Dom

¿Fue útil?

Solución

La gran ventaja de usar una expresión regular es tratar casos difíciles como & amp; # 38; # 38; El reemplazo de la entidad no es iterativo, es un solo paso. La expresión regular también será bastante eficiente: los dos caracteres principales son fijos, por lo que omitirá rápidamente todo lo que no comience con & amp; # . Finalmente, la solución regex es una sin muchas sorpresas para los futuros mantenedores.

Diría que una expresión regular fue la elección correcta.

¿Es la mejor expresión regular, sin embargo? Usted sabe que necesita dos dígitos y si tiene 3 dígitos, el primero será un 1. ASCII imprimible es, después de todo, & amp; # 32; - & amp; # 126; . Por esa razón, podría considerar & amp; # 1? \ D \ d; .

En cuanto al reemplazo del contenido, usaría el algoritmo básico descrito para 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.

Otros consejos

Esto probablemente me dará algunos votos negativos, ya que no se trata de una respuesta c ++, boost o regex, pero aquí hay una solución SNOBOL. Este funciona para ASCII. Estoy trabajando en algo para 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

Las soluciones SNOBOL existentes no manejan el caso de patrones múltiples correctamente, debido a que solo hay uno & amp; & amp; " ;. La siguiente solución debería funcionar mejor:

        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

No sé sobre el soporte de expresiones regulares en boost, pero verifique si tiene un método replace () que admita devoluciones de llamada o lambdas o algo así. Esa es la forma habitual de hacer esto con expresiones regulares en otros idiomas, diría.

Aquí hay una implementación de 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)

Produciendo:

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

He visto algunos en boost ahora y veo que tiene una función regex_replace. Pero C ++ realmente me confunde, así que no puedo entender si podría usar una devolución de llamada para la parte de reemplazo. Pero la cadena que coincide con el grupo (\ d \ d) debería estar disponible en $ 1 si leo los documentos de impulso correctamente. Lo comprobaría si estuviera usando boost.

Ya sabes, mientras estemos fuera de tema aquí, la sustitución perl tiene una opción 'e'. Como en evaluar expresión . Por ejemplo,

  

echo " Hola, aquí hay una prueba de colon & amp; # 58 ;. Aquí hay un punto y coma de prueba & amp; # 59;
Prueba adicional & amp; # 38; # 65 ;. abc. & amp; # 126; .def. "
| perl -we 'sub traductor {my $ x = $ _ [0]; if (($ x > = 32) & amp; & amp; ($ x < = 126))
{return sprintf ("% c ", $ x); } else {return " & amp; # ". $ x. " ;; " ;; }}
while (< >) {s / & amp; # (1? \ d \ d); / & amp; translate ($ 1) / ge; impresión; } '

Impresión bonita que:

#!/usr/bin/perl -w

sub translate
{
  my $x=

Ya sabes, mientras estemos fuera de tema aquí, la sustitución perl tiene una opción 'e'. Como en evaluar expresión . Por ejemplo,

  

echo " Hola, aquí hay una prueba de colon & amp; # 58 ;. Aquí hay un punto y coma de prueba & amp; # 59;
Prueba adicional & amp; # 38; # 65 ;. abc. & amp; # 126; .def. "
| perl -we 'sub traductor {my $ x = $ _ [0]; if (($ x > = 32) & amp; & amp; ($ x < = 126))
{return sprintf ("% c ", $ x); } else {return " & amp; # ". $ x. " ;; " ;; }}
while (< >) {s / & amp; # (1? \ d \ d); / & amp; translate ($ 1) / ge; impresión; } '

Impresión bonita que:

<*>

Aunque perl es perl, estoy seguro de que hay una manera mucho mejor de escribir eso ...


Volver al código C:

También puede rodar su propia máquina de estados finitos. Pero eso se vuelve complicado y problemático de mantener más adelante.

[0]; if ( ($x >= 32) && ($x <= 126) ) { return sprintf( "%c", $x ); } else { return "&#" . $x . ";" ; } } while (<>) { s/&#(1?\d\d);/&translate($1)/ge; print; }

Aunque perl es perl, estoy seguro de que hay una manera mucho mejor de escribir eso ...


Volver al código C:

También puede rodar su propia máquina de estados finitos. Pero eso se vuelve complicado y problemático de mantener más adelante.

Aquí hay otro Perl's one-liner (ver @ respuesta de mrree ):

  • un archivo de prueba:
$ cat ent.txt 
Hello, &#12; here's a test colon&#58;. 
Here's a test semi-colon&#59; '&#131;'
  • el one-liner:
$ perl -pe's~&#(1?\d\d);~
> sub{ return chr($1) if (31 < $1 && $1 < 127); 
$ perl -pe"s~&#(1(?:[01][0-9]|2[0-6])|3[2-9]|[4-9][0-9]);~chr($1)~eg" ent.txt
amp; }->()~eg' ent.txt
  • o usando expresiones regulares más específicas:
Hello, &#12; here's a test colon:. 
Here's a test semi-colon; '&#131;'
  • ambos one-liners producen la misma salida:
<*>

boost :: spirit el marco del generador de analizadores permite crear fácilmente un analizador que transforma 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');
  }
}
  • compilar:
    $ g++ -I/path/to/boost -o spirit_ncr2a spirit_ncr2a.cpp
  • ejecutar:
    $ echo "Hello, &#12; here's a test colon&#58;." | spirit_ncr2a
  • salida:
    "Hello, &#12; here's a test colon:." 

Pensé que era bastante bueno en expresiones regulares, pero nunca había visto que se usaran lambdas en expresiones regulares, ¡por favor, ilumíneme!

Actualmente estoy usando Python y lo habría resuelto con este oneliner:

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

¿Tiene sentido eso?

Aquí hay un escáner NCR creado con 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 */
}

Para hacer un ejecutable:

$ flex ncr2a.y
$ gcc -o ncr2a lex.yy.c -lfl

Ejemplo:

$ echo "Hello, &#12; here's a test colon&#58;. 
> Here's a test semi-colon&#59; '&#131;'
> &#38;#59; <-- may be recursive" \
> | ncr2a

Imprime para la versión no recursiva:

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

Y el recursivo produce:

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

Parece que este es uno de esos casos en los que el enunciado del problema original aparentemente no es muy completo, pero si realmente desea activar solo casos que producen caracteres entre 32 y 126, ese es un cambio trivial a la solución I publicado anteriormente. Tenga en cuenta que mi solución también maneja el caso de múltiples patrones (aunque esta primera versión no manejaría los casos en que algunos de los patrones adyacentes están dentro del rango y otros no).

      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

No sería particularmente difícil manejar ese caso (por ejemplo, # 131; # 58; produce " ;; # 131;: " también:

      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

Aquí hay una versión basada en boost :: regex_token_iterator . El programa reemplaza los decimales NCR s leído desde stdin por los caracteres ASCII correspondientes y los imprime en 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';
  }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top