Files
fn_registry/python/functions/infra/split_vevents_to_vcalendars.py
T
egutierrez a76760edba feat(dav,obsidian): grupo dav completo (CardDAV/CalDAV client + split vcf/ics + import pipelines) + build_obsidian_graph + dav_list_calendars
Funciones reutilizables creadas esta sesion para el sistema self-hosted de contactos/calendario (Xandikos) y la app osint_web:
- grupo dav (infra): split_vcards, split_vevents_to_vcalendars, extract_or_make_uid, carddav_put_vcard, caldav_put_event, dav_list_resources, dav_get_resource, dav_list_calendars
- pipelines: import_vcf_to_carddav, import_ics_to_caldav
- obsidian: build_obsidian_graph (grafo agregado del vault)
2026-06-12 00:43:59 +02:00

57 lines
2.0 KiB
Python

"""Divide un .ics (un VCALENDAR con N VEVENT) en N VCALENDARs independientes.
Funcion pura: sin I/O, sin estado, determinista. Cada salida es un VCALENDAR
completo y autonomo con un unico VEVENT, listo para subir como recurso CalDAV
individual. Las VTIMEZONE del original se incluyen en cada salida (un VEVENT
puede referenciar un TZID definido en el VCALENDAR padre). Solo usa stdlib (re).
"""
import re
_VEVENT_RE = re.compile(r"BEGIN:VEVENT.*?END:VEVENT", re.DOTALL)
_VTIMEZONE_RE = re.compile(r"BEGIN:VTIMEZONE.*?END:VTIMEZONE", re.DOTALL)
_DEFAULT_PRODID = "-//xandikos-migracion//google-export//EN"
def _to_crlf(text: str) -> str:
"""Normaliza saltos de linea a CRLF (RFC 5545)."""
return text.strip().replace("\r\n", "\n").replace("\n", "\r\n")
def split_vevents_to_vcalendars(ics_text: str, prodid: str = _DEFAULT_PRODID) -> list:
"""Divide un VCALENDAR con N VEVENT en N VCALENDARs independientes.
Args:
ics_text: contenido completo del .ics (un VCALENDAR con uno o varios
VEVENT). Tolera LF o CRLF.
prodid: valor del campo PRODID a usar en el header de cada VCALENDAR de
salida. Default: identifica la migracion a Xandikos.
Returns:
Lista de strings. Cada elemento es un VCALENDAR completo y autonomo
('BEGIN:VCALENDAR'..'END:VCALENDAR' terminado en CRLF) con header
VERSION/PRODID/CALSCALE, las VTIMEZONE del original (si las habia) y un
unico VEVENT. Lista vacia si no hay ningun VEVENT.
"""
if not ics_text:
return []
events = _VEVENT_RE.findall(ics_text)
timezones = _VTIMEZONE_RE.findall(ics_text)
tz_block = ""
for tz in timezones:
tz_block += _to_crlf(tz) + "\r\n"
header = (
"BEGIN:VCALENDAR\r\n"
"VERSION:2.0\r\n"
"PRODID:%s\r\n"
"CALSCALE:GREGORIAN\r\n" % prodid
)
out = []
for ev in events:
body = header + tz_block + _to_crlf(ev) + "\r\nEND:VCALENDAR\r\n"
out.append(body)
return out