mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-10 16:14:54 +03:00
563 lines
23 KiB
Python
563 lines
23 KiB
Python
#!/usr/bin/env python3
|
|
# cf-info v1.2 — Compact summary of 1C configuration root
|
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
from collections import OrderedDict
|
|
from lxml import etree
|
|
|
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
sys.stderr.reconfigure(encoding="utf-8")
|
|
|
|
# --- Argument parsing ---
|
|
parser = argparse.ArgumentParser(description="Analyze 1C configuration structure", allow_abbrev=False)
|
|
parser.add_argument("-ConfigPath", "-Path", required=True, help="Path to Configuration.xml or directory")
|
|
parser.add_argument("-Mode", choices=["overview", "brief", "full"], default="overview", help="Output mode")
|
|
parser.add_argument("-Section", "-Name", choices=["home-page"], default=None, help="Drill-down section (alias: -Name)")
|
|
parser.add_argument("-Limit", type=int, default=150, help="Max lines to show")
|
|
parser.add_argument("-Offset", type=int, default=0, help="Lines to skip")
|
|
parser.add_argument("-OutFile", default="", help="Write output to file")
|
|
args = parser.parse_args()
|
|
|
|
# --- Output helper (collect all, paginate at the end) ---
|
|
lines_buf = []
|
|
|
|
def out(text=""):
|
|
lines_buf.append(text)
|
|
|
|
# --- Resolve path ---
|
|
config_path = args.ConfigPath
|
|
if not os.path.isabs(config_path):
|
|
config_path = os.path.join(os.getcwd(), config_path)
|
|
|
|
# Directory -> find Configuration.xml
|
|
if os.path.isdir(config_path):
|
|
candidate = os.path.join(config_path, "Configuration.xml")
|
|
if os.path.isfile(candidate):
|
|
config_path = candidate
|
|
else:
|
|
print(f"[ERROR] No Configuration.xml found in directory: {config_path}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
if not os.path.isfile(config_path):
|
|
print(f"[ERROR] File not found: {config_path}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# --- Load XML ---
|
|
tree = etree.parse(config_path, etree.XMLParser(remove_blank_text=False))
|
|
xml_root = tree.getroot()
|
|
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",
|
|
"app": "http://v8.1c.ru/8.2/managed-application/core",
|
|
}
|
|
|
|
md_root = xml_root # root is MetaDataObject itself
|
|
if etree.QName(md_root.tag).localname != "MetaDataObject":
|
|
print("[ERROR] Not a valid 1C metadata XML file (no MetaDataObject root)", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
cfg_node = md_root.find("md:Configuration", NS)
|
|
if cfg_node is None:
|
|
print("[ERROR] No <Configuration> element found", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
version = md_root.get("version", "")
|
|
props_node = cfg_node.find("md:Properties", NS)
|
|
child_obj_node = cfg_node.find("md:ChildObjects", NS)
|
|
|
|
# --- Helpers ---
|
|
def get_ml_text(node):
|
|
if node is None:
|
|
return ""
|
|
item = node.find("v8:item/v8:content", NS)
|
|
if item is not None and item.text:
|
|
return item.text
|
|
return ""
|
|
|
|
def get_prop_text(prop_name):
|
|
n = props_node.find(f"md:{prop_name}", NS)
|
|
if n is not None and n.text:
|
|
return n.text
|
|
return ""
|
|
|
|
def get_prop_ml(prop_name):
|
|
n = props_node.find(f"md:{prop_name}", NS)
|
|
return get_ml_text(n)
|
|
|
|
# --- Type name maps (canonical order, 44 types) ---
|
|
type_order = [
|
|
"Language", "Subsystem", "StyleItem", "Style",
|
|
"CommonPicture", "SessionParameter", "Role", "CommonTemplate",
|
|
"FilterCriterion", "CommonModule", "CommonAttribute", "ExchangePlan",
|
|
"XDTOPackage", "WebService", "HTTPService", "WSReference",
|
|
"EventSubscription", "ScheduledJob", "SettingsStorage", "FunctionalOption",
|
|
"FunctionalOptionsParameter", "DefinedType", "CommonCommand", "CommandGroup",
|
|
"Constant", "CommonForm", "Catalog", "Document",
|
|
"DocumentNumerator", "Sequence", "DocumentJournal", "Enum",
|
|
"Report", "DataProcessor", "InformationRegister", "AccumulationRegister",
|
|
"ChartOfCharacteristicTypes", "ChartOfAccounts", "AccountingRegister",
|
|
"ChartOfCalculationTypes", "CalculationRegister",
|
|
"BusinessProcess", "Task", "IntegrationService",
|
|
]
|
|
|
|
type_ru_names = {
|
|
"Language": "Языки", "Subsystem": "Подсистемы", "StyleItem": "Элементы стиля", "Style": "Стили",
|
|
"CommonPicture": "Общие картинки", "SessionParameter": "Параметры сеанса", "Role": "Роли",
|
|
"CommonTemplate": "Общие макеты", "FilterCriterion": "Критерии отбора", "CommonModule": "Общие модули",
|
|
"CommonAttribute": "Общие реквизиты", "ExchangePlan": "Планы обмена", "XDTOPackage": "XDTO-пакеты",
|
|
"WebService": "Веб-сервисы", "HTTPService": "HTTP-сервисы", "WSReference": "WS-ссылки",
|
|
"EventSubscription": "Подписки на события", "ScheduledJob": "Регламентные задания",
|
|
"SettingsStorage": "Хранилища настроек", "FunctionalOption": "Функциональные опции",
|
|
"FunctionalOptionsParameter": "Параметры ФО", "DefinedType": "Определяемые типы",
|
|
"CommonCommand": "Общие команды", "CommandGroup": "Группы команд", "Constant": "Константы",
|
|
"CommonForm": "Общие формы", "Catalog": "Справочники", "Document": "Документы",
|
|
"DocumentNumerator": "Нумераторы", "Sequence": "Последовательности", "DocumentJournal": "Журналы документов",
|
|
"Enum": "Перечисления", "Report": "Отчёты", "DataProcessor": "Обработки",
|
|
"InformationRegister": "Регистры сведений", "AccumulationRegister": "Регистры накопления",
|
|
"ChartOfCharacteristicTypes": "ПВХ", "ChartOfAccounts": "Планы счетов",
|
|
"AccountingRegister": "Регистры бухгалтерии", "ChartOfCalculationTypes": "ПВР",
|
|
"CalculationRegister": "Регистры расчёта", "BusinessProcess": "Бизнес-процессы",
|
|
"Task": "Задачи", "IntegrationService": "Сервисы интеграции",
|
|
}
|
|
|
|
# --- Read panel layout (Ext/ClientApplicationInterface.xml) ---
|
|
PANEL_NAMES = {
|
|
"cbab57f2-a0f3-4f0a-89ea-4cb19570ab75": "Открытых",
|
|
"b553047f-c9aa-4157-978d-448ecad24248": "Разделов",
|
|
"13322b22-3960-4d68-93a6-fe2dd7f28ca3": "Избранного",
|
|
"c933ac92-92cd-459d-81cc-e0c8a83ced99": "История",
|
|
"b2735bd3-d822-4430-ba59-c9e869693b24": "Функций",
|
|
}
|
|
CAI_NS = "http://v8.1c.ru/8.2/managed-application/core"
|
|
|
|
def get_panels_layout():
|
|
cfg_dir = os.path.dirname(config_path)
|
|
cai_path = os.path.join(cfg_dir, "Ext", "ClientApplicationInterface.xml")
|
|
if not os.path.isfile(cai_path):
|
|
return None
|
|
try:
|
|
cai_tree = etree.parse(cai_path)
|
|
except Exception:
|
|
return None
|
|
cai_root = cai_tree.getroot()
|
|
layout = {"top": [], "left": [], "right": [], "bottom": [], "declared": []}
|
|
for side in ("top", "left", "right", "bottom"):
|
|
for side_el in cai_root.findall(f"{{{CAI_NS}}}{side}"):
|
|
slot = []
|
|
for u in side_el.iter(f"{{{CAI_NS}}}uuid"):
|
|
key = (u.text or "").strip()
|
|
slot.append(PANEL_NAMES.get(key, f"?{key}"))
|
|
if slot:
|
|
layout[side].append(slot)
|
|
for pd in cai_root.findall(f"{{{CAI_NS}}}panelDef"):
|
|
key = pd.get("id", "")
|
|
layout["declared"].append(PANEL_NAMES.get(key, f"?{key}"))
|
|
return layout
|
|
|
|
def format_layout_slots(slots):
|
|
if not slots:
|
|
return ""
|
|
parts = []
|
|
for slot in slots:
|
|
if len(slot) == 1:
|
|
parts.append(slot[0])
|
|
else:
|
|
parts.append("Стек(" + ", ".join(slot) + ")")
|
|
return " | ".join(parts)
|
|
|
|
panel_layout = get_panels_layout()
|
|
|
|
# --- Read home page layout (Ext/HomePageWorkArea.xml) ---
|
|
HP_NS = "http://v8.1c.ru/8.3/xcf/extrnprops"
|
|
XR_NS_HP = "http://v8.1c.ru/8.3/xcf/readable"
|
|
|
|
def get_home_page_layout():
|
|
cfg_dir = os.path.dirname(config_path)
|
|
hp_path = os.path.join(cfg_dir, "Ext", "HomePageWorkArea.xml")
|
|
if not os.path.isfile(hp_path):
|
|
return None
|
|
try:
|
|
hp_tree = etree.parse(hp_path)
|
|
except Exception:
|
|
return None
|
|
hp_root = hp_tree.getroot()
|
|
result = {"template": "", "left": [], "right": []}
|
|
tn = hp_root.find(f"{{{HP_NS}}}WorkingAreaTemplate")
|
|
if tn is not None and tn.text:
|
|
result["template"] = tn.text.strip()
|
|
for col_name, key in (("LeftColumn", "left"), ("RightColumn", "right")):
|
|
col = hp_root.find(f"{{{HP_NS}}}{col_name}")
|
|
if col is None:
|
|
continue
|
|
items = []
|
|
for it in col.findall(f"{{{HP_NS}}}Item"):
|
|
f = it.find(f"{{{HP_NS}}}Form")
|
|
h = it.find(f"{{{HP_NS}}}Height")
|
|
vis = it.find(f"{{{HP_NS}}}Visibility")
|
|
common = True
|
|
roles = []
|
|
if vis is not None:
|
|
cn = vis.find(f"{{{XR_NS_HP}}}Common")
|
|
if cn is not None and cn.text:
|
|
common = cn.text.strip() == "true"
|
|
for v in vis.findall(f"{{{XR_NS_HP}}}Value"):
|
|
roles.append({"name": v.get("name", ""), "value": (v.text or "").strip() == "true"})
|
|
items.append({
|
|
"form": (f.text or "").strip() if f is not None else "",
|
|
"height": int((h.text or "10").strip()) if h is not None else 10,
|
|
"common": common,
|
|
"roles": roles,
|
|
})
|
|
result[key] = items
|
|
return result
|
|
|
|
home_page = get_home_page_layout()
|
|
|
|
def format_home_page_item(it, detailed):
|
|
badges = [f"h={it['height']}"]
|
|
if not it["common"]:
|
|
badges.append("скрыта")
|
|
if it["roles"]:
|
|
badges.append(f"роли: {len(it['roles'])}" if detailed else f"+{len(it['roles'])} ролей")
|
|
tail = f" ({', '.join(badges)})" if badges else ""
|
|
return f" {it['form']}{tail}"
|
|
|
|
# --- Count objects in ChildObjects ---
|
|
object_counts = OrderedDict()
|
|
total_objects = 0
|
|
|
|
if child_obj_node is not None:
|
|
for child in child_obj_node:
|
|
if not isinstance(child.tag, str):
|
|
continue # skip comments/PIs
|
|
type_name = etree.QName(child.tag).localname
|
|
if type_name not in object_counts:
|
|
object_counts[type_name] = 0
|
|
object_counts[type_name] += 1
|
|
total_objects += 1
|
|
|
|
# --- Read key properties ---
|
|
cfg_name = get_prop_text("Name")
|
|
cfg_synonym = get_prop_ml("Synonym")
|
|
cfg_version = get_prop_text("Version")
|
|
cfg_vendor = get_prop_text("Vendor")
|
|
cfg_compat = get_prop_text("CompatibilityMode")
|
|
cfg_ext_compat = get_prop_text("ConfigurationExtensionCompatibilityMode")
|
|
cfg_default_run = get_prop_text("DefaultRunMode")
|
|
cfg_script = get_prop_text("ScriptVariant")
|
|
cfg_default_lang = get_prop_text("DefaultLanguage")
|
|
cfg_data_lock = get_prop_text("DataLockControlMode")
|
|
dash = "\u2014"
|
|
cfg_modality = get_prop_text("ModalityUseMode")
|
|
cfg_intf_compat = get_prop_text("InterfaceCompatibilityMode")
|
|
cfg_auto_num = get_prop_text("ObjectAutonumerationMode")
|
|
cfg_sync_calls = get_prop_text("SynchronousPlatformExtensionAndAddInCallUseMode")
|
|
cfg_db_spaces = get_prop_text("DatabaseTablespacesUseMode")
|
|
cfg_window_mode = get_prop_text("MainClientApplicationWindowMode")
|
|
|
|
# --- BRIEF mode ---
|
|
if args.Mode == "brief" and not args.Section:
|
|
syn_part = f' {dash} "{cfg_synonym}"' if cfg_synonym else ""
|
|
ver_part = f" v{cfg_version}" if cfg_version else ""
|
|
compat_part = f" | {cfg_compat}" if cfg_compat else ""
|
|
out(f"Конфигурация: {cfg_name}{syn_part}{ver_part} | {total_objects} объектов{compat_part}")
|
|
|
|
# --- OVERVIEW mode ---
|
|
if args.Mode == "overview" and not args.Section:
|
|
syn_part = f' {dash} "{cfg_synonym}"' if cfg_synonym else ""
|
|
ver_part = f" v{cfg_version}" if cfg_version else ""
|
|
out(f"=== Конфигурация: {cfg_name}{syn_part}{ver_part} ===")
|
|
out()
|
|
|
|
# Key properties
|
|
out(f"Формат: {version}")
|
|
if cfg_vendor:
|
|
out(f"Поставщик: {cfg_vendor}")
|
|
if cfg_version:
|
|
out(f"Версия: {cfg_version}")
|
|
out(f"Совместимость: {cfg_compat}")
|
|
out(f"Режим запуска: {cfg_default_run}")
|
|
out(f"Язык скриптов: {cfg_script}")
|
|
out(f"Язык: {cfg_default_lang}")
|
|
out(f"Блокировки: {cfg_data_lock}")
|
|
out(f"Модальность: {cfg_modality}")
|
|
out(f"Интерфейс: {cfg_intf_compat}")
|
|
out()
|
|
|
|
if panel_layout and any(panel_layout[s] for s in ("top", "left", "right", "bottom")):
|
|
out("--- Раскладка панелей ---")
|
|
for s in ("top", "left", "right", "bottom"):
|
|
if panel_layout[s]:
|
|
out(f" {s.ljust(7)} {format_layout_slots(panel_layout[s])}")
|
|
out()
|
|
|
|
# Home page (brief summary)
|
|
if home_page:
|
|
out("--- Начальная страница ---")
|
|
out(f" Шаблон: {home_page['template']}")
|
|
out(f" LeftColumn: {len(home_page['left'])}, RightColumn: {len(home_page['right'])} (детали: -Section home-page)")
|
|
out()
|
|
|
|
# Object counts table
|
|
out(f"--- Состав ({total_objects} объектов) ---")
|
|
out()
|
|
max_type_len = 0
|
|
for type_name in type_order:
|
|
if type_name in object_counts:
|
|
ru_name = type_ru_names.get(type_name, type_name)
|
|
if len(ru_name) > max_type_len:
|
|
max_type_len = len(ru_name)
|
|
if max_type_len < 10:
|
|
max_type_len = 10
|
|
|
|
for type_name in type_order:
|
|
if type_name in object_counts:
|
|
count = object_counts[type_name]
|
|
ru_name = type_ru_names.get(type_name, type_name)
|
|
padded = ru_name.ljust(max_type_len)
|
|
out(f" {padded} {count}")
|
|
|
|
# --- FULL mode ---
|
|
# --- Drill-down: -Section home-page ---
|
|
if args.Section == "home-page":
|
|
if not home_page:
|
|
out("Файл Ext/HomePageWorkArea.xml не найден")
|
|
else:
|
|
out(f"=== Начальная страница: {cfg_name} ===")
|
|
out()
|
|
out(f"Шаблон: {home_page['template']}")
|
|
out()
|
|
for col_lbl, col_key in (("LeftColumn", "left"), ("RightColumn", "right")):
|
|
items = home_page[col_key]
|
|
if not items:
|
|
out(f"{col_lbl}: —")
|
|
out()
|
|
continue
|
|
out(f"{col_lbl} ({len(items)}):")
|
|
for it in items:
|
|
out(format_home_page_item(it, True))
|
|
for r in it["roles"]:
|
|
rval = "true" if r["value"] else "false"
|
|
out(f" {r['name']}: {rval}")
|
|
out()
|
|
|
|
if args.Mode == "full" and not args.Section:
|
|
syn_part = f' {dash} "{cfg_synonym}"' if cfg_synonym else ""
|
|
ver_part = f" v{cfg_version}" if cfg_version else ""
|
|
out(f"=== Конфигурация: {cfg_name}{syn_part}{ver_part} ===")
|
|
out()
|
|
|
|
# --- Section: Identification ---
|
|
out("--- Идентификация ---")
|
|
out(f"UUID: {cfg_node.get('uuid', '')}")
|
|
out(f"Имя: {cfg_name}")
|
|
if cfg_synonym:
|
|
out(f"Синоним: {cfg_synonym}")
|
|
cfg_comment = get_prop_text("Comment")
|
|
if cfg_comment:
|
|
out(f"Комментарий: {cfg_comment}")
|
|
cfg_prefix = get_prop_text("NamePrefix")
|
|
if cfg_prefix:
|
|
out(f"Префикс: {cfg_prefix}")
|
|
if cfg_vendor:
|
|
out(f"Поставщик: {cfg_vendor}")
|
|
if cfg_version:
|
|
out(f"Версия: {cfg_version}")
|
|
cfg_update_addr = get_prop_text("UpdateCatalogAddress")
|
|
if cfg_update_addr:
|
|
out(f"Каталог обн.: {cfg_update_addr}")
|
|
out()
|
|
|
|
# --- Section: Modes ---
|
|
out("--- Режимы работы ---")
|
|
out(f"Формат: {version}")
|
|
out(f"Совместимость: {cfg_compat}")
|
|
out(f"Совм. расширений: {cfg_ext_compat}")
|
|
out(f"Режим запуска: {cfg_default_run}")
|
|
out(f"Язык скриптов: {cfg_script}")
|
|
out(f"Блокировки: {cfg_data_lock}")
|
|
out(f"Автонумерация: {cfg_auto_num}")
|
|
out(f"Модальность: {cfg_modality}")
|
|
out(f"Синхр. вызовы: {cfg_sync_calls}")
|
|
out(f"Интерфейс: {cfg_intf_compat}")
|
|
out(f"Табл. пространства: {cfg_db_spaces}")
|
|
out(f"Режим окна: {cfg_window_mode}")
|
|
out()
|
|
|
|
# --- Section: Language, roles, purposes ---
|
|
out("--- Назначение ---")
|
|
out(f"Язык по умолч.: {cfg_default_lang}")
|
|
|
|
# UsePurposes
|
|
purpose_node = props_node.find("md:UsePurposes", NS)
|
|
if purpose_node is not None:
|
|
purposes = []
|
|
for val in purpose_node.findall("v8:Value", NS):
|
|
if val.text:
|
|
purposes.append(val.text)
|
|
if purposes:
|
|
out(f"Назначения: {', '.join(purposes)}")
|
|
|
|
# DefaultRoles
|
|
roles_node = props_node.find("md:DefaultRoles", NS)
|
|
if roles_node is not None:
|
|
roles = []
|
|
for item in roles_node.findall("xr:Item", NS):
|
|
if item.text:
|
|
roles.append(item.text)
|
|
if roles:
|
|
out(f"Роли по умолч.: {len(roles)}")
|
|
for r in roles:
|
|
out(f" - {r}")
|
|
|
|
# Booleans
|
|
use_mf = get_prop_text("UseManagedFormInOrdinaryApplication")
|
|
use_of = get_prop_text("UseOrdinaryFormInManagedApplication")
|
|
out(f"Управл.формы в обычн.: {use_mf}")
|
|
out(f"Обычн.формы в управл.: {use_of}")
|
|
out()
|
|
|
|
# --- Section: Panel layout ---
|
|
if panel_layout:
|
|
out("--- Раскладка панелей ---")
|
|
for s in ("top", "left", "right", "bottom"):
|
|
slots = panel_layout[s]
|
|
if slots:
|
|
out(f" {s.ljust(7)} {format_layout_slots(slots)}")
|
|
else:
|
|
out(f" {s.ljust(7)} —")
|
|
if panel_layout["declared"]:
|
|
out(f" объявлено: {', '.join(panel_layout['declared'])}")
|
|
out()
|
|
|
|
# --- Section: Home page (brief summary) ---
|
|
if home_page:
|
|
out("--- Начальная страница ---")
|
|
out(f" Шаблон: {home_page['template']}")
|
|
out(f" LeftColumn: {len(home_page['left'])}, RightColumn: {len(home_page['right'])} (детали: -Section home-page)")
|
|
out()
|
|
|
|
# --- Section: Storages & default forms ---
|
|
out("--- Хранилища и формы по умолчанию ---")
|
|
storage_props = [
|
|
"CommonSettingsStorage", "ReportsUserSettingsStorage", "ReportsVariantsStorage",
|
|
"FormDataSettingsStorage", "DynamicListsUserSettingsStorage", "URLExternalDataStorage",
|
|
]
|
|
for sp in storage_props:
|
|
val = get_prop_text(sp)
|
|
if val:
|
|
out(f" {sp}: {val}")
|
|
form_props = [
|
|
"DefaultReportForm", "DefaultReportVariantForm", "DefaultReportSettingsForm",
|
|
"DefaultReportAppearanceTemplate", "DefaultDynamicListSettingsForm", "DefaultSearchForm",
|
|
"DefaultDataHistoryChangeHistoryForm", "DefaultDataHistoryVersionDataForm",
|
|
"DefaultDataHistoryVersionDifferencesForm", "DefaultCollaborationSystemUsersChoiceForm",
|
|
"DefaultConstantsForm", "DefaultInterface", "DefaultStyle",
|
|
]
|
|
for fp in form_props:
|
|
val = get_prop_text(fp)
|
|
if val:
|
|
out(f" {fp}: {val}")
|
|
out()
|
|
|
|
# --- Section: Info ---
|
|
cfg_brief = get_prop_ml("BriefInformation")
|
|
cfg_detail = get_prop_ml("DetailedInformation")
|
|
cfg_copyright = get_prop_ml("Copyright")
|
|
cfg_vendor_addr = get_prop_ml("VendorInformationAddress")
|
|
cfg_info_addr = get_prop_ml("ConfigurationInformationAddress")
|
|
if cfg_brief or cfg_detail or cfg_copyright or cfg_vendor_addr or cfg_info_addr:
|
|
out("--- Информация ---")
|
|
if cfg_brief:
|
|
out(f"Краткая: {cfg_brief}")
|
|
if cfg_detail:
|
|
out(f"Подробная: {cfg_detail}")
|
|
if cfg_copyright:
|
|
out(f"Copyright: {cfg_copyright}")
|
|
if cfg_vendor_addr:
|
|
out(f"Сайт поставщика: {cfg_vendor_addr}")
|
|
if cfg_info_addr:
|
|
out(f"Адрес информ.: {cfg_info_addr}")
|
|
out()
|
|
|
|
# --- Section: Mobile functionalities ---
|
|
mobile_func = props_node.find("md:UsedMobileApplicationFunctionalities", NS)
|
|
if mobile_func is not None:
|
|
enabled_funcs = []
|
|
disabled_funcs = []
|
|
for func in mobile_func.findall("app:functionality", NS):
|
|
f_name = func.find("app:functionality", NS)
|
|
f_use = func.find("app:use", NS)
|
|
if f_name is not None and f_use is not None:
|
|
if f_use.text == "true":
|
|
enabled_funcs.append(f_name.text or "")
|
|
else:
|
|
disabled_funcs.append(f_name.text or "")
|
|
total_func = len(enabled_funcs) + len(disabled_funcs)
|
|
out(f"--- Мобильные функциональности ({total_func}, включено: {len(enabled_funcs)}) ---")
|
|
for f in enabled_funcs:
|
|
out(f" [+] {f}")
|
|
for f in disabled_funcs:
|
|
out(f" [-] {f}")
|
|
out()
|
|
|
|
# --- Section: InternalInfo ---
|
|
internal_info = cfg_node.find("md:InternalInfo", NS)
|
|
if internal_info is not None:
|
|
contained = internal_info.findall("xr:ContainedObject", NS)
|
|
out(f"--- InternalInfo ({len(contained)} ContainedObject) ---")
|
|
for co in contained:
|
|
class_id_node = co.find("xr:ClassId", NS)
|
|
object_id_node = co.find("xr:ObjectId", NS)
|
|
class_id = class_id_node.text if class_id_node is not None else ""
|
|
object_id = object_id_node.text if object_id_node is not None else ""
|
|
out(f" {class_id} -> {object_id}")
|
|
out()
|
|
|
|
# --- Section: ChildObjects (full list) ---
|
|
out(f"--- Состав ({total_objects} объектов) ---")
|
|
out()
|
|
|
|
for type_name in type_order:
|
|
if type_name not in object_counts:
|
|
continue
|
|
count = object_counts[type_name]
|
|
ru_name = type_ru_names.get(type_name, type_name)
|
|
out(f" {ru_name} ({type_name}): {count}")
|
|
|
|
# Collect names for this type
|
|
if child_obj_node is not None:
|
|
for child in child_obj_node:
|
|
if not isinstance(child.tag, str):
|
|
continue
|
|
if etree.QName(child.tag).localname == type_name:
|
|
out(f" {child.text or ''}")
|
|
|
|
# --- Pagination and output ---
|
|
total = len(lines_buf)
|
|
if args.Offset > 0 or args.Limit < total:
|
|
start = min(args.Offset, total)
|
|
end = min(start + args.Limit, total)
|
|
page = lines_buf[start:end]
|
|
result = "\n".join(page)
|
|
if end < total:
|
|
result += f"\n\n... ({end} of {total} lines, use -Offset {end} to continue)"
|
|
else:
|
|
result = "\n".join(lines_buf)
|
|
|
|
print(result)
|
|
|
|
if args.OutFile:
|
|
out_file = args.OutFile
|
|
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(result)
|
|
print(f"\nWritten to: {out_file}")
|