From: <mi...@us...> - 2024-09-04 19:33:42
|
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 <re...@us...> +# Maintainer: doc...@li... # 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. |