使用 JavaScript 宏在 LibreOffice 中实现 Wordle
Implementing Wordle in LibreOffice with JavaScript Macros

原始链接: https://bojidar-bg.dev/blog/2025-11-11-wordle-libreoffice/

## LibreOffice 中的 JavaScript:Wordle 实验 在 LibreOffice 月期间,作者调查了套件中 JavaScript 脚本的现状,特别是利用它的困难过程。目标是什么?在 LibreOffice Writer 中直接构建一个可玩的 Wordle 克隆版。 尽管设置完成后脚本体验出乎意料地稳定,但入门却很困难。文档不足,创建 JavaScript 宏需要手动编辑 OpenDocument 文件结构——添加 JavaScript 文件并更新清单文件。然而,通过 Java 桥接到 JavaScript 的核心脚本 API 运行可靠。 作者成功地实现了 Wordle 的核心功能,包括输入、高亮显示,甚至使用 LibreOffice 的内置工具进行拼写检查。挑战包括在 UNO API 中处理 Java 和 JavaScript 之间的类型差异、处理撤销/重做操作以及在宏执行后重新获得文档焦点。 最终,该项目展示了 UNO 对象模型的强大功能,它提供了对几乎所有 LibreOffice 功能的访问。虽然目前实施起来比较繁琐,但改进文档和内置 JavaScript 编辑器可以显着增强开发体验。完整的 Wordle 实现可供实验。

Hacker News 新闻 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 使用 JavaScript 宏在 LibreOffice 中实现 Wordle (bojidar-bg.dev) 12 分,由 nogajun 1 天前发布 | 隐藏 | 过去 | 收藏 | 1 条评论 shakna 1 天前 [–] > 注意灰色显示的“编辑”和“创建”按钮。 是的……出于某种原因,这种情况已经持续了近十年。[0] 但是,如果我记得正确的话,有一个隐藏的设置可以让你启用未签名的宏,_然后_这些按钮就会可用。只是帮助文档并没有特别强调这一点。[0] https://ask.libreoffice.org/t/need-step-by-step-instructions...回复 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

It is the Month of LibreOffice—time to be awesome with LibreOffice, whether that's spreading the word, supporting others, translating, documenting, bugfixing, or coding new features!

Given that LibreOffice is looking for developers to improve the scripting support and change their current JavaScript runtime (Rhino), I wondered...

What's scripting LibreOffice in JavaScript like, today?

(Spoilers: it's hard to start using JavaScript macros, but they work surprisingly well! )

Video demonstration of playing Wordle in a LibreOffice Writer document

To answer that, I experimented: can I make a simple game inside LibreOffice Writer?

I decided to make a Wordle clone, as the input method was very fitting: player enters words one at a time, and whenever they press "Enter", the game scores their guess. Scoring could be done by highlighting the letters of a word, which is already a feature of Writer!
In that respect, I'm quite happy with the final result that you can see above; my initial idea translated very well into LibreOffice's scripting API.

(You can even try the final result for yourself in the Codeberg repository!)

Starting out

Coming up with an idea is easy.

Getting code to execute is much harder.

LibreOffice's documentation is sorely lacking when it comes to writing macros. There is a page on Scripting LibreOffice which tells you that it is possible to use JavaScript, and directs you to the LibreOffice API where you... won't find anything about JavaScript, except a single support class.

The Document Foundation Wiki is more useful, with the Developer's Guide on Scripting Frameworks, which at least points you to the "Organize Macros → JavaScript" menu option

However, the Developer's Guide lies to you, saying that "[The Organizer dialog for JavaScript] allows you to run macros and edit macros, and create, delete and rename macros and macro libraries."

Here's what the dialog looks like, when you try to edit a JavaScript macro:

A dialog listing JavaScript macros; a library called "Library1" is selected, but both the Edit and Create buttons on the side are grayed out.

Note the grayed-out Edit and Create buttons. 😅

Further down, the Developer's Guide gives a JavaScript macro example. To its credit, the macro works... except the wiki had no explanation of how to embed it into a document... at the time of writing this article 😇.

Is an .odt file with a Wordle macro too much to ask? 😂


After exploring an unzipped .odt file, looking at the Developer's Guide's Java example, and finally finding the JavaScript examples in LibreOffice's source code, I understood how to make an embedded JavaScript macro:

First, I unzip my .odt file, renaming it to a .zip and using an archival tool. Inside of it, there is a folder structure like the following:

root
|- META-INF
|  |- manifest.xml
|- mimetype, content.xml, ...

In here folder, I need to add my JavaScript macro. It goes in Scripts/javascript/<Library name>/<function.js>, with a matching Scripts/javascript/<Library name>/parcel-descriptor.xml file:

root
|- META-INF
|  |- manifest.xml
|- Scripts
|  |- javascript
|  |  |- MyLibraryName
|  |  |  |- MyFile.js
|  |  |  |- parcel-descriptor.xml
|- mimetype, content.xml, ...

The manifest.xml must be updated with the new files:

official Hello World example:

UNO LibreOffice API, as exposed to a Java API, and made accessible through the Rhino engine, together with the whole Java standard library!

I just.. need a way to trigger the macro in the first place. 😁

The original Scripting LibreOffice help page explains all the different places in which we can attach macros to events. These include document loading, hyperlinks, form controls, and keyboard shortcuts—feel free to explore!

Basic Wordle input

For my game, I wanted to have a way of detecting when the player has entered a new line and a way of highlighting the letters of the player's guess.

I looked at shortcuts, but those are not saved with the document, and I didn't want to ask the player to register their own shortcut. Instead, I went with a button that would attach an event listener. Totally missing that I could have used a document load event instead of the button.

But what event ("event broadcaster", in the UNO nomenclature) could I listen to?

After a few false leads, like com.sun.star.document.Events, I finally ended up at XModifyBroadcaster, an interface implemented by the TextDocument service we already use in the script above as doc.

XModifyBroadcaster requires an XModifyListener. I worried that I can't to create one through the script API, but Rhino had me covered: new Interface({member: function() { ... }}) creates new objects implementing a given interface! 🎉

It's now a matter of combining all of that together:

StackOverflow question on setTimeout in Rhino:

UNO LibreOffice API reference comes handy. An XText lets me create a "cursor" for navigating the text and selecting the guessed word.
However, the interface I have, XTextCursor can only move character-by-character and I need to move up a paragraph to get to the player's last guess.
Thankfully, createTextCursor links to the TextCursor service, which also implements other interfaces, like XParagraphCursor. Nifty!

I cast the cursor I get to the paragraph cursor interface, and it works!

A bit of tinkering yields the following script:

TextCursor implements CharacterProperties. These properties can be accessed through XPropertySet, as I've learned from the wiki:

XTextCursor to read the text, so that's solved too!

the final Wordle.js script.

Rather than explain the code in detail, I would like to highlight a few difficulties I encountered while programming it:

Rhino's JavaScript support

Rhino does not fully support newer ECMAScript features, though work is underway. The lack of let, especially, was a constant annoyance as my muscle memory kept getting in the way.

Java strings

The getString function I used for getting the user's guess returns a java.lang.String—not a JavaScript String.
This confused me for over half an hour, because == compares Java strings by reference, before I realized I had the wrong type.

To convert the Java string to a JavaScript string, I can cast it:

CharBackColor property is a long, and attempted to pass a java.lang.Long instead of java.lang.Integer when I was initially set it.

It throws an IllegalArgumentException.

chapter 2 of the Developer Guide, I learned that the UNO API does indeed specify long, but UNO's long maps to Java's int. (And for a 64 bit value, UNO uses hyper instead of long.) Wish I read that earlier!

Printing to the console

Making progress is hard without a way to debug programs, and I often needed to display a few values to make sense of what is happening.

Fortunately, I have the whole Java API available:

XUndoManagerSupplier and its enterHiddenUndoContext function, so that the macro's modifications would be undone/redone together with the user's own action.

the Python spellchecking example, I thought it would be cool if I used LibreOffice's own spellchecker to do limit the player to English words.

Creating an object in UNO involves the service manager factory as explained in the wiki:

Godot's Node system that is used by the Godot Editor itself, in that both are very "practical". That's in stark contrast to the Web's DOM or UI libraries like Jetpack, that all revolve around potential needs of a potential user.

Feel free to try wordle-in-libreoffice for yourself, play around with the LibreOffice JavaScript API, or even contact me with any questions or comments you might have about this article.


This has been my 33rd article for #100DaysToOffload.

Articles tagged technology (16/16) →|

Articles tagged 100DaysToOffload (33/33) →|

Articles on this blog (40/40) →|

Comments?

联系我们 contact @ memedata.com