SF.net SVN: docutils:[9921] trunk/docutils
milde--- via Docutils-checkins <[email protected]>
| Newsgroups | gmane.text.docutils.cvs |
|---|---|
| Message-ID | <[email protected]> |
Revision: 9921
http://sourceforge.net/p/docutils/code/9921
Author: milde
Date: 2024-09-04 19:33:39 +0000 (Wed, 04 Sep 2024)
Log Message:
-----------
Revise `HTMLTranslator.prepare_svg()`.
Change oder of method arguments (sort by importance).
Use a dictionary to update the `<svg style="..."> declarations keeping
exisisting declarations intact.
Simplify conversion of "align" attribute.
Add test cases.
Modified Paths:
--------------
trunk/docutils/docutils/writers/_html_base.py
trunk/docutils/test/test_writers/test_html5_polyglot.py
trunk/docutils/test/test_writers/test_html5_polyglot_misc.py
Modified: trunk/docutils/docutils/writers/_html_base.py
===================================================================
--- trunk/docutils/docutils/writers/_html_base.py 2024-09-04 19:33:30 UTC (rev 9920)
+++ trunk/docutils/docutils/writers/_html_base.py 2024-09-04 19:33:39 UTC (rev 9921)
@@ -28,7 +28,7 @@
import urllib.parse
import urllib.request
import warnings
-import xml.etree.ElementTree as ET # TODO: lazy import in prepare_svg()?
+import xml.etree.ElementTree as ET
from pathlib import Path
from typing import TYPE_CHECKING
@@ -474,39 +474,36 @@
return None
return imgsize
- def prepare_svg(self, node, imagedata, size_declaration):
- # Edit `imagedata` for embedding as SVG image.
- # Use ElementTree to add node attributes.
- # ET also removes comments and preamble code.
+ def prepare_svg(self, code: str, node: nodes.Element, atts: dict) -> str:
+ # Parse SVG source `code` (ignoring comments and preamble code),
+ # add relevant attributes from `node` and `atts` to the root element.
#
- # Internal: interface and behaviour may change without notice.
-
- # SVG namespace
+ # Internal auxiliary method called from `self.visit_image()`.
svg_ns = {'': 'http://www.w3.org/2000/svg',
- 'xlink': 'http://www.w3.org/1999/xlink'}
+ 'xlink': 'http://www.w3.org/1999/xlink'} # SVG namespace
# don't add SVG namespace to all elements
- ET.register_namespace('', svg_ns[''])
- ET.register_namespace('xlink', svg_ns['xlink'])
+ for key, value in svg_ns.items():
+ ET.register_namespace(key, value)
+ # Parse:
try:
- svg = ET.fromstring(imagedata)
+ svg = ET.fromstring(code)
except ET.ParseError as err:
self.messages.append(self.document.reporter.error(
f'Cannot parse SVG image "{node["uri"]}":\n {err}',
base_node=node))
- return imagedata
- # apply image node attributes:
- if size_declaration: # append to style, replacing width & height
- declarations = [d.strip() for d in svg.get('style', '').split(';')]
- declarations = [d for d in declarations
- if d
- and not d.startswith('width')
- and not d.startswith('height')]
- svg.set('style', '; '.join(declarations+[size_declaration]))
- if node['classes'] or 'align' in node:
+ return code
+ # Apply image node attributes:
+ if 'style' in atts:
+ # update style declarations
+ style_att = f"{svg.get('style', '')}; {atts['style']}"
+ style_att = [d.partition(':') for d in style_att.split(';')
+ if d.strip()]
+ style_att = dict((k.strip(), v.strip()) for k, p, v in style_att)
+ style_att = ' '.join(f'{k}: {v};' for k, v in style_att.items())
+ svg.set('style', style_att)
+ if 'classes' in atts or node['classes']:
classes = svg.get('class', '').split()
- classes += node.get('classes', [])
- if 'align' in node:
- classes.append(f'align-{node["align"]}')
+ classes += node['classes'] + atts.get('classes', [])
svg.set('class', ' '.join(classes))
if 'alt' in node and svg.find('title', svg_ns) is None:
svg_title = ET.Element('title')
@@ -598,7 +595,7 @@
infix = ' /'
else:
infix = ''
- return ''.join(prefix) + '<%s%s>' % (' '.join(parts), infix) + suffix
+ return f"{''.join(prefix)}<{' '.join(parts)}{infix}>{suffix}"
def emptytag(self, node, tagname, suffix='\n', **attributes):
"""Construct and return an XML-compatible empty tag."""
@@ -1173,14 +1170,11 @@
mimetype = mimetypes.guess_type(uri)[0]
element = '' # the HTML element (including potential children)
atts = {} # attributes for the HTML tag
- # alignment is handled by CSS rules
- if 'align' in node:
- atts['class'] = 'align-%s' % node['align']
- # set size with "style" attribute (more universal, accepts dimensions)
size_declaration = self.image_size(node)
if size_declaration:
atts['style'] = size_declaration
-
+ if 'align' in node:
+ atts['classes'] = [f"align-{node['align']}"]
# ``:loading:`` option (embed, link, lazy), default from setting,
# exception: only embed videos if told via directive option
loading = 'link' if mimetype in self.videotypes else self.image_loading
@@ -1201,8 +1195,7 @@
else:
self.settings.record_dependencies.add(imagepath)
if mimetype == 'image/svg+xml':
- element = self.prepare_svg(node, imagedata,
- size_declaration)
+ element = self.prepare_svg(imagedata, node, atts)
else:
data64 = base64.b64encode(imagedata).decode()
uri = f'data:{mimetype};base64,{data64}'
Modified: trunk/docutils/test/test_writers/test_html5_polyglot.py
===================================================================
--- trunk/docutils/test/test_writers/test_html5_polyglot.py 2024-09-04 19:33:30 UTC (rev 9920)
+++ trunk/docutils/test/test_writers/test_html5_polyglot.py 2024-09-04 19:33:39 UTC (rev 9921)
@@ -2,9 +2,10 @@
# $Id$
# Author: reggie dugard <[email protected]>
+# Maintainer: [email protected]
# Copyright: This module has been placed in the public domain.
-"""Test HTML5 writer output ("fragment" part).
+"""Test HTML5 writer output ("fragment"/"body" part).
This is the document body (not HTML <body>).
"""
@@ -89,7 +90,7 @@
self.assertEqual(case_expected, parts['body'])
-totest = {}
+totest = {} # expected samples contain only the "body" part of the HMTL output
totest['standard'] = ({}, [
["""\
@@ -147,6 +148,18 @@
</div>
""",
],
+[f"""\
+.. image:: {DATA_ROOT.as_uri()}/circle.svg
+ :loading: embed
+ :width: 50%
+ :height: 30
+ :align: left
+""",
+"""\
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10" style="width: 50%; height: 30px;" class="align-left">
+ <circle cx="5" cy="5" r="4" fill="lightblue" />
+</svg>
+"""],
])
Modified: trunk/docutils/test/test_writers/test_html5_polyglot_misc.py
===================================================================
--- trunk/docutils/test/test_writers/test_html5_polyglot_misc.py 2024-09-04 19:33:30 UTC (rev 9920)
+++ trunk/docutils/test/test_writers/test_html5_polyglot_misc.py 2024-09-04 19:33:39 UTC (rev 9921)
@@ -247,6 +247,19 @@
settings = frontend.get_default_settings(_html_base.Writer)
document = utils.new_document('test data', settings)
translator = _html_base.HTMLTranslator(document)
+ svg_sample = """\
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+ style="background: blue; width: 5ex" viewBox="0 0 10 10">
+ <circle cx="5" cy="5" r="4" fill="lightblue" />
+ <!-- comments are ignored -->
+</svg>
+"""
+ expected = """\
+<svg xmlns="http://www.w3.org/2000/svg" style="background: blue; width: 4em; height: 32px;" viewBox="0 0 10 10" class="test me">
+ <title>blue circle</title><circle cx="5" cy="5" r="4" fill="lightblue" />
+ \n\
+</svg>"""
def test_image_size(self):
image = nodes.image(height='3', width='4em')
@@ -256,6 +269,27 @@
self.assertEqual(self.translator.image_size(image),
'width: 2em; height: 1.5px;')
+ def test_prepare_svg(self):
+ # Internal method: the test is no guaranty for stability,
+ # interface and behaviour may change without notice.
+ image = nodes.image(height='32', width='4em', alt='blue circle',
+ align='left', classes=['test', 'me'])
+ atts = {'style': self.translator.image_size(image)}
+ rv = self.translator.prepare_svg(self.svg_sample, image, atts)
+ self.assertEqual(rv, self.expected)
+ def test_prepare_svg_syntax_variants(self):
+ # parsing "style" declarations must be robust:
+ svg_sample = '<svg style="width:3em"></svg>'
+
+ atts = {'style': 'colon:in:value'}
+ rv = self.translator.prepare_svg(svg_sample, nodes.image(), atts)
+ self.assertEqual(rv, '<svg style="width: 3em; colon: in:value;" />')
+
+ atts = {'style': 'no-colon;'}
+ rv = self.translator.prepare_svg(svg_sample, nodes.image(), atts)
+ self.assertEqual(rv, '<svg style="width: 3em; no-colon: ;" />')
+
+
if __name__ == '__main__':
unittest.main()
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.