LZW-сжатие строк на javascript и распаковка средствами 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("\0", "\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;
    }
    


    Разумеется, для корректной передачи строки, сжатой LZW ее нужно перед передачей кодировать в base64 и перед распаковкой декодировать. Проблем с этим быть не должно. На стороне PHP все гладко, а для JS в и-нете везде витает один и тот же алгоритм

    Ничего сверх-нового в этих функциях нет, но возможно эта статья сэкономит массу времени кому-то еще. Для чего может понадобится сжатие данных на стороне клиента я написал в комментариях.
    • +14
    • 10,2k
    • 9
    Поделиться публикацией

    Похожие публикации

    Комментарии 9

      +4
      У многих возникнет резонный вопрос: А зачем нужно сжимать данные на стороне клиента? На мой взгляд ответ может быть только один (та самая ситуация, с которой я и столкнулся): нужно передавать длинные строки через GET. Разумеется, многие скажут что для этого есть POST и кросдоменные ajax-запросы, но в моем случае это все не подходило по ряду причин. Я писал небольшой счетчик, собирающий инфу о браузерах и размещал его на разных своих сайтах. Вставлять на сайты iframe и делать ajax post — не подходит, потому, что насколько я знаю safari плохо дружит с установкой cookie в iframe (а мне это нужно). Вставлять свой js, который будет слать через POST данные прямо на сайт, где стоит счетчик может быть чревато конфликтами с другими скриптами.

      Еще я заметил, что многие думаю, что длина GET'a ограничена 255-ю символами. Это не так. Погуглив я нашел интересную запись, датированную еще 23.11.2011 года, где челвоек провел небольшое исследование насчет поддержки максимальной длины GET различными браузерами:

      • Firefox 3.6.24 и Firefox 8.0.1 открыли все ссылки, но после тестового значения 8388608 анкор в адресной строке браузера перестал отображаться.
      • Internet Explorer 8.0 повел себя совсем неадекватно. При открытии ссылок они принудительно усекались до длины 4121 символ и отображались в адресной строке браузера, соответственно, так же. Более короткие ссылки открылись без проблем.
      • Opera 11.52 открыла все ссылки без проблем, при отображении даже самых длинных анкоров также никаких замечаний нет.
      • Safari 5.1 открыл все ссылки до тестового значения 8388608, а на нем вылетел с фатальной ошибкой.
      • Chromium 15.0 и Google Chrome 15.0 с трудом отрисовали даже исходную страницу, периодически вываливая вместо нее свое «Опаньки...». Проблемы с отображением URL в адресной строке браузера начались с тестового значения 32768. Ссылки же удалось открыть лишь до 1048576, дальше появлялось неизменное «Опаньки...».


      Думаю, что новые версии описанных браузеров поддерживают длину GET строки не меньше, чем эти. Поддержка старых браузеров в моей ситуации была не нужна. Для IE < 9 я просто ничего не передавал
        +1
        В моем случае мне нужно было передавать строки длиной менее 4000 символов
          0
          А зачем обоснование этого велосипеда в каментах, а не в статье?
            +1
            Я его целенаправленно вынес в отдельный коммент, т.к. посчитал, что это несколько 2 разные темы. Сам способ сжатия данных на стороне клиента и распаковки на сервере описан в статье. А то, как я это применил — вопрос несколько другой, и кто-то может быть прочитав мою ситуацию ответит к комменту альтернативным способ решения ситуации.
            Криво высказался, но как-то так…
          0
          Прав ли я, думая, что в обычных случаях нет смысла сжимать данные передаваемые от клиента на сервер. Например длинный текст написанный пользователем. Или лучше запаковать при помощи javascript и расшифровать на сервере PHP?
            0
            Длинный текст, который вводит пользователь конечно же нет. А вот вставить пиксель, вида .../pixel.php?info=… где параметр info будет содержать закодированную в base64 строку параметры о браузере и разрешении экрана — очень удобно. Например, IE >= 9 по http в заголовках передает один параметр useragent, а через js navigator.userAgent строку примерно на 40% длиннее. Мне как раз и надо было собирать инфу о браузерах, и объект js navigator содержит довольно много информации, чтобы передавать ее не сжимая
          0
          > Вставлять свой js, который будет слать через POST данные прямо на сайт, где стоит счетчик может быть чревато конфликтами с другими скриптами

          какими конфликтами чревато?

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое