mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-17 16:25:16 +03:00
feat(form-validate,cfe-borrow): проверка висячих привязок + идемпотентность re-borrow + добор по Field
Три доработки по форме, полезные модели, использующей навыки. form-validate v1.7→v1.8 (5a): Check 5 генерализован с одного <DataPath> на все 8 тегов-привязок (DataPath/TitleDataPath/FooterDataPath/HeaderDataPath/ MultipleValue*DataPath/RowPicture*). Висячая привязка (корень не в <Attributes>) теперь ловится при validate, а не всплывает на дорогом db-load. Skip-правила (companion-элементы, базовые элементы id<1000000 в BaseForm, opaque-формы) сохранены без изменений. Заодно фикс бага Check 12 в py-порте (type_invalid → type_error_count, краш на невалидном cfg:-типе в config-контексте). cfe-borrow v1.4→v1.5: - #4: borrow_form переиспользует uuid обёртки Forms/<Name>.xml, если файл уже существует, вместо генерации нового → повторное заимствование формы байт-идемпотентно (агент может ретраить без дрейфа identity). - #1: collect для -BorrowMainAttribute дополнен сканом <Field>Объект.X</Field> (поля фильтров/условного оформления/динсписков) — набор заимствованных реквизитов теперь совпадает с Конфигуратором (добавился УдалитьЮрФизЛицо). Тесты: form-validate/dangling-binding (фикстура broken-dangling-binding, expectError+stdoutContains); cfe-borrow/form-bindings + idempotent:true. Регресс 6/6 cfe-borrow + 11/11 form-validate на обоих рантаймах, E2E-load OK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# cfe-borrow v1.4 — Borrow objects from configuration into extension (CFE)
|
||||
# cfe-borrow v1.5 — Borrow objects from configuration into extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ExtensionPath,
|
||||
@@ -506,8 +506,23 @@ function Borrow-Form {
|
||||
}
|
||||
$srcFormContent = [System.IO.File]::ReadAllText($srcFormXmlPath, $enc)
|
||||
|
||||
# 3. Generate form metadata XML (ФормаЭлемента.xml)
|
||||
$newFormUuid = [guid]::NewGuid().ToString()
|
||||
# 3. Generate form metadata XML (ФормаЭлемента.xml).
|
||||
# If the wrapper was already borrowed, reuse its uuid so re-borrow is idempotent
|
||||
# (regenerating it would churn the form's identity on every rerun).
|
||||
$formMetaFileExisting = Join-Path (Join-Path (Join-Path (Join-Path $extDir $dirName) $objName) "Forms") "${formName}.xml"
|
||||
$newFormUuid = ""
|
||||
if (Test-Path $formMetaFileExisting) {
|
||||
try {
|
||||
$existingDoc = New-Object System.Xml.XmlDocument
|
||||
$existingDoc.Load($formMetaFileExisting)
|
||||
$existingFormNode = $existingDoc.DocumentElement.SelectSingleNode("*[local-name()='Form']")
|
||||
if ($existingFormNode) {
|
||||
$existingUuid = $existingFormNode.GetAttribute("uuid")
|
||||
if ($existingUuid) { $newFormUuid = $existingUuid }
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
if (-not $newFormUuid) { $newFormUuid = [guid]::NewGuid().ToString() }
|
||||
$formMetaSb = New-Object System.Text.StringBuilder
|
||||
$formMetaSb.AppendLine("<?xml version=`"1.0`" encoding=`"UTF-8`"?>") | Out-Null
|
||||
$formMetaSb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"$($script:formatVersion)`">") | Out-Null
|
||||
@@ -1040,6 +1055,22 @@ function Collect-FormDataPaths {
|
||||
}
|
||||
}
|
||||
|
||||
# Also scan <Field>Объект.X</Field> — object attributes referenced by filter/conditional-appearance
|
||||
# fields (and dynamic lists), not via a *DataPath binding (e.g. УдалитьЮрФизЛицо). Designer borrows these too.
|
||||
$fieldMatches = [regex]::Matches($content, "<Field>[^<]*\bОбъект\.(\w+(?:\.\w+)*)</Field>")
|
||||
foreach ($m in $fieldMatches) {
|
||||
$path = $m.Groups[1].Value
|
||||
$segments = $path.Split(".")
|
||||
$seg0 = $segments[0]
|
||||
if ($script:standardFields -contains $seg0) { continue }
|
||||
$firstLevel[$seg0] = $true
|
||||
if ($segments.Count -ge 2) {
|
||||
$seg1 = $segments[1]
|
||||
if ($script:standardFields -contains $seg1) { continue }
|
||||
$deepPaths += @{ ObjectAttr = $seg0; SubAttr = $seg1 }
|
||||
}
|
||||
}
|
||||
|
||||
# Deduplicate deep paths
|
||||
$seen = @{}
|
||||
$uniqueDeep = @()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cfe-borrow v1.4 — Borrow objects from configuration into extension (CFE)
|
||||
# cfe-borrow v1.5 — Borrow objects from configuration into extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -690,6 +690,21 @@ def main():
|
||||
continue
|
||||
deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1})
|
||||
|
||||
# Also scan <Field>Объект.X</Field> — object attributes referenced by filter/conditional-appearance
|
||||
# fields (and dynamic lists), not via a *DataPath binding (e.g. УдалитьЮрФизЛицо). Designer borrows these too.
|
||||
for m in re.finditer(r'<Field>[^<]*\bОбъект\.(\w+(?:\.\w+)*)</Field>', content):
|
||||
path = m.group(1)
|
||||
segments = path.split(".")
|
||||
seg0 = segments[0]
|
||||
if seg0 in STANDARD_FIELDS:
|
||||
continue
|
||||
first_level[seg0] = True
|
||||
if len(segments) >= 2:
|
||||
seg1 = segments[1]
|
||||
if seg1 in STANDARD_FIELDS:
|
||||
continue
|
||||
deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1})
|
||||
|
||||
# Deduplicate deep paths
|
||||
seen = set()
|
||||
unique_deep = []
|
||||
@@ -1138,8 +1153,22 @@ def main():
|
||||
with open(src_form_xml_path, "r", encoding="utf-8-sig") as fh:
|
||||
src_form_content = fh.read()
|
||||
|
||||
# 3. Generate form metadata XML
|
||||
new_form_uuid = new_guid()
|
||||
# 3. Generate form metadata XML.
|
||||
# If the wrapper was already borrowed, reuse its uuid so re-borrow is idempotent
|
||||
# (regenerating it would churn the form's identity on every rerun).
|
||||
existing_wrapper = os.path.join(ext_dir, dir_name, obj_name, "Forms", f"{form_name}.xml")
|
||||
new_form_uuid = ""
|
||||
if os.path.isfile(existing_wrapper):
|
||||
try:
|
||||
existing_root = etree.parse(existing_wrapper).getroot()
|
||||
for c in existing_root:
|
||||
if isinstance(c.tag, str) and localname(c) == "Form":
|
||||
new_form_uuid = c.get("uuid", "") or ""
|
||||
break
|
||||
except Exception:
|
||||
new_form_uuid = ""
|
||||
if not new_form_uuid:
|
||||
new_form_uuid = new_guid()
|
||||
form_meta_lines = [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
f'<MetaDataObject {XMLNS_DECL} version="{format_version}">',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# form-validate v1.7 — Validate 1C managed form
|
||||
# form-validate v1.8 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -382,6 +382,10 @@ if (-not $stopped) {
|
||||
$pathChecked = 0
|
||||
$pathBaseSkipped = 0
|
||||
|
||||
# All data-binding tags whose value is an attribute path (root must exist in <Attributes>).
|
||||
$bindingTags = @('DataPath','TitleDataPath','FooterDataPath','HeaderDataPath',
|
||||
'MultipleValueDataPath','MultipleValuePresentDataPath','RowPictureDataPath','MultipleValuePictureDataPath')
|
||||
|
||||
foreach ($el in $allElements) {
|
||||
if ($stopped) { break }
|
||||
$tag = $el.Tag
|
||||
@@ -398,60 +402,63 @@ if (-not $stopped) {
|
||||
try { if ([int]$el.Id -lt 1000000) { $pathBaseSkipped++; continue } } catch {}
|
||||
}
|
||||
|
||||
$dpNode = $node.SelectSingleNode("f:DataPath", $nsMgr)
|
||||
if (-not $dpNode) { continue }
|
||||
foreach ($bTag in $bindingTags) {
|
||||
if ($stopped) { break }
|
||||
$dpNode = $node.SelectSingleNode("f:$bTag", $nsMgr)
|
||||
if (-not $dpNode) { continue }
|
||||
|
||||
$dataPath = $dpNode.InnerText.Trim()
|
||||
if (-not $dataPath) { continue }
|
||||
$dataPath = $dpNode.InnerText.Trim()
|
||||
if (-not $dataPath) { continue }
|
||||
|
||||
# Opaque platform-internal DataPath shapes — not validatable from Form.xml alone:
|
||||
# - bare numeric (e.g. "10", "1000003") — internal index
|
||||
# - "N/M:<uuid>" — metadata reference by UUID
|
||||
if ($dataPath -match '^\d+$' -or $dataPath -match '^\d+/\d+:[0-9a-fA-F-]+$') {
|
||||
continue
|
||||
}
|
||||
|
||||
$pathChecked++
|
||||
|
||||
# Extract root segment of path, strip array indices like [0]
|
||||
$cleanPath = $dataPath -replace '\[\d+\]', ''
|
||||
# Strip leading '~' (current row of DynamicList: ~Список.Поле)
|
||||
if ($cleanPath.StartsWith('~')) { $cleanPath = $cleanPath.Substring(1) }
|
||||
$segments = $cleanPath -split '\.'
|
||||
$rootAttr = $segments[0]
|
||||
|
||||
# Resolve Items.<TableName>.CurrentData.<Field>... — table element, not attribute
|
||||
if ($rootAttr -eq 'Items') {
|
||||
if ($segments.Count -lt 3 -or $segments[2] -ne 'CurrentData') {
|
||||
Report-Warn "[$tag] '$elName': DataPath='$dataPath' — unknown Items.* shape, expected Items.<Table>.CurrentData.*"
|
||||
# Opaque platform-internal shapes — not validatable from Form.xml alone:
|
||||
# - bare numeric (e.g. "10", "1000003") — internal index
|
||||
# - "N/M:<uuid>" — metadata reference by UUID
|
||||
if ($dataPath -match '^\d+$' -or $dataPath -match '^\d+/\d+:[0-9a-fA-F-]+$') {
|
||||
continue
|
||||
}
|
||||
$tableName = $segments[1]
|
||||
$tableEl = $null
|
||||
foreach ($candidate in $allElements) {
|
||||
if ($candidate.Tag -eq 'Table' -and $candidate.Name -eq $tableName) {
|
||||
$tableEl = $candidate
|
||||
break
|
||||
|
||||
$pathChecked++
|
||||
|
||||
# Extract root segment of path, strip array indices like [0]
|
||||
$cleanPath = $dataPath -replace '\[\d+\]', ''
|
||||
# Strip leading '~' (current row of DynamicList: ~Список.Поле)
|
||||
if ($cleanPath.StartsWith('~')) { $cleanPath = $cleanPath.Substring(1) }
|
||||
$segments = $cleanPath -split '\.'
|
||||
$rootAttr = $segments[0]
|
||||
|
||||
# Resolve Items.<TableName>.CurrentData.<Field>... — table element, not attribute
|
||||
if ($rootAttr -eq 'Items') {
|
||||
if ($segments.Count -lt 3 -or $segments[2] -ne 'CurrentData') {
|
||||
Report-Warn "[$tag] '$elName': $bTag='$dataPath' — unknown Items.* shape, expected Items.<Table>.CurrentData.*"
|
||||
continue
|
||||
}
|
||||
$tableName = $segments[1]
|
||||
$tableEl = $null
|
||||
foreach ($candidate in $allElements) {
|
||||
if ($candidate.Tag -eq 'Table' -and $candidate.Name -eq $tableName) {
|
||||
$tableEl = $candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
if (-not $tableEl) {
|
||||
Report-Error "[$tag] '$elName': $bTag='$dataPath' — table element '$tableName' not found"
|
||||
$pathErrors++
|
||||
continue
|
||||
}
|
||||
$tableDpNode = $tableEl.Node.SelectSingleNode("f:DataPath", $nsMgr)
|
||||
if (-not $tableDpNode -or -not $tableDpNode.InnerText.Trim()) {
|
||||
# Table without DataPath — can't resolve further, accept silently
|
||||
continue
|
||||
}
|
||||
$tableDp = $tableDpNode.InnerText.Trim() -replace '\[\d+\]', ''
|
||||
if ($tableDp.StartsWith('~')) { $tableDp = $tableDp.Substring(1) }
|
||||
$rootAttr = ($tableDp -split '\.')[0]
|
||||
}
|
||||
if (-not $tableEl) {
|
||||
Report-Error "[$tag] '$elName': DataPath='$dataPath' — table element '$tableName' not found"
|
||||
$pathErrors++
|
||||
continue
|
||||
}
|
||||
$tableDpNode = $tableEl.Node.SelectSingleNode("f:DataPath", $nsMgr)
|
||||
if (-not $tableDpNode -or -not $tableDpNode.InnerText.Trim()) {
|
||||
# Table without DataPath — can't resolve further, accept silently
|
||||
continue
|
||||
}
|
||||
$tableDp = $tableDpNode.InnerText.Trim() -replace '\[\d+\]', ''
|
||||
if ($tableDp.StartsWith('~')) { $tableDp = $tableDp.Substring(1) }
|
||||
$rootAttr = ($tableDp -split '\.')[0]
|
||||
}
|
||||
|
||||
if (-not $attrMap.ContainsKey($rootAttr)) {
|
||||
Report-Error "[$tag] '$elName': DataPath='$dataPath' — attribute '$rootAttr' not found"
|
||||
$pathErrors++
|
||||
if (-not $attrMap.ContainsKey($rootAttr)) {
|
||||
Report-Error "[$tag] '$elName': $bTag='$dataPath' — attribute '$rootAttr' not found"
|
||||
$pathErrors++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,9 +469,9 @@ if (-not $stopped) {
|
||||
$pathMsg = if ($pathMsg) { "$pathMsg, $skipNote" } else { $skipNote }
|
||||
}
|
||||
if ($pathErrors -eq 0 -and $pathMsg) {
|
||||
Report-OK "DataPath references: $pathMsg"
|
||||
Report-OK "Data bindings: $pathMsg"
|
||||
} elseif ($pathErrors -eq 0) {
|
||||
Report-OK "DataPath references: none"
|
||||
Report-OK "Data bindings: none"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-validate v1.7 — Validate 1C managed form
|
||||
# form-validate v1.8 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -379,6 +379,10 @@ def main():
|
||||
path_checked = 0
|
||||
path_base_skipped = 0
|
||||
|
||||
# All data-binding tags whose value is an attribute path (root must exist in <Attributes>).
|
||||
binding_tags = ["DataPath", "TitleDataPath", "FooterDataPath", "HeaderDataPath",
|
||||
"MultipleValueDataPath", "MultipleValuePresentDataPath", "RowPictureDataPath", "MultipleValuePictureDataPath"]
|
||||
|
||||
skip_tags = {"ContextMenu", "ExtendedTooltip", "AutoCommandBar", "SearchStringAddition", "ViewStatusAddition", "SearchControlAddition"}
|
||||
|
||||
for el in all_elements:
|
||||
@@ -399,55 +403,58 @@ def main():
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
dp_node = node.find(f"{{{F_NS}}}DataPath")
|
||||
if dp_node is None:
|
||||
continue
|
||||
|
||||
data_path = (dp_node.text or "").strip()
|
||||
if not data_path:
|
||||
continue
|
||||
|
||||
# Opaque platform-internal DataPath shapes — not validatable from Form.xml alone:
|
||||
# - bare numeric (e.g. "10", "1000003") — internal index
|
||||
# - "N/M:<uuid>" — metadata reference by UUID
|
||||
if re.match(r'^\d+$', data_path) or re.match(r'^\d+/\d+:[0-9a-fA-F-]+$', data_path):
|
||||
continue
|
||||
|
||||
path_checked += 1
|
||||
|
||||
clean_path = re.sub(r'\[\d+\]', '', data_path)
|
||||
# Strip leading '~' (current row of DynamicList: ~\u0421\u043f\u0438\u0441\u043e\u043a.\u041f\u043e\u043b\u0435)
|
||||
if clean_path.startswith('~'):
|
||||
clean_path = clean_path[1:]
|
||||
segments = clean_path.split(".")
|
||||
root_attr = segments[0]
|
||||
|
||||
# Resolve Items.<TableName>.CurrentData.<Field>... \u2014 table element, not attribute
|
||||
if root_attr == 'Items':
|
||||
if len(segments) < 3 or segments[2] != 'CurrentData':
|
||||
report_warn(f"[{tag}] '{el_name}': DataPath='{data_path}' \u2014 unknown Items.* shape, expected Items.<Table>.CurrentData.*")
|
||||
for b_tag in binding_tags:
|
||||
if stopped:
|
||||
break
|
||||
dp_node = node.find(f"{{{F_NS}}}{b_tag}")
|
||||
if dp_node is None:
|
||||
continue
|
||||
table_name = segments[1]
|
||||
table_el = None
|
||||
for candidate in all_elements:
|
||||
if candidate["Tag"] == 'Table' and candidate["Name"] == table_name:
|
||||
table_el = candidate
|
||||
break
|
||||
if table_el is None:
|
||||
report_error(f"[{tag}] '{el_name}': DataPath='{data_path}' \u2014 table element '{table_name}' not found")
|
||||
|
||||
data_path = (dp_node.text or "").strip()
|
||||
if not data_path:
|
||||
continue
|
||||
|
||||
# Opaque platform-internal shapes — not validatable from Form.xml alone:
|
||||
# - bare numeric (e.g. "10", "1000003") — internal index
|
||||
# - "N/M:<uuid>" — metadata reference by UUID
|
||||
if re.match(r'^\d+$', data_path) or re.match(r'^\d+/\d+:[0-9a-fA-F-]+$', data_path):
|
||||
continue
|
||||
|
||||
path_checked += 1
|
||||
|
||||
clean_path = re.sub(r'\[\d+\]', '', data_path)
|
||||
# Strip leading '~' (current row of DynamicList: ~Список.Поле)
|
||||
if clean_path.startswith('~'):
|
||||
clean_path = clean_path[1:]
|
||||
segments = clean_path.split(".")
|
||||
root_attr = segments[0]
|
||||
|
||||
# Resolve Items.<TableName>.CurrentData.<Field>... — table element, not attribute
|
||||
if root_attr == 'Items':
|
||||
if len(segments) < 3 or segments[2] != 'CurrentData':
|
||||
report_warn(f"[{tag}] '{el_name}': {b_tag}='{data_path}' — unknown Items.* shape, expected Items.<Table>.CurrentData.*")
|
||||
continue
|
||||
table_name = segments[1]
|
||||
table_el = None
|
||||
for candidate in all_elements:
|
||||
if candidate["Tag"] == 'Table' and candidate["Name"] == table_name:
|
||||
table_el = candidate
|
||||
break
|
||||
if table_el is None:
|
||||
report_error(f"[{tag}] '{el_name}': {b_tag}='{data_path}' — table element '{table_name}' not found")
|
||||
path_errors += 1
|
||||
continue
|
||||
table_dp_node = table_el["Node"].find(f"{{{F_NS}}}DataPath")
|
||||
if table_dp_node is None or not (table_dp_node.text or "").strip():
|
||||
continue
|
||||
table_dp = re.sub(r'\[\d+\]', '', (table_dp_node.text or "").strip())
|
||||
if table_dp.startswith('~'):
|
||||
table_dp = table_dp[1:]
|
||||
root_attr = table_dp.split(".")[0]
|
||||
|
||||
if root_attr not in attr_map:
|
||||
report_error(f"[{tag}] '{el_name}': {b_tag}='{data_path}' — attribute '{root_attr}' not found")
|
||||
path_errors += 1
|
||||
continue
|
||||
table_dp_node = table_el["Node"].find(f"{{{F_NS}}}DataPath")
|
||||
if table_dp_node is None or not (table_dp_node.text or "").strip():
|
||||
continue
|
||||
table_dp = re.sub(r'\[\d+\]', '', (table_dp_node.text or "").strip())
|
||||
if table_dp.startswith('~'):
|
||||
table_dp = table_dp[1:]
|
||||
root_attr = table_dp.split(".")[0]
|
||||
|
||||
if root_attr not in attr_map:
|
||||
report_error(f"[{tag}] '{el_name}': DataPath='{data_path}' \u2014 attribute '{root_attr}' not found")
|
||||
path_errors += 1
|
||||
|
||||
path_msg = ""
|
||||
if path_checked > 0:
|
||||
@@ -456,7 +463,7 @@ def main():
|
||||
skip_note = f"{path_base_skipped} base skipped"
|
||||
path_msg = f"{path_msg}, {skip_note}" if path_msg else skip_note
|
||||
if path_errors == 0 and path_msg:
|
||||
report_ok(f"DataPath references: {path_msg}")
|
||||
report_ok(f"Data bindings: {path_msg}")
|
||||
|
||||
# --- Check 6: Button command references ---
|
||||
if not stopped:
|
||||
@@ -724,7 +731,7 @@ def main():
|
||||
# ExternalDataProcessorObject/ExternalReportObject valid only in EPF/ERF context
|
||||
if is_config_context and prefix in ('ExternalDataProcessorObject', 'ExternalReportObject'):
|
||||
report_error(f'12. Type "{tv}": External* type in configuration context (use DataProcessorObject/ReportObject instead)')
|
||||
type_invalid += 1
|
||||
type_error_count += 1
|
||||
else:
|
||||
report_warn(f'12. Type "{tv}": unrecognized cfg prefix')
|
||||
type_warn_count += 1
|
||||
|
||||
@@ -37,5 +37,6 @@
|
||||
}
|
||||
],
|
||||
"params": { "extensionPath": "ext", "object": "Catalog.Товары.Form.ФормаЭлемента" },
|
||||
"args_extra": ["-BorrowMainAttribute", "Form"]
|
||||
"args_extra": ["-BorrowMainAttribute", "Form"],
|
||||
"idempotent": true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "Ошибка: висячая привязка данных (MultipleValueDataPath без реквизита)",
|
||||
"setup": "fixture:broken-dangling-binding",
|
||||
"params": { "formPath": "DataProcessors/Тест/Forms/Форма" },
|
||||
"expectError": true,
|
||||
"expect": { "stdoutContains": "MultipleValueDataPath" }
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems>
|
||||
<InputField name="ПолеМеток" id="2">
|
||||
<DataPath>Метки</DataPath>
|
||||
<MultipleValueDataPath>Метки.Значение</MultipleValueDataPath>
|
||||
<MultipleValuePresentDataPath>Метки.Представление</MultipleValuePresentDataPath>
|
||||
<ContextMenu name="ПолеМетокКонтекстноеМеню" id="3"/>
|
||||
<ExtendedTooltip name="ПолеМетокРасширеннаяПодсказка" id="4"/>
|
||||
</InputField>
|
||||
</ChildItems>
|
||||
<Attributes>
|
||||
<Attribute name="Объект" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:DataProcessorObject.Тест</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
<SavedData>true</SavedData>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
Reference in New Issue
Block a user