Activité Lire un fichier XML et rechercher des objets dans une liste

Situation

Vous avez un fichier XML dont vous aimeriez utiliser le contenu dans un programme. Toutefois, vous vous rendez rapidement compte que l’interprétation de la syntaxe du XML n’est pas aussi simple que celle du CSV.

Heureusement, vous apprenez à la suite de quelques recherches, qu’il existe une recommandation du World Wide Web Consortium (W3C) qui définit une manière standard de lire et modifier un document XML à l’aide d’un langage de programmation (le Document Objet Model ou DOM) et que la bibliothèque standard de Java (Java Class Library ou JCL) contient une implémentation de cette recommandation.

Consigne

Pour ce travail, on vous demande de réaliser les tâches décrites ci-après.

Objectifs

À la fin de ce travail, vous devez :

  1. Connaître le fait que le XML ne peut pas être interprété aussi simplement que le CSV.
  2. Connaître l'existence du DOM pour lire et modifier des fichiers XML.
  3. Être capable d'utiliser un objet de type `ArrayList` en Java.
  4. Être capable d'effectuer une recherche dans une liste d'objets en Java.

Résultat attendu

Un compte rendu de l’activité et les réponses aux questions posées dans une section de votre rapport du module.

Ressources

Documents :

Logiciel :

  • NetBeans

Lire une liste d’adresses à partir d’un fichier XML

La syntaxe du XML ne peut pas être interprétée aussi facilement que le CSV, il est nécessaire pour cela d’utiliser une bibliothèque existante. Dans cet exemple, nous allons utiliser la bibliothèque standard de Java (JCL) pour lire le contenu d’un fichier XML qui contient une liste d’adresses dans un objet de type List de Person (List<Person>).

Créez un nouveau projet ContactManager et copiez le code de la figure 1.

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package ch.epaifribourg.ict.m100;

import java.io.File;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.Element;


public class ContactManager {

    private static class Person {
        int id;
        String firstname = "";
        String lastname = "";
        String street = "";
        String postalCode = "";
        String city = "";
        String email = "";
    }

    public static void main(String[] args) throws Exception {

        // Initialise une chaîne de caractère avec le chemin du fichier XML.
        // Attention : Les backslashes (\) du chemin Windows sont dédoublés
        // car dans une chaîne de caractères, le backslash est le caractère
        // d’échappement qui permet d’écrire des caractères spéciaux tels que
        // \r (carriage return), \n (line feed) ou \t (tab).
        String filename = "C:\\Users\\frossardj\\Documents\\data.xml";

        // Crée un objet de type File avec le chemin du fichier XML.
        File xmlFile = new File(filename);

        // Charge les données des personnes qui se trouve dans le fichier XML
        // dans un objet de type List de Person et affecte une référence à cet
        // objet à la variable personList.
        List<Person> personList = loadPersonDataFromXml(xmlFile);

        // Affiche le résultat dans la sortie standard (System.out).
        printPersonList(System.out, personList);
    }

    /**
     * Charge les données des personnes qui se trouvent dans le fichier XML dans un objet 
     * de type List de Person et renvoie l’objet.
     *
     * @param file le fichier XML à lire
     * @return l’objet person correspondant.
     */
    private static List<Person> loadPersonDataFromXml(File file) throws Exception {

        // Crée un tableau dynamique.
        List<Person> personList = new ArrayList<>();

        // Crée un objet Document qui représente les données du fichier XML
        // sous la forme d’une hiérarchie d’objets de type Node. Un objet de
        // type nœud peut représenter aussi bien un élément, qu’un nœud
        // de texte ou un attribut.
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.parse(file);
        doc.getDocumentElement().normalize();

        // Recherche tous les elements <person>
        NodeList personNodeList = doc.getElementsByTagName("person");

        // Pour chaque élément XML de la liste
        for (int i = 0; i < personNodeList.getLength(); i = i + 1) {
            Node node = personNodeList.item(i);

            if (node.getNodeType() == Node.ELEMENT_NODE) {
               
                // Récupère l'élément
                Element personElement = (Element)node;

                // Crée un nouvel objet de type Person
                Person person = new Person();

                person.id = Integer.parseInt(personElement.getAttribute("id"));
                person.firstname = personElement.getElementsByTagName("firstname").item(0).getTextContent();
                person.lastname = personElement.getElementsByTagName("lastname").item(0).getTextContent();
                person.postalCode = personElement.getElementsByTagName("postal-code").item(0).getTextContent();
                person.street = personElement.getElementsByTagName("street").item(0).getTextContent();
                person.city = personElement.getElementsByTagName("city").item(0).getTextContent();
                person.email = personElement.getElementsByTagName("email").item(0).getTextContent();
                
                // Ajoute la personne à la liste
                personList.add(person);
    
            }
        }

        // Renvoie la référence à l’objet personList
        return personList;
    }

    /**
     * Ecrit les élément d’une liste d'objet de type Person dans le flux de sortie
     * passé en paramètre.
     *
     * @param out le flux de sortie
     * @param personList la liste d'objet de type Person
     */
    private static void printPersonList(PrintStream out, List<Person> personList) {

        for (int i = 0; i < personList.size(); i = i + 1) {
            Person person = personList.get(i);
            printPerson(out, person);
        }
    }

    /**
     * Ecrit les données d’un objet de type Person dans le flux de sortie
     * passé en paramètre.
     *
     * @param out le flux de sortie
     * @param person l’objet de type Person
     */
    private static void printPerson(PrintStream out, Person person) {

            out.printf("(%d) %s %s, %s, %s %s\r\n",
                    person.id,
                    person.firstname,
                    person.lastname,
                    person.street,
                    person.postalCode,
                    person.city);
    }
}
Fig. 1 – Lecture du fichier XML

Exécutez votre programme dans NetBeans et assurez-vous qu’il fonctionne sans erreur. Demandez de l’aide, si vous ne parvenez pas à le faire fonctionner après avoir tout vérifié.

Rechercher des personnes selon un critère dans une liste de personnes

Au lieu d’afficher la liste de toutes les personnes, on aimerait maintenant permettre à l’utilisateur de saisir un nom (id) et afficher uniquement les données des personnes qui portent ce nom. De plus, on aimerait mesurer la durée la recherche et afficher cet durée à la suite des données des personnes trouvées.

Commencez par ajouter la fonction de la figure 2 dans votre code.

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
    /**
     * Cherche dans une liste de personnes les personnes dont le nom est celui
     * passé en paramètre et renvoie la liste des personnes trouvées.
     * Si aucune personne n'a été trouvée, la fonction renvoie une liste vide.
     *
     * @param personList la liste de personnes
     * @param name le nom de la personne recherchée
     * @return l'objet correspondant à la personne
     */
    private static List<Person> findPersonByName1(List<Person> personList, String name) {

        // Crée un nouveau tableau dynamique pour y stocker les
        // personnes qui correspondent au critère de recherche
        List<Person> result = new ArrayList<>();

        // Répète pour chaque personne de la liste.
        for (int i = 0; i < personList.size(); i = i + 1) {

            // Récupère l'objet de type Person.
            Person person = personList.get(i);

            // Teste si le nom de la personne correspond
            // à celui passé en paramètre.
            if (person.lastname.equals(name)) {
                // Ajoute la personne à la liste des
                // personnes trouvées.
                result.add(person);
            }
        }

        // Renvoie la liste des personnes trouvées.
        return result;
    }
Fig. 2 – Fonction de recherche d'une personne à partir de son identifiant.

Allez ensuite la procédure main et supprimez l’appelle de la procédure printPersonList et remplacez-le par le code nécessaire pour demander la valeur de l’identifiant à l’utilisateur en utilisant un objet de type Console, recherchez la personne à l’aide de la fonction que vous venez d’ajouter (findPersonByName1) et afficher les données de la personne avec la procédure printPerson.

Essayez de réaliser cette modification par vous-même puis vérifiez qu’il correspond à celui de la figure 3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    ...

    public static void main(String[] args) throws Exception {

        ...

        // Demande à l'utilisateur de saisir le nom de la personne recherchée.
        System.out.printf("Veuillez saisir le nom d’une person : ");
        Console console = System.console();
        String name = console.readLine();

        // Recherche les personnes dans liste.
        List<Person> result = findPersonByName1(personList, name);

        // Affiche le résultat dans la sortie standard (System.out).
        printPersonList(System.out, result);
    }

    ...
Fig. 3 – Rechercher une personne à partir de son identifiant.

Exécutez votre programme dans NetBeans une fois en indiquant le nom Alexander et une fois en indiquant le nom Walker. S’il fonctionne correctement, vous devriez obtenir les résultats suivant :

Veuillez saisir le nom d'une person : Alexander
(9) Camden Alexander, 4263 London, 30579 Atlanta
(306) Warren Alexander, 2554 Eisenhower Central Av, 18187 Allentown
(465) Leandro Alexander, 540 Kennedy Jackson Bl, 55958 Minneapolis
(1579) Grady Alexander, 7371 Mozart, 73734 Oklahoma City
(2280) Aidan Alexander, 9247 I57 111th St, 5623 Montpelier
(3770) Paityn Alexander, 1170 Wieland, 61159 Rockford
(4017) Trevor Alexander, 2028 Oakland, 37602 Nashville
(4522) Johan Alexander, 3299 Maypole, 28774 Charlotte
(5274) Rex Alexander, 4110 Cannon, 98021 Tacoma
(7550) Payton Alexander, 1823 Mautene, 27124 Raleigh
(8341) Abby Alexander, 4470 Picardy, 15689 Pittsburgh
(9478) Marshall Alexander, 4482 Pontiac, 3105 Manchester

Veuillez saisir le nom d'une person : Walker
(36) Danna Walker, 5513 Vernon, 53134 Madison
(821) Rachel Walker, 3591 Navarre, 28446 Charlotte
(1892) Shawn Walker, 1304 Monterey, 84033 West Valley City
(2824) Mike Walker, 6331 Baggot, 48311 Sterling Heights
(3158) Valeria Walker, 5565 Lamon, 89349 Las Vegas
(4170) Journee Walker, 9813 Palmer, 32335 Tallahassee
(5482) Madalynn Walker, 9329 Old Lake Shore, 23658 Hampton
(6156) Santino Walker, 9708 87th, 99339 Anchorage
(6299) Dustin Walker, 9321 Lee, 6136 Hartford
(6906) Lauren Walker, 7207 Front, 98095 Bellevue
(8522) Breanna Walker, 4005 Wacker Lower, 48115 Ann Arbor
(9657) Braelynn Walker, 7720 Henke, 55475 Minneapolis
(9709) Serena Walker, 1458 111th, 49584 Grand Rapids

Il reste maintenant à mesurer le temps d’exécution de la fonction findPersonByName1. Pour cela, on peut prendre le temps avant et après l’appel de la fonction avec la fonction System.nanoTime et la différence de ces deux temps nous donne le temps d’exécution en ns.

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
    ...

    public static void main(String[] args) throws Exception {

        ...

        // Demande à l'utilisateur de saisir le nom de la personne recherchée.
        System.out.printf("Veuillez saisir le nom d’une person : ");
        Console console = System.console();
        String name = console.readLine();

        // Mesure le temps avant l'exécution
        long startTime = System.nanoTime();

        // Recherche les personnes dans liste.
        List<Person> result = findPersonByName1(personList, name);

        // Mesure le temps après l'exécution
        long endTime = System.nanoTime();

        // Affiche le résultat dans la sortie standard (System.out).
        printPersonList(System.out, result);

        // Affiche le temps d'exécution
        System.out.printf("\nTemps d'exécution : %d ns\n", endTime - startTime);
    }

    ...
Fig. 4 – Mesure du temps d'exécution.

Exécuter plusieurs fois (5 ou 6 fois) le programme en indiquant le nom Alexander et notez à chaque fois le temps d’exécution. Calculer la moyenne des temps d’exécution pour le nom Alexander et faites la même expérience avec le nom Walker. Pour finir, refaites l’expérience avec le nom Skywalker.

Questions

  1. Le temps d’exécution pour une recherche dépend-il du nom que vous avez utilisé ?
  2. Que se passerait-il d’après vous, si on multipliait le nombre d’élément par 10 ou par 100 ? Expliquez votre raisonnement.
  3. Que pourrait-on faire pour diminuer le temps nécessaire à une recherche ? (Pensez à la manière dont vous recherchez un mot dans le dictionnaire)