#!/usr/bin/env python3
# stub-db-create v1.3 — Create temp 1C infobase with metadata stubs for EPF/ERF build
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
import random
import re
import subprocess
import sys
import tempfile
import uuid
IBCMD_NOUSER_HINT = (
"[ibcmd] No -UserName/-Password given; the infobase may require authentication. "
"On Windows ibcmd reads credentials from the console (stdin is ignored), so this "
"call may block instead of failing. If it does not return promptly, abort and "
"re-run with -UserName and -Password.\n"
)
def run_ibcmd(cmd, has_username=False, warn_no_user=True):
"""Run an ibcmd command non-interactively.
input="" closes stdin (EOF) so ibcmd's auth prompt fast-fails instead of hanging.
On Windows without -UserName ibcmd reads the console directly and may still block —
that residual case is flagged via IBCMD_NOUSER_HINT (model-facing).
"""
if warn_no_user and os.name == "nt" and not has_username:
sys.stderr.write(IBCMD_NOUSER_HINT)
sys.stderr.flush()
return subprocess.run(cmd, input="", capture_output=True, encoding="utf-8", errors="replace")
def new_uuid():
return str(uuid.uuid4())
def scan_ref_types(source_dir):
"""Scan XML files for reference/object/recordset types. Returns {metaType: {name: True}}."""
type_map = {}
ref_pattern = re.compile(
r'(?:cfg:|d\dp1:)(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef'
r'|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef'
r'|ExchangePlanRef|BusinessProcessRef|TaskRef)'
r'\.([A-Za-z\u0400-\u04FF\d_]+)'
)
obj_pattern = re.compile(
r'(?:cfg:|d\dp1:)(CatalogObject|DocumentObject|ChartOfAccountsObject'
r'|ChartOfCharacteristicTypesObject|ChartOfCalculationTypesObject'
r'|ExchangePlanObject|BusinessProcessObject|TaskObject)'
r'\.([A-Za-z\u0400-\u04FF\d_]+)'
)
rs_pattern = re.compile(
r'(?:cfg:|d\dp1:)(InformationRegisterRecordSet|AccumulationRegisterRecordSet'
r'|AccountingRegisterRecordSet|CalculationRegisterRecordSet)'
r'\.([A-Za-z\u0400-\u04FF\d_]+)'
)
char_pattern = re.compile(r'cfg:Characteristic\.([A-Za-z\u0400-\u04FF\d_]+)')
dt_pattern = re.compile(r'cfg:DefinedType\.([A-Za-z\u0400-\u04FF\d_]+)')
ref_map = {
'CatalogRef': 'Catalog', 'DocumentRef': 'Document', 'EnumRef': 'Enum',
'ChartOfAccountsRef': 'ChartOfAccounts',
'ChartOfCharacteristicTypesRef': 'ChartOfCharacteristicTypes',
'ChartOfCalculationTypesRef': 'ChartOfCalculationTypes',
'ExchangePlanRef': 'ExchangePlan', 'BusinessProcessRef': 'BusinessProcess', 'TaskRef': 'Task',
}
obj_map = {
'CatalogObject': 'Catalog', 'DocumentObject': 'Document',
'ChartOfAccountsObject': 'ChartOfAccounts',
'ChartOfCharacteristicTypesObject': 'ChartOfCharacteristicTypes',
'ChartOfCalculationTypesObject': 'ChartOfCalculationTypes',
'ExchangePlanObject': 'ExchangePlan', 'BusinessProcessObject': 'BusinessProcess', 'TaskObject': 'Task',
}
rs_map = {
'InformationRegisterRecordSet': 'InformationRegister',
'AccumulationRegisterRecordSet': 'AccumulationRegister',
'AccountingRegisterRecordSet': 'AccountingRegister',
'CalculationRegisterRecordSet': 'CalculationRegister',
}
for dirpath, _, filenames in os.walk(source_dir):
for fn in filenames:
if not fn.endswith('.xml'):
continue
fp = os.path.join(dirpath, fn)
try:
with open(fp, 'r', encoding='utf-8-sig') as f:
content = f.read()
except Exception:
continue
for m in ref_pattern.finditer(content):
mt = ref_map[m.group(1)]
type_map.setdefault(mt, {})[m.group(2)] = True
for m in obj_pattern.finditer(content):
mt = obj_map[m.group(1)]
type_map.setdefault(mt, {})[m.group(2)] = True
for m in rs_pattern.finditer(content):
mt = rs_map[m.group(1)]
type_map.setdefault(mt, {})[m.group(2)] = True
for m in char_pattern.finditer(content):
type_map.setdefault('ChartOfCharacteristicTypes', {})[m.group(1)] = True
for m in dt_pattern.finditer(content):
type_map.setdefault('DefinedType', {})[m.group(1)] = True
return type_map
def scan_register_columns(source_dir):
"""Scan Form.xml for register record set columns referenced via DataPath.
Returns {"RegisterType.RegisterName": {"col1": True, "col2": True}}."""
import xml.etree.ElementTree as ET
register_columns = {}
std_cols = {'LineNumber', 'Period', 'Recorder', 'Active', 'RecordType'}
rs_type_map = {
'InformationRegisterRecordSet': 'InformationRegister',
'AccumulationRegisterRecordSet': 'AccumulationRegister',
'AccountingRegisterRecordSet': 'AccountingRegister',
'CalculationRegisterRecordSet': 'CalculationRegister',
}
rs_pattern = re.compile(
r'^(?:cfg:|d\dp1:)(InformationRegisterRecordSet|AccumulationRegisterRecordSet'
r'|AccountingRegisterRecordSet|CalculationRegisterRecordSet)\.(.+)$'
)
dp_pattern = re.compile(r'([A-Za-z\u0400-\u04FF\d_]+)\.([A-Za-z\u0400-\u04FF\d_]+)')
ns = {
'v8': 'http://v8.1c.ru/8.1/data/core',
'f': 'http://v8.1c.ru/8.3/xcf/logform',
}
for dirpath, _, filenames in os.walk(source_dir):
for fn in filenames:
if fn != 'Form.xml':
continue
fp = os.path.join(dirpath, fn)
try:
with open(fp, 'r', encoding='utf-8-sig') as fh:
content = fh.read()
except Exception:
continue
if '' not in content:
continue
# Parse form attributes to find register recordset types
reg_attr_map = {} # formAttrName -> "RegisterType.RegisterName"
try:
root = ET.fromstring(content)
for attr_node in root.iter('{http://v8.1c.ru/8.3/xcf/logform}Attribute'):
attr_name = attr_node.get('name', '')
for type_node in attr_node.iter('{http://v8.1c.ru/8.1/data/core}Type'):
m = rs_pattern.match(type_node.text or '')
if m:
reg_type = rs_type_map[m.group(1)]
reg_key = f"{reg_type}.{m.group(2)}"
reg_attr_map[attr_name] = reg_key
register_columns.setdefault(reg_key, {})
except Exception:
continue
# Find DataPath references like "AttrName.ColumnName"
for m in dp_pattern.finditer(content):
attr_name, col_name = m.group(1), m.group(2)
if attr_name in reg_attr_map and col_name not in std_cols:
register_columns[reg_attr_map[attr_name]][col_name] = True
return register_columns
NS = (
'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" version="2.17"'
)
CLASS_IDS = [
"9cd510cd-abfc-11d4-9434-004095e12fc7",
"9fcd25a0-4822-11d4-9414-008048da11f9",
"e3687481-0a87-462c-a166-9f34594f9bba",
"9de14907-ec23-4a07-96f0-85521cb6b53b",
"51f2d5d8-ea4d-4064-8892-82951750031e",
"e68182ea-4237-4383-967f-90c1e3370bc7",
"fb282519-d103-4dd3-bc12-cb271d631dfc",
]
GT_DEFS = {
'Catalog': [('CatalogObject','Object'),('CatalogRef','Ref'),('CatalogSelection','Selection'),('CatalogList','List'),('CatalogManager','Manager')],
'Document': [('DocumentObject','Object'),('DocumentRef','Ref'),('DocumentSelection','Selection'),('DocumentList','List'),('DocumentManager','Manager')],
'Enum': [('EnumRef','Ref'),('EnumManager','Manager'),('EnumList','List')],
'ChartOfAccounts': [('ChartOfAccountsObject','Object'),('ChartOfAccountsRef','Ref'),('ChartOfAccountsSelection','Selection'),('ChartOfAccountsList','List'),('ChartOfAccountsManager','Manager')],
'ChartOfCharacteristicTypes': [('ChartOfCharacteristicTypesObject','Object'),('ChartOfCharacteristicTypesRef','Ref'),('ChartOfCharacteristicTypesSelection','Selection'),('ChartOfCharacteristicTypesList','List'),('Characteristic','Characteristic'),('ChartOfCharacteristicTypesManager','Manager')],
'ChartOfCalculationTypes': [('ChartOfCalculationTypesObject','Object'),('ChartOfCalculationTypesRef','Ref'),('ChartOfCalculationTypesSelection','Selection'),('ChartOfCalculationTypesList','List'),('ChartOfCalculationTypesManager','Manager')],
'ExchangePlan': [('ExchangePlanObject','Object'),('ExchangePlanRef','Ref'),('ExchangePlanSelection','Selection'),('ExchangePlanList','List'),('ExchangePlanManager','Manager')],
'BusinessProcess': [('BusinessProcessObject','Object'),('BusinessProcessRef','Ref'),('BusinessProcessSelection','Selection'),('BusinessProcessList','List'),('BusinessProcessManager','Manager')],
'Task': [('TaskObject','Object'),('TaskRef','Ref'),('TaskSelection','Selection'),('TaskList','List'),('TaskManager','Manager')],
'InformationRegister': [('InformationRegisterRecord','Record'),('InformationRegisterManager','Manager'),('InformationRegisterSelection','Selection'),('InformationRegisterList','List'),('InformationRegisterRecordSet','RecordSet'),('InformationRegisterRecordKey','RecordKey'),('InformationRegisterRecordManager','RecordManager')],
'AccumulationRegister': [('AccumulationRegisterRecord','Record'),('AccumulationRegisterManager','Manager'),('AccumulationRegisterSelection','Selection'),('AccumulationRegisterList','List'),('AccumulationRegisterRecordSet','RecordSet'),('AccumulationRegisterRecordKey','RecordKey')],
'AccountingRegister': [('AccountingRegisterRecord','Record'),('AccountingRegisterManager','Manager'),('AccountingRegisterSelection','Selection'),('AccountingRegisterExtDimensions','ExtDimensions'),('AccountingRegisterList','List'),('AccountingRegisterRecordSet','RecordSet'),('AccountingRegisterRecordKey','RecordKey')],
'CalculationRegister': [('CalculationRegisterRecord','Record'),('CalculationRegisterManager','Manager'),('CalculationRegisterSelection','Selection'),('CalculationRegisterList','List'),('CalculationRegisterRecordSet','RecordSet'),('CalculationRegisterRecordKey','RecordKey')],
'DefinedType': [('DefinedType','DefinedType')],
}
META_INFO = {
'Catalog': ('Catalog', 'Catalogs'),
'Document': ('Document', 'Documents'),
'Enum': ('Enum', 'Enums'),
'ChartOfAccounts': ('ChartOfAccounts', 'ChartsOfAccounts'),
'ChartOfCharacteristicTypes': ('ChartOfCharacteristicTypes', 'ChartsOfCharacteristicTypes'),
'ChartOfCalculationTypes': ('ChartOfCalculationTypes', 'ChartsOfCalculationTypes'),
'ExchangePlan': ('ExchangePlan', 'ExchangePlans'),
'BusinessProcess': ('BusinessProcess', 'BusinessProcesses'),
'Task': ('Task', 'Tasks'),
'InformationRegister': ('InformationRegister', 'InformationRegisters'),
'AccumulationRegister': ('AccumulationRegister', 'AccumulationRegisters'),
'AccountingRegister': ('AccountingRegister', 'AccountingRegisters'),
'CalculationRegister': ('CalculationRegister', 'CalculationRegisters'),
'DefinedType': ('DefinedType', 'DefinedTypes'),
}
STD_ATTRS_BY_TYPE = {
'Catalog': ['PredefinedDataName','Predefined','Ref','DeletionMark','IsFolder','Owner','Parent','Description','Code'],
'Document': ['Posted','Ref','DeletionMark','Date','Number'],
'Enum': ['Order','Ref'],
'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'],
'ExchangePlan': ['Ref','DeletionMark','Code','Description','ThisNode','SentNo','ReceivedNo'],
'BusinessProcess': ['Ref','DeletionMark','Date','Number','Started','Completed','HeadTask'],
'Task': ['Ref','DeletionMark','Date','Number','Executed','Description','RoutePoint','BusinessProcess'],
'InformationRegister': ['Active','LineNumber','Recorder','Period'],
'AccumulationRegister': ['Active','LineNumber','Recorder','Period'],
'AccountingRegister': ['Active','Period','Recorder','LineNumber','Account'],
'CalculationRegister': ['Active','Recorder','LineNumber','RegistrationPeriod','CalculationType','ReversingEntry'],
}
STD_ATTR_BODY = """\t\t\t\t
\t\t\t\tDontCheck
\t\t\t\tfalse
\t\t\t\tfalse
\t\t\t\tAuto
\t\t\t\t
\t\t\t\t
\t\t\t\tfalse
\t\t\t\t
\t\t\t\t
\t\t\t\tAuto
\t\t\t\tAuto
\t\t\t\t
\t\t\t\tfalse
\t\t\t\tUse
\t\t\t\tfalse
\t\t\t\t
\t\t\t\t
\t\t\t\t
\t\t\t\tUse
\t\t\t\t
\t\t\t\t
\t\t\t\t
\t\t\t\t"""
def build_std_attrs(meta_type):
attrs = STD_ATTRS_BY_TYPE.get(meta_type)
if not attrs:
return ''
lines = ['\t\t\t']
for a in attrs:
lines.append(f'\t\t\t\t')
lines.append(STD_ATTR_BODY)
lines.append(f'\t\t\t\t')
lines.append('\t\t\t')
return '\n'.join(lines) + '\n'
def build_internal_info(meta_type, obj_name):
gts = GT_DEFS.get(meta_type)
if not gts:
return ''
lines = ['\t\t']
if meta_type == 'ExchangePlan':
lines.append(f'\t\t\t{new_uuid()}')
for prefix, cat in gts:
full = f'{prefix}.{obj_name}'
lines.append(f'\t\t\t')
lines.append(f'\t\t\t\t{new_uuid()}')
lines.append(f'\t\t\t\t{new_uuid()}')
lines.append(f'\t\t\t')
lines.append('\t\t')
return '\n'.join(lines)
# Properties templates per type — returns the Properties content (without tags)
PROPS = {}
PROPS['Catalog'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\tHierarchyFoldersAndItems
\t\t\tfalse
\t\t\t2
\t\t\ttrue
\t\t\tfalse
\t\t\t
\t\t\tToItems
\t\t\t9
\t\t\t25
\t\t\tString
\t\t\tVariable
\t\t\tWholeCatalog
\t\t\tfalse
\t\t\ttrue
\t\t\tAsDescription
{sa}\t\t\t
\t\t\tAuto
\t\t\tInDialog
\t\t\ttrue
\t\t\tBothWays
\t\t\t
\t\t\tBegin
\t\t\tDontUse
\t\t\tDirectly
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tDontUse
\t\t\tAuto
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['Enum'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
{sa}\t\t\t
\t\t\ttrue
\t\t\tBothWays
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tAuto"""
PROPS['InformationRegister'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\tInDialog
\t\t\t
\t\t\t
\t\t\t
\t\t\t
{sa}\t\t\tNonperiodical
\t\t\tIndependent
\t\t\tfalse
\t\t\tfalse
\t\t\tAutomatic
\t\t\tUse
\t\t\tfalse
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['AccumulationRegister'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tBalance
\t\t\tfalse
{sa}\t\t\tAutomatic
\t\t\tUse
\t\t\ttrue
\t\t\t
\t\t\t
\t\t\t"""
PROPS['AccountingRegister'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\tfalse
{sa}\t\t\tAutomatic
\t\t\tUse
\t\t\ttrue
\t\t\t
\t\t\t
\t\t\t"""
PROPS['CalculationRegister'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
{sa}\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t"""
PROPS['ChartOfAccounts'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t20
\t\t\t100
\t\t\tWholeCatalog
\t\t\tfalse
\t\t\ttrue
\t\t\tAsDescription
{sa}\t\t\t
\t\t\tAuto
\t\t\tInDialog
\t\t\ttrue
\t\t\tBothWays
\t\t\t
\t\t\tBegin
\t\t\tDontUse
\t\t\tDirectly
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\ttrue
\t\t\t5
\t\t\t0
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tDontUse
\t\t\tAuto
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['ChartOfCharacteristicTypes'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t9
\t\t\tVariable
\t\t\t25
\t\t\tfalse
\t\t\ttrue
\t\t\tAsDescription
\t\t\t
\t\t\t
\t\t\t\txs:boolean
\t\t\t\txs:string
\t\t\t\t
\t\t\t\t\t0
\t\t\t\t\tVariable
\t\t\t\t
\t\t\t\txs:decimal
\t\t\t\t
\t\t\t\t\t15
\t\t\t\t\t2
\t\t\t\t\tAny
\t\t\t\t
\t\t\t\txs:dateTime
\t\t\t\t
\t\t\t\t\tDateTime
\t\t\t\t
\t\t\t
\t\t\tfalse
\t\t\ttrue
{sa}\t\t\t
\t\t\tAuto
\t\t\tInDialog
\t\t\ttrue
\t\t\tBothWays
\t\t\t
\t\t\tBegin
\t\t\tDontUse
\t\t\tDirectly
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tDontUse
\t\t\tAuto
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['ChartOfCalculationTypes'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t9
\t\t\t25
\t\t\tString
\t\t\tVariable
\t\t\tWholeCatalog
\t\t\tfalse
\t\t\ttrue
\t\t\tAsDescription
{sa}\t\t\t
\t\t\tAuto
\t\t\tInDialog
\t\t\ttrue
\t\t\tBothWays
\t\t\t
\t\t\tBegin
\t\t\tDontUse
\t\t\tDirectly
\t\t\tNotDepend
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tDontUse
\t\t\tAuto
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['ExchangePlan'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t9
\t\t\t25
\t\t\tVariable
{sa}\t\t\tAsDescription
\t\t\t
\t\t\tAuto
\t\t\tInDialog
\t\t\ttrue
\t\t\tBothWays
\t\t\t
\t\t\tBegin
\t\t\tDontUse
\t\t\tDirectly
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tDontUse
\t\t\tAuto
\t\t\tfalse
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['BusinessProcess'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\tString
\t\t\t11
\t\t\tVariable
\t\t\tYear
\t\t\tfalse
\t\t\ttrue
{sa}\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tAuto
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['Task'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\tString
\t\t\t11
\t\t\tVariable
\t\t\tYear
\t\t\tfalse
\t\t\ttrue
\t\t\t25
{sa}\t\t\t
\t\t\t
\t\t\tBegin
\t\t\tDontUse
\t\t\tDirectly
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tAuto
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
PROPS['DefinedType'] = lambda n, sa: f"""\t\t\t{n}
\t\t\t
\t\t\t
\t\t\t
\t\t\t\txs:string
\t\t\t\t
\t\t\t\t\t0
\t\t\t\t\tVariable
\t\t\t\t
\t\t\t"""
def build_doc_props(obj_name, std_attrs, reg_records_xml):
return f"""\t\t\t{obj_name}
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\t
\t\t\tString
\t\t\t11
\t\t\tVariable
\t\t\tYear
\t\t\tfalse
\t\t\ttrue
{std_attrs}\t\t\t
\t\t\t
\t\t\t
\t\t\tDontUse
\t\t\tBegin
\t\t\tDontUse
\t\t\tDirectly
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tAllow
\t\t\tDeny
\t\t\tAutoDelete
\t\t\tWriteModified
\t\t\tAutoFill
\t\t\t{reg_records_xml}
\t\t\ttrue
\t\t\ttrue
\t\t\tfalse
\t\t\t
\t\t\tAutomatic
\t\t\tUse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tAuto
\t\t\tDontUse
\t\t\tfalse
\t\t\tfalse"""
def write_bom(path, content):
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
f.write(content)
def main():
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
parser = argparse.ArgumentParser(description='Create temp 1C infobase with metadata stubs')
parser.add_argument('-SourceDir', required=True)
parser.add_argument('-V8Path', required=True)
parser.add_argument('-TempBasePath', default='')
args = parser.parse_args()
type_map = scan_ref_types(args.SourceDir)
register_columns = scan_register_columns(args.SourceDir)
has_ref_types = len(type_map) > 0
temp_base = args.TempBasePath or os.path.join(tempfile.gettempdir(), f'epf_stub_db_{random.randint(0,999999)}')
# Add registrator stub document if needed
registrator_types = ['AccumulationRegister', 'AccountingRegister', 'CalculationRegister']
needs_registrator = any(rt in type_map and len(type_map[rt]) > 0 for rt in registrator_types)
if needs_registrator:
type_map.setdefault('Document', {})['\u0417\u0430\u0433\u043b\u0443\u0448\u043a\u0430\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430'] = True # ЗаглушкаРегистратора
if has_ref_types:
cfg_dir = os.path.join(temp_base, 'cfg')
os.makedirs(cfg_dir, exist_ok=True)
# Configuration.xml
uuid_cfg = new_uuid()
uuid_lang = new_uuid()
co_ids = [new_uuid() for _ in range(7)]
co_xml = ''
for i in range(7):
co_xml += f'\n\t\t\t\n\t\t\t\t{CLASS_IDS[i]}\n\t\t\t\t{co_ids[i]}\n\t\t\t'
child_xml = '\n\t\t\t\u0420\u0443\u0441\u0441\u043a\u0438\u0439' # Русский
for meta_type, names in type_map.items():
if meta_type not in META_INFO:
continue
tag = META_INFO[meta_type][0]
for name in names:
child_xml += f'\n\t\t\t<{tag}>{name}{tag}>'
cfg_xml = f"""
\t
\t\t{co_xml}
\t\t
\t\t
\t\t\tStubConfig
\t\t\t
\t\t\t
\t\t\t
\t\t\tVersion8_3_24
\t\t\tManagedApplication
\t\t\t
\t\t\t\tPlatformApplication
\t\t\t
\t\t\tRussian
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tfalse
\t\t\tfalse
\t\t\tfalse
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tNormal
\t\t\t
\t\t\t
\t\t\tLanguage.\u0420\u0443\u0441\u0441\u043a\u0438\u0439
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\t
\t\t\tManaged
\t\t\tNotAutoFree
\t\t\tDontUse
\t\t\tDontUse
\t\t\tTaxi
\t\t\tDontUse
\t\t\tVersion8_3_24
\t\t\t
\t\t
\t\t{child_xml}
\t\t
\t
"""
write_bom(os.path.join(cfg_dir, 'Configuration.xml'), cfg_xml)
# Language
lang_dir = os.path.join(cfg_dir, 'Languages')
os.makedirs(lang_dir, exist_ok=True)
lang_xml = f"""
\t
\t\t
\t\t\t\u0420\u0443\u0441\u0441\u043a\u0438\u0439
\t\t\t
\t\t\t\t
\t\t\t\t\tru
\t\t\t\t\t\u0420\u0443\u0441\u0441\u043a\u0438\u0439
\t\t\t\t
\t\t\t
\t\t\t
\t\t\tru
\t\t
\t
"""
write_bom(os.path.join(lang_dir, '\u0420\u0443\u0441\u0441\u043a\u0438\u0439.xml'), lang_xml)
# Metadata stubs
for meta_type, names in type_map.items():
if meta_type not in META_INFO:
continue
tag, dirname = META_INFO[meta_type]
obj_dir = os.path.join(cfg_dir, dirname)
os.makedirs(obj_dir, exist_ok=True)
for obj_name in names:
obj_uuid = new_uuid()
internal_xml = build_internal_info(meta_type, obj_name)
if internal_xml:
internal_xml = '\n' + internal_xml
sa = build_std_attrs(meta_type)
if meta_type == 'Document':
rr_xml = ''
if obj_name == '\u0417\u0430\u0433\u043b\u0443\u0448\u043a\u0430\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430': # ЗаглушкаРегистратора
rr_lines = []
for rt in registrator_types:
if rt in type_map:
for rn in type_map[rt]:
rr_lines.append(f'\t\t\t\t{rt}.{rn}')
if rr_lines:
rr_xml = '\n' + '\n'.join(rr_lines) + '\n\t\t\t'
props_xml = build_doc_props(obj_name, sa, rr_xml)
elif meta_type in PROPS:
props_xml = PROPS[meta_type](obj_name, sa)
else:
props_xml = f'\t\t\t{obj_name}\n\t\t\t\n\t\t\t'
# ChildObjects — varies by type
if meta_type == 'DefinedType':
child_obj_xml = ''
elif meta_type == 'InformationRegister':
reg_key = f'InformationRegister.{obj_name}'
cols = list(register_columns.get(reg_key, {}).keys()) or ['\u0417\u0430\u0433\u043b\u0443\u0448\u043a\u0430']
parts = []
for i, col in enumerate(cols):
u = new_uuid()
if i == 0:
parts.append(f"""\t\t\t
\t\t\t\t
\t\t\t\t\t{col}
\t\t\t\t\t
\t\t\t\t\txs:string10Variable
\t\t\t\t\tfalsefalse
\t\t\t\t\tfalsefalse
\t\t\t\t\t
\t\t\t\t\tfalseDontCheck
\t\t\t\t\tItems
\t\t\t\t\tAutoAutoAuto
\t\t\t\t\tfalsetruefalse
\t\t\t\t\tDontIndexUseUse
\t\t\t\t
\t\t\t""")
else:
parts.append(f"""\t\t\t
\t\t\t\t
\t\t\t\t\t{col}
\t\t\t\t\t
\t\t\t\t\txs:string10Variable
\t\t\t\t\tfalsefalse
\t\t\t\t\tfalsefalse
\t\t\t\t\t
\t\t\t\t\tfalseDontCheck
\t\t\t\t\tItems
\t\t\t\t\tAutoAutoAuto
\t\t\t\t\tDontIndexUseUse
\t\t\t\t
\t\t\t""")
child_obj_xml = '\n\t\t\n' + '\n'.join(parts) + '\n\t\t'
elif meta_type in ('AccumulationRegister', 'AccountingRegister', 'CalculationRegister'):
reg_key = f'{meta_type}.{obj_name}'
cols = list(register_columns.get(reg_key, {}).keys())
parts = []
# Required stub Resource
parts.append(f"""\t\t\t
\t\t\t\t
\t\t\t\t\t\u0417\u0430\u0433\u043b\u0443\u0448\u043a\u0430
\t\t\t\t\t
\t\t\t\t\txs:decimal152Any
\t\t\t\t\tfalsefalse
\t\t\t\t\tfalsefalse
\t\t\t\t\tDontCheck
\t\t\t\t\tItems
\t\t\t\t\tAutoAutoAuto
\t\t\t\t\tUse
\t\t\t\t
\t\t\t""")
# Form-referenced columns as Dimensions
for col in cols:
parts.append(f"""\t\t\t
\t\t\t\t
\t\t\t\t\t{col}
\t\t\t\t\t
\t\t\t\t\txs:string10Variable
\t\t\t\t\tfalsefalse
\t\t\t\t\tfalsefalse
\t\t\t\t\tDontCheck
\t\t\t\t\tItems
\t\t\t\t\tAutoAutoAuto
\t\t\t\t\tUse
\t\t\t\t
\t\t\t""")
child_obj_xml = '\n\t\t\n' + '\n'.join(parts) + '\n\t\t'
else:
child_obj_xml = '\n\t\t'
obj_xml = f"""
\t<{tag} uuid="{obj_uuid}">{internal_xml}
\t\t
{props_xml}
\t\t{child_obj_xml}
\t{tag}>
"""
write_bom(os.path.join(obj_dir, f'{obj_name}.xml'), obj_xml)
print(f'Generated stub configuration with {len(type_map)} metadata types')
if register_columns:
print('WARNING: Register column categories (Dimension/Resource/Attribute) are guessed. Form field bindings may not survive round-trip through a real database.')
# Stub via ibcmd (one call: create [--import --apply])
stub_engine = "ibcmd" if os.path.basename(args.V8Path).lower().startswith("ibcmd") else "1cv8"
if stub_engine == "ibcmd":
import shutil
print(f'Creating infobase (ibcmd): {temp_base}')
ib_data = tempfile.mkdtemp(prefix="stub_data_")
ib_args = [args.V8Path, 'infobase', 'create', f'--db-path={temp_base}', '--create-database']
if has_ref_types:
ib_args += [f'--import={os.path.join(temp_base, "cfg")}', '--apply', '--force']
ib_args.append(f'--data={ib_data}')
result = run_ibcmd(ib_args, warn_no_user=False)
shutil.rmtree(ib_data, ignore_errors=True)
if result.returncode != 0:
if result.stdout:
print(result.stdout)
if result.stderr:
print(result.stderr, file=sys.stderr)
print(f'Failed to create stub infobase (code: {result.returncode})', file=sys.stderr)
sys.exit(1)
if has_ref_types:
import shutil
shutil.rmtree(os.path.join(temp_base, 'cfg'), ignore_errors=True)
print(f'[OK] Stub database created: {temp_base}')
print(temp_base)
sys.exit(0)
# Create infobase
print(f'Creating infobase: {temp_base}')
result = subprocess.run(
[args.V8Path, 'CREATEINFOBASE', f'File={temp_base}', '/DisableStartupDialogs'],
capture_output=True, text=True,
)
if result.returncode != 0:
print(f'Failed to create infobase (code: {result.returncode})', file=sys.stderr)
sys.exit(1)
if has_ref_types:
cfg_dir = os.path.join(temp_base, 'cfg')
# LoadConfigFromFiles
print('Loading configuration from files...')
result = subprocess.run(
[args.V8Path, 'DESIGNER', f'/F{temp_base}', '/LoadConfigFromFiles', cfg_dir, '/DisableStartupDialogs'],
capture_output=True, text=True,
)
if result.returncode != 0:
print(f'Failed to load config (code: {result.returncode})', file=sys.stderr)
sys.exit(1)
# UpdateDBCfg
print('Updating database configuration...')
update_log = os.path.join(tempfile.gettempdir(), 'stub_update_log.txt')
result = subprocess.run(
[args.V8Path, 'DESIGNER', f'/F{temp_base}', '/UpdateDBCfg', '/Out', update_log, '/DisableStartupDialogs'],
capture_output=True, text=True,
)
if result.returncode != 0:
if os.path.isfile(update_log):
try:
with open(update_log, 'r', encoding='utf-8-sig') as f:
print(f.read())
except Exception:
pass
print(f'Failed to update DB config (code: {result.returncode})', file=sys.stderr)
sys.exit(1)
# Cleanup cfg dir
import shutil
shutil.rmtree(cfg_dir, ignore_errors=True)
print(f'[OK] Stub database created: {temp_base}')
print(temp_base)
if __name__ == '__main__':
main()