マージされていないリージョンにカスタムロジックを適用する方法

Contents
[ ]

Mail Merge中に文書からマージされていない領域を完全に削除することが望ましくない場合や、文書が不完全に見える場合があります。 これは、領域が完全に削除されるのではなく、入力データがないことをメッセージの形でユーザーに表示する必要がある場合に発生する可能性があります。

また、領域の前にタイトルが付いている場合や、領域がテーブルに含まれている場合など、未使用の領域を単独で削除するだけでは不十分な場合もあ この領域が使用されていない場合、領域が削除された後もタイトルとテーブルは残り、ドキュメント内で場違いに見えます。

この記事では、ドキュメント内の未使用領域の処理方法を手動で定義するための解決策を提供します。 この機能の基本コードが提供されており、別のプロジェクトで簡単に再利用できます。

各領域に適用されるロジックは、IFieldMergingCallbackインターフェイスを実装するクラス内で定義されます。 同様に、Mail Mergeハンドラを設定して各フィールドのマージ方法を制御することができ、このハンドラを設定して、未使用のリージョン内の各フィールドまたはリージョン全体に対してアクションを実行することができます。 このハンドラー内で、領域のテキストを変更したり、ノードを削除したり、空の行やセルなどを削除したりするコードを設定できます。

このサンプルでは、以下に表示されるドキュメントを使用します。 これには、ネストされた領域と、テーブル内に含まれる領域が含まれます。

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

簡単なデモとして、MailMergeCleanupOptions.REMOVE_UNUSED_REGIONSフラグを有効にしてサンプルドキュメント上でサンプルデータベースを実行できます。 このプロパティは、mail merge中に文書からマージされていない領域を自動的に削除します。

データソースにはStoreDetails領域の2つのレコードが含まれていますが、意図的には、いずれかのレコードの子ContactDetails領域のデータがあります。 さらに、Suppliers領域にはデータ行もありません。 これにより、未使用の領域がドキュメントに残ります。 このデータソースとドキュメントをマージした後の結果は以下のとおりです。

merged-regions-aspose-words-java

画像に記載されているように、2番目のレコードのContactDetails領域とSuppliers領域はデータがないため、Mail Mergeエンジンによって自動的に削除されていることがわかります。 ただし、この出力ドキュメントが不完全に見えるいくつかの問題があります:

  • ContactDetails領域には、“連絡先の詳細"というテキストが含まれた段落が残ります。
  • 同じケースでは、電話番号がないことを示すものではなく、混乱につながる可能性のある空白のみがあります。
  • Suppliers領域に関連するテーブルとタイトルは、テーブル内の領域が削除された後も残ります。

この記事で提供される手法は、これらの問題を回避するために、マージされていない各リージョンにカスタムロジックを適用する方法を示しています。

解決策

ドキュメント内の未使用の各リージョンに手動でロジックを適用するには、Aspose.Wordsで既に利用可能な機能を利用します。

Mail Mergeエンジンは、MailMergeCleanupOptions.RemoveUnusedRegionsフラグを使用して未使用領域を削除するプロパティを提供します。 これは、mail merge中にそのような領域がそのまま残されるように無効にすることができます。 これにより、マージされていない領域をドキュメントに残して、代わりに手動で処理することができます。

次に、IFieldMergingCallback インターフェースを実装するハンドラー クラスを使用して、Mail Merge 中にこれらのマージされていない領域に独自のカスタム ロジックを適用する手段として、MailMerge.FieldMergingCallback プロパティを利用できます。

Handlerクラス内のこのコードは、マージされていない領域に適用されるロジックを制御するために変更する必要がある唯一のクラスです。 このサンプルの他のコードは、どのプロジェクトでも変更せずに再利用できます。

このサンプルプロジェクトは、この手法を示しています。 これには、次の手順が含まれます。:

  1. データソースを使用してドキュメントに対してMail Mergeを実行します。 MailMergeCleanupOptions.RemoveUnusedRegionsフラグは無効になっています今のところ、領域を手動で処理できるようにしたいと考えています。 データのないリージョンは、ドキュメント内でマージされません。
  2. ExecuteCustomLogicOnEmptyRegionsメソッドを呼び出します。 この方法は、このサンプルで提供されています。 マージされていない領域ごとに指定されたハンドラを呼び出すことを可能にするアクションを実行します。 このメソッドは再利用可能であり、それを必要とする任意のプロジェクトに変更されずにコピーできます(依存するメソッドとともに)。このメソッドは、次の手順を実行します:
    1. ユーザーが指定したハンドラーをMailMerge.FieldMergingCallbackプロパティに設定します。
    2. 地域名を含むユーザーのDocumentArrayListを受け入れるCreateDataSourceFromDocumentRegionsメソッドを呼び出します。 このメソッドは、ドキュメント内のマージされていない各領域のテーブルを含むダミーデータソースを作成します。
    3. ダミーデータソースを使用してドキュメント上でMail Mergeを実行します。 このデータソースでMail Mergeを実行すると、アンマージ領域と適用されるカスタムロジックごとにユーザー指定のハンドラーを呼び出すことができます

コード

ExecuteCustomLogicOnEmptyRegionsメソッドの実装は以下の通りです。 このメソッドは、いくつかのパラメータを受け入れます:

  1. 渡されたハンドラーによって処理されるマージされていない領域を含むDocumentオブジェクト。
  2. マージされていない領域に適用するロジックを定義するhandlerクラス。 このハンドラーは、以下を実装する必要があります IFieldMergingCallback インターフェイス。
  3. 適切なオーバーロードを使用することで、このメソッドは3番目のパラメーター、つまり文字列としての領域名のリストを受け入れることもできます。 これが指定された場合、リストに指定された文書に残っている地域名のみが手動で処理されます。 見つかった他のリージョンはハンドラから呼び出されず、自動的に削除されません。 パラメータが2つだけのオーバーロードを指定すると、ドキュメント内の残りのすべての領域が手動で処理されるメソッドに含まれます。

例を示します。

指定されたハンドラーを使用して、未使用領域でカスタムロジックを実行する方法を示します。

// 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();
}

例を示します。

マージされていない領域を手動で処理するために使用されるメソッドを定義します。

// 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;
}

この方法では、文書内のすべてのマージされていない領域を検索します。 これはMailMerge.GetFieldNamesメソッドを使用して実行されます。 このメソッドは、リージョンの開始マーカーと終了マーカー(接頭辞TableStartまたはTableEndの差し込み項目で表されます)を含む、ドキュメント内のすべての差し込み項目を返します。

TableStart差し込み項目が検出されると、これは新しいDataTableとしてDataSetに追加されます。 リージョンは複数回表示される可能性があるため(たとえば、親リージョンが複数のレコードとマージされたネストされたリージョンであるため)、テーブルはDataSetにまだ存在しない場合にのみ作成および追加されます。

適切なリージョンの開始が見つかってデータベースに追加されると、次のフィールド(リージョン内の最初のフィールドに対応)がDataTableに追加されます。 マージされてハンドラーに渡されるリージョン内の各フィールドには、最初のフィールドのみを追加する必要があります。

また、リージョン内の最初のフィールドまたは他のフィールドにロジックを適用しやすくするために、最初のフィールドのフィールド値を"FirstField"に設定します。 これを含めることで、最初のフィールドの名前をハードコードする必要がないことを意味したり、現在のフィールドがハンドラーコードの最初であるかどうかを確認するための追加のコードを実装したりします。

以下のコードは、このシステムがどのように機能するかを示しています。 この記事の冒頭に示されているドキュメントは、同じデータソースで再マージされますが、今回は、未使用の領域はカスタムコードによって処理されます。

例を示します。

Mail Mergeの後のマージされていない領域をユーザー定義コードで処理する方法を示します。

// 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");

このコードは、FieldMergingArgs.TableNameプロパティを使用して取得された領域の名前に基づいて異なる操作を実行します。 ドキュメントとリージョンに応じて、ドキュメント内のマージされていないすべてのリージョンまたは両方の組み合わせに適用される各リージョンまたはコードに依存してロジックを実行するようにハンドラーをコーディングできます。

ContactDetails領域のロジックには、ContactDetails領域の各フィールドのテキストを、データがないことを示す適切なメッセージで変更することが含まれます。 各フィールドの名前は、FieldMergingArgs.FieldNameプロパティを使用してハンドラー内で照合されます。

同様のプロセスがSuppliers領域に適用され、領域を含むテーブルを処理するための余分なコードが追加されます。 コードは、領域がテーブル内に含まれているかどうかをチェックします(すでに削除されている可能性があるため)。 そうであれば、“Heading 1"などの見出しスタイルで書式設定されている限り、文書からテーブル全体とその前の段落が削除されます。

例を示します。

ドキュメント内のマージされていない領域に対して実行されるIFieldMergingCallbackを実装するハンドラーでカスタムロジックを定義する方法を示します。

// 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
}
}

上記のコードの結果を以下に示します。 最初の領域内のマージされていないフィールドは有益なテキストに置き換えられ、テーブルと見出しを削除すると文書が完全に見えるようになります。

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

親テーブルを削除するコードは、テーブル名のチェックを削除することで、特定の領域ではなく、すべての未使用領域で実行するようにすることもできま この場合、テーブル内のいずれかの領域がデータとマージされていない場合、領域とコンテナテーブルの両方も自動的に削除されます。

ハンドラーに別のコードを挿入して、マージされていない領域の処理方法を制御できます。 代わりにハンドラーで以下のコードを使用すると、リージョンの最初の段落のテキストが有用なメッセージに変更され、リージョンの後続の段落は削除されま これらの他の段落は、メッセージをマージした後も地域に残るため、削除されます。

置換テキストは、指定されたテキストをFieldMergingArgs.Textプロパティに設定することにより、最初のフィールドにマージされます。 このプロパティのテキストはMail Mergeエンジンによってフィールドにマージされます。

このコードでは、FieldMergingArgs.FieldValueプロパティをチェックすることにより、地域の最初のフィールドにのみこれが適用されます。 リージョンの最初のフィールドのフィールド値は"FirstField"でマークされています。 これにより、余分なコードを必要としないため、このタイプのロジックを多くのリージョンで実装しやすくなります。

例を示します。

未使用の領域をメッセージに置き換え、余分な段落を削除する方法を示します。

// 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();
}
}

上記のコードが実行された後の結果のドキュメントを以下に示します。 未使用領域は、表示するレコードがないことを示すメッセージに置き換えられます。

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

別の例として、最初にSuppliersRegionを処理していたコードの代わりに、以下のコードを挿入できます。 これにより、テーブル内にメッセージが表示され、ドキュメントからテーブルを削除する代わりにセルがマージされます。 領域は複数のセルを持つテーブル内にあるため、テーブルのセルをマージしてメッセージを中央に配置する方が見栄えが良くなります。

例を示します。

未使用領域のすべての親セルをマージし、テーブル内にメッセージを表示する方法を示します。

// 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".
}
}

上記のコードが実行された後の結果のドキュメントを以下に示します。

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

最後に、ExecuteCustomLogicOnEmptyRegionsメソッドを呼び出し、handlerメソッド内で処理するテーブル名を指定し、他のテーブルを自動的に削除するように指定することができます。

例を示します。

Handlerクラスを介して処理されるContactDetails領域のみを指定する方法を示します。

// 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);

指定されたArrayListを使用してこのオーバーロードを呼び出すと、指定された領域のデータ行のみを含むデータソースが作成されます。 ContactDetails領域以外の領域は処理されず、代わりにMail Mergeエンジンによって自動的に削除されます。 元のハンドラーのコードを使用した上記の呼び出しの結果を以下に示します。

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