initial commit
This commit is contained in:
commit
db6129d356
334
k2t.py
Normal file
334
k2t.py
Normal file
@ -0,0 +1,334 @@
|
||||
from typing import Self, Type, Any
|
||||
import tomllib
|
||||
from dataclasses import dataclass, fields
|
||||
from xml.etree import ElementTree
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class Entry:
|
||||
name: str
|
||||
source: str | None
|
||||
frame_color: str | None
|
||||
fill_color: str | None
|
||||
|
||||
members: list | None = None
|
||||
expanded: bool = False
|
||||
|
||||
dither_pattern: str = 'C1'
|
||||
line_style: str = 'C0'
|
||||
frame_brightness: int = 0
|
||||
fill_brightness: int = 0
|
||||
width: int = 1
|
||||
animation: int = 0
|
||||
|
||||
valid: bool = True
|
||||
visible: bool = True
|
||||
transparent: bool = False
|
||||
marked: bool = False
|
||||
xfill: bool = False
|
||||
|
||||
@classmethod
|
||||
def from_xml(cls: Type[Self], el: ElementTree.Element) -> Self:
|
||||
assert el.tag in ('properties', 'group-members')
|
||||
|
||||
members = []
|
||||
args: dict[str, Any] = {
|
||||
'frame_color': None,
|
||||
'fill_color': None,
|
||||
'source': None,
|
||||
}
|
||||
for child in el:
|
||||
if not child.text:
|
||||
continue
|
||||
|
||||
tag = child.tag
|
||||
dtag = tag.replace('-', '_')
|
||||
if tag in ('frame-color', 'fill-color'):
|
||||
args[dtag] = child.text[1:]
|
||||
elif tag in ('dither-pattern', 'line-style', 'source', 'name'):
|
||||
args[dtag] = child.text
|
||||
elif tag in ('valid', 'visible', 'transparent', 'marked', 'xfill', 'expanded'):
|
||||
val = {
|
||||
'false': False,
|
||||
'true': True,
|
||||
}[child.text.lower()]
|
||||
args[tag] = val
|
||||
elif tag in ('frame-brightness', 'fill-brightness', 'width', 'animation'):
|
||||
args[dtag] = int(child.text)
|
||||
elif tag == 'group-members':
|
||||
members.append(Entry.from_xml(child))
|
||||
else:
|
||||
raise Exception(f'Unexpected tag: {child.tag}')
|
||||
|
||||
if not members:
|
||||
members = None
|
||||
|
||||
return cls(members=members, **args)
|
||||
|
||||
def to_xml(self, is_member: bool = False) -> ElementTree.Element:
|
||||
main_tag = 'group-members' if is_member else 'properties'
|
||||
el = ElementTree.Element(main_tag)
|
||||
for field in fields(self):
|
||||
val = getattr(self, field.name)
|
||||
if val is None or field.name == 'members':
|
||||
continue
|
||||
|
||||
if (val == field.default
|
||||
and (field not in ('dither-pattern', 'line-style')
|
||||
or self.source is None
|
||||
)):
|
||||
continue
|
||||
|
||||
tag = field.name.replace('_', '-')
|
||||
if tag in ('frame-color', 'fill-color'):
|
||||
val = '#' + val
|
||||
if not isinstance(val, str):
|
||||
val = str(val).lower()
|
||||
el2 = ElementTree.Element(tag)
|
||||
el2.text = val
|
||||
el.append(el2)
|
||||
|
||||
if self.members:
|
||||
for member in self.members:
|
||||
el.append(member.to_xml(is_member=True))
|
||||
|
||||
return el
|
||||
|
||||
def to_toml(self, depth: int = 0) -> str:
|
||||
parts = ('layers',) + ('members',) * depth
|
||||
|
||||
s = '[[' + '.'.join(parts) + ']]\n'
|
||||
for field in fields(self):
|
||||
val = getattr(self, field.name)
|
||||
if val == field.default or val is None:
|
||||
continue
|
||||
|
||||
if isinstance(val, str):
|
||||
vstr = '"' + toml_escape(val) + '"'
|
||||
elif isinstance(val, bool):
|
||||
vstr = str(val).lower()
|
||||
elif isinstance(val, int):
|
||||
vstr = str(val)
|
||||
|
||||
key = field.name.replace('_', '-')
|
||||
s += f'{key} = {vstr}\n'
|
||||
|
||||
s += '\n'
|
||||
|
||||
if self.members:
|
||||
for member in self.members:
|
||||
s += member.to_toml(depth + 1)
|
||||
|
||||
return s
|
||||
|
||||
@classmethod
|
||||
def from_toml(cls: Type[Self], tree: dict) -> Self:
|
||||
members = []
|
||||
args: dict[str, Any] = {
|
||||
'frame_color': None,
|
||||
'fill_color': None,
|
||||
'source': None,
|
||||
}
|
||||
for key, val in tree.items():
|
||||
if key != 'members':
|
||||
args[key.replace('-', '_')] = val
|
||||
|
||||
if 'members' in tree:
|
||||
for child in tree['members']:
|
||||
members.append(Entry.from_toml(child))
|
||||
|
||||
if not members:
|
||||
members = None
|
||||
|
||||
return cls(members=members, **args)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class Pattern:
|
||||
pattern: str # 2D, Nx8 or Nx16 or Nx32
|
||||
name: str | None = None
|
||||
order: int | None = None
|
||||
|
||||
@classmethod
|
||||
def from_xml(cls: Type[Self], el: ElementTree.Element) -> Self:
|
||||
assert el.tag == 'custom-dither-pattern'
|
||||
|
||||
lines = []
|
||||
args: dict[str, Any] = {}
|
||||
for child in el:
|
||||
if not child.text:
|
||||
continue
|
||||
|
||||
tag = child.tag
|
||||
if tag == 'name':
|
||||
args[tag] = child.text
|
||||
elif tag == 'order':
|
||||
args[tag] = int(child.text)
|
||||
elif tag == 'pattern':
|
||||
for line in child:
|
||||
assert line.tag == 'line'
|
||||
assert line.text is not None
|
||||
lines.append(line.text)
|
||||
else:
|
||||
raise Exception(f'Unexpected tag: {tag}')
|
||||
|
||||
if not lines:
|
||||
raise Exception('No pattern found in custom-dither-pattern!')
|
||||
pattern = '\n'.join(lines)
|
||||
|
||||
return cls(pattern=pattern, **args)
|
||||
|
||||
def to_xml(self) -> ElementTree.Element:
|
||||
el = ElementTree.Element('custom-dither-pattern')
|
||||
if self.name is not None:
|
||||
el2 = ElementTree.Element('name')
|
||||
el2.text = self.name
|
||||
el.append(el2)
|
||||
if self.order is not None:
|
||||
el2 = ElementTree.Element('order')
|
||||
el2.text = str(self.order)
|
||||
el.append(el2)
|
||||
|
||||
pat = ElementTree.Element('pattern')
|
||||
for line in self.pattern.strip().split('\n'):
|
||||
el2 = ElementTree.Element('line')
|
||||
el2.text = line
|
||||
pat.append(el2)
|
||||
el.append(pat)
|
||||
|
||||
return el
|
||||
|
||||
def to_toml(self) -> str:
|
||||
s = '[[patterns]]\n'
|
||||
if self.name is not None:
|
||||
s += f'name = "{toml_escape(self.name)}"\n'
|
||||
if self.order is not None:
|
||||
s += f'order = {self.order}\n'
|
||||
|
||||
s += 'pattern = """\n'
|
||||
s += self.pattern + '"""\n\n'
|
||||
|
||||
return s
|
||||
|
||||
@classmethod
|
||||
def from_toml(cls: Type[Self], tree: dict) -> Self:
|
||||
lines = tree['pattern'].split('\n')
|
||||
n_px = len(lines[0])
|
||||
assert all(len(line) == n_px for line in lines[1:])
|
||||
return cls(**tree)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class LineStyle:
|
||||
pattern: str # 1D, up to 32 values
|
||||
name: str | None = None
|
||||
order: int | None = None
|
||||
|
||||
@classmethod
|
||||
def from_xml(cls: Type[Self], el: ElementTree.Element) -> Self:
|
||||
assert el.tag == 'custom-line-style'
|
||||
|
||||
args: dict[str, Any] = {}
|
||||
for child in el:
|
||||
if not child.text:
|
||||
continue
|
||||
|
||||
tag = child.tag
|
||||
if tag in ('name', 'pattern'):
|
||||
args[tag] = child.text
|
||||
elif tag == 'order':
|
||||
args[tag] = int(child.text)
|
||||
else:
|
||||
raise Exception(f'Unexpected tag: {tag}')
|
||||
return cls(**args)
|
||||
|
||||
def to_xml(self) -> ElementTree.Element:
|
||||
el = ElementTree.Element('custom-line-style')
|
||||
if self.name is not None:
|
||||
el2 = ElementTree.Element('name')
|
||||
el2.text = self.name
|
||||
el.append(el2)
|
||||
if self.order is not None:
|
||||
el2 = ElementTree.Element('order')
|
||||
el2.text = str(self.order)
|
||||
el.append(el2)
|
||||
el2 = ElementTree.Element('pattern')
|
||||
el2.text = self.pattern
|
||||
el.append(el2)
|
||||
return el
|
||||
|
||||
def to_toml(self) -> str:
|
||||
s = '[[linestyles]]\n'
|
||||
if self.name is not None:
|
||||
s += f'name = "{toml_escape(self.name)}"\n'
|
||||
if self.order is not None:
|
||||
s += f'order = {self.order}\n'
|
||||
s += f'pattern = "{self.pattern}"\n\n'
|
||||
return s
|
||||
|
||||
@classmethod
|
||||
def from_toml(cls: Type[Self], tree: dict) -> Self:
|
||||
assert len(tree['pattern']) <= 32
|
||||
return cls(**tree)
|
||||
|
||||
|
||||
def k2t(kfile: str, tfile: str) -> None:
|
||||
tree = ElementTree.parse(kfile)
|
||||
root = tree.getroot()
|
||||
|
||||
assert root.tag == 'layer-properties'
|
||||
name = None
|
||||
entries = []
|
||||
patterns = []
|
||||
linestyles = []
|
||||
for child in root:
|
||||
if child.tag == 'custom-dither-pattern':
|
||||
patterns.append(Pattern.from_xml(child))
|
||||
elif child.tag == 'custom-line-style':
|
||||
linestyles.append(LineStyle.from_xml(child))
|
||||
elif child.tag == 'properties':
|
||||
entries.append(Entry.from_xml(child))
|
||||
elif child.tag == 'name':
|
||||
name = child.text
|
||||
else:
|
||||
raise Exception(f'Unexpected tag: {child.tag}')
|
||||
|
||||
if name is not None:
|
||||
print(f'Discarding tab name: {name}')
|
||||
|
||||
with open(tfile, 'wt') as ff:
|
||||
for entry in entries:
|
||||
ff.write(entry.to_toml())
|
||||
for pattern in patterns:
|
||||
ff.write(pattern.to_toml())
|
||||
for linestyle in linestyles:
|
||||
ff.write(linestyle.to_toml())
|
||||
|
||||
|
||||
def t2k(tfile: str, kfile: str) -> None:
|
||||
root = ElementTree.Element('layer-properties')
|
||||
|
||||
with open(tfile, 'rb') as ff:
|
||||
ttree = tomllib.load(ff)
|
||||
|
||||
entries = [Entry.from_toml(entry)
|
||||
for entry in ttree['layers']]
|
||||
patterns = [Pattern.from_toml(pat)
|
||||
for pat in ttree['patterns']]
|
||||
linestyles = [LineStyle.from_toml(ls)
|
||||
for ls in ttree['linestyles']]
|
||||
|
||||
for entry in entries:
|
||||
root.append(entry.to_xml())
|
||||
for pattern in patterns:
|
||||
root.append(pattern.to_xml())
|
||||
for linestyle in linestyles:
|
||||
root.append(linestyle.to_xml())
|
||||
|
||||
ktree = ElementTree.ElementTree(root)
|
||||
ElementTree.indent(ktree)
|
||||
ktree.write(kfile)
|
||||
|
||||
|
||||
def toml_escape(string: str) -> str:
|
||||
return string.replace('\\', '\\\\').replace('"', '\\"')
|
Loading…
Reference in New Issue
Block a user