mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-13 15:03:20 +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-compile v1.24 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# mxl-compile v1.25 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -700,6 +700,13 @@ function Expand-ShorthandRow {
|
||||
$cells += $cell
|
||||
$placed[$idx] = $cell
|
||||
$last = $cell
|
||||
# Ячейка занимает СТОЛЬКО позиций, каков её span. У строки со следующими ">" это
|
||||
# получается само (каждый маркер съедает позицию), а объектный элемент несёт span
|
||||
# внутри — без этого сдвига всё правее него уезжало влево.
|
||||
$elSpan = if ($cell.span) { [int]$cell.span } else { 1 }
|
||||
if ($elSpan -gt 1) {
|
||||
for ($s = 1; $s -lt $elSpan; $s++) { $idx++; $placed[$idx] = $cell }
|
||||
}
|
||||
}
|
||||
|
||||
# Колонки, не занятые в этой строке, теряют «ячейку сверху».
|
||||
@@ -709,6 +716,18 @@ function Expand-ShorthandRow {
|
||||
return [PSCustomObject]@{ cells = $cells }
|
||||
}
|
||||
|
||||
# Позиционный список ячеек опознаём по наличию хотя бы одного элемента-строки или null:
|
||||
# маркеры, текст и пропуски бывают только в нём. Список из одних объектов разбирается
|
||||
# как обычный — для простой строки обе трактовки дают один результат, неоднозначности нет.
|
||||
function Test-PositionalCells {
|
||||
param($cells)
|
||||
if ($null -eq $cells -or -not ($cells -is [array])) { return $false }
|
||||
foreach ($el in $cells) {
|
||||
if ($null -eq $el -or $el -is [string]) { return $true }
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
# Карту занятых колонок ведём и по объектным строкам: "|" продолжает ту ячейку,
|
||||
# которая реально стоит выше, независимо от того, какой формой её записали.
|
||||
function Update-OpenByCol {
|
||||
@@ -743,7 +762,14 @@ foreach ($area in $def.areas) {
|
||||
foreach ($row in $area.rows) {
|
||||
$rowIdx++
|
||||
if ($row -is [array]) {
|
||||
# Строка целиком массивом — сахар для { cells: [...] }.
|
||||
$expandedRows += Expand-ShorthandRow -row $row -areaName $areaName -rowIdx $rowIdx -openByCol $openByCol -maxCols $areaMaxCols
|
||||
} elseif (Test-PositionalCells $row.cells) {
|
||||
# Короткая запись — свойство СПИСКА ЯЧЕЕК, а не строки: свои height и rowStyle
|
||||
# строка при этом сохраняет.
|
||||
$expanded = Expand-ShorthandRow -row $row.cells -areaName $areaName -rowIdx $rowIdx -openByCol $openByCol -maxCols $areaMaxCols
|
||||
$row.cells = $expanded.cells
|
||||
$expandedRows += $row
|
||||
} else {
|
||||
$expandedRows += $row
|
||||
if ($row.empty) { $openByCol.Clear() } else { Update-OpenByCol -row $row -openByCol $openByCol }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# mxl-compile v1.24 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# mxl-compile v1.25 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import hashlib
|
||||
@@ -674,7 +674,9 @@ def main():
|
||||
extended = [] # ячейки, чей rowspan уже нарастили в этой строке (span>1 даёт несколько "|")
|
||||
last = None # последняя реальная ячейка слева — цель для ">"
|
||||
|
||||
for idx, el in enumerate(row, start=1):
|
||||
idx = 0
|
||||
for el in row:
|
||||
idx += 1
|
||||
if idx > max_cols:
|
||||
print(f'Row exceeds \'columns\' ({max_cols}): area "{area_name}",'
|
||||
f' row {row_idx}', file=sys.stderr)
|
||||
@@ -729,6 +731,13 @@ def main():
|
||||
cells.append(cell)
|
||||
placed[idx] = cell
|
||||
last = cell
|
||||
# Ячейка занимает СТОЛЬКО позиций, каков её span. У строки со следующими ">" это
|
||||
# получается само (каждый маркер съедает позицию), а объектный элемент несёт span
|
||||
# внутри — без этого сдвига всё правее него уезжало влево.
|
||||
el_span = int(cell.get('span') or 1)
|
||||
for _ in range(el_span - 1):
|
||||
idx += 1
|
||||
placed[idx] = cell
|
||||
|
||||
# Колонки, не занятые в этой строке, теряют «ячейку сверху».
|
||||
open_by_col.clear()
|
||||
@@ -738,6 +747,14 @@ def main():
|
||||
out['cells'] = cells
|
||||
return out
|
||||
|
||||
# Позиционный список ячеек опознаём по наличию хотя бы одного элемента-строки или None:
|
||||
# маркеры, текст и пропуски бывают только в нём. Список из одних объектов разбирается
|
||||
# как обычный — для простой строки обе трактовки дают один результат, неоднозначности нет.
|
||||
def is_positional_cells(cells):
|
||||
if not isinstance(cells, list):
|
||||
return False
|
||||
return any(el is None or isinstance(el, str) for el in cells)
|
||||
|
||||
# Карту занятых колонок ведём и по объектным строкам: "|" продолжает ту ячейку,
|
||||
# которая реально стоит выше, независимо от того, какой формой её записали.
|
||||
def update_open_by_col(row, open_by_col):
|
||||
@@ -767,7 +784,14 @@ def main():
|
||||
expanded_rows = []
|
||||
for row_idx, row in enumerate(area.get('rows', []), start=1):
|
||||
if isinstance(row, list):
|
||||
# Строка целиком массивом — сахар для { cells: [...] }.
|
||||
expanded_rows.append(expand_shorthand_row(row, area_name, row_idx, open_by_col, area_max_cols))
|
||||
elif is_positional_cells(row.get('cells')):
|
||||
# Короткая запись — свойство СПИСКА ЯЧЕЕК, а не строки: свои height и rowStyle
|
||||
# строка при этом сохраняет.
|
||||
expanded = expand_shorthand_row(row['cells'], area_name, row_idx, open_by_col, area_max_cols)
|
||||
row['cells'] = expanded['cells']
|
||||
expanded_rows.append(row)
|
||||
else:
|
||||
expanded_rows.append(row)
|
||||
if row.get('empty'):
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -1 +1 @@
|
||||
{ "columns": 2, "defaultWidth": 10, "fonts": { "default": { "face": "Arial", "size": 10 } }, "styles": {}, "areas": [{ "name": "Test", "rows": [{ "cells": [{ "col": 1, "text": "A" }, { "col": 2, "text": "B" }] }] }] }
|
||||
{ "columns": 2, "defaultWidth": 10, "fonts": { "default": { "face": "Arial", "size": 10 } }, "styles": {}, "areas": [{ "name": "Test", "rows": [["A", "B"]] }] }
|
||||
Reference in New Issue
Block a user