Coverage for gws-app/gws/lib/style/icon.py: 26%
73 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
1from typing import Optional
3import base64
4import re
6import gws
7import gws.lib.net
8import gws.lib.svg
9import gws.lib.osx
10import gws.lib.xmlx as xmlx
13class Error(gws.Error):
14 pass
17class ParsedIcon(gws.Data):
18 """Svg data."""
19 svg: gws.XmlElement
20 """Structure and attributes of the svg."""
23def to_data_url(icon: ParsedIcon) -> str:
24 """Converts a svg to url.
26 Args:
27 icon: Svg object to convert.
29 Returns:
30 The url, or an empty string if ``icon`` is not a svg.
32 """
34 if icon.svg:
35 xml = icon.svg.to_string()
36 return 'data:image/svg+xml;base64,' + base64.standard_b64encode(xml.encode('utf8')).decode('utf8')
37 return ''
40def parse(val: str, opts) -> Optional[ParsedIcon]:
41 """Parses an url or directory path to a svg.
43 Args:
44 val: An url or directory path containing a svg.
45 opts: Url or directory path options.
47 Returns:
48 The svg, if the url is trusted, or if the path is in a trusted directory.
50 """
51 if not val:
52 return
54 val = str(val).strip()
55 m = re.match(r'^url\((.+?)\)$', val)
56 if m:
57 val = m.group(1)
59 val = val.strip('\'\"')
61 bs = _get_bytes(val, opts)
62 if not bs:
63 return
65 if bs.startswith(b'<'):
66 svg = _parse_svg(bs.decode('utf8'))
67 if svg:
68 return ParsedIcon(svg=svg)
70 # @TODO other icon formats?
73##
76def _get_bytes(val, opts) -> Optional[bytes]:
77 if val.startswith('data:'):
78 return _decode_data_url(val, opts)
80 # if not trusted, looks in provided public dirs
82 for img_dir in opts.get('imageDirs', []):
83 path = gws.lib.osx.abs_web_path(val, img_dir)
84 if path:
85 return gws.u.read_file_b(path)
87 # network and aribtrary files only in the trusted mode
89 if not opts.get('trusted'):
90 raise Error('untrusted value', val)
92 if re.match(r'^https?:', val):
93 try:
94 return gws.lib.net.http_request(val).content
95 except Exception as exc:
96 raise Error('network error', val) from exc
98 try:
99 return gws.u.read_file_b(val)
100 except Exception as exc:
101 raise Error('file error', val) from exc
104_PREFIXES = [
105 'data:image/svg+xml;base64,',
106 'data:image/svg+xml;utf8,',
107 'data:image/svg;base64,',
108 'data:image/svg;utf8,',
109]
112def _decode_data_url(val, trusted) -> Optional[bytes]:
113 for pfx in _PREFIXES:
114 if val.startswith(pfx):
115 s = val[len(pfx):]
116 try:
117 if 'base64' in pfx:
118 return base64.standard_b64decode(s)
119 else:
120 return s.encode('utf8')
121 except Exception as exc:
122 raise Error('decode error', val) from exc
125def _parse_svg(val):
126 try:
127 el = xmlx.from_string(val)
128 except Exception as exc:
129 raise Error('parse error', val) from exc
131 el_clean = gws.lib.svg.sanitize_element(el)
133 w = el_clean.get('width')
134 h = el_clean.get('height')
136 if not w or not h:
137 raise Error('missing width or height', val)
139 return el_clean