Anwendungen für FilterReader und FilterWriter
Zwei Beispiele zeigen den Einsatz der Java Filter-Klassen FilterReader und FilterWriter. Einmal zum Schreiben von HTML-Dateien und einmal zum Lesen von HTML-Dokumenten und Überlesen von Tags.
Unsere nächste Klasse bringt uns etwas näher an das HTML-Format heran. HTML steht für HyperText Markup Language. Wir wollen eine Klasse HTMLWriter entwerfen, die Filter Writer erweitert und Textausgaben in HTML konvertiert. In HTML werden Tags eingeführt, die vom Browser erkannt und besonders behandelt werden. Findet etwa der Browser im HTML-Text eine Zeile der Form <b>Dick</b>, so stellt er den Inhalt »Dick« in fetter Schrift dar, da das <B>-Tag den Zeichensatz umstellt. Alle Tags werden in spitzen Klammern geschrieben. Daraus ergibt sich, dass HTML einige spezielle Zeichenfolgen (Entities genannt) verwendet. Wenn diese Zeichen auf der HTML-Seite dargestellt werden, muss dies durch spezielle Zeichensequenzen geschehen.
< wird zu <
> wird zu >
& wird zu &
Kommen diese Zeichen im Quelltext vor, so muss unser HTMLWriter diese Zeichen durch die entsprechende Sequenz ersetzen. Andere Zeichen sollen nicht ersetzt werden.
Den Browsern ist die Struktur der Zeilen in einer HTML-Datei egal. Sie formatieren wiederum nach speziellen Tags. Absätze etwa werden mit <p> eingeleitet, einfache Zeilenvorschübe mit <br>. Unser HTMLWriter soll zwei leere Zeilen durch ein Absatz-Tag markieren. Demnach sollte unser Programm Leerzeilen zählen.
Alle sauberen HTML-Dateien haben einen wohl definierten Anfang und ein wohl definiertes Ende. Das folgende kleine HTML-Dokument ist wohlgeformt und zeigt, was unser Programm für Einträge machen muss. Auf den DOCTYPE für korrektes XHTML wurde hier verzichtet.
<HTML> <HEAD><TITLE> Title of page </TITLE></HEAD> <BODY> </BODY> </HTML>
Der Titel der Seite sollte im Konstruktor übergeben werden können. Hier nun das Programm für den HTMLWriter:
package com.javatutor.insel.io.stream;
import java.io.*;
class HTMLWriter extends FilterWriter
{
private boolean newLine;
/**
* Constructor with title of the page.
*/
public HTMLWriter( Writer writer, String title )
{
super( writer );
try {
out.write( "<HTML><HEAD><TITLE>"
+ title
+ "</TITLE></HEAD><BODY>\n" );
} catch ( IOException e ) { e.printStackTrace(); }
}
/**
* Needed constructor without title on the page.
*/
public HTMLWriter( Writer writer )
{
this( writer, "" );
}
/**
* Writes a single character.
*/
@Override
public void write( int c ) throws IOException
{
switch ( c )
{
case '<' : out.write( "<" ); newLine = false; break;
case '>' : out.write( ">" ); newLine = false; break;
case '&' : out.write( "&" ); newLine = false; break;
case '\n': if ( newLine ) {
out.write( "<P>\n" ); newLine = false;
}
else
out.write( "\n" );
newLine = true;
break;
case '\r': break; // ignore
default : out.write( c ); newLine=false;
}
}
/**
* Writes a portion of an array of characters.
*/
@Override public void
write( char[] cbuf, int off, int len ) throws IOException
{
for ( int i = off; i < len; i++ )
write( cbuf[i] );
}
/**
* Writes a portion of a string.
*/
@Override
public void write( String s, int off, int len ) throws IOException
{
for ( int i = off; i<len; i++ )
write( s.charAt( i ) );
}
/**
* Closes the stream.
*/
@Override
public void close() throws IOException
{
try {
out.write( "</BODY></HTML>\n" );
} catch ( IOException e ) { e.printStackTrace(); }
out.close();
}
}
public class HTMLWriterDemo
{
public static void main( String[] args )
{
StringWriter sw = new StringWriter();
HTMLWriter html = new HTMLWriter( sw, "Toll" );
PrintWriter pw = new PrintWriter( html );
pw.println( "Und eine Menge von Sonderzeichen: < und > und &" );
pw.println( "Zweite Zeile" );
pw.println( );
pw.println( "Leerzeile" );
pw.println( "Keine Leerzeile danach" );
pw.close();
System.out.println( sw.toString() );
}
}
Im Demo-Programm erzeugen wir einen StringWriter, in dem wir die Daten ablegen. Wir müssen close() vor der Anweisung sw.toString() aufrufen, da wir andernfalls nicht den korrekten Abschluss sehen würden. Wenn wir nicht über den PrintWriter schließen, sondern über den HTMLWriter, müssten wir noch einen try/catch-Block um close() setzen, da sie eine IOException erzeugt. Nutzen wir aber PrintWriter, kümmert sich dieser darum, die Exception zu fangen.
Die Ausgabe, die unser Programm nun erzeugt, ist folgende:
<HTML><HEAD><TITLE>Toll</TITLE></HEAD><BODY> Und eine Menge von Sonderzeichen: < und > und & Zweite Zeile <P> Leerzeile Keine Leerzeile danach </BODY></HTML>
HTML-Tags mit einem speziellen Filter überlesen
Unser nächstes Beispiel ist eine Klasse, die den FilterReader so erweitert, dass HTML-Tags überlesen werden. Sie werden allerdings nicht so komfortabel wie beim HTMLWriter im Datenstrom umgesetzt. Die Klasse überschreibt den notwendigen Konstruktor und implementiert die beiden read()-Methoden. Die read()-Methode ohne Parameter legt einfach ein 1 Zeichen großes Feld an und ruft dann die read()-Methode auf, die die Daten in ein Feld liest. Da dieser Methode neben dem Feld auch noch die Größe übergeben werden kann, müssen wirklich so viele Zeichen gelesen werden. Es reicht einfach nicht aus, die übergebene Anzahl von Zeichen vom Reader in zu lesen, sondern hier müssen wir beachten, dass eingestreute Tags nicht zählen. Die Zeichenkette <p>Hallo<p> ist demnach fünf Zeichen lang und nicht elf. Liegt eine solche Zeichenkette vor, so müssen mehr als vier Zeichen vom darunter liegenden Reader abgezogen werden.
package com.javatutor.insel.io.stream;
import java.io.*;
class HTMLReader extends FilterReader
{
private boolean intag = false;
public HTMLReader( Reader in )
{
super( in );
}
@Override
public int read() throws IOException
{
char[] buf = new char[1];
return read( buf, 0, 1 ) == –1 ? –1 : buf[ 0 ];
}
@Override public int
read( char[] cbuf, int off, int len ) throws IOException
{
int numchars = 0;
while ( numchars == 0 )
{
numchars = in.read( cbuf, off, len );
if ( numchars == –1 ) // EOF?
return –1;
int last = off;
for ( int i = off; i < off + numchars; i++ )
{
if ( !intag ) {
if ( cbuf[i] == '<' )
intag = true;
else
cbuf[last++] = cbuf[i];
}
else if (cbuf[i] == '>')
intag = false;
}
numchars = last – off;
}
return numchars;
}
}
public class HTMLReaderDemo
{
public static void main( String[] args )
{
try {
String s = "<html>Hallo!<b>Ganz schön fett.</b>"+
"Ah, wieder normal.</html>";
Reader sr = new StringReader( s );
Reader hr = new HTMLReader( sr );
BufferedReader in = new BufferedReader( hr );
for ( String t; (t = in.readLine()) != null; )
System.out.println( t );
in.close();
}
catch( Exception e ) { System.err.println( e ); }
}
}
Das Programm produziert dann die einfache Ausgabe:
Hallo! Ganz schön fett. Ah, wieder normal.
Der einzige Grund, warum wir auf den HTMLReader noch einen BufferedReader aufsetzen, ist der, dass wir dann die readLine()-Methode nutzen können.