Podstawy#

Kodowanie#

W dzisiejszych czasach stosowanie prawidłowego kodowania w dokumentach nie sprawia już tyle kłopotów, co miało miejsce jeszcze kilka lat temu. Na dobre upowszechnił się zestaw znaków Unicode oraz różne odmiany kodowania UTF dlatego szczegółowa wiedza na temat kodowania powoli staje się zbędna.

Z drugiej jednak strony kodowanie przewija się w wielu specyfikacjach webowych, dlatego w ramach uproszczenia tychże specyfikacji utworzono osobny dokument poświęcony aktualnie stosowanym kodowaniom:

Obydwie specyfikacje są równorzędne względem siebie, ale wersja od WHATWG jest na bieżąco aktualizowana i to właśnie na jej podstawie oparłem prezentowany materiał. Pisząc "specyfikacja" lub "specyfikacja kodowania" mam na myśli przytoczoną wersję.

Dla osób początkujących i kompletnie nieobytych z pojęciami kodowania i dekodowania oraz ich najpopularniejszymi odmianami (np. UTF-8, UTF-16 czy UTF-32) polecam najpierw zaznajomić się z materiałami umieszczonymi w kursie HTML (dział "Zestawy znaków - Wstęp"), gdzie całość ma zdecydowanie przystępniejszy charakter. Opisy ze specyfikacji kodowania to w większości przypadków algorytmiczne wywody, które mogą być przydatne jedynie w sytuacji, kiedy chcielibyśmy samodzielnie napisać jakiś koder lub dekoder.

W razie potrzeby można sięgnąć i przeanalizować jakieś gotowe implementacje, np. Polyfill for the Encoding Living Standard's API.

Nazwy kanoniczne#

Specyfikacja kodowania jasno określiła nazwy dla poszczególnych kodowań, które w potocznym obiegu najczęściej określa się nazwami kanonicznymi # (canonical names). Punktem odniesienia przy ich definiowaniu był dokument IANA Character Sets registry (z wyjątkiem gb18030), co jest odzwierciedleniem wewnętrznego działania aktualnych przeglądarek internetowych. Nazwy kanoniczne w połączeniu z odpowiadającymi im etykietami zostały wymienione w specjalnej tabeli.

Były plany zapisu wszystkich nazw kanonicznych dla kodowań małymi literami i ewentualnemu ich zmapowaniu na odpowiednie wartości w przypadku starszych poleceń, ale ostatecznie się z tego wycofano (Encoding - Bug 32, DOM - Fix, HTML - Fix). W zamian zdecydowano, że tylko nowe polecenia będą zwracały nazwy kodowań zapisane małymi literami.

Nazwy kanoniczne służą po prostu do wymieniania lub odwoływania się do danego kodowania w tekście (np. tej czy innej specyfikacji). Są także łańcuchami znakowymi dla wszystkich poleceń, które w jakikolwiek sposób informują o użytym kodowaniu poprzez zwrócenie jego nazwy. Poniższa tabela zawiera krótki przegląd niektórych poleceń:

Właściwość IDLNazwa kanoniczna
DOM4
TextEncoder.encoding
TextDecoder.encoding
przekonwertowana na małe znaki ASCII
Document.characterSet
Document.charset
Document.inputEncoding
bez modyfikacji
Nieustandaryzowane
Document.defaultCharset (WHATWG - Bug 58)bez modyfikacji

W przypadku starszych poleceń i niektórych kodowań aktualne przeglądarki internetowe mogą zwracać nazwy kanoniczne bez zachowania poprawności w zapisie wielkości znaków. Biorąc to wszystko pod uwagę najlepiej będzie przed porównaniami (najczęściej w warunkach) samodzielnie zamieniać zwracane wartości na małe bądź duże znaki.

Prosty przykład tworzenia koderów tekstowych i dekoderów tekstowych z użyciem wszystkich dostępnych etykiet dla kodowań (włącznie z przekazaniem zapisu w postaci dużych liter):

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<!DOCTYPE html>
<html>

<head>

	<script>

		// Uruchom po całkowitym załadowaniu dokumentu
		window.onload = function(){

			var info = document.getElementById("info");
			var bufor = "";
			bufor = "<b>TEST DLA KODERA TEKSTOWEGO (TextEncoder)</b>";
			var ok = "<span style='color: green'>OK</span>";
			var bad = "<span style='color: red'>BAD</span>";

			var encoderInput = [["UTF-8", "utf8", "unicode-1-1-utf-8"], ["UTF-16BE"], ["UTF-16LE", "utf-16"]];
			var encoderInput_len = encoderInput.length;

			for (var i = 0; i < encoderInput_len; i++){

				var encodings = encoderInput[i];
				encodings_len = encodings.length;

				bufor += "<br><br>" + "Kodowanie: " + encodings[0];
				encodings[0] = encodings[0].toLowerCase();
				encoding_test = encodings[0];

				for (var j = 0; j < encodings_len; j++){

					var lc = new TextEncoder(encodings[j]).encoding;
					var lc_compare = (encoding_test == lc ? ok : bad);
					var uc = new TextEncoder(encodings[j].toUpperCase()).encoding;
					var uc_compare = (encoding_test == uc ? ok : bad);

					bufor += "<br>" + "new TextEncoder(" + encodings[j] + ").encoding: " + lc + " " + lc_compare;
					bufor += "<br>" + "new TextEncoder(" + encodings[j].toUpperCase() + ").encoding: " + uc + " " + uc_compare;

				}

			}

			bufor += "<br><br>" + "<b>TEST DLA DEKODERA TEKSTOWEGO (TextDecoder)</b>";

			var decoderInput = [
				["UTF-8", "utf8", "unicode-1-1-utf-8"],
				["UTF-16BE"],
				["UTF-16LE", "utf-16"],
				["IBM866", "866", "cp866", "csibm866"],
				["ISO-8859-2", "csisolatin2", "iso-ir-101", "iso8859-2", "iso88592", "iso_8859-2", "iso_8859-2:1987", "l2", "latin2"],
				["ISO-8859-3", "csisolatin3", "iso-ir-109", "iso8859-3", "iso88593", "iso_8859-3", "iso_8859-3:1988", "l3", "latin3"],
				["ISO-8859-4", "csisolatin4", "iso-ir-110", "iso8859-4", "iso88594", "iso_8859-4", "iso_8859-4:1988", "l4", "latin4"],
				["ISO-8859-5", "csisolatincyrillic", "cyrillic", "iso-ir-144", "iso8859-5", "iso88595", "iso_8859-5", "iso_8859-5:1988"],
				["ISO-8859-6", "arabic", "asmo-708", "csiso88596e", "csiso88596i", "csisolatinarabic", "ecma-114", "iso-8859-6-e", "iso-8859-6-i", "iso-ir-127", "iso8859-6", "iso88596", "iso_8859-6", "iso_8859-6:1987"],
				["ISO-8859-7", "csisolatingreek", "ecma-118", "elot_928", "greek", "greek8", "iso-ir-126", "iso8859-7", "iso88597", "iso_8859-7", "iso_8859-7:1987", "sun_eu_greek"],
				["ISO-8859-8", "csiso88598e", "csisolatinhebrew", "hebrew", "iso-8859-8-e", "iso-ir-138", "iso8859-8", "iso88598", "iso_8859-8", "iso_8859-8:1988", "visual"],
				["ISO-8859-8-I", "csiso88598i", "logical"],
				["ISO-8859-10", "csisolatin6", "iso-ir-157", "iso8859-10", "iso885910", "l6", "latin6"],
				["ISO-8859-13", "iso8859-13", "iso885913"],
				["ISO-8859-14", "iso8859-14", "iso885914"],
				["ISO-8859-15", "csisolatin9", "iso8859-15", "iso885915", "iso_8859-15", "l9"],
				["ISO-8859-16"],
				["KOI8-R", "cskoi8r", "koi", "koi8", "koi8_r"],
				["KOI8-U", "koi8-ru"],
				["macintosh", "csmacintosh", "mac", "x-mac-roman"],
				["windows-874", "dos-874", "iso-8859-11", "iso8859-11", "iso885911", "tis-620"],
				["windows-1250", "cp1250", "x-cp1250"],
				["windows-1251", "cp1251", "x-cp1251"],
				["windows-1252", "ansi_x3.4-1968", "ascii", "cp1252", "cp819", "csisolatin1", "ibm819", "iso-8859-1", "iso-ir-100", "iso8859-1", "iso88591", "iso_8859-1", "iso_8859-1:1987", "l1", "latin1", "us-ascii", "x-cp1252"],
				["windows-1253", "cp1253","x-cp1253"],
				["windows-1254", "cp1254", "csisolatin5", "iso-8859-9", "iso-ir-148", "iso8859-9", "iso88599", "iso_8859-9", "iso_8859-9:1989", "l5", "latin5", "x-cp1254"],
				["windows-1255", "cp1255", "x-cp1255"],
				["windows-1256", "cp1256", "x-cp1256"],
				["windows-1257", "cp1257", "x-cp1257"],
				["windows-1258", "cp1258", "x-cp1258"],
				["x-mac-cyrillic", "x-mac-ukrainian"],
				["GBK", "chinese", "csgb2312", "csiso58gb231280", "gb2312", "gb_2312", "gb_2312-80", "iso-ir-58", "x-gbk"],
				["gb18030"],
				["Big5", "big5-hkscs", "cn-big5", "csbig5", "x-x-big5"],
				["EUC-JP", "cseucpkdfmtjapanese", "x-euc-jp"],
				["ISO-2022-JP", "csiso2022jp"],
				["Shift_JIS", "csshiftjis", "ms932", "ms_kanji", "shift-jis", "sjis", "windows-31j", "x-sjis"],
				["EUC-KR", "cseuckr", "csksc56011987", "iso-ir-149", "korean", "ks_c_5601-1987", "ks_c_5601-1989", "ksc5601", "ksc_5601", "windows-949"],
				["x-user-defined"]

			];

			var decoderInput_len = decoderInput.length;

			for (var i = 0; i < decoderInput_len; i++){

				var encodings = decoderInput[i];
				encodings_len = encodings.length;

				bufor += "<br><br>" + "Kodowanie: " +encodings[0];
				encodings[0] = encodings[0].toLowerCase();
				encoding_test = encodings[0];

				for (var j = 0; j < encodings_len; j++){

					try{

						var lc = new TextDecoder(encodings[j]).encoding;
						var lc_compare = (encoding_test == lc ? ok : bad);
						var uc = new TextDecoder(encodings[j].toUpperCase()).encoding;
						var uc_compare = (encoding_test == uc ? ok : bad);

						bufor += "<br>" + "new TextDecoder(" + encodings[j] + ").encoding: " + lc + " " + lc_compare;
						bufor += "<br>" + "new TextDecoder(" + encodings[j].toUpperCase() + ").encoding: " + uc + " " + uc_compare;

					}
					catch(e){
						bufor +=  "<br>" + "new TextDecoder(" + encodings[j] + ") zrzuca błąd: " + e + " " + bad;
					}

				}

			}

			info.innerHTML = bufor;

		}

	</script>

</head>

<body>
	<p id="info"></p>
</body>

</html>

Prosty przykład tworzenia nowych dokumentów w ramkach z użyciem wszystkich dostępnych etykiet dla kodowań:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<!DOCTYPE html>
<html>

<head>

	<script>

		// Uruchom po całkowitym załadowaniu dokumentu
		window.onload = function(){

			var info = document.getElementById("info");

			var labels = [
				["UTF-8", "utf8", "unicode-1-1-utf-8"],
				["UTF-16BE"],
				["UTF-16LE", "utf-16"],
				["IBM866", "866", "cp866", "csibm866"],
				["ISO-8859-2", "csisolatin2", "iso-ir-101", "iso8859-2", "iso88592", "iso_8859-2", "iso_8859-2:1987", "l2", "latin2"],
				["ISO-8859-3", "csisolatin3", "iso-ir-109", "iso8859-3", "iso88593", "iso_8859-3", "iso_8859-3:1988", "l3", "latin3"],
				["ISO-8859-4", "csisolatin4", "iso-ir-110", "iso8859-4", "iso88594", "iso_8859-4", "iso_8859-4:1988", "l4", "latin4"],
				["ISO-8859-5", "csisolatincyrillic", "cyrillic", "iso-ir-144", "iso8859-5", "iso88595", "iso_8859-5", "iso_8859-5:1988"],
				["ISO-8859-6", "arabic", "asmo-708", "csiso88596e", "csiso88596i", "csisolatinarabic", "ecma-114", "iso-8859-6-e", "iso-8859-6-i", "iso-ir-127", "iso8859-6", "iso88596", "iso_8859-6", "iso_8859-6:1987"],
				["ISO-8859-7", "csisolatingreek", "ecma-118", "elot_928", "greek", "greek8", "iso-ir-126", "iso8859-7", "iso88597", "iso_8859-7", "iso_8859-7:1987", "sun_eu_greek"],
				["ISO-8859-8", "csiso88598e", "csisolatinhebrew", "hebrew", "iso-8859-8-e", "iso-ir-138", "iso8859-8", "iso88598", "iso_8859-8", "iso_8859-8:1988", "visual"],
				["ISO-8859-8-I", "csiso88598i", "logical"],
				["ISO-8859-10", "csisolatin6", "iso-ir-157", "iso8859-10", "iso885910", "l6", "latin6"],
				["ISO-8859-13", "iso8859-13", "iso885913"],
				["ISO-8859-14", "iso8859-14", "iso885914"],
				["ISO-8859-15", "csisolatin9", "iso8859-15", "iso885915", "iso_8859-15", "l9"],
				["ISO-8859-16"],
				["KOI8-R", "cskoi8r", "koi", "koi8", "koi8_r"],
				["KOI8-U", "koi8-ru"],
				["macintosh", "csmacintosh", "mac", "x-mac-roman"],
				["windows-874", "dos-874", "iso-8859-11", "iso8859-11", "iso885911", "tis-620"],
				["windows-1250", "cp1250", "x-cp1250"],
				["windows-1251", "cp1251", "x-cp1251"],
				["windows-1252", "ansi_x3.4-1968", "ascii", "cp1252", "cp819", "csisolatin1", "ibm819", "iso-8859-1", "iso-ir-100", "iso8859-1", "iso88591", "iso_8859-1", "iso_8859-1:1987", "l1", "latin1", "us-ascii", "x-cp1252"],
				["windows-1253", "cp1253","x-cp1253"],
				["windows-1254", "cp1254", "csisolatin5", "iso-8859-9", "iso-ir-148", "iso8859-9", "iso88599", "iso_8859-9", "iso_8859-9:1989", "l5", "latin5", "x-cp1254"],
				["windows-1255", "cp1255", "x-cp1255"],
				["windows-1256", "cp1256", "x-cp1256"],
				["windows-1257", "cp1257", "x-cp1257"],
				["windows-1258", "cp1258", "x-cp1258"],
				["x-mac-cyrillic", "x-mac-ukrainian"],
				["GBK", "chinese", "csgb2312", "csiso58gb231280", "gb2312", "gb_2312", "gb_2312-80", "iso-ir-58", "x-gbk"],
				["gb18030"],
				["Big5", "big5-hkscs", "cn-big5", "csbig5", "x-x-big5"],
				["EUC-JP", "cseucpkdfmtjapanese", "x-euc-jp"],
				["ISO-2022-JP", "csiso2022jp"],
				["Shift_JIS", "csshiftjis", "ms932", "ms_kanji", "shift-jis", "sjis", "windows-31j", "x-sjis"],
				["EUC-KR", "cseuckr", "csksc56011987", "iso-ir-149", "korean", "ks_c_5601-1987", "ks_c_5601-1989", "ksc5601", "ksc_5601", "windows-949"],
				["csiso2022kr"],
				["hz-gb-2312"],
				["iso-2022-cn"],
				["iso-2022-cn-ext"],
				["iso-2022-kr"],
				["x-user-defined"]

			];

			var labels_len = labels.length;

			test = function(actual, correct){

				var ok = "<span style='color: green'>OK</span>";
				var bad = "<span style='color: red'>BAD</span>";

				if (correct == "UTF-16BE" || correct == "UTF-16LE"){
					correct = "UTF-8";
				}
				if (correct == "x-user-defined"){
					correct = "windows-1252";
				}

				return actual + " " + (actual == correct ? ok : bad);

			}

			for (var i = 0; i < labels_len; i++){

				var encodings = labels[i];
				var encodings_len = encodings.length;
				var encoding_test = encodings[0];

				for (var j = 0; j < encodings_len; j++){

					var getEncoding = function(){

						var mainFrame = window.parent;
						var info = mainFrame.document.getElementById("info");
						var mapped = ["UTF-16BE", "UTF-16LE", "utf-16", "csiso2022kr", "hz-gb-2312", "iso-2022-cn", "iso-2022-cn-ext", "iso-2022-kr", "x-user-defined"];
						var isMapped = "";
						if (mapped.indexOf(enc) != -1){
							isMapped = " (mapowane na inne)";
						}

						info.innerHTML += "<b>" + "Kodowanie: " + enc + isMapped + "</b>" + "<br>";
						info.innerHTML += "&amp;lt;meta charset='" + document.querySelector('meta').attributes[0].value + "'&amp;gt;";
						info.innerHTML += "<br>" + "document.characterSet: " + mainFrame.test(document.characterSet, enc);
						info.innerHTML += "<br>" + "document.charset: " + mainFrame.test(document.charset, enc);
						info.innerHTML += "<br>" + "document.inputEncoding: " + mainFrame.test(document.inputEncoding, enc) + "<br><br>";

					};

					var docHTML = "<!DOCTYPE html><html><head>"
						+ "<meta charset='" + encodings[j].toLowerCase() + "'>"
						+ "<script>" + "var enc = '" + encoding_test + "';" + "window.onload = " + getEncoding.toString() + "<\/script>"
						+ "</head></html>";

					var blob = new Blob([docHTML], {type: "text/html"});
					var obj_url = window.URL.createObjectURL(blob);

					var frame = document.createElement("iframe");
					frame.style.display = "none";
					document.body.appendChild(frame);
					frame.src = obj_url;

					//window.URL.revokeObjectURL(obj_url);

				}

			}

		}

	</script>

</head>

<body>
	<p id="info"></p>
</body>

</html>

Analiza poszczególnych API#

Specyfikacja kodowania jest bardzo minimalistyczna w kontekście dodatkowych API i definiuje jedynie dwa nowe interfejsy. Pierwszy TextEncoder służy do kodowania i drugi TextDecoder służy do dekodowania.

TextEncoder#

Chociaż specyfikacja kodowania definiuje sporo kodowań i odpowiadających im koderów, to koder tekstowy operuję wyłącznie na koderze UTF-8. W przeszłości zezwalano jeszcze na stosowanie kodera UTF-16LE oraz kodera UTF-16BE, ale w platformie webowej nie ma żadnego miejsca, które by z nich korzystały. Nawet w przypadku dokumentów zakodowanych w UTF-16BE lub UTF-16LE, gdzie akcja kodowania ma miejsc np. dla zapytań w adresach URL i przy wysyłaniu formularzy, kodowanie to jest automatycznie mapowane na UTF-8. To spowodowało, że w ostateczności zrezygnowano z możliwości stosowania odmian UTF-16 w koderze tekstowym (Encoding - Bug 18, Mozilla - Bug 1257877, Chromium - Bug 595351).

W razie konieczności należałoby napisać własne rozszerzenie zgodne z algorytmami poszczególnych kodowań. Najprościej będzie w przypadku UTF-16BE lub UTF-16LE bo można skorzystać z wewnętrznej reprezentacji łańcuchów znakowych w JavaScript:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<script>

	function encode_utf16(s, littleEndian){

		var a = new Uint8Array(s.length * 2);
		var view = new DataView(a.buffer);

		s.split('').forEach(function(c, i){
			view.setUint16(i * 2, c.charCodeAt(0), littleEndian);
		});

		return a;

	}

	function encode_builtin(s, endian, serial){

		var output = "";
		var encoder = new TextEncoder(endian);

		if (encoder.encoding == endian){
			if (serial){
				output = encoder.encode(s).join();
			}
			else{
				output = encoder.encode(s);
			}
		}
		else{
			output = "brak obsługi wbudowanego kodera";
		}

		return output;

	}

	document.write("encode_utf16('a'): " + encode_utf16("a"));
	document.write("<br>");
	document.write("encode_utf16('a').join(): " + encode_utf16("a").join());
	document.write("<br>");
	document.write("encode_builtin('a', 'utf-16be'): " + encode_builtin("a", "utf-16be"));
	document.write("<br>");
	document.write("encode_builtin('a', 'utf-16be').join(): " + encode_builtin("a", "utf-16be", true));

	document.write("<br><br>");

	document.write("encode_utf16('a', true): " + encode_utf16("a", true));
	document.write("<br>");
	document.write("encode_utf16('a', true).join(): " + encode_utf16("a", true).join());
	document.write("<br>");
	document.write("encode_builtin('a', 'utf-16le'): " + encode_builtin("a", "utf-16le"));
	document.write("<br>");
	document.write("encode_builtin('a', 'utf-16le').join(): " + encode_builtin("a", "utf-16le", true));

	document.write("<br><br>");

	document.write("encode_utf16('źdźbło'): " + encode_utf16("źdźbło"));
	document.write("<br>");
	document.write("encode_utf16('źdźbło').join(): " + encode_utf16("źdźbło").join());
	document.write("<br>");
	document.write("encode_builtin('źdźbło', 'utf-16be'): " + encode_builtin("źdźbło", "utf-16be"));
	document.write("<br>");
	document.write("encode_builtin('źdźbło', 'utf-16be').join(): " + encode_builtin("źdźbło", "utf-16be", true));

	document.write("<br><br>");

	document.write("encode_utf16('źdźbło', true): " + encode_utf16("źdźbło", true));
	document.write("<br>");
	document.write("encode_utf16('źdźbło', true).join(): " + encode_utf16("źdźbło", true).join());
	document.write("<br>");
	document.write("encode_builtin('źdźbło', 'utf-16le'): " + encode_builtin("źdźbło", "utf-16le"));
	document.write("<br>");
	document.write("encode_builtin('źdźbło', 'utf-16le').join(): " + encode_builtin("źdźbło", "utf-16le", true));

</script>

TextDecoder#

W przypadku akcji związanych z dekodowaniem specyfikacja kodowania jest bardziej liberalna i zezwala na tworzenie dekoderów tekstowych dla wszystkich definiowanych przez siebie kodowań, z wykluczeniem jedynie kodowania replacement. Używane kodowanie jest ustalane w oparciu o pierwszy argument przekazywany do konstruktora new TextDecoder(). Konstruktor pobiera także opcjonalny słownik, który pozwala doprecyzować zachowanie dla tworzonego dekodera tekstowego. Poniżej znajduje się dokładniejszy opis wszystkich kontrolowanych mechanizmów w dekoderze tekstowym.

Ściślejsza obsługa błędów#

W trakcie dekodowania wejściowego strumienia domyślnym zachowaniem dekodera tekstowego dla wszelkich błędów powstałych dla danego słowa jest wyprodukowanie w wyjściowym strumieniu specjalnego znaku U+FFFD (zgodnie z algorytmem przetwarzania). Silniejszą obsługę błędów można wymusić poprzez ustawienie właściwości fatal w słowniku przekazywanym do konstruktora new TextDecoder() na boolowską wartość true, przez co każdy błąd powstały w procesie dekodowania przerwie pracę dekodera tekstowego. Tryb obsługi błędów można sprawdzić za pomocą właściwości TextDecoder.fatal.

Świadome wygenerowanie błędu przy dekodowaniu nie powinno stanowić żadnego problemu. W przypadku kodowania jednobajtowwego wystarczy przekazać bajt (wskaźnik), dla którego nie istnieje w dedykowanym indeksie odpowiadający mu punkty kodowy. Dla UTF-8 można przekazać bajt informujący o konieczności odczytu większej ilości bajtów, ale bez przekazania tych bajtów. Jeśli chodzi o UTF-16 to najprościej przekazać pojedynczy bajt lub dwa bajty reprezentujące surogat, ale bez kolejnych bajtów. Oczywiście dla kodowań wielobajtowych istnieje cały szereg innych kombinacji, które dla danego strumienia wejściowego i dekodera tekstowego są nieprawidłowe.

Prosty przykład:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<script>

	function getInfo(desc, byte, name, options){

		var decoder = new TextDecoder(name, options);
		var byte_str = "new Uint8Array([" + byte.join() + "])";

		document.write(desc + "<br>");
		document.write("decoder.encoding: " + decoder.encoding + "<br>");
		document.write("decoder.fatal: " + decoder.fatal + "<br>");
		document.write("argument dla metody decoder.decode(): " + byte_str + "<br>");

		try{
			var output = decoder.decode(byte);
			document.write("decoder.decode(" + byte_str + "): " + output + "<br>");
		}
		catch(e){
			document.write("Wyjątek: " + e + "<br>");
		}

		document.write("<br>");

	}

	getInfo("Test dekodera dla kodowania ISO-8859-6:", new Uint8Array([164, 65]), "ISO-8859-6");
	getInfo("Test dekodera dla kodowania ISO-8859-6:", new Uint8Array([165, 65]), "ISO-8859-6");
	getInfo("Test dekodera dla kodowania ISO-8859-6:", new Uint8Array([165, 65]), "ISO-8859-6", {fatal: true});

	document.write("<br>");

	getInfo("Test dekodera dla kodowania UTF-8:", new Uint8Array([196, 133, 65]), "UTF-8");
	getInfo("Test dekodera dla kodowania UTF-8:", new Uint8Array([196, 65]), "UTF-8");
	getInfo("Test dekodera dla kodowania UTF-8:", new Uint8Array([196, 65]), "UTF-8", {fatal: true});

	document.write("<br>");

	getInfo("Test dekodera dla kodowania UTF-16BE:", new Uint8Array([1, 5]), "UTF-16BE");
	getInfo("Test dekodera dla kodowania UTF-16BE:", new Uint8Array([1]), "UTF-16BE");
	getInfo("Test dekodera dla kodowania UTF-16BE:", new Uint8Array([1]), "UTF-16BE", {fatal: true});

	document.write("<br>");

	getInfo("Test dekodera dla kodowania UTF-16BE:", new Uint8Array([216, 0, 0, 65]), "UTF-16BE");
	getInfo("Test dekodera dla kodowania UTF-16BE:", new Uint8Array([216, 0, 0, 65]), "UTF-16BE", {fatal: true});

</script>

#Na chwilę obecną jedynie przeglądarka Firefox prawidłowo obsługuje silniejszą kontrolę błędów dla wszystkich kodowań stosowanych przez dekoder tekstowy. Chrome nie zgłasza żadnych problemów w przypadku kodowań jednobajtowych i niektórych bajtów, co można szybko zweryfikować za pomocą poniższego kodu:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<script>

	var singleByteEncoding = [
		"IBM866", "ISO-8859-2", "ISO-8859-3", "ISO-8859-4", "ISO-8859-5", "ISO-8859-6",
		"ISO-8859-7", "ISO-8859-8", "ISO-8859-8-I", "ISO-8859-10", "ISO-8859-13", "ISO-8859-14",
		"ISO-8859-15", "ISO-8859-16", "KOI8-R", "KOI8-U", "macintosh", "windows-874", "windows-1250",
		"windows-1251", "windows-1252", "windows-1253", "windows-1254", "windows-1255", "windows-1256",
		"windows-1257", "windows-1258", "x-mac-cyrillic"
	];

	function getInfo(desc, encoding){

		var decoder = new TextDecoder(encoding, {fatal: true});
		var outputDec = [];
		var outputHex = [];

		document.write(desc + "<br><br>");

		for (var i = 0; i < 256; i++){

			try{
				decoder.decode(new Uint8Array([i]));
			}
			catch(e){
				var pointer = i - 128;
				var hex = "0x" + i.toString(16).toUpperCase();
				document.write(pointer + " " + i + " " + hex + "<br>");
				outputDec.push(i);
				outputHex.push(hex);
			}

		}

		if (outputDec.length == 0){
			document.write(0 + "<br>")
		}
		else{

			document.write("<br>");
			document.write(outputDec.length + "<br>");
			document.write(outputDec.join(", ") + "<br>");

			document.write("<br>");
			document.write(outputHex.length + "<br>");
			document.write(outputHex.join(", ") + "<br>");

		}

		document.write("<hr>");

	}

	for (var i = 0; i < singleByteEncoding.length; i++){
		getInfo("Test kodowania " + singleByteEncoding[i] + ":", singleByteEncoding[i]);
	}

</script>
Przechwytywanie znacznika BOM#

W trakcie dekodowania wejściowego strumienia domyślnym zachowaniem dekodera tekstowego dla kodowań UTF-8, UTF-16BE i UTF-16LE oraz bajtów reprezentujących przewidziany dla nich znacznik BOM jest jego pominięcie w wyjściowym strumieniu (zgodnie z algorytmem serializacji strumienia). Jest to standardowe zachowanie dla pozostałych mechanizmów platformy, które korzystają z algorytmu dekodowania, i dotyczy przede wszystkim przetwarzania wejściowego strumienia bajtów przez parser HTML lub właściwości XMLHttpRequest.responseText oraz XMLHttpRequest.responseXML.

W przypadku dekodera tekstowego pozostawienie znacznika BOM w wyjściowym strumieniu można wymusić poprzez ustawienie właściwości ignoreBOM w słowniku przekazywanym do konstruktora new TextDecoder() na boolowską wartość true. Tryb przechwytywania znacznika BOM można sprawdzić za pomocą właściwości TextDecoder.ignoreBOM.

Trzeba wyraźnie zaznaczyć, że algorytm serializacji strumienia stosowany przez metodę TextDecoder.decode() wykrywa znacznik BOM już po zdekodowaniu wejściowego strumienia (tj. operuje na punkcie kodowym a nie bajtach), co z perspektywy pomijania BOM generuje silniejszą zależność między kodowaniem w dekoderze tekstowym a wprowadzanymi bajtami. Algorytm dekodowania wykrywa znacznik BOM bezpośrednio z trzech pierwszych bajtów (jeszcze przed zdekodowaniem jakichkolwiek danych) i pozwala wyeliminować bajty reprezentujące znacznik BOM dla dowolnej odmiany UTF niezależnie od innych czynników (np. aktualnie stosowanego kodowania).

Prosty przykład:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<script>

	function encode_utf16(s, littleEndian){

		if (littleEndian == "UTF-16BE"){
			littleEndian = false;
		}

		var a = new Uint8Array(s.length * 2);
		var view = new DataView(a.buffer);

		s.split('').forEach(function(c, i){
			view.setUint16(i * 2, c.charCodeAt(0), littleEndian);
		});

		return a;

	}

	function getInfo(desc, byte, name, options){

		var decoder = new TextDecoder(name, options);
		var byte_str = "new Uint8Array([" + byte.join() + "])";

		document.write(desc + "<br>");
		document.write("decoder.encoding: " + decoder.encoding + "<br>");
		document.write("decoder.ignoreBOM: " + decoder.ignoreBOM + "<br>");
		document.write("argument dla metody decoder.decode(): " + byte_str + "<br>");

		var output = decoder.decode(byte);
		var outputTable = name == "UTF-8" ? new TextEncoder().encode(output) : encode_utf16(output, name);
		document.write("decoder.decode(" + byte_str + "): " + output + "<br>");
		document.write("decoder.decode().length: " + output.length + "<br>");
		document.write("encoder.encode(decoder.decode()): " + outputTable + "<br>");

		document.write("<br>");

	}

	var input_utf8 = [0xEF, 0xBB, 0xBF, 0x61, 0x62, 0x63]; // BOMabc
	var input_utf16le = [0xFF, 0xFE, 0x61, 0x00, 0x62, 0x00, 0x63, 0x00]; // BOMabc
	var input_utf16be = [0xFE, 0xFF, 0x00, 0x61, 0x00, 0x62, 0x00, 0x63]; // BOMabc

	getInfo("Test dekodera dla kodowania UTF-8:", new Uint8Array(input_utf8), "UTF-8");
	getInfo("Test dekodera dla kodowania UTF-8:", new Uint8Array(input_utf8), "UTF-8", {ignoreBOM: true});
	getInfo("Test dekodera dla kodowania UTF-8:", new Uint8Array(input_utf16le), "UTF-8");
	getInfo("Test dekodera dla kodowania UTF-8:", new Uint8Array(input_utf16le), "UTF-8", {ignoreBOM: true});
	getInfo("Test dekodera dla kodowania UTF-8:", new Uint8Array(input_utf16be), "UTF-8");
	getInfo("Test dekodera dla kodowania UTF-8:", new Uint8Array(input_utf16be), "UTF-8", {ignoreBOM: true});

	document.write("<hr>");

	getInfo("Test dekodera dla kodowania UTF-16LE:", new Uint8Array(input_utf16le), "UTF-16LE");
	getInfo("Test dekodera dla kodowania UTF-16LE:", new Uint8Array(input_utf16le), "UTF-16LE", {ignoreBOM: true});
	getInfo("Test dekodera dla kodowania UTF-16LE:", new Uint8Array(input_utf16be), "UTF-16LE");
	getInfo("Test dekodera dla kodowania UTF-16LE:", new Uint8Array(input_utf16be), "UTF-16LE", {ignoreBOM: true});
	getInfo("Test dekodera dla kodowania UTF-16LE:", new Uint8Array(input_utf8), "UTF-16LE");
	getInfo("Test dekodera dla kodowania UTF-16LE:", new Uint8Array(input_utf8), "UTF-16LE", {ignoreBOM: true});

	document.write("<hr>");

	getInfo("Test dekodera dla kodowania UTF-16BE:", new Uint8Array(input_utf16be), "UTF-16BE");
	getInfo("Test dekodera dla kodowania UTF-16BE:", new Uint8Array(input_utf16be), "UTF-16BE", {ignoreBOM: true});
	getInfo("Test dekodera dla kodowania UTF-16BE:", new Uint8Array(input_utf16le), "UTF-16BE");
	getInfo("Test dekodera dla kodowania UTF-16BE:", new Uint8Array(input_utf16le), "UTF-16BE", {ignoreBOM: true});
	getInfo("Test dekodera dla kodowania UTF-16BE:", new Uint8Array(input_utf8), "UTF-16BE");
	getInfo("Test dekodera dla kodowania UTF-16BE:", new Uint8Array(input_utf8), "UTF-16BE", {ignoreBOM: true});

</script>
Stopniowe dekodowanie#

Przy domyślnym wywołaniu metoda TextDecoder.decode(), kiedy flaga braku wyczyszczenia w dekoderze tekstowym jest nieustawiona, za każdym razem tworzy nowy dekoder z czystym stanem i dekoduje przekazany strumień w jednym podejściu. To oznacza, że wszelkie cząstkowe bajty z końca strumienia, których nie da się zdekodować będą generowały znak zastępczy lub błąd (zależnie od trybu obsługi błędów). Jest to wygodne w sytuacji, kiedy od razu mamy dostęp do całego strumienia bajtów.

W platformie webowej zazwyczaj mamy do czynienia z sytuacją, gdzie dane przychodzą w pakietach (są strumieniowane) i dobrze byłoby je od razu przetwarzać bez niepotrzebnego czekania na całość. W przypadku kodowań wielobajtowych samodzielne wyłuskanie z takiej porcji danych odpowiednich bajtów i pozostawienie reszty do dalszej obróbki, choć możliwe, byłoby nieco kłopotliwe. Dekoder tekstowy oferuje tryb stopniowego dekodowania, który można wymusić przy wywołaniu metody TextDecoder.decode() poprzez przekazanie do niej drugiego argumentu w postaci słownika z właściwością stream ustawioną na boolowską wartość true. To spowoduje, że wszelkie niedopasowane bajty z końca strumienia są zapamiętywane i przy kolejnym wywołaniu metody z opcją stopniowego dekodowania będą zestawiane z nową porcją danych.

Prosty przykład:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<script>

	var decoder = new TextDecoder();

	var s = "";
	s += decoder.decode(new Uint8Array([0xC4])); // �
	s += decoder.decode(new Uint8Array([0x85])); // ��
	document.write(s + "<br><br>"); // ��

	var s = "";
	s += decoder.decode(new Uint8Array([0xC4]), {stream: true}); // "" - wciąż pusty łańcuch
	s += decoder.decode(new Uint8Array([0x85]), {stream: true}); // ą
	document.write(s + "<br><br>"); // ą

	var s = "";
	s += decoder.decode(new Uint8Array([0xF0, 0x9F, 0x92, 0xA9, 0xC4])); // 💩�
	s += decoder.decode(new Uint8Array([0x85])); // 💩��
	document.write(s + "<br><br>"); // 💩��

	var s = "";
	s += decoder.decode(new Uint8Array([0xF0, 0x9F, 0x92, 0xA9, 0xC4]), {stream: true}); // 💩
	s += decoder.decode(new Uint8Array([0x85]), {stream: true}); // 💩ą
	document.write(s + "<br><br>"); // 💩ą

</script>

Algorytm opisujący działanie w metodzie TextDecoder.decode() jest specyficzny. Warto zaznaczyć, że flaga braku wyczyszczenia determinująca tworzenie nowego dekoder jest sprawdzana w kroku pierwszym, jeszcze przed weryfikacją wartości z właściwości stream przekazanego słownika. To powoduje, że ponowne wywołanie metody TextDecoder.decode() bez opcji stopniowego dekodowania po wywołaniu tejże metody ze stopniowym dekodowaniem dalej będzie operowało na poprzednim dekoderze i jego stanie. W tym osobliwym przypadku utworzenie dekodera z nowym stanem najłatwiej wymusić poprzez dodatkowe wywołanie metody TextDecoder.decode() bez żadnych argumentów.

Prosty przykład:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<script>

	var decoder = new TextDecoder();

	var s = "";
	s += decoder.decode(new Uint8Array([0xF0, 0x9F]), {stream: true}); // "" - wciąż pusty łańcuch
	s += decoder.decode(new Uint8Array([0x92, 0xA9])); // 💩
	s += decoder.decode(new Uint8Array([0xC4]), {stream: true}); // 💩
	s += decoder.decode(new Uint8Array([0x85])); // 💩ą
	document.write(s + "<br><br>"); // 💩ą

	var s = "";
	s += decoder.decode(new Uint8Array([0xF0, 0x9F]), {stream: true}); // "" - wciąż pusty łańcuch
	s += decoder.decode(); // �
	s += decoder.decode(new Uint8Array([0x92, 0xA9])); // ���
	s += decoder.decode(new Uint8Array([0xC4]), {stream: true}); // ���
	s += decoder.decode(); // ����
	s += decoder.decode(new Uint8Array([0x85])); // �����
	document.write(s + "<br><br>"); // �����

</script>
Pasek społecznościowy

SPIS TREŚCI AKTUALNEJ STRONY

Podstawy (H1) Kodowanie (H2) Nazwy kanoniczne (H3) Analiza poszczególnych API (H3) TextEncoder (H4) TextDecoder (H4) Ściślejsza obsługa błędów (H5) Przechwytywanie znacznika BOM (H5) Stopniowe dekodowanie (H5)