It seems I have found a way. I found it with AI. I can rename and move linked files and the links are not broken. It is a little complicated, but it seems to work well.
The linked files are .odt files. The files containing the links MUST be .doc (.docm) files. (Word is able not to rewrite .lnk URLs.)
Let’s suppose all your linked .odt files are stored somewhere inside a folder named FOLDERX. (The code searches upward from the current .odt file and uses the nearest parent folder named FOLDERX as the root folder.)
1/ Create a file named helper_file.odt.vbs and save it to C:\Temp. (The code is below, code number 1.)
2/ Create a macro in LO Writer, Macro A. (The code is below, code number 2.) This macro saves the .odt file and also automatically creates a .lnk shortcut in the folder Shortcutsx (and a PDF in the folder PDF; if you don’t need PDFs, you can ignore this feature).
IT ISN’T POSSIBLE TO DELETE SHORTCUTS, RENAME THEM, OR REORDER THIS FOLDER! However, the folder can be messy. You are not supposed to search for files there, etc.
You can customize your toolbar so that you can run this macro with one click, just as easily as saving files in the normal way.
3/ Create another macro in LO Writer, Macro B. (The code is below, code number 3.) If you saved a file with Macro A and still have the file open, Macro B copies the path to the matching .lnk file in the folder Shortcutsx to the clipboard. So, you now have the path needed for the URL. (You can customize your toolbar so that you can run this macro with one click.)
4/ Create a macro in a DOC file. (The code is below, code number 4.) If you highlight some text in Word and then run the macro (you can customize your Word toolbar so that you can run this macro with one click), it inserts the URL from the clipboard which already contains the URL copied by Macro B.
(If I tried to select the path manually, it sometimes changed automatically: I needed the path to the .lnk file, but it changed to the original linked file instead. In addition, if you create links using the macro, the Shortcutsx folder can contain any amount of clutter resulting, for example, from gradual folder reorganizations (remember that nothing should be deleted from this folder, otherwise existing hyperlinks may stop working), and this will not affect your day-to-day use in any way.)
To avoid having to confirm every time (twice) that the document is trustworthy, I had to modify the Windows registry. If I remember correctly, the following settings were sufficient:
DisableHyperlinkWarning = 1
BlockContentExecutionFromInternet = 0
After that, Word stopped repeatedly displaying security warnings when opening linked files.
1/ Code:
Option Explicit
Const ROOT_FOLDER_NAME = "FOLDERX"
Dim fso, Wsh, Lnk
Dim RootFolder, ShortcutsxFolder
Dim DocPath, RelPath, LinkPath, ParentFolder
Set fso = CreateObject("Scripting.FileSystemObject")
If WScript.Arguments.Count < 1 Then
MsgBox "Nebyla predana cesta k ODT."
WScript.Quit
End If
DocPath = WScript.Arguments(0)
If Not fso.FileExists(DocPath) Then
MsgBox "Neexistuje ODT: " & DocPath
WScript.Quit
End If
RootFolder = NajdiKorenovouSlozku(DocPath, ROOT_FOLDER_NAME)
If RootFolder = "" Then
MsgBox "Soubor neni pod korenem: " & ROOT_FOLDER_NAME
WScript.Quit
End If
ShortcutsxFolder = RootFolder & "\Shortcutsx"
RelPath = Mid(DocPath, Len(RootFolder) + 2)
LinkPath = fso.BuildPath(ShortcutsxFolder, RelPath)
LinkPath = LinkPath & ".lnk"
ParentFolder = fso.GetParentFolderName(LinkPath)
Call CreateFolders(ParentFolder)
If Not fso.FileExists(LinkPath) Then
Set Wsh = CreateObject("WScript.Shell")
Set Lnk = Wsh.CreateShortcut(LinkPath)
Lnk.TargetPath = DocPath
Lnk.WorkingDirectory = fso.GetParentFolderName(DocPath)
Lnk.Save
End If
Function NajdiKorenovouSlozku(ByVal FilePath, ByVal RootName)
Dim FolderPath
FolderPath = fso.GetParentFolderName(FilePath)
Do While FolderPath <> ""
If LCase(fso.GetFileName(FolderPath)) = LCase(RootName) Then
NajdiKorenovouSlozku = FolderPath
Exit Function
End If
If fso.GetParentFolderName(FolderPath) = FolderPath Then Exit Do
FolderPath = fso.GetParentFolderName(FolderPath)
Loop
NajdiKorenovouSlozku = ""
End Function
Sub CreateFolders(FolderPath)
If FolderPath = "" Then Exit Sub
If fso.FolderExists(FolderPath) Then Exit Sub
Call CreateFolders(fso.GetParentFolderName(FolderPath))
fso.CreateFolder FolderPath
End Sub
2/ Macro A:
Option Explicit
Const VBS_SCRIPT As String = "C:\Temp\helper_file.odt.vbs"
Const ROOT_FOLDER_NAME As String = "FOLDERX"
Sub SaveAndCreateShortcut()
On Error GoTo ErrorHandler
Dim oDoc As Object
Dim oDisp As Object
Dim args(0) As New com.sun.star.beans.PropertyValue
Dim sDocPath As String
Dim sCmd As String
oDoc = ThisComponent
If oDoc.URL = "" Then
oDisp = createUnoService("com.sun.star.frame.DispatchHelper")
oDisp.executeDispatch(oDoc.CurrentController.Frame, ".uno:SaveAs", "", 0, args())
If oDoc.URL = "" Then Exit Sub
Else
oDoc.store()
End If
sDocPath = ConvertFromURL(oDoc.URL)
sCmd = "wscript.exe " & Chr(34) & VBS_SCRIPT & Chr(34) & " " & Chr(34) & sDocPath & Chr(34)
Shell sCmd, 0, True
ExportPDF
Exit Sub
ErrorHandler:
MsgBox "Macro error: " & Err & Chr(13) & Error$, 16, "Error"
End Sub
Sub ExportPDF()
On Error GoTo PDFError
Dim oDoc As Object
Dim sDocPath As String
Dim sRootFolder As String
Dim sPdfFolderRoot As String
Dim sRelPath As String
Dim sPdfPath As String
Dim sPdfUrl As String
Dim sPdfFolder As String
Dim nDot As Long
Dim nSlash As Long
Dim pdfArgs(0) As New com.sun.star.beans.PropertyValue
oDoc = ThisComponent
If oDoc.URL = "" Then
MsgBox "Cannot create PDF because the document has not been saved.", 16, "PDF Error"
Exit Sub
End If
sDocPath = ConvertFromURL(oDoc.URL)
sRootFolder = FindRootFolder(sDocPath, ROOT_FOLDER_NAME)
If Len(sRootFolder) = 0 Then
MsgBox "PDF was not created because the folder '" & ROOT_FOLDER_NAME & "' was not found above the document.", 16, "PDF Error"
Exit Sub
End If
sPdfFolderRoot = sRootFolder & "\PDF"
sRelPath = Mid(sDocPath, Len(sRootFolder) + 2)
sPdfPath = sPdfFolderRoot & "\" & sRelPath
nDot = LastPosition(sPdfPath, ".")
nSlash = LastPosition(sPdfPath, "\")
If nDot > nSlash Then
sPdfPath = Left(sPdfPath, nDot - 1) & "_" & TimeStamp() & ".pdf"
Else
sPdfPath = sPdfPath & "_" & TimeStamp() & ".pdf"
End If
nSlash = LastPosition(sPdfPath, "\")
sPdfFolder = Left(sPdfPath, nSlash - 1)
CreateFolders sPdfFolder
sPdfUrl = ConvertToURL(sPdfPath)
pdfArgs(0).Name = "FilterName"
pdfArgs(0).Value = "writer_pdf_Export"
oDoc.storeToURL(sPdfUrl, pdfArgs)
Exit Sub
PDFError:
MsgBox "Error while exporting PDF: " & Err & Chr(13) & Error$, 16, "PDF Error"
End Sub
Function FindRootFolder(ByVal sDocPath As String, ByVal sRootName As String) As String
Dim sFolder As String
Dim sName As String
Dim nSlash As Long
sFolder = GetFileFolder(sDocPath)
Do While Len(sFolder) > 3
sName = GetFolderName(sFolder)
If LCase(sName) = LCase(sRootName) Then
FindRootFolder = sFolder
Exit Function
End If
nSlash = LastPosition(sFolder, "\")
If nSlash <= 3 Then Exit Do
sFolder = Left(sFolder, nSlash - 1)
Loop
FindRootFolder = ""
End Function
Function GetFileFolder(ByVal sFilePath As String) As String
Dim nSlash As Long
nSlash = LastPosition(sFilePath, "\")
If nSlash > 0 Then
GetFileFolder = Left(sFilePath, nSlash - 1)
Else
GetFileFolder = ""
End If
End Function
Function GetFolderName(ByVal sFolder As String) As String
Dim nSlash As Long
nSlash = LastPosition(sFolder, "\")
If nSlash > 0 Then
GetFolderName = Mid(sFolder, nSlash + 1)
Else
GetFolderName = sFolder
End If
End Function
Function TimeStamp() As String
TimeStamp = _
CStr(Year(Now)) & "-" & _
Right("0" & CStr(Month(Now)), 2) & "-" & _
Right("0" & CStr(Day(Now)), 2) & "_" & _
Right("0" & CStr(Hour(Now)), 2) & _
Right("0" & CStr(Minute(Now)), 2) & _
Right("0" & CStr(Second(Now)), 2)
End Function
Sub CreateFolders(ByVal sFolder As String)
Dim sParent As String
Dim nSlash As Long
If Len(sFolder) = 0 Then Exit Sub
If Dir(sFolder, 16) <> "" Then Exit Sub
nSlash = LastPosition(sFolder, "\")
If nSlash <= 3 Then Exit Sub
sParent = Left(sFolder, nSlash - 1)
If Dir(sParent, 16) = "" Then
CreateFolders sParent
End If
MkDir sFolder
End Sub
Function LastPosition(ByVal sText As String, ByVal sSearch As String) As Long
Dim i As Long
For i = Len(sText) To 1 Step -1
If Mid(sText, i, Len(sSearch)) = sSearch Then
LastPosition = i
Exit Function
End If
Next i
LastPosition = 0
End Function
____
3/ Macro B
Option Explicit
Const ROOT_FOLDER_NAME As String = "FOLDERX"
Sub CopyShortcutPathToClipboard()
On Error GoTo ErrorHandler
Dim oDoc As Object
Dim sDocPath As String
Dim sRootFolder As String
Dim sRelPath As String
Dim sLinkPath As String
oDoc = ThisComponent
If oDoc.URL = "" Then
MsgBox "The document has not been saved.", 16, "Error"
Exit Sub
End If
sDocPath = ConvertFromURL(oDoc.URL)
sRootFolder = FindRootFolder(sDocPath, ROOT_FOLDER_NAME)
If Len(sRootFolder) = 0 Then
MsgBox "The document is not located in the folder " & ROOT_FOLDER_NAME & ".", 16, "Error"
Exit Sub
End If
sRelPath = Mid(sDocPath, Len(sRootFolder) + 2)
sLinkPath = sRootFolder & "\Shortcutsx\" & sRelPath
sLinkPath = sLinkPath & ".lnk"
CopyTextToClipboard sLinkPath
Exit Sub
ErrorHandler:
MsgBox "Macro error: " & Err & Chr(13) & Error$, 16, "Error"
End Sub
Function FindRootFolder(ByVal sDocPath As String, ByVal sRootName As String) As String
Dim sFolder As String
Dim sName As String
Dim nSlash As Long
sFolder = GetFileFolder(sDocPath)
Do While Len(sFolder) > 3
sName = GetFolderName(sFolder)
If LCase(sName) = LCase(sRootName) Then
FindRootFolder = sFolder
Exit Function
End If
nSlash = LastPosition(sFolder, "\")
If nSlash <= 3 Then Exit Do
sFolder = Left(sFolder, nSlash - 1)
Loop
FindRootFolder = ""
End Function
Function GetFileFolder(ByVal sFilePath As String) As String
Dim nSlash As Long
nSlash = LastPosition(sFilePath, "\")
If nSlash > 0 Then
GetFileFolder = Left(sFilePath, nSlash - 1)
Else
GetFileFolder = ""
End If
End Function
Function GetFolderName(ByVal sFolder As String) As String
Dim nSlash As Long
nSlash = LastPosition(sFolder, "\")
If nSlash > 0 Then
GetFolderName = Mid(sFolder, nSlash + 1)
Else
GetFolderName = sFolder
End If
End Function
Function LastPosition(ByVal sText As String, ByVal sSearch As String) As Long
Dim i As Long
For i = Len(sText) To 1 Step -1
If Mid(sText, i, Len(sSearch)) = sSearch Then
LastPosition = i
Exit Function
End If
Next i
LastPosition = 0
End Function
Sub CopyTextToClipboard(ByVal sText As String)
Dim oClip As Object
Dim oTransferable As Object
oClip = createUnoService("com.sun.star.datatransfer.clipboard.SystemClipboard")
oTransferable = CreateUnoListener("Clip_", "com.sun.star.datatransfer.XTransferable")
gClipText = sText
oClip.setContents(oTransferable, Nothing)
End Sub
Global gClipText As String
Function Clip_getTransferData(aFlavor)
If aFlavor.MimeType = "text/plain;charset=utf-16" Then
Clip_getTransferData = gClipText
Else
Clip_getTransferData = gClipText
End If
End Function
Function Clip_getTransferDataFlavors()
Dim aFlavor(0) As New com.sun.star.datatransfer.DataFlavor
aFlavor(0).MimeType = "text/plain;charset=utf-16"
aFlavor(0).HumanPresentableName = "Unicode Text"
Clip_getTransferDataFlavors = aFlavor()
End Function
Function Clip_isDataFlavorSupported(aFlavor) As Boolean
Clip_isDataFlavorSupported = True
End Function
____
4/
Option Explicit
#If VBA7 Then
Private Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As Long
Private Declare PtrSafe Function CloseClipboard Lib "user32" () As Long
Private Declare PtrSafe Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As LongPtr
Private Declare PtrSafe Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long
Private Declare PtrSafe Function GlobalLock Lib "kernel32" (ByVal hMem As LongPtr) As LongPtr
Private Declare PtrSafe Function GlobalUnlock Lib "kernel32" (ByVal hMem As LongPtr) As Long
Private Declare PtrSafe Function lstrlenW Lib "kernel32" (ByVal lpString As LongPtr) As Long
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As LongPtr, ByVal Source As LongPtr, ByVal Length As LongPtr)
#Else
Private Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function CloseClipboard Lib "user32" () As Long
Private Declare Function GetClipboardData Lib "user32" (ByVal wFormat As Long) As Long
Private Declare Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long
Private Declare Function GlobalLock Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function GlobalUnlock Lib "kernel32" (ByVal hMem As Long) As Long
Private Declare Function lstrlenW Lib "kernel32" (ByVal lpString As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As Long, ByVal Source As Long, ByVal Length As Long)
#End If
Private Const CF_UNICODETEXT As Long = 13
Sub InsertHyperlinkFromClipboardToSelectedText()
On Error GoTo ErrorHandler
Dim sAddress As String
Dim r As Range
Dim sDisplayText As String
If Selection.Range.Start = Selection.Range.End Then
MsgBox "First select the text that should become the hyperlink.", vbExclamation, "No text selected"
Exit Sub
End If
sAddress = Trim$(GetTextFromWindowsClipboard())
If Len(sAddress) = 0 Then
MsgBox "There is no readable text in the clipboard." & vbCrLf & _
"Try checking it by pressing Ctrl+V in Notepad.", _
vbExclamation, "Clipboard"
Exit Sub
End If
Set r = Selection.Range
sDisplayText = r.Text
ActiveDocument.Hyperlinks.Add _
Anchor:=r, _
Address:=sAddress, _
TextToDisplay:=sDisplayText
Exit Sub
ErrorHandler:
MsgBox "Macro error: " & Err.Number & vbCrLf & Err.Description, vbCritical, "Error"
End Sub
Function GetTextFromWindowsClipboard() As String
On Error GoTo EndFunction
Dim hData As LongPtr
Dim pData As LongPtr
Dim nChars As Long
Dim sText As String
If IsClipboardFormatAvailable(CF_UNICODETEXT) = 0 Then GoTo EndFunction
If OpenClipboard(0) = 0 Then GoTo EndFunction
hData = GetClipboardData(CF_UNICODETEXT)
If hData = 0 Then GoTo CloseIt
pData = GlobalLock(hData)
If pData = 0 Then GoTo CloseIt
nChars = lstrlenW(pData)
If nChars > 0 Then
sText = String$(nChars, vbNullChar)
CopyMemory StrPtr(sText), pData, nChars * 2
GetTextFromWindowsClipboard = sText
End If
GlobalUnlock hData
CloseIt:
CloseClipboard
EndFunction:
End Function