Conversion de texte chinois en Pinyin (Complet et Initiales) sous Lazarus

L'implémentation originale de la conversion du chinois vers le Pinyin est souvent conçue pour l'environnement Delphi. Lors de l'utilisation sous Lazarus, des erreurs de conversion surviennent fréquemment en raison de la gestion des encodages de caractères (UTF-8). Le code suivant a été adapté pour fonctionner correctement sous Lazarus/FreePascal, en incluant le support pour le Pinyin complet et l'extraction des initiales.

Exemple d'utilisation dans une application

unit Main;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
  uPinyinConverter, LConvEncoding;

type
  TFormMain = class(TForm)
    btnConvert: TButton;
    inputEdit: TEdit;
    resultMemo: TMemo;
    procedure btnConvertClick(Sender: TObject);
  end;

var
  FormMain: TFormMain;

implementation

{$R *.lfm}

procedure TFormMain.btnConvertClick(Sender: TObject);
var
  strInput: string;
begin
  strInput := inputEdit.Text;
  // Ajout du Pinyin complet
  resultMemo.Lines.Add(ExtractFullPinyin(strInput));
  // Ajout des initiales uniquement
  resultMemo.Lines.Add(ExtractPinyinInitials(strInput));
end;

end.

Unité de traitement : uPinyinConverter

Cette unité contient la logique de mappage entre les codes GBK et les phonétiques Pinyin. Elle utilise une table d'indexation pour transformer les caractères chinois en leur équivalent phonétique.

unit uPinyinConverter;

{$mode objfpc}{$H+}

interface

uses
  SysUtils, Classes, LazUTF8, LConvEncoding;

const
  TOTAL_PY_ENTRIES = 405;
  
  { Table des sons de base }
  PINYIN_SOUNDS: array [1..TOTAL_PY_ENTRIES] of string[6] = (
    'a', 'ai', 'an', 'ang', 'ao', 'ba', 'bai', 'ban', 'bang', 'bao',
    'bei', 'ben', 'beng', 'bi', 'bian', 'biao', 'bie', 'bin', 'bing', 'bo',
    'bu', 'ca', 'cai', 'can', 'cang', 'cao', 'ce', 'ceng', 'cha', 'chai',
    'chan', 'chang', 'chao', 'che', 'chen', 'cheng', 'chi', 'chong', 'chou', 'chu',
    'chuai', 'chuan', 'chuang', 'chui', 'chun', 'chuo', 'ci', 'cong', 'cou', 'cu',
    'cuan', 'cui', 'cun', 'cuo', 'da', 'dai', 'dan', 'dang', 'dao', 'de',
    'deng', 'di', 'dian', 'diao', 'die', 'ding', 'diu', 'dong', 'dou', 'du',
    'duan', 'dui', 'dun', 'duo', 'e', 'en', 'er', 'fa', 'fan', 'fang',
    'fei', 'fen', 'feng', 'fu', 'fou', 'ga', 'gai', 'gan', 'gang', 'gao',
    'ge', 'ji', 'gen', 'geng', 'gong', 'gou', 'gu', 'gua', 'guai', 'guan',
    'guang', 'gui', 'gun', 'guo', 'ha', 'hai', 'han', 'hang', 'hao', 'he',
    'hei', 'hen', 'heng', 'hong', 'hou', 'hu', 'hua', 'huai', 'huan', 'huang',
    'hui', 'hun', 'huo', 'jia', 'jian', 'jiang', 'qiao', 'jiao', 'jie', 'jin',
    'jing', 'jiong', 'jiu', 'ju', 'juan', 'jue', 'jun', 'ka', 'kai', 'kan',
    'kang', 'kao', 'ke', 'ken', 'keng', 'kong', 'kou', 'ku', 'kua', 'kuai',
    'kuan', 'kuang', 'kui', 'kun', 'kuo', 'la', 'lai', 'lan', 'lang', 'lao',
    'le', 'lei', 'leng', 'li', 'lia', 'lian', 'liang', 'liao', 'lie', 'lin',
    'ling', 'liu', 'long', 'lou', 'lu', 'luan', 'lue', 'lun', 'luo', 'ma',
    'mai', 'man', 'mang', 'mao', 'me', 'mei', 'men', 'meng', 'mi', 'mian',
    'miao', 'mie', 'min', 'ming', 'miu', 'mo', 'mou', 'mu', 'na', 'nai',
    'nan', 'nang', 'nao', 'ne', 'nei', 'nen', 'neng', 'ni', 'nian', 'niang',
    'niao', 'nie', 'nin', 'ning', 'niu', 'nong', 'nu', 'nuan', 'nue', 'yao',
    'nuo', 'o', 'ou', 'pa', 'pai', 'pan', 'pang', 'pao', 'pei', 'pen',
    'peng', 'pi', 'pian', 'piao', 'pie', 'pin', 'ping', 'po', 'pou', 'pu',
    'qi', 'qia', 'qian', 'qiang', 'qie', 'qin', 'qing', 'qiong', 'qiu', 'qu',
    'quan', 'que', 'qun', 'ran', 'rang', 'rao', 're', 'ren', 'reng', 'ri',
    'rong', 'rou', 'ru', 'ruan', 'rui', 'run', 'ruo', 'sa', 'sai', 'san',
    'sang', 'sao', 'se', 'sen', 'seng', 'sha', 'shai', 'shan', 'shang', 'shao',
    'she', 'shen', 'sheng', 'shi', 'shou', 'shu', 'shua', 'shuai', 'shuan', 'shuang',
    'shui', 'shun', 'shuo', 'si', 'song', 'sou', 'su', 'suan', 'sui', 'sun',
    'suo', 'ta', 'tai', 'tan', 'tang', 'tao', 'te', 'teng', 'ti', 'tian',
    'tiao', 'tie', 'ting', 'tong', 'tou', 'tu', 'tuan', 'tui', 'tun', 'tuo',
    'wa', 'wai', 'wan', 'wang', 'wei', 'wen', 'weng', 'wo', 'wu', 'xi',
    'xia', 'xian', 'xiang', 'xiao', 'xie', 'xin', 'xing', 'xiong', 'xiu', 'xu',
    'xuan', 'xue', 'xun', 'ya', 'yan', 'yang', 'ye', 'yi', 'yin', 'ying',
    'yo', 'yong', 'you', 'yu', 'yuan', 'yue', 'yun', 'za', 'zai', 'zan',
    'zang', 'zao', 'ze', 'zei', 'zen', 'zeng', 'zha', 'zhai', 'zhan', 'zhang',
    'zhao', 'zhe', 'zhen', 'zheng', 'zhi', 'zhong', 'zhou', 'zhu', 'zhua', 'zhuai',
    'zhuan', 'zhuang', 'zhui', 'zhun', 'zhuo', 'zi', 'zong', 'zou', 'zu', 'zuan',
    'zui', 'zun', 'zuo', '', 'ei', 'm', 'n', 'dia', 'cen', 'nou',
    'jv', 'qv', 'xv', 'lv', 'nv'
  );

  { Table d'indexation (segmentée pour l'exemple) }
  PINYIN_INDEX: array [1..126, 1..191] of Word = (
    // Note: Les données volumineuses de la table originale doivent être insérées ici.
    // Structure: [Octet1 - 128, Octet2 - 63]
    (354,279,331,0,190,39,284,67,249,167,353,19,133,92,129,152,31,385,105,131,84,348,348,329,133,0,376,116,283,349,344,87,133,0,0,0,284,202,0,0,0,0,0,0,342,345,0,276,199,243,0,0,52,0,375,102,88,176,170,348,136,180,375,0,354,294,299,94,344,368,344,55,347,333,131,347,353,369,166,74,325,92,0,329,6,19,0,306,0,332,372,114,107,20,33,370,279,258,79,191,243,328,220,83,43,324,62,376,229,354,64,73,329,348,336,92,2,349,232,282,84,305,389,357,134,332,343,336,363,332,217,208,335,340,377,389,13,232,348,294,348,374,107,181,378,21,250,14,371,47,346,369,14,332,250,284,10,121,110,333,91,78,194,19,113,123,102,251,47,337,282,320,106,157,348,101,349,189,340,3,175,77,178,29,37,343,377,263,125,294,56),
    { ... les autres lignes suivent le même schéma ... }
    (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
  );

function ProcessChineseText(const AText: string; AFullPinyin: Boolean; ASeparator: Boolean): string;
function ExtractFullPinyin(const AText: string): string;
function ExtractPinyinInitials(const AText: string): string;

implementation

function ProcessChineseText(const AText: string; AFullPinyin: Boolean; ASeparator: Boolean): string;
var
  i, vIdx: Integer;
  vEncodedStr: string;
  vResult: string;
  vPinyin: string;
begin
  vResult := '';
  // Lazarus utilise UTF-8, la table utilise CP936 (GBK)
  vEncodedStr := Utf8ToCp936(AText);
  i := 1;
  
  while i <= Length(vEncodedStr) do
  begin
    // Vérification si caractère sur 2 octets (Chinois)
    if (Ord(vEncodedStr[i]) >= 129) and (i < Length(vEncodedStr)) and (Ord(vEncodedStr[i+1]) >= 64) then
    begin
      vIdx := PINYIN_INDEX[Ord(vEncodedStr[i]) - 128, Ord(vEncodedStr[i+1]) - 63];
      if vIdx > 0 then
      begin
        vPinyin := UpperCase(PINYIN_SOUNDS[vIdx]);
        if not AFullPinyin then
          vPinyin := Copy(vPinyin, 1, 1);
          
        if ASeparator and (vResult <> '') then
          vResult := vResult + ' ' + vPinyin
        else
          vResult := vResult + vPinyin;
      end;
      Inc(i, 2);
    end
    else
    begin
      // Caractère ASCII standard
      if vEncodedStr[i] in ['a'..'z', 'A'..'Z', '0'..'9'] then
        vResult := vResult + UpperCase(vEncodedStr[i]);
      Inc(i);
    end;
  end;
  Result := vResult;
end;

function ExtractFullPinyin(const AText: string): string;
begin
  Result := ProcessChineseText(AText, True, True);
end;

function ExtractPinyinInitials(const AText: string): string;
begin
  Result := ProcessChineseText(AText, False, False);
end;

end.

Le fonctionnement repose sur la conversion préalable de la chaîne UTF-8 en CP936 via Utf8ToCp936 (disponible dans LConvEncoding). L'algorithme parcourt ensuite les octets : s'il détecte une séquence de caractères chinois (octets >= 129), il calcule l'offset dans la matrice PINYIN_INDEX pour récupérer l'index de la syllabe correspondnate dans PINYIN_SOUNDS.

Étiquettes: Lazarus FreePascal Pinyin UTF8 GBK

Publié le 6 août à 12h13