[resolu] naviguer dans une chaine de caractère (ou un fichier)

Bonjour!
J'essaie de naviguer dans un fichier sur ma carte sd mais je n'y arrive pas.
J'aimerais parcourir un fichier xml jusqu'à trouver une certaine balise.

Je n'ai pas réussi a le faire directement à la lecture du fichier car la fonction read() permet de lire un caractère et non pas une chaîne...
Bien sur ce serait le top 8) Si il y a une solution je suis preneur.

Du coup j'ai copié le contenu du fichier dans une chaîne. (normalement il devrait faire quelques ko, ça ne devrait pas saturer la rom de l'Arduino)
J'ai donc pu faire un print() de ma chaîne sans mal, le pb c'est pour naviguer... voici mon code de test:

#include <SD.h>
#define cs 10

unsigned long taille;
File fichier;

void setup()
{
  Serial.begin(9600);
  pinMode(10, OUTPUT); // pour être sur qu'on n’écris pas sur le pin 10
  
  if (!SD.begin(cs)) { // test si la carte est présente et sans erreur
    Serial.println("Carte absente ou illisible");
    return;
  }
  
  fichier = SD.open("test.txt"); // ouvre le fichier
  
  if (!fichier) { // test si l'ouverture a foirée
    Serial.println("Erreur d'ouverture du fichier");
    return;
  }
  taille = fichier.size(); //enregistre la taille du fichier
}

void loop()
{
    char chaine[taille];
    // init de la chaine, de la taille de celle du fichier

    int i=0;
    while (fichier.available()) {
      chaine[i] = fichier.read();
      //Serial.print(chaine[i]);
      i++;
    }
    fichier.close();
    chaine[i] = '\0';
    
    // maintenant on peut commencer la recherche
  int index = chaine.indexOf("<balise>");
  Serial.println(chaine.substring(index));
}

Je me fais jeter par le compilo =( :
request for member ‘substring’ in ‘chaine’, which is of non-class type ‘char [(((unsigned int)(((int)taille) + -0x000000001)) + 1)]’
Je ne vois pas trop ce que c'est...

Yop Youp

youplop:
Du coup j'ai copié le contenu du fichier dans une chaîne. (normalement il devrait faire quelques ko, ça ne devrait pas saturer la rom de l'Arduino)

La sram d'une uno par exemple étant limité à 2 Ko tu risques vite de saturé à ce train là ...

unsigned long taille;

char chaine[taille];

Un tableau de taille unsigned long pourrait aller jusqu'à 4.294.967.296 octet ... même un tableau d'unsigned int de 65.536 octet serait bien suffisant pour saturé la ram même d'une mega avec ses 8ko (8000 octet) ...

Je conseillerai plutôt de récupéré ligne par ligne plutôt que tout le fichier en un coup, une ligne étant délimité par un '\n' c'est facile à trouver sauf évidement si il n'y a aucune ligne d’échappement :sweat_smile:

youplop:
Je me fais jeter par le compilo =( :
request for member ‘substring’ in ‘chaine’, which is of non-class type ‘char [(((unsigned int)(((int)taille) + -0x000000001)) + 1)]’
Je ne vois pas trop ce que c'est...

Là il te dit simplement que la méthode membre 'substring' est impossible vu qu'il n'existe pas de classe du type ‘char [(((unsigned int)(((int)taille) + -0x000000001)) + 1)]’ qui est du type tableau de char et non une classe ...
Attention une chaine de caractère et un objet String ce n'est pas la même chose, tu ne peux pas utilisé de méthode de la classe String sur un tableau de type char qui n'est pas un objet évidement comme tu le fais avec substring ou indexOf.

http://www.mon-club-elec.fr/pmwiki_reference_arduino/pmwiki.php?n=Main.StringObjet

Bien faire la différence entre une variable du type primitif (char, int, long, ...) et du type objet (String, SD, Serial, ... ) qui sont des instance de classe !

Merci pour ta réponse osaka.

J'essaie de voir pour la lecture ligne par ligne.
J'ai remplacé mon tableau de chaîne par un string:String chaine = String(taille);
et le contenu de ma boucle par des fonctions string:

chaine.setCharAt(i, fichier.read());
Serial.print(chaine.charAt(i));
i++;

Pour le coup la sortie n'est pas très bavarde, elle m'affiche qu'un seul caractère.

Bonjour,

Une méthode beaucoup plus rapide pour parser un "node" xml :

  • ouvrir le fichier,
  • faire un while(fichier.available() > 0 && fichier.read() != '<'); pour boucler jusqu'à trouver un '<'
  • crée un tableau de char de petite taille, disont 16, char buffer[16];
  • faire une boucle pour copier le nom de la balise dans le tableau
int i;
char c;
for(i = 0 ; (fichier.available() > 0) && (i < 16) && ((c = fichier.read()) != '>'); i++) buffer[i] = c;
buffer[i] = '\0';
  • vérifié le nom de la balise if(strcmp(buffer, "balise") == 0) .......
  • si c'est pas bon recommence au point 2 sinon il faut récupéré tout le texte jusqu'a la prochaine balise '<'
while((fichier.available() > 0) && ((c = fichier.read()) != '<')) Serial.print(c);

L'avantage de cette méthode c'est quelle n'utilise que trés peut de ram (contrairement à l'objet String), par contre elle demande un fichier xml bien formaté, non vide.

Merci skywodd!
Ta solution à semble bien convenir en effet.
Du coup je voudrais en faire une petite fonction mais je peine encore un peu...
C'est pas grand chose, j'en ai bientôt fini :stuck_out_tongue:

fonction: cherche dans le fichier un mot debut(ex: "<balise"), jusqu'à un caractère fin (ex: '>') et qui renvoie une chaîne qui contient qu'il y a entre les 2, dans la limite de maxi caractères.
Voila l'entête de ma fonction:
char *chercher(char *debut, char fin, int maxi) { ... }

Puis dans mon loop, je déclare une chaîne, dans laquelle je vais stocker le résultat de la fonction et l'afficher:
char chaine[15];
chaine = chercher("<balise", '>', 15);
Serial.println(chaine);

Le compilo me dit que char* est pas compatible avec char[15], mais si j'init ma chaîne avec un char* , ça marche pas...

Yep!

Il faut bien distinguer un tableau de caractères d'un pointeur. Ce sont deux choses différentes. Le pointeur fait réference à une adresse mémoire contenant une variable, ce n'est pas la variable/chaine en elle-même.

La première étape consiste à remplir le tableau un caractère à la fois. Il faut ensuite soit retourner directement le tableau soit, effectivement pointer celui-ci et retourner une valeur char *.

void setup() { }

void loop() { 

char *chaine;
  
chaine = chercher("<balise", '>', 8);

}

char * chercher(char *a, char b, int c) { 
  int index = 0;
  char input[15];
  char *p;
  while (...) { char d = input[index]; index++; 
  if (index >= c) { break; }
  }
  *p = *input;
  return p;
}

Mais bon, il faut voir le traitement que tu désires faire ensuite sur ta chaine de caractère.
Remarques aussi l'utilisation des guillemets double, pour une chaine de caractère, des guillemets simples, pour déclarer un byte.

@+

Zoroastre.

Quand tu fais char "char chaine[15];"en déclaration tu ne fais que donner à un pointeur la premier adresse d'un espace mémoire réservé (à 15 ici).
Ici "chaine" contient l'adresse du premier caractère de ton tableau chaine, par contre quand tu fais chaine[1], [2], [3],..., dans le reste du code tu demande la valeurs (char ici) correspondant à l'adresse chaine +1, +2, +3.
Ton problème est qu'il est demandé un pointeur char* donc une adresse et en faisant char[15] tu lui donne la valeur char correspondant à l'adresse du premier char + 15, le bon paramètre est donc 'chaine'.
Maintenant si tu veux faire la même chose mais à partir d'un certain emplacement de ta chaine de caractère il suffit de donner l'emplacement de ton tableau et en l'incrémentant à l'emplacement voulu.
Exemple:
Tu veux que la chaine à traiter sois pris en compte à partir du 15ème caractère tu feras "chaine+15" et "non chaine[15]" (! cet exemple est partiellement faux comme un tableau débute à 0 )

youplop:
Puis dans mon loop, je déclare une chaîne, dans laquelle je vais stocker le résultat de la fonction et l'afficher:
char chaine[15];
chaine = chercher("<balise", '>', 15);
Serial.println(chaine);

Le compilo me dit que char* est pas compatible avec char[15], mais si j'init ma chaîne avec un char* , ça marche pas...

Normal, tu tente de remplacer le pointeur de début d'un tableau déclaré statiquement, chose interdite, donc il râle !
(Ps: et de toute manière on ne remplit pas un tableau en utilisant = mais memcpy() ou une boucle)

Pour faire ce que tu décrit il faut passer le tableau par référence, en gros le prototype de ta fonction ressemblerai à cela :
void chercher(char *chaine, char balise[], char charFin, int longueur);
chaine -> le tableau qui contiendra la chaine final en sortie de la fonction,
balise -> une chaine de caractère,
charFin -> la caractère de fin de balise,
longueur -> la taille du tableau chaine.

(Ps: ce prototype de fonction est juste, mais d'un point de vue développeur il n'est pas des plus viable ...)

zoroastre:
Il faut bien distinguer un tableau de caractères d'un pointeur. Ce sont deux choses différentes. Le pointeur fait référence à une adresse mémoire contenant une variable, ce n'est pas la variable/chaine en elle-même.

La première étape consiste à remplir le tableau un caractère à la fois. Il faut ensuite soit retourner directement le tableau soit, effectivement pointer celui-ci et retourner une valeur char *.

void setup() { }

void loop() {

char *chaine;
 
chaine = chercher("<balise", '>', 8);

}

char * chercher(char *a, char b, int c) {
 int index = 0;
 char input[15];
 char *p;
 while (...) { char d = input[index]; index++;
  if (index >= c) { break; }
  }
 *p = *input;
 return p;
}

zoroastre ton code ne peut pas marcher pour deux raisons, premièrement tu déclare un char[] statiquement dans la fonction, lors que la fonction recherche() retournera l'adresse du pointeur p, et bien le tableau pointé par p aura était déjà désallouer et tu ira taper dans une zone mémoire totalement corrompu.
Autre probléme :

char *p;
(...)
*p = *input;
  return p;

=> Place le contenu pointé par &input dans le contenu pointé par &p
p pointant sur une adresse inconnu ça ne marchera jamais, de plus *input correspond à input[0] ... donc juste un char.
Le bon code serait p = input; ou plus simplement return p;

Mais comme le tableau aura était désallouer cela ne marcherai pas non plus.
La solution sur ordi aurait était un malloc() mais sur un µc c'est du suicide.
La "bonne" solution consiste à donner l'adresse du tableau crée avant l'appel de la fonction recherche() via un pointeur comme je l'ai fait dans le prototype un peu plus haut et de travailler directement dessus.

Pour ceux qui sont intéressez par l'arithmétique de pointeurs : http://www.siteduzero.com/tutoriel-3-14015-les-tableaux.html

Merci à vous tous et bonnes fêtes!!

C'est vraiment cool cet esprit d'entraide, je m’attendais limite a me faire remballer par ce qu'il me manquait quelques bases, au lieu de ça j'ai des explications qui m'ont bien éclairées sur l'utilisation des chaînes et pointeurs.

@skywodd:

(Ps: ce prototype de fonction est juste, mais d'un point de vue développeur il n'est pas des plus viable ...)

Qu'entends-tu par là? Celui de la fonction ci-dessous est-il mieux?

Le bon code serait p = input; ou plus simplement return p;

Je dirais même plus return input; :stuck_out_tongue: Et en effet ça marche bien mieux sans ces dernières lignes.

Du coup j'ai pu finir ma fonction, que voici, si ça peut permettre d'aider d'autres codeurs:
Je l'ai allégée un peu puisque il y a en paramètres qu'une chaîne en entrée + une par référence.
Elle ne récupère pas les balises, mais renvoie directement les valeurs correspondantes aux mots clés spécifiés (cf exemple plus bas).

void recup(char *valeur, char *motDeb)
{
  strcat(motDeb , "=\""); // ajoute le (=")
  
  char carFin = '"'; // va récupérer les car. jusqu'aux guillemets

  File fichier = SD.open("test.txt"); // ouvre fichier

  int i = 0;
  char car;
  boolean trouve = false;
  boolean valOk = false;

  if (fichier) // si aucune erreur sur le fichier
  {
    while(fichier.available() && !valOk)
    //tant qu'il y a qqch à lire et qu'on a pas trouvé la valeur
    {
      car = fichier.read();
      if (!trouve) // si on a pas encore trouvé le mot
      {
        if (car == motDeb[i]) // si le caractère correspond, passe au suivant
        {
          i++;
          if(motDeb[i] == '\0') // si fin du mot (mot trouvé)
          {
            i=0;
            // avance d'un caractère pour ne pas choper le 1er (")
            fichier.read();
            trouve = true;
          }
        }
        else
          // si un des car. n'est pas bon on reprends depuis le début
          i=0;
      }
      else // si on a trouvé le mot:
      {
        if (car != carFin) // si ce n'est pas ("), stocke dans valeur 
        {
          valeur[i] = car;
          i++;
        }
        else
          valOk = true; // on a fini
      }
    }
  }
  else
    Serial.println("erreur d'ouverture du fichier");
}

Pour l'appel:

  • char chaine[7] = "";*
  • recup(chaine, "width");*
  • Serial.print(chaine);*

youplop:
C'est vraiment cool cet esprit d'entraide, je m’attendais limite a me faire remballer par ce qu'il me manquait quelques bases, au lieu de ça j'ai des explications qui m'ont bien éclairées sur l'utilisation des chaînes et pointeurs.

Hey! C'est le forum Arduino.cc c'est pas n'importe quelle forum 8)

youplop:
Je dirais même plus return input; :stuck_out_tongue: Et en effet ça marche bien mieux sans ces dernières lignes.

Ops! j'avais pas vu, oui oui return input; :slight_smile:

youplop:
Qu'entends-tu par là? Celui de la fonction ci-dessous est-il mieux?

Tu t'ai auto-répondu juste en dessous :wink:
En fait comme ta fonction cherche un balise entre <> c'etait inutile d'avoir un "<..." dans le nom de la balise et un second arguments avec '>' :wink:

youplop:
Du coup j'ai pu finir ma fonction, que voici, si ça peut permettre d'aider d'autres codeurs:
Je l'ai allégée un peu puisque il y a en paramètres qu'une chaîne en entrée + une par référence.
Elle ne récupère pas les balises, mais renvoie directement les valeurs correspondantes aux mots clés spécifiés (cf exemple plus bas).

Juste quelques petites remarques :wink:
Le int i est un peu sur dimensionné, un byte devrait suffire, sinon le code est propre, du bon travail ^^
Par contre un truc bien serrait de retourner i pour savoir le nombre de char lu et du coup de remplacer le Serial.print par un return -1 pour gérer les erreurs :wink:
(Et ce serait bien d'avoir un test pour pas dépasser la taille maxi de "chaine" :wink: )

Ps: pourquoi strcat(motDeb , "=""); ?

int recup(char *valeur, char *motDeb, int longueur) {
	strcpy(motDeb, "=\""); // ajoute le (=")

	File fichier = SD.open("test.txt"); // ouvre fichier
	if (!fichier) return -1 // si aucune erreur sur le fichier

	byte i = 0;
	char car;
	boolean trouve = false;

	while (fichier.available()) //tant qu'il y a qqch à lire et qu'on a pas trouvé la valeur
	{
		car = fichier.read();
		if (!trouve) // si on a pas encore trouvé le mot
		{
			if (car == motDeb[i]) // si le caractère correspond, passe au suivant
			{
				if (!motDeb[++i]) // si fin du mot (mot trouvé)
				{
					i = 0;
					fichier.read(); // avance d'un caractère pour ne pas choper le 1er (")
					trouve = true;
				}
			} else
				i = 0; // si un des car. n'est pas bon on reprends depuis le début
		} else { // si on a trouvé le mot
			if (car != '"') // si ce n'est pas ("), stocke dans valeur 
			{
				if (i == longueur) return -1;
				valeur[i++] = car;
			} else
				return i;
		}
	}
}

(J'espère pas avoir fait d'erreur de recopie)

Voici une version un peu modifiée.
J'ai donc renvoyé donc le int et -1 s'il n'arrive pas a ouvrir le fichier.
Le strcat(motDeb , "=""); rajoute les caractères =" au mot cherché.

Exemple du fichier xml:
width**="120mm"**
Un recupSD(valeur, "width") va donc chercher width=" , puis récupérer les car. jusqu'au prochain " , ce qui renvoie ici 120mm .

Du coup je me suis aperçu d'une anomalie: je modifiais *motDeb, du coup la fonction est foireuse si on l'appelle plusieurs fois de suite.
Bug rectifié sur la version ci-dessous : en-tête de fonction avec un const char *mot, pour être sur de ne rien toucher, et je copie son contenu dans une nouvelle chaîne: motDeb.

int recupSD(char *valeur, const char *mot)
{
  // crée une chaine de la meme taille que *valeur
  // +2 pour (=") + 1 pour le \0
  char motDeb[strlen(mot)+3];
  strcpy(motDeb, mot);
  strcat(motDeb , "=\""); // ajoute le (=")
  
  char carFin = '\"'; // va récupérer les car. jusqu'aux guillemets

  File fichier = SD.open("test.txt"); // ouvre fichier
  fichier.seek(0); // se positionne en début de fichier

  byte i = 0;
  char car;
  boolean trouve = false;
  boolean valOk = false;

  if (fichier) // si aucune erreur sur le fichier
  {
    while(fichier.available() && !valOk)
    //tant qu'il y a qqch à lire et qu'on a pas trouvé la valeur
    {
      car = fichier.read();
      if (!trouve) // si on a pas encore trouvé le mot
      {
        if (car == motDeb[i]) // si le caractère correspond, passe au suivant
        {
          i++;
          if(motDeb[i] == '\0') // si fin du mot (mot trouvé)
          {
            i=0;
            // avance d'un caractère pour ne pas choper le "
            //fichier.read();
            trouve = true;
          }
        }
        else
          // si un des car. n'est pas bon on reprends depuis le debut
          i=0;
      }
      else // si on a trouvé le mot:
      {
        if (car != carFin) // si ce n'est pas ("), stocke dans valeur 
        {
          valeur[i] = car;
          i++;
        }
        else
        {
          valeur[i] = '\0';
          valOk = true; // on a fini
        }
      }
    }
  }
  else
  {
    Serial.println("erreur d'ouverture du fichier");
    i=-1;
  }
    
  return i;
}

Il me reste plus qu'à coder une autre fonction pour convertir le résultat en flottant en tenant compte de l'unité de mesure (cm, mm,...).

Oh pardon, j'avais pas lu ton code...
C'est du abrégé dis donc :smiley:
Que signifie if (!motDeb[++i]) ?
Pour le reste, on m'a appris a ne faire qu'un seul return en fin de fonction, donc je vais laisser comme tel.
Tant pis si ça prends un booléen en plus, pour la place que ça prends :stuck_out_tongue:

youplop:
C'est du abrégé dis donc :smiley:

Quand je peut j'abrège, féniasse inside :sweat_smile:

youplop:
Que signifie if (!motDeb[++i]) ?

C'est équivalent à :

i++;
if(motDeb[i] == '\0') 
{ ...

(Note: bien utiliser ++i et non i++, ++i incrémente i avant de l'utiliser comme index de tableau, avec i++ i serait incrémenté aprés avoir était utilisé comme index de tableau)

youplop:
Pour le reste, on m'a appris a ne faire qu'un seul return en fin de fonction, donc je vais laisser comme tel.

Mettre plusieurs return n'as jamais tué personne, c'est plutôt l'usage de break dans une boucle qui est bofbof (continue aussi c'est bofbof).

youplop:
Tant pis si ça prends un booléen en plus, pour la place que ça prends :stuck_out_tongue:

Ralala, sur un micro-contrôleur chaque octet de ram compte ! O P T I M I S A T I O N :grin: