diff --git a/.gitignore b/.gitignore index 975287e9..a7020574 100644 --- a/.gitignore +++ b/.gitignore @@ -156,3 +156,6 @@ dmypy.json # Cython debug symbols cython_debug/ + +# VSCode project settings +.vscode/ diff --git a/capella2polarion/elements/api_helper.py b/capella2polarion/elements/api_helper.py index 72f8dc95..149de16f 100644 --- a/capella2polarion/elements/api_helper.py +++ b/capella2polarion/elements/api_helper.py @@ -80,27 +80,29 @@ def patch_work_item( logger.error("Updating work item %r failed. %s", wi, error.args[0]) -def split_and_decode_diagram(diagram: str) -> tuple[str, bytes]: - """Split the diagram into type and decoded data.""" - prefix, encoded = diagram.split(";base64,") - return prefix.replace("data:image/", ""), b64.b64decode(encoded) - - def has_visual_changes(old: str, new: str) -> bool: """Return True if the images of the diagrams differ.""" - type_old, decoded_old = split_and_decode_diagram(old) - type_new, decoded_new = split_and_decode_diagram(new) + type_old, decoded_old = _typify_and_decode_diagram(old) + type_new, decoded_new = _typify_and_decode_diagram(new) if type_old != type_new: return True - if type_old == "svg+xml": + if type_old == "image/svg+xml": decoded_old = cairosvg.svg2png(bytestring=decoded_old) decoded_new = cairosvg.svg2png(bytestring=decoded_new) return decoded_old != decoded_new +def _typify_and_decode_diagram(diagram: str) -> tuple[str, bytes]: + """Split the diagram into type and decoded data.""" + prefix, content = diagram.split(";base64,") + mimetype = prefix.replace("data:", "") + content_decoded = b64.b64decode(content) + return mimetype, content_decoded + + def handle_links( left: cabc.Iterable[polarion_api.WorkItemLink], right: cabc.Iterable[polarion_api.WorkItemLink], diff --git a/capella2polarion/elements/serialize.py b/capella2polarion/elements/serialize.py index 674c3670..fd4b340c 100644 --- a/capella2polarion/elements/serialize.py +++ b/capella2polarion/elements/serialize.py @@ -3,7 +3,7 @@ """Objects for serialization of capella objects to workitems.""" from __future__ import annotations -import base64 +import base64 as b64 import collections.abc as cabc import logging import mimetypes @@ -11,6 +11,7 @@ import re import typing as t +import cairosvg import markupsafe import polarion_rest_api_client as polarion_api from capellambse import helpers as chelpers @@ -66,35 +67,31 @@ def element( def diagram(diag: diagr.Diagram, ctx: dict[str, t.Any]) -> CapellaWorkItem: """Serialize a diagram for Polarion.""" diagram_path = ctx["DIAGRAM_CACHE"] / f"{diag.uuid}.svg" - src = _decode_diagram(diagram_path) - style = "; ".join( - (f"{key}: {value}" for key, value in DIAGRAM_STYLES.items()) - ) - description = f'
' + content = diagram_path.read_bytes() + content_svg = b64.standard_b64encode(content) + svg_attachment = attachment("image/svg+xml", f"{diag.uuid}", content_svg) + content_png = b64.b16encode(cairosvg.svg2png(content.decode("utf8"))) + png_attachment = attachment("image/png", f"{diag.uuid}", content_png) return CapellaWorkItem( type="diagram", title=diag.name, - description_type="text/html", - description=description, status="open", + attachments=[svg_attachment, png_attachment], uuid_capella=diag.uuid, ) -def _decode_diagram(diagram_path: pathlib.Path) -> str: - mime_type, _ = mimetypes.guess_type(diagram_path) - if mime_type is None: - logger.error( - "Do not understand the MIME subtype for the diagram '%s'!", - diagram_path, - ) - return "" - content = diagram_path.read_bytes() - content_encoded = base64.standard_b64encode(content) - assert mime_type is not None - image_data = b"data:" + mime_type.encode() + b";base64," + content_encoded - src = image_data.decode() - return src +def attachment( + mime_type: str, name: str, content: bytes +) -> polarion_api.WorkItemAttachment: + """Serialize an attachment for Polarion.""" + return polarion_api.WorkItemAttachment( + work_item_id="", + id="", + content_bytes=content, + mime_type=mime_type, + file_name=name, + ) def generic_work_item( @@ -143,7 +140,7 @@ def repair_images(node: etree._Element) -> None: filehandler = resources[["\x00", workspace][workspace in resources]] try: with filehandler.open(file_path, "r") as img: - b64_img = base64.b64encode(img.read()).decode("utf8") + b64_img = b64.b64encode(img.read()).decode("utf8") node.attrib["src"] = f"data:{mime_type};base64,{b64_img}" except FileNotFoundError: logger.error("Inline image can't be found from %r", file_path) diff --git a/pyproject.toml b/pyproject.toml index 18b1a53a..e2b280ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ dependencies = [ "capellambse", "click", "PyYAML", - "polarion-rest-api-client @ git+https://github.com/DSD-DBS/polarion-rest-api-client.git@feat-add-attachments-workitem-relations", + "polarion-rest-api-client @ git+https://github.com/DSD-DBS/polarion-rest-api-client.git@feat-serialize-attachments", "requests", "cairosvg", ] diff --git a/tests/test_elements.py b/tests/test_elements.py index bde768fd..0d270731 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -55,9 +55,7 @@ TEST_OCAP_UUID: "OperationalCapability", TEST_WE_UUID: "Entity", } -TEST_DIAG_DESCR = ( - 'str: - svg = self.SVG_PATH.read_text() - for key, value in params.items(): - svg = re.sub(f"{key}=[\"'][^\"']*[\"']", f'{key}="{value}"', svg) - content_encoded = b64.b64encode(svg.encode("utf-8")) - image_data = b"data:image/svg+xml;base64," + content_encoded - src = image_data.decode() - return src - @pytest.mark.parametrize( "params,expected", [ @@ -96,6 +85,7 @@ def encode_svg(self, params: dict[str, str]) -> str: id="unchanged", ), pytest.param( + ({"fill": "blue"}, {"fill": "red"}), ({"fill": "blue"}, {"fill": "red"}), True, id="fill_changed", @@ -105,6 +95,11 @@ def encode_svg(self, params: dict[str, str]) -> str: True, id="height_changed", ), + pytest.param( + ({"height": "100"}, {"height": "50"}), + True, + id="height_changed", + ), pytest.param( ({"id": "test"}, {"id": "test2"}), False, @@ -122,13 +117,23 @@ def encode_svg(self, params: dict[str, str]) -> str: ) def test_image_diff(self, params, expected): old_params, new_params = params - old_svg = self.encode_svg(old_params) - new_svg = self.encode_svg(new_params) + old_svg = self._parameterize_and_decode_svg(self.SVG_PATH, old_params) + new_svg = self._parameterize_and_decode_svg(self.SVG_PATH, new_params) is_different = api_helper.has_visual_changes(old_svg, new_svg) assert is_different is expected + def _parameterize_and_decode_svg( + self, diagram_path: pathlib.Path, params: dict[str, str] + ) -> str: + svg = diagram_path.read_text() + for key, value in params.items(): + svg = re.sub(f"{key}=[\"'][^\"']*[\"']", f'{key}="{value}"', svg) + content_encoded = b64.b64encode(svg.encode("utf-8")) + image_data = b"data:image/svg+xml;base64," + content_encoded + return image_data.decode() + class TestDiagramElements: @staticmethod @@ -494,32 +499,6 @@ def test_resolve_element_type(): class TestSerializers: - @staticmethod - def test_diagram(model: capellambse.MelodyModel): - diag = model.diagrams.by_uuid(TEST_DIAG_UUID) - - serialized_diagram = serialize.diagram( - diag, {"DIAGRAM_CACHE": TEST_DIAGRAM_CACHE} - ) - serialized_diagram.description = None - - assert serialized_diagram == serialize.CapellaWorkItem( - type="diagram", - uuid_capella=TEST_DIAG_UUID, - title="[CC] Capability", - description_type="text/html", - status="open", - linked_work_items=[], - ) - - @staticmethod - def test__decode_diagram(): - diagram_path = TEST_DIAGRAM_CACHE / "_APMboAPhEeynfbzU12yy7w.svg" - - diagram = serialize._decode_diagram(diagram_path) - - assert diagram.startswith("data:image/svg+xml;base64,") - @staticmethod @pytest.mark.parametrize( "uuid,expected",