OVH Cloud OVH Cloud

Locale pour lire/écrire en UTF-8

15 réponses
Avatar
Sylvain Togni
Bonjour,

J'ai essayé ça :

std::wofstream file("C:/test.txt");
file.imbue(std::locale("UTF-8"));
file << (wchar_t)0x732b;

mais le constructeur de std::locale lance une exception.

Est-ce mon système (VC++6) qui n'a pas de locale UTF-8
ou est-ce que j'utilise pas std::locale de la bonne façon ?

--
Sylvain Togni

5 réponses

1 2
Avatar
Jean-Marc Bourguet
writes:

Jean-Marc Bourguet wrote:
Samuel Krempp writes:

D'ailleurs, si on veut mettre des caractères non ascii
dans un source, le seul moyen portable est de tous les
traduire en "universal character name", non ?


C'est portables aux implémentations conformes,


Export aussi, mais tu ne me vois pas en train de dire aux gens
d'utiliser export dans du code portable.


C'est bien le point que j'essayais de faire: conformité et
portabilité sont des choses différentes, même si la
conformité est souvent une aide à la portabilité (surtout
vers le futur).

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org



Avatar
kanze
Samuel Krempp wrote:
le Thursday 21 April 2005 09:16, écrivit :

Tu as dû te gourer : uC3A0 est un caractère Hangul. Le code
correct est u00E0. (En général, les codes ISO 8859-1 sont
identique en Unicode, ce qui veut dire que les deux premiers
chiffres d'un u seront toujours 00.)


oups, comme j'avais pas de référence unicode sous la main j'ai
pensé à utiliser iconv(1) pour trouver le code-point
correspondant à 'à', et je suis tombé dans le piège "codate
UTF-8" vs "valeur unicode", que je connais pourtant..
j'ai tapé :
echo à | iconv -f latin1 -t utf8 | hexdump
au lieu de
echo à | iconv -f latin1 -t unicodebig | hexdump

C3,A0 est l'encodage UTF-8 de u00E0
c'est décidemment très facile de s'emmêler !


Maintenant que tu l'expliques, c'est évident, mais j'avoue que
je n'arrivais pas à imaginer d'où tu avais cette valeur.

le code 0x30000060 -- pas Unicode du tout), mais g++ le
rejette avec le message d'erreur « converting to execution
character set: Invalid argument » (ce qui est sont droit,
même si on pourrait en discuter au niveau de la qualité de
l'implémentation).


si le source a été tapé avec emacs, en latin-1, et que g++
fonctionne en UTF-8, il voit arriver un caractère non-valide
(0xE0) ..


Bonne rémarque. Je n'avais pas régardé un dump hex de la source,
pour voir ce que je donnais réelement au compilateur. En effet,
elle a été tappée avec emacs (plutôt que mon vim habituel).
Normalement, j'a défini mon environement pour utiliser du ISO
8859-1 (LC_CTYPE=iso_8859_1), mais je ne sais pas ce qu'en fait
emacs.

Je viens de vérifier : dans 'à', il y a un seul octet entre les
', un 0xEO.

en testant, L'à' (écrit en UTF-8) donne la valeur 0xE0, quelle
que soit la locale au moment de la compilation (comme
L'u00E0')

Tandis qu'écrit en latin1, L'à' est invalide :
"""
LANG=fr_FR.UTF-8 g++ testLocale.cpp
testLocale.cpp:31:18: converting to execution character set :
Argument

invalide
"""
et mettre LANG=fr_FR ne change rien.

CONCLUSION :
mon g++ suppose un encodage UTF-8 (à l'intérieur de wide
character literals, c'est ptet différent dans d'autres
contextes..)


Peut-être différent aussi avec d'autres options à la génération.

indépendamment de la locale en cours. (ce qui est
un choix valable, pour peu que l'utilisateur en aie conscience
!)


C'est un choix comme un autre. Le problème de base reste : le
encodage utilisé dans les sources dépend des outils et du locale
lors de la saisie. Information que le compilateur n'a pas à sa
disposition. Et qu'en plus, risque de ne pas être la même pour
les différents en-têtes qu'on inclut. À la rigueur, dire qu'il
n'y a qu'un seul encodage (et alors, UTF-8 est à peu près la
seule possibilité) se justifie -- au moins, on sait où on est,
et ce qu'il faut faire pour que mon code se compile quelque soit
l'environement. Le seul problème, c'est que jusqu'ici, les
compilateurs n'ont rien fait de spécial ; ils copiaient les
octets entre les "...", sans s'en occuper de ce qu'ils pouvaient
contenir (à part des et des "). Du coup, ceux qui travaille
dans un environement monolingue autre que l'anglais risque
d'avoir des caractères latin1 ou d'autres (selon l'environement)
dans leurs chaînes. Il faudrait éventuellement prévoir une
option pour faire comme avant.

[Le man de mon g++ (3.4) semble dire que les variables LANG,
LC_TYPE sont utilisées, mais en tout cas, pas pour ça
visiblement.. ]


Si tu configures avec nls lors de la génération du compilateur,
LANG va déterminer la langue utilisée pour les messages
diagnostiques. (Je sais que je fais exprès de configurer avec
--disable-nls quand je génère.)

Dans la pratique, la seule solution réelement fiable, c'est
de lire les messages d'un fichier externe:-(.


ouais, c'est un peu dommage..


N'est-ce pas ? Souvent, évidemment, ce n'est pas une contrainte,
pour la simple raison qu'on veut le faire de toute façon, en
choisissant le ficher d'après $LANG. Souvent, mais pas toujours.

enfin, visiblement avec g++ on doit pouvoir faire ce qu'on
veut en UTF-8, avec un peu de chance VC++ et autres peuvent
aussi comprendre des sources encodées en UTF-8 (ça serait un
comportement à la fois portable et pratique..)


Donc, peu probable:-). (VC++ ne serait pas axé UTF-16LE, par
hazard ?)

Pour les char, 'à' donne les bons résultats avec les deux
compilateurs ; CC n'accepte pas 'u00E0', et curieusement
(parce qu'il supporte manifestement les "universal character
name"), g++ donne un avertisement "warning: multi-character
character constant" et génère 0xA0 !


à l'intérieur d'un character-literal NON-wide, c'est un
warning naturel, non ?


Non. 'u00E0' n'est jamais un constante multi-character. Il n'y
a qu'un seul caractère là-dedans.

Un avertissement que le caractère donné n'avait pas de
représentation dans les caractères de base d'exécution, je
comprendrais.

g++3.4 me dit pareil, et le code (tapé en latin-1) :
cout << hex << (int) ((unsigned char) 'à') << endl;
cout << 'u00E0' << endl;

affiche [*quelle que soit* la locale, à la compil aussi bien
qu'à l'éxécution ] :
e0
[logique, g++ passe la valeur 0xE0 sans la toucher]


Sans doute pour des raisons historique ?

puis :
c3a0 [ qui est le codage UTF-8 de 'à']
il pourrait faire une erreur plutot qu'un warning, non ?


Voilà ce qui est intéressant (et un aspect auquel je n'avais pas
pensé). Évidemment, il traduit u00E0 en 'à', il suppose UTF-8,
et le résultat est un caractère composé (multi-byte, en
anglais).

Je réprends mon accusation : c'est un avertissement tout à fait
compréhensible, une fois qu'on comprends ce qui est en train de
se passer.

Quant à en faire une erreur : pour des raisons historiques, le C
et le C++ permettent des constantes de caractère avec plus d'un
caractère. La signification dépend de l'implémentation, mais un
compilateur conforme est obligé à l'accepter.

un char literal avec une telle valeur, c'est un peu bizarre :)
hmm, ça permet de mettre des caractères unicodes dans des
string literals, qui seront implicitement convertis en UTF-8
(non portable je suppose, mais eventuellement pratique)

"u00e0" -> séquence des octets correspondant à l'encodage UTF-8 de
'à'.


C'est un bon choix pour l'avenir. J'espère, en tout cas.

Ce qui est intéressant, c'est que 'à' marche à peu près
partout. Pour des raisons sans doute historique, quand il
s'agit des "..." ou des '...', les compilateurs que je
connais passent le contenu du fichier de façon
transparamment, sans trop poser de questions. Si lors des
sorties sur l'écran, le font utilise le même encodage que
lorsque j'ai édité le fichier source, tout se passe bien.
Sinon...


c'est tout à fait ma conclusion aussi.

les literals normaux (pas L'...' ou L"...") sont manipulés
tels quels, le compilateur n'a en fait aucun besoin de
s'occupper d'encodage des caractères non-ASCII.


Tout compte fait, p't-êt' ben que g++ a fait le bon choix sur le
plan pratique. Même si l'idée de interpréter les caractères
différemment dans les "..." et les L"..." semble un peu bizarre
au premier abord.

Dans des wide-character literals par contre, l'interpretation
des octets du source en caractères wides suppose de connaître
l'encodage du source, et visiblement g++ impose de les taper
en UTF-8 (ou de taper des 'universal character name', façon
u00e0)


Comme j'ai dit, c'est un choix comme un autre. AMHA, il n'existe
pas de bon choix -- celui n'est pas pire que les autres.

bon, je vais garder tout ça qque part, c'est pas la première
fois que je me pose ces questions et je n'arrive jamais à me
souvenir de la conclusion ..


Ça ne serait pas mal, une page de Web vers laquelle on pourrait
faire des liens. (En supposant que tu as le temps et la volenté
de la faire. Je sais que ce n'est pas évident.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Samuel Krempp
le Wednesday 20 April 2005 19:02, écrivit :
J'ai essayé la classe utf8_codecvt_facet de boost :

std::wofstream file("C:/test.txt");
file.imbue(std::locale(std::locale(), new utf8_codecvt_facet));
file << L"à";


au fait c'est quoi ton compilo / StdLib ?

mais ça produit un fichier de 1 octet, en ISO 8859-1 ou quelque
chose du même genre, mais pas en UTF-8.


ce serait pas 0x3f en hexa par hasard ? ('?', caractère choisi lorsqu'il
considère qu'il n'y a pas de caractère équivalent au wide-char en question
dans le jeu destination)
moi c'est ce que j'obtiens sous linux, avec g++ 3.4.

Mais ça marche, si au lieu de faire un imbue, je règle la locale globale
avant d'utiler le wide stream. c'est bizarre, à priori c'est un bug,
l'imbue devrait marcher aussi bien..

std::locale environmentLocale(""); // user's environment locale ($LANG..)
std::locale::global(environmentLocale);
std::wcout << L"_u00E0_u306f_" << std::endl ;
// u306f est un hiragana (caractère japonais) prononcé 'wa'

LANG=fr_FR.UTF-8 ./a.out | hexdump
-> le 'à' donne C3,a0 (i.e. : 'à' codé en UTF-8)
l'hiragana donne e3,81,af, qui est bien son codage UTF-8

LANG=fr_FR ./a.out | hexdump
-> le à donne 0xe0 (à en latin1)
l'hiragana donne '?' (0x3F)

LANG=ja_JP ./a.out | hexdump
-> le à donne 8f,ab,a2 (codage EUC-JP de à)
l'hiragana donne a4,cf

Qu'est-ce qui va pas ?
Quelqu'un a déjà utilisé cette classe ?


la facet boost, non, mais ça me surprendrait énormément que ce soit elle le
problème.
Si tu utilises gcc, il semble que c'est le wstream qui fait mal l'imbue.
regler la locale globale peut servir de workaround.. j'essaierai de trouver
des infos sur ce problème.

--
Sam

Avatar
Samuel Krempp
le Saturday 23 April 2005 16:01,
écrivit :
moi c'est ce que j'obtiens sous linux, avec g++ 3.4.


enfin, ça fait ça pour wcout, mais pour un wofstream imbue marche comme il
se doit.

Mais ça marche, si au lieu de faire un imbue, je règle la locale globale
avant d'utiler le wide stream. c'est bizarre, à priori c'est un bug,
l'imbue devrait marcher aussi bien..


finalement peut être qu'il y a des exceptions pour cout/wcout et que ce
comportement est conforme..


--
Sam

Avatar
Sylvain Togni
le Wednesday 20 April 2005 19:02, écrivit :

J'ai essayé la classe utf8_codecvt_facet de boost :

std::wofstream file("C:/test.txt");
file.imbue(std::locale(std::locale(), new utf8_codecvt_facet));
file << L"à";



au fait c'est quoi ton compilo / StdLib ?


VC6. En fait, je viens de trouver le problème, il faut apparemment
appeler imbue() avant d'ouvrir le flux, pas après :

std::wofstream file;
file.imbue(std::locale(std::locale(), new utf8_codecvt_facet));
file.open("C:/test.txt");
file << L"à";

fontionne correctement.

--
Sylvain Togni


1 2