Monthly Archives: March 2008

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