feat(mxl-compile,mxl-decompile): значение ячейки-поля ввода и настройки элемента управления

Ключ ячейки value несёт значение, набранное в поле ввода, ключ control —
сериализованные настройки самого элемента управления. Оба живут у ячейки,
а не в палитре формата: у двух ячеек с одинаковым оформлением значения
разные.

Тип значения выражается литералом JSON и приведения к объявленному типу
НЕ делается. Так пишет платформа: ссылочный и составной тип она хранит
строкой, а при смене типа ячейки прежнее значение не переписывает — на
корпусе 560 ячеек объявлены числом, но несут строку. Приведение здесь
означало бы переписывать данные при пересборке. Дата литералом JSON не
выражается, поэтому едет строкой и читается как дата только у ячейки,
объявленной датой.

Настройки элемента управления возим дословно: 26 370 вхождений корпуса
дают всего 163 различных блоба, разбирать структуру незачем. Переводы
строк внутри блоба приводим к LF на чтении — XML-парсеры двух портов
нормализуют их по-разному, и без этого порты давали разный JSON на одном
файле.

Стенд СЗначениями снова собирается байт в байт обоими портами: фикстура
обновлена до версии со значениями, флажком и блобом настроек.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-15 13:32:40 +03:00
co-authored by Claude Opus 5
parent 66bb285636
commit 43d2f660da
14 changed files with 951 additions and 17 deletions
@@ -1,4 +1,4 @@
# mxl-decompile v1.21 — Decompile 1C spreadsheet to JSON
# mxl-decompile v1.22 — Decompile 1C spreadsheet to JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -92,6 +92,25 @@ $valueControlNames = @{
# значение» без указания типа — самостоятельное состояние.
# Префикс ссылочного типа разрешаем по URI, а не по имени: локальное объявление платформа
# называет сгенерированным dNpM. Чужое пространство имён оставляем как есть.
# Значение ячейки-поля ввода → литерал DSL. Тип значения выражается самим литералом:
# число → числом, булево → true/false, строка и дата → строкой. Пустое значение пишем
# как есть (0, false, пустая дата), а не схлопываем: так оно собирается обратно тем же
# типом независимо от того, какой тип объявлен у ячейки.
function ConvertTo-DslValue {
param([string]$xsiType, [string]$text)
if ($null -eq $text) { $text = '' }
if ($xsiType -ceq 'xs:boolean') { return ($text.Trim() -ceq 'true') }
if ($xsiType -ceq 'xs:decimal') {
$s = $text.Trim()
$i = 0; $d = 0.0
if ($s -notlike '*.*' -and [int]::TryParse($s, [ref]$i)) { return $i }
if ([double]::TryParse($s, [System.Globalization.NumberStyles]::Float,
[System.Globalization.CultureInfo]::InvariantCulture, [ref]$d)) { return $d }
return $s
}
return $text
}
function Get-ValueQualifier {
param($quals, [string]$kind, [string]$name, [string]$default = '')
if (-not $quals.ContainsKey($kind)) { return $default }
@@ -390,6 +409,22 @@ foreach ($riNode in $root.SelectNodes("d:rowsItem", $ns)) {
$dNode = $cContent.SelectSingleNode("d:detailParameter", $ns)
if ($dNode) { $detail = $dNode.InnerText }
# Значение ячейки-поля ввода. Тип значения свой, из объявленного не выводится,
# поэтому читаем и его.
$value = $null
$vNode = $cContent.SelectSingleNode("d:v", $ns)
if ($vNode) {
$value = @{ Type = $vNode.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance"); Text = $vNode.InnerText }
}
# Настройки элемента управления — сериализованный base64 у самой ячейки.
# Структуру не разбираем, возим дословно.
$control = $null
$ctlNode = $cContent.SelectSingleNode("d:control", $ns)
# Переводы строк внутри блоба приводим к LF: XML-парсеры нормализуют их
# по-разному, и без этого порты давали разный JSON на одном файле.
if ($ctlNode -and $ctlNode.InnerText) { $control = $ctlNode.InnerText -replace "`r`n", "`n" }
# Текст ячейки платформа хранит по элементу на язык. Раньше брался ПЕРВЫЙ, и всё
# остальное терялось — в корпусе ERP 98% макетов держат текст и под ru, и под en.
# Здесь собираем «язык → текст» как есть; свернётся в строку позже, когда станет
@@ -423,6 +458,8 @@ foreach ($riNode in $root.SelectNodes("d:rowsItem", $ns)) {
FormatIdx = $cellFmtIdx
Param = $param
Detail = $detail
Value = $value
Control = $control
Text = $text
HasText = $hasText
}
@@ -1135,6 +1172,10 @@ foreach ($area in $blocks) {
# форматах корпуса.
if ($name -cne 'input') { $dslCell["controlType"] = $name }
}
if ($null -ne $cell.Value) {
$dslCell["value"] = ConvertTo-DslValue $cell.Value.Type $cell.Value.Text
}
if ($cell.Control) { $dslCell["control"] = $cell.Control }
}
# Content
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# mxl-decompile v1.21 — Decompile 1C spreadsheet to JSON
# mxl-decompile v1.22 — Decompile 1C spreadsheet to JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -187,6 +187,23 @@ def value_type_to_dsl(node):
return ' + '.join(parts)
def value_to_dsl(xsi_type, text):
"""Значение ячейки-поля ввода → литерал DSL. Тип значения выражается самим литералом:
число → числом, булево → true/false, строка и дата → строкой. Пустое значение пишем
как есть (0, false, пустая дата), а не схлопываем: так оно собирается обратно тем же
типом независимо от того, какой тип объявлен у ячейки."""
text = text or ''
if xsi_type == 'xs:boolean':
return text.strip() == 'true'
if xsi_type == 'xs:decimal':
s = text.strip()
try:
return int(s) if '.' not in s else float(s)
except ValueError:
return s
return text
def int_of(node, default=0):
if node is not None and node.text:
return int(node.text)
@@ -556,6 +573,22 @@ def main():
if d_node is not None and d_node.text:
detail = d_node.text
# Значение ячейки-поля ввода. Тип значения свой, из объявленного не выводится,
# поэтому читаем и его: пустое значение сворачивается в "", остальные едут как есть.
value = None
v_node = find(c_content, "d:v")
if v_node is not None:
value = (v_node.get(f'{{{XSI_NS}}}type') or '', (v_node.text or ''))
# Настройки элемента управления — сериализованный base64 у самой ячейки.
# Структуру не разбираем, возим дословно.
control = None
ctl_node = find(c_content, "d:control")
if ctl_node is not None and ctl_node.text:
# Переводы строк внутри блоба приводим к LF: XML-парсеры нормализуют их
# по-разному, и без этого порты давали разный JSON на одном файле.
control = ctl_node.text.replace('\r\n', '\n')
# Текст ячейки платформа хранит по элементу на язык. Раньше брался ПЕРВЫЙ, и всё
# остальное терялось — в корпусе ERP 98% макетов держат текст и под ru, и под en.
# Здесь собираем «язык → текст» как есть; свернётся в строку позже, когда станет
@@ -587,6 +620,8 @@ def main():
"FormatIdx": cell_fmt_idx,
"Param": param,
"Detail": detail,
"Value": value,
"Control": control,
"Text": text,
"HasText": has_text,
})
@@ -1128,6 +1163,10 @@ def main():
# форматах корпуса.
if name != "input":
dsl_cell["controlType"] = name
if cell["Value"] is not None:
dsl_cell["value"] = value_to_dsl(*cell["Value"])
if cell["Control"]:
dsl_cell["control"] = cell["Control"]
# Content
fill_type = cell_fmt["FillType"] if cell_fmt else ""