Charactersets

Charactersets

Het correct gebruiken van charactersets is betrekkelijk eenvoudig, maar toch gaat er wel vaker iets fout.

Wat is een characterset?

Computers kennen geen letters, cijfers, leestekens en dergelijke, ze kennen alleen getallen. Om die getallen voor mensen begijpelijk te maken worden ze op het scherm vertaald naar letters en cijfers. Om die vertaling te kunnen doen moet de computer een lijst hebben waarin voor elk getal de bijbehorende letter staat. Die lijst is een characterset.

De bekendste characterset is de ASCII set van 255 tekens.

Waarom bestaan er meerdere sets?

De ASCII set is leuk maar beperkt, er passen maar 255 tekens in en hoewel dat genoeg is voor de letters van ons alfabet (de hoofdletters, de kleine letters en een handvol letters met accenten) en de cijfers is het niet echt genoeg voor talen als Chinees waar het aantal tekens in de tienduizenden kan lopen. Veel talen hebben andere letters dan wij, door accenten of anderszins.

Om de tekens van andere talen toch beschikbaar te maken heeft men eerst gewoon de ASCII tabel aangepast aan andere tekens. Zo kreeg je voor zo'n beetje elke taal een eigen tabel. Op zich logisch, maar nogal onhandig omdat je een document niet zomaar om kunt zetten van de ene naar de andere set zonder de inhoud van het document geweld aan te doen .

Multibyte characterset

Multibyte sets zijn als de ASCII set maar in plaats van één byte voor 255 tekens hebben ze twee of meer bytes om de getallen in op te slaan. Een twee-byte set kan om 256^2=65536 tekens bevatten. Dat is genoeg voor de meest exotische talen.

Latin1

Latin1, ookwel ISO-8859-1 genoemd, is een 8-bit (single-byte) characterset waarin alle standaard tekens en accenten zitten. Dit is genoeg voor de talen die geen bijzondere accenten bevatten, zoals Nederlands, Engels, Frans en Duits.

Deze set wordt volledig ondersteund door PHP, zonder enige aanpassing.

UTF

UTF is een standaard characterset waarin vrij letterlijk alle tekens van alle talen in de hele wereld passen. Door UTF als characterset te gebruiken hoef je theoretisch niet meer van characterset te wisselen, de hele wereld zou dezelfde set kunnen gebruiken en alles altijd goed zien. Maarja....

  • Noot* PHP ondersteunt UTF niet in alle functies. Lees: eigenlijk alleen in de mb_* functies. Andere functies kunnen en zullen de string kapotmaken, zie ook onderaan dit artikel.

Hoe te gebruiken

Het correct gebruiken van charactersets komt neer op slechts een ding: consistentie. Dat wil zeggen dat als er eenmaal een characterset is gekozen (de meest gebruikten zijn op dit moment Latin1 en UTF-8), dit overal gebruikt moet worden: in je HTTP response, in je database verbinding, in PHP zelf en in de HTML header.

Als de karakterset overal hetzelfde staat is het niet meer nodig om tekst door de functie htmlentities() heen te halen. Let er wel op dat het in nog steeds nodig is om htmlspecialchars() te gebruiken om te voorkomen dat er (x)HTML-code kan worden "uitgevoerd".

Charactersets in MySQL

Databasecollatie

In het geval dat je MySQL gebruikt is het dus noodzakelijk om aan te geven dat je de kolommen van het type 'text' (char, varchar, text) maakt met de characterset UTF-8. Dit kan je doen met de volgende syntax:

CREATE TABLE mijntabel (
  mijnkolom TEXT CHARACTER SET utf8
) ENGINE=InnoDB;

Tot slot kan je nog opgeven hoe karakters met elkaar vergeleken worden (volgorde van letters in het alfabet) met COLLATE.
Voorbeeld: een Duitser sorteert de letter ö bij de O, maar Zweden vinden dat de letters ä en ö samen met de å na de Z komen. (Het Zweedse alfabet heeft dan ook 29 letters)

CREATE TABLE mijntabel (
  mijnkolom VARCHAR(20) CHARACTER SET utf8 COLLATE utf8_general_ci
) ENGINE=InnoDB;

Dit laatste is minder interessant, want als je niets opgeeft dan wordt de standaard collatie gebruikt die eigenlijk altijd volstaat.
In exotische gevallen (bijv in talen met veel vreemde tekens zoals Pools of Tsjechisch) wil je hier soms gebruik van maken. De regel is eigenlijk als de taal voorkomt in onderstaande lijst, dat je dan die collatie gebruikt.

mysql> SHOW COLLATION LIKE 'utf8%';
+--------------------+---------+-----+---------+----------+---------+
| Collation          | Charset | Id  | Default | Compiled | Sortlen |
+--------------------+---------+-----+---------+----------+---------+
| utf8_general_ci    | utf8    |  33 | Yes     | Yes      |       1 |
| utf8_bin           | utf8    |  83 |         | Yes      |       1 |
| utf8_unicode_ci    | utf8    | 192 |         | Yes      |       8 |
| utf8_icelandic_ci  | utf8    | 193 |         | Yes      |       8 |
| utf8_latvian_ci    | utf8    | 194 |         | Yes      |       8 |
| utf8_romanian_ci   | utf8    | 195 |         | Yes      |       8 |
| utf8_slovenian_ci  | utf8    | 196 |         | Yes      |       8 |
| utf8_polish_ci     | utf8    | 197 |         | Yes      |       8 |
| utf8_estonian_ci   | utf8    | 198 |         | Yes      |       8 |
| utf8_spanish_ci    | utf8    | 199 |         | Yes      |       8 |
| utf8_swedish_ci    | utf8    | 200 |         | Yes      |       8 |
| utf8_turkish_ci    | utf8    | 201 |         | Yes      |       8 |
| utf8_czech_ci      | utf8    | 202 |         | Yes      |       8 |
| utf8_danish_ci     | utf8    | 203 |         | Yes      |       8 |
| utf8_lithuanian_ci | utf8    | 204 |         | Yes      |       8 |
| utf8_slovak_ci     | utf8    | 205 |         | Yes      |       8 |
| utf8_spanish2_ci   | utf8    | 206 |         | Yes      |       8 |
| utf8_roman_ci      | utf8    | 207 |         | Yes      |       8 |
| utf8_persian_ci    | utf8    | 208 |         | Yes      |       8 |
| utf8_esperanto_ci  | utf8    | 209 |         | Yes      |       8 |
| utf8_hungarian_ci  | utf8    | 210 |         | Yes      |       8 |
+--------------------+---------+-----+---------+----------+---------+
21 rows in set (0.00 sec)

Note: 'Sortlen' is related to the amount of memory required to sort strings expressed in the character set.

In de praktijk gebruik je echter onderstaande code, die aangeeft dat de standaard karakterset voor eventuele tekstkolommen in de tabel UTF-8 is, met de standaard collatie.

CREATE TABLE mijntabel (
  mijnkolom VARCHAR(20)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Karakterset van de databaseverbinding

Vervolgens worden de gegevens in je tabel opgeslagen als UTF-8, maar je moet er dan ook nog voor zorgen dat je aan de server duidelijk maakt dat jouw queries tekst aanbieden en ophalen in UTF-8.

In de MySQL configuratie (my.ini) kan je deze standaard aangeven met de optie

[mysql]
default-character-set=utf8

Als je geen mogelijkheid hebt om de .ini aan te passen, dan zal je na het verbinden met de server aan moeten geven dat de verbinding met UTF-8 werkt.
Om te bekijken welke karakterset je verbinding momenteel gebruikt voer je deze query uit:

SHOW VARIABLES LIKE 'char%';

Dit stelt dus niets in, het laat alleen zien wat er is ingesteld.

Ook de variabelen collation_connection, collation_database en collation_server kunnen van invloed zijn.

Als hier nog verwijzingen staan naar een andere karakterset (en dan met name bij character_set_client en character_set_connection) stel dan in een keer met de volgende query in dat de verbinding queries stuurt in utf8:

SET NAMES 'utf8';

Deze query moet worden uitgevoerd direct nadat je de databaseverbinding hebt geopend. Het moet dus elke keer dat je een verbinding opent gebeuren end an precies één (1) keer.

Voor MySQL gaat vanuit PHP de voorkeur uit naar de functie mysql_set_charset() http://www.php.net/mysql_set_charset

1
  mysql_set_charset('utf8',$link2);

In de handleiding van MySQL staat precies uitgelegd wat de verschillende 'character_set_*' variabelen doen.
http://dev.mysql.com/doc/refman/5.1/en/charset-connection.html

Charactersets in PgSQL

Encoding van de database

In PostgreSQL kun je in de characterset per database instellen, niet per tabel.
Zie: http://www.postgresql.org/docs/8.4/static/multibyte.html

Karakterset van de databaseverbinding

De client-encoding kun je op een vervbinding instellen via SET NAMES 'latin1' . De database verwacht van de applicatie dat er Latin1 wordt aangeleverd en de database zal de data aan de applicatie ook in Latin1 aanleveren.

UTF-8 in je HTTP response

De browser van de eindgebruiker moet ook weten welk characterset er gebruikt gaat worden. In het geval van UTF-8, zal je de volgende header mee moeten sturen:

1
2
3
<?php
header('Content-Type: text/html; charset=UTF-8');
?>

Dit is natuurlijk in het geval dat je text/html terug gaat sturen. Mocht dit XML zijn, dan moet dat uiteraard bij Content-Type worden aangegeven. Een alternatief is om deze header op te geven middels een HTTP-EQUIV in HTML:

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

Wijs is om deze allebei altijd te sturen. Op die manier weet je zeker dat e.e.a. goed komt.

UTF-8 in bestanden

Als er speciale karakters in bestanden worden gebruikt, kunnen deze verkeerd worden weergegeven als de bestanden niet juist zijn opgeslagen.

Voorbeelden hiervan zijn:

ö => ö
é => é

Dit is op te lossen door de pagina's opnieuw op te slaan als UTF-8. In de meeste editors is dit wijzigen onder het kopje "instellingen". Let er daarbij op dat de pagina's worden opgeslagen als UTF-8 zonder BOM.

De BOM (Byte Order Mark) bestaat uit 3 bytes welke aangeven wat de karakterset is van het bestand. Php kan hier echter niet goed mee omgaan. Als de php-pagina geopend wordt in de browser, is deze BOM dan ook te zien. Dit kan tot vervelende situaties leiden, waaronder de veel voorkomende Headers_Already_Sent.

De BOM ziet er zo uit:



Zie ook: http://annevankesteren.nl/2004/12-utf-8.nl.html

Bovenstaande is alleen het geval als in het bestand letterlijk speciale tekens staan. Geef je bijvoorbeeld data weer uit een database, dan is het NIET nodig om het bestand in UTF-8 op te slaan. Het opslaan van bestanden in UTF8 kan onhandig zijn als je ook met editors werkt die dit niet herkennen of ondersteunen.

1
2
<?php echo 'café'; ?> // bestand moet opgeslagen worden als utf8
<?php echo $row['plaats']; ?> // bestand hoeft niet als utf8 opgeslagen te worden

UTF-8 in emails

Ook de reader van de emails (Outlook, Thunderbird) moet weten in welke karakterset een mail is opgesteld. Daartoe kan de regel

1
Content-Type: text/plain; charset="utf-8"

opgenomen worden in de "headers" van het bericht.

Gebruik je PHPMailer dan is de karakterset in te stellen met

1
<?php $oMailer-Charset = 'utf-8';

UTF-8 en .htaccess

De karakterset kan ook worden ingesteld met behulp van een .htaccess-bestand.
Dit kan op de volgende 2 manieren:

AddDefaultCharset UTF-8

Dit geldt alleen voor text/plain of text/html.

Zie: http://httpd.apache.org/docs/2.0/mod/core.html#adddefaultcharset

Of met:

AddType text/html;charset=UTF-8 .html

Hiermee kunnen specifieke bestanden worden opgeven, in dit voorbeeld bestanden met een .html-extensie.

Gebreken PHP t.a.v. multibyte charsets

  • PHP heeft serieuze problemen met charactersets die meer dan een byte kunnen gebruiken voor een karakter. Zo zijn er functies binnen PHP die een UTF-8 string kunnen corrumperen, maar ook het opvragen van de lengte van een UTF-8 string met strlen( ) kan verrassende resultaten hebben. Gelukkig zijn er voor PHP < 6 ook alternatieve functies die dit wel kunnen.
  • json_encode en json_decode accepteren en produceren alleen UTF-8 encoded strings. Er is geen manier om dat te veranderen, als je een andere encoding aanlevert of terug wilt krijgen moet je zelf voor de transcoding zorgen.
  • AJAX werkt vaak met UTF-8 en ook dat kun je niet altijd aanpassen. Kijk naar de document-declaraties om te zien welke encoding de browser gebruikt en verlangt.

Punt van aandacht

Nogaleens valt de opmerking "maar in de database staat het 'goed'" (of juist 'fout'), waarbij bedoeld wordt dat wanneer de database bekeken wordt met een tool als Phpmyadmin de letters goed/fout getoond worden.
Bedenk dan dat phpmyadmin ook maar een phpscript is, en dat het lang niet zeker is dat phpmyadmin de database op dezelfde (juiste?) manier benadert als jouw scripts.
Dit geldt ook voor bijvoorbeeld de commandline tool die Mysql of andere database vaak bijlevert.

Inloggen wachtwoord vergeten? Aanmelden