[php] Twee UTF8 karakters worden als hetzelfde beschouwd?

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • mosymuis
  • Registratie: Maart 2002
  • Laatst online: 27-04 11:53
Ik heb een nieuwssysteem dat alle invoer als UTF8 behandelt. Dat gaat met allerlei raars in de berichten al ruim een jaar helemaal goed, op één uitzonderingssituatie na.

Na invoer van een stuk tekst gaat deze string door een eigen correctiefunctie die allerlei vreemde komma tekens normaliseert, bedoeld voor bijvoorbeeld geplakte teksten uit Word. Als er in deze tekst een Ó karakter (=capital o met accent) voorkomt, wordt deze letter omgezet naar �" (=ongewenst teken), in plaats van dat het met rust wordt gelaten. Dit komt door mijn conversie van chr(147) naar " (=aanhalingsteken), wat ik baseer op het bijbehorende ASCII cijfer (bron, kijk bij 147). Echter, deze lijkt ook effect te hebben op mijn Ó-teken. Nu kom ik sites tegen waarbij de extended ASCII set verschilt van degene die ik gewend ben, zoals hier. Daar staat 147 voor ô. Heeft dat er iets mee te maken? Vreemd genoeg komt dit hele probleem niet voor met É, Í, Ú of Á.

Voorbeeldcode waarbij het mis gaat: [pastebin] (GoT/React verandert de vreemde tekens; dus plaats ik het daar).

De uitkomst van dit stukje code is in de browser:
�" �"
beide zeer verschillende tekens worden dus als hetzelfde omgevormd.

Overigens is dit de volledige functie waardoor het mis gaat (regel #6 is schuldig): [pastebin]. Hebben mensen hiervoor vergelijkbare code geschreven en kan dit wellicht slimmer/beter?

[ Voor 43% gewijzigd door mosymuis op 27-07-2010 16:03 . Reden: code verplaatst naar pastebin; bericht verbeterd ]


Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
mosymuis schreef op dinsdag 27 juli 2010 @ 15:57:
Ik heb een nieuwssysteem dat alle invoer als UTF8 behandelt. Dat gaat met allerlei raars in de berichten al ruim een jaar helemaal goed, op één uitzonderingssituatie na.
..
Echter, deze lijkt ook effect te hebben op mijn Ó-teken. Nu kom ik sites tegen waarbij de extended ASCII set verschilt van degene die ik gewend ben, zoals hier. Daar staat 147 voor ô. Heeft dat er iets mee te maken? Vreemd genoeg komt dit hele probleem niet voor met É, Í, Ú of Á.
Waarom ben je bezig met een extended ASCII codepage terwijl je UTF-8 gebruikt? Je moet gewoon zorgen dat de browser UTF-8 doorstuurt, en het dan ook als UTF-8 behandelen, dan heb je niks met extended ASCII van doen.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:42

.oisyn

Moderator Devschuur®

Demotivational Speaker

Hier klopt natuurlijk niets van :). UTF-8 is compatible met ascii, maar niet met extended ascii. Er is ook niet 1 extended ascii variant, dus vertrouwen op de karakters die in een bepaalde variant staan kun je ook niet doen. En als jij zegt dat je invoer chr(147) bevat, dan is dat géén “ maar onderdeel van een multi-byte sequence. De “ heeft een unicode codepoint van 8220, wat geencodeerd is in UTF-8 met de 3 bytes 0xE2 0x80 0x9C (zie http://www.ltg.ed.ac.uk/~...i?input=8220&mode=decimal). De Ó daarentegen heeft een codepoint van 211, wat in UTF-8 overeen komt met 0xC3 0x93. En zie daar, 0x93 == 147.

[ Voor 18% gewijzigd door .oisyn op 27-07-2010 16:12 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • mosymuis
  • Registratie: Maart 2002
  • Laatst online: 27-04 11:53
@Woy: Waarom ik met die tekens bezig ben blijkt uit de volledige functie die allerlei vreemde afwijkende trema karakters, aanhalingstekens en dergelijke omvormt naar genormaliseerde tekens. Die functie is gaandeweg telkens gegroeid, elke keer als er op de één of andere manier zo'n vreemd teken voorbij kwam. Dit gebeurt vermoedelijk door bijvoorbeeld het plakken van tekst uit Word oid. In het verleden is mij dus gebleken dat het vervangen van chr(147) naar " een slim idee was; maar nu gaat dit plotseling mis voor Ó. Terwijl hij wel óók het teken waar hij voor bedoeld was, vervangt.

@oisyn: wooow.. :) zoveel weet ik er (zoals je ziet) nog niet vanaf. Wat betekent dit practisch; is het uberhaupt onzin om hier met chr(147) te werken? En is er geen completere, betere oplossing voor wat ik met deze serie replaces probeer te doen? En hoe kan het dan dat hij beide karakters daar op dezelfde manier vervangt?

Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
mosymuis schreef op dinsdag 27 juli 2010 @ 16:11:
@Woy: Waarom ik met die tekens bezig ben blijkt uit de volledige functie die allerlei vreemde afwijkende trema karakters, aanhalingstekens en dergelijke omvormt naar genormaliseerde tekens. Die functie is gaandeweg telkens gegroeid, elke keer als er op de één of andere manier zo'n vreemd teken voorbij kwam. Dit gebeurt vermoedelijk door bijvoorbeeld het plakken van tekst uit Word oid. In het verleden is mij dus gebleken dat het vervangen van chr(147) naar " een slim idee was; maar nu gaat dit plotseling mis voor Ó. Terwijl hij wel óók het teken waar hij voor bedoeld was, vervangt.
Ik snap de je characters wilt normaliseren maar dat moet je dus niet doen door chr(147) te vervangen naar ", maar door “ (LEFT DOUBLE QUOTATION MARK) naar " (QUOTATION MARK) te vervangen. Je kunt beide natuurlijk gewoon als string literal opnemen, dan hoef je alleen maar te zorgen dat je bestand als UTF-8 opgeslagen is.

Je neemt dus gewoon een lijst van UTF-8 characters die naar een ander een ander character geconverteerd moet worden. Je hoeft hierbij niet van encoding te veranderen, dus hoef je er ook niks speciaals voor te doen.

[ Voor 15% gewijzigd door Woy op 27-07-2010 16:24 ]

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:42

.oisyn

Moderator Devschuur®

Demotivational Speaker

Voordat ik dit verhaaltje heb gepost is er vast al iemand die een link naar dat Joel Spolsky artikel heeft geplaatst, maar vooruit:

Unicode is een internationale standaard die alle tekens en symbolen registreert onder een bepaald nummer. Zo'n nummer heet een "codepoint", en is dus de universele manier om een bepaald karakter aan te duiden. Er zijn op die manier al miljoen ofzo tekens gestandaardiseerd. Zo'n aanhalingsteken die je aanhaalde, de “, heeft een codepoint van 8220.

Ascii is een oude Amerikaanse standaard dat in feite slechts 128 karakters definieert. Omdat een byte 256 mogelijkheden heeft hebben computer-fabrikanten min of meer zelf verzonnen wat er in de reeks 128-255 moet staan, dit zijn "extended ascii" varianten, maar die zijn geenszins een echte standaard.

UTF-8 is vervolgens een zogenaamde multibyte encoding die alle unicode tekens op een bepaalde manier omzet in bytes. De eerste 128 unicode codepoints komen overeen met de standaard 128 ascii tekens, en in UTF-8 worden deze ook geencodeerd door gewoon die waarde. Voor andere tekens kan een enkel teken uit mogelijk 2 tot 4 (en zelfs tot 6 in de toekomst) bytes bestaan. De eerder aangehaalde 8220 wordt dus geencodeerd met de 3 bytes E2 80 9C (in hexadecimale notatie, in decimaal is het 226 128 156)

Jij hebt nu een willekeurige extended ascii tabel gepakt en daar jouw teken in opgezocht, maar hier heb je niets aan aangezien je zei dat je input in UTF-8 is geencodeerd, en dus niet in wat voor extended ascii set dan ook. Verder werk je met PHP, die een string ziet als bytes, en niet als karakters. De “ is daarom in PHP niet 1 teken, maar 3 tekens, zoals ik in de vorige alinea al liet zien. En ja, als je zoekt naar een teken als 147 dan vind je in UTF-8 dus ook de Ó, aangezien de tweede byte van een Ó in UTF-8 een 147 is. Vervolgens ga je die aanpassen, met als gevolg dat het hele karakter Ó verdwijnt en er iets anders voor in de plaats komt, of zelfs een ongeldig karakter krijgt (niet alle mogelijke reeksen van bytes in UTF-8 hebben een valide betekenis namelijk).

Als we je code er even bijpakken:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
$aKommaTremaReplaces = array(
                "''" => '"',
               
                chr(145) => "'", // `
                chr(146) => "'", // ´
                chr(147) => '"', // `` ; gaat mis met Ó
                chr(148) => '"', // ´´
               
                html_entity_decode("&lsaquo;", ENT_COMPAT, "UTF-8") => "<",
                html_entity_decode("&#8249;", ENT_COMPAT, "UTF-8") => "<",
                html_entity_decode("&rsaquo;", ENT_COMPAT, "UTF-8") => ">",
                html_entity_decode("&#8250;", ENT_COMPAT, "UTF-8") => ">",
               
                html_entity_decode("&laquo;", ENT_COMPAT, "UTF-8") => "<<",
                html_entity_decode("«", ENT_COMPAT, "UTF-8") => "<<",
                html_entity_decode("&raquo;", ENT_COMPAT, "UTF-8") => ">",
                html_entity_decode("»", ENT_COMPAT, "UTF-8") => ">",
               
                html_entity_decode("&lsquo;", ENT_COMPAT, "UTF-8") => "'",
                html_entity_decode("‚", ENT_COMPAT, "UTF-8") => "'",
                html_entity_decode("&rsquo;", ENT_COMPAT, "UTF-8") => "'",
                html_entity_decode("&#8216;", ENT_COMPAT, "UTF-8") => "`",
                html_entity_decode("&#8217;", ENT_COMPAT, "UTF-8") => "&#8217;",
               
                html_entity_decode("‘", ENT_COMPAT, "UTF-8") => "'",
                html_entity_decode("’", ENT_COMPAT, "UTF-8") => "'",
                html_entity_decode("“", ENT_COMPAT, "UTF-8") => '"',
                html_entity_decode("”", ENT_COMPAT, "UTF-8") => '"',
               
                html_entity_decode("&#8220;", ENT_COMPAT, "UTF-8") => '"', // &#8220;
                html_entity_decode("&#8221;", ENT_COMPAT, "UTF-8") => '"', // &#8221;
                html_entity_decode("&ldquo;", ENT_COMPAT, "UTF-8") => '"',
                html_entity_decode("&rdquo;", ENT_COMPAT, "UTF-8") => '"',
               
                html_entity_decode("&ndash;", ENT_COMPAT, "UTF-8") => '-',
                html_entity_decode("–", ENT_COMPAT, "UTF-8") => '-'
        );

Dan gebeurt hier ook nog het een en ander aan onzinnigheid. html_entity_decode() zet een html entity om naar het karakter, en daarbij gebruik je de UTF-8 encoding. De &#8220; in HTML is een manier om het aanhalingsteken op te schrijven, hierbij wordt direct gerefereerd aan het unicode codepoint van het teken. Vervolgens wordt dat geencodeerd als UTF-8, dus eigenljik is een regel als html_entity_decode("&#8221;", ENT_COMPAT, "UTF-8") gelijk aan "\xE2\x80\x9C". Welnu, een &ldquo; doet precies hetzelfde. Het is namelijk hetzelfde entity, maar anders opgeschreven. Regels als chr(127) zijn dus al helemaal onnodig omdat je niet in extended ascii werkt. Je lijstje kan dus heel wat korter:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$aKommaTremaReplaces = array(
                "''" => '"',
               
                html_entity_decode("&lsaquo;", ENT_COMPAT, "UTF-8") => "<",
                html_entity_decode("&rsaquo;", ENT_COMPAT, "UTF-8") => ">",
               
                html_entity_decode("&laquo;", ENT_COMPAT, "UTF-8") => "<<",
                html_entity_decode("&raquo;", ENT_COMPAT, "UTF-8") => ">",
               
                html_entity_decode("&lsquo;", ENT_COMPAT, "UTF-8") => "'",
                html_entity_decode("&rsquo;", ENT_COMPAT, "UTF-8") => "'",
               
                html_entity_decode("&ldquo;", ENT_COMPAT, "UTF-8") => '"',
                html_entity_decode("&rdquo;", ENT_COMPAT, "UTF-8") => '"',
               
                html_entity_decode("&ndash;", ENT_COMPAT, "UTF-8") => '-',
        );

Verder zijn de codepoints 128 t/m 159 control characters, geen printable characters, dus die (dingen als &#147;) gaan zitten vervangen heeft ook geen nut

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
.oisyn schreef op dinsdag 27 juli 2010 @ 16:35:
Verder werk je met PHP, die een string ziet als bytes, en niet als karakters.
Je kunt in PHP dus niet met string literals met daarin multi-byte UTF-8 chars werken :?

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • Janoz
  • Registratie: Oktober 2000
  • Laatst online: 02:21

Janoz

Moderator Devschuur®

!litemod

Nee. Dat lukt alleen als je de mb functies gebruikt. Systeempje he ;)

Ken Thompson's famous line from V6 UNIX is equaly applicable to this post:
'You are not expected to understand this'


Acties:
  • 0 Henk 'm!

  • hostname
  • Registratie: April 2009
  • Laatst online: 17:23
Woy schreef op dinsdag 27 juli 2010 @ 16:58:
[...]

Je kunt in PHP dus niet met string literals met daarin multi-byte UTF-8 chars werken :?
Jawel, maar dan moet je ipv de gewone stringfuncties de multibyte stringfuncties gebruiken. ;)

Acties:
  • 0 Henk 'm!

  • mosymuis
  • Registratie: Maart 2002
  • Laatst online: 27-04 11:53
Ik ben er nu achter dat dit, utf8_encode() er omheen, het probleem verhelpt:
code:
1
utf8_encode(chr(147)) => '"'

en dat is dan weer hetzelfde als
code:
1
html_entity_decode("“", ENT_COMPAT, "UTF-8") => '"'

wat ook voorkwam in de functie, en wat ik dus maar heb weggehaald.

* mosymuis leest nu oisyn's reactie ..

Dankzij je uitleg ben ik een stuk wijzer! _/-\o_ Mijn functie is nu als volgt: [pastebin]. Hierin heb ik alle dubbele karakters weggehaald, verwijs ik er direct naar met de UTF8 HEX (of hoe je het ook noemt), en heb ik in de commentaren ter verduidelijking de pointer, technische naam en HTML entities bijgezet. Nu gebeurt er weinig doms meer hoop ik? :o

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:42

.oisyn

Moderator Devschuur®

Demotivational Speaker

Woy schreef op dinsdag 27 juli 2010 @ 16:58:
[...]

Je kunt in PHP dus niet met string literals met daarin multi-byte UTF-8 chars werken :?
Tuurlijk wel. Als jij een in een PHP bestand in UTF-8 een "ë" zet dan ziet de parser gewoon een ", gevolg door 2 bytes - C3 AB - en dan weer een ". Als je je PHP sourcefile in latin-1 op die manier encode dan krijg je een latin-1 string met een ë. Simpel toch? :P

[ Voor 3% gewijzigd door .oisyn op 27-07-2010 17:52 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:42

.oisyn

Moderator Devschuur®

Demotivational Speaker

mosymuis schreef op dinsdag 27 juli 2010 @ 17:24:
Nu gebeurt er weinig doms meer hoop ik? :o
Wat denk je dat utf8_encode(chr(147)) doet dan?

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • mosymuis
  • Registratie: Maart 2002
  • Laatst online: 27-04 11:53
.oisyn schreef op dinsdag 27 juli 2010 @ 17:39:
[...]

Wat denk je dat utf8_encode(chr(147)) doet dan?
Oja.. die vier. Het lijkt erop dat dat die karakters stiekem dezelfde zijn als degenen die ik eronder al aansprak, dus heb ik dat groepje nu ook maar weggehaald. }:O

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:42

.oisyn

Moderator Devschuur®

Demotivational Speaker

Exactly :P

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • RedRose
  • Registratie: Juni 2001
  • Niet online

RedRose

Icebear

.oisyn's uitleg kan zo de FAQ in. :)

Sundown Circus


Acties:
  • 0 Henk 'm!

  • mosymuis
  • Registratie: Maart 2002
  • Laatst online: 27-04 11:53
RedRose schreef op woensdag 28 juli 2010 @ 10:35:
.oisyn's uitleg kan zo de FAQ in. :)
Ik vind inderdaad ook dat er een hoop onduidelijkheid is op het web over dit onderwerp, en dat hij het scherp uitlegt d:)b

Acties:
  • 0 Henk 'm!

  • Salandur
  • Registratie: Mei 2003
  • Laatst online: 14:41

Salandur

Software Engineer

.oisyn schreef op dinsdag 27 juli 2010 @ 16:35:
Voordat ik dit verhaaltje heb gepost is er vast al iemand die een link naar dat Joel Spolsky artikel heeft geplaatst, maar vooruit:
Helaas, die is nog niet gepost.
RedRose schreef op woensdag 28 juli 2010 @ 10:35:
.oisyn's uitleg kan zo de FAQ in. :)
agreed, met de volgende link er natuurlijk bij: The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)

Assumptions are the mother of all fuck ups | iRacing Profiel

Pagina: 1