From f6a478c85c775e1cbac44724194e4c8161bf60ba Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Mon, 10 Aug 2026 15:03:15 +0300 Subject: [PATCH] =?UTF-8?q?fix(mxl-decompile,mxl-compile):=20=D0=B4=D1=80?= =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D1=8B=D0=B9=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5?= =?UTF-8?q?=D1=80=20=D1=88=D1=80=D0=B8=D1=84=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Размер шрифта в платформенных макетах бывает дробным (8.3, 6.8, 9.8, 11.3, 14.3). Оба навыка приводили его к целому: py падал голым ValueError на int(), ps1 ТИХО округлял через [int] — снова шумный отказ против тихой порчи, как было с col. Найдено раундтрипом по корпусу ERP 8.3.24: на стратифицированной выборке из 40 макетов это давало 12 отказов декомпиляции — 30% выборки и ВСЕ отказы этого этапа. После правки декомпиляция не падает ни на одном, цикл переживают 14 макетов вместо 10. Размер читается инвариантной культурой и остаётся целым, когда дробной части нет, иначе "10" превратилось бы в "10.0". Эмиссия в XML тоже переведена на инвариантную культуру: интерполяция строкой отдавала бы "8,3" под русской локалью. Правка на ps1, зазеркалена в py. Co-Authored-By: Claude Opus 5 (1M context) --- .../mxl-compile/scripts/mxl-compile.ps1 | 25 ++++++- .../skills/mxl-compile/scripts/mxl-compile.py | 17 ++++- .../mxl-decompile/scripts/mxl-decompile.ps1 | 18 ++++- .../mxl-decompile/scripts/mxl-decompile.py | 17 ++++- .../mxl-compile/font-fractional-size.json | 34 +++++++++ .../font-fractional-size/Template.xml | 72 +++++++++++++++++++ 6 files changed, 174 insertions(+), 9 deletions(-) create mode 100644 tests/skills/cases/mxl-compile/font-fractional-size.json create mode 100644 tests/skills/cases/mxl-compile/snapshots/font-fractional-size/Template.xml diff --git a/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 b/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 index fd4b0acd..aa4f4d80 100644 --- a/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 +++ b/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 @@ -1,4 +1,4 @@ -# mxl-compile v1.16 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) +# mxl-compile v1.17 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -209,10 +209,29 @@ $defaultWidth = if ($def.defaultWidth) { [int]$def.defaultWidth } else { 10 } $fontMap = [ordered]@{} # name -> 0-based index $fontEntries = @() # array of hashtables +# Размер шрифта бывает дробным (8.3, 11.3). [int] его ТИХО округлял. Читаем инвариантной +# культурой и держим целым, когда дробной части нет, — иначе "10" стало бы "10.0". +function ConvertTo-FontSize { + param($raw) + $s = [string]$raw + if ([string]::IsNullOrWhiteSpace($s)) { return 0 } + $d = 0.0 + if (-not [double]::TryParse($s, [System.Globalization.NumberStyles]::Float, + [System.Globalization.CultureInfo]::InvariantCulture, [ref]$d)) { return 0 } + if ($d -eq [math]::Floor($d)) { return [int]$d } + return $d +} + +# Число в XML-атрибут: интерполяция строкой отдала бы "8,3" под русской культурой. +function Format-Num { + param($v) + return [System.Convert]::ToString($v, [System.Globalization.CultureInfo]::InvariantCulture) +} + function Add-Font { param([string]$name, $fontDef) $face = if ($fontDef.face) { $fontDef.face } else { "Arial" } - $size = if ($fontDef.size) { [int]$fontDef.size } else { 10 } + $size = if ($fontDef.size) { ConvertTo-FontSize $fontDef.size } else { 10 } $bold = if ($fontDef.bold -eq $true) { "true" } else { "false" } $italic = if ($fontDef.italic -eq $true) { "true" } else { "false" } $underline = if ($fontDef.underline -eq $true) { "true" } else { "false" } @@ -1012,7 +1031,7 @@ if ($hasThickBorders) { # 7i. Font palette foreach ($fe in $fontEntries) { - X "`t" + X "`t" } # 7j. Format palette diff --git a/.claude/skills/mxl-compile/scripts/mxl-compile.py b/.claude/skills/mxl-compile/scripts/mxl-compile.py index 74365dec..28f3a4c5 100644 --- a/.claude/skills/mxl-compile/scripts/mxl-compile.py +++ b/.claude/skills/mxl-compile/scripts/mxl-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# mxl-compile v1.16 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) +# mxl-compile v1.17 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -297,6 +297,19 @@ def format_rank(ver): return int(m.group(1)) * 100 + int(m.group(2)) if m else 0 +def to_font_size(raw): + """Размер шрифта бывает дробным (8.3, 11.3). int() на таком падал, ps1 ТИХО округлял. + Целое держим целым, иначе "10" превратилось бы в "10.0".""" + s = str(raw).strip() if raw is not None else '' + if not s: + return 0 + try: + d = float(s) + except (TypeError, ValueError): + return 0 + return int(d) if d == int(d) else d + + def parse_col_value(val): """Позиция колонки как целое, иначе None. Аналог [int]::TryParse в ps1: целое из JSON приходит int, "3" — строкой, 3.0 — float (ps1 печатает такое как "3").""" @@ -351,7 +364,7 @@ def main(): def add_font(name, font_def): face = font_def.get('face', 'Arial') if font_def else 'Arial' - size = int(font_def.get('size', 10)) if font_def else 10 + size = to_font_size(font_def.get('size', 10)) if font_def else 10 bold = 'true' if font_def and font_def.get('bold') is True else 'false' italic = 'true' if font_def and font_def.get('italic') is True else 'false' underline = 'true' if font_def and font_def.get('underline') is True else 'false' diff --git a/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 b/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 index 063fde64..20445777 100644 --- a/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 +++ b/.claude/skills/mxl-decompile/scripts/mxl-decompile.ps1 @@ -1,4 +1,4 @@ -# mxl-decompile v1.2 — Decompile 1C spreadsheet to JSON +# mxl-decompile v1.3 — Decompile 1C spreadsheet to JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -31,11 +31,25 @@ $ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance") # --- 2. Extract font palette --- +# Размер шрифта бывает дробным (8.3, 11.3 — в корпусе ERP это треть макетов). [int] его +# ТИХО округлял, а py-порт падал на int(). Читаем инвариантной культурой и держим целым, +# когда дробной части нет, — иначе "10" превратилось бы в "10.0". +function ConvertTo-FontSize { + param($raw) + $s = [string]$raw + if ([string]::IsNullOrWhiteSpace($s)) { return 0 } + $d = 0.0 + if (-not [double]::TryParse($s, [System.Globalization.NumberStyles]::Float, + [System.Globalization.CultureInfo]::InvariantCulture, [ref]$d)) { return 0 } + if ($d -eq [math]::Floor($d)) { return [int]$d } + return $d +} + $rawFonts = @() foreach ($fNode in $root.SelectNodes("d:font", $ns)) { $rawFonts += @{ Face = $fNode.GetAttribute("faceName") - Size = [int]$fNode.GetAttribute("height") + Size = ConvertTo-FontSize $fNode.GetAttribute("height") Bold = $fNode.GetAttribute("bold") -eq "true" Italic = $fNode.GetAttribute("italic") -eq "true" Underline = $fNode.GetAttribute("underline") -eq "true" diff --git a/.claude/skills/mxl-decompile/scripts/mxl-decompile.py b/.claude/skills/mxl-decompile/scripts/mxl-decompile.py index 06a94457..e9b9e31f 100644 --- a/.claude/skills/mxl-decompile/scripts/mxl-decompile.py +++ b/.claude/skills/mxl-decompile/scripts/mxl-decompile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# mxl-decompile v1.2 — Decompile 1C spreadsheet to JSON +# mxl-decompile v1.3 — Decompile 1C spreadsheet to JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -56,6 +56,19 @@ def text_of(node): return None +def to_font_size(raw): + """Размер шрифта бывает дробным (8.3, 11.3 — в корпусе ERP это треть макетов). + int() на таком падал, а ps1 ТИХО округлял. Целое держим целым, иначе "10" → "10.0".""" + s = str(raw).strip() if raw is not None else '' + if not s: + return 0 + try: + d = float(s) + except (TypeError, ValueError): + return 0 + return int(d) if d == int(d) else d + + def int_of(node, default=0): if node is not None and node.text: return int(node.text) @@ -207,7 +220,7 @@ def main(): for f_node in findall(root, "d:font"): raw_fonts.append({ "Face": f_node.get("faceName", ""), - "Size": int(f_node.get("height", "0")), + "Size": to_font_size(f_node.get("height", "0")), "Bold": f_node.get("bold") == "true", "Italic": f_node.get("italic") == "true", "Underline": f_node.get("underline") == "true", diff --git a/tests/skills/cases/mxl-compile/font-fractional-size.json b/tests/skills/cases/mxl-compile/font-fractional-size.json new file mode 100644 index 00000000..588cf0e2 --- /dev/null +++ b/tests/skills/cases/mxl-compile/font-fractional-size.json @@ -0,0 +1,34 @@ +{ + "name": "Дробный размер шрифта сохраняется", + "input": { + "columns": 2, + "fonts": { + "default": { "face": "Arial", "size": 10 }, + "мелкий": { "face": "Arial", "size": 8.3 }, + "крупный": { "face": "Arial", "size": 11.3, "bold": true } + }, + "styles": { + "мелкий": { "font": "мелкий" }, + "крупный": { "font": "крупный" } + }, + "areas": [ + { + "name": "Шапка", + "rows": [ + { "cells": [{ "col": 1, "style": "крупный", "text": "Заголовок" }, { "col": 2, "style": "мелкий", "text": "сноска" }] } + ] + } + ] + }, + "params": { + "outputPath": "Template.xml" + }, + "validatePath": "Template.xml", + "expect": { + "files": ["Template.xml"], + "fileContains": { + "file": "Template.xml", + "text": ["height=\"8.3\"", "height=\"11.3\"", "height=\"10\""] + } + } +} diff --git a/tests/skills/cases/mxl-compile/snapshots/font-fractional-size/Template.xml b/tests/skills/cases/mxl-compile/snapshots/font-fractional-size/Template.xml new file mode 100644 index 00000000..f7ec565c --- /dev/null +++ b/tests/skills/cases/mxl-compile/snapshots/font-fractional-size/Template.xml @@ -0,0 +1,72 @@ + + + + ru + ru + + ru + Русский + Русский + + + + 2 + + + 0 + + + 0 + + 2 + + + ru + Заголовок + + + + + + 1 + + 3 + + + ru + сноска + + + + + + + true + 1 + 1 + 1 + + Шапка + + Rows + 0 + 0 + -1 + -1 + + + + + + + 10 + + + 2 + Text + + + 1 + Text + + \ No newline at end of file