Tables merging after mail merge

I have a template that is set up to output records to rows in a table, with headers in between different tables. I’ve discovered that if there is no extra text besides the headers in the template, all the different tables merge together into a single table, while the headers are shoved to the end of the template. However, if there is any text at all other than the table and the header, then the tables remain separate like they are supposed to.

Is there a setting that controls the table-merging, or is this a bug with my code or Aspose? I’m doing a basic mail-merge on several documents, and then inserting the resulting documents one after another into the same target document, which is what is then saved to a file.

To insert the document I’m using this method, which I found on these forums:

public static Node insertDocumentAfterNode(Node insertAfterNode, Document destDoc, Document srcDoc) throws Exception
{
    synchronized(srcDoc)
    {
        synchronized(insertAfterNode)
        {

            CompositeNode <?> dstStory = insertAfterNode.getParentNode();

            NodeImporter importer = new NodeImporter(srcDoc, destDoc, ImportFormatMode.KEEP_SOURCE_FORMATTING);

            int sectCount = srcDoc.getSections().getCount();
            for (int sectIndex = 0; sectIndex <sectCount; sectIndex++)
            {
                Section srcSection = srcDoc.getSections().get(sectIndex);
                int nodeCount = srcSection.getBody().getChildNodes().getCount();
                for (int nodeIndex = 0; nodeIndex <nodeCount; nodeIndex++)
                {
                    Node srcNode = srcSection.getBody().getChildNodes().get(nodeIndex);
                    Node newNode = importer.importNode(srcNode, true);

                    dstStory.insertAfter(newNode, insertAfterNode);
                    insertAfterNode = newNode;
                }
            }
        }
    }
    return insertAfterNode;
}

I’ve attached the following files:
Template_with_extra_text.doc - This is the template that causes the tables to format correctly.
Result_with_extra_text.doc - This is the result from running the mail-merge on the template with extra text. This is correct.
Template_without_extra_text.doc - This is the template that causes the tables to format incorrectly.
Result_without_extra_text.doc - This is the result from running the mail-merge on the template without the extra text. This is incorrect. You can see all the tables were merged together.

Thank you for any help,

-Dylan Gulick
Jama Software

Hi Dylan,

Thanks for your inquiry.

Most likely you have set the MailMerge.RemoveEmptyParagraphs to true. In recent versions this setting has been improved so that paragraphs which contain region marker are removed as well if they contain no extra text.

This would possibly cause your template to contain no paragraphs surrounding the table which would lead these tables to be joined together when you append documents.

You can fix this issue by adding a blank paragraph between the bottom of your table and your field.

Thanks,

Thank you for the response. I did have removeEmptyRegions set to true, but unfortunately setting it to false resulted in the same problem (see attached).

Our setup is such that I do not necessarily have control over how the template will come in, or whether or not it will contain a table at all. This is why I was hoping there was a programmatic solution to this problem rather than one involving changing the template.

Do you have any suggestions? Thank you.

-Dylan Gulick
Jama Software

Hi Dylan,

Thanks for this additional information.

It seems strange the issue still occurs, the only reason I can think why this would still be happening would be if you were using the InsertDocument method found on the documentation page. This method does not copy the last paragraph from a document if it’s empty which could account for the lack of paragraph between tables. However you use your own method so this is probably not the reason.

In any case you can try using the code below to ensure that tables are properly separated. You can run this code on your final document just before saving.

foreach(Table table in doc.GetChildNodes(NodeType.Table, true))
if (table.NextSibling != null && table.NextSibling.NodeType == NodeType.Table)
    table.ParentNode.InsertAfter(new Paragraph(doc), table);

If we can help with anything else, please feel free to ask.

Thanks,

Thanks for the help. After trying your code and snooping around a bit more I seem to have found the culprit. It appears that this is being caused by a fix I applied for an issue we were having where a lot of unnecessary section breaks would be inserted when copying from document to document. I found the solution here: https://forum.aspose.com/t/67700

When i comment out this code the tables are correctly laid out (but obviously the section breaks remain). Removing the section breaks is a very high priority for us. Is there a way to both remove the extra section breaks and keep the table formatting correct?

Thank you for the help!

-Dylan Gulick
Jama Software

Hi Dylan,

Thanks for this additional information.

If you are using your code above that you posted to insert documents then there shouldn’t be any section breaks as you are copying the node at block level (paragraphs and tables).

Could you please create a simple application which demonstrates this issue? We will then look closer into this for you.

Thanks,

I’ve written up a condensed version of my code that displays the issue. It looks like the issue is related to the copyDocument method. I believe I found that method here on the forums before, but I can’t find exactly where I got it from. If I skip the copyDocument part it works correctly, but breaks if it’s in there.

package com.jamasoftware.contour.report.util;

import java.util.ArrayList;
import java.util.Random;

import com.aspose.words.ControlChar;
import com.aspose.words.Document;
import com.aspose.words.DocumentBuilder;
import com.aspose.words.ImportFormatMode;
import com.aspose.words.Node;
import com.aspose.words.NodeType;
import com.aspose.words.Paragraph;
import com.aspose.words.Run;
import com.aspose.words.Section;
import com.aspose.words.SectionStart;

public class DemonstrateTableIssue
{

    public static Document demonstrateIssue() throws Exception
    {
        Document finalDocument = new Document();
        Document templateDoc = new Document();

        populateTemplateWithTable(templateDoc);

        for (int i = 0; i <4; i++)
        {
            Document tempDoc = copyDocument(templateDoc);
            cleanEmptyParagraphs(tempDoc);
            finalDocument.appendDocument(tempDoc, ImportFormatMode.USE_DESTINATION_STYLES);
        }

        removeContinuousSectionBreak(finalDocument);

        return finalDocument;
    }

    private static void populateTemplateWithTable(Document document)
    {
        DocumentBuilder docBuild = new DocumentBuilder(document);
        String style = docBuild.getParagraphFormat().getStyleName();
        docBuild.getParagraphFormat().setStyleName("Heading 1");
        docBuild.writeln("HEADER");
        docBuild.getParagraphFormat().setStyleName(style);
        docBuild.getParagraphFormat().setKeepTogether(true);
        docBuild.startTable();
        for (int i = 0; i <10; i++)
        {
            String column1Value = generateRandomString();
            String column2Value = generateRandomString();
            docBuild.insertCell();
            docBuild.getCellFormat().setWidth(250);
            docBuild.write(column1Value);
            docBuild.insertCell();
            docBuild.getCellFormat().setWidth(250);
            docBuild.write(column2Value);
            docBuild.endRow();
        }
        docBuild.endTable();
        docBuild.getRowFormat().clearFormatting();
        // docBuild.write("static text");//Uncomment this line to make tables work correctly
    }

    private static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

    private static String generateRandomString()
    {
        StringBuilder sb = new StringBuilder(25);
        for (int i = 0; i <25; i++)
        {
            sb.append(AB.charAt(new Random().nextInt(AB.length())));
        }

        return sb.toString();
    }

    private static void removeContinuousSectionBreak(Document docPrequal)
    {
        // Create a paragraph with page break.
        // We will insert this paragraph ad the end of section and then append content of the next section to the current.
        Paragraph breakParagraph = new Paragraph(docPrequal);
        breakParagraph.appendChild(new Run(docPrequal, ControlChar.PAGE_BREAK));
        breakParagraph.getParagraphBreakFont().setSize(1);
        breakParagraph.getParagraphFormat().setSpaceAfter(0);
        breakParagraph.getParagraphFormat().setSpaceBefore(0);

        ArrayList list = new ArrayList();
        Section firstSection = docPrequal.getFirstSection();
        for (Section section: docPrequal.getSections())
        {
            if (section.getPageSetup().getSectionStart() == SectionStart.CONTINUOUS && section != firstSection)
            {
                list.add(section);
                firstSection.appendContent(section);
            }
            else if (section.getPageSetup().getSectionStart() == SectionStart.NEW_PAGE && section != firstSection)
            {
                // Append paragraph with page break.
                firstSection.getBody().appendChild(breakParagraph.deepClone(true));
                list.add(section);
                firstSection.appendContent(section);
            }
            else
            {
                firstSection = section;
            }
        }

        for (Section section: list)
        {
            docPrequal.removeChild(section);
        }
    }

    private static void cleanEmptyParagraphs(Document doc)
    {
        Node[] paragraphs = doc.getChildNodes(NodeType.PARAGRAPH, true).toArray();
        for (Node paragraph: paragraphs)
        {
            try
            {
                if (!((Paragraph) paragraph).hasChildNodes())
                    paragraph.remove();
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }

    private static Document copyDocument(Document doc) throws Exception
    {
        Document newDoc = new Document();

        newDoc.appendDocument(doc, ImportFormatMode.KEEP_SOURCE_FORMATTING);
        for (Section s: newDoc.getSections())
        {
            for (Paragraph p: s.getBody().getParagraphs())
            {
                p.getParagraphFormat().setOutlineLevel(3);
            }
            s.getPageSetup().setSectionStart(SectionStart.CONTINUOUS);
        }
        return newDoc;
    }
}

Hi there,

Thanks for your inquiry.

I think to achieve what you are looking for you can simply remove the call to cleanEmptyParagraphs and change one line in the copyDocument method:

private static Document copyDocument(Document doc) throws Exception
{
    Document newDoc = new Document();
    newDoc.removeAllChildren();

    newDoc.appendDocument(doc, ImportFormatMode.KEEP_SOURCE_FORMATTING);
    for (Section s: newDoc.getSections())
    {
        for (Paragraph p: s.getBody().getParagraphs())
        {
            p.getParagraphFormat().setOutlineLevel(3);
        }
        s.getPageSetup().setSectionStart(SectionStart.CONTINUOUS);
    }
    return newDoc;
}

If there are any other problems, please let us know.

Thanks,

Thank you very much, this worked great! I appreciate the help.

-Dylan Gulick
Jama Software