jaimenms's picture
text2ipc IPC 20260101 PT, multilingual-e5-base, Inference Endpoints handler
2409f79 verified
Raw History Blame Contribute Delete
2.81 kB
"""Turn a WIPO ``ipc_scheme`` XML file into a flat, ordered list of :class:`IpcNode`.
Entries nest physically in the XML (subgroups inside their main group, groups inside
the subclass...). Alongside the classifiable entries there are:
- ``t`` subsection titles (plain headings between classes, no symbol of their own)
- ``g`` guidance headings that span a range of main groups
- ``n`` notes
- ``i`` catchword indexes
None of those are classification targets. Guidance headings are kept as context for
the entries that sit under them; the rest are skipped.
"""
from __future__ import annotations
import xml.etree.ElementTree as ET
from collections.abc import Iterator
from pathlib import Path
from .model import IpcNode
from .symbols import level_of_kind
NS = "{http://www.wipo.int/classifications/ipc/masterfiles}"
TITLE_PART_SEPARATOR = "; "
def parse_scheme(path: Path | str) -> list[IpcNode]:
"""Entries in depth-first order, so every parent precedes its children."""
root = ET.parse(path).getroot()
return list(_walk(root, parent=None))
def _walk(element: ET.Element, *, parent: str | None) -> Iterator[IpcNode]:
heading: tuple[str, str] | None = None # (title, last symbol it covers)
for child in element:
if child.tag != f"{NS}ipcEntry":
continue
kind = child.get("kind", "")
level = level_of_kind(kind)
if level is None:
if kind == "g":
# A guidance heading is an empty leaf in the XML covering the sibling
# main groups from ``symbol`` to ``endSymbol``.
heading = (_title_of(child), child.get("endSymbol") or child.get("symbol", ""))
continue
symbol = child.get("symbol", "")
under_heading = heading is not None and level == "group" and symbol <= heading[1]
yield IpcNode(
symbol=symbol,
level=level,
depth=int(kind) if kind.isdigit() else 0,
parent=parent,
title=_title_of(child),
heading=heading[0] if under_heading and heading else "",
)
yield from _walk(child, parent=symbol)
def _title_of(entry: ET.Element) -> str:
"""Join the title parts, dropping cross references such as "(edge trimmers A01G 3/06)".
Cross references point *away* from the entry; embedding them would attract exactly
the texts that belong elsewhere.
"""
body = entry.find(f"{NS}textBody/{NS}title")
if body is None:
return ""
parts = []
for part in body.findall(f"{NS}titlePart"):
text = part.find(f"{NS}text")
if text is not None:
value = "".join(text.itertext()).strip()
if value:
parts.append(" ".join(value.split()))
return TITLE_PART_SEPARATOR.join(parts)