Regex Sostituzione & amp; # 58; a & # 8220;: & # 8221; eccetera
Domanda
Ho un sacco di stringhe come:
"Hello, here's a test colon:. Here's a test semi-colon;"
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
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; -> &
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:. Here's a test semi-colon;"
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,  here's a test colon:. Here's a test semi-colon; 'ƒ'
- 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.txtamp; }->()~eg' ent.txt
- o usando regex più specifico:
Hello,  here's a test colon:. Here's a test semi-colon; 'ƒ'
- 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,  here's a test colon:." | spirit_ncr2a
- uscita:
"Hello,  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,  here's a test colon:.
> Here's a test semi-colon; 'ƒ'
> &#59; <-- may be recursive" \
> | ncr2a
Stampa per la versione non ricorsiva:
Hello,  here's a test colon:. Here's a test semi-colon; 'ƒ' ; <-- may be recursive
E quello ricorsivo produce:
Hello,  here's a test colon:. Here's a test semi-colon; 'ƒ' ; <-- 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., ':' -> ':'
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';
}
}