Exercice utilitaire python #50
Labels
No Milestone
No project
No Assignees
4 Participants
Notifications
Due Date
Dependencies
No dependencies set.
Reference: ciri/stage_2023#50
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
J’ai utilisé deux méthodes pour afficher le fichier créé :
FileNotFoundError: [Errno 2] No such file or directory: ‘Text_Python.txt’
. La solution trouvée consistait simplement à indiquer le chemin d'accès du fichier :r
pour lire le texte :a
pour ajouter une phrase à la fin du texte :Résultat :
Fonction
open
La fonction
open
est décrite ici :https://docs.python.org/fr/3.11/library/functions.html?highlight=open#open
file est un objet simili-chemin donnant le chemin (absolu ou relatif au répertoire courant)
Dans notre cas, soit on sait ce qu’est le répertoire courant, soit on met un chemin absolu, comme vous l’avez fait.
Pour trouver le répertoire courant dans IDLE (ou à la ligne de commande) j’ai trouvé ceci :
https://stackoverflow.com/questions/15821121/whats-the-working-directory-when-using-idle
Si on connaît le répertoire courant, on peut utiliser un chemin relatif à celui-ci.
Remarquez que les deux syntaxes que vous employez sont très similaires. Dans les deux cas, vous assignez la ‘valeur’ de
open("Text_Python.txt", "r")
à une chose que vous appelezf
, mais que vous pourriez appeler tout autre chose.Le mode
a
veut direappend
et donc les choses ajoutées au fichier avecwrite
s’y ajouteront à la fin du fichier.Méthode
read
La méthode
read
est décrite ici :https://docs.python.org/fr/3/tutorial/inputoutput.html#methods-of-file-objects
Ici,
read
lit le contenu def
, dans le premier cas l’assigne àfile
, dans le second cas le passe directement àprint
.Fonction
print
La fonction
print
est décrite ici :https://docs.python.org/fr/3.11/library/functions.html?highlight=print#print
Dans le premier cas, vous appliquez
print
àfile
, qui contientf.read()
, et dans l’autre vous appliquez directementprint
àf.read
. Le résultat est donc exactement le même, mais c’est intéressant de voir qu’on peut travailler de manières différentes selon nos besoins. Peut-être qu’on aura besoin de travailler séparément surfile
plus loin, alors garderf.read()
dansfile
ça peut être utile.Méthode
write
La méthode est décrite sous
read
.Elle nécessite que
open
ait été lancé avec un mode qui autorise l’écriture.Ce qu’on peut faire maintenant
Nos fichiers vont être composés de lignes et il nous sera important de lire les fichiers ligne par ligne.
La suite de cet exercice pourrait donc être la suivante :
.py
Ce code ne fonctionne pas parce que je suis mauvais en Python, à vous de voir comment le corriger.
Travailler avec des fichiers en Python
1. Ouvrir et fermer des fichiers
Pour travailler avec des fichiers en Python, il est préférable d'utiliser la construction
with open('fichier.ext') as machin:
plutôt que de les ouvrir directement et les fermer manuellement.2. Chemins des fichiers
Je vous conseille fortement d'utiliser le module
pathlib
pour travailler avec les fichiers.La documentation officielle :
https://docs.python.org/fr/3/library/pathlib.html (en français)
https://docs.python.org/3/library/pathlib.html (en anglais)
Deux articles que j'ai trouvés fort utiles pour apprendre ce module :
https://pbpython.com/pathlib-intro.html
https://realpython.com/python-pathlib/
Ça peut paraître plus compliqué au début, mais l'utilisation de ce module permet d'éviter bien des maux de tête plus tard si on veut que le script fonctionne sous Linux, MacOS et Windows.
3. Mauvais en Python ?
Sans donner la réponse, je vous offre un petit indice pour comprendre pourquoi le code de Jean-Christophe ne fonctionne pas. Il faut regarder attentivement comment la fonction
read()
lit les fichiers et réfléchir à ce que ça implique par rapport à l'observation suivante :Il y a au moins trois façons de modifier le code de Jean-Christophe pour qu'il donne le résultat attendu.
Je pourrai vous expliquer laquelle je choisirais et pourquoi après que vous aurez présenté les vôtres.
@Anand a réussi avec la méthode ci-dessus, mais cela n'a pas fonctionné pour @Leodelaune et moi. Voici donc les deux méthodes que nous avons utilisées et les résultats obtenus.
et
Comme vous pouvez le voir, la première ligne du fichier n'est pas prise en compte.
J'ai trouvé une alternative qui prend en compte la première ligne du fichier :
Le code ci-dessus tel quel n'imprimera rien. Je pense que le succès de Anand provient plutôt du code qu'il a présenté dans son commentaire avec l'alternative qui tient compte de la première ligne du fichier.
Je ne cite que la deuxième parce qu'elle est à préférer et, qu'à l'exception du changement de nom de la variable
f
àfile_object
les deux méthodes sont en fait identiques en ce qui concerne le code qui lit le fichier, modifie les lignes, et les imprime.La seule — mais importante ! — différence entre les deux tentatives est la façon d'ouvrir le fichier. Les lignes suivantes
et
font exactement la même chose : elles ouvrent le fichier dans un object appelé
f
.L'importante différence, c'est que si l'on utilise
f = open()
, pour ouvrir le fichier, il faut ensuite le fermer manuellement avecf.close()
une fois qu'on a finit de l'utiliser.Par contre, la construction
with open() as
ferme automatiquement le fichier une fois qu'on a fini de l'utiliser. C'est (en simplifiant) la raison principale pour laquelle l'utilisation dewith
est préférable.Si on vérifie les valeurs dans les variables, on constate que pour les deux méthodes, la première ligne du fichier se trouve dans la variable
contents
.Le cœur des deux tentatives est identique :
Une fois le fichier ouvert, voici ce qui ce passe dans ce code :
contents
, qui prend le type string.<seg>
et</seg>
sont ajoutées respectivement en début et en fin de ligne.Comparons ce code à celui de la solution présentée par Anand :
Le cœur de la solution ici diffère légèrement :
Dans ce code, les deux premières lignes ne font pas la même chose que dans celui des tentatives précédentes :
contents
, qui prend le type list.contents
, à partir du début de la liste.Les lignes 3 et 4 sont identiques.
Dernière observation pour aujourd'hui, parce qu'il se fait tard ici.
Avez-vous remarquez qu'entre le code de Jean-Christophe, Sirine et Anand, trois différentes méthodes (au sens Python) ont été utilisées pour lire le contenu du fichier ?
contents = file_object.read()
contents = file_object.readline()
contents = file_object.readlines()
Je vous laisse un peu de temps pour faire des recherches au sujet des différences entre ces méthodes, et demain j'expliquerai pourquoi le code de Jean-Christophe échoue, celui de Sirine n'affiche pas la première ligne, et celui de Anand fonctionne.
contents = file_object.read()
lit le fichier en séparant les caractères. Voici un extrait de ce que j'obtiens :La commande
.read()
permet normalement de lire le fichier entier, mais ici, je crois que Python considère chaque caractère comme une ligne.contents = file_object.readline()
ne prend en compte que la première ligne, mais en toujours en séparant les caractères :La commande
.readline()
ne permet de lire qu'une seule ligne : à partir d'un caractère de début de ligne jusqu'à un caractère de fin de ligne.contents = file_object.readlines()
on obtient quelque chose de plus correcte :Cette méthode fonctionne puisqu'elle indique qu'il faut lire toutes les lignes du fichier.
Pendant notre réunion de ce matin, nous avons essayé de décaler la balise fermante
</seg>
pour qu’elle se trouve à la fin de chaque ligne plutôt qu'au début de la ligne suivante.Premier essai :
Résultat :
Ici, la balise
</seg>
est seule sur une ligne. Nous avons donc utilisé la méthodestrip
:Et ça fonctionne :
Prochaine étape :
→ Créer une TMX monolingue
Ouvrir des fichiers avec Python
Voici l'explication promise pour le code qui ne fonctionne pas.
Je vais regrouper le code des tentatives de Jean-Christophe et de Sirine parce que la cause du problème est la même dans les deux cas. Je vais aussi répondre aux observations et résulats décrits par Sirine entre mes deux commentaires.
Les méthodes
read()
etreadline()
Le problème de l'affichage provient du fait que quand Python lit un fichier, il utiliser un pointeur pour garder sa place (un peu comme un signet qu'on place dans un livre, ou comme un personne qui suit le texte du doigt en lisant).
Quand on ouvre le fichier, le pointeur est au tout début et se déplace au fur et à mesure qu'on lit le contenu.
Dans le code de Jean-Christophe, la méthode
read()
lit tout le contenu du fichier, et le pointeur se trouve à la fin du fichier, où il n'y a plus rien, lorsqu'on entre dans la boucle.Pas tout à fait. La méthode
read()
lit bel et bien le fichier entier, mais elle le lit comme une seule (longue) chaîne de caractères (string). Quand il procède à une itération sur un chaîne de caractères, Python lit un caractère à la fois. Vous pouvez le constater avec un exemple banal :qui donne le résultat suivant :
Si vous modifiez ce code banal pour ajouter les
<seg>
et</seg>
et les afficher sans sauts de lignes comme dans la tentative originale, vous obtiendrez un<seg>
et</seg>
avant et après chaque lettre.Dans le code de Sirine, la méthode
readline()
lit la première ligne du fichier, et place le pointeur à la deuxième ligne, où il se trouve lorsqu'on entre dans la boucle. Par conséquent, l'affichage commence à la deuxième ligne du fichier.Même chose que ci-dessus. La ligne est lue comme une chaîne de caractères, et c'est l'itération qui modifie un caractère à la fois.
N'oubliez pas que le nom de la variable
line
n'est utile qu'à la personne qui écrit ou modifie le code, et n'a pas de sens pour Python. Les méthodes.read()
et.readline()
créent toutes deux un chaîne de caractères, et la boucle fait une itération sur chacun des éléments de cette chaîne, et donc sur un caractère à la fois. Si vous mettez unprint(contents)
avant la boucle, Python vous affichera soit tout le texte du fichier (pour.read()
), soit sa première ligne (pour.readline()
).Ces deux premières tentatives présentent aussi la particularité de lire le fichier (ou sa première ligne) dans la variable
contents
, mais de faire une itération directement sur l'objet fichierfile_object
. Si le seul but est d'afficher les lignes sans faire de manipulation supplémentaire, on pourrait faire l'itération directement sur l'objet fichier sans s'embêter de la variablecontents
:On obtient exatement le même résultat, avec les
<seg>
et</seg>
avant et après chaque caractère puisque la boucle opère encore et toujours sur une chaîne de caractères.La méthode
readlines()
La grande différence avec les méthodes
read()
etreadline()
, c'est que la méthodereadlines()
crée une liste (au sens de la structure de données « list » en Python), dont chacun des éléments est une chaîne de caractères correspondant à une ligne du fichier.Le code proposé par Anand crée donc dans la variable
contents
une liste qui contient chacune des lignes du fichier, et fait une itération sur chacun des éléments de cette liste pour y ajouter<seg>
et</seg>
au début et à la fin. Puisque l'itération ne se fait pas directement sur la chaîne même, on obtient un résultat correct où l'on crée une nouvelle chaîne de caractères qui commence par « <seg> », suivi de toute la chaîne de caractères contenant le texte de la ligne, et terminée par « </seg> ».La raison pour laquelle la balise fermante
</seg>
se trouve au début de la ligne suivante est que la méthodereadlines()
conserve le saut de ligne dans la chaîne de caractère.Votre solution avec la méthode
strip()
fonctionne bien avec un simple fichier, mais nécessite une précaution. Cette méthode n'élimine pas que les sauts de ligne si on ne précise pas le caratères à enlever de la chaîne. Par défaut, la méthode enlève tous les caractères d'espace blanc (whitespace characters) en début ou en fin de chaîne, ce qui pourrait avoir des conséquences imprévues avec un fichier plus complexe. Il serait peut-être plus prudent de préciser explicitement le saut de ligne avedstrip('\n')
… sauf qu'il faut ensuite songer aux différences entre Windows (qui utilise'\r\n'
) et les autres systèmes qui utilisent'\n'
pour représenter le saut de ligne.En passant, le
end='\n'
dans la ligneprint(line, end='\n')
n'est pas strictement nécessaire puisque que c'est la valeur par défaut. Bien sûr, il n'y a aucun mal à le rendre explicite.Ma solution
Tel que promis, je vous présente aussi la solution que j'utiliserais :
J'avais mentionné
pathlib
dans un message précédent. Je préfère l'utiliser à un chemin inscrit directement en chaîne de caractère parce qu'il simplifie l'expansion du code pour, par exemple, sauvegarder le fichier modifier dans le même répertoire ou le répertoire parent, ou faire d'autres manipulations sur le nom du fichier ou sur les répertoires.Dans la partie principale du code, la ligne importante est la suivante :
En enchaînant la méthode
splitlines()
à la méthoderead()
j'obtiens, à l'instar de la méthodereadlines()
une liste qui contient chacune des lignes du fichier. L'important différence, c'est que la méthodesplitlines()
ne conserve pas le saut de ligne.Si on regarde la valeur de la chaîne de caractère pour la première ligne, on obtient :
avec
readlines()
, etavec
splitlines()
.L'utilisation de
.read().splitlines()
nous permet donc d'éviter d'avoir à traiter chacune des lignes une à la fois pour en retire le saut de ligne, et nous évite aussi d'avoir à s'embêter avec les éventuelles différences entre les fichiers créés sous Windows et ceux créés sous d'autres systèmes.J'espère que ces explications sont suffisamment claires. Sinon, n'hésitez pas à poser des questions.
Mon code pour faire un semblant de TMX :
@Anand
Votre code de « semblant de TMX » produit un fichier TMX valide sur le plan syntaxique.
Toutefois, le choix du code de langue dans "xml:lang = fr" m'intrigue (principalement parce que j'ai le texte de Sirine en tête comme fichier texte). Est-ce que vous utilisez plutôt un fichier test avec des phrases en français ?
Petite astuce Python : on peut éviter d'avoir à mettre des caratères d'échappement pour les guillemets en choisissant judicieusement entre les guillemets simples (apostrophes) et les guillemets doubles. Une chaîne de caractères délimitée par des guillements simples peut contenir des guillemets doubles sans qu'il soit nécessaire de les échapper (et vice versa).
La première fonction
print()
, par exemple, peut être réécrite de la façon suivante :Vous pouvez appliquer le même principe aux autres lignes qui utilisent la fonction
print()
, ou encore a tout autre endroit où vous définissez un chaîne de caractères.Et il va falloir les définir ailleurs tôt ou tard. L'affichage à l'écran, c'est utile pour le retour visuel immédiat, mais ça manque de permanence ! 😁
Vous aller devoir songer à comment sauvegarder cette TMX dans un fichier.
Et ensuite, je pense que Jean-Christophe vous parlera de la distinction entre traiter un fichier qui ne contient que du texte ordinaire et un fichier XML…
Ce sont 2 thèmes qu'on a abordés hier en session. Pour jeudi, on doit donc trouver :
et d'ici la fin mai on va explorer :
Voici ce que nous avons réussi mettre en place durant la réunion de ce matin :
Résultat :
J'ai utilisé la fonction
zip()
pour créer une sorte de boucle. Maintenant, il faut essayer de l'enregistrer dans un fichier autonome.Bravo. Je ne connaissais pas la fonction
zip()
. Ça nous aurait simplifié la vie l'an dernier...Cet article explique la fonction en profondeur :
https://realpython.com/python-zip-function/
Avec ça, il nous reste à explorer la validation XML des
seg
. Et pour ceci, j'ai trouvé ce lien :https://wiki.python.org/moin/EscapingXml
Pour l'interface graphique, j'ai trouvé ça :
https://www.blog.pythonlibrary.org/2021/09/29/create-gui/
Au plaisir de lire votre code lundi !
Jean-Christophe
Je l'avais pourtant utilisé dans mon remaniement du script de l'an dernier quand tu l'avais présenté sur OmegaT-as-a-Book…
Et bravo à tout le monde de l'avoir trouvée et appliquée. C'est la meilleure façon de grouper les bons éléments ensembles quand on veut combiner les éléments correspondants de deux (ou même plusieurs) listes.
Attention toutefois à la longueur des listes. Dans un projet un peu plus ambitieux, il faudra songer à la possibilité que l'une des listes soit plus longue que l'autre (ou les autres) et décider comment traiter de tels cas.
J'aurais cru que pour produire le fichier XML tu aurais plutôt choisi un des modules ici :
https://docs.python.org/3/library/xml.html
(Ils ont l'avantage d'être déjà disponible dans une installation Python standard.)
Pour la validation, est-ce qu'on parle de valider que le XML soit bien formé et valide sur le plan syntaxique, ou de vérifier la conformité avec un DTD ?
Dans le premier cas, on peut apparemment utiliser SAX.
Dans le deuxième, il faudra installer une bibliothèque tierce comme
lxml
ouxmlschema
.Voici un article long et très complet sur les analyseurs XML disponibles en Python :
https://realpython.com/python-xml-parser/
À titre d'information, j'utilise
lxml
pour tout mes besoins XML en Python, et je recommande cette bibliothèques si vous avez l'intention de continuez à faire du codage dans cette veine à moyen ou long terme.Pour le projet en cours, les modules déjà installés avec Python sont suffisant, surtout si vous préférez éviter d'avoir à installer des bibliothèques tierces pour le moment (si on ne valide pas la conformité au DTD).
Toujours dans l'optique de ne pas avoir à installer une bibliothèque tierce, Python contient déjà des modules pour le GUI :
https://docs.python.org/3/library/tkinter.html
Et voici un
tutoriel
dont la section 6 sur les dialogues et les menus sera d'un intérêt particulier si on veut seulement afficher une boîte de dialogue pour demander à l'utilisateur de choisir un fichier.Toutefois, si le but est de créer une interface graphique un peu plus complète et jolie, la bibliothèque
wxPython
présentée dans l'article trouvé par Jean-Christophe sera probablement un meilleur choix.Et voilà ! Mesdames et messieurs, faites vos choix ! 😸
Je vais regarder vos solutions avec intérêt !
Hahaha ! Tu surestimes ma capacité à me souvenir d'un truc qui s'est passé plus tard que la semaine précédente...
J'avais trouvé Element Tree mais dans le tutoriel je n'ai pas trouvé comment convertir une chaîne arbitraire en truc qui passe en XML, or, c'est juste ce dont on a besoin ici.
Et j'ai eu la flemme de chercher plus loin.
L'idée, c'est soit d'utiliser une librairie XML comme on a fait l'an dernier, soit de faire un truc à la main comme ici, mais en faisant attention au contenu textuel. Il n'y a pas de validation au-delà de ça.
En fait, je pensais en particulier à PySimpleGUI.
https://realpython.com/pysimplegui-python/
L'idée c'est d'avoir une GUI pour sélectionner les fichiers en entrée, les langues, et l'emplacement du fichier de sortie.
Puis de passer tout ça à la moulinette gettext pour un faire un truc localisable, et donc le localiser...
Moi aussi :-)
En fait, j'ai triché. Je suis retourné voir mon code pour voir si je l'avais utilisée là. 😄
Avec ElementTree, ce n'est pas très compliqué. Dans notre cas :
tmx
.<seg>
, on leur assigne la valeur de la chaîne arbitraire correspondante.Dans ce cas, les fonctions de ElementTree (ou autres modules XML) font presque tout le travail. Il suffit de préciser les noms et valeurs des éléments et des attributs que l'on veut mettre dans le fichier, et le module s'occupe de créer des éléments XML valides.
Il faut être un peu maso pour faire ça à la main ! 😨
Ah oui. Je l'ai toujours un peu en tête, mais je ne l'ai jamais vraiment essayé.
Intéressant. Rien de super compliqué, mais quand même un peu plus exigeant que de simplement faire apparaître une boîte de dialogue pour sélectionner les fichiers ou leur emplacement au moment voulu.
L'interface va faire po neuve !
Ce qu'on a fait aujourd'hui n'est pas trivial.
Prenez le temps de revoir comment toutes ces choses fonctionnent.
En particulier la structure suivante:
Ça veut dire :
mon_objet_1
,UneFonction
avec les arguments nécessaires,mon_objet_2
.Et on peut maintenant utiliser mon_objet_2 pour faire d'autres trucs.
Faites une ou deux réunions complémentaires d'ici jeudi pour bien échanger et tenter d'être à un niveau de compréhension proche.
Et pas de stress si c'est dur. Il faut juste s'assurer entre nous que ce n'est pas impossible.
Par ailleurs, il ne s'agit pas ici de savoir comment produire un code de ce type sans filet. Il s'agit juste de comprendre ce qu'on cherche, comment ça fonctionne et d'être capable de faire des copier-coller qui fonctionnent, petit à petit.
N'oubliez pas. Petit à petit. Il faut qu'à chaque nouvelle ligne ajoutée, vous ailliez du code qui fonctionne.
J'ai fusionné les deux codes pour obtenir ça :
Ce qui me créer un fichier Aligner.tmx qui contient ça :
Ca m'a l'air correct, il reste la question du comment intégrer au début les lignes :
Le problème, c'est que vous appelez des fichiers test, mais que vous codez manuellement le fait qu'il n'y aura que 2 segments.😓Il faut aller plus loin dans l'intégration puisqu'il faut considérer un nombre arbitraire de lignes dans les fichiers et donc voir comment utiliser la partie😓zip
du code de la semaine dernière.Pour les déclarations, cherchez "declaration" et "doctype" dans la documentation de ElementTree.
Je pense que vous avez jeté de bonnes bases sur lesquelles poursuivre cet exercice, mais comme vous le savez sans doute, il reste encore du chemin à faire. 😀
Voici quelques observations sur vos derniers efforts :
Attributs de l'élément
<header>
La spécification TMX en ligne ou en PDF indique comme « required attributes » les attributs suivants :
Pour créer un fichier TMX valide, il vous faudra donc modifier votre code pour inclure tous ces attributs dans l'élément
<header>
.Pour ce faire, en fouillant dans la documentation, vous pourrez trouvez une façon plus simple que d'utiliser la méthode
.set()
pour chaque attribut individuellement. (Je ne connaissais pas cette méthode —merci ! Toutefois, elle semble plutôt servir à modifier un attribut dans un fichier XML qu'on lit et modifie un peu)Encodage
Je vois des «
ç
» et des «î
» dans la sortie du code d'Anand. Il est possible de régler l'encodage pour obtenir une sortie qui ne bousille pas les caractères non-ASCII.(Un fichier XML devrait normalement être en 'utf-8' ou autre encodage Unicode qui présente les caratères correctement.)
Déclaration et doctype
La déclaration, c'est assez simple, mais pour une raison quelconque, avec
xml.etree.ElementTree
, il faut faire quelques acrobaties pour inclure le doctype.(Je pense que c'est parce que
xml.etree.ElementTree
est plus axée sur la lecture et la modification de fichiers XML existants que sur la création de nouveaux fichiers XML.)Problèmes d'organisation du code
Je vois deux problèmes au niveau du code en soi :
Le fichier
Aligner.tmx
se fait ouvrir deux fois. Pourquoi ?Imbrications inutiles :
En Python, attention à l'indentation. Elle n'est pas que visuelle ! Il est facile d'oublier d'avancer ou de reculer au bon niveau d'indentation.
Dans votre code, je vois :
Avec les cours fichiers tests utilisés pour le moment, on ne voit pas d'effet négatifs, mais si on appliquait le code à des fichiers contenant des centaines ou des milliers de lignes, la différence de performance va se faire sentir.
Jean-Christophe a parlé d'objets et de fonctions plus tôt, ainsi que de gérer un nombre arbitraire de lignes.
J'ai écrit un petit script qui utilise des fonctions pour lire un nombre arbitraire de fichiers contenant un nombre tout aussi arbitraire de lignes.
Pour vous donner quelques pistes, je vais vous présenter le processus que j'ai suivi, le squelette du script (sans code précis !), et les résultats que j'ai obtenu dans un message ultérieur.
Avant d'entrer dans le vif du sujet, petit addendum à mon message précédent. Dans la section sur l'encodage, je n'avais pas remarqué que le site décodait et affichait correctement les caractères qui avaient attiré mon attention dans la sortie produite par le code d'Anand.
Vous aurez devinez que je parlais des codes
ç
et compagnie (maintenant aussi corrigés dans le commentaire lui-même).Parlons maintenant du processus de création de script.
J'ai rédigé un article qui décrit ma démarche et présente le squelette de mon script ainsi que les résultats obtenus.
Puisqu'il est un peu long pour un commentaire dans un ticket, je l'ai plutôt mis dans une page du wiki.
Comme je le mentionne dans la conclusion, il ne s'agit que d'une approche possible parmi plusieures.
J'espère qu'elle vous sera utile comme piste pour poursuivre vos propres idées. Qui sait, peut-être trouverez-vous de meilleures solutions que la mienne !
Si vous avez des questions ou des commentaires, ne vous gênez pas !
Petite suggestion (à vérifier avec Jean-Christophe avant de procéder)
Je pense que vous arrivez à une étape de la création de l'utilitaire où il serait utile de créer un répertoire pour mettre votre code (et vos fichiers de test) sous contrôle de version (probablement en créant chacun votre propre branche).
Il vous sera plus facile de collaborer sur le code de cette façon et vous pourrez créer des tickets plus précis associés à votre projet Python pour les problèmes que vous allez rencontrez.
Mise à jour du code :
On utilise
lxml
car il nous permet d'intégrer le doctype directement dans le.tostring()
.On doit donc aussi utiliser des méthodes
lxml
sur tous les autres éléments, sinon la méthode.tostring()
ne marche pas.Mais la méthode
.set()
dulxml
n'accepte pas l'attributxml:lang
à cause du ' : ', j'ai donc du contourner à l'aide de la méthode.QName().
J'ai ajouté les attributs du header directement dans le
.SubElement()
sauf pour o-tmf à cause du ' - '. J'ai donc aussi du contourner en l'ajoutant via la méthode.set()
et.QName()
.Ce code me créer un fichier
Aligner.tmx
qui contient :Ça avance ! J'aime bien le nouveau nom du « creationtool » !
C'est effectivement la solution la plus simple pour ajouter le doctype.
Belle trouvaille !
Je n'avais pas trouvé cette méthode la première fois que j'ai eu à résoudre ce problème.
Vous auriez pu faire la même chose pour la racine
tmx
juste avant. 😄Astuce : on peut aussi passer les attributs sous forme de dictionnaire Python, et on peut utiliser des variables autant pour les clés que pour les valeurs. Par exemple :
Quand il y a plusieurs attributes à ajouter à un élément, ça peut être plus pratique que d'utiliser la méthode
.set()
.Vous avez maintenant une sortie correcte, mais certains problèmes soulignés dans la version précédente sont toujours présents :
Le nombre de
<tuv>
par<tu>
, et les langues qu'ils prennent, sont figés manuellement dans le code. (Est-ce simplement une mesure temporaire pour vérifier que la sortie est correcte avant de passer à la prochaine étape ?)Les problèmes d'indentation sont toujours là. Dans la logique du programme :
On ne manipule pas les fichiers texte après avoir récupéré les lignes qu'ils contiennent. Par conséquent, la création du document TMX devrait se faire à l'extérieur du bloc d'ouverture et de lecture des fichiers textes.
Il n'est pas nécéssaire de (re)créer et formatter le document TMX chaque fois qu'on ajoute un élément
<tu>
. Le document TMX final devrait être créé une seule fois, après que toutes les phrases aient été jumelées et ajoutées à l'élément<body>
.Ça n'empêche pas le code de fonctionner, mais ça pourrait causer des ennuis avec de plus gros fichiers ou au moment d'apporter des améliorations ou des ajouts au code.
Je vais attendre que Jean-Christophe me donne le feu verte, puis je vous présenterai mon propre code.
Pour les langues, on en a parlé hier.
Pour le nombre de
<tuv>
, il est figé à 2 parce qu'il y a 2 fichiers. Et comme on utilisezip
par simplification, on se limite à 2 fichiers.Avec 3+ fichiers,
zip
ne serait plus suffisant, parce qu'on ne pourrait plus se limiter au fichier le plus court des 3+.Attend le premier juin. Ça sera notre dernière séance.
<voix Darth Vader>
You underestimate the power of the darkzip
…</voix Darth Vader>
Parfait. Ça va me donner le temps de préparer une version « pseudo-pédagogique » ! 😀
Philippe, fais gaffe, il y a des références culturelles qui grillent 😂.
Super !
Code avec moins de problèmes d'indentations :
Pas seulement « moins de problèmes », mais bien « pas de problèmes ». Bravo ! Vous avez réglé tous les cas d'indentation suspecte.
L'ajout de variables pour les langues source et cible, et pour le nom du fichier de sortie, est aussi une excellente amélioration par rapport à la version précédente du code.
Il ne me reste que des petites suggestions du côté présentation/formatage du code. C'est une question de préférence personnelle, mais comme j'aime aérer le code, j'apporterais quelques changements mineurs pour faciliter la lecture et mettre en évidence les éléments de même nature.
Dans la section qui déclare les variables :
J'ai utilisé des lignes vides pour séparer le bloc de variables concernant les fichiers d'entrée et de sortie, le bloc avec les langues, et (comme vous l'aviez déjà fait), le bloc de variables représentant des attributs d'éléments XML.
Dans la boucle qui crée l'élément
<tu>
et les<tuv>
et<seg>
qui s'y trouve :Encore quelques lignes vides supplémentaire pour démarquer l'élément
<tu>
et les éléments<tuv>
. J'ai aussi changé l'ordre des lignes pour mettre tous les éléments d'un même<tuv>
ensemble.Pour moi, c'est plus fidèle à la progression que je visualise pour le code une fois qu'on entre dans la boucle :
<tu>
;<tuv>
dans la langue source ;<seg>
et son texte dans ce premier<tuv>
;<tuv>
dans la langue cible ;<seg>
et son texte dans ce deuxième<tuv>
.Ça ne change en rien la fonctionnalité du code ou son résultat. Toutefois, ça le rend plus facile à lire, corriger, ou modifier pour quelqu'un qui veut s'en servir où y ajouter des fonctionnalités (c.-à-d. moi deux jours plus tard quand je me demande qu'est-ce que je cherchais à faire avec mon code !).
À méditer !
Tel que promis, je vous présente le code complet de mon script.
En fait, je vous en présente trois variantes deux qui utilisent
lxml
et une qui utilisexml.etree.ElementTree
. Vous les trouverez dans la section code du dépot.textes_à_tmx_(lxml_objectify).py
La variante principale (« pseudo-pédagogique »), à laquelle j'ai ajouté beaucoup de commentaires détaillés sur le code.
textes_à_tmx_(lxml_etree).py
Variante qui utilise l'API principale du module
lxml
.textes_à_tmx_(xml.etree.ElementTree).py
La variante qui n'utilise que la bibliothèque Python standard.
J'ai aussi inclus le répertoire
fichiers_texte
avec les fichiers que j'ai utilisés pour vérifier les résultats du script. Vous pouvez donc l'essayer directement, ou remplacer mes fichiers par les vôtres.Enfin, vous trouverez également des explications complémentaires sur le wiki.
Bonne lecture !
Suite d’hier.
Je pense que j’ai trop installé de versions différentes de python sur ma machine et c’est pour ça que lxml n’arrive pas à s’installer.
J’ai tout repris en n’utilisant pas le gestionnaire de paquets
brew
maismacport
à la place et j’arrive à faire tout fonctionner, mais c’est moche parce que la version de tkinter de macport n’utilise pas l’interface graphique de macOS par défaut, mais à la place un équivalent de l’interface graphique qu’on trouve sur Linux, par l’intermédiaire de l’application Xquartz…Comme bilan, mais également à l’attention de @kazephil qui nous regarde, on a fait ça hier :
https://docs.python.org/fr/3/library/tkinter.html?highlight=tkinter#module-tkinter
À partir de ça on a fait un prototype de notre interface en utilisant juste des ‘label’ et des ‘button’.
Pour ça, on a utilisé le code correspondant à la section ‘Association des variables de l’objet graphique’
On n’a pas réussi à y intégrer notre prototype (les explications sur le ‘sous-classage’ nous sont passées très haut au-dessus de la tête) et on a trouvé un tutoriel qui correspondait plus à notre prototype
https://www.geeksforgeeks.org/python-tkinter-entry-widget/
On a encore des bricoles à gérer :
mais on avance.
Voici le code aujourd’hui :
Tout ça, c’est beaucoup de copier/coller et quand ça marchera il faudra penser à
repasser ligne par ligne pour mettre des explications et être capable de comprendre ce que l’on a fait et à quoi on a abouti.
Au final, j’aimerais bien qu’on aboutisse à un document qui ressemble à notre transformation TMX/TBX (https://mac4translators.blogspot.com/2023/04/convertir-un-fichier-tmx-en-tbx-avec.html), avec le nom de tout le monde, comme aboutissement de ce stage, même si la finalisation du code et la publication auront lieu bien après les soutenances.
Tu peux essayer de créer un environnement virtuel avec la version de Python que tu veux utiliser pour l'utilitaire.
Autres articles utiles sur les environnements virtuels :
En principe, de là tout s'installe avec
pip
, plutôt qu'avec le gestionnaire de programme.Installer tkinter dans l'environnement virtuel réglerait peut-être aussi le problème de l'utilisation de l'interface graphique macOS.
Merci ! Je vais regarder ça avec intérêt.
Autre tutoriel qui pourrait être intéressant :
Il porte sur Tk en général, avec des exemples non seulement en Python, mais aussi dans d'autres langages.
Et quelques autres choix d'intérêt spécifiquement orientés Python :
Ce code fonctionne tel quel de ton côté ?
Ici, il plante avec un erreur
TypeError: 'NoneType' object does not support item assignment
à la ligne :L'explication n'est effectivement pas très claire, et je pense qu'elle t'a induit en erreur.
Question : Pourquoi utilises-tu la fonction
print()
?Si tu veux imprimer imprimer à la console, il faut récupérer (
get
) la valeur de la variable de typeStringVar
.C'est ça qui compte !
Attention, ce n'est pas
lxml
que l'on importe, mais plutôt le(s) module(s) désiré(s) offert par cette bibliothèque.Retourne voir le code d'Anand quelques commentaires plus haut.
Mais pour l'interface graphique, ce code me présente un petit dialogue avec « fr » comme langue source et « en » comme langue cible, et un bouton « Submit ».
J'attends patiemment la suite !
J’ai tenté de reprendre le code du début et d’y apporter des modifications pour résoudre les problèmes que nous avions rencontrés auparavant. En cours de route, j'ai découvert quelques fonctions et méthodes que j'ai voulu tester, et comme toujours il y a beaucoup de copier/coller. Au final, je pense avoir plus appris grâce aux messages d'erreur qu'aux tutoriels…
Voici ce que ça donne pour l’instant :
J'ai retiré
from pathlib import Path
car je voulais que le boutonParcourir
nous permette de réellement parcourir nos fichiers pour en sélectionner un. Il a donc été remplacé parfrom tkinter import filedialog
https://www.youtube.com/watch?v=Aim_7fC-inw&feature=youtu.be
J'ai ajouté la fonction
align_files()
avec la méthode.get()
qui utilise les chemins de fichiers et les informations entrées dans l'interface pour aligner les fichiers.https://www.w3schools.com/python/ref_dictionary_get.asp
Ensuite, pour créer une boucle qui permet d'afficher les deux textes segment par segment, j’ai utilisé
aligned_text +=
pour ajouter les deux valeurs définies dans le f-Stringf"{source_seg.strip()}\\n ->\\n{target_seg.strip()}\\n\\n"
, ce qui nous permettra d'afficher le contenu des deux fichiers simultanément.https://www.w3schools.com/python/python_operators.asp
https://realpython.com/python-f-strings/
Enfin, pour afficher le texte aligné dans la même fenêtre, j'ai utilisé le "Text Widget" de Tkinter.
https://www.geeksforgeeks.org/python-tkinter-text-widget/
(Ce n'est pas très utile, mais j'ai ajouté une petite fenêtre qui indique que l'alignement est terminé.)
https://docs.python.org/3/library/tkinter.messagebox.html
Pour le reste, j'ai repris ce que nous avons vu ensemble, avec quelques informations et explications trouvées ici et là (merci internet). Le code fonctionne, mais il a bien sûr besoin d'être amélioré.
Merci pour le code et pour les commentaires et vidéos.
Je viens de tester. C'est parfait.
La grosse fenêtre pourrait peut-être être remplacée par un affichage en 2 colonnes qui affiche les contenus de chaque fichier.
Et ça pourrait servir de point de départ (soyons dingues !) à une fonction de correction de l'alignement au cas où les deux fichiers ne sont pas pile-poil alignés...