none
Adding Quick Parts Programmatically Using Word Office Model RRS feed

  • Question

  • I need to add Document ID Value quickpart to a very large number of existing documents and I would like to make a program to do so.  I'm afraid googling the issue has left me a little confused.  As far as I can tell, the rough form of the footer-insertion part of the solution (in C#) looks something like this:
    	    Word.Application iWord = new Word.Application();
                Word.Document iDoc = iWord.Documents.Open(savePath);
                foreach (Word.Section wordSection in iDoc.Sections)
                {
                    Word.Range footerRange = wordSection.Footers[Word.WdHeaderFooterIndex.wdHeaderFooterPrimary].Range;
                    footerRange.DoSomething()
                }
                iDoc.Save();

    But I can't seem to figure out the details of DoSomething().  

    Please note that it must be the dynamic field, equivalent to Design->Quick Parts->Document Property->Document ID Value, not simply text.

    Thanks!
    • Changed type Bearfacer Monday, May 26, 2014 7:16 PM mistake
    Monday, May 26, 2014 6:37 PM

Answers

  • The quickparts listed in the Document Property dropdown are either the standard ones (corresponding to standard builtin document properties such as Author), the "newer" Cover Page ones, or columns in a SharePoint document library. (If yours is something else, please let us know).

    So I assume that yours is a "SharePoint column." If not, the rest of this message is unlikely to help.

    If so, there is no simple facility in the Word Object Model to replicate exactly what happens when you insert that quick part from the Document Property menu. To do it in code, you have to 

     a. insert the right kind of content control (probably a plain text one in this case) and give it a suitable Title/Tag

     b. connect it to the correct element in the Custom XML data store.

    I have the starting point of some VBA code for that (see below). You'd obviously need to adapt it for C# and to insert at a range rather than the selection.

    The only other aproach I know would be

     a. manually insert a copy of the content control that you want and adjust its properties as required

     b. make a copy of that somewhere where you can retrieve and use it programmatically (e.g. create a new building block/glossary entry or some such)

     c. programmatically insert that wherever you need it

    However, in that case, you will need to be sure that the same namespace GUID is used for the property in all your documents. If, for example, someone has created a separate "Document ID" column in a number of different document libraries, each Document ID may have a different namespace GUID. If the Document ID column is a site column and all the documents are on the same site or have come from the same site, they may share a namespace GUID. 

    FWIW the code I have for getting Xpath and value information given a property name is as follows. It was orignally written to try to deal with a particular type of property that I hope you don't have to deal with :-)

    Sub TESTinsertCC()
    ' Put a property name in here instead of "col1text" and see if you
    ' get a viable Content Control at the Selection
    Debug.Print insertCC(Selection.Range, "col1text").XMLMapping.XPath
    End Sub
    Function insertCC(theRange As Word.Range, ContentTypeItemName As String) As ContentControl
    'Dim cc As Word.ContentControl
    Set insertCC = theRange.ContentControls.Add(wdContentControlText)
    With insertCC
      .XMLMapping.SetMapping getDIPPropXPath(theRange.Document, ContentTypeItemName)
    End With
    'Set cc = Nothing
    End Function
    Function getDIPPropXPath(TheDocument As Word.Document, _
      ContentTypeItemName As String) As String
    ' Attempts to retrieve the display value of a ContentTypeProperty that
    ' cannot be returned directly using the MetaProperty.Value property
    ' TheDocument is the Word document containing the properties
    ' ContentTypeItemName is the displayName of the item
    ' Assume we cannot get this item from the MetaAttribute Value
    ' because VBA does not recognise the Variant Type or content
    ' Two ways we could do it:
    ' a. get the Custom XML Part that contains the data and iterate
    '    through the nodes until we find the one we need.
    ' b. get the Custom XML part the contains the schema(s) and
    '    retrieve the XML Element name and Namespace name, then
    '    get the Custom XML part that contains the schema(s) and
    '    retrieve the 1st sub-element of the element.
    ' This attempts (b). Lots of assumptions, including
    ' - the namespace name of the schema and data parts are fixed
    '   and there is either only one Custom XML Part with that
    '   name or the first one is the one we want
    ' - the leaf element of the first sub-branch of the element
    ' But there are assumptions in (a), too, e.g. that there
    ' aren't two complex items in the DIP with the same name but
    ' different namespaces.
    ' Can almost certainly be improved by inspecting the other
    ' properties in the schema to identify precisely which type
    ' of property we are dealing with. Unfortunately, even this does
    ' not appear to be reliably available from the COntentTypeProperties
    ' info.
    Const SchemaPartNamespaceURI As String = _
      "http://schemas.microsoft.com/office/2006/metadata/contentType"
    Const DataPartNamespaceURI As String = _
      "http://schemas.microsoft.com/office/2006/metadata/properties"
    ' Do everything step by step for explanation and debugging
    Dim cxnDataElement As Office.CustomXMLNode
    Dim cxnsDataElement As Office.CustomXMLNodes
    Dim cxnSchemaElement As Office.CustomXMLNode
    Dim cxnsSchemaElement As Office.CustomXMLNodes
    Dim cxpData As Office.CustomXMLPart
    Dim cxpSchema As Office.CustomXMLPart
    Dim cxpsData As Office.CustomXMLParts
    Dim cxpsSchema As Office.CustomXMLParts
    Dim strDataXPath As String
    Dim strElementName As String
    Dim strElementNamespaceURI As String
    Dim strPrefix As String
    Dim strResult As String
    Dim strSchemaXPath As String
    Debug.Print "TheDocument: " & TheDocument.FullName
    Debug.Print "ContentTypeItemName: " & ContentTypeItemName
    strResult = ""
    Set cxpsSchema = TheDocument.CustomXMLParts.SelectByNamespace(SchemaPartNamespaceURI)
    If cxpsSchema.Count = 0 Then
      MsgBox "Error: Schema CXP not found or does not have the expected Namespace URI"
    Else
      If cxpsSchema.Count > 1 Then
        Debug.Print "Warning: more than one CXP with the expected Schema Namespace URI." & _
          " Using the first."
      End If
      Set cxpSchema = cxpsSchema(1)
      'Debug.Print cxpSchema.XML
      strSchemaXPath = "//xsd:element[@ma:displayName='" & ContentTypeItemName & "']"
      Debug.Print "Schema XPath: " & strSchemaXPath
      Set cxnsSchemaElement = cxpSchema.SelectNodes(strSchemaXPath)
      If cxnsSchemaElement.Count = 0 Then
        MsgBox "Error: Could not find the schema element that defines the element."
      Else
        If cxnsSchemaElement.Count > 1 Then
          Debug.Print "Warning: more than one Schema Element that defines the element."
        End If
        Set cxnSchemaElement = cxnsSchemaElement(1)
        ' don't test for node existence at this point
        'Debug.Print cxnSchemaElement.XML
        strElementName = cxnSchemaElement.SelectSingleNode("@name").Text
        Debug.Print "Actual Element Name: " & strElementName
        strElementNamespaceURI = cxnSchemaElement.ParentNode.SelectSingleNode("@targetNamespace").Text
        Debug.Print "Element Namespace URI: " & strElementNamespaceURI
        Set cxnSchemaElement = Nothing
      End If
      Set cxnsSchemaElement = Nothing
      Set cxpSchema = Nothing
      
      ' Now look for the item in the Custom XML part that contains the data
      
      Set cxpsData = TheDocument.CustomXMLParts.SelectByNamespace(DataPartNamespaceURI)
      If cxpsData.Count = 0 Then
        MsgBox "Error: Data CXP not found or does not have the expected Namespace URI"
      Else
        If cxpsData.Count > 1 Then
          Debug.Print "Warning: more than one CXP with the expected Data Namespace URI." & _
            " Using the first."
        End If
        Set cxpData = cxpsData(1)
        'Debug.Print cxpData.XML
        ' Get the prefix for the Element's namespace.
        ' Note that this may be different from any prefix
        ' you may see if you inspect the Part's XML
        strPrefix = cxpData.NamespaceManager.LookupPrefix(strElementNamespaceURI)
        Debug.Print "Data Prefix: " & strPrefix
        
        ' retrieve any elements with that prefix and the internal XML
        ' property name
        ' not sure this "if" is ever needed or would be effective
        If strPrefix = "" Then
          strDataXPath = "//" & strElementName
        Else
          strDataXPath = "//" & strPrefix & ":" & strElementName
        End If
        Debug.Print "Data XPath: " & strDataXPath
        Set cxnsDataElement = cxpData.SelectNodes(strDataXPath)
        If cxnsDataElement.Count = 0 Then
          MsgBox "Error: Could not find the data element."
        Else
          If cxnsDataElement.Count > 1 Then
            Debug.Print "Warning: more than one Data Element. Using the first."
          End If
          Set cxnDataElement = cxnsDataElement(1)
          ' May need to drill down further
          'Debug.Print cxnDataElement.XML
          While cxnDataElement.HasChildNodes
            Set cxnDataElement = cxnDataElement.FirstChild
          Wend
          strResult = cxnDataElement.Text
          Set cxnDataElement = Nothing
        End If
        Set cxpData = Nothing
        Set cxnsDataElement = Nothing
      End If
      Set cxpsData = Nothing
    End If
    Set cxpsSchema = Nothing
    'getDIPPropValue2 = strResult
    getDIPPropXPath = strDataXPath
    End Function



    Peter Jamieson


    • Marked as answer by Bearfacer Tuesday, May 27, 2014 11:12 AM
    • Edited by Peter Jamieson Tuesday, May 27, 2014 11:15 AM removed typo
    Tuesday, May 27, 2014 9:44 AM

All replies

  • Hi,

    According to your description, you want to simulate the operation of inserting Document Properties of Quick Parts. I think it means inserting specified built-in document properties into the footer section such as "Author", "Company" and so on. We could resort to Document.BuiltInDocumentProperties Property to access to all the built-in document properties for the document.

    Here is a sample for your reference.

                Word.Application iWord = new Word.Application();
                Word.Document iDoc = iWord.Documents.Open(savePath);
                foreach (Word.Section wordSection in iDoc.Sections)
                {
                    Word.Range footerRange = wordSection.Footers[Word.WdHeaderFooterIndex.wdHeaderFooterPrimary].Range;
                    footerRange.Text = iDoc.BuiltInDocumentProperties["Author"].Value + "      " + DateTime.Now;
                }
                iDoc.Save();



    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Tuesday, May 27, 2014 9:35 AM
    Moderator
  • The quickparts listed in the Document Property dropdown are either the standard ones (corresponding to standard builtin document properties such as Author), the "newer" Cover Page ones, or columns in a SharePoint document library. (If yours is something else, please let us know).

    So I assume that yours is a "SharePoint column." If not, the rest of this message is unlikely to help.

    If so, there is no simple facility in the Word Object Model to replicate exactly what happens when you insert that quick part from the Document Property menu. To do it in code, you have to 

     a. insert the right kind of content control (probably a plain text one in this case) and give it a suitable Title/Tag

     b. connect it to the correct element in the Custom XML data store.

    I have the starting point of some VBA code for that (see below). You'd obviously need to adapt it for C# and to insert at a range rather than the selection.

    The only other aproach I know would be

     a. manually insert a copy of the content control that you want and adjust its properties as required

     b. make a copy of that somewhere where you can retrieve and use it programmatically (e.g. create a new building block/glossary entry or some such)

     c. programmatically insert that wherever you need it

    However, in that case, you will need to be sure that the same namespace GUID is used for the property in all your documents. If, for example, someone has created a separate "Document ID" column in a number of different document libraries, each Document ID may have a different namespace GUID. If the Document ID column is a site column and all the documents are on the same site or have come from the same site, they may share a namespace GUID. 

    FWIW the code I have for getting Xpath and value information given a property name is as follows. It was orignally written to try to deal with a particular type of property that I hope you don't have to deal with :-)

    Sub TESTinsertCC()
    ' Put a property name in here instead of "col1text" and see if you
    ' get a viable Content Control at the Selection
    Debug.Print insertCC(Selection.Range, "col1text").XMLMapping.XPath
    End Sub
    Function insertCC(theRange As Word.Range, ContentTypeItemName As String) As ContentControl
    'Dim cc As Word.ContentControl
    Set insertCC = theRange.ContentControls.Add(wdContentControlText)
    With insertCC
      .XMLMapping.SetMapping getDIPPropXPath(theRange.Document, ContentTypeItemName)
    End With
    'Set cc = Nothing
    End Function
    Function getDIPPropXPath(TheDocument As Word.Document, _
      ContentTypeItemName As String) As String
    ' Attempts to retrieve the display value of a ContentTypeProperty that
    ' cannot be returned directly using the MetaProperty.Value property
    ' TheDocument is the Word document containing the properties
    ' ContentTypeItemName is the displayName of the item
    ' Assume we cannot get this item from the MetaAttribute Value
    ' because VBA does not recognise the Variant Type or content
    ' Two ways we could do it:
    ' a. get the Custom XML Part that contains the data and iterate
    '    through the nodes until we find the one we need.
    ' b. get the Custom XML part the contains the schema(s) and
    '    retrieve the XML Element name and Namespace name, then
    '    get the Custom XML part that contains the schema(s) and
    '    retrieve the 1st sub-element of the element.
    ' This attempts (b). Lots of assumptions, including
    ' - the namespace name of the schema and data parts are fixed
    '   and there is either only one Custom XML Part with that
    '   name or the first one is the one we want
    ' - the leaf element of the first sub-branch of the element
    ' But there are assumptions in (a), too, e.g. that there
    ' aren't two complex items in the DIP with the same name but
    ' different namespaces.
    ' Can almost certainly be improved by inspecting the other
    ' properties in the schema to identify precisely which type
    ' of property we are dealing with. Unfortunately, even this does
    ' not appear to be reliably available from the COntentTypeProperties
    ' info.
    Const SchemaPartNamespaceURI As String = _
      "http://schemas.microsoft.com/office/2006/metadata/contentType"
    Const DataPartNamespaceURI As String = _
      "http://schemas.microsoft.com/office/2006/metadata/properties"
    ' Do everything step by step for explanation and debugging
    Dim cxnDataElement As Office.CustomXMLNode
    Dim cxnsDataElement As Office.CustomXMLNodes
    Dim cxnSchemaElement As Office.CustomXMLNode
    Dim cxnsSchemaElement As Office.CustomXMLNodes
    Dim cxpData As Office.CustomXMLPart
    Dim cxpSchema As Office.CustomXMLPart
    Dim cxpsData As Office.CustomXMLParts
    Dim cxpsSchema As Office.CustomXMLParts
    Dim strDataXPath As String
    Dim strElementName As String
    Dim strElementNamespaceURI As String
    Dim strPrefix As String
    Dim strResult As String
    Dim strSchemaXPath As String
    Debug.Print "TheDocument: " & TheDocument.FullName
    Debug.Print "ContentTypeItemName: " & ContentTypeItemName
    strResult = ""
    Set cxpsSchema = TheDocument.CustomXMLParts.SelectByNamespace(SchemaPartNamespaceURI)
    If cxpsSchema.Count = 0 Then
      MsgBox "Error: Schema CXP not found or does not have the expected Namespace URI"
    Else
      If cxpsSchema.Count > 1 Then
        Debug.Print "Warning: more than one CXP with the expected Schema Namespace URI." & _
          " Using the first."
      End If
      Set cxpSchema = cxpsSchema(1)
      'Debug.Print cxpSchema.XML
      strSchemaXPath = "//xsd:element[@ma:displayName='" & ContentTypeItemName & "']"
      Debug.Print "Schema XPath: " & strSchemaXPath
      Set cxnsSchemaElement = cxpSchema.SelectNodes(strSchemaXPath)
      If cxnsSchemaElement.Count = 0 Then
        MsgBox "Error: Could not find the schema element that defines the element."
      Else
        If cxnsSchemaElement.Count > 1 Then
          Debug.Print "Warning: more than one Schema Element that defines the element."
        End If
        Set cxnSchemaElement = cxnsSchemaElement(1)
        ' don't test for node existence at this point
        'Debug.Print cxnSchemaElement.XML
        strElementName = cxnSchemaElement.SelectSingleNode("@name").Text
        Debug.Print "Actual Element Name: " & strElementName
        strElementNamespaceURI = cxnSchemaElement.ParentNode.SelectSingleNode("@targetNamespace").Text
        Debug.Print "Element Namespace URI: " & strElementNamespaceURI
        Set cxnSchemaElement = Nothing
      End If
      Set cxnsSchemaElement = Nothing
      Set cxpSchema = Nothing
      
      ' Now look for the item in the Custom XML part that contains the data
      
      Set cxpsData = TheDocument.CustomXMLParts.SelectByNamespace(DataPartNamespaceURI)
      If cxpsData.Count = 0 Then
        MsgBox "Error: Data CXP not found or does not have the expected Namespace URI"
      Else
        If cxpsData.Count > 1 Then
          Debug.Print "Warning: more than one CXP with the expected Data Namespace URI." & _
            " Using the first."
        End If
        Set cxpData = cxpsData(1)
        'Debug.Print cxpData.XML
        ' Get the prefix for the Element's namespace.
        ' Note that this may be different from any prefix
        ' you may see if you inspect the Part's XML
        strPrefix = cxpData.NamespaceManager.LookupPrefix(strElementNamespaceURI)
        Debug.Print "Data Prefix: " & strPrefix
        
        ' retrieve any elements with that prefix and the internal XML
        ' property name
        ' not sure this "if" is ever needed or would be effective
        If strPrefix = "" Then
          strDataXPath = "//" & strElementName
        Else
          strDataXPath = "//" & strPrefix & ":" & strElementName
        End If
        Debug.Print "Data XPath: " & strDataXPath
        Set cxnsDataElement = cxpData.SelectNodes(strDataXPath)
        If cxnsDataElement.Count = 0 Then
          MsgBox "Error: Could not find the data element."
        Else
          If cxnsDataElement.Count > 1 Then
            Debug.Print "Warning: more than one Data Element. Using the first."
          End If
          Set cxnDataElement = cxnsDataElement(1)
          ' May need to drill down further
          'Debug.Print cxnDataElement.XML
          While cxnDataElement.HasChildNodes
            Set cxnDataElement = cxnDataElement.FirstChild
          Wend
          strResult = cxnDataElement.Text
          Set cxnDataElement = Nothing
        End If
        Set cxpData = Nothing
        Set cxnsDataElement = Nothing
      End If
      Set cxpsData = Nothing
    End If
    Set cxpsSchema = Nothing
    'getDIPPropValue2 = strResult
    getDIPPropXPath = strDataXPath
    End Function



    Peter Jamieson


    • Marked as answer by Bearfacer Tuesday, May 27, 2014 11:12 AM
    • Edited by Peter Jamieson Tuesday, May 27, 2014 11:15 AM removed typo
    Tuesday, May 27, 2014 9:44 AM
  • That's close enough to my situation to be useful-thanks!
    Tuesday, May 27, 2014 11:12 AM
  • Basically you can insert the required xml in the footer. I was able to create a custom SharePoint list item action to add the footer with quick parts. Also I have developed a custom console application to uploads documents to sharepoint and programmatically add footer with quick parts for Document Id and version.
    Tuesday, February 17, 2015 7:08 PM
  • Hi SrinuElm, this is exactly something I want to do. Do you have a working sample code you can share?

    Friday, January 27, 2017 4:25 PM