3 Exemple de création de script en Python
Philippe Tourigny edited this page 2023-06-02 05:14:19 +02:00
This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Création d'une TMX à partir de fichiers texte

Je vous présente ici ma démarche pour créer un script qui produit un fichier TMX à partir de fichiers ou plus contenant un nombre arbitraire de lignes.

Objectifs et limitations

L'objectif de base était de créer la partie du code qui lit le contenu des fichiers textes et produit un fichier TMX valide qui aligne correctement les segments correspondants de chacun des fichiers texte.

En partant du principe que la version finale de l'utilitaire aura une interface graphique pour permettre à l'utilisateur de saisir les fichiers à aligner et les langues source et cible(s), j'ai choisi les conventions suivantes pour tester le code:

  • Les fichiers à aligner se trouvent dans le sous-répertoire fichiers_texte du répertoire contenant l'utilitaire.
  • Le français est désigné comme langue source.
  • Les fichiers ont l'extension txtet leur nom se termine par "_LL", ou LL est le code de langue du fichier.
  • Le fichier TMX produit est sauvegardé dans le sous-répertoire fichiers_tmx du répertoire contenant l'utilitaire, avec le même nom de base que les fichiers texte:
    .
    ├── fichiers_textes
    │   ├── étapes_en.txt
    │   ├── étapes_fr.txt
    │   └── étapes_ja.txt
    ├── fichiers_tmx
    │   └── étapes.tmx
    

Encore à faire:

  • Ajouter du code pour comparer le nombre de lignes de chacun des fichiers et avertir l'utilisateur si un fichier contient plus de lignes que l'autre (ou les autres).

Squelette du code

Le script suit le modèle ci-dessous. Pusique le but est de vous donner un modèle qui peut servir de point de départ, je n'ai laissé que les commentaires et j'ai remplacé le code par le mot-clé pass. J'utilise aussi <argument> ou <arguments> quand la fonction prend au moins un argument.

Dans la même veine, je n'ai laissé que le mot-clé pass et des commentaires décrivant ce que fait le code dans la partie principale du script.

Des précisions sur certaines portions du code suivent la présentation du squelette.

# -*- coding: utf-8 -*-

from pathlib import Path

from lxml import etree as et

### Définir les constantes ###

# Espace de nom XML et attribut @xml:lang
XML = 'http://www.w3.org/XML/1998/namespace'
LANG = '{' + XML + '}' + 'lang'

# Valeurs à saisir via une interface graphique dans la version finale.
CORPUSPATH = Path(Path().cwd()/'fichiers_textes')
SOURCE_LANG = 'fr'

### Fonctions ###

def get_languages_and_files(<argument>):
    '''Récupérer les fichiers et leur languages.'''

    pass


def read_content_by_language(<argument>):
    '''Lire le contenu du fichier pour chacune des languages'''

    pass


def prepare_units(<argument>):
    '''Jumeler les phrases correspondantes dans chacune des langues.'''

    pass


def create_tmx_document():
    '''Créer la structure de base de la TMX.'''

    pass


def make_tu(<arguments>):
    '''Créer un élément <tu> contenant les <tuv> et <seg> pour toutes
    les langues d'un groupe de segments.'''

    pass
        

def build_final_tree(<argument>):
    '''Créer l'arbre XML au complet pour la TMX.'''

    pass


def save_tmx(<arguments>):
    '''Définir le chemin et le nom complet du fichier TMX, et le sauvegarder.'''

    pass


### Programme principal ###

if __name__ == '__main__':
    # Utiliser les fonctions "get_languages_and_files" et
    # "read_content_by_language" pour récupérer les langues
    # et le contenu des fichiers.
    pass

    # Définir le nom de base pour la TMX
    pass
    
    # Utiliser la fonction "create_tmx" pour créer un document TMX.
    pass
    
    # Utiliser la fonction "prepare_units" pour jumeler
    # les unités de traduction.
    pass
        
    # Pour chacune des unités de traduction, créer l'élément <tu> correspondant
    # avec la fonction "make_tu", puis ajouter le <tu> à la TMX.
    pass
    
    # Compléter l'arbre XML
    pass
    
    # Sauvegarder le fichier TMX
    pass

Explications

Certaines parties du modèle mérite de plus amples explications.

Préambule du code

# -*- coding: utf-8 -*-

from pathlib import Path

from lxml import etree as et

Ici, la première ligne précise que le fichier de code source est encodé en utf-8. Elle n'est pas strictement nécessaire puisque, en principe, Python 3 utilise l'encodage utf-8 par défaut pour le code source. J'ai simplement pris l'habitude de la mettre pour évite d'éventuelles mauvaises surprises.

Les importations vous montrent que j'ai choisi la bibliothèque lxml pour mon script, principalement parce qu'elle est plus puissante et un peu plus facile à utiliser que xml.etree.ElementTree. Toutefois, la différence n'est pas énorme. J'ai aussi fait une version avec xml.etree.ElementTree, et au moins 95p.100 du code est identique.

Constantes

### Définir les constantes ###

# Espace de nom XML et attribut @xml:lang
XML = 'http://www.w3.org/XML/1998/namespace'
LANG = '{' + XML + '}' + 'lang'

# Valeurs à saisir via une interface graphique dans la version finale.
CORPUSPATH = Path(Path().cwd()/'fichiers_texte')
SOURCE_LANG = 'fr'

La première fois que j'avais tenté de manipuler des fichiers TMX avec lxml, j'obtenais une erreur si j'essayais d'entrer "xml:lang" directement comme attribut, et la documentation indiquait qu'il fallait définir l'espace de nom. Ici, j'ai simplement repris les constantes que j'avais définies à ce moment.

Je n'aurais peut-être pas eu ce problème si j'avais trouvé la fonction set que vous avez utilisé dans votre code. À tester plus tard…

Dans la constante CORPUSPATH, la fonction Path().cwd() me donne le chemin du répertoire de l'utilitaire, auquel j'ajoute le sous-répertoire fichiers_texte décrit ci-dessus.

Fonctions

Les noms et descriptions des fonctions vous donneront, je l'espère, une bonne idée de la façon dont j'ai abordée cet exercice.

D'ailleurs, je commence souvent avec un squelette comme celui ci-dessus quand je veux écrire un script. Je réfléchis un peu aux étapes nécessaires pour accomplir la tâche désirées, et je prépare une liste de fonctions avec une courte descriptions comme dans l'exemple ci-dessus (mais avec des arguments précis). Ça me donne un point de départ, et je fais des ajustements en cours de route si mon approche initiale ne fonctionne pas ou si je trouve une meilleure idée.

La fonction make_tu, par exemple, était à l'origine une fonction add_tu qui avait pour but de créer l'élément <tu> et de l'ajouter à la TMX.

Toutefois, quand j'ai essayé ça, j'obtenais quelque chose du genre:

<tu>
  <tuv xml:lang="fr">
    <seg>premier segment</seg>
  </tuv>
  <tuv xml:lang="en">
    <seg>first segment</seg>
  </tuv>
  <tuv xml:lang="fr">
    <seg>deuxième segment</seg>
  </tuv>
  <tuv xml:lang="en">
    <seg>second segment</seg>
  </tuv>
  .
  .
  .
<tu/>  

Pas du tout le résultat voulu. N'ayant pas trouvé de solution élégante pour créer et ajouter le <tu> correctement dans la même fonction, je l'ai changée (et renommée) pour qu'elle ne fasse que créer le <tu>, et j'ai mis le code pour ajouter ce <tu> à la TMX ailleurs.

D'autres fonctions pourraient changer quand viendra le temps de créer l'interface graphique, puisqu'elles sont présentement adaptées au principe pour cette première ébauche, le nom du fichier contient aussi le code de langue. Si l'interface saisit ces informations séparéments, il faudra peut-être aussi séparer les fonctions connexes.

La fonction prepare_units est très simple et pourrait simplement être une seule ligne de code dans la partie principale du programme.

Pourquoi en faire une fonction dans ce cas?

J'ai fait ce choix en prévision d'une éventuelle amélioration du code pour traiter les phrases avant de les jumeler comme, par exemple, remplacer certaines espaces par des espaces insécables, ou remplacer les guillemets et apostrophes ASCII par des guillemets et apostrophes typographiques.

Dans mon cas, c'est aussi parce que définir des fonctions (même courtes) m'aide à organiser et écrire le code.

Petite précision pour la fonction save_tmx. L'un des arguments est le nom de base du fichier sans le code de langue («étapes» dans mon exemple) simplement parce que je voulais donner à la TMX le même nom que celui des fichiers, et que j'ai choisi de la passer explicitement à la fonction par souci de clarté dans mon code. (Le nom est défini dans la partie principale du programme et devrait donc, en principe, être accessible dans la fonction sans qu'il soit nécessaire de le passer comme argument.)

Dernière petit note au sujet des fonctions: elles doivent être déclarées avant le code qui les appelle. C'est pour ça que la convention est de définir toutes les fonctions avant la partie principale du programme.

Partie principale du script

### Programme principal ###

if __name__ == '__main__':

Il est très commun de commencer le programme principale avec cette condition. En gros, elle permet d'exécuter le script en tant que script et de l'importer dans un autre script (pour en utiliser les fonctions) sans qu'il ne soit exécuter au moment de l'exécution.

Le reste consiste presqu'entièrement de code qui appelle les fonctions, met le résultat dans une variable, et le manipule de façon approprié.

Résultats

Avec deux fichiers (étapes_fr et étapes_en), j'obtiens ceci :

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE tmx SYSTEM "tmx14.dtd">
<tmx version="1.4">
  <header creationtool="Utilitaire stage 2023" creationtoolversion="0.3" datatype="plaintext" segtype="sentence" adminlang="en-US" srclang="fr" o-tmf="text files"/>
  <body>
    <tu>
      <tuv xml:lang="fr">
        <seg>Étapes:</seg>
      </tuv>
      <tuv xml:lang="en">
        <seg>Steps:</seg>
      </tuv>
    </tu>
    <tu>
      <tuv xml:lang="fr">
        <seg>1. Lire le contenu de deux fichiers;</seg>
      </tuv>
      <tuv xml:lang="en">
        <seg>1. Read the contents of two files.</seg>
      </tuv>
    </tu>
    <tu>
      <tuv xml:lang="fr">
        <seg>2. Combiner ces contenus;</seg>
      </tuv>
      <tuv xml:lang="en">
        <seg>2. Combine those contents.</seg>
      </tuv>
    </tu>
    <tu>
      <tuv xml:lang="fr">
        <seg>3. Convertir ce résultat en TMX;</seg>
      </tuv>
      <tuv xml:lang="en">
        <seg>3. Convert the result to the TMX format.</seg>
      </tuv>
    </tu>
    <tu>
      <tuv xml:lang="fr">
        <seg>4. Écrire le résultat de la conversion dans un fichier.</seg>
      </tuv>
      <tuv xml:lang="en">
        <seg>4. Write the converted data to a file.</seg>
      </tuv>
    </tu>
  </body>
</tmx>

Vous constaterez que les accents en français s'affichent correctement directement dans le fichier XML.


Avec trois fichiers (étapes_fr, étapes_en et `étapes_ja), j'obtiens ceci :

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE tmx SYSTEM "tmx14.dtd">
<tmx version="1.4">
  <header creationtool="Utilitaire stage 2023" creationtoolversion="0.3" datatype="plaintext" segtype="sentence" adminlang="en-US" srclang="fr" o-tmf="text files"/>
  <body>
    <tu>
      <tuv xml:lang="fr">
        <seg>Étapes:</seg>
      </tuv>
      <tuv xml:lang="en">
        <seg>Steps:</seg>
      </tuv>
      <tuv xml:lang="ja">
        <seg>ステップ</seg>
      </tuv>
    </tu>
    <tu>
      <tuv xml:lang="fr">
        <seg>1. Lire le contenu de deux fichiers;</seg>
      </tuv>
      <tuv xml:lang="en">
        <seg>1. Read the contents of two files.</seg>
      </tuv>
      <tuv xml:lang="ja">
        <seg>1.二つのファイルの内容を読み込む。</seg>
      </tuv>
    </tu>
    <tu>
      <tuv xml:lang="fr">
        <seg>2. Combiner ces contenus;</seg>
      </tuv>
      <tuv xml:lang="en">
        <seg>2. Combine those contents.</seg>
      </tuv>
      <tuv xml:lang="ja">
        <seg>2.その内容を結合させる。</seg>
      </tuv>
    </tu>
    <tu>
      <tuv xml:lang="fr">
        <seg>3. Convertir ce résultat en TMX;</seg>
      </tuv>
      <tuv xml:lang="en">
        <seg>3. Convert the result to the TMX format.</seg>
      </tuv>
      <tuv xml:lang="ja">
        <seg>結合の結果をTMXに変換。</seg>
      </tuv>
    </tu>
    <tu>
      <tuv xml:lang="fr">
        <seg>4. Écrire le résultat de la conversion dans un fichier.</seg>
      </tuv>
      <tuv xml:lang="en">
        <seg>4. Write the converted data to a file.</seg>
      </tuv>
      <tuv xml:lang="ja">
        <seg>4.変換の結果をファイルに書き込む。</seg>
      </tuv>
    </tu>
  </body>
</tmx>

Selon la configuration de votre système, vous devriez voir soit des caractères japonais, soit des symboles bizarres, mais pas des encodage du style &#231;.

Vous noterez également que le fichier contient la déclaration et le doctype. Avec lxml, je réussi à intégrer le doctype à l'objet tmxdans le code, et je précise qu'il faut ajouter la déclaration dans la commande qui écrit le fichier.

Avec xml.etree.ElementTree, j'obtiens le même résultat en écrivant directement la déclaration et le doctype dans le fichier manuellement avant d'y écrire la TMX (en évitant, bien sûr, de préciser d'ajouter la déclaration au niveau de l'objet tmx.)

Conclusion

Je ne suis pas programmeur, mais j'essaie quand même de suivre, au mieux de mon niveau de compréhension et de compétence, les «bonnes pratiques» présentées dans divers livres et articles que j'ai lus.

L'approche que j'ai presentée ici n'est ni absolue, ni définitive. Elle n'en n'est qu'une parmi plusieurs possibilités. La seule chose que je peux affirmer avec certitude c'est qu'il est beaucoup plus facile d'écrire et de déboguer le code si on utilise des fonctions plutôt que d'essayer de tout faire dans un programme écrit de façon linéaire.