Extract component hierarchy from Delphi .dfm form files. .dfm files come in two formats: - Text (same `object Name: TClassName ... end` syntax as .lfm) - Binary (starts with a TPF0/FF0A magic header — unreadable as text) Binary .dfm files are skipped gracefully: an empty result is
(path: Path)
| 13671 | |
| 13672 | |
| 13673 | def extract_delphi_form(path: Path) -> dict: |
| 13674 | """Extract component hierarchy from Delphi .dfm form files. |
| 13675 | |
| 13676 | .dfm files come in two formats: |
| 13677 | - Text (same `object Name: TClassName ... end` syntax as .lfm) |
| 13678 | - Binary (starts with a TPF0/FF0A magic header — unreadable as text) |
| 13679 | |
| 13680 | Binary .dfm files are skipped gracefully: an empty result is returned |
| 13681 | so the rest of the pipeline is unaffected. Convert binary forms to |
| 13682 | text in the Delphi IDE via File → Save As (Text DFM) if you want them |
| 13683 | indexed. |
| 13684 | |
| 13685 | Text .dfm files are parsed identically to .lfm: component containment |
| 13686 | (`contains`) and event handler references (`references`, context "event"). |
| 13687 | """ |
| 13688 | try: |
| 13689 | raw = path.read_bytes() |
| 13690 | except Exception as e: |
| 13691 | return {"nodes": [], "edges": [], "error": str(e)} |
| 13692 | |
| 13693 | # Detect binary DFM: Delphi binary resource streams start with FF 0A |
| 13694 | if raw[:2] == b"\xff\x0a": |
| 13695 | return { |
| 13696 | "nodes": [], "edges": [], |
| 13697 | "error": f"binary DFM (convert to text in Delphi IDE to index): {path.name}", |
| 13698 | } |
| 13699 | |
| 13700 | # Text DFM — delegate to the shared form parser (same syntax as .lfm) |
| 13701 | try: |
| 13702 | text = raw.decode("utf-8", errors="replace") |
| 13703 | except Exception as e: |
| 13704 | return {"nodes": [], "edges": [], "error": str(e)} |
| 13705 | |
| 13706 | import re |
| 13707 | str_path = str(path) |
| 13708 | stem = _file_stem(path) |
| 13709 | nodes: list[dict] = [] |
| 13710 | edges: list[dict] = [] |
| 13711 | seen_ids: set[str] = set() |
| 13712 | seen_edge_pairs: set[tuple[str, str, str]] = set() |
| 13713 | |
| 13714 | def add_node(nid: str, label: str, line: int) -> None: |
| 13715 | if nid not in seen_ids: |
| 13716 | seen_ids.add(nid) |
| 13717 | nodes.append({ |
| 13718 | "id": nid, "label": label, "file_type": "code", |
| 13719 | "source_file": str_path, "source_location": f"L{line}", |
| 13720 | }) |
| 13721 | |
| 13722 | def add_edge( |
| 13723 | src: str, tgt: str, relation: str, line: int, |
| 13724 | context: str | None = None, |
| 13725 | ) -> None: |
| 13726 | key = (src, tgt, relation) |
| 13727 | if key in seen_edge_pairs: |
| 13728 | return |
| 13729 | seen_edge_pairs.add(key) |
| 13730 | edge: dict[str, Any] = { |