fix(mxl-compile): не эмитить fillType=Text, UUID для идентификатора раскладки

Две правки по корпусу и по 1С-сертификации.

fillType=Text платформа практически не пишет: на выборке корпуса 344 981 текстовая
ячейка из 348 023 (99,1%) ссылается на формат БЕЗ fillType — наличие <tl> и так
означает текст. Parameter и Template платформа пишет, их оставляем. Правило то же,
что и везде: эмитим то, что эмитит платформа, а не то, что верно в рантайме.

Идентификатор колоночной раскладки платформа хранит как UUID и другой не принимает —
макет с <id>узкая</id> она отвергала целиком. Имя, похожее на UUID (то есть пришедшее
декомпиляцией), проходит насквозь, иначе поехал бы раундтрип; читаемое имя автора
превращается в UUID ДЕТЕРМИНИРОВАННО, из хэша имени, чтобы повторная компиляция
давала тот же файл. Оба порта дают одинаковый хэш.

Дефект с идентификатором нашла 1С-сертификация: ни юнит-кейсы, ни раундтрип по
корпусу его не видели — там идентификаторы приходят из исходных макетов и уже
являются UUID.

Дрейф 33 снэпшотов разобран построчно, посторонних строк нет: 116 сменившихся
индексов формата, 49 удалённых fillType, 43 перестановки свойств внутри палитры,
16 границ <format> (палитра стала короче).

Проверено: тесты 53/53 на обоих рантаймах, verify-snapshots 23/23.

Замечание на будущее: на корпусе эта правка снимает лишь 170 расхождений из 64 865
в категории формата ячейки. Основная причина другая — платформа даёт неоформленной
ячейке ссылку на формат по умолчанию, а компилятор заводит ей собственный формат
с <font>. Это отдельный заход, он сдвинет снэпшоты повторно.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-10 19:58:35 +03:00
co-authored by Claude Opus 5
parent 8302b17814
commit cbe7abdcbb
36 changed files with 119 additions and 189 deletions
@@ -1,4 +1,4 @@
# mxl-compile v1.20 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
# mxl-compile v1.21 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -391,14 +391,29 @@ $colWidthMap = Build-ColWidthMap $def.columnWidths
# columnSets, ключ — идентификатор, на него ссылается область ключом columnSet.
# Склейки по содержимому нет: в корпусе полно раскладок с одинаковым содержимым и разными
# идентификаторами, поэтому опознаёт раскладку только идентификатор.
# Платформа хранит идентификатор раскладки как UUID и другой не принимает. Имя из
# columnSets, полученное декомпиляцией, уже UUID — оставляем как есть, иначе раундтрип
# перестал бы совпадать. Читаемое имя, написанное автором, превращаем в UUID ДЕТЕРМИНИРОВАННО
# (из хэша имени), чтобы повторная компиляция давала тот же файл.
function ConvertTo-LayoutId {
param([string]$name)
if ($name -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') {
return $name
}
$md5 = [System.Security.Cryptography.MD5]::Create()
$h = ($md5.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($name)) | ForEach-Object { $_.ToString('x2') }) -join ''
return "$($h.Substring(0,8))-$($h.Substring(8,4))-$($h.Substring(12,4))-$($h.Substring(16,4))-$($h.Substring(20,12))"
}
$columnLayouts = @()
$columnLayouts += @{ Id = $null; Size = $totalColumns; Widths = $colWidthMap }
$columnLayouts += @{ Id = $null; Name = $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
Id = ConvertTo-LayoutId $prop.Name
Name = $prop.Name
Size = $size
Widths = Build-ColWidthMap $cs.columnWidths
}
@@ -539,11 +554,13 @@ function Esc-XmlText {
}
# Helper: determine fillType from cell content
# Text НЕ эмитим: платформа его практически не пишет — на выборке корпуса 344 981 текстовая
# ячейка из 348 023 (99,1%) ссылается на формат БЕЗ fillType. Наличие <tl> и так означает
# текст, поэтому тег избыточен. Parameter и Template платформа пишет — их оставляем.
function Get-FillType {
param($cell)
if ($cell.param) { return "Parameter" }
if ($cell.template) { return "Template" }
if ($cell.text) { return "Text" }
return ""
}
@@ -679,7 +696,7 @@ foreach ($area in $def.areas) {
# Ширина сетки берётся из раскладки области: у каждой она своя.
$areaMaxCols = $totalColumns
if ($area.PSObject.Properties['columnSet'] -and "$($area.columnSet)" -ne '') {
$lay = @($columnLayouts | Where-Object { $_.Id -eq "$($area.columnSet)" })[0]
$lay = @($columnLayouts | Where-Object { $_.Name -eq "$($area.columnSet)" })[0]
if ($lay) { $areaMaxCols = [int]$lay.Size }
}
$openByCol = @{}
@@ -797,14 +814,16 @@ foreach ($area in $def.areas) {
[Console]::Error.WriteLine("'columnSet' must be a name declared in columnSets, got an object: area `"$($area.name)`"")
exit 1
}
$areaColumnSet = if ($area.PSObject.Properties['columnSet']) { "$($area.columnSet)" } else { '' }
$areaColumnSetName = if ($area.PSObject.Properties['columnSet']) { "$($area.columnSet)" } else { '' }
$areaColumnSet = ''
$areaLayout = $columnLayouts[0]
if ($areaColumnSet) {
$areaLayout = @($columnLayouts | Where-Object { $_.Id -eq $areaColumnSet })[0]
if ($areaColumnSetName) {
$areaLayout = @($columnLayouts | Where-Object { $_.Name -eq $areaColumnSetName })[0]
if (-not $areaLayout) {
[Console]::Error.WriteLine("Unknown 'columnSet': `"$areaColumnSet`" is not declared in columnSets")
[Console]::Error.WriteLine("Unknown 'columnSet': `"$areaColumnSetName`" is not declared in columnSets")
exit 1
}
$areaColumnSet = $areaLayout.Id
}
# Ширина сетки — у КАЖДОЙ раскладки своя, поэтому позиции колонок сверяем с ней,
# а не с документным columns (у макетов с раскладками умолчание бывает и пустым).
@@ -1,7 +1,8 @@
#!/usr/bin/env python3
# mxl-compile v1.20 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
# mxl-compile v1.21 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import hashlib
import json
import math
import os
@@ -495,11 +496,23 @@ def main():
# columnSets, ключ — идентификатор, на него ссылается область ключом columnSet.
# Склейки по содержимому нет: в корпусе полно раскладок с одинаковым содержимым и разными
# идентификаторами, поэтому опознаёт раскладку только идентификатор.
column_layouts = [{'Id': None, 'Size': total_columns, 'Widths': col_width_map}]
for set_id, cs in (defn.get('columnSets') or {}).items():
def to_layout_id(name):
"""Платформа хранит идентификатор раскладки как UUID и другой не принимает. Имя,
полученное декомпиляцией, уже UUID — оставляем как есть, иначе раундтрип перестал бы
совпадать. Читаемое имя автора превращаем в UUID ДЕТЕРМИНИРОВАННО (из хэша имени),
чтобы повторная компиляция давала тот же файл."""
if re.match(r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-'
r'[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$', name):
return name
h = hashlib.md5(name.encode('utf-8')).hexdigest()
return f'{h[0:8]}-{h[8:12]}-{h[12:16]}-{h[16:20]}-{h[20:32]}'
column_layouts = [{'Id': None, 'Name': None, 'Size': total_columns, 'Widths': col_width_map}]
for set_name, 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,
'Id': to_layout_id(set_name),
'Name': set_name,
'Size': size,
'Widths': build_col_width_map(cs.get('columnWidths')),
})
@@ -589,12 +602,13 @@ def main():
# 6c. Helper: determine fillType from cell content
def get_fill_type(cell):
"""Text НЕ эмитим: платформа его практически не пишет — на выборке корпуса 344 981
текстовая ячейка из 348 023 (99,1%) ссылается на формат БЕЗ fillType. Наличие <tl>
и так означает текст. Parameter и Template платформа пишет — их оставляем."""
if cell.get('param'):
return 'Parameter'
if cell.get('template'):
return 'Template'
if cell.get('text'):
return 'Text'
return ''
# Helper: register a cell format and return its index
@@ -715,7 +729,7 @@ def main():
# Ширина сетки берётся из раскладки области: у каждой она своя.
area_max_cols = total_columns
if area.get('columnSet'):
lay = next((x for x in column_layouts if x['Id'] == str(area['columnSet'])), None)
lay = next((x for x in column_layouts if x['Name'] == str(area['columnSet'])), None)
if lay:
area_max_cols = int(lay['Size'])
open_by_col = {}
@@ -821,14 +835,16 @@ def main():
print(f'\'columnSet\' must be a name declared in columnSets, got an object:'
f' area "{area.get("name", "")}"', file=sys.stderr)
sys.exit(1)
area_column_set = str(area.get('columnSet') or '')
area_column_set_name = str(area.get('columnSet') or '')
area_column_set = ''
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_column_set_name:
area_layout = next((x for x in column_layouts if x['Name'] == area_column_set_name), None)
if area_layout is None:
print(f'Unknown \'columnSet\': "{area_column_set}" is not declared in columnSets',
print(f'Unknown \'columnSet\': "{area_column_set_name}" is not declared in columnSets',
file=sys.stderr)
sys.exit(1)
area_column_set = area_layout['Id']
# Ширина сетки — у КАЖДОЙ раскладки своя, поэтому позиции колонок сверяем с ней,
# а не с документным columns (у макетов с раскладками умолчание бывает и пустым).
area_columns = int(area_layout['Size'])