Конвертация строк из UTF-8 в win1251 . Вожможно ли сохранение Диакритики?

Gremboloid

инженера Гр...
Конвертация строк из UTF-8 в win1251 . Вожможно ли сохранение Диакритики?

Доброго времени суток, форумчане.
Всем читавшим faq известно что, чтобы не было проблем с кодировкой надо использовать уникод.
Но вот столкнулся с такой проблемой:
Есть кем-то, до меня написанный, сайт. Все в кодировке win1251 (сам сайт, база на mysql). Моя задача прикрутить к нему вывод данных из новой mssql 2000 информацию о книгах. Для сохранения диакритики, разработчики все сохраняют в уникоде.
Вот и встал ежедневный вопрос "Что делать?"

Конвертнуть весь сайт в UTF не предлогать, очень накладно.

Посмотрел варианты в http://phpclub.ru/faq/wakka.php?wakka=encodings/encodings3&v=fyd и зашел в тупик:
sample 1
sample 2
sample 3
просто iconv
Как видите результат получается разный.
На проверку получается что iconv переводит правильно. Но!? Сохраняются ли при этом всякие умляюты и спец символы (для языков: немецкий, французкий), т.к. в тексте эти языки в перемешку.
Какие будут соображения?
 

kvf77

Red Devil
Gremboloid

все сохраняется, тока вопрос в том, что часто в тойже русской кодировке указывают символы, которых в ней на самом деле нет, для примера, помоему символа № нет в стандарте win1251 (могу ошибаться, но примеры есть), так что имеет смысл выявить такой набор символов и привести их в соответствие
 

Gremboloid

инженера Гр...
Интересно девки пляшут.

kvf77
Да ты прав. Залез по ссылке SiMMa, а там поглубже, докопался до таблицы символов. Действительно их нет (УЖОС).

Чем больше читаю о кодировках, тем больше запутываюсь.
в виде числа &#nnnn; которое есть код буквы (в десятичной системе) в наборе символов Unicode, например, немецкую a-umlaut на русской странице можно ввести как ä
* а русскую 'д' на немецкой странице (т.е. HTML кодировки Western) можно ввести как д

Самый простой способ найти нужное числовое значение:
o Пойти на страницу "Codepages" и найти нужную, скажем, "Cyrillic, Windows codepage 1251".
Там под каждой буквой написано её юникодовое значение - в Hex, шестнадцатеричной системе, например, под 'д' - 0434
o Теперь надо Hex 0434 в десятичную систему перевести - вызвать Калькулятор, например, Start/Run - впечатать calc
В Калькуляторе - View/Scientific. Щёлкнуть на Hex и ввести 0434. Щёлкнуть на Dec - получим нужное десятичное число - 1076.
Не могу переварить эту фразу :(
 

SiMM

Новичок
> Не могу переварить эту фразу
Какую именно? Суть идеи проста - символы однобайтной кодировки, которую ты используешь (например, CP1251) - ты пишешь как обычно. Символы, отсутствующие в этой кодировке можно писать при помощи &#код; (или &#xШестнадцатеричныйКод;), где код - код символа в Unicode.

> Конвертнуть весь сайт в UTF не предлогать, очень накладно.
Кстати, это мнение на самом деле может быть ошибочным ;)
 

Gremboloid

инженера Гр...
Суть идеи проста - символы однобайтной кодировки, которую ты используешь (например, CP1251) - ты пишешь как обычно. Символы, отсутствующие в этой кодировке можно писать при помощи &#код; (или &#xШестнадцатеричныйКод;), где код - код символа в Unicode.
Вот на этом то и застопорился.

Не могу понять этот механизм:confused:
 

SiMM

Новичок
> Не могу понять этот механизм
А в чём конкретно проблема-то? Ну не лезет в однобайтную кодировку 65536 символов :) Поэтому в HTML решили выкрутиться таким вот образом - Numeric character references
 

Gremboloid

инженера Гр...
Век живи, век учись, а дураком помру :D
Решал сегодня проблемы связанные совершенно с другим вопросом и зашел на http://ru3.php.net/imagettftext. а там оно! Пример решения проблемы.
Не долго думая немного модефицировал для своих нужд. и получилось (извините если уж большой код):
PHP:
function UTF8toWIN1251_entities($utf = '')
{
  if($utf == '' || !is_string($utf)) return($utf);

  $max_count = 5; // flag-bits in $max_mark ( 1111 1000 == 5 times 1)
  $max_mark = 248; // marker for a (theoretical ;-)) 5-byte-char and mask for a 4-byte-char;

  $html = '';
  for($str_pos = 0; $str_pos < strlen($utf); $str_pos++) {
   $old_chr = $utf{$str_pos};
   $old_val = ord( $utf{$str_pos} );
   $new_val = 0;

   $utf8_marker = 0;

   // skip non-utf-8-chars
   if( $old_val > 127 ) {
     $mark = $max_mark;
     for($byte_ctr = $max_count; $byte_ctr > 2; $byte_ctr--) {
       // actual byte is utf-8-marker?
       if( ( $old_val & $mark  ) == ( ($mark << 1) & 255 ) ) {
         $utf8_marker = $byte_ctr - 1;
         break;
       }
       $mark = ($mark << 1) & 255;
     }
   }

   // marker found: collect following bytes
   if($utf8_marker > 1 and isset( $utf{$str_pos + 1} ) ) {
     $str_off = 0;
     $new_val = $old_val & (127 >> $utf8_marker);
     for($byte_ctr = $utf8_marker; $byte_ctr > 1; $byte_ctr--) {

       // check if following chars are UTF8 additional data blocks
       // UTF8 and ord() > 127
       if( (ord($utf{$str_pos + 1}) & 192) == 128 ) {
         $new_val = $new_val << 6;
         $str_off++;
         // no need for Addition, bitwise OR is sufficient
         // 63: more UTF8-bytes; 0011 1111
         $new_val = $new_val | ( ord( $utf{$str_pos + $str_off} ) & 63 );
       }
       // no UTF8, but ord() > 127
       // nevertheless convert first char to NCE
       else {
         $new_val = $old_val;
       }
     }
     // build NCE-Code or cyrilics simbol for Win1251
     if ($new_val == 1025) { $html .= chr(168); }
     elseif ($new_val == 1105) { $html .= chr(184); }
     elseif (1040 <= $new_val and $new_val <= 1103) { $html .= chr($new_val - 848); }
     else { $html .= '&#'.$new_val.';'; }
          // Skip additional UTF-8-Bytes
     $str_pos = $str_pos + $str_off;
   }
   else {
     $html .= chr($old_val);
     $new_val = $old_val;
   }
  }
  return($html);
}
Т.е. на выходе мы получаем корректные символы ANSI и буквы русского алфавита (в Win1251 кодировке). А все остальные в их Numeric character references.
PS пытался добавить в комментарий к http://phpclub.ru/faq/wakka.php?wakka=encodings/encodings3
и что-то не так получилось. Мне стыдно :(
 

SiMM

Новичок
> и что-то не так получилось
Тэги надо было использовать ;)
%%(php)<?php
....
?>%%
Вообще задачку можно наверно и проще решить - одним регулярником. Копать модификаторы u и e ([m]reference.pcre.pattern.modifiers[/m]). Но это не принципиально :)

-~{}~ 26.10.05 09:56:

Получается что-то вроде
PHP:
function UTF82entities($str,$charset='CP1251'){ // by SiMM
  return preg_replace('#.#usei',
                      '($x=iconv("UTF-8","'.$charset.'//IGNORE","$0")) === ""
                       ? "&#".join("",unpack("v",iconv("UTF-8","UTF-16LE","$0"))).";"
                       : $x',
                      $str);
}
 

Gremboloid

инженера Гр...
Этот вариант, хороший. Но как выяснилось сравнительным тестом:
Вариант SiMM'a Выполнялся в среднем 7.809396982193 секунд.
sample 6
++ корректно переводит все символы содержащиеся в win1251
!!! Т.к. мне разработчики мне до сих пор не предоставили пополненую базу, не могу точно проверить результат :mad: на следующей неделе постараюсь запостить результат.

Вариант найденый на php.net Все обрабатывал в среднем 0.7131769657135 секунд.
sample 5
++ быстро работает. даже с большим текстом
-- не переводит из UTF-8 все символы содержащиеся в win1251. что в значительной степени увеличивает вес страницы. (нужна доводка многими напильниками)
!!! Т.к. мне разработчики мне до сих пор не предоставили пополненую базу, не могу точно проверить результат :mad: на следующей неделе постараюсь запостить результат.
Но! Если рассматривать решение задачи в рамках поставленой Задачи с некоторыми оговорками (например Книги только добавляются), то при подключении системы кеширования результатов обработки в выйгрыше получает регулярка.
Кто "в теме" (так любят говорить в шаражке "Группа Махаон"), прошу Вас выскажите свое мнение.

SiMM Про %%<?php не знал , спасибо.:D
 

SiMM

Новичок
> Вариант SiMM'a Выполнялся в среднем 7.809396982193 секунд.
Ну правильно - посимвольный перебор с посимвольным применением iconv'а - это не хухры мухры ;)
Если будет желание поразвлечься - может ещё подумаю :)

-~{}~ 26.10.05 22:34:

Вроде побыстрее :) При необходимости (использование в тексте Unicode-символов > 0xFFFF, т.е. символов, UTF8 представление которых длиннее 3х байт) можно юзать UTF-32BE со всеми вытекающими
PHP:
function UTF82entities($str,$charset='CP1251'){ // by SiMM
  static $table = array();
  $p = &$table[$charset];
  if (empty($p))
    for ($i = 0x100; $i--;)
      if ('' !== $c = iconv($charset,'UTF-16BE//IGNORE',chr($i)))
        $p[$c] = chr($i);
  $str = iconv('UTF-8','UTF-16BE',$str);
  $ret = '';
  $len = strlen($str);
  for ($i = 0; $i<$len; $i += 2)
    if (isset($p[$s = substr($str,$i,2)])) $ret .= $p[$s];
    else $ret .= '&#'.hexdec(bin2hex($s)).';';
  return $ret;
}
 

Gremboloid

инженера Гр...
Опа. Выполнялся в среднем 0.11929488182068 секунд.
sample 7
Как занесут новые данные в базу проверю на корректность.
SiMM
В любом случае (согласно расценкам утвержденным 10 июля на PHP OpenAir Party в Москве) с меня причитается $+$+...+$=34$.:eek:
Но с условием: расплачиваться буду только пивом и к пиву. и желательно в рассрочку (я много пить не могу):D
 
Сверху