Python: Créer un fichier XML à l'aide de BeautifulSoup

Le module BeautifulSoup permet de parser un fichier XML (ou HTML) très facilement mais il peut, tout aussi facilement, créer du contenu XML de toute pièce.

Pour l'exemple je vais utiliser le module faker qui permet de générer des données aléatoires en tout genre.

Installation des modules nécessaires

# python3 -m pip install --upgrade bs4 faker

Import des modules

>>> from bs4 import BeautifulSoup as bs
>>> from faker import Faker

Initialisation du module faker

>>> f = Faker('fr_FR')

Je vais générer des profils aléatoires ressemblant à ceci:

>>> f.profile()
{'job': 'aide-soignant',
 'company': 'Dias',
 'ssn': '318-53-8242',
 'residence': '74, rue Boyer\n04256 Hoarau',
 'current_location': (Decimal('17.293354'), Decimal('165.196114')),
 'blood_group': 'A+',
 'website': ['http://boulay.com/'],
 'username': 'arnaudmonique',
 'name': 'Patrick Valette',
 'sex': 'M',
 'address': '86, boulevard Raymond\n67114 Sainte Honoré',
 'mail': 'nbesnard@bouygtel.fr',
 'birthdate': datetime.date(1944, 10, 31)}

Les données générées sont au format json.
Je vais utiliser les clés comme noms des balises (tags) XML.
Mon fichier XML contiendra une balise (tag) principale "<profiles>" qui contiendra toutes les balises (tag) "<profile>".
La clé "ssn" sera un attribut de la balise (tag) "<profile>".
La valeur de la clé "current_location" sera découpée en deux balises (tags) XML, "<latitude>" et "<longitude>" et ces deux balises (tags) seront intégrées à la balise (tag) "<current_location>".
Toutes les valeurs de la clé "website" auront leurs propres balises (tags) XML "<website>" et elles seront toutes regroupées dans une balise (tag) XML "<websites>".

C'est parti...

Création de mon contenu XML de base

# Création de mon enveloppe XML
>>> xml = bs(features='xml')
# Création de mon tag <profiles>
>>> profiles = xml.new_tag('profiles')
# Ajout du tag <profiles> à l'enveloppe XML
>>> xml.append(profiles)

Création du détail de mon XML

# Une boucle pour créer 10 profils aléatoires
>>> for x in range(10):
    # Génération d'un profil aléatoire
    prof = f.profile()
    # Création de mon tag avec l'attribut "ssn" <profile ssn="xxx-xx-xxxx">
    profile = xml.new_tag('profile', attrs={'ssn':prof.get('ssn')})
    # Suppression de la clé "ssn" (plus besoin)
    prof.pop('ssn')
    # Je parcours toutes les clés de mon profil généré
    for k, c in prof.items():
        # Création du tag correspond à la clé du json <xxxx>
        y = xml.new_tag(k)
        # Si ma clé est "current_location" je découpe la valeur en "latitude" et "longitude"
        if k == 'current_location':
            # Création du tag <latitude>
            z1 = xml.new_tag('latitude')
            # Je renseigne la valeur du tag <latitude> avec la première valeur du tuple "current_location"
            z1.string = str(c[0])
            # Création du tag <longitude>
            z2 = xml.new_tag('longitude')
            # Je renseigne la valeur du tag <longitude> avec la seconde valeur du tuple "current_location"
            z2.string = str(c[1])
            # On ajoute les deux tags <latitude> et <longitude> au tag <current_location>
            y.append(z1)
            y.append(z2)
        # Si ma clé est "website" je détaille toutes les valeurs
        elif k == 'website':
            # je renomme mon tag en <websites>
            y = xml.new_tag('websites')
            # je parcours toutes les valeurs de la clé "website"
            for z in c:
                # Création du tag <website>
                z1 = xml.new_tag('website')
                # Je renseigne la valeur du tag <website>
                z1.string = z
                # J'ajoute mon tag <website> au tag <websites>
                y.append(z1)
        else:
            # Sinon, je renseigne la valeur du tag <xxxx>
            y.string = str(c)
        # J'ajoute le tag <xxxx> au tag <profile>
        profile.append(y)
    # J'ajoute le tag <profile> au tag <profiles>
    profiles.append(profile)
   

Et voilà, mon contenu XML est terminé.
Il ressemble à ceci:

<?xml version="1.0" encoding="utf-8"?>
<profiles>
    <profile ssn="002-43-2880">
        <job>projectionniste</job>
        <company>Rossi</company>
        <residence>rue Gomez
11458 Francoisnec</residence>
        <current_location>
            <latitude>-25.928603</latitude>
            <longitude>169.931343</longitude>
        </current_location>
        <blood_group>B+</blood_group>
        <websites>
            <website>https://www.bonnin.com/</website>
        </websites>
        <username>caronmargaret</username>
        <name>David Joubert</name>
        <sex>M</sex>
        <address>avenue Geneviève Giraud
90573 Martineaudan</address>
        <mail>aubertjacqueline@wanadoo.fr</mail>
        <birthdate>1968-02-29</birthdate>
    </profile>
    <profile ssn="007-06-6107">
        <job>prototypiste en matériaux souples</job>
        <company>Maurice Blin S.A.S.</company>
        <residence>798, rue Françoise Ramos
76458 Saint MargaudBourg</residence>
        <current_location>
            <latitude>41.5629005</latitude>
            <longitude>15.774012</longitude>
        </current_location>
        <blood_group>B+</blood_group>
        <websites>
            <website>https://fischer.com/</website>
            <website>http://daniel.net/</website>
            <website>https://paul.com/</website>
            <website>https://www.costa.com/</website>
        </websites>
        <username>marie50</username>
        <name>Inès Daniel</name>
        <sex>F</sex>
        <address>705, rue Thérèse Collet
50313 Jacques-les-Bains</address>
        <mail>hortensehuet@wanadoo.fr</mail>
        <birthdate>1968-06-15</birthdate>
    </profile>
    <profile ssn="017-32-3783">
        <job>hydraulicien</job>
        <company>Letellier et Fils</company>
        <residence>9, rue Gilles
61321 Sainte Virginie</residence>
        <current_location>
            <latitude>23.7306235</latitude>
            <longitude>-59.305275</longitude>
        </current_location>
        <blood_group>A+</blood_group>
        <websites>
            <website>https://www.chevallier.com/</website>
            <website>https://www.hoareau.net/</website>
        </websites>
        <username>rene93</username>
        <name>Céline Blin</name>
        <sex>F</sex>
        <address>43, avenue Vidal
82960 Teixeira</address>
        <mail>davidmartine@laposte.net</mail>
        <birthdate>1954-05-08</birthdate>
    </profile>
    <profile ssn="023-75-0442">
        <job>électricien installateur installatrice</job>
        <company>Dupuis</company>
        <residence>7, rue Jacob
27949 Saint Yves</residence>
        <current_location>
            <latitude>-36.2609615</latitude>
            <longitude>-40.304003</longitude>
        </current_location>
        <blood_group>O-</blood_group>
        <websites>
            <website>http://www.jean.com/</website>
            <website>https://gregoire.org/</website>
            <website>http://www.payet.org/</website>
            <website>http://www.dumas.org/</website>
        </websites>
        <username>wlegrand</username>
        <name>Thierry Martins</name>
        <sex>M</sex>
        <address>33, boulevard Henri Pierre
00436 Georges</address>
        <mail>dumashelene@wanadoo.fr</mail>
        <birthdate>1966-07-04</birthdate>
    </profile>
    <profile ssn="031-65-0389">
        <job>ingénieur logiciel</job>
        <company>Masse SA</company>
        <residence>boulevard de Perrin
96676 Ferrandnec</residence>
        <current_location>
            <latitude>-27.915336</latitude>
            <longitude>161.219956</longitude>
        </current_location>
        <blood_group>A+</blood_group>
        <websites>
            <website>https://www.rousseau.com/</website>
            <website>http://lecomte.com/</website>
            <website>http://lopez.fr/</website>
        </websites>
        <username>gabriel61</username>
        <name>Patrick Delattre</name>
        <sex>M</sex>
        <address>4, rue de Levy
46800 Hamon-sur-Laporte</address>
        <mail>aliceleveque@dbmail.com</mail>
        <birthdate>1943-08-04</birthdate>
    </profile>
    <profile ssn="042-20-4813">
        <job>télévendeur</job>
        <company>Peltier Lacombe S.A.R.L.</company>
        <residence>5, avenue Briand
68000 Coste</residence>
        <current_location>
            <latitude>-56.7088745</latitude>
            <longitude>149.494499</longitude>
        </current_location>
        <blood_group>O+</blood_group>
        <websites>
            <website>https://www.lelievre.fr/</website>
            <website>https://bernier.fr/</website>
        </websites>
        <username>noel32</username>
        <name>Luc Meyer</name>
        <sex>M</sex>
        <address>684, boulevard Margaret Garnier
96838 Da Costa</address>
        <mail>nicole82@bouygtel.fr</mail>
        <birthdate>1921-04-27</birthdate>
    </profile>
    <profile ssn="064-95-7931">
        <job>contremaître</job>
        <company>Roche</company>
        <residence>35, avenue de Andre
80266 Jacob-les-Bains</residence>
        <current_location>
            <latitude>-79.434306</latitude>
            <longitude>-50.008269</longitude>
        </current_location>
        <blood_group>O-</blood_group>
        <websites>
            <website>http://www.parent.fr/</website>
            <website>https://dufour.fr/</website>
            <website>http://www.humbert.com/</website>
            <website>https://www.boyer.net/</website>
        </websites>
        <username>ghernandez</username>
        <name>Gilbert Poirier</name>
        <sex>M</sex>
        <address>60, rue de Collet
40531 Morvanboeuf</address>
        <mail>clemence22@club-internet.fr</mail>
        <birthdate>1968-08-18</birthdate>
    </profile>
    <profile ssn="065-83-0793">
        <job>critique d'art</job>
        <company>Meunier</company>
        <residence>91, avenue Bertin
66624 Guillou</residence>
        <current_location>
            <latitude>40.394601</latitude>
            <longitude>28.203459</longitude>
        </current_location>
        <blood_group>AB+</blood_group>
        <websites>
            <website>http://www.pichon.com/</website>
            <website>http://chauveau.com/</website>
        </websites>
        <username>gillessabine</username>
        <name>Dominique Ferrand</name>
        <sex>F</sex>
        <address>rue Arnaude Baron
22300 Barbe</address>
        <mail>utorres@voila.fr</mail>
        <birthdate>1912-10-02</birthdate>
    </profile>
    <profile ssn="066-39-4549">
        <job>ingénieur de la police technique et scientifique</job>
        <company>Perret</company>
        <residence>96, chemin de Perret
91066 Perrier-la-Forêt</residence>
        <current_location>
            <latitude>-67.974417</latitude>
            <longitude>-32.491563</longitude>
        </current_location>
        <blood_group>O-</blood_group>
        <websites>
            <website>http://richard.net/</website>
            <website>http://www.fischer.fr/</website>
        </websites>
        <username>williamalbert</username>
        <name>Alex Godard</name>
        <sex>F</sex>
        <address>85, boulevard Guichard
20974 Marionnec</address>
        <mail>stephanieneveu@club-internet.fr</mail>
        <birthdate>1993-02-27</birthdate>
    </profile>
    <profile ssn="070-76-9294">
        <job>assistant en architecture</job>
        <company>Aubert Georges S.A.</company>
        <residence>31, rue de Garcia
41968 Saint Sabine</residence>
        <current_location>
            <latitude>36.045194</latitude>
            <longitude>131.046682</longitude>
        </current_location>
        <blood_group>O-</blood_group>
        <websites>
            <website>http://poirier.fr/</website>
        </websites>
        <username>denisalix</username>
        <name>Christophe Coste-Baron</name>
        <sex>M</sex>
        <address>44, boulevard Bertrand
85123 Blondel</address>
        <mail>bouvierremy@ifrance.com</mail>
        <birthdate>1908-03-12</birthdate>
    </profile>

Super simple quand même !