"""Semantic profiles for 1C managed form XML exports.""" from __future__ import annotations import xml.etree.ElementTree as ET from pathlib import Path from typing import Any FORM_XML_KIND_RU = { "Form": "Форма", "Event": "Событие", "Attribute": "Реквизит формы", "Column": "Колонка реквизита", "Command": "Команда формы", "AutoCommandBar": "Командная панель", "Button": "Кнопка", "ButtonGroup": "Группа кнопок", "CommandBar": "Командная панель", "Popup": "Подменю", "Pages": "Страницы", "ColumnGroup": "Группа колонок", "LabelField": "Поле надписи", "InputField": "Поле ввода", "CheckBoxField": "Поле флажка", "Table": "Таблица", "TableColumn": "Колонка таблицы", "Page": "Страница", "UsualGroup": "Группа", "DecorativeLabel": "Декорация надпись", "ContextMenu": "Контекстное меню", "ExtendedTooltip": "Расширенная подсказка", "SearchStringAddition": "Дополнение строки поиска", "ViewStatusAddition": "Дополнение состояния просмотра", "SearchControlAddition": "Дополнение управления поиском", } FORM_XML_PROPERTY_NAMES = { "name": "Имя", "id": "Идентификатор", "Title": "Заголовок", "Type": "Вид", "DataPath": "ПутьКДанным", "TitleLocation": "ПоложениеЗаголовка", "Visible": "Видимость", "UserVisible": "ПользовательскаяВидимость", "Enabled": "Доступность", "ReadOnly": "ТолькоПросмотр", "SkipOnInput": "ПропускатьПриВводе", "DefaultItem": "АктивизироватьПоУмолчанию", "Importance": "ВажностьПриОтображении", "ServerUnavailabilityBehavior": "ПоведениеПриНедоступностиОсновногоСервера", "PasswordMode": "РежимПароля", "Hyperlink": "Гиперссылка", "CommandName": "ИмяКоманды", "WindowOpeningMode": "РежимОткрытияОкна", "AutoSaveDataInSettings": "АвтоСохранениеДанныхВНастройках", "CommandBarLocation": "ПоложениеКоманднойПанели", "ShowCommandBar": "ОтображатьКоманднуюПанель", "AutoCommandBar": "АвтоКоманднаяПанель", "SearchStringAddition": "ДополнениеСтрокиПоиска", "ViewStatusAddition": "ДополнениеСостоянияПросмотра", "SearchControlAddition": "ДополнениеУправленияПоиском", "Representation": "Отображение", "ChangeRowSet": "ИзменятьСоставСтрок", "RowSelectionMode": "РежимВыделенияСтроки", "HorizontalLinesBWA": "ГоризонтальныеЛинии", "VerticalLinesBWA": "ВертикальныеЛинии", "UseAlternationRowColorBWA": "ЧередованиеЦветовСтрок", "AutoInsertNewRow": "АвтоВставкаНовойСтроки", "EnableStartDrag": "РазрешитьНачалоПеретаскивания", "EnableDrag": "РазрешитьПеретаскивание", "RowFilter": "ОтборСтрок", "HeightInTableRows": "ВысотаВСтрокахТаблицы", "Footer": "Подвал", "FileDragMode": "РежимПеретаскиванияФайлов", "SearchStringLocation": "ПоложениеСтрокиПоиска", "ViewStatusLocation": "ПоложениеСостоянияПросмотра", "SearchControlLocation": "ПоложениеУправленияПоиском", "ButtonGroup": "СоставКоманд", "Group": "Группировка", "Behavior": "Поведение", "ShowTitle": "ПоказыватьЗаголовок", "Autofill": "Автозаполнение", "EditWarningRepresentation": "ОтображениеПредупрежденияПриРедактировании", "EditWarning": "ПредупреждениеПриРедактировании", "EditMode": "РежимРедактирования", "AutoEditMode": "АвтоРежимРедактирования", "AutoCellHeight": "АвтоВысотаЯчейки", "FixInTable": "ФиксацияВТаблице", "Shortcut": "СочетаниеКлавиш", "CommandSet": "СоставКоманд", "UseCopy": "ИспользоватьКопирование", "ShowInHeader": "ОтображатьВШапке", "ShowInFooter": "ОтображатьВПодвале", "ToolTip": "Подсказка", "ToolTipRepresentation": "ОтображениеПодсказки", "DropListButton": "КнопкаВыпадающегоСписка", "ChoiceButton": "КнопкаВыбора", "ClearButton": "КнопкаОчистки", "SpinButton": "КнопкаРегулирования", "OpenButton": "КнопкаОткрытия", "CreateButton": "КнопкаСоздания", "QuickChoice": "БыстрыйВыбор", "ChooseType": "ВыбиратьТип", "ChoiceList": "СписокВыбора", "IncompleteChoiceMode": "РежимВыбораНезаполненного", "TextEdit": "РедактированиеТекста", "TextEditUpdate": "ОбновлениеТекстаРедактирования", "MultiLine": "МногострочныйРежим", "AutoLineBreak": "АвтоПереносСтрок", "AutoMarkIncomplete": "АвтоОтметкаНезаполненного", "AutoChoiceIncomplete": "АвтоВыборНезаполненного", "InputHint": "ПодсказкаВвода", "ChoiceHistoryOnInput": "ИсторияВыбораПриВводе", "Picture": "Картинка", "HeaderPicture": "КартинкаШапки", "FooterPicture": "КартинкаПодвала", "BackColor": "ЦветФона", "TextColor": "ЦветТекста", "BorderColor": "ЦветРамки", "Font": "Шрифт", "Shape": "Фигура", "ShapeRepresentation": "ОтображениеФигуры", "PictureLocation": "ПоложениеКартинки", "HeaderBackColor": "ЦветФонаЗаголовка", "Width": "Ширина", "Height": "Высота", "AutoMaxWidth": "АвтоМаксимальнаяШирина", "AutoMaxHeight": "АвтоМаксимальнаяВысота", "MaxWidth": "МаксимальнаяШирина", "MaxHeight": "МаксимальнаяВысота", "TitleHeight": "ВысотаЗаголовка", "HorizontalAlign": "ГоризонтальноеПоложениеВГруппе", "VerticalAlign": "ВертикальноеПоложениеВГруппе", "HorizontalStretch": "РастягиватьПоГоризонтали", "VerticalStretch": "РастягиватьПоВертикали", "LocationInCommandBar": "ПоложениеВКоманднойПанели", "UniqueCommands": "УникальностьКоманд", "DefaultButton": "КнопкаПоУмолчанию", "Check": "Пометка", "MainAttribute": "ОсновнойРеквизит", "Action": "Действие", "AdditionSource": "ИсточникДополнения", "Columns": "Колонки", "Save": "СохраняемыеДанные", "ContextMenu": "КонтекстноеМеню", "ExtendedTooltip": "РасширеннаяПодсказка", "Events": "События", "Handler": "Обработчик", "ChildItems": "ПодчиненныеЭлементы", } FORM_XML_PROPERTY_GROUPS = { "Основные": { "name", "id", "Title", "Type", "DataPath", "CommandName", "WindowOpeningMode", "AutoSaveDataInSettings", "CommandBarLocation", "ShowCommandBar", "AutoCommandBar", "SearchStringAddition", "ViewStatusAddition", "SearchControlAddition", "TitleLocation", "Visible", "UserVisible", "Enabled", "ReadOnly", "SkipOnInput", "DefaultItem", "Importance", "ServerUnavailabilityBehavior", "PasswordMode", "Hyperlink", "Representation", "ButtonGroup", "Group", "Behavior", "ShowTitle", "Autofill", }, "Использование": { "EditWarningRepresentation", "EditWarning", "EditMode", "AutoEditMode", "AutoCellHeight", "FixInTable", "Shortcut", "CommandSet", "UseCopy", "ChangeRowSet", "RowSelectionMode", "AutoInsertNewRow", "EnableStartDrag", "EnableDrag", "RowFilter", "FileDragMode", "ShowInHeader", "ShowInFooter", "ToolTip", "ToolTipRepresentation", "DropListButton", "ChoiceButton", "ClearButton", "SpinButton", "OpenButton", "CreateButton", "QuickChoice", "ChooseType", "ChoiceList", "IncompleteChoiceMode", "TextEdit", "TextEditUpdate", "MultiLine", "AutoLineBreak", "AutoMarkIncomplete", "AutoChoiceIncomplete", "InputHint", "ChoiceHistoryOnInput", "MainAttribute", "Action", "Save", }, "Оформление": { "Picture", "HeaderPicture", "FooterPicture", "BackColor", "TextColor", "BorderColor", "Font", "Shape", "ShapeRepresentation", "PictureLocation", "HeaderBackColor", "Footer", "HorizontalLinesBWA", "VerticalLinesBWA", "UseAlternationRowColorBWA", }, "Расположение": { "Width", "Height", "HeightInTableRows", "SearchStringLocation", "ViewStatusLocation", "SearchControlLocation", "AutoMaxWidth", "AutoMaxHeight", "MaxWidth", "MaxHeight", "TitleHeight", "HorizontalAlign", "VerticalAlign", "HorizontalStretch", "VerticalStretch", "LocationInCommandBar", "UniqueCommands", "DefaultButton", "Check", "AdditionSource", "Columns", }, } FORM_XML_CONTAINER_TAGS = {"ChildItems", "CommandSet", "Events"} def local_name(tag: str) -> str: return tag.rsplit("}", 1)[-1] def property_group(tag: str) -> str: for group, names in FORM_XML_PROPERTY_GROUPS.items(): if tag in names: return group return "Прочее" def xml_scalar(element: ET.Element) -> Any: text = (element.text or "").strip() if text: if text == "true": return True if text == "false": return False return text if "name" in element.attrib: return element.attrib.get("name") if "id" in element.attrib: return element.attrib.get("id") return None def add_property(groups: dict[str, list[dict[str, Any]]], xml_name: str, value: Any, *, source: str = "form_xml") -> None: if value is None: return group = property_group(xml_name) groups.setdefault(group, []).append( { "name": FORM_XML_PROPERTY_NAMES.get(xml_name, xml_name), "xml_name": xml_name, "value": value, "source": source, "status": "ok" if value not in {"", None} else "empty", } ) def element_semantic(element: ET.Element) -> dict[str, Any]: tag = local_name(element.tag) groups: dict[str, list[dict[str, Any]]] = {} add_property(groups, "name", element.attrib.get("name"), source="form_xml_attribute") add_property(groups, "id", element.attrib.get("id"), source="form_xml_attribute") add_property(groups, "Type", FORM_XML_KIND_RU.get(tag, tag), source="form_xml_tag") if tag == "Event" and (element.text or "").strip(): add_property(groups, "Handler", (element.text or "").strip(), source="form_xml_text") for child in list(element): child_tag = local_name(child.tag) if child_tag in FORM_XML_CONTAINER_TAGS: continue add_property(groups, child_tag, xml_scalar(child)) mapped = sum(len(items) for items in groups.values()) return { "groups": groups, "coverage": { "mapped": mapped, "unmapped": 0, "total": mapped, "status": "ok", }, } def walk_form_elements(root: ET.Element, *, limit: int = 5000) -> tuple[list[dict[str, Any]], int, bool]: records: list[dict[str, Any]] = [] total = 0 def walk( element: ET.Element, path: list[int], additional_columns_table: str | None = None, owner_name: str | None = None, ) -> None: nonlocal total tag = local_name(element.tag) if tag == "AdditionalColumns" and element.attrib.get("table"): additional_columns_table = element.attrib.get("table") if "name" in element.attrib: total += 1 if len(records) < limit: records.append( { "name": element.attrib.get("name"), "id": element.attrib.get("id"), "kind": tag, "kind_ru": FORM_XML_KIND_RU.get(tag, tag), "xml_path": ".".join(str(part) for part in path), "semantic": element_semantic(element), **({"additional_columns_table": additional_columns_table} if additional_columns_table else {}), **({"owner": owner_name} if tag == "Event" and owner_name else {}), } ) child_owner = element.attrib.get("name") if "name" in element.attrib and tag != "Event" else owner_name for index, child in enumerate(list(element)): walk(child, [*path, index], additional_columns_table, child_owner) walk(root, []) return records, total, total > len(records) def decode_form_xml(xml: str | bytes | Path, *, max_items: int = 5000) -> dict[str, Any]: if isinstance(xml, Path): root = ET.parse(xml).getroot() source = {"kind": "xml_file", "path": str(xml)} else: root = ET.fromstring(xml) source = {"kind": "xml_text"} items, total, truncated = walk_form_elements(root, limit=max_items) return { "schema": "onec_form_xml_profile.v1", "status": "ok", "source": source, "form": { "version": root.attrib.get("version"), "kind": local_name(root.tag), "kind_ru": FORM_XML_KIND_RU.get(local_name(root.tag), local_name(root.tag)), "semantic": element_semantic(root), }, "items": items, "counts": { "items": len(items), "items_total": total, "items_truncated": truncated, }, }