Be careful when cutting UTF-8 text

Posted by Patrice Neff Fri, 24 Mar 2006

I just fixed a nasty problem on the two planets I run (one for namics and one for local.ch). The aggregator script would run forever without stopping. A bit of debugging showed, that the problem was about how UTF-8 character were handled (or rather weren't handled).

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

Posted by Patrice Neff Thu, 23 Feb 2006

I'm currently teaching to a few teachers at the . I originally thought we would progress much faster, but it didn't work out too well. Most of the teachers didn't have very good knowledge in the languages they are currently using (mostly Visual Basic 6 and VB.Net). So there was not much knowledge to build on. Also there was at least one profesor who had not programed at all in his life.

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, , 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:

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

Posted by Patrice Neff Sat, 18 Feb 2006

In case you have 543 € you somehow need to spend, you might want to hear Kent Beck speaking about testing and extreme programming. He is coming to Zurich, Vienna and Frankfurt at the end of March this year. If you sign up until March 10, you pay 543 €. After that it will cost you 725 €.

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

Posted by Patrice Neff Sat, 03 Dec 2005

I'm currently working through chapter 2.5 (Dynamic Storage Allocation) of .

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

Posted by Patrice Neff Wed, 12 Oct 2005

Mein momentan absolut liebster Autor kommt nach Zürich und ich hänge während der Zeit in Peru rum. Shame on me! Oder so...

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

Posted by Patrice Neff Wed, 28 Sep 2005

Die folgende Story ist zwar bereits ein wenig her, aber immer noch interessant. Nach dem Wirbelsturm welcher New Orleans versenkte, stiegen die Benzinpreise offenbar ziemlich hoch. Da ich kein Auto fahre bin ich davon nicht persönlich betroffen. Deshalb habe ich keine Ahnung, wie teuer Benzin normalerweise ist und habe darum noch das "offenbar" eingefügt. In dem Zusammenhang habe ich eine auf den ersten Blick witzige Story gelesen: einige Tankstellen konnten den Preis nicht auf die über drei Dollars pro Fass hochschrauben. Ihre Zapfsäulen waren zu alt dafür.

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

Posted by Patrice Neff Thu, 04 Aug 2005

Skaliert wird heute vor allem horizontal. Wenn man mit hohen Anforderungen konfrontiert ist, ist es oft einfacher und sogar günstiger zehn Server aufzustellen, welche diese Last zusammen tragen, als einen Server, der mit dieser Last selber umgehen kann. Zudem kann man horizontal endlos (naja, je nach finanziellen Ressourcen) kaufen, während ein einzelner Server irgendwann nicht mehr ausgebaut werden kann. Jedes Betriebssystem hat irgend eine Grenze bei der Anzahl Prozessoren und der RAM kann auch nicht endlos gross werden.

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

Posted by Patrice Neff Tue, 01 Feb 2005

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

Posted by Patrice Neff Tue, 01 Feb 2005

Nokia hat die Programmiersprache Python nun für die Series 60 Smartphones zur Verfügung gestellt.

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

Posted by Patrice Neff Thu, 21 Oct 2004

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

Posted by Patrice Neff Fri, 01 Oct 2004

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

Posted by Patrice Neff Thu, 30 Sep 2004

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

Posted by Patrice Neff Sun, 19 Sep 2004

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.