Creating content from AutoText using the Open XML SDK

Since time immemorial, you’ve been able to save formatted and unformatted text for re-use in Word. Originally, this was called “AutoText”; in Office 2007 it was renamed “BuildingBlocks”.

These can only be saved in Templates. Originally, they were saved by default to; since 2007 the default storage is an independet file, “Built-in Building Blocks.dotx”. But you can choose any document of type template.

The user can choose an entry from a list and Word will insert it at the current selection. The Word APIs also provide access to this functionality.

But what’s the situation with the Word Open XML file format? Is there a simple way to insert AutoText/Building Blocks from a template “container” into a document?

The answer is, “No, there’s no simple way.” It can be done, but it’s just as complex as adding anything else to a document. You have to watch out for “linked content”, such as graphics and, if the formatting is provided by a style (or styles), you also have to copy that (keeping in mind, however, that this could overwrite existing styles).

AutoText entries are stored in the “path” /word/glossary/document.xml. The glossary has other xml files, analogous to what’s found in the /word path, such as styles.xml, fontTable.xml, etc. There’s also a _rels/document.xml.rels file that stores relationships between the Glossary document.xml and the otherĀ files, as well as to any images. The actual images, however, are stored centrally in the /word/media file, along with all other images in the document.

Following is a simplified example in a VB.NET Console appĀ for copying an AutoText entry for a Header from a template into an existing document. The code will replace only the first header in the document, if none is present, a default header will be created. The Header style for the AutoText entry replaces the Header style in the document, if present, otherwise the style will be created. It also copies in any “blip” graphics referenced in the AutoText entry.

Note the use of InnerXML to copy the content from the Glossary document.xml to the HeaderPart.Header of the target document. When copying one part to another you use FeedData, but the source in this case is not a part. InnerXML is also used to copy the style paragraph and run properties from the Glossary to the target style.

Imports DocumentFormat.OpenXml.Drawing
Module Module1

    Sub Main()
    End Sub

    Private Sub CreateHeaderFromAutoText()

        Dim targetPath As String = "C:\Test\AddHeaderFormAT_NoHeader.docx" 
        Dim sourcePath As String = "C:\Test\TestOXML_BB.dotx"

        Using sourcePkg As WordprocessingDocument = _
              WordprocessingDocument.Open(sourcePath, False)

            Dim mainSourcePart As MainDocumentPart = _
            If (mainSourcePart.GetPartsCountOfType( _
                               Of GlossaryDocumentPart)() > 0) Then
                'Read the header information from the AutoText
                Dim glossDoc As GlossaryDocument = _
                    mainSourcePart.GetPartsOfType( _
                    Of GlossaryDocumentPart).FirstOrDefault().GlossaryDocument
                Dim glossPart As DocPart = glossDoc.Descendants( _
                    Of DocPart).Where(Function(g As DocPart)  _
                    g.DocPartProperties.DocPartName.Val.ToString() = _
                Dim headerXML As String = glossPart.DocPartBody.InnerXml

                'Read the style information for the 'Header' style 
                'stored in the AutoText
                'In this example we're assuming that's the only 
                'style used to format the entry
                Dim glossStyle As StyleDefinitionsPart = _
                    mainSourcePart.GetPartsOfType( _
                    Of GlossaryDocumentPart).FirstOrDefault(). _
                Dim hStyle As Style = _
                    glossStyle.Styles.Descendants(Of Style).Where( _
                    Function(s As Style) s.StyleId.Value.ToString() = _
                Dim paraProps As StyleParagraphProperties = _
                Dim runProps As StyleRunProperties = _

                'Check for any linked graphics in the header
                Dim sourceBlips As IEnumerable( _
                    Of DocumentFormat.OpenXml.Drawing.Blip) = _
                    glossPart.DocPartBody.Descendants( _
                    Of DocumentFormat.OpenXml.Drawing.Blip)()
                Using targetPkg As WordprocessingDocument = _
                      WordprocessingDocument.Open(targetPath, True)
                    Dim mainTargetPart As MainDocumentPart = _
                    Dim docStyle As StyleDefinitionsPart = _
                    Dim hTargetStyle As Style = _
                        docStyle.Styles.Descendants(Of Style).Where( _
                        Function(s As Style) s.StyleId.Value.ToString() = _
                    If hTargetStyle Is Nothing Then
                        hTargetStyle = New Style With { _
                                      .StyleId = "Header", _
                                      .StyleParagraphProperties = _
                                        New StyleParagraphProperties, _
                                      .StyleRunProperties = _
                                         New StyleRunProperties, _
                                      .Default = 1, _
                                      .Type = StyleValues.Paragraph}
                        hTargetStyle.StyleName = _
                            New StyleName With {.Val = "header"}
                    End If
                    Dim paraTargetProps As StyleParagraphProperties = _
                    Dim runTargetProps As StyleRunProperties = _
                    paraTargetProps.InnerXml = paraProps.InnerXml
                    runTargetProps.InnerXml = runProps.InnerXml

                    Dim hPart As HeaderPart = _
                    If (hPart Is Nothing) Then
                        hPart = AddHeaderPart(targetPkg)
                    End If
                    Dim h As Header = hPart.Header
                    h.InnerXml = headerXML

                    If sourceBlips.Count > 0 Then
                        Dim sourceBlip As _
                            DocumentFormat.OpenXml.Drawing.Blip = Nothing
                        For Each sourceBlip In sourceBlips
                            Dim imgPart As ImagePart = _
                                glossDoc.GlossaryDocumentPart.GetPartById( _
                            Dim imgTargetPart As ImagePart = _
                            Dim targetBlip As Blip = h.Descendants( _
                                Of Blip).Where( _
                                Function(b As Blip) b.Embed.Value = _
                            targetBlip.Embed = hPart.GetIdOfPart(imgTargetPart)
                    End If
                End Using
                Console.WriteLine("No Glossary part found.")
            End If
        End Using
    End Sub

    'Create a new default header for the document without content
    'Content will be added in the calling procedure
    Private Function AddHeaderPart(ByVal wdPkgTarget As _
                     WordprocessingDocument) As HeaderPart

        Dim newHeaderPart As HeaderPart = _
            wdPkgTarget.MainDocumentPart.AddNewPart( _
            Of HeaderPart)()
        Dim hrID As String = _
        newHeaderPart.Header = New Header()
        Dim hr As HeaderReference = New HeaderReference With _
            {.Type = HeaderFooterValues.Default, .Id = hrID}
        Dim docTarget As Document = _
        docTarget.Body.Elements( _
            Of SectionProperties).LastOrDefault().InsertAt(hr, 0)
        Return newHeaderPart
    End Function
End Module

Leave a Reply