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): колоночные раскладки
Инкремент B кампании mxl-roundtrip. Табличный документ может задавать индивидуальные
ширины колонок для группы строк — в XML это несколько элементов <columns>, из которых
раскладка БЕЗ <id> является умолчанием (ровно одна в каждом макете корпуса), а остальные
адресуются GUID, на который ссылаются строки и области. Декомпилятор читал только первый
<columns>: ширины прочих раскладок терялись, а на макетах с пустым умолчанием он отдавал
columns: 0. Несколько раскладок есть у 63% макетов ERP.
DSL: документные columns/columnWidths — раскладка по умолчанию, дополнительные объявляются
в columnSets (ключ — идентификатор), область ссылается ключом columnSet. Форма повторяет
уже принятую в этом DSL пару «словарь именованных штук наверху — ссылка по имени на
потребителе», как styles/style и fonts/font.
Склейки раскладок по содержимому НЕТ, и это исправление собственной ошибки: замер, на
котором строилось прежнее решение, вырезал идентификатор через findtext('columnsID'),
тогда как у набора он называется <id> (columnsID — имя только у ссылок). Вырезание не
срабатывало, и наборы «различались» просто из-за разных GUID. Правильный замер: в 337
макетах из 517 есть наборы с полностью одинаковым содержимым, до 59 дублей в одном
макете. Содержимое раскладку не опознаёт — опознаёт идентификатор.
Попутно закрыты два дефекта, вскрытые корпусом:
- проверка обязательных полей смотрела на истинность значения, а не на наличие ключа:
columns: 0 — осмысленная величина (умолчание пустое, все строки в именованных
раскладках), и компилятор объявлял её отсутствующей. Тот же класс, что был с col;
- позиции колонок сверялись с документным columns, тогда как ширина сетки у каждой
раскладки своя. Проверки диапазона col, автопотока и короткой формы переведены на
ширину раскладки области.
На пилоте из 40 макетов ERP отказов больше нет ни одного (было 26): цикл проходят все 40
на обоих рантаймах. Скомпилированный XML портов совпадает байт в байт.
Записана находка (debug/mxl-roundtrip/WORKFLOW.md): порты mxl-decompile расходятся по
выходному JSON на 11 макетах из 12 — порядок стилей в словаре, при идентичном XML.
Не ловилось потому, что кейсы снэпшотят входной Template.xml, а не выходной JSON.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d002de3a26
commit
a1642cbe60
@@ -1,4 +1,4 @@
|
||||
# mxl-compile v1.18 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# mxl-compile v1.19 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -192,11 +192,15 @@ if (-not (Test-Path $JsonPath)) {
|
||||
$json = Get-Content -Raw -Encoding UTF8 $JsonPath
|
||||
$def = $json | ConvertFrom-Json
|
||||
|
||||
if (-not $def.columns) {
|
||||
# Проверяем НАЛИЧИЕ ключа, а не истинность значения: `columns: 0` — осмысленная величина
|
||||
# (раскладка по умолчанию пустая, все строки живут в именованных раскладках), а пустой
|
||||
# список областей встречается у макета без строк. Прежняя проверка `-not` объявляла и то
|
||||
# и другое отсутствующим.
|
||||
if (-not $def.PSObject.Properties['columns'] -or $null -eq $def.columns) {
|
||||
Write-Error "Required field 'columns' is missing"
|
||||
exit 1
|
||||
}
|
||||
if (-not $def.areas) {
|
||||
if (-not $def.PSObject.Properties['areas'] -or $null -eq $def.areas) {
|
||||
Write-Error "Required field 'areas' is missing"
|
||||
exit 1
|
||||
}
|
||||
@@ -363,18 +367,40 @@ if ($def.page) {
|
||||
}
|
||||
|
||||
# Build column width map: 1-based col -> width
|
||||
$colWidthMap = @{}
|
||||
if ($def.columnWidths) {
|
||||
foreach ($prop in $def.columnWidths.PSObject.Properties) {
|
||||
$val = "$($prop.Value)"
|
||||
if ($val -match '^([0-9.]+)x$') {
|
||||
$width = [int][math]::Round([double]$Matches[1] * $defaultWidth)
|
||||
} else {
|
||||
$width = [int]$val
|
||||
function Build-ColWidthMap {
|
||||
param($widths)
|
||||
$map = @{}
|
||||
if ($widths) {
|
||||
foreach ($prop in $widths.PSObject.Properties) {
|
||||
$val = "$($prop.Value)"
|
||||
if ($val -match '^([0-9.]+)x$') {
|
||||
$width = [int][math]::Round([double]$Matches[1] * $defaultWidth)
|
||||
} else {
|
||||
$width = [int]$val
|
||||
}
|
||||
foreach ($c in (Parse-ColumnSpec $prop.Name)) { $map[$c] = $width }
|
||||
}
|
||||
$columns = Parse-ColumnSpec $prop.Name
|
||||
foreach ($c in $columns) {
|
||||
$colWidthMap[$c] = $width
|
||||
}
|
||||
return $map
|
||||
}
|
||||
|
||||
$colWidthMap = Build-ColWidthMap $def.columnWidths
|
||||
|
||||
# Колоночные раскладки: документные columns/columnWidths — раскладка по умолчанию (в XML
|
||||
# элемент <columns> БЕЗ <id>, он всегда идёт первым). Дополнительные объявляются в
|
||||
# columnSets, ключ — идентификатор, на него ссылается область ключом columnSet.
|
||||
# Склейки по содержимому нет: в корпусе полно раскладок с одинаковым содержимым и разными
|
||||
# идентификаторами, поэтому опознаёт раскладку только идентификатор.
|
||||
$columnLayouts = @()
|
||||
$columnLayouts += @{ Id = $null; Size = $totalColumns; Widths = $colWidthMap }
|
||||
if ($def.columnSets) {
|
||||
foreach ($prop in $def.columnSets.PSObject.Properties) {
|
||||
$cs = $prop.Value
|
||||
$size = if ($cs.columns) { [int]$cs.columns } else { $totalColumns }
|
||||
$columnLayouts += @{
|
||||
Id = $prop.Name
|
||||
Size = $size
|
||||
Widths = Build-ColWidthMap $cs.columnWidths
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -482,14 +508,17 @@ function Register-Format {
|
||||
$defaultFormatKey = Get-FormatKey -width $defaultWidth
|
||||
$defaultFormatIndex = Register-Format -key $defaultFormatKey -props @{ Width = $defaultWidth }
|
||||
|
||||
# 6b. Column width formats
|
||||
$colFormatMap = @{} # 1-based col -> format index
|
||||
foreach ($col in ($colWidthMap.Keys | Sort-Object)) {
|
||||
$w = $colWidthMap[$col]
|
||||
$key = Get-FormatKey -width $w
|
||||
$idx = Register-Format -key $key -props @{ Width = $w }
|
||||
$colFormatMap[[int]$col] = $idx
|
||||
# 6b. Column width formats — по одной карте на каждую колоночную раскладку
|
||||
foreach ($layout in $columnLayouts) {
|
||||
$map = @{} # 1-based col -> format index
|
||||
foreach ($col in ($layout.Widths.Keys | Sort-Object)) {
|
||||
$w = $layout.Widths[$col]
|
||||
$key = Get-FormatKey -width $w
|
||||
$map[[int]$col] = Register-Format -key $key -props @{ Width = $w }
|
||||
}
|
||||
$layout.FormatMap = $map
|
||||
}
|
||||
$colFormatMap = $columnLayouts[0].FormatMap
|
||||
|
||||
# 6c. Scan areas for row heights and cell formats
|
||||
# We need to do two passes: first collect all formats, then generate XML
|
||||
@@ -551,7 +580,7 @@ function Set-CellProp {
|
||||
}
|
||||
|
||||
function Expand-ShorthandRow {
|
||||
param($row, [string]$areaName, [int]$rowIdx, $openByCol)
|
||||
param($row, [string]$areaName, [int]$rowIdx, $openByCol, [int]$maxCols)
|
||||
|
||||
$cells = @()
|
||||
$placed = @{} # 1-based col -> ячейка, занимающая колонку в ЭТОЙ строке
|
||||
@@ -563,8 +592,8 @@ function Expand-ShorthandRow {
|
||||
$idx++
|
||||
# Внутри функции пишем в stderr напрямую: Write-Error приписал бы к сообщению имя
|
||||
# функции, и текст перестал бы совпадать с py-портом.
|
||||
if ($idx -gt $totalColumns) {
|
||||
[Console]::Error.WriteLine("Row exceeds 'columns' ($totalColumns): area `"$areaName`", row $rowIdx")
|
||||
if ($idx -gt $maxCols) {
|
||||
[Console]::Error.WriteLine("Row exceeds 'columns' ($maxCols): area `"$areaName`", row $rowIdx")
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -647,13 +676,19 @@ function Update-OpenByCol {
|
||||
|
||||
foreach ($area in $def.areas) {
|
||||
$areaName = $area.name
|
||||
# Ширина сетки берётся из раскладки области: у каждой она своя.
|
||||
$areaMaxCols = $totalColumns
|
||||
if ($area.PSObject.Properties['columnSet'] -and "$($area.columnSet)" -ne '') {
|
||||
$lay = @($columnLayouts | Where-Object { $_.Id -eq "$($area.columnSet)" })[0]
|
||||
if ($lay) { $areaMaxCols = [int]$lay.Size }
|
||||
}
|
||||
$openByCol = @{}
|
||||
$rowIdx = 0
|
||||
$expandedRows = @()
|
||||
foreach ($row in $area.rows) {
|
||||
$rowIdx++
|
||||
if ($row -is [array]) {
|
||||
$expandedRows += Expand-ShorthandRow -row $row -areaName $areaName -rowIdx $rowIdx -openByCol $openByCol
|
||||
$expandedRows += Expand-ShorthandRow -row $row -areaName $areaName -rowIdx $rowIdx -openByCol $openByCol -maxCols $areaMaxCols
|
||||
} else {
|
||||
$expandedRows += $row
|
||||
if ($row.empty) { $openByCol.Clear() } else { Update-OpenByCol -row $row -openByCol $openByCol }
|
||||
@@ -721,23 +756,27 @@ X "`t`t</languageInfo>"
|
||||
X "`t</languageSettings>"
|
||||
|
||||
# 7c. Columns
|
||||
X "`t<columns>"
|
||||
X "`t`t<size>$totalColumns</size>"
|
||||
# Раскладка по умолчанию идёт первой и без <id> — так их хранит платформа.
|
||||
foreach ($layout in $columnLayouts) {
|
||||
X "`t<columns>"
|
||||
if ($layout.Id) { X "`t`t<id>$($layout.Id)</id>" }
|
||||
X "`t`t<size>$($layout.Size)</size>"
|
||||
|
||||
# Emit columnsItem for columns with non-default widths
|
||||
foreach ($col in ($colFormatMap.Keys | Sort-Object)) {
|
||||
$fmtIdx = $colFormatMap[$col]
|
||||
$colIdx = $col - 1 # Convert to 0-based
|
||||
X "`t`t<columnsItem>"
|
||||
X "`t`t`t<index>$colIdx</index>"
|
||||
X "`t`t`t<column>"
|
||||
X "`t`t`t`t<formatIndex>$fmtIdx</formatIndex>"
|
||||
X "`t`t`t</column>"
|
||||
X "`t`t</columnsItem>"
|
||||
# Emit columnsItem for columns with non-default widths
|
||||
foreach ($col in ($layout.FormatMap.Keys | Sort-Object)) {
|
||||
$fmtIdx = $layout.FormatMap[$col]
|
||||
$colIdx = $col - 1 # Convert to 0-based
|
||||
X "`t`t<columnsItem>"
|
||||
X "`t`t`t<index>$colIdx</index>"
|
||||
X "`t`t`t<column>"
|
||||
X "`t`t`t`t<formatIndex>$fmtIdx</formatIndex>"
|
||||
X "`t`t`t</column>"
|
||||
X "`t`t</columnsItem>"
|
||||
}
|
||||
|
||||
X "`t</columns>"
|
||||
}
|
||||
|
||||
X "`t</columns>"
|
||||
|
||||
# 7d. Rows — main generation loop
|
||||
$globalRow = 0
|
||||
$merges = @()
|
||||
@@ -749,6 +788,19 @@ foreach ($area in $def.areas) {
|
||||
$areaName = $area.name
|
||||
$activeRowspans = @() # @{ColStart=1-based; ColEnd=1-based; EndLocalRow=int}
|
||||
$localRow = 0
|
||||
# Ссылка области на колоночную раскладку — её получают все строки области.
|
||||
$areaColumnSet = if ($area.PSObject.Properties['columnSet']) { "$($area.columnSet)" } else { '' }
|
||||
$areaLayout = $columnLayouts[0]
|
||||
if ($areaColumnSet) {
|
||||
$areaLayout = @($columnLayouts | Where-Object { $_.Id -eq $areaColumnSet })[0]
|
||||
if (-not $areaLayout) {
|
||||
[Console]::Error.WriteLine("Unknown 'columnSet': `"$areaColumnSet`" is not declared in columnSets")
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
# Ширина сетки — у КАЖДОЙ раскладки своя, поэтому позиции колонок сверяем с ней,
|
||||
# а не с документным columns (у макетов с раскладками умолчание бывает и пустым).
|
||||
$areaColumns = [int]$areaLayout.Size
|
||||
|
||||
foreach ($row in $area.rows) {
|
||||
# Empty row placeholder: emit N empty rows
|
||||
@@ -813,8 +865,8 @@ foreach ($area in $def.areas) {
|
||||
if ($isFree) { break }
|
||||
$cursor++
|
||||
}
|
||||
if (($cursor + $colSpan - 1) -gt $totalColumns) {
|
||||
Write-Error "Row exceeds 'columns' ($totalColumns): area `"$areaName`", row $($localRow + 1)"
|
||||
if (($cursor + $colSpan - 1) -gt $areaColumns) {
|
||||
Write-Error "Row exceeds 'columns' ($areaColumns): area `"$areaName`", row $($localRow + 1)"
|
||||
exit 1
|
||||
}
|
||||
$cell | Add-Member -NotePropertyName col -NotePropertyValue $cursor -Force
|
||||
@@ -831,7 +883,7 @@ foreach ($area in $def.areas) {
|
||||
foreach ($cell in $row.cells) {
|
||||
$cellIdx++
|
||||
$colParsed = 0
|
||||
if (-not [int]::TryParse("$($cell.col)", [ref]$colParsed) -or $colParsed -lt 1 -or $colParsed -gt $totalColumns) {
|
||||
if (-not [int]::TryParse("$($cell.col)", [ref]$colParsed) -or $colParsed -lt 1 -or $colParsed -gt $areaColumns) {
|
||||
Write-Error "Invalid 'col' value `"$($cell.col)`": area `"$areaName`", row $($localRow + 1), cell $cellIdx"
|
||||
exit 1
|
||||
}
|
||||
@@ -927,6 +979,10 @@ foreach ($area in $def.areas) {
|
||||
X "`t`t<index>$globalRow</index>"
|
||||
X "`t`t<row>"
|
||||
|
||||
if ($areaColumnSet) {
|
||||
X "`t`t`t<columnsID>$areaColumnSet</columnsID>"
|
||||
}
|
||||
|
||||
if ($rowFormatIdx -gt 0) {
|
||||
X "`t`t`t<formatIndex>$rowFormatIdx</formatIndex>"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# mxl-compile v1.18 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# mxl-compile v1.19 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import json
|
||||
@@ -348,10 +348,14 @@ def main():
|
||||
with open(json_path, 'r', encoding='utf-8-sig') as f:
|
||||
defn = ci_json(json.load(f))
|
||||
|
||||
if not defn.get('columns'):
|
||||
# Проверяем НАЛИЧИЕ ключа, а не истинность значения: `columns: 0` — осмысленная величина
|
||||
# (раскладка по умолчанию пустая, все строки живут в именованных раскладках), а пустой
|
||||
# список областей встречается у макета без строк. Прежняя проверка объявляла и то
|
||||
# и другое отсутствующим.
|
||||
if 'columns' not in defn or defn.get('columns') is None:
|
||||
print("Required field 'columns' is missing", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not defn.get('areas'):
|
||||
if 'areas' not in defn or defn.get('areas') is None:
|
||||
print("Required field 'areas' is missing", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -473,18 +477,32 @@ def main():
|
||||
default_width = round((target_width - absolute_sum) / total_units)
|
||||
|
||||
# Build column width map: 1-based col -> width
|
||||
col_width_map = {}
|
||||
if defn.get('columnWidths'):
|
||||
for prop_name, prop_value in defn['columnWidths'].items():
|
||||
val = str(prop_value)
|
||||
m = re.match(r'^([0-9.]+)x$', val)
|
||||
if m:
|
||||
width = round(float(m.group(1)) * default_width)
|
||||
else:
|
||||
width = int(val)
|
||||
columns = parse_column_spec(prop_name)
|
||||
for c in columns:
|
||||
col_width_map[c] = width
|
||||
def build_col_width_map(widths):
|
||||
out = {}
|
||||
if widths:
|
||||
for prop_name, prop_value in widths.items():
|
||||
val = str(prop_value)
|
||||
m = re.match(r'^([0-9.]+)x$', val)
|
||||
width = round(float(m.group(1)) * default_width) if m else int(val)
|
||||
for c in parse_column_spec(prop_name):
|
||||
out[c] = width
|
||||
return out
|
||||
|
||||
col_width_map = build_col_width_map(defn.get('columnWidths'))
|
||||
|
||||
# Колоночные раскладки: документные columns/columnWidths — раскладка по умолчанию (в XML
|
||||
# элемент <columns> БЕЗ <id>, он всегда идёт первым). Дополнительные объявляются в
|
||||
# columnSets, ключ — идентификатор, на него ссылается область ключом columnSet.
|
||||
# Склейки по содержимому нет: в корпусе полно раскладок с одинаковым содержимым и разными
|
||||
# идентификаторами, поэтому опознаёт раскладку только идентификатор.
|
||||
column_layouts = [{'Id': None, 'Size': total_columns, 'Widths': col_width_map}]
|
||||
for set_id, cs in (defn.get('columnSets') or {}).items():
|
||||
size = int(cs['columns']) if cs.get('columns') is not None else total_columns
|
||||
column_layouts.append({
|
||||
'Id': set_id,
|
||||
'Size': size,
|
||||
'Widths': build_col_width_map(cs.get('columnWidths')),
|
||||
})
|
||||
|
||||
# --- 5. Style resolver ---
|
||||
def resolve_style(style_name, fill_type):
|
||||
@@ -560,13 +578,14 @@ def main():
|
||||
default_format_key = get_format_key(width=default_width)
|
||||
default_format_index = register_format(default_format_key, {'Width': default_width})
|
||||
|
||||
# 6b. Column width formats
|
||||
col_format_map = {} # 1-based col -> format index
|
||||
for col in sorted(col_width_map):
|
||||
w = col_width_map[col]
|
||||
key = get_format_key(width=w)
|
||||
idx = register_format(key, {'Width': w})
|
||||
col_format_map[int(col)] = idx
|
||||
# 6b. Column width formats — по одной карте на каждую колоночную раскладку
|
||||
for layout in column_layouts:
|
||||
fmap = {} # 1-based col -> format index
|
||||
for col in sorted(layout['Widths']):
|
||||
w = layout['Widths'][col]
|
||||
fmap[int(col)] = register_format(get_format_key(width=w), {'Width': w})
|
||||
layout['FormatMap'] = fmap
|
||||
col_format_map = column_layouts[0]['FormatMap']
|
||||
|
||||
# 6c. Helper: determine fillType from cell content
|
||||
def get_fill_type(cell):
|
||||
@@ -604,15 +623,15 @@ def main():
|
||||
# "{Имя}" — параметр. Разворачиваем в обычную строку с явными col/span/rowspan,
|
||||
# поэтому весь код ниже про шорткат не знает.
|
||||
|
||||
def expand_shorthand_row(row, area_name, row_idx, open_by_col):
|
||||
def expand_shorthand_row(row, area_name, row_idx, open_by_col, max_cols):
|
||||
cells = []
|
||||
placed = {} # 1-based col -> ячейка, занимающая колонку в ЭТОЙ строке
|
||||
extended = [] # ячейки, чей rowspan уже нарастили в этой строке (span>1 даёт несколько "|")
|
||||
last = None # последняя реальная ячейка слева — цель для ">"
|
||||
|
||||
for idx, el in enumerate(row, start=1):
|
||||
if idx > total_columns:
|
||||
print(f'Row exceeds \'columns\' ({total_columns}): area "{area_name}",'
|
||||
if idx > max_cols:
|
||||
print(f'Row exceeds \'columns\' ({max_cols}): area "{area_name}",'
|
||||
f' row {row_idx}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -693,11 +712,17 @@ def main():
|
||||
|
||||
for area in defn['areas']:
|
||||
area_name = area.get('name', '')
|
||||
# Ширина сетки берётся из раскладки области: у каждой она своя.
|
||||
area_max_cols = total_columns
|
||||
if area.get('columnSet'):
|
||||
lay = next((x for x in column_layouts if x['Id'] == str(area['columnSet'])), None)
|
||||
if lay:
|
||||
area_max_cols = int(lay['Size'])
|
||||
open_by_col = {}
|
||||
expanded_rows = []
|
||||
for row_idx, row in enumerate(area.get('rows', []), start=1):
|
||||
if isinstance(row, list):
|
||||
expanded_rows.append(expand_shorthand_row(row, area_name, row_idx, open_by_col))
|
||||
expanded_rows.append(expand_shorthand_row(row, area_name, row_idx, open_by_col, area_max_cols))
|
||||
else:
|
||||
expanded_rows.append(row)
|
||||
if row.get('empty'):
|
||||
@@ -757,21 +782,25 @@ def main():
|
||||
lines.append('\t</languageSettings>')
|
||||
|
||||
# 7c. Columns
|
||||
lines.append('\t<columns>')
|
||||
lines.append(f'\t\t<size>{total_columns}</size>')
|
||||
# Раскладка по умолчанию идёт первой и без <id> — так их хранит платформа.
|
||||
for layout in column_layouts:
|
||||
lines.append('\t<columns>')
|
||||
if layout['Id']:
|
||||
lines.append(f'\t\t<id>{layout["Id"]}</id>')
|
||||
lines.append(f'\t\t<size>{layout["Size"]}</size>')
|
||||
|
||||
# Emit columnsItem for columns with non-default widths
|
||||
for col in sorted(col_format_map.keys()):
|
||||
fmt_idx = col_format_map[col]
|
||||
col_idx = col - 1 # Convert to 0-based
|
||||
lines.append('\t\t<columnsItem>')
|
||||
lines.append(f'\t\t\t<index>{col_idx}</index>')
|
||||
lines.append('\t\t\t<column>')
|
||||
lines.append(f'\t\t\t\t<formatIndex>{fmt_idx}</formatIndex>')
|
||||
lines.append('\t\t\t</column>')
|
||||
lines.append('\t\t</columnsItem>')
|
||||
# Emit columnsItem for columns with non-default widths
|
||||
for col in sorted(layout['FormatMap'].keys()):
|
||||
fmt_idx = layout['FormatMap'][col]
|
||||
col_idx = col - 1 # Convert to 0-based
|
||||
lines.append('\t\t<columnsItem>')
|
||||
lines.append(f'\t\t\t<index>{col_idx}</index>')
|
||||
lines.append('\t\t\t<column>')
|
||||
lines.append(f'\t\t\t\t<formatIndex>{fmt_idx}</formatIndex>')
|
||||
lines.append('\t\t\t</column>')
|
||||
lines.append('\t\t</columnsItem>')
|
||||
|
||||
lines.append('\t</columns>')
|
||||
lines.append('\t</columns>')
|
||||
|
||||
# 7d. Rows -- main generation loop
|
||||
global_row = 0
|
||||
@@ -784,6 +813,18 @@ def main():
|
||||
area_name = area.get('name', '')
|
||||
active_rowspans = []
|
||||
local_row = 0
|
||||
# Ссылка области на колоночную раскладку — её получают все строки области.
|
||||
area_column_set = str(area.get('columnSet') or '')
|
||||
area_layout = column_layouts[0]
|
||||
if area_column_set:
|
||||
area_layout = next((x for x in column_layouts if x['Id'] == area_column_set), None)
|
||||
if area_layout is None:
|
||||
print(f'Unknown \'columnSet\': "{area_column_set}" is not declared in columnSets',
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
# Ширина сетки — у КАЖДОЙ раскладки своя, поэтому позиции колонок сверяем с ней,
|
||||
# а не с документным columns (у макетов с раскладками умолчание бывает и пустым).
|
||||
area_columns = int(area_layout['Size'])
|
||||
|
||||
for row in area.get('rows', []):
|
||||
# Empty row placeholder: emit N empty rows
|
||||
@@ -832,8 +873,8 @@ def main():
|
||||
col_span = int(cell.get('span', 1))
|
||||
while any(c in rowspan_occupied for c in range(cursor, cursor + col_span)):
|
||||
cursor += 1
|
||||
if cursor + col_span - 1 > total_columns:
|
||||
print(f'Row exceeds \'columns\' ({total_columns}): area "{area_name}",'
|
||||
if cursor + col_span - 1 > area_columns:
|
||||
print(f'Row exceeds \'columns\' ({area_columns}): area "{area_name}",'
|
||||
f' row {local_row + 1}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
cell['col'] = cursor
|
||||
@@ -847,7 +888,7 @@ def main():
|
||||
# ронял py голым KeyError/ValueError, а ps1 молча писал Col = -1.
|
||||
for cell_idx, cell in enumerate(row['cells'], start=1):
|
||||
col_parsed = parse_col_value(cell.get('col'))
|
||||
if col_parsed is None or col_parsed < 1 or col_parsed > total_columns:
|
||||
if col_parsed is None or col_parsed < 1 or col_parsed > area_columns:
|
||||
print(f'Invalid \'col\' value "{cell.get("col")}": area "{area_name}",'
|
||||
f' row {local_row + 1}, cell {cell_idx}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -933,6 +974,9 @@ def main():
|
||||
lines.append(f'\t\t<index>{global_row}</index>')
|
||||
lines.append('\t\t<row>')
|
||||
|
||||
if area_column_set:
|
||||
lines.append(f'\t\t\t<columnsID>{area_column_set}</columnsID>')
|
||||
|
||||
if row_format_idx > 0:
|
||||
lines.append(f'\t\t\t<formatIndex>{row_format_idx}</formatIndex>')
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# mxl-decompile v1.4 — Decompile 1C spreadsheet to JSON
|
||||
# mxl-decompile v1.5 — Decompile 1C spreadsheet to JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -117,15 +117,11 @@ function Get-Format {
|
||||
|
||||
# --- 5. Extract columns and default width ---
|
||||
|
||||
$colNode = $root.SelectSingleNode("d:columns", $ns)
|
||||
$totalColumns = [int]$colNode.SelectSingleNode("d:size", $ns).InnerText
|
||||
|
||||
$colFormatIndices = @{}
|
||||
foreach ($ci in $colNode.SelectNodes("d:columnsItem", $ns)) {
|
||||
$colIdx = [int]$ci.SelectSingleNode("d:index", $ns).InnerText
|
||||
$fmtIdx = [int]$ci.SelectSingleNode("d:column/d:formatIndex", $ns).InnerText
|
||||
$colFormatIndices[$colIdx] = $fmtIdx
|
||||
}
|
||||
# Колоночная раскладка («индивидуальная ширина колонок» для группы строк) — элемент <columns>.
|
||||
# Их бывает несколько: раскладка БЕЗ <id> — умолчание (ровно одна в каждом макете корпуса),
|
||||
# остальные адресуются GUID из <id>, на который ссылаются строки (<row><columnsID>) и
|
||||
# области (<area><columnsID>). Раньше читался только первый <columns> — отсюда терялись
|
||||
# ширины и вылезал columns: 0.
|
||||
|
||||
$defaultFmtIdx = 0
|
||||
$n = $root.SelectSingleNode("d:defaultFormatIndex", $ns)
|
||||
@@ -137,16 +133,39 @@ if ($defaultFmtIdx -gt 0) {
|
||||
if ($defFmt -and $defFmt.Width -gt 0) { $defaultWidth = $defFmt.Width }
|
||||
}
|
||||
|
||||
# Build column width map (1-based col → width), only non-default
|
||||
$colWidthMap = [ordered]@{}
|
||||
foreach ($col0 in ($colFormatIndices.Keys | Sort-Object)) {
|
||||
$fmt = Get-Format $colFormatIndices[$col0]
|
||||
if ($fmt -and $fmt.Width -gt 0 -and $fmt.Width -ne $defaultWidth) {
|
||||
$col1 = [string]($col0 + 1)
|
||||
$colWidthMap.Add($col1, $fmt.Width)
|
||||
function Read-ColumnSet {
|
||||
param($node)
|
||||
$byIdx = @{}
|
||||
foreach ($ci in $node.SelectNodes("d:columnsItem", $ns)) {
|
||||
$colIdx = [int]$ci.SelectSingleNode("d:index", $ns).InnerText
|
||||
$fmtIdx = [int]$ci.SelectSingleNode("d:column/d:formatIndex", $ns).InnerText
|
||||
$byIdx[$colIdx] = $fmtIdx
|
||||
}
|
||||
# Карта ширин (1-based колонка → ширина), только отличные от умолчания.
|
||||
$widths = [ordered]@{}
|
||||
foreach ($col0 in ($byIdx.Keys | Sort-Object)) {
|
||||
$fmt = Get-Format $byIdx[$col0]
|
||||
if ($fmt -and $fmt.Width -gt 0 -and $fmt.Width -ne $defaultWidth) {
|
||||
$widths.Add([string]($col0 + 1), $fmt.Width)
|
||||
}
|
||||
}
|
||||
$sizeNode = $node.SelectSingleNode("d:size", $ns)
|
||||
$idNode = $node.SelectSingleNode("d:id", $ns)
|
||||
return @{
|
||||
Id = if ($idNode) { $idNode.InnerText } else { $null }
|
||||
Size = if ($sizeNode) { [int]$sizeNode.InnerText } else { 0 }
|
||||
Widths = $widths
|
||||
}
|
||||
}
|
||||
|
||||
$columnSets = @()
|
||||
foreach ($cn in $root.SelectNodes("d:columns", $ns)) { $columnSets += Read-ColumnSet $cn }
|
||||
|
||||
$defaultSet = @($columnSets | Where-Object { -not $_.Id })[0]
|
||||
if (-not $defaultSet -and $columnSets.Count -gt 0) { $defaultSet = $columnSets[0] }
|
||||
$totalColumns = if ($defaultSet) { $defaultSet.Size } else { 0 }
|
||||
$colWidthMap = if ($defaultSet) { $defaultSet.Widths } else { [ordered]@{} }
|
||||
|
||||
# --- 6. Extract merges ---
|
||||
|
||||
$mergeMap = @{}
|
||||
@@ -243,11 +262,17 @@ foreach ($riNode in $root.SelectNodes("d:rowsItem", $ns)) {
|
||||
}
|
||||
}
|
||||
|
||||
# Ссылка строки на колоночную раскладку; пусто = раскладка по умолчанию.
|
||||
$rowColumnsId = $null
|
||||
$cidNode = $rowNode.SelectSingleNode("d:columnsID", $ns)
|
||||
if ($cidNode) { $rowColumnsId = $cidNode.InnerText }
|
||||
|
||||
for ($r = $rowIdx; $r -le $indexTo; $r++) {
|
||||
$rowData[$r] = @{
|
||||
FormatIdx = $rowFmtIdx
|
||||
Cells = $cells
|
||||
Empty = $isEmpty
|
||||
ColumnsId = $rowColumnsId
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -453,9 +478,26 @@ function Get-StyleName {
|
||||
$maxRowIdx = -1
|
||||
foreach ($k in $rowData.Keys) { if ([int]$k -gt $maxRowIdx) { $maxRowIdx = [int]$k } }
|
||||
|
||||
function Get-RowColumnsId {
|
||||
param([int]$r)
|
||||
if ($rowData.ContainsKey($r)) { return $rowData[$r].ColumnsId }
|
||||
return $null
|
||||
}
|
||||
|
||||
# Область годится в качестве области-диапазона, только если у всех её строк ОДНА раскладка:
|
||||
# иначе её пришлось бы резать, а имя резать нельзя — такая уходит в координатный список.
|
||||
function Test-UniformColumnSet {
|
||||
param([int]$from, [int]$to)
|
||||
$first = Get-RowColumnsId $from
|
||||
for ($r = $from + 1; $r -le $to; $r++) {
|
||||
if ((Get-RowColumnsId $r) -ne $first) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
$blockAreas = @()
|
||||
$overlayAreas = @()
|
||||
$claimed = @{} # строка → занята блоком
|
||||
$claimed = @{} # строка → занята областью-диапазоном
|
||||
foreach ($a in @($namedAreas | Sort-Object @{ Expression = { $_.BeginRow } }, @{ Expression = { $_.EndRow } })) {
|
||||
$fitsBlock = ($a.Type -eq 'Rows' -and $a.BeginRow -ge 0 -and $a.EndRow -ge $a.BeginRow)
|
||||
if ($fitsBlock) {
|
||||
@@ -463,6 +505,7 @@ foreach ($a in @($namedAreas | Sort-Object @{ Expression = { $_.BeginRow } }, @{
|
||||
if ($claimed.ContainsKey($r)) { $fitsBlock = $false; break }
|
||||
}
|
||||
}
|
||||
if ($fitsBlock) { $fitsBlock = Test-UniformColumnSet $a.BeginRow $a.EndRow }
|
||||
if ($fitsBlock) {
|
||||
for ($r = $a.BeginRow; $r -le $a.EndRow; $r++) { $claimed[$r] = $true }
|
||||
$blockAreas += $a
|
||||
@@ -471,18 +514,37 @@ foreach ($a in @($namedAreas | Sort-Object @{ Expression = { $_.BeginRow } }, @{
|
||||
}
|
||||
}
|
||||
|
||||
# Блоки в порядке строк + безымянные заполнители дыр.
|
||||
# Безымянный промежуток режем на куски с одной раскладкой: границы наборов не совпадают
|
||||
# с границами именованных областей, а у области должна быть ровно одна раскладка.
|
||||
function Split-GapByColumnSet {
|
||||
param([int]$from, [int]$to)
|
||||
$out = @()
|
||||
if ($to -lt $from) { return $out }
|
||||
$runStart = $from
|
||||
$runId = Get-RowColumnsId $from
|
||||
for ($r = $from + 1; $r -le $to; $r++) {
|
||||
$id = Get-RowColumnsId $r
|
||||
if ($id -ne $runId) {
|
||||
$out += @{ Name = $null; BeginRow = $runStart; EndRow = $r - 1; ColumnsId = $runId }
|
||||
$runStart = $r; $runId = $id
|
||||
}
|
||||
}
|
||||
$out += @{ Name = $null; BeginRow = $runStart; EndRow = $to; ColumnsId = $runId }
|
||||
return $out
|
||||
}
|
||||
|
||||
# Области в порядке строк + безымянные заполнители дыр.
|
||||
$blocks = @()
|
||||
$cursor = 0
|
||||
foreach ($a in @($blockAreas | Sort-Object @{ Expression = { $_.BeginRow } })) {
|
||||
if ($a.BeginRow -gt $cursor) {
|
||||
$blocks += @{ Name = $null; BeginRow = $cursor; EndRow = $a.BeginRow - 1 }
|
||||
$blocks += Split-GapByColumnSet $cursor ($a.BeginRow - 1)
|
||||
}
|
||||
$blocks += @{ Name = $a.Name; BeginRow = $a.BeginRow; EndRow = $a.EndRow }
|
||||
$blocks += @{ Name = $a.Name; BeginRow = $a.BeginRow; EndRow = $a.EndRow; ColumnsId = (Get-RowColumnsId $a.BeginRow) }
|
||||
$cursor = $a.EndRow + 1
|
||||
}
|
||||
if ($cursor -le $maxRowIdx) {
|
||||
$blocks += @{ Name = $null; BeginRow = $cursor; EndRow = $maxRowIdx }
|
||||
$blocks += Split-GapByColumnSet $cursor $maxRowIdx
|
||||
}
|
||||
|
||||
$dslAreas = @()
|
||||
@@ -609,8 +671,11 @@ foreach ($area in $blocks) {
|
||||
}
|
||||
|
||||
$dslBlock = [ordered]@{}
|
||||
# Безымянный блок — просто кусок сетки, ключ name у него не пишем.
|
||||
# Область без имени — просто кусок сетки, ключ name у неё не пишем.
|
||||
if (-not [string]::IsNullOrEmpty($area.Name)) { $dslBlock["name"] = $area.Name }
|
||||
# Ссылка на колоночную раскладку по имени из columnSets — как style у ячейки на styles.
|
||||
# Умолчание (раскладка без id) не пишем.
|
||||
if (-not [string]::IsNullOrEmpty($area.ColumnsId)) { $dslBlock["columnSet"] = $area.ColumnsId }
|
||||
$dslBlock["rows"] = [array]$compressedRows
|
||||
$dslAreas += $dslBlock
|
||||
}
|
||||
@@ -682,6 +747,21 @@ foreach ($s in $toRemove) { $styleDefs.Remove($s)
|
||||
|
||||
$result["fonts"] = $fontsOut
|
||||
$result["styles"] = $styleDefs
|
||||
|
||||
# Колоночные раскладки помимо умолчания: ключ — идентификатор из макета, на него ссылаются
|
||||
# области. Содержимое раскладку не опознаёт (в корпусе полно наборов с одинаковым
|
||||
# содержимым и разными id), поэтому склейки по содержимому нет.
|
||||
$extraSets = @($columnSets | Where-Object { $_.Id })
|
||||
if ($extraSets.Count -gt 0) {
|
||||
$setsOut = [ordered]@{}
|
||||
foreach ($cs in $extraSets) {
|
||||
$entry = [ordered]@{ columns = $cs.Size }
|
||||
if ($cs.Widths.Count -gt 0) { $entry["columnWidths"] = $cs.Widths }
|
||||
$setsOut[$cs.Id] = $entry
|
||||
}
|
||||
$result["columnSets"] = $setsOut
|
||||
}
|
||||
|
||||
$result["areas"] = [array]$dslAreas
|
||||
|
||||
# Именованные области, не выразимые блоком, — координатами. Тип не пишем: он выводится
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# mxl-decompile v1.4 — Decompile 1C spreadsheet to JSON
|
||||
# mxl-decompile v1.5 — Decompile 1C spreadsheet to JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -296,14 +296,11 @@ def main():
|
||||
|
||||
# --- 5. Extract columns and default width ---
|
||||
|
||||
col_node = find(root, "d:columns")
|
||||
total_columns = int_of(find(col_node, "d:size"))
|
||||
|
||||
col_format_indices = {}
|
||||
for ci in findall(col_node, "d:columnsItem"):
|
||||
col_idx = int_of(find(ci, "d:index"))
|
||||
fmt_idx = int_of(find(ci, "d:column/d:formatIndex"))
|
||||
col_format_indices[col_idx] = fmt_idx
|
||||
# Колоночная раскладка («индивидуальная ширина колонок» для группы строк) — элемент <columns>.
|
||||
# Их бывает несколько: раскладка БЕЗ <id> — умолчание (ровно одна в каждом макете корпуса),
|
||||
# остальные адресуются GUID из <id>, на который ссылаются строки (<row><columnsID>) и
|
||||
# области (<area><columnsID>). Раньше читался только первый <columns> — отсюда терялись
|
||||
# ширины и вылезал columns: 0.
|
||||
|
||||
default_fmt_idx = 0
|
||||
n = find(root, "d:defaultFormatIndex")
|
||||
@@ -316,13 +313,31 @@ def main():
|
||||
if def_fmt and def_fmt["Width"] > 0:
|
||||
default_width = def_fmt["Width"]
|
||||
|
||||
# Build column width map (1-based col -> width), only non-default
|
||||
col_width_map = OrderedDict()
|
||||
for col0 in sorted(col_format_indices.keys()):
|
||||
fmt = get_format(col_format_indices[col0])
|
||||
if fmt and fmt["Width"] > 0 and fmt["Width"] != default_width:
|
||||
col1 = str(col0 + 1)
|
||||
col_width_map[col1] = fmt["Width"]
|
||||
def read_column_set(node):
|
||||
by_idx = {}
|
||||
for ci in findall(node, "d:columnsItem"):
|
||||
by_idx[int_of(find(ci, "d:index"))] = int_of(find(ci, "d:column/d:formatIndex"))
|
||||
# Карта ширин (1-based колонка → ширина), только отличные от умолчания.
|
||||
widths = OrderedDict()
|
||||
for col0 in sorted(by_idx.keys()):
|
||||
fmt = get_format(by_idx[col0])
|
||||
if fmt and fmt["Width"] > 0 and fmt["Width"] != default_width:
|
||||
widths[str(col0 + 1)] = fmt["Width"]
|
||||
id_node = find(node, "d:id")
|
||||
size_node = find(node, "d:size")
|
||||
return {
|
||||
"Id": (text_of(id_node) or None) if id_node is not None else None,
|
||||
"Size": int_of(size_node) if size_node is not None else 0,
|
||||
"Widths": widths,
|
||||
}
|
||||
|
||||
column_sets = [read_column_set(cn) for cn in findall(root, "d:columns")]
|
||||
|
||||
default_set = next((c for c in column_sets if not c["Id"]), None)
|
||||
if default_set is None and column_sets:
|
||||
default_set = column_sets[0]
|
||||
total_columns = default_set["Size"] if default_set else 0
|
||||
col_width_map = default_set["Widths"] if default_set else OrderedDict()
|
||||
|
||||
# --- 6. Extract merges ---
|
||||
|
||||
@@ -426,11 +441,16 @@ def main():
|
||||
"Text": text,
|
||||
})
|
||||
|
||||
# Ссылка строки на колоночную раскладку; пусто = раскладка по умолчанию.
|
||||
cid_node = find(row_node, "d:columnsID")
|
||||
row_columns_id = text_of(cid_node) if cid_node is not None else None
|
||||
|
||||
for r in range(row_idx, index_to + 1):
|
||||
row_data[r] = {
|
||||
"FormatIdx": row_fmt_idx,
|
||||
"Cells": cells,
|
||||
"Empty": is_empty,
|
||||
"ColumnsId": row_columns_id,
|
||||
}
|
||||
|
||||
# --- 9. Build style key (ignoring fillType) ---
|
||||
@@ -645,6 +665,16 @@ def main():
|
||||
|
||||
max_row_idx = max(row_data.keys()) if row_data else -1
|
||||
|
||||
def row_columns_id(r):
|
||||
rd = row_data.get(r)
|
||||
return rd["ColumnsId"] if rd else None
|
||||
|
||||
def uniform_column_set(frm, to):
|
||||
"""Область годится в качестве области-диапазона, только если у всех её строк ОДНА
|
||||
раскладка: иначе её пришлось бы резать, а имя резать нельзя."""
|
||||
first = row_columns_id(frm)
|
||||
return all(row_columns_id(r) == first for r in range(frm + 1, to + 1))
|
||||
|
||||
block_areas = []
|
||||
overlay_areas = []
|
||||
claimed = set()
|
||||
@@ -655,22 +685,41 @@ def main():
|
||||
if r in claimed:
|
||||
fits = False
|
||||
break
|
||||
if fits:
|
||||
fits = uniform_column_set(a["BeginRow"], a["EndRow"])
|
||||
if fits:
|
||||
claimed.update(range(a["BeginRow"], a["EndRow"] + 1))
|
||||
block_areas.append(a)
|
||||
else:
|
||||
overlay_areas.append(a)
|
||||
|
||||
# Блоки в порядке строк + безымянные заполнители дыр.
|
||||
def split_gap_by_column_set(frm, to):
|
||||
"""Безымянный промежуток режем на куски с одной раскладкой: границы наборов не
|
||||
совпадают с границами именованных областей."""
|
||||
out = []
|
||||
if to < frm:
|
||||
return out
|
||||
run_start = frm
|
||||
run_id = row_columns_id(frm)
|
||||
for r in range(frm + 1, to + 1):
|
||||
cid = row_columns_id(r)
|
||||
if cid != run_id:
|
||||
out.append({"Name": None, "BeginRow": run_start, "EndRow": r - 1, "ColumnsId": run_id})
|
||||
run_start, run_id = r, cid
|
||||
out.append({"Name": None, "BeginRow": run_start, "EndRow": to, "ColumnsId": run_id})
|
||||
return out
|
||||
|
||||
# Области в порядке строк + безымянные заполнители дыр.
|
||||
blocks = []
|
||||
cursor = 0
|
||||
for a in sorted(block_areas, key=lambda x: x["BeginRow"]):
|
||||
if a["BeginRow"] > cursor:
|
||||
blocks.append({"Name": None, "BeginRow": cursor, "EndRow": a["BeginRow"] - 1})
|
||||
blocks.append({"Name": a["Name"], "BeginRow": a["BeginRow"], "EndRow": a["EndRow"]})
|
||||
blocks.extend(split_gap_by_column_set(cursor, a["BeginRow"] - 1))
|
||||
blocks.append({"Name": a["Name"], "BeginRow": a["BeginRow"], "EndRow": a["EndRow"],
|
||||
"ColumnsId": row_columns_id(a["BeginRow"])})
|
||||
cursor = a["EndRow"] + 1
|
||||
if cursor <= max_row_idx:
|
||||
blocks.append({"Name": None, "BeginRow": cursor, "EndRow": max_row_idx})
|
||||
blocks.extend(split_gap_by_column_set(cursor, max_row_idx))
|
||||
|
||||
dsl_areas = []
|
||||
|
||||
@@ -789,9 +838,13 @@ def main():
|
||||
compressed_rows.append(OrderedDict([("empty", empty_run)]))
|
||||
|
||||
dsl_block = OrderedDict()
|
||||
# Безымянный блок — просто кусок сетки, ключ name у него не пишем.
|
||||
# Область без имени — просто кусок сетки, ключ name у неё не пишем.
|
||||
if area["Name"]:
|
||||
dsl_block["name"] = area["Name"]
|
||||
# Ссылка на колоночную раскладку по имени из columnSets — как style у ячейки на styles.
|
||||
# Умолчание (раскладка без id) не пишем.
|
||||
if area.get("ColumnsId"):
|
||||
dsl_block["columnSet"] = area["ColumnsId"]
|
||||
dsl_block["rows"] = compressed_rows
|
||||
dsl_areas.append(dsl_block)
|
||||
|
||||
@@ -875,6 +928,20 @@ def main():
|
||||
|
||||
result["fonts"] = fonts_out
|
||||
result["styles"] = style_defs
|
||||
|
||||
# Колоночные раскладки помимо умолчания: ключ — идентификатор из макета, на него ссылаются
|
||||
# области. Содержимое раскладку не опознаёт (в корпусе полно наборов с одинаковым
|
||||
# содержимым и разными id), поэтому склейки по содержимому нет.
|
||||
extra_sets = [c for c in column_sets if c["Id"]]
|
||||
if extra_sets:
|
||||
sets_out = OrderedDict()
|
||||
for cs in extra_sets:
|
||||
entry = OrderedDict([("columns", cs["Size"])])
|
||||
if cs["Widths"]:
|
||||
entry["columnWidths"] = cs["Widths"]
|
||||
sets_out[cs["Id"]] = entry
|
||||
result["columnSets"] = sets_out
|
||||
|
||||
result["areas"] = dsl_areas
|
||||
|
||||
# Именованные области, не выразимые блоком, — координатами. Тип не пишем: он выводится
|
||||
|
||||
Reference in New Issue
Block a user