#!/usr/bin/env python3 # meta-decompile v0.54 — XML объекта метаданных 1С → JSON-черновик формата meta-compile # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # # Зеркало meta-decompile.ps1 (КАНОН). Структура 1:1 — те же имена функций, порядок, комментарии. # Изменения вносить СНАЧАЛА в .ps1, затем переносить сюда. lxml.etree — для полного XPath 1.0 # (SelectSingleNode/union/предикаты), как XmlNamespaceManager в .ps1. # # Поддержаны: Catalog, ExchangePlan, ChartOfCharacteristicTypes, ChartOfAccounts, ChartOfCalculationTypes, Document, # InformationRegister, AccumulationRegister, AccountingRegister, CalculationRegister, BusinessProcess, Task, Enum, ... # Инверс meta-compile (omit-on-default: ключ эмитим только когда значение в XML отличается от умолчания # компилятора). Неподдерживаемый тип / не-MetaDataObject root → exit 3 (ring3, как form-decompile). import argparse import os import re import sys from lxml import etree sys.stdout.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8") # --- Namespaces (зеркало XmlNamespaceManager) --- NS_MD = "http://v8.1c.ru/8.3/MDClasses" NS_V8 = "http://v8.1c.ru/8.1/data/core" NS_XR = "http://v8.1c.ru/8.3/xcf/readable" NS_XSI = "http://www.w3.org/2001/XMLSchema-instance" NS_APP = "http://v8.1c.ru/8.2/managed-application/core" NS = {'md': NS_MD, 'v8': NS_V8, 'xr': NS_XR, 'xsi': NS_XSI, 'app': NS_APP} # Скрипт-скоуп состояние (зеркало $script:* / module-level $props/$dsl/$objType канона). Ставятся в main(). props = None obj_node = None obj_type = None obj_name = None dsl = None object_path = None # --- XML-обёртки (зеркало SelectSingleNode/SelectNodes/GetAttribute/InnerText) --- def _single(node, xpath): if node is None: return None r = node.xpath(xpath, namespaces=NS) return r[0] if r else None def _nodes(node, xpath): if node is None: return [] return node.xpath(xpath, namespaces=NS) def _text(node): """InnerText — конкатенация всего текста-потомка (PreserveWhitespace=true у lxml по умолчанию).""" if node is None: return None return ''.join(node.itertext()) def _attr(node, name, ns=None): """GetAttribute(name[, ns]) — .NET возвращает '' для отсутствующего атрибута, lxml → None.""" if node is None: return '' key = ('{%s}%s' % (ns, name)) if ns else name v = node.get(key) return v if v is not None else '' def _localname(el): t = el.tag if not isinstance(t, str): return '' return t.split('}', 1)[1] if '}' in t else t # --- JSON-эмиттер (контроль порядка/массивов/кириллицы) --- def _json_num(n): """Зеркало "$node" для int/long/double.""" if isinstance(n, bool): return 'true' if n else 'false' if isinstance(n, int): return str(n) r = repr(float(n)) if r.endswith('.0'): r = r[:-2] return r def convert_to_compact_json(node, depth=0): pad = " " * depth pad1 = " " * (depth + 1) if node is None: return "null" if isinstance(node, bool): return "true" if node else "false" if isinstance(node, (int, float)): return _json_num(node) if isinstance(node, dict): if len(node) == 0: return "{}" items = [] for k in node.keys(): items.append("%s%s: %s" % (pad1, quote_json(str(k)), convert_to_compact_json(node[k], depth + 1))) return "{\n" + ",\n".join(items) + "\n" + pad + "}" if isinstance(node, list): if len(node) == 0: return "[]" # Массив скаляров-строк — компактно в строку; массив объектов — по строкам. all_scalar = True for e in node: if isinstance(e, (dict, list)): all_scalar = False break if all_scalar: items = [convert_to_compact_json(e, depth + 1) for e in node] return "[" + ", ".join(items) + "]" items = ["%s%s" % (pad1, convert_to_compact_json(e, depth + 1)) for e in node] return "[\n" + ",\n".join(items) + "\n" + pad + "]" return quote_json(str(node)) def quote_json(s): sb = ['"'] for ch in s: if ch == '"': sb.append('\\"') elif ch == '\\': sb.append('\\\\') elif ch == '\n': sb.append('\\n') elif ch == '\r': sb.append('\\r') elif ch == '\t': sb.append('\\t') else: if ord(ch) < 32: sb.append('\\u%04x' % ord(ch)) else: sb.append(ch) sb.append('"') return ''.join(sb) # --- Сравнение регистров (зеркало PS -ne / -cne) --- def ne_cs(a, b): """PS -cne на строках (регистрочувствительно). True когда различаются с учётом регистра. Зеркало компилятора: авто-синоним/описание опускаем только при ТОЧНОМ совпадении со split_camel_words.""" return (a if a is not None else '') != (b if b is not None else '') def split_camel_words(name): """Авто-синоним: точное зеркало Split-CamelCase из meta-compile (split_camel_case, HE-эвристика). ВАЖНО: логика должна совпадать байт-в-байт с компилятором, иначе ложные «синоним==авто» → диффы.""" 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) # HE: сохраняем прогон заглавных >=2, если сразу за ним НЕ буква (пробел/цифра/спецсимвол/конец); # предлоги/бренды перед буквой → лоуэркейз. Первый символ — как есть. if len(result) > 1: chars = list(result) n = len(chars) keep = [False] * n i = 0 while i < n: if chars[i].isupper(): j = i while j < n and chars[j].isupper(): j += 1 after_boundary = (j == n) or (not chars[j].isalpha()) if j - i >= 2 and after_boundary: for k in range(i, j): keep[k] = True i = j else: i += 1 out = [] for idx, c in enumerate(chars): if idx == 0 or keep[idx]: out.append(c) elif c.isupper(): out.append(c.lower()) else: out.append(c) result = ''.join(out) return result def P(tag): n = _single(props, "md:" + tag) return _text(n) if n is not None else None # --- Synonym (ru) --- def get_ml_ru(node): if node is None: return None it = _single(node, "v8:item[v8:lang='ru']/v8:content") if it is not None: return _text(it) return None def get_ml_value(node): """ML-значение → строка (если единственный item ru) ЛИБО dict{lang:content} (мультиязычно, порядок из XML). None если контента нет. Компактная строка для ru-only, объект для мультиязычных.""" if node is None: return None items = _nodes(node, 'v8:item') if len(items) == 0: return None if len(items) == 1: lang = _text(_single(items[0], 'v8:lang')) content = _text(_single(items[0], 'v8:content')) # Единственный ru-item: пустое содержимое ≡ отсутствие значения → None (иначе tooltip:"" ≠ self-close). if lang == 'ru': return None if content == '' else content o = {} for it in items: l = _text(_single(it, 'v8:lang')) c = _text(_single(it, 'v8:content')) o[l] = c return o # --- Тип реквизита: → shorthand-строка --- def strip_ns_prefix(s): if ':' in s: return s.split(':', 1)[1] return s def get_type_shorthand(type_node): if type_node is None: return "" parts = [] children = [c for c in type_node if isinstance(c.tag, str)] for i in range(len(children)): c = children[i] ln = _localname(c) if ln == 'Type': raw = _text(c).strip() nxt = children[i + 1] if i + 1 < len(children) else None if re.search(r'(^|:)boolean$', raw, re.I): parts.append('Boolean') elif re.search(r'(^|:)string$', raw, re.I): length = '10' al = '' sq = nxt if (nxt is not None and _localname(nxt) == 'StringQualifiers') else _single(type_node, 'v8:StringQualifiers') if sq is not None: l = _single(sq, 'v8:Length') if l is not None: length = _text(l) aln = _single(sq, 'v8:AllowedLength') if aln is not None and _text(aln) == 'Fixed': al = ',fixed' parts.append("String(%s%s)" % (length, al)) elif re.search(r'(^|:)decimal$', raw, re.I): d = '10' f = '0' sign = '' nq = nxt if (nxt is not None and _localname(nxt) == 'NumberQualifiers') else _single(type_node, 'v8:NumberQualifiers') if nq is not None: dn = _single(nq, 'v8:Digits') if dn is not None: d = _text(dn) fn = _single(nq, 'v8:FractionDigits') if fn is not None: f = _text(fn) sn = _single(nq, 'v8:AllowedSign') if sn is not None and _text(sn) == 'Nonnegative': sign = ',nonneg' parts.append("Number(%s,%s%s)" % (d, f, sign)) elif re.search(r'(^|:)dateTime$', raw, re.I): fr = 'DateTime' dq = nxt if (nxt is not None and _localname(nxt) == 'DateQualifiers') else _single(type_node, 'v8:DateQualifiers') if dq is not None: dn = _single(dq, 'v8:DateFractions') if dn is not None: fr = _text(dn) parts.append(fr) # Date | DateTime elif re.search(r'(^|:)base64Binary$', raw, re.I): parts.append('ValueStorage') else: parts.append(strip_ns_prefix(raw)) # cfg:CatalogRef.X → CatalogRef.X elif ln == 'TypeSet': parts.append(strip_ns_prefix(_text(c).strip())) # cfg:DefinedType.X → DefinedType.X return ' + '.join(parts) # Скалярное значение параметра выбора () → JSON-значение (bool/число/строка). def convert_ch_scalar_node(vN): xt = _attr(vN, 'type', NS_XSI) txt = _text(vN) if re.search(r'boolean$', xt, re.I): return txt == 'true' if re.search(r'decimal$', xt, re.I): if re.match(r'^-?\d+$', txt): return int(txt) return float(txt) return txt # app:value (тип прямо на узле) → значение ЛИБО список (v8:FixedArray с детьми v8:Value). def get_choice_param_value(val_node): xt = _attr(val_node, 'type', NS_XSI) if re.search(r'FixedArray$', xt, re.I): arr = [] for sub in _nodes(val_node, 'v8:Value'): arr.append(convert_ch_scalar_node(sub)) return arr return convert_ch_scalar_node(val_node) # → [{name,dataPath,valueChange?}] | строки "name=dataPath". tag: 'md:...' | 'xr:...'. def parse_choice_parameter_links(parent, tag): node = _single(parent, tag) if node is None: return None links = _nodes(node, 'xr:Link') if len(links) == 0: return None arr = [] for lk in links: l_name = _text(_single(lk, 'xr:Name')) l_dp = _text(_single(lk, 'xr:DataPath')) vc_n = _single(lk, 'xr:ValueChange') vcv = _text(vc_n) if vc_n is not None else 'Clear' if vcv == 'Clear': arr.append("%s=%s" % (l_name, l_dp)) else: arr.append({'name': l_name, 'dataPath': l_dp, 'valueChange': vcv}) # PS-трап: `return $arr` БЕЗ унарной запятой → одноэлементный ArrayList разворачивается в скаляр # (в отличие от Get-ChoiceParamValue с `,$arr`). Зеркалим: len==1 → голый элемент. return arr[0] if len(arr) == 1 else arr # → [{name,value?}]. tag: 'md:...' | 'xr:...'. def parse_choice_parameters(parent, tag): node = _single(parent, tag) if node is None: return None items = _nodes(node, 'app:item') if len(items) == 0: return None arr = [] for it in items: p_name = _attr(it, 'name') val_n = _single(it, 'app:value') nil_attr = _attr(val_n, 'nil', NS_XSI) if val_n is not None else '' if val_n is None or nil_attr == 'true': arr.append({'name': p_name}) else: o = {'name': p_name} o['value'] = get_choice_param_value(val_n) arr.append(o) # PS-трап (как выше): одноэлементный ArrayList разворачивается в скаляр при `return $arr`. return arr[0] if len(arr) == 1 else arr # --- Реквизит → DSL: shorthand-строка "Имя: Тип | флаги" ЛИБО object-форма при кастомном синониме. --- def attr_to_dsl(attr_node): ap = _single(attr_node, 'md:Properties') nm = _text(_single(ap, 'md:Name')) ts = get_type_shorthand(_single(ap, 'md:Type')) flags = [] fc = _single(ap, 'md:FillChecking') if fc is not None and _text(fc) == 'ShowError': flags.append('req') ix = _single(ap, 'md:Indexing') if ix is not None: ixt = _text(ix) if ixt == 'Index': flags.append('index') elif ixt == 'IndexWithAdditionalOrder': flags.append('indexAdditional') ml = _single(ap, 'md:MultiLine') if ml is not None and _text(ml) == 'true': flags.append('multiline') # Синоним/подсказка (строка ru-only ИЛИ {ru,en}). syn_node = _single(ap, 'md:Synonym') syn_val = get_ml_value(syn_node) syn_custom = False # Пустой (узел есть, значения нет) ≠ авто-синоним из имени → явный пустой (synonym:""). syn_empty = (syn_node is not None and syn_val is None) if isinstance(syn_val, str): if ne_cs(syn_val, split_camel_words(nm)): syn_custom = True elif syn_val is not None: syn_custom = True # {ru,en} = всегда кастом tt_val = get_ml_value(_single(ap, 'md:ToolTip')) # Extra-свойства реквизита (omit-on-default). Наличие любого → object-форма. def en(tag): n = _single(ap, "md:" + tag) return _text(n) if n is not None else None extra = {} v = en('Comment') if v: extra['comment'] = v v = en('FullTextSearch') if v and v != 'Use': extra['fullTextSearch'] = v v = en('FillFromFillingValue') if v == 'true': extra['fillFromFillingValue'] = True v = en('CreateOnInput') if v and v != 'Auto': extra['createOnInput'] = v v = en('QuickChoice') if v and v != 'Auto': extra['quickChoice'] = v v = en('DataHistory') if v and v != 'Use': extra['dataHistory'] = v v = en('Use') if v and v != 'ForItem': extra['use'] = v v = en('PasswordMode') if v == 'true': extra['passwordMode'] = True v = en('Mask') if v: extra['mask'] = v v = en('ChoiceHistoryOnInput') if v and v != 'Auto': extra['choiceHistoryOnInput'] = v v = en('FillChecking') if v == 'ShowWarning': extra['fillChecking'] = 'ShowWarning' v = en('ExtendedEdit') if v == 'true': extra['extendedEdit'] = True v = en('MarkNegatives') if v == 'true': extra['markNegatives'] = True v = en('ChoiceFoldersAndItems') if v and v != 'Items': extra['choiceFoldersAndItems'] = v v = en('ChoiceForm') if v: extra['choiceForm'] = v # Регистро-специфика измерения (теги присутствуют только у Dimension → безвредно для прочих). v = en('Master') if v == 'true': extra['master'] = True v = en('MainFilter') if v == 'true': extra['mainFilter'] = True v = en('DenyIncompleteValues') if v == 'true': extra['denyIncompleteValues'] = True v = en('UseInTotals') if v == 'false': extra['useInTotals'] = False # дефолт true → захват при false v = en('BaseDimension') if v == 'true': extra['baseDimension'] = True v = en('ScheduleLink') if v: extra['scheduleLink'] = v # ссылка на измерение графика (пустой → пропуск) v = en('Balance') if v == 'true': extra['balance'] = True v = en('AccountingFlag') if v: extra['accountingFlag'] = v # ссылка на признак учёта ПС (пустой → пропуск) v = en('ExtDimensionAccountingFlag') if v: extra['extDimensionAccountingFlag'] = v v = en('AddressingDimension') if v: extra['addressingDimension'] = v # ссылка на измерение регистра исполнителей # MinValue/MaxValue — граница диапазона (omit при nil). Тип сохраняем: xs:string→строка, xs:decimal→число. for mm in ('MinValue', 'MaxValue'): mn = _single(ap, "md:" + mm) if mn is None: continue if _attr(mn, 'nil', NS_XSI) == 'true': continue key = 'minValue' if mm == 'MinValue' else 'maxValue' xt = _attr(mn, 'type', NS_XSI) txt = _text(mn) if re.search(r'decimal|int|double|float', xt, re.I): extra[key] = int(txt) if re.match(r'^-?\d+$', txt) else float(txt) else: extra[key] = txt fmt_v = get_ml_value(_single(ap, 'md:Format')) if fmt_v is not None: extra['format'] = fmt_v efmt_v = get_ml_value(_single(ap, 'md:EditFormat')) if efmt_v is not None: extra['editFormat'] = efmt_v # FillValue (значение заполнения). Форма по умолчанию зависит от типа реквизита: String→typed-empty, # Number→zero, всё остальное→nil. Эмитим `fillValue` только при отклонении от дефолта (§4.2 spec). fv_node = _single(ap, 'md:FillValue') if fv_node is not None: fcat = 'Other' if re.search(r'\+', ts): fcat = 'Other' elif re.search(r'^Boolean', ts): fcat = 'Boolean' elif re.search(r'^String', ts): fcat = 'String' elif re.search(r'^Number', ts): fcat = 'Number' elif re.search(r'^(Date|DateTime)', ts): fcat = 'Date' nil_attr = _attr(fv_node, 'nil', NS_XSI) xsi_t = _attr(fv_node, 'type', NS_XSI) fv_text = _text(fv_node) if nil_attr == 'true': if fcat == 'String' or fcat == 'Number': extra['fillValue'] = None # nil-override # иначе nil — это дефолт → пропускаем elif re.search(r'boolean$', xsi_t, re.I): extra['fillValue'] = (fv_text == 'true') elif re.search(r'decimal$', xsi_t, re.I): # Захватываем как ЧИСЛО (не строку): на составном типе компилятор берёт xsi-тип из JSON-значения. if not (fcat == 'Number' and (fv_text == '0' or fv_text == '')): extra['fillValue'] = int(fv_text) if re.match(r'^-?\d+$', fv_text) else float(fv_text) elif re.search(r'string$', xsi_t, re.I): if not (fcat == 'String' and fv_text == ''): extra['fillValue'] = fv_text elif re.search(r'dateTime$', xsi_t, re.I): extra['fillValue'] = fv_text elif re.search(r'DesignTimeRef$', xsi_t, re.I): # Пустой DTR ≠ nil/xs:string → маркер emptyRef (иначе тип терялся в xs:string). if fv_text == '': extra['fillValue'] = {'emptyRef': True} else: extra['fillValue'] = fv_text # LinkByType (связь по типу): DataPath + LinkItem. Пусто → пропускаем. linkItem=0 → компактно строкой. lbt_node = _single(ap, 'md:LinkByType') if lbt_node is not None: dp_n = _single(lbt_node, 'xr:DataPath') if dp_n is not None and _text(dp_n): li_n = _single(lbt_node, 'xr:LinkItem') li = int(_text(li_n)) if (li_n is not None and _text(li_n)) else 0 if li == 0: extra['linkByType'] = _text(dp_n) else: extra['linkByType'] = {'dataPath': _text(dp_n), 'linkItem': li} cpl_arr = parse_choice_parameter_links(ap, 'md:ChoiceParameterLinks') if cpl_arr is not None: extra['choiceParameterLinks'] = cpl_arr cp_arr = parse_choice_parameters(ap, 'md:ChoiceParameters') if cp_arr is not None: extra['choiceParameters'] = cp_arr # Пустой (реквизит без типа) → ts=''. Отличаем от «дефолтного» отсутствия: явный type:''. type_empty = (ts == '') if syn_custom or syn_empty or (tt_val is not None) or len(extra) > 0 or type_empty: o = {'name': nm} if ts: o['type'] = ts elif type_empty: o['type'] = '' if syn_custom: o['synonym'] = syn_val elif syn_empty: o['synonym'] = '' if tt_val is not None: o['tooltip'] = tt_val for k in extra: o[k] = extra[k] if len(flags) > 0: o['flags'] = list(flags) return o head = ("%s: %s" % (nm, ts)) if ts else nm if len(flags) > 0: return head + " | " + ", ".join(flags) return head # --- Свойства (omit-on-default). --- def add_bool_prop(key, tag, default): v = P(tag) if v is not None: b = (v == 'true') if b != default: dsl[key] = b def add_enum_prop(key, tag, default): v = P(tag) if v is not None and v != '' and v != default: dsl[key] = v def add_int_prop(key, tag, default): v = P(tag) if v is not None and v != '': iv = int(v) if iv != default: dsl[key] = iv # Общий захват структурного (зеркало Emit-CommandPicture). Пишет в tgt ключи picture/loadTransparent. def get_picture_to_dsl(props_node, tgt): ref_n = _single(props_node, 'md:Picture/xr:Ref') abs_n = _single(props_node, 'md:Picture/xr:Abs') if ref_n is not None or abs_n is not None: psrc = _text(ref_n) if ref_n is not None else ("abs:" + _text(abs_n)) lt_n = _single(props_node, 'md:Picture/xr:LoadTransparent') lt_false = (lt_n is not None and _text(lt_n) == 'false') tpx_n = _single(props_node, 'md:Picture/xr:TransparentPixel') if tpx_n is not None: po = {'src': psrc} if lt_false: po['loadTransparent'] = False po['transparentPixel'] = {'x': int(_attr(tpx_n, 'x')), 'y': int(_attr(tpx_n, 'y'))} tgt['picture'] = po else: tgt['picture'] = psrc if lt_false: tgt['loadTransparent'] = False # Короткая форма поля: ..StandardAttribute.X / .Attribute.X → StandardAttribute.X / Attribute.X def short_field(full): m = re.search(r'\.(StandardAttribute|Attribute)\.(.+)$', full) if m: return "%s.%s" % (m.group(1), m.group(2)) return full def add_form_ref(key, tag): v = P(tag) if v: dsl[key] = v # --- Characteristics: короткая форма поля bare/partial. --- def shorten_char_field(full, frm): if full.startswith(frm + "."): rest = full[len(frm) + 1:] m = re.match(r'^StandardAttribute\.(Ref|Parent|Owner)$', rest) if m: return m.group(1) # ссылочные станд. → голое m = re.match(r'^Attribute\.(.+)$', rest) if m: return m.group(1) # кастом → голое return rest # прочие StandardAttribute.X / Dimension.X / Resource.X → частичное return full # --- Предопределённые (local-name xpath, без ns-менеджера) --- def _lx1(el, xp): r = el.xpath(xp) return r[0] if r else None def _lxn(el, xp): return el.xpath(xp) def predef_item_to_dsl(item_el): name = _text(_lx1(item_el, "*[local-name()='Name']")) code_el = _lx1(item_el, "*[local-name()='Code']") code = _text(code_el) if (code_el is not None and _text(code_el)) else '' desc_el = _lx1(item_el, "*[local-name()='Description']") desc = _text(desc_el) if desc_el is not None else '' folder_el = _lx1(item_el, "*[local-name()='IsFolder']") is_folder = (folder_el is not None and _text(folder_el) == 'true') child_container = _lx1(item_el, "*[local-name()='ChildItems']") kids = _lxn(child_container, "*[local-name()='Item']") if child_container is not None else [] # Type — тип значения предопределённой характеристики (ПВХ). Наличие узла → object-форма с ключом type. type_el = _lx1(item_el, "*[local-name()='Type']") type_str = get_type_shorthand(type_el) if type_el is not None else None auto = split_camel_words(name) # Компактная строка для плоских: без узла Type (Catalog) ИЛИ с непустым типом → "(Код) Имя [Наим]: Тип". if (not is_folder) and len(kids) == 0 and (type_str is None or type_str != ''): s = ("(%s) %s" % (code, name)) if code else name if desc == '': s = s + " []" elif ne_cs(desc, auto): s = s + (" [%s]" % desc) if type_str: s = "%s: %s" % (s, type_str) return s # Группа/иерархия/с типом → объект. o = {'name': name} if code: o['code'] = code if desc == '': o['description'] = '' elif ne_cs(desc, auto): o['description'] = desc if type_str is not None: o['type'] = type_str if is_folder: o['isFolder'] = True if len(kids) > 0: sub = [] for k in kids: sub.append(predef_item_to_dsl(k)) o['childItems'] = sub return o def predef_account_to_dsl(item_el): name = _text(_lx1(item_el, "*[local-name()='Name']")) code_el = _lx1(item_el, "*[local-name()='Code']") code = _text(code_el) if (code_el is not None and _text(code_el)) else '' desc_el = _lx1(item_el, "*[local-name()='Description']") desc = _text(desc_el) if desc_el is not None else '' at_el = _lx1(item_el, "*[local-name()='AccountType']") acct_type = _text(at_el) if at_el is not None else 'ActivePassive' off_el = _lx1(item_el, "*[local-name()='OffBalance']") off = (off_el is not None and _text(off_el) == 'true') ord_el = _lx1(item_el, "*[local-name()='Order']") order = _text(ord_el) if ord_el is not None else '' auto = split_camel_words(name) # TRUE-флаги (leaf после последней точки в ref). true_flags = [] for fl in _lxn(item_el, "*[local-name()='AccountingFlags']/*[local-name()='Flag']"): if _text(fl) == 'true': r = _attr(fl, 'ref') true_flags.append(r.split('.')[-1]) # ExtDimensionTypes → subconto. Короткая запись "Тип | Признак1, Признак2". edt_pfx = (dsl['extDimensionTypes'] + ".") if dsl.get('extDimensionTypes') else None subconto = [] for edt in _lxn(item_el, "*[local-name()='ExtDimensionTypes']/*[local-name()='ExtDimensionType']"): sc_t = _attr(edt, 'name') if edt_pfx and sc_t.startswith(edt_pfx): sc_t = sc_t[len(edt_pfx):] t_el = _lx1(edt, "*[local-name()='Turnover']") sc_flags_out = [] if t_el is not None and _text(t_el) == 'true': sc_flags_out.append('Turnover') for fl in _lxn(edt, "*[local-name()='AccountingFlags']/*[local-name()='Flag']"): if _text(fl) == 'true': r = _attr(fl, 'ref') sc_flags_out.append(r.split('.')[-1]) if len(sc_flags_out) > 0: subconto.append(sc_t + " | " + ", ".join(sc_flags_out)) else: subconto.append(sc_t) child_container = _lx1(item_el, "*[local-name()='ChildItems']") kids = _lxn(child_container, "*[local-name()='Item']") if child_container is not None else [] o = {'name': name} if code: o['code'] = code # -cne (регистрочувствительно!): хвостовые аббревиатуры (ОС/НМА) теряли бы регистр при -ne. if desc != auto: o['description'] = desc o['accountType'] = acct_type if off: o['offBalance'] = True o['order'] = order if len(true_flags) > 0: o['flags'] = list(true_flags) if len(subconto) > 0: o['subconto'] = subconto if len(kids) > 0: sub = [] for k in kids: sub.append(predef_account_to_dsl(k)) o['childItems'] = sub return o def predef_calc_type_to_dsl(item_el): name = _text(_lx1(item_el, "*[local-name()='Name']")) code_el = _lx1(item_el, "*[local-name()='Code']") code = _text(code_el) if (code_el is not None and _text(code_el)) else '' desc_el = _lx1(item_el, "*[local-name()='Description']") desc = _text(desc_el) if desc_el is not None else '' apib_el = _lx1(item_el, "*[local-name()='ActionPeriodIsBase']") apib = (apib_el is not None and _text(apib_el) == 'true') auto = split_camel_words(name) if not apib: s = ("(%s) %s" % (code, name)) if code else name if desc == '': s = s + " []" elif desc != auto: # -cne s = s + (" [%s]" % desc) return s o = {'name': name} if code: o['code'] = code if desc != auto: # -cne o['description'] = desc o['actionPeriodIsBase'] = True return o def build_dsl(): """Линейный поток сборки DSL — зеркало script-body .ps1 (стр.392–1459).""" global dsl # === Сборка DSL === dsl = {'type': obj_type, 'name': obj_name} # Синоним объекта: строка ru-only ИЛИ {ru,en}. Кастом → эмитим. Пустой → явный synonym:"". syn_node_obj = _single(props, 'md:Synonym') syn_val = get_ml_value(syn_node_obj) if isinstance(syn_val, str): if ne_cs(syn_val, split_camel_words(obj_name)): dsl['synonym'] = syn_val elif syn_val is not None: dsl['synonym'] = syn_val elif syn_node_obj is not None: dsl['synonym'] = '' cmt = P('Comment') if cmt: dsl['comment'] = cmt # Свойства Catalog (omit-on-default). add_bool_prop('hierarchical', 'Hierarchical', False) add_enum_prop('hierarchyType', 'HierarchyType', 'HierarchyFoldersAndItems') add_bool_prop('limitLevelCount', 'LimitLevelCount', False) add_int_prop('levelCount', 'LevelCount', 2) add_bool_prop('foldersOnTop', 'FoldersOnTop', True) # owners owners_node = _single(props, 'md:Owners') if owners_node is not None: items = [_text(x) for x in _nodes(owners_node, 'xr:Item')] if len(items) > 0: dsl['owners'] = [(s.split('.', 1)[1] if re.match(r'^Catalog\.', s) else s) for s in items] add_enum_prop('subordinationUse', 'SubordinationUse', 'ToItems') # Тип-зависимые дефолты (компилятор задаёт их по типу — декомпилятор обязан зеркалить). descr_len_def = {'ExchangePlan': 150, 'ChartOfCharacteristicTypes': 100, 'ChartOfCalculationTypes': 100}.get(obj_type, 25) code_len_def = 5 if obj_type == 'ChartOfCalculationTypes' else 9 create_inp_def = 'Use' if obj_type in ('Catalog', 'Document') else 'DontUse' data_lock_def = 'Managed' # компилятор эмитит Managed по умолчанию для всех типов (авторинг); Automatic несётся в DSL явно code_series_def = {'ChartOfCharacteristicTypes': 'WholeCharacteristicKind', 'ChartOfAccounts': 'WholeChartOfAccounts'}.get(obj_type, 'WholeCatalog') check_unique_def = (obj_type in ('ChartOfCharacteristicTypes', 'ChartOfAccounts', 'Document', 'DocumentNumerator')) def_pres_def = 'AsCode' if obj_type == 'ChartOfAccounts' else 'AsDescription' add_int_prop('codeLength', 'CodeLength', code_len_def) add_int_prop('descriptionLength', 'DescriptionLength', descr_len_def) add_enum_prop('codeType', 'CodeType', 'String') add_enum_prop('codeAllowedLength', 'CodeAllowedLength', 'Variable') add_bool_prop('autonumbering', 'Autonumbering', True) add_bool_prop('checkUnique', 'CheckUnique', check_unique_def) add_enum_prop('codeSeries', 'CodeSeries', code_series_def) add_enum_prop('defaultPresentation', 'DefaultPresentation', def_pres_def) if obj_type != 'Constant': add_bool_prop('quickChoice', 'QuickChoice', True if obj_type == 'Enum' else False) add_enum_prop('choiceMode', 'ChoiceMode', 'BothWays') add_enum_prop('dataLockControlMode', 'DataLockControlMode', data_lock_def) add_enum_prop('fullTextSearch', 'FullTextSearch', 'Use') add_bool_prop('useStandardCommands', 'UseStandardCommands', False if obj_type in ('Enum', 'CommonForm') else True) add_enum_prop('createOnInput', 'CreateOnInput', create_inp_def) add_enum_prop('editType', 'EditType', 'InDialog') add_bool_prop('includeHelpInContents', 'IncludeHelpInContents', False) add_enum_prop('choiceHistoryOnInput', 'ChoiceHistoryOnInput', 'Auto') add_enum_prop('predefinedDataUpdate', 'PredefinedDataUpdate', 'Auto') add_enum_prop('searchStringModeOnInputByString', 'SearchStringModeOnInputByString', 'Begin') add_enum_prop('fullTextSearchOnInputByString', 'FullTextSearchOnInputByString', 'DontUse') # ExchangePlan-специфичные свойства. if obj_type == 'ExchangePlan': add_bool_prop('distributedInfoBase', 'DistributedInfoBase', False) add_bool_prop('includeConfigurationExtensions', 'IncludeConfigurationExtensions', False) add_enum_prop('dataHistory', 'DataHistory', 'DontUse') add_bool_prop('updateDataHistoryImmediatelyAfterWrite', 'UpdateDataHistoryImmediatelyAfterWrite', False) add_bool_prop('executeAfterWriteDataHistoryVersionProcessing', 'ExecuteAfterWriteDataHistoryVersionProcessing', False) # ChartOfCharacteristicTypes-специфичные свойства. if obj_type == 'ChartOfCharacteristicTypes': add_enum_prop('dataHistory', 'DataHistory', 'DontUse') add_bool_prop('updateDataHistoryImmediatelyAfterWrite', 'UpdateDataHistoryImmediatelyAfterWrite', False) add_bool_prop('executeAfterWriteDataHistoryVersionProcessing', 'ExecuteAfterWriteDataHistoryVersionProcessing', False) cev = P('CharacteristicExtValues') if cev: dsl['characteristicExtValues'] = cev vt_node = _single(props, 'md:Type') if vt_node is not None: vt_str = get_type_shorthand(vt_node) if vt_str and vt_str != 'Boolean + String(100) + Number(15,2) + DateTime': dsl['valueType'] = vt_str # ChartOfAccounts-специфичные свойства. if obj_type == 'ChartOfAccounts': edt = P('ExtDimensionTypes') if edt: dsl['extDimensionTypes'] = edt add_int_prop('maxExtDimensionCount', 'MaxExtDimensionCount', 3 if edt else 0) cm = P('CodeMask') if cm: dsl['codeMask'] = cm add_bool_prop('autoOrderByCode', 'AutoOrderByCode', True) add_int_prop('orderLength', 'OrderLength', 9) add_enum_prop('dataHistory', 'DataHistory', 'DontUse') add_bool_prop('updateDataHistoryImmediatelyAfterWrite', 'UpdateDataHistoryImmediatelyAfterWrite', False) add_bool_prop('executeAfterWriteDataHistoryVersionProcessing', 'ExecuteAfterWriteDataHistoryVersionProcessing', False) # ChartOfCalculationTypes-специфичные свойства. if obj_type == 'ChartOfCalculationTypes': add_enum_prop('dependenceOnCalculationTypes', 'DependenceOnCalculationTypes', 'DontUse') add_bool_prop('actionPeriodUse', 'ActionPeriodUse', False) bct_node = _single(props, 'md:BaseCalculationTypes') if bct_node is not None: bct_items = [_text(x) for x in _nodes(bct_node, 'xr:Item')] if len(bct_items) > 0: dsl['baseCalculationTypes'] = list(bct_items) add_enum_prop('dataHistory', 'DataHistory', 'DontUse') add_bool_prop('updateDataHistoryImmediatelyAfterWrite', 'UpdateDataHistoryImmediatelyAfterWrite', False) add_bool_prop('executeAfterWriteDataHistoryVersionProcessing', 'ExecuteAfterWriteDataHistoryVersionProcessing', False) # Document-специфичные свойства. if obj_type == 'Document': num_ref = P('Numerator') if num_ref: dsl['numerator'] = num_ref add_enum_prop('numberType', 'NumberType', 'String') add_int_prop('numberLength', 'NumberLength', 11) add_enum_prop('numberAllowedLength', 'NumberAllowedLength', 'Variable') add_enum_prop('numberPeriodicity', 'NumberPeriodicity', 'Year') add_enum_prop('posting', 'Posting', 'Allow') add_enum_prop('realTimePosting', 'RealTimePosting', 'Deny') add_enum_prop('registerRecordsDeletion', 'RegisterRecordsDeletion', 'AutoDelete') add_enum_prop('registerRecordsWritingOnPost', 'RegisterRecordsWritingOnPost', 'WriteSelected') add_enum_prop('sequenceFilling', 'SequenceFilling', 'AutoFill') add_bool_prop('postInPrivilegedMode', 'PostInPrivilegedMode', True) add_bool_prop('unpostInPrivilegedMode', 'UnpostInPrivilegedMode', True) rr_node = _single(props, 'md:RegisterRecords') if rr_node is not None: rr_items = [_text(x) for x in _nodes(rr_node, 'xr:Item')] if len(rr_items) > 0: dsl['registerRecords'] = list(rr_items) add_enum_prop('dataHistory', 'DataHistory', 'DontUse') add_bool_prop('updateDataHistoryImmediatelyAfterWrite', 'UpdateDataHistoryImmediatelyAfterWrite', False) add_bool_prop('executeAfterWriteDataHistoryVersionProcessing', 'ExecuteAfterWriteDataHistoryVersionProcessing', False) # InformationRegister-специфичные свойства. if obj_type == 'InformationRegister': add_enum_prop('periodicity', 'InformationRegisterPeriodicity', 'Nonperiodical') add_enum_prop('writeMode', 'WriteMode', 'Independent') add_bool_prop('mainFilterOnPeriod', 'MainFilterOnPeriod', False) add_bool_prop('enableTotalsSliceFirst', 'EnableTotalsSliceFirst', False) add_bool_prop('enableTotalsSliceLast', 'EnableTotalsSliceLast', False) add_enum_prop('dataHistory', 'DataHistory', 'DontUse') add_bool_prop('updateDataHistoryImmediatelyAfterWrite', 'UpdateDataHistoryImmediatelyAfterWrite', False) add_bool_prop('executeAfterWriteDataHistoryVersionProcessing', 'ExecuteAfterWriteDataHistoryVersionProcessing', False) # AccumulationRegister-специфичные свойства. if obj_type == 'AccumulationRegister': add_enum_prop('registerType', 'RegisterType', 'Balance') add_bool_prop('enableTotalsSplitting', 'EnableTotalsSplitting', True) # AccountingRegister-специфичные свойства. if obj_type == 'AccountingRegister': coa = P('ChartOfAccounts') if coa: dsl['chartOfAccounts'] = coa add_bool_prop('correspondence', 'Correspondence', False) add_int_prop('periodAdjustmentLength', 'PeriodAdjustmentLength', 0) add_bool_prop('enableTotalsSplitting', 'EnableTotalsSplitting', True) # CalculationRegister-специфичные свойства. if obj_type == 'CalculationRegister': cct = P('ChartOfCalculationTypes') if cct: dsl['chartOfCalculationTypes'] = cct add_enum_prop('periodicity', 'Periodicity', 'Month') add_bool_prop('actionPeriod', 'ActionPeriod', False) add_bool_prop('basePeriod', 'BasePeriod', False) sch = P('Schedule') if sch: dsl['schedule'] = sch schv = P('ScheduleValue') if schv: dsl['scheduleValue'] = schv schd = P('ScheduleDate') if schd: dsl['scheduleDate'] = schd # BusinessProcess-специфичные свойства. if obj_type == 'BusinessProcess': add_enum_prop('numberType', 'NumberType', 'String') add_int_prop('numberLength', 'NumberLength', 11) add_enum_prop('numberAllowedLength', 'NumberAllowedLength', 'Variable') add_enum_prop('numberPeriodicity', 'NumberPeriodicity', 'Nonperiodical') add_bool_prop('checkUnique', 'CheckUnique', True) add_bool_prop('autonumbering', 'Autonumbering', True) tsk = P('Task') if tsk: dsl['task'] = tsk add_bool_prop('createTaskInPrivilegedMode', 'CreateTaskInPrivilegedMode', True) # Task-специфичные свойства. if obj_type == 'Task': add_enum_prop('numberType', 'NumberType', 'String') add_int_prop('numberLength', 'NumberLength', 14) add_enum_prop('numberAllowedLength', 'NumberAllowedLength', 'Variable') add_bool_prop('checkUnique', 'CheckUnique', True) add_bool_prop('autonumbering', 'Autonumbering', True) tnap = P('TaskNumberAutoPrefix') if tnap and tnap != 'BusinessProcessNumber': dsl['taskNumberAutoPrefix'] = tnap add_int_prop('descriptionLength', 'DescriptionLength', 150) addr = P('Addressing') if addr: dsl['addressing'] = addr maa = P('MainAddressingAttribute') if maa: dsl['mainAddressingAttribute'] = maa cp = P('CurrentPerformer') if cp: dsl['currentPerformer'] = cp add_enum_prop('defaultPresentation', 'DefaultPresentation', 'AsDescription') # Report-специфичные свойства. if obj_type == 'Report': dfm = P('DefaultForm') if dfm: dsl['defaultForm'] = dfm afm = P('AuxiliaryForm') if afm: dsl['auxiliaryForm'] = afm mdcs = P('MainDataCompositionSchema') if mdcs: dsl['mainDataCompositionSchema'] = mdcs dsf = P('DefaultSettingsForm') if dsf: dsl['defaultSettingsForm'] = dsf asf = P('AuxiliarySettingsForm') if asf: dsl['auxiliarySettingsForm'] = asf dvf = P('DefaultVariantForm') if dvf: dsl['defaultVariantForm'] = dvf vs = P('VariantsStorage') if vs: dsl['variantsStorage'] = vs ss = P('SettingsStorage') if ss: dsl['settingsStorage'] = ss ep = get_ml_value(_single(props, 'md:ExtendedPresentation')) if ep is not None: dsl['extendedPresentation'] = ep # DataProcessor-специфичные свойства. if obj_type == 'DataProcessor': dfm = P('DefaultForm') if dfm: dsl['defaultForm'] = dfm afm = P('AuxiliaryForm') if afm: dsl['auxiliaryForm'] = afm ep = get_ml_value(_single(props, 'md:ExtendedPresentation')) if ep is not None: dsl['extendedPresentation'] = ep # DefinedType — тип-псевдоним. if obj_type == 'DefinedType': vt = get_type_shorthand(_single(props, 'md:Type')) if vt: dsl['valueType'] = vt # FunctionalOption. if obj_type == 'FunctionalOption': loc = P('Location') if loc: dsl['location'] = loc add_bool_prop('privilegedGetMode', 'PrivilegedGetMode', True) content_node = _single(props, 'md:Content') if content_node is not None: items = [_text(x) for x in _nodes(content_node, 'xr:Object')] if len(items) > 0: dsl['content'] = list(items) # DocumentJournal. if obj_type == 'DocumentJournal': dfm = P('DefaultForm') if dfm: dsl['defaultForm'] = dfm afm = P('AuxiliaryForm') if afm: dsl['auxiliaryForm'] = afm rd_node = _single(props, 'md:RegisteredDocuments') if rd_node is not None: rd_items = [_text(x) for x in _nodes(rd_node, 'xr:Item')] if len(rd_items) > 0: dsl['registeredDocuments'] = list(rd_items) # Sequence. if obj_type == 'Sequence': add_enum_prop('moveBoundaryOnPosting', 'MoveBoundaryOnPosting', 'DontMove') for ll in (('Documents', 'documents'), ('RegisterRecords', 'registerRecords')): ln = _single(props, "md:" + ll[0]) if ln is not None: items = [_text(x) for x in _nodes(ln, 'xr:Item')] if len(items) > 0: dsl[ll[1]] = list(items) # FilterCriterion. if obj_type == 'FilterCriterion': vt = get_type_shorthand(_single(props, 'md:Type')) if vt: dsl['valueType'] = vt cn = _single(props, 'md:Content') if cn is not None: items = [_text(x) for x in _nodes(cn, 'xr:Item')] if len(items) > 0: dsl['content'] = list(items) dfm = P('DefaultForm') if dfm: dsl['defaultForm'] = dfm afm = P('AuxiliaryForm') if afm: dsl['auxiliaryForm'] = afm # DocumentNumerator. if obj_type == 'DocumentNumerator': add_enum_prop('numberType', 'NumberType', 'String') add_int_prop('numberLength', 'NumberLength', 11) add_enum_prop('numberAllowedLength', 'NumberAllowedLength', 'Variable') add_enum_prop('numberPeriodicity', 'NumberPeriodicity', 'Year') # SettingsStorage. if obj_type == 'SettingsStorage': for fp in (('DefaultSaveForm', 'defaultSaveForm'), ('DefaultLoadForm', 'defaultLoadForm'), ('AuxiliarySaveForm', 'auxiliarySaveForm'), ('AuxiliaryLoadForm', 'auxiliaryLoadForm')): fv = P(fp[0]) if fv: dsl[fp[1]] = fv # CommonModule. if obj_type == 'CommonModule': add_bool_prop('global', 'Global', False) add_bool_prop('clientManagedApplication', 'ClientManagedApplication', False) add_bool_prop('server', 'Server', False) add_bool_prop('externalConnection', 'ExternalConnection', False) add_bool_prop('clientOrdinaryApplication', 'ClientOrdinaryApplication', False) add_bool_prop('serverCall', 'ServerCall', False) add_bool_prop('privileged', 'Privileged', False) add_enum_prop('returnValuesReuse', 'ReturnValuesReuse', 'DontUse') # EventSubscription. if obj_type == 'EventSubscription': src_node = _single(props, 'md:Source') if src_node is not None: src_types = [strip_ns_prefix(_text(x).strip()) for x in _nodes(src_node, 'v8:Type|v8:TypeSet')] if len(src_types) > 0: dsl['source'] = list(src_types) add_enum_prop('event', 'Event', 'BeforeWrite') h = P('Handler') if h: dsl['handler'] = h # CommonForm. if obj_type == 'CommonForm': add_enum_prop('formType', 'FormType', 'Managed') up_node = _single(props, 'md:UsePurposes') if up_node is not None: ups = [_text(x) for x in _nodes(up_node, 'v8:Value')] def2 = ['PlatformApplication', 'MobilePlatformApplication'] same = (len(ups) == len(def2)) if same: for k in range(len(ups)): if ups[k] != def2[k]: same = False break if (not same) and len(ups) > 0: dsl['usePurposes'] = list(ups) ep = get_ml_value(_single(props, 'md:ExtendedPresentation')) if ep is not None: dsl['extendedPresentation'] = ep # SessionParameter. if obj_type == 'SessionParameter': vt = get_type_shorthand(_single(props, 'md:Type')) if vt: dsl['valueType'] = vt # FunctionalOptionsParameter. if obj_type == 'FunctionalOptionsParameter': un = _single(props, 'md:Use') if un is not None: items = [_text(x) for x in _nodes(un, 'xr:Item')] if len(items) > 0: dsl['use'] = list(items) # WSReference. if obj_type == 'WSReference': url = P('LocationURL') if url: dsl['locationURL'] = url # CommonPicture. if obj_type == 'CommonPicture': add_bool_prop('availabilityForChoice', 'AvailabilityForChoice', False) add_bool_prop('availabilityForAppearance', 'AvailabilityForAppearance', False) # CommonTemplate. if obj_type == 'CommonTemplate': add_enum_prop('templateType', 'TemplateType', 'SpreadsheetDocument') # CommandGroup. if obj_type == 'CommandGroup': add_enum_prop('representation', 'Representation', 'Auto') tt = get_ml_value(_single(props, 'md:ToolTip')) if tt is not None: dsl['tooltip'] = tt get_picture_to_dsl(props, dsl) add_enum_prop('category', 'Category', 'NavigationPanel') # CommonCommand. if obj_type == 'CommonCommand': grp = P('Group') if grp: dsl['group'] = grp add_enum_prop('representation', 'Representation', 'Auto') tt = get_ml_value(_single(props, 'md:ToolTip')) if tt is not None: dsl['tooltip'] = tt get_picture_to_dsl(props, dsl) sc = P('Shortcut') if sc: dsl['shortcut'] = sc cpt = get_type_shorthand(_single(props, 'md:CommandParameterType')) if cpt: dsl['commandParameterType'] = cpt add_enum_prop('parameterUseMode', 'ParameterUseMode', 'Single') add_bool_prop('modifiesData', 'ModifiesData', False) add_enum_prop('onMainServerUnavalableBehavior', 'OnMainServerUnavalableBehavior', 'Auto') # CommonAttribute. if obj_type == 'CommonAttribute': vt = get_type_shorthand(_single(props, 'md:Type')) if vt and vt != 'String(0)': dsl['valueType'] = vt add_bool_prop('passwordMode', 'PasswordMode', False) for mlp in (('Format', 'format'), ('EditFormat', 'editFormat'), ('ToolTip', 'tooltip')): mv = get_ml_value(_single(props, "md:" + mlp[0])) if mv is not None: dsl[mlp[1]] = mv add_bool_prop('markNegatives', 'MarkNegatives', False) msk = P('Mask') if msk: dsl['mask'] = msk add_bool_prop('multiLine', 'MultiLine', False) add_bool_prop('extendedEdit', 'ExtendedEdit', False) for mm in (('MinValue', 'minValue'), ('MaxValue', 'maxValue')): mn = _single(props, "md:" + mm[0]) if mn is not None and _attr(mn, 'nil', NS_XSI) != 'true': mxt = _attr(mn, 'type', NS_XSI) if re.search(r'decimal$', mxt, re.I): dsl[mm[1]] = int(_text(mn)) if re.match(r'^-?\d+$', _text(mn)) else float(_text(mn)) else: dsl[mm[1]] = _text(mn) add_bool_prop('fillFromFillingValue', 'FillFromFillingValue', False) # FillValue: тип-зависимый дефолт (String→typed-empty, Number→0, прочее→nil). cat_vt = vt if vt else 'String(0)' fv_n = _single(props, 'md:FillValue') if fv_n is not None: fv_nil = (_attr(fv_n, 'nil', NS_XSI) == 'true') if fv_nil: if re.match(r'^(String|Number)', cat_vt): dsl['fillValue'] = {'nil': True} else: fv_xt = _attr(fv_n, 'type', NS_XSI) if re.search(r'DesignTimeRef$', fv_xt, re.I) and _text(fv_n) == '': dsl['fillValue'] = {'emptyRef': True} elif re.search(r'decimal$', fv_xt, re.I): if _text(fv_n) != '0': dsl['fillValue'] = int(_text(fv_n)) if re.match(r'^-?\d+$', _text(fv_n)) else float(_text(fv_n)) elif _text(fv_n): dsl['fillValue'] = _text(fv_n) add_enum_prop('fillChecking', 'FillChecking', 'DontCheck') add_enum_prop('choiceFoldersAndItems', 'ChoiceFoldersAndItems', 'Items') cpl = parse_choice_parameter_links(props, 'md:ChoiceParameterLinks') if cpl is not None: dsl['choiceParameterLinks'] = cpl cp = parse_choice_parameters(props, 'md:ChoiceParameters') if cp is not None: dsl['choiceParameters'] = cp add_enum_prop('quickChoice', 'QuickChoice', 'Auto') add_enum_prop('createOnInput', 'CreateOnInput', 'Auto') cf = P('ChoiceForm') if cf: dsl['choiceForm'] = cf add_enum_prop('choiceHistoryOnInput', 'ChoiceHistoryOnInput', 'Auto') cn = _single(props, 'md:Content') if cn is not None: c_arr = [] for it in _nodes(cn, 'xr:Item'): md_n = _single(it, 'xr:Metadata') mdv = _text(md_n) if md_n is not None else '' use_n = _single(it, 'xr:Use') usev = _text(use_n) if use_n is not None else 'Use' cs_n = _single(it, 'xr:ConditionalSeparation') csv = _text(cs_n) if cs_n is not None else '' if usev == 'Use' and not csv: c_arr.append(mdv) else: io = {'metadata': mdv} if usev != 'Use': io['use'] = usev if csv: io['conditionalSeparation'] = csv c_arr.append(io) if len(c_arr) > 0: dsl['content'] = c_arr add_enum_prop('autoUse', 'AutoUse', 'DontUse') add_enum_prop('dataSeparation', 'DataSeparation', 'DontUse') add_enum_prop('separatedDataUse', 'SeparatedDataUse', 'Independently') dsv = P('DataSeparationValue') if dsv: dsl['dataSeparationValue'] = dsv dsu = P('DataSeparationUse') if dsu: dsl['dataSeparationUse'] = dsu cs2 = P('ConditionalSeparation') if cs2: dsl['conditionalSeparation'] = cs2 add_enum_prop('usersSeparation', 'UsersSeparation', 'DontUse') add_enum_prop('authenticationSeparation', 'AuthenticationSeparation', 'DontUse') add_enum_prop('configurationExtensionsSeparation', 'ConfigurationExtensionsSeparation', 'DontUse') add_enum_prop('indexing', 'Indexing', 'DontIndex') add_enum_prop('dataHistory', 'DataHistory', 'Use') # ScheduledJob. if obj_type == 'ScheduledJob': mn = P('MethodName') if mn: dsl['methodName'] = mn descr = P('Description') if descr: dsl['description'] = descr k = P('Key') if k: dsl['key'] = k add_bool_prop('use', 'Use', False) add_bool_prop('predefined', 'Predefined', False) add_int_prop('restartCountOnFailure', 'RestartCountOnFailure', 3) add_int_prop('restartIntervalOnFailure', 'RestartIntervalOnFailure', 10) # Constant — богатый одиночный реквизит. if obj_type == 'Constant': vt = get_type_shorthand(_single(props, 'md:Type')) if vt: dsl['valueType'] = vt else: dsl['valueType'] = '' dfm = P('DefaultForm') if dfm: dsl['defaultForm'] = dfm ep = get_ml_value(_single(props, 'md:ExtendedPresentation')) if ep is not None: dsl['extendedPresentation'] = ep add_bool_prop('passwordMode', 'PasswordMode', False) fmt = get_ml_value(_single(props, 'md:Format')) if fmt is not None: dsl['format'] = fmt efmt = get_ml_value(_single(props, 'md:EditFormat')) if efmt is not None: dsl['editFormat'] = efmt tt = get_ml_value(_single(props, 'md:ToolTip')) if tt is not None: dsl['tooltip'] = tt add_bool_prop('markNegatives', 'MarkNegatives', False) msk = P('Mask') if msk: dsl['mask'] = msk add_bool_prop('multiLine', 'MultiLine', False) add_bool_prop('extendedEdit', 'ExtendedEdit', False) for mm in (('MinValue', 'minValue'), ('MaxValue', 'maxValue')): mn = _single(props, "md:" + mm[0]) if mn is not None and _attr(mn, 'nil', NS_XSI) != 'true': mxt = _attr(mn, 'type', NS_XSI) if re.search(r'decimal$', mxt, re.I): dsl[mm[1]] = int(_text(mn)) if re.match(r'^-?\d+$', _text(mn)) else float(_text(mn)) else: dsl[mm[1]] = _text(mn) add_enum_prop('fillChecking', 'FillChecking', 'DontCheck') add_enum_prop('choiceFoldersAndItems', 'ChoiceFoldersAndItems', 'Items') cpl = parse_choice_parameter_links(props, 'md:ChoiceParameterLinks') if cpl is not None: dsl['choiceParameterLinks'] = cpl cp = parse_choice_parameters(props, 'md:ChoiceParameters') if cp is not None: dsl['choiceParameters'] = cp add_enum_prop('quickChoice', 'QuickChoice', 'Auto') cf = P('ChoiceForm') if cf: dsl['choiceForm'] = cf lbt_node = _single(props, 'md:LinkByType') if lbt_node is not None: dp_n = _single(lbt_node, 'md:DataPath') if dp_n is not None and _text(dp_n): li_n = _single(lbt_node, 'md:LinkItem') li = int(_text(li_n)) if (li_n is not None and _text(li_n)) else 0 dsl['linkByType'] = _text(dp_n) if li == 0 else {'dataPath': _text(dp_n), 'linkItem': li} add_enum_prop('dataHistory', 'DataHistory', 'DontUse') add_bool_prop('updateDataHistoryImmediatelyAfterWrite', 'UpdateDataHistoryImmediatelyAfterWrite', False) add_bool_prop('executeAfterWriteDataHistoryVersionProcessing', 'ExecuteAfterWriteDataHistoryVersionProcessing', False) # InputByString — эмитим только при отличии от выведенного дефолта. ib_node = _single(props, 'md:InputByString') if ib_node is not None: ib_actual = [_text(x) for x in _nodes(ib_node, 'xr:Field')] clv = P('CodeLength') dlv = P('DescriptionLength') cl = int(clv) if (clv is not None and clv != '') else 9 dl = int(dlv) if (dlv is not None and dlv != '') else 25 ib_def = [] if dl > 0: ib_def.append("StandardAttribute.Description") if cl > 0: ib_def.append("StandardAttribute.Code") ib_short = [short_field(x) for x in ib_actual] same = (len(ib_short) == len(ib_def)) if same: for k in range(len(ib_short)): if ib_short[k] != ib_def[k]: same = False break if not same: dsl['inputByString'] = list(ib_short) # BasedOn. bo_node = _single(props, 'md:BasedOn') if bo_node is not None: bo_items = [_text(x) for x in _nodes(bo_node, 'xr:Item')] if len(bo_items) > 0: dsl['basedOn'] = list(bo_items) # DataLockFields. dlf_node = _single(props, 'md:DataLockFields') if dlf_node is not None: dlf_fields = [short_field(_text(x)) for x in _nodes(dlf_node, 'xr:Field')] if len(dlf_fields) > 0: dsl['dataLockFields'] = list(dlf_fields) # Формы по умолчанию (omit-on-empty). add_form_ref('defaultObjectForm', 'DefaultObjectForm') add_form_ref('defaultFolderForm', 'DefaultFolderForm') add_form_ref('defaultListForm', 'DefaultListForm') add_form_ref('defaultChoiceForm', 'DefaultChoiceForm') add_form_ref('defaultFolderChoiceForm', 'DefaultFolderChoiceForm') add_form_ref('auxiliaryObjectForm', 'AuxiliaryObjectForm') add_form_ref('auxiliaryFolderForm', 'AuxiliaryFolderForm') add_form_ref('auxiliaryListForm', 'AuxiliaryListForm') add_form_ref('auxiliaryChoiceForm', 'AuxiliaryChoiceForm') add_form_ref('auxiliaryFolderChoiceForm', 'AuxiliaryFolderChoiceForm') add_form_ref('defaultRecordForm', 'DefaultRecordForm') add_form_ref('auxiliaryRecordForm', 'AuxiliaryRecordForm') # Презентации (ML, omit-on-empty). for pp in (('ObjectPresentation', 'objectPresentation'), ('ExtendedObjectPresentation', 'extendedObjectPresentation'), ('RecordPresentation', 'recordPresentation'), ('ExtendedRecordPresentation', 'extendedRecordPresentation'), ('ListPresentation', 'listPresentation'), ('ExtendedListPresentation', 'extendedListPresentation'), ('Explanation', 'explanation')): pv = get_ml_value(_single(props, "md:" + pp[0])) if pv is not None: dsl[pp[1]] = pv # --- Characteristics (привязка ПВХ). --- chars_node = _single(props, 'md:Characteristics') if chars_node is not None: ch_list = _nodes(chars_node, 'xr:Characteristic') if len(ch_list) > 0: ch_arr = [] for ch in ch_list: ct = _single(ch, 'xr:CharacteristicTypes') cv = _single(ch, 'xr:CharacteristicValues') t_from = _attr(ct, 'from') v_from = _attr(cv, 'from') def gt(n, node): x = _single(node, "xr:" + n) return _text(x) if x is not None else "" def giv(n, node): x = _single(node, "xr:" + n) return int(_text(x)) if (x is not None and _text(x) != '') else -1 tfv_node = _single(ct, 'xr:TypesFilterValue') tfv_nil = _attr(tfv_node, 'nil', NS_XSI) if tfv_node is not None else '' types = { 'from': t_from, 'key': shorten_char_field(gt('KeyField', ct), t_from), 'filterField': shorten_char_field(gt('TypesFilterField', ct), t_from), 'filterValue': None if tfv_nil == 'true' else convert_ch_scalar_node(tfv_node), } dpf = giv('DataPathField', ct) if dpf != -1: types['dataPathField'] = dpf mvu = giv('MultipleValuesUseField', ct) if mvu != -1: types['multipleValuesUseField'] = mvu values = { 'from': v_from, 'object': shorten_char_field(gt('ObjectField', cv), v_from), 'type': shorten_char_field(gt('TypeField', cv), v_from), 'value': shorten_char_field(gt('ValueField', cv), v_from), } mvk = giv('MultipleValuesKeyField', cv) if mvk != -1: values['multipleValuesKeyField'] = mvk mvo = giv('MultipleValuesOrderField', cv) if mvo != -1: values['multipleValuesOrderField'] = mvo ch_arr.append({'types': types, 'values': values}) dsl['characteristics'] = ch_arr # --- StandardAttributes: захватываем ОТКЛОНЕНИЯ от профиля материализованного блока. --- std_profile_by_type = { 'Catalog': { 'Owner': {'fillChecking': 'ShowError', 'fillFromFillingValue': True}, 'Parent': {'fillFromFillingValue': True}, 'Description': {'fillChecking': 'ShowError'}, }, 'ExchangePlan': { 'Description': {'fillChecking': 'ShowError'}, 'Code': {'fillChecking': 'ShowError'}, }, 'ChartOfCharacteristicTypes': { 'Description': {'fillChecking': 'ShowError'}, 'Parent': {'fillFromFillingValue': True}, }, 'ChartOfAccounts': { 'Description': {'fillChecking': 'ShowError'}, 'Code': {'fillChecking': 'ShowError'}, 'Parent': {'fillFromFillingValue': True}, }, 'ChartOfCalculationTypes': { 'Description': {'fillChecking': 'ShowError'}, }, 'Document': { 'Date': {'fillChecking': 'ShowError'}, }, } cat_std_profile = std_profile_by_type.get(obj_type, {}) std_fixed_by_type = { 'Catalog': ['PredefinedDataName', 'Predefined', 'Ref', 'DeletionMark', 'IsFolder', 'Owner', 'Parent', 'Description', 'Code'], 'ExchangePlan': ['Ref', 'DeletionMark', 'Code', 'Description', 'ThisNode', 'SentNo', 'ReceivedNo'], 'ChartOfCharacteristicTypes': ['PredefinedDataName', 'Predefined', 'Ref', 'DeletionMark', 'Description', 'Code', 'Parent', 'ValueType'], 'ChartOfAccounts': ['PredefinedDataName', 'Order', 'OffBalance', 'Type', 'Description', 'Code', 'Parent', 'Predefined', 'DeletionMark', 'Ref'], 'ChartOfCalculationTypes': ['PredefinedDataName', 'Predefined', 'Ref', 'DeletionMark', 'ActionPeriodIsBasic', 'Description', 'Code'], 'Document': ['Ref', 'DeletionMark', 'Date', 'Number', 'Posted'], 'Enum': ['Order', 'Ref'], 'DocumentJournal': ['Type', 'Ref', 'Date', 'Posted', 'DeletionMark', 'Number'], } std_fixed = std_fixed_by_type.get(obj_type, []) std_conditional_types = ['Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document'] sa_node = _single(props, 'md:StandardAttributes') if sa_node is not None: sa_map = {} for sa in _nodes(sa_node, 'xr:StandardAttribute'): an = _attr(sa, 'name') prof = cat_std_profile.get(an, {}) ov = {} # FillChecking (профиль или DontCheck) fc_n = _single(sa, 'xr:FillChecking') fc = _text(fc_n) if fc_n is not None else 'DontCheck' prof_fc = prof.get('fillChecking', 'DontCheck') if fc != prof_fc: ov['fillChecking'] = fc # FillFromFillingValue (профиль или false) ffv_n = _single(sa, 'xr:FillFromFillingValue') ffv = (ffv_n is not None and _text(ffv_n) == 'true') prof_ffv = (prof.get('fillFromFillingValue') is True) if ffv != prof_ffv: ov['fillFromFillingValue'] = ffv # Synonym / ToolTip (профиль пуст) syn = get_ml_value(_single(sa, 'xr:Synonym')) if syn is not None: ov['synonym'] = syn tt = get_ml_value(_single(sa, 'xr:ToolTip')) if tt is not None: ov['tooltip'] = tt # FullTextSearch / DataHistory (профиль = Use) fts_n = _single(sa, 'xr:FullTextSearch') if fts_n is not None and _text(fts_n) != 'Use': ov['fullTextSearch'] = _text(fts_n) dh_n = _single(sa, 'xr:DataHistory') if dh_n is not None and _text(dh_n) != 'Use': ov['dataHistory'] = _text(dh_n) # FillValue (дефолт nil). Comment/Mask/ChoiceForm (дефолт пусто). fv_n = _single(sa, 'xr:FillValue') if fv_n is not None and _attr(fv_n, 'nil', NS_XSI) != 'true': fv_xt = _attr(fv_n, 'type', NS_XSI) if re.search(r'DesignTimeRef$', fv_xt, re.I) and _text(fv_n) == '': ov['fillValue'] = {'emptyRef': True} elif re.search(r'TypeDescription$', fv_xt, re.I) and _text(fv_n) == '': ov['fillValue'] = {'typeDescription': True} # пустое типизированное (реквизит ValueType ПВХ) ≠ xs:string else: ov['fillValue'] = convert_ch_scalar_node(fv_n) sa_cmt = _single(sa, 'xr:Comment') if sa_cmt is not None and _text(sa_cmt): ov['comment'] = _text(sa_cmt) sa_msk = _single(sa, 'xr:Mask') if sa_msk is not None and _text(sa_msk): ov['mask'] = _text(sa_msk) sa_fmt = get_ml_value(_single(sa, 'xr:Format')) if sa_fmt is not None: ov['format'] = sa_fmt sa_efmt = get_ml_value(_single(sa, 'xr:EditFormat')) if sa_efmt is not None: ov['editFormat'] = sa_efmt sa_cf = _single(sa, 'xr:ChoiceForm') if sa_cf is not None and _text(sa_cf): ov['choiceForm'] = _text(sa_cf) sa_cpl = parse_choice_parameter_links(sa, 'xr:ChoiceParameterLinks') if sa_cpl is not None: ov['choiceParameterLinks'] = sa_cpl sa_cp = parse_choice_parameters(sa, 'xr:ChoiceParameters') if sa_cp is not None: ov['choiceParameters'] = sa_cp # LinkByType стандартного реквизита (ExtDimensionN→Account у регистра бухгалтерии). sa_lbt = _single(sa, 'xr:LinkByType') if sa_lbt is not None: sa_lbt_dp = _single(sa_lbt, 'xr:DataPath') if sa_lbt_dp is not None and _text(sa_lbt_dp): sa_lbt_li = _single(sa_lbt, 'xr:LinkItem') li = int(_text(sa_lbt_li)) if (sa_lbt_li is not None and _text(sa_lbt_li)) else 0 ov['linkByType'] = {'dataPath': _text(sa_lbt_dp), 'linkItem': li} # Доп./опциональный реквизит (не в фикс-списке) — эмитим по присутствию даже без отклонений. if len(ov) > 0 or (an not in std_fixed): sa_map[an] = ov if len(sa_map) > 0 or (obj_type in std_conditional_types): dsl['standardAttributes'] = sa_map elif obj_type in ('InformationRegister', 'AccumulationRegister', 'AccountingRegister', 'CalculationRegister', 'BusinessProcess', 'Task', 'Enum', 'DocumentJournal'): # Регистр/БП/Задача опускают all-default блок → opt-out `standardAttributes:""`. dsl['standardAttributes'] = '' # --- ChildObjects: Attributes + TabularSections --- child_objs = _single(obj_node, 'md:ChildObjects') if child_objs is not None: attrs = _nodes(child_objs, 'md:Attribute') if len(attrs) > 0: arr = [] for a in attrs: arr.append(attr_to_dsl(a)) dsl['attributes'] = arr # Enum: значения перечисления. ev_nodes = _nodes(child_objs, 'md:EnumValue') if len(ev_nodes) > 0: ev_arr = [] for ev in ev_nodes: evp = _single(ev, 'md:Properties') ev_name = _text(_single(evp, 'md:Name')) ev_syn_node = _single(evp, 'md:Synonym') ev_syn = get_ml_value(ev_syn_node) ev_cmt_n = _single(evp, 'md:Comment') ev_cmt = _text(ev_cmt_n) if ev_cmt_n is not None else '' ev_syn_val = None if isinstance(ev_syn, str): if ne_cs(ev_syn, split_camel_words(ev_name)): ev_syn_val = ev_syn elif ev_syn is not None: ev_syn_val = ev_syn elif ev_syn_node is not None: ev_syn_val = '' if (ev_syn_val is not None) or ev_cmt: o = {'name': ev_name} if ev_syn_val is not None: o['synonym'] = ev_syn_val if ev_cmt: o['comment'] = ev_cmt ev_arr.append(o) else: ev_arr.append(ev_name) dsl['values'] = ev_arr # DocumentJournal: колонки. col_nodes = _nodes(child_objs, 'md:Column') if len(col_nodes) > 0: col_arr = [] for col in col_nodes: cp = _single(col, 'md:Properties') c_name = _text(_single(cp, 'md:Name')) o = {'name': c_name} c_syn_node = _single(cp, 'md:Synonym') c_syn = get_ml_value(c_syn_node) if isinstance(c_syn, str): if ne_cs(c_syn, split_camel_words(c_name)): o['synonym'] = c_syn elif c_syn is not None: o['synonym'] = c_syn elif c_syn_node is not None: o['synonym'] = '' # пустой ≠ авто-синоним → явный '' c_cmt_n = _single(cp, 'md:Comment') if c_cmt_n is not None and _text(c_cmt_n): o['comment'] = _text(c_cmt_n) c_idx_n = _single(cp, 'md:Indexing') if c_idx_n is not None and _text(c_idx_n) != 'DontIndex': o['indexing'] = _text(c_idx_n) c_ref_node = _single(cp, 'md:References') refs = [] if c_ref_node is not None: for it in _nodes(c_ref_node, 'xr:Item'): refs.append(_text(it)) o['references'] = refs col_arr.append(o) dsl['columns'] = col_arr # ChartOfAccounts: признаки учёта. acct_flag_nodes = _nodes(child_objs, 'md:AccountingFlag') if len(acct_flag_nodes) > 0: arr = [] for a in acct_flag_nodes: arr.append(attr_to_dsl(a)) dsl['accountingFlags'] = arr ext_dim_flag_nodes = _nodes(child_objs, 'md:ExtDimensionAccountingFlag') if len(ext_dim_flag_nodes) > 0: arr = [] for a in ext_dim_flag_nodes: arr.append(attr_to_dsl(a)) dsl['extDimensionAccountingFlags'] = arr # Sequence: измерения несут DocumentMap/RegisterRecordsMap. dim_nodes = _nodes(child_objs, 'md:Dimension') if len(dim_nodes) > 0 and obj_type == 'Sequence': arr = [] for dn in dim_nodes: dp = _single(dn, 'md:Properties') d_name = _text(_single(dp, 'md:Name')) o = {'name': d_name} d_syn = get_ml_value(_single(dp, 'md:Synonym')) if isinstance(d_syn, str): if ne_cs(d_syn, split_camel_words(d_name)): o['synonym'] = d_syn elif d_syn is not None: o['synonym'] = d_syn d_cmt_n = _single(dp, 'md:Comment') if d_cmt_n is not None and _text(d_cmt_n): o['comment'] = _text(d_cmt_n) dt = get_type_shorthand(_single(dp, 'md:Type')) if dt: o['type'] = dt for mp in (('DocumentMap', 'documentMap'), ('RegisterRecordsMap', 'registerRecordsMap')): mn = _single(dp, "md:" + mp[0]) if mn is not None: m_items = [_text(x) for x in _nodes(mn, 'xr:Item')] if len(m_items) > 0: o[mp[1]] = list(m_items) arr.append(o) dsl['dimensions'] = arr elif len(dim_nodes) > 0: arr = [] for a in dim_nodes: arr.append(attr_to_dsl(a)) dsl['dimensions'] = arr res_nodes = _nodes(child_objs, 'md:Resource') if len(res_nodes) > 0: arr = [] for a in res_nodes: arr.append(attr_to_dsl(a)) dsl['resources'] = arr # Задача: реквизиты адресации. addr_nodes = _nodes(child_objs, 'md:AddressingAttribute') if len(addr_nodes) > 0: arr = [] for a in addr_nodes: arr.append(attr_to_dsl(a)) dsl['addressingAttributes'] = arr ts_nodes = _nodes(child_objs, 'md:TabularSection') if len(ts_nodes) > 0: ts_map = {} for ts in ts_nodes: tsp = _single(ts, 'md:Properties') ts_name = _text(_single(tsp, 'md:Name')) tco = _single(ts, 'md:ChildObjects') cols = [] if tco is not None: for ca in _nodes(tco, 'md:Attribute'): cols.append(attr_to_dsl(ca)) # Синоним/подсказка/комментарий ТЧ. ts_syn = get_ml_value(_single(tsp, 'md:Synonym')) ts_syn_custom = False if isinstance(ts_syn, str): if ne_cs(ts_syn, split_camel_words(ts_name)): ts_syn_custom = True elif ts_syn is not None: ts_syn_custom = True ts_tt = get_ml_value(_single(tsp, 'md:ToolTip')) ts_cmt_n = _single(tsp, 'md:Comment') ts_cmt = _text(ts_cmt_n) if ts_cmt_n is not None else '' ts_fc_n = _single(tsp, 'md:FillChecking') ts_fc = _text(ts_fc_n) if (ts_fc_n is not None and _text(ts_fc_n) != 'DontCheck') else '' # Use ТЧ (иерархические Catalog/ПВХ: ForItem/ForFolder/ForFolderAndItem; omit при дефолте ForItem). ts_use_n = _single(tsp, 'md:Use') ts_use = _text(ts_use_n) if (ts_use_n is not None and _text(ts_use_n) != 'ForItem') else '' # TS-блок стандартных реквизитов (LineNumber). ln_obj = {} sa_ts_node = _single(tsp, 'md:StandardAttributes') has_block = (sa_ts_node is not None and len(_nodes(sa_ts_node, 'xr:StandardAttribute')) > 0) ln_node = _single(sa_ts_node, "xr:StandardAttribute[@name='LineNumber']") if has_block else None if ln_node is not None: ln_syn = get_ml_value(_single(ln_node, 'xr:Synonym')) if ln_syn is not None: ln_obj['synonym'] = ln_syn ln_cmt_n = _single(ln_node, 'xr:Comment') if ln_cmt_n is not None and _text(ln_cmt_n): ln_obj['comment'] = _text(ln_cmt_n) ln_fts_n = _single(ln_node, 'xr:FullTextSearch') if ln_fts_n is not None and _text(ln_fts_n) != 'Use': ln_obj['fullTextSearch'] = _text(ln_fts_n) ln_tt = get_ml_value(_single(ln_node, 'xr:ToolTip')) if ln_tt is not None: ln_obj['tooltip'] = ln_tt ln_fmt = get_ml_value(_single(ln_node, 'xr:Format')) if ln_fmt is not None: ln_obj['format'] = ln_fmt ln_efmt = get_ml_value(_single(ln_node, 'xr:EditFormat')) if ln_efmt is not None: ln_obj['editFormat'] = ln_efmt ln_chi_n = _single(ln_node, 'xr:ChoiceHistoryOnInput') if ln_chi_n is not None and _text(ln_chi_n) != 'Auto': ln_obj['choiceHistoryOnInput'] = _text(ln_chi_n) ln_fv_n = _single(ln_node, 'xr:FillValue') if ln_fv_n is not None and _attr(ln_fv_n, 'nil', NS_XSI) != 'true': ln_fv_t = _attr(ln_fv_n, 'type', NS_XSI) if re.search(r'decimal$', ln_fv_t, re.I): ln_obj['fillValue'] = int(_text(ln_fv_n)) if re.match(r'^-?\d+$', _text(ln_fv_n)) else float(_text(ln_fv_n)) if ts_syn_custom or (ts_tt is not None) or ts_cmt or ts_fc or ts_use or len(ln_obj) > 0 or (not has_block): to = {} if ts_syn_custom: to['synonym'] = ts_syn if ts_tt is not None: to['tooltip'] = ts_tt if ts_cmt: to['comment'] = ts_cmt if ts_fc: to['fillChecking'] = ts_fc if ts_use: to['use'] = ts_use if not has_block: to['lineNumber'] = '' elif len(ln_obj) > 0: to['lineNumber'] = ln_obj to['attributes'] = cols ts_map[ts_name] = to else: ts_map[ts_name] = cols dsl['tabularSections'] = ts_map # --- Commands (полноблочные в ChildObjects) → DSL commands. --- cmd_nodes = _nodes(child_objs, 'md:Command') if len(cmd_nodes) > 0: cmd_map = {} for cm in cmd_nodes: cp = _single(cm, 'md:Properties') cn = _text(_single(cp, 'md:Name')) o = {} syn = get_ml_value(_single(cp, 'md:Synonym')) if isinstance(syn, str): if ne_cs(syn, split_camel_words(cn)): o['synonym'] = syn elif syn is not None: o['synonym'] = syn cmt_n = _single(cp, 'md:Comment') if cmt_n is not None and _text(cmt_n): o['comment'] = _text(cmt_n) grp_n = _single(cp, 'md:Group') if grp_n is not None and _text(grp_n): o['group'] = _text(grp_n) cpt = get_type_shorthand(_single(cp, 'md:CommandParameterType')) if cpt: o['commandParameterType'] = cpt pum_n = _single(cp, 'md:ParameterUseMode') if pum_n is not None and _text(pum_n) != 'Single': o['parameterUseMode'] = _text(pum_n) md_n = _single(cp, 'md:ModifiesData') if md_n is not None and _text(md_n) == 'true': o['modifiesData'] = True rep_n = _single(cp, 'md:Representation') if rep_n is not None and _text(rep_n) != 'Auto': o['representation'] = _text(rep_n) ctt = get_ml_value(_single(cp, 'md:ToolTip')) if ctt is not None: o['tooltip'] = ctt # — структурный блок. ref_n = _single(cp, 'md:Picture/xr:Ref') abs_n = _single(cp, 'md:Picture/xr:Abs') if ref_n is not None or abs_n is not None: psrc = _text(ref_n) if ref_n is not None else ("abs:" + _text(abs_n)) lt_n = _single(cp, 'md:Picture/xr:LoadTransparent') lt_false = (lt_n is not None and _text(lt_n) == 'false') tpx_n = _single(cp, 'md:Picture/xr:TransparentPixel') if tpx_n is not None: po = {'src': psrc} if lt_false: po['loadTransparent'] = False po['transparentPixel'] = {'x': int(_attr(tpx_n, 'x')), 'y': int(_attr(tpx_n, 'y'))} o['picture'] = po else: o['picture'] = psrc if lt_false: o['loadTransparent'] = False sc_n = _single(cp, 'md:Shortcut') if sc_n is not None and _text(sc_n): o['shortcut'] = _text(sc_n) osu_n = _single(cp, 'md:OnMainServerUnavalableBehavior') if osu_n is not None and _text(osu_n) != 'Auto': o['onMainServerUnavalableBehavior'] = _text(osu_n) cmd_map[cn] = o dsl['commands'] = cmd_map # --- Предопределённые (соседний Ext/Predefined.xml) → DSL predefined. --- obj_dir = os.path.dirname(os.path.abspath(object_path)) predef_path = os.path.join(obj_dir, obj_name, 'Ext', 'Predefined.xml') if os.path.isfile(predef_path): pdoc = etree.parse(predef_path).getroot() root_items = [] if obj_type == 'ChartOfAccounts': for it in _lxn(pdoc, "*[local-name()='Item']"): root_items.append(predef_account_to_dsl(it)) elif obj_type == 'ChartOfCalculationTypes': for it in _lxn(pdoc, "*[local-name()='Item']"): root_items.append(predef_calc_type_to_dsl(it)) else: for it in _lxn(pdoc, "*[local-name()='Item']"): root_items.append(predef_item_to_dsl(it)) if len(root_items) > 0: dsl['predefined'] = root_items # --- Состав плана обмена (соседний Ext/Content.xml) → DSL content (ExchangePlan). --- if obj_type == 'ExchangePlan': content_path = os.path.join(obj_dir, obj_name, 'Ext', 'Content.xml') if os.path.isfile(content_path): cdoc = etree.parse(content_path).getroot() content_items = [] for it in _lxn(cdoc, "*[local-name()='Item']"): md_el = _lx1(it, "*[local-name()='Metadata']") if md_el is None or not _text(md_el): continue ref = _text(md_el) ar_el = _lx1(it, "*[local-name()='AutoRecord']") ar = _text(ar_el) if ar_el is not None else 'Deny' if ar == 'Allow': content_items.append("%s: autoRecord" % ref) else: content_items.append(ref) if len(content_items) > 0: dsl['content'] = content_items SUPPORTED_TYPES = ( 'Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document', 'InformationRegister', 'AccumulationRegister', 'AccountingRegister', 'CalculationRegister', 'BusinessProcess', 'Task', 'Enum', 'Report', 'DataProcessor', 'Constant', 'DefinedType', 'FunctionalOption', 'DocumentJournal', 'Sequence', 'FilterCriterion', 'DocumentNumerator', 'SettingsStorage', 'CommonModule', 'EventSubscription', 'ScheduledJob', 'CommonForm', 'SessionParameter', 'CommonCommand', 'CommandGroup', 'CommonAttribute', 'FunctionalOptionsParameter', 'WSReference', 'CommonPicture', 'CommonTemplate', ) def main(): global props, obj_node, obj_type, obj_name, object_path parser = argparse.ArgumentParser(description='Decompile 1C metadata object XML to JSON DSL (draft)', allow_abbrev=False) parser.add_argument('-ObjectPath', '-Path', dest='ObjectPath', type=str, required=True) parser.add_argument('-OutputPath', dest='OutputPath', type=str, default=None) args = parser.parse_args() object_path = args.ObjectPath if not os.path.exists(object_path): sys.stderr.write("meta-decompile: файл не найден: %s\n" % object_path) sys.exit(2) # --- XML загрузка --- doc = etree.parse(object_path) root_el = doc.getroot() if _localname(root_el) != 'MetaDataObject': sys.stderr.write("meta-decompile: ожидался root , получен <%s>\n" % _localname(root_el)) sys.exit(3) # Первый элемент-потомок MetaDataObject = объект; его LocalName = тип. obj_node = None for c in root_el: if isinstance(c.tag, str): obj_node = c break if obj_node is None: sys.stderr.write("meta-decompile: пустой MetaDataObject\n") sys.exit(3) obj_type = _localname(obj_node) if obj_type not in SUPPORTED_TYPES: sys.stderr.write("meta-decompile: тип '%s' пока не поддержан (…, CommonPicture, CommonTemplate)\n" % obj_type) sys.exit(3) props = _single(obj_node, 'md:Properties') obj_name = P('Name') build_dsl() # === Вывод === json_str = convert_to_compact_json(dsl, 0) if args.OutputPath: with open(args.OutputPath, 'w', encoding='utf-8', newline='') as f: f.write(json_str) else: sys.stdout.write(json_str + "\n") if __name__ == '__main__': main()