feat(mxl-compile,mxl-decompile): шрифт ссылкой на стиль или системный

Шрифт бывает не собственным описанием, а ссылкой: на элемент стиля конфигурации
(style:) или на системный шрифт (sys:). Своих атрибутов у такого шрифта нет, и мы
превращали его в пустую запись — faceName="" height="0". ps1 при сборке подставлял
туда Arial 10, то есть подменял данные молча.

В корпусе таких шрифтов 272 в 213 макетах из 10 924: StyleItem 209, WindowsFont 63.

Запись — та же, что у шрифта в описании формы: { "ref": "style:TextFont" }.
kind выводится из префикса, ключом быть не обязан.

Синтетический стенд показал деталь, которую по корпусу было не разглядеть: префикс
sys в корне документа не объявлен, поэтому платформа дописывает объявление xmlns
прямо на узел шрифта — тот же приём, что с цветами из web-палитры.

Порты после этого сошлись ПОЛНОСТЬЮ: на пилоте из 40 макетов совпадают и JSON
декомпиляторов, и собранный XML. Макет со шрифтами добавлен в побайтовую
регрессию, стенд 8 из 13.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-12 12:47:24 +03:00
co-authored by Claude Opus 5
parent 0a8671a166
commit 98bcf79b41
8 changed files with 262 additions and 6 deletions
@@ -1,4 +1,4 @@
# mxl-decompile v1.19 — Decompile 1C spreadsheet to JSON
# mxl-decompile v1.20 — Decompile 1C spreadsheet to JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -97,6 +97,9 @@ function ConvertTo-FontSize {
$rawFonts = @()
foreach ($fNode in $root.SelectNodes("d:font", $ns)) {
$rawFonts += @{
# Шрифт бывает ссылкой на элемент стиля (style:) или системный шрифт (sys:) —
# тогда своих атрибутов у него нет, и без этого он превращался в пустую запись.
Ref = $fNode.GetAttribute("ref")
Face = $fNode.GetAttribute("faceName")
Size = ConvertTo-FontSize $fNode.GetAttribute("height")
Bold = $fNode.GetAttribute("bold") -eq "true"
@@ -454,6 +457,7 @@ if ($rawFonts.Count -gt 0) {
function Get-FontKey {
param($f)
if ($f.Ref) { return "ref=$($f.Ref)" }
return "$($f.Face)|$($f.Size)|$($f.Bold)|$($f.Italic)|$($f.Underline)|$($f.Strikeout)"
}
@@ -473,7 +477,11 @@ for ($i = 1; $i -lt $rawFonts.Count; $i++) {
$name = $null
if ($f.Face -eq $df.Face -and $f.Size -eq $df.Size) {
if ($f.Ref) {
# Имя из ссылки: style:SmallTextFont → small-text-font-подобное читаемое имя не даёт
# ничего сверх самой ссылки, поэтому берём часть после префикса.
$name = ($f.Ref -replace '^[^:]+:', '').ToLowerInvariant()
} elseif ($f.Face -eq $df.Face -and $f.Size -eq $df.Size) {
if ($f.Bold -and -not $df.Bold -and -not $f.Italic -and -not $f.Underline -and -not $f.Strikeout) {
$name = "bold"
} elseif ($f.Italic -and -not $df.Italic -and -not $f.Bold) {
@@ -1115,6 +1123,7 @@ if ($colWidthMap.Count -gt 0) {
$fontsOut = [ordered]@{}
foreach ($name in $fontDefs.Keys) {
$f = $fontDefs[$name]
if ($f.Ref) { $fontsOut[$name] = [ordered]@{ ref = $f.Ref }; continue }
$fOut = [ordered]@{ face = $f.Face; size = $f.Size }
if ($f.Bold) { $fOut["bold"] = $true }
if ($f.Italic) { $fOut["italic"] = $true }
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# mxl-decompile v1.19 — Decompile 1C spreadsheet to JSON
# mxl-decompile v1.20 — Decompile 1C spreadsheet to JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -267,6 +267,9 @@ def main():
raw_fonts = []
for f_node in findall(root, "d:font"):
raw_fonts.append({
# Шрифт бывает ссылкой на элемент стиля (style:) или системный шрифт (sys:) —
# тогда своих атрибутов у него нет, и без этого он превращался в пустую запись.
"Ref": f_node.get("ref", ""),
"Face": f_node.get("faceName", ""),
"Size": to_font_size(f_node.get("height", "0")),
"Bold": f_node.get("bold") == "true",
@@ -610,6 +613,10 @@ def main():
font_defs["default"] = raw_fonts[0]
def get_font_key(f):
# Ссылку различаем по ней самой: своих атрибутов у такого шрифта нет, и без этого
# две РАЗНЫЕ ссылки схлопывались в одну как «пустые».
if f.get("Ref"):
return "ref=" + f["Ref"]
return f"{f['Face']}|{f['Size']}|{f['Bold']}|{f['Italic']}|{f['Underline']}|{f['Strikeout']}"
font_key_map = {}
@@ -628,7 +635,10 @@ def main():
name = None
if f["Face"] == df["Face"] and f["Size"] == df["Size"]:
if f.get("Ref"):
# Имя из ссылки: читаемее самой ссылки ничего не придумать.
name = f["Ref"].split(":", 1)[1].lower()
elif f["Face"] == df["Face"] and f["Size"] == df["Size"]:
if f["Bold"] and not df["Bold"] and not f["Italic"] and not f["Underline"] and not f["Strikeout"]:
name = "bold"
elif f["Italic"] and not df["Italic"] and not f["Bold"]:
@@ -1117,6 +1127,9 @@ def main():
fonts_out = OrderedDict()
for name, f in font_defs.items():
if f.get("Ref"):
fonts_out[name] = OrderedDict([("ref", f["Ref"])])
continue
f_out = OrderedDict()
f_out["face"] = f["Face"]
f_out["size"] = f["Size"]