mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-10 16:14:54 +03:00
b188d338f9
В шапке ссылочных объектов (справочники, документы, перечисления, ПВХ/ПВР, планы счетов, планы обмена, бизнес-процессы, задачи) теперь выводится строка «Представление типа» — имя ссылочного типа в диалогах выбора типа, с fallback ObjectPresentation -> Synonym -> Name. В режиме full дополнительно выводятся заданные сырые представления (объекта/списка и расширенные). Тесты: раннер принимает stdoutContains строкой или массивом, добавлен stdoutNotContains. Добавлены кейсы meta-info (ед.ч. ПВХ, full со всеми представлениями, fallback на синоним) и негативная проверка у регистра. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1133 lines
48 KiB
Python
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)
|