fix(skd-decompile): убрать падения на ERP-отчётах с dataSetLink и StandardPeriod без companions

Два бага PS-версии, незаметно роняли 9/30 отчётов sample30 в decompile-fail:

1. Get-Text без xpath → SelectSingleNode("", $ns) с .NET XPathException
   "Результатом выражения должен быть NodeSet". Шесть call-sites в
   dataSetLinks (sourceDataSet/destinationDataSet/sourceExpression/...)
   передавали уже-выбранный узел без второго аргумента; [string]$xpath
   дефолтился в "". Фикс: Get-Text возвращает $node.InnerText, если
   xpath пустой.

2. $paramByName[$startMatch] при $startMatch=$null → "индекс массива
   вычислен как NULL". Возникает на StandardPeriod-параметре, для
   которого в отчёте нет companion expressions. Фикс: guard через if.

Python-порт #2 уже был защищён .get(); по #1 в py был обходной костыль
inline-через-inner_text — заменён на единый get_text(node) после
обновления сигнатуры до get_text(node, xpath=None).

verify-roundtrip sample30: 9 bit-perfect + 20 with-diff + 1 ring3 = 30
(до фикса 9 silently падали как decompile-fail, сумма не сходилась).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-05-25 12:29:06 +03:00
parent fea2f37ba6
commit 20a243143a
2 changed files with 12 additions and 15 deletions
@@ -1,4 +1,4 @@
# skd-decompile v0.88 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# skd-decompile v0.89 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -248,6 +248,7 @@ function ConvertTo-CompactJson {
function Get-Text {
param($node, [string]$xpath)
if (-not $node) { return $null }
if ([string]::IsNullOrEmpty($xpath)) { return $node.InnerText }
$n = $node.SelectSingleNode($xpath, $ns)
if ($n) { return $n.InnerText } else { return $null }
}
@@ -2713,8 +2714,8 @@ foreach ($p in $paramsRaw) {
# Также НЕ сворачиваем если companions имеют availableAsField=false — compile
# auto-gen не передаёт этот атрибут (ERP-стиль без него; БСП-стиль с ним —
# вариативность не выразима через @autoDates флаг, пусть companions останутся явными).
$beginP = $paramByName[$startMatch]
$endP = $paramByName[$endMatch]
$beginP = if ($startMatch) { $paramByName[$startMatch] } else { $null }
$endP = if ($endMatch) { $paramByName[$endMatch] } else { $null }
$hasNotAField = ($beginP -and $beginP.notAField) -or ($endP -and $endP.notAField)
if ($startMatch -eq 'НачалоПериода' -and $endMatch -eq 'КонецПериода' -and -not $hasNotAField) {
$p['autoDates'] = $true
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# skd-decompile v0.88 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# skd-decompile v0.89 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
@@ -449,9 +449,11 @@ def convert_to_compact_json(obj, depth=0, indent_unit=' ', line_limit=400):
return convert_string_to_json_literal(str(obj))
def get_text(node, xpath):
def get_text(node, xpath=None):
if not node:
return None
if not xpath:
return node.inner_text
n = node.select_single_node(xpath)
if n:
return n.inner_text
@@ -2760,16 +2762,10 @@ def run(root):
dsl_nodes = root.select_nodes("r:dataSetLink")
for dsl_node in dsl_nodes:
link = {}
link['sourceDataSet'] = get_text(dsl_node.select_single_node("r:sourceDataSet"), '.') if False else (dsl_node.select_single_node("r:sourceDataSet").inner_text if dsl_node.select_single_node("r:sourceDataSet") else None)
# PS code uses Get-Text $node (without xpath) — for our wrapper, just read inner_text
s_n = dsl_node.select_single_node("r:sourceDataSet")
d_n = dsl_node.select_single_node("r:destinationDataSet")
se_n = dsl_node.select_single_node("r:sourceExpression")
de_n = dsl_node.select_single_node("r:destinationExpression")
link['sourceDataSet'] = s_n.inner_text if s_n else None
link['destinationDataSet'] = d_n.inner_text if d_n else None
link['sourceExpression'] = se_n.inner_text if se_n else None
link['destinationExpression'] = de_n.inner_text if de_n else None
link['sourceDataSet'] = get_text(dsl_node.select_single_node("r:sourceDataSet"))
link['destinationDataSet'] = get_text(dsl_node.select_single_node("r:destinationDataSet"))
link['sourceExpression'] = get_text(dsl_node.select_single_node("r:sourceExpression"))
link['destinationExpression'] = get_text(dsl_node.select_single_node("r:destinationExpression"))
p_node = dsl_node.select_single_node("r:parameter")
if p_node:
link['parameter'] = p_node.inner_text