none
How can I programmtically insert links to other pages within the same word doc?

    Question

  • I have a large set of source documents that are written in a choose-your-own-adventure style. They have sentences like "if X, go to slide Y", and I want to turn all the "slide Y" references to links to the actual page.

     

    If they were LaTeX docs, I think I could whip up a 10 line script to do what I want, but I'm not even sure what the right tool to use for word docs is. Do I want to use VSTO for this? If so, is there example code out there I can use, and if not, what should I use?

     

    If there's no example code, is this the right approach?

    1. Somehow programmability insert a bookmark for each page that's labeled with the the page number.

    2. Search for things matching /[sS]lide\s+\d+/ and somehow insert links to the matching pages

    Wednesday, June 30, 2010 1:03 AM

Answers

  • Hi Dan

    OK, now we're in the Word Developer forum...

    I think the first thing you need is a basic course on accessing Word and its object model in C#. You'll find links to useful articles in this KB article, to get you in at the ground level:

    http://support.microsoft.com/kb/311452

    <<1. I’m not sure what my options are, but it seems like it would be ok to insert a bookmark on the first line of text for each page, named “PageX”. That way, each occurrence of “Slide X” could just point to that bookmark. That might result in extra bookmarks, but I don’t think that should cause any problems.>>

    Here's some code that puts a bookmark at the top of every page.

        private void btnBookmarkEachPage_Click(object sender, EventArgs e)
        {
          object unitWdStory = Word.WdUnits.wdStory;
          object oFalse = false;
          object pageBkm = "\\Page";
          object gotoWdGoToPage = Word.WdGoToItem.wdGoToPage;
          object gotoWdGoToNext = Word.WdGoToDirection.wdGoToNext;
          object gotoCount1 = 1;
          object oCollapseStart = Word.WdCollapseDirection.wdCollapseStart;
    
          Word.Selection sel = wdApp.Selection;
          sel.HomeKey(ref unitWdStory, ref oFalse); //Start of doc
          //Assign entire page to range
          Word.Range rngPage = sel.Bookmarks.get_Item(ref pageBkm).Range;
          //We need a second, independent object so that we can later
          //compare the two objects
          Word.Range rngBkm = rngPage.Duplicate;
          //Collapse the second object to a point
          //(will be bookmark location, at top of page)
          rngBkm.Collapse(ref oCollapseStart);
          int counter = 0;
          int lNumPages = (int)sel.get_Information(Word.WdInformation.wdNumberOfPagesInDocument);
          string pageNr = null;
    
          //Execute at least once, 
          //then as long as the last bookmark isn't on the page with the cursor
          // (when can't go to next page, not error occurs, selection stays on last page)
          while (counter==0 || !rngBkm.InRange(rngPage))
          {
            counter++;
            rngPage.Collapse(ref oCollapseStart);
            //Extra security against an infinite loop
            if (counter > lNumPages) break;
            //Determine the page number
            pageNr = sel.get_Information(Word.WdInformation.wdActiveEndPageNumber).ToString();
            rngBkm = rngPage.Duplicate;
            object oRngBkm = rngBkm;
            //Add a bookmark
            rngPage.Bookmarks.Add("bkmPage" + pageNr, ref oRngBkm);
            //Go to the next page
            sel.GoTo(ref gotoWdGoToPage, ref gotoWdGoToNext, ref gotoCount1, ref missing);
            //Get the full page, for the comparison at the top of the loop
            rngPage = sel.Bookmarks.get_Item(ref pageBkm).Range;
          }
        }

     


    Cindy Meister, VSTO/Word MVP
    • Marked as answer by Dan Luu Thursday, July 01, 2010 11:55 PM
    Thursday, July 01, 2010 9:41 AM
  • Hi Dan

    (Note in the code in my last reply, wdApp is the Word Application. How to get that should come out of the material to which I linked.)

    <<2. \s+ will match any number of spaces, and then \d+ will match any number of digits, so every term of the form slide 1, slide 2, etc.  /[sS]lide\s+\d+/ >>

    The search string in Word, using wildcards, is: [Ss]lide @[0-9]@>

    The @ means "any number of the previous character". The > means "to the end of the word".

    Here's the basic code to perform the search (will find the first instance in the document)

    Word.

    Document doc = wdApp.ActiveDocument;
    Word.
    Range rng = doc.Content;
    Word.
    Find f = rng.Find;
    object oTrue = true;
    f.ClearFormatting();
    f.Text = "[Ss]lide @[0-9]@>";
    f.MatchWildcards = true;
    f.Execute(
    ref missing, ref missing, ref missing, ref missing, ref missing,
     
    ref missing, ref oTrue, ref missing, ref missing, ref missing, ref missing,
      ref missing, ref missing, ref missing, ref missing);
    rng.Select();


    Cindy Meister, VSTO/Word MVP
    • Marked as answer by Dan Luu Thursday, July 01, 2010 11:55 PM
    Thursday, July 01, 2010 10:00 AM

All replies

  • Hi Dan

    VSTO doesn't offer anything special or particular for this, with the exception that it's possible to place Windows Forms controls on a document surface. In this case, you could put a LinkLabel control on the document, then program the Click event to jump to a bookmark:

        private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
           object bkmName = "Section1";
          this.Bookmarks.get_Item(ref bkmName).Range.Select();
        }

    Drawback to using any Windows Forms controls: they only display and work when the document is zoomed at 100%.

    Word, itself, supports hyperlinks through HYPERLINK fields. Use Insert/Hyperlink, click the "Places in this document" tab at the left. Select the bookmark you want to target, and enter the text to display. You can see the field code behind the result by pressing Alt+F9 to toggle the field codes on.

    Depending on the Word settings, the user might have to use Ctrl+Click to trigger the Hyperlink. The tooltip that appears when the mouse is hovered over the hyperlink will inform the user of this.

    I'm not sure I'm clear on what your programming requirements are.

    1. Can you be more specific about "each page that's labelled with the page number", please? What is the identification criteria for determining whether a bookmark should be inserted?

    2. You'll need to use Word's search expressions, not RegEx, in order to work with Word's Range.Find object. What's the +\d+/ section of the RegEx mean? (The first part I can guess)


    Cindy Meister, VSTO/Word MVP
    Wednesday, June 30, 2010 9:37 AM
  • Thanks for the quick response, and sorry that I was unclear.

    1. I’m not sure what my options are, but it seems like it would be ok to insert a bookmark on the first line of text for each page, named “PageX”. That way, each occurrence of “Slide X” could just point to that bookmark. That might result in extra bookmarks, but I don’t think that should cause any problems.

    2. \s+ will match any number of spaces, and then \d+ will match any number of digits, so every term of the form slide 1, slide 2, etc.

    The only reason I want to do it programmatically is because there are enough documents that it would take a long time to do it by hand. I don’t know anything about word macros or VSTO, but I got the impression that word macros had to be written in VBA (which I don’t know), but that VSTO can be programmed via F# and C#.

    Wednesday, June 30, 2010 6:46 PM
  • Hi Dan

    VSTO is a special technology that extends the UI. "Macros" are, indeed, VBA. But "in between" is automation ("interop") of the Word application from within another programming environment. You can manipulate Word via a C# WinForms or Console App, for example. (I'm not familiar with F#, so I can't help you with that syntax.)

    I'm going to move this message thread to the Word Developer forum, as there's a broader spectrum of specialists in the Word object model, there. Most tend to be VB-oriented, but they might have thoughts to add about the general approach...


    Cindy Meister, VSTO/Word MVP
    Thursday, July 01, 2010 8:47 AM
  • Hi Dan

    OK, now we're in the Word Developer forum...

    I think the first thing you need is a basic course on accessing Word and its object model in C#. You'll find links to useful articles in this KB article, to get you in at the ground level:

    http://support.microsoft.com/kb/311452

    <<1. I’m not sure what my options are, but it seems like it would be ok to insert a bookmark on the first line of text for each page, named “PageX”. That way, each occurrence of “Slide X” could just point to that bookmark. That might result in extra bookmarks, but I don’t think that should cause any problems.>>

    Here's some code that puts a bookmark at the top of every page.

        private void btnBookmarkEachPage_Click(object sender, EventArgs e)
        {
          object unitWdStory = Word.WdUnits.wdStory;
          object oFalse = false;
          object pageBkm = "\\Page";
          object gotoWdGoToPage = Word.WdGoToItem.wdGoToPage;
          object gotoWdGoToNext = Word.WdGoToDirection.wdGoToNext;
          object gotoCount1 = 1;
          object oCollapseStart = Word.WdCollapseDirection.wdCollapseStart;
    
          Word.Selection sel = wdApp.Selection;
          sel.HomeKey(ref unitWdStory, ref oFalse); //Start of doc
          //Assign entire page to range
          Word.Range rngPage = sel.Bookmarks.get_Item(ref pageBkm).Range;
          //We need a second, independent object so that we can later
          //compare the two objects
          Word.Range rngBkm = rngPage.Duplicate;
          //Collapse the second object to a point
          //(will be bookmark location, at top of page)
          rngBkm.Collapse(ref oCollapseStart);
          int counter = 0;
          int lNumPages = (int)sel.get_Information(Word.WdInformation.wdNumberOfPagesInDocument);
          string pageNr = null;
    
          //Execute at least once, 
          //then as long as the last bookmark isn't on the page with the cursor
          // (when can't go to next page, not error occurs, selection stays on last page)
          while (counter==0 || !rngBkm.InRange(rngPage))
          {
            counter++;
            rngPage.Collapse(ref oCollapseStart);
            //Extra security against an infinite loop
            if (counter > lNumPages) break;
            //Determine the page number
            pageNr = sel.get_Information(Word.WdInformation.wdActiveEndPageNumber).ToString();
            rngBkm = rngPage.Duplicate;
            object oRngBkm = rngBkm;
            //Add a bookmark
            rngPage.Bookmarks.Add("bkmPage" + pageNr, ref oRngBkm);
            //Go to the next page
            sel.GoTo(ref gotoWdGoToPage, ref gotoWdGoToNext, ref gotoCount1, ref missing);
            //Get the full page, for the comparison at the top of the loop
            rngPage = sel.Bookmarks.get_Item(ref pageBkm).Range;
          }
        }

     


    Cindy Meister, VSTO/Word MVP
    • Marked as answer by Dan Luu Thursday, July 01, 2010 11:55 PM
    Thursday, July 01, 2010 9:41 AM
  • Hi Dan

    (Note in the code in my last reply, wdApp is the Word Application. How to get that should come out of the material to which I linked.)

    <<2. \s+ will match any number of spaces, and then \d+ will match any number of digits, so every term of the form slide 1, slide 2, etc.  /[sS]lide\s+\d+/ >>

    The search string in Word, using wildcards, is: [Ss]lide @[0-9]@>

    The @ means "any number of the previous character". The > means "to the end of the word".

    Here's the basic code to perform the search (will find the first instance in the document)

    Word.

    Document doc = wdApp.ActiveDocument;
    Word.
    Range rng = doc.Content;
    Word.
    Find f = rng.Find;
    object oTrue = true;
    f.ClearFormatting();
    f.Text = "[Ss]lide @[0-9]@>";
    f.MatchWildcards = true;
    f.Execute(
    ref missing, ref missing, ref missing, ref missing, ref missing,
     
    ref missing, ref oTrue, ref missing, ref missing, ref missing, ref missing,
      ref missing, ref missing, ref missing, ref missing);
    rng.Select();


    Cindy Meister, VSTO/Word MVP
    • Marked as answer by Dan Luu Thursday, July 01, 2010 11:55 PM
    Thursday, July 01, 2010 10:00 AM
  • Wow! Thanks! Between those two code snippets (and a bit of time paging through "Visual Studio Tools for Office 2007"), it was pretty easy to finish the task and write something to insert the links, despite my total lack of experience with Office automation.
    Thursday, July 01, 2010 11:55 PM
  • Hi Dan

    Glad you got it working :-) I was hoping you'd manage the rest of it, if I gave you the "worst parts" (most difficult to figure out).


    Cindy Meister, VSTO/Word MVP
    Friday, July 02, 2010 8:33 AM