Saludos,
He adaptado el script de Python anterior para hacerlo general, de modo que solicite la lista de palabras y el léxico (si existe) y genere un archivo SMF para importar en FLEx.
#!/usr/bin/python3
"""
wordlist_to_sfm.py
Exporta una lista de palabras de Paratext al formato SFM (Standard Format Markers)
para su importación en FLEx (FieldWorks Language Explorer).
Uso:
python wordlist_to_sfm.py
El script solicitará:
- Un archivo XML de lista de palabras de Paratext (p. ej., exportado desde la herramienta de lista de palabras de Paratext)
- Opcionalmente, un archivo XML de léxico de Paratext para proporcionar glosas en inglés
- Una ruta de archivo de salida para el archivo .sfm resultante
Por defecto, solo se exportan las palabras marcadas como "Correct" en la lista de palabras.
Esto se puede cambiar editando las configuraciones INCLUDE_* a continuación.
El formato de salida es MDF (Multi-Dictionary Formatter), el dialecto estándar de SFM utilizado por FLEx para la importación de léxicos.
"""
import codecs
import os
import xml.etree.ElementTree as etree
# --- Configuración ----------------------------------------------------------------
# Controla qué categorías de ortografía se incluyen en la exportación.
# Establezca en True para incluir palabras en esa categoría, o en False para excluirlas.
INCLUDE_CORRECT = True # Palabras aprobadas por el traductor
INCLUDE_UNKNOWN = False # Palabras aún no revisadas
INCLUDE_INCORRECT = False # Palabras marcadas como errores ortográficos
# Establezca en True para escribir la frecuencia del corpus de la palabra como un campo de comentario (\co_count)
INCLUDE_COUNT = True
# -----------------------------------------------------------------------------
def prompt_path(prompt_text, default):
"""Muestra un prompt con un valor predeterminado opcional; devuelve la entrada del usuario o el valor predeterminado."""
display = f" [{default}]" if default else ""
value = input(f"{prompt_text}{display}: ").strip()
return value if value else default
def get_user_inputs():
"""Pide al usuario las tres rutas de archivo necesarias para ejecutar la exportación."""
print("Paratext Word List to SFM Exporter")
print("-----------------------------------\n")
# La lista de palabras es obligatoria: sigue pidiendo hasta encontrar el archivo
wordlist_path = prompt_path("Word list file (XML)", "Notsi-WL.xml")
while not os.path.exists(wordlist_path):
print(f" Cannot find '{wordlist_path}'. Please check the path and try again.")
wordlist_path = prompt_path("Word list file (XML)", "Notsi-WL.xml")
# El léxico es opcional: se omite silenciosamente si no se encuentra
lexicon_path = prompt_path("Lexicon file for glosses (press Enter to skip)", "")
if lexicon_path and not os.path.exists(lexicon_path):
print(f" Cannot find '{lexicon_path}'. Continuing without glosses.")
lexicon_path = ""
output_path = prompt_path("Output file name", "wordlist_for_flex.sfm")
return wordlist_path, lexicon_path, output_path
def load_lexicon_glosses(lexicon_path):
"""
Lee glosas en inglés desde un archivo XML de léxico de Paratext.
Devuelve un diccionario indexado por la forma del lexema, donde cada valor contiene el tipo de lexema (Word, Prefix, Suffix) y una lista de cadenas de glosas en inglés.
Devuelve un diccionario vacío si no se proporciona una ruta de léxico.
"""
glosses = {}
if not lexicon_path:
print("No lexicon provided - entries will be exported without glosses.")
return glosses
print(f"Reading lexicon from: {lexicon_path}")
with open(lexicon_path, "rb") as f:
data = f.read()
# Elimina cualquier variante de BOM (BOM UTF-8 estándar o BOM doblemente codificado)
for bom in (b"\xef\xbb\xbf", b"\xc3\xaf\xc2\xbb\xc2\xbf"):
if data.startswith(bom):
data = data[len(bom):]
break
xmldoc = etree.fromstring(data)
for item in xmldoc.findall("Entries/item"):
lexeme = next(item.iter("Lexeme"), None)
if lexeme is None:
continue
form = lexeme.get("Form", "")
lex_type = lexeme.get("Type", "Word") # Word, Prefix o Suffix
# Recopila todas las glosas en inglés para esta entrada (puede haber más de un sentido)
entry_glosses = [
gloss.text
for gloss in item.iter("Gloss")
if gloss.get("Language") == "en" and gloss.text
]
if form and entry_glosses:
glosses[form] = {"type": lex_type, "glosses": entry_glosses}
print(f" Found {len(glosses)} entries with English glosses.")
return glosses
def build_approved_list(wordlist_path):
"""
Lee el XML de la lista de palabras de Paratext y devuelve las palabras que pasan los filtros INCLUDE_*.
Devuelve una lista de formas de palabras aprobadas y un diccionario de metadatos que contiene la cuenta del corpus, la división silábica y el desglose morfológico de cada palabra.
"""
wordlist = etree.parse(wordlist_path)
all_items = wordlist.getroot().findall("item")
print(f"Reading word list from: {wordlist_path}")
print(f" {len(all_items)} words found in list.")
approved = []
metadata = {}
for item in all_items:
spelling = item.attrib.get("spelling", "Unknown")
word = item.attrib.get("word", "")
include = (
(spelling == "Correct" and INCLUDE_CORRECT) or
(spelling == "Unknown" and INCLUDE_UNKNOWN) or
(spelling == "Incorrect" and INCLUDE_INCORRECT)
)
if include and word:
approved.append(word)
metadata[word] = {
"count": item.attrib.get("count", "0"),
"hyphenation": item.attrib.get("hyphenation", ""),
"morphology": item.attrib.get("morphology", ""),
"morph_approved": item.attrib.get("morphologyApproved", "False"),
"specificcase": item.attrib.get("specificcase", ""),
}
print(f" {len(approved)} words marked as correct and ready to export.")
return approved, metadata
def write_sfm_entry(outfile, word, lex_type, glosses, meta, include_count):
"""
Escribe una entrada de léxico SFM individual en el archivo de salida.
Convenciones MDF seguidas:
\\lx - lexema (palabra principal); los afijos llevan un guion en el lado unido
\\sn - número de sentido (escrito una vez por cada glosa)
\\ge - glosa en inglés para ese sentido
\\mr - cadena morfológica (cuando ha sido aprobada por el traductor)
\\co_* - campos de comentario para metadatos para los que FLEx no tiene un marcador estándar
"""
# Escribe la palabra principal, añadiendo guiones para mostrar los puntos de unión de los afijos
outfile.write("\n\\lx ")
if lex_type == "Suffix":
outfile.write("-")
outfile.write(word)
if lex_type == "Prefix":
outfile.write("-")
outfile.write("\n")
# Registra el tipo de afijo como comentario para que sobreviva a la importación en FLEx
if lex_type != "Word":
outfile.write(f"\\co_type {lex_type}\n")
# Frecuencia del corpus: útil para priorizar entradas durante el trabajo de diccionario
if include_count and meta:
outfile.write(f"\\co_count {meta['count']}\n")
# Morfología: usa el marcador estándar \\mr si el análisis ha sido aprobado,
# de lo contrario, guárdalo como comentario para evitar importar datos no verificados
if meta and meta["morphology"]:
marker = "\\mr" if meta["morph_approved"] == "True" else "\\co_morph"
outfile.write(f"{marker} {meta['morphology']}\n")
# Escribe un bloque de sentido por cada glosa
for sense_num, gloss_text in enumerate(glosses, start=1):
outfile.write(f"\\sn {sense_num}\n")
outfile.write(f"\\ge {gloss_text}\n")
def main():
"""
Rutina principal de exportación.
Enfoque de dos pasos:
Paso 1 - escribir entradas que tienen glosas del léxico (datos más ricos primero)
Paso 2 - escribir las palabras aprobadas restantes que no tenían entrada en el léxico
Esto asegura que las entradas con glosas no se dupliquen.
"""
wordlist_path, lexicon_path, output_path = get_user_inputs()
print()
approved_words, metadata = build_approved_list(wordlist_path)
lexicon_glosses = load_lexicon_glosses(lexicon_path)
# Abre el archivo de salida como UTF-8 y escribe la cabecera MDF
outfile = codecs.open(output_path, mode="w", encoding="utf-8")
outfile.write("\\_sh v3.0 400 MDF\n\\_DateStampHasFourDigitYear\n\n")
remaining = list(approved_words) # Palabras que aún deben escribirse después del paso 1
with_glosses = 0
# Paso 1: entradas que aparecen tanto en el léxico como en la lista de palabras aprobadas
for form, lex_data in lexicon_glosses.items():
if form not in approved_words:
print(f" Skipping '{form}' (in lexicon but not marked as correct in word list)")
continue
if form in remaining:
remaining.remove(form)
write_sfm_entry(outfile, word=form, lex_type=lex_data["type"],
glosses=lex_data["glosses"], meta=metadata.get(form),
include_count=INCLUDE_COUNT)
with_glosses += 1
# Paso 2: palabras aprobadas sin entrada en el léxico - exportadas sin glosas
for word in remaining:
write_sfm_entry(outfile, word=word, lex_type="Word", glosses=[],
meta=metadata.get(word), include_count=INCLUDE_COUNT)
total = with_glosses + len(remaining)
outfile.close()
print(f"\nExport complete.")
print(f" Total entries written : {total}")
print(f" With glosses : {with_glosses}")
print(f" Without glosses : {len(remaining)}")
print(f" Output file : {output_path}")
if __name__ == "__main__":
main()