Be careful when cutting UTF-8 text
The script uses PHP's DomDocument, more specifically it's functions loadHTML and saveXML, to extract valid XML from the blog posts. That's necessary because the posts are shortened and this shortening can lead to a completely invalid (X)HTML structure. Let alone all the rubbish content that many a software produces.
Shortening the content was of course done with the PHP function substr. And that's where the problem was. The relevant part of the text that caused problem was "steht nur auf Englisch zur Verfügung". Translated to UTF-8 this becomes "steht nur auf Englisch zur Verf??gung". To this string substr was applied and it produced "steht nur auf Englisch zur Verf?" - the second half of the UTF-8 character was cut off. If you know anything about UTF-8 you will go "ouch" here and smile about your knowledge and skip the following two paragraphs. If you don't see the problem yet, let me enlighten you.
UTF-8 is a Unicode encoding. So it can transport any of the characters defined in Unicode which is just about any character that you might ever want to use in today's computing. It's neat because for most of the content in European languages it requires just one byte per character (versus two bytes in UTF-16 for example). When a byte is in the ASCII range it's displayed and all is well. But when the byte is outside of the ASCII range (which only knows about 128 characters and can be encoded in 7 bits per character as you probably know) this means that the following byte belongs to the same character. I'm sorry, I don't really know how to explain that any better so let me just give you an example.
The string Für becomes F??r in UTF-8. So the UTF-8 decoder would read the first byte, the letter F. That fits nicely into ASCII, so that byte is read and the decoder continues with the next character. It reads one byte which is ?. "Holy cow" you hear the decoder exclaim, "that's not ASCII". So the decoder has to read one additional byte and gets the ?. Reading those two characters, putting them together and calculating a bit, the decoder then knows that it has just read an ü.
So do you already see the problem in the UTF-8 string "steht nur auf Englisch zur Verf?"? The decoder arrives at the last byte which is ?. It knows it has to read one more byte, but there are none. So somehow the PHP code in question decides to patiently wait until the string magically grows longer.
The real problem though is of course the careless use of substr. You shouldn't just cut UTF-8 characters in half. The problem can be solved with mb_substr, a substr function that is Unicode-aware. Just give it 'utf-8' as its fourth argument and the problem is solved.
Update: It seems that the problem goes away automatically with newer libxml versions. On my server it's 2.6.16, while Chregu uses 2.6.23 and can't reproduce the problem. Thanks Chregu for digging into this.
Teaching Python
Today is the fourth course and so far I have only covered:
- Python basics such as strings, numbers, conditions, using functions
- A bit more basics such as lists, loops and defining functions
- Creating and using classes
- Reading and writing files, pickle, string methods, library documentation
The next week I should get started with GUI programming because there are only two Python lessons left. Then we are continuing with PHP (and HTML, etc.) for four days.
One advantage of Python is the number of Spanish documentation. I collected some at my delicious account: Spanish Python documentation. The most important ones:
- Inmersión en Python (Spanish version of Dive Into Python)
- Documentación de Python, Spanish translation of the official documentation. Primarily the tutorial and the library reference. Very outdated though.
Generally it seems to me that the state of Spanish translation in the free software world is even worse than for German.
Kent Beck in German-speaking Europe
The talk is organized by iX (part of the Heise publishing house). See the official Web site for information.
(Via Heise)
Trouble understanding a Knuth algorithm
While working through the algorithm A on page 437 (First-fit method) I have stumbled over a problem understanding it. The algorithm itself is very clear and straight-forward. But the description of the step A1 gives me a puzzle. I'll reprint here the whole algorithm in Knuth's wording.
Algorithm A (First-fit method). Let AVAIL point to the first available block of storage, and suppose that each available block with address P has two fields: SIZE(P), the number of words in the block; and LINK(P), a pointer to the next available block. The last pointer is ?. This algorithm searches for and reserves a block of N words, or reports failure.
- A1.
- [Initialize.] Set Q ? LOC(AVAIL). (Throughout the algorithm we use two pointers, Q and P, which are generally related by the condition P = LINK(Q). We assume that LINK(LOC(AVAIL)) = AVAIL.)
- A2.
- [End of list?] Set P ? LINK(Q). If P = ?, the algorithm terminates unsuccessfully; there is no room for a block of N consecutive words.
- A3.
- [Is SIZE enough?] If SIZE(P) ? N, go to A4; otherwise set Q ? P and return to step A2.
- A4.
- [Reserve N.] Set K ? SIZE(P)-N. If K = 0, set LINK(Q) ? LINK(P) (thereby removing an empty area from the list); otherwise set SIZE(P) ? K. The algorithm terminates successfully, having reserved an area of length N beginning with location P+K.
Now what puzzles me is the assertion in step A1:
We assume that LINK(LOC(AVAIL)) = AVAIL. The explanations for LOC on page 235 reads as follows:
If V is the name of some value held in a memory cell, LOC(V) denotes the address of that cell. Consequently if V is a variable whose value is stored in a full word of memory, we have CONTENTS(LOC(V)) = V.
So this whole assertion currently only would make sense if it was LINK(LOC(AVAIL)) = P. Could any of my readers here explain me what I don't get in that explanation. Please enlighten me.
Donald Knuth in Zürich
Ist trotzdem schade, dass ich den Vortrag von Donald Knuth nicht anschauen kann. Bitte bitte macht davon ein Video.
Seine Bücher The Art of Computer Programming versuche ich durchzuarbeiten. Den ersten Band habe ich jedenfalls zusammen mit Managing Gigabytes nach Peru mitgenommen.
(Via Peter)
Technische Limitierung verhindert höhere Benzinpreise
About 200 gas sellers in rural Vermont own pumps too old to compute the higher prices, state authorities said on Friday, causing some to shut their pumps when prices spiked above $3 after the devastation of Hurricane Katrina.
"I knew my pump was old but I didn't expect prices to rise this fast," said Bill MacDonald, owner of central Vermont's Waits River General Store, whose 25-year-old pumps can't display any price above $2.99 a gallon.
Daraus können wir als Software-Entwickler selbstverständlich etwas lernen: möglichst wenig Limitierungen für Werte die nicht fix limitiert sind. Also so etwas wie Preise, Zeiten, Dateigrössen, Texteingaben, etc. Nun fordern aber die meisten Programmiersprache vom Programmierer bei der Deklaration einer Variable eine Entscheidung für eine Datengrösse: int, long, double und so weiter lassen grüssen. Doch lassen sich durch ein wenig (oder viel - je nach Umgebung) Abstraktion und Fleiss diese Variablen durchaus auch endlos wachsen - jedenfalls so lange wie der RAM mitmacht.
Ein Beispiel dafür ist die Programmiersprache Ruby. Diese konvertiert automatisch zwischen den beiden Datentypen Fixnum und Bignum. Ich zitiere aus dem Ruby "Pickaxe" Buch:
Ruby supports integers and floating point numbers. Integers can be any length (up to a maximum determined by the amount of free memory on your system). Integers within a certain range (normally -230 to 230-1 or -262 to 262-1) are held internally in binary form, and are objects of class Fixnum. Integers outside this range are stored in objects of class Bignum (currently implemented as a variable-length set of short integers). This process is transparent, and Ruby automatically manages the conversion back and forth.
Zurück zu meinem Eingangsbeispiel. Bei mechanischen Komponenten ist die Sachlage natürlich anders. Zudem gibt es ja auch bei Software-Projekten oft eine Spezifikation à la, "der Preis wird nie höher als drei Dollar sein", an welche sich der Entwickler hält. Erstaunt hat mich so oder so, dass der Preis nicht auf die logischeren 9.99 Dollars limitiert ist.
Wahrscheinlich sind das also drei Rädchen für den Preis, eines mit den Ziffern Null, Eins und Zwei und die anderen beiden mit den Ziffern von Null bis Neun. Bei meiner Mutter zu Hause steht so eine alte Zapfsäule und ich werde mir die irgendwann nochmals genauer anschauen.
Datenbanken skalieren
Verschiedene Anwendungen lassen sich jedoch unterschiedlich gut horizontal skalieren.
HTTP kann relativ einfach horizontal skaliert werden. Subdomains, Redirects, DNS Round Robin, Load Balancer und Accelerating Proxies lassen sich zu einer beliebigen Mischung zusammen mixen und können - richtig verwendet - zu einer sehr skalierbaren und ausfallsicheren Seite führen. Wobei, wenn selbst Google ausfallen kann, dann muss man das Wort Ausfallsicherheit wohl ein wenig relativ sehen.
Horizontal zu skalieren funktioniert jedoch nicht überall gleich gut und kann teilweise ziemlich schwierig sein. Zum Beispiel bietet MySQL eine sehr gute Replikation an. Jedoch funktioniert diese nur für read-only Zugriffe wirklich gut. Die Schreibzugriffe müssen normalerweise auf einem zentralen Server erfolgen (Für Details warum und wann diese Aussage nicht stimmt, siehe High Performance MySQL von Jeremy D. Zawodny ab Seite 165).
Lotus Notes bietet schon seit Jahren eine sehr gute Replikation an, bei welcher auf verschiedenen Servern gleichzeitig geschrieben werden kann. Jedoch kommt es da auch oft zu Replikationskonflikten, welche manuell gelöst werden müssen. In den meisten Umgebungen ist das nicht praktikabel und bei einer relationalen Datenbank (was Notes nicht ist) kann dies katastrophal sein.
Und jetzt komme ich endlich zum Punkt. Der Punkt ist ein Vortrag von Adam Bosworth, seit einigen Monaten Mitarbeiter bei der wohl am extremsten skalierten Webseite der Welt (hat jemand Google gesagt?). Der Vortrag Database Requirements in the Age of Scalable Services wurde an der MySQL Konferenz gehalten und präsentiert einige innovative und wahrscheinlich auch kontroverse Ideen, wie Datenbanken skaliert werden könnten.
Ich habe seinen Vortrag vorher mal angehört und werde das wohl noch ein paar mal wiederholen müssen, bevor ich ihm richtig folgen kann. An dieser ganzen Thematik habe ich jedoch ein grosses Interesse. So versuche ich nebenbei noch eine Web Server Performance HOWTO zusammen zu stellen.
(Via lesscode.org.)
XMLHttpRequest
Nachdem Applikationen wie zum Beispiel Ta-da List oder SoapBX XMLHttpRequest auf spannende Art und Weise verwenden, wollte auch ich das endlich versuchen. XMLHttpRequest erlaubt es, aus ECMA Script (Java Script) eine HTTP Abfrage zu starten und das Resultat derselben auch lesen zu können. Dies öffnet Tür und Tor für Rich Clients innerhalb der Web Browser. Ein grosser Vorreiter auf dem Gebiet ist GMail. Man kann gegen GMail haben, was man will, aber es ist so in etwa das "richeste" was ich in einem Browser unter Verwendung von HTML schon gesehen habe. Flash und Java sind für mich keine Alternativen.
Ich wollte nun eine kleine Todo-Liste bauen, welche XMLHttpRequest für schnelle Updates verwendet. Da nur Prototyp-mässig ohne wirklich gute Fehlerbehandlung und nur mit dem Firefox unter Linux getestet.
Der HTML Code im Body sieht wie folgt aus:
- Eintrag eins
Und das Java Script so:
var http;
/**
* Handler for the XMLHttpRequest used in submitTodo().
*/
function submitTodoHttpHandler() {
if (http.readyState == 4) {
// only if "OK"
if (http.status == 200) {
var todo_list = document.getElementById('todos');
var todo_input = document.getElementById('todo_title');
var id = http.responseText;
var list_item =
todo_list.appendChild(document.createElement('li'));
list_item.innerHTML = todo_input.value;
todo_input.value = '';
todo_input.focus();
} else {
alert("Could not execute the HTTP query. " +
http.statusText + " / " + http.status);
}
}
}
/**
* This submits the todo item to the server. If everything works
* alright, this returns false, so it can be used as an onSubmit
* action handler. If there is a problem, it returns true thereby
* triggering the usual form handler to save the item in the usual
* way.
*/
function submitTodo() {
if(document.getElementById && window.XMLHttpRequest) {
title = document.getElementById('todo_title').value;
url = '/todo/create_js?title=' + title;
http = new XMLHttpRequest();
http.onreadystatechange = submitTodoHttpHandler;
http.open("GET", url, true);
http.send("");
return false;
} else {
return true;
}
}
Die Voraussetung ist dann, dass das Script /todo/create_js den Titel entgegennimmt, einen neuen Eintrag erstellt und die ID des Eintrags zurückliefert. Diese ID wird zwar momentan nicht verwendet, wird aber bei einer richtigen Applikation vonnöten sein um zum Beispiel den Edit Link zu erstellen.
Nun tippt man im Text-Feld einen Todo-Eintrag ein, bestätigt mit Enter (Form Submit muss ausgelöst werden, kann auch über einen Submit-Button gelöst werden) und der Eintrag wird ohne einen Page-Reload erstellt. Sobald der Eintrag fertig erstellt wurde, wird das Text-Feld geleert.
Habe nur kurz 30 Minuten daran gearbeitet, von daher ist das Script alles andere als perfekt. Richtige Anwendungen müssten noch mindestens folgenses implementieren, welches ich noch nicht gemacht habe:
- Fehlerbehandlung
- Eingabe-Feld sperren, während auf Server-Antwort gewartet wird
- Umlaute und Spezialzeichen in der Eingabe dürfen keine Probleme verursachen
Python für Nokia Smartphones
Ich habe mit Python schon einige Applikationen entwickelt. Teilweise arbeite ich auch noch am pyBKM, einem Bookmark-Manager in Python mit dem wxPython Framework geschrieben.
Ausgabe cachen in PHP
Für einen Kunden arbeite ich gerade daran, den Server zu entlasten. Nun habe ich bereits einige Optimierungen vorgenommen, von MySQL Server besser konfigurieren, über PHP Accelerator installieren, SQL Statements optimieren, Indexes anlegen oder entfernen, etc. Nun experimentiere ich ein wenig mit Output Caching. Die Idee ist, dass die Seite nur einmal alle paar Minuten generiert werden muss, um so in meinem Fall vor allem die Datenbank zu entlasten.
Die Seite, auf welcher ich das Caching jetzt angewendet habe, verwendet einige Dutzend Datenbankabfragen und mehrere davon sind sehr Zeitintensiv. Bessere Index-Erstellung ist leider nicht praktikabel, da die INSERTs sehr schnell sein müssen.
Der Ansatz, den ich momentan verwende stammt von Dave Child und ist in seinem Artikel Caching output in PHP beschrieben.
Neue Java Version nicht für Apple Rechner
Wie ich mich bereits mehrfach beschwert habe (siehe Java Mythos und Java 5.0), läuft ja keine neue Java Version unter Linux auf PowerPC. Nicht mal für Mac OS X scheint die neueste Java Version 1.5 (bzw. 5) zur Verfügung zu stehen. Wie James Gosling selber schreibt, führt er seine neuen Java Programme auf einem entfernten Linux Rechner über SSH Tunnelling aus. Naja, ich weiss ja nicht, was ich unter Plattformunabhängigkeit verstehen soll. Aber jedenfalls weiss ich jetzt, dass es nicht nur an meiner Dummheit liegt.
Java 2 Platform Standard Edition 5.0
Wie die meisten Entwickler gesehen haben, ist Java 5 nun fertig. Die neuen Features lassen sich sehen und räumen mit einigen alten Problemen auf. Die für mich als spärlicher Java Programmierer erfreulichsten Features sind Enums und Generics (aus C++ als Templates bekannt).
Für Linux auf PowerPC steht das neue Java wieder nicht zur Verfügung.
Java und das Mythos der Plattformunabhängigkeit
Java wird immer wieder unter dem Deckmantel der Plattformunabhängigkeit angepriesen. Darüber habe ich mich in einem Artikel bereits ausgiebig ausgelassen. Gerade rege ich mich wieder mal darüber auf. Es scheint tatsächlich für meine Plattform, nämlich Linux auf PowerPC, keine aktuelle Java Runtime Environment zu geben. Die aktuellste Version ist Java 1.3.1. Der nächste, der mir Java als portabel anpreist, bezahlt mir einen Drink.