Repository: LukeCarrier/mkdocs-drawio-exporter (language: python)

Issue:
Inline SVG embed format
Hi @LukeCarrier ,

I work at [TOTVS](https://www.totvs.com/), from Brazil, and we use the  `mkdocs-drawio-exporter` plugin in our `mkdocs` documentation (thanks a lot). We are putting this documentation to be displayed inside [backstage.io](https://backstage.io/) and we are faced with the challenge since, for security reasons, the [backstage.io techdocs](https://backstage.io/docs/features/techdocs/techdocs-overview) plugin removes the `<object/>`, `<embed/>`, `<iframe/>` and some other HTML tags by default (they are using [DOM Purify](https://github.com/cure53/DOMPurify)).

After studying alternatives to solve this problem, we decided to package our `mkdocs` documentation differently, embedding the SVG content inside the HTML body. So, I forked your project by enabling this new embedding format (which I called `html`).

Basically, you need to configure `mkdocs-drawio-exporter` as follows:

```yaml
plugins:
    - drawio-exporter:
        format: svg
        embed_format: html
```

To enable this functionality I had to change the order of some things, so the plugin starts generating the SVGs in the `on_post_page` event and before changing the HTML body. I had to do this to be able to [embed the SVG inside the HTML body](https://github.com/herberton/mkdocs-drawio-exporter/blob/master/mkdocsdrawioexporter/exporter.py#L318).

I also reorganized the source code in order to isolate the responsibilities between [exporter.py](https://github.com/herberton/mkdocs-drawio-exporter/blob/master/mkdocsdrawioexporter/exporter.py) and [plugin.py](https://github.com/herberton/mkdocs-drawio-exporter/blob/master/mkdocsdrawioexporter/plugin.py) in order to simplify usage, reducing parameter passing and promote better maintainability.

I hope to be able to activate these changes in the next version,

Herberton.
Inline SVG embed format
Hi @LukeCarrier ,

I work at [TOTVS](https://www.totvs.com/), from Brazil, and we use the  `mkdocs-drawio-exporter` plugin in our `mkdocs` documentation (thanks a lot). We are putting this documentation to be displayed inside [backstage.io](https://backstage.io/) and we are faced with the challenge since, for security reasons, the [backstage.io techdocs](https://backstage.io/docs/features/techdocs/techdocs-overview) plugin removes the `<object/>`, `<embed/>`, `<iframe/>` and some other HTML tags by default (they are using [DOM Purify](https://github.com/cure53/DOMPurify)).

After studying alternatives to solve this problem, we decided to package our `mkdocs` documentation differently, embedding the SVG content inside the HTML body. So, I forked your project by enabling this new embedding format (which I called `html`).

Basically, you need to configure `mkdocs-drawio-exporter` as follows:

```yaml
plugins:
    - drawio-exporter:
        format: svg
        embed_format: html
```

To enable this functionality I had to change the order of some things, so the plugin starts generating the SVGs in the `on_post_page` event and before changing the HTML body. I had to do this to be able to [embed the SVG inside the HTML body](https://github.com/herberton/mkdocs-drawio-exporter/blob/master/mkdocsdrawioexporter/exporter.py#L318).

I also reorganized the source code in order to isolate the responsibilities between [exporter.py](https://github.com/herberton/mkdocs-drawio-exporter/blob/master/mkdocsdrawioexporter/exporter.py) and [plugin.py](https://github.com/herberton/mkdocs-drawio-exporter/blob/master/mkdocsdrawioexporter/plugin.py) in order to simplify usage, reducing parameter passing and promote better maintainability.

I hope to be able to activate these changes in the next version,

Herberton.


Discussion / hints:
Hey @herberton,

Thanks for the work on this. Given the size of the diff I really need to review the change properly, but I am snowed under with work right now.

You’re welcome to fork and publish to PyPI yourself if you need this immediately.
Hi @LukeCarrier , I found a little bug but I already fix it!!!
Fixes #44 
Hey @herberton,

Thanks for the work on this. Given the size of the diff I really need to review the change properly, but I am snowed under with work right now.

You’re welcome to fork and publish to PyPI yourself if you need this immediately.
Hi @LukeCarrier , I found a little bug but I already fix it!!!
Fixes #44 

Fix patch:
diff --git a/README.md b/README.md
index 7dbf525..9b8da63 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,9 @@ plugins:
         #     value of the src attribute.
         #   * Consider <object type="image/svg+xml" data="{img_src}"></object>
         #     to enable interactive elements (like hyperlinks) in SVGs.
+        #   * Consider {content} to inline SVGs into documents directly, useful
+        #     for styling with CSS, preserving interactivity, and improving
+        #     search by indexing diagram text.
         embed_format: '{img_open}{img_src}{img_close}'
         # Glob pattern for matching source files
         sources: '*.drawio'
diff --git a/mkdocs_drawio_exporter/exporter.py b/mkdocs_drawio_exporter/exporter.py
index beea431..ff0fba5 100644
--- a/mkdocs_drawio_exporter/exporter.py
+++ b/mkdocs_drawio_exporter/exporter.py
@@ -119,6 +119,9 @@ def __hash__(self):
             'page_index', self.page_index,
         ))
 
+    def __repr__(self):
+        return f"Source({self.source_embed}, {self.page_index}, {self.source_rel})"
+
     def resolve_rel_path(self, page_dest_path):
         """Resolve the path of the source, relative to the documentation directory.
 
@@ -142,12 +145,20 @@ class DrawIoExporter:
     :type: logging.Logger
     """
 
-    def __init__(self, log):
+    docs_dir = None
+    """Draw.io docs_dir.
+
+    :type str:
+    """
+
+    def __init__(self, log, docs_dir):
         """Initialise.
 
         :param logging.Logger log: Where to log.
+        :param str docs_dir: MkDocs docs_dir.
         """
         self.log = log
+        self.docs_dir = docs_dir
 
     DRAWIO_EXECUTABLE_NAMES = ['drawio', 'draw.io']
     """Draw.io executable names."""
@@ -178,7 +189,7 @@ def drawio_executable_paths(self, platform):
         else:
             self.log.warning(f'Draw.io executable paths not known for platform "{platform}"')
 
-    def prepare_cache_dir(self, cache_dir, docs_dir):
+    def prepare_cache_dir(self, cache_dir):
         """Ensure the cache path is set, absolute and exists.
 
         :param str cache_dir: Configured cache directory.
@@ -188,7 +199,7 @@ def prepare_cache_dir(self, cache_dir, docs_dir):
         if not cache_dir:
            cache_dir = 'drawio-exporter'
         if not os.path.isabs(cache_dir):
-            cache_dir = os.path.join(docs_dir, cache_dir)
+            cache_dir = os.path.join(self.docs_dir, cache_dir)
         return cache_dir
 
     def prepare_drawio_executable(self, executable, executable_names, platform_executable_paths):
@@ -221,9 +232,21 @@ def prepare_drawio_executable(self, executable, executable_names, platform_execu
         raise ConfigurationError.drawio_executable(
                 None, 'Unable to find Draw.io executable; ensure it\'s on PATH or set drawio_executable option')
 
-    def rewrite_image_embeds(self, output_content, config: Configuration):
+    def validate_config(self, config: Configuration):
+        """Validate the configuration.
+
+        :param dict config: Configuration.
+        :return bool: True if configuration is valid.
+        """
+        if '{content}' in config['embed_format'] and config['format'] != 'svg':
+            raise ConfigurationError(
+                    'embed_format', config['embed_format'],
+                    'cannot inline content of non-SVG format')
+
+    def rewrite_image_embeds(self, page_dest_path, output_content, config: Configuration):
         """Rewrite image embeds.
 
+        :param str page_dest_path: Destination path.
         :param str output_content: Content to rewrite.
         :param str sources: Glob to match Draw.io diagram filenames.
         :param str format: Desired export format.
@@ -240,12 +263,33 @@ def replace(match):
                 page_index = 0
 
             if fnmatch.fnmatch(filename, config["sources"]):
-                content_sources.append(Source(filename, page_index))
+                source = Source(filename, page_index)
+                source.resolve_rel_path(page_dest_path)
+                content_sources.append(source)
                 img_src = f"{filename}-{page_index}.{config["format"]}"
 
+                # Cache the file on-demand and read file content only if we
+                # need to inline the file's content.
+                content = None
+                if "{content}" in config["embed_format"]:
+                    img_path = self.make_cache_filename(
+                            source.source_rel, page_index, config['cache_dir'])
+
+                    abs_src_path = os.path.join(self.docs_dir, source.source_rel)
+                    _, exit_status = self.ensure_file_cached(
+                            abs_src_path, source.source_rel, source.page_index,
+                            config)
+
+                    if exit_status not in (None, 0):
+                        self.log.error(f'Export failed with exit status {exit_status}; skipping rewrite')
+                        return match.group(0)
+
+                    with open(img_path, "r") as f:
+                        content = f.read()
+
                 return config["embed_format"].format(
                         img_open=match.group(1), img_close=match.group(3),
-                        img_src=img_src)
+                        img_src=img_src, content=content)
             else:
                 return match.group(0)
         output_content = IMAGE_RE.sub(replace, output_content)
diff --git a/mkdocs_drawio_exporter/plugin.py b/mkdocs_drawio_exporter/plugin.py
index 647abc7..50d1915 100644
--- a/mkdocs_drawio_exporter/plugin.py
+++ b/mkdocs_drawio_exporter/plugin.py
@@ -34,15 +34,16 @@ class DrawIoExporterPlugin(mkdocs.plugins.BasePlugin):
     sources = []
 
     def on_config(self, config):
-        self.exporter = DrawIoExporter(log)
+        self.exporter = DrawIoExporter(log, config['docs_dir'])
 
         self.config['cache_dir'] = self.exporter.prepare_cache_dir(
-                self.config['cache_dir'], config['docs_dir'])
+                self.config['cache_dir'])
         try:
             self.config['drawio_executable'] = self.exporter.prepare_drawio_executable(
                     self.config['drawio_executable'],
                     DrawIoExporter.DRAWIO_EXECUTABLE_NAMES,
                     self.exporter.drawio_executable_paths(sys.platform))
+            self.exporter.validate_config(self.config)
         except ConfigurationError as e:
             raise mkdocs.exceptions.ConfigurationError(str(e))
 
@@ -54,10 +55,8 @@ def on_config(self, config):
 
     def on_post_page(self, output_content, page, **kwargs):
         output_content, content_sources = self.exporter.rewrite_image_embeds(
-                output_content, self.config)
+                page.file.dest_path, output_content, self.config)
 
-        for source in content_sources:
-            source.resolve_rel_path(page.file.dest_path)
         self.sources += content_sources
 
         return output_content


Test patch:
diff --git a/mkdocs_drawio_exporter/tests/exporter.py b/mkdocs_drawio_exporter/tests/exporter.py
index 1e321aa..f76fcbc 100644
--- a/mkdocs_drawio_exporter/tests/exporter.py
+++ b/mkdocs_drawio_exporter/tests/exporter.py
@@ -3,8 +3,7 @@
 
 import logging
 import os
-from os.path import isabs, join, sep
-import re
+from os.path import join, sep
 
 from ..exporter import Configuration, ConfigurationError, DrawIoExporter
 
@@ -20,11 +19,14 @@ def __init__(self, **kwargs):
 
 class ExporterTests(unittest.TestCase):
     log = None
-    exporter = None
 
     def setUp(self):
         self.log = logging.getLogger(__name__)
-        self.exporter = DrawIoExporter(self.log)
+
+    def make_exporter(self, docs_dir=None):
+        if not docs_dir:
+            docs_dir = sep + 'docs'
+        return DrawIoExporter(self.log, docs_dir)
 
     def make_config(self, **kwargs):
         defaults = {
@@ -42,35 +44,42 @@ def make_config(self, **kwargs):
 
     def test_drawio_executable_paths_warns_on_unknown_platform(self):
         self.log.warning = MagicMock()
-        self.exporter.drawio_executable_paths('win32-but-stable')
+        exporter = self.make_exporter()
+        exporter.drawio_executable_paths('win32-but-stable')
         self.log.warning.assert_called_once()
 
     def test_prepare_cache_dir_defaults(self):
-        assert len(self.exporter.prepare_cache_dir(None, sep + 'docs'))
+        exporter = self.make_exporter()
+        assert len(exporter.prepare_cache_dir(sep + 'docs'))
 
     def test_prepare_cache_dir_resolves_relative_path(self):
-        result = self.exporter.prepare_cache_dir('temp', sep + 'docs')
+        exporter = self.make_exporter()
+
+        result = exporter.prepare_cache_dir('temp')
         assert os.path.isabs(result)
         assert result.startswith(sep + 'docs' + sep)
 
     def test_prepare_drawio_executable_aborts_on_missing_executable(self):
+        exporter = self.make_exporter()
         with self.assertRaises(ConfigurationError):
-            self.exporter.prepare_drawio_executable(
+            exporter.prepare_drawio_executable(
                     sep + join('does', 'not', 'exist'), [], [])
 
     def test_prepare_drawio_executable_uses_valid_specified_exectuable(self):
+        exporter = self.make_exporter()
         # We need something that exists, and right now we're not verifying that
         # it's executable. This will do.
         expected = os.path.abspath(__file__)
-        actual = self.exporter.prepare_drawio_executable(
-                expected, self.exporter.DRAWIO_EXECUTABLE_NAMES, [])
+        actual = exporter.prepare_drawio_executable(
+                expected, exporter.DRAWIO_EXECUTABLE_NAMES, [])
         assert expected == actual
 
     @patch('shutil.which')
     @patch.dict(os.environ, {'PATH': sep + join('does', 'not', 'exist')})
     def test_prepare_drawio_executable_on_path(self, mock_which):
+        exporter = self.make_exporter()
         mock_which.return_value = sep + join('does', 'not', 'exist', 'drawio')
-        result = self.exporter.prepare_drawio_executable(None, self.exporter.DRAWIO_EXECUTABLE_NAMES, [])
+        result = exporter.prepare_drawio_executable(None, exporter.DRAWIO_EXECUTABLE_NAMES, [])
         assert result == mock_which.return_value
 
     @patch('os.path.isfile')
@@ -78,37 +87,44 @@ def test_prepare_drawio_executable_platform_specific(self, mock_isfile):
         expect = sep + join('does', 'not', 'exist', 'someotherdrawio')
         mock_isfile.side_effect = lambda p: p == expect
 
-        result = self.exporter.prepare_drawio_executable(None, [], [
+        exporter = self.make_exporter()
+
+        result = exporter.prepare_drawio_executable(None, [], [
             sep + join('does', 'not', 'exist', 'drawio'),
             expect,
         ])
         assert result == expect
 
     def test_prepare_drawio_executable_raises_on_failure(self):
+        exporter = self.make_exporter()
+
         with self.assertRaises(ConfigurationError):
-            self.exporter.prepare_drawio_executable(None, [], [])
+            exporter.prepare_drawio_executable(None, [], [])
 
     def test_rewrite_image_embeds(self):
+        page_dest_path = "index.html"
         source = '''<h1>Example text</h1>
 <img alt="Some text" src="../some-diagram.drawio" />'''
         object_embed_format = '<object type="image/svg+xml" data="{img_src}"></object>'
 
+        exporter = self.make_exporter()
+
         config = self.make_config(sources='*.nomatch')
-        output_content, sources = self.exporter.rewrite_image_embeds(
-                source, config)
+        output_content, sources = exporter.rewrite_image_embeds(
+                page_dest_path, source, config)
         assert output_content == source
         assert sources == []
 
         config = self.make_config()
-        output_content, sources = self.exporter.rewrite_image_embeds(
-                source, config)
+        output_content, sources = exporter.rewrite_image_embeds(
+                page_dest_path, source, config)
         assert output_content != source
         assert 'src="../some-diagram.drawio-0.svg"' in output_content
         assert len(sources) == 1
 
         config = self.make_config(embed_format=object_embed_format)
-        output_content, sources = self.exporter.rewrite_image_embeds(
-                source, config)
+        output_content, sources = exporter.rewrite_image_embeds(
+                page_dest_path, source, config)
         assert output_content != source
         assert '<object type="image/svg+xml" data="../some-diagram.drawio-0.svg"></object>' in output_content
         assert len(sources) == 1
@@ -120,10 +136,12 @@ def test_filter_cache_files(self):
         ]
         files = [FileMock(**{'abs_src_path': f}) for f in files]
 
-        result = self.exporter.filter_cache_files(files, sep + join('docs', 'nomatch'))
+        exporter = self.make_exporter()
+
+        result = exporter.filter_cache_files(files, sep + join('docs', 'nomatch'))
         assert len(result) == 2
 
-        result = self.exporter.filter_cache_files(files, sep + join('docs', 'drawio-exporter'))
+        result = exporter.filter_cache_files(files, sep + join('docs', 'drawio-exporter'))
         assert len(result) == 1
         assert files[0] not in result
 
@@ -133,20 +151,21 @@ def test_ensure_file_cached(self):
         drawio_executable = sep + join('bin', 'drawio')
         cache_dir = sep + join('docs', 'drawio-exporter')
 
+        exporter = self.make_exporter()
         config = self.make_config(
             cache_dir=cache_dir,
             drawio_executable=drawio_executable,
         )
 
-        self.exporter.make_cache_filename = MagicMock()
-        self.exporter.make_cache_filename.return_value = join(cache_dir, '0000000000000000000000000000000000000000-0')
+        exporter.make_cache_filename = MagicMock()
+        exporter.make_cache_filename.return_value = join(cache_dir, '0000000000000000000000000000000000000000-0')
 
-        self.exporter.export_file = MagicMock()
-        self.exporter.export_file.return_value = 0
+        exporter.export_file = MagicMock()
+        exporter.export_file.return_value = 0
 
-        cache_filename, exit_status = self.exporter.ensure_file_cached(
+        cache_filename, exit_status = exporter.ensure_file_cached(
                 source, source_rel, 0, config)
-        assert cache_filename == self.exporter.make_cache_filename.return_value
+        assert cache_filename == exporter.make_cache_filename.return_value
         assert exit_status == 0
 
     def test_ensure_file_cached_aborts_if_drawio_executable_unavailable(self):
@@ -155,17 +174,18 @@ def test_ensure_file_cached_aborts_if_drawio_executable_unavailable(self):
         drawio_executable = None
         cache_dir = sep + join('docs', 'drawio-exporter')
 
+        exporter = self.make_exporter()
         config = self.make_config(
             cache_dir=cache_dir,
             drawio_executable=drawio_executable,
         )
 
-        self.exporter.export_file = MagicMock()
-        self.exporter.export_file.return_value = 0
+        exporter.export_file = MagicMock()
+        exporter.export_file.return_value = 0
 
         self.log.warning = MagicMock()
 
-        cache_filename, exit_status = self.exporter.ensure_file_cached(
+        _, exit_status = exporter.ensure_file_cached(
                 source, source_rel, 0, config)
 
         assert exit_status == None
@@ -177,27 +197,28 @@ def test_ensure_file_cached_skips_export_if_cache_fresh(self):
         drawio_executable = sep + join('bin', 'drawio')
         cache_dir = sep + join('docs', 'drawio-exporter')
 
+        exporter = self.make_exporter()
         config = self.make_config(
             cache_dir=cache_dir,
             drawio_executable=drawio_executable,
         )
 
-        self.exporter.make_cache_filename = MagicMock()
-        self.exporter.make_cache_filename.return_value = join(cache_dir, '0000000000000000000000000000000000000000-0')
+        exporter.make_cache_filename = MagicMock()
+        exporter.make_cache_filename.return_value = join(cache_dir, '0000000000000000000000000000000000000000-0')
 
-        self.exporter.use_cached_file = MagicMock()
-        self.exporter.use_cached_file.return_value = True
+        exporter.use_cached_file = MagicMock()
+        exporter.use_cached_file.return_value = True
 
-        self.exporter.export_file = MagicMock()
-        self.exporter.export_file.return_value = 0
+        exporter.export_file = MagicMock()
+        exporter.export_file.return_value = 0
 
-        cache_filename, exit_status = self.exporter.ensure_file_cached(
+        cache_filename, exit_status = exporter.ensure_file_cached(
                 source, source_rel, 0, config)
 
-        assert cache_filename == self.exporter.make_cache_filename.return_value
+        assert cache_filename == exporter.make_cache_filename.return_value
         assert exit_status == None
-        self.exporter.use_cached_file.assert_called_once()
-        assert not self.exporter.export_file.called
+        exporter.use_cached_file.assert_called_once()
+        assert not exporter.export_file.called
 
     def test_ensure_file_cached_returns_exit_status_if_non_zero(self):
         source = sep + join('docs', 'diagram.drawio')
@@ -205,23 +226,24 @@ def test_ensure_file_cached_returns_exit_status_if_non_zero(self):
         drawio_executable = sep + join('bin', 'drawio')
         cache_dir = sep + join('docs', 'drawio-exporter')
 
+        exporter = self.make_exporter()
         config = self.make_config(
             cache_dir=cache_dir,
             drawio_executable=drawio_executable,
         )
 
-        self.exporter.make_cache_filename = MagicMock()
-        self.exporter.make_cache_filename.return_value = join(cache_dir, '0000000000000000000000000000000000000000-0')
+        exporter.make_cache_filename = MagicMock()
+        exporter.make_cache_filename.return_value = join(cache_dir, '0000000000000000000000000000000000000000-0')
 
-        self.exporter.use_cached_file = MagicMock()
-        self.exporter.use_cached_file.return_value = False
+        exporter.use_cached_file = MagicMock()
+        exporter.use_cached_file.return_value = False
 
-        self.exporter.export_file = MagicMock()
-        self.exporter.export_file.return_value = 1
+        exporter.export_file = MagicMock()
+        exporter.export_file.return_value = 1
 
         self.log.error = MagicMock()
 
-        cache_filename, exit_status = self.exporter.ensure_file_cached(
+        cache_filename, exit_status = exporter.ensure_file_cached(
                 source, source_rel, 0, config)
 
         assert exit_status == 1
@@ -229,10 +251,12 @@ def test_ensure_file_cached_returns_exit_status_if_non_zero(self):
     def test_make_cache_filename(self):
         cache_dir = sep + 'docs'
 
+        exporter = self.make_exporter()
+
         results = [
-            self.exporter.make_cache_filename('diagram.drawio', 0, cache_dir),
-            self.exporter.make_cache_filename('other-diagram.drawio', 0, cache_dir),
-            self.exporter.make_cache_filename('other-diagram.drawio', 1, cache_dir),
+            exporter.make_cache_filename('diagram.drawio', 0, cache_dir),
+            exporter.make_cache_filename('other-diagram.drawio', 0, cache_dir),
+            exporter.make_cache_filename('other-diagram.drawio', 1, cache_dir),
         ]
 
         for result in results:
@@ -247,6 +271,8 @@ def test_use_cached_file(self, getmtime_mock, exists_mock):
 
         exists_mock.return_value = True
 
+        exporter = self.make_exporter()
+
         def getmtime_return_value(path):
             if path == cache_filename:
                 return 1577043612.1444612
@@ -256,7 +282,7 @@ def getmtime_return_value(path):
                 raise ValueError(f'didn\'t expect path "{path}"')
         getmtime_mock.side_effect = getmtime_return_value
 
-        result = self.exporter.use_cached_file(source, cache_filename)
+        result = exporter.use_cached_file(source, cache_filename)
         assert result == False
 
     @patch('subprocess.call')
@@ -265,13 +291,14 @@ def test_export_file(self, call_mock):
         dest = sep + join('docs', 'diagram.drawio-0.svg')
         drawio_executable = sep + join('bin', 'drawio')
 
+        exporter = self.make_exporter()
         config = self.make_config(
             drawio_executable=drawio_executable,
         )
 
         call_mock.return_value = 0
 
-        result = self.exporter.export_file(
+        result = exporter.export_file(
                 source, 0, dest, config)
 
         assert result == 0
@@ -283,6 +310,7 @@ def test_export_file_logs_exc_on_raise(self, call_mock):
         dest = sep + join('docs', 'diagram.drawio-0.svg')
         drawio_executable = sep + join('bin', 'drawio')
 
+        exporter = self.make_exporter()
         config = self.make_config(
             drawio_executable=drawio_executable,
         )
@@ -291,7 +319,7 @@ def test_export_file_logs_exc_on_raise(self, call_mock):
 
         self.log.exception = MagicMock()
 
-        result = self.exporter.export_file(
+        result = exporter.export_file(
                 source, 0, dest, config)
 
         assert result == None
@@ -305,6 +333,7 @@ def test_export_file_honours_drawio_args(self, call_mock):
         dest = sep + join('docs', 'diagram.drawio-0.svg')
         drawio_executable = sep + join('bin', 'drawio')
 
+        exporter = self.make_exporter()
         config = self.make_config(
             drawio_executable=drawio_executable,
             format='svg'
@@ -312,7 +341,7 @@ def test_export_file_honours_drawio_args(self, call_mock):
 
         def test_drawio_args(config: Configuration, drawio_args):
             test_config = {**config, 'drawio_args': drawio_args}
-            self.exporter.export_file(
+            exporter.export_file(
                     source, page_index, dest, test_config)
             call_mock.assert_called_with([
                 config['drawio_executable'],