mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-06 11:40:20 +03:00
feat(skd-compile,skd-edit): calculatedFields — shorthand и объектные синонимы
- skd-compile v1.13: Parse-CalcShorthand теперь понимает "[Title]:type=expr#flags"
(синхронно со skd-edit). Emit-CalcFields принимает name как синоним
field/dataPath и строковую форму useRestriction ("#noField #noFilter ...").
- skd-edit v1.11: #restrict парсится по known-names pattern — исключает ложные
срабатывания на # внутри строковых литералов в выражении.
Закрывает три ловушки из upload/bug-skd-compile-calculated-field-datapath.md,
где модель писала name вместо field и строковый useRestriction по аналогии
с shorthand-флагами.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
00fafd4af5
commit
1cea9e794e
@@ -85,6 +85,23 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p
|
|||||||
|
|
||||||
В объектной форме: `"useRestriction": { "field": true, "condition": true, "group": true, "order": true }` или `"restrict": ["noField", "noFilter"]`.
|
В объектной форме: `"useRestriction": { "field": true, "condition": true, "group": true, "order": true }` или `"restrict": ["noField", "noFilter"]`.
|
||||||
|
|
||||||
|
### Вычисляемые поля (calculatedFields)
|
||||||
|
|
||||||
|
Shorthand: `"Имя [Заголовок]: тип = Выражение #noField #noFilter #noGroup #noOrder"` — все части кроме имени опциональны.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"calculatedFields": [
|
||||||
|
"Маржа = Цена - Закупка",
|
||||||
|
"Наценка [Наценка, %]: decimal(10,2) = Маржа / Закупка * 100",
|
||||||
|
"Служебное: string = \"\" #noField #noFilter #noGroup #noOrder"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Объектная форма — когда нужна `appearance`:
|
||||||
|
```json
|
||||||
|
{ "name": "Маржа", "title": "Маржа", "expression": "Цена - Закупка", "type": "decimal(15,2)", "useRestriction": "#noField #noFilter" }
|
||||||
|
```
|
||||||
|
|
||||||
### Итоги (shorthand)
|
### Итоги (shorthand)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# skd-compile v1.12 — Compile 1C DCS from JSON
|
# skd-compile v1.13 — Compile 1C DCS from JSON
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[string]$DefinitionFile,
|
[string]$DefinitionFile,
|
||||||
@@ -367,15 +367,51 @@ function Parse-ParamShorthand {
|
|||||||
function Parse-CalcShorthand {
|
function Parse-CalcShorthand {
|
||||||
param([string]$s)
|
param([string]$s)
|
||||||
|
|
||||||
# "DataPath = Expression"
|
# Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
|
||||||
$idx = $s.IndexOf('=')
|
# - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
|
||||||
if ($idx -gt 0) {
|
# an expression (e.g. index access) isn't interpreted as a title.
|
||||||
return @{
|
# - `#restrict` flags use a known-names pattern and are extracted globally —
|
||||||
dataPath = $s.Substring(0, $idx).Trim()
|
# the docs put them after `=`, and the closed flag set avoids matching
|
||||||
expression = $s.Substring($idx + 1).Trim()
|
# `#word` that happens to appear inside a string literal.
|
||||||
}
|
$restrictPattern = '#(noField|noFilter|noCondition|noGroup|noOrder)\b'
|
||||||
|
|
||||||
|
$restrict = @()
|
||||||
|
foreach ($m in [regex]::Matches($s, $restrictPattern)) {
|
||||||
|
$restrict += $m.Groups[1].Value
|
||||||
|
}
|
||||||
|
$s = [regex]::Replace($s, "\s*$restrictPattern", '')
|
||||||
|
|
||||||
|
$eqIdx = $s.IndexOf('=')
|
||||||
|
if ($eqIdx -gt 0) {
|
||||||
|
$lhs = $s.Substring(0, $eqIdx)
|
||||||
|
$rhs = $s.Substring($eqIdx + 1).Trim()
|
||||||
|
} else {
|
||||||
|
$lhs = $s
|
||||||
|
$rhs = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = ""
|
||||||
|
if ($lhs -match '\[([^\]]+)\]') {
|
||||||
|
$title = $Matches[1]
|
||||||
|
$lhs = $lhs -replace '\s*\[[^\]]+\]', ''
|
||||||
|
}
|
||||||
|
$lhs = $lhs.Trim()
|
||||||
|
|
||||||
|
$type = ""
|
||||||
|
$dataPath = $lhs
|
||||||
|
if ($lhs.Contains(':')) {
|
||||||
|
$parts = $lhs -split ':', 2
|
||||||
|
$dataPath = $parts[0].Trim()
|
||||||
|
$type = Resolve-TypeStr ($parts[1].Trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
return @{
|
||||||
|
dataPath = $dataPath
|
||||||
|
expression = $rhs
|
||||||
|
type = $type
|
||||||
|
title = $title
|
||||||
|
restrict = $restrict
|
||||||
}
|
}
|
||||||
return @{ dataPath = $s.Trim(); expression = "" }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- 8b. DataParameter shorthand parser ---
|
# --- 8b. DataParameter shorthand parser ---
|
||||||
@@ -771,64 +807,90 @@ function Emit-DataSetLinks {
|
|||||||
# === CalculatedFields ===
|
# === CalculatedFields ===
|
||||||
function Emit-CalcFields {
|
function Emit-CalcFields {
|
||||||
if (-not $def.calculatedFields) { return }
|
if (-not $def.calculatedFields) { return }
|
||||||
|
$restrictMap = @{
|
||||||
|
"noField" = "field"; "noFilter" = "condition"; "noCondition" = "condition"
|
||||||
|
"noGroup" = "group"; "noOrder" = "order"
|
||||||
|
}
|
||||||
foreach ($cf in $def.calculatedFields) {
|
foreach ($cf in $def.calculatedFields) {
|
||||||
|
# Collect dataPath/expression/title/type/restrict/appearance from either
|
||||||
|
# shorthand string or object form. Object form accepts dataPath/field/name
|
||||||
|
# as synonyms; useRestriction/restrict accepts object, array, or flag string.
|
||||||
|
$title = ""
|
||||||
|
$typeStr = ""
|
||||||
|
$restrictTokens = @()
|
||||||
|
$restrictObj = $null
|
||||||
|
$appearance = $null
|
||||||
|
|
||||||
if ($cf -is [string]) {
|
if ($cf -is [string]) {
|
||||||
$parsed = Parse-CalcShorthand $cf
|
$parsed = Parse-CalcShorthand $cf
|
||||||
|
$dataPath = "$($parsed.dataPath)"
|
||||||
|
$expression = "$($parsed.expression)"
|
||||||
|
$title = "$($parsed.title)"
|
||||||
|
$typeStr = "$($parsed.type)"
|
||||||
|
if ($parsed.restrict) { $restrictTokens = @($parsed.restrict) }
|
||||||
} else {
|
} else {
|
||||||
$dp = if ($cf.dataPath) { "$($cf.dataPath)" } else { "$($cf.field)" }
|
$dataPath = if ($cf.dataPath) { "$($cf.dataPath)" }
|
||||||
$parsed = @{
|
elseif ($cf.field) { "$($cf.field)" }
|
||||||
dataPath = $dp
|
else { "$($cf.name)" }
|
||||||
expression = "$($cf.expression)"
|
$expression = "$($cf.expression)"
|
||||||
}
|
if ($cf.title) { $title = "$($cf.title)" }
|
||||||
}
|
if ($cf.type) { $typeStr = Resolve-TypeStr "$($cf.type)" }
|
||||||
|
|
||||||
X "`t<calculatedField>"
|
|
||||||
X "`t`t<dataPath>$(Esc-Xml $parsed.dataPath)</dataPath>"
|
|
||||||
X "`t`t<expression>$(Esc-Xml $parsed.expression)</expression>"
|
|
||||||
|
|
||||||
if ($cf -isnot [string]) {
|
|
||||||
if ($cf.title) {
|
|
||||||
Emit-MLText -tag "title" -text "$($cf.title)" -indent "`t`t"
|
|
||||||
}
|
|
||||||
if ($cf.type) {
|
|
||||||
$cfType = Resolve-TypeStr "$($cf.type)"
|
|
||||||
X "`t`t<valueType>"
|
|
||||||
Emit-ValueType -typeStr $cfType -indent "`t`t`t"
|
|
||||||
X "`t`t</valueType>"
|
|
||||||
}
|
|
||||||
$restrictVal = if ($cf.restrict) { $cf.restrict } elseif ($cf.useRestriction) { $cf.useRestriction } else { $null }
|
$restrictVal = if ($cf.restrict) { $cf.restrict } elseif ($cf.useRestriction) { $cf.useRestriction } else { $null }
|
||||||
if ($restrictVal) {
|
if ($restrictVal) {
|
||||||
X "`t`t<useRestriction>"
|
|
||||||
if ($restrictVal -is [System.Management.Automation.PSCustomObject] -or $restrictVal -is [hashtable]) {
|
if ($restrictVal -is [System.Management.Automation.PSCustomObject] -or $restrictVal -is [hashtable]) {
|
||||||
# Object form: { "field": true, "condition": true, ... }
|
$restrictObj = $restrictVal
|
||||||
foreach ($prop in $restrictVal.PSObject.Properties) {
|
} elseif ($restrictVal -is [string]) {
|
||||||
if ($prop.Value -eq $true) {
|
# Flag-string form: "#noField #noFilter #noGroup #noOrder" (or without `#`)
|
||||||
X "`t`t`t<$($prop.Name)>true</$($prop.Name)>"
|
foreach ($tok in ($restrictVal -split '\s+')) {
|
||||||
}
|
$t = $tok.Trim().TrimStart('#')
|
||||||
|
if ($t) { $restrictTokens += $t }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
# Array form: ["noField", "noFilter", ...]
|
# Array form: ["noField", "noFilter", ...]
|
||||||
$restrictMap = @{
|
foreach ($r in $restrictVal) { $restrictTokens += "$r" }
|
||||||
"noField" = "field"; "noFilter" = "condition"; "noCondition" = "condition"
|
}
|
||||||
"noGroup" = "group"; "noOrder" = "order"
|
}
|
||||||
}
|
if ($cf.appearance) { $appearance = $cf.appearance }
|
||||||
foreach ($r in $restrictVal) {
|
}
|
||||||
$xmlName = $restrictMap["$r"]
|
|
||||||
if ($xmlName) { X "`t`t`t<$xmlName>true</$xmlName>" }
|
X "`t<calculatedField>"
|
||||||
|
X "`t`t<dataPath>$(Esc-Xml $dataPath)</dataPath>"
|
||||||
|
X "`t`t<expression>$(Esc-Xml $expression)</expression>"
|
||||||
|
|
||||||
|
if ($title) {
|
||||||
|
Emit-MLText -tag "title" -text $title -indent "`t`t"
|
||||||
|
}
|
||||||
|
if ($typeStr) {
|
||||||
|
X "`t`t<valueType>"
|
||||||
|
Emit-ValueType -typeStr $typeStr -indent "`t`t`t"
|
||||||
|
X "`t`t</valueType>"
|
||||||
|
}
|
||||||
|
if ($restrictObj -or $restrictTokens.Count -gt 0) {
|
||||||
|
X "`t`t<useRestriction>"
|
||||||
|
if ($restrictObj) {
|
||||||
|
foreach ($prop in $restrictObj.PSObject.Properties) {
|
||||||
|
if ($prop.Value -eq $true) {
|
||||||
|
X "`t`t`t<$($prop.Name)>true</$($prop.Name)>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
X "`t`t</useRestriction>"
|
} else {
|
||||||
}
|
foreach ($r in $restrictTokens) {
|
||||||
if ($cf.appearance) {
|
$xmlName = $restrictMap["$r"]
|
||||||
X "`t`t<appearance>"
|
if ($xmlName) { X "`t`t`t<$xmlName>true</$xmlName>" }
|
||||||
foreach ($prop in $cf.appearance.PSObject.Properties) {
|
|
||||||
X "`t`t`t<dcscor:item xsi:type=`"dcsset:SettingsParameterValue`">"
|
|
||||||
X "`t`t`t`t<dcscor:parameter>$(Esc-Xml $prop.Name)</dcscor:parameter>"
|
|
||||||
X "`t`t`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml "$($prop.Value)")</dcscor:value>"
|
|
||||||
X "`t`t`t</dcscor:item>"
|
|
||||||
}
|
}
|
||||||
X "`t`t</appearance>"
|
|
||||||
}
|
}
|
||||||
|
X "`t`t</useRestriction>"
|
||||||
|
}
|
||||||
|
if ($appearance) {
|
||||||
|
X "`t`t<appearance>"
|
||||||
|
foreach ($prop in $appearance.PSObject.Properties) {
|
||||||
|
X "`t`t`t<dcscor:item xsi:type=`"dcsset:SettingsParameterValue`">"
|
||||||
|
X "`t`t`t`t<dcscor:parameter>$(Esc-Xml $prop.Name)</dcscor:parameter>"
|
||||||
|
X "`t`t`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml "$($prop.Value)")</dcscor:value>"
|
||||||
|
X "`t`t`t</dcscor:item>"
|
||||||
|
}
|
||||||
|
X "`t`t</appearance>"
|
||||||
}
|
}
|
||||||
|
|
||||||
X "`t</calculatedField>"
|
X "`t</calculatedField>"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# skd-compile v1.12 — Compile 1C DCS from JSON
|
# skd-compile v1.13 — Compile 1C DCS from JSON
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
@@ -277,13 +277,46 @@ def parse_param_shorthand(s):
|
|||||||
# --- Calculated field shorthand parser ---
|
# --- Calculated field shorthand parser ---
|
||||||
|
|
||||||
def parse_calc_shorthand(s):
|
def parse_calc_shorthand(s):
|
||||||
idx = s.find('=')
|
# Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
|
||||||
if idx > 0:
|
# - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
|
||||||
return {
|
# an expression (e.g. index access) isn't interpreted as a title.
|
||||||
'dataPath': s[:idx].strip(),
|
# - `#restrict` flags use a known-names pattern and are extracted globally —
|
||||||
'expression': s[idx + 1:].strip(),
|
# the docs put them after `=`, and the closed flag set avoids matching
|
||||||
}
|
# `#word` that happens to appear inside a string literal.
|
||||||
return {'dataPath': s.strip(), 'expression': ''}
|
restrict_pattern = r'#(noField|noFilter|noCondition|noGroup|noOrder)\b'
|
||||||
|
|
||||||
|
restrict = re.findall(restrict_pattern, s)
|
||||||
|
s = re.sub(r'\s*' + restrict_pattern, '', s)
|
||||||
|
|
||||||
|
eq_idx = s.find('=')
|
||||||
|
if eq_idx > 0:
|
||||||
|
lhs = s[:eq_idx]
|
||||||
|
rhs = s[eq_idx + 1:].strip()
|
||||||
|
else:
|
||||||
|
lhs = s
|
||||||
|
rhs = ''
|
||||||
|
|
||||||
|
title = ''
|
||||||
|
m = re.search(r'\[([^\]]+)\]', lhs)
|
||||||
|
if m:
|
||||||
|
title = m.group(1)
|
||||||
|
lhs = re.sub(r'\s*\[[^\]]+\]', '', lhs)
|
||||||
|
lhs = lhs.strip()
|
||||||
|
|
||||||
|
type_str = ''
|
||||||
|
data_path = lhs
|
||||||
|
if ':' in lhs:
|
||||||
|
colon_idx = lhs.index(':')
|
||||||
|
data_path = lhs[:colon_idx].strip()
|
||||||
|
type_str = resolve_type_str(lhs[colon_idx + 1:].strip())
|
||||||
|
|
||||||
|
return {
|
||||||
|
'dataPath': data_path,
|
||||||
|
'expression': rhs,
|
||||||
|
'type': type_str,
|
||||||
|
'title': title,
|
||||||
|
'restrict': restrict,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# --- DataParameter shorthand parser ---
|
# --- DataParameter shorthand parser ---
|
||||||
@@ -623,56 +656,81 @@ def emit_data_set_links(lines, defn):
|
|||||||
def emit_calc_fields(lines, defn):
|
def emit_calc_fields(lines, defn):
|
||||||
if not defn.get('calculatedFields'):
|
if not defn.get('calculatedFields'):
|
||||||
return
|
return
|
||||||
|
restrict_map = {
|
||||||
|
'noField': 'field', 'noFilter': 'condition', 'noCondition': 'condition',
|
||||||
|
'noGroup': 'group', 'noOrder': 'order',
|
||||||
|
}
|
||||||
for cf in defn['calculatedFields']:
|
for cf in defn['calculatedFields']:
|
||||||
|
# Collect dataPath/expression/title/type/restrict/appearance from either
|
||||||
|
# shorthand string or object form. Object form accepts dataPath/field/name
|
||||||
|
# as synonyms; useRestriction/restrict accepts object, array, or flag string.
|
||||||
|
title = ''
|
||||||
|
type_str = ''
|
||||||
|
restrict_tokens = []
|
||||||
|
restrict_obj = None
|
||||||
|
appearance = None
|
||||||
|
|
||||||
if isinstance(cf, str):
|
if isinstance(cf, str):
|
||||||
parsed = parse_calc_shorthand(cf)
|
parsed = parse_calc_shorthand(cf)
|
||||||
is_obj = False
|
data_path = parsed['dataPath']
|
||||||
|
expression = parsed['expression']
|
||||||
|
title = parsed.get('title', '') or ''
|
||||||
|
type_str = parsed.get('type', '') or ''
|
||||||
|
restrict_tokens = list(parsed.get('restrict') or [])
|
||||||
else:
|
else:
|
||||||
parsed = {
|
data_path = str(cf.get('dataPath') or cf.get('field') or cf.get('name') or '')
|
||||||
'dataPath': str(cf.get('dataPath') or cf.get('field', '')),
|
expression = str(cf.get('expression', ''))
|
||||||
'expression': str(cf.get('expression', '')),
|
|
||||||
}
|
|
||||||
is_obj = True
|
|
||||||
|
|
||||||
lines.append('\t<calculatedField>')
|
|
||||||
lines.append(f'\t\t<dataPath>{esc_xml(parsed["dataPath"])}</dataPath>')
|
|
||||||
lines.append(f'\t\t<expression>{esc_xml(parsed["expression"])}</expression>')
|
|
||||||
|
|
||||||
if is_obj:
|
|
||||||
if cf.get('title'):
|
if cf.get('title'):
|
||||||
emit_mltext(lines, '\t\t', 'title', str(cf['title']))
|
title = str(cf['title'])
|
||||||
if cf.get('type'):
|
if cf.get('type'):
|
||||||
cf_type = resolve_type_str(str(cf['type']))
|
type_str = resolve_type_str(str(cf['type']))
|
||||||
lines.append('\t\t<valueType>')
|
|
||||||
emit_value_type(lines, cf_type, '\t\t\t')
|
restrict_val = cf.get('restrict') if cf.get('restrict') is not None else cf.get('useRestriction')
|
||||||
lines.append('\t\t</valueType>')
|
|
||||||
restrict_val = cf.get('restrict') or cf.get('useRestriction')
|
|
||||||
if restrict_val:
|
if restrict_val:
|
||||||
lines.append('\t\t<useRestriction>')
|
|
||||||
if isinstance(restrict_val, dict):
|
if isinstance(restrict_val, dict):
|
||||||
# Object form: { "field": true, "condition": true, ... }
|
restrict_obj = restrict_val
|
||||||
for xml_name, flag in restrict_val.items():
|
elif isinstance(restrict_val, str):
|
||||||
if flag:
|
# Flag-string form: "#noField #noFilter #noGroup #noOrder" (or without `#`)
|
||||||
lines.append(f'\t\t\t<{esc_xml(str(xml_name))}>true</{esc_xml(str(xml_name))}>')
|
for tok in restrict_val.split():
|
||||||
|
t = tok.strip().lstrip('#')
|
||||||
|
if t:
|
||||||
|
restrict_tokens.append(t)
|
||||||
else:
|
else:
|
||||||
# Array form: ["noField", "noFilter", ...]
|
# Array form: ["noField", "noFilter", ...]
|
||||||
restrict_map = {
|
|
||||||
'noField': 'field', 'noFilter': 'condition', 'noCondition': 'condition',
|
|
||||||
'noGroup': 'group', 'noOrder': 'order',
|
|
||||||
}
|
|
||||||
for r in restrict_val:
|
for r in restrict_val:
|
||||||
xml_name = restrict_map.get(str(r))
|
restrict_tokens.append(str(r))
|
||||||
if xml_name:
|
appearance = cf.get('appearance')
|
||||||
lines.append(f'\t\t\t<{xml_name}>true</{xml_name}>')
|
|
||||||
lines.append('\t\t</useRestriction>')
|
lines.append('\t<calculatedField>')
|
||||||
if cf.get('appearance'):
|
lines.append(f'\t\t<dataPath>{esc_xml(data_path)}</dataPath>')
|
||||||
lines.append('\t\t<appearance>')
|
lines.append(f'\t\t<expression>{esc_xml(expression)}</expression>')
|
||||||
for k, v in cf['appearance'].items():
|
|
||||||
lines.append('\t\t\t<dcscor:item xsi:type="dcsset:SettingsParameterValue">')
|
if title:
|
||||||
lines.append(f'\t\t\t\t<dcscor:parameter>{esc_xml(k)}</dcscor:parameter>')
|
emit_mltext(lines, '\t\t', 'title', title)
|
||||||
lines.append(f'\t\t\t\t<dcscor:value xsi:type="xs:string">{esc_xml(str(v))}</dcscor:value>')
|
if type_str:
|
||||||
lines.append('\t\t\t</dcscor:item>')
|
lines.append('\t\t<valueType>')
|
||||||
lines.append('\t\t</appearance>')
|
emit_value_type(lines, type_str, '\t\t\t')
|
||||||
|
lines.append('\t\t</valueType>')
|
||||||
|
if restrict_obj or restrict_tokens:
|
||||||
|
lines.append('\t\t<useRestriction>')
|
||||||
|
if restrict_obj:
|
||||||
|
for xml_name, flag in restrict_obj.items():
|
||||||
|
if flag:
|
||||||
|
lines.append(f'\t\t\t<{esc_xml(str(xml_name))}>true</{esc_xml(str(xml_name))}>')
|
||||||
|
else:
|
||||||
|
for r in restrict_tokens:
|
||||||
|
xml_name = restrict_map.get(str(r))
|
||||||
|
if xml_name:
|
||||||
|
lines.append(f'\t\t\t<{xml_name}>true</{xml_name}>')
|
||||||
|
lines.append('\t\t</useRestriction>')
|
||||||
|
if appearance:
|
||||||
|
lines.append('\t\t<appearance>')
|
||||||
|
for k, v in appearance.items():
|
||||||
|
lines.append('\t\t\t<dcscor:item xsi:type="dcsset:SettingsParameterValue">')
|
||||||
|
lines.append(f'\t\t\t\t<dcscor:parameter>{esc_xml(k)}</dcscor:parameter>')
|
||||||
|
lines.append(f'\t\t\t\t<dcscor:value xsi:type="xs:string">{esc_xml(str(v))}</dcscor:value>')
|
||||||
|
lines.append('\t\t\t</dcscor:item>')
|
||||||
|
lines.append('\t\t</appearance>')
|
||||||
|
|
||||||
lines.append('\t</calculatedField>')
|
lines.append('\t</calculatedField>')
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# skd-edit v1.10 — Atomic 1C DCS editor
|
# skd-edit v1.11 — Atomic 1C DCS editor
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -251,36 +251,46 @@ function Parse-TotalShorthand {
|
|||||||
function Parse-CalcShorthand {
|
function Parse-CalcShorthand {
|
||||||
param([string]$s)
|
param([string]$s)
|
||||||
|
|
||||||
$title = ""
|
# Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
|
||||||
# Extract [Title] first
|
# - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
|
||||||
if ($s -match '\[([^\]]+)\]') {
|
# an expression (e.g. index access) isn't interpreted as a title.
|
||||||
$title = $Matches[1]
|
# - `#restrict` flags use a known-names pattern and are extracted globally —
|
||||||
$s = $s -replace '\s*\[[^\]]+\]', ''
|
# the docs put them after `=`, and the closed flag set avoids matching
|
||||||
}
|
# `#word` that happens to appear inside a string literal.
|
||||||
|
$restrictPattern = '#(noField|noFilter|noCondition|noGroup|noOrder)\b'
|
||||||
|
|
||||||
# Extract #restrictions
|
|
||||||
$restrict = @()
|
$restrict = @()
|
||||||
$restrictMatches = [regex]::Matches($s, '#(\w+)')
|
foreach ($m in [regex]::Matches($s, $restrictPattern)) {
|
||||||
foreach ($m in $restrictMatches) {
|
|
||||||
$restrict += $m.Groups[1].Value
|
$restrict += $m.Groups[1].Value
|
||||||
}
|
}
|
||||||
$s = [regex]::Replace($s, '\s*#\w+', '')
|
$s = [regex]::Replace($s, "\s*$restrictPattern", '')
|
||||||
|
|
||||||
# Support "Name: Type = Expression" and "Name = Expression"
|
|
||||||
$eqIdx = $s.IndexOf('=')
|
$eqIdx = $s.IndexOf('=')
|
||||||
if ($eqIdx -gt 0) {
|
if ($eqIdx -gt 0) {
|
||||||
$left = $s.Substring(0, $eqIdx).Trim()
|
$lhs = $s.Substring(0, $eqIdx)
|
||||||
$expression = $s.Substring($eqIdx + 1).Trim()
|
$rhs = $s.Substring($eqIdx + 1).Trim()
|
||||||
|
} else {
|
||||||
if ($left.Contains(':')) {
|
$lhs = $s
|
||||||
$colonIdx = $left.IndexOf(':')
|
$rhs = $null
|
||||||
$dataPath = $left.Substring(0, $colonIdx).Trim()
|
|
||||||
$type = Resolve-TypeStr ($left.Substring($colonIdx + 1).Trim())
|
|
||||||
return @{ dataPath = $dataPath; expression = $expression; type = $type; title = $title; restrict = $restrict }
|
|
||||||
}
|
|
||||||
return @{ dataPath = $left; expression = $expression; type = ""; title = $title; restrict = $restrict }
|
|
||||||
}
|
}
|
||||||
return @{ dataPath = $s.Trim(); expression = ""; type = ""; title = $title; restrict = $restrict }
|
|
||||||
|
$title = ""
|
||||||
|
if ($lhs -match '\[([^\]]+)\]') {
|
||||||
|
$title = $Matches[1]
|
||||||
|
$lhs = $lhs -replace '\s*\[[^\]]+\]', ''
|
||||||
|
}
|
||||||
|
$lhs = $lhs.Trim()
|
||||||
|
|
||||||
|
if ($null -ne $rhs) {
|
||||||
|
if ($lhs.Contains(':')) {
|
||||||
|
$colonIdx = $lhs.IndexOf(':')
|
||||||
|
$dataPath = $lhs.Substring(0, $colonIdx).Trim()
|
||||||
|
$type = Resolve-TypeStr ($lhs.Substring($colonIdx + 1).Trim())
|
||||||
|
return @{ dataPath = $dataPath; expression = $rhs; type = $type; title = $title; restrict = $restrict }
|
||||||
|
}
|
||||||
|
return @{ dataPath = $lhs; expression = $rhs; type = ""; title = $title; restrict = $restrict }
|
||||||
|
}
|
||||||
|
return @{ dataPath = $lhs; expression = ""; type = ""; title = $title; restrict = $restrict }
|
||||||
}
|
}
|
||||||
|
|
||||||
function Parse-ParamShorthand {
|
function Parse-ParamShorthand {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# skd-edit v1.10 — Atomic 1C DCS editor (Python port)
|
# skd-edit v1.11 — Atomic 1C DCS editor (Python port)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
@@ -260,26 +260,42 @@ def parse_total_shorthand(s):
|
|||||||
|
|
||||||
|
|
||||||
def parse_calc_shorthand(s):
|
def parse_calc_shorthand(s):
|
||||||
title = ""
|
# Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
|
||||||
m = re.search(r'\[([^\]]+)\]', s)
|
# - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
|
||||||
if m:
|
# an expression (e.g. index access) isn't interpreted as a title.
|
||||||
title = m.group(1)
|
# - `#restrict` flags use a known-names pattern and are extracted globally —
|
||||||
s = re.sub(r'\s*\[[^\]]+\]', '', s)
|
# the docs put them after `=`, and the closed flag set avoids matching
|
||||||
|
# `#word` that happens to appear inside a string literal.
|
||||||
|
restrict_pattern = r'#(noField|noFilter|noCondition|noGroup|noOrder)\b'
|
||||||
|
|
||||||
restrict_matches = re.findall(r'#(\w+)', s)
|
restrict_matches = re.findall(restrict_pattern, s)
|
||||||
s = re.sub(r'\s*#\w+', '', s)
|
s = re.sub(r'\s*' + restrict_pattern, '', s)
|
||||||
|
|
||||||
eq_idx = s.find("=")
|
eq_idx = s.find("=")
|
||||||
if eq_idx > 0:
|
if eq_idx > 0:
|
||||||
left = s[:eq_idx].strip()
|
lhs = s[:eq_idx]
|
||||||
expression = s[eq_idx + 1:].strip()
|
rhs = s[eq_idx + 1:].strip()
|
||||||
if ":" in left:
|
has_rhs = True
|
||||||
colon_idx = left.index(":")
|
else:
|
||||||
data_path = left[:colon_idx].strip()
|
lhs = s
|
||||||
type_str = resolve_type_str(left[colon_idx + 1:].strip())
|
rhs = ""
|
||||||
return {"dataPath": data_path, "expression": expression, "type": type_str, "title": title, "restrict": restrict_matches}
|
has_rhs = False
|
||||||
return {"dataPath": left, "expression": expression, "type": "", "title": title, "restrict": restrict_matches}
|
|
||||||
return {"dataPath": s.strip(), "expression": "", "type": "", "title": title, "restrict": restrict_matches}
|
title = ""
|
||||||
|
m = re.search(r'\[([^\]]+)\]', lhs)
|
||||||
|
if m:
|
||||||
|
title = m.group(1)
|
||||||
|
lhs = re.sub(r'\s*\[[^\]]+\]', '', lhs)
|
||||||
|
lhs = lhs.strip()
|
||||||
|
|
||||||
|
if has_rhs:
|
||||||
|
if ":" in lhs:
|
||||||
|
colon_idx = lhs.index(":")
|
||||||
|
data_path = lhs[:colon_idx].strip()
|
||||||
|
type_str = resolve_type_str(lhs[colon_idx + 1:].strip())
|
||||||
|
return {"dataPath": data_path, "expression": rhs, "type": type_str, "title": title, "restrict": restrict_matches}
|
||||||
|
return {"dataPath": lhs, "expression": rhs, "type": "", "title": title, "restrict": restrict_matches}
|
||||||
|
return {"dataPath": lhs, "expression": "", "type": "", "title": title, "restrict": restrict_matches}
|
||||||
|
|
||||||
|
|
||||||
def parse_param_shorthand(s):
|
def parse_param_shorthand(s):
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "calculatedFields объектная форма с name и строковым useRestriction",
|
||||||
|
"params": { "outputPath": "Template.xml" },
|
||||||
|
"input": {
|
||||||
|
"dataSets": [{
|
||||||
|
"name": "Основной",
|
||||||
|
"query": "ВЫБРАТЬ Т.Номенклатура, Т.Сумма ИЗ Регистр КАК Т",
|
||||||
|
"fields": ["Номенклатура", "Сумма: decimal(15,2)"]
|
||||||
|
}],
|
||||||
|
"calculatedFields": [
|
||||||
|
{
|
||||||
|
"name": "ИмяРесурса",
|
||||||
|
"title": "Имя ресурса",
|
||||||
|
"expression": "\"\"",
|
||||||
|
"useRestriction": "#noField #noFilter #noGroup #noOrder"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"validatePath": "Template.xml",
|
||||||
|
"expect": {
|
||||||
|
"files": ["Template.xml"]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "calculatedFields shorthand с [Title], :type, =expr, #restrict",
|
||||||
|
"params": { "outputPath": "Template.xml" },
|
||||||
|
"input": {
|
||||||
|
"dataSets": [{
|
||||||
|
"name": "Основной",
|
||||||
|
"query": "ВЫБРАТЬ Т.Цена, Т.Закупка ИЗ Регистр КАК Т",
|
||||||
|
"fields": ["Цена: decimal(15,2)", "Закупка: decimal(15,2)"]
|
||||||
|
}],
|
||||||
|
"calculatedFields": [
|
||||||
|
"ИмяРесурса [Имя ресурса]: string = \"\" #noField #noFilter #noGroup #noOrder",
|
||||||
|
"Маржа [Маржа]: decimal(15,2) = Цена - Закупка"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"validatePath": "Template.xml",
|
||||||
|
"expect": {
|
||||||
|
"files": ["Template.xml"]
|
||||||
|
}
|
||||||
|
}
|
||||||
+72
@@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
|
||||||
|
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
|
||||||
|
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
|
||||||
|
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
|
||||||
|
xmlns:v8="http://v8.1c.ru/8.1/data/core"
|
||||||
|
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
|
||||||
|
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<dataSource>
|
||||||
|
<name>ИсточникДанных1</name>
|
||||||
|
<dataSourceType>Local</dataSourceType>
|
||||||
|
</dataSource>
|
||||||
|
<dataSet xsi:type="DataSetQuery">
|
||||||
|
<name>Основной</name>
|
||||||
|
<field xsi:type="DataSetFieldField">
|
||||||
|
<dataPath>Номенклатура</dataPath>
|
||||||
|
<field>Номенклатура</field>
|
||||||
|
</field>
|
||||||
|
<field xsi:type="DataSetFieldField">
|
||||||
|
<dataPath>Сумма</dataPath>
|
||||||
|
<field>Сумма</field>
|
||||||
|
<valueType>
|
||||||
|
<v8:Type>xs:decimal</v8:Type>
|
||||||
|
<v8:NumberQualifiers>
|
||||||
|
<v8:Digits>15</v8:Digits>
|
||||||
|
<v8:FractionDigits>2</v8:FractionDigits>
|
||||||
|
<v8:AllowedSign>Any</v8:AllowedSign>
|
||||||
|
</v8:NumberQualifiers>
|
||||||
|
</valueType>
|
||||||
|
</field>
|
||||||
|
<dataSource>ИсточникДанных1</dataSource>
|
||||||
|
<query>ВЫБРАТЬ Т.Номенклатура, Т.Сумма ИЗ Регистр КАК Т</query>
|
||||||
|
</dataSet>
|
||||||
|
<calculatedField>
|
||||||
|
<dataPath>ИмяРесурса</dataPath>
|
||||||
|
<expression>""</expression>
|
||||||
|
<title xsi:type="v8:LocalStringType">
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>Имя ресурса</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</title>
|
||||||
|
<useRestriction>
|
||||||
|
<field>true</field>
|
||||||
|
<condition>true</condition>
|
||||||
|
<group>true</group>
|
||||||
|
<order>true</order>
|
||||||
|
</useRestriction>
|
||||||
|
</calculatedField>
|
||||||
|
<settingsVariant>
|
||||||
|
<dcsset:name>Основной</dcsset:name>
|
||||||
|
<dcsset:presentation xsi:type="v8:LocalStringType">
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>Основной</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</dcsset:presentation>
|
||||||
|
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
|
||||||
|
<dcsset:selection>
|
||||||
|
</dcsset:selection>
|
||||||
|
<dcsset:item xsi:type="dcsset:StructureItemGroup">
|
||||||
|
<dcsset:order>
|
||||||
|
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
|
||||||
|
</dcsset:order>
|
||||||
|
<dcsset:selection>
|
||||||
|
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
|
||||||
|
</dcsset:selection>
|
||||||
|
</dcsset:item>
|
||||||
|
</dcsset:settings>
|
||||||
|
</settingsVariant>
|
||||||
|
</DataCompositionSchema>
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
|
||||||
|
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
|
||||||
|
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
|
||||||
|
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
|
||||||
|
xmlns:v8="http://v8.1c.ru/8.1/data/core"
|
||||||
|
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
|
||||||
|
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<dataSource>
|
||||||
|
<name>ИсточникДанных1</name>
|
||||||
|
<dataSourceType>Local</dataSourceType>
|
||||||
|
</dataSource>
|
||||||
|
<dataSet xsi:type="DataSetQuery">
|
||||||
|
<name>Основной</name>
|
||||||
|
<field xsi:type="DataSetFieldField">
|
||||||
|
<dataPath>Цена</dataPath>
|
||||||
|
<field>Цена</field>
|
||||||
|
<valueType>
|
||||||
|
<v8:Type>xs:decimal</v8:Type>
|
||||||
|
<v8:NumberQualifiers>
|
||||||
|
<v8:Digits>15</v8:Digits>
|
||||||
|
<v8:FractionDigits>2</v8:FractionDigits>
|
||||||
|
<v8:AllowedSign>Any</v8:AllowedSign>
|
||||||
|
</v8:NumberQualifiers>
|
||||||
|
</valueType>
|
||||||
|
</field>
|
||||||
|
<field xsi:type="DataSetFieldField">
|
||||||
|
<dataPath>Закупка</dataPath>
|
||||||
|
<field>Закупка</field>
|
||||||
|
<valueType>
|
||||||
|
<v8:Type>xs:decimal</v8:Type>
|
||||||
|
<v8:NumberQualifiers>
|
||||||
|
<v8:Digits>15</v8:Digits>
|
||||||
|
<v8:FractionDigits>2</v8:FractionDigits>
|
||||||
|
<v8:AllowedSign>Any</v8:AllowedSign>
|
||||||
|
</v8:NumberQualifiers>
|
||||||
|
</valueType>
|
||||||
|
</field>
|
||||||
|
<dataSource>ИсточникДанных1</dataSource>
|
||||||
|
<query>ВЫБРАТЬ Т.Цена, Т.Закупка ИЗ Регистр КАК Т</query>
|
||||||
|
</dataSet>
|
||||||
|
<calculatedField>
|
||||||
|
<dataPath>ИмяРесурса</dataPath>
|
||||||
|
<expression>""</expression>
|
||||||
|
<title xsi:type="v8:LocalStringType">
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>Имя ресурса</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</title>
|
||||||
|
<valueType>
|
||||||
|
<v8:Type>xs:string</v8:Type>
|
||||||
|
<v8:StringQualifiers>
|
||||||
|
<v8:Length>0</v8:Length>
|
||||||
|
<v8:AllowedLength>Variable</v8:AllowedLength>
|
||||||
|
</v8:StringQualifiers>
|
||||||
|
</valueType>
|
||||||
|
<useRestriction>
|
||||||
|
<field>true</field>
|
||||||
|
<condition>true</condition>
|
||||||
|
<group>true</group>
|
||||||
|
<order>true</order>
|
||||||
|
</useRestriction>
|
||||||
|
</calculatedField>
|
||||||
|
<calculatedField>
|
||||||
|
<dataPath>Маржа</dataPath>
|
||||||
|
<expression>Цена - Закупка</expression>
|
||||||
|
<title xsi:type="v8:LocalStringType">
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>Маржа</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</title>
|
||||||
|
<valueType>
|
||||||
|
<v8:Type>xs:decimal</v8:Type>
|
||||||
|
<v8:NumberQualifiers>
|
||||||
|
<v8:Digits>15</v8:Digits>
|
||||||
|
<v8:FractionDigits>2</v8:FractionDigits>
|
||||||
|
<v8:AllowedSign>Any</v8:AllowedSign>
|
||||||
|
</v8:NumberQualifiers>
|
||||||
|
</valueType>
|
||||||
|
</calculatedField>
|
||||||
|
<settingsVariant>
|
||||||
|
<dcsset:name>Основной</dcsset:name>
|
||||||
|
<dcsset:presentation xsi:type="v8:LocalStringType">
|
||||||
|
<v8:item>
|
||||||
|
<v8:lang>ru</v8:lang>
|
||||||
|
<v8:content>Основной</v8:content>
|
||||||
|
</v8:item>
|
||||||
|
</dcsset:presentation>
|
||||||
|
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
|
||||||
|
<dcsset:selection>
|
||||||
|
</dcsset:selection>
|
||||||
|
<dcsset:item xsi:type="dcsset:StructureItemGroup">
|
||||||
|
<dcsset:order>
|
||||||
|
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
|
||||||
|
</dcsset:order>
|
||||||
|
<dcsset:selection>
|
||||||
|
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
|
||||||
|
</dcsset:selection>
|
||||||
|
</dcsset:item>
|
||||||
|
</dcsset:settings>
|
||||||
|
</settingsVariant>
|
||||||
|
</DataCompositionSchema>
|
||||||
Reference in New Issue
Block a user