OVH Cloud OVH Cloud

std::wifstream et nom de fichier unicode

10 réponses
Avatar
Sylvain Togni
Bonjour,

Pourquoi le constructeur de std::wifstream prend un char const* et pas
un wchat_t const* ? Comment faire pour ouvrir un fichier dont le nom
comporte des charactères unicodes (Chinois par exemple) ?

--
Sylvain Togni

10 réponses

Avatar
adebaene
Sylvain Togni wrote:
Bonjour,

Pourquoi le constructeur de std::wifstream prend un char const* et
pas

un wchat_t const* ?
C'est une bonne question et une defficience de la norme C++ AMHA. Il

faut reconnaitre cependant que le problème n'est pas simple : les
différents encodages, sur les différentes localisations des
différents OS, rend l'écriture d'une solution portable assez
complexe.

Comment faire pour ouvrir un fichier dont le nom
comporte des charactères unicodes (Chinois par exemple) ?
Utiliser l'API native du système pour ouvrir le fichier puis

construire ton stream par dessus, ou bien utiliser boost::filesystem
qui prend en charge ses aspects.

Arnaud

Avatar
Cyrille
Sylvain Togni wrote:

Comment faire pour ouvrir un fichier dont le nom
comporte des charactères unicodes (Chinois par exemple) ?


Utiliser l'API native du système pour ouvrir le fichier puis
construire ton stream par dessus, ou bien utiliser boost::filesystem
qui prend en charge ses aspects.


Euh je ne crois pas que Boost y puisse quelque chose, là.
Boost.Filesystem définit un fstream qui prend un boost::path en
paramètre, lequel est défini avec des char* et des string, pas des
wchar_t et des wstring... Bref, le problème est le même, non?.

J'ai récemment fait un petit programme qui parcourait toute une
arborescence de répertoires sous Windows en utilisant boost::filesystem,
et cela échouait systématiquement quand il rencontrait des fichiers ou
des répertoires dont le nom n'était pas représentable par des chars.

Bref, en-dehors de l'API native, point de salut je pense.

--
win the yes need the no to win against the no!


Avatar
Samuel Krempp
le Tuesday 19 April 2005 12:41, écrivit :

Bonjour,

Pourquoi le constructeur de std::wifstream prend un char const* et pas
un wchat_t const* ?


le contenu du fichier peut utiliser un encoding indépendamment du codage des
noms de fichiers dans le filesystem.
Ceci dit, si on s'occupe d'un wifstream, on est à priori en situation
"internationalisée", et les noms de fichiers ont des chances de l'être
aussi, c'est vrai.

j'imagine que le standard a pris en compte les plateformes où les noms de
fichiers sont juste des séquences d'octets (privés de valeurs interdites),
et ne voulaient pas forcer à implémenter un open(wchar_t const*) sur toutes
les plateformes.
Une telle fonction ne pourrait pas être bien faite à moins que la plateforme
ne fournisse ce qu'il faut. (l'implémenteur de la SL aurait du mal à
obtenir les caractéristiques du filesystem où repose le fichier, ses
paramètres de montage etc, pour convertir un wchar* en char* de manière
utile. la plateforme le pourrait éventuellement)
Il y a peut être une fonction de l'API windows pour ça, mais sous linux je
ne crois pas.

Comment faire pour ouvrir un fichier dont le nom
comporte des charactères unicodes (Chinois par exemple) ?


même par le biais de paramètres char*, il devrait être possible d'ouvrir
n'importe quel fichier : le tout est de trouver la bonne séquence de
caractères..

Si le programme qui a créé le fichier est le même que celui qui ouvre, il
suffit de choisir un mapping
unicode -> séquences de caractères
et s'y tenir. (en évitant tout caractère problematique pour le filesystem..)
a priori, on utiliserait UTF-8 pour ça.

Le VFAT, par exemple, n'a pas d'encoding "standard" des noms de fichiers
dépassant le cadre d'un des 'codepages' - 256 caractères au plus,
(contrairement aux filesystems plus récents), et dans ce cas il est
difficile de deviner le bon mapping..
j'avais testé, avec WinXP sur du VFAT, les noms de fichiers codés en UTF-8
sont bien interpretés (l'explorer les affiche correctement, on peut
rennommer, etc..).
pour le vfat sous linux, les paramètres de montage permettent de choisir la
conversions entre l'encoding du filesystem (option "codepage") et celui de
l'API filesystem linux (option "iocharset", latin1 par défaut)
J'obtiens un bonne compatibilité avec windowsXP en montant les partitions
vfat avec les options :
utf8,codepage…0
(iocharset est laissé à latin1 par défaut, mettre UTF-8 comme charset est
déconseillé à cause de problèmes obscures de minuscules/majuscules de noms
de fichiers.. à la place il faut mettre l'option utf8)

En gros :
-si la plateforme n'a pas vraiment de concept de charset et consièdre juste
des noms de fichiers comme des char* "binaires", un open(wchar *) n'a pas
de sens, mais on peut convertir soi-même un wchar* en char* (e.g. en UTF-8)
-si la plateforme a une API filesystem qui supporte les wchar, soit on
l'utilise, soit on convertit un wchar* en char* qu'on utilisera avec la
S.L. Avec un peu de chance, on peut espérer obtenir le même résultat.
(mais on peut imaginer que l'inverse se produise : que l'on ne puisse
accèder qu'à une sous-partie des noms de fichiers valides via l'interface
char*..)

--
Sam

Avatar
Samuel Krempp
le Tuesday 19 April 2005 12:41, écrivit :

Bonjour,

Pourquoi le constructeur de std::wifstream prend un char const* et pas
un wchat_t const* ?


le contenu du fichier peut utiliser un encoding indépendamment du codage des
noms de fichiers dans le filesystem.
Ceci dit, si on s'occupe d'un wifstream, on est à priori en situation
"internationalisée", et les noms de fichiers ont des chances de l'être
aussi, c'est vrai.

j'imagine que le standard a pris en compte les plateformes où les noms de
fichiers sont juste des séquences d'octets (privés de valeurs interdites),
et ne voulaient pas forcer à implémenter un open(wchar_t const*) sur toutes
les plateformes.
Une telle fonction ne pourrait pas être bien faite à moins que la plateforme
ne fournisse ce qu'il faut. (l'implémenteur de la SL aurait du mal à
obtenir les caractéristiques du filesystem où repose le fichier, ses
paramètres de montage etc, pour convertir un wchar* en char* de manière
utile. la plateforme le pourrait éventuellement)
Il y a peut être une fonction de l'API windows pour ça, mais sous linux je
ne crois pas.

Comment faire pour ouvrir un fichier dont le nom
comporte des charactères unicodes (Chinois par exemple) ?


même par le biais de paramètres char*, il devrait être possible d'ouvrir
n'importe quel fichier : le tout est de trouver la bonne séquence de
caractères..

Si le programme qui a créé le fichier est le même que celui qui ouvre, il
suffit de choisir un mapping
unicode -> séquences de caractères
et s'y tenir. (en évitant tout caractère problematique pour le filesystem..)
a priori, on utiliserait UTF-8 pour ça.

Le VFAT, par exemple, n'a pas d'encoding "standard" des noms de fichiers
dépassant le cadre d'un des 'codepages' - 256 caractères au plus,
(contrairement aux filesystems plus récents), et dans ce cas il est
difficile de deviner le bon mapping..
j'avais testé, avec WinXP sur du VFAT, les noms de fichiers codés en UTF-8
sont bien interpretés (l'explorer les affiche correctement, on peut
rennommer, etc..).
pour le vfat sous linux, les paramètres de montage permettent de choisir la
conversions entre l'encoding du filesystem (option "codepage") et celui de
l'API filesystem linux (option "iocharset", latin1 par défaut)
J'obtiens un bonne compatibilité avec windowsXP en montant les partitions
vfat avec les options :
utf8,codepage…0
(iocharset est laissé à latin1 par défaut, mettre UTF-8 comme charset est
déconseillé à cause de problèmes obscures de minuscules/majuscules de noms
de fichiers.. à la place il faut mettre l'option utf8)

En gros :
-si la plateforme n'a pas vraiment de concept de charset et consièdre juste
des noms de fichiers comme des char* "binaires", un open(wchar *) n'a pas
de sens, mais on peut convertir soi-même un wchar* en char* (e.g. en UTF-8)
-si la plateforme a une API filesystem qui supporte les wchar, soit on
l'utilise, soit on convertit un wchar* en char* qu'on utilisera avec la
S.L. Avec un peu de chance, on peut espérer obtenir le même résultat.
(mais on peut imaginer que l'inverse se produise : que via l'interface
char* on ne puisse accèder qu'à une sous-partie des noms de fichiers
valides ..)

--
Sam

Avatar
Jean-Marc Bourguet
Sylvain Togni <"sylvain.togni at visionobjects.com"> writes:

Bonjour,

Pourquoi le constructeur de std::wifstream prend un char
const* et pas un wchat_t const* ?


Parce que ça n'a pas été proposé (ou pas accepté). Si j'ai
bonne mémoire la question avait été posée sur
comp.lang.c++.moderated et Plaugher avait expliqué la
difficulté qu'il y a simplement à définir ce que ça devrait
faire.

Comment faire pour ouvrir un fichier dont le nom comporte
des charactères unicodes (Chinois par exemple) ?


Utiliser soit une API de la plateforme, soit la connaissance
que l'on a du fonctionnement de celle-ci. En plus, pour une
plateforme donnée ça peut dépendre du système de fichier
utilisé. Par exemple sous Unix et avec les systèmes de
fichiers courants (autrement dit certainement pas quand on
essaie de monter du NTFS par exemple), les noms de fichiers
sont simplement des suites d'octets avec deux valeurs qui
sont interdites: '' et '/'. Il est courant de les
interpréter dans l'encodage courant. Donc avec un encodage
UTF-8, il suffit d'utiliser le nom encodé en UTF-8. Si
l'encodage est EUC, on utilisera le nom encodé en EUC. Mais
il est à remarquer que la suite d'octet utilisée ne sera pas
la même, ce qui est un problème en soi.

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
Samuel Krempp
le Tuesday 19 April 2005 13:57, écrivit :

J'ai récemment fait un petit programme qui parcourait toute une
arborescence de répertoires sous Windows en utilisant boost::filesystem,
et cela échouait systématiquement quand il rencontrait des fichiers ou
des répertoires dont le nom n'était pas représentable par des chars.


une branche "i18n" de boost::filesystem a récemment été créée pour cela.
UTF-8, UTF-16, tout ça tout ça..

http://cvs.sourceforge.net/viewcvs.py/boost/boost/libs/filesystem/doc/?only_with_tag=i18n

on peut esperer que ça finisse par être inclu dans la prochaine release.

--
Sam

Avatar
James Kanze
wrote:
Sylvain Togni wrote:


Pourquoi le constructeur de std::wifstream prend un char
const* et pas un wchat_t const* ?



C'est une bonne question et une defficience de la norme C++
AMHA. Il faut reconnaitre cependant que le problème n'est pas
simple : les différents encodages, sur les différentes
localisations des différents OS, rend l'écriture d'une
solution portable assez complexe.


En général, il n'y a rien de portable en ce qui concerne les
noms de fichier. Même avec char const*.

Comment faire pour ouvrir un fichier dont le nom comporte des
charactères unicodes (Chinois par exemple) ?



Utiliser l'API native du système pour ouvrir le fichier puis
construire ton stream par dessus, ou bien utiliser
boost::filesystem qui prend en charge ses aspects.


Sinon, il faut voir comment la bibliothèque traite le nom. Mes
systèmes (Linux et Solaris) permettent bien les noms de fichier
en Unicode, et je n'ai pas de problème à les utiliser avec char
const*, en passant le nom en UTF-8. Si j'avais à implémenter une
bibliothèque standard pour Windows, j'imagine que j'adopteras
une solution à peu près équivalente, en interprétant le nom
comme du UTF-8. À moins que ça pose des problèmes de
compatibilité avec des programmes existants ; je ne connais pas
assez Windows pour savoir si c'est le cas ou non.

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


Avatar
James Kanze
Samuel Krempp wrote:
le Tuesday 19 April 2005 12:41, écrivit :


Pourquoi le constructeur de std::wifstream prend un char
const* et pas un wchat_t const* ?



le contenu du fichier peut utiliser un encoding indépendamment
du codage des noms de fichiers dans le filesystem. Ceci dit,
si on s'occupe d'un wifstream, on est à priori en situation
"internationalisée", et les noms de fichiers ont des chances
de l'être aussi, c'est vrai.


Sans chercher à te contradire, je crois que le problème est un
peu plus complexe. D'abord, quelque soit l'instantiation de
basic_filebuf, le trasfert des données de et vers le système est
spécifié en termes de char -- un wfilebuf lit des char's, qu'il
rassemble et qu'il transcode en wchar_t ; à l'écriture, il
transcode des wchar_t en char avant de les écrire. À la base, le
système des entrées/sorties du C++ est fortement orienté char
(c-à-d octet, dans la pratique).

Ça s'explique facilement si on se rend compte que le C++ vise
une certaine portabilité, et que le char, c'est la seule chose
portable -- déjà, c'est la seule chose qui marche de façon
transparente à travers le reseau. Et, bien que certains systèmes
ont pris un autre chemin (pour des raisons pas forcément
mauvaises), aujourd'hui, c'est clair que l'Unicode, ça s'exprime
par l'UTF-8 sur le reseau (et n'oublions pas que la plupart des
accès « disque » sont en fait des requêtes sur un reseau).

j'imagine que le standard a pris en compte les plateformes où
les noms de fichiers sont juste des séquences d'octets (privés
de valeurs interdites), et ne voulaient pas forcer à
implémenter un open(wchar_t const*) sur toutes les
plateformes.


Ce n'est pas tellement ça. C'est que la norme ne dit rien sur ce
qui est un nom de fichier valable. Je sais bien implémenter une
bibliothèque avec des noms de fichiers en wchar_t sous Unix, et
je saurais aussi bien implémenter une bibliothèque avec les noms
de fichiers en char qui permet l'accès à tous les fichiers
Windows. Le problème, ce n'est pas l'implémentation, c'est la
définition de la sémantique. (Ceci dit, la norme aurait aussi
bien pû permettre les noms en wchar_t. Après tout, même en char,
l'interprétation du nom dépend du système, et j'ai bien
travaillé sur des systèmes où quelque chose comme 'x.y.z' était
illégal.)

Le point important, à mon avis, est exactement ce que tu as
dis : l'encodage du contenu et l'encodage du nom sont
indépendant l'un de l'autre. On a imbue pour pouvoir spécifier
l'encodage du contenu, mais pour l'instant, on n'a rien pour
pouvoir spécifier l'encodage du nom.

Une telle fonction ne pourrait pas être bien faite à moins que
la plateforme ne fournisse ce qu'il faut. (l'implémenteur de
la SL aurait du mal à obtenir les caractéristiques du
filesystem où repose le fichier, ses paramètres de montage
etc, pour convertir un wchar* en char* de manière utile. la
plateforme le pourrait éventuellement) Il y a peut être une
fonction de l'API windows pour ça, mais sous linux je ne crois
pas.


Je ne suis pas sûr pour Linux, mais pour Solaris et les Open
System, c'est clair (et je crois que ça vaut pour NFS aussi).
L'interprétation des caractères qu'on lit dépend de
l'application qui les lit. La seule contrainte, c'est que
certains codes (dont 0x00 et 0x2F) ne doivent pas apparaître
dans des caractères composés.

En fait, j'ai déjà expérimenté sous Linux, et le nom apparent du
fichier change bien selon le locale. (Nominalement, sous Linux,
les noms de fichiers sont en UTF-8. Mais dans la pratique,
j'imagine qu'il y a beaucoup de monde comme moi qui les
interprète comme s'ils étaient ISO 8859-1. La norme Posix
interdit explicitement à ce que le système rejette des noms qui
comporte des caractères illégaux dans UTF-8, et tout ce que je
vois m'amène à croire que Linux est conforme à Posix à cet
égard.)

Comment faire pour ouvrir un fichier dont le nom comporte des
charactères unicodes (Chinois par exemple) ?



même par le biais de paramètres char*, il devrait être
possible d'ouvrir n'importe quel fichier : le tout est de
trouver la bonne séquence de caractères..


:-). En somme, tu démandes que la bibliothèque documente son
comportement. Et quoi encore ? :-)

Si le programme qui a créé le fichier est le même que celui
qui ouvre, il suffit de choisir un mapping
unicode -> séquences de caractères
et s'y tenir. (en évitant tout caractère problematique pour le
filesystem..) a priori, on utiliserait UTF-8 pour ça.


Je crois que tu connais la différence, et que tu simplifies
exprès, mais je crois que c'est mieux d'être précis. L'Unicode
définit surtout un mapping entre des caractères et des valeurs
entières dans l'intervale 0 à 0x10FFFF (et potentiellement
jusqu'à 0x7FFFFFFF) -- des valeurs « 32 bits ». Il définit
ensuite des « représentation » de ces valeurs. Quand on parle
d'une représentation sur un seul entier, l'encodage s'appelle
UCS-4, ou UCS-2 pour le sous-ensemble du premier 65356 éléments.
Ensuite, il définit des représentations sur des éléments de 8 ou
de 16 bits : UTF-8 et UTF-16, respectivement. Mais même là,
quand il s'agit de UTF-16, il reconnaît que le monde est 8 bits,
et distingue entre UTF-16BE et UTF-16LE pour les transpositions
des codes 16 bits en deux octets.

On remarque donc que pour l'Unicode, on a déjà cinq ou six
représentations possible. Actuellement, autant que je sache,
Linux et Windows supporte Unicode plus ou moins officiellement,
Linux avec UTF-8, et Windows avec UTF-16 sur 16 bits. (Ce qui ne
doit pas aller sans poser de problèmes avec des fichiers sur
reseau, quand les machines sur le reseau n'ont pas le même ordre
d'octets dans le mot. Mais en général, je crois que Window et
reseau ne marche pas bien ensemble.)

Le VFAT, par exemple, n'a pas d'encoding "standard" des noms
de fichiers dépassant le cadre d'un des 'codepages' - 256
caractères au plus, (contrairement aux filesystems plus
récents), et dans ce cas il est difficile de deviner le bon
mapping..


j'avais testé, avec WinXP sur du VFAT, les noms de fichiers
codés en UTF-8 sont bien interpretés (l'explorer les affiche
correctement, on peut rennommer, etc..).
pour le vfat sous linux, les paramètres de montage permettent
de choisir la conversions entre l'encoding du filesystem
(option "codepage") et celui de l'API filesystem linux (option
"iocharset", latin1 par défaut) J'obtiens un bonne
compatibilité avec windowsXP en montant les partitions vfat
avec les options :
utf8,codepage…0
(iocharset est laissé à latin1 par défaut, mettre UTF-8 comme
charset est déconseillé à cause de problèmes obscures de
minuscules/majuscules de noms de fichiers.. à la place il faut
mettre l'option utf8)


Je n'ai pas tout compris là-dessus, mais j'ai l'impression que
tu as expérimenté sous Windows le même phénomène que je connais
sous Unix -- le monde est huit bits, et l'interprétation de ces
huit bits varie d'un système, voire même d'une application à
l'autre.

En gros :
-si la plateforme n'a pas vraiment de concept de charset et
consièdre juste des noms de fichiers comme des char*
"binaires", un open(wchar *) n'a pas de sens, mais on peut
convertir soi-même un wchar* en char* (e.g. en UTF-8)


-si la plateforme a une API filesystem qui supporte les wchar,
soit on l'utilise, soit on convertit un wchar* en char* qu'on
utilisera avec la S.L. Avec un peu de chance, on peut espérer
obtenir le même résultat. (mais on peut imaginer que l'inverse
se produise : que via l'interface char* on ne puisse accèder
qu'à une sous-partie des noms de fichiers valides ..)


Le problème, c'est que le nom de fichier n'est pas borné au
système. On est encore aux débuts, mais je vois bien un filebuf
qui accepte des noms du genre
« http://www.xyz.fr/~kanze/quelquechose ». Alors, qu'importe les
conventions du système sur lequel tourne le programme.

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


Avatar
Fabien LE LEZ
On Tue, 19 Apr 2005 21:36:12 +0200, James Kanze :

je ne connais pas
assez Windows pour savoir si c'est le cas ou non.


En fait, sous Windows, il y a un truc intéressant : tout fichier a un
nom "normal" (celui affiché par l'explorateur) et un nom "court"
(celui vu par les programmes en 16 bits). Ce nom court ne contient
généralement que des caractères ASCII (de 33 à 127).
Exemple : le fichier "Arrête tes conneries.txt" a pour nom court
ARRTET~1.TXT.

E:>echo "Hello World" > "Arrête tes conneries.txt"

E:>type ARRTET~1.TXT
"Hello World"

E:>echo Deuxième ligne >> ARRTET~1.TXT

E:>type "Arrête tes conneries.txt"
"Hello World"
Deuxième ligne



--
;-)

Avatar
Sylvain Togni

En fait, sous Windows, il y a un truc intéressant : tout fichier a un
nom "normal" (celui affiché par l'explorateur) et un nom "court"
(celui vu par les programmes en 16 bits). Ce nom court ne contient
généralement que des caractères ASCII (de 33 à 127).
Exemple : le fichier "Arrête tes conneries.txt" a pour nom court
ARRTET~1.TXT.


Bien vu. C'est apparemment comme ça qu'est implémenté le futur
Boost.FileSystem i18n sous Windows.

Avec quelques difficultés quand même, car si le fichier n'existe
pas, il faut le créer, recupérer son nom court, puis l'effacer.

Du coup, j'ai préféré utiliser une autre solution, qui ne doit
guère fonctionner qu'avec VC++, mais bon, ça me suffit :

std::wfilebuf buffer(_wfopen(filename, L"rb"));
std::wistream stream(&buffer);

Merci à tous pour les pistes.

--
Sylvain Togni