Come applicare la logica personalizzata alle regioni non unite
Ci sono alcune situazioni in cui la rimozione completa delle regioni non unite dal documento durante Mail Merge non è desiderata o il documento appare incompleto. Ciò può verificarsi quando l’assenza di dati di input deve essere visualizzata all’utente sotto forma di messaggio anziché la regione completamente rimossa.
Ci sono anche momenti in cui la rimozione della regione inutilizzata da sola non è sufficiente, ad esempio, se la regione è preceduta da un titolo o la regione è contenuta in una tabella. Se questa regione non è utilizzata, il titolo e la tabella rimarranno comunque dopo la rimozione della regione che apparirà fuori posto nel documento.
In questo articolo viene fornita una soluzione per definire manualmente come vengono gestite le aree non utilizzate nel documento. Il codice di base per questa funzionalità viene fornito e può essere facilmente riutilizzato in un altro progetto.
La logica da applicare a ciascuna regione è definita all’interno di una classe che implementa l’interfaccia IFieldMergingCallback. Allo stesso modo, un gestore Mail Merge può essere impostato per controllare come ogni campo viene unito, questo gestore può essere impostato per eseguire azioni su ciascun campo in un’area non utilizzata o sull’intera area. All’interno di questo gestore, è possibile impostare il codice per modificare il testo di un’area, rimuovere nodi o righe e celle vuote, ecc.
In questo esempio, utilizzeremo il documento visualizzato di seguito. Contiene regioni nidificate e un’area contenuta in una tabella.
Come dimostrazione rapida, possiamo eseguire un database di esempio sul documento di esempio con il flag MailMergeCleanupOptions.REMOVE_UNUSED_REGIONS abilitato. Questa proprietà rimuoverà automaticamente le regioni non unite dal documento durante un mail merge.
L’origine dati include due record per l’area StoreDetails ma contiene di proposito tutti i dati per le aree figlio ContactDetails per uno dei record. Inoltre, la regione Suppliers non ha nemmeno righe di dati. Ciò farà sì che le aree non utilizzate rimangano nel documento. Il risultato dopo l’unione del documento con questa origine dati è riportato di seguito.
Come indicato nell’immagine, è possibile vedere che la regione ContactDetails per il secondo record e le regioni Suppliers sono state rimosse automaticamente dal motore Mail Merge in quanto non hanno dati. Tuttavia, ci sono alcuni problemi che rendono questo documento di output incompleto:
- L’area ContactDetails lascia comunque un paragrafo con il testo “Dettagli di contatto”.
- Nello stesso caso non vi è alcuna indicazione che non ci siano numeri di telefono, solo uno spazio vuoto che potrebbe portare a confusione.
- La tabella e il titolo relativi alla regione Suppliers rimangono anche dopo che la regione all’interno della tabella è stata rimossa.
La tecnica fornita in questo articolo illustra come applicare la logica personalizzata a ciascuna regione non integrata per evitare questi problemi.
Soluzione
Per applicare manualmente la logica a ciascuna regione non utilizzata nel documento, sfruttiamo le funzionalità già disponibili in Aspose.Words.
Il motore Mail Merge fornisce una proprietà per rimuovere le regioni inutilizzate tramite il flag MailMergeCleanupOptions.RemoveUnusedRegions. Questo può essere disabilitato in modo che tali regioni non vengano toccate durante un mail merge. Ciò ci consente di lasciare le regioni non unite nel documento e gestirle manualmente.
Possiamo quindi sfruttare la proprietà MailMerge.FieldMergingCallback come mezzo per applicare la nostra logica personalizzata a queste regioni non unite durante Mail Merge attraverso l’uso di una classe handler che implementa l’interfaccia IFieldMergingCallback.
Questo codice all’interno della classe handler è l’unica classe che dovrai modificare per controllare la logica applicata alle regioni non collegate. L’altro codice in questo esempio può essere riutilizzato senza modifiche in qualsiasi progetto.
Questo progetto di esempio dimostra questa tecnica. Comporta i seguenti passaggi:
- Esegui Mail Merge sul documento utilizzando l’origine dati. Il flag MailMergeCleanupOptions.RemoveUnusedRegions è disabilitato per ora vogliamo che le regioni rimangano in modo da poterle gestire manualmente. Tutte le regioni senza dati verranno lasciate non integrate nel documento.
- Chiama il metodo ExecuteCustomLogicOnEmptyRegions. Questo metodo è fornito in questo esempio. Esegue azioni che consentono al gestore specificato di essere chiamato per ogni regione non collegata. Questo metodo è riutilizzabile e può essere copiato inalterato in qualsiasi progetto che lo richieda (insieme a qualsiasi metodo dipendente).Questo metodo esegue i seguenti passaggi:
- Imposta il gestore specificato dall’utente sulla proprietà MailMerge.FieldMergingCallback.
- Chiama il metodo CreateDataSourceFromDocumentRegions che accetta i nomi delle regioni contenenti Document e ArrayList dell’utente. Questo metodo creerà un’origine dati fittizia contenente tabelle per ogni area non aggregata nel documento.
- Esegue Mail Merge sul documento utilizzando l’origine dati fittizia. Quando Mail Merge viene eseguito con questa origine dati, consente di chiamare il gestore specificato dall’utente per ogni regione di unmerge e applicare la logica personalizzata
Codice
L’implementazione per il metodo ExecuteCustomLogicOnEmptyRegions si trova di seguito. Questo metodo accetta diversi parametri:
- L’oggetto Document contenente le regioni non unite che devono essere gestite dal gestore passato.
- La classe handler che definisce la logica da applicare alle regioni non unite. Questo gestore deve implementare il IFieldMergingCallback interfaccia.
- Attraverso l’uso del sovraccarico appropriato, il metodo può anche accettare un terzo parametro: un elenco di nomi di regioni come stringhe. Se questo è specificato, verranno gestiti manualmente solo i nomi delle regioni rimanenti nel documento specificato nell’elenco. Altre regioni incontrate non verranno richiamate dal gestore e rimosse automaticamente. Quando viene specificato il sovraccarico con solo due parametri, ogni regione rimanente nel documento viene inclusa dal metodo da gestire manualmente.
Esempio
Mostra come eseguire la logica personalizzata sulle regioni inutilizzate utilizzando il gestore specificato.
// 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(); | |
} |
Esempio
Definisce il metodo utilizzato per gestire manualmente le regioni non unite.
// 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; | |
} |
Questo metodo comporta la ricerca di tutte le regioni non unite nel documento. Questo viene eseguito utilizzando il metodo MailMerge.GetFieldNames. Questo metodo restituisce tutti i campi di unione nel documento, inclusi i marcatori di inizio e fine regione (rappresentati da campi di unione con il prefisso TableStart o TableEnd).
Quando viene rilevato un campo di unione TableStart
, questo viene aggiunto come nuovo DataTable al DataSet. Poiché un’area può apparire più di una volta (ad esempio perché è un’area nidificata in cui l’area padre è stata unita a più record), la tabella viene creata e aggiunta solo se non esiste già in DataSet.
Quando è stato trovato e aggiunto al database un inizio regione appropriato, il campo successivo (che corrisponde al primo campo nella regione) viene aggiunto a DataTable. È necessario aggiungere solo il primo campo per ogni campo nell’area da unire e passare al gestore.
Impostiamo anche il valore del campo del primo campo su “FirstField " per semplificare l’applicazione della logica al primo o ad altri campi nell’area. Includendo questo significa che non è necessario codificare il nome del primo campo o implementare codice aggiuntivo per verificare se il campo corrente è il primo nel codice del gestore.
Il codice seguente mostra come funziona questo sistema. Il documento mostrato all’inizio di questo articolo è remerged con la stessa origine dati, ma questa volta, le regioni inutilizzate sono gestite da codice personalizzato.
Esempio
Mostra come gestire le regioni non unite dopo Mail Merge con codice definito dall’utente.
// 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"); |
Il codice esegue diverse operazioni in base al nome della regione recuperata utilizzando la proprietà FieldMergingArgs.TableName. Si noti che, a seconda del documento e delle regioni, è possibile codificare il gestore per eseguire la logica dipendente da ciascuna regione o codice che si applica a ogni regione non integrata nel documento o una combinazione di entrambi.
La logica per la regione ContactDetails comporta la modifica del testo di ciascun campo nella regione ContactDetails con un messaggio appropriato che indica che non ci sono dati. I nomi di ciascun campo vengono abbinati all’interno del gestore utilizzando la proprietà FieldMergingArgs.FieldName.
Un processo simile viene applicato alla regione Suppliers con l’aggiunta di codice aggiuntivo per gestire la tabella che contiene la regione. Il codice controllerà se la regione è contenuta all’interno di una tabella (in quanto potrebbe essere già stata rimossa). Se lo è, rimuoverà l’intera tabella dal documento e il paragrafo che lo precede purché sia formattato con uno stile di intestazione, ad esempio “Heading 1”.
Esempio
Mostra come definire la logica personalizzata in un gestore che implementa IFieldMergingCallback eseguito per le regioni non associate nel documento.
// 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 | |
} | |
} |
Il risultato del codice di cui sopra è mostrato di seguito. I campi non integrati all’interno della prima regione vengono sostituiti con testo informativo e la rimozione della tabella e dell’intestazione consente al documento di apparire completo.
Il codice che rimuove la tabella padre potrebbe anche essere eseguito su ogni regione inutilizzata anziché solo su una regione specifica rimuovendo il controllo per il nome della tabella. In questo caso, se una qualsiasi regione all’interno di una tabella non è stata unita a nessun dato, anche la regione e la tabella contenitore verranno rimosse automaticamente.
Possiamo inserire codice diverso nel gestore per controllare come vengono gestite le regioni non collegate. Utilizzando il codice qui sotto nel gestore invece cambierà il testo nel primo paragrafo della regione di un messaggio utile, mentre eventuali paragrafi successivi nella regione vengono rimossi. Questi altri paragrafi vengono rimossi in quanto rimarrebbero nella regione dopo la fusione del nostro messaggio.
Il testo sostitutivo viene unito al primo campo impostando il testo specificato nella proprietà FieldMergingArgs.Text. Il testo di questa proprietà viene unito al campo dal motore Mail Merge.
Il codice lo applica solo per il primo campo nell’area selezionando la proprietà FieldMergingArgs.FieldValue. Il valore del campo del primo campo nella regione è contrassegnato con " FirstField”. Questo rende questo tipo di logica più facile da implementare su molte regioni in quanto non è richiesto alcun codice aggiuntivo.
Esempio
Mostra come sostituire un’area non utilizzata con un messaggio e rimuovere paragrafi aggiuntivi.
// 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(); | |
} | |
} |
Il documento risultante dopo che il codice sopra è stato eseguito è mostrato di seguito. L’area non utilizzata viene sostituita con un messaggio che indica che non ci sono record da visualizzare.
Come altro esempio, possiamo inserire il codice qui sotto al posto del codice che originariamente gestiva SuppliersRegion. Questo visualizzerà un messaggio all’interno della tabella e unirà le celle invece di rimuovere la tabella dal documento. Poiché la regione risiede all’interno di una tabella con più celle, sembra più bello avere le celle della tabella unite insieme e il messaggio centrato.
Esempio
Mostra come unire tutte le celle padre di un’area inutilizzata e visualizzare un messaggio all’interno della tabella.
// 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". | |
} | |
} |
Il documento risultante dopo che il codice sopra è stato eseguito è mostrato di seguito.
Infine, possiamo chiamare il metodo ExecuteCustomLogicOnEmptyRegions e specificare i nomi delle tabelle che devono essere gestiti all’interno del nostro metodo di gestione, specificando gli altri da rimuovere automaticamente.
Esempio
Mostra come specificare solo l’area ContactDetails
da gestire tramite 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); |
Chiamare questo sovraccarico con ArrayList specificato creerà l’origine dati che contiene solo righe di dati per le regioni specificate. Le regioni diverse dall’area ContactDetails
non verranno gestite e verranno rimosse automaticamente dal motore Mail Merge. Il risultato della chiamata di cui sopra utilizzando il codice nel nostro gestore originale è mostrato di seguito.