feat(form-decompile,form-compile): string(N,fixed) — AllowedLength=Fixed для строк фикс. длины

Колонки/реквизиты строк фиксированной длины (ИНН/КПП/коды) несут
<v8:AllowedLength>Fixed</v8:AllowedLength>, но 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) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-06-11 17:29:09 +03:00
parent b2eaa9e129
commit b781f97832
6 changed files with 65 additions and 13 deletions
@@ -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 "$indent<v8:Type>xs:string</v8:Type>"
X "$indent<v8:StringQualifiers>"
X "$indent`t<v8:Length>$len</v8:Length>"
X "$indent`t<v8:AllowedLength>Variable</v8:AllowedLength>"
X "$indent`t<v8:AllowedLength>$al</v8:AllowedLength>"
X "$indent</v8:StringQualifiers>"
return
}
@@ -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}<v8:Type>xs:boolean</v8:Type>')
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}<v8:Type>xs:string</v8:Type>')
lines.append(f'{indent}<v8:StringQualifiers>')
lines.append(f'{indent}\t<v8:Length>{length}</v8:Length>')
lines.append(f'{indent}\t<v8:AllowedLength>Variable</v8:AllowedLength>')
lines.append(f'{indent}\t<v8:AllowedLength>{al}</v8:AllowedLength>')
lines.append(f'{indent}</v8:StringQualifiers>')
return
@@ -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$' {
+3 -2
View File
@@ -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` |
@@ -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)" }
]}
]
}
}
@@ -164,5 +164,45 @@
<v8:Type>v8:UUID</v8:Type>
</Type>
</Attribute>
<Attribute name="Таблица" id="22">
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Таблица</v8:content>
</v8:item>
</Title>
<Type>
<v8:Type>v8:ValueTable</v8:Type>
</Type>
<Columns>
<Column name="ИНН" id="23">
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>12</v8:Length>
<v8:AllowedLength>Fixed</v8:AllowedLength>
</v8:StringQualifiers>
</Type>
</Column>
<Column name="Код" id="24">
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>9</v8:Length>
<v8:AllowedLength>Fixed</v8:AllowedLength>
</v8:StringQualifiers>
</Type>
</Column>
<Column name="Имя" id="25">
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>100</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</Type>
</Column>
</Columns>
</Attribute>
</Attributes>
</Form>