PDA

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