Files
cc-1c-skills/.claude/skills/meta-info/scripts/meta-info.py
T
Nick Shirokov b188d338f9 feat(meta-info): выводить «Представление типа» для ссылочных объектов
В шапке ссылочных объектов (справочники, документы, перечисления, ПВХ/ПВР,
планы счетов, планы обмена, бизнес-процессы, задачи) теперь выводится строка
«Представление типа» — имя ссылочного типа в диалогах выбора типа, с fallback
ObjectPresentation -> Synonym -> Name. В режиме full дополнительно выводятся
заданные сырые представления (объекта/списка и расширенные).

Тесты: раннер принимает stdoutContains строкой или массивом, добавлен
stdoutNotContains. Добавлены кейсы meta-info (ед.ч. ПВХ, full со всеми
представлениями, fallback на синоним) и негативная проверка у регистра.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 15:40:17 +03:00

1133 lines
48 KiB
Python

# meta-info v1.2 — Compact summary of 1C metadata object (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
import re
import sys
from lxml import etree
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
# ── arg parsing ──────────────────────────────────────────────
parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument("-ObjectPath", "-Path", required=True)
parser.add_argument("-Mode", choices=["overview", "brief", "full"], default="overview")
parser.add_argument("-Name", default="")
parser.add_argument("-Limit", type=int, default=150)
parser.add_argument("-Offset", type=int, default=0)
parser.add_argument("-OutFile", default="")
args = parser.parse_args()
object_path = args.ObjectPath
mode = args.Mode
drill_name = args.Name
limit = args.Limit
offset = args.Offset
out_file = args.OutFile
# ── output helper ────────────────────────────────────────────
lines = []
def out(text):
lines.append(text)
# ── resolve path ─────────────────────────────────────────────
if not os.path.isabs(object_path):
object_path = os.path.join(os.getcwd(), object_path)
if os.path.isdir(object_path):
dir_name = os.path.basename(object_path)
candidate = os.path.join(object_path, f"{dir_name}.xml")
sibling = os.path.join(os.path.dirname(object_path), f"{dir_name}.xml")
if os.path.exists(candidate):
object_path = candidate
elif os.path.exists(sibling):
object_path = sibling
else:
xml_files = [f for f in os.listdir(object_path) if f.endswith(".xml")]
if xml_files:
object_path = os.path.join(object_path, xml_files[0])
else:
print(f"[ERROR] No XML file found in directory: {object_path}")
sys.exit(1)
if not os.path.exists(object_path):
file_name = os.path.splitext(os.path.basename(object_path))[0]
parent_dir = os.path.dirname(object_path)
parent_dir_name = os.path.basename(parent_dir)
if file_name == parent_dir_name:
candidate = os.path.join(os.path.dirname(parent_dir), f"{file_name}.xml")
if os.path.exists(candidate):
object_path = candidate
if not os.path.exists(object_path):
print(f"[ERROR] File not found: {object_path}")
sys.exit(1)
# ── Load XML ─────────────────────────────────────────────────
NS = {
"md": "http://v8.1c.ru/8.3/MDClasses",
"v8": "http://v8.1c.ru/8.1/data/core",
"xr": "http://v8.1c.ru/8.3/xcf/readable",
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
"xs": "http://www.w3.org/2001/XMLSchema",
"cfg": "http://v8.1c.ru/8.1/data/enterprise/current-config",
"app": "http://v8.1c.ru/8.2/managed-application/core",
}
XSI_NS = NS["xsi"]
parser_xml = etree.XMLParser(remove_blank_text=False)
tree = etree.parse(object_path, parser_xml)
xml_root = tree.getroot()
def local_name(node):
return etree.QName(node.tag).localname
def find(parent, xpath):
if parent is None:
return None
r = parent.xpath(xpath, namespaces=NS)
return r[0] if r else None
def find_all(parent, xpath):
if parent is None:
return []
return parent.xpath(xpath, namespaces=NS)
def inner_text(node):
if node is None:
return ""
return node.text or ""
def text_of(node):
if node is None:
return ""
return (node.text or "").strip()
md_root = find(xml_root, "/md:MetaDataObject") if local_name(xml_root) != "MetaDataObject" else xml_root
if local_name(xml_root) == "MetaDataObject":
md_root = xml_root
else:
print("[ERROR] Not a valid 1C metadata XML file")
sys.exit(1)
# ── Detect object type ───────────────────────────────────────
type_node = None
md_type = ""
for child in md_root:
if not isinstance(child.tag, str):
continue
if etree.QName(child.tag).namespace == "http://v8.1c.ru/8.3/MDClasses":
type_node = child
md_type = local_name(child)
break
if type_node is None:
print("[ERROR] Cannot detect metadata type")
sys.exit(1)
# ── Type name maps ───────────────────────────────────────────
type_name_map = {
"Catalog": "Справочник", "Document": "Документ", "Enum": "Перечисление",
"Constant": "Константа", "InformationRegister": "Регистр сведений",
"AccumulationRegister": "Регистр накопления", "AccountingRegister": "Регистр бухгалтерии",
"CalculationRegister": "Регистр расчёта", "ChartOfAccounts": "План счетов",
"ChartOfCharacteristicTypes": "План видов характеристик",
"ChartOfCalculationTypes": "План видов расчёта", "BusinessProcess": "Бизнес-процесс",
"Task": "Задача", "ExchangePlan": "План обмена", "DocumentJournal": "Журнал документов",
"Report": "Отчёт", "DataProcessor": "Обработка",
"DefinedType": "Определяемый тип", "CommonModule": "Общий модуль",
"ScheduledJob": "Регламентное задание", "EventSubscription": "Подписка на событие",
"HTTPService": "HTTP-сервис", "WebService": "Веб-сервис",
}
ref_type_map = {
"CatalogRef": "СправочникСсылка", "DocumentRef": "ДокументСсылка",
"EnumRef": "ПеречислениеСсылка", "ChartOfAccountsRef": "ПланСчетовСсылка",
"ChartOfCharacteristicTypesRef": "ПВХСсылка", "ChartOfCalculationTypesRef": "ПВРСсылка",
"ExchangePlanRef": "ПланОбменаСсылка", "BusinessProcessRef": "БизнесПроцессСсылка",
"TaskRef": "ЗадачаСсылка",
}
reg_type_map = {
"AccumulationRegister": "РН", "AccountingRegister": "РБ",
"CalculationRegister": "РР", "InformationRegister": "РС",
}
period_map = {
"Nonperiodical": "Непериодический", "Day": "День", "Month": "Месяц",
"Quarter": "Квартал", "Year": "Год", "Second": "Секунда",
}
write_mode_map = {
"Independent": "независимая", "RecorderSubordinate": "подчинение регистратору",
}
reuse_map = {
"DontUse": "нет", "DuringRequest": "на время вызова", "DuringSession": "на время сеанса",
}
event_map = {
"BeforeWrite": "ПередЗаписью", "OnWrite": "ПриЗаписи", "AfterWrite": "ПослеЗаписи",
"BeforeDelete": "ПередУдалением", "Posting": "ОбработкаПроведения",
"UndoPosting": "ОбработкаУдаленияПроведения",
"OnReadAtServer": "ПриЧтенииНаСервере",
"FillCheckProcessing": "ОбработкаПроверкиЗаполнения",
}
object_type_map = {
"CatalogObject": "СправочникОбъект", "DocumentObject": "ДокументОбъект",
"ChartOfAccountsObject": "ПланСчетовОбъект",
"ChartOfCharacteristicTypesObject": "ПВХОбъект",
"BusinessProcessObject": "БизнесПроцессОбъект", "TaskObject": "ЗадачаОбъект",
"ExchangePlanObject": "ПланОбменаОбъект",
"InformationRegisterRecordSet": "НаборЗаписейРС",
"AccumulationRegisterRecordSet": "НаборЗаписейРН",
"AccountingRegisterRecordSet": "НаборЗаписейРБ",
}
number_period_map = {
"Year": "по году", "Quarter": "по кварталу", "Month": "по месяцу", "Day": "по дню",
"WholeCatalog": "сквозная",
}
ru_type_name = type_name_map.get(md_type, md_type)
# ── Helpers ──────────────────────────────────────────────────
def get_ml_text(node):
if node is None:
return ""
c = find(node, "v8:item[v8:lang='ru']/v8:content")
if c is not None:
return inner_text(c)
c = find(node, "v8:item/v8:content")
if c is not None:
return inner_text(c)
t = text_of(node)
if t:
return t
return ""
def format_type(type_node_el):
if type_node_el is None:
return ""
types = []
for t in find_all(type_node_el, "v8:Type"):
types.append(format_single_type(inner_text(t), type_node_el))
for t in find_all(type_node_el, "v8:TypeSet"):
raw = inner_text(t)
m = re.match(r'^cfg:DefinedType\.(.+)$', raw)
if m:
types.append(f"ОпределяемыйТип.{m.group(1)}")
continue
m = re.match(r'^cfg:Characteristic\.(.+)$', raw)
if m:
types.append(f"Характеристика.{m.group(1)}")
continue
types.append(raw)
if len(types) == 0:
return ""
if len(types) == 1:
return types[0]
return " | ".join(types)
def format_single_type(raw, parent_node):
if raw == "xs:string":
sq = find(parent_node, "v8:StringQualifiers/v8:Length")
length = inner_text(sq) if sq is not None else ""
return f"Строка({length})" if length else "Строка"
if raw == "xs:decimal":
dg = find(parent_node, "v8:NumberQualifiers/v8:Digits")
fr = find(parent_node, "v8:NumberQualifiers/v8:FractionDigits")
d = inner_text(dg) if dg is not None else ""
f = inner_text(fr) if fr is not None else "0"
return f"Число({d},{f})" if d else "Число"
if raw == "xs:boolean":
return "Булево"
if raw == "xs:dateTime":
dq = find(parent_node, "v8:DateQualifiers/v8:DateFractions")
if dq is not None:
dv = inner_text(dq)
if dv == "Date":
return "Дата"
if dv == "Time":
return "Время"
if dv == "DateTime":
return "ДатаВремя"
return "Дата"
return "ДатаВремя"
if raw == "v8:ValueStorage":
return "ХранилищеЗначения"
if raw == "v8:UUID":
return "УникальныйИдентификатор"
if raw == "v8:Null":
return "Null"
# Normalize d5p1:/dNpN: → cfg: (both map to same namespace)
raw = re.sub(r'^d\d+p\d+:', 'cfg:', raw)
# cfg:CatalogRef.Xxx -> СправочникСсылка.Xxx
m = re.match(r'^cfg:(\w+)Ref\.(.+)$', raw)
if m:
prefix = f"{m.group(1)}Ref"
objn = m.group(2)
if prefix in ref_type_map:
return f"{ref_type_map[prefix]}.{objn}"
# cfg:EnumRef.Xxx
m = re.match(r'^cfg:EnumRef\.(.+)$', raw)
if m:
return f"ПеречислениеСсылка.{m.group(1)}"
# cfg:Characteristic.Xxx
m = re.match(r'^cfg:Characteristic\.(.+)$', raw)
if m:
return f"Характеристика.{m.group(1)}"
# cfg:DefinedType.Xxx
m = re.match(r'^cfg:DefinedType\.(.+)$', raw)
if m:
return f"ОпределяемыйТип.{m.group(1)}"
# Strip cfg: prefix
m = re.match(r'^cfg:(.+)$', raw)
if m:
return m.group(1)
return raw
def format_flags(a_props, is_dimension=False):
flags = []
fc = find(a_props, "md:FillChecking")
if fc is not None and inner_text(fc) == "ShowError":
flags.append("обязательный")
idx = find(a_props, "md:Indexing")
if idx is not None:
iv = inner_text(idx)
if iv == "Index":
flags.append("индекс")
elif iv == "IndexWithAdditionalOrder":
flags.append("индекс+доп")
if is_dimension:
master = find(a_props, "md:Master")
if master is not None and inner_text(master) == "true":
flags.append("ведущее")
ml = find(a_props, "md:MultiLine")
if ml is not None and inner_text(ml) == "true":
flags.append("многострочный")
use = find(a_props, "md:Use")
if use is not None:
uv = inner_text(use)
if uv == "ForFolder":
flags.append("для папок")
elif uv == "ForFolderAndItem":
flags.append("для папок и элементов")
if not flags:
return ""
return f" [{', '.join(flags)}]"
def get_attributes(parent_node, child_tag="Attribute", is_dimension=False):
result = []
for attr in find_all(parent_node, f"md:{child_tag}"):
aprops = find(attr, "md:Properties")
if aprops is None:
continue
attr_name = inner_text(find(aprops, "md:Name"))
type_str = format_type(find(aprops, "md:Type"))
aflags = format_flags(aprops, is_dimension)
result.append({"Name": attr_name, "Type": type_str, "Flags": aflags, "Props": aprops})
return result
def get_tabular_sections(parent_node):
result = []
for ts in find_all(parent_node, "md:TabularSection"):
tprops = find(ts, "md:Properties")
ts_name = inner_text(find(tprops, "md:Name"))
tchild_objs = find(ts, "md:ChildObjects")
cols = get_attributes(tchild_objs) if tchild_objs is not None else []
result.append({"Name": ts_name, "Columns": cols, "ColCount": len(cols)})
return result
def format_attr_line(attr, max_name_len=30):
padded = attr["Name"].ljust(max_name_len)
return f" {padded} {attr['Type']}{attr['Flags']}"
def get_max_name_len(attrs):
mx = 10
for a in attrs:
if len(a["Name"]) > mx:
mx = len(a["Name"])
return min(mx + 2, 40)
def get_simple_children(parent_node, tag):
result = []
for child in find_all(parent_node, f"md:{tag}"):
result.append(inner_text(child))
return result
def sort_attrs_ref_first(attrs):
refs = []
prims = []
for a in attrs:
t = a["Type"]
if (re.search(r'Ссылка\.', t) or re.search(r'Характеристика\.', t) or
re.search(r'ОпределяемыйТип\.', t) or re.search(r'ПланСчетовСсылка', t) or
re.search(r'ПВХСсылка', t) or re.search(r'ПВРСсылка', t)):
refs.append(a)
else:
prims.append(a)
return refs + prims
def decline_cols(n):
m = n % 10
h = n % 100
if 11 <= h <= 19:
return "колонок"
if m == 1:
return "колонка"
if 2 <= m <= 4:
return "колонки"
return "колонок"
def format_source_type(raw):
raw = re.sub(r'^d\d+p\d+:', 'cfg:', raw)
m = re.match(r'^cfg:(\w+)\.(.+)$', raw)
if m:
prefix = m.group(1)
name = m.group(2)
if prefix in object_type_map:
return f"{object_type_map[prefix]}.{name}"
m = re.match(r'^cfg:(.+)$', raw)
if m:
return m.group(1)
return raw
def get_http_endpoints(child_objs):
result = []
for tpl in find_all(child_objs, "md:URLTemplate"):
tp = find(tpl, "md:Properties")
tpl_name = inner_text(find(tp, "md:Name"))
template = inner_text(find(tp, "md:Template"))
methods = []
tpl_co = find(tpl, "md:ChildObjects")
if tpl_co is not None:
for m in find_all(tpl_co, "md:Method"):
mp = find(m, "md:Properties")
http_method = inner_text(find(mp, "md:HTTPMethod"))
handler = inner_text(find(mp, "md:Handler"))
methods.append({"HTTPMethod": http_method, "Handler": handler,
"Name": inner_text(find(mp, "md:Name"))})
result.append({"Name": tpl_name, "Template": template, "Methods": methods})
return result
def get_ws_operations(child_objs):
result = []
for op in find_all(child_objs, "md:Operation"):
oprops = find(op, "md:Properties")
op_name = inner_text(find(oprops, "md:Name"))
ret_type_el = find(oprops, "md:XDTOReturningValueType")
ret_str = inner_text(ret_type_el) if ret_type_el is not None and inner_text(ret_type_el) else "void"
proc_name_el = find(oprops, "md:ProcedureName")
params = []
op_co = find(op, "md:ChildObjects")
if op_co is not None:
for p in find_all(op_co, "md:Parameter"):
pp = find(p, "md:Properties")
p_name = inner_text(find(pp, "md:Name"))
p_type_el = find(pp, "md:XDTOValueType")
p_type_str = inner_text(p_type_el) if p_type_el is not None else "?"
dir_el = find(pp, "md:TransferDirection")
dir_str = f" [{inner_text(dir_el).lower()}]" if dir_el is not None and inner_text(dir_el) != "In" else ""
params.append(f"{p_name}: {p_type_str}{dir_str}")
param_str = ", ".join(params)
result.append({"Name": op_name, "Params": param_str, "ReturnType": ret_str,
"ProcName": inner_text(proc_name_el) if proc_name_el is not None else ""})
return result
# ── Extract metadata ─────────────────────────────────────────
props = find(type_node, "md:Properties")
child_objs = find(type_node, "md:ChildObjects")
obj_name = inner_text(find(props, "md:Name"))
syn_node = find(props, "md:Synonym")
synonym = get_ml_text(syn_node)
# Presentations (type-choice dialogs show "Представление объекта" as the ref type name)
obj_presentation = get_ml_text(find(props, "md:ObjectPresentation"))
ext_obj_presentation = get_ml_text(find(props, "md:ExtendedObjectPresentation"))
list_presentation = get_ml_text(find(props, "md:ListPresentation"))
ext_list_presentation = get_ml_text(find(props, "md:ExtendedListPresentation"))
# Reference (ref-typed) metadata objects — those with a ...Ref type
ref_md_types = {"Catalog", "Document", "Enum", "ChartOfAccounts",
"ChartOfCharacteristicTypes", "ChartOfCalculationTypes",
"ExchangePlan", "BusinessProcess", "Task"}
is_ref_object = md_type in ref_md_types
# Effective type presentation: ObjectPresentation -> Synonym -> Name
type_presentation = obj_presentation or synonym or obj_name
# ── Handle -Name drill-down ──────────────────────────────────
drill_done = False
if drill_name and child_objs is not None:
# Search in attributes/dimensions/resources
attr_tags = ["Attribute", "Dimension", "Resource"]
for tag in attr_tags:
if drill_done:
break
for attr in find_all(child_objs, f"md:{tag}"):
ap = find(attr, "md:Properties")
if ap is None:
continue
an = inner_text(find(ap, "md:Name"))
if an == drill_name:
tag_ru = {"Attribute": "Реквизит", "Dimension": "Измерение", "Resource": "Ресурс"}[tag]
out(f"{tag_ru}: {an}")
type_str = format_type(find(ap, "md:Type"))
out(f" Тип: {type_str}")
fc = find(ap, "md:FillChecking")
out(f" Обязательный: {'да' if fc is not None and inner_text(fc) == 'ShowError' else 'нет'}")
idx = find(ap, "md:Indexing")
idx_val = inner_text(idx) if idx is not None else "DontIndex"
if idx_val == "DontIndex" or not idx_val:
idx_ru = "нет"
elif idx_val == "Index":
idx_ru = "Индекс"
elif idx_val == "IndexWithAdditionalOrder":
idx_ru = "Индекс с доп. упорядочиванием"
else:
idx_ru = idx_val
out(f" Индексирование: {idx_ru}")
ml = find(ap, "md:MultiLine")
if ml is not None and inner_text(ml) == "true":
out(" Многострочный: да")
use = find(ap, "md:Use")
if use is not None and inner_text(use) != "ForItem":
use_val = inner_text(use)
use_ru = {"ForFolder": "для папок", "ForFolderAndItem": "для папок и элементов"}.get(use_val, use_val)
out(f" Использование: {use_ru}")
fv = find(ap, "md:FillValue")
if fv is not None and fv.get(f"{{{XSI_NS}}}nil") != "true" and inner_text(fv):
fv_text = inner_text(fv)
if fv_text.endswith(".EmptyRef"):
fv_text = "Пустая ссылка"
elif fv_text == "false":
fv_text = "Ложь"
elif fv_text == "true":
fv_text = "Истина"
out(f" Значение заполнения: {fv_text}")
else:
out(" Значение заполнения: \u2014")
if tag == "Dimension":
master = find(ap, "md:Master")
out(f" Ведущее: {'да' if master is not None and inner_text(master) == 'true' else 'нет'}")
mf = find(ap, "md:MainFilter")
out(f" Основной отбор: {'да' if mf is not None and inner_text(mf) == 'true' else 'нет'}")
syn_a = find(ap, "md:Synonym")
syn_text = get_ml_text(syn_a)
if syn_text and syn_text != an:
out(f" Синоним: {syn_text}")
drill_done = True
break
# Search in tabular sections
if not drill_done:
for ts in find_all(child_objs, "md:TabularSection"):
tp = find(ts, "md:Properties")
tn = inner_text(find(tp, "md:Name"))
if tn == drill_name:
ts_co = find(ts, "md:ChildObjects")
cols = get_attributes(ts_co) if ts_co is not None else []
out(f"ТЧ: {tn} ({len(cols)} {decline_cols(len(cols))}):")
if cols:
ml = get_max_name_len(cols)
for c in cols:
out(format_attr_line(c, ml))
drill_done = True
break
# Search in enum values
if not drill_done:
for ev in find_all(child_objs, "md:EnumValue"):
ep = find(ev, "md:Properties")
en = inner_text(find(ep, "md:Name"))
if en == drill_name:
syn_e = find(ep, "md:Synonym")
syn_text = get_ml_text(syn_e)
out(f"Значение перечисления: {en}")
if syn_text:
out(f' Синоним: "{syn_text}"')
cm = find(ep, "md:Comment")
if cm is not None and inner_text(cm):
out(f" Комментарий: {inner_text(cm)}")
drill_done = True
break
# Search in HTTPService URLTemplates
if not drill_done and md_type == "HTTPService":
for tpl in find_all(child_objs, "md:URLTemplate"):
tp = find(tpl, "md:Properties")
if inner_text(find(tp, "md:Name")) == drill_name:
template = inner_text(find(tp, "md:Template"))
out(f"Шаблон URL: {drill_name}")
out(f" Путь: {template}")
tpl_co = find(tpl, "md:ChildObjects")
if tpl_co is not None:
for m in find_all(tpl_co, "md:Method"):
mp = find(m, "md:Properties")
http_method = inner_text(find(mp, "md:HTTPMethod"))
handler = inner_text(find(mp, "md:Handler"))
out(f" {http_method} \u2192 {handler}")
drill_done = True
break
# Search in WebService Operations
if not drill_done and md_type == "WebService":
for op in find_all(child_objs, "md:Operation"):
oprops = find(op, "md:Properties")
if inner_text(find(oprops, "md:Name")) == drill_name:
out(f"Операция: {drill_name}")
ret_type_el = find(oprops, "md:XDTOReturningValueType")
out(f" Возвращает: {inner_text(ret_type_el) if ret_type_el is not None and inner_text(ret_type_el) else 'void'}")
proc_name_el = find(oprops, "md:ProcedureName")
if proc_name_el is not None and inner_text(proc_name_el):
out(f" Процедура: {inner_text(proc_name_el)}")
comment_el = find(oprops, "md:Comment")
if comment_el is not None and inner_text(comment_el):
out(f" Комментарий: {inner_text(comment_el)}")
op_co = find(op, "md:ChildObjects")
if op_co is not None:
params_els = find_all(op_co, "md:Parameter")
if params_els:
out(" Параметры:")
for p in params_els:
pp = find(p, "md:Properties")
p_name = inner_text(find(pp, "md:Name"))
p_type_el = find(pp, "md:XDTOValueType")
dir_el = find(pp, "md:TransferDirection")
dir_str = f" [{inner_text(dir_el).lower()}]" if dir_el is not None and inner_text(dir_el) != "In" else ""
out(f" {p_name}: {inner_text(p_type_el) if p_type_el is not None else '?'}{dir_str}")
drill_done = True
break
if not drill_done:
print(f"[ERROR] '{drill_name}' not found in {obj_name}")
sys.exit(1)
# ── Main output (not drill-down) ─────────────────────────────
if not drill_done:
# Build header
header = f"=== {ru_type_name}: {obj_name}"
if synonym and synonym != obj_name:
header += f' \u2014 "{synonym}"'
header += " ==="
out(header)
# Type presentation (ref objects)
if is_ref_object:
out(f"Представление типа: {type_presentation}")
if mode == "full":
if obj_presentation:
out(f"Представление объекта: {obj_presentation}")
if ext_obj_presentation:
out(f"Расширенное представление объекта: {ext_obj_presentation}")
if list_presentation:
out(f"Представление списка: {list_presentation}")
if ext_list_presentation:
out(f"Расширенное представление списка: {ext_list_presentation}")
if mode == "brief":
# Attributes
attrs = get_attributes(child_objs) if child_objs is not None else []
if attrs:
names = ", ".join(a["Name"] for a in attrs)
out(f"Реквизиты ({len(attrs)}): {names}")
# Dimensions/Resources for registers
if md_type.endswith("Register"):
dims = get_attributes(child_objs, "Dimension", True) if child_objs is not None else []
if dims:
names = ", ".join(d["Name"] for d in dims)
out(f"Измерения ({len(dims)}): {names}")
res = get_attributes(child_objs, "Resource") if child_objs is not None else []
if res:
names = ", ".join(r["Name"] for r in res)
out(f"Ресурсы ({len(res)}): {names}")
# Tabular sections
tss = get_tabular_sections(child_objs) if child_objs is not None else []
if tss:
ts_parts = [f"{t['Name']}({t['ColCount']})" for t in tss]
out(f"ТЧ ({len(tss)}): {', '.join(ts_parts)}")
# Enum values
if md_type == "Enum" and child_objs is not None:
vals = []
for ev in find_all(child_objs, "md:EnumValue"):
ep = find(ev, "md:Properties")
vals.append(inner_text(find(ep, "md:Name")))
if vals:
out(f"Значения ({len(vals)}): {', '.join(vals)}")
# DefinedType brief
if md_type == "DefinedType":
type_node2 = find(props, "md:Type")
if type_node2 is not None:
types = []
for t in find_all(type_node2, "v8:Type"):
types.append(format_single_type(inner_text(t), type_node2))
if types:
out(f"Типы ({len(types)}): {', '.join(types)}")
# CommonModule brief
if md_type == "CommonModule":
flags = []
for flag_name, flag_label in [("Global", "Глобальный"), ("Server", "Сервер"),
("ServerCall", "Вызов сервера"),
("ClientManagedApplication", "Клиент управляемое"),
("ClientOrdinaryApplication", "Обычный клиент"),
("ExternalConnection", "Внешнее соединение"),
("Privileged", "Привилегированный")]:
n = find(props, f"md:{flag_name}")
if n is not None and inner_text(n) == "true":
flags.append(flag_label)
reuse = find(props, "md:ReturnValuesReuse")
if reuse is not None and inner_text(reuse) != "DontUse":
reuse_ru = reuse_map.get(inner_text(reuse), inner_text(reuse))
flags.append(f"Повторное использование: {reuse_ru}")
if flags:
out(" | ".join(flags))
# ScheduledJob brief
if md_type == "ScheduledJob":
method = find(props, "md:MethodName")
if method is not None and inner_text(method):
m_name = inner_text(method)
m2 = re.match(r'^CommonModule\.(.+)$', m_name)
if m2:
m_name = m2.group(1)
out(f"Метод: {m_name}")
sj_parts = []
use = find(props, "md:Use")
sj_parts.append(f"Использование: {'да' if use is not None and inner_text(use) == 'true' else 'нет'}")
predef = find(props, "md:Predefined")
sj_parts.append(f"Предопределённое: {'да' if predef is not None and inner_text(predef) == 'true' else 'нет'}")
restart_cnt = find(props, "md:RestartCountOnFailure")
restart_int = find(props, "md:RestartIntervalOnFailure")
if restart_cnt is not None and inner_text(restart_cnt).isdigit() and int(inner_text(restart_cnt)) > 0:
sj_parts.append(f"Перезапуск: {inner_text(restart_cnt)} (через {inner_text(restart_int)} сек)")
out(" | ".join(sj_parts))
# EventSubscription brief
if md_type == "EventSubscription":
es_parts = []
event = find(props, "md:Event")
if event is not None and inner_text(event):
ev_ru = event_map.get(inner_text(event), inner_text(event))
es_parts.append(f"Событие: {ev_ru}")
handler = find(props, "md:Handler")
if handler is not None and inner_text(handler):
h_name = inner_text(handler)
m2 = re.match(r'^CommonModule\.(.+)$', h_name)
if m2:
h_name = m2.group(1)
es_parts.append(f"Обработчик: {h_name}")
source = find(props, "md:Source")
if source is not None:
src_count = len(find_all(source, "v8:Type"))
if src_count > 0:
es_parts.append(f"Источники: {src_count}")
if es_parts:
out(" | ".join(es_parts))
# HTTPService brief
if md_type == "HTTPService":
root_url = find(props, "md:RootURL")
if root_url is not None and inner_text(root_url):
out(f"Корневой URL: /{inner_text(root_url)}")
if child_objs is not None:
endpoints = get_http_endpoints(child_objs)
if endpoints:
total_methods = sum(len(ep["Methods"]) for ep in endpoints)
out(f"Шаблоны: {len(endpoints)} | Методы: {total_methods}")
# WebService brief
if md_type == "WebService":
ns_url = find(props, "md:Namespace")
if ns_url is not None and inner_text(ns_url):
out(f"Пространство имён: {inner_text(ns_url)}")
if child_objs is not None:
ops = get_ws_operations(child_objs)
if ops:
out(f"Операции: {len(ops)}")
else:
# mode: overview / full
# Document-specific header
if md_type == "Document":
num_type = find(props, "md:NumberType")
num_len = find(props, "md:NumberLength")
num_per = find(props, "md:NumberPeriodicity")
auto_num = find(props, "md:Autonumbering")
posting = find(props, "md:Posting")
parts = []
if num_type is not None and num_len is not None:
nt = "Строка" if inner_text(num_type) == "String" else "Число"
piece = f"Номер: {nt}({inner_text(num_len)})"
if num_per is not None:
per_ru = number_period_map.get(inner_text(num_per), inner_text(num_per))
piece += f", {per_ru}"
if auto_num is not None and inner_text(auto_num) == "true":
piece += ", авто"
parts.append(piece)
if posting is not None:
parts.append(f"Проведение: {'да' if inner_text(posting) == 'Allow' else 'нет'}")
if parts:
out(" | ".join(parts))
# Catalog-specific header
if md_type == "Catalog":
parts = []
hier = find(props, "md:Hierarchical")
if hier is not None and inner_text(hier) == "true":
ht = find(props, "md:HierarchyType")
ht_text = "группы и элементы" if ht is not None and inner_text(ht) == "HierarchyFoldersAndItems" else "элементы"
limit_node = find(props, "md:LimitLevelCount")
level_node = find(props, "md:LevelCount")
if limit_node is not None and inner_text(limit_node) == "true" and level_node is not None:
ht_text += f", уровней: {inner_text(level_node)}"
else:
ht_text += ", без ограничения уровней"
parts.append(f"Иерархический: {ht_text}")
code_len = find(props, "md:CodeLength")
desc_len = find(props, "md:DescriptionLength")
if code_len is not None and inner_text(code_len).isdigit() and int(inner_text(code_len)) > 0:
parts.append(f"Код({inner_text(code_len)})")
if desc_len is not None and inner_text(desc_len).isdigit() and int(inner_text(desc_len)) > 0:
parts.append(f"Наименование({inner_text(desc_len)})")
if parts:
out(" | ".join(parts))
# Register-specific header
if md_type.endswith("Register"):
parts = []
if md_type == "InformationRegister":
per = find(props, "md:InformationRegisterPeriodicity")
if per is not None:
per_ru = period_map.get(inner_text(per), inner_text(per))
parts.append(f"Периодичность: {per_ru}")
wm = find(props, "md:WriteMode")
if wm is not None:
wm_ru = write_mode_map.get(inner_text(wm), inner_text(wm))
parts.append(f"Запись: {wm_ru}")
if md_type == "AccumulationRegister":
reg_kind = find(props, "md:RegisterType")
if reg_kind is not None:
rk_val = inner_text(reg_kind)
rk_ru = {"Balances": "остатки", "Turnovers": "обороты"}.get(rk_val, rk_val)
parts.append(f"Вид: {rk_ru}")
if parts:
out(" | ".join(parts))
# Constant
if md_type == "Constant":
type_str = format_type(find(props, "md:Type"))
if type_str:
out(f"Тип: {type_str}")
# Report: MainDataCompositionSchema
if md_type == "Report":
main_dcs = find(props, "md:MainDataCompositionSchema")
if main_dcs is not None and inner_text(main_dcs):
dcs_name = inner_text(main_dcs)
m2 = re.search(r'\.Template\.(.+)$', dcs_name)
if m2:
dcs_name = m2.group(1)
out(f"Основная СКД: {dcs_name}")
# DefinedType
if md_type == "DefinedType":
type_node2 = find(props, "md:Type")
if type_node2 is not None:
types = []
for t in find_all(type_node2, "v8:Type"):
types.append(format_single_type(inner_text(t), type_node2))
if types:
out(f"Типы ({len(types)}):")
for t in types:
out(f" {t}")
# CommonModule
if md_type == "CommonModule":
flags = []
for flag_name, flag_label in [("Global", "Глобальный"), ("Server", "Сервер"),
("ServerCall", "Вызов сервера"),
("ClientManagedApplication", "Клиент управляемое"),
("ClientOrdinaryApplication", "Обычный клиент"),
("ExternalConnection", "Внешнее соединение"),
("Privileged", "Привилегированный")]:
n = find(props, f"md:{flag_name}")
if n is not None and inner_text(n) == "true":
flags.append(flag_label)
reuse = find(props, "md:ReturnValuesReuse")
if reuse is not None and inner_text(reuse) != "DontUse":
reuse_ru = reuse_map.get(inner_text(reuse), inner_text(reuse))
flags.append(f"Повторное использование: {reuse_ru}")
if flags:
out(" | ".join(flags))
# ScheduledJob
if md_type == "ScheduledJob":
method = find(props, "md:MethodName")
if method is not None and inner_text(method):
m_name = inner_text(method)
m2 = re.match(r'^CommonModule\.(.+)$', m_name)
if m2:
m_name = m2.group(1)
out(f"Метод: {m_name}")
sj_parts = []
use = find(props, "md:Use")
sj_parts.append(f"Использование: {'да' if use is not None and inner_text(use) == 'true' else 'нет'}")
predef = find(props, "md:Predefined")
sj_parts.append(f"Предопределённое: {'да' if predef is not None and inner_text(predef) == 'true' else 'нет'}")
restart_cnt = find(props, "md:RestartCountOnFailure")
restart_int = find(props, "md:RestartIntervalOnFailure")
if restart_cnt is not None and inner_text(restart_cnt).isdigit() and int(inner_text(restart_cnt)) > 0:
sj_parts.append(f"Перезапуск: {inner_text(restart_cnt)} (через {inner_text(restart_int)} сек)")
out(" | ".join(sj_parts))
# EventSubscription
if md_type == "EventSubscription":
event = find(props, "md:Event")
if event is not None and inner_text(event):
ev_ru = event_map.get(inner_text(event), inner_text(event))
out(f"Событие: {ev_ru}")
handler = find(props, "md:Handler")
if handler is not None and inner_text(handler):
h_name = inner_text(handler)
m2 = re.match(r'^CommonModule\.(.+)$', h_name)
if m2:
h_name = m2.group(1)
out(f"Обработчик: {h_name}")
source = find(props, "md:Source")
if source is not None:
src_types = []
for t in find_all(source, "v8:Type"):
src_types.append(format_source_type(inner_text(t)))
if src_types:
if mode == "full":
out(f"Источники ({len(src_types)}):")
for s in src_types:
out(f" {s}")
else:
out(f"Источники ({len(src_types)})")
# HTTPService
if md_type == "HTTPService":
root_url = find(props, "md:RootURL")
if root_url is not None and inner_text(root_url):
out(f"Корневой URL: /{inner_text(root_url)}")
if child_objs is not None:
endpoints = get_http_endpoints(child_objs)
if endpoints:
out("")
out(f"Шаблоны URL ({len(endpoints)}):")
for ep in endpoints:
out(f" {ep['Template']}")
for m in ep["Methods"]:
out(f" {m['HTTPMethod'].ljust(6)} \u2192 {m['Handler']}")
# WebService
if md_type == "WebService":
ns_url = find(props, "md:Namespace")
if ns_url is not None and inner_text(ns_url):
out(f"Пространство имён: {inner_text(ns_url)}")
if child_objs is not None:
ops = get_ws_operations(child_objs)
if ops:
out("")
out(f"Операции ({len(ops)}):")
for op in ops:
out(f" {op['Name']}({op['Params']}) \u2192 {op['ReturnType']}")
# Enum values
if md_type == "Enum" and child_objs is not None:
vals = []
for ev in find_all(child_objs, "md:EnumValue"):
ep = find(ev, "md:Properties")
v_name = inner_text(find(ep, "md:Name"))
v_syn = get_ml_text(find(ep, "md:Synonym"))
vals.append({"Name": v_name, "Synonym": v_syn})
if vals:
out("")
out(f"Значения ({len(vals)}):")
ml = get_max_name_len(vals)
for v in vals:
padded = v["Name"].ljust(ml)
syn_text = f'"{v["Synonym"]}"' if v["Synonym"] and v["Synonym"] != v["Name"] else ""
out(f" {padded} {syn_text}")
# Dimensions (registers)
if md_type.endswith("Register") and child_objs is not None:
dims = get_attributes(child_objs, "Dimension", True)
if dims:
out("")
out(f"Измерения ({len(dims)}):")
ml = get_max_name_len(dims)
for d in dims:
out(format_attr_line(d, ml))
# Resources (registers)
if md_type.endswith("Register") and child_objs is not None:
res = get_attributes(child_objs, "Resource")
if res:
out("")
out(f"Ресурсы ({len(res)}):")
ml = get_max_name_len(res)
for r in res:
out(format_attr_line(r, ml))
# Attributes
if child_objs is not None and md_type != "Enum":
attrs = get_attributes(child_objs)
if attrs:
out("")
out(f"Реквизиты ({len(attrs)}):")
sorted_attrs = sort_attrs_ref_first(attrs)
ml = get_max_name_len(sorted_attrs)
for a in sorted_attrs:
out(format_attr_line(a, ml))
# Tabular sections
if child_objs is not None and md_type != "Enum":
tss = get_tabular_sections(child_objs)
if tss:
if mode == "full":
for ts in tss:
out("")
out(f"ТЧ {ts['Name']} ({ts['ColCount']} {decline_cols(ts['ColCount'])}):")
if ts["ColCount"] > 0:
sorted_cols = sort_attrs_ref_first(ts["Columns"])
ml = get_max_name_len(sorted_cols)
for c in sorted_cols:
out(format_attr_line(c, ml))
else:
out("")
ts_parts = [f"{t['Name']}({t['ColCount']})" for t in tss]
out(f"ТЧ ({len(tss)}): {', '.join(ts_parts)}")
# Forms/Templates/Commands in overview for Reports & DataProcessors
if mode == "overview" and child_objs is not None and md_type in ("Report", "DataProcessor"):
forms = get_simple_children(child_objs, "Form")
if forms:
out(f"Формы: {', '.join(forms)}")
templates = get_simple_children(child_objs, "Template")
if templates:
out(f"Макеты: {', '.join(templates)}")
commands = get_simple_children(child_objs, "Command")
if commands:
out(f"Команды: {', '.join(commands)}")
# Full mode: additional sections
if mode == "full" and child_objs is not None:
# Register records (documents)
if md_type == "Document":
reg_recs = []
for item in find_all(props, "md:RegisterRecords/xr:Item"):
raw = inner_text(item)
m2 = re.match(r'^(\w+)\.(.+)$', raw)
if m2:
prefix = m2.group(1)
rname = m2.group(2)
short = reg_type_map.get(prefix, prefix)
reg_recs.append(f"{short}.{rname}")
else:
reg_recs.append(raw)
if reg_recs:
out("")
out(f"Движения ({len(reg_recs)}): {', '.join(reg_recs)}")
# BasedOn
based_on = []
for item in find_all(props, "md:BasedOn/xr:Item"):
raw = inner_text(item)
m2 = re.match(r'^\w+\.(.+)$', raw)
if m2:
based_on.append(m2.group(1))
else:
based_on.append(raw)
if based_on:
out(f"Ввод на основании: {', '.join(based_on)}")
# Forms
forms = get_simple_children(child_objs, "Form")
if forms:
out(f"Формы: {', '.join(forms)}")
# Templates
templates = get_simple_children(child_objs, "Template")
if templates:
out(f"Макеты: {', '.join(templates)}")
# Commands
commands = get_simple_children(child_objs, "Command")
if commands:
out(f"Команды: {', '.join(commands)}")
# ── Pagination and output ────────────────────────────────────
total_lines = len(lines)
out_lines = lines[:]
if offset > 0:
if offset >= total_lines:
print(f"[INFO] Offset {offset} exceeds total lines ({total_lines}). Nothing to show.")
sys.exit(0)
out_lines = out_lines[offset:]
if limit > 0 and len(out_lines) > limit:
shown = out_lines[:limit]
remaining = total_lines - offset - limit
shown.append("")
shown.append(f"[ОБРЕЗАНО] Показано {limit} из {total_lines} строк. Используйте -Offset {offset + limit} для продолжения.")
out_lines = shown
if out_file:
if not os.path.isabs(out_file):
out_file = os.path.join(os.getcwd(), out_file)
with open(out_file, "w", encoding="utf-8-sig") as f:
f.write("\n".join(out_lines))
print(f"Output written to {out_file}")
else:
for ln in out_lines:
print(ln)