fix: rewrite exporters to match exact native formats

This commit is contained in:
Abdessamad Derraz
2026-03-30 15:49:33 +02:00
parent 1e6b499602
commit 74269bab84
6 changed files with 175 additions and 81 deletions

View File

@@ -1,13 +1,25 @@
"""Exporter for Recalbox es_bios.xml format."""
"""Exporter for Recalbox es_bios.xml format.
Produces XML matching the exact format of recalbox's es_bios.xml:
- XML namespace declaration
- <system fullname="..." platform="...">
- <bios path="system/file" md5="..." core="..." /> with optional mandatory, hashMatchMandatory, note
- mandatory absent = true (only explicit when false)
- 2-space indentation
"""
from __future__ import annotations
from pathlib import Path
from xml.etree.ElementTree import Element, SubElement, ElementTree, indent
from .base_exporter import BaseExporter
def _slug_to_display(slug: str) -> str:
"""Convert slug to display name."""
return slug.replace("-", " ").title()
class Exporter(BaseExporter):
"""Export truth data to Recalbox es_bios.xml format."""
@@ -22,13 +34,21 @@ class Exporter(BaseExporter):
scraped_data: dict | None = None,
) -> None:
native_map: dict[str, str] = {}
display_map: dict[str, str] = {}
if scraped_data:
for sys_id, sys_data in scraped_data.get("systems", {}).items():
nid = sys_data.get("native_id")
if nid:
native_map[sys_id] = nid
dname = sys_data.get("name")
if dname:
display_map[sys_id] = dname
root = Element("biosList")
lines: list[str] = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<biosList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
' xsi:noNamespaceSchemaLocation="es_bios.xsd">',
]
systems = truth_data.get("systems", {})
for sys_id in sorted(systems):
@@ -38,33 +58,46 @@ class Exporter(BaseExporter):
continue
native_id = native_map.get(sys_id, sys_id)
system_el = SubElement(root, "system", platform=native_id)
display_name = display_map.get(sys_id, _slug_to_display(sys_id))
lines.append(f' <system fullname="{display_name}" platform="{native_id}">')
for fe in files:
name = fe.get("name", "")
if name.startswith("_"):
if name.startswith("_") or self._is_pattern(name):
continue
dest = fe.get("destination", name)
# Recalbox paths include system prefix
path = f"{native_id}/{dest}" if "/" not in dest else dest
md5 = fe.get("md5", "")
if isinstance(md5, list):
md5 = ",".join(md5)
required = fe.get("required", False)
attrs = {
"path": dest,
"md5": md5,
"mandatory": "true" if required else "false",
"hashMatchMandatory": "true" if required else "false",
}
SubElement(system_el, "bios", **attrs)
required = fe.get("required", True)
indent(root, space=" ")
tree = ElementTree(root)
tree.write(output_path, encoding="unicode", xml_declaration=True)
# Add trailing newline
with open(output_path, "a") as f:
f.write("\n")
# Build cores string from _cores
cores_list = fe.get("_cores", [])
core_str = ",".join(f"libretro/{c}" for c in cores_list) if cores_list else ""
attrs = [f'path="{path}"']
if md5:
attrs.append(f'md5="{md5}"')
if not required:
attrs.append('mandatory="false"')
if not required:
attrs.append('hashMatchMandatory="true"')
if core_str:
attrs.append(f'core="{core_str}"')
lines.append(f' <bios {" ".join(attrs)} />')
lines.append(" </system>")
lines.append("</biosList>")
lines.append("")
Path(output_path).write_text("\n".join(lines), encoding="utf-8")
def validate(self, truth_data: dict, output_path: str) -> list[str]:
from xml.etree.ElementTree import parse as xml_parse
@@ -77,14 +110,16 @@ class Exporter(BaseExporter):
path = bios_el.get("path", "")
if path:
exported_paths.add(path)
# Also index basename
exported_paths.add(path.split("/")[-1])
issues: list[str] = []
for sys_data in truth_data.get("systems", {}).values():
for fe in sys_data.get("files", []):
name = fe.get("name", "")
if name.startswith("_"):
if name.startswith("_") or self._is_pattern(name):
continue
dest = fe.get("destination", name)
if dest not in exported_paths:
issues.append(f"missing: {dest}")
if name not in exported_paths and dest not in exported_paths:
issues.append(f"missing: {name}")
return issues