SVG2DrawIOLib 1.2.1__tar.gz → 1.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/.gitignore +11 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/PKG-INFO +1 -1
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/__about__.py +1 -1
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/icon_analyzer.py +12 -12
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/validator.py +9 -33
- svg2drawiolib-1.2.2/src/SVG2DrawIOLib/xml_utils.py +55 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/test_cli_validate.py +4 -2
- svg2drawiolib-1.2.2/tests/test_xml_utils.py +116 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/.github/workflows/ci.yml +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/.github/workflows/publish.yml +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/.github/workflows/release.yml +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/.pre-commit-config.yaml +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/ARCHITECTURE.md +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/CHANGELOG.md +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/CONTRIBUTING.md +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/LICENSE.txt +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/MANIFEST.in +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/Makefile +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/QUICKSTART.md +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/README.md +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/pyproject.toml +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/__init__.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/cli/__init__.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/cli/add.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/cli/create.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/cli/create_helpers.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/cli/extract.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/cli/helpers.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/cli/inspect.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/cli/list.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/cli/remove.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/cli/rename.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/cli/split_paths.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/cli/validate.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/library_manager.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/models.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/path_splitter.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/src/SVG2DrawIOLib/svg_processor.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/__init__.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/conftest.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/test_cli.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/test_cli_create_helpers.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/test_cli_create_split.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/test_cli_extract.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/test_cli_helpers.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/test_cli_inspect.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/test_cli_rename.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/test_css_modes.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/test_library_manager.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/test_models.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/test_path_splitter.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/test_svg_processor.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/test_svg_processor_coverage.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/tests/test_viewbox_improvements.py +0 -0
- {svg2drawiolib-1.2.1 → svg2drawiolib-1.2.2}/uv.lock +0 -0
|
@@ -38,3 +38,14 @@ debug/
|
|
|
38
38
|
# Temporary test files
|
|
39
39
|
/test_*.xml
|
|
40
40
|
/test_*.py
|
|
41
|
+
|
|
42
|
+
# web-ui (Next.js)
|
|
43
|
+
web-ui/node_modules/
|
|
44
|
+
web-ui/.next/
|
|
45
|
+
web-ui/out/
|
|
46
|
+
web-ui/.env.local
|
|
47
|
+
web-ui/.env*.local
|
|
48
|
+
|
|
49
|
+
# api (FastAPI)
|
|
50
|
+
api/__pycache__/
|
|
51
|
+
api/**/__pycache__/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SVG2DrawIOLib
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Generate DrawIO shape libraries from SVGs
|
|
5
5
|
Project-URL: Homepage, https://github.com/jamesbconner/SVG2DrawIOLib
|
|
6
6
|
Project-URL: Documentation, https://github.com/jamesbconner/SVG2DrawIOLib#readme
|
|
@@ -5,12 +5,12 @@ import binascii
|
|
|
5
5
|
import logging
|
|
6
6
|
import re
|
|
7
7
|
import xml.etree.ElementTree as ET
|
|
8
|
-
import zlib
|
|
9
8
|
from dataclasses import dataclass
|
|
10
9
|
from pathlib import Path
|
|
11
10
|
from typing import Any
|
|
12
11
|
|
|
13
12
|
from SVG2DrawIOLib.models import DrawIOIcon
|
|
13
|
+
from SVG2DrawIOLib.xml_utils import decode_drawio_xml
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
@@ -51,7 +51,7 @@ class IconAnalyzer:
|
|
|
51
51
|
"""Extract SVG content and style information from icon data.
|
|
52
52
|
|
|
53
53
|
Args:
|
|
54
|
-
xml_data: Base64-encoded
|
|
54
|
+
xml_data: Base64-encoded compressed mxGraphModel XML or URL-encoded plain text XML.
|
|
55
55
|
|
|
56
56
|
Returns:
|
|
57
57
|
Tuple of (svg_content, style_info).
|
|
@@ -60,11 +60,8 @@ class IconAnalyzer:
|
|
|
60
60
|
ValueError: If the data cannot be decompressed or parsed.
|
|
61
61
|
"""
|
|
62
62
|
try:
|
|
63
|
-
# Decode
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
# Decompress using raw DEFLATE (wbits=-15)
|
|
67
|
-
decompressed = zlib.decompress(compressed, wbits=-15)
|
|
63
|
+
# Decode XML data (handles both compressed and URL-encoded formats)
|
|
64
|
+
decompressed = decode_drawio_xml(xml_data)
|
|
68
65
|
|
|
69
66
|
# Parse mxGraphModel XML
|
|
70
67
|
root = ET.fromstring(decompressed) # nosec B314 - User-provided library file
|
|
@@ -124,13 +121,16 @@ class IconAnalyzer:
|
|
|
124
121
|
return svg_content, style_info
|
|
125
122
|
|
|
126
123
|
except binascii.Error as e:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
raise ValueError(f"Failed to decompress icon data: {e}") from e
|
|
130
|
-
except ET.ParseError as e:
|
|
131
|
-
raise ValueError(f"Failed to parse mxGraphModel XML: {e}") from e
|
|
124
|
+
# This can occur during SVG base64 decoding (line 93)
|
|
125
|
+
raise ValueError(f"Failed to decode SVG base64: {e}") from e
|
|
132
126
|
except UnicodeDecodeError as e:
|
|
127
|
+
# This can occur during SVG UTF-8 decoding (line 94)
|
|
133
128
|
raise ValueError(f"Failed to decode SVG UTF-8: {e}") from e
|
|
129
|
+
except ET.ParseError as e:
|
|
130
|
+
raise ValueError(f"Failed to parse mxGraphModel XML: {e}") from e
|
|
131
|
+
except ValueError:
|
|
132
|
+
# Re-raise ValueError from decode_drawio_xml or other sources
|
|
133
|
+
raise
|
|
134
134
|
|
|
135
135
|
def extract_to_file(
|
|
136
136
|
self,
|
|
@@ -6,10 +6,11 @@ import json
|
|
|
6
6
|
import logging
|
|
7
7
|
import re
|
|
8
8
|
import xml.etree.ElementTree as ET
|
|
9
|
-
import zlib
|
|
10
9
|
from pathlib import Path
|
|
11
10
|
from typing import Any
|
|
12
11
|
|
|
12
|
+
from SVG2DrawIOLib.xml_utils import decode_drawio_xml
|
|
13
|
+
|
|
13
14
|
logger = logging.getLogger(__name__)
|
|
14
15
|
|
|
15
16
|
|
|
@@ -165,7 +166,7 @@ class LibraryValidator:
|
|
|
165
166
|
}
|
|
166
167
|
)
|
|
167
168
|
|
|
168
|
-
# Validate XML data (base64 encoded
|
|
169
|
+
# Validate XML data (can be base64+compressed or URL-encoded plain text)
|
|
169
170
|
try:
|
|
170
171
|
xml_data = item["xml"]
|
|
171
172
|
if not isinstance(xml_data, str):
|
|
@@ -178,28 +179,15 @@ class LibraryValidator:
|
|
|
178
179
|
)
|
|
179
180
|
return issues
|
|
180
181
|
|
|
181
|
-
#
|
|
182
|
+
# Decode XML data (handles both compressed and URL-encoded formats)
|
|
182
183
|
try:
|
|
183
|
-
|
|
184
|
-
except
|
|
184
|
+
decompressed = decode_drawio_xml(xml_data.encode("utf-8"))
|
|
185
|
+
except ValueError as e:
|
|
185
186
|
issues.append(
|
|
186
187
|
{
|
|
187
188
|
"severity": "error",
|
|
188
189
|
"icon": icon_name,
|
|
189
|
-
"message":
|
|
190
|
-
}
|
|
191
|
-
)
|
|
192
|
-
return issues
|
|
193
|
-
|
|
194
|
-
# Try to decompress
|
|
195
|
-
try:
|
|
196
|
-
decompressed = zlib.decompress(compressed, wbits=-15)
|
|
197
|
-
except zlib.error as e:
|
|
198
|
-
issues.append(
|
|
199
|
-
{
|
|
200
|
-
"severity": "error",
|
|
201
|
-
"icon": icon_name,
|
|
202
|
-
"message": f"Failed to decompress data: {e}",
|
|
190
|
+
"message": str(e),
|
|
203
191
|
}
|
|
204
192
|
)
|
|
205
193
|
return issues
|
|
@@ -225,20 +213,8 @@ class LibraryValidator:
|
|
|
225
213
|
)
|
|
226
214
|
return issues
|
|
227
215
|
|
|
228
|
-
#
|
|
229
|
-
|
|
230
|
-
co = zlib.compressobj(level=9, wbits=-15)
|
|
231
|
-
_ = co.compress(decompressed) + co.flush()
|
|
232
|
-
# Note: Recompressed data may differ slightly due to compression settings
|
|
233
|
-
logger.debug(f"Icon '{icon_name}': round-trip compression successful")
|
|
234
|
-
except Exception as e:
|
|
235
|
-
issues.append(
|
|
236
|
-
{
|
|
237
|
-
"severity": "warning",
|
|
238
|
-
"icon": icon_name,
|
|
239
|
-
"message": f"Failed to recompress data: {e}",
|
|
240
|
-
}
|
|
241
|
-
)
|
|
216
|
+
# For compressed format, verify round-trip (optional validation)
|
|
217
|
+
# Note: We can't easily detect format after decode, so skip this for now
|
|
242
218
|
|
|
243
219
|
# Extract and validate SVG content
|
|
244
220
|
svg_issues = self._validate_svg_content(root, icon_name)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""XML utilities - Shared functions for handling DrawIO XML data."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import binascii
|
|
5
|
+
import html
|
|
6
|
+
import logging
|
|
7
|
+
import urllib.parse
|
|
8
|
+
import zlib
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def decode_drawio_xml(xml_data: bytes) -> bytes:
|
|
14
|
+
"""Decode DrawIO XML data from either compressed or URL-encoded format.
|
|
15
|
+
|
|
16
|
+
DrawIO libraries can store XML in two formats:
|
|
17
|
+
1. Compressed: base64-encoded, zlib-compressed (used by svg2drawiolib create)
|
|
18
|
+
2. URL-encoded: HTML entity-escaped plain text (used by DrawIO native)
|
|
19
|
+
|
|
20
|
+
This function automatically detects the format and decodes accordingly.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
xml_data: Raw XML data as bytes (either compressed or URL-encoded).
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Decompressed/decoded XML data as bytes.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ValueError: If the data cannot be decoded in either format.
|
|
30
|
+
"""
|
|
31
|
+
# Try compressed format first (base64 + zlib)
|
|
32
|
+
try:
|
|
33
|
+
compressed = base64.b64decode(xml_data)
|
|
34
|
+
decompressed = zlib.decompress(compressed, wbits=-15)
|
|
35
|
+
logger.debug("Detected compressed XML format")
|
|
36
|
+
return decompressed
|
|
37
|
+
except (binascii.Error, zlib.error):
|
|
38
|
+
# Not compressed format, try URL-encoded plain text
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
# Try URL-encoded format (DrawIO native)
|
|
42
|
+
try:
|
|
43
|
+
# Decode from bytes to string
|
|
44
|
+
xml_str = xml_data.decode("utf-8")
|
|
45
|
+
# Unescape HTML entities (< -> <, > -> >, etc.)
|
|
46
|
+
unescaped = html.unescape(xml_str)
|
|
47
|
+
# URL decode if needed
|
|
48
|
+
decoded = urllib.parse.unquote(unescaped)
|
|
49
|
+
decompressed = decoded.encode("utf-8")
|
|
50
|
+
logger.debug("Detected URL-encoded XML format")
|
|
51
|
+
return decompressed
|
|
52
|
+
except Exception as e:
|
|
53
|
+
raise ValueError(
|
|
54
|
+
f"Failed to decode XML data (tried both compressed and URL-encoded formats): {e}"
|
|
55
|
+
) from e
|
|
@@ -147,7 +147,8 @@ class TestValidateCommand:
|
|
|
147
147
|
|
|
148
148
|
result = runner.invoke(cli, ["validate", str(library)])
|
|
149
149
|
assert result.exit_code != 0
|
|
150
|
-
|
|
150
|
+
# With fallback to URL-encoded format, this now fails at XML parsing
|
|
151
|
+
assert "Failed to parse mxGraphModel XML" in result.output
|
|
151
152
|
|
|
152
153
|
def test_validate_invalid_compression(self, runner: CliRunner, tmp_path: Path) -> None:
|
|
153
154
|
"""Test validate with data that can't be decompressed."""
|
|
@@ -161,7 +162,8 @@ class TestValidateCommand:
|
|
|
161
162
|
|
|
162
163
|
result = runner.invoke(cli, ["validate", str(library)])
|
|
163
164
|
assert result.exit_code != 0
|
|
164
|
-
|
|
165
|
+
# With fallback to URL-encoded format, this now fails at XML parsing
|
|
166
|
+
assert "Failed to parse mxGraphModel XML" in result.output
|
|
165
167
|
|
|
166
168
|
def test_validate_invalid_mxgraphmodel(self, runner: CliRunner, tmp_path: Path) -> None:
|
|
167
169
|
"""Test validate with invalid mxGraphModel XML."""
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Tests for XML utilities module."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import zlib
|
|
5
|
+
|
|
6
|
+
from SVG2DrawIOLib.xml_utils import decode_drawio_xml
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestDecodeDrawIOXML:
|
|
10
|
+
"""Tests for decode_drawio_xml function."""
|
|
11
|
+
|
|
12
|
+
def test_decode_compressed_format(self) -> None:
|
|
13
|
+
"""Test decoding base64-encoded, zlib-compressed XML."""
|
|
14
|
+
# Create test XML
|
|
15
|
+
xml_content = b"<mxGraphModel><root><mxCell id='0'/></root></mxGraphModel>"
|
|
16
|
+
|
|
17
|
+
# Compress and encode
|
|
18
|
+
co = zlib.compressobj(level=9, wbits=-15)
|
|
19
|
+
compressed = co.compress(xml_content) + co.flush()
|
|
20
|
+
encoded = base64.b64encode(compressed)
|
|
21
|
+
|
|
22
|
+
# Decode
|
|
23
|
+
result = decode_drawio_xml(encoded)
|
|
24
|
+
|
|
25
|
+
assert result == xml_content
|
|
26
|
+
|
|
27
|
+
def test_decode_url_encoded_format(self) -> None:
|
|
28
|
+
"""Test decoding URL-encoded plain text XML."""
|
|
29
|
+
# Create test XML with HTML entities
|
|
30
|
+
xml_content = "<mxGraphModel><root><mxCell id='0'/></root></mxGraphModel>"
|
|
31
|
+
expected = b"<mxGraphModel><root><mxCell id='0'/></root></mxGraphModel>"
|
|
32
|
+
|
|
33
|
+
# Decode
|
|
34
|
+
result = decode_drawio_xml(xml_content.encode("utf-8"))
|
|
35
|
+
|
|
36
|
+
assert result == expected
|
|
37
|
+
|
|
38
|
+
def test_decode_url_encoded_with_special_chars(self) -> None:
|
|
39
|
+
"""Test decoding URL-encoded XML with special characters."""
|
|
40
|
+
# XML with URL-encoded special characters
|
|
41
|
+
xml_content = "<mxGraphModel><root><mxCell id="0"/></root></mxGraphModel>"
|
|
42
|
+
expected = b'<mxGraphModel><root><mxCell id="0"/></root></mxGraphModel>'
|
|
43
|
+
|
|
44
|
+
# Decode
|
|
45
|
+
result = decode_drawio_xml(xml_content.encode("utf-8"))
|
|
46
|
+
|
|
47
|
+
assert result == expected
|
|
48
|
+
|
|
49
|
+
def test_decode_invalid_data(self) -> None:
|
|
50
|
+
"""Test that invalid data is decoded but may not be valid XML."""
|
|
51
|
+
# Data that's neither valid base64+compressed nor valid XML
|
|
52
|
+
# The function will decode it via URL-encoding fallback
|
|
53
|
+
invalid_data = b"this is not valid XML or compressed data!!!"
|
|
54
|
+
|
|
55
|
+
# Should decode without error (validation happens at XML parsing level)
|
|
56
|
+
result = decode_drawio_xml(invalid_data)
|
|
57
|
+
assert result == invalid_data # URL-decode doesn't change this string
|
|
58
|
+
|
|
59
|
+
def test_decode_valid_base64_but_not_compressed(self) -> None:
|
|
60
|
+
"""Test data that's valid base64 but not compressed."""
|
|
61
|
+
# Valid base64 but not compressed data
|
|
62
|
+
data = base64.b64encode(b"not compressed data")
|
|
63
|
+
|
|
64
|
+
# Should fall back to URL-encoded format and decode
|
|
65
|
+
result = decode_drawio_xml(data)
|
|
66
|
+
# The base64 string gets URL-decoded (no change in this case)
|
|
67
|
+
assert isinstance(result, bytes)
|
|
68
|
+
|
|
69
|
+
def test_decode_empty_data(self) -> None:
|
|
70
|
+
"""Test that empty data is handled."""
|
|
71
|
+
# Empty data should decode to empty
|
|
72
|
+
result = decode_drawio_xml(b"")
|
|
73
|
+
assert result == b""
|
|
74
|
+
|
|
75
|
+
def test_decode_compressed_with_complex_xml(self) -> None:
|
|
76
|
+
"""Test decoding compressed XML with complex structure."""
|
|
77
|
+
# Complex XML with attributes and nested elements
|
|
78
|
+
xml_content = b"""<mxGraphModel>
|
|
79
|
+
<root>
|
|
80
|
+
<mxCell id="0"/>
|
|
81
|
+
<mxCell id="1" parent="0"/>
|
|
82
|
+
<mxCell id="2" value="test" style="shape=image;image=data:image/svg+xml,PHN2Zz4=" vertex="1" parent="1">
|
|
83
|
+
<mxGeometry width="100" height="100" as="geometry"/>
|
|
84
|
+
</mxCell>
|
|
85
|
+
</root>
|
|
86
|
+
</mxGraphModel>"""
|
|
87
|
+
|
|
88
|
+
# Compress and encode
|
|
89
|
+
co = zlib.compressobj(level=9, wbits=-15)
|
|
90
|
+
compressed = co.compress(xml_content) + co.flush()
|
|
91
|
+
encoded = base64.b64encode(compressed)
|
|
92
|
+
|
|
93
|
+
# Decode
|
|
94
|
+
result = decode_drawio_xml(encoded)
|
|
95
|
+
|
|
96
|
+
assert result == xml_content
|
|
97
|
+
|
|
98
|
+
def test_decode_url_encoded_with_complex_xml(self) -> None:
|
|
99
|
+
"""Test decoding URL-encoded XML with complex structure."""
|
|
100
|
+
# Complex XML with HTML entities
|
|
101
|
+
xml_content = (
|
|
102
|
+
"<mxGraphModel>"
|
|
103
|
+
"<root>"
|
|
104
|
+
"<mxCell id="0"/>"
|
|
105
|
+
"<mxCell id="1" parent="0"/>"
|
|
106
|
+
"</root>"
|
|
107
|
+
"</mxGraphModel>"
|
|
108
|
+
)
|
|
109
|
+
expected = (
|
|
110
|
+
b'<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel>'
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Decode
|
|
114
|
+
result = decode_drawio_xml(xml_content.encode("utf-8"))
|
|
115
|
+
|
|
116
|
+
assert result == expected
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|