Newer
Older
#!/usr/bin/env python3
'''
Application for splitting groups from one SVG file into separate files
Usage:
python3 vector_layer_split.py infile format outdir
Usage Example:
python3 vector_layer_split.py vector_source.svg tw ../src/art/vector/layers/
'''
import lxml.etree as etree
import sys
import os
import copy
import re
import argparse
parser = argparse.ArgumentParser(
description='Application for splitting groups from one SVG file into separate files.')
parser.add_argument('-o', '--output', dest='output_dir', required=True,
parser.add_argument('-f', '--format', dest='output_format',
parser.add_argument('-p', '--prefix', dest='prefix', default='',
parser.add_argument('input_file', metavar='FILENAME', nargs='+',
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
args = parser.parse_args()
output_format = args.output_format
output_directory = args.output_dir
def splitFile(inputFile):
tree = etree.parse(inputFile)
normalize_svg.fix(tree)
# prepare output template
template = copy.deepcopy(tree)
root = template.getroot()
# remove all svg root attributes except document size
for a in root.attrib:
if (a != "viewBox"):
del root.attrib[a]
# add placeholder for CSS class (needed for workaround for non HTML 5.1 compliant browser)
# if output_format == 'tw':
# root.attrib["class"] = "'+_art_display_class+'"
ns = {
'svg': 'http://www.w3.org/2000/svg',
'inkscape': 'http://www.inkscape.org/namespaces/inkscape',
'sodipodi': "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
}
# remove all content, including metadata
# for twine output, style definitions are removed, too
defs = None
for e in root:
if (e.tag == etree.QName(ns['svg'], 'defs')):
defs = e
if (e.tag == etree.QName(ns['svg'], 'g') or
e.tag == etree.QName(ns['svg'], 'metadata') or
e.tag == etree.QName(ns['svg'], 'defs') or
e.tag == etree.QName(ns['sodipodi'], 'namedview') or
(output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style'))
):
root.remove(e)
# template preparation finished
# prepare regex for later use
regex_xmlns = re.compile(' xmlns[^ ]+')
regex_space = re.compile(r'[>]\s+[<]')
# find all groups
layers = tree.xpath('//svg:g', namespaces=ns)
for layer in layers:
i = layer.get('id')
if ( # disregard non-content groups
i.endswith("_") or # manually suppressed with underscore
i.startswith("XMLID") or # Illustrator generated group
i.startswith("g") # Inkscape generated group
):
continue
# create new canvas
output = copy.deepcopy(template)
# copy all shapes into template
canvas = output.getroot()
for e in layer:
canvas.append(e)
# represent template as SVG (binary string)
svg = etree.tostring(output, pretty_print=False)
# poor man's conditional defs insertion
# TODO: extract only referenced defs (filters, gradients, ...)
# TODO: detect necessity by traversing the elements. do not stupidly search in the string representation
if ("filter:" in svg.decode('utf-8')):
# it seems there is a filter referenced in the generated SVG, re-insert defs from main document
canvas.insert(0, defs)
# re-generate output
svg = etree.tostring(output, pretty_print=False)
if (output_format == 'tw'):
# remove unnecessary attributes
# TODO: never generate unnecessary attributes in the first place
svg = svg.decode('utf-8')
svg = regex_xmlns.sub('', svg)
svg = svg.replace(' inkscape:connector-curvature="0"', '') # this just saves space
svg = svg.replace('\n', '').replace('\r', '') # print cannot be multi-line
svg = regex_space.sub('><', svg) # remove indentation
svg = svg.replace('svg:', '') # svg namespace was removed
if ("Boob" in i): # internal groups are used for scaling
svg = svg.replace('<g ', '<g data-transform="boob" ') # boob art uses the boob scaling
elif ("Belly" in i):
svg = svg.replace('<g ', '<g data-transform="belly" ') # belly art uses the belly scaling
elif ("Balls" in i):
svg = svg.replace('<g ', '<g data-transform="balls" ') # balls art uses the balls scaling
else:
svg = svg.replace('<g ', '<g data-transform="art" ') # otherwise use default scaling
if (not svg.endswith('\n')):
svg += '\n'
svg = svg.encode('utf-8')
# save SVG string to file
i = layer.get('id')
output_path = os.path.join(output_directory, "{0}{1}.svg".format(args.prefix, i))
with open(output_path, 'wb') as f:
if (output_format == 'svg'):
# Header for normal SVG (XML)
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg)
elif (output_format == 'tw'):
f.write(svg)