fix(mxl-compile,mxl-decompile): пустой текст ячейки и расхождение портов на нём

Пустой текст платформа хранит только самозакрывающимся тегом: в корпусе ERP
таких 1 224 460, а из 780 934 непустых блоков нет ни одного, где пусты все
языки. Компилятор же на `text: ""` писал блок с пустым элементом языка —
запись, которой в выгрузках не существует. Теперь любая пустая форма
(строка, пустой объект, все языки пустыми) даёт один и тот же тег, а
декомпилятор возвращает её канонической пустой строкой.

Отсюда же росло расхождение портов. Свёртка текста на пустой карте языков
возвращала null, если ДРУГОГО текста в макете нет вовсе: набор языков
документа при этом пуст. Дальше py писал пропуск колонки, а ps1 наступал
на разворачивание одноэлементного массива — список из одного $null
превращался в $null, и строка целиком уходила в пустые.

Проверено на 12 корпусных макетах: 1411 пустых текстов из 1411 вернулись
на место, JSON портов совпадает.
This commit is contained in:
Nick Shirokov
2026-08-16 12:30:19 +03:00
parent fef55043cf
commit e048fcff70
9 changed files with 186 additions and 14 deletions
@@ -1,4 +1,4 @@
# mxl-compile v1.50 — Compile 1C spreadsheet from JSON
# mxl-compile v1.51 — Compile 1C spreadsheet from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -1191,10 +1191,12 @@ function Emit-CellText {
} else {
foreach ($l in $textLanguages) { $pairs += @{ Lang = $l; Text = "$value" } }
}
# Пустой объект — отдельное состояние: тег текста есть, языков в нём нет. Платформа
# пишет его самозакрывающимся. Для авторинга бесполезно (визуально это тот же пустой
# текст, что и ""), поэтому в описании DSL записи нет — она нужна раундтрипу.
if ($pairs.Count -eq 0) {
# Пустой текст платформа хранит ТОЛЬКО самозакрывающимся тегом: в корпусе ERP таких
# 1 224 460, а из 780 934 непустых блоков ни одного со всеми пустыми языками нет.
# Поэтому и пустая строка, и пустой объект дают одну и ту же запись.
$allEmpty = $true
foreach ($pr in $pairs) { if ("$($pr.Text)" -ne '') { $allEmpty = $false; break } }
if ($pairs.Count -eq 0 -or $allEmpty) {
X "`t`t`t`t`t<tl/>"
return
}
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# mxl-compile v1.50 — Compile 1C spreadsheet from JSON
# mxl-compile v1.51 — Compile 1C spreadsheet from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import hashlib
@@ -1210,10 +1210,10 @@ def main():
pairs = [(str(k), str(v)) for k, v in value.items()]
else:
pairs = [(lang, str(value)) for lang in text_languages]
# Пустой объект — отдельное состояние: тег текста есть, языков в нём нет. Платформа
# пишет его самозакрывающимся. Для авторинга бесполезно (визуально это тот же пустой
# текст, что и ""), поэтому в описании DSL записи нет — она нужна раундтрипу.
if not pairs:
# Пустой текст платформа хранит ТОЛЬКО самозакрывающимся тегом: в корпусе ERP таких
# 1 224 460, а из 780 934 непустых блоков ни одного со всеми пустыми языками нет.
# Поэтому и пустая строка, и пустой объект дают одну и ту же запись.
if not pairs or all(content == '' for _, content in pairs):
lines.append('\t\t\t\t\t<tl/>')
return
lines.append('\t\t\t\t\t<tl>')
@@ -1,4 +1,4 @@
# mxl-decompile v1.29 — Decompile 1C spreadsheet to JSON
# mxl-decompile v1.30 — Decompile 1C spreadsheet to JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -723,6 +723,10 @@ $textLanguages = @($docLangs.Keys)
function Get-DslText {
param($byLang)
if ($byLang -isnot [System.Collections.IDictionary]) { return $byLang }
# Пустой текст (<tl/>) записывается в DSL пустой строкой — той же формой, что
# авторская. Без этой ветки макет, где ДРУГОГО текста нет вовсе, отдавал $null,
# и ячейка с пустым текстом теряла содержимое.
if ($byLang.Count -eq 0) { return '' }
if ($byLang.Count -ne $textLanguages.Count) { return $byLang }
$common = $null
foreach ($l in $textLanguages) {
@@ -1219,8 +1223,10 @@ function ConvertTo-PositionalCells {
# Позиционная форма ценна компактностью: если она длиннее объектной, смысла в ней нет.
$a = Try-InlineJson $out
$b = Try-InlineJson $cells
if ($null -ne $a -and $null -ne $b -and $a.Length -gt $b.Length) { return $cells }
return $out
if ($null -ne $a -and $null -ne $b -and $a.Length -gt $b.Length) { return ,$cells }
# Запятая обязательна: без неё список из одной ячейки разворачивается в саму ячейку,
# а список из одного $null — в $null, и строка целиком уходила в пустые.
return ,$out
}
# --- 12. Build areas ---
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# mxl-decompile v1.29 — Decompile 1C spreadsheet to JSON
# mxl-decompile v1.30 — Decompile 1C spreadsheet to JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -849,6 +849,11 @@ def main():
def get_dsl_text(by_lang):
if not isinstance(by_lang, dict):
return by_lang
# Пустой текст (<tl/>) записывается в DSL пустой строкой — той же формой, что
# авторская. Без этой ветки макет, где ДРУГОГО текста нет вовсе, отдавал null,
# и ячейка с пустым текстом теряла содержимое.
if not by_lang:
return ''
if len(by_lang) != len(text_languages):
return by_lang
common = None