Menu

2013-01-23  Edit

crim.fr, prof java

Analyse de texte structuré et fréquence de mots

Correction du projet de rattrapage

Sujet : création d'une liste de fréquences de mots

L'interface à implémenter :

/*
 * Projet de rattrapage 1er semestre - Liste de fréquences de mots dans un texte 
 */
package fr.crim.a2012.freqlist;

import java.io.File;
import java.io.IOException;

/**
 * Contrat pour un outil élémentaire de statistiques lexicales
 * 
 * @author Frédéric GLORIEUX
 * @author Pierre DITTGEN
 */
public interface FreqList {
    /**
     * Charger un fichier texte
     */
    void lit(File f) throws IOException;

    /**
     * Donner une liste des n mots les plus fréquents (un par ligne),
     * selon le format "mot,fréquence"
     */
    void afficheTetes(int n);
}

Une classe abstraite qui fait une partie du travail :

/*
 * Projet de rattrapage 1er semestre - Liste de fréquences de mots dans un texte 
 */
package fr.crim.a2012.freqlist;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

/**
 * Une classe abstraite qui traite la lecture du fichier d'entrée 
 * 
 * @author Frédéric Glorieux
 * @author Pierre Dittgen
 */
public abstract class AbstractFreqlist implements FreqList {

    /** Ensemble de mots vides, i.e. à ne pas prendre en compte à la lecture. */
    private    Set<String> lesMotsVides = new HashSet<String>();

    /**
     * Constructeur.
     * Charge la liste de mots vides à partir du fichier rsc/fr.stop
     * 
     * @throws IOException en cas d'erreur lors de la lecture du fichier de
     * mots vides
     */
    public AbstractFreqlist() throws IOException {
        // Charger la liste des mots vides
        Scanner scan = new Scanner(new File("rsc/fr.stop"), "UTF-8");
        String ligne;
        while (scan.hasNextLine()) {
            ligne = scan.nextLine().trim();
            if (ligne.isEmpty() || ligne.charAt(0) == '#') {
                continue;
            }
            lesMotsVides.add(ligne);
        }
    }

    /**
     * Cette méthode lit le contenu du fichier passé en paramètre. Pour chaque
     * mot non vide rencontré, elle appelle la méthode mot()
     * @param f Le fichier à lire
     * @see #mot(String)
     * @throws IOException En cas d'erreur durant la lecture du fichier
     */
    @Override
    public void lit(File f) throws IOException {
        Scanner scan = new Scanner(f, "UTF-8");

        // séparateur, tout ce qui n'est pas lettre selon l'unicode
        scan.useDelimiter("\\P{L}+"); 
        String mot;
        while (scan.hasNext()) {
            mot = scan.next().toLowerCase();
            if (mot.isEmpty() || lesMotsVides.contains(mot)) {
                continue;
            }
            mot(mot);
        }
    }

    /**
     * Méthode appelée chaque fois qu'un mot non vide est lu dans le fichier
     * d'entrée. Cette méthode affiche le mot lu.
     * Il est nécessaire de surcharger cette méthode pour la stocker dans 
     * une liste de fréquences
     * @param mot le mot lu
     */
    protected void mot(String mot) {
        System.out.println(mot);
    }

    /**
     * Tester une implémentation de liste de fréquences
     * @param impl une instance de classe implémentant l'interface FreqList
     */
    static public void test(FreqList impl) throws IOException {

        // Lit la société du spectable de Guy Debord
        impl.lit(new File("rsc/debord_spectacle.txt"));

        // Affiche les 20 mots plus fréquents par fréquence décroissante
        System.out.println("Les 20 mots les plus fréquemment rencontrés dans le texte :");
        impl.afficheTetes(20);
    }
}

Une solution :

/*
 * Projet de rattrapage 1er semestre - Liste de fréquences de mots dans un texte 
 */
package fr.crim.a2012.freqlist;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * Implémentation
 * @author Frédéric Glorieux
 */
public class FgFreqlist extends AbstractFreqlist {

    /** Pour stocker les fréquences des mots */
    private Map<String,Integer> dico = new HashMap<String,Integer>();

    /**
     * Constructeur
     * @throws Exception
     */
    public FgFreqlist() throws IOException {
        super();
    }

    @Override
    public void mot(String mot) {
        if (dico.containsKey(mot)) {
            dico.put(mot, new Integer(dico.get(mot)+1));
        } else {
            dico.put(mot, new Integer(1));
        }
    }

    @Override
    public void afficheTetes(int n) {
        Forme[] liste = new Forme[dico.size()];
        int i = 0;
        for (String forme : dico.keySet()) {
            liste[i] = new Forme(forme, dico.get(forme));
            i++;
        }
        Arrays.sort(liste);
        for (i=0; i<n; i++) {
            System.out.println(liste[i]);
        }
    }

    /**
     * Tester les méthodes abstraites partagées
     */
    public static void main(String[] args) throws Exception {
        AbstractFreqlist.test(new FgFreqlist());
    }

    /**
     * Un objet qui combine le terme et sa fréquence
     */
    public class Forme implements Comparable<Forme> {
        private String mot;
        private int compte;

        public Forme(String mot, int compte) {
            this.mot = mot;
            this.compte = compte;
        }

        public String getMot() {
            return mot;
        }

        public int getCompte() {
            return compte;
        }

        public String toString() {
            return mot+","+compte;
        }

        @Override
        public int compareTo(Forme o) {
            return o.getCompte()-this.getCompte();
        }
    }
}

Rappel : utilisation de SAX (Simple API for XML)

SAX et DOM : deux API standardisées pour l'accès aux documents XML

  • DOM : arborescence en mémoire, accès et requêtes (XPath), lecture/écriture
  • SAX : lecture en continue, envoi d'événements :
    • début du document
    • fin du document
    • balise ouvrante
    • balise fermante
    • contenu texte

Donner la liste des événements émis lors de l'analyse de ce document par un parseur SAX :

<?xml version="1.0"?>
<menu date="2013-23-10">
    <entree>œufs mayonnaise</entree>
    <plat><viande>jambon</viande> <legume>coquillettes</legume></plat>
    <dessert>crème caramel</dessert>
</menu>

En Java

  • Package org.xml.sax
  • On crée une classe qui hérite de org.xml.sax.helpers.DefaultHandler en surchargeant les méthodes qui nous intéressent
  • Et on en passe une instance au parser SAX, le parser appelle ensuite les méthodes du handler durant la lecture de la source XML

Créer une classe héritant de DefaultHandler :

public class MyHandler extends DefaultHandler {
    ...

Analyser un document XML en utilisant notre handler personnalisé :

MyHandler handler = new MyHandler();

SAXParserFactory spf = SAXParserFactory.newInstance();
// pour éviter la validation de la DTD
spf.setValidating(false);
spf.setFeature("http://xml.org/sax/features/validation", false);
// Création de l'instance de parser
SAXParser saxParser = spf.newSAXParser();
// Et analyse d'un fichier en utilisant le handler personnalisé
saxParser.parse("rsc/monfichier.xml", handler);

SAXWordCount

La classe fr.crim.saxigraph.SAXWordCount développée lors de la séance du 09/01/2013 affiche les différentes balises trouvées dans le document XML analysé et pour chaque balise, le nombre d'occurrences rencontrées.

Résultats sur le texte étudié (encodé en TEI) :

p,2084
said,631
hi,116
div,47
pb,47
measure,4
date,2
title,2
bibl,2
body,1
creation,1
anchor,1
author,1
titleStmt,1
extent,1
TEI,1
sourceDesc,1
profileDesc,1
publicationStmt,1
note,1

TP : Création d'une liste de fréquences sur le texte étudié

  • Analyse XML du texte avec SAX
  • Traitement du texte compris à l'intérieur des balises <p>...</p> et <said>...</said>
  • Découpage en mots (méthode split)
  • Suppression des mots vides (cf. projet de rattrapage, source et fichier de mots vides disponibles sur la page 2012-12-19)
  • Remplissage d'une table de fréquences des mots rencontrés

Étape 1 : Analyse du document XML

  • Créer un nouveau projet XMLFrequences
  • Reprendre la classe fr.crim.a2012.saxigraph.SAXWordCount et la renommer SAXFrequence
  • Modifier la classe pour afficher uniquement le contenu texte des balises <p> et <said>

Etape intermédiaire

Créer un fichier rsc/menu.xml avec le contenu suivant :

<?xml version="1.0"?>
<menu date="2013-23-10">
    <entree>œufs mayonnaise</entree>
    <plat><viande>jambon</viande> <legume>coquillettes</legume></plat>
    <dessert>crème caramel</dessert>
</menu>

Modifier la classe pour afficher seulement le contenu texte de la balise <plat>

État de la classe en fin de séance :

/*
 * Cours Java / POO
 * M2 Pro Ingénierie Multilingue
 * INALCO
 */
package fr.crim.a2012.saxigraph;

import java.io.IOException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Une spécialisation de la classe DefaultHandler qui affiche sur la sortie
 * standard le contenu texte des balises &lt;plat&gt; 
 * 
 * @author Pierre DITTGEN
 */
public class SAXFrequence extends DefaultHandler {

    private StringBuilder text = new StringBuilder();
    private boolean dansPlat;

    /**
     * Méthode appelée sur la lecture de contenu texte
     */
    @Override
    public void characters(char[] ch, int start, int length) {
        if (dansPlat) {
            text.append(ch, start, length);
        }
    }

    /**
     * Méthode appelée sur une balise ouvrante
     */
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attrs) {
        if ("plat".equals(localName)) {
            dansPlat = true;

            // Remet le contenu du string builder à 0
            text.setLength(0);
        }
    }

    /**
     * Méthode appelée sur une balise fermante
     */
    @Override
    public void endElement(String uri, String localName, String qName) {
        if ("plat".equals(localName)) {
            dansPlat = false;
            System.out.println(text);
        }
    }

    /**
     * Méthode principale
     * @param args paramètres de la ligne de commande (ignorés)
     * @throws SAXException En cas de XML non valide
     * @throws ParserConfigurationException Peu probable
     * @throws IOException Si le fichier XML n'est pas trouvé par exemple
     */
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        SAXParserFactory spf = SAXParserFactory.newInstance();
        spf.setNamespaceAware(true);
        spf.setValidating(false);
        spf.setFeature("http://xml.org/sax/features/validation", false);
        SAXParser saxParser = spf.newSAXParser();
        saxParser.parse("rsc/menu.xml", new SAXFrequence());
    }
}

Related

Wiki: 2012-2013

Discussion

  • crim.fr, prof java

    Pour la prochaine séance (30/01/2013)

    • Reprendre la classe fr.crim.a2012.saxigraph.SAXFrequence développée en séance et la modifier pour afficher uniquement le contenu texte des balises <p> et <said>
    • Le fichier source de la classe fr.crim.a2012.saxigraph.SAXFrequence est à envoyer par mail avant le 30/01/2013 à 9h
     
  • Frédéric Glorieux

    Liste de mots vides

     

Anonymous
Anonymous

Add attachments
Cancel