LZW-сжатие строк на javascript и распаковка средствами PHP

в 10:11, , рубрики: javascript, lzw, php, метки: , ,

Буквально вчера столкнулся с ситуацией, что не смог найти рабочих классов/модулей по сжатию/распаковке строк алгоритмом LZW. Точнее сказать: jsCompress-jsDecompress — работает. PhpCompress-PhpDecompress — работает. А вот jsCompress-PhpDecompress либо возвращает вообще что-то неведомое, либо пустую строку. Честно сказать не знаю, может такой проблемы с ANSI и нет, но вот с utf-8 она очень явно проявляется. Потратив несколько часов на решение проблемы я решил опубликовать готовые к работе функции на хабре.
Объяснять как работает сжатие алгоритмом LZW я не буду, т.к. это прекрасно описано в wiki.

За основу были взяты готовые функции и классы: для PHP на code.google.com/p/php-lzw/ и для JS gist.github.com/843889

JS-функцию оставляем «как есть», без изменений

function lzw_encode(s) {
    var dict = {};
    var data = (s + "").split("");
    var out = [];
    var currChar;
    var phrase = data[0];
    var code = 256;
    for (var i=1; i<data.length; i++) {
        currChar=data[i];
        if (dict[phrase + currChar] != null) {
            phrase += currChar;
        }
        else {
            out.push(phrase.length > 1 ? dict[phrase] : phrase.charCodeAt(0));
            dict[phrase + currChar] = code;
            code++;
            phrase=currChar;
        }
    }
    out.push(phrase.length > 1 ? dict[phrase] : phrase.charCodeAt(0));
    for (var i=0; i<out.length; i++) {
        out[i] = String.fromCharCode(out[i]);
    }
    return out.join("");
}

А вот PHP функцию пришлось чуть-чуть подправить, т.к. строки сжатые алгоритмом LZW могут содержать коды символов большие, чем 255 (аля unicode), и скопипастить дописать одну функцию mb_ord, которая будет возвращать код для этого самого получившегося многобайтового символа.

function mb_ord($string) {
	if (extension_loaded('mbstring') === true) {
		mb_language('Neutral');
		mb_internal_encoding('UTF-8');
		mb_detect_order(array('UTF-8', 'ISO-8859-15', 'ISO-8859-1', 'ASCII'));
		$result = unpack('N', mb_convert_encoding($string, 'UCS-4BE', 'UTF-8'));
		if (is_array($result) === true) return $result[1];
	}
	return ord($string);
}

function lzw_decompress($binary) {
	$dictionary_count = 256;
	$bits = 8;
	$codes = array();
	$rest = 0;
	$rest_length = 0;
	
	mb_internal_encoding("UTF-8"); 
	for ($i = 0; $i < mb_strlen($binary); $i++ ) {$codes[] = mb_ord(mb_substr($binary, $i, 1)); }
		
	// decompression
	$dictionary = range("", "xFF");
	$return = "";
	foreach ($codes as $i => $code) {
		$element = $dictionary[$code];
		if (!isset($element)) $element = $word . $word[0];
		$return .= $element;
		if ($i) $dictionary[] = $word . $element[0];			
		$word = $element;
	}
	return $return;
}

Ничего сверх-нового в этих функциях нет, но возможно эта статья сэкономит массу времени кому-то еще. Для чего может понадобится сжатие данных на стороне клиента я написал в комментариях.

Автор: XAKEPEHOK


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js