mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-28 13:05:53 +03:00
fix(form-validate): проверка 14 сверяет форму с её дескриптором, с выгрузкой — предупреждением
Тело формы сравнивалось с Configuration.xml, а платформа сверяет его с дескриптором формы (Forms/<Имя>.xml, у общей формы CommonForms/<Имя>.xml): только эту пару она отвергает при расхождении («Версия формата загружаемого файла … отличается от версии формата ранее загруженных файлов»). Отсюда были ошибки в обе стороны: согласованная пара 2.18 в выгрузке 2.20 (грузится) получала ERROR, а пара 2.20/2.18 при Configuration.xml 2.18 (не грузится) проходила. Кроме того, при якоре без версии генераторный detect_format_version отдавал дефолт 2.17 — ложное расхождение. Теперь: форма ≠ своему дескриптору — ошибка; иначе форма ≠ выгрузке — предупреждение (платформа грузит, но выгрузка неоднородна). Контекст «конфигурация / EPF» берётся тем же поиском якоря (find_dump_anchor). Новая общая функция ext_body_owner — семья в check-inline-drift. Co-Authored-By: Sergei Pleshanov <2357qwr@gmail.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Sergei Pleshanov
Claude Opus 5.5
parent
a90861800b
commit
c943acefa2
@@ -1,4 +1,4 @@
|
||||
# form-validate v1.19 — Validate 1C managed form
|
||||
# form-validate v1.20 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
[CmdletBinding(PositionalBinding=$false)]
|
||||
param(
|
||||
@@ -74,63 +74,56 @@ function Test-ExternalObjectRoot([string]$xmlPath) {
|
||||
return $false
|
||||
}
|
||||
|
||||
# --- Detect context: config vs EPF/ERF ---
|
||||
# Walk up from FormPath looking for Configuration.xml → config context
|
||||
# No Configuration.xml → external data processor / report (EPF/ERF)
|
||||
$script:isConfigContext = $false
|
||||
$walkDir = Split-Path (Resolve-Path $FormPath) -Parent
|
||||
for ($i = 0; $i -lt 15; $i++) {
|
||||
if (-not $walkDir -or $walkDir -eq (Split-Path $walkDir)) { break }
|
||||
# Порядок проверок тот же, что у Detect-FormatVersion: сначала корень автономной обработки,
|
||||
# потом Configuration.xml — иначе форма внутри EPF, лежащей в дереве конфигурации, взяла бы
|
||||
# версию конфигурации.
|
||||
$extRoot = "$walkDir.xml"
|
||||
if (-not $script:versionAnchor) {
|
||||
if (Test-ExternalObjectRoot $extRoot) {
|
||||
# Ближайший якорь побеждает: автономная обработка остаётся автономной, даже если её
|
||||
# исходники лежат внутри дерева с Configuration.xml (типовая раскладка проекта:
|
||||
# src/cf рядом с src/epf). Иначе её собственные External*-типы считались бы ошибкой.
|
||||
$script:versionAnchor = $extRoot
|
||||
break
|
||||
}
|
||||
}
|
||||
if (Test-Path (Join-Path $walkDir "Configuration.xml")) {
|
||||
$script:isConfigContext = $true
|
||||
$script:configXmlPath = Join-Path $walkDir "Configuration.xml"
|
||||
if (-not $script:versionAnchor) { $script:versionAnchor = $script:configXmlPath }
|
||||
break
|
||||
}
|
||||
$walkDir = Split-Path $walkDir
|
||||
# Штамп версии формата — атрибут version КОРНЕВОГО элемента файла. Копия общего эталона (семья
|
||||
# root_version, авторитет — meta-validate).
|
||||
function Get-RootVersion([string]$xmlPath) {
|
||||
if (-not (Test-Path -LiteralPath $xmlPath -PathType Leaf)) { return $null }
|
||||
$buf = New-Object byte[] 4096
|
||||
$fs = [System.IO.File]::OpenRead($xmlPath)
|
||||
try { $len = $fs.Read($buf, 0, $buf.Length) } finally { $fs.Dispose() }
|
||||
$head = [System.Text.Encoding]::UTF8.GetString($buf, 0, $len)
|
||||
$m = [regex]::Match($head, '<[A-Za-z_][\w.:-]*(\s[^>]*)?/?>')
|
||||
if (-not $m.Success) { return $null }
|
||||
$v = [regex]::Match($m.Groups[1].Value, '(?:^|\s)version="([^"]*)"')
|
||||
if ($v.Success) { return $v.Groups[1].Value }
|
||||
return $null
|
||||
}
|
||||
|
||||
# Версия формата выгрузки. Копия общего эталона (семья detect_format_version, авторитет —
|
||||
# form-compile): та же ветка для автономной EPF/ERF, где версию несёт корень обработки.
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||
$extPath = "$d.xml"
|
||||
if (Test-Path $extPath) {
|
||||
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
|
||||
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
|
||||
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||
}
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
||||
# Длину среза берём по СТРОКЕ, а не по размеру файла: размер в БАЙТАХ, Substring считает
|
||||
# СИМВОЛЫ, и на кириллице байт больше — короткий Configuration.xml ронял навык исключением.
|
||||
$head = $cfgText.Substring(0, [Math]::Min(2000, $cfgText.Length))
|
||||
if ($head -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||
}
|
||||
$parent = Split-Path $d -Parent
|
||||
if ($parent -eq $d) { break }
|
||||
# Якорь выгрузки: корень автономной EPF/ERF либо Configuration.xml, ближайший вверх. Копия общего
|
||||
# эталона (семья find_dump_anchor, авторитет — meta-validate).
|
||||
function Find-DumpAnchor([string]$startDir) {
|
||||
$d = $startDir
|
||||
for ($i = 0; $i -lt 15 -and $d; $i++) {
|
||||
if (Test-ExternalObjectRoot "$d.xml") { return "$d.xml" }
|
||||
$cfg = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfg) { return $cfg }
|
||||
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||
if (-not $parent -or $parent -eq $d) { break }
|
||||
$d = $parent
|
||||
}
|
||||
return "2.17"
|
||||
return $null
|
||||
}
|
||||
|
||||
# Владелец тела X/Ext/<файл>.xml — дескриптор X.xml рядом с каталогом X. У тел конфигурации (и
|
||||
# расширения) соседа-дескриптора нет, владелец — Configuration.xml внутри X. Иначе — не определён.
|
||||
function Get-ExtBodyOwner([string]$bodyPath) {
|
||||
$extDir = [System.IO.Path]::GetDirectoryName($bodyPath)
|
||||
if ([System.IO.Path]::GetFileName($extDir) -cne "Ext") { return $null }
|
||||
$objDir = [System.IO.Path]::GetDirectoryName($extDir)
|
||||
if (Test-Path -LiteralPath "$objDir.xml" -PathType Leaf) { return "$objDir.xml" }
|
||||
$cfg = Join-Path $objDir "Configuration.xml"
|
||||
if (Test-Path -LiteralPath $cfg -PathType Leaf) { return $cfg }
|
||||
return $null
|
||||
}
|
||||
|
||||
# --- Detect context: config vs EPF/ERF ---
|
||||
# Ближайший якорь выгрузки вверх от формы. Configuration.xml → конфигурация; корень EPF/ERF → внешняя
|
||||
# обработка/отчёт. Ближайший побеждает: автономная обработка остаётся автономной, даже если её
|
||||
# исходники лежат внутри дерева с Configuration.xml (типовая раскладка проекта: src/cf рядом с
|
||||
# src/epf). Иначе её собственные External*-типы считались бы ошибкой.
|
||||
$script:dumpAnchor = Find-DumpAnchor (Split-Path (Resolve-Path $FormPath) -Parent)
|
||||
$script:isConfigContext = [bool]($script:dumpAnchor -and [System.IO.Path]::GetFileName($script:dumpAnchor) -eq "Configuration.xml")
|
||||
|
||||
# --- Counters ---
|
||||
|
||||
$errors = 0
|
||||
@@ -1024,22 +1017,27 @@ if (-not $stopped) {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check 14: версия формата формы совпадает с версией выгрузки ---
|
||||
# Версию задаёт платформа, которой выгружали, и в пределах одной выгрузки она едина. Форма из
|
||||
# другой версии — «Неизвестная версия формата N загружаемого файла»: платформа не читает файл,
|
||||
# который новее её самой. Источник версии ищем общим helper-ом: он же покрывает автономную
|
||||
# внешнюю обработку/отчёт, где Configuration.xml нет и версию несёт корень самой обработки.
|
||||
# --- Check 14: версия формата формы — как у её дескриптора; сверка с выгрузкой ---
|
||||
# Тело формы и дескриптор Forms/<Имя>.xml (у общей формы — CommonForms/<Имя>.xml) платформа загружает
|
||||
# только в одной версии: «Версия формата загружаемого файла … отличается от версии формата ранее
|
||||
# загруженных файлов». С остальной выгрузкой форма может расходиться — платформа такое грузит, это
|
||||
# лишь неоднородность выгрузки (типично после мержа веток, выгруженных разными платформами).
|
||||
|
||||
if (-not $stopped -and $script:versionAnchor) {
|
||||
if (-not $stopped) {
|
||||
$formVer = $root.GetAttribute("version")
|
||||
$dumpVer = Detect-FormatVersion (Split-Path (Resolve-Path $FormPath) -Parent)
|
||||
$formFull = (Resolve-Path $FormPath).Path
|
||||
$ownerPath = Get-ExtBodyOwner $formFull
|
||||
$ownerVer = if ($ownerPath) { Get-RootVersion $ownerPath } else { $null }
|
||||
$dumpVer = if ($script:dumpAnchor) { Get-RootVersion $script:dumpAnchor } else { $null }
|
||||
|
||||
if (-not $formVer) {
|
||||
Report-OK "14. Format version: not comparable"
|
||||
} elseif ($formVer -ne $dumpVer) {
|
||||
Report-Error "14. Format version $formVer differs from the dump ($dumpVer) — a dump carries one version, the platform refuses a file it cannot read"
|
||||
} else {
|
||||
Report-OK "14. Format version: $formVer, matches the dump"
|
||||
} elseif ($ownerVer -and $formVer -ne $ownerVer) {
|
||||
Report-Error "14. Format version $formVer differs from the form descriptor $([System.IO.Path]::GetFileName($ownerPath)) ($ownerVer) — the platform refuses to load parts of one object in different formats"
|
||||
} elseif ($dumpVer -and $formVer -ne $dumpVer) {
|
||||
Report-Warn "14. Format version $formVer differs from the dump ($dumpVer) — the platform loads it, but the dump is no longer uniform (typical after merging branches dumped by different platforms)"
|
||||
} elseif ($ownerVer -or $dumpVer) {
|
||||
Report-OK "14. Format version: $formVer, matches the descriptor and the dump"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-validate v1.19 — Validate 1C managed form
|
||||
# form-validate v1.20 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -88,32 +88,54 @@ def _sg_is_external_root(xml_path):
|
||||
return False
|
||||
|
||||
|
||||
# Версия формата выгрузки. Копия общего эталона (семья detect_format_version, авторитет —
|
||||
# form-compile): та же ветка для автономной EPF/ERF, где версию несёт корень обработки.
|
||||
def detect_format_version(d):
|
||||
while d:
|
||||
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||
ext_path = d + ".xml"
|
||||
if os.path.isfile(ext_path):
|
||||
with open(ext_path, "r", encoding="utf-8-sig") as f:
|
||||
ext_head = f.read(2000)
|
||||
if re.search(r'<(ExternalDataProcessor|ExternalReport)[ >]', ext_head):
|
||||
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', ext_head)
|
||||
if m:
|
||||
return m.group(1)
|
||||
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(2000)
|
||||
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', head)
|
||||
if m:
|
||||
return m.group(1)
|
||||
# Штамп версии формата — атрибут version КОРНЕВОГО элемента файла. Копия общего эталона (семья
|
||||
# root_version, авторитет — meta-validate).
|
||||
def root_version(xml_path):
|
||||
if not os.path.isfile(xml_path):
|
||||
return None
|
||||
with open(xml_path, "rb") as f:
|
||||
head = f.read(4096).decode("utf-8", errors="ignore")
|
||||
m = re.search(r'<[A-Za-z_][\w.:-]*(\s[^>]*)?/?>', head)
|
||||
if not m:
|
||||
return None
|
||||
v = re.search(r'(?:^|\s)version="([^"]*)"', m.group(1) or "")
|
||||
if v:
|
||||
return v.group(1)
|
||||
return None
|
||||
|
||||
|
||||
# Якорь выгрузки: корень автономной EPF/ERF либо Configuration.xml, ближайший вверх. Копия общего
|
||||
# эталона (семья find_dump_anchor, авторитет — meta-validate).
|
||||
def find_dump_anchor(start_dir):
|
||||
d = start_dir
|
||||
for _ in range(15):
|
||||
if not d:
|
||||
break
|
||||
if _sg_is_external_root(d + ".xml"):
|
||||
return d + ".xml"
|
||||
cfg = os.path.join(d, "Configuration.xml")
|
||||
if os.path.exists(cfg):
|
||||
return cfg
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
if not parent or parent == d:
|
||||
break
|
||||
d = parent
|
||||
return "2.17"
|
||||
return None
|
||||
|
||||
|
||||
# Владелец тела X/Ext/<файл>.xml — дескриптор X.xml рядом с каталогом X. У тел конфигурации (и
|
||||
# расширения) соседа-дескриптора нет, владелец — Configuration.xml внутри X. Иначе — не определён.
|
||||
def ext_body_owner(body_path):
|
||||
ext_dir = os.path.dirname(body_path)
|
||||
if os.path.basename(ext_dir) != "Ext":
|
||||
return None
|
||||
obj_dir = os.path.dirname(ext_dir)
|
||||
if os.path.isfile(obj_dir + ".xml"):
|
||||
return obj_dir + ".xml"
|
||||
cfg = os.path.join(obj_dir, "Configuration.xml")
|
||||
if os.path.isfile(cfg):
|
||||
return cfg
|
||||
return None
|
||||
|
||||
|
||||
# ── Format version ───────────────────────────────────────────
|
||||
@@ -187,32 +209,12 @@ def main():
|
||||
root = tree.getroot()
|
||||
|
||||
# Detect context: config vs EPF/ERF
|
||||
is_config_context = False
|
||||
config_xml_path = ''
|
||||
version_anchor = ''
|
||||
walk_dir = os.path.dirname(os.path.abspath(form_path))
|
||||
for _ in range(15):
|
||||
parent = os.path.dirname(walk_dir)
|
||||
if parent == walk_dir:
|
||||
break
|
||||
# Порядок проверок тот же, что у detect_format_version: сначала корень автономной обработки,
|
||||
# потом Configuration.xml — иначе форма внутри EPF, лежащей в дереве конфигурации, взяла бы
|
||||
# версию конфигурации.
|
||||
ext_root = walk_dir + '.xml'
|
||||
if not version_anchor:
|
||||
if _sg_is_external_root(ext_root):
|
||||
# Ближайший якорь побеждает: автономная обработка остаётся автономной, даже если её
|
||||
# исходники лежат внутри дерева с Configuration.xml (типовая раскладка проекта:
|
||||
# src/cf рядом с src/epf). Иначе её собственные External*-типы считались бы ошибкой.
|
||||
version_anchor = ext_root
|
||||
break
|
||||
if os.path.isfile(os.path.join(walk_dir, 'Configuration.xml')):
|
||||
is_config_context = True
|
||||
config_xml_path = os.path.join(walk_dir, 'Configuration.xml')
|
||||
if not version_anchor:
|
||||
version_anchor = config_xml_path
|
||||
break
|
||||
walk_dir = parent
|
||||
# Ближайший якорь выгрузки вверх от формы. Configuration.xml → конфигурация; корень EPF/ERF → внешняя
|
||||
# обработка/отчёт. Ближайший побеждает: автономная обработка остаётся автономной, даже если её
|
||||
# исходники лежат внутри дерева с Configuration.xml (типовая раскладка проекта: src/cf рядом с
|
||||
# src/epf). Иначе её собственные External*-типы считались бы ошибкой.
|
||||
dump_anchor = find_dump_anchor(os.path.dirname(os.path.abspath(form_path)))
|
||||
is_config_context = bool(dump_anchor) and os.path.basename(dump_anchor) == 'Configuration.xml'
|
||||
|
||||
errors = 0
|
||||
warnings = 0
|
||||
@@ -957,22 +959,28 @@ def main():
|
||||
elif prefix_errors == 0:
|
||||
report_ok(f'13. Namespace prefixes: {prefix_checked} values, all declared')
|
||||
|
||||
# --- Check 14: версия формата формы совпадает с версией выгрузки ---
|
||||
# Версию задаёт платформа, которой выгружали, и в пределах одной выгрузки она едина. Форма из
|
||||
# другой версии — «Неизвестная версия формата N загружаемого файла»: платформа не читает файл,
|
||||
# который новее её самой. Источник версии ищем общим helper-ом: он же покрывает автономную
|
||||
# внешнюю обработку/отчёт, где Configuration.xml нет и версию несёт корень самой обработки.
|
||||
if not stopped and version_anchor:
|
||||
# --- Check 14: версия формата формы — как у её дескриптора; сверка с выгрузкой ---
|
||||
# Тело формы и дескриптор Forms/<Имя>.xml (у общей формы — CommonForms/<Имя>.xml) платформа загружает
|
||||
# только в одной версии: «Версия формата загружаемого файла … отличается от версии формата ранее
|
||||
# загруженных файлов». С остальной выгрузкой форма может расходиться — платформа такое грузит, это
|
||||
# лишь неоднородность выгрузки (типично после мержа веток, выгруженных разными платформами).
|
||||
if not stopped:
|
||||
form_ver = root.get('version', '')
|
||||
dump_ver = detect_format_version(os.path.dirname(os.path.abspath(form_path)))
|
||||
owner_path = ext_body_owner(os.path.abspath(form_path))
|
||||
owner_ver = root_version(owner_path) if owner_path else None
|
||||
dump_ver = root_version(dump_anchor) if dump_anchor else None
|
||||
|
||||
if not form_ver:
|
||||
report_ok('14. Format version: not comparable')
|
||||
elif form_ver != dump_ver:
|
||||
report_error(f'14. Format version {form_ver} differs from the dump ({dump_ver}) '
|
||||
'— a dump carries one version, the platform refuses a file it cannot read')
|
||||
else:
|
||||
report_ok(f'14. Format version: {form_ver}, matches the dump')
|
||||
elif owner_ver and form_ver != owner_ver:
|
||||
report_error(f'14. Format version {form_ver} differs from the form descriptor '
|
||||
f'{os.path.basename(owner_path)} ({owner_ver}) '
|
||||
'— the platform refuses to load parts of one object in different formats')
|
||||
elif dump_ver and form_ver != dump_ver:
|
||||
report_warn(f'14. Format version {form_ver} differs from the dump ({dump_ver}) — the platform loads it, '
|
||||
'but the dump is no longer uniform (typical after merging branches dumped by different platforms)')
|
||||
elif owner_ver or dump_ver:
|
||||
report_ok(f'14. Format version: {form_ver}, matches the descriptor and the dump')
|
||||
|
||||
# --- Finalize ---
|
||||
checks = ok_count + errors + warnings
|
||||
|
||||
Reference in New Issue
Block a user