From b781f9783285a88a8caa9fbaec42bed3013420c0 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Thu, 11 Jun 2026 17:29:09 +0300 Subject: [PATCH] =?UTF-8?q?feat(form-decompile,form-compile):=20string(N,f?= =?UTF-8?q?ixed)=20=E2=80=94=20AllowedLength=3DFixed=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20=D1=81=D1=82=D1=80=D0=BE=D0=BA=20=D1=84=D0=B8=D0=BA=D1=81.?= =?UTF-8?q?=20=D0=B4=D0=BB=D0=B8=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Колонки/реквизиты строк фиксированной длины (ИНН/КПП/коды) несут Fixed, но DSL выражал только Variable: компилятор хардкодил Variable, декомпилятор не читал AllowedLength → Fixed терялся (форма ЭлектроннаяТранспортнаяНакладная/ТитулПеревозчика*: 3 LOST Fixed + 3 ADDED Variable — мультимножественный учёт тех же колонок). Корпус 8.3.24: AllowedLength ВСЕГДА присутствует в StringQualifiers (Variable 443127 + Fixed 2687, ABSENT=0) → always-эмиссия Variable верна. Fixed (2687) всегда с длиной > 0 (12/10/3/1/36…); при Length=0 — всегда Variable. Грамматика `string(N,fixed)` (по аналогии с `decimal(D,F,nonneg)`). Variable — дефолт (опускаем суффикс); `variable` принимается forgiving. Emit-SingleType (ps1+py) эмитит Fixed при суффиксе; декомпилятор Decompile-Type читает AllowedLength (Fixed → суффикс, Variable/Length=0 → плоский string(N)). Общий путь типов (реквизиты/колонки/valueType/составные). Выборка 46 форм с Fixed (вкл. указанную): 0 потерь AllowedLength, целевая форма → match. Round-trip декомпиляции снэпшота: string(12,fixed)/string(9,fixed) читаются обратно. Кейс attributes-types (+ValueTable с Fixed-колонками ИНН/Код) сертифицирован в 1С. Регресс 43/43 (ps1+py). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../form-compile/scripts/form-compile.ps1 | 9 +++-- .../form-compile/scripts/form-compile.py | 9 +++-- .../form-decompile/scripts/form-decompile.ps1 | 8 +++- docs/form-dsl-spec.md | 5 ++- .../cases/form-compile/attributes-types.json | 7 +++- .../Типы/Forms/Форма/Ext/Form.xml | 40 +++++++++++++++++++ 6 files changed, 65 insertions(+), 13 deletions(-) diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index 298b7fb7..1a8d3cf7 100644 --- a/.claude/skills/form-compile/scripts/form-compile.ps1 +++ b/.claude/skills/form-compile/scripts/form-compile.ps1 @@ -1,4 +1,4 @@ -# form-compile v1.114 — Compile 1C managed form from JSON or object metadata +# form-compile v1.115 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -2056,13 +2056,14 @@ function Emit-SingleType { return } - # string or string(N) - if ($typeStr -match '^string(\((\d+)\))?$') { + # string or string(N) or string(N,fixed) (AllowedLength: Variable дефолт / Fixed) + if ($typeStr -match '^string(\((\d+)(\s*,\s*(fixed|variable))?\))?$') { $len = if ($Matches[2]) { $Matches[2] } else { "0" } + $al = if ($Matches[4] -and $Matches[4].ToLower() -eq 'fixed') { 'Fixed' } else { 'Variable' } X "$indentxs:string" X "$indent" X "$indent`t$len" - X "$indent`tVariable" + X "$indent`t$al" X "$indent" return } diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 97880515..429881a8 100644 --- a/.claude/skills/form-compile/scripts/form-compile.py +++ b/.claude/skills/form-compile/scripts/form-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# form-compile v1.114 — Compile 1C managed form from JSON or object metadata +# form-compile v1.115 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -3307,14 +3307,15 @@ def emit_single_type(lines, type_str, indent): lines.append(f'{indent}xs:boolean') return - # string or string(N) - m = re.match(r'^string(\((\d+)\))?$', type_str) + # string or string(N) or string(N,fixed) (AllowedLength: Variable дефолт / Fixed) + m = re.match(r'^string(\((\d+)(\s*,\s*(fixed|variable))?\))?$', type_str, re.IGNORECASE) if m: length = m.group(2) if m.group(2) else '0' + al = 'Fixed' if (m.group(4) and m.group(4).lower() == 'fixed') else 'Variable' lines.append(f'{indent}xs:string') lines.append(f'{indent}') lines.append(f'{indent}\t{length}') - lines.append(f'{indent}\tVariable') + lines.append(f'{indent}\t{al}') lines.append(f'{indent}') return diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index 132bfc5b..86be4675 100644 --- a/.claude/skills/form-decompile/scripts/form-decompile.ps1 +++ b/.claude/skills/form-decompile/scripts/form-decompile.ps1 @@ -1,4 +1,4 @@ -# form-decompile v0.90 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.91 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -1080,7 +1080,11 @@ function Decompile-Type { switch -regex ($raw) { '^xs:string$' { $len = $typeNode.SelectSingleNode("v8:StringQualifiers/v8:Length", $ns) - if ($len -and [int]$len.InnerText -gt 0) { $short = "string($($len.InnerText))" } else { $short = "string" } + $al = $typeNode.SelectSingleNode("v8:StringQualifiers/v8:AllowedLength", $ns) + $fixed = ($al -and $al.InnerText -eq 'Fixed') # Variable = дефолт (опускаем); Fixed — явно + if ($len -and [int]$len.InnerText -gt 0) { + $short = if ($fixed) { "string($($len.InnerText),fixed)" } else { "string($($len.InnerText))" } + } else { $short = "string" } # Length=0 → всегда Variable (корпус) break } '^xs:decimal$' { diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index 6506a999..2bd6f8c2 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -1095,8 +1095,9 @@ Forgiving-синонимы типа: XML-имя (`SpreadSheetDocumentField`) и | DSL | XML | |-----|-----| -| `"string"` | `xs:string` (неограниченная) | -| `"string(100)"` | `xs:string` + Length=100 | +| `"string"` | `xs:string` (неограниченная, AllowedLength=Variable) | +| `"string(100)"` | `xs:string` + Length=100 (AllowedLength=Variable, дефолт) | +| `"string(12,fixed)"` | `xs:string` + Length=12, AllowedLength=Fixed (строка фиксированной длины, напр. ИНН/КПП). Только с длиной > 0; `variable` принимается forgiving (= дефолт) | | `"decimal(15,2)"` | `xs:decimal` + Digits=15, FractionDigits=2, AllowedSign=Any | | `"decimal(10,0,nonneg)"` | `xs:decimal` + AllowedSign=Nonnegative | | `"boolean"` | `xs:boolean` | diff --git a/tests/skills/cases/form-compile/attributes-types.json b/tests/skills/cases/form-compile/attributes-types.json index 3321cd0d..d45ed593 100644 --- a/tests/skills/cases/form-compile/attributes-types.json +++ b/tests/skills/cases/form-compile/attributes-types.json @@ -30,7 +30,12 @@ { "name": "Период", "type": "СтандартныйПериод", "save": ["Период", "EndDate", "StartDate", "Variant"] }, { "name": "СписокЗначений", "type": "ValueList", "valueType": "string(50) | decimal(10,2)" }, { "name": "СписокЛюбой", "type": "ValueList", "valueType": "" }, - { "name": "Идентификатор", "type": "v8:UUID" } + { "name": "Идентификатор", "type": "v8:UUID" }, + { "name": "Таблица", "type": "ValueTable", "title": "Таблица", "columns": [ + { "name": "ИНН", "type": "string(12,fixed)" }, + { "name": "Код", "type": "string(9,fixed)" }, + { "name": "Имя", "type": "string(100)" } + ]} ] } } diff --git a/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml index bfd843be..64da1196 100644 --- a/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/attributes-types/DataProcessors/Типы/Forms/Форма/Ext/Form.xml @@ -164,5 +164,45 @@ v8:UUID + + + <v8:item> + <v8:lang>ru</v8:lang> + <v8:content>Таблица</v8:content> + </v8:item> + + + v8:ValueTable + + + + + xs:string + + 12 + Fixed + + + + + + xs:string + + 9 + Fixed + + + + + + xs:string + + 100 + Variable + + + + +