Javascript.RU

Создать новую тему Ответ
 
Опции темы Искать в теме
  #1 (permalink)  
Старый 16.03.2010, 21:00
Интересующийся
Отправить личное сообщение для Serge Ageyev Посмотреть профиль Найти все сообщения от Serge Ageyev
 
Регистрация: 16.03.2010
Сообщений: 12

Решение проблемы кодировок для AJAX и PHP без iconv (cp1251 в AJAX)
Вступление:
Многие, не исключая автора, сталкивались с проблемой, когда нужно использовать одновременно php и ajax в web проекте, написанном с использованием однобайтовой кодовой страницы (cp1251, koi-8, cp866, cp1252, latin-1 и т.д.). Все находили известный тезис что "AJAX работает только с UNICODE".
Попробуем разобрать проблему и найти решение.

Для нетерпеливых читателей скажу, что решение есть в виде написанной функции “encodeFormField”, которая работает с ЛЮБОЙ однобайтовой кодировкой. Эту функцию можно (нужно) использовать вместо escape или encodeURIComponent, после чего строковые данные доставляются до php в исходной кодировке (нет необходимости в использовании iconv в php скриптах).
Исподники (BSD License) можно загрузить по адресу:
http://www.viasoft.com.ua/js/encode_form_field.zip

Итак, задача:
1. Есть сайт, написанный на php
2. Кодировка html страниц сайта cp1251 (koi-8, cp866, cp1252, latin-1 и т.д.).
3. Нужно добавить AJAX функциональность, без необходимости переписывать php код

Лирика:
Как пишутся php скрипты обычно:

<form>
 <input type="text" name="my_field" value="Привет!" />
 <input type="submit" value="show me" />
</form>
<div>
<?
if (isset($_REQUEST['my_field']))
 {
  if ($_REQUEST['my_field'] == 'Привет!')
   {
    echo 'Напишите что-то поинтереснее!';
   }
  else
   {
    echo 'Получено:'.htmlspecialchars($_REQUEST['my_field']);
   }
 }
?>
</div>


Все работает, как и ожидалось:
Мы просто читаем данные запроса из массива $_REQUEST['my_field'] и получаем их в том виде, который ввел пользователь (если мы не забыли про "Content-Type: text/html; charset=windows-1251").

Как это работает на самом деле:
1. Браузер передавая данные на сервер, кодирует символы текущей кодировки с кодами > 127 как последовательность байт в виде %{2 hex символа}. Например, в примере выше (для cp1251) мы увидим "test.php?my_field=%CF%F0%E8%E2%E5%F2%21". В этом случае CF - это код буквы "П" F0 - код буквы "р" и т.д.
2. PHP обрабатывает строковые данные "как есть", т.е. манипулирует строковыми данными как последовательностями однобайтовых символов, не пытаясь преобразовывать их в unicode (это скорее хорошо, так как это быстро). Поэтому последовательность байт %CF%F0%E8%E2%E5%F2%21 превратится в строку из 7-ми байт, что соответствует 7-ми символам слова "Привет!" в cp1251
3. Мы можем эту строку записать в БД, сравнить с другой строкой из таких же 7-ми символов и т.д.
Все это очень удобно, просто, так и хочется писать код дальше.
Собственно на php так это и делается в большинстве случаев.

А теперь javascript:
Допустим, мы хотим отправить на сервер HTTP request, сформированный в JavaScript (например, AJAX или даже простой location.replace переход) в котором поле query запроса будет кодироваться из JavaScript функцией.

Попробуем для кодирования поля из нашей формы применить функцию escape:

<form>
 <input type="text" name="my_field" id="my_field" value="Привет!" />
 <input type="button" value="show me" onclick="sendform()"/>
</form>
<div>
<script>
function sendform()
 {
  location.replace(location.pathname+'?my_field='+escape(document.getElementById('my_field').value));
 }
</script>
<?
if (isset($_REQUEST['my_field']))
 {
  if ($_REQUEST['my_field'] == 'Привет!')
   {
    echo 'Напишите что-то поинтереснее!';
   }
  else
   {
    echo 'Получено:'.htmlspecialchars($_REQUEST['my_field']);
   }
 }
?>
</div>


Как видим, "escape" не подходит:
Вместо:
test.php?my_field=%CF%F0%E8%E2%E5%F2%21
На сервер передается:
test.php?my_field=Привет%21

В чем же дело?
«escape» кодирует символы с кодами > 127 из исходной кодовой страницы как %u{4 hex символа(unicode)} и php не распознает такую кодировку (что логично, вспомним, что строки в php это просто последовательности байт).

Попробуем применить вместо escape функцию encodeURIComponent:
Заменим:
location.replace(location.pathname+'?my_field='+escape(document.getElementById('my_field').value));

на:
location.replace(location.pathname+'?my_field='+encodeURIComponent(document.getElementById('my_field').value));


Как видим, "encodeURIComponent" тоже не подходит:
Вместо:
test.php?my_field=%CF%F0%E8%E2%E5%F2%21
На сервер передается:
test.php?my_field=%EF%BF%BD%EF%BF%BD%EF%BF%BD%EF%B F%BD%EF%BF%BD%EF%BF%BD!

В чем же дело?
"encodeURIComponent" кодирует символы с кодами > 127 из исходной кодовой страницы путем перевода их в UTF-8 и затем передает их как последовательность %{2 hex символа}.
Примечание: Аналогично работает с символами > 127 функция encodeURI, впрочем она не подходит для url компонент.

Причина проблемы:
1. В JavaScript все строки представляются в unicode (именно так: При любой загрузке JavaScript кода, все строковые константы в нем автоматически переводятся в unicode).
2. В JavaScript нет других функций, которые кодируют строки параметров url, кроме escape и encodeURIComponent

Собственно вот мы и видим причину, по которой так много пишется что "AJAX работает только с UNICODE".

Существующие варианты решения:
1. Конечно, это не проблема, если весь сайт в UTF-8 или если его можно перенести в UTF-8, но это большой кусок работы и это не всегда возможно.
2. Можно использовать на PHP стороне функцию iconv для того чтобы перекодировать UTF-8 в нужную кодировку, но это не удобно (в частности, нужно просмотреть все обращения к аргументам) -- хотя это наиболее часто рекомендуемый способ.

Как решить проблему проще:
Нужна JavaScript функция, которая кодирует данные также, как и браузер, тогда можно писать на php "как обычно".
Фактически, нужна функция которая использует таблицу перекодировки UNICODE -> текущая кодовая страница.
Существуют решения, которые реализуют такое преобразование для некоторых кодировок, но они не универсальны.

Решение:
После нескольких дней экспериментов, было найдено универсальное решение - функция encodeFormField, которая работает в любой однобайтовой кодировке (cp1251, koi-8, cp866, cp1252, latin-1 и т.д.).
Это и было опробовано в нескольких проектах.
Загрузить скрипт можно отсюда:
http://www.viasoft.com.ua/js/encode_form_field.zip

Основная идея:
0. Используем браузер для построения таблицы перекодировки unicode -> текущая кодовая страница:
1. javascript файл содержит строковую константу, которая содержит символы с кодами от 128 до 255 включительно.
2. Броузер загружает эту строку и преобразовывает ее в unicode в ТЕКУЩЕЙ кодировке.
3. Когда нужно преобразовать символ с кодом > 127 из ТЕКУЩЕЙ кодировки, ищется его позиция в этой строке.
4. Найденная позиция+128 и будет искомым hex кодом символа и она используется для кодировки url (или post) компонента запроса.

Как пользоваться:
Способо если ваш сайт работает строго под какой-то кодировкой
1. Подключите encode_form_field.js
2. Используйте функцию encodeFormField(text) вместо escape(text) или encodeURIComponent(text) и получите правильную кодирование символов для однобайтовых кодировок.

Как пользоваться (продвинутый режим):
Библиотека содержит функцию-заглушку работающую для случая UTF8 и детектор кодировок.
Поэтому, если сайт постепенно мигрирует с однобайтовой кодировки на UTF8, лучше искользовать так:

(Добавилось с версии 1.2)
<script src="encode_form_field_utf8_detect.js"></script>
<script>
 if (encodeFormFieldIsPageOnUTF8())
  {
   document.write('<'+'script src="encode_form_field_utf8_stub.js"></'+'script>');
  }
 else 
  {
   document.write('<'+'script src="encode_form_field.js"></'+'script>');
  }
</script>


Комменты приветствуются.

Успехов!

PS:
НЕ РЕКОМЕНДУЕТСЯ использовать copy-n-paste для того, чтобы вставить этот код к вам на страницу, так как большинство редакторов "сломают" строку преобразования. По этой же причине редактировать исходный файл нужно с осторожностью. Лучше загрузить файл из архива и использовать его на вашем сайте.

PPS:
Скрипит публикуется под BSD License, так что если есть идеи как все это улучшить - welcome!

PPPS:
Некоторые однобайтовые кодировки (например windows-1257, ISO-8859-3) не поддерживают однозначного преобразования в юникод и обратно, по крайней мере браузер не может построить такую таблицу.
Для того, чтобы проверить такой случай, к библиотеке начиная с версии 1.2 прилагаются юнит-тесты

Последний раз редактировалось Serge Ageyev, 17.03.2010 в 20:46. Причина: Описан продвинутый режим подключения для поддеждержки миграции на UTF8 + добавлена заметка про юнит тесты
Ответить с цитированием
  #2 (permalink)  
Старый 16.03.2010, 21:02
Интересующийся
Отправить личное сообщение для Serge Ageyev Посмотреть профиль Найти все сообщения от Serge Ageyev
 
Регистрация: 16.03.2010
Сообщений: 12

На всякий случай прикладываю архив с исходниками сюда
Вложения:
Тип файла: zip encode_form_field.zip (6.8 Кб, 30 просмотров)

Последний раз редактировалось Serge Ageyev, 17.03.2010 в 20:47. Причина: Обновлено до версии 1.2
Ответить с цитированием
  #3 (permalink)  
Старый 16.03.2010, 22:06
Аватар для Gvozd
Матрос
Отправить личное сообщение для Gvozd Посмотреть профиль Найти все сообщения от Gvozd
 
Регистрация: 04.04.2008
Сообщений: 6,246

ну, в принципе я думаю этот скрипт окажется полезным достаточному количеству людей.
есть пара замечаний:
1)стоит обернуть скрипт в замыкание, вынеся наружу только encodeFormField(). незачем загромождать глобальную область видимости тем, что пользователь не будет непосредственно использовать
2)"внимательны" по английски пишется как careful
3)encodeFormFieldIsPageOnUTF8 невнятная какая-то
первое условие мне кажется может не сработать если скрипт подгружается на страницу, а не вшит в нее.
последующие два, мне вообще непонятны.

мне кажется гарантию дал бы способ состоящий из двух подспособов:
1)если есть meta с указанием кодировки, то используем его
2)если его нету, то делает ajax-запрос этой же страницы, для выяснения заголовков, и сохраняем кодировку в cookie.очень плохая идея, учитывая, что страница может быть и динамической, и выполнять какие-то действия при своей генерации
других идей пока нет
Ответить с цитированием
  #4 (permalink)  
Старый 16.03.2010, 22:37
Интересующийся
Отправить личное сообщение для Serge Ageyev Посмотреть профиль Найти все сообщения от Serge Ageyev
 
Регистрация: 16.03.2010
Сообщений: 12

Сообщение от Gvozd Посмотреть сообщение
1)стоит обернуть скрипт в замыкание, вынеся наружу только encodeFormField(). незачем загромождать глобальную область видимости тем, что пользователь не будет непосредственно использовать
Была такая мысль, но строка преобразования и encodeFormFieldIsPageOnUTF8() используется в unit тестах.
Впрочем, в каком-нибудь из следующих релизов скорее всего так и сделаю.

Сообщение от Gvozd Посмотреть сообщение
2)"внимательны" по английски пишется как careful
Спасибо, хорошо что остались люди читающие комментарии :-)

Сообщение от Gvozd Посмотреть сообщение
3)encodeFormFieldIsPageOnUTF8 невнятная какая-то
первое условие мне кажется может не сработать если скрипт подгружается на страницу, а не вшит в нее.
последующие два, мне вообще непонятны.
Согласен, условия выглядят странно, но они подобраны опытным путем,
исходя из того, как "ломают" строку различные браузеры при загрузке скрипта в UTF8 (так как она, не является UTF8 строкой).
Одни ломают длину, другие контент.
По большому счету, скрипт не предназдначен для не UTF-8 сайтов, это скорее emergency fallback (еще бы найти для него солющен под IE...).

Сообщение от Gvozd Посмотреть сообщение
мне кажется гарантию дал бы способ состоящий из двух подспособов:
1)если есть meta с указанием кодировки, то используем его
Так и делал, пока не подобрал условия, чем сильно упростил код :-)
Впрочем, повторюсь, скрипт не предназдначен для работы на UTF8 сайтах (ибо он там не нужен).
Сообщение от Gvozd Посмотреть сообщение
2)если его нету, то делает ajax-запрос этой же страницы, для выяснения заголовков, и сохраняем кодировку в cookie.очень плохая идея, учитывая, что страница может быть и динамической, и выполнять какие-то действия при своей генерации
других идей пока нет
Можно и так, но скрипт перестает быть self-contained, кроме того возникает куча вопросов с асинхронной загрузкой + выглядит тяжеловесно...
Ответить с цитированием
  #5 (permalink)  
Старый 16.03.2010, 23:24
Аватар для Gvozd
Матрос
Отправить личное сообщение для Gvozd Посмотреть профиль Найти все сообщения от Gvozd
 
Регистрация: 04.04.2008
Сообщений: 6,246

Сообщение от Serge Ageyev
Была такая мысль, но строка преобразования и encodeFormFieldIsPageOnUTF8() используется в unit тестах.
Впрочем, в каком-нибудь из следующих релизов скорее всего так и сделаю.
ну вынесите его также во вне замыкания.
Сообщение от Serge Ageyev
Можно и так, но скрипт перестает быть self-contained, кроме того возникает куча вопросов с асинхронной загрузкой + выглядит тяжеловесно...
я ж и говорю, что моя последняя идея на правах бреда
я глянул спецификацию юникода, и считаю что лучше для выявления UTF-8 использовать что-то по типу
var q="А";//кирилическая А в UTF-8
//в HEX выглядит как D0 90
alert(q.length);//если UTF, то 1 символ, а иное, то два

ваша строка, которую вы используете, не имеет в себе последовательностей байтов, которые могли бы интепретироватся как UTF-символ(во всяком случае я такого не вижу, руководствуясь спецификацией). точнее ваша строка с точки зрения UTF является невалидной,и условия, которые вы используете являются скорее багами, чем закономерностью, и поэтому лучше их не использовать.
также советую сделать несколько строк типа переменной q у меня, для каждой из многобайтных кодировок
Ответить с цитированием
  #6 (permalink)  
Старый 16.03.2010, 23:43
Интересующийся
Отправить личное сообщение для Serge Ageyev Посмотреть профиль Найти все сообщения от Serge Ageyev
 
Регистрация: 16.03.2010
Сообщений: 12

Сообщение от Gvozd Посмотреть сообщение
ваша строка, которую вы используете, не имеет в себе последовательностей байтов, которые могли бы интепретироватся как UTF-символ(во всяком случае я такого не вижу, руководствуясь спецификацией).
Это я знаю.
Повторюсь:
Скрипт предназдначен для сайтов, использующих любые однобайтовые кодировки.
Вся возня с UTF8 только для слуая, когда кто-то случайно его в UTF8 странице использует.
Наверное вообще стоит убрать эту часть кода, дабы неповадно было...
Ответить с цитированием
  #7 (permalink)  
Старый 17.03.2010, 00:06
Аватар для Gvozd
Матрос
Отправить личное сообщение для Gvozd Посмотреть профиль Найти все сообщения от Gvozd
 
Регистрация: 04.04.2008
Сообщений: 6,246

Сообщение от Serge Ageyev
Наверное вообще стоит убрать эту часть кода, дабы неповадно было...
можно и так
я-то просто попытался оптимизировать со своей точки зрения этот кусок код)))
больше мне дополнить по скрипту нечего
Ответить с цитированием
  #8 (permalink)  
Старый 17.03.2010, 20:52
Интересующийся
Отправить личное сообщение для Serge Ageyev Посмотреть профиль Найти все сообщения от Serge Ageyev
 
Регистрация: 16.03.2010
Сообщений: 12

Сообщение от Gvozd Посмотреть сообщение
можно и так
я-то просто попытался оптимизировать со своей точки зрения этот кусок код)))
больше мне дополнить по скрипту нечего
Спасибо за коменты, они натолкнули меня на ряд идей


Выложил версию 1.2.
Ответить с цитированием
  #9 (permalink)  
Старый 17.03.2010, 20:55
Интересующийся
Отправить личное сообщение для Serge Ageyev Посмотреть профиль Найти все сообщения от Serge Ageyev
 
Регистрация: 16.03.2010
Сообщений: 12

Выложил версию 1.2:
1. Замкнул код в объекте для миниммизации видимых глобальных объектов

2. Добавил файл encode_form_field_utf8_stub.js для работы под UTF8 (прозрачно вызывает encodeURIComponent)

3. Добавил файл encode_form_field_utf8_detect.js для детекта UTF8
(позволяет реализовать "паттерн" прозрачной миграции):
<script src="encode_form_field_utf8_detect.js"></script>
<script>
 if (encodeFormFieldIsPageOnUTF8())
  {
   document.write('<'+'script src="encode_form_field_utf8_stub.js"></'+'script>');
  }
 else 
  {
   document.write('<'+'script src="encode_form_field.js"></'+'script>');
  }
</script>
Пришлось отказаться от идеи автодетекта UTF8 кодировки, так как под IE это не работает

4. Добавил юнит тесты в архив библиотеки
Ответить с цитированием
  #10 (permalink)  
Старый 22.04.2013, 19:24
Новичок на форуме
Отправить личное сообщение для nullempty Посмотреть профиль Найти все сообщения от nullempty
 
Регистрация: 22.04.2013
Сообщений: 1

Serge Ageyev,
первые две ссылки в этой теме ведут на архив со старой (1.1) версией, пожалуйста, замените архивы и по тем ссылкам.

P.S. Спасибо за идею и реализацию!
Ответить с цитированием
Ответ



Опции темы Искать в теме
Искать в теме:

Расширенный поиск


Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Что выбрать XML или JSON для передачи Ajax - ом из PHP в JS Gozar Общие вопросы Javascript 20 16.08.2009 23:36
глюк форума Gvozd Сайт Javascript.ru 11 18.03.2009 14:37