Erkennung des Encoding in Java

Rekono de kodoprezento per Java / Ĝavo

Wenn Anwendungen, vor Allem Web-Anwendungen, Unicode-Textdateien von verschiedenen Plattformen (MS Windows, Apple, Linux, …) entgegennehmen, haben sie es üblicherweise mit verschiedenen Encodings zu tun. Windows-Dateien sind häufig in UTF-16 dargestellt, Linux-Dateien verwenden üblicherweise UTF-8. Unterschiedliche Browser kennzeichnen solche Dateien mit ganz verschiedenem Content-Type, aus dem das Encoding nicht immer feststellbar ist. Kiam aplikaĵoj, precipe retaj aplikaĵoj, akceptas unikodajn tekstodosierojn el malsamaj platformoj (Vindozo, Apple, Linukso, …), Ili kutime frontas malsamajn kodoprezentojn. Vindozaj dosieroj ofte estas prezentataj per UTF-16, linuksaj dosieroj kutime prezentas sin per UTF-8. Diversaj kroziloj atribuas al tiaj dosieroj plej malsamajn enhavotipojn (Content-Type), el kiuj ne ĉiam eblas trovi la kodoprezenton.
Grundsätzlich ist es leicht, zwischen diesen Encodings zu unterscheiden, wenn die Dateien mit einem Byte-Order-Marker (BOM) beginnen. Ein solches BOM besteht zum Beispiel aus den Bytes 239-187-181 (für UTF-8) oder aus den Bytes 254-255 (oder umgekehrt, für UTF-16). Hier zwei Beispieldateien: Principe estas facile distingi inter tiuj kodoprezentoj, kiam la dosieroj komence prezentas "bajt-ordan markilon" (BOM). Tia markilo konsistas ekzemple el la bajtoj 239-187-181 (por UTF-8) aŭ el la bajtoj 254-255 (aŭ inverse, por UTF-16). Jen du specimenaj dosieroj:
specimeno-utf-8.txt
specimeno-utf-16.txt
In der Programmiersprache Java, in der viele Web-Anwendungen kodiert sind, ergibt sich das Problem, dass man der Klasse java.io.InputStreamReader (FileReader verwendet immer das Standard-Encoding der Plattform) bereits im Konstruktor das richtige Encoding mitgeben muss (sie kann allerdings bei UTF-16 selbsttätig zwischen UTF-16BE und UTF-16LE unterscheiden. Zum Erkennen eines BOM muss also der Eingabestrom zunächst auf Byte-Ebene gelesen werden. Ist dann das Encoding erkannt, kann aus dem bereits angelesenen Strom kein Reader-Objekt mehr erzeugt werden, dem man das erkannte Encoding übergeben könnte. En la programadlingvo Ĝavo, kiun uzas multaj ret-aplikaĵoj, estas problemo, ke al la klaso java.io.InputStreamReader (FileReader ĉiam uzas la aprioran kodoprezenton de la komputilo) necesas jam konstruile transdoni la ĝustan kodoprezenton (ĝi tamen povas, uzante UTF-16, mem distingi inter UTF-16BE kaj UTF-16LE). Por trovi BOM-on do unue necesas legi la enigan fluon je bajta nivelo. Post trovo de la kodoprezento ne plu eblas generi el la eklegita fluo Reader-objekton, kiu uzu la trovitan kodoprezenton.
Abhilfe schafft hier ein Kapseln des Eingabestroms in ein BufferedInputStream-Objekt, das man nach dem Erkennen des BOM auf den Stromanfang zurücksetzen kann. Anschließend kann man ein Reader-Objekt erzeugen, das mit dem erkannten Encoding arbeitet. Elirvojo estas enigi la enigan fluon en BufferedInputStream-objekton, kiun eblas remeti al la komenco post trovo de BOM. Poste eblas generi el la fluo Reader-objekton laborantan per la trovita kodoprezento.
Dieses Vorgehen benutzt die Klasse Kodoprezento. Ĉi tiun procedon uzas la klaso Kodoprezento.
  public class Kodoprezento {
	private static final String STANDARDENCODING = "UTF-8"; // oder was auch immer

	public static InputStreamReader erzeugeReader(FileInputStream fis) throws IOException {
		BufferedInputStream bis = new BufferedInputStream(fis);
		bis.mark(16);   // oder mehr als 16 für heuristische Methoden
		final int b0 = bis.read();
		final int b1 = bis.read();
		final int b2 = bis.read();
		String encoding;
		if (b0 == 239 && b1 == 187 && b2 == 191) { // UTF-8-BOM
			encoding = "UTF-8";
		} else if (b0 == 255 && b1 == 254) { // UTF-16LE-BOM
			encoding = "UTF-16LE";
		} else if (b0 == 254 && b1 == 255) { // UTF-16BE-BOM
			encoding = "UTF-16BE";
		} else {    // Erweiterung um heuristische Methoden möglich
			encoding = STANDARDENCODING;
		}

		bis.reset(); // zurück an den Anfang
		final InputStreamReader reader = new InputStreamReader(bis, encoding);
		return reader;
	} 
  public class Kodoprezento {
	private static final String APRIORA_KODOPREZENTO = "UTF-8"; // aŭ kio ajn

	public static InputStreamReader kreuLegilon(FileInputStream fis) throws IOException {
		BufferedInputStream bis = new BufferedInputStream(fis);
		bis.mark(16); // aŭ pli ol 16 por statistikaj metodoj
		final int b0 = bis.read();
		final int b1 = bis.read();
		final int b2 = bis.read();
		String kodoprezento;
		if (b0 == 239 && b1 == 187 && b2 == 191) { // UTF-8-BOM
			kodoprezento = "UTF-8";
		} else if (b0 == 255 && b1 == 254) { // UTF-16LE-BOM
			kodoprezento = "UTF-16LE";
		} else if (b0 == 254 && b1 == 255) { // UTF-16BE-BOM
			kodoprezento = "UTF-16BE";
		} else {    // eblas aldoni statistikajn metodojn
			kodoprezento = APRIORA_KODOPREZENTO;
		}

		bis.reset(); // reiru al la komenco
		final InputStreamReader legilo = new InputStreamReader(bis, kodoprezento);
		return legilo;
	} 
Hinweis: Es geht hier nicht um heuristische Methoden zur Bestimmung des Encoding einer Datei ohne BOM, sondern nur darum, wie man das Encoding aus dem Inhalt bestimmen und anschließend den Eingabestrom noch in ein Reader-Objekt kapseln kann. Solche heuristischen Methoden lassen sich aber mit der hier beschriebenen Technik kombinieren. Der Parameter von mark sollte dann wohl auf einen höheren Wert als 16 gesetzt werden. Atentigo: Ĉi tie ne temas pri statistikaj metodoj por trovi la kodoprezenton de sen-BOM-a dosiero, sed nur pri la demando, kiel trovi la kodoprezenton el la enhavo kaj poste tamen enmeti la fluon en Reader-objekton. Tiaj statistikaj metodoj estas kombineblaj kun la ĉi-tiea tekniko. Por tio utilas uzi pli grandan valoron ol 16 por la voko al la metodo mark.
Die Klasse und die Beispieldateien sind in einem Archiv zusammengestellt, das auch ein Makefile für Unix-Systeme enthält. Die zum Test angefügte Methode main ergibt, dass für das UTF-8-Beispiel das Encoding UTF8 und für das UTF-16-Beispiel UnicodeBigUnmarked verwendet wird, ohne dass ein Encoding explizit im Programm angegeben werden müsste. La klaso kaj la specimenaj dosieroj estas pakitaj en arĥivo, kune kun Makefile-instrukciaro por uniksaj sistemoj. La testa metodo main rezultigas por la UTF-8-specimeno la kodoprezenton UTF8 kaj por la UTF-16-specimeno la kodoprezenton UnicodeBigUnmarked, sen eksplicita kodoprezento en la programo.
In einer Web-Anwendung kann das InputStream-Objekt zum Beispiel aus einem FormFile-Objekt (Apache Struts) kommen. Dieser Fall ist nicht unwichtig, weil man hier (im Gegensatz zu einer Datei) den Eingabestrom nicht ein zweites Mal öffnen kann. En reta aplikaĵo la InputStream-objekto povas deveni ekzemple el FormFile-objekto (Apache Struts). Tio estas nemalgrava okazo, ĉar male al dosiero ne eblas malfermi duan enig-fluon kun la samaj datenoj.
Reinhard Fößmeier Reinhard Fössmeier