Неправильное преобразование Unicode, как хранить символы ударения в исходном коде Delphi 2010 и обрабатывать наборы символов? - PullRequest
7 голосов
/ 27 июля 2010

Мы обновляем наш проект с Delphi 2006 до Delphi 2010. Старый код был:

InputText: string;
InputText := SomeTEditComponent.Text;
...
for i := 1 to length(InputText) do
if InputText[i] in ['0'..'9', 'a'..'z', 'Ř' { and more special characters } ] then ...

Проблема с буквами акцента - сравнение не удастся.

Я попытался переключить исходный код сANSI для UTF8 и LE UCS-2, но без удачи.Используется только в том случае, если AnsiChar работает:

if CharInSet(AnsiChar(InputText[i]), ['0'..'9', 'a'..'z', 'Ř']) then

Забавно, как Delphi работает с этими буквами - попробуйте это в Evaluate во время отладки:

Ord('Ř') = Ord('Ø')

(да, Delphi говорит True, в Windows 7Чешский)


Вопрос: Как я могу хранить и сравнивать простые строки, не используя их как AnsiStrings?Потому что, если это не работает, почему мы должны использовать Unicode?

Спасибо всем за ответ

Прямо сейчас мы используем в некоторых частях простой CharInSet (AnsiChar (...

)

Ответы [ 5 ]

5 голосов
/ 27 июля 2010

Декларация CharInSet равна

function CharInSet(C: AnsiChar; const CharSet: TSysCharSet): Boolean; overload; inline;
function CharInSet(C: WideChar; const CharSet: TSysCharSet): Boolean; overload; inline;

пока TSysCharSet равен

TSysCharSet = set of AnsiChar;

Таким образом, CharInSet может сравниваться только с набором AnsiChar. Вот почему ваш акцентированный персонаж конвертируется в AnsiChar.

Нет эквивалента set of WideChar, поскольку наборы ограничены 256 элементами. Вы должны реализовать некоторые другие средства для проверки персонажа.

Что-то вроде

const
  specials: string = 'Ř';

if CharInSet(InputText[i], ['0'..'9', 'a'..'z']) or (Pos(InputText[I], specials) > 0) then 

может быть попыткой. Вы можете добавить больше символов к specials по мере необходимости.

4 голосов
/ 27 июля 2010

Не полагайтесь на кодировку файлов исходного кода Delphi.

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

Лучшим способом является указание ваших символов в виде 4-значного кода Unicode.

const
  MyEuroSign = #$20AC;

См. Также мою публикацию в блоге об этом.

2 голосов
/ 27 июля 2010

Как упомянул Уве Раабе, проблема с символами Unicode заключается в том, что они довольно большие.Если бы Delphi позволил вам создать «набор символов», он был бы размером 8 Кб!«Набор AnsiChar» имеет размер всего 32 байта, довольно управляемый.

Я хотел бы предложить несколько альтернатив.Во-первых, это своего рода замена для функции CharInSet, которая использует массив CHAR для выполнения тестов.Единственная заслуга в том, что он может быть вызван сразу практически из любой точки мира, но его преимущества на этом заканчиваются.Я бы избежал этого, если бы мог:

function UnicodeCharInSet(UniChr:Char; CharArray:array of Char):Boolean;
var i:Integer;
begin
  for i:=0 to High(CharArray) do
    if CharArray[i] = UniChr then
    begin
      Result := True;
      Exit;
    end;
  Result := False;
end;

Проблема с этой функцией в том, что она не обрабатывает синтаксис x in ['a'..'z'] и работает медленно!Альтернативы более быстрые, но не настолько близки к замене, как хотелось бы.Первый набор исследуемых альтернатив - это строковые функции от Microsoft. Среди них есть IsCharAlpha и IsCharAlphanumeric, они могут решить множество проблем.Проблема с этими, все "альфа" символы одинаковы: у вас могут получиться действительные альфа-символы в неанглийских и не чешских языках.В качестве альтернативы вы можете использовать класс TCharacter из Embarcadero - реализация полностью в модуле Character.pas, и он выглядит эффективным, я понятия не имею, насколько эффективна реализация Microsoft.альтернатива - написать свои собственные функции, используя оператор case, чтобы заставить вещи работать.Вот пример:

function UnicodeCharIs(UniChr:Char):Boolean;
var i:Integer;
begin
  case UniChr of
    'ă': Result := True;
    'ş': Result := False;
    'Ă': Result := True;
    'Ş': Result := False;
    else Result := False;
  end;
end;

Я проверил ассемблер, сгенерированный для этой функции.В то время как Delphi должен реализовать ряд условий «если» для этого, он делает это очень эффективно, намного лучше, чем реализация серии операторов IF из кода.Но это может привести к значительным улучшениям.

Для тестов, которые используют ALOT, вы можете захотеть найти реализацию на основе битовых масок.

1 голос
/ 28 июля 2010

Вы наткнулись на случай, когда идиома из Pre-Unicode Pascal не должна переводиться напрямую в наиболее визуально схожую идиому в паскале эпохи Unicode.

Во-первых, давайте разберемся со строковыми литералами Unicode.Если вы всегда можете быть уверены, что у вас никогда не будет тела, которое когда-либо будет использовать ваш исходный код с каким-либо инструментом, который может испортить ваши кодировки, тогда вы можете использовать литералы Unicode.Лично я не хотел бы видеть кодовые точки Unicode в строковых литералах в каком-либо из моего кода, по разным причинам, самая веская причина в том, что мой код может быть пересмотрен для интернационализации в какой-то момент, и иметь литералы, которые принадлежат вашему локальному языкуПроникновение в ваш код - еще большая проблема, когда вы используете язык, отличный от тех, которые используют простые символы кодовой страницы Ascii / Ansi.Ваш исходный код будет более читабельным, если вы будете иметь в виду, что ваши акцентированные символы и даже неакцентированные литералы символов будут лучше объявлены, как говорит Джерун, чтобы объявить их в разделе const, вдали от вашего фактического места вкод, который вы используете.

Рассмотрим случай, когда вы используете один и тот же строковый литерал тридцать три раза по всему коду.Почему это должно повторяться вместо константы?И даже если он используется только один раз, не станет ли код более читабельным, если вы объявите вменяемое имя константы?

Итак, сначала вы должны объявить константы, как он показывает.

Во-вторых, функция CharInSet устарела для всех видов использования, кроме тех, для которых она предназначена, для которых вы должны продолжать использоватьНаборы "АнсиЧар".Этот подход больше не рекомендуется в Delphi 2009/2010, и использование массивов буквенных символов Юникода в вашем постоянном разделе было бы более читабельным и более современным.

Я предлагаю вам использоватьJCL StrContainsChars работает и избегает наборов символов, так как вы не можете вообще объявить встроенный набор символов Unicode, язык не позволяет этого.Вместо этого используйте это, и обязательно прокомментируйте это:

implementation
uses
   JclStrings;

    const
       myChar1 = #$2001;
       myChar2 = #$2002;
       myChar3 = #$2003;
       myMatchList1 : Array[0..2] of Char = (myChar1,myChar2,myChar3);




function Match(s:String):Boolean;
begin
        result := StrContainsChars( s, myMatchList1,false);

end;

Строковые и символьные литералы плохи, если их код перебирается, особенно символьные или числовые литералы, которые называются «магическими значениями» и их следует избегать.

PS Ваше утверждение отладки показывает, что Ord ('?') Спокойно понижает символ Юникода до символа байтового размера AnsiChar в отладчике.Это поведение является неожиданным и, вероятно, должно войти в систему QC.

1 голос
/ 27 июля 2010

Вы должны либо использовать IF вместо IN, либо найти реализацию WideCharSet. Это может помочь, если у вас много наборов: http://code.google.com/p/delphilhlplib/source/browse/trunk/Library/src/Extensions/DeHL.WideCharSet.pas.

...