Files
cc-1c-skills/.opencode/skills/meta-info/scripts/meta-info.py
T

1464 lines
67 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# meta-info v1.8 — 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 ""
# Тип-множество: голое имя метатипа без `.Имя` означает ВСЕ ссылки этого класса
# (см. docs/meta-dsl-spec.md §«Тип-множество»). Конкретный тип всегда пишется с точкой,
# поэтому «СправочникСсылка» без точки читается однозначно как обобщённый.
def format_single_type_set(raw):
raw = re.sub(r'^d\d+p\d+:', 'cfg:', raw)
m = re.match(r'^cfg:DefinedType\.(.+)$', raw)
if m:
return f"ОпределяемыйТип.{m.group(1)}"
m = re.match(r'^cfg:Characteristic\.(.+)$', raw)
if m:
return f"Характеристика.{m.group(1)}"
if raw == "cfg:AnyRef":
return "ЛюбаяСсылка"
if raw == "cfg:AnyIBRef":
return "ЛюбаяСсылкаИБ"
m = re.match(r'^cfg:(\w+Ref)$', raw)
if m and m.group(1) in ref_type_map:
return ref_type_map[m.group(1)]
m = re.match(r'^cfg:(.+)$', raw)
if m:
return m.group(1)
return raw
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"):
types.append(format_single_type_set(inner_text(t)))
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)}]"
# Ссылка на объект метаданных (`Catalog.Валюты` в Owners и подобных) — не тип значения,
# но в выводе показываем именно тип, который присваивается: СправочникСсылка.Валюты.
def format_md_object_ref(raw):
m = re.match(r'^(\w+)\.(.+)$', raw)
if m:
key = f"{m.group(1)}Ref"
if key in ref_type_map:
return f"{ref_type_map[key]}.{m.group(2)}"
return raw
# ── Стандартные реквизиты ────────────────────────────────────
# Сколько типов состава печатать списком, прежде чем свернуть в счётчик. По корпусу
# acc/erp/ut/unf состав ПВХ доходит до 151 типа, при этом 20 из 42 ПВХ укладываются в 5.
COMPOSED_TYPE_THRESHOLD = 5
# Имена полей, состав которых свернули в счётчик, — по ним в конце вывода печатается
# единственная подсказка, чем состав развернуть.
collapsed_names = []
# Блок StandardAttributes в XML опционален: платформа материализует его только когда хотя бы
# один стандартный реквизит кастомизирован (docs/meta-dsl-spec.md §7.1.1). Когда блока нет,
# действуют платформенные дефолты — профиль ниже совпадает с $stdAttrProfile в meta-compile
# (выведен из корпуса acc+erp). Правки держать синхронными, иначе навыки разъедутся молча.
std_attr_required_default = {
"Catalog": {"Owner": True, "Description": True},
"Document": {"Date": True},
"ExchangePlan": {"Description": True, "Code": True},
"ChartOfAccounts": {"Description": True, "Code": True},
"ChartOfCharacteristicTypes": {"Description": True},
"ChartOfCalculationTypes": {"Description": True},
}
def test_std_attr_required(props_node, attr_name, obj_type):
fc = find(props_node, f"md:StandardAttributes/xr:StandardAttribute[@name='{attr_name}']/xr:FillChecking")
if fc is not None:
return inner_text(fc) == "ShowError"
return std_attr_required_default.get(obj_type, {}).get(attr_name, False)
def new_std_attr(name, type_str, flags):
f = [x for x in flags if x]
flag_str = f" [{', '.join(f)}]" if f else ""
return {"Name": name, "Type": type_str, "Flags": flag_str}
# Стандартные реквизиты, наличие и характеристики которых задаются настройками объекта, —
# их нельзя вывести из остального вывода, поэтому они попадают и в overview. Ссылка,
# ПометкаУдаления, Родитель и прочее следуют из типа объекта и строки «Иерархический» —
# они только в full (см. get_standard_attributes_full).
def get_standard_attributes(props_node, obj_type, mode_name):
result = []
# Владелец — только у подчинённого справочника; состав типов ниоткуда не выводится
if obj_type == "Catalog":
owners_node = find(props_node, "md:Owners")
owner_types = []
if owners_node is not None:
for it in find_all(owners_node, "xr:Item"):
owner_types.append(format_md_object_ref(inner_text(it)))
if owner_types:
req = "обязательный" if test_std_attr_required(props_node, "Owner", obj_type) else None
result.append(new_std_attr("Владелец", ", ".join(owner_types), [req]))
# ТипЗначения ПВХ — до полутора сотен типов, полный список раздул бы сводку в обоих
# режимах, поэтому сворачиваем в счётчик. Состав смотреть через -Name ТипЗначения.
if obj_type == "ChartOfCharacteristicTypes":
vt = find(props_node, "md:Type")
if vt is not None:
cnt = len(find_all(vt, "v8:Type")) + len(find_all(vt, "v8:TypeSet"))
if cnt > COMPOSED_TYPE_THRESHOLD:
type_str = f"Составной ({cnt})"
collapsed_names.append("ТипЗначения")
else:
type_str = format_type(vt)
if type_str:
result.append(new_std_attr("ТипЗначения", type_str, []))
# Дата — есть всегда, ценна флагом обязательности
if obj_type in ("Document", "BusinessProcess", "Task"):
req = "обязательный" if test_std_attr_required(props_node, "Date", obj_type) else None
result.append(new_std_attr("Дата", "Дата", [req]))
# Номер — тип и длина задаются объектом, при NumberLength=0 номера нет
if obj_type in ("Document", "BusinessProcess", "Task"):
num_len = find(props_node, "md:NumberLength")
if num_len is not None and inner_text(num_len).isdigit() and int(inner_text(num_len)) > 0:
num_type = find(props_node, "md:NumberType")
nt_ru = "Число" if num_type is not None and inner_text(num_type) == "Number" else "Строка"
flags = []
if test_std_attr_required(props_node, "Number", obj_type):
flags.append("обязательный")
num_per = find(props_node, "md:NumberPeriodicity")
if num_per is not None and inner_text(num_per) in number_period_map:
flags.append(number_period_map[inner_text(num_per)])
num_allowed = find(props_node, "md:NumberAllowedLength")
if num_allowed is not None and inner_text(num_allowed) == "Fixed":
flags.append("фикс. длина")
auto_num = find(props_node, "md:Autonumbering")
if auto_num is not None and inner_text(auto_num) == "true":
flags.append("авто")
result.append(new_std_attr("Номер", f"{nt_ru}({inner_text(num_len)})", flags))
# Код — при CodeLength=0 кода у объекта нет вовсе, строку не печатаем
code_len = find(props_node, "md:CodeLength")
if code_len is not None and inner_text(code_len).isdigit() and int(inner_text(code_len)) > 0:
code_type = find(props_node, "md:CodeType")
ct_ru = "Число" if code_type is not None and inner_text(code_type) == "Number" else "Строка"
flags = []
if test_std_attr_required(props_node, "Code", obj_type):
flags.append("обязательный")
code_allowed = find(props_node, "md:CodeAllowedLength")
if code_allowed is not None and inner_text(code_allowed) == "Fixed":
flags.append("фикс. длина")
result.append(new_std_attr("Код", f"{ct_ru}({inner_text(code_len)})", flags))
# Наименование — при DescriptionLength=0 наименования нет
desc_len = find(props_node, "md:DescriptionLength")
if desc_len is not None and inner_text(desc_len).isdigit() and int(inner_text(desc_len)) > 0:
req = "обязательный" if test_std_attr_required(props_node, "Description", obj_type) else None
result.append(new_std_attr("Наименование", f"Строка({inner_text(desc_len)})", [req]))
return result
# Остальные стандартные реквизиты — предсказуемы по типу объекта, но в full полезны
# как перечень доступных полей для запроса.
def get_standard_attributes_full(props_node, obj_type, obj_name):
self_ref_map = {
"Catalog": "СправочникСсылка",
"ChartOfCharacteristicTypes": "ПВХСсылка",
"ChartOfAccounts": "ПланСчетовСсылка",
"ChartOfCalculationTypes": "ПВРСсылка",
"ExchangePlan": "ПланОбменаСсылка",
"Document": "ДокументСсылка",
"BusinessProcess": "БизнесПроцессСсылка",
"Task": "ЗадачаСсылка",
}
self_ref = f"{self_ref_map[obj_type]}.{obj_name}" if obj_type in self_ref_map else ""
result = []
if self_ref:
result.append(new_std_attr("Ссылка", self_ref, []))
result.append(new_std_attr("ПометкаУдаления", "Булево", []))
# Родитель и ЭтоГруппа существуют только у иерархических объектов
hier = find(props_node, "md:Hierarchical")
is_hier = (hier is not None and inner_text(hier) == "true") or obj_type == "ChartOfAccounts"
if is_hier and self_ref:
result.append(new_std_attr("Родитель", self_ref, []))
ht = find(props_node, "md:HierarchyType")
if obj_type == "Catalog" and (ht is None or inner_text(ht) == "HierarchyFoldersAndItems"):
result.append(new_std_attr("ЭтоГруппа", "Булево", []))
if obj_type == "Document":
result.append(new_std_attr("Проведен", "Булево", []))
elif obj_type == "ExchangePlan":
result.append(new_std_attr("ЭтотУзел", "Булево", []))
result.append(new_std_attr("НомерОтправленного", "Число", []))
result.append(new_std_attr("НомерПринятого", "Число", []))
elif obj_type == "BusinessProcess":
result.append(new_std_attr("Стартован", "Булево", []))
result.append(new_std_attr("Завершен", "Булево", []))
result.append(new_std_attr("ВедущаяЗадача", "ЗадачаСсылка", []))
elif obj_type == "Task":
result.append(new_std_attr("Выполнена", "Булево", []))
result.append(new_std_attr("БизнесПроцесс", "БизнесПроцессСсылка", []))
result.append(new_std_attr("ТочкаМаршрута", "БизнесПроцессТочкаМаршрутаСсылка", []))
elif obj_type == "ChartOfAccounts":
result.append(new_std_attr("Вид", "ВидСчета", []))
result.append(new_std_attr("Забалансовый", "Булево", []))
result.append(new_std_attr("Порядок", "Число", []))
elif obj_type == "ChartOfCalculationTypes":
result.append(new_std_attr("ПериодДействияБазовый", "Булево", []))
# Предопределённые данные есть у всех перечисленных типов, кроме документов и задач
if obj_type in ("Catalog", "ChartOfCharacteristicTypes", "ChartOfAccounts", "ChartOfCalculationTypes"):
result.append(new_std_attr("Предопределенный", "Булево", []))
result.append(new_std_attr("ИмяПредопределенныхДанных", "Строка", []))
return result
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}"):
# Form/Template в ChildObjects — простые узлы с именем в тексте, а Command — узел
# с вложенным Properties: у него inner_text склеил бы всё содержимое в одну строку.
name_node = find(child, "md:Properties/md:Name")
result.append(inner_text(name_node) if name_node is not None else 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
# ── Support status of this object (Ext/ParentConfigurations.bin) ──
# See docs/1c-support-state-spec.md. Walks up to the config root, decodes the
# object's support rule. Never throws — degrades to "не на поддержке".
def _meta_is_external_root(xml_path):
if not os.path.isfile(xml_path):
return False
try:
mx = etree.parse(xml_path).getroot()
for child in mx:
if isinstance(child.tag, str):
return child.tag.split("}")[-1] in ("ExternalDataProcessor", "ExternalReport")
except Exception:
return False
return False
def get_object_support_status(obj_uuid):
try:
if _meta_is_external_root(object_path):
return None
d = os.path.dirname(object_path)
bin_path = None
for _ in range(8):
if not d:
break
cand = os.path.join(d, "Ext", "ParentConfigurations.bin")
if os.path.exists(cand) or os.path.exists(os.path.join(d, "Configuration.xml")):
bin_path = cand
break
parent = os.path.dirname(d)
if parent == d:
break
d = parent
if not bin_path or not os.path.exists(bin_path):
return "не на поддержке"
data = open(bin_path, "rb").read()
if len(data) <= 32:
return "снято с поддержки (правки свободны)"
if data[:3] == b"\xef\xbb\xbf":
data = data[3:]
text = data.decode("utf-8", "replace")
h = re.match(r"\{6,(\d+),(\d+),", text)
if not h:
return "не на поддержке"
g = int(h.group(1))
k = int(h.group(2))
if k == 0:
return "снято с поддержки (правки свободны)"
if g == 1:
return "конфигурация read-only (возможность изменения выключена) — правки невозможны без включения"
if not obj_uuid:
return "не на поддержке"
best = None
for m in re.finditer(r"([0-2]),0," + re.escape(obj_uuid.lower()), text):
f1 = int(m.group(1))
if best is None or f1 < best:
best = f1
if best is None:
return "не на поддержке"
return {
0: "на замке — прямая правка сломает обновления; дорабатывай через cfe-* либо включи редактирование объекта",
1: "редактируется с сохранением поддержки",
2: "снято с поддержки (правки свободны)",
}.get(best, "не на поддержке")
except Exception:
return "не на поддержке"
# ── 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
# Не нашли среди дочерних объектов — пробуем стандартные реквизиты ниже
# Drill-down по стандартному реквизиту — единственный способ увидеть состав типов целиком,
# когда в сводке он свёрнут в счётчик. Живёт в Properties, а не в ChildObjects, поэтому идёт
# отдельной веткой и работает даже у объектов без ChildObjects.
if drill_name and not drill_done:
std_all = get_standard_attributes(props, md_type, "full") + get_standard_attributes_full(props, md_type, obj_name)
for s in std_all:
if s["Name"] != drill_name:
continue
out(f"Стандартный реквизит: {s['Name']}")
# Состав типов разворачиваем списком: ради этого drill-down и нужен
type_list = []
type_src = find(props, "md:Type") if drill_name == "ТипЗначения" else None
if type_src is not None:
for t in find_all(type_src, "v8:Type"):
type_list.append(format_single_type(inner_text(t), type_src))
for t in find_all(type_src, "v8:TypeSet"):
type_list.append(format_single_type_set(inner_text(t)))
elif drill_name == "Владелец":
owners_node = find(props, "md:Owners")
if owners_node is not None:
for it in find_all(owners_node, "xr:Item"):
type_list.append(format_md_object_ref(inner_text(it)))
if type_list:
out(f" Типы ({len(type_list)}):")
for t in type_list:
out(f" {t}")
else:
out(f" Тип: {s['Type']}")
flag_text = s["Flags"].strip()
if flag_text:
out(f" Свойства: {flag_text.strip('[]')}")
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)
_support = get_object_support_status(type_node.get('uuid', ''))
if _support is not None:
out(f"Поддержка: {_support}")
# 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":
# Подчинённость — единственный факт структуры, который из brief не выводится никак,
# а без него код записи элемента падает. Про обязательность здесь намеренно молчим:
# обязательными бывают и обычные реквизиты, а их brief не разбирает.
if md_type == "Catalog":
owners_node = find(props, "md:Owners")
owner_names = []
if owners_node is not None:
for it in find_all(owners_node, "xr:Item"):
owner_names.append(re.sub(r'^\w+\.', '', inner_text(it)))
if owner_names:
out(f"Подчинён: {', '.join(owner_names)}")
# 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":
posting = find(props, "md:Posting")
parts = []
# Номер уехал в блок стандартных реквизитов — здесь остаются свойства объекта
if posting is not None:
parts.append(f"Проведение: {'да' if inner_text(posting) == 'Allow' else 'нет'}")
if parts:
out(" | ".join(parts))
# Свойства иерархии и подчинения. Иерархия — не только у справочников: свойство
# Hierarchical есть и у ПВХ (в корпусе acc/erp/ut/unf их 13), а без этой строки
# в full появлялся Родитель, происхождение которого было ниоткуда не видно.
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}")
# Код и Наименование уехали в блок стандартных реквизитов — здесь только свойства объекта.
# Подчинение печатаем лишь когда оно отличается от дефолта ToItems.
if md_type == "Catalog":
sub = find(props, "md:SubordinationUse")
if sub is not None and inner_text(sub) != "ToItems":
sub_ru = {"ToFolders": "группам", "ToFoldersAndItems": "группам и элементам"}.get(
inner_text(sub), inner_text(sub))
parts.append(f"Подчинение: {sub_ru}")
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}")
# Standard attributes
std_attrs = get_standard_attributes(props, md_type, mode)
if mode == "full":
std_attrs += get_standard_attributes_full(props, md_type, obj_name)
if std_attrs:
out("")
out("Стандартные реквизиты:")
ml = get_max_name_len(std_attrs)
for s in std_attrs:
out(format_attr_line(s, ml))
# 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)}")
# Единственная подсказка на весь вывод — чем развернуть свёрнутый в счётчик состав
if collapsed_names:
hints = ", ".join(f"-Name {n}" for n in collapsed_names)
out("")
out(f"Полный состав типов: {hints}")
# ── 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)