UTF-8 in ISO-8859-1 plus numerische Zeichenreferenzen wandeln
Robert
- php
0 Elya0 dedlfix0 Robert
4 Christian Kruse0 Robert0 Robert0 Candid Dauth0 Robert
Hallöle,
aus einer Datenbank bekomme ich UTF-8 Strings mit vielen verschiedenen Zeichen, so daß kein anderes Encoding in der Datenbank sinnvoll ist - außerdem wird die Datenbank noch von anderer (Fremd-)Software benutzt, so daß eine Änderung nicht möglich wäre.
Die HTML-Ausgabe zum Client muß mit ISO-8859-1-Encoding erfolgen (daran kann ich leider nichts ändern - wenn's nach mir ginge, würde ich dem Client UTF-8 schicken).
Jetzt muß ich also die UTF-8-Zeichen umwandeln.
Ok, die Funktion utf8_decode existiert.
Aber: die reicht ja nicht, die wandelt ja nur die UTF-8-Zeichen um, die auch in ISO-8859-1 existieren.
Dann blieben aber noch Zeichen übrig, die nicht in ISO-8859-1 existieren, also hilft mir utf8_decode nicht.
Auch htmlentities() hilft mir nicht, denn in der Datenbank sind Zeichen, für die es keine named entities in HTML gibt.
Gibt es in PHP irgendeine Methode, die mir alle UTF-8-Zeichen, die nicht in ISO-8859-1 enthalten sind (oder von mir aus auch alle, die nicht in US-ASCII enthalten sind) in numerische Zeichenreferenzen (  usw.) umwandelt?
Vielen Dank im Voraus,
cu,
Robert
Hallo Robert,
Gibt es in PHP irgendeine Methode, die mir alle UTF-8-Zeichen, die nicht in ISO-8859-1 enthalten sind (oder von mir aus auch alle, die nicht in US-ASCII enthalten sind) in numerische Zeichenreferenzen (  usw.) umwandelt?
Dieses Tool macht das mit Javascript, vielleicht hilft Dir das als Anregung.
Gruß aus Köln-Ehrenfeld,
Elya
Hallo,
Dieses Tool macht das mit Javascript, vielleicht hilft Dir das als Anregung.
Das scheitert in PHP aber an der nicht vorhandenen Funktion String.charCodeAt() ;-)). Aber die hat Christian ja jetzt indirekt vorgegeben.
viele Grüße
Axel
Hallo Axel,
Das scheitert in PHP aber an der nicht vorhandenen Funktion String.charCodeAt() ;-)). Aber die hat Christian ja jetzt indirekt vorgegeben.
Stimmt natürlich ;-) Bei der Gelegenheit kann ich mich in diesem utf-8-Thread gleich bei Dir und CK für die Hilfe in meinem utf-8-Thread bedanken, mit Eurer und molilys Hilfe hab ich mein Problem lösen können, die Erleuchtung kam leider erst, nachdem der Thread schon im Archiv war. Ich habe CKs ord-Funktionen kombiniert mit den mb-Funktionen und ein brauchbares Ergebnis erzielt.
Gruß aus Köln-Ehrenfeld,
Elya
你好 Elya,
[...] </archiv/2005/3/t104162/#m642038> [...]
Darf man fragen, wofuer du das eigentlich gebraucht hast?
再见,
克里斯蒂安
Hallo Christian,
Darf man fragen, wofuer du das eigentlich gebraucht hast?
ich wußte, daß ich das noch rauslassen müßte ,-) - Вот викириллица
Nochmal danke für Deine Hilfe.
Gruß aus Köln-Ehrenfeld,
Elya
你好 Elya,
Darf man fragen, wofuer du das eigentlich gebraucht hast?
ich wußte, daß ich das noch rauslassen müßte ,-) - Вот викириллица
Ahjo, nice :)
Nochmal danke für Deine Hilfe.
Nicht dafuer, wenn ich keinen Bock haette zu helfen wuerd ich hier nicht
lesen *g*
再见,
克里斯蒂安
Hallo zusammen,
Darf man fragen, wofuer du das eigentlich gebraucht hast?
ich wußte, daß ich das noch rauslassen müßte ,-) - Вот викириллица
wie schön, daß hier mal alle Freunde von utf-8 versammelt sind. Verratet mir doch mal eins: wie sucht Ihr nicht-case-senitiv in utf-8, also z.B. ein ä als [äÄ]? ...Achso: es geht um DBs, die keine multibyte-Unterstützung haben.
Gruß, Andreas
Hallo,
wie schön, daß hier mal alle Freunde von utf-8 versammelt sind. Verratet mir doch mal eins: wie sucht Ihr nicht-case-senitiv in utf-8, also z.B. ein ä als [äÄ]? ...Achso: es geht um DBs, die keine multibyte-Unterstützung haben.
Du meinst, UTF-8 transformierte Octet-Streams stehen als Feldinhalte in Text-Feldern von Datenbanken, die kein UTF-8 unterstützen?
Dann sucht man, wenn Du SELECT ... WHERE meinst, nach Inhalten mit Zeichen außerhalb US-ASCII nicht-case-senitiv in diesen PseudoStrings genau so wie case-senitiv, nämlich gar nicht, weil es nicht geht ;-)).
Wenn im Feldinhalt "Höhe" als "48c3b66865" steht, Du aber nur WHERE feld = "Höhe" eingeben kannst, und Höhe eben in ISO-8859-1 "48f66865" ist, dann kann dort nichts übereinstimmen.
Du hast keine Chance, nutze sie ;-)). Man könnte, wenn die Datenbank das anbietet, mit WHERE feld = "H".concat(0xc3b6).concat("he") suchen. Oder man könnte mit Hilfe des ersten Teils der Funktion von Christian, wo es um Zwei-Octet-UTF-8, beginnend mit c<0xe0 geht, die UTF-8-Octetstreams micht in Entities, sondern in 1-Byte-Hex-Werte umwandeln. Das würde dann in Teilen ISO-8859-1 entsprechen und aus "c3b6" würde "f6". Diese Lösung wäre aber nur für ISO-8859-1-Sonderzeichen geeignet. ISO-8859-2-Sonderzeichen würden falsche Resultate bringen.
viele Grüße
Axel
Hi,
Du meinst, UTF-8 transformierte Octet-Streams...?
wenn die so heißen - ja. Also es steht nicht, wie Du weiter unten beschreibst Hex-Codiert, sondern utf-8-codiert byte für byte quasi als latin-1 geschrieben, also aus ä wird ä und aus Ä wird Ä.
stehen als Feldinhalte in Text-Feldern von Datenbanken, die kein UTF-8 unterstützen?
genau, in MySQL 3.23 bzw. 4.0.x ... jaja, vielleicht sollte ich mich mal mit Version 4.1 beschäftigen ;-) - trau mich aber nicht.
Wenn im Feldinhalt "Höhe" als "48c3b66865" steht, Du aber nur WHERE feld = "Höhe" eingeben kannst, und Höhe eben in ISO-8859-1 "48f66865" ist, dann kann dort nichts übereinstimmen.
natürlich nicht so. Wenn das Suchformular ä als utf-8 abschickt, dann wird die DB schon nach ö gefragt. Das geht ja auch, aber Ä wäre eben eine ganz andere Bytefolge und da bräuchte man ja endlos Tabellen, welch utf-8 Zeichen als Gross-/klein zusammengehören.
Du hast keine Chance, nutze sie ;-)). Man könnte, wenn die Datenbank das anbietet, mit WHERE feld = "H".concat(0xc3b6).concat("he") suchen. Oder man könnte mit Hilfe des ersten Teils der Funktion von Christian, wo es um Zwei-Octet-UTF-8, beginnend mit c<0xe0 geht, die UTF-8-Octetstreams micht in Entities, sondern in 1-Byte-Hex-Werte umwandeln.
na, da kann ich auch bei ISO bleiben :-)
btw.: ist es in anderen Sprachen eigentlich auch so, daß es zwingend zusammengehörende Gross-/kleinVersionen von den Buchstaben gibt? Also, wie in Latin-1 eben ä und Ä zusammengehören und in MySQL ja auch nicht-case-Sensitiv im iso-Modus beide gefunden werden. Und kann MySQL 4.1 das erkennen?
Gruß, Andreas
你好 Andreas,
btw.: ist es in anderen Sprachen eigentlich auch so, daß es zwingend
zusammengehörende Gross-/kleinVersionen von den Buchstaben gibt?
Nein, in laengst nicht allen. Das japanische Schrift-System z. B. kennt
keine Gross- und Kleinschreibung, und die Lateiner kannten eigentlich auch
keine Unterscheidung. Die haben einfach drauf losgeschrieben, in
Grossbuchstaben, und haben ab und zu mal einen Punkt gemacht. Komma? Wasn
das? ;-)
再见,
克里斯蒂安
Hallo,
Also es steht nicht, wie Du weiter unten beschreibst Hex-Codiert, sondern utf-8-codiert byte für byte quasi als latin-1 geschrieben, also aus ä wird ä und aus Ä wird Ä.
Was meinst Du mit _nicht_ Hex-Codiert? Im Speicher, egal wo, stehen Bits, diese bilden Oktets z.B. "01001000", das ist 0x48 und das ist in US-ASCII ein "H".
Das "ä" in UTF-8 ist 0xc3a4. Das wird in ISO-8859-1 als "ä" dargestellt. Das, was Du beschreibst, ist also das, was ich beschrieb ;-)).
Wenn im Feldinhalt "Höhe" als "48c3b66865" steht, Du aber nur WHERE feld = "Höhe" eingeben kannst, und Höhe eben in ISO-8859-1 "48f66865" ist, dann kann dort nichts übereinstimmen.
natürlich nicht so. Wenn das Suchformular ä als utf-8 abschickt, dann wird die DB schon nach ö gefragt.
Aha, also so wie bei feld = "H".concat(0xc3b6).concat("he") für "Höhe", nur anders geschrieben, eben "Höhe".
Das geht ja auch, aber Ä wäre eben eine ganz andere Bytefolge und da bräuchte man ja endlos Tabellen, welch utf-8 Zeichen als Gross-/klein zusammengehören.
Ja, das bräuchte man wohl.
Du hast keine Chance, nutze sie ;-)). Man könnte, wenn die Datenbank das anbietet, mit WHERE feld = "H".concat(0xc3b6).concat("he") suchen. Oder man könnte mit Hilfe des ersten Teils der Funktion von Christian, wo es um Zwei-Octet-UTF-8, beginnend mit c<0xe0 geht, die UTF-8-Octetstreams micht in Entities, sondern in 1-Byte-Hex-Werte umwandeln.
na, da kann ich auch bei ISO bleiben :-)
Richtig, besser wäre auf eine Speicherung in einer Umgebung mit UTF-8-Unterstützung zu setzen, egal ob Datenbank oder nicht. UTF-8 im HTML-Formular lohnt sich nur, wenn die gesamte verarbeitende und speichernde Software auch damit umgehen kann.
zusammengehörende Gross-/kleinVersionen von den Buchstaben
Gibt es die für die Computerspeicherung denn in US-ASCII bzw. ISO-8859-1? Die Möglichkeit der case insensitiven Suche basiert auf der Festlegung, dass die kleinen Buchstaben jeweils "Grossbuchstabe"+0x20 codiert sind.
A = 0x41 = 01000001, a = 0x61 01100001, ...
P = 0x50 = 01010000, p = 0x70 01110000, ...
Ä = 0xC4 = 11000100, ä = 0xE4 11100100, ...
^ ^ Dieses Bit ist das "case-Gen ;-)", das muss beim Vergleich nur ausgeblendet werden.
Ob das bei UTF-8 noch genau so einfach geht? Bei dem UTF-8, welches für die Zeichen aus ISO-8859-1 zuständig ist, sollte es das eigentlich.
viele Grüße
Axel
Hallo,
zusammengehörende Gross-/kleinVersionen von den Buchstaben
A = 0x41 = 01000001, a = 0x61 01100001, ...
P = 0x50 = 01010000, p = 0x70 01110000, ...
Ä = 0xC4 = 11000100, ä = 0xE4 11100100, ...
^ ^ Dieses Bit ist das "case-Gen ;-)", das muss beim Vergleich nur ausgeblendet werden.Ob das bei UTF-8 noch genau so einfach geht? Bei dem UTF-8, welches für die Zeichen aus ISO-8859-1 zuständig ist, sollte es das eigentlich.
Nach nochmaliger Betrachtung unter Berücksichtigung der Code-Map http://old.no/charmap/iso-8859-1.html kann man wohl folgendes vermuten:
1. Normalerweise sollte die nicht casesensitive Suche auch bei UTF-8
Ä = 0xc384, ä = 0xc3a4
Ö = 0xc396, ö = 0xc3b6
Ü = 0xc39c, ü = 0xc3bc
noch funktionieren, weil sich der Unterschied immer noch nur in diesem einen bestimmten Bit zeigt. Offensichtlich ist es aber so, dass die Vergleichsroutinen dieses Bit nicht _immer_ ignorieren, was klar wird, wenn man bedenkt, dass sonst auch nicht zwischen
! = 0x21 = 00100001 und A = 0x41 = 01000001
2 = 0x32 = 00110010 und R = 0x52 = 01010010
^ ^
usw. unterschieden werden könnte. Deshalb wird die "Verwandschaft" zwischen
Ä = 0xc384 = ...10000100 und ä = 0xc3a4 = ...10100100
^ ^
nicht erkannt. In diesem Code-Bereich wird einfach das case-Bit nicht mehr ausgeblendet. Das dies aber eben _in_ den Vergleichsroutinen der Software passiert, wird man daran nicht viel ändern können.
viele Grüße
Axel
Hallöle,
Dieses Tool macht das mit Javascript, vielleicht hilft Dir das als Anregung.
Nicht wirklich - da dort nicht eine Sammlung von UTF-8 bytes als Eingabe vorkommt, sondern direkt die Unicode-Codepoints.
Trotzdem Danke.
cu,
Robert
echo $begrueszung;
Gibt es in PHP irgendeine Methode, die mir alle UTF-8-Zeichen, die nicht in ISO-8859-1 enthalten sind (oder von mir aus auch alle, die nicht in US-ASCII enthalten sind) in numerische Zeichenreferenzen (  usw.) umwandelt?
iconv oder recode sollten dir helfen
echo "$verabschiedung $name";
Hallöle,
echo $begrueszung;
warum eigentlich begrue_sz_ung?
Üblicherweise wird das ß, wenn nicht vorhanden, durch ss ersetzt, nicht durch sz.
Nicht wirklich - bei den Funktionen krieg ich die Meldung, daß ich nichtdefinierte Funktionen aufrufe.
Trotzdem danke, gut zu wissen, daß es theoretisch sowas gibt - nur auf dem betroffenen Server fehlt das.
cu,
Robert
你好 Robert,
Gibt es in PHP irgendeine Methode, die mir alle UTF-8-Zeichen, die nicht
in ISO-8859-1 enthalten sind (oder von mir aus auch alle, die nicht in
US-ASCII enthalten sind) in numerische Zeichenreferenzen (  usw.)
umwandelt?
AFAIK gibts sowas nicht eingebaut, aber es geht recht einfach:
function utf8_to_usascii_with_entities($str) {
$length = strlen($str);
$result = '';
for($i=0;$i<$length;) {
$c = ord($str{$i});
if($c < 0x80) {
$result .= $str{$i};
$i += 1;
}
elseif($c < 0xc2) return NULL;
elseif($c < 0xe0) {
if($length - $i < 2) return NULL;
if(!((ord($str{$i+1}) ^ 0x80) < 0x40)) return NULL;
$num = (($c & 0x1f) << 6) | (ord($str{$i+1}) ^ 0x80);
$result .= '&#'.$num.';';
$i += 2;
}
elseif($c < 0xf0) {
if($length - $i < 3) return NULL;
if(!((ord($str{$i+1}) ^ 0x80) < 0x40 && (ord($str{$i+2}) ^ 0x80) < 0x40 && ($c >= 0xe1 || ord($str{$i+1}) >= 0xa0))) return NULL;
$num = (($c & 0x0f) << 12) | ((ord($str{$i+1}) ^ 0x80) << 6) | (ord($str{$i+2}) ^ 0x80);
$result .= '&#'.$num.';';
$i += 3;
}
elseif($c < 0xf8) {
if($length - $i < 4) return NULL;
if(!((ord($str{$i+1}) ^ 0x80) < 0x40 && (ord($str{$i+2}) ^ 0x80) < 0x40 && (ord($str{$i+3}) ^ 0x80) < 0x40 && ($c >= 0xf1 || ord($str{$i+1} >= 0x90)))) return NULL;
$num = (($c & 0x07) << 18) | ((ord($str{$i+1}) ^ 0x80) << 12) | ((ord($str{$i+2}) ^ 0x80) << 6) | ($str{$i+3} ^ 0x80);
$result .= '&#'.$num.';';
$i += 4;
}
elseif($c < 0xfc) {
if($length - $i < 5) return NULL;
if(!((ord($str{$i+1}) ^ 0x80) < 0x40 && (ord($str{$i+2}) ^ 0x80) < 0x40 && (ord($str{$i+3}) ^ 0x80) < 0x40 && (ord($str{$i+4}) ^ 0x80) < 0x40 && ($c >= 0xf9 || ord($str{$i+1}) >= 0x88))) return NULL;
$num = (($c & 0x03) << 24) | ((ord($str{$i+1}) ^ 0x80) << 18) | ((ord($str{$i+2}) ^ 0x80) << 12) | ((ord($str{$i+3}) ^ 0x80) << 6) | (ord($str{$i+4}) ^ 0x80);
$result .= '&#'.$num.';';
$i += 5;
}
elseif($c < 0xfe) {
if($length - $i < 6) return NULL;
if(!((ord($str{$i+1}) ^ 0x80) < 0x40 && (ord($str{$i+2}) ^ 0x80) < 0x40 && (ord($str{$i+3}) ^ 0x80) < 0x40 && (ord($str{$i+4}) ^ 0x80) < 0x40 && (ord($str{$i+5}) ^ 0x80) < 0x40 && ($c >= 0xfd || $str{$i+1} >= 0x84))) return NULL;
$num = (($c & 0x01) << 30) | ((ord($str{$i+1}) ^ 0x80) << 24) | ((ord($str{$i+2}) ^ 0x80) << 18) | ((ord($str{$i+3}) ^ 0x80) << 12) | ((ord($str{$i+4}) ^ 0x80) << 6) | (ord($str{$i+5}) ^ 0x80);
$result .= '&#'.$num.';';
$i += 6;
}
else return NULL;
}
return $result;
}
Die Funktion wandelt alles, was nicht us-ascii ist, um in &#<nr>; -- die
ISO-8859-1-Unterstuetzung musst du selber bauen.
再见,
克里斯蒂安
Hallöle,
AFAIK gibts sowas nicht eingebaut, aber es geht recht einfach:
Naja, sicher nicht übermäßig kompliziert, aber als "einfach" würd ich das nicht mehr bezeichnen.
Hattest Du die Funktion schon fertig?
Oder hast Du die mal schnell geschrieben?
Zumindest für alle die Zeichen, die jetzt in meinen Testfällen vorkamen, funktioniert die Umsetzung.
Vielen Dank!
cu,
Robert
你好 Robert,
Hattest Du die Funktion schon fertig?
Oder hast Du die mal schnell geschrieben?
Nein, die habe ich jetzt gerade geschrieben.
Zumindest für alle die Zeichen, die jetzt in meinen Testfällen vorkamen,
funktioniert die Umsetzung.
Die funktioniert ziemlich sicher fuer alle Zeichen :)
再见,
克里斯蒂安
Hallo,
Naja, sicher nicht übermäßig kompliziert, aber als "einfach" würd ich das nicht mehr bezeichnen.
Noch ein wenig Hintergrundinformationen: http://forum.de.selfhtml.org/archiv/2005/3/t104162/#m642038.
viele Grüße
Axel
Hallöle nochmal,
hab noch was vergessen:
Die Funktion wandelt alles, was nicht us-ascii ist, um in &#<nr>; -- die ISO-8859-1-Unterstuetzung musst du selber bauen.
Nicht notwendig - entscheidend ist ja nur, daß alle Zeichen außerhalb von ISO-8859-1 codiert sind, ob einige der Zeichen innerhalb von ISO-8859-1 auch kodiert sind, ist ziemlich wurscht.
cu,
Robert
Heißa, Christian,
Die Funktion wandelt alles, was nicht us-ascii ist, um in &#<nr>; -- die
ISO-8859-1-Unterstuetzung musst du selber bauen.
Ungefähr so hatte ich das damals auch gemacht, jedoch ist es sehr rechenaufwändig, jedes Zeichen einzeln zu überprüfen. Bei großen Dateien dauerte die Berechnung auf unserem 133-MHz-Server teilweise zwei Minuten. Also beschloss ich, das Ganze etwas einfacher zu lösen, und löste die Hex-Werte auf, multiplizierte aus und kam irgendwann auf den folgenden Code aus regulären Ausdrücken, wobei die großen Seiten nur noch zwei Sekunden zu berechnen brauchten.
function utf8_htmlentities($string, $mkentities=false, $hex=true)
{
if($hex)
$rep = array("'&#x'.dechex(", ").';'");
else
$rep = array("'&#'.(", ").';'");
if($mkentities) /* Auch ASCII-Zeichen durch HTML-Entities maskieren */
$string = preg_replace("/[\\x0-\\x80]/e", $rep[0]."ord('$0')".$rep[1], $string);
$string = preg_replace("/([\\xc0-\\xdf])([\\x80-\\xbf])/e", $rep[0]."64*ord('$1')+ord('$2')-12416".$rep[1], $string);
$string = preg_replace("/([\\xe0-\\xef])([\\x80-\\xbf])([\\x80-\\xbf])/e", $rep[0]."4096*ord('$1')+64*ord('$2')+ord('$3')-925824".$rep[1], $string);
$string = preg_replace("/([\\xf0-\\xf7])([\\x80-\\xbf])([\\x80-\\xbf])([\\x80-\\xbf])/e", $rep[0]."262144*ord('$1')+2048*ord('$2')+64*ord('$3')+ord('$4')-63185024)".$rep[1], $string);
return $string;
}
Leider ist der Code schwer nachvollziehbar -- ich schaffe es selbst nicht...
Caramba!
Grüße aus Biberach Riss,
Candid Dauth (ehemals Dogfish)
Hallöle,
Ungefähr so hatte ich das damals auch gemacht, jedoch ist es sehr rechenaufwändig, jedes Zeichen einzeln zu überprüfen. Bei großen Dateien dauerte die Berechnung auf unserem 133-MHz-Server teilweise zwei Minuten. Also beschloss ich, das Ganze etwas einfacher zu lösen, und löste die Hex-Werte auf, multiplizierte aus und kam irgendwann auf den folgenden Code aus regulären Ausdrücken, wobei die großen Seiten nur noch zwei Sekunden zu berechnen brauchten.
Mag sein, daß das bei langen Strings einen Gewinn bringt.
Bei mir mit vielen (max. ~25000) kurzen Strings (max. theoretisch 100, praktisch nicht länger als 40 Zeichen), von denen viele nur US-ASCII-Zeichen enthalten und einige wenige Strings nur wenige nicht-US-ASCII-Zeichen enthalten, nehmen sich die beiden Methoden nichts (Durchschnitt über je 20 Messungen: 12,71s <--> 12,65s - wobei der größte Teil der Zeit durch die Ausgabe der Daten verbraucht werden dürfte).
cu,
Robert