import xml.etree.ElementTree as ET import json import argparse import sys import os from tkinter import filedialog, ttk, Tk, messagebox def escapar_markdown(texto): if not texto: return "" return texto.replace("|", "\\|").replace("[", "\\[").replace("]", "\\]") def procesar_feeds(ruta_entrada, formato_salida): try: tree = ET.parse(ruta_entrada) root = tree.getroot() except Exception as e: return f"Error: {e}" feeds = [] for outline in root.findall(".//outline[@xmlUrl]"): feeds.append({ "titulo": outline.get('title') or outline.get('text') or "Sin título", "url_web": outline.get('htmlUrl') or "#", "url_rss": outline.get('xmlUrl') or "#" }) nombre_base = os.path.splitext(ruta_entrada)[0] # Con formatos integrados inline para evitar dramas de etiquetas prohibidas. if formato_salida == 'html': # Contenedor principal con CSS Inline output = '
' for f in feeds: # Estilos directos en cada tarjeta y enlace card_style = 'border: 1px solid #e1e4e8; border-radius: 12px; padding: 15px; display: flex; justify-content: space-between; align-items: center; background: #ffffff; box-shadow: 0 2px 4px rgba(0,0,0,0.05);' title_style = 'font-weight: bold; color: #0366d6; text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1;' rss_style = 'background: #f68140; color: #ffffff !important; padding: 5px 10px; border-radius: 6px; font-size: 12px; text-decoration: none; font-weight: bold; margin-left: 12px;' output += f'\n
' output += f'\n {f["titulo"]}' output += f'\n RSS' output += f'\n
' output += "\n
" ext = ".html" elif formato_salida == 'markdown': output = "| Sitio Web | Feed RSS |\n| :--- | :---: |\n" for f in feeds: titulo_limpio = escapar_markdown(f['titulo']) output += f"| [{titulo_limpio}]({f['url_web']}) | [RSS]({f['url_rss']}) |\n" ext = ".md" elif formato_salida == 'json': output = json.dumps(feeds, indent=4, ensure_ascii=False) ext = ".json" archivo_final = nombre_base + ext with open(archivo_final, "w", encoding="utf-8") as f: f.write(output) return archivo_final def iniciar_gui(): root = Tk() root.title("OPML-to-BlogRoll") root.geometry("400x250") archivo_ruta = {"path": ""} def seleccionar(): path = filedialog.askopenfilename(filetypes=[("Feeds", "*.opml *.xml")]) if path: archivo_ruta["path"] = path lbl.config(text=os.path.basename(path)) def ejecutar(): if not archivo_ruta["path"]: return res = procesar_feeds(archivo_ruta["path"], combo.get().lower()) messagebox.showinfo("Éxito", f"Generado: {res}") root.destroy() ttk.Label(root, text="Convertidor OPML/XML", font=("Arial", 12, "bold")).pack(pady=15) ttk.Button(root, text="Seleccionar Archivo", command=seleccionar).pack() lbl = ttk.Label(root, text="Ninguno", foreground="gray") lbl.pack(pady=5) combo = ttk.Combobox(root, values=["HTML", "Markdown", "JSON"], state="readonly") combo.set("HTML") combo.pack(pady=10) ttk.Button(root, text="Convertir", command=ejecutar).pack(pady=10) root.mainloop() if __name__ == "__main__": if len(sys.argv) > 1: parser = argparse.ArgumentParser() parser.add_argument("entrada") parser.add_argument("-f", "--formato", choices=['html', 'markdown', 'json'], default='html') args = parser.parse_args() if os.path.exists(args.entrada): print(f"Generado: {procesar_feeds(args.entrada, args.formato)}") else: iniciar_gui()