#!/usr/bin/env python3 # meta-compile v1.12 — 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 sys.stdout.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8") # --------------------------------------------------------------------------- # Inline utilities # --------------------------------------------------------------------------- def esc_xml(s): return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"') def new_uuid(): return str(uuid.uuid4()) def write_utf8_bom(path, content): with open(path, 'w', encoding='utf-8-sig', newline='') as f: f.write(content) # --------------------------------------------------------------------------- # XML builder (lines list) # --------------------------------------------------------------------------- lines = [] def X(text): lines.append(text) def emit_mltext(indent, tag, text): if not text: X(f'{indent}<{tag}/>') return X(f'{indent}<{tag}>') X(f'{indent}\t') X(f'{indent}\t\tru') X(f'{indent}\t\t{esc_xml(text)}') X(f'{indent}\t') X(f'{indent}') # --------------------------------------------------------------------------- # 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) # --- 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}xs:boolean') 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}xs:string') X(f'{indent}') X(f'{indent}\t{length}') X(f'{indent}\tVariable') X(f'{indent}') return # Number without params -> Number(10,0) if type_str == 'Number': X(f'{indent}xs:decimal') X(f'{indent}') X(f'{indent}\t10') X(f'{indent}\t0') X(f'{indent}\tAny') X(f'{indent}') 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}xs:decimal') X(f'{indent}') X(f'{indent}\t{digits}') X(f'{indent}\t{fraction}') X(f'{indent}\t{sign}') X(f'{indent}') return # Date / DateTime if type_str == 'Date': X(f'{indent}xs:dateTime') X(f'{indent}') X(f'{indent}\tDate') X(f'{indent}') return if type_str == 'DateTime': X(f'{indent}xs:dateTime') X(f'{indent}') X(f'{indent}\tDateTime') X(f'{indent}') return # DefinedType m = re.match(r'^DefinedType\.(.+)$', type_str) if m: dt_name = m.group(1) X(f'{indent}cfg:DefinedType.{dt_name}') return # ValueStorage if type_str == 'ValueStorage': X(f'{indent}xs:base64Binary') 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}d5p1:{type_str}') return # Fallback X(f'{indent}{type_str}') def emit_value_type(indent, type_str): X(f'{indent}') emit_type_content(f'{indent}\t', type_str) X(f'{indent}') def emit_fill_value(indent, type_str): if not type_str: X(f'{indent}') return type_str = resolve_type_str(type_str) if type_str == 'Boolean': X(f'{indent}false') return if re.match(r'^String', type_str): X(f'{indent}') return if re.match(r'^Number', type_str): X(f'{indent}0') return if re.match(r'^(Date|DateTime)$', type_str): X(f'{indent}') return X(f'{indent}') # --------------------------------------------------------------------------- # 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}') if object_type == 'ExchangePlan': X(f'{indent}\t{new_uuid()}') for gt in types: full_name = f"{gt['prefix']}.{object_name}" X(f'{indent}\t') X(f'{indent}\t\t{new_uuid()}') X(f'{indent}\t\t{new_uuid()}') X(f'{indent}\t') X(f'{indent}') # --------------------------------------------------------------------------- # 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}') X(f'{indent}\t') X(f'{indent}\tDontCheck') X(f'{indent}\tfalse') X(f'{indent}\tfalse') X(f'{indent}\tAuto') X(f'{indent}\t') X(f'{indent}\t') X(f'{indent}\tfalse') X(f'{indent}\t') X(f'{indent}\t') X(f'{indent}\tAuto') X(f'{indent}\tAuto') X(f'{indent}\t') X(f'{indent}\tfalse') X(f'{indent}\tUse') X(f'{indent}\tfalse') X(f'{indent}\t') X(f'{indent}\t') X(f'{indent}\t') X(f'{indent}\tUse') X(f'{indent}\t') X(f'{indent}\t') X(f'{indent}\t') X(f'{indent}\t') X(f'{indent}') def emit_standard_attributes(indent, object_type): attrs = standard_attributes_by_type.get(object_type) if not attrs: return X(f'{indent}') for a in attrs: emit_standard_attribute(f'{indent}\t', a) X(f'{indent}') def emit_tabular_standard_attributes(indent): X(f'{indent}') emit_standard_attribute(f'{indent}\t', 'LineNumber') X(f'{indent}') # --------------------------------------------------------------------------- # 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}') X(f'{indent}\t') X(f'{indent}\t\t{esc_xml(parsed["name"])}') emit_mltext(f'{indent}\t\t', 'Synonym', parsed['synonym']) X(f'{indent}\t\t') type_str = parsed['type'] if type_str: emit_value_type(f'{indent}\t\t', type_str) else: X(f'{indent}\t\t') X(f'{indent}\t\t\txs:string') X(f'{indent}\t\t') X(f'{indent}\t\tfalse') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tfalse') X(f'{indent}\t\t') multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false' X(f'{indent}\t\t{multi_line}') X(f'{indent}\t\tfalse') X(f'{indent}\t\t') X(f'{indent}\t\t') # 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\tfalse') 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{fill_checking}') X(f'{indent}\t\tItems') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tAuto') X(f'{indent}\t\tAuto') X(f'{indent}\t\t') X(f'{indent}\t\t') chi = parsed.get('choiceHistoryOnInput') or 'Auto' X(f'{indent}\t\t{chi}') if context == 'catalog': X(f'{indent}\t\tForItem') 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}') X(f'{indent}\t\tUse') # DataHistory — not for Chart* types and non-InformationRegister register family if context not in ('chart', 'register-other'): X(f'{indent}\t\tUse') X(f'{indent}\t') X(f'{indent}') # --------------------------------------------------------------------------- # 9. TabularSection emitter # --------------------------------------------------------------------------- def emit_tabular_section(indent, ts_name, columns, object_type, object_name): uid = new_uuid() X(f'{indent}') type_prefix = f'{object_type}TabularSection' row_prefix = f'{object_type}TabularSectionRow' X(f'{indent}\t') X(f'{indent}\t\t') X(f'{indent}\t\t\t{new_uuid()}') X(f'{indent}\t\t\t{new_uuid()}') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\t\t{new_uuid()}') X(f'{indent}\t\t\t{new_uuid()}') X(f'{indent}\t\t') X(f'{indent}\t') ts_synonym = split_camel_case(ts_name) X(f'{indent}\t') X(f'{indent}\t\t{esc_xml(ts_name)}') emit_mltext(f'{indent}\t\t', 'Synonym', ts_synonym) X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tDontCheck') emit_tabular_standard_attributes(f'{indent}\t\t') if object_type == 'Catalog': X(f'{indent}\t\tForItem') X(f'{indent}\t') ts_context = 'processor-tabular' if object_type in ('DataProcessor', 'Report') else 'tabular' X(f'{indent}\t') for col in columns: parsed = parse_attribute_shorthand(col) emit_attribute(f'{indent}\t\t', parsed, ts_context) X(f'{indent}\t') X(f'{indent}') # --------------------------------------------------------------------------- # 10. EnumValue emitter # --------------------------------------------------------------------------- def emit_enum_value(indent, parsed): uid = new_uuid() X(f'{indent}') X(f'{indent}\t') X(f'{indent}\t\t{esc_xml(parsed["name"])}') emit_mltext(f'{indent}\t\t', 'Synonym', parsed['synonym']) X(f'{indent}\t\t') X(f'{indent}\t') X(f'{indent}') # --------------------------------------------------------------------------- # 11. Dimension emitter # --------------------------------------------------------------------------- def emit_dimension(indent, parsed, register_type): uid = new_uuid() X(f'{indent}') X(f'{indent}\t') X(f'{indent}\t\t{esc_xml(parsed["name"])}') emit_mltext(f'{indent}\t\t', 'Synonym', parsed['synonym']) X(f'{indent}\t\t') type_str = parsed['type'] if type_str: emit_value_type(f'{indent}\t\t', type_str) else: X(f'{indent}\t\t') X(f'{indent}\t\t\txs:string') X(f'{indent}\t\t') X(f'{indent}\t\tfalse') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tfalse') X(f'{indent}\t\t') multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false' X(f'{indent}\t\t{multi_line}') X(f'{indent}\t\tfalse') X(f'{indent}\t\t') X(f'{indent}\t\t') flags = parsed.get('flags', []) if register_type == 'InformationRegister': fill_from = 'true' if 'master' in flags else 'false' X(f'{indent}\t\t{fill_from}') X(f'{indent}\t\t') fill_checking = 'DontCheck' if 'req' in flags: fill_checking = 'ShowError' X(f'{indent}\t\t{fill_checking}') X(f'{indent}\t\tItems') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tAuto') X(f'{indent}\t\tAuto') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tAuto') 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}') X(f'{indent}\t\t{main_filter}') X(f'{indent}\t\t{deny_incomplete}') if register_type == 'AccumulationRegister': deny_incomplete = 'true' if 'denyincomplete' in flags else 'false' X(f'{indent}\t\t{deny_incomplete}') indexing = 'DontIndex' if 'index' in flags: indexing = 'Index' X(f'{indent}\t\t{indexing}') X(f'{indent}\t\tUse') if register_type == 'AccumulationRegister': use_in_totals = 'false' if 'nouseintotals' in flags else 'true' X(f'{indent}\t\t{use_in_totals}') if register_type == 'InformationRegister': X(f'{indent}\t\tUse') X(f'{indent}\t') X(f'{indent}') # --------------------------------------------------------------------------- # 12. Resource emitter # --------------------------------------------------------------------------- def emit_resource(indent, parsed, register_type): uid = new_uuid() X(f'{indent}') X(f'{indent}\t') X(f'{indent}\t\t{esc_xml(parsed["name"])}') emit_mltext(f'{indent}\t\t', 'Synonym', parsed['synonym']) X(f'{indent}\t\t') type_str = parsed['type'] if type_str: emit_value_type(f'{indent}\t\t', type_str) else: X(f'{indent}\t\t') X(f'{indent}\t\t\txs:decimal') X(f'{indent}\t\t\t') X(f'{indent}\t\t\t\t15') X(f'{indent}\t\t\t\t2') X(f'{indent}\t\t\t\tAny') X(f'{indent}\t\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tfalse') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tfalse') X(f'{indent}\t\t') multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false' X(f'{indent}\t\t{multi_line}') X(f'{indent}\t\tfalse') X(f'{indent}\t\t') X(f'{indent}\t\t') if register_type == 'InformationRegister': X(f'{indent}\t\tfalse') X(f'{indent}\t\t') flags = parsed.get('flags', []) fill_checking = 'DontCheck' if 'req' in flags: fill_checking = 'ShowError' X(f'{indent}\t\t{fill_checking}') X(f'{indent}\t\tItems') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tAuto') X(f'{indent}\t\tAuto') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tAuto') if register_type == 'InformationRegister': X(f'{indent}\t\tDontIndex') X(f'{indent}\t\tUse') X(f'{indent}\t\tUse') if register_type == 'AccumulationRegister': X(f'{indent}\t\tUse') X(f'{indent}\t') X(f'{indent}') # --------------------------------------------------------------------------- # 13. Property emitters per type # --------------------------------------------------------------------------- def emit_catalog_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') hierarchical = 'true' if defn.get('hierarchical') is True else 'false' hierarchy_type = get_enum_prop('HierarchyType', 'hierarchyType', 'HierarchyFoldersAndItems') X(f'{i}{hierarchical}') X(f'{i}{hierarchy_type}') 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}{limit_level_count}') X(f'{i}{level_count}') X(f'{i}{folders_on_top}') X(f'{i}true') owners = defn.get('owners', []) if owners: X(f'{i}') for owner_ref in owners: full_ref = owner_ref if '.' in str(owner_ref) else f'Catalog.{owner_ref}' X(f'{i}\t{full_ref}') X(f'{i}') else: X(f'{i}') subordination_use = get_enum_prop('SubordinationUse', 'subordinationUse', 'ToItems') X(f'{i}{subordination_use}') 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}{code_length}') X(f'{i}{description_length}') X(f'{i}{code_type}') X(f'{i}{code_allowed_length}') code_series = get_enum_prop('CodeSeries', 'codeSeries', 'WholeCatalog') X(f'{i}{code_series}') X(f'{i}{check_unique}') X(f'{i}{autonumbering}') default_presentation = get_enum_prop('DefaultPresentation', 'defaultPresentation', 'AsDescription') X(f'{i}{default_presentation}') emit_standard_attributes(i, 'Catalog') X(f'{i}') X(f'{i}Auto') X(f'{i}InDialog') quick_choice = 'true' if defn.get('quickChoice') is True else 'false' choice_mode = get_enum_prop('ChoiceMode', 'choiceMode', 'BothWays') X(f'{i}{quick_choice}') X(f'{i}{choice_mode}') X(f'{i}') X(f'{i}\tCatalog.{obj_name}.StandardAttribute.Description') X(f'{i}\tCatalog.{obj_name}.StandardAttribute.Code') X(f'{i}') X(f'{i}Begin') X(f'{i}DontUse') X(f'{i}Directly') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}false') X(f'{i}') X(f'{i}') data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic') X(f'{i}{data_lock_control_mode}') full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use') X(f'{i}{full_text_search}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}DontUse') X(f'{i}Auto') X(f'{i}DontUse') X(f'{i}false') X(f'{i}false') def emit_document_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') X(f'{i}true') X(f'{i}') 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}{number_type}') X(f'{i}{number_length}') X(f'{i}{number_allowed_length}') X(f'{i}{number_periodicity}') X(f'{i}{check_unique}') X(f'{i}{autonumbering}') emit_standard_attributes(i, 'Document') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}\tDocument.{obj_name}.StandardAttribute.Number') X(f'{i}') X(f'{i}DontUse') X(f'{i}Begin') X(f'{i}DontUse') X(f'{i}Directly') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') 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}') X(f'{i}{real_time_posting}') X(f'{i}{reg_records_deletion}') X(f'{i}{reg_records_writing}') X(f'{i}{sequence_filling}') # 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}') for rr in reg_records: X(f'{i}\t{rr}') X(f'{i}') else: X(f'{i}') X(f'{i}{post_in_priv}') X(f'{i}{unpost_in_priv}') X(f'{i}false') X(f'{i}') data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic') X(f'{i}{data_lock_control_mode}') full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use') X(f'{i}{full_text_search}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}Auto') X(f'{i}DontUse') X(f'{i}false') X(f'{i}false') def emit_enum_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') X(f'{i}false') emit_standard_attributes(i, 'Enum') X(f'{i}') quick_choice = 'false' if defn.get('quickChoice') is False else 'true' X(f'{i}{quick_choice}') X(f'{i}BothWays') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}Auto') def emit_constant_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') # Type value_type = build_type_str(defn) or 'String' emit_value_type(i, value_type) X(f'{i}true') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}false') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}false') X(f'{i}') X(f'{i}false') X(f'{i}false') X(f'{i}') X(f'{i}') X(f'{i}DontCheck') X(f'{i}Items') X(f'{i}') X(f'{i}') X(f'{i}Auto') X(f'{i}') X(f'{i}') X(f'{i}Auto') data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic') X(f'{i}{data_lock_control_mode}') X(f'{i}DontUse') X(f'{i}false') X(f'{i}false') def emit_information_register_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') X(f'{i}true') X(f'{i}InDialog') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') 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}{periodicity}') X(f'{i}{write_mode}') X(f'{i}{main_filter_on_period}') X(f'{i}false') data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic') X(f'{i}{data_lock_control_mode}') full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use') X(f'{i}{full_text_search}') X(f'{i}false') X(f'{i}false') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}DontUse') X(f'{i}false') X(f'{i}false') def emit_accumulation_register_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') X(f'{i}true') X(f'{i}') X(f'{i}') register_type = get_enum_prop('RegisterType', 'registerType', 'Balance') X(f'{i}{register_type}') X(f'{i}false') emit_standard_attributes(i, 'AccumulationRegister') data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic') X(f'{i}{data_lock_control_mode}') full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use') X(f'{i}{full_text_search}') enable_totals_splitting = 'false' if defn.get('enableTotalsSplitting') is False else 'true' X(f'{i}{enable_totals_splitting}') X(f'{i}') X(f'{i}') X(f'{i}') # --- 13a. DefinedType, CommonModule, ScheduledJob, EventSubscription --- def emit_defined_type_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') # 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}') 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}\td5p1:{resolved}') elif resolved == 'Boolean': X(f'{i}\txs:boolean') elif re.match(r'^String', resolved): X(f'{i}\txs:string') X(f'{i}\t') X(f'{i}\t\t0') X(f'{i}\t\tVariable') X(f'{i}\t') else: X(f'{i}\tcfg:{resolved}') X(f'{i}') else: X(f'{i}') def emit_common_module_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') 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_val}') X(f'{i}{client_managed}') X(f'{i}{server}') X(f'{i}{external_connection}') X(f'{i}{client_ordinary}') X(f'{i}{server_call}') X(f'{i}{privileged}') return_values_reuse = get_enum_prop('ReturnValuesReuse', 'returnValuesReuse', 'DontUse') X(f'{i}{return_values_reuse}') def emit_scheduled_job_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') 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}{esc_xml(method_name)}') description = str(defn['description']) if defn.get('description') else synonym X(f'{i}{esc_xml(description)}') key = str(defn['key']) if defn.get('key') else '' X(f'{i}{esc_xml(key)}') use = 'true' if defn.get('use') is True else 'false' X(f'{i}{use}') predefined = 'true' if defn.get('predefined') is True else 'false' X(f'{i}{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}{restart_count}') X(f'{i}{restart_interval}') def emit_event_subscription_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') sources = list(defn.get('source', [])) if sources: X(f'{i}') for src in sources: resolved = resolve_type_str(str(src)) X(f'{i}\td5p1:{resolved}') X(f'{i}') else: X(f'{i}') event = str(defn['event']) if defn.get('event') else 'BeforeWrite' X(f'{i}{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}{esc_xml(handler)}') # --- 13b. Report, DataProcessor --- def emit_report_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') X(f'{i}true') default_form = str(defn['defaultForm']) if defn.get('defaultForm') else '' if default_form: X(f'{i}{default_form}') else: X(f'{i}') aux_form = str(defn['auxiliaryForm']) if defn.get('auxiliaryForm') else '' if aux_form: X(f'{i}{aux_form}') else: X(f'{i}') main_dcs = str(defn['mainDataCompositionSchema']) if defn.get('mainDataCompositionSchema') else '' if main_dcs: X(f'{i}{main_dcs}') else: X(f'{i}') def_settings = str(defn['defaultSettingsForm']) if defn.get('defaultSettingsForm') else '' if def_settings: X(f'{i}{def_settings}') else: X(f'{i}') aux_settings = str(defn['auxiliarySettingsForm']) if defn.get('auxiliarySettingsForm') else '' if aux_settings: X(f'{i}{aux_settings}') else: X(f'{i}') def_variant = str(defn['defaultVariantForm']) if defn.get('defaultVariantForm') else '' if def_variant: X(f'{i}{def_variant}') else: X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}false') X(f'{i}') X(f'{i}') def emit_data_processor_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') X(f'{i}false') default_form = str(defn['defaultForm']) if defn.get('defaultForm') else '' if default_form: X(f'{i}{default_form}') else: X(f'{i}') aux_form = str(defn['auxiliaryForm']) if defn.get('auxiliaryForm') else '' if aux_form: X(f'{i}{aux_form}') else: X(f'{i}') X(f'{i}false') X(f'{i}') X(f'{i}') # --- 13c. ExchangePlan, ChartOfCharacteristicTypes, DocumentJournal --- def emit_exchange_plan_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') X(f'{i}true') 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}{code_length}') X(f'{i}{code_allowed_length}') X(f'{i}{description_length}') X(f'{i}AsDescription') X(f'{i}InDialog') 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}{distributed}') X(f'{i}{include_ext}') X(f'{i}') quick_choice = 'true' if defn.get('quickChoice') is True else 'false' X(f'{i}{quick_choice}') X(f'{i}BothWays') X(f'{i}') X(f'{i}\tExchangePlan.{obj_name}.StandardAttribute.Description') X(f'{i}\tExchangePlan.{obj_name}.StandardAttribute.Code') X(f'{i}') X(f'{i}Begin') X(f'{i}DontUse') X(f'{i}Directly') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}false') X(f'{i}') data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic') X(f'{i}{data_lock_control_mode}') full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use') X(f'{i}{full_text_search}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}DontUse') X(f'{i}Auto') X(f'{i}DontUse') X(f'{i}false') X(f'{i}false') def emit_chart_of_characteristic_types_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') X(f'{i}true') 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}{code_length}') X(f'{i}{code_allowed_length}') X(f'{i}{description_length}') X(f'{i}{check_unique}') X(f'{i}{autonumbering}') X(f'{i}AsDescription') char_ext_values = str(defn['characteristicExtValues']) if defn.get('characteristicExtValues') else '' if char_ext_values: X(f'{i}{char_ext_values}') else: X(f'{i}') value_types = list(defn.get('valueTypes', [])) if value_types: X(f'{i}') for vt in value_types: emit_type_content(f'{i}\t', str(vt)) X(f'{i}') else: X(f'{i}') X(f'{i}\txs:boolean') X(f'{i}\txs:string') X(f'{i}\t') X(f'{i}\t\t100') X(f'{i}\t\tVariable') X(f'{i}\t') X(f'{i}\txs:decimal') X(f'{i}\t') X(f'{i}\t\t15') X(f'{i}\t\t2') X(f'{i}\t\tAny') X(f'{i}\t') X(f'{i}\txs:dateTime') X(f'{i}\t') X(f'{i}\t\tDateTime') X(f'{i}\t') X(f'{i}') hierarchical = 'true' if defn.get('hierarchical') is True else 'false' X(f'{i}{hierarchical}') X(f'{i}true') emit_standard_attributes(i, 'ChartOfCharacteristicTypes') X(f'{i}') X(f'{i}Auto') X(f'{i}InDialog') quick_choice = 'true' if defn.get('quickChoice') is True else 'false' X(f'{i}{quick_choice}') X(f'{i}BothWays') X(f'{i}') X(f'{i}\tChartOfCharacteristicTypes.{obj_name}.StandardAttribute.Description') X(f'{i}\tChartOfCharacteristicTypes.{obj_name}.StandardAttribute.Code') X(f'{i}') X(f'{i}Begin') X(f'{i}DontUse') X(f'{i}Directly') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}false') X(f'{i}') X(f'{i}') data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic') X(f'{i}{data_lock_control_mode}') full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use') X(f'{i}{full_text_search}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}DontUse') X(f'{i}Auto') X(f'{i}DontUse') X(f'{i}false') X(f'{i}false') def emit_document_journal_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') default_form = str(defn['defaultForm']) if defn.get('defaultForm') else '' if default_form: X(f'{i}{default_form}') else: X(f'{i}') aux_form = str(defn['auxiliaryForm']) if defn.get('auxiliaryForm') else '' if aux_form: X(f'{i}{aux_form}') else: X(f'{i}') X(f'{i}true') reg_docs = list(defn.get('registeredDocuments', [])) if reg_docs: X(f'{i}') 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{rd_str}') X(f'{i}') else: X(f'{i}') emit_standard_attributes(i, 'DocumentJournal') X(f'{i}') X(f'{i}') X(f'{i}') def emit_chart_of_accounts_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') X(f'{i}true') ext_dim_types = str(defn['extDimensionTypes']) if defn.get('extDimensionTypes') else '' if ext_dim_types: X(f'{i}{ext_dim_types}') else: X(f'{i}') max_ext_dim = str(defn['maxExtDimensionCount']) if defn.get('maxExtDimensionCount') is not None else '3' X(f'{i}{max_ext_dim}') code_mask = str(defn['codeMask']) if defn.get('codeMask') else '' if code_mask: X(f'{i}{code_mask}') else: X(f'{i}') 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}{code_length}') X(f'{i}{description_length}') X(f'{i}{code_series}') X(f'{i}false') X(f'{i}AsDescription') X(f'{i}{auto_order}') X(f'{i}{order_length}') X(f'{i}InDialog') emit_standard_attributes(i, 'ChartOfAccounts') X(f'{i}') X(f'{i}\t') X(f'{i}\t\t') for st_attr in ['TurnoversOnly', 'Predefined', 'ExtDimensionType', 'LineNumber']: emit_standard_attribute(f'{i}\t\t\t', st_attr) X(f'{i}\t\t') X(f'{i}\t') X(f'{i}') X(f'{i}') X(f'{i}Auto') quick_choice = 'true' if defn.get('quickChoice') is True else 'false' X(f'{i}{quick_choice}') X(f'{i}BothWays') X(f'{i}') X(f'{i}\tChartOfAccounts.{obj_name}.StandardAttribute.Description') X(f'{i}\tChartOfAccounts.{obj_name}.StandardAttribute.Code') X(f'{i}') X(f'{i}Begin') X(f'{i}DontUse') X(f'{i}Directly') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}false') X(f'{i}') X(f'{i}') data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic') X(f'{i}{data_lock_control_mode}') full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use') X(f'{i}{full_text_search}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}DontUse') X(f'{i}Auto') X(f'{i}DontUse') X(f'{i}false') X(f'{i}false') def emit_accounting_register_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') X(f'{i}true') X(f'{i}') X(f'{i}') chart_of_accounts = str(defn['chartOfAccounts']) if defn.get('chartOfAccounts') else '' if chart_of_accounts: X(f'{i}{chart_of_accounts}') else: X(f'{i}') correspondence = 'true' if defn.get('correspondence') is True else 'false' X(f'{i}{correspondence}') period_adj_len = str(defn['periodAdjustmentLength']) if defn.get('periodAdjustmentLength') is not None else '0' X(f'{i}{period_adj_len}') X(f'{i}false') emit_standard_attributes(i, 'AccountingRegister') data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic') X(f'{i}{data_lock_control_mode}') full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use') X(f'{i}{full_text_search}') X(f'{i}') X(f'{i}') X(f'{i}') def emit_chart_of_calculation_types_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') X(f'{i}true') 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}{code_length}') X(f'{i}{code_type}') X(f'{i}{code_allowed_length}') X(f'{i}{description_length}') X(f'{i}AsDescription') dependence = get_enum_prop('DependenceOnCalculationTypes', 'dependenceOnCalculationTypes', 'DontUse') X(f'{i}{dependence}') base_types = list(defn.get('baseCalculationTypes', [])) if base_types: X(f'{i}') for bt in base_types: X(f'{i}\t{bt}') X(f'{i}') else: X(f'{i}') action_period_use = 'true' if defn.get('actionPeriodUse') is True else 'false' X(f'{i}{action_period_use}') emit_standard_attributes(i, 'ChartOfCalculationTypes') X(f'{i}') X(f'{i}Auto') X(f'{i}InDialog') quick_choice = 'true' if defn.get('quickChoice') is True else 'false' X(f'{i}{quick_choice}') X(f'{i}BothWays') X(f'{i}') X(f'{i}\tChartOfCalculationTypes.{obj_name}.StandardAttribute.Description') X(f'{i}\tChartOfCalculationTypes.{obj_name}.StandardAttribute.Code') X(f'{i}') X(f'{i}Begin') X(f'{i}DontUse') X(f'{i}Directly') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}false') X(f'{i}') X(f'{i}') data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic') X(f'{i}{data_lock_control_mode}') full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use') X(f'{i}{full_text_search}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}DontUse') X(f'{i}Auto') def emit_calculation_register_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') X(f'{i}true') X(f'{i}') X(f'{i}') chart_of_calc_types = str(defn['chartOfCalculationTypes']) if defn.get('chartOfCalculationTypes') else '' if chart_of_calc_types: X(f'{i}{chart_of_calc_types}') else: X(f'{i}') periodicity = get_enum_prop('InformationRegisterPeriodicity', 'periodicity', 'Month') X(f'{i}{periodicity}') action_period = 'true' if defn.get('actionPeriod') is True else 'false' X(f'{i}{action_period}') base_period = 'true' if defn.get('basePeriod') is True else 'false' X(f'{i}{base_period}') schedule = str(defn['schedule']) if defn.get('schedule') else '' if schedule: X(f'{i}{schedule}') else: X(f'{i}') schedule_value = str(defn['scheduleValue']) if defn.get('scheduleValue') else '' if schedule_value: X(f'{i}{schedule_value}') else: X(f'{i}') schedule_date = str(defn['scheduleDate']) if defn.get('scheduleDate') else '' if schedule_date: X(f'{i}{schedule_date}') else: X(f'{i}') X(f'{i}false') emit_standard_attributes(i, 'CalculationRegister') data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic') X(f'{i}{data_lock_control_mode}') full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use') X(f'{i}{full_text_search}') X(f'{i}') X(f'{i}') X(f'{i}') def emit_business_process_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') X(f'{i}true') edit_type = get_enum_prop('EditType', 'editType', 'InDialog') X(f'{i}{edit_type}') 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}{number_type}') X(f'{i}{number_length}') X(f'{i}{number_allowed_length}') X(f'{i}{check_unique}') X(f'{i}{autonumbering}') emit_standard_attributes(i, 'BusinessProcess') X(f'{i}') task_ref = str(defn['task']) if defn.get('task') else '' if task_ref: X(f'{i}{task_ref}') else: X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}\tBusinessProcess.{obj_name}.StandardAttribute.Number') X(f'{i}') X(f'{i}DontUse') X(f'{i}Begin') X(f'{i}DontUse') X(f'{i}Directly') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}false') X(f'{i}') data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic') X(f'{i}{data_lock_control_mode}') full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use') X(f'{i}{full_text_search}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}Auto') X(f'{i}DontUse') X(f'{i}false') X(f'{i}false') def emit_task_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') X(f'{i}true') 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}{number_type}') X(f'{i}{number_length}') X(f'{i}{number_allowed_length}') X(f'{i}{check_unique}') X(f'{i}{autonumbering}') X(f'{i}{task_number_auto_prefix}') X(f'{i}{description_length}') addressing = str(defn['addressing']) if defn.get('addressing') else '' if addressing: X(f'{i}{addressing}') else: X(f'{i}') main_addressing = str(defn['mainAddressingAttribute']) if defn.get('mainAddressingAttribute') else '' if main_addressing: X(f'{i}{main_addressing}') else: X(f'{i}') current_performer = str(defn['currentPerformer']) if defn.get('currentPerformer') else '' if current_performer: X(f'{i}{current_performer}') else: X(f'{i}') emit_standard_attributes(i, 'Task') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}\tTask.{obj_name}.StandardAttribute.Number') X(f'{i}') X(f'{i}DontUse') X(f'{i}Begin') X(f'{i}DontUse') X(f'{i}Directly') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}false') X(f'{i}') data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic') X(f'{i}{data_lock_control_mode}') full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use') X(f'{i}{full_text_search}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}') X(f'{i}Auto') X(f'{i}DontUse') X(f'{i}false') X(f'{i}false') def emit_http_service_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') root_url = str(defn['rootURL']) if defn.get('rootURL') else obj_name.lower() X(f'{i}{esc_xml(root_url)}') reuse_sessions = get_enum_prop('ReuseSessions', 'reuseSessions', 'DontUse') X(f'{i}{reuse_sessions}') session_max_age = str(defn['sessionMaxAge']) if defn.get('sessionMaxAge') is not None else '20' X(f'{i}{session_max_age}') def emit_web_service_properties(indent): i = indent X(f'{i}{esc_xml(obj_name)}') emit_mltext(i, 'Synonym', synonym) X(f'{i}') namespace = str(defn['namespace']) if defn.get('namespace') else '' X(f'{i}{esc_xml(namespace)}') xdto_packages = str(defn['xdtoPackages']) if defn.get('xdtoPackages') else '' if xdto_packages: X(f'{i}{xdto_packages}') else: X(f'{i}') reuse_sessions = get_enum_prop('ReuseSessions', 'reuseSessions', 'DontUse') X(f'{i}{reuse_sessions}') session_max_age = str(defn['sessionMaxAge']) if defn.get('sessionMaxAge') is not None else '20' X(f'{i}{session_max_age}') # --- 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}') X(f'{indent}\t') X(f'{indent}\t\t{esc_xml(name)}') emit_mltext(f'{indent}\t\t', 'Synonym', col_synonym) X(f'{indent}\t\t') X(f'{indent}\t\t{indexing}') if references: X(f'{indent}\t\t') for ref in references: X(f'{indent}\t\t\t{ref}') X(f'{indent}\t\t') else: X(f'{indent}\t\t') X(f'{indent}\t') X(f'{indent}') def emit_accounting_flag(indent, flag_name): uid = new_uuid() flag_synonym = split_camel_case(flag_name) X(f'{indent}') X(f'{indent}\t') X(f'{indent}\t\t{esc_xml(flag_name)}') emit_mltext(f'{indent}\t\t', 'Synonym', flag_synonym) X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\t\txs:boolean') X(f'{indent}\t\t') X(f'{indent}\t\tfalse') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tfalse') X(f'{indent}\t\t') X(f'{indent}\t\tfalse') X(f'{indent}\t\tfalse') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tDontCheck') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tAuto') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tAuto') X(f'{indent}\t') X(f'{indent}') def emit_ext_dimension_accounting_flag(indent, flag_name): uid = new_uuid() flag_synonym = split_camel_case(flag_name) X(f'{indent}') X(f'{indent}\t') X(f'{indent}\t\t{esc_xml(flag_name)}') emit_mltext(f'{indent}\t\t', 'Synonym', flag_synonym) X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\t\txs:boolean') X(f'{indent}\t\t') X(f'{indent}\t\tfalse') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tfalse') X(f'{indent}\t\t') X(f'{indent}\t\tfalse') X(f'{indent}\t\tfalse') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tDontCheck') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tAuto') X(f'{indent}\t\t') X(f'{indent}\t\t') X(f'{indent}\t\tAuto') X(f'{indent}\t') X(f'{indent}') 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}') X(f'{indent}\t') X(f'{indent}\t\t{esc_xml(tmpl_name)}') emit_mltext(f'{indent}\t\t', 'Synonym', tmpl_synonym) X(f'{indent}\t\t') X(f'{indent}\t') if methods: X(f'{indent}\t') 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') X(f'{indent}\t\t\t') X(f'{indent}\t\t\t\t{esc_xml(method_name)}') emit_mltext(f'{indent}\t\t\t\t', 'Synonym', method_synonym) X(f'{indent}\t\t\t\t{http_method}') X(f'{indent}\t\t\t\t{esc_xml(handler)}') X(f'{indent}\t\t\t') X(f'{indent}\t\t') X(f'{indent}\t') else: X(f'{indent}\t') X(f'{indent}') 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}') X(f'{indent}\t') X(f'{indent}\t\t{esc_xml(op_name)}') emit_mltext(f'{indent}\t\t', 'Synonym', op_synonym) X(f'{indent}\t\t') X(f'{indent}\t\t{return_type}') X(f'{indent}\t\t{nillable}') X(f'{indent}\t\t{transactioned}') X(f'{indent}\t\t{esc_xml(handler)}') X(f'{indent}\t') if params: X(f'{indent}\t') 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') X(f'{indent}\t\t\t') X(f'{indent}\t\t\t\t{esc_xml(param_name)}') emit_mltext(f'{indent}\t\t\t\t', 'Synonym', param_synonym) X(f'{indent}\t\t\t\t{param_type}') X(f'{indent}\t\t\t\t{param_nillable}') X(f'{indent}\t\t\t\t{param_dir}') X(f'{indent}\t\t\t') X(f'{indent}\t\t') X(f'{indent}\t') else: X(f'{indent}\t') X(f'{indent}') 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}') X(f'{indent}\t') X(f'{indent}\t\t{esc_xml(name)}') emit_mltext(f'{indent}\t\t', 'Synonym', attr_synonym) X(f'{indent}\t\t') if type_str: emit_value_type(f'{indent}\t\t', type_str) else: X(f'{indent}\t\t') X(f'{indent}\t\t\txs:string') X(f'{indent}\t\t') if addressing_dimension: X(f'{indent}\t\t{addressing_dimension}') else: X(f'{indent}\t\t') X(f'{indent}\t\t{indexing}') X(f'{indent}\t\tUse') X(f'{indent}\t\tUse') X(f'{indent}\t') X(f'{indent}') # --------------------------------------------------------------------------- # 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']+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('') X(f'') X(f'\t<{obj_type} uuid="{obj_uuid}">') # InternalInfo emit_internal_info('\t\t', obj_type, obj_name) # Properties X('\t\t') 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') # 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') 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') else: X('\t\t') # --- 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') for v in values: emit_enum_value('\t\t\t', v) X('\t\t') else: X('\t\t') # --- 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') 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') else: X('\t\t') # --- DocumentJournal: columns --- if obj_type == 'DocumentJournal': columns = list(defn.get('columns', [])) if columns: has_children = True X('\t\t') for col in columns: emit_column('\t\t\t', col) X('\t\t') else: X('\t\t') # --- 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') for tmpl_name in sorted(url_tmpl_order): emit_url_template('\t\t\t', tmpl_name, url_templates[tmpl_name]) X('\t\t') else: X('\t\t') # --- 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') for op_name in sorted(op_order): emit_operation('\t\t\t', op_name, operations[op_name]) X('\t\t') else: X('\t\t') # --- CommonModule: no ChildObjects --- X(f'\t') X('') 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'\r\n\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'\r\n\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(""): raw = raw.replace("", '', 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} added to ChildObjects') elif reg_result == 'already': print(f' Configuration.xml: <{child_tag}>{obj_name} already registered') elif reg_result == 'no-childobj': print('WARNING: Configuration.xml found but 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"')