mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-19 09:40:22 +03:00
feat(mxl-decompile): читать формат целиком, а не девять полей
Декомпилятор снимал из <format> только шрифт, четыре рамки, выравнивания, перенос и форматную строку — остальные тридцать с лишним свойств терялись молча. Компилятор их уже умеет, но до раундтрипа они не доезжали. Теперь формат читается целиком по общей таблице типов — той же, что у компилятора: рамки разворачиваются в описание линии из палитры, булевы и целые приводятся по типу, цвет из web/win-палитры возвращается в привычную запись (префикс узла разрешается в URI пространства имён). Имя стиля осталось читаемой меткой по заметным свойствам: различает стили ключ, имя лишь помогает автору ориентироваться. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
63f7c7a99d
commit
c9e490bd25
@@ -1,4 +1,4 @@
|
|||||||
# mxl-decompile v1.9 — Decompile 1C spreadsheet to JSON
|
# mxl-decompile v1.10 — 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)]
|
||||||
@@ -29,6 +29,51 @@ $ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
|
|||||||
$ns.AddNamespace("v8ui", "http://v8.1c.ru/8.1/data/ui")
|
$ns.AddNamespace("v8ui", "http://v8.1c.ru/8.1/data/ui")
|
||||||
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
||||||
|
|
||||||
|
# Тип значения каждого тега формата — выведен из корпуса, а не выписан на глаз.
|
||||||
|
# line — ссылка в палитру <line>; color — #RRGGBB / style: / web: / win:
|
||||||
|
# ml — многоязычная строка; enum — замкнутый список.
|
||||||
|
# containsValue / valueType / controlType сюда НЕ входят: это свойства конкретной ячейки,
|
||||||
|
# а стиль — сущность общая, один на многие ячейки.
|
||||||
|
function Get-FormatTagKind {
|
||||||
|
return @{
|
||||||
|
'autoIndent' = 'int'; 'autoMarkIncomplete' = 'bool'; 'autoWidthCalculation' = 'bool'
|
||||||
|
'backColor' = 'color'; 'border' = 'line'; 'borderColor' = 'color'
|
||||||
|
'bottomBorder' = 'line'; 'bySelectedColumns' = 'bool'; 'columnSizeChange' = 'enum'
|
||||||
|
'detailsUse' = 'enum'; 'drawingBorder' = 'int'
|
||||||
|
'drawingHaveBottomBorder' = 'bool'; 'drawingHaveLeftBorder' = 'bool'
|
||||||
|
'drawingHaveRightBorder' = 'bool'; 'drawingHaveTopBorder' = 'bool'
|
||||||
|
'editFormat' = 'ml'; 'fillType' = 'enum'; 'font' = 'int'; 'format' = 'ml'
|
||||||
|
'height' = 'int'; 'hidden' = 'bool'; 'horizontalAlignment' = 'enum'
|
||||||
|
'hyperLink' = 'bool'; 'indent' = 'int'; 'leftBorder' = 'line'
|
||||||
|
'markNegatives' = 'bool'; 'mask' = 'ml'; 'pattern' = 'enum'; 'patternColor' = 'color'
|
||||||
|
'picHorizontalAlignment' = 'enum'; 'picIndex' = 'int'; 'picVerticalAlignment' = 'enum'
|
||||||
|
'pictureSizeMode' = 'enum'; 'print' = 'bool'; 'protection' = 'bool'
|
||||||
|
'rightBorder' = 'line'; 'textColor' = 'color'; 'textOrientation' = 'int'
|
||||||
|
'textPlacement' = 'enum'; 'textPosition' = 'enum'; 'topBorder' = 'line'
|
||||||
|
'verticalAlignment' = 'enum'; 'width' = 'int'; 'widthWeightFactor' = 'int'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Теги, значение которых — многоязычная строка (<v8:item> на язык), а не скаляр.
|
||||||
|
function Get-FormatMlTags {
|
||||||
|
return @{ 'format' = $true; 'editFormat' = $true; 'mask' = $true }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Цвет платформа хранит значением с префиксом пространства имён. style: объявлен в корне
|
||||||
|
# документа и читается как есть; web/win объявлены прямо на узле сгенерированным префиксом
|
||||||
|
# (d3p1), поэтому его надо разрешить в URI и вернуть привычное web:/win:.
|
||||||
|
function ConvertTo-DslColor {
|
||||||
|
param($node, [string]$val)
|
||||||
|
if ($val -notlike '*:*' -or $val -like 'style:*') { return $val }
|
||||||
|
$prefix = $val.Substring(0, $val.IndexOf(':'))
|
||||||
|
$name = $val.Substring($val.IndexOf(':') + 1)
|
||||||
|
# get_*(): XML-адаптер PowerShell перекрывает .NET-члены одноимёнными атрибутами узла.
|
||||||
|
$uri = $node.GetNamespaceOfPrefix($prefix)
|
||||||
|
if ($uri -ceq 'http://v8.1c.ru/8.1/data/ui/colors/web') { return "web:$name" }
|
||||||
|
if ($uri -ceq 'http://v8.1c.ru/8.1/data/ui/colors/windows') { return "win:$name" }
|
||||||
|
return $val
|
||||||
|
}
|
||||||
|
|
||||||
# --- 2. Extract font palette ---
|
# --- 2. Extract font palette ---
|
||||||
|
|
||||||
# Размер шрифта бывает дробным (8.3, 11.3 — в корпусе ERP это треть макетов). [int] его
|
# Размер шрифта бывает дробным (8.3, 11.3 — в корпусе ERP это треть макетов). [int] его
|
||||||
@@ -61,50 +106,47 @@ foreach ($fNode in $root.SelectNodes("d:font", $ns)) {
|
|||||||
|
|
||||||
$rawLines = @()
|
$rawLines = @()
|
||||||
foreach ($lNode in $root.SelectNodes("d:line", $ns)) {
|
foreach ($lNode in $root.SelectNodes("d:line", $ns)) {
|
||||||
$rawLines += @{ Width = [int]$lNode.GetAttribute("width") }
|
$st = $lNode.SelectSingleNode("v8ui:style", $ns)
|
||||||
|
$rawLines += @{
|
||||||
|
Width = [int]$lNode.GetAttribute("width")
|
||||||
|
Gap = $lNode.GetAttribute("gap")
|
||||||
|
Style = if ($st) { $st.InnerText } else { "Solid" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- 4. Extract format palette ---
|
# --- 4. Extract format palette ---
|
||||||
|
# Формат читаем целиком — «тег → значение», а не выборочные девять полей. Тип значения
|
||||||
|
# берём из общей таблицы, она же у компилятора.
|
||||||
|
$formatKinds = Get-FormatTagKind
|
||||||
|
$formatMl = Get-FormatMlTags
|
||||||
|
|
||||||
$rawFormats = @()
|
$rawFormats = @()
|
||||||
foreach ($fmtNode in $root.SelectNodes("d:format", $ns)) {
|
foreach ($fmtNode in $root.SelectNodes("d:format", $ns)) {
|
||||||
|
# Совместимость с прежними полями: ими пользуются ширина колонки, высота строки и
|
||||||
|
# признак заполнения — они не часть стиля.
|
||||||
$fmt = @{
|
$fmt = @{
|
||||||
FontIdx = -1
|
FontIdx = -1; Width = 0; Height = 0; FillType = ""
|
||||||
LB = -1; TB = -1; RB = -1; BB = -1
|
Props = [ordered]@{}
|
||||||
Width = 0; Height = 0
|
|
||||||
HA = ""; VA = ""
|
|
||||||
Wrap = $false; FillType = ""; DataFormat = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$n = $fmtNode.SelectSingleNode("d:font", $ns)
|
foreach ($child in $fmtNode.ChildNodes) {
|
||||||
if ($n) { $fmt.FontIdx = [int]$n.InnerText }
|
if ($child.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
|
||||||
$n = $fmtNode.SelectSingleNode("d:leftBorder", $ns)
|
$tag = $child.get_LocalName()
|
||||||
if ($n) { $fmt.LB = [int]$n.InnerText }
|
if ($formatMl.ContainsKey($tag)) {
|
||||||
$n = $fmtNode.SelectSingleNode("d:topBorder", $ns)
|
$item = $child.SelectSingleNode("v8:item/v8:content", $ns)
|
||||||
if ($n) { $fmt.TB = [int]$n.InnerText }
|
if ($item -and $item.InnerText) { $fmt.Props[$tag] = $item.InnerText }
|
||||||
$n = $fmtNode.SelectSingleNode("d:rightBorder", $ns)
|
continue
|
||||||
if ($n) { $fmt.RB = [int]$n.InnerText }
|
}
|
||||||
$n = $fmtNode.SelectSingleNode("d:bottomBorder", $ns)
|
$val = $child.InnerText
|
||||||
if ($n) { $fmt.BB = [int]$n.InnerText }
|
if ([string]::IsNullOrWhiteSpace($val)) { continue }
|
||||||
|
if ($formatKinds[$tag] -ceq 'color') { $val = ConvertTo-DslColor $child $val }
|
||||||
|
$fmt.Props[$tag] = $val
|
||||||
|
}
|
||||||
|
|
||||||
$n = $fmtNode.SelectSingleNode("d:width", $ns)
|
if ($fmt.Props.Contains('font')) { $fmt.FontIdx = [int]$fmt.Props['font'] }
|
||||||
if ($n) { $fmt.Width = [int]$n.InnerText }
|
if ($fmt.Props.Contains('width')) { $fmt.Width = [int]$fmt.Props['width'] }
|
||||||
$n = $fmtNode.SelectSingleNode("d:height", $ns)
|
if ($fmt.Props.Contains('height')) { $fmt.Height = [int]$fmt.Props['height'] }
|
||||||
if ($n) { $fmt.Height = [int]$n.InnerText }
|
if ($fmt.Props.Contains('fillType')) { $fmt.FillType = $fmt.Props['fillType'] }
|
||||||
|
|
||||||
$n = $fmtNode.SelectSingleNode("d:horizontalAlignment", $ns)
|
|
||||||
if ($n) { $fmt.HA = $n.InnerText }
|
|
||||||
$n = $fmtNode.SelectSingleNode("d:verticalAlignment", $ns)
|
|
||||||
if ($n) { $fmt.VA = $n.InnerText }
|
|
||||||
|
|
||||||
$n = $fmtNode.SelectSingleNode("d:textPlacement", $ns)
|
|
||||||
if ($n -and $n.InnerText -eq "Wrap") { $fmt.Wrap = $true }
|
|
||||||
|
|
||||||
$n = $fmtNode.SelectSingleNode("d:fillType", $ns)
|
|
||||||
if ($n) { $fmt.FillType = $n.InnerText }
|
|
||||||
|
|
||||||
$n = $fmtNode.SelectSingleNode("d:format/v8:item/v8:content", $ns)
|
|
||||||
if ($n) { $fmt.DataFormat = $n.InnerText }
|
|
||||||
|
|
||||||
$rawFormats += $fmt
|
$rawFormats += $fmt
|
||||||
}
|
}
|
||||||
@@ -317,43 +359,60 @@ function Get-DslText {
|
|||||||
|
|
||||||
# --- 9. Build style key (ignoring fillType) ---
|
# --- 9. Build style key (ignoring fillType) ---
|
||||||
|
|
||||||
function Get-BorderDesc {
|
# Свойства формата, которые стилем НЕ являются: ширина принадлежит колонке, высота —
|
||||||
|
# строке, вид заполнения выводится из того, чем задано содержимое ячейки.
|
||||||
|
$nonStyleTags = @('width', 'height', 'fillType', 'font')
|
||||||
|
$borderTags = @('border', 'leftBorder', 'topBorder', 'rightBorder', 'bottomBorder')
|
||||||
|
|
||||||
|
function ConvertTo-DslLine {
|
||||||
|
# Ссылку в палитру разворачиваем в описание линии. Ширина 1 подразумевается, поэтому
|
||||||
|
# обычная линия пишется просто именем стиля.
|
||||||
|
param($idx)
|
||||||
|
$i = [int]$idx
|
||||||
|
if ($i -lt 0 -or $i -ge $rawLines.Count) { return "Solid" }
|
||||||
|
$ln = $rawLines[$i]
|
||||||
|
$out = [ordered]@{ style = $ln.Style }
|
||||||
|
if ($ln.Width -ne 1) { $out['width'] = $ln.Width }
|
||||||
|
if ($ln.Gap -ceq 'true') { $out['gap'] = $true }
|
||||||
|
if ($out.Count -eq 1) { return $ln.Style }
|
||||||
|
return $out
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-StyleProps {
|
||||||
|
# Свойства формата, попадающие в стиль, — в каноническом порядке тегов.
|
||||||
param($fmt)
|
param($fmt)
|
||||||
if (-not $fmt) { return @{ Border = "none"; Thick = $false } }
|
$out = [ordered]@{}
|
||||||
|
if (-not $fmt) { return $out }
|
||||||
$lb = $fmt.LB -ge 0; $tb = $fmt.TB -ge 0
|
foreach ($tag in $fmt.Props.Keys) {
|
||||||
$rb = $fmt.RB -ge 0; $bb = $fmt.BB -ge 0
|
if ($nonStyleTags -ccontains $tag) { continue }
|
||||||
|
$val = $fmt.Props[$tag]
|
||||||
if (-not $lb -and -not $tb -and -not $rb -and -not $bb) {
|
if ($borderTags -ccontains $tag) {
|
||||||
return @{ Border = "none"; Thick = $false }
|
$out[$tag] = ConvertTo-DslLine $val
|
||||||
}
|
} elseif ($formatKinds[$tag] -ceq 'bool') {
|
||||||
|
$out[$tag] = ($val -ceq 'true')
|
||||||
$thick = $false
|
} elseif ($formatKinds[$tag] -ceq 'int') {
|
||||||
foreach ($bIdx in @($fmt.LB, $fmt.TB, $fmt.RB, $fmt.BB)) {
|
$out[$tag] = [int]$val
|
||||||
if ($bIdx -ge 0 -and $bIdx -lt $rawLines.Count -and $rawLines[$bIdx].Width -ge 2) {
|
} else {
|
||||||
$thick = $true; break
|
$out[$tag] = $val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return $out
|
||||||
if ($lb -and $tb -and $rb -and $bb) {
|
|
||||||
return @{ Border = "all"; Thick = $thick }
|
|
||||||
}
|
|
||||||
|
|
||||||
$sides = @()
|
|
||||||
if ($tb) { $sides += "top" }
|
|
||||||
if ($bb) { $sides += "bottom" }
|
|
||||||
if ($lb) { $sides += "left" }
|
|
||||||
if ($rb) { $sides += "right" }
|
|
||||||
|
|
||||||
return @{ Border = ($sides -join ","); Thick = $thick }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Get-StyleKey {
|
function Get-StyleKey {
|
||||||
param($fmt)
|
param($fmt)
|
||||||
if (-not $fmt) { return "empty" }
|
if (-not $fmt) { return "empty" }
|
||||||
$fi = if ($fmt.FontIdx -ge 0) { $fmt.FontIdx } else { 0 }
|
$fi = if ($fmt.FontIdx -ge 0) { $fmt.FontIdx } else { 0 }
|
||||||
$bd = Get-BorderDesc $fmt
|
$parts = @("f=$fi")
|
||||||
return "f=$fi|b=$($bd.Border)|bw=$($bd.Thick)|ha=$($fmt.HA)|va=$($fmt.VA)|wr=$($fmt.Wrap)|df=$($fmt.DataFormat)"
|
$props = Get-StyleProps $fmt
|
||||||
|
foreach ($tag in $props.Keys) {
|
||||||
|
$v = $props[$tag]
|
||||||
|
if ($v -is [System.Collections.IDictionary]) {
|
||||||
|
$v = (($v.Keys | ForEach-Object { "$_=$($v[$_])" }) -join ',')
|
||||||
|
}
|
||||||
|
$parts += "$tag=$v"
|
||||||
|
}
|
||||||
|
return ($parts -join '|')
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- 10. Name fonts ---
|
# --- 10. Name fonts ---
|
||||||
@@ -435,28 +494,36 @@ foreach ($r in $rowData.Values) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Имя стиля — читаемая метка по самым заметным свойствам. Полнота тут не нужна:
|
||||||
|
# различает стили ключ, имя лишь помогает автору ориентироваться.
|
||||||
function Name-Style {
|
function Name-Style {
|
||||||
param($fmt)
|
param($fmt)
|
||||||
if (-not $fmt) { return "default" }
|
if (-not $fmt) { return "default" }
|
||||||
$parts = @()
|
$parts = @()
|
||||||
|
$props = Get-StyleProps $fmt
|
||||||
|
|
||||||
$fi = if ($fmt.FontIdx -ge 0) { $fmt.FontIdx } else { 0 }
|
$fi = if ($fmt.FontIdx -ge 0) { $fmt.FontIdx } else { 0 }
|
||||||
if ($fontNames.ContainsKey($fi) -and $fontNames[$fi] -ne "default") {
|
if ($fontNames.ContainsKey($fi) -and $fontNames[$fi] -ne "default") {
|
||||||
$parts += $fontNames[$fi]
|
$parts += $fontNames[$fi]
|
||||||
}
|
}
|
||||||
|
|
||||||
$bd = Get-BorderDesc $fmt
|
if ($props.Contains('border')) {
|
||||||
if ($bd.Border -ne "none") {
|
$parts += "bordered"
|
||||||
if ($bd.Border -eq "all") { $parts += "bordered" }
|
} else {
|
||||||
else { $parts += "border-$($bd.Border)" }
|
$sides = @()
|
||||||
|
foreach ($s in @('leftBorder', 'topBorder', 'rightBorder', 'bottomBorder')) {
|
||||||
|
if ($props.Contains($s)) { $sides += $s.Substring(0, $s.Length - 6) }
|
||||||
|
}
|
||||||
|
if ($sides.Count -gt 0) { $parts += "border-$($sides -join ',')" }
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($fmt.HA -eq "Center") { $parts += "center" }
|
if ($props['horizontalAlignment'] -ceq "Center") { $parts += "center" }
|
||||||
elseif ($fmt.HA -eq "Right") { $parts += "right" }
|
elseif ($props['horizontalAlignment'] -ceq "Right") { $parts += "right" }
|
||||||
if ($fmt.VA -eq "Center") { $parts += "vcenter" }
|
if ($props['verticalAlignment'] -ceq "Center") { $parts += "vcenter" }
|
||||||
elseif ($fmt.VA -eq "Top") { $parts += "vtop" }
|
elseif ($props['verticalAlignment'] -ceq "Top") { $parts += "vtop" }
|
||||||
if ($fmt.Wrap) { $parts += "wrap" }
|
if ($props['textPlacement'] -ceq "Wrap") { $parts += "wrap" }
|
||||||
if ($fmt.DataFormat) { $parts += "fmt" }
|
if ($props.Contains('backColor')) { $parts += "bg" }
|
||||||
|
if ($props.Contains('format')) { $parts += "fmt" }
|
||||||
|
|
||||||
if ($parts.Count -eq 0) { return "default" }
|
if ($parts.Count -eq 0) { return "default" }
|
||||||
return ($parts -join "-")
|
return ($parts -join "-")
|
||||||
@@ -479,21 +546,8 @@ foreach ($key in $styleKeys.Keys) {
|
|||||||
if ($fontNames.ContainsKey($fi) -and $fontNames[$fi] -ne "default") {
|
if ($fontNames.ContainsKey($fi) -and $fontNames[$fi] -ne "default") {
|
||||||
$sDef["font"] = $fontNames[$fi]
|
$sDef["font"] = $fontNames[$fi]
|
||||||
}
|
}
|
||||||
if ($fmt.HA) {
|
$props = Get-StyleProps $fmt
|
||||||
$a = switch ($fmt.HA) { "Left" { "left" } "Center" { "center" } "Right" { "right" } }
|
foreach ($tag in $props.Keys) { $sDef[$tag] = $props[$tag] }
|
||||||
if ($a) { $sDef["align"] = $a }
|
|
||||||
}
|
|
||||||
if ($fmt.VA) {
|
|
||||||
$a = switch ($fmt.VA) { "Top" { "top" } "Center" { "center" } }
|
|
||||||
if ($a) { $sDef["valign"] = $a }
|
|
||||||
}
|
|
||||||
$bd = Get-BorderDesc $fmt
|
|
||||||
if ($bd.Border -ne "none") {
|
|
||||||
$sDef["border"] = $bd.Border
|
|
||||||
if ($bd.Thick) { $sDef["borderWidth"] = "thick" }
|
|
||||||
}
|
|
||||||
if ($fmt.Wrap) { $sDef["wrap"] = $true }
|
|
||||||
if ($fmt.DataFormat) { $sDef["format"] = $fmt.DataFormat }
|
|
||||||
|
|
||||||
$styleDefs[$name] = $sDef
|
$styleDefs[$name] = $sDef
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# mxl-decompile v1.9 — Decompile 1C spreadsheet to JSON
|
# mxl-decompile v1.10 — 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
|
||||||
@@ -69,6 +69,51 @@ def to_font_size(raw):
|
|||||||
return int(d) if d == int(d) else d
|
return int(d) if d == int(d) else d
|
||||||
|
|
||||||
|
|
||||||
|
def format_tag_kind():
|
||||||
|
"""Тип значения каждого тега — выведен из корпуса, а не выписан на глаз.
|
||||||
|
line — ссылка в палитру <line>; color — #RRGGBB / style: / web: / win:
|
||||||
|
ml — многоязычная строка; enum — замкнутый список (см. format_enum_values).
|
||||||
|
containsValue / valueType / controlType сюда НЕ входят: это свойства конкретной ячейки,
|
||||||
|
а стиль — сущность общая, один на многие ячейки."""
|
||||||
|
return {
|
||||||
|
'autoIndent': 'int', 'autoMarkIncomplete': 'bool', 'autoWidthCalculation': 'bool',
|
||||||
|
'backColor': 'color', 'border': 'line', 'borderColor': 'color',
|
||||||
|
'bottomBorder': 'line', 'bySelectedColumns': 'bool', 'columnSizeChange': 'enum',
|
||||||
|
'detailsUse': 'enum', 'drawingBorder': 'int',
|
||||||
|
'drawingHaveBottomBorder': 'bool', 'drawingHaveLeftBorder': 'bool',
|
||||||
|
'drawingHaveRightBorder': 'bool', 'drawingHaveTopBorder': 'bool',
|
||||||
|
'editFormat': 'ml', 'fillType': 'enum', 'font': 'int', 'format': 'ml',
|
||||||
|
'height': 'int', 'hidden': 'bool', 'horizontalAlignment': 'enum',
|
||||||
|
'hyperLink': 'bool', 'indent': 'int', 'leftBorder': 'line',
|
||||||
|
'markNegatives': 'bool', 'mask': 'ml', 'pattern': 'enum', 'patternColor': 'color',
|
||||||
|
'picHorizontalAlignment': 'enum', 'picIndex': 'int', 'picVerticalAlignment': 'enum',
|
||||||
|
'pictureSizeMode': 'enum', 'print': 'bool', 'protection': 'bool',
|
||||||
|
'rightBorder': 'line', 'textColor': 'color', 'textOrientation': 'int',
|
||||||
|
'textPlacement': 'enum', 'textPosition': 'enum', 'topBorder': 'line',
|
||||||
|
'verticalAlignment': 'enum', 'width': 'int', 'widthWeightFactor': 'int',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def format_ml_tags():
|
||||||
|
"""Теги, значение которых — многоязычная строка (<v8:item> на язык), а не скаляр."""
|
||||||
|
return {'format': True, 'editFormat': True, 'mask': True}
|
||||||
|
|
||||||
|
|
||||||
|
def color_to_dsl(node, val):
|
||||||
|
"""Цвет платформа хранит значением с префиксом пространства имён. style: объявлен в корне
|
||||||
|
документа и читается как есть; web/win объявлены прямо на узле сгенерированным префиксом
|
||||||
|
(d3p1), поэтому его надо разрешить в URI и вернуть привычное web:/win:."""
|
||||||
|
if ':' not in val or val.startswith('style:'):
|
||||||
|
return val
|
||||||
|
prefix, name = val.split(':', 1)
|
||||||
|
uri = node.nsmap.get(prefix)
|
||||||
|
if uri == 'http://v8.1c.ru/8.1/data/ui/colors/web':
|
||||||
|
return 'web:' + name
|
||||||
|
if uri == 'http://v8.1c.ru/8.1/data/ui/colors/windows':
|
||||||
|
return 'win:' + name
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
def int_of(node, default=0):
|
def int_of(node, default=0):
|
||||||
if node is not None and node.text:
|
if node is not None and node.text:
|
||||||
return int(node.text)
|
return int(node.text)
|
||||||
@@ -231,61 +276,50 @@ def main():
|
|||||||
|
|
||||||
raw_lines = []
|
raw_lines = []
|
||||||
for l_node in findall(root, "d:line"):
|
for l_node in findall(root, "d:line"):
|
||||||
raw_lines.append({"Width": int(l_node.get("width", "0"))})
|
st = find(l_node, "v8ui:style")
|
||||||
|
raw_lines.append({
|
||||||
|
"Width": int(l_node.get("width", "0")),
|
||||||
|
"Gap": l_node.get("gap", "false"),
|
||||||
|
"Style": (text_of(st) or "Solid") if st is not None else "Solid",
|
||||||
|
})
|
||||||
|
|
||||||
# --- 4. Extract format palette ---
|
# --- 4. Extract format palette ---
|
||||||
|
|
||||||
|
# Формат читаем целиком — «тег → значение», а не выборочные девять полей. Тип значения
|
||||||
|
# берём из общей таблицы, она же у компилятора.
|
||||||
|
kinds = format_tag_kind()
|
||||||
|
ml_tags = format_ml_tags()
|
||||||
|
|
||||||
raw_formats = []
|
raw_formats = []
|
||||||
for fmt_node in findall(root, "d:format"):
|
for fmt_node in findall(root, "d:format"):
|
||||||
|
# Совместимость с прежними полями: ими пользуются ширина колонки, высота строки и
|
||||||
|
# признак заполнения — они не часть стиля.
|
||||||
fmt = {
|
fmt = {
|
||||||
"FontIdx": -1,
|
"FontIdx": -1, "Width": 0, "Height": 0, "FillType": "", "Props": OrderedDict(),
|
||||||
"LB": -1, "TB": -1, "RB": -1, "BB": -1,
|
|
||||||
"Width": 0, "Height": 0,
|
|
||||||
"HA": "", "VA": "",
|
|
||||||
"Wrap": False, "FillType": "", "DataFormat": "",
|
|
||||||
}
|
}
|
||||||
|
for child in fmt_node:
|
||||||
|
tag = etree.QName(child).localname
|
||||||
|
if tag in ml_tags:
|
||||||
|
item = find(child, "v8:item/v8:content")
|
||||||
|
val = text_of(item) if item is not None else None
|
||||||
|
if val:
|
||||||
|
fmt["Props"][tag] = val
|
||||||
|
continue
|
||||||
|
val = (child.text or "").strip()
|
||||||
|
if not val:
|
||||||
|
continue
|
||||||
|
if kinds.get(tag) == "color":
|
||||||
|
val = color_to_dsl(child, val)
|
||||||
|
fmt["Props"][tag] = val
|
||||||
|
|
||||||
n = find(fmt_node, "d:font")
|
if "font" in fmt["Props"]:
|
||||||
if n is not None and n.text:
|
fmt["FontIdx"] = int(fmt["Props"]["font"])
|
||||||
fmt["FontIdx"] = int(n.text)
|
if "width" in fmt["Props"]:
|
||||||
n = find(fmt_node, "d:leftBorder")
|
fmt["Width"] = int(fmt["Props"]["width"])
|
||||||
if n is not None and n.text:
|
if "height" in fmt["Props"]:
|
||||||
fmt["LB"] = int(n.text)
|
fmt["Height"] = int(fmt["Props"]["height"])
|
||||||
n = find(fmt_node, "d:topBorder")
|
if "fillType" in fmt["Props"]:
|
||||||
if n is not None and n.text:
|
fmt["FillType"] = fmt["Props"]["fillType"]
|
||||||
fmt["TB"] = int(n.text)
|
|
||||||
n = find(fmt_node, "d:rightBorder")
|
|
||||||
if n is not None and n.text:
|
|
||||||
fmt["RB"] = int(n.text)
|
|
||||||
n = find(fmt_node, "d:bottomBorder")
|
|
||||||
if n is not None and n.text:
|
|
||||||
fmt["BB"] = int(n.text)
|
|
||||||
|
|
||||||
n = find(fmt_node, "d:width")
|
|
||||||
if n is not None and n.text:
|
|
||||||
fmt["Width"] = int(n.text)
|
|
||||||
n = find(fmt_node, "d:height")
|
|
||||||
if n is not None and n.text:
|
|
||||||
fmt["Height"] = int(n.text)
|
|
||||||
|
|
||||||
n = find(fmt_node, "d:horizontalAlignment")
|
|
||||||
if n is not None and n.text:
|
|
||||||
fmt["HA"] = n.text
|
|
||||||
n = find(fmt_node, "d:verticalAlignment")
|
|
||||||
if n is not None and n.text:
|
|
||||||
fmt["VA"] = n.text
|
|
||||||
|
|
||||||
n = find(fmt_node, "d:textPlacement")
|
|
||||||
if n is not None and n.text == "Wrap":
|
|
||||||
fmt["Wrap"] = True
|
|
||||||
|
|
||||||
n = find(fmt_node, "d:fillType")
|
|
||||||
if n is not None and n.text:
|
|
||||||
fmt["FillType"] = n.text
|
|
||||||
|
|
||||||
n = find(fmt_node, "d:format/v8:item/v8:content")
|
|
||||||
if n is not None and n.text:
|
|
||||||
fmt["DataFormat"] = n.text
|
|
||||||
|
|
||||||
raw_formats.append(fmt)
|
raw_formats.append(fmt)
|
||||||
|
|
||||||
@@ -493,45 +527,54 @@ def main():
|
|||||||
|
|
||||||
# --- 9. Build style key (ignoring fillType) ---
|
# --- 9. Build style key (ignoring fillType) ---
|
||||||
|
|
||||||
def get_border_desc(fmt):
|
# Свойства формата, которые стилем НЕ являются: ширина принадлежит колонке, высота —
|
||||||
|
# строке, вид заполнения выводится из того, чем задано содержимое ячейки.
|
||||||
|
NON_STYLE_TAGS = ("width", "height", "fillType", "font")
|
||||||
|
BORDER_TAGS = ("border", "leftBorder", "topBorder", "rightBorder", "bottomBorder")
|
||||||
|
|
||||||
|
def line_to_dsl(idx):
|
||||||
|
"""Ссылку в палитру разворачиваем в описание линии. Ширина 1 подразумевается,
|
||||||
|
поэтому обычная линия пишется просто именем стиля."""
|
||||||
|
i = int(idx)
|
||||||
|
if i < 0 or i >= len(raw_lines):
|
||||||
|
return "Solid"
|
||||||
|
ln = raw_lines[i]
|
||||||
|
out = OrderedDict()
|
||||||
|
out["style"] = ln["Style"]
|
||||||
|
if ln["Width"] != 1:
|
||||||
|
out["width"] = ln["Width"]
|
||||||
|
if ln["Gap"] == "true":
|
||||||
|
out["gap"] = True
|
||||||
|
if len(out) == 1:
|
||||||
|
return out["style"]
|
||||||
|
return out
|
||||||
|
|
||||||
|
def style_props(fmt):
|
||||||
|
"""Свойства формата, попадающие в стиль, — в каноническом порядке тегов."""
|
||||||
if not fmt:
|
if not fmt:
|
||||||
return {"Border": "none", "Thick": False}
|
return OrderedDict()
|
||||||
|
out = OrderedDict()
|
||||||
lb = fmt["LB"] >= 0
|
for tag, val in fmt["Props"].items():
|
||||||
tb = fmt["TB"] >= 0
|
if tag in NON_STYLE_TAGS:
|
||||||
rb = fmt["RB"] >= 0
|
continue
|
||||||
bb = fmt["BB"] >= 0
|
if tag in BORDER_TAGS:
|
||||||
|
out[tag] = line_to_dsl(val)
|
||||||
if not lb and not tb and not rb and not bb:
|
elif kinds.get(tag) == "bool":
|
||||||
return {"Border": "none", "Thick": False}
|
out[tag] = (val == "true")
|
||||||
|
elif kinds.get(tag) == "int":
|
||||||
thick = False
|
out[tag] = int(val)
|
||||||
for b_idx in [fmt["LB"], fmt["TB"], fmt["RB"], fmt["BB"]]:
|
else:
|
||||||
if b_idx >= 0 and b_idx < len(raw_lines) and raw_lines[b_idx]["Width"] >= 2:
|
out[tag] = val
|
||||||
thick = True
|
return out
|
||||||
break
|
|
||||||
|
|
||||||
if lb and tb and rb and bb:
|
|
||||||
return {"Border": "all", "Thick": thick}
|
|
||||||
|
|
||||||
sides = []
|
|
||||||
if tb:
|
|
||||||
sides.append("top")
|
|
||||||
if bb:
|
|
||||||
sides.append("bottom")
|
|
||||||
if lb:
|
|
||||||
sides.append("left")
|
|
||||||
if rb:
|
|
||||||
sides.append("right")
|
|
||||||
|
|
||||||
return {"Border": ",".join(sides), "Thick": thick}
|
|
||||||
|
|
||||||
def get_style_key(fmt):
|
def get_style_key(fmt):
|
||||||
if not fmt:
|
if not fmt:
|
||||||
return "empty"
|
return "empty"
|
||||||
fi = fmt["FontIdx"] if fmt["FontIdx"] >= 0 else 0
|
fi = fmt["FontIdx"] if fmt["FontIdx"] >= 0 else 0
|
||||||
bd = get_border_desc(fmt)
|
parts = [f"f={fi}"]
|
||||||
return f"f={fi}|b={bd['Border']}|bw={bd['Thick']}|ha={fmt['HA']}|va={fmt['VA']}|wr={fmt['Wrap']}|df={fmt['DataFormat']}"
|
for tag, val in style_props(fmt).items():
|
||||||
|
parts.append(f"{tag}={val}")
|
||||||
|
return "|".join(parts)
|
||||||
|
|
||||||
# --- 10. Name fonts ---
|
# --- 10. Name fonts ---
|
||||||
|
|
||||||
@@ -614,32 +657,40 @@ def main():
|
|||||||
format_to_style_key[cell["FormatIdx"]] = key
|
format_to_style_key[cell["FormatIdx"]] = key
|
||||||
|
|
||||||
def name_style(fmt):
|
def name_style(fmt):
|
||||||
|
"""Имя стиля — читаемая метка по самым заметным свойствам. Полнота тут не нужна:
|
||||||
|
различает стили ключ, имя лишь помогает автору ориентироваться."""
|
||||||
if not fmt:
|
if not fmt:
|
||||||
return "default"
|
return "default"
|
||||||
parts = []
|
parts = []
|
||||||
|
props = style_props(fmt)
|
||||||
|
|
||||||
fi = fmt["FontIdx"] if fmt["FontIdx"] >= 0 else 0
|
fi = fmt["FontIdx"] if fmt["FontIdx"] >= 0 else 0
|
||||||
if fi in font_names and font_names[fi] != "default":
|
if fi in font_names and font_names[fi] != "default":
|
||||||
parts.append(font_names[fi])
|
parts.append(font_names[fi])
|
||||||
|
|
||||||
bd = get_border_desc(fmt)
|
if "border" in props:
|
||||||
if bd["Border"] != "none":
|
parts.append("bordered")
|
||||||
if bd["Border"] == "all":
|
else:
|
||||||
parts.append("bordered")
|
sides = [s[:-6] for s in ("leftBorder", "topBorder", "rightBorder", "bottomBorder")
|
||||||
else:
|
if s in props]
|
||||||
parts.append(f"border-{bd['Border']}")
|
if sides:
|
||||||
|
parts.append("border-" + ",".join(sides))
|
||||||
|
|
||||||
if fmt["HA"] == "Center":
|
ha = props.get("horizontalAlignment")
|
||||||
|
if ha == "Center":
|
||||||
parts.append("center")
|
parts.append("center")
|
||||||
elif fmt["HA"] == "Right":
|
elif ha == "Right":
|
||||||
parts.append("right")
|
parts.append("right")
|
||||||
if fmt["VA"] == "Center":
|
va = props.get("verticalAlignment")
|
||||||
|
if va == "Center":
|
||||||
parts.append("vcenter")
|
parts.append("vcenter")
|
||||||
elif fmt["VA"] == "Top":
|
elif va == "Top":
|
||||||
parts.append("vtop")
|
parts.append("vtop")
|
||||||
if fmt["Wrap"]:
|
if props.get("textPlacement") == "Wrap":
|
||||||
parts.append("wrap")
|
parts.append("wrap")
|
||||||
if fmt["DataFormat"]:
|
if "backColor" in props:
|
||||||
|
parts.append("bg")
|
||||||
|
if "format" in props:
|
||||||
parts.append("fmt")
|
parts.append("fmt")
|
||||||
|
|
||||||
if len(parts) == 0:
|
if len(parts) == 0:
|
||||||
@@ -665,25 +716,7 @@ def main():
|
|||||||
fi = fmt["FontIdx"] if fmt["FontIdx"] >= 0 else 0
|
fi = fmt["FontIdx"] if fmt["FontIdx"] >= 0 else 0
|
||||||
if fi in font_names and font_names[fi] != "default":
|
if fi in font_names and font_names[fi] != "default":
|
||||||
s_def["font"] = font_names[fi]
|
s_def["font"] = font_names[fi]
|
||||||
if fmt["HA"]:
|
s_def.update(style_props(fmt))
|
||||||
a_map = {"Left": "left", "Center": "center", "Right": "right"}
|
|
||||||
a = a_map.get(fmt["HA"])
|
|
||||||
if a:
|
|
||||||
s_def["align"] = a
|
|
||||||
if fmt["VA"]:
|
|
||||||
va_map = {"Top": "top", "Center": "center"}
|
|
||||||
a = va_map.get(fmt["VA"])
|
|
||||||
if a:
|
|
||||||
s_def["valign"] = a
|
|
||||||
bd = get_border_desc(fmt)
|
|
||||||
if bd["Border"] != "none":
|
|
||||||
s_def["border"] = bd["Border"]
|
|
||||||
if bd["Thick"]:
|
|
||||||
s_def["borderWidth"] = "thick"
|
|
||||||
if fmt["Wrap"]:
|
|
||||||
s_def["wrap"] = True
|
|
||||||
if fmt["DataFormat"]:
|
|
||||||
s_def["format"] = fmt["DataFormat"]
|
|
||||||
|
|
||||||
style_defs[name] = s_def
|
style_defs[name] = s_def
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user