Frage

Ich habe ein paar Saiten wie:

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

Ich möchte das durch ersetzen

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

Und so weiter für alle druckbare ASCII -Werte.

Gegenwärtig benutze ich boost::regex_search passen &#(\d+);, Aufbau einer Zeichenfolge, während ich jeweils nacheinander verarbeite (einschließlich Anhänge des Substrings, der seit dem letzten Spiel, das ich gefunden habe, keine Übereinstimmungen enthält).

Kann jemand an eine bessere Möglichkeit denken, dies zu tun? Ich bin offen für Nicht-Regex-Methoden, aber Regex schien in diesem Fall ein ziemlich vernünftiger Ansatz zu sein.

Vielen Dank,

Dom

War es hilfreich?

Lösung

Der große Vorteil der Verwendung eines Regex besteht darin, sich mit den schwierigen Fällen wie zu befassen & Entitätsersatz ist nicht iterativ, es ist ein einziger Schritt. Der Regex wird auch ziemlich effizient sein: Die beiden Lead -Zeichen sind festgelegt, sodass schnell etwas überspringt, das nicht beginnt, nicht mit &#. Schließlich ist die Regex -Lösung eine ohne viel Überraschungen für zukünftige Betreuer.

Ich würde sagen, ein Regex war die richtige Wahl.

Ist es allerdings das beste Regex? Sie wissen, dass Sie zwei Ziffern brauchen, und wenn Sie 3 Ziffern haben  -~. Aus diesem Grund könnten Sie berücksichtigen &#1?\d\d;.

Zum Ersetzen des Inhalts würde ich die verwenden Grundalgorithmus für Boost :: Regex :: Ersetzen :

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.

Andere Tipps

Dies wird mir wahrscheinlich einige Down -Stimmen einbringen, da dies keine C ++, Boost- oder Regex -Antwort ist, sondern hier eine Snobol -Lösung. Dieser funktioniert für ASCII. Ich arbeite an etwas für 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

Die vorhandenen Snobol-Lösungen verarbeiten das Multiple-Muster-Fall nicht ordnungsgemäß, da nur ein "&" vorhanden ist. Die folgende Lösung sollte besser funktionieren:

        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

Ich weiß nichts über den Regex -Support in Boost, aber prüfen Sie, ob sie eine Ersatz () -Methode hat, die Rückrufe oder Lambdas oder solche unterstützt. Das ist der übliche Weg, dies mit Regexes in anderen Sprachen zu tun, die ich sagen würde.

Hier ist eine Python -Implementierung:

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)

Produktion:

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

Ich habe jetzt einige nach Boost gesucht und sehe, dass es eine Regex_Replace -Funktion hat. Aber C ++ verwirrt mich wirklich, sodass ich nicht herausfinden kann, ob Sie einen Rückruf für den Ersatzteil verwenden könnten. Die von der ( d d) Gruppe übereinstimmende Zeichenfolge sollte jedoch in $ 1 verfügbar sein, wenn ich die Boost -Dokumente richtig gelesen habe. Ich würde es überprüfen, wenn ich Boost benutze.

Sie wissen, solange wir hier aus dem Thema sind, hat die Perl -Substitution eine "e" -Aption. Wie in Ausdruck bewerten. Z.B

Echo "Hallo, hier ist ein Testdarmdarm: Hier ist ein Test-Semikolon;
Weitere Test &#65;. ABC. ~ .DEF. "
| perl -wir 'sub übersetzt {my $ x = $ _ [0]; if (($ x> = 32) && ($ x <= 126))
{return sprintf ("%c", $ x); } else {return "&#". $ x. ";"; }}
while (<>) {s/&#(1? d d);/& Translate ($ 1)/Ge; drucken; } '

Ziemlich drücken:

#!/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;
}

Obwohl Perl Perl ist, bin ich sicher, dass es eine viel bessere Möglichkeit gibt, das zu schreiben ...


Zurück zum C -Code:

Sie können auch Ihre eigene endliche Staatsmaschine rollen. Aber das wird später chaotisch und mühsam, später beizubehalten.

Hier ist der Einzeiler eines anderen Perls (siehe @Mrrees Antwort):

  • Eine Testdatei:
$ cat ent.txt 
Hello, &#12; here's a test colon&#58;. 
Here's a test semi-colon&#59; '&#131;'
  • der Einzeiler:
$ perl -pe's~&#(1?\d\d);~
> sub{ return chr($1) if (31 < $1 && $1 < 127); $& }->()~eg' ent.txt
  • oder verwenden spezifischere Regex:
$ perl -pe"s~&#(1(?:[01][0-9]|2[0-6])|3[2-9]|[4-9][0-9]);~chr($1)~eg" ent.txt
  • Beide Einzeiler erzeugen den gleichen Ausgang:
Hello, &#12; here's a test colon:. 
Here's a test semi-colon; '&#131;'

Boost :: Geist Das Parser -Generator -Framework ermöglicht es einfach, einen Parser zu erstellen, der sich wünschenswert verwandelt Ncrs.

// 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');
  }
}
  • kompilieren:
    $ g++ -I/path/to/boost -o spirit_ncr2a spirit_ncr2a.cpp
  • Lauf:
    $ echo "Hello, &#12; here's a test colon&#58;." | spirit_ncr2a
  • Ausgang:
    "Hello, &#12; here's a test colon:." 

Ich dachte, ich wäre ziemlich gut in Regex, aber ich habe noch nie gesehen, dass Lambdas in Regex verwendet wurde, bitte erleuchten Sie mich!

Ich benutze derzeit Python und hätte es mit diesem Oneliner gelöst:

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

Macht das irgendeinen Sinn?

Hier ist ein NCR -Scanner, der mit dem mit dem mit Biegen:

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

Eine ausführbare Datei machen:

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

Beispiel:

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

Es druckt für eine nicht rezisive Version:

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

Und der rekursive produziert:

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

Dies ist einer der Fälle, in denen die ursprüngliche Problemaussage anscheinend nicht sehr vollständig ist, aber wenn Sie wirklich nur auf Fälle auslösen möchten, die Zeichen zwischen 32 und 126 erzeugen, ist dies eine triviale Änderung der Lösung, die ich zuvor gepostet habe. Beachten Sie, dass meine Lösung auch mit dem Fall mehrerer Muster behandelt wird (obwohl diese erste Version keine Fälle behandelt, in denen einige der benachbarten Muster im Bereich sind und andere nicht).

      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

Es wäre nicht besonders schwierig, diesen Fall zu bewältigen (z. B.#131;#58; produziert ";#131 ;:" auch:

      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

Hier ist eine Version basierend auf boost::regex_token_iterator. Das Programm ersetzt die Dezimalzahlung Ncrs lesen aus stdin durch entsprechende ASCII -Zeichen und druckt sie auf 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';
  }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top