2

As per google group, this macro can be used to print mis-spelled words in MS office.

https://groups.google.com/g/microsoft.public.word.spelling.grammar/c/OiFYPkLAbeU

Is there similar option in libre-office writer?

shantanuo
  • 31,689
  • 78
  • 245
  • 403

7 Answers7

1

The following Subroutine replicates what the code in the Google group does. It is more verbose than the MS version but that is to be expected with LibreOffice / OpenOffice. It only does the spellchecker lines and not the green grammar checker ones, which is also the case with the MS version in the Google group.

Sub UnderlineMisspelledWords

    ' From OOME Listing 315 Page 336
    GlobalScope.BasicLibraries.loadLibrary( "Tools" )
    Dim sLocale As String
    sLocale = GetRegistryKeyContent("org.openoffice.Setup/L10N", FALSE).getByName("ooLocale")

    ' ooLocale appears to return a string that consists of the language and country
    ' seperated by a dash, e.g. en-GB
    Dim nDash As Integer
    nDash = InStr(sLocale, "-")

    Dim aLocale As New com.sun.star.lang.Locale
    aLocale.Language = Left(sLocale, nDash - 1)
    aLocale.Country = Right(sLocale, Len(sLocale) -nDash )

    Dim oSpeller As Variant
    oSpeller = createUnoService("com.sun.star.linguistic2.SpellChecker")

    Dim emptyArgs() as new com.sun.star.beans.PropertyValue

    Dim oCursor As Object
    oCursor = ThisComponent.getText.createTextCursor()
    oCursor.gotoStart(False)
    oCursor.collapseToStart()

    Dim s as String, bTest As Boolean
    Do 
        oCursor.gotoEndOfWord(True)
        s = oCursor.getString()
        bTest = oSpeller.isValid(s, aLocale, emptyArgs())

        If Not bTest Then    
            With oCursor
                .CharUnderlineHasColor = True
                .CharUnderlineColor = RGB(255, 0,0)
                .CharUnderline = com.sun.star.awt.FontUnderline.WAVE
                ' Possible alternatives include SMALLWAVE, DOUBLEWAVE and BOLDWAVE
            End With
        End If    
    Loop While oCursor.gotoNextWord(False)

End Sub    

This will change the actual formatting of the font to have a red wavy underline, which will print out like any other formatting. If any of the misspelled words in the document already have some sort of underlining then that will be lost.

You will probably want to remove the underlining after you have printed it. The following Sub removes underlining only where its style exactly matches that of the line added by the first routine.

Sub RemoveUnderlining

    Dim oCursor As Object
    oCursor = ThisComponent.getText.createTextCursor()
    oCursor.gotoStart(False)
    oCursor.collapseToStart()

    Dim s as String, bTest As Boolean
    Do 
    
        oCursor.gotoEndOfWord(True) 
        
        Dim bTest1 As Boolean        
        bTest1 = False
        If oCursor.CharUnderlineHasColor = True Then
            bTest1 = True
        End If
        
        Dim bTest2 As Boolean  
        bTest2 = False
        If oCursor.CharUnderlineColor = RGB(255, 0,0) Then
            bTest2 = True
        End If
        
        Dim bTest3 As Boolean  
        bTest3 = False
        If oCursor.CharUnderline = com.sun.star.awt.FontUnderline.WAVE Then
            bTest3 = True
        End If
        
        If bTest1 And bTest2 And bTest3 Then
            With oCursor
                .CharUnderlineHasColor = False
                .CharUnderline = com.sun.star.awt.FontUnderline.NONE
            End With
        End If
    Loop While oCursor.gotoNextWord(False)

End Sub

This will not restore any original underlining that was replaced by red wavy ones. Other ways of removing the wavy lines that would restore these are:

  1. Pressing undo (Ctrl Z) but you will need to do that once for every word in your document, which could be a bit of a pain.

  2. Running the subroutine UnderlineMisspelledWords on a temporary copy of the document and then discarding it after printing.

I hope this is what you were looking for.

Howard Rudd
  • 901
  • 4
  • 6
  • Please take a look at the bug here. https://bugs.documentfoundation.org/show_bug.cgi?id=97595 Is it possible to write a macro for that? – shantanuo Jun 22 '21 at 07:14
1

In response to your above comment, it is straightforward to modify the above subroutine to do that instead of drawing wavy lines. The code below opens a new Writer document and writes into it a list of the misspelled words together with the alternatives that the spellchecker suggests:

Sub ListMisSpelledWords

    ' From OOME Listing 315 Page 336
    GlobalScope.BasicLibraries.loadLibrary( "Tools" )
    Dim sLocale As String
    sLocale = GetRegistryKeyContent("org.openoffice.Setup/L10N", FALSE).getByName("ooLocale")

    ' ooLocale appears to return a string that consists of the language and country
    ' seperated by a dash, e.g. en-GB
    Dim nDash As Integer
    nDash = InStr(sLocale, "-")

    Dim aLocale As New com.sun.star.lang.Locale
    aLocale.Language = Left(sLocale, nDash - 1)
    aLocale.Country = Right(sLocale, Len(sLocale) -nDash )

    Dim oSource As Object 
    oSource = ThisComponent

    Dim oSourceCursor As Object
    oSourceCursor = oSource.getText.createTextCursor()
    oSourceCursor.gotoStart(False)
    oSourceCursor.collapseToStart()

    Dim oDestination As Object
    oDestination = StarDesktop.loadComponentFromURL( "private:factory/swriter",  "_blank", 0, Array() )

    Dim oDestinationText as Object
    oDestinationText = oDestination.getText()

    Dim oDestinationCursor As Object
    oDestinationCursor = oDestinationText.createTextCursor()

    Dim oSpeller As Object
    oSpeller = createUnoService("com.sun.star.linguistic2.SpellChecker")

    Dim oSpellAlternatives As Object, emptyArgs() as new com.sun.star.beans.PropertyValue
    Dim sMistake as String, oSpell As Object, sAlternatives() as String, bTest As Boolean, s As String, i as Integer

    Do

        oSourceCursor.gotoEndOfWord(True)
        sMistake = oSourceCursor.getString()

        bTest = oSpeller.isValid(sMistake, aLocale, emptyArgs())

        If Not bTest Then
            oSpell = oSpeller.spell(sMistake, aLocale, emptyArgs())
            sAlternatives = oSpell.getAlternatives()
            s = ""
            for i = LBound(sAlternatives) To Ubound(sAlternatives) - 1
                s = s & sAlternatives(i) & ", "
            Next i
            s = s & sAlternatives(Ubound(sAlternatives))
            oDestinationText.insertString(oDestinationCursor, sMistake & ":  " & s & Chr(13), False)
        End If    

    Loop While oSourceCursor.gotoNextWord(False)

End Sub
Howard Rudd
  • 901
  • 4
  • 6
  • This works for English text only. For e.g. for the word "testtt", I get expected results, "testtt: testes, testate, attest, wettest" But if I type devnagari unicode text, then I get an error. "Inadmissible value or data type. Index out of defined range." at line 54. The second problem is that this is not what the OP has requested. The user has checked the spellings using track changes and we need to generate a list of wrong word >> corrected word preferably using XML file. For e.g. in the above case, I may correct "testtt", to "tested" instead of selecting one of the choices. – shantanuo Jun 24 '21 at 07:28
  • According to Pitonyak (https://www.pitonyak.org/book/) page 147 "OOo stores characters as 16-bit unsigned integer Unicode values." So it should be able cope with Unicode. If it doesn't then I don't know what the problem is. You could try setting the locale manually in the macro. – Howard Rudd Jun 25 '21 at 05:41
  • The above routines run the spell checker independently of the "as you type" checking done by the Writer application itself, so they don't have access to the alternatives the user selects when they right-click on the wavy lines. I can't find anywhere in the API documentation how to access those. Maybe I've missed something and someone out there knows how to do it? – Howard Rudd Jun 25 '21 at 05:42
  • You could achieve your objective by creating your own dialog where the user selects the correction from a drop down list. Then you would be able to store all the strings you want in whatever form you like, including the original incorrectly spelled word, all the spell checker's suggestions and the word the user selects. You could even allow the user to type a word that isn't in the list of suggestions. By the way, what does "OP" stand for? – Howard Rudd Jun 25 '21 at 05:42
  • 1) I changed 2 lines to aLocale.Language = "mr" and aLocale.Country = "IN". That solved the unicode related problem. 2) OP means the original poster (in this context the bug submitter at document foundation). 3) If I understand your comment correctly, it is not possible to extract tracked changes from within Libre office using a macro. I do not want to build a new application on top of writer because that will be too complicated to write and maintain by the end-users. The expected users of this functionality are not tech - savvy. – shantanuo Jun 25 '21 at 06:30
  • I'm not 100% certain that the results of the automatic spell checker can't be accessed. It's just that I haven't been able to find out how to do it. The LibreOffice / OpenOffice API is arcane and convoluted and the answer may be buried in there somewhere. If anyone out there knows definitively I'd be interested to know. – Howard Rudd Jun 25 '21 at 08:19
  • Once I get the results of misspelled words and their corrections, I copy-paste them in Linux and use sort -u command to get distinct records. Is it possible to achieve this using another (or the same) macro? – shantanuo Jun 25 '21 at 09:16
  • What do you mean by 'distinct records'? – Howard Rudd Jun 27 '21 at 11:36
  • distinct records mean unique (as denoted by -u parameter of sort command). The records those are not repeated. If the corpus has misspelled word repeated, then the script will show the same word twice. I need to avoid that. – shantanuo Jun 29 '21 at 04:30
  • Please take a look at similar question ... https://stackoverflow.com/questions/68224360/go-to-end-of-word-is-not-always-followed – shantanuo Jul 02 '21 at 11:45
1

I don't know about the dictionaries but, in answer to your previous comment, if you paste the following code below Loop While and above End Sub it will result in the text in the newly opened Writer document being sorted without duplicates. It's not very elegant but it works on the text I've tried it on.

oDestinationCursor.gotoStart(False)
oDestinationCursor.gotoEnd(True)

Dim oSortDescriptor As Object
oSortDescriptor = oDestinationCursor.createSortDescriptor()
oDestinationCursor.sort(oSortDescriptor)

Dim sParagraphToBeChecked As String
Dim sThisWord As String
sThisWord = ""
Dim sPreviousWord As String
sPreviousWord = ""

oDestinationCursor.gotoStart(False)
oDestinationCursor.collapseToStart()

Dim k As Integer
Do
    oDestinationCursor.gotoEndOfParagraph(True)
    sParagraphToBeChecked = oDestinationCursor.getString()
    k = InStr(sParagraphToBeChecked, ":")
    If k <> 0 Then
        sThisWord = Left(sParagraphToBeChecked, k-1)
    End If
    If StrComp(sThisWord, sPreviousWord, 0) = 0 Then
        oDestinationCursor.setString("")
    End If
    sPreviousWord = sThisWord
Loop While oDestinationCursor.gotoNextParagraph(False)

Dim oReplaceDescriptor As Object
oReplaceDescriptor =  oDestination.createReplaceDescriptor()
oReplaceDescriptor.setPropertyValue("SearchRegularExpression", TRUE)
oReplaceDescriptor.setSearchString("^$")
oReplaceDescriptor.setReplaceString("")
oDestination.replaceAll(oReplaceDescriptor)
Howard Rudd
  • 901
  • 4
  • 6
  • This is correct. However, if I have more than 1 language (for e.g. English and Marathi), I get an error. In python I could have used Try, Except - Pass block. Please add similar code. – shantanuo Jul 01 '21 at 04:46
  • In other words, if there are no suggestions, (for e.g. a word having a lot of characters) it will trigger an error on line number 54. I need to suppress the error and just show the wrong word without any suggestions. – shantanuo Jul 01 '21 at 05:18
1

It seems I didn't spot that because the text I tested it on contained only words that were either correct or had more than zero alternatives. I managed to replicate the error by putting in a word consisting of random characters for which the spellchecker was unable to suggest any alternatives. If no alternatives are found the function .getAlternatives() returns an array of size -1 so the error can be avoided by testing for this condition before the array is used. Below is a modified version of the first Do loop in the subroutine with such a condition added. If you replace the existing loop with that it should eliminate the error.

Do

    oSourceCursor.gotoEndOfWord(True)
    sMistake = oSourceCursor.getString()

    bTest = oSpeller.isValid(sMistake, aLocale, emptyArgs())

    If Not bTest Then
        oSpell = oSpeller.spell(sMistake, aLocale, emptyArgs())
        sAlternatives = oSpell.getAlternatives()
        s = ""
        If Ubound(sAlternatives) >= 0 Then
            for i = LBound(sAlternatives) To Ubound(sAlternatives) - 1
                s = s & sAlternatives(i) & ", "
            Next i
            s = s & sAlternatives(Ubound(sAlternatives))
        End If            
        oDestinationText.insertString(oDestinationCursor, sMistake & ":  " & s & Chr(13), False)
    End If    

Loop While oSourceCursor.gotoNextWord(False)

On re-reading the whole subroutine I think it would improve its readability if the variable sMistake were renamed to something like sWordToBeChecked, as the string this variable contains isn't always misspelled. This would of course need to be changed everywhere in the routine and not just in the above snippet.

Howard Rudd
  • 901
  • 4
  • 6
1

Below is a modified version that uses the dispatcher as suggested by Jim K in his answer go to end of word is not always followed. I have written it out in its entirety because the changes are more extensive than just adding or replacing a block. In particular, it is necessary to get the view cursor before creating the empty destination document, otherwise the routine will spell check that.

Sub ListMisSpelledWords2

    ' From OOME Listing 315 Page 336
    GlobalScope.BasicLibraries.loadLibrary( "Tools" )
    Dim sLocale As String
    sLocale = GetRegistryKeyContent("org.openoffice.Setup/L10N", FALSE).getByName("ooLocale")

    ' ooLocale appears to return a string that consists of the language and country
    ' seperated by a dash, e.g. en-GB
    Dim nDash As Integer
    nDash = InStr(sLocale, "-")

    Dim aLocale As New com.sun.star.lang.Locale
    aLocale.Language = Left(sLocale, nDash - 1)
    aLocale.Country = Right(sLocale, Len(sLocale) -nDash )

    Dim oSourceDocument As Object 
    oSourceDocument = ThisComponent

    Dim nWordCount as Integer
    nWordCount = oSourceDocument.WordCount    

    Dim oFrame  As Object, oViewCursor As Object
    With oSourceDocument.getCurrentController
        oFrame = .getFrame()
        oViewCursor = .getViewCursor()
    End With

    Dim oDispatcher as Object
    oDispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
    oDispatcher.executeDispatch(oFrame, ".uno:GoToStartOfDoc", "", 0, Array()) 

    Dim oDestinationDocument As Object
    oDestinationDocument = StarDesktop.loadComponentFromURL( "private:factory/swriter",  "_blank", 0, Array() )

    Dim oDestinationText as Object
    oDestinationText = oDestinationDocument.getText()

    Dim oDestinationCursor As Object
    oDestinationCursor = oDestinationText.createTextCursor()

    Dim oSpeller As Object
    oSpeller = createUnoService("com.sun.star.linguistic2.SpellChecker")

    Dim oSpellAlternatives As Object, emptyArgs() as new com.sun.star.beans.PropertyValue
    Dim sMistake as String, oSpell As Object, sAlternatives() as String, bTest As Boolean, s As String, i as Integer

    For i = 0 To nWordCount - 1

        oDispatcher.executeDispatch(oFrame, ".uno:WordRightSel", "", 0, Array())
        sWordToBeChecked = RTrim( oViewCursor.String )

        bTest = oSpeller.isValid(sWordToBeChecked, aLocale, emptyArgs())

        If Not bTest Then
            oSpell = oSpeller.spell(sWordToBeChecked, aLocale, emptyArgs())
            sAlternatives = oSpell.getAlternatives()
            s = ""
            If Ubound(sAlternatives) >= 0 Then
                for i = LBound(sAlternatives) To Ubound(sAlternatives) - 1
                    s = s & sAlternatives(i) & ", "
                Next i
                s = s & sAlternatives(Ubound(sAlternatives))
            End If            
            oDestinationText.insertString(oDestinationCursor, sWordToBeChecked & ":  " & s & Chr(13), False)
        End If

        oDispatcher.executeDispatch(oFrame, ".uno:GoToPrevWord", "", 0, Array())
        oDispatcher.executeDispatch(oFrame, ".uno:GoToNextWord", "", 0, Array())

    Next i

    oDestinationCursor.gotoStart(False)
    oDestinationCursor.gotoEnd(True)

    ' Sort the paragraphs
    Dim oSortDescriptor As Object
    oSortDescriptor = oDestinationCursor.createSortDescriptor()
    oDestinationCursor.sort(oSortDescriptor)

    ' Remove duplicates
    Dim sParagraphToBeChecked As String, sThisWord As String, sPreviousWord As String
    sThisWord = ""
    sPreviousWord = ""

    oDestinationCursor.gotoStart(False)
    oDestinationCursor.collapseToStart()

    Dim k As Integer
    Do
        oDestinationCursor.gotoEndOfParagraph(True)
        sParagraphToBeChecked = oDestinationCursor.getString()
        k = InStr(sParagraphToBeChecked, ":")
        If k <> 0 Then
            sThisWord = Left(sParagraphToBeChecked, k-1)
        End If
        If StrComp(sThisWord, sPreviousWord, 0) = 0 Then
            oDestinationCursor.setString("")
        End If
        sPreviousWord = sThisWord
    Loop While oDestinationCursor.gotoNextParagraph(False)

    ' Remove empty paragraphs
    Dim oReplaceDescriptor As Object
    oReplaceDescriptor =  oDestinationDocument.createReplaceDescriptor()
    oReplaceDescriptor.setPropertyValue("SearchRegularExpression", TRUE)
    oReplaceDescriptor.setSearchString("^$")
    oReplaceDescriptor.setReplaceString("")
    oDestinationDocument.replaceAll(oReplaceDescriptor)

End Sub
Howard Rudd
  • 901
  • 4
  • 6
  • That is correct. I have paid bounty of 200 points. Can you take a look at this bug? https://bugs.documentfoundation.org/show_bug.cgi?id=142437 – shantanuo Jul 05 '21 at 02:11
  • And if I am not asking for too much, is it possible to get synonyms like wood: forest, vegetation, flora, botany – shantanuo Jul 06 '21 at 05:29
1

It looks like the problem is caused by one of the while loops in the function TrimWord, which removes punctuation from before and after a word before it is fed to the spell-check service. If the word is only one character long and if it is a valid punctuation character then the condition at the beginning of the loop is true, so the loop is entered and the counter n is decremented to zero. Then at the beginning of the next traversal of the loop, even though the condition is false anyway, it still asks the Mid function to return the 0th character of the word, which it can't do because the characters are numbered from 1, so it throws an error. Some languages would ignore the error if the truth value of the condition could be unambiguously determined from the other parts of the expression. It looks like BASIC doesn't do that.

The following modified version of the function gets round the problem in a rather inelegant way, but it seems to work:

Function TrimWord(sWord As String) As String

    Dim n as Long
    n = Len(sWord)
    
    If n > 0 Then
    
        Dim m as Long :  m = 1
        Dim bTest As Boolean
        
        bTest = m <= n
        Do While IsPermissiblePrefix( ASC(Mid(sWord, m, 1) ) ) And bTest
            if (m < n) Then
                m = m + 1
            Else
                bTest = False
            End If
        Loop       
        
        bTest = n > 0
        Do While IsPermissibleSuffix( ASC(Mid(sWord, n, 1) ) ) And bTest
            if (n > 1) Then
                n = n - 1 
            Else
                bTest = False
            End If         
        Loop
        
        If n > m Then
            TrimWord = Mid(sWord, m, (n + 1) - m)
        Else
            TrimWord = sWord
        End If
            
    Else
        TrimWord = ""
    End If

End Function

This works for me.

Howard Rudd
  • 901
  • 4
  • 6
0

Firstly, in response to your question about the bug, I'm not a maintainer so I can't fix that. However, as the bug concerns moving a text cursor to the start and end of a word it should be possible to get round it by searching for the white-space between words instead. Since the white-space characters are (I think) the same in all languages, any problems recognising certain characters from certain alphabets shouldn't matter. The easiest way to do it would be to first read the entire text of the document into a string but LibreOffice strings have a maximum length of 2^16 = 65536 characters and while this seems like a lot it could easily be too small for a reasonable sized document. The limit can be avoided by navigating through the text one paragraph at a time. According to Andrew Pitonyak (OOME Page 388): "I found gotoNextSentence() and gotoNextWord() to be unreliable, but the paragraph cursor worked well."

The code below is yet another modification of the subroutines in previous answers. This time it gets a string from a paragraph and splits it up into words by finding the white-space between the words. It then spell checks the words as before. The subroutine depends on some other functions that are listed below it. These allow you to specify which characters to designate as word separators (i.e. white-space) and which characters to ignore if they are found at the beginning or end of a word. This is necessary so that, for example, the quotes surrounding a quoted word are not counted as part of the word, which would lead to it being recognised as a spelling mistake even if the word inside the quotes is correctly spelled.

I am not familiar with non-latin alphabets and I don't have an appropriate dictionary installed, but I pasted the words from your question go to end of word is not always followed, namely testी, भारत and इंडिया and they all appeared unmodified in the output document.

On the question of looking up synonyms, as each misspelled word has multiple suggestions, and each of those will have multiple synonyms, the output could rapidly become very large and confusing. It may be better for your user to look them up individually if they want to use a different word.

Sub ListMisSpelledWords3

    ' From OOME Listing 315 Page 336
    GlobalScope.BasicLibraries.loadLibrary( "Tools" )
    Dim sLocale As String
    sLocale = GetRegistryKeyContent("org.openoffice.Setup/L10N", FALSE).getByName("ooLocale")

    ' ooLocale appears to return a string that consists of the language and country
    ' seperated by a dash, e.g. en-GB
    Dim nDash As Integer
    nDash = InStr(sLocale, "-")

    Dim aLocale As New com.sun.star.lang.Locale
    aLocale.Language = Left( sLocale, nDash - 1)
    aLocale.Country = Right( sLocale, Len(sLocale) - nDash )

    Dim oSource As Object 
    oSource = ThisComponent

    Dim oSourceCursor As Object
    oSourceCursor = oSource.getText.createTextCursor()
    oSourceCursor.gotoStart(False)
    oSourceCursor.collapseToStart()

    Dim oDestination As Object
    oDestination = StarDesktop.loadComponentFromURL( "private:factory/swriter",  "_blank", 0, Array() )

    Dim oDestinationText as Object
    oDestinationText = oDestination.getText()

    Dim oDestinationCursor As Object
    oDestinationCursor = oDestinationText.createTextCursor()

    Dim oSpeller As Object
    oSpeller = createUnoService("com.sun.star.linguistic2.SpellChecker")

    Dim oSpellAlternatives As Object, emptyArgs() as new com.sun.star.beans.PropertyValue
    Dim sWordToCheck as String, oSpell As Object, sAlternatives() as String, bTest As Boolean
    Dim s As String, i as Integer, j As Integer, sParagraph As String, nWordStart As Integer, nWordEnd As Integer
    Dim nChar As Integer

    Do

        oSourceCursor.gotoEndOfParagraph(True)

        sParagraph = oSourceCursor.getString() & " " 'It is necessary to add a space to the end of
        'the string otherwise the last word of the paragraph is not recognised.

        nWordStart = 1
        nWordEnd = 1

        For i = 1 to Len(sParagraph)

            nChar = ASC(Mid(sParagraph, i, 1))

            If IsWordSeparator(nChar) Then   '1

                If nWordEnd > nWordStart Then   '2

                sWordToCheck = TrimWord( Mid(sParagraph, nWordStart, nWordEnd - nWordStart) )

                    bTest = oSpeller.isValid(sWordToCheck, aLocale, emptyArgs())

                    If Not bTest Then   '3
                        oSpell = oSpeller.spell(sWordToCheck, aLocale, emptyArgs())
                        sAlternatives = oSpell.getAlternatives()
                        s = ""                        
                        If Ubound(sAlternatives) >= 0 Then   '4
                            for j = LBound(sAlternatives) To Ubound(sAlternatives) - 1
                                s = s & sAlternatives(j) & ", "
                            Next j
                                s = s & sAlternatives(Ubound(sAlternatives))
                        End If          '4 
                        oDestinationText.insertString(oDestinationCursor, sWordToCheck & " :  " & s & Chr(13), False)
                    End If  '3

                End If   '2
                    nWordEnd = nWordEnd + 1
                    nWordStart = nWordEnd
                Else
                    nWordEnd = nWordEnd + 1
            End If    '1

        Next i

    Loop While oSourceCursor.gotoNextParagraph(False)

    oDestinationCursor.gotoStart(False)
    oDestinationCursor.gotoEnd(True)

    Dim oSortDescriptor As Object
    oSortDescriptor = oDestinationCursor.createSortDescriptor()
    oDestinationCursor.sort(oSortDescriptor)

    Dim sParagraphToBeChecked As String
    Dim sThisWord As String
    sThisWord = ""
    Dim sPreviousWord As String
    sPreviousWord = ""

    oDestinationCursor.gotoStart(False)
    oDestinationCursor.collapseToStart()

    Dim k As Integer
    Do
        oDestinationCursor.gotoEndOfParagraph(True)
        sParagraphToBeChecked = oDestinationCursor.getString()
        k = InStr(sParagraphToBeChecked, ":")
        If k <> 0 Then
            sThisWord = Left(sParagraphToBeChecked, k-1)
        End If
            If StrComp(sThisWord, sPreviousWord, 0) = 0 Then
            oDestinationCursor.setString("")
        End If
        sPreviousWord = sThisWord
    Loop While oDestinationCursor.gotoNextParagraph(False)

    Dim oReplaceDescriptor As Object
    oReplaceDescriptor =  oDestination.createReplaceDescriptor()
    oReplaceDescriptor.setPropertyValue("SearchRegularExpression", TRUE)
    oReplaceDescriptor.setSearchString("^$")
    oReplaceDescriptor.setReplaceString("")
    oDestination.replaceAll(oReplaceDescriptor)

End Sub

'----------------------------------------------------------------------------

' From OOME Listing 360. 
Function IsWordSeparator(iChar As Integer) As Boolean

    ' Horizontal tab \t 9
    ' New line \n 10
    ' Carriage return \r 13
    ' Space   32
    ' Non-breaking space   160     

    Select Case iChar
    Case 9, 10, 13, 32, 160
        IsWordSeparator = True
    Case Else
        IsWordSeparator = False
    End Select    
End Function

'-------------------------------------

' Characters to be trimmed off beginning of word before spell checking
Function IsPermissiblePrefix(iChar As Integer) As Boolean

    ' Symmetric double quote " 34
    ' Left parenthesis ( 40
    ' Left square bracket [ 91
    ' Back-tick ` 96
    ' Left curly bracket { 123
    ' Left double angle quotation marks « 171
    ' Left single quotation mark ‘ 8216
    ' Left single reversed 9 quotation mark ‛ 8219
    ' Left double quotation mark “ 8220
    ' Left double reversed 9 quotation mark ‟ 8223

    Select Case iChar
    Case 34, 40, 91, 96, 123, 171, 8216, 8219, 8220, 8223
        IsPermissiblePrefix = True
    Case Else
        IsPermissiblePrefix = False
    End Select 

End Function

'-------------------------------------

' Characters to be trimmed off end of word before spell checking
Function IsPermissibleSuffix(iChar As Integer) As Boolean

    ' Exclamation mark ! 33
    ' Symmetric double quote " 34
    ' Apostrophe ' 39
    ' Right parenthesis ) 41
    ' Comma , 44
    ' Full stop . 46
    ' Colon : 58
    ' Semicolon ; 59
    ' Question mark ? 63
    ' Right square bracket ] 93
    ' Right curly bracket } 125
    ' Right double angle quotation marks » 187
    ' Right single quotation mark ‘ 8217
    ' Right double quotation mark “ 8221

    Select Case iChar
    Case 33, 34, 39, 41, 44, 46, 58, 59, 63, 93, 125, 187, 8217, 8221
        IsPermissibleSuffix = True
    Case Else
        IsPermissibleSuffix = False
    End Select    

End Function

'-------------------------------------

Function TrimWord( sWord As String) As String

    Dim n as Integer
    n = Len(sWord)
    
    If n > 0 Then
    
        Dim m as Integer :  m = 1
        Do While IsPermissiblePrefix( ASC(Mid(sWord, m, 1) ) ) And m <= n
                m = m + 1
        Loop
    
        Do While IsPermissibleSuffix( ASC(Mid(sWord, n, 1) ) ) And n >= 1
                n = n - 1
        Loop
        
        If n > m Then
            TrimWord = Mid(sWord, m, (n + 1) - m)
        Else
            TrimWord = sWord
        End If
            
    Else
        TrimWord = ""
    End If

End Function
Howard Rudd
  • 901
  • 4
  • 6
  • It returns the same output as previous macro ListMisSpelledWords2. But the request is to find the mis-spelled words and replace them using auto-correct entry "automatically". For e.g. if the corpus has a word "adn" it should be replaced with "and". This (and previous) macro will list it's options like adn : and, ad, an, add but will not actually replace the word with "and". – shantanuo Jul 07 '21 at 02:17
  • The requested function is already available if you try Tools - Autocorrect - Apply and it works for English text. But for Marathi / Hindi (and possibly many other Asian languages) this option will replace about 50% of the words incorrectly thereby making it useless. The first step would be to find how to call "Auto-correct" list from a macro instead of "Spell-check". – shantanuo Jul 07 '21 at 02:22
  • The second request is about listing the synonyms for correctly spelled words (and not for mis-spelled words as you think). This will be useful while reviewing the document to make it better and easier to understand. Right-clicking on each word to find synonyms is cumbersome for lengthy documents. I will be happy to offer 400 points bounty if that matters. (Even if that does not matter, I appreciate your help) – shantanuo Jul 07 '21 at 02:23
  • So if I'm understanding you correctly you want to to use autocorrect to identify errors and suggest an alternative instead of the spellchecker? I'm not sure that is possible. After a thorough search I can't find any reference to autocorrect in the API documentation or any other developer resources, so it is quite possible that LibreOffice doesn't provide a way to access it programmatically. – Howard Rudd Jul 08 '21 at 11:12
  • There is a dispatch command called `.uno:AutoComplete` so it may be possible to move the view cursor over each word and dispatch this command. I haven't tried it so I don't know whether it would work and even if it does work for English it may still suffer from the same problems you have already identified when used on the languages you are interested in. – Howard Rudd Jul 08 '21 at 11:13
  • Version 3 does have an advantage over the previous versions, which is that it treats words that have punctuation characters in the middle as whole words, whereas all the earlier approaches (and Ctrl + Right arrow) treat punctuation marks, such as ! , ; : etc as a word boundaries. For example v3 treats "Austra;ia" as a single word and suggests "Australia", "Australasia", "Austria" and "Strauss" as alternatives, whereas all the previous versions recognise "Austra" and "ia" as separate words and offer suggestion for those, none of which include what the user had in mind. – Howard Rudd Jul 08 '21 at 11:14
  • I wouldn't wish you to spend all your hard earned points on this question (you must be very accomplished to have so many to give away). You have given me enough already. – Howard Rudd Jul 08 '21 at 11:14
  • Is it possible to write an extension to distribute this routine? – shantanuo May 05 '22 at 09:40
  • 1
    That is certainly possible. The easiest way is to put the functions into a new library and then export the library as an extension. That can be done using the Basic Macro Organiser dialog, which has an export button in the Libraries tab. If a user imports the resulting .oxt file using the extension manager they will then have a copy of that library in their `MyMacros` section. That will enable them to run any of the functions in that libabry in the usual way via the menu commands Tools > Macros > Run Macro. – Howard Rudd May 07 '22 at 17:32
  • 1
    It is also possible to make a more sophisticated extension that could do things like add new commands to menus and buttons to toolbars, but that would require more effort, as well as design choices such as what menu items / buttons you want to put where and what functions you want them to invoke. If all you want to do is make the macros available to others then exporting an `.oxt` file from the Basic Macro Organiser dialog will do the trick with minimal effort. – Howard Rudd May 07 '22 at 17:33
  • Thanks. That worked. I guess Libreoffice has too many features those I need to explore. :) – shantanuo May 08 '22 at 05:40
  • There is a minor problem with the code. If I have a punctuation mark immediately after the word then it works, for e.g. test! but I get an error if the punctuation mark is followed after space for e.g. test ! – shantanuo May 08 '22 at 06:07
  • How do I add a keyboard shortcut (for e.g. F9) for this macro programmatically? – shantanuo May 12 '22 at 04:04
  • 1
    This can be done via the `Customise` dialog, which can be called up by choosing `Tools > Customise` from the main menu of the application or from the Basic IDE . The `Keyboard` tab allows allocation of commands, including macros, to keyboard shortcuts. The list box headed `Category` in the bottom left corner contains a hierarchical list of headings. Near the bottom of this list there is `LibreOffice Macros`. Choose a module, then a function, then a shortcut then press `Modify` then `OK`. In theory it should be possible to make the extension itself add the shortcut, but I'm not sure how. – Howard Rudd May 13 '22 at 14:54