|
#!/usr/bin/env python3 |
|
|
|
from xml.etree import ElementTree |
|
from collections import namedtuple |
|
import argparse |
|
import json |
|
import os |
|
import css_parser |
|
import datetime |
|
|
|
# Defaults to bake into the script |
|
defaults = { |
|
"opercall": "N0CALL", |
|
"opername_first": "You", |
|
"opergrid": "XY12ab", |
|
"utcdt_iso": datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M"), |
|
} |
|
|
|
SVGField = namedtuple("SVGField", ["element", "classes"]) |
|
|
|
ap = argparse.ArgumentParser() |
|
ap.add_argument("--ignore-class", nargs=1, action='append', default=[], |
|
help="Ignore classes with this name") |
|
ap.add_argument("--set-default", nargs=2, action='append', default=[], |
|
help="Set the default value for the named field") |
|
ap.add_argument("--set-value", nargs=2, action='append', default=[], |
|
help="Set the value for the named field") |
|
ap.add_argument("--default", nargs=1, |
|
help="Default value for unspecified template values") |
|
ap.add_argument("template", help="Template SVG file") |
|
ap.add_argument("output", help="Output SVG file") |
|
ap.add_argument("inputs", nargs="*", help="Input JSON files") |
|
|
|
args = ap.parse_args() |
|
|
|
ignore_classes = set([x[0] for x in args.ignore_class]) |
|
|
|
defaults.update((k, json.loads(v)) for k, v in args.set_default) |
|
values = defaults.copy() |
|
|
|
for jsonfile in args.inputs: |
|
print("Importing data from %r" % jsonfile) |
|
with open(jsonfile, "r") as f: |
|
values.update(json.loads(f.read())) |
|
|
|
print("Setting values from command line") |
|
values.update(dict((k, json.loads(v)) for k, v in args.set_value)) |
|
|
|
print("Template input:\n%s" % json.dumps(values, indent=4)) |
|
|
|
print("Importing template %r" % args.template) |
|
svgdoc = ElementTree.parse(args.template) |
|
svgroot = svgdoc.getroot() |
|
|
|
if svgroot.tag == "svg": |
|
namespaces = None |
|
else: |
|
assert svgroot.tag.startswith("{") and svgroot.tag.endswith( |
|
"}svg" |
|
), "Root element is not a SVG tag" |
|
# Pick out the namespace URI |
|
namespaces = dict(svg=svgroot.tag[1:-4]) |
|
|
|
# Figure out what the text and image tags will be called, they'll have |
|
# the same namespace as the `<svg>` tag. |
|
TEXT_TAG = "{%s}text" % namespaces["svg"] |
|
IMAGE_TAG = "{%s}image" % namespaces["svg"] |
|
|
|
# Pick up all the style tags to identify classes |
|
template_fields = {} |
|
for elem in svgdoc.iterfind(".//svg:style", namespaces=namespaces): |
|
css = css_parser.parseString(elem.text) |
|
for rule in css.cssRules: |
|
if not rule.selectorText.startswith("."): |
|
continue |
|
fieldname = rule.selectorText[1:] |
|
properties = {} |
|
print("Parsing field %r" % fieldname) |
|
for prop in rule.style.getProperties(): |
|
if not prop.name.startswith("-vk4msl-template-"): |
|
continue |
|
p = prop.name[17:] |
|
print(" %r = %r" % (p, prop.value)) |
|
properties[p] = json.loads(prop.value) |
|
|
|
if properties: |
|
template_fields[rule.selectorText[1:]] = properties |
|
|
|
print(template_fields) |
|
|
|
# Figure out all classes defined and the elements using them |
|
classnames = {} |
|
for elem in svgdoc.iterfind(".//*[@class]", namespaces=namespaces): |
|
elem_classes = set( |
|
[cls for cls in elem.attrib.get("class", "").split(" ") if cls] |
|
) - ignore_classes |
|
|
|
if not elem_classes: |
|
continue |
|
|
|
field = SVGField(elem, elem_classes) |
|
|
|
for cls in elem_classes: |
|
classnames.setdefault(cls, []).append(field) |
|
|
|
print("All field classes: %s" % ", ".join(sorted(classnames.keys()))) |
|
|
|
# These would be sourced from command line arguments or some file, the |
|
# following shows how to replace the content of text fields. |
|
for cls, fields in classnames.items(): |
|
try: |
|
cls_props = template_fields[cls] |
|
except KeyError: |
|
continue |
|
|
|
if "format" in cls_props: |
|
value = cls_props["format"] % values |
|
elif cls in values: |
|
value = values[cls] |
|
elif args.default is not None: |
|
value = args.default |
|
elif cls_props.get("required", False) is False: |
|
value = "" |
|
else: |
|
raise KeyError("No value for fields of class %r" % cls) |
|
|
|
value = str(value) |
|
|
|
for field in fields: |
|
if field.element.tag == TEXT_TAG: |
|
for tspan in field.element.iterfind( |
|
"./svg:tspan", namespaces=namespaces |
|
): |
|
tspan.text = value |
|
elif field.element.tag == IMAGE_TAG: |
|
# We set the href field. This must be a data URI or an absolute |
|
# URI to a file. |
|
if (not value.startswith("data:")) and (value.split(":", 1)[0] not |
|
in ("http", "https", |
|
"ftp", "file")): |
|
# Assume it's the path to a file |
|
# NB: this won't work with Windows file names. I have no |
|
# solution to this. |
|
value = "file://%s" % os.path.realpath(value) |
|
|
|
# Iterate over the attributes and look for the href tag, which |
|
# may be prefixed by a namespace. Make a note of the new value. |
|
changes = {} |
|
for attr in field.element.attrib.keys(): |
|
if attr == "href" or ( |
|
attr.startswith("{") and attr.endswith("}href") |
|
): |
|
changes[attr] = value |
|
|
|
if changes: |
|
# Apply the changes |
|
field.element.attrib.update(changes) |
|
|
|
print("Writing out %r" % args.output) |
|
svgdoc.write(args.output) |