From 20a243143a9c79e056d2d0b7efa06292f5afc310 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Mon, 25 May 2026 12:29:06 +0300 Subject: [PATCH] =?UTF-8?q?fix(skd-decompile):=20=D1=83=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BF=D0=B0=D0=B4=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BD=D0=B0=20ERP-=D0=BE=D1=82=D1=87=D1=91=D1=82=D0=B0=D1=85?= =?UTF-8?q?=20=D1=81=20dataSetLink=20=D0=B8=20StandardPeriod=20=D0=B1?= =?UTF-8?q?=D0=B5=D0=B7=20companions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Два бага 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) --- .../skd-decompile/scripts/skd-decompile.ps1 | 7 ++++--- .../skd-decompile/scripts/skd-decompile.py | 20 ++++++++----------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/.claude/skills/skd-decompile/scripts/skd-decompile.ps1 b/.claude/skills/skd-decompile/scripts/skd-decompile.ps1 index 56f809f6..bbc0c882 100644 --- a/.claude/skills/skd-decompile/scripts/skd-decompile.ps1 +++ b/.claude/skills/skd-decompile/scripts/skd-decompile.ps1 @@ -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 diff --git a/.claude/skills/skd-decompile/scripts/skd-decompile.py b/.claude/skills/skd-decompile/scripts/skd-decompile.py index c38a3f94..e23d5e4e 100644 --- a/.claude/skills/skd-decompile/scripts/skd-decompile.py +++ b/.claude/skills/skd-decompile/scripts/skd-decompile.py @@ -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