Finding cross-references in LibreOffice Writer

I’m in the middle of reviewing a ~13,000 word paper written in Microsoft Word. One of the things I needed to do was keep track of when a figure or table appeared, and had no reference in the paragraphs. This could be done with pen and paper, but I wanted to do it digitally.

LibreOffice Writer’s default search module cannot find this kind of cross-reference.

LibreOffice Writer has no functionality to expose “this field has no references”.

The Alternative Search and Replace extension for LibreOffice Writer cannot find this kind of cross-reference either. It can do a lot of things, but not this.

I do not know anything about programming in UNO – LibreOffice’s BASIC macro language. ChatGPT does though, so long as it’s put in High mode and given the instruction to validate the work before providing code. The following, placed as a macro in the Standard library, will do a search when run is executed. Searches like “9”, or “Figure 9”, or “Appendix 4” will find the text in the paragraphs matching that string, but only if it’s an internal cross-reference.

This made it easy to confirm that something like Figure 35 in Appendix 6 was actually referenced somewhere in the body of the paper, and was done as a field cross-reference (which means updating field references in the document would flow any changes).

Option Explicit


Sub FindCrossReferences

    Dim oDoc As Variant
    Dim oEnum As Variant
    Dim oField As Variant

    Dim sFilter As String
    Dim sSourceType As String
    Dim sDetails As String
    Dim sHaystack As String

    Dim nCount As Long
    Dim nFound As Long
    Dim ans As Integer


    oDoc = ThisComponent

    If Not oDoc.supportsService( _
        "com.sun.star.text.TextDocument") Then
        MsgBox "This is not a Writer document.", _
               48, "Find Cross-References"
        Exit Sub
    End If

    sFilter = InputBox( _
        "Optional filter." & Chr(13) & Chr(13) & _
        "Matches target name or displayed text." & Chr(13) & _
        "Leave blank to find every cross-reference.", _
        "Find Cross-References", _
        "")

    ' ============================================================
    ' PASS 1: count matching GetReference fields
    ' ============================================================

    nCount = 0
    oEnum = oDoc.TextFields.createEnumeration()

    Do While oEnum.hasMoreElements()
        oField = oEnum.nextElement()
        If oField.supportsService( _
            "com.sun.star.text.textfield.GetReference") Then
            sHaystack = _
                CStr(oField.SourceName) & Chr(10) & _
                CStr(oField.CurrentPresentation)
            If sFilter = "" Or _
               InStr(1, sHaystack, sFilter, 1) > 0 Then
                nCount = nCount + 1
            End If
        End If
    Loop


    If nCount = 0 Then
        If sFilter = "" Then
            MsgBox _
                "No cross-reference fields found.", _
                64, _
                "Find Cross-References"
        Else
            MsgBox _
                "No cross-reference fields matching """ & _
                sFilter & """ found.", _
                64, _
                "Find Cross-References"
        End If
        Exit Sub
    End If

    ans = MsgBox( _
        nCount & " cross-reference field(s) found." & _
        Chr(13) & Chr(13) & _
        "Step through them?", _
        36, _
        "Find Cross-References")

    If ans <> 6 Then Exit Sub

    ' ============================================================
    ' PASS 2: step through matching references
    ' ============================================================

    oEnum = oDoc.TextFields.createEnumeration()
    nFound = 0

    Do While oEnum.hasMoreElements()
        oField = oEnum.nextElement()
        If oField.supportsService( _
            "com.sun.star.text.textfield.GetReference") Then
            sHaystack = _
                CStr(oField.SourceName) & Chr(10) & _
                CStr(oField.CurrentPresentation)
            If sFilter = "" Or _
               InStr(1, sHaystack, sFilter, 1) > 0 Then
                nFound = nFound + 1

                ' ------------------------------------------------
                ' Determine source type
                ' ------------------------------------------------

                Select Case oField.ReferenceFieldSource
                    Case 0
                        sSourceType = "Reference mark"
                    Case 1
                        sSourceType = "Sequence field"
                    Case 2
                        sSourceType = "Bookmark"
                    Case 3
                        sSourceType = "Footnote"
                    Case 4
                        sSourceType = "Endnote"
                    Case 5
                        sSourceType = "Style"
                    Case Else
                        sSourceType = _
                            "Unknown (" & _
                            oField.ReferenceFieldSource & ")"
                End Select

                ' ------------------------------------------------
                ' Jump to the CROSS-REFERENCE occurrence itself.
                '
                ' This is the text in the prose displaying e.g.
                ' "Figure 8", not the target caption/bookmark.
                ' ------------------------------------------------

                GotoCrossReference oDoc, oField

                ' ------------------------------------------------
                ' Show details
                ' ------------------------------------------------

                sDetails = _
                    "Reference " & nFound & _
                    " of " & nCount & Chr(13) & Chr(13) & _
                    "Displayed as: """ & _
                    oField.CurrentPresentation & """" & _
                    Chr(13) & _
                    "Target: " & _
                    oField.SourceName & Chr(13) & _
                    "Source type: " & _
                    sSourceType & Chr(13) & _
                    "Source value: " & _
                    oField.ReferenceFieldSource & Chr(13) & _
                    "Reference part: " & _
                    oField.ReferenceFieldPart & Chr(13) & _
                    "Sequence/ID: " & _
                    oField.SequenceNumber & _
                    Chr(13) & Chr(13) & _
                    "Yes = next reference" & Chr(13) & _
                    "No = stop"

                ans = MsgBox( _
                    sDetails, _
                    36, _
                    "Find Cross-References")
                If ans <> 6 Then Exit Sub
            End If
        End If
    Loop
End Sub



Sub GotoCrossReference( _
    oDoc As Variant, _
    oField As Variant)

    Dim oViewCursor As Variant
    Dim oAnchor As Variant
    Dim nLen As Long

    oViewCursor = _
        oDoc.CurrentController.getViewCursor()
    oAnchor = oField.Anchor

    ' Jump to where the cross-reference field occurs
    ' in the document.
    oViewCursor.gotoRange(oAnchor, False)

    ' Try to make the location visually obvious by
    ' selecting the displayed xref text.
    nLen = Len(CStr(oField.CurrentPresentation))

    If nLen > 0 Then
        oViewCursor.goRight(nLen, True)
    End If
End Sub