Extract component hierarchy from Lazarus .lfm form files. .lfm is a text-based declarative format for UI component trees, structured as: object ComponentName: TClassName PropertyName = Value OnEvent = HandlerName object ChildName: TChildClass ..
(path: Path)
| 13577 | |
| 13578 | |
| 13579 | def extract_lazarus_form(path: Path) -> dict: |
| 13580 | """Extract component hierarchy from Lazarus .lfm form files. |
| 13581 | |
| 13582 | .lfm is a text-based declarative format for UI component trees, structured as: |
| 13583 | object ComponentName: TClassName |
| 13584 | PropertyName = Value |
| 13585 | OnEvent = HandlerName |
| 13586 | object ChildName: TChildClass |
| 13587 | ... |
| 13588 | end |
| 13589 | end |
| 13590 | |
| 13591 | Produces nodes for: |
| 13592 | - The form file itself |
| 13593 | - Each component class encountered (TForm1, TButton, TPanel, ...) |
| 13594 | - Event handler names referenced by OnXxx properties |
| 13595 | |
| 13596 | Produces edges for: |
| 13597 | - file --contains--> root form class |
| 13598 | - parent component --contains--> child component class |
| 13599 | - component --references--> event handler (context: "event") |
| 13600 | """ |
| 13601 | try: |
| 13602 | text = path.read_text(encoding="utf-8", errors="replace") |
| 13603 | except Exception as e: |
| 13604 | return {"nodes": [], "edges": [], "error": str(e)} |
| 13605 | |
| 13606 | import re |
| 13607 | str_path = str(path) |
| 13608 | stem = _file_stem(path) |
| 13609 | nodes: list[dict] = [] |
| 13610 | edges: list[dict] = [] |
| 13611 | seen_ids: set[str] = set() |
| 13612 | seen_edge_pairs: set[tuple[str, str, str]] = set() |
| 13613 | |
| 13614 | def add_node(nid: str, label: str, line: int) -> None: |
| 13615 | if nid not in seen_ids: |
| 13616 | seen_ids.add(nid) |
| 13617 | nodes.append({ |
| 13618 | "id": nid, "label": label, "file_type": "code", |
| 13619 | "source_file": str_path, "source_location": f"L{line}", |
| 13620 | }) |
| 13621 | |
| 13622 | def add_edge( |
| 13623 | src: str, tgt: str, relation: str, line: int, |
| 13624 | context: str | None = None, |
| 13625 | ) -> None: |
| 13626 | key = (src, tgt, relation) |
| 13627 | if key in seen_edge_pairs: |
| 13628 | return |
| 13629 | seen_edge_pairs.add(key) |
| 13630 | edge: dict[str, Any] = { |
| 13631 | "source": src, "target": tgt, "relation": relation, |
| 13632 | "confidence": "EXTRACTED", "source_file": str_path, |
| 13633 | "source_location": f"L{line}", "weight": 1.0, |
| 13634 | } |
| 13635 | if context: |
| 13636 | edge["context"] = context |