Comment appliquer une Logique personnalisée aux régions non fusionnées

Contents
[ ]

Dans certaines situations, il n’est pas souhaitable de supprimer complètement les régions non fusionnées du document pendant Mail Merge ou le document semble incomplet. Cela peut se produire lorsque l’absence de données d’entrée doit être affichée à l’utilisateur sous la forme d’un message au lieu que la région soit complètement supprimée.

Il y a aussi des moments où la suppression de la région inutilisée seule ne suffit pas, par exemple, si la région est précédée d’un titre ou si la région est contenue dans une table. Si cette région n’est pas utilisée, le titre et le tableau resteront toujours après la suppression de la région, ce qui semblera déplacé dans le document.

Cet article fournit une solution pour définir manuellement comment les régions inutilisées du document sont gérées. Le code de base de cette fonctionnalité est fourni et peut être facilement réutilisé dans un autre projet.

La logique à appliquer à chaque région est définie à l’intérieur d’une classe qui implémente l’interface IFieldMergingCallback. De la même manière, un gestionnaire Mail Merge peut être configuré pour contrôler la façon dont chaque champ est fusionné, ce gestionnaire peut être configuré pour effectuer des actions sur chaque champ dans une région inutilisée ou sur la région dans son ensemble. Dans ce gestionnaire, vous pouvez définir le code pour modifier le texte d’une région, supprimer des nœuds ou des lignes et cellules vides, etc.

Dans cet exemple, nous utiliserons le document affiché ci-dessous. Il contient des régions imbriquées et une région contenue dans une table.

apply-custom-logic-to-unmerged-regions-aspose-words-java

À titre de démonstration rapide, nous pouvons exécuter un exemple de base de données sur l’exemple de document avec l’indicateur MailMergeCleanupOptions.REMOVE_UNUSED_REGIONS activé. Cette propriété supprimera automatiquement les régions non fusionnées du document pendant un mail merge.

La source de données inclut deux enregistrements pour la région StoreDetails, mais contient délibérément des données pour les régions ContactDetails enfants pour l’un des enregistrements. De plus, la région Suppliers n’a pas non plus de lignes de données. Cela entraînera le maintien des régions inutilisées dans le document. Le résultat après la fusion du document avec cette source de données est ci-dessous.

merged-regions-aspose-words-java

Comme indiqué sur l’image, vous pouvez voir que la région ContactDetails pour le deuxième enregistrement et les régions Suppliers ont été automatiquement supprimées par le moteur Mail Merge car elles ne contiennent aucune donnée. Cependant, il y a quelques problèmes qui rendent ce document de sortie incomplet:

  • La région ContactDetails laisse toujours un paragraphe avec le texte “Coordonnées”.
  • Dans le même cas, rien n’indique qu’il n’y a pas de numéros de téléphone, seulement un espace vide qui pourrait prêter à confusion.
  • La table et le titre liés à la région Suppliers restent également après la suppression de la région à l’intérieur de la table.

La technique fournie dans cet article montre comment appliquer une logique personnalisée à chaque région non fusionnée pour éviter ces problèmes.

La Solution

Pour appliquer manuellement la logique à chaque région inutilisée du document, nous tirons parti des fonctionnalités déjà disponibles dans Aspose.Words.

Le moteur Mail Merge fournit une propriété pour supprimer les régions inutilisées via l’indicateur MailMergeCleanupOptions.RemoveUnusedRegions. Cela peut être désactivé afin que ces régions ne soient pas touchées pendant a mail merge. Cela nous permet de laisser les régions non fusionnées dans le document et de les gérer nous-mêmes manuellement à la place.

Nous pouvons ensuite tirer parti de la propriété MailMerge.FieldMergingCallback comme moyen d’appliquer notre propre logique personnalisée à ces régions non fusionnées pendant Mail Merge grâce à l’utilisation d’une classe de gestionnaire implémentant l’interface IFieldMergingCallback.

Ce code dans la classe handler est la seule classe que vous devrez modifier pour contrôler la logique appliquée aux régions non fusionnées. L’autre code de cet exemple peut être réutilisé sans modification dans n’importe quel projet.

Cet exemple de projet démontre cette technique. Cela implique les étapes suivantes:

  1. Exécutez Mail Merge sur le document en utilisant votre source de données. L’indicateur MailMergeCleanupOptions.RemoveUnusedRegions est désactivé pour l’instant, nous voulons que les régions restent afin de pouvoir les gérer manuellement. Toutes les régions sans données ne seront pas fusionnées dans le document.
  2. Appelez la méthode ExecuteCustomLogicOnEmptyRegions. Cette méthode est fournie dans cet exemple. Il effectue des actions qui permettent d’appeler le gestionnaire spécifié pour chaque région non fusionnée. Cette méthode est réutilisable et peut être copiée sans modification dans n’importe quel projet qui l’exige (avec toutes les méthodes dépendantes).Cette méthode exécute les étapes suivantes:
    1. Définit le gestionnaire spécifié par l’utilisateur sur la propriété MailMerge.FieldMergingCallback.
    2. Appelle la méthode CreateDataSourceFromDocumentRegions qui accepte les noms de régions contenant Document et ArrayList de l’utilisateur. Cette méthode créera une source de données factice contenant des tables pour chaque région non fusionnée dans le document.
    3. Exécute Mail Merge sur le document à l’aide de la source de données factice. Lorsque Mail Merge est exécuté avec cette source de données, il permet d’appeler le gestionnaire spécifié par l’utilisateur pour chaque région de fusion et d’appliquer la logique personnalisée

Le Code

L’implémentation de la méthode ExecuteCustomLogicOnEmptyRegions se trouve ci-dessous. Cette méthode accepte plusieurs paramètres:

  1. L’objet Document contenant des régions non fusionnées qui doivent être gérées par le gestionnaire passé.
  2. La classe de gestionnaire qui définit la logique à appliquer aux régions non fusionnées. Ce gestionnaire doit implémenter le IFieldMergingCallback interface.
  3. Grâce à l’utilisation de la surcharge appropriée, la méthode peut également accepter un troisième paramètre – une liste de noms de régions sous forme de chaînes. Si cela est spécifié, seuls les noms de région restant le document spécifié dans la liste seront traités manuellement. Les autres régions rencontrées ne seront pas appelées par le gestionnaire et supprimées automatiquement. Lorsque la surcharge avec seulement deux paramètres est spécifiée, chaque région restante du document est incluse par la méthode à gérer manuellement.

Exemple

Montre comment exécuter une logique personnalisée sur des régions inutilisées à l’aide du gestionnaire spécifié.

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-Java
/**
* Applies logic defined in the passed handler class to all unused regions
* in the document. This allows to manually control how unused regions are
* handled in the document.
*
* @param doc The document containing unused regions.
* @param handler The handler which implements the IFieldMergingCallback
* interface and defines the logic to be applied to each unmerged
* region.
*/
public static void executeCustomLogicOnEmptyRegions(Document doc, IFieldMergingCallback handler) throws Exception {
executeCustomLogicOnEmptyRegions(doc, handler, null); // Pass null to handle all regions found in the document.
}
/**
* Applies logic defined in the passed handler class to specific unused
* regions in the document as defined in regionsList. This allows to
* manually control how unused regions are handled in the document.
*
* @param doc The document containing unused regions.
* @param handler The handler which implements the IFieldMergingCallback
* interface and defines the logic to be applied to each unmerged
* region.
* @param regionsList A list of strings corresponding to the region names that are
* to be handled by the supplied handler class. Other regions
* encountered will not be handled and are removed automatically.
*/
public static void executeCustomLogicOnEmptyRegions(Document doc, IFieldMergingCallback handler, ArrayList regionsList) throws Exception {
// Certain regions can be skipped from applying logic to by not adding the table name inside the CreateEmptyDataSource method.
// Enable this cleanup option so any regions which are not handled by the user's logic are removed automatically.
doc.getMailMerge().setCleanupOptions(MailMergeCleanupOptions.REMOVE_UNUSED_REGIONS);
// Set the user's handler which is called for each unmerged region.
doc.getMailMerge().setFieldMergingCallback(handler);
// Execute mail merge using the dummy dataset. The dummy data source contains the table names of
// each unmerged region in the document (excluding ones that the user may have specified to be skipped). This will allow the handler
// to be called for each field in the unmerged regions.
doc.getMailMerge().executeWithRegions(createDataSourceFromDocumentRegions(doc, regionsList));
}
/**
* A helper method that creates an empty Java disconnected ResultSet with
* the specified columns.
*/
private static ResultSet createCachedRowSet(String[] columnNames) throws Exception {
RowSetMetaDataImpl metaData = new RowSetMetaDataImpl();
metaData.setColumnCount(columnNames.length);
for (int i = 0; i < columnNames.length; i++) {
metaData.setColumnName(i + 1, columnNames[i]);
metaData.setColumnType(i + 1, java.sql.Types.VARCHAR);
}
CachedRowSet rowSet = RowSetProvider.newFactory().createCachedRowSet();
;
rowSet.setMetaData(metaData);
return rowSet;
}
/**
* A helper method that adds a new row with the specified values to a
* disconnected ResultSet.
*/
private static void addRow(ResultSet resultSet, String[] values) throws Exception {
resultSet.moveToInsertRow();
for (int i = 0; i < values.length; i++)
resultSet.updateString(i + 1, values[i]);
resultSet.insertRow();
// This "dance" is needed to add rows to the end of the result set properly.
// If I do something else then rows are either added at the front or the result
// set throws an exception about a deleted row during mail merge.
resultSet.moveToCurrentRow();
resultSet.last();
}

Exemple

Définit la méthode utilisée pour gérer manuellement les régions non fusionnées.

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-Java
/**
* Returns a DataSet object containing a DataTable for the unmerged regions
* in the specified document. If regionsList is null all regions found
* within the document are included. If an ArrayList instance is present the
* only the regions specified in the list that are found in the document are
* added.
*/
private static DataSet createDataSourceFromDocumentRegions(Document doc, ArrayList regionsList) throws Exception {
final String TABLE_START_MARKER = "TableStart:";
DataSet dataSet = new DataSet();
String tableName = null;
for (String fieldName : doc.getMailMerge().getFieldNames()) {
if (fieldName.contains(TABLE_START_MARKER)) {
tableName = fieldName.substring(TABLE_START_MARKER.length());
} else if (tableName != null) {
// Only add the table as a new DataTable if it doesn't already exists in the DataSet.
if (dataSet.getTables().get(tableName) == null) {
ResultSet resultSet = createCachedRowSet(new String[]{fieldName});
// We only need to add the first field for the handler to be called for the fields in the region.
if (regionsList == null || regionsList.contains(tableName)) {
addRow(resultSet, new String[]{"FirstField"});
}
dataSet.getTables().add(new DataTable(resultSet, tableName));
}
tableName = null;
}
}
return dataSet;
}

Cette méthode implique de trouver toutes les régions non fusionnées dans le document. Ceci est accompli en utilisant la méthode MailMerge.GetFieldNames. Cette méthode renvoie tous les champs de fusion du document, y compris les marqueurs de début et de fin de région (représentés par des champs de fusion avec le préfixe TableStart ou TableEnd).

Lorsqu’un champ de fusion TableStart est rencontré, il est ajouté en tant que nouveau DataTable au DataSet. Puisqu’une région peut apparaître plus d’une fois (par exemple parce qu’il s’agit d’une région imbriquée où la région parente a été fusionnée avec plusieurs enregistrements), la table n’est créée et ajoutée que si elle n’existe pas déjà dans DataSet.

Lorsqu’un début de région approprié a été trouvé et ajouté à la base de données, le champ suivant (qui correspond au premier champ de la région) est ajouté au DataTable. Seul le premier champ doit être ajouté pour chaque champ de la région à fusionner et à transmettre au gestionnaire.

Nous définissons également la valeur de champ du premier champ sur “FirstField " pour faciliter l’application de la logique au premier ou aux autres champs de la région. En incluant cela, cela signifie qu’il n’est pas nécessaire de coder en dur le nom du premier champ ou d’implémenter du code supplémentaire pour vérifier si le champ actuel est le premier dans le code du gestionnaire.

Le code ci-dessous montre comment fonctionne ce système. Le document présenté au début de cet article est fusionné avec la même source de données, mais cette fois, les régions inutilisées sont gérées par du code personnalisé.

Exemple

Montre comment gérer les régions non fusionnées après Mail Merge avec du code défini par l’utilisateur.

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-Java
// Open the document.
Document doc = new Document(dataDir + "TestFile.doc");
// Create a data source which has some data missing.
// This will result in some regions that are merged and some that remain after executing mail merge.
DataSet data = getDataSource();
// Make sure that we have not set the removal of any unused regions as we will handle them manually.
// We achieve this by removing the RemoveUnusedRegions flag from the cleanup options by using the AND and NOT bitwise operators.
doc.getMailMerge().setCleanupOptions(doc.getMailMerge().getCleanupOptions() & ~MailMergeCleanupOptions.REMOVE_UNUSED_REGIONS);
// Execute mail merge. Some regions will be merged with data, others left unmerged.
doc.getMailMerge().executeWithRegions(data);
// The regions which contained data now would of been merged. Any regions which had no data and were
// not merged will still remain in the document.
Document mergedDoc = doc.deepClone(); //ExSkip
// Apply logic to each unused region left in the document using the logic set out in the handler.
// The handler class must implement the IFieldMergingCallback interface.
executeCustomLogicOnEmptyRegions(doc, new EmptyRegionsHandler());
// Save the output document to disk.
doc.save(dataDir + "TestFile.CustomLogicEmptyRegions1 Out.doc");

Le code effectue différentes opérations en fonction du nom de la région récupérée à l’aide de la propriété FieldMergingArgs.TableName. Notez qu’en fonction de votre document et de vos régions, vous pouvez coder le gestionnaire pour exécuter une logique dépendante de chaque région ou code qui s’applique à chaque région non fusionnée du document ou une combinaison des deux.

La logique de la région ContactDetails implique de modifier le texte de chaque champ de la région ContactDetails avec un message approprié indiquant qu’il n’y a pas de données. Les noms de chaque champ sont mis en correspondance dans le gestionnaire à l’aide de la propriété FieldMergingArgs.FieldName.

Un processus similaire est appliqué à la région Suppliers avec l’ajout de code supplémentaire pour gérer la table qui contient la région. Le code vérifiera si la région est contenue dans une table (car elle a peut-être déjà été supprimée). Si c’est le cas, il supprimera l’intégralité du tableau du document ainsi que le paragraphe qui le précède tant qu’il est formaté avec un style de titre par exemple “Heading 1”.

Exemple

Montre comment définir une logique personnalisée dans un gestionnaire implémentant IFieldMergingCallback qui est exécuté pour les régions non fusionnées dans le document.

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-Java
public static class EmptyRegionsHandler implements IFieldMergingCallback {
/**
* Called for each field belonging to an unmerged region in the
* document.
*/
public void fieldMerging(FieldMergingArgs args) throws Exception {
// Change the text of each field of the ContactDetails region individually.
if ("ContactDetails".equals(args.getTableName())) {
// Set the text of the field based off the field name.
if ("Name".equals(args.getFieldName()))
args.setText("(No details found)");
else if ("Number".equals(args.getFieldName()))
args.setText("(N/A)");
}
// Remove the entire table of the Suppliers region. Also check if the previous paragraph
// before the table is a heading paragraph and if so remove that too.
if ("Suppliers".equals(args.getTableName())) {
Table table = (Table) args.getField().getStart().getAncestor(NodeType.TABLE);
// Check if the table has been removed from the document already.
if (table.getParentNode() != null) {
// Try to find the paragraph which precedes the table before the table is removed from the document.
if (table.getPreviousSibling() != null && table.getPreviousSibling().getNodeType() == NodeType.PARAGRAPH) {
Paragraph previousPara = (Paragraph) table.getPreviousSibling();
if (isHeadingParagraph(previousPara))
previousPara.remove();
}
table.remove();
}
}
}
/**
* Returns true if the paragraph uses any Heading style e.g Heading 1 to
* Heading 9
*/
private boolean isHeadingParagraph(Paragraph para) throws Exception {
return (para.getParagraphFormat().getStyleIdentifier() >= StyleIdentifier.HEADING_1 && para.getParagraphFormat().getStyleIdentifier() <= StyleIdentifier.HEADING_9);
}
public void imageFieldMerging(ImageFieldMergingArgs args) throws Exception {
// Do Nothing
}
}

Le résultat du code ci-dessus est illustré ci-dessous. Les champs non fusionnés dans la première région sont remplacés par du texte informatif et la suppression du tableau et de l’en-tête permet au document d’avoir l’air complet.

apply-custom-logic-to-unmerged-regions-aspose-words-java-2

Le code qui supprime la table parente pourrait également être exécuté sur chaque région inutilisée au lieu d’une région spécifique en supprimant la vérification du nom de la table. Dans ce cas, si une région à l’intérieur d’une table n’a été fusionnée avec aucune donnée, la région et la table conteneur seront également automatiquement supprimées.

Nous pouvons insérer un code différent dans le gestionnaire pour contrôler la gestion des régions non fusionnées. L’utilisation du code ci-dessous dans le gestionnaire à la place changera le texte du premier paragraphe de la région en un message utile tandis que tous les paragraphes suivants de la région sont supprimés. Ces autres paragraphes sont supprimés car ils resteraient dans la région après la fusion de notre message.

Le texte de remplacement est fusionné dans le premier champ en définissant le texte spécifié dans la propriété FieldMergingArgs.Text. Le texte de cette propriété est fusionné dans le champ par le moteur Mail Merge.

Le code applique cela uniquement au premier champ de la région en vérifiant la propriété FieldMergingArgs.FieldValue. La valeur du champ du premier champ de la région est marquée par " FirstField”. Cela rend ce type de logique plus facile à implémenter dans de nombreuses régions car aucun code supplémentaire n’est requis.

Exemple

Montre comment remplacer une région inutilisée par un message et supprimer des paragraphes supplémentaires.

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-Java
// Store the parent paragraph of the current field for easy access.
Paragraph parentParagraph = args.getField().getStart().getParentParagraph();
// Define the logic to be used when the ContactDetails region is encountered.
// The region is removed and replaced with a single line of text stating that there are no records.
if ("ContactDetails".equals(args.getTableName())) {
// Called for the first field encountered in a region. This can be used to execute logic on the first field
// in the region without needing to hard code the field name. Often the base logic is applied to the first field and
// different logic for other fields. The rest of the fields in the region will have a null FieldValue.
if ("FirstField".equals(args.getFieldValue())) {
FindReplaceOptions opts = new FindReplaceOptions();
opts.setMatchCase(false);
opts.setFindWholeWordsOnly(false);
// Remove the "Name:" tag from the start of the paragraph
parentParagraph.getRange().replace("Name:", "", opts);
// Set the text of the first field to display a message stating that there are no records.
args.setText("No records to display");
} else {
// We have already inserted our message in the paragraph belonging to the first field. The other paragraphs in the region
// will still remain so we want to remove these. A check is added to ensure that the paragraph has not already been removed.
// which may happen if more than one field is included in a paragraph.
if (parentParagraph.getParentNode() != null)
parentParagraph.remove();
}
}

Le document résultant après l’exécution du code ci-dessus est illustré ci-dessous. La région inutilisée est remplacée par un message indiquant qu’il n’y a aucun enregistrement à afficher.

apply-custom-logic-to-unmerged-regions-aspose-words-java-3

Comme autre exemple, nous pouvons insérer le code ci-dessous à la place du code gérant à l’origine le SuppliersRegion. Cela affichera un message dans le tableau et fusionnera les cellules au lieu de supprimer le tableau du document. Étant donné que la région réside dans un tableau avec plusieurs cellules, il est plus agréable de fusionner les cellules du tableau et de centrer le message.

Exemple

Montre comment fusionner toutes les cellules parentes d’une région inutilisée et afficher un message dans le tableau.

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-Java
// Replace the unused region in the table with a "no records" message and merge all cells into one.
if ("Suppliers".equals(args.getTableName())) {
if ("FirstField".equals(args.getFieldValue())) {
// We will use the first paragraph to display our message. Make it centered within the table. The other fields in other cells
// within the table will be merged and won't be displayed so we don't need to do anything else with them.
parentParagraph.getParagraphFormat().setAlignment(ParagraphAlignment.CENTER);
args.setText("No records to display");
}
// Merge the cells of the table together.
Cell cell = (Cell) parentParagraph.getAncestor(NodeType.CELL);
if (cell != null) {
if (cell.isFirstCell())
cell.getCellFormat().setHorizontalMerge(CellMerge.FIRST); // If this cell is the first cell in the table then the merge is started using "CellMerge.First".
else
cell.getCellFormat().setHorizontalMerge(CellMerge.PREVIOUS); // Otherwise the merge is continued using "CellMerge.Previous".
}
}

Le document résultant après l’exécution du code ci-dessus est illustré ci-dessous.

apply-custom-logic-to-unmerged-regions-aspose-words-java-4

Enfin, nous pouvons appeler la méthode ExecuteCustomLogicOnEmptyRegions et spécifier les noms de tables qui doivent être traités dans notre méthode de gestionnaire, tout en spécifiant les autres à supprimer automatiquement.

Exemple

Montre comment spécifier uniquement la région ContactDetails à gérer via la classe handler.

// For complete examples and data files, please go to https://github.com/aspose-words/Aspose.Words-for-Java
ArrayList<String> regions = new ArrayList<String>();
regions.add("ContactDetails");
executeCustomLogicOnEmptyRegions(doc, new EmptyRegionsHandler(), regions);

L’appel de cette surcharge avec le ArrayList spécifié créera la source de données qui ne contient que des lignes de données pour les régions spécifiées. Les régions autres que la région ContactDetails ne seront pas gérées et seront supprimées automatiquement par le moteur Mail Merge à la place. Le résultat de l’appel ci-dessus en utilisant le code de notre gestionnaire d’origine est illustré ci-dessous.

apply-custom-logic-to-unmerged-regions-aspose-words-java-5