如果你的论文是用NoteExpress组织参考文献,而且使用了著者出版年制,那么你有福了。这段VBA代码可以给所有带DOI或网址的参考文献增加原文链接,即使生成PDF后也可以点击直接打开浏览器,跳转到提供原文的网站。鼠标悬停时,还可以显示整条题录信息,方便阅读。点击这里下载PDF演示页
使用Adobe Acrobat或Adobe Reader可以获得最佳效果,如图所示:
用法:打开VBA编辑器,将代码复制粘贴过去,运行LinkAllReference,即可。但要注意,生成PDF文件时要按照这个步骤:点击“文件”,“另存为”,在弹出的对话框中,把“保存类型”选为pdf,按下图设置:
进阶的用法是,使用我提供的NoteExpress样式,效果更佳。对于一些没有DOI的文献,这个样式可以把NoteExpress里存储的链接放在参考文献题录里,用$$……$$标记起来,实现同样的点击效果。而且本代码在添加链接之后,会自动把多余的链接隐藏,不影响PDF和打印版的外观。点击这里下载样式
VBA代码如下,Word 2021测试通过,WPS可用。
Sub LinkAllReference()
cursorStart = AddBookmarksToReferenceList("NE.Bib", 1)
Selection.HomeKey Unit:=wdStory
If cursorStart = -1 Then
MsgBox "No field with code containing '" & "NE.Bib" & "' found.", vbInformation
Exit Sub
End If
'The first field
startFieldNumber = SelectNextField("NE.Ref", 1)
cursorStart = Selection.Range.Start
If cursorStart >= 0 Then
cursorStart = cursorStart + 1
Do
If InStr(1, Selection.Text, ";") > 0 Then
'Debug.Print "More than one references: "; Selection.Text
originalLen = Len(Selection.Text)
originalStr = Selection.Text
countRef = Len(originalStr) - Len(Replace(originalStr, ";", "")) + 1
Selection.MoveEnd Unit:=wdCharacter, Count:=-originalLen
For i = 1 To countRef - 1
Selection.MoveStart Unit:=wdCharacter, Count:=1
Selection.MoveEndUntil Cset:=";", Count:=wdForward
If Selection.Hyperlinks.Count = 0 Then dummy = AddHyperlinkToBookmark
Next i
Selection.MoveStart Unit:=wdCharacter, Count:=1
Selection.MoveEndUntil Cset:=")", Count:=wdForward
If Selection.Hyperlinks.Count = 0 Then dummy = AddHyperlinkToBookmark
Else
If Selection.Hyperlinks.Count = 0 Then dummy = AddHyperlinkToBookmark
End If
If startFieldNumber > 0 Then startFieldNumber = SelectNextField("NE.Ref", startFieldNumber)
cursorField = Selection.Range.Start
DoEvents
Loop While cursorField > cursorStart And cursorField > 0 And startFieldNumber > 0
Else
MsgBox "No field with code containing '" & "NE.Ref" & "' found after the cursor position.", vbInformation
End If
dummy = hideRefHyperlink("NE.Bib", 1)
MsgBox "Complete.", vbExclamation
End Sub
Function AddBookmarksToReferenceList(fieldName, startFieldNumber)
Dim selectedRange As Range
Dim para As Paragraph
Dim bookmarkName As String
Dim regex As Object
Dim match As Object
Dim counter As Integer
Selection.HomeKey Unit:=wdStory
For Each field In ActiveDocument.Fields
' Check if the field code contains the specified string
If InStr(1, field.Code.Text, fieldName, vbTextCompare) > 0 Then
' Check if the field is after the current cursor position
If field.Code.Start > currentPosition Then
' Select the field
field.Select
' Exit the loop after the first match (selecting the next occurrence)
Exit For
End If
End If
Next field
' Show hidden font(hyperlinks)
Selection.Font.Hidden = False
cursorStart = Selection.Range.Start
' Check if text is selected
If Selection.Type = wdSelectionIP Or cursorStart = -1 Then
MsgBox "Field NE.Lib not found.", vbExclamation
Exit Function
End If
' Set the selected range
Set selectedRange = Selection.Range
' Initialize counter
counter = 0
' Loop through each paragraph in the selected range
For Each para In selectedRange.Paragraphs
' Get the text until the first comma and 4-digit number
bookmarkName = getBookmarkName(para.Range.Text)
If bookmarkName <> "" Then
counter = counter + 1
ActiveDocument.Bookmarks.Add Name:=bookmarkName, Range:=para.Range
End If
Next para
Set regex = Nothing
Debug.Print counter & " Bookmarks added successfully!"
End Function
Function AddHyperlinkToBookmark()
Dim selectedRange As Range
Dim bookmarkName As String
Dim bookmarkRange As Range
Dim hyperlinkAddress As String
Dim hyperlinkAltText As String
Dim match As Object
' Check if text is selected
If Selection.Type = wdSelectionIP Then
'MsgBox "Please select some text before running this macro.", vbExclamation
Debug.Print "Please select some text before running this macro."
AddHyperlinkToBookmark = -1
Exit Function
End If
' Set the selected range
Set selectedRange = Selection.Range
If Left(selectedRange.Text, 1) = "(" Then
selectedRange.MoveStart Unit:=wdCharacter, Count:=1
If Right(selectedRange.Text, 1) = ")" Then selectedRange.MoveEnd Unit:=wdCharacter, Count:=-1
Selection.Collapse
Selection.Start = selectedRange.Start
Selection.End = selectedRange.End
Else
selectedRange.MoveStart Unit:=wdCharacter, Count:=1
selectedRange.Start = selectedRange.Start - 1
Selection.Collapse
Selection.Start = selectedRange.Start
If Right(selectedRange.Text, 1) = ")" Then
Selection.End = selectedRange.End - 1
Else
Selection.End = selectedRange.End
End If
End If
temptext = Replace(Selection.Text, "(", ", ")
temptext = Replace(temptext, ")", "")
bookmarkName = getBookmarkName(temptext)
' Check if the bookmark exists
matchedBookmark = FindStringInBookmarks(bookmarkName)
If matchedBookmark = "0" Then
'MsgBox "Bookmark not found.", vbExclamation
Debug.Print "Bookmark not found: " & bookmarkName
AddHyperlinkToBookmark = -1
Selection.Collapse Direction:=wdCollapseEnd
Exit Function
ElseIf matchedBookmark = "+" Then
'MsgBox "More than one bookmarks found.", vbExclamation
Debug.Print "Multi bookmarks: " & bookmarkName
AddHyperlinkToBookmark = -1
Selection.Collapse Direction:=wdCollapseEnd
Exit Function
End If
' Set the range of the bookmark
Set bookmarkRange = ActiveDocument.Bookmarks(matchedBookmark).Range
' Get the hyperlink address and alt text
hyperlinkAltText = bookmarkRange.Paragraphs(1).Range.Text ' Alt text is the entire paragraph
hyperlinkAltText = Replace(hyperlinkAltText, ChrW(8204), "") 'Remove zero-width space
hyperlinkAddress = hyperlinkAltText
If InStr(1, bookmarkRange.Paragraphs(1).Range.Text, "$$") > 0 Then hyperlinkAltText = Left(hyperlinkAltText, InStr(1, hyperlinkAltText, "$$") - 1)
hyperlinkAltText = Replace(Replace(hyperlinkAltText, "“", """"), "”", """") 'Replace widechar quote marks to avoid word bug, otherwise the link would be broken
If InStr(1, hyperlinkAddress, "DOI: ") > 0 Then
hyperlinkAddress = "https://doi.org/" & MidBetween(1, hyperlinkAddress, "DOI: ", ".$$")
If Right(hyperlinkAddress, 1) = "." Then hyperlinkAddress = Left(hyperlinkAddress, Len(hyperlinkAddress) - 1)
ElseIf InStr(1, hyperlinkAddress, "/OL]") > 0 Then
hyperlinkAddress = MidBetween(1, hyperlinkAddress, "/OL]. ", "." & vbCrLf)
If Right(hyperlinkAddress, 1) = "." Then hyperlinkAddress = Left(hyperlinkAddress, Len(hyperlinkAddress) - 1)
ElseIf InStr(1, hyperlinkAddress, "$$") > 0 Then
hyperlinkAddress = MidBetween(1, hyperlinkAddress, "$$", "$$")
Else
hyperlinkAddress = "#" & matchedBookmark
End If
' Add the hyperlink
If inOneLine() Then
selectedRange.Hyperlinks.Add Anchor:=selectedRange, Address:=hyperlinkAddress, ScreenTip:=hyperlinkAltText
Else
originalStart = Selection.Start
'originalEnd = Selection.End
originalLen = Len(Selection.Text)
Selection.Collapse Direction:=wdCollapseStart
Selection.EndOf Unit:=wdLine, Extend:=wdExtend
Set selectedRange = Selection.Range
firstLen = Len(selectedRange.Text)
selectedRange.Hyperlinks.Add Anchor:=selectedRange, Address:=hyperlinkAddress, ScreenTip:=hyperlinkAltText
newStart = selectedRange.End + 1
Selection.Collapse
Selection.Start = newStart
Selection.End = newStart + originalLen - firstLen
Set selectedRange = Selection.Range
If selectedRange.Text <> "" Then selectedRange.Hyperlinks.Add Anchor:=selectedRange, Address:=hyperlinkAddress, ScreenTip:=hyperlinkAltText
End If
Set regex = Nothing
AddHyperlinkToBookmark = 1
'MsgBox "Hyperlink added successfully!", vbInformation
'Debug.Print "Hyperlink added: " & hyperlinkAddress
End Function
Function hideRefHyperlink(fieldName, startFieldNumber)
Dim selectedRange As Range
Dim para As Paragraph
Dim bookmarkRange As Range
Selection.HomeKey Unit:=wdStory
For Each field In ActiveDocument.Fields
' Check if the field code contains the specified string
If InStr(1, field.Code.Text, fieldName, vbTextCompare) > 0 Then
' Check if the field is after the current cursor position
If field.Code.Start > currentPosition Then
' Select the field
field.Select
' Exit the loop after the first match (selecting the next occurrence)
Exit For
End If
End If
Next field
cursorStart = Selection.Range.Start
' Check if text is selected
If Selection.Type = wdSelectionIP Or cursorStart = -1 Then
MsgBox "Field NE.Lib not found.", vbExclamation
Exit Function
End If
Set selectedRange = Selection.Range
Set temprange = Selection.Range
' Loop through each paragraph in the selected range
For Each para In selectedRange.Paragraphs
If Len(para.Range.Text) > 1 Then
' Set the selected range
Set temprange = para.Range
temprange.MoveEnd Unit:=wdCharacter, Count:=-1
Selection.Collapse
Selection.Start = temprange.Start
Selection.End = temprange.End
If Selection.Paragraphs.Count > 1 Then
Set temp = Selection.Paragraphs(2).Range
Selection.Collapse
temp.Select
End If
Set bookmarkRange = para.Range
' Get the hyperlink address and alt text
temptext = bookmarkRange.Paragraphs(1).Range.Text ' Alt text is the entire paragraph
temptext = Replace(temptext, ChrW(8204), "") 'Remove zero-width space
If InStr(1, temptext, "DOI: ") > 0 Then
hyperlinkAddress = "https://doi.org/" & MidBetween(1, temptext, "DOI: ", ".$$")
If Right(hyperlinkAddress, 1) = "." Then hyperlinkAddress = Left(hyperlinkAddress, Len(hyperlinkAddress) - 1)
ElseIf InStr(1, temptext, "/OL]") > 0 Then
hyperlinkAddress = MidBetween(1, temptext, "/OL]. ", "." & vbCrLf)
If Right(hyperlinkAddress, 1) = "." Then hyperlinkAddress = Left(hyperlinkAddress, Len(hyperlinkAddress) - 1)
ElseIf InStr(1, temptext, "$$") > 0 Then
hyperlinkAddress = MidBetween(1, temptext, "$$", "$$")
Else
hyperlinkAddress = "#"
If Len(temptext) > 1 Then Debug.Print "No url for: " & Replace(temptext, vbCr, "")
End If
' Add the hyperlink
If hyperlinkAddress <> "#" Then
If Selection.Hyperlinks.Count = 0 Then Selection.Range.Hyperlinks.Add Anchor:=Selection.Range, Address:=hyperlinkAddress
End If
'Hide the temp hyperlink in bookmark range
If InStr(1, bookmarkRange.Text, "$$") > 0 Then
bookmarkRange.MoveStartUntil "$$"
bookmarkRange.MoveEnd wdCharacter, -1
bookmarkRange.Select
bookmarkRange.Font.Hidden = True
End If
End If
'Selection.Range.MoveStart Unit:=wdCharacter, Count:=1
Next para
Debug.Print "Hyperlink in Reference successfully hidden!"
End Function
Function SelectNextField(fieldName, startFieldNumber)
Dim field As field
Dim currentPosition As Long
SelectNextField = -1
' Get the current cursor position
currentPosition = Selection.Range.Start + 1
' Iterate through all fields in the document
For i = startFieldNumber To ActiveDocument.Fields.Count
Set field = ActiveDocument.Fields(i)
' Check if the field code contains the specified string
If InStr(1, field.Code.Text, fieldName, vbTextCompare) > 0 Then
' Check if the field is after the current cursor position
If field.Code.Start > currentPosition Then
' Select the field
field.Select
' Exit the loop after the first match (selecting the next occurrence)
SelectNextField = i
Set field = Nothing
Exit For
End If
End If
Next i
' Inform the user if no matching field is found
If Selection.Type = wdSelectionIP Then
SelectNextField = -1
Set field = Nothing
'MsgBox "No field with code containing '" & fieldName & "' found after the cursor position.", vbInformation
End If
End Function
Function FindStringInBookmarks(searchString As String) As String
Dim bookmark As bookmark
counter = 0
' Loop through all bookmarks in the document
For Each bookmark In ActiveDocument.Bookmarks
' Check if the search string is found in the bookmark's range
If InStr(1, bookmark.Name, searchString, vbTextCompare) = 1 Then
' Return the name of the first matching bookmark
FindStringInBookmarks = bookmark.Name
counter = counter + 1
End If
Next bookmark
' No match or more than one matches found
If counter = 0 Then
FindStringInBookmarks = "0"
ElseIf counter > 1 Then
FindStringInBookmarks = "+"
End If
End Function
Function inOneLine()
' Check if the selection range is within one line
With Selection.Range
originalEnd = Selection.Range.End
.Collapse Direction:=wdCollapseStart
t1 = .Information(wdFirstCharacterLineNumber)
.Start = originalEnd
t2 = .Information(wdFirstCharacterLineNumber)
inOneLine = (t1 = t2)
End With
End Function
Function MidBetween(nCursor, strText, strStart, strEnd)
Dim nStart, nEnd
If InStr(nCursor, strText, strStart) = 0 Then
MidBetween = ""
Else
nStart = InStr(nCursor, strText, strStart) + Len(strStart)
If InStr(nCursor, strText, strEnd) = 0 Then
nEnd = Len(strText)
Else
nEnd = InStr(nStart, strText, strEnd)
End If
MidBetween = Mid(strText, nStart, nEnd - nStart)
End If
End Function
Function getBookmarkName(strRef)
On Error GoTo errhandler
If strRef = "" Or InStr(1, strRef, ",") = 0 Then Exit Function
' Create a regular expression object
Set regex = CreateObject("VBScript.RegExp")
regex.Pattern = "(.*?)(\d{4}[a-z\.-])" ' Match text until the first comma, followed by a 4-digit number and a period
tmp = Replace(strRef, " and ", ", ") & "."
tmp = regex.Execute(tmp)(0)
arr = Split(tmp, ", ")
For i = 0 To UBound(arr)
If InStr(1, arr(i), " ") Then
getBookmarkName = getBookmarkName & MidBetween(1, arr(i), "", " ") & "_"
Else
getBookmarkName = getBookmarkName & arr(i) & "_"
End If
Next i
getBookmarkName = Mid(getBookmarkName, 1, Len(getBookmarkName) - 1)
getBookmarkName = Replace(getBookmarkName, ".", "")
getBookmarkName = Replace(getBookmarkName, "(", "")
getBookmarkName = Replace(getBookmarkName, "-", "")
getBookmarkName = UCase(getBookmarkName)
If Right(getBookmarkName, 1) = "_" Then getBookmarkName = Mid(getBookmarkName, 1, Len(getBookmarkName) - 1)
Exit Function
errhandler:
Debug.Print Err.Description
getBookmarkName = ""
End Function
以上。
造了个大轮子的老狼