fix(template-add): HTML-макет в платформенной раскладке

Навык создавал Templates/<Макет>/Ext/Template.html. Платформа такой файл молча
игнорирует: загрузка проходит без ошибок и предупреждений, а макет в базе пустой.

HTML-макет платформа хранит парой, как справку: дескриптор Ext/Template.xml со
списком страниц и сама страница Ext/Template/<язык>.html (картинки — в _files/).
Раскладка снята с выгрузки acc_8.3.27: 136 HTML-макетов из 136, дескриптор во всех
байт в байт одинаков. Шапка страницы — в виде редактора платформы (одной строкой,
парный </meta>), чтобы первое сохранение в Конфигураторе не давало диффа.

Язык страницы задаётся параметром -Lang (дефолт ru) — как в help-add.
Вывод навыка теперь различает содержимое и дескриптор: «Содержимое» указывает на
страницу, иначе правка ушла бы в дескриптор.

Проверено на 8.3.27.1859: LoadConfigFromFiles + UpdateDBCfg + обратная выгрузка —
текст макета доходит до базы и возвращается байт в байт, дескриптор тоже.
Контроль — тот же макет в старой раскладке: в выгрузке из базы тела нет вовсе.
EPF: epf-build → epf-dump — раскладка возвращается с содержимым.

Диагноз и раскладка — из PR #98.

Co-Authored-By: Roman Syuzyov <rsyuzyov@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-09-22 16:45:50 +03:00
co-authored by Roman Syuzyov Claude Opus 5
parent 6eaf3211cc
commit 1e94b9236f
7 changed files with 108 additions and 43 deletions
+5 -3
View File
@@ -27,13 +27,14 @@ allowed-tools:
| TemplateName | да | — | Имя макета | | TemplateName | да | — | Имя макета |
| TemplateType | да | — | Тип: HTML, Text, SpreadsheetDocument, BinaryData, DataCompositionSchema | | TemplateType | да | — | Тип: HTML, Text, SpreadsheetDocument, BinaryData, DataCompositionSchema |
| Synonym | нет | = TemplateName | Синоним макета | | Synonym | нет | = TemplateName | Синоним макета |
| Lang | нет | `ru` | Код языка страницы — только для типа HTML |
| SrcDir | нет | `src` | Путь к папке типа объектов (`Reports`, `DataProcessors`, `Catalogs`, `Documents`...), внутри которой лежит `<ObjectName>.xml`. Дефолт `src` подходит для каталогов с внешними обработками/отчётами, лежащими рядом | | SrcDir | нет | `src` | Путь к папке типа объектов (`Reports`, `DataProcessors`, `Catalogs`, `Documents`...), внутри которой лежит `<ObjectName>.xml`. Дефолт `src` подходит для каталогов с внешними обработками/отчётами, лежащими рядом |
| -SetMainSKD | нет | — | Принудительно установить MainDataCompositionSchema | | -SetMainSKD | нет | — | Принудительно установить MainDataCompositionSchema |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/add-template.ps1" -ObjectName "<ObjectName>" -TemplateName "<TemplateName>" -TemplateType "<TemplateType>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-SetMainSKD] powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/add-template.ps1" -ObjectName "<ObjectName>" -TemplateName "<TemplateName>" -TemplateType "<TemplateType>" [-Synonym "<Synonym>"] [-Lang "<Lang>"] [-SrcDir "<SrcDir>"] [-SetMainSKD]
``` ```
## Пример ## Пример
@@ -50,7 +51,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/add-template.ps1" -
| Пользователь пишет | TemplateType | Расширение | Содержимое | | Пользователь пишет | TemplateType | Расширение | Содержимое |
|---------------------------------------------|---------------------|------------|-------------------------| |---------------------------------------------|---------------------|------------|-------------------------|
| HTML | HTMLDocument | `.html` | Пустой HTML-документ | | HTML | HTMLDocument | `Template/<lang>.html` | Пустой HTML-документ |
| Text, текстовый документ, текст | TextDocument | `.txt` | Пустой файл | | Text, текстовый документ, текст | TextDocument | `.txt` | Пустой файл |
| SpreadsheetDocument, табличный документ, MXL | SpreadsheetDocument | `.xml` | Минимальный spreadsheet | | SpreadsheetDocument, табличный документ, MXL | SpreadsheetDocument | `.xml` | Минимальный spreadsheet |
| BinaryData, двоичные данные | BinaryData | `.bin` | Пустой файл | | BinaryData, двоичные данные | BinaryData | `.bin` | Пустой файл |
@@ -80,7 +81,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/add-template.ps1" -
├── <TemplateName>.xml # Метаданные макета (1 UUID) ├── <TemplateName>.xml # Метаданные макета (1 UUID)
└── <TemplateName>/ └── <TemplateName>/
└── Ext/ └── Ext/
── Template.<ext> # Содержимое макета ── Template.<ext> # Содержимое макета
└── Template/<lang>.html # HTML: содержимое (Template.xml — дескриптор)
``` ```
## Что модифицируется ## Что модифицируется
@@ -1,4 +1,4 @@
# template-add v1.23 — Add template to 1C object (+write_xml_file/write_utf8_bom: общий эталон записи) # template-add v1.24 — Add template 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)]
@@ -14,6 +14,8 @@ param(
[string]$Synonym = $TemplateName, [string]$Synonym = $TemplateName,
[string]$Lang = "ru",
[string]$SrcDir = "src", [string]$SrcDir = "src",
[switch]$SetMainSKD [switch]$SetMainSKD
@@ -157,7 +159,7 @@ function Assert-EditAllowed([string]$targetPath, [string]$require) {
# --- Маппинг типов --- # --- Маппинг типов ---
$typeMap = @{ $typeMap = @{
"HTML" = @{ TemplateType = "HTMLDocument"; Ext = ".html" } "HTML" = @{ TemplateType = "HTMLDocument"; Ext = ".xml" }
"Text" = @{ TemplateType = "TextDocument"; Ext = ".txt" } "Text" = @{ TemplateType = "TextDocument"; Ext = ".txt" }
"SpreadsheetDocument" = @{ TemplateType = "SpreadsheetDocument"; Ext = ".xml" } "SpreadsheetDocument" = @{ TemplateType = "SpreadsheetDocument"; Ext = ".xml" }
"BinaryData" = @{ TemplateType = "BinaryData"; Ext = ".bin" } "BinaryData" = @{ TemplateType = "BinaryData"; Ext = ".bin" }
@@ -297,7 +299,7 @@ $templateMetaXml = @"
# Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать # Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
# копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл. # копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
# #
# HTML-макет сюда НЕ идёт — платформа хранит его с LF. # HTML-страница макета сюда НЕ идёт — платформа хранит её с LF.
function Write-XmlFile([string]$path, [string]$text, $encoding) { function Write-XmlFile([string]$path, [string]$text, $encoding) {
$t = ($text -replace "`r`n", "`n") -replace "`n", "`r`n" $t = ($text -replace "`r`n", "`n") -replace "`n", "`r`n"
[System.IO.File]::WriteAllText($path, $t.TrimEnd("`r", "`n"), $encoding) [System.IO.File]::WriteAllText($path, $t.TrimEnd("`r", "`n"), $encoding)
@@ -308,21 +310,37 @@ Write-XmlFile $templateMetaPath $templateMetaXml $encBom
# --- 2. Содержимое макета (Templates/<TemplateName>/Ext/Template.<ext>) --- # --- 2. Содержимое макета (Templates/<TemplateName>/Ext/Template.<ext>) ---
$templateFilePath = Join-Path $templateExtDir "Template$($tmpl.Ext)" $templateFilePath = Join-Path $templateExtDir "Template$($tmpl.Ext)"
# Куда класть текст макета. Совпадает с $templateFilePath у всех типов, кроме HTML:
# там содержимое живёт в отдельной странице, а Template.xml — только дескриптор.
$templateBodyPath = $templateFilePath
switch ($TemplateType) { switch ($TemplateType) {
"HTML" { "HTML" {
$content = @" # HTML-макет платформа хранит парой, как справку: дескриптор Ext/Template.xml
<!DOCTYPE html> # со списком страниц и сама страница Ext/Template/<язык>.html (картинки —
<html> # рядом в _files/). Одиночный Ext/Template.html платформа молча игнорирует:
<head> # загрузка проходит без ошибок, а макет в базе пустой.
<meta charset="UTF-8"> $pageXml = @"
<title></title> <?xml version="1.0" encoding="UTF-8"?>
</head> <Help xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$formatVersion">
<body> <Page>$Lang</Page>
</body> </Help>
</html>
"@ "@
[System.IO.File]::WriteAllText($templateFilePath, $content, $encBom) Write-XmlFile $templateFilePath $pageXml $encBom
$pageDir = Join-Path $templateExtDir "Template"
New-Item -ItemType Directory -Path $pageDir -Force | Out-Null
$templateBodyPath = Join-Path $pageDir "$Lang.html"
# Шапка — в том же виде, в каком её пишет редактор платформы (одной строкой,
# парный </meta>): первое сохранение в Конфигураторе даст минимальный дифф.
$content = @"
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta></head><body>
</body></html>
"@
# Страница — с LF: платформа хранит HTML именно так.
$content = ($content -replace "`r`n", "`n")
[System.IO.File]::WriteAllText($templateBodyPath, $content, $encBom)
} }
"Text" { "Text" {
[System.IO.File]::WriteAllText($templateFilePath, "", $encBom) [System.IO.File]::WriteAllText($templateFilePath, "", $encBom)
@@ -463,7 +481,10 @@ if ($alreadyRegistered) {
Write-Host " Already registered: <Template>$TemplateName</Template> in ChildObjects (skipped duplicate)" Write-Host " Already registered: <Template>$TemplateName</Template> in ChildObjects (skipped duplicate)"
} }
Write-Host " Метаданные: $templateMetaPath" Write-Host " Метаданные: $templateMetaPath"
Write-Host " Содержимое: $templateFilePath" Write-Host " Содержимое: $templateBodyPath"
if ($TemplateType -eq "HTML") {
Write-Host " Дескриптор: $templateFilePath"
}
if ($mainDCSUpdated) { if ($mainDCSUpdated) {
Write-Host " MainDataCompositionSchema: $($mainDCS.InnerText)" Write-Host " MainDataCompositionSchema: $($mainDCS.InnerText)"
} }
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# template-add v1.23 — Add template to 1C object (+write_xml_file/write_utf8_bom: общий эталон записи) # template-add v1.24 — Add template 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
@@ -212,7 +212,7 @@ def assert_edit_allowed(target_path, require):
return return
TYPE_MAP = { TYPE_MAP = {
"HTML": {"TemplateType": "HTMLDocument", "Ext": ".html"}, "HTML": {"TemplateType": "HTMLDocument", "Ext": ".xml"},
"Text": {"TemplateType": "TextDocument", "Ext": ".txt"}, "Text": {"TemplateType": "TextDocument", "Ext": ".txt"},
"SpreadsheetDocument": {"TemplateType": "SpreadsheetDocument", "Ext": ".xml"}, "SpreadsheetDocument": {"TemplateType": "SpreadsheetDocument", "Ext": ".xml"},
"BinaryData": {"TemplateType": "BinaryData", "Ext": ".bin"}, "BinaryData": {"TemplateType": "BinaryData", "Ext": ".bin"},
@@ -282,7 +282,7 @@ def write_xml_file(path, content):
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл. копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
HTML-макет сюда НЕ идёт — платформа хранит его с LF. HTML-страница макета сюда НЕ идёт — платформа хранит её с LF.
""" """
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n') text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
write_utf8_bom(path, text) write_utf8_bom(path, text)
@@ -329,6 +329,7 @@ def main():
parser.add_argument("-TemplateType", required=True, parser.add_argument("-TemplateType", required=True,
choices=["HTML", "Text", "SpreadsheetDocument", "BinaryData", "DataCompositionSchema"]) choices=["HTML", "Text", "SpreadsheetDocument", "BinaryData", "DataCompositionSchema"])
parser.add_argument("-Synonym", default=None) parser.add_argument("-Synonym", default=None)
parser.add_argument("-Lang", default="ru")
parser.add_argument("-SrcDir", default="src") parser.add_argument("-SrcDir", default="src")
parser.add_argument("-SetMainSKD", action="store_true") parser.add_argument("-SetMainSKD", action="store_true")
args = ci_parse_args(parser) args = ci_parse_args(parser)
@@ -337,6 +338,7 @@ def main():
template_name = args.TemplateName template_name = args.TemplateName
template_type = args.TemplateType template_type = args.TemplateType
synonym = args.Synonym if args.Synonym is not None else template_name synonym = args.Synonym if args.Synonym is not None else template_name
lang = args.Lang
src_dir = args.SrcDir src_dir = args.SrcDir
set_main_skd = args.SetMainSKD set_main_skd = args.SetMainSKD
@@ -457,20 +459,41 @@ def main():
# --- 2. Template content (Templates/<TemplateName>/Ext/Template.<ext>) --- # --- 2. Template content (Templates/<TemplateName>/Ext/Template.<ext>) ---
template_file_path = os.path.join(template_ext_dir, f"Template{tmpl['Ext']}") template_file_path = os.path.join(template_ext_dir, f"Template{tmpl['Ext']}")
# Куда класть текст макета. Совпадает с template_file_path у всех типов, кроме HTML:
# там содержимое живёт в отдельной странице, а Template.xml — только дескриптор.
template_body_path = template_file_path
if template_type == "HTML": if template_type == "HTML":
content = ( # HTML-макет платформа хранит парой, как справку: дескриптор Ext/Template.xml
'<!DOCTYPE html>\n' # со списком страниц и сама страница Ext/Template/<язык>.html (картинки —
'<html>\n' # рядом в _files/). Одиночный Ext/Template.html платформа молча игнорирует:
'<head>\n' # загрузка проходит без ошибок, а макет в базе пустой.
'\t<meta charset="UTF-8">\n' page_xml = (
'\t<title></title>\n' '<?xml version="1.0" encoding="UTF-8"?>\n'
'</head>\n' '<Help xmlns="http://v8.1c.ru/8.3/xcf/extrnprops"'
'<body>\n' ' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
'</body>\n' ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
'</html>' f' version="{format_version}">\n'
f'\t<Page>{lang}</Page>\n'
'</Help>'
) )
write_utf8_bom(template_file_path, content) write_xml_file(template_file_path, page_xml)
page_dir = os.path.join(template_ext_dir, "Template")
os.makedirs(page_dir, exist_ok=True)
template_body_path = os.path.join(page_dir, f"{lang}.html")
# Шапка — в том же виде, в каком её пишет редактор платформы (одной строкой,
# парный </meta>): первое сохранение в Конфигураторе даст минимальный дифф.
# Страница — с LF: платформа хранит HTML именно так.
content = (
'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">'
'<html><head>'
'<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>'
'</head><body>\n'
'</body></html>'
)
write_utf8_bom(template_body_path, content)
elif template_type == "Text": elif template_type == "Text":
write_utf8_bom(template_file_path, "") write_utf8_bom(template_file_path, "")
@@ -584,7 +607,9 @@ def main():
if already_registered: if already_registered:
print(f" Already registered: <Template>{template_name}</Template> in ChildObjects (skipped duplicate)") print(f" Already registered: <Template>{template_name}</Template> in ChildObjects (skipped duplicate)")
print(f" Метаданные: {template_meta_path}") print(f" Метаданные: {template_meta_path}")
print(f" Содержимое: {template_file_path}") print(f" Содержимое: {template_body_path}")
if template_type == "HTML":
print(f" Дескриптор: {template_file_path}")
if main_dcs_updated: if main_dcs_updated:
print(f" MainDataCompositionSchema: {main_dcs.text}") print(f" MainDataCompositionSchema: {main_dcs.text}")
@@ -6,5 +6,25 @@
"args": { "-Name": "МояОбработка", "-SrcDir": "{workDir}" } "args": { "-Name": "МояОбработка", "-SrcDir": "{workDir}" }
} }
], ],
"params": { "objectName": "МояОбработка", "templateName": "МакетHTML", "templateType": "HTML" } "params": { "objectName": "МояОбработка", "templateName": "МакетHTML", "templateType": "HTML" },
"expect": {
"stdoutContains": ["Содержимое:", "ru.html", "Дескриптор:"],
"preserves": [
{
"file": "МояОбработка/Templates/МакетHTML/Ext/Template.xml",
"bom": true,
"eol": "crlf",
"encoding": "UTF-8",
"finalNewline": false,
"noCR13": true,
"selfClose": "tight"
},
{
"file": "МояОбработка/Templates/МакетHTML/Ext/Template/ru.html",
"bom": true,
"eol": "lf",
"finalNewline": false
}
]
}
} }
@@ -1,9 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
</body>
</html>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<Help xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Page>ru</Page>
</Help>
@@ -0,0 +1,2 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta></head><body>
</body></html>