2011-01-03 17 views
2

Ich habe das folgende Problem: Mein Programm ist ein InputStream übergeben, von dem ich den Inhalt nicht kontrollieren kann. Ich entpacke meinen Eingabestrom mithilfe der javax-Bibliothek, die zu Recht Ausnahmen auslöst, wenn der InputStream das Zeichen & nicht gefolgt von "amp;" enthält.Richtiger Weg zum Formatieren eines Eingabestreams

Die Abhilfe, die ich herauskam, war die folgende Klasse zu erstellen:

import java.io.ByteArrayInputStream; 
import java.io.FilterInputStream; 
import java.io.InputStream; 

/** 
* Provide an input stream where all & characters are properly encoded as & 
*/ 
public class FormattedStream extends FilterInputStream { 
    public FormattedStream(InputStream src) { 
    super(new ByteArrayInputStream(StringUtil.toString(src) 
     .replace("&", "&").replace("amp;amp;", "amp;").getBytes())); 
    } 
} 

Hinweis: StringUtil ist ein einfaches Dienstprogramm, das ich ein Eingangsstrom in einen String abbiegen.

Mit dieser Klasse anstelle rufe ich jetzt die JAXB Unmarshaller mit:

unmarshal(new FormattedStream(inputStream)); 

statt

unmarshal(inputStream); 

Dieser Ansatz funktioniert, aber nicht für ein paar Gründe seltsam erscheinen:

1 - Aufgrund der Einschränkung, dass super das erste Element im Konstruktor sein muss (Einschränkung, die ich trotz allem, was ich darüber gelesen habe, nicht verstehen kann), bin ich gezwungen, all meine Verarbeitung zu machen n eine Zeile, so dass der Code nicht lesbar ist.

2 - in einen String, den gesamten Strom umwandeln und zurück scheint Overkill zu einem Strom

3 - oberhalb Der Code ist leicht in einem falschen, dass ein Strom, enthaltend amp; amp; wird modifiziert, um amp zu enthalten;

Ich konnte 1 durch das Angebot einer FormatInputStream Klasse mit einer Methode bietet:

InputStream preProcess(InputStream inputStream) 

wo ich tun würde, die gleichen Operationen, die ich zur Zeit im Konstruktor meiner FormattedStream Klasse tue, aber es scheint seltsam zu wählen eine andere Schnittstelle wegen einer Codierungsbeschränkung.

konnte ich 2 Adresse von meinem FormattedStream Konstruktor einfach zu halten:

super(src) 

und Überschreiben der drei Lesemethoden, aber das würde viel mehr Codierung beinhalten: Überschreiben der drei Lesemethoden durch die & ersetzt on the fly nicht ist trivial im Vergleich zu der Ein-Zeile Code, den ich derzeit habe, wo ich die replaceAll String-Methode nutzen kann.

Wie bei 3, so scheint es genug von einer Ecke Fall, dass ich darüber keine Sorge, aber vielleicht sollte ich ...

Vorschläge, wie mein Problem in eine elegantere Weise zu lösen?

Antwort

3

ich mit McDowell Antwort einig, dass das Wichtigste ist, die ungültige Datenquelle an erster Stelle zu fixieren.

Wie auch immer, hier ist ein InputStream, der nach einsamen & Zeichen sucht und sie mit einem zusätzlichen amp; heiratet, falls es fehlt. Das Reparieren gebrochener Daten auf diese Weise zahlt sich meistens nicht aus.

Diese Lösung behebt die drei im OP erwähnten Fehler und zeigt nur eine Möglichkeit zur Implementierung von transformierenden InputStreams.

  • Im Konstruktor wird nur der Verweis auf den ursprünglichen InputStream gehalten. Im Konstruktor findet keine Verarbeitung statt, bis der Stream wirklich nach Daten gefragt wird (durch Aufrufe von read()).
  • Der Inhalt ist nicht in einen großen einzelnen String für die Transformation umgewandelt. Stattdessen arbeitet der Strom als Strom und führt nur minimal Read-Ahead (zB das vier Bytes notwendig, ob & zu finden, ist durch amp; oder nicht gefolgt.
  • Der Strom nur einsame & ersetzt, und versucht nicht, zu reinigen up amp;amp; in keiner Weise, weil sie mit dieser Lösung nicht passieren.

.

import java.io.IOException; 
import java.io.InputStream; 
import java.util.ArrayDeque; 
import java.util.Deque; 

public class ReplacerInputStream extends InputStream { 

private static final byte[] REPLACEMENT = "amp;".getBytes(); 
    private final byte[] readBuf = new byte[REPLACEMENT.length]; 
    private final Deque<Byte> backBuf = new ArrayDeque<Byte>(); 
    private final InputStream in; 

    public ReplacerInputStream(InputStream in) { 
     this.in = in; 
    } 

    @Override 
    public int read() throws IOException { 
     if (!backBuf.isEmpty()) { 
      return backBuf.pop(); 
     } 
     int first = in.read(); 
     if (first == '&') { 
      peekAndReplace(); 
     } 
     return first; 
    } 

    private void peekAndReplace() throws IOException { 
     int read = super.read(readBuf, 0, REPLACEMENT.length); 
     for (int i1 = read - 1; i1 >= 0; i1--) { 
      backBuf.push(readBuf[i1]); 
     } 
     for (int i = 0; i < REPLACEMENT.length; i++) { 
      if (read != REPLACEMENT.length || readBuf[i] != REPLACEMENT[i]) { 
       for (int j = REPLACEMENT.length - 1; j >= 0; j--) { 
        // In reverse order 
        backBuf.push(REPLACEMENT[j]); 
       } 
       return; 
      } 
     } 
    } 

} 

Der Code mit den folgenden Eingabedaten geprüft wurde (erster Parameter ist expec ted output, zweiter Parameter ist roher Eingang):

test("Foo &amp; Bar", "Foo & Bar"); 
    test("&amp;&amp;&amp;", "&&&"); 
    test("&amp;&amp;&amp; ", "&&& "); 
    test(" &amp;&amp;&amp;", " &&&"); 
    test("&amp;", "&"); 
    test("&amp;", "&amp;"); 
    test("&amp;&amp;", "&amp;&amp;"); 
    test("&amp;&amp;&amp;", "&amp;&&amp;"); 
    test("test", "test"); 
    test("", ""); 
    test("testtesttest&amp;", "testtesttest&"); 
+0

Das ist großartig: Danke für das Beispiel. Eine Frage allerdings: Müsstest du nicht die Methode read (byte [], int, int) außer Kraft setzen, damit der Code funktioniert, egal wie auf den Stream zugegriffen wird? – double07

+0

Und übrigens, ich bin an die Quelle des Problems gekommen und versuche es dort zu beheben. – double07

0

alle Daten in den Arbeitsspeicher zu vermeiden lesen, könnten Sie ein implementieren FilterInputStream (Sie würden irgendwie diese zusätzlichen Bytes an Pufferung sowohl read() und read(byte[],int,int) und schauen außer Kraft setzen müssen. Das ist nicht in kürzeren Code führen.


Die wirkliche Lösung ist, die ungültige Datenquelle zu reparieren (und wenn Sie das automatisieren möchten, müssen Sie Ihren eigenen XML-Parser schreiben.)

Ihr Ansatz hat ein paar Fehler.

  • Das Ergebnis von String.getBytes() ist systemabhängig; es ist auch eine Transcodierungsoperation, die möglicherweise nicht symmetrisch zu dem ist, was StringUtil.toString tut - Standardkodierungen auf vielen Systemen sind lossy. Sie sollten die Transcodierung mit dem XML document encoding durchführen.
  • Ein globales Suchen und Ersetzen wie dieses kann Ihr Dokument beschädigen - kaufmännische Und-Zeichen können in CDATA, entities and entity declarations existieren.
Verwandte Themen