GitHub Actions
deploy: live Hugging Face Space application
53d9817
Raw History Blame Contribute Delete
3.84 kB
from __future__ import annotations
import json
import xml.etree.ElementTree as ET
from collections.abc import Iterator
from pathlib import Path
from typing import ClassVar
from universal_parser.core.router import register
from universal_parser.core.schema import Element, TableData
from universal_parser.core.sniffer import FileType
from universal_parser.extractors.base import BaseExtractor
@register
class JSONXMLExtractor(BaseExtractor):
"""
Extractor for JSON (.json) and XML (.xml) files.
Handles:
- List of objects in JSON -> Table element (if tabular schema)
- Nested JSON objects -> formatted code block element
- XML -> element nodes parsed into headings, paragraphs, and code blocks
"""
supported_types: ClassVar[list[FileType]] = [FileType.JSON, FileType.XML]
def stream(self, path: str | Path) -> Iterator[Element]:
path_obj = Path(path)
ext = path_obj.suffix.lower()
if ext == ".json":
yield from self._stream_json(path_obj)
elif ext == ".xml":
yield from self._stream_xml(path_obj)
def _stream_json(self, path: Path) -> Iterator[Element]:
try:
with open(path, encoding="utf-8", errors="replace") as f:
data = json.load(f)
# Check if it's a list of uniform dictionaries (tabular JSON)
if isinstance(data, list) and data and all(isinstance(row, dict) for row in data):
headers = list(data[0].keys())
rows = [[str(row.get(h, "")).strip() for h in headers] for row in data]
md_header = "| " + " | ".join(headers) + " |"
md_separator = "| " + " | ".join(["---"] * len(headers)) + " |"
md_rows = ["| " + " | ".join(r) + " |" for r in rows]
markdown_repr = "\n".join([md_header, md_separator] + md_rows)
yield Element(
type="table",
text=path.name,
data=TableData(headers=headers, rows=rows),
markdown_repr=markdown_repr,
confidence=1.0,
)
else:
# General JSON: render formatted code block
formatted_json = json.dumps(data, indent=2)
yield Element(
type="code_block",
text=formatted_json,
markdown_repr=f"```json\n{formatted_json}\n```",
confidence=1.0,
)
except Exception: # noqa: BLE001
return
def _stream_xml(self, path: Path) -> Iterator[Element]:
try:
tree = ET.parse(path)
root = tree.getroot()
# Yield root tag as level 1 heading
yield Element(
type="heading",
level=1,
text=root.tag,
markdown_repr=f"# {root.tag}",
confidence=1.0,
)
for child in root:
text_val = (child.text or "").strip()
if text_val:
yield Element(
type="paragraph",
text=f"{child.tag}: {text_val}",
markdown_repr=f"**{child.tag}**: {text_val}",
confidence=1.0,
)
else:
child_str = ET.tostring(child, encoding="unicode").strip()
if child_str:
yield Element(
type="code_block",
text=child_str,
markdown_repr=f"```xml\n{child_str}\n```",
confidence=1.0,
)
except Exception: # noqa: BLE001
return