mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-13 15:03:20 +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
|
||||
param(
|
||||
[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("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 ---
|
||||
|
||||
# Размер шрифта бывает дробным (8.3, 11.3 — в корпусе ERP это треть макетов). [int] его
|
||||
@@ -61,50 +106,47 @@ foreach ($fNode in $root.SelectNodes("d:font", $ns)) {
|
||||
|
||||
$rawLines = @()
|
||||
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 ---
|
||||
# Формат читаем целиком — «тег → значение», а не выборочные девять полей. Тип значения
|
||||
# берём из общей таблицы, она же у компилятора.
|
||||
$formatKinds = Get-FormatTagKind
|
||||
$formatMl = Get-FormatMlTags
|
||||
|
||||
$rawFormats = @()
|
||||
foreach ($fmtNode in $root.SelectNodes("d:format", $ns)) {
|
||||
# Совместимость с прежними полями: ими пользуются ширина колонки, высота строки и
|
||||
# признак заполнения — они не часть стиля.
|
||||
$fmt = @{
|
||||
FontIdx = -1
|
||||
LB = -1; TB = -1; RB = -1; BB = -1
|
||||
Width = 0; Height = 0
|
||||
HA = ""; VA = ""
|
||||
Wrap = $false; FillType = ""; DataFormat = ""
|
||||
FontIdx = -1; Width = 0; Height = 0; FillType = ""
|
||||
Props = [ordered]@{}
|
||||
}
|
||||
|
||||
$n = $fmtNode.SelectSingleNode("d:font", $ns)
|
||||
if ($n) { $fmt.FontIdx = [int]$n.InnerText }
|
||||
$n = $fmtNode.SelectSingleNode("d:leftBorder", $ns)
|
||||
if ($n) { $fmt.LB = [int]$n.InnerText }
|
||||
$n = $fmtNode.SelectSingleNode("d:topBorder", $ns)
|
||||
if ($n) { $fmt.TB = [int]$n.InnerText }
|
||||
$n = $fmtNode.SelectSingleNode("d:rightBorder", $ns)
|
||||
if ($n) { $fmt.RB = [int]$n.InnerText }
|
||||
$n = $fmtNode.SelectSingleNode("d:bottomBorder", $ns)
|
||||
if ($n) { $fmt.BB = [int]$n.InnerText }
|
||||
foreach ($child in $fmtNode.ChildNodes) {
|
||||
if ($child.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
|
||||
$tag = $child.get_LocalName()
|
||||
if ($formatMl.ContainsKey($tag)) {
|
||||
$item = $child.SelectSingleNode("v8:item/v8:content", $ns)
|
||||
if ($item -and $item.InnerText) { $fmt.Props[$tag] = $item.InnerText }
|
||||
continue
|
||||
}
|
||||
$val = $child.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 ($n) { $fmt.Width = [int]$n.InnerText }
|
||||
$n = $fmtNode.SelectSingleNode("d:height", $ns)
|
||||
if ($n) { $fmt.Height = [int]$n.InnerText }
|
||||
|
||||
$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 }
|
||||
if ($fmt.Props.Contains('font')) { $fmt.FontIdx = [int]$fmt.Props['font'] }
|
||||
if ($fmt.Props.Contains('width')) { $fmt.Width = [int]$fmt.Props['width'] }
|
||||
if ($fmt.Props.Contains('height')) { $fmt.Height = [int]$fmt.Props['height'] }
|
||||
if ($fmt.Props.Contains('fillType')) { $fmt.FillType = $fmt.Props['fillType'] }
|
||||
|
||||
$rawFormats += $fmt
|
||||
}
|
||||
@@ -317,43 +359,60 @@ function Get-DslText {
|
||||
|
||||
# --- 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)
|
||||
if (-not $fmt) { return @{ Border = "none"; Thick = $false } }
|
||||
|
||||
$lb = $fmt.LB -ge 0; $tb = $fmt.TB -ge 0
|
||||
$rb = $fmt.RB -ge 0; $bb = $fmt.BB -ge 0
|
||||
|
||||
if (-not $lb -and -not $tb -and -not $rb -and -not $bb) {
|
||||
return @{ Border = "none"; Thick = $false }
|
||||
}
|
||||
|
||||
$thick = $false
|
||||
foreach ($bIdx in @($fmt.LB, $fmt.TB, $fmt.RB, $fmt.BB)) {
|
||||
if ($bIdx -ge 0 -and $bIdx -lt $rawLines.Count -and $rawLines[$bIdx].Width -ge 2) {
|
||||
$thick = $true; break
|
||||
$out = [ordered]@{}
|
||||
if (-not $fmt) { return $out }
|
||||
foreach ($tag in $fmt.Props.Keys) {
|
||||
if ($nonStyleTags -ccontains $tag) { continue }
|
||||
$val = $fmt.Props[$tag]
|
||||
if ($borderTags -ccontains $tag) {
|
||||
$out[$tag] = ConvertTo-DslLine $val
|
||||
} elseif ($formatKinds[$tag] -ceq 'bool') {
|
||||
$out[$tag] = ($val -ceq 'true')
|
||||
} elseif ($formatKinds[$tag] -ceq 'int') {
|
||||
$out[$tag] = [int]$val
|
||||
} else {
|
||||
$out[$tag] = $val
|
||||
}
|
||||
}
|
||||
|
||||
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 }
|
||||
return $out
|
||||
}
|
||||
|
||||
function Get-StyleKey {
|
||||
param($fmt)
|
||||
if (-not $fmt) { return "empty" }
|
||||
$fi = if ($fmt.FontIdx -ge 0) { $fmt.FontIdx } else { 0 }
|
||||
$bd = Get-BorderDesc $fmt
|
||||
return "f=$fi|b=$($bd.Border)|bw=$($bd.Thick)|ha=$($fmt.HA)|va=$($fmt.VA)|wr=$($fmt.Wrap)|df=$($fmt.DataFormat)"
|
||||
$parts = @("f=$fi")
|
||||
$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 ---
|
||||
@@ -435,28 +494,36 @@ foreach ($r in $rowData.Values) {
|
||||
}
|
||||
}
|
||||
|
||||
# Имя стиля — читаемая метка по самым заметным свойствам. Полнота тут не нужна:
|
||||
# различает стили ключ, имя лишь помогает автору ориентироваться.
|
||||
function Name-Style {
|
||||
param($fmt)
|
||||
if (-not $fmt) { return "default" }
|
||||
$parts = @()
|
||||
$props = Get-StyleProps $fmt
|
||||
|
||||
$fi = if ($fmt.FontIdx -ge 0) { $fmt.FontIdx } else { 0 }
|
||||
if ($fontNames.ContainsKey($fi) -and $fontNames[$fi] -ne "default") {
|
||||
$parts += $fontNames[$fi]
|
||||
}
|
||||
|
||||
$bd = Get-BorderDesc $fmt
|
||||
if ($bd.Border -ne "none") {
|
||||
if ($bd.Border -eq "all") { $parts += "bordered" }
|
||||
else { $parts += "border-$($bd.Border)" }
|
||||
if ($props.Contains('border')) {
|
||||
$parts += "bordered"
|
||||
} else {
|
||||
$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" }
|
||||
elseif ($fmt.HA -eq "Right") { $parts += "right" }
|
||||
if ($fmt.VA -eq "Center") { $parts += "vcenter" }
|
||||
elseif ($fmt.VA -eq "Top") { $parts += "vtop" }
|
||||
if ($fmt.Wrap) { $parts += "wrap" }
|
||||
if ($fmt.DataFormat) { $parts += "fmt" }
|
||||
if ($props['horizontalAlignment'] -ceq "Center") { $parts += "center" }
|
||||
elseif ($props['horizontalAlignment'] -ceq "Right") { $parts += "right" }
|
||||
if ($props['verticalAlignment'] -ceq "Center") { $parts += "vcenter" }
|
||||
elseif ($props['verticalAlignment'] -ceq "Top") { $parts += "vtop" }
|
||||
if ($props['textPlacement'] -ceq "Wrap") { $parts += "wrap" }
|
||||
if ($props.Contains('backColor')) { $parts += "bg" }
|
||||
if ($props.Contains('format')) { $parts += "fmt" }
|
||||
|
||||
if ($parts.Count -eq 0) { return "default" }
|
||||
return ($parts -join "-")
|
||||
@@ -479,21 +546,8 @@ foreach ($key in $styleKeys.Keys) {
|
||||
if ($fontNames.ContainsKey($fi) -and $fontNames[$fi] -ne "default") {
|
||||
$sDef["font"] = $fontNames[$fi]
|
||||
}
|
||||
if ($fmt.HA) {
|
||||
$a = switch ($fmt.HA) { "Left" { "left" } "Center" { "center" } "Right" { "right" } }
|
||||
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 }
|
||||
$props = Get-StyleProps $fmt
|
||||
foreach ($tag in $props.Keys) { $sDef[$tag] = $props[$tag] }
|
||||
|
||||
$styleDefs[$name] = $sDef
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/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
|
||||
|
||||
import argparse
|
||||
@@ -69,6 +69,51 @@ def to_font_size(raw):
|
||||
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):
|
||||
if node is not None and node.text:
|
||||
return int(node.text)
|
||||
@@ -231,61 +276,50 @@ def main():
|
||||
|
||||
raw_lines = []
|
||||
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 ---
|
||||
|
||||
# Формат читаем целиком — «тег → значение», а не выборочные девять полей. Тип значения
|
||||
# берём из общей таблицы, она же у компилятора.
|
||||
kinds = format_tag_kind()
|
||||
ml_tags = format_ml_tags()
|
||||
|
||||
raw_formats = []
|
||||
for fmt_node in findall(root, "d:format"):
|
||||
# Совместимость с прежними полями: ими пользуются ширина колонки, высота строки и
|
||||
# признак заполнения — они не часть стиля.
|
||||
fmt = {
|
||||
"FontIdx": -1,
|
||||
"LB": -1, "TB": -1, "RB": -1, "BB": -1,
|
||||
"Width": 0, "Height": 0,
|
||||
"HA": "", "VA": "",
|
||||
"Wrap": False, "FillType": "", "DataFormat": "",
|
||||
"FontIdx": -1, "Width": 0, "Height": 0, "FillType": "", "Props": OrderedDict(),
|
||||
}
|
||||
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 n is not None and n.text:
|
||||
fmt["FontIdx"] = int(n.text)
|
||||
n = find(fmt_node, "d:leftBorder")
|
||||
if n is not None and n.text:
|
||||
fmt["LB"] = int(n.text)
|
||||
n = find(fmt_node, "d:topBorder")
|
||||
if n is not None and n.text:
|
||||
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
|
||||
if "font" in fmt["Props"]:
|
||||
fmt["FontIdx"] = int(fmt["Props"]["font"])
|
||||
if "width" in fmt["Props"]:
|
||||
fmt["Width"] = int(fmt["Props"]["width"])
|
||||
if "height" in fmt["Props"]:
|
||||
fmt["Height"] = int(fmt["Props"]["height"])
|
||||
if "fillType" in fmt["Props"]:
|
||||
fmt["FillType"] = fmt["Props"]["fillType"]
|
||||
|
||||
raw_formats.append(fmt)
|
||||
|
||||
@@ -493,45 +527,54 @@ def main():
|
||||
|
||||
# --- 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:
|
||||
return {"Border": "none", "Thick": False}
|
||||
|
||||
lb = fmt["LB"] >= 0
|
||||
tb = fmt["TB"] >= 0
|
||||
rb = fmt["RB"] >= 0
|
||||
bb = fmt["BB"] >= 0
|
||||
|
||||
if not lb and not tb and not rb and not bb:
|
||||
return {"Border": "none", "Thick": False}
|
||||
|
||||
thick = False
|
||||
for b_idx in [fmt["LB"], fmt["TB"], fmt["RB"], fmt["BB"]]:
|
||||
if b_idx >= 0 and b_idx < len(raw_lines) and raw_lines[b_idx]["Width"] >= 2:
|
||||
thick = True
|
||||
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}
|
||||
return OrderedDict()
|
||||
out = OrderedDict()
|
||||
for tag, val in fmt["Props"].items():
|
||||
if tag in NON_STYLE_TAGS:
|
||||
continue
|
||||
if tag in BORDER_TAGS:
|
||||
out[tag] = line_to_dsl(val)
|
||||
elif kinds.get(tag) == "bool":
|
||||
out[tag] = (val == "true")
|
||||
elif kinds.get(tag) == "int":
|
||||
out[tag] = int(val)
|
||||
else:
|
||||
out[tag] = val
|
||||
return out
|
||||
|
||||
def get_style_key(fmt):
|
||||
if not fmt:
|
||||
return "empty"
|
||||
fi = fmt["FontIdx"] if fmt["FontIdx"] >= 0 else 0
|
||||
bd = get_border_desc(fmt)
|
||||
return f"f={fi}|b={bd['Border']}|bw={bd['Thick']}|ha={fmt['HA']}|va={fmt['VA']}|wr={fmt['Wrap']}|df={fmt['DataFormat']}"
|
||||
parts = [f"f={fi}"]
|
||||
for tag, val in style_props(fmt).items():
|
||||
parts.append(f"{tag}={val}")
|
||||
return "|".join(parts)
|
||||
|
||||
# --- 10. Name fonts ---
|
||||
|
||||
@@ -614,32 +657,40 @@ def main():
|
||||
format_to_style_key[cell["FormatIdx"]] = key
|
||||
|
||||
def name_style(fmt):
|
||||
"""Имя стиля — читаемая метка по самым заметным свойствам. Полнота тут не нужна:
|
||||
различает стили ключ, имя лишь помогает автору ориентироваться."""
|
||||
if not fmt:
|
||||
return "default"
|
||||
parts = []
|
||||
props = style_props(fmt)
|
||||
|
||||
fi = fmt["FontIdx"] if fmt["FontIdx"] >= 0 else 0
|
||||
if fi in font_names and font_names[fi] != "default":
|
||||
parts.append(font_names[fi])
|
||||
|
||||
bd = get_border_desc(fmt)
|
||||
if bd["Border"] != "none":
|
||||
if bd["Border"] == "all":
|
||||
parts.append("bordered")
|
||||
else:
|
||||
parts.append(f"border-{bd['Border']}")
|
||||
if "border" in props:
|
||||
parts.append("bordered")
|
||||
else:
|
||||
sides = [s[:-6] for s in ("leftBorder", "topBorder", "rightBorder", "bottomBorder")
|
||||
if s in props]
|
||||
if sides:
|
||||
parts.append("border-" + ",".join(sides))
|
||||
|
||||
if fmt["HA"] == "Center":
|
||||
ha = props.get("horizontalAlignment")
|
||||
if ha == "Center":
|
||||
parts.append("center")
|
||||
elif fmt["HA"] == "Right":
|
||||
elif ha == "Right":
|
||||
parts.append("right")
|
||||
if fmt["VA"] == "Center":
|
||||
va = props.get("verticalAlignment")
|
||||
if va == "Center":
|
||||
parts.append("vcenter")
|
||||
elif fmt["VA"] == "Top":
|
||||
elif va == "Top":
|
||||
parts.append("vtop")
|
||||
if fmt["Wrap"]:
|
||||
if props.get("textPlacement") == "Wrap":
|
||||
parts.append("wrap")
|
||||
if fmt["DataFormat"]:
|
||||
if "backColor" in props:
|
||||
parts.append("bg")
|
||||
if "format" in props:
|
||||
parts.append("fmt")
|
||||
|
||||
if len(parts) == 0:
|
||||
@@ -665,25 +716,7 @@ def main():
|
||||
fi = fmt["FontIdx"] if fmt["FontIdx"] >= 0 else 0
|
||||
if fi in font_names and font_names[fi] != "default":
|
||||
s_def["font"] = font_names[fi]
|
||||
if fmt["HA"]:
|
||||
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"]
|
||||
s_def.update(style_props(fmt))
|
||||
|
||||
style_defs[name] = s_def
|
||||
|
||||
|
||||
Reference in New Issue
Block a user