diff --git a/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 b/.claude/skills/mxl-compile/scripts/mxl-compile.ps1 index 217485c7..4b693183 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.27 — Compile 1C spreadsheet from JSON +# mxl-compile v1.28 — Compile 1C spreadsheet from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -480,14 +480,19 @@ function Resolve-Style { } } - return @{ - FontIdx = $fontIdx - LB = $lb; TB = $tb; RB = $rb; BB = $bb - HA = $ha; VA = $va - Wrap = $wrap - FillType = $fillType - NumberFormat = $nf - } + # Набор свойств формата — «тег платформы → значение», только заданные. Порядок вставки + # роли не играет: и ключ дедупликации, и эмиссия идут по каноническому порядку тегов. + $props = @{ font = $fontIdx } + if ($lb -ge 0) { $props['leftBorder'] = $lb } + if ($tb -ge 0) { $props['topBorder'] = $tb } + if ($rb -ge 0) { $props['rightBorder'] = $rb } + if ($bb -ge 0) { $props['bottomBorder'] = $bb } + if ($ha) { $props['horizontalAlignment'] = $ha } + if ($va) { $props['verticalAlignment'] = $va } + if ($wrap) { $props['textPlacement'] = 'Wrap' } + if ($fillType) { $props['fillType'] = $fillType } + if ($nf) { $props['format'] = $nf } + return $props } # --- 6. Format palette builder --- @@ -495,22 +500,45 @@ function Resolve-Style { $formatRegistry = [ordered]@{} # key -> hashtable with properties $formatOrder = @() # ordered keys for index assignment -function Get-FormatKey { - param( - [int]$fontIdx = -1, - [int]$lb = -1, [int]$tb = -1, [int]$rb = -1, [int]$bb = -1, - [string]$ha = "", [string]$va = "", - [bool]$wrap = $false, - [string]$fillType = "", - [string]$numberFormat = "", - [int]$width = -1, - [int]$height = -1 +# Канонический порядок тегов внутри . Снят с корпуса: 766 960 форматов, ни один +# его не нарушает — платформа пишет теги строго в этой последовательности, и от неё +# зависит побайтовое совпадение с выгрузкой. +function Get-FormatTagOrder { + return @( + 'print', 'drawingBorder', + 'drawingHaveLeftBorder', 'drawingHaveTopBorder', 'drawingHaveRightBorder', 'drawingHaveBottomBorder', + 'font', 'leftBorder', 'topBorder', 'rightBorder', 'bottomBorder', 'border', + 'height', 'borderColor', 'width', 'autoWidthCalculation', 'widthWeightFactor', + 'horizontalAlignment', 'verticalAlignment', 'textColor', 'backColor', + 'patternColor', 'pattern', 'textPlacement', 'fillType', 'protection', 'hidden', + 'textOrientation', 'detailsUse', 'bySelectedColumns', 'markNegatives', + 'containsValue', 'valueType', 'format', 'controlType', 'hyperLink', + 'autoMarkIncomplete', 'indent', 'autoIndent', 'editFormat', 'columnSizeChange', + 'mask', 'picIndex', 'pictureSizeMode', 'picHorizontalAlignment', + 'picVerticalAlignment', 'textPosition' ) - return "f=$fontIdx|lb=$lb|tb=$tb|rb=$rb|bb=$bb|ha=$ha|va=$va|wr=$wrap|ft=$fillType|nf=$numberFormat|w=$width|h=$height" +} + +# Теги, значение которых — многоязычная строка ( на язык), а не скаляр. +function Get-FormatMlTags { + return @{ 'format' = $true; 'editFormat' = $true; 'mask' = $true } +} + +$script:formatTagOrder = Get-FormatTagOrder +$script:formatMlTags = Get-FormatMlTags + +function Get-FormatKey { + param([hashtable]$props) + $parts = @() + foreach ($tag in $script:formatTagOrder) { + if ($props.ContainsKey($tag)) { $parts += "$tag=$($props[$tag])" } + } + return ($parts -join '|') } function Register-Format { - param([string]$key, [hashtable]$props) + param([hashtable]$props) + $key = Get-FormatKey $props if (-not $script:formatRegistry.Contains($key)) { $script:formatRegistry[$key] = $props $script:formatOrder += $key @@ -525,16 +553,14 @@ function Register-Format { } # 6a. Default width format -$defaultFormatKey = Get-FormatKey -width $defaultWidth -$defaultFormatIndex = Register-Format -key $defaultFormatKey -props @{ Width = $defaultWidth } +$defaultFormatIndex = Register-Format @{ width = $defaultWidth } # 6b. Column width formats — по одной карте на каждую колоночную раскладку foreach ($layout in $columnLayouts) { $map = @{} # 1-based col -> format index foreach ($col in ($layout.Widths.Keys | Sort-Object)) { $w = $layout.Widths[$col] - $key = Get-FormatKey -width $w - $map[[int]$col] = Register-Format -key $key -props @{ Width = $w } + $map[[int]$col] = Register-Format @{ width = $w } } $layout.FormatMap = $map } @@ -606,27 +632,10 @@ function Register-CellFormat { # платформа: в её палитре у неоформленного макета один формат (ширина колонки), и все # ячейки указывают на него. Мы же заводили каждой свой формат с 0, где ноль # означает «шрифт не задан», то есть формат был пуст по смыслу. - if ($resolved.FontIdx -eq $fontMap["default"] -and - $resolved.LB -lt 0 -and $resolved.TB -lt 0 -and $resolved.RB -lt 0 -and $resolved.BB -lt 0 -and - -not $resolved.HA -and -not $resolved.VA -and -not $resolved.Wrap -and - -not $resolved.FillType -and -not $resolved.NumberFormat) { + if ($resolved.Count -eq 1 -and $resolved['font'] -eq $fontMap["default"]) { return $script:defaultFormatIndex } - $key = Get-FormatKey -fontIdx $resolved.FontIdx ` - -lb $resolved.LB -tb $resolved.TB -rb $resolved.RB -bb $resolved.BB ` - -ha $resolved.HA -va $resolved.VA ` - -wrap $resolved.Wrap -fillType $resolved.FillType ` - -numberFormat $resolved.NumberFormat - $props = @{ - FontIdx = $resolved.FontIdx - LB = $resolved.LB; TB = $resolved.TB - RB = $resolved.RB; BB = $resolved.BB - HA = $resolved.HA; VA = $resolved.VA - Wrap = $resolved.Wrap - FillType = $resolved.FillType - NumberFormat = $resolved.NumberFormat - } - return Register-Format -key $key -props $props + return Register-Format $resolved } # --- 5.5. Шорткат строк: строка-массив ячеек --- @@ -792,8 +801,7 @@ foreach ($area in $def.areas) { # Row height format if ($row.height) { - $hKey = Get-FormatKey -height ([int]$row.height) - Register-Format -key $hKey -props @{ Height = [int]$row.height } | Out-Null + Register-Format @{ height = [int]$row.height } | Out-Null } # rowStyle gap-fill format (no content → no fillType) @@ -955,13 +963,7 @@ foreach ($area in $def.areas) { # Determine row height format $rowFormatIdx = 0 if ($row.height) { - $hKey = Get-FormatKey -height ([int]$row.height) - # Find format index for this key - $rIdx = 0 - foreach ($k in $formatRegistry.Keys) { - $rIdx++ - if ($k -eq $hKey) { $rowFormatIdx = $rIdx; break } - } + $rowFormatIdx = Register-Format @{ height = [int]$row.height } } if ($row.cells -and $row.cells.Count -gt 0) { @@ -1334,46 +1336,19 @@ foreach ($key in $formatRegistry.Keys) { $fmt = $formatRegistry[$key] X "`t" - if ($fmt.FontIdx -ne $null -and $fmt.FontIdx -ge 0) { - X "`t`t$($fmt.FontIdx)" - } - if ($fmt.LB -ne $null -and $fmt.LB -ge 0) { - X "`t`t$($fmt.LB)" - } - if ($fmt.TB -ne $null -and $fmt.TB -ge 0) { - X "`t`t$($fmt.TB)" - } - if ($fmt.RB -ne $null -and $fmt.RB -ge 0) { - X "`t`t$($fmt.RB)" - } - if ($fmt.BB -ne $null -and $fmt.BB -ge 0) { - X "`t`t$($fmt.BB)" - } - if ($fmt.Width) { - X "`t`t$($fmt.Width)" - } - if ($fmt.Height) { - X "`t`t$($fmt.Height)" - } - if ($fmt.HA) { - X "`t`t$($fmt.HA)" - } - if ($fmt.VA) { - X "`t`t$($fmt.VA)" - } - if ($fmt.Wrap -eq $true) { - X "`t`tWrap" - } - if ($fmt.FillType) { - X "`t`t$($fmt.FillType)" - } - if ($fmt.NumberFormat) { - X "`t`t" - X "`t`t`t" - X "`t`t`t`tru" - X "`t`t`t`t$(Esc-XmlText $fmt.NumberFormat)" - X "`t`t`t" - X "`t`t" + foreach ($tag in $script:formatTagOrder) { + if (-not $fmt.ContainsKey($tag)) { continue } + $val = $fmt[$tag] + if ($script:formatMlTags.ContainsKey($tag)) { + X "`t`t<$tag>" + X "`t`t`t" + X "`t`t`t`tru" + X "`t`t`t`t$(Esc-XmlText $val)" + X "`t`t`t" + X "`t`t" + } else { + X "`t`t<$tag>$val" + } } X "`t" diff --git a/.claude/skills/mxl-compile/scripts/mxl-compile.py b/.claude/skills/mxl-compile/scripts/mxl-compile.py index f822cab5..212f94d3 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.27 — Compile 1C spreadsheet from JSON +# mxl-compile v1.28 — Compile 1C spreadsheet from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import hashlib @@ -326,6 +326,38 @@ def parse_col_value(val): return None +def format_tag_order(): + """Канонический порядок тегов внутри . Снят с корпуса: 766 960 форматов, ни один + его не нарушает — платформа пишет теги строго в этой последовательности, и от неё + зависит побайтовое совпадение с выгрузкой.""" + return [ + 'print', 'drawingBorder', + 'drawingHaveLeftBorder', 'drawingHaveTopBorder', 'drawingHaveRightBorder', 'drawingHaveBottomBorder', + 'font', 'leftBorder', 'topBorder', 'rightBorder', 'bottomBorder', 'border', + 'height', 'borderColor', 'width', 'autoWidthCalculation', 'widthWeightFactor', + 'horizontalAlignment', 'verticalAlignment', 'textColor', 'backColor', + 'patternColor', 'pattern', 'textPlacement', 'fillType', 'protection', 'hidden', + 'textOrientation', 'detailsUse', 'bySelectedColumns', 'markNegatives', + 'containsValue', 'valueType', 'format', 'controlType', 'hyperLink', + 'autoMarkIncomplete', 'indent', 'autoIndent', 'editFormat', 'columnSizeChange', + 'mask', 'picIndex', 'pictureSizeMode', 'picHorizontalAlignment', + 'picVerticalAlignment', 'textPosition', + ] + + +def format_ml_tags(): + """Теги, значение которых — многоязычная строка ( на язык), а не скаляр.""" + return {'format': True, 'editFormat': True, 'mask': True} + + +def get_format_key(props): + parts = [] + for tag in format_tag_order(): + if tag in props: + parts.append(f'{tag}={props[tag]}') + return '|'.join(parts) + + def main(): sys.stdout.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8") @@ -568,24 +600,35 @@ def main(): if style.get('format'): nf = style['format'] - return { - 'FontIdx': font_idx, - 'LB': lb, 'TB': tb, 'RB': rb, 'BB': bb, - 'HA': ha, 'VA': va, - 'Wrap': wrap, - 'FillType': fill_type, - 'NumberFormat': nf, - } + # Набор свойств формата — «тег платформы → значение», только заданные. Порядок вставки + # роли не играет: и ключ дедупликации, и эмиссия идут по каноническому порядку тегов. + props = {'font': font_idx} + if lb >= 0: + props['leftBorder'] = lb + if tb >= 0: + props['topBorder'] = tb + if rb >= 0: + props['rightBorder'] = rb + if bb >= 0: + props['bottomBorder'] = bb + if ha: + props['horizontalAlignment'] = ha + if va: + props['verticalAlignment'] = va + if wrap: + props['textPlacement'] = 'Wrap' + if fill_type: + props['fillType'] = fill_type + if nf: + props['format'] = nf + return props # --- 6. Format palette builder --- format_registry = {} # key -> props format_order = [] # ordered keys for index assignment - def get_format_key(font_idx=-1, lb=-1, tb=-1, rb=-1, bb=-1, ha='', va='', - wrap=False, fill_type='', number_format='', width=-1, height=-1): - return f'f={font_idx}|lb={lb}|tb={tb}|rb={rb}|bb={bb}|ha={ha}|va={va}|wr={wrap}|ft={fill_type}|nf={number_format}|w={width}|h={height}' - - def register_format(key, props): + def register_format(props): + key = get_format_key(props) if key not in format_registry: format_registry[key] = props format_order.append(key) @@ -593,15 +636,14 @@ def main(): return format_order.index(key) + 1 # 6a. Default width format - default_format_key = get_format_key(width=default_width) - default_format_index = register_format(default_format_key, {'Width': default_width}) + default_format_index = register_format({'width': default_width}) # 6b. Column width formats — по одной карте на каждую колоночную раскладку for layout in column_layouts: fmap = {} # 1-based col -> format index for col in sorted(layout['Widths']): w = layout['Widths'][col] - fmap[int(col)] = register_format(get_format_key(width=w), {'Width': w}) + fmap[int(col)] = register_format({'width': w}) layout['FormatMap'] = fmap col_format_map = column_layouts[0]['FormatMap'] @@ -644,28 +686,9 @@ def main(): # платформа: в её палитре у неоформленного макета один формат (ширина колонки), и все # ячейки указывают на него. Мы же заводили каждой свой формат с 0, где ноль # означает «шрифт не задан», то есть формат был пуст по смыслу. - if (resolved['FontIdx'] == font_map.get('default', 0) - and resolved['LB'] < 0 and resolved['TB'] < 0 - and resolved['RB'] < 0 and resolved['BB'] < 0 - and not resolved['HA'] and not resolved['VA'] and not resolved['Wrap'] - and not resolved['FillType'] and not resolved['NumberFormat']): + if len(resolved) == 1 and resolved.get('font') == font_map.get('default', 0): return default_format_index - key = get_format_key( - font_idx=resolved['FontIdx'], - lb=resolved['LB'], tb=resolved['TB'], rb=resolved['RB'], bb=resolved['BB'], - ha=resolved['HA'], va=resolved['VA'], - wrap=resolved['Wrap'], fill_type=resolved['FillType'], - number_format=resolved['NumberFormat']) - props = { - 'FontIdx': resolved['FontIdx'], - 'LB': resolved['LB'], 'TB': resolved['TB'], - 'RB': resolved['RB'], 'BB': resolved['BB'], - 'HA': resolved['HA'], 'VA': resolved['VA'], - 'Wrap': resolved['Wrap'], - 'FillType': resolved['FillType'], - 'NumberFormat': resolved['NumberFormat'], - } - return register_format(key, props) + return register_format(resolved) # --- 5.5. Шорткат строк: строка-массив ячеек --- # Та же форма, что у макетов СКД (skd-compile): позиция ячейки = индекс в массиве, @@ -814,8 +837,7 @@ def main(): # Row height format if row.get('height'): - h_key = get_format_key(height=int(row['height'])) - register_format(h_key, {'Height': int(row['height'])}) + register_format({'height': int(row['height'])}) # rowStyle gap-fill format if row.get('rowStyle'): @@ -962,9 +984,7 @@ def main(): # Determine row height format row_format_idx = 0 if row.get('height'): - h_key = get_format_key(height=int(row['height'])) - if h_key in format_registry: - row_format_idx = format_order.index(h_key) + 1 + row_format_idx = register_format({'height': int(row['height'])}) if row.get('cells') and len(row['cells']) > 0: row_has_content = True @@ -1292,35 +1312,20 @@ def main(): fmt = format_registry[key] lines.append('\t') - if fmt.get('FontIdx') is not None and fmt.get('FontIdx', -1) >= 0: - lines.append(f'\t\t{fmt["FontIdx"]}') - if fmt.get('LB') is not None and fmt.get('LB', -1) >= 0: - lines.append(f'\t\t{fmt["LB"]}') - if fmt.get('TB') is not None and fmt.get('TB', -1) >= 0: - lines.append(f'\t\t{fmt["TB"]}') - if fmt.get('RB') is not None and fmt.get('RB', -1) >= 0: - lines.append(f'\t\t{fmt["RB"]}') - if fmt.get('BB') is not None and fmt.get('BB', -1) >= 0: - lines.append(f'\t\t{fmt["BB"]}') - if fmt.get('Width'): - lines.append(f'\t\t{fmt["Width"]}') - if fmt.get('Height'): - lines.append(f'\t\t{fmt["Height"]}') - if fmt.get('HA'): - lines.append(f'\t\t{fmt["HA"]}') - if fmt.get('VA'): - lines.append(f'\t\t{fmt["VA"]}') - if fmt.get('Wrap') is True: - lines.append('\t\tWrap') - if fmt.get('FillType'): - lines.append(f'\t\t{fmt["FillType"]}') - if fmt.get('NumberFormat'): - lines.append('\t\t') - lines.append('\t\t\t') - lines.append('\t\t\t\tru') - lines.append(f'\t\t\t\t{esc_xml_text(fmt["NumberFormat"])}') - lines.append('\t\t\t') - lines.append('\t\t') + ml_tags = format_ml_tags() + for tag in format_tag_order(): + if tag not in fmt: + continue + val = fmt[tag] + if tag in ml_tags: + lines.append(f'\t\t<{tag}>') + lines.append('\t\t\t') + lines.append('\t\t\t\tru') + lines.append(f'\t\t\t\t{esc_xml_text(val)}') + lines.append('\t\t\t') + lines.append(f'\t\t') + else: + lines.append(f'\t\t<{tag}>{val}') lines.append('\t')