From b34104a1f838a5d810d0e19d6a286f5f5cc0c89f Mon Sep 17 00:00:00 2001
From: Haryto <37344295+Haryto@users.noreply.github.com>
Date: Wed, 30 Aug 2023 15:01:46 +0300
Subject: [PATCH] Bake diagram XML in the PNG image meta data (#83)
* Bake diagram XML in the PNG image meta data
* Change drawio default template content
There was a problem with usage of "".
In that case "bs:drawio" tag was parsed before "{{{diagramName}}}" parameter was passed. So it looked like diagram is named "{{{diagramName}}}".
Now when using "{{#tag:drawio ... }}} diagram name is passed before parsing tag, so now it works correctly.
---------
Co-authored-by: akulbii
---
src/Composer/ConfluenceComposer.php | 27 ++++++
src/Composer/_defaultpages/Template/Drawio | 5 +-
src/Utility/DrawIOFileHandler.php | 61 ++++++++++++++
.../DrawIOFileHandlerTest.php | 73 +++++++++++++++++
.../DrawIOFileHandler/data/diagram.drawio | 77 ++++++++++++++++++
.../DrawIOFileHandler/data/diagram.drawio.png | Bin 0 -> 12087 bytes
6 files changed, 242 insertions(+), 1 deletion(-)
create mode 100644 src/Utility/DrawIOFileHandler.php
create mode 100644 tests/phpunit/Utility/DrawIOFileHandler/DrawIOFileHandlerTest.php
create mode 100644 tests/phpunit/Utility/DrawIOFileHandler/data/diagram.drawio
create mode 100644 tests/phpunit/Utility/DrawIOFileHandler/data/diagram.drawio.png
diff --git a/src/Composer/ConfluenceComposer.php b/src/Composer/ConfluenceComposer.php
index 40b5bdb..542ebf5 100644
--- a/src/Composer/ConfluenceComposer.php
+++ b/src/Composer/ConfluenceComposer.php
@@ -7,6 +7,7 @@
use HalloWelt\MediaWiki\Lib\Migration\DataBuckets;
use HalloWelt\MediaWiki\Lib\Migration\IOutputAwareInterface;
use HalloWelt\MediaWiki\Lib\Migration\Workspace;
+use HalloWelt\MigrateConfluence\Utility\DrawIOFileHandler;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Symfony\Component\Console\Output\Output;
@@ -95,10 +96,36 @@ public function buildXML( Builder $builder ) {
$attachments = $pageAttachmentsMap[$pageTitle];
foreach ( $attachments as $attachment ) {
$this->output->writeln( "Attachment: $attachment" );
+
+ $drawIoFileHandler = new DrawIOFileHandler();
+
+ // We do not need DrawIO data files in our wiki, just PNG image
+ if ( $drawIoFileHandler->isDrawIODataFile( $attachment ) ) {
+ continue;
+ }
+
if ( isset( $filesMap[$attachment] ) ) {
$filePath = $filesMap[$attachment][0];
$attachmentContent = file_get_contents( $filePath );
+ if ( $drawIoFileHandler->isDrawIOImage( $attachment ) ) {
+ // Find associated with DrawIO PNG image diagram XML
+ // If image has "image1.drawio.png" name,
+ // Then diagram XML will be stored in the "image1.drawio" file
+ $diagramFileName = substr( $attachment, 0, -4 );
+
+ if ( isset( $filesMap[$diagramFileName] ) ) {
+ $diagramContent = file_get_contents( $filesMap[$diagramFileName][0] );
+
+ // Need to bake DrawIO diagram XML into the PNG image
+ $attachmentContent = $drawIoFileHandler->bakeDiagramDataIntoImage(
+ $attachmentContent, $diagramContent
+ );
+ } else {
+ $this->output->writeln( "No DrawIO diagram XML was found for image '$attachment'" );
+ }
+ }
+
$this->workspace->saveUploadFile( $attachment, $attachmentContent );
$this->customBuckets->addData( 'title-uploads', $pageTitle, $attachment );
} else {
diff --git a/src/Composer/_defaultpages/Template/Drawio b/src/Composer/_defaultpages/Template/Drawio
index f9b5e2c..7578845 100644
--- a/src/Composer/_defaultpages/Template/Drawio
+++ b/src/Composer/_defaultpages/Template/Drawio
@@ -1 +1,4 @@
-
\ No newline at end of file
+{{#tag:drawio
+|
+|filename={{{diagramName}}}
+}}
\ No newline at end of file
diff --git a/src/Utility/DrawIOFileHandler.php b/src/Utility/DrawIOFileHandler.php
new file mode 100644
index 0000000..df53661
--- /dev/null
+++ b/src/Utility/DrawIOFileHandler.php
@@ -0,0 +1,61 @@
+assertTrue( $drawIoFileHandler->isDrawIODataFile( 'diagram.drawio' ) );
+ $this->assertTrue( $drawIoFileHandler->isDrawIODataFile( 'diagram.drawio.tmp' ) );
+
+ $this->assertFalse( $drawIoFileHandler->isDrawIODataFile( 'diagram.drawio.png' ) );
+ }
+
+ /**
+ * @covers \HalloWelt\MigrateConfluence\Utility\DrawIOFileHandler::isDrawIOImage
+ */
+ public function testIsDrawIOImage() {
+ $drawIoFileHandler = new DrawIOFileHandler();
+
+ $this->assertFalse( $drawIoFileHandler->isDrawIOImage( 'diagram.drawio' ) );
+ $this->assertFalse( $drawIoFileHandler->isDrawIOImage( 'diagram.drawio.tmp' ) );
+
+ $this->assertTrue( $drawIoFileHandler->isDrawIOImage( 'diagram.drawio.png' ) );
+ }
+
+ /**
+ * @covers \HalloWelt\MigrateConfluence\Utility\DrawIOFileHandler::bakeDiagramDataIntoImage
+ */
+ public function testBakeDiagramDataIntoImage() {
+ $drawIoFileHandler = new DrawIOFileHandler();
+
+ $diagramXml = file_get_contents( __DIR__ . '/data/diagram.drawio' );
+ $imageContent = file_get_contents( __DIR__ . '/data/diagram.drawio.png' );
+
+ // Get expected diagram XML
+ $matches = [];
+ preg_match( '#(.*?)#s', $diagramXml, $matches );
+
+ $expectedDiagramXML = trim( $matches[0] );
+
+ // Bake diagram XML into PNG image meta data
+ $imageContent = $drawIoFileHandler->bakeDiagramDataIntoImage( $imageContent, $diagramXml );
+
+ // Extract and check diagram XML from the PNG
+ // Extraction is done with the same algorithm how it is done in the wiki
+ $encodedXML = preg_replace(
+ '#^.*?tEXt(.*?)IDAT.*?$#s',
+ '$1',
+ $imageContent
+ );
+ $encodedXML = preg_replace( '/[[:^print:]]/', '', $encodedXML );
+ $partiallyDecodedXML = urldecode( $encodedXML );
+
+ // Get actual diagram XML after extraction from PNG
+ $matches = [];
+ preg_match( '#(.*?)#s', $partiallyDecodedXML, $matches );
+
+ $actualDiagramXML = trim( $matches[0] );
+
+ $this->assertEquals( $expectedDiagramXML, $actualDiagramXML );
+ }
+}
diff --git a/tests/phpunit/Utility/DrawIOFileHandler/data/diagram.drawio b/tests/phpunit/Utility/DrawIOFileHandler/data/diagram.drawio
new file mode 100644
index 0000000..15f2a67
--- /dev/null
+++ b/tests/phpunit/Utility/DrawIOFileHandler/data/diagram.drawio
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/phpunit/Utility/DrawIOFileHandler/data/diagram.drawio.png b/tests/phpunit/Utility/DrawIOFileHandler/data/diagram.drawio.png
new file mode 100644
index 0000000000000000000000000000000000000000..0568db31a9930390f670c788bf604d99c250fe28
GIT binary patch
literal 12087
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