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





Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.