From b4593fd700dec038a8a241dacc5c54615a61e5cc Mon Sep 17 00:00:00 2001 From: "d.svitak" Date: Tue, 12 May 2026 14:23:30 +0200 Subject: [PATCH 1/6] MIG-516 Improvements to design objects migration - cell option for flow to next page change --- .../builder/documentcontent/TableBuilder.kt | 4 ++++ .../api/dto/migrationmodel/documentcontent/Table.kt | 4 ++++ .../persistence/migrationmodel/DocumentContentEntity.kt | 2 ++ .../inspirebuilder/InspireDocumentObjectBuilder.kt | 3 ++- .../kotlin/com/quadient/migration/shared/TableOptions.kt | 9 +++++++++ 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/documentcontent/TableBuilder.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/documentcontent/TableBuilder.kt index 93202d9a..ae5f3040 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/documentcontent/TableBuilder.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/builder/documentcontent/TableBuilder.kt @@ -11,6 +11,7 @@ import com.quadient.migration.api.dto.migrationmodel.TableRow as TableRowModel import com.quadient.migration.shared.BorderOptions import com.quadient.migration.shared.CellAlignment import com.quadient.migration.shared.CellHeight +import com.quadient.migration.shared.CellOverflow import com.quadient.migration.shared.VariablePath import com.quadient.migration.shared.LiteralPath import com.quadient.migration.shared.Size @@ -189,12 +190,14 @@ class TableBuilder : RowBuilderBase, HasBorder { var height: CellHeight? = null override var border: BorderOptions? = null var alignment: CellAlignment? = null + var overflow: CellOverflow? = null fun mergeLeft(value: Boolean) = apply { mergeLeft = value } fun mergeUp(value: Boolean) = apply { mergeUp = value } fun heightFixed(size: Size) = apply { height = CellHeight.Fixed(size) } fun heightCustom(minHeight: Size, maxHeight: Size) = apply { height = CellHeight.Custom(minHeight, maxHeight) } fun alignment(alignment: CellAlignment) = apply { this.alignment = alignment } + fun overflow(overflow: CellOverflow) = apply { this.overflow = overflow } fun build(): Table.Cell { return Table.Cell( @@ -204,6 +207,7 @@ class TableBuilder : RowBuilderBase, HasBorder { height = height, border = border, alignment = alignment, + overflow = overflow, ) } } diff --git a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Table.kt b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Table.kt index ef630d61..eac1e67a 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Table.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/api/dto/migrationmodel/documentcontent/Table.kt @@ -4,6 +4,7 @@ import com.quadient.migration.persistence.migrationmodel.TableEntity import com.quadient.migration.shared.BorderOptions import com.quadient.migration.shared.CellAlignment import com.quadient.migration.shared.CellHeight +import com.quadient.migration.shared.CellOverflow import com.quadient.migration.shared.VariablePath import com.quadient.migration.shared.Size import com.quadient.migration.shared.TableAlignment @@ -126,6 +127,7 @@ data class Table( val height: CellHeight?, val border: BorderOptions? = null, val alignment: CellAlignment? = null, + val overflow: CellOverflow? = null, ) : RefValidatable { override fun collectRefs(): List { return content.flatMap { @@ -145,6 +147,7 @@ data class Table( height = cell.height, border = cell.border, alignment = cell.alignment, + overflow = cell.overflow, ) } } @@ -157,6 +160,7 @@ data class Table( height = height, border = border, alignment = alignment, + overflow = overflow, ) } } diff --git a/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/DocumentContentEntity.kt b/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/DocumentContentEntity.kt index 590b22b3..06d88a55 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/DocumentContentEntity.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/persistence/migrationmodel/DocumentContentEntity.kt @@ -3,6 +3,7 @@ package com.quadient.migration.persistence.migrationmodel import com.quadient.migration.shared.BorderOptions import com.quadient.migration.shared.CellAlignment import com.quadient.migration.shared.CellHeight +import com.quadient.migration.shared.CellOverflow import com.quadient.migration.shared.VariablePath import com.quadient.migration.shared.Position import com.quadient.migration.shared.Size @@ -61,6 +62,7 @@ data class TableEntity( val height: CellHeight? = null, val border: BorderOptions? = null, val alignment: CellAlignment? = null, + val overflow: CellOverflow? = null, ) @Serializable diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt index b411de8f..41cd4d51 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt @@ -50,6 +50,7 @@ import com.quadient.migration.shared.AttachmentType import com.quadient.migration.shared.BorderOptions import com.quadient.migration.shared.CellAlignment import com.quadient.migration.shared.CellHeight +import com.quadient.migration.shared.CellOverflow import com.quadient.migration.shared.Function import com.quadient.migration.shared.Group import com.quadient.migration.shared.IcmPath @@ -1119,7 +1120,7 @@ abstract class InspireDocumentObjectBuilder( } else cellContentFlow val cell = layout.addCell().setSpanLeft(cellModel.mergeLeft).setSpanUp(cellModel.mergeUp) - .setFlowToNextPage(true).setFlow(cellFlow) + .setFlowToNextPage(cellModel.overflow != CellOverflow.MoveCellToNextPage).setFlow(cellFlow) when (cellModel.height) { is CellHeight.Custom -> { diff --git a/migration-library/src/main/kotlin/com/quadient/migration/shared/TableOptions.kt b/migration-library/src/main/kotlin/com/quadient/migration/shared/TableOptions.kt index d3a7e52d..963db0e3 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/shared/TableOptions.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/shared/TableOptions.kt @@ -17,6 +17,15 @@ enum class CellAlignment { Bottom, } +@Serializable +enum class CellOverflow { + /** Overflow cell content to next page (default). Maps to {@code FlowToNextPage=True}. */ + OverflowContentToNextPage, + + /** Move cell to next page. Maps to {@code FlowToNextPage=False}. */ + MoveCellToNextPage, +} + @Serializable sealed interface CellHeight { @Serializable From c782a5ce4aad52df8a04a2149ca117eeec46d180 Mon Sep 17 00:00:00 2001 From: "d.svitak" Date: Tue, 12 May 2026 16:10:06 +0200 Subject: [PATCH 2/6] MIG-516 Improvements to design objects migration - correctly add web editing type to section flows on places where it is missing --- .../service/inspirebuilder/InspireDocumentObjectBuilder.kt | 2 +- .../inspirebuilder/InteractiveDocumentObjectBuilder.kt | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt index 41cd4d51..bd1a1577 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt @@ -406,7 +406,7 @@ abstract class InspireDocumentObjectBuilder( val singleFlow = if (this.size == 1) { this[0] } else { - val sectionFlow = layout.addFlow().setType(Flow.Type.SIMPLE).setSectionFlow(true) + val sectionFlow = layout.addFlow().setType(Flow.Type.SIMPLE).setSectionFlow(true).setWebEditingType(SECTION) flowName?.let { sectionFlow.setName(it) } val sectionFlowText = sectionFlow.addParagraph().addText() diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt index 2286cd6f..ce5563e3 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt @@ -32,6 +32,7 @@ import com.quadient.migration.shared.ImageType import com.quadient.migration.shared.orDefault import com.quadient.wfdxml.WfdXmlBuilder import com.quadient.wfdxml.api.layoutnodes.Flow +import com.quadient.wfdxml.api.layoutnodes.Flow.WebEditingType.SECTION import com.quadient.wfdxml.api.layoutnodes.Image as WfdXmlImage import com.quadient.wfdxml.api.layoutnodes.data.DataType import com.quadient.wfdxml.api.layoutnodes.data.VariableKind @@ -274,7 +275,8 @@ class InteractiveDocumentObjectBuilder( interactiveFlowsWithContent.forEach { val interactiveFlowText = - layout.addFlow().setId(it.key).setType(Flow.Type.SIMPLE).setSectionFlow(true).addParagraph().addText() + layout.addFlow().setId(it.key).setType(Flow.Type.SIMPLE).setSectionFlow(true).setWebEditingType(SECTION) + .addParagraph().addText() val flowName = if (hasMultipleFlows && it.key != mainFlowId) { val interactiveFlowName = From ba465c6724dd4a69ed3a8cb85eb8f540ca4cc09c Mon Sep 17 00:00:00 2001 From: "d.svitak" Date: Thu, 14 May 2026 08:39:01 +0200 Subject: [PATCH 3/6] MIG-516 Improvements to design objects migration - section flow created for each cell flow type expect for simple flow --- .../service/inspirebuilder/InspireDocumentObjectBuilder.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt index ff2fb916..89aa8b8f 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt @@ -1126,10 +1126,8 @@ abstract class InspireDocumentObjectBuilder( val cellContentFlow = buildDocumentContentAsSingleFlow( layout, variableStructure, cellModel.content, null, null, languages ) - val cellFlow = - if (cellContentFlow.type === Flow.Type.SELECT_BY_INLINE_CONDITION || cellContentFlow.type === Flow.Type.SELECT_BY_CONDITION) { - layout.addFlow().setType(Flow.Type.SIMPLE).setSectionFlow(true) - .setWebEditingType(SECTION) + val cellFlow = if (cellContentFlow.type !== Flow.Type.SIMPLE) { + layout.addFlow().setType(Flow.Type.SIMPLE).setSectionFlow(true).setWebEditingType(SECTION) .also { it.addParagraph().addText().appendFlow(cellContentFlow) } } else cellContentFlow From f08c033ec1305aac46fd3fba76565d37a0b1264f Mon Sep 17 00:00:00 2001 From: "d.svitak" Date: Fri, 15 May 2026 09:59:17 +0200 Subject: [PATCH 4/6] MIG-516 Improvements to design objects migration - Pages in Interactive output no longer create standalone template .jld file, but are still taken into account when distributing content to Interactive flows. - Image dimensions in Interactive can be editable even without specified height or width --- CHANGELOG.md | 5 ++ .../service/deploy/InteractiveDeployClient.kt | 14 ++-- .../InspireDocumentObjectBuilder.kt | 3 +- .../InteractiveDocumentObjectBuilder.kt | 65 ++++++++++++------- .../deploy/InteractiveDeployClientTest.kt | 58 ++++++++++++++++- .../InteractiveDocumentObjectBuilderTest.kt | 53 ++------------- .../wfdxml/api/layoutnodes/Image.java | 4 ++ .../internal/layoutnodes/ImageImpl.java | 20 +++--- .../internal/layoutnodes/ImageImplTest.groovy | 44 ++++++++++--- .../wfdxml/workflow/AllInOneUsageExample.xml | 1 - .../workflow/ImageAndLineUsageExample.xml | 1 - .../workflow/SimpleDeltaLayoutWithImage.xml | 2 - 12 files changed, 169 insertions(+), 101 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef40e606..54e955e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,14 +9,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) ### Added - Added barcode and QR code support +- Cell overflow options ### Changed - Shape (line) in Migration Model Example moved to header part of the document to avoid collision with new and dynamic content +- **Breaking** Pages in Interactive output no longer create standalone template .jld file, but are still taken into + account when distributing content to Interactive flows. ### Fixed - First match snippet in Designer output rendered as an inline first match flow to better fit its inline nature +- Wrapping cell content to block in Interactive output for specific cases +- Image dimensions in Interactive can be editable even without specified height or width ## [17.0.21] - 2026-05-07 diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/InteractiveDeployClient.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/InteractiveDeployClient.kt index 6c540f73..89d93b3b 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/InteractiveDeployClient.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/deploy/InteractiveDeployClient.kt @@ -39,6 +39,7 @@ import com.quadient.migration.service.ipsclient.OperationResult import com.quadient.migration.service.resolveTarget import com.quadient.migration.shared.IcmPath import com.quadient.migration.shared.Jrd +import com.quadient.migration.shared.DocumentObjectType import kotlin.time.Clock import org.jetbrains.exposed.v1.core.and import org.jetbrains.exposed.v1.core.eq @@ -88,14 +89,17 @@ class InteractiveDeployClient( } override fun shouldIncludeDependency(documentObject: DocumentObject): Boolean { - return documentObject.internal != true + return documentObject.type != DocumentObjectType.Page && documentObject.internal != true } override fun getAllDocumentObjectsToDeploy(): List { return documentObjectRepository.list( - DocumentObjectTable.skip.extract("skipped") eq "false" and DocumentObjectTable.internal.eq( - false - ) + (DocumentObjectTable.type inList listOf( + DocumentObjectType.Template.toString(), + DocumentObjectType.Block.toString(), + DocumentObjectType.Section.toString(), + DocumentObjectType.Snippet.toString() + ) and DocumentObjectTable.internal.eq(false) and (DocumentObjectTable.skip.extract("skipped") eq "false")) ) } @@ -127,7 +131,7 @@ class InteractiveDeployClient( } require(error.isEmpty()) { error } - return documentObjects + return documentObjects.filter { it.type != DocumentObjectType.Page } } private fun deployDisplayRules( diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt index 89aa8b8f..6560681d 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InspireDocumentObjectBuilder.kt @@ -656,7 +656,8 @@ abstract class InspireDocumentObjectBuilder( protected fun getOrBuildImage(layout: Layout, imageModel: Image, alternateText: String? = null): WfdXmlImage { val image = getImageByName(layout, imageModel.nameOrId()) ?: layout.addImage().setName(imageModel.nameOrId()) - .setImageLocation(getImagePath(imageModel).toString(), LocationType.ICM) + .setImageLocation(getImagePath(imageModel).toString(), LocationType.ICM).setUseResizeWidth(true) + .setUseResizeHeight(true) val options = imageModel.options if (options != null) { diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt index ce5563e3..676f1438 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt @@ -240,35 +240,23 @@ class InteractiveDocumentObjectBuilder( variableStructure ) - DocumentObjectType.Page -> { - documentObject.content.paragraphIfEmpty().forEach { - if (it is Area && !it.interactiveFlowName.isNullOrBlank()) { - val flowName = it.interactiveFlowName!! - val interactiveFlowId = if (flowName.startsWith("Def.")) { - flowName + else -> { + documentObject.content.paragraphIfEmpty().forEach { documentContentPart -> + if (documentContentPart is DocumentObjectRef) { + val referencedModel = documentObjectRepository.findOrFail(documentContentPart.id) + if (referencedModel.type == DocumentObjectType.Page) { + val pageBaseTemplatePath = getBaseTemplateFullPath(projectConfig, referencedModel.baseTemplate) + val pageBaseTemplateData = getOrLoadBaseTemplateData(pageBaseTemplatePath) + ?: error("Unable to deploy document object ${documentObject.id}. Base template '$pageBaseTemplatePath' for page '${referencedModel.id}' does not exist.") + mapPageContentToInteractiveFlows(referencedModel, pageBaseTemplateData, interactiveFlowsWithContent) } else { - currentBaseTemplateData.interactiveFlowNamesToIds[flowName] - } - - if (interactiveFlowId.isNullOrBlank()) { - val errorMessage = - "Failed to find interactive flow '$flowName' in base template '$baseTemplatePath'." - logger.error(errorMessage) - error(errorMessage) + interactiveFlowsWithContent.getOrPut(mainFlowId) { mutableListOf() }.add(documentContentPart) } - - val interactiveFlowContent = - interactiveFlowsWithContent.getOrPut(interactiveFlowId) { mutableListOf() } - interactiveFlowContent.addAll(it.content) } else { - val interactiveFlowContent = interactiveFlowsWithContent.getOrPut(mainFlowId) { mutableListOf() } - interactiveFlowContent.add(it) + interactiveFlowsWithContent.getOrPut(mainFlowId) { mutableListOf() }.add(documentContentPart) } } } - else -> { - interactiveFlowsWithContent[mainFlowId] = documentObject.content.toMutableList() - } } val hasMultipleFlows = interactiveFlowsWithContent.size > 1 @@ -332,7 +320,7 @@ class InteractiveDocumentObjectBuilder( } override fun shouldIncludeInternalDependency(documentObject: DocumentObject): Boolean { - return documentObject.internal == true + return documentObject.internal == true || documentObject.type == DocumentObjectType.Page } override fun resolveParagraphStyleName(name: String): String = @@ -344,6 +332,35 @@ class InteractiveDocumentObjectBuilder( override fun resolveTableStyleName(name: String): String = styleDefinitionData?.tableStyleDisplayNamesToName?.get(name) ?: name + private fun mapPageContentToInteractiveFlows( + page: DocumentObject, + baseTemplateData: BaseTemplateData, + interactiveFlowsWithContent: MutableMap>, + ) { + val baseTemplatePath = getBaseTemplateFullPath(projectConfig, page.baseTemplate) + page.content.paragraphIfEmpty().forEach { contentItem -> + if (contentItem is Area && !contentItem.interactiveFlowName.isNullOrBlank()) { + val flowName = contentItem.interactiveFlowName!! + val interactiveFlowId = if (flowName.startsWith("Def.")) { + flowName + } else { + baseTemplateData.interactiveFlowNamesToIds[flowName] + } + + if (interactiveFlowId.isNullOrBlank()) { + val errorMessage = + "Failed to find interactive flow '$flowName' in base template '$baseTemplatePath'." + logger.error(errorMessage) + error(errorMessage) + } + + interactiveFlowsWithContent.getOrPut(interactiveFlowId) { mutableListOf() }.addAll(contentItem.content) + } else { + interactiveFlowsWithContent.getOrPut(mainFlowId) { mutableListOf() }.add(contentItem) + } + } + } + private fun getOrLoadBaseTemplateData(path: IcmPath): BaseTemplateData? { if (baseTemplateCache.containsKey(path)) return baseTemplateCache[path] diff --git a/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/InteractiveDeployClientTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/InteractiveDeployClientTest.kt index 37df8a7e..21d0ea35 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/InteractiveDeployClientTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/service/deploy/InteractiveDeployClientTest.kt @@ -115,7 +115,8 @@ class InteractiveDeployClientTest { @BeforeEach fun setupAll() { every { documentObjectBuilder.shouldIncludeInternalDependency(any()) } answers { - firstArg().internal ?: false + val documentObject = firstArg() + (documentObject.internal ?: false) || documentObject.type == DocumentObjectType.Page } every { ipsService.writeMetadata(any()) } just runs every { ipsService.setProductionApprovalState(any>()) } returns OperationResult.Success @@ -622,6 +623,61 @@ class InteractiveDeployClientTest { verify(exactly = 1) { documentObjectRepository.list(any>()) } } + @Test + fun `page objects are silently filtered from deploy list`() { + val spy = spyk(subject) + every { spy.deployDocumentObjectsInternal(any(), any(), any(), any(), any(), any()) } returns DeploymentResult( + Uuid.random() + ) + every { documentObjectRepository.list(any>()) } returns listOf( + aBlock(id = "1", type = DocumentObjectType.Block), + aBlock(id = "2", type = DocumentObjectType.Page), + aBlock(id = "3", type = DocumentObjectType.Template), + aBlock(id = "4", type = DocumentObjectType.Section), + ) + + spy.deployDocumentObjects(listOf("1", "2", "3", "4")) + + verify(exactly = 1) { documentObjectRepository.list(any>()) } + verify { + spy.deployDocumentObjectsInternal(match { docObjects -> + docObjects.size == 3 && docObjects.map { it.id }.containsAll(listOf("1", "3", "4")) + }, any(), any(), any(), any(), any()) + } + } + + @Test + fun `deploy list of document objects excludes pages but includes their transitive external dependencies`() { + val spy = spyk(subject) + every { spy.deployDocumentObjectsInternal(any(), any(), any(), any(), any(), any()) } returns DeploymentResult( + Uuid.random() + ) + + val block3 = aDocObj("block3", DocumentObjectType.Block) + val block2 = aDocObj("block2", DocumentObjectType.Block, internal = true, content = listOf(aDocumentObjectRef(block3.id))) + val block1 = aDocObj("block1", DocumentObjectType.Block) + val page = aDocObj("page1", DocumentObjectType.Page, content = listOf(aDocumentObjectRef(block1.id), aDocumentObjectRef(block2.id))) + val template = aDocObj("template1", DocumentObjectType.Template, content = listOf(aDocumentObjectRef(page.id))) + + every { documentObjectRepository.list(any>()) } returns listOf(template) + every { documentObjectRepository.findOrFail("page1") } returns page + every { documentObjectRepository.findOrFail("block1") } returns block1 + every { documentObjectRepository.findOrFail("block2") } returns block2 + every { documentObjectRepository.findOrFail("block3") } returns block3 + + spy.deployDocumentObjects(listOf("template1"), false) + + verify { + spy.deployDocumentObjectsInternal(match { docObjects -> + val ids = docObjects.map { it.id } + ids.containsAll(listOf("template1", "block1", "block3")) + && !ids.contains("page1") + && !ids.contains("block2") + }, any(), any(), any(), any(), any()) + } + } + + @Test fun `deploy list of document objects without dependencies`() { val spy = spyk(subject) diff --git a/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt index e98b48de..22eb06be 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt @@ -1022,7 +1022,7 @@ class InteractiveDocumentObjectBuilderTest { } @Test - fun `build page with multiple areas with interactive flow`() { + fun `build template with reference to page inlines page areas as interactive flows`() { val page = aDocObj( "P_1", Page, listOf( anArea(listOf(aParagraph(aText("interactive flow text 1"))), interactiveFlowName = "Logo"), @@ -1035,7 +1035,9 @@ class InteractiveDocumentObjectBuilderTest { anArea(listOf(aParagraph(aText("interactive flow text 3"))), interactiveFlowName = "Flow BT 1") ) ) + val template = aTemplate("T_1", listOf(aDocumentObjectRef(page.id))) + every { documentObjectRepository.findOrFail(page.id) } returns page every { ipsService.wfd2xml(getBaseTemplateFullPath(config, null)) } returns """ @@ -1074,7 +1076,7 @@ class InteractiveDocumentObjectBuilderTest { """.trimMargin() // when - val result = subject.buildDocumentObject(page).let { xmlMapper.readTree(it.trimIndent()) } + val result = subject.buildDocumentObject(template).let { xmlMapper.readTree(it.trimIndent()) } val mainFlow = result["Flow"].first { it["Id"].textValue() == "Def.MainFlow" } val mainFlowContentFlowId = mainFlow["FlowContent"]["P"]["T"]["O"]["Id"].textValue() @@ -1085,7 +1087,7 @@ class InteractiveDocumentObjectBuilderTest { mainFlowParagraphs[1]["T"][""].textValue().shouldBeEqualTo("main flow text 2") mainFlowParagraphs[2]["T"][""].textValue().shouldBeEqualTo("main flow text 3") result["Flow"].first { it["Id"].textValue() == mainFlowContentFlowId }["Name"].textValue() - .shouldBeEqualTo("P_1name") + .shouldBeEqualTo("T_1") val interactiveFlow = result["Flow"].first { it["Id"].textValue() == "Def.InteractiveFlow1" } val interactiveFlowContentFlowId = interactiveFlow["FlowContent"]["P"]["T"]["O"]["Id"].textValue() @@ -1096,50 +1098,7 @@ class InteractiveDocumentObjectBuilderTest { interactiveFlowParagraphs[1]["T"][""].textValue().shouldBeEqualTo("interactive flow text 2") interactiveFlowParagraphs[2]["T"][""].textValue().shouldBeEqualTo("interactive flow text 3") result["Flow"].first { it["Id"].textValue() == interactiveFlowContentFlowId }["Name"].textValue() - .shouldBeEqualTo("P_1name_Flow BT 1") - } - - @Test - fun `build page with single area with interactive flow`(){ - val page = aDocObj( - "P_1", Page, listOf( - anArea(listOf(aParagraph(aText("interactive flow text 1"))), interactiveFlowName = "Logo") - ) - ) - - every { - ipsService.wfd2xml(getBaseTemplateFullPath(config, null)) - } returns """ - - - - 79 - Letter Content - - - 80 - Flow BT 1 - {"customName":"Logo"} - - - - 80 - Normal - - - - - """.trimMargin() - - // when - val result = subject.buildDocumentObject(page).let { xmlMapper.readTree(it.trimIndent()) } - - val interactiveFlow = result["Flow"].first { it["Id"].textValue() == "Def.InteractiveFlow0" } - val interactiveFlowContentFlowId = interactiveFlow["FlowContent"]["P"]["T"]["O"]["Id"].textValue() - val interactiveFlowContentFlow = result["Flow"].last { it["Id"].textValue() == interactiveFlowContentFlowId } - interactiveFlowContentFlow["FlowContent"]["P"]["T"][""].textValue().shouldBeEqualTo("interactive flow text 1") - result["Flow"].first { it["Id"].textValue() == interactiveFlowContentFlowId }["Name"].textValue() - .shouldBeEqualTo("P_1name") + .shouldBeEqualTo("T_1_Flow BT 1") } @Test diff --git a/wfd-xml/api/src/main/java/com/quadient/wfdxml/api/layoutnodes/Image.java b/wfd-xml/api/src/main/java/com/quadient/wfdxml/api/layoutnodes/Image.java index 30cb1e81..3a35bc23 100644 --- a/wfd-xml/api/src/main/java/com/quadient/wfdxml/api/layoutnodes/Image.java +++ b/wfd-xml/api/src/main/java/com/quadient/wfdxml/api/layoutnodes/Image.java @@ -17,6 +17,10 @@ public interface Image extends Node { Image setResizeHeight(double height); + Image setUseResizeWidth(boolean useResizeWidth); + + Image setUseResizeHeight(boolean useResizeHeight); + Image setHtmlWidthAndHeight(String htmlWidth, String htmlHeight); Image setTransparentR(int min, int max); diff --git a/wfd-xml/impl/src/main/java/com/quadient/wfdxml/internal/layoutnodes/ImageImpl.java b/wfd-xml/impl/src/main/java/com/quadient/wfdxml/internal/layoutnodes/ImageImpl.java index 4e2fa274..3829dc1d 100644 --- a/wfd-xml/impl/src/main/java/com/quadient/wfdxml/internal/layoutnodes/ImageImpl.java +++ b/wfd-xml/impl/src/main/java/com/quadient/wfdxml/internal/layoutnodes/ImageImpl.java @@ -13,8 +13,8 @@ public class ImageImpl extends NodeImpl implements Image { private boolean useResizeHeight = false; private boolean makeTransparent = false; - private double resizeImageWidth = 0.0; - private double resizeImageHeight = 0.0; + private Double resizeImageWidth = null; + private Double resizeImageHeight = null; private double dpiX = 0.0; private double dpiY = 0.0; @@ -49,6 +49,7 @@ public boolean getUseResizeWidth() { return useResizeWidth; } + @Override public ImageImpl setUseResizeWidth(boolean useResizeWidth) { this.useResizeWidth = useResizeWidth; return this; @@ -58,12 +59,13 @@ public boolean getUseResizeHeight() { return useResizeHeight; } + @Override public ImageImpl setUseResizeHeight(boolean useResizeHeight) { this.useResizeHeight = useResizeHeight; return this; } - public double getResizeImageWidth() { + public Double getResizeImageWidth() { return resizeImageWidth; } @@ -72,7 +74,7 @@ public ImageImpl setResizeImageWidth(double resizeImageWidth) { return this; } - public double getResizeImageHeight() { + public Double getResizeImageHeight() { return resizeImageHeight; } @@ -290,11 +292,11 @@ public void export(XmlExporter exporter) { .addElementWithStringData("ImageLocation", imageLocation) .addElementWithDoubleData("ImageDPIX", dpiX) .addElementWithDoubleData("ImageDPIY", dpiY) - .addElementWithBoolData("UseResizeWidth", useResizeWidth) - .addElementWithDoubleData("ResizeImageWidth", resizeImageWidth) - .addElementWithBoolData("UseResizeHeight", useResizeHeight) - .addElementWithDoubleData("ResizeImageHeight", resizeImageHeight) - .addElementWithBoolData("MakeTransparent", makeTransparent) + .addElementWithBoolData("UseResizeWidth", useResizeWidth); + if (resizeImageWidth != null) exporter.addElementWithDoubleData("ResizeImageWidth", resizeImageWidth); + exporter.addElementWithBoolData("UseResizeHeight", useResizeHeight); + if (resizeImageHeight != null) exporter.addElementWithDoubleData("ResizeImageHeight", resizeImageHeight); + exporter.addElementWithBoolData("MakeTransparent", makeTransparent) .beginElement("TransparencyR") .addIntAttribute("X", transparencyRX) .addIntAttribute("Y", transparencyRY) diff --git a/wfd-xml/impl/src/test/groovy/com/quadient/wfdxml/internal/layoutnodes/ImageImplTest.groovy b/wfd-xml/impl/src/test/groovy/com/quadient/wfdxml/internal/layoutnodes/ImageImplTest.groovy index c98cfab8..2900ef98 100644 --- a/wfd-xml/impl/src/test/groovy/com/quadient/wfdxml/internal/layoutnodes/ImageImplTest.groovy +++ b/wfd-xml/impl/src/test/groovy/com/quadient/wfdxml/internal/layoutnodes/ImageImplTest.groovy @@ -25,9 +25,7 @@ class ImageImplTest extends Specification { 0.0 0.0 False - 0.0 False - 0.0 False @@ -90,6 +88,40 @@ class ImageImplTest extends Specification { """) } + def "export image with useResizeWidth and useResizeHeight set to true but no resize values"() { + given: + ImageImpl image = new ImageImpl() + .setUseResizeWidth(true) + .setUseResizeHeight(true) + + when: + image.export(exporter) + + then: + assertXmlEqualsWrapRoot(exporter.buildString(), """ + Simple + 0.0 + 0.0 + True + True + False + + + + False + + + + Figure + + + + 1 + + + """) + } + def "export image with ICM location"() { given: ImageImpl image = new ImageImpl().setImageLocation("vcs://Interactive/StandardPackage/Resources/Images/frog.png", LocationType.ICM) @@ -104,9 +136,7 @@ class ImageImplTest extends Specification { 0.0 0.0 False - 0.0 False - 0.0 False @@ -138,9 +168,7 @@ class ImageImplTest extends Specification { 0.0 0.0 False - 0.0 False - 0.0 False @@ -174,9 +202,7 @@ class ImageImplTest extends Specification { 0.0 0.0 False - 0.0 False - 0.0 False @@ -212,9 +238,7 @@ class ImageImplTest extends Specification { 0.0 0.0 False - 0.0 False - 0.0 False diff --git a/wfd-xml/impl/src/test/resources/com/quadient/wfdxml/workflow/AllInOneUsageExample.xml b/wfd-xml/impl/src/test/resources/com/quadient/wfdxml/workflow/AllInOneUsageExample.xml index 03da72fa..dfe2e2f6 100644 --- a/wfd-xml/impl/src/test/resources/com/quadient/wfdxml/workflow/AllInOneUsageExample.xml +++ b/wfd-xml/impl/src/test/resources/com/quadient/wfdxml/workflow/AllInOneUsageExample.xml @@ -2978,7 +2978,6 @@ 0.0 0.0 False - 0.0 True 0.024 False diff --git a/wfd-xml/impl/src/test/resources/com/quadient/wfdxml/workflow/ImageAndLineUsageExample.xml b/wfd-xml/impl/src/test/resources/com/quadient/wfdxml/workflow/ImageAndLineUsageExample.xml index 3e4e9b40..84f98317 100644 --- a/wfd-xml/impl/src/test/resources/com/quadient/wfdxml/workflow/ImageAndLineUsageExample.xml +++ b/wfd-xml/impl/src/test/resources/com/quadient/wfdxml/workflow/ImageAndLineUsageExample.xml @@ -459,7 +459,6 @@ 0.0 0.0 False - 0.0 True 0.024 False diff --git a/wfd-xml/impl/src/test/resources/com/quadient/wfdxml/workflow/SimpleDeltaLayoutWithImage.xml b/wfd-xml/impl/src/test/resources/com/quadient/wfdxml/workflow/SimpleDeltaLayoutWithImage.xml index 1a2a3eb5..722662c2 100644 --- a/wfd-xml/impl/src/test/resources/com/quadient/wfdxml/workflow/SimpleDeltaLayoutWithImage.xml +++ b/wfd-xml/impl/src/test/resources/com/quadient/wfdxml/workflow/SimpleDeltaLayoutWithImage.xml @@ -35,9 +35,7 @@ 0.0 0.0 False - 0.0 False - 0.0 False From 7a97802c2e9f887806d927bdce5fb32ccaaeb60e Mon Sep 17 00:00:00 2001 From: "d.svitak" Date: Fri, 15 May 2026 16:13:48 +0200 Subject: [PATCH 5/6] MIG-516 Improvements to design objects migration - Templates also work directly with areas in its content. --- CHANGELOG.md | 2 +- .../example/common/mapping/AreasExport.groovy | 11 +- .../example/common/mapping/AreasImport.groovy | 31 +- .../src/test/groovy/AreasExportTest.groovy | 23 +- .../src/test/groovy/AreasImportTest.groovy | 26 + .../InteractiveDocumentObjectBuilder.kt | 47 +- .../InteractiveDocumentObjectBuilderTest.kt | 481 +++++++----------- 7 files changed, 267 insertions(+), 354 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54e955e9..e5e2820b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) - Shape (line) in Migration Model Example moved to header part of the document to avoid collision with new and dynamic content - **Breaking** Pages in Interactive output no longer create standalone template .jld file, but are still taken into - account when distributing content to Interactive flows. + account when distributing content to Interactive flows. Templates also work directly with areas in its content. ### Fixed diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasExport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasExport.groovy index ccbca381..a5faf85a 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasExport.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasExport.groovy @@ -38,7 +38,7 @@ static void run(Migration migration, Path path) { areasFile.withWriter { writer -> def headers = [ - Mapping.displayHeader("templateId", true), + Mapping.displayHeader("templateId", false), Mapping.displayHeader("templateName", true), Mapping.displayHeader("pageId", false), Mapping.displayHeader("pageName", true), @@ -63,6 +63,11 @@ static void run(Migration migration, Path path) { writer.writeLine(buildArea(migration, idx, area, page, template)) } } + + def directAreas = template.content.findAll { it instanceof Area } as List + directAreas.eachWithIndex { area, idx -> + writer.writeLine(buildArea(migration, idx, area, null, template)) + } } if (usedPageIds.size() != pageIds.size()) { @@ -83,8 +88,8 @@ static String buildArea(Migration migration, Number idx, Area area, DocumentObje def builder = new StringBuilder() builder.append(Csv.serialize(template?.id) + ",") builder.append(Csv.serialize(template?.name) + ",") - builder.append(Csv.serialize(page.id) + ",") - builder.append(Csv.serialize(page.name) + ",") + builder.append(Csv.serialize(page?.id) + ",") + builder.append(Csv.serialize(page?.name) + ",") builder.append(Csv.serialize(area.interactiveFlowName) + ",") builder.append(Csv.serialize(area.flowToNextPage) + ",") builder.append(Csv.serialize(area.position.x) + ",") diff --git a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasImport.groovy b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasImport.groovy index e756eca9..6ceafa9d 100644 --- a/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasImport.groovy +++ b/migration-examples/src/main/groovy/com/quadient/migration/example/common/mapping/AreasImport.groovy @@ -1,13 +1,12 @@ //! --- //! displayName: Import Areas //! category: Mapping -//! description: Import areas with modified interactive flow names to their respective pages +//! description: Import areas with modified interactive flow names to their respective pages and templates //! target: gradle //! --- package com.quadient.migration.example.common.mapping import com.quadient.migration.api.Migration -import com.quadient.migration.api.dto.migrationmodel.Area import com.quadient.migration.api.dto.migrationmodel.DocumentObject import com.quadient.migration.api.dto.migrationmodel.MappingItem import com.quadient.migration.example.common.util.Csv @@ -26,27 +25,29 @@ static void run(Migration migration, Path path) { def fileLines = path.toFile().readLines() def columnNames = Csv.parseColumnNames(fileLines.removeFirst()).collect { Mapping.normalizeHeader(it) } - DocumentObject currentPage = null + DocumentObject currentDocumentObject = null MappingItem.Area mapping = null int areaIndex = 0 for (line in fileLines) { def values = Csv.getCells(line, columnNames) def pageId = Csv.deserialize(values.get("pageId"), String.class) + def templateId = Csv.deserialize(values.get("templateId"), String.class) + def documentObjectId = pageId ?: templateId - if (currentPage?.id != pageId) { - if (currentPage != null) { - migration.mappingRepository.upsert(currentPage.id, mapping) - migration.mappingRepository.applyAreaMapping(currentPage.id) + if (currentDocumentObject?.id != documentObjectId) { + if (currentDocumentObject != null) { + migration.mappingRepository.upsert(currentDocumentObject.id, mapping) + migration.mappingRepository.applyAreaMapping(currentDocumentObject.id) } - def pageModel = migration.documentObjectRepository.find(pageId) - if (!pageModel) { - throw new IllegalStateException("Page '${pageId}' not found.") + def documentObjectModel = migration.documentObjectRepository.find(documentObjectId) + if (!documentObjectModel) { + throw new IllegalStateException("Document object '${documentObjectId}' not found.") } - mapping = migration.mappingRepository.getAreaMapping(pageId) - currentPage = pageModel + mapping = migration.mappingRepository.getAreaMapping(documentObjectId) + currentDocumentObject = documentObjectModel areaIndex = 0 } @@ -59,8 +60,8 @@ static void run(Migration migration, Path path) { areaIndex++ } - if (currentPage != null) { - migration.mappingRepository.upsert(currentPage.id, mapping) - migration.mappingRepository.applyAreaMapping(currentPage.id) + if (currentDocumentObject != null) { + migration.mappingRepository.upsert(currentDocumentObject.id, mapping) + migration.mappingRepository.applyAreaMapping(currentDocumentObject.id) } } diff --git a/migration-examples/src/test/groovy/AreasExportTest.groovy b/migration-examples/src/test/groovy/AreasExportTest.groovy index 0e5ea345..ba4a53c5 100644 --- a/migration-examples/src/test/groovy/AreasExportTest.groovy +++ b/migration-examples/src/test/groovy/AreasExportTest.groovy @@ -45,7 +45,7 @@ class AreasExportTest { AreasExport.run(migration, mappingFile) def expected = """\ - templateId (read-only),templateName (read-only),pageId,pageName (read-only),interactiveFlowName,flowToNextPage,x (read-only),y (read-only),width (read-only),height (read-only),contentPreview (read-only) + templateId,templateName (read-only),pageId,pageName (read-only),interactiveFlowName,flowToNextPage,x (read-only),y (read-only),width (read-only),height (read-only),contentPreview (read-only) full tmpl,,full page,,test flow2,false,0mm,0mm,0mm,0mm, full tmpl,,full page,,test flow3,true,0mm,0mm,0mm,0mm, full tmpl,,full page,,,false,0mm,0mm,0mm,0mm, @@ -55,6 +55,27 @@ class AreasExportTest { Assertions.assertEquals(expected, mappingFile.toFile().text.replaceAll("\\r\\n|\\r", "\n")) } + @Test + void exportTemplateDirectAreas() { + Path mappingFile = Paths.get(dir.path, "testProject.csv") + when(migration.mappingRepository.getAreaMapping(any())).thenReturn(new MappingItem.Area(null, [:], [:])) + when((migration.documentObjectRepository as DocumentObjectRepository).list(any())).thenReturn([ + new DocumentObjectBuilder("tmpl with areas", DocumentObjectType.Template) + .content([createArea("Address Content"), createArea(null, true), createArea("Footer")]) + .build(), + ]) + + AreasExport.run(migration, mappingFile) + + def expected = """\ + templateId,templateName (read-only),pageId,pageName (read-only),interactiveFlowName,flowToNextPage,x (read-only),y (read-only),width (read-only),height (read-only),contentPreview (read-only) + tmpl with areas,,,,Address Content,false,0mm,0mm,0mm,0mm, + tmpl with areas,,,,,true,0mm,0mm,0mm,0mm, + tmpl with areas,,,,Footer,false,0mm,0mm,0mm,0mm, + """.stripIndent() + Assertions.assertEquals(expected, mappingFile.toFile().text.replaceAll("\\r\\n|\\r", "\n")) + } + static Area createArea(String flowName, Boolean flowToNextPage = false) { def areaBuilder = new AreaBuilder() .position(new Position(Size.ofMillimeters(0), Size.ofMillimeters(0), Size.ofMillimeters(0), Size.ofMillimeters(0))) diff --git a/migration-examples/src/test/groovy/AreasImportTest.groovy b/migration-examples/src/test/groovy/AreasImportTest.groovy index 798b24f3..a660e091 100644 --- a/migration-examples/src/test/groovy/AreasImportTest.groovy +++ b/migration-examples/src/test/groovy/AreasImportTest.groovy @@ -63,6 +63,32 @@ class AreasImportTest { verify(migration.mappingRepository).applyAreaMapping("page3") } + @Test + void importTemplateDirectAreas() { + Path mappingFile = Paths.get(dir.path, "testProject.csv") + + when(migration.mappingRepository.getAreaMapping("tmpl1")).thenReturn(new MappingItem.Area(null, [:], [:])) + + when(migration.documentObjectRepository.find("tmpl1")).thenReturn( + new DocumentObjectBuilder("tmpl1", DocumentObjectType.Template) + .content([createArea("Address Content", false), createArea(null, false), createArea("Footer", false)]) + .build() + ) + + def input = """\ + templateId,templateName,pageId,pageName,interactiveFlowName,flowToNextPage,x,y,width,height,contentPreview + tmpl1,,,,Updated Address,true,0.0mm,0.0mm,0.0mm,0.0mm, + tmpl1,,,,New Header,false,0.0mm,0.0mm,0.0mm,0.0mm, + tmpl1,,,,Footer,true,0.0mm,0.0mm,0.0mm,0.0mm, + """.stripIndent() + mappingFile.toFile().write(input) + + AreasImport.run(migration, mappingFile) + + verify(migration.mappingRepository).upsert("tmpl1", new MappingItem.Area(null, [0: "Updated Address", 1: "New Header", 2: "Footer"], [0: true, 1: false, 2: true])) + verify(migration.mappingRepository).applyAreaMapping("tmpl1") + } + static Area createArea(String flowName, boolean flowToNextPage) { def areaBuilder = new AreaBuilder() .position(new Position(Size.ofMillimeters(0), Size.ofMillimeters(0), Size.ofMillimeters(0), Size.ofMillimeters(0))) diff --git a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt index 676f1438..f0b152f0 100644 --- a/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt +++ b/migration-library/src/main/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilder.kt @@ -245,15 +245,14 @@ class InteractiveDocumentObjectBuilder( if (documentContentPart is DocumentObjectRef) { val referencedModel = documentObjectRepository.findOrFail(documentContentPart.id) if (referencedModel.type == DocumentObjectType.Page) { - val pageBaseTemplatePath = getBaseTemplateFullPath(projectConfig, referencedModel.baseTemplate) - val pageBaseTemplateData = getOrLoadBaseTemplateData(pageBaseTemplatePath) - ?: error("Unable to deploy document object ${documentObject.id}. Base template '$pageBaseTemplatePath' for page '${referencedModel.id}' does not exist.") - mapPageContentToInteractiveFlows(referencedModel, pageBaseTemplateData, interactiveFlowsWithContent) + referencedModel.content.paragraphIfEmpty().forEach { pageContentPart -> + mapContentItemToInteractiveFlow(pageContentPart, currentBaseTemplateData, interactiveFlowsWithContent) + } } else { interactiveFlowsWithContent.getOrPut(mainFlowId) { mutableListOf() }.add(documentContentPart) } } else { - interactiveFlowsWithContent.getOrPut(mainFlowId) { mutableListOf() }.add(documentContentPart) + mapContentItemToInteractiveFlow(documentContentPart, currentBaseTemplateData, interactiveFlowsWithContent) } } } @@ -332,32 +331,28 @@ class InteractiveDocumentObjectBuilder( override fun resolveTableStyleName(name: String): String = styleDefinitionData?.tableStyleDisplayNamesToName?.get(name) ?: name - private fun mapPageContentToInteractiveFlows( - page: DocumentObject, + private fun mapContentItemToInteractiveFlow( + contentItem: DocumentContent, baseTemplateData: BaseTemplateData, interactiveFlowsWithContent: MutableMap>, ) { - val baseTemplatePath = getBaseTemplateFullPath(projectConfig, page.baseTemplate) - page.content.paragraphIfEmpty().forEach { contentItem -> - if (contentItem is Area && !contentItem.interactiveFlowName.isNullOrBlank()) { - val flowName = contentItem.interactiveFlowName!! - val interactiveFlowId = if (flowName.startsWith("Def.")) { - flowName - } else { - baseTemplateData.interactiveFlowNamesToIds[flowName] - } - - if (interactiveFlowId.isNullOrBlank()) { - val errorMessage = - "Failed to find interactive flow '$flowName' in base template '$baseTemplatePath'." - logger.error(errorMessage) - error(errorMessage) - } - - interactiveFlowsWithContent.getOrPut(interactiveFlowId) { mutableListOf() }.addAll(contentItem.content) + if (contentItem is Area && !contentItem.interactiveFlowName.isNullOrBlank()) { + val flowName = contentItem.interactiveFlowName!! + val interactiveFlowId = if (flowName.startsWith("Def.")) { + flowName } else { - interactiveFlowsWithContent.getOrPut(mainFlowId) { mutableListOf() }.add(contentItem) + baseTemplateData.interactiveFlowNamesToIds[flowName] } + + if (interactiveFlowId.isNullOrBlank()) { + val errorMessage = "Failed to find interactive flow '$flowName' in the base template." + logger.error(errorMessage) + error(errorMessage) + } + + interactiveFlowsWithContent.getOrPut(interactiveFlowId) { mutableListOf() }.addAll(contentItem.content) + } else { + interactiveFlowsWithContent.getOrPut(mainFlowId) { mutableListOf() }.add(contentItem) } } diff --git a/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt index 22eb06be..67e928a8 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt @@ -5,16 +5,13 @@ import com.quadient.migration.api.InspireOutput import com.quadient.migration.api.PathsConfig import com.quadient.migration.api.ProjectConfig import com.quadient.migration.api.dto.migrationmodel.DisplayRule -import com.quadient.migration.api.dto.migrationmodel.DisplayRuleRef +import com.quadient.migration.api.dto.migrationmodel.DocumentContent import com.quadient.migration.api.dto.migrationmodel.DocumentObject -import com.quadient.migration.api.dto.migrationmodel.FirstMatch import com.quadient.migration.api.dto.migrationmodel.Image import com.quadient.migration.api.dto.migrationmodel.ImageRef import com.quadient.migration.api.dto.migrationmodel.ParagraphStyle -import com.quadient.migration.api.dto.migrationmodel.ParagraphStyleRef import com.quadient.migration.api.dto.migrationmodel.StringValue import com.quadient.migration.api.dto.migrationmodel.Tab -import com.quadient.migration.api.dto.migrationmodel.Table import com.quadient.migration.api.dto.migrationmodel.Tabs import com.quadient.migration.api.dto.migrationmodel.TextStyle import com.quadient.migration.api.dto.migrationmodel.Variable @@ -22,10 +19,13 @@ import com.quadient.migration.api.dto.migrationmodel.VariableRef import com.quadient.migration.api.dto.migrationmodel.VariableStructure import com.quadient.migration.api.dto.migrationmodel.builder.DisplayRuleBuilder import com.quadient.migration.api.dto.migrationmodel.builder.DocumentObjectBuilder +import com.quadient.migration.api.dto.migrationmodel.builder.ImageBuilder +import com.quadient.migration.api.dto.migrationmodel.builder.ParagraphBuilder import com.quadient.migration.api.dto.migrationmodel.builder.ParagraphStyleBuilder import com.quadient.migration.api.dto.migrationmodel.builder.TextStyleBuilder import com.quadient.migration.api.dto.migrationmodel.builder.VariableBuilder import com.quadient.migration.api.dto.migrationmodel.builder.VariableStructureBuilder +import com.quadient.migration.api.dto.migrationmodel.builder.documentcontent.AreaBuilder import com.quadient.migration.api.repository.AttachmentRepository import com.quadient.migration.api.repository.DisplayRuleRepository import com.quadient.migration.api.repository.DocumentObjectRepository @@ -39,6 +39,7 @@ import com.quadient.migration.service.ipsclient.IpsService import com.quadient.migration.shared.Alignment import com.quadient.migration.shared.BinOp.* import com.quadient.migration.shared.DataType +import com.quadient.migration.shared.DocumentObjectType import com.quadient.migration.shared.DocumentObjectType.* import com.quadient.migration.shared.AttachmentType import com.quadient.migration.shared.IcmPath @@ -50,20 +51,18 @@ import com.quadient.migration.shared.LiteralDataType import com.quadient.migration.shared.Size import com.quadient.migration.shared.SkipOptions import com.quadient.migration.shared.TabType -import com.quadient.migration.shared.TableAlignment import com.quadient.migration.shared.VariablePathData +import com.quadient.migration.shared.centimeters +import com.quadient.migration.shared.millimeters import com.quadient.migration.shared.toIcmPath -import com.quadient.migration.tools.aCell import com.quadient.migration.tools.model.aBlock import com.quadient.migration.tools.model.aDisplayRule import com.quadient.migration.tools.model.aParagraph import com.quadient.migration.tools.aProjectConfig -import com.quadient.migration.tools.model.aCell import com.quadient.migration.tools.model.aDocObj import com.quadient.migration.tools.model.aDocumentObjectRef import com.quadient.migration.tools.model.aAttachment import com.quadient.migration.tools.model.aImage -import com.quadient.migration.tools.model.aRow import com.quadient.migration.tools.model.aSelectByLanguage import com.quadient.migration.tools.model.aTemplate import com.quadient.migration.tools.model.aText @@ -71,7 +70,6 @@ import com.quadient.migration.tools.model.aTextDef import com.quadient.migration.tools.model.aTextStyle import com.quadient.migration.tools.model.aVariable import com.quadient.migration.tools.model.aVariableStructure -import com.quadient.migration.tools.model.anArea import com.quadient.migration.tools.shouldBeEqualTo import com.quadient.migration.tools.shouldBeNull import com.quadient.migration.tools.shouldBeOfSize @@ -116,11 +114,9 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `build of simple block with string and var ref that is not part of structure results in single flow with one paragraph and text`() { // given - val variable = aVariable("var1", "varName1") - val block = aBlock( - "1", listOf(aParagraph(aText(listOf(StringValue("some text"), VariableRef(variable.id))))) - ) - every { variableRepository.findOrFail(eq(variable.id)) } returns variable + val variable = VariableBuilder("var1").name("varName1").dataType(DataType.String).build().mock() + val block = + DocumentObjectBuilder("1", Block).paragraph { text { string("some text").variableRef(variable) } }.build() // when val result = subject.buildDocumentObject(block).let { xmlMapper.readTree(it.trimIndent()) } @@ -145,9 +141,11 @@ class InteractiveDocumentObjectBuilderTest { .build() .mock() - val block = aBlock( - "1", listOf(aParagraph(aText(StringValue("some text"), textStyle.id), paraStyle.id)) - ) + val block = DocumentObjectBuilder("1", DocumentObjectType.Block).paragraph { + styleRef(paraStyle).text { + styleRef(textStyle).string("some text") + } + }.build() // when val result = subject.buildDocumentObject(block).let { xmlMapper.readTree(it.trimIndent()) } @@ -227,10 +225,8 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `build template with reference to block results in direct external flow`() { // given - val block = aBlock("1", listOf(aParagraph(aText(StringValue("Hello"))))) - val template = aTemplate("2", listOf(aDocumentObjectRef(block.id))) - - every { documentObjectRepository.findOrFail(block.id) } returns block + val block = DocumentObjectBuilder("1", Block).string("Hello").build().mock() + val template = DocumentObjectBuilder("2", Template).documentObjectRef(block).build() // when val result = subject.buildDocumentObject(template).let { xmlMapper.readTree(it.trimIndent()) } @@ -252,47 +248,25 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `build block with table has correctly promoted column widths and spans`() { // given - val variable = aVariable("clientName") - val block = aBlock( - "1", listOf( - Table( - listOf( - aRow( - listOf( - aCell( - aParagraph( - aText( - listOf( - StringValue("Hello "), - VariableRef(variable.id), - StringValue(" how are you?") - ) - ) - ) - ), aCell(aParagraph(aText(StringValue("First row, second cell ")))) - ) - ), aRow( - listOf( - aCell(aParagraph(aText(StringValue("Second row, first cell")))), - aCell(aParagraph(aText(StringValue("Second row, second cell"))), mergeLeft = true) - ) - ) - ), columnWidths = listOf( - Table.ColumnWidth(Size.ofMillimeters(150), 10.0), - Table.ColumnWidth(Size.ofCentimeters(3), 1.0) - ), - firstHeader = emptyList(), - footer = emptyList(), - lastFooter = emptyList(), - minWidth = null, - maxWidth = null, - percentWidth = null, - border = null, - alignment = TableAlignment.Left, - ) - ) - ) - every { variableRepository.findOrFail(eq(variable.id)) } returns variable + val variable = VariableBuilder("clientName").dataType(DataType.String).build().mock() + val block = DocumentObjectBuilder("1", Block).table { + addRow { + addCell { + paragraph { + text { + string("Hello ").variableRef(variable).string(" how are you?") + } + } + } + addCell { string("First row, second cell") } + } + addRow { + addCell { string("Second row, first cell") } + addCell { string("Second row, second cell").mergeLeft(true) } + } + addColumnWidth(150.millimeters(), 10.0) + addColumnWidth(3.centimeters(), 1.0) + }.build() // when val result = subject.buildDocumentObject(block).let { xmlMapper.readTree(it.trimIndent()) } @@ -321,21 +295,12 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `build of more nested and complex structure is correctly wrapped into section if required`() { // given - val externalBlock = aBlock("1", listOf(aParagraph(aText(StringValue("Hello"))))) - val block = aBlock("2", listOf(aParagraph(aText(StringValue("Sir")))), internal = true) - - val section = aBlock( - "5", type = Section, content = listOf( - aDocumentObjectRef(externalBlock.id), - aParagraph(aText(StringValue("In between"))), - aDocumentObjectRef(block.id) - ), internal = true - ) - val template = aTemplate("10", listOf(aDocumentObjectRef(section.id))) + val externalBlock = DocumentObjectBuilder("1", Block).string("Hello").build().mock() + val block = DocumentObjectBuilder("2", Block).string("Sir").internal(true).build().mock() - every { documentObjectRepository.findOrFail(externalBlock.id) } returns externalBlock - every { documentObjectRepository.findOrFail(block.id) } returns block - every { documentObjectRepository.findOrFail(section.id) } returns section + val section = DocumentObjectBuilder("5", Section).documentObjectRef(externalBlock).string("In between") + .documentObjectRef(block).internal(true).build().mock() + val template = DocumentObjectBuilder("10", Template).documentObjectRef(section).build() // when val result = subject.buildDocumentObject(template).let { xmlMapper.readTree(it.trimIndent()) } @@ -373,29 +338,25 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `build block with variable refs in variable structure with converted value based on type`() { // given - val longVar = aVariable("var1", "varName1", dataType = DataType.Integer64, defaultValue = "2025").mock() - val currencyVar = aVariable("var2", "varName2", dataType = DataType.Currency, defaultValue = "249.99").mock() - val boolVar = aVariable("var3", "varName3", dataType = DataType.Boolean, defaultValue = "TRUE").mock() - val variableStructure = aVariableStructure( - structure = mapOf( - longVar.id to VariablePathData("Data.Clients.Value"), - currencyVar.id to VariablePathData("Data.Clients.Value", "Money"), - boolVar.id to VariablePathData("Data.Clients.Value") - ) - ).mock() + val longVar = + VariableBuilder("var1").name("varName1").dataType(DataType.Integer64).defaultValue("2025").build().mock() + val currencyVar = + VariableBuilder("var2").name("varName2").dataType(DataType.Currency).defaultValue("249.99").build().mock() + val boolVar = + VariableBuilder("var3").name("varName3").dataType(DataType.Boolean).defaultValue("TRUE").build().mock() + val variableStructure = VariableStructureBuilder("vs1").addVariable(longVar.id, "Data.Clients.Value") + .addVariable(currencyVar.id, "Data.Clients.Value", "Money").addVariable(boolVar.id, "Data.Clients.Value") + .build().mock() + val config = aProjectConfig(defaultVariableStructure = variableStructure.id) - val block = aBlock( - "1", listOf( - aParagraph( - aText( - listOf( - VariableRef(longVar.id), VariableRef(currencyVar.id), VariableRef(boolVar.id) - ) - ) - ) - ) - ) + val block = DocumentObjectBuilder("1", Block).paragraph { + text { + variableRef(longVar) + variableRef(currencyVar) + variableRef(boolVar) + } + }.build() // when val subject = aSubject(config) @@ -435,14 +396,10 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `build template with external block using display rule wraps the block in condition flow`() { // given - val displayRule = - aDisplayRule(Literal("A", LiteralDataType.String), Equals, Literal("B", LiteralDataType.String)) - - val block = aBlock("1", listOf(aParagraph(aText(StringValue("Hello"))))) - val template = aTemplate("2", listOf(aDocumentObjectRef(block.id, displayRule.id))) + val displayRule = DisplayRuleBuilder("R_1").comparison { value("A").equals().value("B") }.build().mock() - every { documentObjectRepository.findOrFail(block.id) } returns block - every { displayRuleRepository.findOrFail(displayRule.id) } returns displayRule + val block = DocumentObjectBuilder("1", Block).string("Hello").build().mock() + val template = DocumentObjectBuilder("2", Template).documentObjectRef(block, displayRule).build() // when val result = subject.buildDocumentObject(template).let { xmlMapper.readTree(it.trimIndent()) } @@ -528,10 +485,8 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `build block with styled paragraph under display rule and one of its texts under display rule wraps it in multiple condition flows`() { // given - val variable = aVariable("V1", "900_MailCount").mock() - val variableStructure = aVariableStructure( - structure = mapOf(variable.id to VariablePathData("Data.1.Value")) - ).mock() + val variable = VariableBuilder("V1").name("900_MailCount").dataType(DataType.String).build().mock() + val variableStructure = VariableStructureBuilder("VS1").addVariable(variable.id, "Data.1.Value").build().mock() val config = aProjectConfig(defaultVariableStructure = variableStructure.id) val textStyle = TextStyleBuilder("T1").definition { bold(true) }.build().mock() @@ -540,32 +495,18 @@ class InteractiveDocumentObjectBuilderTest { .build() .mock() - val paraDisplayRule = aDisplayRule( - Literal(variable.id, LiteralDataType.Variable), - GreaterOrEqualThan, - Literal("540", LiteralDataType.Number), - id = "R_para" - ) - val textDisplayRule = aDisplayRule( - Literal("A", LiteralDataType.String), NotEquals, Literal("B", LiteralDataType.String), id = "R_text" - ) - - val block = aBlock( - "1", listOf( - aParagraph( - listOf( - aText( - StringValue("This is") - ), aText( - StringValue("Preposterous!"), textStyle.id, DisplayRuleRef(textDisplayRule.id) - ) - ), ParagraphStyleRef(paraStyle.id), DisplayRuleRef(paraDisplayRule.id) - ) - ) - ).mock() + val paraDisplayRule = + DisplayRuleBuilder("R_para").comparison { variable(variable).greaterOrEqualThan().value(540.0) }.build() + .mock() + val textDisplayRule = + DisplayRuleBuilder("R_text").comparison { value("A").notEquals().value("B") }.build().mock() - every { displayRuleRepository.findOrFail(paraDisplayRule.id) } returns paraDisplayRule - every { displayRuleRepository.findOrFail(textDisplayRule.id) } returns textDisplayRule + val block = DocumentObjectBuilder("1", Block).paragraph { + text { string("This is") } + text { + string("Preposterous!").styleRef(textStyle).displayRuleRef(textDisplayRule) + }.styleRef(paraStyle).displayRuleRef(paraDisplayRule) + }.build().mock() // when val subject = aSubject(config) @@ -584,7 +525,7 @@ class InteractiveDocumentObjectBuilderTest { val conditionFlow = result["Flow"].last { it["Id"].textValue() == conditionFlowRef } conditionFlow["Type"].textValue().shouldBeEqualTo("InlCond") val condition = conditionFlow["Condition"] - condition["Value"].textValue().shouldBeEqualTo("return (DATA._1.Current._900_MailCount>=540);") + condition["Value"].textValue().shouldBeEqualTo("return (DATA._1.Current._900_MailCount>=540.0);") val paraFlowRef = condition[""].textValue() val paraFlow = result["Flow"].last { it["Id"].textValue() == paraFlowRef } @@ -612,41 +553,24 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `build block with table that has one row under display rule`() { // given - val displayRule = aDisplayRule( - Literal("TRUE", LiteralDataType.Boolean), Equals, Literal("FalSe", LiteralDataType.Boolean) - ) - - val block = aBlock( - "1", listOf( - Table( - listOf( - aRow( - listOf( - aCell(listOf(aParagraph(aText(StringValue("First row, first cell"))))), - aCell(listOf(aParagraph(aText(StringValue("First row, second cell"))))) - ) - ), aRow( - listOf( - aCell(listOf(aParagraph(aText(StringValue("Second row, first cell"))))), - aCell(listOf(aParagraph(aText(StringValue("Second row, second cell"))))) - ), displayRule.id - ) - ), - columnWidths = listOf(), - firstHeader = emptyList(), - footer = emptyList(), - lastFooter = emptyList(), - minWidth = null, - maxWidth = null, - percentWidth = null, - border = null, - alignment = TableAlignment.Left, - ) - ) - ) + val displayRule = DisplayRuleBuilder("R_1").comparison { + value(true).equals().value(false) + }.build().mock() + + val block = DocumentObjectBuilder("1", Block).table { + addRow { + addCell { string("First row, first cell") } + addCell { string("First row, second cell") } + } + addRow { + addCell { string("Second row, first cell") } + addCell { string("Second row, second cell") } + displayRuleRef(displayRule) + } + }.build().mock() - every { displayRuleRepository.findOrFail(displayRule.id) } returns displayRule - every { documentObjectRepository.find(block.id) } returns block + val config = aProjectConfig(defaultVariableStructure = null) + val subject = aSubject(config) // when val result = subject.buildDocumentObject(block).let { xmlMapper.readTree(it.trimIndent()) } @@ -669,9 +593,8 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `build of template skips unsupported block and template is created without it`() { // given - val unsupportedBlock = aBlock("1", skip = SkipOptions(true, null, null)) - every { documentObjectRepository.findOrFail("block1") } returns unsupportedBlock - val template = aTemplate("10", listOf(aDocumentObjectRef("block1"))) + val unsupportedBlock = DocumentObjectBuilder("1", Block).skip().build().mock() + val template = DocumentObjectBuilder("10", Template).documentObjectRef(unsupportedBlock).build() // when val result = subject.buildDocumentObject(template) @@ -686,7 +609,7 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `build block with variable ref that is not found throws exception`() { // given - val block = aBlock("1", listOf(aParagraph(aText(VariableRef("var1"))))) + val block = DocumentObjectBuilder("1", Block).variableRef("var1").build() every { variableRepository.findOrFail("var1") } throws IllegalStateException("Record 'var1' not found.") // when @@ -699,20 +622,14 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `build template with block using display rule with unmapped variable creates dummy display rule`() { // given - val variable = aVariable("V_999", "DollarsInBank", dataType = DataType.Currency).mock() + val variable = VariableBuilder("V_999").name("DollarsInBank").dataType(DataType.Currency).build().mock() - val displayRule = aDisplayRule( - Literal(variable.id, LiteralDataType.Variable), - GreaterOrEqualThan, - Literal("25000", LiteralDataType.Number), - negation = true - ).mock() + val displayRule = DisplayRuleBuilder("R_1").group { + comparison { variable(variable).greaterOrEqualThan().value(25000.0) }.negate() + }.build().mock() - val block = aBlock("1", listOf(aParagraph(aText(StringValue("Hello"))))).mock() - val template = aTemplate("2", listOf(aDocumentObjectRef(block.id, displayRule.id))) - - every { variableRepository.find(variable.id) } returns variable - every { variableStructureRepository.listAll() } returns listOf() + val block = DocumentObjectBuilder("1", Block).string("Hello").build().mock() + val template = DocumentObjectBuilder("2", Template).documentObjectRef(block, displayRule).build() // when val result = subject.buildDocumentObject(template).let { xmlMapper.readTree(it.trimIndent()) } @@ -720,43 +637,26 @@ class InteractiveDocumentObjectBuilderTest { // then val conditionVariable = result["Variable"].last { it["Type"]?.textValue() == "Calculated" } conditionVariable["Script"].textValue() - .shouldBeEqualTo("return not (String('DollarsInBank>=25000')==String('unmapped'));") + .shouldBeEqualTo("return not (String('DollarsInBank>=25000.0')==String('unmapped'));") } @Test fun `build block with paragraph containing strings and document object refs inside one paragraph text`() { // given - val variable = aVariable("V_100", "Salutation") + val variable = VariableBuilder("V_100").name("Salutation").dataType(DataType.String).build().mock() - val innerBlock = aBlock( - "1", listOf( - aParagraph( - aText(listOf(StringValue("Dear "), VariableRef(variable.id))) - ) - ), internal = true - ) + val innerBlock = DocumentObjectBuilder("1", Block).paragraph { + text { string("Dear ").variableRef(variable) } + }.internal(true).build().mock() - val externalBlock = aBlock("2", listOf(aParagraph(aText(StringValue("This is a good day!"))))) + val externalBlock = DocumentObjectBuilder("2", Block).string("This is a good day!").build().mock() - val block = aBlock( - "10", listOf( - aParagraph( - aText( - listOf( - StringValue("Hello, "), - aDocumentObjectRef(innerBlock.id), - StringValue(". How are you?"), - aDocumentObjectRef(externalBlock.id) - ) - ) - ) - ) - ) - - every { documentObjectRepository.findOrFail(innerBlock.id) } returns innerBlock - every { documentObjectRepository.findOrFail(externalBlock.id) } returns externalBlock - every { variableRepository.findOrFail(variable.id) } returns variable - every { variableStructureRepository.listAll() } returns listOf() + val block = DocumentObjectBuilder("10", Block).paragraph { + text { + string("Hello, ").documentObjectRef(innerBlock).string(". How are you?") + .documentObjectRef(externalBlock) + } + }.build() // when val result = subject.buildDocumentObject(block).let { xmlMapper.readTree(it.trimIndent()) } @@ -774,8 +674,9 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `build block with image`() { // given - val image = aImage("Dog", options = ImageOptions(Size.ofPoints(120), Size.ofPoints(90))).mock() - val block = aBlock("1", listOf(ImageRef(image.id))) + val image = ImageBuilder("Dog").name("Image_Dog").sourcePath("somePath").imageType(ImageType.Jpeg) + .options(ImageOptions(Size.ofPoints(120), Size.ofPoints(90))).build().mock() + val block = DocumentObjectBuilder("1", Block).imageRef(image).build() // when val result = subject.buildDocumentObject(block).let { xmlMapper.readTree(it.trimIndent()) } @@ -784,7 +685,7 @@ class InteractiveDocumentObjectBuilderTest { result["Image"].first()["Name"].textValue().shouldBeEqualTo("Image_Dog") val imageContent = result["Image"].last() imageContent["ImageLocation"].textValue() - .shouldBeEqualTo("VCSLocation,icm://Interactive/${config.interactiveTenant}/Resources/Images/${image.sourcePath}") + .shouldBeEqualTo("VCSLocation,icm://Interactive/${config.interactiveTenant}/Resources/Images/${image.name}.jpg") imageContent["UseResizeWidth"].textValue().shouldBeEqualTo("True") imageContent["UseResizeHeight"].textValue().shouldBeEqualTo("True") imageContent["ResizeImageWidth"].textValue().shouldBeEqualTo(image.options?.resizeWidth?.toMeters().toString()) @@ -795,8 +696,10 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `build block with image uses alternateText from Image`() { // given - val image = aImage("Dog", alternateText = "A cute dog picture").mock() - val block = aBlock("1", listOf(ImageRef(image.id))) + val image = + ImageBuilder("Dog").name("Image_Dog").sourcePath("somePath").alternateText("A cute dog picture").build() + .mock() + val block = DocumentObjectBuilder("1", Block).imageRef(image).build() // when val result = subject.buildDocumentObject(block).let { xmlMapper.readTree(it.trimIndent()) } @@ -866,33 +769,19 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `paragraph with first match is built to inline condition flow with multiple options`() { // given - val rule1 = aDisplayRule( - Literal("A", LiteralDataType.String), Equals, Literal("B", LiteralDataType.String), id = "R_1" - ).mock() - - val rule2 = aDisplayRule( - Literal("C", LiteralDataType.String), Equals, Literal("C", LiteralDataType.String), id = "R_2" - ).mock() - - val block = aDocObj( - "B_1", Block, listOf( - aParagraph( - aText( - listOf( - StringValue("Hello, "), FirstMatch( - cases = listOf( - FirstMatch.Case( - DisplayRuleRef(rule1.id), listOf(aParagraph(aText(StringValue("Mike")))), null - ), FirstMatch.Case( - DisplayRuleRef(rule2.id), listOf(aParagraph(aText(StringValue("Jon")))), null - ) - ), emptyList() - ), StringValue(", how are you?") - ) - ) - ) - ) - ).mock() + val rule1 = DisplayRuleBuilder("R_1").comparison { value("A").equals().value("B") }.build().mock() + val rule2 = DisplayRuleBuilder("R_2").comparison { value("C").equals().value("C") }.build().mock() + + val block = DocumentObjectBuilder( + "B_1", Block + ).paragraph { + text { + string("Hello, ").firstMatch { + addCase().displayRuleRef(rule1).string("Mike") + addCase().displayRuleRef(rule2).string("Jon") + }.string(", how are you?") + } + }.build() // when val result = subject.buildDocumentObject(block).let { xmlMapper.readTree(it.trimIndent()) } @@ -928,12 +817,10 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `build of flow under display rule wraps in in condition flow`() { // given - val rule = - aDisplayRule(Literal("C", LiteralDataType.String), Equals, Literal("C", LiteralDataType.String)).mock() - val innerBlock = aDocObj( - "B_1", Block, listOf(aParagraph(aText(StringValue("Text")))), true, displayRuleRef = rule.id - ).mock() - val template = aDocObj("T_1", Template, listOf(aDocumentObjectRef(innerBlock.id))) + val rule = DisplayRuleBuilder("R_1").comparison { value("C").equals().value("C") }.build().mock() + val innerBlock = + DocumentObjectBuilder("B_1", Block).string("Text").internal(true).displayRuleRef(rule).build().mock() + val template = DocumentObjectBuilder("T_1", Template).documentObjectRef(innerBlock).build() // when val result = subject.buildDocumentObject(template).let { xmlMapper.readTree(it.trimIndent()) } @@ -996,15 +883,10 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `external block with multiple flows under display rule is built to section flow wrapped in condition flow`() { // given - val rule = - aDisplayRule(Literal("C", LiteralDataType.String), Equals, Literal("C", LiteralDataType.String)).mock() - val innerBlock = aDocObj("B_2", Block, listOf(aParagraph(aText(StringValue("Inner Text"))))).mock() - val block = aDocObj( - "B_1", Block, listOf( - aDocumentObjectRef(innerBlock.id), - aParagraph(aText(StringValue("Text"))), - ), false, displayRuleRef = rule.id - ) + val rule = DisplayRuleBuilder("R_1").comparison { value("C").equals().value("C") }.build().mock() + val innerBlock = DocumentObjectBuilder("B_2", Block).string("Inner Text").build().mock() + val block = DocumentObjectBuilder("B_1", Block).documentObjectRef(innerBlock).string("Text").internal(false) + .displayRuleRef(rule).build() // when val result = subject.buildDocumentObject(block).let { xmlMapper.readTree(it.trimIndent()) } @@ -1021,23 +903,25 @@ class InteractiveDocumentObjectBuilderTest { sectionFlowRefs.size().shouldBeEqualTo(2) } - @Test - fun `build template with reference to page inlines page areas as interactive flows`() { - val page = aDocObj( - "P_1", Page, listOf( - anArea(listOf(aParagraph(aText("interactive flow text 1"))), interactiveFlowName = "Logo"), - anArea(listOf(aParagraph(aText("main flow text 1")))), - aParagraph(aText("main flow text 2")), - anArea( - listOf(aParagraph(aText("interactive flow text 2"))), interactiveFlowName = "Def.InteractiveFlow1" - ), - anArea(listOf(aParagraph(aText("main flow text 3"))), interactiveFlowName = "Def.MainFlow"), - anArea(listOf(aParagraph(aText("interactive flow text 3"))), interactiveFlowName = "Flow BT 1") - ) + @ParameterizedTest + @CsvSource("Page", "Template") + fun `build template interactive flow areas inline correctly`(contentType: DocumentObjectType) { + val areaContent = listOf( + AreaBuilder().string("interactive flow text 1").interactiveFlowName("Logo").build(), + AreaBuilder().string("main flow text 1").build(), + ParagraphBuilder().string("main flow text 2").build(), + AreaBuilder().string("interactive flow text 2").interactiveFlowName("Def.InteractiveFlow1").build(), + AreaBuilder().string("main flow text 3").interactiveFlowName("Def.MainFlow").build(), + AreaBuilder().string("interactive flow text 3").interactiveFlowName("Flow BT 1").build() ) - val template = aTemplate("T_1", listOf(aDocumentObjectRef(page.id))) - every { documentObjectRepository.findOrFail(page.id) } returns page + val template = if (contentType == Page) { + val page = DocumentObjectBuilder("P_1", Page).content(areaContent).build().mock() + DocumentObjectBuilder("T_1", Template).documentObjectRef(page).build() + } else { + DocumentObjectBuilder("T_1", Template).content(areaContent).build() + } + every { ipsService.wfd2xml(getBaseTemplateFullPath(config, null)) } returns """ @@ -1104,39 +988,20 @@ class InteractiveDocumentObjectBuilderTest { @Test fun `first match in cell is wrapped in simple section flow`() { // given - val rule1 = aDisplayRule( - Literal("A", LiteralDataType.String), Equals, Literal("A", LiteralDataType.String), id = "rule1" - ).mock() - val rule2 = aDisplayRule( - Literal("B", LiteralDataType.String), Equals, Literal("B", LiteralDataType.String), id = "rule2" - ).mock() - val firstMatch = FirstMatch( - cases = listOf( - FirstMatch.Case( - displayRuleRef = DisplayRuleRef(rule1.id), - content = listOf(aParagraph(aText(StringValue("first case")))), - name = "First" - ), FirstMatch.Case( - displayRuleRef = DisplayRuleRef(rule2.id), - content = listOf(aParagraph(aText(StringValue("second case")))), - name = "Second" - ) - ), default = listOf(aParagraph(aText(StringValue("default case")))) - ) - val block = aBlock( - "B1", listOf( - Table( - rows = listOf( - aRow( - listOf( - aCell(firstMatch) - ) - ) - ), columnWidths = listOf() - ) - ) - ) - + val rule1 = DisplayRuleBuilder("R_1").comparison { value("A").equals().value("A") }.build().mock() + val rule2 = DisplayRuleBuilder("R_2").comparison { value("B").equals().value("B") }.build().mock() + + val block = DocumentObjectBuilder("B1", Block).table { + addRow { + addCell { + firstMatch { + addCase().displayRuleRef(rule1).string("first case").name("First") + addCase().displayRuleRef(rule2).string("second case").name("Second") + defaultString("default case") + } + } + } + }.build() // when val result = subject.buildDocumentObject(block).let { xmlMapper.readTree(it.trimIndent()) } From b2028ccaf6c4e9eda56cd86dac85bc3fb9ec62cb Mon Sep 17 00:00:00 2001 From: "d.svitak" Date: Fri, 15 May 2026 16:43:14 +0200 Subject: [PATCH 6/6] MIG-516 Improvements to design objects migration - fix failing test --- .../InteractiveDocumentObjectBuilderTest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt b/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt index 67e928a8..e0ba6a6a 100644 --- a/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt +++ b/migration-library/src/test/kotlin/com/quadient/migration/service/inspirebuilder/InteractiveDocumentObjectBuilderTest.kt @@ -907,12 +907,12 @@ class InteractiveDocumentObjectBuilderTest { @CsvSource("Page", "Template") fun `build template interactive flow areas inline correctly`(contentType: DocumentObjectType) { val areaContent = listOf( - AreaBuilder().string("interactive flow text 1").interactiveFlowName("Logo").build(), - AreaBuilder().string("main flow text 1").build(), + AreaBuilder().paragraph { string("interactive flow text 1") }.interactiveFlowName("Logo").build(), + AreaBuilder().paragraph { string("main flow text 1") }.build(), ParagraphBuilder().string("main flow text 2").build(), - AreaBuilder().string("interactive flow text 2").interactiveFlowName("Def.InteractiveFlow1").build(), - AreaBuilder().string("main flow text 3").interactiveFlowName("Def.MainFlow").build(), - AreaBuilder().string("interactive flow text 3").interactiveFlowName("Flow BT 1").build() + AreaBuilder().paragraph { string("interactive flow text 2") }.interactiveFlowName("Def.InteractiveFlow1").build(), + AreaBuilder().paragraph { string("main flow text 3") }.interactiveFlowName("Def.MainFlow").build(), + AreaBuilder().paragraph { string("interactive flow text 3") }.interactiveFlowName("Flow BT 1").build() ) val template = if (contentType == Page) {