Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -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
RUN pip3 install poetry wheel mkdocs mkdocs-material mkdocs-print-site-plugin BeautifulSoup4
27 changes: 27 additions & 0 deletions examples/docs/tests/png/index.md
Original file line number Diff line number Diff line change
@@ -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)
```
Binary file added examples/docs/tests/png/missing-mxfile.drawio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/docs/tests/png/test.drawio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/mkdocs-classic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions examples/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
103 changes: 70 additions & 33 deletions mkdocs_drawio/plugin.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)

Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 <jan@larwig.com>",
Expand All @@ -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"
Expand Down
Loading