[Writer/Macro] getCurrentSelection() returns null when macro is invoked headless

Hey there,

I make a pretty extensive use of this trick to batch inspect/update ODT files with macros by invoking LibreOffice in headless mode :

However, I ran into a bug which, so far, seems specific to accessing the CurrentSelection property of a document.

Here’s some sample code of a simplified example:

sub CheckText()
    _checkText(ThisComponent, "This file")
end sub

sub CheckTextNoHeadless()
    _checkText(ThisComponent, "This file")
    ThisComponent.close(true)
end sub

sub CheckTextHeadless(file as String)
    document = _openSilent(file)
    _checkText(document, file)
    document.close(true)
end sub

private sub _checkText(document as object, file)
    dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
    frame = document.CurrentController.Frame

    parEnum = document.Text.createEnumeration()
    par = parEnum.nextElement()

    document.CurrentController.select(par.Anchor.Start)
    dispatcher.executeDispatch(frame, ".uno:EndOfLineSel", "", 0, Array())

    selection = document.getCurrentSelection()
    firstLine = selection.getByIndex(0).getString()

    msgbox("First line of first paragraph:" & Chr(10) & firstLine)
end sub

' Provides ability to invoke the macro headless from different files
' https://superuser.com/a/1630932/3130029
private function _openSilent(filePath as string) as object
    dim fileProperties(1) as new com.sun.star.beans.PropertyValue
    fileProperties(0).Name = "Hidden"
    fileProperties(0).Value = true
    _openSilent = StarDesktop.loadComponentFromURL("file://" & filePath, _
        "_blank", 0, fileProperties())
end function

When I invoke CheckText interactively from Writer, it works as expected (e.g. displays the first line of the first paragraph).

From the command line, I invoke it as follows and it also works as expected:

soffice macro:///Standard.Reformat.CheckTextNoHeadless sample_file.odt

FYI, the main reason to have a separate macro is that CheckTextNoHeadless will close the file when it’s done, allowing to loop over multiple files, e.g.:

for f in *.odt; do soffice macro:///Standard.Reformat.CheckTextNoHeadless "$f"; done

The main problem with the above command is that it spawns a new Writer UI for each file, making the computer unusable while the command is running. Hence the headless trick, which, by the way, is also orders of magnitude faster!

Headless command for a unique file:

soffice --headless macro:///Standard.Reformat.CheckTextHeadless\("/absolute/path/to/sample_file.odt"\)

Batch headless command:

for f in /absolute/path/to/*.odt; do soffice --headless macro:///Standard.Reformat.CheckTextHeadless\("$f"\); done

Small tip: In the above command, LibreOffice will fail to load files that contain a comma. Here’s how to work around it (assuming you run it in the same folder):

rename 's/,/WXYZ1234/g' *.odt; for f in /absolute/path/to/*.odt; do soffice --headless macro:///Standard.Reformat.CheckTextHeadless\("$f"\); done; rename 's/WXYZ1234/,/g' *.odt

To be clear, in my experience, invoking macros headless works extremely well. I do it with dozens of macros that perform numerous different actions on thousands of documents. Hence my surprise, since when I invoke this macro headless on the same file(s), I get the following error:

BASIC runtime error.
Object variable not set.

Indeed, when I add a breakpoint, selection is null, as well as the document.CurrentSelection property.

Once again, I’m able to headlessly access and update many different properties of the document and its children objects, as well as dispatch UNO commands. The issue seems specific to the CurrentSelection property :person_shrugging:

Should I report a bug?

PS: Sorry for the lengthy post, but I initially quite struggled to find some good information about batch headless macro invocation. So I thought gathering it all in one post might help others in the future!

I don’t think this is a bug.
Working in headless mode has its limitations.
By the way, the CurrentSelection property can be Nothing in normal mode as well, for example when the user is editing the annotation text.

Of course, though in this example I explicitly select some text prior to accessing CurrentSelection.

Probably. This is quite an empiric process to figure them out though… Considering the various operations I’m able to perform healdlessly, this still seems like quite a unique limitation.

No. In headless mode, you obviously have everything related to the document: its structure, its text, its formatting… But you have nothing related to interaction: controller, visible cursor, visible selection and so on. This is normal and natural.

In this mode, you would have to use non-interactive elements, like XTextCursor.

1 Like

To be noted that I’m actually able to headlessly access:

  • document.CurrentController.Frame
  • document.Text.createTextCursorByRange(...)
  • dispatcher.executeDispatch(...), including:
    • dispatcher.executeDispatch(frame, ".uno:SelectAll", "", 0, Array())
    • and perform actions on that selection afterwards, as long as I don’t explicitly access CurrentSelection

But I get your point and I hadn’t heard about XTextCursor before so I’ll definitely have a look. Thanks a lot for the tip!