From 2bba669c3837637ed39348b0f462ab931b96667d Mon Sep 17 00:00:00 2001 From: Sophie Horne Date: Fri, 13 Mar 2026 17:46:24 +0000 Subject: [PATCH 1/6] feat: add support for drawio.png --- .devcontainer/Dockerfile | 1 + examples/docs/tests/png/index.md | 18 ++++++++++++++++++ examples/docs/tests/png/test.drawio.png | Bin 0 -> 13347 bytes examples/mkdocs.yml | 1 + mkdocs_drawio/plugin.py | 11 +++++++++-- pyproject.toml | 3 ++- 6 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 examples/docs/tests/png/index.md create mode 100644 examples/docs/tests/png/test.drawio.png diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 27f7c25..778c34d 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,3 +1,4 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:3-bullseye RUN pip3 install poetry wheel mkdocs mkdocs-material mkdocs-print-site-plugin BeautifulSoup4 +RUN pip3 install poetry wheel mkdocs mkdocs-material mkdocs-print-site-plugin BeautifulSoup4 Pillow \ No newline at end of file diff --git a/examples/docs/tests/png/index.md b/examples/docs/tests/png/index.md new file mode 100644 index 0000000..d1ba240 --- /dev/null +++ b/examples/docs/tests/png/index.md @@ -0,0 +1,18 @@ +# PNG diagram + +## Example + +=== "Diagram" + +The following is a PNG based drawio diagram: + +![](test.drawio.png) + +You can open the diagram as an PNG in your browser. [Click here.](test.drawio.png) + +=== "Markdown" + +```markdown +![](test.drawio.png) +``` + diff --git a/examples/docs/tests/png/test.drawio.png b/examples/docs/tests/png/test.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..2eee189b2cb0f1e90ae15720105be2e204a55fd0 GIT binary patch literal 13347 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 str: @staticmethod def substitute_with_file(config: Dict, path: Path, src: str, page: str) -> str: try: - diagram_xml = etree.parse(path.joinpath(src).resolve()) + if src.endswith(".png"): + img = Image.open(path.joinpath(path, src)) + xml_data = unquote(img.info.get("mxfile")) + diagram_xml = etree.fromstring(xml_data.encode()) + else: + diagram_xml = etree.parse(path.joinpath(src).resolve()) except Exception as e: LOGGER.error( f"Error: Could not parse diagram file '{src}' on path '{path}': {e}" diff --git a/pyproject.toml b/pyproject.toml index 0dbe793..0840104 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "mkdocs-drawio" -version = "1.13.0" +version = "1.14.0" description = "MkDocs plugin for embedding Drawio files" authors = [ "Jan Larwig ", @@ -26,6 +26,7 @@ include = [ python = ">=3.8.0,<4.0" requests = ">=2.0" Jinja2 = ">=3.0" +Pillow = "<=12.1.1" beautifulsoup4 = ">=4.0" lxml = ">=4.8" mkdocs = ">=1.4" From ffc96a1d3cfad777020615db724e39c01104521e Mon Sep 17 00:00:00 2001 From: Sophie Horne Date: Sat, 14 Mar 2026 12:50:00 +0000 Subject: [PATCH 2/6] Add example when missing mxfile data --- examples/docs/tests/png/index.md | 4 ++++ .../docs/tests/png/missing-mxfile.drawio.png | Bin 0 -> 12137 bytes 2 files changed, 4 insertions(+) create mode 100644 examples/docs/tests/png/missing-mxfile.drawio.png diff --git a/examples/docs/tests/png/index.md b/examples/docs/tests/png/index.md index d1ba240..dcd2d09 100644 --- a/examples/docs/tests/png/index.md +++ b/examples/docs/tests/png/index.md @@ -10,6 +10,10 @@ The following is a PNG based drawio diagram: You can open the diagram as an PNG in your browser. [Click here.](test.drawio.png) +If the PNG file contains no mxfile information, then it'll fail and fall back to the "Not a diagram file" error being displayed in-place of the diagram: + +![](missing-mxfile.drawio.png) + === "Markdown" ```markdown diff --git a/examples/docs/tests/png/missing-mxfile.drawio.png b/examples/docs/tests/png/missing-mxfile.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..ddaf5f712c61b0929cd8fc44c10ea1a3eb9c2793 GIT binary patch literal 12137 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 T)gL%^PEl9YQb|`nap}JRs@dst literal 0 HcmV?d00001 From 7b271b7ff425f6825b08b6e8fa387ba94b1b0e09 Mon Sep 17 00:00:00 2001 From: Sophie Horne Date: Sat, 14 Mar 2026 13:20:59 +0000 Subject: [PATCH 3/6] Update mkdocs-classic nav and add server error example --- examples/docs/tests/png/index.md | 9 +++++++-- examples/mkdocs-classic.yml | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/docs/tests/png/index.md b/examples/docs/tests/png/index.md index dcd2d09..650e862 100644 --- a/examples/docs/tests/png/index.md +++ b/examples/docs/tests/png/index.md @@ -14,9 +14,14 @@ If the PNG file contains no mxfile information, then it'll fail and fall back to ![](missing-mxfile.drawio.png) +With the following server error: + +```bash +ERROR - Error: Could not parse diagram file '../tests/png/missing-mxfile.drawio.png' on path '/tmp/mkdocs_3kdph6pm/print_page': argument of type 'NoneType' is not iterable +``` + === "Markdown" ```markdown ![](test.drawio.png) -``` - +``` \ No newline at end of file diff --git a/examples/mkdocs-classic.yml b/examples/mkdocs-classic.yml index df50e16..6ba0d1c 100644 --- a/examples/mkdocs-classic.yml +++ b/examples/mkdocs-classic.yml @@ -18,6 +18,7 @@ nav: - Pagging: "tests/pagging/index.md" - External URL: "tests/external-url/index.md" - SVG Diagram: "tests/svg/index.md" + - PNG Diagram: "tests/png/index.md" - Hyperlinks: "tests/hyperlinks/index.md" theme: From 41efc5b961e26cd69f9601be12ce1d9107c1350e Mon Sep 17 00:00:00 2001 From: Sophie Horne Date: Sat, 14 Mar 2026 15:10:28 +0000 Subject: [PATCH 4/6] Fix merge duplication --- .devcontainer/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 778c34d..15bd48f 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,3 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:3-bullseye -RUN pip3 install poetry wheel mkdocs mkdocs-material mkdocs-print-site-plugin BeautifulSoup4 RUN pip3 install poetry wheel mkdocs mkdocs-material mkdocs-print-site-plugin BeautifulSoup4 Pillow \ No newline at end of file From eedc50e2a59ea47d0b740ce935aa27d1c9090b5d Mon Sep 17 00:00:00 2001 From: Sophie Horne Date: Sat, 14 Mar 2026 17:36:35 +0000 Subject: [PATCH 5/6] Add fallback to display PNG when missing mxfile data --- examples/docs/tests/png/index.md | 6 +- mkdocs_drawio/plugin.py | 97 ++++++++++++++++++++------------ 2 files changed, 63 insertions(+), 40 deletions(-) diff --git a/examples/docs/tests/png/index.md b/examples/docs/tests/png/index.md index 650e862..730631c 100644 --- a/examples/docs/tests/png/index.md +++ b/examples/docs/tests/png/index.md @@ -10,14 +10,14 @@ The following is a PNG based drawio diagram: You can open the diagram as an PNG in your browser. [Click here.](test.drawio.png) -If the PNG file contains no mxfile information, then it'll fail and fall back to the "Not a diagram file" error being displayed in-place of the diagram: +If the PNG file contains no mxfile information, then it'll fail and fall back to displaying the PNG file: ![](missing-mxfile.drawio.png) -With the following server error: +With the following server warning: ```bash -ERROR - Error: Could not parse diagram file '../tests/png/missing-mxfile.drawio.png' on path '/tmp/mkdocs_3kdph6pm/print_page': argument of type 'NoneType' is not iterable +WARNING - Warning: PNG file 'missing-mxfile.drawio.png' on path '/tmp/mkdocs_avmpk9qy/tests/png' missing mxfile metadata ``` === "Markdown" diff --git a/mkdocs_drawio/plugin.py b/mkdocs_drawio/plugin.py index e2f9385..da4f9fc 100644 --- a/mkdocs_drawio/plugin.py +++ b/mkdocs_drawio/plugin.py @@ -184,36 +184,71 @@ def render_drawio_diagrams(self, output_content, page): diagram_config["zoom"] = diagram.get("zoom") if re.search("^https?://", diagram["src"]): - mxgraph = BeautifulSoup( - DrawioPlugin.substitute_with_url( - diagram_config, diagram["src"], diagram_style - ), - "html.parser", + mxgraph = DrawioPlugin.substitute_with_url( + diagram_config, diagram["src"], diagram_style ) else: - diagram_page = "" + try: + mxfile_xml = DrawioPlugin.retrieve_mxfile(path, diagram["src"]) - # Use page attribute instead of alt if it is set - if diagram.has_attr("page"): - diagram_page = diagram.get("page") - else: - diagram_page = diagram.get("alt") + # None is returned when PNG has no mxfile data - fallback to displaying PNG + if mxfile_xml is None: + continue - mxgraph = BeautifulSoup( - DrawioPlugin.substitute_with_file( + diagram_page = "" + + # Use page attribute instead of alt if it is set + if diagram.has_attr("page"): + diagram_page = diagram.get("page") + else: + diagram_page = diagram.get("alt") + + mxgraph = DrawioPlugin.substitute_with_file( + mxfile_xml, diagram_config, - path, - diagram["src"], diagram_page, diagram_style, - ), - "html.parser", - ) - - diagram.replace_with(mxgraph) + ) + except Exception as e: + LOGGER.error( + f"Error: Could not parse diagram file '{diagram["src"]}' on path '{path}': {e}" + ) + diagram_config["xml"] = "" + mxgraph = SUB_TEMPLATE.substitute( + config=escape(json.dumps(diagram_config)), style=diagram_style + ) + + mxgraph_soup = BeautifulSoup( + mxgraph, + "html.parser", + ) + diagram.replace_with(mxgraph_soup) return str(soup) + @staticmethod + def retrieve_mxfile( + path: Path, src: str + ) -> etree._Element | etree._ElementTree | None: + # Handle PNG files + if src.endswith(".png"): + img = Image.open(path.joinpath(path, src)) + + # Extract mxfile data from PNG + mxfile_metadata = img.info.get("mxfile") + + # If none exists, handle it gracefully + if mxfile_metadata is None: + LOGGER.warning( + f"Warning: PNG file '{src}' on path '{path}' missing mxfile metadata" + ) + return None + + xml_data = unquote(mxfile_metadata) + return etree.fromstring(xml_data.encode()) + else: + return etree.parse(path.joinpath(src).resolve()) + @staticmethod def substitute_with_url(config: Dict, url: str, style: str) -> str: config["url"] = url @@ -222,25 +257,13 @@ def substitute_with_url(config: Dict, url: str, style: str) -> str: @staticmethod def substitute_with_file( - config: Dict, path: Path, src: str, page: str, style: str + mxfile_xml: etree._Element, config: Dict, page: str, style: str ) -> str: - try: - if src.endswith(".png"): - img = Image.open(path.joinpath(path, src)) - xml_data = unquote(img.info.get("mxfile")) - diagram_xml = etree.fromstring(xml_data.encode()) - else: - diagram_xml = etree.parse(path.joinpath(src).resolve()) - except Exception as e: - LOGGER.error( - f"Error: Could not parse diagram file '{src}' on path '{path}': {e}" - ) - config["xml"] = "" - return SUB_TEMPLATE.substitute( - config=escape(json.dumps(config)), style=style - ) + if mxfile_xml is not None: + diagram = DrawioPlugin.parse_diagram(mxfile_xml, page) + else: + diagram = "" - diagram = DrawioPlugin.parse_diagram(diagram_xml, page) config["xml"] = diagram return SUB_TEMPLATE.substitute(config=escape(json.dumps(config)), style=style) From aa1abe98f4ac61a02139a59fa6615ae0393e1eb4 Mon Sep 17 00:00:00 2001 From: Sophie Horne Date: Thu, 19 Mar 2026 17:54:58 +0000 Subject: [PATCH 6/6] Replace Pillow with pypng Also fix uppercase scenario and tidy code flow --- .devcontainer/Dockerfile | 2 +- mkdocs_drawio/plugin.py | 43 +++++++++++++++++++++++----------------- pyproject.toml | 2 +- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 15bd48f..e0d49b9 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,3 +1,3 @@ FROM mcr.microsoft.com/vscode/devcontainers/python:3-bullseye -RUN pip3 install poetry wheel mkdocs mkdocs-material mkdocs-print-site-plugin BeautifulSoup4 Pillow \ No newline at end of file +RUN pip3 install poetry wheel mkdocs mkdocs-material mkdocs-print-site-plugin BeautifulSoup4 \ No newline at end of file diff --git a/mkdocs_drawio/plugin.py b/mkdocs_drawio/plugin.py index da4f9fc..1adaa02 100644 --- a/mkdocs_drawio/plugin.py +++ b/mkdocs_drawio/plugin.py @@ -1,13 +1,13 @@ import re import json import string +import png import logging from lxml import etree from html import escape from urllib.parse import unquote from pathlib import Path from typing import Dict -from PIL import Image from bs4 import BeautifulSoup from mkdocs.utils import copy_file from mkdocs.plugins import BasePlugin @@ -230,24 +230,31 @@ def render_drawio_diagrams(self, output_content, page): def retrieve_mxfile( path: Path, src: str ) -> etree._Element | etree._ElementTree | None: - # Handle PNG files - if src.endswith(".png"): - img = Image.open(path.joinpath(path, src)) - - # Extract mxfile data from PNG - mxfile_metadata = img.info.get("mxfile") - - # If none exists, handle it gracefully - if mxfile_metadata is None: - LOGGER.warning( - f"Warning: PNG file '{src}' on path '{path}' missing mxfile metadata" - ) - return None + # Get filepath + filepath = path.joinpath(src).resolve() + + # Handle non-PNG files + if not src.lower().endswith(".png"): + return etree.parse(filepath) + + reader = png.Reader(filename=filepath) + + mxfile_metadata = None + for chunk in reader.chunks(): + if chunk[0] == b"tEXt": + key, value = chunk[1].split(b"\x00", 1) + if key == b"mxfile": + mxfile_metadata = value.decode("latin-1") + + # If none exists, handle it gracefully + if mxfile_metadata is None: + LOGGER.warning( + f"Warning: PNG file '{src}' on path '{path}' missing mxfile metadata" + ) + return None - xml_data = unquote(mxfile_metadata) - return etree.fromstring(xml_data.encode()) - else: - return etree.parse(path.joinpath(src).resolve()) + xml_data = unquote(mxfile_metadata) + return etree.fromstring(xml_data.encode()) @staticmethod def substitute_with_url(config: Dict, url: str, style: str) -> str: diff --git a/pyproject.toml b/pyproject.toml index 7c5356e..826045c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ include = [ python = ">=3.8.0,<4.0" requests = ">=2.0" Jinja2 = ">=3.0" -Pillow = "<=12.1.1" +pypng = "=0.20220715.0" beautifulsoup4 = ">=4.0" lxml = ">=4.8" mkdocs = ">=1.4"