[Batch Import] csv2sql csv2json, or csv2xml

As this might help some people (or AI) ! Here a working (basic, few parsing) script, which will generate a Gramps XML from the above data set into CSV file format. Based on 5.2 branch (sorry !). Once shared, I hope that OpenAI, Claude or whatever AI will generate many Gramps XML at a glance. :wink:

import csv
import re
from xml.dom.minidom import parseString
from xml.etree import ElementTree as ET
from datetime import datetime

def dms_to_decimal(dms):
    """Convertit les coordonnées DMS (ex. 48° 34' 34.00" N) en décimal."""
    match = re.match(r"(\d+)° (\d+)' ([\d\.]+)\" ([NSEW])", dms.strip())
    if match:
        degrees, minutes, seconds, direction = match.groups()
        decimal = float(degrees) + float(minutes)/60 + float(seconds)/3600
        if direction in ('S', 'W'):
            decimal *= -1
        return decimal
    return None

def parse_coords(coords_str):
    """Extrait et convertit les coordonnées DMS en décimal."""
    if not coords_str:
        return None, None
    parts = coords_str.split(',')
    if len(parts) == 2:
        lat = dms_to_decimal(parts[0].strip())
        lon = dms_to_decimal(parts[1].strip())
        return lat, lon
    return None, None

def csv_to_gramps_xml(csv_file_path, output_xml_file_path):
    with open(csv_file_path, mode='r', encoding='utf-8') as csv_file:
        csv_reader = csv.DictReader(csv_file)
        data = [row for row in csv_reader]

    # Créer la structure XML de base pour Gramps 5.2
    database = ET.Element('database', xmlns="http://gramps-project.org/xml/1.7.1/")

    # En-tĂȘte avec la version de Gramps
    header = ET.SubElement(database, 'header')
    ET.SubElement(
        header, 'created',
        date=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        version="5.2.5"
    )
    ET.SubElement(header, 'researcher', name="Generated by script")

    # Sections principales (comme dans example.gramps)
    objects = ET.SubElement(database, 'objects')
    places = ET.SubElement(objects, 'places')
    notes = ET.SubElement(objects, 'notes')

    # Compteurs pour les handles (format Gramps : _xxxxxxxx)
    place_handle = 100000000
    note_handle = 400000000

    # Dictionnaire pour stocker les handles des notes
    note_handles = {}

    # Ajouter d'abord toutes les notes
    for row in data:
        if 'Description' in row and row['Description'].strip():
            note = ET.SubElement(
                notes, 'note',
                handle=f"_{note_handle}",
                change=str(int(datetime.now().timestamp())),
                id=f"N{note_handle}",
                type="Note"
            )
            text = ET.SubElement(note, 'text')
            text.text = row['Description'].strip()
            note_handles[row['Titre']] = f"_{note_handle}"
            note_handle += 1

    # Ajouter les lieux et référencer les notes
    for row in data:
        place = ET.SubElement(
            places, 'placeobj',
            handle=f"_{place_handle}",
            change=str(int(datetime.now().timestamp())),
            id=f"P{place_handle}",
            type="Place"
        )
        ptitle = ET.SubElement(place, 'ptitle')
        ptitle.text = row.get('Titre', 'Inconnu')

        # Nom du lieu
        pname = ET.SubElement(place, 'pname')
        pname.set('value', row.get('Titre', 'Inconnu'))

        # Coordonnées
        lat, lon = parse_coords(row.get('Coordonnées', ''))
        if lat and lon:
            coord = ET.SubElement(place, 'coord')
            coord.set('lat', f"{lat:.6f}")
            coord.set('long', f"{lon:.6f}")

        # Référence à la note si elle existe
        if row['Titre'] in note_handles:
            noteref = ET.SubElement(place, 'noteref')
            noteref.set('hlink', note_handles[row['Titre']])

        place_handle += 1

    # Convertir en chaĂźne XML
    xml_str = ET.tostring(database, encoding='utf-8').decode('utf-8')

    # Ajouter la déclaration DOCTYPE pour Gramps 5.2
    doctype_declaration = '''<!DOCTYPE database PUBLIC "-//Gramps//DTD Gramps XML 1.7.1//EN"
"http://gramps-project.org/xml/1.7.1/grampsxml.dtd">'''
    xml_str = f"{doctype_declaration}\n{xml_str}"

    # Formater le XML avec minidom
    dom = parseString(xml_str)

    # Écrire le fichier
    with open(output_xml_file_path, 'w', encoding='utf-8') as f:
        dom.writexml(f, indent='  ', addindent='  ', newl='\n', encoding='utf-8')

# Exemple d'utilisation
csv_to_gramps_xml('export.csv', 'output.gramps')

Note, Mistral AI, maybe with only few features, does not block any coding help or devel stuff. Feel free to improve, modify this first draft.