This repository has been archived on 2023-03-25. You can view files and clone it, but cannot push or open issues or pull requests.
mightyscape-1.1-deprecated/extensions/fablabchemnitz/svgwrite/data/typechecker.py
2020-08-30 12:36:33 +02:00

343 lines
11 KiB
Python

#!/usr/bin/env python
#coding:utf-8
# Author: mozman --<mozman@gmx.at>
# Purpose: typechecker
# Created: 15.10.2010
# Copyright (C) 2010, Manfred Moitzi
# License: MIT License
import re
from svgwrite.data import pattern
from svgwrite.data.colors import colornames
from svgwrite.data.svgparser import is_valid_transferlist, is_valid_pathdata, is_valid_animation_timing
from svgwrite.utils import is_string
def iterflatlist(values):
""" Flatten nested *values*, returns an *iterator*. """
for element in values:
if hasattr(element, "__iter__") and not is_string(element):
for item in iterflatlist(element):
yield item
else:
yield element
INVALID_NAME_CHARS = frozenset([' ', '\t', '\r', '\n', ',', '(', ')'])
WHITESPACE = frozenset([' ', '\t', '\r', '\n'])
SHAPE_PATTERN = re.compile(r"^rect\((.*),(.*),(.*),(.*)\)$")
FUNCIRI_PATTERN = re.compile(r"^url\((.*)\)$")
ICCCOLOR_PATTERN = re.compile(r"^icc-color\((.*)\)$")
COLOR_HEXDIGIT_PATTERN = re.compile(r"^#[a-fA-F0-9]{3}([a-fA-F0-9]{3})?$")
COLOR_RGB_INTEGER_PATTERN = re.compile(r"^rgb\( *\d+ *, *\d+ *, *\d+ *\)$")
COLOR_RGB_PERCENTAGE_PATTERN = re.compile(r"^rgb\( *\d+(\.\d*)?% *, *\d+(\.\d*)?% *, *\d+(\.\d*)?% *\)$")
NMTOKEN_PATTERN = re.compile(r"^[a-zA-Z_:][\w\-\.:]*$")
class Full11TypeChecker(object):
def get_version(self):
return '1.1', 'full'
def is_angle(self, value):
#angle ::= number (~"deg" | ~"grad" | ~"rad")?
if self.is_number(value):
return True
elif is_string(value):
return pattern.angle.match(value.strip()) is not None
return False
def is_anything(self, value):
#anything ::= Char*
return bool(str(value).strip())
is_string = is_anything
is_content_type = is_anything
def is_color(self, value):
#color ::= "#" hexdigit hexdigit hexdigit (hexdigit hexdigit hexdigit)?
# | "rgb(" wsp* integer comma integer comma integer wsp* ")"
# | "rgb(" wsp* number "%" comma number "%" comma number "%" wsp* ")"
# | color-keyword
#hexdigit ::= [0-9A-Fa-f]
#comma ::= wsp* "," wsp*
value = str(value).strip()
if value.startswith('#'):
if COLOR_HEXDIGIT_PATTERN.match(value):
return True
else:
return False
elif value.startswith('rgb('):
if COLOR_RGB_INTEGER_PATTERN.match(value):
return True
elif COLOR_RGB_PERCENTAGE_PATTERN.match(value):
return True
return False
return self.is_color_keyword(value)
def is_color_keyword(self, value):
return value.strip() in colornames
def is_frequency(self, value):
# frequency ::= number (~"Hz" | ~"kHz")
if self.is_number(value):
return True
elif is_string(value):
return pattern.frequency.match(value.strip()) is not None
return False
def is_FuncIRI(self, value):
# FuncIRI ::= "url(" <IRI> ")"
res = FUNCIRI_PATTERN.match(str(value).strip())
if res:
return self.is_IRI(res.group(1))
return False
def is_icccolor(self, value):
# icccolor ::= "icc-color(" name (comma-wsp number)+ ")"
res = ICCCOLOR_PATTERN.match(str(value).strip())
if res:
return self.is_list_of_T(res.group(1), 'name')
return False
def is_integer(self, value):
if isinstance(value, float):
return False
try:
number = int(value)
return True
except:
return False
def is_IRI(self, value):
# Internationalized Resource Identifiers
# a more generalized complement to Uniform Resource Identifiers (URIs)
# nearly everything can be a valid <IRI>
# only a none-empty string ist a valid input
if is_string(value):
return bool(value.strip())
else:
return False
def is_length(self, value):
# length ::= number ("em" | "ex" | "px" | "in" | "cm" | "mm" | "pt" | "pc" | "%")?
if value is None:
return False
if isinstance(value, (int, float)):
return self.is_number(value)
elif is_string(value):
result = pattern.length.match(value.strip())
if result:
number, tmp, unit = result.groups()
return self.is_number(number) # for tiny check!
return False
is_coordinate = is_length
def is_list_of_T(self, value, t='string'):
def split(value):
#TODO: improve split function!!!!
if isinstance(value, (int, float)):
return (value, )
if is_string(value):
return iterflatlist(v.split(',') for v in value.split(' '))
return value
#list-of-Ts ::= T
# | T comma-wsp list-of-Ts
#comma-wsp ::= (wsp+ ","? wsp*) | ("," wsp*)
#wsp ::= (#x20 | #x9 | #xD | #xA)
checker = self.get_func_by_name(t)
for v in split(value):
if not checker(v):
return False
return True
def is_four_numbers(self, value):
def split(value):
if is_string(value):
values = iterflatlist( (v.strip().split(' ') for v in value.split(',')) )
return (v for v in values if v)
else:
return iterflatlist(value)
values = list(split(value))
if len(values) != 4:
return False
checker = self.get_func_by_name('number')
for v in values:
if not checker(v):
return False
return True
def is_semicolon_list(self, value):
#a semicolon-separated list of values
# | value comma-wsp list-of-values
#comma-wsp ::= (wsp+ ";" wsp*) | ("," wsp*)
#wsp ::= (#x20 | #x9 | #xD | #xA)
return self.is_list_of_T(value.replace(';', ' '), 'string')
def is_name(self, value):
# name ::= [^,()#x20#x9#xD#xA] /* any char except ",", "(", ")" or wsp */
chars = frozenset(str(value).strip())
if not chars or INVALID_NAME_CHARS.intersection(chars):
return False
else:
return True
def is_number(self, value):
try:
number = float(value)
return True
except:
return False
def is_number_optional_number(self, value):
#number-optional-number ::= number
# | number comma-wsp number
if is_string(value):
values = re.split('[ ,]+', value.strip())
if 0 < len(values) < 3: # 1 or 2 numbers
for v in values:
if not self.is_number(v):
return False
return True
else:
try: # is it a 2-tuple
n1, n2 = value
if self.is_number(n1) and \
self.is_number(n2):
return True
except TypeError: # just one value
return self.is_number(value)
except ValueError: # more than 2 values
pass
return False
def is_paint(self, value):
#paint ::= "none" |
# "currentColor" |
# <color> [<icccolor>] |
# <funciri> [ "none" | "currentColor" | <color> [<icccolor>] |
# "inherit"
def split_values(value):
try:
funcIRI, value = value.split(")", 1)
values = [funcIRI+")"]
values.extend(split_values(value))
return values
except ValueError:
return value.split()
values = split_values(str(value).strip())
for value in [v.strip() for v in values]:
if value in ('none', 'currentColor', 'inherit'):
continue
elif self.is_color(value):
continue
elif self.is_icccolor(value):
continue
elif self.is_FuncIRI(value):
continue
return False
return True
def is_percentage(self, value):
#percentage ::= number "%"
if self.is_number(value):
return True
elif is_string(value):
return pattern.percentage.match(value.strip()) is not None
return False
def is_time(self, value):
#time ::= <number> (~"ms" | ~"s")?
if self.is_number(value):
return True
elif is_string(value):
return pattern.time.match(value.strip()) is not None
return False
def is_transform_list(self, value):
if is_string(value):
return is_valid_transferlist(value)
else:
return False
def is_path_data(self, value):
if is_string(value):
return is_valid_pathdata(value)
else:
return False
def is_XML_Name(self, value):
# http://www.w3.org/TR/2006/REC-xml-20060816/#NT-Name
# Nmtoken
return bool(NMTOKEN_PATTERN.match(str(value).strip()))
def is_shape(self, value):
# shape ::= (<top> <right> <bottom> <left>)
# where <top>, <bottom> <right>, and <left> specify offsets from the
# respective sides of the box.
# <top>, <right>, <bottom>, and <left> are <length> values
# i.e. 'rect(5px, 10px, 10px, 5px)'
res = SHAPE_PATTERN.match(value.strip())
if res:
for arg in res.groups():
if arg.strip() == 'auto':
continue
if not self.is_length(arg):
return False
else:
return False
return True
def is_timing_value_list(self, value):
if is_string(value):
return is_valid_animation_timing(value)
else:
return False
def is_list_of_text_decoration_style(self, value):
return self.is_list_of_T(value, t='text_decoration_style')
def is_text_decoration_style(self, value):
return value in ('overline', 'underline', 'line-through', 'blink')
def get_func_by_name(self, funcname):
return getattr(self,
'is_'+funcname.replace('-', '_'),
self.is_anything)
def check(self, typename, value):
if typename.startswith('list-of-'):
t = typename[8:]
return self.is_list_of_T(value, t)
return self.get_func_by_name(typename)(value)
FOCUS_CONST = frozenset(['nav-next', 'nav-prev', 'nav-up', 'nav-down', 'nav-left',
'nav-right', 'nav-up-left', 'nav-up-right', 'nav-down-left',
'nav-down-right'])
class Tiny12TypeChecker(Full11TypeChecker):
def get_version(self):
return '1.2', 'tiny'
def is_boolean(self, value):
if isinstance(value, bool):
return True
if is_string(value):
return value.strip().lower() in ('true', 'false')
return False
def is_number(self, value):
try:
number = float(value)
if -32767.9999 <= number <= 32767.9999:
return True
else:
return False
except:
return False
def is_focus(self, value):
return str(value).strip() in FOCUS_CONST