Hello all. I see that when linking to chapter headings in LO Write, it sometimes loses track of which chapter heading it’s supposed to link to. I only found out about this yesterday, but it apparently cannot keep up with much messing around with headings, e.g. moving and renaming headings around the document. I’ll see if I can replicate the issue, but right now I’m a little worried about fixing my own document (+150 pages, hundreds of links).
So:
- How may I prevent breaking more links? Is there some behaviour I should be avoiding?
- How may I find broken internal links? An extension, a script in any language… please! Alex Kemp is onto something – Utilities to fix Broken Hyperlinks? – but it’s not working well for me. Also, it’d be better if this were a LO add-on, which could take me to where the broken links are; or the next broken link, starting from the current cursor position.
EDIT: I started writing a macro. When generating the list of anchors, I need to access the outline numbering for headings. And I need to open the hyperlink editor on the current text portion.
Option Explicit
' PrintArray displays a MsgBox with the whole array
' for DEBUG purposes only
Sub PrintArray(sTitle as String, theArray() as String)
Dim sArray, i, iStart, iStop
sArray = sTitle & ":"
iStart = LBound(theArray)
iStop = UBound(theArray)
If iStart<=iStop then
For i = iStart to iStop
sArray = sArray & Chr(13) & theArray(i)
Next
End if
MsgBox(sArray, 64, "***DEBUG")
End sub
' auxiliary sub for BuildAnchorList
Sub AddItemToAnchorList (oAnchors() as String, sTheAnchor as String, sType as String)
Dim sAnchor
Select Case sType
Case "Heading":
sAnchor = "#" + sTheAnchor + "|outline"
Case "Table":
sAnchor = "#" + sTheAnchor + "|table"
Case "Text Frame":
sAnchor = "#" + sTheAnchor + "|frame"
Case "Image":
sAnchor = "#" + sTheAnchor + "|graphic"
Case "Object":
sAnchor = "#" + sTheAnchor + "|ole"
Case "Section":
sAnchor = "#" + sTheAnchor + "|region"
Case "Bookmark":
sAnchor = "#" + sTheAnchor
End Select
ReDim Preserve oAnchors(UBound(oAnchors)+1) as String
oAnchors(UBound(oAnchors)) = sAnchor
End Sub
' auxiliary sub for BuildAnchorList
Sub AddArrayToAnchorList (oAnchors() as String, oNewAnchors() as String, sType as String)
Dim i, iStart, iStop
iStart = LBound(oNewAnchors)
iStop = UBound(oNewAnchors)
If iStop < iStart then Exit Sub ' empty array, nothing to do
For i = iStart to iStop
AddItemToAnchorList (oAnchors, oNewAnchors(i), sType)
Next
End Sub
Function BuildAnchorList()
Dim oDoc as Object, oAnchors() as String
oDoc = ThisComponent
' get the whole document outline
Dim oParagraphs, thisPara, oTextPortions, thisPortion
oParagraphs = oDoc.Text.createEnumeration ' all the paragraphs
Do While oParagraphs.hasMoreElements
thisPara = oParagraphs.nextElement
If thisPara.ImplementationName = "SwXParagraph" then ' is a paragraph
If thisPara.OutlineLevel>0 Then ' is a heading
' ***
' *** TO DO: How do we get the numbering for each heading?
' For example, if the first level 1 heading text is “Introduction”,
' the correct anchor is `#1.Introduction|outline`
' and we are recording `Introduction|outline`
' ***
AddItemToAnchorList (oAnchors, thisPara.String, "Heading")
End if
End if
Loop
' text tables, text frames, images, objects, bookmarks and text sections
AddArrayToAnchorList(oAnchors, oDoc.getTextTables().ElementNames, "Table")
AddArrayToAnchorList(oAnchors, oDoc.getTextFrames().ElementNames, "Text Frame")
AddArrayToAnchorList(oAnchors, oDoc.getGraphicObjects().ElementNames, "Image")
AddArrayToAnchorList(oAnchors, oDoc.getEmbeddedObjects().ElementNames, "Object")
AddArrayToAnchorList(oAnchors, oDoc.Bookmarks.ElementNames, "Bookmark")
AddArrayToAnchorList(oAnchors, oDoc.getTextSections().ElementNames, "Section")
BuildAnchorList = oAnchors
End Function
Function isInArray( theString as String, theArray() as String)
Dim i, iStart, iStop
iStart = LBound(theArray)
iStop = UBound(theArray)
If iStart<=iStop then
For i = iStart to iStop
If theString = theArray(i) then
isInArray = True
Exit function
End if
Next
End if
isInArray = False
End function
Sub FindBrokenInternalLinks
' Find the next broken internal link
'
' Pseudocode:
'
' * generate link of anchors - *** TO DO: prefix the outline numbering for headings
' * loop, searching for internal links
' - is the internal link in the anchor list?
' * Yes: continue to next link
' * No: (broken link found)
' - select that link text - *** TO DO: cannot select it
' - open link editor so user can fix this
' - stop
' * end loop
' * display message "No bad internal links found"
Dim oDoc as Object, oFrame as Object, oDispatcher as Object
Dim oAnchors() as String ' list of all anchors in the document
Dim oParagraphs, thisPara, oTextPortions, thisPortion ' for interating through doc
Dim sMsg ' for MsgBox
oDoc = ThisComponent
oFrame = ThisComponent.CurrentController.Frame
oDispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
' get all document anchors
oAnchors = BuildAnchorList()
PrintArray("Anchor list", oAnchors) ' *** DEBUG ***
' find links
oParagraphs = oDoc.Text.createEnumeration ' has all the paragraphs
Dim iLinks
Dim oViewCursor
Dim sLink
iLinks = 0 ' internal link counter
' go through all the paragraphs
While oParagraphs.hasMoreElements
thisPara = oParagraphs.nextElement
oTextPortions = thisPara.createEnumeration
' go through all the text portions in current paragraph
While oTextPortions.hasMoreElements
thisPortion = oTextPortions.nextElement
If left(thisPortion.HyperLinkURL, 1) = "#" then
' internal link found
iLinks = iLinks + 1
sLink = thisPortion.HyperLinkURL
If not isInArray(sLink, oAnchors) then
' anchor not found
' *** DEBUG: code below up to MsgBox
Dim sHas
If HasUnoInterfaces(thisPortion, "com.sun.star.text.XTextRange") then _
sHas = "yes" Else sHas = "no"
sMsg = "Bad link: [" & thisPortion.String & "] -> [" _
& thisPortion.HyperLinkURL & "] XTextRange interface? " & sHas
MsgBox (sMsg, 48, "Find broken internal link")
' ***
' *** TO DO: How do we open a _specific_ hyperlink for editing?
' Do we pass parameters to `.uno:EditHyperlink`?
' Do we move the cursor? (Except all moves I found were relative,
' e.g. `.uno:GoRight`)
' Do we use the text portion’s `.Start` and `.End` properties?
' ***
' open the current hyperlink for editing
oDispatcher.executeDispatch(oFrame, ".uno:EditHyperlink", "", 0, Array())
Exit sub
End If
End if
Wend
Wend
If iLinks then
sMsg = iLinks & " internal links found, all good"
Else
sMsg = "This document has no internal links"
End if
MsgBox (sMsg, 64, "Find broken internal link")
End Sub
EDIT: I have also asked on Stack Exchange: http://stackoverflow.com/q/37611030/6418653