-
Notifications
You must be signed in to change notification settings - Fork 9
/
__init__.py
136 lines (112 loc) · 3.93 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import mimetypes
import re
import textwrap
try:
from base64 import decodebytes as decode64
from base64 import encodebytes as encode64
BYTES = True
except ImportError:
from base64 import decodestring as decode64
from base64 import encodestring as encode64
BYTES = False
try:
from urllib.parse import quote, unquote
except ImportError:
from urllib import quote, unquote
MIMETYPE_REGEX = r'[\w]+\/[\w\-\+\.]+'
_MIMETYPE_RE = re.compile('^{}$'.format(MIMETYPE_REGEX))
CHARSET_REGEX = r'[\w\-\+\.]+'
_CHARSET_RE = re.compile('^{}$'.format(CHARSET_REGEX))
DATA_URI_REGEX = (
r'data:' +
r'(?P<mimetype>{})?'.format(MIMETYPE_REGEX) +
r"(?:\;name\=(?P<name>[\w\.\-%!*'~\(\)]+))?" +
r'(?:\;charset\=(?P<charset>{}))?'.format(CHARSET_REGEX) +
r'(?P<base64>\;base64)?' +
r',(?P<data>.*)')
_DATA_URI_RE = re.compile(r'^{}$'.format(DATA_URI_REGEX), re.DOTALL)
class DataURI(str):
@classmethod
def make(cls, mimetype, charset, base64, data):
parts = ['data:']
if mimetype is not None:
if not _MIMETYPE_RE.match(mimetype):
raise ValueError("Invalid mimetype: %r" % mimetype)
parts.append(mimetype)
if charset is not None:
if not _CHARSET_RE.match(charset):
raise ValueError("Invalid charset: %r" % charset)
parts.extend([';charset=', charset])
if base64:
parts.append(';base64')
if BYTES:
_charset = charset or 'utf-8'
if isinstance(data, bytes):
_data = data
else:
_data = bytes(data, _charset)
encoded_data = encode64(_data).decode(_charset).strip()
else:
encoded_data = encode64(data).strip()
else:
encoded_data = quote(data)
parts.extend([',', encoded_data])
return cls(''.join(parts))
@classmethod
def from_file(cls, filename, charset=None, base64=True):
mimetype, _ = mimetypes.guess_type(filename, strict=False)
with open(filename) as fp:
data = fp.read()
return cls.make(mimetype, charset, base64, data)
def __new__(cls, *args, **kwargs):
uri = super(DataURI, cls).__new__(cls, *args, **kwargs)
uri._parse # Trigger any ValueErrors on instantiation.
return uri
def __repr__(self):
return 'DataURI(%s)' % (super(DataURI, self).__repr__(),)
def wrap(self, width=76):
return '\n'.join(textwrap.wrap(self, width))
@property
def mimetype(self):
return self._parse[0]
@property
def name(self):
name = self._parse[1]
if name is not None:
return unquote(name)
return name
@property
def charset(self):
return self._parse[2]
@property
def is_base64(self):
return self._parse[3]
@property
def data(self):
return self._parse[4]
@property
def text(self):
if self.charset is None:
raise ValueError("DataURI has no encoding set.")
return self.data.decode(self.charset)
@property
def _parse(self):
match = _DATA_URI_RE.match(self)
if not match:
raise ValueError("Not a valid data URI: %r" % self)
mimetype = match.group('mimetype') or None
name = match.group('name') or None
charset = match.group('charset') or None
if match.group('base64'):
if BYTES:
_charset = charset or 'utf-8'
if isinstance(match.group('data'), bytes):
_data = match.group('data')
else:
_data = bytes(match.group('data'), _charset)
data = decode64(_data)
else:
data = decode64(match.group('data'))
else:
data = unquote(match.group('data'))
return mimetype, name, charset, bool(match.group('base64')), data