Archives du mot-clé string

Scilab et le caractère NUL (0) dans les chaines de caractères

Hier, j’ai passé un bon moment à lutter avec Scilab et la lecture de données dans un fichier binaire. Au final, le problème venait de la présence du caractère ASCII NUL (valeur 0) dans la chaine de caractères que je lisais avec mgetstr.

Le problème présenté ici affecte la plupart des fonctions concernées : strindex, strcmp, strsubst, part, strtok

Le problème

Utilisons la fonction strindex pour chercher l’indice du début d’un motif « def » dans une chaine de caractères « abcdef » :

str = "abcdef"

idx = strindex(str, "def")

Renvoi :

    4.

Ajoutons un espace, dont la valeur ASCII est 32, au début de la chaine précédente :

str = ascii(32) + str        // Ajout du caractère espace

idx = strindex(str, "def")

Renvoi :

    5.

Ajoutons une tabulation, dont la valeur ASCII est 8, au début de la chaine précédente :

str = ascii(8) + str        // Ajout du caractère tabulation

idx = strindex(str, "def")

Renvoi :

    6.

Aucun problème donc jusqu’ici.

Ajoutons maintenant le caractère NUL, dont la valeur ASCII est 0, au début de la chaine précédente :

str = ascii(0) + str        // Ajout du caractère NUL

idx = strindex(str, "def")

Renvoi :

     []

Aïe ! Aïe ! Aïe !

Le problème est identique avec d’autres fonctions :

part(str, 1)

 ans  =
 
     
 
part(str, 7)

 ans  =
 
     
 
part(str, 8)

 ans  =

L’explication

Les fonctions de Scilab qui traitent les chaines de caractères sont écrites en C. Or pour stocker une chaine de n caractères, il faut (au minimum) un tableau de n+1 éléments en langage C. Le dernier élément identifie la fin de la chaine de caractères. Et bien entendu, le caractère qui a été retenu pour remplir cette mission est le caractère NUL dont la valeur ASCII est 0 (voir la FAQ C à ce sujet).

Conséquence : si une chaine contient le caractère NUL (0), tous les caractères qui suivent seront ignorés.

Par exemple :

str = "hello world"

substr(1) = part(str, 1:5)
substr(2) = part(str, 7:$)

Renvoi :

!hello  !
!       !
!world  !

Alors que :

str = "hello" + ascii(0) + "world"

substr(1) = part(str, 1:5)
substr(2) = part(str, 7:$)

Renvoi :

!hello  !
!       !
!       !

Une solution

La solution consiste à remplacer le caractère NUL par un autre avant de traiter la chaine de caractères. Mais attention, il faudra impérativement remplacer la valeur numérique du caractère 0 par une autre (de 1 à 255) car nous avons vu précédemment que les fonctions Scilab n’avaient aucun effet.

str = "hello" + ascii(0) + "world"

 str  =
 
 hello world  
 
ascii(str)

 ans  =
 
    104.    101.    108.    108.    111.    0.    119.    111.    114.    108.    100.  
 
newStr = strsubst(str, 0, 32)
                              !--error 999
strsubst : Type erroné de l'argument d'entrée n°3 : Une chaîne de caractères attendue.
 
 
newStr = strsubst(str, ascii(0), ascii(32))

 newStr  =
 
 hello

Il faut donc faire la conversion implicitement :

numStr = ascii(str)

 numStr  =
 
    104.    101.    108.    108.    111.    0.    119.    111.    114.    108.    100.  
 
idx = numStr==0

 idx  =
 
  F F F F F T F F F F F  
 
numStr(idx) = 32 // par exemple

 numStr  =
 
    104.    101.    108.    108.    111.    32.    119.    111.    114.    108.    100.  
 
newStr = ascii(numStr)

 newStr  =
 
 hello world

Voila on peut maintenant gérer la chaine de caractères avec les fonctions Scilab :

substr(1) = part(newStr, 1:5)

 substr  =
 
!hello  !
!       !
!       !
 
substr(2) = part(newStr, 7:$)

 substr  =
 
!hello  !
!       !
!world  !
 
idx = strindex(newStr, "world")

 idx  =
 
    7.

Pourquoi mettre le caractère NUL dans une chaine de caractères ?

Quel est l’intérêt de mettre un caractère NUL dans une chaine ? J’aimerais pouvoir vous répondre : « aucun ». Mais j’ai mentionné la lecture de fichier tout au début de ce billet. Un fichier binaire peut contenir des valeurs numériques et des chaines de caractères.

Prenons l’exemple d’un fichier binaire contenant une valeur 0 suivie d’une chaine de caractères :

fd = mopen("test.dat", "wb")
mput(0, "uc", fd)
mput(ascii("hello world"), "uc", fd)
mclose(fd)

La lecture ne pose pas de problème particulier :

fd = mopen("test.dat", "rb")
str = mgetstr(12, fd)
mclose(fd)

Ce qui donne bien :

str

 str  =
 
  hello world  
 
ascii(str)

 ans  =
 
    0.    104.    101.    108.    108.    111.    32.    119.    111.    114.    108.    100.

Par contre :

idx = strindex(str, "world")

 idx  =
 
     []

Vous n’êtes pas convaincu par cet exemple trop simpliste, passons alors à un exemple pratique.

Validation de fichier DICOM

Les fichiers DICOM sont des fichiers binaires utilisés pour le stockage d’informations médicales. Ils contiennent des valeurs numériques et des chaines de caractères. Avant de lire un de ces fichiers, il est utile de tester sa validité. Le test le plus robuste consiste à chercher la chaine de caractères « 1.2.840.10008. » dans les premiers octets du fichier. La position de la chaine dans le fichier est inconnue et rien n’empêche que des valeurs nulles soient écrites avant…

La mauvaise solution :

fd = mopen("test.dcm", "rb")

buffer = mgetstr(2000, fd)
chk = strindex(buffer, "1.2.840.10008.")

if isempty(chk) then
    disp("Fichier non valide")
else
    disp("Fichier valide")
end

mclose(fd)

Le code ci-dessus renverra constamment « Fichier non valide » même si la chaine « 1.2.840.10008. » est bien présente dans le fichier.

La solution plus robuste à utiliser :

fd = mopen("test.dcm", "rb")

buffer = mgeti(2000, "uc", fd)
idx = buffer~=0
chk = strindex(ascii(buffer(idx)), "1.2.840.10008.")

if isempty(chk) then
    disp("Fichier non valide")
else
    disp("Fichier valide")
end

mclose(fd)

Dans cette version du code, les caractères NUL seront pris en compte avant de chercher la chaine « 1.2.840.10008. » Ce code fonctionne donc comme il faut.