Kodierte Texte mit VBA umwandeln

Auch wenn die Assistenten von Access zum Import und Export von Textdateien über ihre Einstellungsseiten genügend Möglichkeiten bieten, um mit allen erdenklichen Formaten zurechtzukommen, gibt es häufig Situationen, in denen die Verarbeitung über VBA-Programmierung benötigt wird. Hier geht es in erster Linie um den Umgang mit Texten in unterschiedlicher Sprachkodierung.

Beispieldatenbank

Die Beispiele dieses Artikels finden Sie in der Datenbank 1503_Textumwandlung.mdb.

Sprachen, Codepages, CharacterSets

Beleuchten wird zunächst, was es mit den Sprachcharakteristiken von Textdateien auf sich hat. Wenn Sie sich eingehender mit Textformaten beschäftigen – Stichwort Internationalisierung -, so begegnen Sie immer wieder Begriffen, wie Language Id, Locale Identifier (LCID), Codepage, Unicode und Character Set. Jeder hat seine eigene Bedeutung und doch hängen alle irgendwie miteinander zusammen. Grundsätzlich geht es dabei nicht nur um Textdateien, sondern generell um den Umgang mit Texten im System. Betroffen sind damit etwa die Tastatur, die Textausgabe auf dem Bildschirm, Schriftarten, oder auch die Interaktion mit dem Internet.

An oberster Stelle steht die Sprache eines Textes, die wiederum einer Gruppe von Sprachen angehören kann. Deutsch etwa gehört zur Gruppe Westeuropa und USA. Windows unterstützt von Haus aus mehrere Sprachgruppen und Sprachen. Dabei gibt es einen Unterschied zwischen nur unterstützten und den installierten Sprachen. Während es genau 17 Gruppen gibt, kann die Zahl der zugehörigen Sprachen selbst variieren. Für den Umgang mit Texten ist die Gruppierung weniger interessant, denn sie betrifft vor allem die Tastaturbelegung, die in der Regel pro Sprachgruppe eindeutig ist.

Welche Sprachen Windows unterstützt können Sie mit einem Doppelklick auf die Tabelle tblLCIDs der Beispieldatenbank herausfinden (siehe Bild 1). In der zweiten Spalte finden Sie die Sprache, in der dritten das zugehörige Land. Auf die Bedeutung der vierten (CP) kommen wir noch zu sprechen. Das Feld LCID legt die eindeutige Id der Sprache fest. Diese Zahl werden Sie häufiger unter Windows finden. Schauen Sie etwa in den Order von Microsoft Office im Explorer zu Ihrer Office-Version. Dort werden Sie als Unterverzeichnisse die von Ihrem Office unterstützten Sprachen als LCID finden. Die 1033 für Englisch (USA) ist immer mit dabei, die 1031 für Deutsch wahrscheinlich auch. In diesen Verzeichnissen befinden sprachspezifische Ressourcen für Office. Installieren Sie etwa zusätzlich die Rechtschreibprüfung für Französisch, so entsteht hier ein weiterer Ordner mit dem Namen 1036.

Inhalt der Tabelle tblLCIDs mit den von Windows unterstützten Sprachen

Bild 1: Inhalt der Tabelle tblLCIDs mit den von Windows unterstützten Sprachen

Ist die Sprache mit ihrer LCID noch eindeutig, so ist es das Land nicht. Filtern Sie die Tabelle nach dem Land Deutschland, so ergeben sich vier Datensätze, unter denen sich etwa auch das sehr spezielle Niedersorbisch befindet.

Der Inhalt dieser Tabelle ist keineswegs in Stein gemeißelt. Je nach Windows-Version, -Installationsoptionen und -Sprache kann sie andere Datensätze aufweisen. Sie können die Tabelle auf Ihrem System selbst erstellen. Dazu gibt es im VBA-Modul mdlTextconverter der Beispieldatenbank eine Prozedur EnumLanguageGroupsLocales. die Sie aus dem Direktfenster des VBA-Editors heraus aufrufen können. Sie löscht zunächst den Inhalt der Tabelle und erstellt ihn dann über verschiedene API-Funktionen neu. Es gibt allerdings keine API-Funktion, die die Sprachen direkt aufzählen könnte. Erst müssen die einzelnen Sprachgruppen ermittelt (EnumLanguageGroups), in einer Collection zwischengespeichert und anschließend der Sprach-Enumeration übergeben werden. In Listing 1 sehen Sie den Code der Prozedur, der hier nur exemplarisch angeführt wird.

Sub EnumLanguageGroupsLocales()
     Dim vID As Variant
     
     Set colInfo = New VBA.Collection
     CurrentDb.Execute "DELETE FROM tblLCIDs"
     CurrentDb.Execute "INSERT INTO tblLCIDs(LCID,Language,Country,CP) " & _
         "VALUES (0,'(Unbekannt)','(Unbekanntes Land)',0)"
     
     EnumLanguageGroups
     For Each vID In colInfo
         EnumLanguageGroupLocales AddrOf(AddressOf LanguageGroupLocalesProc), _
             ByVal CLng(Val("&H" & vID)), ByVal 0&, 0&
     Next vID
End Sub

Listing 1: Prozedur zum Füllen der Tabelle tblLCIDs mit Sprachdatensätzen

Denn im Rahmen dieses Beitrags würde es eindeutig zu weit führen, die nicht unkomplizierten und mit API-Aufrufen gespickten Funktionen näher zu erläutern. Und ohne API-Funktionen kommt man allein mit den Bordmitteln der VBA-, Access- und Office-Bibliotheken bei diesem Thema nicht weiter.

Kommen wir zu der vierten Spalte der Tabelle mit dem Namen CP, was ein Kürzel für Codepage darstellt. Dort ist zu jeder Sprache die Vorgabe-Codepage zu finden. Was aber ist eine Codepage Dazu ein Ausflug in die binäre Speicherung von Texten: Für jedes Zeichen wird bei Single-Byte-Formaten (ANSI, OEM) genau ein Byte festgelegt. Schreiben Sie ein A in einen Text, so finden Sie unter einem Hex-Editor an dieser Stelle die Zahl 65. Genauso gut können Sie auch VBA bemühen:

  Asc ("A")   -> 65
 Chr (65) -> "A"

Die 65 kennzeichnet, welches Zeichen innerhalb der deutschen Sprach-Codepage zu finden ist. Eine Codepage ist damit imgrunde eine Tabelle mit 255 Datensätzen, die jeder Position ein bestimmtes Zeichen zuordnet. Und eben diese Position ist, je nach Sprache, unterschiedlich. So ermitteln Sie etwa für ein ü den Code 220. In Russland aber kennt man kein ü. Die dort verwendete Codepage ordnet der 220 ein anderes, ein kyrillisches Zeichen zu. Ein Text in deutscher Sprache mit Umlauten wird dort falsch dargestellt, wenn er mit dem russischen Notepad geöffnet wird. Ein russisches Word hingegen wird beim öffnen der Textdatei nachfragen, welche Codepage zu verwenden ist, und erst nach Einstellung von Westeuropa (Windows) das korrekte Ergebnis zeitigen.

Die Codepage ist also die zentrale Schaltstelle, wenn es um Textverarbeitung geht. Erst sie ermöglicht es, einem Byte-Code ein bestimmtes Zeichen zuzuordnen.

Nun braucht es nicht für jede einzelne Sprache eine gesondert Codepage. Die ANSICodepage für Westeuropa (1252) etwa kennt fast alle benötigten Zeichen für die Sprachen Europas, also die deutschen Umlaute und die französischen oder spanischen Akzente.

Es ist demnach ökonomischer, mehreren Sprachen eine Codepage zuzuweisen. So steht in der CP-Spalte der Sprachtabelle für Deutsch, Italienisch, Französisch, Spanisch, Englisch überall die 1252. Filtern Sie die Tabelle nach diesem Wert: Erstaunlicherweise gibt es über Hundert Sprachen, die sich diese Codepage teilen.

Nun kann man sich eine Zahl als Bezeichner für eine Codepage nur schwer merken. Deshalb haben Codepages auch Namen. öffnen Sie die Tabelle tblCodepages der Beispieldatenbank (siehe Bild 2). Sie finden dort unter dem Feld Description die Bezeichnungen der Codepages. Die 1251 nennt sich exakt ANSI – Lateinisch I. Das ist gut zu wissen, denn eine Bezeichnung mit dem Teilinhalt Westeuropa werden Sie nämlich vergeblich suchen.

Inhalt der Tabelle tblCodepages

Bild 2: Inhalt der Tabelle tblCodepages

In der Tabelle sind allerlei kryptische Codepages aufgeführt, die zum großen Teil veraltet und nicht mehr in Gebrauch sind.

Sie werden sie unter Umständen dennoch benötigt, wenn Sie es mit Textdateien zu tun haben, die aus IT-Urzeiten stammen, wie die IBM-Codes. Eher noch treffen Sie auf Texte von MS-DOS-Systemen, die in der Tabelle durch das Präfix OEM gekennzeichnet sind.

Es gibt überdies ein Problem mit diesen Bezeichnungen, die leider nicht so eindeutig sind. Die Bezeichnungen in der Tabelle sind das Resultat einer Windows-API-Funktion, die in der Prozedur GetCPName der Beispieldatenbank zum Einsatz kommt:

  GetCPName (1252) 
- > "(ANSI - Lateinisch I)"

Mithilfe dieser Prozedur und derjenigen zum Auflisten der Codepages (EnumCodepages), aufgerufen aus dem VBA-Direktfenster, können Sie den Inhalt der Tabelle neu erstellen. Wie wir noch sehen werden, gibt es aber auch noch die Möglichkeit, sich über DAO und die Access Database Engine alle Codepages ausgeben zu lassen. Dieses Ergebnis gibt die Tabelle tblCodepagesDAO wieder. Und dort steht für die Codepage 1252 die Bezeichnung Westeuropäisch (Windows). Damit Sie alle Sprachen, die zugehörigen Default-Codepages und deren Bezeichnungen im Blick haben, fasst die Abfrage qry_LCID_CP die drei besprochenen Tabellen zusammen und gibt sie wie in Bild 3 wieder. Sie sollte genügen, um aus einer Bezeichnung die zugehörige Codepage zu identifizieren. Denn diese Bezeichnungen listen etwa die Kombinationsfelder von Access oder Word auf, wenn es um die Auswahl einer Codepage geht.

Alle Sprachen und deren Codepages gibt die Abfrage qry_LCID_CP aus

Bild 3: Alle Sprachen und deren Codepages gibt die Abfrage qry_LCID_CP aus

Zuletzt fehlt noch die Erläuterung des Begriffs Character Set, zu Deutsch Zeichensatz. Die Zuordnung der Bytes eines Textes zu bestimmten Zeichen über die Codepage ist das Eine, die visuelle Darstellung dieses Zeichens über Schriften das Andere. Eine Schriftart kann mit der Codepage allein noch nichts anfangen. Der Satz von darstellbaren Zeichen ist durch den Zeichensatz definiert. Die wiederum ist ebenfalls über eine bestimmte Nummer gekennzeichnet. Die Funktion CharSetFromCP der Beispieldatenbank nimmt als Parameter eine Codepage entgegen und gibt die ID des zugehörigen Zeichensatzes zurück:

  CharSetFromCP(1252)
  -> 0 (Deutsch, System-Zeichensatz)
  CharSetFromCP(1251)
 -> 204 (Russisch, Kyrillisch)

Das Ergebnis 0 bedeutet, dass es sich um den Vorgabezeichensatz des installierten Systems handelt. Um kyrillische Zeichen darzustellen, benötigen Sie das CharacterSet 204.

Die Standardschriftarten von Windows, wie Arial, Times New Roman, Courier New, et cetera, enthalten alle diese Zeichen. Die Nummer des Zeichensatzes identifiziert dann, welcher Block von Zeichen zu verwenden ist.

Unicode

Bisher ging es nur im Texte im Single-Byte-Format, wobei ANSI der Standard ist. Die Beschränkung auf 256 mögliche Zeichen durch ein Byte führt hier zum beschriebenen System mit den LCIDs und Codepages, welches einige Kopfschmerzen bereiten kann. Um einiges angenehmer ist da das Unicode-Format, das jedem Zeichen mehrere Bytes spendieren kann.

Bei UTF-16 sind das pro Zeichen in der Regel zwei Bytes. Dadurch steigt die Zahl zuordenbarer Zeichen auf 2^16, was für alle Zeichen dieser Welt ausreicht. Um Codepages müsste man sich im Prinzip keine Gedanken mehr machen, wenn alle Texte in diesem Format gespeichert wären. Allerdings bedeutet diese Erweiterung noch nicht, dass damit auch die Sprache des Textes bekannt ist und welcher Zeichensatz zur Darstellung kommen soll. Insofern behält das System um LCIDs, Codepages und Charactersets weiterhin seine Bedeutung.

Bei UTF-8 wird es komplizierter, weil ein Zeichen meist ein Byte belegt, aber bei spezielleren Zeichen bis zu vier. Der Beginn eines Mehr-Byte-Zeichens wird durch bestimmte Flag-Bytes eingeleitet.

Dasselbe gilt für das wenig verbreitete UTF-7, welches eine noch komplexere Kodierung vorsieht, denn ein Zeichen wird hier durch nur 7 Bits eines Bytes identifiziert. Infolgedessen sind speziellere Zeichen durch eine ganze Kette von Byte-Anweisungen repräsentiert.

Eigentlich ist das UTF-8-Format das optimalste, weil es alle Zeichen darstellen kann, aber wenig Platz benötigt. Weil es jedoch durch variable Byte-Anzahl pro Zeichen etwas schwieriger zu handhaben ist, verwendet VBA ausschließlich das UTF-16-Format. Tatsächlich sind alle Strings unter VBA solche Unicode-Strings. Das lässt sich leicht nachweisen. Während die Funktion Len die Zahl der Zeichen eines Strings zurückgibt, ermittelt LenB die Anzahl der Bytes, die dafür benötigt werden. Sie gibt grundsätzlich das Doppelte aus:

  Len ("ACCESS")  -> 6
 LenB ("ACCESS") ->12

Intern steht dieser Text mit diesen Bytes im Speicher:

65 - 0 - 67 - 67 - 0 - 69 - 0 - 83 -0 - 83 - 0

Das macht auch schon deutlich, dass UTF-16 zur Speichervergeudung neigt. Der häufigste Wert in diesen Strings ist 0. Da Strings in Textfeldern seit Access 2000 ebenfalls grundsätzlich in UTF-16 gespeichert sind, sieht Access die Möglichkeit vor, für diese Felder als Eigenschaft die Unicode-Kompression einzuschalten, um Speicherplatz zu sparen. Die Kompression elimiert die ganzen Nullen und führt intern zu einem Format, das UTF-8 ähnelt.

Es gibt also einige Varianten von Unicode, und bei Unicode-Textdateien kommt noch ein weiterer Stolperstein hinzu. Es gibt zum einen solche Dateien, die die Bytes vertauschen. Der String oben sieht dann so aus:

0 - 65 - 0 - 67 - 67 - 0 - 69 - 0 - 83 -0 - 83 

Hier handelt es sich um die LE-Variante (Low Endian), während VBA die BE-Variante (Big Endian) nutzt. Zum anderen kann eine Unicode-Datei noch sogenannte Lead-Bytes ganz zu Anfang beherbergen, die sie eindeutig als im Unicode-Format kodiert kennzeichnen soll. Das ist im Allgemeinen die Byte-Folge 255, 254 oder 254, 255, je nachdem, ob es sich um Big Endian oder Low Endian handelt. Bei UTF-8 stehen hier sogar 3 Lead-Bytes.

All dies macht klar, dass auch Textdateien im Unicode-Format Probleme bescheren können. Damit VBA mit Strings korrekt umgehen kann, müssen diese immer in UTF-16 vorliegen. Auch alle Steuerelemente, etwa die Textbox, erwarten dieses Format, sonst kommt es zu seltsamen Zeichenfolgen.

Texte konvertieren

Die wichtigste VBA-Funktion zur Umwandlung von ANSI in Unicode und zurück ist StrConv:

  StrConv ("ACCESS", vbUnicode)
-> "A C C E S S"
  StrConv ("ACCESS", vbFromUnicode)
-> ""

Da der übergebene String bereits im Unicode-Format vorliegt, verdoppelt VBA nicht nur das Zeichen-Byte, sondern auch die interne Null, was zu drei Nullen führt, die als Leerzeichen ausgegeben werden. Umgekehrt bildet sich bei der Konvertierung aus Unicode nach ANSI zwar eine gültige Byte-Folge, die aber von VBA nicht angezeigt werden kann, was durch die Fragezeichen deutlich wird. Kombinieren Sie beide Funktionen, so erhalten Sie ein sinnvolles Ergebnis und den Nachweis, dass bei der Konvertierung nach ANSI nichts verloren ging:

  StrConv(StrConv("ACCESS", _         vbFromUnicode),vbUnicode)
- > "ACCESS"

Eine Besonderheit von StrConv ist, dass es nicht nur Strings als Parameter entgegennimmt, sondern auch Byte-Arrays. Oder aus Byte-Arrays wieder Strings erzeugen kann:

Dim bin() As Byte
bin = StrConv("ACCESS", vbFromUnicode)
  UBound(bin)  '-> 5 = 6 Bytes
  StrConv(bin, vbUnicode)
-> "ACCESS"

über Byte-Arrays können Strings so am einfachsten manipuliert werden.

Ein weiterer Parameter von StrConv, der optional angegeben werden kann, ist LCID. Damit kann eine Sprache festgelegt werden, in die der String konvertiert werden soll und umgekehrt. Das macht es möglich einen String von einer Sprach-Id in die andere umzuwandeln. Die Funktion ConvertUnicodeToUnicode (Listing 2) der Beispieldatenbank macht sich das zunutze. Hier wird aus dem übergebenen Text zunächst ein Byte-Array erzeugt. Dabei wird der Parameter LCID weggelassen, was bedeutet, dass StrConv die Sprach-Id des Systems verwendet und damit einen westeuropäischen Text. Beim Zurückverwandeln in einen String als Rückgabe der Funktion jedoch wird die LCID eingesetzt, die als optionaler Parameter übergeben wurde. Rufen Sie die Funktion etwa so auf:

Public Function ConvertUnicodeToUnicode(ByVal sText As String, _
     Optional lLCID As Long) As String
     Dim bin() As Byte
     
     On Error GoTo Fehler
     If lLCID = 0 Then lLCID = GetSystemDefaultLangID
     bin = StrConv(sText, vbFromUnicode)
     ConvertUnicodeToUnicode = StrConv(bin, vbUnicode, lLCID)
     Exit Function
     
Fehler:
     MsgBox "Ungültige Sprach-ID (LCID)", vbCritical
End Function

Listing 2: Funktion zum Konvertieren eines Textes mithilfe einer Sprach-ID (LCID)

  ConvertUnicodeToUnicode( _ 
"André",1031)

Möchten Sie weiterlesen? Dann lösen Sie Ihr Ticket!
Hier geht es zur Bestellung des Jahresabonnements des Magazins Access [basics]:
Zur Bestellung ...
Danach greifen Sie sofort auf alle rund 400 Artikel unseres Angebots zu - auch auf diesen hier!
Oder haben Sie bereits Zugangsdaten? Dann loggen Sie sich gleich hier ein:

Schreibe einen Kommentar