Otóż w Polsce do tej pory często wykorzystywanym kodowaniem jest ISO-8859-2 ale czasy swojej świetności ma już dawno za sobą. Było używane wszędzie tam gdzie nie było obsługi polskich znaków a chciano je wprowadzić. Dzisiaj mamy Unicode i mnóstwo kodowań które je obsługuje.

Każde kodowanie z rodziny ISO 8859 jest zgodne z kodowaniem ASCII lub jak kto woli bazuje na nim, innymi słowy pierwsze 127 (licząc od zera 128, 0x00 – 0x7F) znaków kontrolnych i drukowanych się pokrywa.

Filtrowanie każdego kodowania z rodziny ISO/IEC 8859

Otóż każde z tych kodowań zawiera mnóstwo znaków kontrolnych oraz nieużywanych pozycji. Funkcja clean_iso_8859() oczyszcza właśnie ciąg z tych niepotrzebnych znaków, przyjmuje dwie wartości, pierwsza to ciąg do oczyszczenia a druga to nazwa kodowania w formacie iso-8859-X gdzie X to numer kodowania jakie chcemy oczyścić. Otrzymamy gotowy ciąg z którym możemy dalej robić co nam się żywnie podoba.

Zamienia lub usuwa:

  • SOFT HYPHEN na HYPHEN-MINUS (dywiz na minusa, można zmienić; znak dywizu nie obsługuje chyba żadna przeglądarka)
  • NO-BREAK SPACE na SPACE (niełamliwą spację na zwykła spację, można zmienić)
  • Każde wystąpienie powyżej 2 spacji na pojedynczą
  • Usuwa znaki kontrolne oraz nieużywane pozycje(zależnie od wybranego kodowania) oprócz:

    • HORIZONTAL TABULATION (\t|\x09)
    • LINE FEED (\n|\x0A)
    • CARRIAGE RETURN (\r|\x0D)
    • SPACE ( |\x20)

    Nie każde kodowanie zawiera dodatkowe pozycje do usunięcia, jak na razie zawierają je:

    • iso-8859-3
    • iso-8859-6
    • iso-8859-7
    • iso-8859-8
    • iso-8859-11 (tutaj nie ma znaku dywizu)

    I tylko w wypadku filtrowania tych wyżej wymienionych kodowań należy podawać drugi parametr dla tej funkcji.

function clean_iso_8859($string, $encoding = NULL) {
	// clean_iso_8859() by tosiek - https://tosiek.pl/
	if (!isset($string)) {
		trigger_error('clean_iso_8859() first parameter cannot be empty', E_USER_WARNING);
		return NULL;
	} else {
		settype($string, 'string');
		if ($encoding != NULL) {
			settype($encoding, 'string');
			$encoding = strtolower($encoding);
		}
 
		$pattern = array(
			0 => "\xAD", // SOFT HYPHEN to HYPHEN-MINUS
			1 => "\xA0", // NO-BREAK SPACE to SPACE
		);
		$replacement = array(
			0 => "\x2D",
			1 => "\x20",
		);
 
		//Remove unused positions in encodings
		switch ($encoding) {
			case NULL:
				break;
			case 'iso-8859-1':
				break;
			case 'iso-8859-2':
				break;
			case 'iso-8859-3':
				$string = preg_replace('/[\\xA5\\xAE\\xBE\\xC3\\xD0\\xE3\\xF0]/', '', $string);
				break;
			case 'iso-8859-4':
				break;
			case 'iso-8859-5':
				break;
			case 'iso-8859-6':
				$string = preg_replace('/[\\xA1\\xA2\\xA3\\xA5\\xA6\\xA7\\xA8\\xA9\\xAA\\xAB\\xAE\\xAF\\xB0\\xB1\\xB2\\xB3\\xB4\\xB5\\xB6\\xB7\\xB8\\xB9\\xBA\\xBC\\xBD\\xBE\\xC0\\xDB\\xDC\\xDD\\xDE\\xDF\\xF3\\xF4\\xF5\\xF6\\xF7\\xF8\\xF9\\xFA\\xFB\\xFC\\xFD\\xFE\\xFF]/', '', $string);
				break;
			case 'iso-8859-7':
				$string = preg_replace('/[\\xAE\\xD2\\xFF]/', '', $string);
				break;
			case 'iso-8859-8':
				$string = preg_replace('/[\\xA1\\xBF\\xC0\\xC1\\xC2\\xC3\\xC4\\xC5\\xC6\\xC7\\xC8\\xC9\\xCA\\xCB\\xCC\\xCD\\xCE\\xCF\\xD0\\xD1\\xD2\\xD3\\xD4\\xD5\\xD6\\xD7\\xD8\\xD9\\xDA\\xDB\\xDC\\xDD\\xDE\\xFB\\xFC\\xFF]/', '', $string);
				break;
			case 'iso-8859-9':
				break;
			case 'iso-8859-10':
				break;
			case 'iso-8859-11':
				unset($pattern[0], $replacement[0]); //Do not replace SOFT HYPHEN because there is THAI CHARACTER YO YING
				$string = preg_replace('/[\\xDB\\xDC\\xDD\\xDE\\xFC\\xFD\\xFE\\xFF]/', '', $string);
				break;
			case 'iso-8859-12':
				trigger_error('clean_iso_8859() character set \'iso-8859-12\' not exists', E_USER_WARNING);
				return NULL;
				break;
			case 'iso-8859-13':
				break;
			case 'iso-8859-14':
				break;
			case 'iso-8859-15':
				break;
			case 'iso-8859-16':
				break;
			default:
				trigger_error('clean_iso_8859() unexpected character encoding in parameter 2', E_USER_WARNING);
				return NULL;
		}
 
		//Replace others ALL ISO/IEC 8859
		$string = str_replace($pattern, $replacement, $string);
		$string = preg_replace('/\\x20{2,}/', "\x20", $string); //Replace more then one SPACE to once
 
		/*********ALL ISO/IEC 8859*********
 
		  Remove <control> characters:
		 * NULL (\x00) - BACKSPACE (\x08)
		 * VERTICAL TABULATION (\x0B)
		 * FORM FEED (\x0C)
		 * SHIFT OUT (\x0E) - UNIT SEPARATOR (\x1F)
		 * DELETE (\x7F) - <control> (\x9F)
 
		  Without:
		  - HORIZONTAL TABULATION (\t|\x09)
		  - LINE FEED (\n|\x0A)
		  - CARRIAGE RETURN (\r|\x0D)
		  - SPACE ( |\x20)
 
		 *********ALL ISO/IEC 8859*********/
		$string = preg_replace('/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F-\\x9F]/', '', $string);
 
		return $string;
	}
}

htmlentities ISO/IEC 8859

Otóż standardowa funkcja htmlentities() nie obsługuje żadnego kodowani z rodziny ISO oprócz latin1 i latin9. Dla reszty kodowań także da się zapisać każdy znak za pomocą encji ale niestety nie za pomocą encji symbolicznych np. &oacute; co odpowiada znakowi ó. Dlatego, żeby to wszystko ujednolicić i nie mieszać różnych rodzajów encji zdecydowałem się zapisać wszystko za pomocą encji numerycznych to jest &#378; odpowiada znak ź. Tablice z encjami należy umieścić w skrypcie gdzieś przed funkcją (podzieliłem je także na pojedyncze kodowania):

Funkcja htmlentities_iso() przyjmuje takie same wartości (dla kodowania należy podawać w formacie iso-8859-X gdzie X to numer kodowania) jak oryginalna htmlentities(). Wymaga aby wcześniej skrypcie PHP były podane wyżej wymienione tablice, albo tylko te, z których będziemy korzystać. Nie mogę dać gwarancji czy działa w 100% z ustawionym $double_encode = false – wymagałoby testu na wszystkich istniejących encjach czy ich nie zamienia powtórnie – powiem tyle: u mnie działa ; ) Jak na razie nie jestem w stanie znaleźć jakiegoś praktycznego zastosowania tych dwóch funkcji.

function htmlentities_iso($string, $flags = ENT_COMPAT, $charset = 'iso-8859-1', $double_encode = true) {
	// htmlentities_iso() by tosiek - https://tosiek.pl/
	if (!isset($string)) {
		trigger_error('htmlentities_iso() expects at least 1 parameter', E_USER_WARNING);
		return NULL;
	} else {
		settype($string, 'string');
		settype($flags, 'string');
		settype($charset, 'string');
		settype($double_encode, 'boolean');
		$charset = strtolower($charset);
 
		//Encode existing entities?
		if ($double_encode == true) {
			$string = str_replace("\x26", '&#38;', $string); // or &amp;
		} else {
			$string = preg_replace('/&(?!(((#|#X|#x)+[a-fA-F0-9]{1,4})|[a-zA-Z]{1,10}[0-9]{0,3})+;)/', '&#38;', $string, -1); // or &amp;
		}
 
		//Get html translation table
		switch ($charset) {
			case 'iso-8859-1':
				global $trans_iso_8859_1;
				$trans = $trans_iso_8859_1;
				break;
			case 'iso-8859-2':
				global $trans_iso_8859_2;
				$trans = $trans_iso_8859_2;
				break;
			case 'iso-8859-3':
				global $trans_iso_8859_3;
				$trans = $trans_iso_8859_3;
				break;
			case 'iso-8859-4':
				global $trans_iso_8859_4;
				$trans = $trans_iso_8859_4;
				break;
			case 'iso-8859-5':
				global $trans_iso_8859_5;
				$trans = $trans_iso_8859_5;
				break;
			case 'iso-8859-6':
				global $trans_iso_8859_6;
				$trans = $trans_iso_8859_6;
				break;
			case 'iso-8859-7':
				global $trans_iso_8859_7;
				$trans = $trans_iso_8859_7;
				break;
			case 'iso-8859-8':
				global $trans_iso_8859_8;
				$trans = $trans_iso_8859_8;
				break;
			case 'iso-8859-9':
				global $trans_iso_8859_9;
				$trans = $trans_iso_8859_9;
				break;
			case 'iso-8859-10':
				global $trans_iso_8859_10;
				$trans = $trans_iso_8859_10;
				break;
			case 'iso-8859-11':
				global $trans_iso_8859_11;
				$trans = $trans_iso_8859_11;
				break;
			case 'iso-8859-12':
				trigger_error('htmlentities_iso() character set \'iso-8859-12\' not exists, assuming iso-8859-1', E_USER_WARNING);
				global $trans_iso_8859_1;
				$trans = $trans_iso_8859_1;
				break;
			case 'iso-8859-13':
				global $trans_iso_8859_13;
				$trans = $trans_iso_8859_13;
				break;
			case 'iso-8859-14':
				global $trans_iso_8859_14;
				$trans = $trans_iso_8859_14;
				break;
			case 'iso-8859-15':
				global $trans_iso_8859_15;
				$trans = $trans_iso_8859_15;
				break;
			case 'iso-8859-16':
				global $trans_iso_8859_16;
				$trans = $trans_iso_8859_16;
				break;
			default:
				trigger_error('htmlentities_iso() charset \'' . htmlentities($charset, ENT_QUOTES, 'UTF-8', true) . '\' not supported, assuming iso-8859-1', E_USER_WARNING);
				global $trans_iso_8859_1;
				$trans = $trans_iso_8859_1;
		}
 
		//Set quote style
		switch ($flags) {
			case '0':
				break;
			case '3':
				$trans["\x27"] = '&#39;';
			case '2':
				$trans["\x22"] = '&#34;'; // or &quot;
				break;
			default:
				trigger_error('htmlentities_iso() expects parameter 2 to be long', E_USER_WARNING);
				return NULL;
		}
		$trans["\x3C"] = '&#60;'; // or &lt;
		$trans["\x3E"] = '&#62;'; // or &gt;
 
		$search = array_keys($trans);
		$replace = array_values($trans);
		$string = str_replace($search, $replace, $string);
		return $string;
	}
}

html_entity_decode_iso

I tutaj mam dylemat czy opierać się także na tablicach czy po prostu zamieniać każdą możliwą encję wyrażeniami regularnymi. Przy automatycznym dekodowaniu encji mógłby być problem ze znakami – tak czy siak trzeba by było skonwertować więc znowu się narzuca użycie tablic… Jak na razie w manualu jest podobna funkcja – wystarczy użyć tablic wyżej.

Funkcja html_entity_decode_iso() przyjmuje identyczne argumenty jak oryginalna html_entity_decode() tyle, że kodowanie należy podawać w formacie iso-8859-X gdzie X to numer kodowania. Dodałem także czwarty parametr – odpowiada on za podwójne dekodowanie encji tj. jeżeli poprzednio zakodowało encję podwójnie czyli zamiast początkowego & (ampersand) wsadziło encję: &#38; to po ustawieniu 4 parametru na true funkcja najpierw podmieni podwójnie zakodowane encje a później te z tablic dla właściwego kodowania np. &#38;#160; lub &amp;#160; bądź &#x26;#160; najpierw zamieni początkowa encję znaku ampersand na odpowiedni znak &#160; a potem zgodnie z tablicą na wartość “\xa0”.

function html_entity_decode_iso($string, $flags = ENT_COMPAT, $charset = 'iso-8859-1', $double_encode = false) {
	if (!isset($string)) {
		trigger_error('html_entity_decode_iso() expects at least 1 parameter', E_USER_WARNING);
		return NULL;
	} else {
		settype($string, 'string');
		settype($flags, 'string');
		settype($charset, 'string');
		settype($double_encode, 'boolean');
		$charset = strtolower($charset);
 
		//Decode multi encoded entities
		if ($double_encode == true) {
			$string = str_replace('&amp;', '&', $string);
			$string = preg_replace('/&#(0|x|X)*?(38|26)+;/', '&', $string);
		}
 
		//Get html translation table
		switch ($charset) {
			case 'iso-8859-1':
				global $trans_iso_8859_1;
				$trans = $trans_iso_8859_1;
				break;
			case 'iso-8859-2':
				global $trans_iso_8859_2;
				$trans = $trans_iso_8859_2;
				break;
			case 'iso-8859-3':
				global $trans_iso_8859_3;
				$trans = $trans_iso_8859_3;
				break;
			case 'iso-8859-4':
				global $trans_iso_8859_4;
				$trans = $trans_iso_8859_4;
				break;
			case 'iso-8859-5':
				global $trans_iso_8859_5;
				$trans = $trans_iso_8859_5;
				break;
			case 'iso-8859-6':
				global $trans_iso_8859_6;
				$trans = $trans_iso_8859_6;
				break;
			case 'iso-8859-7':
				global $trans_iso_8859_7;
				$trans = $trans_iso_8859_7;
				break;
			case 'iso-8859-8':
				global $trans_iso_8859_8;
				$trans = $trans_iso_8859_8;
				break;
			case 'iso-8859-9':
				global $trans_iso_8859_9;
				$trans = $trans_iso_8859_9;
				break;
			case 'iso-8859-10':
				global $trans_iso_8859_10;
				$trans = $trans_iso_8859_10;
				break;
			case 'iso-8859-11':
				global $trans_iso_8859_11;
				$trans = $trans_iso_8859_11;
				break;
			case 'iso-8859-12':
				trigger_error('html_entity_decode_iso() character set \'iso-8859-12\' not exists, assuming iso-8859-1', E_USER_WARNING);
				global $trans_iso_8859_1;
				$trans = $trans_iso_8859_1;
				break;
			case 'iso-8859-13':
				global $trans_iso_8859_13;
				$trans = $trans_iso_8859_13;
				break;
			case 'iso-8859-14':
				global $trans_iso_8859_14;
				$trans = $trans_iso_8859_14;
				break;
			case 'iso-8859-15':
				global $trans_iso_8859_15;
				$trans = $trans_iso_8859_15;
				break;
			case 'iso-8859-16':
				global $trans_iso_8859_16;
				$trans = $trans_iso_8859_16;
				break;
			default:
				trigger_error('html_entity_decode_iso() charset \'' . htmlentities($charset, ENT_QUOTES, 'UTF-8', true) . '\' not supported, assuming iso-8859-1', E_USER_WARNING);
				global $trans_iso_8859_1;
				$trans = $trans_iso_8859_1;
		}
 
		//Set quote style
		switch ($flags) {
			case '0':
				break;
			case '3':
				$trans["\x27"] = '&#39;';
			case '2':
				$trans["\x22"] = '&#34;'; // or &quot;
				break;
			default:
				trigger_error('html_entity_decode_iso() expects parameter 2 to be long', E_USER_WARNING);
				return NULL;
		}
		$trans["\x3C"] = '&#60;'; // or &lt;
		$trans["\x3E"] = '&#62;'; // or &gt;
 
		$search = array_values($trans);
		$replace = array_keys($trans);
		$string = str_replace($search, $replace, $string);
		return $string;
	}
}