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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)

- Conflict validation as a check to prevent any overwrites by the following deployment
- Allow building of first match snippets
- Possibility to nest repeated row into repeated row

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,7 @@ class Stats {
}
this.repeatedContentCount++
for (innerRow in row.rows) {
this.collectDisplayRule(innerRow.displayRuleRef)
for (cell in innerRow.cells) {
this.collectContent(cell.content)
}
this.collectTableRow(innerRow)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,57 +45,6 @@ class TableBuilder : RowBuilderBase<TableBuilder>, HasBorder<TableBuilder> {
fun percentWidth(percent: Double) = apply { this.percentWidth = percent }
fun alignment(alignment: TableAlignment) = apply { this.alignment = alignment }

/**
* Add a repeated row group to the table. The rows added to the builder will be repeated
* for each element of the given variable.
* @param variable The literal path or variable reference driving repetition.
*/
fun addRepeatedRow(variable: VariablePath) = RepeatedRowBuilder(variable).apply { rows.add(this) }

/**
* Add a repeated row group to the table and configure it via [init].
* @param variable The literal path or variable reference driving repetition.
* @return This builder instance for method chaining.
*/
fun addRepeatedRow(variable: VariablePath, init: RepeatedRowBuilder.() -> Unit): TableBuilder =
apply { rows.add(RepeatedRowBuilder(variable).apply(init)) }

/**
* Add a repeated row group driven by a literal path (e.g. "Data.Clients").
*/
fun addRepeatedRow(literalPath: String) = addRepeatedRow(LiteralPath(literalPath))

/**
* Add a repeated row group driven by a literal path and configure it via [init].
* @return This builder instance for method chaining.
*/
fun addRepeatedRow(literalPath: String, init: RepeatedRowBuilder.() -> Unit) =
addRepeatedRow(LiteralPath(literalPath), init)

/**
* Add a repeated row group driven by a registered variable reference.
*/
fun addRepeatedRow(variableRef: VariableRef) = addRepeatedRow(VariableRefPath(variableRef.id))

/**
* Add a repeated row group driven by a registered variable reference and configure it via [init].
* @return This builder instance for method chaining.
*/
fun addRepeatedRow(variableRef: VariableRef, init: RepeatedRowBuilder.() -> Unit) =
addRepeatedRow(VariableRefPath(variableRef.id), init)

/**
* Add a repeated row group driven by a [Variable] object.
*/
fun addRepeatedRow(variable: Variable) = addRepeatedRow(VariableRefPath(variable.id))

/**
* Add a repeated row group driven by a [Variable] object and configure it via [init].
* @return This builder instance for method chaining.
*/
fun addRepeatedRow(variable: Variable, init: RepeatedRowBuilder.() -> Unit) =
addRepeatedRow(VariableRefPath(variable.id), init)

/**
* Add a column width to the table. Column widths are added in the order they are defined.
*/
Expand Down Expand Up @@ -225,7 +174,7 @@ class TableBuilder : RowBuilderBase<TableBuilder>, HasBorder<TableBuilder> {

override fun build(): Table.RepeatedRow {
return Table.RepeatedRow(
rows = rows.map { (it as Row).build() },
rows = rows.map { it.build() },
variable = variable,
displayRuleRef = displayRuleRef,
)
Expand Down Expand Up @@ -263,7 +212,7 @@ class TableBuilder : RowBuilderBase<TableBuilder>, HasBorder<TableBuilder> {
}

/**
* Common interface for builders that manage a collection of [TableBuilder.Row] entries.
* Common interface for builders that manage a collection of [TableBuilder.TableRow] entries.
* Implemented by both [TableBuilder] and [TableBuilder.RepeatedRowBuilder].
*/
@Suppress("UNCHECKED_CAST")
Expand Down Expand Up @@ -291,4 +240,59 @@ interface RowBuilderBase<T> {
* @return This builder instance for method chaining.
*/
fun addRows(rows: List<TableBuilder.Row>): T = apply { this.rows.addAll(rows) } as T

/**
* Add a repeated row group. The rows added to the builder will be repeated
* for each element of the given variable.
* @param variable The literal path or variable reference driving repetition.
*/
fun addRepeatedRow(variable: VariablePath): TableBuilder.RepeatedRowBuilder =
TableBuilder.RepeatedRowBuilder(variable).also { rows.add(it) }

/**
* Add a repeated row group and configure it via [init].
* @param variable The literal path or variable reference driving repetition.
* @return This builder instance for method chaining.
*/
fun addRepeatedRow(variable: VariablePath, init: TableBuilder.RepeatedRowBuilder.() -> Unit): T =
apply { rows.add(TableBuilder.RepeatedRowBuilder(variable).apply(init)) } as T

/**
* Add a repeated row group driven by a literal path (e.g. "Data.Clients").
*/
fun addRepeatedRow(literalPath: String): TableBuilder.RepeatedRowBuilder =
addRepeatedRow(LiteralPath(literalPath))

/**
* Add a repeated row group driven by a literal path and configure it via [init].
* @return This builder instance for method chaining.
*/
fun addRepeatedRow(literalPath: String, init: TableBuilder.RepeatedRowBuilder.() -> Unit): T =
addRepeatedRow(LiteralPath(literalPath), init)

/**
* Add a repeated row group driven by a registered variable reference.
*/
fun addRepeatedRow(variableRef: VariableRef): TableBuilder.RepeatedRowBuilder =
addRepeatedRow(VariableRefPath(variableRef.id))

/**
* Add a repeated row group driven by a registered variable reference and configure it via [init].
* @return This builder instance for method chaining.
*/
fun addRepeatedRow(variableRef: VariableRef, init: TableBuilder.RepeatedRowBuilder.() -> Unit): T =
addRepeatedRow(VariableRefPath(variableRef.id), init)

/**
* Add a repeated row group driven by a [Variable] object.
*/
fun addRepeatedRow(variable: Variable): TableBuilder.RepeatedRowBuilder =
addRepeatedRow(VariableRefPath(variable.id))

/**
* Add a repeated row group driven by a [Variable] object and configure it via [init].
* @return This builder instance for method chaining.
*/
fun addRepeatedRow(variable: Variable, init: TableBuilder.RepeatedRowBuilder.() -> Unit): T =
addRepeatedRow(VariableRefPath(variable.id), init)
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ data class Table(
}

data class RepeatedRow(
val rows: List<Row>,
val rows: List<TableRow>,
val variable: VariablePath,
val displayRuleRef: DisplayRuleRef? = null,
) : TableRow {
Expand All @@ -102,7 +102,7 @@ data class Table(

fun toDb(): TableEntity.RepeatedRow {
return TableEntity.RepeatedRow(
rows = rows.map(Row::toDb),
rows = rows.map { it.toDb() },
variable = variable,
displayRuleRef = displayRuleRef?.toDb(),
)
Expand All @@ -111,7 +111,7 @@ data class Table(
companion object {
fun fromDb(row: TableEntity.RepeatedRow): RepeatedRow {
return RepeatedRow(
rows = row.rows.map(Row::fromDb),
rows = row.rows.map(::tableRowFromDb),
variable = row.variable,
displayRuleRef = row.displayRuleRef?.let { DisplayRuleRef.fromDb(it) },
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ data class TableEntity(

@Serializable
data class RepeatedRow(
val rows: List<Row>,
val rows: List<TableRow>,
val variable: VariablePath,
val displayRuleRef: DisplayRuleEntityRef? = null,
) : TableRow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,14 +158,16 @@ abstract class InspireDocumentObjectBuilder(
protected fun collectLanguages(documentObject: DocumentObject): List<String> {
val languages = mutableSetOf<String>()

fun Table.allRows(): List<Table.Row> =
(rows + header + firstHeader + footer + lastFooter).flatMap { row ->
fun List<TableRow>.flattenRows(): List<Table.Row> =
flatMap { row ->
when (row) {
is Table.Row -> listOf(row)
is Table.RepeatedRow -> row.rows
is Table.RepeatedRow -> row.rows.flattenRows()
}
}

fun Table.allRows(): List<Table.Row> = (rows + header + firstHeader + footer + lastFooter).flattenRows()

fun collectLanguagesFromContent(content: List<DocumentContent>) {
for (item in content) {
when (item) {
Expand Down Expand Up @@ -1162,12 +1164,23 @@ abstract class InspireDocumentObjectBuilder(
}

val repeatedRowSet = layout.addRowSet().setType(RowSet.Type.REPEATED)

fun addChildRowSets(target: GeneralRowSet) {
repeatedRow.rows.forEach { childRow ->
when (childRow) {
is Table.Row -> target.addRowSet(buildSingleRowSet(childRow, layout, variableStructure, languages))
is Table.RepeatedRow -> buildRepeatedRowSet(childRow, layout, variableStructure, languages)
?.let { target.addRowSet(it) }
}
}
}

if (repeatedRow.rows.size > 1) {
val multipleRowSet = layout.addRowSet().setType(RowSet.Type.MULTIPLE_ROWS)
repeatedRowSet.addRowSet(multipleRowSet)
repeatedRow.rows.forEach { multipleRowSet.addRowSet(buildSingleRowSet(it, layout, variableStructure, languages)) }
addChildRowSets(multipleRowSet)
} else {
repeatedRow.rows.forEach { repeatedRowSet.addRowSet(buildSingleRowSet(it, layout, variableStructure, languages)) }
addChildRowSets(repeatedRowSet)
}
repeatedRowSet.setVariable(arrayVariable)

Expand All @@ -1184,10 +1197,11 @@ abstract class InspireDocumentObjectBuilder(
val warning = Paragraph("<repeated row by unmapped \$$varName\$>")

val rows = repeatedRow.rows
val rowsWithWarning = rows.firstOrNull()?.let { firstRow ->
val rowsWithWarning = (rows.firstOrNull { it is Table.Row } as? Table.Row)?.let { firstRow ->
firstRow.cells.firstOrNull()?.let { firstCell ->
val cellWithWarning = firstCell.copy(content = listOf(warning) + firstCell.content)
listOf(firstRow.copy(cells = listOf(cellWithWarning) + firstRow.cells.drop(1))) + rows.drop(1)
val modifiedRow = firstRow.copy(cells = listOf(cellWithWarning) + firstRow.cells.drop(1))
listOf(modifiedRow) + rows.filter { it !== firstRow }
}
} ?: rows

Expand Down Expand Up @@ -1313,7 +1327,7 @@ abstract class InspireDocumentObjectBuilder(
} else {
val numberOfColumns = when (val firstRow = model.rows.firstOrNull()) {
is Table.Row -> firstRow.cells.size
is Table.RepeatedRow -> firstRow.rows.firstOrNull()?.cells?.size ?: 0
is Table.RepeatedRow -> firstRow.rows.firstNotNullOfOrNull { (it as? Table.Row)?.cells?.size } ?: 0
null -> 0
}
repeat(numberOfColumns) { table.addColumn() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,65 @@ class InspireDocumentObjectBuilderTest {
secondFlow["FlowContent"]["P"]["T"][""].textValue().shouldBeEqualTo("Second")
}

@Test
fun `buildDocumentObject creates nested repeated rowsets`() {
// given
val itemNameVar = mockVar(VariableBuilder("itemNameVar").name("Item Name").dataType(DataType.String).build())
val ordersArrayVar = mockVar(VariableBuilder("orders").name("Orders").dataType(DataType.Array).build())

val variableStructure = mockVarStructure(
VariableStructureBuilder("VS_1").addVariable(ordersArrayVar.id, "Data.Clients.Value")
.addVariable(itemNameVar.id, ordersArrayVar).build()
)

val block = mockObj(
DocumentObjectBuilder("B_1", Block).table {
addRepeatedRow("Data.Clients.Value") {
addRepeatedRow(ordersArrayVar) {
addRow {
addCell { string("Order: ") }
addCell { paragraph { text { variableRef(itemNameVar.id) } } }
}
}
}
}.variableStructureRef(variableStructure.id).build()
)

// when
val result = subject.buildDocumentObject(block).let { xmlMapper.readTree(it.trimIndent()) }["Layout"]["Layout"]

// then — outer repeated rowset driven by Data.Clients.Value
val outerRepeatedRowSetId = result["Table"].last()["RowSetId"].textValue()
val outerRepeatedRowSet = result["RowSet"].last { it["Id"].textValue() == outerRepeatedRowSetId }
outerRepeatedRowSet["RowSetType"].textValue().shouldBeEqualTo("Repeated")
val outerArrayVar = result["Variable"].last { it["Id"].textValue() == outerRepeatedRowSet["VariableId"].textValue() }
outerArrayVar["VarType"].textValue().shouldBeEqualTo("Array")

// then — inner repeated rowset is nested directly inside the outer
val innerRepeatedRowSetId = outerRepeatedRowSet["SubRowId"].textValue()
val innerRepeatedRowSet = result["RowSet"].last { it["Id"].textValue() == innerRepeatedRowSetId }
innerRepeatedRowSet["RowSetType"].textValue().shouldBeEqualTo("Repeated")
val innerArrayVar = result["Variable"].last { it["Id"].textValue() == innerRepeatedRowSet["VariableId"].textValue() }
innerArrayVar["VarType"].textValue().shouldBeEqualTo("Array")

// then — single row is nested inside the inner repeated rowset
val singleRowId = innerRepeatedRowSet["SubRowId"].textValue()
val singleRow = result["RowSet"].last { it["Id"].textValue() == singleRowId }
singleRow["RowSetType"].textValue().shouldBeEqualTo("Row")

val secondCellId = singleRow["SubRowId"][1].textValue()
val secondCell = result["Cell"].last { it["Id"].textValue() == secondCellId }
val secondCellFlow = result["Flow"].last { it["Id"].textValue() == secondCell["FlowId"].textValue() }
val variableId = secondCellFlow["FlowContent"]["P"]["T"]["O"]["Id"].textValue()
val variable = result["Variable"].first { it["Id"].textValue() == variableId }
variable["Name"].textValue().shouldBeEqualTo("Item Name")

val lockedWebNodes = result["Root"]["LockedWebNodes"]["LockedWebNode"]
val lockedIds = lockedWebNodes.map { it.textValue() }
lockedIds.contains(outerRepeatedRowSetId).shouldBeEqualTo(true)
lockedIds.contains(innerRepeatedRowSetId).shouldBeEqualTo(true)
}

@Test
fun `buildDocumentObject creates repeated flow with literal array path`() {
// given
Expand Down
Loading