Importer dans une liste hiérarchique

Olivier DESCHANELS

Dans cette note technique nous allons détailler pas à pas comment réaliser une liste hiérarchique à partir d'une table d'import. ♪

- Titre : Importer dans une liste hiérarchique

- Auteur : Olivier DESCHANELS

- Parution : 1 janvier 2003

- Licence : Ce document est issu de https://www.developpez.comet reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

- I. Éléments de base

- II. Paramètres du problème

- III. Construction de la méthode 4D

- Méthode de test

- Mise en place de la récursivité

- Paramètre de retour

- Premier appel récursif

- Traitement du second niveau

- Un peu de contrôle

- Rendre générique

- Mettre des bornes

- Éviter les mélanges

- IV. Aller plus loin

- V. Base exemple

I - Éléments de base

Dans nos vies de développeurs nous avons souvent eu à importer des données provenant de diverses sources. Pour cela, il est souvent pratique d'utiliser une table d'import temporaire, table qui sera ensuite relue par une méthode 4D chargée de répartir les informations aux emplacements adéquates dans la base de données.

Dans ce présent exercice, la table [Import] est constituée de champs alphanumériques :

Nous y importons les articles d'un magasin de sport :

Comme nous pouvons le constater, cette liste d'articles peut être parcourue de manière hiérarchique. Voici le résultat que nous désirons obtenir à la fin de cette note technique :

II - Paramètres du problème

La lecture attentive de la liste ci-dessus nous donne quelques données du problème :

- les nombres de branches et d'éléments dans chacune sont variables ;

- le nombre de niveaux avant d'atteindre la fin d'une branche est variable ;

- une valeur peut se retrouver dans plusieurs branches ;

- les valeurs vides ne doivent pas être conservées.

Nous ajouterons que :

- nous ne connaissons pas le nombre maximal de champs à explorer afin de construire une méthode générique ;

- chaque niveau a au moins une valeur.

III - Construction de la méthode 4D

Le but de la méthode est de construire une liste hiérarchique. Dans 4D, une liste hiérarchique est manipulable par programmation via une référence de type entier long. Il semble donc naturel que la méthode 4D qui constituera le cœur de notre étude soit en fait une fonction retournant la référence de la liste hiérarchique construite. Cette méthode sera nommée « LH_Import ».

Méthode de test

Dès à présent nous pouvons construire une méthode de test permettant de mettre en œuvre notre travail :

1. `méthode de test

2. C_ENTIER LONG($liste)

3. $liste:=LH_Import

4. STOCKER LISTE($liste;"LH_import")

Cette méthode appelle la fonction « LH_Import », puis stocke le résultat dans une énumération du mode structure. Ainsi nous pouvons voir le résultat de notre travail sans avoir à créer la moindre interface.

Mise en place de la récursivité

Nous savons déjà que la méthode « LH_Import » retourne une référence de liste hiérarchique. Comme vu également dans les paramètres du problème, la structure de la table est variable. Il est donc difficile de prévoir une méthode où le traitement est fixe. Au contraire nous avons besoin d'une méthode qui va s'adapter niveau par niveau.

L'expérience nous montre que dans ce cas-là nous pouvons mettre en place un traitement récursif.

Dans un tel traitement, la méthode s'appelle elle-même un certain nombre de fois ; à chaque passe, le résultat s'affine un peu plus jusqu'à ce qu'il soit parfait. À ce moment-là, la méthode s'arrête et retourne son résultat.

Dans la méthode de test, nous appelons la méthode « LH_Import » sans aucun paramètre. Sans être devin, il est facile d'imaginer que dans les autres appels au moins un paramètre sera nécessaire pour passer des informations d'un cycle à un autre.

Nous pouvons donc distinguer deux cas :

- soit la méthode ne reçoit aucun paramètre et nous sommes alors dans le premier appel. Nous pouvons alors en profiter pour initialiser les éléments traités par la suite.

- soit la méthode reçoit un ou plusieurs paramètres et alors nous sommes en train de traiter la phase récursive.

Voici la traduction de ces deux cas en code 4D :

1. `méthode : LH_Import

2. Au cas ou

3. : (Nombre de parametres=0)

4. `initialisation de la méthode

5. Sinon

6. `traitement de la récursivité

7. Fin de cas

Paramètre de retour

La référence à une liste hiérarchique est un entier long. Ce sera notre paramètre de retour. Ce paramètre sera déclaré en en-tête de méthode, puis on lui affectera une valeur dans chaque cas. Nous ne savons pas encore quelle valeur lui affecter, mais nous pouvons néanmoins préparer le code :

1. `méthode : LH_Import

2. C_ENTIER LONG($0)

3. `liste hiérarchique obtenue

4. Au cas ou

5. : (Nombre de parametres=0)

6. `initialisation de la méthode

7. $0:=

8. Sinon

9. `traitement de la récursivité

10. $0:=

11. Fin de cas

Premier appel récursif

Maintenant que la structure est en place, nous allons traiter le premier appel à la méthode.

Lors de ce premier appel, nous devons extraire de l'ensemble des enregistrements de la table [Import], les différentes valeurs pour le Champ1. À partir de cette liste de valeurs, nous construisons la liste hiérarchique, ou plus exactement son premier niveau.

Pour travailler sur l'ensemble des enregistrements de la table, la commande TOUT SELECTIONNER convient de toute évidence. Pour extraire les différentes valeurs du Champ1 nous pouvons tirer bénéfice de la commande VALEURS DISTINCTES qui permet d'extraire toutes les valeurs d'un champ indexé ou indexable.

Pour construire la liste hiérarchique nous mettrons en œuvre les commandes Nouvelle Liste et AJOUTER A LISTE. Pour cette dernière, la documentation de 4D recommande d'associer à chaque élément une référence unique afin de faciliter les traitements par la suite. Pour suivre cette recommandation, nous allons mettre en place un compteur que nous initialiserons dans la phase adéquate puis que nous incrémenterons à chaque ajout d'élément dans la liste. Ce compteur sera matérialisé par une variable nommée lh_compteur. Cette variable ne peut pas être une variable locale, car sinon à chaque appel de la méthode elle serait remise à zéro !

En regardant la liste de valeurs de la table [Import] nous commençons par lire le Champ1 puis nous passons au Champ2 et ainsi de suite … Lors du premier appel, nous commençons la lecture ; nous pouvons donc considérer que nous sommes au champ 0 (qui n'existe pas) et que nous allons passer au Champ1. Cette considération est une notion de niveau. Comme nous allons parcourir les niveaux les uns après les autres il semble logique de passer ce niveau en tant que paramètre lors de nos appels récursifs.

Voici tout ceci traduit en code 4D :

1. `méthode : LH_Import

2. C_ENTIER LONG($0) `liste hiérarchique obtenue

3. Au cas ou

4. : (Nombre de parametres=0)

5. `initialisation de la méthode

6. lh_compteur:=0

7. $0:=LH_Import (0)

8. Sinon

9. `traitement de la récursivité

10. TOUT SELECTIONNER([IMPORT])

11. VALEURS DISTINCTES([IMPORT]Champ1;$_valeur)

12. $liste:=Nouvelle liste

13. Boucle ($i;1;Taille tableau($_valeur);1)

14. lh_compteur:=lh_compteur+1

15. AJOUTER A LISTE($liste;$_valeur{$i};lh_compteur)

16. Fin de boucle

17. $0:=$liste

18. Fin de cas

Voici le résultat obtenu pour la liste hiérarchique :

Traitement du second niveau

Le résultat est certes correct, mais loin d'être complet !

Pour traiter le second niveau, nous devons prendre une à une les valeurs du premier niveau puis rechercher les valeurs à affecter au second niveau.

Dans le langage 4D pour ajouter des éléments fils à un élément parent, il faut associer à ce dernier une autre liste hiérarchique. Cette sous-liste sera associée lors d'ajout de l'élément parent à la liste en passant la référence de la sous-liste comme paramètre à la commande AJOUTER A LISTE.

Dans le traitement récursif, nous devons à présent distinguer deux cas :

- soit nous sommes au premier passage et nous utilisons un TOUT SELECTIONNER comme précédemment ;

- soit nous sommes au second passage et nous devons utiliser un CHERCHER.

Ce CHERCHER a pour but de réduire le nombre d'enregistrements à ceux ayant une valeur précise pour le Champ1. Il nous faut donc cette valeur. Nous allons la passer en second paramètre. Nous devons donc modifier le code pour le premier appel. Le problème à ce niveau est que nous n'avons pas de valeur à passer. En fait ce n'est pas grave, car le TOUT SELECTIONNER à suivre ne nécessite pas de valeur, nous pouvons donc passer n'importe quelle valeur. Pour ne pas perturber, nous passerons une chaîne vide.

Lors de la construction de la liste, nous devons donner la référence de la sous-liste associée à un élément en même temps que l'ajout de ce dernier à la liste. Nous devons donc calculer la sous-liste avant l'ajout, et ceci uniquement dans le cas du premier niveau (nous ne traitons pas encore le troisième niveau).

Voici le code 4D résultant :

1. `méthode : LH_Import

3. C_ENTIER LONG($0)  `liste hiérarchique obtenue

4. C_ENTIER($1)  `Niveau dans la récursivité

5. C_ALPHA(80;$2) `valeur

7. Au cas ou

8. : (Nombre de parametres=0)

9. `initialisation de la méthode

10. lh_compteur:=0

11. $0:=LH_Import (0;"")

12. Sinon

13. `traitement de la récursivité

14. Si ($1=0)

15. TOUT SELECTIONNER([IMPORT])

16. VALEURS DISTINCTES([IMPORT]Champ1;$_valeur)

17. Sinon

18. CHERCHER([IMPORT];[IMPORT]Champ1=$2)

19. VALEURS DISTINCTES([IMPORT]Champ2;$_valeur)

20. Fin de si

22. $liste:=Nouvelle liste

23. Boucle ($i;1;Taille tableau($_valeur);1)

24. Si ($1=0)

25. $sous_liste:=LH_Import (1;$_valeur{$i})

26. Fin de si

27. lh_compteur:=lh_compteur+1

28. AJOUTER A LISTE($liste;$_valeur{$i};lh_compteur;$sous_liste;Vrai)

29. Fin de boucle

30. $0:=$liste

31. Fin de cas

La liste obtenue à ce stade est la suivante :

À ce niveau nous devons bien comprendre l'intérêt des tableaux locaux $_valeur. En effet à chaque niveau ces tableaux sont réinitialisés (comme toutes variables locales) pour contenir les valeurs du niveau. Par contre lorsque nous retournons au niveau supérieur le tableau $_valeur contiendra toujours les valeurs qu'il contenait au moment ou nous l'avions quitté. La meilleure solution est de tracer la méthode et de suivre l'évolution du contenu des variables. Lorsque vous avez bien compris l'aspect local des variables, vous pouvez passer à la suite.

Un peu de contrôle

Notre liste commence à prendre tournure. Avant d'aller plus loin, nous allons mettre un peu de sécurité dans notre code. En effet nous n'avons pas traité le cas ou la commande VALEURS DISTINCTES retourne un tableau vide.

Si le tableau est vide, il n'y a aucune liste (ou sous liste) à construire ; il suffit de retourner une référence de liste égale à zéro et le travail est terminé.

Traduisons ceci en code 4D :

1. `méthode : LH_Import

3. C_ENTIER LONG($0)  `liste hiérarchique obtenue

4. C_ENTIER($1)  `Niveau dans la récursivité

6. Au cas ou

7. : (Nombre de parametres=0)

8. `initialisation de la méthode

9. lh_compteur:=0

10. $0:=LH_Import (0;"")

12. Sinon

13. `traitement de la récursivité

14. Si ($1=0)

15. TOUT SELECTIONNER([IMPORT])

16. VALEURS DISTINCTES([IMPORT]Champ1;$_valeur)

17. Sinon

18. CHERCHER([IMPORT];[IMPORT]Champ1=$2)

19. VALEURS DISTINCTES([IMPORT]Champ2;$_valeur)

20. Fin de si

22. Si (Taille tableau($_valeur)>0)

23. $liste:=Nouvelle liste

24. Boucle ($i;1;Taille tableau($_valeur);1)

25. Si ($1=0)

26. $sous_liste:=LH_Import (1;$_valeur{$i})

27. Fin de si

28. lh_compteur:=lh_compteur+1

29. AJOUTER A LISTE($liste;$_valeur{$i};lh_compteur;$sous_liste;Vrai)

30. Fin de boucle

31. Sinon

32. $liste:=0

33. Fin de si

35. $0:=$liste

36. Fin de cas

Rendre générique

Nous avons traité les niveaux 1 et 2. Il est temps de rendre générique le code pour traiter automatiquement les niveaux suivants.

La première modification est au niveau de l'appel de la commande VALEURS DISTINCTES. À chaque niveau nous traitons le champ correspondant à ce niveau. Nous pouvons donc utiliser un pointeur construit pour obtenir la liste de valeurs. De même pour la recherche, nous cherchons toujours la valeur du champ correspondant au niveau précédent. Nous pouvons également utiliser un pointeur :

1. ...

2. $Niveau_traitement:=$1+1

3. Si ($Niveau_traitement=1)

4. TOUT SELECTIONNER([IMPORT])

5. Sinon

6. CHERCHER([IMPORT];Champ(Table(->[IMPORT]);$Niveau_traitement-1)->=$2)

7. Fin de si

8. VALEURS DISTINCTES(Champ(Table(->[IMPORT]);$Niveau_traitement)->;$_valeur)

9. ....

Nous en profitons au passage pour définir la valeur $Niveau_traitement que nous pouvons utiliser pour l'appel récursif :

$sous_liste:=LH_Import ($Niveau_traitement;$_valeur{$i})

Nous n'entourons plus cette ligne de code d'une condition. En effet nous voulons traiter à présent tous les niveaux. Voici donc le code modifié :

1. `méthode : LH_Import

3. C_ENTIER LONG($0)  `liste hiérarchique obtenue

4. C_ENTIER($1)  `Niveau dans la récursivité

6. Au cas ou

7. : (Nombre de parametres=0)

8. `initialisation de la méthode

9. lh_compteur:=0

10. $0:=LH_Import (0;"")

12. Sinon

13. `traitement de la récursivité

14. $Niveau_traitement:=$1+1

15. Si ($Niveau_traitement=1)

16. TOUT SELECTIONNER([IMPORT])

17. Sinon

18. CHERCHER([IMPORT];Champ(Table(->[IMPORT]);$Niveau_traitement-1)->=$2)

19. Fin de si

20. VALEURS DISTINCTES(Champ(Table(->[IMPORT]);$Niveau_traitement)->;$_valeur)

22. Si (Taille tableau($_valeur)>0)

23. $liste:=Nouvelle liste

24. Boucle ($i;1;Taille tableau($_valeur);1)

25. $sous_liste:=LH_Import ($Niveau_traitement;$_valeur{$i})

26. lh_compteur:=lh_compteur+1

27. AJOUTER A LISTE($liste;$_valeur{$i};lh_compteur;$sous_liste;Vrai)

28. Fin de boucle

29. Sinon

30. $liste:=0

31. Fin de si

33. $0:=$liste

34. Fin de cas

Mettre des bornes

Si nous exécutons le code précédent, nous obtenons des erreurs. En effet nous utilisons des pointeurs sur des champs sans contrôler si le champ existe ! Il nous faut donc mettre une protection à cette fin en comparant le niveau courant avec le nombre de champs dans la table.

1. `méthode : LH_Import

3. C_ENTIER LONG($0)  `liste hiérarchique obtenue

4. C_ENTIER($1)  `Niveau dans la récursivité

6. Au cas ou

7. : (Nombre de parametres=0)

8. `initialisation de la méthode

9. lh_compteur:=0

10. $0:=LH_Import (0;"")

12. Sinon

13. `traitement de la récursivité

14. $Niveau_traitement:=$1+1

15. Si ($Niveau_traitement<=Nombre de champs(Table(->[IMPORT])))

16. Si ($Niveau_traitement=1)

17. TOUT SELECTIONNER([IMPORT])

18. Sinon

19. CHERCHER([IMPORT];Champ(Table(->[IMPORT]);$Niveau_traitement-1)->=$2)

20. Fin de si

21. VALEURS DISTINCTES(Champ(Table(->[IMPORT]);$Niveau_traitement)->;$_valeur)

23. Si (Taille tableau($_valeur)>0)

24. $liste:=Nouvelle liste

25. Boucle ($i;1;Taille tableau($_valeur);1)

26. $sous_liste:=LH_Import ($Niveau_traitement;$_valeur{$i})

27. lh_compteur:=lh_compteur+1

28. AJOUTER A LISTE($liste;$_valeur{$i};lh_compteur;$sous_liste;Vrai)

29. Fin de boucle

30. Sinon

31. $liste:=0

32. Fin de si

33. Sinon

34. $liste:=0

35. Fin de si

37. $0:=$liste

38. Fin de cas

La liste obtenue est semblable à ceci :

Nous avons des lignes vides, car nous avons omis de traiter un des paramètres du problème : les valeurs vides ne doivent pas être conservées. Modifions donc le code pour traiter ce cas :

1. `méthode : LH_Import

3. C_ENTIER LONG($0)  `liste hiérarchique obtenue

4. C_ENTIER($1)  `Niveau dans la récursivité

6. Au cas ou

7. : (Nombre de parametres=0)

8. `initialisation de la méthode

9. lh_compteur:=0

10. $0:=LH_Import (0;"")

12. Sinon

13. `traitement de la récursivité

14. $Niveau_traitement:=$1+1

15. Si ($Niveau_traitement<=Nombre de champs(Table(->[IMPORT])))

16. Si ($Niveau_traitement=1)

17. TOUT SELECTIONNER([IMPORT])

18. Sinon

19. CHERCHER([IMPORT];Champ(Table(->[IMPORT]);$Niveau_traitement-1)->=$2)

20. Fin de si

21. VALEURS DISTINCTES(Champ(Table(->[IMPORT]);$Niveau_traitement)->;$_valeur)

23. Si (Taille tableau($_valeur)>0)

24. $liste:=Nouvelle liste

25. Boucle ($i;1;Taille tableau($_valeur);1)

26. Si ($_valeur{$i}#"")

27. $sous_liste:=LH_Import ($Niveau_traitement;$_valeur{$i})

28. lh_compteur:=lh_compteur+1

29. AJOUTER A LISTE($liste;$_valeur{$i};lh_compteur;$sous_liste;Vrai)

30. Fin de si

31. Fin de boucle

32. Sinon

33. $liste:=0

34. Fin de si

35. Sinon

36. $liste:=0

37. Fin de si

39. $0:=$liste

40. Fin de cas

Cette dernière modification se fait au sein de la boucle afin d'éliminer au plus tôt les valeurs inutiles.

Voici à présent la liste résultante :

Éviter les mélanges

À première vue la liste précédente semble correcte. En fait elle n'est pas satisfaisante, car les branches « Sport » sont erronées. Ce phénomène provient de la recherche effectuée qui est trop simple, car elle se base sur une seule valeur.

Lorsque la ligne :

CHERCHER([IMPORT];Champ(Table(->[IMPORT]);$Niveau_traitement-1)->=$2)

est effectuée avec le paramètre $2 à "Sport" nous obtenons quatre enregistrements au lieu de trois dans un cas et un dans l'autre.

Pour éviter ce mélange, nous devons faire la recherche sur tous les champs jusqu'au niveau en cours de traitement.

Le problème est de conserver les valeurs de tous ces champs. Pour cela nous allons utiliser une pile qui se matérialise sous la forme d'un tableau d'alphanumérique. À chaque niveau nous ajoutons dans le tableau la valeur courante du niveau en cours de traitement. La recherche précédente est modifiée pour tenir compte de toutes les valeurs de la pile.

Voici le code 4D révisé :

1. `méthode : LH_Import

3. C_ENTIER LONG($0)  `liste hiérarchique obtenue

4. C_ENTIER($1)  `Niveau dans la récursivité

6. Au cas ou

7. : (Nombre de parametres=0)

8. `initialisation de la méthode

9. lh_compteur:=0

10. TABLEAU ALPHA(80;_champ;Nombre de champs(Table(->[IMPORT])))

11. $0:=LH_Import (0;"")

13. Sinon

14. `traitement de la récursivité

15. $Niveau_traitement:=$1+1

16. Si ($Niveau_traitement<=Nombre de champs(Table(->[IMPORT])))

17. Si ($Niveau_traitement=1)

18. TOUT SELECTIONNER([IMPORT])

19. Sinon

20. Boucle ($j;1;$1-1)

21. CHERCHER([IMPORT];Champ(Table(->[IMPORT]);$j)->=_champ{$j};*)

22. Fin de boucle

23. CHERCHER([IMPORT];Champ(Table(->[IMPORT]);$Niveau_traitement-1)->=$2)

24. Fin de si

25. VALEURS DISTINCTES(Champ(Table(->[IMPORT]);$Niveau_traitement)->;$_valeur)

27. Si (Taille tableau($_valeur)>0)

28. $liste:=Nouvelle liste

29. Boucle ($i;1;Taille tableau($_valeur);1)

30. Si ($_valeur{$i}#"")

31. _champ{$Niveau_traitement}:=$_valeur{$i}

32. $sous_liste:=LH_Import ($Niveau_traitement;$_valeur{$i})

33. lh_compteur:=lh_compteur+1

34. AJOUTER A LISTE($liste;$_valeur{$i};lh_compteur;$sous_liste;Vrai)

35. Fin de si

36. Fin de boucle

37. Sinon

38. $liste:=0

39. Fin de si

40. Sinon

41. $liste:=0

42. Fin de si

44. $0:=$liste

45. Fin de cas

Nous obtenons avec cette méthode la liste suivante :

Cette liste est le résultat escompté ! CQFP (Ce qui fallait programmer).

La base exemple jointe est très simple. Vous lancez simplement la méthode « Test », puis visualisez le résultat dans le formulaire [Interface]Entrée.

IV - Aller plus loin

Pour aller plus loin, nous pouvons imaginer de mettre en relief certains niveaux grâce à des enregistrements de style, à l'ajout d'icônes… Nous sommes alors dans le traitement des listes hiérarchiques par programmation, et ceci est un autre sujet !

V - Base exemple

Téléchargez la base exemple :

Base pour Windows

Base pour Mac OS