Utilisateur:Seudo/doublons.py

La bibliothèque libre.
# -*- coding: utf-8 -*-

# Analyse un dump wikisource et affiche les cas où
# deux pages font la même transclusion (même index,
# même pages de début et de fin, même sections...)

# ##########################################################
# Mode d'emploi : 
# - télécharger un dump Wikisource depuis https://dumps.wikimedia.org/backup-index.html
#   (chercher "frwikisource", puis choisir "All pages, current versions only")
# - décompacter le fichier quelque part sur son disque dur
# - indiquer le chemin dans la variable dumpfic ci-dessous 
#   (os.environ["USERPROFILE"] correspond au dossier de l'utilisateur courant)
# - lancer le programme : python dumpfic.py (durée : quelques minutes, plusieurs
#   millions de pages sont traitées)
# - une liste des doublons est générée dans un fichier dont l'emplacement est 
#   indiqué par la variable out_doublons.
# - cette liste est rédigée en wikicode, donc il suffit de la copier dans une 
#   page temporaire de Wikisource pour voir le résultat.

import locale
import os

# Définir la locale française
locale.setlocale(locale.LC_ALL, 'fr_FR')

dumpid = "frwikisource-20231001-pages-meta-current.xml"

# Indiquer ici le chemin où a été décompacté le dump
dumpfic = (os.environ["USERPROFILE"] + f"/tmp/{dumpid}/{dumpid}")

# Le fichier généré sera ici :
out_doublons = (os.environ["USERPROFILE"] + "/tmp/frwikidump-doublons.txt")

# Fin de la configuration
# ##########################################################

import re
import xml.etree.ElementTree as ET

NS_PRINCIPAL = "0"

def main():
    #read_debut()
    find_doublons()
    
def read_debut():
    maxlines = 10000
    cpt = 0
    with open(dumpfic, "r", encoding="utf-8") as fd:
        while cpt < maxlines:
            cpt += 1
            print(fd.readline())
            
def fmtnb(n):
    return locale.format_string("%d", n, grouping=True)
        
def find_doublons():
    maxarticles = 10000000  # simple précaution pour éviter une boucle infinie
    cpt = 0
    cur_title = ""
    cur_ns = None
    cur_text = ""
    transclusions = {} # relie un index aux pages qui l'utilisent
    print("Début de la lecture du dump...")
    for event, element in ET.iterparse(dumpfic):
        if maxarticles >= 0 and cpt >= maxarticles:
            print("Nombre maximum d'articles atteint")
            break
        tag = re.sub(r'^\{[^\}]*\}', 
                     '',
                     element.tag)
        if tag == "title":
            cur_title = element.text
        elif tag == "ns":
            cur_ns = element.text
        elif tag == "text":
            if cur_ns == NS_PRINCIPAL:
                if cur_text:
                    if cur_title == "Dictionnaire de théologie catholique/ANGE DE LA PASSION":
                        print(f"cur_text existe déjà {cur_title}")
                cur_text += element.text
        elif tag == "page":
            cpt += 1
            if cpt % 50000 == 0:
                print(f"Traité {fmtnb(cpt)} pages...")
            if cur_ns == NS_PRINCIPAL:
                if not cur_text:
                    continue
                # On ignore les cas où deux transclusions se trouvent
                # dans la même page
                if re.search(r"<pages\b .* > .* <pages\b .* >", 
                             cur_text, 
                             re.I | re.S | re.X):
                    continue
                resm = re.search(r'<pages\b ( [^>]+ )>',
                                 cur_text, 
                                 re.I | re.S | re.X)
                if resm:
                    # On cherche les transclusions identiques
                    # (index, from, to, etc.)
                    pagestag = resm.group(1)
                    resm = re.search(r"""\b index=(?: "([^"]+)" 
                                                    | '([^']+)'
                                                    |  ([^\s'"]+) )""",
                                     pagestag, re.I | re.X)
                    unir3 = lambda r: "".join([r.group(x) or "" for x in [1, 2, 3]])
                    if resm:
                        index = unir3(resm)
                        key = ""
                        for m in (["from", "to", "exclude", "include",
                                   "fromsection", "tosection", "onlysection"]):
                            resm = re.search(r"\b " + m + r"""\s*=\s*(?: "([^"]+)" 
                                                                       | '([^']+)'
                                                                       |  ([^\s\/\>'"]+) )""",
                                             pagestag, 
                                             re.I | re.S | re.X)
                            if resm:
                                key += f" {m}={unir3(resm)}" 
                            
                        key = key.strip()
                        if not index in transclusions:
                            transclusions[index] = {}
                        if not key in transclusions[index]:
                            transclusions[index][key] = []
                        transclusions[index][key].append(cur_title)
            cur_ns = None
            cur_text = ""
            cur_title = ""
        element.clear() # Important !
    
    print("Fin de la lecture du dump")
    with open(out_doublons, "w", encoding="utf-8") as outfd:
        outfd.write("== Liste de doublons de transclusion potentiels ==\n")
        outfd.write(f"''Généré à partir du dump {os.path.basename(dumpfic)} avec le script "
                    "[[Utilisateur:Seudo/doublons.py|doublons.py]]''\n")
        
        cpt_doublons = 0
        indexes = sorted(transclusions.keys())
        cpt_index = 0
        fauxpositifs = {}  # Faux positifs présumés
        for index in indexes:
            index_printed = False
            keys = sorted(transclusions[index].keys())
            for key in keys:
                nb_transclusions = len(transclusions[index][key])
                if nb_transclusions >= 2:
                    if(key in ["from=1 to=1", "from=2 to=2"]):
                        if index not in fauxpositifs:
                            fauxpositifs[index] = {}
                        fauxpositifs[index][key] = transclusions[index][key]
                    else:
                        cpt_doublons += 1
                        if not index_printed:
                            group_index = 50
                            if cpt_index % group_index == 0:
                                outfd.write(f"\n=== Index n<sup>o</sup> "
                                            f"{fmtnb(cpt_index + 1)} à "
                                            f"{fmtnb(cpt_index + group_index + 1)} "
                                            "===")
                            outfd.write(f"\n{fmtnb(cpt_index+1)}. "
                                        f"[[Livre:{index}]]")
                            cpt_index += 1
                            index_printed = True
                        txt = key.strip()
                        if not txt:
                            txt = "(fac-similé entier)"
                        outfd.write(f"\n:: ''{txt}'' :"
                                    + "".join([f"\n::# [[{x}]]" 
                                               for x in transclusions[index][key]]))
        indexes = sorted(fauxpositifs.keys())
        if len(indexes) > 0:
            outfd.write("\n== Cas spéciaux ==")
            outfd.write("\nCas où seule la page 1 (from=1 to=1) ou 2 (from=2 to=2) est transcluse.\n");
            cpt_index = 0
            for index in indexes:
                outfd.write(f"\n{fmtnb(cpt_index+1)}. "
                            f"[[Livre:{index}]]")
                keys = sorted(fauxpositifs[index].keys())
                for key in keys:
                    txt = key.strip()
                    if not txt:
                        txt = "(fac-similé entier)"
                    outfd.write(f"\n:: ''{txt}'' :"
                                + "".join([f"\n::# [[{x}]]" 
                                           for x in fauxpositifs[index][key]]))
                cpt_index += 1
        print(f"Nombre de doublons potentiels détectés : {fmtnb(cpt_doublons)}")
        print(f"Nombre de faux positifs présumés : {fmtnb(len(fauxpositifs.keys()))}.");
    print(f"La liste des doublons potentiels a été enregistrée dans {out_doublons}")
    
main()