feat(meta-compile,meta-decompile): значение заполнения реквизита fillValue (v1.27/v0.12)

Пара FillFromFillingValue+FillValue — единый блок «заполнения» (у top-level
реквизитов всегда, у ТЧ нет). Класс-2 фикс: boolean дефолт пустого FillValue
false→nil (модальная форма платформы, 3734 мисматча). Дефолт по типу:
String→typed-empty, Number→0, всё прочее→nil.

DSL-ключ fillValue (интерпретация по типу реквизита): bool/число/строка/дата-
литерал + null→nil-override + DTR-путь (полный рус/англ, GUID.GUID, короткая
запись по типу: EmptyRef / имя-значения перечисления / предопределённое).
Ref-резолвер портирован из form-compile (+ПустаяСсылка/Истина/Ложь, гард
составного типа). Декомпилятор: захват FillValue с omit-при-дефолте.

remaining=0 на выборке категории Attribute>FillValue (baseline-impact 7222),
регресс 39/39 ps1+py. spec v2.7 §4.2, кейс catalog-attr-fillvalue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-07-03 17:46:31 +03:00
parent d8146e1f1b
commit b93fd0807f
11 changed files with 1351 additions and 42 deletions
@@ -1,4 +1,4 @@
# meta-compile v1.16 — Compile 1C metadata object from JSON
# meta-compile v1.17 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -563,33 +563,153 @@ function Emit-ValueType {
X "$indent</Type>"
}
# --- FillValue (значение заполнения реквизита) ---
# Пара FillFromFillingValue+FillValue — единый блок «заполнения» (недоступен у реквизитов ТЧ).
# Форма пустого FillValue зависит от типа реквизита (то же значение по умолчанию, что и «пустое»
# значение типа): String→typed-empty, Number→0, всё остальное (Boolean/Date/Ref/составной/TypeSet)→nil.
# Реальное значение задаётся ключом `fillValue` (интерпретация по типу реквизита; см. §4.2 spec).
# Категория типа реквизита для выбора формы FillValue.
function Get-FillTypeCategory {
param([string]$typeStr)
if (-not $typeStr) { return 'String' } # реквизит без типа → неквалифиц. строка
if ($typeStr -match '\+') { return 'Other' } # составной тип → nil-дефолт
$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' # ссылки, TypeSet, ValueStorage, … → nil-дефолт
}
# Прощающий ввод для ссылочных путей DTR: рус/англ корни, ПустаяСсылка/EmptyRef, ЗначениеПеречисления/EnumValue.
$script:fillRefRoots = @{
'перечисление'='Enum'; 'справочник'='Catalog'; 'документ'='Document';
'плансчетов'='ChartOfAccounts'; 'планвидовхарактеристик'='ChartOfCharacteristicTypes';
'планвидоврасчета'='ChartOfCalculationTypes'; 'планвидоврасчёта'='ChartOfCalculationTypes';
'планобмена'='ExchangePlan'; 'бизнеспроцесс'='BusinessProcess'; 'задача'='Task';
'enum'='Enum'; 'catalog'='Catalog'; 'document'='Document'; 'chartofaccounts'='ChartOfAccounts';
'chartofcharacteristictypes'='ChartOfCharacteristicTypes'; 'chartofcalculationtypes'='ChartOfCalculationTypes';
'exchangeplan'='ExchangePlan'; 'businessprocess'='BusinessProcess'; 'task'='Task'
}
$script:fillEmptyRefWords = @('emptyref','пустаяссылка')
$script:fillEnumValWords = @('enumvalue','значениеперечисления')
$script:fillBoolTrue = @('true','истина','да')
$script:fillBoolFalse = @('false','ложь','нет')
# XxxRef (тип реквизита) → корень DTR-пути (для разворота короткой записи значения).
$script:fillRefKindRoot = @{
'catalogref'='Catalog'; 'documentref'='Document'; 'enumref'='Enum';
'chartofaccountsref'='ChartOfAccounts'; 'chartofcharacteristictypesref'='ChartOfCharacteristicTypes';
'chartofcalculationtypesref'='ChartOfCalculationTypes'; 'exchangeplanref'='ExchangePlan';
'businessprocessref'='BusinessProcess'; 'taskref'='Task'
}
# Короткая запись значения ссылочного реквизита (без точки): имя разворачиваем по типу реквизита.
# "EmptyRef"/"ПустаяСсылка" → <Root>.<Тип>.EmptyRef; для Enum — EnumValue; прочие — предопределённое.
# $null, если развернуть нельзя (тип не одиночный ссылочный).
function Expand-FillShortRef {
param([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"
}
# Строка → нормализованный DTR-путь ("Catalog.X.EmptyRef" / "Enum.X.EnumValue.Y" / GUID.GUID) ЛИБО $null (не ссылка).
function Normalize-FillRef {
param([string]$s)
if ([string]::IsNullOrEmpty($s)) { return $null }
# Raw-ссылка по паре GUID (метаданные.значение) — всегда ссылка.
if ($s -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\.[0-9a-fA-F-]+$') { return $s }
$parts = $s -split '\.'
if ($parts.Count -lt 2) { return $null }
$root = $script:fillRefRoots[$parts[0].ToLower()]
if (-not $root) { return $null }
$typeName = $parts[1]
if ($root -eq 'Enum') {
if ($parts.Count -eq 2) { return $null } # "Enum.X" — не значение
if ($parts.Count -eq 3) {
if ($script:fillEmptyRefWords -contains $parts[2].ToLower()) { return "Enum.$typeName.EmptyRef" }
return "Enum.$typeName.EnumValue.$($parts[2])"
}
$member = $parts[2]
if ($script:fillEnumValWords -contains $member.ToLower()) { $rest = $parts[3..($parts.Count-1)] -join '.' }
else { $rest = $parts[2..($parts.Count-1)] -join '.' }
return "Enum.$typeName.EnumValue.$rest"
}
# Прочие корни: переводим корень, ПустаяСсылка→EmptyRef в хвосте.
$tail = @($parts[1..($parts.Count-1)])
for ($i = 0; $i -lt $tail.Count; $i++) {
if ($script:fillEmptyRefWords -contains $tail[$i].ToLower()) { $tail[$i] = 'EmptyRef' }
}
return "$root." + ($tail -join '.')
}
# Строковый spec → @{ XsiType; Text }. Интерпретация по типу реквизита ($typeStr).
function Resolve-FillValueSpec {
param([string]$s, [string]$typeStr)
$cat = Get-FillTypeCategory $typeStr
if ($s -eq '') { return @{ XsiType='xs:string'; Text='' } }
# String-реквизит: значение заполнения — всегда строковый литерал (без ref/date-детекции).
if ($cat -eq 'String') { return @{ XsiType='xs:string'; Text=$s } }
# Булевы слова (для Boolean-реквизита ИЛИ явное истина/ложь).
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 } }
# Дата: явный Date-реквизит ИЛИ ISO-паттерн. "2020-01-01" → добавить время.
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 }
}
# Полный ссылочный путь DTR (с точкой: "Catalog.X.EmptyRef", "Enum.X.EnumValue.Y", GUID.GUID).
$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 }
}
# Формат числа-значения без привязки к культуре (точка-разделитель).
function Format-FillNum {
param($n)
if ($n -is [double] -or $n -is [decimal]) { return $n.ToString([System.Globalization.CultureInfo]::InvariantCulture) }
return "$n"
}
# $spec — значение ключа `fillValue` ($null при явном nil-override), $hasSpec — присутствует ли ключ.
function Emit-FillValue {
param([string]$indent, [string]$typeStr)
if (-not $typeStr) {
X "$indent<FillValue xsi:nil=`"true`"/>"
return
param([string]$indent, [string]$typeStr, $spec, $hasSpec)
$cat = Get-FillTypeCategory $typeStr
if ($hasSpec -ne $true) {
# Значение не задано — форма по умолчанию для типа.
switch ($cat) {
'String' { X "$indent<FillValue xsi:type=`"xs:string`"/>"; return }
'Number' { X "$indent<FillValue xsi:type=`"xs:decimal`">0</FillValue>"; return }
default { X "$indent<FillValue xsi:nil=`"true`"/>"; return }
}
}
$typeStr = Resolve-TypeStr $typeStr
if ($typeStr -eq "Boolean") {
X "$indent<FillValue xsi:type=`"xs:boolean`">false</FillValue>"
return
if ($null -eq $spec) { X "$indent<FillValue xsi:nil=`"true`"/>"; return } # явный nil-override
if ($spec -is [bool]) {
X "$indent<FillValue xsi:type=`"xs:boolean`">$(if ($spec) { 'true' } else { 'false' })</FillValue>"; return
}
if ($typeStr -match '^String') {
X "$indent<FillValue xsi:type=`"xs:string`"/>"
return
if ($spec -is [int] -or $spec -is [long] -or $spec -is [double] -or $spec -is [decimal]) {
X "$indent<FillValue xsi:type=`"xs:decimal`">$(Format-FillNum $spec)</FillValue>"; return
}
if ($typeStr -match '^Number') {
X "$indent<FillValue xsi:type=`"xs:decimal`">0</FillValue>"
return
}
if ($typeStr -match '^(Date|DateTime)$') {
X "$indent<FillValue xsi:nil=`"true`"/>"
return
}
# References and others
X "$indent<FillValue xsi:nil=`"true`"/>"
$r = Resolve-FillValueSpec "$spec" $typeStr
if ($r.Text -eq '' -and $r.XsiType -eq 'xs:string') { X "$indent<FillValue xsi:type=`"xs:string`"/>"; return }
X "$indent<FillValue xsi:type=`"$($r.XsiType)`">$(Esc-XmlText $r.Text)</FillValue>"
}
# --- 5. Attribute shorthand parser ---
@@ -620,6 +740,8 @@ function Parse-AttributeShorthand {
synonym = ""
comment = ""
flags = @()
hasFillValue = $false
fillValue = $null
}
# Split by | for flags
@@ -669,6 +791,8 @@ function Parse-AttributeShorthand {
format = $val.format
editFormat = $val.editFormat
mask = if ($val.mask) { "$($val.mask)" } else { "" }
hasFillValue = ($val.PSObject -and $val.PSObject.Properties -and ($val.PSObject.Properties.Name -contains 'fillValue'))
fillValue = $val.fillValue
}
}
@@ -1020,7 +1144,7 @@ function Emit-Attribute {
# FillValue — same restriction
if ($context -notin @("tabular", "processor", "chart", "register-other")) {
Emit-FillValue "$indent`t`t" $typeStr
Emit-FillValue "$indent`t`t" $typeStr $parsed.fillValue $parsed.hasFillValue
}
# FillChecking
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-compile v1.15 — Compile 1C metadata object from JSON
# meta-compile v1.17 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -587,24 +587,157 @@ def emit_value_type(indent, type_str):
emit_type_content(f'{indent}\t', type_str)
X(f'{indent}</Type>')
def emit_fill_value(indent, type_str):
# --- FillValue (значение заполнения реквизита) ---
# Пара FillFromFillingValue+FillValue — единый блок «заполнения» (недоступен у реквизитов ТЧ).
# Форма пустого FillValue зависит от типа реквизита (то же значение по умолчанию, что и «пустое»
# значение типа): String→typed-empty, Number→0, всё остальное (Boolean/Date/Ref/составной/TypeSet)→nil.
# Реальное значение задаётся ключом `fillValue` (интерпретация по типу реквизита; см. §4.2 spec).
def get_fill_type_category(type_str):
if not type_str:
X(f'{indent}<FillValue xsi:nil="true"/>')
return 'String' # реквизит без типа → неквалифиц. строка
if '+' in type_str:
return 'Other' # составной тип → nil-дефолт
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' # ссылки, TypeSet, ValueStorage, … → nil-дефолт
# Прощающий ввод для ссылочных путей DTR: рус/англ корни, ПустаяСсылка/EmptyRef, ЗначениеПеречисления/EnumValue.
fill_ref_roots = {
'перечисление': 'Enum', 'справочник': 'Catalog', 'документ': 'Document',
'плансчетов': 'ChartOfAccounts', 'планвидовхарактеристик': 'ChartOfCharacteristicTypes',
'планвидоврасчета': 'ChartOfCalculationTypes', 'планвидоврасчёта': 'ChartOfCalculationTypes',
'планобмена': 'ExchangePlan', 'бизнеспроцесс': 'BusinessProcess', 'задача': 'Task',
'enum': 'Enum', 'catalog': 'Catalog', 'document': 'Document', 'chartofaccounts': 'ChartOfAccounts',
'chartofcharacteristictypes': 'ChartOfCharacteristicTypes', 'chartofcalculationtypes': 'ChartOfCalculationTypes',
'exchangeplan': 'ExchangePlan', 'businessprocess': 'BusinessProcess', 'task': 'Task',
}
fill_empty_ref_words = ('emptyref', 'пустаяссылка')
fill_enum_val_words = ('enumvalue', 'значениеперечисления')
fill_bool_true = ('true', 'истина', 'да')
fill_bool_false = ('false', 'ложь', 'нет')
# XxxRef (тип реквизита) → корень DTR-пути (для разворота короткой записи значения).
fill_ref_kind_root = {
'catalogref': 'Catalog', 'documentref': 'Document', 'enumref': 'Enum',
'chartofaccountsref': 'ChartOfAccounts', 'chartofcharacteristictypesref': 'ChartOfCharacteristicTypes',
'chartofcalculationtypesref': 'ChartOfCalculationTypes', 'exchangeplanref': 'ExchangePlan',
'businessprocessref': 'BusinessProcess', 'taskref': 'Task',
}
def normalize_fill_ref(s):
"""Строка → нормализованный DTR-путь ЛИБО None (не ссылка)."""
if not s:
return None
if re.match(r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\.[0-9a-fA-F-]+$', s):
return s
parts = s.split('.')
if len(parts) < 2:
return None
root = fill_ref_roots.get(parts[0].lower())
if not root:
return None
type_name = parts[1]
if root == 'Enum':
if len(parts) == 2:
return None
if len(parts) == 3:
if parts[2].lower() in fill_empty_ref_words:
return f'Enum.{type_name}.EmptyRef'
return f'Enum.{type_name}.EnumValue.{parts[2]}'
if parts[2].lower() in fill_enum_val_words:
rest = '.'.join(parts[3:])
else:
rest = '.'.join(parts[2:])
return f'Enum.{type_name}.EnumValue.{rest}'
tail = list(parts[1:])
for i in range(len(tail)):
if tail[i].lower() in fill_empty_ref_words:
tail[i] = 'EmptyRef'
return f'{root}.' + '.'.join(tail)
def expand_fill_short_ref(s, type_str):
"""Короткая запись значения ссылочного реквизита (без точки) → полный DTR-путь по типу, либо None."""
if not type_str:
return None
if '+' 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):
"""Строковый spec → (xsi_type, text). Интерпретация по типу реквизита."""
cat = get_fill_type_category(type_str)
if s == '':
return ('xs:string', '')
if cat == 'String':
return ('xs:string', 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 ('xs:boolean', 'true')
if s.lower() in fill_bool_false:
return ('xs:boolean', 'false')
if cat == 'Number':
return ('xs:decimal', 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 = f'{s}T00:00:00'
return ('xs:dateTime', s)
ref = normalize_fill_ref(s)
if ref:
return ('xr:DesignTimeRef', ref)
short = expand_fill_short_ref(s, type_str)
if short:
return ('xr:DesignTimeRef', short)
return ('xs:string', s)
def format_fill_num(n):
if isinstance(n, bool):
return 'true' if n else 'false'
return str(n)
def emit_fill_value(indent, type_str, spec, has_spec):
"""spec — значение ключа fillValue (None при явном nil-override), has_spec — присутствует ли ключ."""
cat = get_fill_type_category(type_str)
if not has_spec:
if cat == 'String':
X(f'{indent}<FillValue xsi:type="xs:string"/>')
elif cat == 'Number':
X(f'{indent}<FillValue xsi:type="xs:decimal">0</FillValue>')
else:
X(f'{indent}<FillValue xsi:nil="true"/>')
return
type_str = resolve_type_str(type_str)
if type_str == 'Boolean':
X(f'{indent}<FillValue xsi:type="xs:boolean">false</FillValue>')
if spec is None:
X(f'{indent}<FillValue xsi:nil="true"/>') # явный nil-override
return
if re.match(r'^String', type_str):
if isinstance(spec, bool):
X(f'{indent}<FillValue xsi:type="xs:boolean">{"true" if spec else "false"}</FillValue>')
return
if isinstance(spec, (int, float)):
X(f'{indent}<FillValue xsi:type="xs:decimal">{format_fill_num(spec)}</FillValue>')
return
xsi_type, text = resolve_fill_value_spec(str(spec), type_str)
if text == '' and xsi_type == 'xs:string':
X(f'{indent}<FillValue xsi:type="xs:string"/>')
return
if re.match(r'^Number', type_str):
X(f'{indent}<FillValue xsi:type="xs:decimal">0</FillValue>')
return
if re.match(r'^(Date|DateTime)$', type_str):
X(f'{indent}<FillValue xsi:nil="true"/>')
return
X(f'{indent}<FillValue xsi:nil="true"/>')
X(f'{indent}<FillValue xsi:type="{xsi_type}">{esc_xml_text(text)}</FillValue>')
# ---------------------------------------------------------------------------
# 5. Attribute shorthand parser
@@ -631,6 +764,8 @@ def parse_attribute_shorthand(val):
'flags': [],
'fillChecking': '',
'indexing': '',
'hasFillValue': False,
'fillValue': None,
}
parts = val.split('|', 1)
main_part = parts[0].strip()
@@ -677,6 +812,8 @@ def parse_attribute_shorthand(val):
'format': val.get('format'),
'editFormat': val.get('editFormat'),
'mask': str(val['mask']) if val.get('mask') else '',
'hasFillValue': ('fillValue' in val),
'fillValue': val.get('fillValue'),
}
def parse_enum_value_shorthand(val):
@@ -1023,7 +1160,7 @@ def emit_attribute(indent, parsed, context):
ffv = 'true' if parsed.get('fillFromFillingValue') is True else 'false'
X(f'{indent}\t\t<FillFromFillingValue>{ffv}</FillFromFillingValue>')
if context not in ('tabular', 'processor', 'chart', 'register-other'):
emit_fill_value(f'{indent}\t\t', type_str)
emit_fill_value(f'{indent}\t\t', type_str, parsed.get('fillValue'), parsed.get('hasFillValue'))
fill_checking = 'DontCheck'
if 'req' in parsed.get('flags', []):
fill_checking = 'ShowError'
@@ -1,4 +1,4 @@
# meta-decompile v0.11 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# meta-decompile v0.12 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
#
# Пилот: только Catalog. Инверс meta-compile (omit-on-default: ключ эмитим только
@@ -221,6 +221,36 @@ function Attr-ToDsl {
$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 }
# FillValue (значение заполнения). Форма по умолчанию зависит от типа реквизита: String→typed-empty,
# Number→zero, всё остальное→nil. Эмитим `fillValue` только при отклонении от дефолта (§4.2 spec).
# nil у String/Number → JSON null (nil-override); реальное значение → строка/bool/число/DTR-путь verbatim.
$fvNode = $ap.SelectSingleNode('md:FillValue', $nsm)
if ($fvNode) {
$fcat = 'Other'
if ($ts -match '\+') { $fcat = 'Other' }
elseif ($ts -match '^Boolean') { $fcat = 'Boolean' }
elseif ($ts -match '^String') { $fcat = 'String' }
elseif ($ts -match '^Number') { $fcat = 'Number' }
elseif ($ts -match '^(Date|DateTime)') { $fcat = 'Date' }
$nilAttr = $fvNode.GetAttribute('nil', 'http://www.w3.org/2001/XMLSchema-instance')
$xsiT = $fvNode.GetAttribute('type', 'http://www.w3.org/2001/XMLSchema-instance')
$fvText = $fvNode.InnerText
if ($nilAttr -eq 'true') {
if ($fcat -eq 'String' -or $fcat -eq 'Number') { $extra['fillValue'] = $null } # nil-override
# иначе nil — это дефолт → пропускаем
} elseif ($xsiT -match 'boolean$') {
$extra['fillValue'] = ($fvText -eq 'true')
} elseif ($xsiT -match 'decimal$') {
if (-not ($fcat -eq 'Number' -and ($fvText -eq '0' -or $fvText -eq ''))) { $extra['fillValue'] = $fvText }
} elseif ($xsiT -match 'string$') {
if (-not ($fcat -eq 'String' -and $fvText -eq '')) { $extra['fillValue'] = $fvText }
} elseif ($xsiT -match 'dateTime$') {
$extra['fillValue'] = $fvText
} elseif ($xsiT -match 'DesignTimeRef$') {
$extra['fillValue'] = $fvText
}
}
if ($synCustom -or ($null -ne $ttVal) -or $extra.Count -gt 0) {
$o = [ordered]@{ name = $nm }
if ($ts) { $o['type'] = $ts }