Convertir archivo .bib a .csv

hola gente, soy nuevo en python así que es probable que mi problema sea básico, pero sencillamente no lo veo.

Hice un script que convierte archivos de biblatex a csv (ojo con esto no es bibtex, biblatex es más moderno) para luego poder insertarlos en una base de datos SQLite, sospecho que el problema lo tengo con la expresión regular que lee el archivo (pero no estoy seguro), la cuestión es que necesito escapar la barra invertida, ya que la misma se utiliza dentro de los campos, el script lo utilizo desde consola con la lógica de:

python script.py archivo.bib archivo.csv

no obtengo ningún error, pero tampoco hace lo esperado, copia un registro bib de ejemplo y el script (no encuentro el modo de adjuntar archivos, ¿es posible?)

@InCollection{Gene2011,
  hyphenation  = {spanish},
  author       = {Gené, Marcela},
  booktitle    = {\enquote{Travesías de la imagen}. Historia de las artes visuales en la Argentina},
  date         = {2011},
  editor       = {María Isabel Baldasarre and Silvia Dolinko},
  keywords     = {listar},
  location     = {Buenos Aires},
  pages        = {95-118},
  publisher    = {EDUNTREF},
  title        = {Varones domados. \emph{Family strips} de los años veinte},
  volume       = {1},
}
import re
import csv
import sys

# Función para leer un archivo .bib y extraer la información
def leer_bib(archivo):
    with open(archivo, 'r', encoding='utf-8') as f:
        texto = f.read()

    # Expresión regular para capturar entradas con llaves
    entradas = re.findall(r'@(\w+)\{(.*?)\}(?:\r?\n)?', texto, re.DOTALL)

    # Convertir las entradas a diccionarios
    datos = []

    for entrada in entradas:
        tipo_entrada, contenido = entrada
        campos = dict(re.findall(r'(\w+)\s*=\s*(?:{([^{}]*?)}|"([^"]*?)")', contenido))
        campos['tipoEntrada'] = tipo_entrada.lower()  # Convertir a minúsculas

        # Escapar las barras invertidas y comillas dobles en el contenido de los campos
        for key, value in campos.items():
            if value is not None:
                value = value.replace('\\', '\\\\').replace('"', '""')
                campos[key] = value.strip()

        datos.append(campos)

    return datos


# Función para escribir la información en un archivo CSV
def escribir_csv(datos, archivo_salida):
    # Lista de nombres de columna en el orden proporcionado
    columnas = ['tipoEntrada', 'citationKey', 'keywords', 'author', 'bookAuthor', 'editor', 'editorA', 'editorB', 'editorC', 'afterword', 'commentator', 'translator', 'holder', 'shortTitle', 'short', 'editorType', 'editorAtype', 'editorBtype', 'editorCtype', 'foreword', 'introduction', 'annotator', 'gender', 'nameAddOn', 'title', 'indexTitle', 'bookTitle', 'mainTitle', 'journalTitle', 'issueTitle', 'eventTitle', 'reprintTitle', 'series', 'bookTitleAddOn', 'mainTitleAddOn', 'journalTitleAddOn', 'issueTitleAddOn', 'eventTitleAddOn', 'chapter', 'volume', 'edition', 'pubState', 'year', 'date', 'urlDate', 'volumes', 'part', 'issue', 'eventDate', 'origDate', 'version', 'location', 'publisher', 'institution', 'organization', 'page', 'pagination', 'hyphenation', 'langId', 'language', 'origLocation', 'origPublisher', 'pageTotal', 'venue', 'bookPagination', 'langIdOpts', 'origLanguage', 'isan', 'isbn', 'ismn', 'isrn', 'issn', 'iswc', 'url', 'doi', 'eid', 'eprinttype', 'eprint', 'entrysubtype', 'label', 'howpublisher', 'addendum', 'shorthand', 'shorthandintro', 'etiquetas', 'options', 'ids', 'related', 'relatedType', 'relatedString', 'entryset', 'crossref', 'xref', 'xdata', 'presort', 'sortkey', 'sortname', 'sortshorthand', 'sorttitle', 'indexsorttitle', 'sortyear', 'file', 'abstract', 'library', 'note', 'annotation', 'number', 'creationdate', 'type']

    # Escribir los datos en un archivo CSV con punto y coma como separador
    with open(archivo_salida, 'w', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=columnas, delimiter=';')
        writer.writeheader()
        for dato in datos:
            # Eliminar campos no deseados del diccionario
            dato_filtrado = {key: value for key, value in dato.items() if key in columnas}
            writer.writerow(dato_filtrado)

    # Leer el archivo CSV recién creado y reemplazar los encabezados year por yearY y date por dateD
    with open(archivo_salida, 'r', encoding='utf-8') as f:
        lines = f.readlines()

    # Reemplazar los encabezados year y date
    if 'year' in columnas:
        lines[0] = lines[0].replace('year', 'yearY')
    if 'date' in columnas:
        lines[0] = lines[0].replace('date', 'dateD')

    # Escribir las líneas modificadas de nuevo en el archivo CSV
    with open(archivo_salida, 'w', newline='', encoding='utf-8') as f:
        f.writelines(lines)

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Uso: python script.py archivo.bib archivo.csv")
        sys.exit(1)

    archivo_entrada = sys.argv[1]
    archivo_salida = sys.argv[2]

    # Leer el archivo .bib y convertirlo a datos
    datos = leer_bib(archivo_entrada)

    # Escribir los datos en un archivo CSV
    escribir_csv(datos, archivo_salida)

    print("Archivo CSV generado correctamente.")

No me queda claro que querés hacer.

  1. Si necesitás extraer las entradas, lo más fácil es usar un módulo ya hecho:

    # Para probar un módulo, siempre probalo primero en un entorno virtual
    mkdir bibtex-project && pushd $_
    python -m venv venv
    . venv/bin/activate
    
    # instalar la version 2, que es la que está en desarrollo activo
    pip install bibtexparser --pre
    

    Probá el módulo primero en el intérprete:

    import bibtexparser
    refs = bibtexparser.parse_file('/path/to/file.bib')
    for k,v in refs.entries_dict.items():
        print(k)
        print(v)
        print()
    

    A la hora de procesar las entradas, no hay ninguna diferencia sintáctica entre bibtex y biblatex; por lo poco que sé, son diferencias estéticas, así que cualquier parser de bibtex debería servirte; te recomiendo que uses uno que esté en activo desarrollo y tenga buena documentación

  2. Si lo tuyo es un proyecto para aprender las bases de scripting de python, te recomiendo que, o empieces por algo más sencillo o agreges algunas funciones más; tenés sólo dos que hacen todo. Si es así te puedo dar algunos tips

  3. No me quedó tiempo para testear bien la Expresión Regular (perdí un poco la práctica), pero al parecer para de buscar en el primer campo. SI tengo que adivinar, estan mal agrupadas las expresiones

Recomendaciones generales:

  1. Lo que no manejés fluídamente o tenga cierta complejidad, probalo primero en el intérprete; los RegEx por ejemplo.

  2. Algo que muchos obvian (me incluyo) es el análisis preliminar del problema; si hacés de eso un hábito, te puede ahorrar muchos dolores de cabeza. Para mí lo más fácil es usar el mismo script:

    #! /usr/bin/env python
    """
    # Convertir un archivo bibtex/biblatex (.bib) a CSV
    - Cargar archivo .bib en una lista de diccionarios
    - Convertir una lista de diccionarios a CSV
    - Guardar CSV en el disco
    """
    

    Un vez que definiste bien el problema, recién ahí empezás a escribir código

    #! /usr/bin/env python
    """
    # Convertir un archivo bibtex/biblatex (.bib) a CSV
    def bib_a_lista(ruta):
        pass
    
    def lista_a_csv(entradas):
        pass
    
    def save_csv(csv_reader):
        pass
    """
    
  3. Está muy bien la estructura del script, pero te recomiendo que uses dos funciones básicas para probar el código fuente, y vayas “guardando” las funciones que funcionan (valga la redundancia). Para que se entienda mejor:

    import sys
    
    def funcion_que_funciona_1(dato):
        return resultado
    
    def testing(dato):
        return resultado
    
    def main():
        resultado = testing(sys.argv[1])
        print(resultado)
    
    if __name__ == '__main__':
        if len(sys.arv) < 2:
            print("Uso: python main.py input > output")
            exit(1)
    
        main()
    

    Una vez que escribiste cada una de las funciones mínimas necesarias, las integrás todas en otra, en varias, o podés usar la misma función main. Lo ideal es que uses Pytest, pero esta bueno que lo hagas así, de esa forma adquirís fluidez en el lenguaje

  4. Con respecto a formatos, mi recomendación es que investigues un poco primero, y armes una muestra mínima del formato para probar el script. En tu caso sería algo como:

    raw_refs = """
       @book{robbins2018,
          title={Learning Web Design},
          author={Robbins, Jennifer},
          year={2018},
       }
      @article{weiss2023,
         title={BibTexParser Readthedocs},
         author={M. Weiss, F. Boulogne and other contributors},
         year={2023},
         url={https://bibtexparser.readthedocs.io}
     }"""
    

    De esa manera tenés por lo menos una idea general del formato con el que estás trabajando por un lado; por otro, te ayuda a modularizar tu script, leer/escribir un archivo, debería ser siempre una función aparte