mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-08 12:33:21 +03:00
Compare commits
39
Commits
@@ -1,4 +1,4 @@
|
||||
# cf-edit v1.10 — Edit 1C configuration root (Configuration.xml)
|
||||
# cf-edit v1.11 — Edit 1C configuration root (Configuration.xml)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][Alias('Path')][string]$ConfigPath,
|
||||
@@ -167,6 +167,11 @@ $script:xmlDoc = New-Object System.Xml.XmlDocument
|
||||
$script:xmlDoc.PreserveWhitespace = $true
|
||||
$script:xmlDoc.Load($resolvedPath)
|
||||
|
||||
# Версия формата редактируемой конфигурации — создаваемые рядом файлы (Ext/HomePageWorkArea.xml)
|
||||
# должны нести ту же версию, иначе в проекте окажутся файлы разных версий формата.
|
||||
$script:formatVersion = $script:xmlDoc.DocumentElement.GetAttribute("version")
|
||||
if (-not $script:formatVersion) { $script:formatVersion = "2.17" }
|
||||
|
||||
$script:addCount = 0
|
||||
$script:removeCount = 0
|
||||
$script:modifyCount = 0
|
||||
@@ -864,7 +869,7 @@ function Do-SetHomePage($valArg) {
|
||||
|
||||
$hpXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<HomePageWorkArea xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<HomePageWorkArea xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$($script:formatVersion)">
|
||||
<WorkingAreaTemplate>$tmpl</WorkingAreaTemplate>
|
||||
$leftXml
|
||||
$rightXml
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cf-edit v1.10 — Edit 1C configuration root (Configuration.xml)
|
||||
# cf-edit v1.11 — Edit 1C configuration root (Configuration.xml)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -410,6 +410,10 @@ def main():
|
||||
tree = etree.parse(resolved_path, xml_parser)
|
||||
xml_root = tree.getroot()
|
||||
|
||||
# Версия формата редактируемой конфигурации — создаваемые рядом файлы (Ext/HomePageWorkArea.xml)
|
||||
# должны нести ту же версию, иначе в проекте окажутся файлы разных версий формата.
|
||||
format_version = xml_root.get('version') or '2.17'
|
||||
|
||||
add_count = 0
|
||||
remove_count = 0
|
||||
modify_count = 0
|
||||
@@ -959,7 +963,7 @@ def main():
|
||||
'<HomePageWorkArea xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" '
|
||||
'xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" '
|
||||
'xmlns:xs="http://www.w3.org/2001/XMLSchema" '
|
||||
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">\r\n'
|
||||
f'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{format_version}">\r\n'
|
||||
f'\t<WorkingAreaTemplate>{tmpl}</WorkingAreaTemplate>\r\n'
|
||||
f'{left_xml}\r\n'
|
||||
f'{right_xml}\r\n'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# cf-init v1.2 — Create empty 1C configuration scaffold
|
||||
# cf-init v1.3 — Create empty 1C configuration scaffold
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -7,7 +7,12 @@ param(
|
||||
[string]$OutputDir = "src",
|
||||
[string]$Version,
|
||||
[string]$Vendor,
|
||||
[string]$CompatibilityMode = "Version8_3_24"
|
||||
[string]$CompatibilityMode = "Version8_3_24",
|
||||
# Версия формата выгрузки (MDClasses). Её задаёт ПЛАТФОРМА, которой выгружают, и от режима
|
||||
# совместимости она не зависит: 8.3.24 пишет 2.17, 8.3.27 — 2.20. Дефолт консервативный —
|
||||
# 2.17 читается всеми поддерживаемыми платформами.
|
||||
[ValidateSet("2.17", "2.20", "2.21")]
|
||||
[string]$FormatVersion = "2.17"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
@@ -73,7 +78,7 @@ $versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version
|
||||
# --- Configuration.xml ---
|
||||
$cfgXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$FormatVersion">
|
||||
<Configuration uuid="$uuidCfg">
|
||||
<InternalInfo>
|
||||
<xr:ContainedObject>
|
||||
@@ -175,7 +180,7 @@ $cfgXml = @"
|
||||
# --- Languages/Русский.xml ---
|
||||
$langXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$FormatVersion">
|
||||
<Language uuid="$uuidLang">
|
||||
<Properties>
|
||||
<Name>Русский</Name>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cf-init v1.2 — Create empty 1C configuration scaffold
|
||||
# cf-init v1.3 — Create empty 1C configuration scaffold
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""Generates minimal XML source files for a 1C configuration."""
|
||||
import sys, os, argparse, uuid
|
||||
@@ -24,6 +24,9 @@ def main():
|
||||
parser.add_argument('-Version', dest='Version', default='')
|
||||
parser.add_argument('-Vendor', dest='Vendor', default='')
|
||||
parser.add_argument('-CompatibilityMode', dest='CompatibilityMode', default='Version8_3_24')
|
||||
# Версия формата выгрузки (MDClasses) — её задаёт ПЛАТФОРМА, а не режим совместимости:
|
||||
# 8.3.24 пишет 2.17, 8.3.27 — 2.20. Дефолт консервативный: 2.17 читается всеми платформами.
|
||||
parser.add_argument('-FormatVersion', dest='FormatVersion', default='2.17', choices=['2.17', '2.20', '2.21'])
|
||||
args = parser.parse_args()
|
||||
|
||||
name = args.Name
|
||||
@@ -96,7 +99,7 @@ def main():
|
||||
\t\t\t</xr:ContainedObject>\n"""
|
||||
|
||||
cfg_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{args.FormatVersion}">
|
||||
\t<Configuration uuid="{uuid_cfg}">
|
||||
\t\t<InternalInfo>
|
||||
{contained_objects}\t\t</InternalInfo>
|
||||
@@ -168,7 +171,7 @@ def main():
|
||||
|
||||
# --- Languages/Русский.xml ---
|
||||
lang_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{args.FormatVersion}">
|
||||
\t<Language uuid="{uuid_lang}">
|
||||
\t\t<Properties>
|
||||
\t\t\t<Name>Русский</Name>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# form-add v1.10 — Add managed form to 1C config object
|
||||
# form-add v1.11 — Add managed form to 1C config object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -484,7 +484,10 @@ if (-not $childObjects) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Добавить <Form>$FormName</Form>
|
||||
# Добавить <Form>$FormName</Form> — идемпотентно (не дублировать уже зарегистрированную)
|
||||
$alreadyRegistered = [bool]$childObjects.SelectSingleNode("md:Form[text()='$FormName']", $nsMgr)
|
||||
|
||||
if (-not $alreadyRegistered) {
|
||||
$formElem = $xmlDoc.CreateElement("Form", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$formElem.InnerText = $FormName
|
||||
|
||||
@@ -538,6 +541,7 @@ if ($insertBefore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- SetDefault ---
|
||||
|
||||
@@ -603,7 +607,11 @@ Write-Host " Metadata: $objDirName\$objBaseName\Forms\$FormName.xml"
|
||||
Write-Host " Form: $objDirName\$objBaseName\Forms\$FormName\Ext\Form.xml"
|
||||
Write-Host " Module: $objDirName\$objBaseName\Forms\$FormName\Ext\Form\Module.bsl"
|
||||
Write-Host ""
|
||||
Write-Host "Registered: <Form>$FormName</Form> in ChildObjects"
|
||||
if ($alreadyRegistered) {
|
||||
Write-Host "Already registered: <Form>$FormName</Form> in ChildObjects (skipped duplicate)"
|
||||
} else {
|
||||
Write-Host "Registered: <Form>$FormName</Form> in ChildObjects"
|
||||
}
|
||||
if ($defaultUpdated) {
|
||||
Write-Host "${defaultPropName}: $defaultValue"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-add v1.10 — Add managed form to 1C config object
|
||||
# form-add v1.11 — Add managed form to 1C config object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -592,47 +592,50 @@ def main():
|
||||
print(f"Не найден элемент ChildObjects в {object_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Add <Form>$FormName</Form>
|
||||
form_elem = etree.Element(f"{{{ns}}}Form")
|
||||
form_elem.text = form_name
|
||||
# Add <Form>$FormName</Form> — idempotent (do not duplicate already-registered form)
|
||||
already_registered = child_objects.find(f"md:Form[.='{form_name}']", NSMAP) is not None
|
||||
|
||||
# Find first <Template> to insert before it
|
||||
first_template = child_objects.find("md:Template", NSMAP)
|
||||
# Find first <TabularSection> to insert before it (if no Template)
|
||||
first_tabular = child_objects.find("md:TabularSection", NSMAP)
|
||||
if not already_registered:
|
||||
form_elem = etree.Element(f"{{{ns}}}Form")
|
||||
form_elem.text = form_name
|
||||
|
||||
# Determine insertion point: before Template, before TabularSection, or at end
|
||||
insert_before = None
|
||||
if first_template is not None:
|
||||
insert_before = first_template
|
||||
elif first_tabular is not None:
|
||||
insert_before = first_tabular
|
||||
# Find first <Template> to insert before it
|
||||
first_template = child_objects.find("md:Template", NSMAP)
|
||||
# Find first <TabularSection> to insert before it (if no Template)
|
||||
first_tabular = child_objects.find("md:TabularSection", NSMAP)
|
||||
|
||||
if insert_before is not None:
|
||||
# Insert before the found element
|
||||
idx = list(child_objects).index(insert_before)
|
||||
child_objects.insert(idx, form_elem)
|
||||
# Whitespace: form_elem gets "\n\t\t\t" as tail (indent before insert_before)
|
||||
form_elem.tail = "\n\t\t\t"
|
||||
else:
|
||||
# Add to end of ChildObjects
|
||||
children = list(child_objects)
|
||||
if len(children) == 0 and (child_objects.text is None or child_objects.text.strip() == ""):
|
||||
# Empty ChildObjects (self-closing)
|
||||
child_objects.text = "\n\t\t\t"
|
||||
child_objects.append(form_elem)
|
||||
form_elem.tail = "\n\t\t"
|
||||
# Determine insertion point: before Template, before TabularSection, or at end
|
||||
insert_before = None
|
||||
if first_template is not None:
|
||||
insert_before = first_template
|
||||
elif first_tabular is not None:
|
||||
insert_before = first_tabular
|
||||
|
||||
if insert_before is not None:
|
||||
# Insert before the found element
|
||||
idx = list(child_objects).index(insert_before)
|
||||
child_objects.insert(idx, form_elem)
|
||||
# Whitespace: form_elem gets "\n\t\t\t" as tail (indent before insert_before)
|
||||
form_elem.tail = "\n\t\t\t"
|
||||
else:
|
||||
if len(children) > 0:
|
||||
last_child = children[-1]
|
||||
old_tail = last_child.tail
|
||||
last_child.tail = "\n\t\t\t"
|
||||
child_objects.append(form_elem)
|
||||
form_elem.tail = old_tail if old_tail else "\n\t\t"
|
||||
else:
|
||||
child_objects.text = (child_objects.text or "") + "\n\t\t\t"
|
||||
# Add to end of ChildObjects
|
||||
children = list(child_objects)
|
||||
if len(children) == 0 and (child_objects.text is None or child_objects.text.strip() == ""):
|
||||
# Empty ChildObjects (self-closing)
|
||||
child_objects.text = "\n\t\t\t"
|
||||
child_objects.append(form_elem)
|
||||
form_elem.tail = "\n\t\t"
|
||||
else:
|
||||
if len(children) > 0:
|
||||
last_child = children[-1]
|
||||
old_tail = last_child.tail
|
||||
last_child.tail = "\n\t\t\t"
|
||||
child_objects.append(form_elem)
|
||||
form_elem.tail = old_tail if old_tail else "\n\t\t"
|
||||
else:
|
||||
child_objects.text = (child_objects.text or "") + "\n\t\t\t"
|
||||
child_objects.append(form_elem)
|
||||
form_elem.tail = "\n\t\t"
|
||||
|
||||
# --- SetDefault ---
|
||||
|
||||
@@ -677,7 +680,10 @@ def main():
|
||||
print(f" Form: {obj_dir_name}\\{obj_base_name}\\Forms\\{form_name}\\Ext\\Form.xml")
|
||||
print(f" Module: {obj_dir_name}\\{obj_base_name}\\Forms\\{form_name}\\Ext\\Form\\Module.bsl")
|
||||
print()
|
||||
print(f"Registered: <Form>{form_name}</Form> in ChildObjects")
|
||||
if already_registered:
|
||||
print(f"Already registered: <Form>{form_name}</Form> in ChildObjects (skipped duplicate)")
|
||||
else:
|
||||
print(f"Registered: <Form>{form_name}</Form> in ChildObjects")
|
||||
if default_updated:
|
||||
print(f"{default_prop_name}: {default_value}")
|
||||
print()
|
||||
|
||||
@@ -550,8 +550,8 @@ PictureField, привязанный к булеву/числу, рисует и
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Компиляция**: `/form-compile` генерирует `Form.xml` и автоматически регистрирует `<Form>` в `ChildObjects` родительского объекта (если OutputPath следует конвенции `.../TypePlural/ObjectName/Forms/FormName/Ext/Form.xml`).
|
||||
2. **Метаданные формы** (`ФормаСписка.xml`) и `Module.bsl` создаёт `/form-add`. Если `/form-add` ещё не вызывался — вызови после `/form-compile`. Он не перезаписывает существующий Form.xml.
|
||||
1. **Каркас**: `/form-add` создаёт метаданные формы (`ФормаСписка.xml`), `Module.bsl` и регистрирует форму у объекта.
|
||||
2. **Компиляция**: `/form-compile` наполняет `Form.xml` элементами.
|
||||
3. **Проверка**: `/form-validate`, `/form-info`.
|
||||
|
||||
## Верификация
|
||||
|
||||
@@ -125,6 +125,7 @@ shorthand — вместо строки задаётся объект:
|
||||
| `use` | `ForItem` | `ForItem` / `ForFolder` / `ForFolderAndItem` (только Catalog / ChartOfCharacteristicTypes) |
|
||||
| `attributes` | `[]` | колонки (shorthand или объектная форма реквизита) |
|
||||
| `lineNumber` | — | кастомизация стандартного реквизита НомерСтроки (см. ниже) |
|
||||
| `lineNumberLength` | по режиму совместимости | `5`…`9` — разрядность номера строки: `5` → до 99 999 строк, `9` → до 999 999 999. Требует формата 2.20 (платформа 8.3.27) |
|
||||
|
||||
### `lineNumber` — стандартный реквизит НомерСтроки
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
| `limitLevelCount` | `false` | bool (ограничивать кол-во уровней) |
|
||||
| `levelCount` | `2` | число уровней (при `limitLevelCount`) |
|
||||
| `foldersOnTop` | `true` | bool (группы сверху) |
|
||||
| `owners` | `[]` | массив ссылок-владельцев: `["CatalogRef.Контрагенты"]` |
|
||||
| `owners` | `[]` | массив владельцев: `["Catalog.Контрагенты"]` |
|
||||
| `subordinationUse` | `ToItems` | `ToItems` / `ToFolders` / `ToFoldersAndItems` (кому подчинён) |
|
||||
| `codeLength` | `9` | длина кода (0 — без кода) |
|
||||
| `codeType` | `String` | `String` / `Number` |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# meta-compile v1.66 — Compile 1C metadata object from JSON
|
||||
# meta-compile v1.68 — Compile 1C metadata object from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -786,11 +786,28 @@ $script:mdRefRoots = @{
|
||||
'журналдокументов'='DocumentJournal'; 'отчет'='Report'; 'отчёт'='Report'; 'обработка'='DataProcessor';
|
||||
'табличнаячасть'='TabularSection'; 'реквизит'='Attribute'; 'измерение'='Dimension'; 'ресурс'='Resource';
|
||||
'стандартныйреквизит'='StandardAttribute'; 'значениеперечисления'='EnumValue'; 'команда'='Command';
|
||||
'признакучета'='AccountingFlag'; 'признакучёта'='AccountingFlag'
|
||||
'признакучета'='AccountingFlag'; 'признакучёта'='AccountingFlag';
|
||||
# Ссылочные формы (тип ссылки вместо объекта метаданных): в MDObjectRef-пути нужен ОБЪЕКТ, т.е.
|
||||
# "CatalogRef.Валюты" → "Catalog.Валюты". Вид метаданных, оканчивающийся на Ref, не существует,
|
||||
# поэтому схлопывание однозначно. В ТИПАХ реквизитов запись CatalogRef.X верна — там эта мапа не применяется.
|
||||
'catalogref'='Catalog'; 'documentref'='Document'; 'enumref'='Enum';
|
||||
'chartofaccountsref'='ChartOfAccounts'; 'chartofcharacteristictypesref'='ChartOfCharacteristicTypes';
|
||||
'chartofcalculationtypesref'='ChartOfCalculationTypes'; 'exchangeplanref'='ExchangePlan';
|
||||
'businessprocessref'='BusinessProcess'; 'taskref'='Task';
|
||||
'справочникссылка'='Catalog'; 'документссылка'='Document'; 'перечислениессылка'='Enum';
|
||||
'плансчетовссылка'='ChartOfAccounts'; 'планвидовхарактеристикссылка'='ChartOfCharacteristicTypes';
|
||||
'планвидоврасчетассылка'='ChartOfCalculationTypes'; 'планвидоврасчётассылка'='ChartOfCalculationTypes';
|
||||
'планобменассылка'='ExchangePlan'; 'бизнеспроцессссылка'='BusinessProcess'; 'задачассылка'='Task'
|
||||
}
|
||||
# $defaultRoot — корень для ГОЛОГО имени без точки (напр. owners: "Валюты" → "Catalog.Валюты").
|
||||
# Без него голое имя возвращается как есть (прежнее поведение вызывающих без подстановки).
|
||||
function Normalize-MDObjectRef {
|
||||
param([string]$ref)
|
||||
if (-not $ref -or -not $ref.Contains('.')) { return $ref }
|
||||
param([string]$ref, [string]$defaultRoot)
|
||||
if (-not $ref) { return $ref }
|
||||
if (-not $ref.Contains('.')) {
|
||||
if ($defaultRoot) { return "$defaultRoot.$ref" }
|
||||
return $ref
|
||||
}
|
||||
$parts = $ref -split '\.'
|
||||
for ($k = 0; $k -lt $parts.Count; $k += 2) {
|
||||
$t = $script:mdRefRoots[$parts[$k].ToLower()]
|
||||
@@ -1304,6 +1321,12 @@ function Emit-StandardAttribute {
|
||||
X "$indent`t<xr:MultiLine>false</xr:MultiLine>"
|
||||
X "$indent`t<xr:FillFromFillingValue>$ffv</xr:FillFromFillingValue>"
|
||||
X "$indent`t<xr:CreateOnInput>Auto</xr:CreateOnInput>"
|
||||
# Формат 2.20 (8.3.27): режим приведения типов. Платформа пишет его КАЖДОМУ стандартному
|
||||
# реквизиту; значение всегда TransformValues, кроме владельца (Owner) — там Deny.
|
||||
if ($script:isFormat220) {
|
||||
$trm = OvOr 'TypeReductionMode' $(if ($attrName -ceq 'Owner') { 'Deny' } else { 'TransformValues' })
|
||||
X "$indent`t<xr:TypeReductionMode>$trm</xr:TypeReductionMode>"
|
||||
}
|
||||
X "$indent`t<xr:MaxValue xsi:nil=`"true`"/>"
|
||||
Emit-MLText "$indent`t" "xr:ToolTip" $tt
|
||||
X "$indent`t<xr:ExtendedEdit>false</xr:ExtendedEdit>"
|
||||
@@ -1497,7 +1520,7 @@ function Emit-BasedOn {
|
||||
$arr = @($items | Where-Object { $_ })
|
||||
if ($arr.Count -eq 0) { X "$indent<BasedOn/>"; return }
|
||||
X "$indent<BasedOn>"
|
||||
foreach ($it in $arr) { X "$indent`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-Xml "$it")</xr:Item>" }
|
||||
foreach ($it in $arr) { X "$indent`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-Xml (Normalize-MDObjectRef "$it"))</xr:Item>" }
|
||||
X "$indent</BasedOn>"
|
||||
}
|
||||
|
||||
@@ -1932,6 +1955,12 @@ function Emit-Attribute {
|
||||
X "$indent`t`t<DataHistory>$dh</DataHistory>"
|
||||
}
|
||||
}
|
||||
# Формат 2.20 (8.3.27): режим приведения типов — последним в Properties и ТОЛЬКО у измерений
|
||||
# регистра сведений (у реквизитов/ресурсов и у прочих семейств регистров платформа его не пишет).
|
||||
if ($script:isFormat220 -and $elemTag -eq "Dimension" -and $context -eq "register-info") {
|
||||
$trm = if ($parsed.typeReductionMode) { "$($parsed.typeReductionMode)" } else { "TransformValues" }
|
||||
X "$indent`t`t<TypeReductionMode>$trm</TypeReductionMode>"
|
||||
}
|
||||
|
||||
X "$indent`t</Properties>"
|
||||
X "$indent</$elemTag>"
|
||||
@@ -2003,7 +2032,7 @@ function Emit-Command {
|
||||
# --- 9. TabularSection emitter ---
|
||||
|
||||
function Emit-TabularSection {
|
||||
param([string]$indent, [string]$tsName, $columns, [string]$objectType, [string]$objectName, $tsSynonymArg = $null, $tsTooltip = $null, $tsComment = $null, $tsLineNumber = $null, $tsFillChecking = $null, $tsUse = $null)
|
||||
param([string]$indent, [string]$tsName, $columns, [string]$objectType, [string]$objectName, $tsSynonymArg = $null, $tsTooltip = $null, $tsComment = $null, $tsLineNumber = $null, $tsFillChecking = $null, $tsUse = $null, $tsLineNumberLength = $null)
|
||||
$uuid = New-Guid-String
|
||||
X "$indent<TabularSection uuid=`"$uuid`">"
|
||||
|
||||
@@ -2042,6 +2071,12 @@ function Emit-TabularSection {
|
||||
$use = if ($tsUse) { "$tsUse" } else { "ForItem" }
|
||||
X "$indent`t`t<Use>$use</Use>"
|
||||
}
|
||||
# Формат 2.20 (8.3.27): длина номера строки ТЧ (5..9 → до 999 999 999 строк вместо 99 999).
|
||||
# Последним в Properties. Дефолт платформа берёт из режима совместимости на момент создания ТЧ.
|
||||
if ($script:isFormat220) {
|
||||
$lnl = if ($null -ne $tsLineNumberLength -and "$tsLineNumberLength" -ne '') { [int]$tsLineNumberLength } else { $script:lineNumberLengthDefault }
|
||||
X "$indent`t`t<LineNumberLength>$lnl</LineNumberLength>"
|
||||
}
|
||||
X "$indent`t</Properties>"
|
||||
|
||||
$tsContext = if ($objectType -in @("DataProcessor","Report")) { "processor-tabular" } else { "tabular" }
|
||||
@@ -2258,8 +2293,7 @@ function Emit-CatalogProperties {
|
||||
if ($def.owners -and $def.owners.Count -gt 0) {
|
||||
X "$i<Owners>"
|
||||
foreach ($ownerRef in $def.owners) {
|
||||
$fullRef = if ("$ownerRef" -match '\.') { "$ownerRef" } else { "Catalog.$ownerRef" }
|
||||
X "$i`t<xr:Item xsi:type=`"xr:MDObjectRef`">$fullRef</xr:Item>"
|
||||
X "$i`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-Xml (Normalize-MDObjectRef "$ownerRef" 'Catalog'))</xr:Item>"
|
||||
}
|
||||
X "$i</Owners>"
|
||||
} else {
|
||||
@@ -2410,7 +2444,7 @@ function Emit-DocumentProperties {
|
||||
}
|
||||
if ($regRecords.Count -gt 0) {
|
||||
X "$i<RegisterRecords>"
|
||||
foreach ($rr in $regRecords) { X "$i`t<xr:Item xsi:type=`"xr:MDObjectRef`">$rr</xr:Item>" }
|
||||
foreach ($rr in $regRecords) { X "$i`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-Xml (Normalize-MDObjectRef "$rr"))</xr:Item>" }
|
||||
X "$i</RegisterRecords>"
|
||||
} else {
|
||||
X "$i<RegisterRecords/>"
|
||||
@@ -3524,7 +3558,7 @@ function Emit-ChartOfCalculationTypesProperties {
|
||||
$baseTypes = @(); if ($def.baseCalculationTypes) { $baseTypes = @($def.baseCalculationTypes | ForEach-Object { Resolve-TypePrefixSyn "$_" }) }
|
||||
if ($baseTypes.Count -gt 0) {
|
||||
X "$i<BaseCalculationTypes>"
|
||||
foreach ($bt in $baseTypes) { X "$i`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-Xml $bt)</xr:Item>" }
|
||||
foreach ($bt in $baseTypes) { X "$i`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-Xml (Normalize-MDObjectRef "$bt"))</xr:Item>" }
|
||||
X "$i</BaseCalculationTypes>"
|
||||
} else { X "$i<BaseCalculationTypes/>" }
|
||||
$actionPeriodUse = if ($def.actionPeriodUse -eq $true) { "true" } else { "false" }
|
||||
@@ -3968,7 +4002,51 @@ function Detect-FormatVersion([string]$dir) {
|
||||
return "2.17"
|
||||
}
|
||||
|
||||
# Режим совместимости конфигурации — из него выводится дефолт <LineNumberLength> табличной части
|
||||
# (≤Version8_3_26 → 5, ≥Version8_3_27 → 9; платформа фиксирует значение при СОЗДАНии ТЧ).
|
||||
# NB: версия ФОРМАТА от режима совместимости не зависит (её задаёт платформа выгрузки) — это
|
||||
# независимые вещи, читаются из одного файла разными функциями.
|
||||
# Читаем префикс побольше: <CompatibilityMode> лежит ~11-12 КБ от начала (в отличие от version=
|
||||
# в первой строке), 2000 байт Detect-FormatVersion сюда не хватает.
|
||||
function Detect-CompatibilityMode([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
# NB: длина файла — в БАЙТАХ, а Substring режет по СИМВОЛАМ (кириллица = 2 байта),
|
||||
# поэтому ограничиваем по длине уже декодированной строки.
|
||||
$text = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
||||
$head = $text.Substring(0, [Math]::Min(65536, $text.Length))
|
||||
if ($head -match '<CompatibilityMode>([^<]+)</CompatibilityMode>') { return $Matches[1].Trim() }
|
||||
}
|
||||
$parent = Split-Path $d -Parent
|
||||
if ($parent -eq $d) { break }
|
||||
$d = $parent
|
||||
}
|
||||
return "Version8_3_24"
|
||||
}
|
||||
|
||||
# Номер версии режима совместимости для сравнений: "Version8_3_27" → 80327, "Version8_5_1" → 80501.
|
||||
function Get-CompatModeRank([string]$mode) {
|
||||
if ($mode -match '^Version(\d+)_(\d+)_(\d+)$') {
|
||||
return [int]$Matches[1] * 10000 + [int]$Matches[2] * 100 + [int]$Matches[3]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209.
|
||||
# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка.
|
||||
function Get-FormatRank([string]$ver) {
|
||||
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||
return 0
|
||||
}
|
||||
|
||||
$script:formatVersion = Detect-FormatVersion $OutputDir
|
||||
$script:compatMode = Detect-CompatibilityMode $OutputDir
|
||||
# Формат 2.20+ (платформа 8.3.27) — только тогда эмитим новые свойства.
|
||||
$script:isFormat220 = (Get-FormatRank $script:formatVersion) -ge 220
|
||||
# Дефолт длины номера строки ТЧ: с режима 8.3.27 платформа заводит новые ТЧ с 9 разрядами.
|
||||
$script:lineNumberLengthDefault = if ((Get-CompatModeRank $script:compatMode) -ge 80327) { 9 } else { 5 }
|
||||
|
||||
# --- 15. Main assembler ---
|
||||
|
||||
@@ -4048,10 +4126,10 @@ if ($objType -in $typesWithAttrTS) {
|
||||
# Нормализуем в $tsSections[name] = @{ columns; synonym; tooltip; comment }.
|
||||
function New-TsEntry { param($val)
|
||||
if ($val -is [array] -or $val.GetType().Name -eq 'Object[]') {
|
||||
return @{ columns = @($val); synonym = $null; tooltip = $null; comment = $null; lineNumber = $null; fillChecking = $null; use = $null }
|
||||
return @{ columns = @($val); synonym = $null; tooltip = $null; comment = $null; lineNumber = $null; fillChecking = $null; use = $null; lineNumberLength = $null }
|
||||
}
|
||||
$cols = if ($val.attributes) { @($val.attributes) } elseif ($val.columns) { @($val.columns) } else { @() }
|
||||
return @{ columns = $cols; synonym = $val.synonym; tooltip = $val.tooltip; comment = if ($val.comment) { "$($val.comment)" } else { $null }; lineNumber = $val.lineNumber; fillChecking = $val.fillChecking; use = $val.use }
|
||||
return @{ columns = $cols; synonym = $val.synonym; tooltip = $val.tooltip; comment = if ($val.comment) { "$($val.comment)" } else { $null }; lineNumber = $val.lineNumber; fillChecking = $val.fillChecking; use = $val.use; lineNumberLength = $val.lineNumberLength }
|
||||
}
|
||||
if ($def.tabularSections -is [array] -or $def.tabularSections.GetType().Name -eq "Object[]") {
|
||||
foreach ($ts in $def.tabularSections) { $tsSections[$ts.name] = New-TsEntry $ts }
|
||||
@@ -4101,7 +4179,7 @@ if ($objType -in $typesWithAttrTS) {
|
||||
}
|
||||
foreach ($tsName in $tsSections.Keys) {
|
||||
$tsE = $tsSections[$tsName]
|
||||
Emit-TabularSection "`t`t`t" $tsName $tsE.columns $objType $objName $tsE.synonym $tsE.tooltip $tsE.comment $tsE.lineNumber $tsE.fillChecking $tsE.use
|
||||
Emit-TabularSection "`t`t`t" $tsName $tsE.columns $objType $objName $tsE.synonym $tsE.tooltip $tsE.comment $tsE.lineNumber $tsE.fillChecking $tsE.use $tsE.lineNumberLength
|
||||
}
|
||||
foreach ($af in $acctFlags) {
|
||||
Emit-Attribute "`t`t`t" $af "account-flag" "AccountingFlag"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# meta-compile v1.66 — Compile 1C metadata object from JSON
|
||||
# meta-compile v1.68 — Compile 1C metadata object from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -822,11 +822,26 @@ md_ref_roots = {
|
||||
'табличнаячасть': 'TabularSection', 'реквизит': 'Attribute', 'измерение': 'Dimension', 'ресурс': 'Resource',
|
||||
'стандартныйреквизит': 'StandardAttribute', 'значениеперечисления': 'EnumValue', 'команда': 'Command',
|
||||
'признакучета': 'AccountingFlag', 'признакучёта': 'AccountingFlag',
|
||||
# Ссылочные формы (тип ссылки вместо объекта метаданных): в MDObjectRef-пути нужен ОБЪЕКТ, т.е.
|
||||
# "CatalogRef.Валюты" → "Catalog.Валюты". Вид метаданных, оканчивающийся на Ref, не существует,
|
||||
# поэтому схлопывание однозначно. В ТИПАХ реквизитов запись CatalogRef.X верна — там мапа не применяется.
|
||||
'catalogref': 'Catalog', 'documentref': 'Document', 'enumref': 'Enum',
|
||||
'chartofaccountsref': 'ChartOfAccounts', 'chartofcharacteristictypesref': 'ChartOfCharacteristicTypes',
|
||||
'chartofcalculationtypesref': 'ChartOfCalculationTypes', 'exchangeplanref': 'ExchangePlan',
|
||||
'businessprocessref': 'BusinessProcess', 'taskref': 'Task',
|
||||
'справочникссылка': 'Catalog', 'документссылка': 'Document', 'перечислениессылка': 'Enum',
|
||||
'плансчетовссылка': 'ChartOfAccounts', 'планвидовхарактеристикссылка': 'ChartOfCharacteristicTypes',
|
||||
'планвидоврасчетассылка': 'ChartOfCalculationTypes', 'планвидоврасчётассылка': 'ChartOfCalculationTypes',
|
||||
'планобменассылка': 'ExchangePlan', 'бизнеспроцессссылка': 'BusinessProcess', 'задачассылка': 'Task',
|
||||
}
|
||||
|
||||
def normalize_md_object_ref(ref):
|
||||
if not ref or '.' not in ref:
|
||||
def normalize_md_object_ref(ref, default_root=None):
|
||||
"""default_root — корень для ГОЛОГО имени без точки (owners: "Валюты" → "Catalog.Валюты").
|
||||
Без него голое имя возвращается как есть (прежнее поведение вызывающих без подстановки)."""
|
||||
if not ref:
|
||||
return ref
|
||||
if '.' not in ref:
|
||||
return f'{default_root}.{ref}' if default_root else ref
|
||||
parts = ref.split('.')
|
||||
for k in range(0, len(parts), 2):
|
||||
t = md_ref_roots.get(parts[k].lower())
|
||||
@@ -1325,6 +1340,11 @@ def emit_standard_attribute(indent, attr_name, ov=None):
|
||||
X(f'{indent}\t<xr:MultiLine>false</xr:MultiLine>')
|
||||
X(f'{indent}\t<xr:FillFromFillingValue>{ffv}</xr:FillFromFillingValue>')
|
||||
X(f'{indent}\t<xr:CreateOnInput>Auto</xr:CreateOnInput>')
|
||||
# Формат 2.20 (8.3.27): режим приведения типов. Платформа пишет его КАЖДОМУ стандартному
|
||||
# реквизиту; значение всегда TransformValues, кроме владельца (Owner) — там Deny.
|
||||
if is_format_220:
|
||||
trm = ov.get('TypeReductionMode', 'Deny' if attr_name == 'Owner' else 'TransformValues')
|
||||
X(f'{indent}\t<xr:TypeReductionMode>{trm}</xr:TypeReductionMode>')
|
||||
X(f'{indent}\t<xr:MaxValue xsi:nil="true"/>')
|
||||
emit_mltext(f'{indent}\t', 'xr:ToolTip', tt)
|
||||
X(f'{indent}\t<xr:ExtendedEdit>false</xr:ExtendedEdit>')
|
||||
@@ -1587,7 +1607,7 @@ def emit_based_on(indent, items):
|
||||
return
|
||||
X(f'{indent}<BasedOn>')
|
||||
for it in arr:
|
||||
X(f'{indent}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(str(it))}</xr:Item>')
|
||||
X(f'{indent}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(normalize_md_object_ref(str(it)))}</xr:Item>')
|
||||
X(f'{indent}</BasedOn>')
|
||||
|
||||
# --- Параметры/связи выбора (порт из form-compile) ---
|
||||
@@ -1997,6 +2017,10 @@ def emit_attribute(indent, parsed, context, elem_tag='Attribute'):
|
||||
# DataHistory — not for Chart* types and non-InformationRegister register family
|
||||
if context not in ('chart', 'register-other', 'register-accum', 'register-calc', 'register-account'):
|
||||
X(f'{indent}\t\t<DataHistory>{parsed.get("dataHistory") or "Use"}</DataHistory>')
|
||||
# Формат 2.20 (8.3.27): режим приведения типов — последним в Properties и ТОЛЬКО у измерений
|
||||
# регистра сведений (у реквизитов/ресурсов и у прочих семейств регистров платформа его не пишет).
|
||||
if is_format_220 and elem_tag == 'Dimension' and context == 'register-info':
|
||||
X(f'{indent}\t\t<TypeReductionMode>{parsed.get("typeReductionMode") or "TransformValues"}</TypeReductionMode>')
|
||||
X(f'{indent}\t</Properties>')
|
||||
X(f'{indent}</{elem_tag}>')
|
||||
|
||||
@@ -2073,7 +2097,7 @@ def emit_command(indent, cmd_name, cmd):
|
||||
X(f'{indent}\t</Properties>')
|
||||
X(f'{indent}</Command>')
|
||||
|
||||
def emit_tabular_section(indent, ts_name, columns, object_type, object_name, ts_synonym_arg=None, ts_tooltip=None, ts_comment=None, ts_line_number=None, ts_fill_checking=None, ts_use=None):
|
||||
def emit_tabular_section(indent, ts_name, columns, object_type, object_name, ts_synonym_arg=None, ts_tooltip=None, ts_comment=None, ts_line_number=None, ts_fill_checking=None, ts_use=None, ts_line_number_length=None):
|
||||
uid = new_uuid()
|
||||
X(f'{indent}<TabularSection uuid="{uid}">')
|
||||
type_prefix = f'{object_type}TabularSection'
|
||||
@@ -2104,6 +2128,11 @@ def emit_tabular_section(indent, ts_name, columns, object_type, object_name, ts_
|
||||
emit_tabular_standard_attributes(f'{indent}\t\t', ts_line_number)
|
||||
if object_type in ('Catalog', 'ChartOfCharacteristicTypes'):
|
||||
X(f'{indent}\t\t<Use>{ts_use if ts_use else "ForItem"}</Use>')
|
||||
# Формат 2.20 (8.3.27): длина номера строки ТЧ (5..9 → до 999 999 999 строк вместо 99 999).
|
||||
# Последним в Properties. Дефолт платформа берёт из режима совместимости на момент создания ТЧ.
|
||||
if is_format_220:
|
||||
lnl = int(ts_line_number_length) if ts_line_number_length not in (None, '') else line_number_length_default
|
||||
X(f'{indent}\t\t<LineNumberLength>{lnl}</LineNumberLength>')
|
||||
X(f'{indent}\t</Properties>')
|
||||
ts_context = 'processor-tabular' if object_type in ('DataProcessor', 'Report') else 'tabular'
|
||||
X(f'{indent}\t<ChildObjects>')
|
||||
@@ -2288,8 +2317,7 @@ def emit_catalog_properties(indent):
|
||||
if owners:
|
||||
X(f'{i}<Owners>')
|
||||
for owner_ref in owners:
|
||||
full_ref = owner_ref if '.' in str(owner_ref) else f'Catalog.{owner_ref}'
|
||||
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{full_ref}</xr:Item>')
|
||||
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(normalize_md_object_ref(str(owner_ref), "Catalog"))}</xr:Item>')
|
||||
X(f'{i}</Owners>')
|
||||
else:
|
||||
X(f'{i}<Owners/>')
|
||||
@@ -2429,7 +2457,7 @@ def emit_document_properties(indent):
|
||||
if reg_records:
|
||||
X(f'{i}<RegisterRecords>')
|
||||
for rr in reg_records:
|
||||
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{rr}</xr:Item>')
|
||||
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(normalize_md_object_ref(str(rr)))}</xr:Item>')
|
||||
X(f'{i}</RegisterRecords>')
|
||||
else:
|
||||
X(f'{i}<RegisterRecords/>')
|
||||
@@ -3483,7 +3511,7 @@ def emit_chart_of_calculation_types_properties(indent):
|
||||
if base_types:
|
||||
X(f'{i}<BaseCalculationTypes>')
|
||||
for bt in base_types:
|
||||
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(bt)}</xr:Item>')
|
||||
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(normalize_md_object_ref(bt))}</xr:Item>')
|
||||
X(f'{i}</BaseCalculationTypes>')
|
||||
else:
|
||||
X(f'{i}<BaseCalculationTypes/>')
|
||||
@@ -3873,7 +3901,44 @@ def detect_format_version(d):
|
||||
d = parent
|
||||
return "2.17"
|
||||
|
||||
def detect_compatibility_mode(d):
|
||||
"""Режим совместимости конфигурации — из него выводится дефолт <LineNumberLength> табличной части
|
||||
(<=Version8_3_26 → 5, >=Version8_3_27 → 9; платформа фиксирует значение при СОЗДАНИИ ТЧ).
|
||||
NB: версия ФОРМАТА от режима совместимости не зависит (её задаёт платформа выгрузки).
|
||||
Читаем префикс побольше: <CompatibilityMode> лежит ~11-12 КБ от начала."""
|
||||
while d:
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(65536)
|
||||
m = re.search(r'<CompatibilityMode>([^<]+)</CompatibilityMode>', head)
|
||||
if m:
|
||||
return m.group(1).strip()
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
break
|
||||
d = parent
|
||||
return "Version8_3_24"
|
||||
|
||||
|
||||
def compat_mode_rank(mode):
|
||||
""""Version8_3_27" → 80327, "Version8_5_1" → 80501."""
|
||||
m = re.match(r'^Version(\d+)_(\d+)_(\d+)$', mode or '')
|
||||
return int(m.group(1)) * 10000 + int(m.group(2)) * 100 + int(m.group(3)) if m else 0
|
||||
|
||||
|
||||
def format_rank(ver):
|
||||
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||
|
||||
|
||||
format_version = detect_format_version(output_dir)
|
||||
compat_mode = detect_compatibility_mode(output_dir)
|
||||
# Формат 2.20+ (платформа 8.3.27) — только тогда эмитим новые свойства.
|
||||
is_format_220 = format_rank(format_version) >= 220
|
||||
# Дефолт длины номера строки ТЧ: с режима 8.3.27 платформа заводит новые ТЧ с 9 разрядами.
|
||||
line_number_length_default = 9 if compat_mode_rank(compat_mode) >= 80327 else 5
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 15. Main assembler
|
||||
@@ -3966,10 +4031,10 @@ if obj_type in types_with_attr_ts:
|
||||
# Значение ТЧ: массив колонок (синоним авто) ЛИБО объект {attributes/columns, synonym, tooltip, comment}.
|
||||
def new_ts_entry(val):
|
||||
if isinstance(val, list):
|
||||
return {'columns': val, 'synonym': None, 'tooltip': None, 'comment': None, 'lineNumber': None, 'fillChecking': None, 'use': None}
|
||||
return {'columns': val, 'synonym': None, 'tooltip': None, 'comment': None, 'lineNumber': None, 'fillChecking': None, 'use': None, 'lineNumberLength': None}
|
||||
cols = _as_list(val.get('attributes') or val.get('columns') or [])
|
||||
return {'columns': cols, 'synonym': val.get('synonym'), 'tooltip': val.get('tooltip'),
|
||||
'comment': str(val['comment']) if val.get('comment') else None, 'lineNumber': val.get('lineNumber'), 'fillChecking': val.get('fillChecking'), 'use': val.get('use')}
|
||||
'comment': str(val['comment']) if val.get('comment') else None, 'lineNumber': val.get('lineNumber'), 'fillChecking': val.get('fillChecking'), 'use': val.get('use'), 'lineNumberLength': val.get('lineNumberLength')}
|
||||
if isinstance(ts_data, list):
|
||||
for ts in ts_data:
|
||||
ts_sections[ts['name']] = new_ts_entry(ts)
|
||||
@@ -4021,7 +4086,7 @@ if obj_type in types_with_attr_ts:
|
||||
emit_attribute('\t\t\t', a, context)
|
||||
for ts_name in ts_order:
|
||||
e = ts_sections[ts_name]
|
||||
emit_tabular_section('\t\t\t', ts_name, e['columns'], obj_type, obj_name, e['synonym'], e['tooltip'], e['comment'], e.get('lineNumber'), e.get('fillChecking'), e.get('use'))
|
||||
emit_tabular_section('\t\t\t', ts_name, e['columns'], obj_type, obj_name, e['synonym'], e['tooltip'], e['comment'], e.get('lineNumber'), e.get('fillChecking'), e.get('use'), e.get('lineNumberLength'))
|
||||
for af in acct_flags:
|
||||
emit_attribute('\t\t\t', af, 'account-flag', 'AccountingFlag')
|
||||
for edf in ext_dim_flags:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# meta-decompile v0.54 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
|
||||
# meta-decompile v0.55 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
#
|
||||
# Поддержаны: Catalog, ExchangePlan, ChartOfCharacteristicTypes, ChartOfAccounts, ChartOfCalculationTypes, Document,
|
||||
@@ -323,6 +323,9 @@ function Attr-ToDsl {
|
||||
$v = & $en 'MainFilter'; if ($v -eq 'true') { $extra['mainFilter'] = $true }
|
||||
$v = & $en 'DenyIncompleteValues'; if ($v -eq 'true') { $extra['denyIncompleteValues'] = $true }
|
||||
$v = & $en 'UseInTotals'; if ($v -eq 'false') { $extra['useInTotals'] = $false } # дефолт true → захват при false
|
||||
# Формат 2.20: режим приведения типов измерения РС. Дефолт TransformValues (его компилятор
|
||||
# эмитит сам) → захватываем только отклонение.
|
||||
$v = & $en 'TypeReductionMode'; if ($v -and $v -ne 'TransformValues') { $extra['typeReductionMode'] = $v }
|
||||
$v = & $en 'BaseDimension'; if ($v -eq 'true') { $extra['baseDimension'] = $true }
|
||||
$v = & $en 'ScheduleLink'; if ($v) { $extra['scheduleLink'] = $v } # ссылка на измерение графика (пустой → пропуск)
|
||||
$v = & $en 'Balance'; if ($v -eq 'true') { $extra['balance'] = $true }
|
||||
@@ -1092,6 +1095,13 @@ if ($saNode) {
|
||||
$ov['linkByType'] = [ordered]@{ dataPath = $saLbtDp.InnerText; linkItem = $li }
|
||||
}
|
||||
}
|
||||
# Формат 2.20: режим приведения типов. Компилятор выводит его сам (TransformValues, у Owner —
|
||||
# Deny), поэтому захватываем только отклонение от этого правила.
|
||||
$saTrmN = $sa.SelectSingleNode('xr:TypeReductionMode', $nsm)
|
||||
if ($saTrmN -and $saTrmN.InnerText) {
|
||||
$saTrmDef = if ($an -ceq 'Owner') { 'Deny' } else { 'TransformValues' }
|
||||
if ($saTrmN.InnerText -ne $saTrmDef) { $ov['TypeReductionMode'] = $saTrmN.InnerText }
|
||||
}
|
||||
# Доп./опциональный реквизит (не в фикс-списке) — эмитим по присутствию даже без отклонений.
|
||||
if ($ov.Count -gt 0 -or ($stdFixed -notcontains $an)) { $saMap[$an] = $ov }
|
||||
}
|
||||
@@ -1262,13 +1272,20 @@ if ($childObjs) {
|
||||
if ($lnFvT -match 'decimal$') { $lnObj['fillValue'] = if ($lnFvN.InnerText -match '^-?\d+$') { [long]$lnFvN.InnerText } else { [double]$lnFvN.InnerText } }
|
||||
}
|
||||
}
|
||||
if ($tsSynCustom -or ($null -ne $tsTt) -or $tsCmt -or $tsFc -or $tsUse -or $lnObj.Count -gt 0 -or (-not $hasBlock)) {
|
||||
# Формат 2.20: длина номера строки ТЧ. Захватываем ВСЕГДА при наличии тега, а не
|
||||
# omit-on-default: дефолт зависит от режима совместимости конфигурации (≤8_3_26 → 5,
|
||||
# ≥8_3_27 → 9) и фиксируется платформой при создании ТЧ, так что вывести его здесь
|
||||
# значило бы продублировать логику компилятора с риском разойтись. Явный захват точен.
|
||||
$tsLnlN = $tsp.SelectSingleNode('md:LineNumberLength', $nsm)
|
||||
$tsLnl = if ($tsLnlN -and $tsLnlN.InnerText) { [int]$tsLnlN.InnerText } else { $null }
|
||||
if ($tsSynCustom -or ($null -ne $tsTt) -or $tsCmt -or $tsFc -or $tsUse -or $lnObj.Count -gt 0 -or (-not $hasBlock) -or ($null -ne $tsLnl)) {
|
||||
$to = [ordered]@{}
|
||||
if ($tsSynCustom) { $to['synonym'] = $tsSyn }
|
||||
if ($null -ne $tsTt) { $to['tooltip'] = $tsTt }
|
||||
if ($tsCmt) { $to['comment'] = $tsCmt }
|
||||
if ($tsFc) { $to['fillChecking'] = $tsFc }
|
||||
if ($tsUse) { $to['use'] = $tsUse }
|
||||
if ($null -ne $tsLnl) { $to['lineNumberLength'] = $tsLnl }
|
||||
if (-not $hasBlock) { $to['lineNumber'] = '' } elseif ($lnObj.Count -gt 0) { $to['lineNumber'] = $lnObj }
|
||||
$to['attributes'] = $cols
|
||||
$tsMap[$tsName] = $to
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# meta-decompile v0.54 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
|
||||
# meta-decompile v0.55 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
#
|
||||
# Зеркало meta-decompile.ps1 (КАНОН). Структура 1:1 — те же имена функций, порядок, комментарии.
|
||||
@@ -456,6 +456,11 @@ def attr_to_dsl(attr_node):
|
||||
v = en('UseInTotals')
|
||||
if v == 'false':
|
||||
extra['useInTotals'] = False # дефолт true → захват при false
|
||||
# Формат 2.20: режим приведения типов измерения РС. Дефолт TransformValues (его компилятор
|
||||
# эмитит сам) → захватываем только отклонение.
|
||||
v = en('TypeReductionMode')
|
||||
if v and v != 'TransformValues':
|
||||
extra['typeReductionMode'] = v
|
||||
v = en('BaseDimension')
|
||||
if v == 'true':
|
||||
extra['baseDimension'] = True
|
||||
@@ -1587,6 +1592,13 @@ def build_dsl():
|
||||
li = int(_text(sa_lbt_li)) if (sa_lbt_li is not None and _text(sa_lbt_li)) else 0
|
||||
ov['linkByType'] = {'dataPath': _text(sa_lbt_dp), 'linkItem': li}
|
||||
# Доп./опциональный реквизит (не в фикс-списке) — эмитим по присутствию даже без отклонений.
|
||||
# Формат 2.20: режим приведения типов. Компилятор выводит его сам (TransformValues,
|
||||
# у Owner — Deny), поэтому захватываем только отклонение от этого правила.
|
||||
sa_trm_n = _single(sa, 'xr:TypeReductionMode')
|
||||
if sa_trm_n is not None and (sa_trm_n.text or '').strip():
|
||||
sa_trm_def = 'Deny' if an == 'Owner' else 'TransformValues'
|
||||
if sa_trm_n.text.strip() != sa_trm_def:
|
||||
ov['TypeReductionMode'] = sa_trm_n.text.strip()
|
||||
if len(ov) > 0 or (an not in std_fixed):
|
||||
sa_map[an] = ov
|
||||
if len(sa_map) > 0 or (obj_type in std_conditional_types):
|
||||
@@ -1782,7 +1794,13 @@ def build_dsl():
|
||||
ln_fv_t = _attr(ln_fv_n, 'type', NS_XSI)
|
||||
if re.search(r'decimal$', ln_fv_t, re.I):
|
||||
ln_obj['fillValue'] = int(_text(ln_fv_n)) if re.match(r'^-?\d+$', _text(ln_fv_n)) else float(_text(ln_fv_n))
|
||||
if ts_syn_custom or (ts_tt is not None) or ts_cmt or ts_fc or ts_use or len(ln_obj) > 0 or (not has_block):
|
||||
# Формат 2.20: длина номера строки ТЧ. Захватываем ВСЕГДА при наличии тега, а не
|
||||
# omit-on-default: дефолт зависит от режима совместимости конфигурации (<=8_3_26 → 5,
|
||||
# >=8_3_27 → 9) и фиксируется платформой при создании ТЧ, так что вывести его здесь
|
||||
# значило бы продублировать логику компилятора с риском разойтись. Явный захват точен.
|
||||
ts_lnl_n = _single(tsp, 'md:LineNumberLength')
|
||||
ts_lnl = int(ts_lnl_n.text) if ts_lnl_n is not None and (ts_lnl_n.text or '').strip() else None
|
||||
if ts_syn_custom or (ts_tt is not None) or ts_cmt or ts_fc or ts_use or len(ln_obj) > 0 or (not has_block) or (ts_lnl is not None):
|
||||
to = {}
|
||||
if ts_syn_custom:
|
||||
to['synonym'] = ts_syn
|
||||
@@ -1794,6 +1812,8 @@ def build_dsl():
|
||||
to['fillChecking'] = ts_fc
|
||||
if ts_use:
|
||||
to['use'] = ts_use
|
||||
if ts_lnl is not None:
|
||||
to['lineNumberLength'] = ts_lnl
|
||||
if not has_block:
|
||||
to['lineNumber'] = ''
|
||||
elif len(ln_obj) > 0:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# meta-edit v1.22 — Edit existing 1C metadata object XML
|
||||
# meta-edit v1.23 — Edit existing 1C metadata object XML
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[string]$DefinitionFile,
|
||||
@@ -1313,7 +1313,7 @@ function Build-ColumnFragment {
|
||||
if ($references.Count -gt 0) {
|
||||
$sb.AppendLine("$indent`t`t<References>") | Out-Null
|
||||
foreach ($ref in $references) {
|
||||
$sb.AppendLine("$indent`t`t`t<xr:Item xsi:type=`"xr:MDObjectRef`">$ref</xr:Item>") | Out-Null
|
||||
$sb.AppendLine("$indent`t`t`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-Xml (Normalize-MDObjectRef "$ref"))</xr:Item>") | Out-Null
|
||||
}
|
||||
$sb.AppendLine("$indent`t`t</References>") | Out-Null
|
||||
} else {
|
||||
@@ -2416,13 +2416,56 @@ function Process-Modify($modifyDef) {
|
||||
# Section 12.5: Complex property helpers
|
||||
# ============================================================
|
||||
|
||||
# Прощающий ввод MDObjectRef-путей: русские корни метаданных → английские + ссылочные формы
|
||||
# ("CatalogRef.Валюты"/"СправочникСсылка.Валюты" → "Catalog.Валюты"). MDObjectRef ссылается на ОБЪЕКТ
|
||||
# метаданных, а не на тип ссылки; вида метаданных, оканчивающегося на Ref, не существует → схлопывание
|
||||
# однозначно. Виды стоят на ЧЁТНЫХ позициях (0,2,4…), имена (нечётные) не трогаем. Канонические
|
||||
# английские пути неизменны (в мапе только неканонические ключи). Зеркало meta-compile.
|
||||
$script:mdRefRoots = @{
|
||||
'справочник'='Catalog'; 'документ'='Document'; 'перечисление'='Enum'; 'константа'='Constant';
|
||||
'регистрсведений'='InformationRegister'; 'регистрнакопления'='AccumulationRegister';
|
||||
'регистрбухгалтерии'='AccountingRegister'; 'регистррасчета'='CalculationRegister'; 'регистррасчёта'='CalculationRegister';
|
||||
'плансчетов'='ChartOfAccounts'; 'планвидовхарактеристик'='ChartOfCharacteristicTypes';
|
||||
'планвидоврасчета'='ChartOfCalculationTypes'; 'планвидоврасчёта'='ChartOfCalculationTypes';
|
||||
'планобмена'='ExchangePlan'; 'бизнеспроцесс'='BusinessProcess'; 'задача'='Task';
|
||||
'журналдокументов'='DocumentJournal'; 'отчет'='Report'; 'отчёт'='Report'; 'обработка'='DataProcessor';
|
||||
'табличнаячасть'='TabularSection'; 'реквизит'='Attribute'; 'измерение'='Dimension'; 'ресурс'='Resource';
|
||||
'стандартныйреквизит'='StandardAttribute'; 'значениеперечисления'='EnumValue'; 'команда'='Command';
|
||||
'признакучета'='AccountingFlag'; 'признакучёта'='AccountingFlag';
|
||||
'catalogref'='Catalog'; 'documentref'='Document'; 'enumref'='Enum';
|
||||
'chartofaccountsref'='ChartOfAccounts'; 'chartofcharacteristictypesref'='ChartOfCharacteristicTypes';
|
||||
'chartofcalculationtypesref'='ChartOfCalculationTypes'; 'exchangeplanref'='ExchangePlan';
|
||||
'businessprocessref'='BusinessProcess'; 'taskref'='Task';
|
||||
'справочникссылка'='Catalog'; 'документссылка'='Document'; 'перечислениессылка'='Enum';
|
||||
'плансчетовссылка'='ChartOfAccounts'; 'планвидовхарактеристикссылка'='ChartOfCharacteristicTypes';
|
||||
'планвидоврасчетассылка'='ChartOfCalculationTypes'; 'планвидоврасчётассылка'='ChartOfCalculationTypes';
|
||||
'планобменассылка'='ExchangePlan'; 'бизнеспроцессссылка'='BusinessProcess'; 'задачассылка'='Task'
|
||||
}
|
||||
# $defaultRoot — корень для ГОЛОГО имени без точки (owners: "Валюты" → "Catalog.Валюты").
|
||||
function Normalize-MDObjectRef {
|
||||
param([string]$ref, [string]$defaultRoot)
|
||||
if (-not $ref) { return $ref }
|
||||
if (-not $ref.Contains('.')) {
|
||||
if ($defaultRoot) { return "$defaultRoot.$ref" }
|
||||
return $ref
|
||||
}
|
||||
$parts = $ref -split '\.'
|
||||
for ($k = 0; $k -lt $parts.Count; $k += 2) {
|
||||
$t = $script:mdRefRoots[$parts[$k].ToLower()]
|
||||
if ($t) { $parts[$k] = $t }
|
||||
}
|
||||
return ($parts -join '.')
|
||||
}
|
||||
|
||||
# mdref — значения списка суть MDObjectRef-пути → прогоняем через Normalize-MDObjectRef.
|
||||
# root — корень для голого имени без точки.
|
||||
$script:complexPropertyMap = @{
|
||||
"Owners" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"' }
|
||||
"RegisterRecords" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"' }
|
||||
"BasedOn" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"' }
|
||||
"Owners" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"'; mdref = $true; root = 'Catalog' }
|
||||
"RegisterRecords" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"'; mdref = $true }
|
||||
"BasedOn" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"'; mdref = $true }
|
||||
"InputByString" = @{ tag = "xr:Field"; attr = $null }
|
||||
"DataLockFields" = @{ tag = "xr:Field"; attr = $null; expand = $true }
|
||||
"RegisteredDocuments" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"' }
|
||||
"RegisteredDocuments" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"'; mdref = $true }
|
||||
}
|
||||
|
||||
# Известные свойства объекта (union по корпусу acc+erp 8.3.24) — allowlist для modify-property.
|
||||
@@ -2873,6 +2916,7 @@ function Add-ComplexPropertyItem([string]$propertyName, [string[]]$values) {
|
||||
$mapEntry = $script:complexPropertyMap[$propertyName]
|
||||
if (-not $mapEntry) { Warn "Unknown complex property: $propertyName"; return }
|
||||
if ($mapEntry.expand) { $values = @($values | ForEach-Object { Expand-DataPath "$_" }) }
|
||||
if ($mapEntry.mdref) { $values = @($values | ForEach-Object { Normalize-MDObjectRef "$_" $mapEntry.root }) }
|
||||
|
||||
$propEl = Find-PropertyElement $propertyName
|
||||
if (-not $propEl) {
|
||||
@@ -2922,6 +2966,7 @@ function Add-ComplexPropertyItem([string]$propertyName, [string[]]$values) {
|
||||
function Remove-ComplexPropertyItem([string]$propertyName, [string[]]$values) {
|
||||
$mapEntry = $script:complexPropertyMap[$propertyName]
|
||||
if ($mapEntry -and $mapEntry.expand) { $values = @($values | ForEach-Object { Expand-DataPath "$_" }) }
|
||||
if ($mapEntry -and $mapEntry.mdref) { $values = @($values | ForEach-Object { Normalize-MDObjectRef "$_" $mapEntry.root }) }
|
||||
$propEl = Find-PropertyElement $propertyName
|
||||
if (-not $propEl) {
|
||||
Warn "Property element '$propertyName' not found in Properties"
|
||||
@@ -2960,6 +3005,7 @@ function Set-ComplexProperty([string]$propertyName, [string[]]$values) {
|
||||
$mapEntry = $script:complexPropertyMap[$propertyName]
|
||||
if (-not $mapEntry) { Warn "Unknown complex property: $propertyName"; return }
|
||||
if ($mapEntry.expand) { $values = @($values | ForEach-Object { Expand-DataPath "$_" }) }
|
||||
if ($mapEntry.mdref) { $values = @($values | ForEach-Object { Normalize-MDObjectRef "$_" $mapEntry.root }) }
|
||||
|
||||
$propEl = Find-PropertyElement $propertyName
|
||||
if (-not $propEl) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# meta-edit v1.22 — Edit existing 1C metadata object XML
|
||||
# meta-edit v1.23 — Edit existing 1C metadata object XML
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -1288,7 +1288,7 @@ def build_column_fragment(col_def, indent):
|
||||
if references:
|
||||
lines.append(f"{indent}\t\t<References>")
|
||||
for ref in references:
|
||||
lines.append(f'{indent}\t\t\t<xr:Item xsi:type="xr:MDObjectRef">{ref}</xr:Item>')
|
||||
lines.append(f'{indent}\t\t\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(normalize_md_object_ref(str(ref)))}</xr:Item>')
|
||||
lines.append(f"{indent}\t\t</References>")
|
||||
else:
|
||||
lines.append(f"{indent}\t\t<References/>")
|
||||
@@ -2235,13 +2235,56 @@ def process_modify(modify_def):
|
||||
# Complex property helpers
|
||||
# ============================================================
|
||||
|
||||
# Прощающий ввод MDObjectRef-путей: русские корни метаданных → английские + ссылочные формы
|
||||
# ("CatalogRef.Валюты"/"СправочникСсылка.Валюты" → "Catalog.Валюты"). MDObjectRef ссылается на ОБЪЕКТ
|
||||
# метаданных, а не на тип ссылки; вида метаданных, оканчивающегося на Ref, не существует → схлопывание
|
||||
# однозначно. Виды на ЧЁТНЫХ позициях (0,2,4…), имена (нечётные) не трогаем. Канонические английские
|
||||
# пути неизменны. Зеркало meta-compile.
|
||||
md_ref_roots = {
|
||||
'справочник': 'Catalog', 'документ': 'Document', 'перечисление': 'Enum', 'константа': 'Constant',
|
||||
'регистрсведений': 'InformationRegister', 'регистрнакопления': 'AccumulationRegister',
|
||||
'регистрбухгалтерии': 'AccountingRegister', 'регистррасчета': 'CalculationRegister', 'регистррасчёта': 'CalculationRegister',
|
||||
'плансчетов': 'ChartOfAccounts', 'планвидовхарактеристик': 'ChartOfCharacteristicTypes',
|
||||
'планвидоврасчета': 'ChartOfCalculationTypes', 'планвидоврасчёта': 'ChartOfCalculationTypes',
|
||||
'планобмена': 'ExchangePlan', 'бизнеспроцесс': 'BusinessProcess', 'задача': 'Task',
|
||||
'журналдокументов': 'DocumentJournal', 'отчет': 'Report', 'отчёт': 'Report', 'обработка': 'DataProcessor',
|
||||
'табличнаячасть': 'TabularSection', 'реквизит': 'Attribute', 'измерение': 'Dimension', 'ресурс': 'Resource',
|
||||
'стандартныйреквизит': 'StandardAttribute', 'значениеперечисления': 'EnumValue', 'команда': 'Command',
|
||||
'признакучета': 'AccountingFlag', 'признакучёта': 'AccountingFlag',
|
||||
'catalogref': 'Catalog', 'documentref': 'Document', 'enumref': 'Enum',
|
||||
'chartofaccountsref': 'ChartOfAccounts', 'chartofcharacteristictypesref': 'ChartOfCharacteristicTypes',
|
||||
'chartofcalculationtypesref': 'ChartOfCalculationTypes', 'exchangeplanref': 'ExchangePlan',
|
||||
'businessprocessref': 'BusinessProcess', 'taskref': 'Task',
|
||||
'справочникссылка': 'Catalog', 'документссылка': 'Document', 'перечислениессылка': 'Enum',
|
||||
'плансчетовссылка': 'ChartOfAccounts', 'планвидовхарактеристикссылка': 'ChartOfCharacteristicTypes',
|
||||
'планвидоврасчетассылка': 'ChartOfCalculationTypes', 'планвидоврасчётассылка': 'ChartOfCalculationTypes',
|
||||
'планобменассылка': 'ExchangePlan', 'бизнеспроцессссылка': 'BusinessProcess', 'задачассылка': 'Task',
|
||||
}
|
||||
|
||||
|
||||
def normalize_md_object_ref(ref, default_root=None):
|
||||
"""default_root — корень для ГОЛОГО имени без точки (owners: "Валюты" → "Catalog.Валюты")."""
|
||||
if not ref:
|
||||
return ref
|
||||
if '.' not in ref:
|
||||
return f'{default_root}.{ref}' if default_root else ref
|
||||
parts = ref.split('.')
|
||||
for k in range(0, len(parts), 2):
|
||||
t = md_ref_roots.get(parts[k].lower())
|
||||
if t:
|
||||
parts[k] = t
|
||||
return '.'.join(parts)
|
||||
|
||||
|
||||
# mdref — значения списка суть MDObjectRef-пути → прогоняем через normalize_md_object_ref.
|
||||
# root — корень для голого имени без точки.
|
||||
complex_property_map = {
|
||||
"Owners": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"'},
|
||||
"RegisterRecords": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"'},
|
||||
"BasedOn": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"'},
|
||||
"Owners": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"', "mdref": True, "root": "Catalog"},
|
||||
"RegisterRecords": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"', "mdref": True},
|
||||
"BasedOn": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"', "mdref": True},
|
||||
"InputByString": {"tag": "xr:Field", "attr": None},
|
||||
"DataLockFields": {"tag": "xr:Field", "attr": None, "expand": True},
|
||||
"RegisteredDocuments": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"'},
|
||||
"RegisteredDocuments": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"', "mdref": True},
|
||||
}
|
||||
|
||||
# Известные свойства объекта (union по корпусу acc+erp 8.3.24) — allowlist для modify-property.
|
||||
@@ -2777,6 +2820,8 @@ def add_complex_property_item(property_name, values):
|
||||
return
|
||||
if map_entry.get("expand"):
|
||||
values = [expand_data_path(str(v)) for v in values]
|
||||
if map_entry.get("mdref"):
|
||||
values = [normalize_md_object_ref(str(v), map_entry.get("root")) for v in values]
|
||||
|
||||
prop_el = find_property_element(property_name)
|
||||
if prop_el is None:
|
||||
@@ -2819,6 +2864,8 @@ def remove_complex_property_item(property_name, values):
|
||||
map_entry = complex_property_map.get(property_name)
|
||||
if map_entry and map_entry.get("expand"):
|
||||
values = [expand_data_path(str(v)) for v in values]
|
||||
if map_entry and map_entry.get("mdref"):
|
||||
values = [normalize_md_object_ref(str(v), map_entry.get("root")) for v in values]
|
||||
prop_el = find_property_element(property_name)
|
||||
if prop_el is None:
|
||||
warn(f"Property element '{property_name}' not found in Properties")
|
||||
@@ -2851,6 +2898,8 @@ def set_complex_property(property_name, values):
|
||||
return
|
||||
if map_entry.get("expand"):
|
||||
values = [expand_data_path(str(v)) for v in values]
|
||||
if map_entry.get("mdref"):
|
||||
values = [normalize_md_object_ref(str(v), map_entry.get("root")) for v in values]
|
||||
|
||||
prop_el = find_property_element(property_name)
|
||||
if prop_el is None:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# meta-validate v1.10 — Validate 1C metadata object structure (+корневой <Type>: скаляр без структуры = ошибка)
|
||||
# meta-validate v1.12 — Validate 1C metadata object structure (+корневой <Type>: скаляр без структуры = ошибка)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -1493,6 +1493,71 @@ if ($script:configDir) {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check 18: свойства, появившиеся в новых версиях формата ---
|
||||
# Реестр «тег → минимальная версия формата». Служит двум целям: (1) поймать свойство в файле со
|
||||
# слишком старым штампом — при сборке на старой платформе оно будет молча отброшено (платформа
|
||||
# рапортует успех, а свойство теряется); (2) подсказать, что конструкция требует более нового
|
||||
# формата. Расширяется одной строкой на свойство — задел под 2.21 (8.5) и последующие.
|
||||
$versionedProps = @{
|
||||
"TypeReductionMode" = "2.20" # режим приведения типов (стандартные реквизиты, измерения РС)
|
||||
"LineNumberLength" = "2.20" # длина номера строки ТЧ (5..9)
|
||||
}
|
||||
# Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17").
|
||||
function Get-FormatRank([string]$v) {
|
||||
if ($v -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||
return 0
|
||||
}
|
||||
$fileRank = Get-FormatRank $version
|
||||
if ($fileRank -gt 0) {
|
||||
foreach ($vp in ($versionedProps.Keys | Sort-Object)) {
|
||||
$nodes = $xmlDoc.SelectNodes("//md:$vp | //xr:$vp", $ns)
|
||||
if ($nodes -and $nodes.Count -gt 0 -and $fileRank -lt (Get-FormatRank $versionedProps[$vp])) {
|
||||
Report-Error "18. <$vp> появился в формате $($versionedProps[$vp]), а файл объявлен как $version — на платформе этой версии свойство будет отброшено при загрузке"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check 19: LineNumberLength — допустимый диапазон 5..9 ---
|
||||
# Длина номера строки ТЧ: 5 (до 99 999 строк) … 9 (до 999 999 999). Границы — из документации 1С.
|
||||
foreach ($lnl in @($xmlDoc.SelectNodes("//md:LineNumberLength", $ns))) {
|
||||
$raw = $lnl.InnerText.Trim()
|
||||
if ($raw -notmatch '^\d+$') {
|
||||
Report-Error "19. LineNumberLength='$raw' — должно быть целое число 5..9"
|
||||
} elseif ([int]$raw -lt 5 -or [int]$raw -gt 9) {
|
||||
Report-Error "19. LineNumberLength=$raw вне допустимого диапазона 5..9"
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check 17: MDObjectRef form — ссылка должна указывать на ОБЪЕКТ метаданных, а не на тип ссылки ---
|
||||
# Owners/BasedOn/RegisterRecords/RegisteredDocuments/References содержат путь вида "Catalog.Валюты".
|
||||
# "CatalogRef.Валюты" — частая ошибка (тип ссылки вместо объекта): платформа отвечает
|
||||
# «Неизвестный объект метаданных». Вида метаданных, оканчивающегося на Ref, не существует → ERROR.
|
||||
# Неизвестный первый сегмент без Ref — только WARN (список видов может быть неполон).
|
||||
|
||||
$mdRefNodes = $xmlDoc.SelectNodes("//*[@xsi:type='xr:MDObjectRef']", $ns)
|
||||
if ($mdRefNodes -and $mdRefNodes.Count -gt 0) {
|
||||
$knownRoots = @($validTypes) + @($structuralOnlyTypes)
|
||||
$badRefForm = @{} # значение -> $true (ссылочная форма, гарантированно нерабочая)
|
||||
$unknownRoot = @{} # значение -> корень
|
||||
foreach ($rn in $mdRefNodes) {
|
||||
$rv = $rn.InnerText.Trim()
|
||||
if (-not $rv) { continue }
|
||||
$root = $rv.Split('.')[0]
|
||||
if ($knownRoots -ccontains $root) { continue }
|
||||
if ($root -cmatch 'Ref$') { $badRefForm[$rv] = $true } else { $unknownRoot[$rv] = $root }
|
||||
}
|
||||
foreach ($bk in ($badRefForm.Keys | Sort-Object)) {
|
||||
$fixed = $bk -replace '^([A-Za-z]+)Ref\.', '$1.'
|
||||
Report-Error "17. MDObjectRef '$bk' — ссылка на ТИП, а не на объект метаданных; нужно '$fixed' (иначе «Неизвестный объект метаданных» при загрузке)"
|
||||
}
|
||||
foreach ($uk in ($unknownRoot.Keys | Sort-Object)) {
|
||||
Report-Warn "17. MDObjectRef '$uk' — неизвестный вид метаданных '$($unknownRoot[$uk])' (опечатка?)"
|
||||
}
|
||||
if ($badRefForm.Count -eq 0 -and $unknownRoot.Count -eq 0) {
|
||||
Report-OK "17. MDObjectRef form: $($mdRefNodes.Count) checked"
|
||||
}
|
||||
}
|
||||
|
||||
# --- Final output ---
|
||||
|
||||
& $finalize
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# meta-validate v1.10 — Validate 1C metadata object structure (Python port) (+корневой <Type>: скаляр без структуры = ошибка)
|
||||
# meta-validate v1.12 — Validate 1C metadata object structure (Python port) (+корневой <Type>: скаляр без структуры = ошибка)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import os
|
||||
@@ -1395,6 +1395,69 @@ if config_dir:
|
||||
elif checked_refs:
|
||||
report_ok(f"16. Reference types: {len(checked_refs)} resolved")
|
||||
|
||||
# ── Check 18: свойства, появившиеся в новых версиях формата ──
|
||||
# Реестр «тег → минимальная версия формата». Служит двум целям: (1) поймать свойство в файле со
|
||||
# слишком старым штампом — при сборке на старой платформе оно будет молча отброшено (платформа
|
||||
# рапортует успех, а свойство теряется); (2) подсказать, что конструкция требует более нового
|
||||
# формата. Расширяется одной строкой на свойство — задел под 2.21 (8.5) и последующие.
|
||||
versioned_props = {
|
||||
"TypeReductionMode": "2.20", # режим приведения типов (стандартные реквизиты, измерения РС)
|
||||
"LineNumberLength": "2.20", # длина номера строки ТЧ (5..9)
|
||||
}
|
||||
|
||||
|
||||
def format_rank(v):
|
||||
""""2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||
m = re.match(r'^(\d+)\.(\d+)$', v or '')
|
||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||
|
||||
|
||||
file_rank = format_rank(version)
|
||||
if file_rank > 0:
|
||||
for vp in sorted(versioned_props):
|
||||
nodes = find_all(root, f"//md:{vp} | //xr:{vp}")
|
||||
if nodes and file_rank < format_rank(versioned_props[vp]):
|
||||
report_error(f"18. <{vp}> появился в формате {versioned_props[vp]}, а файл объявлен как {version} — на платформе этой версии свойство будет отброшено при загрузке")
|
||||
|
||||
# ── Check 19: LineNumberLength — допустимый диапазон 5..9 ──
|
||||
# Длина номера строки ТЧ: 5 (до 99 999 строк) … 9 (до 999 999 999). Границы — из документации 1С.
|
||||
for lnl in find_all(root, "//md:LineNumberLength"):
|
||||
raw = inner_text(lnl).strip()
|
||||
if not re.match(r'^\d+$', raw):
|
||||
report_error(f"19. LineNumberLength='{raw}' — должно быть целое число 5..9")
|
||||
elif int(raw) < 5 or int(raw) > 9:
|
||||
report_error(f"19. LineNumberLength={raw} вне допустимого диапазона 5..9")
|
||||
|
||||
# ── Check 17: MDObjectRef form — ссылка на ОБЪЕКТ метаданных, а не на тип ссылки ──
|
||||
# Owners/BasedOn/RegisterRecords/RegisteredDocuments/References содержат путь вида "Catalog.Валюты".
|
||||
# "CatalogRef.Валюты" — частая ошибка (тип ссылки вместо объекта): платформа отвечает
|
||||
# «Неизвестный объект метаданных». Вида метаданных, оканчивающегося на Ref, не существует → ERROR.
|
||||
# Неизвестный первый сегмент без Ref — только WARN (список видов может быть неполон).
|
||||
|
||||
md_ref_nodes = find_all(root, "//*[@xsi:type='xr:MDObjectRef']")
|
||||
if md_ref_nodes:
|
||||
known_roots = tuple(valid_types) + tuple(structural_only_types)
|
||||
bad_ref_form = {} # значение -> True (ссылочная форма, гарантированно нерабочая)
|
||||
unknown_root = {} # значение -> корень
|
||||
for rn in md_ref_nodes:
|
||||
rv = inner_text(rn).strip()
|
||||
if not rv:
|
||||
continue
|
||||
rroot = rv.split('.')[0]
|
||||
if rroot in known_roots:
|
||||
continue
|
||||
if rroot.endswith('Ref'):
|
||||
bad_ref_form[rv] = True
|
||||
else:
|
||||
unknown_root[rv] = rroot
|
||||
for bk in sorted(bad_ref_form):
|
||||
fixed = re.sub(r'^([A-Za-z]+)Ref\.', r'\1.', bk)
|
||||
report_error(f"17. MDObjectRef '{bk}' — ссылка на ТИП, а не на объект метаданных; нужно '{fixed}' (иначе «Неизвестный объект метаданных» при загрузке)")
|
||||
for uk in sorted(unknown_root):
|
||||
report_warn(f"17. MDObjectRef '{uk}' — неизвестный вид метаданных '{unknown_root[uk]}' (опечатка?)")
|
||||
if not bad_ref_form and not unknown_root:
|
||||
report_ok(f"17. MDObjectRef form: {len(md_ref_nodes)} checked")
|
||||
|
||||
# ── Final output ──────────────────────────────────────────────
|
||||
|
||||
finalize()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# skd-compile v1.108 — Compile 1C DCS from JSON
|
||||
# skd-compile v1.109 — Compile 1C DCS from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[string]$DefinitionFile,
|
||||
@@ -3202,12 +3202,15 @@ function Emit-TableAxisBlock {
|
||||
if ($block.filter) {
|
||||
Emit-Filter -items $block.filter -indent $indent
|
||||
}
|
||||
if ($block.order) {
|
||||
Emit-Order -items $block.order -indent $indent
|
||||
}
|
||||
if ($block.selection) {
|
||||
Emit-Selection -items $block.selection -indent $indent
|
||||
}
|
||||
# Платформа на осях (column/row/point/series) всегда пишет order+selection; при отсутствии
|
||||
# ключа кладёт Auto (как ручное добавление оси в конфигураторе). Ключ присутствует (в т.ч.
|
||||
# пустой [] ) — уважаем как задано.
|
||||
$hasOrderKey = $block.PSObject.Properties.Match('order').Count -gt 0
|
||||
$orderItems = if ($hasOrderKey) { $block.order } else { @('Auto') }
|
||||
Emit-Order -items $orderItems -indent $indent
|
||||
$hasSelKey = $block.PSObject.Properties.Match('selection').Count -gt 0
|
||||
$selItems = if ($hasSelKey) { $block.selection } else { @('Auto') }
|
||||
Emit-Selection -items $selItems -indent $indent
|
||||
if ($block.conditionalAppearance) {
|
||||
Emit-ConditionalAppearance -items $block.conditionalAppearance -indent $indent
|
||||
}
|
||||
@@ -3262,13 +3265,15 @@ function Emit-StructureItem {
|
||||
$gb = if ($item.groupBy) { $item.groupBy } else { $item.groupFields }
|
||||
Emit-GroupItems -groupBy $gb -indent "$indent`t"
|
||||
|
||||
# Emit order/selection only if specified — platform doesn't always emit them on group
|
||||
if ($item.order) {
|
||||
Emit-Order -items $item.order -indent "$indent`t" -blockViewMode $item.orderViewMode -blockUserSettingID $item.orderUserSettingID
|
||||
}
|
||||
if ($item.selection) {
|
||||
Emit-Selection -items $item.selection -indent "$indent`t"
|
||||
}
|
||||
# Платформа на группировке (плоской и вложенной в ось, short/explicit) всегда пишет
|
||||
# order+selection; при отсутствии ключа кладёт Auto. Ключ присутствует (в т.ч. пустой [])
|
||||
# — уважаем как задано (blockViewMode/userSettingID имеют смысл только при явном order).
|
||||
$hasGrpOrderKey = $item.PSObject.Properties.Match('order').Count -gt 0
|
||||
$grpOrderItems = if ($hasGrpOrderKey) { $item.order } else { @('Auto') }
|
||||
Emit-Order -items $grpOrderItems -indent "$indent`t" -blockViewMode $item.orderViewMode -blockUserSettingID $item.orderUserSettingID
|
||||
$hasGrpSelKey = $item.PSObject.Properties.Match('selection').Count -gt 0
|
||||
$grpSelItems = if ($hasGrpSelKey) { $item.selection } else { @('Auto') }
|
||||
Emit-Selection -items $grpSelItems -indent "$indent`t"
|
||||
|
||||
Emit-Filter -items $item.filter -indent "$indent`t"
|
||||
|
||||
@@ -3422,8 +3427,11 @@ function Emit-StructureItem {
|
||||
}
|
||||
}
|
||||
|
||||
# Selection (chart values)
|
||||
Emit-Selection -items $item.selection -indent "$indent`t"
|
||||
# Selection (chart values) — платформа всегда пишет chart-level selection; при отсутствии
|
||||
# ключа кладёт Auto.
|
||||
$hasChartSelKey = $item.PSObject.Properties.Match('selection').Count -gt 0
|
||||
$chartSelItems = if ($hasChartSelKey) { $item.selection } else { @('Auto') }
|
||||
Emit-Selection -items $chartSelItems -indent "$indent`t"
|
||||
|
||||
if ($item.outputParameters) {
|
||||
Emit-OutputParameters -params $item.outputParameters -indent "$indent`t"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# skd-compile v1.108 — Compile 1C DCS from JSON
|
||||
# skd-compile v1.109 — Compile 1C DCS from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import json
|
||||
@@ -2631,10 +2631,13 @@ def emit_table_axis_block(lines, block, indent, emit_name=True):
|
||||
emit_group_items(lines, gb, indent)
|
||||
if block.get('filter'):
|
||||
emit_filter(lines, block['filter'], indent)
|
||||
if block.get('order'):
|
||||
emit_order(lines, block['order'], indent)
|
||||
if block.get('selection'):
|
||||
emit_selection(lines, block['selection'], indent)
|
||||
# Платформа на осях (column/row/point/series) всегда пишет order+selection; при отсутствии
|
||||
# ключа кладёт Auto (как ручное добавление оси в конфигураторе). Ключ присутствует (в т.ч.
|
||||
# пустой [] ) — уважаем как задано.
|
||||
order_items = block['order'] if 'order' in block else ['Auto']
|
||||
emit_order(lines, order_items, indent)
|
||||
sel_items = block['selection'] if 'selection' in block else ['Auto']
|
||||
emit_selection(lines, sel_items, indent)
|
||||
if block.get('conditionalAppearance'):
|
||||
emit_conditional_appearance(lines, block['conditionalAppearance'], indent)
|
||||
if block.get('outputParameters'):
|
||||
@@ -2674,11 +2677,13 @@ def emit_structure_item(lines, item, indent, short_group=False):
|
||||
|
||||
emit_group_items(lines, item.get('groupBy') or item.get('groupFields'), f'{indent}\t')
|
||||
|
||||
# Emit order/selection only if specified — platform doesn't always emit them on group
|
||||
if item.get('order'):
|
||||
emit_order(lines, item['order'], f'{indent}\t', block_view_mode=item.get('orderViewMode'), block_user_setting_id=item.get('orderUserSettingID'))
|
||||
if item.get('selection'):
|
||||
emit_selection(lines, item['selection'], f'{indent}\t')
|
||||
# Платформа на группировке (плоской и вложенной в ось, short/explicit) всегда пишет
|
||||
# order+selection; при отсутствии ключа кладёт Auto. Ключ присутствует (в т.ч. пустой [])
|
||||
# — уважаем как задано (blockViewMode/userSettingID имеют смысл только при явном order).
|
||||
grp_order_items = item['order'] if 'order' in item else ['Auto']
|
||||
emit_order(lines, grp_order_items, f'{indent}\t', block_view_mode=item.get('orderViewMode'), block_user_setting_id=item.get('orderUserSettingID'))
|
||||
grp_sel_items = item['selection'] if 'selection' in item else ['Auto']
|
||||
emit_selection(lines, grp_sel_items, f'{indent}\t')
|
||||
|
||||
emit_filter(lines, item.get('filter'), f'{indent}\t')
|
||||
|
||||
@@ -2783,8 +2788,10 @@ def emit_structure_item(lines, item, indent, short_group=False):
|
||||
emit_table_axis_block(lines, sb, f'{indent}\t\t')
|
||||
lines.append(f'{indent}\t</dcsset:series>')
|
||||
|
||||
# Selection (chart values)
|
||||
emit_selection(lines, item.get('selection'), f'{indent}\t')
|
||||
# Selection (chart values) — платформа всегда пишет chart-level selection; при отсутствии
|
||||
# ключа кладёт Auto.
|
||||
chart_sel_items = item['selection'] if 'selection' in item else ['Auto']
|
||||
emit_selection(lines, chart_sel_items, f'{indent}\t')
|
||||
|
||||
if item.get('outputParameters'):
|
||||
emit_output_parameters(lines, item['outputParameters'], f'{indent}\t')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# skd-decompile v0.90 — Decompile 1C DCS Template.xml to JSON DSL (draft)
|
||||
# skd-decompile v0.91 — Decompile 1C DCS Template.xml to JSON DSL (draft)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -2223,18 +2223,20 @@ function Build-TableAxisBlock {
|
||||
foreach ($fc in $fNode.SelectNodes("dcsset:item", $ns)) { $fa += (Build-FilterItem -itemNode $fc -loc "$loc/filter") }
|
||||
$entry['filter'] = $fa
|
||||
}
|
||||
# order — preserve presence (even [Auto]) for bit-perfect round-trip
|
||||
# order/selection — всегда явные (принцип «декомпилятор всегда явный»): [Auto] сохраняем как есть,
|
||||
# отсутствие/пустоту эмитим как [] — иначе compile впаяет дефолтный Auto (round-trip рвётся на
|
||||
# осях без выбора, напр. ветки use=false). [] на входе compile → эмитит ничего = «нет выбора».
|
||||
# NB: прямое присваивание @() (не через if-выражение — там пустой массив схлопнется в $null).
|
||||
$ordNode = $node.SelectSingleNode("dcsset:order", $ns)
|
||||
if ($ordNode) {
|
||||
$ordItems = Build-Order -ordNode $ordNode -loc "$loc/order"
|
||||
if ($ordItems.Count -gt 0) { $entry['order'] = $ordItems }
|
||||
}
|
||||
# selection — preserve presence (even [Auto])
|
||||
if ($ordItems.Count -gt 0) { $entry['order'] = $ordItems } else { $entry['order'] = @() }
|
||||
} else { $entry['order'] = @() }
|
||||
$selNode = $node.SelectSingleNode("dcsset:selection", $ns)
|
||||
if ($selNode) {
|
||||
$selItems = Build-Selection -selNode $selNode -loc "$loc/selection"
|
||||
if ($selItems.Count -gt 0) { $entry['selection'] = $selItems }
|
||||
}
|
||||
if ($selItems.Count -gt 0) { $entry['selection'] = $selItems } else { $entry['selection'] = @() }
|
||||
} else { $entry['selection'] = @() }
|
||||
# conditionalAppearance block
|
||||
$caN = $node.SelectSingleNode("dcsset:conditionalAppearance", $ns)
|
||||
if ($caN) {
|
||||
@@ -2381,11 +2383,13 @@ function Build-Structure {
|
||||
$entry['series'] = $sArr
|
||||
}
|
||||
# Selection (chart values) — сохраняем даже [Auto] для bit-perfect presence
|
||||
# chart-level selection — всегда явно ([] при отсутствии/пустоте, иначе compile впаяет Auto).
|
||||
# NB: прямое присваивание @() (не через if-выражение — пустой массив там схлопнется в $null).
|
||||
$selN = $it.SelectSingleNode("dcsset:selection", $ns)
|
||||
if ($selN) {
|
||||
$selI = Build-Selection -selNode $selN -loc "$loc/$idx/selection"
|
||||
if ($selI.Count -gt 0) { $entry['selection'] = $selI }
|
||||
}
|
||||
if ($selI.Count -gt 0) { $entry['selection'] = $selI } else { $entry['selection'] = @() }
|
||||
} else { $entry['selection'] = @() }
|
||||
$opN = $it.SelectSingleNode("dcsset:outputParameters", $ns)
|
||||
$op = Build-OutputParameters -opNode $opN
|
||||
if ($op -and $op.Count -gt 0) { $entry['outputParameters'] = $op }
|
||||
@@ -2427,17 +2431,21 @@ function Build-Structure {
|
||||
$gFields = Get-GroupFields -parentNode $it -loc $loc
|
||||
if ($gFields.Count -gt 0) { $entry['groupFields'] = $gFields }
|
||||
|
||||
# Local selection — preserve presence (even [Auto]) for bit-perfect round-trip
|
||||
# Local selection/order — всегда явные: [Auto] как есть, отсутствие/пустоту как [] (иначе compile
|
||||
# впаяет дефолтный Auto → round-trip рвётся на группах без выбора, напр. ветки use=false).
|
||||
# [] не Auto-only → Try-StructureShorthand не свернёт такую группу в shorthand (и не добавит Auto).
|
||||
# NB: прямое присваивание @() (не через if-выражение — пустой массив там схлопнется в $null).
|
||||
$selNode = $it.SelectSingleNode("dcsset:selection", $ns)
|
||||
if ($selNode) {
|
||||
$selItems = Build-Selection -selNode $selNode -loc "$loc/selection"
|
||||
if ($selItems.Count -gt 0) { $entry['selection'] = $selItems }
|
||||
}
|
||||
# Local order — same
|
||||
if ($selItems.Count -gt 0) { $entry['selection'] = $selItems } else { $entry['selection'] = @() }
|
||||
} else { $entry['selection'] = @() }
|
||||
$ordNode = $it.SelectSingleNode("dcsset:order", $ns)
|
||||
if ($ordNode) {
|
||||
$ordItems = Build-Order -ordNode $ordNode -loc "$loc/order"
|
||||
if ($ordItems.Count -gt 0) { $entry['order'] = $ordItems }
|
||||
if ($ordItems.Count -gt 0) { $entry['order'] = $ordItems } else { $entry['order'] = @() }
|
||||
} else { $entry['order'] = @() }
|
||||
if ($ordNode) {
|
||||
# Block-level viewMode/userSettingID на <dcsset:order>
|
||||
foreach ($ch in $ordNode.ChildNodes) {
|
||||
if ($ch.NodeType -ne 'Element' -or $ch.NamespaceURI -ne 'http://v8.1c.ru/8.1/data-composition-system/settings') { continue }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# skd-decompile v0.90 — Decompile 1C DCS Template.xml to JSON DSL (draft)
|
||||
# skd-decompile v0.91 — Decompile 1C DCS Template.xml to JSON DSL (draft)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import os
|
||||
@@ -2327,16 +2327,14 @@ def build_table_axis_block(node, loc, include_name=False):
|
||||
for fc in f_node.select_nodes("dcsset:item"):
|
||||
fa.append(build_filter_item(fc, "%s/filter" % loc))
|
||||
entry['filter'] = fa
|
||||
# order/selection — всегда явные ([Auto] как есть, отсутствие/пустоту как []): иначе compile
|
||||
# впаяет дефолтный Auto → round-trip рвётся на осях без выбора (напр. ветки use=false).
|
||||
ord_node = node.select_single_node("dcsset:order")
|
||||
if ord_node:
|
||||
ord_items = build_order(ord_node, "%s/order" % loc)
|
||||
if len(ord_items) > 0:
|
||||
entry['order'] = ord_items
|
||||
ord_items = build_order(ord_node, "%s/order" % loc) if ord_node else []
|
||||
entry['order'] = ord_items if len(ord_items) > 0 else []
|
||||
sel_node = node.select_single_node("dcsset:selection")
|
||||
if sel_node:
|
||||
sel_items = build_selection(sel_node, "%s/selection" % loc)
|
||||
if len(sel_items) > 0:
|
||||
entry['selection'] = sel_items
|
||||
sel_items = build_selection(sel_node, "%s/selection" % loc) if sel_node else []
|
||||
entry['selection'] = sel_items if len(sel_items) > 0 else []
|
||||
ca_n = node.select_single_node("dcsset:conditionalAppearance")
|
||||
if ca_n:
|
||||
ca = build_conditional_appearance(ca_n, "%s/ca" % loc)
|
||||
@@ -2485,11 +2483,10 @@ def build_structure(node, loc):
|
||||
s_arr.append(build_table_axis_block(s, "%s/%d/series[%d]" % (loc, idx, si)))
|
||||
si += 1
|
||||
entry['series'] = s_arr
|
||||
# chart-level selection — всегда явно ([] при отсутствии/пустоте, иначе compile впаяет Auto)
|
||||
sel_n = it.select_single_node("dcsset:selection")
|
||||
if sel_n:
|
||||
sel_i = build_selection(sel_n, "%s/%d/selection" % (loc, idx))
|
||||
if len(sel_i) > 0:
|
||||
entry['selection'] = sel_i
|
||||
sel_i = build_selection(sel_n, "%s/%d/selection" % (loc, idx)) if sel_n else []
|
||||
entry['selection'] = sel_i if len(sel_i) > 0 else []
|
||||
op_n = it.select_single_node("dcsset:outputParameters")
|
||||
op = build_output_parameters(op_n)
|
||||
if op and len(op) > 0:
|
||||
@@ -2532,16 +2529,16 @@ def build_structure(node, loc):
|
||||
if len(g_fields) > 0:
|
||||
entry['groupFields'] = g_fields
|
||||
|
||||
# Local selection/order — всегда явные ([Auto] как есть, отсутствие/пустоту как []): иначе
|
||||
# compile впаяет дефолтный Auto → round-trip рвётся на группах без выбора (напр. use=false).
|
||||
# [] не Auto-only → try_structure_shorthand не свернёт такую группу в shorthand (без Auto).
|
||||
sel_node = it.select_single_node("dcsset:selection")
|
||||
if sel_node:
|
||||
sel_items = build_selection(sel_node, "%s/selection" % loc)
|
||||
if len(sel_items) > 0:
|
||||
entry['selection'] = sel_items
|
||||
sel_items = build_selection(sel_node, "%s/selection" % loc) if sel_node else []
|
||||
entry['selection'] = sel_items if len(sel_items) > 0 else []
|
||||
ord_node = it.select_single_node("dcsset:order")
|
||||
ord_items = build_order(ord_node, "%s/order" % loc) if ord_node else []
|
||||
entry['order'] = ord_items if len(ord_items) > 0 else []
|
||||
if ord_node:
|
||||
ord_items = build_order(ord_node, "%s/order" % loc)
|
||||
if len(ord_items) > 0:
|
||||
entry['order'] = ord_items
|
||||
for ch in ord_node.child_nodes:
|
||||
if ch.namespace_uri != NS_SET:
|
||||
continue
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# template-add v1.9 — Add template to 1C object
|
||||
# template-add v1.10 — Add template to 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -329,7 +329,10 @@ if (-not $childObjects) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Добавить <Template> в конец ChildObjects
|
||||
# Добавить <Template> в конец ChildObjects — идемпотентно (не дублировать уже зарегистрированный)
|
||||
$alreadyRegistered = [bool]$childObjects.SelectSingleNode("md:Template[text()='$TemplateName']", $nsMgr)
|
||||
|
||||
if (-not $alreadyRegistered) {
|
||||
$templateElem = $xmlDoc.CreateElement("Template", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$templateElem.InnerText = $TemplateName
|
||||
|
||||
@@ -349,6 +352,7 @@ if ($childObjects.ChildNodes.Count -eq 0) {
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t")) | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- 4. MainDataCompositionSchema (для ExternalReport / Report) ---
|
||||
|
||||
@@ -392,6 +396,9 @@ $writer.Close()
|
||||
$stream.Close()
|
||||
|
||||
Write-Host "[OK] Создан макет: $TemplateName ($TemplateType)"
|
||||
if ($alreadyRegistered) {
|
||||
Write-Host " Already registered: <Template>$TemplateName</Template> in ChildObjects (skipped duplicate)"
|
||||
}
|
||||
Write-Host " Метаданные: $templateMetaPath"
|
||||
Write-Host " Содержимое: $templateFilePath"
|
||||
if ($mainDCSUpdated) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# add-template v1.9 — Add template to 1C object
|
||||
# add-template v1.10 — Add template to 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -446,31 +446,34 @@ def main():
|
||||
print(f"Не найден элемент ChildObjects в {root_xml_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Add <Template> to end of ChildObjects
|
||||
template_elem = etree.SubElement(child_objects, f"{{{ns}}}Template")
|
||||
template_elem.text = template_name
|
||||
# Remove auto-appended element to reinsert with proper whitespace
|
||||
child_objects.remove(template_elem)
|
||||
# Add <Template> to end of ChildObjects — idempotent (do not duplicate already-registered template)
|
||||
already_registered = child_objects.find(f"md:Template[.='{template_name}']", NSMAP) is not None
|
||||
|
||||
children = list(child_objects)
|
||||
if len(children) == 0 and (child_objects.text is None or child_objects.text.strip() == ""):
|
||||
# Empty ChildObjects (self-closing)
|
||||
child_objects.text = "\n\t\t\t"
|
||||
child_objects.append(template_elem)
|
||||
template_elem.tail = "\n\t\t"
|
||||
else:
|
||||
if len(children) > 0:
|
||||
last_child = children[-1]
|
||||
# last_child.tail is the trailing whitespace before </ChildObjects>
|
||||
old_tail = last_child.tail
|
||||
last_child.tail = "\n\t\t\t"
|
||||
child_objects.append(template_elem)
|
||||
template_elem.tail = old_tail if old_tail else "\n\t\t"
|
||||
else:
|
||||
# Has text content but no element children
|
||||
child_objects.text = (child_objects.text or "") + "\n\t\t\t"
|
||||
if not already_registered:
|
||||
template_elem = etree.SubElement(child_objects, f"{{{ns}}}Template")
|
||||
template_elem.text = template_name
|
||||
# Remove auto-appended element to reinsert with proper whitespace
|
||||
child_objects.remove(template_elem)
|
||||
|
||||
children = list(child_objects)
|
||||
if len(children) == 0 and (child_objects.text is None or child_objects.text.strip() == ""):
|
||||
# Empty ChildObjects (self-closing)
|
||||
child_objects.text = "\n\t\t\t"
|
||||
child_objects.append(template_elem)
|
||||
template_elem.tail = "\n\t\t"
|
||||
else:
|
||||
if len(children) > 0:
|
||||
last_child = children[-1]
|
||||
# last_child.tail is the trailing whitespace before </ChildObjects>
|
||||
old_tail = last_child.tail
|
||||
last_child.tail = "\n\t\t\t"
|
||||
child_objects.append(template_elem)
|
||||
template_elem.tail = old_tail if old_tail else "\n\t\t"
|
||||
else:
|
||||
# Has text content but no element children
|
||||
child_objects.text = (child_objects.text or "") + "\n\t\t\t"
|
||||
child_objects.append(template_elem)
|
||||
template_elem.tail = "\n\t\t"
|
||||
|
||||
# --- 4. MainDataCompositionSchema (for ExternalReport / Report) ---
|
||||
|
||||
@@ -500,6 +503,8 @@ def main():
|
||||
save_xml_with_bom(tree, root_xml_full)
|
||||
|
||||
print(f"[OK] Создан макет: {template_name} ({template_type})")
|
||||
if already_registered:
|
||||
print(f" Already registered: <Template>{template_name}</Template> in ChildObjects (skipped duplicate)")
|
||||
print(f" Метаданные: {template_meta_path}")
|
||||
print(f" Содержимое: {template_file_path}")
|
||||
if main_dcs_updated:
|
||||
|
||||
@@ -129,7 +129,7 @@ Switch to an already-open tab/window (fuzzy match).
|
||||
|
||||
### Reading form state
|
||||
|
||||
#### `getFormState()` → `{ form, formCount, openForms, fields, buttons, tabs, navigation?, table, tables, filters, reportSettings? }`
|
||||
#### `getFormState()` → `{ form, formCount, openForms, title, fields, buttons, tabs, navigation?, table, tables, filters, reportSettings? }`
|
||||
Returns current form structure. This is the primary way to understand what's on screen.
|
||||
|
||||
**form** — active form number, or `null` when no form is open (desktop).
|
||||
@@ -142,6 +142,8 @@ Returns current form structure. This is the primary way to understand what's on
|
||||
|
||||
**openTabs** — array of `{ name, active? }` from the open-windows tab bar. Only present when the tab bar is enabled in 1C settings. Do NOT rely on this — use `formCount`/`openForms` instead.
|
||||
|
||||
**title** — caption of the active form (`"Контрагенты"`, `"Заказ поставщику ТД00-000052 от 05.07.2022"`). Read from the form's own header, which does not depend on the open-windows tab bar; when the form shows no header, falls back to the active tab's caption, and is `null` when neither is available.
|
||||
|
||||
**fields** — each field has: `name`, `value`, `label?`, `actions?` (select, clear, open), `required?` (true for unfilled mandatory fields), `disabled?` (control is unavailable). `buttons[]` carry `disabled?` too.
|
||||
|
||||
**navigation** — form navigation panel links (for objects with subordinate catalogs): `[{ name, active? }]`. Clickable via `clickElement()`. Only present when the form has a navigation panel (e.g. "Основное", "Объекты метаданных", "Подсистемы").
|
||||
@@ -170,7 +172,7 @@ const form = await getFormState();
|
||||
|
||||
**confirmation** — if present, a Yes/No dialog is shown. Call `clickElement('Да')` or `clickElement('Нет')`.
|
||||
|
||||
**errors.stateText** — array of SpreadsheetDocument state messages (e.g. `"Не установлено значение параметра \"X\""`, `"Отчет не сформирован..."`, `"Изменились настройки..."`). Present when the report area shows an info bar instead of data.
|
||||
**errors.stateText** — array of SpreadsheetDocument state messages (e.g. `"Не установлено значение параметра \"X\""`, `"Отчет не сформирован..."`, `"Изменились настройки..."`). Present when the report area shows an info bar instead of data. The same info bar carries `"Поиск..."` while a list is still searching — actions do not return while it is up, so a filtered list never hands you the previous rows.
|
||||
|
||||
### Reading data
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ node $RUN test <dir|file>... [flags]
|
||||
|
||||
Positional args are test paths (files and/or dirs, multiple allowed). URL is NOT positional — it comes from `webtest.config.mjs`; override with `--url=<url>`.
|
||||
|
||||
`webtest.config.mjs` and `_hooks.mjs` always come from the suite root, whatever path you pass: `test tests/myapp/sales/` and `test tests/myapp/sales/01-order.test.mjs` both run under the config and hooks of `tests/myapp/`, no `--url=` needed. Paths from two different suites in one run are refused — pass one suite and narrow with `--grep=` / `--tags=`.
|
||||
|
||||
Tests live next to the project they cover (not inside the skill). Convention: `tests/` at the project root, with `_hooks.mjs` and `webtest.config.mjs` at the suite root. Tests are ES modules with `*.test.mjs` suffix.
|
||||
|
||||
## When to choose `test` over `exec`
|
||||
@@ -69,7 +71,7 @@ tests/<app-name>/
|
||||
01-end-to-end.test.mjs # multi-user
|
||||
```
|
||||
|
||||
Per-folder `_hooks.mjs` / `webtest.config.mjs` inside the application subfolder are NOT supported — only the application-root copies are loaded.
|
||||
Per-folder `_hooks.mjs` / `webtest.config.mjs` inside the application subfolder are NOT supported — only the application-root copies are loaded, whichever subfolder you point the runner at.
|
||||
|
||||
## Test file anatomy
|
||||
|
||||
@@ -184,8 +186,8 @@ assert.match(string, regex, msg?) // regex.test(string)
|
||||
await assert.throws(asyncFn, msg?) // passes if fn throws (use await)
|
||||
|
||||
// 1C-specific — operate on getFormState() / readTable() output
|
||||
assert.formHasField(state, 'Контрагент', msg?) // state.fields[name] exists
|
||||
assert.formTitle(state, expected, msg?) // state.title includes expected
|
||||
assert.formHasField(state, 'Контрагент', msg?) // fields[] contains a field with that name
|
||||
assert.formTitle(state, expected, msg?) // state.title includes expected (null title → fails saying so)
|
||||
assert.tableHasRow(table, predicate, msg?) // predicate: object (partial match) or fn(row) => bool
|
||||
// object form: { 'Наименование': 'Тест' }
|
||||
// fn form: r => r['Сумма'] > 100
|
||||
@@ -316,7 +318,7 @@ export default async function({ clerk, manager, step, assert }) {
|
||||
});
|
||||
await step('Кладовщик видит новый статус', async () => {
|
||||
const s = await clerk.getFormState();
|
||||
assert.equal(s.fields['Статус']?.value, 'Утверждён');
|
||||
assert.equal(s.fields.find(f => f.name === 'Статус')?.value, 'Утверждён');
|
||||
});
|
||||
await step('Освободить сессию кладовщика', async () => {
|
||||
await manager.closeContext('clerk'); // free a 1C license for the next test
|
||||
@@ -339,7 +341,7 @@ export default async function({ openCommand, clickElement, getFormState, assert,
|
||||
await clickElement('Создать');
|
||||
await clickElement('Провести');
|
||||
const s = await getFormState();
|
||||
assert.ok(s.errorModal || s.fields['Контрагент']?.required,
|
||||
assert.ok(s.errorModal || s.fields.find(f => f.name === 'Контрагент')?.required,
|
||||
'Должна быть ошибка валидации или поле помечено обязательным');
|
||||
}
|
||||
```
|
||||
@@ -359,7 +361,7 @@ export const params = [
|
||||
export default async function({ fillFields, getFormState, assert }, { type, field, value }) {
|
||||
await fillFields({ [field]: value });
|
||||
const state = await getFormState();
|
||||
assert.equal(state.fields[field]?.value, String(value));
|
||||
assert.equal(state.fields.find(f => f.name === field)?.value, String(value));
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// web-test cli/commands/test v1.8 — regression test runner
|
||||
// web-test cli/commands/test v1.9 — regression test runner
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import { existsSync, writeFileSync, mkdirSync, renameSync, copyFileSync, unlinkSync } from 'fs';
|
||||
import { resolve, dirname, basename, relative } from 'path';
|
||||
@@ -9,6 +9,7 @@ import { createAssertions } from '../test-runner/assertions.mjs';
|
||||
import { buildSeverityIndex } from '../test-runner/severity.mjs';
|
||||
import { writeAllure, buildJUnit, syncAllureExtras } from '../test-runner/reporters.mjs';
|
||||
import { discoverTests, resetState } from '../test-runner/discover.mjs';
|
||||
import { findSuiteRoot, startDirOf } from '../test-runner/suite-root.mjs';
|
||||
import { planEviction, touchLru, dropLru } from '../test-runner/context-pool.mjs';
|
||||
|
||||
export async function cmdTest(rawArgs) {
|
||||
@@ -78,12 +79,24 @@ export async function cmdTest(rawArgs) {
|
||||
die(`Test path not found: "${p}". To run a subset use --grep= / --tags=, or pass an existing dir/file.`);
|
||||
}
|
||||
|
||||
// Load config if exists. config (webtest.config.mjs) and hooks (_hooks.mjs) resolve from
|
||||
// the FIRST path's directory — list paths from the same suite folder.
|
||||
const firstPath = resolve(testPaths[0]);
|
||||
const isFile = firstPath.endsWith('.test.mjs');
|
||||
const testDir = isFile ? dirname(firstPath) : firstPath;
|
||||
const configPath = resolve(testDir, 'webtest.config.mjs');
|
||||
// Suite root — the directory `webtest.config.mjs`, `_hooks.mjs`, `_allure/` and report paths
|
||||
// all hang off. It is NOT the passed path: walking up to the nearest marker is what makes
|
||||
// `test tests/myapp/sales/` work, as docs/web-test-regression-spec.md has always promised.
|
||||
// Resolving from the passed path instead lost the hooks of any subfolder run — silently, so
|
||||
// the run went ahead against an unprepared stand.
|
||||
const startDirs = testPaths.map(p => startDirOf(p));
|
||||
const roots = startDirs.map(d => findSuiteRoot(d));
|
||||
// Paths from different suites must not share hooks — that used to resolve to "first path
|
||||
// wins", silently running suite B's tests under suite A's preparation.
|
||||
const distinct = [...new Set(roots.map(r => r?.root ?? null))];
|
||||
if (distinct.length > 1) {
|
||||
const lines = testPaths.map((p, i) => ` ${p} → ${roots[i]?.root ?? '(корень не найден)'}`);
|
||||
die(`Paths belong to different suites — config and hooks would be ambiguous:\n${lines.join('\n')}\n` +
|
||||
`Run them separately, or pass one suite root and narrow with --grep= / --tags=.`);
|
||||
}
|
||||
const suiteRoot = roots[0]?.root ?? startDirs[0];
|
||||
const suiteRootFound = !!roots[0];
|
||||
const configPath = resolve(suiteRoot, 'webtest.config.mjs');
|
||||
let config = {};
|
||||
if (existsSync(configPath)) {
|
||||
const mod = await import('file:///' + configPath.replace(/\\/g, '/'));
|
||||
@@ -103,7 +116,16 @@ export async function cmdTest(rawArgs) {
|
||||
if (url) contextSpecs[defaultContextName] = { ...contextSpecs[defaultContextName], url };
|
||||
} else {
|
||||
const fallbackUrl = url || config.url;
|
||||
if (!fallbackUrl) die('No URL provided and no webtest.config.mjs found');
|
||||
// Name the real problem: with no suite root there is no config to take a URL from — and,
|
||||
// more dangerously, no `_hooks.mjs` either. The old wording talked only about the URL and
|
||||
// sent readers looking in the wrong place.
|
||||
if (!fallbackUrl) {
|
||||
die(suiteRootFound
|
||||
? `No URL: ${configPath} defines neither "contexts" nor "url", and --url= was not given.`
|
||||
: `Suite root not found above "${testPaths[0]}" — no webtest.config.mjs / _hooks.mjs up to ` +
|
||||
`the repository (or working) directory, so there is no URL and no stand preparation.\n` +
|
||||
`Pass the suite root (e.g. tests/myapp/) and narrow with --grep= / --tags=, or give --url=.`);
|
||||
}
|
||||
contextSpecs.default = { url: fallbackUrl };
|
||||
}
|
||||
if (!contextSpecs[defaultContextName]) {
|
||||
@@ -175,7 +197,7 @@ export async function cmdTest(rawArgs) {
|
||||
}
|
||||
const reportDir = opts.reportDir
|
||||
? resolve(opts.reportDir)
|
||||
: (opts.report && !reportToStdout ? dirname(resolve(opts.report)) : testDir);
|
||||
: (opts.report && !reportToStdout ? dirname(resolve(opts.report)) : suiteRoot);
|
||||
if (opts.screenshot !== 'off') {
|
||||
try { mkdirSync(reportDir, { recursive: true }); } catch {}
|
||||
// 1C-error screenshots (taken inside the action wrapper) default to a single
|
||||
@@ -194,7 +216,10 @@ export async function cmdTest(rawArgs) {
|
||||
for (const file of testFiles) {
|
||||
const mod = await import('file:///' + file.replace(/\\/g, '/'));
|
||||
const base = {
|
||||
file: relative(testDir, file).replace(/\\/g, '/'),
|
||||
// Relative to the SUITE ROOT, not to the passed path — otherwise the same test gets a
|
||||
// different id depending on how it was launched (`sales/01-x.test.mjs` vs `01-x.test.mjs`),
|
||||
// and Allure history / JUnit trends treat the two as unrelated tests.
|
||||
file: relative(suiteRoot, file).replace(/\\/g, '/'),
|
||||
name: mod.name || basename(file, '.test.mjs'),
|
||||
tags: mod.tags || [],
|
||||
timeout: mod.timeout || opts.timeout,
|
||||
@@ -229,7 +254,7 @@ export async function cmdTest(rawArgs) {
|
||||
});
|
||||
|
||||
// Load hooks
|
||||
const hooksPath = resolve(testDir, '_hooks.mjs');
|
||||
const hooksPath = resolve(suiteRoot, '_hooks.mjs');
|
||||
let hooks = {};
|
||||
if (existsSync(hooksPath)) {
|
||||
hooks = await import('file:///' + hooksPath.replace(/\\/g, '/'));
|
||||
@@ -239,7 +264,17 @@ export async function cmdTest(rawArgs) {
|
||||
// In `--report -` mode the machine JSON/XML takes over stdout, so progress moves to stderr.
|
||||
const W = reportToStdout ? process.stderr : process.stdout;
|
||||
W.write(`\nweb-test -- ${url}\n`);
|
||||
W.write(`Running ${filtered.length} tests from ${relative(process.cwd(), testDir).replace(/\\/g, '/') || '.'}/\n\n`);
|
||||
// Always name the resolved suite root: a climb that landed on the wrong directory is then
|
||||
// visible in the first line of output instead of being diagnosed from symptoms later.
|
||||
const rel = (p) => relative(process.cwd(), p).replace(/\\/g, '/') || '.';
|
||||
const shownPaths = testPaths.map(p => rel(resolve(p))).filter(p => p !== rel(suiteRoot));
|
||||
W.write(`Running ${filtered.length} tests from ${rel(suiteRoot)}/`);
|
||||
W.write(shownPaths.length ? ` (paths: ${shownPaths.join(', ')})\n\n` : `\n\n`);
|
||||
if (!suiteRootFound) {
|
||||
// Not fatal — a one-off test outside any suite is legitimate. But a missing suite root also
|
||||
// means no `_hooks.mjs` was even looked for above, so the stand is whatever it was.
|
||||
process.stderr.write(`! no suite root (webtest.config.mjs / _hooks.mjs) found above ${rel(startDirs[0])} — running without hooks\n`);
|
||||
}
|
||||
|
||||
const startedAt = new Date().toISOString();
|
||||
const results = [];
|
||||
@@ -819,10 +854,10 @@ export async function cmdTest(rawArgs) {
|
||||
if (opts.format === 'allure') {
|
||||
// Guard against a result-producing path that skipped recordResult; normally a no-op.
|
||||
if (!allureWritten) writeAllure(results, reportDir, severityIndex);
|
||||
syncAllureExtras(testDir, reportDir);
|
||||
syncAllureExtras(suiteRoot, reportDir);
|
||||
} else if (opts.format === 'junit') {
|
||||
if (reportToStdout) process.stdout.write(buildJUnit(report, testDir) + '\n');
|
||||
else writeFileSync(resolve(opts.report), buildJUnit(report, testDir));
|
||||
if (reportToStdout) process.stdout.write(buildJUnit(report, suiteRoot) + '\n');
|
||||
else writeFileSync(resolve(opts.report), buildJUnit(report, suiteRoot));
|
||||
} else if (reportToStdout) {
|
||||
out(report);
|
||||
} else if (opts.report) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// web-test cli/test-runner/assertions v1.0 — ctx.assert API
|
||||
// web-test cli/test-runner/assertions v1.1 — ctx.assert API
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
export function createAssertions() {
|
||||
@@ -37,11 +37,23 @@ export function createAssertions() {
|
||||
throw new AssertionError(msg || 'Expected function to throw');
|
||||
},
|
||||
// 1C-specific
|
||||
// `fields` is an ARRAY of { name, value, ... } — indexing it by field name yields undefined,
|
||||
// which used to make this assertion throw on every call, including the valid ones.
|
||||
formHasField(state, fieldName, msg) {
|
||||
if (!state?.fields?.[fieldName]) throw new AssertionError(msg || `Field "${fieldName}" not found in form. Available: ${Object.keys(state?.fields || {}).join(', ')}`, null, fieldName);
|
||||
const names = (state?.fields || []).map(f => f.name);
|
||||
if (!names.includes(fieldName)) {
|
||||
throw new AssertionError(msg || `Field "${fieldName}" not found in form. Available: ${names.join(', ')}`, null, fieldName);
|
||||
}
|
||||
},
|
||||
formTitle(state, expected, msg) {
|
||||
if (!state?.title?.includes(expected)) throw new AssertionError(msg || `Form title "${state?.title}" does not contain "${expected}"`, state?.title, expected);
|
||||
// `title` is null when the form exposes no caption and the open-windows panel is off —
|
||||
// say so instead of reporting a mismatch against "null".
|
||||
if (state?.title == null) {
|
||||
throw new AssertionError(msg || `Form title is not available (state.title is null), expected it to contain "${expected}"`, null, expected);
|
||||
}
|
||||
if (!state.title.includes(expected)) {
|
||||
throw new AssertionError(msg || `Form title "${state.title}" does not contain "${expected}"`, state.title, expected);
|
||||
}
|
||||
},
|
||||
tableHasRow(table, predicate, msg) {
|
||||
const rows = table?.rows || [];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// web-test cli/test-runner/discover v1.3 — test file discovery + state reset between tests
|
||||
// web-test cli/test-runner/discover v1.4 — test file discovery + state reset between tests
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import { existsSync, readdirSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
@@ -73,7 +73,9 @@ export async function resetState(ctx) {
|
||||
return {
|
||||
clean: false, attempts, lastError,
|
||||
form: state.form,
|
||||
title: state.activeTab || null,
|
||||
// state.title is the form's own caption; activeTab reads the open-windows panel, which the
|
||||
// user can switch off — keep it only as the fallback it always was.
|
||||
title: state.title || state.activeTab || null,
|
||||
modal: !!state.modal,
|
||||
};
|
||||
} catch (e) {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// web-test cli/test-runner/suite-root v1.0 — locate the suite root above a given test path
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import { existsSync, statSync } from 'fs';
|
||||
import { resolve, dirname } from 'path';
|
||||
|
||||
// Files that MARK a suite root. Both count, not just the config: `webtest.config.mjs` is
|
||||
// optional (a single-URL suite may pass --url= instead), and a suite that ships only
|
||||
// `_hooks.mjs` must still be found — otherwise its stand preparation is silently skipped,
|
||||
// which is worse than any URL error.
|
||||
const MARKERS = ['webtest.config.mjs', '_hooks.mjs'];
|
||||
|
||||
// Files that BOUND the climb. A boundary never selects a root — it only stops the search,
|
||||
// so a wrong boundary degrades to "root not found" (= the pre-v1.9 behaviour plus a clear
|
||||
// message) and can never produce a wrong root. `package.json` is deliberately absent: it
|
||||
// occurs nested and would stop the climb below a legitimate suite root.
|
||||
const BOUNDARIES = ['.git', '.v8-project.json'];
|
||||
|
||||
const isDir = (p) => { try { return statSync(p).isDirectory(); } catch { return false; } };
|
||||
|
||||
/**
|
||||
* Walk up from `startPath` looking for a suite root.
|
||||
*
|
||||
* @param {string} startPath A test file or directory (absolute or cwd-relative).
|
||||
* @param {{cwd?: string}} [opts]
|
||||
* @returns {{root: string, marker: string} | null} null when no marker was found within bounds.
|
||||
*
|
||||
* Stops after examining the first directory that contains `.git` / `.v8-project.json`
|
||||
* (that directory IS examined for markers), or — when neither is met — after examining `cwd`.
|
||||
* A path outside `cwd` degenerates to the filesystem root; the marker requirement still
|
||||
* makes a wrong hit unlikely, and the resolved root is printed in the run banner.
|
||||
*/
|
||||
export function findSuiteRoot(startPath, { cwd = process.cwd() } = {}) {
|
||||
const full = resolve(startPath);
|
||||
let dir = isDir(full) ? full : dirname(full);
|
||||
const cwdAbs = resolve(cwd);
|
||||
|
||||
while (true) {
|
||||
for (const m of MARKERS) {
|
||||
if (existsSync(resolve(dir, m))) return { root: dir, marker: m };
|
||||
}
|
||||
const atBoundary = BOUNDARIES.some(b => existsSync(resolve(dir, b))) || dir === cwdAbs;
|
||||
const parent = dirname(dir);
|
||||
if (atBoundary || parent === dir) return null;
|
||||
dir = parent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The directory a path contributes to root resolution — its own dir for a file, itself for
|
||||
* a directory. Also the fallback root when no marker is found (pre-v1.9 behaviour).
|
||||
*/
|
||||
export function startDirOf(testPath) {
|
||||
const full = resolve(testPath);
|
||||
return isDir(full) ? full : dirname(full);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// web-test dom/form-state v1.0 — combined detectForm + readForm + open tabs
|
||||
// web-test dom/form-state v1.1 — combined detectForm + readForm + open tabs + form caption
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import { DETECT_FORM_FN, DETECT_FORMS_FN, READ_FORM_FN } from './_shared.mjs';
|
||||
|
||||
@@ -26,7 +26,27 @@ export function getFormStateScript() {
|
||||
openTabs.push(entry);
|
||||
});
|
||||
const activeTab = openTabs.find(t => t.active)?.name || null;
|
||||
const result = { form: formNum, activeTab, openForms: meta.allForms, formCount: meta.formCount, ...formData };
|
||||
// Caption of the ACTIVE form. Lives in an attribute, not in text — the div itself is empty:
|
||||
// <div class="toplineBox" data-title="Контрагенты">
|
||||
// <div id="VW_page1headerTopLine_title" class="toplineBoxTitle" title="Контрагенты"></div>
|
||||
// Header numbering (VW_page<M>) does not match form numbering (form<N>), so the header cannot
|
||||
// be picked by form number. Several headers can be visible at once — with a selection form up,
|
||||
// BOTH the parent form's header and the pop-up's are visible — so "first visible" would report
|
||||
// the parent's caption for the pop-up: a plausible, wrong answer.
|
||||
// Priority is therefore the one already measured for the close cross (dom/forms.mjs
|
||||
// closeCrossScript): floating window (ps<N>, highest index = topmost) → the form's own header →
|
||||
// and only then the open-windows tab, which the user can switch off in 1C settings.
|
||||
// Anchored on ids, not on the visible text, so a non-Russian locale keeps working.
|
||||
const heads = [...document.querySelectorAll('[id*="headerTopLine_title"]')]
|
||||
.filter(e => e.offsetWidth > 0 && e.offsetHeight > 0);
|
||||
const floating = heads.filter(e => /ps\\d+headerTopLine_title$/.test(e.id));
|
||||
const own = heads.filter(e => /^VW_page\\d+headerTopLine_title$/.test(e.id));
|
||||
const head = floating.pop() || own.pop() || null;
|
||||
let title = head
|
||||
? (head.getAttribute('title') || head.parentElement?.getAttribute('data-title') || null)
|
||||
: null;
|
||||
if (!title) title = activeTab;
|
||||
const result = { form: formNum, activeTab, title, openForms: meta.allForms, formCount: meta.formCount, ...formData };
|
||||
if (meta.modal) result.modal = true;
|
||||
if (openTabs.length) result.openTabs = openTabs;
|
||||
return result;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// web-test core/state v1.17 — module-level state for the web-test engine.
|
||||
// web-test core/state v1.18 — module-level state for the web-test engine.
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
//
|
||||
// Holds the single browser/page/recorder slot plus the multi-context registry,
|
||||
@@ -80,6 +80,11 @@ export const ACTION_WAIT = 2000; // fallback minimum wait
|
||||
export const MAX_WAIT = 10000; // max wait for stability
|
||||
export const POLL_INTERVAL = 200; // polling interval
|
||||
export const STABLE_CYCLES = 3; // consecutive stable cycles needed
|
||||
// Ceiling for the case where 1C is VISIBLY still working (its "Поиск…" state window is up).
|
||||
// Higher than MAX_WAIT on purpose: a search on a production-sized list legitimately runs for
|
||||
// tens of seconds, and returning mid-search hands the caller the previous rows — a wrong
|
||||
// result that looks like a right one. Bounded so a wedged operation still ends the wait.
|
||||
export const BUSY_MAX_WAIT = 60000;
|
||||
|
||||
// 1C browser extension ID (stable across versions, defined by key in manifest.json)
|
||||
export const EXT_ID = 'pbhelknnhilelbnhfpcjlcabhmfangik';
|
||||
|
||||
@@ -1,30 +1,49 @@
|
||||
// web-test core/wait v1.17 — Smart wait helpers: DOM stability polling, JS-expression polling, CDP network monitor.
|
||||
// web-test core/wait v1.18 — Smart wait helpers: DOM stability polling, JS-expression polling, CDP network monitor.
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import { page, MAX_WAIT, POLL_INTERVAL, STABLE_CYCLES } from './state.mjs';
|
||||
import { page, MAX_WAIT, BUSY_MAX_WAIT, POLL_INTERVAL, STABLE_CYCLES } from './state.mjs';
|
||||
import { detectFormScript } from '../../dom.mjs';
|
||||
|
||||
/**
|
||||
* Smart wait: poll until DOM is stable and no loading indicators are visible.
|
||||
* Checks: form number change, loading indicators, DOM stability.
|
||||
* Checks: form number change, loading indicators, busy state window, DOM stability.
|
||||
* @param {number|null} previousFormNum — form number before the action (null = don't check)
|
||||
*/
|
||||
export async function waitForStable(previousFormNum = null) {
|
||||
let stableCount = 0;
|
||||
let lastSnapshot = '';
|
||||
const start = Date.now();
|
||||
let deadline = start + MAX_WAIT;
|
||||
|
||||
while (Date.now() - start < MAX_WAIT) {
|
||||
while (Date.now() < deadline) {
|
||||
await page.waitForTimeout(POLL_INTERVAL);
|
||||
|
||||
// Check for loading indicators
|
||||
const status = await page.evaluate(`(() => {
|
||||
const loading = document.querySelector('.loadingImage, .waitCurtain, .progressBar');
|
||||
const isLoading = loading && loading.offsetWidth > 0;
|
||||
// While a dynamic list is still searching, 1C floats a state window over the grid
|
||||
// ("Поиск…") — and NOTHING else in the DOM says so: the old rows stay put, the element
|
||||
// counters below don't move, so the page looks perfectly stable with stale data.
|
||||
// Match busy markers by text, never "state window present": the same carrier also holds
|
||||
// TERMINAL report messages ("Отчет не сформирован", "Не установлено значение параметра"),
|
||||
// and waiting for those to disappear would hang until the timeout on every report.
|
||||
const busy = [...document.querySelectorAll('.stateWindowSupportSurface')].some(el =>
|
||||
el.offsetWidth > 0 && /^\\s*(Поиск|Ожид|Searching|Please wait)/i.test(el.innerText || ''));
|
||||
const formCount = document.querySelectorAll('input.editInput[id], a.press[id]').length;
|
||||
return { isLoading, formCount };
|
||||
return { isLoading, busy, formCount };
|
||||
})()`);
|
||||
|
||||
// A visible busy indicator outranks DOM stability — it is the only evidence we get that the
|
||||
// server is still working. Push the deadline while it lasts (bounded by BUSY_MAX_WAIT) instead
|
||||
// of reporting "stable": returning mid-search is what handed a caller the previous rows and
|
||||
// let the next click open the wrong document.
|
||||
if (status.busy) {
|
||||
deadline = Math.min(Date.now() + MAX_WAIT, start + BUSY_MAX_WAIT);
|
||||
stableCount = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (status.isLoading) {
|
||||
stableCount = 0;
|
||||
continue;
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
name: xdto-compile
|
||||
description: Создание пакета XDTO 1С из XML-схемы (XSD). Используй когда нужно добавить в конфигурацию пакет XDTO — под обмен, интеграцию, веб-сервис или внешний XML-формат
|
||||
argument-hint: -XsdPath <файл.xsd>|-Xsd <схема> -OutputDir <каталог-исходников> [-Name <имя>] [-Synonym <синоним>] [-Comment <текст>] [-Force]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /xdto-compile — Создание пакета XDTO из XML-схемы
|
||||
|
||||
Собирает пакет XDTO по XML-схеме: `XDTOPackages/<Имя>.xml`,
|
||||
`XDTOPackages/<Имя>/Ext/Package.bin` и регистрацию в `Configuration.xml`.
|
||||
|
||||
Вход — обычная XML-схема, писать её нужно так же, как для любого другого инструмента.
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обязательный | Описание |
|
||||
|----------|--------------|----------|
|
||||
| `XsdPath` | один из двух | Путь к файлу XML-схемы. Псевдоним — `-Path` |
|
||||
| `Xsd` | один из двух | Схема строкой, вместо `-XsdPath` |
|
||||
| `OutputDir` | да | Каталог исходников конфигурации или расширения — там, где лежит `Configuration.xml` |
|
||||
| `Name` | нет | Имя объекта метаданных. По умолчанию — из `xs:appinfo`, иначе имя файла XSD, санированное под идентификатор 1С |
|
||||
| `Synonym` | нет | Синоним (строка). По умолчанию — из `xs:appinfo`, иначе имя пакета. Для нескольких языков задавай синоним в схеме, блоком `xs:appinfo` |
|
||||
| `Comment` | нет | Комментарий. По умолчанию — из `xs:appinfo` |
|
||||
| `Force` | нет | Перезаписать существующий пакет. Без него навык откажется затирать уже собранный пакет |
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-compile.ps1" -XsdPath "<схема.xsd>" -OutputDir "<каталог-исходников>"
|
||||
```
|
||||
|
||||
Примеры:
|
||||
```powershell
|
||||
... -XsdPath bank.xsd -OutputDir src -Name ОбменСБанком -Synonym "Обмен с банком"
|
||||
... -XsdPath fss.xsd -OutputDir src -Force
|
||||
```
|
||||
|
||||
## Читай предупреждения
|
||||
|
||||
XSD выразительнее модели XDTO. Всё, что не переносится один в один, навык переносит
|
||||
приближённо и **пишет об этом**:
|
||||
|
||||
```
|
||||
Предупреждения (2) — конструкции XSD без точного соответствия в модели XDTO:
|
||||
! Документ : вложенная xs:choice уплощена в последовательность — выбор одного из вариантов не сохранён
|
||||
! Документ : кратность на вложенной частице (<xs:sequence minOccurs/maxOccurs>) не выражается в модели XDTO
|
||||
```
|
||||
|
||||
Такое сообщение означает, что пакет собран, но схема упрощена. Если упрощение
|
||||
недопустимо — меняй схему (например, разноси варианты `xs:choice` по разным типам),
|
||||
а не игнорируй.
|
||||
|
||||
Что переносится приближённо: вложенные `xs:sequence`/`xs:choice` (уплощаются в плоский
|
||||
список свойств), `xs:all` (становится последовательностью), кратность на частице,
|
||||
`substitutionGroup`, `xs:key`/`keyref`/`unique`, `xs:redefine`.
|
||||
|
||||
`xs:group` и `xs:attributeGroup` раскрываются по ссылке — их содержимое попадает в тип.
|
||||
`xs:include` игнорируется: зависимости в XDTO разрешаются только по namespace,
|
||||
поэтому включаемую схему нужно собрать отдельным пакетом и заменить `include` на `import`.
|
||||
|
||||
## Посмотреть, что получилось
|
||||
|
||||
Модель пакета лежит в `Ext/Package.bin`. Несмотря на расширение, это текстовый XML,
|
||||
но читать его напрямую обычно незачем: состав собранного пакета показывает
|
||||
`/xdto-info`, а полную схему — `/xdto-decompile`.
|
||||
|
||||
## Зависимости между пакетами
|
||||
|
||||
`<xs:import namespace="…"/>` разрешается по namespace среди пакетов конфигурации
|
||||
или расширения. Если пакета с таким пространством имён нет, платформа при загрузке
|
||||
молча подменит тип на `xs:anyType` — без ошибки. Собирай сначала зависимости, потом
|
||||
зависящий пакет, и проверяй результат через `/xdto-validate`.
|
||||
|
||||
Какие пакеты уже собраны, видно в `ChildObjects` файла `Configuration.xml`.
|
||||
|
||||
## Что XSD выразить не может
|
||||
|
||||
Две вещи модель XDTO умеет, а XML Schema — нет: `nillable` у атрибута и `qualified`
|
||||
у отдельного свойства. Они пишутся атрибутами из пространства имён модели:
|
||||
|
||||
```xml
|
||||
<xs:attribute name="Представление" type="xs:string"
|
||||
xmlns:xdto="http://v8.1c.ru/8.1/xdto" xdto:nillable="true"/>
|
||||
```
|
||||
|
||||
Схема остаётся валидной — валидаторы такие атрибуты игнорируют. Полный список
|
||||
и таблица соответствий XSD ↔ XDTO — в [xsd-reference.md](xsd-reference.md).
|
||||
|
||||
Свойства объекта метаданных можно задать прямо в схеме:
|
||||
|
||||
```xml
|
||||
<xs:annotation>
|
||||
<xs:appinfo>
|
||||
<xdto:package xmlns:xdto="http://v8.1c.ru/8.1/xdto">
|
||||
<xdto:name>ОбменСБанком</xdto:name>
|
||||
<xdto:synonym lang="ru">Обмен с банком</xdto:synonym>
|
||||
</xdto:package>
|
||||
</xs:appinfo>
|
||||
</xs:annotation>
|
||||
```
|
||||
|
||||
## Типичный workflow
|
||||
|
||||
1. Получить XSD от контрагента (или выгрузить схему существующего пакета: `/xdto-decompile`)
|
||||
2. `/xdto-compile -XsdPath <файл> -OutputDir <каталог-исходников>` — прочитать предупреждения
|
||||
3. `/xdto-validate <каталог-исходников>/XDTOPackages/<Имя>` — убедиться, что типы разрешились
|
||||
4. `/db-load-xml` + `/db-update`
|
||||
|
||||
Правка существующего пакета: точечно — `/xdto-edit`; переработать схему целиком —
|
||||
`/xdto-decompile` → правка XSD → `/xdto-compile -Force`.
|
||||
@@ -0,0 +1,957 @@
|
||||
# xdto-compile v1.1 — Build a 1C XDTO package from an XML Schema (XSD)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory=$true, ParameterSetName='File')]
|
||||
[Alias('Path')]
|
||||
[string]$XsdPath,
|
||||
[Parameter(Mandatory=$true, ParameterSetName='Inline')]
|
||||
[string]$Xsd,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$OutputDir,
|
||||
[string]$Name,
|
||||
[object]$Synonym,
|
||||
[string]$Comment,
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Эти пространства имён предоставляет сама платформа — пакетов в конфигурации
|
||||
# для них нет и быть не должно (выведено по корпусу: импортируются, но
|
||||
# targetNamespace с таким значением ни у одного пакета нет)
|
||||
$PLATFORM_NS = @(
|
||||
"http://v8.1c.ru/8.1/data/core",
|
||||
"http://v8.1c.ru/8.1/data/enterprise",
|
||||
"http://v8.1c.ru/8.1/data/enterprise/current-config",
|
||||
"http://v8.1c.ru/8.1/data-composition-system/settings",
|
||||
"http://v8.1c.ru/8.3/data/ext",
|
||||
"http://www.w3.org/2001/XMLSchema"
|
||||
)
|
||||
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$XDTO_NS = "http://v8.1c.ru/8.1/xdto"
|
||||
$XS_NS = "http://www.w3.org/2001/XMLSchema"
|
||||
$XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
$MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
||||
$V8_NS = "http://v8.1c.ru/8.1/data/core"
|
||||
|
||||
# --- Support guard (Ext/ParentConfigurations.bin) ---
|
||||
# See docs/1c-support-state-spec.md. Blocks edits of vendor objects "на замке" /
|
||||
# read-only configs unless allowed. Trigger = bin present; reaction from
|
||||
# .v8-project.json editingAllowedCheck (deny|warn|off, default deny). Never
|
||||
# throws — guard errors degrade to allow.
|
||||
function Get-RootUuid([string]$xmlPath) {
|
||||
if (-not (Test-Path $xmlPath)) { return $null }
|
||||
try {
|
||||
[xml]$mx = Get-Content -Path $xmlPath -Encoding UTF8
|
||||
$el = $mx.DocumentElement.FirstChild
|
||||
while ($el -and $el.NodeType -ne 'Element') { $el = $el.NextSibling }
|
||||
if ($el) { $u = $el.GetAttribute("uuid"); if ($u) { return $u } }
|
||||
} catch {}
|
||||
return $null
|
||||
}
|
||||
function Test-ExternalObjectRoot([string]$xmlPath) {
|
||||
if (-not (Test-Path $xmlPath)) { return $false }
|
||||
try {
|
||||
[xml]$mx = Get-Content -Path $xmlPath -Encoding UTF8
|
||||
$el = $mx.DocumentElement.FirstChild
|
||||
while ($el -and $el.NodeType -ne 'Element') { $el = $el.NextSibling }
|
||||
if ($el) { return @('ExternalDataProcessor','ExternalReport') -contains $el.get_LocalName() }
|
||||
} catch {}
|
||||
return $false
|
||||
}
|
||||
function Find-V8Project([string]$startDir) {
|
||||
$d = $startDir
|
||||
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||
$pj = Join-Path $d ".v8-project.json"
|
||||
if (Test-Path $pj) { return $pj }
|
||||
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||
if ($parent -eq $d) { break }
|
||||
$d = $parent
|
||||
}
|
||||
return $null
|
||||
}
|
||||
function Get-EditMode([string]$cfgDir) {
|
||||
$mode = "deny"
|
||||
try {
|
||||
$pj = Find-V8Project $cfgDir
|
||||
if ($pj) {
|
||||
$cfg = Get-Content -Path $pj -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
if ($cfg.PSObject.Properties.Name -contains 'editingAllowedCheck' -and $cfg.editingAllowedCheck) {
|
||||
$mode = [string]$cfg.editingAllowedCheck
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
return $mode
|
||||
}
|
||||
function Assert-EditAllowed([string]$targetPath) {
|
||||
try {
|
||||
$mode = $null
|
||||
$d = $targetPath
|
||||
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||
$cfgXml = Join-Path $d "Configuration.xml"
|
||||
$supportBin = Join-Path (Join-Path $d "Ext") "ParentConfigurations.bin"
|
||||
# Автономный объект (внешняя обработка/отчёт) — граница климба
|
||||
foreach ($x in @(Get-ChildItem -Path $d -Filter "*.xml" -File -ErrorAction SilentlyContinue)) {
|
||||
if (Test-ExternalObjectRoot $x.FullName) { return }
|
||||
}
|
||||
if (Test-Path $cfgXml) {
|
||||
if (Test-Path $supportBin) {
|
||||
$mode = Get-EditMode $d
|
||||
if ($mode -eq "off") { return }
|
||||
$msg = "Конфигурация находится на поддержке (Ext/ParentConfigurations.bin). Правка может быть запрещена."
|
||||
if ($mode -eq "warn") { Write-Warning $msg; return }
|
||||
throw "$msg Снимите с поддержки (/support-edit) или задайте editingAllowedCheck в .v8-project.json."
|
||||
}
|
||||
return
|
||||
}
|
||||
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||
if ($parent -eq $d) { break }
|
||||
$d = $parent
|
||||
}
|
||||
} catch [System.Management.Automation.RuntimeException] {
|
||||
throw
|
||||
} catch {}
|
||||
}
|
||||
|
||||
# --- Load the schema ---
|
||||
|
||||
if ($PSCmdlet.ParameterSetName -eq 'Inline') {
|
||||
$xsdText = $Xsd
|
||||
$defaultName = "Package"
|
||||
} else {
|
||||
if (-not (Test-Path $XsdPath -PathType Leaf)) { throw "Файл XSD не найден: $XsdPath" }
|
||||
$xsdText = [System.IO.File]::ReadAllText($XsdPath)
|
||||
$defaultName = [System.IO.Path]::GetFileNameWithoutExtension($XsdPath)
|
||||
}
|
||||
|
||||
$xdoc = New-Object System.Xml.XmlDocument
|
||||
$xdoc.PreserveWhitespace = $false
|
||||
try { $xdoc.LoadXml($xsdText) } catch { throw "Не удалось разобрать XSD: $($_.Exception.Message)" }
|
||||
|
||||
$schema = $xdoc.DocumentElement
|
||||
if ($schema.get_LocalName() -ne "schema" -or $schema.NamespaceURI -ne $XS_NS) {
|
||||
throw "Ожидался корневой <xs:schema> в пространстве имён $XS_NS"
|
||||
}
|
||||
|
||||
$targetNs = $schema.GetAttribute("targetNamespace")
|
||||
|
||||
# --- Emit-tree primitives -----------------------------------------------------
|
||||
# A node carries attributes in canonical order; QName values keep their namespace
|
||||
# so prefixes can be assigned per depth at serialization time (the dNpN scheme).
|
||||
|
||||
function New-Node([string]$tag) {
|
||||
return [pscustomobject]@{ Tag = $tag; Attrs = (New-Object System.Collections.ArrayList); Children = (New-Object System.Collections.ArrayList); Text = $null; Prefix = $null; DeclareNs = $null }
|
||||
}
|
||||
function Add-Attr($node, [string]$name, $value) {
|
||||
# $value НЕ типизируем: [string]$null коэрсится в "" и атрибут ложно появляется
|
||||
if ($null -eq $value) { return }
|
||||
[void]$node.Attrs.Add([pscustomobject]@{ Name = $name; Value = [string]$value; Ns = $null; Local = $null })
|
||||
}
|
||||
function Add-QAttr($node, [string]$name, $ns, $local) {
|
||||
if ($null -eq $local) { return }
|
||||
[void]$node.Attrs.Add([pscustomobject]@{ Name = $name; Value = $null; Ns = [string]$ns; Local = [string]$local })
|
||||
}
|
||||
function Add-QListAttr($node, [string]$name, $pairs, [bool]$clark) {
|
||||
if (-not $pairs -or $pairs.Count -eq 0) { return }
|
||||
[void]$node.Attrs.Add([pscustomobject]@{ Name = $name; Value = $null; Ns = $null; Local = $null; List = $pairs; Clark = $clark })
|
||||
}
|
||||
function Add-Child($node, $child) { if ($child) { [void]$node.Children.Add($child) } }
|
||||
|
||||
# Canonical attribute order per element — derived by topological sort over the
|
||||
# whole 8.3.24 corpus (acc + erp, 760 packages), see docs/1c-xdto-spec.md.
|
||||
$ATTR_ORDER = @{
|
||||
"package" = @("targetNamespace", "elementFormQualified", "attributeFormQualified")
|
||||
"import" = @("namespace")
|
||||
"objectType" = @("name", "base", "open", "abstract", "mixed", "ordered", "sequenced")
|
||||
"property" = @("name", "ref", "type", "lowerBound", "upperBound", "nillable", "fixed", "default", "form", "localName", "qualified")
|
||||
"valueType" = @("name", "base", "variety", "itemType", "length", "memberTypes", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive", "minLength", "maxLength", "totalDigits", "fractionDigits", "whiteSpace")
|
||||
"typeDef" = @("xsi:type", "base", "mixed", "open", "ordered", "sequenced", "variety", "itemType", "length", "memberTypes", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive", "minLength", "maxLength", "totalDigits", "fractionDigits", "whiteSpace")
|
||||
"enumeration" = @("xsi:type")
|
||||
}
|
||||
|
||||
function Sort-Attrs($node) {
|
||||
$order = $ATTR_ORDER[$node.Tag]
|
||||
if (-not $order) { return $node.Attrs }
|
||||
$sorted = New-Object System.Collections.ArrayList
|
||||
foreach ($n in $order) {
|
||||
foreach ($a in $node.Attrs) { if ($a.Name -eq $n) { [void]$sorted.Add($a) } }
|
||||
}
|
||||
foreach ($a in $node.Attrs) { if ($order -notcontains $a.Name) { [void]$sorted.Add($a) } }
|
||||
return $sorted
|
||||
}
|
||||
|
||||
function Esc([string]$s) {
|
||||
if ($null -eq $s) { return "" }
|
||||
return $s.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace('"', """)
|
||||
}
|
||||
function EscText([string]$s) {
|
||||
if ($null -eq $s) { return "" }
|
||||
return $s.Replace("&", "&").Replace("<", "<").Replace(">", ">")
|
||||
}
|
||||
|
||||
# --- Serializer with the dNpN prefix scheme ---
|
||||
|
||||
$out = New-Object System.Text.StringBuilder
|
||||
|
||||
function Serialize-Node($node, [int]$depth, $inherited) {
|
||||
$indent = "`t" * ($depth - 1)
|
||||
$attrs = Sort-Attrs $node
|
||||
|
||||
# Namespaces needing a NEW declaration here: те, что ещё не в области видимости.
|
||||
# Сериализатор платформы объявляет префикс на первом узле, где он нужен, а
|
||||
# потомки его переиспользуют — отсюда d2p1 у property внутри objectType.
|
||||
$localNs = New-Object System.Collections.ArrayList
|
||||
function Need-Prefix([string]$ns) {
|
||||
if (-not $ns -or $ns -eq $XS_NS -or $ns -eq $XSI_NS) { return }
|
||||
if ($inherited.ContainsKey($ns)) { return }
|
||||
if (-not $localNs.Contains($ns)) { [void]$localNs.Add($ns) }
|
||||
}
|
||||
foreach ($a in $attrs) {
|
||||
if ($a.PSObject.Properties.Name -contains 'List' -and $a.List) {
|
||||
# Нотация Кларка несёт ns в значении и префикса не требует; редкие
|
||||
# случаи, где платформа его всё же объявила, приходят зеркалом declareNs
|
||||
if (-not $a.Clark) { foreach ($p in $a.List) { Need-Prefix $p.Ns } }
|
||||
} elseif ($a.Ns) { Need-Prefix $a.Ns }
|
||||
}
|
||||
# Свойство с qualified платформа сериализует с явным префиксом пространства
|
||||
# имён XDTO — и в имени тега, и в имени самого атрибута
|
||||
$hasQualified = $false
|
||||
foreach ($a in $attrs) { if ($a.Name -eq "qualified") { $hasQualified = $true } }
|
||||
if ($hasQualified) { Need-Prefix $XDTO_NS }
|
||||
if ($node.DeclareNs) { Need-Prefix $node.DeclareNs }
|
||||
|
||||
$prefixOf = @{}
|
||||
foreach ($k in $inherited.Keys) { $prefixOf[$k] = $inherited[$k] }
|
||||
$nsDecls = ""
|
||||
for ($i = 0; $i -lt $localNs.Count; $i++) {
|
||||
# Осмысленный префикс из исходника (зеркало xdto:prefix) имеет приоритет
|
||||
$px = if ($i -eq 0 -and $node.Prefix) { $node.Prefix } else { "d${depth}p$($i + 1)" }
|
||||
$prefixOf[$localNs[$i]] = $px
|
||||
$nsDecls += " xmlns:$px=`"$(Esc $localNs[$i])`""
|
||||
}
|
||||
function QVal([string]$ns, [string]$local) {
|
||||
if (-not $ns) { return $local }
|
||||
if ($ns -eq $XS_NS) { return "xs:$local" }
|
||||
if ($ns -eq $XSI_NS) { return "xsi:$local" }
|
||||
return "$($prefixOf[$ns]):$local"
|
||||
}
|
||||
|
||||
$attrText = ""
|
||||
foreach ($a in $attrs) {
|
||||
if ($a.PSObject.Properties.Name -contains 'List' -and $a.List) {
|
||||
$vals = @()
|
||||
foreach ($p in $a.List) {
|
||||
if ($a.Clark) { $vals += $(if ($p.Ns) { "{$($p.Ns)}$($p.Local)" } else { $p.Local }) }
|
||||
else { $vals += (QVal $p.Ns $p.Local) }
|
||||
}
|
||||
$attrText += " $($a.Name)=`"$(Esc ($vals -join ' '))`""
|
||||
} elseif ($a.Ns -or $a.Local) {
|
||||
$attrText += " $($a.Name)=`"$(Esc (QVal $a.Ns $a.Local))`""
|
||||
} elseif ($a.Name -eq "qualified") {
|
||||
$attrText += " $($prefixOf[$XDTO_NS]):qualified=`"$(Esc $a.Value)`""
|
||||
} else {
|
||||
$attrText += " $($a.Name)=`"$(Esc $a.Value)`""
|
||||
}
|
||||
}
|
||||
|
||||
$tagName = $node.Tag
|
||||
if ($hasQualified) { $tagName = "$($prefixOf[$XDTO_NS]):$($node.Tag)" }
|
||||
|
||||
$hasChildren = $node.Children.Count -gt 0
|
||||
# Пустое значение пишется самозакрывающимся тегом: <enumeration/>, а не <enumeration></enumeration>
|
||||
$hasText = ($null -ne $node.Text -and $node.Text -ne "")
|
||||
|
||||
if (-not $hasChildren -and -not $hasText) {
|
||||
[void]$out.Append("$indent<$tagName$nsDecls$attrText/>`r`n")
|
||||
return
|
||||
}
|
||||
if ($hasText -and -not $hasChildren) {
|
||||
[void]$out.Append("$indent<$tagName$nsDecls$attrText>$(EscText $node.Text)</$tagName>`r`n")
|
||||
return
|
||||
}
|
||||
[void]$out.Append("$indent<$tagName$nsDecls$attrText>`r`n")
|
||||
foreach ($c in $node.Children) { Serialize-Node $c ($depth + 1) $prefixOf }
|
||||
[void]$out.Append("$indent</$tagName>`r`n")
|
||||
}
|
||||
|
||||
# --- XSD reading helpers ---
|
||||
|
||||
# Предупреждения о том, что XSD выражает, а модель XDTO — нет. Молча ронять
|
||||
# такие конструкции нельзя: пакет соберётся, а половина свойств исчезнет.
|
||||
$script:warnings = New-Object System.Collections.ArrayList
|
||||
function Warn([string]$msg) {
|
||||
if (-not $script:warnings.Contains($msg)) { [void]$script:warnings.Add($msg) }
|
||||
}
|
||||
|
||||
function XA([System.Xml.XmlElement]$el, [string]$name) {
|
||||
if ($el.HasAttribute($name)) { return $el.GetAttribute($name) }
|
||||
return $null
|
||||
}
|
||||
function MA([System.Xml.XmlElement]$el, [string]$name) {
|
||||
# xdto: mirror attribute — the literal value to write into Package.bin.
|
||||
# Префикс не фиксируем: ищем по namespace, а не по строке "xdto:".
|
||||
$a = $el.Attributes.GetNamedItem($name, $XDTO_NS)
|
||||
if ($null -eq $a) { return $null }
|
||||
return $a.Value
|
||||
}
|
||||
function XChildren([System.Xml.XmlElement]$el, [string]$local) {
|
||||
$res = New-Object System.Collections.ArrayList
|
||||
foreach ($c in $el.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.NamespaceURI -eq $XS_NS -and $c.get_LocalName() -eq $local) { [void]$res.Add($c) }
|
||||
}
|
||||
# ArrayList, а не @(): PowerShell разворачивает массив из одного элемента при return
|
||||
return ,$res
|
||||
}
|
||||
function XFirst([System.Xml.XmlElement]$el, [string]$local) {
|
||||
$r = XChildren $el $local
|
||||
if ($r.Count -gt 0) { return $r[0] }
|
||||
return $null
|
||||
}
|
||||
|
||||
# Split a QName from the XSD into (ns, local) using that element's prefix scope
|
||||
function Split-QName([System.Xml.XmlElement]$el, [string]$qname) {
|
||||
if ($null -eq $qname -or $qname -eq "") { return $null }
|
||||
$parts = $qname.Split(":")
|
||||
if ($parts.Count -eq 2) {
|
||||
$ns = $el.GetNamespaceOfPrefix($parts[0])
|
||||
$local = $parts[1]
|
||||
} else {
|
||||
# Прощающий ввод: голое имя типа трактуем как тип целевого пространства
|
||||
$ns = $el.GetNamespaceOfPrefix("")
|
||||
if (-not $ns) { $ns = $targetNs }
|
||||
$local = $parts[0]
|
||||
}
|
||||
return [pscustomobject]@{ Ns = $ns; Local = $local }
|
||||
}
|
||||
function Split-QNameList([System.Xml.XmlElement]$el, [string]$list) {
|
||||
if (-not $list) { return @() }
|
||||
$res = @()
|
||||
foreach ($q in ($list -split "\s+")) { if ($q) { $res += (Split-QName $el $q) } }
|
||||
return $res
|
||||
}
|
||||
|
||||
$FACETS = @("length", "minLength", "maxLength", "totalDigits", "fractionDigits",
|
||||
"minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "whiteSpace")
|
||||
|
||||
# --- simpleType -> valueType / typeDef(ValueType) ---
|
||||
|
||||
function Fill-SimpleType($node, [System.Xml.XmlElement]$st) {
|
||||
$restriction = XFirst $st "restriction"
|
||||
$list = XFirst $st "list"
|
||||
$union = XFirst $st "union"
|
||||
|
||||
if ($list) {
|
||||
$it = Split-QName $list (XA $list "itemType")
|
||||
$mv = MA $list "variety"
|
||||
Add-Attr $node "variety" $(if ($null -ne $mv) { $mv } else { "List" })
|
||||
if ($it) { Add-QAttr $node "itemType" $it.Ns $it.Local }
|
||||
return
|
||||
}
|
||||
if ($union) {
|
||||
$mv = MA $union "variety"
|
||||
Set-AttrValue $node "variety" $(if ($null -ne $mv) { $mv } else { "Union" })
|
||||
$members = @(Split-QNameList $union (XA $union "memberTypes"))
|
||||
# По умолчанию нотация Кларка — так записано 125 из 135 memberTypes корпуса
|
||||
$useClark = ((MA $union "memberTypesForm") -ne "prefixed")
|
||||
if ($members.Count -gt 0) { Add-QListAttr $node "memberTypes" $members $useClark }
|
||||
$node.DeclareNs = MA $union "declareNs"
|
||||
foreach ($anon in (XChildren $union "simpleType")) {
|
||||
# typeDef в контексте простого типа xsi:type не несёт (40 узлов корпуса)
|
||||
$td = New-Node "typeDef"
|
||||
Fill-SimpleType $td $anon
|
||||
Add-Child $node $td
|
||||
}
|
||||
return
|
||||
}
|
||||
if ($restriction) {
|
||||
$b = Split-QName $restriction (XA $restriction "base")
|
||||
if ($b) { Add-QAttr $node "base" $b.Ns $b.Local }
|
||||
$mv = MA $restriction "variety"
|
||||
if ($null -ne $mv) { Add-Attr $node "variety" $mv }
|
||||
# Анонимный базовый тип внутри xs:restriction — typeDef без xsi:type
|
||||
$anonBase = XFirst $restriction "simpleType"
|
||||
if ($anonBase) {
|
||||
$td = New-Node "typeDef"
|
||||
Fill-SimpleType $td $anonBase
|
||||
Add-Child $node $td
|
||||
}
|
||||
foreach ($f in $FACETS) {
|
||||
foreach ($fe in (XChildren $restriction $f)) { Add-Attr $node $f (XA $fe "value") }
|
||||
}
|
||||
foreach ($pe in (XChildren $restriction "pattern")) {
|
||||
$pn = New-Node "pattern"; $pn.Text = (XA $pe "value"); Add-Child $node $pn
|
||||
}
|
||||
foreach ($en in (XChildren $restriction "enumeration")) {
|
||||
$enode = New-Node "enumeration"
|
||||
$mt = MA $en "type"
|
||||
if ($null -ne $mt) {
|
||||
$q = Split-QName $en $mt
|
||||
Add-QAttr $enode "xsi:type" $q.Ns $q.Local
|
||||
}
|
||||
$enode.Text = (XA $en "value")
|
||||
Add-Child $node $enode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-PropKey($p) {
|
||||
foreach ($a in $p.Attrs) { if ($a.Name -eq "name") { return $a.Value } }
|
||||
foreach ($a in $p.Attrs) { if ($a.Name -eq "ref") { return "@" + $a.Local } }
|
||||
return $null
|
||||
}
|
||||
|
||||
# Восстановить исходный порядок свойств по зеркалу xdto:order
|
||||
function Reorder-Properties($node, $names) {
|
||||
$props = @($node.Children | Where-Object { $_.Tag -eq "property" })
|
||||
if ($props.Count -lt 2) { return }
|
||||
$byKey = @{}
|
||||
foreach ($p in $props) {
|
||||
$k = Get-PropKey $p
|
||||
if ($null -ne $k -and -not $byKey.ContainsKey($k)) { $byKey[$k] = $p }
|
||||
}
|
||||
$ordered = New-Object System.Collections.ArrayList
|
||||
foreach ($n in $names) {
|
||||
if ($byKey.ContainsKey($n)) { [void]$ordered.Add($byKey[$n]); $byKey.Remove($n) }
|
||||
}
|
||||
foreach ($p in $props) { if ($ordered -notcontains $p) { [void]$ordered.Add($p) } }
|
||||
$others = @($node.Children | Where-Object { $_.Tag -ne "property" })
|
||||
$node.Children.Clear()
|
||||
foreach ($p in $ordered) { [void]$node.Children.Add($p) }
|
||||
foreach ($o in $others) { [void]$node.Children.Add($o) }
|
||||
}
|
||||
|
||||
function Set-AttrValue($node, [string]$name, [string]$value) {
|
||||
foreach ($a in $node.Attrs) { if ($a.Name -eq $name) { $a.Value = $value; return } }
|
||||
Add-Attr $node $name $value
|
||||
}
|
||||
|
||||
# --- element / attribute -> property ---
|
||||
|
||||
function Build-Property([System.Xml.XmlElement]$el, [bool]$isAttribute) {
|
||||
$p = New-Node "property"
|
||||
|
||||
$xsdName = XA $el "name"
|
||||
$mirrorName = MA $el "name"
|
||||
if ($null -ne $mirrorName) {
|
||||
Add-Attr $p "name" $mirrorName
|
||||
$localName = $xsdName
|
||||
} else {
|
||||
Add-Attr $p "name" $xsdName
|
||||
$localName = $null
|
||||
}
|
||||
|
||||
$refQ = Split-QName $el (XA $el "ref")
|
||||
if ($refQ) { Add-QAttr $p "ref" $refQ.Ns $refQ.Local }
|
||||
|
||||
$typeQ = Split-QName $el (XA $el "type")
|
||||
if ($typeQ) { Add-QAttr $p "type" $typeQ.Ns $typeQ.Local }
|
||||
|
||||
if ($isAttribute) {
|
||||
Add-Attr $p "lowerBound" (MA $el "lowerBound")
|
||||
Add-Attr $p "upperBound" (MA $el "upperBound")
|
||||
Add-Attr $p "nillable" (MA $el "nillable")
|
||||
} else {
|
||||
Add-Attr $p "lowerBound" (XA $el "minOccurs")
|
||||
$maxOcc = XA $el "maxOccurs"
|
||||
if ($null -ne $maxOcc) { Add-Attr $p "upperBound" $(if ($maxOcc -eq "unbounded") { "-1" } else { $maxOcc }) }
|
||||
Add-Attr $p "nillable" (XA $el "nillable")
|
||||
}
|
||||
|
||||
# XSD-шный fixed="V" несёт значение, в модели это fixed="true" + default="V".
|
||||
# Прощающий ввод: модельная форма через зеркало xdto:fixed тоже принимается.
|
||||
$mFixed = MA $el "fixed"
|
||||
if ($null -ne $mFixed) {
|
||||
Add-Attr $p "fixed" $mFixed
|
||||
Add-Attr $p "default" (XA $el "default")
|
||||
if ($mFixed -ceq "true" -and $null -eq (XA $el "default")) {
|
||||
Warn "Свойство `"$(XA $el 'name')`": xdto:fixed=`"true`" без default — платформа отвергнет пакет («Отсутствует фиксированное значение»). Значение задаётся атрибутом default, либо пишите XSD-форму fixed=`"значение`""
|
||||
}
|
||||
} elseif ($null -ne (XA $el "fixed")) {
|
||||
Add-Attr $p "fixed" "true"
|
||||
Add-Attr $p "default" (XA $el "fixed")
|
||||
} else {
|
||||
Add-Attr $p "default" (XA $el "default")
|
||||
}
|
||||
|
||||
if ($isAttribute) {
|
||||
Add-Attr $p "form" "Attribute"
|
||||
} else {
|
||||
$mf = MA $el "form"
|
||||
if ($null -ne $mf) { Add-Attr $p "form" $mf }
|
||||
}
|
||||
Add-Attr $p "localName" $localName
|
||||
Add-Attr $p "qualified" (MA $el "qualified")
|
||||
$p.Prefix = MA $el "prefix"
|
||||
|
||||
# Anonymous inline type
|
||||
$anonSimple = XFirst $el "simpleType"
|
||||
$anonComplex = XFirst $el "complexType"
|
||||
if ($anonSimple) {
|
||||
$td = New-Node "typeDef"
|
||||
Add-Attr $td "xsi:type" "ValueType"
|
||||
Fill-SimpleType $td $anonSimple
|
||||
Add-Child $p $td
|
||||
} elseif ($anonComplex) {
|
||||
$td = New-Node "typeDef"
|
||||
Add-Attr $td "xsi:type" "ObjectType"
|
||||
Fill-ComplexType $td $anonComplex
|
||||
Add-Child $p $td
|
||||
}
|
||||
return $p
|
||||
}
|
||||
|
||||
# --- complexType -> objectType / typeDef(ObjectType) ---
|
||||
|
||||
# Разрешение xs:group / xs:attributeGroup по ссылке
|
||||
$script:GROUPS = @{}
|
||||
$script:ATTR_GROUPS = @{}
|
||||
function Resolve-Group([System.Xml.XmlElement]$el, [string]$kind) {
|
||||
$ref = XA $el "ref"
|
||||
if (-not $ref) { return $null }
|
||||
$q = Split-QName $el $ref
|
||||
if (-not $q) { return $null }
|
||||
$map = if ($kind -eq "group") { $script:GROUPS } else { $script:ATTR_GROUPS }
|
||||
if ($map.ContainsKey($q.Local)) { return $map[$q.Local] }
|
||||
return $null
|
||||
}
|
||||
|
||||
# Модель XDTO знает только плоский список свойств: вложенные частицы уплощаются.
|
||||
# Каждое уплощение — предупреждение, потому что меняется смысл схемы.
|
||||
function Collect-Particle([System.Xml.XmlElement]$particle, $elemList, [ref]$isOpen, [string]$typeName, [int]$depth, [bool]$optionalize = $false) {
|
||||
if ($depth -gt 20) { return }
|
||||
foreach ($c in $particle.ChildNodes) {
|
||||
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element -or $c.NamespaceURI -ne $XS_NS) { continue }
|
||||
switch ($c.get_LocalName()) {
|
||||
"element" {
|
||||
$prop = Build-Property $c $false
|
||||
# Ветка уплощённого xs:choice обязана стать необязательной: иначе
|
||||
# «одно из двух» превращается в «оба сразу», и тип нельзя заполнить
|
||||
if ($optionalize) { Set-AttrValue $prop "lowerBound" "0" }
|
||||
[void]$elemList.Add($prop)
|
||||
}
|
||||
"any" { $isOpen.Value = $true }
|
||||
"sequence" {
|
||||
Warn "$typeName : вложенная xs:sequence уплощена — модель XDTO хранит плоский список свойств"
|
||||
Collect-Particle $c $elemList $isOpen $typeName ($depth + 1) $optionalize
|
||||
}
|
||||
"choice" {
|
||||
$branches = @()
|
||||
foreach ($b in $c.ChildNodes) {
|
||||
if ($b.NodeType -eq [System.Xml.XmlNodeType]::Element -and $b.NamespaceURI -eq $XS_NS -and $b.HasAttribute("name")) {
|
||||
$branches += $b.GetAttribute("name")
|
||||
}
|
||||
}
|
||||
$list = if ($branches.Count -gt 0) { " (" + ($branches -join ", ") + ")" } else { "" }
|
||||
Warn ("$typeName : вложенная xs:choice уплощена — ветки$list сделаны необязательными. " +
|
||||
"Выбор одного из вариантов не сохранён: модель не запретит заполнить сразу несколько или ни одного")
|
||||
Collect-Particle $c $elemList $isOpen $typeName ($depth + 1) $true
|
||||
}
|
||||
"all" {
|
||||
Warn "$typeName : xs:all трактуется как последовательность"
|
||||
Collect-Particle $c $elemList $isOpen $typeName ($depth + 1) $optionalize
|
||||
}
|
||||
"group" {
|
||||
$g = Resolve-Group $c "group"
|
||||
if ($g) {
|
||||
foreach ($gc in $g.ChildNodes) {
|
||||
if ($gc.NodeType -eq [System.Xml.XmlNodeType]::Element -and $gc.NamespaceURI -eq $XS_NS -and
|
||||
@("sequence", "choice", "all") -contains $gc.get_LocalName()) {
|
||||
Collect-Particle $gc $elemList $isOpen $typeName ($depth + 1) $optionalize
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Warn "$typeName : не найдена группа $(XA $c 'ref') — её свойства в пакет не попали"
|
||||
}
|
||||
}
|
||||
}
|
||||
# Кратность на самой частице модель выразить не может
|
||||
if (@("sequence", "choice", "all", "group") -contains $c.get_LocalName()) {
|
||||
if ((XA $c "maxOccurs") -or (XA $c "minOccurs")) {
|
||||
Warn "$typeName : кратность на вложенной частице (<xs:$($c.get_LocalName()) minOccurs/maxOccurs>) не выражается в модели XDTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# open / ordered / sequenced / abstract / mixed: выводим где выводимо,
|
||||
# остальное приходит зеркалом xdto:
|
||||
function Set-TypeFlags($node, [System.Xml.XmlElement]$ct, $isOpen, $choice) {
|
||||
$mOpen = MA $ct "open"
|
||||
if ($null -ne $mOpen) { Add-Attr $node "open" $mOpen }
|
||||
elseif ($isOpen) { Add-Attr $node "open" "true" }
|
||||
|
||||
$mOrdered = MA $ct "ordered"
|
||||
if ($null -ne $mOrdered) { Add-Attr $node "ordered" $mOrdered }
|
||||
elseif ($choice) { Add-Attr $node "ordered" "false" }
|
||||
|
||||
$mSeq = MA $ct "sequenced"
|
||||
if ($null -ne $mSeq) { Add-Attr $node "sequenced" $mSeq }
|
||||
|
||||
$mAbstract = MA $ct "abstract"
|
||||
if ($null -ne $mAbstract) { Add-Attr $node "abstract" $mAbstract }
|
||||
elseif ((XA $ct "abstract") -eq "true") { Add-Attr $node "abstract" "true" }
|
||||
|
||||
$mMixed = MA $ct "mixed"
|
||||
if ($null -ne $mMixed) { Add-Attr $node "mixed" $mMixed }
|
||||
elseif ((XA $ct "mixed") -eq "true") { Add-Attr $node "mixed" "true" }
|
||||
}
|
||||
|
||||
function Fill-ComplexType($node, [System.Xml.XmlElement]$ct) {
|
||||
# xs:complexContent/xs:extension carries the base type
|
||||
$content = XFirst $ct "complexContent"
|
||||
$body = $ct
|
||||
if ($content) {
|
||||
$ext = XFirst $content "extension"
|
||||
if ($ext) {
|
||||
$b = Split-QName $ext (XA $ext "base")
|
||||
if ($b) { Add-QAttr $node "base" $b.Ns $b.Local }
|
||||
$body = $ext
|
||||
}
|
||||
}
|
||||
|
||||
# xs:simpleContent -> a "Text" property holding the element's own value
|
||||
$simple = XFirst $ct "simpleContent"
|
||||
if ($simple) {
|
||||
$ext = XFirst $simple "extension"
|
||||
if ($ext) {
|
||||
foreach ($a in (XChildren $ext "attribute")) { Add-Child $node (Build-Property $a $true) }
|
||||
$tp = New-Node "property"
|
||||
$tName = MA $ext "textName"
|
||||
Add-Attr $tp "name" $(if ($null -ne $tName) { $tName } else { "__content" })
|
||||
$b = Split-QName $ext (XA $ext "base")
|
||||
if ($b) { Add-QAttr $tp "type" $b.Ns $b.Local }
|
||||
Add-Attr $tp "lowerBound" (MA $ext "textlowerBound")
|
||||
Add-Attr $tp "upperBound" (MA $ext "textupperBound")
|
||||
Add-Attr $tp "nillable" (MA $ext "textnillable")
|
||||
Add-Attr $tp "form" "Text"
|
||||
Add-Child $node $tp
|
||||
# xs:simpleContent не отменяет флаги самого xs:complexType
|
||||
Set-TypeFlags $node $ct $false $null
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
# Particle: xs:sequence (ordered) or xs:choice (ordered="false")
|
||||
$seq = XFirst $body "sequence"
|
||||
$cho = XFirst $body "choice"
|
||||
$all = XFirst $body "all"
|
||||
$grp = XFirst $body "group"
|
||||
$particle = if ($seq) { $seq } elseif ($cho) { $cho } elseif ($all) { $all } else { $grp }
|
||||
$isOpen = $false
|
||||
|
||||
# Порядок в XDTO: сначала form="Attribute", потом остальные (верно для 96.5%
|
||||
# типов корпуса). Отклонения приходят зеркалом xdto:order.
|
||||
$elemProps = New-Object System.Collections.ArrayList
|
||||
$typeName = if ($ct.HasAttribute("name")) { $ct.GetAttribute("name") } else { "(анонимный тип)" }
|
||||
if ($particle) {
|
||||
$openRef = [ref]$isOpen
|
||||
if ($all) {
|
||||
Warn "$typeName : xs:all трактуется как последовательность"
|
||||
}
|
||||
if ($grp -and -not $seq -and -not $cho -and -not $all) {
|
||||
# Корневая частица задана ссылкой на группу — раскрываем её содержимое
|
||||
$g = Resolve-Group $grp "group"
|
||||
if ($g) {
|
||||
foreach ($gc in $g.ChildNodes) {
|
||||
if ($gc.NodeType -eq [System.Xml.XmlNodeType]::Element -and $gc.NamespaceURI -eq $XS_NS -and
|
||||
@("sequence", "choice", "all") -contains $gc.get_LocalName()) {
|
||||
Collect-Particle $gc $elemProps $openRef $typeName 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Warn "$typeName : не найдена группа $(XA $grp 'ref') — её свойства в пакет не попали"
|
||||
}
|
||||
} else {
|
||||
Collect-Particle $particle $elemProps $openRef $typeName 0
|
||||
}
|
||||
$isOpen = $openRef.Value
|
||||
}
|
||||
foreach ($a in (XChildren $body "attribute")) { Add-Child $node (Build-Property $a $true) }
|
||||
# xs:attributeGroup раскрываем по ссылке
|
||||
foreach ($ag in (XChildren $body "attributeGroup")) {
|
||||
$g = Resolve-Group $ag "attributeGroup"
|
||||
if ($g) {
|
||||
foreach ($a in (XChildren $g "attribute")) { Add-Child $node (Build-Property $a $true) }
|
||||
} else {
|
||||
Warn "Не найдена группа атрибутов $(XA $ag 'ref') — её атрибуты в пакет не попали"
|
||||
}
|
||||
}
|
||||
foreach ($e in $elemProps) { Add-Child $node $e }
|
||||
if ((XChildren $body "anyAttribute").Count -gt 0) { $isOpen = $true }
|
||||
|
||||
$mOrder = MA $ct "order"
|
||||
if ($null -ne $mOrder) { Reorder-Properties $node ($mOrder -split "\|") }
|
||||
|
||||
Set-TypeFlags $node $ct $isOpen $cho
|
||||
}
|
||||
|
||||
# --- Build the package tree ---
|
||||
|
||||
$pkgNode = New-Node "package"
|
||||
Add-Attr $pkgNode "targetNamespace" $targetNs
|
||||
|
||||
$efqMirror = MA $schema "elementFormQualified"
|
||||
$afqMirror = MA $schema "attributeFormQualified"
|
||||
$efd = XA $schema "elementFormDefault"
|
||||
$afd = XA $schema "attributeFormDefault"
|
||||
if ($null -ne $efqMirror) { Add-Attr $pkgNode "elementFormQualified" $efqMirror }
|
||||
elseif ($null -ne $efd) { Add-Attr $pkgNode "elementFormQualified" $(if ($efd -eq "qualified") { "true" } else { "false" }) }
|
||||
if ($null -ne $afqMirror) { Add-Attr $pkgNode "attributeFormQualified" $afqMirror }
|
||||
elseif ($null -ne $afd) { Add-Attr $pkgNode "attributeFormQualified" $(if ($afd -eq "qualified") { "true" } else { "false" }) }
|
||||
|
||||
# Metadata properties from xs:annotation/xs:appinfo
|
||||
$metaName = $null; $metaComment = $null; $metaSynonym = @()
|
||||
$ann = XFirst $schema "annotation"
|
||||
if ($ann) {
|
||||
$appinfo = XFirst $ann "appinfo"
|
||||
if ($appinfo) {
|
||||
foreach ($pk in $appinfo.ChildNodes) {
|
||||
if ($pk.NodeType -ne [System.Xml.XmlNodeType]::Element -or $pk.NamespaceURI -ne $XDTO_NS) { continue }
|
||||
foreach ($f in $pk.ChildNodes) {
|
||||
if ($f.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
|
||||
switch ($f.get_LocalName()) {
|
||||
"name" { $metaName = $f.InnerText }
|
||||
"comment" { $metaComment = $f.InnerText }
|
||||
"synonym" { $metaSynonym += @{ Lang = $f.GetAttribute("lang"); Content = $f.InnerText } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Реестр глобальных групп — нужен до обхода, чтобы раскрывать ссылки
|
||||
foreach ($node in $schema.ChildNodes) {
|
||||
if ($node.NodeType -ne [System.Xml.XmlNodeType]::Element -or $node.NamespaceURI -ne $XS_NS) { continue }
|
||||
$nm = XA $node "name"
|
||||
if ($node.get_LocalName() -eq "group" -and $nm) { $script:GROUPS[$nm] = $node }
|
||||
if ($node.get_LocalName() -eq "attributeGroup" -and $nm) { $script:ATTR_GROUPS[$nm] = $node }
|
||||
}
|
||||
|
||||
# Конструкции XSD, которым в модели XDTO нет соответствия
|
||||
foreach ($sg in $schema.SelectNodes("//*[local-name()='element'][@substitutionGroup]")) {
|
||||
Warn "Подстановочные группы (substitutionGroup) не поддерживаются моделью XDTO — объявление $($sg.GetAttribute('name')) сохранено как обычное"
|
||||
}
|
||||
foreach ($idc in @("key", "keyref", "unique")) {
|
||||
if ($schema.SelectNodes("//*[local-name()='$idc']").Count -gt 0) {
|
||||
Warn "Ограничения целостности (xs:$idc) в модели XDTO не хранятся — отброшены"
|
||||
}
|
||||
}
|
||||
if ($schema.SelectNodes("//*[local-name()='redefine']").Count -gt 0) {
|
||||
Warn "xs:redefine не поддерживается — переопределения проигнорированы"
|
||||
}
|
||||
if ($schema.SelectNodes("//*[local-name()='include']").Count -gt 0) {
|
||||
Warn "xs:include проигнорирован: модель XDTO разрешает зависимости только по namespace. Соберите включаемую схему отдельным пакетом и добавьте <xs:import>"
|
||||
}
|
||||
|
||||
foreach ($node in $schema.ChildNodes) {
|
||||
if ($node.NodeType -ne [System.Xml.XmlNodeType]::Element -or $node.NamespaceURI -ne $XS_NS) { continue }
|
||||
switch ($node.get_LocalName()) {
|
||||
"annotation" { }
|
||||
"group" { }
|
||||
"attributeGroup" { }
|
||||
"notation" { }
|
||||
"import" {
|
||||
$n = New-Node "import"
|
||||
Add-Attr $n "namespace" (XA $node "namespace")
|
||||
Add-Child $pkgNode $n
|
||||
}
|
||||
"include" { }
|
||||
"element" { Add-Child $pkgNode (Build-Property $node $false) }
|
||||
"attribute" { Add-Child $pkgNode (Build-Property $node $true) }
|
||||
"simpleType" {
|
||||
$n = New-Node "valueType"
|
||||
Add-Attr $n "name" (XA $node "name")
|
||||
Fill-SimpleType $n $node
|
||||
Add-Child $pkgNode $n
|
||||
}
|
||||
"complexType" {
|
||||
$n = New-Node "objectType"
|
||||
Add-Attr $n "name" (XA $node "name")
|
||||
Fill-ComplexType $n $node
|
||||
Add-Child $pkgNode $n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Serialize Package.bin ---
|
||||
|
||||
# Модель XDTO требует строгой последовательности элементов верхнего уровня:
|
||||
# import → property → valueType → objectType. Порядок объявлений в XSD произвольный,
|
||||
# поэтому пересортировываем — иначе платформа отвергает пакет с «Ошибка преобразования
|
||||
# данных XDTO». Все 760 пакетов корпуса этому порядку удовлетворяют, так что
|
||||
# round-trip не затрагивается.
|
||||
$TOP_ORDER = @("import", "property", "valueType", "objectType")
|
||||
$sortedChildren = New-Object System.Collections.ArrayList
|
||||
foreach ($t in $TOP_ORDER) {
|
||||
foreach ($c in $pkgNode.Children) { if ($c.Tag -eq $t) { [void]$sortedChildren.Add($c) } }
|
||||
}
|
||||
foreach ($c in $pkgNode.Children) { if ($TOP_ORDER -notcontains $c.Tag) { [void]$sortedChildren.Add($c) } }
|
||||
$pkgNode.Children.Clear()
|
||||
foreach ($c in $sortedChildren) { [void]$pkgNode.Children.Add($c) }
|
||||
|
||||
$attrsRoot = Sort-Attrs $pkgNode
|
||||
$rootAttrText = ""
|
||||
foreach ($a in $attrsRoot) { $rootAttrText += " $($a.Name)=`"$(Esc $a.Value)`"" }
|
||||
[void]$out.Append("<package xmlns=`"$XDTO_NS`" xmlns:xs=`"$XS_NS`" xmlns:xsi=`"$XSI_NS`"$rootAttrText>`r`n")
|
||||
foreach ($c in $pkgNode.Children) { Serialize-Node $c 2 @{} }
|
||||
[void]$out.Append("</package>")
|
||||
|
||||
$binText = $out.ToString()
|
||||
|
||||
# --- Resolve the package name ---
|
||||
|
||||
if (-not $Name) {
|
||||
if ($metaName) { $Name = $metaName } else { $Name = $defaultName }
|
||||
}
|
||||
# Санация под идентификатор 1С
|
||||
$Name = ($Name -replace '[^\wЀ-ӿ]', '_')
|
||||
if ($Name -match '^\d') { $Name = "_$Name" }
|
||||
|
||||
Assert-EditAllowed $OutputDir
|
||||
|
||||
$pkgRoot = Join-Path $OutputDir "XDTOPackages"
|
||||
$pkgDir = Join-Path $pkgRoot $Name
|
||||
$extDir = Join-Path $pkgDir "Ext"
|
||||
$mdFile = Join-Path $pkgRoot "$Name.xml"
|
||||
$binFile = Join-Path $extDir "Package.bin"
|
||||
|
||||
if ((Test-Path $binFile) -and -not $Force) {
|
||||
throw "Пакет уже существует: $binFile. Используйте -Force для перезаписи."
|
||||
}
|
||||
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
||||
|
||||
$encBom = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($binFile, $binText, $encBom)
|
||||
|
||||
# --- Metadata object file ---
|
||||
|
||||
if (-not $Synonym -and $metaSynonym.Count -gt 0) {
|
||||
$synItems = $metaSynonym
|
||||
} elseif ($Synonym -is [System.Collections.IDictionary]) {
|
||||
$synItems = @()
|
||||
foreach ($k in $Synonym.Keys) { $synItems += @{ Lang = [string]$k; Content = [string]$Synonym[$k] } }
|
||||
} elseif ($Synonym) {
|
||||
$synItems = @(@{ Lang = "ru"; Content = [string]$Synonym })
|
||||
} else {
|
||||
$synItems = @(@{ Lang = "ru"; Content = $Name })
|
||||
}
|
||||
if (-not $Comment -and $metaComment) { $Comment = $metaComment }
|
||||
|
||||
$uuid = [guid]::NewGuid().ToString()
|
||||
$md = New-Object System.Text.StringBuilder
|
||||
function M([string]$s) { [void]$md.Append($s); [void]$md.Append("`r`n") }
|
||||
M '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
M '<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">'
|
||||
M "`t<XDTOPackage uuid=`"$uuid`">"
|
||||
M "`t`t<Properties>"
|
||||
M "`t`t`t<Name>$(EscText $Name)</Name>"
|
||||
M "`t`t`t<Synonym>"
|
||||
foreach ($s in $synItems) {
|
||||
M "`t`t`t`t<v8:item>"
|
||||
M "`t`t`t`t`t<v8:lang>$(EscText $s.Lang)</v8:lang>"
|
||||
M "`t`t`t`t`t<v8:content>$(EscText $s.Content)</v8:content>"
|
||||
M "`t`t`t`t</v8:item>"
|
||||
}
|
||||
M "`t`t`t</Synonym>"
|
||||
if ($Comment) { M "`t`t`t<Comment>$(EscText $Comment)</Comment>" } else { M "`t`t`t<Comment/>" }
|
||||
M "`t`t`t<Namespace>$(EscText $targetNs)</Namespace>"
|
||||
M "`t`t</Properties>"
|
||||
M "`t</XDTOPackage>"
|
||||
[void]$md.Append("</MetaDataObject>")
|
||||
|
||||
[System.IO.File]::WriteAllText($mdFile, $md.ToString(), $encBom)
|
||||
|
||||
# --- Register in Configuration.xml ---
|
||||
|
||||
# Ранняя диагностика: отказ платформы при db-update дешевле поймать на сборке
|
||||
$xdtoRootDir = Join-Path $OutputDir "XDTOPackages"
|
||||
$declaredImports = @()
|
||||
foreach ($c in $pkgNode.Children) { if ($c.Tag -eq "import") { foreach ($a in $c.Attrs) { if ($a.Name -eq "namespace") { $declaredImports += $a.Value } } } }
|
||||
if ($declaredImports.Count -gt 0 -and (Test-Path $xdtoRootDir)) {
|
||||
$knownNs = @{}
|
||||
foreach ($other in (Get-ChildItem $xdtoRootDir -Directory -ErrorAction SilentlyContinue)) {
|
||||
$ob = Join-Path (Join-Path $other.FullName "Ext") "Package.bin"
|
||||
if (-not (Test-Path $ob)) { continue }
|
||||
try {
|
||||
$od = New-Object System.Xml.XmlDocument
|
||||
$od.Load($ob)
|
||||
$knownNs[$od.DocumentElement.GetAttribute("targetNamespace")] = $true
|
||||
} catch {}
|
||||
}
|
||||
foreach ($imp in $declaredImports) {
|
||||
if (-not $knownNs.ContainsKey($imp) -and $PLATFORM_NS -notcontains $imp) {
|
||||
Warn "Импорт `"$imp`" не разрешается: пакета с таким namespace в конфигурации нет. Платформа отвергнет пакет при обновлении — соберите зависимость первой"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$configXmlPath = Join-Path $OutputDir "Configuration.xml"
|
||||
$regResult = "no-config"
|
||||
if (Test-Path $configXmlPath) {
|
||||
$configDoc = New-Object System.Xml.XmlDocument
|
||||
$configDoc.PreserveWhitespace = $true
|
||||
$configDoc.Load($configXmlPath)
|
||||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($configDoc.NameTable)
|
||||
$nsMgr.AddNamespace("md", $MD_NS)
|
||||
$childObjects = $configDoc.SelectSingleNode("//md:Configuration/md:ChildObjects", $nsMgr)
|
||||
if ($childObjects) {
|
||||
$existing = $childObjects.SelectNodes("md:XDTOPackage", $nsMgr)
|
||||
$already = $false
|
||||
foreach ($e in $existing) { if ($e.InnerText -eq $Name) { $already = $true; break } }
|
||||
if ($already) {
|
||||
$regResult = "already"
|
||||
} else {
|
||||
$newElem = $configDoc.CreateElement("XDTOPackage", $MD_NS)
|
||||
$newElem.InnerText = $Name
|
||||
if ($existing.Count -gt 0) {
|
||||
$lastElem = $existing[$existing.Count - 1]
|
||||
$newWs = $configDoc.CreateWhitespace("`n`t`t`t")
|
||||
$childObjects.InsertAfter($newWs, $lastElem) | Out-Null
|
||||
$childObjects.InsertAfter($newElem, $newWs) | Out-Null
|
||||
} else {
|
||||
$lastChild = $childObjects.LastChild
|
||||
if ($lastChild -and $lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) {
|
||||
$newWs = $configDoc.CreateWhitespace("`n`t`t`t")
|
||||
$childObjects.InsertBefore($newWs, $lastChild) | Out-Null
|
||||
$childObjects.InsertBefore($newElem, $lastChild) | Out-Null
|
||||
} else {
|
||||
$childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
||||
$childObjects.AppendChild($newElem) | Out-Null
|
||||
$childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t")) | Out-Null
|
||||
}
|
||||
}
|
||||
$cfgSettings = New-Object System.Xml.XmlWriterSettings
|
||||
$cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
||||
$cfgSettings.Indent = $false
|
||||
$stream = New-Object System.IO.FileStream($configXmlPath, [System.IO.FileMode]::Create)
|
||||
$writer = [System.Xml.XmlWriter]::Create($stream, $cfgSettings)
|
||||
$configDoc.Save($writer)
|
||||
$writer.Close()
|
||||
$stream.Close()
|
||||
$regResult = "added"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Report ---
|
||||
|
||||
$typeCount = 0
|
||||
foreach ($c in $pkgNode.Children) { if ($c.Tag -eq "objectType" -or $c.Tag -eq "valueType") { $typeCount++ } }
|
||||
|
||||
Write-Host "✓ Пакет XDTO собран: $Name"
|
||||
Write-Host " Namespace: $targetNs"
|
||||
Write-Host " Типов: $typeCount"
|
||||
Write-Host " Файлы: XDTOPackages/$Name.xml, XDTOPackages/$Name/Ext/Package.bin"
|
||||
if ($script:warnings.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "Предупреждения ($($script:warnings.Count)) — конструкции XSD без точного соответствия в модели XDTO:"
|
||||
foreach ($w in $script:warnings) { Write-Host " ! $w" }
|
||||
Write-Host ""
|
||||
}
|
||||
switch ($regResult) {
|
||||
"added" { Write-Host " Configuration.xml: <XDTOPackage>$Name</XDTOPackage> добавлен в ChildObjects" }
|
||||
"already" { Write-Host " Configuration.xml: <XDTOPackage>$Name</XDTOPackage> уже зарегистрирован" }
|
||||
"no-config" { Write-Host " Configuration.xml не найден — регистрация пропущена" }
|
||||
}
|
||||
@@ -0,0 +1,962 @@
|
||||
# xdto-compile v1.1 — Build a 1C XDTO package from an XML Schema (XSD) (Python port)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
from lxml import etree
|
||||
|
||||
# Эти пространства имён предоставляет сама платформа — пакетов в конфигурации
|
||||
# для них нет и быть не должно (выведено по корпусу)
|
||||
PLATFORM_NS = {
|
||||
"http://v8.1c.ru/8.1/data/core",
|
||||
"http://v8.1c.ru/8.1/data/enterprise",
|
||||
"http://v8.1c.ru/8.1/data/enterprise/current-config",
|
||||
"http://v8.1c.ru/8.1/data-composition-system/settings",
|
||||
"http://v8.1c.ru/8.3/data/ext",
|
||||
"http://www.w3.org/2001/XMLSchema",
|
||||
}
|
||||
|
||||
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
|
||||
XDTO_NS = "http://v8.1c.ru/8.1/xdto"
|
||||
XS_NS = "http://www.w3.org/2001/XMLSchema"
|
||||
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
||||
|
||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||
parser.add_argument("-XsdPath", "-Path", default="")
|
||||
parser.add_argument("-Xsd", default="")
|
||||
parser.add_argument("-OutputDir", required=True)
|
||||
parser.add_argument("-Name", default="")
|
||||
parser.add_argument("-Synonym", default="")
|
||||
parser.add_argument("-Comment", default="")
|
||||
parser.add_argument("-Force", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def _parse_xml(source, from_string=False):
|
||||
"""Разбор с узким отступлением для не-URI пространств имён.
|
||||
|
||||
Платформа допускает в targetNamespace произвольную строку (в выгрузке БП есть
|
||||
пакет с кириллическим «ДопФайлУниверсальный»), .NET такое принимает, а libxml2
|
||||
отвергает. Откатываемся на восстанавливающий разбор ТОЛЬКО на этой ошибке,
|
||||
иначе по-настоящему битый XML перестал бы отличаться от корректного.
|
||||
"""
|
||||
try:
|
||||
return (etree.fromstring(source) if from_string else etree.parse(source))
|
||||
except etree.XMLSyntaxError as e:
|
||||
if "is not a valid URI" not in str(e):
|
||||
raise
|
||||
p = etree.XMLParser(recover=True)
|
||||
return (etree.fromstring(source, p) if from_string else etree.parse(source, p))
|
||||
|
||||
# ── support guard (Ext/ParentConfigurations.bin) ─────────────
|
||||
# См. docs/1c-support-state-spec.md. Блокирует правку объектов поставщика
|
||||
# «на замке». Триггер — наличие bin; реакция из .v8-project.json
|
||||
# editingAllowedCheck (deny|warn|off, по умолчанию deny).
|
||||
|
||||
|
||||
def find_v8_project(start_dir):
|
||||
d = os.path.abspath(start_dir)
|
||||
for _ in range(20):
|
||||
pj = os.path.join(d, ".v8-project.json")
|
||||
if os.path.exists(pj):
|
||||
return pj
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
break
|
||||
d = parent
|
||||
return None
|
||||
|
||||
|
||||
def get_edit_mode(cfg_dir):
|
||||
try:
|
||||
pj = find_v8_project(cfg_dir)
|
||||
if pj:
|
||||
with open(pj, encoding="utf-8-sig") as f:
|
||||
cfg = json.load(f)
|
||||
return str(cfg.get("editingAllowedCheck") or "deny")
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
return "deny"
|
||||
|
||||
|
||||
def is_external_object_root(xml_path):
|
||||
try:
|
||||
root = _parse_xml(xml_path).getroot()
|
||||
for el in root:
|
||||
if isinstance(el.tag, str):
|
||||
return etree.QName(el).localname in ("ExternalDataProcessor", "ExternalReport")
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def assert_edit_allowed(target_path):
|
||||
d = os.path.abspath(target_path)
|
||||
for _ in range(20):
|
||||
# Автономный объект (внешняя обработка/отчёт) — граница климба
|
||||
try:
|
||||
for f in os.listdir(d):
|
||||
if f.endswith(".xml") and is_external_object_root(os.path.join(d, f)):
|
||||
return
|
||||
except OSError:
|
||||
pass
|
||||
cfg_xml = os.path.join(d, "Configuration.xml")
|
||||
support_bin = os.path.join(d, "Ext", "ParentConfigurations.bin")
|
||||
if os.path.exists(cfg_xml):
|
||||
if os.path.exists(support_bin):
|
||||
mode = get_edit_mode(d)
|
||||
if mode == "off":
|
||||
return
|
||||
msg = ("Конфигурация находится на поддержке (Ext/ParentConfigurations.bin). "
|
||||
"Правка может быть запрещена.")
|
||||
if mode == "warn":
|
||||
print(f"WARNING: {msg}", file=sys.stderr)
|
||||
return
|
||||
print(f"{msg} Снимите с поддержки (/support-edit) или задайте "
|
||||
"editingAllowedCheck в .v8-project.json.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
break
|
||||
d = parent
|
||||
|
||||
|
||||
# ── load the schema ──────────────────────────────────────────
|
||||
|
||||
if args.Xsd:
|
||||
xsd_bytes = args.Xsd.encode("utf-8")
|
||||
default_name = "Package"
|
||||
elif args.XsdPath:
|
||||
if not os.path.isfile(args.XsdPath):
|
||||
print(f"Файл XSD не найден: {args.XsdPath}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
with open(args.XsdPath, "rb") as f:
|
||||
xsd_bytes = f.read()
|
||||
default_name = os.path.splitext(os.path.basename(args.XsdPath))[0]
|
||||
else:
|
||||
print("Укажите -XsdPath или -Xsd", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
schema = _parse_xml(xsd_bytes, from_string=True)
|
||||
except Exception as e: # noqa: BLE001
|
||||
print(f"Не удалось разобрать XSD: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def local(el):
|
||||
return etree.QName(el).localname
|
||||
|
||||
|
||||
if local(schema) != "schema" or etree.QName(schema).namespace != XS_NS:
|
||||
print(f"Ожидался корневой <xs:schema> в пространстве имён {XS_NS}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
target_ns = schema.get("targetNamespace") or ""
|
||||
|
||||
# ── emit-tree primitives ─────────────────────────────────────
|
||||
|
||||
|
||||
class Node:
|
||||
__slots__ = ("tag", "attrs", "children", "text", "prefix", "declare_ns")
|
||||
|
||||
def __init__(self, tag):
|
||||
self.tag = tag
|
||||
self.attrs = [] # список dict: name, value | (ns, local) | list
|
||||
self.children = []
|
||||
self.text = None
|
||||
self.prefix = None
|
||||
self.declare_ns = None
|
||||
|
||||
|
||||
def add_attr(node, name, value):
|
||||
if value is None:
|
||||
return
|
||||
node.attrs.append({"name": name, "value": str(value)})
|
||||
|
||||
|
||||
def add_qattr(node, name, ns, loc):
|
||||
if loc is None:
|
||||
return
|
||||
node.attrs.append({"name": name, "ns": ns, "local": loc})
|
||||
|
||||
|
||||
def add_qlist_attr(node, name, pairs, clark):
|
||||
if not pairs:
|
||||
return
|
||||
node.attrs.append({"name": name, "list": pairs, "clark": clark})
|
||||
|
||||
|
||||
# Канонический порядок атрибутов — топологическая сортировка по корпусу 8.3.24
|
||||
# (acc + erp, 760 пакетов), см. docs/1c-xdto-spec.md.
|
||||
ATTR_ORDER = {
|
||||
"package": ["targetNamespace", "elementFormQualified", "attributeFormQualified"],
|
||||
"import": ["namespace"],
|
||||
"objectType": ["name", "base", "open", "abstract", "mixed", "ordered", "sequenced"],
|
||||
"property": ["name", "ref", "type", "lowerBound", "upperBound", "nillable",
|
||||
"fixed", "default", "form", "localName", "qualified"],
|
||||
"valueType": ["name", "base", "variety", "itemType", "length", "memberTypes",
|
||||
"minExclusive", "maxExclusive", "minInclusive", "maxInclusive",
|
||||
"minLength", "maxLength", "totalDigits", "fractionDigits", "whiteSpace"],
|
||||
"typeDef": ["xsi:type", "base", "mixed", "open", "ordered", "sequenced", "variety",
|
||||
"itemType", "length", "memberTypes", "minExclusive", "maxExclusive",
|
||||
"minInclusive", "maxInclusive", "minLength", "maxLength",
|
||||
"totalDigits", "fractionDigits", "whiteSpace"],
|
||||
"enumeration": ["xsi:type"],
|
||||
}
|
||||
|
||||
|
||||
def sort_attrs(node):
|
||||
order = ATTR_ORDER.get(node.tag)
|
||||
if not order:
|
||||
return node.attrs
|
||||
res = []
|
||||
for n in order:
|
||||
res.extend(a for a in node.attrs if a["name"] == n)
|
||||
res.extend(a for a in node.attrs if a["name"] not in order)
|
||||
return res
|
||||
|
||||
|
||||
def esc(s):
|
||||
if s is None:
|
||||
return ""
|
||||
return str(s).replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
|
||||
|
||||
|
||||
def esc_text(s):
|
||||
if s is None:
|
||||
return ""
|
||||
return str(s).replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
|
||||
# ── serializer with the dNpM prefix scheme ───────────────────
|
||||
|
||||
out = []
|
||||
|
||||
|
||||
def serialize_node(node, depth, inherited):
|
||||
indent = "\t" * (depth - 1)
|
||||
attrs_sorted = sort_attrs(node)
|
||||
|
||||
# Объявляем здесь только те ns, которых ещё нет в области видимости:
|
||||
# сериализатор платформы объявляет префикс на первом нуждающемся узле,
|
||||
# а потомки переиспользуют — отсюда d2p1 у property внутри objectType.
|
||||
local_ns = []
|
||||
|
||||
def need_prefix(ns):
|
||||
if not ns or ns in (XS_NS, XSI_NS):
|
||||
return
|
||||
if ns in inherited:
|
||||
return
|
||||
if ns not in local_ns:
|
||||
local_ns.append(ns)
|
||||
|
||||
for a in attrs_sorted:
|
||||
if "list" in a:
|
||||
# Нотация Кларка несёт ns в значении и префикса не требует
|
||||
if not a["clark"]:
|
||||
for p in a["list"]:
|
||||
need_prefix(p[0])
|
||||
elif a.get("ns"):
|
||||
need_prefix(a["ns"])
|
||||
|
||||
has_qualified = any(a["name"] == "qualified" for a in attrs_sorted)
|
||||
if has_qualified:
|
||||
need_prefix(XDTO_NS)
|
||||
if node.declare_ns:
|
||||
need_prefix(node.declare_ns)
|
||||
|
||||
prefix_of = dict(inherited)
|
||||
ns_decls = ""
|
||||
for i, ns in enumerate(local_ns):
|
||||
# Осмысленный префикс из исходника (зеркало xdto:prefix) имеет приоритет
|
||||
px = node.prefix if (i == 0 and node.prefix) else f"d{depth}p{i + 1}"
|
||||
prefix_of[ns] = px
|
||||
ns_decls += f' xmlns:{px}="{esc(ns)}"'
|
||||
|
||||
def qval(ns, loc):
|
||||
if not ns:
|
||||
return loc
|
||||
if ns == XS_NS:
|
||||
return f"xs:{loc}"
|
||||
if ns == XSI_NS:
|
||||
return f"xsi:{loc}"
|
||||
return f"{prefix_of[ns]}:{loc}"
|
||||
|
||||
attr_text = ""
|
||||
for a in attrs_sorted:
|
||||
if "list" in a:
|
||||
vals = []
|
||||
for ns, loc in a["list"]:
|
||||
vals.append((f"{{{ns}}}{loc}" if ns else loc) if a["clark"] else qval(ns, loc))
|
||||
attr_text += f' {a["name"]}="{esc(" ".join(vals))}"'
|
||||
elif a.get("ns") or a.get("local"):
|
||||
attr_text += f' {a["name"]}="{esc(qval(a.get("ns"), a["local"]))}"'
|
||||
elif a["name"] == "qualified":
|
||||
attr_text += f' {prefix_of[XDTO_NS]}:qualified="{esc(a["value"])}"'
|
||||
else:
|
||||
attr_text += f' {a["name"]}="{esc(a["value"])}"'
|
||||
|
||||
tag_name = f"{prefix_of[XDTO_NS]}:{node.tag}" if has_qualified else node.tag
|
||||
|
||||
has_children = bool(node.children)
|
||||
# Пустое значение пишется самозакрывающимся тегом: <enumeration/>
|
||||
has_text = node.text is not None and node.text != ""
|
||||
|
||||
if not has_children and not has_text:
|
||||
out.append(f"{indent}<{tag_name}{ns_decls}{attr_text}/>\r\n")
|
||||
return
|
||||
if has_text and not has_children:
|
||||
out.append(f"{indent}<{tag_name}{ns_decls}{attr_text}>{esc_text(node.text)}</{tag_name}>\r\n")
|
||||
return
|
||||
out.append(f"{indent}<{tag_name}{ns_decls}{attr_text}>\r\n")
|
||||
for c in node.children:
|
||||
serialize_node(c, depth + 1, prefix_of)
|
||||
out.append(f"{indent}</{tag_name}>\r\n")
|
||||
|
||||
|
||||
# ── XSD reading helpers ──────────────────────────────────────
|
||||
|
||||
# Предупреждения о том, что XSD выражает, а модель XDTO — нет. Молча ронять
|
||||
# такие конструкции нельзя: пакет соберётся, а половина свойств исчезнет.
|
||||
warnings_list = []
|
||||
|
||||
|
||||
def warn(msg):
|
||||
if msg not in warnings_list:
|
||||
warnings_list.append(msg)
|
||||
|
||||
|
||||
GROUPS = {}
|
||||
ATTR_GROUPS = {}
|
||||
|
||||
|
||||
def MA(el, name):
|
||||
# xdto: mirror attribute — литеральное значение для Package.bin.
|
||||
# Ищем по namespace, а не по строке префикса.
|
||||
return el.get(f"{{{XDTO_NS}}}{name}")
|
||||
|
||||
|
||||
def xchildren(el, name):
|
||||
return [c for c in el if isinstance(c.tag, str)
|
||||
and etree.QName(c).namespace == XS_NS and local(c) == name]
|
||||
|
||||
|
||||
def xfirst(el, name):
|
||||
r = xchildren(el, name)
|
||||
return r[0] if r else None
|
||||
|
||||
|
||||
def split_qname(el, qname):
|
||||
if not qname:
|
||||
return None
|
||||
parts = qname.split(":")
|
||||
if len(parts) == 2:
|
||||
ns = el.nsmap.get(parts[0])
|
||||
loc = parts[1]
|
||||
else:
|
||||
# Прощающий ввод: голое имя типа — тип целевого пространства имён
|
||||
ns = el.nsmap.get(None) or target_ns
|
||||
loc = parts[0]
|
||||
return (ns, loc)
|
||||
|
||||
|
||||
def split_qname_list(el, lst):
|
||||
if not lst:
|
||||
return []
|
||||
return [split_qname(el, q) for q in lst.split() if q]
|
||||
|
||||
|
||||
FACETS = ["length", "minLength", "maxLength", "totalDigits", "fractionDigits",
|
||||
"minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "whiteSpace"]
|
||||
|
||||
|
||||
# ── simpleType -> valueType / typeDef(ValueType) ─────────────
|
||||
|
||||
def fill_simple_type(node, st):
|
||||
restriction = xfirst(st, "restriction")
|
||||
lst = xfirst(st, "list")
|
||||
union = xfirst(st, "union")
|
||||
|
||||
if lst is not None:
|
||||
it = split_qname(lst, lst.get("itemType"))
|
||||
mv = MA(lst, "variety")
|
||||
add_attr(node, "variety", mv if mv is not None else "List")
|
||||
if it:
|
||||
add_qattr(node, "itemType", it[0], it[1])
|
||||
return
|
||||
if union is not None:
|
||||
mv = MA(union, "variety")
|
||||
set_attr_value(node, "variety", mv if mv is not None else "Union")
|
||||
members = split_qname_list(union, union.get("memberTypes"))
|
||||
# По умолчанию нотация Кларка — так записано 125 из 135 memberTypes корпуса
|
||||
use_clark = MA(union, "memberTypesForm") != "prefixed"
|
||||
if members:
|
||||
add_qlist_attr(node, "memberTypes", members, use_clark)
|
||||
node.declare_ns = MA(union, "declareNs")
|
||||
for anon in xchildren(union, "simpleType"):
|
||||
# typeDef в контексте простого типа xsi:type не несёт (40 узлов корпуса)
|
||||
td = Node("typeDef")
|
||||
fill_simple_type(td, anon)
|
||||
node.children.append(td)
|
||||
return
|
||||
if restriction is not None:
|
||||
b = split_qname(restriction, restriction.get("base"))
|
||||
if b:
|
||||
add_qattr(node, "base", b[0], b[1])
|
||||
mv = MA(restriction, "variety")
|
||||
if mv is not None:
|
||||
add_attr(node, "variety", mv)
|
||||
# Анонимный базовый тип внутри xs:restriction — typeDef без xsi:type
|
||||
anon_base = xfirst(restriction, "simpleType")
|
||||
if anon_base is not None:
|
||||
td = Node("typeDef")
|
||||
fill_simple_type(td, anon_base)
|
||||
node.children.append(td)
|
||||
for f in FACETS:
|
||||
for fe in xchildren(restriction, f):
|
||||
add_attr(node, f, fe.get("value"))
|
||||
for pe in xchildren(restriction, "pattern"):
|
||||
pn = Node("pattern")
|
||||
pn.text = pe.get("value")
|
||||
node.children.append(pn)
|
||||
for en in xchildren(restriction, "enumeration"):
|
||||
enode = Node("enumeration")
|
||||
mt = MA(en, "type")
|
||||
if mt is not None:
|
||||
q = split_qname(en, mt)
|
||||
add_qattr(enode, "xsi:type", q[0], q[1])
|
||||
enode.text = en.get("value")
|
||||
node.children.append(enode)
|
||||
|
||||
|
||||
def set_attr_value(node, name, value):
|
||||
for a in node.attrs:
|
||||
if a["name"] == name:
|
||||
a["value"] = value
|
||||
return
|
||||
add_attr(node, name, value)
|
||||
|
||||
|
||||
def get_prop_key(p):
|
||||
for a in p.attrs:
|
||||
if a["name"] == "name":
|
||||
return a.get("value")
|
||||
for a in p.attrs:
|
||||
if a["name"] == "ref":
|
||||
return "@" + a["local"]
|
||||
return None
|
||||
|
||||
|
||||
def reorder_properties(node, names):
|
||||
props = [c for c in node.children if c.tag == "property"]
|
||||
if len(props) < 2:
|
||||
return
|
||||
by_key = {}
|
||||
for p in props:
|
||||
k = get_prop_key(p)
|
||||
if k is not None and k not in by_key:
|
||||
by_key[k] = p
|
||||
ordered = []
|
||||
for n in names:
|
||||
if n in by_key:
|
||||
ordered.append(by_key.pop(n))
|
||||
for p in props:
|
||||
if p not in ordered:
|
||||
ordered.append(p)
|
||||
others = [c for c in node.children if c.tag != "property"]
|
||||
node.children = ordered + others
|
||||
|
||||
|
||||
# ── element / attribute -> property ──────────────────────────
|
||||
|
||||
def build_property(el, is_attribute):
|
||||
p = Node("property")
|
||||
|
||||
xsd_name = el.get("name")
|
||||
mirror_name = MA(el, "name")
|
||||
if mirror_name is not None:
|
||||
add_attr(p, "name", mirror_name)
|
||||
local_name = xsd_name
|
||||
else:
|
||||
add_attr(p, "name", xsd_name)
|
||||
local_name = None
|
||||
|
||||
ref_q = split_qname(el, el.get("ref"))
|
||||
if ref_q:
|
||||
add_qattr(p, "ref", ref_q[0], ref_q[1])
|
||||
|
||||
type_q = split_qname(el, el.get("type"))
|
||||
if type_q:
|
||||
add_qattr(p, "type", type_q[0], type_q[1])
|
||||
|
||||
if is_attribute:
|
||||
add_attr(p, "lowerBound", MA(el, "lowerBound"))
|
||||
add_attr(p, "upperBound", MA(el, "upperBound"))
|
||||
add_attr(p, "nillable", MA(el, "nillable"))
|
||||
else:
|
||||
add_attr(p, "lowerBound", el.get("minOccurs"))
|
||||
max_occ = el.get("maxOccurs")
|
||||
if max_occ is not None:
|
||||
add_attr(p, "upperBound", "-1" if max_occ == "unbounded" else max_occ)
|
||||
add_attr(p, "nillable", el.get("nillable"))
|
||||
|
||||
# XSD-шный fixed="V" несёт значение, в модели это fixed="true" + default="V".
|
||||
# Прощающий ввод: модельная форма через зеркало xdto:fixed тоже принимается.
|
||||
m_fixed = MA(el, "fixed")
|
||||
if m_fixed is not None:
|
||||
add_attr(p, "fixed", m_fixed)
|
||||
add_attr(p, "default", el.get("default"))
|
||||
if m_fixed == "true" and el.get("default") is None:
|
||||
warn('Свойство "' + str(el.get("name")) + '": xdto:fixed="true" без default — '
|
||||
"платформа отвергнет пакет («Отсутствует фиксированное значение»). "
|
||||
'Значение задаётся атрибутом default, либо пишите XSD-форму fixed="значение"')
|
||||
elif el.get("fixed") is not None:
|
||||
add_attr(p, "fixed", "true")
|
||||
add_attr(p, "default", el.get("fixed"))
|
||||
else:
|
||||
add_attr(p, "default", el.get("default"))
|
||||
|
||||
if is_attribute:
|
||||
add_attr(p, "form", "Attribute")
|
||||
else:
|
||||
mf = MA(el, "form")
|
||||
if mf is not None:
|
||||
add_attr(p, "form", mf)
|
||||
add_attr(p, "localName", local_name)
|
||||
add_attr(p, "qualified", MA(el, "qualified"))
|
||||
p.prefix = MA(el, "prefix")
|
||||
|
||||
anon_simple = xfirst(el, "simpleType")
|
||||
anon_complex = xfirst(el, "complexType")
|
||||
if anon_simple is not None:
|
||||
td = Node("typeDef")
|
||||
add_attr(td, "xsi:type", "ValueType")
|
||||
fill_simple_type(td, anon_simple)
|
||||
p.children.append(td)
|
||||
elif anon_complex is not None:
|
||||
td = Node("typeDef")
|
||||
add_attr(td, "xsi:type", "ObjectType")
|
||||
fill_complex_type(td, anon_complex)
|
||||
p.children.append(td)
|
||||
return p
|
||||
|
||||
|
||||
# ── complexType -> objectType / typeDef(ObjectType) ──────────
|
||||
|
||||
def resolve_group(el, kind):
|
||||
ref = el.get("ref")
|
||||
if not ref:
|
||||
return None
|
||||
q = split_qname(el, ref)
|
||||
if not q:
|
||||
return None
|
||||
m = GROUPS if kind == "group" else ATTR_GROUPS
|
||||
return m.get(q[1])
|
||||
|
||||
|
||||
# Модель XDTO знает только плоский список свойств: вложенные частицы уплощаются.
|
||||
# Каждое уплощение — предупреждение, потому что меняется смысл схемы.
|
||||
def collect_particle(particle, elem_list, open_flag, type_name, depth, optionalize=False):
|
||||
if depth > 20:
|
||||
return
|
||||
for c in particle:
|
||||
if not isinstance(c.tag, str) or etree.QName(c).namespace != XS_NS:
|
||||
continue
|
||||
ln = local(c)
|
||||
if ln == "element":
|
||||
prop = build_property(c, False)
|
||||
# Ветка уплощённого xs:choice обязана стать необязательной: иначе
|
||||
# «одно из двух» превращается в «оба сразу», и тип нельзя заполнить
|
||||
if optionalize:
|
||||
set_attr_value(prop, "lowerBound", "0")
|
||||
elem_list.append(prop)
|
||||
elif ln == "any":
|
||||
open_flag[0] = True
|
||||
elif ln == "sequence":
|
||||
warn(type_name + " : вложенная xs:sequence уплощена — модель XDTO хранит плоский список свойств")
|
||||
collect_particle(c, elem_list, open_flag, type_name, depth + 1, optionalize)
|
||||
elif ln == "choice":
|
||||
branches = [b.get("name") for b in c
|
||||
if isinstance(b.tag, str) and etree.QName(b).namespace == XS_NS and b.get("name")]
|
||||
lst = (" (" + ", ".join(branches) + ")") if branches else ""
|
||||
warn(type_name + " : вложенная xs:choice уплощена — ветки" + lst + " сделаны необязательными. "
|
||||
"Выбор одного из вариантов не сохранён: модель не запретит заполнить "
|
||||
"сразу несколько или ни одного")
|
||||
collect_particle(c, elem_list, open_flag, type_name, depth + 1, True)
|
||||
elif ln == "all":
|
||||
warn(type_name + " : xs:all трактуется как последовательность")
|
||||
collect_particle(c, elem_list, open_flag, type_name, depth + 1, optionalize)
|
||||
elif ln == "group":
|
||||
g = resolve_group(c, "group")
|
||||
if g is not None:
|
||||
for gc in g:
|
||||
if isinstance(gc.tag, str) and etree.QName(gc).namespace == XS_NS \
|
||||
and local(gc) in ("sequence", "choice", "all"):
|
||||
collect_particle(gc, elem_list, open_flag, type_name, depth + 1, optionalize)
|
||||
else:
|
||||
warn(type_name + " : не найдена группа " + str(c.get("ref")) + " — её свойства в пакет не попали")
|
||||
if ln in ("sequence", "choice", "all", "group"):
|
||||
if c.get("maxOccurs") is not None or c.get("minOccurs") is not None:
|
||||
warn(type_name + " : кратность на вложенной частице (<xs:" + ln +
|
||||
" minOccurs/maxOccurs>) не выражается в модели XDTO")
|
||||
|
||||
|
||||
def set_type_flags(node, ct, is_open, choice):
|
||||
m_open = MA(ct, "open")
|
||||
if m_open is not None:
|
||||
add_attr(node, "open", m_open)
|
||||
elif is_open:
|
||||
add_attr(node, "open", "true")
|
||||
|
||||
m_ordered = MA(ct, "ordered")
|
||||
if m_ordered is not None:
|
||||
add_attr(node, "ordered", m_ordered)
|
||||
elif choice is not None:
|
||||
add_attr(node, "ordered", "false")
|
||||
|
||||
m_seq = MA(ct, "sequenced")
|
||||
if m_seq is not None:
|
||||
add_attr(node, "sequenced", m_seq)
|
||||
|
||||
m_abstract = MA(ct, "abstract")
|
||||
if m_abstract is not None:
|
||||
add_attr(node, "abstract", m_abstract)
|
||||
elif ct.get("abstract") == "true":
|
||||
add_attr(node, "abstract", "true")
|
||||
|
||||
m_mixed = MA(ct, "mixed")
|
||||
if m_mixed is not None:
|
||||
add_attr(node, "mixed", m_mixed)
|
||||
elif ct.get("mixed") == "true":
|
||||
add_attr(node, "mixed", "true")
|
||||
|
||||
|
||||
def fill_complex_type(node, ct):
|
||||
body = ct
|
||||
content = xfirst(ct, "complexContent")
|
||||
if content is not None:
|
||||
ext = xfirst(content, "extension")
|
||||
if ext is not None:
|
||||
b = split_qname(ext, ext.get("base"))
|
||||
if b:
|
||||
add_qattr(node, "base", b[0], b[1])
|
||||
body = ext
|
||||
|
||||
# xs:simpleContent -> свойство "Text", хранящее значение самого элемента
|
||||
simple = xfirst(ct, "simpleContent")
|
||||
if simple is not None:
|
||||
ext = xfirst(simple, "extension")
|
||||
if ext is not None:
|
||||
for a in xchildren(ext, "attribute"):
|
||||
node.children.append(build_property(a, True))
|
||||
tp = Node("property")
|
||||
t_name = MA(ext, "textName")
|
||||
add_attr(tp, "name", t_name if t_name is not None else "__content")
|
||||
b = split_qname(ext, ext.get("base"))
|
||||
if b:
|
||||
add_qattr(tp, "type", b[0], b[1])
|
||||
add_attr(tp, "lowerBound", MA(ext, "textlowerBound"))
|
||||
add_attr(tp, "upperBound", MA(ext, "textupperBound"))
|
||||
add_attr(tp, "nillable", MA(ext, "textnillable"))
|
||||
add_attr(tp, "form", "Text")
|
||||
node.children.append(tp)
|
||||
# xs:simpleContent не отменяет флаги самого xs:complexType
|
||||
set_type_flags(node, ct, False, None)
|
||||
return
|
||||
|
||||
seq = xfirst(body, "sequence")
|
||||
cho = xfirst(body, "choice")
|
||||
all_ = xfirst(body, "all")
|
||||
grp = xfirst(body, "group")
|
||||
particle = seq if seq is not None else (cho if cho is not None else (all_ if all_ is not None else grp))
|
||||
open_flag = [False]
|
||||
|
||||
# Порядок в XDTO: сначала form="Attribute", потом остальные (96.5% типов корпуса)
|
||||
elem_props = []
|
||||
type_name = ct.get("name") or "(анонимный тип)"
|
||||
if particle is not None:
|
||||
if all_ is not None:
|
||||
warn(type_name + " : xs:all трактуется как последовательность")
|
||||
if grp is not None and seq is None and cho is None and all_ is None:
|
||||
# Корневая частица задана ссылкой на группу — раскрываем её содержимое
|
||||
g = resolve_group(grp, "group")
|
||||
if g is not None:
|
||||
for gc in g:
|
||||
if isinstance(gc.tag, str) and etree.QName(gc).namespace == XS_NS \
|
||||
and local(gc) in ("sequence", "choice", "all"):
|
||||
collect_particle(gc, elem_props, open_flag, type_name, 1)
|
||||
else:
|
||||
warn(type_name + " : не найдена группа " + str(grp.get("ref")) + " — её свойства в пакет не попали")
|
||||
else:
|
||||
collect_particle(particle, elem_props, open_flag, type_name, 0)
|
||||
is_open = open_flag[0]
|
||||
for a in xchildren(body, "attribute"):
|
||||
node.children.append(build_property(a, True))
|
||||
# xs:attributeGroup раскрываем по ссылке
|
||||
for ag in xchildren(body, "attributeGroup"):
|
||||
g = resolve_group(ag, "attributeGroup")
|
||||
if g is not None:
|
||||
for a in xchildren(g, "attribute"):
|
||||
node.children.append(build_property(a, True))
|
||||
else:
|
||||
warn("Не найдена группа атрибутов " + str(ag.get("ref")) + " — её атрибуты в пакет не попали")
|
||||
node.children.extend(elem_props)
|
||||
if xchildren(body, "anyAttribute"):
|
||||
is_open = True
|
||||
|
||||
m_order = MA(ct, "order")
|
||||
if m_order is not None:
|
||||
reorder_properties(node, m_order.split("|"))
|
||||
|
||||
set_type_flags(node, ct, is_open, cho)
|
||||
|
||||
|
||||
# ── build the package tree ───────────────────────────────────
|
||||
|
||||
pkg_node = Node("package")
|
||||
add_attr(pkg_node, "targetNamespace", target_ns)
|
||||
|
||||
efq_mirror = MA(schema, "elementFormQualified")
|
||||
afq_mirror = MA(schema, "attributeFormQualified")
|
||||
efd = schema.get("elementFormDefault")
|
||||
afd = schema.get("attributeFormDefault")
|
||||
if efq_mirror is not None:
|
||||
add_attr(pkg_node, "elementFormQualified", efq_mirror)
|
||||
elif efd is not None:
|
||||
add_attr(pkg_node, "elementFormQualified", "true" if efd == "qualified" else "false")
|
||||
if afq_mirror is not None:
|
||||
add_attr(pkg_node, "attributeFormQualified", afq_mirror)
|
||||
elif afd is not None:
|
||||
add_attr(pkg_node, "attributeFormQualified", "true" if afd == "qualified" else "false")
|
||||
|
||||
meta_name = meta_comment = None
|
||||
meta_synonym = []
|
||||
ann = xfirst(schema, "annotation")
|
||||
if ann is not None:
|
||||
appinfo = xfirst(ann, "appinfo")
|
||||
if appinfo is not None:
|
||||
for pk in appinfo:
|
||||
if not isinstance(pk.tag, str) or etree.QName(pk).namespace != XDTO_NS:
|
||||
continue
|
||||
for f in pk:
|
||||
if not isinstance(f.tag, str):
|
||||
continue
|
||||
ln = local(f)
|
||||
if ln == "name":
|
||||
meta_name = f.text or ""
|
||||
elif ln == "comment":
|
||||
meta_comment = f.text or ""
|
||||
elif ln == "synonym":
|
||||
meta_synonym.append({"Lang": f.get("lang") or "", "Content": f.text or ""})
|
||||
|
||||
# Реестр глобальных групп — нужен до обхода, чтобы раскрывать ссылки
|
||||
for node in schema:
|
||||
if not isinstance(node.tag, str) or etree.QName(node).namespace != XS_NS:
|
||||
continue
|
||||
nm = node.get("name")
|
||||
if local(node) == "group" and nm:
|
||||
GROUPS[nm] = node
|
||||
if local(node) == "attributeGroup" and nm:
|
||||
ATTR_GROUPS[nm] = node
|
||||
|
||||
# Конструкции XSD, которым в модели XDTO нет соответствия
|
||||
for sg in schema.iter():
|
||||
if isinstance(sg.tag, str) and local(sg) == "element" and sg.get("substitutionGroup"):
|
||||
warn("Подстановочные группы (substitutionGroup) не поддерживаются моделью XDTO — объявление "
|
||||
+ str(sg.get("name")) + " сохранено как обычное")
|
||||
for idc in ("key", "keyref", "unique"):
|
||||
if any(isinstance(e.tag, str) and local(e) == idc for e in schema.iter()):
|
||||
warn("Ограничения целостности (xs:" + idc + ") в модели XDTO не хранятся — отброшены")
|
||||
if any(isinstance(e.tag, str) and local(e) == "redefine" for e in schema.iter()):
|
||||
warn("xs:redefine не поддерживается — переопределения проигнорированы")
|
||||
if any(isinstance(e.tag, str) and local(e) == "include" for e in schema.iter()):
|
||||
warn("xs:include проигнорирован: модель XDTO разрешает зависимости только по namespace. "
|
||||
"Соберите включаемую схему отдельным пакетом и добавьте <xs:import>")
|
||||
|
||||
for node in schema:
|
||||
if not isinstance(node.tag, str) or etree.QName(node).namespace != XS_NS:
|
||||
continue
|
||||
ln = local(node)
|
||||
if ln == "import":
|
||||
n = Node("import")
|
||||
add_attr(n, "namespace", node.get("namespace"))
|
||||
pkg_node.children.append(n)
|
||||
elif ln in ("annotation", "include", "group", "attributeGroup", "notation"):
|
||||
continue
|
||||
elif ln == "element":
|
||||
pkg_node.children.append(build_property(node, False))
|
||||
elif ln == "attribute":
|
||||
pkg_node.children.append(build_property(node, True))
|
||||
elif ln == "simpleType":
|
||||
n = Node("valueType")
|
||||
add_attr(n, "name", node.get("name"))
|
||||
fill_simple_type(n, node)
|
||||
pkg_node.children.append(n)
|
||||
elif ln == "complexType":
|
||||
n = Node("objectType")
|
||||
add_attr(n, "name", node.get("name"))
|
||||
fill_complex_type(n, node)
|
||||
pkg_node.children.append(n)
|
||||
|
||||
# ── serialize Package.bin ────────────────────────────────────
|
||||
|
||||
# Модель XDTO требует строгой последовательности элементов верхнего уровня:
|
||||
# import → property → valueType → objectType. Порядок объявлений в XSD произвольный,
|
||||
# поэтому пересортировываем — иначе платформа отвергает пакет с «Ошибка преобразования
|
||||
# данных XDTO». Все 760 пакетов корпуса этому порядку удовлетворяют.
|
||||
TOP_ORDER = ["import", "property", "valueType", "objectType"]
|
||||
pkg_node.children = (
|
||||
[c for t in TOP_ORDER for c in pkg_node.children if c.tag == t]
|
||||
+ [c for c in pkg_node.children if c.tag not in TOP_ORDER]
|
||||
)
|
||||
|
||||
root_attr_text = "".join(f' {a["name"]}="{esc(a["value"])}"' for a in sort_attrs(pkg_node))
|
||||
out.append(f'<package xmlns="{XDTO_NS}" xmlns:xs="{XS_NS}" xmlns:xsi="{XSI_NS}"{root_attr_text}>\r\n')
|
||||
for c in pkg_node.children:
|
||||
serialize_node(c, 2, {})
|
||||
out.append("</package>")
|
||||
|
||||
bin_text = "".join(out)
|
||||
|
||||
# ── resolve the package name ─────────────────────────────────
|
||||
|
||||
name = args.Name or meta_name or default_name
|
||||
name = re.sub(r"[^\wЀ-ӿ]", "_", name, flags=re.UNICODE)
|
||||
if re.match(r"^\d", name):
|
||||
name = "_" + name
|
||||
|
||||
assert_edit_allowed(args.OutputDir)
|
||||
|
||||
pkg_root = os.path.join(args.OutputDir, "XDTOPackages")
|
||||
pkg_dir = os.path.join(pkg_root, name)
|
||||
ext_dir = os.path.join(pkg_dir, "Ext")
|
||||
md_file = os.path.join(pkg_root, name + ".xml")
|
||||
bin_file = os.path.join(ext_dir, "Package.bin")
|
||||
|
||||
if os.path.exists(bin_file) and not args.Force:
|
||||
print(f"Пакет уже существует: {bin_file}. Используйте -Force для перезаписи.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
os.makedirs(ext_dir, exist_ok=True)
|
||||
|
||||
with open(bin_file, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf" + bin_text.encode("utf-8"))
|
||||
|
||||
# ── metadata object file ─────────────────────────────────────
|
||||
|
||||
if not args.Synonym and meta_synonym:
|
||||
syn_items = meta_synonym
|
||||
elif args.Synonym:
|
||||
syn_items = [{"Lang": "ru", "Content": args.Synonym}]
|
||||
else:
|
||||
syn_items = [{"Lang": "ru", "Content": name}]
|
||||
comment = args.Comment or meta_comment or ""
|
||||
|
||||
md_lines = [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" '
|
||||
'xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" '
|
||||
'xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" '
|
||||
'xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" '
|
||||
'xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" '
|
||||
'xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" '
|
||||
'xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" '
|
||||
'xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" '
|
||||
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">',
|
||||
f'\t<XDTOPackage uuid="{uuid.uuid4()}">',
|
||||
"\t\t<Properties>",
|
||||
f"\t\t\t<Name>{esc_text(name)}</Name>",
|
||||
"\t\t\t<Synonym>",
|
||||
]
|
||||
for s in syn_items:
|
||||
md_lines += [
|
||||
"\t\t\t\t<v8:item>",
|
||||
f'\t\t\t\t\t<v8:lang>{esc_text(s["Lang"])}</v8:lang>',
|
||||
f'\t\t\t\t\t<v8:content>{esc_text(s["Content"])}</v8:content>',
|
||||
"\t\t\t\t</v8:item>",
|
||||
]
|
||||
md_lines.append("\t\t\t</Synonym>")
|
||||
md_lines.append(f"\t\t\t<Comment>{esc_text(comment)}</Comment>" if comment else "\t\t\t<Comment/>")
|
||||
md_lines.append(f"\t\t\t<Namespace>{esc_text(target_ns)}</Namespace>")
|
||||
md_lines += ["\t\t</Properties>", "\t</XDTOPackage>", "</MetaDataObject>"]
|
||||
|
||||
with open(md_file, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf" + "\r\n".join(md_lines).encode("utf-8"))
|
||||
|
||||
# ── register in Configuration.xml ────────────────────────────
|
||||
|
||||
# Ранняя диагностика: отказ платформы при db-update дешевле поймать на сборке
|
||||
xdto_root_dir = os.path.join(args.OutputDir, "XDTOPackages")
|
||||
declared_imports = [a["value"] for c in pkg_node.children if c.tag == "import"
|
||||
for a in c.attrs if a["name"] == "namespace"]
|
||||
if declared_imports and os.path.isdir(xdto_root_dir):
|
||||
known_ns = set()
|
||||
for other in sorted(os.listdir(xdto_root_dir)):
|
||||
ob = os.path.join(xdto_root_dir, other, "Ext", "Package.bin")
|
||||
if not os.path.exists(ob):
|
||||
continue
|
||||
try:
|
||||
known_ns.add(_parse_xml(ob).getroot().get("targetNamespace"))
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
for imp in declared_imports:
|
||||
if imp not in known_ns and imp not in PLATFORM_NS:
|
||||
warn(f'Импорт "{imp}" не разрешается: пакета с таким namespace в конфигурации нет. '
|
||||
"Платформа отвергнет пакет при обновлении — соберите зависимость первой")
|
||||
|
||||
config_xml = os.path.join(args.OutputDir, "Configuration.xml")
|
||||
reg_result = "no-config"
|
||||
if os.path.exists(config_xml):
|
||||
with open(config_xml, "rb") as f:
|
||||
raw = f.read()
|
||||
had_bom = raw.startswith(b"\xef\xbb\xbf")
|
||||
cfg_doc = _parse_xml(config_xml)
|
||||
child_objects = cfg_doc.find(f".//{{{MD_NS}}}Configuration/{{{MD_NS}}}ChildObjects")
|
||||
if child_objects is not None:
|
||||
existing = child_objects.findall(f"{{{MD_NS}}}XDTOPackage")
|
||||
if any((e.text or "") == name for e in existing):
|
||||
reg_result = "already"
|
||||
else:
|
||||
new_elem = etree.SubElement(child_objects, f"{{{MD_NS}}}XDTOPackage")
|
||||
new_elem.text = name
|
||||
if existing:
|
||||
last = existing[-1]
|
||||
new_elem.tail = last.tail
|
||||
child_objects.remove(new_elem)
|
||||
last.addnext(new_elem)
|
||||
else:
|
||||
new_elem.tail = child_objects.text
|
||||
data = etree.tostring(cfg_doc, xml_declaration=True, encoding="UTF-8")
|
||||
if had_bom:
|
||||
data = b"\xef\xbb\xbf" + data
|
||||
with open(config_xml, "wb") as f:
|
||||
f.write(data)
|
||||
reg_result = "added"
|
||||
|
||||
type_count = sum(1 for c in pkg_node.children if c.tag in ("objectType", "valueType"))
|
||||
print(f"✓ Пакет XDTO собран: {name}")
|
||||
print(f" Namespace: {target_ns}")
|
||||
print(f" Типов: {type_count}")
|
||||
print(f" Файлы: XDTOPackages/{name}.xml, XDTOPackages/{name}/Ext/Package.bin")
|
||||
if warnings_list:
|
||||
print("")
|
||||
print("Предупреждения (" + str(len(warnings_list)) +
|
||||
") — конструкции XSD без точного соответствия в модели XDTO:")
|
||||
for w in warnings_list:
|
||||
print(" ! " + w)
|
||||
print("")
|
||||
if reg_result == "added":
|
||||
print(f" Configuration.xml: <XDTOPackage>{name}</XDTOPackage> добавлен в ChildObjects")
|
||||
elif reg_result == "already":
|
||||
print(f" Configuration.xml: <XDTOPackage>{name}</XDTOPackage> уже зарегистрирован")
|
||||
else:
|
||||
print(" Configuration.xml не найден — регистрация пропущена")
|
||||
@@ -0,0 +1,88 @@
|
||||
# XSD ↔ XDTO — справочник
|
||||
|
||||
## Соответствия
|
||||
|
||||
| XML Schema | Модель XDTO |
|
||||
|---|---|
|
||||
| `xs:schema/@targetNamespace` | пространство имён пакета |
|
||||
| `elementFormDefault` / `attributeFormDefault` | `elementFormQualified` / `attributeFormQualified` |
|
||||
| `xs:import/@namespace` | зависимость от другого пакета (разрешается по namespace) |
|
||||
| `xs:complexType` | объектный тип |
|
||||
| `xs:simpleType` | тип значения |
|
||||
| `xs:element` / `xs:attribute` на верхнем уровне | глобальное свойство пакета |
|
||||
| `xs:element` / `xs:attribute` внутри типа | свойство типа |
|
||||
| `@minOccurs` / `@maxOccurs="unbounded"` | `lowerBound` / `upperBound="-1"` |
|
||||
| `@nillable`, `@default`, `@fixed`, `@ref` | те же по смыслу |
|
||||
| анонимный `xs:simpleType`/`xs:complexType` в объявлении | встроенный тип свойства |
|
||||
| `xs:complexContent/xs:extension/@base` | наследование типа |
|
||||
| `@abstract`, `@mixed` | те же |
|
||||
| `xs:choice` | тип-выбор одного из вариантов |
|
||||
| `xs:any` + `xs:anyAttribute` | открытый тип |
|
||||
| `xs:simpleContent/xs:extension/@base` | свойство собственного значения элемента |
|
||||
| `xs:restriction` + фасеты | базовый тип + ограничения |
|
||||
| `xs:pattern`, `xs:enumeration` | те же |
|
||||
| `xs:list/@itemType` | список |
|
||||
| `xs:union/@memberTypes` | объединение |
|
||||
|
||||
Порядок объявлений верхнего уровня в XSD произвольный — навык сам расставит их
|
||||
в порядке, который требует модель.
|
||||
|
||||
## Аннотации `xdto:`
|
||||
|
||||
Пространство имён — `http://v8.1c.ru/8.1/xdto`. Нужны только там, где XML Schema
|
||||
не может выразить то, что умеет модель. В большинстве схем не нужны вовсе.
|
||||
|
||||
Правило: **чего XSD сказать не может — пиши атрибутом `xdto:` с тем же именем,
|
||||
что и в модели**.
|
||||
|
||||
| Аннотация | Где | Назначение |
|
||||
|---|---|---|
|
||||
| `xdto:nillable` | `xs:attribute` | `nillable` у свойства-атрибута (XSD допускает только у элементов) |
|
||||
| `xdto:lowerBound`, `xdto:upperBound` | `xs:attribute` | кратность свойства-атрибута |
|
||||
| `xdto:qualified` | объявление | переопределение `*FormQualified` для одного свойства |
|
||||
| `xdto:name` | объявление | имя свойства, если XML-имя не годится как идентификатор 1С; XML-имя уйдёт в `localName` |
|
||||
| `xdto:form` | `xs:element` | записать `form` явно |
|
||||
| `xdto:variety` | `xs:restriction`, `xs:list`, `xs:union` | записать разновидность типа явно |
|
||||
| `xdto:open`, `xdto:abstract`, `xdto:mixed`, `xdto:ordered`, `xdto:sequenced` | `xs:complexType` | флаги типа, не выводимые из модели содержимого |
|
||||
| `xdto:order` | `xs:complexType` | исходный порядок свойств, если он не «атрибуты первыми»; имена через `\|` |
|
||||
| `xdto:textName`, `xdto:textlowerBound`, `xdto:textupperBound`, `xdto:textnillable` | `xs:extension` в `xs:simpleContent` | параметры свойства собственного значения |
|
||||
| `xdto:type` | `xs:enumeration` | тип литерала перечисления |
|
||||
| `xdto:prefix` | объявление | осмысленный префикс пространства имён вместо генерируемого |
|
||||
| `xdto:memberTypesForm="prefixed"` | `xs:union` | записать состав объединения префиксами, а не `{ns}имя` |
|
||||
| `xdto:declareNs` | `xs:union` | объявить префикс пространства имён на узле |
|
||||
| `xdto:elementFormQualified`, `xdto:attributeFormQualified` | `xs:schema` | записать флаги явно |
|
||||
|
||||
Пример:
|
||||
|
||||
```xml
|
||||
<xs:complexType name="КонтактнаяИнформация" xdto:sequenced="true">
|
||||
<xs:sequence>
|
||||
<xs:element name="Комментарий" type="xs:string" minOccurs="0"/>
|
||||
<xs:element name="Адрес по документу" type="xs:string"
|
||||
xdto:name="Адрес_по_документу" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="Представление" type="xs:string"
|
||||
xdto:nillable="true" xdto:lowerBound="0"/>
|
||||
</xs:complexType>
|
||||
```
|
||||
|
||||
Аннотации, которые проставляет `/xdto-decompile` при выгрузке существующего пакета,
|
||||
писать вручную не нужно — они нужны, чтобы обратная сборка вернула ровно тот же файл.
|
||||
|
||||
## Свойства объекта метаданных
|
||||
|
||||
```xml
|
||||
<xs:annotation>
|
||||
<xs:appinfo>
|
||||
<xdto:package xmlns:xdto="http://v8.1c.ru/8.1/xdto">
|
||||
<xdto:name>ОбменСБанком</xdto:name>
|
||||
<xdto:synonym lang="ru">Обмен с банком</xdto:synonym>
|
||||
<xdto:synonym lang="en">Bank exchange</xdto:synonym>
|
||||
<xdto:comment>Формат 1С:Предприятие — Клиент банка</xdto:comment>
|
||||
</xdto:package>
|
||||
</xs:appinfo>
|
||||
</xs:annotation>
|
||||
```
|
||||
|
||||
Пространство имён пакета берётся из `targetNamespace` и здесь не дублируется.
|
||||
Параметры `-Name`, `-Synonym`, `-Comment` имеют приоритет над этим блоком.
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
name: xdto-decompile
|
||||
description: Выгрузка пакета XDTO 1С в XML-схему (XSD). Используй когда нужно получить схему существующего пакета — чтобы переработать её целиком, отдать контрагенту или перенести пакет в другую конфигурацию
|
||||
argument-hint: <PackagePath> [-OutFile <файл.xsd>]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /xdto-decompile — Выгрузка пакета XDTO в XML-схему
|
||||
|
||||
Превращает пакет XDTO в обычную XML-схему — читаемую и редактируемую.
|
||||
Заменяет чтение `Ext/Package.bin` напрямую.
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обязательный | Описание |
|
||||
|----------|--------------|----------|
|
||||
| `PackagePath` | да | Каталог пакета, путь к `Ext/Package.bin` или к `<Имя>.xml` объекта метаданных. Псевдоним — `-Path` |
|
||||
| `OutFile` | нет | Записать схему в файл (UTF-8 с BOM). Без него — вывод в stdout |
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-decompile.ps1" -PackagePath "<путь>"
|
||||
```
|
||||
|
||||
Примеры:
|
||||
```powershell
|
||||
... -PackagePath src/XDTOPackages/ОбменСБанком
|
||||
... -PackagePath src/XDTOPackages/ОбменСБанком -OutFile bank.xsd
|
||||
```
|
||||
|
||||
## Переработка схемы целиком
|
||||
|
||||
`/xdto-decompile` → правка XSD → `/xdto-compile -Force` возвращает пакет без потерь,
|
||||
включая имя, синоним и комментарий объекта метаданных — они выгружаются
|
||||
в `xs:annotation/xs:appinfo`.
|
||||
|
||||
Этот же путь даёт версионную копию пакета: смени в схеме `targetNamespace` и собери
|
||||
её с `-Name` нового пакета — имя и синоним задаются флагами `/xdto-compile`, внутри
|
||||
схемы их править не нужно. Меняя пространство имён, поправь **и объявление `xmlns`
|
||||
с тем же URI**: внутренние ссылки пользуются им как префиксом. Заменять все вхождения
|
||||
строки нельзя — пострадает импорт пространства имён, для которого старый URI является
|
||||
префиксом (`urn:пример:обмен` и `urn:пример:обмен:legacy`).
|
||||
|
||||
Этот путь нужен, когда схему меняют широко или сначала надо разобраться, как она
|
||||
устроена. Чтобы поправить одно свойство, схему целиком читать не нужно — `/xdto-edit`.
|
||||
Если нужна не схема, а сводка «что присвоить и что обязательно», — `/xdto-info`.
|
||||
|
||||
В схеме могут встретиться атрибуты с префиксом `xdto:` — так записано то, что
|
||||
XML Schema выразить не может (например `nillable` у атрибута). Схема при этом остаётся
|
||||
валидной, валидаторы такие атрибуты игнорируют. Трогать их обычно не нужно; смысл
|
||||
каждого описан в `xsd-reference.md` навыка `/xdto-compile`.
|
||||
|
||||
## Передача схемы наружу
|
||||
|
||||
Полученную XSD можно отдавать контрагенту как есть — она валидна и не теряет
|
||||
данных пакета. Штатная команда «Экспорт XML-схемы» в Конфигураторе для этого
|
||||
хуже: она теряет `nillable` у свойств-атрибутов.
|
||||
@@ -0,0 +1,612 @@
|
||||
# xdto-decompile v1.0 — Convert 1C XDTO package to XML Schema (XSD)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[Alias('Path')]
|
||||
[string]$PackagePath,
|
||||
[string]$OutFile
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$XDTO_NS = "http://v8.1c.ru/8.1/xdto"
|
||||
$XS_NS = "http://www.w3.org/2001/XMLSchema"
|
||||
$XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
$MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
||||
$V8_NS = "http://v8.1c.ru/8.1/data/core"
|
||||
|
||||
# --- Resolve paths: accept package dir, Package.bin, or metadata .xml ---
|
||||
|
||||
function Resolve-PackagePaths([string]$p) {
|
||||
$binPath = $null
|
||||
$mdPath = $null
|
||||
|
||||
if (Test-Path $p -PathType Leaf) {
|
||||
if ([System.IO.Path]::GetFileName($p) -eq "Package.bin") {
|
||||
$binPath = $p
|
||||
# <Name>/Ext/Package.bin -> <Name>.xml
|
||||
$extDir = [System.IO.Path]::GetDirectoryName($p)
|
||||
$pkgDir = [System.IO.Path]::GetDirectoryName($extDir)
|
||||
$cand = "$pkgDir.xml"
|
||||
if (Test-Path $cand) { $mdPath = $cand }
|
||||
} elseif ($p.EndsWith(".xml")) {
|
||||
$mdPath = $p
|
||||
$pkgDir = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($p), [System.IO.Path]::GetFileNameWithoutExtension($p))
|
||||
$cand = Join-Path (Join-Path $pkgDir "Ext") "Package.bin"
|
||||
if (Test-Path $cand) { $binPath = $cand }
|
||||
}
|
||||
} elseif (Test-Path $p -PathType Container) {
|
||||
$cand = Join-Path (Join-Path $p "Ext") "Package.bin"
|
||||
if (Test-Path $cand) {
|
||||
$binPath = $cand
|
||||
$mdCand = "$($p.TrimEnd('\','/')).xml"
|
||||
if (Test-Path $mdCand) { $mdPath = $mdCand }
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $binPath) { throw "Не найден Ext/Package.bin для пути: $p" }
|
||||
return @{ Bin = $binPath; Md = $mdPath }
|
||||
}
|
||||
|
||||
$paths = Resolve-PackagePaths $PackagePath
|
||||
|
||||
# --- Load package model ---
|
||||
|
||||
$doc = New-Object System.Xml.XmlDocument
|
||||
$doc.PreserveWhitespace = $false
|
||||
$doc.Load($paths.Bin)
|
||||
$pkg = $doc.DocumentElement
|
||||
if ($pkg.get_LocalName() -ne "package") { throw "Ожидался корневой <package>, получен <$($pkg.get_LocalName())>" }
|
||||
|
||||
$targetNs = $pkg.GetAttribute("targetNamespace")
|
||||
|
||||
# --- Namespace -> prefix map for the emitted schema ---
|
||||
|
||||
$nsPrefix = @{}
|
||||
$nsPrefix[$XS_NS] = "xs"
|
||||
if ($targetNs) { $nsPrefix[$targetNs] = "tns" }
|
||||
|
||||
$imports = @()
|
||||
foreach ($imp in $pkg.ChildNodes) {
|
||||
if ($imp.NodeType -ne [System.Xml.XmlNodeType]::Element -or $imp.get_LocalName() -ne "import") { continue }
|
||||
$ns = $imp.GetAttribute("namespace")
|
||||
$imports += $ns
|
||||
if (-not $nsPrefix.ContainsKey($ns)) { $nsPrefix[$ns] = "ns" + ($nsPrefix.Count) }
|
||||
}
|
||||
|
||||
# Any foreign namespace referenced but not imported still needs a prefix
|
||||
function Register-Ns([string]$ns) {
|
||||
if (-not $ns) { return }
|
||||
if (-not $nsPrefix.ContainsKey($ns)) { $nsPrefix[$ns] = "ns" + ($nsPrefix.Count) }
|
||||
}
|
||||
|
||||
# --- Output buffer ---
|
||||
|
||||
$sb = New-Object System.Text.StringBuilder
|
||||
function X([string]$line) { [void]$sb.Append($line); [void]$sb.Append("`r`n") }
|
||||
function Esc([string]$s) {
|
||||
if ($null -eq $s) { return "" }
|
||||
return $s.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace('"', """)
|
||||
}
|
||||
function EscText([string]$s) {
|
||||
if ($null -eq $s) { return "" }
|
||||
return $s.Replace("&", "&").Replace("<", "<").Replace(">", ">")
|
||||
}
|
||||
|
||||
# --- QName conversion: bin prefix -> schema prefix ---
|
||||
|
||||
function Convert-QName([System.Xml.XmlElement]$el, [string]$qname) {
|
||||
if (-not $qname) { return $null }
|
||||
# Нотация Кларка {ns}local — так записаны почти все memberTypes
|
||||
if ($qname.StartsWith("{")) {
|
||||
$close = $qname.IndexOf("}")
|
||||
if ($close -gt 0) {
|
||||
$ns = $qname.Substring(1, $close - 1)
|
||||
$local = $qname.Substring($close + 1)
|
||||
if (-not $ns) { return $local }
|
||||
Register-Ns $ns
|
||||
return "$($nsPrefix[$ns]):$local"
|
||||
}
|
||||
}
|
||||
$parts = $qname.Split(":")
|
||||
if ($parts.Count -eq 2) {
|
||||
$ns = $el.GetNamespaceOfPrefix($parts[0])
|
||||
$local = $parts[1]
|
||||
} else {
|
||||
$ns = $el.GetNamespaceOfPrefix("")
|
||||
$local = $parts[0]
|
||||
}
|
||||
if (-not $ns) { return $qname }
|
||||
Register-Ns $ns
|
||||
return "$($nsPrefix[$ns]):$local"
|
||||
}
|
||||
|
||||
function Convert-QNameList([System.Xml.XmlElement]$el, [string]$list) {
|
||||
if (-not $list) { return $null }
|
||||
$out = @()
|
||||
foreach ($q in ($list -split "\s+")) {
|
||||
if ($q) { $out += (Convert-QName $el $q) }
|
||||
}
|
||||
return ($out -join " ")
|
||||
}
|
||||
|
||||
# --- Attribute helpers ---
|
||||
|
||||
function A([System.Xml.XmlElement]$el, [string]$name) {
|
||||
if ($el.HasAttribute($name)) { return $el.GetAttribute($name) }
|
||||
return $null
|
||||
}
|
||||
|
||||
# Emits `key="value"` pairs, skipping nulls
|
||||
function Attrs([object[]]$pairs) {
|
||||
$out = ""
|
||||
for ($i = 0; $i -lt $pairs.Count; $i += 2) {
|
||||
$v = $pairs[$i + 1]
|
||||
if ($null -ne $v) { $out += " $($pairs[$i])=`"$(Esc ([string]$v))`"" }
|
||||
}
|
||||
return $out
|
||||
}
|
||||
|
||||
# --- xdto: mirror attributes ---
|
||||
# Everything XSD cannot express literally rides as xdto:<same name as in Package.bin>.
|
||||
# Mirrors are emitted only when the literal bin form is not recoverable from the XSD.
|
||||
|
||||
$usesXdtoNs = $false
|
||||
function Mirror([string]$name, $value) {
|
||||
# $value НЕ типизируем: [string]$null коэрсится в "" и зеркало ложно появляется
|
||||
if ($null -eq $value) { return "" }
|
||||
$script:usesXdtoNs = $true
|
||||
return " xdto:$name=`"$(Esc ([string]$value))`""
|
||||
}
|
||||
|
||||
# Обычно префиксы генерируются схемой dNpM, но изредка узел несёт осмысленный
|
||||
# префикс (например dcsset) — его надо сохранить, иначе round-trip не сойдётся.
|
||||
function Mirror-Prefix([System.Xml.XmlElement]$el) {
|
||||
foreach ($a in $el.Attributes) {
|
||||
if ($a.Prefix -ne "xmlns") { continue }
|
||||
if ($a.get_LocalName() -match '^d\d+p\d+$') { continue }
|
||||
return (Mirror "prefix" $a.get_LocalName())
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
# --- Facet emission (simple types) ---
|
||||
|
||||
$FACET_ATTRS = @("length", "minLength", "maxLength", "totalDigits", "fractionDigits",
|
||||
"minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "whiteSpace")
|
||||
|
||||
function Emit-Facets([System.Xml.XmlElement]$el, [string]$indent) {
|
||||
foreach ($f in $FACET_ATTRS) {
|
||||
$v = A $el $f
|
||||
if ($null -ne $v) { X "$indent<xs:$f value=`"$(Esc $v)`"/>" }
|
||||
}
|
||||
foreach ($child in $el.ChildNodes) {
|
||||
if ($child.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
|
||||
if ($child.get_LocalName() -eq "pattern") {
|
||||
X "$indent<xs:pattern value=`"$(Esc $child.InnerText)`"/>"
|
||||
} elseif ($child.get_LocalName() -eq "enumeration") {
|
||||
# xsi:type on enumeration has no XSD counterpart — mirror it
|
||||
$xsiType = $child.GetAttribute("type", $XSI_NS)
|
||||
$m = ""
|
||||
if ($xsiType) { $m = Mirror "type" (Convert-QName $child $xsiType) }
|
||||
X "$indent<xs:enumeration value=`"$(Esc $child.InnerText)`"$m/>"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Has-SimpleContent([System.Xml.XmlElement]$el) {
|
||||
foreach ($c in $el.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "pattern") { return $true }
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "enumeration") { return $true }
|
||||
}
|
||||
foreach ($f in $FACET_ATTRS) { if ($null -ne (A $el $f)) { return $true } }
|
||||
return $false
|
||||
}
|
||||
|
||||
# --- Simple type body (valueType / typeDef xsi:type=ValueType) ---
|
||||
|
||||
function Emit-SimpleTypeBody([System.Xml.XmlElement]$el, [string]$indent) {
|
||||
$variety = A $el "variety"
|
||||
$base = Convert-QName $el (A $el "base")
|
||||
$itemType = Convert-QName $el (A $el "itemType")
|
||||
$memberTypes= Convert-QNameList $el (A $el "memberTypes")
|
||||
|
||||
# variety is mirrored: "Atomic" is written explicitly for only part of the corpus
|
||||
$mv = Mirror "variety" $variety
|
||||
|
||||
# memberTypes почти всегда записаны нотацией Кларка; редкую префиксную форму зеркалим
|
||||
$rawMembers = A $el "memberTypes"
|
||||
if ($null -ne $rawMembers -and -not $rawMembers.StartsWith("{")) {
|
||||
$mv += Mirror "memberTypesForm" "prefixed"
|
||||
}
|
||||
# При нотации Кларка объявление xmlns:dNpM иногда присутствует, иногда нет —
|
||||
# из значения это не выводится (зависит от состояния сериализатора), зеркалим факт
|
||||
if ($null -ne $rawMembers -and $rawMembers.StartsWith("{")) {
|
||||
foreach ($a in $el.Attributes) {
|
||||
if ($a.Prefix -eq "xmlns" -and $a.get_LocalName() -match '^d\d+p\d+$') {
|
||||
$mv += Mirror "declareNs" $a.Value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($variety -eq "List" -or $itemType) {
|
||||
X "$indent<xs:list$(Attrs @('itemType', $itemType))$mv/>"
|
||||
return
|
||||
}
|
||||
if ($variety -eq "Union" -or $memberTypes) {
|
||||
$anon = @()
|
||||
foreach ($c in $el.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $anon += $c }
|
||||
}
|
||||
if ($anon.Count -eq 0) {
|
||||
X "$indent<xs:union$(Attrs @('memberTypes', $memberTypes))$mv/>"
|
||||
} else {
|
||||
X "$indent<xs:union$(Attrs @('memberTypes', $memberTypes))$mv>"
|
||||
foreach ($c in $anon) {
|
||||
X "$indent`t<xs:simpleType>"
|
||||
Emit-SimpleTypeBody $c "$indent`t`t"
|
||||
X "$indent`t</xs:simpleType>"
|
||||
}
|
||||
X "$indent</xs:union>"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# Базовый тип может быть задан не атрибутом base, а вложенным анонимным typeDef
|
||||
$anonBase = $null
|
||||
foreach ($c in $el.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $anonBase = $c; break }
|
||||
}
|
||||
|
||||
if ((Has-SimpleContent $el) -or $anonBase) {
|
||||
X "$indent<xs:restriction$(Attrs @('base', $base))$mv>"
|
||||
if ($anonBase) {
|
||||
X "$indent`t<xs:simpleType>"
|
||||
Emit-SimpleTypeBody $anonBase "$indent`t`t"
|
||||
X "$indent`t</xs:simpleType>"
|
||||
}
|
||||
Emit-Facets $el "$indent`t"
|
||||
X "$indent</xs:restriction>"
|
||||
} else {
|
||||
X "$indent<xs:restriction$(Attrs @('base', $base))$mv/>"
|
||||
}
|
||||
}
|
||||
|
||||
# --- Property classification ---
|
||||
|
||||
function Get-PropForm([System.Xml.XmlElement]$p) {
|
||||
$f = A $p "form"
|
||||
if ($null -eq $f) { return "Element" }
|
||||
return $f
|
||||
}
|
||||
|
||||
function Get-AnonTypeDef([System.Xml.XmlElement]$p) {
|
||||
foreach ($c in $p.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { return $c }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
# --- Property emission ---
|
||||
|
||||
function Emit-Property([System.Xml.XmlElement]$p, [string]$indent, [bool]$isGlobal) {
|
||||
$form = Get-PropForm $p
|
||||
$name = A $p "name"
|
||||
$type = Convert-QName $p (A $p "type")
|
||||
$ref = Convert-QName $p (A $p "ref")
|
||||
$local = A $p "localName"
|
||||
$lower = A $p "lowerBound"
|
||||
$upper = A $p "upperBound"
|
||||
$nill = A $p "nillable"
|
||||
$def = A $p "default"
|
||||
$fix = A $p "fixed"
|
||||
# В модели fixed — булев флаг, значение лежит в default; в XSD наоборот:
|
||||
# fixed="V" несёт само значение. Переводим, а не копируем.
|
||||
$defOut = $def
|
||||
$fixOut = $null
|
||||
$fixMirror = ""
|
||||
if ($fix -eq "true" -and $null -ne $def) { $fixOut = $def; $defOut = $null }
|
||||
elseif ($null -ne $fix) { $fixMirror = Mirror "fixed" $fix }
|
||||
$anon = Get-AnonTypeDef $p
|
||||
# qualified записан как атрибут в пространстве имён XDTO
|
||||
$qual = $p.GetAttribute("qualified", $XDTO_NS)
|
||||
if ($qual -eq "") { $qual = $null }
|
||||
|
||||
# lowerBound/upperBound map 1:1 onto minOccurs/maxOccurs, including "written explicitly"
|
||||
$minOccurs = $lower
|
||||
$maxOccurs = $null
|
||||
if ($null -ne $upper) { $maxOccurs = if ($upper -eq "-1") { "unbounded" } else { $upper } }
|
||||
|
||||
# localName carries the original XML name when it is not a valid 1C identifier
|
||||
$xmlName = if ($null -ne $local) { $local } else { $name }
|
||||
$mirrorName = if ($null -ne $local) { Mirror "name" $name } else { "" }
|
||||
|
||||
$m = ""
|
||||
$isAttr = ($form -eq "Attribute")
|
||||
|
||||
if ($isAttr) {
|
||||
# XSD forbids nillable on attributes, and has no minOccurs/maxOccurs
|
||||
if ($null -ne $qual) { $m += Mirror "qualified" $qual }
|
||||
if ($null -ne $nill) { $m += Mirror "nillable" $nill }
|
||||
if ($null -ne $lower) { $m += Mirror "lowerBound" $lower }
|
||||
if ($null -ne $upper) { $m += Mirror "upperBound" $upper }
|
||||
$m += $mirrorName
|
||||
$m += $fixMirror
|
||||
$body = Attrs @('name', $xmlName, 'ref', $ref, 'type', $type, 'default', $defOut, 'fixed', $fixOut)
|
||||
if ($anon) {
|
||||
X "$indent<xs:attribute$body$m>"
|
||||
X "$indent`t<xs:simpleType>"
|
||||
Emit-SimpleTypeBody $anon "$indent`t`t"
|
||||
X "$indent`t</xs:simpleType>"
|
||||
X "$indent</xs:attribute>"
|
||||
} else {
|
||||
X "$indent<xs:attribute$body$m/>"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if ($form -eq "Text") {
|
||||
# handled by the owning complexType (xs:simpleContent)
|
||||
return
|
||||
}
|
||||
|
||||
# form="Element" written explicitly is indistinguishable in XSD from the default
|
||||
if ($null -ne (A $p "form")) { $m += Mirror "form" $form }
|
||||
if ($null -ne $qual) { $m += Mirror "qualified" $qual }
|
||||
$m += $mirrorName
|
||||
$m += (Mirror-Prefix $p)
|
||||
$m += $fixMirror
|
||||
|
||||
$body = Attrs @('name', $xmlName, 'ref', $ref, 'type', $type,
|
||||
'minOccurs', $minOccurs, 'maxOccurs', $maxOccurs,
|
||||
'nillable', $nill, 'default', $defOut, 'fixed', $fixOut)
|
||||
|
||||
if ($anon) {
|
||||
X "$indent<xs:element$body$m>"
|
||||
if ($anon.GetAttribute("type", $XSI_NS) -eq "ObjectType") {
|
||||
$anonBase = Convert-QName $anon (A $anon "base")
|
||||
if ($anonBase) {
|
||||
X "$indent`t<xs:complexType$(ComplexTypeAttrs $anon)>"
|
||||
X "$indent`t`t<xs:complexContent>"
|
||||
X "$indent`t`t`t<xs:extension$(Attrs @('base', $anonBase))>"
|
||||
Emit-ComplexTypeBody $anon "$indent`t`t`t`t"
|
||||
X "$indent`t`t`t</xs:extension>"
|
||||
X "$indent`t`t</xs:complexContent>"
|
||||
X "$indent`t</xs:complexType>"
|
||||
} else {
|
||||
X "$indent`t<xs:complexType$(ComplexTypeAttrs $anon)>"
|
||||
Emit-ComplexTypeBody $anon "$indent`t`t"
|
||||
X "$indent`t</xs:complexType>"
|
||||
}
|
||||
} else {
|
||||
X "$indent`t<xs:simpleType>"
|
||||
Emit-SimpleTypeBody $anon "$indent`t`t"
|
||||
X "$indent`t</xs:simpleType>"
|
||||
}
|
||||
X "$indent</xs:element>"
|
||||
} else {
|
||||
X "$indent<xs:element$body$m/>"
|
||||
}
|
||||
}
|
||||
|
||||
# --- Complex type body (objectType / typeDef xsi:type=ObjectType) ---
|
||||
|
||||
function Emit-ComplexTypeBody([System.Xml.XmlElement]$el, [string]$indent) {
|
||||
$open = A $el "open"
|
||||
$ordered = A $el "ordered"
|
||||
$sequenced = A $el "sequenced"
|
||||
|
||||
$props = @()
|
||||
foreach ($c in $el.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "property") { $props += $c }
|
||||
}
|
||||
|
||||
$elems = @(); $attrs = @(); $text = $null
|
||||
foreach ($p in $props) {
|
||||
switch (Get-PropForm $p) {
|
||||
"Attribute" { $attrs += $p }
|
||||
"Text" { $text = $p }
|
||||
default { $elems += $p }
|
||||
}
|
||||
}
|
||||
|
||||
# simpleContent: a "Text" property holds the element's own value
|
||||
if ($null -ne $text) {
|
||||
$tType = Convert-QName $text (A $text "type")
|
||||
$tm = ""
|
||||
$tName = A $text "name"
|
||||
if ($tName -ne "__content") { $tm += Mirror "textName" $tName }
|
||||
foreach ($extra in @("lowerBound", "upperBound", "nillable")) {
|
||||
$v = A $text $extra
|
||||
if ($null -ne $v) { $tm += Mirror "text$extra" $v }
|
||||
}
|
||||
X "$indent<xs:simpleContent>"
|
||||
X "$indent`t<xs:extension$(Attrs @('base', $tType))$tm>"
|
||||
foreach ($a in $attrs) { Emit-Property $a "$indent`t`t" $false }
|
||||
X "$indent`t</xs:extension>"
|
||||
X "$indent</xs:simpleContent>"
|
||||
return
|
||||
}
|
||||
|
||||
$particleTag = if ($ordered -eq "false") { "xs:choice" } else { "xs:sequence" }
|
||||
$needParticle = ($elems.Count -gt 0) -or ($open -eq "true")
|
||||
|
||||
if ($needParticle) {
|
||||
X "$indent<$particleTag>"
|
||||
foreach ($e in $elems) { Emit-Property $e "$indent`t" $false }
|
||||
if ($open -eq "true") {
|
||||
X "$indent`t<xs:any namespace=`"##any`" processContents=`"lax`" minOccurs=`"0`" maxOccurs=`"unbounded`"/>"
|
||||
}
|
||||
X "$indent</$particleTag>"
|
||||
}
|
||||
|
||||
foreach ($a in $attrs) { Emit-Property $a "$indent" $false }
|
||||
if ($open -eq "true") {
|
||||
X "$indent<xs:anyAttribute namespace=`"##any`" processContents=`"lax`"/>"
|
||||
}
|
||||
}
|
||||
|
||||
# Attributes of a complexType tag itself (mirrors + XSD-native abstract/mixed)
|
||||
function ComplexTypeAttrs([System.Xml.XmlElement]$el) {
|
||||
$open = A $el "open"
|
||||
$ordered = A $el "ordered"
|
||||
$sequenced = A $el "sequenced"
|
||||
$abstract = A $el "abstract"
|
||||
$mixed = A $el "mixed"
|
||||
|
||||
$out = ""
|
||||
if ($abstract -eq "true") { $out += " abstract=`"true`"" } elseif ($null -ne $abstract) { $out += Mirror "abstract" $abstract }
|
||||
if ($mixed -eq "true") { $out += " mixed=`"true`"" } elseif ($null -ne $mixed) { $out += Mirror "mixed" $mixed }
|
||||
|
||||
# XSD требует объявлять атрибуты после частицы, поэтому исходный порядок свойств
|
||||
# восстановим как «сначала form=Attribute, потом остальные» — это верно для 96.5%
|
||||
# типов корпуса. Расхождения (768 типов) зеркалим списком имён.
|
||||
$order = @(); $kinds = @()
|
||||
foreach ($c in $el.ChildNodes) {
|
||||
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element -or $c.get_LocalName() -ne "property") { continue }
|
||||
$nm = A $c "name"
|
||||
if ($null -eq $nm) { $nm = "@" + ((A $c "ref") -split ":")[-1] }
|
||||
$order += $nm
|
||||
$kinds += $(if ((Get-PropForm $c) -eq "Attribute") { 0 } else { 1 })
|
||||
}
|
||||
if ($order.Count -gt 1) {
|
||||
$natural = $true
|
||||
for ($i = 1; $i -lt $kinds.Count; $i++) { if ($kinds[$i] -lt $kinds[$i - 1]) { $natural = $false; break } }
|
||||
if (-not $natural) { $out += Mirror "order" ($order -join "|") }
|
||||
}
|
||||
|
||||
# open="true" is rendered as xs:any + xs:anyAttribute; anything else is mirrored
|
||||
if ($null -ne $open -and $open -ne "true") { $out += Mirror "open" $open }
|
||||
# ordered="false" is rendered as xs:choice; "true" written explicitly is mirrored
|
||||
if ($null -ne $ordered -and $ordered -ne "false") { $out += Mirror "ordered" $ordered }
|
||||
# sequenced has no XSD counterpart at all
|
||||
if ($null -ne $sequenced) { $out += Mirror "sequenced" $sequenced }
|
||||
|
||||
return $out
|
||||
}
|
||||
|
||||
# --- Metadata properties (Name/Synonym/Comment) from the object's .xml ---
|
||||
|
||||
function Get-MetadataBlock() {
|
||||
if (-not $paths.Md -or -not (Test-Path $paths.Md)) { return $null }
|
||||
$md = New-Object System.Xml.XmlDocument
|
||||
$md.Load($paths.Md)
|
||||
$nsm = New-Object System.Xml.XmlNamespaceManager($md.NameTable)
|
||||
$nsm.AddNamespace("md", $MD_NS)
|
||||
$nsm.AddNamespace("v8", $V8_NS)
|
||||
$props = $md.SelectSingleNode("//md:XDTOPackage/md:Properties", $nsm)
|
||||
if (-not $props) { return $null }
|
||||
|
||||
$res = @{ Name = $null; Comment = $null; Synonym = @() }
|
||||
$n = $props.SelectSingleNode("md:Name", $nsm); if ($n) { $res.Name = $n.InnerText }
|
||||
$c = $props.SelectSingleNode("md:Comment", $nsm); if ($c) { $res.Comment = $c.InnerText }
|
||||
foreach ($item in $props.SelectNodes("md:Synonym/v8:item", $nsm)) {
|
||||
$lang = $item.SelectSingleNode("v8:lang", $nsm)
|
||||
$cont = $item.SelectSingleNode("v8:content", $nsm)
|
||||
$res.Synonym += @{ Lang = $(if ($lang) { $lang.InnerText } else { "" }); Content = $(if ($cont) { $cont.InnerText } else { "" }) }
|
||||
}
|
||||
return $res
|
||||
}
|
||||
|
||||
$meta = Get-MetadataBlock
|
||||
|
||||
# --- Emit ---
|
||||
# Body first: emitting it registers every namespace actually referenced, so the
|
||||
# schema element can declare a complete prefix map.
|
||||
|
||||
$bodyBuilder = New-Object System.Text.StringBuilder
|
||||
$mainBuilder = $sb
|
||||
$sb = $bodyBuilder
|
||||
|
||||
foreach ($node in $pkg.ChildNodes) {
|
||||
if ($node.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
|
||||
switch ($node.get_LocalName()) {
|
||||
"import" {
|
||||
X "`t<xs:import namespace=`"$(Esc $node.GetAttribute('namespace'))`"/>"
|
||||
}
|
||||
"property" {
|
||||
Emit-Property $node "`t" $true
|
||||
}
|
||||
"valueType" {
|
||||
$name = A $node "name"
|
||||
X "`t<xs:simpleType$(Attrs @('name', $name))>"
|
||||
Emit-SimpleTypeBody $node "`t`t"
|
||||
X "`t</xs:simpleType>"
|
||||
}
|
||||
"objectType" {
|
||||
$name = A $node "name"
|
||||
$base = Convert-QName $node (A $node "base")
|
||||
$cta = ComplexTypeAttrs $node
|
||||
if ($base) {
|
||||
X "`t<xs:complexType$(Attrs @('name', $name))$cta>"
|
||||
X "`t`t<xs:complexContent>"
|
||||
X "`t`t`t<xs:extension$(Attrs @('base', $base))>"
|
||||
Emit-ComplexTypeBody $node "`t`t`t`t"
|
||||
X "`t`t`t</xs:extension>"
|
||||
X "`t`t</xs:complexContent>"
|
||||
X "`t</xs:complexType>"
|
||||
} else {
|
||||
$hasBody = $false
|
||||
foreach ($c in $node.ChildNodes) { if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element) { $hasBody = $true; break } }
|
||||
if (-not $hasBody -and (A $node "open") -ne "true") {
|
||||
X "`t<xs:complexType$(Attrs @('name', $name))$cta/>"
|
||||
} else {
|
||||
X "`t<xs:complexType$(Attrs @('name', $name))$cta>"
|
||||
Emit-ComplexTypeBody $node "`t`t"
|
||||
X "`t</xs:complexType>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sb = $mainBuilder
|
||||
|
||||
# --- Schema element ---
|
||||
|
||||
$nsDecls = ""
|
||||
foreach ($kv in ($nsPrefix.GetEnumerator() | Sort-Object { $_.Value })) {
|
||||
$nsDecls += " xmlns:$($kv.Value)=`"$(Esc $kv.Key)`""
|
||||
}
|
||||
if ($usesXdtoNs) { $nsDecls += " xmlns:xdto=`"$XDTO_NS`"" }
|
||||
|
||||
$schemaAttrs = ""
|
||||
$efq = A $pkg "elementFormQualified"
|
||||
$afq = A $pkg "attributeFormQualified"
|
||||
if ($null -ne $efq) { $schemaAttrs += " elementFormDefault=`"$(if ($efq -eq 'true') { 'qualified' } else { 'unqualified' })`"" }
|
||||
if ($null -ne $afq) { $schemaAttrs += " attributeFormDefault=`"$(if ($afq -eq 'true') { 'qualified' } else { 'unqualified' })`"" }
|
||||
|
||||
X "<xs:schema$nsDecls$(Attrs @('targetNamespace', $targetNs))$schemaAttrs>"
|
||||
|
||||
if ($meta) {
|
||||
X "`t<xs:annotation>"
|
||||
X "`t`t<xs:appinfo>"
|
||||
X "`t`t`t<xdto:package xmlns:xdto=`"$XDTO_NS`">"
|
||||
if ($null -ne $meta.Name) { X "`t`t`t`t<xdto:name>$(EscText $meta.Name)</xdto:name>" }
|
||||
if ($null -ne $meta.Comment -and $meta.Comment -ne "") { X "`t`t`t`t<xdto:comment>$(EscText $meta.Comment)</xdto:comment>" }
|
||||
foreach ($s in $meta.Synonym) {
|
||||
X "`t`t`t`t<xdto:synonym lang=`"$(Esc $s.Lang)`">$(EscText $s.Content)</xdto:synonym>"
|
||||
}
|
||||
X "`t`t`t</xdto:package>"
|
||||
X "`t`t</xs:appinfo>"
|
||||
X "`t</xs:annotation>"
|
||||
}
|
||||
|
||||
[void]$sb.Append($bodyBuilder.ToString())
|
||||
X "</xs:schema>"
|
||||
|
||||
# --- Output (UTF-8 with BOM, CRLF — matches the Designer's own XSD export) ---
|
||||
|
||||
$text = $sb.ToString()
|
||||
if ($OutFile) {
|
||||
$dir = [System.IO.Path]::GetDirectoryName($OutFile)
|
||||
if ($dir -and -not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($OutFile, $text, $enc)
|
||||
Write-Host "✓ XSD записана: $OutFile"
|
||||
Write-Host " targetNamespace: $targetNs"
|
||||
} else {
|
||||
[Console]::Out.Write($text)
|
||||
}
|
||||
@@ -0,0 +1,593 @@
|
||||
# xdto-decompile v1.0 — Convert 1C XDTO package to XML Schema (XSD) (Python port)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from lxml import etree
|
||||
|
||||
# newline="" — иначе Windows транслирует \n и CRLF схемы удваивается в \r\r\n
|
||||
sys.stdout.reconfigure(encoding="utf-8", newline="")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
|
||||
XDTO_NS = "http://v8.1c.ru/8.1/xdto"
|
||||
XS_NS = "http://www.w3.org/2001/XMLSchema"
|
||||
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
||||
V8_NS = "http://v8.1c.ru/8.1/data/core"
|
||||
|
||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||
parser.add_argument("-PackagePath", "-Path", required=True)
|
||||
parser.add_argument("-OutFile", default="")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def _parse_xml(source, from_string=False):
|
||||
"""Разбор с узким отступлением для не-URI пространств имён.
|
||||
|
||||
Платформа допускает в targetNamespace произвольную строку (в выгрузке БП есть
|
||||
пакет с кириллическим «ДопФайлУниверсальный»), .NET такое принимает, а libxml2
|
||||
отвергает. Откатываемся на восстанавливающий разбор ТОЛЬКО на этой ошибке,
|
||||
иначе по-настоящему битый XML перестал бы отличаться от корректного.
|
||||
"""
|
||||
try:
|
||||
return (etree.fromstring(source) if from_string else etree.parse(source))
|
||||
except etree.XMLSyntaxError as e:
|
||||
if "is not a valid URI" not in str(e):
|
||||
raise
|
||||
p = etree.XMLParser(recover=True)
|
||||
return (etree.fromstring(source, p) if from_string else etree.parse(source, p))
|
||||
|
||||
# ── resolve paths ────────────────────────────────────────────
|
||||
|
||||
package_path = args.PackagePath
|
||||
bin_path = None
|
||||
md_path = None
|
||||
|
||||
if os.path.isfile(package_path):
|
||||
if os.path.basename(package_path) == "Package.bin":
|
||||
bin_path = package_path
|
||||
pkg_dir = os.path.dirname(os.path.dirname(package_path))
|
||||
if os.path.exists(pkg_dir + ".xml"):
|
||||
md_path = pkg_dir + ".xml"
|
||||
elif package_path.endswith(".xml"):
|
||||
md_path = package_path
|
||||
stem = os.path.join(os.path.dirname(package_path),
|
||||
os.path.splitext(os.path.basename(package_path))[0])
|
||||
c = os.path.join(stem, "Ext", "Package.bin")
|
||||
if os.path.exists(c):
|
||||
bin_path = c
|
||||
elif os.path.isdir(package_path):
|
||||
c = os.path.join(package_path, "Ext", "Package.bin")
|
||||
if os.path.exists(c):
|
||||
bin_path = c
|
||||
m = package_path.rstrip("\\/") + ".xml"
|
||||
if os.path.exists(m):
|
||||
md_path = m
|
||||
|
||||
if not bin_path:
|
||||
print(f"Не найден Ext/Package.bin для пути: {package_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
doc = _parse_xml(bin_path)
|
||||
pkg = doc.getroot()
|
||||
|
||||
|
||||
def local(el):
|
||||
return etree.QName(el).localname
|
||||
|
||||
|
||||
if local(pkg) != "package":
|
||||
print(f"Ожидался корневой <package>, получен <{local(pkg)}>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
target_ns = pkg.get("targetNamespace")
|
||||
|
||||
# ── namespace -> prefix map for the emitted schema ───────────
|
||||
|
||||
ns_prefix = {XS_NS: "xs"}
|
||||
if target_ns:
|
||||
ns_prefix[target_ns] = "tns"
|
||||
|
||||
imports = []
|
||||
for imp in pkg:
|
||||
if isinstance(imp.tag, str) and local(imp) == "import":
|
||||
ns = imp.get("namespace")
|
||||
imports.append(ns)
|
||||
if ns not in ns_prefix:
|
||||
ns_prefix[ns] = "ns" + str(len(ns_prefix))
|
||||
|
||||
|
||||
def register_ns(ns):
|
||||
if ns and ns not in ns_prefix:
|
||||
ns_prefix[ns] = "ns" + str(len(ns_prefix))
|
||||
|
||||
|
||||
# ── output buffer ────────────────────────────────────────────
|
||||
|
||||
lines = []
|
||||
|
||||
|
||||
def X(line):
|
||||
lines.append(line)
|
||||
|
||||
|
||||
def esc(s):
|
||||
if s is None:
|
||||
return ""
|
||||
return str(s).replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
|
||||
|
||||
|
||||
def esc_text(s):
|
||||
if s is None:
|
||||
return ""
|
||||
return str(s).replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
|
||||
# ── QName conversion: bin prefix -> schema prefix ────────────
|
||||
|
||||
def convert_qname(el, qname):
|
||||
if not qname:
|
||||
return None
|
||||
# Нотация Кларка {ns}local — так записаны почти все memberTypes
|
||||
if qname.startswith("{"):
|
||||
close = qname.find("}")
|
||||
if close > 0:
|
||||
ns = qname[1:close]
|
||||
loc = qname[close + 1:]
|
||||
if not ns:
|
||||
return loc
|
||||
register_ns(ns)
|
||||
return f"{ns_prefix[ns]}:{loc}"
|
||||
parts = qname.split(":")
|
||||
if len(parts) == 2:
|
||||
ns = el.nsmap.get(parts[0])
|
||||
loc = parts[1]
|
||||
else:
|
||||
ns = el.nsmap.get(None)
|
||||
loc = parts[0]
|
||||
if not ns:
|
||||
return qname
|
||||
register_ns(ns)
|
||||
return f"{ns_prefix[ns]}:{loc}"
|
||||
|
||||
|
||||
def convert_qname_list(el, lst):
|
||||
if not lst:
|
||||
return None
|
||||
return " ".join(convert_qname(el, q) for q in lst.split() if q)
|
||||
|
||||
|
||||
def attrs(pairs):
|
||||
out = ""
|
||||
for i in range(0, len(pairs), 2):
|
||||
v = pairs[i + 1]
|
||||
if v is not None:
|
||||
out += f' {pairs[i]}="{esc(v)}"'
|
||||
return out
|
||||
|
||||
|
||||
# ── xdto: mirror attributes ──────────────────────────────────
|
||||
|
||||
state = {"uses_xdto": False}
|
||||
|
||||
|
||||
def mirror(name, value):
|
||||
if value is None:
|
||||
return ""
|
||||
state["uses_xdto"] = True
|
||||
return f' xdto:{name}="{esc(value)}"'
|
||||
|
||||
|
||||
DNPM = re.compile(r"^d\d+p\d+$")
|
||||
|
||||
|
||||
def ns_decls_of(el):
|
||||
"""Локальные объявления xmlns на самом узле, как (префикс, uri)."""
|
||||
parent_map = el.getparent().nsmap if el.getparent() is not None else {}
|
||||
for px, uri in el.nsmap.items():
|
||||
if px is None:
|
||||
continue
|
||||
if parent_map.get(px) == uri:
|
||||
continue
|
||||
yield px, uri
|
||||
|
||||
|
||||
def mirror_prefix(el):
|
||||
# Обычно префиксы генерируются схемой dNpM, но изредка узел несёт осмысленный
|
||||
# префикс (например dcsset) — его надо сохранить, иначе round-trip не сойдётся.
|
||||
for px, _uri in ns_decls_of(el):
|
||||
if DNPM.match(px):
|
||||
continue
|
||||
return mirror("prefix", px)
|
||||
return ""
|
||||
|
||||
|
||||
FACET_ATTRS = ["length", "minLength", "maxLength", "totalDigits", "fractionDigits",
|
||||
"minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "whiteSpace"]
|
||||
|
||||
|
||||
def emit_facets(el, indent):
|
||||
for f in FACET_ATTRS:
|
||||
v = el.get(f)
|
||||
if v is not None:
|
||||
X(f'{indent}<xs:{f} value="{esc(v)}"/>')
|
||||
for child in el:
|
||||
if not isinstance(child.tag, str):
|
||||
continue
|
||||
ln = local(child)
|
||||
if ln == "pattern":
|
||||
X(f'{indent}<xs:pattern value="{esc(child.text or "")}"/>')
|
||||
elif ln == "enumeration":
|
||||
xsi_type = child.get(f"{{{XSI_NS}}}type")
|
||||
m = mirror("type", convert_qname(child, xsi_type)) if xsi_type else ""
|
||||
X(f'{indent}<xs:enumeration value="{esc(child.text or "")}"{m}/>')
|
||||
|
||||
|
||||
def has_simple_content(el):
|
||||
for c in el:
|
||||
if isinstance(c.tag, str) and local(c) in ("pattern", "enumeration"):
|
||||
return True
|
||||
return any(el.get(f) is not None for f in FACET_ATTRS)
|
||||
|
||||
|
||||
# ── simple type body (valueType / typeDef xsi:type=ValueType) ─
|
||||
|
||||
def emit_simple_type_body(el, indent):
|
||||
variety = el.get("variety")
|
||||
base = convert_qname(el, el.get("base"))
|
||||
item_type = convert_qname(el, el.get("itemType"))
|
||||
member_types = convert_qname_list(el, el.get("memberTypes"))
|
||||
|
||||
mv = mirror("variety", variety)
|
||||
|
||||
raw_members = el.get("memberTypes")
|
||||
if raw_members is not None and not raw_members.startswith("{"):
|
||||
mv += mirror("memberTypesForm", "prefixed")
|
||||
# При нотации Кларка объявление xmlns:dNpM иногда присутствует, иногда нет —
|
||||
# из значения это не выводится (зависит от состояния сериализатора), зеркалим факт
|
||||
if raw_members is not None and raw_members.startswith("{"):
|
||||
for px, uri in ns_decls_of(el):
|
||||
if DNPM.match(px):
|
||||
mv += mirror("declareNs", uri)
|
||||
break
|
||||
|
||||
if variety == "List" or item_type:
|
||||
X(f'{indent}<xs:list{attrs(["itemType", item_type])}{mv}/>')
|
||||
return
|
||||
if variety == "Union" or member_types:
|
||||
anon = [c for c in el if isinstance(c.tag, str) and local(c) == "typeDef"]
|
||||
if not anon:
|
||||
X(f'{indent}<xs:union{attrs(["memberTypes", member_types])}{mv}/>')
|
||||
else:
|
||||
X(f'{indent}<xs:union{attrs(["memberTypes", member_types])}{mv}>')
|
||||
for c in anon:
|
||||
X(f"{indent}\t<xs:simpleType>")
|
||||
emit_simple_type_body(c, indent + "\t\t")
|
||||
X(f"{indent}\t</xs:simpleType>")
|
||||
X(f"{indent}</xs:union>")
|
||||
return
|
||||
|
||||
# Базовый тип может быть задан не атрибутом base, а вложенным анонимным typeDef
|
||||
anon_base = next((c for c in el if isinstance(c.tag, str) and local(c) == "typeDef"), None)
|
||||
|
||||
if has_simple_content(el) or anon_base is not None:
|
||||
X(f'{indent}<xs:restriction{attrs(["base", base])}{mv}>')
|
||||
if anon_base is not None:
|
||||
X(f"{indent}\t<xs:simpleType>")
|
||||
emit_simple_type_body(anon_base, indent + "\t\t")
|
||||
X(f"{indent}\t</xs:simpleType>")
|
||||
emit_facets(el, indent + "\t")
|
||||
X(f"{indent}</xs:restriction>")
|
||||
else:
|
||||
X(f'{indent}<xs:restriction{attrs(["base", base])}{mv}/>')
|
||||
|
||||
|
||||
# ── property classification ──────────────────────────────────
|
||||
|
||||
def prop_form(p):
|
||||
f = p.get("form")
|
||||
return "Element" if f is None else f
|
||||
|
||||
|
||||
def anon_type_def(p):
|
||||
return next((c for c in p if isinstance(c.tag, str) and local(c) == "typeDef"), None)
|
||||
|
||||
|
||||
# ── property emission ────────────────────────────────────────
|
||||
|
||||
def emit_property(p, indent):
|
||||
form = prop_form(p)
|
||||
name = p.get("name")
|
||||
type_ = convert_qname(p, p.get("type"))
|
||||
ref = convert_qname(p, p.get("ref"))
|
||||
local_name = p.get("localName")
|
||||
lower = p.get("lowerBound")
|
||||
upper = p.get("upperBound")
|
||||
nill = p.get("nillable")
|
||||
default = p.get("default")
|
||||
fixed = p.get("fixed")
|
||||
# В модели fixed — булев флаг, значение лежит в default; в XSD наоборот:
|
||||
# fixed="V" несёт само значение. Переводим, а не копируем.
|
||||
def_out, fix_out, fix_mirror = default, None, ""
|
||||
if fixed == "true" and default is not None:
|
||||
fix_out, def_out = default, None
|
||||
elif fixed is not None:
|
||||
fix_mirror = mirror("fixed", fixed)
|
||||
anon = anon_type_def(p)
|
||||
qual = p.get(f"{{{XDTO_NS}}}qualified")
|
||||
|
||||
min_occurs = lower
|
||||
max_occurs = None
|
||||
if upper is not None:
|
||||
max_occurs = "unbounded" if upper == "-1" else upper
|
||||
|
||||
xml_name = local_name if local_name is not None else name
|
||||
mirror_name = mirror("name", name) if local_name is not None else ""
|
||||
|
||||
m = ""
|
||||
if form == "Attribute":
|
||||
if qual is not None:
|
||||
m += mirror("qualified", qual)
|
||||
if nill is not None:
|
||||
m += mirror("nillable", nill)
|
||||
if lower is not None:
|
||||
m += mirror("lowerBound", lower)
|
||||
if upper is not None:
|
||||
m += mirror("upperBound", upper)
|
||||
m += mirror_name
|
||||
m += fix_mirror
|
||||
body = attrs(["name", xml_name, "ref", ref, "type", type_, "default", def_out, "fixed", fix_out])
|
||||
if anon is not None:
|
||||
X(f"{indent}<xs:attribute{body}{m}>")
|
||||
X(f"{indent}\t<xs:simpleType>")
|
||||
emit_simple_type_body(anon, indent + "\t\t")
|
||||
X(f"{indent}\t</xs:simpleType>")
|
||||
X(f"{indent}</xs:attribute>")
|
||||
else:
|
||||
X(f"{indent}<xs:attribute{body}{m}/>")
|
||||
return
|
||||
|
||||
if form == "Text":
|
||||
# handled by the owning complexType (xs:simpleContent)
|
||||
return
|
||||
|
||||
if p.get("form") is not None:
|
||||
m += mirror("form", form)
|
||||
if qual is not None:
|
||||
m += mirror("qualified", qual)
|
||||
m += mirror_name
|
||||
m += mirror_prefix(p)
|
||||
m += fix_mirror
|
||||
|
||||
body = attrs(["name", xml_name, "ref", ref, "type", type_,
|
||||
"minOccurs", min_occurs, "maxOccurs", max_occurs,
|
||||
"nillable", nill, "default", def_out, "fixed", fix_out])
|
||||
|
||||
if anon is not None:
|
||||
X(f"{indent}<xs:element{body}{m}>")
|
||||
if anon.get(f"{{{XSI_NS}}}type") == "ObjectType":
|
||||
anon_base = convert_qname(anon, anon.get("base"))
|
||||
if anon_base:
|
||||
X(f"{indent}\t<xs:complexType{complex_type_attrs(anon)}>")
|
||||
X(f"{indent}\t\t<xs:complexContent>")
|
||||
X(f'{indent}\t\t\t<xs:extension{attrs(["base", anon_base])}>')
|
||||
emit_complex_type_body(anon, indent + "\t\t\t\t")
|
||||
X(f"{indent}\t\t\t</xs:extension>")
|
||||
X(f"{indent}\t\t</xs:complexContent>")
|
||||
X(f"{indent}\t</xs:complexType>")
|
||||
else:
|
||||
X(f"{indent}\t<xs:complexType{complex_type_attrs(anon)}>")
|
||||
emit_complex_type_body(anon, indent + "\t\t")
|
||||
X(f"{indent}\t</xs:complexType>")
|
||||
else:
|
||||
X(f"{indent}\t<xs:simpleType>")
|
||||
emit_simple_type_body(anon, indent + "\t\t")
|
||||
X(f"{indent}\t</xs:simpleType>")
|
||||
X(f"{indent}</xs:element>")
|
||||
else:
|
||||
X(f"{indent}<xs:element{body}{m}/>")
|
||||
|
||||
|
||||
# ── complex type body ────────────────────────────────────────
|
||||
|
||||
def emit_complex_type_body(el, indent):
|
||||
open_ = el.get("open")
|
||||
ordered = el.get("ordered")
|
||||
|
||||
props = [c for c in el if isinstance(c.tag, str) and local(c) == "property"]
|
||||
elems, attr_props, text = [], [], None
|
||||
for p in props:
|
||||
f = prop_form(p)
|
||||
if f == "Attribute":
|
||||
attr_props.append(p)
|
||||
elif f == "Text":
|
||||
text = p
|
||||
else:
|
||||
elems.append(p)
|
||||
|
||||
if text is not None:
|
||||
t_type = convert_qname(text, text.get("type"))
|
||||
tm = ""
|
||||
t_name = text.get("name")
|
||||
if t_name != "__content":
|
||||
tm += mirror("textName", t_name)
|
||||
for extra in ("lowerBound", "upperBound", "nillable"):
|
||||
v = text.get(extra)
|
||||
if v is not None:
|
||||
tm += mirror("text" + extra, v)
|
||||
X(f"{indent}<xs:simpleContent>")
|
||||
X(f'{indent}\t<xs:extension{attrs(["base", t_type])}{tm}>')
|
||||
for a in attr_props:
|
||||
emit_property(a, indent + "\t\t")
|
||||
X(f"{indent}\t</xs:extension>")
|
||||
X(f"{indent}</xs:simpleContent>")
|
||||
return
|
||||
|
||||
particle_tag = "xs:choice" if ordered == "false" else "xs:sequence"
|
||||
if elems or open_ == "true":
|
||||
X(f"{indent}<{particle_tag}>")
|
||||
for e in elems:
|
||||
emit_property(e, indent + "\t")
|
||||
if open_ == "true":
|
||||
X(f'{indent}\t<xs:any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>')
|
||||
X(f"{indent}</{particle_tag}>")
|
||||
|
||||
for a in attr_props:
|
||||
emit_property(a, indent)
|
||||
if open_ == "true":
|
||||
X(f'{indent}<xs:anyAttribute namespace="##any" processContents="lax"/>')
|
||||
|
||||
|
||||
def complex_type_attrs(el):
|
||||
open_ = el.get("open")
|
||||
ordered = el.get("ordered")
|
||||
sequenced = el.get("sequenced")
|
||||
abstract = el.get("abstract")
|
||||
mixed = el.get("mixed")
|
||||
|
||||
out = ""
|
||||
if abstract == "true":
|
||||
out += ' abstract="true"'
|
||||
elif abstract is not None:
|
||||
out += mirror("abstract", abstract)
|
||||
if mixed == "true":
|
||||
out += ' mixed="true"'
|
||||
elif mixed is not None:
|
||||
out += mirror("mixed", mixed)
|
||||
|
||||
# XSD требует объявлять атрибуты после частицы, поэтому исходный порядок свойств
|
||||
# восстановим как «сначала form=Attribute, потом остальные» — верно для 96.5% типов
|
||||
order, kinds = [], []
|
||||
for c in el:
|
||||
if not isinstance(c.tag, str) or local(c) != "property":
|
||||
continue
|
||||
nm = c.get("name")
|
||||
if nm is None:
|
||||
nm = "@" + (c.get("ref") or "").split(":")[-1]
|
||||
order.append(nm)
|
||||
kinds.append(0 if prop_form(c) == "Attribute" else 1)
|
||||
if len(order) > 1:
|
||||
natural = all(kinds[i] >= kinds[i - 1] for i in range(1, len(kinds)))
|
||||
if not natural:
|
||||
out += mirror("order", "|".join(order))
|
||||
|
||||
if open_ is not None and open_ != "true":
|
||||
out += mirror("open", open_)
|
||||
if ordered is not None and ordered != "false":
|
||||
out += mirror("ordered", ordered)
|
||||
if sequenced is not None:
|
||||
out += mirror("sequenced", sequenced)
|
||||
return out
|
||||
|
||||
|
||||
# ── metadata properties ──────────────────────────────────────
|
||||
|
||||
meta = None
|
||||
if md_path and os.path.exists(md_path):
|
||||
md = _parse_xml(md_path)
|
||||
props_el = md.find(f".//{{{MD_NS}}}XDTOPackage/{{{MD_NS}}}Properties")
|
||||
if props_el is not None:
|
||||
meta = {"Name": None, "Comment": None, "Synonym": []}
|
||||
n = props_el.find(f"{{{MD_NS}}}Name")
|
||||
if n is not None:
|
||||
meta["Name"] = n.text or ""
|
||||
c = props_el.find(f"{{{MD_NS}}}Comment")
|
||||
if c is not None:
|
||||
meta["Comment"] = c.text or ""
|
||||
for item in props_el.iterfind(f"{{{MD_NS}}}Synonym/{{{V8_NS}}}item"):
|
||||
lang = item.find(f"{{{V8_NS}}}lang")
|
||||
cont = item.find(f"{{{V8_NS}}}content")
|
||||
meta["Synonym"].append({
|
||||
"Lang": (lang.text or "") if lang is not None else "",
|
||||
"Content": (cont.text or "") if cont is not None else "",
|
||||
})
|
||||
|
||||
# ── emit ─────────────────────────────────────────────────────
|
||||
# Тело первым: при его генерации регистрируются все использованные пространства
|
||||
# имён, поэтому корневой элемент может объявить полную карту префиксов.
|
||||
|
||||
for node in pkg:
|
||||
if not isinstance(node.tag, str):
|
||||
continue
|
||||
ln = local(node)
|
||||
if ln == "import":
|
||||
X(f'\t<xs:import namespace="{esc(node.get("namespace"))}"/>')
|
||||
elif ln == "property":
|
||||
emit_property(node, "\t")
|
||||
elif ln == "valueType":
|
||||
X(f'\t<xs:simpleType{attrs(["name", node.get("name")])}>')
|
||||
emit_simple_type_body(node, "\t\t")
|
||||
X("\t</xs:simpleType>")
|
||||
elif ln == "objectType":
|
||||
name = node.get("name")
|
||||
base = convert_qname(node, node.get("base"))
|
||||
cta = complex_type_attrs(node)
|
||||
if base:
|
||||
X(f'\t<xs:complexType{attrs(["name", name])}{cta}>')
|
||||
X("\t\t<xs:complexContent>")
|
||||
X(f'\t\t\t<xs:extension{attrs(["base", base])}>')
|
||||
emit_complex_type_body(node, "\t\t\t\t")
|
||||
X("\t\t\t</xs:extension>")
|
||||
X("\t\t</xs:complexContent>")
|
||||
X("\t</xs:complexType>")
|
||||
else:
|
||||
has_body = any(isinstance(c.tag, str) for c in node)
|
||||
if not has_body and node.get("open") != "true":
|
||||
X(f'\t<xs:complexType{attrs(["name", name])}{cta}/>')
|
||||
else:
|
||||
X(f'\t<xs:complexType{attrs(["name", name])}{cta}>')
|
||||
emit_complex_type_body(node, "\t\t")
|
||||
X("\t</xs:complexType>")
|
||||
|
||||
body_lines = lines
|
||||
lines = []
|
||||
|
||||
# ── schema element ───────────────────────────────────────────
|
||||
|
||||
ns_decls = ""
|
||||
for uri, px in sorted(ns_prefix.items(), key=lambda kv: kv[1]):
|
||||
ns_decls += f' xmlns:{px}="{esc(uri)}"'
|
||||
if state["uses_xdto"]:
|
||||
ns_decls += f' xmlns:xdto="{XDTO_NS}"'
|
||||
|
||||
schema_attrs = ""
|
||||
efq = pkg.get("elementFormQualified")
|
||||
afq = pkg.get("attributeFormQualified")
|
||||
if efq is not None:
|
||||
schema_attrs += f' elementFormDefault="{"qualified" if efq == "true" else "unqualified"}"'
|
||||
if afq is not None:
|
||||
schema_attrs += f' attributeFormDefault="{"qualified" if afq == "true" else "unqualified"}"'
|
||||
|
||||
X(f'<xs:schema{ns_decls}{attrs(["targetNamespace", target_ns])}{schema_attrs}>')
|
||||
|
||||
if meta:
|
||||
X("\t<xs:annotation>")
|
||||
X("\t\t<xs:appinfo>")
|
||||
X(f'\t\t\t<xdto:package xmlns:xdto="{XDTO_NS}">')
|
||||
if meta["Name"] is not None:
|
||||
X(f'\t\t\t\t<xdto:name>{esc_text(meta["Name"])}</xdto:name>')
|
||||
if meta["Comment"]:
|
||||
X(f'\t\t\t\t<xdto:comment>{esc_text(meta["Comment"])}</xdto:comment>')
|
||||
for s in meta["Synonym"]:
|
||||
X(f'\t\t\t\t<xdto:synonym lang="{esc(s["Lang"])}">{esc_text(s["Content"])}</xdto:synonym>')
|
||||
X("\t\t\t</xdto:package>")
|
||||
X("\t\t</xs:appinfo>")
|
||||
X("\t</xs:annotation>")
|
||||
|
||||
lines.extend(body_lines)
|
||||
X("</xs:schema>")
|
||||
|
||||
text = "\r\n".join(lines) + "\r\n"
|
||||
|
||||
if args.OutFile:
|
||||
d = os.path.dirname(args.OutFile)
|
||||
if d and not os.path.isdir(d):
|
||||
os.makedirs(d, exist_ok=True)
|
||||
with open(args.OutFile, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf" + text.encode("utf-8"))
|
||||
print(f"✓ XSD записана: {args.OutFile}")
|
||||
print(f" targetNamespace: {target_ns}")
|
||||
else:
|
||||
sys.stdout.write(text)
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
name: xdto-edit
|
||||
description: Точечное редактирование пакета XDTO 1С. Используй когда нужно добавить, изменить или удалить тип или свойство в существующем пакете, переименовать пакет, сменить пространство имён
|
||||
argument-hint: <PackagePath> -Operation <операция> [-Target <путь>] [-Value <значение>] [-NoValidate]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /xdto-edit — Точечное редактирование пакета XDTO
|
||||
|
||||
Меняет один элемент пакета, не требуя читать и переписывать всю схему — для больших
|
||||
пакетов (`EnterpriseData` — около мегабайта) это единственный практичный путь.
|
||||
|
||||
Если нужно переработать схему целиком или сперва разобраться, как она устроена, —
|
||||
`/xdto-decompile` → правка XSD → `/xdto-compile -Force`.
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обязательный | Описание |
|
||||
|----------|--------------|----------|
|
||||
| `PackagePath` | да | Каталог пакета, `Ext/Package.bin` или `<Имя>.xml`. Псевдоним — `-Path` |
|
||||
| `Operation` | да | Операция из таблицы ниже |
|
||||
| `Target` | зависит | Адрес: имя типа или путь `Тип.Свойство` |
|
||||
| `Value` | зависит | Фрагмент XSD, литерал, URI или текст. `@путь` — взять содержимое из файла |
|
||||
| `NoValidate` | нет | Не запускать `xdto-validate` после правки |
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-edit.ps1" -PackagePath "<путь>" -Operation <op> -Target "<адрес>" -Value "<значение>"
|
||||
```
|
||||
|
||||
## Операции
|
||||
|
||||
| Операция | `-Target` | `-Value` |
|
||||
|---|---|---|
|
||||
| `add-property` | имя типа | `<xs:element>` или `<xs:attribute>` |
|
||||
| `replace-property` | `Тип.Свойство` | новое объявление целиком |
|
||||
| `remove-property` | `Тип.Свойство` | — |
|
||||
| `add-type` | — | `<xs:complexType>` или `<xs:simpleType>` |
|
||||
| `remove-type` | имя типа | — |
|
||||
| `add-enum` | имя типа значения | литерал |
|
||||
| `add-import` | — | URI пространства имён |
|
||||
| `rename` | — | новое имя объекта метаданных |
|
||||
| `set-synonym` | — | синоним |
|
||||
| `set-comment` | — | комментарий |
|
||||
| `set-namespace` | — | новый URI пространства имён |
|
||||
|
||||
Батч через `;;` там, где перечисление осмысленно: `remove-property`, `remove-type`,
|
||||
`add-enum`, `add-import`.
|
||||
|
||||
```powershell
|
||||
... -Operation add-property -Target "Платёж" -Value '<xs:element name="Комментарий" type="xs:string" minOccurs="0"/>'
|
||||
... -Operation remove-property -Target "Платёж.Комментарий ;; Платёж.Черновик"
|
||||
... -Operation add-enum -Target "ВидДокумента" -Value "Инкассо ;; Аккредитив"
|
||||
... -Operation rename -Value ОбменСБанком
|
||||
```
|
||||
|
||||
Содержимое всегда описывается фрагментом XML-схемы — тем же языком, что и в
|
||||
`/xdto-compile`. Отдельных параметров вида `-MinOccurs` нет: чтобы поменять свойство,
|
||||
дай его новое объявление целиком через `replace-property`.
|
||||
|
||||
Ограничение длины и прочие фасеты задаются вложенным типом:
|
||||
|
||||
```xml
|
||||
<xs:element name="Комментарий" minOccurs="0">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string"><xs:maxLength value="200"/></xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
```
|
||||
|
||||
Многострочный фрагмент передавай файлом: `-Value "@frag.xsd"`. Инлайн через оболочку
|
||||
надёжен только для однострочных фрагментов без вложенных кавычек.
|
||||
|
||||
## Адресация
|
||||
|
||||
Путь `Тип.Свойство`. Точка безопасна: имена типов и свойств — идентификаторы 1С.
|
||||
Путь продолжается внутрь встроенных типов: `ПлатежныйДокумент.ДатаСписано.ИдПлатежа`.
|
||||
|
||||
Посмотреть, что есть в пакете и как называется нужный тип, — `/xdto-info`.
|
||||
Перед правкой типа полезно `/xdto-info -Mode used-by -Name <Тип>`: покажет,
|
||||
кого затронет изменение.
|
||||
|
||||
## Что тянет за собой переименование и смена namespace
|
||||
|
||||
`rename` меняет имя в объекте метаданных, переименовывает файл `<Имя>.xml` и каталог
|
||||
`<Имя>/`, правит регистрацию в `Configuration.xml`. Новое имя проверяется на
|
||||
допустимость как идентификатор 1С и на занятость.
|
||||
|
||||
`set-namespace` меняет `targetNamespace`, все внутренние ссылки на собственные типы
|
||||
и `<Namespace>` объекта метаданных. Пакеты, импортирующие старое пространство имён,
|
||||
**не изменяются** — при версионировании они и должны продолжать смотреть на прежнее.
|
||||
Навык их перечислит; если правка не версионная, поправь их импорты сам.
|
||||
|
||||
После правки автоматически запускается `/xdto-validate` — отключается через `-NoValidate`.
|
||||
|
||||
## Типичный workflow
|
||||
|
||||
1. `/xdto-info <пакет>` — найти нужный тип
|
||||
2. `/xdto-info <пакет> -Mode used-by -Name <Тип>` — если меняешь существующее
|
||||
3. `/xdto-edit <пакет> -Operation <op> …`
|
||||
4. `/db-load-xml` + `/db-update`
|
||||
@@ -0,0 +1,562 @@
|
||||
# xdto-edit v1.0 — Point edits of a 1C XDTO package
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[Alias('Path')]
|
||||
[string]$PackagePath,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ValidateSet("add-property", "replace-property", "remove-property",
|
||||
"add-type", "remove-type", "add-enum", "add-import",
|
||||
"rename", "set-synonym", "set-comment", "set-namespace")]
|
||||
[string]$Operation,
|
||||
[string]$Target,
|
||||
[string]$Value,
|
||||
[switch]$NoValidate
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$XDTO_NS = "http://v8.1c.ru/8.1/xdto"
|
||||
$XS_NS = "http://www.w3.org/2001/XMLSchema"
|
||||
$MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
||||
$V8_NS = "http://v8.1c.ru/8.1/data/core"
|
||||
|
||||
# --- Support guard (Ext/ParentConfigurations.bin) ---
|
||||
# See docs/1c-support-state-spec.md. Blocks edits of vendor objects "на замке" /
|
||||
# read-only configs unless allowed. Trigger = bin present; reaction from
|
||||
# .v8-project.json editingAllowedCheck (deny|warn|off, default deny). Never
|
||||
# throws — guard errors degrade to allow.
|
||||
function Test-ExternalObjectRoot([string]$xmlPath) {
|
||||
if (-not (Test-Path $xmlPath)) { return $false }
|
||||
try {
|
||||
[xml]$mx = Get-Content -Path $xmlPath -Encoding UTF8
|
||||
$el = $mx.DocumentElement.FirstChild
|
||||
while ($el -and $el.NodeType -ne 'Element') { $el = $el.NextSibling }
|
||||
if ($el) { return @('ExternalDataProcessor','ExternalReport') -contains $el.get_LocalName() }
|
||||
} catch {}
|
||||
return $false
|
||||
}
|
||||
function Find-V8Project([string]$startDir) {
|
||||
$d = $startDir
|
||||
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||
$pj = Join-Path $d ".v8-project.json"
|
||||
if (Test-Path $pj) { return $pj }
|
||||
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||
if ($parent -eq $d) { break }
|
||||
$d = $parent
|
||||
}
|
||||
return $null
|
||||
}
|
||||
function Get-EditMode([string]$cfgDir) {
|
||||
$mode = "deny"
|
||||
try {
|
||||
$pj = Find-V8Project $cfgDir
|
||||
if ($pj) {
|
||||
$cfg = Get-Content -Path $pj -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
if ($cfg.PSObject.Properties.Name -contains 'editingAllowedCheck' -and $cfg.editingAllowedCheck) {
|
||||
$mode = [string]$cfg.editingAllowedCheck
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
return $mode
|
||||
}
|
||||
function Assert-EditAllowed([string]$targetPath) {
|
||||
try {
|
||||
$d = $targetPath
|
||||
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||||
foreach ($x in @(Get-ChildItem -Path $d -Filter "*.xml" -File -ErrorAction SilentlyContinue)) {
|
||||
if (Test-ExternalObjectRoot $x.FullName) { return }
|
||||
}
|
||||
$cfgXml = Join-Path $d "Configuration.xml"
|
||||
$supportBin = Join-Path (Join-Path $d "Ext") "ParentConfigurations.bin"
|
||||
if (Test-Path $cfgXml) {
|
||||
if (Test-Path $supportBin) {
|
||||
$mode = Get-EditMode $d
|
||||
if ($mode -eq "off") { return }
|
||||
$msg = "Конфигурация находится на поддержке (Ext/ParentConfigurations.bin). Правка может быть запрещена."
|
||||
if ($mode -eq "warn") { Write-Warning $msg; return }
|
||||
throw "$msg Снимите с поддержки (/support-edit) или задайте editingAllowedCheck в .v8-project.json."
|
||||
}
|
||||
return
|
||||
}
|
||||
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||
if ($parent -eq $d) { break }
|
||||
$d = $parent
|
||||
}
|
||||
} catch [System.Management.Automation.RuntimeException] {
|
||||
throw
|
||||
} catch {}
|
||||
}
|
||||
|
||||
# --- Resolve package ------------------------------------------------------------
|
||||
|
||||
if (-not [System.IO.Path]::IsPathRooted($PackagePath)) {
|
||||
$PackagePath = Join-Path (Get-Location).Path $PackagePath
|
||||
}
|
||||
|
||||
$pkgDir = $null
|
||||
if (Test-Path $PackagePath -PathType Container) {
|
||||
if (Test-Path (Join-Path (Join-Path $PackagePath "Ext") "Package.bin")) { $pkgDir = $PackagePath }
|
||||
} elseif ((Test-Path $PackagePath -PathType Leaf) -and ([System.IO.Path]::GetFileName($PackagePath) -eq "Package.bin")) {
|
||||
$pkgDir = Split-Path (Split-Path $PackagePath -Parent) -Parent
|
||||
} elseif ($PackagePath.EndsWith(".xml")) {
|
||||
$stem = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($PackagePath),
|
||||
[System.IO.Path]::GetFileNameWithoutExtension($PackagePath))
|
||||
if (Test-Path (Join-Path (Join-Path $stem "Ext") "Package.bin")) { $pkgDir = $stem }
|
||||
}
|
||||
if (-not $pkgDir) { throw "Не найден пакет XDTO по пути: $PackagePath" }
|
||||
|
||||
$pkgName = [System.IO.Path]::GetFileName($pkgDir)
|
||||
$xdtoRoot = Split-Path $pkgDir -Parent
|
||||
$configRoot = Split-Path $xdtoRoot -Parent
|
||||
$binFile = Join-Path (Join-Path $pkgDir "Ext") "Package.bin"
|
||||
$mdFile = Join-Path $xdtoRoot "$pkgName.xml"
|
||||
$configXml = Join-Path $configRoot "Configuration.xml"
|
||||
|
||||
# -Value "@путь" — содержимое берётся из файла. Передавать XSD-фрагмент инлайном
|
||||
# через powershell.exe -File ненадёжно: вложенные кавычки схлопываются на границе
|
||||
# процессов, и вместо понятной ошибки получается сырой сбой разбора XML.
|
||||
if ($Value -and $Value.StartsWith("@")) {
|
||||
$valueFile = $Value.Substring(1)
|
||||
if (-not [System.IO.Path]::IsPathRooted($valueFile)) {
|
||||
$valueFile = Join-Path (Get-Location).Path $valueFile
|
||||
}
|
||||
if (-not (Test-Path $valueFile -PathType Leaf)) { throw "Файл значения не найден: $valueFile" }
|
||||
$Value = [System.IO.File]::ReadAllText($valueFile).Trim()
|
||||
}
|
||||
|
||||
Assert-EditAllowed $pkgDir
|
||||
|
||||
$encBom = New-Object System.Text.UTF8Encoding($true)
|
||||
|
||||
# --- Sibling skills -------------------------------------------------------------
|
||||
# Правка идёт через уже проверенный round-trip: пакет выгружается в XSD, операция
|
||||
# применяется к схеме, пакет собирается обратно. Второго эмиттера не заводим —
|
||||
# байт-точность для всего нетронутого достаётся от компилятора.
|
||||
|
||||
$decompileScript = Join-Path (Join-Path $PSScriptRoot "..\..\xdto-decompile") "scripts\xdto-decompile.ps1"
|
||||
$compileScript = Join-Path (Join-Path $PSScriptRoot "..\..\xdto-compile") "scripts\xdto-compile.ps1"
|
||||
$validateScript = Join-Path (Join-Path $PSScriptRoot "..\..\xdto-validate") "scripts\xdto-validate.ps1"
|
||||
|
||||
# Исключение из автономности навыков, сделанное осознанно: конвертер XSD ↔ модель
|
||||
# нельзя скопировать буквально (xdto-compile — скрипт со сквозным потоком, не библиотека),
|
||||
# а вторая его реализация разошлась бы с первой. Обещание «правка не меняет ни байта
|
||||
# в нетронутом» держится именно на том, что код тот же самый.
|
||||
# Проверяем комплектность заранее, чтобы не падать на середине правки.
|
||||
function Assert-SiblingsPresent([string]$operation) {
|
||||
$needed = @{}
|
||||
if (@("rename", "set-synonym", "set-comment") -notcontains $operation) {
|
||||
$needed["xdto-decompile"] = $decompileScript
|
||||
$needed["xdto-compile"] = $compileScript
|
||||
}
|
||||
$missing = @()
|
||||
foreach ($k in $needed.Keys) { if (-not (Test-Path $needed[$k])) { $missing += $k } }
|
||||
if ($missing.Count -gt 0) {
|
||||
$skillsRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
throw ("Навык неработоспособен: рядом нет " + ($missing -join ", ") + ".`n" +
|
||||
"Операция `"$operation`" выполняется через " +
|
||||
$(if ($missing.Count -gt 1) { "них" } else { "него" }) + ".`n" +
|
||||
"Ожидаются в каталоге навыков: $skillsRoot")
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-Sibling([string]$script, [string[]]$argList, [string]$what) {
|
||||
if (-not (Test-Path $script)) { throw "Не найден навык $what по пути: $script" }
|
||||
$out = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script @argList 2>&1
|
||||
if ($LASTEXITCODE -ne 0) { throw "$what завершился с ошибкой:`n$($out -join "`n")" }
|
||||
return $out
|
||||
}
|
||||
|
||||
# --- Metadata object edits ------------------------------------------------------
|
||||
|
||||
function Edit-Metadata([string]$field, [string]$newValue) {
|
||||
$doc = New-Object System.Xml.XmlDocument
|
||||
$doc.PreserveWhitespace = $true
|
||||
$doc.Load($mdFile)
|
||||
$nsm = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
|
||||
$nsm.AddNamespace("md", $MD_NS)
|
||||
$nsm.AddNamespace("v8", $V8_NS)
|
||||
$props = $doc.SelectSingleNode("//md:XDTOPackage/md:Properties", $nsm)
|
||||
if (-not $props) { throw "В объекте метаданных не найден блок <Properties>" }
|
||||
|
||||
switch ($field) {
|
||||
"Name" {
|
||||
$n = $props.SelectSingleNode("md:Name", $nsm)
|
||||
if (-not $n) { throw "В объекте метаданных нет <Name>" }
|
||||
$n.InnerText = $newValue
|
||||
}
|
||||
"Comment" {
|
||||
$c = $props.SelectSingleNode("md:Comment", $nsm)
|
||||
if (-not $c) {
|
||||
$c = $doc.CreateElement("Comment", $MD_NS)
|
||||
$props.AppendChild($c) | Out-Null
|
||||
}
|
||||
$c.InnerText = $newValue
|
||||
}
|
||||
"Namespace" {
|
||||
$ns = $props.SelectSingleNode("md:Namespace", $nsm)
|
||||
if (-not $ns) { throw "В объекте метаданных нет <Namespace>" }
|
||||
$ns.InnerText = $newValue
|
||||
}
|
||||
"Synonym" {
|
||||
$syn = $props.SelectSingleNode("md:Synonym", $nsm)
|
||||
if (-not $syn) {
|
||||
$syn = $doc.CreateElement("Synonym", $MD_NS)
|
||||
$props.AppendChild($syn) | Out-Null
|
||||
}
|
||||
$item = $syn.SelectSingleNode("v8:item[v8:lang='ru']", $nsm)
|
||||
if (-not $item) {
|
||||
$item = $doc.CreateElement("v8", "item", $V8_NS)
|
||||
$lang = $doc.CreateElement("v8", "lang", $V8_NS); $lang.InnerText = "ru"
|
||||
$cont = $doc.CreateElement("v8", "content", $V8_NS)
|
||||
$item.AppendChild($lang) | Out-Null
|
||||
$item.AppendChild($cont) | Out-Null
|
||||
$syn.AppendChild($item) | Out-Null
|
||||
}
|
||||
$item.SelectSingleNode("v8:content", $nsm).InnerText = $newValue
|
||||
}
|
||||
}
|
||||
|
||||
$settings = New-Object System.Xml.XmlWriterSettings
|
||||
$settings.Encoding = $encBom
|
||||
$settings.Indent = $false
|
||||
$stream = New-Object System.IO.FileStream($mdFile, [System.IO.FileMode]::Create)
|
||||
$writer = [System.Xml.XmlWriter]::Create($stream, $settings)
|
||||
$doc.Save($writer)
|
||||
$writer.Close(); $stream.Close()
|
||||
}
|
||||
|
||||
function Rename-Package([string]$newName) {
|
||||
if ($newName -notmatch '^[\wЀ-ӿ]+$' -or $newName -match '^\d') {
|
||||
throw "`"$newName`" не является допустимым идентификатором 1С"
|
||||
}
|
||||
$newMd = Join-Path $xdtoRoot "$newName.xml"
|
||||
$newDir = Join-Path $xdtoRoot $newName
|
||||
if ((Test-Path $newMd) -or (Test-Path $newDir)) { throw "Имя `"$newName`" уже занято другим пакетом" }
|
||||
|
||||
Edit-Metadata "Name" $newName
|
||||
Move-Item $mdFile $newMd
|
||||
Move-Item $pkgDir $newDir
|
||||
|
||||
if (Test-Path $configXml) {
|
||||
$cfg = New-Object System.Xml.XmlDocument
|
||||
$cfg.PreserveWhitespace = $true
|
||||
$cfg.Load($configXml)
|
||||
$nsm = New-Object System.Xml.XmlNamespaceManager($cfg.NameTable)
|
||||
$nsm.AddNamespace("md", $MD_NS)
|
||||
$found = $false
|
||||
foreach ($e in $cfg.SelectNodes("//md:Configuration/md:ChildObjects/md:XDTOPackage", $nsm)) {
|
||||
if ($e.InnerText -eq $pkgName) { $e.InnerText = $newName; $found = $true; break }
|
||||
}
|
||||
if ($found) {
|
||||
$s = New-Object System.Xml.XmlWriterSettings
|
||||
$s.Encoding = $encBom; $s.Indent = $false
|
||||
$st = New-Object System.IO.FileStream($configXml, [System.IO.FileMode]::Create)
|
||||
$w = [System.Xml.XmlWriter]::Create($st, $s)
|
||||
$cfg.Save($w); $w.Close(); $st.Close()
|
||||
Write-Host " Configuration.xml: <XDTOPackage> переименован в $newName"
|
||||
} else {
|
||||
Write-Warning "В Configuration.xml не найдена запись <XDTOPackage>$pkgName</XDTOPackage> — зарегистрируйте пакет вручную"
|
||||
}
|
||||
}
|
||||
Write-Host "✓ Пакет переименован: $pkgName → $newName"
|
||||
Write-Host " Перемещены: $newName.xml, $newName/"
|
||||
}
|
||||
|
||||
# --- Model edits through the XSD round-trip -------------------------------------
|
||||
|
||||
$XSD_DECL = @{ "add-property" = "element"; "replace-property" = "element"; "remove-property" = "element" }
|
||||
|
||||
function Get-SchemaChildren([System.Xml.XmlElement]$el, [string]$local) {
|
||||
$res = New-Object System.Collections.ArrayList
|
||||
foreach ($c in $el.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.NamespaceURI -eq $XS_NS -and $c.get_LocalName() -eq $local) { [void]$res.Add($c) }
|
||||
}
|
||||
return ,$res
|
||||
}
|
||||
function Get-SchemaFirst([System.Xml.XmlElement]$el, [string]$local) {
|
||||
$r = Get-SchemaChildren $el $local
|
||||
if ($r.Count -gt 0) { return $r[0] }
|
||||
return $null
|
||||
}
|
||||
|
||||
function Find-TypeElement($schema, [string]$typeName) {
|
||||
foreach ($kind in @("complexType", "simpleType")) {
|
||||
foreach ($t in (Get-SchemaChildren $schema $kind)) {
|
||||
if ($t.GetAttribute("name") -eq $typeName) { return $t }
|
||||
}
|
||||
}
|
||||
throw "В пакете нет типа `"$typeName`""
|
||||
}
|
||||
|
||||
# Тело типа: внутрь xs:complexContent/xs:extension, если тип наследуется
|
||||
function Get-TypeBody([System.Xml.XmlElement]$ct) {
|
||||
$content = Get-SchemaFirst $ct "complexContent"
|
||||
if ($content) {
|
||||
$ext = Get-SchemaFirst $content "extension"
|
||||
if ($ext) { return $ext }
|
||||
}
|
||||
return $ct
|
||||
}
|
||||
|
||||
function Find-Declaration([System.Xml.XmlElement]$body, [string]$propName) {
|
||||
foreach ($node in $body.SelectNodes(".//*")) {
|
||||
if ($node.NamespaceURI -ne $XS_NS) { continue }
|
||||
if (@("element", "attribute") -notcontains $node.get_LocalName()) { continue }
|
||||
if ($node.GetAttribute("name") -eq $propName) { return $node }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
# Путь Тип.Свойство[.Вложенное...] — точка безопасна: имена в модели XDTO
|
||||
# являются идентификаторами 1С и точку содержать не могут
|
||||
function Resolve-Path($schema, [string]$path) {
|
||||
$segments = $path.Split(".")
|
||||
$typeEl = Find-TypeElement $schema $segments[0]
|
||||
if ($segments.Count -eq 1) { return [pscustomobject]@{ Type = $typeEl; Decl = $null } }
|
||||
|
||||
$body = Get-TypeBody $typeEl
|
||||
$decl = $null
|
||||
for ($i = 1; $i -lt $segments.Count; $i++) {
|
||||
$decl = Find-Declaration $body $segments[$i]
|
||||
if (-not $decl) { throw "По пути `"$path`" не найдено свойство `"$($segments[$i])`"" }
|
||||
if ($i -lt $segments.Count - 1) {
|
||||
$inner = Get-SchemaFirst $decl "complexType"
|
||||
if (-not $inner) { throw "Свойство `"$($segments[$i])`" не содержит вложенного типа — путь дальше не идёт" }
|
||||
$body = Get-TypeBody $inner
|
||||
}
|
||||
}
|
||||
return [pscustomobject]@{ Type = $typeEl; Decl = $decl }
|
||||
}
|
||||
|
||||
function Import-Fragment($schema, [string]$xml) {
|
||||
$tmp = New-Object System.Xml.XmlDocument
|
||||
$nsAttrs = " xmlns:xs=`"$XS_NS`" xmlns:xdto=`"$XDTO_NS`""
|
||||
$tns = $schema.GetAttribute("targetNamespace")
|
||||
if ($tns) { $nsAttrs += " xmlns:tns=`"$tns`"" }
|
||||
foreach ($a in $schema.Attributes) {
|
||||
if ($a.Prefix -eq "xmlns" -and $a.get_LocalName() -notin @("xs", "xdto", "tns")) {
|
||||
$nsAttrs += " xmlns:$($a.get_LocalName())=`"$($a.Value)`""
|
||||
}
|
||||
}
|
||||
try { $tmp.LoadXml("<wrap$nsAttrs>$xml</wrap>") }
|
||||
catch {
|
||||
throw ("Не удалось разобрать -Value как фрагмент XML-схемы: " + $_.Exception.InnerException.Message + "`n" +
|
||||
"Получено: " + $xml + "`n" +
|
||||
"Если фрагмент передан инлайном, кавычки могли схлопнуться на границе процессов — " +
|
||||
"положите его в файл и укажите -Value `"@путь`".")
|
||||
}
|
||||
$res = New-Object System.Collections.ArrayList
|
||||
foreach ($c in $tmp.DocumentElement.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element) {
|
||||
[void]$res.Add($schema.OwnerDocument.ImportNode($c, $true))
|
||||
}
|
||||
}
|
||||
if ($res.Count -eq 0) { throw "Во фрагменте нет ни одного элемента: $xml" }
|
||||
return ,$res
|
||||
}
|
||||
|
||||
function Apply-ModelOperation($schema) {
|
||||
switch ($Operation) {
|
||||
|
||||
"add-property" {
|
||||
if (-not $Target) { throw "add-property требует -Target <Тип>" }
|
||||
$loc = Resolve-Path $schema $Target
|
||||
$body = Get-TypeBody $(if ($loc.Decl) { Get-SchemaFirst $loc.Decl "complexType" } else { $loc.Type })
|
||||
foreach ($frag in (Import-Fragment $schema $Value)) {
|
||||
$kind = $frag.get_LocalName()
|
||||
if ($kind -eq "attribute") {
|
||||
$body.AppendChild($frag) | Out-Null
|
||||
} elseif ($kind -eq "element") {
|
||||
$particle = Get-SchemaFirst $body "sequence"
|
||||
if (-not $particle) { $particle = Get-SchemaFirst $body "choice" }
|
||||
if (-not $particle) { $particle = Get-SchemaFirst $body "all" }
|
||||
if (-not $particle) {
|
||||
$particle = $schema.OwnerDocument.CreateElement("xs", "sequence", $XS_NS)
|
||||
$firstAttr = Get-SchemaFirst $body "attribute"
|
||||
if ($firstAttr) { $body.InsertBefore($particle, $firstAttr) | Out-Null }
|
||||
else { $body.AppendChild($particle) | Out-Null }
|
||||
}
|
||||
$particle.AppendChild($frag) | Out-Null
|
||||
} else {
|
||||
throw "add-property ожидает <xs:element> или <xs:attribute>, получен <xs:$kind>"
|
||||
}
|
||||
Write-Host " + $($frag.GetAttribute('name')) в тип $Target"
|
||||
}
|
||||
}
|
||||
|
||||
"replace-property" {
|
||||
if (-not $Target) { throw "replace-property требует -Target `"Тип.Свойство`"" }
|
||||
$loc = Resolve-Path $schema $Target
|
||||
if (-not $loc.Decl) { throw "replace-property требует путь вида `"Тип.Свойство`"" }
|
||||
$frags = Import-Fragment $schema $Value
|
||||
if ($frags.Count -ne 1) { throw "replace-property ожидает ровно одно объявление" }
|
||||
$loc.Decl.ParentNode.ReplaceChild($frags[0], $loc.Decl) | Out-Null
|
||||
Write-Host " ~ $Target заменено"
|
||||
}
|
||||
|
||||
"remove-property" {
|
||||
if (-not $Target) { throw "remove-property требует путь `"Тип.Свойство`"" }
|
||||
foreach ($one in ($Target -split "\s*;;\s*")) {
|
||||
if (-not $one) { continue }
|
||||
$loc = Resolve-Path $schema $one
|
||||
if (-not $loc.Decl) { throw "remove-property требует путь вида `"Тип.Свойство`", получено `"$one`"" }
|
||||
$loc.Decl.ParentNode.RemoveChild($loc.Decl) | Out-Null
|
||||
Write-Host " − $one удалено"
|
||||
}
|
||||
}
|
||||
|
||||
"add-type" {
|
||||
foreach ($frag in (Import-Fragment $schema $Value)) {
|
||||
if (@("complexType", "simpleType") -notcontains $frag.get_LocalName()) {
|
||||
throw "add-type ожидает <xs:complexType> или <xs:simpleType>, получен <xs:$($frag.get_LocalName())>"
|
||||
}
|
||||
$schema.AppendChild($frag) | Out-Null
|
||||
Write-Host " + тип $($frag.GetAttribute('name'))"
|
||||
}
|
||||
}
|
||||
|
||||
"remove-type" {
|
||||
if (-not $Target) { throw "remove-type требует -Target <Тип>" }
|
||||
foreach ($one in ($Target -split "\s*;;\s*")) {
|
||||
if (-not $one) { continue }
|
||||
$t = Find-TypeElement $schema $one
|
||||
$t.ParentNode.RemoveChild($t) | Out-Null
|
||||
Write-Host " − тип $one удалён"
|
||||
}
|
||||
}
|
||||
|
||||
"add-enum" {
|
||||
if (-not $Target) { throw "add-enum требует -Target <ТипЗначения>" }
|
||||
$t = Find-TypeElement $schema $Target
|
||||
$restriction = Get-SchemaFirst $t "restriction"
|
||||
if (-not $restriction) { throw "Тип `"$Target`" не является ограничением простого типа" }
|
||||
foreach ($lit in ($Value -split "\s*;;\s*")) {
|
||||
if (-not $lit) { continue }
|
||||
$e = $schema.OwnerDocument.CreateElement("xs", "enumeration", $XS_NS)
|
||||
$e.SetAttribute("value", $lit)
|
||||
$restriction.AppendChild($e) | Out-Null
|
||||
Write-Host " + значение `"$lit`" в тип $Target"
|
||||
}
|
||||
}
|
||||
|
||||
"add-import" {
|
||||
foreach ($uri in ($Value -split "\s*;;\s*")) {
|
||||
if (-not $uri) { continue }
|
||||
$exists = $false
|
||||
foreach ($i in (Get-SchemaChildren $schema "import")) {
|
||||
if ($i.GetAttribute("namespace") -eq $uri) { $exists = $true; break }
|
||||
}
|
||||
if ($exists) { Write-Host " = импорт $uri уже объявлен"; continue }
|
||||
$imp = $schema.OwnerDocument.CreateElement("xs", "import", $XS_NS)
|
||||
$imp.SetAttribute("namespace", $uri)
|
||||
$firstOther = $null
|
||||
foreach ($c in $schema.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -notin @("annotation", "import")) { $firstOther = $c; break }
|
||||
}
|
||||
if ($firstOther) { $schema.InsertBefore($imp, $firstOther) | Out-Null } else { $schema.AppendChild($imp) | Out-Null }
|
||||
Write-Host " + импорт $uri"
|
||||
}
|
||||
}
|
||||
|
||||
"set-namespace" {
|
||||
if (-not $Value) { throw "set-namespace требует -Value <URI>" }
|
||||
$old = $schema.GetAttribute("targetNamespace")
|
||||
# Установка того же значения не отбрасывается: пакет пересобирается вхолостую,
|
||||
# и это заодно проба точности пути «выгрузить → собрать» на любом пакете
|
||||
if ($old -eq $Value) { Write-Host " = namespace уже $Value, пакет пересобран без изменений" }
|
||||
# Меняем и targetNamespace, и объявление префикса, который на него указывал:
|
||||
# иначе ссылки на собственные типы станут ссылками в чужое пространство имён
|
||||
$schema.SetAttribute("targetNamespace", $Value)
|
||||
foreach ($a in @($schema.Attributes)) {
|
||||
if ($a.Prefix -eq "xmlns" -and $a.Value -eq $old) {
|
||||
$schema.SetAttribute("xmlns:$($a.get_LocalName())", $Value)
|
||||
}
|
||||
}
|
||||
Write-Host " ~ namespace: $old → $Value"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Dispatch -------------------------------------------------------------------
|
||||
|
||||
$metaOps = @("rename", "set-synonym", "set-comment")
|
||||
$touchesModel = ($metaOps -notcontains $Operation)
|
||||
|
||||
Assert-SiblingsPresent $Operation
|
||||
|
||||
Write-Host "Пакет: $pkgName"
|
||||
|
||||
if ($Operation -eq "rename") {
|
||||
if (-not $Value) { throw "rename требует -Value <НовоеИмя>" }
|
||||
Rename-Package $Value
|
||||
$pkgName = $Value
|
||||
$pkgDir = Join-Path $xdtoRoot $Value
|
||||
} elseif ($Operation -eq "set-synonym") {
|
||||
if (-not $Value) { throw "set-synonym требует -Value <текст>" }
|
||||
Edit-Metadata "Synonym" $Value
|
||||
Write-Host "✓ Синоним: $Value"
|
||||
} elseif ($Operation -eq "set-comment") {
|
||||
Edit-Metadata "Comment" $Value
|
||||
Write-Host "✓ Комментарий обновлён"
|
||||
} else {
|
||||
$tmpDir = Join-Path ([System.IO.Path]::GetTempPath()) ("xdto-edit_" + [guid]::NewGuid().ToString("N").Substring(0, 8))
|
||||
New-Item -ItemType Directory -Path $tmpDir -Force | Out-Null
|
||||
try {
|
||||
$xsdPath = Join-Path $tmpDir "schema.xsd"
|
||||
Invoke-Sibling $decompileScript @("-PackagePath", $binFile, "-OutFile", $xsdPath) "xdto-decompile" | Out-Null
|
||||
|
||||
$doc = New-Object System.Xml.XmlDocument
|
||||
$doc.PreserveWhitespace = $false
|
||||
$doc.Load($xsdPath)
|
||||
$schema = $doc.DocumentElement
|
||||
|
||||
$oldNamespace = $schema.GetAttribute("targetNamespace")
|
||||
Apply-ModelOperation $schema
|
||||
|
||||
$doc.Save($xsdPath)
|
||||
Invoke-Sibling $compileScript @("-XsdPath", $xsdPath, "-OutputDir", $configRoot, "-Name", $pkgName, "-Force") "xdto-compile" | Out-Null
|
||||
|
||||
if ($Operation -eq "set-namespace") {
|
||||
Edit-Metadata "Namespace" $Value
|
||||
# Зависящие пакеты не трогаем: при версионировании они обязаны продолжать
|
||||
# смотреть на прежний namespace. Но молчать о них нельзя.
|
||||
$dependents = @()
|
||||
foreach ($d in (Get-ChildItem $xdtoRoot -Directory -ErrorAction SilentlyContinue)) {
|
||||
if ($d.Name -eq $pkgName) { continue }
|
||||
$ob = Join-Path (Join-Path $d.FullName "Ext") "Package.bin"
|
||||
if (-not (Test-Path $ob)) { continue }
|
||||
try {
|
||||
$od = New-Object System.Xml.XmlDocument
|
||||
$od.Load($ob)
|
||||
foreach ($imp in $od.DocumentElement.ChildNodes) {
|
||||
if ($imp.NodeType -eq [System.Xml.XmlNodeType]::Element -and $imp.get_LocalName() -eq "import" -and
|
||||
$imp.GetAttribute("namespace") -eq $oldNamespace) { $dependents += $d.Name; break }
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
if ($dependents.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Warning ("Старый namespace импортируют пакеты ($($dependents.Count)): " + ($dependents -join ", ") +
|
||||
". Они не изменены — при версионировании это верно; если нет, поправьте их импорты.")
|
||||
}
|
||||
}
|
||||
Write-Host "✓ Пакет пересобран: XDTOPackages/$pkgName/Ext/Package.bin"
|
||||
} finally {
|
||||
Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
# --- Validate -------------------------------------------------------------------
|
||||
|
||||
if (-not $NoValidate) {
|
||||
if (Test-Path $validateScript) {
|
||||
Write-Host ""
|
||||
Write-Host "--- xdto-validate ---"
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $validateScript -PackagePath (Join-Path $xdtoRoot $pkgName)
|
||||
} else {
|
||||
Write-Host "[SKIP] xdto-validate не найден: $validateScript"
|
||||
}
|
||||
}
|
||||
exit 0
|
||||
@@ -0,0 +1,541 @@
|
||||
# xdto-edit v1.0 — Point edits of a 1C XDTO package (Python port)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import uuid
|
||||
|
||||
from lxml import etree
|
||||
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
|
||||
XDTO_NS = "http://v8.1c.ru/8.1/xdto"
|
||||
XS_NS = "http://www.w3.org/2001/XMLSchema"
|
||||
MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
||||
V8_NS = "http://v8.1c.ru/8.1/data/core"
|
||||
|
||||
OPS = ["add-property", "replace-property", "remove-property", "add-type", "remove-type",
|
||||
"add-enum", "add-import", "rename", "set-synonym", "set-comment", "set-namespace"]
|
||||
|
||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||
parser.add_argument("-PackagePath", "-Path", required=True)
|
||||
parser.add_argument("-Operation", required=True, choices=OPS)
|
||||
parser.add_argument("-Target", default="")
|
||||
parser.add_argument("-Value", default="")
|
||||
parser.add_argument("-NoValidate", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def die(msg):
|
||||
print(msg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def local(el):
|
||||
return etree.QName(el).localname
|
||||
|
||||
|
||||
def _parse_xml(source, from_string=False):
|
||||
"""Разбор с узким отступлением для не-URI пространств имён.
|
||||
|
||||
Платформа допускает в targetNamespace произвольную строку, .NET такое принимает,
|
||||
а libxml2 отвергает. Откатываемся на восстанавливающий разбор ТОЛЬКО на этой ошибке.
|
||||
"""
|
||||
try:
|
||||
return (etree.fromstring(source) if from_string else etree.parse(source))
|
||||
except etree.XMLSyntaxError as e:
|
||||
if "is not a valid URI" not in str(e):
|
||||
raise
|
||||
p = etree.XMLParser(recover=True)
|
||||
return (etree.fromstring(source, p) if from_string else etree.parse(source, p))
|
||||
|
||||
|
||||
# ── support guard ────────────────────────────────────────────
|
||||
# См. docs/1c-support-state-spec.md.
|
||||
|
||||
def find_v8_project(start_dir):
|
||||
d = os.path.abspath(start_dir)
|
||||
for _ in range(20):
|
||||
pj = os.path.join(d, ".v8-project.json")
|
||||
if os.path.exists(pj):
|
||||
return pj
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
break
|
||||
d = parent
|
||||
return None
|
||||
|
||||
|
||||
def get_edit_mode(cfg_dir):
|
||||
try:
|
||||
pj = find_v8_project(cfg_dir)
|
||||
if pj:
|
||||
with open(pj, encoding="utf-8-sig") as f:
|
||||
return str(json.load(f).get("editingAllowedCheck") or "deny")
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
return "deny"
|
||||
|
||||
|
||||
def is_external_object_root(xml_path):
|
||||
try:
|
||||
for el in _parse_xml(xml_path).getroot():
|
||||
if isinstance(el.tag, str):
|
||||
return local(el) in ("ExternalDataProcessor", "ExternalReport")
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def assert_edit_allowed(target_path):
|
||||
d = os.path.abspath(target_path)
|
||||
for _ in range(20):
|
||||
try:
|
||||
for f in os.listdir(d):
|
||||
if f.endswith(".xml") and is_external_object_root(os.path.join(d, f)):
|
||||
return
|
||||
except OSError:
|
||||
pass
|
||||
if os.path.exists(os.path.join(d, "Configuration.xml")):
|
||||
if os.path.exists(os.path.join(d, "Ext", "ParentConfigurations.bin")):
|
||||
mode = get_edit_mode(d)
|
||||
if mode == "off":
|
||||
return
|
||||
msg = ("Конфигурация находится на поддержке (Ext/ParentConfigurations.bin). "
|
||||
"Правка может быть запрещена.")
|
||||
if mode == "warn":
|
||||
print("WARNING: " + msg, file=sys.stderr)
|
||||
return
|
||||
die(msg + " Снимите с поддержки (/support-edit) или задайте "
|
||||
"editingAllowedCheck в .v8-project.json.")
|
||||
return
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
break
|
||||
d = parent
|
||||
|
||||
|
||||
# ── resolve package ──────────────────────────────────────────
|
||||
|
||||
package_path = os.path.abspath(args.PackagePath)
|
||||
pkg_dir = None
|
||||
if os.path.isdir(package_path):
|
||||
if os.path.exists(os.path.join(package_path, "Ext", "Package.bin")):
|
||||
pkg_dir = package_path
|
||||
elif os.path.isfile(package_path) and os.path.basename(package_path) == "Package.bin":
|
||||
pkg_dir = os.path.dirname(os.path.dirname(package_path))
|
||||
elif package_path.endswith(".xml"):
|
||||
stem = os.path.join(os.path.dirname(package_path),
|
||||
os.path.splitext(os.path.basename(package_path))[0])
|
||||
if os.path.exists(os.path.join(stem, "Ext", "Package.bin")):
|
||||
pkg_dir = stem
|
||||
if not pkg_dir:
|
||||
die(f"Не найден пакет XDTO по пути: {package_path}")
|
||||
|
||||
pkg_name = os.path.basename(pkg_dir.rstrip("\\/"))
|
||||
xdto_root = os.path.dirname(pkg_dir)
|
||||
config_root = os.path.dirname(xdto_root)
|
||||
bin_file = os.path.join(pkg_dir, "Ext", "Package.bin")
|
||||
md_file = os.path.join(xdto_root, pkg_name + ".xml")
|
||||
config_xml = os.path.join(config_root, "Configuration.xml")
|
||||
|
||||
# -Value "@путь" — содержимое берётся из файла. Передавать XSD-фрагмент инлайном
|
||||
# ненадёжно: вложенные кавычки схлопываются на границе процессов, и вместо понятной
|
||||
# ошибки получается сырой сбой разбора XML.
|
||||
if args.Value.startswith("@"):
|
||||
value_file = args.Value[1:]
|
||||
if not os.path.isabs(value_file):
|
||||
value_file = os.path.join(os.getcwd(), value_file)
|
||||
if not os.path.isfile(value_file):
|
||||
die("Файл значения не найден: " + value_file)
|
||||
with open(value_file, encoding="utf-8-sig") as f:
|
||||
args.Value = f.read().strip()
|
||||
|
||||
assert_edit_allowed(pkg_dir)
|
||||
|
||||
SKILLS = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
DECOMPILE = os.path.join(SKILLS, "xdto-decompile", "scripts", "xdto-decompile.py")
|
||||
COMPILE = os.path.join(SKILLS, "xdto-compile", "scripts", "xdto-compile.py")
|
||||
VALIDATE = os.path.join(SKILLS, "xdto-validate", "scripts", "xdto-validate.py")
|
||||
|
||||
|
||||
# Исключение из автономности навыков, сделанное осознанно: конвертер XSD <-> модель
|
||||
# нельзя скопировать буквально (xdto-compile — скрипт со сквозным потоком, не библиотека),
|
||||
# а вторая его реализация разошлась бы с первой. Обещание «правка не меняет ни байта
|
||||
# в нетронутом» держится именно на том, что код тот же самый.
|
||||
# Проверяем комплектность заранее, чтобы не падать на середине правки.
|
||||
def assert_siblings_present(operation):
|
||||
needed = {}
|
||||
if operation not in ("rename", "set-synonym", "set-comment"):
|
||||
needed["xdto-decompile"] = DECOMPILE
|
||||
needed["xdto-compile"] = COMPILE
|
||||
missing = [k for k, v in needed.items() if not os.path.exists(v)]
|
||||
if missing:
|
||||
die("Навык неработоспособен: рядом нет " + ", ".join(missing) + ".\n"
|
||||
+ f'Операция "{operation}" выполняется через '
|
||||
+ ("них" if len(missing) > 1 else "него") + ".\n"
|
||||
+ "Ожидаются в каталоге навыков: " + SKILLS)
|
||||
|
||||
|
||||
def invoke_sibling(script, argv, what):
|
||||
if not os.path.exists(script):
|
||||
die(f"Не найден навык {what} по пути: {script}")
|
||||
r = subprocess.run([sys.executable, script, *argv], capture_output=True, text=True, encoding="utf-8")
|
||||
if r.returncode != 0:
|
||||
die(f"{what} завершился с ошибкой:\n{(r.stderr or '') + (r.stdout or '')}")
|
||||
return r.stdout
|
||||
|
||||
|
||||
def save_xml(doc, path):
|
||||
raw = etree.tostring(doc, xml_declaration=True, encoding="UTF-8")
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf" + raw)
|
||||
|
||||
|
||||
# ── metadata object edits ────────────────────────────────────
|
||||
|
||||
def edit_metadata(field, new_value):
|
||||
doc = _parse_xml(md_file)
|
||||
props = doc.find(f".//{{{MD_NS}}}XDTOPackage/{{{MD_NS}}}Properties")
|
||||
if props is None:
|
||||
die("В объекте метаданных не найден блок <Properties>")
|
||||
|
||||
if field in ("Name", "Namespace"):
|
||||
el = props.find(f"{{{MD_NS}}}{field}")
|
||||
if el is None:
|
||||
die(f"В объекте метаданных нет <{field}>")
|
||||
el.text = new_value
|
||||
elif field == "Comment":
|
||||
el = props.find(f"{{{MD_NS}}}Comment")
|
||||
if el is None:
|
||||
el = etree.SubElement(props, f"{{{MD_NS}}}Comment")
|
||||
el.text = new_value
|
||||
elif field == "Synonym":
|
||||
syn = props.find(f"{{{MD_NS}}}Synonym")
|
||||
if syn is None:
|
||||
syn = etree.SubElement(props, f"{{{MD_NS}}}Synonym")
|
||||
item = None
|
||||
for it in syn.iterfind(f"{{{V8_NS}}}item"):
|
||||
lg = it.find(f"{{{V8_NS}}}lang")
|
||||
if lg is not None and (lg.text or "") == "ru":
|
||||
item = it
|
||||
break
|
||||
if item is None:
|
||||
item = etree.SubElement(syn, f"{{{V8_NS}}}item")
|
||||
etree.SubElement(item, f"{{{V8_NS}}}lang").text = "ru"
|
||||
etree.SubElement(item, f"{{{V8_NS}}}content")
|
||||
item.find(f"{{{V8_NS}}}content").text = new_value
|
||||
save_xml(doc, md_file)
|
||||
|
||||
|
||||
def rename_package(new_name):
|
||||
global pkg_name, pkg_dir
|
||||
# \w с re.UNICODE уже покрывает кириллицу; явные диапазоны только плодят ошибки
|
||||
if not re.match(r"^\w+$", new_name, re.UNICODE) or re.match(r"^\d", new_name):
|
||||
die(f'"{new_name}" не является допустимым идентификатором 1С')
|
||||
new_md = os.path.join(xdto_root, new_name + ".xml")
|
||||
new_dir = os.path.join(xdto_root, new_name)
|
||||
if os.path.exists(new_md) or os.path.exists(new_dir):
|
||||
die(f'Имя "{new_name}" уже занято другим пакетом')
|
||||
|
||||
edit_metadata("Name", new_name)
|
||||
shutil.move(md_file, new_md)
|
||||
shutil.move(pkg_dir, new_dir)
|
||||
|
||||
if os.path.exists(config_xml):
|
||||
cfg = _parse_xml(config_xml)
|
||||
found = False
|
||||
for e in cfg.iterfind(f".//{{{MD_NS}}}Configuration/{{{MD_NS}}}ChildObjects/{{{MD_NS}}}XDTOPackage"):
|
||||
if (e.text or "") == pkg_name:
|
||||
e.text = new_name
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
save_xml(cfg, config_xml)
|
||||
print(f" Configuration.xml: <XDTOPackage> переименован в {new_name}")
|
||||
else:
|
||||
print(f"WARNING: В Configuration.xml не найдена запись <XDTOPackage>{pkg_name}</XDTOPackage> — "
|
||||
"зарегистрируйте пакет вручную", file=sys.stderr)
|
||||
print(f"✓ Пакет переименован: {pkg_name} → {new_name}")
|
||||
print(f" Перемещены: {new_name}.xml, {new_name}/")
|
||||
pkg_name = new_name
|
||||
pkg_dir = new_dir
|
||||
|
||||
|
||||
# ── model edits through the XSD round-trip ───────────────────
|
||||
|
||||
def xs_children(el, name):
|
||||
return [c for c in el if isinstance(c.tag, str)
|
||||
and etree.QName(c).namespace == XS_NS and local(c) == name]
|
||||
|
||||
|
||||
def xs_first(el, name):
|
||||
r = xs_children(el, name)
|
||||
return r[0] if r else None
|
||||
|
||||
|
||||
def find_type_element(schema, type_name):
|
||||
for kind in ("complexType", "simpleType"):
|
||||
for t in xs_children(schema, kind):
|
||||
if t.get("name") == type_name:
|
||||
return t
|
||||
die(f'В пакете нет типа "{type_name}"')
|
||||
|
||||
|
||||
def get_type_body(ct):
|
||||
content = xs_first(ct, "complexContent")
|
||||
if content is not None:
|
||||
ext = xs_first(content, "extension")
|
||||
if ext is not None:
|
||||
return ext
|
||||
return ct
|
||||
|
||||
|
||||
def find_declaration(body, prop_name):
|
||||
for node in body.iter():
|
||||
if not isinstance(node.tag, str) or etree.QName(node).namespace != XS_NS:
|
||||
continue
|
||||
if local(node) in ("element", "attribute") and node.get("name") == prop_name:
|
||||
return node
|
||||
return None
|
||||
|
||||
|
||||
def resolve_path(schema, path):
|
||||
# Точка безопасна: имена в модели XDTO — идентификаторы 1С
|
||||
segments = path.split(".")
|
||||
type_el = find_type_element(schema, segments[0])
|
||||
if len(segments) == 1:
|
||||
return (type_el, None)
|
||||
body = get_type_body(type_el)
|
||||
decl = None
|
||||
for i in range(1, len(segments)):
|
||||
decl = find_declaration(body, segments[i])
|
||||
if decl is None:
|
||||
die(f'По пути "{path}" не найдено свойство "{segments[i]}"')
|
||||
if i < len(segments) - 1:
|
||||
inner = xs_first(decl, "complexType")
|
||||
if inner is None:
|
||||
die(f'Свойство "{segments[i]}" не содержит вложенного типа — путь дальше не идёт')
|
||||
body = get_type_body(inner)
|
||||
return (type_el, decl)
|
||||
|
||||
|
||||
def import_fragment(schema, xml):
|
||||
ns = {"xs": XS_NS, "xdto": XDTO_NS}
|
||||
tns = schema.get("targetNamespace")
|
||||
if tns:
|
||||
ns["tns"] = tns
|
||||
for px, uri in schema.nsmap.items():
|
||||
if px and px not in ns:
|
||||
ns[px] = uri
|
||||
decls = " ".join(f'xmlns:{k}="{v}"' for k, v in ns.items())
|
||||
try:
|
||||
wrapped = etree.fromstring(f"<wrap {decls}>{xml}</wrap>".encode("utf-8"))
|
||||
except etree.XMLSyntaxError as e:
|
||||
die("Не удалось разобрать -Value как фрагмент XML-схемы: " + str(e) + "\n"
|
||||
+ "Получено: " + xml + "\n"
|
||||
+ "Если фрагмент передан инлайном, кавычки могли схлопнуться на границе "
|
||||
'процессов — положите его в файл и укажите -Value "@путь".')
|
||||
res = [c for c in wrapped if isinstance(c.tag, str)]
|
||||
if not res:
|
||||
die(f"Во фрагменте нет ни одного элемента: {xml}")
|
||||
return res
|
||||
|
||||
|
||||
def apply_model_operation(schema):
|
||||
op = args.Operation
|
||||
|
||||
if op == "add-property":
|
||||
if not args.Target:
|
||||
die("add-property требует -Target <Тип>")
|
||||
type_el, decl = resolve_path(schema, args.Target)
|
||||
host = xs_first(decl, "complexType") if decl is not None else type_el
|
||||
body = get_type_body(host)
|
||||
for frag in import_fragment(schema, args.Value):
|
||||
kind = local(frag)
|
||||
if kind == "attribute":
|
||||
body.append(frag)
|
||||
elif kind == "element":
|
||||
# Явные is not None: пустой <xs:sequence/> в lxml ложен,
|
||||
# и через "or" мы бы создали вторую частицу
|
||||
particle = xs_first(body, "sequence")
|
||||
if particle is None:
|
||||
particle = xs_first(body, "choice")
|
||||
if particle is None:
|
||||
particle = xs_first(body, "all")
|
||||
if particle is None:
|
||||
particle = etree.Element(f"{{{XS_NS}}}sequence")
|
||||
first_attr = xs_first(body, "attribute")
|
||||
if first_attr is not None:
|
||||
first_attr.addprevious(particle)
|
||||
else:
|
||||
body.append(particle)
|
||||
particle.append(frag)
|
||||
else:
|
||||
die(f"add-property ожидает <xs:element> или <xs:attribute>, получен <xs:{kind}>")
|
||||
print(f' + {frag.get("name")} в тип {args.Target}')
|
||||
|
||||
elif op == "replace-property":
|
||||
if not args.Target:
|
||||
die('replace-property требует -Target "Тип.Свойство"')
|
||||
_, decl = resolve_path(schema, args.Target)
|
||||
if decl is None:
|
||||
die('replace-property требует путь вида "Тип.Свойство"')
|
||||
frags = import_fragment(schema, args.Value)
|
||||
if len(frags) != 1:
|
||||
die("replace-property ожидает ровно одно объявление")
|
||||
decl.getparent().replace(decl, frags[0])
|
||||
print(f" ~ {args.Target} заменено")
|
||||
|
||||
elif op == "remove-property":
|
||||
if not args.Target:
|
||||
die('remove-property требует путь "Тип.Свойство"')
|
||||
for one in [x.strip() for x in args.Target.split(";;") if x.strip()]:
|
||||
_, decl = resolve_path(schema, one)
|
||||
if decl is None:
|
||||
die(f'remove-property требует путь вида "Тип.Свойство", получено "{one}"')
|
||||
decl.getparent().remove(decl)
|
||||
print(f" − {one} удалено")
|
||||
|
||||
elif op == "add-type":
|
||||
for frag in import_fragment(schema, args.Value):
|
||||
if local(frag) not in ("complexType", "simpleType"):
|
||||
die(f"add-type ожидает <xs:complexType> или <xs:simpleType>, получен <xs:{local(frag)}>")
|
||||
schema.append(frag)
|
||||
print(f' + тип {frag.get("name")}')
|
||||
|
||||
elif op == "remove-type":
|
||||
if not args.Target:
|
||||
die("remove-type требует -Target <Тип>")
|
||||
for one in [x.strip() for x in args.Target.split(";;") if x.strip()]:
|
||||
t = find_type_element(schema, one)
|
||||
t.getparent().remove(t)
|
||||
print(f" − тип {one} удалён")
|
||||
|
||||
elif op == "add-enum":
|
||||
if not args.Target:
|
||||
die("add-enum требует -Target <ТипЗначения>")
|
||||
t = find_type_element(schema, args.Target)
|
||||
restriction = xs_first(t, "restriction")
|
||||
if restriction is None:
|
||||
die(f'Тип "{args.Target}" не является ограничением простого типа')
|
||||
for lit in [x.strip() for x in args.Value.split(";;") if x.strip()]:
|
||||
e = etree.SubElement(restriction, f"{{{XS_NS}}}enumeration")
|
||||
e.set("value", lit)
|
||||
print(f' + значение "{lit}" в тип {args.Target}')
|
||||
|
||||
elif op == "add-import":
|
||||
for uri in [x.strip() for x in args.Value.split(";;") if x.strip()]:
|
||||
if any(i.get("namespace") == uri for i in xs_children(schema, "import")):
|
||||
print(f" = импорт {uri} уже объявлен")
|
||||
continue
|
||||
imp = etree.Element(f"{{{XS_NS}}}import")
|
||||
imp.set("namespace", uri)
|
||||
first_other = next((c for c in schema if isinstance(c.tag, str)
|
||||
and local(c) not in ("annotation", "import")), None)
|
||||
if first_other is not None:
|
||||
first_other.addprevious(imp)
|
||||
else:
|
||||
schema.append(imp)
|
||||
print(f" + импорт {uri}")
|
||||
|
||||
elif op == "set-namespace":
|
||||
if not args.Value:
|
||||
die("set-namespace требует -Value <URI>")
|
||||
old = schema.get("targetNamespace")
|
||||
if old == args.Value:
|
||||
# Установка того же значения не отбрасывается: пакет пересобирается вхолостую
|
||||
print(f" = namespace уже {args.Value}, пакет пересобран без изменений")
|
||||
else:
|
||||
print(f" ~ namespace: {old} → {args.Value}")
|
||||
# Меняем и targetNamespace, и объявление префикса, который на него указывал
|
||||
new_nsmap = {px: (args.Value if uri == old else uri) for px, uri in schema.nsmap.items()}
|
||||
rebuilt = etree.Element(schema.tag, nsmap=new_nsmap)
|
||||
for k, v in schema.attrib.items():
|
||||
rebuilt.set(k, v)
|
||||
rebuilt.set("targetNamespace", args.Value)
|
||||
rebuilt.text = schema.text
|
||||
for c in list(schema):
|
||||
rebuilt.append(c)
|
||||
return rebuilt
|
||||
return schema
|
||||
|
||||
|
||||
# ── dispatch ─────────────────────────────────────────────────
|
||||
|
||||
assert_siblings_present(args.Operation)
|
||||
|
||||
print(f"Пакет: {pkg_name}")
|
||||
old_namespace = None
|
||||
|
||||
if args.Operation == "rename":
|
||||
if not args.Value:
|
||||
die("rename требует -Value <НовоеИмя>")
|
||||
rename_package(args.Value)
|
||||
elif args.Operation == "set-synonym":
|
||||
if not args.Value:
|
||||
die("set-synonym требует -Value <текст>")
|
||||
edit_metadata("Synonym", args.Value)
|
||||
print(f"✓ Синоним: {args.Value}")
|
||||
elif args.Operation == "set-comment":
|
||||
edit_metadata("Comment", args.Value)
|
||||
print("✓ Комментарий обновлён")
|
||||
else:
|
||||
tmp_dir = tempfile.mkdtemp(prefix="xdto-edit_")
|
||||
try:
|
||||
xsd_path = os.path.join(tmp_dir, "schema.xsd")
|
||||
invoke_sibling(DECOMPILE, ["-PackagePath", bin_file, "-OutFile", xsd_path], "xdto-decompile")
|
||||
|
||||
doc = _parse_xml(xsd_path)
|
||||
schema = doc.getroot()
|
||||
old_namespace = schema.get("targetNamespace")
|
||||
schema = apply_model_operation(schema)
|
||||
|
||||
with open(xsd_path, "wb") as f:
|
||||
f.write(etree.tostring(schema, xml_declaration=True, encoding="UTF-8"))
|
||||
invoke_sibling(COMPILE, ["-XsdPath", xsd_path, "-OutputDir", config_root,
|
||||
"-Name", pkg_name, "-Force"], "xdto-compile")
|
||||
|
||||
if args.Operation == "set-namespace":
|
||||
edit_metadata("Namespace", args.Value)
|
||||
# Зависящие пакеты не трогаем: при версионировании они обязаны продолжать
|
||||
# смотреть на прежний namespace. Но молчать о них нельзя.
|
||||
dependents = []
|
||||
for d in sorted(os.listdir(xdto_root)):
|
||||
if d == pkg_name or not os.path.isdir(os.path.join(xdto_root, d)):
|
||||
continue
|
||||
ob = os.path.join(xdto_root, d, "Ext", "Package.bin")
|
||||
if not os.path.exists(ob):
|
||||
continue
|
||||
try:
|
||||
for imp in _parse_xml(ob).getroot():
|
||||
if isinstance(imp.tag, str) and local(imp) == "import" \
|
||||
and imp.get("namespace") == old_namespace:
|
||||
dependents.append(d)
|
||||
break
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
if dependents:
|
||||
print("")
|
||||
print(f"WARNING: Старый namespace импортируют пакеты ({len(dependents)}): "
|
||||
+ ", ".join(dependents)
|
||||
+ ". Они не изменены — при версионировании это верно; "
|
||||
"если нет, поправьте их импорты.", file=sys.stderr)
|
||||
print(f"✓ Пакет пересобран: XDTOPackages/{pkg_name}/Ext/Package.bin")
|
||||
finally:
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
|
||||
if not args.NoValidate:
|
||||
if os.path.exists(VALIDATE):
|
||||
print("")
|
||||
print("--- xdto-validate ---")
|
||||
subprocess.run([sys.executable, VALIDATE, "-PackagePath", os.path.join(xdto_root, pkg_name)])
|
||||
else:
|
||||
print(f"[SKIP] xdto-validate не найден: {VALIDATE}")
|
||||
sys.exit(0)
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
name: xdto-info
|
||||
description: Анализ структуры пакета XDTO 1С — типы, свойства, точки входа. Используй как подготовительный шаг при написании кода, создающего и заполняющего объект XDTO, при разборе входящего XML, а также чтобы узнать, какие пакеты есть в конфигурации
|
||||
argument-hint: <PackagePath> [-Namespace <URI>|-Package <имя>] [-Name <Тип>] [-Depth N] [-RequiredOnly] [-Mode used-by] [-Limit N] [-Offset N] [-OutFile <файл>]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /xdto-info — Анализ структуры пакета XDTO
|
||||
|
||||
Показывает структуру типа в терминах 1С: какой тип значения присваивать, что обязательно,
|
||||
где нужен вложенный объект, какие значения допустимы. Заменяет чтение `Package.bin`
|
||||
или XSD с ручным переводом `xs:decimal` → `Число` и `lowerBound="0"` → необязательный.
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обязательный | Описание |
|
||||
|----------|--------------|----------|
|
||||
| `PackagePath` | да | Каталог пакета либо корень исходников конфигурации. Псевдоним — `-Path` |
|
||||
| `Namespace` | нет | Выбрать пакет по пространству имён (когда путь — корень исходников) |
|
||||
| `Package` | нет | Выбрать пакет по имени объекта метаданных |
|
||||
| `Name` | нет | Имя типа. Без выбранного пакета ищется по всей конфигурации |
|
||||
| `Depth` | нет | Глубина разузлования вложенных объектов. По умолчанию 1 |
|
||||
| `RequiredOnly` | нет | Оставить только обязательные свойства — скелет для «заполни обязательное». Необязательный объект уходит вместе со своим содержимым |
|
||||
| `Mode` | нет | `used-by` — показать, кто ссылается на тип |
|
||||
| `Limit` / `Offset` | нет | Пагинация. По умолчанию 150 строк |
|
||||
| `OutFile` | нет | Записать результат в файл (UTF-8 BOM) |
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-info.ps1" -PackagePath "<путь>"
|
||||
```
|
||||
|
||||
## Что показывает
|
||||
|
||||
Точка входа определяется по пути: корень исходников — список пакетов, каталог пакета —
|
||||
его состав.
|
||||
|
||||
| Вызов | Результат |
|
||||
|---|---|
|
||||
| `-PackagePath src` | все пакеты конфигурации: имя, число типов, namespace |
|
||||
| `-PackagePath src/XDTOPackages/ОбменСБанком` | импорты, точки входа, списки типов |
|
||||
| `... -Name ПлатежныйДокумент` | структура типа для заполнения |
|
||||
| `... -Name ПлатежныйДокумент -Depth 3` | то же с раскрытием вложенных объектов |
|
||||
| `... -Mode used-by -Name СуммаТип` | кто ссылается на тип, включая соседние пакеты |
|
||||
|
||||
## Когда известны namespace и тип, но не имя пакета
|
||||
|
||||
Так бывает чаще всего: namespace и имя типа видны в коде или в образце XML,
|
||||
а как называется пакет — нет. Вызов повторяет строку, от которой отталкиваешься:
|
||||
|
||||
```powershell
|
||||
# ФабрикаXDTO.Тип("urn:1C.ru:ClientBankExchange", "ПлатежныйДокумент")
|
||||
... -PackagePath src -Namespace "urn:1C.ru:ClientBankExchange" -Name ПлатежныйДокумент
|
||||
```
|
||||
|
||||
Если известно только имя типа — укажи `-Name` и корень исходников: тип найдётся
|
||||
по всем пакетам. При нескольких совпадениях навык покажет, где именно, чтобы уточнить.
|
||||
|
||||
## Что в выводе
|
||||
|
||||
Свойства показаны так, как их предстоит заполнять в коде: тип значения — в нотации
|
||||
1С и с учётом ограничений (`Строка(6)`, `Число(18,2)`), обязательность и коллекции —
|
||||
флагами, для перечислимых типов перечислены допустимые значения. Непомеченное
|
||||
свойство необязательно.
|
||||
|
||||
Обозначения, которые сами по себе неочевидны, навык поясняет прямо в выводе —
|
||||
и только те, что в нём встретились.
|
||||
|
||||
## Типичный workflow
|
||||
|
||||
1. `/xdto-info src` — какие пакеты есть
|
||||
2. `/xdto-info src -Namespace "<URI>"` — точки входа и типы пакета
|
||||
3. `/xdto-info src -Namespace "<URI>" -Name <Тип> -Depth 2` — структура для кода
|
||||
4. Перед правкой типа: `-Mode used-by -Name <Тип>` — кого затронет
|
||||
|
||||
Нужна сама XML-схема, а не сводка, — это `/xdto-decompile`.
|
||||
@@ -0,0 +1,694 @@
|
||||
# xdto-info v1.0 — Analyze 1C XDTO package structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[Alias('Path')]
|
||||
[string]$PackagePath,
|
||||
[string]$Package,
|
||||
[string]$Namespace,
|
||||
[string]$Name,
|
||||
[ValidateSet("auto", "used-by")]
|
||||
[string]$Mode = "auto",
|
||||
[int]$Depth = 1,
|
||||
[switch]$RequiredOnly,
|
||||
[int]$Limit = 150,
|
||||
[int]$Offset = 0,
|
||||
[string]$OutFile
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$XS_NS = "http://www.w3.org/2001/XMLSchema"
|
||||
$XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
$MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
||||
|
||||
# --- Output ---
|
||||
|
||||
$sb = New-Object System.Text.StringBuilder
|
||||
function O([string]$line = "") { [void]$sb.AppendLine($line) }
|
||||
|
||||
function Fail([string]$msg) {
|
||||
# Отрицательный результат поиска — не исключение: печатаем сообщение
|
||||
# и выходим с кодом 1, без стектрейса PowerShell
|
||||
Write-Host $msg
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Flush-Output {
|
||||
$text = $sb.ToString().TrimEnd()
|
||||
if ($OutFile) {
|
||||
$dir = [System.IO.Path]::GetDirectoryName($OutFile)
|
||||
if ($dir -and -not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
|
||||
[System.IO.File]::WriteAllText($OutFile, $text + "`r`n", (New-Object System.Text.UTF8Encoding($true)))
|
||||
Write-Host "✓ Записано: $OutFile"
|
||||
} else {
|
||||
Write-Host $text
|
||||
}
|
||||
}
|
||||
|
||||
# --- Path resolution -----------------------------------------------------------
|
||||
# Путь может указывать на корень конфигурации (тогда работаем со всеми пакетами)
|
||||
# либо на конкретный пакет.
|
||||
|
||||
if (-not [System.IO.Path]::IsPathRooted($PackagePath)) {
|
||||
$PackagePath = Join-Path (Get-Location).Path $PackagePath
|
||||
}
|
||||
if (-not (Test-Path $PackagePath)) { Fail "Путь не найден: $PackagePath" }
|
||||
|
||||
$configRoot = $null
|
||||
$directPkgDir = $null
|
||||
|
||||
if (Test-Path (Join-Path $PackagePath "Configuration.xml")) {
|
||||
$configRoot = $PackagePath
|
||||
} elseif ((Split-Path $PackagePath -Leaf) -eq "XDTOPackages") {
|
||||
$configRoot = Split-Path $PackagePath -Parent
|
||||
} elseif (Test-Path (Join-Path (Join-Path $PackagePath "Ext") "Package.bin")) {
|
||||
$directPkgDir = $PackagePath
|
||||
$pkgRoot = Split-Path $PackagePath -Parent
|
||||
$configRoot = Split-Path $pkgRoot -Parent
|
||||
} elseif ((Test-Path $PackagePath -PathType Leaf) -and ([System.IO.Path]::GetFileName($PackagePath) -eq "Package.bin")) {
|
||||
$directPkgDir = Split-Path (Split-Path $PackagePath -Parent) -Parent
|
||||
$configRoot = Split-Path (Split-Path $directPkgDir -Parent) -Parent
|
||||
} elseif ($PackagePath.EndsWith(".xml")) {
|
||||
$stem = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($PackagePath),
|
||||
[System.IO.Path]::GetFileNameWithoutExtension($PackagePath))
|
||||
if (Test-Path (Join-Path (Join-Path $stem "Ext") "Package.bin")) {
|
||||
$directPkgDir = $stem
|
||||
$configRoot = Split-Path (Split-Path $stem -Parent) -Parent
|
||||
}
|
||||
}
|
||||
if (-not $configRoot -and -not $directPkgDir) { Fail "Не удалось определить пакет или конфигурацию по пути: $PackagePath" }
|
||||
|
||||
# Sort-Object в PowerShell сортирует по культуре, sorted() в Python — по кодам.
|
||||
# Для паритета портов сортируем ординально в обоих.
|
||||
function Sort-Ordinal($items) {
|
||||
$arr = [string[]]@($items)
|
||||
[array]::Sort($arr, [StringComparer]::Ordinal)
|
||||
return ,$arr
|
||||
}
|
||||
|
||||
# --- Package index -------------------------------------------------------------
|
||||
|
||||
function Read-Package([string]$pkgDir) {
|
||||
$bin = Join-Path (Join-Path $pkgDir "Ext") "Package.bin"
|
||||
if (-not (Test-Path $bin)) { return $null }
|
||||
try {
|
||||
$doc = New-Object System.Xml.XmlDocument
|
||||
$doc.PreserveWhitespace = $false
|
||||
$doc.Load($bin)
|
||||
} catch { return $null }
|
||||
$root = $doc.DocumentElement
|
||||
if ($root.get_LocalName() -ne "package") { return $null }
|
||||
|
||||
$info = [pscustomobject]@{
|
||||
Name = [System.IO.Path]::GetFileName($pkgDir)
|
||||
Dir = $pkgDir
|
||||
Namespace = $root.GetAttribute("targetNamespace")
|
||||
Root = $root
|
||||
Imports = (New-Object System.Collections.ArrayList)
|
||||
Types = @{}
|
||||
GlobalProps= (New-Object System.Collections.ArrayList)
|
||||
}
|
||||
foreach ($n in $root.ChildNodes) {
|
||||
if ($n.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
|
||||
switch ($n.get_LocalName()) {
|
||||
"import" { [void]$info.Imports.Add($n.GetAttribute("namespace")) }
|
||||
"objectType" { $info.Types[$n.GetAttribute("name")] = $n }
|
||||
"valueType" { $info.Types[$n.GetAttribute("name")] = $n }
|
||||
"property" { [void]$info.GlobalProps.Add($n) }
|
||||
}
|
||||
}
|
||||
return $info
|
||||
}
|
||||
|
||||
$packages = New-Object System.Collections.ArrayList
|
||||
$byNamespace = @{}
|
||||
|
||||
if ($configRoot -and (Test-Path (Join-Path $configRoot "XDTOPackages"))) {
|
||||
foreach ($dn in (Sort-Ordinal ((Get-ChildItem (Join-Path $configRoot "XDTOPackages") -Directory -ErrorAction SilentlyContinue).Name))) {
|
||||
$p = Read-Package (Join-Path (Join-Path $configRoot "XDTOPackages") $dn)
|
||||
if ($p) {
|
||||
[void]$packages.Add($p)
|
||||
if (-not $byNamespace.ContainsKey($p.Namespace)) { $byNamespace[$p.Namespace] = $p }
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($directPkgDir -and $packages.Count -eq 0) {
|
||||
$p = Read-Package $directPkgDir
|
||||
if ($p) { [void]$packages.Add($p); $byNamespace[$p.Namespace] = $p }
|
||||
}
|
||||
if ($packages.Count -eq 0) { Fail "Пакеты XDTO не найдены: $PackagePath" }
|
||||
|
||||
# --- Type notation: XSD -> 1С ---------------------------------------------------
|
||||
|
||||
$XS_TO_1C = @{
|
||||
"string" = "Строка"; "normalizedString" = "Строка"; "token" = "Строка"; "NCName" = "Строка"
|
||||
"Name" = "Строка"; "QName" = "Строка"; "anyURI" = "Строка"; "language" = "Строка"
|
||||
"ID" = "Строка"; "IDREF" = "Строка"; "NMTOKEN" = "Строка"
|
||||
"decimal" = "Число"; "integer" = "Число"; "int" = "Число"; "long" = "Число"; "short" = "Число"
|
||||
"byte" = "Число"; "float" = "Число"; "double" = "Число"
|
||||
"nonNegativeInteger" = "Число"; "positiveInteger" = "Число"; "nonPositiveInteger" = "Число"
|
||||
"negativeInteger" = "Число"; "unsignedInt" = "Число"; "unsignedLong" = "Число"
|
||||
"unsignedShort" = "Число"; "unsignedByte" = "Число"
|
||||
"date" = "Дата"; "dateTime" = "Дата"; "time" = "Дата"
|
||||
"boolean" = "Булево"
|
||||
"base64Binary" = "ДвоичныеДанные"; "hexBinary" = "ДвоичныеДанные"
|
||||
"anyType" = "произвольный"; "anySimpleType" = "произвольный"
|
||||
}
|
||||
|
||||
function Split-Ref([System.Xml.XmlElement]$el, [string]$raw) {
|
||||
if (-not $raw) { return $null }
|
||||
if ($raw.StartsWith("{")) {
|
||||
$close = $raw.IndexOf("}")
|
||||
if ($close -lt 0) { return $null }
|
||||
return [pscustomobject]@{ Ns = $raw.Substring(1, $close - 1); Local = $raw.Substring($close + 1) }
|
||||
}
|
||||
$parts = $raw.Split(":")
|
||||
if ($parts.Count -eq 2) {
|
||||
return [pscustomobject]@{ Ns = $el.GetNamespaceOfPrefix($parts[0]); Local = $parts[1] }
|
||||
}
|
||||
return [pscustomobject]@{ Ns = $null; Local = $parts[0] }
|
||||
}
|
||||
|
||||
function Get-Facets([System.Xml.XmlElement]$t) {
|
||||
$res = @{}
|
||||
foreach ($f in @("length", "minLength", "maxLength", "totalDigits", "fractionDigits",
|
||||
"minInclusive", "maxInclusive", "minExclusive", "maxExclusive")) {
|
||||
$v = $t.GetAttribute($f)
|
||||
if ($v) { $res[$f] = $v }
|
||||
}
|
||||
$pat = $null
|
||||
foreach ($c in $t.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "pattern") { $pat = $c.InnerText; break }
|
||||
}
|
||||
if ($pat) { $res["pattern"] = $pat }
|
||||
return $res
|
||||
}
|
||||
|
||||
function Get-Enumerations([System.Xml.XmlElement]$t) {
|
||||
$vals = @()
|
||||
foreach ($c in $t.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "enumeration") { $vals += $c.InnerText }
|
||||
}
|
||||
return $vals
|
||||
}
|
||||
|
||||
# Разворачивает цепочку псевдонимов до примитива, собирая фасеты по пути.
|
||||
# Возвращает @{ Base1C; Facets; Alias; Enum; Kind }
|
||||
function Resolve-Scalar([System.Xml.XmlElement]$t, $pkg, [int]$guard = 0) {
|
||||
$acc = @{ Base1C = $null; Facets = @{}; Alias = $null; Enum = @(); Kind = "scalar" }
|
||||
if ($guard -gt 10 -or -not $t) { return $acc }
|
||||
|
||||
$variety = $t.GetAttribute("variety")
|
||||
if ($variety -eq "List") {
|
||||
$it = Split-Ref $t $t.GetAttribute("itemType")
|
||||
$acc.Kind = "list"
|
||||
$acc.Base1C = "список " + $(if ($it) { Format-RefName $it $pkg } else { "значений" })
|
||||
return $acc
|
||||
}
|
||||
if ($variety -eq "Union" -or $t.GetAttribute("memberTypes")) {
|
||||
$members = @()
|
||||
foreach ($m in (($t.GetAttribute("memberTypes") -split "\s+") | Where-Object { $_ })) {
|
||||
$q = Split-Ref $t $m
|
||||
if ($q) { $members += (Format-RefName $q $pkg) }
|
||||
}
|
||||
foreach ($c in $t.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") {
|
||||
$inner = Resolve-Scalar $c $pkg ($guard + 1)
|
||||
$members += $inner.Base1C
|
||||
}
|
||||
}
|
||||
$acc.Kind = "union"
|
||||
$acc.Base1C = "одно из (" + (($members | Where-Object { $_ }) -join " | ") + ")"
|
||||
return $acc
|
||||
}
|
||||
|
||||
$acc.Facets = Get-Facets $t
|
||||
$acc.Enum = Get-Enumerations $t
|
||||
|
||||
# базовый тип: атрибут base или вложенный анонимный typeDef
|
||||
$baseQ = Split-Ref $t $t.GetAttribute("base")
|
||||
$anonBase = $null
|
||||
foreach ($c in $t.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $anonBase = $c; break }
|
||||
}
|
||||
if (-not $baseQ -and $anonBase) {
|
||||
$inner = Resolve-Scalar $anonBase $pkg ($guard + 1)
|
||||
$acc.Base1C = $inner.Base1C
|
||||
foreach ($k in $inner.Facets.Keys) { if (-not $acc.Facets.ContainsKey($k)) { $acc.Facets[$k] = $inner.Facets[$k] } }
|
||||
if ($inner.Enum.Count -gt 0 -and $acc.Enum.Count -eq 0) { $acc.Enum = $inner.Enum }
|
||||
return $acc
|
||||
}
|
||||
if (-not $baseQ) { $acc.Base1C = "произвольный"; return $acc }
|
||||
|
||||
if ($baseQ.Ns -eq $XS_NS) {
|
||||
$acc.Base1C = $(if ($XS_TO_1C.ContainsKey($baseQ.Local)) { $XS_TO_1C[$baseQ.Local] } else { "xs:$($baseQ.Local)" })
|
||||
return $acc
|
||||
}
|
||||
|
||||
# база — именованный тип значения: разворачиваем дальше
|
||||
$target = Find-Type $baseQ $pkg
|
||||
if ($target -and $target.Element.get_LocalName() -eq "valueType") {
|
||||
$inner = Resolve-Scalar $target.Element $target.Package ($guard + 1)
|
||||
$acc.Base1C = $inner.Base1C
|
||||
foreach ($k in $inner.Facets.Keys) { if (-not $acc.Facets.ContainsKey($k)) { $acc.Facets[$k] = $inner.Facets[$k] } }
|
||||
if ($inner.Enum.Count -gt 0 -and $acc.Enum.Count -eq 0) { $acc.Enum = $inner.Enum }
|
||||
if (-not $acc.Alias) { $acc.Alias = $baseQ.Local }
|
||||
return $acc
|
||||
}
|
||||
$acc.Base1C = $baseQ.Local
|
||||
return $acc
|
||||
}
|
||||
|
||||
function Find-Type($q, $pkg) {
|
||||
if (-not $q) { return $null }
|
||||
$targetPkg = $null
|
||||
if (-not $q.Ns -or ($pkg -and $q.Ns -eq $pkg.Namespace)) { $targetPkg = $pkg }
|
||||
elseif ($byNamespace.ContainsKey($q.Ns)) { $targetPkg = $byNamespace[$q.Ns] }
|
||||
if (-not $targetPkg) { return $null }
|
||||
if (-not $targetPkg.Types.ContainsKey($q.Local)) { return $null }
|
||||
return [pscustomobject]@{ Element = $targetPkg.Types[$q.Local]; Package = $targetPkg }
|
||||
}
|
||||
|
||||
function Format-RefName($q, $pkg) {
|
||||
if (-not $q) { return "" }
|
||||
if ($q.Ns -eq $XS_NS) {
|
||||
return $(if ($XS_TO_1C.ContainsKey($q.Local)) { $XS_TO_1C[$q.Local] } else { "xs:$($q.Local)" })
|
||||
}
|
||||
return $q.Local
|
||||
}
|
||||
|
||||
function Format-Scalar($res) {
|
||||
$t = $res.Base1C
|
||||
$f = $res.Facets
|
||||
if ($t -eq "Строка") {
|
||||
if ($f.ContainsKey("length")) { $t = "Строка($($f['length']))" }
|
||||
elseif ($f.ContainsKey("maxLength")) { $t = "Строка($($f['maxLength']))" }
|
||||
} elseif ($t -eq "Число") {
|
||||
if ($f.ContainsKey("totalDigits")) {
|
||||
$frac = $(if ($f.ContainsKey("fractionDigits")) { $f["fractionDigits"] } else { "0" })
|
||||
$t = "Число($($f['totalDigits']),$frac)"
|
||||
}
|
||||
}
|
||||
return $t
|
||||
}
|
||||
|
||||
function Format-Notes($res) {
|
||||
$notes = @()
|
||||
if ($res.Alias) { $notes += "← $($res.Alias)" }
|
||||
if ($res.Facets.ContainsKey("pattern")) {
|
||||
$p = $res.Facets["pattern"]
|
||||
if ($p.Length -gt 40) { $p = $p.Substring(0, 40) + "…" }
|
||||
$notes += "шаблон $p"
|
||||
}
|
||||
foreach ($k in @("minInclusive", "maxInclusive", "minExclusive", "maxExclusive")) {
|
||||
if ($res.Facets.ContainsKey($k)) { $notes += "$k $($res.Facets[$k])" }
|
||||
}
|
||||
return $notes
|
||||
}
|
||||
|
||||
# --- Property rendering ---------------------------------------------------------
|
||||
|
||||
function Get-PropRows([System.Xml.XmlElement]$type, $pkg, [int]$depth, [int]$indent, $seen) {
|
||||
$rows = New-Object System.Collections.ArrayList
|
||||
foreach ($p in $type.ChildNodes) {
|
||||
if ($p.NodeType -ne [System.Xml.XmlNodeType]::Element -or $p.get_LocalName() -ne "property") { continue }
|
||||
|
||||
$pname = $p.GetAttribute("name")
|
||||
if (-not $pname) {
|
||||
$refQ = Split-Ref $p $p.GetAttribute("ref")
|
||||
$pname = $(if ($refQ) { $refQ.Local } else { "(без имени)" })
|
||||
}
|
||||
$lower = $p.GetAttribute("lowerBound")
|
||||
$upper = $p.GetAttribute("upperBound")
|
||||
$flags = @()
|
||||
# В модели умолчание lowerBound = 1; помечаем обязательные, как в meta-info
|
||||
if ($lower -ne "0") { $flags += "обязательный" }
|
||||
if ($upper -eq "-1") { $flags += "список" }
|
||||
elseif ($upper -and $upper -ne "1") { $flags += "до $upper" }
|
||||
if ($p.GetAttribute("form") -eq "Text") { $flags += "значение элемента" }
|
||||
|
||||
$notes = @()
|
||||
$typeText = ""
|
||||
$children = $null
|
||||
$childPkg = $pkg
|
||||
# Именованный вложенный тип берётся так же, как корневой; окольный путь
|
||||
# через свойство владельца нужен только анонимному — имени у него нет
|
||||
$objName = $null
|
||||
$objNs = $null
|
||||
|
||||
$anon = $null
|
||||
foreach ($c in $p.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $anon = $c; break }
|
||||
}
|
||||
|
||||
if ($anon) {
|
||||
if ($anon.GetAttribute("type", $XSI_NS) -eq "ObjectType") {
|
||||
$typeText = "объект (анонимный)"
|
||||
$objName = "(анонимный)"
|
||||
$children = $anon # анонимные раскрываем всегда: смотреть отдельно негде
|
||||
} else {
|
||||
$res = Resolve-Scalar $anon $pkg
|
||||
$typeText = Format-Scalar $res
|
||||
$notes += (Format-Notes $res)
|
||||
if ($res.Enum.Count -gt 0) { $notes += "значения: " + (($res.Enum | Select-Object -First 8) -join ", ") }
|
||||
}
|
||||
} else {
|
||||
$q = Split-Ref $p $p.GetAttribute("type")
|
||||
if (-not $q) {
|
||||
$typeText = "произвольный"
|
||||
} elseif ($q.Ns -eq $XS_NS) {
|
||||
$typeText = $(if ($XS_TO_1C.ContainsKey($q.Local)) { $XS_TO_1C[$q.Local] } else { "xs:$($q.Local)" })
|
||||
} else {
|
||||
$target = Find-Type $q $pkg
|
||||
if (-not $target) {
|
||||
$typeText = "объект $($q.Local)"
|
||||
$notes += "(пакет не найден: $($q.Ns))"
|
||||
} elseif ($target.Element.get_LocalName() -eq "objectType") {
|
||||
$typeText = "объект $($q.Local)"
|
||||
if ($target.Package.Namespace -ne $pkg.Namespace) { $typeText += " · $($target.Package.Name)" }
|
||||
$objName = $q.Local
|
||||
$objNs = $target.Package.Namespace
|
||||
$children = $target.Element
|
||||
$childPkg = $target.Package
|
||||
} else {
|
||||
$res = Resolve-Scalar $target.Element $target.Package
|
||||
$typeText = Format-Scalar $res
|
||||
$notes += "← $($q.Local)"
|
||||
$n2 = Format-Notes $res | Where-Object { -not $_.StartsWith("←") }
|
||||
$notes += $n2
|
||||
if ($res.Enum.Count -gt 0) { $notes += "значения: " + (($res.Enum | Select-Object -First 8) -join ", ") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[void]$rows.Add([pscustomobject]@{
|
||||
Indent = $indent; Name = $pname; Type = $typeText
|
||||
Flags = $flags; Notes = ($notes | Where-Object { $_ })
|
||||
ObjName = $objName; ObjNs = $objNs
|
||||
})
|
||||
|
||||
if ($children) {
|
||||
$key = "$($childPkg.Namespace)#$($children.GetAttribute('name'))"
|
||||
$isAnon = -not $children.GetAttribute("name")
|
||||
if (-not $isAnon -and $seen.Contains($key)) {
|
||||
[void]$rows.Add([pscustomobject]@{ Indent = $indent + 1; Name = "(раскрыт выше)"; Type = ""; Flags = @(); Notes = @() })
|
||||
} elseif ($isAnon -or $depth -gt 1) {
|
||||
$nextSeen = New-Object System.Collections.Generic.HashSet[string] (,[string[]]$seen)
|
||||
if (-not $isAnon) { [void]$nextSeen.Add($key) }
|
||||
$nextDepth = $(if ($isAnon) { $depth } else { $depth - 1 })
|
||||
foreach ($r in (Get-PropRows $children $childPkg $nextDepth ($indent + 1) $nextSeen)) { [void]$rows.Add($r) }
|
||||
}
|
||||
}
|
||||
}
|
||||
return ,$rows
|
||||
}
|
||||
|
||||
# Оставить только обязательные свойства. Ребёнок необязательного объекта тоже
|
||||
# уходит: он лежит под необязательной веткой и заполнять его не обязательно.
|
||||
function Select-Required($rows) {
|
||||
$res = New-Object System.Collections.ArrayList
|
||||
$cutFrom = -1
|
||||
foreach ($r in $rows) {
|
||||
if ($cutFrom -ge 0 -and $r.Indent -gt $cutFrom) { continue }
|
||||
$cutFrom = -1
|
||||
if ($r.Flags -notcontains "обязательный") { $cutFrom = $r.Indent; continue }
|
||||
[void]$res.Add($r)
|
||||
}
|
||||
return ,$res
|
||||
}
|
||||
|
||||
function Write-Rows($rows) {
|
||||
if ($rows.Count -eq 0) { O " (нет свойств)"; return }
|
||||
$shown = $rows
|
||||
if ($Offset -gt 0 -or $rows.Count -gt $Limit) {
|
||||
$end = [Math]::Min($Offset + $Limit, $rows.Count) - 1
|
||||
if ($Offset -le $end) { $shown = $rows[$Offset..$end] } else { $shown = @() }
|
||||
}
|
||||
$wName = 0; $wType = 0
|
||||
foreach ($r in $shown) {
|
||||
$n = (" " * $r.Indent) + $r.Name
|
||||
if ($n.Length -gt $wName) { $wName = $n.Length }
|
||||
if ($r.Type.Length -gt $wType) { $wType = $r.Type.Length }
|
||||
}
|
||||
foreach ($r in $shown) {
|
||||
$n = (" " * $r.Indent) + $r.Name
|
||||
$line = " " + $n.PadRight($wName + 2) + $r.Type.PadRight($wType + 2)
|
||||
if ($r.Flags.Count -gt 0) { $line += "[" + ($r.Flags -join ", ") + "] " }
|
||||
if ($r.Notes.Count -gt 0) { $line += ($r.Notes -join ", ") }
|
||||
O $line.TrimEnd()
|
||||
}
|
||||
if ($rows.Count -gt $shown.Count) {
|
||||
O " … показано $($shown.Count) из $($rows.Count); листать через -Offset/-Limit"
|
||||
}
|
||||
}
|
||||
|
||||
# --- Modes ----------------------------------------------------------------------
|
||||
|
||||
function Show-PackageList {
|
||||
O "=== Пакеты XDTO: $($packages.Count) ==="
|
||||
O ""
|
||||
$shown = $packages
|
||||
if ($Offset -gt 0 -or $packages.Count -gt $Limit) {
|
||||
$end = [Math]::Min($Offset + $Limit, $packages.Count) - 1
|
||||
if ($Offset -le $end) { $shown = $packages[$Offset..$end] } else { $shown = @() }
|
||||
}
|
||||
$wn = 0
|
||||
foreach ($p in $shown) { if ($p.Name.Length -gt $wn) { $wn = $p.Name.Length } }
|
||||
foreach ($p in $shown) {
|
||||
$cnt = $p.Types.Count
|
||||
O (" " + $p.Name.PadRight($wn + 2) + "$cnt".PadLeft(4) + " " + $p.Namespace)
|
||||
}
|
||||
if ($packages.Count -gt $shown.Count) {
|
||||
O ""
|
||||
O " … показано $($shown.Count) из $($packages.Count); листать через -Offset/-Limit"
|
||||
}
|
||||
O ""
|
||||
O "Колонки: имя пакета, число типов, namespace."
|
||||
O "Следующий шаг: -Package <имя> или -Namespace <URI> — состав пакета; -Name <Тип> — поиск типа по всем пакетам"
|
||||
}
|
||||
|
||||
function Show-PackageOverview($pkg) {
|
||||
O "=== Пакет XDTO: $($pkg.Name) ==="
|
||||
O "Namespace: $($pkg.Namespace)"
|
||||
if ($pkg.Imports.Count -gt 0) {
|
||||
O ""
|
||||
O "Импорты ($($pkg.Imports.Count)):"
|
||||
foreach ($i in $pkg.Imports) {
|
||||
$dep = $(if ($byNamespace.ContainsKey($i)) { $byNamespace[$i].Name } else { "(пакет не найден)" })
|
||||
O " $i → $dep"
|
||||
}
|
||||
}
|
||||
if ($pkg.GlobalProps.Count -eq 0) {
|
||||
O ""
|
||||
O "Точки входа: нет — пакет не объявляет корневых элементов документа"
|
||||
} else {
|
||||
O ""
|
||||
O "Точки входа ($($pkg.GlobalProps.Count)) — корневые элементы документа:"
|
||||
foreach ($gp in $pkg.GlobalProps) {
|
||||
$q = Split-Ref $gp $gp.GetAttribute("type")
|
||||
$tn = $(if ($q) { Format-RefName $q $pkg } else { "произвольный" })
|
||||
$form = $(if ($gp.GetAttribute("form") -eq "Attribute") { " (атрибут)" } else { "" })
|
||||
O (" <" + $gp.GetAttribute("name") + "> → " + $tn + $form)
|
||||
}
|
||||
}
|
||||
$objs = @(); $vals = @()
|
||||
foreach ($k in (Sort-Ordinal $pkg.Types.Keys)) {
|
||||
if ($pkg.Types[$k].get_LocalName() -eq "objectType") { $objs += $k } else { $vals += $k }
|
||||
}
|
||||
if ($objs.Count -gt 0) {
|
||||
O ""
|
||||
O "Объектные типы ($($objs.Count)):"
|
||||
foreach ($n in $objs) {
|
||||
$cnt = 0
|
||||
foreach ($c in $pkg.Types[$n].ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "property") { $cnt++ }
|
||||
}
|
||||
$base = Split-Ref $pkg.Types[$n] $pkg.Types[$n].GetAttribute("base")
|
||||
$suffix = $(if ($base) { " ← $($base.Local)" } else { "" })
|
||||
O (" " + $n.PadRight(40) + "свойств: $cnt" + $suffix)
|
||||
}
|
||||
}
|
||||
if ($vals.Count -gt 0) {
|
||||
O ""
|
||||
O "Типы значений ($($vals.Count)):"
|
||||
foreach ($n in $vals) {
|
||||
$res = Resolve-Scalar $pkg.Types[$n] $pkg
|
||||
$line = " " + $n.PadRight(40) + (Format-Scalar $res)
|
||||
if ($res.Enum.Count -gt 0) { $line += " значения: " + (($res.Enum | Select-Object -First 6) -join ", ") }
|
||||
O $line
|
||||
}
|
||||
}
|
||||
O ""
|
||||
O "Следующий шаг: -Name <Тип> — структура типа для заполнения"
|
||||
}
|
||||
|
||||
# Легенда едет вместе с выводом, а не живёт в инструкции: показываем только те
|
||||
# обозначения, которые реально встретились, иначе она сама становится шумом.
|
||||
function Write-Legend($rows) {
|
||||
$text = ($rows | ForEach-Object { $_.Type + " " + ($_.Flags -join ",") + " " + ($_.Notes -join ",") }) -join " "
|
||||
$items = @()
|
||||
if ($text -match "объект ") { $items += "объект X — присвоить вложенный объект XDTO, состав раскрывает -Depth" }
|
||||
if ($text -match "←") { $items += "← Имя — исходный тип из схемы, слева от стрелки развёрнутое значение" }
|
||||
if ($text -match "список") { $items += "список — коллекция, заполняется через .Добавить()" }
|
||||
if ($text -match "до \d") { $items += "до N — коллекция с ограничением сверху" }
|
||||
if ($text -match "значение элемента") { $items += "значение элемента — собственное значение узла XML" }
|
||||
if ($text -match "·") { $items += "· Пакет — тип объявлен в другом пакете" }
|
||||
if ($items.Count -eq 0) { return }
|
||||
O ""
|
||||
O "Обозначения:"
|
||||
foreach ($i in $items) { O " $i" }
|
||||
}
|
||||
|
||||
function Show-Type($pkg, [string]$typeName) {
|
||||
$el = $pkg.Types[$typeName]
|
||||
$kind = $el.get_LocalName()
|
||||
if ($kind -eq "valueType") {
|
||||
$res = Resolve-Scalar $el $pkg
|
||||
O "=== Тип значения XDTO: $typeName ==="
|
||||
O "Пакет: $($pkg.Name) · $($pkg.Namespace)"
|
||||
O ""
|
||||
O "Значение: $(Format-Scalar $res)"
|
||||
foreach ($n in (Format-Notes $res)) { O " $n" }
|
||||
if ($res.Enum.Count -gt 0) {
|
||||
O ""
|
||||
O "Допустимые значения ($($res.Enum.Count)):"
|
||||
foreach ($v in $res.Enum) { O " $v" }
|
||||
}
|
||||
O ""
|
||||
O "Создание:"
|
||||
# Создать(<Тип>, <Значение>) принимает именно ТипЗначенияXDTO —
|
||||
# для объектного типа эта форма неприменима
|
||||
O " Значение = ФабрикаXDTO.Создать(ФабрикаXDTO.Тип(`"$($pkg.Namespace)`", `"$typeName`"), Значение);"
|
||||
return
|
||||
}
|
||||
|
||||
$hdr = "=== Тип XDTO: $typeName ==="
|
||||
if ($Depth -gt 1) { $hdr += " (глубина $Depth)" }
|
||||
O $hdr
|
||||
O "Пакет: $($pkg.Name) · $($pkg.Namespace)"
|
||||
$base = Split-Ref $el $el.GetAttribute("base")
|
||||
if ($base) { O "Наследует: $($base.Local)" }
|
||||
if ($el.GetAttribute("abstract") -eq "true") { O "Абстрактный — создаётся только тип-наследник" }
|
||||
if ($el.GetAttribute("open") -eq "true") { O "Открытый — допускает произвольные элементы и атрибуты" }
|
||||
O ""
|
||||
|
||||
$seen = New-Object System.Collections.Generic.HashSet[string]
|
||||
[void]$seen.Add("$($pkg.Namespace)#$typeName")
|
||||
$rows = Get-PropRows $el $pkg $Depth 0 $seen
|
||||
$own = @($rows | Where-Object { $_.Indent -eq 0 })
|
||||
if ($RequiredOnly) {
|
||||
$all = $rows.Count
|
||||
$rows = Select-Required $rows
|
||||
$ownReq = @($rows | Where-Object { $_.Indent -eq 0 })
|
||||
# Фильтр обязан сообщать о себе: иначе список читается как полный
|
||||
O "Свойства: обязательных $($ownReq.Count) из $($own.Count) (-RequiredOnly; скрыто строк: $($all - $rows.Count))"
|
||||
} else {
|
||||
O "Свойства ($($own.Count)):"
|
||||
}
|
||||
Write-Rows $rows
|
||||
Write-Legend $rows
|
||||
O ""
|
||||
O "Создание:"
|
||||
O " Тип = ФабрикаXDTO.Тип(`"$($pkg.Namespace)`", `"$typeName`");"
|
||||
O " Объект = ФабрикаXDTO.Создать(Тип);"
|
||||
# Рецепты для вложенных и анонимных типов: имени у анонимного нет, через
|
||||
# ФабрикаXDTO.Тип(ns, имя) его не получить — только от свойства владельца
|
||||
$named = @($rows | Where-Object { $_.ObjName -and $_.ObjName -ne "(анонимный)" } | Select-Object -First 1)
|
||||
if ($named.Count -gt 0) {
|
||||
O " // вложенный именованный тип — так же, как корневой:"
|
||||
O " $($named[0].Name) = ФабрикаXDTO.Создать(ФабрикаXDTO.Тип(`"$($named[0].ObjNs)`", `"$($named[0].ObjName)`"));"
|
||||
}
|
||||
$anonRow = @($rows | Where-Object { $_.ObjName -eq "(анонимный)" } | Select-Object -First 1)
|
||||
if ($anonRow.Count -gt 0) {
|
||||
O " // у анонимного типа нет имени — только через свойство владельца:"
|
||||
O " $($anonRow[0].Name) = ФабрикаXDTO.Создать(Тип.Свойства.Получить(`"$($anonRow[0].Name)`").Тип);"
|
||||
}
|
||||
$textRow = @($rows | Where-Object { $_.Flags -contains "значение элемента" } | Select-Object -First 1)
|
||||
if ($textRow.Count -gt 0) {
|
||||
O " // собственное значение узла лежит в свойстве $($textRow[0].Name)"
|
||||
}
|
||||
}
|
||||
|
||||
function Show-UsedBy([string]$typeName, $ownerPkg) {
|
||||
O "=== Ссылки на тип: $typeName ==="
|
||||
if ($ownerPkg) { O "Объявлен в: $($ownerPkg.Name) · $($ownerPkg.Namespace)" }
|
||||
O ""
|
||||
$hits = New-Object System.Collections.ArrayList
|
||||
foreach ($p in $packages) {
|
||||
foreach ($node in $p.Root.SelectNodes("//*")) {
|
||||
if ($node.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
|
||||
foreach ($a in @("type", "base", "itemType", "memberTypes")) {
|
||||
$raw = $node.GetAttribute($a)
|
||||
if (-not $raw) { continue }
|
||||
foreach ($one in ($raw -split "\s+")) {
|
||||
$q = Split-Ref $node $one
|
||||
if (-not $q -or $q.Local -ne $typeName) { continue }
|
||||
if ($ownerPkg -and $q.Ns -and $q.Ns -ne $ownerPkg.Namespace) { continue }
|
||||
$owner = $node
|
||||
while ($owner -and @("objectType", "valueType") -notcontains $owner.get_LocalName()) { $owner = $owner.ParentNode }
|
||||
$where = $(if ($owner -and $owner.GetAttribute("name")) { $owner.GetAttribute("name") } else { "(верхний уровень)" })
|
||||
$what = $(if ($node.GetAttribute("name")) { $node.GetAttribute("name") } else { $node.get_LocalName() })
|
||||
[void]$hits.Add(" $($p.Name).$where.$what ($a)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($hits.Count -eq 0) { O " Ссылок не найдено"; return }
|
||||
O "Найдено ($($hits.Count)):"
|
||||
foreach ($h in (Sort-Ordinal ($hits | Select-Object -Unique))) { O $h }
|
||||
}
|
||||
|
||||
# --- Dispatch -------------------------------------------------------------------
|
||||
|
||||
# Выбор пакета: явный путь, затем -Namespace / -Package, затем поиск типа по всем
|
||||
$selected = $null
|
||||
if ($directPkgDir) {
|
||||
$leaf = [System.IO.Path]::GetFileName($directPkgDir)
|
||||
$selected = $packages | Where-Object { $_.Name -eq $leaf } | Select-Object -First 1
|
||||
}
|
||||
if (-not $selected -and $Namespace) {
|
||||
$selected = $packages | Where-Object { $_.Namespace -eq $Namespace } | Select-Object -First 1
|
||||
if (-not $selected) { Fail "Пакет с namespace `"$Namespace`" не найден. Список: -PackagePath <корень> без параметров" }
|
||||
}
|
||||
if (-not $selected -and $Package) {
|
||||
$selected = $packages | Where-Object { $_.Name -eq $Package } | Select-Object -First 1
|
||||
if (-not $selected) { Fail "Пакет `"$Package`" не найден. Список: -PackagePath <корень> без параметров" }
|
||||
}
|
||||
|
||||
if ($Mode -eq "used-by") {
|
||||
if (-not $Name) { Fail "Режим used-by требует -Name <Тип>" }
|
||||
$ownerPkg = $selected
|
||||
if (-not $ownerPkg) { $ownerPkg = ($packages | Where-Object { $_.Types.ContainsKey($Name) } | Select-Object -First 1) }
|
||||
Show-UsedBy $Name $ownerPkg
|
||||
Flush-Output
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($Name) {
|
||||
if (-not $selected) {
|
||||
# Тип известен, пакет — нет: ищем по всей конфигурации
|
||||
$found = @($packages | Where-Object { $_.Types.ContainsKey($Name) })
|
||||
if ($found.Count -eq 0) { Fail "Тип `"$Name`" не найден ни в одном пакете конфигурации" }
|
||||
if ($found.Count -gt 1) {
|
||||
O "=== Тип `"$Name`" найден в нескольких пакетах ($($found.Count)) ==="
|
||||
O "Уточните через -Namespace или -Package:"
|
||||
O ""
|
||||
foreach ($f in $found) { O " $($f.Name) · $($f.Namespace)" }
|
||||
Flush-Output
|
||||
exit 0
|
||||
}
|
||||
$selected = $found[0]
|
||||
}
|
||||
if (-not $selected.Types.ContainsKey($Name)) {
|
||||
Fail "В пакете $($selected.Name) нет типа `"$Name`". Список типов: тот же вызов без -Name"
|
||||
}
|
||||
Show-Type $selected $Name
|
||||
Flush-Output
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($selected) { Show-PackageOverview $selected } else { Show-PackageList }
|
||||
Flush-Output
|
||||
exit 0
|
||||
@@ -0,0 +1,698 @@
|
||||
# xdto-info v1.0 — Analyze 1C XDTO package structure (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")
|
||||
|
||||
XS_NS = "http://www.w3.org/2001/XMLSchema"
|
||||
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
|
||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||
parser.add_argument("-PackagePath", "-Path", required=True)
|
||||
parser.add_argument("-Package", default="")
|
||||
parser.add_argument("-Namespace", default="")
|
||||
parser.add_argument("-Name", default="")
|
||||
parser.add_argument("-Mode", default="auto", choices=["auto", "used-by"])
|
||||
parser.add_argument("-Depth", type=int, default=1)
|
||||
parser.add_argument("-RequiredOnly", action="store_true")
|
||||
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()
|
||||
|
||||
LIMIT, OFFSET, DEPTH = args.Limit, args.Offset, args.Depth
|
||||
|
||||
lines = []
|
||||
|
||||
|
||||
def O(line=""):
|
||||
lines.append(line)
|
||||
|
||||
|
||||
def flush_output():
|
||||
text = "\n".join(lines).rstrip()
|
||||
if args.OutFile:
|
||||
d = os.path.dirname(args.OutFile)
|
||||
if d and not os.path.isdir(d):
|
||||
os.makedirs(d, exist_ok=True)
|
||||
with open(args.OutFile, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf" + (text + "\r\n").encode("utf-8"))
|
||||
print(f"✓ Записано: {args.OutFile}")
|
||||
else:
|
||||
print(text)
|
||||
|
||||
|
||||
def die(msg):
|
||||
# Отрицательный результат поиска — не исключение: сообщение и код 1,
|
||||
# без трассировки
|
||||
print(msg)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _parse_xml(source, from_string=False):
|
||||
"""Разбор с узким отступлением для не-URI пространств имён.
|
||||
|
||||
Платформа допускает в targetNamespace произвольную строку (в выгрузке БП есть
|
||||
пакет с кириллическим «ДопФайлУниверсальный»), .NET такое принимает, а libxml2
|
||||
отвергает. Откатываемся на восстанавливающий разбор ТОЛЬКО на этой ошибке,
|
||||
иначе по-настоящему битый XML перестал бы отличаться от корректного.
|
||||
"""
|
||||
try:
|
||||
return (etree.fromstring(source) if from_string else etree.parse(source))
|
||||
except etree.XMLSyntaxError as e:
|
||||
if "is not a valid URI" not in str(e):
|
||||
raise
|
||||
p = etree.XMLParser(recover=True)
|
||||
return (etree.fromstring(source, p) if from_string else etree.parse(source, p))
|
||||
|
||||
|
||||
def local(el):
|
||||
return etree.QName(el).localname
|
||||
|
||||
|
||||
# ── resolve path ─────────────────────────────────────────────
|
||||
|
||||
package_path = os.path.abspath(args.PackagePath)
|
||||
if not os.path.exists(package_path):
|
||||
die(f"Путь не найден: {package_path}")
|
||||
|
||||
config_root = None
|
||||
direct_pkg_dir = None
|
||||
|
||||
if os.path.exists(os.path.join(package_path, "Configuration.xml")):
|
||||
config_root = package_path
|
||||
elif os.path.basename(package_path.rstrip("\\/")) == "XDTOPackages":
|
||||
config_root = os.path.dirname(package_path.rstrip("\\/"))
|
||||
elif os.path.exists(os.path.join(package_path, "Ext", "Package.bin")):
|
||||
direct_pkg_dir = package_path
|
||||
config_root = os.path.dirname(os.path.dirname(package_path))
|
||||
elif os.path.isfile(package_path) and os.path.basename(package_path) == "Package.bin":
|
||||
direct_pkg_dir = os.path.dirname(os.path.dirname(package_path))
|
||||
config_root = os.path.dirname(os.path.dirname(direct_pkg_dir))
|
||||
elif package_path.endswith(".xml"):
|
||||
stem = os.path.join(os.path.dirname(package_path),
|
||||
os.path.splitext(os.path.basename(package_path))[0])
|
||||
if os.path.exists(os.path.join(stem, "Ext", "Package.bin")):
|
||||
direct_pkg_dir = stem
|
||||
config_root = os.path.dirname(os.path.dirname(stem))
|
||||
|
||||
if not config_root and not direct_pkg_dir:
|
||||
die(f"Не удалось определить пакет или конфигурацию по пути: {package_path}")
|
||||
|
||||
|
||||
# ── package index ────────────────────────────────────────────
|
||||
|
||||
class Pkg:
|
||||
__slots__ = ("Name", "Dir", "Namespace", "Root", "Imports", "Types", "GlobalProps")
|
||||
|
||||
|
||||
def read_package(pkg_dir):
|
||||
b = os.path.join(pkg_dir, "Ext", "Package.bin")
|
||||
if not os.path.exists(b):
|
||||
return None
|
||||
try:
|
||||
root = _parse_xml(b).getroot()
|
||||
except Exception: # noqa: BLE001
|
||||
return None
|
||||
if local(root) != "package":
|
||||
return None
|
||||
p = Pkg()
|
||||
p.Name = os.path.basename(pkg_dir.rstrip("\\/"))
|
||||
p.Dir = pkg_dir
|
||||
p.Namespace = root.get("targetNamespace")
|
||||
p.Root = root
|
||||
p.Imports = []
|
||||
p.Types = {}
|
||||
p.GlobalProps = []
|
||||
for n in root:
|
||||
if not isinstance(n.tag, str):
|
||||
continue
|
||||
ln = local(n)
|
||||
if ln == "import":
|
||||
p.Imports.append(n.get("namespace"))
|
||||
elif ln in ("objectType", "valueType"):
|
||||
p.Types[n.get("name")] = n
|
||||
elif ln == "property":
|
||||
p.GlobalProps.append(n)
|
||||
return p
|
||||
|
||||
|
||||
packages = []
|
||||
by_namespace = {}
|
||||
|
||||
if config_root and os.path.isdir(os.path.join(config_root, "XDTOPackages")):
|
||||
base = os.path.join(config_root, "XDTOPackages")
|
||||
for dn in sorted(d for d in os.listdir(base) if os.path.isdir(os.path.join(base, d))):
|
||||
p = read_package(os.path.join(base, dn))
|
||||
if p:
|
||||
packages.append(p)
|
||||
by_namespace.setdefault(p.Namespace, p)
|
||||
if direct_pkg_dir and not packages:
|
||||
p = read_package(direct_pkg_dir)
|
||||
if p:
|
||||
packages.append(p)
|
||||
by_namespace[p.Namespace] = p
|
||||
if not packages:
|
||||
die(f"Пакеты XDTO не найдены: {package_path}")
|
||||
|
||||
# ── type notation: XSD -> 1С ─────────────────────────────────
|
||||
|
||||
XS_TO_1C = {
|
||||
"string": "Строка", "normalizedString": "Строка", "token": "Строка", "NCName": "Строка",
|
||||
"Name": "Строка", "QName": "Строка", "anyURI": "Строка", "language": "Строка",
|
||||
"ID": "Строка", "IDREF": "Строка", "NMTOKEN": "Строка",
|
||||
"decimal": "Число", "integer": "Число", "int": "Число", "long": "Число", "short": "Число",
|
||||
"byte": "Число", "float": "Число", "double": "Число",
|
||||
"nonNegativeInteger": "Число", "positiveInteger": "Число", "nonPositiveInteger": "Число",
|
||||
"negativeInteger": "Число", "unsignedInt": "Число", "unsignedLong": "Число",
|
||||
"unsignedShort": "Число", "unsignedByte": "Число",
|
||||
"date": "Дата", "dateTime": "Дата", "time": "Дата",
|
||||
"boolean": "Булево",
|
||||
"base64Binary": "ДвоичныеДанные", "hexBinary": "ДвоичныеДанные",
|
||||
"anyType": "произвольный", "anySimpleType": "произвольный",
|
||||
}
|
||||
|
||||
FACET_NAMES = ["length", "minLength", "maxLength", "totalDigits", "fractionDigits",
|
||||
"minInclusive", "maxInclusive", "minExclusive", "maxExclusive"]
|
||||
|
||||
|
||||
def split_ref(el, raw):
|
||||
if not raw:
|
||||
return None
|
||||
if raw.startswith("{"):
|
||||
close = raw.find("}")
|
||||
if close < 0:
|
||||
return None
|
||||
return (raw[1:close], raw[close + 1:])
|
||||
parts = raw.split(":")
|
||||
if len(parts) == 2:
|
||||
return (el.nsmap.get(parts[0]), parts[1])
|
||||
return (None, parts[0])
|
||||
|
||||
|
||||
def get_facets(t):
|
||||
res = {}
|
||||
for f in FACET_NAMES:
|
||||
v = t.get(f)
|
||||
if v:
|
||||
res[f] = v
|
||||
for c in t:
|
||||
if isinstance(c.tag, str) and local(c) == "pattern":
|
||||
res["pattern"] = c.text or ""
|
||||
break
|
||||
return res
|
||||
|
||||
|
||||
def get_enumerations(t):
|
||||
return [(c.text or "") for c in t if isinstance(c.tag, str) and local(c) == "enumeration"]
|
||||
|
||||
|
||||
def find_type(q, pkg):
|
||||
if not q:
|
||||
return None
|
||||
ns, loc = q
|
||||
target = None
|
||||
if not ns or (pkg and ns == pkg.Namespace):
|
||||
target = pkg
|
||||
elif ns in by_namespace:
|
||||
target = by_namespace[ns]
|
||||
if not target or loc not in target.Types:
|
||||
return None
|
||||
return (target.Types[loc], target)
|
||||
|
||||
|
||||
def format_ref_name(q, pkg):
|
||||
if not q:
|
||||
return ""
|
||||
ns, loc = q
|
||||
if ns == XS_NS:
|
||||
return XS_TO_1C.get(loc, f"xs:{loc}")
|
||||
return loc
|
||||
|
||||
|
||||
def resolve_scalar(t, pkg, guard=0):
|
||||
acc = {"Base1C": None, "Facets": {}, "Alias": None, "Enum": [], "Kind": "scalar"}
|
||||
if guard > 10 or t is None:
|
||||
return acc
|
||||
|
||||
variety = t.get("variety")
|
||||
if variety == "List":
|
||||
it = split_ref(t, t.get("itemType"))
|
||||
acc["Kind"] = "list"
|
||||
acc["Base1C"] = "список " + (format_ref_name(it, pkg) if it else "значений")
|
||||
return acc
|
||||
if variety == "Union" or t.get("memberTypes"):
|
||||
members = []
|
||||
for m in (t.get("memberTypes") or "").split():
|
||||
q = split_ref(t, m)
|
||||
if q:
|
||||
members.append(format_ref_name(q, pkg))
|
||||
for c in t:
|
||||
if isinstance(c.tag, str) and local(c) == "typeDef":
|
||||
members.append(resolve_scalar(c, pkg, guard + 1)["Base1C"])
|
||||
acc["Kind"] = "union"
|
||||
acc["Base1C"] = "одно из (" + " | ".join(m for m in members if m) + ")"
|
||||
return acc
|
||||
|
||||
acc["Facets"] = get_facets(t)
|
||||
acc["Enum"] = get_enumerations(t)
|
||||
|
||||
base_q = split_ref(t, t.get("base"))
|
||||
anon_base = next((c for c in t if isinstance(c.tag, str) and local(c) == "typeDef"), None)
|
||||
if not base_q and anon_base is not None:
|
||||
inner = resolve_scalar(anon_base, pkg, guard + 1)
|
||||
acc["Base1C"] = inner["Base1C"]
|
||||
for k, v in inner["Facets"].items():
|
||||
acc["Facets"].setdefault(k, v)
|
||||
if inner["Enum"] and not acc["Enum"]:
|
||||
acc["Enum"] = inner["Enum"]
|
||||
return acc
|
||||
if not base_q:
|
||||
acc["Base1C"] = "произвольный"
|
||||
return acc
|
||||
|
||||
if base_q[0] == XS_NS:
|
||||
acc["Base1C"] = XS_TO_1C.get(base_q[1], f"xs:{base_q[1]}")
|
||||
return acc
|
||||
|
||||
target = find_type(base_q, pkg)
|
||||
if target and local(target[0]) == "valueType":
|
||||
inner = resolve_scalar(target[0], target[1], guard + 1)
|
||||
acc["Base1C"] = inner["Base1C"]
|
||||
for k, v in inner["Facets"].items():
|
||||
acc["Facets"].setdefault(k, v)
|
||||
if inner["Enum"] and not acc["Enum"]:
|
||||
acc["Enum"] = inner["Enum"]
|
||||
if not acc["Alias"]:
|
||||
acc["Alias"] = base_q[1]
|
||||
return acc
|
||||
acc["Base1C"] = base_q[1]
|
||||
return acc
|
||||
|
||||
|
||||
def format_scalar(res):
|
||||
t, f = res["Base1C"], res["Facets"]
|
||||
if t == "Строка":
|
||||
if "length" in f:
|
||||
t = f'Строка({f["length"]})'
|
||||
elif "maxLength" in f:
|
||||
t = f'Строка({f["maxLength"]})'
|
||||
elif t == "Число":
|
||||
if "totalDigits" in f:
|
||||
t = f'Число({f["totalDigits"]},{f.get("fractionDigits", "0")})'
|
||||
return t
|
||||
|
||||
|
||||
def format_notes(res):
|
||||
notes = []
|
||||
if res["Alias"]:
|
||||
notes.append("← " + res["Alias"])
|
||||
if "pattern" in res["Facets"]:
|
||||
p = res["Facets"]["pattern"]
|
||||
if len(p) > 40:
|
||||
p = p[:40] + "…"
|
||||
notes.append("шаблон " + p)
|
||||
for k in ("minInclusive", "maxInclusive", "minExclusive", "maxExclusive"):
|
||||
if k in res["Facets"]:
|
||||
notes.append(f'{k} {res["Facets"][k]}')
|
||||
return notes
|
||||
|
||||
|
||||
# ── property rendering ───────────────────────────────────────
|
||||
|
||||
def get_prop_rows(type_el, pkg, depth, indent, seen):
|
||||
rows = []
|
||||
for p in type_el:
|
||||
if not isinstance(p.tag, str) or local(p) != "property":
|
||||
continue
|
||||
|
||||
pname = p.get("name")
|
||||
if not pname:
|
||||
ref_q = split_ref(p, p.get("ref"))
|
||||
pname = ref_q[1] if ref_q else "(без имени)"
|
||||
lower, upper = p.get("lowerBound"), p.get("upperBound")
|
||||
flags = []
|
||||
# В модели умолчание lowerBound = 1; помечаем обязательные, как в meta-info
|
||||
if lower != "0":
|
||||
flags.append("обязательный")
|
||||
if upper == "-1":
|
||||
flags.append("список")
|
||||
elif upper and upper != "1":
|
||||
flags.append("до " + upper)
|
||||
if p.get("form") == "Text":
|
||||
flags.append("значение элемента")
|
||||
|
||||
notes = []
|
||||
type_text = ""
|
||||
children = None
|
||||
child_pkg = pkg
|
||||
# Именованный вложенный тип берётся так же, как корневой; окольный путь
|
||||
# через свойство владельца нужен только анонимному — имени у него нет
|
||||
obj_name = None
|
||||
obj_ns = None
|
||||
|
||||
anon = next((c for c in p if isinstance(c.tag, str) and local(c) == "typeDef"), None)
|
||||
|
||||
if anon is not None:
|
||||
if anon.get(f"{{{XSI_NS}}}type") == "ObjectType":
|
||||
type_text = "объект (анонимный)"
|
||||
obj_name = "(анонимный)"
|
||||
children = anon # анонимные раскрываем всегда: смотреть отдельно негде
|
||||
else:
|
||||
res = resolve_scalar(anon, pkg)
|
||||
type_text = format_scalar(res)
|
||||
notes += format_notes(res)
|
||||
if res["Enum"]:
|
||||
notes.append("значения: " + ", ".join(res["Enum"][:8]))
|
||||
else:
|
||||
q = split_ref(p, p.get("type"))
|
||||
if not q:
|
||||
type_text = "произвольный"
|
||||
elif q[0] == XS_NS:
|
||||
type_text = XS_TO_1C.get(q[1], f"xs:{q[1]}")
|
||||
else:
|
||||
target = find_type(q, pkg)
|
||||
if not target:
|
||||
type_text = "объект " + q[1]
|
||||
notes.append(f"(пакет не найден: {q[0]})")
|
||||
elif local(target[0]) == "objectType":
|
||||
type_text = "объект " + q[1]
|
||||
if target[1].Namespace != pkg.Namespace:
|
||||
type_text += " · " + target[1].Name
|
||||
obj_name = q[1]
|
||||
obj_ns = target[1].Namespace
|
||||
children = target[0]
|
||||
child_pkg = target[1]
|
||||
else:
|
||||
res = resolve_scalar(target[0], target[1])
|
||||
type_text = format_scalar(res)
|
||||
notes.append("← " + q[1])
|
||||
notes += [n for n in format_notes(res) if not n.startswith("←")]
|
||||
if res["Enum"]:
|
||||
notes.append("значения: " + ", ".join(res["Enum"][:8]))
|
||||
|
||||
rows.append({"Indent": indent, "Name": pname, "Type": type_text,
|
||||
"Flags": flags, "Notes": [n for n in notes if n],
|
||||
"ObjName": obj_name, "ObjNs": obj_ns})
|
||||
|
||||
if children is not None:
|
||||
cname = children.get("name")
|
||||
key = f"{child_pkg.Namespace}#{cname}"
|
||||
is_anon = not cname
|
||||
if not is_anon and key in seen:
|
||||
rows.append({"Indent": indent + 1, "Name": "(раскрыт выше)", "Type": "",
|
||||
"Flags": [], "Notes": [], "ObjName": None, "ObjNs": None})
|
||||
elif is_anon or depth > 1:
|
||||
next_seen = set(seen)
|
||||
if not is_anon:
|
||||
next_seen.add(key)
|
||||
next_depth = depth if is_anon else depth - 1
|
||||
rows += get_prop_rows(children, child_pkg, next_depth, indent + 1, next_seen)
|
||||
return rows
|
||||
|
||||
|
||||
# Оставить только обязательные свойства. Ребёнок необязательного объекта тоже
|
||||
# уходит: он лежит под необязательной веткой и заполнять его не обязательно.
|
||||
def select_required(rows):
|
||||
res = []
|
||||
cut_from = -1
|
||||
for r in rows:
|
||||
if cut_from >= 0 and r["Indent"] > cut_from:
|
||||
continue
|
||||
cut_from = -1
|
||||
if "обязательный" not in r["Flags"]:
|
||||
cut_from = r["Indent"]
|
||||
continue
|
||||
res.append(r)
|
||||
return res
|
||||
|
||||
|
||||
def write_rows(rows):
|
||||
if not rows:
|
||||
O(" (нет свойств)")
|
||||
return
|
||||
shown = rows
|
||||
if OFFSET > 0 or len(rows) > LIMIT:
|
||||
shown = rows[OFFSET:OFFSET + LIMIT]
|
||||
w_name = max((len(" " * r["Indent"] + r["Name"]) for r in shown), default=0)
|
||||
w_type = max((len(r["Type"]) for r in shown), default=0)
|
||||
for r in shown:
|
||||
n = " " * r["Indent"] + r["Name"]
|
||||
line = " " + n.ljust(w_name + 2) + r["Type"].ljust(w_type + 2)
|
||||
if r["Flags"]:
|
||||
line += "[" + ", ".join(r["Flags"]) + "] "
|
||||
if r["Notes"]:
|
||||
line += ", ".join(r["Notes"])
|
||||
O(line.rstrip())
|
||||
if len(rows) > len(shown):
|
||||
O(f" … показано {len(shown)} из {len(rows)}; листать через -Offset/-Limit")
|
||||
|
||||
|
||||
# ── modes ────────────────────────────────────────────────────
|
||||
|
||||
def show_package_list():
|
||||
O(f"=== Пакеты XDTO: {len(packages)} ===")
|
||||
O("")
|
||||
shown = packages
|
||||
if OFFSET > 0 or len(packages) > LIMIT:
|
||||
shown = packages[OFFSET:OFFSET + LIMIT]
|
||||
wn = max((len(p.Name) for p in shown), default=0)
|
||||
for p in shown:
|
||||
O(" " + p.Name.ljust(wn + 2) + str(len(p.Types)).rjust(4) + " " + p.Namespace)
|
||||
if len(packages) > len(shown):
|
||||
O("")
|
||||
O(f" … показано {len(shown)} из {len(packages)}; листать через -Offset/-Limit")
|
||||
O("")
|
||||
O("Колонки: имя пакета, число типов, namespace.")
|
||||
O("Следующий шаг: -Package <имя> или -Namespace <URI> — состав пакета; "
|
||||
"-Name <Тип> — поиск типа по всем пакетам")
|
||||
|
||||
|
||||
def show_package_overview(pkg):
|
||||
O(f"=== Пакет XDTO: {pkg.Name} ===")
|
||||
O(f"Namespace: {pkg.Namespace}")
|
||||
if pkg.Imports:
|
||||
O("")
|
||||
O(f"Импорты ({len(pkg.Imports)}):")
|
||||
for i in pkg.Imports:
|
||||
dep = by_namespace[i].Name if i in by_namespace else "(пакет не найден)"
|
||||
O(f" {i} → {dep}")
|
||||
if not pkg.GlobalProps:
|
||||
O("")
|
||||
O("Точки входа: нет — пакет не объявляет корневых элементов документа")
|
||||
else:
|
||||
O("")
|
||||
O(f"Точки входа ({len(pkg.GlobalProps)}) — корневые элементы документа:")
|
||||
for gp in pkg.GlobalProps:
|
||||
q = split_ref(gp, gp.get("type"))
|
||||
tn = format_ref_name(q, pkg) if q else "произвольный"
|
||||
form = " (атрибут)" if gp.get("form") == "Attribute" else ""
|
||||
O(f' <{gp.get("name")}> → {tn}{form}')
|
||||
objs = [k for k in sorted(pkg.Types) if local(pkg.Types[k]) == "objectType"]
|
||||
vals = [k for k in sorted(pkg.Types) if local(pkg.Types[k]) == "valueType"]
|
||||
if objs:
|
||||
O("")
|
||||
O(f"Объектные типы ({len(objs)}):")
|
||||
for n in objs:
|
||||
cnt = sum(1 for c in pkg.Types[n] if isinstance(c.tag, str) and local(c) == "property")
|
||||
base = split_ref(pkg.Types[n], pkg.Types[n].get("base"))
|
||||
suffix = f" ← {base[1]}" if base else ""
|
||||
O(" " + n.ljust(40) + f"свойств: {cnt}" + suffix)
|
||||
if vals:
|
||||
O("")
|
||||
O(f"Типы значений ({len(vals)}):")
|
||||
for n in vals:
|
||||
res = resolve_scalar(pkg.Types[n], pkg)
|
||||
line = " " + n.ljust(40) + format_scalar(res)
|
||||
if res["Enum"]:
|
||||
line += " значения: " + ", ".join(res["Enum"][:6])
|
||||
O(line)
|
||||
O("")
|
||||
O("Следующий шаг: -Name <Тип> — структура типа для заполнения")
|
||||
|
||||
|
||||
# Легенда едет вместе с выводом, а не живёт в инструкции: показываем только те
|
||||
# обозначения, которые реально встретились, иначе она сама становится шумом.
|
||||
def write_legend(rows):
|
||||
text = " ".join(r["Type"] + " " + ",".join(r["Flags"]) + " " + ",".join(r["Notes"]) for r in rows)
|
||||
items = []
|
||||
if "объект " in text:
|
||||
items.append("объект X — присвоить вложенный объект XDTO, состав раскрывает -Depth")
|
||||
if "←" in text:
|
||||
items.append("← Имя — исходный тип из схемы, слева от стрелки развёрнутое значение")
|
||||
if "список" in text:
|
||||
items.append("список — коллекция, заполняется через .Добавить()")
|
||||
if re.search(r"до \d", text):
|
||||
items.append("до N — коллекция с ограничением сверху")
|
||||
if "значение элемента" in text:
|
||||
items.append("значение элемента — собственное значение узла XML")
|
||||
if "·" in text:
|
||||
items.append("· Пакет — тип объявлен в другом пакете")
|
||||
if not items:
|
||||
return
|
||||
O("")
|
||||
O("Обозначения:")
|
||||
for i in items:
|
||||
O(" " + i)
|
||||
|
||||
|
||||
def show_type(pkg, type_name):
|
||||
el = pkg.Types[type_name]
|
||||
if local(el) == "valueType":
|
||||
res = resolve_scalar(el, pkg)
|
||||
O(f"=== Тип значения XDTO: {type_name} ===")
|
||||
O(f"Пакет: {pkg.Name} · {pkg.Namespace}")
|
||||
O("")
|
||||
O("Значение: " + format_scalar(res))
|
||||
for n in format_notes(res):
|
||||
O(" " + n)
|
||||
if res["Enum"]:
|
||||
O("")
|
||||
O(f'Допустимые значения ({len(res["Enum"])}):')
|
||||
for v in res["Enum"]:
|
||||
O(" " + v)
|
||||
O("")
|
||||
O("Создание:")
|
||||
# Создать(<Тип>, <Значение>) принимает именно ТипЗначенияXDTO —
|
||||
# для объектного типа эта форма неприменима
|
||||
O(f' Значение = ФабрикаXDTO.Создать(ФабрикаXDTO.Тип("{pkg.Namespace}", "{type_name}"), Значение);')
|
||||
return
|
||||
|
||||
hdr = f"=== Тип XDTO: {type_name} ==="
|
||||
if DEPTH > 1:
|
||||
hdr += f" (глубина {DEPTH})"
|
||||
O(hdr)
|
||||
O(f"Пакет: {pkg.Name} · {pkg.Namespace}")
|
||||
base = split_ref(el, el.get("base"))
|
||||
if base:
|
||||
O("Наследует: " + base[1])
|
||||
if el.get("abstract") == "true":
|
||||
O("Абстрактный — создаётся только тип-наследник")
|
||||
if el.get("open") == "true":
|
||||
O("Открытый — допускает произвольные элементы и атрибуты")
|
||||
O("")
|
||||
|
||||
seen = {f"{pkg.Namespace}#{type_name}"}
|
||||
rows = get_prop_rows(el, pkg, DEPTH, 0, seen)
|
||||
own = [r for r in rows if r["Indent"] == 0]
|
||||
if args.RequiredOnly:
|
||||
total = len(rows)
|
||||
rows = select_required(rows)
|
||||
own_req = [r for r in rows if r["Indent"] == 0]
|
||||
# Фильтр обязан сообщать о себе: иначе список читается как полный
|
||||
O(f"Свойства: обязательных {len(own_req)} из {len(own)} "
|
||||
f"(-RequiredOnly; скрыто строк: {total - len(rows)})")
|
||||
else:
|
||||
O(f"Свойства ({len(own)}):")
|
||||
write_rows(rows)
|
||||
write_legend(rows)
|
||||
O("")
|
||||
O("Создание:")
|
||||
O(f' Тип = ФабрикаXDTO.Тип("{pkg.Namespace}", "{type_name}");')
|
||||
O(" Объект = ФабрикаXDTO.Создать(Тип);")
|
||||
# Рецепты для вложенных и анонимных типов: имени у анонимного нет, через
|
||||
# ФабрикаXDTO.Тип(ns, имя) его не получить — только от свойства владельца
|
||||
named = next((r for r in rows if r.get("ObjName") and r.get("ObjName") != "(анонимный)"), None)
|
||||
if named:
|
||||
O(" // вложенный именованный тип — так же, как корневой:")
|
||||
O(f' {named["Name"]} = ФабрикаXDTO.Создать(ФабрикаXDTO.Тип("{named["ObjNs"]}", "{named["ObjName"]}"));')
|
||||
anon_row = next((r for r in rows if r.get("ObjName") == "(анонимный)"), None)
|
||||
if anon_row:
|
||||
O(" // у анонимного типа нет имени — только через свойство владельца:")
|
||||
O(f' {anon_row["Name"]} = ФабрикаXDTO.Создать(Тип.Свойства.Получить("{anon_row["Name"]}").Тип);')
|
||||
text_row = next((r for r in rows if "значение элемента" in r["Flags"]), None)
|
||||
if text_row:
|
||||
O(f' // собственное значение узла лежит в свойстве {text_row["Name"]}')
|
||||
|
||||
|
||||
def show_used_by(type_name, owner_pkg):
|
||||
O(f"=== Ссылки на тип: {type_name} ===")
|
||||
if owner_pkg:
|
||||
O(f"Объявлен в: {owner_pkg.Name} · {owner_pkg.Namespace}")
|
||||
O("")
|
||||
hits = []
|
||||
for p in packages:
|
||||
for node in p.Root.iter():
|
||||
if not isinstance(node.tag, str):
|
||||
continue
|
||||
for a in ("type", "base", "itemType", "memberTypes"):
|
||||
raw = node.get(a)
|
||||
if not raw:
|
||||
continue
|
||||
for one in raw.split():
|
||||
q = split_ref(node, one)
|
||||
if not q or q[1] != type_name:
|
||||
continue
|
||||
if owner_pkg and q[0] and q[0] != owner_pkg.Namespace:
|
||||
continue
|
||||
owner = node
|
||||
while owner is not None and local(owner) not in ("objectType", "valueType"):
|
||||
owner = owner.getparent()
|
||||
where = owner.get("name") if (owner is not None and owner.get("name")) else "(верхний уровень)"
|
||||
what = node.get("name") or local(node)
|
||||
hits.append(f" {p.Name}.{where}.{what} ({a})")
|
||||
if not hits:
|
||||
O(" Ссылок не найдено")
|
||||
return
|
||||
O(f"Найдено ({len(hits)}):")
|
||||
for h in sorted(set(hits)):
|
||||
O(h)
|
||||
|
||||
|
||||
# ── dispatch ─────────────────────────────────────────────────
|
||||
|
||||
selected = None
|
||||
if direct_pkg_dir:
|
||||
leaf = os.path.basename(direct_pkg_dir.rstrip("\\/"))
|
||||
selected = next((p for p in packages if p.Name == leaf), None)
|
||||
if not selected and args.Namespace:
|
||||
selected = next((p for p in packages if p.Namespace == args.Namespace), None)
|
||||
if not selected:
|
||||
die(f'Пакет с namespace "{args.Namespace}" не найден. '
|
||||
"Список: -PackagePath <корень> без параметров")
|
||||
if not selected and args.Package:
|
||||
selected = next((p for p in packages if p.Name == args.Package), None)
|
||||
if not selected:
|
||||
die(f'Пакет "{args.Package}" не найден. Список: -PackagePath <корень> без параметров')
|
||||
|
||||
if args.Mode == "used-by":
|
||||
if not args.Name:
|
||||
die("Режим used-by требует -Name <Тип>")
|
||||
owner = selected or next((p for p in packages if args.Name in p.Types), None)
|
||||
show_used_by(args.Name, owner)
|
||||
flush_output()
|
||||
sys.exit(0)
|
||||
|
||||
if args.Name:
|
||||
if not selected:
|
||||
# Тип известен, пакет — нет: ищем по всей конфигурации
|
||||
found = [p for p in packages if args.Name in p.Types]
|
||||
if not found:
|
||||
die(f'Тип "{args.Name}" не найден ни в одном пакете конфигурации')
|
||||
if len(found) > 1:
|
||||
O(f'=== Тип "{args.Name}" найден в нескольких пакетах ({len(found)}) ===')
|
||||
O("Уточните через -Namespace или -Package:")
|
||||
O("")
|
||||
for f in found:
|
||||
O(f" {f.Name} · {f.Namespace}")
|
||||
flush_output()
|
||||
sys.exit(0)
|
||||
selected = found[0]
|
||||
if args.Name not in selected.Types:
|
||||
die(f'В пакете {selected.Name} нет типа "{args.Name}". Список типов: тот же вызов без -Name')
|
||||
show_type(selected, args.Name)
|
||||
flush_output()
|
||||
sys.exit(0)
|
||||
|
||||
if selected:
|
||||
show_package_overview(selected)
|
||||
else:
|
||||
show_package_list()
|
||||
flush_output()
|
||||
sys.exit(0)
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: xdto-validate
|
||||
description: Валидация пакета XDTO 1С. Используй после создания или модификации пакета XDTO для проверки корректности
|
||||
argument-hint: <PackagePath> [-ConfigDir <каталог>] [-Detailed] [-MaxErrors N] [-OutFile <файл>]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /xdto-validate — Валидация пакета XDTO
|
||||
|
||||
Проверяет модель пакета, объект метаданных и его связь с конфигурацией.
|
||||
Каждая находка выводится отдельной строкой с объяснением. Exit code `1` при ошибках.
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обязательный | Описание |
|
||||
|----------|--------------|----------|
|
||||
| `PackagePath` | да | Каталог пакета, `Ext/Package.bin` или `<Имя>.xml` объекта метаданных. Псевдоним — `-Path` |
|
||||
| `ConfigDir` | нет | Корень исходников. По умолчанию определяется по расположению пакета |
|
||||
| `Detailed` | нет | Показывать успешные проверки, а не только проблемы |
|
||||
| `MaxErrors` | нет | Остановиться после N ошибок. По умолчанию 20 |
|
||||
| `OutFile` | нет | Записать отчёт в файл |
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-validate.ps1" -PackagePath "<путь>"
|
||||
```
|
||||
|
||||
`[ERROR]` — платформа такой пакет не примет либо примет неправильно.
|
||||
`[WARN]` — пакет рабочий, но есть риск, о котором стоит знать.
|
||||
|
||||
## Зачем запускать, если пакет и так грузится
|
||||
|
||||
Часть дефектов платформа не диагностирует: неразрешённый тип из чужого пространства
|
||||
имён она молча подменяет на `xs:anyType`, и пакет выглядит загруженным, пока
|
||||
`ФабрикаXDTO` не отдаст в рантайме бесструктурное значение. Такие вещи видно только
|
||||
статически — до загрузки в базу.
|
||||
|
||||
## Типичный workflow
|
||||
|
||||
1. `/xdto-compile`, `/xdto-edit` или переработка через `/xdto-decompile` → `/xdto-compile -Force`
|
||||
2. `/xdto-validate <путь>` — до загрузки в базу
|
||||
3. `/db-load-xml` + `/db-update`
|
||||
@@ -0,0 +1,566 @@
|
||||
# xdto-validate v1.1 — Validate a 1C XDTO package
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$PackagePath,
|
||||
|
||||
[string]$ConfigDir,
|
||||
|
||||
[switch]$Detailed,
|
||||
|
||||
[int]$MaxErrors = 20,
|
||||
|
||||
[string]$OutFile
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Эти пространства имён предоставляет сама платформа — пакетов в конфигурации
|
||||
# для них нет и быть не должно (выведено по корпусу: импортируются, но
|
||||
# targetNamespace с таким значением ни у одного пакета нет)
|
||||
$PLATFORM_NS = @(
|
||||
"http://v8.1c.ru/8.1/data/core",
|
||||
"http://v8.1c.ru/8.1/data/enterprise",
|
||||
"http://v8.1c.ru/8.1/data/enterprise/current-config",
|
||||
"http://v8.1c.ru/8.1/data-composition-system/settings",
|
||||
"http://v8.1c.ru/8.3/data/ext",
|
||||
"http://www.w3.org/2001/XMLSchema"
|
||||
)
|
||||
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$XS_NS = "http://www.w3.org/2001/XMLSchema"
|
||||
$XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
$MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
||||
|
||||
# --- Reporting ---
|
||||
|
||||
$script:errors = 0
|
||||
$script:warnings = 0
|
||||
$script:okCount = 0
|
||||
$script:stopped = $false
|
||||
$script:output = New-Object System.Text.StringBuilder
|
||||
|
||||
function Out-Line([string]$s) { [void]$script:output.AppendLine($s) }
|
||||
function Report-OK([string]$msg) {
|
||||
$script:okCount++
|
||||
if ($Detailed) { Out-Line "[OK] $msg" }
|
||||
}
|
||||
function Report-Error([string]$msg) {
|
||||
$script:errors++
|
||||
Out-Line "[ERROR] $msg"
|
||||
if ($script:errors -ge $MaxErrors) { $script:stopped = $true }
|
||||
}
|
||||
function Report-Warn([string]$msg) {
|
||||
$script:warnings++
|
||||
Out-Line "[WARN] $msg"
|
||||
}
|
||||
|
||||
# --- Resolve paths ---
|
||||
|
||||
if (-not [System.IO.Path]::IsPathRooted($PackagePath)) {
|
||||
$PackagePath = Join-Path (Get-Location).Path $PackagePath
|
||||
}
|
||||
|
||||
$binPath = $null
|
||||
$mdPath = $null
|
||||
if (Test-Path $PackagePath -PathType Leaf) {
|
||||
if ([System.IO.Path]::GetFileName($PackagePath) -eq "Package.bin") {
|
||||
$binPath = $PackagePath
|
||||
$pkgDir = [System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($PackagePath))
|
||||
if (Test-Path "$pkgDir.xml") { $mdPath = "$pkgDir.xml" }
|
||||
} elseif ($PackagePath.EndsWith(".xml")) {
|
||||
$mdPath = $PackagePath
|
||||
$stem = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($PackagePath), [System.IO.Path]::GetFileNameWithoutExtension($PackagePath))
|
||||
$c = Join-Path (Join-Path $stem "Ext") "Package.bin"
|
||||
if (Test-Path $c) { $binPath = $c }
|
||||
}
|
||||
} elseif (Test-Path $PackagePath -PathType Container) {
|
||||
$c = Join-Path (Join-Path $PackagePath "Ext") "Package.bin"
|
||||
if (Test-Path $c) {
|
||||
$binPath = $c
|
||||
$m = "$($PackagePath.TrimEnd('\','/')).xml"
|
||||
if (Test-Path $m) { $mdPath = $m }
|
||||
}
|
||||
}
|
||||
|
||||
$fileName = if ($binPath) { [System.IO.Path]::GetFileName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($binPath))) } else { $PackagePath }
|
||||
|
||||
if (-not $binPath) {
|
||||
Write-Host "[ERROR] Не найден Ext/Package.bin для пути: $PackagePath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Каталог конфигурации — для проверок регистрации и зависимостей
|
||||
if (-not $ConfigDir) {
|
||||
$d = [System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($binPath)))
|
||||
# .../XDTOPackages/<Имя>/Ext/Package.bin -> .../XDTOPackages -> корень
|
||||
$ConfigDir = [System.IO.Path]::GetDirectoryName($d)
|
||||
}
|
||||
|
||||
$finalize = {
|
||||
$checks = $script:okCount + $script:errors + $script:warnings
|
||||
if ($script:errors -eq 0 -and $script:warnings -eq 0 -and -not $Detailed) {
|
||||
$result = "=== Validation OK: $fileName ($checks checks) ==="
|
||||
} else {
|
||||
Out-Line ""
|
||||
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ($checks checks) ==="
|
||||
$result = $script:output.ToString()
|
||||
}
|
||||
Write-Host $result
|
||||
if ($OutFile) {
|
||||
$utf8Bom = New-Object System.Text.UTF8Encoding $true
|
||||
[System.IO.File]::WriteAllText($OutFile, $script:output.ToString(), $utf8Bom)
|
||||
}
|
||||
}
|
||||
|
||||
# --- 1. Well-formedness ---
|
||||
|
||||
$doc = New-Object System.Xml.XmlDocument
|
||||
try {
|
||||
$doc.Load($binPath)
|
||||
} catch {
|
||||
Report-Error "Package.bin не является корректным XML: $($_.Exception.Message)"
|
||||
& $finalize
|
||||
exit 1
|
||||
}
|
||||
$pkg = $doc.DocumentElement
|
||||
if ($pkg.get_LocalName() -ne "package") {
|
||||
Report-Error "Ожидался корневой <package>, найден <$($pkg.get_LocalName())>"
|
||||
& $finalize
|
||||
exit 1
|
||||
}
|
||||
Report-OK "Package.bin: корректный XML, корень <package>"
|
||||
|
||||
$targetNs = $pkg.GetAttribute("targetNamespace")
|
||||
if (-not $targetNs) {
|
||||
Report-Error "У <package> не задан targetNamespace"
|
||||
} else {
|
||||
Report-OK "targetNamespace: $targetNs"
|
||||
}
|
||||
|
||||
# --- 2. Encoding / EOL ---
|
||||
|
||||
$bytes = [System.IO.File]::ReadAllBytes($binPath)
|
||||
if (-not ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF)) {
|
||||
Report-Warn "Package.bin без BOM UTF-8 — платформа пишет файл с BOM"
|
||||
} else {
|
||||
Report-OK "Кодировка: UTF-8 с BOM"
|
||||
}
|
||||
|
||||
# --- 3. Collect declarations ---
|
||||
|
||||
$imports = New-Object System.Collections.ArrayList
|
||||
$localTypes = New-Object System.Collections.Generic.HashSet[string]
|
||||
$objectTypeNames = New-Object System.Collections.Generic.HashSet[string]
|
||||
$valueTypeNames = New-Object System.Collections.Generic.HashSet[string]
|
||||
$globalProps = New-Object System.Collections.Generic.HashSet[string]
|
||||
$topSequence = New-Object System.Collections.ArrayList
|
||||
|
||||
foreach ($n in $pkg.ChildNodes) {
|
||||
if ($n.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
|
||||
[void]$topSequence.Add($n.get_LocalName())
|
||||
switch ($n.get_LocalName()) {
|
||||
"import" { [void]$imports.Add($n.GetAttribute("namespace")) }
|
||||
"objectType" { [void]$localTypes.Add($n.GetAttribute("name")); [void]$objectTypeNames.Add($n.GetAttribute("name")) }
|
||||
"valueType" { [void]$localTypes.Add($n.GetAttribute("name")); [void]$valueTypeNames.Add($n.GetAttribute("name")) }
|
||||
"property" { if ($n.HasAttribute("name")) { [void]$globalProps.Add($n.GetAttribute("name")) } }
|
||||
}
|
||||
}
|
||||
|
||||
# --- Порядок элементов верхнего уровня ---
|
||||
# Модель требует import -> property -> valueType -> objectType. Нарушение платформа
|
||||
# не прощает: db-update падает с «Ошибка преобразования данных XDTO».
|
||||
$TOP_ORDER = @("import", "property", "valueType", "objectType")
|
||||
$prevRank = -1
|
||||
$orderOk = $true
|
||||
foreach ($t in $topSequence) {
|
||||
$rank = [array]::IndexOf($TOP_ORDER, $t)
|
||||
if ($rank -lt 0) { continue }
|
||||
if ($rank -lt $prevRank) {
|
||||
Report-Error "Нарушен порядок элементов верхнего уровня: <$t> после <$($TOP_ORDER[$prevRank])>. Модель требует import -> property -> valueType -> objectType; платформа отвергнет пакет при обновлении конфигурации"
|
||||
$orderOk = $false
|
||||
break
|
||||
}
|
||||
$prevRank = $rank
|
||||
}
|
||||
if ($orderOk) { Report-OK "Порядок элементов верхнего уровня корректен" }
|
||||
|
||||
# --- 4. Duplicate type names ---
|
||||
|
||||
$seen = @{}
|
||||
foreach ($n in $pkg.ChildNodes) {
|
||||
if ($n.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
|
||||
if ($n.get_LocalName() -ne "objectType" -and $n.get_LocalName() -ne "valueType") { continue }
|
||||
$nm = $n.GetAttribute("name")
|
||||
if (-not $nm) { Report-Error "<$($n.get_LocalName())> без атрибута name"; continue }
|
||||
if ($seen.ContainsKey($nm)) {
|
||||
Report-Error "Дублирующееся имя типа: $nm"
|
||||
} else {
|
||||
$seen[$nm] = $true
|
||||
}
|
||||
}
|
||||
if ($localTypes.Count -gt 0) { Report-OK "$($localTypes.Count) тип(ов), имена уникальны" }
|
||||
|
||||
# --- 5. Type references resolve ---
|
||||
|
||||
$usedNamespaces = New-Object System.Collections.Generic.HashSet[string]
|
||||
$anyTypeProps = New-Object System.Collections.ArrayList
|
||||
$refAttrs = @("type", "base", "ref", "itemType")
|
||||
|
||||
function Resolve-Ref([System.Xml.XmlElement]$el, [string]$attr, [string]$raw) {
|
||||
if (-not $raw) { return }
|
||||
# Нотация Кларка {ns}local
|
||||
if ($raw.StartsWith("{")) {
|
||||
$close = $raw.IndexOf("}")
|
||||
if ($close -lt 0) { Report-Error "Некорректная нотация Кларка в $attr=`"$raw`""; return }
|
||||
$ns = $raw.Substring(1, $close - 1)
|
||||
$local = $raw.Substring($close + 1)
|
||||
} else {
|
||||
$parts = $raw.Split(":")
|
||||
if ($parts.Count -eq 2) {
|
||||
$ns = $el.GetNamespaceOfPrefix($parts[0])
|
||||
$local = $parts[1]
|
||||
if (-not $ns) {
|
||||
Report-Error "Префикс `"$($parts[0])`" не объявлен: $attr=`"$raw`" (тип $($el.get_LocalName()))"
|
||||
return
|
||||
}
|
||||
} else {
|
||||
$ns = $null
|
||||
$local = $parts[0]
|
||||
}
|
||||
}
|
||||
|
||||
if ($ns -eq $XS_NS -or $ns -eq $XSI_NS) {
|
||||
if ($local -eq "anyType") { [void]$anyTypeProps.Add($el) }
|
||||
return
|
||||
}
|
||||
if ($ns -eq $targetNs) {
|
||||
if ($attr -eq "ref") {
|
||||
if (-not $globalProps.Contains($local)) {
|
||||
Report-Error "ref=`"$raw`" не разрешается: в пакете нет глобального свойства `"$local`""
|
||||
}
|
||||
} elseif (-not $localTypes.Contains($local)) {
|
||||
Report-Error "$attr=`"$raw`" не разрешается: в пакете нет типа `"$local`""
|
||||
}
|
||||
return
|
||||
}
|
||||
if ($ns) {
|
||||
[void]$usedNamespaces.Add($ns)
|
||||
if (-not $imports.Contains($ns)) {
|
||||
Report-Error "$attr=`"$raw`" ссылается на `"$ns`", но <import namespace=`"$ns`"/> не объявлен"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$nodeCount = 0
|
||||
foreach ($el in $pkg.SelectNodes("//*")) {
|
||||
if ($el.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
|
||||
$nodeCount++
|
||||
foreach ($a in $refAttrs) {
|
||||
if ($el.HasAttribute($a)) { Resolve-Ref $el $a $el.GetAttribute($a) }
|
||||
}
|
||||
if ($el.HasAttribute("memberTypes")) {
|
||||
foreach ($m in ($el.GetAttribute("memberTypes") -split "\s+")) {
|
||||
if ($m) { Resolve-Ref $el "memberTypes" $m }
|
||||
}
|
||||
}
|
||||
if ($script:stopped) { break }
|
||||
}
|
||||
if (-not $script:stopped) { Report-OK "$nodeCount узлов: ссылки на типы разрешаются" }
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- 6. Silent degradation to xs:anyType ---
|
||||
# Платформа при импорте XML-схемы молча подменяет неразрешённый чужой тип на anyType.
|
||||
|
||||
if ($anyTypeProps.Count -gt 0 -and $imports.Count -gt 0) {
|
||||
$names = @()
|
||||
foreach ($p in $anyTypeProps) { if ($p.HasAttribute("name")) { $names += $p.GetAttribute("name") } }
|
||||
$shown = ($names | Select-Object -First 5) -join ", "
|
||||
Report-Warn "Свойств с type=`"xs:anyType`": $($anyTypeProps.Count) при объявленных импортах ($shown). Тип не разрешён — заполнить структурно такое свойство нельзя. Обычно это след импорта XML-схемы: платформа заменяет неразрешённый чужой тип на anyType без ошибки"
|
||||
}
|
||||
|
||||
# --- 7. Unused imports ---
|
||||
|
||||
# Сам по себе неиспользуемый импорт безвреден и встречается в четверти пакетов
|
||||
# типовых конфигураций. Сигналом он становится только вместе с anyType — тогда это
|
||||
# почти наверняка неразрешённая зависимость.
|
||||
$unused = @()
|
||||
foreach ($imp in $imports) { if (-not $usedNamespaces.Contains($imp)) { $unused += $imp } }
|
||||
if ($unused.Count -gt 0 -and $anyTypeProps.Count -gt 0) {
|
||||
Report-Warn "Импорт(ы) без единого использованного типа: $($unused -join ', ') — вместе с anyType это признак неразрешённой зависимости"
|
||||
}
|
||||
if ($imports.Count -gt 0 -and $unused.Count -eq 0) { Report-OK "$($imports.Count) импорт(ов) — все используются" }
|
||||
|
||||
# --- 8. nillable on attribute-form properties ---
|
||||
|
||||
$nillAttrs = @()
|
||||
foreach ($p in $pkg.SelectNodes("//*[local-name()='property']")) {
|
||||
if ($p.GetAttribute("form") -eq "Attribute" -and $p.GetAttribute("nillable") -eq "true") {
|
||||
$nillAttrs += $p.GetAttribute("name")
|
||||
}
|
||||
}
|
||||
if ($nillAttrs.Count -gt 0) {
|
||||
Report-Warn "Свойств с nillable=`"true`" и form=`"Attribute`": $($nillAttrs.Count) ($(($nillAttrs | Select-Object -First 5) -join ', ')). Спецификация XSD не допускает nillable у атрибутов — экспорт XML-схемы в Конфигураторе их потеряет"
|
||||
}
|
||||
|
||||
# --- 9. Facet consistency ---
|
||||
|
||||
$FACET_NUM = @("totalDigits", "fractionDigits")
|
||||
$facetChecked = 0
|
||||
foreach ($t in $pkg.SelectNodes("//*[local-name()='valueType' or local-name()='typeDef']")) {
|
||||
if ($t.get_LocalName() -eq "typeDef" -and $t.GetAttribute("type", $XSI_NS) -eq "ObjectType") { continue }
|
||||
$facetChecked++
|
||||
$nm = if ($t.HasAttribute("name")) { $t.GetAttribute("name") } else { "(анонимный тип)" }
|
||||
|
||||
$len = $t.GetAttribute("length")
|
||||
if ($len -and ($t.HasAttribute("minLength") -or $t.HasAttribute("maxLength"))) {
|
||||
# Спецификация XSD это запрещает, но платформа такие типы хранит — предупреждение, не ошибка
|
||||
Report-Warn "$nm : length задан вместе с minLength/maxLength — спецификация XSD считает их взаимоисключающими"
|
||||
}
|
||||
$minL = $t.GetAttribute("minLength"); $maxL = $t.GetAttribute("maxLength")
|
||||
if ($minL -and $maxL -and ([int]$minL -gt [int]$maxL)) {
|
||||
Report-Error "$nm : minLength ($minL) больше maxLength ($maxL)"
|
||||
}
|
||||
$td = $t.GetAttribute("totalDigits"); $fd = $t.GetAttribute("fractionDigits")
|
||||
if ($td -and $fd -and ([int]$fd -gt [int]$td)) {
|
||||
Report-Error "$nm : fractionDigits ($fd) больше totalDigits ($td)"
|
||||
}
|
||||
$ws = $t.GetAttribute("whiteSpace")
|
||||
if ($ws -and @("preserve", "replace", "collapse") -notcontains $ws) {
|
||||
Report-Error "$nm : недопустимое whiteSpace=`"$ws`""
|
||||
}
|
||||
$var = $t.GetAttribute("variety")
|
||||
if ($var -and @("Atomic", "List", "Union") -notcontains $var) {
|
||||
Report-Error "$nm : недопустимое variety=`"$var`""
|
||||
}
|
||||
if ($var -eq "List" -and -not $t.HasAttribute("itemType")) {
|
||||
Report-Warn "$nm : variety=`"List`" без itemType"
|
||||
}
|
||||
if ($script:stopped) { break }
|
||||
}
|
||||
if ($facetChecked -gt 0 -and -not $script:stopped) { Report-OK "$facetChecked простых тип(ов): фасеты согласованы" }
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- 10. property form / bounds ---
|
||||
|
||||
foreach ($p in $pkg.SelectNodes("//*[local-name()='property']")) {
|
||||
$form = $p.GetAttribute("form")
|
||||
if ($form -and @("Element", "Attribute", "Text") -notcontains $form) {
|
||||
Report-Error "Свойство `"$($p.GetAttribute('name'))`": недопустимое form=`"$form`""
|
||||
}
|
||||
$ub = $p.GetAttribute("upperBound")
|
||||
if ($ub -and $ub -ne "-1" -and ([int]$ub -lt 1)) {
|
||||
Report-Error "Свойство `"$($p.GetAttribute('name'))`": upperBound=`"$ub`" (допустимы -1 или число ≥ 1)"
|
||||
}
|
||||
$lb = $p.GetAttribute("lowerBound")
|
||||
if ($lb -and $ub -and $ub -ne "-1" -and ([int]$lb -gt [int]$ub)) {
|
||||
Report-Error "Свойство `"$($p.GetAttribute('name'))`": lowerBound ($lb) больше upperBound ($ub)"
|
||||
}
|
||||
if (-not $p.HasAttribute("name") -and -not $p.HasAttribute("ref")) {
|
||||
Report-Error "Свойство без name и без ref"
|
||||
}
|
||||
# В модели XDTO fixed — булев признак, само значение лежит в default.
|
||||
# В XML-схеме наоборот: fixed="V" совмещает признак и значение.
|
||||
if ($p.HasAttribute("fixed")) {
|
||||
$fx = $p.GetAttribute("fixed")
|
||||
$pName = if ($p.HasAttribute("name")) { $p.GetAttribute("name") } else { $p.GetAttribute("ref") }
|
||||
if (@("true", "false") -cnotcontains $fx) {
|
||||
Report-Error "Свойство `"$pName`": fixed=`"$fx`" — в модели это булев признак, значение задаётся в default (в XML-схеме признак и значение совмещены в fixed)"
|
||||
} elseif ($fx -ceq "true" -and -not $p.HasAttribute("default")) {
|
||||
Report-Error "Отсутствует фиксированное значение свойства '$pName': есть fixed=`"true`", нет default"
|
||||
}
|
||||
}
|
||||
if ($script:stopped) { break }
|
||||
}
|
||||
if (-not $script:stopped) { Report-OK "Свойства: form, кратности и фиксированные значения корректны" }
|
||||
|
||||
# --- 10b. Structural consistency ---
|
||||
|
||||
$structOk = $true
|
||||
foreach ($t in $pkg.SelectNodes("//*[local-name()='objectType' or local-name()='typeDef']")) {
|
||||
if ($t.get_LocalName() -eq "typeDef" -and $t.GetAttribute("type", $XSI_NS) -ne "ObjectType") { continue }
|
||||
$tn = if ($t.HasAttribute("name")) { $t.GetAttribute("name") } else { "(анонимный тип)" }
|
||||
$propNames = @{}
|
||||
foreach ($c in $t.ChildNodes) {
|
||||
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element -or $c.get_LocalName() -ne "property") { continue }
|
||||
$pn = $c.GetAttribute("name")
|
||||
if ($pn) {
|
||||
if ($propNames.ContainsKey($pn)) {
|
||||
Report-Error "$tn : дублирующееся имя свойства `"$pn`""
|
||||
$structOk = $false
|
||||
}
|
||||
$propNames[$pn] = $true
|
||||
}
|
||||
}
|
||||
if ($script:stopped) { break }
|
||||
}
|
||||
|
||||
foreach ($p in $pkg.SelectNodes("//*[local-name()='property']")) {
|
||||
$pn = if ($p.HasAttribute("name")) { $p.GetAttribute("name") } else { $p.GetAttribute("ref") }
|
||||
if ($p.HasAttribute("name") -and $p.HasAttribute("ref")) {
|
||||
Report-Error "Свойство `"$pn`": заданы одновременно name и ref — допустимо только одно"
|
||||
$structOk = $false
|
||||
}
|
||||
$inlineTypeDef = $null
|
||||
foreach ($c in $p.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $inlineTypeDef = $c; break }
|
||||
}
|
||||
if ($inlineTypeDef -and $p.HasAttribute("type")) {
|
||||
Report-Error "Свойство `"$pn`": заданы одновременно type и вложенный <typeDef> — допустимо только одно"
|
||||
$structOk = $false
|
||||
}
|
||||
if ($inlineTypeDef -and -not $inlineTypeDef.HasAttribute("type", $XSI_NS)) {
|
||||
Report-Error "Свойство `"$pn`": у вложенного <typeDef> не задан xsi:type (ValueType или ObjectType)"
|
||||
$structOk = $false
|
||||
}
|
||||
if ($script:stopped) { break }
|
||||
}
|
||||
|
||||
# Анонимный тип внутри valueType задаёт базовый тип и xsi:type не несёт
|
||||
foreach ($vt in $pkg.SelectNodes("//*[local-name()='valueType']")) {
|
||||
foreach ($c in $vt.ChildNodes) {
|
||||
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element -or $c.get_LocalName() -ne "typeDef") { continue }
|
||||
if ($c.HasAttribute("type", $XSI_NS)) {
|
||||
Report-Warn "$($vt.GetAttribute('name')) : у <typeDef> внутри <valueType> задан xsi:type — платформа его здесь не пишет"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Род базового типа должен совпадать
|
||||
foreach ($t in $pkg.SelectNodes("//*[local-name()='objectType'][@base]")) {
|
||||
$b = $t.GetAttribute("base")
|
||||
$parts = $b.Split(":")
|
||||
if ($parts.Count -ne 2) { continue }
|
||||
$bns = $t.GetNamespaceOfPrefix($parts[0])
|
||||
if ($bns -ne $targetNs) { continue }
|
||||
if ($valueTypeNames.Contains($parts[1])) {
|
||||
Report-Error "$($t.GetAttribute('name')) : base=`"$b`" ссылается на valueType, а objectType может наследоваться только от objectType"
|
||||
$structOk = $false
|
||||
}
|
||||
}
|
||||
foreach ($t in $pkg.SelectNodes("//*[local-name()='valueType'][@base]")) {
|
||||
$b = $t.GetAttribute("base")
|
||||
$parts = $b.Split(":")
|
||||
if ($parts.Count -ne 2) { continue }
|
||||
$bns = $t.GetNamespaceOfPrefix($parts[0])
|
||||
if ($bns -ne $targetNs) { continue }
|
||||
if ($objectTypeNames.Contains($parts[1])) {
|
||||
Report-Error "$($t.GetAttribute('name')) : base=`"$b`" ссылается на objectType, а valueType может строиться только на простом типе"
|
||||
$structOk = $false
|
||||
}
|
||||
}
|
||||
|
||||
# Union без состава
|
||||
foreach ($t in $pkg.SelectNodes("//*[local-name()='valueType' or local-name()='typeDef'][@variety='Union']")) {
|
||||
if ($t.HasAttribute("memberTypes")) { continue }
|
||||
$hasMember = $false
|
||||
foreach ($c in $t.ChildNodes) {
|
||||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $hasMember = $true; break }
|
||||
}
|
||||
if (-not $hasMember) {
|
||||
$tn = if ($t.HasAttribute("name")) { $t.GetAttribute("name") } else { "(анонимный тип)" }
|
||||
Report-Warn "$tn : variety=`"Union`" без memberTypes и без вложенных типов — состав объединения пуст"
|
||||
}
|
||||
}
|
||||
|
||||
if ($structOk -and -not $script:stopped) { Report-OK "Структура типов и свойств согласована" }
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- 11. Metadata object ---
|
||||
|
||||
if ($mdPath) {
|
||||
$md = New-Object System.Xml.XmlDocument
|
||||
$md.Load($mdPath)
|
||||
$nsm = New-Object System.Xml.XmlNamespaceManager($md.NameTable)
|
||||
$nsm.AddNamespace("md", $MD_NS)
|
||||
$mdName = $md.SelectSingleNode("//md:XDTOPackage/md:Properties/md:Name", $nsm)
|
||||
$mdNs = $md.SelectSingleNode("//md:XDTOPackage/md:Properties/md:Namespace", $nsm)
|
||||
if (-not $mdName) {
|
||||
Report-Error "В объекте метаданных не задано <Name>"
|
||||
} elseif ($mdName.InnerText -ne $fileName) {
|
||||
Report-Error "<Name>$($mdName.InnerText)</Name> не совпадает с именем каталога `"$fileName`""
|
||||
} else {
|
||||
Report-OK "Объект метаданных: Name = $($mdName.InnerText)"
|
||||
}
|
||||
if ($mdNs -and $mdNs.InnerText -ne $targetNs) {
|
||||
Report-Error "<Namespace>$($mdNs.InnerText)</Namespace> не совпадает с targetNamespace пакета ($targetNs)"
|
||||
} elseif ($mdNs) {
|
||||
Report-OK "Namespace объекта метаданных совпадает с targetNamespace"
|
||||
}
|
||||
} else {
|
||||
Report-Warn "Файл объекта метаданных <Имя>.xml не найден рядом с каталогом пакета"
|
||||
}
|
||||
|
||||
# --- 12. Registration in Configuration.xml + namespace uniqueness ---
|
||||
|
||||
$configXml = Join-Path $ConfigDir "Configuration.xml"
|
||||
if (Test-Path $configXml) {
|
||||
$cfg = New-Object System.Xml.XmlDocument
|
||||
$cfg.Load($configXml)
|
||||
$nsm2 = New-Object System.Xml.XmlNamespaceManager($cfg.NameTable)
|
||||
$nsm2.AddNamespace("md", $MD_NS)
|
||||
$registered = $false
|
||||
foreach ($e in $cfg.SelectNodes("//md:Configuration/md:ChildObjects/md:XDTOPackage", $nsm2)) {
|
||||
if ($e.InnerText -eq $fileName) { $registered = $true; break }
|
||||
}
|
||||
if ($registered) {
|
||||
Report-OK "Зарегистрирован в Configuration.xml"
|
||||
} else {
|
||||
Report-Error "<XDTOPackage>$fileName</XDTOPackage> отсутствует в ChildObjects файла Configuration.xml — платформа пакет не увидит"
|
||||
}
|
||||
|
||||
# Уникальность targetNamespace среди пакетов конфигурации
|
||||
$pkgRoot = Join-Path $ConfigDir "XDTOPackages"
|
||||
if (Test-Path $pkgRoot) {
|
||||
$clash = @()
|
||||
foreach ($other in (Get-ChildItem $pkgRoot -Directory -ErrorAction SilentlyContinue)) {
|
||||
if ($other.Name -eq $fileName) { continue }
|
||||
$ob = Join-Path (Join-Path $other.FullName "Ext") "Package.bin"
|
||||
if (-not (Test-Path $ob)) { continue }
|
||||
try {
|
||||
$od = New-Object System.Xml.XmlDocument
|
||||
$od.Load($ob)
|
||||
if ($od.DocumentElement.GetAttribute("targetNamespace") -eq $targetNs) { $clash += $other.Name }
|
||||
} catch {}
|
||||
}
|
||||
# Платформа отвергает пакет, если импортируемого namespace нет в конфигурации:
|
||||
# «Ошибка проверки модели XDTO: xdto-package-3.3 … не определен»
|
||||
$knownNs = @{}
|
||||
foreach ($other in (Get-ChildItem $pkgRoot -Directory -ErrorAction SilentlyContinue)) {
|
||||
$ob = Join-Path (Join-Path $other.FullName "Ext") "Package.bin"
|
||||
if (-not (Test-Path $ob)) { continue }
|
||||
try {
|
||||
$od = New-Object System.Xml.XmlDocument
|
||||
$od.Load($ob)
|
||||
$knownNs[$od.DocumentElement.GetAttribute("targetNamespace")] = $other.Name
|
||||
} catch {}
|
||||
}
|
||||
$missing = @()
|
||||
foreach ($imp in $imports) { if (-not $knownNs.ContainsKey($imp) -and $PLATFORM_NS -notcontains $imp) { $missing += $imp } }
|
||||
if ($missing.Count -gt 0) {
|
||||
Report-Error ("Импортируемые пакеты не определены в конфигурации: " + ($missing -join ", ") +
|
||||
". Платформа отвергнет пакет при обновлении конфигурации — соберите зависимости первыми")
|
||||
} elseif ($imports.Count -gt 0) {
|
||||
Report-OK "Все импорты разрешаются в пакеты конфигурации"
|
||||
}
|
||||
|
||||
if ($clash.Count -gt 0) {
|
||||
Report-Warn "targetNamespace `"$targetNs`" объявлен также в пакет(ах): $($clash -join ', '). Платформа это допускает, но <import> на это пространство имён становится неоднозначным"
|
||||
} else {
|
||||
Report-OK "targetNamespace уникален в конфигурации"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Report-Warn "Configuration.xml не найден ($ConfigDir) — проверки регистрации и уникальности namespace пропущены"
|
||||
}
|
||||
|
||||
# --- Final ---
|
||||
|
||||
& $finalize
|
||||
if ($script:errors -gt 0) { exit 1 }
|
||||
exit 0
|
||||
@@ -0,0 +1,569 @@
|
||||
# xdto-validate v1.1 — Validate a 1C XDTO package (Python port)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
from lxml import etree
|
||||
|
||||
# Эти пространства имён предоставляет сама платформа — пакетов в конфигурации
|
||||
# для них нет и быть не должно (выведено по корпусу)
|
||||
PLATFORM_NS = {
|
||||
"http://v8.1c.ru/8.1/data/core",
|
||||
"http://v8.1c.ru/8.1/data/enterprise",
|
||||
"http://v8.1c.ru/8.1/data/enterprise/current-config",
|
||||
"http://v8.1c.ru/8.1/data-composition-system/settings",
|
||||
"http://v8.1c.ru/8.3/data/ext",
|
||||
"http://www.w3.org/2001/XMLSchema",
|
||||
}
|
||||
|
||||
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
|
||||
XS_NS = "http://www.w3.org/2001/XMLSchema"
|
||||
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
||||
|
||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||
parser.add_argument("-PackagePath", "-Path", required=True)
|
||||
parser.add_argument("-ConfigDir", default="")
|
||||
parser.add_argument("-Detailed", action="store_true")
|
||||
parser.add_argument("-MaxErrors", type=int, default=20)
|
||||
parser.add_argument("-OutFile", default="")
|
||||
args = parser.parse_args()
|
||||
|
||||
package_path = os.path.abspath(args.PackagePath)
|
||||
detailed = args.Detailed
|
||||
max_errors = args.MaxErrors
|
||||
out_file = args.OutFile
|
||||
|
||||
# ── reporting ────────────────────────────────────────────────
|
||||
|
||||
state = {"errors": 0, "warnings": 0, "ok": 0, "stopped": False}
|
||||
output = []
|
||||
|
||||
|
||||
def out_line(s):
|
||||
output.append(s)
|
||||
|
||||
|
||||
def report_ok(msg):
|
||||
state["ok"] += 1
|
||||
if detailed:
|
||||
out_line(f"[OK] {msg}")
|
||||
|
||||
|
||||
def report_error(msg):
|
||||
state["errors"] += 1
|
||||
out_line(f"[ERROR] {msg}")
|
||||
if state["errors"] >= max_errors:
|
||||
state["stopped"] = True
|
||||
|
||||
|
||||
def report_warn(msg):
|
||||
state["warnings"] += 1
|
||||
out_line(f"[WARN] {msg}")
|
||||
|
||||
|
||||
# ── resolve paths ────────────────────────────────────────────
|
||||
|
||||
bin_path = None
|
||||
md_path = None
|
||||
|
||||
if os.path.isfile(package_path):
|
||||
if os.path.basename(package_path) == "Package.bin":
|
||||
bin_path = package_path
|
||||
pkg_dir = os.path.dirname(os.path.dirname(package_path))
|
||||
if os.path.exists(pkg_dir + ".xml"):
|
||||
md_path = pkg_dir + ".xml"
|
||||
elif package_path.endswith(".xml"):
|
||||
md_path = package_path
|
||||
stem = os.path.join(os.path.dirname(package_path),
|
||||
os.path.splitext(os.path.basename(package_path))[0])
|
||||
c = os.path.join(stem, "Ext", "Package.bin")
|
||||
if os.path.exists(c):
|
||||
bin_path = c
|
||||
elif os.path.isdir(package_path):
|
||||
c = os.path.join(package_path, "Ext", "Package.bin")
|
||||
if os.path.exists(c):
|
||||
bin_path = c
|
||||
m = package_path.rstrip("\\/") + ".xml"
|
||||
if os.path.exists(m):
|
||||
md_path = m
|
||||
|
||||
if not bin_path:
|
||||
print(f"[ERROR] Не найден Ext/Package.bin для пути: {package_path}")
|
||||
sys.exit(1)
|
||||
|
||||
file_name = os.path.basename(os.path.dirname(os.path.dirname(bin_path)))
|
||||
|
||||
config_dir = args.ConfigDir
|
||||
if not config_dir:
|
||||
# .../XDTOPackages/<Имя>/Ext/Package.bin -> .../XDTOPackages -> корень
|
||||
config_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(bin_path))))
|
||||
|
||||
|
||||
def finalize():
|
||||
checks = state["ok"] + state["errors"] + state["warnings"]
|
||||
if state["errors"] == 0 and state["warnings"] == 0 and not detailed:
|
||||
result = f"=== Validation OK: {file_name} ({checks} checks) ==="
|
||||
else:
|
||||
out_line("")
|
||||
out_line(f"=== Result: {state['errors']} errors, {state['warnings']} warnings ({checks} checks) ===")
|
||||
result = "\n".join(output)
|
||||
print(result)
|
||||
if out_file:
|
||||
with open(out_file, "w", encoding="utf-8-sig", newline="") as f:
|
||||
f.write("\n".join(output))
|
||||
|
||||
|
||||
def _parse_xml(source, from_string=False):
|
||||
"""Разбор с узким отступлением для не-URI пространств имён.
|
||||
|
||||
Платформа допускает в targetNamespace произвольную строку (в выгрузке БП есть
|
||||
пакет с кириллическим «ДопФайлУниверсальный»), .NET такое принимает, а libxml2
|
||||
отвергает. Откатываемся на восстанавливающий разбор ТОЛЬКО на этой ошибке,
|
||||
иначе по-настоящему битый XML перестал бы отличаться от корректного.
|
||||
"""
|
||||
try:
|
||||
return (etree.fromstring(source) if from_string else etree.parse(source))
|
||||
except etree.XMLSyntaxError as e:
|
||||
if "is not a valid URI" not in str(e):
|
||||
raise
|
||||
p = etree.XMLParser(recover=True)
|
||||
return (etree.fromstring(source, p) if from_string else etree.parse(source, p))
|
||||
|
||||
|
||||
def local(el):
|
||||
return etree.QName(el).localname
|
||||
|
||||
|
||||
# ── 1. well-formedness ───────────────────────────────────────
|
||||
|
||||
try:
|
||||
doc = _parse_xml(bin_path)
|
||||
except Exception as e: # noqa: BLE001
|
||||
report_error(f"Package.bin не является корректным XML: {e}")
|
||||
finalize()
|
||||
sys.exit(1)
|
||||
|
||||
pkg = doc.getroot()
|
||||
if local(pkg) != "package":
|
||||
report_error(f"Ожидался корневой <package>, найден <{local(pkg)}>")
|
||||
finalize()
|
||||
sys.exit(1)
|
||||
report_ok("Package.bin: корректный XML, корень <package>")
|
||||
|
||||
target_ns = pkg.get("targetNamespace")
|
||||
if not target_ns:
|
||||
report_error("У <package> не задан targetNamespace")
|
||||
else:
|
||||
report_ok(f"targetNamespace: {target_ns}")
|
||||
|
||||
# ── 2. encoding ──────────────────────────────────────────────
|
||||
|
||||
with open(bin_path, "rb") as f:
|
||||
head = f.read(3)
|
||||
if head != b"\xef\xbb\xbf":
|
||||
report_warn("Package.bin без BOM UTF-8 — платформа пишет файл с BOM")
|
||||
else:
|
||||
report_ok("Кодировка: UTF-8 с BOM")
|
||||
|
||||
# ── 3. declarations ──────────────────────────────────────────
|
||||
|
||||
imports = []
|
||||
local_types = set()
|
||||
object_type_names = set()
|
||||
value_type_names = set()
|
||||
global_props = set()
|
||||
top_sequence = []
|
||||
|
||||
for n in pkg:
|
||||
if not isinstance(n.tag, str):
|
||||
continue
|
||||
ln = local(n)
|
||||
top_sequence.append(ln)
|
||||
if ln == "import":
|
||||
imports.append(n.get("namespace"))
|
||||
elif ln == "objectType":
|
||||
local_types.add(n.get("name"))
|
||||
object_type_names.add(n.get("name"))
|
||||
elif ln == "valueType":
|
||||
local_types.add(n.get("name"))
|
||||
value_type_names.add(n.get("name"))
|
||||
elif ln == "property" and n.get("name"):
|
||||
global_props.add(n.get("name"))
|
||||
|
||||
# ── порядок элементов верхнего уровня ────────────────────────
|
||||
# Модель требует import -> property -> valueType -> objectType. Нарушение платформа
|
||||
# не прощает: db-update падает с «Ошибка преобразования данных XDTO».
|
||||
TOP_ORDER = ["import", "property", "valueType", "objectType"]
|
||||
prev_rank = -1
|
||||
order_ok = True
|
||||
for t in top_sequence:
|
||||
if t not in TOP_ORDER:
|
||||
continue
|
||||
rank = TOP_ORDER.index(t)
|
||||
if rank < prev_rank:
|
||||
report_error(f"Нарушен порядок элементов верхнего уровня: <{t}> после <{TOP_ORDER[prev_rank]}>. "
|
||||
"Модель требует import -> property -> valueType -> objectType; "
|
||||
"платформа отвергнет пакет при обновлении конфигурации")
|
||||
order_ok = False
|
||||
break
|
||||
prev_rank = rank
|
||||
if order_ok:
|
||||
report_ok("Порядок элементов верхнего уровня корректен")
|
||||
|
||||
# ── 4. duplicate type names ──────────────────────────────────
|
||||
|
||||
seen = set()
|
||||
for n in pkg:
|
||||
if not isinstance(n.tag, str) or local(n) not in ("objectType", "valueType"):
|
||||
continue
|
||||
nm = n.get("name")
|
||||
if not nm:
|
||||
report_error(f"<{local(n)}> без атрибута name")
|
||||
continue
|
||||
if nm in seen:
|
||||
report_error(f"Дублирующееся имя типа: {nm}")
|
||||
else:
|
||||
seen.add(nm)
|
||||
if local_types:
|
||||
report_ok(f"{len(local_types)} тип(ов), имена уникальны")
|
||||
|
||||
# ── 5. type references resolve ───────────────────────────────
|
||||
|
||||
used_namespaces = set()
|
||||
any_type_props = []
|
||||
REF_ATTRS = ("type", "base", "ref", "itemType")
|
||||
|
||||
|
||||
def resolve_ref(el, attr, raw):
|
||||
if not raw:
|
||||
return
|
||||
if raw.startswith("{"):
|
||||
close = raw.find("}")
|
||||
if close < 0:
|
||||
report_error(f'Некорректная нотация Кларка в {attr}="{raw}"')
|
||||
return
|
||||
ns = raw[1:close]
|
||||
loc = raw[close + 1:]
|
||||
else:
|
||||
parts = raw.split(":")
|
||||
if len(parts) == 2:
|
||||
ns = el.nsmap.get(parts[0])
|
||||
loc = parts[1]
|
||||
if not ns:
|
||||
report_error(f'Префикс "{parts[0]}" не объявлен: {attr}="{raw}" (тип {local(el)})')
|
||||
return
|
||||
else:
|
||||
ns = None
|
||||
loc = parts[0]
|
||||
|
||||
if ns in (XS_NS, XSI_NS):
|
||||
if loc == "anyType":
|
||||
any_type_props.append(el)
|
||||
return
|
||||
if ns == target_ns:
|
||||
if attr == "ref":
|
||||
if loc not in global_props:
|
||||
report_error(f'ref="{raw}" не разрешается: в пакете нет глобального свойства "{loc}"')
|
||||
elif loc not in local_types:
|
||||
report_error(f'{attr}="{raw}" не разрешается: в пакете нет типа "{loc}"')
|
||||
return
|
||||
if ns:
|
||||
used_namespaces.add(ns)
|
||||
if ns not in imports:
|
||||
report_error(f'{attr}="{raw}" ссылается на "{ns}", но <import namespace="{ns}"/> не объявлен')
|
||||
|
||||
|
||||
node_count = 0
|
||||
for el in pkg.iter():
|
||||
if not isinstance(el.tag, str):
|
||||
continue
|
||||
node_count += 1
|
||||
for a in REF_ATTRS:
|
||||
if el.get(a) is not None:
|
||||
resolve_ref(el, a, el.get(a))
|
||||
if el.get("memberTypes"):
|
||||
for m in el.get("memberTypes").split():
|
||||
resolve_ref(el, "memberTypes", m)
|
||||
if state["stopped"]:
|
||||
break
|
||||
if not state["stopped"]:
|
||||
report_ok(f"{node_count} узлов: ссылки на типы разрешаются")
|
||||
|
||||
if state["stopped"]:
|
||||
finalize()
|
||||
sys.exit(1)
|
||||
|
||||
# ── 6. silent degradation to xs:anyType ──────────────────────
|
||||
|
||||
if any_type_props and imports:
|
||||
names = [p.get("name") for p in any_type_props if p.get("name")]
|
||||
shown = ", ".join(names[:5])
|
||||
report_warn(
|
||||
f'Свойств с type="xs:anyType": {len(any_type_props)} при объявленных импортах ({shown}). '
|
||||
"Тип не разрешён — заполнить структурно такое свойство нельзя. Обычно это след импорта XML-схемы: платформа заменяет неразрешённый "
|
||||
"чужой тип на anyType без ошибки"
|
||||
)
|
||||
|
||||
# ── 7. unused imports ────────────────────────────────────────
|
||||
|
||||
# Сам по себе неиспользуемый импорт безвреден и встречается в четверти пакетов
|
||||
# типовых конфигураций. Сигналом он становится только вместе с anyType.
|
||||
unused = [i for i in imports if i not in used_namespaces]
|
||||
if unused and any_type_props:
|
||||
report_warn(f'Импорт(ы) без единого использованного типа: {", ".join(unused)} — '
|
||||
"вместе с anyType это признак неразрешённой зависимости")
|
||||
if imports and not unused:
|
||||
report_ok(f"{len(imports)} импорт(ов) — все используются")
|
||||
|
||||
# ── 8. nillable on attribute-form properties ─────────────────
|
||||
|
||||
nill_attrs = [p.get("name") for p in pkg.iter()
|
||||
if isinstance(p.tag, str) and local(p) == "property"
|
||||
and p.get("form") == "Attribute" and p.get("nillable") == "true"]
|
||||
if nill_attrs:
|
||||
report_warn(
|
||||
f'Свойств с nillable="true" и form="Attribute": {len(nill_attrs)} ({", ".join(nill_attrs[:5])}). '
|
||||
"Спецификация XSD не допускает nillable у атрибутов — экспорт XML-схемы в Конфигураторе их потеряет"
|
||||
)
|
||||
|
||||
# ── 9. facet consistency ─────────────────────────────────────
|
||||
|
||||
facet_checked = 0
|
||||
for t in pkg.iter():
|
||||
if not isinstance(t.tag, str) or local(t) not in ("valueType", "typeDef"):
|
||||
continue
|
||||
if local(t) == "typeDef" and t.get(f"{{{XSI_NS}}}type") == "ObjectType":
|
||||
continue
|
||||
facet_checked += 1
|
||||
nm = t.get("name") or "(анонимный тип)"
|
||||
|
||||
if t.get("length") and (t.get("minLength") or t.get("maxLength")):
|
||||
# Спецификация XSD это запрещает, но платформа такие типы хранит — предупреждение, не ошибка
|
||||
report_warn(f"{nm} : length задан вместе с minLength/maxLength — "
|
||||
"спецификация XSD считает их взаимоисключающими")
|
||||
min_l, max_l = t.get("minLength"), t.get("maxLength")
|
||||
if min_l and max_l and int(min_l) > int(max_l):
|
||||
report_error(f"{nm} : minLength ({min_l}) больше maxLength ({max_l})")
|
||||
td, fd = t.get("totalDigits"), t.get("fractionDigits")
|
||||
if td and fd and int(fd) > int(td):
|
||||
report_error(f"{nm} : fractionDigits ({fd}) больше totalDigits ({td})")
|
||||
ws = t.get("whiteSpace")
|
||||
if ws and ws not in ("preserve", "replace", "collapse"):
|
||||
report_error(f'{nm} : недопустимое whiteSpace="{ws}"')
|
||||
var = t.get("variety")
|
||||
if var and var not in ("Atomic", "List", "Union"):
|
||||
report_error(f'{nm} : недопустимое variety="{var}"')
|
||||
if var == "List" and t.get("itemType") is None:
|
||||
report_warn(f'{nm} : variety="List" без itemType')
|
||||
if state["stopped"]:
|
||||
break
|
||||
if facet_checked and not state["stopped"]:
|
||||
report_ok(f"{facet_checked} простых тип(ов): фасеты согласованы")
|
||||
|
||||
if state["stopped"]:
|
||||
finalize()
|
||||
sys.exit(1)
|
||||
|
||||
# ── 10. property form / bounds ───────────────────────────────
|
||||
|
||||
for p in pkg.iter():
|
||||
if not isinstance(p.tag, str) or local(p) != "property":
|
||||
continue
|
||||
form = p.get("form")
|
||||
if form and form not in ("Element", "Attribute", "Text"):
|
||||
report_error(f'Свойство "{p.get("name")}": недопустимое form="{form}"')
|
||||
ub = p.get("upperBound")
|
||||
if ub and ub != "-1" and int(ub) < 1:
|
||||
report_error(f'Свойство "{p.get("name")}": upperBound="{ub}" (допустимы -1 или число ≥ 1)')
|
||||
lb = p.get("lowerBound")
|
||||
if lb and ub and ub != "-1" and int(lb) > int(ub):
|
||||
report_error(f'Свойство "{p.get("name")}": lowerBound ({lb}) больше upperBound ({ub})')
|
||||
if p.get("name") is None and p.get("ref") is None:
|
||||
report_error("Свойство без name и без ref")
|
||||
# В модели XDTO fixed — булев признак, само значение лежит в default.
|
||||
# В XML-схеме наоборот: fixed="V" совмещает признак и значение.
|
||||
fx = p.get("fixed")
|
||||
if fx is not None:
|
||||
p_name = p.get("name") if p.get("name") is not None else p.get("ref")
|
||||
if fx not in ("true", "false"):
|
||||
report_error(
|
||||
f'Свойство "{p_name}": fixed="{fx}" — в модели это булев признак, '
|
||||
"значение задаётся в default (в XML-схеме признак и значение совмещены в fixed)"
|
||||
)
|
||||
elif fx == "true" and p.get("default") is None:
|
||||
report_error(
|
||||
f"Отсутствует фиксированное значение свойства '{p_name}': "
|
||||
'есть fixed="true", нет default'
|
||||
)
|
||||
if state["stopped"]:
|
||||
break
|
||||
if not state["stopped"]:
|
||||
report_ok("Свойства: form, кратности и фиксированные значения корректны")
|
||||
|
||||
# ── 10b. structural consistency ──────────────────────────────
|
||||
|
||||
struct_ok = True
|
||||
for t in pkg.iter():
|
||||
if not isinstance(t.tag, str) or local(t) not in ("objectType", "typeDef"):
|
||||
continue
|
||||
if local(t) == "typeDef" and t.get(f"{{{XSI_NS}}}type") != "ObjectType":
|
||||
continue
|
||||
tn = t.get("name") or "(анонимный тип)"
|
||||
prop_names = set()
|
||||
for c in t:
|
||||
if not isinstance(c.tag, str) or local(c) != "property":
|
||||
continue
|
||||
pn = c.get("name")
|
||||
if pn:
|
||||
if pn in prop_names:
|
||||
report_error(f'{tn} : дублирующееся имя свойства "{pn}"')
|
||||
struct_ok = False
|
||||
prop_names.add(pn)
|
||||
if state["stopped"]:
|
||||
break
|
||||
|
||||
for p_ in pkg.iter():
|
||||
if not isinstance(p_.tag, str) or local(p_) != "property":
|
||||
continue
|
||||
pn = p_.get("name") or p_.get("ref")
|
||||
if p_.get("name") is not None and p_.get("ref") is not None:
|
||||
report_error(f'Свойство "{pn}": заданы одновременно name и ref — допустимо только одно')
|
||||
struct_ok = False
|
||||
inline = next((c for c in p_ if isinstance(c.tag, str) and local(c) == "typeDef"), None)
|
||||
if inline is not None and p_.get("type") is not None:
|
||||
report_error(f'Свойство "{pn}": заданы одновременно type и вложенный <typeDef> — допустимо только одно')
|
||||
struct_ok = False
|
||||
if inline is not None and inline.get(f"{{{XSI_NS}}}type") is None:
|
||||
report_error(f'Свойство "{pn}": у вложенного <typeDef> не задан xsi:type (ValueType или ObjectType)')
|
||||
struct_ok = False
|
||||
if state["stopped"]:
|
||||
break
|
||||
|
||||
# Анонимный тип внутри valueType задаёт базовый тип и xsi:type не несёт
|
||||
for vt in pkg.iter():
|
||||
if not isinstance(vt.tag, str) or local(vt) != "valueType":
|
||||
continue
|
||||
for c in vt:
|
||||
if isinstance(c.tag, str) and local(c) == "typeDef" and c.get(f"{{{XSI_NS}}}type") is not None:
|
||||
report_warn(f'{vt.get("name")} : у <typeDef> внутри <valueType> задан xsi:type — '
|
||||
"платформа его здесь не пишет")
|
||||
|
||||
# Род базового типа должен совпадать
|
||||
for t in pkg.iter():
|
||||
if not isinstance(t.tag, str) or t.get("base") is None:
|
||||
continue
|
||||
kind = local(t)
|
||||
if kind not in ("objectType", "valueType"):
|
||||
continue
|
||||
parts = t.get("base").split(":")
|
||||
if len(parts) != 2:
|
||||
continue
|
||||
if t.nsmap.get(parts[0]) != target_ns:
|
||||
continue
|
||||
if kind == "objectType" and parts[1] in value_type_names:
|
||||
report_error(f'{t.get("name")} : base="{t.get("base")}" ссылается на valueType, '
|
||||
"а objectType может наследоваться только от objectType")
|
||||
struct_ok = False
|
||||
if kind == "valueType" and parts[1] in object_type_names:
|
||||
report_error(f'{t.get("name")} : base="{t.get("base")}" ссылается на objectType, '
|
||||
"а valueType может строиться только на простом типе")
|
||||
struct_ok = False
|
||||
|
||||
# Union без состава
|
||||
for t in pkg.iter():
|
||||
if not isinstance(t.tag, str) or local(t) not in ("valueType", "typeDef"):
|
||||
continue
|
||||
if t.get("variety") != "Union" or t.get("memberTypes") is not None:
|
||||
continue
|
||||
if not any(isinstance(c.tag, str) and local(c) == "typeDef" for c in t):
|
||||
tn = t.get("name") or "(анонимный тип)"
|
||||
report_warn(f'{tn} : variety="Union" без memberTypes и без вложенных типов — состав объединения пуст')
|
||||
|
||||
if struct_ok and not state["stopped"]:
|
||||
report_ok("Структура типов и свойств согласована")
|
||||
|
||||
if state["stopped"]:
|
||||
finalize()
|
||||
sys.exit(1)
|
||||
|
||||
# ── 11. metadata object ──────────────────────────────────────
|
||||
|
||||
if md_path:
|
||||
md = _parse_xml(md_path)
|
||||
md_name = md.find(f".//{{{MD_NS}}}XDTOPackage/{{{MD_NS}}}Properties/{{{MD_NS}}}Name")
|
||||
md_ns = md.find(f".//{{{MD_NS}}}XDTOPackage/{{{MD_NS}}}Properties/{{{MD_NS}}}Namespace")
|
||||
if md_name is None:
|
||||
report_error("В объекте метаданных не задано <Name>")
|
||||
elif (md_name.text or "") != file_name:
|
||||
report_error(f'<Name>{md_name.text}</Name> не совпадает с именем каталога "{file_name}"')
|
||||
else:
|
||||
report_ok(f"Объект метаданных: Name = {md_name.text}")
|
||||
if md_ns is not None and (md_ns.text or "") != target_ns:
|
||||
report_error(f"<Namespace>{md_ns.text}</Namespace> не совпадает с targetNamespace пакета ({target_ns})")
|
||||
elif md_ns is not None:
|
||||
report_ok("Namespace объекта метаданных совпадает с targetNamespace")
|
||||
else:
|
||||
report_warn("Файл объекта метаданных <Имя>.xml не найден рядом с каталогом пакета")
|
||||
|
||||
# ── 12. registration + namespace uniqueness ──────────────────
|
||||
|
||||
config_xml = os.path.join(config_dir, "Configuration.xml")
|
||||
if os.path.exists(config_xml):
|
||||
cfg = _parse_xml(config_xml)
|
||||
registered = any((e.text or "") == file_name
|
||||
for e in cfg.iterfind(f".//{{{MD_NS}}}Configuration/{{{MD_NS}}}ChildObjects/{{{MD_NS}}}XDTOPackage"))
|
||||
if registered:
|
||||
report_ok("Зарегистрирован в Configuration.xml")
|
||||
else:
|
||||
report_error(f"<XDTOPackage>{file_name}</XDTOPackage> отсутствует в ChildObjects файла "
|
||||
"Configuration.xml — платформа пакет не увидит")
|
||||
|
||||
pkg_root = os.path.join(config_dir, "XDTOPackages")
|
||||
if os.path.isdir(pkg_root):
|
||||
clash = []
|
||||
for other in sorted(os.listdir(pkg_root)):
|
||||
if other == file_name:
|
||||
continue
|
||||
ob = os.path.join(pkg_root, other, "Ext", "Package.bin")
|
||||
if not os.path.exists(ob):
|
||||
continue
|
||||
try:
|
||||
if _parse_xml(ob).getroot().get("targetNamespace") == target_ns:
|
||||
clash.append(other)
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
# Платформа отвергает пакет, если импортируемого namespace нет в конфигурации:
|
||||
# «Ошибка проверки модели XDTO: xdto-package-3.3 … не определен»
|
||||
known_ns = {}
|
||||
for other in sorted(os.listdir(pkg_root)):
|
||||
ob = os.path.join(pkg_root, other, "Ext", "Package.bin")
|
||||
if not os.path.exists(ob):
|
||||
continue
|
||||
try:
|
||||
known_ns[_parse_xml(ob).getroot().get("targetNamespace")] = other
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
missing_imports = [i for i in imports if i not in known_ns and i not in PLATFORM_NS]
|
||||
if missing_imports:
|
||||
report_error("Импортируемые пакеты не определены в конфигурации: "
|
||||
+ ", ".join(missing_imports)
|
||||
+ ". Платформа отвергнет пакет при обновлении конфигурации — "
|
||||
"соберите зависимости первыми")
|
||||
elif imports:
|
||||
report_ok("Все импорты разрешаются в пакеты конфигурации")
|
||||
|
||||
if clash:
|
||||
report_warn(f'targetNamespace "{target_ns}" объявлен также в пакет(ах): {", ".join(clash)}. '
|
||||
"Платформа это допускает, но <import> на это пространство имён становится неоднозначным")
|
||||
else:
|
||||
report_ok("targetNamespace уникален в конфигурации")
|
||||
else:
|
||||
report_warn(f"Configuration.xml не найден ({config_dir}) — проверки регистрации и уникальности namespace пропущены")
|
||||
|
||||
finalize()
|
||||
sys.exit(1 if state["errors"] > 0 else 0)
|
||||
@@ -21,3 +21,8 @@
|
||||
|
||||
# Бинарники 1С
|
||||
*.bin binary
|
||||
|
||||
# Package.bin пакетов XDTO — текстовый XML, несмотря на расширение. Оставляем
|
||||
# под правилом *.bin binary (байты не нормализуются), но включаем текстовый diff,
|
||||
# иначе изменение модели пакета в истории выглядит как «Binary files differ».
|
||||
XDTOPackages/**/Package.bin diff
|
||||
|
||||
@@ -32,6 +32,9 @@ __pycache__/
|
||||
.claude/skills/web-test/scripts/node_modules/
|
||||
.claude/skills/web-test/.browser-session.json
|
||||
|
||||
# Маркер отработавшего prepare() в фикстуре _suite-root
|
||||
tests/web-test/_suite-root/prepare-ran.txt
|
||||
|
||||
# Скриншоты и видео (артефакты тестирования web-test)
|
||||
*.png
|
||||
*.mp4
|
||||
|
||||
@@ -70,6 +70,7 @@ python tools/cc-1c-skills/scripts/switch.py
|
||||
| Расширения (CFE) | 5 навыков `/cfe-*` | Создание, заимствование, перехват методов, валидация, анализ расширений | [Подробнее](docs/cfe-guide.md) |
|
||||
| Подсистемы (Subsystem) | 4 навыка `/subsystem-*` | Анализ, создание, редактирование, валидация подсистем конфигурации | [Подробнее](docs/subsystem-guide.md) |
|
||||
| Командный интерфейс (CI) | 2 навыка `/interface-*` | Редактирование и валидация CommandInterface.xml подсистем | [Подробнее](docs/subsystem-guide.md) |
|
||||
| Пакеты XDTO | 5 навыков `/xdto-*` | Анализ, создание из XML-схемы, выгрузка в схему, точечное редактирование, валидация пакетов XDTO | [Подробнее](docs/xdto-guide.md) |
|
||||
| Базы данных (DB) | 9 навыков `/db-*` | Создание баз, загрузка/выгрузка конфигураций, обновление БД, загрузка из Git | [Подробнее](docs/db-guide.md) |
|
||||
| Веб-публикация (Web) | 4 навыка `/web-*` | Публикация баз через Apache, статус, остановка, удаление публикаций | [Подробнее](docs/web-guide.md) |
|
||||
| Тестирование (Web) | `/web-test` | Взаимодействие с веб-клиентом 1С — навигация, формы, таблицы, отчёты, тестирование | [Подробнее](docs/web-test-guide.md) |
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
| XML-элемент | Каталог | Русское название | Спецификация |
|
||||
|-------------|---------|-----------------|--------------|
|
||||
| `ExchangePlan` | `ExchangePlans/` | Планы обмена | [1c-config-objects-spec.md § 15](1c-config-objects-spec.md#15-планы-обмена-exchangeplans) |
|
||||
| `XDTOPackage` | `XDTOPackages/` | XDTO-пакеты | [1c-configuration-spec.md § 6.14](1c-configuration-spec.md#614-xdtopackage--xdto-пакет) |
|
||||
| `XDTOPackage` | `XDTOPackages/` | XDTO-пакеты | [1c-xdto-spec.md](1c-xdto-spec.md) |
|
||||
| `WebService` | `WebServices/` | Веб-сервисы | [1c-config-objects-spec.md § 25](1c-config-objects-spec.md#25-веб-сервисы-webservices) |
|
||||
| `HTTPService` | `HTTPServices/` | HTTP-сервисы | [1c-config-objects-spec.md § 24](1c-config-objects-spec.md#24-http-сервисы-httpservices) |
|
||||
| `WSReference` | `WSReferences/` | WS-ссылки | [1c-configuration-spec.md § 6.15](1c-configuration-spec.md#615-wsreference--ws-ссылка) |
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
# Спецификация формата XML пакетов XDTO 1С
|
||||
|
||||
Формат: XML-выгрузка конфигурации 1С:Предприятие 8.3 (Конфигуратор → Конфигурация → Выгрузить конфигурацию в файлы).
|
||||
Версии формата: `2.17` (платформа 8.3.20–8.3.24), `2.20` (платформа 8.3.27+).
|
||||
|
||||
Источники: выгрузки Бухгалтерия предприятия (8.3.24), ERP 2 (8.3.24) — 760 пакетов.
|
||||
|
||||
> **Связанные спецификации:**
|
||||
> - Корневая структура конфигурации — [1c-configuration-spec.md](1c-configuration-spec.md)
|
||||
> - DSL навыков (XSD как формат описания) — [xdto-dsl-spec.md](xdto-dsl-spec.md)
|
||||
> - Сводный индекс — [1c-specs-index.md](1c-specs-index.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. Структура каталогов
|
||||
|
||||
```
|
||||
XDTOPackages/
|
||||
├── ОбменСБанком.xml # Объект метаданных — 4 свойства, и всё
|
||||
├── ОбменСБанком/
|
||||
│ └── Ext/
|
||||
│ └── Package.bin # Модель пакета
|
||||
└── ...
|
||||
```
|
||||
|
||||
Регистрация в корневом `Configuration.xml`:
|
||||
|
||||
```xml
|
||||
<ChildObjects>
|
||||
...
|
||||
<XDTOPackage>ОбменСБанком</XDTOPackage>
|
||||
</ChildObjects>
|
||||
```
|
||||
|
||||
Ни форм, ни модулей, ни макетов у пакета XDTO нет.
|
||||
|
||||
---
|
||||
|
||||
## 2. Объект метаданных `<Имя>.xml`
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" ... version="2.17">
|
||||
<XDTOPackage uuid="6417d7c8-6436-4907-98eb-44cd7638f3f1">
|
||||
<Properties>
|
||||
<Name>ApdexExport</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Apdex export</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<Namespace>www.v8.1c.ru/ssl/performace-assessment/apdexExport</Namespace>
|
||||
</Properties>
|
||||
</XDTOPackage>
|
||||
</MetaDataObject>
|
||||
```
|
||||
|
||||
| Свойство | Описание |
|
||||
|---|---|
|
||||
| `Name` | Имя объекта метаданных. Должно быть валидным идентификатором 1С |
|
||||
| `Synonym` | Многоязычное представление |
|
||||
| `Comment` | Комментарий |
|
||||
| `Namespace` | URI целевого пространства имён. Дублирует `targetNamespace` в `Package.bin` |
|
||||
|
||||
Других свойств у пакета XDTO нет.
|
||||
|
||||
---
|
||||
|
||||
## 3. `Ext/Package.bin` — модель пакета
|
||||
|
||||
Несмотря на расширение `.bin`, это **текстовый XML**: UTF-8 с BOM, перевод строки CRLF,
|
||||
отступ — символы табуляции.
|
||||
|
||||
```xml
|
||||
<package xmlns="http://v8.1c.ru/8.1/xdto"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
targetNamespace="urn:1C.ru:ClientBankExchange">
|
||||
<property xmlns:d2p1="urn:1C.ru:ClientBankExchange" name="ClientBankExchange" type="d2p1:ClientBankExchange"/>
|
||||
<valueType name="СуммаТип" base="xs:decimal" variety="Atomic" totalDigits="18" fractionDigits="2"/>
|
||||
<objectType name="ClientBankExchange">
|
||||
<property xmlns:d3p1="urn:1C.ru:ClientBankExchange" name="ВерсияФормата" type="d3p1:ВерсияСхемы"/>
|
||||
<property name="Отправитель">
|
||||
<typeDef xsi:type="ValueType" base="xs:string" variety="Atomic" maxLength="160"/>
|
||||
</property>
|
||||
</objectType>
|
||||
</package>
|
||||
```
|
||||
|
||||
### 3.1. Вложенность элементов
|
||||
|
||||
```
|
||||
package > objectType | valueType | property | import
|
||||
objectType > property
|
||||
valueType > enumeration | pattern | typeDef
|
||||
property > typeDef
|
||||
typeDef > property | enumeration | pattern
|
||||
```
|
||||
|
||||
Всего восемь имён элементов. `typeDef` — анонимный (встроенный) тип; его разновидность
|
||||
задаёт `xsi:type` = `ValueType` либо `ObjectType`.
|
||||
|
||||
**Порядок элементов верхнего уровня обязателен:**
|
||||
|
||||
```
|
||||
import* → property* → valueType* → objectType*
|
||||
```
|
||||
|
||||
Ему удовлетворяют все 760 пакетов корпуса. Нарушение порядка платформа не прощает —
|
||||
`db-update` падает с «Ошибка преобразования данных XDTO: Чтение объекта типа
|
||||
`{http://v8.1c.ru/8.1/xdto}Package`… Проверка свойства 'property'». Это существенно
|
||||
при сборке из XML-схемы, где порядок объявлений верхнего уровня произвольный.
|
||||
|
||||
### 3.2. `<package>`
|
||||
|
||||
| Атрибут | Значения | Описание |
|
||||
|---|---|---|
|
||||
| `targetNamespace` | URI | Целевое пространство имён |
|
||||
| `elementFormQualified` | `true` \| `false` | Умолчание при отсутствии — `true` |
|
||||
| `attributeFormQualified` | `true` \| `false` | Умолчание при отсутствии — `false` |
|
||||
|
||||
Порядок атрибутов: `targetNamespace`, `elementFormQualified`, `attributeFormQualified`.
|
||||
|
||||
### 3.3. `<import>`
|
||||
|
||||
`<import namespace="URI"/>` — объявление зависимости от другого пакета. Разрешается
|
||||
по namespace среди пакетов конфигурации; `schemaLocation` в модели XDTO нет.
|
||||
|
||||
### 3.4. `<objectType>` и `<typeDef xsi:type="ObjectType">`
|
||||
|
||||
| Атрибут | Значения | Описание |
|
||||
|---|---|---|
|
||||
| `name` | идентификатор | Только у именованного `objectType` |
|
||||
| `base` | QName | Базовый тип (наследование) |
|
||||
| `open` | `true` \| `false` | Допускает произвольные элементы и атрибуты |
|
||||
| `abstract` | `true` \| `false` | Абстрактный тип |
|
||||
| `mixed` | `true` \| `false` | Смешанное содержимое |
|
||||
| `ordered` | `true` \| `false` | `false` — выбор одного из вариантов (аналог `xs:choice`) |
|
||||
| `sequenced` | `true` | Последовательное содержимое |
|
||||
|
||||
Порядок атрибутов `objectType`: `name`, `base`, `open`, `abstract`, `mixed`, `ordered`, `sequenced`.
|
||||
Порядок атрибутов `typeDef`: `xsi:type`, `base`, `mixed`, `open`, `ordered`, `sequenced`, далее атрибуты простого типа.
|
||||
|
||||
### 3.5. `<valueType>` и `<typeDef xsi:type="ValueType">`
|
||||
|
||||
| Атрибут | Значения |
|
||||
|---|---|
|
||||
| `name` | идентификатор (только у именованного `valueType`) |
|
||||
| `base` | QName базового типа |
|
||||
| `variety` | `Atomic` \| `List` \| `Union` |
|
||||
| `itemType` | QName — тип элемента списка (`variety="List"`) |
|
||||
| `memberTypes` | список типов объединения (`variety="Union"`) |
|
||||
|
||||
Фасеты задаются **атрибутами**, а не дочерними элементами:
|
||||
`length`, `minLength`, `maxLength`, `totalDigits`, `fractionDigits`,
|
||||
`minInclusive`, `maxInclusive`, `minExclusive`, `maxExclusive`,
|
||||
`whiteSpace` (`preserve` \| `collapse`).
|
||||
|
||||
Базовый тип может задаваться не атрибутом `base`, а вложенным анонимным `typeDef`
|
||||
**без** `xsi:type` — соответствует анонимному `xs:simpleType` внутри `xs:restriction`:
|
||||
|
||||
```xml
|
||||
<valueType name="INN12Type" variety="Atomic" length="12">
|
||||
<typeDef base="xs:string" variety="Atomic"/>
|
||||
<pattern>[0-9]{12}</pattern>
|
||||
</valueType>
|
||||
```
|
||||
|
||||
Дочерними элементами идут только `<pattern>` и `<enumeration>` — значение в тексте узла:
|
||||
|
||||
```xml
|
||||
<valueType name="НомерСчетаТип" base="xs:string" variety="Atomic" length="20">
|
||||
<pattern>[0-9]{20}</pattern>
|
||||
</valueType>
|
||||
```
|
||||
|
||||
У `<enumeration>` встречается атрибут `xsi:type` (например `xs:string`), указывающий тип литерала.
|
||||
|
||||
### 3.6. `<property>`
|
||||
|
||||
| Атрибут | Значения | Описание |
|
||||
|---|---|---|
|
||||
| `name` | идентификатор | Имя свойства |
|
||||
| `ref` | QName | Ссылка на глобальное свойство вместо собственного объявления |
|
||||
| `type` | QName | Тип свойства. При отсутствии и `type`, и вложенного `typeDef` — произвольный тип |
|
||||
| `lowerBound` | `0` \| `1` | Минимальная кратность. Умолчание при отсутствии — `1` |
|
||||
| `upperBound` | число \| `-1` | Максимальная кратность; `-1` — неограниченно. Умолчание — `1` |
|
||||
| `nillable` | `true` \| `false` | Допускает `xsi:nil` |
|
||||
| `fixed` | значение | Фиксированное значение |
|
||||
| `default` | значение | Значение по умолчанию |
|
||||
| `form` | `Element` \| `Attribute` \| `Text` | Форма представления в XML. Умолчание — `Element` |
|
||||
| `localName` | строка | Исходное XML-имя, если оно не является валидным идентификатором 1С |
|
||||
| `qualified` | `true` \| `false` | Переопределение `*FormQualified` для конкретного свойства |
|
||||
|
||||
Порядок атрибутов: `name`, `ref`, `type`, `lowerBound`, `upperBound`, `nillable`,
|
||||
`fixed`, `default`, `form`, `localName`, `qualified`.
|
||||
|
||||
**`form="Text"`** — свойство хранит собственное значение элемента (аналог `xs:simpleContent`).
|
||||
Такое свойство платформа всегда называет `__content`:
|
||||
|
||||
```xml
|
||||
<objectType name="Error">
|
||||
<property name="code" type="xs:NCName" lowerBound="1" form="Attribute"/>
|
||||
<property name="__content" type="xs:string" form="Text"/>
|
||||
</objectType>
|
||||
```
|
||||
|
||||
Наличие `form="Text"` не отменяет флагов самого типа — `mixed`, `sequenced` и прочие
|
||||
задаются как обычно:
|
||||
|
||||
```xml
|
||||
<objectType name="Account" mixed="true" sequenced="true">
|
||||
<property name="bic" type="d3p1:BicType" lowerBound="1" form="Attribute"/>
|
||||
<property name="__content" type="d3p1:AccNumType" form="Text"/>
|
||||
</objectType>
|
||||
```
|
||||
|
||||
**`localName`** — при импорте XML-схемы имя, недопустимое как идентификатор 1С,
|
||||
санируется, а оригинал сохраняется:
|
||||
|
||||
```xml
|
||||
<property name="isFixPlaceResidence_" type="xs:boolean" localName="isFixPlaceResidence "/>
|
||||
```
|
||||
|
||||
### 3.7. Порядок свойств внутри типа
|
||||
|
||||
Свойства с `form="Attribute"` обычно идут перед остальными — так записаны 96.5% типов
|
||||
корпуса. Оставшиеся 3.5% содержат произвольное чередование; порядок значим и сохраняется
|
||||
платформой как есть.
|
||||
|
||||
---
|
||||
|
||||
## 4. Схема префиксов пространств имён
|
||||
|
||||
Каждая ссылка на тип из пространства имён, отличного от `xs:`/`xsi:`, требует объявления
|
||||
префикса вида `dNpM`:
|
||||
|
||||
```xml
|
||||
<property xmlns:d3p1="urn:1C.ru:ClientBankExchange" name="ВерсияФормата" type="d3p1:ВерсияСхемы"/>
|
||||
```
|
||||
|
||||
- `N` — глубина узла: `package` = 1, его прямые потомки = 2, свойство внутри `objectType` = 3,
|
||||
свойство внутри `typeDef` = 5, глубже — 7, 9 и т.д.
|
||||
- `M` — порядковый номер нового пространства имён, объявляемого на этом узле (с 1).
|
||||
|
||||
Префикс объявляется **на первом узле, которому он нужен**; потомки переиспользуют его из
|
||||
области видимости, а не объявляют заново:
|
||||
|
||||
```xml
|
||||
<objectType xmlns:d2p1="…/Permissions/1.0.0.1" name="InternetConnectionBase" base="d2p1:PermissionBase">
|
||||
<property name="Protocol" type="d2p1:NetworkProtocols" lowerBound="0" nillable="true"/>
|
||||
</objectType>
|
||||
```
|
||||
|
||||
Целевое пространство имён самого пакета исключением не является — ссылка на собственный
|
||||
тип тоже требует локального объявления.
|
||||
|
||||
Частоты по корпусу: `d3p1` — 109 738, `d2p1` — 13 108, `d5p1` — 9 581, далее по убыванию до `d16p1`.
|
||||
|
||||
### 4.1. Нотация Кларка в `memberTypes`
|
||||
|
||||
Значения `memberTypes` записываются нотацией Кларка `{URI}Локальное`, без префикса
|
||||
(125 случаев из 135 в корпусе; остальные — обычные префиксные QName):
|
||||
|
||||
```xml
|
||||
<valueType xmlns:d2p1="http://v8.1c.ru/8.1/data/core" name="UUID" variety="Union"
|
||||
memberTypes="{http://v8.1c.ru/8.1/data/core}UUID {http://www.w3.org/2001/XMLSchema}base64Binary"/>
|
||||
```
|
||||
|
||||
Обратите внимание: объявление `xmlns:d2p1` присутствует, хотя в значении не используется —
|
||||
пространство имён всё равно проходит через общий механизм выделения префиксов. Если оно уже
|
||||
объявлено предком, нового объявления не появляется.
|
||||
|
||||
Остальные атрибуты-QName (`type`, `base`, `ref`, `itemType`) всегда префиксные.
|
||||
|
||||
### 4.2. Осмысленные префиксы
|
||||
|
||||
Изредка вместо сгенерированного `dNpM` встречается содержательный префикс — например
|
||||
`dcsset` для настроек компоновки данных:
|
||||
|
||||
```xml
|
||||
<property xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
|
||||
name="Настройка" type="dcsset:Filter" lowerBound="0"/>
|
||||
```
|
||||
|
||||
### 4.3. Свойство с `qualified` сериализуется с префиксом
|
||||
|
||||
Если у свойства задан `qualified`, платформа пишет и сам тег, и имя атрибута с явным
|
||||
префиксом пространства имён модели XDTO (хотя по умолчанию оно и так объявлено как `xmlns=`):
|
||||
|
||||
```xml
|
||||
<d3p2:property xmlns:d3p1="http://bssys.com/upg/request" xmlns:d3p2="http://v8.1c.ru/8.1/xdto"
|
||||
name="numCheck" type="d3p1:BoolType" lowerBound="0" form="Attribute"
|
||||
d3p2:qualified="false"/>
|
||||
```
|
||||
|
||||
Префикс для XDTO выделяется последним, после префиксов типов. Такое встречается редко
|
||||
(16 узлов на весь корпус) и появляется при импорте XML-схемы, где `form` задан на
|
||||
объявлении в unqualified-пакете.
|
||||
|
||||
---
|
||||
|
||||
## 5. Умолчания записываются непоследовательно
|
||||
|
||||
Один и тот же смысл в корпусе встречается в двух написаниях: `nillable="false"` присутствует
|
||||
явно 6 899 раз, `objectType open="false"` — 6 раз, `abstract="false"` — 2 раза, явное
|
||||
`form="Element"` — 44 раза. Пакеты, полученные импортом XML-схемы, и пакеты, созданные
|
||||
руками в Конфигураторе, отличаются стилем.
|
||||
|
||||
Практическое следствие: приведение пакета к «каноническому» виду меняет байты реальных
|
||||
файлов. Инструменты, которым важен точный round-trip, обязаны сохранять literal-форму.
|
||||
|
||||
---
|
||||
|
||||
## 6. Тихая деградация в `xs:anyType`
|
||||
|
||||
При импорте XML-схемы через Конфигуратор тип из чужого пространства имён, для которого
|
||||
в конфигурации нет соответствующего пакета, **молча** заменяется на `xs:anyType` —
|
||||
без ошибки и без предупреждения:
|
||||
|
||||
```xml
|
||||
<!-- в схеме было type="ns2:contact" -->
|
||||
<property name="contact" type="xs:anyType"/>
|
||||
```
|
||||
|
||||
При этом `<import namespace="…"/>` в пакете остаётся. Признак «объявлен импорт, но
|
||||
ни один тип из этого пространства имён не используется» — надёжный индикатор проблемы.
|
||||
+32
-1
@@ -308,7 +308,7 @@ JSON-**строка** → `xsi:type="xs:string"` (напр. год `"2000"`, к
|
||||
}
|
||||
```
|
||||
|
||||
Свойства объектной формы ТЧ: `synonym` (ML; нет ключа → авто из имени), `tooltip` (ML), `comment` (строка), `fillChecking` (`DontCheck`|`ShowError`|`ShowWarning`), `use` (`ForItem`|`ForFolder`|`ForFolderAndItem`, только Catalog/ПВХ; omit при дефолте `ForItem`), `attributes` (колонки; синоним `columns`), `lineNumber` (кастомизация стандартного реквизита НомерСтроки, см. ниже).
|
||||
Свойства объектной формы ТЧ: `synonym` (ML; нет ключа → авто из имени), `tooltip` (ML), `comment` (строка), `fillChecking` (`DontCheck`|`ShowError`|`ShowWarning`), `use` (`ForItem`|`ForFolder`|`ForFolderAndItem`, только Catalog/ПВХ; omit при дефолте `ForItem`), `attributes` (колонки; синоним `columns`), `lineNumber` (кастомизация стандартного реквизита НомерСтроки, см. ниже), `lineNumberLength` (разрядность номера строки, см. §5.2).
|
||||
|
||||
Для Catalog/ChartOfCharacteristicTypes в Properties ТЧ пишется `<Use>` (дефолт `ForItem`; ключ `use` объектной формы → `ForFolder`/`ForFolderAndItem`). Document `<Use>` не имеет.
|
||||
|
||||
@@ -341,6 +341,30 @@ LineNumber дефолтные. Ключ `lineNumber` на объектной ф
|
||||
Декомпилятор эмитит `lineNumber` только при отклонении ≥1 свойства от дефолта. NB: редкий хвост ТЧ (~2.5% корпуса)
|
||||
блок `<StandardAttributes>` вовсе опускает — правило не выведено; компилятор такие не воспроизводит (эмитит блок всегда).
|
||||
|
||||
### 5.2 `lineNumberLength` — разрядность номера строки (формат 2.20)
|
||||
|
||||
Свойство `<LineNumberLength>` появилось в формате **2.20** (платформа 8.3.27): целое **5..9**, задаёт
|
||||
предельное число строк ТЧ — `5` → 99 999 (прежний потолок), `9` → 999 999 999. Нужно прикладным решениям
|
||||
с большими табличными частями (классический пример — «Показатели» в документе расчёта зарплаты).
|
||||
|
||||
```json
|
||||
"tabularSections": {
|
||||
"Показатели": { "attributes": ["Значение: Number(15,2)"], "lineNumberLength": 9 }
|
||||
}
|
||||
```
|
||||
|
||||
Ключа нет → компилятор берёт **дефолт из режима совместимости** конфигурации (`CompatibilityMode`
|
||||
в `Configuration.xml`): `Version8_3_27` и выше → `9`, ниже → `5`. Это зеркалит конфигуратор, который
|
||||
фиксирует значение в момент создания ТЧ и позже не пересчитывает: в одной конфигурации нормально
|
||||
соседствуют ТЧ с `5` (созданные раньше) и `9` (созданные на новом режиме).
|
||||
|
||||
В формате 2.17 тег не эмитится вовсе. Декомпилятор захватывает значение при наличии тега — всегда,
|
||||
а не только при отличии от дефолта: дефолт зависит от режима совместимости, и выводить его повторно
|
||||
значило бы дублировать логику компилятора.
|
||||
|
||||
**Ограничение расширений:** у *заимствованной* ТЧ свойство переопределить нельзя (по документации 1С —
|
||||
до версии 8.3.28); у ТЧ, добавленных самим расширением, — можно.
|
||||
|
||||
---
|
||||
|
||||
## 6. Значения перечислений
|
||||
@@ -442,6 +466,13 @@ LineNumber дефолтные. Ключ `lineNumber` на объектной ф
|
||||
|
||||
В `standardAttributes` указывают **только отклонения** от профиля (переопределение синонима, нетиповой fillChecking и т.п.).
|
||||
|
||||
**Формат 2.20 — `TypeReductionMode`** (режим приведения типов). Платформа 8.3.27 пишет его каждому
|
||||
стандартному реквизиту; значение выводится компилятором и в DSL обычно не указывается:
|
||||
`TransformValues` для всех, кроме `Owner` — там `Deny`. Ключ `TypeReductionMode` в override оставлен
|
||||
на случай отклонения от этого правила (правило выведено на корпусе acc; при расхождении в другой
|
||||
конфигурации значение можно задать явно). У измерений регистра сведений действует то же свойство —
|
||||
ключ реквизита `typeReductionMode` (дефолт `TransformValues`). В формате 2.17 тег не эмитится.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "Catalog", "name": "Контрагенты",
|
||||
|
||||
@@ -921,15 +921,13 @@ Platform-pattern: `startDate`/`endDate`/`date` эмитятся ТОЛЬКО д
|
||||
"type": "table",
|
||||
"name": "Таблица",
|
||||
"rows": [
|
||||
{ "groupBy": ["Номенклатура"], "selection": ["Auto"], "order": ["Auto"] }
|
||||
{ "groupBy": ["Номенклатура"] }
|
||||
],
|
||||
"columns": [
|
||||
{
|
||||
"name": "Период",
|
||||
"groupBy": ["Период"],
|
||||
"filter": ["Сумма > 0"],
|
||||
"selection": ["Auto"],
|
||||
"order": ["Auto"],
|
||||
"outputParameters": { "РасположениеИтогов": "None" },
|
||||
"userSettingID": "auto",
|
||||
"userSettingPresentation": { "ru": "Колонка с периодом" }
|
||||
|
||||
@@ -254,6 +254,15 @@ export default async function({
|
||||
23 passed, 1 failed, 0 skipped (3m 42s)
|
||||
```
|
||||
|
||||
### Прогон части набора
|
||||
|
||||
```
|
||||
> Прогони только сценарии из папки 03-приходные-накладные
|
||||
> Прогони только тесты с тегом контрагенты
|
||||
```
|
||||
|
||||
Подмножество выбирается тремя способами: путём к подпапке или отдельному файлу, тегом (`--tags=`) или фильтром по имени теста (`--grep=`). Путь к подпапке работает наравне с остальными — конфиг и подготовка стенда всё равно берутся из корня набора (папки приложения в `tests/`), движок находит его сам, поднимаясь вверх. Отдельного URL или флагов для этого не нужно.
|
||||
|
||||
### Подробный отчёт
|
||||
|
||||
```
|
||||
|
||||
@@ -29,12 +29,22 @@ node run.mjs test <dir|file>... [флаги]
|
||||
| `--report=path` | (нет) | Записать машинный отчёт в файл (JSON или XML для `--format=junit`) |
|
||||
| `--report=-` | (нет) | Машинный отчёт в stdout (`-` = stdout); человеческий прогресс уходит в stderr |
|
||||
| `--format=fmt` | json | Формат отчёта: `json` / `allure` / `junit` |
|
||||
| `--report-dir=path` | dirname(report) / testDir | Каталог для скриншотов, видео, Allure-результатов |
|
||||
| `--report-dir=path` | dirname(report) / корень сьюта | Каталог для скриншотов, видео, Allure-результатов |
|
||||
| `--screenshot=strategy` | on-failure | `on-failure` / `every-step` / `off` |
|
||||
| `--record` | false | Записывать видео для каждого теста (mp4 в `--report-dir`) |
|
||||
| `-- <hookArgs…>` | — | Всё после `--` пробрасывается в `_hooks.mjs` как `hookArgs` (см. §6.1) |
|
||||
|
||||
URL не передаётся позиционно — он берётся из `webtest.config.mjs` в каталоге тестов, а флаг `--url=` переопределяет URL дефолтного контекста. `webtest.config.mjs` и `_hooks.mjs` резолвятся от каталога **первого** пути, поэтому перечисляемые файлы должны лежать в одной папке сьюта.
|
||||
URL не передаётся позиционно — он берётся из `webtest.config.mjs`, а флаг `--url=` переопределяет URL дефолтного контекста.
|
||||
|
||||
### Резолв корня сьюта
|
||||
|
||||
`webtest.config.mjs` и `_hooks.mjs` резолвятся не от переданного пути, а от **корня сьюта**: от каталога пути движок поднимается вверх до первого каталога, где лежит `webtest.config.mjs` или `_hooks.mjs`. Именно поэтому запуск подкаталога (`test tests/myapp/sales/`) и отдельного файла работает без `--url=`. Подъём ограничен каталогом с `.git` или `.v8-project.json` (сам каталог проверяется), а если их нет — текущим рабочим каталогом; выше поиск не идёт. Не нашли маркер — корнем считается переданный каталог (тогда хуков нет, и движок пишет об этом предупреждение в stderr).
|
||||
|
||||
Маркером служат **оба** файла, а не только конфиг: конфиг необязателен (§7), и сьют, у которого есть только `_hooks.mjs`, иначе молча остался бы без подготовки стенда.
|
||||
|
||||
Если переданные пути принадлежат разным сьютам (корни не совпали) — прогон не стартует: конфиг и хуки были бы взяты от первого пути, то есть чужие. Запускайте сьюты отдельно.
|
||||
|
||||
Найденный корень печатается в шапке прогона, рядом — переданные пути, если они от него отличаются.
|
||||
|
||||
### Валидация CLI
|
||||
|
||||
@@ -200,7 +210,7 @@ export default async function({ clerk, manager, step }) {
|
||||
ctx.testInfo = {
|
||||
name, // 'Навигация по разделам' (с подставленными params)
|
||||
file, // '01-navigation.test.mjs' (basename)
|
||||
filePath, // '01-navigation.test.mjs' (relative к testDir, разделитель '/')
|
||||
filePath, // '01-navigation.test.mjs' (relative к корню сьюта, разделитель '/')
|
||||
tags, // ['nav', 'smoke']
|
||||
timeout, // 60000 (ms)
|
||||
attempt, // 1..maxAttempts (1-based)
|
||||
@@ -296,11 +306,13 @@ await assert.throws(asyncFn, msg?) // ожидает исключение
|
||||
|
||||
```js
|
||||
assert.formHasField(state, fieldName, msg?)
|
||||
// проверяет наличие state.fields[fieldName]; в сообщении об ошибке
|
||||
// перечисляются доступные поля для быстрой диагностики
|
||||
// проверяет, что в массиве state.fields есть поле с таким name;
|
||||
// в сообщении об ошибке перечисляются доступные поля для быстрой диагностики
|
||||
|
||||
assert.formTitle(state, expected, msg?)
|
||||
// проверяет, что state.title содержит expected
|
||||
// проверяет, что state.title СОДЕРЖИТ expected (подстрока, не строгое равенство).
|
||||
// state.title — заголовок активной формы: сначала из шапки формы, при её отсутствии —
|
||||
// из панели открытых окон; null, если недоступны оба (тогда ассерт падает с этим фактом)
|
||||
|
||||
assert.tableHasRow(table, predicate, msg?)
|
||||
// predicate: объект (частичное совпадение по ===) или функция row => bool
|
||||
@@ -320,7 +332,7 @@ assert.noErrors(state, msg?)
|
||||
|
||||
## 6. Хуки
|
||||
|
||||
Все хуки определяются в `_hooks.mjs` в корне каталога тестов.
|
||||
Все хуки определяются в `_hooks.mjs` в корне сьюта (§1 «Резолв корня сьюта»).
|
||||
|
||||
### Три уровня
|
||||
|
||||
@@ -451,7 +463,7 @@ node run.mjs test tests/myapp/ --bail -- --rebuild-stand --reload-data
|
||||
|
||||
## 7. Файл конфигурации
|
||||
|
||||
`webtest.config.mjs` в корне каталога тестов. Необязателен — если отсутствует, URL должен быть передан через CLI.
|
||||
`webtest.config.mjs` в корне сьюта (§1 «Резолв корня сьюта»). Необязателен — если отсутствует, URL должен быть передан через CLI.
|
||||
|
||||
```js
|
||||
export default {
|
||||
@@ -599,7 +611,7 @@ await step('Менеджер утверждает', async () => {
|
||||
await step('Кладовщик проверяет статус', async () => {
|
||||
// страница кладовщика ТА ЖЕ — форма открыта, навигация не нужна
|
||||
const state = await clerk.getFormState();
|
||||
assert.equal(state.fields['Статус']?.value, 'Утверждён');
|
||||
assert.equal(state.fields.find(f => f.name === 'Статус')?.value, 'Утверждён');
|
||||
});
|
||||
```
|
||||
|
||||
@@ -782,7 +794,7 @@ await step('Кладовщик проверяет статус', async () => {
|
||||
Движок всегда заполняет следующие метки (`labels`):
|
||||
|
||||
- **`tag`** — по одному на каждый элемент `mod.tags[]`. Готовая фильтрация в Allure-отчёте без дополнительной разметки.
|
||||
- **`suite`** — `dirname(t.filePath)`. Тесты в корне `testDir` идут под `'root'`, тесты в подкаталоге `sales/` — под `'sales'`. Это даёт левую группировку отчёта без ручной разметки.
|
||||
- **`suite`** — `dirname(t.filePath)`. Тесты в корне сьюта идут под `'root'`, тесты в подкаталоге `sales/` — под `'sales'`. Это даёт левую группировку отчёта без ручной разметки.
|
||||
- **`severity`** — резолв в порядке приоритета:
|
||||
1. `export const severity = 'critical'` в самом тесте, **если значение валидное** (одно из `blocker | critical | normal | minor | trivial`). Если экспорт задан, но значение невалидное — пункт пропускается и идём в (3); резолв через теги (пункт 2) при этом **не выполняется** (хотел бы автор иначе — он бы не объявлял `severity`).
|
||||
2. Иначе **максимальный ранг** среди тегов теста (стандартные имена `blocker | critical | normal | minor | trivial` напрямую, либо через `config.severity`-маппинг).
|
||||
@@ -792,9 +804,9 @@ await step('Кладовщик проверяет статус', async () => {
|
||||
|
||||
Пример: `tags: ['smoke', 'recording']` + `severity: { critical: ['smoke'], minor: ['recording'] }` → severity = `critical` (5 > 2).
|
||||
|
||||
#### Доп. файлы Allure через `<testDir>/_allure/`
|
||||
#### Доп. файлы Allure через `<корень сьюта>/_allure/`
|
||||
|
||||
Движок ищет каталог `_allure/` рядом с тестами и копирует все его файлы в `reportDir` перед генерацией отчёта. Конвенция для статичной настройки Allure, для которой нет места внутри JSON-файла теста:
|
||||
Движок ищет каталог `_allure/` в корне сьюта и копирует все его файлы в `reportDir` перед генерацией отчёта. Конвенция для статичной настройки Allure, для которой нет места внутри JSON-файла теста:
|
||||
|
||||
| Файл | Назначение |
|
||||
|------|-----------|
|
||||
@@ -908,7 +920,7 @@ export const params = [
|
||||
export default async function({ fillFields, getFormState, assert }, { type, field, value }) {
|
||||
await fillFields({ [field]: value });
|
||||
const state = await getFormState();
|
||||
assert.equal(state.fields[field]?.value, String(value));
|
||||
assert.equal(state.fields.find(f => f.name === field)?.value, String(value));
|
||||
}
|
||||
```
|
||||
|
||||
@@ -922,7 +934,7 @@ export default async function({ fillFields, getFormState, assert }, { type, fiel
|
||||
|
||||
## 14. Обнаружение тестов
|
||||
|
||||
`testDir` (первый позиционный аргумент после URL) — каталог, в котором живут тесты. Сборщик рекурсивно обходит дерево и собирает файлы по правилам ниже.
|
||||
Позиционные аргументы — пути к тестам; каталог, от которого считаются относительные пути в отчёте, — корень сьюта (§1 «Резолв корня сьюта»). Сборщик рекурсивно обходит дерево и собирает файлы по правилам ниже.
|
||||
|
||||
```
|
||||
tests/myapp/
|
||||
@@ -944,14 +956,13 @@ tests/myapp/
|
||||
| Шаблон имени | Только `*.test.mjs` |
|
||||
| Несколько путей | `node run.mjs test a.test.mjs b.test.mjs dir/` — наборы объединяются, дублируются и сортируются |
|
||||
| Порядок | Сортировка по полному относительному пути (`sales/01` идёт до `warehouse/01`) |
|
||||
| `file` в отчёте | `relative(testDir, file)` с разделителем `/`, например `sales/01-order-create.test.mjs` |
|
||||
| `file` в отчёте | `relative(<корень сьюта>, file)` с разделителем `/`, например `sales/01-order-create.test.mjs` |
|
||||
| Фильтр по пути с CLI | `node run.mjs test tests/myapp/sales/` запустит только подкаталог |
|
||||
| Конкретный файл | `node run.mjs test tests/myapp/sales/01-order-create.test.mjs` |
|
||||
|
||||
### Чего НЕТ (сознательное упрощение)
|
||||
|
||||
- **`_hooks.mjs` на уровне подкаталога.** Движок ищет `_hooks.mjs` только в корне `testDir`. Подкаталоги свои хуки не получают.
|
||||
- **`webtest.config.mjs` на уровне подкаталога.** Тоже только в корне.
|
||||
- **`_hooks.mjs` / `webtest.config.mjs` на уровне подкаталога.** Оба берутся только из корня сьюта — того каталога, где лежит ближайший из них (§1 «Резолв корня сьюта»). Подкаталоги своих копий не получают; вложенный каталог со своим `webtest.config.mjs` — это уже отдельный сьют.
|
||||
- **Многоуровневой Suite-разметки из дерева каталогов.** Allure-метка `suite` строится только по первому уровню (`dirname(filePath)`); более глубокую группировку делайте через `tags`.
|
||||
- **Контекста по умолчанию на уровне подкаталога.** Каждый тест объявляет `context` / `contexts` сам; от пути контексты не наследуются.
|
||||
|
||||
@@ -1117,7 +1128,8 @@ JSON-отчёт (`tests[]`, полная структура — §9) для ка
|
||||
|
||||
| Термин | Определение |
|
||||
|--------|-------------|
|
||||
| **testDir** | Каталог тестов, переданный позиционным аргументом движку. Корень для discovery, `_hooks.mjs`, `webtest.config.mjs`, `_allure/`. |
|
||||
| **Test path** | Путь к тесту или каталогу тестов, переданный позиционным аргументом. Корень только для discovery — что именно запускать. |
|
||||
| **Suite root (корень сьюта)** | Каталог, найденный подъёмом от test path до первого `webtest.config.mjs` / `_hooks.mjs` (§1). От него берутся конфиг, хуки, `_allure/`, каталог отчёта по умолчанию и относительные пути `file` в отчёте. Не зависит от того, запустили сьют целиком или один его подкаталог, — поэтому ID теста в отчёте стабилен. |
|
||||
| **Context (BrowserContext)** | Изолированная сессия Playwright. Куки/состояние/страница независимы. В рамках одного теста используется один или несколько контекстов. |
|
||||
| **Active context** | Контекст, на котором сейчас оперируют функции browser-API. Переключается `setActiveContext`. |
|
||||
| **Primary context** | Контекст, активный на входе в тест. Декларация (`mod.context` или `mod.contexts[0]`). Зафиксирован в `testInfo.primaryContext`. |
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
# Спецификация XDTO DSL — XML Schema как формат описания пакета
|
||||
|
||||
Навыки `/xdto-compile` (XSD → пакет) и `/xdto-decompile` (пакет → XSD) используют в качестве
|
||||
формата описания **обычную XML-схему**. Отдельного DSL нет: XSD и модель XDTO выражают одно и
|
||||
то же, различаясь синтаксисом и умолчаниями, а схема в реальных задачах обычно уже есть —
|
||||
прислана контрагентом.
|
||||
|
||||
> Формат самих исходников (`Package.bin`, объект метаданных, схема префиксов) —
|
||||
> [1c-xdto-spec.md](1c-xdto-spec.md).
|
||||
|
||||
## Пример
|
||||
|
||||
```xml
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:tns="urn:1C.ru:ObmenSBankom"
|
||||
targetNamespace="urn:1C.ru:ObmenSBankom"
|
||||
elementFormDefault="qualified">
|
||||
<xs:simpleType name="СуммаТип">
|
||||
<xs:restriction base="xs:decimal">
|
||||
<xs:totalDigits value="18"/>
|
||||
<xs:fractionDigits value="2"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="Платёж">
|
||||
<xs:sequence>
|
||||
<xs:element name="Дата" type="xs:date"/>
|
||||
<xs:element name="Сумма" type="tns:СуммаТип"/>
|
||||
<xs:element name="Назначение" type="xs:string" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="Номер" type="xs:string"/>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Таблица соответствий
|
||||
|
||||
| XML Schema | Модель XDTO |
|
||||
|---|---|
|
||||
| `xs:schema/@targetNamespace` | `package/@targetNamespace` |
|
||||
| `elementFormDefault="qualified"` | `elementFormQualified="true"` |
|
||||
| `attributeFormDefault="qualified"` | `attributeFormQualified="true"` |
|
||||
| `xs:import/@namespace` | `import/@namespace` |
|
||||
| `xs:complexType` | `objectType` |
|
||||
| `xs:simpleType` | `valueType` |
|
||||
| `xs:element` (глобальный) | `property` в `package` |
|
||||
| `xs:attribute` (глобальный) | `property form="Attribute"` в `package` |
|
||||
| `xs:element` (локальный) | `property` |
|
||||
| `xs:attribute` (локальный) | `property form="Attribute"` |
|
||||
| `@minOccurs` | `@lowerBound` |
|
||||
| `@maxOccurs="unbounded"` | `@upperBound="-1"` |
|
||||
| `@nillable`, `@default` | те же имена |
|
||||
| `@fixed="V"` | `@fixed="true"` + `@default="V"` — в модели признак и значение разнесены |
|
||||
| `xs:element/@ref` | `property/@ref` |
|
||||
| анонимный `xs:simpleType` в объявлении | `typeDef xsi:type="ValueType"` |
|
||||
| анонимный `xs:complexType` в объявлении | `typeDef xsi:type="ObjectType"` |
|
||||
| `xs:complexContent/xs:extension/@base` | `objectType/@base` |
|
||||
| `@abstract="true"`, `@mixed="true"` | те же имена |
|
||||
| `xs:choice` | `ordered="false"` |
|
||||
| `xs:sequence` | порядок по умолчанию |
|
||||
| `xs:any` + `xs:anyAttribute` | `open="true"` |
|
||||
| `xs:simpleContent/xs:extension/@base` | свойство `__content` с `form="Text"` |
|
||||
| `xs:restriction/@base` + дочерние фасеты | `@base` + фасеты **атрибутами** |
|
||||
| `xs:pattern`, `xs:enumeration` | `<pattern>`, `<enumeration>` — значение в тексте узла |
|
||||
| `xs:list/@itemType` | `variety="List"` + `@itemType` |
|
||||
| `xs:union/@memberTypes` | `variety="Union"` + `@memberTypes` |
|
||||
|
||||
Ловушки, на которых модель ошибается чаще всего:
|
||||
|
||||
- **Кратность инвертирована по смыслу**: `lowerBound="0"` = необязательный, `upperBound="-1"` = неограниченный.
|
||||
- **Фасеты в XDTO — атрибуты**, а не дочерние элементы: `maxLength="30"`, не `<xs:maxLength value="30"/>`.
|
||||
- **Каждая ссылка на тип требует локального объявления префикса** `dNpM` — в том числе на
|
||||
собственный `targetNamespace`.
|
||||
|
||||
Всё это делает компилятор; писать вручную ничего из перечисленного не нужно.
|
||||
|
||||
---
|
||||
|
||||
## 2. Аннотации `xdto:`
|
||||
|
||||
Две вещи XDTO выражает, а XML Schema — нет:
|
||||
|
||||
- `nillable` у атрибута (спецификация XSD допускает его только у элементов);
|
||||
- `qualified` у отдельного свойства.
|
||||
|
||||
Плюс XSD не различает «атрибут записан явно» и «атрибут опущен, действует умолчание»,
|
||||
хотя в реальных пакетах встречаются оба написания одного смысла.
|
||||
|
||||
Такие случаи описываются атрибутами из пространства имён самой модели XDTO —
|
||||
`http://v8.1c.ru/8.1/xdto`. Правило одно:
|
||||
|
||||
> **Чего XSD сказать не может — пиши атрибутом `xdto:` с тем же именем, что в `Package.bin`.**
|
||||
|
||||
```xml
|
||||
<xs:attribute name="Представление" type="xs:string"
|
||||
xmlns:xdto="http://v8.1c.ru/8.1/xdto" xdto:nillable="true"/>
|
||||
```
|
||||
|
||||
Такая схема остаётся валидной: XML Schema разрешает атрибуты из чужих пространств имён
|
||||
на объявлениях (`anyAttribute namespace="##other"` в схеме схем). Валидаторы их игнорируют.
|
||||
|
||||
Аннотации строго опциональны — подавляющее большинство схем обходится без единой.
|
||||
|
||||
| Аннотация | Где | Назначение |
|
||||
|---|---|---|
|
||||
| `xdto:nillable` | `xs:attribute` | `nillable` у свойства-атрибута |
|
||||
| `xdto:lowerBound`, `xdto:upperBound` | `xs:attribute` | кратность свойства-атрибута |
|
||||
| `xdto:qualified` | объявление | переопределение `*FormQualified` |
|
||||
| `xdto:form` | `xs:element` | записать `form` явно (например `form="Element"`) |
|
||||
| `xdto:name` | объявление | имя свойства, если XML-имя не является идентификатором 1С; тогда XML-имя уходит в `localName` |
|
||||
| `xdto:variety` | `xs:restriction`, `xs:list`, `xs:union` | записать `variety` явно |
|
||||
| `xdto:memberTypesForm="prefixed"` | `xs:union` | писать `memberTypes` префиксами, а не нотацией Кларка |
|
||||
| `xdto:open`, `xdto:abstract`, `xdto:mixed`, `xdto:ordered`, `xdto:sequenced` | `xs:complexType` | значения, не выводимые из модели содержимого |
|
||||
| `xdto:order` | `xs:complexType` | исходный порядок свойств, если он не «атрибуты первыми»; имена через `\|` |
|
||||
| `xdto:textName`, `xdto:textlowerBound`, `xdto:textupperBound`, `xdto:textnillable` | `xs:extension` в `xs:simpleContent` | параметры свойства `form="Text"`, если оно названо не `__content` |
|
||||
| `xdto:elementFormQualified`, `xdto:attributeFormQualified` | `xs:schema` | записать флаги явно |
|
||||
| `xdto:fixed` | объявление | признак фиксированного значения отдельно от самого значения: в XSD `fixed="V"` совмещает их, в модели это `fixed="true"` + `default="V"`. Нужен только для `fixed="false"` при заданном `default` |
|
||||
| `xdto:type` | `xs:enumeration` | `xsi:type` литерала перечисления |
|
||||
| `xdto:prefix` | объявление | осмысленный префикс пространства имён вместо генерируемого `dNpM` (например `dcsset`) |
|
||||
| `xdto:declareNs` | `xs:union` | объявить префикс пространства имён на узле (при нотации Кларка платформа иногда его пишет, иногда нет) |
|
||||
|
||||
### Что выводится само
|
||||
|
||||
Аннотация нужна только там, где вывод невозможен:
|
||||
|
||||
| Свойство XDTO | Выводится из |
|
||||
|---|---|
|
||||
| `open="true"` | наличие `xs:any` / `xs:anyAttribute` |
|
||||
| `ordered="false"` | `xs:choice` вместо `xs:sequence` |
|
||||
| `abstract`, `mixed` | одноимённые атрибуты `xs:complexType` |
|
||||
| `variety="List"` / `"Union"` | `xs:list` / `xs:union` |
|
||||
| порядок свойств | атрибуты первыми, затем остальные |
|
||||
|
||||
`sequenced` из XSD не выводится (в корпусе он не коррелирует однозначно ни с одной
|
||||
конструкцией) и всегда приходит аннотацией.
|
||||
|
||||
---
|
||||
|
||||
## 3. Свойства объекта метаданных
|
||||
|
||||
`Name`, `Synonym` и `Comment` живут в `xs:annotation/xs:appinfo` — штатном месте XML Schema
|
||||
для инструментальных метаданных. `Namespace` не дублируется: его единственный источник —
|
||||
`targetNamespace`.
|
||||
|
||||
```xml
|
||||
<xs:annotation>
|
||||
<xs:appinfo>
|
||||
<xdto:package xmlns:xdto="http://v8.1c.ru/8.1/xdto">
|
||||
<xdto:name>ОбменСБанком</xdto:name>
|
||||
<xdto:synonym lang="ru">Обмен с банком</xdto:synonym>
|
||||
<xdto:comment>Формат 1С:Предприятие — Клиент банка</xdto:comment>
|
||||
</xdto:package>
|
||||
</xs:appinfo>
|
||||
</xs:annotation>
|
||||
```
|
||||
|
||||
Блок опционален. Параметры `-Name`, `-Synonym`, `-Comment` навыка `/xdto-compile`
|
||||
имеют приоритет над ним. Без того и другого имя берётся из имени файла XSD.
|
||||
|
||||
Благодаря этому блоку пара `/xdto-decompile` → `/xdto-compile` замыкается без потерь,
|
||||
включая свойства объекта метаданных.
|
||||
|
||||
---
|
||||
|
||||
## 4. Прощающий ввод
|
||||
|
||||
Компилятор принимает и не вполне канонические схемы:
|
||||
|
||||
- **Имя типа без префикса** (`type="Платёж"`) трактуется как тип целевого пространства имён.
|
||||
- **`form="qualified"` на глобальном объявлении** — так экспортирует XML-схему сам
|
||||
Конфигуратор, хотя спецификация XSD допускает `form` только у локальных объявлений.
|
||||
Такие файлы читаются; сам навык пишет корректно.
|
||||
- **Порядок объявлений верхнего уровня произвольный** — компилятор расставляет их
|
||||
в требуемом моделью порядке `import → property → valueType → objectType`.
|
||||
- **`xs:group` и `xs:attributeGroup`** раскрываются по ссылке: содержимое группы
|
||||
подставляется в тип.
|
||||
|
||||
## 5. Конструкции без точного соответствия
|
||||
|
||||
XML Schema выразительнее модели XDTO. Перечисленное ниже переносится приближённо,
|
||||
и компилятор об этом **предупреждает** — молча терять свойства нельзя.
|
||||
|
||||
| Конструкция | Что происходит |
|
||||
|---|---|
|
||||
| вложенная `xs:sequence` | уплощается в плоский список свойств |
|
||||
| вложенная `xs:choice` | уплощается, и **ветки становятся необязательными**: иначе «одно из двух» превратилось бы в «оба обязательны» и тип нельзя было бы заполнить. Запрет «ровно один из» не сохраняется |
|
||||
| `xs:all` | трактуется как последовательность |
|
||||
| `minOccurs`/`maxOccurs` на самой частице | не выражается, отбрасывается |
|
||||
| `substitutionGroup` | объявление сохраняется как обычное |
|
||||
| `xs:key`, `xs:keyref`, `xs:unique` | отбрасываются |
|
||||
| `xs:redefine` | игнорируется |
|
||||
| `xs:include` | игнорируется: зависимости разрешаются только по namespace — включаемую схему нужно собрать отдельным пакетом и заменить на `xs:import` |
|
||||
|
||||
`ordered="false"` (выбор одного из вариантов) выводится только из **корневой** `xs:choice`
|
||||
типа: модель хранит признак на типе целиком, а не на вложенной частице.
|
||||
@@ -0,0 +1,185 @@
|
||||
# Работа с пакетами XDTO
|
||||
|
||||
Пакет XDTO описывает XML-формат: какие есть типы, из каких свойств состоят, что
|
||||
обязательно. По нему платформа умеет читать и писать XML — через `ФабрикаXDTO`.
|
||||
Встречается везде, где 1С обменивается данными наружу: ЭДО, ЕГАИС, ВЕТИС, ФСС,
|
||||
клиент-банк, веб-сервисы, `EnterpriseData`.
|
||||
|
||||
Навыки вызываются агентом сами — задачу можно ставить обычными словами. Ниже
|
||||
примеры формулировок и того, что за ними происходит.
|
||||
|
||||
**Что стоит назвать в задаче.** Формулировка может быть сколь угодно общей, но
|
||||
объект работы лучше назвать: каталог исходников конфигурации, имя пакета (а для
|
||||
точечной правки — и типа), путь к файлу схемы. Чего не назвали — про то агент
|
||||
спросит, прежде чем что-то делать: «добавь схему от контрагента» без пути к файлу
|
||||
он не угадает, а искать по всему диску не станет.
|
||||
|
||||
## Навыки
|
||||
|
||||
| Навык | Задача |
|
||||
|---|---|
|
||||
| [`/xdto-info`](../.claude/skills/xdto-info/SKILL.md) | Что в пакете и как заполнять тип — в терминах 1С |
|
||||
| [`/xdto-compile`](../.claude/skills/xdto-compile/SKILL.md) | Собрать пакет по XML-схеме |
|
||||
| [`/xdto-decompile`](../.claude/skills/xdto-decompile/SKILL.md) | Выгрузить пакет в XML-схему |
|
||||
| [`/xdto-edit`](../.claude/skills/xdto-edit/SKILL.md) | Точечно поправить существующий пакет |
|
||||
| [`/xdto-validate`](../.claude/skills/xdto-validate/SKILL.md) | Проверить перед загрузкой в базу |
|
||||
|
||||
Формат описания — обычная XML-схема, своего DSL нет. Схема в реальных задачах
|
||||
обычно уже есть: её присылает контрагент или публикует регулятор.
|
||||
|
||||
## Сценарии
|
||||
|
||||
### Разобраться, что есть в незнакомой конфигурации
|
||||
|
||||
> «Какие пакеты XDTO есть в конфигурации `src`?»
|
||||
>
|
||||
> «Что внутри пакета `ОбменСБанком` — какие типы и с чего начинать чтение?»
|
||||
|
||||
Обычный первый шаг, когда формат чужой или давно не открывался. Список пакетов
|
||||
показывает имя, пространство имён и число типов; обзор пакета — типы и точки входа.
|
||||
|
||||
### Написать код, который заполняет объект XDTO
|
||||
|
||||
> «Сформируй платёжное поручение по пакету `ОбменСБанком` и запиши в `out.xml`»
|
||||
>
|
||||
> «Напиши обработку выгрузки заказов по пакету `ОбменМаркетплейс`, начни с типа `Заказ`»
|
||||
|
||||
Самая частая задача, и обычно она часть большей. Прежде чем писать код, агент
|
||||
смотрит структуру типа: какой тип значения присваивать каждому свойству, что
|
||||
обязательно, где нужно создать вложенный объект, какие значения допустимы.
|
||||
|
||||
Выводится это уже в терминах 1С — `Строка(6)`, `Число(18,2)`, `[обязательный]`, —
|
||||
поэтому переводить `xs:decimal` и `lowerBound="0"` в голове не приходится.
|
||||
Если тип большой, помогает срез только обязательных свойств: получается готовый
|
||||
скелет заполнения.
|
||||
|
||||
### Разобрать входящий XML
|
||||
|
||||
> «Разбери `in/egais-ttn.xml` по пакету `ЕГАИС3` — с какого типа начинать?»
|
||||
>
|
||||
> «Какие точки входа у пакета `ЕГАИС3`?»
|
||||
|
||||
Чтобы прочитать документ, надо знать, с какого типа начинать. Это **точки входа**
|
||||
пакета — его глобальные объявления; агент покажет их вместе со списком типов.
|
||||
|
||||
### Добавить пакет по схеме контрагента
|
||||
|
||||
> «Контрагент прислал `schemas/orders.xsd` — добавь пакет в конфигурацию, исходники в `src`»
|
||||
>
|
||||
> «Собери пакет по `schemas/fss-person.xsd`, имя `FSS_Person_01`»
|
||||
|
||||
Пакет собирается по схеме и сразу регистрируется в конфигурации.
|
||||
|
||||
**Обрати внимание на предупреждения.** XML Schema выразительнее модели XDTO, и часть
|
||||
конструкций переносится приближённо: вложенный `xs:choice` уплощается (ветки при этом
|
||||
становятся необязательными), `xs:all` превращается в последовательность, кратность
|
||||
на частице отбрасывается. Агент об этом сообщит — если упрощение недопустимо,
|
||||
схему надо менять, а не игнорировать сообщение.
|
||||
|
||||
Если схема ссылается на чужое пространство имён через `<xs:import>`, сначала нужен
|
||||
пакет-зависимость: платформа отвергнет конфигурацию, где импортируемого пакета нет.
|
||||
Об этом скажут ещё на сборке — чинить дешевле там, чем на `/db-update`. Исключение —
|
||||
пространства имён самой платформы (`http://www.w3.org/2001/XMLSchema`,
|
||||
`http://v8.1c.ru/8.1/data/core` и подобные): их пакетами объявлять не нужно.
|
||||
|
||||
### Поправить существующий пакет
|
||||
|
||||
> «Добавь в платёжное поручение необязательный комментарий, не длиннее 200 символов»
|
||||
>
|
||||
> «Убери свойство `СтарыйКод` у типа `Документ` в пакете `Обмен`»
|
||||
>
|
||||
> «Добавь в перечисление видов документов значение Инкассо»
|
||||
|
||||
Точечная правка не требует читать схему целиком — для больших пакетов вроде
|
||||
`EnterpriseData` это единственный практичный путь. После правки автоматически
|
||||
запускается проверка.
|
||||
|
||||
Перед изменением существующего типа полезно узнать, кого оно затронет:
|
||||
|
||||
> «Кто ссылается на тип `Адрес` из пакета `КонтактнаяИнформация`?»
|
||||
|
||||
Ответ — список типов и пакетов, включая те, что ссылаются через границу пакета.
|
||||
|
||||
Если переработка широкая — «перепиши обмен под новую версию формата» — схема
|
||||
выгружается целиком, правится и собирается обратно. Пара выгрузка-сборка
|
||||
замыкается без потерь, включая имя, синоним и комментарий объекта метаданных:
|
||||
проверено на 760 пакетах типовых конфигураций — сборка даёт исходный файл модели
|
||||
побайтово.
|
||||
|
||||
### Новая версия пакета
|
||||
|
||||
> «Сделай из пакета `Обмен` версию 2: новый пакет `ОбменV2` с namespace `urn:…:v2`,
|
||||
> старый не трогай»
|
||||
|
||||
Типовой приём: рядом со старым пакетом появляется новый с другим пространством
|
||||
имён (`EnterpriseData_1_19` → `_1_20`), а старые потребители продолжают смотреть
|
||||
на прежний namespace.
|
||||
|
||||
Отдельной команды для этого нет: агент выгружает схему исходного пакета, меняет в ней
|
||||
пространство имён и собирает под новым именем. Дополнительных действий это не требует —
|
||||
достаточно назвать в задаче исходный пакет, новое имя и новый namespace. Внутренние
|
||||
ссылки переводятся на него целиком, импорты чужих пакетов сохраняются, исходный пакет
|
||||
остаётся как был.
|
||||
|
||||
Если же надо сменить namespace **у существующего** пакета, а не сделать копию, агент
|
||||
перепишет все внутренние ссылки и перечислит пакеты, которые импортируют старый — но
|
||||
менять их не станет, потому что при версионировании это было бы ошибкой.
|
||||
|
||||
### Отдать схему контрагенту
|
||||
|
||||
> «Выгрузи схему пакета `Обмен` в `schemas/exchange.xsd`, отправлю партнёру»
|
||||
|
||||
Получается валидная XSD, ничего не теряющая. Штатный «Экспорт XML-схемы»
|
||||
в Конфигураторе для этого хуже: он теряет признак `nillable` у свойств-атрибутов,
|
||||
а для пакетов с неквалифицированной формой элементов выдаёт XSD, которую строгий
|
||||
валидатор не принимает.
|
||||
|
||||
### Проверить перед загрузкой в базу
|
||||
|
||||
> «Проверь пакет `Обмен` перед загрузкой в базу»
|
||||
>
|
||||
> «Я правил `Package.bin` руками — проверь, что платформа его примет»
|
||||
|
||||
Проверка ловит то, на чём `/db-update` отказывается принимать конфигурацию:
|
||||
импорт пакета, которого в конфигурации нет; ссылку на несуществующий тип или
|
||||
необъявленный префикс; нарушенный порядок элементов верхнего уровня; расхождение
|
||||
объекта метаданных с моделью; отсутствие пакета в `Configuration.xml`. Дешевле
|
||||
поймать здесь, чем в отказе загрузки. После правки через `/xdto-edit` проверка
|
||||
запускается сама.
|
||||
|
||||
### Разобраться, почему обмен ведёт себя странно
|
||||
|
||||
> «Пакет `ФСС` загрузился, но обращение к `Смена.Сотрудник` возвращает
|
||||
> что-то бесструктурное — разберись»
|
||||
|
||||
Такой симптом почти всегда означает, что тип не разрешился и стал «произвольным».
|
||||
Платформа об этом молчит: при импорте XML-схемы неразрешённый чужой тип заменяется
|
||||
без единой ошибки, пакет выглядит загруженным. Проверка называет и симптом,
|
||||
и причину — объявленный, но неиспользуемый импорт.
|
||||
|
||||
## Структура файлов
|
||||
|
||||
```
|
||||
XDTOPackages/
|
||||
├── ОбменСБанком.xml объект метаданных: Name, Synonym, Comment, Namespace
|
||||
└── ОбменСБанком/
|
||||
└── Ext/
|
||||
└── Package.bin модель пакета (текстовый XML, несмотря на расширение)
|
||||
```
|
||||
|
||||
Плюс регистрация в корневом `Configuration.xml` — без неё платформа пакет не увидит.
|
||||
|
||||
## Рабочий цикл
|
||||
|
||||
1. Посмотреть, что есть — `/xdto-info`
|
||||
2. Изменить — `/xdto-compile` или `/xdto-edit`
|
||||
3. Проверить до загрузки — `/xdto-validate`
|
||||
4. Применить — `/db-load-xml` + `/db-update`
|
||||
|
||||
Точный синтаксис параметров каждого навыка — в его `SKILL.md` по ссылкам выше.
|
||||
|
||||
## Спецификации
|
||||
|
||||
- Формат исходников — [1c-xdto-spec.md](1c-xdto-spec.md)
|
||||
- XML Schema как формат описания, таблица соответствий и аннотации `xdto:` —
|
||||
[xdto-dsl-spec.md](xdto-dsl-spec.md)
|
||||
+85
-5
@@ -129,7 +129,11 @@ ibcmd-проход автоматически `○ skipped`, если рядом
|
||||
}
|
||||
```
|
||||
|
||||
Раннер проверит: exitCode=0 + выход совпадает со snapshot (если есть).
|
||||
Раннер проверит: exitCode=0 + выход совпадает с эталоном.
|
||||
|
||||
Эталон **обязателен**: если его нет и кейс не объявил `noSnapshot`, тест падает. Иначе потерянный
|
||||
(или не созданный при добавлении кейса) эталон неотличим от намеренного отсутствия — тест зелёный,
|
||||
хотя выход не проверяется.
|
||||
|
||||
### С параметрами навыка
|
||||
|
||||
@@ -223,22 +227,98 @@ ibcmd-проход автоматически `○ skipped`, если рядом
|
||||
| `setup` | нет | Переопределение setup из `_skill.json` |
|
||||
| `outputPath` | нет | Относительный путь для навыков с `-OutputPath` |
|
||||
| `args_extra` | нет | Массив дополнительных CLI-аргументов |
|
||||
| `preRun` | нет | Массив шагов подготовки (создание объектов и т.п.) |
|
||||
| `expect` | нет | Дополнительные проверки: `files`, `stdoutContains` (строка/массив), `stdoutNotContains` (строка/массив) |
|
||||
| `preRun` | нет | Массив шагов подготовки (см. ниже) |
|
||||
| `expect` | нет | Дополнительные проверки (см. ниже) |
|
||||
| `expectError` | нет | `true` или строка — ожидается ошибка |
|
||||
| `noSnapshot` | нет | Непустая строка с причиной — кейс объявляет, что эталон не нужен (см. «Эталоны») |
|
||||
| `idempotent` | нет | `true` — повторный прогон с теми же аргументами должен дать байт-в-байт тот же `workDir` |
|
||||
| `runtimeOnly` | нет | `"powershell"` / `"python"` — кейс имеет смысл только на одном порте, на другом `○ skipped` |
|
||||
| `skipValidation` | нет | `true` — не запускать `postValidate` из `_skill.json` (только при `--with-validation`) |
|
||||
|
||||
### Ключи `expect`
|
||||
|
||||
| Ключ | Описание |
|
||||
|---|---|
|
||||
| `files` | Массив путей относительно `workDir` — каждый должен существовать после прогона |
|
||||
| `stdoutContains` | Строка или массив строк — все должны присутствовать в stdout |
|
||||
| `stdoutNotContains` | Строка или массив строк — ни одной не должно быть в stdout |
|
||||
| `preserves` | Объект (или массив объектов) — байтовые свойства файла, которые навык обязан сохранить |
|
||||
|
||||
`preserves` проверяет то, что снэпшот-сравнение нормализует и потому увидеть не может:
|
||||
|
||||
| Ключ | Описание |
|
||||
|---|---|
|
||||
| `file` | Путь к файлу относительно `workDir` (обязателен) |
|
||||
| `bom` | `true`/`false` — наличие UTF-8 BOM |
|
||||
| `eol` | `"crlf"` / `"lf"` |
|
||||
| `encoding` | Ожидаемое значение в XML-декларации, напр. `"UTF-8"` |
|
||||
| `finalNewline` | `true`/`false` — перевод строки в конце файла |
|
||||
| `noCR13` | `true` — в выходе не должно быть литерала ` ` |
|
||||
|
||||
`preserves` и эталон **дополняют** друг друга: первый следит за байтовым стилем файла, второй — за
|
||||
структурой содержимого. Наличие одного не отменяет необходимости другого.
|
||||
|
||||
### Шаги `preRun`
|
||||
|
||||
Массив шагов, выполняемых до запуска проверяемого навыка:
|
||||
|
||||
| Форма шага | Описание |
|
||||
|---|---|
|
||||
| `{ "script": "<навык>/scripts/<файл>", "input": {...}, "args": { "-Flag": "{inputFile}" } }` | Прогон другого навыка для подготовки фикстуры. Плейсхолдеры: `{inputFile}`, `{workDir}` |
|
||||
| `{ "writeFile": { "path": "<путь>", "content": "<строка или объект>" } }` | Записать произвольный файл в `workDir` (объект сериализуется в JSON) |
|
||||
|
||||
## Эталоны (snapshots)
|
||||
|
||||
Эталон — директория `snapshots/<имя-кейса>/` внутри папки навыка. Содержит ожидаемый выход навыка после нормализации.
|
||||
|
||||
### Когда эталон обязателен
|
||||
|
||||
Всегда, кроме трёх случаев:
|
||||
|
||||
- `expectError` — проверяется факт ошибки, выхода нет;
|
||||
- `setup: "external:<path>"` — рабочая директория read-only, эталон физически не создать;
|
||||
- кейс объявил `noSnapshot` (см. ниже).
|
||||
|
||||
Во всех остальных случаях отсутствующий (или пустой) эталон — **падение** с подсказкой, что делать.
|
||||
|
||||
### `noSnapshot` — когда эталон не нужен
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Валидатор: ссылочный тип разрешается",
|
||||
"noSnapshot": "meta-validate только читает и печатает — эталон зафиксировал бы выход preRun, а не проверяемого навыка; проверяется stdout",
|
||||
"expect": { "stdoutContains": "16. Reference types:" }
|
||||
}
|
||||
```
|
||||
|
||||
Типичный случай — навык ничего не пишет в рабочую директорию (info/validate): эталон зафиксировал бы
|
||||
выход `preRun`, а не проверяемого навыка, и дублировал бы эталоны того навыка.
|
||||
|
||||
**Причина обязательна** — непустая строка; `true` не принимается и валит кейс. Смысл в том, что
|
||||
отключение сверки должно стоить автору формулировки, а ревьюеру быть видно в diff'е: проверить
|
||||
осмысленность причины рантайм не может.
|
||||
|
||||
Если у кейса стоит `noSnapshot`, но каталог эталона существует — тоже падение: такой эталон
|
||||
не сверяется и создаёт ложное впечатление покрытия. Удалите каталог либо снимите `noSnapshot`.
|
||||
|
||||
### Создание / обновление эталонов
|
||||
|
||||
```bash
|
||||
node tests/skills/runner.mjs --update-snapshots # все кейсы
|
||||
node tests/skills/runner.mjs cases/meta-compile/enum --update-snapshots # один кейс — предпочтительно
|
||||
node tests/skills/runner.mjs cases/meta-compile --update-snapshots # один навык
|
||||
node tests/skills/runner.mjs cases/meta-compile/enum --update-snapshots # один кейс
|
||||
node tests/skills/runner.mjs --update-snapshots # все кейсы
|
||||
```
|
||||
|
||||
> Прогон по навыку/сюите **перезаписывает эталоны всех** кейсов сразу: если побочно поехал вывод
|
||||
> соседнего кейса, его эталон обновится вместе с целевым и непреднамеренная регрессия замаскируется.
|
||||
> Поэтому по умолчанию — точечно по кейсу, а после массового пересъёма обязательно проверяйте
|
||||
> `git diff` по `snapshots/`: каждая ± строка должна быть ожидаемой.
|
||||
>
|
||||
> Массовый пересъём легитимен, когда изменение вывода и правда затрагивает многих — например, правка
|
||||
> `meta-compile` меняет фикстуры ~20 навыков, чьи кейсы строятся его `preRun`-прогоном.
|
||||
>
|
||||
> Кейсы с `noSnapshot` пропускаются — эталон им не создаётся.
|
||||
|
||||
### Когда обновлять
|
||||
|
||||
- После **намеренного** изменения логики навыка (новый выход — новый эталон)
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Configuration uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-002</xr:ClassId>
|
||||
<xr:ObjectId>UUID-003</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-004</xr:ClassId>
|
||||
<xr:ObjectId>UUID-005</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-006</xr:ClassId>
|
||||
<xr:ObjectId>UUID-007</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-008</xr:ClassId>
|
||||
<xr:ObjectId>UUID-009</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-010</xr:ClassId>
|
||||
<xr:ObjectId>UUID-011</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-012</xr:ClassId>
|
||||
<xr:ObjectId>UUID-013</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-014</xr:ClassId>
|
||||
<xr:ObjectId>UUID-015</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>КрлфКонф</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>КрлфКонф</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment />
|
||||
<NamePrefix />
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_27</ConfigurationExtensionCompatibilityMode>
|
||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ScriptVariant>Russian</ScriptVariant>
|
||||
<DefaultRoles />
|
||||
<Vendor></Vendor>
|
||||
<Version>1.0.0.2</Version>
|
||||
<UpdateCatalogAddress />
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
|
||||
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
|
||||
<AdditionalFullTextSearchDictionaries />
|
||||
<CommonSettingsStorage />
|
||||
<ReportsUserSettingsStorage />
|
||||
<ReportsVariantsStorage />
|
||||
<FormDataSettingsStorage />
|
||||
<DynamicListsUserSettingsStorage />
|
||||
<URLExternalDataStorage />
|
||||
<Content />
|
||||
<DefaultReportForm />
|
||||
<DefaultReportVariantForm />
|
||||
<DefaultReportSettingsForm />
|
||||
<DefaultReportAppearanceTemplate />
|
||||
<DefaultDynamicListSettingsForm />
|
||||
<DefaultSearchForm />
|
||||
<DefaultDataHistoryChangeHistoryForm />
|
||||
<DefaultDataHistoryVersionDataForm />
|
||||
<DefaultDataHistoryVersionDifferencesForm />
|
||||
<DefaultCollaborationSystemUsersChoiceForm />
|
||||
<RequiredMobileApplicationPermissions />
|
||||
<UsedMobileApplicationFunctionalities>
|
||||
<app:functionality>
|
||||
<app:functionality>Biometrics</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Location</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundLocation</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BluetoothPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>WiFiPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Contacts</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Calendars</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PushNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>LocalNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InAppPurchases</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PersonalComputerFileExchange</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Ads</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NumberDialing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AutoSendSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ReceiveSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SMSLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Camera</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Microphone</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>MusicLibrary</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PictureAndVideoLibraries</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InstallPackages</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>OSBackup</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ApplicationUsageStatistics</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BarcodeScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioRecording</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllFilesAccess</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Videoconferences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NFC</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>DocumentScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SpeechToText</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Geofences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>IncomingShareRequests</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
</UsedMobileApplicationFunctionalities>
|
||||
<StandaloneConfigurationRestrictionRoles />
|
||||
<MobileApplicationURLs />
|
||||
<AllowedIncomingShareRequestTypes />
|
||||
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
|
||||
<DefaultInterface />
|
||||
<DefaultStyle />
|
||||
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
||||
<BriefInformation />
|
||||
<DetailedInformation />
|
||||
<Copyright />
|
||||
<VendorInformationAddress />
|
||||
<ConfigurationInformationAddress />
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
||||
<ModalityUseMode>DontUse</ModalityUseMode>
|
||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
<CompatibilityMode>Version8_3_27</CompatibilityMode>
|
||||
<DefaultConstantsForm />
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Language>Русский</Language>
|
||||
</ChildObjects>
|
||||
</Configuration>
|
||||
</MetaDataObject>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
|
||||
<top>
|
||||
<panel id="UUID-001">
|
||||
<uuid>UUID-002</uuid>
|
||||
</panel>
|
||||
</top>
|
||||
<left>
|
||||
<panel id="UUID-003">
|
||||
<uuid>UUID-004</uuid>
|
||||
</panel>
|
||||
</left>
|
||||
<panelDef id="UUID-004"/>
|
||||
<panelDef id="UUID-005"/>
|
||||
<panelDef id="UUID-006"/>
|
||||
<panelDef id="UUID-002"/>
|
||||
<panelDef id="UUID-007"/>
|
||||
</ClientApplicationInterface>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Language uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>Русский</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Русский</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<LanguageCode>ru</LanguageCode>
|
||||
</Properties>
|
||||
</Language>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "form-compile затем form-add не дублирует регистрацию формы в ChildObjects",
|
||||
"setup": "none",
|
||||
"preRun": [
|
||||
{
|
||||
"script": "epf-init/scripts/init",
|
||||
"args": { "-Name": "КонсольКода", "-SrcDir": "{workDir}" }
|
||||
},
|
||||
{
|
||||
"script": "form-compile/scripts/form-compile",
|
||||
"input": {
|
||||
"title": "Форма",
|
||||
"attributes": [{ "name": "Объект", "type": "ExternalDataProcessorObject.КонсольКода", "main": true }]
|
||||
},
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputPath": "{workDir}/КонсольКода/Forms/Форма/Ext/Form.xml" }
|
||||
}
|
||||
],
|
||||
"params": { "objectPath": "КонсольКода.xml", "formName": "Форма" },
|
||||
"expect": { "stdoutContains": "Already registered" },
|
||||
"validatePath": "КонсольКода/Forms/Форма"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<ExternalDataProcessor uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-002</xr:ClassId>
|
||||
<xr:ObjectId>UUID-003</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:GeneratedType name="ExternalDataProcessorObject.КонсольКода" category="Object">
|
||||
<xr:TypeId>UUID-004</xr:TypeId>
|
||||
<xr:ValueId>UUID-005</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>КонсольКода</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>КонсольКода</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment />
|
||||
<DefaultForm>ExternalDataProcessor.КонсольКода.Form.Форма</DefaultForm>
|
||||
<AuxiliaryForm />
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Form>Форма</Form>
|
||||
</ChildObjects>
|
||||
</ExternalDataProcessor>
|
||||
</MetaDataObject>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#Область ОписаниеПеременных
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ПрограммныйИнтерфейс
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область СлужебныеПроцедурыИФункции
|
||||
|
||||
#КонецОбласти
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Form uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>Форма</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Форма</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<FormType>Managed</FormType>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ExtendedPresentation/>
|
||||
</Properties>
|
||||
</Form>
|
||||
</MetaDataObject>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcssch="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Title>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Форма</v8:content>
|
||||
</v8:item>
|
||||
</Title>
|
||||
<AutoTitle>false</AutoTitle>
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1"/>
|
||||
<Attributes>
|
||||
<Attribute name="Объект" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:ExternalDataProcessorObject.КонсольКода</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#Область ОбработчикиСобытийФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиСобытийЭлементовФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиКомандФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиОповещений
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область СлужебныеПроцедурыИФункции
|
||||
|
||||
#КонецОбласти
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "InputByString / DataLockFields / BasedOn каталога",
|
||||
"name": "InputByString / DataLockFields / BasedOn каталога (+нормализация MDObjectRef: CatalogRef./рус. → Catalog.)",
|
||||
"input": {
|
||||
"type": "Catalog",
|
||||
"name": "ДоговорыКонтрагентов",
|
||||
"owners": ["Catalog.Контрагенты"],
|
||||
"owners": ["CatalogRef.Контрагенты"],
|
||||
"codeLength": 11,
|
||||
"descriptionLength": 100,
|
||||
"inputByString": ["Код", "Наименование", "НомерДоговора"],
|
||||
"dataLockFields": ["Организация", "Контрагент", "Владелец"],
|
||||
"basedOn": ["Catalog.Контрагенты", "Document.ЗаказПоставщику"],
|
||||
"basedOn": ["Справочник.Контрагенты", "DocumentRef.ЗаказПоставщику"],
|
||||
"attributes": [
|
||||
"Организация: CatalogRef.Организации",
|
||||
"Контрагент: CatalogRef.Контрагенты",
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "Формат 2.20 при режиме совместимости 8_3_24 → LineNumberLength 5 (дефолт из режима, не из формата)",
|
||||
"setup": "empty-config-220-compat24",
|
||||
"input": {
|
||||
"type": "Catalog",
|
||||
"name": "СоСтроками",
|
||||
"tabularSections": {
|
||||
"Строки": ["Значение: String(50)"],
|
||||
"СтрокиДлинные": { "attributes": ["Значение: String(50)"], "lineNumberLength": 9 }
|
||||
}
|
||||
},
|
||||
"validatePath": "Catalogs/СоСтроками",
|
||||
"expect": {
|
||||
"files": ["Catalogs/СоСтроками.xml"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "Формат 2.20 (8.3.27): TypeReductionMode + LineNumberLength (режим 8_3_27 → 9)",
|
||||
"setup": "empty-config-220",
|
||||
"preRun": [
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": { "type": "Catalog", "name": "Валюты" },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
}
|
||||
],
|
||||
"input": [
|
||||
{
|
||||
"type": "Catalog",
|
||||
"name": "Договоры",
|
||||
"owners": ["Catalog.Валюты"],
|
||||
"standardAttributes": { "Description": { "fillChecking": "ShowError" } },
|
||||
"tabularSections": { "Условия": ["Условие: String(100)"] }
|
||||
},
|
||||
{
|
||||
"type": "InformationRegister",
|
||||
"name": "КурсыВалют",
|
||||
"periodicity": "Day",
|
||||
"dimensions": ["Валюта: CatalogRef.Валюты | master, mainFilter"],
|
||||
"resources": ["Курс: Number(15,4)"]
|
||||
}
|
||||
],
|
||||
"validatePath": "Catalogs/Договоры",
|
||||
"expect": {
|
||||
"files": ["Catalogs/Договоры.xml", "InformationRegisters/КурсыВалют.xml"]
|
||||
}
|
||||
}
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
|
||||
<Catalog uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="CatalogObject.СоСтроками" category="Object">
|
||||
<xr:TypeId>UUID-002</xr:TypeId>
|
||||
<xr:ValueId>UUID-003</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogRef.СоСтроками" category="Ref">
|
||||
<xr:TypeId>UUID-004</xr:TypeId>
|
||||
<xr:ValueId>UUID-005</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogSelection.СоСтроками" category="Selection">
|
||||
<xr:TypeId>UUID-006</xr:TypeId>
|
||||
<xr:ValueId>UUID-007</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogList.СоСтроками" category="List">
|
||||
<xr:TypeId>UUID-008</xr:TypeId>
|
||||
<xr:ValueId>UUID-009</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogManager.СоСтроками" category="Manager">
|
||||
<xr:TypeId>UUID-010</xr:TypeId>
|
||||
<xr:ValueId>UUID-011</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>СоСтроками</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Со строками</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<Hierarchical>false</Hierarchical>
|
||||
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
|
||||
<LimitLevelCount>false</LimitLevelCount>
|
||||
<LevelCount>2</LevelCount>
|
||||
<FoldersOnTop>true</FoldersOnTop>
|
||||
<UseStandardCommands>true</UseStandardCommands>
|
||||
<Owners/>
|
||||
<SubordinationUse>ToItems</SubordinationUse>
|
||||
<CodeLength>9</CodeLength>
|
||||
<DescriptionLength>25</DescriptionLength>
|
||||
<CodeType>String</CodeType>
|
||||
<CodeAllowedLength>Variable</CodeAllowedLength>
|
||||
<CodeSeries>WholeCatalog</CodeSeries>
|
||||
<CheckUnique>false</CheckUnique>
|
||||
<Autonumbering>true</Autonumbering>
|
||||
<DefaultPresentation>AsDescription</DefaultPresentation>
|
||||
<Characteristics/>
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.СоСтроками.StandardAttribute.Description</xr:Field>
|
||||
<xr:Field>Catalog.СоСтроками.StandardAttribute.Code</xr:Field>
|
||||
</InputByString>
|
||||
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
|
||||
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
|
||||
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
|
||||
<DefaultObjectForm/>
|
||||
<DefaultFolderForm/>
|
||||
<DefaultListForm/>
|
||||
<DefaultChoiceForm/>
|
||||
<DefaultFolderChoiceForm/>
|
||||
<AuxiliaryObjectForm/>
|
||||
<AuxiliaryFolderForm/>
|
||||
<AuxiliaryListForm/>
|
||||
<AuxiliaryChoiceForm/>
|
||||
<AuxiliaryFolderChoiceForm/>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<BasedOn/>
|
||||
<DataLockFields/>
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<ObjectPresentation/>
|
||||
<ExtendedObjectPresentation/>
|
||||
<ListPresentation/>
|
||||
<ExtendedListPresentation/>
|
||||
<Explanation/>
|
||||
<CreateOnInput>Use</CreateOnInput>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<DataHistory>DontUse</DataHistory>
|
||||
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
|
||||
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<TabularSection uuid="UUID-012">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="CatalogTabularSection.СоСтроками.Строки" category="TabularSection">
|
||||
<xr:TypeId>UUID-013</xr:TypeId>
|
||||
<xr:ValueId>UUID-014</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogTabularSectionRow.СоСтроками.Строки" category="TabularSectionRow">
|
||||
<xr:TypeId>UUID-015</xr:TypeId>
|
||||
<xr:ValueId>UUID-016</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>Строки</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Строки</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<ToolTip/>
|
||||
<FillChecking>DontCheck</FillChecking>
|
||||
<StandardAttributes>
|
||||
<xr:StandardAttribute name="LineNumber">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
</StandardAttributes>
|
||||
<Use>ForItem</Use>
|
||||
<LineNumberLength>5</LineNumberLength>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Attribute uuid="UUID-017">
|
||||
<Properties>
|
||||
<Name>Значение</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Значение</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<Type>
|
||||
<v8:Type>xs:string</v8:Type>
|
||||
<v8:StringQualifiers>
|
||||
<v8:Length>50</v8:Length>
|
||||
<v8:AllowedLength>Variable</v8:AllowedLength>
|
||||
</v8:StringQualifiers>
|
||||
</Type>
|
||||
<PasswordMode>false</PasswordMode>
|
||||
<Format/>
|
||||
<EditFormat/>
|
||||
<ToolTip/>
|
||||
<MarkNegatives>false</MarkNegatives>
|
||||
<Mask/>
|
||||
<MultiLine>false</MultiLine>
|
||||
<ExtendedEdit>false</ExtendedEdit>
|
||||
<MinValue xsi:nil="true"/>
|
||||
<MaxValue xsi:nil="true"/>
|
||||
<FillChecking>DontCheck</FillChecking>
|
||||
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
|
||||
<ChoiceParameterLinks/>
|
||||
<ChoiceParameters/>
|
||||
<QuickChoice>Auto</QuickChoice>
|
||||
<CreateOnInput>Auto</CreateOnInput>
|
||||
<ChoiceForm/>
|
||||
<LinkByType/>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<Indexing>DontIndex</Indexing>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<DataHistory>Use</DataHistory>
|
||||
</Properties>
|
||||
</Attribute>
|
||||
</ChildObjects>
|
||||
</TabularSection>
|
||||
<TabularSection uuid="UUID-018">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="CatalogTabularSection.СоСтроками.СтрокиДлинные" category="TabularSection">
|
||||
<xr:TypeId>UUID-019</xr:TypeId>
|
||||
<xr:ValueId>UUID-020</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogTabularSectionRow.СоСтроками.СтрокиДлинные" category="TabularSectionRow">
|
||||
<xr:TypeId>UUID-021</xr:TypeId>
|
||||
<xr:ValueId>UUID-022</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>СтрокиДлинные</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Строки длинные</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<ToolTip/>
|
||||
<FillChecking>DontCheck</FillChecking>
|
||||
<StandardAttributes>
|
||||
<xr:StandardAttribute name="LineNumber">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
</StandardAttributes>
|
||||
<Use>ForItem</Use>
|
||||
<LineNumberLength>9</LineNumberLength>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Attribute uuid="UUID-023">
|
||||
<Properties>
|
||||
<Name>Значение</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Значение</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<Type>
|
||||
<v8:Type>xs:string</v8:Type>
|
||||
<v8:StringQualifiers>
|
||||
<v8:Length>50</v8:Length>
|
||||
<v8:AllowedLength>Variable</v8:AllowedLength>
|
||||
</v8:StringQualifiers>
|
||||
</Type>
|
||||
<PasswordMode>false</PasswordMode>
|
||||
<Format/>
|
||||
<EditFormat/>
|
||||
<ToolTip/>
|
||||
<MarkNegatives>false</MarkNegatives>
|
||||
<Mask/>
|
||||
<MultiLine>false</MultiLine>
|
||||
<ExtendedEdit>false</ExtendedEdit>
|
||||
<MinValue xsi:nil="true"/>
|
||||
<MaxValue xsi:nil="true"/>
|
||||
<FillChecking>DontCheck</FillChecking>
|
||||
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
|
||||
<ChoiceParameterLinks/>
|
||||
<ChoiceParameters/>
|
||||
<QuickChoice>Auto</QuickChoice>
|
||||
<CreateOnInput>Auto</CreateOnInput>
|
||||
<ChoiceForm/>
|
||||
<LinkByType/>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<Indexing>DontIndex</Indexing>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<DataHistory>Use</DataHistory>
|
||||
</Properties>
|
||||
</Attribute>
|
||||
</ChildObjects>
|
||||
</TabularSection>
|
||||
</ChildObjects>
|
||||
</Catalog>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,252 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
|
||||
<Configuration uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-002</xr:ClassId>
|
||||
<xr:ObjectId>UUID-003</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-004</xr:ClassId>
|
||||
<xr:ObjectId>UUID-005</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-006</xr:ClassId>
|
||||
<xr:ObjectId>UUID-007</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-008</xr:ClassId>
|
||||
<xr:ObjectId>UUID-009</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-010</xr:ClassId>
|
||||
<xr:ObjectId>UUID-011</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-012</xr:ClassId>
|
||||
<xr:ObjectId>UUID-013</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-014</xr:ClassId>
|
||||
<xr:ObjectId>UUID-015</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>TestConfig</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>TestConfig</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment />
|
||||
<NamePrefix />
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
|
||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ScriptVariant>Russian</ScriptVariant>
|
||||
<DefaultRoles />
|
||||
<Vendor></Vendor>
|
||||
<Version></Version>
|
||||
<UpdateCatalogAddress />
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
|
||||
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
|
||||
<AdditionalFullTextSearchDictionaries />
|
||||
<CommonSettingsStorage />
|
||||
<ReportsUserSettingsStorage />
|
||||
<ReportsVariantsStorage />
|
||||
<FormDataSettingsStorage />
|
||||
<DynamicListsUserSettingsStorage />
|
||||
<URLExternalDataStorage />
|
||||
<Content />
|
||||
<DefaultReportForm />
|
||||
<DefaultReportVariantForm />
|
||||
<DefaultReportSettingsForm />
|
||||
<DefaultReportAppearanceTemplate />
|
||||
<DefaultDynamicListSettingsForm />
|
||||
<DefaultSearchForm />
|
||||
<DefaultDataHistoryChangeHistoryForm />
|
||||
<DefaultDataHistoryVersionDataForm />
|
||||
<DefaultDataHistoryVersionDifferencesForm />
|
||||
<DefaultCollaborationSystemUsersChoiceForm />
|
||||
<RequiredMobileApplicationPermissions />
|
||||
<UsedMobileApplicationFunctionalities>
|
||||
<app:functionality>
|
||||
<app:functionality>Biometrics</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Location</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundLocation</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BluetoothPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>WiFiPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Contacts</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Calendars</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PushNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>LocalNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InAppPurchases</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PersonalComputerFileExchange</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Ads</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NumberDialing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AutoSendSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ReceiveSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SMSLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Camera</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Microphone</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>MusicLibrary</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PictureAndVideoLibraries</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InstallPackages</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>OSBackup</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ApplicationUsageStatistics</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BarcodeScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioRecording</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllFilesAccess</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Videoconferences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NFC</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>DocumentScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SpeechToText</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Geofences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>IncomingShareRequests</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
</UsedMobileApplicationFunctionalities>
|
||||
<StandaloneConfigurationRestrictionRoles />
|
||||
<MobileApplicationURLs />
|
||||
<AllowedIncomingShareRequestTypes />
|
||||
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
|
||||
<DefaultInterface />
|
||||
<DefaultStyle />
|
||||
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
||||
<BriefInformation />
|
||||
<DetailedInformation />
|
||||
<Copyright />
|
||||
<VendorInformationAddress />
|
||||
<ConfigurationInformationAddress />
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
||||
<ModalityUseMode>DontUse</ModalityUseMode>
|
||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
<CompatibilityMode>Version8_3_24</CompatibilityMode>
|
||||
<DefaultConstantsForm />
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Language>Русский</Language>
|
||||
<Catalog>СоСтроками</Catalog>
|
||||
</ChildObjects>
|
||||
</Configuration>
|
||||
</MetaDataObject>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
|
||||
<top>
|
||||
<panel id="UUID-001">
|
||||
<uuid>UUID-002</uuid>
|
||||
</panel>
|
||||
</top>
|
||||
<left>
|
||||
<panel id="UUID-003">
|
||||
<uuid>UUID-004</uuid>
|
||||
</panel>
|
||||
</left>
|
||||
<panelDef id="UUID-004"/>
|
||||
<panelDef id="UUID-005"/>
|
||||
<panelDef id="UUID-006"/>
|
||||
<panelDef id="UUID-002"/>
|
||||
<panelDef id="UUID-007"/>
|
||||
</ClientApplicationInterface>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
|
||||
<Language uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>Русский</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Русский</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<LanguageCode>ru</LanguageCode>
|
||||
</Properties>
|
||||
</Language>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
|
||||
<Catalog uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="CatalogObject.Валюты" category="Object">
|
||||
<xr:TypeId>UUID-002</xr:TypeId>
|
||||
<xr:ValueId>UUID-003</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogRef.Валюты" category="Ref">
|
||||
<xr:TypeId>UUID-004</xr:TypeId>
|
||||
<xr:ValueId>UUID-005</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogSelection.Валюты" category="Selection">
|
||||
<xr:TypeId>UUID-006</xr:TypeId>
|
||||
<xr:ValueId>UUID-007</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogList.Валюты" category="List">
|
||||
<xr:TypeId>UUID-008</xr:TypeId>
|
||||
<xr:ValueId>UUID-009</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogManager.Валюты" category="Manager">
|
||||
<xr:TypeId>UUID-010</xr:TypeId>
|
||||
<xr:ValueId>UUID-011</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>Валюты</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Валюты</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<Hierarchical>false</Hierarchical>
|
||||
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
|
||||
<LimitLevelCount>false</LimitLevelCount>
|
||||
<LevelCount>2</LevelCount>
|
||||
<FoldersOnTop>true</FoldersOnTop>
|
||||
<UseStandardCommands>true</UseStandardCommands>
|
||||
<Owners/>
|
||||
<SubordinationUse>ToItems</SubordinationUse>
|
||||
<CodeLength>9</CodeLength>
|
||||
<DescriptionLength>25</DescriptionLength>
|
||||
<CodeType>String</CodeType>
|
||||
<CodeAllowedLength>Variable</CodeAllowedLength>
|
||||
<CodeSeries>WholeCatalog</CodeSeries>
|
||||
<CheckUnique>false</CheckUnique>
|
||||
<Autonumbering>true</Autonumbering>
|
||||
<DefaultPresentation>AsDescription</DefaultPresentation>
|
||||
<Characteristics/>
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Валюты.StandardAttribute.Description</xr:Field>
|
||||
<xr:Field>Catalog.Валюты.StandardAttribute.Code</xr:Field>
|
||||
</InputByString>
|
||||
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
|
||||
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
|
||||
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
|
||||
<DefaultObjectForm/>
|
||||
<DefaultFolderForm/>
|
||||
<DefaultListForm/>
|
||||
<DefaultChoiceForm/>
|
||||
<DefaultFolderChoiceForm/>
|
||||
<AuxiliaryObjectForm/>
|
||||
<AuxiliaryFolderForm/>
|
||||
<AuxiliaryListForm/>
|
||||
<AuxiliaryChoiceForm/>
|
||||
<AuxiliaryFolderChoiceForm/>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<BasedOn/>
|
||||
<DataLockFields/>
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<ObjectPresentation/>
|
||||
<ExtendedObjectPresentation/>
|
||||
<ListPresentation/>
|
||||
<ExtendedListPresentation/>
|
||||
<Explanation/>
|
||||
<CreateOnInput>Use</CreateOnInput>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<DataHistory>DontUse</DataHistory>
|
||||
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
|
||||
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
|
||||
</Properties>
|
||||
<ChildObjects/>
|
||||
</Catalog>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,437 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
|
||||
<Catalog uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="CatalogObject.Договоры" category="Object">
|
||||
<xr:TypeId>UUID-002</xr:TypeId>
|
||||
<xr:ValueId>UUID-003</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogRef.Договоры" category="Ref">
|
||||
<xr:TypeId>UUID-004</xr:TypeId>
|
||||
<xr:ValueId>UUID-005</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogSelection.Договоры" category="Selection">
|
||||
<xr:TypeId>UUID-006</xr:TypeId>
|
||||
<xr:ValueId>UUID-007</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogList.Договоры" category="List">
|
||||
<xr:TypeId>UUID-008</xr:TypeId>
|
||||
<xr:ValueId>UUID-009</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogManager.Договоры" category="Manager">
|
||||
<xr:TypeId>UUID-010</xr:TypeId>
|
||||
<xr:ValueId>UUID-011</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>Договоры</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Договоры</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<Hierarchical>false</Hierarchical>
|
||||
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
|
||||
<LimitLevelCount>false</LimitLevelCount>
|
||||
<LevelCount>2</LevelCount>
|
||||
<FoldersOnTop>true</FoldersOnTop>
|
||||
<UseStandardCommands>true</UseStandardCommands>
|
||||
<Owners>
|
||||
<xr:Item xsi:type="xr:MDObjectRef">Catalog.Валюты</xr:Item>
|
||||
</Owners>
|
||||
<SubordinationUse>ToItems</SubordinationUse>
|
||||
<CodeLength>9</CodeLength>
|
||||
<DescriptionLength>25</DescriptionLength>
|
||||
<CodeType>String</CodeType>
|
||||
<CodeAllowedLength>Variable</CodeAllowedLength>
|
||||
<CodeSeries>WholeCatalog</CodeSeries>
|
||||
<CheckUnique>false</CheckUnique>
|
||||
<Autonumbering>true</Autonumbering>
|
||||
<DefaultPresentation>AsDescription</DefaultPresentation>
|
||||
<StandardAttributes>
|
||||
<xr:StandardAttribute name="PredefinedDataName">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Predefined">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Ref">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="DeletionMark">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="IsFolder">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Owner">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>ShowError</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>true</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>Deny</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Parent">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>true</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Description">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>ShowError</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Code">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
</StandardAttributes>
|
||||
<Characteristics/>
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Договоры.StandardAttribute.Description</xr:Field>
|
||||
<xr:Field>Catalog.Договоры.StandardAttribute.Code</xr:Field>
|
||||
</InputByString>
|
||||
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
|
||||
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
|
||||
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
|
||||
<DefaultObjectForm/>
|
||||
<DefaultFolderForm/>
|
||||
<DefaultListForm/>
|
||||
<DefaultChoiceForm/>
|
||||
<DefaultFolderChoiceForm/>
|
||||
<AuxiliaryObjectForm/>
|
||||
<AuxiliaryFolderForm/>
|
||||
<AuxiliaryListForm/>
|
||||
<AuxiliaryChoiceForm/>
|
||||
<AuxiliaryFolderChoiceForm/>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<BasedOn/>
|
||||
<DataLockFields/>
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<ObjectPresentation/>
|
||||
<ExtendedObjectPresentation/>
|
||||
<ListPresentation/>
|
||||
<ExtendedListPresentation/>
|
||||
<Explanation/>
|
||||
<CreateOnInput>Use</CreateOnInput>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<DataHistory>DontUse</DataHistory>
|
||||
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
|
||||
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<TabularSection uuid="UUID-012">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="CatalogTabularSection.Договоры.Условия" category="TabularSection">
|
||||
<xr:TypeId>UUID-013</xr:TypeId>
|
||||
<xr:ValueId>UUID-014</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogTabularSectionRow.Договоры.Условия" category="TabularSectionRow">
|
||||
<xr:TypeId>UUID-015</xr:TypeId>
|
||||
<xr:ValueId>UUID-016</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>Условия</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Условия</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<ToolTip/>
|
||||
<FillChecking>DontCheck</FillChecking>
|
||||
<StandardAttributes>
|
||||
<xr:StandardAttribute name="LineNumber">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
</StandardAttributes>
|
||||
<Use>ForItem</Use>
|
||||
<LineNumberLength>9</LineNumberLength>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Attribute uuid="UUID-017">
|
||||
<Properties>
|
||||
<Name>Условие</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Условие</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<Type>
|
||||
<v8:Type>xs:string</v8:Type>
|
||||
<v8:StringQualifiers>
|
||||
<v8:Length>100</v8:Length>
|
||||
<v8:AllowedLength>Variable</v8:AllowedLength>
|
||||
</v8:StringQualifiers>
|
||||
</Type>
|
||||
<PasswordMode>false</PasswordMode>
|
||||
<Format/>
|
||||
<EditFormat/>
|
||||
<ToolTip/>
|
||||
<MarkNegatives>false</MarkNegatives>
|
||||
<Mask/>
|
||||
<MultiLine>false</MultiLine>
|
||||
<ExtendedEdit>false</ExtendedEdit>
|
||||
<MinValue xsi:nil="true"/>
|
||||
<MaxValue xsi:nil="true"/>
|
||||
<FillChecking>DontCheck</FillChecking>
|
||||
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
|
||||
<ChoiceParameterLinks/>
|
||||
<ChoiceParameters/>
|
||||
<QuickChoice>Auto</QuickChoice>
|
||||
<CreateOnInput>Auto</CreateOnInput>
|
||||
<ChoiceForm/>
|
||||
<LinkByType/>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<Indexing>DontIndex</Indexing>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<DataHistory>Use</DataHistory>
|
||||
</Properties>
|
||||
</Attribute>
|
||||
</ChildObjects>
|
||||
</TabularSection>
|
||||
</ChildObjects>
|
||||
</Catalog>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,254 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
|
||||
<Configuration uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-002</xr:ClassId>
|
||||
<xr:ObjectId>UUID-003</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-004</xr:ClassId>
|
||||
<xr:ObjectId>UUID-005</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-006</xr:ClassId>
|
||||
<xr:ObjectId>UUID-007</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-008</xr:ClassId>
|
||||
<xr:ObjectId>UUID-009</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-010</xr:ClassId>
|
||||
<xr:ObjectId>UUID-011</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-012</xr:ClassId>
|
||||
<xr:ObjectId>UUID-013</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-014</xr:ClassId>
|
||||
<xr:ObjectId>UUID-015</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>TestConfig</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>TestConfig</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment />
|
||||
<NamePrefix />
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_27</ConfigurationExtensionCompatibilityMode>
|
||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ScriptVariant>Russian</ScriptVariant>
|
||||
<DefaultRoles />
|
||||
<Vendor></Vendor>
|
||||
<Version></Version>
|
||||
<UpdateCatalogAddress />
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
|
||||
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
|
||||
<AdditionalFullTextSearchDictionaries />
|
||||
<CommonSettingsStorage />
|
||||
<ReportsUserSettingsStorage />
|
||||
<ReportsVariantsStorage />
|
||||
<FormDataSettingsStorage />
|
||||
<DynamicListsUserSettingsStorage />
|
||||
<URLExternalDataStorage />
|
||||
<Content />
|
||||
<DefaultReportForm />
|
||||
<DefaultReportVariantForm />
|
||||
<DefaultReportSettingsForm />
|
||||
<DefaultReportAppearanceTemplate />
|
||||
<DefaultDynamicListSettingsForm />
|
||||
<DefaultSearchForm />
|
||||
<DefaultDataHistoryChangeHistoryForm />
|
||||
<DefaultDataHistoryVersionDataForm />
|
||||
<DefaultDataHistoryVersionDifferencesForm />
|
||||
<DefaultCollaborationSystemUsersChoiceForm />
|
||||
<RequiredMobileApplicationPermissions />
|
||||
<UsedMobileApplicationFunctionalities>
|
||||
<app:functionality>
|
||||
<app:functionality>Biometrics</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Location</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundLocation</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BluetoothPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>WiFiPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Contacts</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Calendars</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PushNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>LocalNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InAppPurchases</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PersonalComputerFileExchange</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Ads</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NumberDialing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AutoSendSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ReceiveSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SMSLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Camera</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Microphone</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>MusicLibrary</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PictureAndVideoLibraries</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InstallPackages</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>OSBackup</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ApplicationUsageStatistics</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BarcodeScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioRecording</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllFilesAccess</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Videoconferences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NFC</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>DocumentScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SpeechToText</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Geofences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>IncomingShareRequests</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
</UsedMobileApplicationFunctionalities>
|
||||
<StandaloneConfigurationRestrictionRoles />
|
||||
<MobileApplicationURLs />
|
||||
<AllowedIncomingShareRequestTypes />
|
||||
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
|
||||
<DefaultInterface />
|
||||
<DefaultStyle />
|
||||
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
||||
<BriefInformation />
|
||||
<DetailedInformation />
|
||||
<Copyright />
|
||||
<VendorInformationAddress />
|
||||
<ConfigurationInformationAddress />
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
||||
<ModalityUseMode>DontUse</ModalityUseMode>
|
||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
<CompatibilityMode>Version8_3_27</CompatibilityMode>
|
||||
<DefaultConstantsForm />
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Language>Русский</Language>
|
||||
<Catalog>Валюты</Catalog>
|
||||
<Catalog>Договоры</Catalog>
|
||||
<InformationRegister>КурсыВалют</InformationRegister>
|
||||
</ChildObjects>
|
||||
</Configuration>
|
||||
</MetaDataObject>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
|
||||
<top>
|
||||
<panel id="UUID-001">
|
||||
<uuid>UUID-002</uuid>
|
||||
</panel>
|
||||
</top>
|
||||
<left>
|
||||
<panel id="UUID-003">
|
||||
<uuid>UUID-004</uuid>
|
||||
</panel>
|
||||
</left>
|
||||
<panelDef id="UUID-004"/>
|
||||
<panelDef id="UUID-005"/>
|
||||
<panelDef id="UUID-006"/>
|
||||
<panelDef id="UUID-002"/>
|
||||
<panelDef id="UUID-007"/>
|
||||
</ClientApplicationInterface>
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
|
||||
<InformationRegister uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="InformationRegisterRecord.КурсыВалют" category="Record">
|
||||
<xr:TypeId>UUID-002</xr:TypeId>
|
||||
<xr:ValueId>UUID-003</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="InformationRegisterManager.КурсыВалют" category="Manager">
|
||||
<xr:TypeId>UUID-004</xr:TypeId>
|
||||
<xr:ValueId>UUID-005</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="InformationRegisterSelection.КурсыВалют" category="Selection">
|
||||
<xr:TypeId>UUID-006</xr:TypeId>
|
||||
<xr:ValueId>UUID-007</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="InformationRegisterList.КурсыВалют" category="List">
|
||||
<xr:TypeId>UUID-008</xr:TypeId>
|
||||
<xr:ValueId>UUID-009</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="InformationRegisterRecordSet.КурсыВалют" category="RecordSet">
|
||||
<xr:TypeId>UUID-010</xr:TypeId>
|
||||
<xr:ValueId>UUID-011</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="InformationRegisterRecordKey.КурсыВалют" category="RecordKey">
|
||||
<xr:TypeId>UUID-012</xr:TypeId>
|
||||
<xr:ValueId>UUID-013</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="InformationRegisterRecordManager.КурсыВалют" category="RecordManager">
|
||||
<xr:TypeId>UUID-014</xr:TypeId>
|
||||
<xr:ValueId>UUID-015</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>КурсыВалют</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Курсы валют</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<UseStandardCommands>true</UseStandardCommands>
|
||||
<EditType>InDialog</EditType>
|
||||
<DefaultRecordForm/>
|
||||
<DefaultListForm/>
|
||||
<AuxiliaryRecordForm/>
|
||||
<AuxiliaryListForm/>
|
||||
<StandardAttributes>
|
||||
<xr:StandardAttribute name="Active">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="LineNumber">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Recorder">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
<xr:StandardAttribute name="Period">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
</StandardAttributes>
|
||||
<InformationRegisterPeriodicity>Day</InformationRegisterPeriodicity>
|
||||
<WriteMode>Independent</WriteMode>
|
||||
<MainFilterOnPeriod>false</MainFilterOnPeriod>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<EnableTotalsSliceFirst>false</EnableTotalsSliceFirst>
|
||||
<EnableTotalsSliceLast>false</EnableTotalsSliceLast>
|
||||
<RecordPresentation/>
|
||||
<ExtendedRecordPresentation/>
|
||||
<ListPresentation/>
|
||||
<ExtendedListPresentation/>
|
||||
<Explanation/>
|
||||
<DataHistory>DontUse</DataHistory>
|
||||
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
|
||||
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Resource uuid="UUID-016">
|
||||
<Properties>
|
||||
<Name>Курс</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Курс</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<Type>
|
||||
<v8:Type>xs:decimal</v8:Type>
|
||||
<v8:NumberQualifiers>
|
||||
<v8:Digits>15</v8:Digits>
|
||||
<v8:FractionDigits>4</v8:FractionDigits>
|
||||
<v8:AllowedSign>Any</v8:AllowedSign>
|
||||
</v8:NumberQualifiers>
|
||||
</Type>
|
||||
<PasswordMode>false</PasswordMode>
|
||||
<Format/>
|
||||
<EditFormat/>
|
||||
<ToolTip/>
|
||||
<MarkNegatives>false</MarkNegatives>
|
||||
<Mask/>
|
||||
<MultiLine>false</MultiLine>
|
||||
<ExtendedEdit>false</ExtendedEdit>
|
||||
<MinValue xsi:nil="true"/>
|
||||
<MaxValue xsi:nil="true"/>
|
||||
<FillFromFillingValue>false</FillFromFillingValue>
|
||||
<FillValue xsi:type="xs:decimal">0</FillValue>
|
||||
<FillChecking>DontCheck</FillChecking>
|
||||
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
|
||||
<ChoiceParameterLinks/>
|
||||
<ChoiceParameters/>
|
||||
<QuickChoice>Auto</QuickChoice>
|
||||
<CreateOnInput>Auto</CreateOnInput>
|
||||
<ChoiceForm/>
|
||||
<LinkByType/>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<Indexing>DontIndex</Indexing>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<DataHistory>Use</DataHistory>
|
||||
</Properties>
|
||||
</Resource>
|
||||
<Dimension uuid="UUID-017">
|
||||
<Properties>
|
||||
<Name>Валюта</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Валюта</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<Type>
|
||||
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Валюты</v8:Type>
|
||||
</Type>
|
||||
<PasswordMode>false</PasswordMode>
|
||||
<Format/>
|
||||
<EditFormat/>
|
||||
<ToolTip/>
|
||||
<MarkNegatives>false</MarkNegatives>
|
||||
<Mask/>
|
||||
<MultiLine>false</MultiLine>
|
||||
<ExtendedEdit>false</ExtendedEdit>
|
||||
<MinValue xsi:nil="true"/>
|
||||
<MaxValue xsi:nil="true"/>
|
||||
<FillFromFillingValue>true</FillFromFillingValue>
|
||||
<FillValue xsi:nil="true"/>
|
||||
<FillChecking>DontCheck</FillChecking>
|
||||
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
|
||||
<ChoiceParameterLinks/>
|
||||
<ChoiceParameters/>
|
||||
<QuickChoice>Auto</QuickChoice>
|
||||
<CreateOnInput>Auto</CreateOnInput>
|
||||
<ChoiceForm/>
|
||||
<LinkByType/>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<Master>true</Master>
|
||||
<MainFilter>true</MainFilter>
|
||||
<DenyIncompleteValues>false</DenyIncompleteValues>
|
||||
<Indexing>DontIndex</Indexing>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<DataHistory>Use</DataHistory>
|
||||
<TypeReductionMode>TransformValues</TypeReductionMode>
|
||||
</Properties>
|
||||
</Dimension>
|
||||
</ChildObjects>
|
||||
</InformationRegister>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
|
||||
<Language uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>Русский</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Русский</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<LanguageCode>ru</LanguageCode>
|
||||
</Properties>
|
||||
</Language>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "Нормализация MDObjectRef в Owners/BasedOn (CatalogRef./рус. запись → Catalog.)",
|
||||
"preRun": [
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": [
|
||||
{ "type": "Catalog", "name": "Контрагенты" },
|
||||
{ "type": "Catalog", "name": "Организации" },
|
||||
{ "type": "Document", "name": "ЗаказПоставщику" },
|
||||
{ "type": "Catalog", "name": "ДоговорыКонтрагентов", "owners": ["Catalog.Контрагенты"] }
|
||||
],
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
}
|
||||
],
|
||||
"params": { "objectPath": "Catalogs/ДоговорыКонтрагентов" },
|
||||
"input": {
|
||||
"modify": {
|
||||
"properties": {
|
||||
"Owners": ["CatalogRef.Контрагенты", "СправочникСсылка.Организации"],
|
||||
"BasedOn": ["Справочник.Контрагенты", "DocumentRef.ЗаказПоставщику"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Catalog uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="CatalogObject.ДоговорыКонтрагентов" category="Object">
|
||||
<xr:TypeId>UUID-002</xr:TypeId>
|
||||
<xr:ValueId>UUID-003</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogRef.ДоговорыКонтрагентов" category="Ref">
|
||||
<xr:TypeId>UUID-004</xr:TypeId>
|
||||
<xr:ValueId>UUID-005</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogSelection.ДоговорыКонтрагентов" category="Selection">
|
||||
<xr:TypeId>UUID-006</xr:TypeId>
|
||||
<xr:ValueId>UUID-007</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogList.ДоговорыКонтрагентов" category="List">
|
||||
<xr:TypeId>UUID-008</xr:TypeId>
|
||||
<xr:ValueId>UUID-009</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogManager.ДоговорыКонтрагентов" category="Manager">
|
||||
<xr:TypeId>UUID-010</xr:TypeId>
|
||||
<xr:ValueId>UUID-011</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>ДоговорыКонтрагентов</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Договоры контрагентов</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment />
|
||||
<Hierarchical>false</Hierarchical>
|
||||
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
|
||||
<LimitLevelCount>false</LimitLevelCount>
|
||||
<LevelCount>2</LevelCount>
|
||||
<FoldersOnTop>true</FoldersOnTop>
|
||||
<UseStandardCommands>true</UseStandardCommands>
|
||||
<Owners>
|
||||
<xr:Item xsi:type="xr:MDObjectRef">Catalog.Контрагенты</xr:Item>
|
||||
<xr:Item xsi:type="xr:MDObjectRef">Catalog.Организации</xr:Item>
|
||||
</Owners>
|
||||
<SubordinationUse>ToItems</SubordinationUse>
|
||||
<CodeLength>9</CodeLength>
|
||||
<DescriptionLength>25</DescriptionLength>
|
||||
<CodeType>String</CodeType>
|
||||
<CodeAllowedLength>Variable</CodeAllowedLength>
|
||||
<CodeSeries>WholeCatalog</CodeSeries>
|
||||
<CheckUnique>false</CheckUnique>
|
||||
<Autonumbering>true</Autonumbering>
|
||||
<DefaultPresentation>AsDescription</DefaultPresentation>
|
||||
<Characteristics />
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.ДоговорыКонтрагентов.StandardAttribute.Description</xr:Field>
|
||||
<xr:Field>Catalog.ДоговорыКонтрагентов.StandardAttribute.Code</xr:Field>
|
||||
</InputByString>
|
||||
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
|
||||
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
|
||||
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
|
||||
<DefaultObjectForm />
|
||||
<DefaultFolderForm />
|
||||
<DefaultListForm />
|
||||
<DefaultChoiceForm />
|
||||
<DefaultFolderChoiceForm />
|
||||
<AuxiliaryObjectForm />
|
||||
<AuxiliaryFolderForm />
|
||||
<AuxiliaryListForm />
|
||||
<AuxiliaryChoiceForm />
|
||||
<AuxiliaryFolderChoiceForm />
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<BasedOn>
|
||||
<xr:Item xsi:type="xr:MDObjectRef">Catalog.Контрагенты</xr:Item>
|
||||
<xr:Item xsi:type="xr:MDObjectRef">Document.ЗаказПоставщику</xr:Item>
|
||||
</BasedOn>
|
||||
<DataLockFields />
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<ObjectPresentation />
|
||||
<ExtendedObjectPresentation />
|
||||
<ListPresentation />
|
||||
<ExtendedListPresentation />
|
||||
<Explanation />
|
||||
<CreateOnInput>Use</CreateOnInput>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<DataHistory>DontUse</DataHistory>
|
||||
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
|
||||
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
|
||||
</Properties>
|
||||
<ChildObjects />
|
||||
</Catalog>
|
||||
</MetaDataObject>
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Catalog uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="CatalogObject.Контрагенты" category="Object">
|
||||
<xr:TypeId>UUID-002</xr:TypeId>
|
||||
<xr:ValueId>UUID-003</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogRef.Контрагенты" category="Ref">
|
||||
<xr:TypeId>UUID-004</xr:TypeId>
|
||||
<xr:ValueId>UUID-005</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogSelection.Контрагенты" category="Selection">
|
||||
<xr:TypeId>UUID-006</xr:TypeId>
|
||||
<xr:ValueId>UUID-007</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogList.Контрагенты" category="List">
|
||||
<xr:TypeId>UUID-008</xr:TypeId>
|
||||
<xr:ValueId>UUID-009</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogManager.Контрагенты" category="Manager">
|
||||
<xr:TypeId>UUID-010</xr:TypeId>
|
||||
<xr:ValueId>UUID-011</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>Контрагенты</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Контрагенты</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<Hierarchical>false</Hierarchical>
|
||||
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
|
||||
<LimitLevelCount>false</LimitLevelCount>
|
||||
<LevelCount>2</LevelCount>
|
||||
<FoldersOnTop>true</FoldersOnTop>
|
||||
<UseStandardCommands>true</UseStandardCommands>
|
||||
<Owners/>
|
||||
<SubordinationUse>ToItems</SubordinationUse>
|
||||
<CodeLength>9</CodeLength>
|
||||
<DescriptionLength>25</DescriptionLength>
|
||||
<CodeType>String</CodeType>
|
||||
<CodeAllowedLength>Variable</CodeAllowedLength>
|
||||
<CodeSeries>WholeCatalog</CodeSeries>
|
||||
<CheckUnique>false</CheckUnique>
|
||||
<Autonumbering>true</Autonumbering>
|
||||
<DefaultPresentation>AsDescription</DefaultPresentation>
|
||||
<Characteristics/>
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Контрагенты.StandardAttribute.Description</xr:Field>
|
||||
<xr:Field>Catalog.Контрагенты.StandardAttribute.Code</xr:Field>
|
||||
</InputByString>
|
||||
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
|
||||
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
|
||||
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
|
||||
<DefaultObjectForm/>
|
||||
<DefaultFolderForm/>
|
||||
<DefaultListForm/>
|
||||
<DefaultChoiceForm/>
|
||||
<DefaultFolderChoiceForm/>
|
||||
<AuxiliaryObjectForm/>
|
||||
<AuxiliaryFolderForm/>
|
||||
<AuxiliaryListForm/>
|
||||
<AuxiliaryChoiceForm/>
|
||||
<AuxiliaryFolderChoiceForm/>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<BasedOn/>
|
||||
<DataLockFields/>
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<ObjectPresentation/>
|
||||
<ExtendedObjectPresentation/>
|
||||
<ListPresentation/>
|
||||
<ExtendedListPresentation/>
|
||||
<Explanation/>
|
||||
<CreateOnInput>Use</CreateOnInput>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<DataHistory>DontUse</DataHistory>
|
||||
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
|
||||
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
|
||||
</Properties>
|
||||
<ChildObjects/>
|
||||
</Catalog>
|
||||
</MetaDataObject>
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Catalog uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="CatalogObject.Организации" category="Object">
|
||||
<xr:TypeId>UUID-002</xr:TypeId>
|
||||
<xr:ValueId>UUID-003</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogRef.Организации" category="Ref">
|
||||
<xr:TypeId>UUID-004</xr:TypeId>
|
||||
<xr:ValueId>UUID-005</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogSelection.Организации" category="Selection">
|
||||
<xr:TypeId>UUID-006</xr:TypeId>
|
||||
<xr:ValueId>UUID-007</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogList.Организации" category="List">
|
||||
<xr:TypeId>UUID-008</xr:TypeId>
|
||||
<xr:ValueId>UUID-009</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogManager.Организации" category="Manager">
|
||||
<xr:TypeId>UUID-010</xr:TypeId>
|
||||
<xr:ValueId>UUID-011</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>Организации</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Организации</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<Hierarchical>false</Hierarchical>
|
||||
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
|
||||
<LimitLevelCount>false</LimitLevelCount>
|
||||
<LevelCount>2</LevelCount>
|
||||
<FoldersOnTop>true</FoldersOnTop>
|
||||
<UseStandardCommands>true</UseStandardCommands>
|
||||
<Owners/>
|
||||
<SubordinationUse>ToItems</SubordinationUse>
|
||||
<CodeLength>9</CodeLength>
|
||||
<DescriptionLength>25</DescriptionLength>
|
||||
<CodeType>String</CodeType>
|
||||
<CodeAllowedLength>Variable</CodeAllowedLength>
|
||||
<CodeSeries>WholeCatalog</CodeSeries>
|
||||
<CheckUnique>false</CheckUnique>
|
||||
<Autonumbering>true</Autonumbering>
|
||||
<DefaultPresentation>AsDescription</DefaultPresentation>
|
||||
<Characteristics/>
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.Организации.StandardAttribute.Description</xr:Field>
|
||||
<xr:Field>Catalog.Организации.StandardAttribute.Code</xr:Field>
|
||||
</InputByString>
|
||||
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
|
||||
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
|
||||
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
|
||||
<DefaultObjectForm/>
|
||||
<DefaultFolderForm/>
|
||||
<DefaultListForm/>
|
||||
<DefaultChoiceForm/>
|
||||
<DefaultFolderChoiceForm/>
|
||||
<AuxiliaryObjectForm/>
|
||||
<AuxiliaryFolderForm/>
|
||||
<AuxiliaryListForm/>
|
||||
<AuxiliaryChoiceForm/>
|
||||
<AuxiliaryFolderChoiceForm/>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<BasedOn/>
|
||||
<DataLockFields/>
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<ObjectPresentation/>
|
||||
<ExtendedObjectPresentation/>
|
||||
<ListPresentation/>
|
||||
<ExtendedListPresentation/>
|
||||
<Explanation/>
|
||||
<CreateOnInput>Use</CreateOnInput>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<DataHistory>DontUse</DataHistory>
|
||||
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
|
||||
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
|
||||
</Properties>
|
||||
<ChildObjects/>
|
||||
</Catalog>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,255 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Configuration uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-002</xr:ClassId>
|
||||
<xr:ObjectId>UUID-003</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-004</xr:ClassId>
|
||||
<xr:ObjectId>UUID-005</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-006</xr:ClassId>
|
||||
<xr:ObjectId>UUID-007</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-008</xr:ClassId>
|
||||
<xr:ObjectId>UUID-009</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-010</xr:ClassId>
|
||||
<xr:ObjectId>UUID-011</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-012</xr:ClassId>
|
||||
<xr:ObjectId>UUID-013</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-014</xr:ClassId>
|
||||
<xr:ObjectId>UUID-015</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>TestConfig</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>TestConfig</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment />
|
||||
<NamePrefix />
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
|
||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ScriptVariant>Russian</ScriptVariant>
|
||||
<DefaultRoles />
|
||||
<Vendor></Vendor>
|
||||
<Version></Version>
|
||||
<UpdateCatalogAddress />
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
|
||||
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
|
||||
<AdditionalFullTextSearchDictionaries />
|
||||
<CommonSettingsStorage />
|
||||
<ReportsUserSettingsStorage />
|
||||
<ReportsVariantsStorage />
|
||||
<FormDataSettingsStorage />
|
||||
<DynamicListsUserSettingsStorage />
|
||||
<URLExternalDataStorage />
|
||||
<Content />
|
||||
<DefaultReportForm />
|
||||
<DefaultReportVariantForm />
|
||||
<DefaultReportSettingsForm />
|
||||
<DefaultReportAppearanceTemplate />
|
||||
<DefaultDynamicListSettingsForm />
|
||||
<DefaultSearchForm />
|
||||
<DefaultDataHistoryChangeHistoryForm />
|
||||
<DefaultDataHistoryVersionDataForm />
|
||||
<DefaultDataHistoryVersionDifferencesForm />
|
||||
<DefaultCollaborationSystemUsersChoiceForm />
|
||||
<RequiredMobileApplicationPermissions />
|
||||
<UsedMobileApplicationFunctionalities>
|
||||
<app:functionality>
|
||||
<app:functionality>Biometrics</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Location</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundLocation</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BluetoothPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>WiFiPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Contacts</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Calendars</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PushNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>LocalNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InAppPurchases</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PersonalComputerFileExchange</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Ads</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NumberDialing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AutoSendSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ReceiveSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SMSLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Camera</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Microphone</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>MusicLibrary</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PictureAndVideoLibraries</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InstallPackages</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>OSBackup</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ApplicationUsageStatistics</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BarcodeScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioRecording</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllFilesAccess</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Videoconferences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NFC</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>DocumentScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SpeechToText</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Geofences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>IncomingShareRequests</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
</UsedMobileApplicationFunctionalities>
|
||||
<StandaloneConfigurationRestrictionRoles />
|
||||
<MobileApplicationURLs />
|
||||
<AllowedIncomingShareRequestTypes />
|
||||
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
|
||||
<DefaultInterface />
|
||||
<DefaultStyle />
|
||||
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
||||
<BriefInformation />
|
||||
<DetailedInformation />
|
||||
<Copyright />
|
||||
<VendorInformationAddress />
|
||||
<ConfigurationInformationAddress />
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
||||
<ModalityUseMode>DontUse</ModalityUseMode>
|
||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
<CompatibilityMode>Version8_3_24</CompatibilityMode>
|
||||
<DefaultConstantsForm />
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Language>Русский</Language>
|
||||
<Catalog>Контрагенты</Catalog>
|
||||
<Catalog>Организации</Catalog>
|
||||
<Catalog>ДоговорыКонтрагентов</Catalog>
|
||||
<Document>ЗаказПоставщику</Document>
|
||||
</ChildObjects>
|
||||
</Configuration>
|
||||
</MetaDataObject>
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Document uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="DocumentObject.ЗаказПоставщику" category="Object">
|
||||
<xr:TypeId>UUID-002</xr:TypeId>
|
||||
<xr:ValueId>UUID-003</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="DocumentRef.ЗаказПоставщику" category="Ref">
|
||||
<xr:TypeId>UUID-004</xr:TypeId>
|
||||
<xr:ValueId>UUID-005</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="DocumentSelection.ЗаказПоставщику" category="Selection">
|
||||
<xr:TypeId>UUID-006</xr:TypeId>
|
||||
<xr:ValueId>UUID-007</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="DocumentList.ЗаказПоставщику" category="List">
|
||||
<xr:TypeId>UUID-008</xr:TypeId>
|
||||
<xr:ValueId>UUID-009</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="DocumentManager.ЗаказПоставщику" category="Manager">
|
||||
<xr:TypeId>UUID-010</xr:TypeId>
|
||||
<xr:ValueId>UUID-011</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>ЗаказПоставщику</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Заказ поставщику</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<UseStandardCommands>true</UseStandardCommands>
|
||||
<Numerator/>
|
||||
<NumberType>String</NumberType>
|
||||
<NumberLength>11</NumberLength>
|
||||
<NumberAllowedLength>Variable</NumberAllowedLength>
|
||||
<NumberPeriodicity>Year</NumberPeriodicity>
|
||||
<CheckUnique>true</CheckUnique>
|
||||
<Autonumbering>true</Autonumbering>
|
||||
<Characteristics/>
|
||||
<BasedOn/>
|
||||
<InputByString>
|
||||
<xr:Field>Document.ЗаказПоставщику.StandardAttribute.Number</xr:Field>
|
||||
</InputByString>
|
||||
<CreateOnInput>Use</CreateOnInput>
|
||||
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
|
||||
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
|
||||
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
|
||||
<DefaultObjectForm/>
|
||||
<DefaultListForm/>
|
||||
<DefaultChoiceForm/>
|
||||
<AuxiliaryObjectForm/>
|
||||
<AuxiliaryListForm/>
|
||||
<AuxiliaryChoiceForm/>
|
||||
<Posting>Allow</Posting>
|
||||
<RealTimePosting>Deny</RealTimePosting>
|
||||
<RegisterRecordsDeletion>AutoDelete</RegisterRecordsDeletion>
|
||||
<RegisterRecordsWritingOnPost>WriteSelected</RegisterRecordsWritingOnPost>
|
||||
<SequenceFilling>AutoFill</SequenceFilling>
|
||||
<RegisterRecords/>
|
||||
<PostInPrivilegedMode>true</PostInPrivilegedMode>
|
||||
<UnpostInPrivilegedMode>true</UnpostInPrivilegedMode>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<DataLockFields/>
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<ObjectPresentation/>
|
||||
<ExtendedObjectPresentation/>
|
||||
<ListPresentation/>
|
||||
<ExtendedListPresentation/>
|
||||
<Explanation/>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<DataHistory>DontUse</DataHistory>
|
||||
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
|
||||
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
|
||||
</Properties>
|
||||
<ChildObjects/>
|
||||
</Document>
|
||||
</MetaDataObject>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
|
||||
<top>
|
||||
<panel id="UUID-001">
|
||||
<uuid>UUID-002</uuid>
|
||||
</panel>
|
||||
</top>
|
||||
<left>
|
||||
<panel id="UUID-003">
|
||||
<uuid>UUID-004</uuid>
|
||||
</panel>
|
||||
</left>
|
||||
<panelDef id="UUID-004"/>
|
||||
<panelDef id="UUID-005"/>
|
||||
<panelDef id="UUID-006"/>
|
||||
<panelDef id="UUID-002"/>
|
||||
<panelDef id="UUID-007"/>
|
||||
</ClientApplicationInterface>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Language uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>Русский</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Русский</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<LanguageCode>ru</LanguageCode>
|
||||
</Properties>
|
||||
</Language>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,176 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Catalog uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="CatalogObject.КрлфСпр" category="Object">
|
||||
<xr:TypeId>UUID-002</xr:TypeId>
|
||||
<xr:ValueId>UUID-003</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogRef.КрлфСпр" category="Ref">
|
||||
<xr:TypeId>UUID-004</xr:TypeId>
|
||||
<xr:ValueId>UUID-005</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogSelection.КрлфСпр" category="Selection">
|
||||
<xr:TypeId>UUID-006</xr:TypeId>
|
||||
<xr:ValueId>UUID-007</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogList.КрлфСпр" category="List">
|
||||
<xr:TypeId>UUID-008</xr:TypeId>
|
||||
<xr:ValueId>UUID-009</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogManager.КрлфСпр" category="Manager">
|
||||
<xr:TypeId>UUID-010</xr:TypeId>
|
||||
<xr:ValueId>UUID-011</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>КрлфСпр</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>КРЛФ спр</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment />
|
||||
<Hierarchical>false</Hierarchical>
|
||||
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
|
||||
<LimitLevelCount>false</LimitLevelCount>
|
||||
<LevelCount>2</LevelCount>
|
||||
<FoldersOnTop>true</FoldersOnTop>
|
||||
<UseStandardCommands>true</UseStandardCommands>
|
||||
<Owners />
|
||||
<SubordinationUse>ToItems</SubordinationUse>
|
||||
<CodeLength>9</CodeLength>
|
||||
<DescriptionLength>25</DescriptionLength>
|
||||
<CodeType>String</CodeType>
|
||||
<CodeAllowedLength>Variable</CodeAllowedLength>
|
||||
<CodeSeries>WholeCatalog</CodeSeries>
|
||||
<CheckUnique>false</CheckUnique>
|
||||
<Autonumbering>true</Autonumbering>
|
||||
<DefaultPresentation>AsDescription</DefaultPresentation>
|
||||
<Characteristics />
|
||||
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
|
||||
<EditType>InDialog</EditType>
|
||||
<QuickChoice>false</QuickChoice>
|
||||
<ChoiceMode>BothWays</ChoiceMode>
|
||||
<InputByString>
|
||||
<xr:Field>Catalog.КрлфСпр.StandardAttribute.Description</xr:Field>
|
||||
<xr:Field>Catalog.КрлфСпр.StandardAttribute.Code</xr:Field>
|
||||
</InputByString>
|
||||
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
|
||||
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
|
||||
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
|
||||
<DefaultObjectForm />
|
||||
<DefaultFolderForm />
|
||||
<DefaultListForm />
|
||||
<DefaultChoiceForm />
|
||||
<DefaultFolderChoiceForm />
|
||||
<AuxiliaryObjectForm />
|
||||
<AuxiliaryFolderForm />
|
||||
<AuxiliaryListForm />
|
||||
<AuxiliaryChoiceForm />
|
||||
<AuxiliaryFolderChoiceForm />
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<BasedOn />
|
||||
<DataLockFields />
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<ObjectPresentation />
|
||||
<ExtendedObjectPresentation />
|
||||
<ListPresentation />
|
||||
<ExtendedListPresentation />
|
||||
<Explanation />
|
||||
<CreateOnInput>Use</CreateOnInput>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<DataHistory>DontUse</DataHistory>
|
||||
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
|
||||
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Attribute uuid="UUID-012">
|
||||
<Properties>
|
||||
<Name>База</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>База</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment />
|
||||
<Type>
|
||||
<v8:Type>xs:string</v8:Type>
|
||||
<v8:StringQualifiers>
|
||||
<v8:Length>10</v8:Length>
|
||||
<v8:AllowedLength>Variable</v8:AllowedLength>
|
||||
</v8:StringQualifiers>
|
||||
</Type>
|
||||
<PasswordMode>false</PasswordMode>
|
||||
<Format />
|
||||
<EditFormat />
|
||||
<ToolTip />
|
||||
<MarkNegatives>false</MarkNegatives>
|
||||
<Mask />
|
||||
<MultiLine>false</MultiLine>
|
||||
<ExtendedEdit>false</ExtendedEdit>
|
||||
<MinValue xsi:nil="true" />
|
||||
<MaxValue xsi:nil="true" />
|
||||
<FillFromFillingValue>false</FillFromFillingValue>
|
||||
<FillValue xsi:type="xs:string" />
|
||||
<FillChecking>DontCheck</FillChecking>
|
||||
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
|
||||
<ChoiceParameterLinks />
|
||||
<ChoiceParameters />
|
||||
<QuickChoice>Auto</QuickChoice>
|
||||
<CreateOnInput>Auto</CreateOnInput>
|
||||
<ChoiceForm />
|
||||
<LinkByType />
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<Use>ForItem</Use>
|
||||
<Indexing>DontIndex</Indexing>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<DataHistory>Use</DataHistory>
|
||||
</Properties>
|
||||
</Attribute>
|
||||
<Attribute uuid="UUID-013">
|
||||
<Properties>
|
||||
<Name>Новый</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Новый</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment />
|
||||
<Type>
|
||||
<v8:Type>xs:boolean</v8:Type>
|
||||
</Type>
|
||||
<PasswordMode>false</PasswordMode>
|
||||
<Format />
|
||||
<EditFormat />
|
||||
<ToolTip />
|
||||
<MarkNegatives>false</MarkNegatives>
|
||||
<Mask />
|
||||
<MultiLine>false</MultiLine>
|
||||
<ExtendedEdit>false</ExtendedEdit>
|
||||
<MinValue xsi:nil="true" />
|
||||
<MaxValue xsi:nil="true" />
|
||||
<FillFromFillingValue>false</FillFromFillingValue>
|
||||
<FillValue xsi:type="xs:boolean">false</FillValue>
|
||||
<FillChecking>DontCheck</FillChecking>
|
||||
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
|
||||
<ChoiceParameterLinks />
|
||||
<ChoiceParameters />
|
||||
<QuickChoice>Auto</QuickChoice>
|
||||
<CreateOnInput>Auto</CreateOnInput>
|
||||
<ChoiceForm />
|
||||
<LinkByType />
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<Use>ForItem</Use>
|
||||
<Indexing>DontIndex</Indexing>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<DataHistory>Use</DataHistory>
|
||||
</Properties>
|
||||
</Attribute>
|
||||
</ChildObjects>
|
||||
</Catalog>
|
||||
</MetaDataObject>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user