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 }], namedAreas: [{ name, rows, cols }],
rowGroups: [{ rows, name, collapsed, titleLocation }], rowGroups: [{ rows, name, collapsed, titleLocation }],
columnGroups: [{ cols, 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 } } columnSets: { name: { columns, columnWidths, columnStyles } }
} }
``` ```
@@ -83,6 +85,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/mxl-compile.ps1" -J
- `rowspan` — объединение строк вниз (rowStyle учитывает занятые ячейки) - `rowspan` — объединение строк вниз (rowStyle учитывает занятые ячейки)
- Содержимое ячейки задаётся одним из ключей: `param` — параметр заполнения, `text` — статический текст, `template` — текст со вставками `[Параметр]` - Содержимое ячейки задаётся одним из ключей: `param` — параметр заполнения, `text` — статический текст, `template` — текст со вставками `[Параметр]`
- `rowGroups`/`columnGroups` — группы строк и колонок (сворачиваются кнопкой на полях): плоский список диапазонов, вложенность выражена вхождением одного в другой - `rowGroups`/`columnGroups` — группы строк и колонок (сворачиваются кнопкой на полях): плоский список диапазонов, вложенность выражена вхождением одного в другой
- `header`/`footer` — колонтитулы: три слота (`left`/`center`/`right`), общий шрифт и вертикальное выравнивание; текст может нести поля `[&НомерСтраницы]`, `[&Дата]`
- `note` — всплывающая подсказка у ячейки: строка, объект языков или `{ text, style, autoSize, box }` - `note` — всплывающая подсказка у ячейки: строка, объект языков или `{ text, style, autoSize, box }`
- `valueType` делает ячейку полем ввода (`"Number(15,3,nonneg)"`, `"String(10)"`, `"CatalogRef.Валюты"`, составной через ` + `); текста в такой ячейке быть не может, значение задаётся ключом `value` - `valueType` делает ячейку полем ввода (`"Number(15,3,nonneg)"`, `"String(10)"`, `"CatalogRef.Валюты"`, составной через ` + `); текста в такой ячейке быть не может, значение задаётся ключом `value`
@@ -89,6 +89,8 @@
| `columnSets` | нет | — | Дополнительные колоночные раскладки (см. `styles.md`) | | `columnSets` | нет | — | Дополнительные колоночные раскладки (см. `styles.md`) |
| `rowGroups` | нет | — | Группы строк (см. ниже) | | `rowGroups` | нет | — | Группы строк (см. ниже) |
| `columnGroups` | нет | — | Группы колонок (см. ниже) | | `columnGroups` | нет | — | Группы колонок (см. ниже) |
| `header` / `footer` | нет | — | Верхний и нижний колонтитулы (см. ниже) |
| `printSettings` | нет | — | Параметры печати (см. ниже) |
## Области (`areas[]`) ## Области (`areas[]`)
@@ -296,6 +298,56 @@
Координаты ячейки в примечании не задаются. `autoSize` и `box` независимы: при автоподгоне Координаты ячейки в примечании не задаются. `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, поэтому Пересборка макета из DSL — это полная перегенерация, а не точечная правка XML, поэтому
diff после round-trip обычно шире фактической доработки. 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [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 } $defaultFormatIndex = Register-Format @{ width = $defaultWidth }
# Формат колонтитула заводим здесь же: он идёт в палитре следом за умолчанием, и он
# обязан попасть в реестр ДО отсева неиспользуемых шрифтов — иначе шрифт колонтитула,
# на который больше никто не ссылается, будет выброшен.
$headerFmt = Get-HeaderFormatIndex $def.header
$footerFmt = Get-HeaderFormatIndex $def.footer
# Шрифт, на который не ссылается ни один формат, платформа в палитру не кладёт: у макета # Шрифт, на который не ссылается ни один формат, платформа в палитру не кладёт: у макета
# без оформления элемента <font> нет вовсе. Мы же всегда заводили Arial 10 по умолчанию. # без оформления элемента <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. Группировки строк и колонок. Платформа хранит их плоским списком диапазонов; # 7e-bis. Группировки строк и колонок. Платформа хранит их плоским списком диапазонов;
# вложенность выражена вхождением одного диапазона в другой, а частичных пересечений не # вложенность выражена вхождением одного диапазона в другой, а частичных пересечений не
# бывает вовсе (корпус: 40 620 886 пар непересекающихся, 599 958 вложенных, 0 частичных). # бывает вовсе (корпус: 40 620 886 пар непересекающихся, 599 958 вложенных, 0 частичных).
@@ -2156,6 +2263,8 @@ X "`t<vgRows>$totalRowCount</vgRows>"
Emit-Groups 'vg' $rowGroups Emit-Groups 'vg' $rowGroups
Emit-Groups 'hg' $colGroups Emit-Groups 'hg' $colGroups
Emit-PrintSettings $def.printSettings
# 7f. Merges # 7f. Merges
foreach ($m in $merges) { foreach ($m in $merges) {
X "`t<merge>" X "`t<merge>"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import hashlib import hashlib
@@ -1462,9 +1462,92 @@ def main():
if note: if note:
register_note_format(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}) 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 по умолчанию. # без оформления элемента <font> нет вовсе. Мы же всегда заводили Arial 10 по умолчанию.
# Отбрасываем неиспользуемые и перенумеровываем ссылки — индексы шрифтов позиционные. # Отбрасываем неиспользуемые и перенумеровываем ссылки — индексы шрифтов позиционные.
@@ -1953,6 +2036,14 @@ def main():
'EndCol': cols[1] - 1 if cols else -1, '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. Группировки строк и колонок. Платформа хранит их плоским списком диапазонов; # 7e-bis. Группировки строк и колонок. Платформа хранит их плоским списком диапазонов;
# вложенность выражена вхождением одного диапазона в другой, а частичных пересечений не # вложенность выражена вхождением одного диапазона в другой, а частичных пересечений не
# бывает вовсе (корпус: 40 620 886 пар непересекающихся, 599 958 вложенных, 0 частичных). # бывает вовсе (корпус: 40 620 886 пар непересекающихся, 599 958 вложенных, 0 частичных).
@@ -2063,6 +2154,8 @@ def main():
emit_groups(lines, 'vg', row_groups) emit_groups(lines, 'vg', row_groups)
emit_groups(lines, 'hg', col_groups) emit_groups(lines, 'hg', col_groups)
emit_print_settings(lines, defn.get('printSettings'))
# 7f. Merges # 7f. Merges
for m in merges: for m in merges:
lines.append('\t<merge>') 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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -265,6 +265,68 @@ function Get-Format {
return $rawFormats[$idx - 1] 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. Группировки строк и колонок --- # --- 4b. Группировки строк и колонок ---
# Платформа хранит их плоским списком диапазонов, родитель раньше детей. Число уровней # Платформа хранит их плоским списком диапазонов, родитель раньше детей. Число уровней
# (<vgLevels>) не читаем: оно выводится из вложенности — совпало на всех 1797 макетах. # (<vgLevels>) не читаем: оно выводится из вложенности — совпало на всех 1797 макетах.
@@ -1477,6 +1539,14 @@ foreach ($g in ($rowGroups + $colGroups)) {
if ($rowGroups.Count -gt 0) { $result["rowGroups"] = [array]$rowGroups } if ($rowGroups.Count -gt 0) { $result["rowGroups"] = [array]$rowGroups }
if ($colGroups.Count -gt 0) { $result["columnGroups"] = [array]$colGroups } 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 $result["areas"] = [array]$dslAreas
# Именованные области, не выразимые блоком, — координатами. Тип не пишем: он выводится # Именованные области, не выразимые блоком, — координатами. Тип не пишем: он выводится
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/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 # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -431,6 +431,67 @@ def main():
return None return None
return raw_formats[idx - 1] 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. Группировки строк и колонок --- # --- 4b. Группировки строк и колонок ---
# Платформа хранит их плоским списком диапазонов, родитель раньше детей. Число уровней # Платформа хранит их плоским списком диапазонов, родитель раньше детей. Число уровней
# (<vgLevels>) не читаем: оно выводится из вложенности — совпало на всех 1797 макетах. # (<vgLevels>) не читаем: оно выводится из вложенности — совпало на всех 1797 макетах.
@@ -1472,6 +1533,17 @@ def main():
if col_groups: if col_groups:
result["columnGroups"] = 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 result["areas"] = dsl_areas
# Именованные области, не выразимые блоком, — координатами. Тип не пишем: он выводится # Именованные области, не выразимые блоком, — координатами. Тип не пишем: он выводится
+35 -2
View File
@@ -601,7 +601,40 @@ current-config не объявляет, поэтому вынести объяв
обратные, ячейки продолжают ссылаться на записи со старыми ширинами. Из итогового XML это обратные, ячейки продолжают ссылаться на записи со старыми ширинами. Из итогового XML это
значение не выводится — воспроизводить его не нужно. значение не выводится — воспроизводить его не нужно.
## Группировки строк и колонок ## Колонтитулы и параметры печати
```xml
<leftHeader>
<f>2</f> <!-- формат: шрифт, выравнивание, признак вывода, стартовая страница -->
<tl><v8:item><v8:lang>ru</v8:lang><v8:content>Слева</v8:content></v8:item></tl>
</leftHeader>
<printSettings>
<topMargin>1000</topMargin><firstPageNumber>15</firstPageNumber>
</printSettings>
```
Шесть элементов идут после строк и перед `<templateMode>`: `leftHeader`, `centerHeader`,
`rightHeader`, `leftFooter`, `centerFooter`, `rightFooter`. Пишутся не комплектом: `leftHeader`
есть у 1 013 макетов, остальные — у 677–790.
| Наблюдение | Данные корпуса |
|---|---|
| текст слота | `<tl>` — обычная строка, `<tfl>` — форматированная (разметка внутри содержимого: `<b>…</>`, `<fontsize 12>`, `<colorstyle -16>`) |
| формат слота | `<height>` = признак вывода (`1` / `-1`), `<width>` = страница, с которой печатать, плюс шрифт и `verticalAlignment` |
| формат общий на колонтитул | у 674 макетов из 677; разный у трёх |
| формат делится с другими владельцами | 637 против 153 «только колонтитулы» — палитра дедуплицирована, поэтому `<width>` в чужой записи означает ширину колонки, а не страницу |
**Ловушка:** перенос строки внутри текста колонтитула платформа хранит голым LF, тогда как весь
остальной файл идёт CRLF. Это единственное место документа с другим переводом строки.
`printSettings` (2 350 макетов) — плоский набор в фиксированном порядке: `pageOrientation` ·
`scale` · `collate` · `copies` · `perPage` · `topMargin` · `leftMargin` · `bottomMargin` ·
`rightMargin` · `headerSize` · `footerSize` · `fitToPage` · `blackAndWhite` · `printerName` ·
`paper` · `paperSource` · `pageWidth` · `pageHeight` · `duplexType` · `pagePlacementAlternation` ·
`firstPageNumber`. Номер первой страницы принадлежит именно параметрам печати, а не колонтитулу.
## Группы строк и колонок
```xml ```xml
<vgLevels>2</vgLevels> <vgLevels>2</vgLevels>
@@ -616,7 +649,7 @@ current-config не объявляет, поэтому вынести объяв
<hg></hg> <!-- то же для колонок --> <hg></hg> <!-- то же для колонок -->
``` ```
Строки группируются часто (1 797 макетов, 170 967 группировок), колонки — почти никогда Строки группируются часто (1 797 макетов, 170 967 групп), колонки — почти никогда
(1 макет, 1 группировка). Порядок документных тегов: `height``vgLevels``vgRows``vg` (1 макет, 1 группировка). Порядок документных тегов: `height``vgLevels``vgRows``vg`
`hg`; `vgLevels` пишется только при наличии группировок. `hg`; `vgLevels` пишется только при наличии группировок.
+53 -1
View File
@@ -89,6 +89,8 @@
| `columnSets` | нет | — | Дополнительные колоночные раскладки (см. `mxl-dsl-styles.md`) | | `columnSets` | нет | — | Дополнительные колоночные раскладки (см. `mxl-dsl-styles.md`) |
| `rowGroups` | нет | — | Группы строк (см. ниже) | | `rowGroups` | нет | — | Группы строк (см. ниже) |
| `columnGroups` | нет | — | Группы колонок (см. ниже) | | `columnGroups` | нет | — | Группы колонок (см. ниже) |
| `header` / `footer` | нет | — | Верхний и нижний колонтитулы (см. ниже) |
| `printSettings` | нет | — | Параметры печати (см. ниже) |
## Области (`areas[]`) ## Области (`areas[]`)
@@ -302,6 +304,56 @@
и компилятор проставляет это сам. `autoSize` и `box` независимы: при автоподгоне размера и компилятор проставляет это сам. `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`. Незнакомый ключ — ошибка. Порядок в объекте не
важен: компилятор пишет свойства в том порядке, что и платформа.
## Группы строк и колонок ## Группы строк и колонок
Диапазон строк или колонок, который сворачивается кнопкой на полях. Задаются плоским списком; Диапазон строк или колонок, который сворачивается кнопкой на полях. Задаются плоским списком;
@@ -347,7 +399,7 @@ XML не возвращается.
- объединения, не привязанные к ячейке (по всей высоте или ширине документа); - объединения, не привязанные к ячейке (по всей высоте или ширине документа);
- рисунки и картинки, в том числе штрихкоды; - рисунки и картинки, в том числе штрихкоды;
- колонтитулы, параметры печати, область печати. - область печати.
Пересборка макета из DSL — это полная перегенерация, а не точечная правка XML, поэтому Пересборка макета из DSL — это полная перегенерация, а не точечная правка XML, поэтому
diff после round-trip обычно шире фактической доработки. diff после round-trip обычно шире фактической доработки.
@@ -0,0 +1,10 @@
{
"name": "Ошибка: неизвестный ключ параметров печати",
"input": {
"columns": 1,
"areas": [{ "rows": [["a"]] }],
"printSettings": { "topMargin": 500, "marginTop": 500 }
},
"params": { "outputPath": "Template.xml" },
"expectError": "printSettings: unknown key \"marginTop\""
}
@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<document xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<languageSettings>
<currentLanguage>ru</currentLanguage>
<defaultLanguage>ru</defaultLanguage>
<languageInfo>
<id>ru</id>
<code>Русский</code>
<description>Русский</description>
</languageInfo>
<languageInfo>
<id>en</id>
<code>Английский</code>
<description>Английский</description>
</languageInfo>
</languageSettings>
<columns>
<size>1</size>
</columns>
<rowsItem>
<index>0</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Текст</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<leftHeader>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Слева</v8:content>
</v8:item>
</tl>
</leftHeader>
<centerHeader>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Стр. [&amp;НомерСтраницы] из [&amp;СтраницВсего]. Дата [&amp;Дата][&amp;Время]</v8:content>
</v8:item>
</tl>
</centerHeader>
<rightHeader>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Справа</v8:content>
</v8:item>
</tl>
</rightHeader>
<leftFooter>
<f>3</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Нижний слева</v8:content>
</v8:item>
</tl>
</leftFooter>
<centerFooter>
<f>3</f>
<tfl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>&lt;b&gt;Форматируемая&lt;/&gt; &lt;fontsize 12&gt;&lt;colorstyle -16&gt;строка&lt;/&gt;&lt;/&gt;</v8:content>
</v8:item>
</tfl>
</centerFooter>
<rightFooter>
<f>3</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Нижний справа</v8:content>
</v8:item>
</tl>
</rightFooter>
<templateMode>true</templateMode>
<defaultFormatIndex>1</defaultFormatIndex>
<height>1</height>
<vgRows>1</vgRows>
<printSettings>
<topMargin>1000</topMargin>
<leftMargin>1000</leftMargin>
<bottomMargin>1000</bottomMargin>
<rightMargin>1000</rightMargin>
<headerSize>1200</headerSize>
<footerSize>1000</footerSize>
<firstPageNumber>15</firstPageNumber>
</printSettings>
<font faceName="Arial" height="8" bold="false" italic="false" underline="false" strikeout="false" kind="Absolute" scale="100"/>
<format>
<width>72</width>
</format>
<format>
<font>0</font>
<height>-1</height>
<width>2</width>
<verticalAlignment>Center</verticalAlignment>
</format>
<format>
<font>0</font>
<height>1</height>
<width>21</width>
<verticalAlignment>Top</verticalAlignment>
</format>
</document>
@@ -0,0 +1,29 @@
{
"name": "Колонтитулы и параметры печати",
"input": {
"columns": 2,
"fonts": { "мелкий": { "face": "Arial", "size": 8 } },
"areas": [{ "rows": [["Отчёт"]] }],
"header": {
"font": "мелкий",
"verticalAlignment": "Bottom",
"startPage": 2,
"left": "Слева\nвторая строка",
"center": "Стр. [&НомерСтраницы] из [&СтраницВсего]",
"right": { "ru": "Справа", "en": "Right" }
},
"footer": {
"font": "мелкий",
"verticalAlignment": "Top",
"show": false,
"center": { "formatted": "<b>Итог</> <colorstyle -16>красным</>" }
},
"printSettings": {
"pageOrientation": "Landscape",
"topMargin": 500, "leftMargin": 500, "bottomMargin": 500, "rightMargin": 500,
"headerSize": 1000, "footerSize": 1000,
"fitToPage": true, "blackAndWhite": false, "firstPageNumber": 7
}
},
"params": { "outputPath": "Template.xml" }
}
@@ -0,0 +1,13 @@
{
"name": "Раундтрип макета ПЛАТФОРМЫ байт в байт: Колонтитулы",
"setup": "fixture:platform-roundtrip",
"preRun": [
{ "script": "mxl-decompile/scripts/mxl-decompile",
"args": { "-TemplatePath": "Колонтитулы.xml", "-OutputPath": "back.json" },
"cwd": "{workDir}" }
],
"params": { "outputPath": "out.xml" },
"noSnapshot": "эталон — файл платформы, сверяется expect.filesEqual",
"expect": { "filesEqual": { "actual": "out.xml", "expected": "Колонтитулы.xml" } },
"inputFrom": "back.json"
}
@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<document xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<languageSettings>
<currentLanguage>ru</currentLanguage>
<defaultLanguage>ru</defaultLanguage>
<languageInfo>
<id>ru</id>
<code>Русский</code>
<description>Русский</description>
</languageInfo>
</languageSettings>
<columns>
<size>2</size>
</columns>
<rowsItem>
<index>0</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Отчёт</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<leftHeader>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Слева
вторая строка</v8:content>
</v8:item>
</tl>
</leftHeader>
<centerHeader>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Стр. [&amp;НомерСтраницы] из [&amp;СтраницВсего]</v8:content>
</v8:item>
</tl>
</centerHeader>
<rightHeader>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Справа</v8:content>
</v8:item>
<v8:item>
<v8:lang>en</v8:lang>
<v8:content>Right</v8:content>
</v8:item>
</tl>
</rightHeader>
<centerFooter>
<f>3</f>
<tfl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>&lt;b&gt;Итог&lt;/&gt; &lt;colorstyle -16&gt;красным&lt;/&gt;</v8:content>
</v8:item>
</tfl>
</centerFooter>
<templateMode>true</templateMode>
<defaultFormatIndex>1</defaultFormatIndex>
<height>1</height>
<vgRows>1</vgRows>
<printSettings>
<pageOrientation>Landscape</pageOrientation>
<topMargin>500</topMargin>
<leftMargin>500</leftMargin>
<bottomMargin>500</bottomMargin>
<rightMargin>500</rightMargin>
<headerSize>1000</headerSize>
<footerSize>1000</footerSize>
<fitToPage>true</fitToPage>
<blackAndWhite>false</blackAndWhite>
<firstPageNumber>7</firstPageNumber>
</printSettings>
<font faceName="Arial" height="8" bold="false" italic="false" underline="false" strikeout="false" kind="Absolute" scale="100"/>
<format>
<width>10</width>
</format>
<format>
<font>0</font>
<height>1</height>
<width>2</width>
<verticalAlignment>Bottom</verticalAlignment>
</format>
<format>
<font>0</font>
<height>-1</height>
<width>1</width>
<verticalAlignment>Top</verticalAlignment>
</format>
</document>
@@ -0,0 +1,21 @@
{
"name": "Roundtrip — колонтитулы и параметры печати",
"preRun": [
{ "script": "mxl-compile/scripts/mxl-compile",
"input": {
"columns": 2,
"fonts": { "мелкий": { "face": "Arial", "size": 8 } },
"areas": [{ "rows": [["Отчёт"]] }],
"header": { "font": "мелкий", "verticalAlignment": "Bottom", "startPage": 2,
"left": "Слева\nвторая строка", "center": "Стр. [&НомерСтраницы]" },
"footer": { "font": "мелкий", "show": false,
"center": { "formatted": "<b>Итог</>" } },
"printSettings": { "pageOrientation": "Landscape", "fitToPage": true, "firstPageNumber": 7 }
},
"args": { "-JsonPath": "{inputFile}", "-OutputPath": "Template.xml" },
"cwd": "{workDir}" }
],
"params": { "templatePath": "Template.xml" },
"args_extra": ["-OutputPath", "{workDir}/back.json"],
"expect": { "files": ["back.json"], "stdoutContains": "[OK] Decompiled:" }
}
@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<document xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<languageSettings>
<currentLanguage>ru</currentLanguage>
<defaultLanguage>ru</defaultLanguage>
<languageInfo>
<id>ru</id>
<code>Русский</code>
<description>Русский</description>
</languageInfo>
</languageSettings>
<columns>
<size>2</size>
</columns>
<rowsItem>
<index>0</index>
<row>
<c>
<c>
<f>0</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Отчёт</v8:content>
</v8:item>
</tl>
</c>
</c>
</row>
</rowsItem>
<leftHeader>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Слева
вторая строка</v8:content>
</v8:item>
</tl>
</leftHeader>
<centerHeader>
<f>2</f>
<tl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Стр. [&amp;НомерСтраницы]</v8:content>
</v8:item>
</tl>
</centerHeader>
<centerFooter>
<f>3</f>
<tfl>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>&lt;b&gt;Итог&lt;/&gt;</v8:content>
</v8:item>
</tfl>
</centerFooter>
<templateMode>true</templateMode>
<defaultFormatIndex>1</defaultFormatIndex>
<height>1</height>
<vgRows>1</vgRows>
<printSettings>
<pageOrientation>Landscape</pageOrientation>
<fitToPage>true</fitToPage>
<firstPageNumber>7</firstPageNumber>
</printSettings>
<font faceName="Arial" height="8" bold="false" italic="false" underline="false" strikeout="false" kind="Absolute" scale="100"/>
<format>
<width>10</width>
</format>
<format>
<font>0</font>
<height>1</height>
<width>2</width>
<verticalAlignment>Bottom</verticalAlignment>
</format>
<format>
<font>0</font>
<height>-1</height>
<width>1</width>
</format>
</document>
@@ -0,0 +1,10 @@
{
"columns": 2,
"defaultWidth": 10,
"fonts": { "default": { "face": "Arial", "size": 8 } },
"styles": {},
"header": { "font": "default", "startPage": 2, "verticalAlignment": "Bottom", "left": "Слева\nвторая строка", "center": "Стр. [&НомерСтраницы]" },
"footer": { "font": "default", "show": false, "center": { "formatted": "<b>Итог</>" } },
"printSettings": { "pageOrientation": "Landscape", "fitToPage": true, "firstPageNumber": 7 },
"areas": [{ "rows": [["Отчёт"]] }]
}