404 lines
15 KiB
Python
404 lines
15 KiB
Python
"""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,
|
|
},
|
|
}
|