283 lines
8.0 KiB
Python
283 lines
8.0 KiB
Python
|
#!/usr/bin/env python
|
|||
|
#coding:utf-8
|
|||
|
# Author: mozman
|
|||
|
# Purpose: svg base element
|
|||
|
# Created: 08.09.2010
|
|||
|
# Copyright (c) 2010-2020, Manfred Moitzi
|
|||
|
# License: MIT License
|
|||
|
"""
|
|||
|
The **BaseElement** is the root for all SVG elements.
|
|||
|
"""
|
|||
|
|
|||
|
from svgwrite.etree import etree
|
|||
|
|
|||
|
import copy
|
|||
|
|
|||
|
from svgwrite.params import Parameter
|
|||
|
from svgwrite.utils import AutoID
|
|||
|
|
|||
|
|
|||
|
class BaseElement(object):
|
|||
|
"""
|
|||
|
The **BaseElement** is the root for all SVG elements. The SVG attributes
|
|||
|
are stored in **attribs**, and the SVG subelements are stored in
|
|||
|
**elements**.
|
|||
|
|
|||
|
"""
|
|||
|
elementname = 'baseElement'
|
|||
|
|
|||
|
def __init__(self, **extra):
|
|||
|
"""
|
|||
|
:param extra: extra SVG attributes (keyword arguments)
|
|||
|
|
|||
|
* add trailing '_' to reserved keywords: ``'class_'``, ``'from_'``
|
|||
|
* replace inner '-' by '_': ``'stroke_width'``
|
|||
|
|
|||
|
|
|||
|
SVG attribute names will be checked, if **debug** is `True`.
|
|||
|
|
|||
|
workaround for removed **attribs** parameter in Version 0.2.2::
|
|||
|
|
|||
|
# replace
|
|||
|
element = BaseElement(attribs=adict)
|
|||
|
|
|||
|
#by
|
|||
|
element = BaseElement()
|
|||
|
element.update(adict)
|
|||
|
|
|||
|
"""
|
|||
|
# the keyword 'factory' specifies the object creator
|
|||
|
factory = extra.pop('factory', None)
|
|||
|
if factory is not None:
|
|||
|
# take parameter from 'factory'
|
|||
|
self._parameter = factory._parameter
|
|||
|
else:
|
|||
|
# default parameter debug=True profile='full'
|
|||
|
self._parameter = Parameter()
|
|||
|
|
|||
|
# override debug setting
|
|||
|
debug = extra.pop('debug', None)
|
|||
|
if debug is not None:
|
|||
|
self._parameter.debug = debug
|
|||
|
|
|||
|
# override profile setting
|
|||
|
profile = extra.pop('profile', None)
|
|||
|
if profile is not None:
|
|||
|
self._parameter.profile = profile
|
|||
|
|
|||
|
self.attribs = dict()
|
|||
|
self.update(extra)
|
|||
|
self.elements = list()
|
|||
|
|
|||
|
def update(self, attribs):
|
|||
|
""" Update SVG Attributes from `dict` attribs.
|
|||
|
|
|||
|
Rules for keys:
|
|||
|
|
|||
|
1. trailing '_' will be removed (``'class_'`` -> ``'class'``)
|
|||
|
2. inner '_' will be replaced by '-' (``'stroke_width'`` -> ``'stroke-width'``)
|
|||
|
|
|||
|
"""
|
|||
|
for key, value in attribs.items():
|
|||
|
# remove trailing underscores
|
|||
|
# and replace inner underscores
|
|||
|
key = key.rstrip('_').replace('_', '-')
|
|||
|
self.__setitem__(key, value)
|
|||
|
|
|||
|
def copy(self):
|
|||
|
newobj = copy.copy(self) # shallow copy of object
|
|||
|
newobj.attribs = copy.copy(self.attribs) # shallow copy of attributes
|
|||
|
newobj.elements = copy.copy(self.elements) # shallow copy of subelements
|
|||
|
if 'id' in newobj.attribs: # create a new 'id'
|
|||
|
newobj['id'] = newobj.next_id()
|
|||
|
return newobj
|
|||
|
|
|||
|
@property
|
|||
|
def debug(self):
|
|||
|
return self._parameter.debug
|
|||
|
|
|||
|
@property
|
|||
|
def profile(self):
|
|||
|
return self._parameter.profile
|
|||
|
|
|||
|
@property
|
|||
|
def validator(self):
|
|||
|
return self._parameter.validator
|
|||
|
|
|||
|
@validator.setter
|
|||
|
def validator(self, value):
|
|||
|
self._parameter.validator = value
|
|||
|
|
|||
|
@property
|
|||
|
def version(self):
|
|||
|
return self._parameter.get_version()
|
|||
|
|
|||
|
def set_parameter(self, parameter):
|
|||
|
self._parameter = parameter
|
|||
|
|
|||
|
def next_id(self, value=None):
|
|||
|
return AutoID.next_id(value)
|
|||
|
|
|||
|
def get_id(self):
|
|||
|
""" Get the object `id` string, if the object does not have an `id`,
|
|||
|
a new `id` will be created.
|
|||
|
|
|||
|
:returns: `string`
|
|||
|
"""
|
|||
|
if 'id' not in self.attribs:
|
|||
|
self.attribs['id'] = self.next_id()
|
|||
|
return self.attribs['id']
|
|||
|
|
|||
|
def get_iri(self):
|
|||
|
"""
|
|||
|
Get the `IRI` reference string of the object. (i.e., ``'#id'``).
|
|||
|
|
|||
|
:returns: `string`
|
|||
|
"""
|
|||
|
return "#%s" % self.get_id()
|
|||
|
|
|||
|
def get_funciri(self):
|
|||
|
"""
|
|||
|
Get the `FuncIRI` reference string of the object. (i.e. ``'url(#id)'``).
|
|||
|
|
|||
|
:returns: `string`
|
|||
|
"""
|
|||
|
return "url(%s)" % self.get_iri()
|
|||
|
|
|||
|
def __getitem__(self, key):
|
|||
|
""" Get SVG attribute by `key`.
|
|||
|
|
|||
|
:param string key: SVG attribute name
|
|||
|
:return: SVG attribute value
|
|||
|
|
|||
|
"""
|
|||
|
return self.attribs[key]
|
|||
|
|
|||
|
def __setitem__(self, key, value):
|
|||
|
""" Set SVG attribute by `key` to `value`.
|
|||
|
|
|||
|
:param string key: SVG attribute name
|
|||
|
:param object value: SVG attribute value
|
|||
|
|
|||
|
"""
|
|||
|
# Attribute checking is only done by using the __setitem__() method or
|
|||
|
# by self['attribute'] = value
|
|||
|
if self.debug:
|
|||
|
self.validator.check_svg_attribute_value(self.elementname, key, value)
|
|||
|
self.attribs[key] = value
|
|||
|
|
|||
|
def add(self, element):
|
|||
|
""" Add an SVG element as subelement.
|
|||
|
|
|||
|
:param element: append this SVG element
|
|||
|
:returns: the added element
|
|||
|
|
|||
|
"""
|
|||
|
if self.debug:
|
|||
|
self.validator.check_valid_children(self.elementname, element.elementname)
|
|||
|
self.elements.append(element)
|
|||
|
return element
|
|||
|
|
|||
|
def tostring(self):
|
|||
|
""" Get the XML representation as unicode `string`.
|
|||
|
|
|||
|
:return: unicode XML string of this object and all its subelements
|
|||
|
|
|||
|
"""
|
|||
|
xml = self.get_xml()
|
|||
|
# required for Python 2 support
|
|||
|
xml_utf8_str = etree.tostring(xml, encoding='utf-8')
|
|||
|
return xml_utf8_str.decode('utf-8')
|
|||
|
# just Python 3: return etree.tostring(xml, encoding='unicode')
|
|||
|
|
|||
|
def _repr_svg_(self):
|
|||
|
""" Show SVG in IPython, Jupyter Notebook, and Jupyter Lab
|
|||
|
|
|||
|
:return: unicode XML string of this object and all its subelements
|
|||
|
|
|||
|
"""
|
|||
|
return self.tostring()
|
|||
|
|
|||
|
def get_xml(self):
|
|||
|
""" Get the XML representation as `ElementTree` object.
|
|||
|
|
|||
|
:return: XML `ElementTree` of this object and all its subelements
|
|||
|
|
|||
|
"""
|
|||
|
xml = etree.Element(self.elementname)
|
|||
|
if self.debug:
|
|||
|
self.validator.check_all_svg_attribute_values(self.elementname, self.attribs)
|
|||
|
for attribute, value in sorted(self.attribs.items()):
|
|||
|
# filter 'None' values
|
|||
|
if value is not None:
|
|||
|
value = self.value_to_string(value)
|
|||
|
if value: # just add not empty attributes
|
|||
|
xml.set(attribute, value)
|
|||
|
|
|||
|
for element in self.elements:
|
|||
|
xml.append(element.get_xml())
|
|||
|
return xml
|
|||
|
|
|||
|
def value_to_string(self, value):
|
|||
|
"""
|
|||
|
Converts *value* into a <string> includes a value check, depending
|
|||
|
on :attr:`self.debug` and :attr:`self.profile`.
|
|||
|
|
|||
|
"""
|
|||
|
if isinstance(value, (int, float)):
|
|||
|
if self.debug:
|
|||
|
self.validator.check_svg_type(value, 'number')
|
|||
|
if isinstance(value, float) and self.profile == 'tiny':
|
|||
|
value = round(value, 4)
|
|||
|
return str(value)
|
|||
|
|
|||
|
def set_desc(self, title=None, desc=None):
|
|||
|
""" Insert a **title** and/or a **desc** element as first subelement.
|
|||
|
"""
|
|||
|
if desc is not None:
|
|||
|
self.elements.insert(0, Desc(desc))
|
|||
|
if title is not None:
|
|||
|
self.elements.insert(0, Title(title))
|
|||
|
|
|||
|
def set_metadata(self, xmldata):
|
|||
|
"""
|
|||
|
:param xmldata: an xml.etree.ElementTree - Element() object.
|
|||
|
"""
|
|||
|
metadata = Metadata(xmldata)
|
|||
|
if len(self.elements) == 0:
|
|||
|
self.elements.append(metadata)
|
|||
|
else:
|
|||
|
pos = 0
|
|||
|
while self.elements[pos].elementname in ('title', 'desc'):
|
|||
|
pos += 1
|
|||
|
if pos == len(self.elements):
|
|||
|
self.elements.append(metadata)
|
|||
|
return
|
|||
|
self.elements.insert(pos, metadata)
|
|||
|
|
|||
|
|
|||
|
class Title(object):
|
|||
|
elementname = 'title'
|
|||
|
|
|||
|
def __init__(self, text):
|
|||
|
self.xml = etree.Element(self.elementname)
|
|||
|
self.xml.text = str(text)
|
|||
|
|
|||
|
def get_xml(self):
|
|||
|
return self.xml
|
|||
|
|
|||
|
|
|||
|
class Desc(Title):
|
|||
|
elementname = 'desc'
|
|||
|
|
|||
|
|
|||
|
class Metadata(Title):
|
|||
|
elementname = 'metadata'
|
|||
|
|
|||
|
def __init__(self, xmldata):
|
|||
|
"""
|
|||
|
:param xmldata: an xml.etree.ElementTree - Element() object.
|
|||
|
"""
|
|||
|
self.xml = etree.Element('metadata')
|
|||
|
self.xml.append(xmldata)
|