Creating Open XML WordprocessingML Tables with Vertically Merged Cells

Summary:   Learn to create tables with vertically-merged cells in Open XML WordprocessingML documents.

Applies to: Office 2010 | Open XML | Visual Studio Tools for Microsoft Office | Word 2007 | Word 2010

In this article
Introduction
Understanding the Markup
Generating Tables with Merged Cells
Example: Creating Tables with Vertically-Merged Cells
Conclusion
Additional Resources

Published:   August 2010

Provided by:   Eric White, Microsoft Corporation

Contents

  • Introduction

  • Understanding the Markup

  • Generating Tables with Merged Cells

  • Example: Creating Tables with Vertically-Merged Cells

  • Conclusion

  • Additional Resources

Introduction

Vertically merging cells in tables in word-processing documents improves readability. For example, in a table that lists invoices for multiple vendors, vertically merging cells that contain the same vendor makes it apparent which invoices are for a given vendor. When you generate documents, you can generate tables that include vertically-merged cells. This article describes the markup and provides an example of generating a table with vertically-merged cells.

The following image shows a table without vertically-merged cells.

Figure 1. Table without vertically-merged cells

Table without vertically merged cells

The following image shows the same table with vertically-merged cells.

Figure 2. Table with vertically-merged cells

Table with vertically merged cells

Understanding the Markup

The markup for merging cells vertically is not complex, but before we discuss it, first let's review table markup in WordprocessingML.

  • Tables (w:tbl) contain rows (w:tr). Rows contain cells (w:tc), which then contain block-level content such as paragraphs (w:p).

  • A common pattern that you see with WordprocessingML is that certain elements contain a property child element. The naming convention is that the name of the parent element followed by "Pr". For example, tables contain a child Table Property element (w:tblPr). Rows contain a child Table Row Property element (w:trPr)

The element that controls vertical merging is the Table Cell Property element (w:tcPr). This element can contain a Vertically Merged Cell child element (w:vMerge). If w:vMerge contains a w:val attribute that has a value of "restart", then the cell that contains it is the top-most of a vertically-merged column of cells. If w:vMerge contains a w:val attribute that has a value of "continue", then the cell that contains it is merged with the cell before it that has a w:val attribute that has a value of "restart".

Put in simple terms, you put a "restart" in the top-most cell, and put a "continue" in the cells after it. The following example is the markup for a table with vertically-merged cells. I simplified it to highlight the pattern. However, it is valid WordprocessingML.

<w:tbl>
  <w:tblPr>
    <w:tblStyle w:val="TableGrid" />
    <w:tblW w:w="0"
            w:type="auto" />
    <w:tblLook w:val="04A0"
               w:firstRow="1"
               w:lastRow="0"
               w:firstColumn="1"
               w:lastColumn="0"
               w:noHBand="0"
               w:noVBand="1" />
  </w:tblPr>
  <w:tr>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>Vendor Number</w:t>
        </w:r>
      </w:p>
    </w:tc>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>Invoice Number</w:t>
        </w:r>
      </w:p>
    </w:tc>
  </w:tr>
  <w:tr>
    <w:tc>
      <w:tcPr>
        <w:vMerge w:val="restart" />
      </w:tcPr>
      <w:p>
        <w:r>
          <w:t>1</w:t>
        </w:r>
      </w:p>
    </w:tc>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>37</w:t>
        </w:r>
      </w:p>
    </w:tc>
  </w:tr>
  <w:tr>
    <w:tc>
      <w:tcPr>
        <w:vMerge w:val="continue" />
      </w:tcPr>
      <w:p />
    </w:tc>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>38</w:t>
        </w:r>
      </w:p>
    </w:tc>
  </w:tr>
  <w:tr>
    <w:tc>
      <w:tcPr>
        <w:vMerge w:val="continue" />
      </w:tcPr>
      <w:p />
    </w:tc>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>39</w:t>
        </w:r>
      </w:p>
    </w:tc>
  </w:tr>
  <w:tr>
    <w:tc>
      <w:tcPr>
        <w:vMerge w:val="restart" />
      </w:tcPr>
      <w:p>
        <w:r>
          <w:t>2</w:t>
        </w:r>
      </w:p>
    </w:tc>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>52</w:t>
        </w:r>
      </w:p>
    </w:tc>
  </w:tr>
  <w:tr>
    <w:tc>
      <w:tcPr>
        <w:vMerge w:val="continue" />
      </w:tcPr>
      <w:p />
    </w:tc>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>53</w:t>
        </w:r>
      </w:p>
    </w:tc>
  </w:tr>
</w:tbl>

The default value for the w:val attribute of the w:vMerge element is "continue", so the markup that Word 2010 generates is as follows.

  <w:tr>
    <w:tc>
      <w:tcPr>
        <w:vMerge w:val="restart" />
      </w:tcPr>
      <w:p>
        <w:r>
          <w:t>2</w:t>
        </w:r>
      </w:p>
    </w:tc>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>52</w:t>
        </w:r>
      </w:p>
    </w:tc>
  </w:tr>
  <w:tr>
    <w:tc>
      <w:tcPr>
        <w:vMerge />
      </w:tcPr>
      <w:p />
    </w:tc>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>53</w:t>
        </w:r>
      </w:p>
    </w:tc>
  </w:tr>

Generating Tables with Merged Cells

After reviewing the markup, it is easier to generate a table with vertically-merged cells. Typically, you want data with a hierarchical structure, similar to the following.

<Root>
  <Vendor VendorNumber='1'>
    <Invoice InvoiceNumber='37' />
    <Invoice InvoiceNumber='38' />
    <Invoice InvoiceNumber='39' />
  </Vendor>
  <Vendor VendorNumber='2'>
    <Invoice InvoiceNumber='52' />
    <Invoice InvoiceNumber='53' />
  </Vendor>
</Root>

You want to transform it to the following.

<Root>
  <Invoice Vendor="1" Invoice="37" First="true" />
  <Invoice Vendor="1" Invoice="38" First="false" />
  <Invoice Vendor="1" Invoice="39" First="false" />
  <Invoice Vendor="2" Invoice="52" First="true" />
  <Invoice Vendor="2" Invoice="53" First="false" />
</Root>

The following is a LINQ query and projection that transforms the first form of XML into the second form.

XElement data = XElement.Parse(
    @"<Root>
        <Vendor VendorNumber='1'>
          <Invoice InvoiceNumber='37' />
          <Invoice InvoiceNumber='38' />
          <Invoice InvoiceNumber='39' />
        </Vendor>
        <Vendor VendorNumber='2'>
          <Invoice InvoiceNumber='52' />
          <Invoice InvoiceNumber='53' />
        </Vendor>
      </Root>");

// First, transform the XML into a more usable form.
XElement newData = new XElement("Root",
    data.Elements("Vendor")
        .Elements("Invoice")
        .Select(i => new XElement("Invoice",
            new XAttribute("Vendor", i.Parent.Attribute("VendorNumber").Value),
            new XAttribute("Invoice", i.Attribute("InvoiceNumber").Value),
            new XAttribute("First", i.Parent.Elements().First() == i))));

In this query and projection, the expression i.Parent.Elements().First() == i determines whether a particular element is first in the sequence of child elements.

Finally, you can write code to generate a table that includes the Vertical Merge element in the appropriate cells. The best way to write code such as this is to use the document reflector tool that is included with the Welcome to the Open XML SDK 2.0 for Microsoft Office. In the following example, you can see where the code inserts the Vertical Merge element, with the appropriate value for the w:val attribute.

// Open the document, and insert a table with vertically-merged cells as the first
// block-level content element in the document.
using (WordprocessingDocument doc =
    WordprocessingDocument.Open("Test.docx", true))
{
    Table table = new Table();
    TableProperties tableProperties = new TableProperties();
    TableStyle tableStyle = new TableStyle() { Val = "TableGrid" };
    TableWidth tableWidth = new TableWidth()
        { Width = "0", Type = TableWidthUnitValues.Auto };
    TableLook tableLook = new TableLook() { Val = "04A0", FirstRow = true,
        LastRow = false, FirstColumn = true, LastColumn = false,
        NoHorizontalBand = false, NoVerticalBand = true };
    tableProperties.Append(tableStyle);
    tableProperties.Append(tableWidth);
    tableProperties.Append(tableLook);
    table.Append(tableProperties);
    foreach (var invoice in newData.Elements("Invoice"))
    {
        TableRow tableRow = new TableRow();
        TableCell tableCell1 = new TableCell();
        TableCellProperties tableCellProperties = new TableCellProperties();
        VerticalMerge verticalMerge = new VerticalMerge()
        { Val = (bool)invoice.Attribute("First") ? MergedCellValues.Restart :MergedCellValues.Continue };
        tableCellProperties.Append(verticalMerge);
        Paragraph paragraph1 = new Paragraph();
        if ((bool)invoice.Attribute("First"))
        {
            Run run = new Run();
            Text text = new Text();
            text.Text = invoice.Attribute("Vendor").Value;
            run.Append(text);
            paragraph1.Append(run);
        }
        tableCell1.Append(tableCellProperties);
        tableCell1.Append(paragraph1);
        TableCell tableCell2 = new TableCell();
        Paragraph paragraph2 = new Paragraph();
        Run run2 = new Run();
        Text text2 = new Text();
        text2.Text = invoice.Attribute("Invoice").Value;
        run2.Append(text2);
        paragraph2.Append(run2);
        tableCell2.Append(paragraph2);
        tableRow.Append(tableCell1);
        tableRow.Append(tableCell2);
        table.Append(tableRow);
    }
    doc.MainDocumentPart.Document.Body.InsertAt(table, 0);
    // If there is not a TableGrid style, then create one.
    if (!doc.MainDocumentPart.StyleDefinitionsPart.Styles.Elements<Style>()
            .Any(s => s.StyleId == "TableGrid"))
        doc.MainDocumentPart.StyleDefinitionsPart.Styles.Append(
            GenerateStyle());
}

Example: Creating Tables with Vertically-Merged Cells

The following example opens a document named Test.docx, and inserts a table at the beginning of the document. The table contains vertically-merged cells. I generated much of the following code by using the Open XML SDK 2.0 Productivity Tool. Then, I modified the code according to my requirements. For more information about the Open XML SDK 2.0 Productivity Tool, see the video at Announcing the Release of the December 2009 CTP for the Open XML SDK.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

class Program
{
    static void Main(string[] args)
    {
        XElement data = XElement.Parse(
            @"<Root>
                <Vendor VendorNumber='1'>
                  <Invoice InvoiceNumber='37' />
                  <Invoice InvoiceNumber='38' />
                  <Invoice InvoiceNumber='39' />
                </Vendor>
                <Vendor VendorNumber='2'>
                  <Invoice InvoiceNumber='52' />
                  <Invoice InvoiceNumber='53' />
                </Vendor>
              </Root>");

        // First, transform the XML into a more usable form.
        XElement newData = new XElement("Root",
            data.Elements("Vendor")
                .Elements("Invoice")
                .Select(i => new XElement("Invoice",
                    new XAttribute("Vendor", i.Parent.Attribute("VendorNumber").Value),
                    new XAttribute("Invoice", i.Attribute("InvoiceNumber").Value),
                    new XAttribute("First", i.Parent.Elements().First() == i))));

        // Open the document, and insert a table with vertically-merged cells as the first
        // block-level content element in the document.
        using (WordprocessingDocument doc =
            WordprocessingDocument.Open("Test.docx", true))
        {
            Table table = new Table();
            TableProperties tableProperties = new TableProperties();
            TableStyle tableStyle = new TableStyle() { Val = "TableGrid" };
            TableWidth tableWidth = new TableWidth()
                { Width = "0", Type = TableWidthUnitValues.Auto };
            TableLook tableLook = new TableLook() { Val = "04A0", FirstRow = true,
                LastRow = false, FirstColumn = true, LastColumn = false,
                NoHorizontalBand = false, NoVerticalBand = true };
            tableProperties.Append(tableStyle);
            tableProperties.Append(tableWidth);
            tableProperties.Append(tableLook);
            table.Append(tableProperties);
            foreach (var invoice in newData.Elements("Invoice"))
            {
                TableRow tableRow = new TableRow();
                TableCell tableCell1 = new TableCell();
                TableCellProperties tableCellProperties = new TableCellProperties();
                VerticalMerge verticalMerge = new VerticalMerge()
                { Val = (bool)invoice.Attribute("First") ? MergedCellValues.Restart :
                    MergedCellValues.Continue };
                tableCellProperties.Append(verticalMerge);
                Paragraph paragraph1 = new Paragraph();
                if ((bool)invoice.Attribute("First"))
                {
                    Run run = new Run();
                    Text text = new Text();
                    text.Text = invoice.Attribute("Vendor").Value;
                    run.Append(text);
                    paragraph1.Append(run);
                }
                tableCell1.Append(tableCellProperties);
                tableCell1.Append(paragraph1);
                TableCell tableCell2 = new TableCell();
                Paragraph paragraph2 = new Paragraph();
                Run run2 = new Run();
                Text text2 = new Text();
                text2.Text = invoice.Attribute("Invoice").Value;
                run2.Append(text2);
                paragraph2.Append(run2);
                tableCell2.Append(paragraph2);
                tableRow.Append(tableCell1);
                tableRow.Append(tableCell2);
                table.Append(tableRow);
            }
            doc.MainDocumentPart.Document.Body.InsertAt(table, 0);
            // If there is not a TableGrid style, then create one.
            if (!doc.MainDocumentPart.StyleDefinitionsPart.Styles.Elements<Style>()
                    .Any(s => s.StyleId == "TableGrid"))
                doc.MainDocumentPart.StyleDefinitionsPart.Styles.Append(
                    GenerateStyle());
        }
    }

    // Creates a Style instance and adds its children.
    public static Style GenerateStyle()
    {
        Style style1 = new Style() { Type = StyleValues.Table, StyleId = "TableGrid" };
        StyleName styleName1 = new StyleName() { Val = "Table Grid" };
        BasedOn basedOn1 = new BasedOn() { Val = "TableNormal" };
        UIPriority uIPriority1 = new UIPriority() { Val = 59 };
        Rsid rsid1 = new Rsid() { Val = "005F1CC5" };

        StyleParagraphProperties styleParagraphProperties1 = new StyleParagraphProperties();
        SpacingBetweenLines spacingBetweenLines1 = new SpacingBetweenLines() { After = "0",
            Line = "240", LineRule = LineSpacingRuleValues.Auto };

        styleParagraphProperties1.Append(spacingBetweenLines1);

        StyleTableProperties styleTableProperties1 = new StyleTableProperties();
        TableIndentation tableIndentation1 = new TableIndentation() { Width = 0,
            Type = TableWidthUnitValues.Dxa };

        TableBorders tableBorders1 = new TableBorders();
        TopBorder topBorder1 = new TopBorder() { Val = BorderValues.Single, Color = "auto",
            Size = (UInt32Value)4U, Space = (UInt32Value)0U };
        LeftBorder leftBorder1 = new LeftBorder() { Val = BorderValues.Single, Color = "auto",
            Size = (UInt32Value)4U, Space = (UInt32Value)0U };
        BottomBorder bottomBorder1 = new BottomBorder() { Val = BorderValues.Single,
            Color = "auto", Size = (UInt32Value)4U, Space = (UInt32Value)0U };
        RightBorder rightBorder1 = new RightBorder() { Val = BorderValues.Single,
            Color = "auto", Size = (UInt32Value)4U, Space = (UInt32Value)0U };
        InsideHorizontalBorder insideHorizontalBorder1 = new InsideHorizontalBorder()
            { Val = BorderValues.Single, Color = "auto", Size = (UInt32Value)4U,
                Space = (UInt32Value)0U };
        InsideVerticalBorder insideVerticalBorder1 = new InsideVerticalBorder() {
            Val = BorderValues.Single, Color = "auto", Size = (UInt32Value)4U,
            Space = (UInt32Value)0U };

        tableBorders1.Append(topBorder1);
        tableBorders1.Append(leftBorder1);
        tableBorders1.Append(bottomBorder1);
        tableBorders1.Append(rightBorder1);
        tableBorders1.Append(insideHorizontalBorder1);
        tableBorders1.Append(insideVerticalBorder1);

        TableCellMarginDefault tableCellMarginDefault1 = new TableCellMarginDefault();
        TopMargin topMargin1 = new TopMargin() { Width = "0", Type = TableWidthUnitValues.Dxa };
        TableCellLeftMargin tableCellLeftMargin1 = new TableCellLeftMargin() { Width = 108,
            Type = TableWidthValues.Dxa };
        BottomMargin bottomMargin1 = new BottomMargin() { Width = "0",
            Type = TableWidthUnitValues.Dxa };
        TableCellRightMargin tableCellRightMargin1 = new TableCellRightMargin() { Width = 108,
            Type = TableWidthValues.Dxa };

        tableCellMarginDefault1.Append(topMargin1);
        tableCellMarginDefault1.Append(tableCellLeftMargin1);
        tableCellMarginDefault1.Append(bottomMargin1);
        tableCellMarginDefault1.Append(tableCellRightMargin1);

        styleTableProperties1.Append(tableIndentation1);
        styleTableProperties1.Append(tableBorders1);
        styleTableProperties1.Append(tableCellMarginDefault1);

        style1.Append(styleName1);
        style1.Append(basedOn1);
        style1.Append(uIPriority1);
        style1.Append(rsid1);
        style1.Append(styleParagraphProperties1);
        style1.Append(styleTableProperties1);
        return style1;
    }
}

Conclusion

Generating Open XML WordprocessingML documents is a great way to automate document generation processes. After generating Open XML WordprocessingML documents, you can use Word Automation Services to convert to PDF or XPS, to print or send electronically. One important way to improve the readability of tables in your generated documents is to generate them with vertically merged cells.

Additional Resources

See Open XML Developer Center on MSDN for articles, how-to videos, and links to blog posts. The following links provide important information for getting started with the :