LibreOffice Developer's Guide: Chapter 18 - Scripting Framework
An LibreOffice macro is a short program used to automate a number of steps. The Scripting Framework is a new feature in LibreOffice. It allows users to write and run macros for LibreOffice in a number of programming and scripting languages including:
- BeanShell (https://www.beanshell.org/)
- JavaScript (https://www.mozilla.org/rhino/)
- Java (https://www.java.com)
- Python (Python as a macro language)
- LibreOffice Basic (LibreOffice Basic)
The framework is designed so that developers can add support for new languages.
Structure of this Chapter
This chapter is organized into the following sections:
- Section Using the Scripting Framework describes the user interface features of the Scripting Framework
- Describes how to run a macro using the Run Macro dialog.
- Describes how to use the Organizer dialogs to create, edit and manage macros.
- Section Writing Macros provides a guide on how to get started with writing Scripting Framework macros
- Describes how to write a simple HelloWorld macro.
- Describes how Scripting Framework macros interact with LibreOffice and the LibreOffice API.
- Describes how to create a dialog from a Scripting Framework macro.
- Describes how to compile and deploy a Java macro.
- Section How the Scripting Framework Works describes how the plug-able architecture of the Scripting Framework allows support for new scripting languages to be added easily.
- Section Writing a LanguageScriptProvider UNO Component using the Java Helper Classes describes how to use the Scripting Framework Java helper classes to add support for a new scripting language
- Describes how to use the ScriptProvider abstract base class.
- Describes how to add editor and management support.
- Describes how to build and register a ScriptProvider.
- Section Writing a LanguageScriptProvider UNO Component From Scratch describes how to write a LanguageScriptProvider UNO component.
Who Should Read this Chapter
If you are interested in automating LibreOffice using BeanShell, JavaScript, Java, Python or LibreOffice Basic then you should read sections Using the Scripting Framework and Writing Macros.
If you are interested in adding support to run and write macros in a language with a Java based interpreter then you should read section Writing a LanguageScriptProvider UNO Component using the Java Helper Classes.
If you are interested in adding support for a scripting language from scratch then you should read section Writing a LanguageScriptProvider UNO Component From Scratch.
Using the Scripting Framework
This section describes how to run and organize macros using the
▸ submenu:Running Macros
To run a macro use the menu item Tools > Macros > Run Macro… This opens the Macro Selector dialog:
The Library list box contains a tree representation of all macro libraries. At the top level, there are three entries:
- My Macros (macros belonging to the current user)
- OpenOffice Macros (macros available to all users of the installation)
- DocumentName Macros (macros contained in the currently active document)
Each of these entries can be expanded to show any macro libraries they contain. When a library has been selected, the macros contained in that library are displayed in the Macro name list box. When a macro is selected its description, if one exists, is displayed at the bottom of the dialog. Selecting a macro and clicking Run will close the dialog and run the macro. Clicking Cancel will close the dialog without running a macro.
Macros can also be run directly from the Macro Organizer Editing, Creating and Managing Macros and from some macro editors.
Editing, Creating and Managing Macros
The Scripting Framework provides support for editing, creating and managing macros via the Tools > Macro > Organize Macros menu. From there you can open a macro management dialog for BeanShell, JavaScript or LibreOffice Basic macros.
The Tools > Macro > Organize Macros menu of LibreOffice can only execute existing Python macros. Facilities for creating, editing, deleting, are not yet implemented.
The Organizer dialogs for BeanShell and JavaScript
The Organizer dialogs for BeanShell and JavaScript dialogs work in the same way. The dialog allows you to run macros and edit macros, and create, delete and rename macros and macro libraries.
The dialog displays the complete hierarchy of macro libraries and macros that are available for the language. The buttons in the dialog are enabled or disabled depending on which item is selected, so for example, if a read only library is selected the Create button is disabled. The buttons in the dialog are used as follows:
- Run
- Closes the dialog and runs the selected macro.
- Create
- Pops up a dialog prompting the user for a name for the new library (if a top-level entry is selected) or macro (if a library is selected). The dialog will suggest a name which the user can change if they wish. When the OK button is clicked, the new library or macro should appear in the Organizer.
- Edit
- Opens an Editor window for the selected macro.
- Rename
- Opens a dialog prompting the user for a new name for the selected library or macro. By default the dialog will contain the current name, which the user can then change. If the user presses OK the library or macro is renamed in the Organizer.
- Delete
- Deletes the currently selected entry.
BeanShell Editor
The macro source is listed in the main window with line numbers in the left-hand sidebar. The editor supports simple editing functions (Ctrl-X to cut, Ctrl-C to copy, Ctrl-V to paste, double click to select a word, triple click to select a line). The Run button executes the source code as displayed in the Editor window.
Basic and Dialogs
The LibreOffice Basic and Dialog Organizers are described in the LibreOffice Basic chapter.
Macro Recording
Macro Recording is only supported for LibreOffice Basic in Writer and Calc modules and is accessible via the Tools > Macro > Record Macro menu item.
The Macro Recorder is intended for simple, sequential tasks. It generates dispatch commands. Use instead the API resources for elaborate programming.
Writing Macros
The HelloWorld macro
Here is a comparison of HelloWorld macros in the different script languages available in LibreOffice.
Basic
Sub writeHelloWorld()
Dim oDoc As Object, xText As Object, xTextRange As Object
oDoc = ThisComponent
xText = oDoc.getText()
xTextRange = xText.getEnd()
xTextRange.CharBackColor = 1234567
xTextRange.CharHeight = 16.0
' CharUnderline receives a group constant
xTextRange.CharUnderline = com.sun.star.awt.FontUnderline.WAVE
' CharPosture receives an enum
xTextRange.CharPosture = com.sun.star.awt.FontSlant.ITALIC
xTextRange.setString( "Hello World (in Basic)" )
End Sub
Basic interprets pairs of get and set methods at UNO objects as object properties if they follow this pattern:
SomeType getSomeProperty()
void setSomeProperty(SomeType aValue)
Using these facilities some lines can be simplified:
xText = oDoc.Text
xTextRange = xText.End
xTextRange.String = "Hello World (in Basic)"
Service properties can be directly accessed in Basic, it is not necessary to use getPropertyValue
or setPropertyValue
methods.
BeanShell
As BeanShell accepts typeless variables, the code is simplified compared to Java.
import com.sun.star.uno.UnoRuntime;
import com.sun.star.text.XTextDocument;
import com.sun.star.text.XText;
import com.sun.star.text.XTextRange;
import com.sun.star.beans.XPropertySet;
import com.sun.star.awt.FontSlant;
import com.sun.star.awt.FontUnderline;
oDoc = XSCRIPTCONTEXT.getDocument();
xTextDoc = UnoRuntime.queryInterface(XTextDocument.class,oDoc);
xText = xTextDoc.getText();
xTextRange = xText.getEnd();
pv = UnoRuntime.queryInterface(XPropertySet.class, xTextRange);
pv.setPropertyValue("CharBackColor", 1234567);
pv.setPropertyValue("CharHeight", 16.0);
// CharUnderline receives a group constant
pv.setPropertyValue("CharUnderline", com.sun.star.awt.FontUnderline.WAVE);
// CharPosture receives an enum
pv.setPropertyValue("CharPosture", com.sun.star.awt.FontSlant.ITALIC) ;
xTextRange.setString( "Hello World (in BeanShell)" );
return 0;
BeanShell interprets pairs of get and set methods at UNO objects as object properties if they follow this pattern:
SomeType getSomeProperty()
void setSomeProperty(SomeType aValue)
Using these facilities some lines can be simplified:
xText = xTextDoc.Text;
xTextRange = xText.End;
xTextRange.String = "Hello World (in BeanShell)";
Service properties are only accessed with getPropertyValue
or setPropertyValue
.
JavaScript
As JavaScript accepts typeless variables, the code is simplified compared to Java.
importClass(Packages.com.sun.star.uno.UnoRuntime)
importClass(Packages.com.sun.star.text.XTextDocument)
importClass(Packages.com.sun.star.text.XText)
importClass(Packages.com.sun.star.text.XTextRange)
importClass(Packages.com.sun.star.beans.XPropertySet)
importClass(Packages.com.sun.star.awt.FontSlant)
importClass(Packages.com.sun.star.awt.FontUnderline)
oDoc = XSCRIPTCONTEXT.getDocument()
xTextDoc = UnoRuntime.queryInterface(XTextDocument,oDoc)
xText = xTextDoc.getText()
xTextRange = xText.getEnd()
pv = UnoRuntime.queryInterface(XPropertySet, xTextRange)
pv.setPropertyValue("CharHeight", 16.0) // Double
// CharBackColor receives an Integer
pv.setPropertyValue("CharBackColor", new java.lang.Integer(1234567))
// CharUnderline receives a group constant
pv.setPropertyValue("CharUnderline",
new java.lang.Short(Packages.com.sun.star.awt.FontUnderline.WAVE))
// CharPosture receives an enum
pv.setPropertyValue("CharPosture", Packages.com.sun.star.awt.FontSlant.ITALIC)
xTextRange.setString( "Hello World (in JavaScript)" )
JavaScript does not accept the simplifications seen in Basic and BeanShell.
Setting an integer value to a property requires to use a java class.
Java
Other sections of this document provide numerous Java examples. Here is the HelloWorld provided in LibreOffice
import com.sun.star.uno.UnoRuntime;
import com.sun.star.frame.XModel;
import com.sun.star.text.XTextDocument;
import com.sun.star.text.XTextRange;
import com.sun.star.text.XText;
import com.sun.star.script.provider.XScriptContext;
public class HelloWorld {
public static void printHW(XScriptContext xScriptContext)
{
XModel xDocModel = xScriptContext.getDocument();
// getting the text document object
XTextDocument xtextdocument = UnoRuntime.queryInterface(
XTextDocument.class, xDocModel);
XText xText = xtextdocument.getText();
XTextRange xTextRange = xText.getEnd();
xTextRange.setString( "Hello World (in Java)" );
}
}
Compiling and Deploying Java macros
Because Java is a compiled language it is not possible to execute Java source code as a macro directly from within LibreOffice. The code must first be compiled and then deployed within a LibreOffice installation or document. The following steps show how to create a Java macro using the HelloWorld example code:
- Create a HelloWorld directory for your macro
- Create a HelloWorld.java file using the HelloWorld source code
- Compile the HelloWorld.java file. With versions < 7, the following jar files from the program/classes directory of a LibreOffice installation must be in the classpath: ridl.jar, unoil.jar, jurt.jar. From version 7, only libreoffice.jar is necessary.
- Create a HelloWorld.jar file containing the HelloWorld.class file
- Create a parcel-descriptor.xml file for your macro
<?xml version="1.0" encoding="UTF-8"?>
<parcel language="Java" xmlns:parcel="scripting.dtd">
<script language="Java">
<locale lang="en">
<displayname value="HelloWorld"/>
<description>
Prints "Hello World".
</description>
</locale>
<functionname value="HelloWorld.printHW"/>
<logicalname value="HelloWorld.printHW"/>
<languagedepprops>
<prop name="classpath" value="HelloWorld.jar"/>
</languagedepprops>
</script>
</parcel>
The parcel-descriptor.xml file is used by the Scripting Framework to find macros. The functionname element indicates the name of the Java method which should be executed as a macro. The classpath element can be used to indicate any jar or class files which are used by the macro. If the classpath element is not included, then the directory in which the parcel-desciptor.xml file is found and any jar files in that directory will be used as the classpath. The necessary Java UNO classes are available automatically.
- Copy the HelloWorld directory into the share/Scripts/java (or basis/share/Scripts/java) directory of a LibreOffice installation or into the user/Scripts/java directory of a user installation. If you want to deploy the macro to a document you need to place it in a Scripts/java directory within the document zip file.
- If LibreOffice is running, you will need to restart it in order for the macro to appear in the Macro Selector dialog.
If these files are embedded in document file, they have to be registered in META-INF/manifest.xml:
<manifest:file-entry manifest:media-type="text/xml" manifest:full-path="Scripts/java/Test/parcel-descriptor.xml"/>
<manifest:file-entry manifest:media-type="application/binary" manifest:full-path="Scripts/java/Test/test.jar"/>
<manifest:file-entry manifest:media-type="" manifest:full-path="Scripts/java/Test/"/>
<manifest:file-entry manifest:media-type="" manifest:full-path="Scripts/java/"/>
<manifest:file-entry manifest:media-type="" manifest:full-path="Scripts/"/>
Python
A Python module may contain several scripts.
def HelloPython( ):
import uno
def HelloPython( ):
oDoc = XSCRIPTCONTEXT.getDocument()
xText = oDoc.getText()
xTextRange = xText.getEnd()
xTextRange.CharHeight = 16.0
xTextRange.CharBackColor = 1234567
# CharUnderline receives a group constant
xTextRange.CharUnderline = uno.getConstantByName("com.sun.star.awt.FontUnderline.WAVE")
# CharPosture receives an enum
xTextRange.CharPosture = uno.getConstantByName("com.sun.star.awt.FontSlant.ITALIC")
xTextRange.setString( "Hello World (in Python)" )
return None
Python interprets pairs of get and set methods at UNO objects as object properties if they follow this pattern:
SomeType getSomeProperty()
void setSomeProperty(SomeType aValue)
Using these facilities some lines can be simplified:
xText = oDoc.Text
xTextRange = xText.End
xTextRange.String = "Hello World (in Python)"
Service properties can be directly accessed in Python, no need to use getPropertyValue
or setPropertyValue
.
Features of script languages supported by LibreOffice
Language feature | Basic | BeanShell | JavaScript | Java macro | Python |
---|---|---|---|---|---|
Interpreted | Yes | Yes | Yes | No | Yes |
Integrated editor | Yes, colored | Yes | No | No | No |
Integrated debugger | Yes | No | No | No | No |
Script organizer | Yes | Yes | Yes | No | No |
Script encryption | Possible | No | No | No | No |
Typeless variables | Yes: Variant | Yes | Yes | No | Yes |
Getter/Setter | Yes | Yes | No | No | Yes |
Direct property access | Yes | No | No | No | Yes |
Function for Calc formula | Yes | No | No | No | No |
UNO component creation | No | No | No | Yes | Yes |
Using the LibreOffice API from macros
BeanShell, JavaScript, Java, Python macros are supplied with a variable of type com.sun.star.script.provider.XScriptContext which can be used to access the LibreOffice API. This interface has three methods:
- com.sun.star.frame.XModel
getDocument( )
- Returns the
XModel
interface of the document for which the macro was invoked (see Using the Component Framework)
- com.sun.star.frame.XDesktop
getDesktop( )
- Returns the
XDesktop
interface for the application which can be used to access open document, and load documents (see Using the Desktop)
- com.sun.star.uno.XComponentContext
getComponentContext( )
- Returns the
XComponentContext
interface which is used to create instances of services (see Service Manager and Component Context)
Depending on the language the macro accesses XScriptContext
in different ways:
- BeanShell: Using the global variable
XSCRIPTCONTEXT
oDoc = XSCRIPTCONTEXT.getDocument();
- JavaScript: Using the global variable
XSCRIPTCONTEXT
oDoc = XSCRIPTCONTEXT.getDocument();
- Java: The first parameter passed to the macro method is always of type
XScriptContext
Xmodel xDocModel = xScriptContext.getDocument();
- Python: Using the global variable
XSCRIPTCONTEXT
oDoc = XSCRIPTCONTEXT.getDocument()
Handling arguments passed to macros
In certain cases arguments may be passed to macros, for example, when a macro is assigned to a button in a document. In this case the arguments are passed to the macro as follows:
- BeanShell: In the global
Object[]
variableARGUMENTS
event = (ActionEvent) ARGUMENTS[0];
- JavaScript: In the global
Object[]
variableARGUMENTS
event = ARGUMENTS[0];
- Java: The arguments are passed as an
Object[]
in the second parameter to the macro method
public void handleButtonPress( XScriptContext xScriptContext, Object[] args)
Each of the arguments in the Object[]
are of the UNO type Any. For more information on how the Any type is used in Java see Type Mappings.
The ButtonPressHandler macros in the Highlight library of a LibreOffice installation show how a macro can handle arguments.
- Python: The arguments are passed as parameters of the called function.
Creating dialogs from macros
Dialogs which have been built in the Dialog Editor can be loaded by macros using the com.sun.star.awt.XDialogProvider API. The method createDialog()
from interface XDialogProvider
uses a string as a parameter. This string is the URL to the dialog. It is formed as follows:
vnd.sun.star.script:DIALOGREF?location=[application|document]
where DIALOGREF
is the name of the dialog that you want to create, and location is either application or document depending on where the dialog is stored.
For example if you wanted to load dialog called MyDialog, which is in a Dialog Library called MyDialogLibrary in the LibreOffice dialogs area of your installation then the URL would be:
vnd.sun.star.script:MyDialogLibrary.MyDialog?location=application
If you wanted to load a dialog called MyDocumentDialog which in a library called MyDocumentLibrary which is located in a document then the URL would be:
vnd.sun.star.script:MyDocumentLibrary.MyDocumentDialog?location=document
The following code shows how to create a dialog from a Java macro:
public XDialog getDialog(XScriptContext context)
{
XDialog theDialog;
// We must pass the XModel of the current document when creating a DialogProvider object
Object[] args = { context.getDocument() };
Object obj;
try {
obj = xmcf.createInstanceWithArgumentsAndContext(
"com.sun.star.awt.DialogProvider", args, context.getComponentContext());
}
catch (com.sun.star.uno.Exception e) {
System.err.println("Error getting DialogProvider object");
return null;
}
XDialogProvider xDialogProvider = (XDialogProvider)
UnoRuntime.queryInterface(XDialogProvider.class, obj);
// Got DialogProvider, now get dialog
try {
theDialog = xDialogProvider.createDialog(
"vnd.sun.star.script:MyDialogLibrary.MyDialog?location=application");
}
catch (java.lang.Exception e) {
System.err.println("Got exception on first creating dialog: " + e.getMessage());
}
return theDialog;
}
Calling a macro with the Scripting Framework
The service com.sun.star.script.provider.ScriptProvider exports interface XScriptProvider which offers method getScript( )
. This method returns an interface XScript which offers the method invoke( )
which will call the script with parameters if necessary.
The identity and location of the called script is contained in an URI, which follows a particular syntax.
How the Scripting Framework works
The goals of the ScriptingFramework are to provide plug-able support for new scripting languages and allow macros written in supported languages to be:
- Executed
- Displayed
- Organized
- Assigned to LibreOffice events, key combinations, menu and toolbar items
This is achieved by enabling new language support to be added by deploying an UNO component that satisfies the service definition specified by com.sun.star.script.provider.LanguageScriptProvider. The ScriptingFramework detects supported languages by discovering the available components that satisfy the service specification and obey the naming convention "com.sun.star.script.provider.ScriptProviderFor[Language]
"
LibreOffice comes with a number of reference LanguageScriptProviders installed by default.
LanguageScriptProviders | |
---|---|
Language | Service name |
Java
|
com.sun.star.script.provider.ScriptProviderForJava
|
JavaScript
|
com.sun.star.script.provider.ScriptProviderForJavaScript
|
BeanShell
|
com.sun.star.script.provider.ScriptProviderForBeanShell
|
Basic
|
com.sun.star.script.provider.ScriptProviderForBasic
|
Python
|
com.sun.star.script.provider.ScriptProviderForPython
|
For more details on naming conventions, interfaces and implementation of a LanguageScriptProvider please see Writing a LanguageScriptProvider UNO Component From Scratch and Writing a LanguageScriptProvider UNO Component using the Java Helper Classes.
The illustration above shows the simplified interaction between the Office Process and the ScriptingFramework when invoking a macro. Macros are identified by a URI and are represented by objects implementing the com.sun.star.script.provider.XScript interface. When the getScript()
method is called the ScriptingFramework uses the URI to determine the correct LanguageScriptProvider to call getScript()
on. The LanguageScriptProvider translates a URI into a object that implements Xscript
. Office can then invoke the macro by calling invoke on that object.
Writing a LanguageScriptProvider UNO Component Using the Java Helper Classes
The Scripting Framework provides a set of Java Helper classes which make it easier to add support for scripting languages for which a Java interpreter exists. This set of classes will handle all of the UNO plumbing required to implement a LanguageScriptProvider, leaving the developer to focus on writing the code to execute their scripting language macros. The steps to add a new LanguageScriptProvider using Java are:
- Create a new ScriptProviderForYourLanguage by inheriting from the abstract ScriptProvider Java base class
- Implement the com.sun.star.script.provider.XScript UNO interface with code to run your scripting language interpreter from Java
- Optionally, add support for editing your scripting language macros by implementing the ScriptEditor Java interface
- Build and register your ScriptProvider
The ScriptProvider Abstract Base Class
The ScriptProvider class is an abstract Java class with three abstract methods:
// this method is used to get a script for a script URI
public abstract XScript getScript( String scriptURI )
throws com.sun.star.uno.RuntimeException,
com.sun.star.script.provider.ScriptFrameworkErrorException;
// This method is used to determine whether the ScriptProvider has a ScriptEditor
public abstract boolean hasScriptEditor();
// This method is used to get the ScriptEditor for this ScriptProvider
public abstract ScriptEditor getScriptEditor();
The most important method is the getScript()
method which must be implemented in order for LibreOffice to execute macros in your scripting language. Fortunately this is made easy by a set of helper methods in the ScriptProvider class, and by a set of helper classes which implement the BrowseNode API described in Writing a LanguageScriptProvider UNO Component From Scratch.
Here is an example of a ScriptProvider implementation for a new ScriptProviderForYourLanguage:
import com.sun.star.uno.XComponentContext;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.lang.XSingleServiceFactory;
import com.sun.star.registry.XRegistryKey;
import com.sun.star.comp.loader.FactoryHelper;
import com.sun.star.lang.XServiceInfo;
import com.sun.star.lang.XInitialization;
import com.sun.star.script.provider.XScriptContext;
import com.sun.star.script.provider.XScript;
import com.sun.star.script.framework.provider.ScriptProvider;
import com.sun.star.script.framework.provider.ScriptEditor;
import com.sun.star.script.framework.container.ScriptMetaData;
public class ScriptProviderForYourLanguage
{
public static class _ScriptProviderForYourLanguage extends ScriptProvider
{
public _ScriptProviderForYourLanguage(XComponentContext ctx)
{
super (ctx, "YourLanguage");
}
public XScript getScript(String scriptURI)
throws com.sun.star.uno.RuntimeException,
com.sun.star.script.provider.ScriptFrameworkErrorException
{
YourLanguageScript script = null;
try
{
ScriptMetaData scriptMetaData = getScriptData(scriptURI);
XScriptContext xScriptContext = getScriptingContext();
script = new YourLanguageScript(xScriptContext, scriptMetaData);
}
catch (com.sun.star.uno.Exception e)
{
System.err.println("Failed to get script: " + scriptURI);
}
return script;
}
public boolean hasScriptEditor()
{
return true;
}
public ScriptEditor getScriptEditor()
{
return new ScriptEditorForYourLanguage();
}
}
// code to register and create a service factory for ScriptProviderForYourLanguage
// this code is the standard code for registering classes which implement UNO services
public static XSingleServiceFactory __getServiceFactory( String implName,
XMultiServiceFactory multiFactory,
XRegistryKey regKey )
{
XSingleServiceFactory xSingleServiceFactory = null;
if ( implName.equals( ScriptProviderForYourLanguage._ScriptProviderForYourLanguage.class.getName() ) )
{
xSingleServiceFactory = FactoryHelper.getServiceFactory(
ScriptProviderForYourLanguage._ScriptProviderForYourLanguage.class,
"com.sun.star.script.provider.ScriptProviderForYourLanguage",
multiFactory,
regKey );
}
return xSingleServiceFactory;
}
public static boolean __writeRegistryServiceInfo( XRegistryKey regKey )
{
String impl =
"ScriptProviderForYourLanguage$_ScriptProviderForYourLanguage";
String service1 =
"com.sun.star.script.provider.ScriptProvider";
String service2 =
"com.sun.star.script.provider.LanguageScriptProvider";
String service3 =
"com.sun.star.script.provider.ScriptProviderForYourLanguage";
FactoryHelper.writeRegistryServiceInfo(impl, service1, regKey);
FactoryHelper.writeRegistryServiceInfo(impl, service2, regKey);
FactoryHelper.writeRegistryServiceInfo(impl, service3, regKey);
return true;
}
}
The getScriptData()
and getScriptingContext()
methods, make the implementation of the getScript()
method easy.
The __getServiceFactory()
and __writeRegistryServiceInfo()
methods are standard LibreOffice methods for registering UNO components. The only thing you need to change in them is the name of your ScriptProvider.
Implementing the XScript interface
The next step is to provide the YourLanguageScript
implementation which will execute the macro code. The following example shows the code for the YourLanguageScript
class:
import com.sun.star.uno.Type;
import com.sun.star.uno.Any;
import com.sun.star.lang.IllegalArgumentException;
import com.sun.star.lang.WrappedTargetException;
import com.sun.star.reflection.InvocationTargetException;
import com.sun.star.script.CannotConvertException;
import com.sun.star.script.provider.XScriptContext;
import com.sun.star.script.provider.XScript;
import com.sun.star.script.provider.ScriptFrameworkErrorException;
import com.sun.star.script.provider.ScriptFrameworkErrorType;
import com.sun.star.script.framework.provider.ClassLoaderFactory;
import com.sun.star.script.framework.container.ScriptMetaData;
public class YourLanguageScript implements XScript
{
private XScriptContext xScriptContext;
private ScriptMetaData scriptMetaData;
public YourLanguageScript(XScriptContext xsc, ScriptMetaData smd)
{
this.xScriptContext = xsc;
this.scriptMetaData = smd;
}
public Object invoke( Object[] aParams,
short[][] aOutParamIndex,
Object[][] aOutParam )
throws com.sun.star.script.provider.ScriptFrameworkErrorException,
com.sun.star.reflection.InvocationTargetException
{
// Initialise the out paramters - not used at the moment
aOutParamIndex[0] = new short[0];
aOutParam[0] = new Object[0];
// Use the following code to set up a ClassLoader if you need one
ClassLoader cl = null;
try {
cl = ClassLoaderFactory.getURLClassLoader( scriptMetaData );
}
catch ( java.lang.Exception e )
{
// Framework error
throw new ScriptFrameworkErrorException(
e.getMessage(), null,
scriptMetaData.getLanguageName(), scriptMetaData.getLanguage(),
ScriptFrameworkErrorType.UNKNOWN );
}
// Load the source of your script using the scriptMetaData object
scriptMetaData.loadSource();
String source = scriptMetaData.getSource();
Any result = null;
// This is where you add the code to execute your script
// You should pass the xScriptContext variable to the script
// so that it can access the application API
result = yourlanguageinterpreter.run( source );
// The invoke method should return a com.sun.star.uno.Any object
// containing the result of the script. This can be created using
// the com.sun.star.uno.AnyConverter helper class
if (result == null)
{
return new Any(new Type(), null);
}
return result;
}
}
If the interpreter for YourLanguage
supports Java class loading, then the ClassLoaderFactory
helper class can be used to load any class or jar files associated with a macro. The ClassLoaderFactory
uses the parcel-descriptor.xml file (see Compiling and Deploying Java macros) to discover what class and jar files need to be loaded by the script.
The ScriptMetaData
class will load the source code of the macro which can then be passed to the YourLanguage
interpreter.
Implementing the ScriptEditor interface
If you want to add support for editing scripts you need to implement the ScriptEditor
interface:
package com.sun.star.script.framework.provider;
import com.sun.star.script.provider.XScriptContext;
import com.sun.star.script.framework.container.ScriptMetaData;
public interface ScriptEditor
{
public Object execute() throws Exception;
public void indicateErrorLine( int lineNum );
public void edit(XScriptContext context, ScriptMetaData entry);
public String getTemplate();
public String getExtension();
}
The edit()
method is called when a user presses the Edit button in the Macro Organizer. The ScriptEditor
implementation can use the ScriptMetaData
object to obtain the source code for the macro and display it.
The getTemplate()
method should return a template of a macro in your language, for example the code to write HelloWorld
into a document. The getExtension()
method should return the filename extension used for macros written in your language. These methods are called when the Create button is pressed in the Macro Organizer.
The execute()
and indicateErrorLine()
methods are not called by the Macro Organizer and so they do not have to do anything. They are used by the implementation of the ScriptProviderForBeanShell
to execute the source code that is displayed in the ScriptEditorForBeanShell
, and to open the ScriptEditorForBeanShell
at the line for which an error has occurred. The developer may wish to do the same when writing their ScriptProviderForYourLanguage
and ScriptEditorForYourLanguage
.
The following code shows an example ScriptEditorForYourLanguage.java file:
import com.sun.star.script.framework.provider.ScriptEditor;
import com.sun.star.script.provider.XScriptContext;
import com.sun.star.script.framework.container.ScriptMetaData;
import javax.swing.*;
public class ScriptEditorForYourLanguage implements ScriptEditor
{
public Object execute() throws Exception
{
return null;
}
public void indicateErrorLine( int lineNum )
{
return;
}
public void edit(XScriptContext context, ScriptMetaData entry)
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JTextArea ta = new JTextArea();
entry.loadSource();
ta.setText(entry.getSource());
frame.getContentPane().add(ta);
frame.setSize(400, 400);
frame.show();
}
public String getTemplate()
{
return "the code for a YourLanguage script";
}
public String getExtension()
{
return "yl";
}
}
Building and registering your ScriptProvider
In order to compile these classes you need to include the UNO and Scripting Framework jar files in your classpath. You can find these in the program/classes directory of your LibreOffice installation. The jar files that you need to include are: ridl.jar, unoil.jar, jurt.jar and ScriptFramework.jar.
To compile ScriptProviderForYourLanuage
:
- Compile the ScriptProviderForYourLanguage.java, ScriptEditorForYourLanguage.java and YourLanguageScript.java files
- Create a jar file for
ScriptProviderForYourLanguage
with the following in the manifest file. (Use the-m
switch to the jar command to add the manifest data)
Built-By: Yours Truly RegistrationClassName: ScriptProviderForYourLanguage
- Register the
ScriptProviderForYourLanguage
jar file using the Extension Manager.
Now you should see an entry for YourLanguage
in the Tools - Macros - Organize Macros… menu.
Writing a LanguageScriptProvider UNO Component From Scratch
To provide support for a new scripting language a new LanguageScriptProvider
for that language needs to be created. The new LanguageScriptProvider
, an UNO component, must be written in a language from which there is an existing UNO bridge. Details about UNO bridges can be found at UNO C++ Bridges.
The LanguageScriptProvider
is an UNO component that provides the environment to execute a macro for a specific language. For example when LibreOffice encounters a Scripting Framework URI (see Scripting Framework URI Specification) the ScriptingFramework
finds the appropriate LanguageScriptProvider
to execute the script. A LanguageScriptProvider
has the following responsibilities:
- It must support the com.sun.star.script.provider.LanguageScriptProvider service.
- It is responsible for creating the environment necessary to run a script.
- It is responsible for implementing the com.sun.star.script.browse.BrowseNode service to allow macros to be organized and browsed.
- Given a script URI it is responsible for returning a command like object that implements the com.sun.star.script.provider.XScript interface that can execute the macro indicated by the URI.
- The name of the any
LanguageScriptProvider
service must be of the form "com.sun.star.script.provider.ScriptProviderFor[Language]
", where Language is the language name as it appears in a script URI.
Scripting Framework URI Specification
General syntax
The URI is a case-sensitive string. It is composed of fixed terms and three parameters:
vnd.sun.star.script:MACROPARAM?language=LANGPARAM&location=LOCPARAM
where:
MACROPARAM
identifies the script.LANGPARAM
identifies theLanguageScriptProvider
needed to execute the script as described. Current language values : Basic, BeanShell, Java, JavaScript, PythonLOCPARAM
identifies the container of the script, i.e. My Macros, or OpenOffice Macros, or within the current document, or in an extension.
As the syntax of these parameters depends on each LanguageScriptProvider
they are described hereafter for each implemented language. Terms like myLibrary
, myMacro
are placeholders that stand for the names used in a real case.
Basic script
A Basic script is usually called : a macro. It may be a Sub or a Function.
The Basic script is supposed to be created through the user interface, see section Editing, Creating and Managing Macros.
LANGPARAM
Basic
LOCPARAM
- macro in a library of My Macros :
application
- macro in a library of OpenOffice Macros :
application
- macro in an extension installed for the current user :
application
- macro in an extension installed for all users :
application
- macro in a library of the document that calls the macro :
document
MACROPARAM
The Basic macro myMacro
is stored in module myModule
itself stored in library myLibrary
.
- General case : the value of MACROPARAM is :
myLibrary.myModule.myMacro
- Basic script in an extension : same as above. The directory
myLibrary
containing the files constituting the Basic library must be stored in the extension package (see description in section Basic / Application Library Container).
Remember that, unlike usual calls in Basic, you must respect the majuscules and minuscules of library, module, macro names.
BeanShell script
The BeanShell script is supposed to be created through the user interface, see section Editing, Creating and Managing Macros.
LANGPARAM
BeanShell
LOCPARAM
- script in a library of My Macros :
user
- script in a library of OpenOffice Macros :
share
- script in an extension installed for the current user :
user:uno_packages/myExtension.oxt
where myExtension.oxt is the file name of the extension package. - script in an extension installed for all users :
share:uno_packages/myExtension.oxt
- script in a library of the document that calls the script :
document
MACROPARAM
The BeanShell script myMacro.bsh
is stored in library myLibrary
.
- General case : the value of MACROPARAM is :
myLibrary.myMacro.bsh
- BeanShell script in an extension : same as above. The directory
myLibrary
containing the BeanShell script file and parcel-descriptor.xml file must be stored in the extension package.
Java script
Java scripts are macros in compiled Java. This is different from language JavaScript. See section Scripting / Writing Macros / Compiling and Deploying Java macros.
LANGPARAM
Java
LOCPARAM
- script in a library of My Macros :
user
- script in a library of OpenOffice Macros :
share
- script in an extension installed for the current user :
user:uno_packages/myExtension.oxt
where myExtension.oxt is the file name of the extension package. - script in an extension installed for all users :
share:uno_packages/myExtension.oxt
- script in a library of the document that calls the script :
document
MACROPARAM
The Java script file myMacro.jar
is stored in directory myLibrary
. The Java method to be executed is myMethod
(this is the value of functionname
element of the parcel-descriptor.xml file).
- General case : the value of MACROPARAM is :
myLibrary.myMethod
- Java compiled script in an extension : same as above. The directory
myLibrary
must be stored in the extension package.
JavaScript script
The JavaScript script is supposed to be created through the user interface, see section Editing, Creating and Managing Macros.
LANGPARAM
JavaScript
LOCPARAM
- script in a library of My Macros :
user
- script in a library of OpenOffice Macros :
share
- script in an extension installed for the current user :
user:uno_packages/myExtension.oxt
where myExtension.oxt is the file name of the extension package. - script in an extension installed for all users :
share:uno_packages/myExtension.oxt
- script in a library of the document that calls the script :
document
MACROPARAM
The JavaScript file myMacro.js
is stored in library myLibrary
.
- General case : the value of MACROPARAM is :
myLibrary.myMacro.js
- JavaScript script in an extension : same as above. The directory
myLibrary
containing the JavaScript file and parcel-descriptor.xml file must be stored in the extension package.
Python script
The location of a Python script is described in Python as a macro language.
LANGPARAM
Python
LOCPARAM
- script in a library of My Macros :
user
- script in a library of OpenOffice Macros :
share
- script in an extension installed for the current user :
user:uno_packages
- script in an extension installed for all users :
share:uno_packages
- script in a library of the document that calls the script :
document
MACROPARAM
- General case : the Python module
myModule.py
is stored in directoryScripts/python
of the user or share directory of the installation, or of the document. The name of the Python function to be executed ismyFunction
. The value of MACROPARAM is :myModule.py$myFunction
- Python script in an extension : myExtension.oxt is the file name of the extension package. The Python module is stored in directory
myScript
in this package. The value of MACROPARAM is :myExtension.oxt|myScript|myModule.py$myFunction
Complete URI examples
These URI's call a script stored in My Macros
vnd.sun.star.script:myLibrary.myModule.myMacro?language=Basic&location=application vnd.sun.star.script:myLibrary.myMacro.bsh?language=BeanShell&location=user vnd.sun.star.script:myLibrary.myMethod?language=Java&location=user vnd.sun.star.script:myLibrary.myMacro.js?language=JavaScript&location=user vnd.sun.star.script:myModule.py$myFunction?language=Python&location=user
These URI's call a script stored in an extension installed for all users
vnd.sun.star.script:myLibrary.myModule.myMacro?language=Basic&location=application vnd.sun.star.script:myLibrary.myMacro.bsh?language=BeanShell&location=share:uno_packages/myExtension.oxt vnd.sun.star.script:myLibrary.myMethod?language=Java&location=share:uno_packages/myExtension.oxt vnd.sun.star.script:myLibrary.myMacro.js?language=JavaScript&location=share:uno_packages/myExtension.oxt vnd.sun.star.script:myExtension.oxt|myScript|myModule.py$myFunction?language=Python&location=share:uno_packages
.
Storage of Scripts
A LanguageScriptProvider
is responsible for knowing about how its own macros are stored: where, what format and what kind of directory structure is used. The Scripting Framework attempts to standardize how to store and discover macros by defining:
- A default directory structure.
- Macros can only be stored under a directory with the language name ( as it appears in the script URI ) in lowercase under a directory called Scripts, which is located in either the user or share directories of a LibreOffice installation or a LibreOffice document.
- Example for a
LanguageScriptProvider
for the “JavaScript” macro library. It is located in <OfficePath>/share/Scripts/JavaScript/Highlight
- A generic mechanism for enabling discovery of macros in macro libraries and associating meta-data with scripts located in this libraries. See parcel-descriptor.xml in Compiling and Deploying Java Macros. Example the parcel-descriptor for the JavaScript Highlight macro library is located in <OfficePath>/share/Scripts/JavaScript/Highlight/parcel-descriptor.xml
Implementation
A LanguageScriptProvider
implementation must follow the service definition com.sun.star.script.provider.LanguageScriptProvider
Since a LanguageScriptProvider
is an UNO component, it must additionally contain the component operations needed by a UNO service manager. These operations are certain static methods in Java or export functions in C++. It also has to implement the core interfaces used to enable communication with UNO and the application environment. For more information on the component operations and core interfaces, please see Component Architecture and Core Interfaces to Implement.
The interface Initialization supports method:
void initialize( [in] sequence<any> arguments )
The LanguageScriptProvider
is responsible for organizing and execution of macros written in a specific language for a certain location. The possible locations for macros are within a document or either the user or share directories in a LibreOffice installation. The LanguageScriptProvider
is initialized for given location context which is passed as the first argument to the initialize()
method. The location context is a string with the following possible values
Location context | |
---|---|
"user "
|
String . Denotes the user directory in a LibreOffice installation
|
"share "
|
String . Denotes the share directory in a LibreOffice installation
|
url
|
String . For user or share directory, the url has scheme vnd.sun.star.expand
example: user directory " share directory " |
Where for a currently open document the url has scheme vnd.sun.star.tdoc
example:
|
The com.sun.star.script.browse.XBrowseNode interface supported by a LanguageScriptProvider
service is the initial point of contact for the [AOo] application. In order for the %PRODUCNAME
to process and display macros it needs to be able to list those macros. Additionally the MacroOrganizer
dialogs use the com.sun.star.script.browse.BrowseNode service to create/delete new macros and macro libraries.
The interface com.sun.star.script.browse.XBrowseNode supports the following methods:
string getName()
sequence < ::com::sun::star::script::browse::XBrowseNode > getChildNodes()
boolean hasChildNodes()
short getType()
The method getName()
returns the name of the node.
The method getChildNodes()
method should return the nodes which represent the next level in the hierarchy of macros and macro Libraries the LanguageScriptProvider
is managing.
The method getType()
returns the type of the node.
Nodes can be of three types represented by the constants com.sun.star.script.browse.BrowseNodeTypes
Constants of com.sun.star.script.browse.BrowseNodeTypes | |
---|---|
Value | Description |
SCRIPT | Indicates that the node is a script. |
CONTAINER | Indicates that the node is a container of other nodes e.g. Library |
ROOT | Reserved for use by the ScriptingFramework .
|
The objects implementing XBrowseNodes
can must also implement com.sun.star.beans.XPropertySet.
Properties of object implementing com.sun.star.script.browse.BrowseNode | |
---|---|
Uri
|
string . Found on script nodes only, is the script URI for the macro associated with this node.
|
Description
|
string . Found on script nodes only, is a description of the macro associated with this node.
|
Creatable
|
boolean . True if the implementation can create a new container or macro as a child of this node.
|
Creatable
|
boolean . True if the implementation can delete this node.
|
Editable
|
boolean . True if the implementation is able to open the macro in an editor.
|
Renamable
|
boolean . True if the implementation can rename this node.
|
Note that a node that has the Creatable
, Deletable
, Editable
or Renamable
properties set to true is expected to implement the com.sun.star.script.XInvocation interface.
The interface com.sun.star.script.XInvocation supports the following methods:
com::sun::star::beans::XIntrospectionAccess getIntrospection();
any invoke( [in] string aFunctionName,
[in] sequence<any> aParams,
[out] sequence<short> aOutParamIndex,
[out] sequence<any> aOutParam )
void setValue( [in] string aPropertyName,
[in] any aValue )
any getValue( [in] string aPropertyName )
boolean hasMethod( [in] string aName )
boolean hasProperty( [in] string aName )
The invoke()
function is passed as an argument the property keys of the com.sun.star.script.browse.BrowseNode service
Elements of aParam sequence in invoke call
| |
---|---|
aFunctionName | aParams |
Editable
|
None required. |
Creatable
|
aParam[0] should contain the name of the new child node to be created.
|
Deletable
|
None required. |
Renamable
|
aParam[0] should contain the new name for the node.
|
Uri
|
None required. |
Description
|
None required. |
Access to a macro if provided for by the com.sun.star.script.provider.XScriptProviderinterface which supports the following method:
::com::sun::star::script::provider::XScript getScript( [in] string sScriptURI )
The getScript()
method is passed a script URI sScriptURI
and the LanguageScriptProvider implementation needs to parse this URI so it can interpret the details and validate them. As the LanguageScriptProvider
is responsible for exporting and generating the URI associated with a macro it is also responsible for performing the reverse translation for a give n URI and returning an object implementing com.sun.star.script.provider.XScript interface which will allow the macro to be invoked.
com.sun.star.script.provider.XScript which supports the following methods:
any invoke( [in] sequence<any> aParams,
[out] sequence<short> aOutParamIndex,
[out] sequence<any> aOutParam )
In addition to the parameters that may be passed to an object implementing com.sun.star.script.provider.XScript it is up to the that object to decide what extra information to pass to a running macro. It makes sense to pass information to the macro which makes the macro writer's job easier. Information such as a reference to the document (context), a reference to the service manager (available from the component context passed into the LanguageScriptProvider
component's constructor by UNO), and a reference to the desktop (available from UNO using this service manager).
All the Java based reference LanguagesScriptProvider
provided with LibreOffice make this information available to the running macro in the form of an object implementing the interface com.sun.star.script.provider.XScriptContext. This provides accessor methods to get the current document, the desktop and the component context. Depending on the constraints of the language this information is passed to the macros in different ways, for example in Beanshell and JavaScript this is available as an environment variable and in the case of Java it is passed as the first argument to the macro.
Integration with Extension Manager
The Extension Manager is a tool for deploying components, configuration data and macro libraries (see chapter Extensions). It provides a convenient mechanism for macro developers to distribute their macros.
The scripting framework supports deployment of macros in extensions. Currently only extensions for the media type "application/vnd.sun.star.framework-script
" are supported. Macros deployed in extensions of this media type must use the ScriptingFramework
storage scheme and parcel-descriptor.xml to function correctly. An implementation of the com.sun.star.deployment.PackageRegistryBackend service is provided which supports deployment of macro libraries of media type "application/vnd.sun.star.framework-script
" with the Extension Manager.
Overview of how ScriptingFramework integrates with the Extension Manager API
Registration
Macro libraries contained in extensions are registered by the Extensions Manager. It informs the LanguageScriptProvider
by calling its insertByName()
method. The LanguageScriptProvider
persists in the registration of the macro library in order to be aware of registered libraries when LibreOffice is restarted at a future time.
Deregistration
Deregistration of a macro library contained in an extension is similar to the registration process described above, the Extension Manager informs the LanguageScriptProvider
that a macro library has been removed by calling its removeByName()
method. The LanguageScriptProvider
removes the macro library from its persisted store of registered macro libraries.
Implementation of LanguageScriptProvider with support for Package Manager
In order for the LanguageScriptProvider
to handle macro libraries contained in UNO packages with media type "application/vnd.sun.star.framework-script
" it's initialize()
method must be able to accept a special location context that indicates to the LanguageScriptProvider
that it is dealing with extensions.
Location context | |
---|---|
"user:uno_packages "
|
String . Denotes the user installation deployment context.
|
"share:uno_packages "
|
String . Denotes the share installation deployment context.
|
On initialization the LanguageScriptProvider
needs to determine what macro libraries are already deployed by examining its persistent store.
A LanguageScriptProvider
that does not use the Java abstract helper class com.sun.star.script.framework.provider.ScriptProvider
will need to persist in the extensions deployed for the supported language themselves.
The LanguageScriptProvider
additionally needs to support the com.sun.star.container.XNameContainer interface which supports the following methods.
void insertByName( [in] string aName,
[in] any aElement )
void removeByName( [in] string Name )
On registration of an extension containing scripts the LanguageScriptProvider
's insertByName()
method is called with aName
containing the URI to a macro library contained in the extension and aElement
contains an object implementing com.sun.star.deployment.XPackage Note that the URI contains the full path to the macro library contained in the extension. For example, if the library is named my macros then the path includes the MyMacros directory.
On deregistration of an extension containing scripts the LanguageScriptProvider
's removeByName()
method is called with aName
containing the URL to a macro library to be de-registered.
com.sun.star.container.XNameContainer interface itself inherits from com.sun.star.container.XNameAccess which supports the following method
boolean hasByName( [in] string aName )
To determine whether the macro library in an extension is already registered the LanguageScriptProvider
's hasByName()
is called with aName
containing the URL to the script library. The other methods of the interfaces inherited by com.sun.star.container.XNameContainer are omitted for brevity and because they are not used in the interaction between the Extension Manager and the LanguageScriptProvider
. A Developer however still must implement these methods.
Implementation of the BrowseNode service
The LanguageScriptProvider
created for an installation deployment context needs to expose the macro and macro libraries that it is managing. How this is achieved is up to the developer. A LanguageScriptProviders
created by extending the Java abstract helper class com.sun.star.script.framework.provider.ScriptProvider
creates nodes for each extension that contain macro libraries for the supported language. Each extension node contains the macro library nodes for the supported language and those nodes in turn contain macro nodes.
An alternative implementation could merge the macro libraries into the existing tree for macro libraries and not distinguish whether the macros are located in an extension or not. This is loosely the approach taken for LibreOffice Basic.
Example of creating an extension containing a macro library suitable for deploying with Extension Manager. The following example shows how to create an UNO package from the Beanshell macro library Capitalize. This macro library is located in the <OfficeDir>/share/beanshell/Capitalize directory of a LibreOffice installation. The extension created will be deployable using the Extension Manager.
First create a scratch directory for example called temp. Copy the macro library directory and its contents into temp. In temp create a sub-directory called META-INF and within this directory create a file called manifest.xml.
<Dir> Temp | |-<Dir> Capitalise | | | |--parcel-desc.xml | |--capitalise.bsh | |-<Dir> META-INF | |--manifest.xml
The contents of the manifest.xml file for the Capitalize macro library are as follows
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE manifest:manifest PUBLIC "-//OpenOffice.org//DTD Manifest 1.0//EN" "Manifest.dtd">
<manifest:manifest xmlns:manifest="http://openoffice.org/2001/manifest">
<manifest:file-entry manifest:media-type="application/vnd.sun.star.framework-script" manifest:full-path="Capitalise/"/>
</manifest:manifest>
Next create a zip file containing the contents (but not including ) the temp directory. The name of the file should have the extension ".oxt" e.g. Capitalise.oxt.
Deploying a macro library contained in an extension.To deploy the extension you need to use the Extension Manager (see chapter Extensions). Once the extension has been deployed successfully the macro will be available for assignment or execution.