The previous post discussed how to find out what Word Open XML is required in order to insert a particular type of content. This article will consider a practical example and demonstrate how the Word Open XML can be stored in a resource file, then loaded and modified by JavaScript code before being inserted into the Word document.

A common requirement when building Word documents is bookmarking text then later inserting a reference to the bookmark (a “cross-reference”). Depending on the construction of the REF field the cross-reference can display the bookmarked text or the page information for the bookmark.

For this task, two sets of WordOpenXML are required, one for defining a bookmark and another for inserting a REF field. Both were found and prepared for the solution as follows:

  1. Copy the basic, minimum WordOpenXML from “Create better add-ins for Word with Office Open XML” and paste it into a Notepad file. Save the file to a convenient location as a “template” for re-use. Be sure to save the file with the extension .xml and the encoding utf-8.
  2. Create a Word document containing bookmarked text and a cross-reference to that text.
  3. Open document in the Open XML SDK Productivity Tool and inspect the tag using Reflect Code.
  4. Open the “template” file in Notepad (or another editor) and select the tags.
  5. Copy the tags for the bookmark, together with its child elements, and paste it to the “template” file. (The selected content will be replaced.)
  6. Edit the WordOpenXML to remove extraneous information, such as the “GoBack” bookmark and rsId attributes.
  7. Save this file (with the extension .xml and the encoding utf-8) to the same folder containing the JavaScript code file. (In my case, this was the ../App/Home folder of a Visual Studio “App for Office” solution.)
  8. Repeat steps 4 – 8 for the tags containing the defintion of the REF field.

Create a Visual Studio “App for Office” and modify Home.html to contain two buttons, one for inserting the bookmark and the other for inserting the reference. (The relevant HTML code is at the end of this post, for your reference.)

The code in Home.js follows at the end of the post (before the HTML code). Of special interest is reading and parsing the XML file containing the WordOpenXML. JQuery/Ajax is used to read the file.

  1. Notice the file path is relative to the location of the JavaScript file being executed. Since both are in the same folder, only the file name is required.
  2. Retrieving the file is async and uses a function callback, similarly to the Web Add-in APIs.
  3. It’s important to note that the successful execution does not return a string containing the XML. An XMLDocument object is returned that must be parsed using XML tools.
  4. Thus, the w:t element containing the text to be bookmarked is located using the method getElementsByTagNameNS. The selection made by the user in the document replaces the current content of this node.
  5. The XMLDocument object must be converted to a string, however, in order to write it to the Word document. This is done using the XMLSerializer().serializeToString method.
      var sFileName = "TestOoxml_RefBookmark.xml";
          $.ajax({
              type: "GET",
              url: sFileName,
              dataType: "xml",
              success: function (resultOoxml) {
                Ooxml = resultOoxml;
                var textNode = 
                    Ooxml.documentElement.getElementsByTagNameNS('*', 't');
                //Replace the text to be bookmarked with the selection
                textNode[0].textContent = selection;
                sOoxml = 
                  new XMLSerializer().serializeToString(Ooxml.documentElement);
                console.log(sOoxml);

This little sample project is fine for demonstration purposes. But in the real world it’s more likely multiple bookmarks are needed. Word requires unique bookmark names for each bookmark; if a bookmark is inserted with the same name as a previous one, the earlier bookmark is removed. The reason for this behavior becomes obvious when you think about cross-references: if there’s more than one bookmark with the same name, how should the cross-reference know which bookmark to reference?

One way to make a bookmark name unique is to append a “counter” at the end of the name. It’s a tricky proposition, of course, to store the last counter value used. Traditionally, VBA developers have used “DocVariables” for such purposes. While these still exist, they’re not so convenient for the JavaScript (or OOXML) developer as they’re “deep” in the Word Open XML. The better approach is to work with a Custom XML Part, which provides the topic for the next post: How to save document-specific information in a Custom XML Part for later retrieval.

Home.js code:

/// <reference path="../App.js" />

(function () {
    "use strict";

    // The initialize function must be run each time a new page is loaded
    Office.initialize = function (reason) {
        $(document).ready(function () {
            app.initialize();

            $('#bookmarkSelection').click(bookmarkSelection);
            $('#refBookmark').click(refBookmark);
        });
    };
    //Insert a reference to the bookmark at the current selection
    function refBookmark() {
      var sFileName = "TestOoxml_RefBookmark.xml";
      var Ooxml;
      var docOoxml;
      var sOoxml;

      $.ajax({
          type: "GET",
          url: sFileName,
          dataType: "xml",
          success: function (resultOoxml) {
            Ooxml = resultOoxml;
            var textNode = Ooxml.documentElement.getElementsByTagNameNS('*', 't');
            //Replace the selection with the Ref field.
            //Since it's not possible to "look up" the bookmark using the
            //2013 APIs the text content of the bookmark is not known - 
            //the user will have to update the field manually.
            textNode[0].textContent = "Press F9 to update the field";
            sOoxml = new XMLSerializer().serializeToString(Ooxml.documentElement);
            console.log(sOoxml);
            //Write the Ref field back to the document
            Office.context.document.setSelectedDataAsync(sOoxml, 
                { coercionType: Office.CoercionType.Ooxml },
                function (result) {
                    if (result.status === Office.AsyncResultStatus.Succeeded) {
                        app.showNotification('Success!!');
                    } else {
                        app.showNotification('Error:', result.error.message);
                    }
                });
          }
      });
    }

    // Bookmark the current selection
    function bookmarkSelection() {
      var selection;
      var sFileName = "TestOoxml_Bookmark.xml";
      var Ooxml;
      var docOoxml;
      var sOoxml;
      //Get the current selection in order to bookmark it
      //The selection will actually be replaced with the 
      //bookmark containing the selected text.
      //The bookmark name is currently given in the XML: bkm
      //We can change this, later
      Office.context.document.getSelectedDataAsync(Office.CoercionType.Text, 
          function (resultGet) {
              if (resultGet.status === Office.AsyncResultStatus.Succeeded) {
                  selection = resultGet.value;
                  //Get the WordOpenXML for creating a bookmark
                  $.ajax({
                      type: "GET",
                      url: sFileName,
                      dataType: "xml",
                      success: function (resultOoxml) {
                        Ooxml = resultOoxml;
                        var textNode = 
                            Ooxml.documentElement.getElementsByTagNameNS('*', 't');
                        //Replace the text to be bookmarked with the selection
                        textNode[0].textContent = selection;
                        sOoxml = 
                          new XMLSerializer().serializeToString(Ooxml.documentElement);
                        console.log(sOoxml);
                        //Write the bookmark back to the document
                        Office.context.document.setSelectedDataAsync(sOoxml, 
                           { coercionType: Office.CoercionType.Ooxml },
                            function (result) {
                              if (result.status === "succeeded") {
                                  app.showNotification('Success!!');
                              } else {
                                  app.showNotification('Error:', result.error.message);
                              }
                            });
                        }
                  });
              }
              else {
                app.showNotification('Error:', resultGet.error.message);
              }
          });
    }
})();

The relevant HTML code:

<button id="bookmarkSelection">Bookmark the selection</button>
<p>Insert a reference to the bookmark:</p>
<button id="refBookmark">Ref the bookmark</button>

<p style="margin-top: 50px;">
    <a href="javascript:location.reload(true)">Refresh add-in</a>
</p>
Leave a Reply