Skip to content

Commit bc96bc6

Browse files
committed
Better error messages in SVG backend for metadata
1 parent f8ab952 commit bc96bc6

File tree

2 files changed

+85
-23
lines changed

2 files changed

+85
-23
lines changed

lib/matplotlib/backends/backend_svg.py

+51-23
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,12 @@ def _write_metadata(self, metadata):
359359
writer = self.writer
360360

361361
if 'Title' in metadata:
362-
writer.element('title', text=metadata['Title'])
362+
title = metadata['Title']
363+
if isinstance(title, str):
364+
writer.element('title', text=metadata['Title'])
365+
else:
366+
raise TypeError(f'Invalid type for Title metadata. Expected'
367+
f' str, not {type(title)}.')
363368

364369
# Special handling.
365370
date = metadata.get('Date', None)
@@ -376,14 +381,14 @@ def _write_metadata(self, metadata):
376381
elif isinstance(d, (datetime.datetime, datetime.date)):
377382
dates.append(d.isoformat())
378383
else:
379-
raise ValueError(
384+
raise TypeError(
380385
'Invalid type for Date metadata. '
381386
'Expected iterable of str, date, or datetime, '
382-
'not {!r}.'.format(type(d)))
387+
'not {}.'.format(type(d)))
383388
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)))
389+
raise TypeError('Invalid type for Date metadata. '
390+
'Expected str, date, datetime, or iterable '
391+
'of the same, not {}.'.format(type(date)))
387392
metadata['Date'] = '/'.join(dates)
388393
elif 'Date' not in metadata:
389394
# Do not add `Date` if the user explicitly set `Date` to `None`
@@ -420,8 +425,12 @@ def ensure_metadata(mid):
420425
info = metadata.pop(key.title(), None)
421426
if info is not None:
422427
mid = ensure_metadata(mid)
423-
writer.element(f'dc:{key}', text=info)
424-
428+
if isinstance(info, str):
429+
writer.element(f'dc:{key}', text=info)
430+
else:
431+
raise TypeError(f'Invalid type for {key.title()} '
432+
f'metadata. Expected str'
433+
f', not {type(info)}.')
425434
# Multiple Agent values.
426435
for key in ['creator', 'contributor', 'publisher', 'rights']:
427436
agents = metadata.pop(key.title(), None)
@@ -431,27 +440,46 @@ def ensure_metadata(mid):
431440
if isinstance(agents, str):
432441
agents = [agents]
433442

434-
mid = ensure_metadata(mid)
435-
writer.start(f'dc:{key}')
436-
for agent in agents:
437-
writer.start('cc:Agent')
438-
writer.element('dc:title', text=agent)
439-
writer.end('cc:Agent')
440-
writer.end(f'dc:{key}')
443+
if np.iterable(agents):
444+
mid = ensure_metadata(mid)
445+
writer.start(f'dc:{key}')
446+
for agent in agents:
447+
writer.start('cc:Agent')
448+
if isinstance(agent, str):
449+
writer.element('dc:title', text=agent)
450+
else:
451+
raise TypeError(f'Invalid type for {key.title()} '
452+
f'metadata. Expected iterable of str'
453+
f', not {type(agent)}.')
454+
writer.end('cc:Agent')
455+
writer.end(f'dc:{key}')
456+
else:
457+
raise TypeError(f'Invalid type for {key.title()} metadata. '
458+
f'Expected str or iterable '
459+
f'of str, not {type(agents)}.')
441460

442461
# Multiple values.
443462
keywords = metadata.pop('Keywords', None)
444463
if keywords is not None:
445464
if isinstance(keywords, str):
446465
keywords = [keywords]
447-
448-
mid = ensure_metadata(mid)
449-
writer.start('dc:subject')
450-
writer.start('rdf:Bag')
451-
for keyword in keywords:
452-
writer.element('rdf:li', text=keyword)
453-
writer.end('rdf:Bag')
454-
writer.end('dc:subject')
466+
if np.iterable(keywords):
467+
mid = ensure_metadata(mid)
468+
writer.start('dc:subject')
469+
writer.start('rdf:Bag')
470+
for keyword in keywords:
471+
if isinstance(keyword, str):
472+
writer.element('rdf:li', text=keyword)
473+
else:
474+
raise TypeError('Invalid type for Keywords metadata. '
475+
'Expected iterable of str'
476+
', not {}.'.format(type(keyword)))
477+
writer.end('rdf:Bag')
478+
writer.end('dc:subject')
479+
else:
480+
raise TypeError('Invalid type for Keywords metadata. '
481+
'Expected str or iterable '
482+
'of str, not {}.'.format(type(keywords)))
455483

456484
if mid is not None:
457485
writer.close(mid)

lib/matplotlib/tests/test_backend_svg.py

+34
Original file line numberDiff line numberDiff line change
@@ -469,3 +469,37 @@ def test_svg_metadata():
469469
values = [node.text for node in
470470
rdf.findall(f'./{CCNS}Work/{DCNS}subject/{RDFNS}Bag/{RDFNS}li')]
471471
assert values == metadata['Keywords']
472+
473+
474+
@pytest.mark.parametrize('metadata,error,message', [
475+
({'Date': 1}, TypeError, "Invalid type for Date metadata. Expected str"),
476+
({'Date': [1]}, TypeError,
477+
"Invalid type for Date metadata. Expected iterable"),
478+
({'Keywords': 1}, TypeError,
479+
"Invalid type for Keywords metadata. Expected str"),
480+
({'Keywords': [1]}, TypeError,
481+
"Invalid type for Keywords metadata. Expected iterable"),
482+
({'Creator': 1}, TypeError,
483+
"Invalid type for Creator metadata. Expected str"),
484+
({'Creator': [1]}, TypeError,
485+
"Invalid type for Creator metadata. Expected iterable"),
486+
({'Title': 1}, TypeError,
487+
"Invalid type for Title metadata. Expected str"),
488+
({'Format': 1}, TypeError,
489+
"Invalid type for Format metadata. Expected str"),
490+
({'Foo': 'Bar'}, ValueError, "Unknown metadata key"),
491+
])
492+
def test_svg_incorrect_metadata(metadata, error, message):
493+
with pytest.raises(error, match=message), BytesIO() as fd:
494+
fig, ax = plt.subplots()
495+
fig.savefig(fd, format='svg', metadata=metadata)
496+
497+
498+
def test_svg_escape():
499+
fig, ax = plt.subplots()
500+
fig.text(0.5, 0.5, "<\'\"&>", gid="<\'\"&>")
501+
ax.set_axis_off()
502+
with BytesIO() as fd:
503+
fig.savefig(fd, format='svg')
504+
buf = fd.getvalue().decode()
505+
assert '&lt;&apos;&quot;&amp;&gt;"' in buf

0 commit comments

Comments
 (0)