Files
cc-1c-skills/.codeassistant/skills/meta-compile/scripts/meta-compile.py
T
2026-06-30 15:48:21 +00:00

2903 lines
132 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# meta-compile v1.14 — 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('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;')
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)
def emit_mltext(indent, tag, text):
if not text:
X(f'{indent}<{tag}/>')
return
X(f'{indent}<{tag}>')
X(f'{indent}\t<v8:item>')
X(f'{indent}\t\t<v8:lang>ru</v8:lang>')
X(f'{indent}\t\t<v8:content>{esc_xml(text)}</v8:content>')
X(f'{indent}\t</v8:item>')
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)
if len(result) > 1:
result = result[0] + result[1:].lower()
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',
}
# 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', 'HierarchyItemsOnly'],
'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'],
'ChoiceMode': ['BothWays', 'QuickChoice', 'FromForm'],
}
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)
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',
]
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
synonym = str(defn['synonym']) if defn.get('synonym') 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',
'number': 'Number',
'string': 'String',
'boolean': 'Boolean',
'date': 'Date',
'datetime': 'DateTime',
'bool': 'Boolean',
# 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',
}
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)
m = re.match(r'^String(\((\d+)\))?$', type_str)
if m:
length = m.group(2) if m.group(2) else '10'
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>Variable</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
if type_str == 'Date':
X(f'{indent}<v8:Type>xs:dateTime</v8:Type>')
X(f'{indent}<v8:DateQualifiers>')
X(f'{indent}\t<v8:DateFractions>Date</v8:DateFractions>')
X(f'{indent}</v8:DateQualifiers>')
return
if type_str == 'DateTime':
X(f'{indent}<v8:Type>xs:dateTime</v8:Type>')
X(f'{indent}<v8:DateQualifiers>')
X(f'{indent}\t<v8:DateFractions>DateTime</v8:DateFractions>')
X(f'{indent}</v8:DateQualifiers>')
return
# DefinedType
m = re.match(r'^DefinedType\.(.+)$', type_str)
if m:
dt_name = m.group(1)
X(f'{indent}<v8:TypeSet>cfg:DefinedType.{dt_name}</v8:TypeSet>')
return
# ValueStorage
if type_str == 'ValueStorage':
X(f'{indent}<v8:Type>xs:base64Binary</v8:Type>')
return
# Reference types — use local xmlns declaration for 1C compatibility
m = re.match(r'^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|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>')
def emit_fill_value(indent, type_str):
if not type_str:
X(f'{indent}<FillValue xsi:nil="true"/>')
return
type_str = resolve_type_str(type_str)
if type_str == 'Boolean':
X(f'{indent}<FillValue xsi:type="xs:boolean">false</FillValue>')
return
if re.match(r'^String', type_str):
X(f'{indent}<FillValue xsi:type="xs:string"/>')
return
if re.match(r'^Number', type_str):
X(f'{indent}<FillValue xsi:type="xs:decimal">0</FillValue>')
return
if re.match(r'^(Date|DateTime)$', type_str):
X(f'{indent}<FillValue xsi:nil="true"/>')
return
X(f'{indent}<FillValue xsi:nil="true"/>')
# ---------------------------------------------------------------------------
# 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': '',
'synonym': '',
'comment': '',
'flags': [],
'fillChecking': '',
'indexing': '',
}
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
name = str(val.get('name', ''))
return {
'name': name,
'type': build_type_str(val),
'synonym': str(val['synonym']) if val.get('synonym') else split_camel_case(name),
'comment': str(val['comment']) if val.get('comment') else '',
'flags': list(val.get('flags', [])),
'fillChecking': str(val['fillChecking']) if val.get('fillChecking') else '',
'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 '',
}
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,
'synonym': str(val['synonym']) if val.get('synonym') 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': 'ChartOfCharacteristicTypesCharacteristic', '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'},
],
}
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', 'Predefined', 'Ref', 'DeletionMark', 'Description', 'Code', 'Parent', 'Order', 'Type', 'OffBalance'],
'ChartOfCharacteristicTypes': ['PredefinedDataName', 'Predefined', 'Ref', 'DeletionMark', 'Description', 'Code', 'Parent', 'ValueType'],
'ChartOfCalculationTypes': ['PredefinedDataName', 'Predefined', 'Ref', 'DeletionMark', 'Description', 'Code', 'ActionPeriodIsBasic'],
'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'],
}
def emit_standard_attribute(indent, attr_name):
X(f'{indent}<xr:StandardAttribute name="{attr_name}">')
X(f'{indent}\t<xr:LinkByType/>')
X(f'{indent}\t<xr:FillChecking>DontCheck</xr:FillChecking>')
X(f'{indent}\t<xr:MultiLine>false</xr:MultiLine>')
X(f'{indent}\t<xr:FillFromFillingValue>false</xr:FillFromFillingValue>')
X(f'{indent}\t<xr:CreateOnInput>Auto</xr:CreateOnInput>')
X(f'{indent}\t<xr:MaxValue xsi:nil="true"/>')
X(f'{indent}\t<xr:ToolTip/>')
X(f'{indent}\t<xr:ExtendedEdit>false</xr:ExtendedEdit>')
X(f'{indent}\t<xr:Format/>')
X(f'{indent}\t<xr:ChoiceForm/>')
X(f'{indent}\t<xr:QuickChoice>Auto</xr:QuickChoice>')
X(f'{indent}\t<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>')
X(f'{indent}\t<xr:EditFormat/>')
X(f'{indent}\t<xr:PasswordMode>false</xr:PasswordMode>')
X(f'{indent}\t<xr:DataHistory>Use</xr:DataHistory>')
X(f'{indent}\t<xr:MarkNegatives>false</xr:MarkNegatives>')
X(f'{indent}\t<xr:MinValue xsi:nil="true"/>')
X(f'{indent}\t<xr:Synonym/>')
X(f'{indent}\t<xr:Comment/>')
X(f'{indent}\t<xr:FullTextSearch>Use</xr:FullTextSearch>')
X(f'{indent}\t<xr:ChoiceParameterLinks/>')
X(f'{indent}\t<xr:FillValue xsi:nil="true"/>')
X(f'{indent}\t<xr:Mask/>')
X(f'{indent}\t<xr:ChoiceParameters/>')
X(f'{indent}</xr:StandardAttribute>')
def emit_standard_attributes(indent, object_type):
attrs = standard_attributes_by_type.get(object_type)
if not attrs:
return
X(f'{indent}<StandardAttributes>')
for a in attrs:
emit_standard_attribute(f'{indent}\t', a)
X(f'{indent}</StandardAttributes>')
def emit_tabular_standard_attributes(indent):
X(f'{indent}<StandardAttributes>')
emit_standard_attribute(f'{indent}\t', 'LineNumber')
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',
}
def emit_attribute(indent, parsed, context):
attr_name = parsed['name']
if 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}<Attribute 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>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>')
X(f'{indent}\t\t<ExtendedEdit>false</ExtendedEdit>')
X(f'{indent}\t\t<MinValue xsi:nil="true"/>')
X(f'{indent}\t\t<MaxValue xsi:nil="true"/>')
# 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'):
X(f'{indent}\t\t<FillFromFillingValue>false</FillFromFillingValue>')
if context not in ('tabular', 'processor', 'chart', 'register-other'):
emit_fill_value(f'{indent}\t\t', type_str)
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>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/>')
X(f'{indent}\t\t<LinkByType/>')
chi = parsed.get('choiceHistoryOnInput') or 'Auto'
X(f'{indent}\t\t<ChoiceHistoryOnInput>{chi}</ChoiceHistoryOnInput>')
if context == 'catalog':
X(f'{indent}\t\t<Use>ForItem</Use>')
if context not in ('processor', 'processor-tabular'):
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>')
X(f'{indent}\t\t<FullTextSearch>Use</FullTextSearch>')
# DataHistory — not for Chart* types and non-InformationRegister register family
if context not in ('chart', 'register-other'):
X(f'{indent}\t\t<DataHistory>Use</DataHistory>')
X(f'{indent}\t</Properties>')
X(f'{indent}</Attribute>')
# ---------------------------------------------------------------------------
# 9. TabularSection emitter
# ---------------------------------------------------------------------------
def emit_tabular_section(indent, ts_name, columns, object_type, object_name):
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 = 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)
X(f'{indent}\t\t<Comment/>')
X(f'{indent}\t\t<ToolTip/>')
X(f'{indent}\t\t<FillChecking>DontCheck</FillChecking>')
emit_tabular_standard_attributes(f'{indent}\t\t')
if object_type == 'Catalog':
X(f'{indent}\t\t<Use>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'])
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>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>')
X(f'{indent}\t\t<ExtendedEdit>false</ExtendedEdit>')
X(f'{indent}\t\t<MinValue xsi:nil="true"/>')
X(f'{indent}\t\t<MaxValue xsi:nil="true"/>')
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>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/>')
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>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>')
X(f'{indent}\t\t<ExtendedEdit>false</ExtendedEdit>')
X(f'{indent}\t\t<MinValue xsi:nil="true"/>')
X(f'{indent}\t\t<MaxValue xsi:nil="true"/>')
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>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/>')
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)
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>')
X(f'{i}<UseStandardCommands>true</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')
X(f'{i}<Characteristics/>')
X(f'{i}<PredefinedDataUpdate>Auto</PredefinedDataUpdate>')
X(f'{i}<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>')
X(f'{i}<InputByString>')
X(f'{i}\t<xr:Field>Catalog.{obj_name}.StandardAttribute.Description</xr:Field>')
X(f'{i}\t<xr:Field>Catalog.{obj_name}.StandardAttribute.Code</xr:Field>')
X(f'{i}</InputByString>')
X(f'{i}<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>')
X(f'{i}<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>')
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
X(f'{i}<DefaultObjectForm/>')
X(f'{i}<DefaultFolderForm/>')
X(f'{i}<DefaultListForm/>')
X(f'{i}<DefaultChoiceForm/>')
X(f'{i}<DefaultFolderChoiceForm/>')
X(f'{i}<AuxiliaryObjectForm/>')
X(f'{i}<AuxiliaryFolderForm/>')
X(f'{i}<AuxiliaryListForm/>')
X(f'{i}<AuxiliaryChoiceForm/>')
X(f'{i}<AuxiliaryFolderChoiceForm/>')
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
X(f'{i}<BasedOn/>')
X(f'{i}<DataLockFields/>')
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
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>')
X(f'{i}<ObjectPresentation/>')
X(f'{i}<ExtendedObjectPresentation/>')
X(f'{i}<ListPresentation/>')
X(f'{i}<ExtendedListPresentation/>')
X(f'{i}<Explanation/>')
X(f'{i}<CreateOnInput>DontUse</CreateOnInput>')
X(f'{i}<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)
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
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('InformationRegisterPeriodicity', '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')
X(f'{i}<Characteristics/>')
X(f'{i}<BasedOn/>')
X(f'{i}<InputByString>')
X(f'{i}\t<xr:Field>Document.{obj_name}.StandardAttribute.Number</xr:Field>')
X(f'{i}</InputByString>')
X(f'{i}<CreateOnInput>DontUse</CreateOnInput>')
X(f'{i}<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>')
X(f'{i}<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>')
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
X(f'{i}<DefaultObjectForm/>')
X(f'{i}<DefaultListForm/>')
X(f'{i}<DefaultChoiceForm/>')
X(f'{i}<AuxiliaryObjectForm/>')
X(f'{i}<AuxiliaryListForm/>')
X(f'{i}<AuxiliaryChoiceForm/>')
posting = get_enum_prop('Posting', 'posting', 'Allow')
real_time_posting = get_enum_prop('RealTimePosting', 'realTimePosting', 'Deny')
reg_records_deletion = get_enum_prop('RegisterRecordsDeletion', 'registerRecordsDeletion', 'AutoDelete')
reg_records_writing = get_enum_prop('RegisterRecordsWritingOnPost', 'registerRecordsWritingOnPost', 'WriteModified')
sequence_filling = str(defn['sequenceFilling']) if defn.get('sequenceFilling') else 'AutoFill'
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}<Posting>{posting}</Posting>')
X(f'{i}<RealTimePosting>{real_time_posting}</RealTimePosting>')
X(f'{i}<RegisterRecordsDeletion>{reg_records_deletion}</RegisterRecordsDeletion>')
X(f'{i}<RegisterRecordsWritingOnPost>{reg_records_writing}</RegisterRecordsWritingOnPost>')
X(f'{i}<SequenceFilling>{sequence_filling}</SequenceFilling>')
# RegisterRecords
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/>')
X(f'{i}<PostInPrivilegedMode>{post_in_priv}</PostInPrivilegedMode>')
X(f'{i}<UnpostInPrivilegedMode>{unpost_in_priv}</UnpostInPrivilegedMode>')
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
X(f'{i}<DataLockFields/>')
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
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>')
X(f'{i}<ObjectPresentation/>')
X(f'{i}<ExtendedObjectPresentation/>')
X(f'{i}<ListPresentation/>')
X(f'{i}<ExtendedListPresentation/>')
X(f'{i}<Explanation/>')
X(f'{i}<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
X(f'{i}<DataHistory>DontUse</DataHistory>')
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>')
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>')
def emit_enum_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>false</UseStandardCommands>')
emit_standard_attributes(i, 'Enum')
X(f'{i}<Characteristics/>')
quick_choice = 'false' if defn.get('quickChoice') is False else 'true'
X(f'{i}<QuickChoice>{quick_choice}</QuickChoice>')
X(f'{i}<ChoiceMode>BothWays</ChoiceMode>')
X(f'{i}<DefaultListForm/>')
X(f'{i}<DefaultChoiceForm/>')
X(f'{i}<AuxiliaryListForm/>')
X(f'{i}<AuxiliaryChoiceForm/>')
X(f'{i}<ListPresentation/>')
X(f'{i}<ExtendedListPresentation/>')
X(f'{i}<Explanation/>')
X(f'{i}<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
def emit_constant_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>')
# Type
value_type = build_type_str(defn) or 'String'
emit_value_type(i, value_type)
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
X(f'{i}<DefaultForm/>')
X(f'{i}<ExtendedPresentation/>')
X(f'{i}<Explanation/>')
X(f'{i}<PasswordMode>false</PasswordMode>')
X(f'{i}<Format/>')
X(f'{i}<EditFormat/>')
X(f'{i}<ToolTip/>')
X(f'{i}<MarkNegatives>false</MarkNegatives>')
X(f'{i}<Mask/>')
X(f'{i}<MultiLine>false</MultiLine>')
X(f'{i}<ExtendedEdit>false</ExtendedEdit>')
X(f'{i}<MinValue xsi:nil="true"/>')
X(f'{i}<MaxValue xsi:nil="true"/>')
X(f'{i}<FillChecking>DontCheck</FillChecking>')
X(f'{i}<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>')
X(f'{i}<ChoiceParameterLinks/>')
X(f'{i}<ChoiceParameters/>')
X(f'{i}<QuickChoice>Auto</QuickChoice>')
X(f'{i}<ChoiceForm/>')
X(f'{i}<LinkByType/>')
X(f'{i}<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
X(f'{i}<DataHistory>DontUse</DataHistory>')
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>')
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>')
def emit_information_register_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
X(f'{i}<EditType>InDialog</EditType>')
X(f'{i}<DefaultRecordForm/>')
X(f'{i}<DefaultListForm/>')
X(f'{i}<AuxiliaryRecordForm/>')
X(f'{i}<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 = 'false'
if defn.get('mainFilterOnPeriod') is not None:
main_filter_on_period = 'true' if defn['mainFilterOnPeriod'] is True else 'false'
elif periodicity != 'Nonperiodical':
main_filter_on_period = 'true'
X(f'{i}<InformationRegisterPeriodicity>{periodicity}</InformationRegisterPeriodicity>')
X(f'{i}<WriteMode>{write_mode}</WriteMode>')
X(f'{i}<MainFilterOnPeriod>{main_filter_on_period}</MainFilterOnPeriod>')
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
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>')
X(f'{i}<EnableTotalsSliceFirst>false</EnableTotalsSliceFirst>')
X(f'{i}<EnableTotalsSliceLast>false</EnableTotalsSliceLast>')
X(f'{i}<RecordPresentation/>')
X(f'{i}<ExtendedRecordPresentation/>')
X(f'{i}<ListPresentation/>')
X(f'{i}<ExtendedListPresentation/>')
X(f'{i}<Explanation/>')
X(f'{i}<DataHistory>DontUse</DataHistory>')
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>')
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>')
def emit_accumulation_register_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
X(f'{i}<DefaultListForm/>')
X(f'{i}<AuxiliaryListForm/>')
register_type = get_enum_prop('RegisterType', 'registerType', 'Balance')
X(f'{i}<RegisterType>{register_type}</RegisterType>')
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
emit_standard_attributes(i, 'AccumulationRegister')
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
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>')
X(f'{i}<ListPresentation/>')
X(f'{i}<ExtendedListPresentation/>')
X(f'{i}<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)
X(f'{i}<Comment/>')
# Accept both valueType and valueTypes
value_types = list(defn.get('valueTypes', []))
if not value_types and defn.get('valueType'):
vt_raw = defn['valueType']
value_types = list(vt_raw) if isinstance(vt_raw, list) else [vt_raw]
if value_types:
X(f'{i}<Type>')
for vt in value_types:
resolved = resolve_type_str(str(vt))
if re.match(r'^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.', resolved):
X(f'{i}\t<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:{resolved}</v8:Type>')
elif resolved == 'Boolean':
X(f'{i}\t<v8:Type>xs:boolean</v8:Type>')
elif re.match(r'^String', resolved):
X(f'{i}\t<v8:Type>xs:string</v8:Type>')
X(f'{i}\t<v8:StringQualifiers>')
X(f'{i}\t\t<v8:Length>0</v8:Length>')
X(f'{i}\t\t<v8:AllowedLength>Variable</v8:AllowedLength>')
X(f'{i}\t</v8:StringQualifiers>')
else:
X(f'{i}\t<v8:Type>cfg:{resolved}</v8:Type>')
X(f'{i}</Type>')
else:
X(f'{i}<Type/>')
def emit_common_module_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
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)
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 = str(defn['description']) if defn.get('description') else synonym
X(f'{i}<Description>{esc_xml(description)}</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)
X(f'{i}<Comment/>')
sources = list(defn.get('source', []))
if sources:
X(f'{i}<Source>')
for src in sources:
resolved = resolve_type_str(str(src))
X(f'{i}\t<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:{resolved}</v8:Type>')
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)
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
default_form = str(defn['defaultForm']) if defn.get('defaultForm') else ''
if default_form:
X(f'{i}<DefaultForm>{default_form}</DefaultForm>')
else:
X(f'{i}<DefaultForm/>')
aux_form = str(defn['auxiliaryForm']) if defn.get('auxiliaryForm') else ''
if aux_form:
X(f'{i}<AuxiliaryForm>{aux_form}</AuxiliaryForm>')
else:
X(f'{i}<AuxiliaryForm/>')
main_dcs = str(defn['mainDataCompositionSchema']) if defn.get('mainDataCompositionSchema') else ''
if main_dcs:
X(f'{i}<MainDataCompositionSchema>{main_dcs}</MainDataCompositionSchema>')
else:
X(f'{i}<MainDataCompositionSchema/>')
def_settings = str(defn['defaultSettingsForm']) if defn.get('defaultSettingsForm') else ''
if def_settings:
X(f'{i}<DefaultSettingsForm>{def_settings}</DefaultSettingsForm>')
else:
X(f'{i}<DefaultSettingsForm/>')
aux_settings = str(defn['auxiliarySettingsForm']) if defn.get('auxiliarySettingsForm') else ''
if aux_settings:
X(f'{i}<AuxiliarySettingsForm>{aux_settings}</AuxiliarySettingsForm>')
else:
X(f'{i}<AuxiliarySettingsForm/>')
def_variant = str(defn['defaultVariantForm']) if defn.get('defaultVariantForm') else ''
if def_variant:
X(f'{i}<DefaultVariantForm>{def_variant}</DefaultVariantForm>')
else:
X(f'{i}<DefaultVariantForm/>')
X(f'{i}<VariantsStorage/>')
X(f'{i}<SettingsStorage/>')
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
X(f'{i}<ExtendedPresentation/>')
X(f'{i}<Explanation/>')
def emit_data_processor_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>false</UseStandardCommands>')
default_form = str(defn['defaultForm']) if defn.get('defaultForm') else ''
if default_form:
X(f'{i}<DefaultForm>{default_form}</DefaultForm>')
else:
X(f'{i}<DefaultForm/>')
aux_form = str(defn['auxiliaryForm']) if defn.get('auxiliaryForm') else ''
if aux_form:
X(f'{i}<AuxiliaryForm>{aux_form}</AuxiliaryForm>')
else:
X(f'{i}<AuxiliaryForm/>')
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
X(f'{i}<ExtendedPresentation/>')
X(f'{i}<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)
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>true</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 '100'
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>AsDescription</DefaultPresentation>')
X(f'{i}<EditType>InDialog</EditType>')
emit_standard_attributes(i, 'ExchangePlan')
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}<BasedOn/>')
quick_choice = 'true' if defn.get('quickChoice') is True else 'false'
X(f'{i}<QuickChoice>{quick_choice}</QuickChoice>')
X(f'{i}<ChoiceMode>BothWays</ChoiceMode>')
X(f'{i}<InputByString>')
X(f'{i}\t<xr:Field>ExchangePlan.{obj_name}.StandardAttribute.Description</xr:Field>')
X(f'{i}\t<xr:Field>ExchangePlan.{obj_name}.StandardAttribute.Code</xr:Field>')
X(f'{i}</InputByString>')
X(f'{i}<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>')
X(f'{i}<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>')
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
X(f'{i}<DefaultObjectForm/>')
X(f'{i}<DefaultListForm/>')
X(f'{i}<DefaultChoiceForm/>')
X(f'{i}<AuxiliaryObjectForm/>')
X(f'{i}<AuxiliaryListForm/>')
X(f'{i}<AuxiliaryChoiceForm/>')
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
X(f'{i}<DataLockFields/>')
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
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>')
X(f'{i}<ObjectPresentation/>')
X(f'{i}<ExtendedObjectPresentation/>')
X(f'{i}<ListPresentation/>')
X(f'{i}<ExtendedListPresentation/>')
X(f'{i}<Explanation/>')
X(f'{i}<CreateOnInput>DontUse</CreateOnInput>')
X(f'{i}<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
X(f'{i}<DataHistory>DontUse</DataHistory>')
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>')
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>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)
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>true</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 '25'
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}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>')
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
X(f'{i}<CheckUnique>{check_unique}</CheckUnique>')
X(f'{i}<Autonumbering>{autonumbering}</Autonumbering>')
X(f'{i}<DefaultPresentation>AsDescription</DefaultPresentation>')
char_ext_values = str(defn['characteristicExtValues']) if defn.get('characteristicExtValues') else ''
if char_ext_values:
X(f'{i}<CharacteristicExtValues>{char_ext_values}</CharacteristicExtValues>')
else:
X(f'{i}<CharacteristicExtValues/>')
value_types = list(defn.get('valueTypes', []))
if value_types:
X(f'{i}<Type>')
for vt in value_types:
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>')
hierarchical = 'true' if defn.get('hierarchical') is True else 'false'
X(f'{i}<Hierarchical>{hierarchical}</Hierarchical>')
X(f'{i}<FoldersOnTop>true</FoldersOnTop>')
emit_standard_attributes(i, 'ChartOfCharacteristicTypes')
X(f'{i}<Characteristics/>')
X(f'{i}<PredefinedDataUpdate>Auto</PredefinedDataUpdate>')
X(f'{i}<EditType>InDialog</EditType>')
quick_choice = 'true' if defn.get('quickChoice') is True else 'false'
X(f'{i}<QuickChoice>{quick_choice}</QuickChoice>')
X(f'{i}<ChoiceMode>BothWays</ChoiceMode>')
X(f'{i}<InputByString>')
X(f'{i}\t<xr:Field>ChartOfCharacteristicTypes.{obj_name}.StandardAttribute.Description</xr:Field>')
X(f'{i}\t<xr:Field>ChartOfCharacteristicTypes.{obj_name}.StandardAttribute.Code</xr:Field>')
X(f'{i}</InputByString>')
X(f'{i}<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>')
X(f'{i}<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>')
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
X(f'{i}<DefaultObjectForm/>')
X(f'{i}<DefaultFolderForm/>')
X(f'{i}<DefaultListForm/>')
X(f'{i}<DefaultChoiceForm/>')
X(f'{i}<DefaultFolderChoiceForm/>')
X(f'{i}<AuxiliaryObjectForm/>')
X(f'{i}<AuxiliaryFolderForm/>')
X(f'{i}<AuxiliaryListForm/>')
X(f'{i}<AuxiliaryChoiceForm/>')
X(f'{i}<AuxiliaryFolderChoiceForm/>')
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
X(f'{i}<BasedOn/>')
X(f'{i}<DataLockFields/>')
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
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>')
X(f'{i}<ObjectPresentation/>')
X(f'{i}<ExtendedObjectPresentation/>')
X(f'{i}<ListPresentation/>')
X(f'{i}<ExtendedListPresentation/>')
X(f'{i}<Explanation/>')
X(f'{i}<CreateOnInput>DontUse</CreateOnInput>')
X(f'{i}<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
X(f'{i}<DataHistory>DontUse</DataHistory>')
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>')
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>')
def emit_document_journal_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>')
default_form = str(defn['defaultForm']) if defn.get('defaultForm') else ''
if default_form:
X(f'{i}<DefaultForm>{default_form}</DefaultForm>')
else:
X(f'{i}<DefaultForm/>')
aux_form = str(defn['auxiliaryForm']) if defn.get('auxiliaryForm') else ''
if aux_form:
X(f'{i}<AuxiliaryForm>{aux_form}</AuxiliaryForm>')
else:
X(f'{i}<AuxiliaryForm/>')
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
reg_docs = list(defn.get('registeredDocuments', []))
if reg_docs:
X(f'{i}<RegisteredDocuments>')
for rd in reg_docs:
rd_str = str(rd)
if '.' in rd_str:
dot_idx = rd_str.index('.')
rd_prefix = rd_str[:dot_idx]
rd_suffix = rd_str[dot_idx + 1:]
if rd_prefix in object_type_synonyms:
rd_prefix = object_type_synonyms[rd_prefix]
rd_str = f'{rd_prefix}.{rd_suffix}'
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{rd_str}</xr:Item>')
X(f'{i}</RegisteredDocuments>')
else:
X(f'{i}<RegisteredDocuments/>')
emit_standard_attributes(i, 'DocumentJournal')
X(f'{i}<ListPresentation/>')
X(f'{i}<ExtendedListPresentation/>')
X(f'{i}<Explanation/>')
def emit_chart_of_accounts_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
ext_dim_types = str(defn['extDimensionTypes']) if defn.get('extDimensionTypes') else ''
if ext_dim_types:
X(f'{i}<ExtDimensionTypes>{ext_dim_types}</ExtDimensionTypes>')
else:
X(f'{i}<ExtDimensionTypes/>')
max_ext_dim = str(defn['maxExtDimensionCount']) if defn.get('maxExtDimensionCount') is not None else '3'
X(f'{i}<MaxExtDimensionCount>{max_ext_dim}</MaxExtDimensionCount>')
code_mask = str(defn['codeMask']) if defn.get('codeMask') else ''
if code_mask:
X(f'{i}<CodeMask>{code_mask}</CodeMask>')
else:
X(f'{i}<CodeMask/>')
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '8'
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '120'
code_series = str(defn['codeSeries']) if defn.get('codeSeries') else 'WholeChartOfAccounts'
auto_order = 'false' if defn.get('autoOrderByCode') is False else 'true'
order_length = str(defn['orderLength']) if defn.get('orderLength') is not None else '5'
X(f'{i}<CodeLength>{code_length}</CodeLength>')
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
X(f'{i}<CodeSeries>{code_series}</CodeSeries>')
X(f'{i}<CheckUnique>false</CheckUnique>')
X(f'{i}<DefaultPresentation>AsDescription</DefaultPresentation>')
X(f'{i}<AutoOrderByCode>{auto_order}</AutoOrderByCode>')
X(f'{i}<OrderLength>{order_length}</OrderLength>')
X(f'{i}<EditType>InDialog</EditType>')
emit_standard_attributes(i, 'ChartOfAccounts')
X(f'{i}<StandardTabularSections>')
X(f'{i}\t<xr:StandardTabularSection name="ExtDimensionTypes">')
X(f'{i}\t\t<xr:StandardAttributes>')
for st_attr in ['TurnoversOnly', 'Predefined', 'ExtDimensionType', 'LineNumber']:
emit_standard_attribute(f'{i}\t\t\t', st_attr)
X(f'{i}\t\t</xr:StandardAttributes>')
X(f'{i}\t</xr:StandardTabularSection>')
X(f'{i}</StandardTabularSections>')
X(f'{i}<Characteristics/>')
X(f'{i}<PredefinedDataUpdate>Auto</PredefinedDataUpdate>')
quick_choice = 'true' if defn.get('quickChoice') is True else 'false'
X(f'{i}<QuickChoice>{quick_choice}</QuickChoice>')
X(f'{i}<ChoiceMode>BothWays</ChoiceMode>')
X(f'{i}<InputByString>')
X(f'{i}\t<xr:Field>ChartOfAccounts.{obj_name}.StandardAttribute.Description</xr:Field>')
X(f'{i}\t<xr:Field>ChartOfAccounts.{obj_name}.StandardAttribute.Code</xr:Field>')
X(f'{i}</InputByString>')
X(f'{i}<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>')
X(f'{i}<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>')
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
X(f'{i}<DefaultObjectForm/>')
X(f'{i}<DefaultListForm/>')
X(f'{i}<DefaultChoiceForm/>')
X(f'{i}<AuxiliaryObjectForm/>')
X(f'{i}<AuxiliaryListForm/>')
X(f'{i}<AuxiliaryChoiceForm/>')
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
X(f'{i}<BasedOn/>')
X(f'{i}<DataLockFields/>')
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
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>')
X(f'{i}<ObjectPresentation/>')
X(f'{i}<ExtendedObjectPresentation/>')
X(f'{i}<ListPresentation/>')
X(f'{i}<ExtendedListPresentation/>')
X(f'{i}<Explanation/>')
X(f'{i}<CreateOnInput>DontUse</CreateOnInput>')
X(f'{i}<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
X(f'{i}<DataHistory>DontUse</DataHistory>')
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>')
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>')
def emit_accounting_register_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
X(f'{i}<DefaultListForm/>')
X(f'{i}<AuxiliaryListForm/>')
chart_of_accounts = str(defn['chartOfAccounts']) if defn.get('chartOfAccounts') else ''
if chart_of_accounts:
X(f'{i}<ChartOfAccounts>{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>')
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
emit_standard_attributes(i, 'AccountingRegister')
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
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>')
X(f'{i}<ListPresentation/>')
X(f'{i}<ExtendedListPresentation/>')
X(f'{i}<Explanation/>')
def emit_chart_of_calculation_types_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>true</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 '25'
code_type = get_enum_prop('CodeType', 'codeType', 'String')
code_allowed_length = get_enum_prop('CodeAllowedLength', 'codeAllowedLength', 'Variable')
X(f'{i}<CodeLength>{code_length}</CodeLength>')
X(f'{i}<CodeType>{code_type}</CodeType>')
X(f'{i}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>')
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
X(f'{i}<DefaultPresentation>AsDescription</DefaultPresentation>')
dependence = get_enum_prop('DependenceOnCalculationTypes', 'dependenceOnCalculationTypes', 'DontUse')
X(f'{i}<DependenceOnCalculationTypes>{dependence}</DependenceOnCalculationTypes>')
base_types = list(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">{bt}</xr:Item>')
X(f'{i}</BaseCalculationTypes>')
else:
X(f'{i}<BaseCalculationTypes/>')
action_period_use = 'true' if defn.get('actionPeriodUse') is True else 'false'
X(f'{i}<ActionPeriodUse>{action_period_use}</ActionPeriodUse>')
emit_standard_attributes(i, 'ChartOfCalculationTypes')
X(f'{i}<Characteristics/>')
X(f'{i}<PredefinedDataUpdate>Auto</PredefinedDataUpdate>')
X(f'{i}<EditType>InDialog</EditType>')
quick_choice = 'true' if defn.get('quickChoice') is True else 'false'
X(f'{i}<QuickChoice>{quick_choice}</QuickChoice>')
X(f'{i}<ChoiceMode>BothWays</ChoiceMode>')
X(f'{i}<InputByString>')
X(f'{i}\t<xr:Field>ChartOfCalculationTypes.{obj_name}.StandardAttribute.Description</xr:Field>')
X(f'{i}\t<xr:Field>ChartOfCalculationTypes.{obj_name}.StandardAttribute.Code</xr:Field>')
X(f'{i}</InputByString>')
X(f'{i}<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>')
X(f'{i}<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>')
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
X(f'{i}<DefaultObjectForm/>')
X(f'{i}<DefaultListForm/>')
X(f'{i}<DefaultChoiceForm/>')
X(f'{i}<AuxiliaryObjectForm/>')
X(f'{i}<AuxiliaryListForm/>')
X(f'{i}<AuxiliaryChoiceForm/>')
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
X(f'{i}<BasedOn/>')
X(f'{i}<DataLockFields/>')
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
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>')
X(f'{i}<ObjectPresentation/>')
X(f'{i}<ExtendedObjectPresentation/>')
X(f'{i}<ListPresentation/>')
X(f'{i}<ExtendedListPresentation/>')
X(f'{i}<Explanation/>')
X(f'{i}<CreateOnInput>DontUse</CreateOnInput>')
X(f'{i}<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
def emit_calculation_register_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
X(f'{i}<DefaultListForm/>')
X(f'{i}<AuxiliaryListForm/>')
chart_of_calc_types = str(defn['chartOfCalculationTypes']) if defn.get('chartOfCalculationTypes') else ''
if chart_of_calc_types:
X(f'{i}<ChartOfCalculationTypes>{chart_of_calc_types}</ChartOfCalculationTypes>')
else:
X(f'{i}<ChartOfCalculationTypes/>')
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>{schedule}</Schedule>')
else:
X(f'{i}<Schedule/>')
schedule_value = str(defn['scheduleValue']) if defn.get('scheduleValue') else ''
if schedule_value:
X(f'{i}<ScheduleValue>{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>{schedule_date}</ScheduleDate>')
else:
X(f'{i}<ScheduleDate/>')
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
emit_standard_attributes(i, 'CalculationRegister')
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
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>')
X(f'{i}<ListPresentation/>')
X(f'{i}<ExtendedListPresentation/>')
X(f'{i}<Explanation/>')
def emit_business_process_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
edit_type = get_enum_prop('EditType', 'editType', 'InDialog')
X(f'{i}<EditType>{edit_type}</EditType>')
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'
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}<CheckUnique>{check_unique}</CheckUnique>')
X(f'{i}<Autonumbering>{autonumbering}</Autonumbering>')
emit_standard_attributes(i, 'BusinessProcess')
X(f'{i}<Characteristics/>')
task_ref = str(defn['task']) if defn.get('task') else ''
if task_ref:
X(f'{i}<Task>{task_ref}</Task>')
else:
X(f'{i}<Task/>')
X(f'{i}<BasedOn/>')
X(f'{i}<InputByString>')
X(f'{i}\t<xr:Field>BusinessProcess.{obj_name}.StandardAttribute.Number</xr:Field>')
X(f'{i}</InputByString>')
X(f'{i}<CreateOnInput>DontUse</CreateOnInput>')
X(f'{i}<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>')
X(f'{i}<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>')
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
X(f'{i}<DefaultObjectForm/>')
X(f'{i}<DefaultListForm/>')
X(f'{i}<DefaultChoiceForm/>')
X(f'{i}<AuxiliaryObjectForm/>')
X(f'{i}<AuxiliaryListForm/>')
X(f'{i}<AuxiliaryChoiceForm/>')
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
X(f'{i}<DataLockFields/>')
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
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>')
X(f'{i}<ObjectPresentation/>')
X(f'{i}<ExtendedObjectPresentation/>')
X(f'{i}<ListPresentation/>')
X(f'{i}<ExtendedListPresentation/>')
X(f'{i}<Explanation/>')
X(f'{i}<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
X(f'{i}<DataHistory>DontUse</DataHistory>')
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>')
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>')
def emit_task_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>true</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>')
addressing = str(defn['addressing']) if defn.get('addressing') else ''
if addressing:
X(f'{i}<Addressing>{addressing}</Addressing>')
else:
X(f'{i}<Addressing/>')
main_addressing = str(defn['mainAddressingAttribute']) if defn.get('mainAddressingAttribute') else ''
if main_addressing:
X(f'{i}<MainAddressingAttribute>{main_addressing}</MainAddressingAttribute>')
else:
X(f'{i}<MainAddressingAttribute/>')
current_performer = str(defn['currentPerformer']) if defn.get('currentPerformer') else ''
if current_performer:
X(f'{i}<CurrentPerformer>{current_performer}</CurrentPerformer>')
else:
X(f'{i}<CurrentPerformer/>')
emit_standard_attributes(i, 'Task')
X(f'{i}<Characteristics/>')
X(f'{i}<BasedOn/>')
X(f'{i}<InputByString>')
X(f'{i}\t<xr:Field>Task.{obj_name}.StandardAttribute.Number</xr:Field>')
X(f'{i}</InputByString>')
X(f'{i}<CreateOnInput>DontUse</CreateOnInput>')
X(f'{i}<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>')
X(f'{i}<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>')
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
X(f'{i}<DefaultObjectForm/>')
X(f'{i}<DefaultListForm/>')
X(f'{i}<DefaultChoiceForm/>')
X(f'{i}<AuxiliaryObjectForm/>')
X(f'{i}<AuxiliaryListForm/>')
X(f'{i}<AuxiliaryChoiceForm/>')
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
X(f'{i}<DataLockFields/>')
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
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>')
X(f'{i}<ObjectPresentation/>')
X(f'{i}<ExtendedObjectPresentation/>')
X(f'{i}<ListPresentation/>')
X(f'{i}<ExtendedListPresentation/>')
X(f'{i}<Explanation/>')
X(f'{i}<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
X(f'{i}<DataHistory>DontUse</DataHistory>')
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>')
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>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 = ''
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 = str(col_def['synonym']) if col_def.get('synonym') else split_camel_case(name)
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)
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">{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_accounting_flag(indent, flag_name):
uid = new_uuid()
flag_synonym = split_camel_case(flag_name)
X(f'{indent}<AccountingFlag uuid="{uid}">')
X(f'{indent}\t<Properties>')
X(f'{indent}\t\t<Name>{esc_xml(flag_name)}</Name>')
emit_mltext(f'{indent}\t\t', 'Synonym', flag_synonym)
X(f'{indent}\t\t<Comment/>')
X(f'{indent}\t\t<Type>')
X(f'{indent}\t\t\t<v8:Type>xs:boolean</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>false</MarkNegatives>')
X(f'{indent}\t\t<Mask/>')
X(f'{indent}\t\t<MultiLine>false</MultiLine>')
X(f'{indent}\t\t<ExtendedEdit>false</ExtendedEdit>')
X(f'{indent}\t\t<MinValue xsi:nil="true"/>')
X(f'{indent}\t\t<MaxValue xsi:nil="true"/>')
X(f'{indent}\t\t<FillChecking>DontCheck</FillChecking>')
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<ChoiceForm/>')
X(f'{indent}\t\t<LinkByType/>')
X(f'{indent}\t\t<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
X(f'{indent}\t</Properties>')
X(f'{indent}</AccountingFlag>')
def emit_ext_dimension_accounting_flag(indent, flag_name):
uid = new_uuid()
flag_synonym = split_camel_case(flag_name)
X(f'{indent}<ExtDimensionAccountingFlag uuid="{uid}">')
X(f'{indent}\t<Properties>')
X(f'{indent}\t\t<Name>{esc_xml(flag_name)}</Name>')
emit_mltext(f'{indent}\t\t', 'Synonym', flag_synonym)
X(f'{indent}\t\t<Comment/>')
X(f'{indent}\t\t<Type>')
X(f'{indent}\t\t\t<v8:Type>xs:boolean</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>false</MarkNegatives>')
X(f'{indent}\t\t<Mask/>')
X(f'{indent}\t\t<MultiLine>false</MultiLine>')
X(f'{indent}\t\t<ExtendedEdit>false</ExtendedEdit>')
X(f'{indent}\t\t<MinValue xsi:nil="true"/>')
X(f'{indent}\t\t<MaxValue xsi:nil="true"/>')
X(f'{indent}\t\t<FillChecking>DontCheck</FillChecking>')
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<ChoiceForm/>')
X(f'{indent}\t\t<LinkByType/>')
X(f'{indent}\t\t<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
X(f'{indent}\t</Properties>')
X(f'{indent}</ExtDimensionAccountingFlag>')
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):
uid = new_uuid()
name = ''
attr_synonym = ''
type_str = ''
addressing_dimension = ''
indexing = 'Index'
parsed = parse_attribute_shorthand(addr_def)
name = parsed['name']
attr_synonym = parsed['synonym']
type_str = parsed['type']
if not isinstance(addr_def, str):
if addr_def.get('addressingDimension'):
addressing_dimension = str(addr_def['addressingDimension'])
if addr_def.get('indexing'):
indexing = str(addr_def['indexing'])
X(f'{indent}<AddressingAttribute uuid="{uid}">')
X(f'{indent}\t<Properties>')
X(f'{indent}\t\t<Name>{esc_xml(name)}</Name>')
emit_mltext(f'{indent}\t\t', 'Synonym', attr_synonym)
X(f'{indent}\t\t<Comment/>')
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>')
if addressing_dimension:
X(f'{indent}\t\t<AddressingDimension>{addressing_dimension}</AddressingDimension>')
else:
X(f'{indent}\t\t<AddressingDimension/>')
X(f'{indent}\t\t<Indexing>{indexing}</Indexing>')
X(f'{indent}\t\t<FullTextSearch>Use</FullTextSearch>')
X(f'{indent}\t\t<DataHistory>Use</DataHistory>')
X(f'{indent}\t</Properties>')
X(f'{indent}</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,
'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',
]
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']
if isinstance(ts_data, list):
for ts in ts_data:
ts_name = ts['name']
ts_cols = _as_list(ts.get('attributes', []))
ts_sections[ts_name] = ts_cols
ts_order.append(ts_name)
else:
for k, v in ts_data.items():
ts_sections[k] = _as_list(v)
ts_order.append(k)
# ChartOfAccounts: AccountingFlags + ExtDimensionAccountingFlags
acct_flags = []
ext_dim_flags = []
if obj_type == 'ChartOfAccounts':
if defn.get('accountingFlags'):
acct_flags = _as_list(defn['accountingFlags'])
if defn.get('extDimensionAccountingFlags'):
ext_dim_flags = _as_list(defn['extDimensionAccountingFlags'])
# Task: AddressingAttributes
addr_attrs = []
if obj_type == 'Task' and defn.get('addressingAttributes'):
addr_attrs = _as_list(defn['addressingAttributes'])
child_count = len(attrs) + len(ts_sections) + len(acct_flags) + len(ext_dim_flags) + len(addr_attrs)
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 in ('ChartOfAccounts', 'ChartOfCharacteristicTypes', 'ChartOfCalculationTypes'):
context = 'chart'
else:
context = 'object'
for a in attrs:
emit_attribute('\t\t\t', a, context)
for ts_name in ts_order:
columns = ts_sections[ts_name]
emit_tabular_section('\t\t\t', ts_name, columns, obj_type, obj_name)
for af in acct_flags:
af_name = af['name'] if isinstance(af, dict) else str(af)
emit_accounting_flag('\t\t\t', af_name)
for edf in ext_dim_flags:
edf_name = edf['name'] if isinstance(edf, dict) else str(edf)
emit_ext_dimension_accounting_flag('\t\t\t', edf_name)
for aa in addr_attrs:
emit_addressing_attribute('\t\t\t', aa)
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))
if dims or resources or reg_attrs:
has_children = True
X('\t\t<ChildObjects>')
for r in resources:
emit_resource('\t\t\t', r, obj_type)
for d in dims:
emit_dimension('\t\t\t', d, obj_type)
# InformationRegister.Attribute supports FillFromFillingValue/FillValue/DataHistory;
# AccumulationRegister/AccountingRegister/CalculationRegister.Attribute do NOT.
reg_ctx = 'register-info' if obj_type == 'InformationRegister' else 'register-other'
for a in reg_attrs:
emit_attribute('\t\t\t', a, reg_ctx)
X('\t\t</ChildObjects>')
else:
X('\t\t<ChildObjects/>')
# --- DocumentJournal: columns ---
if obj_type == 'DocumentJournal':
columns = list(defn.get('columns', []))
if columns:
has_children = True
X('\t\t<ChildObjects>')
for col in columns:
emit_column('\t\t\t', col)
X('\t\t</ChildObjects>')
else:
X('\t\t<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',
}
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)
# Special files
if obj_type == 'ExchangePlan':
content_path = os.path.join(ext_dir, 'Content.xml')
if not os.path.isfile(content_path):
ensure_ext_dir()
content_xml = f'<?xml version="1.0" encoding="UTF-8"?>\r\n<ExchangePlanContent xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" 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)
# ---------------------------------------------------------------------------
# 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):
# Parse preserving whitespace via raw string manipulation
with open(config_xml_path, 'r', encoding='utf-8-sig') as f:
config_content = f.read()
ns = 'http://v8.1c.ru/8.3/MDClasses'
ET.register_namespace('', ns)
# Parse all namespaces used in the file
# Use iterparse to collect namespace prefixes
namespaces_in_file = {}
for evt, elem in ET.iterparse(config_xml_path, events=['start-ns']):
prefix, uri = elem
if prefix:
namespaces_in_file[prefix] = uri
ET.register_namespace(prefix, uri)
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 not None:
existing = child_objects.findall(f'{{{ns}}}{child_tag}')
already_exists = False
for e in existing:
if (e.text or '').strip() == obj_name:
already_exists = True
break
if already_exists:
reg_result = 'already'
else:
new_elem = ET.SubElement(child_objects, f'{{{ns}}}{child_tag}')
new_elem.text = obj_name
if existing:
# Insert after last existing element of same type
last_elem = existing[-1]
all_children = list(child_objects)
idx = all_children.index(last_elem)
child_objects.remove(new_elem)
child_objects.insert(idx + 1, new_elem)
# Write back preserving BOM
tree.write(config_xml_path, encoding='utf-8', xml_declaration=True)
# Re-read to add BOM, fix declaration quotes, ensure trailing newline
with open(config_xml_path, 'r', encoding='utf-8') as f:
raw = f.read()
if raw.startswith("<?xml version='1.0' encoding='utf-8'?>"):
raw = raw.replace("<?xml version='1.0' encoding='utf-8'?>", '<?xml version="1.0" encoding="UTF-8"?>', 1)
if not raw.endswith('\n'):
raw += '\n'
write_utf8_bom(config_xml_path, raw)
reg_result = 'added'
else:
reg_result = 'no-childobj'
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"')