Blog webdeveloperski Patryk yarpo Jar

Zmiana kodowania znaków w Ajax

Autor wiadomości Maj 20, 2011

Ajax odczytuje dane z serwera. Dane te są przesyłane jako ciąg znaków (czy też XML, który ostatecznie też jest ciągiem znaków). Znaki są w komputerze reprezentowane przez liczby. Podstawowy zestaw znaków (alfabet angielski, cyfry, znaki specjalne) są zakodowane wg (chyba) ogólnie uznawanego standardu ASCII. Jest to 7-mio bitowy kod, przyporządkowujący liczby z zakresu <0, 127> literom (alfabetu angielskiego), cyfrom, znakom przestankowym i innym symbolom oraz poleceniom sterującym. Przykładowo znak cyfry "1" ma wartość 49, "2" ma wartość 50, itd. Litery (wielkie) zaczynają się od "A" - liczba 65, konsekwentnie "B" = 66, ..., "Z" = 90, "a" = 97. Stąd właśnie różnica dla komputera między "a" oraz "A". Problem z kodowaniem pojawia się dla nietypowych znaków, np. "ą", "Ś", "ź", itp. znaków diakrytycznych.

Poniższy artykuł będzie o tym, jak obejść problem z różnym kodowaniem liter w przypadku odczytywania danych z serwera za pomocą Ajaksa.

Różne zestawy znaków (charset)

ASCII wykorzystuje 7 bitów. Bajt ma 8 bitów, więc pozostał jeden bit do zagospodarowania. Jeśli się nie mylę, to dawniej ten jeden bit wykorzystywany był jako bit kontroli parzystości (nie znalazłem źródła). Później stwierdzono, że szkoda tego bitu do celu kontrolowania poprawności, więc zagospodarowano go inaczej. Wartości <128-255> zostały użyte do zakodowania innych znaków - w tym znaków narodowych. Niestety ludzie są pomysłowi i stworzyli daleko więcej liter niż tylko 255. Choćby polski język posiada: ą, ć, ę, ł, ń, ó, ś, ź, ż razy 2 (małe i wielkie litery). Język niemiecki ma swoje umlauty, francuski swoje ślimaki, hiszpański, węgierski, czeski itd. Doliczmy do tego cyrylicę, alfabet grecki i już mamy nadkomplet. A co, jeśli chcielibyśmy kodować znaki arabskie, hinduskie, japońskie? Dobra, 255 nie wystarczy. Co zatem zrobić?

Proste rozwiązanie - trudne problemy

Wymyślono prosty sposób - przecież Polak (np. w ISO-8859-2) nie potrzebuje umlautów na stronie, a Węgier wcale nie ma ochoty czytać o "żółci" (polskie znaki). Dlatego dla Polaka można przedział <128-255> zagospodarować inaczej niż dla takiego Węgra czy Hiszpana. Co prawda, nie będą oni mogli w jednym dokumencie zapisywać na jednym bajcie (8 bitach) liter z innych alfabetów, ale przecież pomyślmy - jak często się to zdarza?

Pomysł wydawał się prosty i przyjemny. Do dziś powoduje problemy 🙂

UTF-8

Istnieje sposób na zapisanie większości (jeśli nie wszystkich) liter ze wszystkich alfabetów. Polega to na tym, że znaki ASCII zapisujemy na 1 bajcie (wartości <0-127>), podobnie odgórnie dla całego świata zakres <128-255>. Co z resztą znaków? W UTF-8 znak nie musi być zapisany na tylko jednym bajcie. Jeden znak może zajmować 2 bajty (lub więcej). Dzięki temu nie musimy się przejmować tym, że Niemiec nie odczyta "ą", a Polak "Ö". W zamian pliki są odrobinę większe - dla języka polskiego, średnio 5% (każdy znak diakrytyczny zajmuje 2 bajty zamiast jednego).

Ajax i różne charsety

Załóżmy, że mamy problem: strona w UTF-8, a responseText z Ajaksa w ISO-8859-2.

Rozwiązanie 1: sprowadzić oba pliki do tego samego formatu.

Rozwiązanie 2: jawnie zadeklarować w pliku o innym kodowaniu, że jest to inne kodowanie, np.:

<?php
header('Content-Type: text/html; charset=iso-8859-2');
echo "Witaj świecie!";

W powyższym przykładzie, nawet na stronie z kodowaniem UTF-8 (pewnie z jeszcze innymi także) tekst odczytany Ajaksem zostanie prawidłowo wyświetlony.

Rozwiązanie 1 i 2 nie zawsze mogą być możliwe do wykonania. Z różnych powodów. Należy zatem:

  1. siąść i płakać
  2. poczytać dalej (sugerowana odpowiedź)

XMLHttpRequest.overrideMimeType

Obiekt XMLHttpRequest w przeglądarkach (z tego grona wykluczam IE, nawet 9) ma zaimplementowaną metodę overrideMimeType. Pozwala ona pobrać dane z serwera, zapisane w innym kodowaniu.

<html>
<head>
	<title>Przykład Ajax - kodowanie znaków</title>
	<!-- WAŻNE - UTF-8!!! -->
	<meta http-equiv='content-type' content='text/html; charset=utf-8' />
	<script type='text/javascript'>
	function test( charset )
	{
		var oClient = new XMLHttpRequest();
		oClient.open('POST', charset + '.php', true);
		oClient.overrideMimeType('text/html; charset=' + charset); // #

		oClient.onreadystatechange = function()
		{
			if (4 === this.readyState &amp;&amp; 200 === this.status)
			{
				document.getElementById('result').innerHTML = this.responseText;
			}
		}
		oClient.send(null);
	}
	</script>
</head>
<body>
	<a href='javascript:test("utf-8")'>Testuj UTF-8</a><br />
	<a href='javascript:test("iso-8859-2")'>Testuj ISO-8859-2</a><br />
	<div id="result"></div>
</body>
</html>

oraz plik 'utf-8.php':

<?php
header('Content-Type: text/html; charset=utf-8');
echo "Witaj świecie!";

plik 'iso-8859-2.php':

<?php
// odpalaj przykład po zakomentowaniu poniższej linii
header('Content-Type: text/html; charset=iso-8859-2');
echo "Witaj świecie!";

W pliku 'iso-8859-2.php' jeśli wykomentujemy wywołanie jawnego ustawienia zestawu znaków kodowych, oraz linię oznaczoną "#" w pliku html, wynik przestanie być prawidłowo wyświetlany.

Ważne (i oczywiste): pliki rzeczywiście muszą być zapisane w ISO-8859-2 oraz UTF-8. Samo wpisanie w kodzie `header' z odpowiednim charsetem nie koduje pliku. Tym zajmuje się edytor tekstowy, np. geany. ["dokument" -> "ustaw kodowanie znaków" -> "Unicode"/"Wschodnia Europa"].

Demo do pobrania i odpalenia lokalnie:

Warto przeczytać:

Komentarze (0) Trackbacks (1)

Leave a comment