IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Importer dans une liste hiérarchique

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

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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 :

Pictures 0121x0200

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

Pictures 0636x0242

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 :

Pictures 0549x0365

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 :

 
Sélectionnez
1.
2.
3.
4.
`méthode de test
C_ENTIER LONG($liste)
   $liste:=LH_Import
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 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
`méthode : LH_Import
Au cas ou
  : (Nombre de parametres=0)
    `initialisation de la méthode
  Sinon
    `traitement de la récursivité
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 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
`méthode : LH_Import
C_ENTIER LONG($0)
`liste hiérarchique obtenue
Au cas ou
  : (Nombre de parametres=0)
    `initialisation de la méthode
    $0:=
  Sinon
    `traitement de la récursivité
    $0:=
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 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
`méthode : LH_Import
C_ENTIER LONG($0) `liste hiérarchique obtenue
Au cas ou
  : (Nombre de parametres=0)
    `initialisation de la méthode
    lh_compteur:=0
    $0:=LH_Import (0)
  Sinon
    `traitement de la récursivité
    TOUT SELECTIONNER([IMPORT])
    VALEURS DISTINCTES([IMPORT]Champ1;$_valeur)
    $liste:=Nouvelle liste
    Boucle ($i;1;Taille tableau($_valeur);1)
        lh_compteur:=lh_compteur+1
        AJOUTER A LISTE($liste;$_valeur{$i};lh_compteur)
    Fin de boucle
    $0:=$liste
Fin de cas

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

Pictures 0380x0174

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 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
`méthode : LH_Import

C_ENTIER LONG($0)  `liste hiérarchique obtenue
C_ENTIER($1)  `Niveau dans la récursivité
C_ALPHA(80;$2) `valeur

Au cas ou
  : (Nombre de parametres=0)
    `initialisation de la méthode
    lh_compteur:=0
    $0:=LH_Import (0;"")
  Sinon
    `traitement de la récursivité
    Si ($1=0)
        TOUT SELECTIONNER([IMPORT])
        VALEURS DISTINCTES([IMPORT]Champ1;$_valeur)
    Sinon
        CHERCHER([IMPORT];[IMPORT]Champ1=$2)
        VALEURS DISTINCTES([IMPORT]Champ2;$_valeur)
    Fin de si

    $liste:=Nouvelle liste
    Boucle ($i;1;Taille tableau($_valeur);1)
    Si ($1=0)
        $sous_liste:=LH_Import (1;$_valeur{$i})
    Fin de si
    lh_compteur:=lh_compteur+1
    AJOUTER A LISTE($liste;$_valeur{$i};lh_compteur;$sous_liste;Vrai)
    Fin de boucle
    $0:=$liste
Fin de cas

La liste obtenue à ce stade est la suivante :

Pictures 0373x0165

À 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 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
`méthode : LH_Import

C_ENTIER LONG($0)  `liste hiérarchique obtenue
C_ENTIER($1)  `Niveau dans la récursivité

Au cas ou
  : (Nombre de parametres=0)
    `initialisation de la méthode
    lh_compteur:=0
    $0:=LH_Import (0;"")

  Sinon
    `traitement de la récursivité
    Si ($1=0)
    TOUT SELECTIONNER([IMPORT])
    VALEURS DISTINCTES([IMPORT]Champ1;$_valeur)
    Sinon
    CHERCHER([IMPORT];[IMPORT]Champ1=$2)
    VALEURS DISTINCTES([IMPORT]Champ2;$_valeur)
    Fin de si

    Si (Taille tableau($_valeur)>0)
        $liste:=Nouvelle liste
        Boucle ($i;1;Taille tableau($_valeur);1)
            Si ($1=0)
                $sous_liste:=LH_Import (1;$_valeur{$i})
            Fin de si
            lh_compteur:=lh_compteur+1
            AJOUTER A LISTE($liste;$_valeur{$i};lh_compteur;$sous_liste;Vrai)
        Fin de boucle
    Sinon
        $liste:=0
    Fin de si

    $0:=$liste
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 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
...
$Niveau_traitement:=$1+1
Si ($Niveau_traitement=1)
    TOUT SELECTIONNER([IMPORT])
Sinon
    CHERCHER([IMPORT];Champ(Table(->[IMPORT]);$Niveau_traitement-1)->=$2)
Fin de si
VALEURS DISTINCTES(Champ(Table(->[IMPORT]);$Niveau_traitement)->;$_valeur)
....

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

 
Sélectionnez
$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é :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
`méthode : LH_Import

C_ENTIER LONG($0)  `liste hiérarchique obtenue
C_ENTIER($1)  `Niveau dans la récursivité

Au cas ou
  : (Nombre de parametres=0)
    `initialisation de la méthode
    lh_compteur:=0
    $0:=LH_Import (0;"")

  Sinon
    `traitement de la récursivité
    $Niveau_traitement:=$1+1
    Si ($Niveau_traitement=1)
        TOUT SELECTIONNER([IMPORT])
    Sinon
        CHERCHER([IMPORT];Champ(Table(->[IMPORT]);$Niveau_traitement-1)->=$2)
    Fin de si
    VALEURS DISTINCTES(Champ(Table(->[IMPORT]);$Niveau_traitement)->;$_valeur)

    Si (Taille tableau($_valeur)>0)
        $liste:=Nouvelle liste
        Boucle ($i;1;Taille tableau($_valeur);1)
            $sous_liste:=LH_Import ($Niveau_traitement;$_valeur{$i})
            lh_compteur:=lh_compteur+1
            AJOUTER A LISTE($liste;$_valeur{$i};lh_compteur;$sous_liste;Vrai)
        Fin de boucle
    Sinon
        $liste:=0
    Fin de si

    $0:=$liste
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.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
`méthode : LH_Import

C_ENTIER LONG($0)  `liste hiérarchique obtenue
C_ENTIER($1)  `Niveau dans la récursivité

Au cas ou
  : (Nombre de parametres=0)
    `initialisation de la méthode
    lh_compteur:=0
    $0:=LH_Import (0;"")

  Sinon
    `traitement de la récursivité
    $Niveau_traitement:=$1+1
    Si ($Niveau_traitement<=Nombre de champs(Table(->[IMPORT])))
        Si ($Niveau_traitement=1)
            TOUT SELECTIONNER([IMPORT])
        Sinon
            CHERCHER([IMPORT];Champ(Table(->[IMPORT]);$Niveau_traitement-1)->=$2)
        Fin de si
        VALEURS DISTINCTES(Champ(Table(->[IMPORT]);$Niveau_traitement)->;$_valeur)

        Si (Taille tableau($_valeur)>0)
            $liste:=Nouvelle liste
            Boucle ($i;1;Taille tableau($_valeur);1)
                $sous_liste:=LH_Import ($Niveau_traitement;$_valeur{$i})
                lh_compteur:=lh_compteur+1
                AJOUTER A LISTE($liste;$_valeur{$i};lh_compteur;$sous_liste;Vrai)
            Fin de boucle
        Sinon
            $liste:=0
        Fin de si
    Sinon
        $liste:=0
    Fin de si

    $0:=$liste
Fin de cas

La liste obtenue est semblable à ceci :

Pictures 0441x0244

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 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
`méthode : LH_Import

C_ENTIER LONG($0)  `liste hiérarchique obtenue
C_ENTIER($1)  `Niveau dans la récursivité

Au cas ou
  : (Nombre de parametres=0)
    `initialisation de la méthode
    lh_compteur:=0
    $0:=LH_Import (0;"")

  Sinon
    `traitement de la récursivité
    $Niveau_traitement:=$1+1
    Si ($Niveau_traitement<=Nombre de champs(Table(->[IMPORT])))
        Si ($Niveau_traitement=1)
            TOUT SELECTIONNER([IMPORT])
        Sinon
            CHERCHER([IMPORT];Champ(Table(->[IMPORT]);$Niveau_traitement-1)->=$2)
        Fin de si
        VALEURS DISTINCTES(Champ(Table(->[IMPORT]);$Niveau_traitement)->;$_valeur)

        Si (Taille tableau($_valeur)>0)
            $liste:=Nouvelle liste
            Boucle ($i;1;Taille tableau($_valeur);1)
                Si ($_valeur{$i}#"")
                    $sous_liste:=LH_Import ($Niveau_traitement;$_valeur{$i})
                    lh_compteur:=lh_compteur+1
                    AJOUTER A LISTE($liste;$_valeur{$i};lh_compteur;$sous_liste;Vrai)
                Fin de si
            Fin de boucle
        Sinon
            $liste:=0
        Fin de si
    Sinon
        $liste:=0
    Fin de si

    $0:=$liste
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 :

Pictures 0450x0480

É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 :

 
Sélectionnez
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é :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
`méthode : LH_Import

C_ENTIER LONG($0)  `liste hiérarchique obtenue
C_ENTIER($1)  `Niveau dans la récursivité

Au cas ou
  : (Nombre de parametres=0)
    `initialisation de la méthode
    lh_compteur:=0
    TABLEAU ALPHA(80;_champ;Nombre de champs(Table(->[IMPORT])))
    $0:=LH_Import (0;"")

  Sinon
    `traitement de la récursivité
    $Niveau_traitement:=$1+1
    Si ($Niveau_traitement<=Nombre de champs(Table(->[IMPORT])))
        Si ($Niveau_traitement=1)
            TOUT SELECTIONNER([IMPORT])
        Sinon
            Boucle ($j;1;$1-1)
                CHERCHER([IMPORT];Champ(Table(->[IMPORT]);$j)->=_champ{$j};*)
            Fin de boucle
            CHERCHER([IMPORT];Champ(Table(->[IMPORT]);$Niveau_traitement-1)->=$2)
        Fin de si
        VALEURS DISTINCTES(Champ(Table(->[IMPORT]);$Niveau_traitement)->;$_valeur)

        Si (Taille tableau($_valeur)>0)
            $liste:=Nouvelle liste
            Boucle ($i;1;Taille tableau($_valeur);1)
                Si ($_valeur{$i}#"")
                    _champ{$Niveau_traitement}:=$_valeur{$i}
                    $sous_liste:=LH_Import ($Niveau_traitement;$_valeur{$i})
                    lh_compteur:=lh_compteur+1
                    AJOUTER A LISTE($liste;$_valeur{$i};lh_compteur;$sous_liste;Vrai)
                Fin de si
            Fin de boucle
        Sinon
            $liste:=0
        Fin de si
    Sinon
        $liste:=0
    Fin de si

    $0:=$liste
Fin de cas

Nous obtenons avec cette méthode la liste suivante :

Pictures 0438x0352

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

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

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.