вторник, 16 ноября 2010 г.

Что, чёрт возьми, не так с TranslateCharsetInfo?

Это перевод What the hell is wrong with TranslateCharsetInfo, anyway? Автор: Майкл Каплан.

Однажды у Колина появился клиент с вопросом о неожиданном поведении функции TranslateCharsetInfo:
У моего клиента есть проблемы с API TranslateCharsetInfo. Они указывают флаг TCI_SRCLOCALE для использования LCID как источника:
fResult := TranslateCharsetInfo(langid, csi, TCI_SRCLOCALE);
Они обнаружили, что результаты этого вызова не всегда соответствуют значениям, указанным в http://www.microsoft.com/globaldev/nlsweb/default.mspx

В частности:
LCID $0C04 – Chinese (Hong Kong S.A.R.) - возвращает кодовую страницу 936 вместо ожидаемой 950
LCID $0004 - Chinese (Simplified) – возвращает кодовую страницу 950 вместо ожидаемой 936

То, что я вижу - это ожидаемое поведение или нет? Если нет, то какой есть другой способ?

Мы также попробовали:
GetLocaleInfo(langid, LOCALE_IDEFAULTANSICODEPAGE, szLocaleData, SizeOf(szLocaleData)/SizeOf(Char));
Хотя этот способ возвращает 950 для LCID $0C04 (уже лучше), но LCID $0004 всё ещё возвращает 950 вместо 936.

Или я что-то упускаю?

Many thanks,
Colin
Результаты двух значений LCID на самом деле вызваны двумя совершенно различными причинами.

В обоих случаях GDI зависит от информации, возвращаемой GetLocaleInfo с значением LOCALE_FONTSIGNATURE для LCTYPE. Другими словами, GDI зависит от NLS.

В одном случае тот факт, что .NET Framework рассматривает $0004 как "Simplfied Chinese", даже хотя вот уже несколько версий Windows обрабатывают нейтральный LCID как имеющий неявный SUBLANG_DEFAULT, делая его $0404 (Chinese - Taiwan, традиционная китайская локаль), может быть рассмотрен как недостаток дизайна NLS, который был исправлен только в Vista (через функцию ConvertDefaultLocale, которая управляет логикой всех NLS API функций, пропускающих через себя LCID.

Другими словами, это был баг, который больше не воспроизводится в Vista :-)

Теперь насчёт второй половины вопроса. Давайте используем секретный парсинг LOCALESIGNATURE функции CultureAndRegionInfoBuilder с простым кодом вроде такого:
using System;
using System.Globalization;
namespace LDML {
    class LDML {
        [STAThread]
        static void Main(string[] args) {
            CultureAndRegionInfoBuilder carib = new CultureAndRegionInfoBuilder(args[0], CultureAndRegionModifiers.Replacement);
            carib.LoadDataFromCultureInfo(new CultureInfo(args[0], false));
            carib.LoadDataFromRegionInfo(new RegionInfo(args[0]));
            carib.Save(args[0] + ".ldml");
        }
    }
}
Тогда, если вы сохраните этот код в файл ldml.cs и скомпилируете его:
csc ldml.cs /r:sysglobl.dll
то вы можете сохранить LDML, который использует zh-HK ($0C04), и посмотреть на разметку:
      <msLocale:fontSignature>
        <msLocale:unicodeRanges>
          <mslocale:range type="0">
          <mslocale:range type="1">
          <mslocale:range type="2">
          <mslocale:range type="3">
          <mslocale:range type="5">
          <mslocale:range type="7">
          <mslocale:range type="9">
          <mslocale:range type="31">
          <mslocale:range type="35">
          <mslocale:range type="36">
          <mslocale:range type="37">
          <mslocale:range type="38">
          <mslocale:range type="39">
          <mslocale:range type="42">
          <mslocale:range type="43">
          <mslocale:range type="45">
          <mslocale:range type="46">
          <mslocale:range type="48">
          <mslocale:range type="49">
          <mslocale:range type="50">
          <mslocale:range type="51">
          <mslocale:range type="54">
          <mslocale:range type="59">
          <mslocale:range type="60">
          <mslocale:range type="68">
        </msLocale:unicodeRanges>
        <msLocale:defaultCodePages>
          <msLocale:ansiCodePage />
          <msLocale:ansiOemCodePage>
            <msLocale:codePage type="936" />
          </msLocale:ansiOemCodePage>
          <msLocale:oemCodePage />
        </msLocale:defaultCodePages>
        <msLocale:codePages>
          <msLocale:ansiCodePage />
          <msLocale:ansiOemCodePage>
            <msLocale:codePage type="936" />
          </msLocale:ansiOemCodePage>
          <msLocale:oemCodePage />
        </msLocale:codePages>
      </msLocale:fontSignature>
Обратите особое внимание на кодовую страницу, которую имеет LOCALESIGNATURE (отмечено КРАСНЫМ). Так что, технически, это не вина GDI (возвращение неверной информации), потому что GDI просто использовал данные NLS.

Хотя, снова технически, вы не можете винить и NLS, потому что все данные LOCALESIGNATURE предоставляются нам командой типографии. Когда-нибудь мы можем захотеть от них обновление, такое чтобы GetLocaleInfo смогла возвращать согласованные результаты от LOCALE_IDEFAULTANSICODEPAGE, LOCALE_IDEFAULTCODEPAGE и LOCALE_FONTSIGNATURE...

Хотя, как это исправить - это интересный вопрос.

В теории, учитывая увеличивающееся в последние годы использование упрощённого китайского в Гонконге, было бы интересным изменение кодовой страницы по-умолчанию LOCALESIGNATURE на 950, но указание обеих страниц 936 и 950 в секции кодовых страниц. С описательной точи зрения в этом есть смысл.

Однако на практике raison d'être LOCALESIGNATURE - предоставлять информацию для создания имеющего смысл шрифта по-умолчанию для локали. И, вообще говоря, у кого-нибудь может не быть шрифтов, поддерживающих обе кодовые страницы. Так что лучшее исправление, вероятно, просто сделать так, чтобы LOCALESIGNATURE совпадала бы с локалью, в которой она сидит...

This post brought to you by (U+1789, KHMER LETTER NYO)

4 комментария:

  1. *шёпотом, забившись в угол*
    - Я вижу русские буквы в url!.. :)

    P.S. Из rss в конец url опять кракозябры дописываются...

    ОтветитьУдалить
  2. Да, я вот тоже заметил :D

    P.S. А что там с RSS?

    ОтветитьУдалить
  3. > А что там с RSS?

    Ссылка, открытая из читалки Feedreader 3.14 на эту статью выглядит так. Раньше тоже был мусор, потом пропал, теперь опять вернулся.

    ОтветитьУдалить
  4. А, это нормально. Так должно быть. Это отслеживаются заходы по RSS в FeedBurner.

    Если я ничего не путаю, конечно.

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.