Download text2ipc/scheme/parse.py from jaimenms/text2ipc-pt: direct link, hf CLI and curl.
- Browser
- Download file 2.81 kB
-
https://huggingface.co/jaimenms/text2ipc-pt/resolve/main/text2ipc/scheme/parse.py
- Command line
-
hf download hf://jaimenms/text2ipc-pt/text2ipc/scheme/parse.py
-
curl -L -o parse.py https://huggingface.co/jaimenms/text2ipc-pt/resolve/main/text2ipc/scheme/parse.py
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) | |