Skip to content

Commit

Permalink
impl
Browse files Browse the repository at this point in the history
Issue #126
  • Loading branch information
rsoika committed Nov 18, 2022
1 parent de593c8 commit d357984
Show file tree
Hide file tree
Showing 2 changed files with 295 additions and 0 deletions.
62 changes: 62 additions & 0 deletions imixs-adapters-odf/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Imixs ODF Adapter

This adapter module provides services to update the content of a ODF document. The implementation is based on
[LibreOFfice ODF Toolkit](https://odftoolkit.org/).



## ODF FindReplace Adapter

With the adapter class `org.imixs.workflow.odf.ODFDOMFindReplaceAdapter` it is possible to update the content of ODF documents (.odf).


### ODF Documents

The adapter can replace text fragments in a paragraph of a ODF document. The adapter uses the ODF Toolkit to find and replace all paragraphs matching a given Regular expression. The result document is saved in the current workitem.

The following example searches for an attachment with the name `Agreement-????.odf` and replaces the text fragments `[company.name]`, `[company.country]` and `[contract_startdate]` with the corresponding item values.

<poi-update name=
"filename">Agreement-<itemvalue>numsequencenumber</itemvalue>.docx</poi-update>
<poi-update name="findreplace">
<find>\[company\.name\]</find>
<replace><itemvalue>company.name</itemvalue></replace>
</poi-update>
<poi-update name="findreplace">
<find>\[company\.country\]</find>
<replace><itemvalue>company.country</itemvalue></replace>
</poi-update>
<poi-update name="findreplace">
<find>\[contract\.startdate\]</find>
<replace><itemvalue format="EEE, MMM d, yyyy">contract.start</itemvalue></replace>
</poi-update>


### Regular Expressions for Document Name

You can also define the filename as a pattern including regular expressions. See the following example:

<poi-update name="filename">.*<itemvalue>numsequencenumber</itemvalue>\.odf</poi-update>

This expression will match all files ending with the sequence number and the file extension '.docx'



# Development

## Maven


The imixs-adapter-poi module can be added into an application module. The module provides CDI and Rest API components.

Add the following maven dependency into a parent project:


<!-- ODF Adapter -->
<dependency>
<groupId>org.imixs.workflow</groupId>
<artifactId>imixs-adapters-odf</artifactId>
<version>${org.imixs.adapters.version}</version>
<scope>provided</scope>
</dependency>

Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package org.imixs.workflow.odf;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.inject.Inject;

import org.imixs.archive.core.SnapshotService;
import org.imixs.workflow.FileData;
import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.SignalAdapter;
import org.imixs.workflow.engine.ReportService;
import org.imixs.workflow.engine.WorkflowService;
import org.imixs.workflow.exceptions.AdapterException;
import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.util.XMLParser;
import org.odftoolkit.odfdom.doc.OdfDocument;
import org.odftoolkit.odfdom.doc.OdfTextDocument;
import org.odftoolkit.odfdom.incubator.search.InvalidNavigationException;
import org.odftoolkit.odfdom.incubator.search.TextNavigation;
import org.odftoolkit.odfdom.incubator.search.TextSelection;

/**
* This ODFDOMFindReplaceAdapter can be used to find and replace text fragements
* in a Open Document Format (ODF) documents (e.g. .odt, .ods).
* <p>
* The adapter can be configured by the event workflow result:
* <p>
*
* <pre>
* {@code
<odf-update name=
"filename">PLA Membership Agreement-<itemvalue>numsequencenumber</itemvalue>.odf</odf-update>
<odf-update name="findreplace">
<find>[company.name]</find>
<replace><itemvalue>company.name</itemvalue></replace>
</odf-update>
<odf-update name="findreplace">
<find>[company.country]</find>
<replace><itemvalue>company.country</itemvalue></replace>
</odf-update>
<odf-update name="findreplace">
<find>[contract.startdate]</find>
<replace><itemvalue format=
"EEE, MMM d, yyyy">contract.start</itemvalue></replace>
</odf-update>
}
* </pre>
*
*
* @version 1.0
* @author rsoika
*/
public class ODFDOMFindReplaceAdapter implements SignalAdapter {

private static Logger logger = Logger.getLogger(ODFDOMFindReplaceAdapter.class.getName());
public static final String DOCUMENT_ERROR = "DOCUMENT_ERROR";
public static final String CONFIG_ERROR = "CONFIG_ERROR";

@Inject
WorkflowService workflowService;

@Inject
SnapshotService snapshotService;

@Inject
ReportService reportService;

/**
* This method computes the findreplace configuration and updates an attachments
* form type .docx
*
* @throws PluginException
*/
@SuppressWarnings("unchecked")
@Override
public ItemCollection execute(ItemCollection document, ItemCollection event)
throws AdapterException, PluginException {

// read the config
ItemCollection odfConfig = workflowService.evalWorkflowResult(event, "odf-update", document, false);
if (odfConfig == null || !odfConfig.hasItem("findreplace")) {
throw new PluginException(ODFDOMFindReplaceAdapter.class.getSimpleName(), CONFIG_ERROR,
"wrong odf configuration");
}
String fileName = odfConfig.getItemValueString("filename");
fileName = workflowService.adaptText(fileName, document);

if (!fileName.toLowerCase().endsWith(".odt") && !fileName.toLowerCase().endsWith(".ods")) {
throw new PluginException(ODFDOMFindReplaceAdapter.class.getSimpleName(), CONFIG_ERROR,
"Only .odt, .ods files are supported!");
}
List<String> replaceDevList = odfConfig.getItemValue("findreplace");
String eval = odfConfig.getItemValueString("eval");
if (replaceDevList.size() == 0) {
throw new PluginException(ODFDOMFindReplaceAdapter.class.getSimpleName(), CONFIG_ERROR,
"wrong odf configuration");
}

logger.info("......starting update file: " + fileName + "...");

// First we test if the fileName is unique. If not found we test regular
// expressions...
boolean foundFile = false;
FileData fileData = document.getFileData(fileName);
if (fileData != null) {
// file data found - so we can updated it....
foundFile = true;
updateFileData(fileData, document, replaceDevList, eval);
} else {
// not found, we can test regular expressions...
List<String> fileNames = document.getFileNames();
Pattern pattern = Pattern.compile(fileName);
// get all fileNames....
for (String aFileName : fileNames) {
// test if aFilename matches the pattern or the pattern is null
if (pattern.matcher(aFileName).find()) {
// fetch the file
fileData = document.getFileData(aFileName);
if (fileData != null) {
// file data found - so we can updated it....
foundFile = true;
updateFileData(fileData, document, replaceDevList, eval);
}

}
}
}

// did we found at least one file
if (foundFile == false) {
throw new PluginException(ODFDOMFindReplaceAdapter.class.getSimpleName(), CONFIG_ERROR,
"wrong odf configuration - no file found matching '" + fileName + "' !");
}

return document;
}

/**
* This helper method updates the content of a given FileData object with a
* replaceDevList
* <p>
* The method verifies if the content of the file need to be loaded from the
* snapshot
* <p>
*
* @param fileData - the fileData object containing the text or calc file
* @param replaceDevList - list of text markers or cell positions to be replaced
* @param eval - optional list of cell positions to be evaluated after
* update (this is for XSSFWorkbooks only)
* @throws IOException
* @throws PluginException
*
*/
public void updateFileData(FileData fileData, ItemCollection document, List<String> replaceDevList, String eval)
throws PluginException {
byte[] newContent = null;

String fileName = fileData.getName();
if (fileData.getContent() == null || fileData.getContent().length < 3) {
// load the snapshot
fileData = snapshotService.getWorkItemFile(document.getUniqueID(), fileName);
}

// odf files....
if (fileName.toLowerCase().endsWith(".odf")) {

try (InputStream inputStream = new ByteArrayInputStream(fileData.getContent())) {

OdfTextDocument doc = (OdfTextDocument) OdfDocument.loadDocument(inputStream);

logger.info("OdfDocument loaded");

for (String entityDev : replaceDevList) {
ItemCollection entityData = XMLParser.parseItemStructure(entityDev);

if (entityData != null) {
String find = entityData.getItemValueString("find");
find = workflowService.adaptText(find, document);
String replace = entityData.getItemValueString("replace");
replace = workflowService.adaptText(replace, document);
replaceODFDocument(doc, find, replace);
}
}

logger.info("findreplace completed");

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
doc.save(byteArrayOutputStream);
doc.close();
newContent = byteArrayOutputStream.toByteArray();

} catch (Exception e) {
logger.warning("Failed to convert document: " + e.getMessage());
throw new PluginException(ODFDOMFindReplaceAdapter.class.getSimpleName(), DOCUMENT_ERROR,
"unable to update document '" + fileName + "': " + e.getMessage());
}

FileData fileDataNew = new FileData(fileData.getName(), newContent, fileData.getContentType(), null);
// update the fileData
document.addFileData(fileDataNew);
logger.info("new document added");
}
}

/**
* Helper method replaces a given text in a OdfTextDocument
*
* @throws InvalidNavigationException
*/
private void replaceODFDocument(OdfTextDocument doc, String pattern, String replace)
throws InvalidNavigationException {

TextNavigation searchPattern = new TextNavigation(pattern, doc);
while (searchPattern.hasNext()) {
logger.info("..found match!");

TextSelection textSelection = (TextSelection) searchPattern.getCurrentItem();

logger.info("...Position=" + textSelection.getIndex());
logger.info("...Text=" + textSelection.getText());

textSelection.replaceWith(replace);
}

}

}

0 comments on commit d357984

Please sign in to comment.