mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-20 18:19:43 +03:00
feat(mxl-compile,mxl-decompile): позиционный список ячеек в выводе декомпилятора
Компилятор давно принимал короткую форму, а декомпилятор всегда писал самую развёрнутую. Теперь он выбирает самую короткую из применимых, как это делает skd-decompile. Короткая запись стала свойством СПИСКА ЯЧЕЕК, а не строки: cells принимает позиционный список, поэтому строка с height или rowStyle тоже может им пользоваться. Раньше наличие свойств строки лишало её короткой записи — искусственная связка. Если же своих свойств у строки нет, она пишется просто массивом, без ключа cells. Позиционный список опознаётся по наличию элемента-строки или null; список из одних объектов читается как обычный — для простой строки обе трактовки дают один результат. Три дефекта, вскрытых проверкой на потери (каждый раз XML переставал совпадать с прежним, и это приводило к причине): - компилятор двигал позицию на единицу за элемент, поэтому объектный элемент со span: 3 занимал одну позицию вместо трёх и всё правее него уезжало влево. У маркеров ">" этого не было — каждый съедает свою позицию; - проход «удалить неиспользуемые стили» не заглядывал в строки-массивы: у массива нет свойства cells, и стиль, использованный только внутри такой строки, вырезался как неиспользуемый; - в py тот же проход падал на позиционном списке — "style" in c для None бросает TypeError, а для строки делает подстрочный поиск. Плюс ловушка PowerShell: += разворачивает вложенный массив, и строка-массив расплющивалась в список строк — нужна запятая. Проверено: XML на всех 40 макетах пилота совпал с прежним БАЙТ В БАЙТ, то есть сокращение без потерь. Объём декомпилированного JSON меньше на 11%. Тесты 54/54 на обоих рантаймах, гарды зелёные. В WORKFLOW уточнена находка про паритет портов: декомпиляторы расходятся по JSON на 33 макетах из 40, а компиляторы на одном и том же входе — всего на 2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a9697b613d
commit
4cceb03269
@@ -1,4 +1,4 @@
|
||||
# mxl-decompile v1.7 — Decompile 1C spreadsheet to JSON
|
||||
# mxl-decompile v1.8 — Decompile 1C spreadsheet to JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -480,6 +480,54 @@ function Get-StyleName {
|
||||
return "default"
|
||||
}
|
||||
|
||||
# Список признаётся позиционным по тому же правилу, что и в компиляторе: есть элемент-строка
|
||||
# или пропуск. Список из одних объектов таковым не считаем — он читается как обычный.
|
||||
function Test-PositionalList {
|
||||
param($cells)
|
||||
foreach ($el in $cells) {
|
||||
if ($null -eq $el -or $el -is [string]) { return $true }
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
# Позиционная запись списка ячеек: позиция берётся из порядка, `col` не пишется.
|
||||
# Применяем, когда первая ячейка стоит в колонке 1 — иначе список начнётся с череды null
|
||||
# и станет длиннее объектного. Ячейка, у которой кроме текста или параметра ничего нет,
|
||||
# пишется строкой; span раскрывается маркерами ">"; всё прочее — объектным элементом без col.
|
||||
function ConvertTo-PositionalCells {
|
||||
param($cells)
|
||||
if ($cells.Count -eq 0) { return @() }
|
||||
$first = $cells[0]
|
||||
if ([int]$first["col"] -ne 1) { return $cells }
|
||||
|
||||
$out = @()
|
||||
$expected = 1
|
||||
foreach ($c in $cells) {
|
||||
$col = [int]$c["col"]
|
||||
if ($col -lt $expected) { return $cells } # перекрытие — позиционно не выразить
|
||||
while ($expected -lt $col) { $out += $null; $expected++ }
|
||||
|
||||
$span = if ($c.Contains("span")) { [int]$c["span"] } else { 1 }
|
||||
$keys = @($c.Keys | Where-Object { $_ -notin @("col", "span") })
|
||||
$plainText = ($keys.Count -eq 1 -and $keys[0] -eq "text" -and $c["text"] -is [string])
|
||||
$plainParam = ($keys.Count -eq 1 -and $keys[0] -eq "param")
|
||||
|
||||
if ($plainText) { $out += $c["text"] }
|
||||
elseif ($plainParam) { $out += "{$($c["param"])}" }
|
||||
else {
|
||||
$obj = [ordered]@{}
|
||||
foreach ($k in $c.Keys) { if ($k -ne "col") { $obj[$k] = $c[$k] } }
|
||||
$out += $obj
|
||||
}
|
||||
# span раскрываем маркерами — кроме объектного элемента, он несёт span сам.
|
||||
if (($plainText -or $plainParam) -and $span -gt 1) {
|
||||
for ($i = 1; $i -lt $span; $i++) { $out += ">" }
|
||||
}
|
||||
$expected = $col + $span
|
||||
}
|
||||
return $out
|
||||
}
|
||||
|
||||
# --- 12. Build areas ---
|
||||
|
||||
# Сетка нарезается на блоки: непересекающиеся области типа Rows задают границы, строки вне
|
||||
@@ -664,8 +712,14 @@ foreach ($area in $blocks) {
|
||||
$dslCells += $dslCell
|
||||
}
|
||||
|
||||
if ($dslCells.Count -gt 0) { $dslRow["cells"] = [array]$dslCells }
|
||||
$areaRows += $dslRow
|
||||
if ($dslCells.Count -gt 0) { $dslRow["cells"] = [array](ConvertTo-PositionalCells $dslCells) }
|
||||
# Самая короткая из применимых форм: если у строки нет своих свойств, а список ячеек
|
||||
# позиционный — строка пишется просто массивом, без ключа cells.
|
||||
if ($dslRow.Count -eq 1 -and $dslRow.Contains("cells") -and (Test-PositionalList $dslRow["cells"])) {
|
||||
$areaRows += ,([array]$dslRow["cells"])
|
||||
} else {
|
||||
$areaRows += $dslRow
|
||||
}
|
||||
}
|
||||
|
||||
# Compress consecutive empty rows ({}) into { empty = N }
|
||||
@@ -680,7 +734,8 @@ foreach ($area in $blocks) {
|
||||
else { $compressedRows += [ordered]@{ empty = $emptyRun } }
|
||||
$emptyRun = 0
|
||||
}
|
||||
$compressedRows += $r
|
||||
# Запятая обязательна: строка-массив иначе развернётся в список строк.
|
||||
$compressedRows += ,$r
|
||||
}
|
||||
}
|
||||
if ($emptyRun -gt 0) {
|
||||
@@ -755,8 +810,11 @@ if ($styleDefs.Contains("default") -and $styleDefs["default"].Count -eq 0) {
|
||||
$usedStyles = @{}
|
||||
foreach ($a in $dslAreas) {
|
||||
foreach ($r in $a.rows) {
|
||||
if ($r.rowStyle) { $usedStyles[$r.rowStyle] = $true }
|
||||
if ($r.cells) { foreach ($c in $r.cells) { if ($c.style) { $usedStyles[$c.style] = $true } } }
|
||||
# Строка может быть массивом (короткая форма) — у неё нет свойства cells, и без этой
|
||||
# ветки стиль, использованный только внутри такой строки, вырезался как «неиспользуемый».
|
||||
$cellList = if ($r -is [array]) { $r } else { $r.cells }
|
||||
if ($r -isnot [array] -and $r.rowStyle) { $usedStyles[$r.rowStyle] = $true }
|
||||
if ($cellList) { foreach ($c in $cellList) { if ($c -isnot [string] -and $c.style) { $usedStyles[$c.style] = $true } } }
|
||||
}
|
||||
}
|
||||
$toRemove = @($styleDefs.Keys | Where-Object { -not $usedStyles.ContainsKey($_) })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# mxl-decompile v1.7 — Decompile 1C spreadsheet to JSON
|
||||
# mxl-decompile v1.8 — Decompile 1C spreadsheet to JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -667,6 +667,39 @@ def main():
|
||||
return style_names[key]
|
||||
return "default"
|
||||
|
||||
def to_positional_cells(cells):
|
||||
"""Позиционная запись списка ячеек: позиция берётся из порядка, `col` не пишется.
|
||||
Применяем, когда первая ячейка стоит в колонке 1 — иначе список начнётся с череды None
|
||||
и станет длиннее объектного. Ячейка, у которой кроме текста или параметра ничего нет,
|
||||
пишется строкой; span раскрывается маркерами ">"; прочее — объектным элементом без col."""
|
||||
if not cells or int(cells[0].get("col", 0)) != 1:
|
||||
return cells
|
||||
out = []
|
||||
expected = 1
|
||||
for c in cells:
|
||||
col = int(c.get("col", 0))
|
||||
if col < expected:
|
||||
return cells # перекрытие — позиционно не выразить
|
||||
while expected < col:
|
||||
out.append(None)
|
||||
expected += 1
|
||||
span = int(c.get("span", 1) or 1)
|
||||
keys = [k for k in c if k not in ("col", "span")]
|
||||
plain_text = len(keys) == 1 and keys[0] == "text" and isinstance(c["text"], str)
|
||||
plain_param = len(keys) == 1 and keys[0] == "param"
|
||||
if plain_text:
|
||||
out.append(c["text"])
|
||||
elif plain_param:
|
||||
out.append("{%s}" % c["param"])
|
||||
else:
|
||||
obj = OrderedDict((k, v) for k, v in c.items() if k != "col")
|
||||
out.append(obj)
|
||||
# span раскрываем маркерами — кроме объектного элемента, он несёт span сам.
|
||||
if (plain_text or plain_param) and span > 1:
|
||||
out.extend([">"] * (span - 1))
|
||||
expected = col + span
|
||||
return out
|
||||
|
||||
# --- 12. Build areas ---
|
||||
|
||||
# Сетка нарезается на блоки: непересекающиеся области типа Rows задают границы, строки вне
|
||||
@@ -831,8 +864,14 @@ def main():
|
||||
dsl_cells.append(dsl_cell)
|
||||
|
||||
if len(dsl_cells) > 0:
|
||||
dsl_row["cells"] = dsl_cells
|
||||
area_rows.append(dsl_row)
|
||||
dsl_row["cells"] = to_positional_cells(dsl_cells)
|
||||
# Самая короткая из применимых форм: если у строки нет своих свойств, а список ячеек
|
||||
# позиционный — строка пишется просто массивом, без ключа cells.
|
||||
if (len(dsl_row) == 1 and "cells" in dsl_row
|
||||
and any(el is None or isinstance(el, str) for el in dsl_row["cells"])):
|
||||
area_rows.append(dsl_row["cells"])
|
||||
else:
|
||||
area_rows.append(dsl_row)
|
||||
|
||||
# Compress consecutive empty rows ({}) into { empty = N }
|
||||
compressed_rows = []
|
||||
@@ -933,12 +972,19 @@ def main():
|
||||
used_styles = set()
|
||||
for a in dsl_areas:
|
||||
for r in a["rows"]:
|
||||
if "rowStyle" in r:
|
||||
used_styles.add(r["rowStyle"])
|
||||
if "cells" in r:
|
||||
for c in r["cells"]:
|
||||
if "style" in c:
|
||||
used_styles.add(c["style"])
|
||||
# Строка может быть массивом (короткая форма) — у неё нет ключа cells, и без этой
|
||||
# ветки стиль, использованный только внутри такой строки, вырезался как «неиспользуемый».
|
||||
if isinstance(r, list):
|
||||
cell_list = r
|
||||
else:
|
||||
if "rowStyle" in r:
|
||||
used_styles.add(r["rowStyle"])
|
||||
cell_list = r.get("cells") or []
|
||||
# Список ячеек может быть позиционным: строки, None и маркеры стиля не несут,
|
||||
# стиль бывает только у объектного элемента.
|
||||
for c in cell_list:
|
||||
if isinstance(c, dict) and "style" in c:
|
||||
used_styles.add(c["style"])
|
||||
to_remove = [s for s in style_defs if s not in used_styles]
|
||||
for s in to_remove:
|
||||
del style_defs[s]
|
||||
|
||||
Reference in New Issue
Block a user