Le script est fonctionnel, il ne manque plus que la création des dossiers de destination ainsi que la copie des fichiers Ajout .gitignore
476 lines
19 KiB
Python
476 lines
19 KiB
Python
# ~~ Script DORY Digitanie ~~
|
|
|
|
# Voir algorithme.txt pour plus de détails et commentaires
|
|
|
|
|
|
|
|
import os # getcwd(),
|
|
import openpyxl # gestion et manipulation de fichier Open XML
|
|
|
|
|
|
## TODO : voir le pb des dicos qui auraient plusieurs fois les memes clés (se qui n'est pas possible) à cause de plusieurs fichiers qui auraient la meme extension
|
|
|
|
|
|
|
|
# - une liste des extensions nécessaires
|
|
NECESSAIRES = ["cpg", "dbf", "prj", "shp", "shx"]
|
|
|
|
# - une liste des extentions auxiliaires
|
|
AUXILIAIRES = ["qix", "qmd", "qml"]
|
|
|
|
# - la configuration de la feuille Excel
|
|
CONFIG = {"NOM" : "A5",
|
|
"TYPE" : "B5",
|
|
"NB_FICHIERS" : "C5",
|
|
"ENCODAGE" : "D5",
|
|
"PROJECTION" : "E5",
|
|
"VALIDITE" : "F5"}
|
|
|
|
|
|
|
|
# REPRESENTATION DES DONNEES
|
|
|
|
class _Fichier:
|
|
def __init__(self,
|
|
nom_original="Pas de nom original",
|
|
chemin="Pas de chemin",
|
|
extension="Pas d'extension",
|
|
nom= "Pas de nom",
|
|
implication="Non-conforme"):
|
|
self.nom_original = nom_original # - son nom original - string
|
|
self.chemin = chemin # - son chemin - string
|
|
self.extension = extension # - son extension - string
|
|
self.nom = nom # - son nom formaté - string
|
|
self.implication = implication # - son implication - enum
|
|
# {"Nécessaire",
|
|
# "Auxiliaire",
|
|
# "Non-conforme",
|
|
# "A-ignorer"}
|
|
def afficher(self):
|
|
"""Affiche dans la sortie standard les éléments du fichier
|
|
"""
|
|
print("nom : ".center(16), self.nom)
|
|
print("nom orig : ".center(16), self.nom_original)
|
|
print("ext : ".center(16), self.extension)
|
|
print("chemin : ".center(16), self.chemin)
|
|
print("implication :".center(16), self.implication)
|
|
|
|
def impliquer(self):
|
|
"""Définir l'implication d'un fichier
|
|
|
|
Les fichiers nécessaires sont indispensables sinon l'importation
|
|
de la couche échoue.
|
|
Les fichiers optionels (ou auxiliaires) contiennent des données
|
|
supplémentaires lors de l'importation.
|
|
Les fichiers non conformes pourraient être importer dans QGIS. Mais,
|
|
ne sont pas conformes au cahier des charges.
|
|
Les fichiers ignorés ne fonctionnent pas dans QGIS.
|
|
L'implication est définie en fonction de l'extension du fichier.
|
|
"""
|
|
match self.extension:
|
|
case "dbf" | "cpg" | "prj" | "shx" | "shp":
|
|
self.implication = "Necessaire"
|
|
case "qix" | "qmd" | "qml":
|
|
self.implication = "Auxiliaire"
|
|
case "gpkg" | "sqlite":
|
|
self.implication = "Non-conforme"
|
|
case _:
|
|
self.implication = "A-ignorer"
|
|
# TODO : gerer la casse des extensions pour que le controle se fasse
|
|
|
|
|
|
|
|
## TODO Mettre les init de dico à vide {} plutot ??
|
|
class _Catalogue:
|
|
def __init__(self,
|
|
necessaires,
|
|
auxiliaires,
|
|
non_conformes
|
|
):
|
|
self.necessaires = necessaires # - son dictionnaire de fichiers nécessaires avec pour clé l'extention
|
|
# de chaque fichier - dico de paire string : _Fichier
|
|
self.auxiliaires = auxiliaires # - son dictionnaire de fichiers auxiliaires avec pour clé l'extention
|
|
# de chaque fichier - dico de paire string : _Fichier
|
|
self.non_conformes = non_conformes # - son dictionnaire de fichiers non-conformes avec pour clé l'extention
|
|
# de chaque fichier - dico de paire string : _Fichier
|
|
# PB : Pour le moment cette facon de representer des groupes de fichiers
|
|
# ne gèrent pas le fait qu'il y ai plusieurs fichiers avec la meme extension,
|
|
# en effet, je ne peux pas avoir plusieurs clés avec la meme extension
|
|
|
|
def afficher(self):
|
|
"""Affiche le catalogue dans la sortie standard
|
|
|
|
Affiche aussi un message si pour chaque dictionnaire vide
|
|
"""
|
|
if self.necessaires: # TODO renvoie False si dico = {} ou alors dois je écrire if necessaires == {}
|
|
print("fichiers nécessaires :".center(28))
|
|
for fichier in self.necessaires.values():
|
|
print(" ", fichier.nom, fichier.extension)
|
|
else:
|
|
print("Pas de Nécessaires".center(28))
|
|
if self.auxiliaires:
|
|
print("fichiers auxiliaires :".center(28))
|
|
for fichier in self.auxiliaires.values():
|
|
print(" ", fichier.nom, fichier.extension)
|
|
else:
|
|
print("Pas d'Auxiliaires".center(28))
|
|
if self.non_conformes:
|
|
print("fichiers non conformes :".center(28))
|
|
for fichier in self.non_conformes.values():
|
|
print(" ", fichier.non_conformes)
|
|
else:
|
|
print("Pas de Non-conformes".center(28))
|
|
|
|
|
|
|
|
class _Projet:
|
|
def __init__(self,
|
|
nom="Pas de nom",
|
|
necessaires = {},
|
|
auxiliaires = {},
|
|
non_conformes = {},
|
|
nb_fichiers=0,
|
|
conformite="Non conforme",
|
|
projection="Inconnu",
|
|
encodage="Inconnu"):
|
|
self.nom = nom # - son nom - string
|
|
self.necessaires = necessaires
|
|
self.auxiliaires = auxiliaires
|
|
self.non_conformes = non_conformes
|
|
self.nb_fichiers = nb_fichiers # - son nombre de fichiers - entier
|
|
self.conformite = conformite # - sa conformité - bool
|
|
self.projection = projection # - sa projection géodésique - string // ou énumération --> voir Constantes
|
|
self.encodage = encodage # - son type d'encodage prévu dans QGIS string
|
|
# /* pour ce script, ce devrait être forcement "UTF8" */
|
|
|
|
def afficher(self):
|
|
"""Affiche dans la sortie standard le contenu d'un projet
|
|
"""
|
|
print("nom : ".center(16), self.nom)
|
|
print("nb fichiers :".center(16), self.nb_fichiers)
|
|
print("conformité :".center(16), self.conformite)
|
|
print("projection :".center(16), self.projection)
|
|
print("encodage :".center(16), self.encodage)
|
|
print("necessaires :".center(16), self.necessaires)
|
|
print("auxiliaires :".center(16), self.auxiliaires)
|
|
print("non_conformes :".center(16), self.non_conformes)
|
|
#self.catalogue.afficher()
|
|
|
|
def cataloguer(self, fichier):
|
|
"""Catalogue le fichier dans le projet
|
|
|
|
La méthode ajoute le fichier suivant son extension
|
|
dans un des dictionnaires du catalogue
|
|
"""
|
|
match fichier.implication:
|
|
case "Necessaire":
|
|
self.necessaires[fichier.extension] = fichier
|
|
case "Auxiliaire":
|
|
self.auxiliaires[fichier.extension] = fichier
|
|
case "Non-conforme":
|
|
self.non_conformes[fichier.extension] = fichier
|
|
|
|
def lire_projection(self):
|
|
"""Récupère la projection des données géomatiques définie.
|
|
|
|
Lis la première ligne du fichier *.PRJ du projet
|
|
et controle la présence de la chaine "Lambert_93"
|
|
"""
|
|
if "prj" in self.necessaires:
|
|
df = open(self.necessaires["prj"].chemin)
|
|
chaine = df.readline()
|
|
df.close()
|
|
if "Lambert_93" in chaine:
|
|
self.projection = "LAMBERT93"
|
|
else:
|
|
print(projet.nom, " : pas de PRJ trouvé --> pas de projection définie")
|
|
|
|
def lire_encodage(self):
|
|
"""Récupère l'encodage prévu pour les fichiers dans QGIS
|
|
|
|
Lis la première ligne du fichier *.CPG du projet
|
|
et controle la présence de la chaine "UTF-8"
|
|
"""
|
|
if "cpg" in self.necessaires:
|
|
df = open(self.necessaires["cpg"].chemin)
|
|
chaine = df.readline()
|
|
#print("encod lu : ", chaine)
|
|
df.close()
|
|
if "UTF-8" in chaine:
|
|
self.encodage = "UTF8"
|
|
else:
|
|
print(projet.nom, " : pas de CPG trouvé --> pas d'encodage définie")
|
|
|
|
def valider(self):
|
|
"""Valide la conformité du projet
|
|
|
|
Controle si les 5 fichiers differents nécessaires sont présents.
|
|
Controle si la projection est correcte (pour le moment Lambert93).
|
|
Controle si l'encodage défini pour l'import dans QGIS est bien UTF8.
|
|
Si ces 3 controles sont OK, alors le projet est défini comme valide.
|
|
"""
|
|
nb_controles_OK = 0
|
|
# Controle présences fichiers
|
|
valide = [False, False, False, False, False]
|
|
for extension in self.necessaires.keys():
|
|
match extension:
|
|
case "dbf":
|
|
valide[0] = True
|
|
case "cpg":
|
|
valide[1] = True
|
|
case "prj" :
|
|
valide[2] = True
|
|
case "shx" :
|
|
valide[3] = True
|
|
case "shp" :
|
|
valide[4] = True
|
|
if valide[0] and valide[1] and valide[2] and valide[3] and valide[4]:
|
|
nb_controles_OK += 1
|
|
|
|
# Controle projection
|
|
if self.projection == "LAMBERT93":
|
|
nb_controles_OK += 1
|
|
|
|
# Controle encodage
|
|
if self.encodage == "UTF8":
|
|
nb_controles_OK += 1
|
|
|
|
# Si les 3 controles sont OK ALors le projet est conforme
|
|
if nb_controles_OK == 3:
|
|
self.conformite = "Conforme"
|
|
|
|
|
|
|
|
# DEFINITIONS DE FONCTIONS
|
|
|
|
def formatter(chaine):
|
|
"""Formate selon nomenclature.
|
|
|
|
Formate la chaine de caractères passée en paramètre :
|
|
Enlève tous les accents français. Enlève la cédille du C.
|
|
Remplace les espaces ' ' et les traits d'union '-' par
|
|
des tirets bas '_'.
|
|
Ne traite pas pour le moment le AE et OE ligaturé.
|
|
"""
|
|
#TODO : gérer les accents sur majuscules
|
|
#TODO : amélioration ou exercice, utiliser la méthode str.translate() et maketrans
|
|
resultat = ""
|
|
for c in chaine:
|
|
match c:
|
|
case "à" | "â" | "ä":
|
|
resultat+= "a"
|
|
case "é" | "è" | "ê" | "ë":
|
|
resultat+= "e"
|
|
case "î" | "ï":
|
|
resultat+= "i"
|
|
case "ô" | "ö":
|
|
resultat+= "o"
|
|
case "ù" | "û" | "ü":
|
|
resultat+= "u"
|
|
case "ÿ":
|
|
resultat+= "y"
|
|
case "ç":
|
|
resultat+= "c"
|
|
case " " | "-":
|
|
resultat+= "_"
|
|
case _:
|
|
resultat+= c
|
|
return resultat
|
|
|
|
|
|
|
|
|
|
# DEBUT DU PROGRAMME
|
|
|
|
liste = [] # - une liste des fichiers list _Fichier
|
|
dico = {} # - un dictionnaire des projets avec pour clé le nom de chaque projet
|
|
# dico de paire string : Entité Projet
|
|
racine = os.getcwd() # - un chemin absolue vers le repertoire racine à traiter string
|
|
print("Racine : ".center(18), racine)
|
|
|
|
|
|
|
|
# --recupérer le chemin du dossier à traiter
|
|
# Si il y a PAS un dossier de traitement dans le working directory Alors
|
|
pas_de_dossier = True
|
|
|
|
for a in os.scandir():
|
|
if a.is_dir():
|
|
#il y a un dossier
|
|
pas_de_dossier = False
|
|
print(f"Dossier à Traiter trouvé : {a.name}")
|
|
racine = a
|
|
if pas_de_dossier:
|
|
print("Pas de dossier trouvé...\nFin de programme\n")
|
|
os.system("pause")
|
|
# fin du programme
|
|
exit()
|
|
|
|
|
|
# --analyser le dossier
|
|
# initialiser une liste de _Fichiers
|
|
liste = []
|
|
# Pour Chaque fichier des dossiers et sous dossiers à explorer Faire
|
|
for dossier_courant, list_sousdossiers, list_fichiers in os.walk(racine): ##TODO voir la fonction fwalk qui renvoie un 4-tuple(dirpath, dirnames, filenames, dirfd), and it supports dir_fd
|
|
for fichier_courant in list_fichiers:
|
|
# --lire, analyser chaque fichier et peupler la liste
|
|
# initialiser un _Fichier
|
|
ce_Fichier = _Fichier()
|
|
# lire son chemin
|
|
ce_Fichier.chemin = dossier_courant + "\\" + fichier_courant
|
|
# déterminer son nom original et son extension
|
|
# lire le nom original
|
|
# lire son extension
|
|
ce_Fichier.nom_original, ce_Fichier.extension = fichier_courant.split(".")
|
|
# formatter et écrire le nom
|
|
ce_Fichier.nom = formatter(ce_Fichier.nom_original)
|
|
# déterminer son implication
|
|
ce_Fichier.impliquer()
|
|
# ajouter le _Fichier à la liste
|
|
liste.append(ce_Fichier)
|
|
# Fin Pour
|
|
|
|
# /* Ici la liste des _Fichiers doit être correctement remplie */
|
|
#TEST affichage de la liste de fichier
|
|
|
|
# initialiser un dictionnaire de _Projets avec comme clé leurs noms respectifs
|
|
print("Init Dico\n")
|
|
dico = {}
|
|
# Pour Chaque fichier de la liste Faire
|
|
for fichier_courant in liste:
|
|
if fichier_courant.implication not in "A-ignorer":
|
|
# --associer le fichier courant à un projet du dictionnaire
|
|
# /* Vérifier que le projet existe déjà dans le dico */
|
|
# Si le nom du fichier courant n'est pas dans le dico Alors
|
|
if fichier_courant.nom not in dico:
|
|
# initialiser un _Projet
|
|
projet = _Projet()
|
|
projet.necessaires = {}
|
|
projet.auxiliaires = {}
|
|
projet.non_conformes = {}
|
|
projet.nom = fichier_courant.nom
|
|
print("création clé : ", projet.nom)
|
|
# ajouter le nom comme clé dans le dictionnaire
|
|
# associer le projet à cette clé
|
|
dico[projet.nom] = projet
|
|
|
|
#test contenu du dico
|
|
|
|
# Fin Si
|
|
# cataloguer le fichier dans le projet concerné
|
|
projet.cataloguer(fichier_courant)
|
|
# il y a un fichier de plus au projet
|
|
projet.nb_fichiers += 1
|
|
else:
|
|
# sinon le fichier est a ignorer
|
|
print("Ignoré : ".center(16), fichier_courant.nom_original)
|
|
# Fin Pour
|
|
|
|
# Pour Chaque projet du dictionnaire Faire
|
|
for projet in dico.values():
|
|
# --analyser et completer les infos du projet courant
|
|
# /* conformité, projection, encodage etc */
|
|
# lire la projection du projet courant
|
|
projet.lire_projection()
|
|
# lire l'encodage du projet courant
|
|
projet.lire_encodage()
|
|
# Si tous les fichiers nécessaires sont présents ET
|
|
# la projection est correcte ET
|
|
# l'encodage est correct Alors
|
|
# passer le projet à conforme
|
|
projet.valider()
|
|
# Fin Si
|
|
# Fin Pour
|
|
|
|
# /* Ici le dictionnaire des _Projets doit être correctement remplie */
|
|
|
|
|
|
os.system("pause")
|
|
|
|
# --produire le rapport
|
|
# créer un classeur Excel
|
|
# TODO pour le moment je vais partir sur un modele deja ecrit que j'ouvre
|
|
# Contrainte : l'opérateur doit placer le modèle dans le même dossier racine que le present script
|
|
classeur = openpyxl.load_workbook("modele_VISA_DORY.xlsx")
|
|
feuille = classeur.active
|
|
|
|
|
|
# /* Note : peut être utiliser un modèle pré-config */
|
|
# --mettre en page la première feuille du classeur
|
|
# inserer une image
|
|
# écrire les nom des colonnes
|
|
# écrire les formules de colorations auto des colonnes
|
|
|
|
# Pour chaque projet du dictionnaire Faire
|
|
ligne_courante = 4
|
|
for nom_projet, projet in dico.items():
|
|
# --completer la feuille avec les infos du projet courant
|
|
# /* Note : Chaque ligne de la feuille correspond à un projet */
|
|
# /* Utiliser la config fournie en Constante */
|
|
# écrire le nom du projet
|
|
cell = feuille["A"+str(ligne_courante)]
|
|
cell.value = nom_projet
|
|
print(cell.value)
|
|
# écrire le type d'extention
|
|
cell = feuille["B"+str(ligne_courante)]
|
|
if "shp" in projet.necessaires:
|
|
# /* Note : ce sera "SHP" pour les projets conformes et le type
|
|
# natif pour les non-conformes */
|
|
cell.value = "SHP"
|
|
else:
|
|
for extension in projet.non_conformes:
|
|
cell.value = extension.upper()
|
|
print(cell.value)
|
|
# écrire le nombre de fichiers du projet
|
|
cell = feuille["C"+str(ligne_courante)]
|
|
cell.value = projet.nb_fichiers
|
|
# écrire l'encodage
|
|
cell = feuille["D"+str(ligne_courante)]
|
|
cell.value = projet.encodage
|
|
# écrire la projection
|
|
cell = feuille["E"+str(ligne_courante)]
|
|
cell.value = projet.projection
|
|
# écrire la validité (conformité)
|
|
cell = feuille["F"+str(ligne_courante)]
|
|
if projet.conformite in "Conforme":
|
|
cell.value = "OK"
|
|
else:
|
|
cell.value = "FAIL"
|
|
|
|
ligne_courante+= 1
|
|
|
|
# encadrer le "tout" pour délimiter le tableau de la feuille
|
|
# Fin Pour
|
|
|
|
# /* Ici le rapport doit être correctement produit et remplie */
|
|
classeur.save("VISA_DORY_PRE.xlsx")
|
|
print("ligne_courante = ", ligne_courante )
|
|
|
|
# --produire le dossier de mise à disposition
|
|
# créer un dossier "Travail" et un dossier "Livraison"
|
|
# --créer l'arborescence du dossier "Travail"
|
|
# le dossier Travail devient le working directory
|
|
# créer un dossier "00-Cadastre"
|
|
# créer un dossier "01-Donnees_Valides"
|
|
# créer un dossier "02-Donnees_Invalides
|
|
# créer un dossier "03-Production"
|
|
# créer un dossier "04-Verif_Geometrie"
|
|
# --peupler le dossier "Travail"
|
|
# Pour Chaque projet du dictionnaire Faire
|
|
# copier les fichiers des projets conforme dans le dossier "01"
|
|
# copier les fichiers des projets non-conformes dans le dossier "02"
|
|
# Fin Pour
|
|
|
|
# /* Ici les dossiers doivent être correctement produit et remplie */
|
|
|
|
# ARRET DU PROGRAMME
|
|
|
|
|
|
|
|
# /* TODO Controler qu'il y a bien les 5 necessaires par projet sinon FAIL
|
|
# --> a faire dans le controle de conformité de chaque projet */
|
|
|
|
# /* TODO S'assurer que les GPKG et autres non conformes peuplent correctement
|
|
# les infos dans les projets --> géré si l'initialisation des
|
|
# projets est correctement fait */
|