fix(mxl-decompile): абсолютный OutputPath + общий сериализатор декомпиляторов

Безусловная склейка OutputPath с текущим каталогом давала "C:\cwd\C:\out.json" и
роняла запись сообщением про формат пути — навык не умел писать по абсолютному
пути вообще. Py-порт этот случай обрабатывал, ps1 нет. Приведено к общей идиоме
репозитория; кейс на абсолютный путь падает без правки и проходит с ней.

Кейс заодно вскрыл, что порты пишут РАЗНЫЙ JSON: ps1 отдавал стиль ConvertTo-Json
из PS 5.1 (выравнивание ключей, \uXXXX вместо кириллицы), py — json.dumps(indent=2).
Один и тот же макет давал 4708 байт против 1744. Не всплывало потому, что ни один
кейс не снимал сам JSON — все проверяли только stdout.

mxl-decompile был единственным декомпилятором мимо общего сериализатора: у
form/meta/skd-decompile для этого свой ConvertTo-CompactJson. Перенесён вариант
skd-decompile как самый полный, py дополнительно переведён на newline='' —
как у соседей. Теперь порты совпадают байт в байт, а decompile→compile даёт
исходный XML байт в байт.

Семья заведена в check-inline-drift.mjs (4 функции, 3 варианта): раньше шесть
копий жили вообще без гарда. В form-decompile переименована локальная переменная
в string-literal — копии различались только ею.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-10 13:35:50 +03:00
co-authored by Claude Opus 5
parent f33436adff
commit ccbbecbcf2
8 changed files with 387 additions and 27 deletions
@@ -0,0 +1,17 @@
{
"name": "OutputPath принимается и абсолютным",
"preRun": [
{
"script": "mxl-compile/scripts/mxl-compile",
"input": { "columns": 2, "areas": [{ "name": "Test", "rows": [{ "cells": [{ "col": 1, "text": "A" }, { "col": 2, "text": "B" }] }] }] },
"args": { "-JsonPath": "{inputFile}", "-OutputPath": "Template.xml" },
"cwd": "{workDir}"
}
],
"params": { "templatePath": "Template.xml" },
"args_extra": ["-OutputPath", "{workDir}/back.json"],
"expect": {
"files": ["back.json"],
"stdoutContains": "[OK] Decompiled:"
}
}
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<document xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<languageSettings>
<currentLanguage>ru</currentLanguage>
<defaultLanguage>ru</defaultLanguage>
<languageInfo>
<id>ru</id>
<code>Русский</code>
<description>Русский</description>
</languageInfo>
</languageSettings>
<columns>
<size>2</size>
</columns>
<rowsItem>
<index>0</index>
<row>
<c>
<i>0</i>
<c>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>A</v8:content>
</v8:item>
</tl>
</c>
</c>
<c>
<i>1</i>
<c>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>B</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<templateMode>true</templateMode>
<defaultFormatIndex>1</defaultFormatIndex>
<height>1</height>
<vgRows>1</vgRows>
<namedItem xsi:type="NamedItemCells">
<name>Test</name>
<area>
<type>Rows</type>
<beginRow>0</beginRow>
<endRow>0</endRow>
<beginColumn>-1</beginColumn>
<endColumn>-1</endColumn>
</area>
</namedItem>
<font faceName="Arial" height="10" bold="false" italic="false" underline="false" strikeout="false" kind="Absolute" scale="100"/>
<format>
<width>10</width>
</format>
<format>
<font>0</font>
<fillType>Text</fillType>
</format>
</document>
@@ -0,0 +1 @@
{ "columns": 2, "defaultWidth": 10, "fonts": { "default": { "face": "Arial", "size": 10 } }, "styles": {}, "areas": [{ "name": "Test", "rows": [{ "cells": [{ "col": 1, "style": "default", "text": "A" }, { "col": 2, "style": "default", "text": "B" }] }] }] }
+37
View File
@@ -319,6 +319,43 @@ const FAMILIES = [
],
},
// ─── Компактный JSON декомпиляторов ─────────────────────────────────────
// Штатные сериализаторы не годятся: ConvertTo-Json (PS5.1) выравнивает ключи по самому
// длинному и эскейпит кириллицу в \uXXXX, json.dumps даёт иную раскладку inline/multiline.
// Поэтому у декомпиляторов свой сериализатор — и он обязан совпадать в портах байт в байт,
// иначе один и тот же макет даёт разный DSL на разных рантаймах.
{
name: 'decompile json: string literal',
py: 'convert_string_to_json_literal', ps1: 'Convert-StringToJsonLiteral',
variants: [
{ id: 'base', authority: 'skd-decompile', consumers: ['form-decompile', 'mxl-decompile'] }],
},
{
name: 'decompile json: try inline',
py: 'try_inline_json', ps1: 'Try-InlineJson',
variants: [
{ id: 'base', authority: 'skd-decompile', consumers: ['mxl-decompile'] },
{ id: 'no-pscustomobject', authority: 'form-decompile', consumers: [],
why: 'form-decompile строит дерево на ordered-хэштейблах и ветку PSCustomObject не проходит' }],
},
{
// Только в PY: в ps1 числа печатает [System.Convert]::ToString с InvariantCulture прямо
// в теле сериализатора, отдельной функции там нет — ps1: null.
name: 'decompile json: number', py: '_fmt_number', ps1: null,
variants: [
{ id: 'base', authority: 'skd-decompile', consumers: ['mxl-decompile'] }],
},
{
name: 'decompile json: compact',
py: 'convert_to_compact_json', ps1: 'ConvertTo-CompactJson',
variants: [
{ id: 'base', authority: 'skd-decompile', consumers: ['mxl-decompile'] },
{ id: 'line-limit-120', authority: 'form-decompile', consumers: [],
why: 'у форм строки DSL длиннее, порог inline снижен со 400 до 120' },
{ id: 'legacy', authority: 'meta-decompile', consumers: [],
why: 'ранний вариант со своим Quote-Json и без inline-попытки; сведение меняет вывод meta-decompile' }],
},
];
// ─── Семьи, разъехавшиеся целиком ───────────────────────────────────────────