0
votes

Here's a head-splitter: I'm trying to programmatically create hidden bookmarks for existing headings in a doc, so that I can create hyperlinks elsewhere in the doc that point to these bookmarks. (I want to use hyperlinks instead of cross-references so I can specify my own 'Display Text' for the links, which isnt possible using cross-refs).

I want my bookmarks to be named after the headings they relate to, with a custom prefix. Example:

  1. style: Heading1
  2. heading text: Entrance & Hallway
  3. bookmark name: _Hd1_Entrance_&_Hallway

I'm specifying a custom prefix to make each bookmark unique to it's style, so I can then have 2 matching headings in the doc, so long as they are in different heading styles. (example: _Hd1_Entrance_&_Hallway and _Hd3_Entrance_&_Hallway)

The catch is: if my heading contains special chars like '&', I get a 'Bad Bookmark Name' error, which I understand, and this is documented on the web. I'm only allowed to use a limited character set.

So how come if I manually create a hyperlink using Word's own dialog, selecting a 'Place In This Document' such as a heading like "Entrance & Hallway", Word manages this no problem? Once the Hlink is created, I can now see the hidden bookmark associated with this Hlink in Word's 'Bookmarks' dialog - and it's quite happily named "_Entrance _&_Hallway". This confounds me!

Anyone have an explanation? I'd really like to be able to leverage this same functionality, but cannot fathom how. Any help is greatly valued! Thanks,

Sub ScratchPad_Bookmarks()
Dim doc As Document
Dim rng As Range
Dim sHdName As String
Dim sBmName As String
    Set doc = ActiveDocument
    'Insert a heading at start of document
    sHdName = "Entrance & Hallway"
    doc.Range.InsertBefore sHdName & vbCr
    doc.Paragraphs(1).Range.Style = doc.Styles("Heading 1")

    'Find the above heading in the active document
    Set rng = doc.Range
    With rng.Find
        .ClearFormatting
        .Text = sHdName
        .Style = "Heading 1"
        If Not .Execute Then
            'Heading not found, so quit
            Exit Sub
        End If
    End With

    'rng has collapsed to the found heading, so create a bookmark
    'rng.Select 'debug
    sBmName = Replace(rng.Text, " ", "_")
    rng.Collapse wdCollapseStart
    rng.Bookmarks.Add sBmName
    'sBmName contains '&' so this throws a Runtime error:
    '5828: Bad Bookmark Name  (as expected)
End Sub

The above doesnt work. However to test the manual operation yourself is easy. Just create a heading that includes a '&' character, style it as Heading 1. In the next paragraph, insert a hyperlink using Word's own dialog. Select Place In This Doc and select the heading you just created. Shouldn't be a problem. Now open Word's Bookmark dialog, enable the Hidden Bookmarks view, and voila: a hidden bookmark with a '&' character. (Wd 2010) Say what?!

1
Are you sure you're correctly including & into a string? Could you show us the line of code where you try to define the bookmark?Matteo NNZ
Apologies for the above. Even a straightforward instruction throws the runtime error - such as: r.Bookmarks.Add "This & That". This is as expected though. I've verified that my strings contain "&" explicitly - this is actually what I want. ie: my headings must use & instead of "and".user3084170
Reading the official documentation, I can read bookmark names should be one word only; I fear that's the reason.Matteo NNZ
P.s. my knowledge of VBA for Word is very poor, you might want to wait the answer of someone else; but I clearly read "The name of the bookmark. The name cannot be more than one word." in the doc.Matteo NNZ
I believe this is overcome using underscores instead of spaces, which is how Word achieves multi-word bookmarks, much like the example above. Thanks all the same though.user3084170

1 Answers

2
votes

IMO Word is "cheating", either by breaking its own rules for bookmark names, or by denying you the full range of names that it allowed by the Specification.

If you go back to the early ECMA standard for .docx, a bookmark's name is defined as an ST_String, which is a restriction of xsd:string with a maximum length of 255 characters. I do not think this has changed since in any of the ECMA or ISO versions of the standards.

However, Microsoft's implementation notes [MS-OE376].pdf, which pertain to the ECMA version of the specs, and [MS-OI29500].pdf, which pertain to the 2012 ISO version, specify that the name can be no longer than 40 characters, but do not describe any other limitations.

My (previous) understanding was that bookmark names were limited to 40 characters, had to consist of "letters", "digits" and "underscores" and had to start with an underscore (for hidden bookmarks) or a letter. (Not sure about, e.g. "_1"). And I believe that VBA still imposes those rules, although I have never checked what its understanding of a "letter" or a "digit" is - are non-Latin letters/digits allowed?

However, if you save a document as a .xml or edit the document.xml within a .docx, you can modify the bookmark name so that it contains, e.g. "&". Further, Word will re-save such characters. But it will truncate names to 40 characters when you open, and it doesn't retain the original name when you re-save. I don't think the use of initial "_" to denote "hidden" is in the standard, either.

So, given the specifications, I would say that Word is "cheating" by not allowing you to use the full range of possible names, rather than "cheating" by allowing hyperlink destinations to contain "&".

You could insert bookmarks with "&" in Windows versions of Word in VBA by using InsertXML to insert a chunk of XML with a preconfigured bookmark name (see some simple sample code below) but I suspect you would have to work rather harder to move a bookmark. You would probably have to extract the exiting XML of the thing you wanted to "cover", then surround it with the bookmarkStart and bookmarkEnd tags, and that sounds like a pretty nasty exercise to me.

As a final observation, AFAICR the bookmark names that you can specify for legacy form fields have a 20-character length restriction.

That code:

Sub insertbm()
Dim x As String
x = ""
x = x & "<?xml version='1.0' encoding='utf-8' standalone='yes'?>"
x = x & "<pkg:package xmlns:pkg='http://schemas.microsoft.com/office/2006/xmlPackage'>"
x = x & "  <pkg:part pkg:name='/_rels/.rels' pkg:contentType='application/vnd.openxmlformats-package.relationships+xml'>"
x = x & "    <pkg:xmlData>"
x = x & "      <Relationships xmlns='http://schemas.openxmlformats.org/package/2006/relationships'>"
x = x & "        <Relationship Id='rId1' Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument' Target='word/document.xml' />"
x = x & "      </Relationships>"
x = x & "    </pkg:xmlData>"
x = x & "  </pkg:part>"
x = x & "  <pkg:part pkg:name='/word/document.xml' pkg:contentType='application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml'>"
x = x & "    <pkg:xmlData>"
x = x & "      <w:document xmlns:w='http://schemas.openxmlformats.org/wordprocessingml/2006/main'>"
x = x & "        <w:body>"
x = x & "          <w:p>"
x = x & "            <w:bookmarkStart w:id='0' w:name='bookmark&amp;name' />"
x = x & "            <w:r>"
x = x & "              <w:t>"
x = x & "bookmarkedtext</w:t>"
x = x & "            </w:r>"
x = x & "            <w:bookmarkEnd w:id='0' />"
x = x & "          </w:p>"
x = x & "        </w:body>"
x = x & "      </w:document>"
x = x & "    </pkg:xmlData>"
x = x & "  </pkg:part>"
x = x & "</pkg:package>"
Selection.InsertXML x
End Sub