воскресенье, 21 ноября 2010 г.

Несколько подсказок для CompareString

Это перевод A few of the gotchas of CompareString. Автор: Майкл Каплан.

CompareString - это одна из самых клёвых функций. Я думал так и до того, как стал её владельцем, и даже до того, как я встретил людей, которые раньше были её владельцами (или женщину, которая её написала).

Но, как и любая другая API функция, CompareString имеет свои подводные камни.

Итак, если вы подумаете о всей NLS информации как об огромной базе данных, то идентификатор локали (a.k.a. LCID) - это первичный ключ в ней. Это самый первый параметр к CompareString.

Если вы вызываете не-Unicode версию (CompareStringA), то вместо того, чтобы конвертировать строку с использованием кодовой страницы системы по-умолчанию, функция конвертирует параметры, используя кодовую страницу по-умолчанию этой локали. Помимо других вещей, из этого также следует, что вы не можете использовать CompareStringA для обработки строк в UTF-8.

Окей, давайте теперь посмотрим на самый важный параметр: второй, который с флагами. Я опишу каждый из флагов:
NORM_IGNORECASE - игнорировать регистр. Ну, лучшим именем для этого флага было бы IGNORE_TERTIARYWEIGHT, поскольку именно это он и делает (маскирует третичные веса), хотя, очевидно, уже слишком поздно вносить такое изменение. Этот флаг может вызвать нежелательные результаты, когда его используют при сравнении строк, которые зависят от этого веса - что, к счастью, происходит не так уж часто. Но если вы не ожидаете, что "ʏ", "Y" и "y" (U+028f, U+0059 и U+0079, a.k.a. LATIN LETTER SMALL CAPITAL Y, LATIN LETTER CAPITAL Y и LATIN LETTER SMALL Y) окажутся равными, то вам лучше бы дважды подумать, прежде чем указывать этот флаг. Вы также потеряете отличия между конечными буквами для иврита (т.е. "מ" и "ם", U+05de и U+05dd a.k.a. HEBREW LETTER MEM и HEBREW LETTER FINAL MEM), арабского (т.е. "ش" U+0634 a.k.a. ARABIC LETTER SHEEN и его изолированной, конечной, начальной и медиальной формами (ﺵ, ﺶ, ﺷ и ﺸ a.k.a. U+feb5, U+feb6, U+feb7 и U+feb8) и других языков.

NORM_IGNORENONSPACE - игнорировать непробельные (nonspacing) символы. И снова, лучшим названием флага было бы IGNORE_SECONDARYWEIGHT, поскольку именно это он и делает (маскирует вторичный вес). Этот флаг может вызвать нежелательные результаты, когда его используют при сравнении строк, содержащих символы, зависящие от этого веса. Самый яркий пример можно увидеть в корейском, где слог U+ac00 (가, Hangul Syllable Kiyeok A) может рассматриваться равным всем этим символам: 伽 佳 假 價 加 可 呵 哥 嘉 嫁 家 暇 架 枷 柯 歌 珂 痂 稼 苛 茄 街 袈 訶 賈 跏 軻 迦 駕 仮 傢 咖 哿 坷 宊 斝 榎 檟 珈 笳 耞 舸 葭 謌. Для остальных слогов в хангыле, это будет где-то лучше, где-то хуже. Но проблема также существует и в некоторых других языках.

NORM_IGNORESYMBOLS - игнорировать символы вроде "_", "#" и "*". Список символов "увеличивается", когда указывается SORT_STRINGSORT, поскольку пунктуация также рассматривается как символы. Это иногда бывает полезно, но также создаст хаос, если вы ищете строки в тексте типа исходника C++ или C#.

SORT_STRINGSORT - рассматривать пунктуацию наравне с символами. К примеру, сортировка типа STRING рассматривает 'co-op' и 'co_op' как строки, сортируемые вместе, потому что и дефис и подчёркивание оба рассматриваются как символы. С другой стороны, сортировка WORD рассматривает дефис и апостроф по-разному, поэтому 'co-op' и 'co_op' не будут расположены вместе, но зато 'co-op' и 'coop' будут рядом. Это документировано в заголовочном файле winnls.h:
//
//  Sorting Flags.
//
//    WORD Sort:    culturally correct sort
//                  hyphen and apostrophe are special cased
//                  example: "coop" and "co-op" will sort together in a list
//
//                        co_op     <-------  underscore (symbol)
//                        coat
//                        comb
//                        coop
//                        co-op     <-------  hyphen (punctuation)
//                        cork
//                        went
//                        were
//                        we're     <-------  apostrophe (punctuation)
//
//
//    STRING Sort:  hyphen and apostrophe will sort with all other symbols
//
//                        co-op     <-------  hyphen (punctuation)
//                        co_op     <-------  underscore (symbol)
//                        coat
//                        comb
//                        coop
//                        cork
//                        we're     <-------  apostrophe (punctuation)
//                        went
//                        were
//

NORM_IGNOREKANATYPE - не делать различий между хираганой и катаканой. Соответствующие символы хираганы и катаканы рассматриваются равными (т.е. "げ" U+3052 HIRAGANA LETTER GE и "ゲ" U+30B2 KATAKANA LETTER GE). Аналогичный эффект имеет и вызов LCMapString с флагом LCMAP_HIRAGANA или LCMAP_KATAKANA для обеих строк. Есть много случаев, когда это отличие важно.

NORM_IGNOREWIDTH - не делать различий между полу- и полноширинными вариантами одного и того же символа. Эти две формы символов существуют в Unicode из-за обратной совместимости с устаревшими стандартами CJK, которые кодировались в две формы. В этих старых стандартах половинная форма использовала один байт, а полноширинная - два байта. Кроме того, сам символ рисовался в два раза шире (например, "ヲ" U+30F2 KATAKANA LETTER WO и "ヲ", U+FF66 HALFWIDTH KATAKANA LETTER WO). Этот же эффект имеет вызов LCMapString с LCMAP_FULLWIDTH или LCMAP_HALFWIDTH для обеих строк. Вообще говоря, есть интересные случаи, когда какой-либо вариант используется для украшательства или функциональность. Т.е. хотя изначальная причина для этих двух форм была в стандартах, то сегодня эти формы используются более практично. К примеру, свойства в японском Access выводятся полноширинными, а их описания - в половинную ширину, т.к. это лучше выглядит визуально.
Ну а третий и пятый параметры функции CompareString - это сами строки для сравнения.

Наконец, четвёртый и шестой параметры указывают длины соответствующих строк в символах.

Итак, для практического использования вам нужно: использовать осмысленные строки, у которых определены веса в таблицах Windows. Тогда у вас будут получаться лингвистически осмысленные результаты. Когда же вы выходите за эти рамки, то вы (или ваши пользователи) получаете результаты, которые вы не ожидаете. И, как указывает информация о флагах, безразборное употребление флагов - это крайне плохая идея, которая приведёт к не интуитивным результатам.

А что будет интуитивным? В моём понимании лучше всего смотрится такой подход:
  1. Указание потенциально опасных флагов, что даст вам какой-то результат поиска.
  2. Повторный вызов функции, но уже без флагов, чтобы получить меньший и более конкретный список.
  3. Используя эти "предпочтительные результаты" из пункта 2, приоритезировать пункт 1.
Мы можем назвать это принципом "Google" - большой список поиска не впечатляет, потому что очень много требуется просмотреть, но сгруппируй наиболее подходящие результаты в начале - и вам редко потребуется просматривать полный список. Я крайне рекомендую такой подход, совмещённый с вопросами версионности, которые я уже обсуждал.

Итак, есть ещё несколько вопросов, которые я мог бы обсудить, но они могут подождать до другого раза...

This post brought to you by "ʏ" (U+028f, a.k.a. LATIN LETTER SMALL CAPITAL Y)

Комментариев нет:

Отправить комментарий

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

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

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

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

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