mapnik/utils/upgrade_map_xml/upgrade_map_xml.py

270 lines
9.2 KiB
Python
Executable file

#!/usr/bin/env python
import os
import re
import sys
import optparse
import tempfile
__version__ = '0.1.0'
HAS_LXML = False
try:
import lxml.etree as etree
HAS_LXML = True
except ImportError:
try:
import elementtree.ElementTree as etree
except ImportError:
import xml.etree.ElementTree as etree
def color_text(color, text):
if os.name == 'nt':
return text
return "\033[9%sm%s\033[0m" % (color,text)
def indent(elem, level=0):
""" http://infix.se/2007/02/06/gentlemen-indent-your-xml
"""
i = "\n" + level*" "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
for e in elem:
indent(e, level+1)
if not e.tail or not e.tail.strip():
e.tail = i + " "
if not e.tail or not e.tail.strip():
e.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
def name2expr(sym):
name = sym.attrib['name']
if re.match('^\[.*\]',name) is None \
and '[' not in name \
and ']' not in name \
and not name.startswith("'") \
and not name.endswith("'") \
and re.match("^\'.*\'",name) is None:
print>>sys.stderr,"Fixing %s" % name
expression = '[%s]' % name
sym.attrib['name'] = expression
def handle_attr_changes(sym):
# http://www.w3schools.com/css/pr_text_text-transform.asp
# http://code.google.com/p/mapnik-utils/issues/detail?id=32&colspec=ID%20Type%20Status%20Priority%20Component%20Owner%20Summary
text_transform = sym.attrib.get('text_convert')
if text_transform:
# note: css supports text-transform:capitalize but Mapnik does not (yet)
t_ = {'tolower':'lowercase','toupper':'uppercase','none':'none'}
new = t_.get(text_transform)
if new:
sym.attrib['text_transform'] = new
else:
sym.attrib['text_transform'] = text_transform
sym.attrib.pop('text_convert')
minimum_distance = sym.attrib.get('min_distance')
if minimum_distance:
sym.attrib['minimum_distance'] = minimum_distance
sym.attrib.pop('min_distance')
minimum_padding = sym.attrib.get('min_padding')
if minimum_padding:
sym.attrib['minimum_padding'] = minimum_padding
sym.attrib.pop('min_padding')
def fixup_sym_with_image(sym):
if sym.attrib.get('width'):
sym.attrib.pop('width')
if sym.attrib.get('height'):
sym.attrib.pop('height')
if sym.attrib.get('type'):
sym.attrib.pop('type')
def fixup_sym_attributes(sym):
if len(sym.findall('CssParameter')):
# copy, so we don't loose after clear()
attrib = dict(sym.attrib)
for css in sym.findall('CssParameter'):
key = css.attrib.get('name')
value = css.text
attrib[key] = value
sym.clear() # remove CssParameter elements
for k,v in attrib.items(): # insert attributes instead
sym.attrib[k] = v
def underscore2dash(elem):
for i in elem.attrib.items():
if '_' in i[0]:
new = i[0].replace('_','-')
old = i[0]
elem.attrib[new] = i[1]
elem.attrib.pop(old)
print>>sys.stderr,"Changing %s to %s" % (old,new)
def upgrade(input_xml,output_xml=None,indent_xml=True):
if not os.path.exists(input_xml):
sys.stderr.write('input xml "%s" does not exist' % input_xml)
sys.exit(1)
pre_read = open(input_xml,'r')
if '!ENTITY' in pre_read.read() and not HAS_LXML:
sys.stderr.write('\nSorry, it appears the xml you are trying to upgrade has entities, which requires lxml (python bindings to libxml2)\n')
sys.stderr.write('Install lxml with: "easy_install lxml" or download from http://codespeak.net/lxml/\n')
sys.exit(1)
try:
tree = etree.parse(input_xml)
except:
print 'Could not parse "%s" invalid XML' % input_xml
return
if hasattr(tree,'xinclude'):
tree.xinclude()
root = tree.getroot()
# rename 'bgcolor' to 'background-color'
if root.attrib.get('bgcolor'):
root.attrib['background-color'] = root.attrib.get('bgcolor')
root.attrib.pop('bgcolor')
# underscores to spaces for <Map ..>
underscore2dash(root)
root.set('minimum-version', '0.7.2')
# underscores to spaces for <FontSet ..>
fontset = root.findall('FontSet') or root.findall('*/FontSet')
for f in fontset:
font = f.findall('Font')
for f_ in font:
underscore2dash(f_)
# underscores to spaces for <Layer ..>
layers = root.findall('Layer') or root.findall('*/Layer')
for l in layers:
underscore2dash(l)
styles = root.findall('Style') or root.findall('*/Style')
if not len(styles):
sys.stderr.write('### Warning, no styles encountered and nothing able to be upgraded!\n')
else:
for style in styles:
for rule in style.findall('Rule'):
for sym in rule.findall('TextSymbolizer') or []:
name2expr(sym)
handle_attr_changes(sym)
fixup_sym_attributes(sym)
underscore2dash(sym)
for sym in rule.findall('ShieldSymbolizer') or []:
name2expr(sym)
handle_attr_changes(sym)
fixup_sym_attributes(sym)
underscore2dash(sym)
fixup_sym_with_image(sym)
for sym in rule.findall('PointSymbolizer') or []:
fixup_sym_with_image(sym)
fixup_sym_attributes(sym)
underscore2dash(sym)
for sym in rule.findall('PolygonPatternSymbolizer') or []:
fixup_sym_with_image(sym)
fixup_sym_attributes(sym)
underscore2dash(sym)
for sym in rule.findall('LinePatternSymbolizer') or []:
fixup_sym_with_image(sym)
fixup_sym_attributes(sym)
underscore2dash(sym)
for sym in rule.findall('LineSymbolizer') or []:
fixup_sym_attributes(sym)
underscore2dash(sym)
for sym in rule.findall('PolygonSymbolizer') or []:
fixup_sym_attributes(sym)
underscore2dash(sym)
for sym in rule.findall('RasterSymbolizer') or []:
fixup_sym_attributes(sym)
underscore2dash(sym)
for sym in rule.findall('BuildingSymbolizer') or []:
fixup_sym_attributes(sym)
underscore2dash(sym)
for sym in rule.findall('GlyphSymbolizer') or []:
fixup_sym_attributes(sym)
underscore2dash(sym)
for sym in rule.findall('MarkersSymbolizer') or []:
fixup_sym_attributes(sym)
underscore2dash(sym)
if indent_xml:
indent(root)
if output_xml:
tree.write(output_xml)
else:
tree.write(input_xml)
parser = optparse.OptionParser(usage="""%prog <input xml> [options]
Upgrade a Mapnik XML stylesheet to Mapnik 2.0
Full help:
$ %prog -h (or --help for possible options)
Read 'map.xml' and write new 'map2.xml'
$ %prog map.xml map2.xml
Update 'map.xml' in place (*Careful*)
$ %prog map.xml --in-place
""", version='%prog ' + __version__)
parser.add_option('--indent',
dest='indent_xml',
action='store_true',
default=False,
help='Indent the resulting XML')
parser.add_option('--in-place',
dest='update_in_place',
action='store_true',
default=False,
help='Update and overwrite the map in place')
if __name__ == "__main__":
(options, args) = parser.parse_args()
if not len(args) > 0:
parser.error("Please provide the path to a map.xml and a new xml to write")
input_xml = args[0]
output_xml = None
if len(args) < 3 and not options.update_in_place:
if len(args) == 2:
output_xml = args[1]
if (len(args) == 1) or (input_xml == output_xml):
parser.error(color_text(1,'\n\nAre you sure you want to overwrite "%s"?\nIf so, then pass --in-place to confirm.\nOtherwise pass a different filename to write an upgraded copy to.\n' % input_xml))
print 'Upgrading "%s" to "%s"...' % (input_xml,output_xml)
upgrade(input_xml,output_xml=output_xml,indent_xml=options.indent_xml)
elif len(args) == 1:
print 'Upgrading "%s" to "%s"...' % (input_xml,output_xml)
upgrade(input_xml,output_xml=output_xml,indent_xml=options.indent_xml)
elif len(args) > 1: # assume a list of inputs
found = []
for input_xml in args:
if os.path.exists(input_xml) and input_xml not in found:
print 'Upgrading "%s" in place...' % input_xml
found.append(input_xml)
upgrade(input_xml,output_xml=None,indent_xml=options.indent_xml)