Skip to content

Commit d3038bf

Browse files
authored
Merge pull request #23206 from oscargus/svgtests
Change exception type for incorrect SVG date metadata
2 parents a68f21f + b312ba2 commit d3038bf

File tree

3 files changed

+79
-19
lines changed

3 files changed

+79
-19
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Changed exception type for incorrect SVG date metadata
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
Providing date metadata with incorrect type to the SVG backend earlier
5+
resulted in a ``ValueError``. Now, a ``TypeError`` is raised instead.

lib/matplotlib/backends/backend_svg.py

+40-17
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,23 @@ def generate_css(attrib=None):
298298
_capstyle_d = {'projecting': 'square', 'butt': 'butt', 'round': 'round'}
299299

300300

301+
def _check_is_str(info, key):
302+
if not isinstance(info, str):
303+
raise TypeError(f'Invalid type for {key} metadata. Expected str, not '
304+
f'{type(info)}.')
305+
306+
307+
def _check_is_iterable_of_str(infos, key):
308+
if np.iterable(infos):
309+
for info in infos:
310+
if not isinstance(info, str):
311+
raise TypeError(f'Invalid type for {key} metadata. Expected '
312+
f'iterable of str, not {type(info)}.')
313+
else:
314+
raise TypeError(f'Invalid type for {key} metadata. Expected str or '
315+
f'iterable of str, not {type(infos)}.')
316+
317+
301318
class RendererSVG(RendererBase):
302319
def __init__(self, width, height, svgwriter, basename=None, image_dpi=72,
303320
*, metadata=None):
@@ -359,7 +376,9 @@ def _write_metadata(self, metadata):
359376
writer = self.writer
360377

361378
if 'Title' in metadata:
362-
writer.element('title', text=metadata['Title'])
379+
title = metadata['Title']
380+
_check_is_str(title, 'Title')
381+
writer.element('title', text=title)
363382

364383
# Special handling.
365384
date = metadata.get('Date', None)
@@ -376,14 +395,14 @@ def _write_metadata(self, metadata):
376395
elif isinstance(d, (datetime.datetime, datetime.date)):
377396
dates.append(d.isoformat())
378397
else:
379-
raise ValueError(
380-
'Invalid type for Date metadata. '
381-
'Expected iterable of str, date, or datetime, '
382-
'not {!r}.'.format(type(d)))
398+
raise TypeError(
399+
f'Invalid type for Date metadata. '
400+
f'Expected iterable of str, date, or datetime, '
401+
f'not {type(d)}.')
383402
else:
384-
raise ValueError('Invalid type for Date metadata. '
385-
'Expected str, date, datetime, or iterable '
386-
'of the same, not {!r}.'.format(type(date)))
403+
raise TypeError(f'Invalid type for Date metadata. '
404+
f'Expected str, date, datetime, or iterable '
405+
f'of the same, not {type(date)}.')
387406
metadata['Date'] = '/'.join(dates)
388407
elif 'Date' not in metadata:
389408
# Do not add `Date` if the user explicitly set `Date` to `None`
@@ -415,36 +434,40 @@ def ensure_metadata(mid):
415434
writer.element('dc:type', attrib={'rdf:resource': uri})
416435

417436
# Single value only.
418-
for key in ['title', 'coverage', 'date', 'description', 'format',
419-
'identifier', 'language', 'relation', 'source']:
420-
info = metadata.pop(key.title(), None)
437+
for key in ['Title', 'Coverage', 'Date', 'Description', 'Format',
438+
'Identifier', 'Language', 'Relation', 'Source']:
439+
info = metadata.pop(key, None)
421440
if info is not None:
422441
mid = ensure_metadata(mid)
423-
writer.element(f'dc:{key}', text=info)
442+
_check_is_str(info, key)
443+
writer.element(f'dc:{key.lower()}', text=info)
424444

425445
# Multiple Agent values.
426-
for key in ['creator', 'contributor', 'publisher', 'rights']:
427-
agents = metadata.pop(key.title(), None)
446+
for key in ['Creator', 'Contributor', 'Publisher', 'Rights']:
447+
agents = metadata.pop(key, None)
428448
if agents is None:
429449
continue
430450

431451
if isinstance(agents, str):
432452
agents = [agents]
433453

454+
_check_is_iterable_of_str(agents, key)
455+
# Now we know that we have an iterable of str
434456
mid = ensure_metadata(mid)
435-
writer.start(f'dc:{key}')
457+
writer.start(f'dc:{key.lower()}')
436458
for agent in agents:
437459
writer.start('cc:Agent')
438460
writer.element('dc:title', text=agent)
439461
writer.end('cc:Agent')
440-
writer.end(f'dc:{key}')
462+
writer.end(f'dc:{key.lower()}')
441463

442464
# Multiple values.
443465
keywords = metadata.pop('Keywords', None)
444466
if keywords is not None:
445467
if isinstance(keywords, str):
446468
keywords = [keywords]
447-
469+
_check_is_iterable_of_str(keywords, 'Keywords')
470+
# Now we know that we have an iterable of str
448471
mid = ensure_metadata(mid)
449472
writer.start('dc:subject')
450473
writer.start('rdf:Bag')

lib/matplotlib/tests/test_backend_svg.py

+34-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import numpy as np
1010

11-
1211
import matplotlib as mpl
1312
from matplotlib.figure import Figure
1413
from matplotlib.text import Text
@@ -426,7 +425,7 @@ def test_svg_metadata():
426425
**{k: [f'{k} bar', f'{k} baz'] for k in multi_value},
427426
}
428427

429-
fig, ax = plt.subplots()
428+
fig = plt.figure()
430429
with BytesIO() as fd:
431430
fig.savefig(fd, format='svg', metadata=metadata)
432431
buf = fd.getvalue().decode()
@@ -495,3 +494,36 @@ def test_multi_font_type42():
495494

496495
plt.rc('font', family=['DejaVu Sans', 'WenQuanYi Zen Hei'], size=27)
497496
fig.text(0.15, 0.475, "There are 几个汉字 in between!")
497+
498+
499+
@pytest.mark.parametrize('metadata,error,message', [
500+
({'Date': 1}, TypeError, "Invalid type for Date metadata. Expected str"),
501+
({'Date': [1]}, TypeError,
502+
"Invalid type for Date metadata. Expected iterable"),
503+
({'Keywords': 1}, TypeError,
504+
"Invalid type for Keywords metadata. Expected str"),
505+
({'Keywords': [1]}, TypeError,
506+
"Invalid type for Keywords metadata. Expected iterable"),
507+
({'Creator': 1}, TypeError,
508+
"Invalid type for Creator metadata. Expected str"),
509+
({'Creator': [1]}, TypeError,
510+
"Invalid type for Creator metadata. Expected iterable"),
511+
({'Title': 1}, TypeError,
512+
"Invalid type for Title metadata. Expected str"),
513+
({'Format': 1}, TypeError,
514+
"Invalid type for Format metadata. Expected str"),
515+
({'Foo': 'Bar'}, ValueError, "Unknown metadata key"),
516+
])
517+
def test_svg_incorrect_metadata(metadata, error, message):
518+
with pytest.raises(error, match=message), BytesIO() as fd:
519+
fig = plt.figure()
520+
fig.savefig(fd, format='svg', metadata=metadata)
521+
522+
523+
def test_svg_escape():
524+
fig = plt.figure()
525+
fig.text(0.5, 0.5, "<\'\"&>", gid="<\'\"&>")
526+
with BytesIO() as fd:
527+
fig.savefig(fd, format='svg')
528+
buf = fd.getvalue().decode()
529+
assert '&lt;&apos;&quot;&amp;&gt;"' in buf

0 commit comments

Comments
 (0)