|
Probieren Sie doch einmal folgendes aus:
Dim colTest As New Collection
colTest.Add "Item1", "daß"
colTest.Add "Item2", "dass"
An und für sich sieht das doch ganz gut aus - es werden zwei
Elemente mit offensichtlich verschiedenen Schlüsseln hinzugefügt.
Doch Sie erhalten den Laufzeitfehler 457 - "Dieser
Schlüssel ist bereits einem Element dieser Auflistung
zugeordnet".
Bei Schlüsseln aus Wörtern in verständlicher Sprache mögen
Sie das Problem ja gegebenenfalls noch umgehen können, indem Sie
auch für sich die beiden Schreibenweisen als identisch betrachten
und sie eventuell vor der Schlüsselzuweisung entsprechend
umwandeln. Doch wenn es sich um abstrakt (anhand welcher Kriterien
und aus welchen Gründen auch immer) generierte Schlüssel handelt,
etwa "a9xssv&t" und "a9xßv&t", dann
sollten die beiden Schlüssel wohl doch als voneinander verschieden
behandelt werden können.
Warum macht aber die Collection intern aus dem "ß" ein
"ss"? Zwei Gedanken führen auf die Spur des
Phänomens. Der erste: Die Collection ignoriert auch die
Groß-/Kleinschreibung. Der zweite Gedanke: Sie behandelt auch noch
andere Zeichen auf gleiche Weise, nämlich die Zeichen
"Æ" (Ascii 198) und
"æ" (Ascii 230). Letztere beiden
werden wie "AE" und "ae"
behandelt (wobei es auch nur um die Groß-/Kleinschreibungsvarianten
handelt, die es beim "ß" ja nicht gibt). Bei
keinem anderen Zeichen sonst tritt das Phänomen auf.
Alle drei Zeichen haben übrigens eines gemeinsam: Es handelt
sich um so genannte "Ligaturen". Das sind zu einem Zeichen
verschmolzene, ursprünglich einmal getrennt mit zwei Zeichen
geschriebene Buchstaben bzw. Laute (Eigentlich müsste zwar das
"ß" im Sinne der ursprünglichen Verschmelzung in
"sz" aufgelöst werden, aber diese Auflösung ist
etwas verstaubt und wird heute kaum noch verwendet, "auszer"
von wohl einigen wenigen EMail-/NG-Anti-Umlaut-Fans...).
Die Auflösung der Ligatur-Zeichen in Einzelzeichen tritt auch bei
der Anwendung der Vergleichsmethode nach der Anweisung "Option
Compare Text" auf, die global für ein Modul in dessen
Deklarationsteils platziert werden kann (etwa beim Like-Operator),
und bei Funktionen wie StrComp, InStr, InStrRev, Replace usw., bei
denen die Vergleichsmethode "vbCompareText" per optionalem
Parameter angegeben werden kann.
Daraus ergibt sich, dass die Collection offensichtlich intern und
unabhängig von der Moduleinstellung (die ja in der Voreinstellung
standardmäßig einem "Option Compare Binary"
entspricht) die Textvergleichsmethode verwendet. Leider lässt sich
diese interne Voreinstellung der Collection nicht ändern - sie
bietet weder eine entsprechende Eigenschaft oder (optionale)
Parameter dazu, und sie reagiert auch nicht auf eine Änderung der
Voreinstellung eines Moduls.
Sie werden wohl oder übel die Schlüssel modifizieren müssen.
Es würde allerdings wenig nützen, jeden Schlüssel vor Verwendung
einzeln auf das Vorkommen einer Ligatur hin zu prüfen und irgendwie
zu modifizieren. Denn jede willkürliche Modifikation könnte
schließlich auch auf "natürliche" Weise vorkommen. So
wäre eine wirkliche Eindeutigkeit nicht zu gewährleisten, wenn Sie
nicht exakt vorherbestimmen können welche Schlüssel auftreten
könnten. Nur eine systematische Modifikation aller Schlüssel hilft
in diesem Fall.
Um lediglich das Ligaturen-Problem zu lösen, würde eine
Unterscheidung der Länge genügen. Hängen Sie jeweils die
Längenangabe an den Schlüssel an, unterscheiden sich ein
Schlüssel der eine Ligatur enthält, und ein Schlüssel, der eine
aufgelöste Ligatur enthält, eindeutig voneinander.
Wenn Sie jedoch noch den Schritt weiter gehen wollen und
vollkommen eindeutige Schlüssel benötigen, die auch hinsichtlich
der Groß-/Kleinschreibung eindeutig sind, wird es etwas
aufwändiger. Der Aufwand wird sicher auch etwas Performance und
Speicher kosten. Doch da Schlüssel meistens recht kurze Strings
sind, mag das, falls es wirklich auf die Eindeutigkeit ankommt,
nicht so sehr ins Gewicht fallen. Zu lösen wäre also folgende
Aufgabe:
Dim colTest As New Collection
colTest.Add "Item1", "abc"
colTest.Add "Item2", "ABC"
Und wie erhalten Sie nun einen, für solche Zwecke
"binären", eindeutigen Schlüssel? Nun, fügen Sie
einfach hinter jeden Kleinbuchstaben ein Leerzeichen und hinter
jeden Großbuchstaben ein beliebiges, einmal festgelegtes Zeichen
(etwa Ascii 255) ein. Dadurch unterscheiden sich nun in
der Folge Groß- und Kleinbuchstaben aufgrund des jeweils
nachfolgenden Zeichens. Ebenso ist das Ligaturen-Problem gelöst, da
in einem Schlüssel vorkommende aufgelöste Ligaturen ("ss",
"AE", "ae") schon vor der Zuweisung als
Schlüssel an die Collection durch das zusätzlich eingefügte
Zeichen auseinandergerissen werden.
Die folgende Hilfsfunktion KeyUnique präpariert einen Schlüssel
nach Wunsch. Sie können im optionalen Parameter CompareMethod den
Grad der Eindeutigkeit angeben. Die Voreinstellung ist kuCompareText
- der Schlüssel wird nicht präpariert und die Collection verhält
sich wie gewohnt. Geben Sie kuCompareLigatures an, werden nur
Ligaturen eindeutig umgesetzt, Groß- und Kleinschreibung sind, wie
bei der Collection gewohnt, weiterhin nicht eindeutig. Die Angabe
kuCompareBinary hingegen sorgt (wie beschrieben) dafür, dass ein
Schlüssel in jedem Fall eindeutig ist. Diese Art der
Schlüsselpräparierung verhilft Ihnen auch zu eindeutigen
Schlüsseln bei anderen Collections (etwa der Nodes-Collection beim
TreeView-Steuerelement), die ebenfalls nicht die Groß-/Kleinschreibung
berücksichtigen, selbst wenn sie keine Probleme mit den Ligaturen
haben sollten.
Public Enum CompareMethodConstants
kuCompareBinary
kuCompareLigatures
kuCompareText
End Enum
Public Function KeyUnique(Key As String, _
Optional ByVal CompareMethod As CompareMethodConstants = _
kuCompareText) As String
Dim nRetKey As String
Dim l As Long
Dim nChar As String
Select Case CompareMethod
Case kuCompareBinary
nRetKey = Space$(Len(Key) * 2)
For l = 1 To Len(Key)
nChar = Mid$(Key, l, 1)
If UCase(nChar) = nChar Then
Mid$(nRetKey, l * 2 - 1, 2) = nChar & Chr$(255)
Else
Mid$(nRetKey, l * 2 - 1, 1) = nChar
End If
Next 'l
KeyUnique = nRetKey
Case kuCompareLigatures
KeyUnique = Key & CStr(Len(Key))
Case kuCompareText
KeyUnique = Key
End Select
End Function
|