mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-12 14:33:20 +03:00
Compare commits
69
Commits
w-2026-08-02
...
main
@@ -1,4 +1,4 @@
|
|||||||
# cf-edit v1.11 — Edit 1C configuration root (Configuration.xml)
|
# cf-edit v1.19 — Edit 1C configuration root (Configuration.xml)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)][Alias('Path')][string]$ConfigPath,
|
[Parameter(Mandatory)][Alias('Path')][string]$ConfigPath,
|
||||||
@@ -163,6 +163,8 @@ function Assert-EditAllowed([string]$targetPath, [string]$require) {
|
|||||||
Assert-EditAllowed $resolvedPath 'editable'
|
Assert-EditAllowed $resolvedPath 'editable'
|
||||||
|
|
||||||
# --- Load XML with PreserveWhitespace ---
|
# --- Load XML with PreserveWhitespace ---
|
||||||
|
# NB: парсер XML по спецификации схлопывает CRLF в LF, а вставки ниже собираются с
|
||||||
|
# явным CRLF — поэтому EOL приводится к целевому в точке записи (см. финализацию).
|
||||||
$script:xmlDoc = New-Object System.Xml.XmlDocument
|
$script:xmlDoc = New-Object System.Xml.XmlDocument
|
||||||
$script:xmlDoc.PreserveWhitespace = $true
|
$script:xmlDoc.PreserveWhitespace = $true
|
||||||
$script:xmlDoc.Load($resolvedPath)
|
$script:xmlDoc.Load($resolvedPath)
|
||||||
@@ -691,7 +693,9 @@ $bodyBlock$declarations
|
|||||||
if (-not (Test-Path $extDir)) { New-Item -ItemType Directory -Path $extDir -Force | Out-Null }
|
if (-not (Test-Path $extDir)) { New-Item -ItemType Directory -Path $extDir -Force | Out-Null }
|
||||||
$caiPath = Join-Path $extDir "ClientApplicationInterface.xml"
|
$caiPath = Join-Path $extDir "ClientApplicationInterface.xml"
|
||||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||||
[System.IO.File]::WriteAllText($caiPath, $caiXml, $utf8Bom)
|
# Файл создаём мы — канон: CRLF в разделителях, без перевода строки в конце.
|
||||||
|
$caiXml = ($caiXml -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
|
[System.IO.File]::WriteAllText($caiPath, $caiXml.TrimEnd("`r", "`n"), $utf8Bom)
|
||||||
$script:modifyCount++
|
$script:modifyCount++
|
||||||
Info "Wrote panel layout: $caiPath"
|
Info "Wrote panel layout: $caiPath"
|
||||||
}
|
}
|
||||||
@@ -880,7 +884,9 @@ $rightXml
|
|||||||
if (-not (Test-Path $extDir)) { New-Item -ItemType Directory -Path $extDir -Force | Out-Null }
|
if (-not (Test-Path $extDir)) { New-Item -ItemType Directory -Path $extDir -Force | Out-Null }
|
||||||
$hpPath = Join-Path $extDir "HomePageWorkArea.xml"
|
$hpPath = Join-Path $extDir "HomePageWorkArea.xml"
|
||||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||||
[System.IO.File]::WriteAllText($hpPath, $hpXml, $utf8Bom)
|
# Файл создаём мы — канон: CRLF в разделителях, без перевода строки в конце.
|
||||||
|
$hpXml = ($hpXml -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
|
[System.IO.File]::WriteAllText($hpPath, $hpXml.TrimEnd("`r", "`n"), $utf8Bom)
|
||||||
$script:modifyCount++
|
$script:modifyCount++
|
||||||
Info "Wrote home page layout: $hpPath"
|
Info "Wrote home page layout: $hpPath"
|
||||||
}
|
}
|
||||||
@@ -982,6 +988,14 @@ $memStream.Close()
|
|||||||
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
|
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
|
||||||
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
|
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
|
||||||
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$text = [regex]::Replace($text, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $resolvedPath) -and ([System.IO.File]::ReadAllText($resolvedPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
|
|
||||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||||
[System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom)
|
[System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cf-edit v1.11 — Edit 1C configuration root (Configuration.xml)
|
# cf-edit v1.19 — Edit 1C configuration root (Configuration.xml)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -12,6 +12,65 @@ import uuid as _uuid
|
|||||||
from html import escape as html_escape
|
from html import escape as html_escape
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
class CIDict(dict):
|
||||||
|
# Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные
|
||||||
|
# реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки
|
||||||
|
# сохраняется — от него зависит порядок эмиссии.
|
||||||
|
def _actual(self, key):
|
||||||
|
if not isinstance(key, str) or dict.__contains__(self, key):
|
||||||
|
return key
|
||||||
|
ci = self.__dict__.get('_ci')
|
||||||
|
if ci is None or len(ci) != len(self):
|
||||||
|
ci = {k.lower(): k for k in self if isinstance(k, str)}
|
||||||
|
self.__dict__['_ci'] = ci
|
||||||
|
return ci.get(key.lower(), key)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return dict.__getitem__(self, self._actual(key))
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return dict.__contains__(self, self._actual(key))
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return dict.get(self, self._actual(key), default)
|
||||||
|
|
||||||
|
def pop(self, key, *default):
|
||||||
|
return dict.pop(self, self._actual(key), *default)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
# запись по ключу, отличающемуся регистром, обновляет существующий, а не плодит дубль
|
||||||
|
dict.__setitem__(self, self._actual(key), value)
|
||||||
|
|
||||||
|
def ci_json(obj):
|
||||||
|
"""Рекурсивно оборачивает разобранный JSON: словари → CIDict, списки обходятся."""
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return CIDict((k, ci_json(v)) for k, v in obj.items())
|
||||||
|
if isinstance(obj, list):
|
||||||
|
return [ci_json(v) for v in obj]
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
||||||
@@ -341,21 +400,22 @@ def _detect_xml_style(path):
|
|||||||
|
|
||||||
|
|
||||||
def _finalize_xml_bytes(xml_bytes, style):
|
def _finalize_xml_bytes(xml_bytes, style):
|
||||||
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None)."""
|
"""Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
|
||||||
enc_decl = style["enc"] if style else "utf-8"
|
выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
|
||||||
|
enc_decl = style["enc"] if style else "UTF-8"
|
||||||
xml_bytes = xml_bytes.replace(
|
xml_bytes = xml_bytes.replace(
|
||||||
b"<?xml version='1.0' encoding='UTF-8'?>",
|
b"<?xml version='1.0' encoding='UTF-8'?>",
|
||||||
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
||||||
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
||||||
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
||||||
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
||||||
# Финальный перенос — как в оригинале (новый файл → есть)
|
# Финальный перенос — как в оригинале (новый файл → нет, канон #57)
|
||||||
want_final_nl = style["final_nl"] if style else True
|
want_final_nl = style["final_nl"] if style else False
|
||||||
xml_bytes = xml_bytes.rstrip(b"\n")
|
xml_bytes = xml_bytes.rstrip(b"\n")
|
||||||
if want_final_nl:
|
if want_final_nl:
|
||||||
xml_bytes += b"\n"
|
xml_bytes += b"\n"
|
||||||
# EOL — как в оригинале (новый файл → LF, текущее поведение)
|
# EOL — как в оригинале (новый файл → CRLF, канон #57)
|
||||||
if style and style["crlf"]:
|
if (style["crlf"] if style else True):
|
||||||
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
||||||
return xml_bytes
|
return xml_bytes
|
||||||
|
|
||||||
@@ -379,7 +439,7 @@ def main():
|
|||||||
parser.add_argument("-Operation", default=None, choices=["modify-property", "add-childObject", "remove-childObject", "add-defaultRole", "remove-defaultRole", "set-defaultRoles", "set-panels", "set-home-page"])
|
parser.add_argument("-Operation", default=None, choices=["modify-property", "add-childObject", "remove-childObject", "add-defaultRole", "remove-defaultRole", "set-defaultRoles", "set-panels", "set-home-page"])
|
||||||
parser.add_argument("-Value", default=None)
|
parser.add_argument("-Value", default=None)
|
||||||
parser.add_argument("-NoValidate", action="store_true")
|
parser.add_argument("-NoValidate", action="store_true")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
if args.DefinitionFile and args.Operation:
|
if args.DefinitionFile and args.Operation:
|
||||||
print("Cannot use both -DefinitionFile and -Operation", file=sys.stderr)
|
print("Cannot use both -DefinitionFile and -Operation", file=sys.stderr)
|
||||||
@@ -762,7 +822,7 @@ def main():
|
|||||||
layout = value
|
layout = value
|
||||||
if isinstance(layout, str):
|
if isinstance(layout, str):
|
||||||
try:
|
try:
|
||||||
layout = json.loads(layout)
|
layout = ci_json(json.loads(layout))
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
print(f"set-panels value must be valid JSON object", file=sys.stderr)
|
print(f"set-panels value must be valid JSON object", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -917,7 +977,7 @@ def main():
|
|||||||
layout = value
|
layout = value
|
||||||
if isinstance(layout, str):
|
if isinstance(layout, str):
|
||||||
try:
|
try:
|
||||||
layout = json.loads(layout)
|
layout = ci_json(json.loads(layout))
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
print("set-home-page value must be valid JSON object", file=sys.stderr)
|
print("set-home-page value must be valid JSON object", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -985,7 +1045,7 @@ def main():
|
|||||||
if not os.path.isabs(def_file):
|
if not os.path.isabs(def_file):
|
||||||
def_file = os.path.join(os.getcwd(), def_file)
|
def_file = os.path.join(os.getcwd(), def_file)
|
||||||
with open(def_file, "r", encoding="utf-8-sig") as fh:
|
with open(def_file, "r", encoding="utf-8-sig") as fh:
|
||||||
ops = json.loads(fh.read())
|
ops = ci_json(json.loads(fh.read()))
|
||||||
if isinstance(ops, list):
|
if isinstance(ops, list):
|
||||||
operations = ops
|
operations = ops
|
||||||
else:
|
else:
|
||||||
@@ -995,23 +1055,25 @@ def main():
|
|||||||
|
|
||||||
for op in operations:
|
for op in operations:
|
||||||
op_name = op.get("operation", args.Operation or "")
|
op_name = op.get("operation", args.Operation or "")
|
||||||
|
# PS сравнивает имя операции через switch, а он регистронезависим.
|
||||||
|
op_key = str(op_name).lower()
|
||||||
op_value = op.get("value", args.Value or "")
|
op_value = op.get("value", args.Value or "")
|
||||||
|
|
||||||
if op_name == "modify-property":
|
if op_key == "modify-property":
|
||||||
do_modify_property(op_value if isinstance(op_value, str) else str(op_value))
|
do_modify_property(op_value if isinstance(op_value, str) else str(op_value))
|
||||||
elif op_name == "add-childObject":
|
elif op_key == "add-childobject":
|
||||||
do_add_child_object(op_value if isinstance(op_value, str) else str(op_value))
|
do_add_child_object(op_value if isinstance(op_value, str) else str(op_value))
|
||||||
elif op_name == "remove-childObject":
|
elif op_key == "remove-childobject":
|
||||||
do_remove_child_object(op_value if isinstance(op_value, str) else str(op_value))
|
do_remove_child_object(op_value if isinstance(op_value, str) else str(op_value))
|
||||||
elif op_name == "add-defaultRole":
|
elif op_key == "add-defaultrole":
|
||||||
do_add_default_role(op_value if isinstance(op_value, str) else str(op_value))
|
do_add_default_role(op_value if isinstance(op_value, str) else str(op_value))
|
||||||
elif op_name == "remove-defaultRole":
|
elif op_key == "remove-defaultrole":
|
||||||
do_remove_default_role(op_value if isinstance(op_value, str) else str(op_value))
|
do_remove_default_role(op_value if isinstance(op_value, str) else str(op_value))
|
||||||
elif op_name == "set-defaultRoles":
|
elif op_key == "set-defaultroles":
|
||||||
do_set_default_roles(op_value if isinstance(op_value, str) else str(op_value))
|
do_set_default_roles(op_value if isinstance(op_value, str) else str(op_value))
|
||||||
elif op_name == "set-panels":
|
elif op_key == "set-panels":
|
||||||
do_set_panels(op_value)
|
do_set_panels(op_value)
|
||||||
elif op_name == "set-home-page":
|
elif op_key == "set-home-page":
|
||||||
do_set_home_page(op_value)
|
do_set_home_page(op_value)
|
||||||
else:
|
else:
|
||||||
print(f"Unknown operation: {op_name}", file=sys.stderr)
|
print(f"Unknown operation: {op_name}", file=sys.stderr)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# cf-info v1.4 — Compact summary of 1C configuration root
|
# cf-info v1.5 — Compact summary of 1C configuration root
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$true)][Alias('Path')][string]$ConfigPath,
|
[Parameter(Mandatory=$true)][Alias('Path')][string]$ConfigPath,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cf-info v1.4 — Compact summary of 1C configuration root
|
# cf-info v1.5 — Compact summary of 1C configuration root
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -12,6 +12,28 @@ from lxml import etree
|
|||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
# --- Argument parsing ---
|
# --- Argument parsing ---
|
||||||
parser = argparse.ArgumentParser(description="Analyze 1C configuration structure", allow_abbrev=False)
|
parser = argparse.ArgumentParser(description="Analyze 1C configuration structure", allow_abbrev=False)
|
||||||
parser.add_argument("-ConfigPath", "-Path", required=True, help="Path to Configuration.xml or directory")
|
parser.add_argument("-ConfigPath", "-Path", required=True, help="Path to Configuration.xml or directory")
|
||||||
@@ -20,7 +42,7 @@ parser.add_argument("-Section", "-Name", choices=["home-page"], default=None, he
|
|||||||
parser.add_argument("-Limit", type=int, default=150, help="Max lines to show")
|
parser.add_argument("-Limit", type=int, default=150, help="Max lines to show")
|
||||||
parser.add_argument("-Offset", type=int, default=0, help="Lines to skip")
|
parser.add_argument("-Offset", type=int, default=0, help="Lines to skip")
|
||||||
parser.add_argument("-OutFile", default="", help="Write output to file")
|
parser.add_argument("-OutFile", default="", help="Write output to file")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
# --- Output helper (collect all, paginate at the end) ---
|
# --- Output helper (collect all, paginate at the end) ---
|
||||||
lines_buf = []
|
lines_buf = []
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# cf-init v1.4 — Create empty 1C configuration scaffold
|
# cf-init v1.11 — Create empty 1C configuration scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -16,6 +16,12 @@ param(
|
|||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Esc-XmlText {
|
||||||
|
param([string]$s)
|
||||||
|
# Эскейп ТЕКСТА элемента: только & < > — кавычку и апостроф платформа держит сырыми.
|
||||||
|
return $s.Replace('&','&').Replace('<','<').Replace('>','>')
|
||||||
|
}
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Resolve output dir ---
|
# --- Resolve output dir ---
|
||||||
@@ -43,6 +49,9 @@ $co6 = [guid]::NewGuid().ToString()
|
|||||||
$co7 = [guid]::NewGuid().ToString()
|
$co7 = [guid]::NewGuid().ToString()
|
||||||
|
|
||||||
# --- Mobile functionalities ---
|
# --- Mobile functionalities ---
|
||||||
|
# Версия формата как число — по ней ниже включаются вставки 2.21.
|
||||||
|
$is221 = (($FormatVersion -match '^(\d+)\.(\d+)$') -and ([int]$Matches[1] * 100 + [int]$Matches[2]) -ge 221)
|
||||||
|
|
||||||
$mobileFuncs = @(
|
$mobileFuncs = @(
|
||||||
@("Biometrics","true"), @("Location","false"), @("BackgroundLocation","false"),
|
@("Biometrics","true"), @("Location","false"), @("BackgroundLocation","false"),
|
||||||
@("BluetoothPrinters","false"), @("WiFiPrinters","false"), @("Contacts","false"),
|
@("BluetoothPrinters","false"), @("WiFiPrinters","false"), @("Contacts","false"),
|
||||||
@@ -59,6 +68,9 @@ $mobileFuncs = @(
|
|||||||
@("DocumentScanning","false"), @("SpeechToText","false"), @("Geofences","false"),
|
@("DocumentScanning","false"), @("SpeechToText","false"), @("Geofences","false"),
|
||||||
@("IncomingShareRequests","false"), @("AllIncomingShareRequestsTypesProcessing","false")
|
@("IncomingShareRequests","false"), @("AllIncomingShareRequestsTypesProcessing","false")
|
||||||
)
|
)
|
||||||
|
# TextToSpeech — возможность мобильного приложения, добавленная форматом 2.21 (8.5),
|
||||||
|
# последней в списке. На младших форматах платформа её не пишет.
|
||||||
|
if ($is221) { $mobileFuncs += ,@("TextToSpeech","false") }
|
||||||
|
|
||||||
$mobileXml = ""
|
$mobileXml = ""
|
||||||
foreach ($mf in $mobileFuncs) {
|
foreach ($mf in $mobileFuncs) {
|
||||||
@@ -68,17 +80,43 @@ foreach ($mf in $mobileFuncs) {
|
|||||||
# --- Synonym XML ---
|
# --- Synonym XML ---
|
||||||
$synonymXml = ""
|
$synonymXml = ""
|
||||||
if ($Synonym) {
|
if ($Synonym) {
|
||||||
$synonymXml = "`r`n`t`t`t`t<v8:item>`r`n`t`t`t`t`t<v8:lang>ru</v8:lang>`r`n`t`t`t`t`t<v8:content>$([System.Security.SecurityElement]::Escape($Synonym))</v8:content>`r`n`t`t`t`t</v8:item>`r`n`t`t`t"
|
$synonymXml = "`r`n`t`t`t`t<v8:item>`r`n`t`t`t`t`t<v8:lang>ru</v8:lang>`r`n`t`t`t`t`t<v8:content>$(Esc-XmlText ($Synonym))</v8:content>`r`n`t`t`t`t</v8:item>`r`n`t`t`t"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Optional properties ---
|
# --- Optional properties ---
|
||||||
$vendorXml = if ($Vendor) { [System.Security.SecurityElement]::Escape($Vendor) } else { "" }
|
# Элемент целиком, а не значение внутри пары: при пустом значении Конфигуратор
|
||||||
$versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version) } else { "" }
|
# пишет <Vendor/>, а не <Vendor></Vendor>.
|
||||||
|
$vendorEl = if ($Vendor) { "<Vendor>$(Esc-XmlText ($Vendor))</Vendor>" } else { "<Vendor/>" }
|
||||||
|
$versionEl = if ($Version) { "<Version>$(Esc-XmlText ($Version))</Version>" } else { "<Version/>" }
|
||||||
|
|
||||||
|
# --- Свойства и пространство имён формата 2.21 (платформа 8.5) ---
|
||||||
|
# Значения и ПОЗИЦИИ сняты с выгрузки 8.5.1 (debug/fmt221/dump_v851): те же исходники,
|
||||||
|
# выгруженные с 8.3.27 и с 8.5.1, различаются ровно этим. Порядок важен — вставки идут
|
||||||
|
# на своё место, а не в конец.
|
||||||
|
$nl = "`r`n"
|
||||||
|
$f221AuxForms = ""; $f221WindowVariant = ""; $f221OpenVariant = ""; $f221Captions = ""; $f221Migration = ""
|
||||||
|
$palNs = ""
|
||||||
|
if ($is221) {
|
||||||
|
$palNs = ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette"'
|
||||||
|
# Скобки вокруг -join обязательны: без них `$nl + (массив) -join $nl` разбирается как
|
||||||
|
# `($nl + массив) -join $nl`, массив склеивается пробелами и все теги уезжают в одну строку.
|
||||||
|
$f221AuxForms = $nl + ((@(
|
||||||
|
"<AuxiliaryReportForm/>", "<AuxiliaryReportVariantForm/>", "<AuxiliaryReportSettingsForm/>",
|
||||||
|
"<AuxiliaryDynamicListSettingsForm/>", "<AuxiliaryDataHistoryChangeHistoryForm/>",
|
||||||
|
"<AuxiliaryDataHistoryVersionDataForm/>", "<AuxiliaryDataHistoryVersionDifferencesForm/>",
|
||||||
|
"<AuxiliaryCollaborationSystemUsersChoiceForm/>"
|
||||||
|
) | ForEach-Object { "`t`t`t$_" }) -join $nl)
|
||||||
|
$f221WindowVariant = $nl + "`t`t`t<MainClientApplicationWindowInterfaceVariant>NavigationLeft</MainClientApplicationWindowInterfaceVariant>" +
|
||||||
|
$nl + "`t`t`t<ClientApplicationTheme>Auto</ClientApplicationTheme>"
|
||||||
|
$f221OpenVariant = $nl + "`t`t`t<ClientApplicationWindowsOpenVariant>OpenDataInDialogs</ClientApplicationWindowsOpenVariant>"
|
||||||
|
$f221Captions = $nl + "`t`t`t<Caption/>" + $nl + "`t`t`t<ShortCaption/>"
|
||||||
|
$f221Migration = $nl + "`t`t`t<Version85InterfaceMigrationMode>DontUse</Version85InterfaceMigrationMode>"
|
||||||
|
}
|
||||||
|
|
||||||
# --- Configuration.xml ---
|
# --- Configuration.xml ---
|
||||||
$cfgXml = @"
|
$cfgXml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$FormatVersion">
|
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"$palNs xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$FormatVersion">
|
||||||
<Configuration uuid="$uuidCfg">
|
<Configuration uuid="$uuidCfg">
|
||||||
<InternalInfo>
|
<InternalInfo>
|
||||||
<xr:ContainedObject>
|
<xr:ContainedObject>
|
||||||
@@ -111,7 +149,7 @@ $cfgXml = @"
|
|||||||
</xr:ContainedObject>
|
</xr:ContainedObject>
|
||||||
</InternalInfo>
|
</InternalInfo>
|
||||||
<Properties>
|
<Properties>
|
||||||
<Name>$([System.Security.SecurityElement]::Escape($Name))</Name>
|
<Name>$(Esc-XmlText ($Name))</Name>
|
||||||
<Synonym>$synonymXml</Synonym>
|
<Synonym>$synonymXml</Synonym>
|
||||||
<Comment/>
|
<Comment/>
|
||||||
<NamePrefix/>
|
<NamePrefix/>
|
||||||
@@ -122,8 +160,8 @@ $cfgXml = @"
|
|||||||
</UsePurposes>
|
</UsePurposes>
|
||||||
<ScriptVariant>Russian</ScriptVariant>
|
<ScriptVariant>Russian</ScriptVariant>
|
||||||
<DefaultRoles/>
|
<DefaultRoles/>
|
||||||
<Vendor>$vendorXml</Vendor>
|
$vendorEl
|
||||||
<Version>$versionXml</Version>
|
$versionEl
|
||||||
<UpdateCatalogAddress/>
|
<UpdateCatalogAddress/>
|
||||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||||
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
|
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
|
||||||
@@ -145,15 +183,15 @@ $cfgXml = @"
|
|||||||
<DefaultDataHistoryChangeHistoryForm/>
|
<DefaultDataHistoryChangeHistoryForm/>
|
||||||
<DefaultDataHistoryVersionDataForm/>
|
<DefaultDataHistoryVersionDataForm/>
|
||||||
<DefaultDataHistoryVersionDifferencesForm/>
|
<DefaultDataHistoryVersionDifferencesForm/>
|
||||||
<DefaultCollaborationSystemUsersChoiceForm/>
|
<DefaultCollaborationSystemUsersChoiceForm/>$f221AuxForms
|
||||||
<RequiredMobileApplicationPermissions/>
|
<RequiredMobileApplicationPermissions/>
|
||||||
<UsedMobileApplicationFunctionalities>$mobileXml
|
<UsedMobileApplicationFunctionalities>$mobileXml
|
||||||
</UsedMobileApplicationFunctionalities>
|
</UsedMobileApplicationFunctionalities>
|
||||||
<StandaloneConfigurationRestrictionRoles/>
|
<StandaloneConfigurationRestrictionRoles/>
|
||||||
<MobileApplicationURLs/>
|
<MobileApplicationURLs/>
|
||||||
<AllowedIncomingShareRequestTypes/>
|
<AllowedIncomingShareRequestTypes/>$f221WindowVariant
|
||||||
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
|
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>$f221OpenVariant
|
||||||
<DefaultInterface/>
|
<DefaultInterface/>$f221Captions
|
||||||
<DefaultStyle/>
|
<DefaultStyle/>
|
||||||
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
||||||
<BriefInformation/>
|
<BriefInformation/>
|
||||||
@@ -165,7 +203,7 @@ $cfgXml = @"
|
|||||||
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
||||||
<ModalityUseMode>DontUse</ModalityUseMode>
|
<ModalityUseMode>DontUse</ModalityUseMode>
|
||||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>$f221Migration
|
||||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||||
<CompatibilityMode>$CompatibilityMode</CompatibilityMode>
|
<CompatibilityMode>$CompatibilityMode</CompatibilityMode>
|
||||||
<DefaultConstantsForm/>
|
<DefaultConstantsForm/>
|
||||||
@@ -180,7 +218,7 @@ $cfgXml = @"
|
|||||||
# --- Languages/Русский.xml ---
|
# --- Languages/Русский.xml ---
|
||||||
$langXml = @"
|
$langXml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$FormatVersion">
|
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"$palNs xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$FormatVersion">
|
||||||
<Language uuid="$uuidLang">
|
<Language uuid="$uuidLang">
|
||||||
<Properties>
|
<Properties>
|
||||||
<Name>Русский</Name>
|
<Name>Русский</Name>
|
||||||
@@ -240,11 +278,20 @@ if (-not (Test-Path $extDir)) {
|
|||||||
# --- Write files with UTF-8 BOM ---
|
# --- Write files with UTF-8 BOM ---
|
||||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
|
||||||
[System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc)
|
# XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
|
||||||
|
#
|
||||||
|
# Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
|
||||||
|
# копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||||
|
function Write-XmlFile([string]$path, [string]$text, $encoding) {
|
||||||
|
$t = ($text -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
|
[System.IO.File]::WriteAllText($path, $t.TrimEnd("`r", "`n"), $encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-XmlFile $cfgFile $cfgXml $enc
|
||||||
$langFile = Join-Path $langDir "Русский.xml"
|
$langFile = Join-Path $langDir "Русский.xml"
|
||||||
[System.IO.File]::WriteAllText($langFile, $langXml, $enc)
|
Write-XmlFile $langFile $langXml $enc
|
||||||
$caiFile = Join-Path $extDir "ClientApplicationInterface.xml"
|
$caiFile = Join-Path $extDir "ClientApplicationInterface.xml"
|
||||||
[System.IO.File]::WriteAllText($caiFile, $caiXml, $enc)
|
Write-XmlFile $caiFile $caiXml $enc
|
||||||
|
|
||||||
# --- Output ---
|
# --- Output ---
|
||||||
Write-Host "[OK] Создана конфигурация: $Name"
|
Write-Host "[OK] Создана конфигурация: $Name"
|
||||||
|
|||||||
@@ -1,19 +1,55 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cf-init v1.4 — Create empty 1C configuration scaffold
|
# cf-init v1.11 — Create empty 1C configuration scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
"""Generates minimal XML source files for a 1C configuration."""
|
"""Generates minimal XML source files for a 1C configuration."""
|
||||||
import sys, os, argparse, uuid
|
import sys, os, argparse, re, uuid
|
||||||
|
|
||||||
def esc_xml(s):
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
return s.replace('&','&').replace('<','<').replace('>','>').replace('"','"')
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
def esc_xml_text(s):
|
||||||
|
# Эскейп ТЕКСТА элемента: только & < > — кавычку и апостроф платформа держит сырыми.
|
||||||
|
return s.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||||
|
|
||||||
def new_uuid():
|
def new_uuid():
|
||||||
return str(uuid.uuid4())
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
def write_utf8_bom(path, content):
|
def write_utf8_bom(path, content):
|
||||||
|
# newline='' — без трансляции: иначе текстовый режим Python дал бы CRLF на Windows
|
||||||
|
# и LF на macOS, то есть вывод навыка зависел бы от ОС.
|
||||||
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
def write_xml_file(path, content):
|
||||||
|
"""XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
|
||||||
|
|
||||||
|
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
|
||||||
|
копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||||
|
"""
|
||||||
|
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
|
||||||
|
write_utf8_bom(path, text)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
@@ -29,7 +65,7 @@ def main():
|
|||||||
# Дефолт консервативный: 2.17 читается всеми платформами.
|
# Дефолт консервативный: 2.17 читается всеми платформами.
|
||||||
parser.add_argument('-FormatVersion', dest='FormatVersion', default='2.17',
|
parser.add_argument('-FormatVersion', dest='FormatVersion', default='2.17',
|
||||||
choices=['2.17', '2.18', '2.19', '2.20', '2.21'])
|
choices=['2.17', '2.18', '2.19', '2.20', '2.21'])
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
name = args.Name
|
name = args.Name
|
||||||
synonym = args.Synonym if args.Synonym else name
|
synonym = args.Synonym if args.Synonym else name
|
||||||
@@ -54,6 +90,10 @@ def main():
|
|||||||
co = [new_uuid() for _ in range(7)]
|
co = [new_uuid() for _ in range(7)]
|
||||||
|
|
||||||
# --- Mobile functionalities ---
|
# --- Mobile functionalities ---
|
||||||
|
# Версия формата как число — по ней ниже включаются вставки 2.21.
|
||||||
|
_fm = re.match(r'^(\d+)\.(\d+)$', args.FormatVersion)
|
||||||
|
is_221 = bool(_fm) and int(_fm.group(1)) * 100 + int(_fm.group(2)) >= 221
|
||||||
|
|
||||||
mobile_funcs = [
|
mobile_funcs = [
|
||||||
("Biometrics","true"), ("Location","false"), ("BackgroundLocation","false"),
|
("Biometrics","true"), ("Location","false"), ("BackgroundLocation","false"),
|
||||||
("BluetoothPrinters","false"), ("WiFiPrinters","false"), ("Contacts","false"),
|
("BluetoothPrinters","false"), ("WiFiPrinters","false"), ("Contacts","false"),
|
||||||
@@ -70,6 +110,10 @@ def main():
|
|||||||
("DocumentScanning","false"), ("SpeechToText","false"), ("Geofences","false"),
|
("DocumentScanning","false"), ("SpeechToText","false"), ("Geofences","false"),
|
||||||
("IncomingShareRequests","false"), ("AllIncomingShareRequestsTypesProcessing","false"),
|
("IncomingShareRequests","false"), ("AllIncomingShareRequestsTypesProcessing","false"),
|
||||||
]
|
]
|
||||||
|
# TextToSpeech — возможность мобильного приложения, добавленная форматом 2.21 (8.5),
|
||||||
|
# последней в списке. На младших форматах платформа её не пишет.
|
||||||
|
if is_221:
|
||||||
|
mobile_funcs.append(("TextToSpeech", "false"))
|
||||||
|
|
||||||
mobile_xml = ""
|
mobile_xml = ""
|
||||||
for func_name, func_use in mobile_funcs:
|
for func_name, func_use in mobile_funcs:
|
||||||
@@ -78,10 +122,12 @@ def main():
|
|||||||
# --- Synonym XML ---
|
# --- Synonym XML ---
|
||||||
synonym_xml = ""
|
synonym_xml = ""
|
||||||
if synonym:
|
if synonym:
|
||||||
synonym_xml = f"\r\n\t\t\t\t<v8:item>\r\n\t\t\t\t\t<v8:lang>ru</v8:lang>\r\n\t\t\t\t\t<v8:content>{esc_xml(synonym)}</v8:content>\r\n\t\t\t\t</v8:item>\r\n\t\t\t"
|
synonym_xml = f"\r\n\t\t\t\t<v8:item>\r\n\t\t\t\t\t<v8:lang>ru</v8:lang>\r\n\t\t\t\t\t<v8:content>{esc_xml_text(synonym)}</v8:content>\r\n\t\t\t\t</v8:item>\r\n\t\t\t"
|
||||||
|
|
||||||
vendor_xml = esc_xml(vendor) if vendor else ""
|
# Элемент целиком, а не значение внутри пары: при пустом значении Конфигуратор
|
||||||
version_xml = esc_xml(version) if version else ""
|
# пишет <Vendor/>, а не <Vendor></Vendor>.
|
||||||
|
vendor_el = f"<Vendor>{esc_xml_text(vendor)}</Vendor>" if vendor else "<Vendor/>"
|
||||||
|
version_el = f"<Version>{esc_xml_text(version)}</Version>" if version else "<Version/>"
|
||||||
|
|
||||||
class_ids = [
|
class_ids = [
|
||||||
"9cd510cd-abfc-11d4-9434-004095e12fc7",
|
"9cd510cd-abfc-11d4-9434-004095e12fc7",
|
||||||
@@ -93,6 +139,28 @@ def main():
|
|||||||
"fb282519-d103-4dd3-bc12-cb271d631dfc",
|
"fb282519-d103-4dd3-bc12-cb271d631dfc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Свойства и пространство имён формата 2.21 (платформа 8.5). Значения и ПОЗИЦИИ сняты
|
||||||
|
# с выгрузки 8.5.1 (debug/fmt221/dump_v851): те же исходники, выгруженные с 8.3.27 и
|
||||||
|
# с 8.5.1, различаются ровно этим. Порядок важен — вставки идут на своё место.
|
||||||
|
pal_ns = ""
|
||||||
|
f221_aux_forms = f221_window_variant = f221_open_variant = f221_captions = f221_migration = ""
|
||||||
|
if is_221:
|
||||||
|
pal_ns = ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette"'
|
||||||
|
f221_aux_forms = "\r\n" + "\r\n".join(
|
||||||
|
f"\t\t\t{t}" for t in (
|
||||||
|
"<AuxiliaryReportForm/>", "<AuxiliaryReportVariantForm/>", "<AuxiliaryReportSettingsForm/>",
|
||||||
|
"<AuxiliaryDynamicListSettingsForm/>", "<AuxiliaryDataHistoryChangeHistoryForm/>",
|
||||||
|
"<AuxiliaryDataHistoryVersionDataForm/>", "<AuxiliaryDataHistoryVersionDifferencesForm/>",
|
||||||
|
"<AuxiliaryCollaborationSystemUsersChoiceForm/>"))
|
||||||
|
f221_window_variant = ("\r\n\t\t\t<MainClientApplicationWindowInterfaceVariant>NavigationLeft"
|
||||||
|
"</MainClientApplicationWindowInterfaceVariant>"
|
||||||
|
"\r\n\t\t\t<ClientApplicationTheme>Auto</ClientApplicationTheme>")
|
||||||
|
f221_open_variant = ("\r\n\t\t\t<ClientApplicationWindowsOpenVariant>OpenDataInDialogs"
|
||||||
|
"</ClientApplicationWindowsOpenVariant>")
|
||||||
|
f221_captions = "\r\n\t\t\t<Caption/>\r\n\t\t\t<ShortCaption/>"
|
||||||
|
f221_migration = ("\r\n\t\t\t<Version85InterfaceMigrationMode>DontUse"
|
||||||
|
"</Version85InterfaceMigrationMode>")
|
||||||
|
|
||||||
contained_objects = ""
|
contained_objects = ""
|
||||||
for i in range(7):
|
for i in range(7):
|
||||||
contained_objects += f"""\t\t\t<xr:ContainedObject>
|
contained_objects += f"""\t\t\t<xr:ContainedObject>
|
||||||
@@ -101,12 +169,12 @@ def main():
|
|||||||
\t\t\t</xr:ContainedObject>\n"""
|
\t\t\t</xr:ContainedObject>\n"""
|
||||||
|
|
||||||
cfg_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
cfg_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{args.FormatVersion}">
|
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"{pal_ns} xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{args.FormatVersion}">
|
||||||
\t<Configuration uuid="{uuid_cfg}">
|
\t<Configuration uuid="{uuid_cfg}">
|
||||||
\t\t<InternalInfo>
|
\t\t<InternalInfo>
|
||||||
{contained_objects}\t\t</InternalInfo>
|
{contained_objects}\t\t</InternalInfo>
|
||||||
\t\t<Properties>
|
\t\t<Properties>
|
||||||
\t\t\t<Name>{esc_xml(name)}</Name>
|
\t\t\t<Name>{esc_xml_text(name)}</Name>
|
||||||
\t\t\t<Synonym>{synonym_xml}</Synonym>
|
\t\t\t<Synonym>{synonym_xml}</Synonym>
|
||||||
\t\t\t<Comment/>
|
\t\t\t<Comment/>
|
||||||
\t\t\t<NamePrefix/>
|
\t\t\t<NamePrefix/>
|
||||||
@@ -117,8 +185,8 @@ def main():
|
|||||||
\t\t\t</UsePurposes>
|
\t\t\t</UsePurposes>
|
||||||
\t\t\t<ScriptVariant>Russian</ScriptVariant>
|
\t\t\t<ScriptVariant>Russian</ScriptVariant>
|
||||||
\t\t\t<DefaultRoles/>
|
\t\t\t<DefaultRoles/>
|
||||||
\t\t\t<Vendor>{vendor_xml}</Vendor>
|
\t\t\t{vendor_el}
|
||||||
\t\t\t<Version>{version_xml}</Version>
|
\t\t\t{version_el}
|
||||||
\t\t\t<UpdateCatalogAddress/>
|
\t\t\t<UpdateCatalogAddress/>
|
||||||
\t\t\t<IncludeHelpInContents>false</IncludeHelpInContents>
|
\t\t\t<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||||
\t\t\t<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
|
\t\t\t<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
|
||||||
@@ -140,15 +208,15 @@ def main():
|
|||||||
\t\t\t<DefaultDataHistoryChangeHistoryForm/>
|
\t\t\t<DefaultDataHistoryChangeHistoryForm/>
|
||||||
\t\t\t<DefaultDataHistoryVersionDataForm/>
|
\t\t\t<DefaultDataHistoryVersionDataForm/>
|
||||||
\t\t\t<DefaultDataHistoryVersionDifferencesForm/>
|
\t\t\t<DefaultDataHistoryVersionDifferencesForm/>
|
||||||
\t\t\t<DefaultCollaborationSystemUsersChoiceForm/>
|
\t\t\t<DefaultCollaborationSystemUsersChoiceForm/>{f221_aux_forms}
|
||||||
\t\t\t<RequiredMobileApplicationPermissions/>
|
\t\t\t<RequiredMobileApplicationPermissions/>
|
||||||
\t\t\t<UsedMobileApplicationFunctionalities>{mobile_xml}
|
\t\t\t<UsedMobileApplicationFunctionalities>{mobile_xml}
|
||||||
\t\t\t</UsedMobileApplicationFunctionalities>
|
\t\t\t</UsedMobileApplicationFunctionalities>
|
||||||
\t\t\t<StandaloneConfigurationRestrictionRoles/>
|
\t\t\t<StandaloneConfigurationRestrictionRoles/>
|
||||||
\t\t\t<MobileApplicationURLs/>
|
\t\t\t<MobileApplicationURLs/>
|
||||||
\t\t\t<AllowedIncomingShareRequestTypes/>
|
\t\t\t<AllowedIncomingShareRequestTypes/>{f221_window_variant}
|
||||||
\t\t\t<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
|
\t\t\t<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>{f221_open_variant}
|
||||||
\t\t\t<DefaultInterface/>
|
\t\t\t<DefaultInterface/>{f221_captions}
|
||||||
\t\t\t<DefaultStyle/>
|
\t\t\t<DefaultStyle/>
|
||||||
\t\t\t<DefaultLanguage>Language.Русский</DefaultLanguage>
|
\t\t\t<DefaultLanguage>Language.Русский</DefaultLanguage>
|
||||||
\t\t\t<BriefInformation/>
|
\t\t\t<BriefInformation/>
|
||||||
@@ -160,7 +228,7 @@ def main():
|
|||||||
\t\t\t<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
\t\t\t<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
||||||
\t\t\t<ModalityUseMode>DontUse</ModalityUseMode>
|
\t\t\t<ModalityUseMode>DontUse</ModalityUseMode>
|
||||||
\t\t\t<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
\t\t\t<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||||
\t\t\t<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
\t\t\t<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>{f221_migration}
|
||||||
\t\t\t<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
\t\t\t<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||||
\t\t\t<CompatibilityMode>{compat}</CompatibilityMode>
|
\t\t\t<CompatibilityMode>{compat}</CompatibilityMode>
|
||||||
\t\t\t<DefaultConstantsForm/>
|
\t\t\t<DefaultConstantsForm/>
|
||||||
@@ -173,7 +241,7 @@ def main():
|
|||||||
|
|
||||||
# --- Languages/Русский.xml ---
|
# --- Languages/Русский.xml ---
|
||||||
lang_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
lang_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{args.FormatVersion}">
|
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"{pal_ns} xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{args.FormatVersion}">
|
||||||
\t<Language uuid="{uuid_lang}">
|
\t<Language uuid="{uuid_lang}">
|
||||||
\t\t<Properties>
|
\t\t<Properties>
|
||||||
\t\t\t<Name>Русский</Name>
|
\t\t\t<Name>Русский</Name>
|
||||||
@@ -222,11 +290,11 @@ def main():
|
|||||||
os.makedirs(ext_dir, exist_ok=True)
|
os.makedirs(ext_dir, exist_ok=True)
|
||||||
|
|
||||||
# --- Write files ---
|
# --- Write files ---
|
||||||
write_utf8_bom(cfg_file, cfg_xml)
|
write_xml_file(cfg_file, cfg_xml)
|
||||||
lang_file = os.path.join(lang_dir, "Русский.xml")
|
lang_file = os.path.join(lang_dir, "Русский.xml")
|
||||||
write_utf8_bom(lang_file, lang_xml)
|
write_xml_file(lang_file, lang_xml)
|
||||||
cai_file = os.path.join(ext_dir, "ClientApplicationInterface.xml")
|
cai_file = os.path.join(ext_dir, "ClientApplicationInterface.xml")
|
||||||
write_utf8_bom(cai_file, cai_xml)
|
write_xml_file(cai_file, cai_xml)
|
||||||
|
|
||||||
print(f"[OK] Создана конфигурация: {name}")
|
print(f"[OK] Создана конфигурация: {name}")
|
||||||
print(f" Каталог: {output_dir}")
|
print(f" Каталог: {output_dir}")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# cf-validate v1.5 — Validate 1C configuration root structure
|
# cf-validate v1.6 — Validate 1C configuration root structure
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
|
|||||||
@@ -1,10 +1,32 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cf-validate v1.5 — Validate 1C configuration XML structure
|
# cf-validate v1.6 — Validate 1C configuration XML structure
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
"""Validates Configuration.xml: root structure, InternalInfo, properties, ChildObjects, languages."""
|
"""Validates Configuration.xml: root structure, InternalInfo, properties, ChildObjects, languages."""
|
||||||
import sys, os, argparse, re
|
import sys, os, argparse, re
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
NS = {
|
NS = {
|
||||||
'md': 'http://v8.1c.ru/8.3/MDClasses',
|
'md': 'http://v8.1c.ru/8.3/MDClasses',
|
||||||
'v8': 'http://v8.1c.ru/8.1/data/core',
|
'v8': 'http://v8.1c.ru/8.1/data/core',
|
||||||
@@ -170,7 +192,7 @@ def main():
|
|||||||
parser.add_argument('-Detailed', action='store_true')
|
parser.add_argument('-Detailed', action='store_true')
|
||||||
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
|
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
|
||||||
parser.add_argument('-OutFile', dest='OutFile', default='')
|
parser.add_argument('-OutFile', dest='OutFile', default='')
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
config_path = args.ConfigPath
|
config_path = args.ConfigPath
|
||||||
max_errors = args.MaxErrors
|
max_errors = args.MaxErrors
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ allowed-tools:
|
|||||||
- `Enum.ВидыОплат` — перечисление
|
- `Enum.ВидыОплат` — перечисление
|
||||||
- `Catalog.Контрагенты.Form.ФормаЭлемента` — форма объекта (заимствование формы)
|
- `Catalog.Контрагенты.Form.ФормаЭлемента` — форма объекта (заимствование формы)
|
||||||
- `Catalog.X ;; CommonModule.Y ;; Enum.Z` — несколько объектов
|
- `Catalog.X ;; CommonModule.Y ;; Enum.Z` — несколько объектов
|
||||||
Поддерживаются все 44 типа объектов конфигурации.
|
|
||||||
|
|
||||||
### Заимствование форм
|
### Заимствование форм
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# cfe-borrow v1.11 — Borrow objects from configuration into extension (CFE)
|
# cfe-borrow v1.20 — Borrow objects from configuration into extension (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)][string]$ExtensionPath,
|
[Parameter(Mandatory)][string]$ExtensionPath,
|
||||||
@@ -125,7 +125,7 @@ $childTypeDirMap = @{
|
|||||||
"Sequence"="Sequences"; "IntegrationService"="IntegrationServices"
|
"Sequence"="Sequences"; "IntegrationService"="IntegrationServices"
|
||||||
"XDTOPackage"="XDTOPackages"; "WebService"="WebServices"
|
"XDTOPackage"="XDTOPackages"; "WebService"="WebServices"
|
||||||
"HTTPService"="HTTPServices"; "WSReference"="WSReferences"
|
"HTTPService"="HTTPServices"; "WSReference"="WSReferences"
|
||||||
"CommonAttribute"="CommonAttributes"; "Style"="Styles"
|
"CommonAttribute"="CommonAttributes"; "Style"="Styles"; "Bot"="Bots"; "Language"="Languages"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- 4b. Russian synonym → English type ---
|
# --- 4b. Russian synonym → English type ---
|
||||||
@@ -153,7 +153,7 @@ $synonymMap = @{
|
|||||||
$script:typeOrder = @(
|
$script:typeOrder = @(
|
||||||
"Language","Subsystem","StyleItem","Style",
|
"Language","Subsystem","StyleItem","Style",
|
||||||
"CommonPicture","SessionParameter","Role","CommonTemplate",
|
"CommonPicture","SessionParameter","Role","CommonTemplate",
|
||||||
"FilterCriterion","CommonModule","CommonAttribute","ExchangePlan",
|
"FilterCriterion","CommonModule","Bot","CommonAttribute","ExchangePlan",
|
||||||
"XDTOPackage","WebService","HTTPService","WSReference",
|
"XDTOPackage","WebService","HTTPService","WSReference",
|
||||||
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
|
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
|
||||||
"FunctionalOptionsParameter","DefinedType","CommonCommand","CommandGroup",
|
"FunctionalOptionsParameter","DefinedType","CommonCommand","CommandGroup",
|
||||||
@@ -368,6 +368,14 @@ function Expand-SelfClosingElement($container, $parentIndent) {
|
|||||||
function Detect-FormatVersion([string]$dir) {
|
function Detect-FormatVersion([string]$dir) {
|
||||||
$d = $dir
|
$d = $dir
|
||||||
while ($d) {
|
while ($d) {
|
||||||
|
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||||
|
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||||
|
$extPath = "$d.xml"
|
||||||
|
if (Test-Path $extPath) {
|
||||||
|
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
|
||||||
|
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
|
||||||
|
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||||
|
}
|
||||||
$cfgPath = Join-Path $d "Configuration.xml"
|
$cfgPath = Join-Path $d "Configuration.xml"
|
||||||
if (Test-Path $cfgPath) {
|
if (Test-Path $cfgPath) {
|
||||||
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
||||||
@@ -388,6 +396,20 @@ $script:formatVersion = Detect-FormatVersion $extDir
|
|||||||
# --- 8. Namespaces declaration for object XML ---
|
# --- 8. Namespaces declaration for object XML ---
|
||||||
$script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
$script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||||
|
|
||||||
|
# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209.
|
||||||
|
# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка.
|
||||||
|
function Get-FormatRank([string]$ver) {
|
||||||
|
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
|
||||||
|
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
|
||||||
|
# дописать в конец нельзя.
|
||||||
|
if ((Get-FormatRank $script:formatVersion) -ge 221) {
|
||||||
|
$script:xmlnsDecl = $script:xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
|
||||||
|
}
|
||||||
|
|
||||||
# --- 9. Parse -Object into items ---
|
# --- 9. Parse -Object into items ---
|
||||||
$items = @()
|
$items = @()
|
||||||
foreach ($part in $Object.Split(";;")) {
|
foreach ($part in $Object.Split(";;")) {
|
||||||
@@ -824,11 +846,22 @@ function Borrow-Form {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extract the <Form ...> opening tag from source text (preserves namespace declarations)
|
# Открывающий тег <Form ...> берём из исходной формы — ради её объявлений пространств имён,
|
||||||
|
# но version подставляем СВОЮ: форма обязана нести версию расширения, иначе платформа
|
||||||
|
# отвергает импорт (форма 2.13 внутри расширения 2.17). Раньше тег копировался целиком,
|
||||||
|
# и версия источника молча побеждала.
|
||||||
$xmlDecl = '<?xml version="1.0" encoding="UTF-8"?>'
|
$xmlDecl = '<?xml version="1.0" encoding="UTF-8"?>'
|
||||||
$formTag = "<Form version=`"${formVersion}`">"
|
$formTag = "<Form version=`"${formVersion}`">"
|
||||||
if ($srcFormContent -match '(?s)^(<\?xml[^?]*\?>)') { $xmlDecl = $Matches[1] }
|
if ($srcFormContent -match '(?s)^(<\?xml[^?]*\?>)') { $xmlDecl = $Matches[1] }
|
||||||
if ($srcFormContent -match '(<Form[^>]*>)') { $formTag = $Matches[1] }
|
if ($srcFormContent -match '(<Form[^>]*>)') {
|
||||||
|
$srcTag = $Matches[1]
|
||||||
|
$srcNs = $srcTag -replace '^<Form\s*', '' -replace '\s*/?>$', '' -replace '\s*version="[^"]*"', ''
|
||||||
|
# 2.21 (8.5): пространство палитры. Место строгое — после lf, перед style.
|
||||||
|
if ((Get-FormatRank $formVersion) -ge 221 -and $srcNs -notmatch 'xmlns:pal=') {
|
||||||
|
$srcNs = $srcNs -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
|
||||||
|
}
|
||||||
|
$formTag = if ($srcNs) { "<Form $srcNs version=`"${formVersion}`">" } else { "<Form version=`"${formVersion}`">" }
|
||||||
|
}
|
||||||
|
|
||||||
# Build output Form.xml
|
# Build output Form.xml
|
||||||
$formXmlSb = New-Object System.Text.StringBuilder
|
$formXmlSb = New-Object System.Text.StringBuilder
|
||||||
@@ -917,7 +950,15 @@ function Borrow-Form {
|
|||||||
New-Item -ItemType Directory -Path $formXmlDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $formXmlDir -Force | Out-Null
|
||||||
}
|
}
|
||||||
$formXmlFile = Join-Path $formXmlDir "Form.xml"
|
$formXmlFile = Join-Path $formXmlDir "Form.xml"
|
||||||
[System.IO.File]::WriteAllText($formXmlFile, $formXmlSb.ToString(), $enc)
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
# Здесь источник не XmlWriter, а OuterXml исходного документа — спацовывает так же.
|
||||||
|
$formXmlText = $formXmlSb.ToString()
|
||||||
|
$formXmlText = [regex]::Replace($formXmlText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
# Файл создаём мы — канон выгрузки: CRLF в разделителях строк.
|
||||||
|
$formXmlText = ($formXmlText -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
|
[System.IO.File]::WriteAllText($formXmlFile, $formXmlText, $enc)
|
||||||
Info " Created: $formXmlFile"
|
Info " Created: $formXmlFile"
|
||||||
|
|
||||||
# 6. Create empty Module.bsl — but NEVER overwrite an existing one (re-borrow must
|
# 6. Create empty Module.bsl — but NEVER overwrite an existing one (re-borrow must
|
||||||
@@ -1023,8 +1064,16 @@ function Register-FormInObject {
|
|||||||
$text2 = [System.Text.Encoding]::UTF8.GetString($bytes2)
|
$text2 = [System.Text.Encoding]::UTF8.GetString($bytes2)
|
||||||
if ($text2.Length -gt 0 -and $text2[0] -eq [char]0xFEFF) { $text2 = $text2.Substring(1) }
|
if ($text2.Length -gt 0 -and $text2[0] -eq [char]0xFEFF) { $text2 = $text2.Substring(1) }
|
||||||
$text2 = $text2.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
$text2 = $text2.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$text2 = [regex]::Replace($text2, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
|
||||||
$utf8Bom2 = New-Object System.Text.UTF8Encoding($true)
|
$utf8Bom2 = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $objFile) -and ([System.IO.File]::ReadAllText($objFile) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$text2 = ($text2 -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
[System.IO.File]::WriteAllText($objFile, $text2, $utf8Bom2)
|
[System.IO.File]::WriteAllText($objFile, $text2, $utf8Bom2)
|
||||||
Info " Registered form in: $objFile"
|
Info " Registered form in: $objFile"
|
||||||
}
|
}
|
||||||
@@ -1413,7 +1462,17 @@ function Merge-AttributesIntoObject {
|
|||||||
# Insert attributes before </ChildObjects>
|
# Insert attributes before </ChildObjects>
|
||||||
$text3 = $text3 -replace '</ChildObjects>', "${allAttrXml}`r`n`t`t</ChildObjects>"
|
$text3 = $text3 -replace '</ChildObjects>', "${allAttrXml}`r`n`t`t</ChildObjects>"
|
||||||
|
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
# Стоит ПОСЛЕ вставки реквизитов, чтобы накрыть и их.
|
||||||
|
$text3 = [regex]::Replace($text3, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
|
||||||
$utf8Bom3 = New-Object System.Text.UTF8Encoding($true)
|
$utf8Bom3 = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $objFile) -and ([System.IO.File]::ReadAllText($objFile) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$text3 = ($text3 -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
[System.IO.File]::WriteAllText($objFile, $text3, $utf8Bom3)
|
[System.IO.File]::WriteAllText($objFile, $text3, $utf8Bom3)
|
||||||
Info " Merged $added attribute(s) into: $objFile"
|
Info " Merged $added attribute(s) into: $objFile"
|
||||||
}
|
}
|
||||||
@@ -1877,8 +1936,16 @@ $memStream.Close()
|
|||||||
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
|
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
|
||||||
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
|
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
|
||||||
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$text = [regex]::Replace($text, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
|
||||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $extResolvedPath) -and ([System.IO.File]::ReadAllText($extResolvedPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
[System.IO.File]::WriteAllText($extResolvedPath, $text, $utf8Bom)
|
[System.IO.File]::WriteAllText($extResolvedPath, $text, $utf8Bom)
|
||||||
Info "Saved: $extResolvedPath"
|
Info "Saved: $extResolvedPath"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cfe-borrow v1.11 — Borrow objects from configuration into extension (CFE)
|
# cfe-borrow v1.20 — Borrow objects from configuration into extension (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -9,6 +9,28 @@ import sys
|
|||||||
import uuid
|
import uuid
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
||||||
XR_NS = "http://v8.1c.ru/8.3/xcf/readable"
|
XR_NS = "http://v8.1c.ru/8.3/xcf/readable"
|
||||||
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
||||||
@@ -80,6 +102,7 @@ CHILD_TYPE_DIR_MAP = {
|
|||||||
"XDTOPackage": "XDTOPackages", "WebService": "WebServices",
|
"XDTOPackage": "XDTOPackages", "WebService": "WebServices",
|
||||||
"HTTPService": "HTTPServices", "WSReference": "WSReferences",
|
"HTTPService": "HTTPServices", "WSReference": "WSReferences",
|
||||||
"CommonAttribute": "CommonAttributes", "Style": "Styles",
|
"CommonAttribute": "CommonAttributes", "Style": "Styles",
|
||||||
|
"Bot": "Bots", "Language": "Languages",
|
||||||
}
|
}
|
||||||
|
|
||||||
SYNONYM_MAP = {
|
SYNONYM_MAP = {
|
||||||
@@ -123,7 +146,7 @@ SYNONYM_MAP = {
|
|||||||
TYPE_ORDER = [
|
TYPE_ORDER = [
|
||||||
"Language", "Subsystem", "StyleItem", "Style",
|
"Language", "Subsystem", "StyleItem", "Style",
|
||||||
"CommonPicture", "SessionParameter", "Role", "CommonTemplate",
|
"CommonPicture", "SessionParameter", "Role", "CommonTemplate",
|
||||||
"FilterCriterion", "CommonModule", "CommonAttribute", "ExchangePlan",
|
"FilterCriterion", "CommonModule", "Bot", "CommonAttribute", "ExchangePlan",
|
||||||
"XDTOPackage", "WebService", "HTTPService", "WSReference",
|
"XDTOPackage", "WebService", "HTTPService", "WSReference",
|
||||||
"EventSubscription", "ScheduledJob", "SettingsStorage", "FunctionalOption",
|
"EventSubscription", "ScheduledJob", "SettingsStorage", "FunctionalOption",
|
||||||
"FunctionalOptionsParameter", "DefinedType", "CommonCommand", "CommandGroup",
|
"FunctionalOptionsParameter", "DefinedType", "CommonCommand", "CommandGroup",
|
||||||
@@ -309,6 +332,16 @@ XMLNS_DECL = (
|
|||||||
|
|
||||||
def detect_format_version(d):
|
def detect_format_version(d):
|
||||||
while d:
|
while d:
|
||||||
|
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||||
|
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||||
|
ext_path = d + ".xml"
|
||||||
|
if os.path.isfile(ext_path):
|
||||||
|
with open(ext_path, "r", encoding="utf-8-sig") as f:
|
||||||
|
ext_head = f.read(2000)
|
||||||
|
if re.search(r'<(ExternalDataProcessor|ExternalReport)[ >]', ext_head):
|
||||||
|
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', ext_head)
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
cfg_path = os.path.join(d, "Configuration.xml")
|
cfg_path = os.path.join(d, "Configuration.xml")
|
||||||
if os.path.isfile(cfg_path):
|
if os.path.isfile(cfg_path):
|
||||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||||
@@ -323,6 +356,23 @@ def detect_format_version(d):
|
|||||||
return "2.17"
|
return "2.17"
|
||||||
|
|
||||||
|
|
||||||
|
def format_rank(ver):
|
||||||
|
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||||
|
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||||
|
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||||
|
|
||||||
|
|
||||||
|
def apply_pal_ns(format_version):
|
||||||
|
"""2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
|
||||||
|
Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
|
||||||
|
дописать в конец нельзя."""
|
||||||
|
global XMLNS_DECL
|
||||||
|
if format_rank(format_version) >= 221:
|
||||||
|
XMLNS_DECL = XMLNS_DECL.replace(
|
||||||
|
' xmlns:style=',
|
||||||
|
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
|
||||||
|
|
||||||
|
|
||||||
def get_child_indent(container):
|
def get_child_indent(container):
|
||||||
if container.text and "\n" in container.text:
|
if container.text and "\n" in container.text:
|
||||||
after_nl = container.text.rsplit("\n", 1)[-1]
|
after_nl = container.text.rsplit("\n", 1)[-1]
|
||||||
@@ -389,21 +439,22 @@ def _detect_xml_style(path):
|
|||||||
|
|
||||||
|
|
||||||
def _finalize_xml_bytes(xml_bytes, style):
|
def _finalize_xml_bytes(xml_bytes, style):
|
||||||
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None)."""
|
"""Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
|
||||||
enc_decl = style["enc"] if style else "utf-8"
|
выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
|
||||||
|
enc_decl = style["enc"] if style else "UTF-8"
|
||||||
xml_bytes = xml_bytes.replace(
|
xml_bytes = xml_bytes.replace(
|
||||||
b"<?xml version='1.0' encoding='UTF-8'?>",
|
b"<?xml version='1.0' encoding='UTF-8'?>",
|
||||||
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
||||||
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
||||||
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
||||||
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
||||||
# Финальный перенос — как в оригинале (новый файл → есть)
|
# Финальный перенос — как в оригинале (новый файл → нет, канон #57)
|
||||||
want_final_nl = style["final_nl"] if style else True
|
want_final_nl = style["final_nl"] if style else False
|
||||||
xml_bytes = xml_bytes.rstrip(b"\n")
|
xml_bytes = xml_bytes.rstrip(b"\n")
|
||||||
if want_final_nl:
|
if want_final_nl:
|
||||||
xml_bytes += b"\n"
|
xml_bytes += b"\n"
|
||||||
# EOL — как в оригинале (новый файл → LF, текущее поведение)
|
# EOL — как в оригинале (новый файл → CRLF, канон #57)
|
||||||
if style and style["crlf"]:
|
if (style["crlf"] if style else True):
|
||||||
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
||||||
return xml_bytes
|
return xml_bytes
|
||||||
|
|
||||||
@@ -418,9 +469,24 @@ def save_xml_bom(tree, path):
|
|||||||
f.write(xml_bytes)
|
f.write(xml_bytes)
|
||||||
|
|
||||||
|
|
||||||
def save_text_bom(path, text):
|
def write_utf8_bom(path, content):
|
||||||
with open(path, "w", encoding="utf-8-sig") as fh:
|
# newline='' — без трансляции: иначе текстовый режим Python дал бы CRLF на Windows
|
||||||
fh.write(text)
|
# и LF на macOS, то есть вывод навыка зависел бы от ОС.
|
||||||
|
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def write_xml_file(path, content):
|
||||||
|
"""XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
|
||||||
|
|
||||||
|
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
|
||||||
|
копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||||
|
|
||||||
|
Только для файлов, которые СОЗДАЁМ: правка существующего наследует его стиль.
|
||||||
|
"""
|
||||||
|
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
|
||||||
|
write_utf8_bom(path, text)
|
||||||
|
|
||||||
|
|
||||||
def new_guid():
|
def new_guid():
|
||||||
@@ -435,7 +501,7 @@ def main():
|
|||||||
parser.add_argument("-ConfigPath", required=True)
|
parser.add_argument("-ConfigPath", required=True)
|
||||||
parser.add_argument("-Object", required=True)
|
parser.add_argument("-Object", required=True)
|
||||||
parser.add_argument("-BorrowMainAttribute", nargs="?", const="Form", default=None)
|
parser.add_argument("-BorrowMainAttribute", nargs="?", const="Form", default=None)
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
# --- 1. Resolve paths ---
|
# --- 1. Resolve paths ---
|
||||||
ext_path = args.ExtensionPath
|
ext_path = args.ExtensionPath
|
||||||
@@ -471,6 +537,7 @@ def main():
|
|||||||
cfg_dir = os.path.dirname(cfg_resolved)
|
cfg_dir = os.path.dirname(cfg_resolved)
|
||||||
|
|
||||||
format_version = detect_format_version(ext_dir)
|
format_version = detect_format_version(ext_dir)
|
||||||
|
apply_pal_ns(format_version)
|
||||||
|
|
||||||
# --- 2. Load extension Configuration.xml ---
|
# --- 2. Load extension Configuration.xml ---
|
||||||
xml_parser = etree.XMLParser(remove_blank_text=False)
|
xml_parser = etree.XMLParser(remove_blank_text=False)
|
||||||
@@ -997,7 +1064,9 @@ def main():
|
|||||||
warn(f"Cannot merge attributes: {obj_file} not found")
|
warn(f"Cannot merge attributes: {obj_file} not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
with open(obj_file, "r", encoding="utf-8-sig") as fh:
|
# newline="" => без трансляции: иначе CRLF молча схлопнется в LF при чтении
|
||||||
|
# и файл будет переписан в LF.
|
||||||
|
with open(obj_file, "r", encoding="utf-8-sig", newline="") as fh:
|
||||||
obj_content = fh.read()
|
obj_content = fh.read()
|
||||||
|
|
||||||
# Collect existing attribute names for dedup (text-based)
|
# Collect existing attribute names for dedup (text-based)
|
||||||
@@ -1019,7 +1088,7 @@ def main():
|
|||||||
obj_content = re.sub(r'<ChildObjects\s*/>', f"<ChildObjects>{all_attr_xml}\r\n\t\t</ChildObjects>", obj_content)
|
obj_content = re.sub(r'<ChildObjects\s*/>', f"<ChildObjects>{all_attr_xml}\r\n\t\t</ChildObjects>", obj_content)
|
||||||
else:
|
else:
|
||||||
obj_content = obj_content.replace("</ChildObjects>", f"{all_attr_xml}\r\n\t\t</ChildObjects>")
|
obj_content = obj_content.replace("</ChildObjects>", f"{all_attr_xml}\r\n\t\t</ChildObjects>")
|
||||||
save_text_bom(obj_file, obj_content)
|
write_utf8_bom(obj_file, obj_content)
|
||||||
info(f" Merged {added} attribute(s) into: {obj_file}")
|
info(f" Merged {added} attribute(s) into: {obj_file}")
|
||||||
|
|
||||||
# --- 11h. Borrow main attribute orchestrator ---
|
# --- 11h. Borrow main attribute orchestrator ---
|
||||||
@@ -1056,7 +1125,9 @@ def main():
|
|||||||
obj_file = os.path.join(ext_dir, dir_name, f"{obj_name}.xml")
|
obj_file = os.path.join(ext_dir, dir_name, f"{obj_name}.xml")
|
||||||
|
|
||||||
# Read existing object XML (needed for dedup + enrichment)
|
# Read existing object XML (needed for dedup + enrichment)
|
||||||
with open(obj_file, "r", encoding="utf-8-sig") as fh:
|
# newline="" => без трансляции: иначе CRLF молча схлопнется в LF при чтении
|
||||||
|
# и файл будет переписан в LF.
|
||||||
|
with open(obj_file, "r", encoding="utf-8-sig", newline="") as fh:
|
||||||
obj_content = fh.read()
|
obj_content = fh.read()
|
||||||
|
|
||||||
# Dedup: skip attributes/TS already present in object's ChildObjects (idempotent re-borrow)
|
# Dedup: skip attributes/TS already present in object's ChildObjects (idempotent re-borrow)
|
||||||
@@ -1104,7 +1175,7 @@ def main():
|
|||||||
f"<ChildObjects>{existing_inner}\r\n{adopted_content}\r\n\t\t</ChildObjects>"
|
f"<ChildObjects>{existing_inner}\r\n{adopted_content}\r\n\t\t</ChildObjects>"
|
||||||
)
|
)
|
||||||
|
|
||||||
save_text_bom(obj_file, obj_content)
|
write_utf8_bom(obj_file, obj_content)
|
||||||
info(f" Enriched object: {obj_file}")
|
info(f" Enriched object: {obj_file}")
|
||||||
|
|
||||||
# Step 4: Collect all reference types and borrow as shells
|
# Step 4: Collect all reference types and borrow as shells
|
||||||
@@ -1133,7 +1204,7 @@ def main():
|
|||||||
target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[rt["TypeName"]])
|
target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[rt["TypeName"]])
|
||||||
os.makedirs(target_dir, exist_ok=True)
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
target_file = os.path.join(target_dir, f"{rt['ObjName']}.xml")
|
target_file = os.path.join(target_dir, f"{rt['ObjName']}.xml")
|
||||||
save_text_bom(target_file, borrowed_xml)
|
write_xml_file(target_file, borrowed_xml)
|
||||||
add_to_child_objects(rt["TypeName"], rt["ObjName"])
|
add_to_child_objects(rt["TypeName"], rt["ObjName"])
|
||||||
borrowed_files.append(target_file)
|
borrowed_files.append(target_file)
|
||||||
info(f" Auto-borrowed: {rt['TypeName']}.{rt['ObjName']}")
|
info(f" Auto-borrowed: {rt['TypeName']}.{rt['ObjName']}")
|
||||||
@@ -1198,7 +1269,7 @@ def main():
|
|||||||
t_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[target_type_name])
|
t_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[target_type_name])
|
||||||
os.makedirs(t_target_dir, exist_ok=True)
|
os.makedirs(t_target_dir, exist_ok=True)
|
||||||
t_target_file = os.path.join(t_target_dir, f"{target_obj_name}.xml")
|
t_target_file = os.path.join(t_target_dir, f"{target_obj_name}.xml")
|
||||||
save_text_bom(t_target_file, t_borrowed_xml)
|
write_xml_file(t_target_file, t_borrowed_xml)
|
||||||
add_to_child_objects(target_type_name, target_obj_name)
|
add_to_child_objects(target_type_name, target_obj_name)
|
||||||
borrowed_files.append(t_target_file)
|
borrowed_files.append(t_target_file)
|
||||||
info(f" Auto-borrowed for deep path: {target_type_name}.{target_obj_name}")
|
info(f" Auto-borrowed for deep path: {target_type_name}.{target_obj_name}")
|
||||||
@@ -1222,7 +1293,7 @@ def main():
|
|||||||
s_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]])
|
s_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]])
|
||||||
os.makedirs(s_target_dir, exist_ok=True)
|
os.makedirs(s_target_dir, exist_ok=True)
|
||||||
s_target_file = os.path.join(s_target_dir, f"{srt['ObjName']}.xml")
|
s_target_file = os.path.join(s_target_dir, f"{srt['ObjName']}.xml")
|
||||||
save_text_bom(s_target_file, s_borrowed_xml)
|
write_xml_file(s_target_file, s_borrowed_xml)
|
||||||
add_to_child_objects(srt["TypeName"], srt["ObjName"])
|
add_to_child_objects(srt["TypeName"], srt["ObjName"])
|
||||||
borrowed_files.append(s_target_file)
|
borrowed_files.append(s_target_file)
|
||||||
info(f" Auto-borrowed (deep): {srt['TypeName']}.{srt['ObjName']}")
|
info(f" Auto-borrowed (deep): {srt['TypeName']}.{srt['ObjName']}")
|
||||||
@@ -1279,7 +1350,7 @@ def main():
|
|||||||
os.makedirs(form_meta_dir, exist_ok=True)
|
os.makedirs(form_meta_dir, exist_ok=True)
|
||||||
|
|
||||||
form_meta_file = os.path.join(form_meta_dir, f"{form_name}.xml")
|
form_meta_file = os.path.join(form_meta_dir, f"{form_name}.xml")
|
||||||
save_text_bom(form_meta_file, "\n".join(form_meta_lines))
|
write_xml_file(form_meta_file, "\n".join(form_meta_lines))
|
||||||
info(f" Created: {form_meta_file}")
|
info(f" Created: {form_meta_file}")
|
||||||
|
|
||||||
# 5. Generate Form.xml with BaseForm
|
# 5. Generate Form.xml with BaseForm
|
||||||
@@ -1365,7 +1436,7 @@ def main():
|
|||||||
target_dir = os.path.join(ext_dir, "CommonPictures")
|
target_dir = os.path.join(ext_dir, "CommonPictures")
|
||||||
os.makedirs(target_dir, exist_ok=True)
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
target_file = os.path.join(target_dir, f"{pic_name}.xml")
|
target_file = os.path.join(target_dir, f"{pic_name}.xml")
|
||||||
save_text_bom(target_file, borrowed_xml)
|
write_xml_file(target_file, borrowed_xml)
|
||||||
add_to_child_objects("CommonPicture", pic_name)
|
add_to_child_objects("CommonPicture", pic_name)
|
||||||
auto_borrowed_pics.append(pic_name)
|
auto_borrowed_pics.append(pic_name)
|
||||||
borrowed_files.append(target_file)
|
borrowed_files.append(target_file)
|
||||||
@@ -1414,7 +1485,7 @@ def main():
|
|||||||
target_dir = os.path.join(ext_dir, "StyleItems")
|
target_dir = os.path.join(ext_dir, "StyleItems")
|
||||||
os.makedirs(target_dir, exist_ok=True)
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
target_file = os.path.join(target_dir, f"{style_name}.xml")
|
target_file = os.path.join(target_dir, f"{style_name}.xml")
|
||||||
save_text_bom(target_file, borrowed_xml)
|
write_xml_file(target_file, borrowed_xml)
|
||||||
add_to_child_objects("StyleItem", style_name)
|
add_to_child_objects("StyleItem", style_name)
|
||||||
borrowed_files.append(target_file)
|
borrowed_files.append(target_file)
|
||||||
info(f" Auto-borrowed: StyleItem.{style_name}")
|
info(f" Auto-borrowed: StyleItem.{style_name}")
|
||||||
@@ -1478,14 +1549,17 @@ def main():
|
|||||||
target_dir = os.path.join(ext_dir, "Enums")
|
target_dir = os.path.join(ext_dir, "Enums")
|
||||||
os.makedirs(target_dir, exist_ok=True)
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
target_file = os.path.join(target_dir, f"{enum_name}.xml")
|
target_file = os.path.join(target_dir, f"{enum_name}.xml")
|
||||||
save_text_bom(target_file, borrowed_xml)
|
write_xml_file(target_file, borrowed_xml)
|
||||||
add_to_child_objects("Enum", enum_name)
|
add_to_child_objects("Enum", enum_name)
|
||||||
borrowed_files.append(target_file)
|
borrowed_files.append(target_file)
|
||||||
info(f" Auto-borrowed: Enum.{enum_name} (with {len(ev_xmls)} EnumValue(s))")
|
info(f" Auto-borrowed: Enum.{enum_name} (with {len(ev_xmls)} EnumValue(s))")
|
||||||
else:
|
else:
|
||||||
warn(f" Enum.{enum_name} not found in source config")
|
warn(f" Enum.{enum_name} not found in source config")
|
||||||
|
|
||||||
# Extract the <Form ...> opening tag from source text
|
# Открывающий тег <Form ...> берём из исходной формы — ради её объявлений пространств
|
||||||
|
# имён, но version подставляем СВОЮ: форма обязана нести версию расширения, иначе
|
||||||
|
# платформа отвергает импорт (форма 2.13 внутри расширения 2.17). Раньше тег
|
||||||
|
# копировался целиком, и версия источника молча побеждала.
|
||||||
xml_decl = '<?xml version="1.0" encoding="UTF-8"?>'
|
xml_decl = '<?xml version="1.0" encoding="UTF-8"?>'
|
||||||
form_tag = f'<Form version="{form_version}">'
|
form_tag = f'<Form version="{form_version}">'
|
||||||
m_decl = re.search(r'^(<\?xml[^?]*\?>)', src_form_content)
|
m_decl = re.search(r'^(<\?xml[^?]*\?>)', src_form_content)
|
||||||
@@ -1493,7 +1567,15 @@ def main():
|
|||||||
xml_decl = m_decl.group(1)
|
xml_decl = m_decl.group(1)
|
||||||
m_tag = re.search(r'(<Form[^>]*>)', src_form_content)
|
m_tag = re.search(r'(<Form[^>]*>)', src_form_content)
|
||||||
if m_tag:
|
if m_tag:
|
||||||
form_tag = m_tag.group(1)
|
src_ns = re.sub(r'^<Form\s*', '', m_tag.group(1))
|
||||||
|
src_ns = re.sub(r'\s*/?>$', '', src_ns)
|
||||||
|
src_ns = re.sub(r'\s*version="[^"]*"', '', src_ns)
|
||||||
|
# 2.21 (8.5): пространство палитры. Место строгое — после lf, перед style.
|
||||||
|
if format_rank(form_version) >= 221 and 'xmlns:pal=' not in src_ns:
|
||||||
|
src_ns = src_ns.replace(
|
||||||
|
' xmlns:style=',
|
||||||
|
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
|
||||||
|
form_tag = f'<Form {src_ns} version="{form_version}">' if src_ns else f'<Form version="{form_version}">'
|
||||||
|
|
||||||
# Build output
|
# Build output
|
||||||
parts = []
|
parts = []
|
||||||
@@ -1572,7 +1654,7 @@ def main():
|
|||||||
form_xml_dir = os.path.join(form_meta_dir, form_name, "Ext")
|
form_xml_dir = os.path.join(form_meta_dir, form_name, "Ext")
|
||||||
os.makedirs(form_xml_dir, exist_ok=True)
|
os.makedirs(form_xml_dir, exist_ok=True)
|
||||||
form_xml_file = os.path.join(form_xml_dir, "Form.xml")
|
form_xml_file = os.path.join(form_xml_dir, "Form.xml")
|
||||||
save_text_bom(form_xml_file, "".join(parts))
|
write_xml_file(form_xml_file, "".join(parts))
|
||||||
info(f" Created: {form_xml_file}")
|
info(f" Created: {form_xml_file}")
|
||||||
|
|
||||||
# 6. Create empty Module.bsl — but NEVER overwrite an existing one (re-borrow must
|
# 6. Create empty Module.bsl — but NEVER overwrite an existing one (re-borrow must
|
||||||
@@ -1583,7 +1665,7 @@ def main():
|
|||||||
if os.path.isfile(module_bsl_file):
|
if os.path.isfile(module_bsl_file):
|
||||||
info(" Preserved existing Module.bsl")
|
info(" Preserved existing Module.bsl")
|
||||||
else:
|
else:
|
||||||
save_text_bom(module_bsl_file, "")
|
write_utf8_bom(module_bsl_file, "")
|
||||||
info(f" Created: {module_bsl_file}")
|
info(f" Created: {module_bsl_file}")
|
||||||
|
|
||||||
# 7. Register form in parent object ChildObjects
|
# 7. Register form in parent object ChildObjects
|
||||||
@@ -1656,7 +1738,7 @@ def main():
|
|||||||
target_dir = os.path.join(ext_dir, dir_name)
|
target_dir = os.path.join(ext_dir, dir_name)
|
||||||
os.makedirs(target_dir, exist_ok=True)
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
target_file = os.path.join(target_dir, f"{obj_name}.xml")
|
target_file = os.path.join(target_dir, f"{obj_name}.xml")
|
||||||
save_text_bom(target_file, borrowed_xml)
|
write_xml_file(target_file, borrowed_xml)
|
||||||
info(f" Created: {target_file}")
|
info(f" Created: {target_file}")
|
||||||
|
|
||||||
add_to_child_objects(type_name, obj_name)
|
add_to_child_objects(type_name, obj_name)
|
||||||
@@ -1683,7 +1765,7 @@ def main():
|
|||||||
os.makedirs(target_dir, exist_ok=True)
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
|
|
||||||
target_file = os.path.join(target_dir, f"{obj_name}.xml")
|
target_file = os.path.join(target_dir, f"{obj_name}.xml")
|
||||||
save_text_bom(target_file, borrowed_xml)
|
write_xml_file(target_file, borrowed_xml)
|
||||||
info(f" Created: {target_file}")
|
info(f" Created: {target_file}")
|
||||||
|
|
||||||
add_to_child_objects(type_name, obj_name)
|
add_to_child_objects(type_name, obj_name)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# cfe-diff v1.0 — Analyze and compare 1C configuration extension (CFE)
|
# cfe-diff v1.2 — Analyze and compare 1C configuration extension (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -49,7 +49,9 @@ $childTypeDirMap = @{
|
|||||||
"SettingsStorage"="SettingsStorages"; "FilterCriterion"="FilterCriteria"
|
"SettingsStorage"="SettingsStorages"; "FilterCriterion"="FilterCriteria"
|
||||||
"CommandGroup"="CommandGroups"; "DocumentNumerator"="DocumentNumerators"
|
"CommandGroup"="CommandGroups"; "DocumentNumerator"="DocumentNumerators"
|
||||||
"Sequence"="Sequences"; "IntegrationService"="IntegrationServices"
|
"Sequence"="Sequences"; "IntegrationService"="IntegrationServices"
|
||||||
"CommonAttribute"="CommonAttributes"
|
"CommonAttribute"="CommonAttributes"; "Style"="Styles"; "XDTOPackage"="XDTOPackages"
|
||||||
|
"WebService"="WebServices"; "HTTPService"="HTTPServices"; "WSReference"="WSReferences"
|
||||||
|
"Bot"="Bots"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Parse extension Configuration.xml ---
|
# --- Parse extension Configuration.xml ---
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cfe-diff v1.0 — Analyze and compare 1C configuration extension (CFE)
|
# cfe-diff v1.2 — Analyze and compare 1C configuration extension (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -8,6 +8,28 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
# --- Namespace maps ---
|
# --- Namespace maps ---
|
||||||
|
|
||||||
MD_NSMAP = {
|
MD_NSMAP = {
|
||||||
@@ -60,6 +82,12 @@ CHILD_TYPE_DIR_MAP = {
|
|||||||
"Sequence": "Sequences",
|
"Sequence": "Sequences",
|
||||||
"IntegrationService": "IntegrationServices",
|
"IntegrationService": "IntegrationServices",
|
||||||
"CommonAttribute": "CommonAttributes",
|
"CommonAttribute": "CommonAttributes",
|
||||||
|
"Style": "Styles",
|
||||||
|
"XDTOPackage": "XDTOPackages",
|
||||||
|
"WebService": "WebServices",
|
||||||
|
"HTTPService": "HTTPServices",
|
||||||
|
"WSReference": "WSReferences",
|
||||||
|
"Bot": "Bots",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -468,7 +496,7 @@ def main():
|
|||||||
parser.add_argument("-ExtensionPath", required=True, help="Path to extension dump root")
|
parser.add_argument("-ExtensionPath", required=True, help="Path to extension dump root")
|
||||||
parser.add_argument("-ConfigPath", required=True, help="Path to base config dump root")
|
parser.add_argument("-ConfigPath", required=True, help="Path to base config dump root")
|
||||||
parser.add_argument("-Mode", choices=["A", "B"], default="A", help="A=overview, B=transfer check")
|
parser.add_argument("-Mode", choices=["A", "B"], default="A", help="A=overview, B=transfer check")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
extension_path = args.ExtensionPath
|
extension_path = args.ExtensionPath
|
||||||
config_path = args.ConfigPath
|
config_path = args.ConfigPath
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# cfe-init v1.2 — Create 1C configuration extension scaffold (CFE)
|
# cfe-init v1.10 — Create 1C configuration extension scaffold (CFE) (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -16,6 +16,12 @@ param(
|
|||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Esc-XmlText {
|
||||||
|
param([string]$s)
|
||||||
|
# Эскейп ТЕКСТА элемента: только & < > — кавычку и апостроф платформа держит сырыми.
|
||||||
|
return $s.Replace('&','&').Replace('<','<').Replace('>','>')
|
||||||
|
}
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
# --- Default NamePrefix ---
|
# --- Default NamePrefix ---
|
||||||
@@ -121,20 +127,23 @@ $co7 = [guid]::NewGuid().ToString()
|
|||||||
# --- Synonym XML ---
|
# --- Synonym XML ---
|
||||||
$synonymXml = ""
|
$synonymXml = ""
|
||||||
if ($Synonym) {
|
if ($Synonym) {
|
||||||
$synonymXml = "`r`n`t`t`t`t<v8:item>`r`n`t`t`t`t`t<v8:lang>ru</v8:lang>`r`n`t`t`t`t`t<v8:content>$([System.Security.SecurityElement]::Escape($Synonym))</v8:content>`r`n`t`t`t`t</v8:item>`r`n`t`t`t"
|
$synonymXml = "`r`n`t`t`t`t<v8:item>`r`n`t`t`t`t`t<v8:lang>ru</v8:lang>`r`n`t`t`t`t`t<v8:content>$(Esc-XmlText ($Synonym))</v8:content>`r`n`t`t`t`t</v8:item>`r`n`t`t`t"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Optional properties ---
|
# --- Optional properties ---
|
||||||
$vendorXml = if ($Vendor) { [System.Security.SecurityElement]::Escape($Vendor) } else { "" }
|
# Элемент целиком, а не значение внутри пары: при пустом значении Конфигуратор
|
||||||
$versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version) } else { "" }
|
# пишет <Vendor/>, а не <Vendor></Vendor>.
|
||||||
|
$vendorEl = if ($Vendor) { "<Vendor>$(Esc-XmlText ($Vendor))</Vendor>" } else { "<Vendor/>" }
|
||||||
|
$versionEl = if ($Version) { "<Version>$(Esc-XmlText ($Version))</Version>" } else { "<Version/>" }
|
||||||
|
|
||||||
# --- Role name ---
|
# --- Role name ---
|
||||||
$roleName = "${NamePrefix}ОсновнаяРоль"
|
$roleName = "${NamePrefix}ОсновнаяРоль"
|
||||||
|
|
||||||
# --- DefaultRoles XML ---
|
# --- DefaultRoles XML ---
|
||||||
$defaultRolesXml = ""
|
# Элемент целиком: без роли Конфигуратор пишет <DefaultRoles/>, а не пустую пару.
|
||||||
|
$defaultRolesEl = "<DefaultRoles/>"
|
||||||
if (-not $NoRole) {
|
if (-not $NoRole) {
|
||||||
$defaultRolesXml = "`r`n`t`t`t`t<xr:Item xsi:type=`"xr:MDObjectRef`">Role.$roleName</xr:Item>`r`n`t`t`t"
|
$defaultRolesEl = "<DefaultRoles>`r`n`t`t`t`t<xr:Item xsi:type=`"xr:MDObjectRef`">Role.$roleName</xr:Item>`r`n`t`t`t</DefaultRoles>"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- ChildObjects ---
|
# --- ChildObjects ---
|
||||||
@@ -144,10 +153,32 @@ if (-not $NoRole) {
|
|||||||
}
|
}
|
||||||
$childObjectsXml += "`r`n`t`t"
|
$childObjectsXml += "`r`n`t`t"
|
||||||
|
|
||||||
|
# Объявления пространств имён — одной переменной: места эмиссии её только интерполируют.
|
||||||
|
# Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
|
||||||
|
$xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||||
|
|
||||||
|
# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209.
|
||||||
|
# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка.
|
||||||
|
function Get-FormatRank([string]$ver) {
|
||||||
|
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
|
||||||
|
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
|
||||||
|
# дописать в конец нельзя.
|
||||||
|
# Caption/ShortCaption — свойства корня из того же формата 2.21, между Version и
|
||||||
|
# DefaultLanguage (позиция снята с выгрузки расширения из базы 8.5).
|
||||||
|
$f221Captions = ""
|
||||||
|
if ((Get-FormatRank $formatVersion) -ge 221) {
|
||||||
|
$xmlnsDecl = $xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
|
||||||
|
$f221Captions = "`r`n`t`t`t<Caption/>`r`n`t`t`t<ShortCaption/>"
|
||||||
|
}
|
||||||
|
|
||||||
# --- Configuration.xml ---
|
# --- Configuration.xml ---
|
||||||
$cfgXml = @"
|
$cfgXml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$formatVersion">
|
<MetaDataObject $xmlnsDecl version="$formatVersion">
|
||||||
<Configuration uuid="$uuidCfg">
|
<Configuration uuid="$uuidCfg">
|
||||||
<InternalInfo>
|
<InternalInfo>
|
||||||
<xr:ContainedObject>
|
<xr:ContainedObject>
|
||||||
@@ -181,21 +212,21 @@ $cfgXml = @"
|
|||||||
</InternalInfo>
|
</InternalInfo>
|
||||||
<Properties>
|
<Properties>
|
||||||
<ObjectBelonging>Adopted</ObjectBelonging>
|
<ObjectBelonging>Adopted</ObjectBelonging>
|
||||||
<Name>$([System.Security.SecurityElement]::Escape($Name))</Name>
|
<Name>$(Esc-XmlText ($Name))</Name>
|
||||||
<Synonym>$synonymXml</Synonym>
|
<Synonym>$synonymXml</Synonym>
|
||||||
<Comment/>
|
<Comment/>
|
||||||
<ConfigurationExtensionPurpose>$Purpose</ConfigurationExtensionPurpose>
|
<ConfigurationExtensionPurpose>$Purpose</ConfigurationExtensionPurpose>
|
||||||
<KeepMappingToExtendedConfigurationObjectsByIDs>true</KeepMappingToExtendedConfigurationObjectsByIDs>
|
<KeepMappingToExtendedConfigurationObjectsByIDs>true</KeepMappingToExtendedConfigurationObjectsByIDs>
|
||||||
<NamePrefix>$([System.Security.SecurityElement]::Escape($NamePrefix))</NamePrefix>
|
<NamePrefix>$(Esc-XmlText ($NamePrefix))</NamePrefix>
|
||||||
<ConfigurationExtensionCompatibilityMode>$CompatibilityMode</ConfigurationExtensionCompatibilityMode>
|
<ConfigurationExtensionCompatibilityMode>$CompatibilityMode</ConfigurationExtensionCompatibilityMode>
|
||||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||||
<UsePurposes>
|
<UsePurposes>
|
||||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||||
</UsePurposes>
|
</UsePurposes>
|
||||||
<ScriptVariant>Russian</ScriptVariant>
|
<ScriptVariant>Russian</ScriptVariant>
|
||||||
<DefaultRoles>$defaultRolesXml</DefaultRoles>
|
$defaultRolesEl
|
||||||
<Vendor>$vendorXml</Vendor>
|
$vendorEl
|
||||||
<Version>$versionXml</Version>
|
$versionEl$f221Captions
|
||||||
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
||||||
<BriefInformation/>
|
<BriefInformation/>
|
||||||
<DetailedInformation/>
|
<DetailedInformation/>
|
||||||
@@ -212,7 +243,7 @@ $cfgXml = @"
|
|||||||
# --- Languages/Русский.xml (adopted format) ---
|
# --- Languages/Русский.xml (adopted format) ---
|
||||||
$langXml = @"
|
$langXml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$formatVersion">
|
<MetaDataObject $xmlnsDecl version="$formatVersion">
|
||||||
<Language uuid="$uuidLang">
|
<Language uuid="$uuidLang">
|
||||||
<InternalInfo/>
|
<InternalInfo/>
|
||||||
<Properties>
|
<Properties>
|
||||||
@@ -229,10 +260,10 @@ $langXml = @"
|
|||||||
# --- Role XML ---
|
# --- Role XML ---
|
||||||
$roleXml = @"
|
$roleXml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$formatVersion">
|
<MetaDataObject $xmlnsDecl version="$formatVersion">
|
||||||
<Role uuid="$uuidRole">
|
<Role uuid="$uuidRole">
|
||||||
<Properties>
|
<Properties>
|
||||||
<Name>$([System.Security.SecurityElement]::Escape($roleName))</Name>
|
<Name>$(Esc-XmlText ($roleName))</Name>
|
||||||
<Synonym/>
|
<Synonym/>
|
||||||
<Comment/>
|
<Comment/>
|
||||||
</Properties>
|
</Properties>
|
||||||
@@ -252,9 +283,18 @@ if (-not (Test-Path $langDir)) {
|
|||||||
# --- Write files with UTF-8 BOM ---
|
# --- Write files with UTF-8 BOM ---
|
||||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
|
||||||
[System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc)
|
# XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
|
||||||
|
#
|
||||||
|
# Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
|
||||||
|
# копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||||
|
function Write-XmlFile([string]$path, [string]$text, $encoding) {
|
||||||
|
$t = ($text -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
|
[System.IO.File]::WriteAllText($path, $t.TrimEnd("`r", "`n"), $encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-XmlFile $cfgFile $cfgXml $enc
|
||||||
$langFile = Join-Path $langDir "Русский.xml"
|
$langFile = Join-Path $langDir "Русский.xml"
|
||||||
[System.IO.File]::WriteAllText($langFile, $langXml, $enc)
|
Write-XmlFile $langFile $langXml $enc
|
||||||
|
|
||||||
# --- Role ---
|
# --- Role ---
|
||||||
if (-not $NoRole) {
|
if (-not $NoRole) {
|
||||||
@@ -263,7 +303,7 @@ if (-not $NoRole) {
|
|||||||
New-Item -ItemType Directory -Path $roleDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $roleDir -Force | Out-Null
|
||||||
}
|
}
|
||||||
$roleFile = Join-Path $roleDir "$roleName.xml"
|
$roleFile = Join-Path $roleDir "$roleName.xml"
|
||||||
[System.IO.File]::WriteAllText($roleFile, $roleXml, $enc)
|
Write-XmlFile $roleFile $roleXml $enc
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Output ---
|
# --- Output ---
|
||||||
|
|||||||
@@ -1,20 +1,62 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cfe-init v1.2 — Create 1C configuration extension scaffold (CFE)
|
# cfe-init v1.10 — Create 1C configuration extension scaffold (CFE) (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
"""Generates minimal XML source files for a 1C configuration extension."""
|
"""Generates minimal XML source files for a 1C configuration extension."""
|
||||||
import sys, os, argparse, uuid
|
import sys, os, re, argparse, uuid
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
def esc_xml(s):
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
return s.replace('&','&').replace('<','<').replace('>','>').replace('"','"')
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
def esc_xml_text(s):
|
||||||
|
# Эскейп ТЕКСТА элемента: только & < > — кавычку и апостроф платформа держит сырыми.
|
||||||
|
return s.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||||
|
|
||||||
def new_uuid():
|
def new_uuid():
|
||||||
return str(uuid.uuid4())
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
def write_utf8_bom(path, content):
|
def write_utf8_bom(path, content):
|
||||||
|
# newline='' — без трансляции: иначе текстовый режим Python дал бы CRLF на Windows
|
||||||
|
# и LF на macOS, то есть вывод навыка зависел бы от ОС.
|
||||||
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
def write_xml_file(path, content):
|
||||||
|
"""XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
|
||||||
|
|
||||||
|
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
|
||||||
|
копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||||
|
"""
|
||||||
|
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
|
||||||
|
write_utf8_bom(path, text)
|
||||||
|
|
||||||
|
|
||||||
|
def format_rank(ver):
|
||||||
|
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||||
|
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||||
|
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
@@ -29,7 +71,7 @@ def main():
|
|||||||
parser.add_argument('-CompatibilityMode', dest='CompatibilityMode', default='Version8_3_24')
|
parser.add_argument('-CompatibilityMode', dest='CompatibilityMode', default='Version8_3_24')
|
||||||
parser.add_argument('-ConfigPath', dest='ConfigPath', default=None)
|
parser.add_argument('-ConfigPath', dest='ConfigPath', default=None)
|
||||||
parser.add_argument('-NoRole', dest='NoRole', action='store_true')
|
parser.add_argument('-NoRole', dest='NoRole', action='store_true')
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
name = args.Name
|
name = args.Name
|
||||||
synonym = args.Synonym if args.Synonym else name
|
synonym = args.Synonym if args.Synonym else name
|
||||||
@@ -126,18 +168,23 @@ def main():
|
|||||||
# --- Synonym XML ---
|
# --- Synonym XML ---
|
||||||
synonym_xml = ""
|
synonym_xml = ""
|
||||||
if synonym:
|
if synonym:
|
||||||
synonym_xml = f"\r\n\t\t\t\t<v8:item>\r\n\t\t\t\t\t<v8:lang>ru</v8:lang>\r\n\t\t\t\t\t<v8:content>{esc_xml(synonym)}</v8:content>\r\n\t\t\t\t</v8:item>\r\n\t\t\t"
|
synonym_xml = f"\r\n\t\t\t\t<v8:item>\r\n\t\t\t\t\t<v8:lang>ru</v8:lang>\r\n\t\t\t\t\t<v8:content>{esc_xml_text(synonym)}</v8:content>\r\n\t\t\t\t</v8:item>\r\n\t\t\t"
|
||||||
|
|
||||||
vendor_xml = esc_xml(vendor) if vendor else ""
|
# Элемент целиком, а не значение внутри пары: при пустом значении Конфигуратор
|
||||||
version_xml = esc_xml(version) if version else ""
|
# пишет <Vendor/>, а не <Vendor></Vendor>.
|
||||||
|
vendor_el = f"<Vendor>{esc_xml_text(vendor)}</Vendor>" if vendor else "<Vendor/>"
|
||||||
|
version_el = f"<Version>{esc_xml_text(version)}</Version>" if version else "<Version/>"
|
||||||
|
|
||||||
# --- Role name ---
|
# --- Role name ---
|
||||||
role_name = f"{name_prefix}ОсновнаяРоль"
|
role_name = f"{name_prefix}ОсновнаяРоль"
|
||||||
|
|
||||||
# --- DefaultRoles XML ---
|
# --- DefaultRoles XML ---
|
||||||
default_roles_xml = ""
|
# Элемент целиком: без роли Конфигуратор пишет <DefaultRoles/>, а не пустую пару.
|
||||||
|
default_roles_el = "<DefaultRoles/>"
|
||||||
if not args.NoRole:
|
if not args.NoRole:
|
||||||
default_roles_xml = f'\r\n\t\t\t\t<xr:Item xsi:type="xr:MDObjectRef">Role.{role_name}</xr:Item>\r\n\t\t\t'
|
default_roles_el = ('<DefaultRoles>\r\n\t\t\t\t'
|
||||||
|
f'<xr:Item xsi:type="xr:MDObjectRef">Role.{role_name}</xr:Item>'
|
||||||
|
'\r\n\t\t\t</DefaultRoles>')
|
||||||
|
|
||||||
# --- ChildObjects ---
|
# --- ChildObjects ---
|
||||||
child_objects_xml = f"\r\n\t\t\t<Language>Русский</Language>"
|
child_objects_xml = f"\r\n\t\t\t<Language>Русский</Language>"
|
||||||
@@ -156,6 +203,40 @@ def main():
|
|||||||
]
|
]
|
||||||
|
|
||||||
contained_objects = ""
|
contained_objects = ""
|
||||||
|
|
||||||
|
# Объявления пространств имён — одной переменной: места эмиссии её только подставляют.
|
||||||
|
# Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
|
||||||
|
xmlns_decl = (
|
||||||
|
'xmlns="http://v8.1c.ru/8.3/MDClasses"'
|
||||||
|
' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"'
|
||||||
|
' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"'
|
||||||
|
' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"'
|
||||||
|
' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"'
|
||||||
|
' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"'
|
||||||
|
' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
|
||||||
|
' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"'
|
||||||
|
' xmlns:v8="http://v8.1c.ru/8.1/data/core"'
|
||||||
|
' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"'
|
||||||
|
' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"'
|
||||||
|
' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"'
|
||||||
|
' xmlns:xen="http://v8.1c.ru/8.3/xcf/enums"'
|
||||||
|
' xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef"'
|
||||||
|
' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
|
||||||
|
' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
||||||
|
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
|
||||||
|
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
|
||||||
|
# дописать в конец нельзя.
|
||||||
|
# Caption/ShortCaption — свойства корня из того же формата 2.21, между Version и
|
||||||
|
# DefaultLanguage (позиция снята с выгрузки расширения из базы 8.5).
|
||||||
|
f221_captions = ""
|
||||||
|
if format_rank(format_version) >= 221:
|
||||||
|
xmlns_decl = xmlns_decl.replace(
|
||||||
|
' xmlns:style=',
|
||||||
|
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
|
||||||
|
f221_captions = "\r\n\t\t\t<Caption/>\r\n\t\t\t<ShortCaption/>"
|
||||||
for i in range(7):
|
for i in range(7):
|
||||||
contained_objects += f"""\t\t\t<xr:ContainedObject>
|
contained_objects += f"""\t\t\t<xr:ContainedObject>
|
||||||
\t\t\t\t<xr:ClassId>{class_ids[i]}</xr:ClassId>
|
\t\t\t\t<xr:ClassId>{class_ids[i]}</xr:ClassId>
|
||||||
@@ -163,27 +244,27 @@ def main():
|
|||||||
\t\t\t</xr:ContainedObject>\n"""
|
\t\t\t</xr:ContainedObject>\n"""
|
||||||
|
|
||||||
cfg_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
cfg_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{format_version}">
|
<MetaDataObject {xmlns_decl} version="{format_version}">
|
||||||
\t<Configuration uuid="{uuid_cfg}">
|
\t<Configuration uuid="{uuid_cfg}">
|
||||||
\t\t<InternalInfo>
|
\t\t<InternalInfo>
|
||||||
{contained_objects}\t\t</InternalInfo>
|
{contained_objects}\t\t</InternalInfo>
|
||||||
\t\t<Properties>
|
\t\t<Properties>
|
||||||
\t\t\t<ObjectBelonging>Adopted</ObjectBelonging>
|
\t\t\t<ObjectBelonging>Adopted</ObjectBelonging>
|
||||||
\t\t\t<Name>{esc_xml(name)}</Name>
|
\t\t\t<Name>{esc_xml_text(name)}</Name>
|
||||||
\t\t\t<Synonym>{synonym_xml}</Synonym>
|
\t\t\t<Synonym>{synonym_xml}</Synonym>
|
||||||
\t\t\t<Comment/>
|
\t\t\t<Comment/>
|
||||||
\t\t\t<ConfigurationExtensionPurpose>{purpose}</ConfigurationExtensionPurpose>
|
\t\t\t<ConfigurationExtensionPurpose>{purpose}</ConfigurationExtensionPurpose>
|
||||||
\t\t\t<KeepMappingToExtendedConfigurationObjectsByIDs>true</KeepMappingToExtendedConfigurationObjectsByIDs>
|
\t\t\t<KeepMappingToExtendedConfigurationObjectsByIDs>true</KeepMappingToExtendedConfigurationObjectsByIDs>
|
||||||
\t\t\t<NamePrefix>{esc_xml(name_prefix)}</NamePrefix>
|
\t\t\t<NamePrefix>{esc_xml_text(name_prefix)}</NamePrefix>
|
||||||
\t\t\t<ConfigurationExtensionCompatibilityMode>{compat}</ConfigurationExtensionCompatibilityMode>
|
\t\t\t<ConfigurationExtensionCompatibilityMode>{compat}</ConfigurationExtensionCompatibilityMode>
|
||||||
\t\t\t<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
\t\t\t<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||||
\t\t\t<UsePurposes>
|
\t\t\t<UsePurposes>
|
||||||
\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||||
\t\t\t</UsePurposes>
|
\t\t\t</UsePurposes>
|
||||||
\t\t\t<ScriptVariant>Russian</ScriptVariant>
|
\t\t\t<ScriptVariant>Russian</ScriptVariant>
|
||||||
\t\t\t<DefaultRoles>{default_roles_xml}</DefaultRoles>
|
\t\t\t{default_roles_el}
|
||||||
\t\t\t<Vendor>{vendor_xml}</Vendor>
|
\t\t\t{vendor_el}
|
||||||
\t\t\t<Version>{version_xml}</Version>
|
\t\t\t{version_el}{f221_captions}
|
||||||
\t\t\t<DefaultLanguage>Language.Русский</DefaultLanguage>
|
\t\t\t<DefaultLanguage>Language.Русский</DefaultLanguage>
|
||||||
\t\t\t<BriefInformation/>
|
\t\t\t<BriefInformation/>
|
||||||
\t\t\t<DetailedInformation/>
|
\t\t\t<DetailedInformation/>
|
||||||
@@ -198,7 +279,7 @@ def main():
|
|||||||
|
|
||||||
# --- Languages/Русский.xml (adopted format) ---
|
# --- Languages/Русский.xml (adopted format) ---
|
||||||
lang_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
lang_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{format_version}">
|
<MetaDataObject {xmlns_decl} version="{format_version}">
|
||||||
\t<Language uuid="{uuid_lang}">
|
\t<Language uuid="{uuid_lang}">
|
||||||
\t\t<InternalInfo/>
|
\t\t<InternalInfo/>
|
||||||
\t\t<Properties>
|
\t\t<Properties>
|
||||||
@@ -213,10 +294,10 @@ def main():
|
|||||||
|
|
||||||
# --- Role XML ---
|
# --- Role XML ---
|
||||||
role_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
role_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{format_version}">
|
<MetaDataObject {xmlns_decl} version="{format_version}">
|
||||||
\t<Role uuid="{uuid_role}">
|
\t<Role uuid="{uuid_role}">
|
||||||
\t\t<Properties>
|
\t\t<Properties>
|
||||||
\t\t\t<Name>{esc_xml(role_name)}</Name>
|
\t\t\t<Name>{esc_xml_text(role_name)}</Name>
|
||||||
\t\t\t<Synonym/>
|
\t\t\t<Synonym/>
|
||||||
\t\t\t<Comment/>
|
\t\t\t<Comment/>
|
||||||
\t\t</Properties>
|
\t\t</Properties>
|
||||||
@@ -229,9 +310,9 @@ def main():
|
|||||||
os.makedirs(lang_dir, exist_ok=True)
|
os.makedirs(lang_dir, exist_ok=True)
|
||||||
|
|
||||||
# --- Write files ---
|
# --- Write files ---
|
||||||
write_utf8_bom(cfg_file, cfg_xml)
|
write_xml_file(cfg_file, cfg_xml)
|
||||||
lang_file = os.path.join(lang_dir, "Русский.xml")
|
lang_file = os.path.join(lang_dir, "Русский.xml")
|
||||||
write_utf8_bom(lang_file, lang_xml)
|
write_xml_file(lang_file, lang_xml)
|
||||||
|
|
||||||
# --- Role ---
|
# --- Role ---
|
||||||
role_file = None
|
role_file = None
|
||||||
@@ -239,7 +320,7 @@ def main():
|
|||||||
role_dir = os.path.join(output_dir, "Roles")
|
role_dir = os.path.join(output_dir, "Roles")
|
||||||
os.makedirs(role_dir, exist_ok=True)
|
os.makedirs(role_dir, exist_ok=True)
|
||||||
role_file = os.path.join(role_dir, f"{role_name}.xml")
|
role_file = os.path.join(role_dir, f"{role_name}.xml")
|
||||||
write_utf8_bom(role_file, role_xml)
|
write_xml_file(role_file, role_xml)
|
||||||
|
|
||||||
# --- Output ---
|
# --- Output ---
|
||||||
print(f"[OK] Создано расширение: {name}")
|
print(f"[OK] Создано расширение: {name}")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# cfe-patch-method v2.5 — Source-aware method interceptor for 1C extension (CFE)
|
# cfe-patch-method v2.7 — Source-aware method interceptor for 1C extension (CFE) (+прощающий ввод: имя каталога наравне с именем типа)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cfe-patch-method v2.5 — Source-aware method interceptor for 1C extension (CFE)
|
# cfe-patch-method v2.7 — Source-aware method interceptor for 1C extension (CFE) (+прощающий ввод: имя каталога наравне с именем типа)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -10,6 +10,28 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
TYPE_DIR_MAP = {
|
TYPE_DIR_MAP = {
|
||||||
"Catalog": "Catalogs", "Document": "Documents", "Enum": "Enums",
|
"Catalog": "Catalogs", "Document": "Documents", "Enum": "Enums",
|
||||||
"CommonModule": "CommonModules", "Report": "Reports", "DataProcessor": "DataProcessors",
|
"CommonModule": "CommonModules", "Report": "Reports", "DataProcessor": "DataProcessors",
|
||||||
@@ -19,6 +41,24 @@ TYPE_DIR_MAP = {
|
|||||||
"BusinessProcess": "BusinessProcesses", "Task": "Tasks",
|
"BusinessProcess": "BusinessProcesses", "Task": "Tasks",
|
||||||
"InformationRegister": "InformationRegisters", "AccumulationRegister": "AccumulationRegisters",
|
"InformationRegister": "InformationRegisters", "AccumulationRegister": "AccumulationRegisters",
|
||||||
"AccountingRegister": "AccountingRegisters", "CalculationRegister": "CalculationRegisters",
|
"AccountingRegister": "AccountingRegisters", "CalculationRegister": "CalculationRegisters",
|
||||||
|
# Прощающий ввод: имя каталога принимается наравне с именем типа (Catalogs.X ≡ Catalog.X) —
|
||||||
|
# PS1-порт так умел с самого начала, PY отставал.
|
||||||
|
"Catalogs": "Catalogs",
|
||||||
|
"Documents": "Documents",
|
||||||
|
"Enums": "Enums",
|
||||||
|
"CommonModules": "CommonModules",
|
||||||
|
"Reports": "Reports",
|
||||||
|
"DataProcessors": "DataProcessors",
|
||||||
|
"ExchangePlans": "ExchangePlans",
|
||||||
|
"ChartsOfAccounts": "ChartsOfAccounts",
|
||||||
|
"ChartsOfCharacteristicTypes": "ChartsOfCharacteristicTypes",
|
||||||
|
"ChartsOfCalculationTypes": "ChartsOfCalculationTypes",
|
||||||
|
"BusinessProcesses": "BusinessProcesses",
|
||||||
|
"Tasks": "Tasks",
|
||||||
|
"InformationRegisters": "InformationRegisters",
|
||||||
|
"AccumulationRegisters": "AccumulationRegisters",
|
||||||
|
"AccountingRegisters": "AccountingRegisters",
|
||||||
|
"CalculationRegisters": "CalculationRegisters",
|
||||||
}
|
}
|
||||||
# accept plural forms too
|
# accept plural forms too
|
||||||
for _v in list(TYPE_DIR_MAP.values()):
|
for _v in list(TYPE_DIR_MAP.values()):
|
||||||
@@ -539,7 +579,7 @@ def main():
|
|||||||
choices=["", "Before", "After", "Instead", "ModificationAndControl"])
|
choices=["", "Before", "After", "Instead", "ModificationAndControl"])
|
||||||
parser.add_argument("-Check", action="store_true")
|
parser.add_argument("-Check", action="store_true")
|
||||||
parser.add_argument("-Actualize", action="store_true")
|
parser.add_argument("-Actualize", action="store_true")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
extension_path = args.ExtensionPath
|
extension_path = args.ExtensionPath
|
||||||
config_path = args.ConfigPath
|
config_path = args.ConfigPath
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# cfe-validate v1.5 — Validate 1C configuration extension structure (CFE)
|
# cfe-validate v1.7 — Validate 1C configuration extension structure (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -108,7 +108,7 @@ $validClassIds = @(
|
|||||||
$childObjectTypes = @(
|
$childObjectTypes = @(
|
||||||
"Language","Subsystem","StyleItem","Style",
|
"Language","Subsystem","StyleItem","Style",
|
||||||
"CommonPicture","SessionParameter","Role","CommonTemplate",
|
"CommonPicture","SessionParameter","Role","CommonTemplate",
|
||||||
"FilterCriterion","CommonModule","CommonAttribute","ExchangePlan",
|
"FilterCriterion","CommonModule","Bot","CommonAttribute","ExchangePlan",
|
||||||
"XDTOPackage","WebService","HTTPService","WSReference",
|
"XDTOPackage","WebService","HTTPService","WSReference",
|
||||||
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
|
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
|
||||||
"FunctionalOptionsParameter","DefinedType","CommonCommand","CommandGroup",
|
"FunctionalOptionsParameter","DefinedType","CommonCommand","CommandGroup",
|
||||||
@@ -122,7 +122,7 @@ $childObjectTypes = @(
|
|||||||
|
|
||||||
# Type -> directory mapping
|
# Type -> directory mapping
|
||||||
$childTypeDirMap = @{
|
$childTypeDirMap = @{
|
||||||
"Language"="Languages"; "Subsystem"="Subsystems"; "StyleItem"="StyleItems"; "Style"="Styles"
|
"Language"="Languages"; "Subsystem"="Subsystems"; "StyleItem"="StyleItems"; "Style"="Styles"; "Bot"="Bots"
|
||||||
"CommonPicture"="CommonPictures"; "SessionParameter"="SessionParameters"; "Role"="Roles"
|
"CommonPicture"="CommonPictures"; "SessionParameter"="SessionParameters"; "Role"="Roles"
|
||||||
"CommonTemplate"="CommonTemplates"; "FilterCriterion"="FilterCriteria"; "CommonModule"="CommonModules"
|
"CommonTemplate"="CommonTemplates"; "FilterCriterion"="FilterCriteria"; "CommonModule"="CommonModules"
|
||||||
"CommonAttribute"="CommonAttributes"; "ExchangePlan"="ExchangePlans"; "XDTOPackage"="XDTOPackages"
|
"CommonAttribute"="CommonAttributes"; "ExchangePlan"="ExchangePlans"; "XDTOPackage"="XDTOPackages"
|
||||||
|
|||||||
@@ -1,10 +1,32 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# cfe-validate v1.5 — Validate 1C configuration extension XML structure (CFE)
|
# cfe-validate v1.7 — Validate 1C configuration extension XML structure (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
"""Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects."""
|
"""Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects."""
|
||||||
import sys, os, argparse, re
|
import sys, os, argparse, re
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
NS = {
|
NS = {
|
||||||
'md': 'http://v8.1c.ru/8.3/MDClasses',
|
'md': 'http://v8.1c.ru/8.3/MDClasses',
|
||||||
'v8': 'http://v8.1c.ru/8.1/data/core',
|
'v8': 'http://v8.1c.ru/8.1/data/core',
|
||||||
@@ -37,7 +59,7 @@ VALID_CLASS_IDS = [
|
|||||||
CHILD_OBJECT_TYPES = [
|
CHILD_OBJECT_TYPES = [
|
||||||
'Language', 'Subsystem', 'StyleItem', 'Style',
|
'Language', 'Subsystem', 'StyleItem', 'Style',
|
||||||
'CommonPicture', 'SessionParameter', 'Role', 'CommonTemplate',
|
'CommonPicture', 'SessionParameter', 'Role', 'CommonTemplate',
|
||||||
'FilterCriterion', 'CommonModule', 'CommonAttribute', 'ExchangePlan',
|
'FilterCriterion', 'CommonModule', 'Bot', 'CommonAttribute', 'ExchangePlan',
|
||||||
'XDTOPackage', 'WebService', 'HTTPService', 'WSReference',
|
'XDTOPackage', 'WebService', 'HTTPService', 'WSReference',
|
||||||
'EventSubscription', 'ScheduledJob', 'SettingsStorage', 'FunctionalOption',
|
'EventSubscription', 'ScheduledJob', 'SettingsStorage', 'FunctionalOption',
|
||||||
'FunctionalOptionsParameter', 'DefinedType', 'CommonCommand', 'CommandGroup',
|
'FunctionalOptionsParameter', 'DefinedType', 'CommonCommand', 'CommandGroup',
|
||||||
@@ -54,6 +76,7 @@ CHILD_TYPE_DIR_MAP = {
|
|||||||
'Language': 'Languages', 'Subsystem': 'Subsystems', 'StyleItem': 'StyleItems', 'Style': 'Styles',
|
'Language': 'Languages', 'Subsystem': 'Subsystems', 'StyleItem': 'StyleItems', 'Style': 'Styles',
|
||||||
'CommonPicture': 'CommonPictures', 'SessionParameter': 'SessionParameters', 'Role': 'Roles',
|
'CommonPicture': 'CommonPictures', 'SessionParameter': 'SessionParameters', 'Role': 'Roles',
|
||||||
'CommonTemplate': 'CommonTemplates', 'FilterCriterion': 'FilterCriteria', 'CommonModule': 'CommonModules',
|
'CommonTemplate': 'CommonTemplates', 'FilterCriterion': 'FilterCriteria', 'CommonModule': 'CommonModules',
|
||||||
|
'Bot': 'Bots',
|
||||||
'CommonAttribute': 'CommonAttributes', 'ExchangePlan': 'ExchangePlans', 'XDTOPackage': 'XDTOPackages',
|
'CommonAttribute': 'CommonAttributes', 'ExchangePlan': 'ExchangePlans', 'XDTOPackage': 'XDTOPackages',
|
||||||
'WebService': 'WebServices', 'HTTPService': 'HTTPServices', 'WSReference': 'WSReferences',
|
'WebService': 'WebServices', 'HTTPService': 'HTTPServices', 'WSReference': 'WSReferences',
|
||||||
'EventSubscription': 'EventSubscriptions', 'ScheduledJob': 'ScheduledJobs',
|
'EventSubscription': 'EventSubscriptions', 'ScheduledJob': 'ScheduledJobs',
|
||||||
@@ -154,7 +177,7 @@ def main():
|
|||||||
parser.add_argument('-Detailed', action='store_true')
|
parser.add_argument('-Detailed', action='store_true')
|
||||||
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
|
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
|
||||||
parser.add_argument('-OutFile', dest='OutFile', default='')
|
parser.add_argument('-OutFile', dest='OutFile', default='')
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
extension_path = args.ExtensionPath
|
extension_path = args.ExtensionPath
|
||||||
max_errors = args.MaxErrors
|
max_errors = args.MaxErrors
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# db-create v1.10 — Create 1C information base
|
# db-create v1.11 — Create 1C information base
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-create v1.10 — Create 1C information base
|
# db-create v1.11 — Create 1C information base
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -14,6 +14,28 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path():
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
@@ -355,7 +377,7 @@ def main():
|
|||||||
help="Extra ibcmd arguments in --key=value form")
|
help="Extra ibcmd arguments in --key=value form")
|
||||||
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
||||||
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
||||||
args = parser.parse_args(argv)
|
args = ci_parse_args(parser, argv)
|
||||||
|
|
||||||
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# db-dump-cf v1.12 — Dump 1C configuration to CF file
|
# db-dump-cf v1.13 — Dump 1C configuration to CF file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-dump-cf v1.12 — Dump 1C configuration to CF file
|
# db-dump-cf v1.13 — Dump 1C configuration to CF file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -14,6 +14,28 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path():
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
@@ -375,7 +397,7 @@ def main():
|
|||||||
help="Extra ibcmd arguments in --key=value form")
|
help="Extra ibcmd arguments in --key=value form")
|
||||||
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
||||||
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
||||||
args = parser.parse_args(argv)
|
args = ci_parse_args(parser, argv)
|
||||||
|
|
||||||
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# db-dump-dt v1.11 — Dump 1C information base to DT file
|
# db-dump-dt v1.12 — Dump 1C information base to DT file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-dump-dt v1.11 — Dump 1C information base to DT file
|
# db-dump-dt v1.12 — Dump 1C information base to DT file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -14,6 +14,28 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path():
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
@@ -373,7 +395,7 @@ def main():
|
|||||||
help="Extra ibcmd arguments in --key=value form")
|
help="Extra ibcmd arguments in --key=value form")
|
||||||
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
||||||
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
||||||
args = parser.parse_args(argv)
|
args = ci_parse_args(parser, argv)
|
||||||
|
|
||||||
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# db-dump-xml v1.14 — Dump 1C configuration to XML files
|
# db-dump-xml v1.15 — Dump 1C configuration to XML files
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-dump-xml v1.14 — Dump 1C configuration to XML files
|
# db-dump-xml v1.15 — Dump 1C configuration to XML files
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -14,6 +14,28 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path():
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
@@ -388,7 +410,7 @@ def main():
|
|||||||
help="Extra ibcmd arguments in --key=value form")
|
help="Extra ibcmd arguments in --key=value form")
|
||||||
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
||||||
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
||||||
args = parser.parse_args(argv)
|
args = ci_parse_args(parser, argv)
|
||||||
|
|
||||||
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# db-load-cf v1.13 — Load 1C configuration from CF file
|
# db-load-cf v1.14 — Load 1C configuration from CF file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-load-cf v1.13 — Load 1C configuration from CF file
|
# db-load-cf v1.14 — Load 1C configuration from CF file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -14,6 +14,28 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path():
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
@@ -393,7 +415,7 @@ def main():
|
|||||||
help="Extra ibcmd arguments in --key=value form")
|
help="Extra ibcmd arguments in --key=value form")
|
||||||
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
||||||
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
||||||
args = parser.parse_args(argv)
|
args = ci_parse_args(parser, argv)
|
||||||
|
|
||||||
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# db-load-dt v1.12 — Load 1C information base from DT file
|
# db-load-dt v1.13 — Load 1C information base from DT file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-load-dt v1.12 — Load 1C information base from DT file
|
# db-load-dt v1.13 — Load 1C information base from DT file
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -14,6 +14,28 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path():
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
@@ -393,7 +415,7 @@ def main():
|
|||||||
help="Extra ibcmd arguments in --key=value form")
|
help="Extra ibcmd arguments in --key=value form")
|
||||||
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
||||||
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
||||||
args = parser.parse_args(argv)
|
args = ci_parse_args(parser, argv)
|
||||||
|
|
||||||
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# db-load-git v1.18 — Load Git changes into 1C database
|
# db-load-git v1.19 — Load Git changes into 1C database
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-load-git v1.18 — Load Git changes into 1C database
|
# db-load-git v1.19 — Load Git changes into 1C database
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -14,6 +14,28 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path():
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
@@ -430,7 +452,7 @@ def main():
|
|||||||
help="Extra ibcmd arguments in --key=value form")
|
help="Extra ibcmd arguments in --key=value form")
|
||||||
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
||||||
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
||||||
args = parser.parse_args(argv)
|
args = ci_parse_args(parser, argv)
|
||||||
|
|
||||||
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# db-load-xml v1.19 — Load 1C configuration from XML files
|
# db-load-xml v1.20 — Load 1C configuration from XML files
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-load-xml v1.19 — Load 1C configuration from XML files
|
# db-load-xml v1.20 — Load 1C configuration from XML files
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -14,6 +14,28 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path():
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
@@ -413,7 +435,7 @@ def main():
|
|||||||
help="Extra ibcmd arguments in --key=value form")
|
help="Extra ibcmd arguments in --key=value form")
|
||||||
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
||||||
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
||||||
args = parser.parse_args(argv)
|
args = ci_parse_args(parser, argv)
|
||||||
|
|
||||||
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# db-run v1.7 — Launch 1C:Enterprise
|
# db-run v1.8 — Launch 1C:Enterprise
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-run v1.7 — Launch 1C:Enterprise
|
# db-run v1.8 — Launch 1C:Enterprise
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -11,6 +11,28 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path():
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
@@ -281,7 +303,7 @@ def main():
|
|||||||
help="Extra ibcmd arguments in --key=value form")
|
help="Extra ibcmd arguments in --key=value form")
|
||||||
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
||||||
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
||||||
args = parser.parse_args(argv)
|
args = ci_parse_args(parser, argv)
|
||||||
|
|
||||||
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# db-update v1.13 — Update 1C database configuration
|
# db-update v1.14 — Update 1C database configuration
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# db-update v1.13 — Update 1C database configuration
|
# db-update v1.14 — Update 1C database configuration
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -14,6 +14,28 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path():
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
@@ -395,7 +417,7 @@ def main():
|
|||||||
help="Extra ibcmd arguments in --key=value form")
|
help="Extra ibcmd arguments in --key=value form")
|
||||||
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
||||||
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
||||||
args = parser.parse_args(argv)
|
args = ci_parse_args(parser, argv)
|
||||||
|
|
||||||
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# epf-build v1.12 — Build external data processor or report (EPF/ERF) from XML sources
|
# epf-build v1.13 — Build external data processor or report (EPF/ERF) from XML sources
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# epf-build v1.12 — Build external data processor or report (EPF/ERF) from XML sources
|
# epf-build v1.13 — Build external data processor or report (EPF/ERF) from XML sources
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -14,6 +14,28 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path():
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
@@ -374,7 +396,7 @@ def main():
|
|||||||
help="Extra ibcmd arguments in --key=value form")
|
help="Extra ibcmd arguments in --key=value form")
|
||||||
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
||||||
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
||||||
args = parser.parse_args(argv)
|
args = ci_parse_args(parser, argv)
|
||||||
|
|
||||||
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# epf-dump v1.11 — Dump external data processor or report (EPF/ERF) to XML sources
|
# epf-dump v1.12 — Dump external data processor or report (EPF/ERF) to XML sources
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||||
<#
|
<#
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# epf-dump v1.11 — Dump external data processor or report (EPF/ERF) to XML sources
|
# epf-dump v1.12 — Dump external data processor or report (EPF/ERF) to XML sources
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -14,6 +14,28 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _find_project_v8path():
|
def _find_project_v8path():
|
||||||
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
"""Walk up from CWD to find .v8-project.json and read its v8path."""
|
||||||
@@ -380,7 +402,7 @@ def main():
|
|||||||
help="Extra ibcmd arguments in --key=value form")
|
help="Extra ibcmd arguments in --key=value form")
|
||||||
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
known_opts = {s.lower() for a in parser._actions for s in a.option_strings}
|
||||||
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
argv, v8_extra, ibcmd_extra = extract_extra_args(sys.argv[1:], known_opts)
|
||||||
args = parser.parse_args(argv)
|
args = ci_parse_args(parser, argv)
|
||||||
|
|
||||||
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
args.V8Path = clean_path(args.V8Path, "-V8Path")
|
||||||
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
args.InfoBasePath = clean_path(args.InfoBasePath, "-InfoBasePath")
|
||||||
|
|||||||
@@ -18,19 +18,20 @@ allowed-tools:
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/epf-init <Name> [Synonym] [SrcDir]
|
/epf-init <Name> [Synonym] [SrcDir] [FormatVersion]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Параметр | Обязательный | По умолчанию | Описание |
|
| Параметр | Обязательный | По умолчанию | Описание |
|
||||||
|-----------|:------------:|--------------|-------------------------------------|
|
|---------------|:------------:|--------------|------------------------------------------------|
|
||||||
| Name | да | — | Имя обработки (латиница/кириллица) |
|
| Name | да | — | Имя обработки (латиница/кириллица) |
|
||||||
| Synonym | нет | = Name | Синоним (отображаемое имя) |
|
| Synonym | нет | = Name | Синоним (отображаемое имя) |
|
||||||
| SrcDir | нет | `src` | Каталог исходников относительно CWD |
|
| SrcDir | нет | `src` | Каталог исходников относительно CWD |
|
||||||
|
| FormatVersion | нет | `2.17` | Версия формата: 2.20 — платформа 8.3.27, 2.21 — 8.5. Дефолт открывается любой платформой |
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"]
|
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<2.17|2.18|2.19|2.20|2.21>"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Дальнейшие шаги
|
## Дальнейшие шаги
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# epf-init v1.1 — Init 1C external data processor scaffold
|
# epf-init v1.7 — Init 1C external data processor scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -6,10 +6,22 @@ param(
|
|||||||
|
|
||||||
[string]$Synonym = $Name,
|
[string]$Synonym = $Name,
|
||||||
|
|
||||||
[string]$SrcDir = "src"
|
[string]$SrcDir = "src",
|
||||||
|
|
||||||
|
# Версия формата выгрузки. Своей конфигурации у автономной обработки нет, наследовать
|
||||||
|
# версию неоткуда — поэтому её задают явно. Формы, макеты и справку внутри обработки
|
||||||
|
# навыки берут уже отсюда: их детектор читает version из корня этого файла.
|
||||||
|
[ValidateSet("2.17", "2.18", "2.19", "2.20", "2.21")]
|
||||||
|
[string]$FormatVersion = "2.17"
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Esc-XmlText {
|
||||||
|
param([string]$s)
|
||||||
|
# Эскейп ТЕКСТА элемента: только & < > — кавычку и апостроф платформа держит сырыми.
|
||||||
|
return $s.Replace('&','&').Replace('<','<').Replace('>','>')
|
||||||
|
}
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
@@ -18,9 +30,17 @@ $uuid2 = [guid]::NewGuid().ToString()
|
|||||||
$uuid3 = [guid]::NewGuid().ToString()
|
$uuid3 = [guid]::NewGuid().ToString()
|
||||||
$uuid4 = [guid]::NewGuid().ToString()
|
$uuid4 = [guid]::NewGuid().ToString()
|
||||||
|
|
||||||
|
# Объявления пространств имён — одной переменной: места эмиссии её только интерполируют.
|
||||||
|
$xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||||
|
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (после lf, перед style):
|
||||||
|
# платформа держит объявления по алфавиту, дописать в конец нельзя.
|
||||||
|
if (($FormatVersion -match '^(\d+)\.(\d+)$') -and ([int]$Matches[1] * 100 + [int]$Matches[2]) -ge 221) {
|
||||||
|
$xmlnsDecl = $xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
|
||||||
|
}
|
||||||
|
|
||||||
$xml = @"
|
$xml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
<MetaDataObject $xmlnsDecl version="$FormatVersion">
|
||||||
<ExternalDataProcessor uuid="$uuid1">
|
<ExternalDataProcessor uuid="$uuid1">
|
||||||
<InternalInfo>
|
<InternalInfo>
|
||||||
<xr:ContainedObject>
|
<xr:ContainedObject>
|
||||||
@@ -33,11 +53,11 @@ $xml = @"
|
|||||||
</xr:GeneratedType>
|
</xr:GeneratedType>
|
||||||
</InternalInfo>
|
</InternalInfo>
|
||||||
<Properties>
|
<Properties>
|
||||||
<Name>$Name</Name>
|
<Name>$(Esc-XmlText $Name)</Name>
|
||||||
<Synonym>
|
<Synonym>
|
||||||
<v8:item>
|
<v8:item>
|
||||||
<v8:lang>ru</v8:lang>
|
<v8:lang>ru</v8:lang>
|
||||||
<v8:content>$Synonym</v8:content>
|
<v8:content>$(Esc-XmlText $Synonym)</v8:content>
|
||||||
</v8:item>
|
</v8:item>
|
||||||
</Synonym>
|
</Synonym>
|
||||||
<Comment/>
|
<Comment/>
|
||||||
@@ -64,7 +84,16 @@ $extDir = Join-Path $processorDir "Ext"
|
|||||||
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
||||||
|
|
||||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
[System.IO.File]::WriteAllText((Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml"), $xml, $enc)
|
# XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
|
||||||
|
#
|
||||||
|
# Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
|
||||||
|
# копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||||
|
function Write-XmlFile([string]$path, [string]$text, $encoding) {
|
||||||
|
$t = ($text -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
|
[System.IO.File]::WriteAllText($path, $t.TrimEnd("`r", "`n"), $encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-XmlFile (Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml") $xml $enc
|
||||||
|
|
||||||
# --- Модуль объекта ---
|
# --- Модуль объекта ---
|
||||||
|
|
||||||
@@ -83,6 +112,11 @@ $moduleBsl = @"
|
|||||||
"@
|
"@
|
||||||
|
|
||||||
$modulePath = Join-Path $extDir "ObjectModule.bsl"
|
$modulePath = Join-Path $extDir "ObjectModule.bsl"
|
||||||
|
# Модуль пишем в каноне платформы: CRLF в разделителях строк (корпус: 2643 CRLF,
|
||||||
|
# чисто-LF 0 из 3001). Хвостовой перевод НЕ навязываем — у платформы он
|
||||||
|
# неканоничен (1235 модулей с ним, 766 без). Шаблон берёт переводы строк из
|
||||||
|
# самого скрипта, а он в репозитории хранится с LF.
|
||||||
|
$moduleBsl = ($moduleBsl -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc)
|
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc)
|
||||||
|
|
||||||
Write-Host "[OK] Создана обработка: $rootFile"
|
Write-Host "[OK] Создана обработка: $rootFile"
|
||||||
|
|||||||
@@ -1,19 +1,61 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# epf-init v1.1 — Init 1C external data processor scaffold
|
# epf-init v1.7 — Init 1C external data processor scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
"""Generates minimal XML source files for a 1C external data processor."""
|
"""Generates minimal XML source files for a 1C external data processor."""
|
||||||
import sys, os, argparse, uuid
|
import sys, os, re, argparse, uuid
|
||||||
|
|
||||||
def esc_xml(s):
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
return s.replace('&','&').replace('<','<').replace('>','>').replace('"','"')
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
def esc_xml_text(s):
|
||||||
|
# Эскейп ТЕКСТА элемента: только & < > — кавычку и апостроф платформа держит сырыми.
|
||||||
|
return s.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||||
|
|
||||||
def new_uuid():
|
def new_uuid():
|
||||||
return str(uuid.uuid4())
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
def write_utf8_bom(path, content):
|
def write_utf8_bom(path, content):
|
||||||
|
# newline='' — без трансляции: иначе текстовый режим Python дал бы CRLF на Windows
|
||||||
|
# и LF на macOS, то есть вывод навыка зависел бы от ОС.
|
||||||
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
def write_xml_file(path, content):
|
||||||
|
"""XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
|
||||||
|
|
||||||
|
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
|
||||||
|
копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||||
|
"""
|
||||||
|
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
|
||||||
|
write_utf8_bom(path, text)
|
||||||
|
|
||||||
|
|
||||||
|
def format_rank(ver):
|
||||||
|
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||||
|
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||||
|
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
@@ -21,7 +63,12 @@ def main():
|
|||||||
parser.add_argument('-Name', dest='Name', required=True)
|
parser.add_argument('-Name', dest='Name', required=True)
|
||||||
parser.add_argument('-Synonym', dest='Synonym', default=None)
|
parser.add_argument('-Synonym', dest='Synonym', default=None)
|
||||||
parser.add_argument('-SrcDir', dest='SrcDir', default='src')
|
parser.add_argument('-SrcDir', dest='SrcDir', default='src')
|
||||||
args = parser.parse_args()
|
# Версия формата выгрузки. Своей конфигурации у автономного объекта нет, наследовать
|
||||||
|
# версию неоткуда — поэтому её задают явно. Формы, макеты и справку внутри объекта
|
||||||
|
# навыки берут уже отсюда: их детектор читает version из корня этого файла.
|
||||||
|
parser.add_argument('-FormatVersion', dest='FormatVersion', default='2.17',
|
||||||
|
choices=['2.17', '2.18', '2.19', '2.20', '2.21'])
|
||||||
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
name = args.Name
|
name = args.Name
|
||||||
synonym = args.Synonym if args.Synonym else name
|
synonym = args.Synonym if args.Synonym else name
|
||||||
@@ -32,8 +79,36 @@ def main():
|
|||||||
uuid3 = new_uuid()
|
uuid3 = new_uuid()
|
||||||
uuid4 = new_uuid()
|
uuid4 = new_uuid()
|
||||||
|
|
||||||
|
# Объявления пространств имён — одной переменной: места эмиссии её только подставляют.
|
||||||
|
xmlns_decl = (
|
||||||
|
'xmlns="http://v8.1c.ru/8.3/MDClasses"'
|
||||||
|
' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"'
|
||||||
|
' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"'
|
||||||
|
' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"'
|
||||||
|
' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"'
|
||||||
|
' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"'
|
||||||
|
' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
|
||||||
|
' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"'
|
||||||
|
' xmlns:v8="http://v8.1c.ru/8.1/data/core"'
|
||||||
|
' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"'
|
||||||
|
' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"'
|
||||||
|
' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"'
|
||||||
|
' xmlns:xen="http://v8.1c.ru/8.3/xcf/enums"'
|
||||||
|
' xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef"'
|
||||||
|
' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
|
||||||
|
' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
||||||
|
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||||
|
)
|
||||||
|
format_version = args.FormatVersion
|
||||||
|
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (после lf, перед
|
||||||
|
# style): платформа держит объявления по алфавиту, дописать в конец нельзя.
|
||||||
|
if format_rank(format_version) >= 221:
|
||||||
|
xmlns_decl = xmlns_decl.replace(
|
||||||
|
' xmlns:style=',
|
||||||
|
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
|
||||||
|
|
||||||
xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
<MetaDataObject {xmlns_decl} version="{format_version}">
|
||||||
\t<ExternalDataProcessor uuid="{uuid1}">
|
\t<ExternalDataProcessor uuid="{uuid1}">
|
||||||
\t\t<InternalInfo>
|
\t\t<InternalInfo>
|
||||||
\t\t\t<xr:ContainedObject>
|
\t\t\t<xr:ContainedObject>
|
||||||
@@ -46,11 +121,11 @@ def main():
|
|||||||
\t\t\t</xr:GeneratedType>
|
\t\t\t</xr:GeneratedType>
|
||||||
\t\t</InternalInfo>
|
\t\t</InternalInfo>
|
||||||
\t\t<Properties>
|
\t\t<Properties>
|
||||||
\t\t\t<Name>{esc_xml(name)}</Name>
|
\t\t\t<Name>{esc_xml_text(name)}</Name>
|
||||||
\t\t\t<Synonym>
|
\t\t\t<Synonym>
|
||||||
\t\t\t\t<v8:item>
|
\t\t\t\t<v8:item>
|
||||||
\t\t\t\t\t<v8:lang>ru</v8:lang>
|
\t\t\t\t\t<v8:lang>ru</v8:lang>
|
||||||
\t\t\t\t\t<v8:content>{esc_xml(synonym)}</v8:content>
|
\t\t\t\t\t<v8:content>{esc_xml_text(synonym)}</v8:content>
|
||||||
\t\t\t\t</v8:item>
|
\t\t\t\t</v8:item>
|
||||||
\t\t\t</Synonym>
|
\t\t\t</Synonym>
|
||||||
\t\t\t<Comment/>
|
\t\t\t<Comment/>
|
||||||
@@ -72,7 +147,7 @@ def main():
|
|||||||
ext_dir = os.path.join(processor_dir, "Ext")
|
ext_dir = os.path.join(processor_dir, "Ext")
|
||||||
os.makedirs(ext_dir, exist_ok=True)
|
os.makedirs(ext_dir, exist_ok=True)
|
||||||
|
|
||||||
write_utf8_bom(os.path.join(os.path.abspath(src_dir), f"{name}.xml"), xml)
|
write_xml_file(os.path.join(os.path.abspath(src_dir), f"{name}.xml"), xml)
|
||||||
|
|
||||||
# --- Модуль объекта ---
|
# --- Модуль объекта ---
|
||||||
module_bsl = """\
|
module_bsl = """\
|
||||||
@@ -89,7 +164,10 @@ def main():
|
|||||||
#КонецОбласти"""
|
#КонецОбласти"""
|
||||||
|
|
||||||
module_path = os.path.join(ext_dir, "ObjectModule.bsl")
|
module_path = os.path.join(ext_dir, "ObjectModule.bsl")
|
||||||
write_utf8_bom(module_path, module_bsl)
|
# Модуль пишем в каноне платформы: CRLF в разделителях строк (корпус: 2643 CRLF,
|
||||||
|
# чисто-LF 0 из 3001). Хвостовой перевод НЕ навязываем — у платформы он
|
||||||
|
# неканоничен (1235 модулей с ним, 766 без).
|
||||||
|
write_utf8_bom(module_path, module_bsl.replace('\r\n', '\n').replace('\n', '\r\n'))
|
||||||
|
|
||||||
print(f"[OK] Создана обработка: {root_file}")
|
print(f"[OK] Создана обработка: {root_file}")
|
||||||
print(f" Каталог: {processor_dir}")
|
print(f" Каталог: {processor_dir}")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# epf-validate v1.3 — Validate 1C external data processor / report structure
|
# epf-validate v1.4 — Validate 1C external data processor / report structure
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
|
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
|
||||||
param(
|
param(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# epf-validate v1.3 — Validate 1C external data processor / report structure
|
# epf-validate v1.4 — Validate 1C external data processor / report structure
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
|
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
|
||||||
|
|
||||||
@@ -10,6 +10,28 @@ import sys
|
|||||||
from io import StringIO
|
from io import StringIO
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
||||||
V8_NS = "http://v8.1c.ru/8.1/data/core"
|
V8_NS = "http://v8.1c.ru/8.1/data/core"
|
||||||
XR_NS = "http://v8.1c.ru/8.3/xcf/readable"
|
XR_NS = "http://v8.1c.ru/8.3/xcf/readable"
|
||||||
@@ -50,7 +72,7 @@ def main():
|
|||||||
parser.add_argument("-Detailed", action="store_true")
|
parser.add_argument("-Detailed", action="store_true")
|
||||||
parser.add_argument("-MaxErrors", type=int, default=30)
|
parser.add_argument("-MaxErrors", type=int, default=30)
|
||||||
parser.add_argument("-OutFile", default=None)
|
parser.add_argument("-OutFile", default=None)
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
max_errors = args.MaxErrors
|
max_errors = args.MaxErrors
|
||||||
|
|
||||||
|
|||||||
@@ -18,20 +18,21 @@ allowed-tools:
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
/erf-init <Name> [Synonym] [SrcDir] [--with-skd]
|
/erf-init <Name> [Synonym] [SrcDir] [FormatVersion] [--with-skd]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Параметр | Обязательный | По умолчанию | Описание |
|
| Параметр | Обязательный | По умолчанию | Описание |
|
||||||
|-----------|:------------:|--------------|---------------------------------------|
|
|---------------|:------------:|--------------|---------------------------------------|
|
||||||
| Name | да | — | Имя отчёта (латиница/кириллица) |
|
| Name | да | — | Имя отчёта (латиница/кириллица) |
|
||||||
| Synonym | нет | = Name | Синоним (отображаемое имя) |
|
| Synonym | нет | = Name | Синоним (отображаемое имя) |
|
||||||
| SrcDir | нет | `src` | Каталог исходников относительно CWD |
|
| SrcDir | нет | `src` | Каталог исходников относительно CWD |
|
||||||
| --WithSKD | нет | — | Создать пустую СКД и привязать к MainDataCompositionSchema |
|
| FormatVersion | нет | `2.17` | Версия формата: 2.20 — платформа 8.3.27, 2.21 — 8.5. Дефолт открывается любой платформой |
|
||||||
|
| --WithSKD | нет | — | Создать пустую СКД и привязать к MainDataCompositionSchema |
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-WithSKD]
|
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<2.17|2.18|2.19|2.20|2.21>"] [-WithSKD]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Дальнейшие шаги
|
## Дальнейшие шаги
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# erf-init v1.1 — Init 1C external report scaffold
|
# erf-init v1.7 — Init 1C external report scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -8,10 +8,22 @@ param(
|
|||||||
|
|
||||||
[string]$SrcDir = "src",
|
[string]$SrcDir = "src",
|
||||||
|
|
||||||
[switch]$WithSKD
|
[switch]$WithSKD,
|
||||||
|
|
||||||
|
# Версия формата выгрузки. Своей конфигурации у автономного отчёта нет, наследовать
|
||||||
|
# версию неоткуда — поэтому её задают явно. Формы, макеты и справку внутри отчёта
|
||||||
|
# навыки берут уже отсюда: их детектор читает version из корня этого файла.
|
||||||
|
[ValidateSet("2.17", "2.18", "2.19", "2.20", "2.21")]
|
||||||
|
[string]$FormatVersion = "2.17"
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Esc-XmlText {
|
||||||
|
param([string]$s)
|
||||||
|
# Эскейп ТЕКСТА элемента: только & < > — кавычку и апостроф платформа держит сырыми.
|
||||||
|
return $s.Replace('&','&').Replace('<','<').Replace('>','>')
|
||||||
|
}
|
||||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
@@ -20,6 +32,14 @@ $uuid2 = [guid]::NewGuid().ToString()
|
|||||||
$uuid3 = [guid]::NewGuid().ToString()
|
$uuid3 = [guid]::NewGuid().ToString()
|
||||||
$uuid4 = [guid]::NewGuid().ToString()
|
$uuid4 = [guid]::NewGuid().ToString()
|
||||||
|
|
||||||
|
# Объявления пространств имён — одной переменной: места эмиссии её только интерполируют.
|
||||||
|
$xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||||
|
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (после lf, перед style):
|
||||||
|
# платформа держит объявления по алфавиту, дописать в конец нельзя.
|
||||||
|
if (($FormatVersion -match '^(\d+)\.(\d+)$') -and ([int]$Matches[1] * 100 + [int]$Matches[2]) -ge 221) {
|
||||||
|
$xmlnsDecl = $xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
|
||||||
|
}
|
||||||
|
|
||||||
# --- Формируем Properties ---
|
# --- Формируем Properties ---
|
||||||
|
|
||||||
$mainDCSValue = ""
|
$mainDCSValue = ""
|
||||||
@@ -48,7 +68,7 @@ $childObjectsXml = if ($childObjectsContent) {
|
|||||||
|
|
||||||
$xml = @"
|
$xml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
<MetaDataObject $xmlnsDecl version="$FormatVersion">
|
||||||
<ExternalReport uuid="$uuid1">
|
<ExternalReport uuid="$uuid1">
|
||||||
<InternalInfo>
|
<InternalInfo>
|
||||||
<xr:ContainedObject>
|
<xr:ContainedObject>
|
||||||
@@ -61,11 +81,11 @@ $xml = @"
|
|||||||
</xr:GeneratedType>
|
</xr:GeneratedType>
|
||||||
</InternalInfo>
|
</InternalInfo>
|
||||||
<Properties>
|
<Properties>
|
||||||
<Name>$Name</Name>
|
<Name>$(Esc-XmlText $Name)</Name>
|
||||||
<Synonym>
|
<Synonym>
|
||||||
<v8:item>
|
<v8:item>
|
||||||
<v8:lang>ru</v8:lang>
|
<v8:lang>ru</v8:lang>
|
||||||
<v8:content>$Synonym</v8:content>
|
<v8:content>$(Esc-XmlText $Synonym)</v8:content>
|
||||||
</v8:item>
|
</v8:item>
|
||||||
</Synonym>
|
</Synonym>
|
||||||
<Comment/>
|
<Comment/>
|
||||||
@@ -98,7 +118,16 @@ $extDir = Join-Path $reportDir "Ext"
|
|||||||
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
||||||
|
|
||||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
[System.IO.File]::WriteAllText((Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml"), $xml, $enc)
|
# XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
|
||||||
|
#
|
||||||
|
# Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
|
||||||
|
# копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||||
|
function Write-XmlFile([string]$path, [string]$text, $encoding) {
|
||||||
|
$t = ($text -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
|
[System.IO.File]::WriteAllText($path, $t.TrimEnd("`r", "`n"), $encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-XmlFile (Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml") $xml $enc
|
||||||
|
|
||||||
# --- Модуль объекта ---
|
# --- Модуль объекта ---
|
||||||
|
|
||||||
@@ -117,6 +146,11 @@ $moduleBsl = @"
|
|||||||
"@
|
"@
|
||||||
|
|
||||||
$modulePath = Join-Path $extDir "ObjectModule.bsl"
|
$modulePath = Join-Path $extDir "ObjectModule.bsl"
|
||||||
|
# Модуль пишем в каноне платформы: CRLF в разделителях строк (корпус: 2643 CRLF,
|
||||||
|
# чисто-LF 0 из 3001). Хвостовой перевод НЕ навязываем — у платформы он
|
||||||
|
# неканоничен (1235 модулей с ним, 766 без). Шаблон берёт переводы строк из
|
||||||
|
# самого скрипта, а он в репозитории хранится с LF.
|
||||||
|
$moduleBsl = ($moduleBsl -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc)
|
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc)
|
||||||
|
|
||||||
Write-Host "[OK] Создан отчёт: $rootFile"
|
Write-Host "[OK] Создан отчёт: $rootFile"
|
||||||
@@ -136,7 +170,7 @@ if ($WithSKD) {
|
|||||||
|
|
||||||
$skdMetaXml = @"
|
$skdMetaXml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
<MetaDataObject $xmlnsDecl version="$FormatVersion">
|
||||||
<Template uuid="$skdUuid">
|
<Template uuid="$skdUuid">
|
||||||
<Properties>
|
<Properties>
|
||||||
<Name>$skdName</Name>
|
<Name>$skdName</Name>
|
||||||
@@ -153,7 +187,7 @@ if ($WithSKD) {
|
|||||||
</MetaDataObject>
|
</MetaDataObject>
|
||||||
"@
|
"@
|
||||||
|
|
||||||
[System.IO.File]::WriteAllText($skdMetaPath, $skdMetaXml, $enc)
|
Write-XmlFile $skdMetaPath $skdMetaXml $enc
|
||||||
|
|
||||||
$skdContent = @"
|
$skdContent = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
@@ -173,7 +207,7 @@ if ($WithSKD) {
|
|||||||
"@
|
"@
|
||||||
|
|
||||||
$skdFilePath = Join-Path $skdExtDir "Template.xml"
|
$skdFilePath = Join-Path $skdExtDir "Template.xml"
|
||||||
[System.IO.File]::WriteAllText($skdFilePath, $skdContent, $enc)
|
Write-XmlFile $skdFilePath $skdContent $enc
|
||||||
|
|
||||||
Write-Host " СКД: $skdMetaPath"
|
Write-Host " СКД: $skdMetaPath"
|
||||||
Write-Host " Тело: $skdFilePath"
|
Write-Host " Тело: $skdFilePath"
|
||||||
|
|||||||
@@ -1,19 +1,61 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# erf-init v1.1 — Init 1C external report scaffold
|
# erf-init v1.7 — Init 1C external report scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
"""Generates minimal XML source files for a 1C external report."""
|
"""Generates minimal XML source files for a 1C external report."""
|
||||||
import sys, os, argparse, uuid
|
import sys, os, re, argparse, uuid
|
||||||
|
|
||||||
def esc_xml(s):
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
return s.replace('&','&').replace('<','<').replace('>','>').replace('"','"')
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
def esc_xml_text(s):
|
||||||
|
# Эскейп ТЕКСТА элемента: только & < > — кавычку и апостроф платформа держит сырыми.
|
||||||
|
return s.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||||
|
|
||||||
def new_uuid():
|
def new_uuid():
|
||||||
return str(uuid.uuid4())
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
def write_utf8_bom(path, content):
|
def write_utf8_bom(path, content):
|
||||||
|
# newline='' — без трансляции: иначе текстовый режим Python дал бы CRLF на Windows
|
||||||
|
# и LF на macOS, то есть вывод навыка зависел бы от ОС.
|
||||||
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
def write_xml_file(path, content):
|
||||||
|
"""XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
|
||||||
|
|
||||||
|
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
|
||||||
|
копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||||
|
"""
|
||||||
|
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
|
||||||
|
write_utf8_bom(path, text)
|
||||||
|
|
||||||
|
|
||||||
|
def format_rank(ver):
|
||||||
|
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||||
|
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||||
|
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
@@ -21,8 +63,13 @@ def main():
|
|||||||
parser.add_argument('-Name', dest='Name', required=True)
|
parser.add_argument('-Name', dest='Name', required=True)
|
||||||
parser.add_argument('-Synonym', dest='Synonym', default=None)
|
parser.add_argument('-Synonym', dest='Synonym', default=None)
|
||||||
parser.add_argument('-SrcDir', dest='SrcDir', default='src')
|
parser.add_argument('-SrcDir', dest='SrcDir', default='src')
|
||||||
|
# Версия формата выгрузки. Своей конфигурации у автономного объекта нет, наследовать
|
||||||
|
# версию неоткуда — поэтому её задают явно. Формы, макеты и справку внутри объекта
|
||||||
|
# навыки берут уже отсюда: их детектор читает version из корня этого файла.
|
||||||
|
parser.add_argument('-FormatVersion', dest='FormatVersion', default='2.17',
|
||||||
|
choices=['2.17', '2.18', '2.19', '2.20', '2.21'])
|
||||||
parser.add_argument('-WithSKD', dest='WithSKD', action='store_true')
|
parser.add_argument('-WithSKD', dest='WithSKD', action='store_true')
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
name = args.Name
|
name = args.Name
|
||||||
synonym = args.Synonym if args.Synonym else name
|
synonym = args.Synonym if args.Synonym else name
|
||||||
@@ -33,6 +80,34 @@ def main():
|
|||||||
uuid3 = new_uuid()
|
uuid3 = new_uuid()
|
||||||
uuid4 = new_uuid()
|
uuid4 = new_uuid()
|
||||||
|
|
||||||
|
# Объявления пространств имён — одной переменной: места эмиссии её только подставляют.
|
||||||
|
xmlns_decl = (
|
||||||
|
'xmlns="http://v8.1c.ru/8.3/MDClasses"'
|
||||||
|
' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"'
|
||||||
|
' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"'
|
||||||
|
' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"'
|
||||||
|
' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"'
|
||||||
|
' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"'
|
||||||
|
' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
|
||||||
|
' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"'
|
||||||
|
' xmlns:v8="http://v8.1c.ru/8.1/data/core"'
|
||||||
|
' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"'
|
||||||
|
' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"'
|
||||||
|
' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"'
|
||||||
|
' xmlns:xen="http://v8.1c.ru/8.3/xcf/enums"'
|
||||||
|
' xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef"'
|
||||||
|
' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
|
||||||
|
' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
||||||
|
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||||
|
)
|
||||||
|
format_version = args.FormatVersion
|
||||||
|
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (после lf, перед
|
||||||
|
# style): платформа держит объявления по алфавиту, дописать в конец нельзя.
|
||||||
|
if format_rank(format_version) >= 221:
|
||||||
|
xmlns_decl = xmlns_decl.replace(
|
||||||
|
' xmlns:style=',
|
||||||
|
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
|
||||||
|
|
||||||
# --- Properties ---
|
# --- Properties ---
|
||||||
main_dcs_value = ""
|
main_dcs_value = ""
|
||||||
child_objects_content = ""
|
child_objects_content = ""
|
||||||
@@ -45,7 +120,7 @@ def main():
|
|||||||
child_objects_xml = f"<ChildObjects>{child_objects_content}\t\t</ChildObjects>" if child_objects_content else "<ChildObjects/>"
|
child_objects_xml = f"<ChildObjects>{child_objects_content}\t\t</ChildObjects>" if child_objects_content else "<ChildObjects/>"
|
||||||
|
|
||||||
xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
<MetaDataObject {xmlns_decl} version="{format_version}">
|
||||||
\t<ExternalReport uuid="{uuid1}">
|
\t<ExternalReport uuid="{uuid1}">
|
||||||
\t\t<InternalInfo>
|
\t\t<InternalInfo>
|
||||||
\t\t\t<xr:ContainedObject>
|
\t\t\t<xr:ContainedObject>
|
||||||
@@ -58,11 +133,11 @@ def main():
|
|||||||
\t\t\t</xr:GeneratedType>
|
\t\t\t</xr:GeneratedType>
|
||||||
\t\t</InternalInfo>
|
\t\t</InternalInfo>
|
||||||
\t\t<Properties>
|
\t\t<Properties>
|
||||||
\t\t\t<Name>{esc_xml(name)}</Name>
|
\t\t\t<Name>{esc_xml_text(name)}</Name>
|
||||||
\t\t\t<Synonym>
|
\t\t\t<Synonym>
|
||||||
\t\t\t\t<v8:item>
|
\t\t\t\t<v8:item>
|
||||||
\t\t\t\t\t<v8:lang>ru</v8:lang>
|
\t\t\t\t\t<v8:lang>ru</v8:lang>
|
||||||
\t\t\t\t\t<v8:content>{esc_xml(synonym)}</v8:content>
|
\t\t\t\t\t<v8:content>{esc_xml_text(synonym)}</v8:content>
|
||||||
\t\t\t\t</v8:item>
|
\t\t\t\t</v8:item>
|
||||||
\t\t\t</Synonym>
|
\t\t\t</Synonym>
|
||||||
\t\t\t<Comment/>
|
\t\t\t<Comment/>
|
||||||
@@ -90,7 +165,7 @@ def main():
|
|||||||
ext_dir = os.path.join(report_dir, "Ext")
|
ext_dir = os.path.join(report_dir, "Ext")
|
||||||
os.makedirs(ext_dir, exist_ok=True)
|
os.makedirs(ext_dir, exist_ok=True)
|
||||||
|
|
||||||
write_utf8_bom(os.path.join(os.path.abspath(src_dir), f"{name}.xml"), xml)
|
write_xml_file(os.path.join(os.path.abspath(src_dir), f"{name}.xml"), xml)
|
||||||
|
|
||||||
# --- Модуль объекта ---
|
# --- Модуль объекта ---
|
||||||
module_bsl = """\
|
module_bsl = """\
|
||||||
@@ -107,7 +182,10 @@ def main():
|
|||||||
#КонецОбласти"""
|
#КонецОбласти"""
|
||||||
|
|
||||||
module_path = os.path.join(ext_dir, "ObjectModule.bsl")
|
module_path = os.path.join(ext_dir, "ObjectModule.bsl")
|
||||||
write_utf8_bom(module_path, module_bsl)
|
# Модуль пишем в каноне платформы: CRLF в разделителях строк (корпус: 2643 CRLF,
|
||||||
|
# чисто-LF 0 из 3001). Хвостовой перевод НЕ навязываем — у платформы он
|
||||||
|
# неканоничен (1235 модулей с ним, 766 без).
|
||||||
|
write_utf8_bom(module_path, module_bsl.replace('\r\n', '\n').replace('\n', '\r\n'))
|
||||||
|
|
||||||
print(f"[OK] Создан отчёт: {root_file}")
|
print(f"[OK] Создан отчёт: {root_file}")
|
||||||
print(f" Каталог: {report_dir}")
|
print(f" Каталог: {report_dir}")
|
||||||
@@ -124,7 +202,7 @@ def main():
|
|||||||
skd_uuid = new_uuid()
|
skd_uuid = new_uuid()
|
||||||
|
|
||||||
skd_meta_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
skd_meta_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
<MetaDataObject {xmlns_decl} version="{format_version}">
|
||||||
\t<Template uuid="{skd_uuid}">
|
\t<Template uuid="{skd_uuid}">
|
||||||
\t\t<Properties>
|
\t\t<Properties>
|
||||||
\t\t\t<Name>{skd_name}</Name>
|
\t\t\t<Name>{skd_name}</Name>
|
||||||
@@ -140,7 +218,7 @@ def main():
|
|||||||
\t</Template>
|
\t</Template>
|
||||||
</MetaDataObject>'''
|
</MetaDataObject>'''
|
||||||
|
|
||||||
write_utf8_bom(skd_meta_path, skd_meta_xml)
|
write_xml_file(skd_meta_path, skd_meta_xml)
|
||||||
|
|
||||||
skd_content = '''<?xml version="1.0" encoding="UTF-8"?>
|
skd_content = '''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
|
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
|
||||||
@@ -158,7 +236,7 @@ def main():
|
|||||||
</DataCompositionSchema>'''
|
</DataCompositionSchema>'''
|
||||||
|
|
||||||
skd_file_path = os.path.join(skd_ext_dir, "Template.xml")
|
skd_file_path = os.path.join(skd_ext_dir, "Template.xml")
|
||||||
write_utf8_bom(skd_file_path, skd_content)
|
write_xml_file(skd_file_path, skd_content)
|
||||||
|
|
||||||
print(f" СКД: {skd_meta_path}")
|
print(f" СКД: {skd_meta_path}")
|
||||||
print(f" Тело: {skd_file_path}")
|
print(f" Тело: {skd_file_path}")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# form-add v1.12 — Add managed form to 1C config object
|
# form-add v1.25 — Add managed form to 1C config object (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -154,6 +154,14 @@ function Assert-EditAllowed([string]$targetPath, [string]$require) {
|
|||||||
function Detect-FormatVersion([string]$dir) {
|
function Detect-FormatVersion([string]$dir) {
|
||||||
$d = $dir
|
$d = $dir
|
||||||
while ($d) {
|
while ($d) {
|
||||||
|
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||||
|
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||||
|
$extPath = "$d.xml"
|
||||||
|
if (Test-Path $extPath) {
|
||||||
|
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
|
||||||
|
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
|
||||||
|
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||||
|
}
|
||||||
$cfgPath = Join-Path $d "Configuration.xml"
|
$cfgPath = Join-Path $d "Configuration.xml"
|
||||||
if (Test-Path $cfgPath) {
|
if (Test-Path $cfgPath) {
|
||||||
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
||||||
@@ -169,6 +177,13 @@ function Detect-FormatVersion([string]$dir) {
|
|||||||
return "2.17"
|
return "2.17"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209.
|
||||||
|
# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка.
|
||||||
|
function Get-FormatRank([string]$ver) {
|
||||||
|
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# --- Фаза 1: Определение типа объекта ---
|
# --- Фаза 1: Определение типа объекта ---
|
||||||
|
|
||||||
# Resolve ObjectPath (directory → .xml)
|
# Resolve ObjectPath (directory → .xml)
|
||||||
@@ -190,7 +205,26 @@ if (-not (Test-Path $ObjectPath)) {
|
|||||||
|
|
||||||
$objectXmlFull = Resolve-Path $ObjectPath
|
$objectXmlFull = Resolve-Path $ObjectPath
|
||||||
Assert-EditAllowed $objectXmlFull.Path 'editable'
|
Assert-EditAllowed $objectXmlFull.Path 'editable'
|
||||||
$script:formatVersion = Detect-FormatVersion (Split-Path $objectXmlFull.Path -Parent)
|
# Версию берём прежде всего из корня самого объекта — он её несёт всегда, а у автономной
|
||||||
|
# внешней обработки/отчёта подниматься к Configuration.xml просто некуда.
|
||||||
|
$script:formatVersion = $null
|
||||||
|
$objHead = [System.IO.File]::ReadAllText($objectXmlFull.Path, [System.Text.Encoding]::UTF8)
|
||||||
|
$objHead = $objHead.Substring(0, [Math]::Min(2000, $objHead.Length))
|
||||||
|
if ($objHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { $script:formatVersion = $Matches[1] }
|
||||||
|
if (-not $script:formatVersion) { $script:formatVersion = Detect-FormatVersion (Split-Path $objectXmlFull.Path -Parent) }
|
||||||
|
|
||||||
|
# Объявления пространств имён — одной переменной на корень: места эмиссии их только
|
||||||
|
# интерполируют. Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
|
||||||
|
$script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||||
|
$script:formNsDecl = 'xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" 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:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||||
|
|
||||||
|
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
|
||||||
|
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
|
||||||
|
# дописать в конец нельзя.
|
||||||
|
if ((Get-FormatRank $script:formatVersion) -ge 221) {
|
||||||
|
$script:xmlnsDecl = $script:xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
|
||||||
|
$script:formNsDecl = $script:formNsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
|
||||||
|
}
|
||||||
|
|
||||||
$xmlDoc = New-Object System.Xml.XmlDocument
|
$xmlDoc = New-Object System.Xml.XmlDocument
|
||||||
$xmlDoc.PreserveWhitespace = $true
|
$xmlDoc.PreserveWhitespace = $true
|
||||||
@@ -313,9 +347,16 @@ if ($objectType -in $processorLikeTypes) {
|
|||||||
$extPresentationLine = "`n`t`t`t<ExtendedPresentation/>"
|
$extPresentationLine = "`n`t`t`t<ExtendedPresentation/>"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Использование в режиме совместимости интерфейса — свойство формата 2.21 (8.5),
|
||||||
|
# сразу после UsePurposes (проверено по выгрузке 8.5, до ExtendedPresentation).
|
||||||
|
$useInIfcLine = ""
|
||||||
|
if ((Get-FormatRank $script:formatVersion) -ge 221) {
|
||||||
|
$useInIfcLine = "`n`t`t`t<UseInInterfaceCompatibilityMode>Any</UseInInterfaceCompatibilityMode>"
|
||||||
|
}
|
||||||
|
|
||||||
$formMetaXml = @"
|
$formMetaXml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$($script:formatVersion)">
|
<MetaDataObject $($script:xmlnsDecl) version="$($script:formatVersion)">
|
||||||
<Form uuid="$formUuid">
|
<Form uuid="$formUuid">
|
||||||
<Properties>
|
<Properties>
|
||||||
<Name>$FormName</Name>
|
<Name>$FormName</Name>
|
||||||
@@ -331,20 +372,29 @@ $formMetaXml = @"
|
|||||||
<UsePurposes>
|
<UsePurposes>
|
||||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||||
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
|
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
|
||||||
</UsePurposes>$extPresentationLine
|
</UsePurposes>$useInIfcLine$extPresentationLine
|
||||||
</Properties>
|
</Properties>
|
||||||
</Form>
|
</Form>
|
||||||
</MetaDataObject>
|
</MetaDataObject>
|
||||||
"@
|
"@
|
||||||
|
|
||||||
[System.IO.File]::WriteAllText($formMetaPath, $formMetaXml, $encBom)
|
# XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
|
||||||
|
#
|
||||||
|
# Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
|
||||||
|
# копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||||
|
#
|
||||||
|
# Модуль .bsl сюда НЕ идёт — он пишется отдельно.
|
||||||
|
function Write-XmlFile([string]$path, [string]$text, $encoding) {
|
||||||
|
$t = ($text -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
|
[System.IO.File]::WriteAllText($path, $t.TrimEnd("`r", "`n"), $encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-XmlFile $formMetaPath $formMetaXml $encBom
|
||||||
|
|
||||||
# --- 3b. Form.xml ---
|
# --- 3b. Form.xml ---
|
||||||
|
|
||||||
$formXmlPath = Join-Path $formExtDir "Form.xml"
|
$formXmlPath = Join-Path $formExtDir "Form.xml"
|
||||||
|
|
||||||
$formNsDecl = 'xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" 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:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
|
||||||
|
|
||||||
if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
|
if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
|
||||||
# Динамический список
|
# Динамический список
|
||||||
# MainTable: тип.имя
|
# MainTable: тип.имя
|
||||||
@@ -352,7 +402,7 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
|
|||||||
|
|
||||||
$formXml = @"
|
$formXml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Form $formNsDecl version="$($script:formatVersion)">
|
<Form $($script:formNsDecl) version="$($script:formatVersion)">
|
||||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||||
<Autofill>true</Autofill>
|
<Autofill>true</Autofill>
|
||||||
</AutoCommandBar>
|
</AutoCommandBar>
|
||||||
@@ -377,7 +427,7 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
|
|||||||
|
|
||||||
$formXml = @"
|
$formXml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Form $formNsDecl version="$($script:formatVersion)">
|
<Form $($script:formNsDecl) version="$($script:formatVersion)">
|
||||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||||
<Autofill>true</Autofill>
|
<Autofill>true</Autofill>
|
||||||
</AutoCommandBar>
|
</AutoCommandBar>
|
||||||
@@ -424,7 +474,7 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
|
|||||||
|
|
||||||
$formXml = @"
|
$formXml = @"
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Form $formNsDecl version="$($script:formatVersion)">
|
<Form $($script:formNsDecl) version="$($script:formatVersion)">
|
||||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||||
<Autofill>true</Autofill>
|
<Autofill>true</Autofill>
|
||||||
</AutoCommandBar>
|
</AutoCommandBar>
|
||||||
@@ -444,7 +494,7 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
|
|||||||
if (Test-Path $formXmlPath) {
|
if (Test-Path $formXmlPath) {
|
||||||
Write-Host "[SKIP] Form.xml already exists: $formXmlPath — not overwriting"
|
Write-Host "[SKIP] Form.xml already exists: $formXmlPath — not overwriting"
|
||||||
} else {
|
} else {
|
||||||
[System.IO.File]::WriteAllText($formXmlPath, $formXml, $encBom)
|
Write-XmlFile $formXmlPath $formXml $encBom
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- 3c. Module.bsl ---
|
# --- 3c. Module.bsl ---
|
||||||
@@ -476,6 +526,11 @@ $moduleBsl = @"
|
|||||||
if (Test-Path $modulePath) {
|
if (Test-Path $modulePath) {
|
||||||
Write-Host "[SKIP] Module.bsl already exists: $modulePath — not overwriting"
|
Write-Host "[SKIP] Module.bsl already exists: $modulePath — not overwriting"
|
||||||
} else {
|
} else {
|
||||||
|
# Модуль пишем в каноне платформы: CRLF в разделителях строк (корпус: 2643 CRLF,
|
||||||
|
# чисто-LF 0 из 3001). Хвостовой перевод НЕ навязываем — у платформы он
|
||||||
|
# неканоничен (1235 модулей с ним, 766 без). Шаблон берёт переводы строк из
|
||||||
|
# самого скрипта, а он в репозитории хранится с LF.
|
||||||
|
$moduleBsl = ($moduleBsl -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $encBom)
|
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $encBom)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,12 +640,27 @@ if ($SetDefault -or $isFirstFormForPurpose) {
|
|||||||
$settings = New-Object System.Xml.XmlWriterSettings
|
$settings = New-Object System.Xml.XmlWriterSettings
|
||||||
$settings.Encoding = $encBom
|
$settings.Encoding = $encBom
|
||||||
$settings.Indent = $false
|
$settings.Indent = $false
|
||||||
|
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
|
||||||
|
|
||||||
$stream = New-Object System.IO.FileStream($objectXmlFull.Path, [System.IO.FileMode]::Create)
|
# Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
|
||||||
$writer = [System.Xml.XmlWriter]::Create($stream, $settings)
|
$memStream = New-Object System.IO.MemoryStream
|
||||||
|
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
|
||||||
$xmlDoc.Save($writer)
|
$xmlDoc.Save($writer)
|
||||||
$writer.Close()
|
$writer.Flush(); $writer.Close()
|
||||||
$stream.Close()
|
|
||||||
|
$xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
|
||||||
|
$memStream.Close()
|
||||||
|
if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) }
|
||||||
|
$xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$xmlText = [regex]::Replace($xmlText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $objectXmlFull.Path) -and ([System.IO.File]::ReadAllText($objectXmlFull.Path) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
|
[System.IO.File]::WriteAllText($objectXmlFull.Path, $xmlText, $encBom)
|
||||||
|
|
||||||
# --- Фаза 5: Вывод ---
|
# --- Фаза 5: Вывод ---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# form-add v1.12 — Add managed form to 1C config object
|
# form-add v1.25 — Add managed form to 1C config object (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -11,6 +11,28 @@ import uuid
|
|||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
||||||
@@ -196,6 +218,16 @@ NSMAP = {
|
|||||||
|
|
||||||
def detect_format_version(d):
|
def detect_format_version(d):
|
||||||
while d:
|
while d:
|
||||||
|
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||||
|
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||||
|
ext_path = d + ".xml"
|
||||||
|
if os.path.isfile(ext_path):
|
||||||
|
with open(ext_path, "r", encoding="utf-8-sig") as f:
|
||||||
|
ext_head = f.read(2000)
|
||||||
|
if re.search(r'<(ExternalDataProcessor|ExternalReport)[ >]', ext_head):
|
||||||
|
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', ext_head)
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
cfg_path = os.path.join(d, "Configuration.xml")
|
cfg_path = os.path.join(d, "Configuration.xml")
|
||||||
if os.path.isfile(cfg_path):
|
if os.path.isfile(cfg_path):
|
||||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||||
@@ -210,6 +242,12 @@ def detect_format_version(d):
|
|||||||
return "2.17"
|
return "2.17"
|
||||||
|
|
||||||
|
|
||||||
|
def format_rank(ver):
|
||||||
|
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||||
|
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||||
|
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||||
|
|
||||||
|
|
||||||
def _detect_xml_style(path):
|
def _detect_xml_style(path):
|
||||||
"""Стиль существующего файла для round-trip-сохранения: BOM / EOL / регистр encoding /
|
"""Стиль существующего файла для round-trip-сохранения: BOM / EOL / регистр encoding /
|
||||||
финальный перенос. None → файл новый (сохранить текущее поведение)."""
|
финальный перенос. None → файл новый (сохранить текущее поведение)."""
|
||||||
@@ -227,21 +265,22 @@ def _detect_xml_style(path):
|
|||||||
|
|
||||||
|
|
||||||
def _finalize_xml_bytes(xml_bytes, style):
|
def _finalize_xml_bytes(xml_bytes, style):
|
||||||
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None)."""
|
"""Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
|
||||||
enc_decl = style["enc"] if style else "utf-8"
|
выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
|
||||||
|
enc_decl = style["enc"] if style else "UTF-8"
|
||||||
xml_bytes = xml_bytes.replace(
|
xml_bytes = xml_bytes.replace(
|
||||||
b"<?xml version='1.0' encoding='UTF-8'?>",
|
b"<?xml version='1.0' encoding='UTF-8'?>",
|
||||||
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
||||||
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
||||||
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
||||||
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
||||||
# Финальный перенос — как в оригинале (новый файл → есть)
|
# Финальный перенос — как в оригинале (новый файл → нет, канон #57)
|
||||||
want_final_nl = style["final_nl"] if style else True
|
want_final_nl = style["final_nl"] if style else False
|
||||||
xml_bytes = xml_bytes.rstrip(b"\n")
|
xml_bytes = xml_bytes.rstrip(b"\n")
|
||||||
if want_final_nl:
|
if want_final_nl:
|
||||||
xml_bytes += b"\n"
|
xml_bytes += b"\n"
|
||||||
# EOL — как в оригинале (новый файл → LF, текущее поведение)
|
# EOL — как в оригинале (новый файл → CRLF, канон #57)
|
||||||
if style and style["crlf"]:
|
if (style["crlf"] if style else True):
|
||||||
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
||||||
return xml_bytes
|
return xml_bytes
|
||||||
|
|
||||||
@@ -257,10 +296,24 @@ def save_xml_with_bom(tree, path):
|
|||||||
f.write(xml_bytes)
|
f.write(xml_bytes)
|
||||||
|
|
||||||
|
|
||||||
def write_text_with_bom(path, text):
|
def write_utf8_bom(path, content):
|
||||||
"""Write text to file with UTF-8 BOM."""
|
# newline='' — без трансляции: иначе текстовый режим Python дал бы CRLF на Windows
|
||||||
with open(path, "w", encoding="utf-8-sig") as f:
|
# и LF на macOS, то есть вывод навыка зависел бы от ОС.
|
||||||
f.write(text)
|
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def write_xml_file(path, content):
|
||||||
|
"""XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
|
||||||
|
|
||||||
|
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
|
||||||
|
копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||||
|
|
||||||
|
Модуль .bsl сюда НЕ идёт — он пишется отдельно.
|
||||||
|
"""
|
||||||
|
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
|
||||||
|
write_utf8_bom(path, text)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -272,7 +325,7 @@ def main():
|
|||||||
parser.add_argument("-Synonym", default=None)
|
parser.add_argument("-Synonym", default=None)
|
||||||
parser.add_argument("-Purpose", default="Object")
|
parser.add_argument("-Purpose", default="Object")
|
||||||
parser.add_argument("-SetDefault", action="store_true")
|
parser.add_argument("-SetDefault", action="store_true")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
object_path = args.ObjectPath
|
object_path = args.ObjectPath
|
||||||
form_name = args.FormName
|
form_name = args.FormName
|
||||||
@@ -299,7 +352,64 @@ def main():
|
|||||||
|
|
||||||
object_xml_full = os.path.abspath(object_path)
|
object_xml_full = os.path.abspath(object_path)
|
||||||
assert_edit_allowed(object_xml_full, "editable")
|
assert_edit_allowed(object_xml_full, "editable")
|
||||||
format_version = detect_format_version(os.path.dirname(object_xml_full))
|
# Версию берём прежде всего из корня самого объекта — он её несёт всегда, а у автономной
|
||||||
|
# внешней обработки/отчёта подниматься к Configuration.xml просто некуда.
|
||||||
|
format_version = None
|
||||||
|
with open(object_xml_full, "r", encoding="utf-8-sig") as f:
|
||||||
|
obj_head = f.read(2000)
|
||||||
|
m_ver = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', obj_head)
|
||||||
|
if m_ver:
|
||||||
|
format_version = m_ver.group(1)
|
||||||
|
if not format_version:
|
||||||
|
format_version = detect_format_version(os.path.dirname(object_xml_full))
|
||||||
|
|
||||||
|
# Объявления пространств имён — одной переменной на корень: места эмиссии их только
|
||||||
|
# подставляют. Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
|
||||||
|
xmlns_decl = (
|
||||||
|
'xmlns="http://v8.1c.ru/8.3/MDClasses"'
|
||||||
|
' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"'
|
||||||
|
' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"'
|
||||||
|
' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"'
|
||||||
|
' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"'
|
||||||
|
' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"'
|
||||||
|
' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
|
||||||
|
' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"'
|
||||||
|
' xmlns:v8="http://v8.1c.ru/8.1/data/core"'
|
||||||
|
' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"'
|
||||||
|
' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"'
|
||||||
|
' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"'
|
||||||
|
' xmlns:xen="http://v8.1c.ru/8.3/xcf/enums"'
|
||||||
|
' xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef"'
|
||||||
|
' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
|
||||||
|
' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
||||||
|
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||||
|
)
|
||||||
|
form_ns_decl = (
|
||||||
|
'xmlns="http://v8.1c.ru/8.3/xcf/logform"'
|
||||||
|
' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"'
|
||||||
|
' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"'
|
||||||
|
' 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:ent="http://v8.1c.ru/8.1/data/enterprise"'
|
||||||
|
' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"'
|
||||||
|
' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
|
||||||
|
' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"'
|
||||||
|
' xmlns:v8="http://v8.1c.ru/8.1/data/core"'
|
||||||
|
' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"'
|
||||||
|
' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"'
|
||||||
|
' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"'
|
||||||
|
' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
|
||||||
|
' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
||||||
|
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
|
||||||
|
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
|
||||||
|
# дописать в конец нельзя.
|
||||||
|
if format_rank(format_version) >= 221:
|
||||||
|
pal = ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
|
||||||
|
xmlns_decl = xmlns_decl.replace(' xmlns:style=', pal)
|
||||||
|
form_ns_decl = form_ns_decl.replace(' xmlns:style=', pal)
|
||||||
|
|
||||||
parser_xml = etree.XMLParser(remove_blank_text=False)
|
parser_xml = etree.XMLParser(remove_blank_text=False)
|
||||||
tree = etree.parse(object_xml_full, parser_xml)
|
tree = etree.parse(object_xml_full, parser_xml)
|
||||||
@@ -388,24 +498,7 @@ def main():
|
|||||||
|
|
||||||
form_meta_xml = (
|
form_meta_xml = (
|
||||||
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||||
'<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses"'
|
f'<MetaDataObject {xmlns_decl} version="{format_version}">\n'
|
||||||
' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"'
|
|
||||||
' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"'
|
|
||||||
' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"'
|
|
||||||
' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"'
|
|
||||||
' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"'
|
|
||||||
' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
|
|
||||||
' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"'
|
|
||||||
' xmlns:v8="http://v8.1c.ru/8.1/data/core"'
|
|
||||||
' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"'
|
|
||||||
' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"'
|
|
||||||
' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"'
|
|
||||||
' xmlns:xen="http://v8.1c.ru/8.3/xcf/enums"'
|
|
||||||
' xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef"'
|
|
||||||
' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
|
|
||||||
' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
|
||||||
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
|
||||||
f' version="{format_version}">\n'
|
|
||||||
f'\t<Form uuid="{form_uuid}">\n'
|
f'\t<Form uuid="{form_uuid}">\n'
|
||||||
'\t\t<Properties>\n'
|
'\t\t<Properties>\n'
|
||||||
f'\t\t\t<Name>{form_name}</Name>\n'
|
f'\t\t\t<Name>{form_name}</Name>\n'
|
||||||
@@ -422,37 +515,22 @@ def main():
|
|||||||
'\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>\n'
|
'\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>\n'
|
||||||
'\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>\n'
|
'\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>\n'
|
||||||
'\t\t\t</UsePurposes>\n'
|
'\t\t\t</UsePurposes>\n'
|
||||||
|
# Использование в режиме совместимости интерфейса — свойство формата 2.21 (8.5),
|
||||||
|
# сразу после UsePurposes (проверено по выгрузке 8.5, до ExtendedPresentation).
|
||||||
|
+ ('\t\t\t<UseInInterfaceCompatibilityMode>Any</UseInInterfaceCompatibilityMode>\n'
|
||||||
|
if format_rank(format_version) >= 221 else '')
|
||||||
+ ('\t\t\t<ExtendedPresentation/>\n' if object_type in processor_like_types else '')
|
+ ('\t\t\t<ExtendedPresentation/>\n' if object_type in processor_like_types else '')
|
||||||
+ '\t\t</Properties>\n'
|
+ '\t\t</Properties>\n'
|
||||||
'\t</Form>\n'
|
'\t</Form>\n'
|
||||||
'</MetaDataObject>'
|
'</MetaDataObject>'
|
||||||
)
|
)
|
||||||
|
|
||||||
write_text_with_bom(form_meta_path, form_meta_xml)
|
write_xml_file(form_meta_path, form_meta_xml)
|
||||||
|
|
||||||
# --- 3b. Form.xml ---
|
# --- 3b. Form.xml ---
|
||||||
|
|
||||||
form_xml_path = os.path.join(form_ext_dir, "Form.xml")
|
form_xml_path = os.path.join(form_ext_dir, "Form.xml")
|
||||||
|
|
||||||
form_ns_decl = (
|
|
||||||
'xmlns="http://v8.1c.ru/8.3/xcf/logform"'
|
|
||||||
' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"'
|
|
||||||
' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"'
|
|
||||||
' 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:ent="http://v8.1c.ru/8.1/data/enterprise"'
|
|
||||||
' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"'
|
|
||||||
' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
|
|
||||||
' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"'
|
|
||||||
' xmlns:v8="http://v8.1c.ru/8.1/data/core"'
|
|
||||||
' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"'
|
|
||||||
' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"'
|
|
||||||
' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"'
|
|
||||||
' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
|
|
||||||
' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
|
||||||
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
|
||||||
)
|
|
||||||
|
|
||||||
if purpose in ("List", "Choice"):
|
if purpose in ("List", "Choice"):
|
||||||
# Dynamic list
|
# Dynamic list
|
||||||
main_table = f"{object_type}.{object_name}"
|
main_table = f"{object_type}.{object_name}"
|
||||||
@@ -551,7 +629,7 @@ def main():
|
|||||||
if os.path.exists(form_xml_path):
|
if os.path.exists(form_xml_path):
|
||||||
print(f"[SKIP] Form.xml already exists: {form_xml_path} — not overwriting")
|
print(f"[SKIP] Form.xml already exists: {form_xml_path} — not overwriting")
|
||||||
else:
|
else:
|
||||||
write_text_with_bom(form_xml_path, form_xml)
|
write_xml_file(form_xml_path, form_xml)
|
||||||
|
|
||||||
# --- 3c. Module.bsl ---
|
# --- 3c. Module.bsl ---
|
||||||
|
|
||||||
@@ -582,7 +660,10 @@ def main():
|
|||||||
if os.path.exists(module_path):
|
if os.path.exists(module_path):
|
||||||
print(f"[SKIP] Module.bsl already exists: {module_path} — not overwriting")
|
print(f"[SKIP] Module.bsl already exists: {module_path} — not overwriting")
|
||||||
else:
|
else:
|
||||||
write_text_with_bom(module_path, module_bsl)
|
# Модуль пишем в каноне платформы: CRLF в разделителях строк (корпус: 2643 CRLF,
|
||||||
|
# чисто-LF 0 из 3001). Хвостовой перевод НЕ навязываем — у платформы он
|
||||||
|
# неканоничен (1235 модулей с ним, 766 без).
|
||||||
|
write_utf8_bom(module_path, module_bsl.replace('\r\n', '\n').replace('\n', '\r\n'))
|
||||||
|
|
||||||
# --- Phase 4: Register in parent object ---
|
# --- Phase 4: Register in parent object ---
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# form-compile v1.176 — Compile 1C managed form from JSON or object metadata
|
# form-compile v1.191 — Compile 1C managed form from JSON or object metadata (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[string]$JsonPath,
|
[string]$JsonPath,
|
||||||
@@ -1335,6 +1335,14 @@ function Generate-ChartOfAccountsChoiceDSL($meta, [hashtable]$presetData) {
|
|||||||
function Detect-FormatVersion([string]$dir) {
|
function Detect-FormatVersion([string]$dir) {
|
||||||
$d = $dir
|
$d = $dir
|
||||||
while ($d) {
|
while ($d) {
|
||||||
|
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||||
|
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||||
|
$extPath = "$d.xml"
|
||||||
|
if (Test-Path $extPath) {
|
||||||
|
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
|
||||||
|
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
|
||||||
|
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||||
|
}
|
||||||
$cfgPath = Join-Path $d "Configuration.xml"
|
$cfgPath = Join-Path $d "Configuration.xml"
|
||||||
if (Test-Path $cfgPath) {
|
if (Test-Path $cfgPath) {
|
||||||
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
||||||
@@ -1350,6 +1358,13 @@ function Detect-FormatVersion([string]$dir) {
|
|||||||
return "2.17"
|
return "2.17"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209.
|
||||||
|
# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка.
|
||||||
|
function Get-FormatRank([string]$ver) {
|
||||||
|
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# --- Support guard (Ext/ParentConfigurations.bin) ---
|
# --- Support guard (Ext/ParentConfigurations.bin) ---
|
||||||
# See docs/1c-support-state-spec.md. Blocks edits of vendor objects "на замке" /
|
# See docs/1c-support-state-spec.md. Blocks edits of vendor objects "на замке" /
|
||||||
# read-only configs unless allowed. Trigger = bin present; reaction from
|
# read-only configs unless allowed. Trigger = bin present; reaction from
|
||||||
@@ -1485,6 +1500,17 @@ $script:outPathResolved = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $Ou
|
|||||||
Assert-EditAllowed $script:outPathResolved 'editable'
|
Assert-EditAllowed $script:outPathResolved 'editable'
|
||||||
$script:formatVersion = Detect-FormatVersion ([System.IO.Path]::GetDirectoryName($script:outPathResolved))
|
$script:formatVersion = Detect-FormatVersion ([System.IO.Path]::GetDirectoryName($script:outPathResolved))
|
||||||
|
|
||||||
|
# Объявления пространств имён — одной переменной: место эмиссии её только интерполирует.
|
||||||
|
# Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
|
||||||
|
$script:formNsDecl = 'xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcssch="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||||
|
|
||||||
|
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
|
||||||
|
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
|
||||||
|
# дописать в конец нельзя.
|
||||||
|
if ((Get-FormatRank $script:formatVersion) -ge 221) {
|
||||||
|
$script:formNsDecl = $script:formNsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
|
||||||
|
}
|
||||||
|
|
||||||
# --- 0. Path normalization and mode dispatch ---
|
# --- 0. Path normalization and mode dispatch ---
|
||||||
|
|
||||||
# Form name → purpose mapping
|
# Form name → purpose mapping
|
||||||
@@ -1679,6 +1705,12 @@ function X {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Esc-Xml {
|
function Esc-Xml {
|
||||||
|
param([string]$s)
|
||||||
|
# Эскейп ЗНАЧЕНИЯ АТРИБУТА: & < > и кавычка — внутри "..." литеральная " невалидна.
|
||||||
|
return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"')
|
||||||
|
}
|
||||||
|
|
||||||
|
function Esc-XmlText {
|
||||||
# Экранирование ТЕКСТА элемента (<v8:content>, <Value>): только & < > .
|
# Экранирование ТЕКСТА элемента (<v8:content>, <Value>): только & < > .
|
||||||
# Кавычки/апострофы в тексте экранировать НЕ нужно (1С их не экранирует — пишет литерально);
|
# Кавычки/апострофы в тексте экранировать НЕ нужно (1С их не экранирует — пишет литерально);
|
||||||
# " ломал бы раундтрип. Кавычки спецсимвольны лишь в значениях атрибутов.
|
# " ломал бы раундтрип. Кавычки спецсимвольны лишь в значениях атрибутов.
|
||||||
@@ -1693,14 +1725,14 @@ function Emit-MLItems {
|
|||||||
param($val, [string]$indent)
|
param($val, [string]$indent)
|
||||||
if ($val -is [System.Collections.IDictionary]) {
|
if ($val -is [System.Collections.IDictionary]) {
|
||||||
foreach ($k in $val.Keys) {
|
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>"
|
X "$indent<v8:item>"; X "$indent`t<v8:lang>$k</v8:lang>"; X "$indent`t<v8:content>$(Esc-XmlText "$($val[$k])")</v8:content>"; X "$indent</v8:item>"
|
||||||
}
|
}
|
||||||
} elseif ($val -is [System.Management.Automation.PSCustomObject]) {
|
} elseif ($val -is [System.Management.Automation.PSCustomObject]) {
|
||||||
foreach ($p in $val.PSObject.Properties) {
|
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>"
|
X "$indent<v8:item>"; X "$indent`t<v8:lang>$($p.Name)</v8:lang>"; X "$indent`t<v8:content>$(Esc-XmlText "$($p.Value)")</v8:content>"; X "$indent</v8:item>"
|
||||||
}
|
}
|
||||||
} else {
|
} 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>"
|
X "$indent<v8:item>"; X "$indent`t<v8:lang>ru</v8:lang>"; X "$indent`t<v8:content>$(Esc-XmlText "$val")</v8:content>"; X "$indent</v8:item>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1719,7 +1751,7 @@ function Emit-USPresentation {
|
|||||||
param($val, [string]$tag, [string]$indent)
|
param($val, [string]$tag, [string]$indent)
|
||||||
if ($null -eq $val) { return }
|
if ($null -eq $val) { return }
|
||||||
if ($val -is [string]) {
|
if ($val -is [string]) {
|
||||||
X "$indent<$tag xsi:type=`"xs:string`">$(Esc-Xml $val)</$tag>"
|
X "$indent<$tag xsi:type=`"xs:string`">$(Esc-XmlText $val)</$tag>"
|
||||||
} else {
|
} else {
|
||||||
Emit-MLText -tag $tag -text $val -indent $indent -xsiType "v8:LocalStringType"
|
Emit-MLText -tag $tag -text $val -indent $indent -xsiType "v8:LocalStringType"
|
||||||
}
|
}
|
||||||
@@ -1848,10 +1880,10 @@ function Emit-FilterItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($item.presentation) { Emit-USPresentation -val $item.presentation -tag "dcsset:presentation" -indent "$indent`t" }
|
if ($item.presentation) { Emit-USPresentation -val $item.presentation -tag "dcsset:presentation" -indent "$indent`t" }
|
||||||
if ($item.viewMode) { X "$indent`t<dcsset:viewMode>$(Esc-Xml "$($item.viewMode)")</dcsset:viewMode>" }
|
if ($item.viewMode) { X "$indent`t<dcsset:viewMode>$(Esc-XmlText "$($item.viewMode)")</dcsset:viewMode>" }
|
||||||
if ($item.userSettingID) {
|
if ($item.userSettingID) {
|
||||||
$guid = if ("$($item.userSettingID)" -eq "auto") { New-Guid-String } else { "$($item.userSettingID)" }
|
$guid = if ("$($item.userSettingID)" -eq "auto") { New-Guid-String } else { "$($item.userSettingID)" }
|
||||||
X "$indent`t<dcsset:userSettingID>$(Esc-Xml $guid)</dcsset:userSettingID>"
|
X "$indent`t<dcsset:userSettingID>$(Esc-XmlText $guid)</dcsset:userSettingID>"
|
||||||
}
|
}
|
||||||
if ($item.userSettingPresentation) { Emit-USPresentation -val $item.userSettingPresentation -tag "dcsset:userSettingPresentation" -indent "$indent`t" }
|
if ($item.userSettingPresentation) { Emit-USPresentation -val $item.userSettingPresentation -tag "dcsset:userSettingPresentation" -indent "$indent`t" }
|
||||||
X "$indent</dcsset:item>"
|
X "$indent</dcsset:item>"
|
||||||
@@ -1859,10 +1891,10 @@ function Emit-FilterItem {
|
|||||||
}
|
}
|
||||||
X "$indent<dcsset:item xsi:type=`"dcsset:FilterItemComparison`">"
|
X "$indent<dcsset:item xsi:type=`"dcsset:FilterItemComparison`">"
|
||||||
if ($item.use -eq $false) { X "$indent`t<dcsset:use>false</dcsset:use>" }
|
if ($item.use -eq $false) { X "$indent`t<dcsset:use>false</dcsset:use>" }
|
||||||
X "$indent`t<dcsset:left xsi:type=`"dcscor:Field`">$(Esc-Xml "$($item.field)")</dcsset:left>"
|
X "$indent`t<dcsset:left xsi:type=`"dcscor:Field`">$(Esc-XmlText "$($item.field)")</dcsset:left>"
|
||||||
$compType = $script:comparisonTypes["$($item.op)"]
|
$compType = $script:comparisonTypes["$($item.op)"]
|
||||||
if (-not $compType) { $compType = "$($item.op)" }
|
if (-not $compType) { $compType = "$($item.op)" }
|
||||||
X "$indent`t<dcsset:comparisonType>$(Esc-Xml $compType)</dcsset:comparisonType>"
|
X "$indent`t<dcsset:comparisonType>$(Esc-XmlText $compType)</dcsset:comparisonType>"
|
||||||
$valIsArray = ($item.value -is [array]) -or ($item.value -is [System.Collections.IList] -and $item.value -isnot [string])
|
$valIsArray = ($item.value -is [array]) -or ($item.value -is [System.Collections.IList] -and $item.value -isnot [string])
|
||||||
if ($valIsArray) {
|
if ($valIsArray) {
|
||||||
if (@($item.value).Count -eq 0) {
|
if (@($item.value).Count -eq 0) {
|
||||||
@@ -1881,7 +1913,7 @@ function Emit-FilterItem {
|
|||||||
elseif ("$v" -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена|Catalog|Enum|Document|ChartOfAccounts|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.') { $vt = 'dcscor:DesignTimeValue' }
|
elseif ("$v" -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена|Catalog|Enum|Document|ChartOfAccounts|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.') { $vt = 'dcscor:DesignTimeValue' }
|
||||||
else { $vt = 'xs:string' }
|
else { $vt = 'xs:string' }
|
||||||
}
|
}
|
||||||
$vStr = if ($v -is [bool]) { "$v".ToLower() } else { Esc-Xml "$v" }
|
$vStr = if ($v -is [bool]) { "$v".ToLower() } else { Esc-XmlText "$v" }
|
||||||
$nsAttr = Get-ValueTypeNsAttr -valueType $vt -value "$v"
|
$nsAttr = Get-ValueTypeNsAttr -valueType $vt -value "$v"
|
||||||
X "$indent`t<dcsset:right$nsAttr xsi:type=`"$vt`">$vStr</dcsset:right>"
|
X "$indent`t<dcsset:right$nsAttr xsi:type=`"$vt`">$vStr</dcsset:right>"
|
||||||
}
|
}
|
||||||
@@ -1907,8 +1939,8 @@ function Emit-FilterItem {
|
|||||||
$variant = "$sv"; $hasDate = $false; $dateV = $null
|
$variant = "$sv"; $hasDate = $false; $dateV = $null
|
||||||
}
|
}
|
||||||
X "$indent`t<dcsset:right xsi:type=`"v8:$sdType`">"
|
X "$indent`t<dcsset:right xsi:type=`"v8:$sdType`">"
|
||||||
X "$indent`t`t<v8:variant xsi:type=`"v8:${sdType}Variant`">$(Esc-Xml $variant)</v8:variant>"
|
X "$indent`t`t<v8:variant xsi:type=`"v8:${sdType}Variant`">$(Esc-XmlText $variant)</v8:variant>"
|
||||||
if ($hasDate) { X "$indent`t`t<v8:date>$(Esc-Xml $dateV)</v8:date>" }
|
if ($hasDate) { X "$indent`t`t<v8:date>$(Esc-XmlText $dateV)</v8:date>" }
|
||||||
X "$indent`t</dcsset:right>"
|
X "$indent`t</dcsset:right>"
|
||||||
} elseif ("$($item.value)" -eq '_') {
|
} elseif ("$($item.value)" -eq '_') {
|
||||||
# "_" — маркер пустого значения: платформа эмитит пустой self-closing <dcsset:right>
|
# "_" — маркер пустого значения: платформа эмитит пустой self-closing <dcsset:right>
|
||||||
@@ -1926,15 +1958,15 @@ function Emit-FilterItem {
|
|||||||
elseif ("$v" -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена|Catalog|Enum|Document|ChartOfAccounts|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.') { $vt = "dcscor:DesignTimeValue" }
|
elseif ("$v" -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена|Catalog|Enum|Document|ChartOfAccounts|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.') { $vt = "dcscor:DesignTimeValue" }
|
||||||
else { $vt = "xs:string" }
|
else { $vt = "xs:string" }
|
||||||
}
|
}
|
||||||
$vStr = if ($item.value -is [bool]) { "$($item.value)".ToLower() } else { Esc-Xml "$($item.value)" }
|
$vStr = if ($item.value -is [bool]) { "$($item.value)".ToLower() } else { Esc-XmlText "$($item.value)" }
|
||||||
$nsAttr = Get-ValueTypeNsAttr -valueType $vt -value "$($item.value)"
|
$nsAttr = Get-ValueTypeNsAttr -valueType $vt -value "$($item.value)"
|
||||||
X "$indent`t<dcsset:right$nsAttr xsi:type=`"$vt`">$vStr</dcsset:right>"
|
X "$indent`t<dcsset:right$nsAttr xsi:type=`"$vt`">$vStr</dcsset:right>"
|
||||||
}
|
}
|
||||||
if ($item.presentation) { Emit-USPresentation -val $item.presentation -tag "dcsset:presentation" -indent "$indent`t" }
|
if ($item.presentation) { Emit-USPresentation -val $item.presentation -tag "dcsset:presentation" -indent "$indent`t" }
|
||||||
if ($item.viewMode) { X "$indent`t<dcsset:viewMode>$(Esc-Xml "$($item.viewMode)")</dcsset:viewMode>" }
|
if ($item.viewMode) { X "$indent`t<dcsset:viewMode>$(Esc-XmlText "$($item.viewMode)")</dcsset:viewMode>" }
|
||||||
if ($item.userSettingID) {
|
if ($item.userSettingID) {
|
||||||
$uid = if ("$($item.userSettingID)" -eq "auto") { New-Guid-String } else { "$($item.userSettingID)" }
|
$uid = if ("$($item.userSettingID)" -eq "auto") { New-Guid-String } else { "$($item.userSettingID)" }
|
||||||
X "$indent`t<dcsset:userSettingID>$(Esc-Xml $uid)</dcsset:userSettingID>"
|
X "$indent`t<dcsset:userSettingID>$(Esc-XmlText $uid)</dcsset:userSettingID>"
|
||||||
}
|
}
|
||||||
if ($item.userSettingPresentation) { Emit-USPresentation -val $item.userSettingPresentation -tag "dcsset:userSettingPresentation" -indent "$indent`t" }
|
if ($item.userSettingPresentation) { Emit-USPresentation -val $item.userSettingPresentation -tag "dcsset:userSettingPresentation" -indent "$indent`t" }
|
||||||
X "$indent</dcsset:item>"
|
X "$indent</dcsset:item>"
|
||||||
@@ -1958,10 +1990,10 @@ function Emit-Filter {
|
|||||||
Emit-FilterItem -item ([pscustomobject]$obj) -indent "$indent`t"
|
Emit-FilterItem -item ([pscustomobject]$obj) -indent "$indent`t"
|
||||||
} else { Emit-FilterItem -item $item -indent "$indent`t" }
|
} else { Emit-FilterItem -item $item -indent "$indent`t" }
|
||||||
}
|
}
|
||||||
if ($null -ne $blockViewMode) { X "$indent`t<dcsset:viewMode>$(Esc-Xml "$blockViewMode")</dcsset:viewMode>" }
|
if ($null -ne $blockViewMode) { X "$indent`t<dcsset:viewMode>$(Esc-XmlText "$blockViewMode")</dcsset:viewMode>" }
|
||||||
if ($null -ne $blockUserSettingID) {
|
if ($null -ne $blockUserSettingID) {
|
||||||
$uid = if ("$blockUserSettingID" -eq 'auto') { New-Guid-String } else { "$blockUserSettingID" }
|
$uid = if ("$blockUserSettingID" -eq 'auto') { New-Guid-String } else { "$blockUserSettingID" }
|
||||||
X "$indent`t<dcsset:userSettingID>$(Esc-Xml $uid)</dcsset:userSettingID>"
|
X "$indent`t<dcsset:userSettingID>$(Esc-XmlText $uid)</dcsset:userSettingID>"
|
||||||
}
|
}
|
||||||
if ($null -ne $blockUserSettingPresentation) { Emit-USPresentation -val $blockUserSettingPresentation -tag "dcsset:userSettingPresentation" -indent "$indent`t" }
|
if ($null -ne $blockUserSettingPresentation) { Emit-USPresentation -val $blockUserSettingPresentation -tag "dcsset:userSettingPresentation" -indent "$indent`t" }
|
||||||
X "$indent</dcsset:filter>"
|
X "$indent</dcsset:filter>"
|
||||||
@@ -1983,7 +2015,7 @@ function Emit-Order {
|
|||||||
if ($parts.Count -gt 1 -and $parts[1] -match '^(?i)(desc|убыв)') { $dir = "Desc" }
|
if ($parts.Count -gt 1 -and $parts[1] -match '^(?i)(desc|убыв)') { $dir = "Desc" }
|
||||||
elseif ($parts.Count -gt 1 -and $parts[1] -match '^(?i)(asc|возр)') { $dir = "Asc" }
|
elseif ($parts.Count -gt 1 -and $parts[1] -match '^(?i)(asc|возр)') { $dir = "Asc" }
|
||||||
X "$indent`t<dcsset:item xsi:type=`"dcsset:OrderItemField`">"
|
X "$indent`t<dcsset:item xsi:type=`"dcsset:OrderItemField`">"
|
||||||
X "$indent`t`t<dcsset:field>$(Esc-Xml $field)</dcsset:field>"
|
X "$indent`t`t<dcsset:field>$(Esc-XmlText $field)</dcsset:field>"
|
||||||
X "$indent`t`t<dcsset:orderType>$dir</dcsset:orderType>"
|
X "$indent`t`t<dcsset:orderType>$dir</dcsset:orderType>"
|
||||||
X "$indent`t</dcsset:item>"
|
X "$indent`t</dcsset:item>"
|
||||||
}
|
}
|
||||||
@@ -1993,16 +2025,16 @@ function Emit-Order {
|
|||||||
if ($dir -match '^(?i)(desc|убыв)') { $dir = "Desc" } elseif ($dir -match '^(?i)(asc|возр)') { $dir = "Asc" }
|
if ($dir -match '^(?i)(desc|убыв)') { $dir = "Desc" } elseif ($dir -match '^(?i)(asc|возр)') { $dir = "Asc" }
|
||||||
X "$indent`t<dcsset:item xsi:type=`"dcsset:OrderItemField`">"
|
X "$indent`t<dcsset:item xsi:type=`"dcsset:OrderItemField`">"
|
||||||
if ($item.use -eq $false) { X "$indent`t`t<dcsset:use>false</dcsset:use>" }
|
if ($item.use -eq $false) { X "$indent`t`t<dcsset:use>false</dcsset:use>" }
|
||||||
X "$indent`t`t<dcsset:field>$(Esc-Xml "$($item.field)")</dcsset:field>"
|
X "$indent`t`t<dcsset:field>$(Esc-XmlText "$($item.field)")</dcsset:field>"
|
||||||
X "$indent`t`t<dcsset:orderType>$dir</dcsset:orderType>"
|
X "$indent`t`t<dcsset:orderType>$dir</dcsset:orderType>"
|
||||||
if ($item.viewMode) { X "$indent`t`t<dcsset:viewMode>$(Esc-Xml "$($item.viewMode)")</dcsset:viewMode>" }
|
if ($item.viewMode) { X "$indent`t`t<dcsset:viewMode>$(Esc-XmlText "$($item.viewMode)")</dcsset:viewMode>" }
|
||||||
X "$indent`t</dcsset:item>"
|
X "$indent`t</dcsset:item>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($null -ne $blockViewMode) { X "$indent`t<dcsset:viewMode>$(Esc-Xml "$blockViewMode")</dcsset:viewMode>" }
|
if ($null -ne $blockViewMode) { X "$indent`t<dcsset:viewMode>$(Esc-XmlText "$blockViewMode")</dcsset:viewMode>" }
|
||||||
if ($null -ne $blockUserSettingID) {
|
if ($null -ne $blockUserSettingID) {
|
||||||
$uid = if ("$blockUserSettingID" -eq 'auto') { New-Guid-String } else { "$blockUserSettingID" }
|
$uid = if ("$blockUserSettingID" -eq 'auto') { New-Guid-String } else { "$blockUserSettingID" }
|
||||||
X "$indent`t<dcsset:userSettingID>$(Esc-Xml $uid)</dcsset:userSettingID>"
|
X "$indent`t<dcsset:userSettingID>$(Esc-XmlText $uid)</dcsset:userSettingID>"
|
||||||
}
|
}
|
||||||
if ($null -ne $blockUserSettingPresentation) { Emit-USPresentation -val $blockUserSettingPresentation -tag "dcsset:userSettingPresentation" -indent "$indent`t" }
|
if ($null -ne $blockUserSettingPresentation) { Emit-USPresentation -val $blockUserSettingPresentation -tag "dcsset:userSettingPresentation" -indent "$indent`t" }
|
||||||
X "$indent</dcsset:order>"
|
X "$indent</dcsset:order>"
|
||||||
@@ -2034,7 +2066,7 @@ function Emit-AppearanceValue {
|
|||||||
if (_HasKey $val 'items') { $nestedItems = (_Get $val 'items') }
|
if (_HasKey $val 'items') { $nestedItems = (_Get $val 'items') }
|
||||||
}
|
}
|
||||||
if ($useWrapper) { X "$indent`t<dcscor:use>false</dcscor:use>" }
|
if ($useWrapper) { X "$indent`t<dcscor:use>false</dcscor:use>" }
|
||||||
X "$indent`t<dcscor:parameter>$(Esc-Xml $key)</dcscor:parameter>"
|
X "$indent`t<dcscor:parameter>$(Esc-XmlText $key)</dcscor:parameter>"
|
||||||
$isFontDict = $false
|
$isFontDict = $false
|
||||||
if ($innerVal -is [PSCustomObject]) {
|
if ($innerVal -is [PSCustomObject]) {
|
||||||
$tProp = $innerVal.PSObject.Properties['@type']
|
$tProp = $innerVal.PSObject.Properties['@type']
|
||||||
@@ -2050,7 +2082,7 @@ function Emit-AppearanceValue {
|
|||||||
$lg = if (_HasKey $innerVal 'gap') { if ((_Get $innerVal 'gap')) { 'true' } else { 'false' } } else { 'false' }
|
$lg = if (_HasKey $innerVal 'gap') { if ((_Get $innerVal 'gap')) { 'true' } else { 'false' } } else { 'false' }
|
||||||
$ls = if (_HasKey $innerVal 'style') { "$(_Get $innerVal 'style')" } else { 'None' }
|
$ls = if (_HasKey $innerVal 'style') { "$(_Get $innerVal 'style')" } else { 'None' }
|
||||||
X "$indent`t<dcscor:value xsi:type=`"v8ui:Line`" width=`"$lw`" gap=`"$lg`">"
|
X "$indent`t<dcscor:value xsi:type=`"v8ui:Line`" width=`"$lw`" gap=`"$lg`">"
|
||||||
X "$indent`t`t<v8ui:style xsi:type=`"v8ui:SpreadsheetDocumentCellLineType`">$(Esc-Xml $ls)</v8ui:style>"
|
X "$indent`t`t<v8ui:style xsi:type=`"v8ui:SpreadsheetDocumentCellLineType`">$(Esc-XmlText $ls)</v8ui:style>"
|
||||||
X "$indent`t</dcscor:value>"
|
X "$indent`t</dcscor:value>"
|
||||||
} elseif ($isFontDict) {
|
} elseif ($isFontDict) {
|
||||||
$attrParts = @()
|
$attrParts = @()
|
||||||
@@ -2063,7 +2095,7 @@ function Emit-AppearanceValue {
|
|||||||
X "$indent`t<dcscor:value xsi:type=`"v8ui:Font`" $($attrParts -join ' ')/>"
|
X "$indent`t<dcscor:value xsi:type=`"v8ui:Font`" $($attrParts -join ' ')/>"
|
||||||
} elseif ($isDict -and (_HasKey $innerVal 'field')) {
|
} elseif ($isDict -and (_HasKey $innerVal 'field')) {
|
||||||
# Ссылка на поле (dcscor:Field) — значение параметра оформления = поле компоновки
|
# Ссылка на поле (dcscor:Field) — значение параметра оформления = поле компоновки
|
||||||
X "$indent`t<dcscor:value xsi:type=`"dcscor:Field`">$(Esc-Xml "$(_Get $innerVal 'field')")</dcscor:value>"
|
X "$indent`t<dcscor:value xsi:type=`"dcscor:Field`">$(Esc-XmlText "$(_Get $innerVal 'field')")</dcscor:value>"
|
||||||
} elseif ($isDict) {
|
} elseif ($isDict) {
|
||||||
# Локализуемый текст параметра оформления: платформа объявляет xsi:type на dcscor:value
|
# Локализуемый текст параметра оформления: платформа объявляет xsi:type на dcscor:value
|
||||||
Emit-MLText -tag "dcscor:value" -text $innerVal -indent "$indent`t" -xsiType "v8:LocalStringType"
|
Emit-MLText -tag "dcscor:value" -text $innerVal -indent "$indent`t" -xsiType "v8:LocalStringType"
|
||||||
@@ -2078,19 +2110,19 @@ function Emit-AppearanceValue {
|
|||||||
'ТипМакета' = 'dcsset:DataCompositionGroupTemplateType'
|
'ТипМакета' = 'dcsset:DataCompositionGroupTemplateType'
|
||||||
}
|
}
|
||||||
$keyType = $keyTypeMap[$key]
|
$keyType = $keyTypeMap[$key]
|
||||||
if ($keyType) { X "$indent`t<dcscor:value xsi:type=`"$keyType`">$(Esc-Xml $actualVal)</dcscor:value>" }
|
if ($keyType) { X "$indent`t<dcscor:value xsi:type=`"$keyType`">$(Esc-XmlText $actualVal)</dcscor:value>" }
|
||||||
elseif ($actualVal -match '^(style|web|win):') { X "$indent`t<dcscor:value xsi:type=`"v8ui:Color`">$(Esc-Xml $actualVal)</dcscor:value>" }
|
elseif ($actualVal -match '^(style|web|win):') { X "$indent`t<dcscor:value xsi:type=`"v8ui:Color`">$(Esc-XmlText $actualVal)</dcscor:value>" }
|
||||||
elseif ($actualVal -eq "true" -or $actualVal -eq "false") { X "$indent`t<dcscor:value xsi:type=`"xs:boolean`">$actualVal</dcscor:value>" }
|
elseif ($actualVal -eq "true" -or $actualVal -eq "false") { X "$indent`t<dcscor:value xsi:type=`"xs:boolean`">$actualVal</dcscor:value>" }
|
||||||
elseif ($key -eq "Текст" -or $key -eq "Заголовок" -or $key -eq "Формат") {
|
elseif ($key -eq "Текст" -or $key -eq "Заголовок" -or $key -eq "Формат") {
|
||||||
# Текст/Заголовок/Формат: голая строка = плоский xs:string (так платформа хранит
|
# Текст/Заголовок/Формат: голая строка = плоский xs:string (так платформа хранит
|
||||||
# нелокализованный литерал). Локализуемый текст → объект {ru,en} (ветка isDict выше).
|
# нелокализованный литерал). Локализуемый текст → объект {ru,en} (ветка isDict выше).
|
||||||
# Пустая строка → самозакрывающийся тег (как у платформы).
|
# Пустая строка → самозакрывающийся тег (как у платформы).
|
||||||
if ($actualVal -eq '') { X "$indent`t<dcscor:value xsi:type=`"xs:string`"/>" }
|
if ($actualVal -eq '') { X "$indent`t<dcscor:value xsi:type=`"xs:string`"/>" }
|
||||||
else { X "$indent`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml $actualVal)</dcscor:value>" }
|
else { X "$indent`t<dcscor:value xsi:type=`"xs:string`">$(Esc-XmlText $actualVal)</dcscor:value>" }
|
||||||
}
|
}
|
||||||
elseif ($actualVal -match '^-?\d+(\.\d+)?$') { X "$indent`t<dcscor:value xsi:type=`"xs:decimal`">$actualVal</dcscor:value>" }
|
elseif ($actualVal -match '^-?\d+(\.\d+)?$') { X "$indent`t<dcscor:value xsi:type=`"xs:decimal`">$actualVal</dcscor:value>" }
|
||||||
elseif ($key -eq 'ЦветТекста' -or $key -eq 'ЦветФона' -or $key -eq 'ЦветГраницы') { X "$indent`t<dcscor:value xsi:type=`"v8ui:Color`">$(Esc-Xml $actualVal)</dcscor:value>" }
|
elseif ($key -eq 'ЦветТекста' -or $key -eq 'ЦветФона' -or $key -eq 'ЦветГраницы') { X "$indent`t<dcscor:value xsi:type=`"v8ui:Color`">$(Esc-XmlText $actualVal)</dcscor:value>" }
|
||||||
else { X "$indent`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml $actualVal)</dcscor:value>" }
|
else { X "$indent`t<dcscor:value xsi:type=`"xs:string`">$(Esc-XmlText $actualVal)</dcscor:value>" }
|
||||||
}
|
}
|
||||||
if ($nestedItems) {
|
if ($nestedItems) {
|
||||||
$niProps = if ($nestedItems -is [PSCustomObject]) { $nestedItems.PSObject.Properties } else { $null }
|
$niProps = if ($nestedItems -is [PSCustomObject]) { $nestedItems.PSObject.Properties } else { $null }
|
||||||
@@ -2113,7 +2145,7 @@ function Emit-ConditionalAppearance {
|
|||||||
X "$indent`t`t<dcsset:selection>"
|
X "$indent`t`t<dcsset:selection>"
|
||||||
foreach ($sel in $ca.selection) {
|
foreach ($sel in $ca.selection) {
|
||||||
X "$indent`t`t`t<dcsset:item>"
|
X "$indent`t`t`t<dcsset:item>"
|
||||||
X "$indent`t`t`t`t<dcsset:field>$(Esc-Xml "$sel")</dcsset:field>"
|
X "$indent`t`t`t`t<dcsset:field>$(Esc-XmlText "$sel")</dcsset:field>"
|
||||||
X "$indent`t`t`t</dcsset:item>"
|
X "$indent`t`t`t</dcsset:item>"
|
||||||
}
|
}
|
||||||
X "$indent`t`t</dcsset:selection>"
|
X "$indent`t`t</dcsset:selection>"
|
||||||
@@ -2132,12 +2164,12 @@ function Emit-ConditionalAppearance {
|
|||||||
Emit-MLItems -val $ca.presentation -indent "$indent`t`t`t"
|
Emit-MLItems -val $ca.presentation -indent "$indent`t`t`t"
|
||||||
X "$indent`t`t</dcsset:presentation>"
|
X "$indent`t`t</dcsset:presentation>"
|
||||||
}
|
}
|
||||||
else { X "$indent`t`t<dcsset:presentation xsi:type=`"xs:string`">$(Esc-Xml "$($ca.presentation)")</dcsset:presentation>" }
|
else { X "$indent`t`t<dcsset:presentation xsi:type=`"xs:string`">$(Esc-XmlText "$($ca.presentation)")</dcsset:presentation>" }
|
||||||
}
|
}
|
||||||
if ($ca.viewMode) { X "$indent`t`t<dcsset:viewMode>$(Esc-Xml "$($ca.viewMode)")</dcsset:viewMode>" }
|
if ($ca.viewMode) { X "$indent`t`t<dcsset:viewMode>$(Esc-XmlText "$($ca.viewMode)")</dcsset:viewMode>" }
|
||||||
if ($ca.userSettingID) {
|
if ($ca.userSettingID) {
|
||||||
$uid = if ("$($ca.userSettingID)" -eq "auto") { New-Guid-String } else { "$($ca.userSettingID)" }
|
$uid = if ("$($ca.userSettingID)" -eq "auto") { New-Guid-String } else { "$($ca.userSettingID)" }
|
||||||
X "$indent`t`t<dcsset:userSettingID>$(Esc-Xml $uid)</dcsset:userSettingID>"
|
X "$indent`t`t<dcsset:userSettingID>$(Esc-XmlText $uid)</dcsset:userSettingID>"
|
||||||
}
|
}
|
||||||
if ($ca.userSettingPresentation) { Emit-USPresentation -val $ca.userSettingPresentation -tag "dcsset:userSettingPresentation" -indent "$indent`t`t" }
|
if ($ca.userSettingPresentation) { Emit-USPresentation -val $ca.userSettingPresentation -tag "dcsset:userSettingPresentation" -indent "$indent`t`t" }
|
||||||
if ($ca.useInDontUse -and $ca.useInDontUse.Count -gt 0) {
|
if ($ca.useInDontUse -and $ca.useInDontUse.Count -gt 0) {
|
||||||
@@ -2153,10 +2185,10 @@ function Emit-ConditionalAppearance {
|
|||||||
}
|
}
|
||||||
X "$indent`t</dcsset:item>"
|
X "$indent`t</dcsset:item>"
|
||||||
}
|
}
|
||||||
if ($null -ne $blockViewMode) { X "$indent`t<dcsset:viewMode>$(Esc-Xml "$blockViewMode")</dcsset:viewMode>" }
|
if ($null -ne $blockViewMode) { X "$indent`t<dcsset:viewMode>$(Esc-XmlText "$blockViewMode")</dcsset:viewMode>" }
|
||||||
if ($null -ne $blockUserSettingID) {
|
if ($null -ne $blockUserSettingID) {
|
||||||
$uid = if ("$blockUserSettingID" -eq 'auto') { New-Guid-String } else { "$blockUserSettingID" }
|
$uid = if ("$blockUserSettingID" -eq 'auto') { New-Guid-String } else { "$blockUserSettingID" }
|
||||||
X "$indent`t<dcsset:userSettingID>$(Esc-Xml $uid)</dcsset:userSettingID>"
|
X "$indent`t<dcsset:userSettingID>$(Esc-XmlText $uid)</dcsset:userSettingID>"
|
||||||
}
|
}
|
||||||
if ($null -ne $blockUserSettingPresentation) { Emit-USPresentation -val $blockUserSettingPresentation -tag "dcsset:userSettingPresentation" -indent "$indent`t" }
|
if ($null -ne $blockUserSettingPresentation) { Emit-USPresentation -val $blockUserSettingPresentation -tag "dcsset:userSettingPresentation" -indent "$indent`t" }
|
||||||
X "$indent</$wrapTag>"
|
X "$indent</$wrapTag>"
|
||||||
@@ -2195,14 +2227,14 @@ function Emit-GroupItemField {
|
|||||||
$pae = if ($level.periodAdditionEnd) { "$($level.periodAdditionEnd)" } else { '0001-01-01T00:00:00' }
|
$pae = if ($level.periodAdditionEnd) { "$($level.periodAdditionEnd)" } else { '0001-01-01T00:00:00' }
|
||||||
}
|
}
|
||||||
X "$indent<dcsset:item xsi:type=`"dcsset:GroupItemField`">"
|
X "$indent<dcsset:item xsi:type=`"dcsset:GroupItemField`">"
|
||||||
X "$indent`t<dcsset:field>$(Esc-Xml $field)</dcsset:field>"
|
X "$indent`t<dcsset:field>$(Esc-XmlText $field)</dcsset:field>"
|
||||||
X "$indent`t<dcsset:groupType>$(Esc-Xml $gt)</dcsset:groupType>"
|
X "$indent`t<dcsset:groupType>$(Esc-XmlText $gt)</dcsset:groupType>"
|
||||||
X "$indent`t<dcsset:periodAdditionType>$(Esc-Xml $pat)</dcsset:periodAdditionType>"
|
X "$indent`t<dcsset:periodAdditionType>$(Esc-XmlText $pat)</dcsset:periodAdditionType>"
|
||||||
# Авто-детект: ISO-дата → xs:dateTime, иначе путь → dcscor:Field.
|
# Авто-детект: ISO-дата → xs:dateTime, иначе путь → dcscor:Field.
|
||||||
$pabT = if ($pab -match '^\d{4}-\d{2}-\d{2}T') { 'xs:dateTime' } else { 'dcscor:Field' }
|
$pabT = if ($pab -match '^\d{4}-\d{2}-\d{2}T') { 'xs:dateTime' } else { 'dcscor:Field' }
|
||||||
$paeT = if ($pae -match '^\d{4}-\d{2}-\d{2}T') { 'xs:dateTime' } else { 'dcscor:Field' }
|
$paeT = if ($pae -match '^\d{4}-\d{2}-\d{2}T') { 'xs:dateTime' } else { 'dcscor:Field' }
|
||||||
X "$indent`t<dcsset:periodAdditionBegin xsi:type=`"$pabT`">$(Esc-Xml $pab)</dcsset:periodAdditionBegin>"
|
X "$indent`t<dcsset:periodAdditionBegin xsi:type=`"$pabT`">$(Esc-XmlText $pab)</dcsset:periodAdditionBegin>"
|
||||||
X "$indent`t<dcsset:periodAdditionEnd xsi:type=`"$paeT`">$(Esc-Xml $pae)</dcsset:periodAdditionEnd>"
|
X "$indent`t<dcsset:periodAdditionEnd xsi:type=`"$paeT`">$(Esc-XmlText $pae)</dcsset:periodAdditionEnd>"
|
||||||
X "$indent</dcsset:item>"
|
X "$indent</dcsset:item>"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2271,22 +2303,22 @@ function Emit-CalcFields {
|
|||||||
}
|
}
|
||||||
$ci = "$indent`t"
|
$ci = "$indent`t"
|
||||||
X "$indent<CalculatedField>"
|
X "$indent<CalculatedField>"
|
||||||
X "$ci<dcssch:dataPath>$(Esc-Xml $dataPath)</dcssch:dataPath>"
|
X "$ci<dcssch:dataPath>$(Esc-XmlText $dataPath)</dcssch:dataPath>"
|
||||||
X "$ci<dcssch:expression>$(Esc-Xml $expression)</dcssch:expression>"
|
X "$ci<dcssch:expression>$(Esc-XmlText $expression)</dcssch:expression>"
|
||||||
if ($title) { Emit-MLText -tag 'dcssch:title' -text $title -indent $ci -xsiType 'v8:LocalStringType' }
|
if ($title) { Emit-MLText -tag 'dcssch:title' -text $title -indent $ci -xsiType 'v8:LocalStringType' }
|
||||||
if ($restrict.Count -gt 0) {
|
if ($restrict.Count -gt 0) {
|
||||||
X "$ci<dcssch:useRestriction>"
|
X "$ci<dcssch:useRestriction>"
|
||||||
foreach ($r in @('field','condition','group','order')) { if ($restrict -contains $r) { X "$ci`t<dcssch:$r>true</dcssch:$r>" } }
|
foreach ($r in @('field','condition','group','order')) { if ($restrict -contains $r) { X "$ci`t<dcssch:$r>true</dcssch:$r>" } }
|
||||||
X "$ci</dcssch:useRestriction>"
|
X "$ci</dcssch:useRestriction>"
|
||||||
}
|
}
|
||||||
if ($pres) { X "$ci<dcssch:presentationExpression>$(Esc-Xml "$pres")</dcssch:presentationExpression>" }
|
if ($pres) { X "$ci<dcssch:presentationExpression>$(Esc-XmlText "$pres")</dcssch:presentationExpression>" }
|
||||||
if ($orderExpr) {
|
if ($orderExpr) {
|
||||||
$oeList = if ($orderExpr -is [System.Collections.IList]) { $orderExpr } else { @($orderExpr) }
|
$oeList = if ($orderExpr -is [System.Collections.IList]) { $orderExpr } else { @($orderExpr) }
|
||||||
foreach ($oe in $oeList) {
|
foreach ($oe in $oeList) {
|
||||||
if ($oe -is [string]) { $exprV = $oe; $oType = 'Asc'; $auto = 'false' }
|
if ($oe -is [string]) { $exprV = $oe; $oType = 'Asc'; $auto = 'false' }
|
||||||
else { $exprV = "$($oe.expression)"; $oType = if ($oe.orderType) { "$($oe.orderType)" } else { 'Asc' }; $auto = if ($oe.autoOrder) { 'true' } else { 'false' } }
|
else { $exprV = "$($oe.expression)"; $oType = if ($oe.orderType) { "$($oe.orderType)" } else { 'Asc' }; $auto = if ($oe.autoOrder) { 'true' } else { 'false' } }
|
||||||
X "$ci<dcssch:orderExpression>"
|
X "$ci<dcssch:orderExpression>"
|
||||||
X "$ci`t<expression xmlns=`"$($script:dcsCommonNs)`">$(Esc-Xml $exprV)</expression>"
|
X "$ci`t<expression xmlns=`"$($script:dcsCommonNs)`">$(Esc-XmlText $exprV)</expression>"
|
||||||
X "$ci`t<orderType xmlns=`"$($script:dcsCommonNs)`">$oType</orderType>"
|
X "$ci`t<orderType xmlns=`"$($script:dcsCommonNs)`">$oType</orderType>"
|
||||||
X "$ci`t<autoOrder xmlns=`"$($script:dcsCommonNs)`">$auto</autoOrder>"
|
X "$ci`t<autoOrder xmlns=`"$($script:dcsCommonNs)`">$auto</autoOrder>"
|
||||||
X "$ci</dcssch:orderExpression>"
|
X "$ci</dcssch:orderExpression>"
|
||||||
@@ -2371,26 +2403,49 @@ $script:knownInvalidTypes = @{
|
|||||||
"FormTable" = "UI element type, not a data type"
|
"FormTable" = "UI element type, not a data type"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Алиас на локальный словарь: тело Resolve-TypeStr ниже — общая реализация,
|
||||||
|
# одинаковая во всех навыках (реестр в tests/skills/check-inline-drift.mjs).
|
||||||
|
$script:typeSynonyms = $script:formTypeSynonyms
|
||||||
|
|
||||||
function Resolve-TypeStr {
|
function Resolve-TypeStr {
|
||||||
param([string]$typeStr)
|
param([string]$typeStr)
|
||||||
if (-not $typeStr) { return $typeStr }
|
if (-not $typeStr) { return $typeStr }
|
||||||
# Lenient: strip leading cfg: prefix if user passed it (canonical form is without prefix)
|
|
||||||
if ($typeStr -match '^cfg:(.+)$') { $typeStr = $Matches[1] }
|
# Прощающий ввод: ведущий префикс приходит копипастой из выгрузки. Без срезания он ломает
|
||||||
|
# поиск в словаре — русское имя типа остаётся непереведённым, и платформа отвечает
|
||||||
|
# «Неизвестное имя типа». cfg: снимаем всегда — он однозначно означает текущую конфигурацию.
|
||||||
|
# Сгенерированный dNpM: (в корпусе на этом URI встречаются d4p1, d5p1, d6p1 — имя префикса
|
||||||
|
# платформа выдаёт по порядку объявления) снимаем ТОЛЬКО у ссылочных типов, с точкой:
|
||||||
|
# сам по себе префикс многозначен — в формах d5p1:Chart, d5p1:TextDocument,
|
||||||
|
# d5p1:GeographicalSchema адресуют чужие пространства имён, и там он часть значения.
|
||||||
|
if ($typeStr.StartsWith('cfg:')) {
|
||||||
|
$typeStr = $typeStr.Substring(4)
|
||||||
|
} elseif ($typeStr.Contains('.') -and $typeStr -match '^d\d+p\d+:') {
|
||||||
|
$typeStr = $typeStr.Substring($typeStr.IndexOf(':') + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Параметризованные типы: Number(15,2), Строка(100)
|
||||||
if ($typeStr -match '^([^(]+)\((.+)\)$') {
|
if ($typeStr -match '^([^(]+)\((.+)\)$') {
|
||||||
$base = $Matches[1].Trim(); $params = $Matches[2]
|
$baseName = $Matches[1].Trim()
|
||||||
$r = $script:formTypeSynonyms[$base.ToLower()]
|
$params = $Matches[2]
|
||||||
if ($r) { return "$r($params)" }
|
$resolved = $script:typeSynonyms[$baseName.ToLower()]
|
||||||
|
if ($resolved) { return "$resolved($params)" }
|
||||||
return $typeStr
|
return $typeStr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Ссылочные типы: СправочникСсылка.Организации → CatalogRef.Организации
|
||||||
if ($typeStr.Contains('.')) {
|
if ($typeStr.Contains('.')) {
|
||||||
$i = $typeStr.IndexOf('.')
|
$dotIdx = $typeStr.IndexOf('.')
|
||||||
$prefix = $typeStr.Substring(0, $i); $suffix = $typeStr.Substring($i)
|
$prefix = $typeStr.Substring(0, $dotIdx)
|
||||||
$r = $script:formTypeSynonyms[$prefix.ToLower()]
|
$suffix = $typeStr.Substring($dotIdx) # includes the dot
|
||||||
if ($r) { return "$r$suffix" }
|
$resolved = $script:typeSynonyms[$prefix.ToLower()]
|
||||||
|
if ($resolved) { return "$resolved$suffix" }
|
||||||
return $typeStr
|
return $typeStr
|
||||||
}
|
}
|
||||||
$r = $script:formTypeSynonyms[$typeStr.ToLower()]
|
|
||||||
if ($r) { return $r }
|
# Простое имя
|
||||||
|
$resolved = $script:typeSynonyms[$typeStr.ToLower()]
|
||||||
|
if ($resolved) { return $resolved }
|
||||||
return $typeStr
|
return $typeStr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3210,7 +3265,7 @@ function Emit-CommonElementProps {
|
|||||||
if ($null -ne $el.($p[0])) { X "$indent<$($p[1])>$(if ($el.($p[0])){'true'}else{'false'})</$($p[1])>" }
|
if ($null -ne $el.($p[0])) { X "$indent<$($p[1])>$(if ($el.($p[0])){'true'}else{'false'})</$($p[1])>" }
|
||||||
}
|
}
|
||||||
# Динамический заголовок колонки-группы из данных (HeaderDataPath) — перед HeaderHorizontalAlign (порядок XSD)
|
# Динамический заголовок колонки-группы из данных (HeaderDataPath) — перед HeaderHorizontalAlign (порядок XSD)
|
||||||
if ($el.headerDataPath) { X "$indent<HeaderDataPath>$(Esc-Xml "$($el.headerDataPath)")</HeaderDataPath>" }
|
if ($el.headerDataPath) { X "$indent<HeaderDataPath>$(Esc-XmlText "$($el.headerDataPath)")</HeaderDataPath>" }
|
||||||
if ($el.footerHorizontalAlign) { X "$indent<FooterHorizontalAlign>$($el.footerHorizontalAlign)</FooterHorizontalAlign>" }
|
if ($el.footerHorizontalAlign) { X "$indent<FooterHorizontalAlign>$($el.footerHorizontalAlign)</FooterHorizontalAlign>" }
|
||||||
if ($el.headerHorizontalAlign) { X "$indent<HeaderHorizontalAlign>$($el.headerHorizontalAlign)</HeaderHorizontalAlign>" }
|
if ($el.headerHorizontalAlign) { X "$indent<HeaderHorizontalAlign>$($el.headerHorizontalAlign)</HeaderHorizontalAlign>" }
|
||||||
# Формат заголовка колонки-группы (ML-текст) — после HeaderHorizontalAlign (порядок XSD)
|
# Формат заголовка колонки-группы (ML-текст) — после HeaderHorizontalAlign (порядок XSD)
|
||||||
@@ -3230,8 +3285,8 @@ function Emit-PictureRef {
|
|||||||
if (-not $src) { return }
|
if (-not $src) { return }
|
||||||
$srcStr = "$src"
|
$srcStr = "$src"
|
||||||
X "$indent<$picTag>"
|
X "$indent<$picTag>"
|
||||||
if ($srcStr -match '^abs:(.*)$') { X "$indent`t<xr:Abs>$(Esc-Xml $matches[1])</xr:Abs>" }
|
if ($srcStr -match '^abs:(.*)$') { X "$indent`t<xr:Abs>$(Esc-XmlText $matches[1])</xr:Abs>" }
|
||||||
else { X "$indent`t<xr:Ref>$(Esc-Xml $srcStr)</xr:Ref>" }
|
else { X "$indent`t<xr:Ref>$(Esc-XmlText $srcStr)</xr:Ref>" }
|
||||||
X "$indent`t<xr:LoadTransparent>$(if ($lt) { 'true' } else { 'false' })</xr:LoadTransparent>"
|
X "$indent`t<xr:LoadTransparent>$(if ($lt) { 'true' } else { 'false' })</xr:LoadTransparent>"
|
||||||
if ($tpx) { X "$indent`t<xr:TransparentPixel x=`"$($tpx.x)`" y=`"$($tpx.y)`"/>" }
|
if ($tpx) { X "$indent`t<xr:TransparentPixel x=`"$($tpx.x)`" y=`"$($tpx.y)`"/>" }
|
||||||
X "$indent</$picTag>"
|
X "$indent</$picTag>"
|
||||||
@@ -3260,8 +3315,8 @@ function Emit-CommandPicture {
|
|||||||
if ($null -eq $lt -and $null -ne $elemLt) { $lt = [bool]$elemLt }
|
if ($null -eq $lt -and $null -ne $elemLt) { $lt = [bool]$elemLt }
|
||||||
$srcStr = "$src"
|
$srcStr = "$src"
|
||||||
X "$indent<Picture>"
|
X "$indent<Picture>"
|
||||||
if ($srcStr -match '^abs:(.*)$') { X "$indent`t<xr:Abs>$(Esc-Xml $matches[1])</xr:Abs>" }
|
if ($srcStr -match '^abs:(.*)$') { X "$indent`t<xr:Abs>$(Esc-XmlText $matches[1])</xr:Abs>" }
|
||||||
else { X "$indent`t<xr:Ref>$(Esc-Xml $srcStr)</xr:Ref>" }
|
else { X "$indent`t<xr:Ref>$(Esc-XmlText $srcStr)</xr:Ref>" }
|
||||||
X "$indent`t<xr:LoadTransparent>$(if ($lt -eq $false) { 'false' } else { 'true' })</xr:LoadTransparent>"
|
X "$indent`t<xr:LoadTransparent>$(if ($lt -eq $false) { 'false' } else { 'true' })</xr:LoadTransparent>"
|
||||||
if ($tpx) { X "$indent`t<xr:TransparentPixel x=`"$($tpx.x)`" y=`"$($tpx.y)`"/>" }
|
if ($tpx) { X "$indent`t<xr:TransparentPixel x=`"$($tpx.x)`" y=`"$($tpx.y)`"/>" }
|
||||||
X "$indent</Picture>"
|
X "$indent</Picture>"
|
||||||
@@ -3423,7 +3478,7 @@ function Emit-GenericScalars {
|
|||||||
X "$indent<$($s.Tag)>$(if ($p.Value){'true'}else{'false'})</$($s.Tag)>"
|
X "$indent<$($s.Tag)>$(if ($p.Value){'true'}else{'false'})</$($s.Tag)>"
|
||||||
} else {
|
} else {
|
||||||
$v = "$($p.Value)"; if ($v -eq '') { continue }
|
$v = "$($p.Value)"; if ($v -eq '') { continue }
|
||||||
X "$indent<$($s.Tag)>$(Esc-Xml $v)</$($s.Tag)>"
|
X "$indent<$($s.Tag)>$(Esc-XmlText $v)</$($s.Tag)>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3470,7 +3525,7 @@ function Emit-BorderTag {
|
|||||||
$width = if ($val.PSObject.Properties['width'] -and $null -ne $val.width) { $val.width } else { 1 }
|
$width = if ($val.PSObject.Properties['width'] -and $null -ne $val.width) { $val.width } else { 1 }
|
||||||
$style = if ($val.PSObject.Properties['style']) { "$($val.style)" } else { $null }
|
$style = if ($val.PSObject.Properties['style']) { "$($val.style)" } else { $null }
|
||||||
X "$indent<Border width=`"$width`">"
|
X "$indent<Border width=`"$width`">"
|
||||||
if ($style) { X "$indent`t<v8ui:style xsi:type=`"v8ui:ControlBorderType`">$(Esc-Xml $style)</v8ui:style>" }
|
if ($style) { X "$indent`t<v8ui:style xsi:type=`"v8ui:ControlBorderType`">$(Esc-XmlText $style)</v8ui:style>" }
|
||||||
X "$indent</Border>"
|
X "$indent</Border>"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3497,13 +3552,13 @@ function PL-Bool {
|
|||||||
}
|
}
|
||||||
function Emit-PlannerColor {
|
function Emit-PlannerColor {
|
||||||
param([string]$tag, $o, [string]$key, [string]$ind)
|
param([string]$tag, $o, [string]$key, [string]$ind)
|
||||||
X "$ind<pl:$tag>$(Esc-Xml "$(PL-Get $o $key 'auto')")</pl:$tag>"
|
X "$ind<pl:$tag>$(Esc-XmlText "$(PL-Get $o $key 'auto')")</pl:$tag>"
|
||||||
}
|
}
|
||||||
# <pl:text>/<pl:tooltip>… — пустое → самозакрывающийся тег (как в выгрузке платформы).
|
# <pl:text>/<pl:tooltip>… — пустое → самозакрывающийся тег (как в выгрузке платформы).
|
||||||
function Emit-PlannerText {
|
function Emit-PlannerText {
|
||||||
param([string]$tag, $v, [string]$ind)
|
param([string]$tag, $v, [string]$ind)
|
||||||
if ([string]::IsNullOrEmpty("$v")) { X "$ind<pl:$tag/>" }
|
if ([string]::IsNullOrEmpty("$v")) { X "$ind<pl:$tag/>" }
|
||||||
else { X "$ind<pl:$tag>$(Esc-Xml "$v")</pl:$tag>" }
|
else { X "$ind<pl:$tag>$(Esc-XmlText "$v")</pl:$tag>" }
|
||||||
}
|
}
|
||||||
# Признак ссылочного значения (объект разреза/элемент-ссылка) → xsi:type="xr:DesignTimeRef";
|
# Признак ссылочного значения (объект разреза/элемент-ссылка) → xsi:type="xr:DesignTimeRef";
|
||||||
# иначе xs:string. Покрывает англ. (Enum.X.EnumValue.Y) и рус. (Справочник.X) метатипы.
|
# иначе xs:string. Покрывает англ. (Enum.X.EnumValue.Y) и рус. (Справочник.X) метатипы.
|
||||||
@@ -3518,7 +3573,7 @@ function Emit-PlannerValue {
|
|||||||
param($v, [string]$ind)
|
param($v, [string]$ind)
|
||||||
if ($null -eq $v -or "$v" -eq '') { X "$ind<pl:value xsi:nil=`"true`"/>"; return }
|
if ($null -eq $v -or "$v" -eq '') { X "$ind<pl:value xsi:nil=`"true`"/>"; return }
|
||||||
$t = if (Test-PlannerRef "$v") { 'xr:DesignTimeRef' } else { 'xs:string' }
|
$t = if (Test-PlannerRef "$v") { 'xr:DesignTimeRef' } else { 'xs:string' }
|
||||||
X "$ind<pl:value xsi:type=`"$t`">$(Esc-Xml "$v")</pl:value>"
|
X "$ind<pl:value xsi:type=`"$t`">$(Esc-XmlText "$v")</pl:value>"
|
||||||
}
|
}
|
||||||
function Emit-PlannerFont {
|
function Emit-PlannerFont {
|
||||||
param($o, [string]$ind)
|
param($o, [string]$ind)
|
||||||
@@ -3532,14 +3587,14 @@ function Emit-PlannerBorder {
|
|||||||
$bw = if ($b) { PL-Get $b 'width' 1 } else { 1 }
|
$bw = if ($b) { PL-Get $b 'width' 1 } else { 1 }
|
||||||
$bs = if ($b) { PL-Get $b 'style' 'Single' } else { 'Single' }
|
$bs = if ($b) { PL-Get $b 'style' 'Single' } else { 'Single' }
|
||||||
X "$ind<pl:border width=`"$bw`">"
|
X "$ind<pl:border width=`"$bw`">"
|
||||||
X "$ind`t<v8ui:style xsi:type=`"v8ui:ControlBorderType`">$(Esc-Xml "$bs")</v8ui:style>"
|
X "$ind`t<v8ui:style xsi:type=`"v8ui:ControlBorderType`">$(Esc-XmlText "$bs")</v8ui:style>"
|
||||||
X "$ind</pl:border>"
|
X "$ind</pl:border>"
|
||||||
}
|
}
|
||||||
function Emit-PlannerLevel {
|
function Emit-PlannerLevel {
|
||||||
param($lv, [string]$cns, [string]$ind)
|
param($lv, [string]$cns, [string]$ind)
|
||||||
$li = "$ind`t"
|
$li = "$ind`t"
|
||||||
X "$ind<level xmlns=`"$cns`">"
|
X "$ind<level xmlns=`"$cns`">"
|
||||||
X "$li<measure>$(Esc-Xml "$(PL-Get $lv 'measure' 'Hour')")</measure>"
|
X "$li<measure>$(Esc-XmlText "$(PL-Get $lv 'measure' 'Hour')")</measure>"
|
||||||
X "$li<interval>$(PL-Get $lv 'interval' 1)</interval>"
|
X "$li<interval>$(PL-Get $lv 'interval' 1)</interval>"
|
||||||
X "$li<show>$(PL-Bool (PL-Get $lv 'show' $true))</show>"
|
X "$li<show>$(PL-Bool (PL-Get $lv 'show' $true))</show>"
|
||||||
$line = PL-Get $lv 'line' $null
|
$line = PL-Get $lv 'line' $null
|
||||||
@@ -3547,10 +3602,10 @@ function Emit-PlannerLevel {
|
|||||||
$lg = if ($line) { PL-Get $line 'gap' $false } else { $false }
|
$lg = if ($line) { PL-Get $line 'gap' $false } else { $false }
|
||||||
$lst = if ($line) { PL-Get $line 'style' 'Solid' } else { 'Solid' }
|
$lst = if ($line) { PL-Get $line 'style' 'Solid' } else { 'Solid' }
|
||||||
X "$li<line width=`"$lw`" gap=`"$(PL-Bool $lg)`">"
|
X "$li<line width=`"$lw`" gap=`"$(PL-Bool $lg)`">"
|
||||||
X "$li`t<v8ui:style xsi:type=`"v8ui:ChartLineType`">$(Esc-Xml "$lst")</v8ui:style>"
|
X "$li`t<v8ui:style xsi:type=`"v8ui:ChartLineType`">$(Esc-XmlText "$lst")</v8ui:style>"
|
||||||
X "$li</line>"
|
X "$li</line>"
|
||||||
X "$li<scaleColor>$(Esc-Xml "$(PL-Get $lv 'scaleColor' 'auto')")</scaleColor>"
|
X "$li<scaleColor>$(Esc-XmlText "$(PL-Get $lv 'scaleColor' 'auto')")</scaleColor>"
|
||||||
X "$li<dayFormatRule>$(Esc-Xml "$(PL-Get $lv 'dayFormatRule' 'MonthDayWeekDay')")</dayFormatRule>"
|
X "$li<dayFormatRule>$(Esc-XmlText "$(PL-Get $lv 'dayFormatRule' 'MonthDayWeekDay')")</dayFormatRule>"
|
||||||
$fmt = PL-Get $lv 'format' $null
|
$fmt = PL-Get $lv 'format' $null
|
||||||
if ($null -eq $fmt) { $fmt = [ordered]@{ '#' = 'DF="HH:mm"'; 'ru' = 'DF="HH:mm"' } }
|
if ($null -eq $fmt) { $fmt = [ordered]@{ '#' = 'DF="HH:mm"'; 'ru' = 'DF="HH:mm"' } }
|
||||||
X "$li<format>"
|
X "$li<format>"
|
||||||
@@ -3561,8 +3616,8 @@ function Emit-PlannerLevel {
|
|||||||
X "$li<labels>"
|
X "$li<labels>"
|
||||||
X "$li`t<ticks>$ticks</ticks>"
|
X "$li`t<ticks>$ticks</ticks>"
|
||||||
X "$li</labels>"
|
X "$li</labels>"
|
||||||
X "$li<backColor>$(Esc-Xml "$(PL-Get $lv 'backColor' 'auto')")</backColor>"
|
X "$li<backColor>$(Esc-XmlText "$(PL-Get $lv 'backColor' 'auto')")</backColor>"
|
||||||
X "$li<textColor>$(Esc-Xml "$(PL-Get $lv 'textColor' 'auto')")</textColor>"
|
X "$li<textColor>$(Esc-XmlText "$(PL-Get $lv 'textColor' 'auto')")</textColor>"
|
||||||
X "$li<showPereodicalLabels>$(PL-Bool (PL-Get $lv 'showPereodicalLabels' $true))</showPereodicalLabels>"
|
X "$li<showPereodicalLabels>$(PL-Bool (PL-Get $lv 'showPereodicalLabels' $true))</showPereodicalLabels>"
|
||||||
X "$ind</level>"
|
X "$ind</level>"
|
||||||
}
|
}
|
||||||
@@ -3571,14 +3626,14 @@ function Emit-PlannerTimeScale {
|
|||||||
$cns = $script:CHART_NS
|
$cns = $script:CHART_NS
|
||||||
$ci = "$ind`t"
|
$ci = "$ind`t"
|
||||||
X "$ind<pl:timeScale>"
|
X "$ind<pl:timeScale>"
|
||||||
X "$ci<placement xmlns=`"$cns`">$(Esc-Xml "$(if ($ts) { PL-Get $ts 'placement' 'Left' } else { 'Left' })")</placement>"
|
X "$ci<placement xmlns=`"$cns`">$(Esc-XmlText "$(if ($ts) { PL-Get $ts 'placement' 'Left' } else { 'Left' })")</placement>"
|
||||||
$levels = if ($ts) { @(PL-Get $ts 'levels' @()) } else { @() }
|
$levels = if ($ts) { @(PL-Get $ts 'levels' @()) } else { @() }
|
||||||
if (@($levels).Count -eq 0) { $levels = @($null) } # один уровень-дефолт
|
if (@($levels).Count -eq 0) { $levels = @($null) } # один уровень-дефолт
|
||||||
foreach ($lv in $levels) { Emit-PlannerLevel $lv $cns $ci }
|
foreach ($lv in $levels) { Emit-PlannerLevel $lv $cns $ci }
|
||||||
$transp = if ($ts) { PL-Get $ts 'transparent' $false } else { $false }
|
$transp = if ($ts) { PL-Get $ts 'transparent' $false } else { $false }
|
||||||
X "$ci<transparent xmlns=`"$cns`">$(PL-Bool $transp)</transparent>"
|
X "$ci<transparent xmlns=`"$cns`">$(PL-Bool $transp)</transparent>"
|
||||||
X "$ci<backColor xmlns=`"$cns`">$(Esc-Xml "$(if ($ts) { PL-Get $ts 'backColor' 'auto' } else { 'auto' })")</backColor>"
|
X "$ci<backColor xmlns=`"$cns`">$(Esc-XmlText "$(if ($ts) { PL-Get $ts 'backColor' 'auto' } else { 'auto' })")</backColor>"
|
||||||
X "$ci<textColor xmlns=`"$cns`">$(Esc-Xml "$(if ($ts) { PL-Get $ts 'textColor' 'auto' } else { 'auto' })")</textColor>"
|
X "$ci<textColor xmlns=`"$cns`">$(Esc-XmlText "$(if ($ts) { PL-Get $ts 'textColor' 'auto' } else { 'auto' })")</textColor>"
|
||||||
X "$ci<currentLevel xmlns=`"$cns`">$(if ($ts) { PL-Get $ts 'currentLevel' 0 } else { 0 })</currentLevel>"
|
X "$ci<currentLevel xmlns=`"$cns`">$(if ($ts) { PL-Get $ts 'currentLevel' 0 } else { 0 })</currentLevel>"
|
||||||
X "$ind</pl:timeScale>"
|
X "$ind</pl:timeScale>"
|
||||||
}
|
}
|
||||||
@@ -3603,7 +3658,7 @@ function Emit-PlannerItem {
|
|||||||
X "$ii<pl:id>$id</pl:id>"
|
X "$ii<pl:id>$id</pl:id>"
|
||||||
X "$ii<pl:textFormatted>$(PL-Bool (PL-Get $it 'textFormatted' $false))</pl:textFormatted>"
|
X "$ii<pl:textFormatted>$(PL-Bool (PL-Get $it 'textFormatted' $false))</pl:textFormatted>"
|
||||||
Emit-PlannerBorder $it $ii 'border'
|
Emit-PlannerBorder $it $ii 'border'
|
||||||
X "$ii<pl:editMode>$(Esc-Xml "$(PL-Get $it 'editMode' 'EnableEdit')")</pl:editMode>"
|
X "$ii<pl:editMode>$(Esc-XmlText "$(PL-Get $it 'editMode' 'EnableEdit')")</pl:editMode>"
|
||||||
X "$ind</pl:item>"
|
X "$ind</pl:item>"
|
||||||
}
|
}
|
||||||
# Элемент измерения (<pl:item> внутри <pl:dimension>) — рекурсивен: может нести вложенные
|
# Элемент измерения (<pl:item> внутри <pl:dimension>) — рекурсивен: может нести вложенные
|
||||||
@@ -3658,7 +3713,7 @@ function Emit-PlannerSettings {
|
|||||||
$wfmt = PL-Get $pl 'timeScaleWrapHeadersFormat' $null
|
$wfmt = PL-Get $pl 'timeScaleWrapHeadersFormat' $null
|
||||||
if ($null -eq $wfmt) { $wfmt = [ordered]@{ '#' = 'DLF="DD"'; 'ru' = 'DLF="DD"' } }
|
if ($null -eq $wfmt) { $wfmt = [ordered]@{ '#' = 'DLF="DD"'; 'ru' = 'DLF="DD"' } }
|
||||||
Emit-MLText -tag 'pl:timeScaleWrapHeadersFormat' -text $wfmt -indent $si
|
Emit-MLText -tag 'pl:timeScaleWrapHeadersFormat' -text $wfmt -indent $si
|
||||||
X "$si<pl:periodicVariantUnit>$(Esc-Xml "$(PL-Get $pl 'periodicVariantUnit' 'Day')")</pl:periodicVariantUnit>"
|
X "$si<pl:periodicVariantUnit>$(Esc-XmlText "$(PL-Get $pl 'periodicVariantUnit' 'Day')")</pl:periodicVariantUnit>"
|
||||||
X "$si<pl:periodicVariantRepetition>$(PL-Get $pl 'periodicVariantRepetition' 1)</pl:periodicVariantRepetition>"
|
X "$si<pl:periodicVariantRepetition>$(PL-Get $pl 'periodicVariantRepetition' 1)</pl:periodicVariantRepetition>"
|
||||||
X "$si<pl:timeScaleWrapBeginIndent>$(PL-Get $pl 'timeScaleWrapBeginIndent' 0)</pl:timeScaleWrapBeginIndent>"
|
X "$si<pl:timeScaleWrapBeginIndent>$(PL-Get $pl 'timeScaleWrapBeginIndent' 0)</pl:timeScaleWrapBeginIndent>"
|
||||||
X "$si<pl:timeScaleWrapEndIndent>$(PL-Get $pl 'timeScaleWrapEndIndent' 0)</pl:timeScaleWrapEndIndent>"
|
X "$si<pl:timeScaleWrapEndIndent>$(PL-Get $pl 'timeScaleWrapEndIndent' 0)</pl:timeScaleWrapEndIndent>"
|
||||||
@@ -3671,16 +3726,16 @@ function Emit-PlannerSettings {
|
|||||||
X "$si</pl:period>"
|
X "$si</pl:period>"
|
||||||
}
|
}
|
||||||
X "$si<pl:displayCurrentDate>$(PL-Bool (PL-Get $pl 'displayCurrentDate' $true))</pl:displayCurrentDate>"
|
X "$si<pl:displayCurrentDate>$(PL-Bool (PL-Get $pl 'displayCurrentDate' $true))</pl:displayCurrentDate>"
|
||||||
X "$si<pl:itemsTimeRepresentation>$(Esc-Xml "$(PL-Get $pl 'itemsTimeRepresentation' 'BeginTime')")</pl:itemsTimeRepresentation>"
|
X "$si<pl:itemsTimeRepresentation>$(Esc-XmlText "$(PL-Get $pl 'itemsTimeRepresentation' 'BeginTime')")</pl:itemsTimeRepresentation>"
|
||||||
X "$si<pl:itemsBehaviorWhenSpaceInsufficient>$(Esc-Xml "$(PL-Get $pl 'itemsBehaviorWhenSpaceInsufficient' 'CollapseItems')")</pl:itemsBehaviorWhenSpaceInsufficient>"
|
X "$si<pl:itemsBehaviorWhenSpaceInsufficient>$(Esc-XmlText "$(PL-Get $pl 'itemsBehaviorWhenSpaceInsufficient' 'CollapseItems')")</pl:itemsBehaviorWhenSpaceInsufficient>"
|
||||||
X "$si<pl:autoMinColumnWidth>$(PL-Bool (PL-Get $pl 'autoMinColumnWidth' $true))</pl:autoMinColumnWidth>"
|
X "$si<pl:autoMinColumnWidth>$(PL-Bool (PL-Get $pl 'autoMinColumnWidth' $true))</pl:autoMinColumnWidth>"
|
||||||
X "$si<pl:autoMinRowHeight>$(PL-Bool (PL-Get $pl 'autoMinRowHeight' $true))</pl:autoMinRowHeight>"
|
X "$si<pl:autoMinRowHeight>$(PL-Bool (PL-Get $pl 'autoMinRowHeight' $true))</pl:autoMinRowHeight>"
|
||||||
X "$si<pl:minColumnWidth>$(PL-Get $pl 'minColumnWidth' 0)</pl:minColumnWidth>"
|
X "$si<pl:minColumnWidth>$(PL-Get $pl 'minColumnWidth' 0)</pl:minColumnWidth>"
|
||||||
X "$si<pl:minRowHeight>$(PL-Get $pl 'minRowHeight' 0)</pl:minRowHeight>"
|
X "$si<pl:minRowHeight>$(PL-Get $pl 'minRowHeight' 0)</pl:minRowHeight>"
|
||||||
X "$si<pl:fixDimensionsHeader>$(Esc-Xml "$(PL-Get $pl 'fixDimensionsHeader' 'auto')")</pl:fixDimensionsHeader>"
|
X "$si<pl:fixDimensionsHeader>$(Esc-XmlText "$(PL-Get $pl 'fixDimensionsHeader' 'auto')")</pl:fixDimensionsHeader>"
|
||||||
X "$si<pl:fixTimeScaleHeader>$(Esc-Xml "$(PL-Get $pl 'fixTimeScaleHeader' 'auto')")</pl:fixTimeScaleHeader>"
|
X "$si<pl:fixTimeScaleHeader>$(Esc-XmlText "$(PL-Get $pl 'fixTimeScaleHeader' 'auto')")</pl:fixTimeScaleHeader>"
|
||||||
Emit-PlannerBorder $pl $si 'border'
|
Emit-PlannerBorder $pl $si 'border'
|
||||||
X "$si<pl:newItemsTextType>$(Esc-Xml "$(PL-Get $pl 'newItemsTextType' 'String')")</pl:newItemsTextType>"
|
X "$si<pl:newItemsTextType>$(Esc-XmlText "$(PL-Get $pl 'newItemsTextType' 'String')")</pl:newItemsTextType>"
|
||||||
X "$ind</Settings>"
|
X "$ind</Settings>"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3713,13 +3768,13 @@ function Emit-ChartNode {
|
|||||||
if ($keys -contains 'gap') {
|
if ($keys -contains 'gap') {
|
||||||
$w = Get-Prop $val 'width'; $g = Get-Prop $val 'gap'; $st = Get-Prop $val 'style'
|
$w = Get-Prop $val 'width'; $g = Get-Prop $val 'gap'; $st = Get-Prop $val 'style'
|
||||||
X "$ind<d4p1:$name width=`"$w`" gap=`"$(PL-Bool $g)`">"
|
X "$ind<d4p1:$name width=`"$w`" gap=`"$(PL-Bool $g)`">"
|
||||||
X "$ind`t<v8ui:style xsi:type=`"v8ui:ChartLineType`">$(Esc-Xml "$st")</v8ui:style>"
|
X "$ind`t<v8ui:style xsi:type=`"v8ui:ChartLineType`">$(Esc-XmlText "$st")</v8ui:style>"
|
||||||
X "$ind</d4p1:$name>"; return
|
X "$ind</d4p1:$name>"; return
|
||||||
}
|
}
|
||||||
if (($keys -contains 'style') -and ($keys -contains 'width')) {
|
if (($keys -contains 'style') -and ($keys -contains 'width')) {
|
||||||
$w = Get-Prop $val 'width'; $st = Get-Prop $val 'style'
|
$w = Get-Prop $val 'width'; $st = Get-Prop $val 'style'
|
||||||
X "$ind<d4p1:$name width=`"$w`">"
|
X "$ind<d4p1:$name width=`"$w`">"
|
||||||
X "$ind`t<v8ui:style xsi:type=`"v8ui:ControlBorderType`">$(Esc-Xml "$st")</v8ui:style>"
|
X "$ind`t<v8ui:style xsi:type=`"v8ui:ControlBorderType`">$(Esc-XmlText "$st")</v8ui:style>"
|
||||||
X "$ind</d4p1:$name>"; return
|
X "$ind</d4p1:$name>"; return
|
||||||
}
|
}
|
||||||
$isFont = $false; foreach ($fk in $script:CHART_FONT_KEYS) { if ($keys -contains $fk) { $isFont = $true; break } }
|
$isFont = $false; foreach ($fk in $script:CHART_FONT_KEYS) { if ($keys -contains $fk) { $isFont = $true; break } }
|
||||||
@@ -3735,7 +3790,7 @@ function Emit-ChartNode {
|
|||||||
}
|
}
|
||||||
if ($null -eq $val -or "$val" -eq '') { X "$ind<d4p1:$name/>"; return }
|
if ($null -eq $val -or "$val" -eq '') { X "$ind<d4p1:$name/>"; return }
|
||||||
if ($val -is [bool]) { X "$ind<d4p1:$name>$(PL-Bool $val)</d4p1:$name>"; return }
|
if ($val -is [bool]) { X "$ind<d4p1:$name>$(PL-Bool $val)</d4p1:$name>"; return }
|
||||||
X "$ind<d4p1:$name>$(Esc-Xml "$val")</d4p1:$name>"
|
X "$ind<d4p1:$name>$(Esc-XmlText "$val")</d4p1:$name>"
|
||||||
}
|
}
|
||||||
function Emit-ChartSettings {
|
function Emit-ChartSettings {
|
||||||
param($chart, [string]$ind, [string]$ctype = 'd4p1:Chart')
|
param($chart, [string]$ind, [string]$ctype = 'd4p1:Chart')
|
||||||
@@ -3757,7 +3812,7 @@ function Emit-Appearance {
|
|||||||
if ($null -eq $val -or ($val -is [string] -and $val -eq '')) { continue }
|
if ($null -eq $val -or ($val -is [string] -and $val -eq '')) { continue }
|
||||||
$spec = $script:appearanceSpec[$key]
|
$spec = $script:appearanceSpec[$key]
|
||||||
switch ($spec.kind) {
|
switch ($spec.kind) {
|
||||||
'color' { X "$indent<$($spec.tag)>$(Esc-Xml "$val")</$($spec.tag)>" }
|
'color' { X "$indent<$($spec.tag)>$(Esc-XmlText "$val")</$($spec.tag)>" }
|
||||||
'font' { Emit-FontTag -tag $spec.tag -val $val -indent $indent }
|
'font' { Emit-FontTag -tag $spec.tag -val $val -indent $indent }
|
||||||
'border' { Emit-BorderTag -val $val -indent $indent }
|
'border' { Emit-BorderTag -val $val -indent $indent }
|
||||||
}
|
}
|
||||||
@@ -4042,13 +4097,13 @@ function Emit-Input {
|
|||||||
@('choiceForm','ChoiceForm'), @('choiceHistoryOnInput','ChoiceHistoryOnInput'),
|
@('choiceForm','ChoiceForm'), @('choiceHistoryOnInput','ChoiceHistoryOnInput'),
|
||||||
@('choiceFoldersAndItems','ChoiceFoldersAndItems'), @('footerDataPath','FooterDataPath')
|
@('choiceFoldersAndItems','ChoiceFoldersAndItems'), @('footerDataPath','FooterDataPath')
|
||||||
)) {
|
)) {
|
||||||
if ($el.($p[0])) { X "$inner<$($p[1])>$(Esc-Xml "$($el.($p[0]))")</$($p[1])>" }
|
if ($el.($p[0])) { X "$inner<$($p[1])>$(Esc-XmlText "$($el.($p[0]))")</$($p[1])>" }
|
||||||
}
|
}
|
||||||
# MinValue/MaxValue — типизированное. JSON-число → xs:decimal, строка → xs:string (тип сохранён декомпилятором).
|
# MinValue/MaxValue — типизированное. JSON-число → xs:decimal, строка → xs:string (тип сохранён декомпилятором).
|
||||||
foreach ($p in @(@('minValue','MinValue'), @('maxValue','MaxValue'))) {
|
foreach ($p in @(@('minValue','MinValue'), @('maxValue','MaxValue'))) {
|
||||||
if ($null -ne $el.($p[0])) {
|
if ($null -ne $el.($p[0])) {
|
||||||
$mvt = if ($el.($p[0]) -is [string]) { 'xs:string' } else { 'xs:decimal' }
|
$mvt = if ($el.($p[0]) -is [string]) { 'xs:string' } else { 'xs:decimal' }
|
||||||
X "$inner<$($p[1]) xsi:type=`"$mvt`">$(Esc-Xml "$($el.($p[0]))")</$($p[1])>"
|
X "$inner<$($p[1]) xsi:type=`"$mvt`">$(Esc-XmlText "$($el.($p[0]))")</$($p[1])>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($el.choiceButtonRepresentation) { X "$inner<ChoiceButtonRepresentation>$($el.choiceButtonRepresentation)</ChoiceButtonRepresentation>" }
|
if ($el.choiceButtonRepresentation) { X "$inner<ChoiceButtonRepresentation>$($el.choiceButtonRepresentation)</ChoiceButtonRepresentation>" }
|
||||||
@@ -4111,7 +4166,7 @@ function Emit-Check {
|
|||||||
|
|
||||||
if ($null -ne $el.warningOnEdit) { Emit-MLText -tag "WarningOnEdit" -text $el.warningOnEdit -indent $inner }
|
if ($null -ne $el.warningOnEdit) { Emit-MLText -tag "WarningOnEdit" -text $el.warningOnEdit -indent $inner }
|
||||||
# FooterDataPath / FooterText — общие cell-свойства колонки (как у input/labelField)
|
# FooterDataPath / FooterText — общие cell-свойства колонки (как у input/labelField)
|
||||||
if ($el.footerDataPath) { X "$inner<FooterDataPath>$(Esc-Xml "$($el.footerDataPath)")</FooterDataPath>" }
|
if ($el.footerDataPath) { X "$inner<FooterDataPath>$(Esc-XmlText "$($el.footerDataPath)")</FooterDataPath>" }
|
||||||
if ($null -ne $el.footerText) { Emit-MLText -tag "FooterText" -text $el.footerText -indent $inner }
|
if ($null -ne $el.footerText) { Emit-MLText -tag "FooterText" -text $el.footerText -indent $inner }
|
||||||
|
|
||||||
# Формат / формат редактирования (LocalStringType — строка или {ru,en})
|
# Формат / формат редактирования (LocalStringType — строка или {ru,en})
|
||||||
@@ -4268,7 +4323,7 @@ function Emit-ChoicePresentation {
|
|||||||
foreach ($pair in $pairs) {
|
foreach ($pair in $pairs) {
|
||||||
X "$indent`t<v8:item>"
|
X "$indent`t<v8:item>"
|
||||||
X "$indent`t`t<v8:lang>$($pair[0])</v8:lang>"
|
X "$indent`t`t<v8:lang>$($pair[0])</v8:lang>"
|
||||||
X "$indent`t`t<v8:content>$(Esc-Xml $pair[1])</v8:content>"
|
X "$indent`t`t<v8:content>$(Esc-XmlText $pair[1])</v8:content>"
|
||||||
X "$indent`t</v8:item>"
|
X "$indent`t</v8:item>"
|
||||||
}
|
}
|
||||||
X "$indent</Presentation>"
|
X "$indent</Presentation>"
|
||||||
@@ -4278,7 +4333,7 @@ function Emit-ChoicePresentation {
|
|||||||
function Get-ChoiceValueTag {
|
function Get-ChoiceValueTag {
|
||||||
param($norm)
|
param($norm)
|
||||||
if ([string]::IsNullOrEmpty($norm.Text)) { return "<Value xsi:type=`"$($norm.XsiType)`"/>" }
|
if ([string]::IsNullOrEmpty($norm.Text)) { return "<Value xsi:type=`"$($norm.XsiType)`"/>" }
|
||||||
return "<Value xsi:type=`"$($norm.XsiType)`">$(Esc-Xml $norm.Text)</Value>"
|
return "<Value xsi:type=`"$($norm.XsiType)`">$(Esc-XmlText $norm.Text)</Value>"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Emit <ChoiceList> (список выбора) — у RadioButtonField и InputField.
|
# Emit <ChoiceList> (список выбора) — у RadioButtonField и InputField.
|
||||||
@@ -4491,8 +4546,8 @@ function Emit-ChoiceParameterLinks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
X "$indent`t<xr:Link>"
|
X "$indent`t<xr:Link>"
|
||||||
X "$indent`t`t<xr:Name>$(Esc-Xml "$name")</xr:Name>"
|
X "$indent`t`t<xr:Name>$(Esc-XmlText "$name")</xr:Name>"
|
||||||
X "$indent`t`t<xr:DataPath xsi:type=`"xs:string`">$(Esc-Xml "$dp")</xr:DataPath>"
|
X "$indent`t`t<xr:DataPath xsi:type=`"xs:string`">$(Esc-XmlText "$dp")</xr:DataPath>"
|
||||||
X "$indent`t`t<xr:ValueChange>$vc</xr:ValueChange>"
|
X "$indent`t`t<xr:ValueChange>$vc</xr:ValueChange>"
|
||||||
X "$indent`t</xr:Link>"
|
X "$indent`t</xr:Link>"
|
||||||
}
|
}
|
||||||
@@ -4509,7 +4564,7 @@ function Emit-TypeLink {
|
|||||||
$li = Get-ElProp $tl @('linkItem','элементСвязи')
|
$li = Get-ElProp $tl @('linkItem','элементСвязи')
|
||||||
if ($null -eq $li) { $li = 0 }
|
if ($null -eq $li) { $li = 0 }
|
||||||
X "$indent<TypeLink>"
|
X "$indent<TypeLink>"
|
||||||
X "$indent`t<xr:DataPath>$(Esc-Xml "$dp")</xr:DataPath>"
|
X "$indent`t<xr:DataPath>$(Esc-XmlText "$dp")</xr:DataPath>"
|
||||||
X "$indent`t<xr:LinkItem>$li</xr:LinkItem>"
|
X "$indent`t<xr:LinkItem>$li</xr:LinkItem>"
|
||||||
X "$indent</TypeLink>"
|
X "$indent</TypeLink>"
|
||||||
}
|
}
|
||||||
@@ -4616,7 +4671,7 @@ function Emit-LabelField {
|
|||||||
if ($el.titleLocation) { X "$inner<TitleLocation>$(Map-TitleLoc "$($el.titleLocation)")</TitleLocation>" }
|
if ($el.titleLocation) { X "$inner<TitleLocation>$(Map-TitleLoc "$($el.titleLocation)")</TitleLocation>" }
|
||||||
if ($el.editMode) { X "$inner<EditMode>$($el.editMode)</EditMode>" }
|
if ($el.editMode) { X "$inner<EditMode>$($el.editMode)</EditMode>" }
|
||||||
# FooterDataPath — путь данных подвала колонки (общий cell-prop, как у input); после EditMode
|
# FooterDataPath — путь данных подвала колонки (общий cell-prop, как у input); после EditMode
|
||||||
if ($el.footerDataPath) { X "$inner<FooterDataPath>$(Esc-Xml "$($el.footerDataPath)")</FooterDataPath>" }
|
if ($el.footerDataPath) { X "$inner<FooterDataPath>$(Esc-XmlText "$($el.footerDataPath)")</FooterDataPath>" }
|
||||||
# PasswordMode на LabelField — платформа эмитит явный false (редко); факт. значение
|
# PasswordMode на LabelField — платформа эмитит явный false (редко); факт. значение
|
||||||
if ($null -ne $el.passwordMode) { X "$inner<PasswordMode>$(if ($el.passwordMode){'true'}else{'false'})</PasswordMode>" }
|
if ($null -ne $el.passwordMode) { X "$inner<PasswordMode>$(if ($el.passwordMode){'true'}else{'false'})</PasswordMode>" }
|
||||||
Emit-ColumnPics -el $el -indent $inner
|
Emit-ColumnPics -el $el -indent $inner
|
||||||
@@ -4939,7 +4994,7 @@ function Emit-Button {
|
|||||||
if (($btnParam -is [System.Management.Automation.PSCustomObject] -or $btnParam -is [hashtable]) -and $btnParam.type) {
|
if (($btnParam -is [System.Management.Automation.PSCustomObject] -or $btnParam -is [hashtable]) -and $btnParam.type) {
|
||||||
Emit-Type -typeStr "$($btnParam.type)" -indent $inner -tag "Parameter" -tagAttrs ' xsi:type="v8:TypeDescription"'
|
Emit-Type -typeStr "$($btnParam.type)" -indent $inner -tag "Parameter" -tagAttrs ' xsi:type="v8:TypeDescription"'
|
||||||
} else {
|
} else {
|
||||||
X "$inner<Parameter xsi:type=`"xr:MDObjectRef`">$(Esc-Xml "$btnParam")</Parameter>"
|
X "$inner<Parameter xsi:type=`"xr:MDObjectRef`">$(Esc-XmlText "$btnParam")</Parameter>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# DataPath — привязка команды кнопки к контексту (Объект.Ref, Items.X.CurrentData.Поле)
|
# DataPath — привязка команды кнопки к контексту (Объект.Ref, Items.X.CurrentData.Поле)
|
||||||
@@ -4994,8 +5049,8 @@ function Emit-PictureDecoration {
|
|||||||
$srcStr = "$($el.src)"
|
$srcStr = "$($el.src)"
|
||||||
$lt = if ($el.loadTransparent -eq $true) { "true" } else { "false" }
|
$lt = if ($el.loadTransparent -eq $true) { "true" } else { "false" }
|
||||||
X "$inner<Picture>"
|
X "$inner<Picture>"
|
||||||
if ($srcStr -match '^abs:(.*)$') { X "$inner`t<xr:Abs>$(Esc-Xml $matches[1])</xr:Abs>" }
|
if ($srcStr -match '^abs:(.*)$') { X "$inner`t<xr:Abs>$(Esc-XmlText $matches[1])</xr:Abs>" }
|
||||||
else { X "$inner`t<xr:Ref>$(Esc-Xml $srcStr)</xr:Ref>" }
|
else { X "$inner`t<xr:Ref>$(Esc-XmlText $srcStr)</xr:Ref>" }
|
||||||
X "$inner`t<xr:LoadTransparent>$lt</xr:LoadTransparent>"
|
X "$inner`t<xr:LoadTransparent>$lt</xr:LoadTransparent>"
|
||||||
if ($el.transparentPixel) { X "$inner`t<xr:TransparentPixel x=`"$($el.transparentPixel.x)`" y=`"$($el.transparentPixel.y)`"/>" }
|
if ($el.transparentPixel) { X "$inner`t<xr:TransparentPixel x=`"$($el.transparentPixel.x)`" y=`"$($el.transparentPixel.y)`"/>" }
|
||||||
X "$inner</Picture>"
|
X "$inner</Picture>"
|
||||||
@@ -5039,7 +5094,7 @@ function Emit-PictureField {
|
|||||||
if ($null -ne $el.enableDrag) { X "$inner<EnableDrag>$(if ($el.enableDrag){'true'}else{'false'})</EnableDrag>" }
|
if ($null -ne $el.enableDrag) { X "$inner<EnableDrag>$(if ($el.enableDrag){'true'}else{'false'})</EnableDrag>" }
|
||||||
|
|
||||||
# FooterDataPath / FooterText — общие cell-свойства колонки (как у input/labelField)
|
# FooterDataPath / FooterText — общие cell-свойства колонки (как у input/labelField)
|
||||||
if ($el.footerDataPath) { X "$inner<FooterDataPath>$(Esc-Xml "$($el.footerDataPath)")</FooterDataPath>" }
|
if ($el.footerDataPath) { X "$inner<FooterDataPath>$(Esc-XmlText "$($el.footerDataPath)")</FooterDataPath>" }
|
||||||
if ($null -ne $el.footerText) { Emit-MLText -tag "FooterText" -text $el.footerText -indent $inner }
|
if ($null -ne $el.footerText) { Emit-MLText -tag "FooterText" -text $el.footerText -indent $inner }
|
||||||
|
|
||||||
# ValuesPicture — picture (collection) used to render the field's value.
|
# ValuesPicture — picture (collection) used to render the field's value.
|
||||||
@@ -5395,18 +5450,18 @@ function Emit-DLValue {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
$valStr = if ($val -is [bool]) { if ($val) { 'true' } else { 'false' } } else { "$val" }
|
$valStr = if ($val -is [bool]) { if ($val) { 'true' } else { 'false' } } else { "$val" }
|
||||||
if ($type -match '^(date|dateTime|time)') { X "$indent<dcssch:value xsi:type=`"xs:dateTime`">$(Esc-Xml $valStr)</dcssch:value>" }
|
if ($type -match '^(date|dateTime|time)') { X "$indent<dcssch:value xsi:type=`"xs:dateTime`">$(Esc-XmlText $valStr)</dcssch:value>" }
|
||||||
elseif ($type -eq "boolean") { X "$indent<dcssch:value xsi:type=`"xs:boolean`">$(Esc-Xml $valStr)</dcssch:value>" }
|
elseif ($type -eq "boolean") { X "$indent<dcssch:value xsi:type=`"xs:boolean`">$(Esc-XmlText $valStr)</dcssch:value>" }
|
||||||
elseif ($type -eq 'v8:Type') { $nsAttr = Get-ValueTypeNsAttr -valueType 'v8:Type' -value $valStr; X "$indent<dcssch:value$nsAttr xsi:type=`"v8:Type`">$(Esc-Xml $valStr)</dcssch:value>" }
|
elseif ($type -eq 'v8:Type') { $nsAttr = Get-ValueTypeNsAttr -valueType 'v8:Type' -value $valStr; X "$indent<dcssch:value$nsAttr xsi:type=`"v8:Type`">$(Esc-XmlText $valStr)</dcssch:value>" }
|
||||||
elseif ($type -match '^ent:') { X "$indent<dcssch:value xsi:type=`"$type`">$(Esc-Xml $valStr)</dcssch:value>" } # системное перечисление (ent:X) — value несёт тот же xsi:type
|
elseif ($type -match '^ent:') { X "$indent<dcssch:value xsi:type=`"$type`">$(Esc-XmlText $valStr)</dcssch:value>" } # системное перечисление (ent:X) — value несёт тот же xsi:type
|
||||||
elseif ($type -match '^decimal') { X "$indent<dcssch:value xsi:type=`"xs:decimal`">$(Esc-Xml $valStr)</dcssch:value>" }
|
elseif ($type -match '^decimal') { X "$indent<dcssch:value xsi:type=`"xs:decimal`">$(Esc-XmlText $valStr)</dcssch:value>" }
|
||||||
elseif ($type -match '^string') { X "$indent<dcssch:value xsi:type=`"xs:string`">$(Esc-Xml $valStr)</dcssch:value>" }
|
elseif ($type -match '^string') { X "$indent<dcssch:value xsi:type=`"xs:string`">$(Esc-XmlText $valStr)</dcssch:value>" }
|
||||||
elseif ($type -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|BusinessProcessRef|TaskRef|ExchangePlanRef)\.') { X "$indent<dcssch:value xsi:type=`"dcscor:DesignTimeValue`">$(Esc-Xml $valStr)</dcssch:value>" }
|
elseif ($type -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|BusinessProcessRef|TaskRef|ExchangePlanRef)\.') { X "$indent<dcssch:value xsi:type=`"dcscor:DesignTimeValue`">$(Esc-XmlText $valStr)</dcssch:value>" }
|
||||||
else {
|
else {
|
||||||
if ($valStr -match '^\d{4}-\d{2}-\d{2}T') { X "$indent<dcssch:value xsi:type=`"xs:dateTime`">$(Esc-Xml $valStr)</dcssch:value>" }
|
if ($valStr -match '^\d{4}-\d{2}-\d{2}T') { X "$indent<dcssch:value xsi:type=`"xs:dateTime`">$(Esc-XmlText $valStr)</dcssch:value>" }
|
||||||
elseif ($valStr -eq "true" -or $valStr -eq "false") { X "$indent<dcssch:value xsi:type=`"xs:boolean`">$(Esc-Xml $valStr)</dcssch:value>" }
|
elseif ($valStr -eq "true" -or $valStr -eq "false") { X "$indent<dcssch:value xsi:type=`"xs:boolean`">$(Esc-XmlText $valStr)</dcssch:value>" }
|
||||||
elseif ($valStr -match '^(ПланСчетов|Справочник|Перечисление|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена)\.' -or $valStr -match '^(ChartOfAccounts|Catalog|Enum|Document|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.') { X "$indent<dcssch:value xsi:type=`"dcscor:DesignTimeValue`">$(Esc-Xml $valStr)</dcssch:value>" }
|
elseif ($valStr -match '^(ПланСчетов|Справочник|Перечисление|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена)\.' -or $valStr -match '^(ChartOfAccounts|Catalog|Enum|Document|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.') { X "$indent<dcssch:value xsi:type=`"dcscor:DesignTimeValue`">$(Esc-XmlText $valStr)</dcssch:value>" }
|
||||||
else { X "$indent<dcssch:value xsi:type=`"xs:string`">$(Esc-Xml $valStr)</dcssch:value>" }
|
else { X "$indent<dcssch:value xsi:type=`"xs:string`">$(Esc-XmlText $valStr)</dcssch:value>" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5440,7 +5495,7 @@ function Emit-DLInputParameters {
|
|||||||
foreach ($item in $items) {
|
foreach ($item in $items) {
|
||||||
X "$indent`t<dcscor:item>"
|
X "$indent`t<dcscor:item>"
|
||||||
if ((Has-DLProp $item 'use') -and $null -ne $item.use -and -not $item.use) { X "$indent`t`t<dcscor:use>false</dcscor:use>" }
|
if ((Has-DLProp $item 'use') -and $null -ne $item.use -and -not $item.use) { X "$indent`t`t<dcscor:use>false</dcscor:use>" }
|
||||||
X "$indent`t`t<dcscor:parameter>$(Esc-Xml "$($item.parameter)")</dcscor:parameter>"
|
X "$indent`t`t<dcscor:parameter>$(Esc-XmlText "$($item.parameter)")</dcscor:parameter>"
|
||||||
if (Has-DLProp $item 'choiceParameters') {
|
if (Has-DLProp $item 'choiceParameters') {
|
||||||
$cpItems = if ($null -ne $item.choiceParameters) { @($item.choiceParameters) } else { @() }
|
$cpItems = if ($null -ne $item.choiceParameters) { @($item.choiceParameters) } else { @() }
|
||||||
if ($cpItems.Count -eq 0) { X "$indent`t`t<dcscor:value xsi:type=`"dcscor:ChoiceParameters`"/>" }
|
if ($cpItems.Count -eq 0) { X "$indent`t`t<dcscor:value xsi:type=`"dcscor:ChoiceParameters`"/>" }
|
||||||
@@ -5448,11 +5503,11 @@ function Emit-DLInputParameters {
|
|||||||
X "$indent`t`t<dcscor:value xsi:type=`"dcscor:ChoiceParameters`">"
|
X "$indent`t`t<dcscor:value xsi:type=`"dcscor:ChoiceParameters`">"
|
||||||
foreach ($cpItem in $cpItems) {
|
foreach ($cpItem in $cpItems) {
|
||||||
X "$indent`t`t`t<dcscor:item>"
|
X "$indent`t`t`t<dcscor:item>"
|
||||||
X "$indent`t`t`t`t<dcscor:choiceParameter>$(Esc-Xml "$($cpItem.name)")</dcscor:choiceParameter>"
|
X "$indent`t`t`t`t<dcscor:choiceParameter>$(Esc-XmlText "$($cpItem.name)")</dcscor:choiceParameter>"
|
||||||
foreach ($v in @($cpItem.values)) {
|
foreach ($v in @($cpItem.values)) {
|
||||||
if ($v -is [bool]) { X "$indent`t`t`t`t<dcscor:value xsi:type=`"xs:boolean`">$(if ($v) { 'true' } else { 'false' })</dcscor:value>" }
|
if ($v -is [bool]) { X "$indent`t`t`t`t<dcscor:value xsi:type=`"xs:boolean`">$(if ($v) { 'true' } else { 'false' })</dcscor:value>" }
|
||||||
elseif ($v -is [int] -or $v -is [long] -or $v -is [double] -or $v -is [decimal]) { X "$indent`t`t`t`t<dcscor:value xsi:type=`"xs:decimal`">$v</dcscor:value>" }
|
elseif ($v -is [int] -or $v -is [long] -or $v -is [double] -or $v -is [decimal]) { X "$indent`t`t`t`t<dcscor:value xsi:type=`"xs:decimal`">$v</dcscor:value>" }
|
||||||
else { X "$indent`t`t`t`t<dcscor:value xsi:type=`"dcscor:DesignTimeValue`">$(Esc-Xml "$v")</dcscor:value>" }
|
else { X "$indent`t`t`t`t<dcscor:value xsi:type=`"dcscor:DesignTimeValue`">$(Esc-XmlText "$v")</dcscor:value>" }
|
||||||
}
|
}
|
||||||
X "$indent`t`t`t</dcscor:item>"
|
X "$indent`t`t`t</dcscor:item>"
|
||||||
}
|
}
|
||||||
@@ -5465,8 +5520,8 @@ function Emit-DLInputParameters {
|
|||||||
X "$indent`t`t<dcscor:value xsi:type=`"dcscor:ChoiceParameterLinks`">"
|
X "$indent`t`t<dcscor:value xsi:type=`"dcscor:ChoiceParameterLinks`">"
|
||||||
foreach ($cplItem in $cplItems) {
|
foreach ($cplItem in $cplItems) {
|
||||||
X "$indent`t`t`t<dcscor:item>"
|
X "$indent`t`t`t<dcscor:item>"
|
||||||
X "$indent`t`t`t`t<dcscor:choiceParameter>$(Esc-Xml "$($cplItem.name)")</dcscor:choiceParameter>"
|
X "$indent`t`t`t`t<dcscor:choiceParameter>$(Esc-XmlText "$($cplItem.name)")</dcscor:choiceParameter>"
|
||||||
X "$indent`t`t`t`t<dcscor:value>$(Esc-Xml "$($cplItem.value)")</dcscor:value>"
|
X "$indent`t`t`t`t<dcscor:value>$(Esc-XmlText "$($cplItem.value)")</dcscor:value>"
|
||||||
$mode = if ($cplItem.mode) { "$($cplItem.mode)" } else { 'Auto' }
|
$mode = if ($cplItem.mode) { "$($cplItem.mode)" } else { 'Auto' }
|
||||||
X "$indent`t`t`t`t<dcscor:mode xmlns:d8p1=`"http://v8.1c.ru/8.1/data/enterprise`" xsi:type=`"d8p1:LinkedValueChangeMode`">$mode</dcscor:mode>"
|
X "$indent`t`t`t`t<dcscor:mode xmlns:d8p1=`"http://v8.1c.ru/8.1/data/enterprise`" xsi:type=`"d8p1:LinkedValueChangeMode`">$mode</dcscor:mode>"
|
||||||
X "$indent`t`t`t</dcscor:item>"
|
X "$indent`t`t`t</dcscor:item>"
|
||||||
@@ -5477,15 +5532,15 @@ function Emit-DLInputParameters {
|
|||||||
# Связь по типу (dcscor:TypeLink) — field + linkItem (структурное значение параметра).
|
# Связь по типу (dcscor:TypeLink) — field + linkItem (структурное значение параметра).
|
||||||
$tl = $item.typeLink
|
$tl = $item.typeLink
|
||||||
X "$indent`t`t<dcscor:value xsi:type=`"dcscor:TypeLink`">"
|
X "$indent`t`t<dcscor:value xsi:type=`"dcscor:TypeLink`">"
|
||||||
$tlf = Get-Prop $tl 'field'; if ($null -ne $tlf) { X "$indent`t`t`t<dcscor:field>$(Esc-Xml "$tlf")</dcscor:field>" }
|
$tlf = Get-Prop $tl 'field'; if ($null -ne $tlf) { X "$indent`t`t`t<dcscor:field>$(Esc-XmlText "$tlf")</dcscor:field>" }
|
||||||
$tli = Get-Prop $tl 'linkItem'; if ($null -ne $tli) { X "$indent`t`t`t<dcscor:linkItem>$(Esc-Xml "$tli")</dcscor:linkItem>" }
|
$tli = Get-Prop $tl 'linkItem'; if ($null -ne $tli) { X "$indent`t`t`t<dcscor:linkItem>$(Esc-XmlText "$tli")</dcscor:linkItem>" }
|
||||||
X "$indent`t`t</dcscor:value>"
|
X "$indent`t`t</dcscor:value>"
|
||||||
} elseif (Has-DLProp $item 'value') {
|
} elseif (Has-DLProp $item 'value') {
|
||||||
$val = $item.value
|
$val = $item.value
|
||||||
if ($val -is [bool]) { X "$indent`t`t<dcscor:value xsi:type=`"xs:boolean`">$(if ($val) { 'true' } else { 'false' })</dcscor:value>" }
|
if ($val -is [bool]) { X "$indent`t`t<dcscor:value xsi:type=`"xs:boolean`">$(if ($val) { 'true' } else { 'false' })</dcscor:value>" }
|
||||||
elseif ($val -is [int] -or $val -is [long] -or $val -is [double] -or $val -is [decimal]) { X "$indent`t`t<dcscor:value xsi:type=`"xs:decimal`">$val</dcscor:value>" }
|
elseif ($val -is [int] -or $val -is [long] -or $val -is [double] -or $val -is [decimal]) { X "$indent`t`t<dcscor:value xsi:type=`"xs:decimal`">$val</dcscor:value>" }
|
||||||
elseif ($val -is [hashtable] -or $val -is [System.Collections.IDictionary] -or $val -is [PSCustomObject]) { Emit-DLMLText -tag "dcscor:value" -text $val -indent "$indent`t`t" }
|
elseif ($val -is [hashtable] -or $val -is [System.Collections.IDictionary] -or $val -is [PSCustomObject]) { Emit-DLMLText -tag "dcscor:value" -text $val -indent "$indent`t`t" }
|
||||||
else { X "$indent`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml "$val")</dcscor:value>" }
|
else { X "$indent`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-XmlText "$val")</dcscor:value>" }
|
||||||
}
|
}
|
||||||
X "$indent`t</dcscor:item>"
|
X "$indent`t</dcscor:item>"
|
||||||
}
|
}
|
||||||
@@ -5560,16 +5615,16 @@ function Emit-DataParameters {
|
|||||||
}
|
}
|
||||||
X "$indent`t<dcscor:item xsi:type=`"dcsset:SettingsParameterValue`">"
|
X "$indent`t<dcscor:item xsi:type=`"dcsset:SettingsParameterValue`">"
|
||||||
if ($dp.use -eq $false) { X "$indent`t`t<dcscor:use>false</dcscor:use>" }
|
if ($dp.use -eq $false) { X "$indent`t`t<dcscor:use>false</dcscor:use>" }
|
||||||
X "$indent`t`t<dcscor:parameter>$(Esc-Xml "$($dp.parameter)")</dcscor:parameter>"
|
X "$indent`t`t<dcscor:parameter>$(Esc-XmlText "$($dp.parameter)")</dcscor:parameter>"
|
||||||
$dpValIsArr = ($dp.value -is [array]) -or ($dp.value -is [System.Collections.IList] -and $dp.value -isnot [string])
|
$dpValIsArr = ($dp.value -is [array]) -or ($dp.value -is [System.Collections.IList] -and $dp.value -isnot [string])
|
||||||
if ($dpValIsArr) {
|
if ($dpValIsArr) {
|
||||||
# Список значений параметра (valueListAllowed) — отдельный <dcscor:value> на каждое.
|
# Список значений параметра (valueListAllowed) — отдельный <dcscor:value> на каждое.
|
||||||
$avtype = "$($dp.valueType)"
|
$avtype = "$($dp.valueType)"
|
||||||
foreach ($v in @($dp.value)) {
|
foreach ($v in @($dp.value)) {
|
||||||
$vStr = if ($v -is [bool]) { "$v".ToLower() } else { "$v" }
|
$vStr = if ($v -is [bool]) { "$v".ToLower() } else { "$v" }
|
||||||
if ($avtype -match '^[a-zA-Z]+:') { X "$indent`t`t<dcscor:value xsi:type=`"$avtype`">$(Esc-Xml $vStr)</dcscor:value>" }
|
if ($avtype -match '^[a-zA-Z]+:') { X "$indent`t`t<dcscor:value xsi:type=`"$avtype`">$(Esc-XmlText $vStr)</dcscor:value>" }
|
||||||
elseif ("$vStr" -match '^(ПланСчетов|Справочник|Перечисление|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена)\.' -or "$vStr" -match '^(ChartOfAccounts|Catalog|Enum|Document|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.') { X "$indent`t`t<dcscor:value xsi:type=`"dcscor:DesignTimeValue`">$(Esc-Xml $vStr)</dcscor:value>" }
|
elseif ("$vStr" -match '^(ПланСчетов|Справочник|Перечисление|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена)\.' -or "$vStr" -match '^(ChartOfAccounts|Catalog|Enum|Document|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.') { X "$indent`t`t<dcscor:value xsi:type=`"dcscor:DesignTimeValue`">$(Esc-XmlText $vStr)</dcscor:value>" }
|
||||||
else { X "$indent`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml $vStr)</dcscor:value>" }
|
else { X "$indent`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-XmlText $vStr)</dcscor:value>" }
|
||||||
}
|
}
|
||||||
} elseif ($dp.nilValue -eq $true) {
|
} elseif ($dp.nilValue -eq $true) {
|
||||||
X "$indent`t`t<dcscor:value xsi:nil=`"true`"/>"
|
X "$indent`t`t<dcscor:value xsi:nil=`"true`"/>"
|
||||||
@@ -5592,41 +5647,41 @@ function Emit-DataParameters {
|
|||||||
if ($dp.value -is [PSCustomObject] -and $dp.value.PSObject.Properties['date']) { $_d = "$($dp.value.date)" }
|
if ($dp.value -is [PSCustomObject] -and $dp.value.PSObject.Properties['date']) { $_d = "$($dp.value.date)" }
|
||||||
elseif (($dp.value -is [System.Collections.IDictionary]) -and $dp.value.Contains('date')) { $_d = "$($dp.value['date'])" }
|
elseif (($dp.value -is [System.Collections.IDictionary]) -and $dp.value.Contains('date')) { $_d = "$($dp.value['date'])" }
|
||||||
X "$indent`t`t<dcscor:value xsi:type=`"v8:StandardBeginningDate`">"
|
X "$indent`t`t<dcscor:value xsi:type=`"v8:StandardBeginningDate`">"
|
||||||
X "$indent`t`t`t<v8:variant xsi:type=`"v8:StandardBeginningDateVariant`">$(Esc-Xml $_variantStr)</v8:variant>"
|
X "$indent`t`t`t<v8:variant xsi:type=`"v8:StandardBeginningDateVariant`">$(Esc-XmlText $_variantStr)</v8:variant>"
|
||||||
if ($_variantStr -eq 'Custom') { if (-not $_d) { $_d = '0001-01-01T00:00:00' }; X "$indent`t`t`t<v8:date>$(Esc-Xml $_d)</v8:date>" }
|
if ($_variantStr -eq 'Custom') { if (-not $_d) { $_d = '0001-01-01T00:00:00' }; X "$indent`t`t`t<v8:date>$(Esc-XmlText $_d)</v8:date>" }
|
||||||
X "$indent`t`t</dcscor:value>"
|
X "$indent`t`t</dcscor:value>"
|
||||||
} else {
|
} else {
|
||||||
$_sd = $null; $_ed = $null
|
$_sd = $null; $_ed = $null
|
||||||
if ($dp.value -is [PSCustomObject]) { if ($dp.value.PSObject.Properties['startDate']) { $_sd = "$($dp.value.startDate)" }; if ($dp.value.PSObject.Properties['endDate']) { $_ed = "$($dp.value.endDate)" } }
|
if ($dp.value -is [PSCustomObject]) { if ($dp.value.PSObject.Properties['startDate']) { $_sd = "$($dp.value.startDate)" }; if ($dp.value.PSObject.Properties['endDate']) { $_ed = "$($dp.value.endDate)" } }
|
||||||
else { if ($dp.value.Contains('startDate')) { $_sd = "$($dp.value['startDate'])" }; if ($dp.value.Contains('endDate')) { $_ed = "$($dp.value['endDate'])" } }
|
else { if ($dp.value.Contains('startDate')) { $_sd = "$($dp.value['startDate'])" }; if ($dp.value.Contains('endDate')) { $_ed = "$($dp.value['endDate'])" } }
|
||||||
X "$indent`t`t<dcscor:value xsi:type=`"v8:StandardPeriod`">"
|
X "$indent`t`t<dcscor:value xsi:type=`"v8:StandardPeriod`">"
|
||||||
X "$indent`t`t`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">$(Esc-Xml $_variantStr)</v8:variant>"
|
X "$indent`t`t`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">$(Esc-XmlText $_variantStr)</v8:variant>"
|
||||||
if ($_variantStr -eq 'Custom') { if (-not $_sd) { $_sd = '0001-01-01T00:00:00' }; if (-not $_ed) { $_ed = '0001-01-01T00:00:00' }; X "$indent`t`t`t<v8:startDate>$(Esc-Xml $_sd)</v8:startDate>"; X "$indent`t`t`t<v8:endDate>$(Esc-Xml $_ed)</v8:endDate>" }
|
if ($_variantStr -eq 'Custom') { if (-not $_sd) { $_sd = '0001-01-01T00:00:00' }; if (-not $_ed) { $_ed = '0001-01-01T00:00:00' }; X "$indent`t`t`t<v8:startDate>$(Esc-XmlText $_sd)</v8:startDate>"; X "$indent`t`t`t<v8:endDate>$(Esc-XmlText $_ed)</v8:endDate>" }
|
||||||
X "$indent`t`t</dcscor:value>"
|
X "$indent`t`t</dcscor:value>"
|
||||||
}
|
}
|
||||||
} elseif ($vtype -match '^[a-zA-Z]+:') {
|
} elseif ($vtype -match '^[a-zA-Z]+:') {
|
||||||
$vStr = if ($dp.value -is [bool]) { "$($dp.value)".ToLower() } else { "$($dp.value)" }
|
$vStr = if ($dp.value -is [bool]) { "$($dp.value)".ToLower() } else { "$($dp.value)" }
|
||||||
X "$indent`t`t<dcscor:value xsi:type=`"$vtype`">$(Esc-Xml $vStr)</dcscor:value>"
|
X "$indent`t`t<dcscor:value xsi:type=`"$vtype`">$(Esc-XmlText $vStr)</dcscor:value>"
|
||||||
} elseif ($vtype -eq 'boolean' -or $dp.value -is [bool]) {
|
} elseif ($vtype -eq 'boolean' -or $dp.value -is [bool]) {
|
||||||
X "$indent`t`t<dcscor:value xsi:type=`"xs:boolean`">$(Esc-Xml ("$($dp.value)".ToLower()))</dcscor:value>"
|
X "$indent`t`t<dcscor:value xsi:type=`"xs:boolean`">$(Esc-XmlText ("$($dp.value)".ToLower()))</dcscor:value>"
|
||||||
} elseif ($vtype -match '^date' -or "$($dp.value)" -match '^\d{4}-\d{2}-\d{2}T') {
|
} elseif ($vtype -match '^date' -or "$($dp.value)" -match '^\d{4}-\d{2}-\d{2}T') {
|
||||||
X "$indent`t`t<dcscor:value xsi:type=`"xs:dateTime`">$(Esc-Xml "$($dp.value)")</dcscor:value>"
|
X "$indent`t`t<dcscor:value xsi:type=`"xs:dateTime`">$(Esc-XmlText "$($dp.value)")</dcscor:value>"
|
||||||
} elseif ($vtype -match '^decimal') {
|
} elseif ($vtype -match '^decimal') {
|
||||||
X "$indent`t`t<dcscor:value xsi:type=`"xs:decimal`">$(Esc-Xml "$($dp.value)")</dcscor:value>"
|
X "$indent`t`t<dcscor:value xsi:type=`"xs:decimal`">$(Esc-XmlText "$($dp.value)")</dcscor:value>"
|
||||||
} elseif ($vtype -match '^string') {
|
} elseif ($vtype -match '^string') {
|
||||||
X "$indent`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml "$($dp.value)")</dcscor:value>"
|
X "$indent`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-XmlText "$($dp.value)")</dcscor:value>"
|
||||||
} elseif ("$($dp.value)" -match '^(ПланСчетов|Справочник|Перечисление|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена)\.' -or "$($dp.value)" -match '^(ChartOfAccounts|Catalog|Enum|Document|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.') {
|
} elseif ("$($dp.value)" -match '^(ПланСчетов|Справочник|Перечисление|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена)\.' -or "$($dp.value)" -match '^(ChartOfAccounts|Catalog|Enum|Document|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.') {
|
||||||
X "$indent`t`t<dcscor:value xsi:type=`"dcscor:DesignTimeValue`">$(Esc-Xml "$($dp.value)")</dcscor:value>"
|
X "$indent`t`t<dcscor:value xsi:type=`"dcscor:DesignTimeValue`">$(Esc-XmlText "$($dp.value)")</dcscor:value>"
|
||||||
} else {
|
} else {
|
||||||
X "$indent`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml "$($dp.value)")</dcscor:value>"
|
X "$indent`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-XmlText "$($dp.value)")</dcscor:value>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($dp.viewMode) { X "$indent`t`t<dcsset:viewMode>$(Esc-Xml "$($dp.viewMode)")</dcsset:viewMode>" }
|
if ($dp.viewMode) { X "$indent`t`t<dcsset:viewMode>$(Esc-XmlText "$($dp.viewMode)")</dcsset:viewMode>" }
|
||||||
if ($dp.userSettingID) { $uid = if ("$($dp.userSettingID)" -eq "auto") { New-Guid-String } else { "$($dp.userSettingID)" }; X "$indent`t`t<dcsset:userSettingID>$(Esc-Xml $uid)</dcsset:userSettingID>" }
|
if ($dp.userSettingID) { $uid = if ("$($dp.userSettingID)" -eq "auto") { New-Guid-String } else { "$($dp.userSettingID)" }; X "$indent`t`t<dcsset:userSettingID>$(Esc-XmlText $uid)</dcsset:userSettingID>" }
|
||||||
if ($dp.userSettingPresentation) { Emit-USPresentation -val $dp.userSettingPresentation -tag "dcsset:userSettingPresentation" -indent "$indent`t`t" }
|
if ($dp.userSettingPresentation) { Emit-USPresentation -val $dp.userSettingPresentation -tag "dcsset:userSettingPresentation" -indent "$indent`t`t" }
|
||||||
X "$indent`t</dcscor:item>"
|
X "$indent`t</dcscor:item>"
|
||||||
}
|
}
|
||||||
if ($null -ne $blockViewMode) { X "$indent`t<dcsset:viewMode>$(Esc-Xml "$blockViewMode")</dcsset:viewMode>" }
|
if ($null -ne $blockViewMode) { X "$indent`t<dcsset:viewMode>$(Esc-XmlText "$blockViewMode")</dcsset:viewMode>" }
|
||||||
X "$indent</dcsset:dataParameters>"
|
X "$indent</dcsset:dataParameters>"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5634,7 +5689,7 @@ function Emit-DLParameter {
|
|||||||
param($p, $parsed, [string]$indent)
|
param($p, $parsed, [string]$indent)
|
||||||
X "$indent<Parameter>"
|
X "$indent<Parameter>"
|
||||||
$ci = "$indent`t"
|
$ci = "$indent`t"
|
||||||
X "$ci<dcssch:name>$(Esc-Xml $parsed.name)</dcssch:name>"
|
X "$ci<dcssch:name>$(Esc-XmlText $parsed.name)</dcssch:name>"
|
||||||
# Title: явный override (shorthand [..] / объект title/presentation) или авто из имени.
|
# Title: явный override (shorthand [..] / объект title/presentation) или авто из имени.
|
||||||
$title = $null
|
$title = $null
|
||||||
if ($parsed.title) { $title = $parsed.title }
|
if ($parsed.title) { $title = $parsed.title }
|
||||||
@@ -5668,7 +5723,7 @@ function Emit-DLParameter {
|
|||||||
# expression
|
# expression
|
||||||
$expr = $null
|
$expr = $null
|
||||||
if ($p -isnot [string] -and (Has-DLProp $p 'expression') -and $p.expression) { $expr = "$($p.expression)" }
|
if ($p -isnot [string] -and (Has-DLProp $p 'expression') -and $p.expression) { $expr = "$($p.expression)" }
|
||||||
if ($expr) { X "$ci<dcssch:expression>$(Esc-Xml $expr)</dcssch:expression>" }
|
if ($expr) { X "$ci<dcssch:expression>$(Esc-XmlText $expr)</dcssch:expression>" }
|
||||||
# availableValues
|
# availableValues
|
||||||
if ($p -isnot [string] -and (Has-DLProp $p 'availableValues') -and $p.availableValues) {
|
if ($p -isnot [string] -and (Has-DLProp $p 'availableValues') -and $p.availableValues) {
|
||||||
foreach ($av in @($p.availableValues)) { Emit-DLAvailableValue -av $av -type $parsed.type -indent $ci }
|
foreach ($av in @($p.availableValues)) { Emit-DLAvailableValue -av $av -type $parsed.type -indent $ci }
|
||||||
@@ -5687,7 +5742,7 @@ function Emit-DLParameter {
|
|||||||
# use
|
# use
|
||||||
$useVal = $null
|
$useVal = $null
|
||||||
if ($p -isnot [string] -and (Has-DLProp $p 'use') -and $p.use) { $useVal = "$($p.use)" }
|
if ($p -isnot [string] -and (Has-DLProp $p 'use') -and $p.use) { $useVal = "$($p.use)" }
|
||||||
if ($useVal) { X "$ci<dcssch:use>$(Esc-Xml $useVal)</dcssch:use>" }
|
if ($useVal) { X "$ci<dcssch:use>$(Esc-XmlText $useVal)</dcssch:use>" }
|
||||||
X "$indent</Parameter>"
|
X "$indent</Parameter>"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5807,7 +5862,7 @@ function Emit-Attributes {
|
|||||||
}
|
}
|
||||||
if ($saveFields.Count -gt 0) {
|
if ($saveFields.Count -gt 0) {
|
||||||
X "$inner<Save>"
|
X "$inner<Save>"
|
||||||
foreach ($f in $saveFields) { X "$inner`t<Field>$(Esc-Xml $f)</Field>" }
|
foreach ($f in $saveFields) { X "$inner`t<Field>$(Esc-XmlText $f)</Field>" }
|
||||||
X "$inner</Save>"
|
X "$inner</Save>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5908,7 +5963,7 @@ function Emit-Attributes {
|
|||||||
X "$si<DynamicDataRead>$ddr</DynamicDataRead>"
|
X "$si<DynamicDataRead>$ddr</DynamicDataRead>"
|
||||||
if ($hasQuery) {
|
if ($hasQuery) {
|
||||||
$qtext = Resolve-QueryValue "$($st.query)" $script:queryBaseDir
|
$qtext = Resolve-QueryValue "$($st.query)" $script:queryBaseDir
|
||||||
X "$si<QueryText>$(Esc-Xml $qtext)</QueryText>"
|
X "$si<QueryText>$(Esc-XmlText $qtext)</QueryText>"
|
||||||
}
|
}
|
||||||
# Явные поля набора (редко): override title/dataPath
|
# Явные поля набора (редко): override title/dataPath
|
||||||
if ($st.fields) {
|
if ($st.fields) {
|
||||||
@@ -5923,8 +5978,8 @@ function Emit-Attributes {
|
|||||||
if ($null -ne (Get-Prop $fld 'dataPath')) { $dp = "$($fld.dataPath)" }
|
if ($null -ne (Get-Prop $fld 'dataPath')) { $dp = "$($fld.dataPath)" }
|
||||||
elseif ($isFolder) { $dp = "" }
|
elseif ($isFolder) { $dp = "" }
|
||||||
else { $dp = "$($fld.field)" }
|
else { $dp = "$($fld.field)" }
|
||||||
if ($dp -eq "") { X "$si`t<dcssch:dataPath/>" } else { X "$si`t<dcssch:dataPath>$(Esc-Xml "$dp")</dcssch:dataPath>" }
|
if ($dp -eq "") { X "$si`t<dcssch:dataPath/>" } else { X "$si`t<dcssch:dataPath>$(Esc-XmlText "$dp")</dcssch:dataPath>" }
|
||||||
if (-not $isFolder) { X "$si`t<dcssch:field>$(Esc-Xml "$($fld.field)")</dcssch:field>" }
|
if (-not $isFolder) { X "$si`t<dcssch:field>$(Esc-XmlText "$($fld.field)")</dcssch:field>" }
|
||||||
if ($fld.title) {
|
if ($fld.title) {
|
||||||
X "$si`t<dcssch:title xsi:type=`"v8:LocalStringType`">"
|
X "$si`t<dcssch:title xsi:type=`"v8:LocalStringType`">"
|
||||||
Emit-MLItems -val $fld.title -indent "$si`t`t"
|
Emit-MLItems -val $fld.title -indent "$si`t`t"
|
||||||
@@ -5934,7 +5989,7 @@ function Emit-Attributes {
|
|||||||
Emit-RestrictBlock 'useRestriction' $fld.useRestriction "$si`t"
|
Emit-RestrictBlock 'useRestriction' $fld.useRestriction "$si`t"
|
||||||
Emit-RestrictBlock 'attributeUseRestriction' $fld.attributeUseRestriction "$si`t"
|
Emit-RestrictBlock 'attributeUseRestriction' $fld.attributeUseRestriction "$si`t"
|
||||||
# presentationExpression поля — перед valueType (порядок исходника)
|
# presentationExpression поля — перед valueType (порядок исходника)
|
||||||
if ($fld.presentationExpression) { X "$si`t<dcssch:presentationExpression>$(Esc-Xml "$($fld.presentationExpression)")</dcssch:presentationExpression>" }
|
if ($fld.presentationExpression) { X "$si`t<dcssch:presentationExpression>$(Esc-XmlText "$($fld.presentationExpression)")</dcssch:presentationExpression>" }
|
||||||
# valueType поля набора (тип значения; вычисляемые/кастомные поля)
|
# valueType поля набора (тип значения; вычисляемые/кастомные поля)
|
||||||
if ($fld.valueType) { Emit-DLValueType -typeStr "$($fld.valueType)" -indent "$si`t" }
|
if ($fld.valueType) { Emit-DLValueType -typeStr "$($fld.valueType)" -indent "$si`t" }
|
||||||
# appearance поля (формат/оформление) — после valueType (порядок исходника)
|
# appearance поля (формат/оформление) — после valueType (порядок исходника)
|
||||||
@@ -5954,8 +6009,8 @@ function Emit-Attributes {
|
|||||||
Emit-DLParameters -params $st.parameters -indent $si
|
Emit-DLParameters -params $st.parameters -indent $si
|
||||||
# Ключ набора (query-based список без MainTable): KeyType (RowNumber/FieldValue/RowKey)
|
# Ключ набора (query-based список без MainTable): KeyType (RowNumber/FieldValue/RowKey)
|
||||||
# + KeyField* — после Parameter*, до MainTable. Захват/эмит факт. значений.
|
# + KeyField* — после Parameter*, до MainTable. Захват/эмит факт. значений.
|
||||||
if ($st.keyType) { X "$si<KeyType>$(Esc-Xml "$($st.keyType)")</KeyType>" }
|
if ($st.keyType) { X "$si<KeyType>$(Esc-XmlText "$($st.keyType)")</KeyType>" }
|
||||||
if ($st.keyFields) { foreach ($kf in @($st.keyFields)) { X "$si<KeyField>$(Esc-Xml "$kf")</KeyField>" } }
|
if ($st.keyFields) { foreach ($kf in @($st.keyFields)) { X "$si<KeyField>$(Esc-XmlText "$kf")</KeyField>" } }
|
||||||
if ($st.mainTable) { X "$si<MainTable>$(Normalize-MetaTypeRef "$($st.mainTable)")</MainTable>" }
|
if ($st.mainTable) { X "$si<MainTable>$(Normalize-MetaTypeRef "$($st.mainTable)")</MainTable>" }
|
||||||
# GetInvisibleFieldPresentations — после MainTable (дефолт true; эмитим только при заданном ключе = отклонении false).
|
# GetInvisibleFieldPresentations — после MainTable (дефолт true; эмитим только при заданном ключе = отклонении false).
|
||||||
if ($null -ne $st.getInvisibleFieldPresentations) { X "$si<GetInvisibleFieldPresentations>$(if ($st.getInvisibleFieldPresentations){'true'}else{'false'})</GetInvisibleFieldPresentations>" }
|
if ($null -ne $st.getInvisibleFieldPresentations) { X "$si<GetInvisibleFieldPresentations>$(if ($st.getInvisibleFieldPresentations){'true'}else{'false'})</GetInvisibleFieldPresentations>" }
|
||||||
@@ -6092,7 +6147,7 @@ function Emit-Commands {
|
|||||||
if (-not $cmdTable) { $cmdTable = $cmd.associatedTableElementId }
|
if (-not $cmdTable) { $cmdTable = $cmd.associatedTableElementId }
|
||||||
if (-not $cmdTable) { $cmdTable = $cmd.используемаяТаблица }
|
if (-not $cmdTable) { $cmdTable = $cmd.используемаяТаблица }
|
||||||
if ($cmdTable) {
|
if ($cmdTable) {
|
||||||
X "$inner<AssociatedTableElementId xsi:type=`"xs:string`">$(Esc-Xml "$cmdTable")</AssociatedTableElementId>"
|
X "$inner<AssociatedTableElementId xsi:type=`"xs:string`">$(Esc-XmlText "$cmdTable")</AssociatedTableElementId>"
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($cmd.shortcut) {
|
if ($cmd.shortcut) {
|
||||||
@@ -6184,10 +6239,10 @@ function Emit-CommandInterface {
|
|||||||
# group из дерева побеждает (если задан и непустой); явный group элемента — фолбэк
|
# group из дерева побеждает (если задан и непустой); явный group элемента — фолбэк
|
||||||
if ($treeGroup) { $grp = $treeGroup }
|
if ($treeGroup) { $grp = $treeGroup }
|
||||||
X "$inner`t<Item>"
|
X "$inner`t<Item>"
|
||||||
X "$inner`t`t<Command>$(Esc-Xml "$cmd")</Command>"
|
X "$inner`t`t<Command>$(Esc-XmlText "$cmd")</Command>"
|
||||||
X "$inner`t`t<Type>$type</Type>"
|
X "$inner`t`t<Type>$type</Type>"
|
||||||
if ($attr) { X "$inner`t`t<Attribute>$(Esc-Xml "$attr")</Attribute>" }
|
if ($attr) { X "$inner`t`t<Attribute>$(Esc-XmlText "$attr")</Attribute>" }
|
||||||
if ($grp) { X "$inner`t`t<CommandGroup>$(Esc-Xml "$grp")</CommandGroup>" }
|
if ($grp) { X "$inner`t`t<CommandGroup>$(Esc-XmlText "$grp")</CommandGroup>" }
|
||||||
if ($null -ne $idx) { X "$inner`t`t<Index>$idx</Index>" }
|
if ($null -ne $idx) { X "$inner`t`t<Index>$idx</Index>" }
|
||||||
if ($null -ne $dv) { X "$inner`t`t<DefaultVisible>$(if ($dv){'true'}else{'false'})</DefaultVisible>" }
|
if ($null -ne $dv) { X "$inner`t`t<DefaultVisible>$(if ($dv){'true'}else{'false'})</DefaultVisible>" }
|
||||||
if ($null -ne $vis) { Emit-XrFlag -tag 'Visible' -val $vis -indent "$inner`t`t" }
|
if ($null -ne $vis) { Emit-XrFlag -tag 'Visible' -val $vis -indent "$inner`t`t" }
|
||||||
@@ -6463,25 +6518,14 @@ function Compute-MainAcbAutofill {
|
|||||||
|
|
||||||
# --- 12. Main compilation ---
|
# --- 12. Main compilation ---
|
||||||
|
|
||||||
# Title
|
# Буфер и счётчики — с чистого листа: до этой точки они могли быть тронуты режимом from-object.
|
||||||
if ($def.title) {
|
|
||||||
Emit-MLText -tag "Title" -text $def.title -indent "`t"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Header
|
|
||||||
X '<?xml version="1.0" encoding="UTF-8"?>'
|
|
||||||
X "<Form xmlns=`"http://v8.1c.ru/8.3/xcf/logform`" xmlns:app=`"http://v8.1c.ru/8.2/managed-application/core`" xmlns:cfg=`"http://v8.1c.ru/8.1/data/enterprise/current-config`" xmlns:dcscor=`"http://v8.1c.ru/8.1/data-composition-system/core`" xmlns:dcssch=`"http://v8.1c.ru/8.1/data-composition-system/schema`" xmlns:dcsset=`"http://v8.1c.ru/8.1/data-composition-system/settings`" xmlns:ent=`"http://v8.1c.ru/8.1/data/enterprise`" xmlns:lf=`"http://v8.1c.ru/8.2/managed-application/logform`" xmlns:style=`"http://v8.1c.ru/8.1/data/ui/style`" xmlns:sys=`"http://v8.1c.ru/8.1/data/ui/fonts/system`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" xmlns:v8ui=`"http://v8.1c.ru/8.1/data/ui`" xmlns:web=`"http://v8.1c.ru/8.1/data/ui/colors/web`" xmlns:win=`"http://v8.1c.ru/8.1/data/ui/colors/windows`" xmlns:xr=`"http://v8.1c.ru/8.3/xcf/readable`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" version=`"$($script:formatVersion)`">"
|
|
||||||
|
|
||||||
# Oops — Title was emitted before header. Need to fix the order.
|
|
||||||
# Actually, let me restructure: build the body into a separate buffer, then assemble
|
|
||||||
|
|
||||||
# Reset and rebuild properly
|
|
||||||
$script:xml = New-Object System.Text.StringBuilder 8192
|
$script:xml = New-Object System.Text.StringBuilder 8192
|
||||||
$script:nextId = 1
|
$script:nextId = 1
|
||||||
$script:seenElementNames = @{} # пул имён элементов (глобально по всей форме)
|
$script:seenElementNames = @{} # пул имён элементов (глобально по всей форме)
|
||||||
|
|
||||||
|
# Header
|
||||||
X '<?xml version="1.0" encoding="UTF-8"?>'
|
X '<?xml version="1.0" encoding="UTF-8"?>'
|
||||||
X "<Form xmlns=`"http://v8.1c.ru/8.3/xcf/logform`" xmlns:app=`"http://v8.1c.ru/8.2/managed-application/core`" xmlns:cfg=`"http://v8.1c.ru/8.1/data/enterprise/current-config`" xmlns:dcscor=`"http://v8.1c.ru/8.1/data-composition-system/core`" xmlns:dcssch=`"http://v8.1c.ru/8.1/data-composition-system/schema`" xmlns:dcsset=`"http://v8.1c.ru/8.1/data-composition-system/settings`" xmlns:ent=`"http://v8.1c.ru/8.1/data/enterprise`" xmlns:lf=`"http://v8.1c.ru/8.2/managed-application/logform`" xmlns:style=`"http://v8.1c.ru/8.1/data/ui/style`" xmlns:sys=`"http://v8.1c.ru/8.1/data/ui/fonts/system`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" xmlns:v8ui=`"http://v8.1c.ru/8.1/data/ui`" xmlns:web=`"http://v8.1c.ru/8.1/data/ui/colors/web`" xmlns:win=`"http://v8.1c.ru/8.1/data/ui/colors/windows`" xmlns:xr=`"http://v8.1c.ru/8.3/xcf/readable`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" version=`"$($script:formatVersion)`">"
|
X "<Form $($script:formNsDecl) version=`"$($script:formatVersion)`">"
|
||||||
|
|
||||||
# 12a. Title (from def.title or properties.title — must be multilingual XML)
|
# 12a. Title (from def.title or properties.title — must be multilingual XML)
|
||||||
$formTitle = $def.title
|
$formTitle = $def.title
|
||||||
@@ -6535,7 +6579,7 @@ if ($null -ne $def.mobileCommandBarContent -and @($def.mobileCommandBarContent).
|
|||||||
X "`t`t`t<xr:CheckState>0</xr:CheckState>"
|
X "`t`t`t<xr:CheckState>0</xr:CheckState>"
|
||||||
# пустое значение → самозакрывающийся тег (зеркало платформы)
|
# пустое значение → самозакрывающийся тег (зеркало платформы)
|
||||||
if ([string]::IsNullOrEmpty("$nm")) { X "`t`t`t<xr:Value xsi:type=`"xs:string`"/>" }
|
if ([string]::IsNullOrEmpty("$nm")) { X "`t`t`t<xr:Value xsi:type=`"xs:string`"/>" }
|
||||||
else { X "`t`t`t<xr:Value xsi:type=`"xs:string`">$(Esc-Xml "$nm")</xr:Value>" }
|
else { X "`t`t`t<xr:Value xsi:type=`"xs:string`">$(Esc-XmlText "$nm")</xr:Value>" }
|
||||||
X "`t`t</xr:Item>"
|
X "`t`t</xr:Item>"
|
||||||
}
|
}
|
||||||
X "`t</MobileDeviceCommandBarContent>"
|
X "`t</MobileDeviceCommandBarContent>"
|
||||||
@@ -6622,7 +6666,7 @@ if (-not (Test-Path $outDir)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
[System.IO.File]::WriteAllText($outPath, $xml.ToString(), $enc)
|
[System.IO.File]::WriteAllText($outPath, $xml.ToString().TrimEnd("`r", "`n"), $enc)
|
||||||
|
|
||||||
# --- 13b. Auto-register form in parent object XML ---
|
# --- 13b. Auto-register form in parent object XML ---
|
||||||
|
|
||||||
@@ -6677,11 +6721,26 @@ if ($formsLeaf -eq 'Forms') {
|
|||||||
$regSettings = New-Object System.Xml.XmlWriterSettings
|
$regSettings = New-Object System.Xml.XmlWriterSettings
|
||||||
$regSettings.Encoding = $regEnc
|
$regSettings.Encoding = $regEnc
|
||||||
$regSettings.Indent = $false
|
$regSettings.Indent = $false
|
||||||
$regStream = New-Object System.IO.FileStream($objectXmlPath, [System.IO.FileMode]::Create)
|
$regSettings.NewLineHandling = [System.Xml.NewLineHandling]::None
|
||||||
$regWriter = [System.Xml.XmlWriter]::Create($regStream, $regSettings)
|
# Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
|
||||||
|
$regMem = New-Object System.IO.MemoryStream
|
||||||
|
$regWriter = [System.Xml.XmlWriter]::Create($regMem, $regSettings)
|
||||||
$objDoc.Save($regWriter)
|
$objDoc.Save($regWriter)
|
||||||
$regWriter.Close()
|
$regWriter.Flush(); $regWriter.Close()
|
||||||
$regStream.Close()
|
|
||||||
|
$regText = [System.Text.Encoding]::UTF8.GetString($regMem.ToArray())
|
||||||
|
$regMem.Close()
|
||||||
|
if ($regText.Length -gt 0 -and $regText[0] -eq [char]0xFEFF) { $regText = $regText.Substring(1) }
|
||||||
|
$regText = $regText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$regText = [regex]::Replace($regText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $objectXmlPath) -and ([System.IO.File]::ReadAllText($objectXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$regText = ($regText -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
|
[System.IO.File]::WriteAllText($objectXmlPath, $regText, $regEnc)
|
||||||
|
|
||||||
Write-Host " Registered: <Form>$formName</Form> in $objectName.xml"
|
Write-Host " Registered: <Form>$formName</Form> in $objectName.xml"
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
# form-decompile v0.147 — Decompile 1C managed Form.xml to JSON DSL (draft)
|
# form-decompile v0.148 — Decompile 1C managed Form.xml to JSON DSL (draft)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
|
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
|
||||||
param(
|
param(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# form-decompile v0.147 — Decompile 1C managed Form.xml to JSON DSL (draft)
|
# form-decompile v0.148 — Decompile 1C managed Form.xml to JSON DSL (draft)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
|
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
|
||||||
#
|
#
|
||||||
@@ -13,6 +13,28 @@ import xml.etree.ElementTree as ET
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
# --- 1. Namespaces ---
|
# --- 1. Namespaces ---
|
||||||
NS_LF = "http://v8.1c.ru/8.3/xcf/logform"
|
NS_LF = "http://v8.1c.ru/8.3/xcf/logform"
|
||||||
NS_V8 = "http://v8.1c.ru/8.1/data/core"
|
NS_V8 = "http://v8.1c.ru/8.1/data/core"
|
||||||
@@ -3115,7 +3137,7 @@ def main():
|
|||||||
parser = argparse.ArgumentParser(description='Decompile 1C managed Form.xml to JSON DSL', allow_abbrev=False)
|
parser = argparse.ArgumentParser(description='Decompile 1C managed Form.xml to JSON DSL', allow_abbrev=False)
|
||||||
parser.add_argument('-FormPath', '-Path', dest='FormPath', type=str, required=True)
|
parser.add_argument('-FormPath', '-Path', dest='FormPath', type=str, required=True)
|
||||||
parser.add_argument('-OutputPath', dest='OutputPath', type=str, default=None)
|
parser.add_argument('-OutputPath', dest='OutputPath', type=str, default=None)
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
form_path = args.FormPath
|
form_path = args.FormPath
|
||||||
output_path = args.OutputPath
|
output_path = args.OutputPath
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# form-edit v1.6 — Edit 1C managed form elements
|
# form-edit v1.14 — Edit 1C managed form elements (+esc_xml/esc_xml_text: разное экранирование атрибута и текста)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -270,6 +270,12 @@ function X {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Esc-Xml {
|
function Esc-Xml {
|
||||||
|
param([string]$s)
|
||||||
|
# Эскейп ЗНАЧЕНИЯ АТРИБУТА: & < > и кавычка — внутри "..." литеральная " невалидна.
|
||||||
|
return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"')
|
||||||
|
}
|
||||||
|
|
||||||
|
function Esc-XmlText {
|
||||||
# Экранирование ТЕКСТА элемента: только & < > . Кавычки в тексте платформа НЕ экранирует —
|
# Экранирование ТЕКСТА элемента: только & < > . Кавычки в тексте платформа НЕ экранирует —
|
||||||
# пишет литерально (проверено: 92142 сырых кавычки на корпус, ни одной "). " платформа
|
# пишет литерально (проверено: 92142 сырых кавычки на корпус, ни одной "). " платформа
|
||||||
# принимает, но при выгрузке нормализует обратно в кавычку → лишний шум в роундтрипе.
|
# принимает, но при выгрузке нормализует обратно в кавычку → лишний шум в роундтрипе.
|
||||||
@@ -282,7 +288,7 @@ function Emit-MLText {
|
|||||||
X "$indent<$tag>"
|
X "$indent<$tag>"
|
||||||
X "$indent`t<v8:item>"
|
X "$indent`t<v8:item>"
|
||||||
X "$indent`t`t<v8:lang>ru</v8:lang>"
|
X "$indent`t`t<v8:lang>ru</v8:lang>"
|
||||||
X "$indent`t`t<v8:content>$(Esc-Xml $text)</v8:content>"
|
X "$indent`t`t<v8:content>$(Esc-XmlText $text)</v8:content>"
|
||||||
X "$indent`t</v8:item>"
|
X "$indent`t</v8:item>"
|
||||||
X "$indent</$tag>"
|
X "$indent</$tag>"
|
||||||
}
|
}
|
||||||
@@ -311,24 +317,49 @@ $script:formTypeSynonyms["бизнеспроцессссылка"] = "
|
|||||||
$script:formTypeSynonyms["задачассылка"] = "TaskRef"
|
$script:formTypeSynonyms["задачассылка"] = "TaskRef"
|
||||||
$script:formTypeSynonyms["определяемыйтип"] = "DefinedType"
|
$script:formTypeSynonyms["определяемыйтип"] = "DefinedType"
|
||||||
|
|
||||||
|
# Алиас на локальный словарь: тело Resolve-TypeStr ниже — общая реализация,
|
||||||
|
# одинаковая во всех навыках (реестр в tests/skills/check-inline-drift.mjs).
|
||||||
|
$script:typeSynonyms = $script:formTypeSynonyms
|
||||||
|
|
||||||
function Resolve-TypeStr {
|
function Resolve-TypeStr {
|
||||||
param([string]$typeStr)
|
param([string]$typeStr)
|
||||||
if (-not $typeStr) { return $typeStr }
|
if (-not $typeStr) { return $typeStr }
|
||||||
|
|
||||||
|
# Прощающий ввод: ведущий префикс приходит копипастой из выгрузки. Без срезания он ломает
|
||||||
|
# поиск в словаре — русское имя типа остаётся непереведённым, и платформа отвечает
|
||||||
|
# «Неизвестное имя типа». cfg: снимаем всегда — он однозначно означает текущую конфигурацию.
|
||||||
|
# Сгенерированный dNpM: (в корпусе на этом URI встречаются d4p1, d5p1, d6p1 — имя префикса
|
||||||
|
# платформа выдаёт по порядку объявления) снимаем ТОЛЬКО у ссылочных типов, с точкой:
|
||||||
|
# сам по себе префикс многозначен — в формах d5p1:Chart, d5p1:TextDocument,
|
||||||
|
# d5p1:GeographicalSchema адресуют чужие пространства имён, и там он часть значения.
|
||||||
|
if ($typeStr.StartsWith('cfg:')) {
|
||||||
|
$typeStr = $typeStr.Substring(4)
|
||||||
|
} elseif ($typeStr.Contains('.') -and $typeStr -match '^d\d+p\d+:') {
|
||||||
|
$typeStr = $typeStr.Substring($typeStr.IndexOf(':') + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Параметризованные типы: Number(15,2), Строка(100)
|
||||||
if ($typeStr -match '^([^(]+)\((.+)\)$') {
|
if ($typeStr -match '^([^(]+)\((.+)\)$') {
|
||||||
$base = $Matches[1].Trim(); $params = $Matches[2]
|
$baseName = $Matches[1].Trim()
|
||||||
$r = $script:formTypeSynonyms[$base.ToLower()]
|
$params = $Matches[2]
|
||||||
if ($r) { return "$r($params)" }
|
$resolved = $script:typeSynonyms[$baseName.ToLower()]
|
||||||
|
if ($resolved) { return "$resolved($params)" }
|
||||||
return $typeStr
|
return $typeStr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Ссылочные типы: СправочникСсылка.Организации → CatalogRef.Организации
|
||||||
if ($typeStr.Contains('.')) {
|
if ($typeStr.Contains('.')) {
|
||||||
$i = $typeStr.IndexOf('.')
|
$dotIdx = $typeStr.IndexOf('.')
|
||||||
$prefix = $typeStr.Substring(0, $i); $suffix = $typeStr.Substring($i)
|
$prefix = $typeStr.Substring(0, $dotIdx)
|
||||||
$r = $script:formTypeSynonyms[$prefix.ToLower()]
|
$suffix = $typeStr.Substring($dotIdx) # includes the dot
|
||||||
if ($r) { return "$r$suffix" }
|
$resolved = $script:typeSynonyms[$prefix.ToLower()]
|
||||||
|
if ($resolved) { return "$resolved$suffix" }
|
||||||
return $typeStr
|
return $typeStr
|
||||||
}
|
}
|
||||||
$r = $script:formTypeSynonyms[$typeStr.ToLower()]
|
|
||||||
if ($r) { return $r }
|
# Простое имя
|
||||||
|
$resolved = $script:typeSynonyms[$typeStr.ToLower()]
|
||||||
|
if ($resolved) { return $resolved }
|
||||||
return $typeStr
|
return $typeStr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,7 +623,7 @@ function Emit-Label {
|
|||||||
X "$inner<Title formatted=`"$formatted`">"
|
X "$inner<Title formatted=`"$formatted`">"
|
||||||
X "$inner`t<v8:item>"
|
X "$inner`t<v8:item>"
|
||||||
X "$inner`t`t<v8:lang>ru</v8:lang>"
|
X "$inner`t`t<v8:lang>ru</v8:lang>"
|
||||||
X "$inner`t`t<v8:content>$(Esc-Xml "$($el.title)")</v8:content>"
|
X "$inner`t`t<v8:content>$(Esc-XmlText "$($el.title)")</v8:content>"
|
||||||
X "$inner`t</v8:item>"
|
X "$inner`t</v8:item>"
|
||||||
X "$inner</Title>"
|
X "$inner</Title>"
|
||||||
}
|
}
|
||||||
@@ -1387,8 +1418,16 @@ if ($def.elementEvents -and $def.elementEvents.Count -gt 0) {
|
|||||||
$content = $xmlDoc.OuterXml
|
$content = $xmlDoc.OuterXml
|
||||||
# Ensure encoding declaration is uppercase UTF-8
|
# Ensure encoding declaration is uppercase UTF-8
|
||||||
$content = $content -replace '^<\?xml version="1.0" encoding="utf-8"\?>', '<?xml version="1.0" encoding="UTF-8"?>'
|
$content = $content -replace '^<\?xml version="1.0" encoding="utf-8"\?>', '<?xml version="1.0" encoding="UTF-8"?>'
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$content = [regex]::Replace($content, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
|
||||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $resolvedFormPath) -and ([System.IO.File]::ReadAllText($resolvedFormPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$content = ($content -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
[System.IO.File]::WriteAllText($resolvedFormPath, $content, $enc)
|
[System.IO.File]::WriteAllText($resolvedFormPath, $content, $enc)
|
||||||
|
|
||||||
# === 14. Summary ===
|
# === 14. Summary ===
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# form-edit v1.6 — Edit 1C managed form elements (Python port)
|
# form-edit v1.14 — Edit 1C managed form elements (Python port) (+esc_xml/esc_xml_text: разное экранирование атрибута и текста)
|
||||||
# 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
|
||||||
@@ -11,6 +11,65 @@ from lxml import etree
|
|||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
class CIDict(dict):
|
||||||
|
# Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные
|
||||||
|
# реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки
|
||||||
|
# сохраняется — от него зависит порядок эмиссии.
|
||||||
|
def _actual(self, key):
|
||||||
|
if not isinstance(key, str) or dict.__contains__(self, key):
|
||||||
|
return key
|
||||||
|
ci = self.__dict__.get('_ci')
|
||||||
|
if ci is None or len(ci) != len(self):
|
||||||
|
ci = {k.lower(): k for k in self if isinstance(k, str)}
|
||||||
|
self.__dict__['_ci'] = ci
|
||||||
|
return ci.get(key.lower(), key)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return dict.__getitem__(self, self._actual(key))
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return dict.__contains__(self, self._actual(key))
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return dict.get(self, self._actual(key), default)
|
||||||
|
|
||||||
|
def pop(self, key, *default):
|
||||||
|
return dict.pop(self, self._actual(key), *default)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
# запись по ключу, отличающемуся регистром, обновляет существующий, а не плодит дубль
|
||||||
|
dict.__setitem__(self, self._actual(key), value)
|
||||||
|
|
||||||
|
def ci_json(obj):
|
||||||
|
"""Рекурсивно оборачивает разобранный JSON: словари → CIDict, списки обходятся."""
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return CIDict((k, ci_json(v)) for k, v in obj.items())
|
||||||
|
if isinstance(obj, list):
|
||||||
|
return [ci_json(v) for v in obj]
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
||||||
# Blocks edits of vendor objects "на замке" / read-only configs. Trigger = bin
|
# Blocks edits of vendor objects "на замке" / read-only configs. Trigger = bin
|
||||||
@@ -192,7 +251,7 @@ def assert_edit_allowed(target_path, require):
|
|||||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||||
parser.add_argument("-FormPath", "-Path", required=True)
|
parser.add_argument("-FormPath", "-Path", required=True)
|
||||||
parser.add_argument("-JsonPath", required=True)
|
parser.add_argument("-JsonPath", required=True)
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
form_path = args.FormPath
|
form_path = args.FormPath
|
||||||
json_path = args.JsonPath
|
json_path = args.JsonPath
|
||||||
@@ -226,6 +285,11 @@ def local_name(node):
|
|||||||
# ── helpers ──────────────────────────────────────────────────
|
# ── helpers ──────────────────────────────────────────────────
|
||||||
|
|
||||||
def esc_xml(s):
|
def esc_xml(s):
|
||||||
|
# Эскейп ЗНАЧЕНИЯ АТРИБУТА: & < > и кавычка — внутри "..." литеральная " невалидна.
|
||||||
|
return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
|
||||||
|
|
||||||
|
|
||||||
|
def esc_xml_text(s):
|
||||||
"""Экранирование ТЕКСТА элемента: только & < > . Кавычки платформа в тексте не экранирует
|
"""Экранирование ТЕКСТА элемента: только & < > . Кавычки платформа в тексте не экранирует
|
||||||
(92142 сырых кавычки на корпус, ни одной "); " она принимает, но нормализует обратно."""
|
(92142 сырых кавычки на корпус, ни одной "); " она принимает, но нормализует обратно."""
|
||||||
return s.replace('&', '&').replace('<', '<').replace('>', '>')
|
return s.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||||
@@ -254,7 +318,7 @@ root = tree.getroot()
|
|||||||
# ── 2. Load JSON ────────────────────────────────────────────
|
# ── 2. Load JSON ────────────────────────────────────────────
|
||||||
|
|
||||||
with open(json_path, "r", encoding="utf-8-sig") as f:
|
with open(json_path, "r", encoding="utf-8-sig") as f:
|
||||||
defn = json.load(f)
|
defn = ci_json(json.load(f))
|
||||||
|
|
||||||
# ── 3. Form name + header ───────────────────────────────────
|
# ── 3. Form name + header ───────────────────────────────────
|
||||||
|
|
||||||
@@ -386,23 +450,48 @@ _FORM_TYPE_SYNONYMS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Алиас на локальный словарь: тело resolve_type_str ниже — общая реализация,
|
||||||
|
# одинаковая во всех навыках (реестр в tests/skills/check-inline-drift.mjs).
|
||||||
|
TYPE_SYNONYMS = _FORM_TYPE_SYNONYMS
|
||||||
|
|
||||||
|
|
||||||
def resolve_type_str(type_str):
|
def resolve_type_str(type_str):
|
||||||
if not type_str:
|
if not type_str:
|
||||||
return type_str
|
return type_str
|
||||||
|
# Прощающий ввод: ведущий префикс приходит копипастой из выгрузки. Без срезания он ломает
|
||||||
|
# поиск в словаре — русское имя типа остаётся непереведённым, и платформа отвечает
|
||||||
|
# «Неизвестное имя типа». cfg: снимаем всегда — он однозначно означает текущую конфигурацию.
|
||||||
|
# Сгенерированный dNpM: (в корпусе на этом URI встречаются d4p1, d5p1, d6p1 — имя префикса
|
||||||
|
# платформа выдаёт по порядку объявления) снимаем ТОЛЬКО у ссылочных типов, с точкой:
|
||||||
|
# сам по себе префикс многозначен — в формах d5p1:Chart, d5p1:TextDocument,
|
||||||
|
# d5p1:GeographicalSchema адресуют чужие пространства имён, и там он часть значения.
|
||||||
|
if type_str.startswith('cfg:'):
|
||||||
|
type_str = type_str[4:]
|
||||||
|
elif '.' in type_str and re.match(r'^d\d+p\d+:', type_str):
|
||||||
|
type_str = type_str[type_str.index(':') + 1:]
|
||||||
|
# Параметризованные типы: Number(15,2), Строка(100)
|
||||||
m = re.match(r'^([^(]+)\((.+)\)$', type_str)
|
m = re.match(r'^([^(]+)\((.+)\)$', type_str)
|
||||||
if m:
|
if m:
|
||||||
base, params = m.group(1).strip(), m.group(2)
|
base_name = m.group(1).strip()
|
||||||
r = _FORM_TYPE_SYNONYMS.get(base.lower())
|
params = m.group(2)
|
||||||
return f"{r}({params})" if r else type_str
|
resolved = TYPE_SYNONYMS.get(base_name.lower())
|
||||||
|
if resolved:
|
||||||
|
return f'{resolved}({params})'
|
||||||
|
return type_str
|
||||||
|
# Ссылочные типы: СправочникСсылка.Организации -> CatalogRef.Организации
|
||||||
if '.' in type_str:
|
if '.' in type_str:
|
||||||
i = type_str.index('.')
|
dot_idx = type_str.index('.')
|
||||||
prefix, suffix = type_str[:i], type_str[i:]
|
prefix = type_str[:dot_idx]
|
||||||
r = _FORM_TYPE_SYNONYMS.get(prefix.lower())
|
suffix = type_str[dot_idx:] # includes the dot
|
||||||
return f"{r}{suffix}" if r else type_str
|
resolved = TYPE_SYNONYMS.get(prefix.lower())
|
||||||
r = _FORM_TYPE_SYNONYMS.get(type_str.lower())
|
if resolved:
|
||||||
return r if r else type_str
|
return f'{resolved}{suffix}'
|
||||||
|
return type_str
|
||||||
|
# Простое имя
|
||||||
|
resolved = TYPE_SYNONYMS.get(type_str.lower())
|
||||||
|
if resolved:
|
||||||
|
return resolved
|
||||||
|
return type_str
|
||||||
def emit_type(type_str, indent):
|
def emit_type(type_str, indent):
|
||||||
if not type_str:
|
if not type_str:
|
||||||
X(f"{indent}<Type/>")
|
X(f"{indent}<Type/>")
|
||||||
@@ -496,7 +585,7 @@ def emit_mltext(tag, text, indent):
|
|||||||
X(f"{indent}<{tag}>")
|
X(f"{indent}<{tag}>")
|
||||||
X(f"{indent}\t<v8:item>")
|
X(f"{indent}\t<v8:item>")
|
||||||
X(f"{indent}\t\t<v8:lang>ru</v8:lang>")
|
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\t<v8:content>{esc_xml_text(text)}</v8:content>")
|
||||||
X(f"{indent}\t</v8:item>")
|
X(f"{indent}\t</v8:item>")
|
||||||
X(f"{indent}</{tag}>")
|
X(f"{indent}</{tag}>")
|
||||||
|
|
||||||
@@ -724,7 +813,7 @@ def emit_label(el, name, _id, indent):
|
|||||||
X(f'{inner}<Title formatted="{formatted}">')
|
X(f'{inner}<Title formatted="{formatted}">')
|
||||||
X(f"{inner}\t<v8:item>")
|
X(f"{inner}\t<v8:item>")
|
||||||
X(f"{inner}\t\t<v8:lang>ru</v8:lang>")
|
X(f"{inner}\t\t<v8:lang>ru</v8:lang>")
|
||||||
X(f"{inner}\t\t<v8:content>{esc_xml(str(el['title']))}</v8:content>")
|
X(f"{inner}\t\t<v8:content>{esc_xml_text(str(el['title']))}</v8:content>")
|
||||||
X(f"{inner}\t</v8:item>")
|
X(f"{inner}\t</v8:item>")
|
||||||
X(f"{inner}</Title>")
|
X(f"{inner}</Title>")
|
||||||
emit_common_flags(el, inner)
|
emit_common_flags(el, inner)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# form-info v1.5 — Analyze 1C managed form structure
|
# form-info v1.7 — Analyze 1C managed form structure (+единое имя хелпера состояния поддержки)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$true)]
|
[Parameter(Mandatory=$true)]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# form-info v1.5 — Analyze 1C managed form structure
|
# form-info v1.7 — Analyze 1C managed form structure (+единое имя хелпера состояния поддержки)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -8,6 +8,28 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
# --- Namespace map ---
|
# --- Namespace map ---
|
||||||
|
|
||||||
NSMAP = {
|
NSMAP = {
|
||||||
@@ -353,7 +375,7 @@ def get_support_status_for_path(target_path):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
def is_external_root(xml_path):
|
def _sg_is_external_root(xml_path):
|
||||||
if not os.path.isfile(xml_path):
|
if not os.path.isfile(xml_path):
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
@@ -367,14 +389,14 @@ def get_support_status_for_path(target_path):
|
|||||||
rp = os.path.abspath(target_path)
|
rp = os.path.abspath(target_path)
|
||||||
# The target file itself may be the element meta-xml (e.g. Subsystems/X.xml).
|
# The target file itself may be the element meta-xml (e.g. Subsystems/X.xml).
|
||||||
elem_uuid = root_uuid(rp)
|
elem_uuid = root_uuid(rp)
|
||||||
if is_external_root(rp):
|
if _sg_is_external_root(rp):
|
||||||
return None
|
return None
|
||||||
bin_path = None
|
bin_path = None
|
||||||
d = os.path.dirname(rp)
|
d = os.path.dirname(rp)
|
||||||
for _ in range(12):
|
for _ in range(12):
|
||||||
if not d:
|
if not d:
|
||||||
break
|
break
|
||||||
if is_external_root(d + ".xml"):
|
if _sg_is_external_root(d + ".xml"):
|
||||||
return None
|
return None
|
||||||
if not elem_uuid:
|
if not elem_uuid:
|
||||||
elem_uuid = root_uuid(d + ".xml")
|
elem_uuid = root_uuid(d + ".xml")
|
||||||
@@ -433,7 +455,7 @@ def main():
|
|||||||
parser.add_argument("-Limit", type=int, default=150, help="Max lines to show")
|
parser.add_argument("-Limit", type=int, default=150, help="Max lines to show")
|
||||||
parser.add_argument("-Offset", type=int, default=0, help="Line offset for pagination")
|
parser.add_argument("-Offset", type=int, default=0, help="Line offset for pagination")
|
||||||
parser.add_argument("-Expand", default="", help="Expand collapsed section by name, or * for all")
|
parser.add_argument("-Expand", default="", help="Expand collapsed section by name, or * for all")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
form_path = args.FormPath
|
form_path = args.FormPath
|
||||||
limit = args.Limit
|
limit = args.Limit
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# form-remove v1.4 — Remove form from 1C object
|
# form-remove v1.9 — Remove form from 1C object
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -64,6 +64,10 @@ foreach ($node in $formNodes) {
|
|||||||
$parent.RemoveChild($prev) | Out-Null
|
$parent.RemoveChild($prev) | Out-Null
|
||||||
}
|
}
|
||||||
$parent.RemoveChild($node) | Out-Null
|
$parent.RemoveChild($node) | Out-Null
|
||||||
|
# Опустевший контейнер: остаётся отступ-whitespace, и XmlWriter пишет пару
|
||||||
|
# <ChildObjects>\n\t\t</ChildObjects>. Платформа пишет только <ChildObjects/>
|
||||||
|
# (1394 самозакрывающихся на acc+erp, пустых пар ни в одной форме — 0).
|
||||||
|
if ($parent.SelectNodes("*").Count -eq 0) { $parent.IsEmpty = $true }
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,7 +78,9 @@ foreach ($node in $formNodes) {
|
|||||||
$formRefRe = "Form\.$([regex]::Escape($FormName))$"
|
$formRefRe = "Form\.$([regex]::Escape($FormName))$"
|
||||||
foreach ($node in $xmlDoc.SelectNodes("//md:*", $nsMgr)) {
|
foreach ($node in $xmlDoc.SelectNodes("//md:*", $nsMgr)) {
|
||||||
if ($node.LocalName -like "*Form" -and $node.InnerText -and $node.InnerText -match $formRefRe) {
|
if ($node.LocalName -like "*Form" -and $node.InnerText -and $node.InnerText -match $formRefRe) {
|
||||||
$node.InnerText = ""
|
# IsEmpty, а не InnerText="": пустая строка сериализуется парой <Tag></Tag>, а
|
||||||
|
# Конфигуратор пустых пар не пишет (0 на 476 942 XML корпуса) — нужен <Tag/>.
|
||||||
|
$node.IsEmpty = $true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,11 +89,26 @@ $encBom = New-Object System.Text.UTF8Encoding($true)
|
|||||||
$settings = New-Object System.Xml.XmlWriterSettings
|
$settings = New-Object System.Xml.XmlWriterSettings
|
||||||
$settings.Encoding = $encBom
|
$settings.Encoding = $encBom
|
||||||
$settings.Indent = $false
|
$settings.Indent = $false
|
||||||
|
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
|
||||||
|
|
||||||
$stream = New-Object System.IO.FileStream($rootXmlFull.Path, [System.IO.FileMode]::Create)
|
# Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
|
||||||
$writer = [System.Xml.XmlWriter]::Create($stream, $settings)
|
$memStream = New-Object System.IO.MemoryStream
|
||||||
|
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
|
||||||
$xmlDoc.Save($writer)
|
$xmlDoc.Save($writer)
|
||||||
$writer.Close()
|
$writer.Flush(); $writer.Close()
|
||||||
$stream.Close()
|
|
||||||
|
$xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
|
||||||
|
$memStream.Close()
|
||||||
|
if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) }
|
||||||
|
$xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$xmlText = [regex]::Replace($xmlText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $rootXmlFull.Path) -and ([System.IO.File]::ReadAllText($rootXmlFull.Path) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
|
[System.IO.File]::WriteAllText($rootXmlFull.Path, $xmlText, $encBom)
|
||||||
|
|
||||||
Write-Host "[OK] Форма $FormName удалена из $rootXmlPath"
|
Write-Host "[OK] Форма $FormName удалена из $rootXmlPath"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# remove-form v1.4 — Remove form from 1C object
|
# form-remove v1.9 — Remove form from 1C object
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -10,6 +10,28 @@ import sys
|
|||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
NSMAP = {"md": "http://v8.1c.ru/8.3/MDClasses"}
|
NSMAP = {"md": "http://v8.1c.ru/8.3/MDClasses"}
|
||||||
|
|
||||||
|
|
||||||
@@ -30,21 +52,22 @@ def _detect_xml_style(path):
|
|||||||
|
|
||||||
|
|
||||||
def _finalize_xml_bytes(xml_bytes, style):
|
def _finalize_xml_bytes(xml_bytes, style):
|
||||||
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None)."""
|
"""Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
|
||||||
enc_decl = style["enc"] if style else "utf-8"
|
выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
|
||||||
|
enc_decl = style["enc"] if style else "UTF-8"
|
||||||
xml_bytes = xml_bytes.replace(
|
xml_bytes = xml_bytes.replace(
|
||||||
b"<?xml version='1.0' encoding='UTF-8'?>",
|
b"<?xml version='1.0' encoding='UTF-8'?>",
|
||||||
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
||||||
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
||||||
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
||||||
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
||||||
# Финальный перенос — как в оригинале (новый файл → есть)
|
# Финальный перенос — как в оригинале (новый файл → нет, канон #57)
|
||||||
want_final_nl = style["final_nl"] if style else True
|
want_final_nl = style["final_nl"] if style else False
|
||||||
xml_bytes = xml_bytes.rstrip(b"\n")
|
xml_bytes = xml_bytes.rstrip(b"\n")
|
||||||
if want_final_nl:
|
if want_final_nl:
|
||||||
xml_bytes += b"\n"
|
xml_bytes += b"\n"
|
||||||
# EOL — как в оригинале (новый файл → LF, текущее поведение)
|
# EOL — как в оригинале (новый файл → CRLF, канон #57)
|
||||||
if style and style["crlf"]:
|
if (style["crlf"] if style else True):
|
||||||
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
||||||
return xml_bytes
|
return xml_bytes
|
||||||
|
|
||||||
@@ -67,7 +90,7 @@ def main():
|
|||||||
parser.add_argument("-ObjectName", "-ProcessorName", required=True)
|
parser.add_argument("-ObjectName", "-ProcessorName", required=True)
|
||||||
parser.add_argument("-FormName", required=True)
|
parser.add_argument("-FormName", required=True)
|
||||||
parser.add_argument("-SrcDir", default="src")
|
parser.add_argument("-SrcDir", default="src")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
object_name = args.ObjectName
|
object_name = args.ObjectName
|
||||||
form_name = args.FormName
|
form_name = args.FormName
|
||||||
@@ -119,6 +142,10 @@ def main():
|
|||||||
if parent.text and parent.text.strip() == "":
|
if parent.text and parent.text.strip() == "":
|
||||||
parent.text = ""
|
parent.text = ""
|
||||||
parent.remove(node)
|
parent.remove(node)
|
||||||
|
# Опустевший контейнер: text="" сериализуется парой <ChildObjects></ChildObjects>,
|
||||||
|
# а нужен <ChildObjects/> — PS-порт через DOM даёт именно его.
|
||||||
|
if len(parent) == 0 and not (parent.text or "").strip():
|
||||||
|
parent.text = None
|
||||||
break
|
break
|
||||||
|
|
||||||
# Clear any Default*/Auxiliary* form slot that pointed to the removed form
|
# Clear any Default*/Auxiliary* form slot that pointed to the removed form
|
||||||
@@ -129,7 +156,9 @@ def main():
|
|||||||
if not isinstance(el.tag, str):
|
if not isinstance(el.tag, str):
|
||||||
continue
|
continue
|
||||||
if etree.QName(el).localname.endswith("Form") and el.text and ref_re.search(el.text):
|
if etree.QName(el).localname.endswith("Form") and el.text and ref_re.search(el.text):
|
||||||
el.text = ""
|
# text=None, а не "": пустая строка сериализуется парой <Tag></Tag>, а
|
||||||
|
# Конфигуратор пустых пар не пишет (0 на 476 942 XML корпуса) — нужен <Tag/>.
|
||||||
|
el.text = None
|
||||||
|
|
||||||
# Save with BOM
|
# Save with BOM
|
||||||
save_xml_with_bom(tree, root_xml_full)
|
save_xml_with_bom(tree, root_xml_full)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# form-validate v1.9 — Validate 1C managed form
|
# form-validate v1.10 — Validate 1C managed form
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# form-validate v1.9 — Validate 1C managed form
|
# form-validate v1.10 — Validate 1C managed form
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -8,6 +8,28 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
F_NS = "http://v8.1c.ru/8.3/xcf/logform"
|
F_NS = "http://v8.1c.ru/8.3/xcf/logform"
|
||||||
V8_NS = "http://v8.1c.ru/8.1/data/core"
|
V8_NS = "http://v8.1c.ru/8.1/data/core"
|
||||||
|
|
||||||
@@ -60,7 +82,7 @@ def main():
|
|||||||
parser.add_argument("-FormPath", "-Path", required=True)
|
parser.add_argument("-FormPath", "-Path", required=True)
|
||||||
parser.add_argument("-Detailed", action="store_true")
|
parser.add_argument("-Detailed", action="store_true")
|
||||||
parser.add_argument("-MaxErrors", type=int, default=30)
|
parser.add_argument("-MaxErrors", type=int, default=30)
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
form_path = args.FormPath
|
form_path = args.FormPath
|
||||||
detailed = args.Detailed
|
detailed = args.Detailed
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# help-add v1.9 — Add built-in help to 1C object
|
# help-add v1.19 — Add built-in help to 1C object (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -149,10 +149,20 @@ function Assert-EditAllowed([string]$targetPath, [string]$require) {
|
|||||||
function Detect-FormatVersion([string]$dir) {
|
function Detect-FormatVersion([string]$dir) {
|
||||||
$d = $dir
|
$d = $dir
|
||||||
while ($d) {
|
while ($d) {
|
||||||
|
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||||
|
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||||
|
$extPath = "$d.xml"
|
||||||
|
if (Test-Path $extPath) {
|
||||||
|
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
|
||||||
|
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
|
||||||
|
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||||
|
}
|
||||||
$cfgPath = Join-Path $d "Configuration.xml"
|
$cfgPath = Join-Path $d "Configuration.xml"
|
||||||
if (Test-Path $cfgPath) {
|
if (Test-Path $cfgPath) {
|
||||||
$content = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
||||||
$head = $content.Substring(0, [Math]::Min(2000, $content.Length))
|
# Длину среза берём по СТРОКЕ, а не по размеру файла: размер в БАЙТАХ, Substring считает
|
||||||
|
# СИМВОЛЫ, и на кириллице байт больше — короткий Configuration.xml ронял навык исключением.
|
||||||
|
$head = $cfgText.Substring(0, [Math]::Min(2000, $cfgText.Length))
|
||||||
if ($head -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
if ($head -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||||
}
|
}
|
||||||
$parent = Split-Path $d -Parent
|
$parent = Split-Path $d -Parent
|
||||||
@@ -195,7 +205,18 @@ $helpXml = @"
|
|||||||
</Help>
|
</Help>
|
||||||
"@
|
"@
|
||||||
|
|
||||||
[System.IO.File]::WriteAllText($helpXmlPath, $helpXml, $encBom)
|
# XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
|
||||||
|
#
|
||||||
|
# Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
|
||||||
|
# копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||||
|
#
|
||||||
|
# HTML-страница сюда НЕ идёт — платформа хранит её с LF.
|
||||||
|
function Write-XmlFile([string]$path, [string]$text, $encoding) {
|
||||||
|
$t = ($text -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
|
[System.IO.File]::WriteAllText($path, $t.TrimEnd("`r", "`n"), $encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-XmlFile $helpXmlPath $helpXml $encBom
|
||||||
|
|
||||||
# --- 2. Help/<lang>.html ---
|
# --- 2. Help/<lang>.html ---
|
||||||
|
|
||||||
@@ -255,11 +276,26 @@ if (Test-Path $formsDir) {
|
|||||||
$settings = New-Object System.Xml.XmlWriterSettings
|
$settings = New-Object System.Xml.XmlWriterSettings
|
||||||
$settings.Encoding = $encBom
|
$settings.Encoding = $encBom
|
||||||
$settings.Indent = $false
|
$settings.Indent = $false
|
||||||
$stream = New-Object System.IO.FileStream($formMeta.FullName, [System.IO.FileMode]::Create)
|
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
|
||||||
$writer = [System.Xml.XmlWriter]::Create($stream, $settings)
|
# Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
|
||||||
|
$memStream = New-Object System.IO.MemoryStream
|
||||||
|
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
|
||||||
$xmlDoc.Save($writer)
|
$xmlDoc.Save($writer)
|
||||||
$writer.Close()
|
$writer.Flush(); $writer.Close()
|
||||||
$stream.Close()
|
|
||||||
|
$xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
|
||||||
|
$memStream.Close()
|
||||||
|
if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) }
|
||||||
|
$xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$xmlText = [regex]::Replace($xmlText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $formMeta.FullName) -and ([System.IO.File]::ReadAllText($formMeta.FullName) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
|
[System.IO.File]::WriteAllText($formMeta.FullName, $xmlText, $encBom)
|
||||||
|
|
||||||
Write-Host " IncludeHelpInContents добавлен: $($formMeta.Name)"
|
Write-Host " IncludeHelpInContents добавлен: $($formMeta.Name)"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# add-help v1.9 — Add built-in help to 1C object
|
# help-add v1.19 — Add built-in help to 1C object (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -10,6 +10,28 @@ import sys
|
|||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
NSMAP = {"md": "http://v8.1c.ru/8.3/MDClasses"}
|
NSMAP = {"md": "http://v8.1c.ru/8.3/MDClasses"}
|
||||||
|
|
||||||
|
|
||||||
@@ -191,6 +213,16 @@ def assert_edit_allowed(target_path, require):
|
|||||||
|
|
||||||
def detect_format_version(d):
|
def detect_format_version(d):
|
||||||
while d:
|
while d:
|
||||||
|
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||||
|
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||||
|
ext_path = d + ".xml"
|
||||||
|
if os.path.isfile(ext_path):
|
||||||
|
with open(ext_path, "r", encoding="utf-8-sig") as f:
|
||||||
|
ext_head = f.read(2000)
|
||||||
|
if re.search(r'<(ExternalDataProcessor|ExternalReport)[ >]', ext_head):
|
||||||
|
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', ext_head)
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
cfg_path = os.path.join(d, "Configuration.xml")
|
cfg_path = os.path.join(d, "Configuration.xml")
|
||||||
if os.path.isfile(cfg_path):
|
if os.path.isfile(cfg_path):
|
||||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||||
@@ -222,21 +254,22 @@ def _detect_xml_style(path):
|
|||||||
|
|
||||||
|
|
||||||
def _finalize_xml_bytes(xml_bytes, style):
|
def _finalize_xml_bytes(xml_bytes, style):
|
||||||
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None)."""
|
"""Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
|
||||||
enc_decl = style["enc"] if style else "utf-8"
|
выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
|
||||||
|
enc_decl = style["enc"] if style else "UTF-8"
|
||||||
xml_bytes = xml_bytes.replace(
|
xml_bytes = xml_bytes.replace(
|
||||||
b"<?xml version='1.0' encoding='UTF-8'?>",
|
b"<?xml version='1.0' encoding='UTF-8'?>",
|
||||||
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
||||||
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
||||||
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
||||||
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
||||||
# Финальный перенос — как в оригинале (новый файл → есть)
|
# Финальный перенос — как в оригинале (новый файл → нет, канон #57)
|
||||||
want_final_nl = style["final_nl"] if style else True
|
want_final_nl = style["final_nl"] if style else False
|
||||||
xml_bytes = xml_bytes.rstrip(b"\n")
|
xml_bytes = xml_bytes.rstrip(b"\n")
|
||||||
if want_final_nl:
|
if want_final_nl:
|
||||||
xml_bytes += b"\n"
|
xml_bytes += b"\n"
|
||||||
# EOL — как в оригинале (новый файл → LF, текущее поведение)
|
# EOL — как в оригинале (новый файл → CRLF, канон #57)
|
||||||
if style and style["crlf"]:
|
if (style["crlf"] if style else True):
|
||||||
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
||||||
return xml_bytes
|
return xml_bytes
|
||||||
|
|
||||||
@@ -252,10 +285,24 @@ def save_xml_with_bom(tree, path):
|
|||||||
f.write(xml_bytes)
|
f.write(xml_bytes)
|
||||||
|
|
||||||
|
|
||||||
def write_text_with_bom(path, text):
|
def write_utf8_bom(path, content):
|
||||||
"""Write text to file with UTF-8 BOM."""
|
# newline='' — без трансляции: иначе текстовый режим Python дал бы CRLF на Windows
|
||||||
with open(path, "w", encoding="utf-8-sig") as f:
|
# и LF на macOS, то есть вывод навыка зависел бы от ОС.
|
||||||
f.write(text)
|
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def write_xml_file(path, content):
|
||||||
|
"""XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
|
||||||
|
|
||||||
|
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
|
||||||
|
копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||||
|
|
||||||
|
HTML-страница сюда НЕ идёт — платформа хранит её с LF.
|
||||||
|
"""
|
||||||
|
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
|
||||||
|
write_utf8_bom(path, text)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -265,7 +312,7 @@ def main():
|
|||||||
parser.add_argument("-ObjectName", required=True)
|
parser.add_argument("-ObjectName", required=True)
|
||||||
parser.add_argument("-Lang", default="ru")
|
parser.add_argument("-Lang", default="ru")
|
||||||
parser.add_argument("-SrcDir", default="src")
|
parser.add_argument("-SrcDir", default="src")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
object_name = args.ObjectName
|
object_name = args.ObjectName
|
||||||
lang = args.Lang
|
lang = args.Lang
|
||||||
@@ -301,7 +348,7 @@ def main():
|
|||||||
'</Help>'
|
'</Help>'
|
||||||
)
|
)
|
||||||
|
|
||||||
write_text_with_bom(help_xml_path, help_xml)
|
write_xml_file(help_xml_path, help_xml)
|
||||||
|
|
||||||
# --- 2. Help/<lang>.html ---
|
# --- 2. Help/<lang>.html ---
|
||||||
|
|
||||||
@@ -324,7 +371,7 @@ def main():
|
|||||||
'</html>'
|
'</html>'
|
||||||
)
|
)
|
||||||
|
|
||||||
write_text_with_bom(help_html_path, help_html)
|
write_utf8_bom(help_html_path, help_html)
|
||||||
|
|
||||||
# --- 3. Check IncludeHelpInContents in form metadata ---
|
# --- 3. Check IncludeHelpInContents in form metadata ---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# img-grid v1.1 — Overlay numbered grid on image
|
# img-grid v1.2 — Overlay numbered grid on image
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
"""Overlay a numbered grid on an image to help determine column/row proportions.
|
"""Overlay a numbered grid on an image to help determine column/row proportions.
|
||||||
|
|
||||||
@@ -16,6 +16,28 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
MARGIN_TOP = 20
|
MARGIN_TOP = 20
|
||||||
MARGIN_LEFT = 24
|
MARGIN_LEFT = 24
|
||||||
|
|
||||||
@@ -30,7 +52,7 @@ def main():
|
|||||||
parser.add_argument("-r", "--rows", type=int, default=0,
|
parser.add_argument("-r", "--rows", type=int, default=0,
|
||||||
help="Number of horizontal divisions (0 = auto, match cell aspect ratio)")
|
help="Number of horizontal divisions (0 = auto, match cell aspect ratio)")
|
||||||
parser.add_argument("-o", "--output", help="Output path (default: <name>-grid.<ext>)")
|
parser.add_argument("-o", "--output", help="Output path (default: <name>-grid.<ext>)")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
if args.cols <= 0:
|
if args.cols <= 0:
|
||||||
parser.error("--cols must be greater than 0")
|
parser.error("--cols must be greater than 0")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# interface-edit v1.9 — Edit 1C CommandInterface.xml
|
# interface-edit v1.18 — Edit 1C CommandInterface.xml (+русские алиасы типов: формы с ё и без)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)][Alias('Path')][string]$CIPath,
|
[Parameter(Mandatory)][Alias('Path')][string]$CIPath,
|
||||||
@@ -162,6 +162,14 @@ Assert-EditAllowed $CIPath 'editable'
|
|||||||
function Detect-FormatVersion([string]$dir) {
|
function Detect-FormatVersion([string]$dir) {
|
||||||
$d = $dir
|
$d = $dir
|
||||||
while ($d) {
|
while ($d) {
|
||||||
|
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||||
|
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||||
|
$extPath = "$d.xml"
|
||||||
|
if (Test-Path $extPath) {
|
||||||
|
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
|
||||||
|
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
|
||||||
|
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||||
|
}
|
||||||
$cfgPath = Join-Path $d "Configuration.xml"
|
$cfgPath = Join-Path $d "Configuration.xml"
|
||||||
if (Test-Path $cfgPath) {
|
if (Test-Path $cfgPath) {
|
||||||
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
||||||
@@ -202,7 +210,12 @@ if (-not (Test-Path $CIPath)) {
|
|||||||
</CommandInterface>
|
</CommandInterface>
|
||||||
"@
|
"@
|
||||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||||
[System.IO.File]::WriteAllText($CIPath, $emptyCI, $utf8Bom)
|
# Файл СОЗДАЁМ — пишем канон выгрузки: CRLF, без перевода строки в конце.
|
||||||
|
# (Правка существующего файла, наоборот, наследует его стиль — это делает
|
||||||
|
# основной путь сохранения ниже.) Нормализация нужна потому, что here-string
|
||||||
|
# берёт переводы строк из самого .ps1, а он в репозитории хранится с LF.
|
||||||
|
$emptyCI = ($emptyCI -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
|
[System.IO.File]::WriteAllText($CIPath, $emptyCI.TrimEnd("`r", "`n"), $utf8Bom)
|
||||||
Write-Host "[INFO] Created new CommandInterface.xml: $CIPath"
|
Write-Host "[INFO] Created new CommandInterface.xml: $CIPath"
|
||||||
} else {
|
} else {
|
||||||
Write-Error "File not found: $CIPath (use -CreateIfMissing to create)"
|
Write-Error "File not found: $CIPath (use -CreateIfMissing to create)"
|
||||||
@@ -383,6 +396,10 @@ $script:typeNormMap = @{
|
|||||||
"ПланОбмена"="ExchangePlan"; "ЖурналДокументов"="DocumentJournal"
|
"ПланОбмена"="ExchangePlan"; "ЖурналДокументов"="DocumentJournal"
|
||||||
"ОбщийМодуль"="CommonModule"; "ОбщаяКоманда"="CommonCommand"
|
"ОбщийМодуль"="CommonModule"; "ОбщаяКоманда"="CommonCommand"
|
||||||
"ОбщаяФорма"="CommonForm"; "Подсистема"="Subsystem"
|
"ОбщаяФорма"="CommonForm"; "Подсистема"="Subsystem"
|
||||||
|
"РегистрРасчёта"="CalculationRegister"; "РегистрРасчета"="CalculationRegister"
|
||||||
|
"ПланВидовРасчёта"="ChartOfCalculationTypes"; "ПланВидовРасчета"="ChartOfCalculationTypes"
|
||||||
|
"Роль"="Role"; "ОбщийМакет"="CommonTemplate"; "ЭлементСтиля"="StyleItem"
|
||||||
|
"ОбщийРеквизит"="CommonAttribute"; "ГруппаКоманд"="CommandGroup"
|
||||||
# Russian plural
|
# Russian plural
|
||||||
"Справочники"="Catalog"; "Документы"="Document"; "Перечисления"="Enum"
|
"Справочники"="Catalog"; "Документы"="Document"; "Перечисления"="Enum"
|
||||||
"Константы"="Constant"; "Отчёты"="Report"; "Отчеты"="Report"; "Обработки"="DataProcessor"
|
"Константы"="Constant"; "Отчёты"="Report"; "Отчеты"="Report"; "Обработки"="DataProcessor"
|
||||||
@@ -392,6 +409,10 @@ $script:typeNormMap = @{
|
|||||||
"БизнесПроцессы"="BusinessProcess"; "Задачи"="Task"
|
"БизнесПроцессы"="BusinessProcess"; "Задачи"="Task"
|
||||||
"ПланыОбмена"="ExchangePlan"; "ЖурналыДокументов"="DocumentJournal"
|
"ПланыОбмена"="ExchangePlan"; "ЖурналыДокументов"="DocumentJournal"
|
||||||
"Подсистемы"="Subsystem"
|
"Подсистемы"="Subsystem"
|
||||||
|
"РегистрыРасчёта"="CalculationRegister"; "РегистрыРасчета"="CalculationRegister"
|
||||||
|
"ПланыВидовРасчёта"="ChartOfCalculationTypes"; "ПланыВидовРасчета"="ChartOfCalculationTypes"
|
||||||
|
"Роли"="Role"; "ОбщиеМакеты"="CommonTemplate"; "ЭлементыСтиля"="StyleItem"
|
||||||
|
"ОбщиеРеквизиты"="CommonAttribute"; "ГруппыКоманд"="CommandGroup"
|
||||||
}
|
}
|
||||||
|
|
||||||
function Normalize-CmdName([string]$name) {
|
function Normalize-CmdName([string]$name) {
|
||||||
@@ -674,8 +695,16 @@ $memStream.Close()
|
|||||||
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
|
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
|
||||||
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
|
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
|
||||||
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$text = [regex]::Replace($text, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
|
||||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $resolvedPath) -and ([System.IO.File]::ReadAllText($resolvedPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
[System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom)
|
[System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom)
|
||||||
Info "Saved: $resolvedPath"
|
Info "Saved: $resolvedPath"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# interface-edit v1.9 — Edit 1C CommandInterface.xml
|
# interface-edit v1.18 — Edit 1C CommandInterface.xml (+русские алиасы типов: формы с ё и без)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -10,6 +10,65 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
class CIDict(dict):
|
||||||
|
# Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные
|
||||||
|
# реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки
|
||||||
|
# сохраняется — от него зависит порядок эмиссии.
|
||||||
|
def _actual(self, key):
|
||||||
|
if not isinstance(key, str) or dict.__contains__(self, key):
|
||||||
|
return key
|
||||||
|
ci = self.__dict__.get('_ci')
|
||||||
|
if ci is None or len(ci) != len(self):
|
||||||
|
ci = {k.lower(): k for k in self if isinstance(k, str)}
|
||||||
|
self.__dict__['_ci'] = ci
|
||||||
|
return ci.get(key.lower(), key)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return dict.__getitem__(self, self._actual(key))
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return dict.__contains__(self, self._actual(key))
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return dict.get(self, self._actual(key), default)
|
||||||
|
|
||||||
|
def pop(self, key, *default):
|
||||||
|
return dict.pop(self, self._actual(key), *default)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
# запись по ключу, отличающемуся регистром, обновляет существующий, а не плодит дубль
|
||||||
|
dict.__setitem__(self, self._actual(key), value)
|
||||||
|
|
||||||
|
def ci_json(obj):
|
||||||
|
"""Рекурсивно оборачивает разобранный JSON: словари → CIDict, списки обходятся."""
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return CIDict((k, ci_json(v)) for k, v in obj.items())
|
||||||
|
if isinstance(obj, list):
|
||||||
|
return [ci_json(v) for v in obj]
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
||||||
@@ -189,6 +248,16 @@ def assert_edit_allowed(target_path, require):
|
|||||||
|
|
||||||
def detect_format_version(d):
|
def detect_format_version(d):
|
||||||
while d:
|
while d:
|
||||||
|
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||||
|
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||||
|
ext_path = d + ".xml"
|
||||||
|
if os.path.isfile(ext_path):
|
||||||
|
with open(ext_path, "r", encoding="utf-8-sig") as f:
|
||||||
|
ext_head = f.read(2000)
|
||||||
|
if re.search(r'<(ExternalDataProcessor|ExternalReport)[ >]', ext_head):
|
||||||
|
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', ext_head)
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
cfg_path = os.path.join(d, "Configuration.xml")
|
cfg_path = os.path.join(d, "Configuration.xml")
|
||||||
if os.path.isfile(cfg_path):
|
if os.path.isfile(cfg_path):
|
||||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||||
@@ -282,7 +351,7 @@ def import_ci_fragment(xml_string):
|
|||||||
def parse_value_list(val):
|
def parse_value_list(val):
|
||||||
val = val.strip()
|
val = val.strip()
|
||||||
if val.startswith("["):
|
if val.startswith("["):
|
||||||
arr = json.loads(val)
|
arr = ci_json(json.loads(val))
|
||||||
return [str(item) for item in arr]
|
return [str(item) for item in arr]
|
||||||
return [val]
|
return [val]
|
||||||
|
|
||||||
@@ -304,21 +373,22 @@ def _detect_xml_style(path):
|
|||||||
|
|
||||||
|
|
||||||
def _finalize_xml_bytes(xml_bytes, style):
|
def _finalize_xml_bytes(xml_bytes, style):
|
||||||
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None)."""
|
"""Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
|
||||||
enc_decl = style["enc"] if style else "utf-8"
|
выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
|
||||||
|
enc_decl = style["enc"] if style else "UTF-8"
|
||||||
xml_bytes = xml_bytes.replace(
|
xml_bytes = xml_bytes.replace(
|
||||||
b"<?xml version='1.0' encoding='UTF-8'?>",
|
b"<?xml version='1.0' encoding='UTF-8'?>",
|
||||||
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
||||||
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
||||||
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
||||||
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
||||||
# Финальный перенос — как в оригинале (новый файл → есть)
|
# Финальный перенос — как в оригинале (новый файл → нет, канон #57)
|
||||||
want_final_nl = style["final_nl"] if style else True
|
want_final_nl = style["final_nl"] if style else False
|
||||||
xml_bytes = xml_bytes.rstrip(b"\n")
|
xml_bytes = xml_bytes.rstrip(b"\n")
|
||||||
if want_final_nl:
|
if want_final_nl:
|
||||||
xml_bytes += b"\n"
|
xml_bytes += b"\n"
|
||||||
# EOL — как в оригинале (новый файл → LF, текущее поведение)
|
# EOL — как в оригинале (новый файл → CRLF, канон #57)
|
||||||
if style and style["crlf"]:
|
if (style["crlf"] if style else True):
|
||||||
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
||||||
return xml_bytes
|
return xml_bytes
|
||||||
|
|
||||||
@@ -357,6 +427,10 @@ TYPE_NORM_MAP = {
|
|||||||
'ПланОбмена': 'ExchangePlan', 'ЖурналДокументов': 'DocumentJournal',
|
'ПланОбмена': 'ExchangePlan', 'ЖурналДокументов': 'DocumentJournal',
|
||||||
'ОбщийМодуль': 'CommonModule', 'ОбщаяКоманда': 'CommonCommand',
|
'ОбщийМодуль': 'CommonModule', 'ОбщаяКоманда': 'CommonCommand',
|
||||||
'ОбщаяФорма': 'CommonForm', 'Подсистема': 'Subsystem',
|
'ОбщаяФорма': 'CommonForm', 'Подсистема': 'Subsystem',
|
||||||
|
'РегистрРасчёта': 'CalculationRegister', 'РегистрРасчета': 'CalculationRegister',
|
||||||
|
'ПланВидовРасчёта': 'ChartOfCalculationTypes', 'ПланВидовРасчета': 'ChartOfCalculationTypes',
|
||||||
|
'Роль': 'Role', 'ОбщийМакет': 'CommonTemplate', 'ЭлементСтиля': 'StyleItem',
|
||||||
|
'ОбщийРеквизит': 'CommonAttribute', 'ГруппаКоманд': 'CommandGroup',
|
||||||
# Russian plural
|
# Russian plural
|
||||||
'Справочники': 'Catalog', 'Документы': 'Document', 'Перечисления': 'Enum',
|
'Справочники': 'Catalog', 'Документы': 'Document', 'Перечисления': 'Enum',
|
||||||
'Константы': 'Constant', 'Отчёты': 'Report', 'Отчеты': 'Report', 'Обработки': 'DataProcessor',
|
'Константы': 'Constant', 'Отчёты': 'Report', 'Отчеты': 'Report', 'Обработки': 'DataProcessor',
|
||||||
@@ -366,6 +440,10 @@ TYPE_NORM_MAP = {
|
|||||||
'БизнесПроцессы': 'BusinessProcess', 'Задачи': 'Task',
|
'БизнесПроцессы': 'BusinessProcess', 'Задачи': 'Task',
|
||||||
'ПланыОбмена': 'ExchangePlan', 'ЖурналыДокументов': 'DocumentJournal',
|
'ПланыОбмена': 'ExchangePlan', 'ЖурналыДокументов': 'DocumentJournal',
|
||||||
'Подсистемы': 'Subsystem',
|
'Подсистемы': 'Subsystem',
|
||||||
|
'РегистрыРасчёта': 'CalculationRegister', 'РегистрыРасчета': 'CalculationRegister',
|
||||||
|
'ПланыВидовРасчёта': 'ChartOfCalculationTypes', 'ПланыВидовРасчета': 'ChartOfCalculationTypes',
|
||||||
|
'Роли': 'Role', 'ОбщиеМакеты': 'CommonTemplate', 'ЭлементыСтиля': 'StyleItem',
|
||||||
|
'ОбщиеРеквизиты': 'CommonAttribute', 'ГруппыКоманд': 'CommandGroup',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -401,7 +479,7 @@ def main():
|
|||||||
parser.add_argument("-Value", default=None)
|
parser.add_argument("-Value", default=None)
|
||||||
parser.add_argument("-CreateIfMissing", action="store_true")
|
parser.add_argument("-CreateIfMissing", action="store_true")
|
||||||
parser.add_argument("-NoValidate", action="store_true")
|
parser.add_argument("-NoValidate", action="store_true")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
# --- Mode validation ---
|
# --- Mode validation ---
|
||||||
if args.DefinitionFile and args.Operation:
|
if args.DefinitionFile and args.Operation:
|
||||||
@@ -438,7 +516,12 @@ def main():
|
|||||||
f'\tversion="{format_version}">\n'
|
f'\tversion="{format_version}">\n'
|
||||||
f'</CommandInterface>'
|
f'</CommandInterface>'
|
||||||
)
|
)
|
||||||
with open(ci_path, "w", encoding="utf-8-sig") as fh:
|
# Файл СОЗДАЁМ — пишем канон выгрузки: CRLF в разделителях. (Правка
|
||||||
|
# существующего файла, наоборот, наследует его стиль — это делает
|
||||||
|
# save_xml_bom через _detect_xml_style.) newline="" обязателен: без него
|
||||||
|
# текстовый режим дал бы CRLF на Windows и LF на macOS.
|
||||||
|
empty_ci = empty_ci.replace("\r\n", "\n").replace("\n", "\r\n").rstrip("\r\n")
|
||||||
|
with open(ci_path, "w", encoding="utf-8-sig", newline="") as fh:
|
||||||
fh.write(empty_ci)
|
fh.write(empty_ci)
|
||||||
print(f"[INFO] Created new CommandInterface.xml: {ci_path}")
|
print(f"[INFO] Created new CommandInterface.xml: {ci_path}")
|
||||||
else:
|
else:
|
||||||
@@ -564,7 +647,7 @@ def main():
|
|||||||
|
|
||||||
def do_place(json_val):
|
def do_place(json_val):
|
||||||
nonlocal add_count, modify_count
|
nonlocal add_count, modify_count
|
||||||
defn = json_val if isinstance(json_val, dict) else json.loads(json_val)
|
defn = ci_json(json_val if isinstance(json_val, dict) else json.loads(json_val))
|
||||||
cmd_name = normalize_cmd_name(str(defn["command"]))
|
cmd_name = normalize_cmd_name(str(defn["command"]))
|
||||||
group_name = str(defn["group"])
|
group_name = str(defn["group"])
|
||||||
if not cmd_name or not group_name:
|
if not cmd_name or not group_name:
|
||||||
@@ -592,7 +675,7 @@ def main():
|
|||||||
|
|
||||||
def do_order(json_val):
|
def do_order(json_val):
|
||||||
nonlocal add_count, remove_count
|
nonlocal add_count, remove_count
|
||||||
defn = json_val if isinstance(json_val, dict) else json.loads(json_val)
|
defn = ci_json(json_val if isinstance(json_val, dict) else json.loads(json_val))
|
||||||
group_name = str(defn["group"])
|
group_name = str(defn["group"])
|
||||||
commands = [normalize_cmd_name(str(c)) for c in defn["commands"]]
|
commands = [normalize_cmd_name(str(c)) for c in defn["commands"]]
|
||||||
if not group_name or not commands:
|
if not group_name or not commands:
|
||||||
@@ -626,7 +709,7 @@ def main():
|
|||||||
|
|
||||||
def do_subsystem_order(json_val):
|
def do_subsystem_order(json_val):
|
||||||
nonlocal add_count, remove_count
|
nonlocal add_count, remove_count
|
||||||
parsed = json_val if isinstance(json_val, list) else json.loads(json_val)
|
parsed = ci_json(json_val if isinstance(json_val, list) else json.loads(json_val))
|
||||||
subsystems = [str(s) for s in parsed]
|
subsystems = [str(s) for s in parsed]
|
||||||
if not subsystems:
|
if not subsystems:
|
||||||
print("subsystem-order requires array of subsystem paths", file=sys.stderr)
|
print("subsystem-order requires array of subsystem paths", file=sys.stderr)
|
||||||
@@ -651,7 +734,7 @@ def main():
|
|||||||
|
|
||||||
def do_group_order(json_val):
|
def do_group_order(json_val):
|
||||||
nonlocal add_count, remove_count
|
nonlocal add_count, remove_count
|
||||||
parsed = json_val if isinstance(json_val, list) else json.loads(json_val)
|
parsed = ci_json(json_val if isinstance(json_val, list) else json.loads(json_val))
|
||||||
groups = [str(g) for g in parsed]
|
groups = [str(g) for g in parsed]
|
||||||
if not groups:
|
if not groups:
|
||||||
print("group-order requires array of group names", file=sys.stderr)
|
print("group-order requires array of group names", file=sys.stderr)
|
||||||
@@ -681,7 +764,7 @@ def main():
|
|||||||
if not os.path.isabs(def_file):
|
if not os.path.isabs(def_file):
|
||||||
def_file = os.path.join(os.getcwd(), def_file)
|
def_file = os.path.join(os.getcwd(), def_file)
|
||||||
with open(def_file, "r", encoding="utf-8-sig") as fh:
|
with open(def_file, "r", encoding="utf-8-sig") as fh:
|
||||||
ops = json.loads(fh.read())
|
ops = ci_json(json.loads(fh.read()))
|
||||||
if isinstance(ops, list):
|
if isinstance(ops, list):
|
||||||
operations = ops
|
operations = ops
|
||||||
else:
|
else:
|
||||||
@@ -691,19 +774,21 @@ def main():
|
|||||||
|
|
||||||
for op in operations:
|
for op in operations:
|
||||||
op_name = op.get("operation", args.Operation or "")
|
op_name = op.get("operation", args.Operation or "")
|
||||||
|
# PS сравнивает имя операции через switch, а он регистронезависим.
|
||||||
|
op_key = str(op_name).lower()
|
||||||
op_value = op.get("value", args.Value or "")
|
op_value = op.get("value", args.Value or "")
|
||||||
|
|
||||||
if op_name == "hide":
|
if op_key == "hide":
|
||||||
do_hide(parse_value_list(op_value))
|
do_hide(parse_value_list(op_value))
|
||||||
elif op_name == "show":
|
elif op_key == "show":
|
||||||
do_show(parse_value_list(op_value))
|
do_show(parse_value_list(op_value))
|
||||||
elif op_name == "place":
|
elif op_key == "place":
|
||||||
do_place(op_value)
|
do_place(op_value)
|
||||||
elif op_name == "order":
|
elif op_key == "order":
|
||||||
do_order(op_value)
|
do_order(op_value)
|
||||||
elif op_name == "subsystem-order":
|
elif op_key == "subsystem-order":
|
||||||
do_subsystem_order(op_value)
|
do_subsystem_order(op_value)
|
||||||
elif op_name == "group-order":
|
elif op_key == "group-order":
|
||||||
do_group_order(op_value)
|
do_group_order(op_value)
|
||||||
else:
|
else:
|
||||||
print(f"Unknown operation: {op_name}", file=sys.stderr)
|
print(f"Unknown operation: {op_name}", file=sys.stderr)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# interface-validate v1.1 — Validate 1C CommandInterface.xml structure
|
# interface-validate v1.3 — Validate 1C CommandInterface.xml structure (+Report-*: общий эталон вывода валидаторов)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)][Alias('Path')][string]$CIPath,
|
[Parameter(Mandatory)][Alias('Path')][string]$CIPath,
|
||||||
@@ -51,16 +51,21 @@ $script:output = New-Object System.Text.StringBuilder 8192
|
|||||||
$script:allCommandNames = @()
|
$script:allCommandNames = @()
|
||||||
|
|
||||||
function Out-Line([string]$msg) { $script:output.AppendLine($msg) | Out-Null }
|
function Out-Line([string]$msg) { $script:output.AppendLine($msg) | Out-Null }
|
||||||
function Report-OK([string]$msg) {
|
function Report-OK {
|
||||||
|
param([string]$msg)
|
||||||
$script:okCount++
|
$script:okCount++
|
||||||
if ($Detailed) { Out-Line "[OK] $msg" }
|
if ($Detailed) { Out-Line "[OK] $msg" }
|
||||||
}
|
}
|
||||||
function Report-Error([string]$msg) {
|
function Report-Error {
|
||||||
|
param([string]$msg)
|
||||||
$script:errors++
|
$script:errors++
|
||||||
Out-Line "[ERROR] $msg"
|
Out-Line "[ERROR] $msg"
|
||||||
if ($script:errors -ge $MaxErrors) { $script:stopped = $true }
|
if ($script:errors -ge $MaxErrors) {
|
||||||
|
$script:stopped = $true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function Report-Warn([string]$msg) {
|
function Report-Warn {
|
||||||
|
param([string]$msg)
|
||||||
$script:warnings++
|
$script:warnings++
|
||||||
Out-Line "[WARN] $msg"
|
Out-Line "[WARN] $msg"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,32 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# interface-validate v1.1 — Validate 1C CommandInterface.xml structure
|
# interface-validate v1.3 — Validate 1C CommandInterface.xml structure (+Report-*: общий эталон вывода валидаторов)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
"""Validates CommandInterface.xml sections, command references, order, duplicates."""
|
"""Validates CommandInterface.xml sections, command references, order, duplicates."""
|
||||||
import sys, os, argparse, re
|
import sys, os, argparse, re
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
NS_CI = 'http://v8.1c.ru/8.3/xcf/extrnprops'
|
NS_CI = 'http://v8.1c.ru/8.3/xcf/extrnprops'
|
||||||
NS_XR = 'http://v8.1c.ru/8.3/xcf/readable'
|
NS_XR = 'http://v8.1c.ru/8.3/xcf/readable'
|
||||||
NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
|
NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
|
||||||
@@ -83,7 +105,7 @@ def main():
|
|||||||
parser.add_argument('-Detailed', action='store_true')
|
parser.add_argument('-Detailed', action='store_true')
|
||||||
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
|
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
|
||||||
parser.add_argument('-OutFile', dest='OutFile', default='')
|
parser.add_argument('-OutFile', dest='OutFile', default='')
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
ci_path = args.CIPath
|
ci_path = args.CIPath
|
||||||
detailed = args.Detailed
|
detailed = args.Detailed
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# meta-compile v1.76 — Compile 1C metadata object from JSON
|
# meta-compile v1.94 — Compile 1C metadata object from JSON
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -161,7 +161,10 @@ if ($def -is [array] -or ($null -ne $def -and $def.GetType().BaseType.Name -eq '
|
|||||||
$idx = 0
|
$idx = 0
|
||||||
foreach ($item in $def) {
|
foreach ($item in $def) {
|
||||||
$idx++
|
$idx++
|
||||||
$tmpJson = Join-Path ([System.IO.Path]::GetTempPath()) "meta-compile-batch-$idx.json"
|
# Имя с GUID, а не "batch-$idx": фиксированное имя в общем %TEMP% сталкивало
|
||||||
|
# два параллельных запуска навыка на одной машине — Set-Content падал с
|
||||||
|
# «file is being used by another process». py-порт уже брал mkstemp.
|
||||||
|
$tmpJson = Join-Path ([System.IO.Path]::GetTempPath()) "meta-compile-batch-$idx-$([guid]::NewGuid().ToString('N')).json"
|
||||||
try {
|
try {
|
||||||
$item | ConvertTo-Json -Depth 20 | Set-Content -Encoding UTF8 $tmpJson
|
$item | ConvertTo-Json -Depth 20 | Set-Content -Encoding UTF8 $tmpJson
|
||||||
$proc = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -File `"$PSCommandPath`" -JsonPath `"$tmpJson`" -OutputDir `"$OutputDir`"" -NoNewWindow -Wait -PassThru
|
$proc = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -File `"$PSCommandPath`" -JsonPath `"$tmpJson`" -OutputDir `"$OutputDir`"" -NoNewWindow -Wait -PassThru
|
||||||
@@ -405,6 +408,11 @@ $validTypes = @("Catalog","Document","Enum","Constant","InformationRegister","Ac
|
|||||||
"Sequence","FilterCriterion","DocumentNumerator","SettingsStorage","CommonForm",
|
"Sequence","FilterCriterion","DocumentNumerator","SettingsStorage","CommonForm",
|
||||||
"SessionParameter","CommonCommand","CommandGroup","CommonAttribute","FunctionalOptionsParameter","WSReference",
|
"SessionParameter","CommonCommand","CommandGroup","CommonAttribute","FunctionalOptionsParameter","WSReference",
|
||||||
"CommonPicture","CommonTemplate")
|
"CommonPicture","CommonTemplate")
|
||||||
|
# -notin регистронезависим, поэтому "catalog" проходил проверку и дальше шёл в ИМЯ ТЕГА и в
|
||||||
|
# Configuration.xml как есть — выгрузка получалась с <catalog>, которую платформа не принимает.
|
||||||
|
# Прощаем регистр, но приводим к канону списка.
|
||||||
|
$canonType = $validTypes | Where-Object { $_ -eq $objType } | Select-Object -First 1
|
||||||
|
if ($canonType) { $objType = $canonType }
|
||||||
if ($objType -notin $validTypes) {
|
if ($objType -notin $validTypes) {
|
||||||
Write-Error "Unsupported type: $objType. Valid: $($validTypes -join ', ')"
|
Write-Error "Unsupported type: $objType. Valid: $($validTypes -join ', ')"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -544,6 +552,10 @@ $script:typeNamespaceMap = @{
|
|||||||
}
|
}
|
||||||
# Типы current-config пространства (cfg:, объявлено в корне): объектные (CatalogObject.X/DataProcessorObject.X/…)
|
# Типы current-config пространства (cfg:, объявлено в корне): объектные (CatalogObject.X/DataProcessorObject.X/…)
|
||||||
# и голые (ConstantsSet/ReportBuilder). Ссылочные (*Ref.X/DefinedType.X) идут ОТДЕЛЬНО через локальный d5p1 (§memory).
|
# и голые (ConstantsSet/ReportBuilder). Ссылочные (*Ref.X/DefinedType.X) идут ОТДЕЛЬНО через локальный d5p1 (§memory).
|
||||||
|
# Префикс current-config для ссылочных типов. 'cfg' — для файлов, чья шапка его объявляет
|
||||||
|
# (объектный XML, Ext/Form.xml общей формы). $null на время сборки Ext/Predefined.xml, чья
|
||||||
|
# шапка его НЕ объявляет: там и платформа уходит на локальное объявление.
|
||||||
|
$script:cfgPrefix = 'cfg'
|
||||||
$script:cfgBareTypes = @("ConstantsSet", "ReportBuilder", "FilterCriterion")
|
$script:cfgBareTypes = @("ConstantsSet", "ReportBuilder", "FilterCriterion")
|
||||||
$script:cfgObjectKinds = @("Catalog","Document","Enum","ChartOfAccounts","ChartOfCharacteristicTypes",
|
$script:cfgObjectKinds = @("Catalog","Document","Enum","ChartOfAccounts","ChartOfCharacteristicTypes",
|
||||||
"ChartOfCalculationTypes","ExchangePlan","BusinessProcess","Task","InformationRegister","AccumulationRegister",
|
"ChartOfCalculationTypes","ExchangePlan","BusinessProcess","Task","InformationRegister","AccumulationRegister",
|
||||||
@@ -574,7 +586,20 @@ function Resolve-TypeStr {
|
|||||||
param([string]$typeStr)
|
param([string]$typeStr)
|
||||||
if (-not $typeStr) { return $typeStr }
|
if (-not $typeStr) { return $typeStr }
|
||||||
|
|
||||||
# Check for parameterized types: Number(15,2), Строка(100), etc.
|
# Прощающий ввод: ведущий префикс приходит копипастой из выгрузки. Без срезания он ломает
|
||||||
|
# поиск в словаре — русское имя типа остаётся непереведённым, и платформа отвечает
|
||||||
|
# «Неизвестное имя типа». cfg: снимаем всегда — он однозначно означает текущую конфигурацию.
|
||||||
|
# Сгенерированный dNpM: (в корпусе на этом URI встречаются d4p1, d5p1, d6p1 — имя префикса
|
||||||
|
# платформа выдаёт по порядку объявления) снимаем ТОЛЬКО у ссылочных типов, с точкой:
|
||||||
|
# сам по себе префикс многозначен — в формах d5p1:Chart, d5p1:TextDocument,
|
||||||
|
# d5p1:GeographicalSchema адресуют чужие пространства имён, и там он часть значения.
|
||||||
|
if ($typeStr.StartsWith('cfg:')) {
|
||||||
|
$typeStr = $typeStr.Substring(4)
|
||||||
|
} elseif ($typeStr.Contains('.') -and $typeStr -match '^d\d+p\d+:') {
|
||||||
|
$typeStr = $typeStr.Substring($typeStr.IndexOf(':') + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Параметризованные типы: Number(15,2), Строка(100)
|
||||||
if ($typeStr -match '^([^(]+)\((.+)\)$') {
|
if ($typeStr -match '^([^(]+)\((.+)\)$') {
|
||||||
$baseName = $Matches[1].Trim()
|
$baseName = $Matches[1].Trim()
|
||||||
$params = $Matches[2]
|
$params = $Matches[2]
|
||||||
@@ -583,7 +608,7 @@ function Resolve-TypeStr {
|
|||||||
return $typeStr
|
return $typeStr
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check for reference types: СправочникСсылка.Организации → CatalogRef.Организации
|
# Ссылочные типы: СправочникСсылка.Организации → CatalogRef.Организации
|
||||||
if ($typeStr.Contains('.')) {
|
if ($typeStr.Contains('.')) {
|
||||||
$dotIdx = $typeStr.IndexOf('.')
|
$dotIdx = $typeStr.IndexOf('.')
|
||||||
$prefix = $typeStr.Substring(0, $dotIdx)
|
$prefix = $typeStr.Substring(0, $dotIdx)
|
||||||
@@ -593,10 +618,9 @@ function Resolve-TypeStr {
|
|||||||
return $typeStr
|
return $typeStr
|
||||||
}
|
}
|
||||||
|
|
||||||
# Simple name lookup
|
# Простое имя
|
||||||
$resolved = $script:typeSynonyms[$typeStr.ToLower()]
|
$resolved = $script:typeSynonyms[$typeStr.ToLower()]
|
||||||
if ($resolved) { return $resolved }
|
if ($resolved) { return $resolved }
|
||||||
|
|
||||||
return $typeStr
|
return $typeStr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,10 +629,44 @@ function Emit-TypeContent {
|
|||||||
if (-not $typeStr) { return }
|
if (-not $typeStr) { return }
|
||||||
|
|
||||||
# Composite type: "Type1 + Type2 + Type3"
|
# Composite type: "Type1 + Type2 + Type3"
|
||||||
|
# Платформа пишет сначала ВСЕ <v8:Type>/<v8:TypeSet>, и только потом блоки
|
||||||
|
# квалификаторов — а рекурсия ниже печатала бы каждую часть целиком (тип вместе со
|
||||||
|
# своими квалификаторами). На одиночном типе оба порядка совпадают, поэтому
|
||||||
|
# расхождение вылезало только на составном.
|
||||||
|
# Порядок самих блоков квалификаторов тоже канонический и НЕ зеркалит порядок типов:
|
||||||
|
# Number, String, Date (корпус acc+erp, контрпримеров нет — при типах
|
||||||
|
# boolean,string,dateTime,decimal квалификаторы идут Number,String,Date).
|
||||||
|
# Порядок типов при этом сохраняем как в DSL: он и есть порядок источника.
|
||||||
if ($typeStr.Contains(' + ')) {
|
if ($typeStr.Contains(' + ')) {
|
||||||
$parts = $typeStr -split '\s*\+\s*'
|
$parts = $typeStr -split '\s*\+\s*'
|
||||||
|
$typeLines = New-Object System.Collections.ArrayList
|
||||||
|
$qualBlocks = @{} # 'Number'|'String'|'Date' → строки блока
|
||||||
foreach ($part in $parts) {
|
foreach ($part in $parts) {
|
||||||
|
# X пишет в StringBuilder, поэтому «перехват» — это запомнить длину, вызвать
|
||||||
|
# эмиттер и откатить добавленное. В py-порту X добавляет в список, и там тот
|
||||||
|
# же алгоритм выражен срезом — различие рантаймов, не логики.
|
||||||
|
$before = $script:xml.Length
|
||||||
Emit-TypeContent $indent $part.Trim()
|
Emit-TypeContent $indent $part.Trim()
|
||||||
|
$chunk = $script:xml.ToString($before, $script:xml.Length - $before)
|
||||||
|
[void]$script:xml.Remove($before, $script:xml.Length - $before)
|
||||||
|
$curQual = $null
|
||||||
|
foreach ($line in ($chunk -split "`r?`n")) {
|
||||||
|
if ($line -eq '') { continue }
|
||||||
|
if ($line -match '<v8:(String|Number|Date)Qualifiers>') {
|
||||||
|
$curQual = $Matches[1]
|
||||||
|
$qualBlocks[$curQual] = New-Object System.Collections.ArrayList
|
||||||
|
}
|
||||||
|
if ($curQual) {
|
||||||
|
[void]$qualBlocks[$curQual].Add($line)
|
||||||
|
if ($line -match '</v8:(String|Number|Date)Qualifiers>') { $curQual = $null }
|
||||||
|
} else {
|
||||||
|
[void]$typeLines.Add($line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($line in $typeLines) { X $line }
|
||||||
|
foreach ($q in @('Number', 'String', 'Date')) {
|
||||||
|
if ($qualBlocks.ContainsKey($q)) { foreach ($line in $qualBlocks[$q]) { X $line } }
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -723,9 +781,22 @@ function Emit-TypeContent {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
# Reference types — use local xmlns declaration for 1C compatibility
|
# Ссылочные типы — корневой cfg:, как пишет платформа. Раньше здесь объявлялся
|
||||||
|
# ЛОКАЛЬНЫЙ xmlns:d5p1 на тот же URI, что уже объявлен в шапке ($script:xmlnsDecl):
|
||||||
|
# формально эквивалентно (значим URI, не префикс) и платформой принималось, но
|
||||||
|
# первый же цикл «загрузить в базу → выгрузить» переписывал каждый ссылочный тип
|
||||||
|
# в cfg: — то есть давал diff-шум на ровном месте. Форма пришла из СКД, где cfg:
|
||||||
|
# действительно не работает; в метаданных такого ограничения нет.
|
||||||
|
# NB: локальная xmlns остаётся законной для ЧУЖИХ пространств — см. $script:typeNamespaceMap.
|
||||||
|
# $script:cfgPrefix = $null означает «пишем файл, корень которого cfg НЕ объявляет»
|
||||||
|
# (Ext/Predefined.xml — его шапка это predef/v8/xr/xs/xsi). Там платформа сама уходит
|
||||||
|
# на локальную форму: в корпусе `<v8:Type xmlns:d6p1="…current-config">d6p1:CatalogRef.Валюты`.
|
||||||
if ($typeStr -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|BusinessProcessRoutePointRef|TaskRef)\.(.+)$') {
|
if ($typeStr -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|BusinessProcessRoutePointRef|TaskRef)\.(.+)$') {
|
||||||
X "$indent<v8:Type xmlns:d5p1=`"http://v8.1c.ru/8.1/data/enterprise/current-config`">d5p1:$typeStr</v8:Type>"
|
if ($script:cfgPrefix) {
|
||||||
|
X "$indent<v8:Type>$($script:cfgPrefix):$typeStr</v8:Type>"
|
||||||
|
} else {
|
||||||
|
X "$indent<v8:Type xmlns:d5p1=`"http://v8.1c.ru/8.1/data/enterprise/current-config`">d5p1:$typeStr</v8:Type>"
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1245,15 +1316,21 @@ $script:standardAttributesByType = @{
|
|||||||
"Document" = @("Posted","Ref","DeletionMark","Date","Number")
|
"Document" = @("Posted","Ref","DeletionMark","Date","Number")
|
||||||
"Enum" = @("Order","Ref")
|
"Enum" = @("Order","Ref")
|
||||||
"InformationRegister" = @("Active","LineNumber","Recorder","Period")
|
"InformationRegister" = @("Active","LineNumber","Recorder","Period")
|
||||||
"AccumulationRegister" = @("Active","LineNumber","Recorder","Period")
|
"AccumulationRegister" = @("RecordType","Active","LineNumber","Recorder","Period")
|
||||||
"AccountingRegister" = @("Active","Period","Recorder","LineNumber","Account")
|
"AccountingRegister" = @("PeriodAdjustment","Account","RecordType","Active","LineNumber","Recorder","Period")
|
||||||
"CalculationRegister" = @("Active","Recorder","LineNumber","RegistrationPeriod","CalculationType","ReversingEntry")
|
"CalculationRegister" = @("RegistrationPeriod","ReversingEntry","Active","EndOfBasePeriod","BegOfBasePeriod","EndOfActionPeriod","BegOfActionPeriod","ActionPeriod","CalculationType","LineNumber","Recorder")
|
||||||
"ChartOfAccounts" = @("PredefinedDataName","Order","OffBalance","Type","Description","Code","Parent","Predefined","DeletionMark","Ref")
|
"ChartOfAccounts" = @("PredefinedDataName","Order","OffBalance","Type","Description","Code","Parent","Predefined","DeletionMark","Ref")
|
||||||
"ChartOfCharacteristicTypes" = @("PredefinedDataName","Predefined","Ref","DeletionMark","Description","Code","Parent","ValueType")
|
"ChartOfCharacteristicTypes" = @("PredefinedDataName","ValueType","Description","Code","IsFolder","Parent","Predefined","DeletionMark","Ref")
|
||||||
"ChartOfCalculationTypes" = @("PredefinedDataName","Predefined","Ref","DeletionMark","ActionPeriodIsBasic","Description","Code")
|
"ChartOfCalculationTypes" = @("PredefinedDataName","Predefined","Ref","DeletionMark","ActionPeriodIsBasic","Description","Code")
|
||||||
"BusinessProcess" = @("Ref","DeletionMark","Date","Number","Started","Completed","HeadTask")
|
"BusinessProcess" = @("Started","HeadTask","Completed","Ref","DeletionMark","Date","Number")
|
||||||
"Task" = @("Ref","DeletionMark","Date","Number","Executed","Description","RoutePoint","BusinessProcess")
|
"Task" = @("Executed","Description","RoutePoint","BusinessProcess","Ref","DeletionMark","Date","Number")
|
||||||
"ExchangePlan" = @("Ref","DeletionMark","Code","Description","ThisNode","SentNo","ReceivedNo")
|
# Порядок в каждом списке — канон выгрузки, снят с корпуса acc+erp (внутри типа разброса нет).
|
||||||
|
# Условные члены перечислены в $script:stdAttrConditions — позицию они берут отсюда, а
|
||||||
|
# присутствие определяется свойствами объекта.
|
||||||
|
# У ПВХ IsFolder входит в фикс-список: он есть у всех 23 объектов корпуса с этим блоком.
|
||||||
|
# У регистра расчёта список безусловен: реквизиты периода действия и базового периода
|
||||||
|
# платформа пишет при любых ActionPeriod/BasePeriod/Periodicity (синтетика, все 4 комбинации).
|
||||||
|
"ExchangePlan" = @("ThisNode","ReceivedNo","SentNo","Ref","DeletionMark","Description","Code")
|
||||||
"DocumentJournal" = @("Type","Ref","Date","Posted","DeletionMark","Number")
|
"DocumentJournal" = @("Type","Ref","Date","Posted","DeletionMark","Number")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1372,6 +1449,55 @@ function Emit-StandardAttribute {
|
|||||||
# - stdAttrProfile[тип]: профиль материализованного блока (пусто = schema-дефолт), поверх — DSL-override.
|
# - stdAttrProfile[тип]: профиль материализованного блока (пусто = schema-дефолт), поверх — DSL-override.
|
||||||
# Миграция типа = добавить его в stdAttrConditionalTypes + stdAttrProfile и переснять снэпшоты; КОД НЕ ТРОГАЕМ.
|
# Миграция типа = добавить его в stdAttrConditionalTypes + stdAttrProfile и переснять снэпшоты; КОД НЕ ТРОГАЕМ.
|
||||||
$script:stdAttrConditionalTypes = @('Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document')
|
$script:stdAttrConditionalTypes = @('Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document')
|
||||||
|
|
||||||
|
# Условные члены списка типа: позиция берётся из $script:standardAttributesByType, а присутствие —
|
||||||
|
# из свойств самого объекта, как у платформы. Предикат ОБЯЗАН принимать определение параметром:
|
||||||
|
# scriptblock не видит $def вызывающей функции, и отказ был бы молчаливым.
|
||||||
|
$script:stdAttrConditions = @{
|
||||||
|
"AccountingRegister" = @{
|
||||||
|
"PeriodAdjustment" = { param($d) $v = 0; if ($null -ne $d.periodAdjustmentLength) { $v = [int]"$($d.periodAdjustmentLength)" }; $v -gt 0 }
|
||||||
|
"RecordType" = { param($d) -not ($d.correspondence -eq $true) }
|
||||||
|
}
|
||||||
|
"AccumulationRegister" = @{
|
||||||
|
"RecordType" = { param($d) $raw = if ($d.registerType) { "$($d.registerType)" } else { "Balance" }; (Normalize-EnumValue "RegisterType" $raw) -eq "Balance" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Хвостовая группа: реквизиты, которых нет в списке типа и которые идут ПОСЛЕ него.
|
||||||
|
# У бухрегистра это пары субконто. Именами их не перечислить: их количество задаётся
|
||||||
|
# свойством MaxExtDimensionCount плана счетов, а не константой (в корпусе везде 3, но
|
||||||
|
# это однородность выборки, а не правило). Поэтому — шаблон, а не список.
|
||||||
|
$script:stdAttrTailPattern = @{
|
||||||
|
"AccountingRegister" = '^ExtDimension(Type)?\d+$'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Состав хвоста задаёт не DSL, а объект, на который регистр ссылается: пар субконто столько,
|
||||||
|
# сколько у плана счетов MaxExtDimensionCount. Читаем его из выгрузки — как версию формата из
|
||||||
|
# Configuration.xml, — чтобы регистр, описанный неполным DSL, совпал с тем, что материализует
|
||||||
|
# платформа. План не найден → хвост не генерируем и говорим об этом в выводе.
|
||||||
|
$script:stdAttrTailHint = $null
|
||||||
|
$script:stdAttrTailDerived = @{
|
||||||
|
"AccountingRegister" = {
|
||||||
|
param($d, $objectName, $outDir)
|
||||||
|
$ref = "$($d.chartOfAccounts)"
|
||||||
|
if (-not $ref) { return @() }
|
||||||
|
$chartName = $ref -replace '^.*\.', '' # ссылка вида ChartOfAccounts.X (имя объекта точек не содержит)
|
||||||
|
$path = Join-Path (Join-Path $outDir "ChartsOfAccounts") "$chartName.xml"
|
||||||
|
if (-not (Test-Path -LiteralPath $path)) {
|
||||||
|
$script:stdAttrTailHint = "ChartOfAccounts '$chartName' not found in dump — ExtDimension pairs not generated (platform will add them on load)"
|
||||||
|
return @()
|
||||||
|
}
|
||||||
|
$n = 0
|
||||||
|
if ([System.IO.File]::ReadAllText($path) -match '<MaxExtDimensionCount>(\d+)</MaxExtDimensionCount>') { $n = [int]$matches[1] }
|
||||||
|
$out = @()
|
||||||
|
for ($i = 1; $i -le $n; $i++) {
|
||||||
|
# ExtDimensionN связан с Account через LinkByType (LinkItem = номер), ExtDimensionTypeN — нет.
|
||||||
|
$out += @{ name = "ExtDimension$i"; ov = @{ LinkByType = @{ dataPath = "AccountingRegister.$objectName.StandardAttribute.Account"; linkItem = $i } } }
|
||||||
|
$out += @{ name = "ExtDimensionType$i"; ov = @{} }
|
||||||
|
}
|
||||||
|
return $out
|
||||||
|
}
|
||||||
|
}
|
||||||
function Emit-StandardAttributes {
|
function Emit-StandardAttributes {
|
||||||
param([string]$indent, [string]$objectType)
|
param([string]$indent, [string]$objectType)
|
||||||
$attrs = $script:standardAttributesByType[$objectType]
|
$attrs = $script:standardAttributesByType[$objectType]
|
||||||
@@ -1381,14 +1507,45 @@ function Emit-StandardAttributes {
|
|||||||
if ($conditional -and $null -eq $sa) { return } # условный тип без кастомизации → блока нет
|
if ($conditional -and $null -eq $sa) { return } # условный тип без кастомизации → блока нет
|
||||||
if ($sa -is [string] -and $sa -eq '') { return } # opt-out `standardAttributes:""` (дом-конвенция суппресса, ~5% регистров опускают all-default блок — правило не выводимо)
|
if ($sa -is [string] -and $sa -eq '') { return } # opt-out `standardAttributes:""` (дом-конвенция суппресса, ~5% регистров опускают all-default блок — правило не выводимо)
|
||||||
$profile = $script:stdAttrProfile[$objectType]; if (-not $profile) { $profile = @{} }
|
$profile = $script:stdAttrProfile[$objectType]; if (-not $profile) { $profile = @{} }
|
||||||
# Доп. (опциональные) стандартные реквизиты вне фикс-списка типа — напр. ExchangeDate у части ПланОбмена
|
# Список типа задаёт ПОРЯДОК всех известных стандартных реквизитов, включая условные:
|
||||||
# (легаси, присутствие не выводится из свойств). Эмитим по факту наличия ключа в DSL, ПЕРЕД фикс-списком (их позиция).
|
# их позиция бывает и до, и после обязательных (у бухрегистра PeriodAdjustment идёт
|
||||||
$extra = @()
|
# перед Account, RecordType — после, а ExtDimension1..3/ExtDimensionType1..3 — после Period),
|
||||||
if ($sa) { foreach ($k in $sa.PSObject.Properties.Name) { if ($attrs -notcontains $k) { $extra += $k } } }
|
# поэтому «условные скопом вперёд» не выражает канон.
|
||||||
|
$cond = $script:stdAttrConditions[$objectType]
|
||||||
|
# Ключи, которых нет в списке типа ВООБЩЕ. По умолчанию их позиция — ПЕРЕД списком
|
||||||
|
# (легаси вроде ExchangeDate у части планов обмена). Подходящие под хвостовой шаблон
|
||||||
|
# типа идут ПОСЛЕ, в порядке номера, а внутри номера — сначала ExtDimensionN, затем
|
||||||
|
# ExtDimensionTypeN (порядок платформы).
|
||||||
|
$tailRe = $script:stdAttrTailPattern[$objectType]
|
||||||
|
$extra = @(); $tail = @()
|
||||||
|
if ($sa) {
|
||||||
|
foreach ($k in $sa.PSObject.Properties.Name) {
|
||||||
|
if ($attrs -contains $k) { continue }
|
||||||
|
if ($tailRe -and $k -match $tailRe) { $tail += $k } else { $extra += $k }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Хвост, выведенный из связанного объекта: дополняет DSL, а не заменяет его — лишнее из DSL
|
||||||
|
# остаётся (прощаем), недостающее добавляется вместе со своими значениями по умолчанию.
|
||||||
|
$derivedOv = @{}
|
||||||
|
$gen = $script:stdAttrTailDerived[$objectType]
|
||||||
|
if ($gen) {
|
||||||
|
foreach ($e in @(& $gen $def $objName $OutputDir)) {
|
||||||
|
$derivedOv[$e.name] = $e.ov
|
||||||
|
if ($tail -notcontains $e.name) { $tail += $e.name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$tail = @($tail | Sort-Object @{e={[int]([regex]::Match($_, '\d+').Value)}}, @{e={ if ($_ -match 'Type\d+$') { 1 } else { 0 } }})
|
||||||
X "$indent<StandardAttributes>"
|
X "$indent<StandardAttributes>"
|
||||||
foreach ($a in ($extra + $attrs)) {
|
foreach ($a in ($extra + $attrs + $tail)) {
|
||||||
|
# Условный реквизит: эмитим, если так велят свойства объекта ЛИБО если ключ есть в DSL.
|
||||||
|
# Дизъюнкция страхует роундтрип — декомпилятор перечисляет все имена блока.
|
||||||
|
if ($cond -and $cond.ContainsKey($a)) {
|
||||||
|
$present = ($sa -and $sa.PSObject.Properties[$a]) -or (& $cond[$a] $def)
|
||||||
|
if (-not $present) { continue }
|
||||||
|
}
|
||||||
$ov = @{}
|
$ov = @{}
|
||||||
if ($profile.ContainsKey($a)) { foreach ($k in $profile[$a].Keys) { $ov[$k] = $profile[$a][$k] } }
|
if ($profile.ContainsKey($a)) { foreach ($k in $profile[$a].Keys) { $ov[$k] = $profile[$a][$k] } }
|
||||||
|
if ($derivedOv.ContainsKey($a)) { foreach ($k in $derivedOv[$a].Keys) { $ov[$k] = $derivedOv[$a][$k] } }
|
||||||
if ($sa) { # DSL-override применяем всегда при наличии ключа (для не-условных типов тоже, напр. ExchangePlan)
|
if ($sa) { # DSL-override применяем всегда при наличии ключа (для не-условных типов тоже, напр. ExchangePlan)
|
||||||
$d = $sa.$a
|
$d = $sa.$a
|
||||||
if ($d) {
|
if ($d) {
|
||||||
@@ -1946,9 +2103,12 @@ function Emit-Attribute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Use — only for catalog top-level attributes
|
# Use — у реквизитов справочника и ПВХ. Позиция РАЗНАЯ: справочник пишет Use ПЕРЕД
|
||||||
|
# Indexing, ПВХ — ПОСЛЕ него (корпус acc+erp: Catalog `Use,Indexing,FullTextSearch`,
|
||||||
|
# ПВХ `Indexing,Use,FullTextSearch,DataHistory`). Отсюда отдельный контекст "cct":
|
||||||
|
# структурно реквизит ПВХ совпадает со справочником, расходится только этим порядком.
|
||||||
|
$use = if ($parsed.use) { $parsed.use } else { "ForItem" }
|
||||||
if ($context -eq "catalog") {
|
if ($context -eq "catalog") {
|
||||||
$use = if ($parsed.use) { $parsed.use } else { "ForItem" }
|
|
||||||
X "$indent`t`t<Use>$use</Use>"
|
X "$indent`t`t<Use>$use</Use>"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1964,6 +2124,7 @@ function Emit-Attribute {
|
|||||||
if ($parsed.indexing) { $indexing = $parsed.indexing }
|
if ($parsed.indexing) { $indexing = $parsed.indexing }
|
||||||
X "$indent`t`t<Indexing>$indexing</Indexing>"
|
X "$indent`t`t<Indexing>$indexing</Indexing>"
|
||||||
}
|
}
|
||||||
|
if ($context -eq "cct") { X "$indent`t`t<Use>$use</Use>" }
|
||||||
|
|
||||||
# Реквизит адресации задачи: AddressingDimension (ссылка на измерение регистра исполнителей), между Indexing и FullTextSearch.
|
# Реквизит адресации задачи: AddressingDimension (ссылка на измерение регистра исполнителей), между Indexing и FullTextSearch.
|
||||||
if ($context -eq "task-addressing" -and $elemTag -eq "AddressingAttribute") {
|
if ($context -eq "task-addressing" -and $elemTag -eq "AddressingAttribute") {
|
||||||
@@ -2131,6 +2292,11 @@ function Emit-EnumValue {
|
|||||||
X "$indent`t`t<Name>$(Esc-XmlText $parsed.name)</Name>"
|
X "$indent`t`t<Name>$(Esc-XmlText $parsed.name)</Name>"
|
||||||
Emit-MLText "$indent`t`t" "Synonym" $parsed.synonym
|
Emit-MLText "$indent`t`t" "Synonym" $parsed.synonym
|
||||||
if ($parsed.comment) { X "$indent`t`t<Comment>$(Esc-XmlText $parsed.comment)</Comment>" } else { X "$indent`t`t<Comment/>" }
|
if ($parsed.comment) { X "$indent`t`t<Comment>$(Esc-XmlText $parsed.comment)</Comment>" } else { X "$indent`t`t<Comment/>" }
|
||||||
|
# Цвет значения перечисления — свойство формата 2.21 (8.5), последним в Properties.
|
||||||
|
if ($script:isFormat221) {
|
||||||
|
$color = if ($parsed.color) { "$($parsed.color)" } else { "auto" }
|
||||||
|
X "$indent`t`t<Color>$(Esc-XmlText $color)</Color>"
|
||||||
|
}
|
||||||
X "$indent`t</Properties>"
|
X "$indent`t</Properties>"
|
||||||
X "$indent</EnumValue>"
|
X "$indent</EnumValue>"
|
||||||
}
|
}
|
||||||
@@ -2797,6 +2963,11 @@ function Emit-CommonFormProperties {
|
|||||||
} else {
|
} else {
|
||||||
X "$i<UsePurposes/>"
|
X "$i<UsePurposes/>"
|
||||||
}
|
}
|
||||||
|
# Использование в режиме совместимости интерфейса — свойство формата 2.21 (8.5),
|
||||||
|
# между UsePurposes и UseStandardCommands.
|
||||||
|
if ($script:isFormat221) {
|
||||||
|
X "$i<UseInInterfaceCompatibilityMode>$(Get-EnumProp 'UseInInterfaceCompatibilityMode' 'useInInterfaceCompatibilityMode' 'Any')</UseInInterfaceCompatibilityMode>"
|
||||||
|
}
|
||||||
$useStdCmds = if (Get-BoolProp "useStandardCommands" $false) { "true" } else { "false" }
|
$useStdCmds = if (Get-BoolProp "useStandardCommands" $false) { "true" } else { "false" }
|
||||||
X "$i<UseStandardCommands>$useStdCmds</UseStandardCommands>"
|
X "$i<UseStandardCommands>$useStdCmds</UseStandardCommands>"
|
||||||
Emit-MLText $i "ExtendedPresentation" $def.extendedPresentation
|
Emit-MLText $i "ExtendedPresentation" $def.extendedPresentation
|
||||||
@@ -3040,7 +3211,9 @@ function Emit-ScheduledJobProperties {
|
|||||||
if ($description) { X "$i<Description>$(Esc-XmlText $description)</Description>" } else { X "$i<Description/>" }
|
if ($description) { X "$i<Description>$(Esc-XmlText $description)</Description>" } else { X "$i<Description/>" }
|
||||||
|
|
||||||
$key = if ($def.key) { "$($def.key)" } else { "" }
|
$key = if ($def.key) { "$($def.key)" } else { "" }
|
||||||
X "$i<Key>$(Esc-XmlText $key)</Key>"
|
# Пустое значение → самозакрывающийся, как у <Description> выше: Конфигуратор
|
||||||
|
# не пишет пустых пар.
|
||||||
|
if ($key) { X "$i<Key>$(Esc-XmlText $key)</Key>" } else { X "$i<Key/>" }
|
||||||
|
|
||||||
$use = if ($def.use -eq $true) { "true" } else { "false" }
|
$use = if ($def.use -eq $true) { "true" } else { "false" }
|
||||||
X "$i<Use>$use</Use>"
|
X "$i<Use>$use</Use>"
|
||||||
@@ -3105,6 +3278,8 @@ function Emit-ReportProperties {
|
|||||||
Emit-VerbatimRef $i "DefaultSettingsForm" $def.defaultSettingsForm
|
Emit-VerbatimRef $i "DefaultSettingsForm" $def.defaultSettingsForm
|
||||||
Emit-VerbatimRef $i "AuxiliarySettingsForm" $def.auxiliarySettingsForm
|
Emit-VerbatimRef $i "AuxiliarySettingsForm" $def.auxiliarySettingsForm
|
||||||
Emit-VerbatimRef $i "DefaultVariantForm" $def.defaultVariantForm
|
Emit-VerbatimRef $i "DefaultVariantForm" $def.defaultVariantForm
|
||||||
|
# Вспомогательная форма варианта отчёта — свойство формата 2.21 (8.5).
|
||||||
|
if ($script:isFormat221) { Emit-VerbatimRef $i "AuxiliaryVariantForm" $def.auxiliaryVariantForm }
|
||||||
Emit-VerbatimRef $i "VariantsStorage" $def.variantsStorage
|
Emit-VerbatimRef $i "VariantsStorage" $def.variantsStorage
|
||||||
Emit-VerbatimRef $i "SettingsStorage" $def.settingsStorage
|
Emit-VerbatimRef $i "SettingsStorage" $def.settingsStorage
|
||||||
$inclHelp = if (Get-BoolProp "includeHelpInContents" $false) { "true" } else { "false" }
|
$inclHelp = if (Get-BoolProp "includeHelpInContents" $false) { "true" } else { "false" }
|
||||||
@@ -3840,7 +4015,8 @@ function Emit-WebServiceProperties {
|
|||||||
if ($def.comment) { X "$i<Comment>$(Esc-XmlText "$($def.comment)")</Comment>" } else { X "$i<Comment/>" }
|
if ($def.comment) { X "$i<Comment>$(Esc-XmlText "$($def.comment)")</Comment>" } else { X "$i<Comment/>" }
|
||||||
|
|
||||||
$namespace = if ($def.namespace) { "$($def.namespace)" } else { "" }
|
$namespace = if ($def.namespace) { "$($def.namespace)" } else { "" }
|
||||||
X "$i<Namespace>$(Esc-XmlText $namespace)</Namespace>"
|
# Пустое значение → самозакрывающийся, как у <Comment> выше.
|
||||||
|
if ($namespace) { X "$i<Namespace>$(Esc-XmlText $namespace)</Namespace>" } else { X "$i<Namespace/>" }
|
||||||
|
|
||||||
# XDTOPackages — СПИСОК элементов, а не скаляр: значение либо ссылка на пакет конфигурации
|
# XDTOPackages — СПИСОК элементов, а не скаляр: значение либо ссылка на пакет конфигурации
|
||||||
# (xr:MDObjectRef "XDTOPackage.Имя"), либо URI внешнего пространства имён (xs:string).
|
# (xr:MDObjectRef "XDTOPackage.Имя"), либо URI внешнего пространства имён (xs:string).
|
||||||
@@ -4082,6 +4258,14 @@ $script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.
|
|||||||
function Detect-FormatVersion([string]$dir) {
|
function Detect-FormatVersion([string]$dir) {
|
||||||
$d = $dir
|
$d = $dir
|
||||||
while ($d) {
|
while ($d) {
|
||||||
|
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||||
|
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||||
|
$extPath = "$d.xml"
|
||||||
|
if (Test-Path $extPath) {
|
||||||
|
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
|
||||||
|
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
|
||||||
|
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||||
|
}
|
||||||
$cfgPath = Join-Path $d "Configuration.xml"
|
$cfgPath = Join-Path $d "Configuration.xml"
|
||||||
if (Test-Path $cfgPath) {
|
if (Test-Path $cfgPath) {
|
||||||
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
||||||
@@ -4143,6 +4327,15 @@ $script:compatMode = Detect-CompatibilityMode $OutputDir
|
|||||||
# глушил бы TypeReductionMode, который платформа этих версий пишет, и роундтрип бы разъезжался.
|
# глушил бы TypeReductionMode, который платформа этих версий пишет, и роундтрип бы разъезжался.
|
||||||
$script:isFormat218 = (Get-FormatRank $script:formatVersion) -ge 218
|
$script:isFormat218 = (Get-FormatRank $script:formatVersion) -ge 218
|
||||||
$script:isFormat220 = (Get-FormatRank $script:formatVersion) -ge 220
|
$script:isFormat220 = (Get-FormatRank $script:formatVersion) -ge 220
|
||||||
|
$script:isFormat221 = (Get-FormatRank $script:formatVersion) -ge 221
|
||||||
|
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
|
||||||
|
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту.
|
||||||
|
# Только для шапок MetaDataObject и Form — в файлах с корнем extrnprops
|
||||||
|
# (Ext/ClientApplicationInterface.xml и т.п.) платформа его не пишет.
|
||||||
|
if ($script:isFormat221) {
|
||||||
|
$palNs = ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette"'
|
||||||
|
$script:xmlnsDecl = $script:xmlnsDecl -replace ' xmlns:style=', "$palNs xmlns:style="
|
||||||
|
}
|
||||||
# Дефолт длины номера строки ТЧ: с режима 8.3.27 платформа заводит новые ТЧ с 9 разрядами.
|
# Дефолт длины номера строки ТЧ: с режима 8.3.27 платформа заводит новые ТЧ с 9 разрядами.
|
||||||
$script:lineNumberLengthDefault = if ((Get-CompatModeRank $script:compatMode) -ge 80327) { 9 } else { 5 }
|
$script:lineNumberLengthDefault = if ((Get-CompatModeRank $script:compatMode) -ge 80327) { 9 } else { 5 }
|
||||||
|
|
||||||
@@ -4268,7 +4461,7 @@ if ($objType -in $typesWithAttrTS) {
|
|||||||
"Catalog" { "catalog" }
|
"Catalog" { "catalog" }
|
||||||
"Document" { "document" }
|
"Document" { "document" }
|
||||||
{ $_ -in @("DataProcessor","Report") } { "processor" }
|
{ $_ -in @("DataProcessor","Report") } { "processor" }
|
||||||
"ChartOfCharacteristicTypes" { "catalog" } # реквизиты ПВХ структурно как у справочника (Use/FillFromFillingValue/DataHistory)
|
"ChartOfCharacteristicTypes" { "cct" } # как catalog (Use/FillFromFillingValue/DataHistory), но Use ПОСЛЕ Indexing
|
||||||
{ $_ -in @("ChartOfAccounts","ChartOfCalculationTypes") } { "account" } # как catalog, но БЕЗ <Use> (реквизиты ПС/ПВР не иерархичны как справочник)
|
{ $_ -in @("ChartOfAccounts","ChartOfCalculationTypes") } { "account" } # как catalog, но БЕЗ <Use> (реквизиты ПС/ПВР не иерархичны как справочник)
|
||||||
default { "object" }
|
default { "object" }
|
||||||
}
|
}
|
||||||
@@ -4356,16 +4549,31 @@ if ($objType -in @("InformationRegister","AccumulationRegister","AccountingRegis
|
|||||||
$regCtx = switch ($objType) { "InformationRegister" { "register-info" } "CalculationRegister" { "register-calc" } default { "register-other" } }
|
$regCtx = switch ($objType) { "InformationRegister" { "register-info" } "CalculationRegister" { "register-calc" } default { "register-other" } }
|
||||||
# Все семейства регистров: ресурсы/измерения — через богатый Emit-Attribute (общий слой object-свойств).
|
# Все семейства регистров: ресурсы/измерения — через богатый Emit-Attribute (общий слой object-свойств).
|
||||||
$dimResCtx = switch ($objType) { "InformationRegister" { "register-info" } "AccumulationRegister" { "register-accum" } "CalculationRegister" { "register-calc" } "AccountingRegister" { "register-account" } default { $null } }
|
$dimResCtx = switch ($objType) { "InformationRegister" { "register-info" } "AccumulationRegister" { "register-accum" } "CalculationRegister" { "register-calc" } "AccountingRegister" { "register-account" } default { $null } }
|
||||||
foreach ($r in $resources) {
|
# Порядок видов детей — канон выгрузки, снят с корпуса (acc+erp, разброса внутри
|
||||||
if ($dimResCtx) { Emit-Attribute "`t`t`t" $r $dimResCtx "Resource" }
|
# типа нет): у большинства регистров Resource, Attribute, Dimension, а у
|
||||||
else { Emit-Resource "`t`t`t" $r $objType }
|
# бухгалтерского — Dimension, Resource, Attribute. Команды у платформы идут
|
||||||
}
|
# последними, как и здесь.
|
||||||
foreach ($d in $dims) {
|
$kindOrder = if ($objType -eq "AccountingRegister") { @('dim','res','attr') } else { @('res','attr','dim') }
|
||||||
if ($dimResCtx) { Emit-Attribute "`t`t`t" $d $dimResCtx "Dimension" }
|
foreach ($kind in $kindOrder) {
|
||||||
else { Emit-Dimension "`t`t`t" $d $objType }
|
switch ($kind) {
|
||||||
}
|
'res' {
|
||||||
foreach ($a in $regAttrs) {
|
foreach ($r in $resources) {
|
||||||
Emit-Attribute "`t`t`t" $a $regCtx
|
if ($dimResCtx) { Emit-Attribute "`t`t`t" $r $dimResCtx "Resource" }
|
||||||
|
else { Emit-Resource "`t`t`t" $r $objType }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'dim' {
|
||||||
|
foreach ($d in $dims) {
|
||||||
|
if ($dimResCtx) { Emit-Attribute "`t`t`t" $d $dimResCtx "Dimension" }
|
||||||
|
else { Emit-Dimension "`t`t`t" $d $objType }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'attr' {
|
||||||
|
foreach ($a in $regAttrs) {
|
||||||
|
Emit-Attribute "`t`t`t" $a $regCtx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
foreach ($cmd in $regCommands) {
|
foreach ($cmd in $regCommands) {
|
||||||
Emit-Command "`t`t`t" $cmd.name $cmd.def
|
Emit-Command "`t`t`t" $cmd.name $cmd.def
|
||||||
@@ -4609,9 +4817,13 @@ function Build-PredefinedXml {
|
|||||||
$sb = New-Object System.Text.StringBuilder
|
$sb = New-Object System.Text.StringBuilder
|
||||||
[void]$sb.Append("<?xml version=`"1.0`" encoding=`"UTF-8`"?>`n")
|
[void]$sb.Append("<?xml version=`"1.0`" encoding=`"UTF-8`"?>`n")
|
||||||
[void]$sb.Append("<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" xmlns:xr=`"http://v8.1c.ru/8.3/xcf/readable`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" xsi:type=`"$xsiType`" version=`"$($script:formatVersion)`">`n")
|
[void]$sb.Append("<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" xmlns:xr=`"http://v8.1c.ru/8.3/xcf/readable`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" xsi:type=`"$xsiType`" version=`"$($script:formatVersion)`">`n")
|
||||||
foreach ($it in $items) { Emit-PredefItem $sb $it "`t" $codeType }
|
# Шапка Predefined.xml не объявляет cfg (predef/v8/xr/xs/xsi) — на время сборки этого
|
||||||
|
# файла ссылочный тип уходит на локальную форму, как делает и платформа.
|
||||||
|
$savedCfgPrefix = $script:cfgPrefix; $script:cfgPrefix = $null
|
||||||
|
try { foreach ($it in $items) { Emit-PredefItem $sb $it "`t" $codeType } }
|
||||||
|
finally { $script:cfgPrefix = $savedCfgPrefix }
|
||||||
[void]$sb.Append("</PredefinedData>`n")
|
[void]$sb.Append("</PredefinedData>`n")
|
||||||
return $sb.ToString()
|
return ($sb.ToString() -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Предопределённые СЧЕТА Плана счетов (отдельная грамматика: AccountType/OffBalance/Order/AccountingFlags/
|
# --- Предопределённые СЧЕТА Плана счетов (отдельная грамматика: AccountType/OffBalance/Order/AccountingFlags/
|
||||||
@@ -4697,9 +4909,12 @@ function Build-PredefinedAccountXml {
|
|||||||
$sb = New-Object System.Text.StringBuilder
|
$sb = New-Object System.Text.StringBuilder
|
||||||
[void]$sb.Append("<?xml version=`"1.0`" encoding=`"UTF-8`"?>`n")
|
[void]$sb.Append("<?xml version=`"1.0`" encoding=`"UTF-8`"?>`n")
|
||||||
[void]$sb.Append("<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" xmlns:xr=`"http://v8.1c.ru/8.3/xcf/readable`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" xsi:type=`"ChartOfAccountsPredefinedItems`" version=`"$($script:formatVersion)`">`n")
|
[void]$sb.Append("<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" xmlns:xr=`"http://v8.1c.ru/8.3/xcf/readable`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" xsi:type=`"ChartOfAccountsPredefinedItems`" version=`"$($script:formatVersion)`">`n")
|
||||||
foreach ($it in $items) { Emit-PredefAccount $sb $it "`t" $objName $acctFlagNames $extDimFlagNames $extDimTypesRef }
|
# См. Build-PredefinedXml: шапка этого файла cfg не объявляет.
|
||||||
|
$savedCfgPrefix = $script:cfgPrefix; $script:cfgPrefix = $null
|
||||||
|
try { foreach ($it in $items) { Emit-PredefAccount $sb $it "`t" $objName $acctFlagNames $extDimFlagNames $extDimTypesRef } }
|
||||||
|
finally { $script:cfgPrefix = $savedCfgPrefix }
|
||||||
[void]$sb.Append("</PredefinedData>`n")
|
[void]$sb.Append("</PredefinedData>`n")
|
||||||
return $sb.ToString()
|
return ($sb.ToString() -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Предопределённые ВИДЫ РАСЧЁТА (плоские: Name/Code/Description/ActionPeriodIsBase). Строка "(Код) Имя [Наим]"
|
# --- Предопределённые ВИДЫ РАСЧЁТА (плоские: Name/Code/Description/ActionPeriodIsBase). Строка "(Код) Имя [Наим]"
|
||||||
@@ -4726,7 +4941,7 @@ function Build-PredefinedCalcTypeXml {
|
|||||||
[void]$sb.Append("<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" xmlns:xr=`"http://v8.1c.ru/8.3/xcf/readable`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" xsi:type=`"CalculationTypePredefinedItems`" version=`"$($script:formatVersion)`">`n")
|
[void]$sb.Append("<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" xmlns:xr=`"http://v8.1c.ru/8.3/xcf/readable`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" xsi:type=`"CalculationTypePredefinedItems`" version=`"$($script:formatVersion)`">`n")
|
||||||
foreach ($it in $items) { Emit-PredefCalcType $sb $it "`t" }
|
foreach ($it in $items) { Emit-PredefCalcType $sb $it "`t" }
|
||||||
[void]$sb.Append("</PredefinedData>`n")
|
[void]$sb.Append("</PredefinedData>`n")
|
||||||
return $sb.ToString()
|
return ($sb.ToString() -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||||
}
|
}
|
||||||
|
|
||||||
$extDir = Join-Path $objSubDir "Ext"
|
$extDir = Join-Path $objSubDir "Ext"
|
||||||
@@ -4741,7 +4956,20 @@ if ($objType -notin $typesNoSubDir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
[System.IO.File]::WriteAllText($mainXmlPath, $metadataXml, $enc)
|
|
||||||
|
# Единая точка записи XML. Конфигуратор не пишет перевод строки в конце файла —
|
||||||
|
# последний байт `>`; сборка через AppendLine добавляла лишний.
|
||||||
|
# KeepEol в имени — отличие от одноимённой функции в скелетных навыках
|
||||||
|
# (cf-init и др.): та ЕЩЁ и нормализует EOL к CRLF, а здесь этого делать
|
||||||
|
# НЕЛЬЗЯ — в объектном XML бывают многострочные текстовые узлы (запрос,
|
||||||
|
# синоним, значение заполнения), и сплошная нормализация меняла бы содержимое.
|
||||||
|
# Разделители тут и так CRLF: документ собран через AppendLine.
|
||||||
|
# Модули .bsl сюда НЕ идут: у них свой хвост.
|
||||||
|
function Write-XmlFileKeepEol([string]$path, [string]$text, $encoding) {
|
||||||
|
[System.IO.File]::WriteAllText($path, $text.TrimEnd("`r", "`n"), $encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-XmlFileKeepEol $mainXmlPath $metadataXml $enc
|
||||||
|
|
||||||
# Module files
|
# Module files
|
||||||
$modulesCreated = @()
|
$modulesCreated = @()
|
||||||
@@ -4822,8 +5050,10 @@ if ($objType -eq "CommonForm") {
|
|||||||
$cfFormXmlPath = Join-Path $extDir "Form.xml"
|
$cfFormXmlPath = Join-Path $extDir "Form.xml"
|
||||||
if (-not (Test-Path $cfFormXmlPath)) {
|
if (-not (Test-Path $cfFormXmlPath)) {
|
||||||
$cfFormNs = 'xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" 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:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
$cfFormNs = 'xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" 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:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||||
$cfFormXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`n<Form $cfFormNs version=`"$($script:formatVersion)`">`n`t<AutoCommandBar name=`"ФормаКоманднаяПанель`" id=`"-1`">`n`t`t<Autofill>true</Autofill>`n`t</AutoCommandBar>`n`t<ChildItems/>`n</Form>`n"
|
# Шапка Form на 2.21 тоже несёт палитру — см. комментарий у $script:xmlnsDecl.
|
||||||
[System.IO.File]::WriteAllText($cfFormXmlPath, $cfFormXml, $enc)
|
if ($script:isFormat221) { $cfFormNs = $cfFormNs -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=' }
|
||||||
|
$cfFormXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<Form $cfFormNs version=`"$($script:formatVersion)`">`r`n`t<AutoCommandBar name=`"ФормаКоманднаяПанель`" id=`"-1`">`r`n`t`t<Autofill>true</Autofill>`r`n`t</AutoCommandBar>`r`n`t<ChildItems/>`r`n</Form>`r`n"
|
||||||
|
Write-XmlFileKeepEol $cfFormXmlPath $cfFormXml $enc
|
||||||
$modulesCreated += $cfFormXmlPath
|
$modulesCreated += $cfFormXmlPath
|
||||||
}
|
}
|
||||||
$cfModuleDir = Join-Path $extDir "Form"
|
$cfModuleDir = Join-Path $extDir "Form"
|
||||||
@@ -4876,7 +5106,7 @@ if ($objType -eq "ExchangePlan") {
|
|||||||
[void]$sbC.Append("`t</Item>`r`n")
|
[void]$sbC.Append("`t</Item>`r`n")
|
||||||
}
|
}
|
||||||
[void]$sbC.Append("</ExchangePlanContent>`r`n")
|
[void]$sbC.Append("</ExchangePlanContent>`r`n")
|
||||||
[System.IO.File]::WriteAllText($contentPath, $sbC.ToString(), $enc)
|
Write-XmlFileKeepEol $contentPath $sbC.ToString() $enc
|
||||||
$modulesCreated += $contentPath
|
$modulesCreated += $contentPath
|
||||||
} elseif (-not (Test-Path $contentPath)) {
|
} elseif (-not (Test-Path $contentPath)) {
|
||||||
# При пустом составе платформа всё равно пишет пустой <ExchangePlanContent/> — проверено на
|
# При пустом составе платформа всё равно пишет пустой <ExchangePlanContent/> — проверено на
|
||||||
@@ -4884,7 +5114,7 @@ if ($objType -eq "ExchangePlan") {
|
|||||||
# (Единственный план обмена БЕЗ файла найден в УТ — хвостовая аномалия конфигурации, не правило.)
|
# (Единственный план обмена БЕЗ файла найден в УТ — хвостовая аномалия конфигурации, не правило.)
|
||||||
Ensure-ExtDir
|
Ensure-ExtDir
|
||||||
$contentXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<ExchangePlanContent $xepNs version=`"$($script:formatVersion)`"/>`r`n"
|
$contentXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<ExchangePlanContent $xepNs version=`"$($script:formatVersion)`"/>`r`n"
|
||||||
[System.IO.File]::WriteAllText($contentPath, $contentXml, $enc)
|
Write-XmlFileKeepEol $contentPath $contentXml $enc
|
||||||
$modulesCreated += $contentPath
|
$modulesCreated += $contentPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4893,7 +5123,7 @@ if ($objType -eq "BusinessProcess") {
|
|||||||
if (-not (Test-Path $flowchartPath)) {
|
if (-not (Test-Path $flowchartPath)) {
|
||||||
Ensure-ExtDir
|
Ensure-ExtDir
|
||||||
$flowchartXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<Flowchart xmlns=`"http://v8.1c.ru/8.3/MDClasses`" version=`"$($script:formatVersion)`"/>`r`n"
|
$flowchartXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<Flowchart xmlns=`"http://v8.1c.ru/8.3/MDClasses`" version=`"$($script:formatVersion)`"/>`r`n"
|
||||||
[System.IO.File]::WriteAllText($flowchartPath, $flowchartXml, $enc)
|
Write-XmlFileKeepEol $flowchartPath $flowchartXml $enc
|
||||||
$modulesCreated += $flowchartPath
|
$modulesCreated += $flowchartPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4908,20 +5138,20 @@ if ($objType -eq 'ChartOfAccounts' -and $def.predefined -and @($def.predefined).
|
|||||||
$edtRef = if ($def.extDimensionTypes) { Resolve-TypePrefixSyn "$($def.extDimensionTypes)" } else { '' }
|
$edtRef = if ($def.extDimensionTypes) { Resolve-TypePrefixSyn "$($def.extDimensionTypes)" } else { '' }
|
||||||
$predefXml = Build-PredefinedAccountXml @($def.predefined) $objName $afNames $edfNames $edtRef
|
$predefXml = Build-PredefinedAccountXml @($def.predefined) $objName $afNames $edfNames $edtRef
|
||||||
$predefPath = Join-Path $extDir "Predefined.xml"
|
$predefPath = Join-Path $extDir "Predefined.xml"
|
||||||
[System.IO.File]::WriteAllText($predefPath, $predefXml, $enc)
|
Write-XmlFileKeepEol $predefPath $predefXml $enc
|
||||||
$modulesCreated += $predefPath
|
$modulesCreated += $predefPath
|
||||||
} elseif ($objType -eq 'ChartOfCalculationTypes' -and $def.predefined -and @($def.predefined).Count -gt 0) {
|
} elseif ($objType -eq 'ChartOfCalculationTypes' -and $def.predefined -and @($def.predefined).Count -gt 0) {
|
||||||
Ensure-ExtDir
|
Ensure-ExtDir
|
||||||
$predefXml = Build-PredefinedCalcTypeXml @($def.predefined)
|
$predefXml = Build-PredefinedCalcTypeXml @($def.predefined)
|
||||||
$predefPath = Join-Path $extDir "Predefined.xml"
|
$predefPath = Join-Path $extDir "Predefined.xml"
|
||||||
[System.IO.File]::WriteAllText($predefPath, $predefXml, $enc)
|
Write-XmlFileKeepEol $predefPath $predefXml $enc
|
||||||
$modulesCreated += $predefPath
|
$modulesCreated += $predefPath
|
||||||
} elseif ($predefRootByType.ContainsKey($objType) -and $def.predefined -and @($def.predefined).Count -gt 0) {
|
} elseif ($predefRootByType.ContainsKey($objType) -and $def.predefined -and @($def.predefined).Count -gt 0) {
|
||||||
Ensure-ExtDir
|
Ensure-ExtDir
|
||||||
$catCodeType = if ($def.codeType) { "$($def.codeType)" } else { 'String' }
|
$catCodeType = if ($def.codeType) { "$($def.codeType)" } else { 'String' }
|
||||||
$predefXml = Build-PredefinedXml @($def.predefined) $predefRootByType[$objType] $catCodeType
|
$predefXml = Build-PredefinedXml @($def.predefined) $predefRootByType[$objType] $catCodeType
|
||||||
$predefPath = Join-Path $extDir "Predefined.xml"
|
$predefPath = Join-Path $extDir "Predefined.xml"
|
||||||
[System.IO.File]::WriteAllText($predefPath, $predefXml, $enc)
|
Write-XmlFileKeepEol $predefPath $predefXml $enc
|
||||||
$modulesCreated += $predefPath
|
$modulesCreated += $predefPath
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4990,15 +5220,31 @@ if (Test-Path $configXmlPath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Save
|
# Save. Пишем через MemoryStream, а не прямо в файл: нужен шаг пост-обработки
|
||||||
|
# строки — XmlWriter отдаёт `encoding="utf-8"` и `<a />`, Конфигуратор пишет
|
||||||
|
# `encoding="UTF-8"` и `<a/>`.
|
||||||
$cfgSettings = New-Object System.Xml.XmlWriterSettings
|
$cfgSettings = New-Object System.Xml.XmlWriterSettings
|
||||||
$cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
$cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
||||||
$cfgSettings.Indent = $false
|
$cfgSettings.Indent = $false
|
||||||
$stream = New-Object System.IO.FileStream($configXmlPath, [System.IO.FileMode]::Create)
|
$cfgSettings.NewLineHandling = [System.Xml.NewLineHandling]::None
|
||||||
$writer = [System.Xml.XmlWriter]::Create($stream, $cfgSettings)
|
$memStream = New-Object System.IO.MemoryStream
|
||||||
|
$writer = [System.Xml.XmlWriter]::Create($memStream, $cfgSettings)
|
||||||
$configDoc.Save($writer)
|
$configDoc.Save($writer)
|
||||||
$writer.Close()
|
$writer.Flush(); $writer.Close()
|
||||||
$stream.Close()
|
|
||||||
|
$cfgText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
|
||||||
|
$memStream.Close()
|
||||||
|
if ($cfgText.Length -gt 0 -and $cfgText[0] -eq [char]0xFEFF) { $cfgText = $cfgText.Substring(1) }
|
||||||
|
$cfgText = $cfgText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$cfgText = [regex]::Replace($cfgText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $configXmlPath) -and ([System.IO.File]::ReadAllText($configXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$cfgText = ($cfgText -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
|
[System.IO.File]::WriteAllText($configXmlPath, $cfgText, (New-Object System.Text.UTF8Encoding($true)))
|
||||||
|
|
||||||
$regResult = "added"
|
$regResult = "added"
|
||||||
}
|
}
|
||||||
@@ -5059,6 +5305,9 @@ switch ($regResult) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Cross-reference hints
|
# Cross-reference hints
|
||||||
|
if ($script:stdAttrTailHint) {
|
||||||
|
Write-Host "[HINT] $($script:stdAttrTailHint)"
|
||||||
|
}
|
||||||
if ($objType -eq "AccountingRegister" -and -not $def.chartOfAccounts) {
|
if ($objType -eq "AccountingRegister" -and -not $def.chartOfAccounts) {
|
||||||
Write-Host "[HINT] AccountingRegister requires ChartOfAccounts reference:"
|
Write-Host "[HINT] AccountingRegister requires ChartOfAccounts reference:"
|
||||||
Write-Host " /meta-edit -Operation modify-property -Value `"ChartOfAccounts=ChartOfAccounts.XXX`""
|
Write-Host " /meta-edit -Operation modify-property -Value `"ChartOfAccounts=ChartOfAccounts.XXX`""
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# meta-compile v1.76 — Compile 1C metadata object from JSON
|
# meta-compile v1.94 — Compile 1C metadata object from JSON
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -16,6 +16,69 @@ from lxml import etree
|
|||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Регистронезависимый ввод — паритет с PS1. В PowerShell регистр не значим нигде, куда
|
||||||
|
# смотрит пользовательский ввод: свойства объекта из ConvertFrom-Json, ключи Hashtable,
|
||||||
|
# -eq/-contains, имена параметров, ValidateSet. В Python совпадение точное, поэтому порт
|
||||||
|
# молча терял свойства DSL, написанные в другом регистре. Обёртки ниже выравнивают поведение.
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
class CIDict(dict):
|
||||||
|
# Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные
|
||||||
|
# реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки
|
||||||
|
# сохраняется — от него зависит порядок эмиссии.
|
||||||
|
def _actual(self, key):
|
||||||
|
if not isinstance(key, str) or dict.__contains__(self, key):
|
||||||
|
return key
|
||||||
|
ci = self.__dict__.get('_ci')
|
||||||
|
if ci is None or len(ci) != len(self):
|
||||||
|
ci = {k.lower(): k for k in self if isinstance(k, str)}
|
||||||
|
self.__dict__['_ci'] = ci
|
||||||
|
return ci.get(key.lower(), key)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return dict.__getitem__(self, self._actual(key))
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return dict.__contains__(self, self._actual(key))
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return dict.get(self, self._actual(key), default)
|
||||||
|
|
||||||
|
def pop(self, key, *default):
|
||||||
|
return dict.pop(self, self._actual(key), *default)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
# запись по ключу, отличающемуся регистром, обновляет существующий, а не плодит дубль
|
||||||
|
dict.__setitem__(self, self._actual(key), value)
|
||||||
|
|
||||||
|
def ci_json(obj):
|
||||||
|
"""Рекурсивно оборачивает разобранный JSON: словари → CIDict, списки обходятся."""
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return CIDict((k, ci_json(v)) for k, v in obj.items())
|
||||||
|
if isinstance(obj, list):
|
||||||
|
return [ci_json(v) for v in obj]
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
||||||
# Blocks edits of vendor objects "на замке" / read-only configs. Trigger = bin
|
# Blocks edits of vendor objects "на замке" / read-only configs. Trigger = bin
|
||||||
@@ -206,9 +269,22 @@ def new_uuid():
|
|||||||
return str(uuid.uuid4())
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
def write_utf8_bom(path, content):
|
def write_utf8_bom(path, content):
|
||||||
|
# newline='' — без трансляции: иначе текстовый режим Python дал бы CRLF на Windows
|
||||||
|
# и LF на macOS, то есть вывод навыка зависел бы от ОС.
|
||||||
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
def write_xml_file_keep_eol(path, content):
|
||||||
|
# Единая точка записи XML. Конфигуратор не пишет перевод строки в конце файла —
|
||||||
|
# последний байт `>`.
|
||||||
|
# keep_eol в имени — отличие от одноимённой функции в скелетных навыках
|
||||||
|
# (cf-init и др.): та ЕЩЁ и нормализует EOL к CRLF, а здесь этого делать
|
||||||
|
# НЕЛЬЗЯ — в объектном XML бывают многострочные текстовые узлы (запрос,
|
||||||
|
# синоним, значение заполнения). Разделители и так CRLF — их даёт join строк.
|
||||||
|
# Модули .bsl сюда НЕ идут: у них свой хвост.
|
||||||
|
write_utf8_bom(path, content.rstrip('\r\n'))
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# XML builder (lines list)
|
# XML builder (lines list)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -289,7 +365,7 @@ def split_camel_case(name):
|
|||||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||||
parser.add_argument('-JsonPath', required=True)
|
parser.add_argument('-JsonPath', required=True)
|
||||||
parser.add_argument('-OutputDir', required=True)
|
parser.add_argument('-OutputDir', required=True)
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
json_path = args.JsonPath
|
json_path = args.JsonPath
|
||||||
output_dir = args.OutputDir
|
output_dir = args.OutputDir
|
||||||
@@ -301,7 +377,7 @@ if not os.path.isfile(json_path):
|
|||||||
with open(json_path, 'r', encoding='utf-8-sig') as f:
|
with open(json_path, 'r', encoding='utf-8-sig') as f:
|
||||||
json_text = f.read()
|
json_text = f.read()
|
||||||
|
|
||||||
defn = json.loads(json_text)
|
defn = ci_json(json.loads(json_text))
|
||||||
|
|
||||||
assert_edit_allowed(output_dir, "editable")
|
assert_edit_allowed(output_dir, "editable")
|
||||||
|
|
||||||
@@ -395,6 +471,10 @@ enum_value_aliases = {
|
|||||||
'ИндексироватьСДопУпорядочиванием': 'IndexWithAdditionalOrder',
|
'ИндексироватьСДопУпорядочиванием': 'IndexWithAdditionalOrder',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Словари, по которым ищут ПОЛЬЗОВАТЕЛЬСКИЙ ввод, — регистронезависимы, как хеш-таблицы PS1.
|
||||||
|
object_type_synonyms = CIDict(object_type_synonyms)
|
||||||
|
enum_value_aliases = CIDict(enum_value_aliases)
|
||||||
|
|
||||||
# Valid enum values per property (from meta-validate)
|
# Valid enum values per property (from meta-validate)
|
||||||
valid_enum_values = {
|
valid_enum_values = {
|
||||||
'RegisterType': ['Balance', 'Turnovers'],
|
'RegisterType': ['Balance', 'Turnovers'],
|
||||||
@@ -553,6 +633,8 @@ valid_types = [
|
|||||||
'SessionParameter', 'CommonCommand', 'CommandGroup', 'CommonAttribute', 'FunctionalOptionsParameter', 'WSReference',
|
'SessionParameter', 'CommonCommand', 'CommandGroup', 'CommonAttribute', 'FunctionalOptionsParameter', 'WSReference',
|
||||||
'CommonPicture', 'CommonTemplate',
|
'CommonPicture', 'CommonTemplate',
|
||||||
]
|
]
|
||||||
|
# Регистр имени вида — как в PS (-contains регистронезависим): приводим к канону списка
|
||||||
|
obj_type = next((t for t in valid_types if t.lower() == obj_type.lower()), obj_type)
|
||||||
if obj_type not in valid_types:
|
if obj_type not in valid_types:
|
||||||
print(f"Unsupported type: {obj_type}. Valid: {', '.join(valid_types)}", file=sys.stderr)
|
print(f"Unsupported type: {obj_type}. Valid: {', '.join(valid_types)}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -627,47 +709,103 @@ type_namespace_map = {
|
|||||||
"SpreadsheetDocument": {"ns": "http://v8.1c.ru/8.2/data/spreadsheet", "prefix": "mxl"},
|
"SpreadsheetDocument": {"ns": "http://v8.1c.ru/8.2/data/spreadsheet", "prefix": "mxl"},
|
||||||
}
|
}
|
||||||
# Типы current-config пространства (cfg:, объявлено в корне): голые и объектные. Ссылочные — отдельно (d5p1).
|
# Типы current-config пространства (cfg:, объявлено в корне): голые и объектные. Ссылочные — отдельно (d5p1).
|
||||||
|
# Префикс current-config для ссылочных типов. 'cfg' — для файлов, чья шапка его объявляет
|
||||||
|
# (объектный XML, Ext/Form.xml общей формы). None на время сборки Ext/Predefined.xml, чья
|
||||||
|
# шапка его НЕ объявляет: там и платформа уходит на локальное объявление.
|
||||||
|
cfg_prefix = 'cfg'
|
||||||
cfg_bare_types = {"ConstantsSet", "ReportBuilder", "FilterCriterion"}
|
cfg_bare_types = {"ConstantsSet", "ReportBuilder", "FilterCriterion"}
|
||||||
cfg_object_kinds = {"Catalog", "Document", "Enum", "ChartOfAccounts", "ChartOfCharacteristicTypes",
|
cfg_object_kinds = {"Catalog", "Document", "Enum", "ChartOfAccounts", "ChartOfCharacteristicTypes",
|
||||||
"ChartOfCalculationTypes", "ExchangePlan", "BusinessProcess", "Task", "InformationRegister",
|
"ChartOfCalculationTypes", "ExchangePlan", "BusinessProcess", "Task", "InformationRegister",
|
||||||
"AccumulationRegister", "AccountingRegister", "CalculationRegister", "DataProcessor", "Report",
|
"AccumulationRegister", "AccountingRegister", "CalculationRegister", "DataProcessor", "Report",
|
||||||
"DocumentJournal", "Constant", "ConstantValue", "Sequence", "Recalculation"}
|
"DocumentJournal", "Constant", "ConstantValue", "Sequence", "Recalculation"}
|
||||||
|
|
||||||
|
# Алиас на локальный словарь: тело resolve_type_str ниже — общая реализация,
|
||||||
|
# одинаковая во всех навыках (реестр в tests/skills/check-inline-drift.mjs).
|
||||||
|
type_synonyms = CIDict(type_synonyms)
|
||||||
|
TYPE_SYNONYMS = type_synonyms
|
||||||
|
|
||||||
|
|
||||||
def resolve_type_str(type_str):
|
def resolve_type_str(type_str):
|
||||||
if not type_str:
|
if not type_str:
|
||||||
return type_str
|
return type_str
|
||||||
# Parameterized types: Number(15,2), Строка(100), etc.
|
# Прощающий ввод: ведущий префикс приходит копипастой из выгрузки. Без срезания он ломает
|
||||||
|
# поиск в словаре — русское имя типа остаётся непереведённым, и платформа отвечает
|
||||||
|
# «Неизвестное имя типа». cfg: снимаем всегда — он однозначно означает текущую конфигурацию.
|
||||||
|
# Сгенерированный dNpM: (в корпусе на этом URI встречаются d4p1, d5p1, d6p1 — имя префикса
|
||||||
|
# платформа выдаёт по порядку объявления) снимаем ТОЛЬКО у ссылочных типов, с точкой:
|
||||||
|
# сам по себе префикс многозначен — в формах d5p1:Chart, d5p1:TextDocument,
|
||||||
|
# d5p1:GeographicalSchema адресуют чужие пространства имён, и там он часть значения.
|
||||||
|
if type_str.startswith('cfg:'):
|
||||||
|
type_str = type_str[4:]
|
||||||
|
elif '.' in type_str and re.match(r'^d\d+p\d+:', type_str):
|
||||||
|
type_str = type_str[type_str.index(':') + 1:]
|
||||||
|
# Параметризованные типы: Number(15,2), Строка(100)
|
||||||
m = re.match(r'^([^(]+)\((.+)\)$', type_str)
|
m = re.match(r'^([^(]+)\((.+)\)$', type_str)
|
||||||
if m:
|
if m:
|
||||||
base_name = m.group(1).strip()
|
base_name = m.group(1).strip()
|
||||||
params = m.group(2)
|
params = m.group(2)
|
||||||
resolved = type_synonyms.get(base_name.lower())
|
resolved = TYPE_SYNONYMS.get(base_name.lower())
|
||||||
if resolved:
|
if resolved:
|
||||||
return f'{resolved}({params})'
|
return f'{resolved}({params})'
|
||||||
return type_str
|
return type_str
|
||||||
# Reference types: СправочникСсылка.Организации -> CatalogRef.Организации
|
# Ссылочные типы: СправочникСсылка.Организации -> CatalogRef.Организации
|
||||||
if '.' in type_str:
|
if '.' in type_str:
|
||||||
dot_idx = type_str.index('.')
|
dot_idx = type_str.index('.')
|
||||||
prefix = type_str[:dot_idx]
|
prefix = type_str[:dot_idx]
|
||||||
suffix = type_str[dot_idx:] # includes the dot
|
suffix = type_str[dot_idx:] # includes the dot
|
||||||
resolved = type_synonyms.get(prefix.lower())
|
resolved = TYPE_SYNONYMS.get(prefix.lower())
|
||||||
if resolved:
|
if resolved:
|
||||||
return f'{resolved}{suffix}'
|
return f'{resolved}{suffix}'
|
||||||
return type_str
|
return type_str
|
||||||
# Simple name lookup
|
# Простое имя
|
||||||
resolved = type_synonyms.get(type_str.lower())
|
resolved = TYPE_SYNONYMS.get(type_str.lower())
|
||||||
if resolved:
|
if resolved:
|
||||||
return resolved
|
return resolved
|
||||||
return type_str
|
return type_str
|
||||||
|
|
||||||
def emit_type_content(indent, type_str):
|
def emit_type_content(indent, type_str):
|
||||||
if not type_str:
|
if not type_str:
|
||||||
return
|
return
|
||||||
# Composite type: "Type1 + Type2 + Type3"
|
# Composite type: "Type1 + Type2 + Type3"
|
||||||
|
# Платформа пишет сначала ВСЕ <v8:Type>/<v8:TypeSet>, и только потом блоки
|
||||||
|
# квалификаторов — а рекурсия ниже печатала бы каждую часть целиком (тип вместе со
|
||||||
|
# своими квалификаторами). На одиночном типе оба порядка совпадают, поэтому
|
||||||
|
# расхождение вылезало только на составном.
|
||||||
|
# Порядок самих блоков квалификаторов тоже канонический и НЕ зеркалит порядок типов:
|
||||||
|
# Number, String, Date (корпус acc+erp, контрпримеров нет — при типах
|
||||||
|
# boolean,string,dateTime,decimal квалификаторы идут Number,String,Date).
|
||||||
|
# Порядок типов при этом сохраняем как в DSL: он и есть порядок источника.
|
||||||
if ' + ' in type_str:
|
if ' + ' in type_str:
|
||||||
parts = [p.strip() for p in type_str.split('+')]
|
parts = [p.strip() for p in type_str.split('+')]
|
||||||
|
type_lines = []
|
||||||
|
qual_blocks = {}
|
||||||
for part in parts:
|
for part in parts:
|
||||||
|
# X добавляет в список lines, поэтому «перехват» — это срез и откат хвоста.
|
||||||
|
# В PS-порту X пишет в StringBuilder и тот же алгоритм выражен через
|
||||||
|
# Length/Remove — различие рантаймов, не логики.
|
||||||
|
before = len(lines)
|
||||||
emit_type_content(indent, part)
|
emit_type_content(indent, part)
|
||||||
|
chunk = lines[before:]
|
||||||
|
del lines[before:]
|
||||||
|
cur_qual = None
|
||||||
|
for line in chunk:
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
m = re.search(r'<v8:(String|Number|Date)Qualifiers>', line)
|
||||||
|
if m:
|
||||||
|
cur_qual = m.group(1)
|
||||||
|
qual_blocks[cur_qual] = []
|
||||||
|
if cur_qual:
|
||||||
|
qual_blocks[cur_qual].append(line)
|
||||||
|
if re.search(r'</v8:(String|Number|Date)Qualifiers>', line):
|
||||||
|
cur_qual = None
|
||||||
|
else:
|
||||||
|
type_lines.append(line)
|
||||||
|
for line in type_lines:
|
||||||
|
X(line)
|
||||||
|
for q in ('Number', 'String', 'Date'):
|
||||||
|
if q in qual_blocks:
|
||||||
|
for line in qual_blocks[q]:
|
||||||
|
X(line)
|
||||||
return
|
return
|
||||||
type_str = resolve_type_str(type_str)
|
type_str = resolve_type_str(type_str)
|
||||||
# Boolean
|
# Boolean
|
||||||
@@ -760,10 +898,22 @@ def emit_type_content(indent, type_str):
|
|||||||
X(f'{indent}<v8:Type>cfg:{type_str}</v8:Type>')
|
X(f'{indent}<v8:Type>cfg:{type_str}</v8:Type>')
|
||||||
return
|
return
|
||||||
|
|
||||||
# Reference types — use local xmlns declaration for 1C compatibility
|
# Ссылочные типы — корневой cfg:, как пишет платформа. Раньше здесь объявлялся
|
||||||
|
# ЛОКАЛЬНЫЙ xmlns:d5p1 на тот же URI, что уже объявлен в шапке: формально
|
||||||
|
# эквивалентно (значим URI, не префикс) и платформой принималось, но первый же
|
||||||
|
# цикл «загрузить в базу → выгрузить» переписывал каждый ссылочный тип в cfg: —
|
||||||
|
# то есть давал diff-шум на ровном месте. Форма пришла из СКД, где cfg:
|
||||||
|
# действительно не работает; в метаданных такого ограничения нет.
|
||||||
|
# NB: локальная xmlns остаётся законной для ЧУЖИХ пространств — см. type_namespace_map.
|
||||||
|
# cfg_prefix = None означает «пишем файл, корень которого cfg НЕ объявляет»
|
||||||
|
# (Ext/Predefined.xml — его шапка это predef/v8/xr/xs/xsi). Там платформа сама уходит
|
||||||
|
# на локальную форму: в корпусе `<v8:Type xmlns:d6p1="…current-config">d6p1:CatalogRef.Валюты`.
|
||||||
m = re.match(r'^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|BusinessProcessRoutePointRef|TaskRef)\.(.+)$', type_str)
|
m = re.match(r'^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|BusinessProcessRoutePointRef|TaskRef)\.(.+)$', type_str)
|
||||||
if m:
|
if m:
|
||||||
X(f'{indent}<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:{type_str}</v8:Type>')
|
if cfg_prefix:
|
||||||
|
X(f'{indent}<v8:Type>{cfg_prefix}:{type_str}</v8:Type>')
|
||||||
|
else:
|
||||||
|
X(f'{indent}<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:{type_str}</v8:Type>')
|
||||||
return
|
return
|
||||||
# Fallback
|
# Fallback
|
||||||
X(f'{indent}<v8:Type>{type_str}</v8:Type>')
|
X(f'{indent}<v8:Type>{type_str}</v8:Type>')
|
||||||
@@ -1270,15 +1420,24 @@ standard_attributes_by_type = {
|
|||||||
'Document': ['Posted', 'Ref', 'DeletionMark', 'Date', 'Number'],
|
'Document': ['Posted', 'Ref', 'DeletionMark', 'Date', 'Number'],
|
||||||
'Enum': ['Order', 'Ref'],
|
'Enum': ['Order', 'Ref'],
|
||||||
'InformationRegister': ['Active', 'LineNumber', 'Recorder', 'Period'],
|
'InformationRegister': ['Active', 'LineNumber', 'Recorder', 'Period'],
|
||||||
'AccumulationRegister': ['Active', 'LineNumber', 'Recorder', 'Period'],
|
'AccumulationRegister': ['RecordType', 'Active', 'LineNumber', 'Recorder', 'Period'],
|
||||||
'AccountingRegister': ['Active', 'Period', 'Recorder', 'LineNumber', 'Account'],
|
'AccountingRegister': ['PeriodAdjustment', 'Account', 'RecordType', 'Active', 'LineNumber', 'Recorder', 'Period'],
|
||||||
'CalculationRegister': ['Active', 'Recorder', 'LineNumber', 'RegistrationPeriod', 'CalculationType', 'ReversingEntry'],
|
'CalculationRegister': ['RegistrationPeriod', 'ReversingEntry', 'Active', 'EndOfBasePeriod', 'BegOfBasePeriod', 'EndOfActionPeriod', 'BegOfActionPeriod', 'ActionPeriod', 'CalculationType', 'LineNumber', 'Recorder'],
|
||||||
'ChartOfAccounts': ['PredefinedDataName', 'Order', 'OffBalance', 'Type', 'Description', 'Code', 'Parent', 'Predefined', 'DeletionMark', 'Ref'],
|
'ChartOfAccounts': ['PredefinedDataName', 'Order', 'OffBalance', 'Type', 'Description', 'Code', 'Parent', 'Predefined', 'DeletionMark', 'Ref'],
|
||||||
'ChartOfCharacteristicTypes': ['PredefinedDataName', 'Predefined', 'Ref', 'DeletionMark', 'Description', 'Code', 'Parent', 'ValueType'],
|
'ChartOfCharacteristicTypes': ['PredefinedDataName', 'ValueType', 'Description', 'Code', 'IsFolder', 'Parent', 'Predefined', 'DeletionMark', 'Ref'],
|
||||||
'ChartOfCalculationTypes': ['PredefinedDataName', 'Predefined', 'Ref', 'DeletionMark', 'ActionPeriodIsBasic', 'Description', 'Code'],
|
'ChartOfCalculationTypes': ['PredefinedDataName', 'Predefined', 'Ref', 'DeletionMark', 'ActionPeriodIsBasic', 'Description', 'Code'],
|
||||||
'BusinessProcess': ['Ref', 'DeletionMark', 'Date', 'Number', 'Started', 'Completed', 'HeadTask'],
|
# Порядок в каждом списке — канон выгрузки, снят с корпуса acc+erp (внутри типа разброса нет).
|
||||||
'Task': ['Ref', 'DeletionMark', 'Date', 'Number', 'Executed', 'Description', 'RoutePoint', 'BusinessProcess'],
|
# Условные члены перечислены в std_attr_conditions — позицию они берут отсюда, а
|
||||||
'ExchangePlan': ['Ref', 'DeletionMark', 'Code', 'Description', 'ThisNode', 'SentNo', 'ReceivedNo'],
|
# присутствие определяется свойствами объекта.
|
||||||
|
# У ПВХ IsFolder входит в фикс-список: он есть у всех 23 объектов корпуса с этим блоком.
|
||||||
|
# У регистра расчёта список безусловен: реквизиты периода действия и базового периода
|
||||||
|
# платформа пишет при любых ActionPeriod/BasePeriod/Periodicity (синтетика, все 4 комбинации).
|
||||||
|
'BusinessProcess': ['Started', 'HeadTask', 'Completed', 'Ref', 'DeletionMark', 'Date', 'Number'],
|
||||||
|
'Task': ['Executed', 'Description', 'RoutePoint', 'BusinessProcess', 'Ref', 'DeletionMark', 'Date', 'Number'],
|
||||||
|
# Порядок снят с выгрузки: у плана обмена блок начинается с ThisNode, а не с Ref
|
||||||
|
# (acc+erp, 8 объектов, разброса нет). Прочие типы в этой таблице совпадают с
|
||||||
|
# платформой — расхождений порядка по ним корпусный раундтрип не показал.
|
||||||
|
'ExchangePlan': ['ThisNode', 'ReceivedNo', 'SentNo', 'Ref', 'DeletionMark', 'Description', 'Code'],
|
||||||
'DocumentJournal': ['Type', 'Ref', 'Date', 'Posted', 'DeletionMark', 'Number'],
|
'DocumentJournal': ['Type', 'Ref', 'Date', 'Posted', 'DeletionMark', 'Number'],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1398,6 +1557,64 @@ def emit_standard_attribute(indent, attr_name, ov=None):
|
|||||||
# std_attr_conditional_types: типы, где блок только при кастомизации (DSL-ключ standardAttributes).
|
# std_attr_conditional_types: типы, где блок только при кастомизации (DSL-ключ standardAttributes).
|
||||||
# Прочие типы → блок всегда (текущее поведение). Миграция типа = +строчка в оба справочника + снэпшоты.
|
# Прочие типы → блок всегда (текущее поведение). Миграция типа = +строчка в оба справочника + снэпшоты.
|
||||||
std_attr_conditional_types = {'Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document'}
|
std_attr_conditional_types = {'Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document'}
|
||||||
|
|
||||||
|
# Условные члены списка типа: позиция берётся из standard_attributes_by_type, а присутствие —
|
||||||
|
# из свойств самого объекта, как у платформы. Предикат принимает определение параметром
|
||||||
|
# (зеркало .ps1, где scriptblock не видит $def вызывающей функции).
|
||||||
|
def _period_adjustment_used(d):
|
||||||
|
v = d.get('periodAdjustmentLength')
|
||||||
|
return v is not None and int(str(v)) > 0
|
||||||
|
|
||||||
|
std_attr_conditions = {
|
||||||
|
'AccountingRegister': {
|
||||||
|
'PeriodAdjustment': _period_adjustment_used,
|
||||||
|
'RecordType': lambda d: d.get('correspondence') is not True,
|
||||||
|
},
|
||||||
|
'AccumulationRegister': {
|
||||||
|
'RecordType': lambda d: normalize_enum_value('RegisterType', str(d.get('registerType') or 'Balance')) == 'Balance',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Хвостовая группа: реквизиты, которых нет в списке типа и которые идут ПОСЛЕ него.
|
||||||
|
# У бухрегистра это пары субконто. Именами их не перечислить: их количество задаётся
|
||||||
|
# свойством MaxExtDimensionCount плана счетов, а не константой (в корпусе везде 3, но
|
||||||
|
# это однородность выборки, а не правило). Поэтому — шаблон, а не список.
|
||||||
|
std_attr_tail_pattern = {
|
||||||
|
'AccountingRegister': r'^ExtDimension(Type)?\d+$',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Состав хвоста задаёт не DSL, а объект, на который регистр ссылается: пар субконто столько,
|
||||||
|
# сколько у плана счетов MaxExtDimensionCount. Читаем его из выгрузки — как версию формата из
|
||||||
|
# Configuration.xml, — чтобы регистр, описанный неполным DSL, совпал с тем, что материализует
|
||||||
|
# платформа. План не найден → хвост не генерируем и говорим об этом в выводе.
|
||||||
|
std_attr_tail_hint = None
|
||||||
|
|
||||||
|
def _acc_register_ext_dimension_tail(d, object_name, out_dir):
|
||||||
|
global std_attr_tail_hint
|
||||||
|
ref = str(d.get('chartOfAccounts') or '')
|
||||||
|
if not ref:
|
||||||
|
return []
|
||||||
|
chart_name = re.sub(r'^.*\.', '', ref) # ссылка вида ChartOfAccounts.X (имя объекта точек не содержит)
|
||||||
|
path = os.path.join(out_dir, 'ChartsOfAccounts', chart_name + '.xml')
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
std_attr_tail_hint = ("ChartOfAccounts '%s' not found in dump — ExtDimension pairs not generated "
|
||||||
|
"(platform will add them on load)" % chart_name)
|
||||||
|
return []
|
||||||
|
with open(path, encoding='utf-8-sig') as f:
|
||||||
|
m = re.search(r'<MaxExtDimensionCount>(\d+)</MaxExtDimensionCount>', f.read())
|
||||||
|
n = int(m.group(1)) if m else 0
|
||||||
|
out = []
|
||||||
|
for i in range(1, n + 1):
|
||||||
|
# ExtDimensionN связан с Account через LinkByType (LinkItem = номер), ExtDimensionTypeN — нет.
|
||||||
|
out.append(('ExtDimension%d' % i,
|
||||||
|
{'LinkByType': {'dataPath': 'AccountingRegister.%s.StandardAttribute.Account' % object_name,
|
||||||
|
'linkItem': i}}))
|
||||||
|
out.append(('ExtDimensionType%d' % i, {}))
|
||||||
|
return out
|
||||||
|
|
||||||
|
std_attr_tail_derived = {
|
||||||
|
'AccountingRegister': _acc_register_ext_dimension_tail,
|
||||||
|
}
|
||||||
def emit_standard_attributes(indent, object_type):
|
def emit_standard_attributes(indent, object_type):
|
||||||
attrs = standard_attributes_by_type.get(object_type)
|
attrs = standard_attributes_by_type.get(object_type)
|
||||||
if not attrs:
|
if not attrs:
|
||||||
@@ -1409,12 +1626,45 @@ def emit_standard_attributes(indent, object_type):
|
|||||||
if isinstance(sa, str) and sa == '':
|
if isinstance(sa, str) and sa == '':
|
||||||
return # opt-out `standardAttributes:""` (дом-конвенция суппресса, ~5% регистров опускают all-default блок)
|
return # opt-out `standardAttributes:""` (дом-конвенция суппресса, ~5% регистров опускают all-default блок)
|
||||||
profile = std_attr_profile.get(object_type, {})
|
profile = std_attr_profile.get(object_type, {})
|
||||||
# Доп. (опциональные) стандартные реквизиты вне фикс-списка — напр. ExchangeDate у части ПланОбмена
|
# Список типа задаёт ПОРЯДОК всех известных стандартных реквизитов, включая условные:
|
||||||
# (легаси, присутствие не выводится). Эмитим по факту ключа в DSL, ПЕРЕД фикс-списком (их позиция).
|
# их позиция бывает и до, и после обязательных (у бухрегистра PeriodAdjustment идёт
|
||||||
extra = [k for k in sa if k not in attrs] if isinstance(sa, dict) else []
|
# перед Account, RecordType — после, а ExtDimension1..3/ExtDimensionType1..3 — после Period),
|
||||||
|
# поэтому «условные скопом вперёд» не выражает канон.
|
||||||
|
cond = std_attr_conditions.get(object_type)
|
||||||
|
# Ключи, которых нет в списке типа ВООБЩЕ. По умолчанию их позиция — ПЕРЕД списком
|
||||||
|
# (легаси вроде ExchangeDate у части планов обмена). Подходящие под хвостовой шаблон
|
||||||
|
# типа идут ПОСЛЕ, в порядке номера, а внутри номера — сначала ExtDimensionN, затем
|
||||||
|
# ExtDimensionTypeN (порядок платформы).
|
||||||
|
tail_re = std_attr_tail_pattern.get(object_type)
|
||||||
|
extra, tail = [], []
|
||||||
|
if isinstance(sa, dict):
|
||||||
|
for k in sa:
|
||||||
|
if k in attrs:
|
||||||
|
continue
|
||||||
|
if tail_re and re.match(tail_re, k):
|
||||||
|
tail.append(k)
|
||||||
|
else:
|
||||||
|
extra.append(k)
|
||||||
|
# Хвост, выведенный из связанного объекта: дополняет DSL, а не заменяет его — лишнее из DSL
|
||||||
|
# остаётся (прощаем), недостающее добавляется вместе со своими значениями по умолчанию.
|
||||||
|
derived_ov = {}
|
||||||
|
gen = std_attr_tail_derived.get(object_type)
|
||||||
|
if gen:
|
||||||
|
for name, dov in gen(defn, obj_name, output_dir):
|
||||||
|
derived_ov[name] = dov
|
||||||
|
if name not in tail:
|
||||||
|
tail.append(name)
|
||||||
|
tail.sort(key=lambda k: (int(re.search(r'\d+', k).group()), 1 if re.search(r'Type\d+$', k) else 0))
|
||||||
X(f'{indent}<StandardAttributes>')
|
X(f'{indent}<StandardAttributes>')
|
||||||
for a in extra + list(attrs):
|
for a in extra + list(attrs) + tail:
|
||||||
|
# Условный реквизит: эмитим, если так велят свойства объекта ЛИБО если ключ есть в DSL.
|
||||||
|
# Дизъюнкция страхует роундтрип — декомпилятор перечисляет все имена блока.
|
||||||
|
if cond and a in cond:
|
||||||
|
present = (isinstance(sa, dict) and a in sa) or cond[a](defn)
|
||||||
|
if not present:
|
||||||
|
continue
|
||||||
ov = dict(profile.get(a, {}))
|
ov = dict(profile.get(a, {}))
|
||||||
|
ov.update(derived_ov.get(a, {}))
|
||||||
if isinstance(sa, dict):
|
if isinstance(sa, dict):
|
||||||
d = sa.get(a)
|
d = sa.get(a)
|
||||||
if d:
|
if d:
|
||||||
@@ -2016,8 +2266,13 @@ def emit_attribute(indent, parsed, context, elem_tag='Attribute'):
|
|||||||
X(f'{indent}\t\t<ExtDimensionAccountingFlag>{esc_xml_text(str(parsed["extDimensionAccountingFlag"]))}</ExtDimensionAccountingFlag>')
|
X(f'{indent}\t\t<ExtDimensionAccountingFlag>{esc_xml_text(str(parsed["extDimensionAccountingFlag"]))}</ExtDimensionAccountingFlag>')
|
||||||
else:
|
else:
|
||||||
X(f'{indent}\t\t<ExtDimensionAccountingFlag/>')
|
X(f'{indent}\t\t<ExtDimensionAccountingFlag/>')
|
||||||
|
# Use — у реквизитов справочника и ПВХ. Позиция РАЗНАЯ: справочник пишет Use ПЕРЕД
|
||||||
|
# Indexing, ПВХ — ПОСЛЕ него (корпус acc+erp: Catalog `Use,Indexing,FullTextSearch`,
|
||||||
|
# ПВХ `Indexing,Use,FullTextSearch,DataHistory`). Отсюда отдельный контекст 'cct':
|
||||||
|
# структурно реквизит ПВХ совпадает со справочником, расходится только этим порядком.
|
||||||
|
use_value = parsed.get("use") or "ForItem"
|
||||||
if context == 'catalog':
|
if context == 'catalog':
|
||||||
X(f'{indent}\t\t<Use>{parsed.get("use") or "ForItem"}</Use>')
|
X(f'{indent}\t\t<Use>{use_value}</Use>')
|
||||||
if context not in ('processor', 'processor-tabular'):
|
if context not in ('processor', 'processor-tabular'):
|
||||||
# Признаки учёта ПС (account-flag) не имеют <Indexing>/<FullTextSearch>, но имеют <DataHistory>.
|
# Признаки учёта ПС (account-flag) не имеют <Indexing>/<FullTextSearch>, но имеют <DataHistory>.
|
||||||
if context != 'account-flag':
|
if context != 'account-flag':
|
||||||
@@ -2031,6 +2286,8 @@ def emit_attribute(indent, parsed, context, elem_tag='Attribute'):
|
|||||||
if parsed.get('indexing'):
|
if parsed.get('indexing'):
|
||||||
indexing = parsed['indexing']
|
indexing = parsed['indexing']
|
||||||
X(f'{indent}\t\t<Indexing>{indexing}</Indexing>')
|
X(f'{indent}\t\t<Indexing>{indexing}</Indexing>')
|
||||||
|
if context == 'cct':
|
||||||
|
X(f'{indent}\t\t<Use>{use_value}</Use>')
|
||||||
# Реквизит адресации задачи: AddressingDimension (между Indexing и FullTextSearch).
|
# Реквизит адресации задачи: AddressingDimension (между Indexing и FullTextSearch).
|
||||||
if context == 'task-addressing' and elem_tag == 'AddressingAttribute':
|
if context == 'task-addressing' and elem_tag == 'AddressingAttribute':
|
||||||
if parsed.get('addressingDimension'):
|
if parsed.get('addressingDimension'):
|
||||||
@@ -2187,6 +2444,10 @@ def emit_enum_value(indent, parsed):
|
|||||||
X(f'{indent}\t\t<Comment>{esc_xml_text(parsed["comment"])}</Comment>')
|
X(f'{indent}\t\t<Comment>{esc_xml_text(parsed["comment"])}</Comment>')
|
||||||
else:
|
else:
|
||||||
X(f'{indent}\t\t<Comment/>')
|
X(f'{indent}\t\t<Comment/>')
|
||||||
|
# Цвет значения перечисления — свойство формата 2.21 (8.5), последним в Properties.
|
||||||
|
if is_format_221:
|
||||||
|
color = str(parsed['color']) if parsed.get('color') else 'auto'
|
||||||
|
X(f'{indent}\t\t<Color>{esc_xml_text(color)}</Color>')
|
||||||
X(f'{indent}\t</Properties>')
|
X(f'{indent}\t</Properties>')
|
||||||
X(f'{indent}</EnumValue>')
|
X(f'{indent}</EnumValue>')
|
||||||
|
|
||||||
@@ -2811,6 +3072,12 @@ def emit_common_form_properties(indent):
|
|||||||
X(f'{i}</UsePurposes>')
|
X(f'{i}</UsePurposes>')
|
||||||
else:
|
else:
|
||||||
X(f'{i}<UsePurposes/>')
|
X(f'{i}<UsePurposes/>')
|
||||||
|
# Использование в режиме совместимости интерфейса — свойство формата 2.21 (8.5),
|
||||||
|
# между UsePurposes и UseStandardCommands.
|
||||||
|
if is_format_221:
|
||||||
|
X(f'{i}<UseInInterfaceCompatibilityMode>'
|
||||||
|
f'{get_enum_prop("UseInInterfaceCompatibilityMode", "useInInterfaceCompatibilityMode", "Any")}'
|
||||||
|
f'</UseInInterfaceCompatibilityMode>')
|
||||||
use_std_cmds = 'true' if get_bool_prop('useStandardCommands', False) else 'false'
|
use_std_cmds = 'true' if get_bool_prop('useStandardCommands', False) else 'false'
|
||||||
X(f'{i}<UseStandardCommands>{use_std_cmds}</UseStandardCommands>')
|
X(f'{i}<UseStandardCommands>{use_std_cmds}</UseStandardCommands>')
|
||||||
emit_mltext(i, 'ExtendedPresentation', defn.get('extendedPresentation'))
|
emit_mltext(i, 'ExtendedPresentation', defn.get('extendedPresentation'))
|
||||||
@@ -3069,7 +3336,12 @@ def emit_scheduled_job_properties(indent):
|
|||||||
else:
|
else:
|
||||||
X(f'{i}<Description/>')
|
X(f'{i}<Description/>')
|
||||||
key = str(defn['key']) if defn.get('key') else ''
|
key = str(defn['key']) if defn.get('key') else ''
|
||||||
X(f'{i}<Key>{esc_xml_text(key)}</Key>')
|
# Пустое значение → самозакрывающийся, как у <Description> выше: Конфигуратор
|
||||||
|
# не пишет пустых пар.
|
||||||
|
if key:
|
||||||
|
X(f'{i}<Key>{esc_xml_text(key)}</Key>')
|
||||||
|
else:
|
||||||
|
X(f'{i}<Key/>')
|
||||||
use = 'true' if defn.get('use') is True else 'false'
|
use = 'true' if defn.get('use') is True else 'false'
|
||||||
X(f'{i}<Use>{use}</Use>')
|
X(f'{i}<Use>{use}</Use>')
|
||||||
predefined = 'true' if defn.get('predefined') is True else 'false'
|
predefined = 'true' if defn.get('predefined') is True else 'false'
|
||||||
@@ -3123,6 +3395,9 @@ def emit_report_properties(indent):
|
|||||||
emit_verbatim_ref(i, 'DefaultSettingsForm', defn.get('defaultSettingsForm'))
|
emit_verbatim_ref(i, 'DefaultSettingsForm', defn.get('defaultSettingsForm'))
|
||||||
emit_verbatim_ref(i, 'AuxiliarySettingsForm', defn.get('auxiliarySettingsForm'))
|
emit_verbatim_ref(i, 'AuxiliarySettingsForm', defn.get('auxiliarySettingsForm'))
|
||||||
emit_verbatim_ref(i, 'DefaultVariantForm', defn.get('defaultVariantForm'))
|
emit_verbatim_ref(i, 'DefaultVariantForm', defn.get('defaultVariantForm'))
|
||||||
|
# Вспомогательная форма варианта отчёта — свойство формата 2.21 (8.5).
|
||||||
|
if is_format_221:
|
||||||
|
emit_verbatim_ref(i, 'AuxiliaryVariantForm', defn.get('auxiliaryVariantForm'))
|
||||||
emit_verbatim_ref(i, 'VariantsStorage', defn.get('variantsStorage'))
|
emit_verbatim_ref(i, 'VariantsStorage', defn.get('variantsStorage'))
|
||||||
emit_verbatim_ref(i, 'SettingsStorage', defn.get('settingsStorage'))
|
emit_verbatim_ref(i, 'SettingsStorage', defn.get('settingsStorage'))
|
||||||
incl_help = 'true' if get_bool_prop('includeHelpInContents', False) else 'false'
|
incl_help = 'true' if get_bool_prop('includeHelpInContents', False) else 'false'
|
||||||
@@ -3760,7 +4035,8 @@ def emit_web_service_properties(indent):
|
|||||||
emit_mltext(i, 'Synonym', synonym)
|
emit_mltext(i, 'Synonym', synonym)
|
||||||
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>' if defn.get('comment') else f'{i}<Comment/>')
|
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>' if defn.get('comment') else f'{i}<Comment/>')
|
||||||
namespace = str(defn['namespace']) if defn.get('namespace') else ''
|
namespace = str(defn['namespace']) if defn.get('namespace') else ''
|
||||||
X(f'{i}<Namespace>{esc_xml_text(namespace)}</Namespace>')
|
# Пустое значение → самозакрывающийся, как у <Comment> выше.
|
||||||
|
X(f'{i}<Namespace>{esc_xml_text(namespace)}</Namespace>' if namespace else f'{i}<Namespace/>')
|
||||||
# XDTOPackages — СПИСОК элементов: ссылка на пакет конфигурации (xr:MDObjectRef) либо URI
|
# XDTOPackages — СПИСОК элементов: ссылка на пакет конфигурации (xr:MDObjectRef) либо URI
|
||||||
# внешнего пространства имён (xs:string). Presentation пуст, CheckState 0 (корпус: 19/19).
|
# внешнего пространства имён (xs:string). Presentation пуст, CheckState 0 (корпус: 19/19).
|
||||||
pkgs = defn.get('xdtoPackages') or []
|
pkgs = defn.get('xdtoPackages') or []
|
||||||
@@ -3982,6 +4258,16 @@ xmlns_decl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8
|
|||||||
|
|
||||||
def detect_format_version(d):
|
def detect_format_version(d):
|
||||||
while d:
|
while d:
|
||||||
|
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||||
|
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||||
|
ext_path = d + ".xml"
|
||||||
|
if os.path.isfile(ext_path):
|
||||||
|
with open(ext_path, "r", encoding="utf-8-sig") as f:
|
||||||
|
ext_head = f.read(2000)
|
||||||
|
if re.search(r'<(ExternalDataProcessor|ExternalReport)[ >]', ext_head):
|
||||||
|
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', ext_head)
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
cfg_path = os.path.join(d, "Configuration.xml")
|
cfg_path = os.path.join(d, "Configuration.xml")
|
||||||
if os.path.isfile(cfg_path):
|
if os.path.isfile(cfg_path):
|
||||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||||
@@ -4034,6 +4320,15 @@ compat_mode = detect_compatibility_mode(output_dir)
|
|||||||
# глушил бы TypeReductionMode, который платформа этих версий пишет, и роундтрип бы разъезжался.
|
# глушил бы TypeReductionMode, который платформа этих версий пишет, и роундтрип бы разъезжался.
|
||||||
is_format_218 = format_rank(format_version) >= 218
|
is_format_218 = format_rank(format_version) >= 218
|
||||||
is_format_220 = format_rank(format_version) >= 220
|
is_format_220 = format_rank(format_version) >= 220
|
||||||
|
is_format_221 = format_rank(format_version) >= 221
|
||||||
|
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
|
||||||
|
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту.
|
||||||
|
# Только для шапок MetaDataObject и Form — в файлах с корнем extrnprops
|
||||||
|
# (Ext/ClientApplicationInterface.xml и т.п.) платформа его не пишет.
|
||||||
|
if is_format_221:
|
||||||
|
xmlns_decl = xmlns_decl.replace(
|
||||||
|
' xmlns:style=',
|
||||||
|
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
|
||||||
# Дефолт длины номера строки ТЧ: с режима 8.3.27 платформа заводит новые ТЧ с 9 разрядами.
|
# Дефолт длины номера строки ТЧ: с режима 8.3.27 платформа заводит новые ТЧ с 9 разрядами.
|
||||||
line_number_length_default = 9 if compat_mode_rank(compat_mode) >= 80327 else 5
|
line_number_length_default = 9 if compat_mode_rank(compat_mode) >= 80327 else 5
|
||||||
|
|
||||||
@@ -4174,7 +4469,7 @@ if obj_type in types_with_attr_ts:
|
|||||||
elif obj_type in ('DataProcessor', 'Report'):
|
elif obj_type in ('DataProcessor', 'Report'):
|
||||||
context = 'processor'
|
context = 'processor'
|
||||||
elif obj_type == 'ChartOfCharacteristicTypes':
|
elif obj_type == 'ChartOfCharacteristicTypes':
|
||||||
context = 'catalog' # реквизиты ПВХ структурно как у справочника (Use/FillFromFillingValue/DataHistory)
|
context = 'cct' # как catalog (Use/FillFromFillingValue/DataHistory), но Use ПОСЛЕ Indexing
|
||||||
elif obj_type in ('ChartOfAccounts', 'ChartOfCalculationTypes'):
|
elif obj_type in ('ChartOfAccounts', 'ChartOfCalculationTypes'):
|
||||||
context = 'account' # как catalog, но БЕЗ <Use> (реквизиты ПС/ПВР не иерархичны как справочник)
|
context = 'account' # как catalog, но БЕЗ <Use> (реквизиты ПС/ПВР не иерархичны как справочник)
|
||||||
else:
|
else:
|
||||||
@@ -4245,18 +4540,27 @@ if obj_type in ('InformationRegister', 'AccumulationRegister', 'AccountingRegist
|
|||||||
# Все семейства регистров: ресурсы/измерения — через богатый emit_attribute (общий слой object-свойств).
|
# Все семейства регистров: ресурсы/измерения — через богатый emit_attribute (общий слой object-свойств).
|
||||||
dim_res_ctx = {'InformationRegister': 'register-info', 'AccumulationRegister': 'register-accum',
|
dim_res_ctx = {'InformationRegister': 'register-info', 'AccumulationRegister': 'register-accum',
|
||||||
'CalculationRegister': 'register-calc', 'AccountingRegister': 'register-account'}.get(obj_type)
|
'CalculationRegister': 'register-calc', 'AccountingRegister': 'register-account'}.get(obj_type)
|
||||||
for r in resources:
|
# Порядок видов детей — канон выгрузки, снят с корпуса (acc+erp, разброса внутри
|
||||||
if dim_res_ctx:
|
# типа нет): у большинства регистров Resource, Attribute, Dimension, а у
|
||||||
emit_attribute('\t\t\t', r, dim_res_ctx, 'Resource')
|
# бухгалтерского — Dimension, Resource, Attribute. Команды у платформы идут
|
||||||
|
# последними, как и здесь.
|
||||||
|
kind_order = ['dim', 'res', 'attr'] if obj_type == 'AccountingRegister' else ['res', 'attr', 'dim']
|
||||||
|
for kind in kind_order:
|
||||||
|
if kind == 'res':
|
||||||
|
for r in resources:
|
||||||
|
if dim_res_ctx:
|
||||||
|
emit_attribute('\t\t\t', r, dim_res_ctx, 'Resource')
|
||||||
|
else:
|
||||||
|
emit_resource('\t\t\t', r, obj_type)
|
||||||
|
elif kind == 'dim':
|
||||||
|
for d in dims:
|
||||||
|
if dim_res_ctx:
|
||||||
|
emit_attribute('\t\t\t', d, dim_res_ctx, 'Dimension')
|
||||||
|
else:
|
||||||
|
emit_dimension('\t\t\t', d, obj_type)
|
||||||
else:
|
else:
|
||||||
emit_resource('\t\t\t', r, obj_type)
|
for a in reg_attrs:
|
||||||
for d in dims:
|
emit_attribute('\t\t\t', a, reg_ctx)
|
||||||
if dim_res_ctx:
|
|
||||||
emit_attribute('\t\t\t', d, dim_res_ctx, 'Dimension')
|
|
||||||
else:
|
|
||||||
emit_dimension('\t\t\t', d, obj_type)
|
|
||||||
for a in reg_attrs:
|
|
||||||
emit_attribute('\t\t\t', a, reg_ctx)
|
|
||||||
for cmd in reg_commands:
|
for cmd in reg_commands:
|
||||||
emit_command('\t\t\t', cmd['name'], cmd['def'])
|
emit_command('\t\t\t', cmd['name'], cmd['def'])
|
||||||
X('\t\t</ChildObjects>')
|
X('\t\t</ChildObjects>')
|
||||||
@@ -4360,7 +4664,7 @@ if obj_type == 'WebService':
|
|||||||
X(f'\t</{obj_type}>')
|
X(f'\t</{obj_type}>')
|
||||||
X('</MetaDataObject>')
|
X('</MetaDataObject>')
|
||||||
|
|
||||||
metadata_xml = '\n'.join(lines) + '\n'
|
metadata_xml = '\r\n'.join(lines)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 16. Write files
|
# 16. Write files
|
||||||
@@ -4422,7 +4726,7 @@ os.makedirs(type_dir, exist_ok=True)
|
|||||||
if obj_type not in types_no_sub_dir:
|
if obj_type not in types_no_sub_dir:
|
||||||
os.makedirs(obj_sub_dir, exist_ok=True)
|
os.makedirs(obj_sub_dir, exist_ok=True)
|
||||||
|
|
||||||
write_utf8_bom(main_xml_path, metadata_xml)
|
write_xml_file_keep_eol(main_xml_path, metadata_xml)
|
||||||
|
|
||||||
# Module files
|
# Module files
|
||||||
modules_created = []
|
modules_created = []
|
||||||
@@ -4498,10 +4802,14 @@ if obj_type == 'CommonForm':
|
|||||||
'xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" '
|
'xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" '
|
||||||
'xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" '
|
'xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" '
|
||||||
'xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"')
|
'xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"')
|
||||||
cf_form_xml = ('<?xml version="1.0" encoding="UTF-8"?>\n<Form ' + cf_ns + ' version="' + format_version + '">\n'
|
# Шапка Form на 2.21 тоже несёт палитру — см. комментарий у xmlns_decl.
|
||||||
'\t<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">\n\t\t<Autofill>true</Autofill>\n\t</AutoCommandBar>\n'
|
if is_format_221:
|
||||||
'\t<ChildItems/>\n</Form>\n')
|
cf_ns = cf_ns.replace(' xmlns:style=',
|
||||||
write_utf8_bom(cf_form_xml_path, cf_form_xml)
|
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
|
||||||
|
cf_form_xml = ('<?xml version="1.0" encoding="UTF-8"?>\r\n<Form ' + cf_ns + ' version="' + format_version + '">\r\n'
|
||||||
|
'\t<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">\r\n\t\t<Autofill>true</Autofill>\r\n\t</AutoCommandBar>\r\n'
|
||||||
|
'\t<ChildItems/>\r\n</Form>\r\n')
|
||||||
|
write_xml_file_keep_eol(cf_form_xml_path, cf_form_xml)
|
||||||
modules_created.append(cf_form_xml_path)
|
modules_created.append(cf_form_xml_path)
|
||||||
cf_module_dir = os.path.join(ext_dir, 'Form')
|
cf_module_dir = os.path.join(ext_dir, 'Form')
|
||||||
os.makedirs(cf_module_dir, exist_ok=True)
|
os.makedirs(cf_module_dir, exist_ok=True)
|
||||||
@@ -4589,10 +4897,18 @@ def emit_predef_item(out, val, indent, code_type):
|
|||||||
def build_predefined_xml(items, xsi_type, code_type):
|
def build_predefined_xml(items, xsi_type, code_type):
|
||||||
out = ['<?xml version="1.0" encoding="UTF-8"?>']
|
out = ['<?xml version="1.0" encoding="UTF-8"?>']
|
||||||
out.append(f'<PredefinedData xmlns="http://v8.1c.ru/8.3/xcf/predef" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="{xsi_type}" version="{format_version}">')
|
out.append(f'<PredefinedData xmlns="http://v8.1c.ru/8.3/xcf/predef" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="{xsi_type}" version="{format_version}">')
|
||||||
for it in items:
|
# Шапка Predefined.xml не объявляет cfg (predef/v8/xr/xs/xsi) — на время сборки этого
|
||||||
emit_predef_item(out, it, '\t', code_type)
|
# файла ссылочный тип уходит на локальную форму, как делает и платформа.
|
||||||
|
global cfg_prefix
|
||||||
|
saved_cfg_prefix = cfg_prefix
|
||||||
|
cfg_prefix = None
|
||||||
|
try:
|
||||||
|
for it in items:
|
||||||
|
emit_predef_item(out, it, '\t', code_type)
|
||||||
|
finally:
|
||||||
|
cfg_prefix = saved_cfg_prefix
|
||||||
out.append('</PredefinedData>')
|
out.append('</PredefinedData>')
|
||||||
return '\n'.join(out) + '\n'
|
return '\r\n'.join(out)
|
||||||
|
|
||||||
# --- Предопределённые СЧЕТА Плана счетов (отдельная грамматика: AccountType/OffBalance/Order/AccountingFlags/
|
# --- Предопределённые СЧЕТА Плана счетов (отдельная грамматика: AccountType/OffBalance/Order/AccountingFlags/
|
||||||
# ExtDimensionTypes/ChildItems). Флаги перечисляем по def-порядку признаков плана; в DSL — только TRUE. ---
|
# ExtDimensionTypes/ChildItems). Флаги перечисляем по def-порядку признаков плана; в DSL — только TRUE. ---
|
||||||
@@ -4687,10 +5003,17 @@ def emit_predef_account(out, val, indent, obj_nm, acct_flag_names, ext_dim_flag_
|
|||||||
def build_predefined_account_xml(items, obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref=''):
|
def build_predefined_account_xml(items, obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref=''):
|
||||||
out = ['<?xml version="1.0" encoding="UTF-8"?>']
|
out = ['<?xml version="1.0" encoding="UTF-8"?>']
|
||||||
out.append(f'<PredefinedData xmlns="http://v8.1c.ru/8.3/xcf/predef" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ChartOfAccountsPredefinedItems" version="{format_version}">')
|
out.append(f'<PredefinedData xmlns="http://v8.1c.ru/8.3/xcf/predef" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ChartOfAccountsPredefinedItems" version="{format_version}">')
|
||||||
for it in items:
|
# См. build_predefined_xml: шапка этого файла cfg не объявляет.
|
||||||
emit_predef_account(out, it, '\t', obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref)
|
global cfg_prefix
|
||||||
|
saved_cfg_prefix = cfg_prefix
|
||||||
|
cfg_prefix = None
|
||||||
|
try:
|
||||||
|
for it in items:
|
||||||
|
emit_predef_account(out, it, '\t', obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref)
|
||||||
|
finally:
|
||||||
|
cfg_prefix = saved_cfg_prefix
|
||||||
out.append('</PredefinedData>')
|
out.append('</PredefinedData>')
|
||||||
return '\n'.join(out) + '\n'
|
return '\r\n'.join(out)
|
||||||
|
|
||||||
# Предопределённые ВИДЫ РАСЧЁТА (плоские: Name/Code/Description/ActionPeriodIsBase).
|
# Предопределённые ВИДЫ РАСЧЁТА (плоские: Name/Code/Description/ActionPeriodIsBase).
|
||||||
def emit_predef_calc_type(out, val, indent):
|
def emit_predef_calc_type(out, val, indent):
|
||||||
@@ -4712,7 +5035,7 @@ def build_predefined_calc_type_xml(items):
|
|||||||
for it in items:
|
for it in items:
|
||||||
emit_predef_calc_type(out, it, '\t')
|
emit_predef_calc_type(out, it, '\t')
|
||||||
out.append('</PredefinedData>')
|
out.append('</PredefinedData>')
|
||||||
return '\n'.join(out) + '\n'
|
return '\r\n'.join(out)
|
||||||
|
|
||||||
# Special files
|
# Special files
|
||||||
# --- Состав плана обмена (ExchangePlan, Ext/Content.xml). Ключ `content`/`Состав`:
|
# --- Состав плана обмена (ExchangePlan, Ext/Content.xml). Ключ `content`/`Состав`:
|
||||||
@@ -4766,7 +5089,7 @@ if obj_type == 'ExchangePlan':
|
|||||||
parts.append(f'\t\t<AutoRecord>{it["autoRecord"]}</AutoRecord>\r\n')
|
parts.append(f'\t\t<AutoRecord>{it["autoRecord"]}</AutoRecord>\r\n')
|
||||||
parts.append('\t</Item>\r\n')
|
parts.append('\t</Item>\r\n')
|
||||||
parts.append('</ExchangePlanContent>\r\n')
|
parts.append('</ExchangePlanContent>\r\n')
|
||||||
write_utf8_bom(content_path, ''.join(parts))
|
write_xml_file_keep_eol(content_path, ''.join(parts))
|
||||||
modules_created.append(content_path)
|
modules_created.append(content_path)
|
||||||
elif not os.path.isfile(content_path):
|
elif not os.path.isfile(content_path):
|
||||||
# При пустом составе платформа всё равно пишет пустой <ExchangePlanContent/> — проверено на
|
# При пустом составе платформа всё равно пишет пустой <ExchangePlanContent/> — проверено на
|
||||||
@@ -4774,7 +5097,7 @@ if obj_type == 'ExchangePlan':
|
|||||||
# (Единственный план обмена БЕЗ файла найден в УТ — хвостовая аномалия конфигурации, не правило.)
|
# (Единственный план обмена БЕЗ файла найден в УТ — хвостовая аномалия конфигурации, не правило.)
|
||||||
ensure_ext_dir()
|
ensure_ext_dir()
|
||||||
content_xml = f'<?xml version="1.0" encoding="UTF-8"?>\r\n<ExchangePlanContent {xep_ns} version="{format_version}"/>\r\n'
|
content_xml = f'<?xml version="1.0" encoding="UTF-8"?>\r\n<ExchangePlanContent {xep_ns} version="{format_version}"/>\r\n'
|
||||||
write_utf8_bom(content_path, content_xml)
|
write_xml_file_keep_eol(content_path, content_xml)
|
||||||
modules_created.append(content_path)
|
modules_created.append(content_path)
|
||||||
|
|
||||||
if obj_type == 'BusinessProcess':
|
if obj_type == 'BusinessProcess':
|
||||||
@@ -4782,7 +5105,7 @@ if obj_type == 'BusinessProcess':
|
|||||||
if not os.path.isfile(flowchart_path):
|
if not os.path.isfile(flowchart_path):
|
||||||
ensure_ext_dir()
|
ensure_ext_dir()
|
||||||
flowchart_xml = f'<?xml version="1.0" encoding="UTF-8"?>\r\n<Flowchart xmlns="http://v8.1c.ru/8.3/MDClasses" version="{format_version}"/>\r\n'
|
flowchart_xml = f'<?xml version="1.0" encoding="UTF-8"?>\r\n<Flowchart xmlns="http://v8.1c.ru/8.3/MDClasses" version="{format_version}"/>\r\n'
|
||||||
write_utf8_bom(flowchart_path, flowchart_xml)
|
write_xml_file_keep_eol(flowchart_path, flowchart_xml)
|
||||||
modules_created.append(flowchart_path)
|
modules_created.append(flowchart_path)
|
||||||
|
|
||||||
# Предопределённые элементы (Ext/Predefined.xml). Root-элемент по типу.
|
# Предопределённые элементы (Ext/Predefined.xml). Root-элемент по типу.
|
||||||
@@ -4795,20 +5118,20 @@ if obj_type == 'ChartOfAccounts' and defn.get('predefined'):
|
|||||||
edt_ref = resolve_type_prefix_syn(str(defn['extDimensionTypes'])) if defn.get('extDimensionTypes') else ''
|
edt_ref = resolve_type_prefix_syn(str(defn['extDimensionTypes'])) if defn.get('extDimensionTypes') else ''
|
||||||
predef_xml = build_predefined_account_xml(defn['predefined'], obj_name, af_names, edf_names, edt_ref)
|
predef_xml = build_predefined_account_xml(defn['predefined'], obj_name, af_names, edf_names, edt_ref)
|
||||||
predef_path = os.path.join(ext_dir, 'Predefined.xml')
|
predef_path = os.path.join(ext_dir, 'Predefined.xml')
|
||||||
write_utf8_bom(predef_path, predef_xml)
|
write_xml_file_keep_eol(predef_path, predef_xml)
|
||||||
modules_created.append(predef_path)
|
modules_created.append(predef_path)
|
||||||
elif obj_type == 'ChartOfCalculationTypes' and defn.get('predefined'):
|
elif obj_type == 'ChartOfCalculationTypes' and defn.get('predefined'):
|
||||||
ensure_ext_dir()
|
ensure_ext_dir()
|
||||||
predef_xml = build_predefined_calc_type_xml(defn['predefined'])
|
predef_xml = build_predefined_calc_type_xml(defn['predefined'])
|
||||||
predef_path = os.path.join(ext_dir, 'Predefined.xml')
|
predef_path = os.path.join(ext_dir, 'Predefined.xml')
|
||||||
write_utf8_bom(predef_path, predef_xml)
|
write_xml_file_keep_eol(predef_path, predef_xml)
|
||||||
modules_created.append(predef_path)
|
modules_created.append(predef_path)
|
||||||
elif obj_type in predef_root_by_type and defn.get('predefined'):
|
elif obj_type in predef_root_by_type and defn.get('predefined'):
|
||||||
ensure_ext_dir()
|
ensure_ext_dir()
|
||||||
cat_code_type = str(defn['codeType']) if defn.get('codeType') else 'String'
|
cat_code_type = str(defn['codeType']) if defn.get('codeType') else 'String'
|
||||||
predef_xml = build_predefined_xml(defn['predefined'], predef_root_by_type[obj_type], cat_code_type)
|
predef_xml = build_predefined_xml(defn['predefined'], predef_root_by_type[obj_type], cat_code_type)
|
||||||
predef_path = os.path.join(ext_dir, 'Predefined.xml')
|
predef_path = os.path.join(ext_dir, 'Predefined.xml')
|
||||||
write_utf8_bom(predef_path, predef_xml)
|
write_xml_file_keep_eol(predef_path, predef_xml)
|
||||||
modules_created.append(predef_path)
|
modules_created.append(predef_path)
|
||||||
|
|
||||||
# Модули команд (Commands/<Имя>/Ext/CommandModule.bsl) — заготовка обработчика.
|
# Модули команд (Commands/<Имя>/Ext/CommandModule.bsl) — заготовка обработчика.
|
||||||
@@ -4949,6 +5272,8 @@ elif reg_result == 'no-config':
|
|||||||
print(f' Configuration.xml: not found at {config_xml_path} (register manually)')
|
print(f' Configuration.xml: not found at {config_xml_path} (register manually)')
|
||||||
|
|
||||||
# Cross-reference hints
|
# Cross-reference hints
|
||||||
|
if std_attr_tail_hint:
|
||||||
|
print(f'[HINT] {std_attr_tail_hint}')
|
||||||
if obj_type == 'AccountingRegister' and not defn.get('chartOfAccounts'):
|
if obj_type == 'AccountingRegister' and not defn.get('chartOfAccounts'):
|
||||||
print('[HINT] AccountingRegister requires ChartOfAccounts reference:')
|
print('[HINT] AccountingRegister requires ChartOfAccounts reference:')
|
||||||
print(' /meta-edit -Operation modify-property -Value "ChartOfAccounts=ChartOfAccounts.XXX"')
|
print(' /meta-edit -Operation modify-property -Value "ChartOfAccounts=ChartOfAccounts.XXX"')
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# meta-decompile v0.63 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
|
# meta-decompile v0.64 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
#
|
#
|
||||||
# Поддержаны: Catalog, ExchangePlan, ChartOfCharacteristicTypes, ChartOfAccounts, ChartOfCalculationTypes, Document,
|
# Поддержаны: Catalog, ExchangePlan, ChartOfCharacteristicTypes, ChartOfAccounts, ChartOfCalculationTypes, Document,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# meta-decompile v0.63 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
|
# meta-decompile v0.64 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
#
|
#
|
||||||
# Зеркало meta-decompile.ps1 (КАНОН). Структура 1:1 — те же имена функций, порядок, комментарии.
|
# Зеркало meta-decompile.ps1 (КАНОН). Структура 1:1 — те же имена функций, порядок, комментарии.
|
||||||
@@ -20,6 +20,28 @@ from lxml import etree
|
|||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
# --- Namespaces (зеркало XmlNamespaceManager) ---
|
# --- Namespaces (зеркало XmlNamespaceManager) ---
|
||||||
NS_MD = "http://v8.1c.ru/8.3/MDClasses"
|
NS_MD = "http://v8.1c.ru/8.3/MDClasses"
|
||||||
NS_V8 = "http://v8.1c.ru/8.1/data/core"
|
NS_V8 = "http://v8.1c.ru/8.1/data/core"
|
||||||
@@ -2099,7 +2121,7 @@ def main():
|
|||||||
parser = argparse.ArgumentParser(description='Decompile 1C metadata object XML to JSON DSL (draft)', allow_abbrev=False)
|
parser = argparse.ArgumentParser(description='Decompile 1C metadata object XML to JSON DSL (draft)', allow_abbrev=False)
|
||||||
parser.add_argument('-ObjectPath', '-Path', dest='ObjectPath', type=str, required=True)
|
parser.add_argument('-ObjectPath', '-Path', dest='ObjectPath', type=str, required=True)
|
||||||
parser.add_argument('-OutputPath', dest='OutputPath', type=str, default=None)
|
parser.add_argument('-OutputPath', dest='OutputPath', type=str, default=None)
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
object_path = args.ObjectPath
|
object_path = args.ObjectPath
|
||||||
if not os.path.exists(object_path):
|
if not os.path.exists(object_path):
|
||||||
|
|||||||
@@ -132,8 +132,7 @@ JSON — строки и/или объекты (для групп с вложе
|
|||||||
|
|
||||||
**Свойства** задавайте по имени свойства 1С (PascalCase, как в конфигураторе): `Indexing`, `FillChecking`,
|
**Свойства** задавайте по имени свойства 1С (PascalCase, как в конфигураторе): `Indexing`, `FillChecking`,
|
||||||
`Use`, `FullTextSearch`, `DataHistory`, `PasswordMode`, `MultiLine`, `Mask`, `CreateOnInput`, `QuickChoice` и др.
|
`Use`, `FullTextSearch`, `DataHistory`, `PasswordMode`, `MultiLine`, `Mask`, `CreateOnInput`, `QuickChoice` и др.
|
||||||
Свойство можно задать, даже если у реквизита оно ещё не выставлено. Опечатка в имени свойства → ошибка
|
Свойство можно задать, даже если у реквизита оно ещё не выставлено.
|
||||||
(правка не теряется молча).
|
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
-Operation modify-attribute -Value "СтароеИмя: name=НовоеИмя, type=Строка(500)"
|
-Operation modify-attribute -Value "СтароеИмя: name=НовоеИмя, type=Строка(500)"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
```
|
```
|
||||||
|
|
||||||
Свойство можно задать, даже если оно ещё не выставлено у объекта (например `FullTextSearch`, `DataHistory`).
|
Свойство можно задать, даже если оно ещё не выставлено у объекта (например `FullTextSearch`, `DataHistory`).
|
||||||
Опечатка в имени свойства → ошибка (правка не теряется молча). Допустимы имена свойств соответствующего типа объекта.
|
Допустимы имена свойств соответствующего типа объекта.
|
||||||
|
|
||||||
### Type — тип значения (Константа, ПВХ)
|
### Type — тип значения (Константа, ПВХ)
|
||||||
|
|
||||||
@@ -21,8 +21,7 @@
|
|||||||
```powershell
|
```powershell
|
||||||
-Operation modify-property -Value "Type=String(100) + Number(15,2) + CatalogRef.Номенклатура"
|
-Operation modify-property -Value "Type=String(100) + Number(15,2) + CatalogRef.Номенклатура"
|
||||||
```
|
```
|
||||||
Структурные свойства (со вложенными узлами) в скалярный текст не превращаются: попытка задать
|
Структурные свойства (со вложенными узлами) через `Ключ=Значение` не задаются — исключение только `Type`.
|
||||||
такое свойство обычным `Ключ=Значение` (кроме `Type`) завершается ошибкой до записи файла.
|
|
||||||
|
|
||||||
## Свойства-списки
|
## Свойства-списки
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# meta-edit v1.24 — Edit existing 1C metadata object XML
|
# meta-edit v1.38 — Edit existing 1C metadata object XML
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[string]$DefinitionFile,
|
[string]$DefinitionFile,
|
||||||
@@ -89,7 +89,7 @@ $script:validEnumValues = @{
|
|||||||
"RegisterRecordsDeletion" = @("AutoDelete","AutoDeleteOnUnpost","AutoDeleteOff")
|
"RegisterRecordsDeletion" = @("AutoDelete","AutoDeleteOnUnpost","AutoDeleteOff")
|
||||||
"RegisterRecordsWritingOnPost" = @("WriteModified","WriteSelected","WriteAll")
|
"RegisterRecordsWritingOnPost" = @("WriteModified","WriteSelected","WriteAll")
|
||||||
"ReturnValuesReuse" = @("DontUse","DuringRequest","DuringSession")
|
"ReturnValuesReuse" = @("DontUse","DuringRequest","DuringSession")
|
||||||
"ReuseSessions" = @("DontUse","AutoUse")
|
"ReuseSessions" = @("DontUse","Use","AutoUse")
|
||||||
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
|
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
|
||||||
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
|
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
|
||||||
}
|
}
|
||||||
@@ -313,6 +313,23 @@ function Info($msg) {
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
$root = $script:xmlDoc.DocumentElement
|
$root = $script:xmlDoc.DocumentElement
|
||||||
|
|
||||||
|
# Префикс пространства current-config, объявленный в КОРНЕ файла (у платформы — cfg).
|
||||||
|
# Ищем по объявлениям корня, а не через GetPrefixOfNamespace: ссылочный тип живёт в
|
||||||
|
# ТЕКСТЕ узла, поэтому XML-слой этот префикс не отслеживает. $null = корень URI не
|
||||||
|
# объявляет → эмиттер остаётся на самодостаточной локальной форме.
|
||||||
|
# Версия формата правимого файла — из его же корня. Нужна эмиттерам: часть свойств
|
||||||
|
# появилась в поздних версиях (напр. <Color> у значения перечисления — в 2.21).
|
||||||
|
$script:formatVersion = $root.GetAttribute("version")
|
||||||
|
if (-not $script:formatVersion) { $script:formatVersion = "2.17" }
|
||||||
|
$script:isFormat221 = ($script:formatVersion -match '^(\d+)\.(\d+)$') -and ([int]$Matches[1] * 100 + [int]$Matches[2]) -ge 221
|
||||||
|
|
||||||
|
$script:cfgUri = 'http://v8.1c.ru/8.1/data/enterprise/current-config'
|
||||||
|
$script:cfgPrefix = $null
|
||||||
|
foreach ($a in $root.Attributes) {
|
||||||
|
if ($a.Prefix -eq 'xmlns' -and $a.Value -eq $script:cfgUri) { $script:cfgPrefix = $a.LocalName; break }
|
||||||
|
}
|
||||||
|
|
||||||
if ($root.LocalName -ne "MetaDataObject") {
|
if ($root.LocalName -ne "MetaDataObject") {
|
||||||
Write-Error "Root element must be MetaDataObject, got: $($root.LocalName)"
|
Write-Error "Root element must be MetaDataObject, got: $($root.LocalName)"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -444,7 +461,20 @@ function Resolve-TypeStr {
|
|||||||
param([string]$typeStr)
|
param([string]$typeStr)
|
||||||
if (-not $typeStr) { return $typeStr }
|
if (-not $typeStr) { return $typeStr }
|
||||||
|
|
||||||
# Parameterized: Number(15,2), Строка(100)
|
# Прощающий ввод: ведущий префикс приходит копипастой из выгрузки. Без срезания он ломает
|
||||||
|
# поиск в словаре — русское имя типа остаётся непереведённым, и платформа отвечает
|
||||||
|
# «Неизвестное имя типа». cfg: снимаем всегда — он однозначно означает текущую конфигурацию.
|
||||||
|
# Сгенерированный dNpM: (в корпусе на этом URI встречаются d4p1, d5p1, d6p1 — имя префикса
|
||||||
|
# платформа выдаёт по порядку объявления) снимаем ТОЛЬКО у ссылочных типов, с точкой:
|
||||||
|
# сам по себе префикс многозначен — в формах d5p1:Chart, d5p1:TextDocument,
|
||||||
|
# d5p1:GeographicalSchema адресуют чужие пространства имён, и там он часть значения.
|
||||||
|
if ($typeStr.StartsWith('cfg:')) {
|
||||||
|
$typeStr = $typeStr.Substring(4)
|
||||||
|
} elseif ($typeStr.Contains('.') -and $typeStr -match '^d\d+p\d+:') {
|
||||||
|
$typeStr = $typeStr.Substring($typeStr.IndexOf(':') + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Параметризованные типы: Number(15,2), Строка(100)
|
||||||
if ($typeStr -match '^([^(]+)\((.+)\)$') {
|
if ($typeStr -match '^([^(]+)\((.+)\)$') {
|
||||||
$baseName = $Matches[1].Trim()
|
$baseName = $Matches[1].Trim()
|
||||||
$params = $Matches[2]
|
$params = $Matches[2]
|
||||||
@@ -453,17 +483,17 @@ function Resolve-TypeStr {
|
|||||||
return $typeStr
|
return $typeStr
|
||||||
}
|
}
|
||||||
|
|
||||||
# Reference: СправочникСсылка.Организации
|
# Ссылочные типы: СправочникСсылка.Организации → CatalogRef.Организации
|
||||||
if ($typeStr.Contains('.')) {
|
if ($typeStr.Contains('.')) {
|
||||||
$dotIdx = $typeStr.IndexOf('.')
|
$dotIdx = $typeStr.IndexOf('.')
|
||||||
$prefix = $typeStr.Substring(0, $dotIdx)
|
$prefix = $typeStr.Substring(0, $dotIdx)
|
||||||
$suffix = $typeStr.Substring($dotIdx)
|
$suffix = $typeStr.Substring($dotIdx) # includes the dot
|
||||||
$resolved = $script:typeSynonyms[$prefix.ToLower()]
|
$resolved = $script:typeSynonyms[$prefix.ToLower()]
|
||||||
if ($resolved) { return "$resolved$suffix" }
|
if ($resolved) { return "$resolved$suffix" }
|
||||||
return $typeStr
|
return $typeStr
|
||||||
}
|
}
|
||||||
|
|
||||||
# Simple
|
# Простое имя
|
||||||
$resolved = $script:typeSynonyms[$typeStr.ToLower()]
|
$resolved = $script:typeSynonyms[$typeStr.ToLower()]
|
||||||
if ($resolved) { return $resolved }
|
if ($resolved) { return $resolved }
|
||||||
return $typeStr
|
return $typeStr
|
||||||
@@ -557,9 +587,19 @@ function Build-TypeContentXml {
|
|||||||
return $sb.ToString().TrimEnd("`r","`n")
|
return $sb.ToString().TrimEnd("`r","`n")
|
||||||
}
|
}
|
||||||
|
|
||||||
# Reference types — use local xmlns declaration for 1C compatibility
|
# Ссылочные типы — префиксом, объявленным в КОРНЕ файла (у платформы это cfg).
|
||||||
|
# Раньше здесь всегда объявлялся локальный xmlns:d5p1 на тот же URI, что уже есть
|
||||||
|
# в шапке: платформа принимала, но при цикле «загрузить в базу → выгрузить»
|
||||||
|
# переписывала каждый ссылочный тип в cfg: — diff-шум на ровном месте.
|
||||||
|
# Если корень URI не объявляет (файл не от платформы), остаёмся на самодостаточной
|
||||||
|
# локальной форме: префикс тут — ТЕКСТ узла, XML-слой про него не знает и сам
|
||||||
|
# объявление не добавит, так что иначе получился бы неразрешимый префикс.
|
||||||
if ($typeStr -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.(.+)$') {
|
if ($typeStr -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.(.+)$') {
|
||||||
$sb.AppendLine("$indent<v8:Type xmlns:d5p1=`"http://v8.1c.ru/8.1/data/enterprise/current-config`">d5p1:$typeStr</v8:Type>") | Out-Null
|
if ($script:cfgPrefix) {
|
||||||
|
$sb.AppendLine("$indent<v8:Type>$($script:cfgPrefix):$typeStr</v8:Type>") | Out-Null
|
||||||
|
} else {
|
||||||
|
$sb.AppendLine("$indent<v8:Type xmlns:d5p1=`"$script:cfgUri`">d5p1:$typeStr</v8:Type>") | Out-Null
|
||||||
|
}
|
||||||
return $sb.ToString().TrimEnd("`r","`n")
|
return $sb.ToString().TrimEnd("`r","`n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1279,6 +1319,9 @@ function Build-EnumValueFragment {
|
|||||||
$sb.AppendLine("$indent`t`t<Name>$(Esc-XmlText $parsed.name)</Name>") | Out-Null
|
$sb.AppendLine("$indent`t`t<Name>$(Esc-XmlText $parsed.name)</Name>") | Out-Null
|
||||||
$sb.AppendLine($(Build-MLTextXml "$indent`t`t" "Synonym" $parsed.synonym)) | Out-Null
|
$sb.AppendLine($(Build-MLTextXml "$indent`t`t" "Synonym" $parsed.synonym)) | Out-Null
|
||||||
$sb.AppendLine("$indent`t`t<Comment/>") | Out-Null
|
$sb.AppendLine("$indent`t`t<Comment/>") | Out-Null
|
||||||
|
# Цвет значения — свойство формата 2.21 (8.5). Без него добавленное значение
|
||||||
|
# отличалось бы от соседних, написанных платформой.
|
||||||
|
if ($script:isFormat221) { $sb.AppendLine("$indent`t`t<Color>auto</Color>") | Out-Null }
|
||||||
$sb.AppendLine("$indent`t</Properties>") | Out-Null
|
$sb.AppendLine("$indent`t</Properties>") | Out-Null
|
||||||
$sb.Append("$indent</EnumValue>") | Out-Null
|
$sb.Append("$indent</EnumValue>") | Out-Null
|
||||||
return $sb.ToString()
|
return $sb.ToString()
|
||||||
@@ -2067,6 +2110,11 @@ function Modify-Properties($propsDef) {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Значение свойства-перечисления приводим к канону (как это делает meta-compile): иначе
|
||||||
|
# в XML уезжает то, что дала модель, и платформа отвергает выгрузку уже при загрузке.
|
||||||
|
# Неизвестное свойство функция пропускает как есть, неизвестное значение — отвергает.
|
||||||
|
$valueStr = Normalize-EnumValue $propName $valueStr
|
||||||
|
|
||||||
$propEl.InnerText = $valueStr
|
$propEl.InnerText = $valueStr
|
||||||
Info "Modified property: $propName = $valueStr"
|
Info "Modified property: $propName = $valueStr"
|
||||||
$script:modifyCount++
|
$script:modifyCount++
|
||||||
@@ -2811,7 +2859,11 @@ function Build-ChoiceParametersXml([string]$indent, $cp) {
|
|||||||
$script:fillBoolTrue = @('true','истина','да')
|
$script:fillBoolTrue = @('true','истина','да')
|
||||||
$script:fillBoolFalse = @('false','ложь','нет')
|
$script:fillBoolFalse = @('false','ложь','нет')
|
||||||
|
|
||||||
function Esc-XmlText([string]$s) { return $s.Replace('&','&').Replace('<','<').Replace('>','>') }
|
function Esc-XmlText {
|
||||||
|
param([string]$s)
|
||||||
|
# Эскейп ТЕКСТА элемента: только & < > — кавычку и апостроф платформа держит сырыми.
|
||||||
|
return $s.Replace('&','&').Replace('<','<').Replace('>','>')
|
||||||
|
}
|
||||||
|
|
||||||
function Get-FillTypeCategory([string]$typeStr) {
|
function Get-FillTypeCategory([string]$typeStr) {
|
||||||
if (-not $typeStr) { return 'String' }
|
if (-not $typeStr) { return 'String' }
|
||||||
@@ -3138,7 +3190,8 @@ function Add-PredefinedItems($items) {
|
|||||||
$hdr = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" xmlns:xr=`"http://v8.1c.ru/8.3/xcf/readable`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" xsi:type=`"$xsiType`" version=`"$version`">`r`n"
|
$hdr = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" xmlns:xr=`"http://v8.1c.ru/8.3/xcf/readable`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" xsi:type=`"$xsiType`" version=`"$version`">`r`n"
|
||||||
$text = "$hdr$itemsXml</PredefinedData>`r`n"
|
$text = "$hdr$itemsXml</PredefinedData>`r`n"
|
||||||
}
|
}
|
||||||
[System.IO.File]::WriteAllText($path, $text, $utf8Bom)
|
# Создаваемый файл — по канону: без перевода строки в конце.
|
||||||
|
[System.IO.File]::WriteAllText($path, $text.TrimEnd("`r", "`n"), $utf8Bom)
|
||||||
$n = @($items).Count
|
$n = @($items).Count
|
||||||
Info "Added $n predefined item(s) → $path"
|
Info "Added $n predefined item(s) → $path"
|
||||||
$script:addCount += $n
|
$script:addCount += $n
|
||||||
@@ -3208,9 +3261,17 @@ if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) {
|
|||||||
$text = $text.Substring(1)
|
$text = $text.Substring(1)
|
||||||
}
|
}
|
||||||
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$text = [regex]::Replace($text, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
|
||||||
# Write with BOM
|
# Write with BOM
|
||||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $resolvedPath) -and ([System.IO.File]::ReadAllText($resolvedPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
[System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom)
|
[System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom)
|
||||||
|
|
||||||
Info "Saved: $resolvedPath"
|
Info "Saved: $resolvedPath"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# meta-edit v1.24 — Edit existing 1C metadata object XML
|
# meta-edit v1.38 — Edit existing 1C metadata object XML
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -11,6 +11,65 @@ import sys
|
|||||||
import uuid
|
import uuid
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
class CIDict(dict):
|
||||||
|
# Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные
|
||||||
|
# реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки
|
||||||
|
# сохраняется — от него зависит порядок эмиссии.
|
||||||
|
def _actual(self, key):
|
||||||
|
if not isinstance(key, str) or dict.__contains__(self, key):
|
||||||
|
return key
|
||||||
|
ci = self.__dict__.get('_ci')
|
||||||
|
if ci is None or len(ci) != len(self):
|
||||||
|
ci = {k.lower(): k for k in self if isinstance(k, str)}
|
||||||
|
self.__dict__['_ci'] = ci
|
||||||
|
return ci.get(key.lower(), key)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return dict.__getitem__(self, self._actual(key))
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return dict.__contains__(self, self._actual(key))
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return dict.get(self, self._actual(key), default)
|
||||||
|
|
||||||
|
def pop(self, key, *default):
|
||||||
|
return dict.pop(self, self._actual(key), *default)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
# запись по ключу, отличающемуся регистром, обновляет существующий, а не плодит дубль
|
||||||
|
dict.__setitem__(self, self._actual(key), value)
|
||||||
|
|
||||||
|
def ci_json(obj):
|
||||||
|
"""Рекурсивно оборачивает разобранный JSON: словари → CIDict, списки обходятся."""
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return CIDict((k, ci_json(v)) for k, v in obj.items())
|
||||||
|
if isinstance(obj, list):
|
||||||
|
return [ci_json(v) for v in obj]
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
||||||
@@ -198,6 +257,12 @@ V8_NS = "http://v8.1c.ru/8.1/data/core"
|
|||||||
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
||||||
XS_NS = "http://www.w3.org/2001/XMLSchema"
|
XS_NS = "http://www.w3.org/2001/XMLSchema"
|
||||||
CFG_NS = "http://v8.1c.ru/8.1/data/enterprise/current-config"
|
CFG_NS = "http://v8.1c.ru/8.1/data/enterprise/current-config"
|
||||||
|
# Версия формата правимого файла — из его же корня. Нужна эмиттерам: часть свойств
|
||||||
|
# появилась в поздних версиях (напр. <Color> у значения перечисления — в 2.21).
|
||||||
|
is_format_221 = False
|
||||||
|
# Префикс current-config, объявленный в КОРНЕ правимого файла (у платформы — cfg).
|
||||||
|
# None = корень его не объявляет → эмиттер ссылочных типов остаётся на локальной форме.
|
||||||
|
cfg_prefix = None
|
||||||
|
|
||||||
NSMAP_WRAPPER = {
|
NSMAP_WRAPPER = {
|
||||||
None: MD_NS,
|
None: MD_NS,
|
||||||
@@ -252,7 +317,8 @@ def localname(el):
|
|||||||
|
|
||||||
|
|
||||||
def esc_xml(s):
|
def esc_xml(s):
|
||||||
return s.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
|
# Эскейп ЗНАЧЕНИЯ АТРИБУТА: & < > и кавычка — внутри "..." литеральная " невалидна.
|
||||||
|
return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -291,6 +357,9 @@ enum_value_aliases = {
|
|||||||
'ИндексироватьСДопУпорядочиванием': 'IndexWithAdditionalOrder',
|
'ИндексироватьСДопУпорядочиванием': 'IndexWithAdditionalOrder',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Словарь ищет ПОЛЬЗОВАТЕЛЬСКИЙ ввод — поиск регистронезависим, как хеш-таблица PS1.
|
||||||
|
enum_value_aliases = CIDict(enum_value_aliases)
|
||||||
|
|
||||||
valid_enum_values = {
|
valid_enum_values = {
|
||||||
'RegisterType': ['Balance', 'Turnovers'],
|
'RegisterType': ['Balance', 'Turnovers'],
|
||||||
'WriteMode': ['Independent', 'RecorderSubordinate'],
|
'WriteMode': ['Independent', 'RecorderSubordinate'],
|
||||||
@@ -311,7 +380,7 @@ valid_enum_values = {
|
|||||||
'RegisterRecordsDeletion': ['AutoDelete', 'AutoDeleteOnUnpost', 'AutoDeleteOff'],
|
'RegisterRecordsDeletion': ['AutoDelete', 'AutoDeleteOnUnpost', 'AutoDeleteOff'],
|
||||||
'RegisterRecordsWritingOnPost': ['WriteModified', 'WriteSelected', 'WriteAll'],
|
'RegisterRecordsWritingOnPost': ['WriteModified', 'WriteSelected', 'WriteAll'],
|
||||||
'ReturnValuesReuse': ['DontUse', 'DuringRequest', 'DuringSession'],
|
'ReturnValuesReuse': ['DontUse', 'DuringRequest', 'DuringSession'],
|
||||||
'ReuseSessions': ['DontUse', 'AutoUse'],
|
'ReuseSessions': ['DontUse', 'Use', 'AutoUse'],
|
||||||
'FillChecking': ['DontCheck', 'ShowError', 'ShowWarning'],
|
'FillChecking': ['DontCheck', 'ShowError', 'ShowWarning'],
|
||||||
'Indexing': ['DontIndex', 'Index', 'IndexWithAdditionalOrder'],
|
'Indexing': ['DontIndex', 'Index', 'IndexWithAdditionalOrder'],
|
||||||
}
|
}
|
||||||
@@ -410,37 +479,48 @@ type_synonyms = {
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
|
|
||||||
|
# Алиас на локальный словарь: тело resolve_type_str ниже — общая реализация,
|
||||||
|
# одинаковая во всех навыках (реестр в tests/skills/check-inline-drift.mjs).
|
||||||
|
TYPE_SYNONYMS = type_synonyms
|
||||||
|
|
||||||
|
|
||||||
def resolve_type_str(type_str):
|
def resolve_type_str(type_str):
|
||||||
if not type_str:
|
if not type_str:
|
||||||
return type_str
|
return type_str
|
||||||
|
# Прощающий ввод: ведущий префикс приходит копипастой из выгрузки. Без срезания он ломает
|
||||||
# Parameterized: Number(15,2), Строка(100)
|
# поиск в словаре — русское имя типа остаётся непереведённым, и платформа отвечает
|
||||||
m = re.match(r"^([^(]+)\((.+)\)$", type_str)
|
# «Неизвестное имя типа». cfg: снимаем всегда — он однозначно означает текущую конфигурацию.
|
||||||
|
# Сгенерированный dNpM: (в корпусе на этом URI встречаются d4p1, d5p1, d6p1 — имя префикса
|
||||||
|
# платформа выдаёт по порядку объявления) снимаем ТОЛЬКО у ссылочных типов, с точкой:
|
||||||
|
# сам по себе префикс многозначен — в формах d5p1:Chart, d5p1:TextDocument,
|
||||||
|
# d5p1:GeographicalSchema адресуют чужие пространства имён, и там он часть значения.
|
||||||
|
if type_str.startswith('cfg:'):
|
||||||
|
type_str = type_str[4:]
|
||||||
|
elif '.' in type_str and re.match(r'^d\d+p\d+:', type_str):
|
||||||
|
type_str = type_str[type_str.index(':') + 1:]
|
||||||
|
# Параметризованные типы: Number(15,2), Строка(100)
|
||||||
|
m = re.match(r'^([^(]+)\((.+)\)$', type_str)
|
||||||
if m:
|
if m:
|
||||||
base_name = m.group(1).strip()
|
base_name = m.group(1).strip()
|
||||||
params = m.group(2)
|
params = m.group(2)
|
||||||
resolved = type_synonyms.get(base_name.lower())
|
resolved = TYPE_SYNONYMS.get(base_name.lower())
|
||||||
if resolved:
|
if resolved:
|
||||||
return f"{resolved}({params})"
|
return f'{resolved}({params})'
|
||||||
return type_str
|
return type_str
|
||||||
|
# Ссылочные типы: СправочникСсылка.Организации -> CatalogRef.Организации
|
||||||
# Reference: СправочникСсылка.Организации
|
if '.' in type_str:
|
||||||
if "." in type_str:
|
dot_idx = type_str.index('.')
|
||||||
dot_idx = type_str.index(".")
|
|
||||||
prefix = type_str[:dot_idx]
|
prefix = type_str[:dot_idx]
|
||||||
suffix = type_str[dot_idx:]
|
suffix = type_str[dot_idx:] # includes the dot
|
||||||
resolved = type_synonyms.get(prefix.lower())
|
resolved = TYPE_SYNONYMS.get(prefix.lower())
|
||||||
if resolved:
|
if resolved:
|
||||||
return f"{resolved}{suffix}"
|
return f'{resolved}{suffix}'
|
||||||
return type_str
|
return type_str
|
||||||
|
# Простое имя
|
||||||
# Simple
|
resolved = TYPE_SYNONYMS.get(type_str.lower())
|
||||||
resolved = type_synonyms.get(type_str.lower())
|
|
||||||
if resolved:
|
if resolved:
|
||||||
return resolved
|
return resolved
|
||||||
return type_str
|
return type_str
|
||||||
|
|
||||||
|
|
||||||
def build_type_content_xml(indent, type_str):
|
def build_type_content_xml(indent, type_str):
|
||||||
if not type_str:
|
if not type_str:
|
||||||
return ""
|
return ""
|
||||||
@@ -525,14 +605,23 @@ def build_type_content_xml(indent, type_str):
|
|||||||
lines.append(f"{indent}<v8:TypeSet>cfg:DefinedType.{dt_name}</v8:TypeSet>")
|
lines.append(f"{indent}<v8:TypeSet>cfg:DefinedType.{dt_name}</v8:TypeSet>")
|
||||||
return "\r\n".join(lines)
|
return "\r\n".join(lines)
|
||||||
|
|
||||||
# Reference types — use local xmlns declaration for 1C compatibility
|
# Ссылочные типы — префиксом, объявленным в КОРНЕ файла (у платформы это cfg).
|
||||||
|
# Раньше здесь всегда объявлялся локальный xmlns:d5p1 на тот же URI, что уже есть
|
||||||
|
# в шапке: платформа принимала, но при цикле «загрузить в базу → выгрузить»
|
||||||
|
# переписывала каждый ссылочный тип в cfg: — diff-шум на ровном месте.
|
||||||
|
# Если корень URI не объявляет (файл не от платформы), остаёмся на самодостаточной
|
||||||
|
# локальной форме: префикс тут — ТЕКСТ узла, XML-слой про него не знает и сам
|
||||||
|
# объявление не добавит, так что иначе получился бы неразрешимый префикс.
|
||||||
m = re.match(
|
m = re.match(
|
||||||
r"^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|"
|
r"^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|"
|
||||||
r"ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.(.+)$",
|
r"ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.(.+)$",
|
||||||
type_str,
|
type_str,
|
||||||
)
|
)
|
||||||
if m:
|
if m:
|
||||||
lines.append(f'{indent}<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:{type_str}</v8:Type>')
|
if cfg_prefix:
|
||||||
|
lines.append(f'{indent}<v8:Type>{cfg_prefix}:{type_str}</v8:Type>')
|
||||||
|
else:
|
||||||
|
lines.append(f'{indent}<v8:Type xmlns:d5p1="{CFG_NS}">d5p1:{type_str}</v8:Type>')
|
||||||
return "\r\n".join(lines)
|
return "\r\n".join(lines)
|
||||||
|
|
||||||
# Fallback
|
# Fallback
|
||||||
@@ -1254,6 +1343,10 @@ def build_enum_value_fragment(parsed, indent):
|
|||||||
lines.append(f"{indent}\t\t<Name>{esc_xml_text(parsed['name'])}</Name>")
|
lines.append(f"{indent}\t\t<Name>{esc_xml_text(parsed['name'])}</Name>")
|
||||||
lines.append(build_mltext_xml(f"{indent}\t\t", "Synonym", parsed["synonym"]))
|
lines.append(build_mltext_xml(f"{indent}\t\t", "Synonym", parsed["synonym"]))
|
||||||
lines.append(f"{indent}\t\t<Comment/>")
|
lines.append(f"{indent}\t\t<Comment/>")
|
||||||
|
# Цвет значения — свойство формата 2.21 (8.5). Без него добавленное значение
|
||||||
|
# отличалось бы от соседних, написанных платформой.
|
||||||
|
if is_format_221:
|
||||||
|
lines.append(f"{indent}\t\t<Color>auto</Color>")
|
||||||
lines.append(f"{indent}\t</Properties>")
|
lines.append(f"{indent}\t</Properties>")
|
||||||
lines.append(f"{indent}</EnumValue>")
|
lines.append(f"{indent}</EnumValue>")
|
||||||
return "\r\n".join(lines)
|
return "\r\n".join(lines)
|
||||||
@@ -1936,6 +2029,11 @@ def modify_properties(props_def):
|
|||||||
print(f"meta-edit: modify-property: свойство '{prop_name}' структурное (содержит дочерние узлы) — установка скалярного текста повредит XML; не поддерживается", file=sys.stderr)
|
print(f"meta-edit: modify-property: свойство '{prop_name}' структурное (содержит дочерние узлы) — установка скалярного текста повредит XML; не поддерживается", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Значение свойства-перечисления приводим к канону (как это делает meta-compile): иначе
|
||||||
|
# в XML уезжает то, что дала модель, и платформа отвергает выгрузку уже при загрузке.
|
||||||
|
# Неизвестное свойство функция пропускает как есть, неизвестное значение — отвергает.
|
||||||
|
value_str = normalize_enum_value(prop_name, value_str)
|
||||||
|
|
||||||
# Set inner text — clear children first, set text
|
# Set inner text — clear children first, set text
|
||||||
for ch in list(prop_el):
|
for ch in list(prop_el):
|
||||||
prop_el.remove(ch)
|
prop_el.remove(ch)
|
||||||
@@ -2962,21 +3060,22 @@ def _detect_xml_style(path):
|
|||||||
|
|
||||||
|
|
||||||
def _finalize_xml_bytes(xml_bytes, style):
|
def _finalize_xml_bytes(xml_bytes, style):
|
||||||
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None)."""
|
"""Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
|
||||||
enc_decl = style["enc"] if style else "utf-8"
|
выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
|
||||||
|
enc_decl = style["enc"] if style else "UTF-8"
|
||||||
xml_bytes = xml_bytes.replace(
|
xml_bytes = xml_bytes.replace(
|
||||||
b"<?xml version='1.0' encoding='UTF-8'?>",
|
b"<?xml version='1.0' encoding='UTF-8'?>",
|
||||||
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
||||||
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
||||||
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
||||||
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
||||||
# Финальный перенос — как в оригинале (новый файл → есть)
|
# Финальный перенос — как в оригинале (новый файл → нет, канон #57)
|
||||||
want_final_nl = style["final_nl"] if style else True
|
want_final_nl = style["final_nl"] if style else False
|
||||||
xml_bytes = xml_bytes.rstrip(b"\n")
|
xml_bytes = xml_bytes.rstrip(b"\n")
|
||||||
if want_final_nl:
|
if want_final_nl:
|
||||||
xml_bytes += b"\n"
|
xml_bytes += b"\n"
|
||||||
# EOL — как в оригинале (новый файл → LF, текущее поведение)
|
# EOL — как в оригинале (новый файл → CRLF, канон #57)
|
||||||
if style and style["crlf"]:
|
if (style["crlf"] if style else True):
|
||||||
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
||||||
return xml_bytes
|
return xml_bytes
|
||||||
|
|
||||||
@@ -2985,13 +3084,9 @@ def save_xml(tree, path):
|
|||||||
"""Save XML tree preserving the existing file's BOM/EOL/encoding-case/final-newline."""
|
"""Save XML tree preserving the existing file's BOM/EOL/encoding-case/final-newline."""
|
||||||
style = _detect_xml_style(path)
|
style = _detect_xml_style(path)
|
||||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||||
# Fix d5p1 namespace declarations stripped by lxml (it treats them as unused
|
# Костыль, возвращавший xmlns:d5p1 (lxml выбрасывал его как неиспользуемый, ведь
|
||||||
# because d5p1: appears only in text content, not in element/attribute names)
|
# префикс встречается только в тексте узла), удалён вместе с переходом на корневой
|
||||||
xml_bytes = re.sub(
|
# cfg: — объявление теперь берётся из шапки самого файла и не требует починки.
|
||||||
b'(<v8:Type)(?! xmlns:d5p1)(>d5p1:)',
|
|
||||||
b'\\1 xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config"\\2',
|
|
||||||
xml_bytes
|
|
||||||
)
|
|
||||||
xml_bytes = _finalize_xml_bytes(xml_bytes, style)
|
xml_bytes = _finalize_xml_bytes(xml_bytes, style)
|
||||||
with open(path, "wb") as f:
|
with open(path, "wb") as f:
|
||||||
if style is None or style["bom"]:
|
if style is None or style["bom"]:
|
||||||
@@ -3094,7 +3189,9 @@ def add_predefined_items(items):
|
|||||||
item_list = items if isinstance(items, list) else [items]
|
item_list = items if isinstance(items, list) else [items]
|
||||||
items_xml = ''.join(build_predef_item_xml('\t', it, code_type) for it in item_list)
|
items_xml = ''.join(build_predef_item_xml('\t', it, code_type) for it in item_list)
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
with open(path, 'r', encoding='utf-8-sig') as f:
|
# newline='' => без трансляции переводов строк: иначе CRLF молча схлопнется
|
||||||
|
# в LF при чтении и файл будет переписан в LF (#44/#46/#47).
|
||||||
|
with open(path, 'r', encoding='utf-8-sig', newline='') as f:
|
||||||
text = f.read()
|
text = f.read()
|
||||||
text = text.replace('</PredefinedData>', items_xml + '</PredefinedData>')
|
text = text.replace('</PredefinedData>', items_xml + '</PredefinedData>')
|
||||||
else:
|
else:
|
||||||
@@ -3103,7 +3200,10 @@ def add_predefined_items(items):
|
|||||||
'xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" '
|
'xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" '
|
||||||
'xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
|
'xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
|
||||||
f'xsi:type="{xsi_type}" version="{version}">\r\n')
|
f'xsi:type="{xsi_type}" version="{version}">\r\n')
|
||||||
text = hdr + items_xml + '</PredefinedData>\r\n'
|
text = hdr + items_xml + '</PredefinedData>'
|
||||||
|
# Без перевода строки в конце — канон #57. Срезаем в ОБЕИХ ветках: файл, созданный
|
||||||
|
# прежней версией навыка, мог унести хвост, а PS-порт срезает безусловно.
|
||||||
|
text = text.rstrip('\r\n')
|
||||||
with open(path, 'wb') as f:
|
with open(path, 'wb') as f:
|
||||||
f.write(b'\xef\xbb\xbf')
|
f.write(b'\xef\xbb\xbf')
|
||||||
f.write(text.encode('utf-8'))
|
f.write(text.encode('utf-8'))
|
||||||
@@ -3141,7 +3241,7 @@ def main():
|
|||||||
parser.add_argument("-Operation", default=None, choices=valid_operations, help="Inline operation")
|
parser.add_argument("-Operation", default=None, choices=valid_operations, help="Inline operation")
|
||||||
parser.add_argument("-Value", default=None, help="Inline value")
|
parser.add_argument("-Value", default=None, help="Inline value")
|
||||||
parser.add_argument("-NoValidate", action="store_true", help="Skip auto-validation")
|
parser.add_argument("-NoValidate", action="store_true", help="Skip auto-validation")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
# --- Mode validation ---
|
# --- Mode validation ---
|
||||||
if args.DefinitionFile and args.Operation:
|
if args.DefinitionFile and args.Operation:
|
||||||
@@ -3155,7 +3255,7 @@ def main():
|
|||||||
if not os.path.exists(args.DefinitionFile):
|
if not os.path.exists(args.DefinitionFile):
|
||||||
die(f"Definition file not found: {args.DefinitionFile}")
|
die(f"Definition file not found: {args.DefinitionFile}")
|
||||||
with open(args.DefinitionFile, "r", encoding="utf-8-sig") as f:
|
with open(args.DefinitionFile, "r", encoding="utf-8-sig") as f:
|
||||||
definition = json.load(f)
|
definition = ci_json(json.load(f))
|
||||||
|
|
||||||
# --- Resolve object path ---
|
# --- Resolve object path ---
|
||||||
object_path = args.ObjectPath
|
object_path = args.ObjectPath
|
||||||
@@ -3192,6 +3292,13 @@ def main():
|
|||||||
xml_tree = etree.parse(resolved_path, xml_parser)
|
xml_tree = etree.parse(resolved_path, xml_parser)
|
||||||
xml_root = xml_tree.getroot()
|
xml_root = xml_tree.getroot()
|
||||||
|
|
||||||
|
# Префикс current-config берём из объявлений корня — им и пишем ссылочные типы.
|
||||||
|
global cfg_prefix, is_format_221
|
||||||
|
cfg_prefix = next((p for p, u in (xml_root.nsmap or {}).items() if u == CFG_NS and p), None)
|
||||||
|
_fv = xml_root.get("version") or "2.17"
|
||||||
|
_m = re.match(r'^(\d+)\.(\d+)$', _fv)
|
||||||
|
is_format_221 = bool(_m) and int(_m.group(1)) * 100 + int(_m.group(2)) >= 221
|
||||||
|
|
||||||
# --- Detect object type ---
|
# --- Detect object type ---
|
||||||
if localname(xml_root) != "MetaDataObject":
|
if localname(xml_root) != "MetaDataObject":
|
||||||
die(f"Root element must be MetaDataObject, got: {localname(xml_root)}")
|
die(f"Root element must be MetaDataObject, got: {localname(xml_root)}")
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/meta-info.ps1" -Obj
|
|||||||
| `brief` | Всё одной-двумя строками: имена полей, счётчики |
|
| `brief` | Всё одной-двумя строками: имена полей, счётчики |
|
||||||
| `full` | Всё раскрыто: колонки ТЧ, список источников подписки, движения, формы |
|
| `full` | Всё раскрыто: колонки ТЧ, список источников подписки, движения, формы |
|
||||||
|
|
||||||
`-Name` — drill-down: раскрыть конкретный элемент объекта (ТЧ, реквизит, шаблон URL, операцию веб-сервиса).
|
`-Name` — drill-down: раскрыть конкретный элемент объекта (ТЧ, реквизит, стандартный реквизит,
|
||||||
|
шаблон URL, операцию веб-сервиса). Составной тип в сводке свёрнут в счётчик — полный список
|
||||||
|
типов даёт drill-down: `-Name ТипЗначения` у ПВХ, `-Name Владелец` у подчинённого справочника.
|
||||||
|
|
||||||
## Поддерживаемые типы (23)
|
## Поддерживаемые типы (23)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# meta-info v1.4 — Compact summary of 1C metadata object
|
# meta-info v1.10 — Compact summary of 1C metadata object (+единое имя хелпера состояния поддержки)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$true)][Alias('Path')][string]$ObjectPath,
|
[Parameter(Mandatory=$true)][Alias('Path')][string]$ObjectPath,
|
||||||
@@ -170,6 +170,22 @@ function Get-MLText($node) {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Тип-множество: голое имя метатипа без `.Имя` означает ВСЕ ссылки этого класса
|
||||||
|
# (см. docs/meta-dsl-spec.md §«Тип-множество»). Конкретный тип всегда пишется с точкой,
|
||||||
|
# поэтому «СправочникСсылка» без точки читается однозначно как обобщённый.
|
||||||
|
function Format-SingleTypeSet([string]$raw) {
|
||||||
|
$raw = $raw -replace '^d\d+p\d+:', 'cfg:'
|
||||||
|
if ($raw -match '^cfg:DefinedType\.(.+)$') { return "ОпределяемыйТип.$($Matches[1])" }
|
||||||
|
if ($raw -match '^cfg:Characteristic\.(.+)$') { return "Характеристика.$($Matches[1])" }
|
||||||
|
if ($raw -eq 'cfg:AnyRef') { return "ЛюбаяСсылка" }
|
||||||
|
if ($raw -eq 'cfg:AnyIBRef') { return "ЛюбаяСсылкаИБ" }
|
||||||
|
if ($raw -match '^cfg:(\w+Ref)$' -and $refTypeMap.ContainsKey($Matches[1])) {
|
||||||
|
return $refTypeMap[$Matches[1]]
|
||||||
|
}
|
||||||
|
if ($raw -match '^cfg:(.+)$') { return $Matches[1] }
|
||||||
|
return $raw
|
||||||
|
}
|
||||||
|
|
||||||
function Format-Type($typeNode) {
|
function Format-Type($typeNode) {
|
||||||
if (-not $typeNode) { return "" }
|
if (-not $typeNode) { return "" }
|
||||||
$types = @()
|
$types = @()
|
||||||
@@ -178,14 +194,7 @@ function Format-Type($typeNode) {
|
|||||||
$types += Format-SingleType $raw $typeNode
|
$types += Format-SingleType $raw $typeNode
|
||||||
}
|
}
|
||||||
foreach ($t in $typeNode.SelectNodes("v8:TypeSet", $ns)) {
|
foreach ($t in $typeNode.SelectNodes("v8:TypeSet", $ns)) {
|
||||||
$raw = $t.InnerText
|
$types += Format-SingleTypeSet $t.InnerText
|
||||||
if ($raw -match '^cfg:DefinedType\.(.+)$') {
|
|
||||||
$types += "ОпределяемыйТип.$($Matches[1])"
|
|
||||||
} elseif ($raw -match '^cfg:Characteristic\.(.+)$') {
|
|
||||||
$types += "Характеристика.$($Matches[1])"
|
|
||||||
} else {
|
|
||||||
$types += $raw
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ($types.Count -eq 0) { return "" }
|
if ($types.Count -eq 0) { return "" }
|
||||||
if ($types.Count -eq 1) { return $types[0] }
|
if ($types.Count -eq 1) { return $types[0] }
|
||||||
@@ -287,6 +296,202 @@ function Format-Flags($propsNode, [bool]$isDimension = $false) {
|
|||||||
return " [$($flags -join ', ')]"
|
return " [$($flags -join ', ')]"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Ссылка на объект метаданных (`Catalog.Валюты` в Owners и подобных) — не тип значения,
|
||||||
|
# но в выводе показываем именно тип, который присваивается: СправочникСсылка.Валюты.
|
||||||
|
function Format-MDObjectRef([string]$raw) {
|
||||||
|
if ($raw -match '^(\w+)\.(.+)$') {
|
||||||
|
$key = "$($Matches[1])Ref"
|
||||||
|
if ($refTypeMap.ContainsKey($key)) { return "$($refTypeMap[$key]).$($Matches[2])" }
|
||||||
|
}
|
||||||
|
return $raw
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Стандартные реквизиты ---
|
||||||
|
|
||||||
|
# Сколько типов состава печатать списком, прежде чем свернуть в счётчик. По корпусу
|
||||||
|
# acc/erp/ut/unf состав ПВХ доходит до 151 типа, при этом 20 из 42 ПВХ укладываются в 5.
|
||||||
|
$script:composedTypeThreshold = 5
|
||||||
|
|
||||||
|
# Имена полей, состав которых свернули в счётчик, — по ним в конце вывода печатается
|
||||||
|
# единственная подсказка, чем состав развернуть.
|
||||||
|
$script:collapsedNames = @()
|
||||||
|
|
||||||
|
# Блок StandardAttributes в XML опционален: платформа материализует его только когда хотя бы
|
||||||
|
# один стандартный реквизит кастомизирован (docs/meta-dsl-spec.md §7.1.1). Когда блока нет,
|
||||||
|
# действуют платформенные дефолты — профиль ниже совпадает с $stdAttrProfile в meta-compile
|
||||||
|
# (выведен из корпуса acc+erp). Правки держать синхронными, иначе навыки разъедутся молча.
|
||||||
|
$stdAttrRequiredDefault = @{
|
||||||
|
"Catalog" = @{ "Owner" = $true; "Description" = $true }
|
||||||
|
"Document" = @{ "Date" = $true }
|
||||||
|
"ExchangePlan" = @{ "Description" = $true; "Code" = $true }
|
||||||
|
"ChartOfAccounts" = @{ "Description" = $true; "Code" = $true }
|
||||||
|
"ChartOfCharacteristicTypes" = @{ "Description" = $true }
|
||||||
|
"ChartOfCalculationTypes" = @{ "Description" = $true }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-StdAttrRequired($propsNode, [string]$attrName, [string]$objType) {
|
||||||
|
$fc = $propsNode.SelectSingleNode("md:StandardAttributes/xr:StandardAttribute[@name='$attrName']/xr:FillChecking", $ns)
|
||||||
|
if ($fc) { return ($fc.InnerText -eq "ShowError") }
|
||||||
|
if ($stdAttrRequiredDefault.ContainsKey($objType) -and $stdAttrRequiredDefault[$objType].ContainsKey($attrName)) {
|
||||||
|
return $stdAttrRequiredDefault[$objType][$attrName]
|
||||||
|
}
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
function New-StdAttr([string]$name, [string]$type, $flags) {
|
||||||
|
$f = @($flags | Where-Object { $_ })
|
||||||
|
$flagStr = if ($f.Count -gt 0) { " [$($f -join ', ')]" } else { "" }
|
||||||
|
return @{ Name = $name; Type = $type; Flags = $flagStr }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Стандартные реквизиты, наличие и характеристики которых задаются настройками объекта, —
|
||||||
|
# их нельзя вывести из остального вывода, поэтому они попадают и в overview. Ссылка,
|
||||||
|
# ПометкаУдаления, Родитель и прочее следуют из типа объекта и строки «Иерархический» —
|
||||||
|
# они только в full (см. Get-StandardAttributesFull).
|
||||||
|
function Get-StandardAttributes($propsNode, [string]$objType, [string]$mode) {
|
||||||
|
$result = @()
|
||||||
|
|
||||||
|
# Владелец — только у подчинённого справочника; состав типов ниоткуда не выводится
|
||||||
|
if ($objType -eq "Catalog") {
|
||||||
|
$ownersNode = $propsNode.SelectSingleNode("md:Owners", $ns)
|
||||||
|
$ownerTypes = @()
|
||||||
|
if ($ownersNode) {
|
||||||
|
foreach ($it in $ownersNode.SelectNodes("xr:Item", $ns)) {
|
||||||
|
$ownerTypes += Format-MDObjectRef $it.InnerText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($ownerTypes.Count -gt 0) {
|
||||||
|
$req = if (Test-StdAttrRequired $propsNode "Owner" $objType) { "обязательный" } else { $null }
|
||||||
|
$result += New-StdAttr "Владелец" ($ownerTypes -join ", ") @($req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ТипЗначения ПВХ — до полутора сотен типов, полный список раздул бы сводку в обоих
|
||||||
|
# режимах, поэтому сворачиваем в счётчик. Состав смотреть через -Name ТипЗначения.
|
||||||
|
if ($objType -eq "ChartOfCharacteristicTypes") {
|
||||||
|
$vt = $propsNode.SelectSingleNode("md:Type", $ns)
|
||||||
|
if ($vt) {
|
||||||
|
$cnt = $vt.SelectNodes("v8:Type", $ns).Count + $vt.SelectNodes("v8:TypeSet", $ns).Count
|
||||||
|
$typeStr = if ($cnt -gt $script:composedTypeThreshold) {
|
||||||
|
$script:collapsedNames += "ТипЗначения"
|
||||||
|
"Составной ($cnt)"
|
||||||
|
} else { Format-Type $vt }
|
||||||
|
if ($typeStr) { $result += New-StdAttr "ТипЗначения" $typeStr @() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Дата — есть всегда, ценна флагом обязательности
|
||||||
|
if ($objType -in @("Document", "BusinessProcess", "Task")) {
|
||||||
|
$req = if (Test-StdAttrRequired $propsNode "Date" $objType) { "обязательный" } else { $null }
|
||||||
|
$result += New-StdAttr "Дата" "Дата" @($req)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Номер — тип и длина задаются объектом, при NumberLength=0 номера нет
|
||||||
|
if ($objType -in @("Document", "BusinessProcess", "Task")) {
|
||||||
|
$numLen = $propsNode.SelectSingleNode("md:NumberLength", $ns)
|
||||||
|
if ($numLen -and [int]$numLen.InnerText -gt 0) {
|
||||||
|
$numType = $propsNode.SelectSingleNode("md:NumberType", $ns)
|
||||||
|
$ntRu = if ($numType -and $numType.InnerText -eq "Number") { "Число" } else { "Строка" }
|
||||||
|
$flags = @()
|
||||||
|
if (Test-StdAttrRequired $propsNode "Number" $objType) { $flags += "обязательный" }
|
||||||
|
$numPer = $propsNode.SelectSingleNode("md:NumberPeriodicity", $ns)
|
||||||
|
if ($numPer -and $numberPeriodMap.ContainsKey($numPer.InnerText)) { $flags += $numberPeriodMap[$numPer.InnerText] }
|
||||||
|
$numAllowed = $propsNode.SelectSingleNode("md:NumberAllowedLength", $ns)
|
||||||
|
if ($numAllowed -and $numAllowed.InnerText -eq "Fixed") { $flags += "фикс. длина" }
|
||||||
|
$autoNum = $propsNode.SelectSingleNode("md:Autonumbering", $ns)
|
||||||
|
if ($autoNum -and $autoNum.InnerText -eq "true") { $flags += "авто" }
|
||||||
|
$result += New-StdAttr "Номер" "$ntRu($($numLen.InnerText))" $flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Код — при CodeLength=0 кода у объекта нет вовсе, строку не печатаем
|
||||||
|
$codeLen = $propsNode.SelectSingleNode("md:CodeLength", $ns)
|
||||||
|
if ($codeLen -and [int]$codeLen.InnerText -gt 0) {
|
||||||
|
$codeType = $propsNode.SelectSingleNode("md:CodeType", $ns)
|
||||||
|
$ctRu = if ($codeType -and $codeType.InnerText -eq "Number") { "Число" } else { "Строка" }
|
||||||
|
$flags = @()
|
||||||
|
if (Test-StdAttrRequired $propsNode "Code" $objType) { $flags += "обязательный" }
|
||||||
|
$codeAllowed = $propsNode.SelectSingleNode("md:CodeAllowedLength", $ns)
|
||||||
|
if ($codeAllowed -and $codeAllowed.InnerText -eq "Fixed") { $flags += "фикс. длина" }
|
||||||
|
$result += New-StdAttr "Код" "$ctRu($($codeLen.InnerText))" $flags
|
||||||
|
}
|
||||||
|
|
||||||
|
# Наименование — при DescriptionLength=0 наименования нет
|
||||||
|
$descLen = $propsNode.SelectSingleNode("md:DescriptionLength", $ns)
|
||||||
|
if ($descLen -and [int]$descLen.InnerText -gt 0) {
|
||||||
|
$req = if (Test-StdAttrRequired $propsNode "Description" $objType) { "обязательный" } else { $null }
|
||||||
|
$result += New-StdAttr "Наименование" "Строка($($descLen.InnerText))" @($req)
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
# Остальные стандартные реквизиты — предсказуемы по типу объекта, но в full полезны
|
||||||
|
# как перечень доступных полей для запроса.
|
||||||
|
function Get-StandardAttributesFull($propsNode, [string]$objType, [string]$objName) {
|
||||||
|
$selfRef = switch ($objType) {
|
||||||
|
"Catalog" { "СправочникСсылка.$objName" }
|
||||||
|
"ChartOfCharacteristicTypes" { "ПВХСсылка.$objName" }
|
||||||
|
"ChartOfAccounts" { "ПланСчетовСсылка.$objName" }
|
||||||
|
"ChartOfCalculationTypes" { "ПВРСсылка.$objName" }
|
||||||
|
"ExchangePlan" { "ПланОбменаСсылка.$objName" }
|
||||||
|
"Document" { "ДокументСсылка.$objName" }
|
||||||
|
"BusinessProcess" { "БизнесПроцессСсылка.$objName" }
|
||||||
|
"Task" { "ЗадачаСсылка.$objName" }
|
||||||
|
default { "" }
|
||||||
|
}
|
||||||
|
$result = @()
|
||||||
|
if ($selfRef) { $result += New-StdAttr "Ссылка" $selfRef @() }
|
||||||
|
$result += New-StdAttr "ПометкаУдаления" "Булево" @()
|
||||||
|
|
||||||
|
# Родитель и ЭтоГруппа существуют только у иерархических объектов
|
||||||
|
$hier = $propsNode.SelectSingleNode("md:Hierarchical", $ns)
|
||||||
|
$isHier = ($hier -and $hier.InnerText -eq "true") -or $objType -eq "ChartOfAccounts"
|
||||||
|
if ($isHier -and $selfRef) {
|
||||||
|
$result += New-StdAttr "Родитель" $selfRef @()
|
||||||
|
$ht = $propsNode.SelectSingleNode("md:HierarchyType", $ns)
|
||||||
|
if ($objType -eq "Catalog" -and (-not $ht -or $ht.InnerText -eq "HierarchyFoldersAndItems")) {
|
||||||
|
$result += New-StdAttr "ЭтоГруппа" "Булево" @()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($objType) {
|
||||||
|
"Document" {
|
||||||
|
$result += New-StdAttr "Проведен" "Булево" @()
|
||||||
|
}
|
||||||
|
"ExchangePlan" {
|
||||||
|
$result += New-StdAttr "ЭтотУзел" "Булево" @()
|
||||||
|
$result += New-StdAttr "НомерОтправленного" "Число" @()
|
||||||
|
$result += New-StdAttr "НомерПринятого" "Число" @()
|
||||||
|
}
|
||||||
|
"BusinessProcess" {
|
||||||
|
$result += New-StdAttr "Стартован" "Булево" @()
|
||||||
|
$result += New-StdAttr "Завершен" "Булево" @()
|
||||||
|
$result += New-StdAttr "ВедущаяЗадача" "ЗадачаСсылка" @()
|
||||||
|
}
|
||||||
|
"Task" {
|
||||||
|
$result += New-StdAttr "Выполнена" "Булево" @()
|
||||||
|
$result += New-StdAttr "БизнесПроцесс" "БизнесПроцессСсылка" @()
|
||||||
|
$result += New-StdAttr "ТочкаМаршрута" "БизнесПроцессТочкаМаршрутаСсылка" @()
|
||||||
|
}
|
||||||
|
"ChartOfAccounts" {
|
||||||
|
$result += New-StdAttr "Вид" "ВидСчета" @()
|
||||||
|
$result += New-StdAttr "Забалансовый" "Булево" @()
|
||||||
|
$result += New-StdAttr "Порядок" "Число" @()
|
||||||
|
}
|
||||||
|
"ChartOfCalculationTypes" {
|
||||||
|
$result += New-StdAttr "ПериодДействияБазовый" "Булево" @()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Предопределённые данные есть у всех перечисленных типов, кроме документов и задач
|
||||||
|
if ($objType -in @("Catalog", "ChartOfCharacteristicTypes", "ChartOfAccounts", "ChartOfCalculationTypes")) {
|
||||||
|
$result += New-StdAttr "Предопределенный" "Булево" @()
|
||||||
|
$result += New-StdAttr "ИмяПредопределенныхДанных" "Строка" @()
|
||||||
|
}
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
function Get-Attributes($parentNode, [string]$childTag = "Attribute", [bool]$isDimension = $false) {
|
function Get-Attributes($parentNode, [string]$childTag = "Attribute", [bool]$isDimension = $false) {
|
||||||
$result = @()
|
$result = @()
|
||||||
foreach ($attr in $parentNode.SelectNodes("md:$childTag", $ns)) {
|
foreach ($attr in $parentNode.SelectNodes("md:$childTag", $ns)) {
|
||||||
@@ -329,7 +534,10 @@ function Get-MaxNameLen($attrs) {
|
|||||||
function Get-SimpleChildren($parentNode, [string]$tag) {
|
function Get-SimpleChildren($parentNode, [string]$tag) {
|
||||||
$result = @()
|
$result = @()
|
||||||
foreach ($child in $parentNode.SelectNodes("md:$tag", $ns)) {
|
foreach ($child in $parentNode.SelectNodes("md:$tag", $ns)) {
|
||||||
$result += $child.InnerText
|
# Form/Template в ChildObjects — простые узлы с именем в тексте, а Command — узел
|
||||||
|
# с вложенным Properties: у него InnerText склеил бы всё содержимое в одну строку.
|
||||||
|
$nameNode = $child.SelectSingleNode("md:Properties/md:Name", $ns)
|
||||||
|
if ($nameNode) { $result += $nameNode.InnerText } else { $result += $child.InnerText }
|
||||||
}
|
}
|
||||||
return $result
|
return $result
|
||||||
}
|
}
|
||||||
@@ -650,6 +858,43 @@ if ($Name -and $childObjs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Не нашли среди дочерних объектов — пробуем стандартные реквизиты ниже
|
||||||
|
}
|
||||||
|
|
||||||
|
# Drill-down по стандартному реквизиту — единственный способ увидеть состав типов целиком,
|
||||||
|
# когда в сводке он свёрнут в счётчик. Живёт в Properties, а не в ChildObjects, поэтому идёт
|
||||||
|
# отдельной веткой и работает даже у объектов без ChildObjects.
|
||||||
|
if ($Name -and -not $drillDone) {
|
||||||
|
$stdAll = @(Get-StandardAttributes $props $mdType "full") + @(Get-StandardAttributesFull $props $mdType $objName)
|
||||||
|
foreach ($s in $stdAll) {
|
||||||
|
if ($s.Name -ne $Name) { continue }
|
||||||
|
Out "Стандартный реквизит: $($s.Name)"
|
||||||
|
|
||||||
|
# Состав типов разворачиваем списком: ради этого drill-down и нужен
|
||||||
|
$typeSrc = $null
|
||||||
|
if ($Name -eq "ТипЗначения") { $typeSrc = $props.SelectSingleNode("md:Type", $ns) }
|
||||||
|
$typeList = @()
|
||||||
|
if ($typeSrc) {
|
||||||
|
foreach ($t in $typeSrc.SelectNodes("v8:Type", $ns)) { $typeList += Format-SingleType $t.InnerText $typeSrc }
|
||||||
|
foreach ($t in $typeSrc.SelectNodes("v8:TypeSet", $ns)) { $typeList += Format-SingleTypeSet $t.InnerText }
|
||||||
|
} elseif ($Name -eq "Владелец") {
|
||||||
|
$ownersNode = $props.SelectSingleNode("md:Owners", $ns)
|
||||||
|
if ($ownersNode) {
|
||||||
|
foreach ($it in $ownersNode.SelectNodes("xr:Item", $ns)) { $typeList += Format-MDObjectRef $it.InnerText }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($typeList.Count -gt 0) {
|
||||||
|
Out " Типы ($($typeList.Count)):"
|
||||||
|
foreach ($t in $typeList) { Out " $t" }
|
||||||
|
} else {
|
||||||
|
Out " Тип: $($s.Type)"
|
||||||
|
}
|
||||||
|
$flagText = $s.Flags.Trim()
|
||||||
|
if ($flagText) { Out " Свойства: $($flagText.Trim('[', ']'))" }
|
||||||
|
$drillDone = $true
|
||||||
|
break
|
||||||
|
}
|
||||||
if (-not $drillDone) {
|
if (-not $drillDone) {
|
||||||
Write-Host "[ERROR] '$Name' not found in $objName"
|
Write-Host "[ERROR] '$Name' not found in $objName"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -680,6 +925,20 @@ if (-not $drillDone) {
|
|||||||
|
|
||||||
# --- Mode: brief ---
|
# --- Mode: brief ---
|
||||||
if ($Mode -eq "brief") {
|
if ($Mode -eq "brief") {
|
||||||
|
# Подчинённость — единственный факт структуры, который из brief не выводится никак,
|
||||||
|
# а без него код записи элемента падает. Про обязательность здесь намеренно молчим:
|
||||||
|
# обязательными бывают и обычные реквизиты, а их brief не разбирает.
|
||||||
|
if ($mdType -eq "Catalog") {
|
||||||
|
$ownersNode = $props.SelectSingleNode("md:Owners", $ns)
|
||||||
|
$ownerNames = @()
|
||||||
|
if ($ownersNode) {
|
||||||
|
foreach ($it in $ownersNode.SelectNodes("xr:Item", $ns)) {
|
||||||
|
$ownerNames += ($it.InnerText -replace '^\w+\.', '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($ownerNames.Count -gt 0) { Out "Подчинён: $($ownerNames -join ', ')" }
|
||||||
|
}
|
||||||
|
|
||||||
# Attributes
|
# Attributes
|
||||||
$attrs = @()
|
$attrs = @()
|
||||||
if ($childObjs) { $attrs = @(Get-Attributes $childObjs) }
|
if ($childObjs) { $attrs = @(Get-Attributes $childObjs) }
|
||||||
@@ -828,51 +1087,47 @@ if (-not $drillDone) {
|
|||||||
|
|
||||||
# Document-specific header properties
|
# Document-specific header properties
|
||||||
if ($mdType -eq "Document") {
|
if ($mdType -eq "Document") {
|
||||||
$numType = $props.SelectSingleNode("md:NumberType", $ns)
|
|
||||||
$numLen = $props.SelectSingleNode("md:NumberLength", $ns)
|
|
||||||
$numPer = $props.SelectSingleNode("md:NumberPeriodicity", $ns)
|
|
||||||
$autoNum = $props.SelectSingleNode("md:Autonumbering", $ns)
|
|
||||||
$posting = $props.SelectSingleNode("md:Posting", $ns)
|
$posting = $props.SelectSingleNode("md:Posting", $ns)
|
||||||
|
|
||||||
$parts = @()
|
$parts = @()
|
||||||
if ($numType -and $numLen) {
|
# Номер уехал в блок стандартных реквизитов — здесь остаются свойства объекта
|
||||||
$nt = if ($numType.InnerText -eq "String") { "Строка" } else { "Число" }
|
|
||||||
$piece = "Номер: $nt($($numLen.InnerText))"
|
|
||||||
if ($numPer) {
|
|
||||||
$perRu = if ($numberPeriodMap.ContainsKey($numPer.InnerText)) { $numberPeriodMap[$numPer.InnerText] } else { $numPer.InnerText }
|
|
||||||
$piece += ", $perRu"
|
|
||||||
}
|
|
||||||
if ($autoNum -and $autoNum.InnerText -eq "true") { $piece += ", авто" }
|
|
||||||
$parts += $piece
|
|
||||||
}
|
|
||||||
if ($posting) {
|
if ($posting) {
|
||||||
$parts += "Проведение: $(if ($posting.InnerText -eq 'Allow') { 'да' } else { 'нет' })"
|
$parts += "Проведение: $(if ($posting.InnerText -eq 'Allow') { 'да' } else { 'нет' })"
|
||||||
}
|
}
|
||||||
if ($parts.Count -gt 0) { Out ($parts -join " | ") }
|
if ($parts.Count -gt 0) { Out ($parts -join " | ") }
|
||||||
}
|
}
|
||||||
|
|
||||||
# Catalog-specific header properties
|
# Свойства иерархии и подчинения. Иерархия — не только у справочников: свойство
|
||||||
if ($mdType -eq "Catalog") {
|
# Hierarchical есть и у ПВХ (в корпусе acc/erp/ut/unf их 13), а без этой строки
|
||||||
$parts = @()
|
# в full появлялся Родитель, происхождение которого было ниоткуда не видно.
|
||||||
$hier = $props.SelectSingleNode("md:Hierarchical", $ns)
|
$parts = @()
|
||||||
if ($hier -and $hier.InnerText -eq "true") {
|
$hier = $props.SelectSingleNode("md:Hierarchical", $ns)
|
||||||
$ht = $props.SelectSingleNode("md:HierarchyType", $ns)
|
if ($hier -and $hier.InnerText -eq "true") {
|
||||||
$htText = if ($ht -and $ht.InnerText -eq "HierarchyFoldersAndItems") { "группы и элементы" } else { "элементы" }
|
$ht = $props.SelectSingleNode("md:HierarchyType", $ns)
|
||||||
$limitNode = $props.SelectSingleNode("md:LimitLevelCount", $ns)
|
$htText = if ($ht -and $ht.InnerText -eq "HierarchyFoldersAndItems") { "группы и элементы" } else { "элементы" }
|
||||||
$levelNode = $props.SelectSingleNode("md:LevelCount", $ns)
|
$limitNode = $props.SelectSingleNode("md:LimitLevelCount", $ns)
|
||||||
if ($limitNode -and $limitNode.InnerText -eq "true" -and $levelNode) {
|
$levelNode = $props.SelectSingleNode("md:LevelCount", $ns)
|
||||||
$htText += ", уровней: $($levelNode.InnerText)"
|
if ($limitNode -and $limitNode.InnerText -eq "true" -and $levelNode) {
|
||||||
} else {
|
$htText += ", уровней: $($levelNode.InnerText)"
|
||||||
$htText += ", без ограничения уровней"
|
} else {
|
||||||
}
|
$htText += ", без ограничения уровней"
|
||||||
$parts += "Иерархический: $htText"
|
|
||||||
}
|
}
|
||||||
$codeLen = $props.SelectSingleNode("md:CodeLength", $ns)
|
$parts += "Иерархический: $htText"
|
||||||
$descLen = $props.SelectSingleNode("md:DescriptionLength", $ns)
|
|
||||||
if ($codeLen -and [int]$codeLen.InnerText -gt 0) { $parts += "Код($($codeLen.InnerText))" }
|
|
||||||
if ($descLen -and [int]$descLen.InnerText -gt 0) { $parts += "Наименование($($descLen.InnerText))" }
|
|
||||||
if ($parts.Count -gt 0) { Out ($parts -join " | ") }
|
|
||||||
}
|
}
|
||||||
|
# Код и Наименование уехали в блок стандартных реквизитов — здесь только свойства объекта.
|
||||||
|
# Подчинение печатаем лишь когда оно отличается от дефолта ToItems.
|
||||||
|
if ($mdType -eq "Catalog") {
|
||||||
|
$sub = $props.SelectSingleNode("md:SubordinationUse", $ns)
|
||||||
|
if ($sub -and $sub.InnerText -ne "ToItems") {
|
||||||
|
$subRu = switch ($sub.InnerText) {
|
||||||
|
"ToFolders" { "группам" }
|
||||||
|
"ToFoldersAndItems" { "группам и элементам" }
|
||||||
|
default { $sub.InnerText }
|
||||||
|
}
|
||||||
|
$parts += "Подчинение: $subRu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($parts.Count -gt 0) { Out ($parts -join " | ") }
|
||||||
|
|
||||||
# Register-specific header properties
|
# Register-specific header properties
|
||||||
if ($mdType -match "Register$") {
|
if ($mdType -match "Register$") {
|
||||||
@@ -1059,6 +1314,16 @@ if (-not $drillDone) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- Standard attributes ---
|
||||||
|
$stdAttrs = @(Get-StandardAttributes $props $mdType $Mode)
|
||||||
|
if ($Mode -eq "full") { $stdAttrs += @(Get-StandardAttributesFull $props $mdType $objName) }
|
||||||
|
if ($stdAttrs.Count -gt 0) {
|
||||||
|
Out ""
|
||||||
|
Out "Стандартные реквизиты:"
|
||||||
|
$ml = Get-MaxNameLen $stdAttrs
|
||||||
|
foreach ($s in $stdAttrs) { Out (Format-AttrLine $s $ml) }
|
||||||
|
}
|
||||||
|
|
||||||
# --- Dimensions (registers) ---
|
# --- Dimensions (registers) ---
|
||||||
if ($mdType -match "Register$" -and $childObjs) {
|
if ($mdType -match "Register$" -and $childObjs) {
|
||||||
$dims = @(Get-Attributes $childObjs "Dimension" $true)
|
$dims = @(Get-Attributes $childObjs "Dimension" $true)
|
||||||
@@ -1177,6 +1442,13 @@ if (-not $drillDone) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Единственная подсказка на весь вывод — чем развернуть свёрнутый в счётчик состав
|
||||||
|
if ($script:collapsedNames.Count -gt 0) {
|
||||||
|
$hints = ($script:collapsedNames | ForEach-Object { "-Name $_" }) -join ", "
|
||||||
|
Out ""
|
||||||
|
Out "Полный состав типов: $hints"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Pagination and output ---
|
# --- Pagination and output ---
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# meta-info v1.4 — Compact summary of 1C metadata object (Python port)
|
# meta-info v1.10 — Compact summary of 1C metadata object (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
|
||||||
@@ -10,6 +10,28 @@ from lxml import etree
|
|||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
# ── arg parsing ──────────────────────────────────────────────
|
# ── arg parsing ──────────────────────────────────────────────
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||||
@@ -19,7 +41,7 @@ parser.add_argument("-Name", default="")
|
|||||||
parser.add_argument("-Limit", type=int, default=150)
|
parser.add_argument("-Limit", type=int, default=150)
|
||||||
parser.add_argument("-Offset", type=int, default=0)
|
parser.add_argument("-Offset", type=int, default=0)
|
||||||
parser.add_argument("-OutFile", default="")
|
parser.add_argument("-OutFile", default="")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
object_path = args.ObjectPath
|
object_path = args.ObjectPath
|
||||||
mode = args.Mode
|
mode = args.Mode
|
||||||
@@ -227,6 +249,30 @@ def get_ml_text(node):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
# Тип-множество: голое имя метатипа без `.Имя` означает ВСЕ ссылки этого класса
|
||||||
|
# (см. docs/meta-dsl-spec.md §«Тип-множество»). Конкретный тип всегда пишется с точкой,
|
||||||
|
# поэтому «СправочникСсылка» без точки читается однозначно как обобщённый.
|
||||||
|
def format_single_type_set(raw):
|
||||||
|
raw = re.sub(r'^d\d+p\d+:', 'cfg:', raw)
|
||||||
|
m = re.match(r'^cfg:DefinedType\.(.+)$', raw)
|
||||||
|
if m:
|
||||||
|
return f"ОпределяемыйТип.{m.group(1)}"
|
||||||
|
m = re.match(r'^cfg:Characteristic\.(.+)$', raw)
|
||||||
|
if m:
|
||||||
|
return f"Характеристика.{m.group(1)}"
|
||||||
|
if raw == "cfg:AnyRef":
|
||||||
|
return "ЛюбаяСсылка"
|
||||||
|
if raw == "cfg:AnyIBRef":
|
||||||
|
return "ЛюбаяСсылкаИБ"
|
||||||
|
m = re.match(r'^cfg:(\w+Ref)$', raw)
|
||||||
|
if m and m.group(1) in ref_type_map:
|
||||||
|
return ref_type_map[m.group(1)]
|
||||||
|
m = re.match(r'^cfg:(.+)$', raw)
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
|
return raw
|
||||||
|
|
||||||
|
|
||||||
def format_type(type_node_el):
|
def format_type(type_node_el):
|
||||||
if type_node_el is None:
|
if type_node_el is None:
|
||||||
return ""
|
return ""
|
||||||
@@ -234,16 +280,7 @@ def format_type(type_node_el):
|
|||||||
for t in find_all(type_node_el, "v8:Type"):
|
for t in find_all(type_node_el, "v8:Type"):
|
||||||
types.append(format_single_type(inner_text(t), type_node_el))
|
types.append(format_single_type(inner_text(t), type_node_el))
|
||||||
for t in find_all(type_node_el, "v8:TypeSet"):
|
for t in find_all(type_node_el, "v8:TypeSet"):
|
||||||
raw = inner_text(t)
|
types.append(format_single_type_set(inner_text(t)))
|
||||||
m = re.match(r'^cfg:DefinedType\.(.+)$', raw)
|
|
||||||
if m:
|
|
||||||
types.append(f"ОпределяемыйТип.{m.group(1)}")
|
|
||||||
continue
|
|
||||||
m = re.match(r'^cfg:Characteristic\.(.+)$', raw)
|
|
||||||
if m:
|
|
||||||
types.append(f"Характеристика.{m.group(1)}")
|
|
||||||
continue
|
|
||||||
types.append(raw)
|
|
||||||
if len(types) == 0:
|
if len(types) == 0:
|
||||||
return ""
|
return ""
|
||||||
if len(types) == 1:
|
if len(types) == 1:
|
||||||
@@ -341,6 +378,190 @@ def format_flags(a_props, is_dimension=False):
|
|||||||
return f" [{', '.join(flags)}]"
|
return f" [{', '.join(flags)}]"
|
||||||
|
|
||||||
|
|
||||||
|
# Ссылка на объект метаданных (`Catalog.Валюты` в Owners и подобных) — не тип значения,
|
||||||
|
# но в выводе показываем именно тип, который присваивается: СправочникСсылка.Валюты.
|
||||||
|
def format_md_object_ref(raw):
|
||||||
|
m = re.match(r'^(\w+)\.(.+)$', raw)
|
||||||
|
if m:
|
||||||
|
key = f"{m.group(1)}Ref"
|
||||||
|
if key in ref_type_map:
|
||||||
|
return f"{ref_type_map[key]}.{m.group(2)}"
|
||||||
|
return raw
|
||||||
|
|
||||||
|
|
||||||
|
# ── Стандартные реквизиты ────────────────────────────────────
|
||||||
|
|
||||||
|
# Сколько типов состава печатать списком, прежде чем свернуть в счётчик. По корпусу
|
||||||
|
# acc/erp/ut/unf состав ПВХ доходит до 151 типа, при этом 20 из 42 ПВХ укладываются в 5.
|
||||||
|
COMPOSED_TYPE_THRESHOLD = 5
|
||||||
|
|
||||||
|
# Имена полей, состав которых свернули в счётчик, — по ним в конце вывода печатается
|
||||||
|
# единственная подсказка, чем состав развернуть.
|
||||||
|
collapsed_names = []
|
||||||
|
|
||||||
|
# Блок StandardAttributes в XML опционален: платформа материализует его только когда хотя бы
|
||||||
|
# один стандартный реквизит кастомизирован (docs/meta-dsl-spec.md §7.1.1). Когда блока нет,
|
||||||
|
# действуют платформенные дефолты — профиль ниже совпадает с $stdAttrProfile в meta-compile
|
||||||
|
# (выведен из корпуса acc+erp). Правки держать синхронными, иначе навыки разъедутся молча.
|
||||||
|
std_attr_required_default = {
|
||||||
|
"Catalog": {"Owner": True, "Description": True},
|
||||||
|
"Document": {"Date": True},
|
||||||
|
"ExchangePlan": {"Description": True, "Code": True},
|
||||||
|
"ChartOfAccounts": {"Description": True, "Code": True},
|
||||||
|
"ChartOfCharacteristicTypes": {"Description": True},
|
||||||
|
"ChartOfCalculationTypes": {"Description": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_std_attr_required(props_node, attr_name, obj_type):
|
||||||
|
fc = find(props_node, f"md:StandardAttributes/xr:StandardAttribute[@name='{attr_name}']/xr:FillChecking")
|
||||||
|
if fc is not None:
|
||||||
|
return inner_text(fc) == "ShowError"
|
||||||
|
return std_attr_required_default.get(obj_type, {}).get(attr_name, False)
|
||||||
|
|
||||||
|
|
||||||
|
def new_std_attr(name, type_str, flags):
|
||||||
|
f = [x for x in flags if x]
|
||||||
|
flag_str = f" [{', '.join(f)}]" if f else ""
|
||||||
|
return {"Name": name, "Type": type_str, "Flags": flag_str}
|
||||||
|
|
||||||
|
|
||||||
|
# Стандартные реквизиты, наличие и характеристики которых задаются настройками объекта, —
|
||||||
|
# их нельзя вывести из остального вывода, поэтому они попадают и в overview. Ссылка,
|
||||||
|
# ПометкаУдаления, Родитель и прочее следуют из типа объекта и строки «Иерархический» —
|
||||||
|
# они только в full (см. get_standard_attributes_full).
|
||||||
|
def get_standard_attributes(props_node, obj_type, mode_name):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
# Владелец — только у подчинённого справочника; состав типов ниоткуда не выводится
|
||||||
|
if obj_type == "Catalog":
|
||||||
|
owners_node = find(props_node, "md:Owners")
|
||||||
|
owner_types = []
|
||||||
|
if owners_node is not None:
|
||||||
|
for it in find_all(owners_node, "xr:Item"):
|
||||||
|
owner_types.append(format_md_object_ref(inner_text(it)))
|
||||||
|
if owner_types:
|
||||||
|
req = "обязательный" if test_std_attr_required(props_node, "Owner", obj_type) else None
|
||||||
|
result.append(new_std_attr("Владелец", ", ".join(owner_types), [req]))
|
||||||
|
|
||||||
|
# ТипЗначения ПВХ — до полутора сотен типов, полный список раздул бы сводку в обоих
|
||||||
|
# режимах, поэтому сворачиваем в счётчик. Состав смотреть через -Name ТипЗначения.
|
||||||
|
if obj_type == "ChartOfCharacteristicTypes":
|
||||||
|
vt = find(props_node, "md:Type")
|
||||||
|
if vt is not None:
|
||||||
|
cnt = len(find_all(vt, "v8:Type")) + len(find_all(vt, "v8:TypeSet"))
|
||||||
|
if cnt > COMPOSED_TYPE_THRESHOLD:
|
||||||
|
type_str = f"Составной ({cnt})"
|
||||||
|
collapsed_names.append("ТипЗначения")
|
||||||
|
else:
|
||||||
|
type_str = format_type(vt)
|
||||||
|
if type_str:
|
||||||
|
result.append(new_std_attr("ТипЗначения", type_str, []))
|
||||||
|
|
||||||
|
# Дата — есть всегда, ценна флагом обязательности
|
||||||
|
if obj_type in ("Document", "BusinessProcess", "Task"):
|
||||||
|
req = "обязательный" if test_std_attr_required(props_node, "Date", obj_type) else None
|
||||||
|
result.append(new_std_attr("Дата", "Дата", [req]))
|
||||||
|
|
||||||
|
# Номер — тип и длина задаются объектом, при NumberLength=0 номера нет
|
||||||
|
if obj_type in ("Document", "BusinessProcess", "Task"):
|
||||||
|
num_len = find(props_node, "md:NumberLength")
|
||||||
|
if num_len is not None and inner_text(num_len).isdigit() and int(inner_text(num_len)) > 0:
|
||||||
|
num_type = find(props_node, "md:NumberType")
|
||||||
|
nt_ru = "Число" if num_type is not None and inner_text(num_type) == "Number" else "Строка"
|
||||||
|
flags = []
|
||||||
|
if test_std_attr_required(props_node, "Number", obj_type):
|
||||||
|
flags.append("обязательный")
|
||||||
|
num_per = find(props_node, "md:NumberPeriodicity")
|
||||||
|
if num_per is not None and inner_text(num_per) in number_period_map:
|
||||||
|
flags.append(number_period_map[inner_text(num_per)])
|
||||||
|
num_allowed = find(props_node, "md:NumberAllowedLength")
|
||||||
|
if num_allowed is not None and inner_text(num_allowed) == "Fixed":
|
||||||
|
flags.append("фикс. длина")
|
||||||
|
auto_num = find(props_node, "md:Autonumbering")
|
||||||
|
if auto_num is not None and inner_text(auto_num) == "true":
|
||||||
|
flags.append("авто")
|
||||||
|
result.append(new_std_attr("Номер", f"{nt_ru}({inner_text(num_len)})", flags))
|
||||||
|
|
||||||
|
# Код — при CodeLength=0 кода у объекта нет вовсе, строку не печатаем
|
||||||
|
code_len = find(props_node, "md:CodeLength")
|
||||||
|
if code_len is not None and inner_text(code_len).isdigit() and int(inner_text(code_len)) > 0:
|
||||||
|
code_type = find(props_node, "md:CodeType")
|
||||||
|
ct_ru = "Число" if code_type is not None and inner_text(code_type) == "Number" else "Строка"
|
||||||
|
flags = []
|
||||||
|
if test_std_attr_required(props_node, "Code", obj_type):
|
||||||
|
flags.append("обязательный")
|
||||||
|
code_allowed = find(props_node, "md:CodeAllowedLength")
|
||||||
|
if code_allowed is not None and inner_text(code_allowed) == "Fixed":
|
||||||
|
flags.append("фикс. длина")
|
||||||
|
result.append(new_std_attr("Код", f"{ct_ru}({inner_text(code_len)})", flags))
|
||||||
|
|
||||||
|
# Наименование — при DescriptionLength=0 наименования нет
|
||||||
|
desc_len = find(props_node, "md:DescriptionLength")
|
||||||
|
if desc_len is not None and inner_text(desc_len).isdigit() and int(inner_text(desc_len)) > 0:
|
||||||
|
req = "обязательный" if test_std_attr_required(props_node, "Description", obj_type) else None
|
||||||
|
result.append(new_std_attr("Наименование", f"Строка({inner_text(desc_len)})", [req]))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# Остальные стандартные реквизиты — предсказуемы по типу объекта, но в full полезны
|
||||||
|
# как перечень доступных полей для запроса.
|
||||||
|
def get_standard_attributes_full(props_node, obj_type, obj_name):
|
||||||
|
self_ref_map = {
|
||||||
|
"Catalog": "СправочникСсылка",
|
||||||
|
"ChartOfCharacteristicTypes": "ПВХСсылка",
|
||||||
|
"ChartOfAccounts": "ПланСчетовСсылка",
|
||||||
|
"ChartOfCalculationTypes": "ПВРСсылка",
|
||||||
|
"ExchangePlan": "ПланОбменаСсылка",
|
||||||
|
"Document": "ДокументСсылка",
|
||||||
|
"BusinessProcess": "БизнесПроцессСсылка",
|
||||||
|
"Task": "ЗадачаСсылка",
|
||||||
|
}
|
||||||
|
self_ref = f"{self_ref_map[obj_type]}.{obj_name}" if obj_type in self_ref_map else ""
|
||||||
|
|
||||||
|
result = []
|
||||||
|
if self_ref:
|
||||||
|
result.append(new_std_attr("Ссылка", self_ref, []))
|
||||||
|
result.append(new_std_attr("ПометкаУдаления", "Булево", []))
|
||||||
|
|
||||||
|
# Родитель и ЭтоГруппа существуют только у иерархических объектов
|
||||||
|
hier = find(props_node, "md:Hierarchical")
|
||||||
|
is_hier = (hier is not None and inner_text(hier) == "true") or obj_type == "ChartOfAccounts"
|
||||||
|
if is_hier and self_ref:
|
||||||
|
result.append(new_std_attr("Родитель", self_ref, []))
|
||||||
|
ht = find(props_node, "md:HierarchyType")
|
||||||
|
if obj_type == "Catalog" and (ht is None or inner_text(ht) == "HierarchyFoldersAndItems"):
|
||||||
|
result.append(new_std_attr("ЭтоГруппа", "Булево", []))
|
||||||
|
|
||||||
|
if obj_type == "Document":
|
||||||
|
result.append(new_std_attr("Проведен", "Булево", []))
|
||||||
|
elif obj_type == "ExchangePlan":
|
||||||
|
result.append(new_std_attr("ЭтотУзел", "Булево", []))
|
||||||
|
result.append(new_std_attr("НомерОтправленного", "Число", []))
|
||||||
|
result.append(new_std_attr("НомерПринятого", "Число", []))
|
||||||
|
elif obj_type == "BusinessProcess":
|
||||||
|
result.append(new_std_attr("Стартован", "Булево", []))
|
||||||
|
result.append(new_std_attr("Завершен", "Булево", []))
|
||||||
|
result.append(new_std_attr("ВедущаяЗадача", "ЗадачаСсылка", []))
|
||||||
|
elif obj_type == "Task":
|
||||||
|
result.append(new_std_attr("Выполнена", "Булево", []))
|
||||||
|
result.append(new_std_attr("БизнесПроцесс", "БизнесПроцессСсылка", []))
|
||||||
|
result.append(new_std_attr("ТочкаМаршрута", "БизнесПроцессТочкаМаршрутаСсылка", []))
|
||||||
|
elif obj_type == "ChartOfAccounts":
|
||||||
|
result.append(new_std_attr("Вид", "ВидСчета", []))
|
||||||
|
result.append(new_std_attr("Забалансовый", "Булево", []))
|
||||||
|
result.append(new_std_attr("Порядок", "Число", []))
|
||||||
|
elif obj_type == "ChartOfCalculationTypes":
|
||||||
|
result.append(new_std_attr("ПериодДействияБазовый", "Булево", []))
|
||||||
|
|
||||||
|
# Предопределённые данные есть у всех перечисленных типов, кроме документов и задач
|
||||||
|
if obj_type in ("Catalog", "ChartOfCharacteristicTypes", "ChartOfAccounts", "ChartOfCalculationTypes"):
|
||||||
|
result.append(new_std_attr("Предопределенный", "Булево", []))
|
||||||
|
result.append(new_std_attr("ИмяПредопределенныхДанных", "Строка", []))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_attributes(parent_node, child_tag="Attribute", is_dimension=False):
|
def get_attributes(parent_node, child_tag="Attribute", is_dimension=False):
|
||||||
result = []
|
result = []
|
||||||
for attr in find_all(parent_node, f"md:{child_tag}"):
|
for attr in find_all(parent_node, f"md:{child_tag}"):
|
||||||
@@ -381,7 +602,10 @@ def get_max_name_len(attrs):
|
|||||||
def get_simple_children(parent_node, tag):
|
def get_simple_children(parent_node, tag):
|
||||||
result = []
|
result = []
|
||||||
for child in find_all(parent_node, f"md:{tag}"):
|
for child in find_all(parent_node, f"md:{tag}"):
|
||||||
result.append(inner_text(child))
|
# Form/Template в ChildObjects — простые узлы с именем в тексте, а Command — узел
|
||||||
|
# с вложенным Properties: у него inner_text склеил бы всё содержимое в одну строку.
|
||||||
|
name_node = find(child, "md:Properties/md:Name")
|
||||||
|
result.append(inner_text(name_node) if name_node is not None else inner_text(child))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -472,7 +696,7 @@ def get_ws_operations(child_objs):
|
|||||||
# ── Support status of this object (Ext/ParentConfigurations.bin) ──
|
# ── Support status of this object (Ext/ParentConfigurations.bin) ──
|
||||||
# See docs/1c-support-state-spec.md. Walks up to the config root, decodes the
|
# See docs/1c-support-state-spec.md. Walks up to the config root, decodes the
|
||||||
# object's support rule. Never throws — degrades to "не на поддержке".
|
# object's support rule. Never throws — degrades to "не на поддержке".
|
||||||
def _meta_is_external_root(xml_path):
|
def _sg_is_external_root(xml_path):
|
||||||
if not os.path.isfile(xml_path):
|
if not os.path.isfile(xml_path):
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
@@ -487,7 +711,7 @@ def _meta_is_external_root(xml_path):
|
|||||||
|
|
||||||
def get_object_support_status(obj_uuid):
|
def get_object_support_status(obj_uuid):
|
||||||
try:
|
try:
|
||||||
if _meta_is_external_root(object_path):
|
if _sg_is_external_root(object_path):
|
||||||
return None
|
return None
|
||||||
d = os.path.dirname(object_path)
|
d = os.path.dirname(object_path)
|
||||||
bin_path = None
|
bin_path = None
|
||||||
@@ -705,6 +929,44 @@ if drill_name and child_objs is not None:
|
|||||||
drill_done = True
|
drill_done = True
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Не нашли среди дочерних объектов — пробуем стандартные реквизиты ниже
|
||||||
|
|
||||||
|
# Drill-down по стандартному реквизиту — единственный способ увидеть состав типов целиком,
|
||||||
|
# когда в сводке он свёрнут в счётчик. Живёт в Properties, а не в ChildObjects, поэтому идёт
|
||||||
|
# отдельной веткой и работает даже у объектов без ChildObjects.
|
||||||
|
if drill_name and not drill_done:
|
||||||
|
std_all = get_standard_attributes(props, md_type, "full") + get_standard_attributes_full(props, md_type, obj_name)
|
||||||
|
for s in std_all:
|
||||||
|
if s["Name"] != drill_name:
|
||||||
|
continue
|
||||||
|
out(f"Стандартный реквизит: {s['Name']}")
|
||||||
|
|
||||||
|
# Состав типов разворачиваем списком: ради этого drill-down и нужен
|
||||||
|
type_list = []
|
||||||
|
type_src = find(props, "md:Type") if drill_name == "ТипЗначения" else None
|
||||||
|
if type_src is not None:
|
||||||
|
for t in find_all(type_src, "v8:Type"):
|
||||||
|
type_list.append(format_single_type(inner_text(t), type_src))
|
||||||
|
for t in find_all(type_src, "v8:TypeSet"):
|
||||||
|
type_list.append(format_single_type_set(inner_text(t)))
|
||||||
|
elif drill_name == "Владелец":
|
||||||
|
owners_node = find(props, "md:Owners")
|
||||||
|
if owners_node is not None:
|
||||||
|
for it in find_all(owners_node, "xr:Item"):
|
||||||
|
type_list.append(format_md_object_ref(inner_text(it)))
|
||||||
|
|
||||||
|
if type_list:
|
||||||
|
out(f" Типы ({len(type_list)}):")
|
||||||
|
for t in type_list:
|
||||||
|
out(f" {t}")
|
||||||
|
else:
|
||||||
|
out(f" Тип: {s['Type']}")
|
||||||
|
flag_text = s["Flags"].strip()
|
||||||
|
if flag_text:
|
||||||
|
out(f" Свойства: {flag_text.strip('[]')}")
|
||||||
|
drill_done = True
|
||||||
|
break
|
||||||
|
|
||||||
if not drill_done:
|
if not drill_done:
|
||||||
print(f"[ERROR] '{drill_name}' not found in {obj_name}")
|
print(f"[ERROR] '{drill_name}' not found in {obj_name}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -736,6 +998,18 @@ if not drill_done:
|
|||||||
out(f"Расширенное представление списка: {ext_list_presentation}")
|
out(f"Расширенное представление списка: {ext_list_presentation}")
|
||||||
|
|
||||||
if mode == "brief":
|
if mode == "brief":
|
||||||
|
# Подчинённость — единственный факт структуры, который из brief не выводится никак,
|
||||||
|
# а без него код записи элемента падает. Про обязательность здесь намеренно молчим:
|
||||||
|
# обязательными бывают и обычные реквизиты, а их brief не разбирает.
|
||||||
|
if md_type == "Catalog":
|
||||||
|
owners_node = find(props, "md:Owners")
|
||||||
|
owner_names = []
|
||||||
|
if owners_node is not None:
|
||||||
|
for it in find_all(owners_node, "xr:Item"):
|
||||||
|
owner_names.append(re.sub(r'^\w+\.', '', inner_text(it)))
|
||||||
|
if owner_names:
|
||||||
|
out(f"Подчинён: {', '.join(owner_names)}")
|
||||||
|
|
||||||
# Attributes
|
# Attributes
|
||||||
attrs = get_attributes(child_objs) if child_objs is not None else []
|
attrs = get_attributes(child_objs) if child_objs is not None else []
|
||||||
if attrs:
|
if attrs:
|
||||||
@@ -865,48 +1139,39 @@ if not drill_done:
|
|||||||
|
|
||||||
# Document-specific header
|
# Document-specific header
|
||||||
if md_type == "Document":
|
if md_type == "Document":
|
||||||
num_type = find(props, "md:NumberType")
|
|
||||||
num_len = find(props, "md:NumberLength")
|
|
||||||
num_per = find(props, "md:NumberPeriodicity")
|
|
||||||
auto_num = find(props, "md:Autonumbering")
|
|
||||||
posting = find(props, "md:Posting")
|
posting = find(props, "md:Posting")
|
||||||
parts = []
|
parts = []
|
||||||
if num_type is not None and num_len is not None:
|
# Номер уехал в блок стандартных реквизитов — здесь остаются свойства объекта
|
||||||
nt = "Строка" if inner_text(num_type) == "String" else "Число"
|
|
||||||
piece = f"Номер: {nt}({inner_text(num_len)})"
|
|
||||||
if num_per is not None:
|
|
||||||
per_ru = number_period_map.get(inner_text(num_per), inner_text(num_per))
|
|
||||||
piece += f", {per_ru}"
|
|
||||||
if auto_num is not None and inner_text(auto_num) == "true":
|
|
||||||
piece += ", авто"
|
|
||||||
parts.append(piece)
|
|
||||||
if posting is not None:
|
if posting is not None:
|
||||||
parts.append(f"Проведение: {'да' if inner_text(posting) == 'Allow' else 'нет'}")
|
parts.append(f"Проведение: {'да' if inner_text(posting) == 'Allow' else 'нет'}")
|
||||||
if parts:
|
if parts:
|
||||||
out(" | ".join(parts))
|
out(" | ".join(parts))
|
||||||
|
|
||||||
# Catalog-specific header
|
# Свойства иерархии и подчинения. Иерархия — не только у справочников: свойство
|
||||||
|
# Hierarchical есть и у ПВХ (в корпусе acc/erp/ut/unf их 13), а без этой строки
|
||||||
|
# в full появлялся Родитель, происхождение которого было ниоткуда не видно.
|
||||||
|
parts = []
|
||||||
|
hier = find(props, "md:Hierarchical")
|
||||||
|
if hier is not None and inner_text(hier) == "true":
|
||||||
|
ht = find(props, "md:HierarchyType")
|
||||||
|
ht_text = "группы и элементы" if ht is not None and inner_text(ht) == "HierarchyFoldersAndItems" else "элементы"
|
||||||
|
limit_node = find(props, "md:LimitLevelCount")
|
||||||
|
level_node = find(props, "md:LevelCount")
|
||||||
|
if limit_node is not None and inner_text(limit_node) == "true" and level_node is not None:
|
||||||
|
ht_text += f", уровней: {inner_text(level_node)}"
|
||||||
|
else:
|
||||||
|
ht_text += ", без ограничения уровней"
|
||||||
|
parts.append(f"Иерархический: {ht_text}")
|
||||||
|
# Код и Наименование уехали в блок стандартных реквизитов — здесь только свойства объекта.
|
||||||
|
# Подчинение печатаем лишь когда оно отличается от дефолта ToItems.
|
||||||
if md_type == "Catalog":
|
if md_type == "Catalog":
|
||||||
parts = []
|
sub = find(props, "md:SubordinationUse")
|
||||||
hier = find(props, "md:Hierarchical")
|
if sub is not None and inner_text(sub) != "ToItems":
|
||||||
if hier is not None and inner_text(hier) == "true":
|
sub_ru = {"ToFolders": "группам", "ToFoldersAndItems": "группам и элементам"}.get(
|
||||||
ht = find(props, "md:HierarchyType")
|
inner_text(sub), inner_text(sub))
|
||||||
ht_text = "группы и элементы" if ht is not None and inner_text(ht) == "HierarchyFoldersAndItems" else "элементы"
|
parts.append(f"Подчинение: {sub_ru}")
|
||||||
limit_node = find(props, "md:LimitLevelCount")
|
if parts:
|
||||||
level_node = find(props, "md:LevelCount")
|
out(" | ".join(parts))
|
||||||
if limit_node is not None and inner_text(limit_node) == "true" and level_node is not None:
|
|
||||||
ht_text += f", уровней: {inner_text(level_node)}"
|
|
||||||
else:
|
|
||||||
ht_text += ", без ограничения уровней"
|
|
||||||
parts.append(f"Иерархический: {ht_text}")
|
|
||||||
code_len = find(props, "md:CodeLength")
|
|
||||||
desc_len = find(props, "md:DescriptionLength")
|
|
||||||
if code_len is not None and inner_text(code_len).isdigit() and int(inner_text(code_len)) > 0:
|
|
||||||
parts.append(f"Код({inner_text(code_len)})")
|
|
||||||
if desc_len is not None and inner_text(desc_len).isdigit() and int(inner_text(desc_len)) > 0:
|
|
||||||
parts.append(f"Наименование({inner_text(desc_len)})")
|
|
||||||
if parts:
|
|
||||||
out(" | ".join(parts))
|
|
||||||
|
|
||||||
# Register-specific header
|
# Register-specific header
|
||||||
if md_type.endswith("Register"):
|
if md_type.endswith("Register"):
|
||||||
@@ -1067,6 +1332,17 @@ if not drill_done:
|
|||||||
syn_text = f'"{v["Synonym"]}"' if v["Synonym"] and v["Synonym"] != v["Name"] else ""
|
syn_text = f'"{v["Synonym"]}"' if v["Synonym"] and v["Synonym"] != v["Name"] else ""
|
||||||
out(f" {padded} {syn_text}")
|
out(f" {padded} {syn_text}")
|
||||||
|
|
||||||
|
# Standard attributes
|
||||||
|
std_attrs = get_standard_attributes(props, md_type, mode)
|
||||||
|
if mode == "full":
|
||||||
|
std_attrs += get_standard_attributes_full(props, md_type, obj_name)
|
||||||
|
if std_attrs:
|
||||||
|
out("")
|
||||||
|
out("Стандартные реквизиты:")
|
||||||
|
ml = get_max_name_len(std_attrs)
|
||||||
|
for s in std_attrs:
|
||||||
|
out(format_attr_line(s, ml))
|
||||||
|
|
||||||
# Dimensions (registers)
|
# Dimensions (registers)
|
||||||
if md_type.endswith("Register") and child_objs is not None:
|
if md_type.endswith("Register") and child_objs is not None:
|
||||||
dims = get_attributes(child_objs, "Dimension", True)
|
dims = get_attributes(child_objs, "Dimension", True)
|
||||||
@@ -1174,6 +1450,12 @@ if not drill_done:
|
|||||||
if commands:
|
if commands:
|
||||||
out(f"Команды: {', '.join(commands)}")
|
out(f"Команды: {', '.join(commands)}")
|
||||||
|
|
||||||
|
# Единственная подсказка на весь вывод — чем развернуть свёрнутый в счётчик состав
|
||||||
|
if collapsed_names:
|
||||||
|
hints = ", ".join(f"-Name {n}" for n in collapsed_names)
|
||||||
|
out("")
|
||||||
|
out(f"Полный состав типов: {hints}")
|
||||||
|
|
||||||
# ── Pagination and output ────────────────────────────────────
|
# ── Pagination and output ────────────────────────────────────
|
||||||
|
|
||||||
total_lines = len(lines)
|
total_lines = len(lines)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# meta-remove v1.5 — Remove metadata object from 1C configuration dump
|
# meta-remove v1.9 — Remove metadata object from 1C configuration dump
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -493,9 +493,29 @@ if (-not $cfgNode) {
|
|||||||
# Save Configuration.xml
|
# Save Configuration.xml
|
||||||
if ($actions -gt 0 -and -not $DryRun) {
|
if ($actions -gt 0 -and -not $DryRun) {
|
||||||
$enc = New-Object System.Text.UTF8Encoding $true
|
$enc = New-Object System.Text.UTF8Encoding $true
|
||||||
$sw = New-Object System.IO.StreamWriter($configXml, $false, $enc)
|
# Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
|
||||||
$xmlDoc.Save($sw)
|
$settings = New-Object System.Xml.XmlWriterSettings
|
||||||
$sw.Close()
|
$settings.Encoding = $enc
|
||||||
|
$settings.Indent = $false
|
||||||
|
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
|
||||||
|
$memStream = New-Object System.IO.MemoryStream
|
||||||
|
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
|
||||||
|
$xmlDoc.Save($writer)
|
||||||
|
$writer.Flush(); $writer.Close()
|
||||||
|
|
||||||
|
$xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
|
||||||
|
$memStream.Close()
|
||||||
|
if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) }
|
||||||
|
$xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$xmlText = [regex]::Replace($xmlText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $configXml) -and ([System.IO.File]::ReadAllText($configXml) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
|
[System.IO.File]::WriteAllText($configXml, $xmlText, $enc)
|
||||||
Write-Host "[OK] Configuration.xml saved"
|
Write-Host "[OK] Configuration.xml saved"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -559,9 +579,29 @@ function Remove-FromSubsystems {
|
|||||||
|
|
||||||
if ($modified -and -not $DryRun) {
|
if ($modified -and -not $DryRun) {
|
||||||
$enc = New-Object System.Text.UTF8Encoding $true
|
$enc = New-Object System.Text.UTF8Encoding $true
|
||||||
$sw = New-Object System.IO.StreamWriter($xmlFile.FullName, $false, $enc)
|
# Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
|
||||||
$ssDoc.Save($sw)
|
$settings = New-Object System.Xml.XmlWriterSettings
|
||||||
$sw.Close()
|
$settings.Encoding = $enc
|
||||||
|
$settings.Indent = $false
|
||||||
|
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
|
||||||
|
$memStream = New-Object System.IO.MemoryStream
|
||||||
|
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
|
||||||
|
$ssDoc.Save($writer)
|
||||||
|
$writer.Flush(); $writer.Close()
|
||||||
|
|
||||||
|
$xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
|
||||||
|
$memStream.Close()
|
||||||
|
if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) }
|
||||||
|
$xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$xmlText = [regex]::Replace($xmlText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $xmlFile.FullName) -and ([System.IO.File]::ReadAllText($xmlFile.FullName) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
|
[System.IO.File]::WriteAllText($xmlFile.FullName, $xmlText, $enc)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Recurse into child subsystems
|
# Recurse into child subsystems
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# meta-remove v1.5 — Remove metadata object from 1C configuration dump
|
# meta-remove v1.9 — Remove metadata object from 1C configuration dump
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -10,6 +10,28 @@ import sys
|
|||||||
import shutil
|
import shutil
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
||||||
@@ -292,21 +314,22 @@ def _detect_xml_style(path):
|
|||||||
|
|
||||||
|
|
||||||
def _finalize_xml_bytes(xml_bytes, style):
|
def _finalize_xml_bytes(xml_bytes, style):
|
||||||
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None)."""
|
"""Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
|
||||||
enc_decl = style["enc"] if style else "utf-8"
|
выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
|
||||||
|
enc_decl = style["enc"] if style else "UTF-8"
|
||||||
xml_bytes = xml_bytes.replace(
|
xml_bytes = xml_bytes.replace(
|
||||||
b"<?xml version='1.0' encoding='UTF-8'?>",
|
b"<?xml version='1.0' encoding='UTF-8'?>",
|
||||||
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
|
||||||
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
# Канонизировать переносы к LF (убирает от \r в tail'ах)
|
||||||
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
xml_bytes = (xml_bytes.replace(b" \n", b"\n").replace(b" ", b"")
|
||||||
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
.replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
|
||||||
# Финальный перенос — как в оригинале (новый файл → есть)
|
# Финальный перенос — как в оригинале (новый файл → нет, канон #57)
|
||||||
want_final_nl = style["final_nl"] if style else True
|
want_final_nl = style["final_nl"] if style else False
|
||||||
xml_bytes = xml_bytes.rstrip(b"\n")
|
xml_bytes = xml_bytes.rstrip(b"\n")
|
||||||
if want_final_nl:
|
if want_final_nl:
|
||||||
xml_bytes += b"\n"
|
xml_bytes += b"\n"
|
||||||
# EOL — как в оригинале (новый файл → LF, текущее поведение)
|
# EOL — как в оригинале (новый файл → CRLF, канон #57)
|
||||||
if style and style["crlf"]:
|
if (style["crlf"] if style else True):
|
||||||
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
|
||||||
return xml_bytes
|
return xml_bytes
|
||||||
|
|
||||||
@@ -330,7 +353,7 @@ def main():
|
|||||||
parser.add_argument("-DryRun", action="store_true")
|
parser.add_argument("-DryRun", action="store_true")
|
||||||
parser.add_argument("-KeepFiles", action="store_true")
|
parser.add_argument("-KeepFiles", action="store_true")
|
||||||
parser.add_argument("-Force", action="store_true")
|
parser.add_argument("-Force", action="store_true")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
config_dir = args.ConfigDir
|
config_dir = args.ConfigDir
|
||||||
if not os.path.isabs(config_dir):
|
if not os.path.isabs(config_dir):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# meta-validate v1.13 — Validate 1C metadata object structure (+корневой <Type>: скаляр без структуры = ошибка)
|
# meta-validate v1.18 — Validate 1C metadata object structure
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -209,7 +209,7 @@ $standardAttributesByType = @{
|
|||||||
"Enum" = @("Order","Ref")
|
"Enum" = @("Order","Ref")
|
||||||
"InformationRegister" = @("Active","LineNumber","Recorder","Period")
|
"InformationRegister" = @("Active","LineNumber","Recorder","Period")
|
||||||
"AccumulationRegister" = @("Active","LineNumber","Recorder","Period","RecordType")
|
"AccumulationRegister" = @("Active","LineNumber","Recorder","Period","RecordType")
|
||||||
"AccountingRegister" = @("Active","Period","Recorder","LineNumber","Account")
|
"AccountingRegister" = @("Active","Period","Recorder","LineNumber","Account","PeriodAdjustment","RecordType")
|
||||||
"CalculationRegister" = @("Active","Recorder","LineNumber","RegistrationPeriod","CalculationType","ReversingEntry","ActionPeriod","BegOfActionPeriod","EndOfActionPeriod","BegOfBasePeriod","EndOfBasePeriod")
|
"CalculationRegister" = @("Active","Recorder","LineNumber","RegistrationPeriod","CalculationType","ReversingEntry","ActionPeriod","BegOfActionPeriod","EndOfActionPeriod","BegOfBasePeriod","EndOfBasePeriod")
|
||||||
"ChartOfAccounts" = @("PredefinedDataName","Predefined","Ref","DeletionMark","Description","Code","Parent","Order","Type","OffBalance")
|
"ChartOfAccounts" = @("PredefinedDataName","Predefined","Ref","DeletionMark","Description","Code","Parent","Order","Type","OffBalance")
|
||||||
"ChartOfCharacteristicTypes" = @("PredefinedDataName","Predefined","Ref","DeletionMark","Description","Code","Parent","IsFolder","ValueType")
|
"ChartOfCharacteristicTypes" = @("PredefinedDataName","Predefined","Ref","DeletionMark","Description","Code","Parent","IsFolder","ValueType")
|
||||||
@@ -220,6 +220,14 @@ $standardAttributesByType = @{
|
|||||||
"DocumentJournal" = @("Type","Ref","Date","Posted","DeletionMark","Number")
|
"DocumentJournal" = @("Type","Ref","Date","Posted","DeletionMark","Number")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Стандартные реквизиты, присутствие которых зависит от свойств объекта: у бухрегистра
|
||||||
|
# PeriodAdjustment — от длины периода корректировки, RecordType — от корреспонденции; у регистра
|
||||||
|
# накопления RecordType — от вида регистра. Их отсутствие законно, в «Missing» не попадают.
|
||||||
|
$stdAttrConditionalNames = @{
|
||||||
|
"AccountingRegister" = @("PeriodAdjustment","RecordType")
|
||||||
|
"AccumulationRegister" = @("RecordType")
|
||||||
|
}
|
||||||
|
|
||||||
# Types that have StandardAttributes block
|
# Types that have StandardAttributes block
|
||||||
$typesWithStdAttrs = @(
|
$typesWithStdAttrs = @(
|
||||||
"Catalog","Document","Enum",
|
"Catalog","Document","Enum",
|
||||||
@@ -279,7 +287,7 @@ $validPropertyValues = @{
|
|||||||
"InformationRegisterPeriodicity" = @("Nonperiodical","Second","Day","Month","Quarter","Year","RecorderPosition")
|
"InformationRegisterPeriodicity" = @("Nonperiodical","Second","Day","Month","Quarter","Year","RecorderPosition")
|
||||||
"RegisterType" = @("Balance","Turnovers")
|
"RegisterType" = @("Balance","Turnovers")
|
||||||
"ReturnValuesReuse" = @("DontUse","DuringRequest","DuringSession")
|
"ReturnValuesReuse" = @("DontUse","DuringRequest","DuringSession")
|
||||||
"ReuseSessions" = @("DontUse","AutoUse")
|
"ReuseSessions" = @("DontUse","Use","AutoUse")
|
||||||
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
|
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
|
||||||
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
|
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
|
||||||
"DataHistory" = @("Use","DontUse")
|
"DataHistory" = @("Use","DontUse")
|
||||||
@@ -344,9 +352,10 @@ if ($root.NamespaceURI -ne $expectedNs) {
|
|||||||
$version = $root.GetAttribute("version")
|
$version = $root.GetAttribute("version")
|
||||||
if (-not $version) {
|
if (-not $version) {
|
||||||
Report-Warn "1. Missing version attribute on MetaDataObject"
|
Report-Warn "1. Missing version attribute on MetaDataObject"
|
||||||
} elseif ($version -notin @("2.17", "2.18", "2.19", "2.20")) {
|
} elseif ($version -notin @("2.17", "2.18", "2.19", "2.20", "2.21")) {
|
||||||
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26), 2.20 (8.3.27).
|
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26),
|
||||||
Report-Warn "1. Unusual version '$version' (expected 2.17-2.20)"
|
# 2.20 (8.3.27), 2.21 (8.5). Версию задаёт платформа ВЫГРУЗКИ, а не режим совместимости.
|
||||||
|
Report-Warn "1. Unusual version '$version' (expected 2.17-2.21)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Detect type element — exactly one child element in md namespace
|
# Detect type element — exactly one child element in md namespace
|
||||||
@@ -606,11 +615,9 @@ if ($typesWithStdAttrs -contains $mdType) {
|
|||||||
if ($saName) {
|
if ($saName) {
|
||||||
$foundNames += $saName
|
$foundNames += $saName
|
||||||
if ($expectedStdAttrs -notcontains $saName) {
|
if ($expectedStdAttrs -notcontains $saName) {
|
||||||
# AccountingRegister has dynamic ExtDimension{N}/ExtDimensionType{N} and optional PeriodAdjustment
|
# AccountingRegister: пары субконто, число которых задаётся планом счетов
|
||||||
$isDynamic = ($mdType -eq "AccountingRegister" -and ($saName -match '^ExtDimension\d+$' -or $saName -match '^ExtDimensionType\d+$' -or $saName -eq "PeriodAdjustment"))
|
$isDynamic = ($mdType -eq "AccountingRegister" -and ($saName -match '^ExtDimension\d+$' -or $saName -match '^ExtDimensionType\d+$'))
|
||||||
# CalculationRegister has conditional period attrs
|
if (-not $isDynamic) {
|
||||||
$isCalcDynamic = ($mdType -eq "CalculationRegister" -and $saName -in @("ActionPeriod","BegOfActionPeriod","EndOfActionPeriod","BegOfBasePeriod","EndOfBasePeriod"))
|
|
||||||
if (-not $isDynamic -and -not $isCalcDynamic) {
|
|
||||||
Report-Warn "5. Unexpected StandardAttribute '$saName' for $mdType"
|
Report-Warn "5. Unexpected StandardAttribute '$saName' for $mdType"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -621,7 +628,8 @@ if ($typesWithStdAttrs -contains $mdType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($expectedStdAttrs) {
|
if ($expectedStdAttrs) {
|
||||||
$missingAttrs = @($expectedStdAttrs | Where-Object { $foundNames -notcontains $_ })
|
$condNames = $stdAttrConditionalNames[$mdType]; if (-not $condNames) { $condNames = @() }
|
||||||
|
$missingAttrs = @($expectedStdAttrs | Where-Object { $foundNames -notcontains $_ -and $condNames -notcontains $_ })
|
||||||
if ($missingAttrs.Count -gt 0) {
|
if ($missingAttrs.Count -gt 0) {
|
||||||
Report-Warn "5. Missing StandardAttributes: $($missingAttrs -join ', ')"
|
Report-Warn "5. Missing StandardAttributes: $($missingAttrs -join ', ')"
|
||||||
}
|
}
|
||||||
@@ -1502,10 +1510,14 @@ if ($script:configDir) {
|
|||||||
$versionedProps = @{
|
$versionedProps = @{
|
||||||
"TypeReductionMode" = "2.18" # режим приведения типов (стандартные реквизиты, измерения РС)
|
"TypeReductionMode" = "2.18" # режим приведения типов (стандартные реквизиты, измерения РС)
|
||||||
"LineNumberLength" = "2.20" # длина номера строки ТЧ (5..9)
|
"LineNumberLength" = "2.20" # длина номера строки ТЧ (5..9)
|
||||||
|
# 2.21 (8.5): подтверждено синтетикой — одни исходники, выгрузка с 8.3.27 и с 8.5.1.
|
||||||
|
"Color" = "2.21" # цвет значения перечисления
|
||||||
|
"AuxiliaryVariantForm" = "2.21" # вспомогательная форма варианта отчёта
|
||||||
|
"UseInInterfaceCompatibilityMode" = "2.21" # использование общей формы в режиме совместимости интерфейса
|
||||||
}
|
}
|
||||||
# Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17").
|
# Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17").
|
||||||
function Get-FormatRank([string]$v) {
|
function Get-FormatRank([string]$ver) {
|
||||||
if ($v -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
$fileRank = Get-FormatRank $version
|
$fileRank = Get-FormatRank $version
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# meta-validate v1.13 — Validate 1C metadata object structure (Python port) (+корневой <Type>: скаляр без структуры = ошибка)
|
# meta-validate v1.18 — Validate 1C metadata object structure (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
|
||||||
@@ -11,6 +11,28 @@ from lxml import etree
|
|||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
# ── arg parsing ──────────────────────────────────────────────
|
# ── arg parsing ──────────────────────────────────────────────
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||||
@@ -18,7 +40,7 @@ parser.add_argument("-ObjectPath", "-Path", required=True)
|
|||||||
parser.add_argument("-Detailed", action="store_true")
|
parser.add_argument("-Detailed", action="store_true")
|
||||||
parser.add_argument("-MaxErrors", type=int, default=30)
|
parser.add_argument("-MaxErrors", type=int, default=30)
|
||||||
parser.add_argument("-OutFile", default="")
|
parser.add_argument("-OutFile", default="")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
detailed = args.Detailed
|
detailed = args.Detailed
|
||||||
max_errors = args.MaxErrors
|
max_errors = args.MaxErrors
|
||||||
@@ -209,7 +231,7 @@ standard_attributes_by_type = {
|
|||||||
"Enum": ["Order", "Ref"],
|
"Enum": ["Order", "Ref"],
|
||||||
"InformationRegister": ["Active", "LineNumber", "Recorder", "Period"],
|
"InformationRegister": ["Active", "LineNumber", "Recorder", "Period"],
|
||||||
"AccumulationRegister": ["Active", "LineNumber", "Recorder", "Period", "RecordType"],
|
"AccumulationRegister": ["Active", "LineNumber", "Recorder", "Period", "RecordType"],
|
||||||
"AccountingRegister": ["Active", "Period", "Recorder", "LineNumber", "Account"],
|
"AccountingRegister": ["Active", "Period", "Recorder", "LineNumber", "Account", "PeriodAdjustment", "RecordType"],
|
||||||
"CalculationRegister": ["Active", "Recorder", "LineNumber", "RegistrationPeriod", "CalculationType", "ReversingEntry", "ActionPeriod", "BegOfActionPeriod", "EndOfActionPeriod", "BegOfBasePeriod", "EndOfBasePeriod"],
|
"CalculationRegister": ["Active", "Recorder", "LineNumber", "RegistrationPeriod", "CalculationType", "ReversingEntry", "ActionPeriod", "BegOfActionPeriod", "EndOfActionPeriod", "BegOfBasePeriod", "EndOfBasePeriod"],
|
||||||
"ChartOfAccounts": ["PredefinedDataName", "Predefined", "Ref", "DeletionMark", "Description", "Code", "Parent", "Order", "Type", "OffBalance"],
|
"ChartOfAccounts": ["PredefinedDataName", "Predefined", "Ref", "DeletionMark", "Description", "Code", "Parent", "Order", "Type", "OffBalance"],
|
||||||
"ChartOfCharacteristicTypes": ["PredefinedDataName", "Predefined", "Ref", "DeletionMark", "Description", "Code", "Parent", "IsFolder", "ValueType"],
|
"ChartOfCharacteristicTypes": ["PredefinedDataName", "Predefined", "Ref", "DeletionMark", "Description", "Code", "Parent", "IsFolder", "ValueType"],
|
||||||
@@ -220,6 +242,14 @@ standard_attributes_by_type = {
|
|||||||
"DocumentJournal": ["Type", "Ref", "Date", "Posted", "DeletionMark", "Number"],
|
"DocumentJournal": ["Type", "Ref", "Date", "Posted", "DeletionMark", "Number"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Стандартные реквизиты, присутствие которых зависит от свойств объекта: у бухрегистра
|
||||||
|
# PeriodAdjustment — от длины периода корректировки, RecordType — от корреспонденции; у регистра
|
||||||
|
# накопления RecordType — от вида регистра. Их отсутствие законно, в «Missing» не попадают.
|
||||||
|
std_attr_conditional_names = {
|
||||||
|
"AccountingRegister": ("PeriodAdjustment", "RecordType"),
|
||||||
|
"AccumulationRegister": ("RecordType",),
|
||||||
|
}
|
||||||
|
|
||||||
# Types that have StandardAttributes block
|
# Types that have StandardAttributes block
|
||||||
types_with_std_attrs = (
|
types_with_std_attrs = (
|
||||||
"Catalog", "Document", "Enum",
|
"Catalog", "Document", "Enum",
|
||||||
@@ -281,7 +311,7 @@ valid_property_values = {
|
|||||||
"InformationRegisterPeriodicity": ["Nonperiodical", "Second", "Day", "Month", "Quarter", "Year", "RecorderPosition"],
|
"InformationRegisterPeriodicity": ["Nonperiodical", "Second", "Day", "Month", "Quarter", "Year", "RecorderPosition"],
|
||||||
"RegisterType": ["Balance", "Turnovers"],
|
"RegisterType": ["Balance", "Turnovers"],
|
||||||
"ReturnValuesReuse": ["DontUse", "DuringRequest", "DuringSession"],
|
"ReturnValuesReuse": ["DontUse", "DuringRequest", "DuringSession"],
|
||||||
"ReuseSessions": ["DontUse", "AutoUse"],
|
"ReuseSessions": ["DontUse", "Use", "AutoUse"],
|
||||||
"FillChecking": ["DontCheck", "ShowError", "ShowWarning"],
|
"FillChecking": ["DontCheck", "ShowError", "ShowWarning"],
|
||||||
"Indexing": ["DontIndex", "Index", "IndexWithAdditionalOrder"],
|
"Indexing": ["DontIndex", "Index", "IndexWithAdditionalOrder"],
|
||||||
"DataHistory": ["Use", "DontUse"],
|
"DataHistory": ["Use", "DontUse"],
|
||||||
@@ -371,9 +401,10 @@ if root_ns != expected_ns:
|
|||||||
version = root.get("version", "")
|
version = root.get("version", "")
|
||||||
if not version:
|
if not version:
|
||||||
report_warn("1. Missing version attribute on MetaDataObject")
|
report_warn("1. Missing version attribute on MetaDataObject")
|
||||||
elif version not in ("2.17", "2.18", "2.19", "2.20"):
|
elif version not in ("2.17", "2.18", "2.19", "2.20", "2.21"):
|
||||||
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26), 2.20 (8.3.27).
|
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26),
|
||||||
report_warn(f"1. Unusual version '{version}' (expected 2.17-2.20)")
|
# 2.20 (8.3.27), 2.21 (8.5). Версию задаёт платформа ВЫГРУЗКИ, а не режим совместимости.
|
||||||
|
report_warn(f"1. Unusual version '{version}' (expected 2.17-2.21)")
|
||||||
|
|
||||||
# Detect type element -- exactly one child element in md namespace
|
# Detect type element -- exactly one child element in md namespace
|
||||||
type_node = None
|
type_node = None
|
||||||
@@ -594,23 +625,19 @@ if md_type in types_with_std_attrs:
|
|||||||
if sa_name:
|
if sa_name:
|
||||||
found_names.append(sa_name)
|
found_names.append(sa_name)
|
||||||
if sa_name not in expected_std_attrs:
|
if sa_name not in expected_std_attrs:
|
||||||
# AccountingRegister has dynamic attrs
|
# AccountingRegister: пары субконто, число которых задаётся планом счетов
|
||||||
is_dynamic = (md_type == "AccountingRegister" and
|
is_dynamic = (md_type == "AccountingRegister" and
|
||||||
(re.match(r'^ExtDimension\d+$', sa_name) or
|
(re.match(r'^ExtDimension\d+$', sa_name) or
|
||||||
re.match(r'^ExtDimensionType\d+$', sa_name) or
|
re.match(r'^ExtDimensionType\d+$', sa_name)))
|
||||||
sa_name == "PeriodAdjustment"))
|
if not is_dynamic:
|
||||||
# CalculationRegister has conditional period attrs
|
|
||||||
is_calc_dynamic = (md_type == "CalculationRegister" and
|
|
||||||
sa_name in ("ActionPeriod", "BegOfActionPeriod", "EndOfActionPeriod",
|
|
||||||
"BegOfBasePeriod", "EndOfBasePeriod"))
|
|
||||||
if not is_dynamic and not is_calc_dynamic:
|
|
||||||
report_warn(f"5. Unexpected StandardAttribute '{sa_name}' for {md_type}")
|
report_warn(f"5. Unexpected StandardAttribute '{sa_name}' for {md_type}")
|
||||||
else:
|
else:
|
||||||
report_error("5. StandardAttribute without 'name' attribute")
|
report_error("5. StandardAttribute without 'name' attribute")
|
||||||
check5_ok = False
|
check5_ok = False
|
||||||
|
|
||||||
if expected_std_attrs:
|
if expected_std_attrs:
|
||||||
missing_attrs = [a for a in expected_std_attrs if a not in found_names]
|
cond_names = std_attr_conditional_names.get(md_type, ())
|
||||||
|
missing_attrs = [a for a in expected_std_attrs if a not in found_names and a not in cond_names]
|
||||||
if missing_attrs:
|
if missing_attrs:
|
||||||
report_warn(f"5. Missing StandardAttributes: {', '.join(missing_attrs)}")
|
report_warn(f"5. Missing StandardAttributes: {', '.join(missing_attrs)}")
|
||||||
|
|
||||||
@@ -1404,12 +1431,16 @@ if config_dir:
|
|||||||
versioned_props = {
|
versioned_props = {
|
||||||
"TypeReductionMode": "2.18", # режим приведения типов (стандартные реквизиты, измерения РС)
|
"TypeReductionMode": "2.18", # режим приведения типов (стандартные реквизиты, измерения РС)
|
||||||
"LineNumberLength": "2.20", # длина номера строки ТЧ (5..9)
|
"LineNumberLength": "2.20", # длина номера строки ТЧ (5..9)
|
||||||
|
# 2.21 (8.5): подтверждено синтетикой — одни исходники, выгрузка с 8.3.27 и с 8.5.1.
|
||||||
|
"Color": "2.21", # цвет значения перечисления
|
||||||
|
"AuxiliaryVariantForm": "2.21", # вспомогательная форма варианта отчёта
|
||||||
|
"UseInInterfaceCompatibilityMode": "2.21", # использование общей формы в режиме совместимости интерфейса
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def format_rank(v):
|
def format_rank(ver):
|
||||||
""""2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17")."""
|
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||||
m = re.match(r'^(\d+)\.(\d+)$', v or '')
|
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# mxl-compile v1.5 — Compile 1C spreadsheet from JSON
|
# mxl-compile v1.14 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -142,6 +142,46 @@ function Assert-EditAllowed([string]$targetPath, [string]$require) {
|
|||||||
} catch { return }
|
} catch { return }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- Detect XML format version ---
|
||||||
|
# У корня <document> нет атрибута version, поэтому версию берём из конфигурации, в дерево
|
||||||
|
# которой пишем макет. Вне конфигурации (автономный .xml, исходники EPF) остаётся 2.17.
|
||||||
|
|
||||||
|
function Detect-FormatVersion([string]$dir) {
|
||||||
|
$d = $dir
|
||||||
|
while ($d) {
|
||||||
|
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||||
|
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||||
|
$extPath = "$d.xml"
|
||||||
|
if (Test-Path $extPath) {
|
||||||
|
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
|
||||||
|
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
|
||||||
|
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||||
|
}
|
||||||
|
$cfgPath = Join-Path $d "Configuration.xml"
|
||||||
|
if (Test-Path $cfgPath) {
|
||||||
|
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
||||||
|
# Длину среза берём по СТРОКЕ, а не по размеру файла: размер в БАЙТАХ, Substring считает
|
||||||
|
# СИМВОЛЫ, и на кириллице байт больше — короткий Configuration.xml ронял навык исключением.
|
||||||
|
$head = $cfgText.Substring(0, [Math]::Min(2000, $cfgText.Length))
|
||||||
|
if ($head -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||||
|
}
|
||||||
|
$parent = Split-Path $d -Parent
|
||||||
|
if ($parent -eq $d) { break }
|
||||||
|
$d = $parent
|
||||||
|
}
|
||||||
|
return "2.17"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209.
|
||||||
|
# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка.
|
||||||
|
function Get-FormatRank([string]$ver) {
|
||||||
|
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$script:outPathResolved = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Join-Path (Get-Location) $OutputPath }
|
||||||
|
$script:formatVersion = Detect-FormatVersion ([System.IO.Path]::GetDirectoryName($script:outPathResolved))
|
||||||
|
|
||||||
# --- 1. Load and validate JSON ---
|
# --- 1. Load and validate JSON ---
|
||||||
|
|
||||||
if (-not (Test-Path $JsonPath)) {
|
if (-not (Test-Path $JsonPath)) {
|
||||||
@@ -437,6 +477,12 @@ foreach ($col in ($colWidthMap.Keys | Sort-Object)) {
|
|||||||
|
|
||||||
# Helper: escape XML special characters
|
# Helper: escape XML special characters
|
||||||
function Esc-Xml {
|
function Esc-Xml {
|
||||||
|
param([string]$s)
|
||||||
|
# Эскейп ЗНАЧЕНИЯ АТРИБУТА: & < > и кавычка — внутри "..." литеральная " невалидна.
|
||||||
|
return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"')
|
||||||
|
}
|
||||||
|
|
||||||
|
function Esc-XmlText {
|
||||||
# Экранирование ТЕКСТА элемента: только & < > . Кавычки в тексте платформа НЕ экранирует —
|
# Экранирование ТЕКСТА элемента: только & < > . Кавычки в тексте платформа НЕ экранирует —
|
||||||
# пишет литерально (проверено: 92142 сырых кавычки на корпус, ни одной "). " платформа
|
# пишет литерально (проверено: 92142 сырых кавычки на корпус, ни одной "). " платформа
|
||||||
# принимает, но при выгрузке нормализует обратно в кавычку → лишний шум в роундтрипе.
|
# принимает, но при выгрузке нормализует обратно в кавычку → лишний шум в роундтрипе.
|
||||||
@@ -512,8 +558,14 @@ function X {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# 7a. Header
|
# 7a. Header
|
||||||
|
$docNsDecl = 'xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" 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"'
|
||||||
|
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (перед style):
|
||||||
|
# платформа держит объявления по алфавиту, дописать в конец нельзя.
|
||||||
|
if ((Get-FormatRank $script:formatVersion) -ge 221) {
|
||||||
|
$docNsDecl = $docNsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
|
||||||
|
}
|
||||||
X '<?xml version="1.0" encoding="UTF-8"?>'
|
X '<?xml version="1.0" encoding="UTF-8"?>'
|
||||||
X '<document xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" 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">'
|
X "<document $docNsDecl>"
|
||||||
|
|
||||||
# 7b. Language settings
|
# 7b. Language settings
|
||||||
X "`t<languageSettings>"
|
X "`t<languageSettings>"
|
||||||
@@ -714,7 +766,7 @@ foreach ($area in $def.areas) {
|
|||||||
X "`t`t`t`t`t<tl>"
|
X "`t`t`t`t`t<tl>"
|
||||||
X "`t`t`t`t`t`t<v8:item>"
|
X "`t`t`t`t`t`t<v8:item>"
|
||||||
X "`t`t`t`t`t`t`t<v8:lang>ru</v8:lang>"
|
X "`t`t`t`t`t`t`t<v8:lang>ru</v8:lang>"
|
||||||
X "`t`t`t`t`t`t`t<v8:content>$(Esc-Xml $cellInfo.Text)</v8:content>"
|
X "`t`t`t`t`t`t`t<v8:content>$(Esc-XmlText $cellInfo.Text)</v8:content>"
|
||||||
X "`t`t`t`t`t`t</v8:item>"
|
X "`t`t`t`t`t`t</v8:item>"
|
||||||
X "`t`t`t`t`t</tl>"
|
X "`t`t`t`t`t</tl>"
|
||||||
}
|
}
|
||||||
@@ -723,7 +775,7 @@ foreach ($area in $def.areas) {
|
|||||||
X "`t`t`t`t`t<tl>"
|
X "`t`t`t`t`t<tl>"
|
||||||
X "`t`t`t`t`t`t<v8:item>"
|
X "`t`t`t`t`t`t<v8:item>"
|
||||||
X "`t`t`t`t`t`t`t<v8:lang>ru</v8:lang>"
|
X "`t`t`t`t`t`t`t<v8:lang>ru</v8:lang>"
|
||||||
X "`t`t`t`t`t`t`t<v8:content>$(Esc-Xml $cellInfo.Template)</v8:content>"
|
X "`t`t`t`t`t`t`t<v8:content>$(Esc-XmlText $cellInfo.Template)</v8:content>"
|
||||||
X "`t`t`t`t`t`t</v8:item>"
|
X "`t`t`t`t`t`t</v8:item>"
|
||||||
X "`t`t`t`t`t</tl>"
|
X "`t`t`t`t`t</tl>"
|
||||||
}
|
}
|
||||||
@@ -839,7 +891,7 @@ foreach ($key in $formatRegistry.Keys) {
|
|||||||
X "`t`t<format>"
|
X "`t`t<format>"
|
||||||
X "`t`t`t<v8:item>"
|
X "`t`t`t<v8:item>"
|
||||||
X "`t`t`t`t<v8:lang>ru</v8:lang>"
|
X "`t`t`t`t<v8:lang>ru</v8:lang>"
|
||||||
X "`t`t`t`t<v8:content>$(Esc-Xml $fmt.NumberFormat)</v8:content>"
|
X "`t`t`t`t<v8:content>$(Esc-XmlText $fmt.NumberFormat)</v8:content>"
|
||||||
X "`t`t`t</v8:item>"
|
X "`t`t`t</v8:item>"
|
||||||
X "`t`t</format>"
|
X "`t`t</format>"
|
||||||
}
|
}
|
||||||
@@ -854,8 +906,13 @@ X '</document>'
|
|||||||
|
|
||||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
$resolvedPath = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Join-Path (Get-Location) $OutputPath }
|
$resolvedPath = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Join-Path (Get-Location) $OutputPath }
|
||||||
|
# Каталог назначения создаём сами: типовой путь — Templates/<Имя>/Ext/Template.xml,
|
||||||
|
# и его может ещё не быть. Так делают и form-compile, и skd-compile, и py-порт этого
|
||||||
|
# навыка; без этого PS-порт падал на «Could not find a part of the path».
|
||||||
|
$outDir = [System.IO.Path]::GetDirectoryName($resolvedPath)
|
||||||
|
if ($outDir -and -not (Test-Path $outDir)) { New-Item -ItemType Directory -Path $outDir -Force | Out-Null }
|
||||||
Assert-EditAllowed $resolvedPath 'editable'
|
Assert-EditAllowed $resolvedPath 'editable'
|
||||||
[System.IO.File]::WriteAllText($resolvedPath, $xml.ToString(), $enc)
|
[System.IO.File]::WriteAllText($resolvedPath, $xml.ToString().TrimEnd("`r", "`n"), $enc)
|
||||||
|
|
||||||
# --- 9. Summary ---
|
# --- 9. Summary ---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# mxl-compile v1.5 — Compile 1C spreadsheet from JSON
|
# mxl-compile v1.14 — Compile 1C spreadsheet from JSON (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||||
# 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
|
||||||
@@ -10,6 +10,65 @@ import sys
|
|||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
class CIDict(dict):
|
||||||
|
# Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные
|
||||||
|
# реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки
|
||||||
|
# сохраняется — от него зависит порядок эмиссии.
|
||||||
|
def _actual(self, key):
|
||||||
|
if not isinstance(key, str) or dict.__contains__(self, key):
|
||||||
|
return key
|
||||||
|
ci = self.__dict__.get('_ci')
|
||||||
|
if ci is None or len(ci) != len(self):
|
||||||
|
ci = {k.lower(): k for k in self if isinstance(k, str)}
|
||||||
|
self.__dict__['_ci'] = ci
|
||||||
|
return ci.get(key.lower(), key)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return dict.__getitem__(self, self._actual(key))
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return dict.__contains__(self, self._actual(key))
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return dict.get(self, self._actual(key), default)
|
||||||
|
|
||||||
|
def pop(self, key, *default):
|
||||||
|
return dict.pop(self, self._actual(key), *default)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
# запись по ключу, отличающемуся регистром, обновляет существующий, а не плодит дубль
|
||||||
|
dict.__setitem__(self, self._actual(key), value)
|
||||||
|
|
||||||
|
def ci_json(obj):
|
||||||
|
"""Рекурсивно оборачивает разобранный JSON: словари → CIDict, списки обходятся."""
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return CIDict((k, ci_json(v)) for k, v in obj.items())
|
||||||
|
if isinstance(obj, list):
|
||||||
|
return [ci_json(v) for v in obj]
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
||||||
@@ -188,23 +247,69 @@ def assert_edit_allowed(target_path, require):
|
|||||||
|
|
||||||
|
|
||||||
def esc_xml(s):
|
def esc_xml(s):
|
||||||
|
# Эскейп ЗНАЧЕНИЯ АТРИБУТА: & < > и кавычка — внутри "..." литеральная " невалидна.
|
||||||
|
return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
|
||||||
|
|
||||||
|
|
||||||
|
def esc_xml_text(s):
|
||||||
"""Экранирование ТЕКСТА элемента: только & < > . Кавычки платформа в тексте не экранирует
|
"""Экранирование ТЕКСТА элемента: только & < > . Кавычки платформа в тексте не экранирует
|
||||||
(92142 сырых кавычки на корпус, ни одной "); " она принимает, но нормализует обратно."""
|
(92142 сырых кавычки на корпус, ни одной "); " она принимает, но нормализует обратно."""
|
||||||
return s.replace('&', '&').replace('<', '<').replace('>', '>')
|
return s.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||||
|
|
||||||
|
|
||||||
def write_utf8_bom(path, content):
|
def write_utf8_bom(path, content):
|
||||||
|
# newline='' — без трансляции: иначе текстовый режим Python дал бы CRLF на Windows
|
||||||
|
# и LF на macOS, то есть вывод навыка зависел бы от ОС.
|
||||||
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def detect_format_version(d):
|
||||||
|
while d:
|
||||||
|
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||||
|
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||||
|
ext_path = d + ".xml"
|
||||||
|
if os.path.isfile(ext_path):
|
||||||
|
with open(ext_path, "r", encoding="utf-8-sig") as f:
|
||||||
|
ext_head = f.read(2000)
|
||||||
|
if re.search(r'<(ExternalDataProcessor|ExternalReport)[ >]', ext_head):
|
||||||
|
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', ext_head)
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
|
cfg_path = os.path.join(d, "Configuration.xml")
|
||||||
|
if os.path.isfile(cfg_path):
|
||||||
|
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||||
|
head = f.read(2000)
|
||||||
|
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', head)
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
|
parent = os.path.dirname(d)
|
||||||
|
if parent == d:
|
||||||
|
break
|
||||||
|
d = parent
|
||||||
|
return "2.17"
|
||||||
|
|
||||||
|
|
||||||
|
def format_rank(ver):
|
||||||
|
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||||
|
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||||
|
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
parser = argparse.ArgumentParser(description='Compile 1C spreadsheet from JSON', allow_abbrev=False)
|
parser = argparse.ArgumentParser(description='Compile 1C spreadsheet from JSON', allow_abbrev=False)
|
||||||
parser.add_argument('-JsonPath', type=str, required=True)
|
parser.add_argument('-JsonPath', type=str, required=True)
|
||||||
parser.add_argument('-OutputPath', type=str, required=True)
|
parser.add_argument('-OutputPath', type=str, required=True)
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
|
# --- Detect XML format version ---
|
||||||
|
# У корня <document> нет атрибута version, поэтому версию берём из конфигурации, в дерево
|
||||||
|
# которой пишем макет. Вне конфигурации (автономный .xml, исходники EPF) остаётся 2.17.
|
||||||
|
out_path_resolved = args.OutputPath if os.path.isabs(args.OutputPath) else os.path.join(os.getcwd(), args.OutputPath)
|
||||||
|
format_version = detect_format_version(os.path.dirname(out_path_resolved))
|
||||||
|
|
||||||
# --- 1. Load and validate JSON ---
|
# --- 1. Load and validate JSON ---
|
||||||
json_path = args.JsonPath
|
json_path = args.JsonPath
|
||||||
@@ -213,7 +318,7 @@ def main():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
with open(json_path, 'r', encoding='utf-8-sig') as f:
|
with open(json_path, 'r', encoding='utf-8-sig') as f:
|
||||||
defn = json.load(f)
|
defn = ci_json(json.load(f))
|
||||||
|
|
||||||
if not defn.get('columns'):
|
if not defn.get('columns'):
|
||||||
print("Required field 'columns' is missing", file=sys.stderr)
|
print("Required field 'columns' is missing", file=sys.stderr)
|
||||||
@@ -496,7 +601,16 @@ def main():
|
|||||||
|
|
||||||
# 7a. Header
|
# 7a. Header
|
||||||
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
|
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
|
||||||
lines.append('<document xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" 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">')
|
doc_ns_decl = ('xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
|
||||||
|
' 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"')
|
||||||
|
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (перед style):
|
||||||
|
# платформа держит объявления по алфавиту, дописать в конец нельзя.
|
||||||
|
if format_rank(format_version) >= 221:
|
||||||
|
doc_ns_decl = doc_ns_decl.replace(
|
||||||
|
' xmlns:style=',
|
||||||
|
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
|
||||||
|
lines.append(f'<document {doc_ns_decl}>')
|
||||||
|
|
||||||
# 7b. Language settings
|
# 7b. Language settings
|
||||||
lines.append('\t<languageSettings>')
|
lines.append('\t<languageSettings>')
|
||||||
@@ -678,7 +792,7 @@ def main():
|
|||||||
lines.append('\t\t\t\t\t<tl>')
|
lines.append('\t\t\t\t\t<tl>')
|
||||||
lines.append('\t\t\t\t\t\t<v8:item>')
|
lines.append('\t\t\t\t\t\t<v8:item>')
|
||||||
lines.append('\t\t\t\t\t\t\t<v8:lang>ru</v8:lang>')
|
lines.append('\t\t\t\t\t\t\t<v8:lang>ru</v8:lang>')
|
||||||
lines.append(f'\t\t\t\t\t\t\t<v8:content>{esc_xml(cell_info["Text"])}</v8:content>')
|
lines.append(f'\t\t\t\t\t\t\t<v8:content>{esc_xml_text(cell_info["Text"])}</v8:content>')
|
||||||
lines.append('\t\t\t\t\t\t</v8:item>')
|
lines.append('\t\t\t\t\t\t</v8:item>')
|
||||||
lines.append('\t\t\t\t\t</tl>')
|
lines.append('\t\t\t\t\t</tl>')
|
||||||
|
|
||||||
@@ -686,7 +800,7 @@ def main():
|
|||||||
lines.append('\t\t\t\t\t<tl>')
|
lines.append('\t\t\t\t\t<tl>')
|
||||||
lines.append('\t\t\t\t\t\t<v8:item>')
|
lines.append('\t\t\t\t\t\t<v8:item>')
|
||||||
lines.append('\t\t\t\t\t\t\t<v8:lang>ru</v8:lang>')
|
lines.append('\t\t\t\t\t\t\t<v8:lang>ru</v8:lang>')
|
||||||
lines.append(f'\t\t\t\t\t\t\t<v8:content>{esc_xml(cell_info["Template"])}</v8:content>')
|
lines.append(f'\t\t\t\t\t\t\t<v8:content>{esc_xml_text(cell_info["Template"])}</v8:content>')
|
||||||
lines.append('\t\t\t\t\t\t</v8:item>')
|
lines.append('\t\t\t\t\t\t</v8:item>')
|
||||||
lines.append('\t\t\t\t\t</tl>')
|
lines.append('\t\t\t\t\t</tl>')
|
||||||
|
|
||||||
@@ -782,7 +896,7 @@ def main():
|
|||||||
lines.append('\t\t<format>')
|
lines.append('\t\t<format>')
|
||||||
lines.append('\t\t\t<v8:item>')
|
lines.append('\t\t\t<v8:item>')
|
||||||
lines.append('\t\t\t\t<v8:lang>ru</v8:lang>')
|
lines.append('\t\t\t\t<v8:lang>ru</v8:lang>')
|
||||||
lines.append(f'\t\t\t\t<v8:content>{esc_xml(fmt["NumberFormat"])}</v8:content>')
|
lines.append(f'\t\t\t\t<v8:content>{esc_xml_text(fmt["NumberFormat"])}</v8:content>')
|
||||||
lines.append('\t\t\t</v8:item>')
|
lines.append('\t\t\t</v8:item>')
|
||||||
lines.append('\t\t</format>')
|
lines.append('\t\t</format>')
|
||||||
|
|
||||||
@@ -802,7 +916,7 @@ def main():
|
|||||||
if out_dir and not os.path.exists(out_dir):
|
if out_dir and not os.path.exists(out_dir):
|
||||||
os.makedirs(out_dir, exist_ok=True)
|
os.makedirs(out_dir, exist_ok=True)
|
||||||
|
|
||||||
content = '\n'.join(lines) + '\n'
|
content = '\r\n'.join(lines)
|
||||||
write_utf8_bom(out_path, content)
|
write_utf8_bom(out_path, content)
|
||||||
|
|
||||||
# --- 9. Summary ---
|
# --- 9. Summary ---
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# mxl-decompile v1.0 — Decompile 1C spreadsheet to JSON
|
# mxl-decompile v1.1 — Decompile 1C spreadsheet to JSON
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# mxl-decompile v1.0 — Decompile 1C spreadsheet to JSON
|
# mxl-decompile v1.1 — Decompile 1C spreadsheet to JSON
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -9,6 +9,28 @@ import sys
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
# --- Namespace map ---
|
# --- Namespace map ---
|
||||||
|
|
||||||
NSMAP = {
|
NSMAP = {
|
||||||
@@ -49,7 +71,7 @@ def main():
|
|||||||
parser = argparse.ArgumentParser(description="Decompile 1C spreadsheet to JSON", allow_abbrev=False)
|
parser = argparse.ArgumentParser(description="Decompile 1C spreadsheet to JSON", allow_abbrev=False)
|
||||||
parser.add_argument("-TemplatePath", "-Path", required=True, help="Path to Template.xml")
|
parser.add_argument("-TemplatePath", "-Path", required=True, help="Path to Template.xml")
|
||||||
parser.add_argument("-OutputPath", default=None, help="Output JSON path (stdout if omitted)")
|
parser.add_argument("-OutputPath", default=None, help="Output JSON path (stdout if omitted)")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
template_path = args.TemplatePath
|
template_path = args.TemplatePath
|
||||||
output_path = args.OutputPath
|
output_path = args.OutputPath
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# mxl-info v1.2 — Analyze 1C spreadsheet structure
|
# mxl-info v1.4 — Analyze 1C spreadsheet structure (+единое имя хелпера состояния поддержки)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Alias('Path')]
|
[Alias('Path')]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# mxl-info v1.2 — Analyze 1C spreadsheet structure
|
# mxl-info v1.4 — Analyze 1C spreadsheet structure (+единое имя хелпера состояния поддержки)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -12,6 +12,28 @@ from lxml import etree
|
|||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
# --- Argument parsing ---
|
# --- Argument parsing ---
|
||||||
parser = argparse.ArgumentParser(description="Analyze 1C spreadsheet (MXL) structure", allow_abbrev=False)
|
parser = argparse.ArgumentParser(description="Analyze 1C spreadsheet (MXL) structure", allow_abbrev=False)
|
||||||
parser.add_argument("-TemplatePath", "-Path", default="", help="Path to Template.xml")
|
parser.add_argument("-TemplatePath", "-Path", default="", help="Path to Template.xml")
|
||||||
@@ -23,7 +45,7 @@ parser.add_argument("-WithText", action="store_true", default=False, help="Inclu
|
|||||||
parser.add_argument("-MaxParams", type=int, default=10, help="Max parameters to show per area")
|
parser.add_argument("-MaxParams", type=int, default=10, help="Max parameters to show per area")
|
||||||
parser.add_argument("-Limit", type=int, default=150, help="Max lines to show")
|
parser.add_argument("-Limit", type=int, default=150, help="Max lines to show")
|
||||||
parser.add_argument("-Offset", type=int, default=0, help="Lines to skip")
|
parser.add_argument("-Offset", type=int, default=0, help="Lines to skip")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
# --- Resolve template path ---
|
# --- Resolve template path ---
|
||||||
template_path = args.TemplatePath
|
template_path = args.TemplatePath
|
||||||
@@ -321,7 +343,7 @@ def get_support_status_for_path(target_path):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
def is_external_root(xml_path):
|
def _sg_is_external_root(xml_path):
|
||||||
if not os.path.isfile(xml_path):
|
if not os.path.isfile(xml_path):
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
@@ -335,14 +357,14 @@ def get_support_status_for_path(target_path):
|
|||||||
rp = os.path.abspath(target_path)
|
rp = os.path.abspath(target_path)
|
||||||
# The target file itself may be the element meta-xml (e.g. Subsystems/X.xml).
|
# The target file itself may be the element meta-xml (e.g. Subsystems/X.xml).
|
||||||
elem_uuid = root_uuid(rp)
|
elem_uuid = root_uuid(rp)
|
||||||
if is_external_root(rp):
|
if _sg_is_external_root(rp):
|
||||||
return None
|
return None
|
||||||
bin_path = None
|
bin_path = None
|
||||||
d = os.path.dirname(rp)
|
d = os.path.dirname(rp)
|
||||||
for _ in range(12):
|
for _ in range(12):
|
||||||
if not d:
|
if not d:
|
||||||
break
|
break
|
||||||
if is_external_root(d + ".xml"):
|
if _sg_is_external_root(d + ".xml"):
|
||||||
return None
|
return None
|
||||||
if not elem_uuid:
|
if not elem_uuid:
|
||||||
elem_uuid = root_uuid(d + ".xml")
|
elem_uuid = root_uuid(d + ".xml")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# mxl-validate v1.1 — Validate 1C spreadsheet
|
# mxl-validate v1.2 — Validate 1C spreadsheet
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Alias('Path')]
|
[Alias('Path')]
|
||||||
|
|||||||
@@ -1,10 +1,32 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# mxl-validate v1.1 — Validate 1C spreadsheet document Template.xml
|
# mxl-validate v1.2 — Validate 1C spreadsheet document Template.xml
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
"""Validates spreadsheet Template.xml: height, palette refs, column/row indices, areas, merges."""
|
"""Validates spreadsheet Template.xml: height, palette refs, column/row indices, areas, merges."""
|
||||||
import sys, os, argparse
|
import sys, os, argparse
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
NS_D = 'http://v8.1c.ru/8.2/data/spreadsheet'
|
NS_D = 'http://v8.1c.ru/8.2/data/spreadsheet'
|
||||||
NS_V8 = 'http://v8.1c.ru/8.1/data/core'
|
NS_V8 = 'http://v8.1c.ru/8.1/data/core'
|
||||||
NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
|
NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
|
||||||
@@ -61,7 +83,7 @@ def main():
|
|||||||
parser.add_argument('-SrcDir', dest='SrcDir', default='src')
|
parser.add_argument('-SrcDir', dest='SrcDir', default='src')
|
||||||
parser.add_argument('-Detailed', action='store_true')
|
parser.add_argument('-Detailed', action='store_true')
|
||||||
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=20)
|
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=20)
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
template_path = args.TemplatePath
|
template_path = args.TemplatePath
|
||||||
processor_name = args.ProcessorName
|
processor_name = args.ProcessorName
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# role-compile v1.10 — Compile 1C role from JSON
|
# role-compile v1.24 — Compile 1C role from JSON (+русские алиасы типов: формы с ё и без)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -171,6 +171,12 @@ function X {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Esc-Xml {
|
function Esc-Xml {
|
||||||
|
param([string]$s)
|
||||||
|
# Эскейп ЗНАЧЕНИЯ АТРИБУТА: & < > и кавычка — внутри "..." литеральная " невалидна.
|
||||||
|
return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"')
|
||||||
|
}
|
||||||
|
|
||||||
|
function Esc-XmlText {
|
||||||
# Экранирование ТЕКСТА элемента: только & < > . Кавычки в тексте платформа НЕ экранирует —
|
# Экранирование ТЕКСТА элемента: только & < > . Кавычки в тексте платформа НЕ экранирует —
|
||||||
# пишет литерально (проверено: 92142 сырых кавычки на корпус, ни одной "). " платформа
|
# пишет литерально (проверено: 92142 сырых кавычки на корпус, ни одной "). " платформа
|
||||||
# принимает, но при выгрузке нормализует обратно в кавычку → лишний шум в роундтрипе.
|
# принимает, но при выгрузке нормализует обратно в кавычку → лишний шум в роундтрипе.
|
||||||
@@ -187,15 +193,18 @@ $script:typeAliases = @{
|
|||||||
"РегистрНакопления" = "AccumulationRegister"
|
"РегистрНакопления" = "AccumulationRegister"
|
||||||
"РегистрБухгалтерии" = "AccountingRegister"
|
"РегистрБухгалтерии" = "AccountingRegister"
|
||||||
"РегистрРасчета" = "CalculationRegister"
|
"РегистрРасчета" = "CalculationRegister"
|
||||||
|
"РегистрРасчёта" = "CalculationRegister"
|
||||||
"Константа" = "Constant"
|
"Константа" = "Constant"
|
||||||
"ПланСчетов" = "ChartOfAccounts"
|
"ПланСчетов" = "ChartOfAccounts"
|
||||||
"ПланВидовХарактеристик" = "ChartOfCharacteristicTypes"
|
"ПланВидовХарактеристик" = "ChartOfCharacteristicTypes"
|
||||||
"ПланВидовРасчета" = "ChartOfCalculationTypes"
|
"ПланВидовРасчета" = "ChartOfCalculationTypes"
|
||||||
|
"ПланВидовРасчёта" = "ChartOfCalculationTypes"
|
||||||
"ПланОбмена" = "ExchangePlan"
|
"ПланОбмена" = "ExchangePlan"
|
||||||
"БизнесПроцесс" = "BusinessProcess"
|
"БизнесПроцесс" = "BusinessProcess"
|
||||||
"Задача" = "Task"
|
"Задача" = "Task"
|
||||||
"Обработка" = "DataProcessor"
|
"Обработка" = "DataProcessor"
|
||||||
"Отчет" = "Report"
|
"Отчет" = "Report"
|
||||||
|
"Отчёт" = "Report"
|
||||||
"ОбщаяФорма" = "CommonForm"
|
"ОбщаяФорма" = "CommonForm"
|
||||||
"ОбщаяКоманда" = "CommonCommand"
|
"ОбщаяКоманда" = "CommonCommand"
|
||||||
"Подсистема" = "Subsystem"
|
"Подсистема" = "Subsystem"
|
||||||
@@ -644,6 +653,14 @@ if ($def.objects) {
|
|||||||
function Detect-FormatVersion([string]$dir) {
|
function Detect-FormatVersion([string]$dir) {
|
||||||
$d = $dir
|
$d = $dir
|
||||||
while ($d) {
|
while ($d) {
|
||||||
|
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||||
|
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||||
|
$extPath = "$d.xml"
|
||||||
|
if (Test-Path $extPath) {
|
||||||
|
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
|
||||||
|
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
|
||||||
|
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||||
|
}
|
||||||
$cfgPath = Join-Path $d "Configuration.xml"
|
$cfgPath = Join-Path $d "Configuration.xml"
|
||||||
if (Test-Path $cfgPath) {
|
if (Test-Path $cfgPath) {
|
||||||
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
||||||
@@ -659,7 +676,14 @@ function Detect-FormatVersion([string]$dir) {
|
|||||||
return "2.17"
|
return "2.17"
|
||||||
}
|
}
|
||||||
|
|
||||||
$resolvedOutputDir = if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path (Get-Location) $OutputDir }
|
# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209.
|
||||||
|
# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка.
|
||||||
|
function Get-FormatRank([string]$ver) {
|
||||||
|
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$resolvedOutputDir =if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path (Get-Location) $OutputDir }
|
||||||
Assert-EditAllowed $resolvedOutputDir 'editable'
|
Assert-EditAllowed $resolvedOutputDir 'editable'
|
||||||
$formatVersion = Detect-FormatVersion $resolvedOutputDir
|
$formatVersion = Detect-FormatVersion $resolvedOutputDir
|
||||||
|
|
||||||
@@ -672,40 +696,30 @@ $uuid = [guid]::NewGuid().ToString()
|
|||||||
$script:xmlBuf = New-Object System.Text.StringBuilder 4096
|
$script:xmlBuf = New-Object System.Text.StringBuilder 4096
|
||||||
|
|
||||||
X '<?xml version="1.0" encoding="UTF-8"?>'
|
X '<?xml version="1.0" encoding="UTF-8"?>'
|
||||||
X '<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses"'
|
# Объявления пространств имён — одной переменной и одной строкой, как пишет платформа.
|
||||||
X ' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"'
|
$xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||||
X ' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"'
|
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (после lf, перед style):
|
||||||
X ' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"'
|
# платформа держит объявления по алфавиту. В Rights.xml палитра НЕ идёт (проверено по выгрузке 8.5).
|
||||||
X ' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"'
|
if ((Get-FormatRank $formatVersion) -ge 221) {
|
||||||
X ' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"'
|
$xmlnsDecl = $xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
|
||||||
X ' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
|
|
||||||
X ' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"'
|
|
||||||
X ' xmlns:v8="http://v8.1c.ru/8.1/data/core"'
|
|
||||||
X ' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"'
|
|
||||||
X ' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"'
|
|
||||||
X ' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"'
|
|
||||||
X ' xmlns:xen="http://v8.1c.ru/8.3/xcf/enums"'
|
|
||||||
X ' xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef"'
|
|
||||||
X ' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
|
|
||||||
X ' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
|
||||||
X ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
|
||||||
X " version=`"$formatVersion`">"
|
|
||||||
X " <Role uuid=`"$uuid`">"
|
|
||||||
X ' <Properties>'
|
|
||||||
X " <Name>$roleName</Name>"
|
|
||||||
X ' <Synonym>'
|
|
||||||
X ' <v8:item>'
|
|
||||||
X ' <v8:lang>ru</v8:lang>'
|
|
||||||
X " <v8:content>$(Esc-Xml $synonym)</v8:content>"
|
|
||||||
X ' </v8:item>'
|
|
||||||
X ' </Synonym>'
|
|
||||||
if ($comment) {
|
|
||||||
X " <Comment>$(Esc-Xml $comment)</Comment>"
|
|
||||||
} else {
|
|
||||||
X ' <Comment/>'
|
|
||||||
}
|
}
|
||||||
X ' </Properties>'
|
X "<MetaDataObject $xmlnsDecl version=`"$formatVersion`">"
|
||||||
X ' </Role>'
|
X "`t<Role uuid=`"$uuid`">"
|
||||||
|
X "`t`t<Properties>"
|
||||||
|
X "`t`t`t<Name>$roleName</Name>"
|
||||||
|
X "`t`t`t<Synonym>"
|
||||||
|
X "`t`t`t`t<v8:item>"
|
||||||
|
X "`t`t`t`t`t<v8:lang>ru</v8:lang>"
|
||||||
|
X "`t`t`t`t`t<v8:content>$(Esc-XmlText $synonym)</v8:content>"
|
||||||
|
X "`t`t`t`t</v8:item>"
|
||||||
|
X "`t`t`t</Synonym>"
|
||||||
|
if ($comment) {
|
||||||
|
X "`t`t`t<Comment>$(Esc-XmlText $comment)</Comment>"
|
||||||
|
} else {
|
||||||
|
X "`t`t`t<Comment/>"
|
||||||
|
}
|
||||||
|
X "`t`t</Properties>"
|
||||||
|
X "`t</Role>"
|
||||||
X '</MetaDataObject>'
|
X '</MetaDataObject>'
|
||||||
|
|
||||||
$metadataXml = $script:xmlBuf.ToString()
|
$metadataXml = $script:xmlBuf.ToString()
|
||||||
@@ -715,48 +729,45 @@ $metadataXml = $script:xmlBuf.ToString()
|
|||||||
$script:xmlBuf = New-Object System.Text.StringBuilder 8192
|
$script:xmlBuf = New-Object System.Text.StringBuilder 8192
|
||||||
|
|
||||||
X '<?xml version="1.0" encoding="UTF-8"?>'
|
X '<?xml version="1.0" encoding="UTF-8"?>'
|
||||||
X '<Rights xmlns="http://v8.1c.ru/8.2/roles"'
|
X "<Rights xmlns=`"http://v8.1c.ru/8.2/roles`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" xsi:type=`"Rights`" version=`"$formatVersion`">"
|
||||||
X ' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
|
||||||
X ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
|
||||||
X " xsi:type=`"Rights`" version=`"$formatVersion`">"
|
|
||||||
|
|
||||||
# Global flags (defaults match typical 1C roles)
|
# Global flags (defaults match typical 1C roles)
|
||||||
$sfno = if ($null -ne $def.setForNewObjects) { "$($def.setForNewObjects)".ToLower() } else { "false" }
|
$sfno = if ($null -ne $def.setForNewObjects) { "$($def.setForNewObjects)".ToLower() } else { "false" }
|
||||||
$sfab = if ($null -ne $def.setForAttributesByDefault) { "$($def.setForAttributesByDefault)".ToLower() } else { "true" }
|
$sfab = if ($null -ne $def.setForAttributesByDefault) { "$($def.setForAttributesByDefault)".ToLower() } else { "true" }
|
||||||
$irco = if ($null -ne $def.independentRightsOfChildObjects) { "$($def.independentRightsOfChildObjects)".ToLower() } else { "false" }
|
$irco = if ($null -ne $def.independentRightsOfChildObjects) { "$($def.independentRightsOfChildObjects)".ToLower() } else { "false" }
|
||||||
|
|
||||||
X " <setForNewObjects>$sfno</setForNewObjects>"
|
X "`t<setForNewObjects>$sfno</setForNewObjects>"
|
||||||
X " <setForAttributesByDefault>$sfab</setForAttributesByDefault>"
|
X "`t<setForAttributesByDefault>$sfab</setForAttributesByDefault>"
|
||||||
X " <independentRightsOfChildObjects>$irco</independentRightsOfChildObjects>"
|
X "`t<independentRightsOfChildObjects>$irco</independentRightsOfChildObjects>"
|
||||||
|
|
||||||
# Object blocks
|
# Object blocks
|
||||||
$totalRights = 0
|
$totalRights = 0
|
||||||
foreach ($obj in $parsedObjects) {
|
foreach ($obj in $parsedObjects) {
|
||||||
X ' <object>'
|
X "`t<object>"
|
||||||
X " <name>$($obj.Name)</name>"
|
X "`t`t<name>$($obj.Name)</name>"
|
||||||
foreach ($right in $obj.Rights) {
|
foreach ($right in $obj.Rights) {
|
||||||
X ' <right>'
|
X "`t`t<right>"
|
||||||
X " <name>$($right.Name)</name>"
|
X "`t`t`t<name>$($right.Name)</name>"
|
||||||
X " <value>$($right.Value)</value>"
|
X "`t`t`t<value>$($right.Value)</value>"
|
||||||
if ($right.Condition) {
|
if ($right.Condition) {
|
||||||
X ' <restrictionByCondition>'
|
X "`t`t`t<restrictionByCondition>"
|
||||||
X " <condition>$(Esc-Xml $right.Condition)</condition>"
|
X "`t`t`t`t<condition>$(Esc-XmlText $right.Condition)</condition>"
|
||||||
X ' </restrictionByCondition>'
|
X "`t`t`t</restrictionByCondition>"
|
||||||
}
|
}
|
||||||
X ' </right>'
|
X "`t`t</right>"
|
||||||
$totalRights++
|
$totalRights++
|
||||||
}
|
}
|
||||||
X ' </object>'
|
X "`t</object>"
|
||||||
}
|
}
|
||||||
|
|
||||||
# RLS restriction templates
|
# RLS restriction templates
|
||||||
$templateCount = 0
|
$templateCount = 0
|
||||||
if ($def.templates) {
|
if ($def.templates) {
|
||||||
foreach ($tpl in $def.templates) {
|
foreach ($tpl in $def.templates) {
|
||||||
X ' <restrictionTemplate>'
|
X "`t<restrictionTemplate>"
|
||||||
X " <name>$(Esc-Xml "$($tpl.name)")</name>"
|
X "`t`t<name>$(Esc-XmlText "$($tpl.name)")</name>"
|
||||||
X " <condition>$(Esc-Xml "$($tpl.condition)")</condition>"
|
X "`t`t<condition>$(Esc-XmlText "$($tpl.condition)")</condition>"
|
||||||
X ' </restrictionTemplate>'
|
X "`t</restrictionTemplate>"
|
||||||
$templateCount++
|
$templateCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -799,8 +810,8 @@ if (-not (Test-Path $extDir)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||||
[System.IO.File]::WriteAllText($metadataPath, $metadataXml, $enc)
|
[System.IO.File]::WriteAllText($metadataPath, $metadataXml.TrimEnd("`r", "`n"), $enc)
|
||||||
[System.IO.File]::WriteAllText($rightsPath, $rightsXml, $enc)
|
[System.IO.File]::WriteAllText($rightsPath, $rightsXml.TrimEnd("`r", "`n"), $enc)
|
||||||
|
|
||||||
# --- 12. Register in Configuration.xml ---
|
# --- 12. Register in Configuration.xml ---
|
||||||
|
|
||||||
@@ -856,11 +867,26 @@ if (Test-Path $configXmlPath) {
|
|||||||
$cfgSettings = New-Object System.Xml.XmlWriterSettings
|
$cfgSettings = New-Object System.Xml.XmlWriterSettings
|
||||||
$cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
$cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
||||||
$cfgSettings.Indent = $false
|
$cfgSettings.Indent = $false
|
||||||
$stream = New-Object System.IO.FileStream($configXmlPath, [System.IO.FileMode]::Create)
|
$cfgSettings.NewLineHandling = [System.Xml.NewLineHandling]::None
|
||||||
$writer = [System.Xml.XmlWriter]::Create($stream, $cfgSettings)
|
# Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
|
||||||
|
$memStream = New-Object System.IO.MemoryStream
|
||||||
|
$writer = [System.Xml.XmlWriter]::Create($memStream, $cfgSettings)
|
||||||
$configDoc.Save($writer)
|
$configDoc.Save($writer)
|
||||||
$writer.Close()
|
$writer.Flush(); $writer.Close()
|
||||||
$stream.Close()
|
|
||||||
|
$cfgText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
|
||||||
|
$memStream.Close()
|
||||||
|
if ($cfgText.Length -gt 0 -and $cfgText[0] -eq [char]0xFEFF) { $cfgText = $cfgText.Substring(1) }
|
||||||
|
$cfgText = $cfgText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$cfgText = [regex]::Replace($cfgText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $configXmlPath) -and ([System.IO.File]::ReadAllText($configXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$cfgText = ($cfgText -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
|
[System.IO.File]::WriteAllText($configXmlPath, $cfgText, (New-Object System.Text.UTF8Encoding($true)))
|
||||||
|
|
||||||
$regResult = "added"
|
$regResult = "added"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# role-compile v1.10 — Compile 1C role from JSON
|
# role-compile v1.24 — Compile 1C role 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
|
||||||
@@ -10,6 +10,65 @@ import uuid
|
|||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
class CIDict(dict):
|
||||||
|
# Ключи храним КАК ЕСТЬ: часть из них — имена объектов (табличные части, стандартные
|
||||||
|
# реквизиты), они попадают в XML. Регистронезависим только поиск. Порядок вставки
|
||||||
|
# сохраняется — от него зависит порядок эмиссии.
|
||||||
|
def _actual(self, key):
|
||||||
|
if not isinstance(key, str) or dict.__contains__(self, key):
|
||||||
|
return key
|
||||||
|
ci = self.__dict__.get('_ci')
|
||||||
|
if ci is None or len(ci) != len(self):
|
||||||
|
ci = {k.lower(): k for k in self if isinstance(k, str)}
|
||||||
|
self.__dict__['_ci'] = ci
|
||||||
|
return ci.get(key.lower(), key)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return dict.__getitem__(self, self._actual(key))
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return dict.__contains__(self, self._actual(key))
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return dict.get(self, self._actual(key), default)
|
||||||
|
|
||||||
|
def pop(self, key, *default):
|
||||||
|
return dict.pop(self, self._actual(key), *default)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
# запись по ключу, отличающемуся регистром, обновляет существующий, а не плодит дубль
|
||||||
|
dict.__setitem__(self, self._actual(key), value)
|
||||||
|
|
||||||
|
def ci_json(obj):
|
||||||
|
"""Рекурсивно оборачивает разобранный JSON: словари → CIDict, списки обходятся."""
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return CIDict((k, ci_json(v)) for k, v in obj.items())
|
||||||
|
if isinstance(obj, list):
|
||||||
|
return [ci_json(v) for v in obj]
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
# Support guard (Ext/ParentConfigurations.bin) — see docs/1c-support-state-spec.md
|
||||||
@@ -189,6 +248,16 @@ def assert_edit_allowed(target_path, require):
|
|||||||
|
|
||||||
def detect_format_version(d):
|
def detect_format_version(d):
|
||||||
while d:
|
while d:
|
||||||
|
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||||
|
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||||
|
ext_path = d + ".xml"
|
||||||
|
if os.path.isfile(ext_path):
|
||||||
|
with open(ext_path, "r", encoding="utf-8-sig") as f:
|
||||||
|
ext_head = f.read(2000)
|
||||||
|
if re.search(r'<(ExternalDataProcessor|ExternalReport)[ >]', ext_head):
|
||||||
|
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', ext_head)
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
cfg_path = os.path.join(d, "Configuration.xml")
|
cfg_path = os.path.join(d, "Configuration.xml")
|
||||||
if os.path.isfile(cfg_path):
|
if os.path.isfile(cfg_path):
|
||||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||||
@@ -203,7 +272,26 @@ def detect_format_version(d):
|
|||||||
return "2.17"
|
return "2.17"
|
||||||
|
|
||||||
|
|
||||||
|
def format_rank(ver):
|
||||||
|
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||||
|
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||||
|
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||||
|
|
||||||
|
|
||||||
|
def detect_eol(text):
|
||||||
|
# Перевод строки ВСТАВКИ берём из самого файла: канон CRLF относится к файлам,
|
||||||
|
# которые мы создаём, а правка существующего сохраняет его стиль (#44/#46/#47).
|
||||||
|
# Семантика та же, что у _detect_xml_style в остальных портах: есть CRLF → CRLF.
|
||||||
|
# Мажоритарное правило здесь было расхождением — на смешанном входе оно давало
|
||||||
|
# другой ответ, чем канон, при том же назначении.
|
||||||
|
return '\r\n' if '\r\n' in text else '\n'
|
||||||
|
|
||||||
def esc_xml(s):
|
def esc_xml(s):
|
||||||
|
# Эскейп ЗНАЧЕНИЯ АТРИБУТА: & < > и кавычка — внутри "..." литеральная " невалидна.
|
||||||
|
return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
|
||||||
|
|
||||||
|
|
||||||
|
def esc_xml_text(s):
|
||||||
"""Экранирование ТЕКСТА элемента: только & < > . Кавычки платформа в тексте не экранирует
|
"""Экранирование ТЕКСТА элемента: только & < > . Кавычки платформа в тексте не экранирует
|
||||||
(92142 сырых кавычки на корпус, ни одной "); " она принимает, но нормализует обратно."""
|
(92142 сырых кавычки на корпус, ни одной "); " она принимает, но нормализует обратно."""
|
||||||
return s.replace('&', '&').replace('<', '<').replace('>', '>')
|
return s.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||||
@@ -216,7 +304,7 @@ def emit_mltext(lines, indent, tag, text):
|
|||||||
lines.append(f"{indent}<{tag}>")
|
lines.append(f"{indent}<{tag}>")
|
||||||
lines.append(f"{indent}\t<v8:item>")
|
lines.append(f"{indent}\t<v8:item>")
|
||||||
lines.append(f"{indent}\t\t<v8:lang>ru</v8:lang>")
|
lines.append(f"{indent}\t\t<v8:lang>ru</v8:lang>")
|
||||||
lines.append(f"{indent}\t\t<v8:content>{esc_xml(text)}</v8:content>")
|
lines.append(f"{indent}\t\t<v8:content>{esc_xml_text(text)}</v8:content>")
|
||||||
lines.append(f"{indent}\t</v8:item>")
|
lines.append(f"{indent}\t</v8:item>")
|
||||||
lines.append(f"{indent}</{tag}>")
|
lines.append(f"{indent}</{tag}>")
|
||||||
|
|
||||||
@@ -226,10 +314,13 @@ def new_uuid():
|
|||||||
|
|
||||||
|
|
||||||
def write_utf8_bom(path, content):
|
def write_utf8_bom(path, content):
|
||||||
|
# newline='' — без трансляции: иначе текстовый режим Python дал бы CRLF на Windows
|
||||||
|
# и LF на macOS, то есть вывод навыка зависел бы от ОС.
|
||||||
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# --- Russian synonyms -> canonical English names ---
|
# --- Russian synonyms -> canonical English names ---
|
||||||
|
|
||||||
TYPE_ALIASES = {
|
TYPE_ALIASES = {
|
||||||
@@ -239,15 +330,18 @@ TYPE_ALIASES = {
|
|||||||
"РегистрНакопления": "AccumulationRegister",
|
"РегистрНакопления": "AccumulationRegister",
|
||||||
"РегистрБухгалтерии": "AccountingRegister",
|
"РегистрБухгалтерии": "AccountingRegister",
|
||||||
"РегистрРасчета": "CalculationRegister",
|
"РегистрРасчета": "CalculationRegister",
|
||||||
|
"РегистрРасчёта": "CalculationRegister",
|
||||||
"Константа": "Constant",
|
"Константа": "Constant",
|
||||||
"ПланСчетов": "ChartOfAccounts",
|
"ПланСчетов": "ChartOfAccounts",
|
||||||
"ПланВидовХарактеристик": "ChartOfCharacteristicTypes",
|
"ПланВидовХарактеристик": "ChartOfCharacteristicTypes",
|
||||||
"ПланВидовРасчета": "ChartOfCalculationTypes",
|
"ПланВидовРасчета": "ChartOfCalculationTypes",
|
||||||
|
"ПланВидовРасчёта": "ChartOfCalculationTypes",
|
||||||
"ПланОбмена": "ExchangePlan",
|
"ПланОбмена": "ExchangePlan",
|
||||||
"БизнесПроцесс": "BusinessProcess",
|
"БизнесПроцесс": "BusinessProcess",
|
||||||
"Задача": "Task",
|
"Задача": "Task",
|
||||||
"Обработка": "DataProcessor",
|
"Обработка": "DataProcessor",
|
||||||
"Отчет": "Report",
|
"Отчет": "Report",
|
||||||
|
"Отчёт": "Report",
|
||||||
"ОбщаяФорма": "CommonForm",
|
"ОбщаяФорма": "CommonForm",
|
||||||
"ОбщаяКоманда": "CommonCommand",
|
"ОбщаяКоманда": "CommonCommand",
|
||||||
"Подсистема": "Subsystem",
|
"Подсистема": "Subsystem",
|
||||||
@@ -632,7 +726,7 @@ def main():
|
|||||||
parser = argparse.ArgumentParser(description='Compile 1C role from JSON', allow_abbrev=False)
|
parser = argparse.ArgumentParser(description='Compile 1C role from JSON', allow_abbrev=False)
|
||||||
parser.add_argument('-JsonPath', type=str, required=True)
|
parser.add_argument('-JsonPath', type=str, required=True)
|
||||||
parser.add_argument('-OutputDir', type=str, required=True)
|
parser.add_argument('-OutputDir', type=str, required=True)
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
# --- 1. Load and validate JSON ---
|
# --- 1. Load and validate JSON ---
|
||||||
json_path = args.JsonPath
|
json_path = args.JsonPath
|
||||||
@@ -641,7 +735,7 @@ def main():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
with open(json_path, 'r', encoding='utf-8-sig') as f:
|
with open(json_path, 'r', encoding='utf-8-sig') as f:
|
||||||
defn = json.load(f)
|
defn = ci_json(json.load(f))
|
||||||
|
|
||||||
if not defn.get('name'):
|
if not defn.get('name'):
|
||||||
print("JSON must have 'name' field (role programmatic name)", file=sys.stderr)
|
print("JSON must have 'name' field (role programmatic name)", file=sys.stderr)
|
||||||
@@ -673,90 +767,80 @@ def main():
|
|||||||
# --- 4. Emit metadata XML (Roles/Name.xml) ---
|
# --- 4. Emit metadata XML (Roles/Name.xml) ---
|
||||||
lines = []
|
lines = []
|
||||||
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
|
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
|
||||||
lines.append('<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses"')
|
# Объявления пространств имён — одной переменной и одной строкой, как пишет платформа.
|
||||||
lines.append(' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"')
|
xmlns_decl = ('xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"')
|
||||||
lines.append(' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"')
|
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (после lf, перед
|
||||||
lines.append(' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"')
|
# style): платформа держит объявления по алфавиту. В Rights.xml палитра НЕ идёт.
|
||||||
lines.append(' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"')
|
if format_rank(format_version) >= 221:
|
||||||
lines.append(' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"')
|
xmlns_decl = xmlns_decl.replace(
|
||||||
lines.append(' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"')
|
' xmlns:style=',
|
||||||
lines.append(' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"')
|
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
|
||||||
lines.append(' xmlns:v8="http://v8.1c.ru/8.1/data/core"')
|
lines.append(f'<MetaDataObject {xmlns_decl} version="{format_version}">')
|
||||||
lines.append(' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"')
|
lines.append(f'\t<Role uuid="{uid}">')
|
||||||
lines.append(' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"')
|
lines.append('\t\t<Properties>')
|
||||||
lines.append(' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"')
|
lines.append(f'\t\t\t<Name>{role_name}</Name>')
|
||||||
lines.append(' xmlns:xen="http://v8.1c.ru/8.3/xcf/enums"')
|
lines.append('\t\t\t<Synonym>')
|
||||||
lines.append(' xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef"')
|
lines.append('\t\t\t\t<v8:item>')
|
||||||
lines.append(' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"')
|
lines.append('\t\t\t\t\t<v8:lang>ru</v8:lang>')
|
||||||
lines.append(' xmlns:xs="http://www.w3.org/2001/XMLSchema"')
|
lines.append(f'\t\t\t\t\t<v8:content>{esc_xml_text(synonym)}</v8:content>')
|
||||||
lines.append(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"')
|
lines.append('\t\t\t\t</v8:item>')
|
||||||
lines.append(f' version="{format_version}">')
|
lines.append('\t\t\t</Synonym>')
|
||||||
lines.append(f' <Role uuid="{uid}">')
|
|
||||||
lines.append(' <Properties>')
|
|
||||||
lines.append(f' <Name>{role_name}</Name>')
|
|
||||||
lines.append(' <Synonym>')
|
|
||||||
lines.append(' <v8:item>')
|
|
||||||
lines.append(' <v8:lang>ru</v8:lang>')
|
|
||||||
lines.append(f' <v8:content>{esc_xml(synonym)}</v8:content>')
|
|
||||||
lines.append(' </v8:item>')
|
|
||||||
lines.append(' </Synonym>')
|
|
||||||
if comment:
|
if comment:
|
||||||
lines.append(f' <Comment>{esc_xml(comment)}</Comment>')
|
lines.append(f'\t\t\t<Comment>{esc_xml_text(comment)}</Comment>')
|
||||||
else:
|
else:
|
||||||
lines.append(' <Comment/>')
|
lines.append('\t\t\t<Comment/>')
|
||||||
lines.append(' </Properties>')
|
lines.append('\t\t</Properties>')
|
||||||
lines.append(' </Role>')
|
lines.append('\t</Role>')
|
||||||
lines.append('</MetaDataObject>')
|
lines.append('</MetaDataObject>')
|
||||||
|
|
||||||
metadata_xml = '\n'.join(lines) + '\n'
|
metadata_xml = '\r\n'.join(lines)
|
||||||
|
|
||||||
# --- 5. Emit Rights XML (Roles/Name/Ext/Rights.xml) ---
|
# --- 5. Emit Rights XML (Roles/Name/Ext/Rights.xml) ---
|
||||||
lines = []
|
lines = []
|
||||||
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
|
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
|
||||||
lines.append('<Rights xmlns="http://v8.1c.ru/8.2/roles"')
|
lines.append('<Rights xmlns="http://v8.1c.ru/8.2/roles" xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
||||||
lines.append(' xmlns:xs="http://www.w3.org/2001/XMLSchema"')
|
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Rights"'
|
||||||
lines.append(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"')
|
f' version="{format_version}">')
|
||||||
lines.append(f' xsi:type="Rights" version="{format_version}">')
|
|
||||||
|
|
||||||
# Global flags
|
# Global flags
|
||||||
sfno = str(defn['setForNewObjects']).lower() if defn.get('setForNewObjects') is not None else 'false'
|
sfno = str(defn['setForNewObjects']).lower() if defn.get('setForNewObjects') is not None else 'false'
|
||||||
sfab = str(defn['setForAttributesByDefault']).lower() if defn.get('setForAttributesByDefault') is not None else 'true'
|
sfab = str(defn['setForAttributesByDefault']).lower() if defn.get('setForAttributesByDefault') is not None else 'true'
|
||||||
irco = str(defn['independentRightsOfChildObjects']).lower() if defn.get('independentRightsOfChildObjects') is not None else 'false'
|
irco = str(defn['independentRightsOfChildObjects']).lower() if defn.get('independentRightsOfChildObjects') is not None else 'false'
|
||||||
|
|
||||||
lines.append(f' <setForNewObjects>{sfno}</setForNewObjects>')
|
lines.append(f'\t<setForNewObjects>{sfno}</setForNewObjects>')
|
||||||
lines.append(f' <setForAttributesByDefault>{sfab}</setForAttributesByDefault>')
|
lines.append(f'\t<setForAttributesByDefault>{sfab}</setForAttributesByDefault>')
|
||||||
lines.append(f' <independentRightsOfChildObjects>{irco}</independentRightsOfChildObjects>')
|
lines.append(f'\t<independentRightsOfChildObjects>{irco}</independentRightsOfChildObjects>')
|
||||||
|
|
||||||
# Object blocks
|
# Object blocks
|
||||||
total_rights = 0
|
total_rights = 0
|
||||||
for obj in parsed_objects:
|
for obj in parsed_objects:
|
||||||
lines.append(' <object>')
|
lines.append('\t<object>')
|
||||||
lines.append(f' <name>{obj["Name"]}</name>')
|
lines.append(f'\t\t<name>{obj["Name"]}</name>')
|
||||||
for right in obj['Rights']:
|
for right in obj['Rights']:
|
||||||
lines.append(' <right>')
|
lines.append('\t\t<right>')
|
||||||
lines.append(f' <name>{right["Name"]}</name>')
|
lines.append(f'\t\t\t<name>{right["Name"]}</name>')
|
||||||
lines.append(f' <value>{right["Value"]}</value>')
|
lines.append(f'\t\t\t<value>{right["Value"]}</value>')
|
||||||
if right['Condition']:
|
if right['Condition']:
|
||||||
lines.append(' <restrictionByCondition>')
|
lines.append('\t\t\t<restrictionByCondition>')
|
||||||
lines.append(f' <condition>{esc_xml(right["Condition"])}</condition>')
|
lines.append(f'\t\t\t\t<condition>{esc_xml_text(right["Condition"])}</condition>')
|
||||||
lines.append(' </restrictionByCondition>')
|
lines.append('\t\t\t</restrictionByCondition>')
|
||||||
lines.append(' </right>')
|
lines.append('\t\t</right>')
|
||||||
total_rights += 1
|
total_rights += 1
|
||||||
lines.append(' </object>')
|
lines.append('\t</object>')
|
||||||
|
|
||||||
# RLS restriction templates
|
# RLS restriction templates
|
||||||
template_count = 0
|
template_count = 0
|
||||||
if defn.get('templates'):
|
if defn.get('templates'):
|
||||||
for tpl in defn['templates']:
|
for tpl in defn['templates']:
|
||||||
lines.append(' <restrictionTemplate>')
|
lines.append('\t<restrictionTemplate>')
|
||||||
lines.append(f' <name>{esc_xml(str(tpl["name"]))}</name>')
|
lines.append(f'\t\t<name>{esc_xml_text(str(tpl["name"]))}</name>')
|
||||||
lines.append(f' <condition>{esc_xml(str(tpl["condition"]))}</condition>')
|
lines.append(f'\t\t<condition>{esc_xml_text(str(tpl["condition"]))}</condition>')
|
||||||
lines.append(' </restrictionTemplate>')
|
lines.append('\t</restrictionTemplate>')
|
||||||
template_count += 1
|
template_count += 1
|
||||||
|
|
||||||
lines.append('</Rights>')
|
lines.append('</Rights>')
|
||||||
|
|
||||||
rights_xml = '\n'.join(lines) + '\n'
|
rights_xml = '\r\n'.join(lines)
|
||||||
|
|
||||||
# --- 6. Write output files ---
|
# --- 6. Write output files ---
|
||||||
out_dir = args.OutputDir
|
out_dir = args.OutputDir
|
||||||
@@ -791,9 +875,13 @@ def main():
|
|||||||
reg_result = None
|
reg_result = None
|
||||||
|
|
||||||
if os.path.exists(config_xml_path):
|
if os.path.exists(config_xml_path):
|
||||||
with open(config_xml_path, 'r', encoding='utf-8-sig') as f:
|
# newline='' => без трансляции переводов строк: иначе CRLF молча схлопнется
|
||||||
|
# в LF при чтении и файл будет переписан в LF.
|
||||||
|
with open(config_xml_path, 'r', encoding='utf-8-sig', newline='') as f:
|
||||||
raw_text = f.read()
|
raw_text = f.read()
|
||||||
|
|
||||||
|
eol = detect_eol(raw_text)
|
||||||
|
|
||||||
# Check if already registered
|
# Check if already registered
|
||||||
if f'<Role>{role_name}</Role>' in raw_text:
|
if f'<Role>{role_name}</Role>' in raw_text:
|
||||||
reg_result = 'already'
|
reg_result = 'already'
|
||||||
@@ -807,10 +895,15 @@ def main():
|
|||||||
# Insert after last existing <Role>
|
# Insert after last existing <Role>
|
||||||
last_match = matches[-1]
|
last_match = matches[-1]
|
||||||
insert_pos = last_match.end()
|
insert_pos = last_match.end()
|
||||||
raw_text = raw_text[:insert_pos] + f'\n\t\t\t{new_role_tag}' + raw_text[insert_pos:]
|
raw_text = raw_text[:insert_pos] + eol + f'\t\t\t{new_role_tag}' + raw_text[insert_pos:]
|
||||||
else:
|
else:
|
||||||
# No existing roles — insert before </ChildObjects>
|
# No existing roles — insert before </ChildObjects>
|
||||||
raw_text = raw_text.replace('</ChildObjects>', f'\t\t\t{new_role_tag}\n\t\t</ChildObjects>')
|
# Отступ вставки берём у закрывающего тега +1 уровень: подстановка
|
||||||
|
# по голому '</ChildObjects>' удваивала бы уже присутствующий отступ
|
||||||
|
# строки (получалось 5 табов вместо 3 — PS-порт через DOM даёт 3).
|
||||||
|
raw_text = re.sub(r'([ \t]*)</ChildObjects>',
|
||||||
|
lambda m: m.group(1) + '\t' + new_role_tag + eol + m.group(1) + '</ChildObjects>',
|
||||||
|
raw_text, count=1)
|
||||||
|
|
||||||
write_utf8_bom(config_xml_path, raw_text)
|
write_utf8_bom(config_xml_path, raw_text)
|
||||||
reg_result = 'added'
|
reg_result = 'added'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# role-info v1.2 — Analyze 1C role rights
|
# role-info v1.4 — Analyze 1C role rights (+единое имя хелпера состояния поддержки)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$true)][Alias('Path')][string]$RightsPath,
|
[Parameter(Mandatory=$true)][Alias('Path')][string]$RightsPath,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# role-info v1.2 — Analyze 1C role rights
|
# role-info v1.4 — Analyze 1C role rights (+единое имя хелпера состояния поддержки)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -12,6 +12,28 @@ from lxml import etree
|
|||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
|
|
||||||
|
# Регистронезависимый ввод — паритет с PS1: в PowerShell имена параметров и [ValidateSet]
|
||||||
|
# регистр не различают, в argparse совпадение точное.
|
||||||
|
def ci_parse_args(parser, argv=None):
|
||||||
|
"""parse_args по правилам PS: имена параметров и значения choices регистронезависимы."""
|
||||||
|
argv = list(sys.argv[1:] if argv is None else argv)
|
||||||
|
names = {s.lower(): s for a in parser._actions for s in a.option_strings}
|
||||||
|
for i, tok in enumerate(argv):
|
||||||
|
if tok.startswith('-') and tok.lower() in names:
|
||||||
|
argv[i] = names[tok.lower()]
|
||||||
|
# choices — зеркало [ValidateSet]; канонизируем ДО разбора, иначе argparse отвергнет регистр
|
||||||
|
choice_map = {}
|
||||||
|
for a in parser._actions:
|
||||||
|
if a.choices:
|
||||||
|
for s in a.option_strings:
|
||||||
|
choice_map[s] = {str(c).lower(): c for c in a.choices}
|
||||||
|
for i in range(len(argv) - 1):
|
||||||
|
m = choice_map.get(argv[i])
|
||||||
|
if m and argv[i + 1].lower() in m:
|
||||||
|
argv[i + 1] = m[argv[i + 1].lower()]
|
||||||
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
# --- Argument parsing ---
|
# --- Argument parsing ---
|
||||||
parser = argparse.ArgumentParser(description="Analyze 1C role rights", allow_abbrev=False)
|
parser = argparse.ArgumentParser(description="Analyze 1C role rights", allow_abbrev=False)
|
||||||
parser.add_argument("-RightsPath", "-Path", required=True, help="Path to Rights.xml")
|
parser.add_argument("-RightsPath", "-Path", required=True, help="Path to Rights.xml")
|
||||||
@@ -19,7 +41,7 @@ parser.add_argument("-ShowDenied", action="store_true", default=False, help="Sho
|
|||||||
parser.add_argument("-Limit", type=int, default=150, help="Max lines to show")
|
parser.add_argument("-Limit", type=int, default=150, help="Max lines to show")
|
||||||
parser.add_argument("-Offset", type=int, default=0, help="Lines to skip")
|
parser.add_argument("-Offset", type=int, default=0, help="Lines to skip")
|
||||||
parser.add_argument("-OutFile", default="", help="Write output to file")
|
parser.add_argument("-OutFile", default="", help="Write output to file")
|
||||||
args = parser.parse_args()
|
args = ci_parse_args(parser)
|
||||||
|
|
||||||
# --- Output helper (collect all, paginate at the end) ---
|
# --- Output helper (collect all, paginate at the end) ---
|
||||||
lines_buf = []
|
lines_buf = []
|
||||||
@@ -161,7 +183,7 @@ def get_support_status_for_path(target_path):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
def is_external_root(xml_path):
|
def _sg_is_external_root(xml_path):
|
||||||
if not os.path.isfile(xml_path):
|
if not os.path.isfile(xml_path):
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
@@ -175,14 +197,14 @@ def get_support_status_for_path(target_path):
|
|||||||
rp = os.path.abspath(target_path)
|
rp = os.path.abspath(target_path)
|
||||||
# The target file itself may be the element meta-xml (e.g. Subsystems/X.xml).
|
# The target file itself may be the element meta-xml (e.g. Subsystems/X.xml).
|
||||||
elem_uuid = root_uuid(rp)
|
elem_uuid = root_uuid(rp)
|
||||||
if is_external_root(rp):
|
if _sg_is_external_root(rp):
|
||||||
return None
|
return None
|
||||||
bin_path = None
|
bin_path = None
|
||||||
d = os.path.dirname(rp)
|
d = os.path.dirname(rp)
|
||||||
for _ in range(12):
|
for _ in range(12):
|
||||||
if not d:
|
if not d:
|
||||||
break
|
break
|
||||||
if is_external_root(d + ".xml"):
|
if _sg_is_external_root(d + ".xml"):
|
||||||
return None
|
return None
|
||||||
if not elem_uuid:
|
if not elem_uuid:
|
||||||
elem_uuid = root_uuid(d + ".xml")
|
elem_uuid = root_uuid(d + ".xml")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# role-validate v1.1 — Validate 1C role structure
|
# role-validate v1.2 — Validate 1C role structure
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user