diff --git a/examples/docs/tests/png/index.md b/examples/docs/tests/png/index.md new file mode 100644 index 0000000..730631c --- /dev/null +++ b/examples/docs/tests/png/index.md @@ -0,0 +1,27 @@ +# 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) + +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 warning: + +```bash +WARNING - Warning: PNG file 'missing-mxfile.drawio.png' on path '/tmp/mkdocs_avmpk9qy/tests/png' missing mxfile metadata +``` + +=== "Markdown" + +```markdown +![](test.drawio.png) +``` \ No newline at end of file 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 0000000..ddaf5f7 Binary files /dev/null and b/examples/docs/tests/png/missing-mxfile.drawio.png differ diff --git a/examples/docs/tests/png/test.drawio.png b/examples/docs/tests/png/test.drawio.png new file mode 100644 index 0000000..2eee189 Binary files /dev/null and b/examples/docs/tests/png/test.drawio.png differ 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: diff --git a/examples/mkdocs.yml b/examples/mkdocs.yml index af04687..3d9d323 100644 --- a/examples/mkdocs.yml +++ b/examples/mkdocs.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: diff --git a/mkdocs_drawio/plugin.py b/mkdocs_drawio/plugin.py index 4008949..1adaa02 100644 --- a/mkdocs_drawio/plugin.py +++ b/mkdocs_drawio/plugin.py @@ -1,9 +1,11 @@ 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 bs4 import BeautifulSoup @@ -156,7 +158,7 @@ def render_drawio_diagrams(self, output_content, page): # search for images using drawio extension diagrams = soup.find_all( - "img", src=re.compile(r".*\.drawio(.svg)?$", re.IGNORECASE) + "img", src=re.compile(r".*\.drawio(\.svg|\.png)?$", re.IGNORECASE) ) if len(diagrams) == 0: return output_content @@ -182,36 +184,78 @@ 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: + # 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()) + @staticmethod def substitute_with_url(config: Dict, url: str, style: str) -> str: config["url"] = url @@ -220,20 +264,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: - 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) diff --git a/pyproject.toml b/pyproject.toml index 4e6d667..826045c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "mkdocs-drawio" -version = "1.15.0" +version = "1.16.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" +pypng = "=0.20220715.0" beautifulsoup4 = ">=4.0" lxml = ">=4.8" mkdocs = ">=1.4"