Wie funktioniert UTF-8?

Ines hat sich im Beitrag Flash: Umlaute in XML gewundert, wieso in Flash Sonderzeichen so komisch kodiert werden. Ich hätte es fast als Kommentar beantwortet, aber dann schien es mir sinnvoll zu sein, einen eigenen Beitrag zu schreiben. Hier mal zum Verständnis ein gekürzter Ausschnitt:

ä   >   %C3%A4

Warum Flash solch eine eigenartige hex-Interpretation der Umlaute benötigt, ist mir schleierhaft. Ich konnte es jedenfalls nicht anhand einer ASCII-Tabelle nachvollziehen.

Eigentlich macht Flash gar nichts komisches sondern eine reine Kodierung in UTF-8. Um das zu erklären, muss man etwas weiter ausholen. Früher gab es die »ASCII-Tabellen«. Ein Zeichen wurde als ein Byte kodiert, d.h. bei 256 Zeichen ist Schluss. Die Lösung bestand darin, dass es nicht nur eine ASCII-Tabelle gab, sondern viele für verschiedene Sprachen und Alphabete. Beispiele für solche sogenannten Codepages oder Zeichensätze sind cp1252 oder iso8859-15. Die ersten 127 Plätze der Tabelle waren immer gleich, man sprach von 7-Bit-ASCII, darunter fallen z.B. Ziffern und das normale lateinische Alphabet, Sonderzeichen wie das kleine ä wurden in der anderen Hälfte kodiert.

Diese vielen Codepages stellen natürlich eine erhebliche Einschränkung dar, deswegen wurde Unicode entwickelt. Die einfachste Variante wäre es, statt ein Byte pro Zeichen einfach mehrere zu nehmen, bei UTF-16 und UTF-32 wird das auch so gemacht. Für Texte mit einem geringen Zeichenvorrat ist das aber Verschwendung von Speicherplatz, immerhin konnte man auch in iso8859 ganze Texte abbilden, ohne dass einem Zeichen fehlen und die wären gegenüber einer Kodierung mit konstant zwei Byte pro Zeichen nur halb so groß.

Deswegen gibt es UTF-8. Der Trick ist nun, dass in UTF-8 Zeichen mit unterschiedlich vielen Bytes kodiert werden können – es gibt 1-Byte-, 2-Byte-, 3-Byte und 4-Byte-Zeichen. Öffnet man einen Texteditor, legt eine UTF-8-Datei an, schreibt das kleine ä hinein und betrachtet das Ergebnis dann in einem Hex-Editor, sieht man dass es sich um ein 2-Byte-Zeichen handelt: C34A.

Wieso kann man diesen Wert nun nicht einfach so in einer Unicode-Tabelle nachschlagen? Das liegt daran, dass man in einem Strom von Zeichen ja auch die 1-Byte von den 2-Byte-Zeichen (und natürlich von den noch längeren) unterscheiden können muss. Wenn ich Zeichen a mit 8 kodieren würde, b mit 9 und ß mit 89, woran sehe ich dann, ob es ab ist oder ß? Deswegen werden bei UTF-8 nie alle acht Bit eines Bytes zum Speichern des eigentlichen Inhalts verwendet.

Um das zu erklären hilft zunächst ein Blick auf eine Unicode-Tabelle, z.B. auf decodeunicode.org, diese Tabelle gibt es nämlich trotzdem. Dort hat jedes Zeichen eine Zahl zugeordnet. So können die gleichen Zeichen mit dem gleichen Platz in der Unicode-Tabelle in verschiedenen Kodierungen wie eben UTF-8 und UTF-16 kodiert werden.

Das kleine ä hat also in Unicode den Platz 00E4 in Hexadezimal, das entspricht 228 im Dezimalsystem. Als nächstes hilft ein Blick auf Wikipedia. Dort sieht man, dass es in UTF-8 spezielle Markerbits gibt. Ich zeige das mal anhand vom oben erwähnten C3A4:

1100 0011 1010 0100

Diese Bits zeigen an, dass es sich um ein 2-Byte-Zeichen handelt. Wenn ich diese Bits jetzt wegstreiche, bleibt folgendes übrig:

000 1110 0100

Die führenden Nullen noch weg und dann ist das hexadezimal wieder genau E4 und dezimal 228, also genau der Platz vom kleinen ä in der Unicode-Tabelle. Die Bits des E4 werden also zusammen mit dem, was ich oben Markerbits genannt habe, in die Zwei Byte des C3A4 gepackt. Bei Zeichen, die weiter hinten stehen in der Unicode-Tabelle, werden die entsprechenden Bits dann eben auf drei oder vier Bytes verteilt. Kurz und gut, sieht alles komisch aus, ist aber keine Hexerei.

happy

svnsync und relocate

Ich spiegel bei mir zu Haus ein paar Subversion-Repositories von Software, die mir sehr wichtig ist oder wo ich persönlich schon mal in den Quellcode geschaut oder auch mal einen Patch eingereicht habe. Das ganze geschieht mit svnsync und einem kleinen Shell-Skript, das per Cronjob alle acht Stunden läuft. In den letzten Tagen bekam ich Fehlermeldungen, dass ein Quell-Repository nicht mehr auffindbar war. Eine kurze Recherche ergab, dass der anonyme Zugriff per https eingestellt wurde und nur noch Zugriff per http möglich ist, was de facto einer anderen URL gleich kommt, quasi einem Umzug des Repos. Bei einer normalen Working Copy von Subversion hat man kein Problem, da macht man in einem solchen Fall

svn switch --relocate https://svn.quelle.foo/svn/trunk 
    http://svn.quelle.bar/svn/trunk

und fertig. Bei svnsync gestaltet sich das etwas schwieriger. Die URL des Quellrepos wird dort in den Properties der Revision 0 des Zielrepos gespeichert, so dass man bei der Synchronisation nur noch die Ziel-URL angibt. Ein “Relocate” muss dann durch eine Änderung des entsprechenden Properties geschehen, in meinem Fall sah das dann so (ähnlich) aus:

svn ps svn:sync-from-url --revprop -r 0 
    'http://svn.licq.org/svn' https://foo.local/svn/licq-mirror 
    --username syncuser --password syncpass

Man überschreibt also einfach die bisherige Quell-URL mit der neuen, das ist alles, danach einfach wie gewohnt

svnsync sync https://foo.local/svn/licq-mirror 
    --username syncuser --password syncpass

aufrufen und alles funktioniert wie vorher auch, nur eben mit neuer Quell-URL.

Probleme beim Upgrade von Debian Sid

Nachdem Tux ja schon in XOrg vs. Eclipse über Probleme beim normalen Ugrade von Debian Sid berichtet hat, könnte dies zu einer kleinen Artikelserie werden. Heute in diesem Kino: libdjvulibre21. Bei Debian gibt’s schon einen Bugreport und die Lösung hab ich in einem Wiki gefunden:

dpkg --purge libdjvulibre15

purge mit aptitude reichte nicht aus. Danach braucht’s nur noch das einfache aptitude safe-upgrade und alles ist wieder in Butter.

Grub auf mehreren Festplatten

Leute spielen mit Eisenbahnen, sammeln Briefmarken oder züchten Rosen. Ich probiere neue Betriebssysteme aus und pflege verschiedene Installationen für den einen oder anderen Zweck. In einem Testrechner sind hier drei Festplatten verbaut, alles alte Dinger, aber genug Platz für mehrere Betriebssysteme.

In diesem speziellen Fall sind auf der ersten Platte (80 GB) Windows 98 und XP installiert sowie ein Ubuntu und ein Eisfair-1. Auf der zweiten Platte (40 GB) residiert ganz allein ein Debian Etch als Host für Xen und last but not least ist auf der dritten Platte (4 GB) noch Platz für eine Beta-Version von Eisfair-2. Wäre nur die erste Platte eingebaut, hätte ich kein Problem. Ubuntu benutzt Grub und Grub erkennt bei der Installation andere Systeme. Ubuntu selbst und das Eisfair-1 von der ersten Platte starten ohne Probleme, Ubuntu hat die Hand auf dem Bootloader und aktualisiert bei Kernel-Updates seine eigenen Einträge. Die beiden Windosen lassen sich über chainloader ebenfalls problemlos booten.

Interessant wird es mit Betriebssystemen auf den anderen beiden Platten. Die bringen für gewöhnlich ihren eigenen Bootloader mit und verwalten die entsprechenden Einträge bei Kernel-Updates oder ähnlichem auch selbst. Der neue Eisfair-Installer schreibt sich in den MBR der Platte, wo das System landet, in dem Fall die dritte Platte hdc. Debian lässt einem da mehr Freiheit, aber in Frage kommen im Prinzip nur der MBR von der zweiten Platte (hdb) oder direkt die Bootpartition /dev/hdb1. Will ich die Systeme booten, kann ich natürlich die Namen, Orte und Optionen der Kernel von Hand bei jedem Update in die Grub-Konfiguration des Ubuntu auf hda eintragen, aber das ist mir ehrlich gesagt zu umständlich.

Die nächste Möglichkeit ist der bereits erwähnte chainloader. Ja, aber… der funktioniert nur, wenn z.B. Debian seinen Bootloader an den Anfang einer Partition und nicht in den MBR schreibt. Ich habe es nicht hinbekommen, mit dem chainloader sozusagen den MBR einer anderen Platte zu starten. Klar, ich könnte jedesmal ins BIOS und die Bootplatte umstellen, aber das ist mir dann auch zu umständlich.

Heute habe ich eine weitere Möglichkeit herausgefunden. Es ist aus dem Grub-Menü heraus möglich, eine andere Grub-Config zu laden. Anstatt eines langen Eintrags mit kernel usw. reicht dann folgendes:

title       hdb: Debian
configfile  (hd1,0)/grub/menu.lst

Trage ich das bei den zusätzlichen Systemen in die /boot/grub/menu.lst meines Ubuntu ein, kann ich ganz locker die menu.lst laden, die unter der Fuchtel des Debian auf der zweiten Platte steht. Umgekehrt geht’s natürlich auch, so dass ich munter hin und her springen kann, auch zu dem Grub vom Eisfair-2 oder zurück zum Debian-Bootmenü und von da wieder zum Bootmenü vom Ubuntu und wenn ich dann vom Umherspringen in meinen Grub-Menüs genug habe, boote ich einfach das System, das ich will.

Smilies für Foren auf eigenem Webspace

Internetforen gibt es wie Sand am Meer und es gibt ebenso viele Seiten, wo man Smilies finden kann, die man über die Forencodes oder HTML einbinden kann. Diese Seiten tauchen auf und verschwinden wieder, einige fordern sogar direkt dazu auf, die Smilies runterzuladen und auf den eigenen Webspace zu packen. Das ist nicht weiter schlimm, aber da liegen sie nun in den Untiefen in irgendeinem Unterordner der eigenen Domain. Will man einen Smiley verwenden, wühlt man sich lange durch Verzeichnisse und schreibt die URL des Bildchens zusammen mit den Forencodes mühsam von Hand zusammen.

Genauso ging es mir und daher hab ich mir ein Skript in PHP gebastelt, das mir nicht nur alle Smilies, die ich gesammelt habe, übersichtlich anzeigt, sondern auch gleich noch den Forencode mit der passenden URL dazu. Der brauch nur noch kopiert werden und schon hat man den eigenen besonders witzigen Smiley im Internetforum seiner Wahl eingefügt. Folgendes habe ich in ein Grundgerüst einer HTML-Seite eingefügt. Ich hab das einfach mal ein wenig getrennt hier, damit es sich leichter kommentieren lässt, später einfach wieder zusammenfügen…

<?php
$extcounter = 0;
function fileextension($filename)
{
    $pospunkt = strrpos($filename,".");
    return substr($filename, $pospunkt+1,
        strlen($filename) - $pospunkt);
}

function isimagefile($filename) {
    $extension = fileextension($filename);
    if( ($extension == "gif") OR
        ($extension == "jpg") OR
        ($extension == "jpeg") OR
        ($extension == "png") ) {
        return true;
    }
    else {
        return false;
    }
}

In den beiden Funktionen passiert erstmal nichts spannendes. Die erste gibt von einem Dateinamen die Erweiterung, also den Teil nach dem letzten Punkt zurück. Die zweite Funktion bekommt so ein Ergebnis vorgesetzt und gibt wahr oder falsch zurück, je nachdem ob es die Erweiterung einer Bilddatei ist oder nicht.

function echodir($path = ".")
{
    $counter = 1;
    global $extcounter;
    $dir = dir($path);
    while(false !== ($file = $dir->read())) {
        if(("." == $file) OR (".." == $file))
            continue;
        if(is_dir($path."/".$file)) {
            echo "<h2>$file</h2>n";
            echo "<p>n";
            echo "<table>n";
            echodir($path."/".$file);
            echo "</table>n";
            echo "</p>n";
        }
        else {
            if(isimagefile($file)) {
                if($counter%2) {
                    echo "<tr>n";
                }
                echo "<td><img src="".$path."/".$file.""/></td>";
                echo "<td>[img]http://www.lespocky.de/smilies/" .
                    substr($path,2,strlen($path)-2) .
                    "/".$file."[/img]</td>n";
                if(($counter+1)%2) {
                    echo "</tr>n";
                }
                $counter++;
                $extcounter++;
            }
        }
    }
    $dir->close();
}

Hier passiert das spannende. Vorweg sei gesagt, wie die Dateien organisiert sind. In dem Ordner, wo das Skript liegt, befinden sich keine Grafikdateien, sondern nur Ordner. In diesen Ordnern liegen dann direkt die Smilies. Tiefere Ordnerebenen gibt es nicht.

echodir() ist eine rekursiv aufgerufene Funktion. Wird kein Argument übergeben, so ist das aktuelle Verzeichnis zu durchsuchen. Da wird dann in der while-Schleife jeder Eintrag durchgegangen. Bei “.” und “..” passiert nix weiter, es geht zum nächsten Schleifendurchlauf. Dann passiert eine Unterscheidung, ob der Eintrag eine Datei oder ein weiteres Verzeichnis ist. Bei einem Verzeichnis wird eine neue Überschrift geschrieben und eine Tabelle begonnen. Die Smilies sollen in zwei Spalten angezeigt werden, damit nicht so viel Platz verschwendet wird. Nach Beginn der Tabelle, wird echodir selbst rekursiv aufgerufen.

In diesem Aufruf wird die Funktion wegen der oben erwähnten Organisation der Dateien, dann auch Dateien finden und diese dann zweispaltig eintragen. Um die Spalten zu erkennen und an den korrekten Stellen die tr-Tags zu öffnen und zu schließen, werden die Smilies in $counter mitgezählt und über den Modulo-Operator jeweils in linke oder rechte Spalte geschrieben. Ausgegeben wird nicht nur der Smilie selbst zum Angucken, sondern auch das fertige Foren-Tag zum Markieren und Kopieren.

Irgendwann gibt es dann keine Verzeichnisse und Dateien mehr zum lesen, alles Smilies sind ausgegeben und … ach ja, die Funktion ist ja erst definiert und erklärt, aufgerufen muss sie noch werden:

echodir();

echo "<hr /><p>Das waren ".$extcounter." Smilies..</p>n";

?>

Die letzte Zeile ist noch ein bisschen Spielerei als Abschluss. Da die ganze Auflistung von dem PHP-Skript übernommen werden, brauchen neue Smilies nur auf den Webspace kopiert werden, fertig.

happy

Subversion Relocate mit Eclipse und TortoiseSVN

Wir sind vor einigen Tagen mit unserem Projekt IMPULS von Sourceforge auf unseren eigenen, diesen Webserver umgezogen. Das heißt, wir haben nicht nur die Homepage migriert und ein Trac eingerichtet, sondern auch das Subversion-Repo verschoben. Für ausgecheckte Working Copies ist dann ein sogenannte Relocate fällig. In Verbindung mit Eclipse kann man da auf die Nase fallen.

Subversion mit Eclipse ist eigentlich erstmal kein Problem. Es gibt Subclipse und da funktionieren Checkout, Update, Commit usw. wunderbar. Rechtsklick auf auf’s Projekt, »Team« angeklickt und los geht’s – leider ohne Relocate. Wenn ich das jetzt einfach außerhalb von Eclipse machen würde, zerschieße ich mein Projekt, weil in den Projekteinstellungen der Pfad auf’s Repository vermerkt ist. Es gibt aber eine Lösung. Zunächst vergewissern wir uns, welche URL grad für das Projekt angegeben ist:

Subversion Pfad in Projekteinstellungen

Hier ist also noch der alte Pfad des Repos bei Sourceforge eingetragen. Der nächste Schritt in der Punkt »Disconnect« im bereits erwähnten Menü »Team«. Draufklicken und dann kommt folgender Dialog. Dort bestätigt man mit der Option, die Subversion-Dateien zu behalten.

Disconnect im Menü »Team« von Eclipse

Dann kommt das eigentliche Relocate. Das geschieht unter Windows am einfachsten mit TortoiseSVN, wie im folgenden Bild zu sehen ist. Natürlich kann man hier auch die Kommandozeilenversion von Subversion benutzen, wenn man grad kein TortoiseSVN zur Hand hat. Wie das geht, steht im Subversion-Buch.

Relocate mit TortoiseSVN

Der letzte Schritt besteht darin, dem Projekt in Eclipse wieder beizubringen, dass es eigentlich ein mit Subversion verwaltetes ist. Dazu wieder ein Rechtsklick auf das Projekt und im Menü »Team« den Punkt »Share« und dann natürlich »SVN«. Subclipse erkennt, dass es sich bereits um eine korrekte Working Copy handelt und übernimmt den richtigen Pfad aus Schritt drei. Nur noch einmal den folgenden Dialog abnicken und fertig ist die Laube.

Subversion Working Copy Eclipse wieder bekannt machen

Probleme in OpenSource-Gemeinschaften

Ich lese zur Zeit das Buch »Producing Open Source Software« von Karl Fogel. Er beschreibt aus Insidersicht was für Probleme in OpenSource-Projekten so auftreten und wie man diese von vornherein vermeiden oder später lösen könnte. Ich finde dieses Buch sehr interessant in Bezug auf die Projekte, wo ich persönlich beteiligt bin oder die ich intensiv verfolge (eisfair, IMPULS, climm). Viele Probleme sind da weniger technischer denn sozialer Natur und es gibt interessante Parallelen zwischen all diesen Projekten und den im Buch als Beispiel herangezogenen. Vom Glanz so erfolgreicher Projekte wie Subversion lässt man sich da nur allzu leicht blenden. Es ist vielmehr so, dass die allermeisten Projekte besser laufen könnten. Das ist mir am Wochenende beim Release von Eisfair 1.5.0 aufgefallen und gerade heute noch an anderer Stelle deutlich geworden, als Oliver von F!XMBR über seinen persönlichen Frust mit FreeBSD berichtet hat.

Ich will in Bezug auf Eisfair an dieser Stelle nicht ins Detail gehen, aber ich werde das für die Vorschläge, die ich diese Woche im Eisfair-Team machen will, berücksichtigen…

Ausgabeprofil »LaTeX => HTML« für TeXnicCenter

Angenommen ich möchte ein LaTeX-Dokument mit tex4ht in HTML umwandeln und benutze TeXnicCenter als Editor für das eigentliche Dokument. Da liegt es nahe, sich ein passendes Ausgabeprofil zu basteln, so dass man direkt aus TeXnicCenter die gewünschte Ausgabe produzieren kann. Man muss dabei aber aufpassen, was für Optionen man htlatex.exe mitgibt. Die folgenden zwei Screenshots illustrieren funktionierende Einstellungen.

Ideal wäre natürlich, wenn nicht jedesmal ein neuer Tab im Firefox aufgemacht werden würde, wenn man im TeXnicCenter F5 drückt, sondern der alte aktualisiert/ersetzt würde. Wenn da jemand einen Tipp hätte, wie man das realisieren kann, würde ich mich freuen.

Windows Vista: Diktatur des Systems

Heute früh habe ich noch gedacht, dass ich zur Zeit gar nichts habe, worüber ich bloggen könnte – gerade ist mir wieder etwas über den Weg gelaufen:

Für meinen HiWi-Job an der Uni habe ich einen Labor-Arbeitsplatz bekommen. Das ist ein angenehmer Raum, der arbeitsam eingerichtet ist und mit guter Rechentechnik ausgestattet ist. Der Nachteil: Auf den Maschinen läuft Windows Vista. (Darüber will ich jetzt nicht allzu sehr meckern, ich könnte auf das alternativ installierte Fedora Linux ausweichen – aber dann würde ich mich darüber beschwerden …)

Um die Sicherheit des Systems zu erhöhen, sind automatische Updates installiert. Die werden nach einem mir nicht bekannten (weil eigentlich uninteressanten) Prinzip angestoßen und laufen im Hintergrund ab.

Gerade wurde solch ein Update durchgeführt. Und damit die Änderungen auch wirksam werden, muss (wie bei Windows schon immer üblich) das System neugestartet werden. Ich hätte erwartet, dass mir genau das mitgeteilt wird – mit der Option, den Neustart jetzt auszuführen oder auf einen späteren Zeitpunkt zu verschieben.

Aber bei Vista scheint zu gelten: Rechne nicht damit, lass Dich überraschen!

Jedenfalls erschien in der rechten unteren Bildschirmecke ein Fensterchen, das mich freundlich darauf hinwies, dass mir noch ganze 5 Minuten bis zu einem unabwendbaren Systemneustart bleiben.

Da frage ich mich doch: Was erlaubt Microsoft sich eigentlich? Ich möchte mit dem System arbeiten – und zwar, wann es mir passt, nicht, wenn das System mal Lust dazu hat! Wenigstens die Möglichkeit, den Neustart abzuwenden, hätte ich erwartet.

So hat mich die Aktion “nur” 10 Minuten Arbeitszeit gekostet. Was passiert, wenn ich auf dem Rechner eine aufwändigere Berechnung laufen lasse? Vielleicht noch über Nacht? Muss ich damit rechnen, morgens ohne Ergebnisse dazustehen, weil das Betriebssystem zwischendurch mal neustarten wollte? Offenbar ja.

Bevor ich mich daran mache, Dinge zu tun, die ich nicht innerhalb von 5 Minuten unterbrechen kann, muss ich mir also noch ein vernünftiges Betriebssystem zulegen oder aber die automatischen Updates deaktivieren.

Um mal, abschließend, einen Gruppennamen aus einem mittlerweile nicht mehr ganz so beliebten Studentenportals zu zitieren: “War doof, merkste selber, ne?”

Kleiner Passwortgenerator in Perl

Aus der Reihe »Schnelles Kamel-Gehacktes« heute der Beitrag, wie man quick’n’dirty einen Passwortgenerator in Perl schreibt. Anforderungen vorher: 8 Zeichen, weil viele Linuxe sowieso dahinter abhacken; nur Zeichen, die in den ersten 7 Bit der ASCII-Tabelle stehen und dann auch nur welche, die man direkt oder mit Umschalttaste (Shift) eingeben kann, nichts wozu man AltGr oder ähnliches bräuchte. Dazu braucht man eine ASCII-Tabelle und dann geht’s los:

#!/usr/bin/perl -w
# $Id: gen_passwd.pl 111 2008-02-06 13:45:24Z alex $
use strict;

my @allowed_chars;
# ASCII von 0x21 bis 0x3f
# das sind die meisten Sonderzeichen und die Ziffern
for (my $i=0x21; $i<=0x3f; $i++) {
	push @allowed_chars, chr($i);
}

# ASCII von A bis Z
for (my $i=0x41; $i<=0x5a; $i++) {
	push @allowed_chars, chr($i);
}

# ASCII von a bis z
for (my $i=0x61; $i<=0x7a; $i++) {
	push @allowed_chars, chr($i);
}

push @allowed_chars, chr(0x5f); # '_'

my $char_count = scalar @allowed_chars;

for (my $i=0; $i<8; $i++) {
	print $allowed_chars[int rand $char_count];
}

print "\n";

Diese Passwörter eignen sich gut als initiale Passwörter für neue Nutzer. Die Nutzer sollten sodann aufgefordert werden, sich mit passwd ein neues zu setzen. Da so ein zufälliges Passwort mit Zahlen und Sonderzeichen sehr schwer zu merken ist, werden sie dieser Aufforderung sicher nachkommen.