diff --git a/.claude/skills/mxl-compile/reference/dsl-spec.md b/.claude/skills/mxl-compile/reference/dsl-spec.md index da7cb268..ce6cd002 100644 --- a/.claude/skills/mxl-compile/reference/dsl-spec.md +++ b/.claude/skills/mxl-compile/reference/dsl-spec.md @@ -111,6 +111,7 @@ | `name` | да | Имя области | | `rows` | \* | Строки: число или диапазон `"N-M"`, 1-based | | `cols` | \* | Колонки: число или диапазон `"N-M"`, 1-based | +| `columnSet` | нет | Колоночная раскладка области. Без ключа выводится из накрытых строк, `""` — привязки нет | \* Обязательна хотя бы одна из осей. diff --git a/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 b/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 index aabab98e..57d8d685 100644 --- a/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 +++ b/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 @@ -1,4 +1,4 @@ -# mxl-compile v1.45 — Compile 1C spreadsheet from JSON +# mxl-compile v1.46 — Compile 1C spreadsheet from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -1703,6 +1703,9 @@ foreach ($layout in $columnLayouts) { $globalRow = 0 $merges = @() $namedItems = @() +# Строка документа → колоночная раскладка. Нужна для вывода привязки именованной области: +# на корпусе она совпадает с раскладкой накрытых строк у 1 021 570 областей из 1 044 339. +$rowColumnSet = @{} $totalRowCount = 0 # Копилка подряд идущих одинаковых пустых строк — пишется одним rowsItem с indexTo. @@ -1938,6 +1941,9 @@ foreach ($area in $def.areas) { # непустые не схлопывает, даже когда они совпадают. Поэтому пустую строку не пишем # сразу, а копим в пробеле и сбрасываем, когда он оборвался. if (-not $rowHasContent) { + # Пустая строка тоже принадлежит раскладке: без этой записи привязка именованной + # области, накрывающей пустые строки, не выводилась. + $rowColumnSet[$globalRow] = $areaColumnSet Add-GapRow -key "$areaColumnSet|$rowFormatIdx" -row $globalRow ` -columnSet $areaColumnSet -formatIdx $rowFormatIdx $localRow++ @@ -1950,6 +1956,7 @@ foreach ($area in $def.areas) { X "`t`t$globalRow" X "`t`t" + $rowColumnSet[$globalRow] = $areaColumnSet if ($areaColumnSet) { X "`t`t`t$areaColumnSet" } @@ -2028,11 +2035,12 @@ foreach ($area in $def.areas) { # Имя на блоке — сахар: разворачиваем его в обычную именованную область типа Rows. if (-not [string]::IsNullOrEmpty($areaName)) { $namedItems += @{ - Name = $areaName - BeginRow = $areaStartRow - EndRow = $areaEndRow - BeginCol = -1 - EndCol = -1 + Name = $areaName + BeginRow = $areaStartRow + EndRow = $areaEndRow + BeginCol = -1 + EndCol = -1 + ColumnSet = $areaColumnSet } } } @@ -2125,13 +2133,38 @@ if ($def.namedAreas) { [Console]::Error.WriteLine("namedAreas: at least one of 'rows'/'cols' is required: $where") exit 1 } + # Привязка к колоночной раскладке: своим ключом либо выводится из накрытых строк. + # Три состояния: ключа нет → выводим из накрытых строк; "" → привязки нет вовсе; + # имя → явная привязка. На корпусе область типа Rows в 13 623 случаях повторяет + # раскладку строк и в 8 179 её не несёт, поэтому вывод обязан переопределяться. + $naSet = '' + $naHasKey = [bool]$na.PSObject.Properties['columnSet'] + if ($naHasKey -and -not "$($na.columnSet)") { + $naSet = '' + } elseif ($naHasKey) { + $naSetName = "$($na.columnSet)" + $naLayout = @($columnLayouts | Where-Object { $_.Name -eq $naSetName })[0] + if (-not $naLayout) { + [Console]::Error.WriteLine("namedAreas: unknown 'columnSet' `"$naSetName`": $where") + exit 1 + } + $naSet = $naLayout.Id + } elseif ($rows) { + $sets = @() + for ($r = $rows.From - 1; $r -le $rows.To - 1; $r++) { + if ($rowColumnSet.ContainsKey($r)) { $sets += $rowColumnSet[$r] } + } + $uniq = @($sets | Select-Object -Unique) + if ($uniq.Count -eq 1) { $naSet = $uniq[0] } + } # DSL 1-based, XML 0-based; отсутствующая ось помечается -1. $namedItems += @{ - Name = $naName - BeginRow = if ($rows) { $rows.From - 1 } else { -1 } - EndRow = if ($rows) { $rows.To - 1 } else { -1 } - BeginCol = if ($cols) { $cols.From - 1 } else { -1 } - EndCol = if ($cols) { $cols.To - 1 } else { -1 } + Name = $naName + BeginRow = if ($rows) { $rows.From - 1 } else { -1 } + EndRow = if ($rows) { $rows.To - 1 } else { -1 } + BeginCol = if ($cols) { $cols.From - 1 } else { -1 } + EndCol = if ($cols) { $cols.To - 1 } else { -1 } + ColumnSet = $naSet } } } @@ -2302,6 +2335,7 @@ foreach ($ni in $sortedNamedItems) { X "`t`t`t$($ni.EndRow)" X "`t`t`t$($ni.BeginCol)" X "`t`t`t$($ni.EndCol)" + if ($ni.ColumnSet) { X "`t`t`t$($ni.ColumnSet)" } X "`t`t" X "`t" } diff --git a/.claude/skills/mxl-compile/scripts/mxl-compile.py b/.claude/skills/mxl-compile/scripts/mxl-compile.py index 4eb60656..eb82be76 100644 --- a/.claude/skills/mxl-compile/scripts/mxl-compile.py +++ b/.claude/skills/mxl-compile/scripts/mxl-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# mxl-compile v1.45 — Compile 1C spreadsheet from JSON +# mxl-compile v1.46 — Compile 1C spreadsheet from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import hashlib @@ -1644,6 +1644,9 @@ def main(): global_row = 0 merges = [] named_items = [] + # Строка документа → колоночная раскладка. Нужна для вывода привязки именованной области: + # на корпусе она совпадает с раскладкой накрытых строк у 1 021 570 областей из 1 044 339. + row_column_set = {} active_rowspans = [] # list of {ColStart, ColEnd, StartLocalRow, EndLocalRow} # Копилка подряд идущих одинаковых пустых строк — пишется одним rowsItem с indexTo. @@ -1857,6 +1860,9 @@ def main(): # непустые не схлопывает, даже когда они совпадают. Поэтому пустую строку не пишем # сразу, а копим в пробеле и сбрасываем, когда он оборвался. if not row_has_content: + # Пустая строка тоже принадлежит раскладке: без этой записи привязка + # именованной области, накрывающей пустые строки, не выводилась. + row_column_set[global_row] = area_column_set add_gap_row(f'{area_column_set}|{row_format_idx}', global_row, area_column_set, row_format_idx) local_row += 1 @@ -1868,6 +1874,7 @@ def main(): lines.append(f'\t\t{global_row}') lines.append('\t\t') + row_column_set[global_row] = area_column_set if area_column_set: lines.append(f'\t\t\t{area_column_set}') @@ -1945,6 +1952,7 @@ def main(): 'EndRow': area_end_row, 'BeginCol': -1, 'EndCol': -1, + 'ColumnSet': area_column_set, }) flush_gap() @@ -2027,6 +2035,23 @@ def main(): if not rows and not cols: print(f'namedAreas: at least one of \'rows\'/\'cols\' is required: {where}', file=sys.stderr) sys.exit(1) + # Привязка к колоночной раскладке. Три состояния: ключа нет → выводим из накрытых + # строк; "" → привязки нет вовсе; имя → явная. На корпусе область типа Rows в 13 623 + # случаях повторяет раскладку строк и в 8 179 её не несёт, поэтому вывод обязан + # переопределяться. + na_set = '' + if 'columnSet' in na: + na_set_name = str(na.get('columnSet') or '') + if na_set_name: + na_layout = next((x for x in column_layouts if x['Name'] == na_set_name), None) + if na_layout is None: + print(f"namedAreas: unknown 'columnSet' \"{na_set_name}\": {where}", file=sys.stderr) + sys.exit(1) + na_set = na_layout['Id'] + elif rows: + covered = {row_column_set[r] for r in range(rows[0] - 1, rows[1]) if r in row_column_set} + if len(covered) == 1: + na_set = covered.pop() # DSL 1-based, XML 0-based; отсутствующая ось помечается -1. named_items.append({ 'Name': na_name, @@ -2034,6 +2059,7 @@ def main(): 'EndRow': rows[1] - 1 if rows else -1, 'BeginCol': cols[0] - 1 if cols else -1, 'EndCol': cols[1] - 1 if cols else -1, + 'ColumnSet': na_set, }) # 7d-ter. Колонтитулы: шесть слотов идут после строк и перед скалярными свойствами документа. @@ -2185,6 +2211,8 @@ def main(): lines.append(f'\t\t\t{ni["EndRow"]}') lines.append(f'\t\t\t{ni.get("BeginCol", -1)}') lines.append(f'\t\t\t{ni.get("EndCol", -1)}') + if ni.get('ColumnSet'): + lines.append(f'\t\t\t{ni["ColumnSet"]}') lines.append('\t\t') lines.append('\t') diff --git a/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 b/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 index 0462d66e..dd849f5d 100644 --- a/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 +++ b/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 @@ -1,4 +1,4 @@ -# mxl-decompile v1.25 — Decompile 1C spreadsheet to JSON +# mxl-decompile v1.26 — Decompile 1C spreadsheet to JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -459,6 +459,9 @@ foreach ($niNode in $root.SelectNodes("d:namedItem", $ns)) { EndRow = & $getCoord 'endRow' BeginCol = & $getCoord 'beginColumn' EndCol = & $getCoord 'endColumn' + # Привязка области к колоночной раскладке: у 913 483 прямоугольных областей корпуса + # она есть, и из накрытых строк выводится не всегда. + ColumnsId = $(if ($areaNode.SelectSingleNode("d:columnsID", $ns)) { $areaNode.SelectSingleNode("d:columnsID", $ns).InnerText } else { '' }) } } @@ -1140,6 +1143,13 @@ foreach ($a in @($namedAreas | Sort-Object @{ Expression = { $_.BeginRow } }, @{ } } if ($fitsBlock) { $fitsBlock = Test-UniformColumnSet $a.BeginRow $a.EndRow } + if ($fitsBlock) { + # Блок задаёт раскладку строкам, и область наследует её же. Когда область несёт ДРУГУЮ + # привязку, блоком её не выразить — уводим в namedAreas, где привязка пишется явным ключом. + $rowSet = Get-RowColumnsId $a.BeginRow + if ($null -eq $rowSet) { $rowSet = '' } + $fitsBlock = ($a.ColumnsId -ceq $rowSet) + } if ($fitsBlock) { for ($r = $a.BeginRow; $r -le $a.EndRow; $r++) { $claimed[$r] = $true } $blockAreas += $a @@ -1561,6 +1571,18 @@ if ($overlayAreas.Count -gt 0) { if ($a.BeginCol -ge 0) { $entry["cols"] = if ($a.EndCol -gt $a.BeginCol) { "$($a.BeginCol + 1)-$($a.EndCol + 1)" } else { $a.BeginCol + 1 } } + # Привязку пишем, только когда она не выводится из накрытых строк. + $derived = '' + if ($a.BeginRow -ge 0) { + $covered = @() + for ($r = $a.BeginRow; $r -le $a.EndRow; $r++) { + $cid = Get-RowColumnsId $r + $covered += $(if ($null -eq $cid) { '' } else { $cid }) + } + $uniq = @($covered | Select-Object -Unique) + if ($uniq.Count -eq 1) { $derived = $uniq[0] } + } + if ($a.ColumnsId -cne $derived) { $entry["columnSet"] = $a.ColumnsId } $naOut += $entry } $result["namedAreas"] = [array]$naOut diff --git a/.claude/skills/mxl-decompile/scripts/mxl-decompile.py b/.claude/skills/mxl-decompile/scripts/mxl-decompile.py index e4861e3b..8f018f7e 100644 --- a/.claude/skills/mxl-decompile/scripts/mxl-decompile.py +++ b/.claude/skills/mxl-decompile/scripts/mxl-decompile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# mxl-decompile v1.25 — Decompile 1C spreadsheet to JSON +# mxl-decompile v1.26 — Decompile 1C spreadsheet to JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -609,6 +609,9 @@ def main(): "EndRow": coord("endRow"), "BeginCol": coord("beginColumn"), "EndCol": coord("endColumn"), + # Привязка области к колоночной раскладке: у 913 483 прямоугольных областей корпуса + # она есть, и из накрытых строк выводится не всегда. + "ColumnsId": text_of(find(area_node, "d:columnsID")) or "", }) # --- 8. Extract rows --- @@ -1130,6 +1133,11 @@ def main(): break if fits: fits = uniform_column_set(a["BeginRow"], a["EndRow"]) + if fits: + # Блок задаёт раскладку строкам, и область наследует её же. Когда область несёт + # ДРУГУЮ привязку, блоком её не выразить — уводим в namedAreas, где привязка пишется + # явным ключом. + fits = a["ColumnsId"] == (row_columns_id(a["BeginRow"]) or "") if fits: claimed.update(range(a["BeginRow"], a["EndRow"] + 1)) block_areas.append(a) @@ -1558,6 +1566,14 @@ def main(): if a["BeginCol"] >= 0: entry["cols"] = (f'{a["BeginCol"] + 1}-{a["EndCol"] + 1}' if a["EndCol"] > a["BeginCol"] else a["BeginCol"] + 1) + # Привязку пишем, только когда она не выводится из накрытых строк. + derived = "" + if a["BeginRow"] >= 0: + covered = {row_columns_id(r) or "" for r in range(a["BeginRow"], a["EndRow"] + 1)} + if len(covered) == 1: + derived = covered.pop() + if a["ColumnsId"] != derived: + entry["columnSet"] = a["ColumnsId"] na_out.append(entry) result["namedAreas"] = na_out diff --git a/docs/1c-spreadsheet-spec.md b/docs/1c-spreadsheet-spec.md index 94d86f28..9427ffd1 100644 --- a/docs/1c-spreadsheet-spec.md +++ b/docs/1c-spreadsheet-spec.md @@ -127,6 +127,11 @@ Типичное применение: сложные печатные формы (УПД, УКД), где шапка/подвал/табличная часть имеют разную разбивку на колонки. +Именованная область тоже ссылается на набор — тегом `` внутри ``, последним. +На корпусе привязку несут 913 483 прямоугольных области, 20 063 полосы строк и 743 полосы колонок; +висячих ссылок нет ни одной. Из накрытых строк она выводится не всегда: у областей типа `Rows` +13 623 повторяют раскладку строк, а 8 179 её не несут при тех же строках. + ## Строки и ячейки ### Строка diff --git a/docs/mxl-dsl-spec.md b/docs/mxl-dsl-spec.md index c7812f12..0ee9bc44 100644 --- a/docs/mxl-dsl-spec.md +++ b/docs/mxl-dsl-spec.md @@ -111,6 +111,7 @@ | `name` | да | Имя области | | `rows` | \* | Строки: число или диапазон `"N-M"`, 1-based | | `cols` | \* | Колонки: число или диапазон `"N-M"`, 1-based | +| `columnSet` | нет | Колоночная раскладка области. Без ключа выводится из накрытых строк, `""` — привязки нет | \* Обязательна хотя бы одна из осей. diff --git a/tests/skills/cases/mxl-compile/named-area-column-set.json b/tests/skills/cases/mxl-compile/named-area-column-set.json new file mode 100644 index 00000000..bbf5741b --- /dev/null +++ b/tests/skills/cases/mxl-compile/named-area-column-set.json @@ -0,0 +1,17 @@ +{ + "name": "Именованная область с привязкой к колоночной раскладке", + "input": { + "columns": 3, + "columnSets": { "узкая": { "columns": 2, "columnWidths": { "1-2": 20 } } }, + "areas": [ + { "name": "Шапка", "rows": [["Заголовок"]] }, + { "name": "Таблица", "columnSet": "узкая", "rows": [["Товар", "Цена"]] } + ], + "namedAreas": [ + { "name": "ПоУзкой", "rows": 2, "cols": "1-2" }, + { "name": "БезПривязки", "rows": 2, "cols": 1, "columnSet": "" }, + { "name": "ЯвнаяПривязка", "rows": 1, "cols": 1, "columnSet": "узкая" } + ] + }, + "params": { "outputPath": "Template.xml" } +} diff --git a/tests/skills/cases/mxl-compile/snapshots/column-sets/Template.xml b/tests/skills/cases/mxl-compile/snapshots/column-sets/Template.xml index c7126c4e..3453cc0d 100644 --- a/tests/skills/cases/mxl-compile/snapshots/column-sets/Template.xml +++ b/tests/skills/cases/mxl-compile/snapshots/column-sets/Template.xml @@ -126,6 +126,7 @@ 1 -1 -1 + 12320174-2b07-3806-9f7f-ec3834007fdb diff --git a/tests/skills/cases/mxl-compile/snapshots/column-styles/Template.xml b/tests/skills/cases/mxl-compile/snapshots/column-styles/Template.xml index 31ca42c5..64d2b722 100644 --- a/tests/skills/cases/mxl-compile/snapshots/column-styles/Template.xml +++ b/tests/skills/cases/mxl-compile/snapshots/column-styles/Template.xml @@ -115,6 +115,7 @@ 1 -1 -1 + 01f06d2d-2103-3169-8cc8-32872f100129 diff --git a/tests/skills/cases/mxl-compile/snapshots/named-area-column-set/Template.xml b/tests/skills/cases/mxl-compile/snapshots/named-area-column-set/Template.xml new file mode 100644 index 00000000..680eb254 --- /dev/null +++ b/tests/skills/cases/mxl-compile/snapshots/named-area-column-set/Template.xml @@ -0,0 +1,138 @@ + + + + ru + ru + + ru + Русский + Русский + + + + 3 + + + 12320174-2b07-3806-9f7f-ec3834007fdb + 2 + + 0 + + 1 + + + + 1 + + 1 + + + + + 0 + + + + 0 + + + ru + Заголовок + + + + + + + + 1 + + 12320174-2b07-3806-9f7f-ec3834007fdb + + + 0 + + + ru + Товар + + + + + + + 0 + + + ru + Цена + + + + + + + true + 2 + 2 + 2 + + БезПривязки + + Rectangle + 1 + 1 + 0 + 0 + + + + ПоУзкой + + Rectangle + 1 + 1 + 0 + 1 + 12320174-2b07-3806-9f7f-ec3834007fdb + + + + Таблица + + Rows + 1 + 1 + -1 + -1 + 12320174-2b07-3806-9f7f-ec3834007fdb + + + + Шапка + + Rows + 0 + 0 + -1 + -1 + + + + ЯвнаяПривязка + + Rectangle + 0 + 0 + 0 + 0 + 12320174-2b07-3806-9f7f-ec3834007fdb + + + + 20 + + + 10 + + \ No newline at end of file