mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-23 05:01:01 +03:00
feat(meta-compile,meta-decompile): мультиязычный ML для синонима и подсказки реквизита (v1.18/v0.4)
Раундтрип-находка (класс-1 синоним + класс-3 подсказка). Корпус: у реквизитов 21677 мультиязычных
синонимов (ru+en) и 16063 непустых подсказки (10689 мультиязычных). meta-compile писал ВСЁ ML только ru
(Emit-MLText брал строку) и хардкодил <ToolTip/> пустым → массовая потеря en + подсказок.
- meta-compile (дуал-порт): Emit-MLText/emit_mltext принимают строку (→ru) ИЛИ объект {lang:content}
(→<v8:item> на язык, в порядке ключей) через новый Emit-MLItems/emit_ml_items. Parse object-форма
реквизита пробрасывает synonym/tooltip без стрингификации; Emit-Attribute эмитит <ToolTip> из tooltip.
- meta-decompile: Get-MLValue → строка (ru-only) | {ru,en} (мультиязычно, порядок из XML); object-форма
реквизита при кастомном синониме ИЛИ наличии подсказки.
- spec meta-dsl-spec.md v2.3: §4.2 (tooltip) + §4.4 ML-значения (строка/{ru,en}), консистентно с form-compile.
- тест-кейс catalog-attr-ml (мультиязычный синоним + подсказка, обе формы).
Валидация: PS==PY паритет; полный прогон −228748 строк (447285→218537, >половины); регресс 35/35 (ps+py);
1С-cert зелёный.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ed42d5e9cb
commit
58bc95264e
@@ -331,17 +331,30 @@ function Esc-Xml {
|
||||
return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"')
|
||||
}
|
||||
|
||||
# ML-значение: строка → один <v8:item> ru; объект {lang: content} → item на язык (в порядке ключей).
|
||||
function Emit-MLItems {
|
||||
param([string]$indent, $val)
|
||||
if ($val -is [System.Collections.IDictionary]) {
|
||||
foreach ($k in $val.Keys) {
|
||||
X "$indent<v8:item>"; X "$indent`t<v8:lang>$k</v8:lang>"; X "$indent`t<v8:content>$(Esc-Xml "$($val[$k])")</v8:content>"; X "$indent</v8:item>"
|
||||
}
|
||||
} elseif ($val -is [System.Management.Automation.PSCustomObject]) {
|
||||
foreach ($p in $val.PSObject.Properties) {
|
||||
X "$indent<v8:item>"; X "$indent`t<v8:lang>$($p.Name)</v8:lang>"; X "$indent`t<v8:content>$(Esc-Xml "$($p.Value)")</v8:content>"; X "$indent</v8:item>"
|
||||
}
|
||||
} else {
|
||||
X "$indent<v8:item>"; X "$indent`t<v8:lang>ru</v8:lang>"; X "$indent`t<v8:content>$(Esc-Xml "$val")</v8:content>"; X "$indent</v8:item>"
|
||||
}
|
||||
}
|
||||
function Emit-MLText {
|
||||
param([string]$indent, [string]$tag, [string]$text)
|
||||
if (-not $text) {
|
||||
param([string]$indent, [string]$tag, $text)
|
||||
# Пусто (null / пустая строка) → самозакрывающийся тег.
|
||||
if (($null -eq $text) -or (($text -is [string]) -and ($text -eq ''))) {
|
||||
X "$indent<$tag/>"
|
||||
return
|
||||
}
|
||||
X "$indent<$tag>"
|
||||
X "$indent`t<v8:item>"
|
||||
X "$indent`t`t<v8:lang>ru</v8:lang>"
|
||||
X "$indent`t`t<v8:content>$(Esc-Xml $text)</v8:content>"
|
||||
X "$indent`t</v8:item>"
|
||||
Emit-MLItems "$indent`t" $text
|
||||
X "$indent</$tag>"
|
||||
}
|
||||
|
||||
@@ -611,12 +624,13 @@ function Parse-AttributeShorthand {
|
||||
return $parsed
|
||||
}
|
||||
|
||||
# Object form
|
||||
# Object form. synonym/tooltip — сквозной проброс (строка ИЛИ объект {ru,en}), НЕ стрингифаим.
|
||||
$name = "$($val.name)"
|
||||
return @{
|
||||
name = $name
|
||||
type = Build-TypeStr $val
|
||||
synonym = if ($val.synonym) { "$($val.synonym)" } else { Split-CamelCase $name }
|
||||
synonym = if ($null -ne $val.synonym) { $val.synonym } else { Split-CamelCase $name }
|
||||
tooltip = $val.tooltip
|
||||
comment = if ($val.comment) { "$($val.comment)" } else { "" }
|
||||
flags = @(if ($val.flags) { $val.flags } else { @() })
|
||||
fillChecking = if ($val.fillChecking) { "$($val.fillChecking)" } else { "" }
|
||||
@@ -962,7 +976,7 @@ function Emit-Attribute {
|
||||
X "$indent`t`t<PasswordMode>false</PasswordMode>"
|
||||
X "$indent`t`t<Format/>"
|
||||
X "$indent`t`t<EditFormat/>"
|
||||
X "$indent`t`t<ToolTip/>"
|
||||
Emit-MLText "$indent`t`t" "ToolTip" $parsed.tooltip
|
||||
X "$indent`t`t<MarkNegatives>false</MarkNegatives>"
|
||||
X "$indent`t`t<Mask/>"
|
||||
$multiLine = if ($parsed.multiLine -eq $true -or $parsed.flags -contains "multiline") { "true" } else { "false" }
|
||||
|
||||
@@ -197,15 +197,27 @@ lines = []
|
||||
def X(text):
|
||||
lines.append(text)
|
||||
|
||||
# ML-значение: строка → один <v8:item> ru; dict {lang: content} → item на язык (в порядке ключей).
|
||||
def emit_ml_items(indent, val):
|
||||
if isinstance(val, dict):
|
||||
for k, v in val.items():
|
||||
X(f'{indent}<v8:item>')
|
||||
X(f'{indent}\t<v8:lang>{k}</v8:lang>')
|
||||
X(f'{indent}\t<v8:content>{esc_xml(str(v))}</v8:content>')
|
||||
X(f'{indent}</v8:item>')
|
||||
else:
|
||||
X(f'{indent}<v8:item>')
|
||||
X(f'{indent}\t<v8:lang>ru</v8:lang>')
|
||||
X(f'{indent}\t<v8:content>{esc_xml(str(val))}</v8:content>')
|
||||
X(f'{indent}</v8:item>')
|
||||
|
||||
def emit_mltext(indent, tag, text):
|
||||
if not text:
|
||||
# Пусто (None / '') → самозакрывающийся тег.
|
||||
if text is None or (isinstance(text, str) and text == ''):
|
||||
X(f'{indent}<{tag}/>')
|
||||
return
|
||||
X(f'{indent}<{tag}>')
|
||||
X(f'{indent}\t<v8:item>')
|
||||
X(f'{indent}\t\t<v8:lang>ru</v8:lang>')
|
||||
X(f'{indent}\t\t<v8:content>{esc_xml(text)}</v8:content>')
|
||||
X(f'{indent}\t</v8:item>')
|
||||
emit_ml_items(f'{indent}\t', text)
|
||||
X(f'{indent}</{tag}>')
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -615,12 +627,13 @@ def parse_attribute_shorthand(val):
|
||||
parsed['type'] = colon_parts[1].strip()
|
||||
parsed['synonym'] = split_camel_case(parsed['name'])
|
||||
return parsed
|
||||
# Object form
|
||||
# Object form. synonym/tooltip — сквозной проброс (строка ИЛИ dict {ru,en}), НЕ стрингифаим.
|
||||
name = str(val.get('name', ''))
|
||||
return {
|
||||
'name': name,
|
||||
'type': build_type_str(val),
|
||||
'synonym': str(val['synonym']) if val.get('synonym') else split_camel_case(name),
|
||||
'synonym': val['synonym'] if val.get('synonym') is not None else split_camel_case(name),
|
||||
'tooltip': val.get('tooltip'),
|
||||
'comment': str(val['comment']) if val.get('comment') else '',
|
||||
'flags': list(val.get('flags', [])),
|
||||
'fillChecking': str(val['fillChecking']) if val.get('fillChecking') else '',
|
||||
@@ -957,7 +970,7 @@ def emit_attribute(indent, parsed, context):
|
||||
X(f'{indent}\t\t<PasswordMode>false</PasswordMode>')
|
||||
X(f'{indent}\t\t<Format/>')
|
||||
X(f'{indent}\t\t<EditFormat/>')
|
||||
X(f'{indent}\t\t<ToolTip/>')
|
||||
emit_mltext(f'{indent}\t\t', 'ToolTip', parsed.get('tooltip'))
|
||||
X(f'{indent}\t\t<MarkNegatives>false</MarkNegatives>')
|
||||
X(f'{indent}\t\t<Mask/>')
|
||||
multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false'
|
||||
|
||||
Reference in New Issue
Block a user