Bug 569165 - [Model2Doc][Docx] Model2Doc should provide a way to merge cells in tables
* Add isCellMergedWithRightCell and isCellMergedWithBottomCell NamedStyles
* Take those styles in account in the Docx transcription
Signed-off-by: Pauline DEVILLE <pauline.deville@cea.fr>
Change-Id: Iaae6d0d05964253d6444fe37b35e9330ed59bc46
diff --git a/plugins/core/org.eclipse.papyrus.model2doc.core.styles/.classpath b/plugins/core/org.eclipse.papyrus.model2doc.core.styles/.classpath
index 9909fb1..04c2c97 100755
--- a/plugins/core/org.eclipse.papyrus.model2doc.core.styles/.classpath
+++ b/plugins/core/org.eclipse.papyrus.model2doc.core.styles/.classpath
@@ -8,5 +8,6 @@
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src-gen"/>
<classpathentry kind="src" path="api"/>
+ <classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/plugins/core/org.eclipse.papyrus.model2doc.core.styles/api/org/eclipse/papyrus/model2doc/core/styles/NamedStyleConstants.java b/plugins/core/org.eclipse.papyrus.model2doc.core.styles/api/org/eclipse/papyrus/model2doc/core/styles/NamedStyleConstants.java
new file mode 100755
index 0000000..8eca09b
--- /dev/null
+++ b/plugins/core/org.eclipse.papyrus.model2doc.core.styles/api/org/eclipse/papyrus/model2doc/core/styles/NamedStyleConstants.java
@@ -0,0 +1,46 @@
+/*****************************************************************************
+ * Copyright (c) 2020 CEA LIST and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Pauline DEVILLE (CEA LIST) pauline.deville@cea.fr - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.model2doc.core.styles;
+
+/**
+ * This class list every style available
+ */
+public final class NamedStyleConstants {
+
+ /**
+ * Merge the cell with the cell just in the right
+ *
+ * This style can be apply on Cell
+ * This style wait for a boolean value
+ */
+ public static final String MERGED_WITH_RIGHT_CELL = "mergedWithRightCell"; //$NON-NLS-1$
+
+ /**
+ * Merge the cell with the cell just in the bottom
+ *
+ * This style can be apply on Cell
+ * This style wait for a boolean value
+ */
+ public static final String MERGED_WITH_BOTTOM_CELL = "mergedWithBottomCell"; //$NON-NLS-1$
+
+ /**
+ * Constructor.
+ *
+ */
+ private NamedStyleConstants() {
+ // avoid instantiation
+ }
+}
diff --git a/plugins/core/org.eclipse.papyrus.model2doc.core.styles/build.properties b/plugins/core/org.eclipse.papyrus.model2doc.core.styles/build.properties
index 9a0a3cf..83c8710 100755
--- a/plugins/core/org.eclipse.papyrus.model2doc.core.styles/build.properties
+++ b/plugins/core/org.eclipse.papyrus.model2doc.core.styles/build.properties
@@ -19,6 +19,7 @@
icons/
jars.compile.order = .
source.. = src-gen/,\
- api/
+ api/,\
+ src/
output.. = bin/
src.includes = about.html
diff --git a/plugins/core/org.eclipse.papyrus.model2doc.core.styles/api/org/eclipse/papyrus/model2doc/core/styles/internal/operations/NamedStyleOperations.java b/plugins/core/org.eclipse.papyrus.model2doc.core.styles/src/org/eclipse/papyrus/model2doc/core/styles/internal/operations/NamedStyleOperations.java
similarity index 100%
rename from plugins/core/org.eclipse.papyrus.model2doc.core.styles/api/org/eclipse/papyrus/model2doc/core/styles/internal/operations/NamedStyleOperations.java
rename to plugins/core/org.eclipse.papyrus.model2doc.core.styles/src/org/eclipse/papyrus/model2doc/core/styles/internal/operations/NamedStyleOperations.java
diff --git a/plugins/doc/org.eclipse.papyrus.model2doc.doc/src/site/mediawiki/images/devDoc/false.jpg b/plugins/doc/org.eclipse.papyrus.model2doc.doc/src/site/mediawiki/images/devDoc/false.jpg
new file mode 100755
index 0000000..12080fc
--- /dev/null
+++ b/plugins/doc/org.eclipse.papyrus.model2doc.doc/src/site/mediawiki/images/devDoc/false.jpg
Binary files differ
diff --git a/plugins/doc/org.eclipse.papyrus.model2doc.doc/src/site/mediawiki/images/devDoc/true.jpg b/plugins/doc/org.eclipse.papyrus.model2doc.doc/src/site/mediawiki/images/devDoc/true.jpg
new file mode 100755
index 0000000..8f88f4f
--- /dev/null
+++ b/plugins/doc/org.eclipse.papyrus.model2doc.doc/src/site/mediawiki/images/devDoc/true.jpg
Binary files differ
diff --git a/plugins/doc/org.eclipse.papyrus.model2doc.doc/src/site/mediawiki/model2doc-devDoc.mediawiki b/plugins/doc/org.eclipse.papyrus.model2doc.doc/src/site/mediawiki/model2doc-devDoc.mediawiki
index 23253cc..868200f 100755
--- a/plugins/doc/org.eclipse.papyrus.model2doc.doc/src/site/mediawiki/model2doc-devDoc.mediawiki
+++ b/plugins/doc/org.eclipse.papyrus.model2doc.doc/src/site/mediawiki/model2doc-devDoc.mediawiki
@@ -157,4 +157,36 @@
=How to contribute a new Document generator=
This generator is in charge to generate the final document from a DocumentStructure.
You must create a new class implementing <code>org.eclipse.papyrus.model2doc.emf.structure2document.generator.IStructure2DocumentGenerator</code>.
-This class must be registered with the extension point <code>org.eclipse.papyrus.model2doc.emf.structure2document.documentgenerator</code>.
\ No newline at end of file
+This class must be registered with the extension point <code>org.eclipse.papyrus.model2doc.emf.structure2document.documentgenerator</code>.
+
+=Styles=
+The '''org.eclipse.papyrus.model2doc.core.styles''' plugin provide style metamodel. It define NamedStyle for Integer, String, Boolean and Double using single or multiple references.
+
+==How to add new NamedStyle==
+*You should add the new NamedStyle name in '''org.eclipse.papyrus.model2doc.core.styles.NamedStyleConstants'''
+*Update the transcription to use your new NamedStyle
+*Don't forget the update this documentation to be sure that the list of NamedStyles is updated
+
+==Existing NamedStyles==
+{| class="wikitable"
+! Name
+! Description
+! Value type
+! Applies to
+! Odt
+! Docx
+|-
+|mergedWithRightCell
+|Merge the cell with the cell located just to the right
+|Boolean
+|Cell
+|[[Image:images/devDoc/false.jpg]]
+|[[Image:images/devDoc/true.jpg]]
+|-
+|mergedWithBottomCell
+|Merge the cell with the cell located just to below
+|Boolean
+|Cell
+|[[Image:images/devDoc/false.jpg]]
+|[[Image:images/devDoc/true.jpg]]
+|}
diff --git a/plugins/docx/org.eclipse.papyrus.model2doc.docx/META-INF/MANIFEST.MF b/plugins/docx/org.eclipse.papyrus.model2doc.docx/META-INF/MANIFEST.MF
index f8aa988..000a9cc 100755
--- a/plugins/docx/org.eclipse.papyrus.model2doc.docx/META-INF/MANIFEST.MF
+++ b/plugins/docx/org.eclipse.papyrus.model2doc.docx/META-INF/MANIFEST.MF
@@ -14,6 +14,7 @@
org.eclipse.papyrus.model2doc.core.generatorconfiguration;bundle-version="[0.8.0,1.0.0)",
org.eclipse.papyrus.model2doc.core.builtintypes;bundle-version="[0.8.0,1.0.0)",
org.eclipse.papyrus.model2doc.core.author;bundle-version="[0.7.0,1.0.0)",
+ org.eclipse.papyrus.model2doc.core.styles;bundle-version="[0.8.0,1.0.0)",
org.apache.xmlbeans;bundle-version="[3.1.0,4.0.0)",
org.apache.poi.ooxml.schemas;bundle-version="[4.1.0,5.0.0)",
org.apache.poi;bundle-version="[4.1.0,5.0.0)"
diff --git a/plugins/docx/org.eclipse.papyrus.model2doc.docx/src/org/eclipse/papyrus/model2doc/docx/internal/poi/CustomXWPFDocument.java b/plugins/docx/org.eclipse.papyrus.model2doc.docx/src/org/eclipse/papyrus/model2doc/docx/internal/poi/CustomXWPFDocument.java
index cdce22b..7921df3 100755
--- a/plugins/docx/org.eclipse.papyrus.model2doc.docx/src/org/eclipse/papyrus/model2doc/docx/internal/poi/CustomXWPFDocument.java
+++ b/plugins/docx/org.eclipse.papyrus.model2doc.docx/src/org/eclipse/papyrus/model2doc/docx/internal/poi/CustomXWPFDocument.java
@@ -20,6 +20,7 @@
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
+import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSimpleField;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STOnOff;
@@ -72,4 +73,32 @@
toc.setDirty(STOnOff.TRUE); // FIXME a pop up appear when we open the document
}
+ /**
+ * Create an empty table with one row and one column as default.
+ *
+ * @return a new table
+ */
+ @Override
+ public XWPFTable createTable() {
+ XWPFTable table = new CustomXWPFTable(getDocument().getBody().addNewTbl(), this);
+ bodyElements.add(table);
+ tables.add(table);
+ return table;
+ }
+
+ /**
+ * Create an empty table with a number of rows and cols specified
+ *
+ * @param rows
+ * @param cols
+ * @return table
+ */
+ @Override
+ public XWPFTable createTable(int rows, int cols) {
+ XWPFTable table = new CustomXWPFTable(getDocument().getBody().addNewTbl(), this, rows, cols);
+ bodyElements.add(table);
+ tables.add(table);
+ return table;
+ }
+
}
diff --git a/plugins/docx/org.eclipse.papyrus.model2doc.docx/src/org/eclipse/papyrus/model2doc/docx/internal/poi/CustomXWPFTable.java b/plugins/docx/org.eclipse.papyrus.model2doc.docx/src/org/eclipse/papyrus/model2doc/docx/internal/poi/CustomXWPFTable.java
new file mode 100755
index 0000000..78aa88c
--- /dev/null
+++ b/plugins/docx/org.eclipse.papyrus.model2doc.docx/src/org/eclipse/papyrus/model2doc/docx/internal/poi/CustomXWPFTable.java
@@ -0,0 +1,137 @@
+/*****************************************************************************
+ * Copyright (c) 2020 CEA LIST and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Pauline DEVILLE (CEA LIST) pauline.deville@cea.fr - Initial API and implementation
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.model2doc.docx.internal.poi;
+
+import org.apache.poi.xwpf.usermodel.IBody;
+import org.apache.poi.xwpf.usermodel.XWPFTable;
+import org.apache.poi.xwpf.usermodel.XWPFTableCell;
+import org.eclipse.papyrus.model2doc.docx.Activator;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHMerge;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTVMerge;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMerge;
+
+/**
+ *
+ */
+public class CustomXWPFTable extends XWPFTable {
+
+ /**
+ * Constructor.
+ *
+ * @param table
+ * @param part
+ */
+ public CustomXWPFTable(CTTbl table, IBody part) {
+ super(table, part);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param table
+ * @param part
+ * @param row
+ * @param col
+ */
+ public CustomXWPFTable(CTTbl table, IBody part, int row, int col) {
+ super(table, part, row, col);
+ }
+
+ /**
+ * Merge cells present between the startCell and the endCell, the content of the merge cell is the content of the startCell
+ *
+ * @param rowIndex
+ * the position of the row to merge
+ * @param startCellIndex
+ * the position in the row of the first cell to merge
+ * @param endCell
+ * the position in the row of the last cell to merge
+ */
+ public void horizontalCellMerge(int rowIndex, int startCellIndex, int endCellIndex) {
+ // some check
+ if (rowIndex >= getNumberOfRows()) {
+ Activator.log.warn("The merge of cells is not possible since the range of cells is out of bounds"); //$NON-NLS-1$
+ }
+ if (startCellIndex >= getRow(rowIndex).getTableCells().size() || endCellIndex >= getRow(rowIndex).getTableCells().size()) {
+ Activator.log.warn("The merge of cells is not possible since the range of cells is out of bounds"); //$NON-NLS-1$
+ }
+
+ // merge
+ CTHMerge startMerge = CTHMerge.Factory.newInstance();
+ startMerge.setVal(STMerge.RESTART);
+ CTHMerge continueMerge = CTHMerge.Factory.newInstance();
+ continueMerge.setVal(STMerge.CONTINUE);
+
+ XWPFTableCell startCell = getRow(rowIndex).getCell(startCellIndex);
+ startCell.getCTTc().addNewTcPr().setHMerge(startMerge);
+
+ int index = startCellIndex + 1;
+ while (index <= endCellIndex) {
+ XWPFTableCell continueCell = getRow(rowIndex).getCell(index);
+ CTTcPr tcpr = continueCell.getCTTc().getTcPr();
+ if (tcpr == null) {
+ tcpr = continueCell.getCTTc().addNewTcPr();
+ }
+ tcpr.setHMerge(continueMerge);
+ index++;
+ }
+ }
+
+ /**
+ * Merge cells present between the startCell and the endCell, the content of the merge cell is the content of the startCell
+ *
+ * @param columnIndex
+ * the position of the column to merge
+ * @param startRowIndex
+ * the position of the first row to merge
+ * @param endRowIndex
+ * the position of the last row to merge
+ */
+ public void verticalCellMerge(int columnIndex, int startRowIndex, int endRowIndex) {
+ // some check
+ if (startRowIndex >= getNumberOfRows() || endRowIndex >= getNumberOfRows()) {
+ Activator.log.warn("The merge of cells is not possible since the range of cells is out of bounds"); //$NON-NLS-1$
+ }
+ if (columnIndex >= getRow(0).getTableCells().size()) {
+ Activator.log.warn("The merge of cells is not possible since the range of cells is out of bounds"); //$NON-NLS-1$
+ }
+
+ // merge
+ CTVMerge startMerge = CTVMerge.Factory.newInstance();
+ startMerge.setVal(STMerge.RESTART);
+ CTVMerge continueMerge = CTVMerge.Factory.newInstance();
+ continueMerge.setVal(STMerge.CONTINUE);
+
+ XWPFTableCell startCell = getRow(startRowIndex).getCell(columnIndex);
+ startCell.getCTTc().addNewTcPr().setVMerge(startMerge);
+
+ int index = startRowIndex + 1;
+ while (index <= endRowIndex) {
+ XWPFTableCell continueCell = getRow(index).getCell(columnIndex);
+ CTTcPr tcpr = continueCell.getCTTc().getTcPr();
+ if (tcpr == null) {
+ tcpr = continueCell.getCTTc().addNewTcPr();
+ }
+ tcpr.setVMerge(continueMerge);
+ index++;
+ }
+ }
+
+
+
+}
diff --git a/plugins/docx/org.eclipse.papyrus.model2doc.docx/src/org/eclipse/papyrus/model2doc/docx/internal/transcription/DocxTranscription.java b/plugins/docx/org.eclipse.papyrus.model2doc.docx/src/org/eclipse/papyrus/model2doc/docx/internal/transcription/DocxTranscription.java
index 61beb57..63ac549 100755
--- a/plugins/docx/org.eclipse.papyrus.model2doc.docx/src/org/eclipse/papyrus/model2doc/docx/internal/transcription/DocxTranscription.java
+++ b/plugins/docx/org.eclipse.papyrus.model2doc.docx/src/org/eclipse/papyrus/model2doc/docx/internal/transcription/DocxTranscription.java
@@ -19,6 +19,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@@ -42,12 +43,15 @@
import org.eclipse.papyrus.model2doc.core.builtintypes.TextCell;
import org.eclipse.papyrus.model2doc.core.generatorconfiguration.IDocumentGeneratorConfiguration;
import org.eclipse.papyrus.model2doc.core.generatorconfiguration.operations.GeneratorConfigurationOperations;
+import org.eclipse.papyrus.model2doc.core.styles.BooleanNamedStyle;
+import org.eclipse.papyrus.model2doc.core.styles.NamedStyleConstants;
import org.eclipse.papyrus.model2doc.core.transcription.CoverPage;
import org.eclipse.papyrus.model2doc.core.transcription.ImageDescription;
import org.eclipse.papyrus.model2doc.core.transcription.Transcription;
import org.eclipse.papyrus.model2doc.docx.Activator;
import org.eclipse.papyrus.model2doc.docx.Messages;
import org.eclipse.papyrus.model2doc.docx.internal.poi.CustomXWPFDocument;
+import org.eclipse.papyrus.model2doc.docx.internal.poi.CustomXWPFTable;
import org.eclipse.papyrus.model2doc.docx.internal.services.StyleServiceImpl;
import org.eclipse.papyrus.model2doc.docx.internal.util.ImageUtils;
import org.eclipse.papyrus.model2doc.docx.services.StyleService;
@@ -200,27 +204,118 @@
// create and fill the table
XWPFTable xwpfTable = document.createTable(rowsNumber, colNumbers);
Iterator<Row> rowIter = table.getRows().iterator();
- int rowNumber = 0;
+ int rowIndex = 0;
while (rowIter.hasNext()) {
Row row = rowIter.next();
Iterator<Cell> cellIter = row.getCells().iterator();
- int cellNumber = 0;
+ int cellIndex = 0;
while (cellIter.hasNext()) {
Cell cell = cellIter.next();
if (cell instanceof TextCell) {
TextCell textCell = (TextCell) cell;
- xwpfTable.getRow(rowNumber).getCell(cellNumber).setText(textCell.getText());
+ xwpfTable.getRow(rowIndex).getCell(cellIndex).setText(textCell.getText());
}
- cellNumber++;
+ cellIndex++;
}
- rowNumber++;
+ rowIndex++;
}
- // apply style
+ // apply styles
+ rowIter = table.getRows().iterator();
+ List<Cell> verticalMergedCells = new ArrayList<>();
+ while (rowIter.hasNext()) {
+ Row row = rowIter.next();
+ Iterator<Cell> cellIter = row.getCells().iterator();
+ while (cellIter.hasNext()) {
+ Cell cell = cellIter.next();
+
+ // Horizontal cell merge
+ BooleanNamedStyle style = (BooleanNamedStyle) cell.getNamedStyle(NamedStyleConstants.MERGED_WITH_RIGHT_CELL);
+ if (null != style && style.isValue()) {
+ Cell lastMergedCell = findLastHorizontalMergedCell(cellIter);
+
+ if (lastMergedCell != null && xwpfTable instanceof CustomXWPFTable) {
+ ((CustomXWPFTable) xwpfTable).horizontalCellMerge(
+ table.getRows().indexOf(row),
+ row.getCells().indexOf(cell),
+ row.getCells().indexOf(lastMergedCell));
+ }
+ }
+
+ // Vertical cell merge
+ style = (BooleanNamedStyle) cell.getNamedStyle(NamedStyleConstants.MERGED_WITH_BOTTOM_CELL);
+ if (null != style && style.isValue()) {
+ if (false == verticalMergedCells.contains(cell)) { // otherwise it is already managed
+ Row lastMergedRow = findVerticalMergedCells(table, row, cell, verticalMergedCells);
+ if (lastMergedRow != null && xwpfTable instanceof CustomXWPFTable) {
+ ((CustomXWPFTable) xwpfTable).verticalCellMerge(
+ row.getCells().indexOf(cell),
+ table.getRows().indexOf(row),
+ table.getRows().indexOf(lastMergedRow));
+ }
+ }
+ }
+ }
+ }
+
xwpfTable.setWidthType(TableWidthType.PCT); // resize table to use the page width
styleService.applyTableStyle(xwpfTable, document, table);
}
+ /**
+ * Find the first cell following the iterator which does not have the {@link NamedStyleConstants.MERGED_WITH_RIGHT_CELL} style
+ *
+ * @param cellIter
+ * the start of the research
+ * @return the last cell concerning by the merge
+ */
+ private Cell findLastHorizontalMergedCell(Iterator<Cell> cellIter) {
+ while (cellIter.hasNext()) {
+ Cell cell = cellIter.next();
+ BooleanNamedStyle style = (BooleanNamedStyle) cell.getNamedStyle(NamedStyleConstants.MERGED_WITH_RIGHT_CELL);
+ if (null != style && style.isValue()) {
+ continue;
+ } else {
+ return cell;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * This method add every cells that must be merged with the firstCell (according to the {@link NamedStyleConstants.MERGED_WITH_BOTTOM_CELL} style)
+ * to the mergedCells collection then it return the row of the laster merged cell
+ *
+ * @param table
+ * the table
+ * @param row
+ * the row of the first cell of the merge
+ * @param firstCell
+ * the first cell of the merge
+ * @param mergedCells
+ * the list of merged cells
+ * @return the row of the last merged cell
+ */
+ private Row findVerticalMergedCells(final AbstractTable table, final Row row, final Cell firstCell, List<Cell> mergedCells) {
+ int columnIndex = row.getCells().indexOf(firstCell);
+ int rowIndex = table.getRows().indexOf(row) + 1;
+
+ while (rowIndex < table.getRowsNumber()) {
+ Cell cell = table.getRows().get(rowIndex).getCells().get(columnIndex);
+
+ BooleanNamedStyle style = (BooleanNamedStyle) cell.getNamedStyle(NamedStyleConstants.MERGED_WITH_BOTTOM_CELL);
+ if (null != style && style.isValue()) {
+ mergedCells.add(cell);
+ } else {
+ mergedCells.add(cell);
+ return table.getRows().get(rowIndex);
+ }
+ rowIndex++;
+ }
+
+ return null;
+ }
+
@Override
public void insertFile(IFileReference fileReference) {
// TODO Auto-generated method stub