leyendo el boe
This commit is contained in:
parent
993c427cfc
commit
0c3acffeb9
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<response>
|
||||
<status>
|
||||
<code>400</code>
|
||||
<text>No soportado ningún mime type de la cabecera Accept.</text>
|
||||
</status>
|
||||
<data/>
|
||||
</response>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,11 @@
|
|||
import requests
|
||||
import xml.etree.ElementTree as ET
|
||||
import datetime
|
||||
|
||||
today = datetime.date.today()
|
||||
url = f"https://www.boe.es/datosabiertos/api/boe/sumario/{today.strftime('%Y%m%d')}"
|
||||
|
||||
response = requests.get(url)
|
||||
|
||||
print(response.status_code)
|
||||
print(response.text[:1000]) # primeros caracteres
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
"""Convierte oep2026.xml a un markdown limpio y estructurado."""
|
||||
import xml.etree.ElementTree as ET
|
||||
import re
|
||||
import os
|
||||
|
||||
XML_PATH = os.path.join(os.path.dirname(__file__), "oep2026.xml")
|
||||
MD_PATH = os.path.join(os.path.dirname(__file__), "oep2026.md")
|
||||
|
||||
# ── helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
def inner_text(elem):
|
||||
"""Texto plano de un elemento (ignora etiquetas internas como <strong>)."""
|
||||
return "".join(elem.itertext()).strip()
|
||||
|
||||
def cell_text(td):
|
||||
t = inner_text(td)
|
||||
return t if t and t.strip() else ""
|
||||
|
||||
# ── table renderer ────────────────────────────────────────────────────────────
|
||||
|
||||
HEADER_6 = ["Cód.", "Cuerpo / Escala", "Cupo general", "Discp. general", "Discp. intelectual", "Total plazas"]
|
||||
SEP_6 = "|:---|:---|---:|---:|---:|---:|"
|
||||
|
||||
def table_to_md(table):
|
||||
"""Renderiza una <table> HTML como tabla Markdown con una sola cabecera."""
|
||||
lines = []
|
||||
# Cabecera única al inicio de la tabla
|
||||
lines.append("| " + " | ".join(HEADER_6) + " |")
|
||||
lines.append(SEP_6)
|
||||
|
||||
tbody = table.find("tbody")
|
||||
if tbody is None:
|
||||
return "\n".join(lines)
|
||||
|
||||
for tr in tbody.findall("tr"):
|
||||
cells = tr.findall("td")
|
||||
if not cells:
|
||||
continue
|
||||
colspan = int(cells[0].get("colspan", "1"))
|
||||
txt = inner_text(cells[0])
|
||||
|
||||
if colspan >= 4:
|
||||
# Fila de sección/grupo → línea de separación visual
|
||||
if not txt:
|
||||
continue
|
||||
if txt.isupper():
|
||||
lines.append(f"| **{txt}** | | | | | |")
|
||||
elif txt.startswith("Sub") or txt.startswith("sub"):
|
||||
lines.append(f"| *{txt}* | | | | | |")
|
||||
else:
|
||||
lines.append(f"| {txt} | | | | | |")
|
||||
else:
|
||||
row_cells = [cell_text(c) for c in cells]
|
||||
while len(row_cells) < 6:
|
||||
row_cells.append("")
|
||||
# Detectar filas "Total": el primer texto no vacío contiene "Total" o "TOTAL"
|
||||
first_nonempty = next((v for v in row_cells if v), "")
|
||||
if "otal" in first_nonempty:
|
||||
row_cells = [f"**{v}**" if v else "" for v in row_cells]
|
||||
lines.append("| " + " | ".join(row_cells) + " |")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def table_to_md_short(table):
|
||||
"""Para tablas con 4 columnas (ANEXO VII)."""
|
||||
lines = []
|
||||
thead = table.find("thead")
|
||||
if thead is not None:
|
||||
for tr in thead.findall("tr"):
|
||||
cols = [cell_text(c) for c in tr]
|
||||
if any(cols):
|
||||
lines.append("| " + " | ".join(cols) + " |")
|
||||
lines.append("|" + "|".join([":---" if i == 0 else "---:" for i in range(len(cols))]) + "|")
|
||||
tbody = table.find("tbody")
|
||||
if tbody is None:
|
||||
return "\n".join(lines)
|
||||
for tr in tbody.findall("tr"):
|
||||
cells = tr.findall("td")
|
||||
if not cells:
|
||||
continue
|
||||
colspan = cells[0].get("colspan", "1")
|
||||
if int(colspan) >= 3:
|
||||
txt = inner_text(cells[0])
|
||||
if txt:
|
||||
lines.append(f"\n*{txt}*\n")
|
||||
else:
|
||||
row = [cell_text(c) for c in cells]
|
||||
lines.append("| " + " | ".join(row) + " |")
|
||||
return "\n".join(lines)
|
||||
|
||||
# ── main conversion ───────────────────────────────────────────────────────────
|
||||
|
||||
def convert():
|
||||
tree = ET.parse(XML_PATH)
|
||||
root = tree.getroot()
|
||||
|
||||
meta = root.find("metadatos")
|
||||
texto = root.find("texto")
|
||||
|
||||
titulo = inner_text(meta.find("titulo"))
|
||||
referencia = inner_text(meta.find("identificador"))
|
||||
fecha_pub = inner_text(meta.find("fecha_publicacion"))
|
||||
fecha_pub_fmt = f"{fecha_pub[6:8]}/{fecha_pub[4:6]}/{fecha_pub[:4]}"
|
||||
fecha_disp = inner_text(meta.find("fecha_disposicion"))
|
||||
fecha_disp_fmt = f"{fecha_disp[6:8]}/{fecha_disp[4:6]}/{fecha_disp[:4]}"
|
||||
departamento = inner_text(meta.find("departamento"))
|
||||
numero_oficial = inner_text(meta.find("numero_oficial"))
|
||||
url_eli = inner_text(meta.find("url_eli"))
|
||||
|
||||
md = []
|
||||
|
||||
# ── Título y metadatos ────────────────────────────────────────────────────
|
||||
md.append(f"# {titulo}\n")
|
||||
md.append(f"> **Referencia:** {referencia} ")
|
||||
md.append(f"> **Fecha disposición:** {fecha_disp_fmt} ")
|
||||
md.append(f"> **Fecha publicación:** {fecha_pub_fmt} ")
|
||||
md.append(f"> **Departamento:** {departamento} ")
|
||||
md.append(f"> **Núm. oficial:** RD {numero_oficial} ")
|
||||
md.append(f"> **ELI:** <{url_eli}>")
|
||||
md.append("")
|
||||
|
||||
# ── Cuerpo del texto ──────────────────────────────────────────────────────
|
||||
in_preamble = True
|
||||
preamble_lines = []
|
||||
|
||||
for child in texto:
|
||||
tag = child.tag
|
||||
cls = child.get("class", "")
|
||||
text = inner_text(child)
|
||||
|
||||
if tag == "p":
|
||||
if cls == "articulo":
|
||||
if in_preamble and preamble_lines:
|
||||
md.append("## PREÁMBULO\n")
|
||||
md.extend(preamble_lines)
|
||||
preamble_lines = []
|
||||
in_preamble = False
|
||||
|
||||
# Distinguir artículo vs disposición
|
||||
if re.match(r"^(Artículo|Artículo\s+\d)", text):
|
||||
md.append(f"\n### {text}\n")
|
||||
else:
|
||||
# disposición adicional / final
|
||||
md.append(f"\n### {text}\n")
|
||||
|
||||
elif cls in ("parrafo", "parrafo_2"):
|
||||
if in_preamble:
|
||||
preamble_lines.append(text + "\n")
|
||||
else:
|
||||
md.append(text + "\n")
|
||||
|
||||
elif cls == "centro_redonda":
|
||||
md.append(f"\n**{text}**\n")
|
||||
|
||||
elif cls in ("firma_rey", "firma_ministro"):
|
||||
md.append(f"*{text}* ")
|
||||
|
||||
elif cls == "anexo_num":
|
||||
md.append(f"\n---\n\n## {text}")
|
||||
|
||||
elif cls == "anexo_tit":
|
||||
md.append(f"\n### {text}\n")
|
||||
|
||||
elif cls == "anexo":
|
||||
# ANEXO VII no tiene anexo_num ni tit separados
|
||||
md.append(f"\n---\n\n## {text}\n")
|
||||
|
||||
elif tag == "table":
|
||||
# Detectar si es tabla de 4 cols (ANEXO VII)
|
||||
cols = child.find("colgroup")
|
||||
ncols = len(cols.findall("col")) if cols is not None else 6
|
||||
if ncols <= 4:
|
||||
md.append(table_to_md_short(child))
|
||||
else:
|
||||
md.append(table_to_md(child))
|
||||
md.append("")
|
||||
|
||||
# Si no hubo ningún artículo (no debería pasar)
|
||||
if in_preamble and preamble_lines:
|
||||
md.append("## PREÁMBULO\n")
|
||||
md.extend(preamble_lines)
|
||||
|
||||
output = "\n".join(md)
|
||||
# Limpieza de líneas en blanco excesivas
|
||||
output = re.sub(r"\n{4,}", "\n\n\n", output)
|
||||
|
||||
with open(MD_PATH, "w", encoding="utf-8") as f:
|
||||
f.write(output)
|
||||
|
||||
print(f"✓ Generado: {MD_PATH}")
|
||||
lines = output.count("\n")
|
||||
print(f" {lines} líneas, {len(output)} caracteres")
|
||||
|
||||
if __name__ == "__main__":
|
||||
convert()
|
||||
Loading…
Reference in New Issue