Kodowanie znaków na stronach www

Artykuł dodany: 22 października 2007. Ostatnia modyfikacja: 31 października 2011.

Stopień trudności (1 - dla początkujących, 5 - dla ekspertów): 1

Zanim przejdziesz do dalszej lektury zapoznaj się z podstawami protokołu HTTP przedstawionymi w poprzednim artykule.

Teoria

W informatyce stosujemy bajty (oznaczamy je wielką literą B) które odzwierciedlają jednostki adresowania pamięci. Każdy bajt równy jest 8 bitom (mała litera b). Zbiór znaków jest to zestaw liter, cyfr oraz wszystkich możliwych do przedstawienia symboli które przechowywane są w komputerze jako jeden lub więcej bajtów. Ponieważ komputer nie rozumie czym jest alfabet stworzono standard, który wiąże zrozumiałe dla komputera adresowanie ze zrozumiałymi nam symbolami. Standard ten nazywa się ASCII i zawiera m.in. podstawowe symbole używane w języku angielskim. Poniższa tabela prezentuje zbiór podstawowy znaków ASCII:

Tabela kodów ASCII

W reprezentacji bitowej zostało wykorzystanych pierwszych 7 bitów (czyli 27). 8 bit początkowo służył do kontroli parzystości ale, na skutek zwiększonego zapotrzebowania na zapis różnych znaków, on także został wykorzystany tworząc rozszerzoną tablicę kodów ASCII.

Ponieważ w różnych państwach wykorzystywane są inne symbole należało stworzyć standard obejmujący większość znaków narodowych. Zajęła się tym organizacja ECMA tworząc standard ISO/IEC 8859 zaaprobowany przez ISO. W standardzie tym pozostawiono bez zmian wszystkie znaki z podstawowej tabeli kodów ASCII (0 – 127), pozycjom 128-159 przypisano dodatkowe kody sterujące, natomiast pozostały zakres 160-255 posłużył przypisaniu znaków z różnych alfabetów. Obowiązującym standardem dla Europy środkowej i wschodniej, co obejmuje także Polskę, został ISO-8859-2 lub też Latin-2.

W systemie operacyjnym Microsoft Windows został wykorzystany inny sposób kodowania znaków. Dla Polski jest to Windows-1250 (CP-1250). Jest on dość zbliżony do ISO ale nigdy (na całe szczęście) nie został uznany za standard. Dlatego zwłaszcza na stronach internetowych odradzane jest jego używanie gdyż może to doprowadzić do nieprawidłowego wyświetlania znaków.

Mimo że standard ISO/IEC 8859 obejmuje dość obszerny zestaw znaków jest on niewystarczający. Aby objąć wszystkie pisma używane na świecie stworzono Unicode (Unikod) w którym znaki zapisywane są na wielu bajtach. Istnieją różne wariacje kodowań Unicode – UTF-8, UTF-16 oraz UTF-32. Każdy z tych zestawów charakteryzuje się zapisem danych z wykorzystaniem różnej liczby bajtów. Przykładowo w UTF-32 każdy znak zapisywany jest na dokładnie 4 bajtach, w najczęściej wykorzystywanym UTF-8 znaki ASCII zajmują dokładnie 1 bajt, pozostałe od 2 do 4. Zainteresowanych zgłębieniem tematu odsyłam na stronę http://www.unicode.org/.

Ze swojej strony mogę wszystkich zachęcić do korzystania z Unikodu we wszystkich projektach i systemach. Mimo, że najprawdopodobniej uzyskamy lekki narzut na przechowywanie znaków ze względu na zmienną i większą niż w ISO/IEC 8859 długość, korzyści powinny przeważyć. Podczas stosowania unikodu nie musimy przejmować się zmianą kodowania w edytorze dla różnych projektów, niektóre edytory w ogóle nie przyjmują innego kodowania niż UTF, podczas obrabiania danych z bazy nie musimy martwić się o przekodowywanie znaków dla różnych połączeń. W każdej chwili może zajść potrzeba wstawienia znaków ze słownika innego, niż ten z którego aktualnie korzystamy. Stosowanie unikodu ujednolica projekty.

Kodowanie znaków w PHP / HTML

Przede wszystkim przyda Ci się czytelniku wiedza o działaniu protokołu HTTP. To klient z serwerem ustalają w nagłówkach jakie kodowanie będzie użyte i na podstawie tej negocjacji dostaniesz w kliencie określony zestaw znaków. Ustalanie kodowania poprzez tag meta:

<meta http-equiv="content-type" content="text/html; charset=utf-8" />

NIE MA NAJMNIEJSZEGO ZNACZENIA! Jest to potwornie ważna informacja, jeden z powodów tak wielu problemów z “krzakami” na stronie. <meta http-equiv="content-type"sugeruje jedynie sposób konfiguracji serwera, ustalenie odpowiadających nagłówków. Żaden serwer nigdy tej funkcji nie zaimplementował. Wykorzystanie powyższego zapisu może się przydać wyłącznie dla stron lokalnych, w czystym HTMLu, będących np. zapisem na dysk jakiejś strony internetowej. Jak więc ustalić poprawnie kodowane? Istnieje kilka sposobów. Jeżeli masz dostęp do PHP skorzystaj z funkcji header() odpowiedzialnej za wysyłanie nagłówków.

<?php header('Content-type: text/html; charset=UTF-8'); ?>

Musi być ona użyta przed wysłaniem jakiejkolwiek treści do klienta inaczej dostanesz błąd. Aby ominąć częściowo tą niedogodność należy wykorzystać funkcje buforowania, których dokładny opis znajdziesz w dokumentacji php. Zwróć szczególną uwagę na funkcję ob_start().

Dokumenty XML

Kodowanie w dokumentach XML wskazujemy w prologu używając konstrukcji:

<?xml version="1.0" encoding="ISO-8859-2" standalone="yes"?>

Domyślnie używane jest kodowanie UTF-8. Formalnie, ponieważ XHTML jest także dokumentem XML, powinien on uwzględniać prolog. Jednak przeglądarka Internet Explorer gdy wykrywa prolog przechodzi w tryb wstecznej zgodności (“quirks mode”) co ma swoje nieciekawe implikacje. Przy odrobinie determinacji można oczywiście wykrywać przeglądarkę i ustawiać odpowiednią zawartość dokumentu. Aczkolwiek nie jest to konieczne.

Kodowanie poprzez serwer Apache

Istnieje także możliwość wpływania bezpośredniego na konfigurację serwera. Jeżeli korzystasz z serwera Apache i masz dostęp do plików konfiguracyjnych pomocne mogą okazać się dwa linki:

http://httpd.apache.org/docs/2.0/mod/mod_mime.html
http://httpd.apache.org/docs/2.0/mod/core.html#addDefaultCharset

Możesz także wykorzystać plik .htaccess (kropka na początku oznacza, iż jest to plik ukryty) przegrany do folderu z danymi www:

AddDefaultCharset UTF-8

Lub nawet bardziej skomplikowany zapis działający tylko dla plików o rozszerzeniach .htm, .html, .css oraz .js:

<FilesMatch ".(htm|html|css|js)$">
AddDefaultCharset UTF-8
</FilesMatch>

Kodowanie w Lighttpd

W serwerze Lighttpd w pliku konfiguracyjnym należy podać zapis:

mimetype.assign = (
".css"     =>      "text/css; charset=utf-8",
".htm"     =>      "text/html; charset=utf-8",
".html"    =>      "text/html; charset=utf-8",
".txt"     =>      "text/plain; charset=utf-8",
".svg"     =>      "image/svg+xml; charset=utf-8",
".js"      =>      "application/x-javascript; charset=utf-8",
".xml"     =>      "application/xml; charset=utf-8",
# Domyślnie.
""         =>      "text/plain; charset=utf-8"
)

Dobór odpowiedniego edytora

Na nic zdadzą się nasze wysiłki jeżeli edytor w którym piszemy kod strony nie będzie potrafił poprawnie zakodować zawartości. Już na wstępie zapomnij o windowsowym notatniku gdyż ten co najwyżej nadaje się do podglądu bardzo małych, nieskomplikowanych plików. Wspominałem wcześniej o wykorzystywaniu unikodu ponieważ wiele edytorów nawet nie umożliwia ustawienia innych systemów kodowań. Z taką sytuacją mamy przykładowo do czynienia w środowisku programistycznym Eclipse w którym nie doszukamy się kodowania ISO-8859. Nie będę tutaj narzucał żadnego konkretnego edytora gdyż jest to kwesta przyzwyczajeń ale dobrze mieć pod ręką edytor umożliwiający wczytanie/zapis pliku w dowolnym wybranym kodowaniu. Może się to przydać gdy pracujemy ze starszymi projektami które nie korzystały z unikodu lub gdy musimy jakiś tekst przekodować na szybko. Ciekawą alternatywą mogą być edytory stricte XMLowe które zazwyczaj oferują zarówno wiele unikatowych funkcji (jak wbudowana walidacja, czy podpowiadanie składni na podstawie DTD / Schemy), jak i dobrze radzą sobie z różnymi stronami kodowymi.

Baza danych MySQL

Jest naprawdę interesujące jak wiele problemów z kodowaniem napotykają użytkownicy tego serwera. A jest to o tyle dziwne, że MySQL ma doskonale rozwiązaną obsługę różnych stron kodowych. Kodowanie możemy ustawić na poziomie bazy, tabeli a nawet pojedynczej kolumny. Oczywiście na nic wszelkie przykłady bez odpowiedniego przygotowania teoretycznego. Chociaż wiem, że wcześniej przejrzałeś już każdą stronę dokumentacji na wylot, formalności musi być zadość ;)

Nową bazę danych zakładamy poleceniem:

CREATE DATABASE db_name 
CHARACTER SET utf8 
COLLATE utf8_polish_ci;

Ustawiliśmy kodowanie na UTF8, metodę porównywania napisów na utf8_polish_ci dzięki czemu przykładowo podczas sortowania litera ‘ą’ znajdzie się zaraz za ‘a’, a nie gdzieś na końcu. Jeżeli baza ma zdefiniowane kodowanie a nie definiowaliśmy takowego w tabeli, wówczas zostanie automatycznie przejęte.

Tworzenie nowej tabeli jest proste:

CREATE TABLE `t1` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(50) collate utf8_polish_ci default NULL,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci;

Tutaj podobnie, z tym że zdefiniowaliśmy także metodę porównywania napisów dla kolumny ‘name’. Jest to unikalne podejście, nieokreślone w standardzie SQL tak więc nieprzenośne.

Teraz najważniejsze. Podczas wykonywania połączenia musisz wykonać polecenie:

SET NAMES 'charset_name'

Informuje ono serwer, jaki zestaw znaków klient będzie wykorzystywał i w jakim kodowaniu serwer ma zwrócić odpowiedź. Brak tego zapytania jest najczęstszą przyczyną pojawiania się krzaków w danych pobranych z bazy. Odpowiednikiem tego zapytania jest:

SET character_set_client = x;
SET character_set_results = x;
SET character_set_connection = x;

Istnieje również możliwość automatycznego wykonywania tego zapytania podczas połączenia. Odpowiednią dyrektywę ustawiamy w pliku my.ini / my.cnf w sekcji [mysqld]:

init_connect='SET NAMES utf8'

Skoro jesteśmy już przy plikach konfiguracyjnych dodajmy również:

default-character-set=utf8
default-collation=utf8_polish_ci

Cały czas obowiązuje to o czym pisałem wcześniej. Czyli jeżeli ustawiasz kodowanie w bazie, musisz także wysłać odpowiednie nagłówki.

Zupełnie inną kwestią jest eksport / import danych. Na początek taka uwaga, że opcje połączeń nie dotyczą użytkownika ‘root’. Jeżeli eksportujesz dane nie importuj ich od razu na ślepo ale otwórz plik .sql w edytorze obsługującym wiele kodowań i sprawdź, czy plik zakodowany jest rzeczywiście zgodnie z tym, czego się spodziewasz. Jeżeli zobaczysz jakiekolwiek krzaki wniosek jest prosty – zostało użyte inne kodowanie, najpewniej utf-8 (dla MySQL od wersji 4.1) lub latin1 (dla MySQL 4.0 i starszych). Zamknij plik i otwórz go ponownie używając przewidywanego kodowania. Jeżeli krzaki nie występują możesz bez obaw i dla ułatwienia sobie późniejszego życia, przekodować plik na utf-8. W tabelach będziesz musiał zmienić kodowanie i metodę porównywana napisów zgodnie z tym, co pisałem wcześniej, a plik zapisać już z domyślnym nowym kodowaniem. I nie bój się. To nie jest czarna magia. Obowiązują dokładnie takie same reguły jak w przypadku plików tesktowych, html, js lub innych. To po prostu jest zwykły plik tekstowy. Jeżeli używasz polecenia ‘mysqldump’ powinna zainteresować Cię opcja ‘-default-character-set’ i raczej nie zdawaj się na domyślne ustawienia.

Baza danych PostgreSQL

Mimo że jest to serwer o ogromnych możliwościach rozczarowuje trochę opcjami kodowania. Polega mianowicie na ustawieniach lokalnych użytkownika na koncie którego pracuje, a dodatkowe opcje kodowania można ustawiać wyłącznie podczas inicjacji klastra. Czyli formalnie jeżeli już na samym początku nie zdecydujemy się na określone kodowanie, później pojawi się problem np. z sortowaniem. Co gorsza ustawienie innego niż C locale powoduje spowolnienie działania całego serwera.

Co należy ustawić? W pliku ~/.bash_profile (dla użytkownika domyślnego ‘postgres’):

export LC_COLLATE=pl_PL.UTF-8
export LC_CTYPE=pl_PL.UTF-8
export LC_MESSAGES=pl_PL.UTF-8
export LC_MONETARY=pl_PL.UTF-8
export LC_NUMERIC=pl_PL.UTF-8
export LC_TIME=pl_PL.UTF-8

Następnie inicjujemy po raz pierwszy:

initdb --locale=pl_PL.UTF-8...

Dla połączenia to samo co w Mysql:

SET NAMES 'utf8'

Od wersji 8.4 istnieje możliwość zmiany metody porównywania napisów dla poszczególnych baz danych. Przeczytaj dokumentację aby poznać ograniczenia.
Wraz z rozwojem systemu bazodanowego ewoluowała również metoda porównywania napisów. Z pojawieniem się wersji Postgresql oznaczonej numerem 9.1 wprowadzono porównywanie na poziomie pojedynczych kolumn.

CREATE TABLE test1 (
    a text COLLATE "de_DE",
    b text COLLATE "pl_PL",
    ...
);

Więcej możesz przeczytać w dokumentacji Postgresql.

Pliki CSS

Sprawa jest tutaj wyjątkowo prosta. Na początku pliku (przed jakimkolwiek innym znakiem) wstaw:

@charset "utf-8";

Jeżeli dokument zawiera BOM zapoznaj się z dodatkowymi informacjami w dokumentacji CSS.

Szybkie podsumowanie

Jeszcze raz, jakie są najważniejsze kroki aby poprawnie obsługiwać kodowanie?

  1. Serwer musi wysyłać odpowiednie nagłówki
  2. Przy połączeniu z bazą danych należy wykonać polecenie SET NAMES
  3. Pliki muszą być zapisane z docelowym kodowaniem w edytorze

Komentarze

Nie ma jeszcze żadnych komentarzy do wyświetlenia. Może chcesz zostać pierwszą osobą która podzieli się swoją opinią?

Dodaj komentarz

*
Nazwa zostanie wyświetlona wraz z komentarzem. Możesz też utworzyć nowe konto w serwisie, dzięki czemu uzyskasz dodatkową funkcjonalność.
*
Akceptowana jest ograniczona składnia Textile. Wszystkie tagi HTML zostaną usunięte.