Domanda

Ho un sacco di stringhe come:

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

Vorrei sostituirlo con

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

E così via per tutti i valori ASCII stampabili .

Attualmente sto usando boost :: regex_search per abbinare & amp; # (\ d +); , costruendo una stringa mentre elaboro ogni partita a turno (incluso l'aggiunta la sottostringa non contiene corrispondenze dall'ultima corrispondenza che ho trovato).

Qualcuno può pensare a un modo migliore per farlo? Sono aperto a metodi non regex, ma in questo caso regex sembrava un approccio ragionevolmente ragionevole.

Grazie,

Dom

È stato utile?

Soluzione

Il grande vantaggio dell'uso di una regex è quello di affrontare i casi difficili come & amp; # 38; # 38; La sostituzione di entità non è iterativa, è un singolo passo. Anche il regex sarà abbastanza efficiente: i due personaggi principali sono fissi, quindi salterà rapidamente tutto ciò che non inizia con & amp; # . Infine, la soluzione regex è senza sorprese per i futuri manutentori.

Direi che una regex è stata la scelta giusta.

Ma è la migliore regex? Sai che hai bisogno di due cifre e se hai 3 cifre, la prima sarà una 1. ASCII stampabile è dopo tutto & amp; # 32; - & amp; # 126; . Per questo motivo, potresti prendere in considerazione & amp; # 1? \ D \ d; .

Per quanto riguarda la sostituzione del contenuto, utilizzerei algoritmo di base descritto per boost :: regex :: sostituisci :

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.

Altri suggerimenti

Questo probabilmente mi farà guadagnare qualche voto negativo, visto che questa non è una risposta c ++, boost o regex, ma ecco una soluzione SNOBOL. Questo funziona per ASCII. Sto lavorando a qualcosa per 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

Le soluzioni SNOBOL esistenti non gestiscono correttamente il caso a più schemi, poiché esiste solo un "& amp;" La seguente soluzione dovrebbe funzionare meglio:

        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

Non conosco il supporto regex in boost, ma controlla se ha un metodo replace () che supporta callback o lambdas o qualcosa del genere. Questo è il solito modo di farlo con regex in altre lingue, direi.

Ecco un'implementazione di 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)

In produzione:

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

Ho visto un po 'di boost ora e vedo che ha una funzione regex_replace. Ma C ++ mi confonde davvero, quindi non riesco a capire se è possibile utilizzare un callback per la parte di sostituzione. Ma la stringa corrispondente al gruppo (\ d \ d) dovrebbe essere disponibile in $ 1 se leggo correttamente i documenti boost. Verificherei se stessi usando boost.

Sai, finché non siamo fuori tema, la sostituzione perl ha un'opzione 'e'. Come in valuta espressione . Per es.

  

echo " Ciao, ecco un test due punti & amp; # 58 ;. Ecco un test semi-colon & amp; # 59;
Ulteriori test & amp; # 38; # 65 ;. . Abc & amp; # 126;. DEF "
| perl -we 'sub translate {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; stampare; } '

Stampa carina che:

#!/usr/bin/perl -w

sub translate
{
  my $x=

Sai, finché non siamo fuori tema, la sostituzione perl ha un'opzione 'e'. Come in valuta espressione . Per es.

  

echo " Ciao, ecco un test due punti & amp; # 58 ;. Ecco un test semi-colon & amp; # 59;
Ulteriori test & amp; # 38; # 65 ;. . Abc & amp; # 126;. DEF "
| perl -we 'sub translate {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; stampare; } '

Stampa carina che:

<*>

Sebbene perl sia perl, sono sicuro che ci sia un modo molto migliore per scriverlo ...


Torna al codice C:

Puoi anche lanciare la tua macchina a stati finiti. Ma diventa disordinato e problematico da mantenere in seguito.

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

Sebbene perl sia perl, sono sicuro che ci sia un modo molto migliore per scriverlo ...


Torna al codice C:

Puoi anche lanciare la tua macchina a stati finiti. Ma diventa disordinato e problematico da mantenere in seguito.

Ecco un'altra riga di Perl (vedi @ mrree's answer ):

  • un file di prova:
$ cat ent.txt 
Hello, &#12; here's a test colon&#58;. 
Here's a test semi-colon&#59; '&#131;'
  • 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 regex più specifico:
Hello, &#12; here's a test colon:. 
Here's a test semi-colon; '&#131;'
  • entrambi i liner a una riga producono lo stesso output:
<*>

boost :: spirit il framework del generatore di parser consente di creare facilmente un parser che trasforma le desiderabili 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');
  }
}
  • compilazione:
    $ g++ -I/path/to/boost -o spirit_ncr2a spirit_ncr2a.cpp
  • Esegui:
    $ echo "Hello, &#12; here's a test colon&#58;." | spirit_ncr2a
  • uscita:
    "Hello, &#12; here's a test colon:." 

Pensavo di essere abbastanza bravo con regex ma non ho mai visto lambda usati in regex, per favore illuminami!

Attualmente sto usando Python e lo avrei risolto con questo oneliner:

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

Ha senso?

Ecco uno scanner NCR creato utilizzando 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 */
}

Per creare un eseguibile:

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

Esempio:

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

Stampa per la versione non ricorsiva:

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

E quello ricorsivo produce:

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

Questo è uno di quei casi in cui la dichiarazione del problema originale apparentemente non è molto completa, a quanto pare, ma se vuoi davvero innescare solo casi che producono caratteri tra 32 e 126, è una banale modifica alla soluzione I pubblicato in precedenza. Nota che la mia soluzione gestisce anche il caso a più schemi (sebbene questa prima versione non gestisca i casi in cui alcuni dei modelli adiacenti sono nel range e altri 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

Non sarebbe particolarmente difficile gestire quel caso (ad esempio; # 131; # 58; produce " ;; # 131;: " pure:

      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

Ecco una versione basata su boost :: regex_token_iterator . Il programma sostituisce i decimali NCR letti da stdin con i corrispondenti caratteri ASCII e li stampa su 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';
  }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top