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