Free Support Forum - aspose.com

Problem merging with empty table

I have a document template in word which must be merged using a dataset with multiple tables. Within the document there is a word table with a single row including the required TableStart, TableEnd fields. In version 8 of the API, if a table was empty (no rows) the corresponding area between TableStart and TableEnd would be removed from the resulting document. In Version 9, this does not happen, and the table is left with the fields unmerged, including TableStart and TableEnd, in the resulting document. Why?? It made sense that if table was empty the corresponding area within TableStart, TableEnd should be removed automatically. Is there another way of doing this in version 9?

Hi John,

Thanks for your inquiry.

We are aware that customers want this functionality back. Your request has been linked to the appropriate issue. You will be informed when this is avaliable again.

In the mean time you can use this custom class below to remove the empty regions from your document.

// Open the document.<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

Document doc = new Document("Document Out.doc");

// Execute mail merge. Will have no affect as there is no data.

doc.MailMerge.ExecuteWithRegions(data);

// Remove the unmerged mail merge regions from the document. Pass a boolean true value to remove the parent container of the region i.e the row or table.

doc.Accept(new RemoveEmptyRegions(true));

// Save the output document to disk.

doc.Save(dataDir + "Document Out.doc");

class RemoveEmptyRegions : DocumentVisitor

{

// Defines the markers used for the start and end of regions.

private const string StartRegionText = "tablestart";

private const string EndRegionText = "tableend";

// Internal variables.

private Cell mStartCell;

private bool mRemoveParentContainer;

private static int mRegionDepth = 0;

private Stack mFieldStack = new Stack();

///

/// Removes unmerged mail merge regions from a document.

///

public RemoveEmptyRegions() : this(false)

{

}

///

/// Removes unmerged mail merge regions from a document.

///

/// Specifies whether to remove the parent table or row container of the region. Default is false.

public RemoveEmptyRegions(bool removeContainer)

{

mRemoveParentContainer = removeContainer;

}

///

/// Called when a FieldStart node is encountered in the document.

///

public override <?xml:namespace prefix = st2 ns = "urn:schemas-microsoft-com:office:smarttags" /><?xml:namespace prefix = st1 ns = "urn:schemas:contacts" />VisitorAction VisitFieldStart(FieldStart fieldStart)

{

// Only check merge fields.

if (fieldStart.FieldType == FieldType.FieldMergeField)

{

// Extract the field code. Store it to match when a FieldEnd is encountered.

string fieldCode = GetFieldCode(fieldStart);

mFieldStack.Push(fieldCode);

if (fieldCode.ToLower().Contains(StartRegionText))

{

if (isAtOuterRegion() && isInsideTable(fieldStart))

{

// If this region is inside a table store the cell of the region start for use later.

mStartCell = (Cell)fieldStart.GetAncestor(NodeType.Cell);

}

// Increase region depth.

mRegionDepth++;

}

}

if(isInsideRegion())

fieldStart.Remove();

return VisitorAction.Continue;

}

///

/// Called when a FieldEnd node is encountered in the document.

///

public override VisitorAction VisitFieldEnd(FieldEnd fieldEnd)

{

// Only look at merge fields.

if (fieldEnd.FieldType == FieldType.FieldMergeField)

{

string fieldStartCode = (string)mFieldStack.Pop();

// Matching region end

if (fieldStartCode.ToLower().Contains(EndRegionText))

{

// Decrease region depth.

mRegionDepth--;

if (isAtOuterRegion())

{

// Region is contained inside table and should remove parent container.

if (isInsideTable(fieldEnd) && mRemoveParentContainer)

{

if (mStartCell.Equals(fieldEnd.GetAncestor(NodeType.Cell)))

{

// The region is in one cell. Remove the parent row.

mStartCell.ParentRow.Remove();

}

else

{

// The region spans over a row. Remove the parent table.

mStartCell.ParentRow.ParentTable.Remove();

}

}

// Remove the paragraph of the parent.

fieldEnd.ParentParagraph.Remove();

}

}

if (isInsideRegion())

fieldEnd.Remove();

}

return VisitorAction.Continue;

}

///

/// Called when a FieldSeparator node is encountered in the document.

///

public override VisitorAction VisitFieldSeparator(FieldSeparator fieldSeparator)

{

if (isInsideRegion())

fieldSeparator.Remove();

return VisitorAction.Continue;

}

///

/// Called when visiting of a Paragraph node is ended in the document.

///

public override VisitorAction VisitParagraphEnd(Paragraph paragraph)

{

if (isInsideRegion())

paragraph.Remove();

return VisitorAction.Continue;

}

///

/// Called when a Run node is encountered in the document.

///

public override VisitorAction VisitRun(Run run)

{

if (isInsideRegion())

run.Remove();

return VisitorAction.Continue;

}

///

/// Called when a Table node is encountered in the document.

///

public override VisitorAction VisitTableStart(Table table)

{

if (isInsideRegion())

table.Remove();

return VisitorAction.Continue;

}

///

/// Get the field code of a field passed by the FieldStart node. The field code consists of the text between the

/// FieldStart and FieldSeparator nodes.

///

private string GetFieldCode(FieldStart fieldStart)

{

StringBuilder builder = new StringBuilder();

// Iterate through all nodes between the FieldStart and FieldEnd node and gather the field code.

for (Node node = fieldStart; node != null && node.NodeType != NodeType.FieldSeparator &&

node.NodeType != NodeType.FieldEnd; node = node.NextPreOrder(node.Document))

{

// Use the text of Run nodes only to avoid duplication.

if (node.NodeType == NodeType.Run)

builder.Append(node.GetText());

}

return builder.ToString();

}

///

/// Returns true if a node is contained inside a table.

///

private bool isInsideTable(Node node)

{

return (node.GetAncestor(NodeType.Table) != null) ? true : false;

}

///

/// Returns true if the current position of the visitor is inside a region.

///

private bool isInsideRegion()

{

return mRegionDepth > 0;

}

///

/// Returns true if the visitor is in an outer region (not

///

private bool isAtOuterRegion()

{

return mRegionDepth == 0 ? true : false;

}

}

If you have any troubles please feel free to ask.

Thanks,

Any update on when a fix will be available. We also suffer from the same symptoms…

Hello

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

Thanks for your request. Unfortunately the issue is still unresolved. And currently I cannot provide you any reliable estimate regarding this issue. You will be notified as soon as it is fixed.

While you are waiting for the fix you can try using the workaround provided above.

Best regards,

I’ve given the code above a whirl, and i think it covers most cases… but isn’t quite like how it was in words 8.x.


I believe that if you had a row in a table with text and merge fields and the table was empty, the text would still remain in that one row…with this code it wipes everything out…

Hi Julian,

Thanks for your inquiry.

Could you attach an example document here which demostrates this behaviour and I will provide you with some further information.

Thanks,

Very simple - See my attached file.


If you do a mail merge with regions the text in the middle row is removed.

On another note, im trying to use your code that you posted above but its throwing:
System.InvalidOperationException: Cannot remove because there is no parent.
at Aspose.Words.Node.Remove()
at com.isi.jet.MailMergeRemoveEmptyRegions.VisitFieldEnd(FieldEnd fieldEnd) in RemoveEmptyRegions.cs:line 104
at Aspose.Words.Fields.FieldEnd.Accept(DocumentVisitor visitor)
at Aspose.Words.CompositeNode.x464d2134480a7bf2(DocumentVisitor x672ff13faf031f3d)
at Aspose.Words.CompositeNode.xf7ae36cd24e0b11c(DocumentVisitor x672ff13faf031f3d)
at Aspose.Words.Paragraph.Accept(DocumentVisitor visitor)
at Aspose.Words.CompositeNode.x464d2134480a7bf2(DocumentVisitor x672ff13faf031f3d)
at Aspose.Words.CompositeNode.xf7ae36cd24e0b11c(DocumentVisitor x672ff13faf031f3d)
at Aspose.Words.Tables.Cell.Accept(DocumentVisitor visitor)
at Aspose.Words.CompositeNode.x464d2134480a7bf2(DocumentVisitor x672ff13faf031f3d)
at Aspose.Words.CompositeNode.xf7ae36cd24e0b11c(DocumentVisitor x672ff13faf031f3d)
at Aspose.Words.Tables.Row.Accept(DocumentVisitor visitor)
at Aspose.Words.CompositeNode.x464d2134480a7bf2(DocumentVisitor x672ff13faf031f3d)
at Aspose.Words.CompositeNode.xf7ae36cd24e0b11c(DocumentVisitor x672ff13faf031f3d)
at Aspose.Words.Tables.Table.Accept(DocumentVisitor visitor)
at Aspose.Words.CompositeNode.x464d2134480a7bf2(DocumentVisitor x672ff13faf031f3d)
at Aspose.Words.CompositeNode.xf7ae36cd24e0b11c(DocumentVisitor x672ff13faf031f3d)
at Aspose.Words.Body.Accept(DocumentVisitor visitor)
at Aspose.Words.CompositeNode.x464d2134480a7bf2(DocumentVisitor x672ff13faf031f3d)
at Aspose.Words.CompositeNode.xf7ae36cd24e0b11c(DocumentVisitor x672ff13faf031f3d)
at Aspose.Words.Section.Accept(DocumentVisitor visitor)
at Aspose.Words.CompositeNode.x464d2134480a7bf2(DocumentVisitor x672ff13faf031f3d)
at Aspose.Words.CompositeNode.xf7ae36cd24e0b11c(DocumentVisitor x672ff13faf031f3d)
at Aspose.Words.Document.Accept(DocumentVisitor visitor)

If you add the line:


if (fieldEnd.ParentParagraph.ParentNode !=null)

On line 104 it should solve the issue.

Hi Julian,

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

Thanks for your request. We are planning to add an option to remove empty regions during mail merge. This option will be available in one of the next versions that comes out in the next couple of months. We will notify you once it is available.

Best regards.

Hi Julian,

Thanks for this additional information.

I have tested the behaviour using Aspose.Words 8.2.1 and the output appears the same as the entire table is removed. Regarding the exception, that was a bug that I never came across during my testing. The extra peice of code you suggested is correct.

Thanks,

I think i might have found another problem with this code…


You declare private static int mRegionDepth = 0;

So in my multi threaded server environment, all instances of this class are sharing this mRegionDepth variable. Each thread is increasing and decreasing the count out of context to other documents. (one document could start and the couter might already be at 5)

Am i missing something or should this just be: private int mRegionDepth = 0;

??

Thanks,

Julian


Hi Julian,

Thanks for your inquiry.

Yes it should be an instance member instead, there is no reason for it to be static.

Thanks,

You guys should really update your samples and your documentation with these bugs. They caused serious production issues with us. With that static variable, caused many blank documents.

Hi Julian,

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

My apologizes for inconvenience. We will surely correct the code example. Code suggested by Adam is just a workaround. We will let you know once option to remove empty regions during mail merge is available.

Best regards,

I am currently evaluating the latest version of Aspose.Words and have been hit by this exact same problem. Where the version we currently use removes the empty region, 9.5.0.0 leaves them behind.


We are using a custom IMailMergeDataSource to merge arrays of data in to a table in a Word document. To me it seems perfectly logical that if I merge with an empty array then there should be no output for the mail merge, so that particular region (and table row) should be removed from the document. I think the decision you made with 9.1 to leave them in there with the TableStart fields when I’ve explicitly merged and said there is no data is wrong.

We cannot accept the workaround of the RemoveEmptyRegions class as it will remove all empty merge fields and we may have a requirement for partial mail-merging where some field are left in the template, but others are not. Traversing the whole document and removing all empty regions is overkill when my code has already visited the fields that are to be removed and merged them with empty data.

Please restore the old functionality or we will have considerable trouble upgrading.

Hi Alex,

<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

Thanks for your inquiry. We will add an option that will allow removing empty region, in the next version of Aspose.Words. It comes out in mid of December. We will notify you.

Best regards,

The issues you have found earlier (filed as 17666) have been fixed in this update.

Could you give us correct URL to actual version it was fixed in? Or at least correct version number. The link provided just points to latest version. :frowning:

Hi Graham,


Thanks for your inquiry. This issue was originally fixed in Aspoe.Words version 9.6.0; but, we always encourage our customers to use the latest release versions of Aspose.Words as it contains newly introduced features, enhancements and fixes for issues reported earlier. Please download Aspose.Words v11.8.0 from the following link:
http://www.aspose.com/community/files/51/.net-components/aspose.words-for-.net/default.aspx

Best Regards,