diff --git a/.claude/skills/meta-compile/scripts/meta-compile.ps1 b/.claude/skills/meta-compile/scripts/meta-compile.ps1
index 2edbce71..b4b2b4af 100644
--- a/.claude/skills/meta-compile/scripts/meta-compile.ps1
+++ b/.claude/skills/meta-compile/scripts/meta-compile.ps1
@@ -1,4 +1,4 @@
-# meta-compile v1.30 — Compile 1C metadata object from JSON
+# meta-compile v1.31 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -509,13 +509,14 @@ function Emit-TypeContent {
return
}
- # String or String(N)
- if ($typeStr -match '^String(\((\d+)\))?$') {
+ # String or String(N) or String(N,fixed|variable) — AllowedLength: Variable дефолт / Fixed (фикс. длина).
+ if ($typeStr -match '^String(\((\d+)(\s*,\s*(fixed|variable))?\))?$') {
$len = if ($Matches[2]) { $Matches[2] } else { "10" }
+ $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
}
@@ -829,6 +830,9 @@ function Parse-AttributeShorthand {
format = $val.format
editFormat = $val.editFormat
mask = if ($val.mask) { "$($val.mask)" } else { "" }
+ extendedEdit = if ($val.extendedEdit -eq $true) { $true } else { $false }
+ minValue = $val.minValue
+ maxValue = $val.maxValue
hasFillValue = ($val.PSObject -and $val.PSObject.Properties -and ($val.PSObject.Properties.Name -contains 'fillValue'))
fillValue = $val.fillValue
linkByType = $val.linkByType
@@ -1481,6 +1485,15 @@ function Emit-Characteristics {
X "$indent"
}
+# / — граница диапазона реквизита. Нет ключа → nil (не задано). Значение типизировано
+# (зеркало form-compile): число → xs:decimal, строка → xs:string (тип сохранён декомпилятором).
+function Emit-MinMaxValue {
+ param([string]$indent, [string]$tag, $val)
+ if ($null -eq $val) { X "$indent<$tag xsi:nil=`"true`"/>"; return }
+ $t = if ($val -is [string]) { 'xs:string' } else { 'xs:decimal' }
+ X "$indent<$tag xsi:type=`"$t`">$(Esc-Xml "$val")$tag>"
+}
+
function Emit-Attribute {
param([string]$indent, $parsed, [string]$context)
# $context: "catalog", "document", "object", "processor", "tabular", "processor-tabular", "register"
@@ -1525,9 +1538,10 @@ function Emit-Attribute {
if ($parsed.mask) { X "$indent`t`t$(Esc-XmlText $parsed.mask)" } else { X "$indent`t`t" }
$multiLine = if ($parsed.multiLine -eq $true -or $parsed.flags -contains "multiline") { "true" } else { "false" }
X "$indent`t`t$multiLine"
- X "$indent`t`tfalse"
- X "$indent`t`t"
- X "$indent`t`t"
+ $extEdit = if ($parsed.extendedEdit -eq $true) { "true" } else { "false" }
+ X "$indent`t`t$extEdit"
+ Emit-MinMaxValue "$indent`t`t" "MinValue" $parsed.minValue
+ Emit-MinMaxValue "$indent`t`t" "MaxValue" $parsed.maxValue
# FillFromFillingValue — not for tabular/processor/chart/register-other
# (Chart*, AccumulationRegister/AccountingRegister/CalculationRegister don't support these)
@@ -1736,9 +1750,10 @@ function Emit-Dimension {
X "$indent`t`t"
$multiLine = if ($parsed.multiLine -eq $true -or $parsed.flags -contains "multiline") { "true" } else { "false" }
X "$indent`t`t$multiLine"
- X "$indent`t`tfalse"
- X "$indent`t`t"
- X "$indent`t`t"
+ $extEdit = if ($parsed.extendedEdit -eq $true) { "true" } else { "false" }
+ X "$indent`t`t$extEdit"
+ Emit-MinMaxValue "$indent`t`t" "MinValue" $parsed.minValue
+ Emit-MinMaxValue "$indent`t`t" "MaxValue" $parsed.maxValue
# InformationRegister dimensions have FillFromFillingValue
if ($registerType -eq "InformationRegister") {
@@ -1830,9 +1845,10 @@ function Emit-Resource {
X "$indent`t`t"
$multiLine = if ($parsed.multiLine -eq $true -or $parsed.flags -contains "multiline") { "true" } else { "false" }
X "$indent`t`t$multiLine"
- X "$indent`t`tfalse"
- X "$indent`t`t"
- X "$indent`t`t"
+ $extEdit = if ($parsed.extendedEdit -eq $true) { "true" } else { "false" }
+ X "$indent`t`t$extEdit"
+ Emit-MinMaxValue "$indent`t`t" "MinValue" $parsed.minValue
+ Emit-MinMaxValue "$indent`t`t" "MaxValue" $parsed.maxValue
# InformationRegister resources have FillFromFillingValue, FillValue
if ($registerType -eq "InformationRegister") {
diff --git a/.claude/skills/meta-compile/scripts/meta-compile.py b/.claude/skills/meta-compile/scripts/meta-compile.py
index 6132bfe4..723840b1 100644
--- a/.claude/skills/meta-compile/scripts/meta-compile.py
+++ b/.claude/skills/meta-compile/scripts/meta-compile.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# meta-compile v1.30 — Compile 1C metadata object from JSON
+# meta-compile v1.31 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -550,14 +550,15 @@ def emit_type_content(indent, type_str):
if type_str == 'Boolean':
X(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|variable) — AllowedLength: Variable дефолт / Fixed (фикс. длина).
+ m = re.match(r'^String(\((\d+)(\s*,\s*(fixed|variable))?\))?$', type_str)
if m:
length = m.group(2) if m.group(2) else '10'
+ al = 'Fixed' if (m.group(4) and m.group(4).lower() == 'fixed') else 'Variable'
X(f'{indent}xs:string')
X(f'{indent}')
X(f'{indent}\t{length}')
- X(f'{indent}\tVariable')
+ X(f'{indent}\t{al}')
X(f'{indent}')
return
# Number without params -> Number(10,0)
@@ -853,6 +854,9 @@ def parse_attribute_shorthand(val):
'format': val.get('format'),
'editFormat': val.get('editFormat'),
'mask': str(val['mask']) if val.get('mask') else '',
+ 'extendedEdit': True if val.get('extendedEdit') is True else False,
+ 'minValue': val.get('minValue'),
+ 'maxValue': val.get('maxValue'),
'hasFillValue': ('fillValue' in val),
'fillValue': val.get('fillValue'),
'linkByType': val.get('linkByType'),
@@ -1552,6 +1556,14 @@ def emit_characteristics(indent, chars):
X(f'{indent}\t')
X(f'{indent}')
+def emit_min_max_value(indent, tag, val):
+ """/ — граница диапазона. None → nil. Число → xs:decimal, строка → xs:string."""
+ if val is None:
+ X(f'{indent}<{tag} xsi:nil="true"/>')
+ return
+ t = 'xs:string' if isinstance(val, str) else 'xs:decimal'
+ X(f'{indent}<{tag} xsi:type="{t}">{esc_xml(str(val))}{tag}>')
+
def emit_attribute(indent, parsed, context):
attr_name = parsed['name']
ctx_reserved = RESERVED_BY_CONTEXT.get(context)
@@ -1589,9 +1601,10 @@ def emit_attribute(indent, parsed, context):
X(f'{indent}\t\t')
multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false'
X(f'{indent}\t\t{multi_line}')
- X(f'{indent}\t\tfalse')
- X(f'{indent}\t\t')
- X(f'{indent}\t\t')
+ ext_edit = 'true' if parsed.get('extendedEdit') is True else 'false'
+ X(f'{indent}\t\t{ext_edit}')
+ emit_min_max_value(f'{indent}\t\t', 'MinValue', parsed.get('minValue'))
+ emit_min_max_value(f'{indent}\t\t', 'MaxValue', parsed.get('maxValue'))
# FillFromFillingValue / FillValue — not for tabular/processor/chart/register-other
# (Chart*, AccumulationRegister/AccountingRegister/CalculationRegister don't support these)
if context not in ('tabular', 'processor', 'chart', 'register-other'):
@@ -1776,9 +1789,10 @@ def emit_dimension(indent, parsed, register_type):
X(f'{indent}\t\t')
multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false'
X(f'{indent}\t\t{multi_line}')
- X(f'{indent}\t\tfalse')
- X(f'{indent}\t\t')
- X(f'{indent}\t\t')
+ ext_edit = 'true' if parsed.get('extendedEdit') is True else 'false'
+ X(f'{indent}\t\t{ext_edit}')
+ emit_min_max_value(f'{indent}\t\t', 'MinValue', parsed.get('minValue'))
+ emit_min_max_value(f'{indent}\t\t', 'MaxValue', parsed.get('maxValue'))
flags = parsed.get('flags', [])
if register_type == 'InformationRegister':
fill_from = 'true' if 'master' in flags else 'false'
@@ -1850,9 +1864,10 @@ def emit_resource(indent, parsed, register_type):
X(f'{indent}\t\t')
multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false'
X(f'{indent}\t\t{multi_line}')
- X(f'{indent}\t\tfalse')
- X(f'{indent}\t\t')
- X(f'{indent}\t\t')
+ ext_edit = 'true' if parsed.get('extendedEdit') is True else 'false'
+ X(f'{indent}\t\t{ext_edit}')
+ emit_min_max_value(f'{indent}\t\t', 'MinValue', parsed.get('minValue'))
+ emit_min_max_value(f'{indent}\t\t', 'MaxValue', parsed.get('maxValue'))
if register_type == 'InformationRegister':
X(f'{indent}\t\tfalse')
X(f'{indent}\t\t')
diff --git a/.claude/skills/meta-decompile/scripts/meta-decompile.ps1 b/.claude/skills/meta-decompile/scripts/meta-decompile.ps1
index 767be0e2..b621051c 100644
--- a/.claude/skills/meta-decompile/scripts/meta-decompile.ps1
+++ b/.claude/skills/meta-decompile/scripts/meta-decompile.ps1
@@ -1,4 +1,4 @@
-# meta-decompile v0.20 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
+# meta-decompile v0.21 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
#
# Пилот: только Catalog. Инверс meta-compile (omit-on-default: ключ эмитим только
@@ -156,9 +156,12 @@ function Get-TypeShorthand {
switch -regex ($raw) {
'(^|:)boolean$' { $parts += 'Boolean'; break }
'(^|:)string$' {
- $len = '10'
- if ($next -and $next.LocalName -eq 'StringQualifiers') { $l = $next.SelectSingleNode('v8:Length', $nsm); if ($l) { $len = $l.InnerText } }
- $parts += "String($len)"; break
+ $len = '10'; $al = ''
+ if ($next -and $next.LocalName -eq 'StringQualifiers') {
+ $l = $next.SelectSingleNode('v8:Length', $nsm); if ($l) { $len = $l.InnerText }
+ $aln = $next.SelectSingleNode('v8:AllowedLength', $nsm); if ($aln -and $aln.InnerText -eq 'Fixed') { $al = ',fixed' }
+ }
+ $parts += "String($len$al)"; break
}
'(^|:)decimal$' {
$d = '10'; $f = '0'; $sign = ''
@@ -279,6 +282,19 @@ function Attr-ToDsl {
$v = & $en 'Mask'; if ($v) { $extra['mask'] = $v }
$v = & $en 'ChoiceHistoryOnInput'; if ($v -and $v -ne 'Auto') { $extra['choiceHistoryOnInput'] = $v }
$v = & $en 'FillChecking'; if ($v -eq 'ShowWarning') { $extra['fillChecking'] = 'ShowWarning' }
+ $v = & $en 'ExtendedEdit'; if ($v -eq 'true') { $extra['extendedEdit'] = $true }
+ # MinValue/MaxValue — граница диапазона (omit при nil). Тип сохраняем: xs:string→строка, xs:decimal→число.
+ foreach ($mm in @('MinValue','MaxValue')) {
+ $mn = $ap.SelectSingleNode("md:$mm", $nsm)
+ if (-not $mn) { continue }
+ if ($mn.GetAttribute('nil', 'http://www.w3.org/2001/XMLSchema-instance') -eq 'true') { continue }
+ $key = if ($mm -eq 'MinValue') { 'minValue' } else { 'maxValue' }
+ $xt = $mn.GetAttribute('type', 'http://www.w3.org/2001/XMLSchema-instance')
+ $txt = $mn.InnerText
+ if ($xt -match 'decimal|int|double|float') {
+ if ($txt -match '^-?\d+$') { $extra[$key] = [long]$txt } else { $extra[$key] = [double]$txt }
+ } else { $extra[$key] = $txt }
+ }
$fmtV = Get-MLValue ($ap.SelectSingleNode('md:Format', $nsm)); if ($null -ne $fmtV) { $extra['format'] = $fmtV }
$efmtV = Get-MLValue ($ap.SelectSingleNode('md:EditFormat', $nsm)); if ($null -ne $efmtV) { $extra['editFormat'] = $efmtV }
diff --git a/docs/meta-dsl-spec.md b/docs/meta-dsl-spec.md
index ace5e5af..d71952d5 100644
--- a/docs/meta-dsl-spec.md
+++ b/docs/meta-dsl-spec.md
@@ -59,7 +59,8 @@ JSON DSL для описания объектов метаданных конф
| DSL | XML |
|-----|-----|
-| `String` или `String(100)` | `xs:string` + StringQualifiers |
+| `String` или `String(100)` | `xs:string` + StringQualifiers (AllowedLength=Variable) |
+| `String(100,fixed)` | `xs:string` + AllowedLength=Fixed (строка фикс. длины) |
| `Number(15,2)` | `xs:decimal` + NumberQualifiers |
| `Number(10,0,nonneg)` | `xs:decimal` + AllowedSign=Nonnegative |
| `Boolean` | `xs:boolean` |
@@ -180,6 +181,15 @@ JSON DSL для описания объектов метаданных конф
| `choiceHistoryOnInput` | ChoiceHistoryOnInput | Auto | Auto/DontUse |
| `indexing` | Indexing | DontIndex | флаги `index`/`indexAdditional` |
| `multiLine` | MultiLine | false | bool (флаг `multiline`) |
+| `extendedEdit` | ExtendedEdit | false | bool (расширенное редактирование — многострочное поле) |
+| `minValue` / `maxValue` | MinValue / MaxValue | nil (не задано) | граница диапазона, типизировано (см. ниже) |
+
+**`minValue` / `maxValue`** — границы диапазона значений реквизита (``/``). Без ключа →
+`xsi:nil="true"` (не задано). Значение **типизировано** (зеркало form-compile): JSON-**число** → `xsi:type="xs:decimal"`,
+JSON-**строка** → `xsi:type="xs:string"` (напр. год `"2000"`, код `"01"`). Тип сохраняется декомпилятором из XML.
+
+**Фикс. длина строки:** тип `String(N,fixed)` → `Fixed` (строка ровно N символов);
+`String(N)` / `String(N,variable)` → `Variable` (дефолт). См. §3.2.
**`fillValue` — значение заполнения реквизита** (``). Пара с `fillFromFillingValue` — единый блок
«заполнения» (недоступен у реквизитов ТЧ; там оба свойства не эмитятся). Форма **пустого** значения зависит от
diff --git a/tests/skills/cases/meta-compile/catalog-attr-props.json b/tests/skills/cases/meta-compile/catalog-attr-props.json
index ca9064f0..18a3230b 100644
--- a/tests/skills/cases/meta-compile/catalog-attr-props.json
+++ b/tests/skills/cases/meta-compile/catalog-attr-props.json
@@ -6,7 +6,10 @@
{ "name": "ИНН", "type": "String(12)", "fullTextSearch": "DontUse", "comment": "Налоговый номер",
"mask": "999999999999", "use": "ForFolderAndItem", "fillCheck": true },
{ "name": "Курс", "type": "Number(15,4)", "format": "ЧДЦ=4", "editFormat": { "ru": "ЧДЦ=4" },
- "createOnInput": "Use", "quickChoice": false, "fillFromFillingValue": true, "dataHistory": "DontUse", "passwordMode": true }
+ "createOnInput": "Use", "quickChoice": false, "fillFromFillingValue": true, "dataHistory": "DontUse", "passwordMode": true },
+ { "name": "ГодЗаключения", "type": "Number(4,0,nonneg)", "minValue": 2000, "maxValue": 3999 },
+ { "name": "КодРегиона", "type": "String(3,fixed)", "minValue": "01", "maxValue": "99" },
+ { "name": "Описание", "type": "String(500)", "extendedEdit": true }
]
},
"validatePath": "Catalogs/Контрагенты",
diff --git a/tests/skills/cases/meta-compile/snapshots/catalog-attr-props/Catalogs/Контрагенты.xml b/tests/skills/cases/meta-compile/snapshots/catalog-attr-props/Catalogs/Контрагенты.xml
index 2c34738a..f857e51c 100644
--- a/tests/skills/cases/meta-compile/snapshots/catalog-attr-props/Catalogs/Контрагенты.xml
+++ b/tests/skills/cases/meta-compile/snapshots/catalog-attr-props/Catalogs/Контрагенты.xml
@@ -186,6 +186,139 @@
DontUse
+
+
+ ГодЗаключения
+
+
+ ru
+ Год заключения
+
+
+
+
+ xs:decimal
+
+ 4
+ 0
+ Nonnegative
+
+
+ false
+
+
+
+ false
+
+ false
+ false
+ 2000
+ 3999
+ false
+ 0
+ DontCheck
+ Items
+
+
+ Auto
+ Auto
+
+
+ Auto
+
+ DontIndex
+ Use
+ Use
+
+
+
+
+ КодРегиона
+
+
+ ru
+ Код региона
+
+
+
+
+ xs:string
+
+ 3
+ Fixed
+
+
+ false
+
+
+
+ false
+
+ false
+ false
+ 01
+ 99
+ false
+
+ DontCheck
+ Items
+
+
+ Auto
+ Auto
+
+
+ Auto
+
+ DontIndex
+ Use
+ Use
+
+
+
+
+ Описание
+
+
+ ru
+ Описание
+
+
+
+
+ xs:string
+
+ 500
+ Variable
+
+
+ false
+
+
+
+ false
+
+ false
+ true
+
+
+ false
+
+ DontCheck
+ Items
+
+
+ Auto
+ Auto
+
+
+ Auto
+
+ DontIndex
+ Use
+ Use
+
+