Search code examples
castingms-wordvstoopenxmlword-interop

How do I cast a VSTO Globals.ThisAddIn.Application.ActiveDocument to an OpenXml WordprocessingDocument on the fly?


I have an application program that provides the ability to export a document as an XML file. The exported document is in OpenXml format and is recognized/editable using Word (See Note 1 below). The document contains a rather convoluted table structure where the "top" table has a few cells each of which contain a child table. My task is to write a VSTO add-in that provides the user a button. When the user opens one of these XML files and clicks the button, the add-in locates and manipulates text within the "top" table and child tables.

My original code (see "Code" below) used the Microsoft.Office.Interop.Word.Table class to locate each cell within the "top" table and child tables.

Things got weird when my code started throwing an exception because on one of the tables, the Column.Count property showed 3, but accessing the cell using objTable.Cell(row, column) threw The requested member of the collection does not exist. Using the debugger, I can see that column 3 does not exist even thought the Count property shows 3 (Note: I observed that the Column indices are 1-based, not zero based).

Do I need to cast the Word document to an OpenXml document on the fly within the add-in and use the OpenXml Table class to successfully access the tables?

Thinking that this is the answer, I installed Open XML Package Editor for Modern Visual Studios and added references for DocumentFormat.OpenXml and Windows.Base. However, when I do the cast:

WordprocessingDocument doc = (WordprocessingDocument)Globals.ThisAddIn.Application.ActiveDocument;

It throws this exception:

System.InvalidCastException. Unable to cast COM object of type 'Microsoft.Office.Interop.Word.DocumentClass' to class type 'DocumentFormat.OpenXml.Packaging.WordprocessingDocument'. Instances of types that represent COM components cannot be cast to types that do not represent COM components; however they can be cast to interfaces as long as the underlying COM component supports QueryInterface calls for the IID of the interface.

Can I/How do I cast Globals.ThisAddIn.Application.ActiveDocument to an OpenXml WordprocessingDocument on the fly inside my VSTO add-in?

Code

Microsoft.Office.Interop.Word.Range rngDoc = Globals.ThisAddIn.Application.ActiveDocument.Content;

int i = 1;

foreach (Microsoft.Office.Interop.Word.Table objTable in rngDoc.Tables)
{
    DumpTable(objTable: objTable, tableNumber: i++, childTableNumber: 0);
}


private void DumpTable(Microsoft.Office.Interop.Word.Table objTable, int tableNumber, int childTableNumber)
{
    for (int row = 1; row <= objTable.Rows.Count; row++)
    {
        for (int column = 1; column <= objTable.Columns.Count; column++)
        {
            Cell cell = null;

            try
            {
                cell = objTable.Cell(row, column);

                Debug.WriteLine(string.Format("Table {0}.{1}. row={2}. column={3}. cell text={4}", tableNumber, childTableNumber, row, column, cell.Range.Text));
            }
            catch (Exception e)
            {
                Debug.WriteLine(string.Format("Table {0}.{1}. row={2} + column={3} threw exception: {4}", tableNumber, childTableNumber, row, column, e.Message));
            }
        }
    }

    Debug.WriteLine(string.Format("Table {0}.{1}. Start Child Tables", tableNumber, childTableNumber));

    foreach (Microsoft.Office.Interop.Word.Table child_tb in objTable.Tables)
    {
        DumpTable(child_tb, tableNumber, childTableNumber + 1);
    }

    Debug.WriteLine(string.Format("Table {0}.{1}. End Child Tables", tableNumber, childTableNumber++));
}

Note 1

I assume the document is is OpenXml format based on an examination of the file's preamble (see xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006")

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?mso-application progid="Word.Document"?>
<w:wordDocument xmlns:aml="http://schemas.microsoft.com/aml/2001/core" 
xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" 
xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" 
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
xmlns:o="urn:schemas-microsoft-com:office:office" 
xmlns:v="urn:schemas-microsoft-com:vml" 
xmlns:w10="urn:schemas-microsoft-com:office:word" 
xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml" 
xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint" 
xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" 
xmlns:wsp="http://schemas.microsoft.com/office/word/2003/wordml/sp2" 
xmlns:sl="http://schemas.microsoft.com/schemaLibrary/2003/core" 
xmlns:ns0="http://tempuri.org/AllInOneOctoFBISchema.xsd" 
xmlns:ns1="http://tempuri.org/AllInOneOctoFBIFirstFooterSchema.xsd" 
w:macrosPresent="no" w:embeddedObjPresent="no" 
w:ocxPresent="no" xml:space="preserve"><w:ignoreSubtree 
w:val="http://schemas.microsoft.com/office/word/2003/wordml/sp2"/>
.
.
remainder of file 

Solution

  • There is no direct cast. These objects are not related to each other. You need to save the document and then open the saved file using the Open XML SDK. See Welcome to the Open XML SDK 2.5 for Office for more information.