Im Rahmen meiner Tätigkeit für den parlamentarischen Untersuchungsausschuss zur Untersuchung der politischen Verantwortung im Zusammenhang mit dem Kampfflugzeugsystem “Eurofighter Typhoon” von Anfang 2000 bis Ende 2017 stellte sich das Problem, Dokumentenbezeichnungen in Word-Dateien durch Hyperlinks, welche zu diesen Dokumenten in einer zentralen Datenbank verlinken sollten, zu ersetzen.
Eine erste Hürde bestand darin, dass diese Dokumentenbezeichnungen in allen Bereichen der Word-Dateien vorkommen konnten, daher die Dokumentenbezeichnungen konnten im normalen Fließtext verwendet werden und/oder in Fuß-/Endnoten als Zitat (dies war überwiegend der Fall).
Ein weiteres Problem bestand darin, die Dokumentenbezeichnungen automatisiert im Dokument ausfindig zu machen. Die Struktur der Bezeichnung war stets DokNr gefolgt von (idealerweise einem) Leerzeichen und einer fünfstelligen Zahl (z.B. DokNr 12345). Zu beachten war hierbei, dass die einzelnen Dokumentenbezeichnungen sich unterscheiden, aber auch öfters/wiederholt vorkommen konnten (z.B. wenn ein Dokument mehrfach zitiert wurde).
Mein erster Fehlversuch basierte auf RegExp (war meiner Affinität für PHP/JS-Code geschuldet wo ich gerne und recht häufig mit preg_match und preg_replace arbeite) und sah wie folgt aus:
Sub TextToHyperlink()
Dim rngFT As Word.Range
Dim matchFT As Word.Range
Dim rngStory As Word.Range
Dim objRegExp As Object
For Each rngStory In ActiveDocument.StoryRanges
Set rngFT = rngStory
Set matchFT = rngStory
Set objRegExp = CreateObject("VBScript.RegExp")
With objRegExp
.Global = True
.IgnoreCase = False
.Pattern = "(DokNr) ([0-9]+)"
End With
Set Matches = objRegExp.Execute(matchFT)
For Each match In Matches
With rngFT.Find
.Forward = False
.ClearFormatting
.MatchWholeWord = True
.MatchCase = False
.Wrap = wdFindContinue
.Execute FindText:=match.Value
End With
ActiveDocument.Hyperlinks.Add Anchor:=rngFT, _
Address:="https://datenbank.domain.at/search/results.php?pardoknr" _
& match.Submatches(1) & "&f=U", SubAddress:="", ScreenTip:="", _
TextToDisplay:="DokNr " & match.Submatches(1)
Next match
Next rngStory
Set rngFT = Nothing
Set matchFT = Nothing
Set rngStory = Nothing
Set objRegExp = Nothing
End Sub
Visual Basic
Der Code schien zu funktionieren und schloss dank der “For Each”-Schleife durch ActiveDocument.StoryRanges auch alle Textbereiche (Kopfbereich, Fließtext, Fußnoten, etc.) mit ein, jedoch zeigte sich bald der “logische Fehler” meines Codes, wodurch Dokumentenbezeichnungen nur einmal durch Hyperlinks ersetzt wurden, auch wenn diese öfters im Text vorkamen. So wurde beispielsweise nur beim ersten Zitat die DokNr 12345 durch einen Hyperlink zum Dokument ersetzt, alle weiteren Vorkommnissen von DokNr 12345 wurden ignoriert und nicht wie intendiert durch einen Hyperlink ersetzt.
Ich überlasse es jetzt Dir als Leser den logischen Fehler zu finden – ein Tipp: den Inhalt der Variable “Matches” (ein Array) ansehen.
Nach ein wenig Recherche bin ich zu einer wesentlich einfacheren und eleganteren Lösung unter Einbeziehung von “wildcards” gekommen:
Sub TextToHyperlink()
Application.ScreenUpdating = False
Dim rngStory As Word.Range
Dim matchFT As Word.Range
For Each rngStory In ActiveDocument.StoryRanges
Set matchFT = rngStory
With matchFT
With .Find
.Text = "DokNr @[0-9]{5}"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchWildcards = True
.Execute
End With
Do While .Find.Found
TrimString = Trim(.Text)
DokNr = Right(TrimString, 5)
.Hyperlinks.Add Anchor:=.Duplicate, Address:="https://datenbank.domain.at/results.php?pardoknr" _
& DokNr & "&f=U", TextToDisplay:=.Text
.Start = .Hyperlinks(1).Range.End
.Find.Execute
Loop
End With
Next rngStory
Set rngStory = Nothing
Set matchFT = Nothing
Application.ScreenUpdating = True
End Sub
Visual Basic
Kurze Analyse des Codes:
Die For-Each-Schleife
...
For Each rngStory In ActiveDocument.StoryRanges
...
Next rngStory
...
Visual Basic
stellt sicher, dass ALLE Textbereiche (Kopfbereich, Fließtext, Fußnoten, etc.) nach den Dokumentenbezeichnungen durchsucht werden.
Mit
...
With .Find
.Text = "DokNr @[0-9]{5}"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchWildcards = True
.Execute
End With
...
Visual Basic
werden alle Dokumentenbezeichnungen im jeweiligen Textbereich gesucht. Durch DokNr @ wird sichergestellt, dass die Dokumentenbezeichnungen auch dann als solche erkannt werden, wenn mehrere Leerzeichen zwischen DokNr und der fünfstelligen Dokumentennummer gesetzt wurden. Durch [0-9]{5} werden nur Zahlen zwischen 0-9 gefunden, wobei diese immer fünfstellig sein müssen.
Danach ersetzt dieser Code:
...
Do While .Find.Found
TrimString = Trim(.Text)
DokNr = Right(TrimString, 5)
.Hyperlinks.Add Anchor:=.Duplicate, Address:="https://datenbank.domain.at/results.php?pardoknr" _
& DokNr & "&f=U", TextToDisplay:=.Text
.Start = .Hyperlinks(1).Range.End
.Find.Execute
Loop
...
Visual Basic
die jeweils gefundenen Dokumentenbezeichnungen durch die zugehörigen Hyperlinks. Trim(.Text) sorgt dafür, dass etwaige Leerzeichen vor oder hinter der Dokumentenbezeichnung entfernt werden. Right(TrimString, 5) extrahiert dann die fünfstellige Dokumentennummer. Der folgende .Hyperlinks.Add-Code ersetzt anschließend die Dokumentenbezeichnung durch einen Hyperlink, wobei die Variable DokNr die fünfstellige Dokumentennummer in die URL des Hyperlinks einbaut. Durch TextToDisplay:=.Text wird sichergestellt, dass als Linktext wieder die (durch einen Hyperlink ersetzte) Dokumentenbezeichnung verwendet wird.
