accueil — articles — à propos — contact

fichiers textes

Les fichiers textes « pleins » forment un sous-ensemble des fichiers (chaines finies d'octets). Ils se différencient par leur contenu uniquement composé d'octets ou de groupes d'octets pertinents pour l'humain, c'est-à-dire destinés à la lecture (action de lire, de prendre connaissance du contenu écrit) sans traitement important. À l'opposé, on retrouve les autres fichiers. Par exemple, un fichier MP3 n'est pas un fichier destiné à la lecture. Aussi, un document d'une suite bureautique, comme Microsoft Word, est éventuellement destiné à la lecture mais demande un traitement important, puisque le texte est formaté par un logiciel spécialisé. Les fichiers qui ne sont pas textes sont souvent appelés « fichiers binaires », ce que nous voyons, dans un contexte informatique moderne, comme un pléonasme : tous les fichiers sont « binaires ».

Maintenant, les fichiers textes sont utilisés partout ! Si vous programmez, peu importe le langage (non ésotérique), vos fichiers sources sont des fichiers textes ; sans format, ils sont beaucoup plus faciles et rapides à traiter par un éventuel compilateur ou interpréteur. Les pages Web sont également des fichiers textes, ainsi qu'une grande majorité de fichiers de configurations sur les systèmes Unix ou *nix. Bien qu'ils constituent des fichiers assez primitifs, certains aspects valent la peine d'être approchés. En particulier, le traitement des fichiers textes est susceptible d'occasionner beaucoup, beaucoup d'erreurs et d'incompatibilités (vous avez probablement déjà connu une page Web où l'encodage était ambigu, ce qui a pour effet d'afficher des caractères impertinents). Les sujets couverts par cet article serviront entre autres à mieux gérer celles-ci. Ce dernier couvre donc l'encodage des caractères et les caractères de contrôle typiques. Une base de langage C est requise pour certaines explications. Le lecteur devrait également avoir accès à un éditeur texte muni de plus de fonctionnalités que ceux qui viennent par défaut avec les systèmes d'exploitation (comme le « Bloc-Note » (ou Notepad) de Windows (notepad.exe), ou même gedit sur Linux). Nous cherchons un éditeur texte qui peut modifier l'encodage des caractères, réencoder un fichier avec un autre schéma, afficher les caractères de contrôle et qui supporte plusieurs types de nouvelles lignes. Nous suggérons Notepad2 ou Notepad++ pour Windows et Geany pour Linux.

1 la longue histoire d'ASCII

Selon Wikipedia, l'ASCII est un schéma d'encodage de caractères dont la première version date de 1963. Basé sur l'ordre logique de l'alphabet anglais, ce code est devenu, au fil du temps, le véritable standard pour encoder un message textuel en données informatiques. À l'époque, le nombre de bits requis pour transmettre un message était particulièrement important. Plus de bits à transmettre demandent effectivement plus de temps, ou autant de temps avec de l'équipement plus évolué, qui demande donc plus d'argent. Le code propose ainsi une association entre chaque caractère et un mot de 7 bits. Les machines de l'époque qui enregistraient les données à l'aide d'octets assignaient habituellement 0 au huitième.


Table ASCII de 1968

Comme l'ASCII n'était pas seulement voué à enregistrer des données, mais surtout à les transmettre (et ce à plusieurs types de machines), on retrouve également 32 codes de contrôle (0x00 à 0x1f). Ceux-ci pouvaient être utilisés pour dicter à un téléscripteur, par exemple, d'effectuer un retour de chariot ou d'accepter la fin d'une réception. Dans la pratique moderne, plusieurs de ces caractères sont désuets. La dernière section couvre l'utilisation contemporaine de certains d'entre eux.

L'ASCII, avec le temps, a vu ses défauts apparaitre. Son plus gros : se limiter à 7 bits qui encodent seulement un sous-ensemble des caractères latins. Ainsi, que faire en France ou en Suède, par exemple, pays où non seulement tous les caractères d'ASCII sont utilisables, mais également plusieurs autres ? Pire encore : que faire en Chine ou en Afghanistan, où le nombre de glyphes différents peut dépasser le millier ? Inévitablement, il faut attribuer plus de bits au code. Ce n'était certainement pas la priorité dans les années 1960, mais le problème devait éventuellement faire surface. La deuxième section explique quelques solutions.

2 encodage des caractères

Nous avons déjà croisé l'ASCII en tant que schéma d'encodage de caractères. Observons-le avec un exemple. Nous allons créer un fichier texte avec « ASCII » comme encodage. Dans une représentation hexadécimale d'un fichier, on met souvent le décalage (offset) à gauche, le contenu hexadécimal au centre et l'équivalent ASCII du contenu à droite. Seuls les caractères allant de 0x20 à 0x7e sont affichés. Nous créons donc un fichier avec comme seul contenu la chaine Bonjour vous!. En observant son contenu, nous obtenons

          0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f     0123456789abcdef
0000000   42 6f 6e 6a 6f 75 72 20 76 6f 75 73 21             Bonjour vous!...
Fichier texte encodé avec ASCII

Il est évident, ici, que chaque caractère représenté est associé à son octet. Les octets sont tous de valeur inférieure à 0x80, puisque le code ne demande que 7 bits.


Concept d'un message en communication

Il est maintenant important de rappeler le concept d'un message, en communication : ensemble de signes transmis par un canal, impliquant un codage par l'émetteur et un décodage par le récepteur. Lorsque deux personnes conversent, des idées sont échangées à tour de rôle. Celles-ci sont alors codées en message (des sons grâce à la voix, mais aussi des intonations, des gestes et d'autres subtilités non verbales), puis ce dernier est envoyé par l'émetteur. Il est ensuite décodé par le récepteur, interprété en idées qui correspondent à ceux de l'émetteur si le code est bien établi. Dans cet exemple, un code mal fixé correspond à une communication dans une langue naturelle que les deux personnes concernées ne maitrisent pas autant. Ou encore, certaines différences culturelles peuvent mener à une mauvaise interprétation des gestes effectués au cours de la conversation.

Les encodages de caractères en informatique ne sont pas exempts de ce principe, c'est-à-dire que le récepteur doit autant précisément connaitre le code que l'émetteur, sans quoi l'interprétation du message ne sera pas exacte et le résultat sera biaisé. Alors, lorsque nous parlons du code ASCII, par exemple, il faut comprendre qu'un fichier texte encodé grâce à ce schéma n'est que lisible là où il y a un décodeur approprié. La géométrie du glyphe A, à titre d'exemple, n'est pas transmise par le message. La seule information reçue est une séquence de caractères. Le récepteur se charge lui-même d'interpréter ces données.

2.1 encodages fixes sur 8 bits

Nous avons vu que le code ASCII est défini sur 7 bits. Maintenant, avec un bit de plus, nous avons le droit à 128 caractères additionnels ! C'est de ce bit que profitent différents encodages régionnaux basés sur l'ASCII. « Régionnaux » réfère ici au fait que l'extension de 128 caractères soit fondée sur les besoins de différentes régions. Les régions du monde qui utilisent des caractères latins « de l'ouest », comme la France, la Suède, le Québec et le Portugal, peuvent faire appel à l'encodage ISO-8859-1. Ce dernier étend donc l'ASCII pour rendre disponibles un total de 191 caractères du script latin. Dans la même veine, ISO-8859-15 assure une plus grande couverture des caractères latins, et Windows-1252 (aussi appelé « ANSI ») est compatible ISO-8859-1, mais ajoute plus de caractères dans la plage 0x80—0x9f qu'ISO-8859-1 n'utilise pas. Avec les éditeurs de fichiers textes modernes, créer un document avec l'encodage ANSI, Windows-1252 ou ISO-8859-1 revient à créer un document compatible ASCII avec une extension latine.

Nous avons mentionné trois encodages sur 8 bits qui étendent l'ASCII avec une panoplie de caractères latins. Il faut toutefois comprendre qu'une longue liste d'encodages est disponible. Entre autres, ISO-8859-5 couvre l'alphabet cyrillique et ISO-8859-7 l'alphabet grec. Le contexte est donc important afin qu'un logiciel sache quel code utiliser pour décoder un fichier texte muni d'un encodage fixe sur 8 bits. À l'ouverture d'un document dont le contenu est écrit en russe, et a donc été encodé avec ISO-8859-5, la majorité des éditeurs de fichiers textes afficheront un contenu déformé en assumant ISO-8859-1 ou Windows-1252. ISO-8859-1 est effectivement souvent choisi par défaut puisqu'il s'agit du plus populaire. La seule façon de bien visualiser le fichier texte est alors de sélectionner manuellement ISO-8859-5 en tant qu'encodage actuel. Le texte affiché prend alors la forme désirée par l'auteur puisque les mêmes octets sont désormais associés à d'autres glyphes. Les pages Web peuvent avoir le même problème, mais plusieurs mécanismes permettent de l'éviter, dont une balise HTML <meta> figurant dans l'en-tête de la page :

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-5" />

Observons maintenant le résultat hexadécimal de l'écriture des chaines allô à vous! avec ISO-8859-1 et 34 пожаловать? avec ISO-8859-5.

          0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f     0123456789abcdef
0000000   61 6c 6c f4 20 e0 20 76 6f 75 73 21                all. . vous!....
Fichier texte encodé avec ISO-8859-1
          0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f     0123456789abcdef
0000000   33 34 20 df de d6 d0 db de d2 d0 e2 ec 3f          34 ..........?..
Fichier texte encodé avec ISO-8859-5

On remarque les caractères qui appartiennent à la plage ASCII. La chaine 34 пожаловать? utilise bel et bien les caractères ASCII 3 (0x33), 4 (0x34),   (0x20) et ? (0x3f).

Enfin, une dernière extension d'ASCII sur 8 bits fixe vaut la peine d'être mentionnée : Code page 437. Cet encodage date de 1981 et inclut différents symboles « internationaux » (principalement latins) en plus de glyphes graphiques permettant de construire, sur un terminal textuel, un semblant d'interface graphique. Ainsi, des caractères tels que des coins () et des barres () permettent de tracer des contours, tandis que d'autres comme des rectangles pleins () peuvent faire du remplissage (par exemple, pour une barre de progrès). C'est l'encodage original de l'IBM PC, de MS-DOS et de la plupart des puces compatibles VGA (c'est donc l'encodage utilisé pour afficher le texte au démarrage d'un PC moderne). La « console » Windows (cmd.exe ou COMMAND.COM) utilise également cet encodage par défaut, que nous pouvons vérifier en écrivant le code suivant et en l'exécutant sur celle-ci :

#include <stdlib.h>
#include <stdio.h>

int main(void) {
	printf("\xc9\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xbb\n"
		"\xba bonjour! \x01 \xba\n"
		"\xc8\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xbc\n");
		
	return EXIT_SUCCESS;
}

Démonstration de l'utilisation de Code page 437 par la console Windows

Bref, les extensions d'ASCII sur 8 bits apportent une solution à son ensemble limité de caractères. Cela dit, rien ne permet de déterminer à tout coup l'extension à utiliser pour décoder un fichier texte, puisque ceci dépend de la région et du contexte. Une solution universelle serait intéressante, solution idéaliste qui consisterait en un code capable d'encoder et de décoder tous les glyphes au monde. Voici Unicode.

2.2 Unicode — présentation

Unicode est né précisément de l'importante limitation des encodages sur 8 bits, en 1987, alors qu'un employé de Xerox et un autre d'Apple ont décidé d'investiguer la possibilité d'un encodage de caractères universel. Son mendat est l'unification des différents encodages en un seul, multilingue. Pour ce faire, une organisation (l'Unicode Consortium) s'assure d'assigner, au fil du temps et des révisions d'Unicode, un code numérique et un nom anglais unique à chaque glyphe des scripts vivants et morts. Différents symboles (mathématiques, musicaux et logiques, entre autres) sont également inclus. Si le principe semble facile, il n'en demeure pas moins que la tâche est ardue pour l'organisation. Un nouvel encodage de caractères peut effectivement prendre plusieurs années avant d'être adopté correctement, puisque chaque logiciel et librairie doivent s'adapter. De plus, les changements apportés au standard Unicode doivent respecter une certaine rétrocompatibilité. L'organisation ne peut pas modifier le code de n'importe quel caractère d'une version à l'autre, sans quoi l'ensemble des logiciels qui font usage d'Unicode seraient très instables.

Pour ce qui est de l'association, la version actuelle d'Unicode est divisée en 17 plages de 0x10000 (65 536) caractères. La plus intéressante est la première (plage 0), appelée « Basic Multilingual Plane » (BMP), dont l'objectif est l'unification des encodages de caractères précédents et la couverture des scripts vivants. Dans la BMP, on retrouve entre autres l'ASCII de 0x0000 à 0x007f, puis la partie non-ASCII d'ISO-8859-1 de 0x0080 à 0x00ff.

Si ce n'est pas déjà remarqué, la BMP à elle seule assigne chaque caractère à 16 bits, et Unicode au complet chaque caractère à 24 bits. Afin de représenter ces codes, Unicode rend disponibles plusieurs schémas d'encodage sous l'appellation UTF. Nous observons ici UTF-16 et UTF-8.

2.3 UTF-16

Le schéma d'encodage UTF-16 permet d'encoder tous les caractères d'Unicode (même ceux en dehors de la BMP) sur 16 bits et plus. En fait, celui-ci vient d'un plus vieux schéma, UCS-2, qui ne permettait que d'encoder la BMP sur 16 bits fixes (nous nous rappelons que la BMP est une plage d'Unicode, et que chacune des 17 plages mesure 16 bits et peut donc totaliser 65 536 caractères). Avec le besoin de ne pas être confiné à encoder le seul contenu de la BMP, UTF-16 est né.

UTF-16 utilise un mécanisme particulier pour faire en sorte qu'une « extension » d'encodage soit détectée, c'est-à-dire pour signifier au décodeur que le caractère est plutôt encodé sur deux mots de 16 bits (donc 32 bits). Ceci est nécessaire lorsqu'un caractère en dehors de la BMP doit être encodé. Le mécanisme fait usage d'une portion non utilisée de la BMP (intervalle 0xd800—0xdfff) afin d'annoncer que les 16 bits suivants font partie du même caractère. Le décodeur doit donc, pour chaque caractère de 16 bits lu, vérifier cette portion afin de s'assurer que les prochains 16 bits appartiennent ou non à un autre caractère. Comme nous nous intéressons ici particulièrement à la BMP, qui contient la majorité des glyphes connus, nous n'approcherons pas davantage le fonctionnement de ce processus.

En se limitant à la BMP, donc, le schéma n'est pas plus compliqué à comprendre qu'il ne le faut. Chaque caractère est encodé sur 16 bits, selon leur association au sein d'Unicode, et un texte de n caractères mesure donc 2n octets. Ceci dit, lorsque la donnée atomique d'un fichier n'est plus l'octet tel que dans les fichiers textes encodés sur 8 bits fixes (ASCII, ISO-8859-1 et ainsi de suite), le boutisme (endianness), c'est-à-dire l'ordre des octets selon leurs significations, entre en jeu. Effectivement que le mot de 16 bits 0xc23b, où l'octet le plus significatif est ici 0xc2, peut être sérialisé dans un fichier comme c23b (gros-boutiste, big-endian, c'est-à-dire « gros bout en premier ») ou 3bc2 (petit-boutiste, little-endian, ou « petit bout en premier »). Un fichier texte encodé avec UTF-16 peut donc être petit-boutiste ou gros-boutiste ; l'encodage se nomme alors respectivement UTF-16LE et UTF-16BE. Le standard UTF-16 spécifie donc réellement trois sous-encodages : les deux derniers mentionnés, puis UTF-16. Si l'encodage d'un fichier ou d'un flux de texte plein est connu comme étant encodé avec UTF-16, la séquence de caractères doit absolument être précédée d'un BOM (expliqué au paragraphe suivant) afin de spécifier le boutisme au décodeur. Mais s'il est connu comme étant petit-boutiste ou gros-boutiste, alors l'ordre des octets est implicite et le BOM n'est pas nécessaire.

Le BOM d'Unicode est simplement un caractère spécifique de la BMP, 0xfeff (ZERO WIDTH NO-BREAK SPACE), qui sert, lorsque placé au début d'un fichier texte ou d'un flux encodé avec UTF-16, à spécifier le boutisme du fichier. Utilisé ailleurs qu'au tout début d'un fichier/flux UTF, le caractère 0xfeff est désuet et dénote une espace non sécable de largeur nulle. Étant donné que la valeur, 0xfeff, est déjà connue par le décodeur, il est possible de déterminer le boutisme du fichier/flux en la comparant au BOM. La chaine feff indique donc un fichier/flux gros-boutiste et fffe un petit-boutiste. Sans cette information, un décodeur n'est pas en mesure de deviner le boutisme d'une chaine UTF-16 puisque, grosso modo, toutes les permutations des 16 bits sont assignées à un caractère spécifique. En ouvrant un fichier UTF-16LE ou UTF-16BE sans BOM avec geany, par exemple, une erreur nous est retournée, comme quoi le fichier texte n'est pas reconnu. Puis, en ayant pris soin d'ajouter le BOM (n'importe quel éditeur texte avancé offre le choix de l'inclure ou non au fichier), le fichier s'ouvre à merveille.

Regardons maintenant quelques exemples hexadécimaux de fichiers textes encodés avec UTF-16. Les fichiers contiennent la seule chaine mangé → huh-₠. La flèche () vers la droite est le caractère Unicode 0x2192 et le signe de devise européenne () 0x20a0.

          0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f     0123456789abcdef
0000000   6d 00 61 00 6e 00 67 00 e9 00 20 00 92 21 20 00    m.a.n.g... ..! .
0000010   68 00 75 00 68 00 2d 00 a0 20                      h.u.h.-.. ......
Fichier texte encodé avec UTF-16LE sans BOM

Plusieurs faits peuvent être remarqués de ce dernier fichier. D'abord, on voit que les caractères utilisés qui font partie d'ASCII, puisque les 128 premiers caractères de la BMP correspondent. Le petit-boutisme est également facilement visible. Enfin, l'accent est mis sur la flèche vers la droite (0x2192) qui, puisque composée de l'octet 0x21 correspondant au caractère ASCII !, fait afficher ce caractère dans la représentation ASCII à droite. L'ouverture de ce fichier avec un éditeur texte qui ne prend pas en charge Unicode affichera donc un ! et possiblement plusieurs caractères étranges. Voyons maintenant la même chaine, mais encodée avec UTF-16BE avec BOM.

          0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f     0123456789abcdef
0000000   fe ff 00 6d 00 61 00 6e 00 67 00 e9 00 20 21 92    ...m.a.n.g... !.
0000010   00 20 00 68 00 75 00 68 00 2d 20 a0                . .h.u.h.- .....
Fichier texte encodé avec UTF-16BE avec BOM

On remarque bien le BOM, 0xfeff, qui annonce l'encodage gros-boutiste du fichier. Le programme simple suivant, en langage C, permet de déterminer si un fichier possède un BOM, et, si c'est le cas, si le fichier est petit-boutiste ou gros-boutiste :

#include <stdlib.h>
#include <stdio.h>

#define BOM_LE	0
#define BOM_BE	1
#define BOM_NO	2

int utf16_bom(const char* filename) {
	unsigned char buf [2];
	FILE* fh;
	
	fh = fopen(filename, "rb");	// Ouverture en lecture seule binaire.
	if (fh != NULL) {
		fread(buf, 1, 2, fh);	// Lecture des deux premiers octets.
		fclose(fh);
		if (buf[0] == 0xfe && buf[1] == 0xff) {		// 0xfeff.
			return BOM_BE;
		} else if (buf[0] == 0xff && buf[1] == 0xfe) {	// 0xfffe.
			return BOM_LE;
		} else {
			return BOM_NO;
		}
	} else {
		return -1;
	}
}

int main(int argc, char* argv []) {
	if (argc != 2) {
		fprintf(stderr, "Le programme attend un argument : "
			"le chemin du fichier à vérifier.\n");
		return EXIT_FAILURE;
	}
	switch (utf16_bom(argv[1])) {
		case BOM_NO:
		printf("Aucun BOM trouvé.\n");
		break;
		
		case BOM_LE:
		printf("BOM présent : petit-boutiste.\n");
		break;
		
		case BOM_BE:
		printf("BOM présent : gros-boutiste.\n");
		break;
		
		default:
		fprintf(stderr, "Erreur de lecture du fichier.\n");
		return EXIT_FAILURE;
	}
	
	return EXIT_SUCCESS;
}

2.4 UTF-8


Schéma d'encodage UTF-8

L'encodage UTF-8 est probablement le plus populaire aujourd'hui, entre autres grâce au fait qu'il est 100 % compatible ASCII. Autrement dit, il n'y a aucune différence entre un fichier texte encodé avec l'ASCII et le même réencodé avec UTF-8. Cet exploit est possible grâce au fait que le schéma soit de longueur variable. Un caractère peut donc s'écrire sur un octet (dans quel cas ce caractère est élément d'ASCII), ou sur deux, trois ou quatre octets. Cette sous-section décrit le concept.

Sans donner d'explications trop détaillées, l'illustration à gauche montre assez bien le schéma UTF-8.

Autrement dit, le nombre de 1 plus significatifs du premier octet montrent sur combien d'octets s'étire le caractère, à moins qu'il n'en prenne qu'un. Aussi, il est facile de montrer que, en se fiant sur la disposition des bits variables au sein des octets, les 128 premiers caractères (0x0000—0x007f) s'encodent sur un octet, les caractères 0x0080—0x07ff sur deux, les caractères 0x0800—0xffff sur trois et les caractères 0x10000—0x10fffff sur quatre. Les caractères de la BMP peuvent ainsi toujours être encodés sur 3 octets maximum.

UTF-8 permet dans plusieurs cas de sauver sur la taille d'un fichier ou d'un flux face à UTF-16, puisque l'utilisation de caractères de l'ASCII ne requièrent qu'un octet chaque. Aussi, une bonne partie de la BMP s'encode sur 2 octets, tout comme avec UTF-16 (donc, à tout le moins, aucune perte), et comprend entre autres les caractères d'ISO-8859-1. Également, UTF-8 ne nécessite pas de BOM puisque le standard précise déjà un ordre bien défini.

Observons la même chaine (mangé → huh-₠) que nous avons déjà encodée avec UTF-16, mais cette fois-ci encodée avec UTF-8 :

          0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f     0123456789abcdef
0000000   6d 61 6e 67 c3 a9 20 e2 86 92 20 68 75 68 2d e2    mang.. ... huh-.
0000010   82 a0                                              ................
Fichier texte encodé avec UTF-8

On voit immédiatement que les quatre premiers caractères sont encodés sur un seul octet puisqu'ils sont consécutivement plus petit que 0x80. Puis, le 0xc de 0xc3 (donc 0b1100) spécifie un caractère sur deux octets, donc 0xc3a9. En se fiant à l'illustration précédente et aux bits variables, on trouve le caractère 0xe9, c'est-à-dire le code Unicode de é. La partie accentuée du fichier pointe un caractère de trois octets, soit la flèche vers la droite, puisque 0x2192 est plus grand que 0x07ff et plus petit que 0x10000. Notez que ce fichier texte UTF-8 occupe 18 octets tandis que son équivalent UTF-16BE avec BOM en prend 28.

Le programme suivant, en langage C, lit un fichier texte UTF-8 valide et liste, pour chaque indice de caractère, sur combien d'octets le caractère est encodé.

#include <stdlib.h>
#include <stdio.h>

int utf8_list(const char* filename) {
	unsigned char buf [4];
	int c;
	unsigned int index = 0, cur;
	FILE* fh;
	
	fh = fopen(filename, "rb");	// Ouverture en lecture seule binaire.
	if (fh != NULL) {
		c = getc(fh);
		while (c != EOF) {
			if ((c >> 7) == 0) {		// Forme 0xxxxxxx.
				cur = 1;
			} else if ((c >> 5) == 6) {	// Forme 110yyyxx.
				cur = 2;
			} else if ((c >> 4) == 0xe) {	// Forme 1110yyyy.
				cur = 3;
			} else if ((c >> 3) == 0x1e) {	// Forme 11110zzz.
				cur = 4;
			}
			fseek(fh, cur - 1, SEEK_CUR);	// Sauter les 10******.
			printf("Caractère %u encodé sur %u octet%s.\n",
				index, cur, (cur > 1 ? "s" : ""));
			++index;
			c = getc(fh);
		}
		fclose(fh);
	} else {
		return -1;
	}
}

int main(int argc, char* argv []) {
	if (argc != 2) {
		fprintf(stderr, "Le programme attend un argument : "
			"le chemin du fichier à vérifier.\n");
		return EXIT_FAILURE;
	}
	if (utf8_list(argv[1]) == -1) {
		fprintf(stderr, "Erreur de lecture du fichier.\n");
		return EXIT_FAILURE;
	}
	
	return EXIT_SUCCESS;
}

Le résultat de l'exécution de ce code sur notre fichier contenant la chaine mangé → huh-₠ mène au résultat

$ ./utf-8-list utf-8
Caractère 0 encodé sur 1 octet.
Caractère 1 encodé sur 1 octet.
Caractère 2 encodé sur 1 octet.
Caractère 3 encodé sur 1 octet.
Caractère 4 encodé sur 2 octets.
Caractère 5 encodé sur 1 octet.
Caractère 6 encodé sur 3 octets.
Caractère 7 encodé sur 1 octet.
Caractère 8 encodé sur 1 octet.
Caractère 9 encodé sur 1 octet.
Caractère 10 encodé sur 1 octet.
Caractère 11 encodé sur 1 octet.
Caractère 12 encodé sur 3 octets.

Bien que le BOM ne soit pas nécessaire avec UTF-8, on le retrouve parfois en début de fichier, suite à un mauvais réencodage d'un fichier encodé avec UTF-16. Tout réencodage de UTF-16 à UTF-8 devrait au minimum s'assurer de retirer le BOM s'il existe. Ainsi, certains éditeurs textes qui essaient de détecter automatiquement l'encodage peuvent interpréter le fichier UTF-8 avec BOM comme étant un simple fichier ISO-8859-1, ce qui affichera comme premiers caractères  (puisque le BOM s'encode sur trois octets avec UTF-8 : 0xefbbbf). C'est le cas, entre autres, de Notepad sur Windows.

3 caractères de contrôle

Par caractère de contrôle, nous entendons ici un caractère admissible dans un fichier texte, mais invisible directement. L'exemple le plus concret est probablement le ou les caractères de nouvelle ligne, bien approfondis dans cette section. L'ASCII définit 32 caractères de contrôle (0x00—0x1e), et Unicode, en plus de ceux-ci, en ajoute davantage. Nous nous intéressons ici particulièrement aux caractères de l'ASCII, qui sont les plus importants et les mieux connus depuis maintenant plusieurs décennies. Parmi ces 32 caractères (dont chacun a un acronyme de deux ou trois caractères), un petit sous-ensemble seulement est encore pertinent pour les fichiers textes modernes. Auparavant, tel que décrit dans la section sur l'ASCII, plusieurs types de périphériques utilisaient directement les caractères de contrôle. À titre d'exemple, les caractères STX (0x02) et ETX (0x03) pouvaient être utilisés pour signaler à une imprimante le début et la fin d'un texte à imprimer dans une série de textes différents sérialisés en une seule transmission. La transmission totale, quant à elle, pouvait se terminer avec EOT (0x04).

3.1 la nouvelle ligne démystifiée

Une nouvelle ligne est le passage du curseur d'écriture de texte plein à gauche complètement, et une rangée de caractères plus bas. L'analogie avec les vieilles machines à écrire est directe : lorsque nous désirons changer de ligne (créer une nouvelle ligne), nous appuyons sur le levier à gauche du cylindre vers la droite, lequel fait avancer le chariot vers la droite complètement (donc le curseur, lui fixe et relatif au chariot, « vers la gauche »), puis, en continuant à appuyer une fois le chariot bloqué, la force appliquée sur le levier active un mécanisme qui fait tourner le cylindre d'un angle ajustable pour passer à la ligne suivante. On retrouve donc deux actions concrètes : un retour de chariot et une alimentation ou présentation de ligne. Les deux caractères de contrôle qui correspondent à ces actions sont CR (carriage return, 0x0d) et LF (line feed, 0x0a).

De tous les systèmes populaires (Unix, Windows, Mac OS), les Unix et *nix sont probablement ceux qui utilisent le plus efficacement ces caractères. Mais regardons d'abord les deux autres.

La plupart des logiciels purement « Windows » (ou DOS) qui ont à traiter ou lire des fichiers textes assument le patron de nouvelle ligne sur Windows, c'est-à-dire CR suivi de LF (0x0d0a, souvent appelé CRLF). Chaque nouvelle ligne demande donc cette chaine de deux octets. Le peu de fichiers de configurations textes produits par Windows (puisque la plupart des paramètres du système se trouvent dans un registre binaire) adoptent ce schéma. C'est également le seul schéma disponible avec Notepad de Windows, ainsi qu'avec MS-DOS Editor.

Mac OS, jusqu'à sa version 9 inclusivement, fait usage de CR seulement en guise de nouvelle ligne. C'est également le cas de l'Apple II. Mac OS X, quant à lui, est un système Unix (dérivé de Mac OS X Server, lui-même éventuellement dérivé de BSD), et adopte donc le patron de nouvelle ligne des systèmes Unix.

Enfin, les systèmes Unix et *nix se fient seulement sur LF comme nouvelle ligne. Ces systèmes incluent entre autres Unix, GNU/Linux, FreeBSD, Mac OS X et BeOS.

Il est important de comprendre que le système n'a aucun poids sur la liberté d'une application à produire un fichier texte avec n'importe quel type de nouvelle ligne. Seulement, les trois différentes méthodes décrivent des attentes pour les logiciels implémentés sur chacun de ces systèmes. À titre d'exemple, un fichier texte produit avec gedit sur Linux et muni de nouvelles lignes s'affichera de façon étrange sur Notepad (Windows) ; les caractères de nouvelle ligne LF prendront généralement la forme de petits rectangles vides plutôt que de produire le comportement voulu de sauter une ligne. Toutefois, l'ouverture du même fichier avec Notepad2, un éditeur texte alternatif pour Windows qui reconnait plusieurs types de nouvelle ligne, affichera le résultat attendu. Ainsi, certains logiciels devinent le type de nouvelle ligne et peuvent bien utiliser le fichier (les fureteurs sont un bon exemple, puisque les pages Web sont autant produites sur des systèmes Windows que Unix), puis d'autres en attendent un en particulier selon le système.

Lors de l'utilisation d'un client FTP, on voit souvent les modes de transfert « ASCII » et « binaire ». Si le mode binaire transfert un fichier tel quel, c'est que l'autre mode transforme le fichier en fonction du système l'accueillant. Si nous envoyons un fichier texte avec des nouvelles lignes de type Windows à partir de Windows vers une machine Unix ou *nix, alors, en mode ASCII, le fichier stocké sur le serveur comportera des nouvelles lignes de type Unix. Le cas des modes « texte » et « binaire » lors de l'ouverture d'un fichier avec plusieurs langages de programmation est analogue. Lorsqu'un fichier est ouvert en mode « texte », l'écriture d'une nouvelle ligne (habituellement avec la séquence \n) vers celui-ci produira le bon schéma en fonction du système qui compile/exécute, augmentant ainsi la portabilité du code. Dans le cas contraire, la séquence \n signifie réellement LF (0x0a). L'exécution du code suivant sur Windows démontre ce concept.

#include <stdlib.h>
#include <stdio.h>

int main(void) {
	char text [] = "bonjour\nvous!\n";
	
	FILE* fht = fopen("out_text.txt", "w"); // Mode « texte ».
	FILE* fhb = fopen("out_bin.txt", "wb"); // Mode « binaire ».
	fprintf(fht, "%s", text);
	fprintf(fhb, "%s", text);
	fclose(fht);
	fclose(fhb);
	
	return EXIT_SUCCESS;
}

En examinant les deux fichiers obtenus, nous avons

          0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f     0123456789abcdef
0000000   62 6f 6e 6a 6f 75 72 0d 0a 76 6f 75 73 21 0d 0a    bonjour..vous!..
Fichier texte obtenu avec le mode « texte »
          0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f     0123456789abcdef
0000000   62 6f 6e 6a 6f 75 72 0a 76 6f 75 73 21 0a          bonjour.vous!...
Fichier texte obtenu avec le mode « binaire »

Mais ces deux caractères, CR et LF, peuvent être utilisés indépendamment dans certains contextes, dont le cas particulier de l'affichage sur un terminal. Effectivement, lors de l'affichage sur une console, dont la console Windows, le caractère LF produit toujours un saut de ligne et le caractère CR (habituellement la séquence \r) a pour effet de ramener le curseur à gauche complètement (telle qu'une machine à écrire) sans sauter de ligne. Ceci permet entre autres de mettre une ligne à jour en évitant le défilement du contenu du terminal. Le code suivant exécuté sur Linux aide à visualiser le tout.

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main(void) {
	unsigned int i;
	
	for (i = 1; i < 100; ++i) {
		printf("%u\r", i);
		fflush(stdout);
		usleep(250000);
		if (i % 5 == 0) {
			printf("\n");
		}
	}
	
	return EXIT_SUCCESS;
}

Notre introduction aux flux standards permet ici de comprendre la fonction fflush, mais celle-ci n'a aucun lien avec le propos des fichiers textes.

3.2 tabulations

Lors de l'analyse hexadécimale d'un fichier texte, on trouve également régulièrement l'octet ASCII 0x09 : TAB. Comme l'histoire des tabulations est d'intérêt négligeable, l'important est de comprendre que l'utilisation moderne prescrit à ce caractère un rôle d'indentation sur une colonne (dans le cas d'une tabulation horizontale) multiple d'un entier lors de l'affichage d'un fichier texte plein en comportant. Généralement, cet entier est 8. Cela dit, il est tout à fait possible de le fixer à un nombre arbitraire (4 est souvent utilisé). Le mendat de ce caractère de contrôle est donc de remplacer une succession de caractères d'espacement afin de sauver de l'espace disque et d'être plus efficace lors de l'édition.

À titre d'exemple, voici le contenu d'un fichier texte ISO-8859-1 et le résultat de son affichage.

          0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f     0123456789abcdef
0000000   73 61 09 6c 75 74 0a 73 61 6c 75 74 09 74 6f 69    sa.lut.salut.toi
0000010   0a 09 09 73 61 6c 75 74                            ...salut........
Fichier texte avec tabulations
sa	lut
salut	toi
		salut
ce document est valide HTML5 — PHP/jQuery/HTML5/CSS3 et design par Philippe Proulx

Philippe Proulx

sur LinkedIn
sur Stack Overflow
courriel principal et Google Chat : exeppelxiteloxop@gmail.com (enlever les x)
courriel institutionnel : phzilippze.proulzx@polymtl.ca (enlever les z)

code Huffman

endodage et décodage de fichiers grâce au code Huffman, sérialisation d'un arbre binaire

flux standards

entrée et sorties standards sur les systèmes Unix et *nix, descripteurs de fichiers, pratique grâce à Bash

intro à Linux

introduction à Linux montrant l'interpréteur de commandes, les commandes classiques, le système de fichiers, les processus et l'interface graphique, le tout entouré de capsules historiques et philosophiques

logiciels, liens et commandes pratiques

liste en constante évolution de logiciels, de liens Web et de commandes shell overpratiques

pilote et outils pour ENC28J60

ensemble d'outils permettant d'opérer le Microchip ENC28J60 avec un microprocesseur AVR, permettant une communication UDP avec un réseau