feat(meta-edit): структурное свойство FillValue явный (Фаза 2 Шаг 3b-2b, v1.17)

modify-attribute/-dimension/-resource: FillValue с явным значением — порт Emit-FillValue
+ Resolve-FillValueSpec/Get-FillTypeCategory/Expand-FillShortRef/fillBool-таблицы в
meta-edit. Тип реквизита извлекается из XML (<Type>/<v8:Type>, Get-AttrTypeStrFromXml) —
по нему категоризация значения. Маркеры {nil}/{emptyRef}; bool→xs:boolean, число→
xs:decimal, ref-путь/короткая ссылка→xr:DesignTimeRef (EmptyRef разворачивается по типу),
строка→xs:string. Esc-XmlText (&<> без ") для паритета текста FillValue.

Завершает Шаг 3: все структурные свойства реквизита (Format/EditFormat/ToolTip/ChoiceForm/
MinValue/MaxValue/LinkByType/ChoiceParameterLinks/ChoiceParameters/FillValue) доступны в
modify, БАЙТ-В-БАЙТ = meta-compile (ps1 и py), платформенный cert в 1С.

Cert декомпиляцией: fillValue на 4 типах реквизита = meta-compile. verify-snapshots
--case modify-attribute-structural (FillValue EmptyRef+decimal) грузится в 1С.
meta-edit 14/14 ps1+py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-07-12 10:47:07 +03:00
parent 39dc3fc1e7
commit 0d02159a34
4 changed files with 224 additions and 5 deletions
+93 -1
View File
@@ -1,4 +1,4 @@
# meta-edit v1.16 — Edit existing 1C metadata object XML (+structural attr props Format/EditFormat/ToolTip/ChoiceForm/MinValue/MaxValue/LinkByType/ChoiceParameterLinks/ChoiceParameters)
# meta-edit v1.17 — Edit existing 1C metadata object XML (+structural attr props Format/EditFormat/ToolTip/ChoiceForm/MinValue/MaxValue/LinkByType/ChoiceParameterLinks/ChoiceParameters/FillValue)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$DefinitionFile,
@@ -2287,6 +2287,11 @@ function Modify-ChildElements($modifyDef, [string]$childType) {
Info "Set $xmlTag '$elemName'.ChoiceParameters"; $script:modifyCount++
}
}
"FillValue" {
if (Set-AttrPropertyElement $propsEl "FillValue" (Build-FillValueExplicitXml (Get-AttrTypeStrFromXml $propsEl) $changeValue)) {
Info "Set $xmlTag '$elemName'.FillValue"; $script:modifyCount++
}
}
default {
# Scalar property change (Indexing, FillChecking, Use, etc.)
$scalarEl = $null
@@ -2698,6 +2703,93 @@ function Build-ChoiceParametersXml([string]$indent, $cp) {
return $sb.ToString()
}
# --- Порт из meta-compile: явное значение заполнения (FillValue) ---
$script:fillBoolTrue = @('true','истина','да')
$script:fillBoolFalse = @('false','ложь','нет')
function Esc-XmlText([string]$s) { return $s.Replace('&','&amp;').Replace('<','&lt;').Replace('>','&gt;') }
function Get-FillTypeCategory([string]$typeStr) {
if (-not $typeStr) { return 'String' }
if ($typeStr -match '\+') { return 'Other' }
$t = Resolve-TypeStr $typeStr
if ($t -match '^Boolean$') { return 'Boolean' }
if ($t -match '^String(\(|$)') { return 'String' }
if ($t -match '^Number(\(|$)') { return 'Number' }
if ($t -match '^(Date|DateTime)$') { return 'Date' }
return 'Other'
}
function Expand-FillShortRef([string]$s, [string]$typeStr) {
if (-not $typeStr) { return $null }
if ($typeStr -match '\+') { return $null }
$t = Resolve-TypeStr $typeStr
if ($t -notmatch '^(\w+Ref)\.(.+)$') { return $null }
$root = $script:fillRefKindRoot[$Matches[1].ToLower()]
if (-not $root) { return $null }
$typeName = $Matches[2]
if ($script:fillEmptyRefWords -contains $s.ToLower()) { return "$root.$typeName.EmptyRef" }
if ($root -eq 'Enum') { return "Enum.$typeName.EnumValue.$s" }
return "$root.$typeName.$s"
}
function Resolve-FillValueSpec([string]$s, [string]$typeStr) {
$cat = Get-FillTypeCategory $typeStr
if ($s -eq '') { return @{ XsiType='xs:string'; Text='' } }
if ($cat -eq 'String') { return @{ XsiType='xs:string'; Text=$s } }
if ($cat -eq 'Boolean' -or ($script:fillBoolTrue -contains $s.ToLower()) -or ($script:fillBoolFalse -contains $s.ToLower())) {
if ($script:fillBoolTrue -contains $s.ToLower()) { return @{ XsiType='xs:boolean'; Text='true' } }
if ($script:fillBoolFalse -contains $s.ToLower()) { return @{ XsiType='xs:boolean'; Text='false' } }
}
if ($cat -eq 'Number') { return @{ XsiType='xs:decimal'; Text=$s } }
if ($cat -eq 'Date' -or $s -match '^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2})?$') {
if ($s -match '^\d{4}-\d{2}-\d{2}$') { $s = "${s}T00:00:00" }
return @{ XsiType='xs:dateTime'; Text=$s }
}
$ref = Normalize-FillRef $s
if ($ref) { return @{ XsiType='xr:DesignTimeRef'; Text=$ref } }
$short = Expand-FillShortRef $s $typeStr
if ($short) { return @{ XsiType='xr:DesignTimeRef'; Text=$short } }
return @{ XsiType='xs:string'; Text=$s }
}
# Извлечь тип реквизита из XML (<Type>/<v8:Type>) → DSL-typeStr для категоризации FillValue.
function Get-AttrTypeStrFromXml($propsEl) {
$typeEl = $null
foreach ($ch in $propsEl.ChildNodes) { if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'Type') { $typeEl = $ch; break } }
if (-not $typeEl) { return "" }
$mapped = @()
foreach ($ch in $typeEl.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'Type') {
$t = $ch.InnerText.Trim()
$colon = $t.IndexOf(':'); if ($colon -ge 0) { $t = $t.Substring($colon + 1) }
switch -Regex ($t) {
'^string$' { $mapped += 'String'; break }
'^decimal$' { $mapped += 'Number'; break }
'^boolean$' { $mapped += 'Boolean'; break }
'^dateTime$' { $mapped += 'Date'; break }
default { $mapped += $t }
}
}
}
if ($mapped.Count -eq 0) { return "" }
if ($mapped.Count -gt 1) { return ($mapped -join ' + ') }
return $mapped[0]
}
# FillValue — явное значение (порт Emit-FillValue, ветка hasSpec). Маркеры {nil}/{emptyRef}; иначе по типу.
function Build-FillValueExplicitXml([string]$typeStr, $spec) {
if ($null -eq $spec) { return "<FillValue xsi:nil=`"true`"/>" }
if ($spec -is [bool]) { return "<FillValue xsi:type=`"xs:boolean`">$(if ($spec) { 'true' } else { 'false' })</FillValue>" }
if ($spec -is [int] -or $spec -is [long] -or $spec -is [double] -or $spec -is [decimal]) { return "<FillValue xsi:type=`"xs:decimal`">$(Format-FillNum $spec)</FillValue>" }
if ((Get-ChElProp $spec @('nil')) -eq $true) { return "<FillValue xsi:nil=`"true`"/>" }
if ((Get-ChElProp $spec @('emptyRef','пустаяссылка')) -eq $true) { return "<FillValue xsi:type=`"xr:DesignTimeRef`"/>" }
$r = Resolve-FillValueSpec "$spec" $typeStr
if ($r.Text -eq '' -and $r.XsiType -eq 'xs:string') { return "<FillValue xsi:type=`"xs:string`"/>" }
return "<FillValue xsi:type=`"$($r.XsiType)`">$(Esc-XmlText $r.Text)</FillValue>"
}
function Find-PropertyElement([string]$propName) {
foreach ($child in $script:propertiesEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $propName) {
+127 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-edit v1.16 — Edit existing 1C metadata object XML (+structural attr props Format/EditFormat/ToolTip/ChoiceForm/MinValue/MaxValue/LinkByType/ChoiceParameterLinks/ChoiceParameters)
# meta-edit v1.17 — Edit existing 1C metadata object XML (+structural attr props Format/EditFormat/ToolTip/ChoiceForm/MinValue/MaxValue/LinkByType/ChoiceParameterLinks/ChoiceParameters/FillValue)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -2127,6 +2127,10 @@ def modify_child_elements(modify_def, child_type):
if set_attr_property_element(props_el, "ChoiceParameters", build_choice_parameters_xml(get_child_indent(props_el), change_value)):
info(f"Set {xml_tag} '{elem_name}'.ChoiceParameters")
modify_count += 1
elif change_prop == "FillValue":
if set_attr_property_element(props_el, "FillValue", build_fill_value_explicit_xml(get_attr_type_str_from_xml(props_el), change_value)):
info(f"Set {xml_tag} '{elem_name}'.FillValue")
modify_count += 1
else:
# Scalar property change (Indexing, FillChecking, Use, etc.)
@@ -2577,6 +2581,128 @@ def build_choice_parameters_xml(indent, cp):
return "\r\n".join(parts)
# --- Порт из meta-compile: явное значение заполнения (FillValue) ---
fill_bool_true = {'true', 'истина', 'да'}
fill_bool_false = {'false', 'ложь', 'нет'}
def esc_xml_text(s):
return s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
def get_fill_type_category(type_str):
if not type_str:
return 'String'
if '+' in type_str:
return 'Other'
t = resolve_type_str(type_str)
if re.match(r'^Boolean$', t):
return 'Boolean'
if re.match(r'^String(\(|$)', t):
return 'String'
if re.match(r'^Number(\(|$)', t):
return 'Number'
if re.match(r'^(Date|DateTime)$', t):
return 'Date'
return 'Other'
def expand_fill_short_ref(s, type_str):
if not type_str or '+' in type_str:
return None
t = resolve_type_str(type_str)
m = re.match(r'^(\w+Ref)\.(.+)$', t)
if not m:
return None
root = fill_ref_kind_root.get(m.group(1).lower())
if not root:
return None
type_name = m.group(2)
if s.lower() in fill_empty_ref_words:
return f"{root}.{type_name}.EmptyRef"
if root == 'Enum':
return f"Enum.{type_name}.EnumValue.{s}"
return f"{root}.{type_name}.{s}"
def resolve_fill_value_spec(s, type_str):
cat = get_fill_type_category(type_str)
if s == '':
return {'XsiType': 'xs:string', 'Text': ''}
if cat == 'String':
return {'XsiType': 'xs:string', 'Text': s}
if cat == 'Boolean' or s.lower() in fill_bool_true or s.lower() in fill_bool_false:
if s.lower() in fill_bool_true:
return {'XsiType': 'xs:boolean', 'Text': 'true'}
if s.lower() in fill_bool_false:
return {'XsiType': 'xs:boolean', 'Text': 'false'}
if cat == 'Number':
return {'XsiType': 'xs:decimal', 'Text': s}
if cat == 'Date' or re.match(r'^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2})?$', s):
if re.match(r'^\d{4}-\d{2}-\d{2}$', s):
s = s + 'T00:00:00'
return {'XsiType': 'xs:dateTime', 'Text': s}
ref = normalize_fill_ref(s)
if ref:
return {'XsiType': 'xr:DesignTimeRef', 'Text': ref}
short = expand_fill_short_ref(s, type_str)
if short:
return {'XsiType': 'xr:DesignTimeRef', 'Text': short}
return {'XsiType': 'xs:string', 'Text': s}
def get_attr_type_str_from_xml(props_el):
"""Извлечь тип реквизита из XML (<Type>/<v8:Type>) → DSL-typeStr для категоризации FillValue."""
type_el = None
for ch in props_el:
if localname(ch) == 'Type':
type_el = ch
break
if type_el is None:
return ""
mapped = []
for ch in type_el:
if localname(ch) == 'Type':
t = (ch.text or '').strip()
colon = t.find(':')
if colon >= 0:
t = t[colon + 1:]
if t == 'string':
mapped.append('String')
elif t == 'decimal':
mapped.append('Number')
elif t == 'boolean':
mapped.append('Boolean')
elif t == 'dateTime':
mapped.append('Date')
else:
mapped.append(t)
if not mapped:
return ""
if len(mapped) > 1:
return ' + '.join(mapped)
return mapped[0]
def build_fill_value_explicit_xml(type_str, spec):
"""FillValue — явное значение (порт Emit-FillValue, ветка hasSpec)."""
if spec is None:
return '<FillValue xsi:nil="true"/>'
if isinstance(spec, bool):
return f'<FillValue xsi:type="xs:boolean">{"true" if spec else "false"}</FillValue>'
if isinstance(spec, (int, float)):
return f'<FillValue xsi:type="xs:decimal">{format_fill_num(spec)}</FillValue>'
if get_ch_el_prop(spec, ['nil']) is True:
return '<FillValue xsi:nil="true"/>'
if get_ch_el_prop(spec, ['emptyRef', 'пустаяссылка']) is True:
return '<FillValue xsi:type="xr:DesignTimeRef"/>'
r = resolve_fill_value_spec(str(spec), type_str)
if r['Text'] == '' and r['XsiType'] == 'xs:string':
return '<FillValue xsi:type="xs:string"/>'
return f'<FillValue xsi:type="{r["XsiType"]}">{esc_xml_text(r["Text"])}</FillValue>'
def find_property_element(prop_name):
for child in properties_el:
if localname(child) == prop_name:
@@ -24,9 +24,10 @@
"ChoiceParameters": [
{ "name": "Отбор.ПометкаУдаления", "value": false },
{ "name": "Отбор.Ссылка", "value": "Catalog.Спр.EmptyRef" }
]
],
"FillValue": "EmptyRef"
},
"Сумма": { "MinValue": 0, "MaxValue": 1000000 }
"Сумма": { "MinValue": 0, "MaxValue": 1000000, "FillValue": 0 }
}
}
}
@@ -169,7 +169,7 @@
<MinValue xsi:nil="true" />
<MaxValue xsi:nil="true" />
<FillFromFillingValue>false</FillFromFillingValue>
<FillValue xsi:nil="true" />
<FillValue xsi:type="xr:DesignTimeRef">Catalog.Спр.EmptyRef</FillValue>
<FillChecking>DontCheck</FillChecking>
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
<ChoiceParameterLinks>