4000 lines
185 KiB
Python
4000 lines
185 KiB
Python
"""Mechanical profiles for decoded 1C form payloads (root marker 4)."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
from typing import Any
|
||
|
||
from .child_records import collect_evidence, declared_child_records
|
||
from .payload import GUID_RE, collect_strings, root_signature, scalar
|
||
from .structured_metadata import get_by_path
|
||
|
||
|
||
BSL_ROUTINE_RE = re.compile(r"(?im)^\s*(?:&[^\r\n]+\s*)*(?:Асинх\s+)?(Процедура|Функция)\s+([A-Za-zА-Яа-яЁё_][\wА-Яа-яЁё]*)")
|
||
BSL_IDENTIFIER_RE = re.compile(r"^[A-Za-zА-Яа-яЁё_][\wА-Яа-яЁё]*$")
|
||
BSL_PATH_RE = re.compile(r"^[A-Za-zА-Яа-яЁё_][\wА-Яа-яЁё]*(?:\.[A-Za-zА-Яа-яЁё_][\wА-Яа-яЁё]*)*$")
|
||
|
||
FORM_EVENT_NAMES = {
|
||
"01d80ddd-dce5-4db3-beb5-f63c97cb05b9": "OnEditEnd",
|
||
"047d4d09-961c-4bdc-8519-eef10674c35b": "AfterWrite",
|
||
"11707a99-4eb9-4373-bc8c-84891483a034": "Click",
|
||
"1282f000-23b6-4887-87f4-9e8e79db3d32": "Selection",
|
||
"14256303-d2b7-4a58-bfab-e77493d10a59": "EditTextChange",
|
||
"178a97c4-0ffe-4fcc-93e6-505369939da5": "AutoComplete",
|
||
"1960479b-4d89-4eba-8b39-0aa802020558": "StartChoice",
|
||
"213d1900-dcad-4616-9f20-3f077156a40f": "AfterWriteAtServer",
|
||
"2391e7b8-7235-45d7-ab7e-6ff3dc086396": "BeforeAddRow",
|
||
"2988b2a5-c887-4928-94ae-5d0c9c31e999": "DetailProcessing",
|
||
"2ccfdec5-583d-4eca-8319-e55de492665a": "BeforeDeleteRow",
|
||
"3699f6a3-9a2a-4c82-a775-6ff4824a08ca": "NotificationProcessing",
|
||
"390d5e4b-e732-4c88-8748-9e211a416984": "OnReadAtServer",
|
||
"1952a54f-35ad-4928-902f-df212ab38ca3": "OnSaveDataInSettingsAtServer",
|
||
"3c3da18f-fc18-4f77-8c2d-96c25bec40a5": "Selection",
|
||
"3ccc650e-f631-4cae-8e33-3eaac610b5f9": "OnOpen",
|
||
"526c501f-ed3f-4db4-8731-fd0324707501": "OnCurrentPageChange",
|
||
"509eca20-d6e4-4fef-a0f8-3a6b44c64178": "URLProcessing",
|
||
"60edb81d-887b-478e-94ee-7fef2b13393d": "OnActivateRow",
|
||
"650da4af-3233-4ce0-a1ae-23f87a226eee": "DetailProcessing",
|
||
"8a5894c9-d2ff-4c1d-b433-89cc352bbfbc": "BeforeWrite",
|
||
"8f42e083-be92-4102-b1f0-fa58452c1a63": "BeforeWriteAtServer",
|
||
"93dfba16-26db-46f8-acb5-4f92f50c855f": "NavigationProcessing",
|
||
"9f2e5ddb-3492-4f5d-8f0d-416b8d1d5c5b": "OnCreateAtServer",
|
||
"97365900-eadf-4dfd-a9aa-fbb9ecabd079": "OnGetDataAtServer",
|
||
"9874537f-454c-40ae-83e9-3b9cefbc6d08": "Click",
|
||
"ab930362-ff94-4dcb-ad16-188805d23e3c": "BeforeRowChange",
|
||
"aeba313d-c467-44b3-b4a2-956340932c8f": "Creating",
|
||
"ac5a9c5a-5f1d-4fc5-b88c-a187038c16d1": "Opening",
|
||
"b3c10170-c5ff-4cba-b537-679e1c872b45": "OnStartEdit",
|
||
"bf0ac0e1-bcbb-4dfe-8fc4-0b1923b461a6": "BeforeWriteAtServer",
|
||
"b50dc41b-c15a-4ebe-a17f-d01e51c47de6": "Clearing",
|
||
"c331eb1b-d32b-4533-844c-1276600b64e3": "TextEditEnd",
|
||
"ca21cd18-35b2-4281-b5c8-016ecc8da8ac": "OnClose",
|
||
"d710ea07-5c96-4c43-ab6e-e138d3653780": "URLProcessing",
|
||
"de65638d-a806-4a76-bc10-f62bbc86e0e7": "AfterDeleteRow",
|
||
"eba5f295-c611-4dd9-84b5-22911ad60c53": "Click",
|
||
"e773807c-0c0c-4689-a093-231ddcd6409f": "BeforeLoadDataFromSettingsAtServer",
|
||
"e73d6384-49d2-4885-a752-a674d6ff7742": "FillCheckProcessingAtServer",
|
||
"70636369-514c-4662-977e-1c3976c9756c": "Tuning",
|
||
"f228b12f-d892-4925-b338-695617357b32": "OnActivateCell",
|
||
"f72043b8-2d79-414e-bc4e-3972fe9dbca1": "ChoiceProcessing",
|
||
"fe115cc8-9e33-4684-a166-bd5136fe7a9f": "OnChange",
|
||
}
|
||
|
||
MARKER_NAMES = {
|
||
"9": "AttributeOrCommand",
|
||
"12": "ExtendedTooltip",
|
||
"22": "ContainerItem",
|
||
"31": "CommandBarButton",
|
||
"34": "Button",
|
||
"35": "InputField",
|
||
"37": "InputField",
|
||
"55": "DynamicListTable",
|
||
}
|
||
|
||
FORM_ITEM_TYPE_NAMES = {
|
||
"0": "Командная панель",
|
||
"1": "Подменю",
|
||
"2": "Группа колонок",
|
||
"3": "Страницы",
|
||
"4": "Страница",
|
||
"5": "Группа",
|
||
"6": "Группа кнопок",
|
||
"8": "Контекстное меню",
|
||
"9": "Командная панель",
|
||
"12": "Расширенная подсказка",
|
||
"31": "Кнопка командной панели",
|
||
"34": "Кнопка",
|
||
"48": "Поле формы",
|
||
"55": "Динамический список",
|
||
"73": "Таблица формы",
|
||
}
|
||
|
||
FORM_TABLE_ADDITION_TYPE_NAMES = {
|
||
"0": "SearchStringAddition",
|
||
"1": "ViewStatusAddition",
|
||
"2": "SearchControlAddition",
|
||
}
|
||
|
||
FORM_FIELD_SUBTYPE_NAMES = {
|
||
"1": "Поле надписи",
|
||
"2": "Поле ввода",
|
||
"3": "Поле флажка",
|
||
"4": "PictureField",
|
||
"5": "Поле переключателя",
|
||
"6": "Поле табличного документа",
|
||
"11": "ChartField",
|
||
"14": "GraphicalSchemaField",
|
||
}
|
||
|
||
FORM_DECORATION_TYPE_NAMES = {
|
||
"0": "Декорация надписи",
|
||
"1": "Декорация картинки",
|
||
}
|
||
|
||
FORM_LOCAL_COMMAND_GROUP_GUID = "409b9a53-7f7e-4178-86c1-33176c7c7a7a"
|
||
FORM_STANDARD_COMMANDS = {
|
||
("198ea630-fda2-4cda-8a23-f999f4c67ee6", "0"): "Form.StandardCommand.CustomizeForm",
|
||
("39bb0fe9-771d-4dd5-8a6e-2d16984523af", "0"): "Form.StandardCommand.Help",
|
||
("fe558fde-99b3-45d0-a060-9fc2905309f6", "0"): "Form.StandardCommand.Write",
|
||
("1f317795-c420-4a30-b594-c492abc55f7a", "0"): "Form.StandardCommand.Reread",
|
||
("68baa1bc-edd1-4d9b-ad80-1d53fb8a7988", "0"): "Form.StandardCommand.Copy",
|
||
("827b541d-30c1-4f06-aecf-92aa496a0835", "0"): "Form.StandardCommand.SetDeletionMark",
|
||
("3a17e914-ec6a-4280-b4df-78914f40522b", "0"): "Form.StandardCommand.ShowInList",
|
||
("174e58ce-82ad-4787-b956-9367937f7971", "0"): "Form.StandardCommand.ChangeHistory",
|
||
("6886601d-276c-4d3f-af0a-05c586025608", "0"): "Form.StandardCommand.Change",
|
||
("bdefa701-6685-453e-a02a-3683d0cc16d3", "0"): "Form.StandardCommand.Find",
|
||
("96e0bc70-f8ff-4732-8119-060923203629", "0"): "Form.StandardCommand.CancelSearch",
|
||
}
|
||
|
||
FORM_GRAPHICAL_SCHEMA_STANDARD_COMMANDS = {
|
||
("e2d6f793-b786-4640-a91b-8d77f73860f1", "3"): "Print",
|
||
("1d13f9a3-402a-46cb-9c68-1709356840f2", "3"): "Preview",
|
||
("01db2225-b62d-4112-a4b6-d39d627bf79f", "3"): "PageSetup",
|
||
}
|
||
|
||
FORM_TABLE_STANDARD_COMMANDS = {
|
||
"b0016a68-ec64-4e6d-b905-c71fd62efc4c": "Add",
|
||
"0ae4bea5-23be-42a7-b69e-97b11b29c453": "Copy",
|
||
"b41f5bbc-ba5d-4888-8cd1-db246a371418": "Change",
|
||
"8d772f97-c0ef-47c0-9cb0-efea28c61341": "Delete",
|
||
"9ef79140-3de6-436a-8dda-610bb963f5db": "EndEdit",
|
||
"daa306cd-a78a-4e74-a14c-739daba624cb": "SetDateInterval",
|
||
"c0519548-2a9a-44de-a25e-faf01e089d4d": "Find",
|
||
"44ad3ec9-f3c2-4913-9224-5f9fb6418743": "CancelSearch",
|
||
"88078230-1f6b-415f-99e4-ad2ff73810cf": "CopyToClipboard",
|
||
"37740564-9e86-44a0-bea9-3f485a5a3f91": "MoveUp",
|
||
"fa51b106-eae6-44c7-8054-76cbb3100603": "MoveDown",
|
||
"2bbe4e12-06d2-409b-a972-eea585125d83": "SortListAsc",
|
||
"58b2a785-23f6-4b0e-a324-9a1323285595": "SortListDesc",
|
||
"49602716-fea6-497f-8047-726404038857": "OutputList",
|
||
}
|
||
|
||
FORM_OBJECT_COMMANDS = {
|
||
("0fa77ef7-a836-4459-9bca-6010d0bdfc7f", "0"): "Выполнено",
|
||
("dcff004c-1b61-4a19-b977-ea21bf688614", "0"): "Перенаправить",
|
||
}
|
||
|
||
FORM_STANDARD_DATA_FIELDS = {
|
||
"-2": "Code",
|
||
"-3": "Description",
|
||
"-4": "Parent",
|
||
"-5": "Ref",
|
||
}
|
||
|
||
FORM_PUBLIC_DATA_FIELD_NAMES = {
|
||
"Код": "Code",
|
||
"Номер": "Number",
|
||
"Наименование": "Description",
|
||
"Родитель": "Parent",
|
||
"НомерСтроки": "LineNumber",
|
||
}
|
||
|
||
FORM_ITEM_MARKERS = {"6", "12", "22", "31", "34", "35", "37", "48", "55", "73"}
|
||
|
||
FORM_ITEM_PARAMETER_ROLES = {
|
||
"6": {
|
||
0: "Маркер дополнения таблицы",
|
||
1: "Идентификатор",
|
||
2: "Использование",
|
||
3: "Подчинение",
|
||
4: "Группа",
|
||
5: "Вид дополнения",
|
||
6: "Имя",
|
||
},
|
||
"12": {
|
||
0: "Маркер расширенной подсказки",
|
||
1: "Идентификатор",
|
||
2: "Использование",
|
||
3: "Подчинение",
|
||
4: "Группа",
|
||
6: "Имя",
|
||
},
|
||
"22": {
|
||
0: "Маркер элемента",
|
||
1: "Идентификатор",
|
||
2: "Использование",
|
||
3: "Подчинение",
|
||
4: "Группа",
|
||
5: "Вид элемента",
|
||
6: "Имя",
|
||
7: "Заголовок",
|
||
},
|
||
"31": {
|
||
0: "Маркер кнопки командной панели",
|
||
1: "Идентификатор",
|
||
2: "Использование",
|
||
3: "Подчинение",
|
||
4: "Группа",
|
||
5: "Имя",
|
||
6: "Заголовок",
|
||
},
|
||
"34": {
|
||
0: "Маркер кнопки",
|
||
1: "Идентификатор",
|
||
2: "Использование",
|
||
3: "Подчинение",
|
||
4: "Группа",
|
||
5: "Вид кнопки",
|
||
6: "Имя",
|
||
},
|
||
"35": {
|
||
0: "Маркер поля ввода",
|
||
1: "Идентификатор",
|
||
2: "Использование",
|
||
3: "Подчинение",
|
||
4: "Группа",
|
||
5: "Вид элемента",
|
||
6: "Имя",
|
||
9: "Заголовок",
|
||
11: "Путь к данным",
|
||
},
|
||
"37": {
|
||
0: "Маркер поля ввода",
|
||
1: "Идентификатор",
|
||
2: "Использование",
|
||
3: "Подчинение",
|
||
4: "Группа",
|
||
5: "Вид элемента",
|
||
6: "Имя",
|
||
9: "Заголовок",
|
||
11: "Путь к данным",
|
||
},
|
||
"48": {
|
||
0: "Маркер поля формы",
|
||
1: "Идентификатор",
|
||
2: "Использование",
|
||
3: "Подчинение",
|
||
4: "Группа",
|
||
5: "Вид элемента",
|
||
6: "Имя",
|
||
12: "Путь к данным",
|
||
},
|
||
"55": {
|
||
0: "Маркер динамического списка",
|
||
1: "Идентификатор",
|
||
2: "Использование",
|
||
3: "Подчинение",
|
||
4: "Группа",
|
||
5: "Имя",
|
||
11: "Путь к данным",
|
||
},
|
||
"73": {
|
||
0: "Маркер таблицы формы",
|
||
1: "Идентификатор",
|
||
2: "Использование",
|
||
3: "Подчинение",
|
||
4: "Группа",
|
||
5: "Имя",
|
||
12: "Путь к данным",
|
||
},
|
||
}
|
||
|
||
SECTION_RECORD_PARAMETER_ROLES = {
|
||
0: "Маркер записи",
|
||
1: "Идентификатор",
|
||
2: "Имя",
|
||
3: "Заголовок",
|
||
6: "Имя",
|
||
}
|
||
|
||
FORM_ITEM_SEMANTIC_PROPERTIES = {
|
||
"6": {
|
||
1: ("Основные", "Идентификатор"),
|
||
2: ("Иерархия", "Использование"),
|
||
3: ("Иерархия", "Подчинение"),
|
||
4: ("Иерархия", "Группа"),
|
||
5: ("Основные", "Вид"),
|
||
6: ("Основные", "Имя"),
|
||
},
|
||
"12": {
|
||
1: ("Основные", "Идентификатор"),
|
||
2: ("Иерархия", "Использование"),
|
||
3: ("Иерархия", "Подчинение"),
|
||
4: ("Иерархия", "Группа"),
|
||
6: ("Основные", "Имя"),
|
||
},
|
||
"22": {
|
||
1: ("Основные", "Идентификатор"),
|
||
2: ("Иерархия", "Использование"),
|
||
3: ("Иерархия", "Подчинение"),
|
||
4: ("Иерархия", "Группа"),
|
||
5: ("Основные", "Вид"),
|
||
6: ("Основные", "Имя"),
|
||
7: ("Основные", "Заголовок"),
|
||
},
|
||
"31": {
|
||
1: ("Основные", "Идентификатор"),
|
||
2: ("Иерархия", "Использование"),
|
||
3: ("Иерархия", "Подчинение"),
|
||
4: ("Иерархия", "Группа"),
|
||
5: ("Основные", "Имя"),
|
||
6: ("Основные", "Заголовок"),
|
||
},
|
||
"34": {
|
||
1: ("Основные", "Идентификатор"),
|
||
2: ("Иерархия", "Использование"),
|
||
3: ("Иерархия", "Подчинение"),
|
||
4: ("Иерархия", "Группа"),
|
||
5: ("Основные", "Вид"),
|
||
6: ("Основные", "Имя"),
|
||
},
|
||
"35": {
|
||
1: ("Основные", "Идентификатор"),
|
||
2: ("Иерархия", "Использование"),
|
||
3: ("Иерархия", "Подчинение"),
|
||
4: ("Иерархия", "Группа"),
|
||
5: ("Основные", "Вид"),
|
||
6: ("Основные", "Имя"),
|
||
9: ("Основные", "Заголовок"),
|
||
},
|
||
"37": {
|
||
1: ("Основные", "Идентификатор"),
|
||
2: ("Иерархия", "Использование"),
|
||
3: ("Иерархия", "Подчинение"),
|
||
4: ("Иерархия", "Группа"),
|
||
5: ("Основные", "Вид"),
|
||
6: ("Основные", "Имя"),
|
||
9: ("Основные", "Заголовок"),
|
||
},
|
||
"48": {
|
||
1: ("Основные", "Идентификатор"),
|
||
2: ("Иерархия", "Использование"),
|
||
3: ("Иерархия", "Подчинение"),
|
||
4: ("Иерархия", "Группа"),
|
||
5: ("Основные", "Вид"),
|
||
6: ("Основные", "Имя"),
|
||
},
|
||
"55": {
|
||
1: ("Основные", "Идентификатор"),
|
||
2: ("Иерархия", "Использование"),
|
||
3: ("Иерархия", "Подчинение"),
|
||
4: ("Иерархия", "Группа"),
|
||
5: ("Основные", "Имя"),
|
||
},
|
||
"73": {
|
||
1: ("Основные", "Идентификатор"),
|
||
2: ("Иерархия", "Использование"),
|
||
3: ("Иерархия", "Подчинение"),
|
||
4: ("Иерархия", "Группа"),
|
||
5: ("Основные", "Имя"),
|
||
},
|
||
}
|
||
|
||
SECTION_RECORD_SEMANTIC_PROPERTIES = {
|
||
1: ("Основные", "Идентификатор"),
|
||
2: ("Основные", "Имя"),
|
||
3: ("Основные", "Заголовок"),
|
||
6: ("Основные", "Имя"),
|
||
}
|
||
|
||
|
||
def children(node: Any) -> list[Any]:
|
||
if isinstance(node, dict) and node.get("type") in {"list", "sequence"}:
|
||
return node.get("items") or []
|
||
return []
|
||
|
||
|
||
def child_at(node: Any, index: int) -> Any:
|
||
items = children(node)
|
||
return items[index] if 0 <= index < len(items) else None
|
||
|
||
|
||
def atoms(node: Any, *, limit: int = 100) -> list[str]:
|
||
result: list[str] = []
|
||
|
||
def walk(value: Any) -> None:
|
||
if len(result) >= limit:
|
||
return
|
||
text = scalar(value)
|
||
if text:
|
||
result.append(text)
|
||
return
|
||
for child in children(value):
|
||
walk(child)
|
||
|
||
walk(node)
|
||
return result
|
||
|
||
|
||
def guids(node: Any, *, limit: int = 100) -> list[str]:
|
||
return [value.lower() for value in atoms(node, limit=limit * 4) if GUID_RE.fullmatch(value)][:limit]
|
||
|
||
|
||
def child_scalar(node: Any, index: int) -> str | None:
|
||
items = children(node)
|
||
if index < 0 or index >= len(items):
|
||
return None
|
||
return scalar(items[index])
|
||
|
||
|
||
def direct_scalar(node: Any) -> str | None:
|
||
if isinstance(node, dict) and node.get("type") in {"atom", "string", "number", "guid", "base64"}:
|
||
value = node.get("value")
|
||
return "" if value is None else str(value)
|
||
return None
|
||
|
||
|
||
def child_direct_scalar(node: Any, index: int) -> str | None:
|
||
items = children(node)
|
||
if index < 0 or index >= len(items):
|
||
return None
|
||
return direct_scalar(items[index])
|
||
|
||
|
||
def child_direct_scalar_from_end(node: Any, offset: int) -> tuple[str | None, int | None]:
|
||
items = children(node)
|
||
index = len(items) + offset
|
||
if offset >= 0 or index < 0 or index >= len(items):
|
||
return None, None
|
||
return direct_scalar(items[index]), index
|
||
|
||
|
||
def scalar_kind(value: str | None) -> str:
|
||
if value is None:
|
||
return "empty"
|
||
if GUID_RE.fullmatch(value):
|
||
return "guid"
|
||
if value in {"0", "1"}:
|
||
return "boolean_or_number"
|
||
if re.fullmatch(r"-?\d+", value):
|
||
return "number"
|
||
if re.fullmatch(r"-?\d+(?:\.\d+)?", value):
|
||
return "number"
|
||
return "string"
|
||
|
||
|
||
def path_join(path: str, index: int) -> str:
|
||
return f"{path}.{index}" if path else str(index)
|
||
|
||
|
||
def node_scalar_values(node: Any, *, limit: int = 40) -> list[dict[str, Any]]:
|
||
values: list[dict[str, Any]] = []
|
||
|
||
def walk(value: Any, path: str) -> None:
|
||
if len(values) >= limit:
|
||
return
|
||
text = scalar(value)
|
||
if text is not None:
|
||
values.append({"value": text, "kind": scalar_kind(text), "position": {"indices": [int(part) for part in path.split(".") if part.isdigit()]}})
|
||
return
|
||
for index, child in enumerate(children(value)):
|
||
walk(child, path_join(path, index))
|
||
|
||
walk(node, "")
|
||
return values
|
||
|
||
|
||
def node_scalar_entries(node: Any, base_path: str = "", *, limit: int = 20000) -> list[dict[str, Any]]:
|
||
result: list[dict[str, Any]] = []
|
||
|
||
def walk(value: Any, path: str) -> None:
|
||
if len(result) >= limit:
|
||
return
|
||
text = direct_scalar(value)
|
||
if text is not None:
|
||
result.append({"value": text, "path": path, "kind": scalar_kind(text)})
|
||
return
|
||
for index, child in enumerate(children(value)):
|
||
walk(child, path_join(path, index))
|
||
|
||
walk(node, base_path)
|
||
return result
|
||
|
||
|
||
def direct_parameters(
|
||
node: Any,
|
||
base_path: str,
|
||
*,
|
||
roles: dict[int, str] | None = None,
|
||
limit: int = 200,
|
||
) -> list[dict[str, Any]]:
|
||
result: list[dict[str, Any]] = []
|
||
for index, child in enumerate(children(node)[:limit]):
|
||
item_path = path_join(base_path, index)
|
||
text = scalar(child)
|
||
entry: dict[str, Any] = {
|
||
"index": index,
|
||
"presentation": (roles or {}).get(index) or f"Параметр {index}",
|
||
"position": {"indices": [int(part) for part in item_path.split(".") if part.isdigit()]},
|
||
}
|
||
if text is not None:
|
||
entry["value"] = text
|
||
entry["value_kind"] = scalar_kind(text)
|
||
else:
|
||
child_items = children(child)
|
||
evidence = collect_evidence(child)
|
||
localized = localized_text(child, item_path, max_depth=3)
|
||
entry.update(
|
||
{
|
||
"kind": "group",
|
||
"items": len(child_items),
|
||
"strings": sorted(evidence["strings"])[:20],
|
||
"guids": sorted(evidence["guids"])[:20],
|
||
"values_sample": node_scalar_values(child, limit=20),
|
||
}
|
||
)
|
||
if localized:
|
||
entry["localized_text"] = {"lang": localized.get("lang"), "text": localized.get("value")}
|
||
result.append(entry)
|
||
return result
|
||
|
||
|
||
def parameter_value(parameters: list[dict[str, Any]], index: int) -> Any:
|
||
for parameter in parameters:
|
||
if parameter.get("index") == index:
|
||
return parameter.get("value")
|
||
return None
|
||
|
||
|
||
def semantic_property(name: str, value: Any, *, index: int | None = None, source: str = "form_payload") -> dict[str, Any]:
|
||
result = {
|
||
"name": name,
|
||
"value": value,
|
||
"source": source,
|
||
"status": "ok" if value is not None and value != "" else "empty",
|
||
}
|
||
if index is not None:
|
||
result["parameter_index"] = index
|
||
return result
|
||
|
||
|
||
def item_type_name(marker: str | None, type_code: str | None) -> str | None:
|
||
marker_text = str(marker or "")
|
||
type_text = str(type_code or "")
|
||
if marker_text == "6":
|
||
return FORM_TABLE_ADDITION_TYPE_NAMES.get(type_text, type_text or None)
|
||
if marker_text in {"35", "37", "48"}:
|
||
return FORM_FIELD_SUBTYPE_NAMES.get(type_text, "Поле")
|
||
if marker_text == "22":
|
||
return FORM_ITEM_TYPE_NAMES.get(type_text, type_text or None)
|
||
return FORM_ITEM_TYPE_NAMES.get(marker_text, type_text or marker_text or None)
|
||
|
||
|
||
def form_item_public_type_name(marker: str | None, type_code: str | None, name: str | None) -> str | None:
|
||
"""Resolve form element type using payload shape plus stable XML-confirmed naming rules."""
|
||
marker_text = str(marker or "")
|
||
name_text = str(name or "")
|
||
if marker_text == "12":
|
||
if name_text.endswith(("РасширеннаяПодсказка", "ExtendedTooltip")):
|
||
return "Расширенная подсказка"
|
||
return FORM_DECORATION_TYPE_NAMES.get(str(type_code or ""), "Декорация")
|
||
return item_type_name(marker, type_code)
|
||
|
||
|
||
def bool_presentation(value: str | None) -> bool | None:
|
||
if value == "1":
|
||
return True
|
||
if value == "0":
|
||
return False
|
||
return None
|
||
|
||
|
||
def auto_bool_presentation(value: str | None) -> str | bool | None:
|
||
if value == "1":
|
||
return True
|
||
if value == "0":
|
||
return False
|
||
if value == "2":
|
||
return "Авто"
|
||
return None
|
||
|
||
|
||
def title_location_presentation(value: str | None) -> str | None:
|
||
return {
|
||
"0": "None",
|
||
"1": "Auto",
|
||
"2": "Left",
|
||
"3": "Top",
|
||
"4": "Right",
|
||
}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def horizontal_align_presentation(value: str | None) -> str | None:
|
||
return {
|
||
"0": "Left",
|
||
"1": "Center",
|
||
"2": "Right",
|
||
"3": "Auto",
|
||
}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def vertical_align_presentation(value: str | None) -> str | None:
|
||
return {
|
||
"0": "Top",
|
||
"1": "Center",
|
||
"2": "Bottom",
|
||
"3": "Auto",
|
||
}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def button_importance_presentation(value: str | None) -> str | None:
|
||
return {
|
||
"0": "Supplementary",
|
||
"1": "Main",
|
||
}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def input_edit_mode_presentation(value: str | None) -> str | None:
|
||
return {
|
||
"0": "Directly",
|
||
"1": "Auto",
|
||
"2": "EnterOnInput",
|
||
}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def tooltip_representation_presentation(value: str | None) -> str | None:
|
||
return {
|
||
"0": "Auto",
|
||
"3": "Button",
|
||
}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def choice_folders_and_items_presentation(value: str | None) -> str | None:
|
||
return {
|
||
"0": "Items",
|
||
"3": "Auto",
|
||
}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def edit_text_update_presentation(value: str | None) -> str | None:
|
||
return {
|
||
"0": "Auto",
|
||
"2": "OnValueChange",
|
||
}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def choice_button_representation_presentation(value: str | None) -> str | None:
|
||
return {
|
||
"0": "Auto",
|
||
"2": "ShowInDropListAndInInputField",
|
||
"3": "ShowInInputField",
|
||
}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def choice_history_on_input_presentation(value: str | None) -> str | None:
|
||
return {
|
||
"0": "Auto",
|
||
"1": "DontUse",
|
||
}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def shortcut_presentation(node: Any) -> str | None:
|
||
"""Decode the confirmed managed-form shortcut tuple ``{0,key,modifiers}``."""
|
||
if child_direct_scalar(node, 0) != "0":
|
||
return None
|
||
key_code = child_direct_scalar(node, 1)
|
||
modifiers = child_direct_scalar(node, 2)
|
||
if not key_code or modifiers is None:
|
||
return None
|
||
try:
|
||
code = int(key_code)
|
||
modifier_mask = int(modifiers)
|
||
except ValueError:
|
||
return None
|
||
if 112 <= code <= 123:
|
||
key_name = f"F{code - 111}"
|
||
elif 32 <= code <= 126:
|
||
key_name = chr(code).upper()
|
||
else:
|
||
return None
|
||
modifier_names = []
|
||
if modifier_mask & 8:
|
||
modifier_names.append("Ctrl")
|
||
if modifier_mask & 16:
|
||
modifier_names.append("Alt")
|
||
if modifier_mask & 4:
|
||
modifier_names.append("Shift")
|
||
return "+".join([*modifier_names, key_name])
|
||
|
||
|
||
def choice_parameter_links_presentation(node: Any) -> dict[str, Any] | None:
|
||
"""Decode the stable public part of an input field ChoiceParameterLinks node.
|
||
|
||
Marker 5007 is followed by the declared link count. Link data paths use
|
||
internal form attribute references, so only the public link names are
|
||
exposed here until those references are resolved independently.
|
||
"""
|
||
if child_direct_scalar(node, 0) != "5007":
|
||
return None
|
||
try:
|
||
count = int(child_direct_scalar(node, 1) or "0")
|
||
except ValueError:
|
||
return None
|
||
if count <= 0:
|
||
return None
|
||
names: list[str] = []
|
||
for item in children(node)[2:]:
|
||
value = direct_scalar(item)
|
||
if not value or not BSL_PATH_RE.fullmatch(value):
|
||
continue
|
||
names.append(value)
|
||
if len(names) == count:
|
||
break
|
||
if len(names) != count:
|
||
return None
|
||
return {
|
||
"count": count,
|
||
"links": [{"name": name} for name in names],
|
||
}
|
||
|
||
|
||
def choice_list_presentation(node: Any) -> dict[str, Any] | None:
|
||
"""Decode confirmed numeric ChoiceList items from input-field payloads."""
|
||
if child_direct_scalar(node, 0) != "3":
|
||
return None
|
||
try:
|
||
count = int(child_direct_scalar(node, 1) or "0")
|
||
except ValueError:
|
||
return None
|
||
if count <= 0:
|
||
return None
|
||
result: list[dict[str, Any]] = []
|
||
node_items = children(node)
|
||
for item_index in range(count):
|
||
encoded_item = node_items[3 + item_index * 2] if 3 + item_index * 2 < len(node_items) else None
|
||
if child_direct_scalar(encoded_item, 0) != "#":
|
||
return None
|
||
payload = child_at(encoded_item, 2)
|
||
encoded_value = child_at(payload, 2)
|
||
value_kind = child_direct_scalar(encoded_value, 0)
|
||
localized = localized_text(encoded_item, "", max_depth=8)
|
||
presentation = str((localized or {}).get("value") or "")
|
||
if value_kind == "N":
|
||
scalar_value = child_direct_scalar(encoded_value, 1)
|
||
if scalar_value is None:
|
||
return None
|
||
value: dict[str, Any] = {"kind": "Number", "value": scalar_value}
|
||
elif value_kind == "U":
|
||
type_guid = child_direct_scalar(payload, 3)
|
||
value_guid = child_direct_scalar(payload, 4)
|
||
if not type_guid or not value_guid or not GUID_RE.fullmatch(type_guid) or not GUID_RE.fullmatch(value_guid):
|
||
return None
|
||
value = {
|
||
"kind": "EnumValue",
|
||
"type_guid": type_guid.lower(),
|
||
"value_guid": value_guid.lower(),
|
||
"status": "identity_pending",
|
||
}
|
||
else:
|
||
return None
|
||
result.append(
|
||
{
|
||
"presentation": presentation,
|
||
"value": value,
|
||
}
|
||
)
|
||
return {"count": count, "items": result}
|
||
|
||
|
||
def type_link_reference(node: Any) -> dict[str, Any] | None:
|
||
if child_direct_scalar(node, 0) != "3" or child_direct_scalar(node, 1) != "2":
|
||
return None
|
||
field_reference = children(node)[3] if len(children(node)) > 3 else None
|
||
field_ids = [value for value in atoms(field_reference, limit=8) if re.fullmatch(r"\d+", value or "")]
|
||
if not field_ids:
|
||
return None
|
||
link_item = child_direct_scalar(node, 4)
|
||
return {
|
||
"field_id": field_ids[-1],
|
||
"link_item": int(link_item) if re.fullmatch(r"\d+", str(link_item or "")) else 0,
|
||
}
|
||
|
||
|
||
def style_value_presentation(node: Any) -> str | dict[str, Any] | None:
|
||
if child_direct_scalar(node, 0) != "3":
|
||
return None
|
||
variant = child_direct_scalar(node, 1)
|
||
payload = children(node)[2] if len(children(node)) > 2 else None
|
||
code = child_direct_scalar(payload, 0)
|
||
if variant == "4" and code == "0":
|
||
return "Авто"
|
||
if variant == "1" and code == "18":
|
||
return "win:ButtonText"
|
||
if variant == "2" and code == "27":
|
||
return "web:DarkGreen"
|
||
if variant != "3":
|
||
return None
|
||
standard = {
|
||
"-21": "style:ButtonTextColor",
|
||
"-22": "style:BorderColor",
|
||
"-23": "style:ToolTipBackColor",
|
||
"-35": "style:TableHeaderBackColor",
|
||
"-1": "style:FormBackColor",
|
||
}.get(str(code or ""))
|
||
if standard:
|
||
return standard
|
||
guid = child_direct_scalar(payload, 1)
|
||
if str(guid or "").lower() == "ad87bd29-0ad1-4da4-ac62-38e714e0cb9f":
|
||
return "style:ПоясняющийТекст"
|
||
if guid and GUID_RE.fullmatch(guid):
|
||
return {
|
||
"kind": "StyleItem",
|
||
"guid": guid.lower(),
|
||
"status": "identity_pending",
|
||
}
|
||
return None
|
||
|
||
|
||
def font_value_presentation(node: Any) -> str | dict[str, Any] | None:
|
||
if child_direct_scalar(node, 0) != "7":
|
||
return None
|
||
variant = child_direct_scalar(node, 1)
|
||
if variant == "3":
|
||
return "Авто"
|
||
if variant != "2":
|
||
return None
|
||
reference = children(node)[3] if len(children(node)) > 3 else None
|
||
code = child_direct_scalar(reference, 0)
|
||
standard = {
|
||
"-31": "style:NormalTextFont",
|
||
"-32": "style:LargeTextFont",
|
||
}.get(str(code or ""))
|
||
if standard:
|
||
return standard
|
||
guid = child_direct_scalar(reference, 1)
|
||
if code == "0" and guid and GUID_RE.fullmatch(guid):
|
||
return {
|
||
"kind": "StyleItem",
|
||
"guid": guid.lower(),
|
||
"status": "identity_pending",
|
||
}
|
||
return None
|
||
|
||
|
||
def auto_enum_presentation(value: str | None) -> str | None:
|
||
return {"3": "Авто"}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def button_representation_presentation(value: str | None) -> str | None:
|
||
return {
|
||
"0": "Text",
|
||
"1": "Picture",
|
||
"2": "PictureAndText",
|
||
"3": "Авто",
|
||
}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def command_bar_location_presentation(value: str | None) -> str | None:
|
||
return {
|
||
"0": "Авто",
|
||
"1": "В командной панели",
|
||
"2": "В дополнительном подменю",
|
||
}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def add_grouped_property(groups: dict[str, list[dict[str, Any]]], group: str, prop: dict[str, Any]) -> None:
|
||
groups.setdefault(group, []).append(prop)
|
||
|
||
|
||
def mark_semantic_parameter_mapped(semantic: dict[str, Any], index: int) -> None:
|
||
unmapped = semantic.get("unmapped_parameters")
|
||
coverage = semantic.get("coverage")
|
||
if not isinstance(unmapped, list) or not isinstance(coverage, dict):
|
||
return
|
||
before = len(unmapped)
|
||
semantic["unmapped_parameters"] = [item for item in unmapped if item.get("index") != index]
|
||
if len(semantic["unmapped_parameters"]) == before:
|
||
return
|
||
coverage["mapped"] = int(coverage.get("mapped") or 0) + (before - len(semantic["unmapped_parameters"]))
|
||
coverage["unmapped"] = max(0, int(coverage.get("unmapped") or 0) - (before - len(semantic["unmapped_parameters"])))
|
||
coverage["status"] = "partial" if coverage["unmapped"] else "ok"
|
||
|
||
|
||
def semantic_coverage(parameters: list[dict[str, Any]], mapped_indexes: set[int]) -> dict[str, Any]:
|
||
available = [item for item in parameters if item.get("index") is not None]
|
||
unmapped = [item for item in available if item.get("index") not in mapped_indexes]
|
||
return {
|
||
"mapped": len(available) - len(unmapped),
|
||
"unmapped": len(unmapped),
|
||
"total": len(available),
|
||
"status": "partial" if unmapped else "ok",
|
||
}
|
||
|
||
|
||
def semantic_unmapped_parameters(parameters: list[dict[str, Any]], mapped_indexes: set[int]) -> list[dict[str, Any]]:
|
||
result = []
|
||
for parameter in parameters:
|
||
if parameter.get("index") in mapped_indexes:
|
||
continue
|
||
public = {
|
||
"index": parameter.get("index"),
|
||
"presentation": parameter.get("presentation"),
|
||
"value": parameter.get("value"),
|
||
"value_kind": parameter.get("value_kind"),
|
||
"kind": parameter.get("kind"),
|
||
"items": parameter.get("items"),
|
||
"strings": parameter.get("strings"),
|
||
"guids": parameter.get("guids"),
|
||
"localized_text": parameter.get("localized_text"),
|
||
}
|
||
result.append({key: value for key, value in public.items() if value is not None and value != [] and value != {}})
|
||
return result
|
||
|
||
|
||
def public_semantic(semantic: dict[str, Any], *, include_diagnostics: bool) -> dict[str, Any]:
|
||
if include_diagnostics:
|
||
return semantic
|
||
groups: dict[str, list[dict[str, Any]]] = {}
|
||
for group, properties in (semantic.get("groups") or {}).items():
|
||
groups[group] = [
|
||
{key: prop.get(key) for key in ("name", "value", "status") if key in prop}
|
||
for prop in properties
|
||
if isinstance(prop, dict)
|
||
]
|
||
return {"groups": groups}
|
||
|
||
|
||
def public_rows_semantics(rows: list[dict[str, Any]], *, include_diagnostics: bool) -> None:
|
||
if include_diagnostics:
|
||
return
|
||
for row in rows:
|
||
semantic = row.get("semantic")
|
||
if isinstance(semantic, dict):
|
||
row["semantic"] = public_semantic(semantic, include_diagnostics=False)
|
||
|
||
|
||
def add_derived_semantic_property(row: dict[str, Any], group: str, name: str, value: Any, *, source: str) -> None:
|
||
if value is None or value == "":
|
||
return
|
||
semantic = row.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
for prop in groups.get(group, []):
|
||
if isinstance(prop, dict) and prop.get("name") == name:
|
||
return
|
||
add_grouped_property(groups, group, semantic_property(name, value, source=source))
|
||
|
||
|
||
def form_item_semantic_properties(row: dict[str, Any], parameters: list[dict[str, Any]]) -> dict[str, Any]:
|
||
marker = str(row.get("marker") or "")
|
||
type_code = str(row.get("type_code") or "")
|
||
role_map = dict(FORM_ITEM_SEMANTIC_PROPERTIES.get(marker, {}))
|
||
if marker == "34":
|
||
name_path = str(row.get("name_path") or "")
|
||
if name_path.endswith(".5"):
|
||
role_map.pop(6, None)
|
||
role_map[4] = ("Основные", "Вид")
|
||
role_map[5] = ("Основные", "Имя")
|
||
elif marker == "48":
|
||
name_path = str(row.get("name_path") or "")
|
||
if name_path.endswith(".6"):
|
||
role_map.pop(7, None)
|
||
role_map[5] = ("Основные", "Вид")
|
||
role_map[6] = ("Основные", "Имя")
|
||
elif marker in {"35", "37"} and str(row.get("name_path") or "").endswith(".7"):
|
||
role_map = {
|
||
(index + 1 if index >= 5 else index): value
|
||
for index, value in role_map.items()
|
||
}
|
||
elif marker == "73":
|
||
name_path = str(row.get("name_path") or "")
|
||
if name_path.endswith(".5"):
|
||
role_map.pop(6, None)
|
||
role_map[5] = ("Основные", "Имя")
|
||
groups: dict[str, list[dict[str, Any]]] = {}
|
||
mapped: set[int] = set()
|
||
for index, (group, name) in role_map.items():
|
||
mapped.add(index)
|
||
value = parameter_value(parameters, index)
|
||
if name == "Вид":
|
||
value = item_type_name(marker, str(value) if value is not None else type_code)
|
||
add_grouped_property(groups, group, semantic_property(name, value, index=index))
|
||
if marker == "31":
|
||
add_grouped_property(groups, "Основные", semantic_property("Вид", "Кнопка командной панели", source="marker"))
|
||
elif type_code and (6 if marker in {"35", "37"} and str(row.get("name_path") or "").endswith(".7") else 5) not in mapped:
|
||
type_index = 6 if marker in {"35", "37"} and str(row.get("name_path") or "").endswith(".7") else 5
|
||
add_grouped_property(groups, "Основные", semantic_property("Вид", row.get("type_name") or item_type_name(marker, type_code), index=type_index))
|
||
mapped.add(type_index)
|
||
return {
|
||
"groups": groups,
|
||
"unmapped_parameters": semantic_unmapped_parameters(parameters, mapped),
|
||
"coverage": semantic_coverage(parameters, mapped),
|
||
}
|
||
|
||
|
||
def enrich_item_reference_semantics(records: list[dict[str, Any]]) -> None:
|
||
by_name = {str(row.get("name") or ""): row for row in records if row.get("name")}
|
||
by_id = {str(row.get("id") or ""): row for row in records if row.get("id") not in {None, ""}}
|
||
for row in records:
|
||
owner = str(row.get("name") or "")
|
||
owner_path = str(row.get("path") or "")
|
||
if not owner or not owner_path:
|
||
continue
|
||
descendants = [
|
||
candidate
|
||
for candidate in records
|
||
if str(candidate.get("path") or "").startswith(owner_path + ".") and candidate.get("name") != owner
|
||
]
|
||
for suffix, prop_name in (
|
||
("КонтекстноеМеню", "КонтекстноеМеню"),
|
||
("РасширеннаяПодсказка", "РасширеннаяПодсказка"),
|
||
("ExtendedTooltip", "РасширеннаяПодсказка"),
|
||
("КоманднаяПанель", "AutoCommandBar"),
|
||
):
|
||
child = by_name.get(owner + suffix)
|
||
if child is not None and child in descendants:
|
||
add_derived_semantic_property(row, "Прочее", prop_name, child.get("name"), source="item_reference:name_path")
|
||
for child in descendants:
|
||
child_type = str(child.get("type_name") or "")
|
||
if child_type in {"SearchStringAddition", "ViewStatusAddition", "SearchControlAddition"} and str(child.get("name") or "").startswith(owner):
|
||
add_derived_semantic_property(row, "Прочее", child_type, child.get("name"), source="item_reference:child_type")
|
||
user_settings_group_ids = [
|
||
*([str(row.pop("_user_settings_group_id"))] if row.get("_user_settings_group_id") not in {None, ""} else []),
|
||
*[str(value) for value in row.pop("_user_settings_group_ids", [])],
|
||
]
|
||
referenced_groups = [
|
||
by_id[value]
|
||
for value in user_settings_group_ids
|
||
if value in by_id
|
||
and (
|
||
str(by_id[value].get("type_name") or "") in {"Группа", "Группа колонок", "Группа кнопок"}
|
||
or "настрой" in str(by_id[value].get("name") or "").casefold()
|
||
)
|
||
]
|
||
named_settings_groups = [
|
||
candidate
|
||
for candidate in records
|
||
if "настро" in str(candidate.get("name") or "").casefold()
|
||
and "пользователь" in str(candidate.get("name") or "").casefold()
|
||
and str(candidate.get("type_name") or "") == "Группа"
|
||
]
|
||
user_settings_group = referenced_groups[-1] if referenced_groups else (
|
||
named_settings_groups[0] if str(row.get("marker") or "") == "55" and len(named_settings_groups) == 1 else None
|
||
)
|
||
if user_settings_group is not None:
|
||
add_derived_semantic_property(
|
||
row,
|
||
"Использование",
|
||
"UserSettingsGroup",
|
||
user_settings_group.get("name"),
|
||
source="item_reference:id",
|
||
)
|
||
|
||
addition_representations = {
|
||
"SearchStringAddition": "SearchStringRepresentation",
|
||
"ViewStatusAddition": "ViewStatusRepresentation",
|
||
"SearchControlAddition": "SearchControl",
|
||
}
|
||
table_rows = [row for row in records if str(row.get("marker") or "") in {"55", "73"}]
|
||
for row in records:
|
||
representation = addition_representations.get(str(row.get("type_name") or ""))
|
||
row_path = str(row.get("path") or "")
|
||
if not representation or not row_path:
|
||
continue
|
||
owners = [
|
||
candidate
|
||
for candidate in table_rows
|
||
if str(candidate.get("path") or "") and row_path.startswith(str(candidate.get("path")) + ".")
|
||
]
|
||
if not owners:
|
||
continue
|
||
owner = max(owners, key=lambda candidate: len(str(candidate.get("path") or "").split(".")))
|
||
source = {
|
||
"owner_element": owner.get("name"),
|
||
"representation": representation,
|
||
}
|
||
row["addition_source"] = source
|
||
add_derived_semantic_property(row, "Основные", "AdditionSource", source, source="item_hierarchy:table_addition")
|
||
|
||
|
||
def enrich_input_field_specific_semantics(row: dict[str, Any], node: Any) -> None:
|
||
marker = str(row.get("marker") or "")
|
||
if marker not in {"35", "37"}:
|
||
return
|
||
semantic = row.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
legacy_offset = 1 if str(row.get("name_path") or "").endswith(".7") else 0
|
||
|
||
def actual_index(index: int) -> int:
|
||
return index + legacy_offset if index >= 5 else index
|
||
|
||
def field_scalar(index: int) -> str | None:
|
||
return child_direct_scalar(node, actual_index(index))
|
||
|
||
title_location = title_location_presentation(field_scalar(7))
|
||
if title_location is not None:
|
||
add_grouped_property(groups, "Основные", semantic_property("ПоложениеЗаголовка", title_location, index=actual_index(7), source="form_payload_input_field"))
|
||
mark_semantic_parameter_mapped(semantic, actual_index(7))
|
||
visible = bool_presentation(field_scalar(43))
|
||
if visible is not None:
|
||
add_grouped_property(groups, "Основные", semantic_property("Видимость", visible, index=actual_index(43), source="form_payload_input_field"))
|
||
mark_semantic_parameter_mapped(semantic, actual_index(43))
|
||
enabled = bool_presentation(field_scalar(13))
|
||
if enabled is not None:
|
||
add_grouped_property(groups, "Основные", semantic_property("Доступность", enabled, index=actual_index(13), source="form_payload_input_field"))
|
||
mark_semantic_parameter_mapped(semantic, actual_index(13))
|
||
read_only = bool_presentation(field_scalar(14))
|
||
if read_only is not None:
|
||
add_grouped_property(groups, "Основные", semantic_property("ТолькоПросмотр", read_only, index=actual_index(14), source="form_payload_input_field"))
|
||
mark_semantic_parameter_mapped(semantic, actual_index(14))
|
||
skip_on_input = {"1": True, "2": False}.get(str(field_scalar(15) or ""))
|
||
if skip_on_input is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Использование",
|
||
semantic_property("ПропускатьПриВводе", skip_on_input, index=actual_index(15), source="form_payload_input_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, actual_index(15))
|
||
default_item = bool_presentation(field_scalar(16))
|
||
if default_item is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Основные",
|
||
semantic_property(
|
||
"АктивизироватьПоУмолчанию",
|
||
default_item,
|
||
index=actual_index(16),
|
||
source="form_payload_input_field",
|
||
),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, actual_index(16))
|
||
show_in_header = bool_presentation(field_scalar(20))
|
||
if show_in_header is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("ShowInHeader", show_in_header, index=actual_index(20), source="form_payload_input_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, actual_index(20))
|
||
show_in_footer = bool_presentation(field_scalar(21))
|
||
if show_in_footer is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("ShowInFooter", show_in_footer, index=actual_index(21), source="form_payload_input_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, actual_index(21))
|
||
fixing_in_table = "Left" if field_scalar(49) == "1" else None
|
||
if fixing_in_table is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("FixingInTable", fixing_in_table, index=actual_index(49), source="form_payload_input_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, actual_index(49))
|
||
auto_cell_height = bool_presentation(field_scalar(28))
|
||
if auto_cell_height is True:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("AutoCellHeight", True, index=actual_index(28), source="form_payload_input_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, actual_index(28))
|
||
cell_hyperlink = bool_presentation(field_scalar(22))
|
||
if cell_hyperlink is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Использование",
|
||
semantic_property("CellHyperlink", cell_hyperlink, index=actual_index(22), source="form_payload_input_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, actual_index(22))
|
||
direct_properties = [
|
||
("Расположение", "TitleHeight", field_scalar(8), actual_index(8)),
|
||
("Расположение", "HorizontalAlign", horizontal_align_presentation(field_scalar(23)), actual_index(23)),
|
||
("Использование", "РежимРедактирования", input_edit_mode_presentation(field_scalar(26)), actual_index(26)),
|
||
("Использование", "AutoEditMode", True if field_scalar(26) == "2" else None, actual_index(26)),
|
||
("Расположение", "GroupHorizontalAlign", horizontal_align_presentation(field_scalar(53)), actual_index(53)),
|
||
("Расположение", "GroupVerticalAlign", vertical_align_presentation(field_scalar(54)), actual_index(54)),
|
||
("Оформление", "ToolTipRepresentation", tooltip_representation_presentation(field_scalar(50)), actual_index(50)),
|
||
]
|
||
for group, name, value, index in direct_properties:
|
||
if value is not None:
|
||
add_grouped_property(groups, group, semantic_property(name, value, index=index, source="form_payload_input_field"))
|
||
mark_semantic_parameter_mapped(semantic, index)
|
||
|
||
specific_index = actual_index(39)
|
||
specific = children(node)[specific_index] if len(children(node)) > specific_index else None
|
||
if specific is None:
|
||
return
|
||
choice_list = choice_list_presentation(children(specific)[1] if len(children(specific)) > 1 else None)
|
||
if choice_list is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Использование",
|
||
semantic_property("ChoiceList", choice_list, index=1, source="form_payload_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 1)
|
||
specific_marker = child_direct_scalar(specific, 0)
|
||
if marker == "37" and str(row.get("type_code") or "") == "1" and specific_marker == "11":
|
||
hyperlink = bool_presentation(child_direct_scalar(specific, 7))
|
||
if hyperlink is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Использование",
|
||
semantic_property("Hiperlink", hyperlink, index=7, source="form_payload_label_field"),
|
||
)
|
||
label_properties = [
|
||
("Ширина", child_direct_scalar(specific, 1)),
|
||
("Высота", child_direct_scalar(specific, 2)),
|
||
("ВертикальноеПоложениеВГруппе", {"2": "Top"}.get(str(child_direct_scalar(specific, 3) or ""))),
|
||
("РастягиватьПоВертикали", bool_presentation(child_direct_scalar(specific, 4))),
|
||
("РастягиватьПоГоризонтали", bool_presentation(child_direct_scalar(specific, 17))),
|
||
]
|
||
for name, value in label_properties:
|
||
if value not in {None, "", "0"} or isinstance(value, bool):
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property(name, value, index=39, source="form_payload_label_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 39)
|
||
text_color = style_value_presentation(child_at(specific, 8))
|
||
if text_color is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Оформление",
|
||
semantic_property("TextColor", text_color, index=39, source="form_payload_label_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 39)
|
||
expected_specific_marker = "32" if marker == "35" else "36"
|
||
if marker == "35" and specific_marker == "11":
|
||
auto_max_width = bool_presentation(child_direct_scalar(specific, 15))
|
||
max_width = child_direct_scalar(specific, 16)
|
||
if auto_max_width is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("АвтоМаксимальнаяШирина", auto_max_width, index=39, source="form_payload_label_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 39)
|
||
if max_width not in {None, ""}:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("МаксимальнаяШирина", max_width, index=39, source="form_payload_label_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 39)
|
||
return
|
||
if specific_marker != expected_specific_marker:
|
||
if marker == "37" and str(row.get("type_code") or "") == "11" and specific_marker == "1":
|
||
chart_height = child_direct_scalar(specific, 2)
|
||
if chart_height not in {None, "", "0"}:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("Высота", chart_height, index=39, source="form_payload_chart_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 39)
|
||
if marker == "37" and str(row.get("type_code") or "") == "14" and specific_marker == "3":
|
||
graphical_properties = [
|
||
("Расположение", "Ширина", child_direct_scalar(specific, 1)),
|
||
("Расположение", "Высота", child_direct_scalar(specific, 2)),
|
||
("Использование", "Edit", bool_presentation(child_direct_scalar(specific, 3))),
|
||
]
|
||
for group, name, value in graphical_properties:
|
||
if value not in {None, ""}:
|
||
add_grouped_property(
|
||
groups,
|
||
group,
|
||
semantic_property(name, value, index=39, source="form_payload_graphical_schema_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 39)
|
||
if marker == "37" and str(row.get("type_code") or "") == "4" and specific_marker == "10":
|
||
picture_properties = [
|
||
("Расположение", "Ширина", child_direct_scalar(specific, 1)),
|
||
("Расположение", "РастягиватьПоГоризонтали", bool_presentation(child_direct_scalar(specific, 2))),
|
||
("Использование", "РежимПеретаскиванияФайлов", {"1": "AsFile"}.get(str(child_direct_scalar(specific, 17) or ""))),
|
||
]
|
||
for group, name, value in picture_properties:
|
||
if value not in {None, ""}:
|
||
add_grouped_property(
|
||
groups,
|
||
group,
|
||
semantic_property(name, value, index=39, source="form_payload_picture_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 39)
|
||
return
|
||
open_button = auto_bool_presentation(child_direct_scalar(specific, 15))
|
||
quick_choice = auto_bool_presentation(child_direct_scalar(specific, 23))
|
||
choose_type = bool_presentation(child_direct_scalar(specific, 32))
|
||
min_value = child_direct_scalar(child_at(specific, 16), 1) if child_direct_scalar(child_at(specific, 16), 0) == "N" else None
|
||
max_value = child_direct_scalar(child_at(specific, 17), 1) if child_direct_scalar(child_at(specific, 17), 0) == "N" else None
|
||
properties = [
|
||
("Расположение", "Width", child_direct_scalar(specific, 2), 2),
|
||
("Расположение", "Height", child_direct_scalar(specific, 3), 3),
|
||
("Расположение", "HorizontalStretch", auto_bool_presentation(child_direct_scalar(specific, 4)), 4),
|
||
("Расположение", "VerticalStretch", auto_bool_presentation(child_direct_scalar(specific, 5)), 5),
|
||
("Расположение", "Wrap", bool_presentation(child_direct_scalar(specific, 6)), 6),
|
||
("Использование", "PasswordMode", {"1": True, "2": False}.get(str(child_direct_scalar(specific, 7) or "")), 7),
|
||
("Использование", "MultiLine", multiline_presentation(child_direct_scalar(specific, 8)), 8),
|
||
("Использование", "ChoiceListButton", bool_presentation(child_direct_scalar(specific, 11)), 11),
|
||
("Использование", "КнопкаВыпадающегоСписка", auto_bool_presentation(child_direct_scalar(specific, 47)), 47),
|
||
("Использование", "КнопкаВыбора", bool_presentation(child_direct_scalar(specific, 12)), 12),
|
||
("Использование", "SpinButton", auto_bool_presentation(child_direct_scalar(specific, 14)), 14),
|
||
("Использование", "OpenButton", open_button, 15),
|
||
("Использование", "КнопкаОчистки", auto_bool_presentation(child_direct_scalar(specific, 13)), 13),
|
||
("Использование", "CreateButton", bool_presentation(child_direct_scalar(specific, 45)), 45),
|
||
("Использование", "ListChoiceMode", bool_presentation(child_direct_scalar(specific, 19)), 19),
|
||
("Расположение", "ChoiceListHeight", child_direct_scalar(specific, 21), 21),
|
||
("Использование", "ChoiceFoldersAndItems", choice_folders_and_items_presentation(child_direct_scalar(specific, 24)), 24),
|
||
("Использование", "БыстрыйВыбор", quick_choice, 23),
|
||
("Использование", "AutoChoiceIncomplete", auto_bool_presentation(child_direct_scalar(specific, 28)), 28),
|
||
("Использование", "MarkRequiredComplete", auto_bool_presentation(child_direct_scalar(specific, 31)), 31),
|
||
("Использование", "AutoMarkIncomplete", auto_bool_presentation(child_direct_scalar(specific, 31)), 31),
|
||
("Использование", "ВыбиратьТип", choose_type, 32),
|
||
("Использование", "TypeDomainEnabled", choose_type, 32),
|
||
("Использование", "ExtendedEdit", bool_presentation(child_direct_scalar(specific, 52)), 52),
|
||
("Использование", "MinValue", min_value, 16),
|
||
("Использование", "MaxValue", max_value, 17),
|
||
("Использование", "РедактированиеТекста", bool_presentation(child_direct_scalar(specific, 41)), 41),
|
||
("Использование", "EditTextUpdate", edit_text_update_presentation(child_direct_scalar(specific, 43)), 43),
|
||
("Использование", "ChoiceButtonRepresentation", choice_button_representation_presentation(child_direct_scalar(specific, 46)), 46),
|
||
("Использование", "ChoiceHistoryOnInput", choice_history_on_input_presentation(child_direct_scalar(specific, 48)), 48),
|
||
("Использование", "ExtendedEditMultipleValues", bool_presentation(child_direct_scalar(specific, 65)), 65),
|
||
("Расположение", "AutoMaxHeight", bool_presentation(child_direct_scalar(specific, 52)), 52),
|
||
("Расположение", "MaxHeight", child_direct_scalar(specific, 53), 53),
|
||
("Расположение", "АвтоМаксимальнаяШирина", bool_presentation(child_direct_scalar(specific, 49)), 49),
|
||
("Расположение", "МаксимальнаяШирина", child_direct_scalar(specific, 50), 50),
|
||
("Расположение", "FooterHorizontalAlign", {"2": "Left"}.get(str(child_direct_scalar(specific, 45) or "")), 45),
|
||
]
|
||
for group, name, value, index in properties:
|
||
if value is not None:
|
||
add_grouped_property(groups, group, semantic_property(name, value, index=index, source="form_payload_input_field"))
|
||
mark_semantic_parameter_mapped(semantic, index)
|
||
mask = child_direct_scalar(specific, 18)
|
||
if mask:
|
||
add_grouped_property(
|
||
groups,
|
||
"Использование",
|
||
semantic_property("Mask", mask, index=18, source="form_payload_input_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 18)
|
||
for name, index in (("Format", 29), ("EditFormat", 30), ("InputHint", 44)):
|
||
value_node = children(specific)[index] if len(children(specific)) > index else None
|
||
localized = localized_text(value_node, "", max_depth=3) if value_node is not None else None
|
||
value = str((localized or {}).get("value") or "")
|
||
if value:
|
||
group = "Форматирование" if name in {"Format", "EditFormat"} else "Оформление"
|
||
add_grouped_property(
|
||
groups,
|
||
group,
|
||
semantic_property(name, value, index=index, source="form_payload_input_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, index)
|
||
for name, index in (("TextColor", 37), ("BackColor", 38), ("BorderColor", 39)):
|
||
value = style_value_presentation(children(specific)[index] if len(children(specific)) > index else None)
|
||
if value is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Оформление",
|
||
semantic_property(name, value, index=index, source="form_payload_input_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, index)
|
||
font = font_value_presentation(children(specific)[40] if len(children(specific)) > 40 else None)
|
||
if font is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Оформление",
|
||
semantic_property("Font", font, index=40, source="form_payload_input_field"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 40)
|
||
type_link = type_link_reference(children(specific)[42] if len(children(specific)) > 42 else None)
|
||
if type_link is not None:
|
||
row["_type_link_reference"] = type_link
|
||
choice_parameter_links = choice_parameter_links_presentation(
|
||
children(specific)[64] if len(children(specific)) > 64 else None
|
||
)
|
||
if choice_parameter_links is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Использование",
|
||
semantic_property(
|
||
"ChoiceParameterLinks",
|
||
choice_parameter_links,
|
||
index=64,
|
||
source="form_payload_input_field",
|
||
),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 64)
|
||
|
||
|
||
def enrich_container_specific_semantics(row: dict[str, Any], node: Any) -> None:
|
||
if str(row.get("marker") or "") != "22":
|
||
return
|
||
semantic = row.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
visible = bool_presentation(child_direct_scalar(node, 10))
|
||
visible_index = 10
|
||
if row.get("type_name") == "Группа":
|
||
width = child_direct_scalar(node, 12)
|
||
if width not in {None, "", "0"}:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("Ширина", width, index=12, source="form_payload_group_layout"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 12)
|
||
for index in (26, 28):
|
||
if child_direct_scalar(node, index) == "0":
|
||
visible = False
|
||
visible_index = index
|
||
break
|
||
if visible is not None:
|
||
add_grouped_property(groups, "Основные", semantic_property("Видимость", visible, index=visible_index, source="form_payload_container"))
|
||
mark_semantic_parameter_mapped(semantic, visible_index)
|
||
if row.get("type_name") == "Командная панель":
|
||
autofill_index = 28 if len(children(node)) == 29 else len(children(node)) - 1
|
||
autofill = bool_presentation(child_direct_scalar(node, autofill_index))
|
||
if autofill is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Использование",
|
||
semantic_property("Автозаполнение", autofill, index=autofill_index, source="form_payload_command_bar"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, autofill_index)
|
||
command_source = {
|
||
"a9f3b1ac-f51b-431e-b102-55a69acdecad": "Form",
|
||
}.get(str(child_direct_scalar(node, 22) or "").lower())
|
||
if command_source is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Использование",
|
||
semantic_property("CommandSource", command_source, index=22, source="form_payload_command_bar"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 22)
|
||
if row.get("type_name") == "Группа кнопок":
|
||
button_group_options = child_at(node, 20)
|
||
command_source_guid = str(child_direct_scalar(child_at(button_group_options, 1), 1) or "").lower()
|
||
command_source = {
|
||
"02023637-7868-4a5f-8576-835a76e0c9ba": "Form",
|
||
"2ef6d6fa-847a-485e-8684-d37a3ab5efb8": "FormCommandPanelGlobalCommands",
|
||
}.get(command_source_guid)
|
||
if child_direct_scalar(button_group_options, 0) == "2" and command_source is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Использование",
|
||
semantic_property("CommandSource", command_source, index=20, source="form_payload_button_group"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 20)
|
||
container_representation = {
|
||
"Группа кнопок": "Compact",
|
||
"Подменю": "Picture",
|
||
}.get(str(row.get("type_name") or ""))
|
||
if container_representation:
|
||
add_grouped_property(
|
||
groups,
|
||
"Оформление",
|
||
semantic_property("Отображение", container_representation, source="form_payload_container_type"),
|
||
)
|
||
if row.get("type_name") == "Страница":
|
||
page_options = child_at(node, 20)
|
||
page_grouping = {
|
||
"1": "Horizontal",
|
||
"2": "HorizontalIfPossible",
|
||
}.get(str(child_direct_scalar(page_options, 16) or ""), "Vertical")
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("Группировка", page_grouping, index=20, source="form_payload_page_layout"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 20)
|
||
add_grouped_property(groups, "Использование", semantic_property("ScrollOnCompress", False, source="form_payload_container_type"))
|
||
if child_direct_scalar(page_options, 6) == "0":
|
||
add_grouped_property(
|
||
groups,
|
||
"Оформление",
|
||
semantic_property("ПоказыватьЗаголовок", False, index=20, source="form_payload_page_layout"),
|
||
)
|
||
for name, option_index in (("HorizontalSpacing", 10), ("VerticalSpacing", 11)):
|
||
spacing = {"4": "OneAndHalf"}.get(str(child_direct_scalar(page_options, option_index) or ""))
|
||
if spacing is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property(name, spacing, index=20, source="form_payload_page_layout"),
|
||
)
|
||
if row.get("type_name") == "Страницы":
|
||
pages_options = children(node)[20] if len(children(node)) > 20 else None
|
||
primary_representation = child_direct_scalar(pages_options, 1)
|
||
repeated_representation = child_direct_scalar(pages_options, 5)
|
||
compact_pages_layout = child_direct_scalar(pages_options, 0) == "3" and len(children(pages_options)) == 5
|
||
if primary_representation == repeated_representation or compact_pages_layout:
|
||
pages_representation = {
|
||
"0": "None",
|
||
"1": "TabsOnTop",
|
||
}.get(str(primary_representation or ""))
|
||
if pages_representation is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Оформление",
|
||
semantic_property(
|
||
"PagesRepresentation",
|
||
pages_representation,
|
||
index=20,
|
||
source="form_payload_pages_layout",
|
||
),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 20)
|
||
if row.get("type_name") in {"Страница", "Страницы"}:
|
||
horizontal_stretch = bool_presentation(child_direct_scalar(node, 14))
|
||
if horizontal_stretch is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("РастягиватьПоГоризонтали", horizontal_stretch, index=14, source="form_payload_container_layout"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 14)
|
||
if row.get("type_name") == "Группа колонок":
|
||
column_options = child_at(node, 20)
|
||
column_grouping = {"0": "Horizontal", "2": "InCell"}.get(str(child_direct_scalar(column_options, 1) or ""))
|
||
if column_grouping is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("Группировка", column_grouping, index=20, source="form_payload_column_group"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 20)
|
||
if child_direct_scalar(node, 14) == "1":
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("РастягиватьПоГоризонтали", True, index=14, source="form_payload_column_group"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 14)
|
||
show_in_header = bool_presentation(child_direct_scalar(node, 19))
|
||
if show_in_header is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("ShowInHeader", show_in_header, index=19, source="form_payload_column_group"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 19)
|
||
if child_direct_scalar(column_options, 11) == "1":
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("FixingInTable", "Left", index=20, source="form_payload_column_group"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 20)
|
||
node_items = children(node)
|
||
horizontal_align_index = len(node_items) - 3 if len(node_items) >= 29 else None
|
||
vertical_align_index = len(node_items) - 2 if len(node_items) >= 29 else None
|
||
horizontal_align = (
|
||
horizontal_align_presentation(child_direct_scalar(node, horizontal_align_index))
|
||
if horizontal_align_index is not None
|
||
else None
|
||
)
|
||
vertical_align = (
|
||
vertical_align_presentation(child_direct_scalar(node, vertical_align_index))
|
||
if vertical_align_index is not None
|
||
else None
|
||
)
|
||
if horizontal_align is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property(
|
||
"GroupHorizontalAlign",
|
||
horizontal_align,
|
||
index=horizontal_align_index,
|
||
source="form_payload_container_layout_tail",
|
||
),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, horizontal_align_index)
|
||
if vertical_align is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property(
|
||
"GroupVerticalAlign",
|
||
vertical_align,
|
||
index=vertical_align_index,
|
||
source="form_payload_container_layout_tail",
|
||
),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, vertical_align_index)
|
||
if row.get("type_name") == "Группа":
|
||
horizontal_stretch = bool_presentation(child_direct_scalar(node, 14))
|
||
vertical_stretch = bool_presentation(child_direct_scalar(node, 15))
|
||
if horizontal_stretch is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("РастягиватьПоГоризонтали", horizontal_stretch, index=14, source="form_payload_group_layout"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 14)
|
||
if vertical_stretch is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("РастягиватьПоВертикали", vertical_stretch, index=15, source="form_payload_group_layout"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 15)
|
||
managed_group_options = child_at(node, 20)
|
||
managed_group_layout = child_direct_scalar(managed_group_options, 0) == "29"
|
||
legacy_group_mode = child_direct_scalar(node, 22)
|
||
compact_vertical_weak = len(node_items) == 30 and child_direct_scalar(node, 21) == "0" and child_direct_scalar(node, 24) == "1"
|
||
if managed_group_layout:
|
||
primary_group = child_direct_scalar(managed_group_options, 1)
|
||
grouping_variant = child_direct_scalar(managed_group_options, 4)
|
||
extended_grouping_variant = child_direct_scalar(managed_group_options, 22)
|
||
representation_group = child_direct_scalar(managed_group_options, 3)
|
||
layout_group = None
|
||
grouping_index = 20
|
||
representation_index = 20
|
||
grouping = (
|
||
"Vertical"
|
||
if primary_group == "0"
|
||
else ("HorizontalIfPossible" if grouping_variant == "1" or extended_grouping_variant == "2" else "AlwaysHorizontal")
|
||
)
|
||
if child_direct_scalar(managed_group_options, 27) == "1":
|
||
grouping = "Horizontal"
|
||
representation = {"0": "None", "2": "WeakSeparation", "3": "NormalSeparation"}.get(str(representation_group or ""), "None")
|
||
group_mode = grouping_variant
|
||
united = bool_presentation(child_direct_scalar(managed_group_options, 21))
|
||
if united is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Прочее",
|
||
semantic_property("United", united, index=20, source="form_payload_managed_group_layout"),
|
||
)
|
||
back_color = style_value_presentation(children(managed_group_options)[9] if len(children(managed_group_options)) > 9 else None)
|
||
if back_color is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Оформление",
|
||
semantic_property("ЦветФона", back_color, index=20, source="form_payload_managed_group_layout"),
|
||
)
|
||
child_items_width = {"1": "Equal", "5": "LeftNarrowest"}.get(str(child_direct_scalar(managed_group_options, 2) or ""))
|
||
if child_items_width is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("ChildItemsWidth", child_items_width, index=20, source="form_payload_managed_group_layout"),
|
||
)
|
||
vertical_spacing = {"2": "Half"}.get(str(child_direct_scalar(managed_group_options, 16) or ""))
|
||
if vertical_spacing is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("VerticalSpacing", vertical_spacing, index=20, source="form_payload_managed_group_layout"),
|
||
)
|
||
horizontal_align = {"3": "Center"}.get(str(child_direct_scalar(managed_group_options, 27) or ""))
|
||
if horizontal_align is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("ГоризонтальноеПоложениеВГруппе", horizontal_align, index=20, source="form_payload_managed_group_layout"),
|
||
)
|
||
managed_horizontal_align = horizontal_align_presentation(child_direct_scalar(managed_group_options, 17))
|
||
if managed_horizontal_align not in {None, "Auto"}:
|
||
groups["Расположение"] = [
|
||
prop for prop in groups.get("Расположение", []) if prop.get("name") != "GroupHorizontalAlign"
|
||
]
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property(
|
||
"ГоризонтальноеПоложениеВГруппе",
|
||
managed_horizontal_align,
|
||
index=20,
|
||
source="form_payload_managed_group_layout",
|
||
),
|
||
)
|
||
tooltip_representation = {"1": "Button", "2": "None"}.get(str(child_direct_scalar(managed_group_options, 22) or ""))
|
||
if tooltip_representation is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Оформление",
|
||
semantic_property("ОтображениеПодсказки", tooltip_representation, index=20, source="form_payload_managed_group_layout"),
|
||
)
|
||
elif compact_vertical_weak:
|
||
group_mode = "0"
|
||
primary_group = None
|
||
representation_group = None
|
||
layout_group = child_direct_scalar(node, 26)
|
||
grouping_index = 21
|
||
representation_index = 24
|
||
grouping = "Vertical"
|
||
representation = "WeakSeparation"
|
||
elif legacy_group_mode in {"2", "3"}:
|
||
group_mode = legacy_group_mode
|
||
primary_group = child_direct_scalar(node, 23)
|
||
representation_group = child_direct_scalar(node, 25)
|
||
layout_group = child_direct_scalar(node, 27)
|
||
grouping_index = 22
|
||
representation_index = 25
|
||
else:
|
||
group_mode = child_direct_scalar(node, 21)
|
||
primary_group = child_direct_scalar(node, 22)
|
||
representation_group = child_direct_scalar(node, 24)
|
||
layout_group = child_direct_scalar(node, 26)
|
||
grouping_index = 21
|
||
representation_index = 24
|
||
if not managed_group_layout and not compact_vertical_weak:
|
||
weak_representation = bool(primary_group and representation_group and primary_group != representation_group)
|
||
typed_primary_group = child_scalar(node, 22)
|
||
typed_representation_group = child_scalar(node, 24)
|
||
compact_horizontal_if_possible = (
|
||
group_mode == "2"
|
||
and child_direct_scalar(node, 12) == "0"
|
||
and typed_primary_group == "3d3cb80c-508b-41fa-8a18-680cdf5f1712"
|
||
and typed_representation_group == "77ffcc29-7f2d-4223-b22f-19666e7250ba"
|
||
)
|
||
grouping = (
|
||
"HorizontalIfPossible"
|
||
if compact_horizontal_if_possible
|
||
else ("Vertical" if weak_representation else ("HorizontalIfPossible" if group_mode == "3" else "AlwaysHorizontal"))
|
||
)
|
||
representation = "WeakSeparation" if weak_representation or compact_horizontal_if_possible else "None"
|
||
else:
|
||
weak_representation = True
|
||
add_grouped_property(groups, "Расположение", semantic_property("Группировка", grouping, index=grouping_index, source="form_payload_group_layout"))
|
||
add_grouped_property(groups, "Поведение", semantic_property("Поведение", "Usual", source="form_payload_group_layout"))
|
||
add_grouped_property(groups, "Оформление", semantic_property("Отображение", representation, index=representation_index, source="form_payload_group_layout"))
|
||
if managed_group_layout:
|
||
add_grouped_property(groups, "Оформление", semantic_property("ПоказыватьЗаголовок", False, index=20, source="form_payload_managed_group_layout"))
|
||
mark_semantic_parameter_mapped(semantic, 20)
|
||
elif compact_vertical_weak:
|
||
add_grouped_property(groups, "Оформление", semantic_property("ПоказыватьЗаголовок", False, source="live_sql_compact_group_layout"))
|
||
elif not weak_representation and layout_group:
|
||
add_grouped_property(groups, "Оформление", semantic_property("ПоказыватьЗаголовок", False, source="form_payload_group_layout"))
|
||
|
||
|
||
def enrich_table_addition_specific_semantics(row: dict[str, Any], node: Any) -> None:
|
||
if str(row.get("marker") or "") != "6":
|
||
return
|
||
semantic = row.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
properties = [
|
||
("Основные", "Видимость", bool_presentation(child_direct_scalar(node, 9)), 9),
|
||
("Основные", "Доступность", bool_presentation(child_direct_scalar(node, 10)), 10),
|
||
]
|
||
for group, name, value, index in properties:
|
||
if value is not None:
|
||
add_grouped_property(groups, group, semantic_property(name, value, index=index, source="form_payload_table_addition"))
|
||
mark_semantic_parameter_mapped(semantic, index)
|
||
|
||
|
||
def enrich_dynamic_list_specific_semantics(row: dict[str, Any], node: Any) -> None:
|
||
if str(row.get("marker") or "") != "55":
|
||
return
|
||
semantic = row.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
add_grouped_property(groups, "Основные", semantic_property("Вид", "Динамический список", source="form_payload_dynamic_list"))
|
||
visible = bool_presentation(child_direct_scalar(node, 13))
|
||
if visible is not None:
|
||
add_grouped_property(groups, "Основные", semantic_property("Видимость", visible, index=13, source="form_payload_dynamic_list"))
|
||
mark_semantic_parameter_mapped(semantic, 13)
|
||
properties = [
|
||
("Основные", "ПоложениеКоманднойПанели", {"0": "None", "1": "Top", "2": "Bottom"}.get(str(child_direct_scalar(node, 6) or "")), 6),
|
||
("Основные", "АктивизироватьПоУмолчанию", bool_presentation(child_direct_scalar(node, 16)), 16),
|
||
("Основные", "ТолькоПросмотр", bool_presentation(child_direct_scalar(node, 14)), 14),
|
||
("Использование", "ПропускатьПриВводе", bool_presentation(child_direct_scalar(node, 15)), 15),
|
||
("Использование", "ChangeRowSet", bool_presentation(child_direct_scalar(node, 17)), 17),
|
||
("Использование", "ChangeRowOrder", bool_presentation(child_direct_scalar(node, 18)), 18),
|
||
("Оформление", "Отображение", "List", None),
|
||
("Оформление", "ЦветРамки", "style:BorderColor", None),
|
||
("Расположение", "HeightInTableRows", child_direct_scalar(node, 21), 21),
|
||
("Оформление", "Footer", bool_presentation(child_direct_scalar(node, 28)), 28),
|
||
("Использование", "RowSelectionMode", row_selection_mode_presentation(child_direct_scalar(node, 31)), 31),
|
||
("Оформление", "HorizontalLinesBWA", bool_presentation(child_direct_scalar(node, 33)), 33),
|
||
("Оформление", "VerticalLinesBWA", bool_presentation(child_direct_scalar(node, 34)), 34),
|
||
("Оформление", "UseAlternationRowColorBWA", bool_presentation(child_direct_scalar(node, 35)), 35),
|
||
("Использование", "AutoInsertNewRow", bool_presentation(child_direct_scalar(node, 42)), 42),
|
||
("Использование", "EnableStartDrag", bool_presentation(child_direct_scalar(node, 41)), 41),
|
||
("Использование", "EnableDrag", bool_presentation(child_direct_scalar(node, 53)), 53),
|
||
]
|
||
for group, name, value, index in properties:
|
||
if value is not None:
|
||
add_grouped_property(groups, group, semantic_property(name, value, index=index, source="form_payload_dynamic_list"))
|
||
if index is not None:
|
||
mark_semantic_parameter_mapped(semantic, index)
|
||
property_count = child_direct_scalar(node, 54)
|
||
if property_count and property_count.isdigit():
|
||
pair_count = int(property_count)
|
||
property_values: dict[str, tuple[Any, int]] = {}
|
||
property_keys: set[str] = set()
|
||
pair_index = 55
|
||
for _ in range(pair_count):
|
||
key = child_direct_scalar(node, pair_index)
|
||
if key:
|
||
property_keys.add(key)
|
||
value_node = child_at(node, pair_index + 1)
|
||
value_kind = child_direct_scalar(value_node, 0)
|
||
if key and value_kind == "B":
|
||
property_values[key] = (bool_presentation(child_direct_scalar(value_node, 1)), pair_index + 1)
|
||
elif key and value_kind == "N":
|
||
property_values[key] = (child_direct_scalar(value_node, 1), pair_index + 1)
|
||
elif key and value_kind == "#":
|
||
encoded_value = child_direct_scalar(value_node, 2)
|
||
if encoded_value is None and key in {"14", "16"}:
|
||
encoded_value = next((value for value in reversed(atoms(value_node, limit=20)) if value in {"0", "1", "2"}), None)
|
||
property_values[key] = (encoded_value, pair_index + 1)
|
||
elif key == "16":
|
||
value_atoms = atoms(value_node, limit=20)
|
||
encoded_value = next((value for value in reversed(value_atoms) if value in {"0", "1"}), None)
|
||
if encoded_value is None and "U" in value_atoms:
|
||
encoded_value = "1"
|
||
if encoded_value is None:
|
||
encoded_value = "1"
|
||
property_values[key] = (encoded_value, pair_index + 1)
|
||
pair_index += 2
|
||
dynamic_properties = [
|
||
("Использование", "AutoRefresh", property_values.get("5")),
|
||
("Использование", "AutoRefreshPeriod", property_values.get("6")),
|
||
("Использование", "ChoiceFoldersAndItems", property_values.get("8")),
|
||
("Использование", "RestoreCurrentRow", property_values.get("9")),
|
||
("Использование", "ShowRoot", property_values.get("11")),
|
||
("Использование", "AllowRootChoice", property_values.get("12")),
|
||
("Использование", "UpdateOnDataChange", property_values.get("14")),
|
||
("Использование", "AllowGettingCurrentRowURL", property_values.get("16")),
|
||
]
|
||
for group, name, value_and_index in dynamic_properties:
|
||
if value_and_index is None:
|
||
continue
|
||
value, index = value_and_index
|
||
if name == "ChoiceFoldersAndItems":
|
||
value = choice_folders_and_items_presentation(value)
|
||
elif name == "UpdateOnDataChange":
|
||
value = {"0": "Auto", "1": "Always", "2": "Never"}.get(str(value or ""))
|
||
elif name == "AllowGettingCurrentRowURL":
|
||
value = bool_presentation(value)
|
||
if value is not None:
|
||
add_grouped_property(groups, group, semantic_property(name, value, index=index, source="form_payload_dynamic_list_property_bag"))
|
||
mark_semantic_parameter_mapped(semantic, index)
|
||
if "16" in property_keys and "AllowGettingCurrentRowURL" not in {
|
||
str(prop.get("name") or "") for prop in groups.get("Использование", [])
|
||
}:
|
||
add_grouped_property(
|
||
groups,
|
||
"Использование",
|
||
semantic_property(
|
||
"AllowGettingCurrentRowURL",
|
||
True,
|
||
source="form_payload_dynamic_list_property_bag_default",
|
||
),
|
||
)
|
||
user_settings_group = child_at(node, pair_index)
|
||
user_settings_group_ids = [value for value in atoms(user_settings_group, limit=20) if value not in {"", "0"} and value.isdigit()]
|
||
if user_settings_group_ids:
|
||
row["_user_settings_group_ids"] = user_settings_group_ids
|
||
mark_semantic_parameter_mapped(semantic, pair_index)
|
||
initial_tree_view_node = child_at(node, pair_index + 1)
|
||
initial_tree_view = {"0": "ExpandTopLevel", "1": "DoNotExpand", "2": "ExpandAll"}.get(
|
||
str(child_direct_scalar(initial_tree_view_node, 0) or "")
|
||
)
|
||
if initial_tree_view is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Использование",
|
||
semantic_property("InitialTreeView", initial_tree_view, index=pair_index + 1, source="form_payload_dynamic_list_tail"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, pair_index + 1)
|
||
row_picture_field = child_direct_scalar(node, pair_index + 2)
|
||
if row_picture_field not in {None, "", "0"} and row.get("name"):
|
||
add_grouped_property(
|
||
groups,
|
||
"Использование",
|
||
semantic_property(
|
||
"RowPictureDataPath",
|
||
f"{row.get('name')}.DefaultPicture",
|
||
index=pair_index + 2,
|
||
source="form_payload_dynamic_list_standard_field_reference",
|
||
),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, pair_index + 2)
|
||
items = children(node)
|
||
has_location_tail = (
|
||
len(items) >= 30
|
||
and child_direct_scalar(child_at(node, len(items) - 26), 0) == "12"
|
||
and child_direct_scalar(child_at(node, len(items) - 21), 0) == "5"
|
||
and child_direct_scalar(child_at(node, len(items) - 19), 0) == "5"
|
||
and child_direct_scalar(child_at(node, len(items) - 17), 0) == "5"
|
||
)
|
||
if has_location_tail:
|
||
relative_properties = [
|
||
("Расположение", "SearchStringLocation", search_string_location_presentation, -25),
|
||
("Расположение", "ViewStatusLocation", view_status_location_presentation, -24),
|
||
("Расположение", "SearchControlLocation", search_control_location_presentation, -23),
|
||
("Использование", "FileDragMode", file_drag_mode_presentation, -2),
|
||
]
|
||
for group, name, presenter, offset in relative_properties:
|
||
raw_value, index = child_direct_scalar_from_end(node, offset)
|
||
value = presenter(raw_value)
|
||
if value is not None and index is not None:
|
||
add_grouped_property(groups, group, semantic_property(name, value, index=index, source="form_payload_dynamic_list_tail"))
|
||
mark_semantic_parameter_mapped(semantic, index)
|
||
|
||
|
||
def row_selection_mode_presentation(value: str | None) -> str | None:
|
||
return {"2": "Cell"}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def search_string_location_presentation(value: str | None) -> str | None:
|
||
return {
|
||
"0": "Default",
|
||
"1": "None",
|
||
"2": "CommandBar",
|
||
"3": "Top",
|
||
"5": "FormCaption",
|
||
"6": "PullFromTop",
|
||
}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def view_status_location_presentation(value: str | None) -> str | None:
|
||
return {"0": "Default", "1": "None", "2": "Top"}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def search_control_location_presentation(value: str | None) -> str | None:
|
||
return {"0": "Default", "1": "None", "2": "CommandBar"}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def file_drag_mode_presentation(value: str | None) -> str | None:
|
||
return {"0": "AsFile", "1": "Default"}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def table_height_in_rows_parameter(node: Any) -> tuple[str | None, int]:
|
||
live_value = child_direct_scalar(node, 38)
|
||
legacy_value = child_direct_scalar(node, 39)
|
||
if live_value not in {None, "", "0"} and legacy_value in {None, "", "0"}:
|
||
return live_value, 38
|
||
return legacy_value, 39
|
||
|
||
|
||
def enrich_table_specific_semantics(row: dict[str, Any], node: Any) -> None:
|
||
if str(row.get("marker") or "") != "73":
|
||
return
|
||
semantic = row.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
height_value, height_index = table_height_in_rows_parameter(node)
|
||
enable_start_drag_value = bool_presentation(child_direct_scalar(node, 43))
|
||
enable_start_drag_index = 43
|
||
if enable_start_drag_value is None:
|
||
enable_start_drag_value = bool_presentation(child_direct_scalar(node, 41))
|
||
enable_start_drag_index = 41
|
||
properties = [
|
||
("Основные", "Видимость", False if child_direct_scalar(node, 72) == "0" else None, 72),
|
||
("Основные", "Доступность", False if child_direct_scalar(node, 13) == "0" else None, 13),
|
||
("Оформление", "Отображение", "List", None),
|
||
("Расположение", "HeightInTableRows", height_value, height_index),
|
||
("Использование", "RowSelectionMode", row_selection_mode_presentation(child_direct_scalar(node, 31)), 31),
|
||
("Оформление", "HorizontalLinesBWA", bool_presentation(child_direct_scalar(node, 33)), 33),
|
||
("Оформление", "VerticalLinesBWA", bool_presentation(child_direct_scalar(node, 34)), 34),
|
||
("Оформление", "UseAlternationRowColorBWA", bool_presentation(child_direct_scalar(node, 35)), 35),
|
||
("Использование", "AutoInsertNewRow", bool_presentation(child_direct_scalar(node, 42)), 42),
|
||
("Использование", "EnableStartDrag", enable_start_drag_value, enable_start_drag_index),
|
||
("Использование", "EnableDrag", bool_presentation(child_direct_scalar(node, 53)), 53),
|
||
]
|
||
for group, name, value, index in properties:
|
||
if value not in {None, ""}:
|
||
add_grouped_property(groups, group, semantic_property(name, value, index=index, source="form_payload_table"))
|
||
if index is not None:
|
||
mark_semantic_parameter_mapped(semantic, index)
|
||
|
||
|
||
def enrich_command_button_specific_semantics(row: dict[str, Any], node: Any) -> None:
|
||
if str(row.get("marker") or "") != "31":
|
||
return
|
||
semantic = row.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
command_bar_location_raw = child_direct_scalar(node, 14)
|
||
command_bar_location = command_bar_location_presentation(command_bar_location_raw)
|
||
if command_bar_location_raw == "2":
|
||
command_bar_location = "InCommandBar" if child_direct_scalar(node, 15) == "1" else "В дополнительном подменю"
|
||
properties = [
|
||
("Основные", "Видимость", {"0": False, "1": True, "2": True}.get(str(child_direct_scalar(node, 49) or "")), 49),
|
||
("Основные", "Отображение", button_representation_presentation(child_direct_scalar(node, 10)), 10),
|
||
("Основные", "Доступность", bool_presentation(child_direct_scalar(node, 31)), 31),
|
||
("Основные", "ButtonImportance", button_importance_presentation(child_direct_scalar(node, 11)), 11),
|
||
("Основные", "КнопкаПоУмолчанию", bool_presentation(child_direct_scalar(node, 11)), 11),
|
||
("Использование", "ПропускатьПриВводе", {"0": False, "1": True, "2": False}.get(str(child_direct_scalar(node, 29) or "")), 29),
|
||
("Расположение", "ПоложениеВКоманднойПанели", command_bar_location, 14),
|
||
("Расположение", "УникальностьКоманды", bool_presentation(child_direct_scalar(node, 26)), 26),
|
||
("Расположение", "GroupHorizontalAlign", horizontal_align_presentation(child_direct_scalar(node, 41)), 41),
|
||
("Расположение", "GroupVerticalAlign", vertical_align_presentation(child_direct_scalar(node, 42)), 42),
|
||
("Оформление", "ЦветРамки", auto_enum_presentation(child_direct_scalar(node, 43)), 43),
|
||
("Оформление", "ShapeRepresentation", "None" if child_direct_scalar(node, 45) == "3" else None, 45),
|
||
("Использование", "ToolTipRepresentation", {"1": "Button", "2": "Balloon"}.get(str(child_direct_scalar(node, 50) or "")), 50),
|
||
]
|
||
for group, name, value, index in properties:
|
||
if value is not None:
|
||
add_grouped_property(groups, group, semantic_property(name, value, index=index, source="form_payload_command_button"))
|
||
mark_semantic_parameter_mapped(semantic, index)
|
||
|
||
|
||
def enrich_decoration_specific_semantics(row: dict[str, Any], node: Any) -> None:
|
||
if str(row.get("marker") or "") != "12":
|
||
return
|
||
semantic = row.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
width = child_direct_scalar(node, 10)
|
||
specific = child_at(node, 18)
|
||
properties = [
|
||
("Ширина", width if width not in {None, "", "0"} else None, 10, "Расположение"),
|
||
("Высота", child_direct_scalar(node, 11) if child_direct_scalar(node, 11) not in {None, "", "0"} else None, 11, "Расположение"),
|
||
("РастягиватьПоГоризонтали", auto_bool_presentation(child_direct_scalar(node, 12)), 12, "Расположение"),
|
||
("РастягиватьПоВертикали", auto_bool_presentation(child_direct_scalar(node, 13)), 13, "Расположение"),
|
||
("ЦветТекста", style_value_presentation(child_at(node, 14)), 14, "Оформление"),
|
||
("ЦветФона", style_value_presentation(child_at(specific, 6)), 18, "Оформление"),
|
||
("Видимость", bool_presentation(child_direct_scalar(node, 21)), 21, "Основные"),
|
||
("АвтоМаксимальнаяШирина", bool_presentation(child_direct_scalar(node, 27)), 27, "Расположение"),
|
||
("МаксимальнаяШирина", child_direct_scalar(node, 28) if child_direct_scalar(node, 28) not in {None, "", "0"} else None, 28, "Расположение"),
|
||
("АвтоМаксимальнаяВысота", bool_presentation(child_direct_scalar(node, 29)), 29, "Расположение"),
|
||
("МаксимальнаяВысота", child_direct_scalar(node, 31) if child_direct_scalar(node, 31) not in {None, "", "0"} else None, 31, "Расположение"),
|
||
(
|
||
"ГоризонтальноеПоложениеВГруппе",
|
||
horizontal_align_presentation(child_direct_scalar(specific, 2))
|
||
if child_direct_scalar(specific, 2) not in {None, "", "0"}
|
||
else None,
|
||
18,
|
||
"Расположение",
|
||
),
|
||
("ОтображениеПодсказки", {"2": "Balloon"}.get(str(child_direct_scalar(node, 22) or "")), 22, "Оформление"),
|
||
("Гиперссылка", bool_presentation(child_direct_scalar(specific, 1)), 18, "Использование"),
|
||
("GroupHorizontalAlign", horizontal_align_presentation(child_direct_scalar(node, 32)), 32),
|
||
("GroupVerticalAlign", vertical_align_presentation(child_direct_scalar(node, 33)), 33),
|
||
]
|
||
for item in properties:
|
||
name, value, index = item[:3]
|
||
group = item[3] if len(item) > 3 else "Расположение"
|
||
if value is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
group,
|
||
semantic_property(name, value, index=index, source="form_payload_decoration"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, index)
|
||
|
||
|
||
def enrich_radio_button_specific_semantics(row: dict[str, Any], node: Any) -> None:
|
||
if str(row.get("marker") or "") != "37" or str(row.get("type_code") or "") != "5":
|
||
return
|
||
specific = child_at(node, 39)
|
||
if child_direct_scalar(specific, 0) != "8":
|
||
return
|
||
radio_button_type = {"0": "Auto", "2": "Tumbler"}.get(str(child_direct_scalar(specific, 7) or ""))
|
||
if radio_button_type is None:
|
||
return
|
||
semantic = row.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
add_grouped_property(
|
||
groups,
|
||
"Прочее",
|
||
semantic_property("RadioButtonType", radio_button_type, index=39, source="form_payload_radio_button"),
|
||
)
|
||
columns_count = child_direct_scalar(specific, 2)
|
||
if columns_count not in {None, "", "0"}:
|
||
add_grouped_property(
|
||
groups,
|
||
"Расположение",
|
||
semantic_property("ColumnsCount", columns_count, index=39, source="form_payload_radio_button"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 39)
|
||
|
||
|
||
def enrich_form_button_specific_semantics(row: dict[str, Any], node: Any) -> None:
|
||
if str(row.get("marker") or "") != "34":
|
||
return
|
||
semantic = row.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
properties = [
|
||
("Основные", "Видимость", False if child_direct_scalar(node, 26) == "0" else None, 26),
|
||
("Основные", "Доступность", False if child_direct_scalar(node, 7) == "0" else None, 7),
|
||
]
|
||
for group, name, value, index in properties:
|
||
if value is not None:
|
||
add_grouped_property(groups, group, semantic_property(name, value, index=index, source="form_payload_button"))
|
||
mark_semantic_parameter_mapped(semantic, index)
|
||
|
||
|
||
def form_button_command_binding(node: Any, base_path: str) -> dict[str, Any] | None:
|
||
if child_direct_scalar(node, 0) != "34":
|
||
return None
|
||
binding = children(node)[8] if len(children(node)) > 8 else None
|
||
binding_items = children(binding)
|
||
if len(binding_items) < 2:
|
||
return None
|
||
command_id = direct_scalar(binding_items[0])
|
||
group_guid = direct_scalar(binding_items[1])
|
||
if command_id in {None, ""} or group_guid in {None, ""}:
|
||
return None
|
||
group_guid = str(group_guid).lower()
|
||
result: dict[str, Any] = {
|
||
"command_id": str(command_id),
|
||
"group_guid": group_guid,
|
||
"path": path_join(base_path, 8),
|
||
"command_id_path": path_join(path_join(base_path, 8), 0),
|
||
"group_guid_path": path_join(path_join(base_path, 8), 1),
|
||
}
|
||
standard_name = FORM_STANDARD_COMMANDS.get((group_guid, str(command_id)))
|
||
if standard_name:
|
||
result.update({"command_name": standard_name, "scope": "standard", "match_by": "standard_command_guid"})
|
||
elif group_guid == FORM_LOCAL_COMMAND_GROUP_GUID:
|
||
result.update({"scope": "form", "match_by": "form_command_id"})
|
||
else:
|
||
result.update({"scope": "unknown", "match_by": "command_binding"})
|
||
return result
|
||
|
||
|
||
def form_command_reference(node: Any, base_path: str) -> dict[str, Any] | None:
|
||
"""Expose metadata-command references used by command-bar buttons."""
|
||
if child_direct_scalar(node, 0) != "31":
|
||
return None
|
||
reference = children(node)[8] if len(children(node)) > 8 else None
|
||
values = atoms(reference, limit=10)
|
||
command_guid = next((value.lower() for value in values if GUID_RE.fullmatch(value or "")), None)
|
||
if not command_guid:
|
||
return None
|
||
command_code = next((value for value in values if re.fullmatch(r"-?\d+", value or "")), None)
|
||
result = {
|
||
"guid": command_guid,
|
||
**({"code": command_code} if command_code is not None else {}),
|
||
"path": path_join(base_path, 8),
|
||
"scope": "metadata",
|
||
"status": "identity_pending",
|
||
}
|
||
standard_name = FORM_STANDARD_COMMANDS.get((command_guid, str(command_code or "")))
|
||
if standard_name:
|
||
result.update({"command_name": standard_name, "scope": "standard", "status": "ok", "match_by": "standard_command_guid"})
|
||
elif command_guid == FORM_LOCAL_COMMAND_GROUP_GUID and command_code is not None:
|
||
result.update({"command_id": command_code, "scope": "form", "status": "ok", "match_by": "form_command_id"})
|
||
return result
|
||
|
||
|
||
def edit_mode_presentation(value: str | None) -> str | None:
|
||
return {
|
||
"3": "EnterOnInput",
|
||
}.get(str(value) if value is not None else "")
|
||
|
||
|
||
def multiline_presentation(value: str | None) -> bool | None:
|
||
if value == "1":
|
||
return True
|
||
if value == "2":
|
||
return False
|
||
return None
|
||
|
||
|
||
def form_field_details_node(node: Any) -> tuple[Any | None, int | None]:
|
||
for index in (39, 40):
|
||
details = children(node)[index] if len(children(node)) > index else None
|
||
if details is not None and child_direct_scalar(details, 0) == "38":
|
||
return details, index
|
||
return None, None
|
||
|
||
|
||
def enrich_form_field_specific_semantics(row: dict[str, Any], node: Any) -> None:
|
||
if str(row.get("marker") or "") != "48":
|
||
return
|
||
semantic = row.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
field_properties = [
|
||
("Основные", "ПоложениеЗаголовка", "None" if child_direct_scalar(node, 7) == "0" else None, 7),
|
||
("Основные", "Видимость", False if child_direct_scalar(node, 43) == "0" else None, 43),
|
||
("Основные", "Доступность", False if child_direct_scalar(node, 13) == "0" else None, 13),
|
||
("Основные", "ТолькоПросмотр", True if child_direct_scalar(node, 14) == "1" else None, 14),
|
||
("Основные", "ПропускатьПриВводе", True if child_direct_scalar(node, 15) == "1" else None, 15),
|
||
]
|
||
for group, name, value, index in field_properties:
|
||
if value is not None:
|
||
add_grouped_property(groups, group, semantic_property(name, value, index=index, source="form_payload_field"))
|
||
mark_semantic_parameter_mapped(semantic, index)
|
||
details, details_index = form_field_details_node(node)
|
||
if details is not None and details_index is not None:
|
||
auto_max_width = bool_presentation(child_direct_scalar(details, 49))
|
||
multiline = multiline_presentation(child_direct_scalar(details, 8))
|
||
properties = [
|
||
("Расположение", "Ширина", child_direct_scalar(details, 2), details_index),
|
||
("Расположение", "Высота", child_direct_scalar(details, 3), details_index),
|
||
("Расположение", "АвтоМаксимальнаяШирина", False if auto_max_width is False else None, details_index),
|
||
("Использование", "MultiLine", True if multiline is True else None, details_index),
|
||
]
|
||
for group, name, value, index in properties:
|
||
if value not in {None, "", "0"}:
|
||
add_grouped_property(groups, group, semantic_property(name, value, index=index, source="form_payload_field_details"))
|
||
edit_mode_index = 62
|
||
edit_mode = edit_mode_presentation(child_direct_scalar(node, edit_mode_index))
|
||
if edit_mode is None:
|
||
edit_mode_index = 61
|
||
edit_mode = edit_mode_presentation(child_direct_scalar(node, edit_mode_index))
|
||
if edit_mode is not None:
|
||
add_grouped_property(groups, "Использование", semantic_property("РежимРедактирования", edit_mode, index=edit_mode_index, source="form_payload_field"))
|
||
mark_semantic_parameter_mapped(semantic, edit_mode_index)
|
||
auto_edit_index = edit_mode_index + 1
|
||
auto_edit = bool_presentation(child_direct_scalar(node, auto_edit_index))
|
||
if edit_mode is not None and auto_edit is not None:
|
||
add_grouped_property(groups, "Использование", semantic_property("AutoEditMode", auto_edit, index=auto_edit_index, source="form_payload_field"))
|
||
mark_semantic_parameter_mapped(semantic, auto_edit_index)
|
||
|
||
|
||
def section_record_semantic_properties(row: dict[str, Any], parameters: list[dict[str, Any]], node: Any = None) -> dict[str, Any]:
|
||
groups: dict[str, list[dict[str, Any]]] = {}
|
||
mapped: set[int] = set()
|
||
for index, (group, name) in SECTION_RECORD_SEMANTIC_PROPERTIES.items():
|
||
mapped.add(index)
|
||
value = parameter_value(parameters, index)
|
||
source = "form_payload"
|
||
# A managed-form record can store the localized title outside its
|
||
# direct parameter #3. The row decoder already resolves that exact
|
||
# title path, so expose it instead of misleading an agent with an
|
||
# empty semantic «Заголовок» beside a non-empty public row.title.
|
||
if index == 3 and value in {None, ""} and row.get("title") not in {None, ""}:
|
||
value = row.get("title")
|
||
source = "form_payload_title_path"
|
||
add_grouped_property(groups, group, semantic_property(name, value, index=index, source=source))
|
||
if row.get("category"):
|
||
add_grouped_property(groups, "Основные", semantic_property("Категория", row.get("category"), source="decoder"))
|
||
add_grouped_property(groups, "Основные", semantic_property("Вид", row.get("category"), source="decoder"))
|
||
if row.get("category") == "Command" and row.get("name"):
|
||
raw_action = parameter_value(parameters, 8)
|
||
explicit_action = str(raw_action or "").strip()
|
||
action = explicit_action if BSL_IDENTIFIER_RE.fullmatch(explicit_action) else str(row.get("name") or "")
|
||
row["action"] = action
|
||
row["action_source"] = "form_payload_parameter_8" if explicit_action == action else "command_name_fallback"
|
||
if explicit_action == action:
|
||
mapped.add(8)
|
||
add_grouped_property(
|
||
groups,
|
||
"Основные",
|
||
semantic_property("Action", action, index=8 if explicit_action == action else None, source=row["action_source"]),
|
||
)
|
||
current_row_code = str(parameter_value(parameters, 9) or "")
|
||
if current_row_code:
|
||
current_row_use = {"1": "DontUse", "2": "DontUse", "3": "DontUse"}.get(current_row_code, current_row_code)
|
||
add_grouped_property(groups, "Использование", semantic_property("CurrentRowUse", current_row_use, index=9, source="form_payload_command"))
|
||
mapped.add(9)
|
||
modifies_saved_data = bool_presentation(str(parameter_value(parameters, 10) or ""))
|
||
if modifies_saved_data is not None:
|
||
add_grouped_property(groups, "Использование", semantic_property("ModifiesSavedData", modifies_saved_data, index=10, source="form_payload_command"))
|
||
mapped.add(10)
|
||
representation = {"1": "Picture", "2": "TextPicture"}.get(str(child_direct_scalar(node, 9) or ""))
|
||
if representation is not None:
|
||
add_grouped_property(groups, "Основные", semantic_property("Отображение", representation, index=9, source="form_payload_command"))
|
||
shortcut = shortcut_presentation(child_at(node, 6))
|
||
if shortcut is not None:
|
||
add_grouped_property(groups, "Использование", semantic_property("СочетаниеКлавиш", shortcut, index=6, source="form_payload_command"))
|
||
mapped.add(6)
|
||
if row.get("category") == "Attribute":
|
||
main_attribute = bool_presentation(str(parameter_value(parameters, 10) or ""))
|
||
if main_attribute is None and row.get("index") == 0 and str(row.get("name") or "") == "Объект":
|
||
main_attribute = True
|
||
if main_attribute is not None:
|
||
add_grouped_property(groups, "Основные", semantic_property("MainAttribute", main_attribute, index=10 if parameter_value(parameters, 10) is not None else None, source="form_payload_attribute"))
|
||
if parameter_value(parameters, 10) is not None:
|
||
mapped.add(10)
|
||
saved_data = bool_presentation(str(parameter_value(parameters, 11) or ""))
|
||
if saved_data is not None:
|
||
add_grouped_property(groups, "Использование", semantic_property("SavedData", saved_data, index=11, source="form_payload_attribute"))
|
||
mapped.add(11)
|
||
fill_check = {"1": "ShowError"}.get(str(parameter_value(parameters, 12) or ""))
|
||
if fill_check is not None:
|
||
add_grouped_property(groups, "Использование", semantic_property("FillCheck", fill_check, index=12, source="form_payload_attribute"))
|
||
mapped.add(12)
|
||
return {
|
||
"groups": groups,
|
||
"unmapped_parameters": semantic_unmapped_parameters(parameters, mapped),
|
||
"coverage": semantic_coverage(parameters, mapped),
|
||
}
|
||
|
||
|
||
def first_scalar_with_path(node: Any, base_path: str, *, skip_guids: bool = False) -> dict[str, str] | None:
|
||
direct = scalar(node)
|
||
if direct not in {None, ""} and (not skip_guids or not GUID_RE.fullmatch(direct)):
|
||
return {"value": direct, "path": base_path}
|
||
for index, child in enumerate(children(node)):
|
||
found = first_scalar_with_path(child, path_join(base_path, index), skip_guids=skip_guids)
|
||
if found:
|
||
return found
|
||
return None
|
||
|
||
|
||
def localized_text(node: Any, base_path: str, *, max_depth: int = 2) -> dict[str, str] | None:
|
||
def walk(value: Any, path: str, depth: int) -> dict[str, str] | None:
|
||
if depth > max_depth:
|
||
return None
|
||
items = children(value)
|
||
for index in range(len(items) - 1):
|
||
lang = scalar(items[index])
|
||
text = scalar(items[index + 1])
|
||
if lang and text and re.fullmatch(r"[a-z]{2}(?:[-_][A-Z]{2})?", lang):
|
||
return {"lang": lang, "value": text, "path": path_join(path, index + 1)}
|
||
for index, child in enumerate(items):
|
||
found = walk(child, path_join(path, index), depth + 1)
|
||
if found:
|
||
return found
|
||
return None
|
||
|
||
return walk(node, base_path, 0)
|
||
|
||
|
||
def record_id(node: Any, base_path: str) -> dict[str, str] | None:
|
||
items = children(node)
|
||
if len(items) > 1:
|
||
direct = first_scalar_with_path(items[1], path_join(base_path, 1))
|
||
if direct:
|
||
return direct
|
||
return None
|
||
|
||
|
||
def reference_id_from_node(node: Any) -> str | None:
|
||
values = atoms(node, limit=20)
|
||
numbers = [value for value in values if re.fullmatch(r"-?\d+", value or "")]
|
||
return numbers[-1] if numbers else None
|
||
|
||
|
||
def data_path_reference_from_node(node: Any) -> dict[str, str]:
|
||
items = children(node)
|
||
if len(items) >= 3 and scalar(items[0]) == "2":
|
||
owner_id = reference_id_from_node(items[1])
|
||
field_id = reference_id_from_node(items[2])
|
||
if owner_id and field_id:
|
||
return {"attribute_id": owner_id, "field_id": field_id}
|
||
reference = reference_id_from_node(node)
|
||
return {"attribute_id": reference} if reference else {}
|
||
|
||
|
||
def footer_data_path_reference(primary_node: Any, footer_node: Any) -> dict[str, Any] | None:
|
||
primary_items = children(primary_node)
|
||
primary_candidate = primary_items[3] if len(primary_items) > 3 else None
|
||
primary_aggregate = (
|
||
child_direct_scalar(primary_node, 0) == "3"
|
||
and str(child_direct_scalar(primary_candidate, 0) or "").startswith("101")
|
||
)
|
||
aggregate_owner = primary_node if primary_aggregate else footer_node
|
||
if child_direct_scalar(aggregate_owner, 0) != "3":
|
||
return None
|
||
aggregate_items = children(aggregate_owner)
|
||
aggregate_node = aggregate_items[3] if len(aggregate_items) > 3 else None
|
||
aggregate_code = child_direct_scalar(aggregate_node, 0)
|
||
field_guid = child_direct_scalar(aggregate_node, 1)
|
||
if not aggregate_code or not aggregate_code.startswith("101") or not field_guid or not GUID_RE.fullmatch(field_guid):
|
||
return None
|
||
if not primary_aggregate:
|
||
primary_guids = guids(primary_node, limit=8)
|
||
if not primary_guids or primary_guids[-1] != field_guid.lower():
|
||
return None
|
||
result: dict[str, Any] = {"aggregate": "Total", "field_guid": field_guid.lower()}
|
||
if primary_aggregate:
|
||
result["primary_aggregate"] = True
|
||
return result
|
||
|
||
|
||
def record_name(node: Any, *, category: str | None = None) -> str | None:
|
||
items = children(node)
|
||
marker = child_scalar(node, 0)
|
||
name_indexes = [3, 6] if category == "Attribute" and marker == "9" else [2, 6, 3]
|
||
for index in name_indexes:
|
||
if index >= len(items):
|
||
continue
|
||
name = scalar(items[index])
|
||
if name and not GUID_RE.fullmatch(name) and not re.fullmatch(r"-?\d+(?:\.\d+)?", name):
|
||
return name
|
||
strings = collect_strings(node, limit=20)
|
||
return next((item for item in strings if item and item not in {"#", "Pattern", "B", "U", "S", "N", "D", "ru"}), None)
|
||
|
||
|
||
def form_parameter_rows(tree: Any, *, include_parameters: bool = True, max_parameters: int = 80) -> list[dict[str, Any]]:
|
||
section = child_at(tree, 4)
|
||
if child_direct_scalar(section, 0) != "0":
|
||
return []
|
||
result: list[dict[str, Any]] = []
|
||
for index, node in enumerate(children(section)[2:]):
|
||
if child_direct_scalar(node, 0) != "0":
|
||
continue
|
||
name = str(child_direct_scalar(node, 1) or "")
|
||
if not name or not BSL_IDENTIFIER_RE.fullmatch(name):
|
||
continue
|
||
key_parameter = bool_presentation(child_direct_scalar(node, 3))
|
||
groups: dict[str, list[dict[str, Any]]] = {
|
||
"Основные": [semantic_property("Вид", "Parameter", source="form_payload_parameter")],
|
||
}
|
||
if key_parameter is not None:
|
||
add_grouped_property(
|
||
groups,
|
||
"Использование",
|
||
semantic_property("KeyParameter", key_parameter, index=3, source="form_payload_parameter"),
|
||
)
|
||
row: dict[str, Any] = {
|
||
"category": "Parameter",
|
||
"type_name": "Parameter",
|
||
"name": name,
|
||
"path": f"4.{index + 2}",
|
||
"semantic": {"groups": groups},
|
||
}
|
||
if include_parameters:
|
||
row["parameters"] = direct_parameters(node, row["path"], limit=max_parameters)
|
||
result.append(row)
|
||
return result
|
||
|
||
|
||
def event_handlers(tree: Any) -> list[dict[str, Any]]:
|
||
base_path = "1.19"
|
||
node = get_by_path(tree, base_path)
|
||
if node is None or not children(node):
|
||
for candidate_path in ("1.18", "1.27"):
|
||
candidate = get_by_path(tree, candidate_path)
|
||
candidate_items = children(candidate)
|
||
if any(
|
||
GUID_RE.fullmatch(scalar(candidate_items[index]) or "") and BSL_IDENTIFIER_RE.fullmatch(scalar(candidate_items[index + 1]) or "")
|
||
for index in range(1, max(1, len(candidate_items) - 1))
|
||
):
|
||
base_path = candidate_path
|
||
node = candidate
|
||
break
|
||
items = children(node)
|
||
if not any(
|
||
GUID_RE.fullmatch(scalar(items[index]) or "") and BSL_IDENTIFIER_RE.fullmatch(scalar(items[index + 1]) or "")
|
||
for index in range(1, max(1, len(items) - 1))
|
||
):
|
||
form_node = get_by_path(tree, "1")
|
||
for candidate_index, candidate in enumerate(children(form_node)):
|
||
candidate_items = children(candidate)
|
||
if any(
|
||
GUID_RE.fullmatch(scalar(candidate_items[index]) or "")
|
||
and BSL_IDENTIFIER_RE.fullmatch(scalar(candidate_items[index + 1]) or "")
|
||
for index in range(1, max(1, len(candidate_items) - 1))
|
||
):
|
||
base_path = f"1.{candidate_index}"
|
||
node = candidate
|
||
break
|
||
items = children(node)
|
||
result: list[dict[str, Any]] = []
|
||
index = 1
|
||
while index + 1 < len(items):
|
||
guid = scalar(items[index])
|
||
handler = scalar(items[index + 1])
|
||
if guid and handler and GUID_RE.fullmatch(guid) and BSL_IDENTIFIER_RE.fullmatch(handler):
|
||
event_guid = guid.lower() if GUID_RE.fullmatch(guid) else None
|
||
event_name = FORM_EVENT_NAMES.get(event_guid or "")
|
||
result.append(
|
||
{
|
||
"name": event_name,
|
||
"type_name": "Event",
|
||
"event_name": FORM_EVENT_NAMES.get(event_guid or ""),
|
||
"handler": handler,
|
||
"guid": event_guid,
|
||
"path": f"{base_path}.{index + 1}",
|
||
}
|
||
)
|
||
index += 2
|
||
return result
|
||
|
||
|
||
def event_handlers_from_node(node: Any, base_path: str, *, owner: str | None = None, max_depth: int = 3) -> list[dict[str, Any]]:
|
||
result: list[dict[str, Any]] = []
|
||
seen: set[tuple[str | None, str]] = set()
|
||
|
||
def add_event(guid: str | None, handler: str, path: str) -> None:
|
||
event_guid = guid.lower() if guid and GUID_RE.fullmatch(guid) else None
|
||
key = (event_guid, handler.casefold())
|
||
if key in seen:
|
||
return
|
||
seen.add(key)
|
||
result.append(
|
||
{
|
||
"kind": "element_event",
|
||
**({"owner": owner} if owner else {}),
|
||
"event_name": FORM_EVENT_NAMES.get(event_guid or ""),
|
||
"handler": handler,
|
||
"guid": event_guid,
|
||
"path": path,
|
||
}
|
||
)
|
||
|
||
def walk(value: Any, path: str, depth: int) -> None:
|
||
if depth > max_depth:
|
||
return
|
||
items = children(value)
|
||
if depth > 0:
|
||
marker = scalar(items[0]) if items else None
|
||
if marker in FORM_ITEM_MARKERS:
|
||
return
|
||
if len(items) >= 3 and scalar(items[0]) and re.fullmatch(r"\d+", scalar(items[0]) or ""):
|
||
index = 1
|
||
while index + 1 < len(items):
|
||
guid = scalar(items[index])
|
||
handler = scalar(items[index + 1])
|
||
if guid and handler and GUID_RE.fullmatch(guid) and BSL_IDENTIFIER_RE.fullmatch(handler):
|
||
add_event(guid, handler, path_join(path, index + 1))
|
||
index += 2
|
||
continue
|
||
index += 1
|
||
for index, child in enumerate(items):
|
||
walk(child, path_join(path, index), depth + 1)
|
||
|
||
walk(node, base_path, 0)
|
||
return result
|
||
|
||
|
||
def form_item_name(node: Any, base_path: str) -> dict[str, str] | None:
|
||
marker = child_scalar(node, 0)
|
||
if marker == "22":
|
||
index = 7 if not child_scalar(node, 5) and re.fullmatch(r"\d+", child_scalar(node, 6) or "") else 6
|
||
elif marker == "31":
|
||
index = 5
|
||
elif marker == "34":
|
||
direct = child_scalar(node, 5)
|
||
index = 5 if direct and not re.fullmatch(r"-?\d+(?:\.\d+)?", direct) else 6
|
||
elif marker in {"35", "37"}:
|
||
index = 7 if not child_scalar(node, 5) and re.fullmatch(r"\d+", child_scalar(node, 6) or "") else 6
|
||
elif marker == "48":
|
||
index = 7 if children(child_at(node, 5)) else 6
|
||
elif marker == "55":
|
||
index = 5
|
||
elif marker == "73":
|
||
index = 5 if child_scalar(node, 5) and not re.fullmatch(r"-?\d+(?:\.\d+)?", child_scalar(node, 5) or "") else 6
|
||
else:
|
||
index = 6
|
||
name = child_scalar(node, index)
|
||
if name and not GUID_RE.fullmatch(name):
|
||
return {"value": name, "path": path_join(base_path, index)}
|
||
found = first_scalar_with_path(node, base_path, skip_guids=True)
|
||
if found and found["value"] not in {"#", "Pattern", "B", "U", "S", "N", "D", "ru"}:
|
||
return found
|
||
return None
|
||
|
||
|
||
def form_item_type_code(node: Any, marker: str | None) -> str | None:
|
||
if marker == "6":
|
||
return child_scalar(node, 5)
|
||
if marker == "34":
|
||
direct = child_scalar(node, 5)
|
||
return child_scalar(node, 4) if direct and not re.fullmatch(r"-?\d+(?:\.\d+)?", direct or "") else direct
|
||
if marker == "48":
|
||
return child_scalar(node, 6) if children(child_at(node, 5)) else child_scalar(node, 5)
|
||
if marker == "22":
|
||
direct = child_scalar(node, 5)
|
||
shifted = child_scalar(node, 6)
|
||
if not direct and re.fullmatch(r"\d+", shifted or ""):
|
||
return shifted
|
||
return direct
|
||
if marker in {"35", "37"}:
|
||
direct = child_scalar(node, 5)
|
||
shifted = child_scalar(node, 6)
|
||
if not direct and re.fullmatch(r"\d+", shifted or ""):
|
||
return shifted
|
||
return direct
|
||
if marker == "12":
|
||
return child_scalar(node, 5)
|
||
return child_scalar(node, 0)
|
||
|
||
|
||
def embedded_table_addition_row(
|
||
node: Any,
|
||
path: str,
|
||
depth: int,
|
||
*,
|
||
include_parameters: bool,
|
||
max_parameters: int,
|
||
) -> dict[str, Any] | None:
|
||
"""Normalize a table addition stored as marker 5 inside a marker 55/73 tail."""
|
||
items = children(node)
|
||
type_code = child_direct_scalar(node, 5)
|
||
name = child_direct_scalar(node, 6)
|
||
identity = children(items[1]) if len(items) > 1 else []
|
||
item_id = direct_scalar(identity[0]) if identity else None
|
||
owner_guid = direct_scalar(identity[1]) if len(identity) > 1 else None
|
||
if (
|
||
len(items) < 20
|
||
or child_direct_scalar(node, 0) != "5"
|
||
or type_code not in FORM_TABLE_ADDITION_TYPE_NAMES
|
||
or not name
|
||
or not item_id
|
||
or not re.fullmatch(r"\d+", item_id)
|
||
or not owner_guid
|
||
or not GUID_RE.fullmatch(owner_guid)
|
||
):
|
||
return None
|
||
evidence = collect_evidence(node)
|
||
title = localized_text(items[7], path_join(path, 7)) if len(items) > 7 else None
|
||
parameters = direct_parameters(node, path, roles=FORM_ITEM_PARAMETER_ROLES["6"], limit=max_parameters)
|
||
row: dict[str, Any] = {
|
||
"name": name,
|
||
"name_path": path_join(path, 6),
|
||
"id": item_id,
|
||
"id_path": path_join(path_join(path, 1), 0),
|
||
"title": title["value"] if title else None,
|
||
"title_lang": title.get("lang") if title else None,
|
||
"title_path": title["path"] if title else None,
|
||
"path": path,
|
||
"depth": depth,
|
||
"marker": "6",
|
||
"marker_name": "TableAddition",
|
||
"type_code": type_code,
|
||
"type_name": FORM_TABLE_ADDITION_TYPE_NAMES[type_code],
|
||
"strings_sample": sorted(evidence["strings"])[:30],
|
||
"guids_sample": sorted(evidence["guids"])[:20],
|
||
}
|
||
row["semantic"] = form_item_semantic_properties(row, parameters)
|
||
enrich_table_addition_specific_semantics(row, node)
|
||
row["semantic"] = public_semantic(row["semantic"], include_diagnostics=include_parameters)
|
||
if include_parameters:
|
||
row["parameters"] = parameters
|
||
return row
|
||
|
||
|
||
def item_records(
|
||
tree: Any,
|
||
*,
|
||
limit: int = 500,
|
||
include_parameters: bool = True,
|
||
max_parameters: int = 80,
|
||
) -> tuple[list[dict[str, Any]], int, bool]:
|
||
records: list[dict[str, Any]] = []
|
||
total = 0
|
||
|
||
def walk(node: Any, path: list[int], depth: int) -> None:
|
||
nonlocal total
|
||
items = children(node)
|
||
marker = scalar(items[0]) if items else None
|
||
if len(items) >= 6 and marker in FORM_ITEM_MARKERS:
|
||
total += 1
|
||
current_path = ".".join(str(part) for part in path)
|
||
name = form_item_name(node, current_path)
|
||
if name and len(records) < limit:
|
||
evidence = collect_evidence(node)
|
||
title = localized_text(node, current_path)
|
||
item_id = record_id(node, current_path)
|
||
parameter_roles = FORM_ITEM_PARAMETER_ROLES.get(marker or "", {})
|
||
type_code = form_item_type_code(node, marker)
|
||
public_type_name = form_item_public_type_name(marker, type_code, name["value"])
|
||
row = {
|
||
"name": name["value"],
|
||
"name_path": name["path"],
|
||
"id": item_id["value"] if item_id else None,
|
||
"id_path": item_id["path"] if item_id else None,
|
||
"title": title["value"] if title else None,
|
||
"title_lang": title.get("lang") if title else None,
|
||
"title_path": title["path"] if title else None,
|
||
"path": current_path,
|
||
"depth": depth,
|
||
"marker": marker,
|
||
"marker_name": MARKER_NAMES.get(marker),
|
||
"type_code": type_code,
|
||
"type_name": public_type_name,
|
||
"strings_sample": sorted(evidence["strings"])[:30],
|
||
"guids_sample": sorted(evidence["guids"])[:20],
|
||
}
|
||
command_binding = form_button_command_binding(node, current_path)
|
||
if command_binding:
|
||
row["command_binding"] = command_binding
|
||
command_reference = form_command_reference(node, current_path)
|
||
if command_reference:
|
||
row["command_reference"] = command_reference
|
||
if command_reference.get("scope") == "form" and "command_binding" not in row:
|
||
row["command_binding"] = command_reference
|
||
data_path_index = 12 if marker in {"48", "73"} else 11
|
||
if marker in {"35", "37"} and str(row.get("name_path") or "").endswith(".7"):
|
||
data_path_index = 12
|
||
if marker == "31":
|
||
data_path_index = 9
|
||
if marker in {"31", "35", "37", "48", "55", "73"} and len(items) > data_path_index:
|
||
data_path_reference = data_path_reference_from_node(items[data_path_index])
|
||
if data_path_reference.get("attribute_id"):
|
||
row["data_path_attribute_id"] = data_path_reference["attribute_id"]
|
||
if data_path_reference.get("field_id"):
|
||
row["data_path_field_id"] = data_path_reference["field_id"]
|
||
if data_path_reference:
|
||
row["data_path_attribute_id_path"] = path_join(current_path, data_path_index)
|
||
if marker in {"35", "37"} and len(items) > 12:
|
||
footer_reference = footer_data_path_reference(items[11], items[12])
|
||
if footer_reference is not None:
|
||
row["_footer_data_path_reference"] = footer_reference
|
||
item_events = event_handlers_from_node(node, current_path, owner=name["value"], max_depth=3)
|
||
if item_events:
|
||
row["events"] = item_events
|
||
parameters = direct_parameters(node, current_path, roles=parameter_roles, limit=max_parameters)
|
||
row["semantic"] = form_item_semantic_properties(row, parameters)
|
||
enrich_container_specific_semantics(row, node)
|
||
enrich_table_addition_specific_semantics(row, node)
|
||
enrich_input_field_specific_semantics(row, node)
|
||
enrich_form_field_specific_semantics(row, node)
|
||
enrich_dynamic_list_specific_semantics(row, node)
|
||
enrich_table_specific_semantics(row, node)
|
||
enrich_command_button_specific_semantics(row, node)
|
||
enrich_decoration_specific_semantics(row, node)
|
||
enrich_radio_button_specific_semantics(row, node)
|
||
enrich_form_button_specific_semantics(row, node)
|
||
row["semantic"] = public_semantic(row["semantic"], include_diagnostics=include_parameters)
|
||
if include_parameters:
|
||
row["parameters"] = parameters
|
||
records.append(row)
|
||
supports_embedded_additions = marker in {"55", "73"} or (
|
||
marker == "22" and str(row.get("type_name") or "") == "Командная панель"
|
||
)
|
||
if supports_embedded_additions:
|
||
for child_index, child in enumerate(items):
|
||
addition = embedded_table_addition_row(
|
||
child,
|
||
path_join(current_path, child_index),
|
||
depth + 1,
|
||
include_parameters=include_parameters,
|
||
max_parameters=max_parameters,
|
||
)
|
||
if addition is None:
|
||
continue
|
||
total += 1
|
||
if len(records) < limit:
|
||
records.append(addition)
|
||
for index, child in enumerate(items):
|
||
walk(child, [*path, index], depth + 1)
|
||
|
||
root1 = get_by_path(tree, "1")
|
||
walk(root1, [1], 0)
|
||
enrich_item_reference_semantics(records)
|
||
public_rows_semantics(records, include_diagnostics=include_parameters)
|
||
return records[:limit], total, total > len(records)
|
||
|
||
|
||
def section_record_total(tree: Any, path: str) -> int:
|
||
node = get_by_path(tree, path)
|
||
return len(declared_child_records(node, path)) if node is not None else 0
|
||
|
||
|
||
def section_records(
|
||
tree: Any,
|
||
path: str,
|
||
category: str,
|
||
*,
|
||
limit: int = 500,
|
||
include_parameters: bool = True,
|
||
max_parameters: int = 80,
|
||
) -> list[dict[str, Any]]:
|
||
node = get_by_path(tree, path)
|
||
if node is None:
|
||
return []
|
||
result = []
|
||
for record in declared_child_records(node, path)[:limit]:
|
||
evidence = collect_evidence(record.node)
|
||
name = record_name(record.node, category=category)
|
||
title = localized_text(record.node, record.path)
|
||
item_id = record_id(record.node, record.path)
|
||
row = {
|
||
"category": category,
|
||
"index": record.index,
|
||
"path": record.path,
|
||
"name": name,
|
||
"id": item_id["value"] if item_id else None,
|
||
"id_path": item_id["path"] if item_id else None,
|
||
"title": title["value"] if title else None,
|
||
"title_lang": title.get("lang") if title else None,
|
||
"title_path": title["path"] if title else None,
|
||
"marker": child_scalar(record.node, 0),
|
||
"marker_name": MARKER_NAMES.get(child_scalar(record.node, 0) or ""),
|
||
"strings_sample": sorted(evidence["strings"])[:30],
|
||
"guids_sample": sorted(evidence["guids"])[:20],
|
||
}
|
||
parameters = direct_parameters(record.node, record.path, roles=SECTION_RECORD_PARAMETER_ROLES, limit=max_parameters)
|
||
row["semantic"] = section_record_semantic_properties(row, parameters, record.node)
|
||
row["semantic"] = public_semantic(row["semantic"], include_diagnostics=include_parameters)
|
||
if include_parameters:
|
||
row["parameters"] = parameters
|
||
if category == "Attribute":
|
||
dynamic_fields = dynamic_list_fields(record.node, owner_name=name, owner_path=record.path)
|
||
if dynamic_fields:
|
||
row["dynamic_list_fields"] = dynamic_fields
|
||
settings = dynamic_list_settings(record.node, owner_path=record.path)
|
||
if settings:
|
||
row["dynamic_list_settings"] = settings
|
||
result.append(row)
|
||
return result
|
||
|
||
|
||
def attribute_by_id(attributes: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
||
return {str(item.get("id")): item for item in attributes if item.get("id") is not None}
|
||
|
||
|
||
def dynamic_field_by_id(attributes: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
||
result: dict[str, dict[str, Any]] = {}
|
||
for attribute in attributes:
|
||
for field in attribute.get("dynamic_list_fields") or []:
|
||
field_id = field.get("id")
|
||
if field_id is None:
|
||
continue
|
||
result[str(field_id)] = field
|
||
return result
|
||
|
||
|
||
def dynamic_field_by_owner_and_id(attributes: list[dict[str, Any]]) -> dict[tuple[str, str], dict[str, Any]]:
|
||
result: dict[tuple[str, str], dict[str, Any]] = {}
|
||
for attribute in attributes:
|
||
owner_id = attribute.get("id")
|
||
if owner_id is None:
|
||
continue
|
||
for field in attribute.get("dynamic_list_fields") or []:
|
||
field_id = field.get("id")
|
||
if field_id is None:
|
||
continue
|
||
result[(str(owner_id), str(field_id))] = field
|
||
return result
|
||
|
||
|
||
def parameter_reference_id(parameter: dict[str, Any] | None) -> str | None:
|
||
if not parameter:
|
||
return None
|
||
values = parameter.get("values_sample") or []
|
||
numbers = [str(item.get("value")) for item in values if str(item.get("value") or "").isdigit()]
|
||
if numbers:
|
||
return numbers[-1]
|
||
value = parameter.get("value")
|
||
return str(value) if str(value or "").isdigit() else None
|
||
|
||
|
||
def enrich_item_data_paths(items: list[dict[str, Any]], attributes: list[dict[str, Any]]) -> None:
|
||
by_id = attribute_by_id(attributes)
|
||
by_name = {str(attribute.get("name") or ""): attribute for attribute in attributes if attribute.get("name")}
|
||
dynamic_by_id = dynamic_field_by_id(attributes)
|
||
dynamic_by_owner_and_id = dynamic_field_by_owner_and_id(attributes)
|
||
table_items = [
|
||
item
|
||
for item in items
|
||
if str(item.get("marker") or "") in {"55", "73"} and item.get("name") and item.get("path")
|
||
]
|
||
table_items.sort(key=lambda item: len(str(item.get("path") or "").split(".")), reverse=True)
|
||
|
||
def fallback_data_path(item: dict[str, Any]) -> tuple[str | None, dict[str, Any] | None, str | None]:
|
||
item_name = str(item.get("name") or "")
|
||
if not item_name:
|
||
return None, None, None
|
||
attribute = by_name.get(item_name)
|
||
if attribute is not None:
|
||
return item_name, attribute, "form_attribute_name_match"
|
||
item_path = str(item.get("path") or "")
|
||
for table_item in table_items:
|
||
table_path = str(table_item.get("path") or "")
|
||
table_name = str(table_item.get("name") or "")
|
||
if not table_path or not table_name or not item_path.startswith(table_path + ".") or not item_name.startswith(table_name):
|
||
continue
|
||
table_attribute = by_name.get(table_name)
|
||
field_name = item_name[len(table_name) :]
|
||
if not field_name:
|
||
continue
|
||
if table_attribute is not None:
|
||
for field in table_attribute.get("dynamic_list_fields") or []:
|
||
if field_name in {str(field.get("name") or ""), str(field.get("data_name") or "")}:
|
||
return str(field.get("path_to_data") or f"{table_name}.{field_name}"), table_attribute, "tabular_attribute_field_name_match"
|
||
object_attribute = by_name.get("Объект")
|
||
if object_attribute is not None:
|
||
public_field_name = FORM_PUBLIC_DATA_FIELD_NAMES.get(field_name, field_name)
|
||
return f"Объект.{table_name}.{public_field_name}", object_attribute, "object_tabular_item_name_match"
|
||
return None, None, None
|
||
|
||
for item in items:
|
||
marker = str(item.get("marker") or "")
|
||
if marker not in {"31", "35", "37", "48", "55", "73"}:
|
||
continue
|
||
parameters = item.get("parameters") or []
|
||
data_path_index = 9 if marker == "31" else (12 if marker in {"48", "73"} else 11)
|
||
reference = item.get("data_path_attribute_id") or parameter_reference_id(next((parameter for parameter in parameters if parameter.get("index") == data_path_index), None))
|
||
if not reference:
|
||
continue
|
||
field_id = item.get("data_path_field_id")
|
||
attribute = by_id.get(reference)
|
||
dynamic_field = dynamic_by_owner_and_id.get((str(reference), str(field_id))) if field_id else None
|
||
fallback_source = None
|
||
standard_field = FORM_STANDARD_DATA_FIELDS.get(str(field_id or ""))
|
||
if standard_field and attribute:
|
||
attribute_name = str(attribute.get("name") or "")
|
||
if str(field_id) == "-2" and str(item.get("name") or "") == "Номер":
|
||
standard_field = "Number"
|
||
if str(field_id) == "-3" and str(item.get("name") or "") in {"Дата", "Date"}:
|
||
standard_field = "Date"
|
||
if attribute_name in {"Запись", "Record"} and str(item.get("name") or "") in {"Период", "Period"}:
|
||
standard_field = "Period"
|
||
is_dynamic_list = bool(attribute.get("dynamic_list_fields") or attribute.get("dynamic_list_settings"))
|
||
path_to_data = (
|
||
f"Items.{attribute_name}.CurrentData.{standard_field}"
|
||
if is_dynamic_list
|
||
else f"{attribute_name}.{standard_field}"
|
||
)
|
||
dynamic_field = None
|
||
elif dynamic_field:
|
||
dynamic_name = dynamic_field.get("data_name") or dynamic_field.get("name")
|
||
if marker == "31" and attribute and attribute.get("dynamic_list_settings"):
|
||
path_to_data = f"Items.{attribute.get('name')}.CurrentData.{dynamic_name}"
|
||
else:
|
||
path_to_data = dynamic_field.get("path_to_data") or dynamic_name
|
||
elif attribute:
|
||
attribute_name = str(attribute.get("name") or "")
|
||
item_name = str(item.get("name") or "")
|
||
item_path = str(item.get("path") or "")
|
||
owner_table = next(
|
||
(
|
||
table_item
|
||
for table_item in table_items
|
||
if table_item is not item
|
||
and str(table_item.get("path") or "")
|
||
and item_path.startswith(str(table_item.get("path")) + ".")
|
||
),
|
||
None,
|
||
)
|
||
if attribute_name != "Объект" and item_name.endswith("ДатаНачала"):
|
||
path_to_data = f"{attribute_name}.StartDate"
|
||
elif attribute_name != "Объект" and item_name.endswith("ДатаОкончания"):
|
||
path_to_data = f"{attribute_name}.EndDate"
|
||
elif attribute_name in {"Запись", "Record"} and item_name and item_name != attribute_name:
|
||
path_to_data = f"{attribute_name}.{FORM_PUBLIC_DATA_FIELD_NAMES.get(item_name, item_name)}"
|
||
elif attribute_name != "Объект":
|
||
path_to_data = attribute_name
|
||
elif owner_table is not None:
|
||
table_name = str(owner_table.get("name") or "")
|
||
field_name = item_name[len(table_name) :] if item_name.startswith(table_name) else item_name
|
||
public_field_name = FORM_PUBLIC_DATA_FIELD_NAMES.get(field_name, field_name)
|
||
path_to_data = f"{attribute_name}.{table_name}.{public_field_name}"
|
||
elif item_name and item_name != attribute_name:
|
||
public_field_name = FORM_PUBLIC_DATA_FIELD_NAMES.get(item_name, item_name)
|
||
path_to_data = f"{attribute_name}.{public_field_name}"
|
||
else:
|
||
path_to_data = attribute_name
|
||
elif not field_id and dynamic_by_id.get(reference):
|
||
dynamic_field = dynamic_by_id.get(reference)
|
||
path_to_data = dynamic_field.get("path_to_data") or dynamic_field.get("data_name") or dynamic_field.get("name")
|
||
else:
|
||
path_to_data, attribute, fallback_source = fallback_data_path(item)
|
||
if not path_to_data:
|
||
continue
|
||
dynamic_field = None
|
||
item["path_to_data"] = path_to_data
|
||
if attribute and attribute.get("dynamic_list_settings"):
|
||
item["dynamic_list_settings"] = attribute.get("dynamic_list_settings")
|
||
semantic = item.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
add_grouped_property(
|
||
groups,
|
||
"Основные",
|
||
semantic_property(
|
||
"ПутьКДанным",
|
||
path_to_data,
|
||
source=fallback_source or ("standard_data_field_reference" if standard_field else ("dynamic_list_field_reference" if dynamic_field and not attribute else "form_attribute_reference")),
|
||
),
|
||
)
|
||
for item in items:
|
||
pending = item.pop("_type_link_reference", None)
|
||
if not isinstance(pending, dict):
|
||
continue
|
||
owner_id = str(item.get("data_path_attribute_id") or "")
|
||
field_id = str(pending.get("field_id") or "")
|
||
attribute = by_id.get(owner_id)
|
||
target = dynamic_by_owner_and_id.get((owner_id, field_id))
|
||
owner_name = str((attribute or {}).get("name") or "")
|
||
field_name = str((target or {}).get("data_name") or (target or {}).get("name") or "")
|
||
if not owner_name or not field_name:
|
||
continue
|
||
semantic = item.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
add_grouped_property(
|
||
groups,
|
||
"Использование",
|
||
semantic_property(
|
||
"TypeLink",
|
||
{
|
||
"path_to_data": f"Items.{owner_name}.CurrentData.{field_name}",
|
||
"link_item": int(pending.get("link_item") or 0),
|
||
},
|
||
index=42,
|
||
source="form_payload_type_link_reference",
|
||
),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 42)
|
||
for item in items:
|
||
pending = item.pop("_footer_data_path_reference", None)
|
||
if not isinstance(pending, dict) or pending.get("aggregate") != "Total":
|
||
continue
|
||
path_to_data = str(item.get("path_to_data") or "")
|
||
if "." not in path_to_data and pending.get("primary_aggregate"):
|
||
field_guid = str(pending.get("field_guid") or "")
|
||
candidates = {
|
||
str(candidate.get("path_to_data") or "")
|
||
for candidate in items
|
||
if candidate is not item
|
||
and field_guid
|
||
and field_guid in {str(value).lower() for value in candidate.get("guids_sample") or []}
|
||
and "." in str(candidate.get("path_to_data") or "")
|
||
}
|
||
if candidates:
|
||
deepest = max(candidate.count(".") for candidate in candidates)
|
||
deepest_candidates = {candidate for candidate in candidates if candidate.count(".") == deepest}
|
||
if len(deepest_candidates) == 1:
|
||
path_to_data = deepest_candidates.pop()
|
||
if "." not in path_to_data:
|
||
continue
|
||
owner_path, field_name = path_to_data.rsplit(".", 1)
|
||
if not owner_path or not field_name:
|
||
continue
|
||
semantic = item.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
total_path = f"{owner_path}.Total{field_name}"
|
||
property_name = "ПутьКДанным" if pending.get("primary_aggregate") else "FooterDataPath"
|
||
if pending.get("primary_aggregate"):
|
||
item["path_to_data"] = total_path
|
||
add_grouped_property(
|
||
groups,
|
||
"Основные",
|
||
semantic_property(property_name, total_path, index=12, source="form_payload_footer_data_reference"),
|
||
)
|
||
mark_semantic_parameter_mapped(semantic, 12)
|
||
|
||
|
||
def enrich_page_title_data_paths(items: list[dict[str, Any]]) -> None:
|
||
"""Resolve a page title counter from its single descendant table.
|
||
|
||
Managed-form pages encode this through internal object-field GUIDs. The
|
||
descendant table has already been resolved to a public data path, so the
|
||
public ``RowsCount`` path can be derived without exposing storage IDs.
|
||
"""
|
||
tables = [
|
||
item
|
||
for item in items
|
||
if str(item.get("marker") or "") in {"55", "73"}
|
||
and item.get("path_to_data")
|
||
and item.get("path")
|
||
]
|
||
for page in items:
|
||
if str(page.get("type_name") or "") != "Страница":
|
||
continue
|
||
page_path = str(page.get("path") or "")
|
||
if not page_path:
|
||
continue
|
||
descendant_tables = [
|
||
table for table in tables if str(table.get("path") or "").startswith(page_path + ".")
|
||
]
|
||
if len(descendant_tables) != 1:
|
||
continue
|
||
table_path = str(descendant_tables[0].get("path_to_data") or "")
|
||
if not table_path:
|
||
continue
|
||
semantic = page.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
add_grouped_property(
|
||
groups,
|
||
"Основные",
|
||
semantic_property(
|
||
"TitleDataPath",
|
||
f"{table_path}.RowsCount",
|
||
source="page_single_descendant_table",
|
||
),
|
||
)
|
||
|
||
|
||
def enrich_item_event_links(items: list[dict[str, Any]], module: dict[str, Any] | None) -> None:
|
||
names = routine_names(module)
|
||
for item in items:
|
||
events = item.get("events") or []
|
||
if not events:
|
||
continue
|
||
links = []
|
||
semantic = item.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
for event in events:
|
||
handler = str(event.get("handler") or "")
|
||
resolved = handler.casefold() in names
|
||
event_presentation = str(event.get("event_name") or "")
|
||
item_name = str(item.get("name") or "")
|
||
if not event_presentation and item_name and handler.casefold().startswith(item_name.casefold()):
|
||
event_presentation = handler[len(item_name) :] or "Обработчик"
|
||
add_grouped_property(
|
||
groups,
|
||
"События",
|
||
semantic_property(event_presentation or "Обработчик", handler, source="element_event_handler"),
|
||
)
|
||
links.append(
|
||
{
|
||
"kind": "element_event",
|
||
"element": item.get("name"),
|
||
"event_name": event_presentation or event.get("event_name"),
|
||
"handler": handler,
|
||
"status": "resolved" if resolved else "missing",
|
||
}
|
||
)
|
||
item["handler_links"] = links
|
||
|
||
|
||
def dynamic_list_fields(node: Any, *, owner_name: str | None, owner_path: str | None = None) -> list[dict[str, Any]]:
|
||
values = atoms(node, limit=20000)
|
||
fields: dict[str, dict[str, Any]] = {}
|
||
index = 0
|
||
typed_prefixes = {"S", "N", "B", "U", "#"}
|
||
while index < len(values) - 1:
|
||
key = values[index]
|
||
value_offset = 2 if values[index + 1] in typed_prefixes and index + 2 < len(values) else 1
|
||
value = values[index + value_offset]
|
||
match = re.fullmatch(r"FieldsMapItem(Id|Name|SecondaryName)(\d+)", key or "")
|
||
if match:
|
||
field = fields.setdefault(match.group(2), {})
|
||
if match.group(1) == "Id":
|
||
field["id"] = value
|
||
elif match.group(1) == "Name":
|
||
field["data_name"] = value
|
||
elif match.group(1) == "SecondaryName":
|
||
field["name"] = value
|
||
index += 1
|
||
result = []
|
||
seen_ids: set[str] = set()
|
||
for ordinal in sorted(fields, key=lambda item: int(item)):
|
||
field = fields[ordinal]
|
||
name = field.get("name") or field.get("data_name")
|
||
if not name:
|
||
continue
|
||
data_name = field.get("data_name")
|
||
field_id = field.get("id")
|
||
if field_id is not None:
|
||
seen_ids.add(str(field_id))
|
||
result.append(
|
||
{
|
||
"name": name,
|
||
"data_name": data_name,
|
||
"id": field_id,
|
||
"path_to_data": f"{owner_name}.{data_name}" if owner_name and data_name else data_name,
|
||
"ordinal": int(ordinal),
|
||
"source": "dynamic_list_field_map",
|
||
}
|
||
)
|
||
for index, child in enumerate(children(node)):
|
||
if child_scalar(child, 0) != "5":
|
||
continue
|
||
field_id = child_scalar(child, 1)
|
||
name = child_scalar(child, 3)
|
||
if not field_id or not name or str(field_id) in seen_ids:
|
||
continue
|
||
field_path = path_join(owner_path, index) if owner_path else None
|
||
title = localized_text(child, field_path or "")
|
||
result.append(
|
||
{
|
||
"name": name,
|
||
"data_name": name,
|
||
"id": field_id,
|
||
"title": title.get("value") if title else None,
|
||
"title_lang": title.get("lang") if title else None,
|
||
"title_path": title.get("path") if title else None,
|
||
**({"path": field_path} if field_path else {}),
|
||
"path_to_data": f"{owner_name}.{name}" if owner_name and name else name,
|
||
"ordinal": len(result),
|
||
"source": "tabular_attribute_field",
|
||
}
|
||
)
|
||
seen_ids.add(str(field_id))
|
||
return result
|
||
|
||
|
||
DYNAMIC_LIST_TYPED_PREFIXES = {"S", "N", "B", "U", "#"}
|
||
DYNAMIC_LIST_SETTING_ALIASES = {
|
||
"main_table": {"основнаятаблица", "maintable", "source_table", "sourcetable"},
|
||
"custom_query": {"произвольныйзапрос", "customquery", "arbitraryquery"},
|
||
"query_text": {"текстзапроса", "querytext", "запрос", "query"},
|
||
}
|
||
|
||
|
||
def dynamic_list_bool(value: str | None) -> bool | str | None:
|
||
if value == "1":
|
||
return True
|
||
if value == "0":
|
||
return False
|
||
return value if value not in {None, ""} else None
|
||
|
||
|
||
def next_dynamic_list_value(entries: list[dict[str, Any]], index: int) -> dict[str, Any] | None:
|
||
next_index = index + 1
|
||
if next_index < len(entries) and entries[next_index].get("value") in DYNAMIC_LIST_TYPED_PREFIXES:
|
||
next_index += 1
|
||
if next_index < len(entries):
|
||
return entries[next_index]
|
||
return None
|
||
|
||
|
||
def dynamic_list_settings(node: Any, *, owner_path: str | None = None) -> dict[str, Any] | None:
|
||
entries = node_scalar_entries(node, owner_path or "", limit=30000)
|
||
settings: dict[str, Any] = {}
|
||
paths: dict[str, str] = {}
|
||
sources: dict[str, str] = {}
|
||
for index, entry in enumerate(entries):
|
||
key = normalize_key(entry.get("value"))
|
||
target = next((name for name, aliases in DYNAMIC_LIST_SETTING_ALIASES.items() if key in aliases), None)
|
||
if not target:
|
||
continue
|
||
value_entry = next_dynamic_list_value(entries, index)
|
||
if not value_entry:
|
||
continue
|
||
value = value_entry.get("value")
|
||
settings[target] = dynamic_list_bool(value) if target == "custom_query" else value
|
||
paths[target] = str(value_entry.get("path") or "")
|
||
sources[target] = "key_value"
|
||
if "query_text" not in settings:
|
||
query_entry = next(
|
||
(
|
||
entry
|
||
for entry in entries
|
||
if isinstance(entry.get("value"), str)
|
||
and re.search(r"(?i)\bselect\b|выбрать", str(entry.get("value") or ""))
|
||
),
|
||
None,
|
||
)
|
||
if query_entry:
|
||
settings["query_text"] = query_entry.get("value")
|
||
paths["query_text"] = str(query_entry.get("path") or "")
|
||
sources["query_text"] = "query_text_heuristic"
|
||
if not settings:
|
||
return None
|
||
result = {
|
||
"main_table": settings.get("main_table"),
|
||
"custom_query": settings.get("custom_query"),
|
||
"query_text": settings.get("query_text"),
|
||
"paths": paths,
|
||
"sources": sources,
|
||
"status": "ok",
|
||
}
|
||
if settings.get("custom_query") is True and not settings.get("query_text"):
|
||
result["status"] = "query_text_missing"
|
||
return {key: value for key, value in result.items() if value is not None and value != "" and value != {} and value != []}
|
||
|
||
|
||
def normalize_key(value: Any) -> str:
|
||
return re.sub(r"[\s._-]+", "", str(value or "")).casefold()
|
||
|
||
|
||
def dynamic_list_column_items(attributes: list[dict[str, Any]], *, limit: int = 5000) -> tuple[list[dict[str, Any]], int, bool]:
|
||
records: list[dict[str, Any]] = []
|
||
total = 0
|
||
for attribute in attributes:
|
||
owner_name = attribute.get("name")
|
||
for field in attribute.get("dynamic_list_fields") or []:
|
||
total += 1
|
||
if len(records) >= limit:
|
||
continue
|
||
name = field.get("name")
|
||
path_to_data = field.get("path_to_data")
|
||
semantic = {
|
||
"groups": {
|
||
"Основные": [
|
||
semantic_property("Идентификатор", field.get("id"), source="dynamic_list_field_map"),
|
||
semantic_property("Имя", name, source="dynamic_list_field_map"),
|
||
semantic_property("Заголовок", name, source="dynamic_list_field_map"),
|
||
semantic_property("Вид", "Колонка динамического списка", source="dynamic_list_field_map"),
|
||
semantic_property("ПутьКДанным", path_to_data, source="dynamic_list_field_map"),
|
||
]
|
||
},
|
||
"coverage": {"mapped": 5, "unmapped": 0, "total": 5, "status": "ok"},
|
||
}
|
||
records.append(
|
||
{
|
||
"name": name,
|
||
"id": field.get("id"),
|
||
"title": name,
|
||
"marker": "dynamic_list_field",
|
||
"marker_name": "DynamicListField",
|
||
"type_code": "dynamic_list_field",
|
||
"type_name": "Колонка динамического списка",
|
||
"owner": owner_name,
|
||
"data_name": field.get("data_name"),
|
||
"path_to_data": path_to_data,
|
||
"semantic": semantic,
|
||
}
|
||
)
|
||
return records, total, total > len(records)
|
||
|
||
|
||
def additional_column_items(tree: Any, items: list[dict[str, Any]], *, limit: int = 5000) -> tuple[list[dict[str, Any]], int, bool]:
|
||
"""Decode managed-form AdditionalColumns stored after declared attributes."""
|
||
section = child_at(tree, 3)
|
||
section_items = children(section)
|
||
declared = child_direct_scalar(section, 1)
|
||
declared_count = int(declared) if str(declared or "").isdigit() else 0
|
||
tail_start = min(len(section_items), 2 + declared_count)
|
||
table_items = [
|
||
item
|
||
for item in items
|
||
if str(item.get("marker") or "") in {"55", "73"} and item.get("path_to_data")
|
||
]
|
||
records: list[dict[str, Any]] = []
|
||
total = 0
|
||
for group_index, group in enumerate(section_items[tail_start:], start=tail_start):
|
||
if child_direct_scalar(group, 0) != "0":
|
||
continue
|
||
group_items = children(group)
|
||
count_text = child_direct_scalar(group, 2)
|
||
column_count = int(count_text) if str(count_text or "").isdigit() else 0
|
||
owner_guids = guids(child_at(group, 1), limit=4)
|
||
owner_guid = owner_guids[-1] if owner_guids else ""
|
||
owners = [
|
||
item
|
||
for item in table_items
|
||
if owner_guid and owner_guid in {str(value).lower() for value in item.get("guids_sample") or []}
|
||
]
|
||
if len(owners) != 1:
|
||
continue
|
||
owner = owners[0]
|
||
owner_path = str(owner.get("path_to_data") or "")
|
||
for offset, column in enumerate(group_items[3 : 3 + column_count], start=3):
|
||
if child_direct_scalar(column, 0) != "5":
|
||
continue
|
||
name = child_direct_scalar(column, 3)
|
||
if not name:
|
||
continue
|
||
total += 1
|
||
if len(records) >= limit:
|
||
continue
|
||
column_id = child_direct_scalar(column, 1)
|
||
path_to_data = f"{owner_path}.{name}"
|
||
records.append(
|
||
{
|
||
"name": name,
|
||
"id": column_id,
|
||
"marker": "additional_column",
|
||
"marker_name": "AdditionalColumn",
|
||
"type_code": "additional_column",
|
||
"type_name": "Колонка реквизита",
|
||
"owner": owner.get("name"),
|
||
"owner_guid": owner_guid,
|
||
"path": f"3.{group_index}.{offset}",
|
||
"path_to_data": path_to_data,
|
||
"semantic": {
|
||
"groups": {
|
||
"Основные": [
|
||
semantic_property("Идентификатор", column_id, source="form_payload_additional_column"),
|
||
semantic_property("Имя", name, source="form_payload_additional_column"),
|
||
semantic_property("Вид", "Колонка реквизита", source="form_payload_additional_column"),
|
||
semantic_property("ПутьКДанным", path_to_data, source="form_payload_additional_column"),
|
||
]
|
||
},
|
||
"coverage": {"mapped": 4, "unmapped": 0, "total": 4, "status": "ok"},
|
||
},
|
||
}
|
||
)
|
||
return records, total, total > len(records)
|
||
|
||
|
||
def form_common_parameters(tree: Any, *, limit: int = 120) -> list[dict[str, Any]]:
|
||
node = get_by_path(tree, "1")
|
||
if node is None:
|
||
return []
|
||
parameters = direct_parameters(
|
||
node,
|
||
"1",
|
||
roles={
|
||
0: "Версия/тип формы",
|
||
1: "Основные свойства формы",
|
||
27: "События формы",
|
||
},
|
||
limit=limit,
|
||
)
|
||
for parameter in parameters:
|
||
index = parameter.get("index")
|
||
typed_node = child_at(node, index) if isinstance(index, int) else None
|
||
typed_marker = child_direct_scalar(typed_node, 0)
|
||
if typed_marker == "#":
|
||
parameter["typed_guid"] = child_direct_scalar(typed_node, 1)
|
||
parameter["typed_value"] = child_direct_scalar(typed_node, 2)
|
||
parameter["typed_kind"] = "enum"
|
||
elif typed_marker == "B":
|
||
parameter["typed_value"] = child_direct_scalar(typed_node, 1)
|
||
parameter["typed_kind"] = "boolean"
|
||
return parameters
|
||
|
||
|
||
def form_common_semantic(parameters: list[dict[str, Any]], *, include_diagnostics: bool = True) -> dict[str, Any]:
|
||
by_index = {int(item["index"]): item for item in parameters if isinstance(item, dict) and isinstance(item.get("index"), int)}
|
||
form_version = str((by_index.get(0) or {}).get("value") or "")
|
||
auto_save_raw = str((by_index.get(7) or {}).get("value") or "")
|
||
auto_save_data_in_settings = {"0": "DontUse", "1": "Use"}.get(auto_save_raw)
|
||
group_indices = [11, 40, 47, 57]
|
||
group_values = [str((by_index.get(index) or {}).get("value") or "") for index in group_indices]
|
||
form_group = None
|
||
if group_values == ["0", "0", "0", "0"]:
|
||
form_group = "Vertical"
|
||
elif group_values == ["1", "1", "1", "1"]:
|
||
form_group = "Horizontal"
|
||
elif group_values == ["1", "1", "3", "3"]:
|
||
form_group = "AlwaysHorizontal"
|
||
elif group_values == ["1", "2", "2", "2"]:
|
||
form_group = "HorizontalIfPossible"
|
||
elif form_version in {"49", "50"} and group_values[0] == "0":
|
||
form_group = "Vertical"
|
||
window_primary = str((by_index.get(2) or {}).get("value") or "")
|
||
window_companion = str((by_index.get(54) or {}).get("value") or "")
|
||
window_opening_mode = None
|
||
if (window_primary, window_companion) == ("0", "0"):
|
||
window_opening_mode = "DontBlock"
|
||
elif (window_primary, window_companion) == ("1", "1"):
|
||
window_opening_mode = "LockOwner"
|
||
elif (window_primary, window_companion) == ("2", "2"):
|
||
window_opening_mode = "LockWholeInterface"
|
||
elif form_version in {"49", "50"}:
|
||
window_opening_mode = {"0": "DontBlock", "1": "LockOwner"}.get(window_primary)
|
||
show_primary = str((by_index.get(17) or {}).get("value") or "")
|
||
show_companion = str((by_index.get(56) or {}).get("value") or "")
|
||
show_command_bar = None
|
||
if (show_primary, show_companion) == ("0", "0"):
|
||
show_command_bar = False
|
||
elif show_companion == "1" and show_primary in {"2", "3"}:
|
||
show_command_bar = True
|
||
command_bar_location = {
|
||
("0", "0"): "None",
|
||
("2", "1"): "Top",
|
||
("3", "1"): "Bottom",
|
||
}.get((show_primary, show_companion))
|
||
if form_version in {"49", "50"} and show_primary == "0":
|
||
show_command_bar = False
|
||
command_bar_location = "None"
|
||
auto_title = bool_presentation(str((by_index.get(9) or {}).get("value") or "")) if form_version in {"49", "50"} else None
|
||
vertical_scroll = "useIfNecessary" if form_version == "49" and str((by_index.get(36) or {}).get("value") or "") == "2" else None
|
||
use_for_raw = str((by_index.get(20) or {}).get("typed_value") or "")
|
||
use_for_guid = str((by_index.get(20) or {}).get("typed_guid") or "")
|
||
use_for_folders_and_items = choice_folders_and_items_presentation(use_for_raw) if use_for_guid == "59ef2b80-c86b-11d5-a3c1-0050bae0a776" else None
|
||
auto_time = "CurrentOrLast" if use_for_guid == "adeb08a0-415c-11d6-b9d1-0050bae0a95d" and use_for_raw == "3" else None
|
||
posting_parameter = by_index.get(22) or {}
|
||
use_posting_mode = (
|
||
"Auto"
|
||
if str(posting_parameter.get("typed_guid") or "") == "20d89b09-bd04-4304-a8c7-4d07fac6338a"
|
||
and str(posting_parameter.get("typed_value") or "") == "3"
|
||
else None
|
||
)
|
||
repost_parameter = by_index.get(24) or {}
|
||
repost_on_write = bool_presentation(str(repost_parameter.get("typed_value") or "")) if repost_parameter.get("typed_kind") == "boolean" else None
|
||
mapped_indexes: set[int] = set()
|
||
groups: dict[str, list[dict[str, Any]]] = {}
|
||
if form_group is not None:
|
||
mapped_indexes.update(group_indices)
|
||
groups.setdefault("Основные", []).append(
|
||
{
|
||
"name": "Группировка",
|
||
"value": form_group,
|
||
"source": "controlled_designer_form_root_group",
|
||
"status": "ok",
|
||
"parameter_indices": group_indices,
|
||
"parameter_values": group_values,
|
||
"write_shape": "composite_scalar",
|
||
}
|
||
)
|
||
if auto_save_data_in_settings is not None:
|
||
mapped_indexes.add(7)
|
||
groups.setdefault("Основные", []).append(
|
||
{
|
||
"name": "АвтоСохранениеДанныхВНастройках",
|
||
"value": auto_save_data_in_settings,
|
||
"source": "controlled_designer_form_auto_save_data_in_settings",
|
||
"status": "ok",
|
||
"parameter_indices": [7],
|
||
"parameter_values": [auto_save_raw],
|
||
"write_shape": "scalar_enum",
|
||
}
|
||
)
|
||
if window_opening_mode is not None:
|
||
mapped_indexes.update({2, 54})
|
||
groups.setdefault("Основные", []).append(
|
||
{
|
||
"name": "РежимОткрытияОкна",
|
||
"value": window_opening_mode,
|
||
"source": "controlled_designer_form_window_opening_mode",
|
||
"status": "ok",
|
||
"parameter_indices": [2, 54],
|
||
"parameter_values": [window_primary, window_companion],
|
||
"write_shape": "paired_scalar",
|
||
}
|
||
)
|
||
if show_command_bar is not None:
|
||
mapped_indexes.update({17, 56})
|
||
groups.setdefault("Основные", []).append(
|
||
{
|
||
"name": "ОтображатьКоманднуюПанель",
|
||
"value": show_command_bar,
|
||
"source": "controlled_designer_form_show_command_bar",
|
||
"status": "ok",
|
||
"parameter_indices": [17, 56],
|
||
"parameter_values": [show_primary, show_companion],
|
||
"write_shape": "paired_scalar",
|
||
}
|
||
)
|
||
if command_bar_location is not None:
|
||
mapped_indexes.update({17, 56})
|
||
groups.setdefault("Основные", []).append(
|
||
{
|
||
"name": "ПоложениеКоманднойПанели",
|
||
"value": command_bar_location,
|
||
"source": "controlled_designer_form_command_bar_location",
|
||
"status": "ok",
|
||
"parameter_indices": [17, 56],
|
||
"parameter_values": [show_primary, show_companion],
|
||
"write_shape": "paired_scalar_shared",
|
||
}
|
||
)
|
||
if use_for_folders_and_items is not None:
|
||
mapped_indexes.add(20)
|
||
groups.setdefault("Основные", []).append(
|
||
{
|
||
"name": "UseForFoldersAndItems",
|
||
"value": use_for_folders_and_items,
|
||
"source": "live_sql_typed_form_enum",
|
||
"status": "ok",
|
||
"parameter_index": 20,
|
||
"parameter_value": use_for_raw,
|
||
"write_shape": "typed_enum_read_only",
|
||
}
|
||
)
|
||
if auto_title is not None:
|
||
mapped_indexes.add(9)
|
||
groups.setdefault("Прочее", []).append(
|
||
{
|
||
"name": "AutoTitle",
|
||
"value": auto_title,
|
||
"source": "form_payload_root_versioned",
|
||
"status": "ok",
|
||
"parameter_index": 9,
|
||
"write_shape": "scalar_boolean_read_only",
|
||
}
|
||
)
|
||
if vertical_scroll is not None:
|
||
mapped_indexes.add(36)
|
||
groups.setdefault("Прочее", []).append(
|
||
{
|
||
"name": "VerticalScroll",
|
||
"value": vertical_scroll,
|
||
"source": "form_payload_root_versioned",
|
||
"status": "ok",
|
||
"parameter_index": 36,
|
||
"write_shape": "scalar_enum_read_only",
|
||
}
|
||
)
|
||
for name, value, index, source in (
|
||
("AutoTime", auto_time, 20, "live_sql_typed_form_enum"),
|
||
("UsePostingMode", use_posting_mode, 22, "live_sql_typed_form_enum"),
|
||
("RepostOnWrite", repost_on_write, 24, "live_sql_typed_form_boolean"),
|
||
):
|
||
if value is None:
|
||
continue
|
||
mapped_indexes.add(index)
|
||
groups.setdefault("Прочее", []).append(
|
||
{
|
||
"name": name,
|
||
"value": value,
|
||
"source": source,
|
||
"status": "ok",
|
||
"parameter_index": index,
|
||
"write_shape": "typed_read_only",
|
||
}
|
||
)
|
||
semantic = {
|
||
"groups": groups,
|
||
"coverage": semantic_coverage(parameters, mapped_indexes),
|
||
"unmapped_parameters": semantic_unmapped_parameters(parameters, mapped_indexes),
|
||
}
|
||
return public_semantic(semantic, include_diagnostics=include_diagnostics)
|
||
|
||
|
||
def enrich_form_common_semantic(form_semantic: dict[str, Any], items: list[dict[str, Any]]) -> None:
|
||
command_bar = next(
|
||
(
|
||
item
|
||
for item in items
|
||
if isinstance(item, dict)
|
||
and str(item.get("id") or "") == "-1"
|
||
and str(item.get("type_name") or "") == "Командная панель"
|
||
and item.get("name")
|
||
),
|
||
None,
|
||
)
|
||
if command_bar is None:
|
||
return
|
||
groups = form_semantic.setdefault("groups", {})
|
||
add_grouped_property(
|
||
groups,
|
||
"Основные",
|
||
semantic_property(
|
||
"АвтоКоманднаяПанель",
|
||
command_bar.get("name"),
|
||
source="form_item_reference:id=-1",
|
||
),
|
||
)
|
||
|
||
|
||
def module_summary(tree: Any, *, include_text: bool = False) -> dict[str, Any] | None:
|
||
text = scalar(get_by_path(tree, "2"))
|
||
if not text:
|
||
return None
|
||
routines = [{"kind": match.group(1), "name": match.group(2)} for match in BSL_ROUTINE_RE.finditer(text)]
|
||
result: dict[str, Any] = {
|
||
"path": "2",
|
||
"bytes_estimate": len(text.encode("utf-8")),
|
||
"chars": len(text),
|
||
"routine_count": len(routines),
|
||
"routine_names": [str(item.get("name") or "") for item in routines if item.get("name")],
|
||
"routines_sample": routines[:80],
|
||
"text_preview": text[:500],
|
||
}
|
||
if include_text:
|
||
result["text"] = text
|
||
return result
|
||
|
||
|
||
def routine_names(module: dict[str, Any] | None) -> set[str]:
|
||
summary = module or {}
|
||
names = {str(value or "").casefold() for value in summary.get("routine_names") or [] if value}
|
||
names.update(
|
||
str(item.get("name") or "").casefold()
|
||
for item in summary.get("routines_sample") or []
|
||
if isinstance(item, dict) and item.get("name")
|
||
)
|
||
return names
|
||
|
||
|
||
def handler_links(events: list[dict[str, Any]], module: dict[str, Any] | None) -> list[dict[str, Any]]:
|
||
names = routine_names(module)
|
||
links = []
|
||
for event in events:
|
||
handler = str(event.get("handler") or "")
|
||
links.append(
|
||
{
|
||
"kind": "form_event",
|
||
"event_name": event.get("event_name"),
|
||
"handler": handler,
|
||
"status": "resolved" if handler.casefold() in names else "missing",
|
||
"event_path": event.get("path"),
|
||
}
|
||
)
|
||
return links
|
||
|
||
|
||
def command_handler_links(commands: list[dict[str, Any]], module: dict[str, Any] | None) -> list[dict[str, Any]]:
|
||
names = routine_names(module)
|
||
links = []
|
||
for command in commands:
|
||
handler = str(command.get("action") or command.get("name") or "")
|
||
if not handler:
|
||
continue
|
||
resolved = handler.casefold() in names
|
||
action_is_explicit = command.get("action_source") == "form_payload_parameter_8"
|
||
status = "resolved" if resolved else "missing" if action_is_explicit else "no_handler_expected"
|
||
links.append(
|
||
{
|
||
"kind": "form_command",
|
||
"command": command.get("name"),
|
||
"handler": handler,
|
||
"status": status,
|
||
"match_by": "action" if action_is_explicit else "command_name",
|
||
**(
|
||
{}
|
||
if resolved
|
||
else {
|
||
"diagnostics": {
|
||
"message": (
|
||
"Явный обработчик Action не найден в декодированном модуле формы."
|
||
if action_is_explicit
|
||
else "Явный BSL-обработчик с именем команды не найден; команда может быть платформенной или декларативной."
|
||
)
|
||
}
|
||
}
|
||
),
|
||
"command_path": command.get("path"),
|
||
"command_guid": (command.get("guids_sample") or [None])[0],
|
||
}
|
||
)
|
||
return links
|
||
|
||
|
||
def button_command_links(items: list[dict[str, Any]], commands: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||
command_by_guid = {}
|
||
command_by_id = {str(command.get("id") or ""): command for command in commands if command.get("id") is not None}
|
||
for command in commands:
|
||
for guid in command.get("guids_sample") or []:
|
||
command_by_guid[str(guid).lower()] = command
|
||
links = []
|
||
for item in items:
|
||
if item.get("marker") not in {"31", "34"}:
|
||
continue
|
||
binding = item.get("command_binding") if isinstance(item.get("command_binding"), dict) else None
|
||
if binding and binding.get("command_name"):
|
||
links.append(
|
||
{
|
||
"kind": "command_button",
|
||
"button": item.get("name"),
|
||
"command": binding.get("command_name"),
|
||
"command_name": binding.get("command_name"),
|
||
"status": "resolved",
|
||
"match_by": binding.get("match_by"),
|
||
"button_path": item.get("path"),
|
||
"command_path": binding.get("path"),
|
||
"command_guid": binding.get("group_guid"),
|
||
"command_binding": binding,
|
||
}
|
||
)
|
||
continue
|
||
if binding and binding.get("scope") == "form":
|
||
command = command_by_id.get(str(binding.get("command_id") or ""))
|
||
if command is not None:
|
||
links.append(
|
||
{
|
||
"kind": "command_button",
|
||
"button": item.get("name"),
|
||
"command": command.get("name"),
|
||
"command_name": f"Form.Command.{command.get('name')}",
|
||
"status": "resolved",
|
||
"match_by": binding.get("match_by"),
|
||
"button_path": item.get("path"),
|
||
"command_path": command.get("path"),
|
||
"command_guid": binding.get("group_guid"),
|
||
"command_binding": binding,
|
||
}
|
||
)
|
||
continue
|
||
command_reference = item.get("command_reference") if isinstance(item.get("command_reference"), dict) else None
|
||
if command_reference:
|
||
reference_key = (str(command_reference.get("guid") or "").lower(), str(command_reference.get("code") or ""))
|
||
suffix = FORM_GRAPHICAL_SCHEMA_STANDARD_COMMANDS.get(
|
||
reference_key
|
||
)
|
||
graphical_fields = [candidate for candidate in items if candidate.get("type_name") == "GraphicalSchemaField"]
|
||
if suffix and len(graphical_fields) == 1:
|
||
owner_name = str(graphical_fields[0].get("name") or "")
|
||
command_name = f"Form.Item.{owner_name}.StandardCommand.{suffix}"
|
||
links.append(
|
||
{
|
||
"kind": "command_button",
|
||
"button": item.get("name"),
|
||
"command": command_name,
|
||
"command_name": command_name,
|
||
"status": "resolved",
|
||
"match_by": "graphical_schema_standard_command_guid",
|
||
"button_path": item.get("path"),
|
||
"command_path": command_reference.get("path"),
|
||
"command_guid": command_reference.get("guid"),
|
||
}
|
||
)
|
||
continue
|
||
suffix = FORM_TABLE_STANDARD_COMMANDS.get(reference_key[0])
|
||
table_fields = [
|
||
candidate
|
||
for candidate in items
|
||
if candidate.get("type_name") in {"Динамический список", "Таблица формы"}
|
||
and str(candidate.get("id") or "") == reference_key[1]
|
||
]
|
||
if suffix and len(table_fields) == 1:
|
||
owner_name = str(table_fields[0].get("name") or "")
|
||
command_name = f"Form.Item.{owner_name}.StandardCommand.{suffix}"
|
||
links.append(
|
||
{
|
||
"kind": "command_button",
|
||
"button": item.get("name"),
|
||
"command": command_name,
|
||
"command_name": command_name,
|
||
"status": "resolved",
|
||
"match_by": "table_standard_command_guid",
|
||
"button_path": item.get("path"),
|
||
"command_path": command_reference.get("path"),
|
||
"command_guid": command_reference.get("guid"),
|
||
}
|
||
)
|
||
continue
|
||
object_command = FORM_OBJECT_COMMANDS.get(reference_key)
|
||
query_texts = [
|
||
str((candidate.get("dynamic_list_settings") or {}).get("query_text") or "")
|
||
for candidate in items
|
||
if isinstance(candidate.get("dynamic_list_settings"), dict)
|
||
]
|
||
public_refs = {
|
||
f"Task.{match.group(1)}"
|
||
for query_text in query_texts
|
||
for match in re.finditer(r"(?:Задача|Task)\.([A-Za-zА-Яа-яЁё_][\wА-Яа-яЁё]*)", query_text)
|
||
}
|
||
if object_command and len(public_refs) == 1:
|
||
command_name = f"{next(iter(public_refs))}.Command.{object_command}"
|
||
links.append(
|
||
{
|
||
"kind": "command_button",
|
||
"button": item.get("name"),
|
||
"command": command_name,
|
||
"command_name": command_name,
|
||
"status": "resolved",
|
||
"match_by": "object_command_guid_and_dynamic_query",
|
||
"button_path": item.get("path"),
|
||
"command_path": command_reference.get("path"),
|
||
"command_guid": command_reference.get("guid"),
|
||
}
|
||
)
|
||
continue
|
||
if object_command:
|
||
item_name = str(item.get("name") or "")
|
||
name_match = re.search(
|
||
rf"(?:Задача|Task)([A-Za-zА-Яа-яЁё_][\wА-Яа-яЁё]*?){re.escape(object_command)}$",
|
||
item_name,
|
||
)
|
||
if name_match:
|
||
command_name = f"Task.{name_match.group(1)}.Command.{object_command}"
|
||
links.append(
|
||
{
|
||
"kind": "command_button",
|
||
"button": item.get("name"),
|
||
"command": command_name,
|
||
"command_name": command_name,
|
||
"status": "resolved",
|
||
"match_by": "object_command_guid_and_button_name",
|
||
"button_path": item.get("path"),
|
||
"command_path": command_reference.get("path"),
|
||
"command_guid": command_reference.get("guid"),
|
||
}
|
||
)
|
||
continue
|
||
matched_guid = next((guid for guid in item.get("guids_sample") or [] if str(guid).lower() in command_by_guid), None)
|
||
button_text = " ".join(str(value or "") for value in [item.get("name"), item.get("title"), *(item.get("strings_sample") or [])]).casefold()
|
||
command = command_by_guid[str(matched_guid).lower()] if matched_guid else None
|
||
match_by = "guid"
|
||
if command is None:
|
||
item_name = str(item.get("name") or "").casefold()
|
||
for candidate in sorted(commands, key=lambda value: len(str(value.get("name") or "")), reverse=True):
|
||
candidate_name = str(candidate.get("name") or "").casefold()
|
||
if candidate_name and (item_name.endswith(candidate_name) or candidate_name in item_name):
|
||
command = candidate
|
||
match_by = "name"
|
||
break
|
||
if command is None:
|
||
continue
|
||
command_names = [str(value or "") for value in [command.get("name"), command.get("title"), command.get("id")]]
|
||
if match_by == "guid" and not any(value and value.casefold() in button_text for value in command_names):
|
||
command = None
|
||
item_name = str(item.get("name") or "").casefold()
|
||
for candidate in sorted(commands, key=lambda value: len(str(value.get("name") or "")), reverse=True):
|
||
candidate_name = str(candidate.get("name") or "").casefold()
|
||
if candidate_name and (item_name.endswith(candidate_name) or candidate_name in item_name):
|
||
command = candidate
|
||
match_by = "name"
|
||
matched_guid = None
|
||
break
|
||
if command is None:
|
||
continue
|
||
links.append(
|
||
{
|
||
"kind": "command_button",
|
||
"button": item.get("name"),
|
||
"command": command.get("name"),
|
||
"command_name": f"Form.Command.{command.get('name')}",
|
||
"status": "resolved",
|
||
"match_by": match_by,
|
||
"button_path": item.get("path"),
|
||
"command_path": command.get("path"),
|
||
"command_guid": matched_guid,
|
||
}
|
||
)
|
||
return links
|
||
|
||
|
||
def enrich_button_command_semantics(items: list[dict[str, Any]], links: list[dict[str, Any]]) -> None:
|
||
command_by_button = {str(link.get("button") or ""): link for link in links if link.get("button") and (link.get("command") or link.get("command_name"))}
|
||
for item in items:
|
||
link = command_by_button.get(str(item.get("name") or ""))
|
||
if not link:
|
||
continue
|
||
semantic = item.setdefault("semantic", {})
|
||
groups = semantic.setdefault("groups", {})
|
||
add_grouped_property(
|
||
groups,
|
||
"Основные",
|
||
semantic_property("ИмяКоманды", link.get("command_name") or f"Form.Command.{link.get('command')}", source=f"button_command_link:{link.get('match_by') or 'unknown'}"),
|
||
)
|
||
|
||
|
||
FORM_AUXILIARY_ITEM_TYPES = {
|
||
"Контекстное меню", "Расширенная подсказка", "SearchStringAddition",
|
||
"ViewStatusAddition", "SearchControlAddition",
|
||
}
|
||
|
||
|
||
def form_item_coverage_summary(items: list[dict[str, Any]]) -> dict[str, Any]:
|
||
"""Report semantic coverage without auxiliary form records hiding control quality."""
|
||
buckets = {
|
||
"all_items": {"items": 0, "mapped": 0, "unmapped": 0},
|
||
"interactive_items": {"items": 0, "mapped": 0, "unmapped": 0},
|
||
"auxiliary_items": {"items": 0, "mapped": 0, "unmapped": 0},
|
||
}
|
||
for item in items:
|
||
if not isinstance(item, dict):
|
||
continue
|
||
coverage = (item.get("semantic") or {}).get("coverage") if isinstance(item.get("semantic"), dict) else None
|
||
if not isinstance(coverage, dict):
|
||
continue
|
||
target = "auxiliary_items" if str(item.get("type_name") or "") in FORM_AUXILIARY_ITEM_TYPES else "interactive_items"
|
||
for bucket_name in ("all_items", target):
|
||
bucket = buckets[bucket_name]
|
||
bucket["items"] += 1
|
||
bucket["mapped"] += int(coverage.get("mapped") or 0)
|
||
bucket["unmapped"] += int(coverage.get("unmapped") or 0)
|
||
for bucket in buckets.values():
|
||
bucket["total"] = bucket["mapped"] + bucket["unmapped"]
|
||
bucket["status"] = "partial" if bucket["unmapped"] else "ok"
|
||
return buckets
|
||
|
||
|
||
def decode_form_payload(
|
||
tree: Any,
|
||
*,
|
||
max_items: int = 500,
|
||
include_module_text: bool = False,
|
||
include_parameters: bool = True,
|
||
max_parameters: int = 80,
|
||
) -> dict[str, Any]:
|
||
root = root_signature(tree)
|
||
items, items_total, items_truncated = item_records(tree, limit=max_items, include_parameters=include_parameters, max_parameters=max_parameters)
|
||
attributes = section_records(tree, "3", "Attribute", limit=max_items, include_parameters=include_parameters, max_parameters=max_parameters)
|
||
parameters = form_parameter_rows(tree, include_parameters=include_parameters, max_parameters=max_parameters)
|
||
enrich_item_data_paths(items, attributes)
|
||
enrich_page_title_data_paths(items)
|
||
additional_items, additional_items_total, additional_items_truncated = additional_column_items(
|
||
tree,
|
||
items,
|
||
limit=max(0, max_items - len(items)),
|
||
)
|
||
if additional_items:
|
||
items.extend(additional_items)
|
||
items_total += additional_items_total
|
||
items_truncated = items_truncated or additional_items_truncated or items_total > len(items)
|
||
dynamic_items, dynamic_items_total, dynamic_items_truncated = dynamic_list_column_items(attributes, limit=max(0, max_items - len(items)))
|
||
if dynamic_items:
|
||
items.extend(dynamic_items)
|
||
items_total += dynamic_items_total
|
||
items_truncated = items_truncated or dynamic_items_truncated or items_total > len(items)
|
||
commands = section_records(tree, "5", "Command", limit=max_items, include_parameters=include_parameters, max_parameters=max_parameters)
|
||
tables = section_records(tree, "6", "Table", limit=max_items, include_parameters=include_parameters, max_parameters=max_parameters)
|
||
command_bars = section_records(tree, "7", "CommandBar", limit=max_items, include_parameters=include_parameters, max_parameters=max_parameters)
|
||
totals = {
|
||
"items": items_total,
|
||
"attributes": section_record_total(tree, "3"),
|
||
"commands": section_record_total(tree, "5"),
|
||
"tables": section_record_total(tree, "6"),
|
||
"command_bars": section_record_total(tree, "7"),
|
||
}
|
||
module = module_summary(tree, include_text=include_module_text)
|
||
enrich_item_event_links(items, module)
|
||
events = event_handlers(tree)
|
||
event_links = handler_links(events, module)
|
||
link_items, _, _ = item_records(tree, limit=max(items_total, max_items), include_parameters=False)
|
||
link_commands = section_records(tree, "5", "Command", limit=max(totals["commands"], max_items), include_parameters=False)
|
||
command_links = command_handler_links(link_commands, module)
|
||
button_links = button_command_links(link_items, link_commands)
|
||
enrich_button_command_semantics(items, button_links)
|
||
public_rows_semantics(items, include_diagnostics=include_parameters)
|
||
public_rows_semantics(attributes, include_diagnostics=include_parameters)
|
||
public_rows_semantics(parameters, include_diagnostics=include_parameters)
|
||
public_rows_semantics(commands, include_diagnostics=include_parameters)
|
||
public_rows_semantics(tables, include_diagnostics=include_parameters)
|
||
public_rows_semantics(command_bars, include_diagnostics=include_parameters)
|
||
form_parameters = form_common_parameters(tree, limit=max_parameters)
|
||
form_semantic = form_common_semantic(form_parameters, include_diagnostics=include_parameters)
|
||
enrich_form_common_semantic(form_semantic, items)
|
||
coverage_summary = form_item_coverage_summary(items)
|
||
result = {
|
||
"schema": "onec_form_payload_profile.v1",
|
||
"status": "ok" if root.get("root_marker") == "4" else "not_form_payload",
|
||
"root": root,
|
||
"form_semantic": form_semantic,
|
||
"item_coverage": coverage_summary,
|
||
**({"form_parameters": form_parameters} if include_parameters else {}),
|
||
"events": events,
|
||
"items": items,
|
||
"attributes": attributes,
|
||
"parameters": parameters,
|
||
"commands": commands,
|
||
"tables": tables,
|
||
"command_bars": command_bars,
|
||
"module": module,
|
||
"handler_links": event_links,
|
||
"command_links": command_links,
|
||
"button_command_links": button_links,
|
||
"counts": {},
|
||
}
|
||
result["counts"] = {
|
||
"events": len(result["events"]),
|
||
"items": len(result["items"]),
|
||
"items_total": totals["items"],
|
||
"attributes": len(result["attributes"]),
|
||
"parameters": len(result["parameters"]),
|
||
"attributes_total": totals["attributes"],
|
||
"commands": len(result["commands"]),
|
||
"commands_total": totals["commands"],
|
||
"tables": len(result["tables"]),
|
||
"tables_total": totals["tables"],
|
||
"command_bars": len(result["command_bars"]),
|
||
"command_bars_total": totals["command_bars"],
|
||
"module_routines": ((result.get("module") or {}).get("routine_count") or 0),
|
||
"handler_links": len(result["handler_links"]),
|
||
"resolved_handlers": sum(1 for item in result["handler_links"] if item.get("status") == "resolved"),
|
||
"missing_handlers": sum(1 for item in result["handler_links"] if item.get("status") == "missing"),
|
||
"command_links": len(result["command_links"]),
|
||
"resolved_commands": sum(1 for item in result["command_links"] if item.get("status") == "resolved"),
|
||
"missing_commands": sum(1 for item in result["command_links"] if item.get("status") == "missing"),
|
||
"button_command_links": len(result["button_command_links"]),
|
||
"parameters_included": bool(include_parameters),
|
||
"max_parameters": max_parameters if include_parameters else 0,
|
||
"items_truncated": items_truncated,
|
||
"attributes_truncated": totals["attributes"] > len(result["attributes"]),
|
||
"commands_truncated": totals["commands"] > len(result["commands"]),
|
||
"tables_truncated": totals["tables"] > len(result["tables"]),
|
||
"command_bars_truncated": totals["command_bars"] > len(result["command_bars"]),
|
||
}
|
||
return result
|