Graue Haare zuhauf gab es heute im Büro. Die Aufgabe an sich klingt
einfach: Eine halb-strukturierte Text-Datei wird im Browser hochgeladen,
geparst und die Daten der Datei werden in einer Datenstruktur wieder
zurückgeliefert. Klingt erst einmal trivial.
Der Datei-Upload und die Antwort des Server muss allerdings über AJAX laufen.
Die Datenstruktur wird per JSON kodiert vom Server an den Client zurückgeschickt.
Als Javascript-Bibliothek kommt YUI zum Einsatz. Auf letzteren Punkt sollte
man allerdings nicht die Schuld schieben, das Problem dürfte durchaus auch mit
anderen Bibliotheken und Eigenimplementierungen auftreten.
Beginnen wir mit der hochzuladenen Datei. Sie kann zum Beispiel so aussehen:
<h1>XDepth speichert HDR-Bilder in JPEGs</h1>
<h2>Öffnen in normalen Bildbetrachtern möglich</h2>
Das Photoshop-Plug-in XDepth speichert die Dateien, die sich aus der
High-Dynamic-Range-Fotografie ergeben, in JPEG-Dateien. Die Dateigröße
dieser 32-Bit-Bilder wird sehr stark reduziert - aber die Farben und
die Helligkeitsinformationen der HDR-Bilder bleiben bestehen.
Das Plug-in ist für die Photoshop-Versionen CS2 und CS3 jeweils
für die Mac- und Windows-Variante erhältlich. Beim Speichern von
32-Bit-Dateien wird dadurch ein neuer Dateityp im Menü
"Speichern unter" eingeblendet: das XDP-Format mit der gleichnamigen
Dateiendung.
<img src="http:// www.golem.de/0811/63365-xpepth.png"
align="middle" width="500px">
Im Vergleich zu Microsofts Format HD-Photo soll XDepth eine
deutlich höhere Kompression und gleichzeitig eine bessere Bildqualität
erzielen. Die JPEG-Kompatibilität ist jedoch der größte Vorteil
von XDepth.
<keywords>XDepth, hdr, photoshop, plugin</keywords>
Beim Parsen wird sie grob in folgende Struktur gebracht:
artikel = {
headline : "XDepth speichert HDR-Bilder in JPEGs",
subheadline : "Öffnen in normalen Bildbetrachtern möglich",
abstract : "Das Photoshop-Plug-in XDepth speichert die Dateien, die
sich aus der High-Dynamic-Range-Fotografie ergeben, in
JPEG-Dateien. Die Dateigröße dieser 32-Bit-Bilder wird sehr
stark reduziert - aber die Farben und die
Helligkeitsinformationen der HDR-Bilder bleiben bestehen.",
text : "Das Plug-in ist für die Photoshop-Versionen CS2 und CS3 jeweils
für die Mac- und Windows-Variante erhältlich. Beim Speichern von
32-Bit-Dateien wird dadurch ein neuer Dateityp im Menü
"Speichern unter" eingeblendet: das XDP-Format mit der
gleichnamigen Dateiendung.
<img src="http:// www.golem.de/0811/63365-xpepth.png"
align="middle" width="500px">
Im Vergleich zu Microsofts Format HD-Photo soll XDepth eine
deutlich höhere Kompression und gleichzeitig eine bessere
Bildqualität erzielen. Die JPEG-Kompatibilität ist jedoch der
größte Vorteil von XDepth.",
keywords : [ "XDepth", "hdr", "photoshop", "plugin" ]
}
Den text-Eintrag behalten wir im Hinterkopf.
Schauen wir uns als nächstes an, wie YUI den Datei-Upload per
AJAX realisiert. Die Basis des Problems mit Datei-Uploads ist:
XMLRequest erlaubt keine Datei-Uploads. Will man einen Datei trotzdem
mit einem AJAX-vergleichbaren Prozess hochladen, muss man notgedrungen
ein Formular abschicken und die "Ausgabe" des Formulars in ein Frame
umleiten. YUI erzeugt dafür einen unsichtbaren IFrame und ergänzt das
entsprechende form-Element um ein target-Attribut. Die Server-Ausgabe,
unsere Datenstruktur, landet im unsichtbaren Frame. Von dort holt sich
YUI die Daten und liefert sie zurück, damit wir sie weiter verarbeiten
können. Von diesem ganzen Vorgang bekommen wir aber im Grunde nichts
mit. Der entsprechende Javascript-Quellcode sieht so aus:
YAHOO.util.Connect.setForm("MyForm", true);
var conn = YAHOO.util.Connect.asyncRequest('POST', 'http:///MyServer/ajaxController.php',
{
upload : function(o) {
var structure = YAHOO.lang.JSON.parse(o.responseText);
}
}
Soweit kein Thema bis jetzt. Allerdings funktionierte es nicht,
YAHOO.lang.JSON.parse() kann o.responseText nicht in eine reguläre
Datenstruktur überführen. Irgendwas in der JSON-Notation in der
Zeichenkette haut nicht hin.
Eine optische Kontrolle der Server-Antwort per Firebug funktioniert
nicht - das mochte mit dem Datei-Upload/POST-Request nicht umgehen.
Also kurzerhand ein alert(o.responseText) eingefügt und der
Server-Antwort ins Gesicht schauen. Und siehe da: Die Kodierung sah
tatsächlich sehr seltsam aus. Ich vermutete zuerst einen Bug
in der serverseiten JSON-Kodierung von HTML_AJAX_JSON, also flugs
Zend_Json_Encoder ausprobiert.
Das Problem blieb bestehen. Also die Ausgabe der JSON-Kodierung
angeschaut, alles sah korrekt aus. Da muss irgendwas später die
Ausgabe kaputt machen. Zwischenzeitlich probierte ich ein wenig
an der hochgeladenen Datei herum, alles läuft perfekt, solange
keine HTML-Tags im Artikel-Text sind. Ein einzelnes Tag aber
schiesst die ganze Sache bereits ab.
Ich fing an nach PHP-Einstellungen zu suchen, die irgendwie die
Ausgabe veränderten, ob es ein automatisches htmlspecialchars()
gibt oder artverwandtes. Die Suche war erfolglos. Schließlich
schaute ich mir die Serverausgabe an, die direkt beim Client
ankommt. Und siehe da: die Server-Antwort kommt perfekt an.
Der Server erwies sich als unschuldig.
Irgendwas muss demzufolge passieren zwischen der Ankunft am
Client und der Übergabe an meine Javascript-Methode. Ein gezielter
Blick in die YUI-Bug-Datenbank
bestätigte mein Vermutung. Aber auch dort gab es keine Lösung, die
mir optimal schien. Also ging es los in den Eingeweiden von
YUI.util.Connect rumzuwühlen.
Die entscheidende Entdeckung machte ich, als ich den Inhalt
des versteckten IFrames sah, wo die Server-Antwort ankommt.
Und es dämmerte mir.
Firefox macht mit unseren Server-Antwort was er mit jeder
anderen Webseite macht: er versucht sie so gut wie es geht
darzustellen. Das wirkt sich auch auf die interne Repräsentation
im DOM des IFrames aus. Und genau über diesen greift YUI zu,
um an die Server-Antwort zu kommen.
Wenn unser Artikel-Text also Tags enthält und der Content-Type
text/html lautet, dann interpretiert er es als HTML-Dokument und
"bereinigt" die Struktur. Wenn YUI nun per document.body.innerText
oder analog Funktionen auf den Frame-Inhalt zugreift, dann erhält
es zwar aus Browser-Sicht syntaktisch korrekt Daten, die nur kein
JSON mehr sind.
Die Umstellung auf application/json bringt nichts. Dann wird uns
die Server-Antwort zum Download angeboten - vergessen Sie nicht:
Wir führen keinen XMLRequest durch, sondern einfach einen Form-Request
mit einem Frame als Ziel!
Eine alternative Variante mit text/plain brachte ein interessantes
Verhalten von Firefox zum Vorschein. Normaler Text wird nämlich auch
einfach in ein "virtuelles" HTML-Dokument eingebettet mit Hilfe eines
pre-Tags. Damit haben wir das gleiche Problem wie bei text/html.
An diesem Punkt heißt es doch in den bitteren Apfel zu beißen -
sprich spitze Klammern zu markieren. Aber auf keinen Fall mit
< und >. Entitäten werden im Frame nämlich genauso
wieder verwurschtelt, stattdessen verwende ich @lt@ und @gt@.
Damit ist unser Problem aber auch noch nicht komplett gelöst und
idiotensicher. Die Server-Antwort, die YUI liefert, kann nämlich
z.B. immer noch so aussehen:
"<pre>{"headline":"XDepth speichert HDR-Bilder in JPEGs"...}</pre>"
Die Tags stören. Wir lösen es, in dem wir einfach nur den Text
auswerten, der zwischen den ersten öffnenden und letzten schließenden
geschweiften Klammer steht:
upload : function(o) {
var start = o.responseText.indexOf('{');
var end = o.responseText.lastIndexOf('}');
var text = o.responseText.slice(start, end+1);
var response = YAHOO.lang.JSON.parse(text);
var txt = response.Result.articleText;
txt = txt.replace(/gt/mgi, '>');
txt = txt.replace(/lt/mgi, '<');
...
}
Eine andere, tiefgreifendere Lösung besteht seit Firefox 3. Die DOM-API
erlaubt den
Zugriff auf den Inhalt einer Datei,
die somit auch per regulären POST-XMLRequest übertragen werden kann.

… ist das schon einen Urlaub wert. Ok, der Urlaub war schon angemeldet und bewilligt. Ein altes Problem fast unmittelbar vorher zu lösen, sorgt dann aber doch für ein gutes Gewissen. Die Lösung umfasst wenige Zeilen JavaScript-Code: var elIfra
Aufgenommen: Dez 11, 03:50