View Full Version : [SOLVED:] Macro for Conditional Text
docteam135
08-07-2014, 10:32 AM
A co-worker has asked me about creating an interactive document where they could show/hide portions of a contract based on which services the client elects to purchase.  
So I found this awesome set of macros (see abelard.com.au/words-3-2.pdf), but they're slightly out-dated.  They don't completely work, and I'm not sure if it's the tagging or the processing of the tags that's not working properly.  It might be that I'm copying/pasting it wrong?  I'm pasting it at the end of the post for you to take a look at.  I can see the tags when I complete that part of the process, but then when I go to process the tags, no matter what I put in the text box it just automatically hides everything on the page.  
But my main question here is twofold.  Is this the best way to go about using conditional text in word?  For tech writers out there, I'd like to mimic the type of functionality that exists in FrameMaker for conditional formatting.  If so, I'm also wondering how to display, like it shows in the article, a Display Content window from which the user can select the tags to display.  So for example, if you take a series of paragraphs with JQ, JA, SQ, and SA tags, you could select to display any combination of these tags. 
 
Sub AddTagToParagraphs()
 Dim newTag As String, oldTag As String
 Dim myPara As Paragraph
 oldTag = TagPara(Selection.Paragraphs(1), "")
 newTag = UCase(InputBox("Type tag for selected paragraph(s).", "Single Source ", oldTag))
 For Each myPara In Selection.Paragraphs
 oldTag = TagPara(myPara, newTag)
 Next myPara
 Set myPara = Nothing
End Sub
Sub ProcessTags()
 Dim TagsToShow As String
 Dim myPara As Paragraph
 Dim myTag As String
 Dim i As Integer
 Dim hidePara As Boolean
 TagsToShow = UCase(InputBox("Type tags to make visible. Leave blank to show all", "Single Source"))
 ActiveWindow.View.ShowHiddenText = True
 ActiveDocument.Range.Font.Hidden = False
    For Each myPara In ActiveDocument.Paragraphs
    myTag = TagPara(myPara, "")
        If myTag <> "" Then hidePara = False
    For i = 1 To Len(TagsToShow)
         If InStr(myTag, Mid(TagsToShow, i, 1)) = 0 Then hidePara = True
    Next i
    If hidePara Then
    myPara.Range.Font.Hidden = True
    End If
    Next myPara
     ActiveWindow.View.ShowHiddenText = False
     ActiveWindow.ActivePane.View.ShowAll = False
 Set myPara = Nothing
End Sub
Function TagPara(myPara As Paragraph, myTag As String) As String
 Dim pos1 As Integer
 Dim pos2 As Integer
 Dim myText As String
 Dim myRange As Range
 Set myRange = myPara.Range
 myText = myRange.Text
 pos1 = InStr(myText, "{")
 pos2 = InStr(myText, "}")
 If pos1 > 0 And pos2 > pos1 Then
 myRange.SetRange Start:=myPara.Range.Start + pos1 - 1, End:=myPara.Range.Start + pos2
 Else
 myRange.MoveEnd unit:=wdCharacter, Count:=-1
myRange.Collapse direction:=wdCollapseEnd
 End If
 If myTag <> "" Then myRange.Text = "{" & myTag & "}"
 myText = myRange.Text
 If Len(myText) > 1 Then TagPara = Mid(myText, 2, Len(myText) - 2) Else
TagPara = ""
 myRange.Font.Hidden = True
 Set myRange = Nothing
End Function
gmaxey
08-07-2014, 11:08 AM
I can confirm that the macros don't work.  While the concept presented is interesting, I don't think it is really practical.  Every Word user owns his or her installation and regardless of what magic the code my appear to do, they could still elect to how hidden text.  Therefore the purpose is easily defeated.
What are you actually trying to do?  Another option is to use BuildingBlocks or IncludeText fields to create the correct content based on the condition.
docteam135
08-07-2014, 11:19 AM
You're absolutely correct, if this is as far as it goes, it would be impractical.  But the idea is to be able to show/hide sections of a contract based on which services the client has selected.  And then create a PDF which we then send to the client.  The Word doc itself would be used by one team in my company.  Each member of that team has a slew of clients that require contracts for the specific services they are requesting.  That employee would be able to open up the Word contract, select which sections to display, create a PDF (or print it out depending on the client's needs), and send that to the client for a signature.  
My team works with conditional text in FrameMaker a lot, because depending on the type of client, only certain pieces of our product are relevant. So we'll have single-source users guide that we can then create separate PDF users guides for every type of client we have quite easily.  But it's too much work for my team to handle every single contract this other team needs, and it's cost prohibitive to give anyone else in the company FrameMaker licenses just for contracts.  So we're trying to find a way, using the tools that this other team has, to give them a template from which they can easily create the hundreds of contracts they need.  Does that make sense?
docteam135
08-07-2014, 11:25 AM
I guess the gist of what we're trying to do is create a template that many employees can use to quickly build contracts, and then print/PDF those contracts to send to clients.  That's the gist of it.  The contracts range from 10 to 200 pages depending on what products/services are selected.  So as it stands, it's very time consuming for our employees to create contracts.
gmaxey
08-07-2014, 12:52 PM
I've tinkered with the code you posted.  While I did not conduct exhaustive testing I think it is working:
Option Explicit
Sub AddTagToParagraphs()
Dim strTag As String
Dim oPar As Paragraph
  strTag = UCase(InputBox("Type tag for selected paragraph(s).", "Single Source"))
  For Each oPar In Selection.Paragraphs
    fcnTagParagraphs oPar, strTag
  Next oPar
lbl_Exit:
  Exit Sub
End Sub
 
Sub ProcessTags()
Dim arrTagsToShow() As String
Dim oPar As Paragraph
Dim strTag As String
Dim lngIndex As Integer
Dim bHide As Boolean
  arrTagsToShow = Split(UCase(InputBox("Type tags to make visible delimted with the pipe ""|"" character." & vbCr + vbCr _
                        & "Leave blank to show all", "Single Source")), "|")
  ActiveWindow.View.ShowHiddenText = True
  
  'Unhide everything.
  ActiveDocument.Range.Font.Hidden = False
  If UBound(arrTagsToShow) = -1 Then GoTo lbl_Exit
  For Each oPar In ActiveDocument.Paragraphs
    'Get tag if any.
    strTag = fcnWhatTag(oPar)
    If Not strTag = "" Then
      bHide = True
      For lngIndex = 0 To UBound(arrTagsToShow)
        If strTag = arrTagsToShow(lngIndex) Then
          bHide = False
          Exit For
        End If
      Next lngIndex
      oPar.Range.Font.Hidden = bHide
    End If
  Next oPar
  
  ActiveWindow.View.ShowHiddenText = False
  ActiveWindow.ActivePane.View.ShowAll = False
lbl_Exit:
  HideTags
  Exit Sub
End Sub
 
Function fcnTagParagraphs(oPar As Paragraph, strTag As String) As String
Dim oRng As Range
Dim oRngFind As Range
  Set oRng = oPar.Range
  Set oRngFind = oRng.Duplicate
  'Remove any existing tag
  With oRngFind.Find
    .Text = "\{%*%\}"
    .MatchWildcards = True
    Do While .Execute
      If oRngFind.InRange(oRng) Then
        oRngFind.Font.Hidden = False
        oRngFind.Delete
      Else
        Exit Do
      End If
    Loop
  End With
  With oRng
    .Collapse wdCollapseEnd
    .End = oPar.Range.End - 1
    .Text = "{%" & strTag & "%}"
    .Font.Hidden = True
  End With
lbl_Exit:
  Exit Function
End Function
Function fcnWhatTag(oPar As Paragraph) As String
Dim oRng As Range
  Set oRng = oPar.Range
  With oRng.Find
    .Text = "\{%*%\}"
    .MatchWildcards = True
    If .Execute Then
      oRng.Start = oRng.Start + 2
      oRng.End = oRng.End - 2
      fcnWhatTag = oRng.Text
    Else
      fcnWhatTag = ""
    End If
  End With
lbl_Exit:
  Exit Function
End Function
Sub HideTags()
Dim oRng As Range
  Set oRng = ActiveDocument.Range
  With oRng.Find
    .Text = "\{%*%\}"
    .MatchWildcards = True
    While .Execute
      oRng.Font.Hidden = True
      oRng.Collapse wdCollapseEnd
    Wend
  End With
lbl_Exit:
  Exit Sub
End Sub
docteam135
08-07-2014, 02:14 PM
Hey Greg,
Yup it works.  The code is perfect.  If the document is too large, or if there are tables and you try to add tags to content within a table, it breaks and Word crashes.  So I think complex conditional text is just beyond the scope of Word.  However, we've figured out a way to adapt the contracts to make this work.  Thank you so very much!
docteam135
08-07-2014, 02:23 PM
Oooh!  Okay, purely a curiosity question.  What if, instead of adding tags to paragraphs and then processing those tags - create separate styles for every service/product and then create macros to hide or show all paragraphs with those styles?  Can that be done?  It might be more time consuming in the setup, and the styles pane would look ridiculously long... But that might be more something that Word can handle... Is that a feasible alternative?  Not asking you to do it, just throwing out an idea to see what you think :)
gmaxey
08-07-2014, 02:47 PM
Say you had some text related to "gadgets" and you create and apply a style named "gadgets" to text, or even a whole table.
You could show\hide any text with "gadgets" applied simply by toggle the style font attribute:
 
ActiveDocument.Sytles("gadgets").Font.Hidden = True   '(or false)
gmaxey
08-07-2014, 03:22 PM
I don't know it this concept would work for you, but I typically do this sort of thing using a template that includes every possible bit of content.  I create a new document from the template and then use code to delete the content not need from the bottom working up.  A simple example.  Say my template contains 10 paragraphs of text and for this particular contract I only what to use 2, 7 and 9
Sub KillContent()
Dim arrKeeperIndex() As String
Dim colKeepers As New Collection
Dim lngIndex As Long
  'My template contains 10 paragraphs.  For this document I want to keep 2, 7 and 9
  arrKeeperIndex = Split("2,7,9", ",")
  For lngIndex = 0 To UBound(arrKeeperIndex)
    On Error Resume Next
    colKeepers.Add arrKeeperIndex(lngIndex), arrKeeperIndex(lngIndex)
    On Error GoTo 0
  Next lngIndex
  For lngIndex = ActiveDocument.Paragraphs.Count To 1 Step -1
    On Error Resume Next
    colKeepers.Add CStr(lngIndex), CStr(lngIndex)
    'If the index can be added to the collection that means it is not a keeper.
    If Err.Number = 0 Then
      'So kill it.
      ActiveDocument.Paragraphs(lngIndex).Range.Delete
      colKeepers.Remove colKeepers.Count
    End If
  Next lngIndex
End Sub
macropod
08-07-2014, 04:58 PM
Cross-posted at: http://answers.microsoft.com/en-us/office/forum/office_2013_release-word/macro-for-conditional-text/84dd7e52-cc4f-4283-84e4-2c58fed3388e
For cross-posting etiquette, please read: http://www.excelguru.ca/content.php?184
docteam135
08-08-2014, 07:44 AM
Okay, so I ended up doing the styles.font.hidden option and repeating it for every service.  So the macro is assigned to a button in a custom tab on the ribbon, and the macro for one service looks like this:
Sub ProjectManagment()
    With ActiveDocument
    .Styles("Project Management H3").Font.Hidden = wdToggle
    .Styles("Project Management Body").Font.Hidden = wdToggle
    .Styles("Project Management Bulleted").Font.Hidden = wdToggle
    End With
End Sub
That way, the guys who do the contracts just click the buttons related to the services the clients want and it's super simple for them.  Thank you for helping me and soundboarding everything for me with this.  I would never have been able to come up with this solution on my own.  You rock!
gmaxey
08-08-2014, 10:07 AM
Glad I could help and that you found a solution.
lanseninsane
12-07-2014, 06:15 PM
Dear Greg, 
How could one go about using the original code to permanently delete the paragraphs instead of hiding them? I am a complete novice in VBA and i'd love to learn more.  I've tried the following below, but the paragraph editing seems to be very arbitrary. I've added a track changes feature to ensure the content is not permanently lost. Any help would be appreciated!
Thanks
Sub ProcessTags()
Dim arrTagsToShow() As String
Dim oPar As Paragraph
Dim strTag As String
Dim lngIndex As Long
Dim bHide As Boolean
ActiveDocument.TrackRevisions = True
  arrTagsToShow = Split(UCase(InputBox("Type tags to make visible delimted with the pipe character." & vbCr + vbCr _
                        & "Leave blank to show all", "Single Source")), "|")
  ActiveWindow.View.ShowHiddenText = True
  
  'Unhide everything.
  ActiveDocument.range.Font.Hidden = False
  If UBound(arrTagsToShow) = -1 Then GoTo lbl_Exit
  For Each oPar In ActiveDocument.Paragraphs
    'Get tag if any.
    strTag = fcnWhatTag(oPar)
    If Not strTag = "" Then
      bHide = False
      For lngIndex = 0 To UBound(arrTagsToShow)
        If strTag = arrTagsToShow(lngIndex) Then
          bHide = False
        End If
        If Not strTag = arrTagsToShow(lngIndex) Then
        oPar.range.Delete
        End If
      Exit For
      Next lngIndex
      oPar.range.Font.Hidden = bHide
    End If
  Next oPar
  
  ActiveWindow.View.ShowHiddenText = False
  ActiveWindow.ActivePane.View.ShowAll = False
lbl_Exit:
  HideTags
  Exit Sub
End Sub
Powered by vBulletin® Version 4.2.5 Copyright © 2025 vBulletin Solutions Inc. All rights reserved.