Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

Preventing / finding broken internal links to chapter headings

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:

  1. How may I prevent breaking more links? Is there some behaviour I should be avoiding?
  2. How may I find broken internal links? An extension, a script in any language… please! Alex Kemp is onto something – https://ask.libreoffice.org/en/question/51794/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.

Any help greatly appreciated – even if it’s to say that there’s no such thing, or that this bug has been reported. Thank you!

Preventing / finding broken internal links to chapter headings

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:

  1. How may I prevent breaking more links? Is there some behaviour I should be avoiding?
  2. How may I find broken internal links? An extension, a script in any language… please! Alex Kemp is onto something – https://ask.libreoffice.org/en/question/51794/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.

Any help greatly appreciated – even if it’s to say that there’s no such thing, or that this bug has been reported. Thank you!

EDIT: I started writing a macro. I can find all of the internal links, but I can't test them yet. Help, please:

Sub FindBrokenInternalLinks
    ' Find the next broken internal link
    '
    ' Pseudocode:
    '
    ' * access the current Write document
    ' * loop, searching for internal links
    '     - test the internal link: is it working?
    '         * Yes: continue to next link
    '         * No: (broken link found)
    '             - select that link text
    '             - open link editor so user can fix this
    '             - stop
    ' * end loop
    ' * display message "No bad internal links found"
    '
    ' Notes:
    ' * "test the internal link" cound be a case of building a list of Headings and
    '   Bookmarks for the current document, and comparing each internal URL to it
    ' * com.sun.star.text.Bookmarks is a list of bookmarks - how to use this?
    ' * the current link seach works, but looks brute-force - is there better?
    '   is there a collection of Hyperlink elements we can access?
    ' * in the loop, thisPortion.Start and thisPortion.End could _probably_ be used
    '   to create a text selection, to then open the link editor with
    '   .uno:HyperlinkDialog

    oDoc = ThisComponent
    enum1 = oDoc.Text.createEnumeration
    iLinkCounter = 0 ' internal link counter

    ' go through all the elements
    While enum1.hasMoreElements
        thisPara = enum1.nextElement
        enum2 = thisPara.createEnumeration
        While enum2.hasMoreElements
            thisPortion = enum2.nextElement
            If left(thisPortion.HyperLinkURL, 1) = "#" then
                ' internal link found
                iLinkCounter = iLinkCounter + 1
                sMsg = "String: [" + thisPortion.String + "] URL: [" _
                    + thisPortion.HyperLinkURL + "]"
                iChoice = MsgBox (sMsg, 1, "Find broken internal link")
                If iChoice = 2 then
                    sMsg = "You pressed Cancel. Links found: " + iLinkCounter
                    MsgBox (sMsg, 0, "Find broken internal link")
                    Exit sub
                End If
            End if
        Wend
    Wend
    sMsg = "Links found: " + iLinkCounter
    MsgBox (sMsg, 0, "Find broken internal link")
End Sub

Preventing / finding broken internal links to chapter headings

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:

  1. How may I prevent breaking more links? Is there some behaviour I should be avoiding?
  2. How may I find broken internal links? An extension, a script in any language… please! Alex Kemp is onto something – https://ask.libreoffice.org/en/question/51794/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.

Any help greatly appreciated – even if it’s to say that there’s no such thing, or that this bug has been reported. Thank you!

EDIT: I started writing a macro. I can find all of the internal links, but I can't test them yet. Help, please:

Sub FindBrokenInternalLinks
    ' Find the next broken internal link
    '
    ' Pseudocode:
    '
    ' * access the current Write document
    ' * loop, searching for internal links
    '     - test the internal link: is it working?
    '         * Yes: continue to next link
    '         * No: (broken link found)
    '             - select that link text
    '             - open link editor so user can fix this
    '             - stop
    ' * end loop
    ' * display message "No bad internal links found"
    '
    ' Notes:
    ' * "test the internal link" cound be a case of building a list of Headings and
    '   Bookmarks for the current document, and comparing each internal URL to it
    ' * com.sun.star.text.Bookmarks is a list of bookmarks - how to use this?
    ' * the current link seach works, but looks brute-force - is there better?
    '   is there a collection of Hyperlink elements we can access?
    ' * in the loop, thisPortion.Start and thisPortion.End could _probably_ be used
    '   to create a text selection, to then open the link editor with
    '   .uno:HyperlinkDialog

    oDoc = ThisComponent
    enum1 = oDoc.Text.createEnumeration
    iLinkCounter = 0 ' internal link counter

    ' go through all the elements
    While enum1.hasMoreElements
        thisPara = enum1.nextElement
        enum2 = thisPara.createEnumeration
        While enum2.hasMoreElements
            thisPortion = enum2.nextElement
            If left(thisPortion.HyperLinkURL, 1) = "#" then
                ' internal link found
                iLinkCounter = iLinkCounter + 1
                sMsg = "String: [" + thisPortion.String + "] URL: [" _
                    + thisPortion.HyperLinkURL + "]"
                iChoice = MsgBox (sMsg, 1, "Find broken internal link")
                If iChoice = 2 then
                    sMsg = "You pressed Cancel. Links found: " + iLinkCounter
                    MsgBox (sMsg, 0, "Find broken internal link")
                    Exit sub
                End If
            End if
        Wend
    Wend
    sMsg = "Links found: " + iLinkCounter
    MsgBox (sMsg, 0, "Find broken internal link")
End Sub

EDIT: I have also asked on Stack Exchange: http://stackoverflow.com/q/37611030/6418653

Preventing / finding broken internal links to chapter headings

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:

  1. How may I prevent breaking more links? Is there some behaviour I should be avoiding?
  2. How may I find broken internal links? An extension, a script in any language… please! Alex Kemp is onto something – https://ask.libreoffice.org/en/question/51794/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.

Any help greatly appreciated – even if it’s to say that there’s no such thing, or that this bug has been reported. Thank you!

EDIT: I started writing a macro. When generating the list of anchors, I can find all of the internal links, but need to access the outline numbering for headings. And I can't test them yet. Help, please: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:
    '
    ' * access the current Write document
generate link of anchors - *** TO DO: prefix the outline numbering for headings
    ' * loop, searching for internal links
    '     - test is the internal link: is it working?
link in the anchor list?
    '         * Yes: continue to next link
    '         * No: (broken link found)
    '             - select that link text
text - *** TO DO: cannot select it
    '             - open link editor so user can fix this
    '             - stop
    ' * end loop
    ' * display message "No bad internal links found"
    '
    ' Notes:
    ' * "test the internal link" cound be a case of building a 
    Dim oDoc as Object, oFrame as Object, oDispatcher as Object
    Dim oAnchors() as String ' list of Headings and
    '   Bookmarks all anchors in the document
    Dim oParagraphs, thisPara, oTextPortions, thisPortion ' for the current document, and comparing each internal URL to it
    ' * com.sun.star.text.Bookmarks is a list of bookmarks - how to use this?
    ' * the current link seach works, but looks brute-force - is there better?
    '   is there a collection of Hyperlink elements we can access?
    ' * in the loop, thisPortion.Start and thisPortion.End could _probably_ be used
    '   to create a text selection, to then open the link editor with
    '   .uno:HyperlinkDialog
interating through doc
    Dim sMsg ' for MsgBox

    oDoc = ThisComponent
    enum1 = oDoc.Text.createEnumeration
    iLinkCounter 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 elements
paragraphs
    While enum1.hasMoreElements
oParagraphs.hasMoreElements
        thisPara = enum1.nextElement
        enum2 oParagraphs.nextElement
        oTextPortions = thisPara.createEnumeration
        ' go through all the text portions in current paragraph
        While enum2.hasMoreElements
oTextPortions.hasMoreElements
            thisPortion = enum2.nextElement
oTextPortions.nextElement
            If left(thisPortion.HyperLinkURL, 1) = "#" then
                ' internal link found
                iLinkCounter = iLinkCounter 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 = "String: "Bad link: [" + & thisPortion.String + & "] URL: -> [" _
                    +     & thisPortion.HyperLinkURL + "]"
                iChoice = & "] XTextRange interface? " & sHas
                    MsgBox (sMsg, 1, 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 iChoice = 2 iLinks then
         sMsg = "You pressed Cancel. Links found: iLinks & " + iLinkCounter
                internal links found, all good"
    Else
        sMsg = "This document has no internal links"
    End if
    MsgBox (sMsg, 0, "Find broken internal link")
                    Exit sub
                End If
            End if
        Wend
    Wend
    sMsg = "Links found: " + iLinkCounter
    MsgBox (sMsg, 0, 64, "Find broken internal link")
End Sub

EDIT: I have also asked on Stack Exchange: http://stackoverflow.com/q/37611030/6418653