Embedding SharePoint Document Version History / Repeating data in Word using Open XML SDK

In my latest post I described how to add/extend approval and publishing infos within .docx custom properties. Within this post I would like to give you an example on how to add your own simple xml content schema with repeating elements using the Microsoft Word Developer ribbon extension and the xml mapping pane.

To stay within the SharePoint context the target of this tutorial would be to add document version information from SharePoint to a word custom property component/part and display the information within an embeded table in the document.

Create an xml data component template

The first thing you need to do is to define a custom property xml schema or to create a simple xml file serving as the template of the version data (This template will be populated with live data later from SharePoint)

[sourcecode language=”xml”]
< ?xml version="1.0" encoding="utf-8"?>


1
Nettingsmeier, Bjoern
This will describe the changes made


[/sourcecode]

Note: It is recommended to define a “random” namespace because word uses the namespace to identify/display the property component.

Save the xml content to a document and go on with the next steps.

Preparing the document template in Word

Please ensure that you have enabled the ribbon tab “developer tools”. To enable the developer tab open ms word and navigate to File / Options / Customize Ribbon and check the developer tools option.

office_enable_dev_tab

Now open the developer tab and add our xml as a custom xml component. Select XML mapping pane and within the component dropdown execute “Add new part”.

office_xml_mapping_pane

Our newly added xml component should now be available within the mapping pane dropdown displaying the namespace you used in the xml template (e.g. https://www.ilikesharepoint.de).

As you want to define a repeating area you should now add an element container to your document. In this example we would simply use two table rows containing three columns.

office_version_tabrow

The first table row should be used as the table header and is staticly filled with header information. Now you need to select the second table row as this would be the anker point of our repeating elements. When you selected the table row expand the versions tree within the xml mapping area and right click on version element, click insert content control / repeating.

office_insert_repeating

Now the second table row has been defined as a repeating row which will outomatically populate data from the xml component. The last thing you need to do is to define the different columns that should be displayed. Place the cursor in each of the columns of the second row and add version properties as plain text components. Select a column in the second table row, expand the version tree within the xml mapping area and right click on version element, click add control / plain text. The sample data of our xml template should appear.

office_insert_plain

Perfect. Save the word document and upload it to SharePoint.

Populating version data using the Open XML SDK

The source code example of the following chapter depends on the xml template we defined in the first step therefore you need to tailor it if you like to embed different content.

Depending on the SharePoint Version you use, please download the appropriate Open XML SDK’s.

Please ensure that you install the SDK and the library DocumentFormat.OpenXml.dll is placed inside the Global Assembly Cache or is part of your SharePoint WSP package definition.

gac_openxml

Event Receiver

This exercise will not in depth explain on how to build and assign a SharePoint library event receiver so let us assume that there is already a receiver in place and it reacts on the library updated event.

[sourcecode language=”csharp”]
public override void ItemUpdated(SPItemEventProperties properties)
{
this.EventFiringEnabled = false;
CreateUpdateVersionHistory(properties.ListItem);
this.EventFiringEnabled = true;
}
[/sourcecode]

The following source serves as the document processing function.
[sourcecode language=”csharp”]
internal void CreateUpdateVersionHistory(SPListItem item)
{
// We only like to process docx documents
if (!Path.GetExtension(item.File.Name).Equals(“.docx”, StringComparison.InvariantCultureIgnoreCase))
{
return;
}

// We do not make changes on locked or checkedout files
if (item.File.LockType != SPFile.SPLockType.None || item.File.Level == SPFileLevel.Checkout)
{
return;
}

// The XML helper strings
string baseXML = “< ?xml version=\"1.0\" encoding=\"utf-8\" ?>” +
{0}“;

string versionXML = “” +
{0}” +
{1}” +
{2}” +
“;

string result = “”;

// Populate / Build XML
foreach (SPFileVersion v in item.File.Versions)
{
result += string.Format(versionXML,
v.VersionLabel,
v.CreatedBy.Name,
v.CheckInComment);
}

result = string.Format(baseXML, result);

// Open the file binary stream
using (Stream fs = item.File.OpenBinaryStream())
{
using (WordprocessingDocument doc = WordprocessingDocument.Open(fs, true))
{
MainDocumentPart mainPart = doc.MainDocumentPart;

// Check if custom xml part exists, and replace content
IEnumerator ienum = doc.MainDocumentPart.CustomXmlParts.GetEnumerator();
bool addNew = true;

while (ienum.MoveNext())
{
bool doUpdate = false;
using (var stream = ienum.Current.GetStream(FileMode.Open, FileAccess.Read))
{
stream.Position = 0;

XmlDocument xmldoc = new XmlDocument();
xmldoc.Load(stream);

XmlNodeList list = xmldoc.GetElementsByTagName(“versions”);
if (list != null &amp;amp;amp;&amp;amp;amp; list.Count == 1)
{
doUpdate = true;
}
}

if (doUpdate)
{
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(result)))
{
ienum.Current.FeedData(ms);
addNew = false;
}
}
}

// Add a new xml component if none exists
if (addNew)
{
CustomXmlPart myXmlPart = mainPart.AddCustomXmlPart(CustomXmlPartType.CustomXml);
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(result)))
{
myXmlPart.FeedData(ms);
}
}

// Set update fields
DocumentSettingsPart settingsPart = doc.MainDocumentPart.GetPartsOfType().First();

// Create object to update fields on open
UpdateFieldsOnOpen updateFields = new UpdateFieldsOnOpen();
updateFields.Val = new DocumentFormat.OpenXml.OnOffValue(true);

// Insert object into settings part.
settingsPart.Settings.PrependChild(updateFields);
settingsPart.Settings.Save();

}

// Save binary stream
item.File.SaveBinary(fs);
}
}
}
[/sourcecode]

Let us have a look at some of the basic functionality.

  • The code first iterates through all existing custom XML components to analyze if there is allready a Version XML available.
  • Depending on the availability of the XML component the code either updates the existing one, or creates a new custom XML component and feeds the data.
  • When opening a word document in your client application custom properties need to be refreshed manually by the user. The UpdateFieldsOnOpen Attribute forces an update message when the user updates the document. There will be another blog post regarding the updates of custom properties soon.

The Result

First of all we added the Event Receiver to our document library and enable versioning. Now upload our word template document or define it as a template for a content type. (look here)
Please make some changes to the document, Checkout, Change Metadata, Checkin, Publish … Now open the document in MS Word.

office_version_tab

Another interesting Thing would be to also display the changed metadata fields of each version, which can be found under SPFileVersion.Properties

There are a lot more use cases to discuss using xml component data mapping e.g. inserting new data directly within word and processing it afterwards in SharePoint. Perhaps this would be a possible subject of an upcoming post 😉

The article or information provided here represents completely my own personal view & thought. It is recommended to test the content or scripts of the site in the lab, before making use in the production environment & use it completely at your own risk. The articles, scripts, suggestions or tricks published on the site are provided AS-IS with no warranties or guarantees and confers no rights.

About Björn Nettingsmeier 2 Articles
Björn is an IT-Pro, Designer, Developer, and Senior Consultant of software solutions regarding the process automation area and Microsoft SharePoint.

4 Comments

  1. Hello Björn.

    I am really interested in your post, because it could help me solve the problem I’m having (embed documents’ version history inside the document itself).

    I’m following your solution step-by-step, but I have some issues when coding in Visual Studio.

    I’m receiving the following errors:

    Stream fs = item.File.OpenBinaryStream() -> the OpenBinaryStream() method is not available for Microsoft.Sharepoint.SPFile types.

    DocumentSettingsPart settingsPart = doc.MainDocumentPart.GetPartsOfType().First() -> does not contain a definition for First()

    item.File.SaveBinary(fs) -> The best overloaded method match for Microsoft.SharePoint.SPFile.SaveBinary(byte[]) has some invalid arguments

    These are the three errors I’m getting. Could you please help me solve them? I am developing for SharePoint 2013, with Open XML 2.5, as you suggested. Furthermore I installed the SharePoint2013 SDK and Office Tools for Visual Studio 2012

    • Hello Björn.

      Thank you for your interesting and helpful post.
      I use your script and it runs well.

      Is there a way to prevent a new Version after modifying the SPFile?

      If you could help me that would be awesome.

      Greetings
      Arthur

      My Settings:
      – SharePoint 2016
      – Open XML 2.5

  2. I really like how this works, but like most others we are now using SharePoint online which I understand does not support SharePoint library event receivers. Is there a way to get this functionality in SharePoint online?

Leave a Reply to Roberto Cancel reply

Your email address will not be published.


*