如何在Star Basic中检查内部链接是否损坏?

Júl*_*eis 5 hyperlink openoffice-basic libreoffice-basic libreoffice-writer

我正在为LibreOffice Writer创建一个Basic宏来检查内部链接是否损坏.简而言之:

  • 生成所有锚点的列表
  • 循环遍历文档,查找内部超链接
  • 如果内部超链接不在锚列表中,则打开它进行编辑(并停止)

我的代码有一些未解决的问题:

  1. (内fnBuildAnchorList)我们如何获得每个标题的编号?例如,如果第一个1级标题文本是"简介",则正确的锚是#1.Introduction|outline我们正在录制的Introduction|outline
  2. (内subInspectLink)我们如何正确测试标题的超链接?我注意到,当我手动跟踪指向标题的链接时,它会在编号相同时成功,但也会在文本相同时成功.
    例如,如果有内部链接#1.My first heading|outline,可以使用超链接到达,#1.Previous header name|outline 也可以使用超链接#2.3.5.My first heading|outline
  3. (内subInspectLink)我们如何打开特定的超链接进行编辑?我们传递参数.uno:EditHyperlink吗?我们移动光标吗?(我发现的所有动作都是相对的,例如.uno:GoRight)我们是否使用文本部分.Start.End属性?
REM  *****  BASIC  *****
Option Explicit


' PrintArray displays a MsgBox with the whole array
' for DEBUG purposes only
Sub subPrintArray(sTitle as String, theArray() as String)
    Dim sArray
    sArray = sTitle & ":" & Chr(13) & Join(theArray,Chr(13))
    MsgBox(sArray, 64, "***DEBUG")
End sub

' auxiliary sub for BuildAnchorList
Sub subAddItemToAnchorList (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 subAddArrayToAnchorList (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
        subAddItemToAnchorList (oAnchors, oNewAnchors(i), sType)
    Next
End Sub

Function fnBuildAnchorList()
    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`
                ' ***
                subAddItemToAnchorList (oAnchors, thisPara.String, "Heading")
            End if
        End if
    Loop
    ' text tables, text frames, images, objects, bookmarks and text sections
    subAddArrayToAnchorList(oAnchors, oDoc.getTextTables().ElementNames, "Table")
    subAddArrayToAnchorList(oAnchors, oDoc.getTextFrames().ElementNames, "Text Frame")
    subAddArrayToAnchorList(oAnchors, oDoc.getGraphicObjects().ElementNames, "Image")
    subAddArrayToAnchorList(oAnchors, oDoc.getEmbeddedObjects().ElementNames, "Object")
    subAddArrayToAnchorList(oAnchors, oDoc.Bookmarks.ElementNames, "Bookmark")
    subAddArrayToAnchorList(oAnchors, oDoc.getTextSections().ElementNames, "Section")

    fnBuildAnchorList = oAnchors
End Function

Function fnIsInArray( theString as String, theArray() as String )
    Dim i as Integer, iStart as Integer, iStop as Integer
    iStart = LBound(theArray)
    iStop = UBound(theArray)
    If iStart<=iStop then
        For i = iStart to iStop
            If theString = theArray(i) then
                fnIsInArray = True
                Exit function
            End if
        Next
    End if
    fnIsInArray = False
End function

Function fnIsOutlineInArray ( theString as String, theArray() as String )
    Dim i as Integer
    For i = LBound(theArray) to UBound(theArray)
        If theArray(i) = Right(theString,Len(theArray(i))) then
            fnIsOutlineInArray = True
            Exit function
        End if
    Next
    fnIsOutlineInArray = False
End function

' auxiliary function to FindBrokenInternalLinks
' inspects any links inside the current document fragment
' used to have an enumeration inside an enumeration, per OOo examples,
' but tables don't have .createEnumeration
Sub subInspectLinks( oAnchors as Object, oFragment as Object, iFragments as Integer, iLinks as Integer )
    Dim sMsg, sImplementation, thisPortion
    sImplementation = oFragment.implementationName
    Select Case sImplementation

        Case "SwXParagraph":
            ' paragraphs can be enumerated
            Dim oParaPortions, sLink, notFound
            oParaPortions = oFragment.createEnumeration
            ' go through all the text portions in current paragraph
            While oParaPortions.hasMoreElements
                thisPortion = oParaPortions.nextElement
                iFragments = iFragments + 1
                If Left(thisPortion.HyperLinkURL, 1) = "#" then
                    ' internal link found: get it all except initial # character
                    iLinks = iLinks + 1
                    sLink = right(thisPortion.HyperLinkURL, Len(thisPortion.HyperLinkURL)-1)
                    If Left(sLink,14) = "__RefHeading__" then
                        ' link inside a table of contents, no need to check
                        notFound = False
                    Elseif Right(sLink,8) = "|outline" then
                        ' special case for outline: since we don't know how to get the
                        ' outline numbering, we have to match the right most part of the
                        ' link only
                        notFound = not fnIsOutlineInArray(sLink, oAnchors)
                    Else
                        notFound = not fnIsInArray(sLink, oAnchors)
                    End if
                    If notFound then
                        ' anchor not found
                        ' *** DEBUG: code below up to MsgBox
                        sMsg = "Fragment #" & iFragments & ", internal link #" & iLinks & Chr(13) _
                            & "Bad link: [" & thisPortion.String & "] -> [" _
                            & thisPortion.HyperLinkURL & "] " & Chr(13) _
                            & "Paragraph:" & Chr(13) & oFragment.String & Chr(13) _
                            & "OK to continue, Cancel to stop"
                        Dim iChoice as Integer
                        iChoice = MsgBox (sMsg, 48+1, "Find broken internal link")
                        If iChoice = 2 Then End
                        ' ***
                        ' *** 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?
                        ' ***
                    End If
                End if
            Wend
            ' *** END paragraph

        Case "SwXTextTable":
            ' text tables have cells
            Dim i, eCells, thisCell, oCellPortions
            eCells = oFragment.getCellNames()
            For i = LBound(eCells) to UBound(eCells)
                thisCell = oFragment.getCellByName(eCells(i))
                oCellPortions = thisCell.createEnumeration
                    While oCellPortions.hasMoreElements
                        thisPortion = oCellPortions.nextElement
                        iFragments = iFragments + 1
                        ' a table cell may contain a paragraph or another table,
                        ' so call recursively
                        subInspectLinks (oAnchors, thisPortion, iFragments, iLinks)
                    Wend
'               xray thisPortion
                'SwXCell has .String
            Next
            ' *** END text table

        Case Else
            sMsg = "Implementation method '" & sImplementation & "' not covered by regular code." _
                & "OK to continue, Cancel to stop"
            If 2 = MsgBox(sMsg, 48+1) then End
            ' uses xray for element inspection; if not available, comment the two following lines
            BasicLibraries.loadLibrary("XrayTool")
            xray oFragment
            ' *** END unknown case

    End Select
End sub

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, oFragments as Object, thisFragment as Object
    Dim iFragments as Integer, iLinks as Integer, sMsg as String
    Dim oAnchors() as String ' list of all anchors in the document
'   Dim sMsg ' for MsgBox

    oDoc = ThisComponent

    ' get all document anchors
    oAnchors = fnBuildAnchorList()
'   subPrintArray("Anchor list", oAnchors) ' *** DEBUG ***
'   MsgBox( UBound(oAnchors)-LBound(oAnchors)+1 & " anchors found – stand by for checking")

    ' find links    
    iFragments = 0 ' fragment counter
    iLinks = 0     ' internal link counter
    oFragments = oDoc.Text.createEnumeration ' has all the paragraphs
    While oFragments.hasMoreElements
        thisFragment = oFragments.nextElement
        iFragments = iFragments + 1
        subInspectLinks (oAnchors, thisFragment, iFragments, iLinks)
    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

' *** END FindBrokenInternalLinks ***
Run Code Online (Sandbox Code Playgroud)

您可以使用带有标题的任何文档检查第一个问题 - 将弹出一个包含所有锚点的MsgBox,您将看到缺少的大纲编号.

第二个问题需要一个内部链接错误的文档.

Jim*_*m K 1

看看。您可以使用它\而不是创建宏,\也可以从代码中借用一些概念。

\n\n

测试链接(可能使用.uno:JumpToMark)似乎没有帮助,\n因为即使目标不存在,内部链接也总是会去某个地方。\n相反,请按照您的建议构建有效目标的列表。

\n\n

为了保存有效目标的列表,cOOol 代码使用 Python 集。\n如果您想使用 Basic,那么数据结构会受到更多限制。\n但是,可以通过声明一个新的\n Collection对象\n也可以通过使用基本数组,也许使用ReDim.

\n\n

另请查看 cOOol 代码如何定义有效的目标字符串。例如:

\n\n
internal_targets.add(\'0.\' * heading_level + data + \'|outline\')            \n
Run Code Online (Sandbox Code Playgroud)\n\n

要打开超链接对话框,请选择超链接文本,然后调用:

\n\n
dispatcher.executeDispatch(document, ".uno:EditHyperlink", "", 0, Array())\n
Run Code Online (Sandbox Code Playgroud)\n\n

编辑

\n\n

好的,我为此工作了几个小时并得出了以下代码:

\n\n
REM  *****  BASIC  *****\nOption Explicit\n\n\n\' PrintArray displays a MsgBox with the whole array\n\' for DEBUG purposes only\nSub subPrintArray(sTitle as String, theArray() as String)\n    Dim sArray\n    sArray = sTitle & ":" & Chr(13) & Join(theArray,Chr(13))\n    MsgBox(sArray, 64, "***DEBUG")\nEnd sub\n\n\' auxiliary sub for BuildAnchorList\nSub subAddItemToAnchorList (oAnchors() as String, sTheAnchor as String, sType as String)\n    Dim sAnchor\n    Select Case sType\n        Case "Heading":\n            sAnchor = sTheAnchor + "|outline"\n        Case "Table":\n            sAnchor = sTheAnchor + "|table"\n        Case "Text Frame":\n            sAnchor = sTheAnchor + "|frame"\n        Case "Image":\n            sAnchor = sTheAnchor + "|graphic"\n        Case "Object":\n            sAnchor = sTheAnchor + "|ole"\n        Case "Section":\n            sAnchor = sTheAnchor + "|region"\n        Case "Bookmark":\n            sAnchor = sTheAnchor\n    End Select\n    ReDim Preserve oAnchors(UBound(oAnchors)+1) as String\n    oAnchors(UBound(oAnchors)) = sAnchor\nEnd Sub\n\n\' auxiliary sub for BuildAnchorList\nSub subAddArrayToAnchorList (oAnchors() as String, oNewAnchors() as String, sType as String)\n    Dim i, iStart, iStop\n    iStart = LBound(oNewAnchors)\n    iStop = UBound(oNewAnchors)\n    If iStop < iStart then Exit Sub \' empty array, nothing to do\n    For i = iStart to iStop\n        subAddItemToAnchorList (oAnchors, oNewAnchors(i), sType)\n    Next\nEnd Sub\n\n\' Updates outlineLevels for the current level.\n\' Returns a string like "1.2.3"\nFunction fnGetOutlinePrefix(outlineLevel as Integer, outlineLevels() as Integer)\n    Dim level as Integer, prefix as String\n    outlineLevels(outlineLevel) = outlineLevels(outlineLevel) + 1\n    For level = outlineLevel + 1 to 9\n        \' Reset all lower levels.\n        outlineLevels(level) = 0\n    Next\n    prefix = ""\n    For level = 0 To outlineLevel\n        prefix = prefix & outlineLevels(level) & "."\n    Next\n    fnGetOutlinePrefix = prefix\nEnd Function\n\nFunction fnBuildAnchorList()\n    Dim oDoc as Object, oAnchors() as String, anchorName as String\n    Dim level as Integer, levelCounter as Integer\n    Dim outlineLevels(10) as Integer\n    For level = 0 to 9\n        outlineLevels(level) = 0\n    Next\n    oDoc = ThisComponent\n\n    \' get the whole document outline\n    Dim oParagraphs, thisPara, oTextPortions, thisPortion\n    oParagraphs = oDoc.Text.createEnumeration \' all the paragraphs\n    Do While oParagraphs.hasMoreElements\n        thisPara = oParagraphs.nextElement\n        If thisPara.ImplementationName = "SwXParagraph" then \' is a paragraph\n            If thisPara.OutlineLevel>0 Then \' is a heading\n                level = thisPara.OutlineLevel - 1\n                anchorName = fnGetOutlinePrefix(level, outlineLevels) & thisPara.String\n                subAddItemToAnchorList (oAnchors, anchorName, "Heading")\n            End if\n        End if\n    Loop\n    \' text tables, text frames, images, objects, bookmarks and text sections\n    subAddArrayToAnchorList(oAnchors, oDoc.getTextTables().ElementNames, "Table")\n    subAddArrayToAnchorList(oAnchors, oDoc.getTextFrames().ElementNames, "Text Frame")\n    subAddArrayToAnchorList(oAnchors, oDoc.getGraphicObjects().ElementNames, "Image")\n    subAddArrayToAnchorList(oAnchors, oDoc.getEmbeddedObjects().ElementNames, "Object")\n    subAddArrayToAnchorList(oAnchors, oDoc.Bookmarks.ElementNames, "Bookmark")\n    subAddArrayToAnchorList(oAnchors, oDoc.getTextSections().ElementNames, "Section")\n\n    fnBuildAnchorList = oAnchors\nEnd Function\n\nFunction fnIsInArray( theString as String, theArray() as String )\n    Dim i as Integer\n    For i = LBound(theArray()) To UBound(theArray())\n        If theString = theArray(i) Then\n            fnIsInArray = True\n            Exit function\n        End if\n    Next\n    fnIsInArray = False\nEnd function\n\n\' Open a _specific_ hyperlink for editing.\nSub subEditHyperlink(textRange as Object)\n    Dim document As Object\n    Dim dispatcher As Object\n    Dim oVC As Object\n\n    oVC = ThisComponent.getCurrentController().getViewCursor()\n    oVC.gotoRange(textRange.getStart(), False)\n    document = ThisComponent.CurrentController.Frame\n    dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")\n    dispatcher.executeDispatch(document, ".uno:EditHyperlink", "", 0, Array())\nEnd Sub\n\n\' auxiliary function to FindBrokenInternalLinks\n\' inspects any links inside the current document fragment\n\' used to have an enumeration inside an enumeration, per OOo examples,\n\' but tables don\'t have .createEnumeration\nSub subInspectLinks(oAnchors() as String, oFragment as Object, iFragments as Integer, iLinks as Integer, iBadLinks as Integer)\n    Dim sMsg, sImplementation, thisPortion\n    sImplementation = oFragment.implementationName\n    Select Case sImplementation\n\n        Case "SwXParagraph":\n            \' paragraphs can be enumerated\n            Dim oParaPortions, sLink, notFound\n            oParaPortions = oFragment.createEnumeration\n            \' go through all the text portions in current paragraph\n            While oParaPortions.hasMoreElements\n                thisPortion = oParaPortions.nextElement\n                iFragments = iFragments + 1\n                If Left(thisPortion.HyperLinkURL, 1) = "#" then\n                    \' internal link found: get it all except initial # character\n                    iLinks = iLinks + 1\n                    sLink = right(thisPortion.HyperLinkURL, Len(thisPortion.HyperLinkURL)-1)\n                    If Left(sLink,14) = "__RefHeading__" then\n                        \' link inside a table of contents, no need to check\n                        notFound = False\n                    Else\n                        notFound = not fnIsInArray(sLink, oAnchors)\n                    End if\n                    If notFound then\n                        \' anchor not found\n                        \' *** DEBUG: code below up to MsgBox\n                        iBadLinks = iBadLinks + 1\n                        sMsg = "Fragment #" & iFragments & ", internal link #" & iLinks & Chr(13) _\n                            & "Bad link: [" & thisPortion.String & "] -> [" _\n                            & thisPortion.HyperLinkURL & "] " & Chr(13) _\n                            & "Paragraph:" & Chr(13) & oFragment.String & Chr(13) _\n                            & "Yes to edit link, No to continue, Cancel to stop"\n                        Dim iChoice as Integer\n                        iChoice = MsgBox (sMsg, MB_YESNOCANCEL + MB_ICONEXCLAMATION, _\n                            "Find broken internal link")\n                        If iChoice = IDCANCEL Then\n                            End\n                        ElseIf iChoice = IDYES Then\n                            subEditHyperlink(thisPortion)\n                        End If\n                    End If\n                End if\n            Wend\n            \' *** END paragraph\n\n        Case "SwXTextTable":\n            \' text tables have cells\n            Dim i, eCells, thisCell, oCellPortions\n            eCells = oFragment.getCellNames()\n            For i = LBound(eCells) to UBound(eCells)\n                thisCell = oFragment.getCellByName(eCells(i))\n                oCellPortions = thisCell.createEnumeration\n                    While oCellPortions.hasMoreElements\n                        thisPortion = oCellPortions.nextElement\n                        iFragments = iFragments + 1\n                        \' a table cell may contain a paragraph or another table,\n                        \' so call recursively\n                        subInspectLinks (oAnchors, thisPortion, iFragments, iLinks)\n                    Wend\n\'               xray thisPortion\n                \'SwXCell has .String\n            Next\n            \' *** END text table\n\n        Case Else\n            sMsg = "Implementation method \'" & sImplementation & "\' not covered by regular code." _\n                & "OK to continue, Cancel to stop"\n            If 2 = MsgBox(sMsg, 48+1) then End\n            \' uses xray for element inspection; if not available, comment the two following lines\n            BasicLibraries.loadLibrary("XrayTool")\n            xray oFragment\n            \' *** END unknown case\n\n    End Select\nEnd sub\n\nSub FindBrokenInternalLinks\n    \' Find the next broken internal link\n    \'\n    \' Pseudocode:\n    \'\n    \' * generate link of anchors - *** TO DO: prefix the outline numbering\n    \' *  for headings loop, searching for internal links\n    \'     - is the internal link in the anchor list?\n    \'         * Yes: continue to next link\n    \'         * No: (broken link found)\n    \'             - select that link text - *** TO DO: cannot select it\n    \'             - open link editor so user can fix this\n    \'             - stop\n    \' * end loop\n    \' * display message "No bad internal links found"\n\n    Dim oDoc as Object, oFragments as Object, thisFragment as Object\n    Dim iFragments as Integer, iLinks as Integer, iBadLinks as Integer, sMsg as String\n    Dim oAnchors() as String \' list of all anchors in the document\n\n    oDoc = ThisComponent\n\n    \' get all document anchors\n    oAnchors = fnBuildAnchorList()\n\'   subPrintArray("Anchor list", oAnchors) \' *** DEBUG ***\n\'   MsgBox( UBound(oAnchors)-LBound(oAnchors)+1 & " anchors found \xe2\x80\x93 stand by for checking")\n\n    \' find links    \n    iFragments = 0 \' fragment counter\n    iLinks = 0     \' internal link counter\n    iBadLinks = 0\n    oFragments = oDoc.Text.createEnumeration \' has all the paragraphs\n    While oFragments.hasMoreElements\n        thisFragment = oFragments.nextElement\n        iFragments = iFragments + 1\n        subInspectLinks (oAnchors, thisFragment, iFragments, iLinks, iBadLinks)\n    Wend\n    If iBadLinks > 0 Then\n        sMsg = iBadLinks & " bad link(s), " & iLinks - iBadLinks & " good link(s)"\n    ElseIf iLinks Then\n        sMsg = iLinks & " internal link(s) found, all good"\n    Else\n        sMsg = "This document has no internal links"\n    End if\n    MsgBox (sMsg, 64, "Find broken internal link")\n\nEnd Sub\n\n\' *** END FindBrokenInternalLinks ***\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在它检查大纲编号。也许它太严格了——也许有一个关闭大纲数字检查的选项会很好。

\n\n

就问题 3 而言,此代码现在打开正确的编辑链接(只要在消息框中单击“是”)。

\n