feat(mxl-compile,mxl-decompile): колонтитулы и параметры печати

Ключи header и footer описывают колонтитул: три слота (left, center,
right), общий шрифт и вертикальное выравнивание, признак вывода и
страница, с которой печатать. Ключ printSettings — плоский набор
параметров печати с именами тегов платформы; порядок компилятор
выставляет сам, незнакомый ключ отвергает.

Слот устроен как ячейка: ссылка на формат плюс текст. Текст бывает двух
видов, и это не текст против шаблона, а обычная строка против
форматированной: у форматированной разметка живёт прямо в содержимом
(<b>жирный</>, <fontsize 12>, <colorstyle -16>), поэтому она возится как
есть. Признак вывода и стартовая страница лежат в формате как <height>
и <width>, но читаются только у записи с <height>: палитра
дедуплицирована, и колонтитул без своих настроек ссылается на чужую
запись, где <width> — ширина колонки (637 таких ссылок против 153 своих).

Перенос строки внутри текста колонтитула платформа хранит голым LF при
CRLF во всём остальном файле — единственное такое место в документе,
поэтому содержимое пишется одной строкой вывода.

Формат колонтитула регистрируется до отсева неиспользуемых шрифтов:
иначе шрифт, на который ссылается только колонтитул, выбрасывался бы
из палитры.

Стенд Колонтитулы собирается байт в байт обоими портами и добавлен
в платформенные фикстуры.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-15 17:07:54 +03:00
co-authored by Claude Opus 5
parent 1e0b82d419
commit 702a3b6a8d
16 changed files with 881 additions and 8 deletions
+3
View File
@@ -64,6 +64,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/mxl-compile.ps1" -J
namedAreas: [{ name, rows, cols }],
rowGroups: [{ rows, name, collapsed, titleLocation }],
columnGroups: [{ cols, name, collapsed, titleLocation }],
header: { left, center, right, font, verticalAlignment, show, startPage }, footer: { … },
printSettings: { pageOrientation, topMargin, …, fitToPage, firstPageNumber },
columnSets: { name: { columns, columnWidths, columnStyles } }
}
```
@@ -83,6 +85,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/mxl-compile.ps1" -J
- `rowspan` — объединение строк вниз (rowStyle учитывает занятые ячейки)
- Содержимое ячейки задаётся одним из ключей: `param` — параметр заполнения, `text` — статический текст, `template` — текст со вставками `[Параметр]`
- `rowGroups`/`columnGroups` — группы строк и колонок (сворачиваются кнопкой на полях): плоский список диапазонов, вложенность выражена вхождением одного в другой
- `header`/`footer` — колонтитулы: три слота (`left`/`center`/`right`), общий шрифт и вертикальное выравнивание; текст может нести поля `[&НомерСтраницы]`, `[&Дата]`
- `note` — всплывающая подсказка у ячейки: строка, объект языков или `{ text, style, autoSize, box }`
- `valueType` делает ячейку полем ввода (`"Number(15,3,nonneg)"`, `"String(10)"`, `"CatalogRef.Валюты"`, составной через ` + `); текста в такой ячейке быть не может, значение задаётся ключом `value`
@@ -89,6 +89,8 @@
| `columnSets` | нет | — | Дополнительные колоночные раскладки (см. `styles.md`) |
| `rowGroups` | нет | — | Группы строк (см. ниже) |
| `columnGroups` | нет | — | Группы колонок (см. ниже) |
| `header` / `footer` | нет | — | Верхний и нижний колонтитулы (см. ниже) |
| `printSettings` | нет | — | Параметры печати (см. ниже) |
## Области (`areas[]`)
@@ -296,6 +298,56 @@
Координаты ячейки в примечании не задаются. `autoSize` и `box` независимы: при автоподгоне
размера положение окошка всё равно хранится.
## Колонтитулы
У верхнего и нижнего колонтитула по три слота — `left`, `center`, `right` — и общие настройки:
```json
"header": {
"font": "мелкий",
"verticalAlignment": "Bottom",
"startPage": 2,
"left": "Слева\nвторая строка",
"center": "Стр. [&НомерСтраницы] из [&СтраницВсего]",
"right": { "ru": "Справа", "en": "Right" }
},
"footer": {
"show": false,
"center": { "formatted": "<b>Итог</> <colorstyle -16>красным</>" }
}
```
| Поле | По умолч. | Описание |
|------|-----------|----------|
| `left`, `center`, `right` | — | Текст слота: строка, объект «язык → текст» или `{ "formatted": … }` |
| `font` | — | Имя шрифта из `fonts` — на весь колонтитул |
| `verticalAlignment` | — | Положение текста по вертикали: `Top`, `Center`, `Bottom` |
| `show` | `true` | Выводить ли колонтитул |
| `startPage` | `1` | Страница, с которой колонтитул печатается |
Текст может быть многострочным (`\n`) и содержать поля `[&НомерСтраницы]`, `[&СтраницВсего]`,
`[&Дата]`, `[&Время]` — платформа подставляет их при печати. `{ "formatted": … }` — форматированная
строка: разметка живёт прямо в тексте (`<b>жирный</>`, `<fontsize 12>`, `<colorstyle -16>`).
## Параметры печати
Плоский объект; имя ключа совпадает с именем свойства в выгрузке:
```json
"printSettings": {
"pageOrientation": "Landscape",
"topMargin": 500, "leftMargin": 500, "bottomMargin": 500, "rightMargin": 500,
"headerSize": 1000, "footerSize": 1000,
"fitToPage": true, "firstPageNumber": 7
}
```
Допустимые ключи: `pageOrientation`, `scale`, `collate`, `copies`, `perPage`, `topMargin`,
`leftMargin`, `bottomMargin`, `rightMargin`, `headerSize`, `footerSize`, `fitToPage`,
`blackAndWhite`, `printerName`, `paper`, `paperSource`, `pageWidth`, `pageHeight`, `duplexType`,
`pagePlacementAlternation`, `firstPageNumber`. Незнакомый ключ — ошибка. Порядок в объекте не
важен: компилятор пишет свойства в том порядке, что и платформа.
## Группы строк и колонок
Диапазон строк или колонок, который сворачивается кнопкой на полях. Задаются плоским списком;
@@ -339,7 +391,7 @@ XML не возвращается.
- объединения, не привязанные к ячейке (по всей высоте или ширине документа);
- рисунки и картинки, в том числе штрихкоды;
- колонтитулы, параметры печати, область печати.
- область печати.
Пересборка макета из DSL — это полная перегенерация, а не точечная правка XML, поэтому
diff после round-trip обычно шире фактической доработки.
@@ -1,4 +1,4 @@
# mxl-compile v1.44 — Compile 1C spreadsheet from JSON
# mxl-compile v1.45 — Compile 1C spreadsheet from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -1485,9 +1485,108 @@ foreach ($area in $def.areas) {
}
}
# 7d-ter. Колонтитулы. Шесть слотов: left/center/right у верхнего и нижнего. Слот устроен
# как ячейка — ссылка на формат плюс текст, — а формат несёт то, что в диалоге задаётся на весь
# колонтитул: шрифт, вертикальное выравнивание, признак вывода (<height> 1/-1) и страницу,
# с которой колонтитул печатается (<width>).
$script:headerSlots = @('left', 'center', 'right')
function Get-HeaderFormatIndex {
param($hf)
if ($null -eq $hf) { return 0 }
$props = @{}
if ($hf.font -and $fontMap.Contains("$($hf.font)")) { $props['font'] = $fontMap["$($hf.font)"] }
# Признак вывода и стартовая страница осмысленны только вместе: платформа пишет их парой.
$props['height'] = if ($hf.show -eq $false -or "$($hf.show)" -eq 'false') { -1 } else { 1 }
$props['width'] = if ($hf.startPage) { [int]$hf.startPage } else { 1 }
if ($hf.verticalAlignment) { $props['verticalAlignment'] = "$($hf.verticalAlignment)" }
return Register-Format $props
}
function Emit-HeaderSlot {
param([string]$tag, $value, [int]$fmtIdx)
if ($null -eq $value) { return }
# Форматированная строка платформы — тот же текст, только разметка живёт внутри содержимого
# (<b>жирный</>, <fontsize 12>…), поэтому возим её как есть и различаем именем тега.
$formatted = $false
$text = $value
if ($value -is [System.Management.Automation.PSCustomObject] -and $null -ne $value.formatted) {
$formatted = $true
$text = $value.formatted
}
$pairs = @()
if ($text -is [System.Collections.IDictionary]) {
foreach ($k in $text.Keys) { $pairs += @{ Lang = "$k"; Text = "$($text[$k])" } }
} elseif ($text -is [System.Management.Automation.PSCustomObject]) {
foreach ($pr in $text.PSObject.Properties) { $pairs += @{ Lang = $pr.Name; Text = "$($pr.Value)" } }
} else {
foreach ($l in $textLanguages) { $pairs += @{ Lang = $l; Text = "$text" } }
}
$textTag = if ($formatted) { 'tfl' } else { 'tl' }
X "`t<$tag>"
X "`t`t<f>$fmtIdx</f>"
if ($pairs.Count -eq 0) {
X "`t`t<$textTag/>"
} else {
X "`t`t<$textTag>"
foreach ($pr in $pairs) {
X "`t`t`t<v8:item>"
X "`t`t`t`t<v8:lang>$($pr.Lang)</v8:lang>"
# Перенос строки внутри колонтитула платформа хранит голым LF, тогда как весь файл
# идёт CRLF. Пишем содержимое одной строкой вывода, чтобы перенос не переписался.
X "`t`t`t`t<v8:content>$(Esc-XmlText $pr.Text)</v8:content>"
X "`t`t`t</v8:item>"
}
X "`t`t</$textTag>"
}
X "`t</$tag>"
}
# Параметры печати. Имя ключа совпадает с именем тега в выгрузке, как у свойств стиля.
# Порядок снят с корпуса (2350 макетов): платформа пишет теги строго в этой последовательности.
function Get-PrintSettingsOrder {
return @(
'pageOrientation', 'scale', 'collate', 'copies', 'perPage',
'topMargin', 'leftMargin', 'bottomMargin', 'rightMargin', 'headerSize', 'footerSize',
'fitToPage', 'blackAndWhite', 'printerName', 'paper', 'paperSource',
'pageWidth', 'pageHeight', 'duplexType', 'pagePlacementAlternation', 'firstPageNumber'
)
}
function Emit-PrintSettings {
param($ps)
if ($null -eq $ps) { return }
$order = Get-PrintSettingsOrder
$known = @{}
foreach ($k in $order) { $known[$k] = $true }
foreach ($pr in $ps.PSObject.Properties) {
if (-not $known.ContainsKey($pr.Name)) {
[Console]::Error.WriteLine("printSettings: unknown key `"$($pr.Name)`". Allowed: $($order -join ', ')")
exit 1
}
}
$emitted = @()
foreach ($tag in $order) {
$v = $ps.$tag
if ($null -eq $v) { continue }
if ($v -is [bool]) { $v = if ($v) { 'true' } else { 'false' } }
$emitted += "`t`t<$tag>$(Esc-XmlText "$v")</$tag>"
}
if ($emitted.Count -eq 0) { return }
X "`t<printSettings>"
foreach ($line in $emitted) { X $line }
X "`t</printSettings>"
}
# Формат по умолчанию — последняя запись палитры (см. выше).
$defaultFormatIndex = Register-Format @{ width = $defaultWidth }
# Формат колонтитула заводим здесь же: он идёт в палитре следом за умолчанием, и он
# обязан попасть в реестр ДО отсева неиспользуемых шрифтов — иначе шрифт колонтитула,
# на который больше никто не ссылается, будет выброшен.
$headerFmt = Get-HeaderFormatIndex $def.header
$footerFmt = Get-HeaderFormatIndex $def.footer
# Шрифт, на который не ссылается ни один формат, платформа в палитру не кладёт: у макета
# без оформления элемента <font> нет вовсе. Мы же всегда заводили Arial 10 по умолчанию.
# Отбрасываем неиспользуемые и перенумеровываем ссылки — индексы шрифтов позиционные.
@@ -2037,6 +2136,14 @@ if ($def.namedAreas) {
}
}
# 7d-ter. Колонтитулы: шесть слотов идут после строк и перед скалярными свойствами документа.
foreach ($slot in $script:headerSlots) {
if ($def.header) { Emit-HeaderSlot "$($slot)Header" $def.header.$slot $headerFmt }
}
foreach ($slot in $script:headerSlots) {
if ($def.footer) { Emit-HeaderSlot "$($slot)Footer" $def.footer.$slot $footerFmt }
}
# 7e-bis. Группировки строк и колонок. Платформа хранит их плоским списком диапазонов;
# вложенность выражена вхождением одного диапазона в другой, а частичных пересечений не
# бывает вовсе (корпус: 40 620 886 пар непересекающихся, 599 958 вложенных, 0 частичных).
@@ -2156,6 +2263,8 @@ X "`t<vgRows>$totalRowCount</vgRows>"
Emit-Groups 'vg' $rowGroups
Emit-Groups 'hg' $colGroups
Emit-PrintSettings $def.printSettings
# 7f. Merges
foreach ($m in $merges) {
X "`t<merge>"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# mxl-compile v1.44 — Compile 1C spreadsheet from JSON
# mxl-compile v1.45 — Compile 1C spreadsheet from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import hashlib
@@ -1462,9 +1462,92 @@ def main():
if note:
register_note_format(note)
# Колонтитулы. Шесть слотов: left/center/right у верхнего и нижнего. Слот устроен как
# ячейка — ссылка на формат плюс текст, — а формат несёт то, что в диалоге задаётся на весь
# колонтитул: шрифт, вертикальное выравнивание, признак вывода (<height> 1/-1) и страницу,
# с которой колонтитул печатается (<width>).
def header_format_index(hf):
if not hf:
return 0
props = {}
if hf.get('font') and str(hf['font']) in font_map:
props['font'] = font_map[str(hf['font'])]
# Признак вывода и стартовая страница осмысленны только вместе: платформа пишет их парой.
props['height'] = -1 if (hf.get('show') is False or str(hf.get('show')).lower() == 'false') else 1
props['width'] = int(hf['startPage']) if hf.get('startPage') else 1
if hf.get('verticalAlignment'):
props['verticalAlignment'] = str(hf['verticalAlignment'])
return register_format(props)
def emit_header_slot(lines, tag, value, fmt_idx):
if value is None:
return
# Форматированная строка платформы — тот же текст, только разметка живёт внутри
# содержимого (<b>жирный</>, <fontsize 12>…), поэтому возим её как есть и различаем
# именем тега.
formatted = isinstance(value, dict) and value.get('formatted') is not None
text = value['formatted'] if formatted else value
if isinstance(text, dict):
pairs = [(str(k), str(v)) for k, v in text.items()]
else:
pairs = [(lang, str(text)) for lang in text_languages]
text_tag = 'tfl' if formatted else 'tl'
lines.append(f'\t<{tag}>')
lines.append(f'\t\t<f>{fmt_idx}</f>')
if not pairs:
lines.append(f'\t\t<{text_tag}/>')
else:
lines.append(f'\t\t<{text_tag}>')
for lang, content in pairs:
lines.append('\t\t\t<v8:item>')
lines.append(f'\t\t\t\t<v8:lang>{lang}</v8:lang>')
# Перенос строки внутри колонтитула платформа хранит голым LF, тогда как весь
# файл идёт CRLF. Содержимое кладём одним элементом списка, чтобы перенос
# не переписался при склейке строк документа.
lines.append(f'\t\t\t\t<v8:content>{esc_xml_text(content)}</v8:content>')
lines.append('\t\t\t</v8:item>')
lines.append(f'\t\t</{text_tag}>')
lines.append(f'\t</{tag}>')
# Параметры печати. Имя ключа совпадает с именем тега в выгрузке, как у свойств стиля.
# Порядок снят с корпуса (2350 макетов): платформа пишет теги строго в этой последовательности.
def print_settings_order():
return ('pageOrientation', 'scale', 'collate', 'copies', 'perPage',
'topMargin', 'leftMargin', 'bottomMargin', 'rightMargin', 'headerSize', 'footerSize',
'fitToPage', 'blackAndWhite', 'printerName', 'paper', 'paperSource',
'pageWidth', 'pageHeight', 'duplexType', 'pagePlacementAlternation', 'firstPageNumber')
def emit_print_settings(lines, ps):
if not ps:
return
order = print_settings_order()
for key in ps:
if key not in order:
print(f'printSettings: unknown key "{key}". Allowed: {", ".join(order)}', file=sys.stderr)
sys.exit(1)
emitted = []
for tag in order:
v = ps.get(tag)
if v is None:
continue
if isinstance(v, bool):
v = 'true' if v else 'false'
emitted.append(f'\t\t<{tag}>{esc_xml_text(str(v))}</{tag}>')
if not emitted:
return
lines.append('\t<printSettings>')
lines.extend(emitted)
lines.append('\t</printSettings>')
# Формат по умолчанию — последняя запись палитры (см. выше).
default_format_index = register_format({'width': default_width})
# Формат колонтитула заводим здесь же: он идёт в палитре следом за умолчанием, и он
# обязан попасть в реестр ДО отсева неиспользуемых шрифтов — иначе шрифт колонтитула,
# на который больше никто не ссылается, будет выброшен.
header_fmt = header_format_index(defn.get('header'))
footer_fmt = header_format_index(defn.get('footer'))
# Шрифт, на который не ссылается ни один формат, платформа в палитру не кладёт: у макета
# без оформления элемента <font> нет вовсе. Мы же всегда заводили Arial 10 по умолчанию.
# Отбрасываем неиспользуемые и перенумеровываем ссылки — индексы шрифтов позиционные.
@@ -1953,6 +2036,14 @@ def main():
'EndCol': cols[1] - 1 if cols else -1,
})
# 7d-ter. Колонтитулы: шесть слотов идут после строк и перед скалярными свойствами документа.
for slot in ('left', 'center', 'right'):
if defn.get('header'):
emit_header_slot(lines, f'{slot}Header', defn['header'].get(slot), header_fmt)
for slot in ('left', 'center', 'right'):
if defn.get('footer'):
emit_header_slot(lines, f'{slot}Footer', defn['footer'].get(slot), footer_fmt)
# 7e-bis. Группировки строк и колонок. Платформа хранит их плоским списком диапазонов;
# вложенность выражена вхождением одного диапазона в другой, а частичных пересечений не
# бывает вовсе (корпус: 40 620 886 пар непересекающихся, 599 958 вложенных, 0 частичных).
@@ -2063,6 +2154,8 @@ def main():
emit_groups(lines, 'vg', row_groups)
emit_groups(lines, 'hg', col_groups)
emit_print_settings(lines, defn.get('printSettings'))
# 7f. Merges
for m in merges:
lines.append('\t<merge>')
@@ -1,4 +1,4 @@
# mxl-decompile v1.24 — Decompile 1C spreadsheet to JSON
# mxl-decompile v1.25 — Decompile 1C spreadsheet to JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -265,6 +265,68 @@ function Get-Format {
return $rawFormats[$idx - 1]
}
# --- 4a. Колонтитулы и параметры печати ---
# Слот колонтитула устроен как ячейка: ссылка на формат плюс текст. Признак вывода и стартовую
# страницу читаем только у записи, где есть <height>: палитра дедуплицирована, и колонтитул без
# своих настроек ссылается на чужую запись, где <width> — ширина колонки, а не номер страницы.
function Read-HeaderPart {
param([string]$suffix)
$out = [ordered]@{}
$fmtIdx = $null
foreach ($slot in @('left', 'center', 'right')) {
$el = $root.SelectSingleNode("d:$slot$suffix", $ns)
if (-not $el) { continue }
if ($null -eq $fmtIdx) {
$fNode = $el.SelectSingleNode("d:f", $ns)
$fmtIdx = if ($fNode) { [int]$fNode.InnerText } else { 0 }
}
foreach ($textTag in @('tl', 'tfl')) {
$t = $el.SelectSingleNode("d:$textTag", $ns)
if (-not $t) { continue }
$byLang = [ordered]@{}
foreach ($it in $t.SelectNodes("v8:item", $ns)) {
$l = $it.SelectSingleNode("v8:lang", $ns)
$cnt = $it.SelectSingleNode("v8:content", $ns)
$byLang[$(if ($l) { $l.InnerText } else { '' })] = $(if ($cnt) { $cnt.InnerText } else { '' })
}
$value = Get-DslText $byLang
$out[$slot] = if ($textTag -ceq 'tfl') { [ordered]@{ formatted = $value } } else { $value }
break
}
}
if ($out.Count -eq 0 -and $null -eq $fmtIdx) { return $null }
$fmt = if ($fmtIdx) { Get-Format $fmtIdx } else { $null }
if ($fmt) {
$props = $fmt.Props
$head = [ordered]@{}
if ($fmt.FontIdx -ge 0) { $head["font"] = $fontNames[$fmt.FontIdx] }
if ($props.Contains('height')) {
if ("$($props['height'])" -ceq '-1') { $head["show"] = $false }
$w = if ($props.Contains('width')) { [int]$props['width'] } else { 1 }
if ($w -ne 1) { $head["startPage"] = $w }
}
if ($props.Contains('verticalAlignment')) { $head["verticalAlignment"] = $props['verticalAlignment'] }
foreach ($k in $out.Keys) { $head[$k] = $out[$k] }
$out = $head
}
return $out
}
$printSettings = $null
$psNode = $root.SelectSingleNode("d:printSettings", $ns)
if ($psNode) {
$printSettings = [ordered]@{}
foreach ($c in $psNode.ChildNodes) {
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
$v = $c.InnerText.Trim()
$i = 0
if ($v -ceq 'true') { $printSettings[$c.get_LocalName()] = $true }
elseif ($v -ceq 'false') { $printSettings[$c.get_LocalName()] = $false }
elseif ([int]::TryParse($v, [ref]$i)) { $printSettings[$c.get_LocalName()] = $i }
else { $printSettings[$c.get_LocalName()] = $v }
}
}
# --- 4b. Группировки строк и колонок ---
# Платформа хранит их плоским списком диапазонов, родитель раньше детей. Число уровней
# (<vgLevels>) не читаем: оно выводится из вложенности — совпало на всех 1797 макетах.
@@ -1477,6 +1539,14 @@ foreach ($g in ($rowGroups + $colGroups)) {
if ($rowGroups.Count -gt 0) { $result["rowGroups"] = [array]$rowGroups }
if ($colGroups.Count -gt 0) { $result["columnGroups"] = [array]$colGroups }
# Читаем здесь, а не в разделе 4a: тексту колонтитула нужен свод языков макета, который
# становится известен только после разбора строк.
$headerPart = Read-HeaderPart 'Header'
$footerPart = Read-HeaderPart 'Footer'
if ($headerPart) { $result["header"] = $headerPart }
if ($footerPart) { $result["footer"] = $footerPart }
if ($printSettings) { $result["printSettings"] = $printSettings }
$result["areas"] = [array]$dslAreas
# Именованные области, не выразимые блоком, — координатами. Тип не пишем: он выводится
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# mxl-decompile v1.24 — Decompile 1C spreadsheet to JSON
# mxl-decompile v1.25 — Decompile 1C spreadsheet to JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -431,6 +431,67 @@ def main():
return None
return raw_formats[idx - 1]
# --- 4a. Колонтитулы и параметры печати ---
# Слот колонтитула устроен как ячейка: ссылка на формат плюс текст. Признак вывода и стартовую
# страницу читаем только у записи, где есть <height>: палитра дедуплицирована, и колонтитул без
# своих настроек ссылается на чужую запись, где <width> — ширина колонки, а не номер страницы.
def read_header_part(suffix):
slots = OrderedDict()
fmt_idx = None
for slot in ('left', 'center', 'right'):
el = find(root, f'd:{slot}{suffix}')
if el is None:
continue
if fmt_idx is None:
fmt_idx = int_of(find(el, 'd:f'))
for text_tag in ('tl', 'tfl'):
t = find(el, 'd:' + text_tag)
if t is None:
continue
by_lang = OrderedDict()
for it in findall(t, 'v8:item'):
lang = text_of(find(it, 'v8:lang')) or ''
by_lang[lang] = text_of(find(it, 'v8:content')) or ''
value = get_dsl_text(by_lang)
slots[slot] = OrderedDict([('formatted', value)]) if text_tag == 'tfl' else value
break
if not slots and fmt_idx is None:
return None
fmt = get_format(fmt_idx) if fmt_idx else None
if not fmt:
return slots
head = OrderedDict()
if fmt["FontIdx"] >= 0:
head["font"] = font_names.get(fmt["FontIdx"], "default")
props = fmt["Props"]
if 'height' in props:
if str(props['height']) == '-1':
head["show"] = False
width = int(props.get('width', 1))
if width != 1:
head["startPage"] = width
if 'verticalAlignment' in props:
head["verticalAlignment"] = props['verticalAlignment']
head.update(slots)
return head
print_settings = None
ps_node = find(root, 'd:printSettings')
if ps_node is not None:
print_settings = OrderedDict()
for c in ps_node:
name = etree.QName(c).localname
v = (c.text or '').strip()
if v == 'true':
print_settings[name] = True
elif v == 'false':
print_settings[name] = False
else:
try:
print_settings[name] = int(v)
except ValueError:
print_settings[name] = v
# --- 4b. Группировки строк и колонок ---
# Платформа хранит их плоским списком диапазонов, родитель раньше детей. Число уровней
# (<vgLevels>) не читаем: оно выводится из вложенности — совпало на всех 1797 макетах.
@@ -1472,6 +1533,17 @@ def main():
if col_groups:
result["columnGroups"] = col_groups
# Читаем здесь, а не в разделе 4a: тексту колонтитула нужен свод языков макета, который
# становится известен только после разбора строк.
header_part = read_header_part('Header')
footer_part = read_header_part('Footer')
if header_part:
result["header"] = header_part
if footer_part:
result["footer"] = footer_part
if print_settings:
result["printSettings"] = print_settings
result["areas"] = dsl_areas
# Именованные области, не выразимые блоком, — координатами. Тип не пишем: он выводится