mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-13 22:35:16 +03:00
4782 lines
253 KiB
Python
4782 lines
253 KiB
Python
#!/usr/bin/env python3
|
||
# meta-compile v1.65 — Compile 1C metadata object from JSON
|
||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||
|
||
import argparse
|
||
import json
|
||
import os
|
||
import re
|
||
import subprocess
|
||
import sys
|
||
import tempfile
|
||
import uuid
|
||
import xml.etree.ElementTree as ET
|
||
from lxml import etree
|
||
|
||
sys.stdout.reconfigure(encoding="utf-8")
|
||
sys.stderr.reconfigure(encoding="utf-8")
|
||
|
||
# ============================================================
|
||
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
||
# Blocks edits of vendor objects "на замке" / read-only configs. Trigger = bin
|
||
# present; reaction from .v8-project.json editingAllowedCheck (deny|warn|off,
|
||
# default deny). Never throws (except sys.exit on deny) — errors degrade to allow.
|
||
# ============================================================
|
||
|
||
def _sg_root_uuid(xml_path):
|
||
if not os.path.isfile(xml_path):
|
||
return None
|
||
try:
|
||
mx = etree.parse(xml_path).getroot()
|
||
for child in mx:
|
||
if isinstance(child.tag, str) and child.get("uuid"):
|
||
return child.get("uuid")
|
||
except Exception:
|
||
return None
|
||
return None
|
||
|
||
|
||
def _sg_find_v8project(start_dir):
|
||
d = start_dir
|
||
for _ in range(20):
|
||
if not d:
|
||
break
|
||
pj = os.path.join(d, ".v8-project.json")
|
||
if os.path.isfile(pj):
|
||
return pj
|
||
parent = os.path.dirname(d)
|
||
if parent == d:
|
||
break
|
||
d = parent
|
||
return None
|
||
|
||
|
||
def _sg_get_edit_mode(cfg_dir):
|
||
try:
|
||
pj = _sg_find_v8project(os.getcwd()) or _sg_find_v8project(cfg_dir)
|
||
if not pj:
|
||
return "deny"
|
||
proj = json.loads(open(pj, encoding="utf-8-sig").read())
|
||
cfg_full = os.path.normcase(os.path.abspath(cfg_dir)).rstrip("\\/")
|
||
for db in proj.get("databases", []):
|
||
src = db.get("configSrc")
|
||
if src:
|
||
src_full = os.path.normcase(os.path.abspath(src)).rstrip("\\/")
|
||
if cfg_full == src_full or cfg_full.startswith(src_full + os.sep):
|
||
if db.get("editingAllowedCheck"):
|
||
return db["editingAllowedCheck"]
|
||
if proj.get("editingAllowedCheck"):
|
||
return proj["editingAllowedCheck"]
|
||
return "deny"
|
||
except Exception:
|
||
return "deny"
|
||
|
||
|
||
def assert_edit_allowed(target_path, require):
|
||
try:
|
||
rp = os.path.abspath(target_path)
|
||
elem_uuid = _sg_root_uuid(rp)
|
||
cfg_dir = None
|
||
bin_path = None
|
||
d = rp if os.path.isdir(rp) else os.path.dirname(rp)
|
||
for _ in range(12):
|
||
if not d:
|
||
break
|
||
if not elem_uuid:
|
||
elem_uuid = _sg_root_uuid(d + ".xml")
|
||
if not cfg_dir:
|
||
cand = os.path.join(d, "Ext", "ParentConfigurations.bin")
|
||
if os.path.exists(cand) or os.path.exists(os.path.join(d, "Configuration.xml")):
|
||
cfg_dir = d
|
||
bin_path = cand
|
||
if elem_uuid and cfg_dir:
|
||
break
|
||
parent = os.path.dirname(d)
|
||
if parent == d:
|
||
break
|
||
d = parent
|
||
if not elem_uuid and cfg_dir:
|
||
elem_uuid = _sg_root_uuid(os.path.join(cfg_dir, "Configuration.xml"))
|
||
if not bin_path or not os.path.exists(bin_path):
|
||
return
|
||
data = open(bin_path, "rb").read()
|
||
if len(data) <= 32:
|
||
return
|
||
if data[:3] == b"\xef\xbb\xbf":
|
||
data = data[3:]
|
||
text = data.decode("utf-8", "replace")
|
||
h = re.match(r"\{6,(\d+),(\d+),", text)
|
||
if not h:
|
||
return
|
||
g = int(h.group(1))
|
||
k = int(h.group(2))
|
||
if k == 0:
|
||
return
|
||
best = None
|
||
if elem_uuid:
|
||
for m in re.finditer(r"([0-2]),0," + re.escape(elem_uuid.lower()), text):
|
||
f1 = int(m.group(1))
|
||
if best is None or f1 < best:
|
||
best = f1
|
||
blocked = False
|
||
code = ""
|
||
reason = ""
|
||
if g == 1:
|
||
blocked = True
|
||
code = "capability-off"
|
||
reason = "возможность изменения конфигурации выключена (вся конфигурация read-only)"
|
||
elif require == "removed":
|
||
if best is not None and best != 2:
|
||
blocked = True
|
||
code = "not-removed"
|
||
reason = "объект не снят с поддержки — удаление сломает обновления"
|
||
else:
|
||
if best is not None and best == 0:
|
||
blocked = True
|
||
code = "locked"
|
||
reason = "объект на замке — редактирование сломает обновления"
|
||
if not blocked:
|
||
return
|
||
mode = _sg_get_edit_mode(cfg_dir)
|
||
if mode == "off":
|
||
return
|
||
if mode == "warn":
|
||
sys.stderr.write(f"[support-guard] ПРЕДУПРЕЖДЕНИЕ: {reason}. Цель: {rp}\n")
|
||
return
|
||
head = "[support-guard] Редактирование отклонено: это объект типовой конфигурации на поддержке поставщика, прямое редактирование молча сломает будущие обновления."
|
||
cfe = "Рекомендуемый путь: внести доработку в расширение (навыки cfe-borrow / cfe-patch-method) — состояние поддержки менять не нужно, обновления вендора сохраняются."
|
||
off_note = "Снять проверку для этой базы: editingAllowedCheck = warn|off в .v8-project.json."
|
||
if code == "capability-off":
|
||
state = f"Состояние: у всей конфигурации выключена возможность изменения (режим read-only «из коробки») — поэтому объект «{rp}» редактировать нельзя."
|
||
fix = (
|
||
"Либо снять защиту явно (навык support-edit, два шага):\n"
|
||
f' 1. support-edit -Path "{cfg_dir}" -Capability on — включить возможность изменения (объекты пока остаются на замке);\n'
|
||
f' 2. support-edit -Path "{rp}" -Set editable — открыть этот объект для редактирования.\n'
|
||
" Изменение применяется в базу полной загрузкой выгрузки и обходит механизм обновлений вендора."
|
||
)
|
||
elif code == "not-removed":
|
||
state = f"Состояние: объект «{rp}» на поддержке (не снят с поддержки) — его удаление разорвёт обновления вендора."
|
||
fix = (
|
||
"Либо сначала снять объект с поддержки, затем удалять:\n"
|
||
f' support-edit -Path "{rp}" -Set off-support — объект уходит из-под обновлений, после этого удаление безопасно.'
|
||
)
|
||
else:
|
||
state = f"Состояние: объект «{rp}» на замке (возможность изменения конфигурации включена, но сам объект не редактируется)."
|
||
fix = (
|
||
"Либо разрешить редактирование этого объекта (навык support-edit, выбрать одно):\n"
|
||
f' support-edit -Path "{rp}" -Set editable — редактировать и дальше получать обновления вендора (возможны конфликты слияния);\n'
|
||
f' support-edit -Path "{rp}" -Set off-support — снять с поддержки: обновления по объекту больше не приходят.'
|
||
)
|
||
sys.stderr.write(head + "\n" + state + "\n" + cfe + "\n" + fix + "\n" + off_note + "\n")
|
||
sys.exit(1)
|
||
except SystemExit:
|
||
raise
|
||
except Exception:
|
||
return
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Inline utilities
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def esc_xml(s):
|
||
return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
|
||
|
||
def esc_xml_text(s):
|
||
# Эскейп ТЕКСТА элемента: только & < > (кавычки в тексте 1С держит raw).
|
||
return s.replace('&', '&').replace('<', '<').replace('>', '>')
|
||
|
||
def new_uuid():
|
||
return str(uuid.uuid4())
|
||
|
||
def write_utf8_bom(path, content):
|
||
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
||
f.write(content)
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# XML builder (lines list)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
lines = []
|
||
|
||
def X(text):
|
||
lines.append(text)
|
||
|
||
# ML-значение: строка → один <v8:item> ru; dict {lang: content} → item на язык (в порядке ключей).
|
||
def emit_ml_items(indent, val):
|
||
if isinstance(val, dict):
|
||
for k, v in val.items():
|
||
X(f'{indent}<v8:item>')
|
||
X(f'{indent}\t<v8:lang>{k}</v8:lang>')
|
||
X(f'{indent}\t<v8:content>{esc_xml_text(str(v))}</v8:content>')
|
||
X(f'{indent}</v8:item>')
|
||
else:
|
||
X(f'{indent}<v8:item>')
|
||
X(f'{indent}\t<v8:lang>ru</v8:lang>')
|
||
X(f'{indent}\t<v8:content>{esc_xml_text(str(val))}</v8:content>')
|
||
X(f'{indent}</v8:item>')
|
||
|
||
def emit_mltext(indent, tag, text):
|
||
# Пусто (None / '') → самозакрывающийся тег.
|
||
if text is None or (isinstance(text, str) and text == ''):
|
||
X(f'{indent}<{tag}/>')
|
||
return
|
||
X(f'{indent}<{tag}>')
|
||
emit_ml_items(f'{indent}\t', text)
|
||
X(f'{indent}</{tag}>')
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# CamelCase splitter
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def split_camel_case(name):
|
||
if not name:
|
||
return name
|
||
result = re.sub(r'([а-яё])([А-ЯЁ])', r'\1 \2', name)
|
||
result = re.sub(r'([a-z])([A-Z])', r'\1 \2', result)
|
||
# Лоуэркейзим хвост, СОХРАНЯЯ аббревиатуры (зеркало эвристики платформы): максимальный прогон
|
||
# заглавных длиной >=2, если сразу за ним НЕ буква (пробел/цифра/спецсимвол/конец) — остаётся заглавным
|
||
# (НДС, ЕГАИС, ОС, ЭП). Прилипшие предлоги (СКлиентами)/бренды (ЮКасса) идут перед буквой → лоуэркейз.
|
||
# Первый символ строки — как есть.
|
||
if len(result) > 1:
|
||
chars = list(result)
|
||
n = len(chars)
|
||
keep = [False] * n
|
||
i = 0
|
||
while i < n:
|
||
if chars[i].isupper():
|
||
j = i
|
||
while j < n and chars[j].isupper():
|
||
j += 1
|
||
after_boundary = (j == n) or (not chars[j].isalpha())
|
||
if j - i >= 2 and after_boundary:
|
||
for k in range(i, j):
|
||
keep[k] = True
|
||
i = j
|
||
else:
|
||
i += 1
|
||
out = []
|
||
for idx, c in enumerate(chars):
|
||
if idx == 0 or keep[idx]:
|
||
out.append(c)
|
||
elif c.isupper():
|
||
out.append(c.lower())
|
||
else:
|
||
out.append(c)
|
||
result = ''.join(out)
|
||
return result
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 1. Load and validate JSON
|
||
# ---------------------------------------------------------------------------
|
||
|
||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||
parser.add_argument('-JsonPath', required=True)
|
||
parser.add_argument('-OutputDir', required=True)
|
||
args = parser.parse_args()
|
||
|
||
json_path = args.JsonPath
|
||
output_dir = args.OutputDir
|
||
|
||
if not os.path.isfile(json_path):
|
||
print(f'File not found: {json_path}', file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
with open(json_path, 'r', encoding='utf-8-sig') as f:
|
||
json_text = f.read()
|
||
|
||
defn = json.loads(json_text)
|
||
|
||
assert_edit_allowed(output_dir, "editable")
|
||
|
||
# --- Batch mode: JSON array of objects ---
|
||
if isinstance(defn, list):
|
||
batch_ok = 0
|
||
batch_fail = 0
|
||
for idx, item in enumerate(defn, 1):
|
||
tmp_fd, tmp_path = tempfile.mkstemp(suffix='.json', prefix=f'meta-compile-batch-{idx}-')
|
||
try:
|
||
with os.fdopen(tmp_fd, 'w', encoding='utf-8') as f:
|
||
json.dump(item, f, ensure_ascii=False, indent=2)
|
||
rc = subprocess.call([sys.executable, __file__, '-JsonPath', tmp_path, '-OutputDir', output_dir])
|
||
if rc == 0:
|
||
batch_ok += 1
|
||
else:
|
||
batch_fail += 1
|
||
finally:
|
||
if os.path.exists(tmp_path):
|
||
os.unlink(tmp_path)
|
||
print()
|
||
print(f"=== Batch: {len(defn)} objects, {batch_ok} compiled, {batch_fail} failed ===")
|
||
sys.exit(1 if batch_fail > 0 else 0)
|
||
|
||
# Normalize field synonyms: accept "objectType" as alias for "type"
|
||
if not defn.get('type') and defn.get('objectType'):
|
||
defn['type'] = defn['objectType']
|
||
|
||
# Object type synonyms (Russian -> English)
|
||
object_type_synonyms = {
|
||
'Справочник': 'Catalog',
|
||
'Каталог': 'Catalog',
|
||
'Документ': 'Document',
|
||
'Перечисление': 'Enum',
|
||
'Константа': 'Constant',
|
||
'РегистрСведений': 'InformationRegister',
|
||
'РегистрНакопления': 'AccumulationRegister',
|
||
'РегистрБухгалтерии': 'AccountingRegister',
|
||
'РегистрРасчёта': 'CalculationRegister',
|
||
'РегистрРасчета': 'CalculationRegister',
|
||
'ПланСчетов': 'ChartOfAccounts',
|
||
'ПланВидовХарактеристик': 'ChartOfCharacteristicTypes',
|
||
'ПланВидовРасчёта': 'ChartOfCalculationTypes',
|
||
'ПланВидовРасчета': 'ChartOfCalculationTypes',
|
||
'БизнесПроцесс': 'BusinessProcess',
|
||
'Задача': 'Task',
|
||
'ПланОбмена': 'ExchangePlan',
|
||
'ЖурналДокументов': 'DocumentJournal',
|
||
'Отчёт': 'Report',
|
||
'Отчет': 'Report',
|
||
'Обработка': 'DataProcessor',
|
||
'ОбщийМодуль': 'CommonModule',
|
||
'РегламентноеЗадание': 'ScheduledJob',
|
||
'ПодпискаНаСобытие': 'EventSubscription',
|
||
'HTTPСервис': 'HTTPService',
|
||
'ВебСервис': 'WebService',
|
||
'ОпределяемыйТип': 'DefinedType',
|
||
'ФункциональнаяОпция': 'FunctionalOption',
|
||
}
|
||
|
||
# Enum property value synonyms — model often gets these slightly wrong
|
||
enum_value_aliases = {
|
||
# RegisterType (AccumulationRegister)
|
||
'Balances': 'Balance', 'Остатки': 'Balance', 'Обороты': 'Turnovers',
|
||
# WriteMode (InformationRegister)
|
||
'RecordSubordinate': 'RecorderSubordinate', 'Subordinate': 'RecorderSubordinate',
|
||
'ПодчинениеРегистратору': 'RecorderSubordinate', 'Независимый': 'Independent',
|
||
# DependenceOnCalculationTypes (ChartOfCalculationTypes)
|
||
'NotDependOnCalculationTypes': 'DontUse', 'NoDependence': 'DontUse', 'NotUsed': 'DontUse',
|
||
'Depend': 'OnActionPeriod', 'ПоПериодуДействия': 'OnActionPeriod',
|
||
# InformationRegisterPeriodicity
|
||
'None': 'Nonperiodical', 'Daily': 'Day', 'Monthly': 'Month',
|
||
'Quarterly': 'Quarter', 'Yearly': 'Year',
|
||
'Непериодический': 'Nonperiodical', 'Секунда': 'Second', 'День': 'Day',
|
||
'Месяц': 'Month', 'Квартал': 'Quarter', 'Год': 'Year',
|
||
'ПозицияРегистратора': 'RecorderPosition',
|
||
# DataLockControlMode
|
||
'Автоматический': 'Automatic', 'Управляемый': 'Managed',
|
||
# FullTextSearch
|
||
'Использовать': 'Use', 'НеИспользовать': 'DontUse',
|
||
# Posting
|
||
'Разрешить': 'Allow', 'Запретить': 'Deny',
|
||
# EditType
|
||
'ВДиалоге': 'InDialog', 'ВСписке': 'InList', 'ОбаСпособа': 'BothWays',
|
||
# DefaultPresentation
|
||
'ВВидеНаименования': 'AsDescription', 'ВВидеКода': 'AsCode',
|
||
# FillChecking
|
||
'НеПроверять': 'DontCheck', 'Ошибка': 'ShowError', 'Предупреждение': 'ShowWarning',
|
||
# Indexing
|
||
'НеИндексировать': 'DontIndex', 'Индексировать': 'Index',
|
||
'ИндексироватьСДопУпорядочиванием': 'IndexWithAdditionalOrder',
|
||
}
|
||
|
||
# Valid enum values per property (from meta-validate)
|
||
valid_enum_values = {
|
||
'RegisterType': ['Balance', 'Turnovers'],
|
||
'WriteMode': ['Independent', 'RecorderSubordinate'],
|
||
'InformationRegisterPeriodicity': ['Nonperiodical', 'Second', 'Day', 'Month', 'Quarter', 'Year', 'RecorderPosition'],
|
||
'DependenceOnCalculationTypes': ['DontUse', 'OnActionPeriod'],
|
||
'DataLockControlMode': ['Automatic', 'Managed'],
|
||
'FullTextSearch': ['Use', 'DontUse'],
|
||
'DataHistory': ['Use', 'DontUse'],
|
||
'DefaultPresentation': ['AsDescription', 'AsCode'],
|
||
'Posting': ['Allow', 'Deny'],
|
||
'RealTimePosting': ['Allow', 'Deny'],
|
||
'EditType': ['InDialog', 'InList', 'BothWays'],
|
||
'HierarchyType': ['HierarchyFoldersAndItems', 'HierarchyOfItems'],
|
||
'CodeType': ['String', 'Number'],
|
||
'CodeAllowedLength': ['Variable', 'Fixed'],
|
||
'NumberType': ['String', 'Number'],
|
||
'NumberAllowedLength': ['Variable', 'Fixed'],
|
||
'RegisterRecordsDeletion': ['AutoDelete', 'AutoDeleteOnUnpost', 'AutoDeleteOff'],
|
||
'RegisterRecordsWritingOnPost': ['WriteModified', 'WriteSelected', 'WriteAll'],
|
||
'ReturnValuesReuse': ['DontUse', 'DuringRequest', 'DuringSession'],
|
||
'ReuseSessions': ['DontUse', 'AutoUse'],
|
||
'FillChecking': ['DontCheck', 'ShowError', 'ShowWarning'],
|
||
'Indexing': ['DontIndex', 'Index', 'IndexWithAdditionalOrder'],
|
||
'SubordinationUse': ['ToItems', 'ToFolders', 'ToFoldersAndItems'],
|
||
'CodeSeries': ['WholeCatalog', 'WithinSubordination', 'WithinOwnerSubordination', 'WholeCharacteristicKind', 'WholeChartOfAccounts'],
|
||
'ChoiceMode': ['BothWays', 'QuickChoice', 'FromForm'],
|
||
'CreateOnInput': ['Auto', 'Use', 'DontUse'],
|
||
'ChoiceHistoryOnInput': ['Auto', 'DontUse'],
|
||
'PredefinedDataUpdate': ['Auto', 'DontAutoUpdate', 'AutoUpdate'],
|
||
'SearchStringModeOnInputByString': ['Begin', 'AnyPart'],
|
||
'FullTextSearchOnInputByString': ['Use', 'DontUse'],
|
||
'Category': ['NavigationPanel', 'ActionsPanel', 'FormCommandBar', 'FormNavigationPanel'],
|
||
}
|
||
|
||
# --- Группы команд объекта (командный интерфейс) ---
|
||
# Группы командного интерфейса РАЗДЕЛА (панель навигации/действий): команда БЕЗ параметра. Группы формы: параметр доступен.
|
||
SECTION_COMMAND_GROUPS = [
|
||
'NavigationPanelImportant', 'NavigationPanelOrdinary', 'NavigationPanelSeeAlso',
|
||
'ActionsPanelCreate', 'ActionsPanelReports', 'ActionsPanelTools',
|
||
]
|
||
FORM_COMMAND_GROUPS = [
|
||
'FormCommandBarImportant', 'FormCommandBarCreateBasedOn',
|
||
'FormNavigationPanelImportant', 'FormNavigationPanelGoTo', 'FormNavigationPanelSeeAlso',
|
||
]
|
||
VALID_COMMAND_GROUPS = SECTION_COMMAND_GROUPS + FORM_COMMAND_GROUPS
|
||
COMMAND_GROUP_ALIASES = {
|
||
'Панель навигации.Важное': 'NavigationPanelImportant',
|
||
'Панель навигации.Обычное': 'NavigationPanelOrdinary',
|
||
'Панель навигации.См. также': 'NavigationPanelSeeAlso',
|
||
'Панель действий.Создать': 'ActionsPanelCreate',
|
||
'Панель действий.Отчеты': 'ActionsPanelReports',
|
||
'Панель действий.Отчёты': 'ActionsPanelReports',
|
||
'Панель действий.Сервис': 'ActionsPanelTools',
|
||
'Командная панель формы.Важное': 'FormCommandBarImportant',
|
||
'Командная панель формы.Создать на основании': 'FormCommandBarCreateBasedOn',
|
||
'Панель навигации формы.Важное': 'FormNavigationPanelImportant',
|
||
'Панель навигации формы.Перейти': 'FormNavigationPanelGoTo',
|
||
'Панель навигации формы.См. также': 'FormNavigationPanelSeeAlso',
|
||
}
|
||
|
||
|
||
def resolve_command_group(raw, cmd_name):
|
||
g = (str(raw) if raw is not None else '').strip()
|
||
if not g:
|
||
sys.stderr.write(f"Команде '{cmd_name}' не задана группа (group). 1С требует группу. "
|
||
f"Валидные: {', '.join(VALID_COMMAND_GROUPS)}; либо CommandGroup.<Имя> — кастомная группа.\n")
|
||
sys.exit(1)
|
||
if g in COMMAND_GROUP_ALIASES:
|
||
return COMMAND_GROUP_ALIASES[g]
|
||
m = re.match(r'^(?:CommandGroup|ГруппаКоманд)\.(.+)$', g)
|
||
if m:
|
||
return f'CommandGroup.{m.group(1)}'
|
||
return g
|
||
|
||
def normalize_enum_value(prop_name, value):
|
||
# 1. Check alias dictionary — silent auto-correct
|
||
if value in enum_value_aliases:
|
||
return enum_value_aliases[value]
|
||
# 2. Case-insensitive match against valid values — silent
|
||
valid = valid_enum_values.get(prop_name)
|
||
if valid:
|
||
for v in valid:
|
||
if v.lower() == value.lower():
|
||
return v
|
||
# 3. Known property, unknown value — error with hint
|
||
print(f"Invalid value '{value}' for property '{prop_name}'. Valid values: {', '.join(valid)}", file=sys.stderr)
|
||
sys.exit(1)
|
||
# 4. Unknown property — pass-through (no validation data)
|
||
return value
|
||
|
||
def get_enum_prop(prop_name, field_name, default):
|
||
val = defn.get(field_name)
|
||
raw = str(val) if val else default
|
||
return normalize_enum_value(prop_name, raw)
|
||
|
||
def get_bool_prop(field_name, default):
|
||
"""Bool object-свойство: presence-aware (иначе false спутать с отсутствием). Прощаем строки."""
|
||
val = defn.get(field_name)
|
||
if val is None:
|
||
return default
|
||
if isinstance(val, bool):
|
||
return val
|
||
return str(val).lower() in ('true', '1', 'да', 'истина')
|
||
|
||
def normalize_form_ref(s):
|
||
"""Прощающая нормализация ссылки на форму: рус корень, Форма→Form, короткая запись → вставка Form."""
|
||
if not s:
|
||
return s
|
||
parts = s.split('.')
|
||
if len(parts) < 3:
|
||
return s
|
||
root = fill_ref_roots.get(parts[0].lower())
|
||
if root:
|
||
parts[0] = root
|
||
for k in range(1, len(parts)):
|
||
if parts[k].lower() == 'форма':
|
||
parts[k] = 'Form'
|
||
if 'Form' not in parts and len(parts) == 3:
|
||
parts = [parts[0], parts[1], 'Form', parts[2]]
|
||
return '.'.join(parts)
|
||
|
||
def emit_form_ref(i, tag, val):
|
||
"""Ссылка на форму по умолчанию: непустая → <Tag>значение</Tag>, иначе <Tag/>."""
|
||
if val:
|
||
X(f'{i}<{tag}>{esc_xml(normalize_form_ref(str(val)))}</{tag}>')
|
||
else:
|
||
X(f'{i}<{tag}/>')
|
||
|
||
def emit_verbatim_ref(i, tag, val):
|
||
"""Ссылка verbatim (без normalize_form_ref): формы/схемы/хранилища Report/DataProcessor, где имя формы
|
||
может быть буквально «Форма» (normalize перевёл бы имя-сегмент Форма→Form) либо ref не-форменного вида."""
|
||
if val:
|
||
X(f'{i}<{tag}>{esc_xml(str(val))}</{tag}>')
|
||
else:
|
||
X(f'{i}<{tag}/>')
|
||
|
||
if not defn.get('type'):
|
||
print("JSON must have 'type' field", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
obj_type = str(defn['type'])
|
||
if obj_type in object_type_synonyms:
|
||
obj_type = object_type_synonyms[obj_type]
|
||
|
||
valid_types = [
|
||
'Catalog', 'Document', 'Enum', 'Constant', 'InformationRegister',
|
||
'AccumulationRegister', 'AccountingRegister', 'CalculationRegister',
|
||
'ChartOfAccounts', 'ChartOfCharacteristicTypes', 'ChartOfCalculationTypes',
|
||
'BusinessProcess', 'Task', 'ExchangePlan', 'DocumentJournal',
|
||
'Report', 'DataProcessor', 'CommonModule', 'ScheduledJob',
|
||
'EventSubscription', 'HTTPService', 'WebService', 'DefinedType',
|
||
'FunctionalOption',
|
||
'Sequence', 'FilterCriterion', 'DocumentNumerator', 'SettingsStorage',
|
||
'CommonForm',
|
||
'SessionParameter', 'CommonCommand', 'CommandGroup', 'CommonAttribute', 'FunctionalOptionsParameter', 'WSReference',
|
||
'CommonPicture', 'CommonTemplate',
|
||
]
|
||
if obj_type not in valid_types:
|
||
print(f"Unsupported type: {obj_type}. Valid: {', '.join(valid_types)}", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
if not defn.get('name'):
|
||
print("JSON must have 'name' field", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
obj_name = str(defn['name'])
|
||
|
||
# Auto-synonym
|
||
# Проброс без стрингификации (строка ИЛИ {ru,en} — мультиязычный синоним объекта).
|
||
synonym = defn['synonym'] if defn.get('synonym') is not None else split_camel_case(obj_name)
|
||
comment = str(defn['comment']) if defn.get('comment') else ''
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 4. Type system
|
||
# ---------------------------------------------------------------------------
|
||
|
||
type_synonyms = {
|
||
'число': 'Number',
|
||
'строка': 'String',
|
||
'булево': 'Boolean',
|
||
'дата': 'Date',
|
||
'датавремя': 'DateTime',
|
||
'время': 'Time',
|
||
'time': 'Time',
|
||
'number': 'Number',
|
||
'string': 'String',
|
||
'boolean': 'Boolean',
|
||
'date': 'Date',
|
||
'datetime': 'DateTime',
|
||
'bool': 'Boolean',
|
||
# ValueStorage / UUID — прощающий ввод (base64Binary / рус. форма → канон).
|
||
'valuestorage': 'ValueStorage',
|
||
'base64binary': 'ValueStorage',
|
||
'хранилищезначений': 'ValueStorage',
|
||
'хранилищезначения': 'ValueStorage',
|
||
'uuid': 'UUID',
|
||
'уникальныйидентификатор': 'UUID',
|
||
# Reference synonyms (Russian, lowercase)
|
||
'справочникссылка': 'CatalogRef',
|
||
'документссылка': 'DocumentRef',
|
||
'перечислениессылка': 'EnumRef',
|
||
'плансчетовссылка': 'ChartOfAccountsRef',
|
||
'планвидовхарактеристикссылка': 'ChartOfCharacteristicTypesRef',
|
||
'планвидоврасчётассылка': 'ChartOfCalculationTypesRef',
|
||
'планвидоврасчетассылка': 'ChartOfCalculationTypesRef',
|
||
'планобменассылка': 'ExchangePlanRef',
|
||
'бизнеспроцессссылка': 'BusinessProcessRef',
|
||
'задачассылка': 'TaskRef',
|
||
'определяемыйтип': 'DefinedType',
|
||
'definedtype': 'DefinedType',
|
||
# English lowercase ref synonyms
|
||
'catalogref': 'CatalogRef',
|
||
'documentref': 'DocumentRef',
|
||
'enumref': 'EnumRef',
|
||
# Платформенные коллекции (прощающий ввод рус. форм).
|
||
'таблицазначений': 'ValueTable',
|
||
'деревозначений': 'ValueTree',
|
||
'списокзначений': 'ValueListType',
|
||
'стандартныйпериод': 'StandardPeriod',
|
||
}
|
||
|
||
# Платформенные типы, требующие префикса v8: (коллекции/периоды, частые в реквизитах обработок/отчётов).
|
||
v8_platform_types = {"ValueTable", "ValueTree", "ValueList", "ValueListType", "StandardPeriod",
|
||
"StandardBeginningDate", "PointInTime", "TypeDescription", "FixedArray", "FixedMap", "FixedStructure"}
|
||
# Типы со ВЫДЕЛЕННЫМ пространством имён (локальный xmlns на <v8:Type>). prefix — канон корпуса.
|
||
type_namespace_map = {
|
||
"Chart": {"ns": "http://v8.1c.ru/8.2/data/chart", "prefix": "d5p1"},
|
||
"SettingsComposer": {"ns": "http://v8.1c.ru/8.1/data-composition-system/settings", "prefix": "dcsset"},
|
||
"SpreadsheetDocument": {"ns": "http://v8.1c.ru/8.2/data/spreadsheet", "prefix": "mxl"},
|
||
}
|
||
# Типы current-config пространства (cfg:, объявлено в корне): голые и объектные. Ссылочные — отдельно (d5p1).
|
||
cfg_bare_types = {"ConstantsSet", "ReportBuilder", "FilterCriterion"}
|
||
cfg_object_kinds = {"Catalog", "Document", "Enum", "ChartOfAccounts", "ChartOfCharacteristicTypes",
|
||
"ChartOfCalculationTypes", "ExchangePlan", "BusinessProcess", "Task", "InformationRegister",
|
||
"AccumulationRegister", "AccountingRegister", "CalculationRegister", "DataProcessor", "Report",
|
||
"DocumentJournal", "Constant", "ConstantValue", "Sequence", "Recalculation"}
|
||
|
||
def resolve_type_str(type_str):
|
||
if not type_str:
|
||
return type_str
|
||
# Parameterized types: Number(15,2), Строка(100), etc.
|
||
m = re.match(r'^([^(]+)\((.+)\)$', type_str)
|
||
if m:
|
||
base_name = m.group(1).strip()
|
||
params = m.group(2)
|
||
resolved = type_synonyms.get(base_name.lower())
|
||
if resolved:
|
||
return f'{resolved}({params})'
|
||
return type_str
|
||
# Reference types: СправочникСсылка.Организации -> CatalogRef.Организации
|
||
if '.' in type_str:
|
||
dot_idx = type_str.index('.')
|
||
prefix = type_str[:dot_idx]
|
||
suffix = type_str[dot_idx:] # includes the dot
|
||
resolved = type_synonyms.get(prefix.lower())
|
||
if resolved:
|
||
return f'{resolved}{suffix}'
|
||
return type_str
|
||
# Simple name lookup
|
||
resolved = type_synonyms.get(type_str.lower())
|
||
if resolved:
|
||
return resolved
|
||
return type_str
|
||
|
||
def emit_type_content(indent, type_str):
|
||
if not type_str:
|
||
return
|
||
# Composite type: "Type1 + Type2 + Type3"
|
||
if ' + ' in type_str:
|
||
parts = [p.strip() for p in type_str.split('+')]
|
||
for part in parts:
|
||
emit_type_content(indent, part)
|
||
return
|
||
type_str = resolve_type_str(type_str)
|
||
# Boolean
|
||
if type_str == 'Boolean':
|
||
X(f'{indent}<v8:Type>xs:boolean</v8:Type>')
|
||
return
|
||
# String or String(N) or String(N,fixed|variable) — AllowedLength: Variable дефолт / Fixed (фикс. длина).
|
||
m = re.match(r'^String(\((\d+)(\s*,\s*(fixed|variable))?\))?$', type_str)
|
||
if m:
|
||
length = m.group(2) if m.group(2) else '10'
|
||
al = 'Fixed' if (m.group(4) and m.group(4).lower() == 'fixed') else 'Variable'
|
||
X(f'{indent}<v8:Type>xs:string</v8:Type>')
|
||
X(f'{indent}<v8:StringQualifiers>')
|
||
X(f'{indent}\t<v8:Length>{length}</v8:Length>')
|
||
X(f'{indent}\t<v8:AllowedLength>{al}</v8:AllowedLength>')
|
||
X(f'{indent}</v8:StringQualifiers>')
|
||
return
|
||
# Number without params -> Number(10,0)
|
||
if type_str == 'Number':
|
||
X(f'{indent}<v8:Type>xs:decimal</v8:Type>')
|
||
X(f'{indent}<v8:NumberQualifiers>')
|
||
X(f'{indent}\t<v8:Digits>10</v8:Digits>')
|
||
X(f'{indent}\t<v8:FractionDigits>0</v8:FractionDigits>')
|
||
X(f'{indent}\t<v8:AllowedSign>Any</v8:AllowedSign>')
|
||
X(f'{indent}</v8:NumberQualifiers>')
|
||
return
|
||
|
||
# Number(D,F) or Number(D,F,nonneg)
|
||
m = re.match(r'^Number\((\d+),(\d+)(,nonneg)?\)$', type_str)
|
||
if m:
|
||
digits = m.group(1)
|
||
fraction = m.group(2)
|
||
sign = 'Nonnegative' if m.group(3) else 'Any'
|
||
X(f'{indent}<v8:Type>xs:decimal</v8:Type>')
|
||
X(f'{indent}<v8:NumberQualifiers>')
|
||
X(f'{indent}\t<v8:Digits>{digits}</v8:Digits>')
|
||
X(f'{indent}\t<v8:FractionDigits>{fraction}</v8:FractionDigits>')
|
||
X(f'{indent}\t<v8:AllowedSign>{sign}</v8:AllowedSign>')
|
||
X(f'{indent}</v8:NumberQualifiers>')
|
||
return
|
||
# Date / DateTime / Time — общая структура xs:dateTime + DateFractions (различаются лишь составом).
|
||
if type_str in ('Date', 'DateTime', 'Time'):
|
||
X(f'{indent}<v8:Type>xs:dateTime</v8:Type>')
|
||
X(f'{indent}<v8:DateQualifiers>')
|
||
X(f'{indent}\t<v8:DateFractions>{type_str}</v8:DateFractions>')
|
||
X(f'{indent}</v8:DateQualifiers>')
|
||
return
|
||
# TypeSet — тип-множество: ОпределяемыйТип (DefinedType) ИЛИ Характеристика ПВХ (Characteristic).
|
||
if re.match(r'^(DefinedType|Characteristic)\.(.+)$', type_str):
|
||
X(f'{indent}<v8:TypeSet>cfg:{type_str}</v8:TypeSet>')
|
||
return
|
||
# Голый метатип-категория (CatalogRef/DocumentRef/…/AnyRef/AnyIBRef без имени объекта) — множество
|
||
# «любой объект категории» → TypeSet (а не конкретный Type с именем).
|
||
if re.match(r'^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef|AnyRef|AnyIBRef)$', type_str):
|
||
X(f'{indent}<v8:TypeSet>cfg:{type_str}</v8:TypeSet>')
|
||
return
|
||
# ValueStorage (ХранилищеЗначения) — канон v8:ValueStorage (не xs:base64Binary).
|
||
if type_str == 'ValueStorage':
|
||
X(f'{indent}<v8:Type>v8:ValueStorage</v8:Type>')
|
||
return
|
||
# UUID (УникальныйИдентификатор)
|
||
if type_str == 'UUID':
|
||
X(f'{indent}<v8:Type>v8:UUID</v8:Type>')
|
||
return
|
||
# Платформенные типы-коллекции/периоды (ТаблицаЗначений/ДеревоЗначений/…) — канон с префиксом v8:.
|
||
if type_str in v8_platform_types:
|
||
X(f'{indent}<v8:Type>v8:{type_str}</v8:Type>')
|
||
return
|
||
# Типы с выделенным пространством имён (Chart/SettingsComposer/SpreadsheetDocument) — локальный xmlns.
|
||
if type_str in type_namespace_map:
|
||
m2 = type_namespace_map[type_str]
|
||
X(f'{indent}<v8:Type xmlns:{m2["prefix"]}="{m2["ns"]}">{m2["prefix"]}:{type_str}</v8:Type>')
|
||
return
|
||
# Типы current-config (cfg:): голые (ConstantsSet/…) и объектные (CatalogObject.X/DataProcessorObject.X/…).
|
||
if type_str in cfg_bare_types:
|
||
X(f'{indent}<v8:Type>cfg:{type_str}</v8:Type>')
|
||
return
|
||
m3 = re.match(r'^(\w+)(Object|List|Manager|Selection|RecordSet|RecordKey|RecordManager)\.(.+)$', type_str)
|
||
if m3 and m3.group(1) in cfg_object_kinds:
|
||
X(f'{indent}<v8:Type>cfg:{type_str}</v8:Type>')
|
||
return
|
||
# Голый объектный метатип (без имени): Object/RecordSet + ConstantValueManager → «любой объект категории» = TypeSet cfg:;
|
||
# прочие Manager/List/Selection → сам тип менеджера/списка = Type cfg: (напр. в Source подписки на событие).
|
||
mbs = re.match(r'^(\w+)(Object|RecordSet)$', type_str)
|
||
if (mbs and mbs.group(1) in cfg_object_kinds) or type_str == 'ConstantValueManager':
|
||
X(f'{indent}<v8:TypeSet>cfg:{type_str}</v8:TypeSet>')
|
||
return
|
||
mbt = re.match(r'^(\w+)(Manager|List|Selection|RecordKey|RecordManager)$', type_str)
|
||
if mbt and mbt.group(1) in cfg_object_kinds:
|
||
X(f'{indent}<v8:Type>cfg:{type_str}</v8:Type>')
|
||
return
|
||
|
||
# Reference types — use local xmlns declaration for 1C compatibility
|
||
m = re.match(r'^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|BusinessProcessRoutePointRef|TaskRef)\.(.+)$', type_str)
|
||
if m:
|
||
X(f'{indent}<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:{type_str}</v8:Type>')
|
||
return
|
||
# Fallback
|
||
X(f'{indent}<v8:Type>{type_str}</v8:Type>')
|
||
|
||
def emit_value_type(indent, type_str):
|
||
X(f'{indent}<Type>')
|
||
emit_type_content(f'{indent}\t', type_str)
|
||
X(f'{indent}</Type>')
|
||
|
||
# --- FillValue (значение заполнения реквизита) ---
|
||
# Пара FillFromFillingValue+FillValue — единый блок «заполнения» (недоступен у реквизитов ТЧ).
|
||
# Форма пустого FillValue зависит от типа реквизита (то же значение по умолчанию, что и «пустое»
|
||
# значение типа): String→typed-empty, Number→0, всё остальное (Boolean/Date/Ref/составной/TypeSet)→nil.
|
||
# Реальное значение задаётся ключом `fillValue` (интерпретация по типу реквизита; см. §4.2 spec).
|
||
|
||
def get_fill_type_category(type_str):
|
||
if not type_str:
|
||
return 'String' # реквизит без типа → неквалифиц. строка
|
||
if '+' in type_str:
|
||
return 'Other' # составной тип → nil-дефолт
|
||
t = resolve_type_str(type_str)
|
||
if re.match(r'^Boolean$', t):
|
||
return 'Boolean'
|
||
if re.match(r'^String(\(|$)', t):
|
||
return 'String'
|
||
if re.match(r'^Number(\(|$)', t):
|
||
return 'Number'
|
||
if re.match(r'^(Date|DateTime)$', t):
|
||
return 'Date'
|
||
return 'Other' # ссылки, TypeSet, ValueStorage, … → nil-дефолт
|
||
|
||
# Прощающий ввод для ссылочных путей DTR: рус/англ корни, ПустаяСсылка/EmptyRef, ЗначениеПеречисления/EnumValue.
|
||
fill_ref_roots = {
|
||
'перечисление': 'Enum', 'справочник': 'Catalog', 'документ': 'Document',
|
||
'плансчетов': 'ChartOfAccounts', 'планвидовхарактеристик': 'ChartOfCharacteristicTypes',
|
||
'планвидоврасчета': 'ChartOfCalculationTypes', 'планвидоврасчёта': 'ChartOfCalculationTypes',
|
||
'планобмена': 'ExchangePlan', 'бизнеспроцесс': 'BusinessProcess', 'задача': 'Task',
|
||
'enum': 'Enum', 'catalog': 'Catalog', 'document': 'Document', 'chartofaccounts': 'ChartOfAccounts',
|
||
'chartofcharacteristictypes': 'ChartOfCharacteristicTypes', 'chartofcalculationtypes': 'ChartOfCalculationTypes',
|
||
'exchangeplan': 'ExchangePlan', 'businessprocess': 'BusinessProcess', 'task': 'Task',
|
||
}
|
||
fill_empty_ref_words = ('emptyref', 'пустаяссылка')
|
||
fill_enum_val_words = ('enumvalue', 'значениеперечисления')
|
||
fill_bool_true = ('true', 'истина', 'да')
|
||
fill_bool_false = ('false', 'ложь', 'нет')
|
||
# Прощающий ввод MDObjectRef-путей (Location/Content функц. опции): русские корни метаданных + подвиды →
|
||
# английские. Виды на ЧЁТНЫХ позициях (0,2,4…); имена (нечётные) не трогаем. Английские пути неизменны.
|
||
md_ref_roots = {
|
||
'справочник': 'Catalog', 'документ': 'Document', 'перечисление': 'Enum', 'константа': 'Constant',
|
||
'регистрсведений': 'InformationRegister', 'регистрнакопления': 'AccumulationRegister',
|
||
'регистрбухгалтерии': 'AccountingRegister', 'регистррасчета': 'CalculationRegister', 'регистррасчёта': 'CalculationRegister',
|
||
'плансчетов': 'ChartOfAccounts', 'планвидовхарактеристик': 'ChartOfCharacteristicTypes',
|
||
'планвидоврасчета': 'ChartOfCalculationTypes', 'планвидоврасчёта': 'ChartOfCalculationTypes',
|
||
'планобмена': 'ExchangePlan', 'бизнеспроцесс': 'BusinessProcess', 'задача': 'Task',
|
||
'журналдокументов': 'DocumentJournal', 'отчет': 'Report', 'отчёт': 'Report', 'обработка': 'DataProcessor',
|
||
'табличнаячасть': 'TabularSection', 'реквизит': 'Attribute', 'измерение': 'Dimension', 'ресурс': 'Resource',
|
||
'стандартныйреквизит': 'StandardAttribute', 'значениеперечисления': 'EnumValue', 'команда': 'Command',
|
||
'признакучета': 'AccountingFlag', 'признакучёта': 'AccountingFlag',
|
||
}
|
||
|
||
def normalize_md_object_ref(ref):
|
||
if not ref or '.' not in ref:
|
||
return ref
|
||
parts = ref.split('.')
|
||
for k in range(0, len(parts), 2):
|
||
t = md_ref_roots.get(parts[k].lower())
|
||
if t:
|
||
parts[k] = t
|
||
return '.'.join(parts)
|
||
# Значения платформенного перечисления ВидСчета (ent:AccountType) — FillValue реквизита Тип у Плана счетов.
|
||
ACCOUNT_TYPE_VALUES = ('Active', 'Passive', 'ActivePassive')
|
||
# «Только обороты» (<Turnover>) — предопределённый признак учёта субконто; токен в списке flags наравне с добавленными.
|
||
SUBCONTO_TURNOVER_TOKENS = ('turnover', 'толькообороты', 'только обороты', 'оборотный')
|
||
# XxxRef (тип реквизита) → корень DTR-пути (для разворота короткой записи значения).
|
||
fill_ref_kind_root = {
|
||
'catalogref': 'Catalog', 'documentref': 'Document', 'enumref': 'Enum',
|
||
'chartofaccountsref': 'ChartOfAccounts', 'chartofcharacteristictypesref': 'ChartOfCharacteristicTypes',
|
||
'chartofcalculationtypesref': 'ChartOfCalculationTypes', 'exchangeplanref': 'ExchangePlan',
|
||
'businessprocessref': 'BusinessProcess', 'taskref': 'Task',
|
||
}
|
||
|
||
def normalize_fill_ref(s):
|
||
"""Строка → нормализованный DTR-путь ЛИБО None (не ссылка)."""
|
||
if not s:
|
||
return None
|
||
if re.match(r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\.[0-9a-fA-F-]+$', s):
|
||
return s
|
||
parts = s.split('.')
|
||
if len(parts) < 2:
|
||
return None
|
||
root = fill_ref_roots.get(parts[0].lower())
|
||
if not root:
|
||
return None
|
||
type_name = parts[1]
|
||
if root == 'Enum':
|
||
if len(parts) == 2:
|
||
return None
|
||
if len(parts) == 3:
|
||
if parts[2].lower() in fill_empty_ref_words:
|
||
return f'Enum.{type_name}.EmptyRef'
|
||
return f'Enum.{type_name}.EnumValue.{parts[2]}'
|
||
if parts[2].lower() in fill_enum_val_words:
|
||
rest = '.'.join(parts[3:])
|
||
else:
|
||
rest = '.'.join(parts[2:])
|
||
return f'Enum.{type_name}.EnumValue.{rest}'
|
||
tail = list(parts[1:])
|
||
for i in range(len(tail)):
|
||
if tail[i].lower() in fill_empty_ref_words:
|
||
tail[i] = 'EmptyRef'
|
||
return f'{root}.' + '.'.join(tail)
|
||
|
||
def expand_fill_short_ref(s, type_str):
|
||
"""Короткая запись значения ссылочного реквизита (без точки) → полный DTR-путь по типу, либо None."""
|
||
if not type_str:
|
||
return None
|
||
if '+' in type_str: # составной тип — короткая форма неоднозначна
|
||
return None
|
||
t = resolve_type_str(type_str)
|
||
m = re.match(r'^(\w+Ref)\.(.+)$', t)
|
||
if not m:
|
||
return None
|
||
root = fill_ref_kind_root.get(m.group(1).lower())
|
||
if not root:
|
||
return None
|
||
type_name = m.group(2)
|
||
if s.lower() in fill_empty_ref_words:
|
||
return f'{root}.{type_name}.EmptyRef'
|
||
if root == 'Enum':
|
||
return f'Enum.{type_name}.EnumValue.{s}'
|
||
return f'{root}.{type_name}.{s}'
|
||
|
||
def resolve_fill_value_spec(s, type_str):
|
||
"""Строковый spec → (xsi_type, text). Интерпретация по типу реквизита."""
|
||
cat = get_fill_type_category(type_str)
|
||
if s == '':
|
||
return ('xs:string', '')
|
||
if cat == 'String':
|
||
return ('xs:string', s)
|
||
if cat == 'Boolean' or s.lower() in fill_bool_true or s.lower() in fill_bool_false:
|
||
if s.lower() in fill_bool_true:
|
||
return ('xs:boolean', 'true')
|
||
if s.lower() in fill_bool_false:
|
||
return ('xs:boolean', 'false')
|
||
if cat == 'Number':
|
||
return ('xs:decimal', s)
|
||
if cat == 'Date' or re.match(r'^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2})?$', s):
|
||
if re.match(r'^\d{4}-\d{2}-\d{2}$', s):
|
||
s = f'{s}T00:00:00'
|
||
return ('xs:dateTime', s)
|
||
ref = normalize_fill_ref(s)
|
||
if ref:
|
||
return ('xr:DesignTimeRef', ref)
|
||
short = expand_fill_short_ref(s, type_str)
|
||
if short:
|
||
return ('xr:DesignTimeRef', short)
|
||
return ('xs:string', s)
|
||
|
||
def format_fill_num(n):
|
||
if isinstance(n, bool):
|
||
return 'true' if n else 'false'
|
||
return str(n)
|
||
|
||
def emit_fill_value(indent, type_str, spec, has_spec, type_empty=False):
|
||
"""spec — значение ключа fillValue (None при явном nil-override), has_spec — присутствует ли ключ.
|
||
type_empty — реквизит с пустым <Type/>: форма пустого значения nil, НЕ xs:string."""
|
||
cat = 'Other' if type_empty else get_fill_type_category(type_str)
|
||
if not has_spec:
|
||
if cat == 'String':
|
||
X(f'{indent}<FillValue xsi:type="xs:string"/>')
|
||
elif cat == 'Number':
|
||
X(f'{indent}<FillValue xsi:type="xs:decimal">0</FillValue>')
|
||
else:
|
||
X(f'{indent}<FillValue xsi:nil="true"/>')
|
||
return
|
||
if spec is None:
|
||
X(f'{indent}<FillValue xsi:nil="true"/>') # явный nil-override
|
||
return
|
||
if isinstance(spec, dict) and spec.get('nil') is True:
|
||
X(f'{indent}<FillValue xsi:nil="true"/>') # явный nil на типизированном (маркер декомпилятора)
|
||
return
|
||
if isinstance(spec, dict) and spec.get('emptyRef') is True:
|
||
X(f'{indent}<FillValue xsi:type="xr:DesignTimeRef"/>') # пустая ссылка (маркер декомпилятора)
|
||
return
|
||
if isinstance(spec, bool):
|
||
X(f'{indent}<FillValue xsi:type="xs:boolean">{"true" if spec else "false"}</FillValue>')
|
||
return
|
||
if isinstance(spec, (int, float)):
|
||
X(f'{indent}<FillValue xsi:type="xs:decimal">{format_fill_num(spec)}</FillValue>')
|
||
return
|
||
xsi_type, text = resolve_fill_value_spec(str(spec), type_str)
|
||
if text == '' and xsi_type == 'xs:string':
|
||
X(f'{indent}<FillValue xsi:type="xs:string"/>')
|
||
return
|
||
X(f'{indent}<FillValue xsi:type="{xsi_type}">{esc_xml_text(text)}</FillValue>')
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 5. Attribute shorthand parser
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def build_type_str(obj):
|
||
t = str(obj.get('valueType') or obj.get('type') or '')
|
||
if t and '(' not in t:
|
||
if t == 'String' and obj.get('length'):
|
||
t = f"String({obj['length']})"
|
||
elif t == 'Number' and obj.get('length'):
|
||
prec = obj.get('precision', 0)
|
||
nn = ',nonneg' if obj.get('nonneg') or obj.get('nonnegative') else ''
|
||
t = f"Number({obj['length']},{prec}{nn})"
|
||
return t
|
||
|
||
def parse_attribute_shorthand(val):
|
||
if isinstance(val, str):
|
||
parsed = {
|
||
'name': '',
|
||
'type': '',
|
||
'typeEmpty': False,
|
||
'synonym': '',
|
||
'comment': '',
|
||
'flags': [],
|
||
'fillChecking': '',
|
||
'indexing': '',
|
||
'hasFillValue': False,
|
||
'fillValue': None,
|
||
}
|
||
parts = val.split('|', 1)
|
||
main_part = parts[0].strip()
|
||
if len(parts) > 1:
|
||
flag_str = parts[1].strip()
|
||
parsed['flags'] = [f.strip().lower() for f in flag_str.split(',') if f.strip()]
|
||
colon_parts = main_part.split(':', 1)
|
||
parsed['name'] = colon_parts[0].strip()
|
||
if len(colon_parts) > 1:
|
||
parsed['type'] = colon_parts[1].strip()
|
||
parsed['synonym'] = split_camel_case(parsed['name'])
|
||
return parsed
|
||
# Object form. synonym/tooltip — сквозной проброс (строка ИЛИ dict {ru,en}), НЕ стрингифаим.
|
||
name = str(val.get('name', ''))
|
||
# fillCheck — синоним fillChecking (bool true→ShowError). quickChoice — прощаем bool (true→Use, false→DontUse).
|
||
if val.get('fillChecking'):
|
||
fc = str(val['fillChecking'])
|
||
elif val.get('fillCheck') is not None:
|
||
fc = ('ShowError' if val['fillCheck'] else '') if isinstance(val['fillCheck'], bool) else str(val['fillCheck'])
|
||
else:
|
||
fc = ''
|
||
if val.get('quickChoice') is not None:
|
||
qc = ('Use' if val['quickChoice'] else 'DontUse') if isinstance(val['quickChoice'], bool) else str(val['quickChoice'])
|
||
else:
|
||
qc = ''
|
||
return {
|
||
'name': name,
|
||
'type': build_type_str(val),
|
||
# Явный `type: ""` (ключ есть, значение пустое) ≠ отсутствие: пустой <Type/> (реквизит без типа).
|
||
'typeEmpty': ('type' in val and str(val.get('type') or '').strip() == '' and not val.get('valueType')),
|
||
'synonym': val['synonym'] if val.get('synonym') is not None else split_camel_case(name),
|
||
'tooltip': val.get('tooltip'),
|
||
'comment': str(val['comment']) if val.get('comment') else '',
|
||
# Лоуэркейз как в строковом пути (стр.809): проверки флагов регистронезависимы (зеркало PS -contains).
|
||
'flags': [str(f).strip().lower() for f in val.get('flags', [])],
|
||
'fillChecking': fc,
|
||
'indexing': str(val['indexing']) if val.get('indexing') else '',
|
||
'multiLine': True if val.get('multiLine') is True else False,
|
||
'choiceHistoryOnInput': str(val['choiceHistoryOnInput']) if val.get('choiceHistoryOnInput') else '',
|
||
'fullTextSearch': str(val['fullTextSearch']) if val.get('fullTextSearch') else '',
|
||
'fillFromFillingValue': True if val.get('fillFromFillingValue') is True else False,
|
||
'createOnInput': str(val['createOnInput']) if val.get('createOnInput') else '',
|
||
'quickChoice': qc,
|
||
'dataHistory': str(val['dataHistory']) if val.get('dataHistory') else '',
|
||
'use': str(val['use']) if val.get('use') else '',
|
||
'passwordMode': True if val.get('passwordMode') is True else False,
|
||
'format': val.get('format'),
|
||
'editFormat': val.get('editFormat'),
|
||
'mask': str(val['mask']) if val.get('mask') else '',
|
||
'extendedEdit': True if val.get('extendedEdit') is True else False,
|
||
'markNegatives': True if val.get('markNegatives') is True else False,
|
||
'choiceForm': str(val['choiceForm']) if val.get('choiceForm') else '',
|
||
'choiceFoldersAndItems': str(val['choiceFoldersAndItems']) if val.get('choiceFoldersAndItems') else '',
|
||
'minValue': val.get('minValue'),
|
||
'maxValue': val.get('maxValue'),
|
||
'hasFillValue': ('fillValue' in val),
|
||
'fillValue': val.get('fillValue'),
|
||
'linkByType': val.get('linkByType'),
|
||
'choiceParameterLinks': val.get('choiceParameterLinks'),
|
||
'choiceParameters': val.get('choiceParameters'),
|
||
'master': val.get('master') is True,
|
||
'mainFilter': val.get('mainFilter') is True,
|
||
'denyIncompleteValues': val.get('denyIncompleteValues') is True,
|
||
'useInTotals': (val.get('useInTotals') is True) if val.get('useInTotals') is not None else True,
|
||
'baseDimension': val.get('baseDimension') is True,
|
||
'scheduleLink': val.get('scheduleLink'),
|
||
'balance': val.get('balance') is True,
|
||
'accountingFlag': val.get('accountingFlag'),
|
||
'extDimensionAccountingFlag': val.get('extDimensionAccountingFlag'),
|
||
'addressingDimension': val.get('addressingDimension'),
|
||
}
|
||
|
||
def parse_enum_value_shorthand(val):
|
||
if isinstance(val, str):
|
||
return {
|
||
'name': val,
|
||
'synonym': split_camel_case(val),
|
||
'comment': '',
|
||
}
|
||
name = str(val.get('name', ''))
|
||
return {
|
||
'name': name,
|
||
# строка ИЛИ {ru,en} → emit_mltext; None → авто из имени
|
||
'synonym': val['synonym'] if val.get('synonym') is not None else split_camel_case(name),
|
||
'comment': str(val['comment']) if val.get('comment') else '',
|
||
}
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 6. GeneratedType categories
|
||
# ---------------------------------------------------------------------------
|
||
|
||
generated_types = {
|
||
'Catalog': [
|
||
{'prefix': 'CatalogObject', 'category': 'Object'},
|
||
{'prefix': 'CatalogRef', 'category': 'Ref'},
|
||
{'prefix': 'CatalogSelection', 'category': 'Selection'},
|
||
{'prefix': 'CatalogList', 'category': 'List'},
|
||
{'prefix': 'CatalogManager', 'category': 'Manager'},
|
||
],
|
||
'Document': [
|
||
{'prefix': 'DocumentObject', 'category': 'Object'},
|
||
{'prefix': 'DocumentRef', 'category': 'Ref'},
|
||
{'prefix': 'DocumentSelection', 'category': 'Selection'},
|
||
{'prefix': 'DocumentList', 'category': 'List'},
|
||
{'prefix': 'DocumentManager', 'category': 'Manager'},
|
||
],
|
||
'Enum': [
|
||
{'prefix': 'EnumRef', 'category': 'Ref'},
|
||
{'prefix': 'EnumManager', 'category': 'Manager'},
|
||
{'prefix': 'EnumList', 'category': 'List'},
|
||
],
|
||
'Constant': [
|
||
{'prefix': 'ConstantManager', 'category': 'Manager'},
|
||
{'prefix': 'ConstantValueManager', 'category': 'ValueManager'},
|
||
{'prefix': 'ConstantValueKey', 'category': 'ValueKey'},
|
||
],
|
||
'InformationRegister': [
|
||
{'prefix': 'InformationRegisterRecord', 'category': 'Record'},
|
||
{'prefix': 'InformationRegisterManager', 'category': 'Manager'},
|
||
{'prefix': 'InformationRegisterSelection', 'category': 'Selection'},
|
||
{'prefix': 'InformationRegisterList', 'category': 'List'},
|
||
{'prefix': 'InformationRegisterRecordSet', 'category': 'RecordSet'},
|
||
{'prefix': 'InformationRegisterRecordKey', 'category': 'RecordKey'},
|
||
{'prefix': 'InformationRegisterRecordManager', 'category': 'RecordManager'},
|
||
],
|
||
'AccumulationRegister': [
|
||
{'prefix': 'AccumulationRegisterRecord', 'category': 'Record'},
|
||
{'prefix': 'AccumulationRegisterManager', 'category': 'Manager'},
|
||
{'prefix': 'AccumulationRegisterSelection', 'category': 'Selection'},
|
||
{'prefix': 'AccumulationRegisterList', 'category': 'List'},
|
||
{'prefix': 'AccumulationRegisterRecordSet', 'category': 'RecordSet'},
|
||
{'prefix': 'AccumulationRegisterRecordKey', 'category': 'RecordKey'},
|
||
],
|
||
'AccountingRegister': [
|
||
{'prefix': 'AccountingRegisterRecord', 'category': 'Record'},
|
||
{'prefix': 'AccountingRegisterExtDimensions', 'category': 'ExtDimensions'},
|
||
{'prefix': 'AccountingRegisterRecordSet', 'category': 'RecordSet'},
|
||
{'prefix': 'AccountingRegisterRecordKey', 'category': 'RecordKey'},
|
||
{'prefix': 'AccountingRegisterSelection', 'category': 'Selection'},
|
||
{'prefix': 'AccountingRegisterList', 'category': 'List'},
|
||
{'prefix': 'AccountingRegisterManager', 'category': 'Manager'},
|
||
],
|
||
'CalculationRegister': [
|
||
{'prefix': 'CalculationRegisterRecord', 'category': 'Record'},
|
||
{'prefix': 'CalculationRegisterManager', 'category': 'Manager'},
|
||
{'prefix': 'CalculationRegisterSelection', 'category': 'Selection'},
|
||
{'prefix': 'CalculationRegisterList', 'category': 'List'},
|
||
{'prefix': 'CalculationRegisterRecordSet', 'category': 'RecordSet'},
|
||
{'prefix': 'CalculationRegisterRecordKey', 'category': 'RecordKey'},
|
||
{'prefix': 'RecalculationsManager', 'category': 'Recalcs'},
|
||
],
|
||
'ChartOfAccounts': [
|
||
{'prefix': 'ChartOfAccountsObject', 'category': 'Object'},
|
||
{'prefix': 'ChartOfAccountsRef', 'category': 'Ref'},
|
||
{'prefix': 'ChartOfAccountsSelection', 'category': 'Selection'},
|
||
{'prefix': 'ChartOfAccountsList', 'category': 'List'},
|
||
{'prefix': 'ChartOfAccountsManager', 'category': 'Manager'},
|
||
{'prefix': 'ChartOfAccountsExtDimensionTypes', 'category': 'ExtDimensionTypes'},
|
||
{'prefix': 'ChartOfAccountsExtDimensionTypesRow', 'category': 'ExtDimensionTypesRow'},
|
||
],
|
||
'ChartOfCharacteristicTypes': [
|
||
{'prefix': 'ChartOfCharacteristicTypesObject', 'category': 'Object'},
|
||
{'prefix': 'ChartOfCharacteristicTypesRef', 'category': 'Ref'},
|
||
{'prefix': 'ChartOfCharacteristicTypesSelection', 'category': 'Selection'},
|
||
{'prefix': 'ChartOfCharacteristicTypesList', 'category': 'List'},
|
||
{'prefix': 'Characteristic', 'category': 'Characteristic'},
|
||
{'prefix': 'ChartOfCharacteristicTypesManager', 'category': 'Manager'},
|
||
],
|
||
'ChartOfCalculationTypes': [
|
||
{'prefix': 'ChartOfCalculationTypesObject', 'category': 'Object'},
|
||
{'prefix': 'ChartOfCalculationTypesRef', 'category': 'Ref'},
|
||
{'prefix': 'ChartOfCalculationTypesSelection', 'category': 'Selection'},
|
||
{'prefix': 'ChartOfCalculationTypesList', 'category': 'List'},
|
||
{'prefix': 'ChartOfCalculationTypesManager', 'category': 'Manager'},
|
||
{'prefix': 'DisplacingCalculationTypes', 'category': 'DisplacingCalculationTypes'},
|
||
{'prefix': 'DisplacingCalculationTypesRow', 'category': 'DisplacingCalculationTypesRow'},
|
||
{'prefix': 'BaseCalculationTypes', 'category': 'BaseCalculationTypes'},
|
||
{'prefix': 'BaseCalculationTypesRow', 'category': 'BaseCalculationTypesRow'},
|
||
{'prefix': 'LeadingCalculationTypes', 'category': 'LeadingCalculationTypes'},
|
||
{'prefix': 'LeadingCalculationTypesRow', 'category': 'LeadingCalculationTypesRow'},
|
||
],
|
||
'BusinessProcess': [
|
||
{'prefix': 'BusinessProcessObject', 'category': 'Object'},
|
||
{'prefix': 'BusinessProcessRef', 'category': 'Ref'},
|
||
{'prefix': 'BusinessProcessSelection', 'category': 'Selection'},
|
||
{'prefix': 'BusinessProcessList', 'category': 'List'},
|
||
{'prefix': 'BusinessProcessManager', 'category': 'Manager'},
|
||
{'prefix': 'BusinessProcessRoutePointRef', 'category': 'RoutePointRef'},
|
||
],
|
||
'Task': [
|
||
{'prefix': 'TaskObject', 'category': 'Object'},
|
||
{'prefix': 'TaskRef', 'category': 'Ref'},
|
||
{'prefix': 'TaskSelection', 'category': 'Selection'},
|
||
{'prefix': 'TaskList', 'category': 'List'},
|
||
{'prefix': 'TaskManager', 'category': 'Manager'},
|
||
],
|
||
'ExchangePlan': [
|
||
{'prefix': 'ExchangePlanObject', 'category': 'Object'},
|
||
{'prefix': 'ExchangePlanRef', 'category': 'Ref'},
|
||
{'prefix': 'ExchangePlanSelection', 'category': 'Selection'},
|
||
{'prefix': 'ExchangePlanList', 'category': 'List'},
|
||
{'prefix': 'ExchangePlanManager', 'category': 'Manager'},
|
||
],
|
||
'DefinedType': [
|
||
{'prefix': 'DefinedType', 'category': 'DefinedType'},
|
||
],
|
||
'DocumentJournal': [
|
||
{'prefix': 'DocumentJournalSelection', 'category': 'Selection'},
|
||
{'prefix': 'DocumentJournalList', 'category': 'List'},
|
||
{'prefix': 'DocumentJournalManager', 'category': 'Manager'},
|
||
],
|
||
'Report': [
|
||
{'prefix': 'ReportObject', 'category': 'Object'},
|
||
{'prefix': 'ReportManager', 'category': 'Manager'},
|
||
],
|
||
'DataProcessor': [
|
||
{'prefix': 'DataProcessorObject', 'category': 'Object'},
|
||
{'prefix': 'DataProcessorManager', 'category': 'Manager'},
|
||
],
|
||
'Sequence': [
|
||
{'prefix': 'SequenceRecord', 'category': 'Record'},
|
||
{'prefix': 'SequenceManager', 'category': 'Manager'},
|
||
{'prefix': 'SequenceRecordSet', 'category': 'RecordSet'},
|
||
],
|
||
'FilterCriterion': [
|
||
{'prefix': 'FilterCriterionManager', 'category': 'Manager'},
|
||
{'prefix': 'FilterCriterionList', 'category': 'List'},
|
||
],
|
||
'SettingsStorage': [
|
||
{'prefix': 'SettingsStorageManager', 'category': 'Manager'},
|
||
],
|
||
'WSReference': [
|
||
{'prefix': 'WSReferenceManager', 'category': 'Manager'},
|
||
],
|
||
}
|
||
|
||
def emit_internal_info(indent, object_type, object_name):
|
||
types = generated_types.get(object_type)
|
||
if not types:
|
||
return
|
||
X(f'{indent}<InternalInfo>')
|
||
if object_type == 'ExchangePlan':
|
||
X(f'{indent}\t<xr:ThisNode>{new_uuid()}</xr:ThisNode>')
|
||
for gt in types:
|
||
full_name = f"{gt['prefix']}.{object_name}"
|
||
X(f'{indent}\t<xr:GeneratedType name="{full_name}" category="{gt["category"]}">')
|
||
X(f'{indent}\t\t<xr:TypeId>{new_uuid()}</xr:TypeId>')
|
||
X(f'{indent}\t\t<xr:ValueId>{new_uuid()}</xr:ValueId>')
|
||
X(f'{indent}\t</xr:GeneratedType>')
|
||
X(f'{indent}</InternalInfo>')
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 7. StandardAttributes
|
||
# ---------------------------------------------------------------------------
|
||
|
||
standard_attributes_by_type = {
|
||
'Catalog': ['PredefinedDataName', 'Predefined', 'Ref', 'DeletionMark', 'IsFolder', 'Owner', 'Parent', 'Description', 'Code'],
|
||
'Document': ['Posted', 'Ref', 'DeletionMark', 'Date', 'Number'],
|
||
'Enum': ['Order', 'Ref'],
|
||
'InformationRegister': ['Active', 'LineNumber', 'Recorder', 'Period'],
|
||
'AccumulationRegister': ['Active', 'LineNumber', 'Recorder', 'Period'],
|
||
'AccountingRegister': ['Active', 'Period', 'Recorder', 'LineNumber', 'Account'],
|
||
'CalculationRegister': ['Active', 'Recorder', 'LineNumber', 'RegistrationPeriod', 'CalculationType', 'ReversingEntry'],
|
||
'ChartOfAccounts': ['PredefinedDataName', 'Order', 'OffBalance', 'Type', 'Description', 'Code', 'Parent', 'Predefined', 'DeletionMark', 'Ref'],
|
||
'ChartOfCharacteristicTypes': ['PredefinedDataName', 'Predefined', 'Ref', 'DeletionMark', 'Description', 'Code', 'Parent', 'ValueType'],
|
||
'ChartOfCalculationTypes': ['PredefinedDataName', 'Predefined', 'Ref', 'DeletionMark', 'ActionPeriodIsBasic', 'Description', 'Code'],
|
||
'BusinessProcess': ['Ref', 'DeletionMark', 'Date', 'Number', 'Started', 'Completed', 'HeadTask'],
|
||
'Task': ['Ref', 'DeletionMark', 'Date', 'Number', 'Executed', 'Description', 'RoutePoint', 'BusinessProcess'],
|
||
'ExchangePlan': ['Ref', 'DeletionMark', 'Code', 'Description', 'ThisNode', 'SentNo', 'ReceivedNo'],
|
||
'DocumentJournal': ['Type', 'Ref', 'Date', 'Posted', 'DeletionMark', 'Number'],
|
||
}
|
||
|
||
# Профиль материализованного блока StandardAttributes (см. коммент в .ps1). Пока только Catalog.
|
||
std_attr_profile = {
|
||
'Catalog': {
|
||
'Owner': {'FillChecking': 'ShowError', 'FillFromFillingValue': 'true'},
|
||
'Parent': {'FillFromFillingValue': 'true'},
|
||
'Description': {'FillChecking': 'ShowError'},
|
||
},
|
||
# ExchangePlan: блок при кастомизации; Наименование/Код → FillChecking=ShowError (корпус 40/38 из 41).
|
||
'ExchangePlan': {
|
||
'Description': {'FillChecking': 'ShowError'},
|
||
'Code': {'FillChecking': 'ShowError'},
|
||
},
|
||
# ChartOfCharacteristicTypes: Наименование → FillChecking=ShowError (21/23), Родитель → FFV=true (23/23).
|
||
'ChartOfCharacteristicTypes': {
|
||
'Description': {'FillChecking': 'ShowError'},
|
||
'Parent': {'FillFromFillingValue': 'true'},
|
||
},
|
||
# ChartOfAccounts: Наименование/Код → FillChecking=ShowError (3/3), Родитель → FFV=true (3/3).
|
||
'ChartOfAccounts': {
|
||
'Description': {'FillChecking': 'ShowError'},
|
||
'Code': {'FillChecking': 'ShowError'},
|
||
'Parent': {'FillFromFillingValue': 'true'},
|
||
},
|
||
# ChartOfCalculationTypes: Наименование → FillChecking=ShowError (Код здесь DontCheck).
|
||
'ChartOfCalculationTypes': {
|
||
'Description': {'FillChecking': 'ShowError'},
|
||
},
|
||
# Document: Дата → FillChecking=ShowError (974/1010 доков acc+erp; дата обязательна).
|
||
'Document': {
|
||
'Date': {'FillChecking': 'ShowError'},
|
||
},
|
||
}
|
||
|
||
# ov — dict переопределений (профиль + DSL): FillChecking, FillFromFillingValue, Synonym,
|
||
# FullTextSearch, DataHistory. Прочие поля — фиксированный schema-дефолт.
|
||
def emit_standard_attribute(indent, attr_name, ov=None):
|
||
ov = ov or {}
|
||
fc = ov.get('FillChecking', 'DontCheck')
|
||
ffv = ov.get('FillFromFillingValue', 'false')
|
||
dh = ov.get('DataHistory', 'Use')
|
||
fts = ov.get('FullTextSearch', 'Use')
|
||
syn = ov.get('Synonym', '')
|
||
tt = ov.get('ToolTip', '')
|
||
cf = ov.get('ChoiceForm', '')
|
||
cmt = ov.get('Comment', '')
|
||
msk = ov.get('Mask', '')
|
||
fmt = ov.get('Format')
|
||
efmt = ov.get('EditFormat')
|
||
chi = ov.get('ChoiceHistoryOnInput', 'Auto')
|
||
X(f'{indent}<xr:StandardAttribute name="{attr_name}">')
|
||
# LinkByType стандартного реквизита (напр. ExtDimensionN→Account у регистра бухгалтерии). DataPath verbatim.
|
||
lbt = ov.get('LinkByType')
|
||
if lbt:
|
||
lbt_dp = str(lbt.get('dataPath')) if isinstance(lbt, dict) else str(lbt)
|
||
lbt_li = lbt.get('linkItem', 0) if isinstance(lbt, dict) else 0
|
||
X(f'{indent}\t<xr:LinkByType>')
|
||
X(f'{indent}\t\t<xr:DataPath>{esc_xml(lbt_dp)}</xr:DataPath>')
|
||
X(f'{indent}\t\t<xr:LinkItem>{lbt_li}</xr:LinkItem>')
|
||
X(f'{indent}\t</xr:LinkByType>')
|
||
else:
|
||
X(f'{indent}\t<xr:LinkByType/>')
|
||
X(f'{indent}\t<xr:FillChecking>{fc}</xr:FillChecking>')
|
||
X(f'{indent}\t<xr:MultiLine>false</xr:MultiLine>')
|
||
X(f'{indent}\t<xr:FillFromFillingValue>{ffv}</xr:FillFromFillingValue>')
|
||
X(f'{indent}\t<xr:CreateOnInput>Auto</xr:CreateOnInput>')
|
||
X(f'{indent}\t<xr:MaxValue xsi:nil="true"/>')
|
||
emit_mltext(f'{indent}\t', 'xr:ToolTip', tt)
|
||
X(f'{indent}\t<xr:ExtendedEdit>false</xr:ExtendedEdit>')
|
||
emit_mltext(f'{indent}\t', 'xr:Format', fmt)
|
||
if cf:
|
||
X(f'{indent}\t<xr:ChoiceForm>{esc_xml(str(cf))}</xr:ChoiceForm>')
|
||
else:
|
||
X(f'{indent}\t<xr:ChoiceForm/>')
|
||
X(f'{indent}\t<xr:QuickChoice>Auto</xr:QuickChoice>')
|
||
X(f'{indent}\t<xr:ChoiceHistoryOnInput>{chi}</xr:ChoiceHistoryOnInput>')
|
||
emit_mltext(f'{indent}\t', 'xr:EditFormat', efmt)
|
||
X(f'{indent}\t<xr:PasswordMode>false</xr:PasswordMode>')
|
||
X(f'{indent}\t<xr:DataHistory>{dh}</xr:DataHistory>')
|
||
X(f'{indent}\t<xr:MarkNegatives>false</xr:MarkNegatives>')
|
||
X(f'{indent}\t<xr:MinValue xsi:nil="true"/>')
|
||
emit_mltext(f'{indent}\t', 'xr:Synonym', syn)
|
||
if cmt:
|
||
X(f'{indent}\t<xr:Comment>{esc_xml_text(str(cmt))}</xr:Comment>')
|
||
else:
|
||
X(f'{indent}\t<xr:Comment/>')
|
||
X(f'{indent}\t<xr:FullTextSearch>{fts}</xr:FullTextSearch>')
|
||
emit_choice_parameter_links(f'{indent}\t', ov.get('ChoiceParameterLinks'), 'xr:ChoiceParameterLinks')
|
||
fv_raw = ov.get('FillValue', None)
|
||
if fv_raw is None:
|
||
X(f'{indent}\t<xr:FillValue xsi:nil="true"/>')
|
||
elif isinstance(fv_raw, dict) and fv_raw.get('emptyRef') is True:
|
||
X(f'{indent}\t<xr:FillValue xsi:type="xr:DesignTimeRef"/>')
|
||
elif isinstance(fv_raw, dict) and fv_raw.get('typeDescription') is True:
|
||
X(f'{indent}\t<xr:FillValue xsi:type="v8:TypeDescription"/>') # пустое типизированное (ValueType ПВХ)
|
||
else:
|
||
fv_xt, fv_tx = normalize_choice_value(fv_raw)
|
||
if fv_tx == '' or fv_tx is None:
|
||
X(f'{indent}\t<xr:FillValue xsi:type="{fv_xt}"/>')
|
||
else:
|
||
X(f'{indent}\t<xr:FillValue xsi:type="{fv_xt}">{esc_xml(fv_tx)}</xr:FillValue>')
|
||
if msk:
|
||
X(f'{indent}\t<xr:Mask>{esc_xml_text(str(msk))}</xr:Mask>')
|
||
else:
|
||
X(f'{indent}\t<xr:Mask/>')
|
||
emit_choice_parameters(f'{indent}\t', ov.get('ChoiceParameters'), 'xr:ChoiceParameters')
|
||
X(f'{indent}</xr:StandardAttribute>')
|
||
|
||
# Единый эмиттер блока StandardAttributes — поведение правят ДАННЫЕ, не форк кода (см. коммент в .ps1).
|
||
# std_attr_conditional_types: типы, где блок только при кастомизации (DSL-ключ standardAttributes).
|
||
# Прочие типы → блок всегда (текущее поведение). Миграция типа = +строчка в оба справочника + снэпшоты.
|
||
std_attr_conditional_types = {'Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document'}
|
||
def emit_standard_attributes(indent, object_type):
|
||
attrs = standard_attributes_by_type.get(object_type)
|
||
if not attrs:
|
||
return
|
||
conditional = object_type in std_attr_conditional_types
|
||
sa = defn.get('standardAttributes')
|
||
if conditional and sa is None:
|
||
return
|
||
if isinstance(sa, str) and sa == '':
|
||
return # opt-out `standardAttributes:""` (дом-конвенция суппресса, ~5% регистров опускают all-default блок)
|
||
profile = std_attr_profile.get(object_type, {})
|
||
# Доп. (опциональные) стандартные реквизиты вне фикс-списка — напр. ExchangeDate у части ПланОбмена
|
||
# (легаси, присутствие не выводится). Эмитим по факту ключа в DSL, ПЕРЕД фикс-списком (их позиция).
|
||
extra = [k for k in sa if k not in attrs] if isinstance(sa, dict) else []
|
||
X(f'{indent}<StandardAttributes>')
|
||
for a in extra + list(attrs):
|
||
ov = dict(profile.get(a, {}))
|
||
if isinstance(sa, dict):
|
||
d = sa.get(a)
|
||
if d:
|
||
if d.get('synonym') is not None:
|
||
ov['Synonym'] = d['synonym'] # строка ИЛИ {ru,en}
|
||
if d.get('tooltip') is not None:
|
||
ov['ToolTip'] = d['tooltip'] # строка ИЛИ {ru,en}
|
||
if d.get('fillChecking'):
|
||
ov['FillChecking'] = str(d['fillChecking'])
|
||
if d.get('fillFromFillingValue') is not None:
|
||
ov['FillFromFillingValue'] = 'true' if d['fillFromFillingValue'] else 'false'
|
||
if d.get('fullTextSearch'):
|
||
ov['FullTextSearch'] = str(d['fullTextSearch'])
|
||
if d.get('dataHistory'):
|
||
ov['DataHistory'] = str(d['dataHistory'])
|
||
if d.get('fillValue') is not None:
|
||
ov['FillValue'] = d['fillValue']
|
||
if d.get('choiceParameterLinks') is not None:
|
||
ov['ChoiceParameterLinks'] = d['choiceParameterLinks']
|
||
if d.get('choiceParameters') is not None:
|
||
ov['ChoiceParameters'] = d['choiceParameters']
|
||
if d.get('comment'):
|
||
ov['Comment'] = str(d['comment'])
|
||
if d.get('mask'):
|
||
ov['Mask'] = str(d['mask'])
|
||
if d.get('format') is not None:
|
||
ov['Format'] = d['format'] # строка ИЛИ {ru,en}
|
||
if d.get('editFormat') is not None:
|
||
ov['EditFormat'] = d['editFormat']
|
||
if d.get('choiceForm'):
|
||
ov['ChoiceForm'] = str(d['choiceForm'])
|
||
if d.get('linkByType') is not None:
|
||
ov['LinkByType'] = d['linkByType']
|
||
emit_standard_attribute(f'{indent}\t', a, ov)
|
||
X(f'{indent}</StandardAttributes>')
|
||
|
||
def emit_tabular_standard_attributes(indent, line_number=None):
|
||
"""LineNumber/НомерСтроки ТЧ. Блок эмитится всегда. DSL `lineNumber` переопределяет
|
||
synonym/comment/fullTextSearch/tooltip/format/editFormat/choiceHistoryOnInput."""
|
||
ov = None
|
||
if line_number:
|
||
ov = {}
|
||
if line_number.get('synonym') is not None:
|
||
ov['Synonym'] = line_number['synonym']
|
||
if line_number.get('comment'):
|
||
ov['Comment'] = str(line_number['comment'])
|
||
if line_number.get('fullTextSearch'):
|
||
ov['FullTextSearch'] = str(line_number['fullTextSearch'])
|
||
if line_number.get('tooltip') is not None:
|
||
ov['ToolTip'] = line_number['tooltip']
|
||
if line_number.get('format') is not None:
|
||
ov['Format'] = line_number['format']
|
||
if line_number.get('editFormat') is not None:
|
||
ov['EditFormat'] = line_number['editFormat']
|
||
if line_number.get('choiceHistoryOnInput'):
|
||
ov['ChoiceHistoryOnInput'] = str(line_number['choiceHistoryOnInput'])
|
||
if line_number.get('fillValue') is not None:
|
||
ov['FillValue'] = line_number['fillValue']
|
||
X(f'{indent}<StandardAttributes>')
|
||
emit_standard_attribute(f'{indent}\t', 'LineNumber', ov)
|
||
X(f'{indent}</StandardAttributes>')
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 8. Attribute emitter
|
||
# ---------------------------------------------------------------------------
|
||
|
||
RESERVED_ATTR_NAMES = {
|
||
'Ref', 'DeletionMark', 'Code', 'Description', 'Date', 'Number', 'Posted',
|
||
'Parent', 'Owner', 'IsFolder', 'Predefined', 'PredefinedDataName',
|
||
'Recorder', 'Period', 'LineNumber', 'Active', 'Order', 'Type', 'OffBalance',
|
||
'Started', 'Completed', 'HeadTask', 'Executed', 'RoutePoint', 'BusinessProcess',
|
||
'ThisNode', 'SentNo', 'ReceivedNo', 'CalculationType', 'RegistrationPeriod',
|
||
'ReversingEntry', 'Account', 'ValueType', 'ActionPeriodIsBasic',
|
||
}
|
||
RESERVED_ATTR_NAMES_RU = {
|
||
'\u0421\u0441\u044b\u043b\u043a\u0430', '\u041f\u043e\u043c\u0435\u0442\u043a\u0430\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u044f',
|
||
'\u041a\u043e\u0434', '\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435',
|
||
'\u0414\u0430\u0442\u0430', '\u041d\u043e\u043c\u0435\u0440', '\u041f\u0440\u043e\u0432\u0435\u0434\u0435\u043d',
|
||
'\u0420\u043e\u0434\u0438\u0442\u0435\u043b\u044c', '\u0412\u043b\u0430\u0434\u0435\u043b\u0435\u0446',
|
||
'\u042d\u0442\u043e\u0413\u0440\u0443\u043f\u043f\u0430', '\u041f\u0440\u0435\u0434\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0439',
|
||
'\u0418\u043c\u044f\u041f\u0440\u0435\u0434\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0445\u0414\u0430\u043d\u043d\u044b\u0445',
|
||
'\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440', '\u041f\u0435\u0440\u0438\u043e\u0434',
|
||
'\u041d\u043e\u043c\u0435\u0440\u0421\u0442\u0440\u043e\u043a\u0438', '\u0410\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c',
|
||
'\u041f\u043e\u0440\u044f\u0434\u043e\u043a', '\u0422\u0438\u043f', '\u0417\u0430\u0431\u0430\u043b\u0430\u043d\u0441\u043e\u0432\u044b\u0439',
|
||
'\u0421\u0442\u0430\u0440\u0442\u043e\u0432\u0430\u043d', '\u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d',
|
||
'\u0412\u0435\u0434\u0443\u0449\u0430\u044f\u0417\u0430\u0434\u0430\u0447\u0430',
|
||
'\u0412\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430', '\u0422\u043e\u0447\u043a\u0430\u041c\u0430\u0440\u0448\u0440\u0443\u0442\u0430',
|
||
'\u0411\u0438\u0437\u043d\u0435\u0441\u041f\u0440\u043e\u0446\u0435\u0441\u0441',
|
||
'\u042d\u0442\u043e\u0442\u0423\u0437\u0435\u043b', '\u041d\u043e\u043c\u0435\u0440\u041e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e',
|
||
'\u041d\u043e\u043c\u0435\u0440\u041f\u0440\u0438\u043d\u044f\u0442\u043e\u0433\u043e',
|
||
'\u0412\u0438\u0434\u0420\u0430\u0441\u0447\u0435\u0442\u0430', '\u041f\u0435\u0440\u0438\u043e\u0434\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438',
|
||
'\u0421\u0442\u043e\u0440\u043d\u043e\u0417\u0430\u043f\u0438\u0441\u044c',
|
||
'\u0421\u0447\u0435\u0442', '\u0422\u0438\u043f\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u044f',
|
||
'\u041f\u0435\u0440\u0438\u043e\u0434\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044f\u0411\u0430\u0437\u043e\u0432\u044b\u0439',
|
||
}
|
||
|
||
# Стандартные реквизиты по типу объекта (EN + RU). Совпадение имени реквизита с ними платформа
|
||
# не позволит — жёсткий отказ. Контексты вне карты → мягкое предупреждение по плоскому списку.
|
||
RESERVED_BY_CONTEXT = {
|
||
'catalog': {
|
||
'ref', 'ссылка', # Ссылка
|
||
'deletionmark', 'пометкаудаления', # ПометкаУдаления
|
||
'predefined', 'предопределенный', # Предопределенный
|
||
'predefineddataname', 'имяпредопределенныхданных', # ИмяПредопределенныхДанных
|
||
'code', 'код', # Код
|
||
'description', 'наименование', # Наименование
|
||
'owner', 'владелец', # Владелец
|
||
'parent', 'родитель', # Родитель
|
||
'isfolder', 'этогруппа', # ЭтоГруппа
|
||
},
|
||
'document': {
|
||
'ref', 'ссылка', # Ссылка
|
||
'deletionmark', 'пометкаудаления', # ПометкаУдаления
|
||
'date', 'дата', # Дата
|
||
'number', 'номер', # Номер
|
||
'posted', 'проведен', # Проведен
|
||
},
|
||
}
|
||
|
||
# Стандартные реквизиты по типу для разворота dataPath: [(EN, RU), ...].
|
||
STD_ATTR_BY_TYPE = {
|
||
'Catalog': [('Ref', 'Ссылка'), ('DeletionMark', 'ПометкаУдаления'), ('Predefined', 'Предопределенный'),
|
||
('PredefinedDataName', 'ИмяПредопределенныхДанных'), ('Code', 'Код'), ('Description', 'Наименование'),
|
||
('Owner', 'Владелец'), ('Parent', 'Родитель'), ('IsFolder', 'ЭтоГруппа')],
|
||
'Document': [('Ref', 'Ссылка'), ('DeletionMark', 'ПометкаУдаления'), ('Date', 'Дата'), ('Number', 'Номер'),
|
||
('Posted', 'Проведен')],
|
||
}
|
||
|
||
def resolve_std_attr_en(name):
|
||
pairs = STD_ATTR_BY_TYPE.get(obj_type)
|
||
if not pairs:
|
||
return None
|
||
low = name.lower()
|
||
for en, ru in pairs:
|
||
if low == en.lower() or low == ru.lower():
|
||
return en
|
||
return None
|
||
|
||
def expand_data_path(dp):
|
||
"""Путь к реквизиту самого объекта: "Ссылка"→<Тип>.<Имя>.StandardAttribute.Ref; обычное имя→.Attribute.имя;
|
||
частичное StandardAttribute.X/Attribute.X→префикс; полный путь→verbatim."""
|
||
if not dp:
|
||
return dp
|
||
s = str(dp)
|
||
if re.search(r'[:/]', s):
|
||
return s # спец-путь (напр. 0:GUID/0:GUID в зависимостях ПВХ) — не разворачиваем
|
||
if re.match(r'^-?\d+$', s):
|
||
return s # голый (отрицательный) индекс-маркер (напр. -8 в ChoiceParameterLinks) — verbatim
|
||
if re.match(r'^(StandardAttribute|Attribute)\.', s):
|
||
return f'{obj_type}.{obj_name}.{s}'
|
||
if '.' not in s:
|
||
en = resolve_std_attr_en(s)
|
||
if en:
|
||
return f'{obj_type}.{obj_name}.StandardAttribute.{en}'
|
||
return f'{obj_type}.{obj_name}.Attribute.{s}'
|
||
return s
|
||
|
||
def emit_link_by_type(indent, spec):
|
||
"""<LinkByType> (связь по типу): DataPath + LinkItem. spec — {dataPath, linkItem?} или строка-путь; нет → <LinkByType/>."""
|
||
if not spec:
|
||
X(f'{indent}<LinkByType/>')
|
||
return
|
||
if isinstance(spec, str):
|
||
dp, li = spec, 0
|
||
else:
|
||
dp = str(spec.get('dataPath') or spec.get('path') or spec.get('путь') or '')
|
||
li = spec.get('linkItem')
|
||
if li is None:
|
||
li = spec.get('элементСвязи')
|
||
if li is None:
|
||
li = 0
|
||
if not dp:
|
||
X(f'{indent}<LinkByType/>')
|
||
return
|
||
dp = expand_data_path(dp)
|
||
X(f'{indent}<LinkByType>')
|
||
X(f'{indent}\t<xr:DataPath>{esc_xml(str(dp))}</xr:DataPath>')
|
||
X(f'{indent}\t<xr:LinkItem>{li}</xr:LinkItem>')
|
||
X(f'{indent}</LinkByType>')
|
||
|
||
def emit_field_block(indent, tag, fields):
|
||
"""<Tag> со списком <xr:Field> (InputByString/DataLockFields). Пусто → self-close."""
|
||
arr = [f for f in (fields or []) if str(f) != '']
|
||
if not arr:
|
||
X(f'{indent}<{tag}/>')
|
||
return
|
||
X(f'{indent}<{tag}>')
|
||
for f in arr:
|
||
X(f'{indent}\t<xr:Field>{esc_xml(str(f))}</xr:Field>')
|
||
X(f'{indent}</{tag}>')
|
||
|
||
def emit_based_on(indent, items):
|
||
"""<BasedOn> — «ввод на основании», список MDObjectRef. Нет/пусто → self-close."""
|
||
arr = [it for it in (items or []) if it]
|
||
if not arr:
|
||
X(f'{indent}<BasedOn/>')
|
||
return
|
||
X(f'{indent}<BasedOn>')
|
||
for it in arr:
|
||
X(f'{indent}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(str(it))}</xr:Item>')
|
||
X(f'{indent}</BasedOn>')
|
||
|
||
# --- Параметры/связи выбора (порт из form-compile) ---
|
||
|
||
def ch_el_prop(obj, names):
|
||
if obj is None:
|
||
return None
|
||
if isinstance(obj, dict):
|
||
for n in names:
|
||
if n in obj:
|
||
return obj[n]
|
||
return None
|
||
|
||
def convert_to_ch_scalar(s):
|
||
t = str(s).strip()
|
||
if re.match(r'^(?i:true|истина)$', t):
|
||
return True
|
||
if re.match(r'^(?i:false|ложь)$', t):
|
||
return False
|
||
if re.match(r'^-?\d+$', t):
|
||
return int(t)
|
||
if re.match(r'^-?\d+\.\d+$', t):
|
||
return float(t)
|
||
return t
|
||
|
||
def expand_choice_ref_value(value, type_str):
|
||
"""Голое значение (без точки) + тип параметра → полный DTR-путь, либо None. Принимает EnumRef.X / Enum.X / рус."""
|
||
if not type_str:
|
||
return None
|
||
t = resolve_type_str(type_str)
|
||
root = tn = None
|
||
m = re.match(r'^(\w+Ref)\.(.+)$', t)
|
||
if m:
|
||
root = fill_ref_kind_root.get(m.group(1).lower())
|
||
tn = m.group(2)
|
||
else:
|
||
m = re.match(r'^([^.]+)\.(.+)$', t)
|
||
if m:
|
||
root = fill_ref_roots.get(m.group(1).lower())
|
||
tn = m.group(2)
|
||
if not root:
|
||
return None
|
||
if str(value).lower() in fill_empty_ref_words:
|
||
return f'{root}.{tn}.EmptyRef'
|
||
if root == 'Enum':
|
||
return f'Enum.{tn}.EnumValue.{value}'
|
||
return f'{root}.{tn}.{value}'
|
||
|
||
def normalize_choice_value_t(value, type_str):
|
||
"""Значение параметра выбора → (xsi_type, text). type_str разворачивает голые ref-имена."""
|
||
if type_str and isinstance(value, str) and '.' not in value:
|
||
ex = expand_choice_ref_value(value, type_str)
|
||
if ex:
|
||
return ('xr:DesignTimeRef', ex)
|
||
return normalize_choice_value(value)
|
||
|
||
def normalize_choice_value(value):
|
||
"""Значение параметра выбора → (xsi_type, text). Авто-детект по значению."""
|
||
if isinstance(value, bool):
|
||
return ('xs:boolean', 'true' if value else 'false')
|
||
if isinstance(value, (int, float)):
|
||
return ('xs:decimal', format_fill_num(value))
|
||
s = str(value)
|
||
if s == '':
|
||
return ('xs:string', '')
|
||
if re.match(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$', s):
|
||
return ('xs:dateTime', s)
|
||
ref = normalize_fill_ref(s)
|
||
if ref:
|
||
return ('xr:DesignTimeRef', ref)
|
||
if s in ACCOUNT_TYPE_VALUES:
|
||
return ('ent:AccountType', s)
|
||
return ('xs:string', s)
|
||
|
||
def convert_from_ch_param_shorthand(s):
|
||
eq = s.find('=')
|
||
if eq < 0:
|
||
return {'name': s.strip()}
|
||
name = s[:eq].strip()
|
||
rest = s[eq + 1:]
|
||
if ',' in rest:
|
||
return {'name': name, 'value': [convert_to_ch_scalar(p) for p in rest.split(',')]}
|
||
return {'name': name, 'value': convert_to_ch_scalar(rest)}
|
||
|
||
def convert_from_ch_link_shorthand(s):
|
||
eq = s.find('=')
|
||
if eq < 0:
|
||
return {'name': s.strip()}
|
||
o = {'name': s[:eq].strip()}
|
||
rest = s[eq + 1:].strip()
|
||
m = re.match(r'^(.*):(?i:(Clear|DontChange|очистить|неизменять))$', rest)
|
||
if m:
|
||
o['dataPath'] = m.group(1).strip()
|
||
o['valueChange'] = m.group(2)
|
||
else:
|
||
o['dataPath'] = rest
|
||
return o
|
||
|
||
def emit_choice_parameters(indent, cp, tag='ChoiceParameters'):
|
||
if not cp:
|
||
X(f'{indent}<{tag}/>')
|
||
return
|
||
if isinstance(cp, (str, dict)):
|
||
cp = [cp]
|
||
X(f'{indent}<{tag}>')
|
||
for item in cp:
|
||
if isinstance(item, str):
|
||
item = convert_from_ch_param_shorthand(item)
|
||
name = ch_el_prop(item, ['name', 'имя'])
|
||
ptype = ch_el_prop(item, ['type', 'тип'])
|
||
has_val = isinstance(item, dict) and ('value' in item or 'значение' in item)
|
||
val = item.get('value', item.get('значение')) if has_val else None
|
||
val_is_array = isinstance(val, (list, tuple))
|
||
X(f'{indent}\t<app:item name="{esc_xml(str(name))}">')
|
||
if not has_val:
|
||
X(f'{indent}\t\t<app:value xsi:nil="true"/>')
|
||
elif val_is_array:
|
||
X(f'{indent}\t\t<app:value xsi:type="v8:FixedArray">')
|
||
for v in val:
|
||
xt, tx = normalize_choice_value_t(v, ptype)
|
||
if tx == '' or tx is None:
|
||
X(f'{indent}\t\t\t<v8:Value xsi:type="{xt}"/>')
|
||
else:
|
||
X(f'{indent}\t\t\t<v8:Value xsi:type="{xt}">{esc_xml(tx)}</v8:Value>')
|
||
X(f'{indent}\t\t</app:value>')
|
||
else:
|
||
xt, tx = normalize_choice_value_t(val, ptype)
|
||
if tx == '' or tx is None:
|
||
X(f'{indent}\t\t<app:value xsi:type="{xt}"/>')
|
||
else:
|
||
X(f'{indent}\t\t<app:value xsi:type="{xt}">{esc_xml(tx)}</app:value>')
|
||
X(f'{indent}\t</app:item>')
|
||
X(f'{indent}</{tag}>')
|
||
|
||
def emit_choice_parameter_links(indent, cpl, tag='ChoiceParameterLinks'):
|
||
if not cpl:
|
||
X(f'{indent}<{tag}/>')
|
||
return
|
||
if isinstance(cpl, (str, dict)):
|
||
cpl = [cpl]
|
||
X(f'{indent}<{tag}>')
|
||
for lk in cpl:
|
||
if isinstance(lk, str):
|
||
lk = convert_from_ch_link_shorthand(lk)
|
||
name = ch_el_prop(lk, ['name', 'имя'])
|
||
dp = expand_data_path(ch_el_prop(lk, ['dataPath', 'path', 'путь']))
|
||
vc_raw = ch_el_prop(lk, ['valueChange', 'режимИзменения'])
|
||
vc = 'Clear'
|
||
if vc_raw:
|
||
low = str(vc_raw).lower()
|
||
if re.match(r'^(clear|очистить|очистка)$', low):
|
||
vc = 'Clear'
|
||
elif re.match(r'^(dontchange|неизменять|неменять|нет)$', low):
|
||
vc = 'DontChange'
|
||
else:
|
||
vc = str(vc_raw)
|
||
X(f'{indent}\t<xr:Link>')
|
||
X(f'{indent}\t\t<xr:Name>{esc_xml(str(name))}</xr:Name>')
|
||
X(f'{indent}\t\t<xr:DataPath xsi:type="xs:string">{esc_xml(str(dp))}</xr:DataPath>')
|
||
X(f'{indent}\t\t<xr:ValueChange>{vc}</xr:ValueChange>')
|
||
X(f'{indent}\t</xr:Link>')
|
||
X(f'{indent}</{tag}>')
|
||
|
||
# --- Characteristics (привязка ПВХ «Дополнительные реквизиты и сведения») ---
|
||
|
||
CHAR_FROM_TS_TYPES = {'Catalog', 'Document', 'ChartOfCharacteristicTypes', 'ChartOfCalculationTypes',
|
||
'ChartOfAccounts', 'ExchangePlan', 'BusinessProcess', 'Task'}
|
||
CHAR_MEMBER_RU = {'ТабличнаяЧасть': 'TabularSection', 'Измерение': 'Dimension', 'Ресурс': 'Resource', 'Реквизит': 'Attribute'}
|
||
|
||
def normalize_char_from(from_):
|
||
if not from_:
|
||
return from_
|
||
parts = str(from_).split('.')
|
||
if parts[0] in object_type_synonyms:
|
||
parts[0] = object_type_synonyms[parts[0]]
|
||
for i in range(1, len(parts)):
|
||
if parts[i] in CHAR_MEMBER_RU:
|
||
parts[i] = CHAR_MEMBER_RU[parts[i]]
|
||
if len(parts) == 3 and parts[0] in CHAR_FROM_TS_TYPES:
|
||
parts = [parts[0], parts[1], 'TabularSection', parts[2]]
|
||
return '.'.join(parts)
|
||
|
||
def resolve_char_std_en(name):
|
||
n = str(name).lower()
|
||
if n in ('ref', 'ссылка'):
|
||
return 'Ref'
|
||
if n in ('parent', 'родитель'):
|
||
return 'Parent'
|
||
if n in ('owner', 'владелец'):
|
||
return 'Owner'
|
||
return None
|
||
|
||
def char_int_field(obj, names):
|
||
v = ch_el_prop(obj, names)
|
||
if v is None or str(v) == '':
|
||
return -1
|
||
return int(v)
|
||
|
||
def expand_char_field(field, from_):
|
||
s = str(field or '')
|
||
if not s:
|
||
return s
|
||
if s == '-1':
|
||
return '-1' # поле не задано (empty-характеристика)
|
||
if re.match(r'^(StandardAttribute|Attribute|Dimension|Resource)\.', s):
|
||
return f'{from_}.{s}'
|
||
if '.' not in s:
|
||
en = resolve_char_std_en(s)
|
||
if en:
|
||
return f'{from_}.StandardAttribute.{en}'
|
||
return f'{from_}.Attribute.{s}'
|
||
return s
|
||
|
||
def emit_characteristics(indent, chars):
|
||
if not chars:
|
||
X(f'{indent}<Characteristics/>')
|
||
return
|
||
X(f'{indent}<Characteristics>')
|
||
for ch in chars:
|
||
types = ch_el_prop(ch, ['types', 'characteristicTypes', 'типы'])
|
||
values = ch_el_prop(ch, ['values', 'characteristicValues', 'значения'])
|
||
t_from = normalize_char_from(ch_el_prop(types, ['from', 'source', 'источник']) or '')
|
||
v_from = normalize_char_from(ch_el_prop(values, ['from', 'source', 'источник']) or '')
|
||
key = expand_char_field(ch_el_prop(types, ['key', 'keyField']), t_from)
|
||
tff = expand_char_field(ch_el_prop(types, ['filterField', 'typesFilterField']), t_from)
|
||
obj = expand_char_field(ch_el_prop(values, ['object', 'objectField']), v_from)
|
||
typ = expand_char_field(ch_el_prop(values, ['type', 'typeField']), v_from)
|
||
val = expand_char_field(ch_el_prop(values, ['value', 'valueField']), v_from)
|
||
dpf = char_int_field(types, ['dataPathField'])
|
||
mvu = char_int_field(types, ['multipleValuesUseField'])
|
||
mvk = char_int_field(values, ['multipleValuesKeyField'])
|
||
mvo = char_int_field(values, ['multipleValuesOrderField'])
|
||
X(f'{indent}\t<xr:Characteristic>')
|
||
X(f'{indent}\t\t<xr:CharacteristicTypes from="{esc_xml(t_from)}">')
|
||
X(f'{indent}\t\t\t<xr:KeyField>{esc_xml(key)}</xr:KeyField>')
|
||
X(f'{indent}\t\t\t<xr:TypesFilterField>{esc_xml(tff)}</xr:TypesFilterField>')
|
||
# filterValue: None→nil; голое→xs:string, полный путь→DTR, bool→xs:boolean.
|
||
tfv_raw = ch_el_prop(types, ['filterValue', 'typesFilterValue'])
|
||
if tfv_raw is None:
|
||
X(f'{indent}\t\t\t<xr:TypesFilterValue xsi:nil="true"/>')
|
||
else:
|
||
tfv_xt, tfv_tx = normalize_choice_value(tfv_raw)
|
||
if tfv_tx == '' or tfv_tx is None:
|
||
X(f'{indent}\t\t\t<xr:TypesFilterValue xsi:type="{tfv_xt}"/>')
|
||
else:
|
||
X(f'{indent}\t\t\t<xr:TypesFilterValue xsi:type="{tfv_xt}">{esc_xml(tfv_tx)}</xr:TypesFilterValue>')
|
||
X(f'{indent}\t\t\t<xr:DataPathField>{dpf}</xr:DataPathField>')
|
||
X(f'{indent}\t\t\t<xr:MultipleValuesUseField>{mvu}</xr:MultipleValuesUseField>')
|
||
X(f'{indent}\t\t</xr:CharacteristicTypes>')
|
||
X(f'{indent}\t\t<xr:CharacteristicValues from="{esc_xml(v_from)}">')
|
||
X(f'{indent}\t\t\t<xr:ObjectField>{esc_xml(obj)}</xr:ObjectField>')
|
||
X(f'{indent}\t\t\t<xr:TypeField>{esc_xml(typ)}</xr:TypeField>')
|
||
X(f'{indent}\t\t\t<xr:ValueField>{esc_xml(val)}</xr:ValueField>')
|
||
X(f'{indent}\t\t\t<xr:MultipleValuesKeyField>{mvk}</xr:MultipleValuesKeyField>')
|
||
X(f'{indent}\t\t\t<xr:MultipleValuesOrderField>{mvo}</xr:MultipleValuesOrderField>')
|
||
X(f'{indent}\t\t</xr:CharacteristicValues>')
|
||
X(f'{indent}\t</xr:Characteristic>')
|
||
X(f'{indent}</Characteristics>')
|
||
|
||
def emit_min_max_value(indent, tag, val):
|
||
"""<MinValue>/<MaxValue> — граница диапазона. None → nil. Число → xs:decimal, строка → xs:string."""
|
||
if val is None:
|
||
X(f'{indent}<{tag} xsi:nil="true"/>')
|
||
return
|
||
t = 'xs:string' if isinstance(val, str) else 'xs:decimal'
|
||
X(f'{indent}<{tag} xsi:type="{t}">{esc_xml(str(val))}</{tag}>')
|
||
|
||
def emit_attribute(indent, parsed, context, elem_tag='Attribute'):
|
||
# context: "catalog", "document", "object", "processor", "tabular", "processor-tabular", "register",
|
||
# "account" (реквизит Плана счетов: как catalog, но без <Use>), "account-flag" (признак учёта ПС:
|
||
# как account, но без <Indexing>/<FullTextSearch>, тип по умолчанию Boolean; elem_tag = AccountingFlag/ExtDimensionAccountingFlag)
|
||
attr_name = parsed['name']
|
||
ctx_reserved = RESERVED_BY_CONTEXT.get(context)
|
||
if ctx_reserved is not None:
|
||
if attr_name.lower() in ctx_reserved:
|
||
print(f"meta-compile: имя реквизита '{attr_name}' зарезервировано стандартным реквизитом объекта '{context}'. Выберите другое имя.", file=sys.stderr)
|
||
sys.exit(1)
|
||
elif context not in ('tabular', 'processor-tabular') and (attr_name in RESERVED_ATTR_NAMES or attr_name in RESERVED_ATTR_NAMES_RU):
|
||
print(f"WARNING: Attribute '{attr_name}' conflicts with a standard attribute name. This may cause errors when loading into 1C.", file=sys.stderr)
|
||
uid = new_uuid()
|
||
X(f'{indent}<{elem_tag} uuid="{uid}">')
|
||
X(f'{indent}\t<Properties>')
|
||
X(f'{indent}\t\t<Name>{esc_xml(parsed["name"])}</Name>')
|
||
emit_mltext(f'{indent}\t\t', 'Synonym', parsed['synonym'])
|
||
if parsed.get('comment'):
|
||
X(f'{indent}\t\t<Comment>{esc_xml_text(parsed["comment"])}</Comment>')
|
||
else:
|
||
X(f'{indent}\t\t<Comment/>')
|
||
type_str = parsed['type']
|
||
if parsed.get('typeEmpty'):
|
||
# Явный пустой тип (реквизит без типа / произвольный) → <Type/>.
|
||
X(f'{indent}\t\t<Type/>')
|
||
elif type_str:
|
||
emit_value_type(f'{indent}\t\t', type_str)
|
||
elif context == 'account-flag':
|
||
X(f'{indent}\t\t<Type>')
|
||
X(f'{indent}\t\t\t<v8:Type>xs:boolean</v8:Type>')
|
||
X(f'{indent}\t\t</Type>')
|
||
else:
|
||
X(f'{indent}\t\t<Type>')
|
||
X(f'{indent}\t\t\t<v8:Type>xs:string</v8:Type>')
|
||
X(f'{indent}\t\t</Type>')
|
||
pw_mode = 'true' if parsed.get('passwordMode') is True else 'false'
|
||
X(f'{indent}\t\t<PasswordMode>{pw_mode}</PasswordMode>')
|
||
emit_mltext(f'{indent}\t\t', 'Format', parsed.get('format'))
|
||
emit_mltext(f'{indent}\t\t', 'EditFormat', parsed.get('editFormat'))
|
||
emit_mltext(f'{indent}\t\t', 'ToolTip', parsed.get('tooltip'))
|
||
X(f'{indent}\t\t<MarkNegatives>{"true" if parsed.get("markNegatives") is True else "false"}</MarkNegatives>')
|
||
if parsed.get('mask'):
|
||
X(f'{indent}\t\t<Mask>{esc_xml_text(parsed["mask"])}</Mask>')
|
||
else:
|
||
X(f'{indent}\t\t<Mask/>')
|
||
multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false'
|
||
X(f'{indent}\t\t<MultiLine>{multi_line}</MultiLine>')
|
||
ext_edit = 'true' if parsed.get('extendedEdit') is True else 'false'
|
||
X(f'{indent}\t\t<ExtendedEdit>{ext_edit}</ExtendedEdit>')
|
||
emit_min_max_value(f'{indent}\t\t', 'MinValue', parsed.get('minValue'))
|
||
emit_min_max_value(f'{indent}\t\t', 'MaxValue', parsed.get('maxValue'))
|
||
# FillFromFillingValue / FillValue — not for tabular/processor/chart/register-other
|
||
# (Chart*, AccumulationRegister/AccountingRegister/CalculationRegister don't support these)
|
||
if context not in ('tabular', 'processor', 'chart', 'register-other', 'register-accum', 'register-calc', 'register-account'):
|
||
ffv = 'true' if (parsed.get('fillFromFillingValue') is True or (elem_tag == 'Dimension' and 'master' in parsed.get('flags', []))) else 'false'
|
||
X(f'{indent}\t\t<FillFromFillingValue>{ffv}</FillFromFillingValue>')
|
||
if context not in ('tabular', 'processor', 'chart', 'register-other', 'register-accum', 'register-calc', 'register-account'):
|
||
emit_fill_value(f'{indent}\t\t', type_str, parsed.get('fillValue'), parsed.get('hasFillValue'), bool(parsed.get('typeEmpty')))
|
||
fill_checking = 'DontCheck'
|
||
if 'req' in parsed.get('flags', []):
|
||
fill_checking = 'ShowError'
|
||
if parsed.get('fillChecking'):
|
||
fill_checking = parsed['fillChecking']
|
||
X(f'{indent}\t\t<FillChecking>{fill_checking}</FillChecking>')
|
||
X(f'{indent}\t\t<ChoiceFoldersAndItems>{parsed.get("choiceFoldersAndItems") or "Items"}</ChoiceFoldersAndItems>')
|
||
emit_choice_parameter_links(f'{indent}\t\t', parsed.get('choiceParameterLinks'))
|
||
emit_choice_parameters(f'{indent}\t\t', parsed.get('choiceParameters'))
|
||
X(f'{indent}\t\t<QuickChoice>{parsed.get("quickChoice") or "Auto"}</QuickChoice>')
|
||
X(f'{indent}\t\t<CreateOnInput>{parsed.get("createOnInput") or "Auto"}</CreateOnInput>')
|
||
X(f'{indent}\t\t<ChoiceForm>{esc_xml(str(parsed["choiceForm"]))}</ChoiceForm>' if parsed.get('choiceForm') else f'{indent}\t\t<ChoiceForm/>')
|
||
emit_link_by_type(f'{indent}\t\t', parsed.get('linkByType'))
|
||
chi = parsed.get('choiceHistoryOnInput') or 'Auto'
|
||
X(f'{indent}\t\t<ChoiceHistoryOnInput>{chi}</ChoiceHistoryOnInput>')
|
||
# Измерение регистра сведений: Master/MainFilter/DenyIncompleteValues (между ChoiceHistoryOnInput и Indexing).
|
||
if elem_tag == 'Dimension' and context == 'register-info':
|
||
master = 'true' if (parsed.get('master') is True or 'master' in parsed.get('flags', [])) else 'false'
|
||
main_filter = 'true' if (parsed.get('mainFilter') is True or 'mainfilter' in parsed.get('flags', [])) else 'false'
|
||
deny_incomplete = 'true' if (parsed.get('denyIncompleteValues') is True or 'denyincomplete' in parsed.get('flags', [])) else 'false'
|
||
X(f'{indent}\t\t<Master>{master}</Master>')
|
||
X(f'{indent}\t\t<MainFilter>{main_filter}</MainFilter>')
|
||
X(f'{indent}\t\t<DenyIncompleteValues>{deny_incomplete}</DenyIncompleteValues>')
|
||
# Измерение регистра накопления: DenyIncompleteValues (между ChoiceHistoryOnInput и Indexing).
|
||
if elem_tag == 'Dimension' and context == 'register-accum':
|
||
deny_incomplete = 'true' if (parsed.get('denyIncompleteValues') is True or 'denyincomplete' in parsed.get('flags', [])) else 'false'
|
||
X(f'{indent}\t\t<DenyIncompleteValues>{deny_incomplete}</DenyIncompleteValues>')
|
||
# Измерение регистра расчёта: DenyIncompleteValues + BaseDimension.
|
||
if elem_tag == 'Dimension' and context == 'register-calc':
|
||
deny_incomplete = 'true' if (parsed.get('denyIncompleteValues') is True or 'denyincomplete' in parsed.get('flags', [])) else 'false'
|
||
base_dimension = 'true' if (parsed.get('baseDimension') is True or 'base' in parsed.get('flags', [])) else 'false'
|
||
X(f'{indent}\t\t<DenyIncompleteValues>{deny_incomplete}</DenyIncompleteValues>')
|
||
X(f'{indent}\t\t<BaseDimension>{base_dimension}</BaseDimension>')
|
||
# Регистр расчёта: ScheduleLink у измерений и реквизитов (НЕ ресурсов), перед Indexing. Дефолт пустой.
|
||
if context == 'register-calc' and elem_tag in ('Dimension', 'Attribute'):
|
||
if parsed.get('scheduleLink'):
|
||
X(f'{indent}\t\t<ScheduleLink>{esc_xml(str(parsed["scheduleLink"]))}</ScheduleLink>')
|
||
else:
|
||
X(f'{indent}\t\t<ScheduleLink/>')
|
||
# Измерение/ресурс регистра бухгалтерии: Balance + AccountingFlag, затем DenyIncompleteValues (изм.) / ExtDimensionAccountingFlag (рес.).
|
||
if context == 'register-account' and elem_tag in ('Dimension', 'Resource'):
|
||
balance = 'true' if (parsed.get('balance') is True or 'balance' in parsed.get('flags', [])) else 'false'
|
||
X(f'{indent}\t\t<Balance>{balance}</Balance>')
|
||
if parsed.get('accountingFlag'):
|
||
X(f'{indent}\t\t<AccountingFlag>{esc_xml(str(parsed["accountingFlag"]))}</AccountingFlag>')
|
||
else:
|
||
X(f'{indent}\t\t<AccountingFlag/>')
|
||
if elem_tag == 'Dimension':
|
||
deny_incomplete = 'true' if (parsed.get('denyIncompleteValues') is True or 'denyincomplete' in parsed.get('flags', [])) else 'false'
|
||
X(f'{indent}\t\t<DenyIncompleteValues>{deny_incomplete}</DenyIncompleteValues>')
|
||
else:
|
||
if parsed.get('extDimensionAccountingFlag'):
|
||
X(f'{indent}\t\t<ExtDimensionAccountingFlag>{esc_xml(str(parsed["extDimensionAccountingFlag"]))}</ExtDimensionAccountingFlag>')
|
||
else:
|
||
X(f'{indent}\t\t<ExtDimensionAccountingFlag/>')
|
||
if context == 'catalog':
|
||
X(f'{indent}\t\t<Use>{parsed.get("use") or "ForItem"}</Use>')
|
||
if context not in ('processor', 'processor-tabular'):
|
||
# Признаки учёта ПС (account-flag) не имеют <Indexing>/<FullTextSearch>, но имеют <DataHistory>.
|
||
if context != 'account-flag':
|
||
# Ресурс регистра накопления НЕ имеет <Indexing> (только <FullTextSearch>); измерение/реквизит — имеют.
|
||
if not (context in ('register-accum', 'register-calc', 'register-account') and elem_tag == 'Resource'):
|
||
indexing = 'DontIndex'
|
||
if 'index' in parsed.get('flags', []):
|
||
indexing = 'Index'
|
||
if 'indexadditional' in parsed.get('flags', []):
|
||
indexing = 'IndexWithAdditionalOrder'
|
||
if parsed.get('indexing'):
|
||
indexing = parsed['indexing']
|
||
X(f'{indent}\t\t<Indexing>{indexing}</Indexing>')
|
||
# Реквизит адресации задачи: AddressingDimension (между Indexing и FullTextSearch).
|
||
if context == 'task-addressing' and elem_tag == 'AddressingAttribute':
|
||
if parsed.get('addressingDimension'):
|
||
X(f'{indent}\t\t<AddressingDimension>{esc_xml(str(parsed["addressingDimension"]))}</AddressingDimension>')
|
||
else:
|
||
X(f'{indent}\t\t<AddressingDimension/>')
|
||
X(f'{indent}\t\t<FullTextSearch>{parsed.get("fullTextSearch") or "Use"}</FullTextSearch>')
|
||
# Измерение регистра накопления: UseInTotals (после FullTextSearch, дефолт true).
|
||
if elem_tag == 'Dimension' and context == 'register-accum':
|
||
use_in_totals = 'false' if (parsed.get('useInTotals') is False or 'nouseintotals' in parsed.get('flags', [])) else 'true'
|
||
X(f'{indent}\t\t<UseInTotals>{use_in_totals}</UseInTotals>')
|
||
# DataHistory — not for Chart* types and non-InformationRegister register family
|
||
if context not in ('chart', 'register-other', 'register-accum', 'register-calc', 'register-account'):
|
||
X(f'{indent}\t\t<DataHistory>{parsed.get("dataHistory") or "Use"}</DataHistory>')
|
||
X(f'{indent}\t</Properties>')
|
||
X(f'{indent}</{elem_tag}>')
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 9. TabularSection emitter
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def emit_command_picture(indent, cmd):
|
||
"""<Picture> команды — структурный блок (зеркало form-compile). Дефолт LoadTransparent=true (конвенция
|
||
кнопки/команды): фиксируем только false. Значение: строка-ref + sibling loadTransparent ЛИБО объект
|
||
{src, loadTransparent?, transparentPixel?}. src с префиксом "abs:" → <xr:Abs>, иначе <xr:Ref>. Нет → <Picture/>."""
|
||
pic = cmd.get('picture')
|
||
if not pic:
|
||
X(f'{indent}<Picture/>')
|
||
return
|
||
lt = True
|
||
tpx = None
|
||
if isinstance(pic, str):
|
||
src = pic
|
||
if cmd.get('loadTransparent') is False:
|
||
lt = False
|
||
else:
|
||
src = str(pic.get('src') or pic.get('ref') or '')
|
||
if pic.get('loadTransparent') is False:
|
||
lt = False
|
||
tpx = pic.get('transparentPixel')
|
||
if not src:
|
||
X(f'{indent}<Picture/>')
|
||
return
|
||
X(f'{indent}<Picture>')
|
||
m = re.match(r'^abs:(.*)$', src)
|
||
if m:
|
||
X(f'{indent}\t<xr:Abs>{esc_xml(m.group(1))}</xr:Abs>')
|
||
else:
|
||
X(f'{indent}\t<xr:Ref>{esc_xml(src)}</xr:Ref>')
|
||
X(f'{indent}\t<xr:LoadTransparent>{"true" if lt else "false"}</xr:LoadTransparent>')
|
||
if tpx:
|
||
X(f'{indent}\t<xr:TransparentPixel x="{tpx.get("x")}" y="{tpx.get("y")}"/>')
|
||
X(f'{indent}</Picture>')
|
||
|
||
def emit_command(indent, cmd_name, cmd):
|
||
X(f'{indent}<Command uuid="{new_uuid()}">')
|
||
X(f'{indent}\t<Properties>')
|
||
X(f'{indent}\t\t<Name>{esc_xml(cmd_name)}</Name>')
|
||
syn = cmd['synonym'] if cmd.get('synonym') is not None else split_camel_case(cmd_name)
|
||
emit_mltext(f'{indent}\t\t', 'Synonym', syn)
|
||
if cmd.get('comment'):
|
||
X(f'{indent}\t\t<Comment>{esc_xml_text(str(cmd["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{indent}\t\t<Comment/>')
|
||
group = resolve_command_group(cmd.get('group'), cmd_name)
|
||
if cmd.get('commandParameterType') and group in SECTION_COMMAND_GROUPS:
|
||
sys.stderr.write(f"Команда '{cmd_name}': тип параметра (commandParameterType) недоступен для команд "
|
||
f"командного интерфейса раздела ('{group}'). Тип параметра — только для групп формы "
|
||
f"(FormCommandBar*/FormNavigationPanel*) или CommandGroup.<Имя>.\n")
|
||
sys.exit(1)
|
||
X(f'{indent}\t\t<Group>{esc_xml(group)}</Group>')
|
||
if cmd.get('commandParameterType'):
|
||
X(f'{indent}\t\t<CommandParameterType>')
|
||
emit_type_content(f'{indent}\t\t\t', str(cmd['commandParameterType']))
|
||
X(f'{indent}\t\t</CommandParameterType>')
|
||
else:
|
||
X(f'{indent}\t\t<CommandParameterType/>')
|
||
X(f'{indent}\t\t<ParameterUseMode>{cmd.get("parameterUseMode") or "Single"}</ParameterUseMode>')
|
||
X(f'{indent}\t\t<ModifiesData>{"true" if cmd.get("modifiesData") is True else "false"}</ModifiesData>')
|
||
X(f'{indent}\t\t<Representation>{cmd.get("representation") or "Auto"}</Representation>')
|
||
emit_mltext(f'{indent}\t\t', 'ToolTip', cmd.get('tooltip'))
|
||
emit_command_picture(f'{indent}\t\t', cmd)
|
||
if cmd.get('shortcut'):
|
||
X(f'{indent}\t\t<Shortcut>{esc_xml(str(cmd["shortcut"]))}</Shortcut>')
|
||
else:
|
||
X(f'{indent}\t\t<Shortcut/>')
|
||
X(f'{indent}\t\t<OnMainServerUnavalableBehavior>{cmd.get("onMainServerUnavalableBehavior") or "Auto"}</OnMainServerUnavalableBehavior>')
|
||
X(f'{indent}\t</Properties>')
|
||
X(f'{indent}</Command>')
|
||
|
||
def emit_tabular_section(indent, ts_name, columns, object_type, object_name, ts_synonym_arg=None, ts_tooltip=None, ts_comment=None, ts_line_number=None, ts_fill_checking=None, ts_use=None):
|
||
uid = new_uuid()
|
||
X(f'{indent}<TabularSection uuid="{uid}">')
|
||
type_prefix = f'{object_type}TabularSection'
|
||
row_prefix = f'{object_type}TabularSectionRow'
|
||
X(f'{indent}\t<InternalInfo>')
|
||
X(f'{indent}\t\t<xr:GeneratedType name="{type_prefix}.{object_name}.{ts_name}" category="TabularSection">')
|
||
X(f'{indent}\t\t\t<xr:TypeId>{new_uuid()}</xr:TypeId>')
|
||
X(f'{indent}\t\t\t<xr:ValueId>{new_uuid()}</xr:ValueId>')
|
||
X(f'{indent}\t\t</xr:GeneratedType>')
|
||
X(f'{indent}\t\t<xr:GeneratedType name="{row_prefix}.{object_name}.{ts_name}" category="TabularSectionRow">')
|
||
X(f'{indent}\t\t\t<xr:TypeId>{new_uuid()}</xr:TypeId>')
|
||
X(f'{indent}\t\t\t<xr:ValueId>{new_uuid()}</xr:ValueId>')
|
||
X(f'{indent}\t\t</xr:GeneratedType>')
|
||
X(f'{indent}\t</InternalInfo>')
|
||
ts_synonym = ts_synonym_arg if ts_synonym_arg is not None else split_camel_case(ts_name)
|
||
X(f'{indent}\t<Properties>')
|
||
X(f'{indent}\t\t<Name>{esc_xml(ts_name)}</Name>')
|
||
emit_mltext(f'{indent}\t\t', 'Synonym', ts_synonym)
|
||
if ts_comment:
|
||
X(f'{indent}\t\t<Comment>{esc_xml_text(ts_comment)}</Comment>')
|
||
else:
|
||
X(f'{indent}\t\t<Comment/>')
|
||
emit_mltext(f'{indent}\t\t', 'ToolTip', ts_tooltip)
|
||
X(f'{indent}\t\t<FillChecking>{ts_fill_checking if ts_fill_checking else "DontCheck"}</FillChecking>')
|
||
# TS-блок стандартных реквизитов (LineNumber) эмитим ВСЕГДА, кроме подавления `lineNumber: ""` (дом-конвенция
|
||
# суппресса): ~6% ТЧ исторически опускают блок (правило не выводимо — Товары all-default его имеет, соседи нет).
|
||
if not (isinstance(ts_line_number, str) and ts_line_number == ''):
|
||
emit_tabular_standard_attributes(f'{indent}\t\t', ts_line_number)
|
||
if object_type in ('Catalog', 'ChartOfCharacteristicTypes'):
|
||
X(f'{indent}\t\t<Use>{ts_use if ts_use else "ForItem"}</Use>')
|
||
X(f'{indent}\t</Properties>')
|
||
ts_context = 'processor-tabular' if object_type in ('DataProcessor', 'Report') else 'tabular'
|
||
X(f'{indent}\t<ChildObjects>')
|
||
for col in columns:
|
||
parsed = parse_attribute_shorthand(col)
|
||
emit_attribute(f'{indent}\t\t', parsed, ts_context)
|
||
X(f'{indent}\t</ChildObjects>')
|
||
X(f'{indent}</TabularSection>')
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 10. EnumValue emitter
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def emit_enum_value(indent, parsed):
|
||
uid = new_uuid()
|
||
X(f'{indent}<EnumValue uuid="{uid}">')
|
||
X(f'{indent}\t<Properties>')
|
||
X(f'{indent}\t\t<Name>{esc_xml(parsed["name"])}</Name>')
|
||
emit_mltext(f'{indent}\t\t', 'Synonym', parsed['synonym'])
|
||
if parsed.get('comment'):
|
||
X(f'{indent}\t\t<Comment>{esc_xml_text(parsed["comment"])}</Comment>')
|
||
else:
|
||
X(f'{indent}\t\t<Comment/>')
|
||
X(f'{indent}\t</Properties>')
|
||
X(f'{indent}</EnumValue>')
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 11. Dimension emitter
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def emit_dimension(indent, parsed, register_type):
|
||
uid = new_uuid()
|
||
X(f'{indent}<Dimension uuid="{uid}">')
|
||
X(f'{indent}\t<Properties>')
|
||
X(f'{indent}\t\t<Name>{esc_xml(parsed["name"])}</Name>')
|
||
emit_mltext(f'{indent}\t\t', 'Synonym', parsed['synonym'])
|
||
X(f'{indent}\t\t<Comment/>')
|
||
type_str = parsed['type']
|
||
if type_str:
|
||
emit_value_type(f'{indent}\t\t', type_str)
|
||
else:
|
||
X(f'{indent}\t\t<Type>')
|
||
X(f'{indent}\t\t\t<v8:Type>xs:string</v8:Type>')
|
||
X(f'{indent}\t\t</Type>')
|
||
X(f'{indent}\t\t<PasswordMode>false</PasswordMode>')
|
||
X(f'{indent}\t\t<Format/>')
|
||
X(f'{indent}\t\t<EditFormat/>')
|
||
X(f'{indent}\t\t<ToolTip/>')
|
||
X(f'{indent}\t\t<MarkNegatives>{"true" if parsed.get("markNegatives") is True else "false"}</MarkNegatives>')
|
||
X(f'{indent}\t\t<Mask/>')
|
||
multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false'
|
||
X(f'{indent}\t\t<MultiLine>{multi_line}</MultiLine>')
|
||
ext_edit = 'true' if parsed.get('extendedEdit') is True else 'false'
|
||
X(f'{indent}\t\t<ExtendedEdit>{ext_edit}</ExtendedEdit>')
|
||
emit_min_max_value(f'{indent}\t\t', 'MinValue', parsed.get('minValue'))
|
||
emit_min_max_value(f'{indent}\t\t', 'MaxValue', parsed.get('maxValue'))
|
||
flags = parsed.get('flags', [])
|
||
if register_type == 'InformationRegister':
|
||
fill_from = 'true' if 'master' in flags else 'false'
|
||
X(f'{indent}\t\t<FillFromFillingValue>{fill_from}</FillFromFillingValue>')
|
||
X(f'{indent}\t\t<FillValue xsi:nil="true"/>')
|
||
fill_checking = 'DontCheck'
|
||
if 'req' in flags:
|
||
fill_checking = 'ShowError'
|
||
X(f'{indent}\t\t<FillChecking>{fill_checking}</FillChecking>')
|
||
X(f'{indent}\t\t<ChoiceFoldersAndItems>{parsed.get("choiceFoldersAndItems") or "Items"}</ChoiceFoldersAndItems>')
|
||
X(f'{indent}\t\t<ChoiceParameterLinks/>')
|
||
X(f'{indent}\t\t<ChoiceParameters/>')
|
||
X(f'{indent}\t\t<QuickChoice>Auto</QuickChoice>')
|
||
X(f'{indent}\t\t<CreateOnInput>Auto</CreateOnInput>')
|
||
X(f'{indent}\t\t<ChoiceForm>{esc_xml(str(parsed["choiceForm"]))}</ChoiceForm>' if parsed.get('choiceForm') else f'{indent}\t\t<ChoiceForm/>')
|
||
X(f'{indent}\t\t<LinkByType/>')
|
||
X(f'{indent}\t\t<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
|
||
if register_type == 'InformationRegister':
|
||
master = 'true' if 'master' in flags else 'false'
|
||
main_filter = 'true' if 'mainfilter' in flags else 'false'
|
||
deny_incomplete = 'true' if 'denyincomplete' in flags else 'false'
|
||
X(f'{indent}\t\t<Master>{master}</Master>')
|
||
X(f'{indent}\t\t<MainFilter>{main_filter}</MainFilter>')
|
||
X(f'{indent}\t\t<DenyIncompleteValues>{deny_incomplete}</DenyIncompleteValues>')
|
||
if register_type == 'AccumulationRegister':
|
||
deny_incomplete = 'true' if 'denyincomplete' in flags else 'false'
|
||
X(f'{indent}\t\t<DenyIncompleteValues>{deny_incomplete}</DenyIncompleteValues>')
|
||
indexing = 'DontIndex'
|
||
if 'index' in flags:
|
||
indexing = 'Index'
|
||
X(f'{indent}\t\t<Indexing>{indexing}</Indexing>')
|
||
X(f'{indent}\t\t<FullTextSearch>Use</FullTextSearch>')
|
||
if register_type == 'AccumulationRegister':
|
||
use_in_totals = 'false' if 'nouseintotals' in flags else 'true'
|
||
X(f'{indent}\t\t<UseInTotals>{use_in_totals}</UseInTotals>')
|
||
if register_type == 'InformationRegister':
|
||
X(f'{indent}\t\t<DataHistory>Use</DataHistory>')
|
||
X(f'{indent}\t</Properties>')
|
||
X(f'{indent}</Dimension>')
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 12. Resource emitter
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def emit_resource(indent, parsed, register_type):
|
||
uid = new_uuid()
|
||
X(f'{indent}<Resource uuid="{uid}">')
|
||
X(f'{indent}\t<Properties>')
|
||
X(f'{indent}\t\t<Name>{esc_xml(parsed["name"])}</Name>')
|
||
emit_mltext(f'{indent}\t\t', 'Synonym', parsed['synonym'])
|
||
X(f'{indent}\t\t<Comment/>')
|
||
type_str = parsed['type']
|
||
if type_str:
|
||
emit_value_type(f'{indent}\t\t', type_str)
|
||
else:
|
||
X(f'{indent}\t\t<Type>')
|
||
X(f'{indent}\t\t\t<v8:Type>xs:decimal</v8:Type>')
|
||
X(f'{indent}\t\t\t<v8:NumberQualifiers>')
|
||
X(f'{indent}\t\t\t\t<v8:Digits>15</v8:Digits>')
|
||
X(f'{indent}\t\t\t\t<v8:FractionDigits>2</v8:FractionDigits>')
|
||
X(f'{indent}\t\t\t\t<v8:AllowedSign>Any</v8:AllowedSign>')
|
||
X(f'{indent}\t\t\t</v8:NumberQualifiers>')
|
||
X(f'{indent}\t\t</Type>')
|
||
X(f'{indent}\t\t<PasswordMode>false</PasswordMode>')
|
||
X(f'{indent}\t\t<Format/>')
|
||
X(f'{indent}\t\t<EditFormat/>')
|
||
X(f'{indent}\t\t<ToolTip/>')
|
||
X(f'{indent}\t\t<MarkNegatives>{"true" if parsed.get("markNegatives") is True else "false"}</MarkNegatives>')
|
||
X(f'{indent}\t\t<Mask/>')
|
||
multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false'
|
||
X(f'{indent}\t\t<MultiLine>{multi_line}</MultiLine>')
|
||
ext_edit = 'true' if parsed.get('extendedEdit') is True else 'false'
|
||
X(f'{indent}\t\t<ExtendedEdit>{ext_edit}</ExtendedEdit>')
|
||
emit_min_max_value(f'{indent}\t\t', 'MinValue', parsed.get('minValue'))
|
||
emit_min_max_value(f'{indent}\t\t', 'MaxValue', parsed.get('maxValue'))
|
||
if register_type == 'InformationRegister':
|
||
X(f'{indent}\t\t<FillFromFillingValue>false</FillFromFillingValue>')
|
||
X(f'{indent}\t\t<FillValue xsi:nil="true"/>')
|
||
flags = parsed.get('flags', [])
|
||
fill_checking = 'DontCheck'
|
||
if 'req' in flags:
|
||
fill_checking = 'ShowError'
|
||
X(f'{indent}\t\t<FillChecking>{fill_checking}</FillChecking>')
|
||
X(f'{indent}\t\t<ChoiceFoldersAndItems>{parsed.get("choiceFoldersAndItems") or "Items"}</ChoiceFoldersAndItems>')
|
||
X(f'{indent}\t\t<ChoiceParameterLinks/>')
|
||
X(f'{indent}\t\t<ChoiceParameters/>')
|
||
X(f'{indent}\t\t<QuickChoice>Auto</QuickChoice>')
|
||
X(f'{indent}\t\t<CreateOnInput>Auto</CreateOnInput>')
|
||
X(f'{indent}\t\t<ChoiceForm>{esc_xml(str(parsed["choiceForm"]))}</ChoiceForm>' if parsed.get('choiceForm') else f'{indent}\t\t<ChoiceForm/>')
|
||
X(f'{indent}\t\t<LinkByType/>')
|
||
X(f'{indent}\t\t<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
|
||
if register_type == 'InformationRegister':
|
||
X(f'{indent}\t\t<Indexing>DontIndex</Indexing>')
|
||
X(f'{indent}\t\t<FullTextSearch>Use</FullTextSearch>')
|
||
X(f'{indent}\t\t<DataHistory>Use</DataHistory>')
|
||
if register_type == 'AccumulationRegister':
|
||
X(f'{indent}\t\t<FullTextSearch>Use</FullTextSearch>')
|
||
X(f'{indent}\t</Properties>')
|
||
X(f'{indent}</Resource>')
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 13. Property emitters per type
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def emit_catalog_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
hierarchical = 'true' if defn.get('hierarchical') is True else 'false'
|
||
hierarchy_type = get_enum_prop('HierarchyType', 'hierarchyType', 'HierarchyFoldersAndItems')
|
||
X(f'{i}<Hierarchical>{hierarchical}</Hierarchical>')
|
||
X(f'{i}<HierarchyType>{hierarchy_type}</HierarchyType>')
|
||
limit_level_count = 'true' if defn.get('limitLevelCount') is True else 'false'
|
||
level_count = str(defn['levelCount']) if defn.get('levelCount') is not None else '2'
|
||
folders_on_top = 'false' if defn.get('foldersOnTop') is False else 'true'
|
||
X(f'{i}<LimitLevelCount>{limit_level_count}</LimitLevelCount>')
|
||
X(f'{i}<LevelCount>{level_count}</LevelCount>')
|
||
X(f'{i}<FoldersOnTop>{folders_on_top}</FoldersOnTop>')
|
||
use_std_cmds = 'true' if get_bool_prop('useStandardCommands', True) else 'false'
|
||
X(f'{i}<UseStandardCommands>{use_std_cmds}</UseStandardCommands>')
|
||
owners = defn.get('owners', [])
|
||
if owners:
|
||
X(f'{i}<Owners>')
|
||
for owner_ref in owners:
|
||
full_ref = owner_ref if '.' in str(owner_ref) else f'Catalog.{owner_ref}'
|
||
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{full_ref}</xr:Item>')
|
||
X(f'{i}</Owners>')
|
||
else:
|
||
X(f'{i}<Owners/>')
|
||
subordination_use = get_enum_prop('SubordinationUse', 'subordinationUse', 'ToItems')
|
||
X(f'{i}<SubordinationUse>{subordination_use}</SubordinationUse>')
|
||
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '9'
|
||
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '25'
|
||
code_type = get_enum_prop('CodeType', 'codeType', 'String')
|
||
code_allowed_length = get_enum_prop('CodeAllowedLength', 'codeAllowedLength', 'Variable')
|
||
autonumbering = 'false' if defn.get('autonumbering') is False else 'true'
|
||
check_unique = 'true' if defn.get('checkUnique') is True else 'false'
|
||
X(f'{i}<CodeLength>{code_length}</CodeLength>')
|
||
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
|
||
X(f'{i}<CodeType>{code_type}</CodeType>')
|
||
X(f'{i}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>')
|
||
code_series = get_enum_prop('CodeSeries', 'codeSeries', 'WholeCatalog')
|
||
X(f'{i}<CodeSeries>{code_series}</CodeSeries>')
|
||
X(f'{i}<CheckUnique>{check_unique}</CheckUnique>')
|
||
X(f'{i}<Autonumbering>{autonumbering}</Autonumbering>')
|
||
default_presentation = get_enum_prop('DefaultPresentation', 'defaultPresentation', 'AsDescription')
|
||
X(f'{i}<DefaultPresentation>{default_presentation}</DefaultPresentation>')
|
||
emit_standard_attributes(i, 'Catalog')
|
||
emit_characteristics(i, defn.get('characteristics'))
|
||
X(f'{i}<PredefinedDataUpdate>{get_enum_prop("PredefinedDataUpdate", "predefinedDataUpdate", "Auto")}</PredefinedDataUpdate>')
|
||
X(f'{i}<EditType>{get_enum_prop("EditType", "editType", "InDialog")}</EditType>')
|
||
quick_choice = 'true' if defn.get('quickChoice') is True else 'false'
|
||
choice_mode = get_enum_prop('ChoiceMode', 'choiceMode', 'BothWays')
|
||
X(f'{i}<QuickChoice>{quick_choice}</QuickChoice>')
|
||
X(f'{i}<ChoiceMode>{choice_mode}</ChoiceMode>')
|
||
# InputByString: override `inputByString` (массив имён, авто-резолв; [] = пусто) ЛИБО дефолт [Descr при D>0]+[Code при C>0].
|
||
if 'inputByString' in defn:
|
||
ib_fields = [expand_data_path(str(x)) for x in (defn.get('inputByString') or [])]
|
||
else:
|
||
ib_fields = []
|
||
if int(description_length) > 0:
|
||
ib_fields.append(f'Catalog.{obj_name}.StandardAttribute.Description')
|
||
if int(code_length) > 0:
|
||
ib_fields.append(f'Catalog.{obj_name}.StandardAttribute.Code')
|
||
emit_field_block(i, 'InputByString', ib_fields)
|
||
X(f'{i}<SearchStringModeOnInputByString>{get_enum_prop("SearchStringModeOnInputByString", "searchStringModeOnInputByString", "Begin")}</SearchStringModeOnInputByString>')
|
||
X(f'{i}<FullTextSearchOnInputByString>{get_enum_prop("FullTextSearchOnInputByString", "fullTextSearchOnInputByString", "DontUse")}</FullTextSearchOnInputByString>')
|
||
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
|
||
emit_form_ref(i, 'DefaultObjectForm', defn.get('defaultObjectForm'))
|
||
emit_form_ref(i, 'DefaultFolderForm', defn.get('defaultFolderForm'))
|
||
emit_form_ref(i, 'DefaultListForm', defn.get('defaultListForm'))
|
||
emit_form_ref(i, 'DefaultChoiceForm', defn.get('defaultChoiceForm'))
|
||
emit_form_ref(i, 'DefaultFolderChoiceForm', defn.get('defaultFolderChoiceForm'))
|
||
emit_form_ref(i, 'AuxiliaryObjectForm', defn.get('auxiliaryObjectForm'))
|
||
emit_form_ref(i, 'AuxiliaryFolderForm', defn.get('auxiliaryFolderForm'))
|
||
emit_form_ref(i, 'AuxiliaryListForm', defn.get('auxiliaryListForm'))
|
||
emit_form_ref(i, 'AuxiliaryChoiceForm', defn.get('auxiliaryChoiceForm'))
|
||
emit_form_ref(i, 'AuxiliaryFolderChoiceForm', defn.get('auxiliaryFolderChoiceForm'))
|
||
incl_help = 'true' if get_bool_prop('includeHelpInContents', False) else 'false'
|
||
X(f'{i}<IncludeHelpInContents>{incl_help}</IncludeHelpInContents>')
|
||
emit_based_on(i, defn.get('basedOn'))
|
||
dl_fields = [expand_data_path(str(x)) for x in defn.get('dataLockFields', [])] if 'dataLockFields' in defn else []
|
||
emit_field_block(i, 'DataLockFields', dl_fields)
|
||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Managed')
|
||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||
emit_mltext(i, 'ObjectPresentation', defn.get('objectPresentation'))
|
||
emit_mltext(i, 'ExtendedObjectPresentation', defn.get('extendedObjectPresentation'))
|
||
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
|
||
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
X(f'{i}<CreateOnInput>{get_enum_prop("CreateOnInput", "createOnInput", "Use")}</CreateOnInput>')
|
||
X(f'{i}<ChoiceHistoryOnInput>{get_enum_prop("ChoiceHistoryOnInput", "choiceHistoryOnInput", "Auto")}</ChoiceHistoryOnInput>')
|
||
X(f'{i}<DataHistory>DontUse</DataHistory>')
|
||
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>')
|
||
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>')
|
||
|
||
def emit_document_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
use_std_cmd = 'true' if get_bool_prop('useStandardCommands', True) else 'false'
|
||
X(f'{i}<UseStandardCommands>{use_std_cmd}</UseStandardCommands>')
|
||
if defn.get('numerator'):
|
||
X(f'{i}<Numerator>{esc_xml(str(defn["numerator"]))}</Numerator>')
|
||
else:
|
||
X(f'{i}<Numerator/>')
|
||
number_type = get_enum_prop('NumberType', 'numberType', 'String')
|
||
number_length = str(defn['numberLength']) if defn.get('numberLength') is not None else '11'
|
||
number_allowed_length = get_enum_prop('NumberAllowedLength', 'numberAllowedLength', 'Variable')
|
||
number_periodicity = get_enum_prop('NumberPeriodicity', 'numberPeriodicity', 'Year')
|
||
check_unique = 'false' if defn.get('checkUnique') is False else 'true'
|
||
autonumbering = 'false' if defn.get('autonumbering') is False else 'true'
|
||
X(f'{i}<NumberType>{number_type}</NumberType>')
|
||
X(f'{i}<NumberLength>{number_length}</NumberLength>')
|
||
X(f'{i}<NumberAllowedLength>{number_allowed_length}</NumberAllowedLength>')
|
||
X(f'{i}<NumberPeriodicity>{number_periodicity}</NumberPeriodicity>')
|
||
X(f'{i}<CheckUnique>{check_unique}</CheckUnique>')
|
||
X(f'{i}<Autonumbering>{autonumbering}</Autonumbering>')
|
||
emit_standard_attributes(i, 'Document')
|
||
emit_characteristics(i, defn.get('characteristics'))
|
||
emit_based_on(i, defn.get('basedOn'))
|
||
# InputByString: override `inputByString` ЛИБО дефолт [Номер].
|
||
if 'inputByString' in defn:
|
||
ib_fields = [expand_data_path(str(x)) for x in (defn.get('inputByString') or [])]
|
||
else:
|
||
ib_fields = [f'Document.{obj_name}.StandardAttribute.Number']
|
||
emit_field_block(i, 'InputByString', ib_fields)
|
||
X(f'{i}<CreateOnInput>{get_enum_prop("CreateOnInput", "createOnInput", "Use")}</CreateOnInput>')
|
||
X(f'{i}<SearchStringModeOnInputByString>{get_enum_prop("SearchStringModeOnInputByString", "searchStringModeOnInputByString", "Begin")}</SearchStringModeOnInputByString>')
|
||
X(f'{i}<FullTextSearchOnInputByString>{get_enum_prop("FullTextSearchOnInputByString", "fullTextSearchOnInputByString", "DontUse")}</FullTextSearchOnInputByString>')
|
||
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
|
||
emit_form_ref(i, 'DefaultObjectForm', defn.get('defaultObjectForm'))
|
||
emit_form_ref(i, 'DefaultListForm', defn.get('defaultListForm'))
|
||
emit_form_ref(i, 'DefaultChoiceForm', defn.get('defaultChoiceForm'))
|
||
emit_form_ref(i, 'AuxiliaryObjectForm', defn.get('auxiliaryObjectForm'))
|
||
emit_form_ref(i, 'AuxiliaryListForm', defn.get('auxiliaryListForm'))
|
||
emit_form_ref(i, 'AuxiliaryChoiceForm', defn.get('auxiliaryChoiceForm'))
|
||
X(f'{i}<Posting>{get_enum_prop("Posting", "posting", "Allow")}</Posting>')
|
||
X(f'{i}<RealTimePosting>{get_enum_prop("RealTimePosting", "realTimePosting", "Deny")}</RealTimePosting>')
|
||
X(f'{i}<RegisterRecordsDeletion>{get_enum_prop("RegisterRecordsDeletion", "registerRecordsDeletion", "AutoDelete")}</RegisterRecordsDeletion>')
|
||
X(f'{i}<RegisterRecordsWritingOnPost>{get_enum_prop("RegisterRecordsWritingOnPost", "registerRecordsWritingOnPost", "WriteSelected")}</RegisterRecordsWritingOnPost>')
|
||
X(f'{i}<SequenceFilling>{get_enum_prop("SequenceFilling", "sequenceFilling", "AutoFill")}</SequenceFilling>')
|
||
# RegisterRecords — движения (список MDObjectRef, синонимы типов резолвятся).
|
||
reg_records = []
|
||
if defn.get('registerRecords'):
|
||
for rr in defn['registerRecords']:
|
||
rr_str = str(rr)
|
||
if '.' in rr_str:
|
||
dot_idx = rr_str.index('.')
|
||
rr_prefix = rr_str[:dot_idx]
|
||
rr_suffix = rr_str[dot_idx + 1:]
|
||
if rr_prefix in object_type_synonyms:
|
||
rr_prefix = object_type_synonyms[rr_prefix]
|
||
reg_records.append(f'{rr_prefix}.{rr_suffix}')
|
||
else:
|
||
reg_records.append(rr_str)
|
||
if reg_records:
|
||
X(f'{i}<RegisterRecords>')
|
||
for rr in reg_records:
|
||
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{rr}</xr:Item>')
|
||
X(f'{i}</RegisterRecords>')
|
||
else:
|
||
X(f'{i}<RegisterRecords/>')
|
||
post_in_priv = 'false' if defn.get('postInPrivilegedMode') is False else 'true'
|
||
unpost_in_priv = 'false' if defn.get('unpostInPrivilegedMode') is False else 'true'
|
||
X(f'{i}<PostInPrivilegedMode>{post_in_priv}</PostInPrivilegedMode>')
|
||
X(f'{i}<UnpostInPrivilegedMode>{unpost_in_priv}</UnpostInPrivilegedMode>')
|
||
incl_help = 'true' if get_bool_prop('includeHelpInContents', False) else 'false'
|
||
X(f'{i}<IncludeHelpInContents>{incl_help}</IncludeHelpInContents>')
|
||
dl_fields = [expand_data_path(str(x)) for x in (defn.get('dataLockFields') or [])] if 'dataLockFields' in defn else []
|
||
emit_field_block(i, 'DataLockFields', dl_fields)
|
||
X(f'{i}<DataLockControlMode>{get_enum_prop("DataLockControlMode", "dataLockControlMode", "Managed")}</DataLockControlMode>')
|
||
X(f'{i}<FullTextSearch>{get_enum_prop("FullTextSearch", "fullTextSearch", "Use")}</FullTextSearch>')
|
||
emit_mltext(i, 'ObjectPresentation', defn.get('objectPresentation'))
|
||
emit_mltext(i, 'ExtendedObjectPresentation', defn.get('extendedObjectPresentation'))
|
||
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
|
||
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
X(f'{i}<ChoiceHistoryOnInput>{get_enum_prop("ChoiceHistoryOnInput", "choiceHistoryOnInput", "Auto")}</ChoiceHistoryOnInput>')
|
||
X(f'{i}<DataHistory>{get_enum_prop("DataHistory", "dataHistory", "DontUse")}</DataHistory>')
|
||
upd_dh = 'true' if get_bool_prop('updateDataHistoryImmediatelyAfterWrite', False) else 'false'
|
||
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>{upd_dh}</UpdateDataHistoryImmediatelyAfterWrite>')
|
||
exec_dh = 'true' if get_bool_prop('executeAfterWriteDataHistoryVersionProcessing', False) else 'false'
|
||
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>{exec_dh}</ExecuteAfterWriteDataHistoryVersionProcessing>')
|
||
|
||
def emit_enum_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(defn["comment"])}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
use_std_cmds = 'true' if get_bool_prop('useStandardCommands', False) else 'false'
|
||
X(f'{i}<UseStandardCommands>{use_std_cmds}</UseStandardCommands>')
|
||
emit_standard_attributes(i, 'Enum')
|
||
emit_characteristics(i, defn.get('characteristics'))
|
||
quick_choice = 'false' if defn.get('quickChoice') is False else 'true'
|
||
X(f'{i}<QuickChoice>{quick_choice}</QuickChoice>')
|
||
X(f'{i}<ChoiceMode>{get_enum_prop("ChoiceMode", "choiceMode", "BothWays")}</ChoiceMode>')
|
||
emit_form_ref(i, 'DefaultListForm', defn.get('defaultListForm'))
|
||
emit_form_ref(i, 'DefaultChoiceForm', defn.get('defaultChoiceForm'))
|
||
emit_form_ref(i, 'AuxiliaryListForm', defn.get('auxiliaryListForm'))
|
||
emit_form_ref(i, 'AuxiliaryChoiceForm', defn.get('auxiliaryChoiceForm'))
|
||
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
|
||
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
X(f'{i}<ChoiceHistoryOnInput>{get_enum_prop("ChoiceHistoryOnInput", "choiceHistoryOnInput", "Auto")}</ChoiceHistoryOnInput>')
|
||
|
||
def emit_constant_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
# Type — valueType (явный '' → <Type/>, реквизит без типа; отсутствие → String дефолт).
|
||
value_type = build_type_str(defn)
|
||
type_empty = (defn.get('valueType') is not None and str(defn.get('valueType')).strip() == '') or \
|
||
(defn.get('type') is not None and str(defn.get('type')).strip() == '')
|
||
if type_empty:
|
||
X(f'{i}<Type/>')
|
||
else:
|
||
emit_value_type(i, value_type or 'String')
|
||
use_std_cmds = 'true' if get_bool_prop('useStandardCommands', True) else 'false'
|
||
X(f'{i}<UseStandardCommands>{use_std_cmds}</UseStandardCommands>')
|
||
emit_verbatim_ref(i, 'DefaultForm', defn.get('defaultForm'))
|
||
emit_mltext(i, 'ExtendedPresentation', defn.get('extendedPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
X(f'{i}<PasswordMode>{"true" if get_bool_prop("passwordMode", False) else "false"}</PasswordMode>')
|
||
emit_mltext(i, 'Format', defn.get('format'))
|
||
emit_mltext(i, 'EditFormat', defn.get('editFormat'))
|
||
emit_mltext(i, 'ToolTip', defn.get('tooltip'))
|
||
X(f'{i}<MarkNegatives>{"true" if get_bool_prop("markNegatives", False) else "false"}</MarkNegatives>')
|
||
if defn.get('mask'):
|
||
X(f'{i}<Mask>{esc_xml_text(str(defn["mask"]))}</Mask>')
|
||
else:
|
||
X(f'{i}<Mask/>')
|
||
X(f'{i}<MultiLine>{"true" if get_bool_prop("multiLine", False) else "false"}</MultiLine>')
|
||
X(f'{i}<ExtendedEdit>{"true" if get_bool_prop("extendedEdit", False) else "false"}</ExtendedEdit>')
|
||
emit_min_max_value(i, 'MinValue', defn.get('minValue'))
|
||
emit_min_max_value(i, 'MaxValue', defn.get('maxValue'))
|
||
X(f'{i}<FillChecking>{get_enum_prop("FillChecking", "fillChecking", "DontCheck")}</FillChecking>')
|
||
X(f'{i}<ChoiceFoldersAndItems>{get_enum_prop("ChoiceFoldersAndItems", "choiceFoldersAndItems", "Items")}</ChoiceFoldersAndItems>')
|
||
emit_choice_parameter_links(i, defn.get('choiceParameterLinks'))
|
||
emit_choice_parameters(i, defn.get('choiceParameters'))
|
||
X(f'{i}<QuickChoice>{get_enum_prop("QuickChoice", "quickChoice", "Auto")}</QuickChoice>')
|
||
if defn.get('choiceForm'):
|
||
X(f'{i}<ChoiceForm>{esc_xml(str(defn["choiceForm"]))}</ChoiceForm>')
|
||
else:
|
||
X(f'{i}<ChoiceForm/>')
|
||
emit_link_by_type(i, defn.get('linkByType'))
|
||
X(f'{i}<ChoiceHistoryOnInput>{get_enum_prop("ChoiceHistoryOnInput", "choiceHistoryOnInput", "Auto")}</ChoiceHistoryOnInput>')
|
||
X(f'{i}<DataLockControlMode>{get_enum_prop("DataLockControlMode", "dataLockControlMode", "Managed")}</DataLockControlMode>')
|
||
X(f'{i}<DataHistory>{get_enum_prop("DataHistory", "dataHistory", "DontUse")}</DataHistory>')
|
||
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>{"true" if get_bool_prop("updateDataHistoryImmediatelyAfterWrite", False) else "false"}</UpdateDataHistoryImmediatelyAfterWrite>')
|
||
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>{"true" if get_bool_prop("executeAfterWriteDataHistoryVersionProcessing", False) else "false"}</ExecuteAfterWriteDataHistoryVersionProcessing>')
|
||
|
||
def emit_information_register_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
use_std_cmd = 'true' if get_bool_prop('useStandardCommands', True) else 'false'
|
||
X(f'{i}<UseStandardCommands>{use_std_cmd}</UseStandardCommands>')
|
||
X(f'{i}<EditType>{get_enum_prop("EditType", "editType", "InDialog")}</EditType>')
|
||
emit_form_ref(i, 'DefaultRecordForm', defn.get('defaultRecordForm'))
|
||
emit_form_ref(i, 'DefaultListForm', defn.get('defaultListForm'))
|
||
emit_form_ref(i, 'AuxiliaryRecordForm', defn.get('auxiliaryRecordForm'))
|
||
emit_form_ref(i, 'AuxiliaryListForm', defn.get('auxiliaryListForm'))
|
||
emit_standard_attributes(i, 'InformationRegister')
|
||
periodicity = get_enum_prop('InformationRegisterPeriodicity', 'periodicity', 'Nonperiodical')
|
||
write_mode = get_enum_prop('WriteMode', 'writeMode', 'Independent')
|
||
main_filter_on_period = 'true' if get_bool_prop('mainFilterOnPeriod', False) else 'false'
|
||
X(f'{i}<InformationRegisterPeriodicity>{periodicity}</InformationRegisterPeriodicity>')
|
||
X(f'{i}<WriteMode>{write_mode}</WriteMode>')
|
||
X(f'{i}<MainFilterOnPeriod>{main_filter_on_period}</MainFilterOnPeriod>')
|
||
incl_help = 'true' if get_bool_prop('includeHelpInContents', False) else 'false'
|
||
X(f'{i}<IncludeHelpInContents>{incl_help}</IncludeHelpInContents>')
|
||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Managed')
|
||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||
en_tot_first = 'true' if get_bool_prop('enableTotalsSliceFirst', False) else 'false'
|
||
en_tot_last = 'true' if get_bool_prop('enableTotalsSliceLast', False) else 'false'
|
||
X(f'{i}<EnableTotalsSliceFirst>{en_tot_first}</EnableTotalsSliceFirst>')
|
||
X(f'{i}<EnableTotalsSliceLast>{en_tot_last}</EnableTotalsSliceLast>')
|
||
emit_mltext(i, 'RecordPresentation', defn.get('recordPresentation'))
|
||
emit_mltext(i, 'ExtendedRecordPresentation', defn.get('extendedRecordPresentation'))
|
||
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
|
||
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
X(f'{i}<DataHistory>{get_enum_prop("DataHistory", "dataHistory", "DontUse")}</DataHistory>')
|
||
upd_dh = 'true' if get_bool_prop('updateDataHistoryImmediatelyAfterWrite', False) else 'false'
|
||
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>{upd_dh}</UpdateDataHistoryImmediatelyAfterWrite>')
|
||
exec_dh = 'true' if get_bool_prop('executeAfterWriteDataHistoryVersionProcessing', False) else 'false'
|
||
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>{exec_dh}</ExecuteAfterWriteDataHistoryVersionProcessing>')
|
||
|
||
def emit_accumulation_register_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
use_std_cmd = 'true' if get_bool_prop('useStandardCommands', True) else 'false'
|
||
X(f'{i}<UseStandardCommands>{use_std_cmd}</UseStandardCommands>')
|
||
emit_form_ref(i, 'DefaultListForm', defn.get('defaultListForm'))
|
||
emit_form_ref(i, 'AuxiliaryListForm', defn.get('auxiliaryListForm'))
|
||
register_type = get_enum_prop('RegisterType', 'registerType', 'Balance')
|
||
X(f'{i}<RegisterType>{register_type}</RegisterType>')
|
||
incl_help = 'true' if get_bool_prop('includeHelpInContents', False) else 'false'
|
||
X(f'{i}<IncludeHelpInContents>{incl_help}</IncludeHelpInContents>')
|
||
emit_standard_attributes(i, 'AccumulationRegister')
|
||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Managed')
|
||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||
enable_totals_splitting = 'false' if defn.get('enableTotalsSplitting') is False else 'true'
|
||
X(f'{i}<EnableTotalsSplitting>{enable_totals_splitting}</EnableTotalsSplitting>')
|
||
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
|
||
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
|
||
# --- 13a. DefinedType, CommonModule, ScheduledJob, EventSubscription ---
|
||
|
||
def emit_defined_type_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
# Type — единый эмиттер emit_value_type/emit_type_content (составной через ' + '); valueType или valueTypes.
|
||
if defn.get('valueType'):
|
||
vt = str(defn['valueType'])
|
||
elif defn.get('valueTypes'):
|
||
vt = ' + '.join(str(x) for x in defn['valueTypes'])
|
||
else:
|
||
vt = ''
|
||
if vt:
|
||
emit_value_type(i, vt)
|
||
else:
|
||
X(f'{i}<Type/>')
|
||
|
||
def emit_functional_option_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
# Location — хранилище значения опции (ссылка verbatim; location или value).
|
||
loc = str(defn['location']) if defn.get('location') else (str(defn['value']) if defn.get('value') else '')
|
||
if loc:
|
||
X(f'{i}<Location>{esc_xml(normalize_md_object_ref(loc))}</Location>')
|
||
else:
|
||
X(f'{i}<Location/>')
|
||
# PrivilegedGetMode — дефолт true (корпус 2864/2864).
|
||
X(f'{i}<PrivilegedGetMode>{"true" if get_bool_prop("privilegedGetMode", True) else "false"}</PrivilegedGetMode>')
|
||
# Content — зависимые объекты (список MDObjectRef-путей). omit-on-empty.
|
||
content = list(defn['content']) if defn.get('content') else []
|
||
if content:
|
||
X(f'{i}<Content>')
|
||
for obj in content:
|
||
X(f'{i}\t<xr:Object>{esc_xml(normalize_md_object_ref(str(obj)))}</xr:Object>')
|
||
X(f'{i}</Content>')
|
||
else:
|
||
X(f'{i}<Content/>')
|
||
|
||
def emit_md_ref_list(indent, tag, items):
|
||
"""Список MDObjectRef (Documents/RegisterRecords/DocumentMap/…) с <xr:Item>. omit-on-empty."""
|
||
arr = list(items) if items else []
|
||
if arr:
|
||
X(f'{indent}<{tag}>')
|
||
for it in arr:
|
||
X(f'{indent}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(normalize_md_object_ref(str(it)))}</xr:Item>')
|
||
X(f'{indent}</{tag}>')
|
||
else:
|
||
X(f'{indent}<{tag}/>')
|
||
|
||
def emit_sequence_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
X(f'{i}<MoveBoundaryOnPosting>{get_enum_prop("MoveBoundaryOnPosting", "moveBoundaryOnPosting", "DontMove")}</MoveBoundaryOnPosting>')
|
||
emit_md_ref_list(i, 'Documents', defn.get('documents'))
|
||
emit_md_ref_list(i, 'RegisterRecords', defn.get('registerRecords'))
|
||
X(f'{i}<DataLockControlMode>{get_enum_prop("DataLockControlMode", "dataLockControlMode", "Managed")}</DataLockControlMode>')
|
||
|
||
def emit_filter_criterion_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
if defn.get('valueType'):
|
||
vt = str(defn['valueType'])
|
||
elif defn.get('valueTypes'):
|
||
vt = ' + '.join(str(x) for x in defn['valueTypes'])
|
||
else:
|
||
vt = ''
|
||
if vt:
|
||
emit_value_type(i, vt)
|
||
else:
|
||
X(f'{i}<Type/>')
|
||
use_std_cmds = 'true' if get_bool_prop('useStandardCommands', True) else 'false'
|
||
X(f'{i}<UseStandardCommands>{use_std_cmds}</UseStandardCommands>')
|
||
content = list(defn['content']) if defn.get('content') else []
|
||
if content:
|
||
X(f'{i}<Content>')
|
||
for obj in content:
|
||
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(normalize_md_object_ref(str(obj)))}</xr:Item>')
|
||
X(f'{i}</Content>')
|
||
else:
|
||
X(f'{i}<Content/>')
|
||
emit_verbatim_ref(i, 'DefaultForm', defn.get('defaultForm'))
|
||
emit_verbatim_ref(i, 'AuxiliaryForm', defn.get('auxiliaryForm'))
|
||
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
|
||
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
|
||
def emit_document_numerator_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
X(f'{i}<NumberType>{get_enum_prop("NumberType", "numberType", "String")}</NumberType>')
|
||
num_len = str(defn['numberLength']) if defn.get('numberLength') is not None else '11'
|
||
X(f'{i}<NumberLength>{num_len}</NumberLength>')
|
||
X(f'{i}<NumberAllowedLength>{get_enum_prop("NumberAllowedLength", "numberAllowedLength", "Variable")}</NumberAllowedLength>')
|
||
X(f'{i}<NumberPeriodicity>{get_enum_prop("NumberPeriodicity", "numberPeriodicity", "Year")}</NumberPeriodicity>')
|
||
X(f'{i}<CheckUnique>{"true" if get_bool_prop("checkUnique", True) else "false"}</CheckUnique>')
|
||
|
||
def emit_settings_storage_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
emit_verbatim_ref(i, 'DefaultSaveForm', defn.get('defaultSaveForm'))
|
||
emit_verbatim_ref(i, 'DefaultLoadForm', defn.get('defaultLoadForm'))
|
||
emit_verbatim_ref(i, 'AuxiliarySaveForm', defn.get('auxiliarySaveForm'))
|
||
emit_verbatim_ref(i, 'AuxiliaryLoadForm', defn.get('auxiliaryLoadForm'))
|
||
|
||
def emit_common_form_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
X(f'{i}<FormType>{get_enum_prop("FormType", "formType", "Managed")}</FormType>')
|
||
incl_help = 'true' if get_bool_prop('includeHelpInContents', False) else 'false'
|
||
X(f'{i}<IncludeHelpInContents>{incl_help}</IncludeHelpInContents>')
|
||
purposes = list(defn['usePurposes']) if defn.get('usePurposes') else ['PlatformApplication', 'MobilePlatformApplication']
|
||
if purposes:
|
||
X(f'{i}<UsePurposes>')
|
||
for p in purposes:
|
||
X(f'{i}\t<v8:Value xsi:type="app:ApplicationUsePurpose">{p}</v8:Value>')
|
||
X(f'{i}</UsePurposes>')
|
||
else:
|
||
X(f'{i}<UsePurposes/>')
|
||
use_std_cmds = 'true' if get_bool_prop('useStandardCommands', False) else 'false'
|
||
X(f'{i}<UseStandardCommands>{use_std_cmds}</UseStandardCommands>')
|
||
emit_mltext(i, 'ExtendedPresentation', defn.get('extendedPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
|
||
def _emit_comment(i):
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
|
||
def emit_session_parameter_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
_emit_comment(i)
|
||
if defn.get('valueType'):
|
||
vt = str(defn['valueType'])
|
||
elif defn.get('valueTypes'):
|
||
vt = ' + '.join(str(x) for x in defn['valueTypes'])
|
||
else:
|
||
vt = ''
|
||
if vt:
|
||
emit_value_type(i, vt)
|
||
else:
|
||
X(f'{i}<Type/>')
|
||
|
||
def emit_functional_options_parameter_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
_emit_comment(i)
|
||
emit_md_ref_list(i, 'Use', defn.get('use'))
|
||
|
||
def emit_ws_reference_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
_emit_comment(i)
|
||
url = str(defn['locationURL']) if defn.get('locationURL') else (str(defn['locationUrl']) if defn.get('locationUrl') else '')
|
||
if url:
|
||
X(f'{i}<LocationURL>{esc_xml_text(url)}</LocationURL>')
|
||
else:
|
||
X(f'{i}<LocationURL/>')
|
||
|
||
def emit_common_picture_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
_emit_comment(i)
|
||
X(f'{i}<AvailabilityForChoice>{"true" if get_bool_prop("availabilityForChoice", False) else "false"}</AvailabilityForChoice>')
|
||
X(f'{i}<AvailabilityForAppearance>{"true" if get_bool_prop("availabilityForAppearance", False) else "false"}</AvailabilityForAppearance>')
|
||
|
||
def emit_common_template_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
_emit_comment(i)
|
||
X(f'{i}<TemplateType>{get_enum_prop("TemplateType", "templateType", "SpreadsheetDocument")}</TemplateType>')
|
||
|
||
def emit_command_group_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
_emit_comment(i)
|
||
X(f'{i}<Representation>{get_enum_prop("Representation", "representation", "Auto")}</Representation>')
|
||
emit_mltext(i, 'ToolTip', defn.get('tooltip'))
|
||
emit_command_picture(i, defn)
|
||
X(f'{i}<Category>{get_enum_prop("Category", "category", "NavigationPanel")}</Category>')
|
||
|
||
def emit_common_command_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
_emit_comment(i)
|
||
group = str(defn['group']) if defn.get('group') else ''
|
||
if group:
|
||
X(f'{i}<Group>{esc_xml(group)}</Group>')
|
||
else:
|
||
X(f'{i}<Group/>')
|
||
X(f'{i}<Representation>{get_enum_prop("Representation", "representation", "Auto")}</Representation>')
|
||
emit_mltext(i, 'ToolTip', defn.get('tooltip'))
|
||
emit_command_picture(i, defn)
|
||
if defn.get('shortcut'):
|
||
X(f'{i}<Shortcut>{esc_xml(str(defn["shortcut"]))}</Shortcut>')
|
||
else:
|
||
X(f'{i}<Shortcut/>')
|
||
incl_help = 'true' if get_bool_prop('includeHelpInContents', False) else 'false'
|
||
X(f'{i}<IncludeHelpInContents>{incl_help}</IncludeHelpInContents>')
|
||
if defn.get('commandParameterType'):
|
||
X(f'{i}<CommandParameterType>')
|
||
emit_type_content(f'{i}\t', str(defn['commandParameterType']))
|
||
X(f'{i}</CommandParameterType>')
|
||
else:
|
||
X(f'{i}<CommandParameterType/>')
|
||
X(f'{i}<ParameterUseMode>{get_enum_prop("ParameterUseMode", "parameterUseMode", "Single")}</ParameterUseMode>')
|
||
X(f'{i}<ModifiesData>{"true" if get_bool_prop("modifiesData", False) else "false"}</ModifiesData>')
|
||
X(f'{i}<OnMainServerUnavalableBehavior>{get_enum_prop("OnMainServerUnavalableBehavior", "onMainServerUnavalableBehavior", "Auto")}</OnMainServerUnavalableBehavior>')
|
||
|
||
def emit_common_attribute_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
_emit_comment(i)
|
||
vt = str(defn['valueType']) if defn.get('valueType') else 'String(0)'
|
||
emit_value_type(i, vt)
|
||
X(f'{i}<PasswordMode>{"true" if get_bool_prop("passwordMode", False) else "false"}</PasswordMode>')
|
||
emit_mltext(i, 'Format', defn.get('format'))
|
||
emit_mltext(i, 'EditFormat', defn.get('editFormat'))
|
||
emit_mltext(i, 'ToolTip', defn.get('tooltip'))
|
||
X(f'{i}<MarkNegatives>{"true" if get_bool_prop("markNegatives", False) else "false"}</MarkNegatives>')
|
||
if defn.get('mask'):
|
||
X(f'{i}<Mask>{esc_xml_text(str(defn["mask"]))}</Mask>')
|
||
else:
|
||
X(f'{i}<Mask/>')
|
||
X(f'{i}<MultiLine>{"true" if get_bool_prop("multiLine", False) else "false"}</MultiLine>')
|
||
X(f'{i}<ExtendedEdit>{"true" if get_bool_prop("extendedEdit", False) else "false"}</ExtendedEdit>')
|
||
emit_min_max_value(i, 'MinValue', defn.get('minValue'))
|
||
emit_min_max_value(i, 'MaxValue', defn.get('maxValue'))
|
||
X(f'{i}<FillFromFillingValue>{"true" if get_bool_prop("fillFromFillingValue", False) else "false"}</FillFromFillingValue>')
|
||
emit_fill_value(i, vt, defn.get('fillValue'), defn.get('fillValue') is not None)
|
||
X(f'{i}<FillChecking>{get_enum_prop("FillChecking", "fillChecking", "DontCheck")}</FillChecking>')
|
||
X(f'{i}<ChoiceFoldersAndItems>{get_enum_prop("ChoiceFoldersAndItems", "choiceFoldersAndItems", "Items")}</ChoiceFoldersAndItems>')
|
||
emit_choice_parameter_links(i, defn.get('choiceParameterLinks'))
|
||
emit_choice_parameters(i, defn.get('choiceParameters'))
|
||
X(f'{i}<QuickChoice>{get_enum_prop("QuickChoice", "quickChoice", "Auto")}</QuickChoice>')
|
||
X(f'{i}<CreateOnInput>{get_enum_prop("CreateOnInput", "createOnInput", "Auto")}</CreateOnInput>')
|
||
if defn.get('choiceForm'):
|
||
X(f'{i}<ChoiceForm>{esc_xml(str(defn["choiceForm"]))}</ChoiceForm>')
|
||
else:
|
||
X(f'{i}<ChoiceForm/>')
|
||
emit_link_by_type(i, defn.get('linkByType'))
|
||
X(f'{i}<ChoiceHistoryOnInput>{get_enum_prop("ChoiceHistoryOnInput", "choiceHistoryOnInput", "Auto")}</ChoiceHistoryOnInput>')
|
||
content = list(defn['content']) if defn.get('content') else []
|
||
if content:
|
||
X(f'{i}<Content>')
|
||
for c in content:
|
||
md = c if isinstance(c, str) else str(c.get('metadata', ''))
|
||
use = 'Use' if isinstance(c, str) else (str(c['use']) if c.get('use') else 'Use')
|
||
X(f'{i}\t<xr:Item>')
|
||
X(f'{i}\t\t<xr:Metadata>{esc_xml(normalize_md_object_ref(md))}</xr:Metadata>')
|
||
X(f'{i}\t\t<xr:Use>{use}</xr:Use>')
|
||
cs = '' if isinstance(c, str) else (str(c['conditionalSeparation']) if c.get('conditionalSeparation') else '')
|
||
if cs:
|
||
X(f'{i}\t\t<xr:ConditionalSeparation>{esc_xml(cs)}</xr:ConditionalSeparation>')
|
||
else:
|
||
X(f'{i}\t\t<xr:ConditionalSeparation/>')
|
||
X(f'{i}\t</xr:Item>')
|
||
X(f'{i}</Content>')
|
||
else:
|
||
X(f'{i}<Content/>')
|
||
X(f'{i}<AutoUse>{get_enum_prop("AutoUse", "autoUse", "DontUse")}</AutoUse>')
|
||
X(f'{i}<DataSeparation>{get_enum_prop("DataSeparation", "dataSeparation", "DontUse")}</DataSeparation>')
|
||
X(f'{i}<SeparatedDataUse>{get_enum_prop("SeparatedDataUse", "separatedDataUse", "Independently")}</SeparatedDataUse>')
|
||
dsv = str(defn['dataSeparationValue']) if defn.get('dataSeparationValue') else ''
|
||
X(f'{i}<DataSeparationValue>{esc_xml(dsv)}</DataSeparationValue>' if dsv else f'{i}<DataSeparationValue/>')
|
||
dsu = str(defn['dataSeparationUse']) if defn.get('dataSeparationUse') else ''
|
||
X(f'{i}<DataSeparationUse>{esc_xml(dsu)}</DataSeparationUse>' if dsu else f'{i}<DataSeparationUse/>')
|
||
cs2 = str(defn['conditionalSeparation']) if defn.get('conditionalSeparation') else ''
|
||
X(f'{i}<ConditionalSeparation>{esc_xml(cs2)}</ConditionalSeparation>' if cs2 else f'{i}<ConditionalSeparation/>')
|
||
X(f'{i}<UsersSeparation>{get_enum_prop("UsersSeparation", "usersSeparation", "DontUse")}</UsersSeparation>')
|
||
X(f'{i}<AuthenticationSeparation>{get_enum_prop("AuthenticationSeparation", "authenticationSeparation", "DontUse")}</AuthenticationSeparation>')
|
||
X(f'{i}<ConfigurationExtensionsSeparation>{get_enum_prop("ConfigurationExtensionsSeparation", "configurationExtensionsSeparation", "DontUse")}</ConfigurationExtensionsSeparation>')
|
||
X(f'{i}<Indexing>{get_enum_prop("Indexing", "indexing", "DontIndex")}</Indexing>')
|
||
X(f'{i}<FullTextSearch>{get_enum_prop("FullTextSearch", "fullTextSearch", "Use")}</FullTextSearch>')
|
||
X(f'{i}<DataHistory>{get_enum_prop("DataHistory", "dataHistory", "Use")}</DataHistory>')
|
||
|
||
def emit_sequence_dimension(indent, dim_def):
|
||
uid = new_uuid()
|
||
parsed = parse_attribute_shorthand(dim_def)
|
||
X(f'{indent}<Dimension uuid="{uid}">')
|
||
X(f'{indent}\t<Properties>')
|
||
X(f'{indent}\t\t<Name>{esc_xml(parsed["name"])}</Name>')
|
||
emit_mltext(f'{indent}\t\t', 'Synonym', parsed['synonym'])
|
||
if parsed.get('comment'):
|
||
X(f'{indent}\t\t<Comment>{esc_xml_text(parsed["comment"])}</Comment>')
|
||
else:
|
||
X(f'{indent}\t\t<Comment/>')
|
||
if parsed.get('typeEmpty'):
|
||
X(f'{indent}\t\t<Type/>')
|
||
elif parsed['type']:
|
||
emit_value_type(f'{indent}\t\t', parsed['type'])
|
||
else:
|
||
X(f'{indent}\t\t<Type/>')
|
||
dm = None if isinstance(dim_def, str) else dim_def.get('documentMap')
|
||
rrm = None if isinstance(dim_def, str) else dim_def.get('registerRecordsMap')
|
||
emit_md_ref_list(f'{indent}\t\t', 'DocumentMap', dm)
|
||
emit_md_ref_list(f'{indent}\t\t', 'RegisterRecordsMap', rrm)
|
||
X(f'{indent}\t</Properties>')
|
||
X(f'{indent}</Dimension>')
|
||
|
||
def emit_common_module_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
context = str(defn['context']) if defn.get('context') else ''
|
||
global_val = 'true' if defn.get('global') is True else 'false'
|
||
server = 'false'
|
||
server_call = 'false'
|
||
client_managed = 'false'
|
||
client_ordinary = 'false'
|
||
external_connection = 'false'
|
||
privileged = 'false'
|
||
if context == 'server' or context == 'serverCall':
|
||
server = 'true'
|
||
server_call = 'true'
|
||
elif context == 'client':
|
||
client_managed = 'true'
|
||
elif context == 'serverClient':
|
||
server = 'true'
|
||
client_managed = 'true'
|
||
else:
|
||
if defn.get('server') is True:
|
||
server = 'true'
|
||
if defn.get('serverCall') is True:
|
||
server_call = 'true'
|
||
if defn.get('clientManagedApplication') is True:
|
||
client_managed = 'true'
|
||
if defn.get('clientOrdinaryApplication') is True:
|
||
client_ordinary = 'true'
|
||
if defn.get('externalConnection') is True:
|
||
external_connection = 'true'
|
||
if defn.get('privileged') is True:
|
||
privileged = 'true'
|
||
X(f'{i}<Global>{global_val}</Global>')
|
||
X(f'{i}<ClientManagedApplication>{client_managed}</ClientManagedApplication>')
|
||
X(f'{i}<Server>{server}</Server>')
|
||
X(f'{i}<ExternalConnection>{external_connection}</ExternalConnection>')
|
||
X(f'{i}<ClientOrdinaryApplication>{client_ordinary}</ClientOrdinaryApplication>')
|
||
X(f'{i}<ServerCall>{server_call}</ServerCall>')
|
||
X(f'{i}<Privileged>{privileged}</Privileged>')
|
||
return_values_reuse = get_enum_prop('ReturnValuesReuse', 'returnValuesReuse', 'DontUse')
|
||
X(f'{i}<ReturnValuesReuse>{return_values_reuse}</ReturnValuesReuse>')
|
||
|
||
def emit_scheduled_job_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
method_name = str(defn['methodName']) if defn.get('methodName') else ''
|
||
# Ensure CommonModule. prefix
|
||
if method_name and not method_name.startswith('CommonModule.'):
|
||
method_name = f'CommonModule.{method_name}'
|
||
X(f'{i}<MethodName>{esc_xml(method_name)}</MethodName>')
|
||
# Description — плоская строка (дефолт ПУСТО, не синоним — иначе роундтрип рвётся).
|
||
description = str(defn['description']) if defn.get('description') else ''
|
||
if description:
|
||
X(f'{i}<Description>{esc_xml_text(description)}</Description>')
|
||
else:
|
||
X(f'{i}<Description/>')
|
||
key = str(defn['key']) if defn.get('key') else ''
|
||
X(f'{i}<Key>{esc_xml(key)}</Key>')
|
||
use = 'true' if defn.get('use') is True else 'false'
|
||
X(f'{i}<Use>{use}</Use>')
|
||
predefined = 'true' if defn.get('predefined') is True else 'false'
|
||
X(f'{i}<Predefined>{predefined}</Predefined>')
|
||
restart_count = str(defn['restartCountOnFailure']) if defn.get('restartCountOnFailure') is not None else '3'
|
||
restart_interval = str(defn['restartIntervalOnFailure']) if defn.get('restartIntervalOnFailure') is not None else '10'
|
||
X(f'{i}<RestartCountOnFailure>{restart_count}</RestartCountOnFailure>')
|
||
X(f'{i}<RestartIntervalOnFailure>{restart_interval}</RestartIntervalOnFailure>')
|
||
|
||
def emit_event_subscription_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
sources = list(defn.get('source', []))
|
||
if sources:
|
||
X(f'{i}<Source>')
|
||
for src in sources:
|
||
emit_type_content(f'{i}\t', resolve_type_str(str(src)))
|
||
X(f'{i}</Source>')
|
||
else:
|
||
X(f'{i}<Source/>')
|
||
event = str(defn['event']) if defn.get('event') else 'BeforeWrite'
|
||
X(f'{i}<Event>{event}</Event>')
|
||
handler = str(defn['handler']) if defn.get('handler') else ''
|
||
# Ensure CommonModule. prefix
|
||
if handler and not handler.startswith('CommonModule.'):
|
||
handler = f'CommonModule.{handler}'
|
||
X(f'{i}<Handler>{esc_xml(handler)}</Handler>')
|
||
|
||
# --- 13b. Report, DataProcessor ---
|
||
|
||
def emit_report_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(defn["comment"])}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
# UseStandardCommands: дефолт true (авторски-безопасно — доступность через стандартный командный интерфейс;
|
||
# при false и без переопределения размещения команд объект доступен лишь по навигационной ссылке).
|
||
use_std_cmds = 'true' if get_bool_prop('useStandardCommands', True) else 'false'
|
||
X(f'{i}<UseStandardCommands>{use_std_cmds}</UseStandardCommands>')
|
||
emit_verbatim_ref(i, 'DefaultForm', defn.get('defaultForm'))
|
||
emit_verbatim_ref(i, 'AuxiliaryForm', defn.get('auxiliaryForm'))
|
||
emit_verbatim_ref(i, 'MainDataCompositionSchema', defn.get('mainDataCompositionSchema'))
|
||
emit_verbatim_ref(i, 'DefaultSettingsForm', defn.get('defaultSettingsForm'))
|
||
emit_verbatim_ref(i, 'AuxiliarySettingsForm', defn.get('auxiliarySettingsForm'))
|
||
emit_verbatim_ref(i, 'DefaultVariantForm', defn.get('defaultVariantForm'))
|
||
emit_verbatim_ref(i, 'VariantsStorage', defn.get('variantsStorage'))
|
||
emit_verbatim_ref(i, 'SettingsStorage', defn.get('settingsStorage'))
|
||
incl_help = 'true' if get_bool_prop('includeHelpInContents', False) else 'false'
|
||
X(f'{i}<IncludeHelpInContents>{incl_help}</IncludeHelpInContents>')
|
||
emit_mltext(i, 'ExtendedPresentation', defn.get('extendedPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
|
||
def emit_data_processor_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(defn["comment"])}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
use_std_cmds = 'true' if get_bool_prop('useStandardCommands', True) else 'false'
|
||
X(f'{i}<UseStandardCommands>{use_std_cmds}</UseStandardCommands>')
|
||
emit_verbatim_ref(i, 'DefaultForm', defn.get('defaultForm'))
|
||
emit_verbatim_ref(i, 'AuxiliaryForm', defn.get('auxiliaryForm'))
|
||
incl_help = 'true' if get_bool_prop('includeHelpInContents', False) else 'false'
|
||
X(f'{i}<IncludeHelpInContents>{incl_help}</IncludeHelpInContents>')
|
||
emit_mltext(i, 'ExtendedPresentation', defn.get('extendedPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
|
||
# --- 13c. ExchangePlan, ChartOfCharacteristicTypes, DocumentJournal ---
|
||
|
||
def emit_exchange_plan_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
X(f'{i}<UseStandardCommands>{"true" if get_bool_prop("useStandardCommands", True) else "false"}</UseStandardCommands>')
|
||
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '9'
|
||
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '150'
|
||
code_allowed_length = get_enum_prop('CodeAllowedLength', 'codeAllowedLength', 'Variable')
|
||
X(f'{i}<CodeLength>{code_length}</CodeLength>')
|
||
X(f'{i}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>')
|
||
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
|
||
X(f'{i}<DefaultPresentation>{get_enum_prop("DefaultPresentation", "defaultPresentation", "AsDescription")}</DefaultPresentation>')
|
||
X(f'{i}<EditType>{get_enum_prop("EditType", "editType", "InDialog")}</EditType>')
|
||
X(f'{i}<QuickChoice>{"true" if defn.get("quickChoice") is True else "false"}</QuickChoice>')
|
||
X(f'{i}<ChoiceMode>{get_enum_prop("ChoiceMode", "choiceMode", "BothWays")}</ChoiceMode>')
|
||
# InputByString: override `inputByString` ЛИБО дефолт [Descr при D>0]+[Code при C>0] (prefix ExchangePlan).
|
||
if 'inputByString' in defn:
|
||
ib_fields = [expand_data_path(str(x)) for x in (defn.get('inputByString') or [])]
|
||
else:
|
||
ib_fields = []
|
||
if int(description_length) > 0:
|
||
ib_fields.append(f'ExchangePlan.{obj_name}.StandardAttribute.Description')
|
||
if int(code_length) > 0:
|
||
ib_fields.append(f'ExchangePlan.{obj_name}.StandardAttribute.Code')
|
||
emit_field_block(i, 'InputByString', ib_fields)
|
||
X(f'{i}<SearchStringModeOnInputByString>{get_enum_prop("SearchStringModeOnInputByString", "searchStringModeOnInputByString", "Begin")}</SearchStringModeOnInputByString>')
|
||
X(f'{i}<FullTextSearchOnInputByString>{get_enum_prop("FullTextSearchOnInputByString", "fullTextSearchOnInputByString", "DontUse")}</FullTextSearchOnInputByString>')
|
||
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
|
||
emit_form_ref(i, 'DefaultObjectForm', defn.get('defaultObjectForm'))
|
||
emit_form_ref(i, 'DefaultListForm', defn.get('defaultListForm'))
|
||
emit_form_ref(i, 'DefaultChoiceForm', defn.get('defaultChoiceForm'))
|
||
emit_form_ref(i, 'AuxiliaryObjectForm', defn.get('auxiliaryObjectForm'))
|
||
emit_form_ref(i, 'AuxiliaryListForm', defn.get('auxiliaryListForm'))
|
||
emit_form_ref(i, 'AuxiliaryChoiceForm', defn.get('auxiliaryChoiceForm'))
|
||
emit_standard_attributes(i, 'ExchangePlan')
|
||
emit_characteristics(i, defn.get('characteristics'))
|
||
emit_based_on(i, defn.get('basedOn'))
|
||
distributed = 'true' if defn.get('distributedInfoBase') is True else 'false'
|
||
include_ext = 'true' if defn.get('includeConfigurationExtensions') is True else 'false'
|
||
X(f'{i}<DistributedInfoBase>{distributed}</DistributedInfoBase>')
|
||
X(f'{i}<IncludeConfigurationExtensions>{include_ext}</IncludeConfigurationExtensions>')
|
||
X(f'{i}<CreateOnInput>{get_enum_prop("CreateOnInput", "createOnInput", "DontUse")}</CreateOnInput>')
|
||
X(f'{i}<ChoiceHistoryOnInput>{get_enum_prop("ChoiceHistoryOnInput", "choiceHistoryOnInput", "Auto")}</ChoiceHistoryOnInput>')
|
||
X(f'{i}<IncludeHelpInContents>{"true" if get_bool_prop("includeHelpInContents", False) else "false"}</IncludeHelpInContents>')
|
||
dl_fields = [expand_data_path(str(x)) for x in defn.get('dataLockFields', [])] if 'dataLockFields' in defn else []
|
||
emit_field_block(i, 'DataLockFields', dl_fields)
|
||
X(f'{i}<DataLockControlMode>{get_enum_prop("DataLockControlMode", "dataLockControlMode", "Managed")}</DataLockControlMode>')
|
||
X(f'{i}<FullTextSearch>{get_enum_prop("FullTextSearch", "fullTextSearch", "Use")}</FullTextSearch>')
|
||
emit_mltext(i, 'ObjectPresentation', defn.get('objectPresentation'))
|
||
emit_mltext(i, 'ExtendedObjectPresentation', defn.get('extendedObjectPresentation'))
|
||
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
|
||
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
X(f'{i}<DataHistory>{get_enum_prop("DataHistory", "dataHistory", "DontUse")}</DataHistory>')
|
||
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>{"true" if get_bool_prop("updateDataHistoryImmediatelyAfterWrite", False) else "false"}</UpdateDataHistoryImmediatelyAfterWrite>')
|
||
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>{"true" if get_bool_prop("executeAfterWriteDataHistoryVersionProcessing", False) else "false"}</ExecuteAfterWriteDataHistoryVersionProcessing>')
|
||
|
||
def emit_chart_of_characteristic_types_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
X(f'{i}<UseStandardCommands>{"true" if get_bool_prop("useStandardCommands", True) else "false"}</UseStandardCommands>')
|
||
X(f'{i}<IncludeHelpInContents>{"true" if get_bool_prop("includeHelpInContents", False) else "false"}</IncludeHelpInContents>')
|
||
if defn.get('characteristicExtValues'):
|
||
X(f'{i}<CharacteristicExtValues>{esc_xml(str(defn["characteristicExtValues"]))}</CharacteristicExtValues>')
|
||
else:
|
||
X(f'{i}<CharacteristicExtValues/>')
|
||
vt = defn.get('valueType')
|
||
if not vt and defn.get('valueTypes'):
|
||
vt = ' + '.join(defn['valueTypes'])
|
||
if vt:
|
||
X(f'{i}<Type>')
|
||
emit_type_content(f'{i}\t', str(vt))
|
||
X(f'{i}</Type>')
|
||
else:
|
||
X(f'{i}<Type>')
|
||
X(f'{i}\t<v8:Type>xs:boolean</v8:Type>')
|
||
X(f'{i}\t<v8:Type>xs:string</v8:Type>')
|
||
X(f'{i}\t<v8:StringQualifiers>')
|
||
X(f'{i}\t\t<v8:Length>100</v8:Length>')
|
||
X(f'{i}\t\t<v8:AllowedLength>Variable</v8:AllowedLength>')
|
||
X(f'{i}\t</v8:StringQualifiers>')
|
||
X(f'{i}\t<v8:Type>xs:decimal</v8:Type>')
|
||
X(f'{i}\t<v8:NumberQualifiers>')
|
||
X(f'{i}\t\t<v8:Digits>15</v8:Digits>')
|
||
X(f'{i}\t\t<v8:FractionDigits>2</v8:FractionDigits>')
|
||
X(f'{i}\t\t<v8:AllowedSign>Any</v8:AllowedSign>')
|
||
X(f'{i}\t</v8:NumberQualifiers>')
|
||
X(f'{i}\t<v8:Type>xs:dateTime</v8:Type>')
|
||
X(f'{i}\t<v8:DateQualifiers>')
|
||
X(f'{i}\t\t<v8:DateFractions>DateTime</v8:DateFractions>')
|
||
X(f'{i}\t</v8:DateQualifiers>')
|
||
X(f'{i}</Type>')
|
||
X(f'{i}<Hierarchical>{"true" if defn.get("hierarchical") is True else "false"}</Hierarchical>')
|
||
X(f'{i}<FoldersOnTop>{"false" if defn.get("foldersOnTop") is False else "true"}</FoldersOnTop>')
|
||
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '9'
|
||
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '100'
|
||
X(f'{i}<CodeLength>{code_length}</CodeLength>')
|
||
X(f'{i}<CodeAllowedLength>{get_enum_prop("CodeAllowedLength", "codeAllowedLength", "Variable")}</CodeAllowedLength>')
|
||
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
|
||
X(f'{i}<CodeSeries>{get_enum_prop("CodeSeries", "codeSeries", "WholeCharacteristicKind")}</CodeSeries>')
|
||
X(f'{i}<CheckUnique>{"false" if defn.get("checkUnique") is False else "true"}</CheckUnique>')
|
||
X(f'{i}<Autonumbering>{"false" if defn.get("autonumbering") is False else "true"}</Autonumbering>')
|
||
X(f'{i}<DefaultPresentation>{get_enum_prop("DefaultPresentation", "defaultPresentation", "AsDescription")}</DefaultPresentation>')
|
||
emit_standard_attributes(i, 'ChartOfCharacteristicTypes')
|
||
emit_characteristics(i, defn.get('characteristics'))
|
||
X(f'{i}<PredefinedDataUpdate>{get_enum_prop("PredefinedDataUpdate", "predefinedDataUpdate", "Auto")}</PredefinedDataUpdate>')
|
||
X(f'{i}<EditType>{get_enum_prop("EditType", "editType", "InDialog")}</EditType>')
|
||
X(f'{i}<QuickChoice>{"true" if defn.get("quickChoice") is True else "false"}</QuickChoice>')
|
||
X(f'{i}<ChoiceMode>{get_enum_prop("ChoiceMode", "choiceMode", "BothWays")}</ChoiceMode>')
|
||
if 'inputByString' in defn:
|
||
ib_fields = [expand_data_path(str(x)) for x in (defn.get('inputByString') or [])]
|
||
else:
|
||
ib_fields = []
|
||
if int(description_length) > 0:
|
||
ib_fields.append(f'ChartOfCharacteristicTypes.{obj_name}.StandardAttribute.Description')
|
||
if int(code_length) > 0:
|
||
ib_fields.append(f'ChartOfCharacteristicTypes.{obj_name}.StandardAttribute.Code')
|
||
emit_field_block(i, 'InputByString', ib_fields)
|
||
X(f'{i}<CreateOnInput>{get_enum_prop("CreateOnInput", "createOnInput", "DontUse")}</CreateOnInput>')
|
||
X(f'{i}<SearchStringModeOnInputByString>{get_enum_prop("SearchStringModeOnInputByString", "searchStringModeOnInputByString", "Begin")}</SearchStringModeOnInputByString>')
|
||
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
|
||
X(f'{i}<FullTextSearchOnInputByString>{get_enum_prop("FullTextSearchOnInputByString", "fullTextSearchOnInputByString", "DontUse")}</FullTextSearchOnInputByString>')
|
||
X(f'{i}<ChoiceHistoryOnInput>{get_enum_prop("ChoiceHistoryOnInput", "choiceHistoryOnInput", "Auto")}</ChoiceHistoryOnInput>')
|
||
emit_form_ref(i, 'DefaultObjectForm', defn.get('defaultObjectForm'))
|
||
emit_form_ref(i, 'DefaultFolderForm', defn.get('defaultFolderForm'))
|
||
emit_form_ref(i, 'DefaultListForm', defn.get('defaultListForm'))
|
||
emit_form_ref(i, 'DefaultChoiceForm', defn.get('defaultChoiceForm'))
|
||
emit_form_ref(i, 'DefaultFolderChoiceForm', defn.get('defaultFolderChoiceForm'))
|
||
emit_form_ref(i, 'AuxiliaryObjectForm', defn.get('auxiliaryObjectForm'))
|
||
emit_form_ref(i, 'AuxiliaryFolderForm', defn.get('auxiliaryFolderForm'))
|
||
emit_form_ref(i, 'AuxiliaryListForm', defn.get('auxiliaryListForm'))
|
||
emit_form_ref(i, 'AuxiliaryChoiceForm', defn.get('auxiliaryChoiceForm'))
|
||
emit_form_ref(i, 'AuxiliaryFolderChoiceForm', defn.get('auxiliaryFolderChoiceForm'))
|
||
emit_based_on(i, defn.get('basedOn'))
|
||
dl_fields = [expand_data_path(str(x)) for x in defn.get('dataLockFields', [])] if 'dataLockFields' in defn else []
|
||
emit_field_block(i, 'DataLockFields', dl_fields)
|
||
X(f'{i}<DataLockControlMode>{get_enum_prop("DataLockControlMode", "dataLockControlMode", "Managed")}</DataLockControlMode>')
|
||
X(f'{i}<FullTextSearch>{get_enum_prop("FullTextSearch", "fullTextSearch", "Use")}</FullTextSearch>')
|
||
emit_mltext(i, 'ObjectPresentation', defn.get('objectPresentation'))
|
||
emit_mltext(i, 'ExtendedObjectPresentation', defn.get('extendedObjectPresentation'))
|
||
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
|
||
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
X(f'{i}<DataHistory>{get_enum_prop("DataHistory", "dataHistory", "DontUse")}</DataHistory>')
|
||
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>{"true" if get_bool_prop("updateDataHistoryImmediatelyAfterWrite", False) else "false"}</UpdateDataHistoryImmediatelyAfterWrite>')
|
||
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>{"true" if get_bool_prop("executeAfterWriteDataHistoryVersionProcessing", False) else "false"}</ExecuteAfterWriteDataHistoryVersionProcessing>')
|
||
|
||
def emit_document_journal_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
emit_verbatim_ref(i, 'DefaultForm', defn.get('defaultForm'))
|
||
emit_verbatim_ref(i, 'AuxiliaryForm', defn.get('auxiliaryForm'))
|
||
use_std_cmds = 'true' if get_bool_prop('useStandardCommands', True) else 'false'
|
||
X(f'{i}<UseStandardCommands>{use_std_cmds}</UseStandardCommands>')
|
||
reg_docs = list(defn.get('registeredDocuments', []))
|
||
if reg_docs:
|
||
X(f'{i}<RegisteredDocuments>')
|
||
for rd in reg_docs:
|
||
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(normalize_md_object_ref(str(rd)))}</xr:Item>')
|
||
X(f'{i}</RegisteredDocuments>')
|
||
else:
|
||
X(f'{i}<RegisteredDocuments/>')
|
||
incl_help = 'true' if get_bool_prop('includeHelpInContents', False) else 'false'
|
||
X(f'{i}<IncludeHelpInContents>{incl_help}</IncludeHelpInContents>')
|
||
emit_standard_attributes(i, 'DocumentJournal')
|
||
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
|
||
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
|
||
def resolve_type_prefix_syn(ref):
|
||
"""Ссылка на объект: русский префикс типа → английский (ПланВидовХарактеристик.X → ChartOfCharacteristicTypes.X)."""
|
||
if ref and '.' in ref:
|
||
p, s = ref.split('.', 1)
|
||
if p in object_type_synonyms:
|
||
p = object_type_synonyms[p]
|
||
return f'{p}.{s}'
|
||
return ref
|
||
|
||
def emit_chart_of_accounts_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
X(f'{i}<UseStandardCommands>{"true" if get_bool_prop("useStandardCommands", True) else "false"}</UseStandardCommands>')
|
||
X(f'{i}<IncludeHelpInContents>{"true" if get_bool_prop("includeHelpInContents", False) else "false"}</IncludeHelpInContents>')
|
||
emit_based_on(i, defn.get('basedOn'))
|
||
# ExtDimensionTypes — ссылка на ПВХ видов субконто (прощающий ввод: ПланВидовХарактеристик.X → ChartOfCharacteristicTypes.X).
|
||
ext_dim_types = resolve_type_prefix_syn(str(defn['extDimensionTypes'])) if defn.get('extDimensionTypes') else ''
|
||
if ext_dim_types:
|
||
X(f'{i}<ExtDimensionTypes>{esc_xml(ext_dim_types)}</ExtDimensionTypes>')
|
||
else:
|
||
X(f'{i}<ExtDimensionTypes/>')
|
||
# Количество субконто: без ПВХ (extDimensionTypes) платформа не даёт > 0 → дефолт 0; с ПВХ — 3.
|
||
max_ext_dim = str(defn['maxExtDimensionCount']) if defn.get('maxExtDimensionCount') is not None else ('3' if ext_dim_types else '0')
|
||
X(f'{i}<MaxExtDimensionCount>{max_ext_dim}</MaxExtDimensionCount>')
|
||
if defn.get('codeMask'):
|
||
X(f'{i}<CodeMask>{esc_xml_text(str(defn["codeMask"]))}</CodeMask>')
|
||
else:
|
||
X(f'{i}<CodeMask/>')
|
||
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '9'
|
||
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '25'
|
||
X(f'{i}<CodeLength>{code_length}</CodeLength>')
|
||
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
|
||
X(f'{i}<CodeSeries>{get_enum_prop("CodeSeries", "codeSeries", "WholeChartOfAccounts")}</CodeSeries>')
|
||
X(f'{i}<CheckUnique>{"false" if defn.get("checkUnique") is False else "true"}</CheckUnique>')
|
||
X(f'{i}<DefaultPresentation>{get_enum_prop("DefaultPresentation", "defaultPresentation", "AsCode")}</DefaultPresentation>')
|
||
emit_standard_attributes(i, 'ChartOfAccounts')
|
||
emit_characteristics(i, defn.get('characteristics'))
|
||
# StandardTabularSections — ExtDimensionTypes (обёртка платформенно-константна: Synonym с пустым lang «Виды субконто»,
|
||
# Comment/ToolTip/FillChecking; 4 вложенных стандартных реквизита, ExtDimensionType → FillChecking=ShowError).
|
||
X(f'{i}<StandardTabularSections>')
|
||
X(f'{i}\t<xr:StandardTabularSection name="ExtDimensionTypes">')
|
||
X(f'{i}\t\t<xr:Synonym>')
|
||
X(f'{i}\t\t\t<v8:item>')
|
||
X(f'{i}\t\t\t\t<v8:lang/>')
|
||
X(f'{i}\t\t\t\t<v8:content>Виды субконто</v8:content>')
|
||
X(f'{i}\t\t\t</v8:item>')
|
||
X(f'{i}\t\t</xr:Synonym>')
|
||
X(f'{i}\t\t<xr:Comment/>')
|
||
X(f'{i}\t\t<xr:ToolTip/>')
|
||
X(f'{i}\t\t<xr:FillChecking>DontCheck</xr:FillChecking>')
|
||
X(f'{i}\t\t<xr:StandardAttributes>')
|
||
for st_attr in ['TurnoversOnly', 'Predefined', 'ExtDimensionType', 'LineNumber']:
|
||
st_ov = {'FillChecking': 'ShowError'} if st_attr == 'ExtDimensionType' else None
|
||
emit_standard_attribute(f'{i}\t\t\t', st_attr, st_ov)
|
||
X(f'{i}\t\t</xr:StandardAttributes>')
|
||
X(f'{i}\t</xr:StandardTabularSection>')
|
||
X(f'{i}</StandardTabularSections>')
|
||
X(f'{i}<PredefinedDataUpdate>{get_enum_prop("PredefinedDataUpdate", "predefinedDataUpdate", "Auto")}</PredefinedDataUpdate>')
|
||
X(f'{i}<EditType>{get_enum_prop("EditType", "editType", "InDialog")}</EditType>')
|
||
X(f'{i}<QuickChoice>{"true" if defn.get("quickChoice") is True else "false"}</QuickChoice>')
|
||
X(f'{i}<ChoiceMode>{get_enum_prop("ChoiceMode", "choiceMode", "BothWays")}</ChoiceMode>')
|
||
# InputByString: override ЛИБО дефолт [Descr при D>0]+[Code при C>0] (prefix ChartOfAccounts).
|
||
if 'inputByString' in defn:
|
||
ib_fields = [expand_data_path(str(x)) for x in (defn.get('inputByString') or [])]
|
||
else:
|
||
ib_fields = []
|
||
if int(description_length) > 0:
|
||
ib_fields.append(f'ChartOfAccounts.{obj_name}.StandardAttribute.Description')
|
||
if int(code_length) > 0:
|
||
ib_fields.append(f'ChartOfAccounts.{obj_name}.StandardAttribute.Code')
|
||
emit_field_block(i, 'InputByString', ib_fields)
|
||
X(f'{i}<SearchStringModeOnInputByString>{get_enum_prop("SearchStringModeOnInputByString", "searchStringModeOnInputByString", "Begin")}</SearchStringModeOnInputByString>')
|
||
X(f'{i}<FullTextSearchOnInputByString>{get_enum_prop("FullTextSearchOnInputByString", "fullTextSearchOnInputByString", "DontUse")}</FullTextSearchOnInputByString>')
|
||
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
|
||
X(f'{i}<CreateOnInput>{get_enum_prop("CreateOnInput", "createOnInput", "DontUse")}</CreateOnInput>')
|
||
X(f'{i}<ChoiceHistoryOnInput>{get_enum_prop("ChoiceHistoryOnInput", "choiceHistoryOnInput", "Auto")}</ChoiceHistoryOnInput>')
|
||
emit_form_ref(i, 'DefaultObjectForm', defn.get('defaultObjectForm'))
|
||
emit_form_ref(i, 'DefaultListForm', defn.get('defaultListForm'))
|
||
emit_form_ref(i, 'DefaultChoiceForm', defn.get('defaultChoiceForm'))
|
||
emit_form_ref(i, 'AuxiliaryObjectForm', defn.get('auxiliaryObjectForm'))
|
||
emit_form_ref(i, 'AuxiliaryListForm', defn.get('auxiliaryListForm'))
|
||
emit_form_ref(i, 'AuxiliaryChoiceForm', defn.get('auxiliaryChoiceForm'))
|
||
auto_order = 'false' if defn.get('autoOrderByCode') is False else 'true'
|
||
X(f'{i}<AutoOrderByCode>{auto_order}</AutoOrderByCode>')
|
||
order_length = str(defn['orderLength']) if defn.get('orderLength') is not None else '9'
|
||
X(f'{i}<OrderLength>{order_length}</OrderLength>')
|
||
dl_fields = [expand_data_path(str(x)) for x in defn.get('dataLockFields', [])] if 'dataLockFields' in defn else []
|
||
emit_field_block(i, 'DataLockFields', dl_fields)
|
||
X(f'{i}<DataLockControlMode>{get_enum_prop("DataLockControlMode", "dataLockControlMode", "Managed")}</DataLockControlMode>')
|
||
X(f'{i}<FullTextSearch>{get_enum_prop("FullTextSearch", "fullTextSearch", "Use")}</FullTextSearch>')
|
||
X(f'{i}<DataHistory>{get_enum_prop("DataHistory", "dataHistory", "DontUse")}</DataHistory>')
|
||
upd_dh = 'true' if get_bool_prop('updateDataHistoryImmediatelyAfterWrite', False) else 'false'
|
||
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>{upd_dh}</UpdateDataHistoryImmediatelyAfterWrite>')
|
||
exec_dh = 'true' if get_bool_prop('executeAfterWriteDataHistoryVersionProcessing', False) else 'false'
|
||
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>{exec_dh}</ExecuteAfterWriteDataHistoryVersionProcessing>')
|
||
emit_mltext(i, 'ObjectPresentation', defn.get('objectPresentation'))
|
||
emit_mltext(i, 'ExtendedObjectPresentation', defn.get('extendedObjectPresentation'))
|
||
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
|
||
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
|
||
def emit_accounting_register_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
X(f'{i}<UseStandardCommands>{"true" if get_bool_prop("useStandardCommands", True) else "false"}</UseStandardCommands>')
|
||
X(f'{i}<IncludeHelpInContents>{"true" if get_bool_prop("includeHelpInContents", False) else "false"}</IncludeHelpInContents>')
|
||
chart_of_accounts = str(defn['chartOfAccounts']) if defn.get('chartOfAccounts') else ''
|
||
if chart_of_accounts:
|
||
X(f'{i}<ChartOfAccounts>{esc_xml(chart_of_accounts)}</ChartOfAccounts>')
|
||
else:
|
||
X(f'{i}<ChartOfAccounts/>')
|
||
correspondence = 'true' if defn.get('correspondence') is True else 'false'
|
||
X(f'{i}<Correspondence>{correspondence}</Correspondence>')
|
||
period_adj_len = str(defn['periodAdjustmentLength']) if defn.get('periodAdjustmentLength') is not None else '0'
|
||
X(f'{i}<PeriodAdjustmentLength>{period_adj_len}</PeriodAdjustmentLength>')
|
||
emit_form_ref(i, 'DefaultListForm', defn.get('defaultListForm'))
|
||
emit_form_ref(i, 'AuxiliaryListForm', defn.get('auxiliaryListForm'))
|
||
emit_standard_attributes(i, 'AccountingRegister')
|
||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Managed')
|
||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||
X(f'{i}<EnableTotalsSplitting>{"false" if defn.get("enableTotalsSplitting") is False else "true"}</EnableTotalsSplitting>')
|
||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
|
||
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
|
||
# Стандартные ТЧ ПВР: Ведущие/Вытесняющие/Базовые виды расчёта (обёртка платформенно-константна, пустой lang;
|
||
# вложены Predefined/CalculationType(ShowError)/LineNumber).
|
||
CALC_TYPES_STD_TABULAR = [
|
||
('LeadingCalculationTypes', 'Ведущие виды расчета'),
|
||
('DisplacingCalculationTypes', 'Вытесняющие виды расчета'),
|
||
('BaseCalculationTypes', 'Базовые виды расчета'),
|
||
]
|
||
def emit_calc_types_std_tabular(i):
|
||
X(f'{i}<StandardTabularSections>')
|
||
for name, syn in CALC_TYPES_STD_TABULAR:
|
||
X(f'{i}\t<xr:StandardTabularSection name="{name}">')
|
||
X(f'{i}\t\t<xr:Synonym>')
|
||
X(f'{i}\t\t\t<v8:item>')
|
||
X(f'{i}\t\t\t\t<v8:lang/>')
|
||
X(f'{i}\t\t\t\t<v8:content>{esc_xml_text(syn)}</v8:content>')
|
||
X(f'{i}\t\t\t</v8:item>')
|
||
X(f'{i}\t\t</xr:Synonym>')
|
||
X(f'{i}\t\t<xr:Comment/>')
|
||
X(f'{i}\t\t<xr:ToolTip/>')
|
||
X(f'{i}\t\t<xr:FillChecking>DontCheck</xr:FillChecking>')
|
||
X(f'{i}\t\t<xr:StandardAttributes>')
|
||
for st_attr in ('Predefined', 'CalculationType', 'LineNumber'):
|
||
st_ov = {'FillChecking': 'ShowError'} if st_attr == 'CalculationType' else None
|
||
emit_standard_attribute(f'{i}\t\t\t', st_attr, st_ov)
|
||
X(f'{i}\t\t</xr:StandardAttributes>')
|
||
X(f'{i}\t</xr:StandardTabularSection>')
|
||
X(f'{i}</StandardTabularSections>')
|
||
|
||
def emit_chart_of_calculation_types_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
X(f'{i}<UseStandardCommands>{"true" if get_bool_prop("useStandardCommands", True) else "false"}</UseStandardCommands>')
|
||
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '5'
|
||
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '100'
|
||
X(f'{i}<CodeLength>{code_length}</CodeLength>')
|
||
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
|
||
X(f'{i}<CodeType>{get_enum_prop("CodeType", "codeType", "String")}</CodeType>')
|
||
X(f'{i}<CodeAllowedLength>{get_enum_prop("CodeAllowedLength", "codeAllowedLength", "Variable")}</CodeAllowedLength>')
|
||
X(f'{i}<DefaultPresentation>{get_enum_prop("DefaultPresentation", "defaultPresentation", "AsDescription")}</DefaultPresentation>')
|
||
X(f'{i}<EditType>{get_enum_prop("EditType", "editType", "InDialog")}</EditType>')
|
||
X(f'{i}<QuickChoice>{"true" if defn.get("quickChoice") is True else "false"}</QuickChoice>')
|
||
X(f'{i}<ChoiceMode>{get_enum_prop("ChoiceMode", "choiceMode", "BothWays")}</ChoiceMode>')
|
||
if 'inputByString' in defn:
|
||
ib_fields = [expand_data_path(str(x)) for x in (defn.get('inputByString') or [])]
|
||
else:
|
||
ib_fields = []
|
||
if int(description_length) > 0:
|
||
ib_fields.append(f'ChartOfCalculationTypes.{obj_name}.StandardAttribute.Description')
|
||
if int(code_length) > 0:
|
||
ib_fields.append(f'ChartOfCalculationTypes.{obj_name}.StandardAttribute.Code')
|
||
emit_field_block(i, 'InputByString', ib_fields)
|
||
X(f'{i}<SearchStringModeOnInputByString>{get_enum_prop("SearchStringModeOnInputByString", "searchStringModeOnInputByString", "Begin")}</SearchStringModeOnInputByString>')
|
||
X(f'{i}<FullTextSearchOnInputByString>{get_enum_prop("FullTextSearchOnInputByString", "fullTextSearchOnInputByString", "DontUse")}</FullTextSearchOnInputByString>')
|
||
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
|
||
X(f'{i}<CreateOnInput>{get_enum_prop("CreateOnInput", "createOnInput", "DontUse")}</CreateOnInput>')
|
||
X(f'{i}<ChoiceHistoryOnInput>{get_enum_prop("ChoiceHistoryOnInput", "choiceHistoryOnInput", "Auto")}</ChoiceHistoryOnInput>')
|
||
emit_form_ref(i, 'DefaultObjectForm', defn.get('defaultObjectForm'))
|
||
emit_form_ref(i, 'DefaultListForm', defn.get('defaultListForm'))
|
||
emit_form_ref(i, 'DefaultChoiceForm', defn.get('defaultChoiceForm'))
|
||
emit_form_ref(i, 'AuxiliaryObjectForm', defn.get('auxiliaryObjectForm'))
|
||
emit_form_ref(i, 'AuxiliaryListForm', defn.get('auxiliaryListForm'))
|
||
emit_form_ref(i, 'AuxiliaryChoiceForm', defn.get('auxiliaryChoiceForm'))
|
||
emit_based_on(i, defn.get('basedOn'))
|
||
X(f'{i}<DependenceOnCalculationTypes>{get_enum_prop("DependenceOnCalculationTypes", "dependenceOnCalculationTypes", "DontUse")}</DependenceOnCalculationTypes>')
|
||
base_types = [resolve_type_prefix_syn(str(x)) for x in defn.get('baseCalculationTypes', [])]
|
||
if base_types:
|
||
X(f'{i}<BaseCalculationTypes>')
|
||
for bt in base_types:
|
||
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(bt)}</xr:Item>')
|
||
X(f'{i}</BaseCalculationTypes>')
|
||
else:
|
||
X(f'{i}<BaseCalculationTypes/>')
|
||
X(f'{i}<ActionPeriodUse>{"true" if defn.get("actionPeriodUse") is True else "false"}</ActionPeriodUse>')
|
||
emit_standard_attributes(i, 'ChartOfCalculationTypes')
|
||
emit_characteristics(i, defn.get('characteristics'))
|
||
emit_calc_types_std_tabular(i)
|
||
X(f'{i}<PredefinedDataUpdate>{get_enum_prop("PredefinedDataUpdate", "predefinedDataUpdate", "Auto")}</PredefinedDataUpdate>')
|
||
X(f'{i}<IncludeHelpInContents>{"true" if get_bool_prop("includeHelpInContents", False) else "false"}</IncludeHelpInContents>')
|
||
dl_fields = [expand_data_path(str(x)) for x in defn.get('dataLockFields', [])] if 'dataLockFields' in defn else []
|
||
emit_field_block(i, 'DataLockFields', dl_fields)
|
||
X(f'{i}<DataLockControlMode>{get_enum_prop("DataLockControlMode", "dataLockControlMode", "Managed")}</DataLockControlMode>')
|
||
X(f'{i}<FullTextSearch>{get_enum_prop("FullTextSearch", "fullTextSearch", "Use")}</FullTextSearch>')
|
||
emit_mltext(i, 'ObjectPresentation', defn.get('objectPresentation'))
|
||
emit_mltext(i, 'ExtendedObjectPresentation', defn.get('extendedObjectPresentation'))
|
||
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
|
||
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
X(f'{i}<DataHistory>{get_enum_prop("DataHistory", "dataHistory", "DontUse")}</DataHistory>')
|
||
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>{"true" if get_bool_prop("updateDataHistoryImmediatelyAfterWrite", False) else "false"}</UpdateDataHistoryImmediatelyAfterWrite>')
|
||
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>{"true" if get_bool_prop("executeAfterWriteDataHistoryVersionProcessing", False) else "false"}</ExecuteAfterWriteDataHistoryVersionProcessing>')
|
||
|
||
def emit_calculation_register_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
X(f'{i}<UseStandardCommands>{"true" if get_bool_prop("useStandardCommands", True) else "false"}</UseStandardCommands>')
|
||
emit_form_ref(i, 'DefaultListForm', defn.get('defaultListForm'))
|
||
emit_form_ref(i, 'AuxiliaryListForm', defn.get('auxiliaryListForm'))
|
||
periodicity = get_enum_prop('InformationRegisterPeriodicity', 'periodicity', 'Month')
|
||
X(f'{i}<Periodicity>{periodicity}</Periodicity>')
|
||
action_period = 'true' if defn.get('actionPeriod') is True else 'false'
|
||
X(f'{i}<ActionPeriod>{action_period}</ActionPeriod>')
|
||
base_period = 'true' if defn.get('basePeriod') is True else 'false'
|
||
X(f'{i}<BasePeriod>{base_period}</BasePeriod>')
|
||
schedule = str(defn['schedule']) if defn.get('schedule') else ''
|
||
if schedule:
|
||
X(f'{i}<Schedule>{esc_xml(schedule)}</Schedule>')
|
||
else:
|
||
X(f'{i}<Schedule/>')
|
||
schedule_value = str(defn['scheduleValue']) if defn.get('scheduleValue') else ''
|
||
if schedule_value:
|
||
X(f'{i}<ScheduleValue>{esc_xml(schedule_value)}</ScheduleValue>')
|
||
else:
|
||
X(f'{i}<ScheduleValue/>')
|
||
schedule_date = str(defn['scheduleDate']) if defn.get('scheduleDate') else ''
|
||
if schedule_date:
|
||
X(f'{i}<ScheduleDate>{esc_xml(schedule_date)}</ScheduleDate>')
|
||
else:
|
||
X(f'{i}<ScheduleDate/>')
|
||
chart_of_calc_types = str(defn['chartOfCalculationTypes']) if defn.get('chartOfCalculationTypes') else ''
|
||
if chart_of_calc_types:
|
||
X(f'{i}<ChartOfCalculationTypes>{esc_xml(chart_of_calc_types)}</ChartOfCalculationTypes>')
|
||
else:
|
||
X(f'{i}<ChartOfCalculationTypes/>')
|
||
X(f'{i}<IncludeHelpInContents>{"true" if get_bool_prop("includeHelpInContents", False) else "false"}</IncludeHelpInContents>')
|
||
emit_standard_attributes(i, 'CalculationRegister')
|
||
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Managed')
|
||
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
|
||
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
|
||
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
|
||
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
|
||
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
|
||
def emit_business_process_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
X(f'{i}<UseStandardCommands>{"true" if get_bool_prop("useStandardCommands", True) else "false"}</UseStandardCommands>')
|
||
X(f'{i}<EditType>{get_enum_prop("EditType", "editType", "InDialog")}</EditType>')
|
||
if 'inputByString' in defn:
|
||
ib_fields = [expand_data_path(str(x)) for x in (defn.get('inputByString') or [])]
|
||
else:
|
||
ib_fields = [f'BusinessProcess.{obj_name}.StandardAttribute.Number']
|
||
emit_field_block(i, 'InputByString', ib_fields)
|
||
X(f'{i}<CreateOnInput>{get_enum_prop("CreateOnInput", "createOnInput", "DontUse")}</CreateOnInput>')
|
||
X(f'{i}<SearchStringModeOnInputByString>{get_enum_prop("SearchStringModeOnInputByString", "searchStringModeOnInputByString", "Begin")}</SearchStringModeOnInputByString>')
|
||
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
|
||
X(f'{i}<FullTextSearchOnInputByString>{get_enum_prop("FullTextSearchOnInputByString", "fullTextSearchOnInputByString", "DontUse")}</FullTextSearchOnInputByString>')
|
||
emit_form_ref(i, 'DefaultObjectForm', defn.get('defaultObjectForm'))
|
||
emit_form_ref(i, 'DefaultListForm', defn.get('defaultListForm'))
|
||
emit_form_ref(i, 'DefaultChoiceForm', defn.get('defaultChoiceForm'))
|
||
emit_form_ref(i, 'AuxiliaryObjectForm', defn.get('auxiliaryObjectForm'))
|
||
emit_form_ref(i, 'AuxiliaryListForm', defn.get('auxiliaryListForm'))
|
||
emit_form_ref(i, 'AuxiliaryChoiceForm', defn.get('auxiliaryChoiceForm'))
|
||
X(f'{i}<ChoiceHistoryOnInput>{get_enum_prop("ChoiceHistoryOnInput", "choiceHistoryOnInput", "Auto")}</ChoiceHistoryOnInput>')
|
||
number_type = get_enum_prop('NumberType', 'numberType', 'String')
|
||
number_length = str(defn['numberLength']) if defn.get('numberLength') is not None else '11'
|
||
number_allowed_length = get_enum_prop('NumberAllowedLength', 'numberAllowedLength', 'Variable')
|
||
check_unique = 'false' if defn.get('checkUnique') is False else 'true'
|
||
X(f'{i}<NumberType>{number_type}</NumberType>')
|
||
X(f'{i}<NumberLength>{number_length}</NumberLength>')
|
||
X(f'{i}<NumberAllowedLength>{number_allowed_length}</NumberAllowedLength>')
|
||
X(f'{i}<CheckUnique>{check_unique}</CheckUnique>')
|
||
emit_standard_attributes(i, 'BusinessProcess')
|
||
emit_characteristics(i, defn.get('characteristics'))
|
||
X(f'{i}<Autonumbering>{"false" if defn.get("autonumbering") is False else "true"}</Autonumbering>')
|
||
emit_based_on(i, defn.get('basedOn'))
|
||
X(f'{i}<NumberPeriodicity>{get_enum_prop("NumberPeriodicity", "numberPeriodicity", "Nonperiodical")}</NumberPeriodicity>')
|
||
task_ref = str(defn['task']) if defn.get('task') else ''
|
||
if task_ref:
|
||
X(f'{i}<Task>{esc_xml(task_ref)}</Task>')
|
||
else:
|
||
X(f'{i}<Task/>')
|
||
X(f'{i}<CreateTaskInPrivilegedMode>{"true" if get_bool_prop("createTaskInPrivilegedMode", True) else "false"}</CreateTaskInPrivilegedMode>')
|
||
dl_fields = [expand_data_path(str(x)) for x in defn.get('dataLockFields', [])] if 'dataLockFields' in defn else []
|
||
emit_field_block(i, 'DataLockFields', dl_fields)
|
||
X(f'{i}<DataLockControlMode>{get_enum_prop("DataLockControlMode", "dataLockControlMode", "Managed")}</DataLockControlMode>')
|
||
X(f'{i}<IncludeHelpInContents>{"true" if get_bool_prop("includeHelpInContents", False) else "false"}</IncludeHelpInContents>')
|
||
X(f'{i}<FullTextSearch>{get_enum_prop("FullTextSearch", "fullTextSearch", "Use")}</FullTextSearch>')
|
||
emit_mltext(i, 'ObjectPresentation', defn.get('objectPresentation'))
|
||
emit_mltext(i, 'ExtendedObjectPresentation', defn.get('extendedObjectPresentation'))
|
||
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
|
||
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
X(f'{i}<DataHistory>{get_enum_prop("DataHistory", "dataHistory", "DontUse")}</DataHistory>')
|
||
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>{"true" if get_bool_prop("updateDataHistoryImmediatelyAfterWrite", False) else "false"}</UpdateDataHistoryImmediatelyAfterWrite>')
|
||
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>{"true" if get_bool_prop("executeAfterWriteDataHistoryVersionProcessing", False) else "false"}</ExecuteAfterWriteDataHistoryVersionProcessing>')
|
||
|
||
def emit_task_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
if defn.get('comment'):
|
||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
|
||
else:
|
||
X(f'{i}<Comment/>')
|
||
X(f'{i}<UseStandardCommands>{"true" if get_bool_prop("useStandardCommands", True) else "false"}</UseStandardCommands>')
|
||
number_type = get_enum_prop('NumberType', 'numberType', 'String')
|
||
number_length = str(defn['numberLength']) if defn.get('numberLength') is not None else '14'
|
||
number_allowed_length = get_enum_prop('NumberAllowedLength', 'numberAllowedLength', 'Variable')
|
||
check_unique = 'false' if defn.get('checkUnique') is False else 'true'
|
||
autonumbering = 'false' if defn.get('autonumbering') is False else 'true'
|
||
task_number_auto_prefix = str(defn['taskNumberAutoPrefix']) if defn.get('taskNumberAutoPrefix') else 'BusinessProcessNumber'
|
||
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '150'
|
||
X(f'{i}<NumberType>{number_type}</NumberType>')
|
||
X(f'{i}<NumberLength>{number_length}</NumberLength>')
|
||
X(f'{i}<NumberAllowedLength>{number_allowed_length}</NumberAllowedLength>')
|
||
X(f'{i}<CheckUnique>{check_unique}</CheckUnique>')
|
||
X(f'{i}<Autonumbering>{autonumbering}</Autonumbering>')
|
||
X(f'{i}<TaskNumberAutoPrefix>{task_number_auto_prefix}</TaskNumberAutoPrefix>')
|
||
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
|
||
X(f'{i}<Addressing>{esc_xml(str(defn["addressing"]))}</Addressing>' if defn.get('addressing') else f'{i}<Addressing/>')
|
||
X(f'{i}<MainAddressingAttribute>{esc_xml(str(defn["mainAddressingAttribute"]))}</MainAddressingAttribute>' if defn.get('mainAddressingAttribute') else f'{i}<MainAddressingAttribute/>')
|
||
X(f'{i}<CurrentPerformer>{esc_xml(str(defn["currentPerformer"]))}</CurrentPerformer>' if defn.get('currentPerformer') else f'{i}<CurrentPerformer/>')
|
||
emit_based_on(i, defn.get('basedOn'))
|
||
emit_standard_attributes(i, 'Task')
|
||
emit_characteristics(i, defn.get('characteristics'))
|
||
X(f'{i}<DefaultPresentation>{get_enum_prop("DefaultPresentation", "defaultPresentation", "AsDescription")}</DefaultPresentation>')
|
||
X(f'{i}<EditType>{get_enum_prop("EditType", "editType", "InDialog")}</EditType>')
|
||
if 'inputByString' in defn:
|
||
ib_fields = [expand_data_path(str(x)) for x in (defn.get('inputByString') or [])]
|
||
else:
|
||
ib_fields = [f'Task.{obj_name}.StandardAttribute.Number']
|
||
emit_field_block(i, 'InputByString', ib_fields)
|
||
X(f'{i}<SearchStringModeOnInputByString>{get_enum_prop("SearchStringModeOnInputByString", "searchStringModeOnInputByString", "Begin")}</SearchStringModeOnInputByString>')
|
||
X(f'{i}<FullTextSearchOnInputByString>{get_enum_prop("FullTextSearchOnInputByString", "fullTextSearchOnInputByString", "DontUse")}</FullTextSearchOnInputByString>')
|
||
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
|
||
X(f'{i}<CreateOnInput>{get_enum_prop("CreateOnInput", "createOnInput", "DontUse")}</CreateOnInput>')
|
||
emit_form_ref(i, 'DefaultObjectForm', defn.get('defaultObjectForm'))
|
||
emit_form_ref(i, 'DefaultListForm', defn.get('defaultListForm'))
|
||
emit_form_ref(i, 'DefaultChoiceForm', defn.get('defaultChoiceForm'))
|
||
emit_form_ref(i, 'AuxiliaryObjectForm', defn.get('auxiliaryObjectForm'))
|
||
emit_form_ref(i, 'AuxiliaryListForm', defn.get('auxiliaryListForm'))
|
||
emit_form_ref(i, 'AuxiliaryChoiceForm', defn.get('auxiliaryChoiceForm'))
|
||
X(f'{i}<ChoiceHistoryOnInput>{get_enum_prop("ChoiceHistoryOnInput", "choiceHistoryOnInput", "Auto")}</ChoiceHistoryOnInput>')
|
||
X(f'{i}<IncludeHelpInContents>{"true" if get_bool_prop("includeHelpInContents", False) else "false"}</IncludeHelpInContents>')
|
||
dl_fields = [expand_data_path(str(x)) for x in defn.get('dataLockFields', [])] if 'dataLockFields' in defn else []
|
||
emit_field_block(i, 'DataLockFields', dl_fields)
|
||
X(f'{i}<DataLockControlMode>{get_enum_prop("DataLockControlMode", "dataLockControlMode", "Managed")}</DataLockControlMode>')
|
||
X(f'{i}<FullTextSearch>{get_enum_prop("FullTextSearch", "fullTextSearch", "Use")}</FullTextSearch>')
|
||
emit_mltext(i, 'ObjectPresentation', defn.get('objectPresentation'))
|
||
emit_mltext(i, 'ExtendedObjectPresentation', defn.get('extendedObjectPresentation'))
|
||
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
|
||
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
|
||
emit_mltext(i, 'Explanation', defn.get('explanation'))
|
||
X(f'{i}<DataHistory>{get_enum_prop("DataHistory", "dataHistory", "DontUse")}</DataHistory>')
|
||
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>{"true" if get_bool_prop("updateDataHistoryImmediatelyAfterWrite", False) else "false"}</UpdateDataHistoryImmediatelyAfterWrite>')
|
||
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>{"true" if get_bool_prop("executeAfterWriteDataHistoryVersionProcessing", False) else "false"}</ExecuteAfterWriteDataHistoryVersionProcessing>')
|
||
|
||
def emit_http_service_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
X(f'{i}<Comment/>')
|
||
root_url = str(defn['rootURL']) if defn.get('rootURL') else obj_name.lower()
|
||
X(f'{i}<RootURL>{esc_xml(root_url)}</RootURL>')
|
||
reuse_sessions = get_enum_prop('ReuseSessions', 'reuseSessions', 'DontUse')
|
||
X(f'{i}<ReuseSessions>{reuse_sessions}</ReuseSessions>')
|
||
session_max_age = str(defn['sessionMaxAge']) if defn.get('sessionMaxAge') is not None else '20'
|
||
X(f'{i}<SessionMaxAge>{session_max_age}</SessionMaxAge>')
|
||
|
||
def emit_web_service_properties(indent):
|
||
i = indent
|
||
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
|
||
emit_mltext(i, 'Synonym', synonym)
|
||
X(f'{i}<Comment/>')
|
||
namespace = str(defn['namespace']) if defn.get('namespace') else ''
|
||
X(f'{i}<Namespace>{esc_xml(namespace)}</Namespace>')
|
||
xdto_packages = str(defn['xdtoPackages']) if defn.get('xdtoPackages') else ''
|
||
if xdto_packages:
|
||
X(f'{i}<XDTOPackages>{xdto_packages}</XDTOPackages>')
|
||
else:
|
||
X(f'{i}<XDTOPackages/>')
|
||
reuse_sessions = get_enum_prop('ReuseSessions', 'reuseSessions', 'DontUse')
|
||
X(f'{i}<ReuseSessions>{reuse_sessions}</ReuseSessions>')
|
||
session_max_age = str(defn['sessionMaxAge']) if defn.get('sessionMaxAge') is not None else '20'
|
||
X(f'{i}<SessionMaxAge>{session_max_age}</SessionMaxAge>')
|
||
|
||
|
||
# --- 13g. ChildObjects emitters for new types ---
|
||
|
||
def emit_column(indent, col_def):
|
||
uid = new_uuid()
|
||
name = ''
|
||
col_synonym = None
|
||
comment = ''
|
||
indexing = 'DontIndex'
|
||
references = []
|
||
if isinstance(col_def, str):
|
||
name = col_def
|
||
col_synonym = split_camel_case(name)
|
||
else:
|
||
name = str(col_def.get('name', ''))
|
||
col_synonym = col_def['synonym'] if col_def.get('synonym') is not None else split_camel_case(name) # строка ИЛИ {ru,en}
|
||
if col_def.get('comment'):
|
||
comment = str(col_def['comment'])
|
||
if col_def.get('indexing'):
|
||
indexing = str(col_def['indexing'])
|
||
if col_def.get('references'):
|
||
references = list(col_def['references'])
|
||
X(f'{indent}<Column uuid="{uid}">')
|
||
X(f'{indent}\t<Properties>')
|
||
X(f'{indent}\t\t<Name>{esc_xml(name)}</Name>')
|
||
emit_mltext(f'{indent}\t\t', 'Synonym', col_synonym)
|
||
if comment:
|
||
X(f'{indent}\t\t<Comment>{esc_xml_text(comment)}</Comment>')
|
||
else:
|
||
X(f'{indent}\t\t<Comment/>')
|
||
X(f'{indent}\t\t<Indexing>{indexing}</Indexing>')
|
||
if references:
|
||
X(f'{indent}\t\t<References>')
|
||
for ref in references:
|
||
X(f'{indent}\t\t\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(normalize_md_object_ref(str(ref)))}</xr:Item>')
|
||
X(f'{indent}\t\t</References>')
|
||
else:
|
||
X(f'{indent}\t\t<References/>')
|
||
X(f'{indent}\t</Properties>')
|
||
X(f'{indent}</Column>')
|
||
|
||
def emit_url_template(indent, tmpl_name, tmpl_def):
|
||
uid = new_uuid()
|
||
tmpl_synonym = split_camel_case(tmpl_name)
|
||
template = ''
|
||
methods = {}
|
||
if isinstance(tmpl_def, str):
|
||
template = tmpl_def
|
||
else:
|
||
template = str(tmpl_def['template']) if tmpl_def.get('template') else f'/{tmpl_name.lower()}'
|
||
if tmpl_def.get('methods'):
|
||
for k, v in tmpl_def['methods'].items():
|
||
methods[k] = str(v)
|
||
X(f'{indent}<URLTemplate uuid="{uid}">')
|
||
X(f'{indent}\t<Properties>')
|
||
X(f'{indent}\t\t<Name>{esc_xml(tmpl_name)}</Name>')
|
||
emit_mltext(f'{indent}\t\t', 'Synonym', tmpl_synonym)
|
||
X(f'{indent}\t\t<Template>{esc_xml(template)}</Template>')
|
||
X(f'{indent}\t</Properties>')
|
||
if methods:
|
||
X(f'{indent}\t<ChildObjects>')
|
||
for method_name, http_method in sorted(methods.items()):
|
||
method_uuid = new_uuid()
|
||
method_synonym = split_camel_case(method_name)
|
||
handler = f'{tmpl_name}{method_name}'
|
||
X(f'{indent}\t\t<Method uuid="{method_uuid}">')
|
||
X(f'{indent}\t\t\t<Properties>')
|
||
X(f'{indent}\t\t\t\t<Name>{esc_xml(method_name)}</Name>')
|
||
emit_mltext(f'{indent}\t\t\t\t', 'Synonym', method_synonym)
|
||
X(f'{indent}\t\t\t\t<HTTPMethod>{http_method}</HTTPMethod>')
|
||
X(f'{indent}\t\t\t\t<Handler>{esc_xml(handler)}</Handler>')
|
||
X(f'{indent}\t\t\t</Properties>')
|
||
X(f'{indent}\t\t</Method>')
|
||
X(f'{indent}\t</ChildObjects>')
|
||
else:
|
||
X(f'{indent}\t<ChildObjects/>')
|
||
X(f'{indent}</URLTemplate>')
|
||
|
||
def emit_operation(indent, op_name, op_def):
|
||
uid = new_uuid()
|
||
op_synonym = split_camel_case(op_name)
|
||
return_type = 'xs:string'
|
||
nillable = 'false'
|
||
transactioned = 'false'
|
||
handler = op_name
|
||
params = {}
|
||
if isinstance(op_def, str):
|
||
return_type = op_def
|
||
else:
|
||
if op_def.get('returnType'):
|
||
return_type = str(op_def['returnType'])
|
||
if op_def.get('nillable') is True:
|
||
nillable = 'true'
|
||
if op_def.get('transactioned') is True:
|
||
transactioned = 'true'
|
||
if op_def.get('handler'):
|
||
handler = str(op_def['handler'])
|
||
if op_def.get('parameters'):
|
||
for k, v in op_def['parameters'].items():
|
||
params[k] = v
|
||
X(f'{indent}<Operation uuid="{uid}">')
|
||
X(f'{indent}\t<Properties>')
|
||
X(f'{indent}\t\t<Name>{esc_xml(op_name)}</Name>')
|
||
emit_mltext(f'{indent}\t\t', 'Synonym', op_synonym)
|
||
X(f'{indent}\t\t<Comment/>')
|
||
X(f'{indent}\t\t<XDTOReturningValueType>{return_type}</XDTOReturningValueType>')
|
||
X(f'{indent}\t\t<Nillable>{nillable}</Nillable>')
|
||
X(f'{indent}\t\t<Transactioned>{transactioned}</Transactioned>')
|
||
X(f'{indent}\t\t<ProcedureName>{esc_xml(handler)}</ProcedureName>')
|
||
X(f'{indent}\t</Properties>')
|
||
if params:
|
||
X(f'{indent}\t<ChildObjects>')
|
||
for param_name, param_def in sorted(params.items()):
|
||
param_uuid = new_uuid()
|
||
param_synonym = split_camel_case(param_name)
|
||
param_type = 'xs:string'
|
||
param_nillable = 'true'
|
||
param_dir = 'In'
|
||
if isinstance(param_def, str):
|
||
param_type = param_def
|
||
else:
|
||
if param_def.get('type'):
|
||
param_type = str(param_def['type'])
|
||
if param_def.get('nillable') is False:
|
||
param_nillable = 'false'
|
||
if param_def.get('direction'):
|
||
param_dir = str(param_def['direction'])
|
||
X(f'{indent}\t\t<Parameter uuid="{param_uuid}">')
|
||
X(f'{indent}\t\t\t<Properties>')
|
||
X(f'{indent}\t\t\t\t<Name>{esc_xml(param_name)}</Name>')
|
||
emit_mltext(f'{indent}\t\t\t\t', 'Synonym', param_synonym)
|
||
X(f'{indent}\t\t\t\t<XDTOValueType>{param_type}</XDTOValueType>')
|
||
X(f'{indent}\t\t\t\t<Nillable>{param_nillable}</Nillable>')
|
||
X(f'{indent}\t\t\t\t<TransferDirection>{param_dir}</TransferDirection>')
|
||
X(f'{indent}\t\t\t</Properties>')
|
||
X(f'{indent}\t\t</Parameter>')
|
||
X(f'{indent}\t</ChildObjects>')
|
||
else:
|
||
X(f'{indent}\t<ChildObjects/>')
|
||
X(f'{indent}</Operation>')
|
||
|
||
def emit_addressing_attribute(indent, addr_def):
|
||
# Реквизит адресации = полный object-слой реквизита (контекст task-addressing) + AddressingDimension.
|
||
parsed = parse_attribute_shorthand(addr_def)
|
||
emit_attribute(indent, parsed, 'task-addressing', 'AddressingAttribute')
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 14. Namespaces
|
||
# ---------------------------------------------------------------------------
|
||
|
||
xmlns_decl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 14a. Detect format version from existing Configuration.xml
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def detect_format_version(d):
|
||
while d:
|
||
cfg_path = os.path.join(d, "Configuration.xml")
|
||
if os.path.isfile(cfg_path):
|
||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||
head = f.read(2000)
|
||
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', head)
|
||
if m:
|
||
return m.group(1)
|
||
parent = os.path.dirname(d)
|
||
if parent == d:
|
||
break
|
||
d = parent
|
||
return "2.17"
|
||
|
||
format_version = detect_format_version(output_dir)
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 15. Main assembler
|
||
# ---------------------------------------------------------------------------
|
||
|
||
obj_uuid = new_uuid()
|
||
|
||
X('<?xml version="1.0" encoding="UTF-8"?>')
|
||
X(f'<MetaDataObject {xmlns_decl} version="{format_version}">')
|
||
X(f'\t<{obj_type} uuid="{obj_uuid}">')
|
||
|
||
# InternalInfo
|
||
emit_internal_info('\t\t', obj_type, obj_name)
|
||
|
||
# Properties
|
||
X('\t\t<Properties>')
|
||
|
||
property_emitters = {
|
||
'Catalog': emit_catalog_properties,
|
||
'Document': emit_document_properties,
|
||
'Enum': emit_enum_properties,
|
||
'Constant': emit_constant_properties,
|
||
'InformationRegister': emit_information_register_properties,
|
||
'AccumulationRegister': emit_accumulation_register_properties,
|
||
'DefinedType': emit_defined_type_properties,
|
||
'FunctionalOption': emit_functional_option_properties,
|
||
'Sequence': emit_sequence_properties,
|
||
'FilterCriterion': emit_filter_criterion_properties,
|
||
'DocumentNumerator': emit_document_numerator_properties,
|
||
'SettingsStorage': emit_settings_storage_properties,
|
||
'CommonForm': emit_common_form_properties,
|
||
'SessionParameter': emit_session_parameter_properties,
|
||
'CommonCommand': emit_common_command_properties,
|
||
'CommandGroup': emit_command_group_properties,
|
||
'CommonAttribute': emit_common_attribute_properties,
|
||
'FunctionalOptionsParameter': emit_functional_options_parameter_properties,
|
||
'WSReference': emit_ws_reference_properties,
|
||
'CommonPicture': emit_common_picture_properties,
|
||
'CommonTemplate': emit_common_template_properties,
|
||
'CommonModule': emit_common_module_properties,
|
||
'ScheduledJob': emit_scheduled_job_properties,
|
||
'EventSubscription': emit_event_subscription_properties,
|
||
'Report': emit_report_properties,
|
||
'DataProcessor': emit_data_processor_properties,
|
||
'ExchangePlan': emit_exchange_plan_properties,
|
||
'ChartOfCharacteristicTypes': emit_chart_of_characteristic_types_properties,
|
||
'DocumentJournal': emit_document_journal_properties,
|
||
'ChartOfAccounts': emit_chart_of_accounts_properties,
|
||
'AccountingRegister': emit_accounting_register_properties,
|
||
'ChartOfCalculationTypes': emit_chart_of_calculation_types_properties,
|
||
'CalculationRegister': emit_calculation_register_properties,
|
||
'BusinessProcess': emit_business_process_properties,
|
||
'Task': emit_task_properties,
|
||
'HTTPService': emit_http_service_properties,
|
||
'WebService': emit_web_service_properties,
|
||
}
|
||
|
||
property_emitters[obj_type]('\t\t\t')
|
||
|
||
X('\t\t</Properties>')
|
||
|
||
# ChildObjects
|
||
has_children = False
|
||
|
||
# --- Types with Attributes + TabularSections ---
|
||
types_with_attr_ts = [
|
||
'Catalog', 'Document', 'Report', 'DataProcessor', 'ExchangePlan',
|
||
'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes',
|
||
'BusinessProcess', 'Task',
|
||
]
|
||
|
||
commands = [] # заполняется внутри блока types_with_attr_ts; на уровне модуля для записи модулей команд
|
||
if obj_type in types_with_attr_ts:
|
||
def _as_list(val):
|
||
"""Normalize attributes: dict {"K":"V"} → ["K:V"], list/other → list."""
|
||
if val is None:
|
||
return []
|
||
if isinstance(val, dict):
|
||
return [f"{k}:{v}" for k, v in val.items()]
|
||
return list(val)
|
||
|
||
attrs = []
|
||
if defn.get('attributes'):
|
||
for a in _as_list(defn['attributes']):
|
||
attrs.append(parse_attribute_shorthand(a))
|
||
ts_sections = {}
|
||
ts_order = []
|
||
if defn.get('tabularSections'):
|
||
ts_data = defn['tabularSections']
|
||
# Значение ТЧ: массив колонок (синоним авто) ЛИБО объект {attributes/columns, synonym, tooltip, comment}.
|
||
def new_ts_entry(val):
|
||
if isinstance(val, list):
|
||
return {'columns': val, 'synonym': None, 'tooltip': None, 'comment': None, 'lineNumber': None, 'fillChecking': None, 'use': None}
|
||
cols = _as_list(val.get('attributes') or val.get('columns') or [])
|
||
return {'columns': cols, 'synonym': val.get('synonym'), 'tooltip': val.get('tooltip'),
|
||
'comment': str(val['comment']) if val.get('comment') else None, 'lineNumber': val.get('lineNumber'), 'fillChecking': val.get('fillChecking'), 'use': val.get('use')}
|
||
if isinstance(ts_data, list):
|
||
for ts in ts_data:
|
||
ts_sections[ts['name']] = new_ts_entry(ts)
|
||
ts_order.append(ts['name'])
|
||
else:
|
||
for k, v in ts_data.items():
|
||
ts_sections[k] = new_ts_entry(v)
|
||
ts_order.append(k)
|
||
# ChartOfAccounts: AccountingFlags + ExtDimensionAccountingFlags (признаки учёта — структурно как реквизит,
|
||
# но без Indexing/FullTextSearch/Use; тип по умолчанию Boolean). Парсим как реквизиты.
|
||
acct_flags = []
|
||
ext_dim_flags = []
|
||
if obj_type == 'ChartOfAccounts':
|
||
if defn.get('accountingFlags'):
|
||
acct_flags = [parse_attribute_shorthand(af) for af in _as_list(defn['accountingFlags'])]
|
||
if defn.get('extDimensionAccountingFlags'):
|
||
ext_dim_flags = [parse_attribute_shorthand(edf) for edf in _as_list(defn['extDimensionAccountingFlags'])]
|
||
# Task: AddressingAttributes
|
||
addr_attrs = []
|
||
if obj_type == 'Task' and defn.get('addressingAttributes'):
|
||
addr_attrs = _as_list(defn['addressingAttributes'])
|
||
# Commands (map имя→объект ИЛИ array [{name,...}])
|
||
commands = []
|
||
if defn.get('commands'):
|
||
cd = defn['commands']
|
||
if isinstance(cd, list):
|
||
for c in cd:
|
||
commands.append({'name': str(c.get('name', '')), 'def': c})
|
||
else:
|
||
for k, v in cd.items():
|
||
commands.append({'name': k, 'def': v})
|
||
child_count = len(attrs) + len(ts_sections) + len(acct_flags) + len(ext_dim_flags) + len(addr_attrs) + len(commands)
|
||
if child_count > 0:
|
||
has_children = True
|
||
X('\t\t<ChildObjects>')
|
||
if obj_type == 'Catalog':
|
||
context = 'catalog'
|
||
elif obj_type == 'Document':
|
||
context = 'document'
|
||
elif obj_type in ('DataProcessor', 'Report'):
|
||
context = 'processor'
|
||
elif obj_type == 'ChartOfCharacteristicTypes':
|
||
context = 'catalog' # реквизиты ПВХ структурно как у справочника (Use/FillFromFillingValue/DataHistory)
|
||
elif obj_type in ('ChartOfAccounts', 'ChartOfCalculationTypes'):
|
||
context = 'account' # как catalog, но БЕЗ <Use> (реквизиты ПС/ПВР не иерархичны как справочник)
|
||
else:
|
||
context = 'object'
|
||
for a in attrs:
|
||
emit_attribute('\t\t\t', a, context)
|
||
for ts_name in ts_order:
|
||
e = ts_sections[ts_name]
|
||
emit_tabular_section('\t\t\t', ts_name, e['columns'], obj_type, obj_name, e['synonym'], e['tooltip'], e['comment'], e.get('lineNumber'), e.get('fillChecking'), e.get('use'))
|
||
for af in acct_flags:
|
||
emit_attribute('\t\t\t', af, 'account-flag', 'AccountingFlag')
|
||
for edf in ext_dim_flags:
|
||
emit_attribute('\t\t\t', edf, 'account-flag', 'ExtDimensionAccountingFlag')
|
||
for aa in addr_attrs:
|
||
emit_addressing_attribute('\t\t\t', aa)
|
||
for cmd in commands:
|
||
emit_command('\t\t\t', cmd['name'], cmd['def'])
|
||
X('\t\t</ChildObjects>')
|
||
else:
|
||
X('\t\t<ChildObjects/>')
|
||
|
||
# --- Enum: enum values ---
|
||
if obj_type == 'Enum':
|
||
values = []
|
||
if defn.get('values'):
|
||
for v in defn['values']:
|
||
values.append(parse_enum_value_shorthand(v))
|
||
if values:
|
||
has_children = True
|
||
X('\t\t<ChildObjects>')
|
||
for v in values:
|
||
emit_enum_value('\t\t\t', v)
|
||
X('\t\t</ChildObjects>')
|
||
else:
|
||
X('\t\t<ChildObjects/>')
|
||
|
||
# --- Constant, DefinedType, ScheduledJob, EventSubscription: no ChildObjects ---
|
||
|
||
# --- Registers: dimensions + resources + attributes ---
|
||
if obj_type in ('InformationRegister', 'AccumulationRegister', 'AccountingRegister', 'CalculationRegister'):
|
||
dims = []
|
||
resources = []
|
||
reg_attrs = []
|
||
if defn.get('dimensions'):
|
||
for d in defn['dimensions']:
|
||
dims.append(parse_attribute_shorthand(d))
|
||
if defn.get('resources'):
|
||
for r in defn['resources']:
|
||
resources.append(parse_attribute_shorthand(r))
|
||
if defn.get('attributes'):
|
||
for a in defn['attributes']:
|
||
reg_attrs.append(parse_attribute_shorthand(a))
|
||
reg_commands = []
|
||
if defn.get('commands'):
|
||
cd = defn['commands']
|
||
if isinstance(cd, list):
|
||
for c in cd:
|
||
reg_commands.append({'name': str(c.get('name', '')), 'def': c})
|
||
else:
|
||
for k, v in cd.items():
|
||
reg_commands.append({'name': k, 'def': v})
|
||
if dims or resources or reg_attrs or reg_commands:
|
||
has_children = True
|
||
X('\t\t<ChildObjects>')
|
||
# InformationRegister.Attribute несёт FillFromFillingValue/FillValue/DataHistory; CalculationRegister.Attribute
|
||
# несёт <ScheduleLink> (register-calc); прочие — register-other.
|
||
reg_ctx = {'InformationRegister': 'register-info', 'CalculationRegister': 'register-calc'}.get(obj_type, 'register-other')
|
||
# Все семейства регистров: ресурсы/измерения — через богатый emit_attribute (общий слой object-свойств).
|
||
dim_res_ctx = {'InformationRegister': 'register-info', 'AccumulationRegister': 'register-accum',
|
||
'CalculationRegister': 'register-calc', 'AccountingRegister': 'register-account'}.get(obj_type)
|
||
for r in resources:
|
||
if dim_res_ctx:
|
||
emit_attribute('\t\t\t', r, dim_res_ctx, 'Resource')
|
||
else:
|
||
emit_resource('\t\t\t', r, obj_type)
|
||
for d in dims:
|
||
if dim_res_ctx:
|
||
emit_attribute('\t\t\t', d, dim_res_ctx, 'Dimension')
|
||
else:
|
||
emit_dimension('\t\t\t', d, obj_type)
|
||
for a in reg_attrs:
|
||
emit_attribute('\t\t\t', a, reg_ctx)
|
||
for cmd in reg_commands:
|
||
emit_command('\t\t\t', cmd['name'], cmd['def'])
|
||
X('\t\t</ChildObjects>')
|
||
else:
|
||
X('\t\t<ChildObjects/>')
|
||
|
||
# --- DocumentJournal: columns + commands ---
|
||
if obj_type == 'DocumentJournal':
|
||
columns = list(defn.get('columns', []))
|
||
dj_commands = []
|
||
if defn.get('commands'):
|
||
cd = defn['commands']
|
||
if isinstance(cd, list):
|
||
for c in cd:
|
||
dj_commands.append({'name': str(c.get('name', '')), 'def': c})
|
||
else:
|
||
for k, v in cd.items():
|
||
dj_commands.append({'name': k, 'def': v})
|
||
if columns or dj_commands:
|
||
has_children = True
|
||
X('\t\t<ChildObjects>')
|
||
for col in columns:
|
||
emit_column('\t\t\t', col)
|
||
for cmd in dj_commands:
|
||
emit_command('\t\t\t', cmd['name'], cmd['def'])
|
||
X('\t\t</ChildObjects>')
|
||
else:
|
||
X('\t\t<ChildObjects/>')
|
||
|
||
# --- Sequence: dimensions ---
|
||
if obj_type == 'Sequence':
|
||
seq_dims = list(defn.get('dimensions', []))
|
||
if seq_dims:
|
||
has_children = True
|
||
X('\t\t<ChildObjects>')
|
||
for d in seq_dims:
|
||
emit_sequence_dimension('\t\t\t', d)
|
||
X('\t\t</ChildObjects>')
|
||
else:
|
||
X('\t\t<ChildObjects/>')
|
||
|
||
# --- FilterCriterion / SettingsStorage: ChildObjects (формы стрипаются; FilterCriterion может нести <Command>) ---
|
||
if obj_type in ('FilterCriterion', 'SettingsStorage'):
|
||
fc_commands = []
|
||
if defn.get('commands'):
|
||
cd = defn['commands']
|
||
if isinstance(cd, list):
|
||
for c in cd:
|
||
fc_commands.append({'name': str(c.get('name', '')), 'def': c})
|
||
else:
|
||
for k, v in cd.items():
|
||
fc_commands.append({'name': k, 'def': v})
|
||
if fc_commands:
|
||
has_children = True
|
||
X('\t\t<ChildObjects>')
|
||
for cmd in fc_commands:
|
||
emit_command('\t\t\t', cmd['name'], cmd['def'])
|
||
X('\t\t</ChildObjects>')
|
||
else:
|
||
X('\t\t<ChildObjects/>')
|
||
# DocumentNumerator: ChildObjects нет вовсе (не эмитим).
|
||
|
||
# --- HTTPService: URLTemplates ---
|
||
if obj_type == 'HTTPService':
|
||
url_templates = {}
|
||
url_tmpl_order = []
|
||
if defn.get('urlTemplates'):
|
||
for k, v in defn['urlTemplates'].items():
|
||
url_templates[k] = v
|
||
url_tmpl_order.append(k)
|
||
if url_templates:
|
||
has_children = True
|
||
X('\t\t<ChildObjects>')
|
||
for tmpl_name in sorted(url_tmpl_order):
|
||
emit_url_template('\t\t\t', tmpl_name, url_templates[tmpl_name])
|
||
X('\t\t</ChildObjects>')
|
||
else:
|
||
X('\t\t<ChildObjects/>')
|
||
|
||
# --- WebService: Operations ---
|
||
if obj_type == 'WebService':
|
||
operations = {}
|
||
op_order = []
|
||
if defn.get('operations'):
|
||
for k, v in defn['operations'].items():
|
||
operations[k] = v
|
||
op_order.append(k)
|
||
if operations:
|
||
has_children = True
|
||
X('\t\t<ChildObjects>')
|
||
for op_name in sorted(op_order):
|
||
emit_operation('\t\t\t', op_name, operations[op_name])
|
||
X('\t\t</ChildObjects>')
|
||
else:
|
||
X('\t\t<ChildObjects/>')
|
||
|
||
# --- CommonModule: no ChildObjects ---
|
||
|
||
X(f'\t</{obj_type}>')
|
||
X('</MetaDataObject>')
|
||
|
||
metadata_xml = '\n'.join(lines) + '\n'
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 16. Write files
|
||
# ---------------------------------------------------------------------------
|
||
|
||
type_plural_map = {
|
||
'Catalog': 'Catalogs',
|
||
'Document': 'Documents',
|
||
'Enum': 'Enums',
|
||
'Constant': 'Constants',
|
||
'InformationRegister': 'InformationRegisters',
|
||
'AccumulationRegister': 'AccumulationRegisters',
|
||
'AccountingRegister': 'AccountingRegisters',
|
||
'CalculationRegister': 'CalculationRegisters',
|
||
'ChartOfAccounts': 'ChartsOfAccounts',
|
||
'ChartOfCharacteristicTypes': 'ChartsOfCharacteristicTypes',
|
||
'ChartOfCalculationTypes': 'ChartsOfCalculationTypes',
|
||
'BusinessProcess': 'BusinessProcesses',
|
||
'Task': 'Tasks',
|
||
'ExchangePlan': 'ExchangePlans',
|
||
'DocumentJournal': 'DocumentJournals',
|
||
'Report': 'Reports',
|
||
'DataProcessor': 'DataProcessors',
|
||
'CommonModule': 'CommonModules',
|
||
'ScheduledJob': 'ScheduledJobs',
|
||
'EventSubscription': 'EventSubscriptions',
|
||
'HTTPService': 'HTTPServices',
|
||
'WebService': 'WebServices',
|
||
'DefinedType': 'DefinedTypes',
|
||
'FunctionalOption': 'FunctionalOptions',
|
||
'Sequence': 'Sequences',
|
||
'FilterCriterion': 'FilterCriteria',
|
||
'DocumentNumerator': 'DocumentNumerators',
|
||
'SettingsStorage': 'SettingsStorages',
|
||
'CommonForm': 'CommonForms',
|
||
'SessionParameter': 'SessionParameters',
|
||
'CommonCommand': 'CommonCommands',
|
||
'CommandGroup': 'CommandGroups',
|
||
'CommonAttribute': 'CommonAttributes',
|
||
'FunctionalOptionsParameter': 'FunctionalOptionsParameters',
|
||
'WSReference': 'WSReferences',
|
||
'CommonPicture': 'CommonPictures',
|
||
'CommonTemplate': 'CommonTemplates',
|
||
}
|
||
|
||
type_plural = type_plural_map[obj_type]
|
||
type_dir = os.path.join(output_dir, type_plural)
|
||
|
||
# Main XML file
|
||
main_xml_path = os.path.join(type_dir, f'{obj_name}.xml')
|
||
|
||
# Types that don't have subdirectory structure
|
||
types_no_sub_dir = ['DefinedType', 'ScheduledJob', 'EventSubscription']
|
||
|
||
obj_sub_dir = os.path.join(type_dir, obj_name)
|
||
ext_dir = os.path.join(obj_sub_dir, 'Ext')
|
||
|
||
os.makedirs(type_dir, exist_ok=True)
|
||
if obj_type not in types_no_sub_dir:
|
||
os.makedirs(obj_sub_dir, exist_ok=True)
|
||
|
||
write_utf8_bom(main_xml_path, metadata_xml)
|
||
|
||
# Module files
|
||
modules_created = []
|
||
|
||
types_with_object_module = [
|
||
'Catalog', 'Document', 'Report', 'DataProcessor', 'ExchangePlan',
|
||
'ChartOfAccounts', 'ChartOfCharacteristicTypes', 'ChartOfCalculationTypes',
|
||
'BusinessProcess', 'Task',
|
||
]
|
||
types_with_record_set_module = [
|
||
'InformationRegister', 'AccumulationRegister', 'AccountingRegister', 'CalculationRegister',
|
||
]
|
||
types_with_manager_module = ['Report', 'DataProcessor', 'Constant', 'Enum']
|
||
types_with_value_manager_module = ['Constant']
|
||
types_with_module = ['CommonModule', 'HTTPService', 'WebService']
|
||
|
||
def ensure_ext_dir():
|
||
os.makedirs(ext_dir, exist_ok=True)
|
||
|
||
if obj_type in types_with_object_module:
|
||
module_path = os.path.join(ext_dir, 'ObjectModule.bsl')
|
||
if not os.path.isfile(module_path):
|
||
ensure_ext_dir()
|
||
write_utf8_bom(module_path, '')
|
||
modules_created.append(module_path)
|
||
|
||
if obj_type in types_with_manager_module:
|
||
module_path = os.path.join(ext_dir, 'ManagerModule.bsl')
|
||
if not os.path.isfile(module_path):
|
||
ensure_ext_dir()
|
||
write_utf8_bom(module_path, '')
|
||
modules_created.append(module_path)
|
||
|
||
if obj_type in types_with_value_manager_module:
|
||
module_path = os.path.join(ext_dir, 'ValueManagerModule.bsl')
|
||
if not os.path.isfile(module_path):
|
||
ensure_ext_dir()
|
||
write_utf8_bom(module_path, '')
|
||
modules_created.append(module_path)
|
||
|
||
if obj_type in types_with_record_set_module:
|
||
module_path = os.path.join(ext_dir, 'RecordSetModule.bsl')
|
||
if not os.path.isfile(module_path):
|
||
ensure_ext_dir()
|
||
write_utf8_bom(module_path, '')
|
||
modules_created.append(module_path)
|
||
|
||
if obj_type in types_with_module:
|
||
module_path = os.path.join(ext_dir, 'Module.bsl')
|
||
if not os.path.isfile(module_path):
|
||
ensure_ext_dir()
|
||
write_utf8_bom(module_path, '')
|
||
modules_created.append(module_path)
|
||
|
||
# CommonCommand — заготовка модуля команды (CommandModule.bsl).
|
||
if obj_type == 'CommonCommand':
|
||
module_path = os.path.join(ext_dir, 'CommandModule.bsl')
|
||
if not os.path.isfile(module_path):
|
||
ensure_ext_dir()
|
||
write_utf8_bom(module_path, '')
|
||
modules_created.append(module_path)
|
||
|
||
# CommonForm — заготовка структуры формы под компиляцию (form-compile наполняет содержимое).
|
||
if obj_type == 'CommonForm':
|
||
ensure_ext_dir()
|
||
cf_form_xml_path = os.path.join(ext_dir, 'Form.xml')
|
||
if not os.path.isfile(cf_form_xml_path):
|
||
cf_ns = ('xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" '
|
||
'xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" '
|
||
'xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" '
|
||
'xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" '
|
||
'xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" '
|
||
'xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" '
|
||
'xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" '
|
||
'xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"')
|
||
cf_form_xml = ('<?xml version="1.0" encoding="UTF-8"?>\n<Form ' + cf_ns + ' version="' + format_version + '">\n'
|
||
'\t<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">\n\t\t<Autofill>true</Autofill>\n\t</AutoCommandBar>\n'
|
||
'\t<ChildItems/>\n</Form>\n')
|
||
write_utf8_bom(cf_form_xml_path, cf_form_xml)
|
||
modules_created.append(cf_form_xml_path)
|
||
cf_module_dir = os.path.join(ext_dir, 'Form')
|
||
os.makedirs(cf_module_dir, exist_ok=True)
|
||
cf_module_path = os.path.join(cf_module_dir, 'Module.bsl')
|
||
if not os.path.isfile(cf_module_path):
|
||
write_utf8_bom(cf_module_path, '')
|
||
modules_created.append(cf_module_path)
|
||
|
||
# --- Predefined data (Ext/Predefined.xml). Элемент: "(Код) Имя [Наименование]" ИЛИ объект (+рус. синонимы).
|
||
# Наименование: нет [..]/ключа → авто(Split-CamelCase); [] / "" → пусто; [текст]/текст → как есть.
|
||
def resolve_predef_item(val):
|
||
if isinstance(val, str):
|
||
# Грамматика "(Код) Имя [Наименование]: Тип": сначала вынуть [Наим] (может содержать ':'), затем тип по ':'.
|
||
s = val
|
||
ptype = None
|
||
desc_raw = None
|
||
has_desc = False
|
||
md = re.search(r'\[(.*)\]', s)
|
||
if md:
|
||
desc_raw = md.group(1)
|
||
has_desc = True
|
||
s = re.sub(r'\s*\[.*\]', '', s)
|
||
if ':' in s:
|
||
left, right = s.split(':', 1)
|
||
s = left
|
||
ptype = right.strip() # '' → пустой <Type/>
|
||
m = re.match(r'^\s*(?:\(([^)]*)\)\s*)?(\S+)\s*$', s.strip())
|
||
name = m.group(2)
|
||
code = m.group(1) if m.group(1) is not None else ''
|
||
desc = desc_raw if has_desc else split_camel_case(name)
|
||
return {'name': name, 'code': code, 'desc': desc, 'isFolder': False, 'children': [], 'type': ptype}
|
||
def gv(keys):
|
||
for k in keys:
|
||
if k in val:
|
||
return val[k]
|
||
return None
|
||
name = str(gv(['name', 'имя']) or '')
|
||
code_v = gv(['code', 'код'])
|
||
code = str(code_v) if code_v is not None else ''
|
||
has_desc = ('description' in val) or ('наименование' in val)
|
||
desc_v = gv(['description', 'наименование'])
|
||
desc = ('' if desc_v is None else str(desc_v)) if has_desc else split_camel_case(name)
|
||
is_folder = gv(['isFolder', 'группа']) is True
|
||
subs = gv(['childItems', 'подчиненные']) or []
|
||
type_v = gv(['type', 'тип']) # тип значения характеристики (ПВХ): строка "A + B" ИЛИ массив
|
||
if isinstance(type_v, list):
|
||
type_v = ' + '.join(type_v)
|
||
return {'name': name, 'code': code, 'desc': desc, 'isFolder': is_folder, 'children': list(subs), 'type': type_v}
|
||
|
||
def emit_predef_item(out, val, indent, code_type):
|
||
r = resolve_predef_item(val)
|
||
out.append(f'{indent}<Item id="{new_uuid()}">')
|
||
out.append(f'{indent}\t<Name>{esc_xml_text(r["name"])}</Name>')
|
||
if not r['code']:
|
||
out.append(f'{indent}\t<Code/>')
|
||
elif code_type == 'Number':
|
||
out.append(f'{indent}\t<Code xsi:type="xs:decimal">{esc_xml_text(r["code"])}</Code>')
|
||
else:
|
||
out.append(f'{indent}\t<Code>{esc_xml_text(r["code"])}</Code>')
|
||
if r['desc'] == '':
|
||
out.append(f'{indent}\t<Description/>')
|
||
else:
|
||
out.append(f'{indent}\t<Description>{esc_xml_text(r["desc"])}</Description>')
|
||
# Type — тип значения предопределённой характеристики (ПВХ). None→нет блока; ''→<Type/>; 'A + B'→наполненный.
|
||
rt = r.get('type')
|
||
if rt is not None and str(rt) == '':
|
||
out.append(f'{indent}\t<Type/>')
|
||
elif rt:
|
||
out.append(f'{indent}\t<Type>')
|
||
global lines
|
||
saved = lines
|
||
lines = []
|
||
emit_type_content(indent + '\t\t', str(rt))
|
||
out.extend(lines)
|
||
lines = saved
|
||
out.append(f'{indent}\t</Type>')
|
||
out.append(f'{indent}\t<IsFolder>{"true" if r["isFolder"] else "false"}</IsFolder>')
|
||
if r['children']:
|
||
out.append(f'{indent}\t<ChildItems>')
|
||
for c in r['children']:
|
||
emit_predef_item(out, c, indent + '\t\t', code_type)
|
||
out.append(f'{indent}\t</ChildItems>')
|
||
out.append(f'{indent}</Item>')
|
||
|
||
def build_predefined_xml(items, xsi_type, code_type):
|
||
out = ['<?xml version="1.0" encoding="UTF-8"?>']
|
||
out.append(f'<PredefinedData xmlns="http://v8.1c.ru/8.3/xcf/predef" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="{xsi_type}" version="{format_version}">')
|
||
for it in items:
|
||
emit_predef_item(out, it, '\t', code_type)
|
||
out.append('</PredefinedData>')
|
||
return '\n'.join(out) + '\n'
|
||
|
||
# --- Предопределённые СЧЕТА Плана счетов (отдельная грамматика: AccountType/OffBalance/Order/AccountingFlags/
|
||
# ExtDimensionTypes/ChildItems). Флаги перечисляем по def-порядку признаков плана; в DSL — только TRUE. ---
|
||
def _predef_acc_get(o, keys):
|
||
for k in keys:
|
||
if isinstance(o, dict):
|
||
if k in o:
|
||
return o[k]
|
||
return None
|
||
|
||
def emit_predef_account_flags(out, indent, tag, ref_kind, obj_nm, flag_names, true_set):
|
||
if not flag_names:
|
||
out.append(f'{indent}<{tag}/>')
|
||
return
|
||
tset = set(str(t) for t in (true_set or []))
|
||
out.append(f'{indent}<{tag}>')
|
||
for fn in flag_names:
|
||
v = 'true' if fn in tset else 'false'
|
||
out.append(f'{indent}\t<Flag ref="ChartOfAccounts.{obj_nm}.{ref_kind}.{fn}">{v}</Flag>')
|
||
out.append(f'{indent}</{tag}>')
|
||
|
||
def emit_predef_account(out, val, indent, obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref=''):
|
||
name = str(_predef_acc_get(val, ('name', 'имя')) or '')
|
||
code_v = _predef_acc_get(val, ('code', 'код'))
|
||
code = str(code_v) if code_v is not None else ''
|
||
has_desc = isinstance(val, dict) and ('description' in val or 'наименование' in val)
|
||
desc_v = _predef_acc_get(val, ('description', 'наименование'))
|
||
desc = str(desc_v) if has_desc else split_camel_case(name)
|
||
acct_type = str(_predef_acc_get(val, ('accountType', 'видСчета', 'вид')) or '') or 'ActivePassive'
|
||
off = 'true' if _predef_acc_get(val, ('offBalance', 'забалансовый')) is True else 'false'
|
||
order = str(_predef_acc_get(val, ('order', 'порядок')) or '')
|
||
flags = _predef_acc_get(val, ('flags', 'признаки'))
|
||
subconto = _predef_acc_get(val, ('subconto', 'extDimensionTypes', 'видыСубконто'))
|
||
children = _predef_acc_get(val, ('childItems', 'подчиненные'))
|
||
|
||
out.append(f'{indent}<Item id="{new_uuid()}">')
|
||
out.append(f'{indent}\t<Name>{esc_xml_text(name)}</Name>')
|
||
out.append(f'{indent}\t<Code/>' if not code else f'{indent}\t<Code>{esc_xml_text(code)}</Code>')
|
||
out.append(f'{indent}\t<Description/>' if desc == '' else f'{indent}\t<Description>{esc_xml_text(desc)}</Description>')
|
||
out.append(f'{indent}\t<AccountType>{acct_type}</AccountType>')
|
||
out.append(f'{indent}\t<OffBalance>{off}</OffBalance>')
|
||
out.append(f'{indent}\t<Order>{esc_xml_text(order)}</Order>')
|
||
emit_predef_account_flags(out, f'{indent}\t', 'AccountingFlags', 'AccountingFlag', obj_nm, acct_flag_names, flags)
|
||
sub_arr = list(subconto) if subconto else []
|
||
if not sub_arr:
|
||
out.append(f'{indent}\t<ExtDimensionTypes/>')
|
||
else:
|
||
out.append(f'{indent}\t<ExtDimensionTypes>')
|
||
for sc in sub_arr:
|
||
# Строковая форма "Тип | Признак1, Признак2" (флаги после |, turnover=false). Объектная — {type, turnover?, flags?}.
|
||
if isinstance(sc, str):
|
||
sc_turn_v = None
|
||
if '|' in sc:
|
||
tpart, fpart = sc.split('|', 1)
|
||
sc_type = tpart.strip()
|
||
sc_flags = [x.strip() for x in fpart.split(',') if x.strip()]
|
||
else:
|
||
sc_type = sc.strip()
|
||
sc_flags = None
|
||
else:
|
||
sc_type = str(_predef_acc_get(sc, ('type', 'тип')) or '')
|
||
sc_turn_v = _predef_acc_get(sc, ('turnover', 'толькоОбороты', 'оборотный'))
|
||
sc_flags = _predef_acc_get(sc, ('flags', 'признаки'))
|
||
# Короткая запись: голое имя значения → префикс ПВХ видов субконто плана (extDimensionTypes); иначе резолв синонима.
|
||
if '.' not in sc_type:
|
||
if ext_dim_types_ref:
|
||
sc_type = f'{ext_dim_types_ref}.{sc_type}'
|
||
else:
|
||
sc_type = resolve_type_prefix_syn(sc_type)
|
||
# «Только обороты» — токен в списке flags (или отдельный ключ turnover); вынимаем из настоящих признаков.
|
||
sc_turn = 'true' if sc_turn_v is True else 'false'
|
||
real_flags = []
|
||
for f in (sc_flags or []):
|
||
if str(f).strip().lower() in SUBCONTO_TURNOVER_TOKENS:
|
||
sc_turn = 'true'
|
||
else:
|
||
real_flags.append(f)
|
||
sc_flags = real_flags
|
||
out.append(f'{indent}\t\t<ExtDimensionType name="{esc_xml(sc_type)}">')
|
||
out.append(f'{indent}\t\t\t<Turnover>{sc_turn}</Turnover>')
|
||
emit_predef_account_flags(out, f'{indent}\t\t\t', 'AccountingFlags', 'ExtDimensionAccountingFlag', obj_nm, ext_dim_flag_names, sc_flags)
|
||
out.append(f'{indent}\t\t</ExtDimensionType>')
|
||
out.append(f'{indent}\t</ExtDimensionTypes>')
|
||
child_arr = list(children) if children else []
|
||
if child_arr:
|
||
out.append(f'{indent}\t<ChildItems>')
|
||
for c in child_arr:
|
||
emit_predef_account(out, c, f'{indent}\t\t', obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref)
|
||
out.append(f'{indent}\t</ChildItems>')
|
||
out.append(f'{indent}</Item>')
|
||
|
||
def build_predefined_account_xml(items, obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref=''):
|
||
out = ['<?xml version="1.0" encoding="UTF-8"?>']
|
||
out.append(f'<PredefinedData xmlns="http://v8.1c.ru/8.3/xcf/predef" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ChartOfAccountsPredefinedItems" version="{format_version}">')
|
||
for it in items:
|
||
emit_predef_account(out, it, '\t', obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref)
|
||
out.append('</PredefinedData>')
|
||
return '\n'.join(out) + '\n'
|
||
|
||
# Предопределённые ВИДЫ РАСЧЁТА (плоские: Name/Code/Description/ActionPeriodIsBase).
|
||
def emit_predef_calc_type(out, val, indent):
|
||
r = resolve_predef_item(val)
|
||
apib = 'false'
|
||
if not isinstance(val, str):
|
||
if _predef_acc_get(val, ('actionPeriodIsBase', 'периодДействияБазовый')) is True:
|
||
apib = 'true'
|
||
out.append(f'{indent}<Item id="{new_uuid()}">')
|
||
out.append(f'{indent}\t<Name>{esc_xml_text(r["name"])}</Name>')
|
||
out.append(f'{indent}\t<Code/>' if not r['code'] else f'{indent}\t<Code>{esc_xml_text(r["code"])}</Code>')
|
||
out.append(f'{indent}\t<Description/>' if r['desc'] == '' else f'{indent}\t<Description>{esc_xml_text(r["desc"])}</Description>')
|
||
out.append(f'{indent}\t<ActionPeriodIsBase>{apib}</ActionPeriodIsBase>')
|
||
out.append(f'{indent}</Item>')
|
||
|
||
def build_predefined_calc_type_xml(items):
|
||
out = ['<?xml version="1.0" encoding="UTF-8"?>']
|
||
out.append(f'<PredefinedData xmlns="http://v8.1c.ru/8.3/xcf/predef" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="CalculationTypePredefinedItems" version="{format_version}">')
|
||
for it in items:
|
||
emit_predef_calc_type(out, it, '\t')
|
||
out.append('</PredefinedData>')
|
||
return '\n'.join(out) + '\n'
|
||
|
||
# Special files
|
||
# --- Состав плана обмена (ExchangePlan, Ext/Content.xml). Ключ `content`/`Состав`:
|
||
# [ "MDRef" (AutoRecord=Deny, дефолт) | "MDRef: autoRecord" (Allow) | {metadata, autoRecord} ]. ---
|
||
def parse_exchange_content_item(entry):
|
||
if isinstance(entry, str):
|
||
ref, ar = entry, 'Deny'
|
||
ci = entry.rfind(':')
|
||
if ci >= 0:
|
||
ref = entry[:ci].strip()
|
||
flag = entry[ci + 1:].strip()
|
||
if re.match(r'^(autoRecord|АвтоРегистрация|Allow|Разрешить)$', flag, re.I):
|
||
ar = 'Allow'
|
||
elif re.match(r'^(Deny|Запретить)$', flag, re.I):
|
||
ar = 'Deny'
|
||
return {'metadata': ref.strip(), 'autoRecord': ar}
|
||
ref = ''
|
||
for k in ('metadata', 'Метаданные', 'объект'):
|
||
if entry.get(k) is not None:
|
||
ref = str(entry[k]); break
|
||
raw_ar = entry.get('autoRecord')
|
||
if raw_ar is None:
|
||
raw_ar = entry.get('АвтоРегистрация')
|
||
ar = 'Deny'
|
||
if isinstance(raw_ar, bool):
|
||
if raw_ar:
|
||
ar = 'Allow'
|
||
elif raw_ar is not None and re.match(r'^(Allow|Разрешить|true|autoRecord|АвтоРегистрация)$', str(raw_ar), re.I):
|
||
ar = 'Allow'
|
||
return {'metadata': ref.strip(), 'autoRecord': ar}
|
||
|
||
if obj_type == 'ExchangePlan':
|
||
content_path = os.path.join(ext_dir, 'Content.xml')
|
||
xep_ns = 'xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||
c_src = defn.get('content')
|
||
if c_src is None:
|
||
c_src = defn.get('Состав')
|
||
c_items = []
|
||
if c_src:
|
||
for e in (c_src if isinstance(c_src, list) else [c_src]):
|
||
it = parse_exchange_content_item(e)
|
||
if it['metadata']:
|
||
c_items.append(it)
|
||
if c_items:
|
||
ensure_ext_dir()
|
||
parts = ['<?xml version="1.0" encoding="UTF-8"?>\r\n',
|
||
f'<ExchangePlanContent {xep_ns} version="{format_version}">\r\n']
|
||
for it in c_items:
|
||
parts.append('\t<Item>\r\n')
|
||
parts.append(f'\t\t<Metadata>{esc_xml(it["metadata"])}</Metadata>\r\n')
|
||
parts.append(f'\t\t<AutoRecord>{it["autoRecord"]}</AutoRecord>\r\n')
|
||
parts.append('\t</Item>\r\n')
|
||
parts.append('</ExchangePlanContent>\r\n')
|
||
write_utf8_bom(content_path, ''.join(parts))
|
||
modules_created.append(content_path)
|
||
elif not os.path.isfile(content_path):
|
||
ensure_ext_dir()
|
||
content_xml = f'<?xml version="1.0" encoding="UTF-8"?>\r\n<ExchangePlanContent {xep_ns} version="{format_version}"/>\r\n'
|
||
write_utf8_bom(content_path, content_xml)
|
||
modules_created.append(content_path)
|
||
|
||
if obj_type == 'BusinessProcess':
|
||
flowchart_path = os.path.join(ext_dir, 'Flowchart.xml')
|
||
if not os.path.isfile(flowchart_path):
|
||
ensure_ext_dir()
|
||
flowchart_xml = f'<?xml version="1.0" encoding="UTF-8"?>\r\n<Flowchart xmlns="http://v8.1c.ru/8.3/MDClasses" version="{format_version}"/>\r\n'
|
||
write_utf8_bom(flowchart_path, flowchart_xml)
|
||
modules_created.append(flowchart_path)
|
||
|
||
# Предопределённые элементы (Ext/Predefined.xml). Root-элемент по типу.
|
||
predef_root_by_type = {'Catalog': 'CatalogPredefinedItems', 'ChartOfCharacteristicTypes': 'PlanOfCharacteristicKindPredefinedItems'}
|
||
if obj_type == 'ChartOfAccounts' and defn.get('predefined'):
|
||
# Предопределённые СЧЕТА — отдельная грамматика (флаги разворачиваются по def-порядку признаков плана).
|
||
ensure_ext_dir()
|
||
af_names = [parse_attribute_shorthand(af)['name'] for af in _as_list(defn['accountingFlags'])] if defn.get('accountingFlags') else []
|
||
edf_names = [parse_attribute_shorthand(edf)['name'] for edf in _as_list(defn['extDimensionAccountingFlags'])] if defn.get('extDimensionAccountingFlags') else []
|
||
edt_ref = resolve_type_prefix_syn(str(defn['extDimensionTypes'])) if defn.get('extDimensionTypes') else ''
|
||
predef_xml = build_predefined_account_xml(defn['predefined'], obj_name, af_names, edf_names, edt_ref)
|
||
predef_path = os.path.join(ext_dir, 'Predefined.xml')
|
||
write_utf8_bom(predef_path, predef_xml)
|
||
modules_created.append(predef_path)
|
||
elif obj_type == 'ChartOfCalculationTypes' and defn.get('predefined'):
|
||
ensure_ext_dir()
|
||
predef_xml = build_predefined_calc_type_xml(defn['predefined'])
|
||
predef_path = os.path.join(ext_dir, 'Predefined.xml')
|
||
write_utf8_bom(predef_path, predef_xml)
|
||
modules_created.append(predef_path)
|
||
elif obj_type in predef_root_by_type and defn.get('predefined'):
|
||
ensure_ext_dir()
|
||
cat_code_type = str(defn['codeType']) if defn.get('codeType') else 'String'
|
||
predef_xml = build_predefined_xml(defn['predefined'], predef_root_by_type[obj_type], cat_code_type)
|
||
predef_path = os.path.join(ext_dir, 'Predefined.xml')
|
||
write_utf8_bom(predef_path, predef_xml)
|
||
modules_created.append(predef_path)
|
||
|
||
# Модули команд (Commands/<Имя>/Ext/CommandModule.bsl) — заготовка обработчика.
|
||
if commands:
|
||
cmd_module_stub = '&НаКлиенте\r\nПроцедура ОбработкаКоманды(ПараметрКоманды, ПараметрыВыполненияКоманды)\r\n\r\n\t// Вставьте обработчик команды.\r\n\r\nКонецПроцедуры\r\n'
|
||
for cmd in commands:
|
||
cmd_dir = os.path.join(obj_sub_dir, 'Commands', cmd['name'], 'Ext')
|
||
os.makedirs(cmd_dir, exist_ok=True)
|
||
cmd_mod_path = os.path.join(cmd_dir, 'CommandModule.bsl')
|
||
write_utf8_bom(cmd_mod_path, cmd_module_stub)
|
||
modules_created.append(cmd_mod_path)
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 17. Register in Configuration.xml
|
||
# ---------------------------------------------------------------------------
|
||
|
||
config_xml_path = os.path.join(output_dir, 'Configuration.xml')
|
||
reg_result = None
|
||
|
||
child_tag = obj_type
|
||
|
||
if os.path.isfile(config_xml_path):
|
||
# Read raw content, preserving BOM/EOL byte-for-byte (newline='' => no translation).
|
||
with open(config_xml_path, 'r', encoding='utf-8-sig', newline='') as f:
|
||
config_content = f.read()
|
||
|
||
ns = 'http://v8.1c.ru/8.3/MDClasses'
|
||
# ET is used ONLY read-only here: to locate ChildObjects and detect a duplicate.
|
||
# We deliberately do NOT re-serialize Configuration.xml with ElementTree.write():
|
||
# it drops every xmlns declaration used only inside attribute VALUES (e.g.
|
||
# xsi:type="app:ApplicationUsePurpose" in UsePurposes) because ET never sees those
|
||
# prefixes in element/attribute names. The dropped declaration makes XDTO read the
|
||
# value as anyType and Designer refuses to load the file (issue #38). Registration is
|
||
# therefore done by raw-text insertion, preserving BOM, EOL and all namespaces
|
||
# byte-for-byte (same approach as subsystem-compile).
|
||
tree = ET.parse(config_xml_path)
|
||
root = tree.getroot()
|
||
|
||
child_objects = root.find(f'{{{ns}}}Configuration/{{{ns}}}ChildObjects')
|
||
if child_objects is None:
|
||
# Try direct path
|
||
config_elem = root.find(f'{{{ns}}}Configuration')
|
||
if config_elem is not None:
|
||
child_objects = config_elem.find(f'{{{ns}}}ChildObjects')
|
||
|
||
if child_objects is None:
|
||
reg_result = 'no-childobj'
|
||
else:
|
||
existing = child_objects.findall(f'{{{ns}}}{child_tag}')
|
||
already_exists = any((e.text or '').strip() == obj_name for e in existing)
|
||
|
||
if already_exists:
|
||
reg_result = 'already'
|
||
else:
|
||
eol = '\r\n' if '\r\n' in config_content else '\n'
|
||
entry = f'<{child_tag}>{esc_xml(obj_name)}</{child_tag}>'
|
||
|
||
block = re.search(r'<ChildObjects\s*>.*?</ChildObjects>', config_content, re.S)
|
||
if block is None:
|
||
# Empty self-closing <ChildObjects/> => open it with the first entry.
|
||
empty = re.search(r'<ChildObjects\s*/>', config_content)
|
||
if empty is None:
|
||
reg_result = 'no-childobj'
|
||
else:
|
||
replacement = f'<ChildObjects>{eol}\t\t\t{entry}{eol}\t\t</ChildObjects>'
|
||
new_content = config_content[:empty.start()] + replacement + config_content[empty.end():]
|
||
write_utf8_bom(config_xml_path, new_content)
|
||
reg_result = 'added'
|
||
else:
|
||
close_same = f'</{child_tag}>'
|
||
last_same = config_content.rfind(close_same, block.start(), block.end())
|
||
if last_same != -1:
|
||
# After the last element of the same type (keeps them grouped).
|
||
insert_at = last_same + len(close_same)
|
||
new_content = (config_content[:insert_at]
|
||
+ f'{eol}\t\t\t{entry}'
|
||
+ config_content[insert_at:])
|
||
else:
|
||
# No element of this type yet: new line before </ChildObjects>,
|
||
# reusing the block's existing closing indent for </ChildObjects>.
|
||
close_at = config_content.rfind('</ChildObjects>', block.start(), block.end())
|
||
new_content = (config_content[:close_at]
|
||
+ f'\t{entry}{eol}\t\t'
|
||
+ config_content[close_at:])
|
||
write_utf8_bom(config_xml_path, new_content)
|
||
reg_result = 'added'
|
||
else:
|
||
reg_result = 'no-config'
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 18. Summary
|
||
# ---------------------------------------------------------------------------
|
||
|
||
attr_count = len(defn.get('attributes', []))
|
||
ts_count = 0
|
||
if defn.get('tabularSections'):
|
||
ts_data = defn['tabularSections']
|
||
if isinstance(ts_data, list):
|
||
ts_count = len(ts_data)
|
||
else:
|
||
ts_count = len(ts_data)
|
||
dim_count = len(defn.get('dimensions', []))
|
||
res_count = len(defn.get('resources', []))
|
||
val_count = len(defn.get('values', []))
|
||
col_count = len(defn.get('columns', []))
|
||
|
||
print(f"[OK] {obj_type} '{obj_name}' compiled")
|
||
print(f' UUID: {obj_uuid}')
|
||
print(f' File: {main_xml_path}')
|
||
|
||
details = []
|
||
if attr_count > 0:
|
||
details.append(f'Attributes: {attr_count}')
|
||
if ts_count > 0:
|
||
details.append(f'TabularSections: {ts_count}')
|
||
if dim_count > 0:
|
||
details.append(f'Dimensions: {dim_count}')
|
||
if res_count > 0:
|
||
details.append(f'Resources: {res_count}')
|
||
if val_count > 0:
|
||
details.append(f'Values: {val_count}')
|
||
if col_count > 0:
|
||
details.append(f'Columns: {col_count}')
|
||
|
||
if details:
|
||
print(f" {', '.join(details)}")
|
||
|
||
for mc in modules_created:
|
||
print(f' Module: {mc}')
|
||
|
||
if reg_result == 'added':
|
||
print(f' Configuration.xml: <{child_tag}>{obj_name}</{child_tag}> added to ChildObjects')
|
||
elif reg_result == 'already':
|
||
print(f' Configuration.xml: <{child_tag}>{obj_name}</{child_tag}> already registered')
|
||
elif reg_result == 'no-childobj':
|
||
print('WARNING: Configuration.xml found but <ChildObjects> not found', file=sys.stderr)
|
||
elif reg_result == 'no-config':
|
||
print(f' Configuration.xml: not found at {config_xml_path} (register manually)')
|
||
|
||
# Cross-reference hints
|
||
if obj_type == 'AccountingRegister' and not defn.get('chartOfAccounts'):
|
||
print('[HINT] AccountingRegister requires ChartOfAccounts reference:')
|
||
print(' /meta-edit -Operation modify-property -Value "ChartOfAccounts=ChartOfAccounts.XXX"')
|
||
if obj_type == 'CalculationRegister' and not defn.get('chartOfCalculationTypes'):
|
||
print('[HINT] CalculationRegister requires ChartOfCalculationTypes reference:')
|
||
print(' /meta-edit -Operation modify-property -Value "ChartOfCalculationTypes=ChartOfCalculationTypes.XXX"')
|
||
if obj_type == 'BusinessProcess' and not defn.get('task'):
|
||
print('[HINT] BusinessProcess requires Task reference:')
|
||
print(' /meta-edit -Operation modify-property -Value "Task=Task.XXX"')
|
||
if obj_type == 'ChartOfAccounts':
|
||
max_ext_dim = int(defn['maxExtDimensionCount']) if defn.get('maxExtDimensionCount') is not None else 0
|
||
if max_ext_dim > 0 and not defn.get('extDimensionTypes'):
|
||
print('[HINT] ChartOfAccounts with MaxExtDimensionCount>0 requires ExtDimensionTypes:')
|
||
print(' /meta-edit -Operation modify-property -Value "ExtDimensionTypes=ChartOfCharacteristicTypes.XXX"')
|