mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-13 01:14:56 +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:
@@ -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"]`.
|
||||
|
||||
### Вычисляемые поля (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)
|
||||
|
||||
```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
|
||||
param(
|
||||
[string]$DefinitionFile,
|
||||
@@ -367,15 +367,51 @@ function Parse-ParamShorthand {
|
||||
function Parse-CalcShorthand {
|
||||
param([string]$s)
|
||||
|
||||
# "DataPath = Expression"
|
||||
$idx = $s.IndexOf('=')
|
||||
if ($idx -gt 0) {
|
||||
return @{
|
||||
dataPath = $s.Substring(0, $idx).Trim()
|
||||
expression = $s.Substring($idx + 1).Trim()
|
||||
}
|
||||
# Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
|
||||
# - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
|
||||
# an expression (e.g. index access) isn't interpreted as a title.
|
||||
# - `#restrict` flags use a known-names pattern and are extracted globally —
|
||||
# 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'
|
||||
|
||||
$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 ---
|
||||
@@ -771,64 +807,90 @@ function Emit-DataSetLinks {
|
||||
# === CalculatedFields ===
|
||||
function Emit-CalcFields {
|
||||
if (-not $def.calculatedFields) { return }
|
||||
$restrictMap = @{
|
||||
"noField" = "field"; "noFilter" = "condition"; "noCondition" = "condition"
|
||||
"noGroup" = "group"; "noOrder" = "order"
|
||||
}
|
||||
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]) {
|
||||
$parsed = Parse-CalcShorthand $cf
|
||||
$dataPath = "$($parsed.dataPath)"
|
||||
$expression = "$($parsed.expression)"
|
||||
$title = "$($parsed.title)"
|
||||
$typeStr = "$($parsed.type)"
|
||||
if ($parsed.restrict) { $restrictTokens = @($parsed.restrict) }
|
||||
} else {
|
||||
$dp = if ($cf.dataPath) { "$($cf.dataPath)" } else { "$($cf.field)" }
|
||||
$parsed = @{
|
||||
dataPath = $dp
|
||||
expression = "$($cf.expression)"
|
||||
}
|
||||
}
|
||||
$dataPath = if ($cf.dataPath) { "$($cf.dataPath)" }
|
||||
elseif ($cf.field) { "$($cf.field)" }
|
||||
else { "$($cf.name)" }
|
||||
$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 }
|
||||
if ($restrictVal) {
|
||||
X "`t`t<useRestriction>"
|
||||
if ($restrictVal -is [System.Management.Automation.PSCustomObject] -or $restrictVal -is [hashtable]) {
|
||||
# Object form: { "field": true, "condition": true, ... }
|
||||
foreach ($prop in $restrictVal.PSObject.Properties) {
|
||||
if ($prop.Value -eq $true) {
|
||||
X "`t`t`t<$($prop.Name)>true</$($prop.Name)>"
|
||||
}
|
||||
$restrictObj = $restrictVal
|
||||
} elseif ($restrictVal -is [string]) {
|
||||
# Flag-string form: "#noField #noFilter #noGroup #noOrder" (or without `#`)
|
||||
foreach ($tok in ($restrictVal -split '\s+')) {
|
||||
$t = $tok.Trim().TrimStart('#')
|
||||
if ($t) { $restrictTokens += $t }
|
||||
}
|
||||
} else {
|
||||
# Array form: ["noField", "noFilter", ...]
|
||||
$restrictMap = @{
|
||||
"noField" = "field"; "noFilter" = "condition"; "noCondition" = "condition"
|
||||
"noGroup" = "group"; "noOrder" = "order"
|
||||
}
|
||||
foreach ($r in $restrictVal) {
|
||||
$xmlName = $restrictMap["$r"]
|
||||
if ($xmlName) { X "`t`t`t<$xmlName>true</$xmlName>" }
|
||||
foreach ($r in $restrictVal) { $restrictTokens += "$r" }
|
||||
}
|
||||
}
|
||||
if ($cf.appearance) { $appearance = $cf.appearance }
|
||||
}
|
||||
|
||||
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>"
|
||||
}
|
||||
if ($cf.appearance) {
|
||||
X "`t`t<appearance>"
|
||||
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>"
|
||||
} else {
|
||||
foreach ($r in $restrictTokens) {
|
||||
$xmlName = $restrictMap["$r"]
|
||||
if ($xmlName) { X "`t`t`t<$xmlName>true</$xmlName>" }
|
||||
}
|
||||
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>"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/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
|
||||
import argparse
|
||||
import json
|
||||
@@ -277,13 +277,46 @@ def parse_param_shorthand(s):
|
||||
# --- Calculated field shorthand parser ---
|
||||
|
||||
def parse_calc_shorthand(s):
|
||||
idx = s.find('=')
|
||||
if idx > 0:
|
||||
return {
|
||||
'dataPath': s[:idx].strip(),
|
||||
'expression': s[idx + 1:].strip(),
|
||||
}
|
||||
return {'dataPath': s.strip(), 'expression': ''}
|
||||
# Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
|
||||
# - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
|
||||
# an expression (e.g. index access) isn't interpreted as a title.
|
||||
# - `#restrict` flags use a known-names pattern and are extracted globally —
|
||||
# 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 = 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 ---
|
||||
@@ -623,56 +656,81 @@ def emit_data_set_links(lines, defn):
|
||||
def emit_calc_fields(lines, defn):
|
||||
if not defn.get('calculatedFields'):
|
||||
return
|
||||
restrict_map = {
|
||||
'noField': 'field', 'noFilter': 'condition', 'noCondition': 'condition',
|
||||
'noGroup': 'group', 'noOrder': 'order',
|
||||
}
|
||||
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):
|
||||
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:
|
||||
parsed = {
|
||||
'dataPath': str(cf.get('dataPath') or cf.get('field', '')),
|
||||
'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:
|
||||
data_path = str(cf.get('dataPath') or cf.get('field') or cf.get('name') or '')
|
||||
expression = str(cf.get('expression', ''))
|
||||
if cf.get('title'):
|
||||
emit_mltext(lines, '\t\t', 'title', str(cf['title']))
|
||||
title = str(cf['title'])
|
||||
if cf.get('type'):
|
||||
cf_type = resolve_type_str(str(cf['type']))
|
||||
lines.append('\t\t<valueType>')
|
||||
emit_value_type(lines, cf_type, '\t\t\t')
|
||||
lines.append('\t\t</valueType>')
|
||||
restrict_val = cf.get('restrict') or cf.get('useRestriction')
|
||||
type_str = resolve_type_str(str(cf['type']))
|
||||
|
||||
restrict_val = cf.get('restrict') if cf.get('restrict') is not None else cf.get('useRestriction')
|
||||
if restrict_val:
|
||||
lines.append('\t\t<useRestriction>')
|
||||
if isinstance(restrict_val, dict):
|
||||
# Object form: { "field": true, "condition": true, ... }
|
||||
for xml_name, flag in restrict_val.items():
|
||||
if flag:
|
||||
lines.append(f'\t\t\t<{esc_xml(str(xml_name))}>true</{esc_xml(str(xml_name))}>')
|
||||
restrict_obj = restrict_val
|
||||
elif isinstance(restrict_val, str):
|
||||
# Flag-string form: "#noField #noFilter #noGroup #noOrder" (or without `#`)
|
||||
for tok in restrict_val.split():
|
||||
t = tok.strip().lstrip('#')
|
||||
if t:
|
||||
restrict_tokens.append(t)
|
||||
else:
|
||||
# Array form: ["noField", "noFilter", ...]
|
||||
restrict_map = {
|
||||
'noField': 'field', 'noFilter': 'condition', 'noCondition': 'condition',
|
||||
'noGroup': 'group', 'noOrder': 'order',
|
||||
}
|
||||
for r in restrict_val:
|
||||
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 cf.get('appearance'):
|
||||
lines.append('\t\t<appearance>')
|
||||
for k, v in cf['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>')
|
||||
restrict_tokens.append(str(r))
|
||||
appearance = cf.get('appearance')
|
||||
|
||||
lines.append('\t<calculatedField>')
|
||||
lines.append(f'\t\t<dataPath>{esc_xml(data_path)}</dataPath>')
|
||||
lines.append(f'\t\t<expression>{esc_xml(expression)}</expression>')
|
||||
|
||||
if title:
|
||||
emit_mltext(lines, '\t\t', 'title', title)
|
||||
if type_str:
|
||||
lines.append('\t\t<valueType>')
|
||||
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>')
|
||||
|
||||
|
||||
@@ -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
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -251,36 +251,46 @@ function Parse-TotalShorthand {
|
||||
function Parse-CalcShorthand {
|
||||
param([string]$s)
|
||||
|
||||
$title = ""
|
||||
# Extract [Title] first
|
||||
if ($s -match '\[([^\]]+)\]') {
|
||||
$title = $Matches[1]
|
||||
$s = $s -replace '\s*\[[^\]]+\]', ''
|
||||
}
|
||||
# Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
|
||||
# - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
|
||||
# an expression (e.g. index access) isn't interpreted as a title.
|
||||
# - `#restrict` flags use a known-names pattern and are extracted globally —
|
||||
# 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 = @()
|
||||
$restrictMatches = [regex]::Matches($s, '#(\w+)')
|
||||
foreach ($m in $restrictMatches) {
|
||||
foreach ($m in [regex]::Matches($s, $restrictPattern)) {
|
||||
$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('=')
|
||||
if ($eqIdx -gt 0) {
|
||||
$left = $s.Substring(0, $eqIdx).Trim()
|
||||
$expression = $s.Substring($eqIdx + 1).Trim()
|
||||
|
||||
if ($left.Contains(':')) {
|
||||
$colonIdx = $left.IndexOf(':')
|
||||
$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 }
|
||||
$lhs = $s.Substring(0, $eqIdx)
|
||||
$rhs = $s.Substring($eqIdx + 1).Trim()
|
||||
} else {
|
||||
$lhs = $s
|
||||
$rhs = $null
|
||||
}
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
import argparse
|
||||
import os
|
||||
@@ -260,26 +260,42 @@ def parse_total_shorthand(s):
|
||||
|
||||
|
||||
def parse_calc_shorthand(s):
|
||||
title = ""
|
||||
m = re.search(r'\[([^\]]+)\]', s)
|
||||
if m:
|
||||
title = m.group(1)
|
||||
s = re.sub(r'\s*\[[^\]]+\]', '', s)
|
||||
# Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
|
||||
# - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
|
||||
# an expression (e.g. index access) isn't interpreted as a title.
|
||||
# - `#restrict` flags use a known-names pattern and are extracted globally —
|
||||
# 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)
|
||||
s = re.sub(r'\s*#\w+', '', s)
|
||||
restrict_matches = re.findall(restrict_pattern, s)
|
||||
s = re.sub(r'\s*' + restrict_pattern, '', s)
|
||||
|
||||
eq_idx = s.find("=")
|
||||
if eq_idx > 0:
|
||||
left = s[:eq_idx].strip()
|
||||
expression = s[eq_idx + 1:].strip()
|
||||
if ":" in left:
|
||||
colon_idx = left.index(":")
|
||||
data_path = left[:colon_idx].strip()
|
||||
type_str = resolve_type_str(left[colon_idx + 1:].strip())
|
||||
return {"dataPath": data_path, "expression": expression, "type": type_str, "title": title, "restrict": restrict_matches}
|
||||
return {"dataPath": left, "expression": expression, "type": "", "title": title, "restrict": restrict_matches}
|
||||
return {"dataPath": s.strip(), "expression": "", "type": "", "title": title, "restrict": restrict_matches}
|
||||
lhs = s[:eq_idx]
|
||||
rhs = s[eq_idx + 1:].strip()
|
||||
has_rhs = True
|
||||
else:
|
||||
lhs = s
|
||||
rhs = ""
|
||||
has_rhs = False
|
||||
|
||||
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):
|
||||
|
||||
@@ -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