mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-20 10:09:42 +03:00
feat(mxl-compile): прощающий ввод — ячейка без col
Ячейка без col роняла py голым KeyError, а ps1 молча брал null как 0 и писал Col = -1 — битую ячейку без единого сообщения. При этом опустить col модели естественно: DSL устроен как HTML-таблица (rows → cells), и одиночный заголовок или обычная строка пишутся без позиций. Строка, в которой col нет НИ У ОДНОЙ ячейки, теперь раскладывается слева направо с учётом span и занятых сверху rowspan-колонок. Смешанную строку не угадываем — это опечатка; переполнение columns и явный col вне 1..columns тоже дают внятную ошибку в stderr вместо тихой порчи. Документация не менялась: col остаётся единственной каноничной формой, прощающий ввод живёт только в коде — как регистронезависимость ключей DSL. Второй способ адресации в SKILL.md превратил бы одно правило в развилку. Правка сделана на ps1 и зазеркалена в py; вывод портов на общем примере совпадает байт в байт. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
863c7ebbff
commit
f33436adff
@@ -1,4 +1,4 @@
|
|||||||
# mxl-compile v1.14 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
# mxl-compile v1.15 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -652,6 +652,49 @@ foreach ($area in $def.areas) {
|
|||||||
if ($row.cells -and $row.cells.Count -gt 0) {
|
if ($row.cells -and $row.cells.Count -gt 0) {
|
||||||
$rowHasContent = $true
|
$rowHasContent = $true
|
||||||
|
|
||||||
|
# Прощающий ввод: строка, в которой НИ У ОДНОЙ ячейки нет col, раскладывается
|
||||||
|
# слева направо с учётом span и rowspan сверху. Канон один и он в документации —
|
||||||
|
# col обязателен; здесь мы лишь спасаем естественный DSL вместо тихой порчи
|
||||||
|
# ($null -> [int]0 -> Col = -1). Смешанную строку не угадываем: это опечатка.
|
||||||
|
$positioned = @($row.cells | Where-Object {
|
||||||
|
$_.PSObject.Properties['col'] -and $null -ne $_.col -and "$($_.col)" -ne ""
|
||||||
|
})
|
||||||
|
if ($positioned.Count -eq 0) {
|
||||||
|
$cursor = 1
|
||||||
|
foreach ($cell in $row.cells) {
|
||||||
|
$colSpan = if ($cell.span) { [int]$cell.span } else { 1 }
|
||||||
|
while ($true) {
|
||||||
|
$isFree = $true
|
||||||
|
for ($c = $cursor; $c -lt ($cursor + $colSpan); $c++) {
|
||||||
|
if ($rowspanOccupied[$c]) { $isFree = $false; break }
|
||||||
|
}
|
||||||
|
if ($isFree) { break }
|
||||||
|
$cursor++
|
||||||
|
}
|
||||||
|
if (($cursor + $colSpan - 1) -gt $totalColumns) {
|
||||||
|
Write-Error "Row exceeds 'columns' ($totalColumns): area `"$areaName`", row $($localRow + 1)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
$cell | Add-Member -NotePropertyName col -NotePropertyValue $cursor -Force
|
||||||
|
$cursor += $colSpan
|
||||||
|
}
|
||||||
|
} elseif ($positioned.Count -ne $row.cells.Count) {
|
||||||
|
Write-Error "Cell without 'col' mixed with positioned cells: area `"$areaName`", row $($localRow + 1)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Позиция обязана быть в 1..columns: до этой проверки нечисловой или нулевой col
|
||||||
|
# молча превращался в Col = -1 и давал битую ячейку без единого сообщения.
|
||||||
|
$cellIdx = 0
|
||||||
|
foreach ($cell in $row.cells) {
|
||||||
|
$cellIdx++
|
||||||
|
$colParsed = 0
|
||||||
|
if (-not [int]::TryParse("$($cell.col)", [ref]$colParsed) -or $colParsed -lt 1 -or $colParsed -gt $totalColumns) {
|
||||||
|
Write-Error "Invalid 'col' value `"$($cell.col)`": area `"$areaName`", row $($localRow + 1), cell $cellIdx"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Build set of occupied columns (1-based): explicit cells + rowspan from above
|
# Build set of occupied columns (1-based): explicit cells + rowspan from above
|
||||||
$occupiedCols = @{}
|
$occupiedCols = @{}
|
||||||
foreach ($rsk in $rowspanOccupied.Keys) { $occupiedCols[$rsk] = $true }
|
foreach ($rsk in $rowspanOccupied.Keys) { $occupiedCols[$rsk] = $true }
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# mxl-compile v1.14 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
# mxl-compile v1.15 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
@@ -297,6 +297,21 @@ def format_rank(ver):
|
|||||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||||
|
|
||||||
|
|
||||||
|
def parse_col_value(val):
|
||||||
|
"""Позиция колонки как целое, иначе None. Аналог [int]::TryParse в ps1:
|
||||||
|
целое из JSON приходит int, "3" — строкой, 3.0 — float (ps1 печатает такое как "3")."""
|
||||||
|
if isinstance(val, bool) or val is None:
|
||||||
|
return None
|
||||||
|
if isinstance(val, int):
|
||||||
|
return val
|
||||||
|
if isinstance(val, float):
|
||||||
|
return int(val) if val.is_integer() else None
|
||||||
|
try:
|
||||||
|
return int(str(val).strip())
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
@@ -690,6 +705,38 @@ def main():
|
|||||||
if row.get('cells') and len(row['cells']) > 0:
|
if row.get('cells') and len(row['cells']) > 0:
|
||||||
row_has_content = True
|
row_has_content = True
|
||||||
|
|
||||||
|
# Прощающий ввод: строка, в которой НИ У ОДНОЙ ячейки нет col, раскладывается
|
||||||
|
# слева направо с учётом span и rowspan сверху. Канон один и он в документации —
|
||||||
|
# col обязателен; здесь мы лишь спасаем естественный DSL вместо тихой порчи
|
||||||
|
# (в ps1 $null -> [int]0 -> Col = -1). Смешанную строку не угадываем: это опечатка.
|
||||||
|
positioned = [c for c in row['cells']
|
||||||
|
if 'col' in c and c.get('col') is not None and str(c.get('col')) != '']
|
||||||
|
if len(positioned) == 0:
|
||||||
|
cursor = 1
|
||||||
|
for cell in row['cells']:
|
||||||
|
col_span = int(cell.get('span', 1))
|
||||||
|
while any(c in rowspan_occupied for c in range(cursor, cursor + col_span)):
|
||||||
|
cursor += 1
|
||||||
|
if cursor + col_span - 1 > total_columns:
|
||||||
|
print(f'Row exceeds \'columns\' ({total_columns}): area "{area_name}",'
|
||||||
|
f' row {local_row + 1}', file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
cell['col'] = cursor
|
||||||
|
cursor += col_span
|
||||||
|
elif len(positioned) != len(row['cells']):
|
||||||
|
print(f'Cell without \'col\' mixed with positioned cells: area "{area_name}",'
|
||||||
|
f' row {local_row + 1}', file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Позиция обязана быть в 1..columns: до этой проверки нечисловой или нулевой col
|
||||||
|
# ронял py голым KeyError/ValueError, а ps1 молча писал Col = -1.
|
||||||
|
for cell_idx, cell in enumerate(row['cells'], start=1):
|
||||||
|
col_parsed = parse_col_value(cell.get('col'))
|
||||||
|
if col_parsed is None or col_parsed < 1 or col_parsed > total_columns:
|
||||||
|
print(f'Invalid \'col\' value "{cell.get("col")}": area "{area_name}",'
|
||||||
|
f' row {local_row + 1}, cell {cell_idx}', file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# Build set of occupied columns (1-based)
|
# Build set of occupied columns (1-based)
|
||||||
occupied_cols = dict(rowspan_occupied)
|
occupied_cols = dict(rowspan_occupied)
|
||||||
for cell in row['cells']:
|
for cell in row['cells']:
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "Ошибка: явный col вне диапазона 1..columns",
|
||||||
|
"input": {
|
||||||
|
"columns": 3,
|
||||||
|
"areas": [
|
||||||
|
{
|
||||||
|
"name": "Шапка",
|
||||||
|
"rows": [
|
||||||
|
{ "cells": [{ "col": 0, "text": "А" }] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"outputPath": "Template.xml"
|
||||||
|
},
|
||||||
|
"expectError": "Invalid 'col' value \"0\": area \"Шапка\", row 1, cell 1"
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "Ошибка: раскладка без col не влезает в columns",
|
||||||
|
"input": {
|
||||||
|
"columns": 3,
|
||||||
|
"areas": [
|
||||||
|
{
|
||||||
|
"name": "Шапка",
|
||||||
|
"rows": [
|
||||||
|
{ "cells": [{ "text": "А" }, { "text": "Б" }, { "text": "В" }, { "text": "Г" }] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"outputPath": "Template.xml"
|
||||||
|
},
|
||||||
|
"expectError": "Row exceeds 'columns' (3): area \"Шапка\", row 1"
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "Ошибка: в строке часть ячеек с col, часть без",
|
||||||
|
"input": {
|
||||||
|
"columns": 3,
|
||||||
|
"areas": [
|
||||||
|
{
|
||||||
|
"name": "Шапка",
|
||||||
|
"rows": [
|
||||||
|
{ "cells": [{ "col": 1, "text": "А" }, { "text": "Б" }] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"outputPath": "Template.xml"
|
||||||
|
},
|
||||||
|
"expectError": "Cell without 'col' mixed with positioned cells: area \"Шапка\", row 1"
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "Прощающий ввод: строка без col раскладывается слева направо",
|
||||||
|
"input": {
|
||||||
|
"columns": 3,
|
||||||
|
"areas": [
|
||||||
|
{
|
||||||
|
"name": "Таблица",
|
||||||
|
"rows": [
|
||||||
|
{ "cells": [{ "text": "А" }, { "text": "Б" }, { "text": "В" }] },
|
||||||
|
{ "cells": [{ "col": 1, "rowspan": 2, "text": "Сквозная" }, { "col": 2, "text": "X" }, { "col": 3, "text": "Y" }] },
|
||||||
|
{ "cells": [{ "text": "П" }, { "text": "Р" }] },
|
||||||
|
{ "cells": [{ "span": 2, "text": "Широкая" }, { "text": "Хвост" }] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"outputPath": "Template.xml"
|
||||||
|
},
|
||||||
|
"validatePath": "Template.xml",
|
||||||
|
"expect": {
|
||||||
|
"files": [
|
||||||
|
"Template.xml"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<languageSettings>
|
||||||
|
<currentLanguage>ru</currentLanguage>
|
||||||
|
<defaultLanguage>ru</defaultLanguage>
|
||||||
|
<languageInfo>
|
||||||
|
<id>ru</id>
|
||||||
|
<code>Русский</code>
|
||||||
|
<description>Русский</description>
|
||||||
|
</languageInfo>
|
||||||
|
</languageSettings>
|
||||||
|
<columns>
|
||||||
|
<size>3</size>
|
||||||
|
</columns>
|
||||||
|
<rowsItem>
|
||||||
|
<index>0</index>
|
||||||
|
<row>
|
||||||
|
<c>
|
||||||
|
<i>0</i>
|
||||||
|
<c>
|
||||||
|
<f>2</f>
|
||||||
|
<tl>
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>А</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</tl>
|
||||||
|
</c>
|
||||||
|
</c>
|
||||||
|
<c>
|
||||||
|
<i>1</i>
|
||||||
|
<c>
|
||||||
|
<f>2</f>
|
||||||
|
<tl>
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>Б</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</tl>
|
||||||
|
</c>
|
||||||
|
</c>
|
||||||
|
<c>
|
||||||
|
<i>2</i>
|
||||||
|
<c>
|
||||||
|
<f>2</f>
|
||||||
|
<tl>
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>В</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</tl>
|
||||||
|
</c>
|
||||||
|
</c>
|
||||||
|
</row>
|
||||||
|
</rowsItem>
|
||||||
|
<rowsItem>
|
||||||
|
<index>1</index>
|
||||||
|
<row>
|
||||||
|
<c>
|
||||||
|
<i>0</i>
|
||||||
|
<c>
|
||||||
|
<f>2</f>
|
||||||
|
<tl>
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>Сквозная</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</tl>
|
||||||
|
</c>
|
||||||
|
</c>
|
||||||
|
<c>
|
||||||
|
<i>1</i>
|
||||||
|
<c>
|
||||||
|
<f>2</f>
|
||||||
|
<tl>
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>X</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</tl>
|
||||||
|
</c>
|
||||||
|
</c>
|
||||||
|
<c>
|
||||||
|
<i>2</i>
|
||||||
|
<c>
|
||||||
|
<f>2</f>
|
||||||
|
<tl>
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>Y</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</tl>
|
||||||
|
</c>
|
||||||
|
</c>
|
||||||
|
</row>
|
||||||
|
</rowsItem>
|
||||||
|
<rowsItem>
|
||||||
|
<index>2</index>
|
||||||
|
<row>
|
||||||
|
<c>
|
||||||
|
<i>1</i>
|
||||||
|
<c>
|
||||||
|
<f>2</f>
|
||||||
|
<tl>
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>П</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</tl>
|
||||||
|
</c>
|
||||||
|
</c>
|
||||||
|
<c>
|
||||||
|
<i>2</i>
|
||||||
|
<c>
|
||||||
|
<f>2</f>
|
||||||
|
<tl>
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>Р</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</tl>
|
||||||
|
</c>
|
||||||
|
</c>
|
||||||
|
</row>
|
||||||
|
</rowsItem>
|
||||||
|
<rowsItem>
|
||||||
|
<index>3</index>
|
||||||
|
<row>
|
||||||
|
<c>
|
||||||
|
<i>0</i>
|
||||||
|
<c>
|
||||||
|
<f>2</f>
|
||||||
|
<tl>
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>Широкая</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</tl>
|
||||||
|
</c>
|
||||||
|
</c>
|
||||||
|
<c>
|
||||||
|
<i>2</i>
|
||||||
|
<c>
|
||||||
|
<f>2</f>
|
||||||
|
<tl>
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>Хвост</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</tl>
|
||||||
|
</c>
|
||||||
|
</c>
|
||||||
|
</row>
|
||||||
|
</rowsItem>
|
||||||
|
<templateMode>true</templateMode>
|
||||||
|
<defaultFormatIndex>1</defaultFormatIndex>
|
||||||
|
<height>4</height>
|
||||||
|
<vgRows>4</vgRows>
|
||||||
|
<merge>
|
||||||
|
<r>1</r>
|
||||||
|
<c>0</c>
|
||||||
|
<h>1</h>
|
||||||
|
<w>0</w>
|
||||||
|
</merge>
|
||||||
|
<merge>
|
||||||
|
<r>3</r>
|
||||||
|
<c>0</c>
|
||||||
|
<w>1</w>
|
||||||
|
</merge>
|
||||||
|
<namedItem xsi:type="NamedItemCells">
|
||||||
|
<name>Таблица</name>
|
||||||
|
<area>
|
||||||
|
<type>Rows</type>
|
||||||
|
<beginRow>0</beginRow>
|
||||||
|
<endRow>3</endRow>
|
||||||
|
<beginColumn>-1</beginColumn>
|
||||||
|
<endColumn>-1</endColumn>
|
||||||
|
</area>
|
||||||
|
</namedItem>
|
||||||
|
<font faceName="Arial" height="10" bold="false" italic="false" underline="false" strikeout="false" kind="Absolute" scale="100"/>
|
||||||
|
<format>
|
||||||
|
<width>10</width>
|
||||||
|
</format>
|
||||||
|
<format>
|
||||||
|
<font>0</font>
|
||||||
|
<fillType>Text</fillType>
|
||||||
|
</format>
|
||||||
|
</document>
|
||||||
Reference in New Issue
Block a user