"""Discover Ext/Help/ru.html pages in a 1C configuration dump.""" from __future__ import annotations import re import xml.etree.ElementTree as ET from dataclasses import dataclass from pathlib import Path from export1c_help.types_map import DIR_TO_LINK_TYPE _SYN_RE = re.compile( r"\s*\s*ru\s*(.*?)", re.DOTALL, ) @dataclass(frozen=True) class HelpPage: """One Help/ru.html page.""" path: Path src_root: Path type_dir: str object_name: str form_name: str | None meta_key: str synonym: str link_aliases: tuple[str, ...] = () @property def is_form(self) -> bool: return self.form_name is not None @property def rel_posix(self) -> str: return self.path.relative_to(self.src_root).as_posix() def _xml_unescape(s: str) -> str: return ( s.replace("<", "<") .replace(">", ">") .replace("&", "&") .replace(""", '"') .replace("'", "'") ) def _read_ru_synonym(xml_path: Path) -> str | None: if not xml_path.is_file(): return None try: text = xml_path.read_text(encoding="utf-8-sig", errors="ignore") except OSError: return None m = _SYN_RE.search(text) if m: return _xml_unescape(m.group(1).strip()) try: root = ET.parse(xml_path).getroot() except ET.ParseError: return None for el in root.iter(): if el.tag.endswith("content") and el.text and el.text.strip(): return el.text.strip() return None def _subsystem_chain(parts: tuple[str, ...]) -> list[str] | None: """ From relative parts ending with Ext/Help/ru.html build subsystem name chain. Subsystems/A/Ext/Help/ru.html → [A] Subsystems/A/Subsystems/B/Ext/Help/ru.html → [A, B] """ if parts[0] != "Subsystems": return None body = parts[1:-3] # drop Ext/Help/ru.html names: list[str] = [] i = 0 while i < len(body): if i == 0: names.append(body[i]) i += 1 continue if body[i] == "Subsystems" and i + 1 < len(body): names.append(body[i + 1]) i += 2 continue return None return names or None def _subsystem_xml(src_root: Path, chain: list[str]) -> Path: # Subsystems/A.xml or Subsystems/A/Subsystems/B.xml path = src_root / "Subsystems" for idx, name in enumerate(chain): if idx == 0: candidate = path / f"{name}.xml" path = path / name else: candidate = path / "Subsystems" / f"{name}.xml" path = path / "Subsystems" / name return candidate def _parse_help_path(src_root: Path, html_path: Path) -> HelpPage | None: try: rel = html_path.relative_to(src_root) except ValueError: return None parts = rel.parts if len(parts) < 4 or parts[-1] != "ru.html" or parts[-2] != "Help" or parts[-3] != "Ext": return None type_dir = parts[0] link_type = DIR_TO_LINK_TYPE.get(type_dir) if not link_type: return None form_name: str | None = None aliases: list[str] = [] if type_dir == "Subsystems": chain = _subsystem_chain(parts) if not chain: return None object_name = chain[-1] meta_key = "Subsystem." + ".Subsystem.".join(chain) # 1C help links usually use the leaf name only aliases.append(f"Subsystem.{object_name}") syn = _read_ru_synonym(_subsystem_xml(src_root, chain)) or object_name return HelpPage( path=html_path, src_root=src_root, type_dir=type_dir, object_name=object_name, form_name=None, meta_key=meta_key, synonym=syn, link_aliases=tuple(aliases), ) if type_dir == "CommonForms": object_name = parts[1] meta_key = f"CommonForm.{object_name}" syn = _read_ru_synonym(src_root / "CommonForms" / f"{object_name}.xml") or object_name return HelpPage( path=html_path, src_root=src_root, type_dir=type_dir, object_name=object_name, form_name=None, meta_key=meta_key, synonym=syn, ) if "Forms" in parts: try: forms_idx = parts.index("Forms") except ValueError: return None object_name = parts[1] form_name = parts[forms_idx + 1] meta_key = f"{link_type}.{object_name}.Form.{form_name}" form_xml = src_root / type_dir / object_name / "Forms" / f"{form_name}.xml" syn = _read_ru_synonym(form_xml) or form_name obj_syn = _read_ru_synonym(src_root / type_dir / f"{object_name}.xml") if obj_syn: syn = f"{obj_syn} ({syn})" return HelpPage( path=html_path, src_root=src_root, type_dir=type_dir, object_name=object_name, form_name=form_name, meta_key=meta_key, synonym=syn, ) object_name = parts[1] meta_key = f"{link_type}.{object_name}" syn = _read_ru_synonym(src_root / type_dir / f"{object_name}.xml") or object_name return HelpPage( path=html_path, src_root=src_root, type_dir=type_dir, object_name=object_name, form_name=None, meta_key=meta_key, synonym=syn, ) def discover_help_pages( src_root: Path, *, include_forms: bool = True, ) -> list[HelpPage]: """Find all Help/ru.html under src_root.""" src_root = src_root.resolve() pages: list[HelpPage] = [] seen_meta: set[str] = set() for html in sorted(src_root.rglob("Ext/Help/ru.html")): page = _parse_help_path(src_root, html) if page is None: continue if page.is_form and not include_forms: continue if page.meta_key in seen_meta: # should not happen; keep first continue seen_meta.add(page.meta_key) pages.append(page) return pages def build_link_title_map(pages: list[HelpPage], titles: dict[str, str]) -> dict[str, str]: """ meta_key / alias → wiki title for href rewrite. Leaf aliases (Subsystem.X) are registered only when unique. """ result: dict[str, str] = {} alias_owners: dict[str, list[str]] = {} for p in pages: result[p.meta_key] = titles[p.meta_key] for alias in p.link_aliases: alias_owners.setdefault(alias, []).append(p.meta_key) for alias, owners in alias_owners.items(): if len(owners) == 1: result[alias] = titles[owners[0]] return result