LibreOffice Developer's Guide: Chapter 4 - Extensions

From The Document Foundation Wiki
Jump to navigation Jump to search

An extension is a file intended for the distribution of code and / or data which is to be used by LibreOffice. The file has the file extension “oxt” (formerly .uno.pkg and .zip), and it acts as a container for various items, such as libraries, JARs, configuration data, type libraries, Basic libraries, Basic dialogs, etc. Before LibreOffice can use any content of the extension, it needs to be installed by the Extension Manager.

Extension Manager

The Extension Manager is a tool for managing extensions and other deployable items, such as separate libraries, JARs, configuration data files. This includes adding, removing, enabling and disabling of these items.

The Extension Manager can be started from within the office by pressing the menu item Tools ▸ Extension Manager or by running the unopkg executable, which is contained in the program directory of the office installation.

When an extension is installed, a copy is created, which is kept either in the user installation or the shared installation (<office-directory>/share). The original extension can therefore be (re)moved after installation.

Extension Repositories

(As of OpenOffice.org 3.3)

LibreOffice stores extensions in different places, the so-called repositories. This mostly does not matter for users because this has no influence on the extension's functionality. There are three repositories: user, shared and bundled. An extension can be part of every repository at the same time, but one repository can only hold one version of an extension. That is, assuming one has version one and version two of an extension, which also may be contained in different files, then only one of them can be installed per repository. The extension manager enforces this by checking the extension identifier which is stored within an extension. The repositories are prioritized, meaning that an extension in a repository with a high priority is preferred over the same extension in a repository with a lower priority. The user repository has the highest and the bundled repository has the lowest priority. An extension repository consists basically of two parts, the extensions and additional data, which is produced by the extension manager. In order to easier refer to the locations where these are stored, we name the location containing the extensions the extensions folder and the place where the other data is stored the registration data folder. Both folders may not necessarily be within the same parent folder. The registration data folder for all repositories are within the user installation (user data).

User repository

This repository contains extensions, aka user extensions, which can only be used by the user who installs them. Users can manage the user extensions themselves. That is, they can add, remove, disable and update them.

Shared repository

The extensions in this repository, which are also referred to as shared extension, can be used by all users - that's where the name “shared” comes from. Therefore, they need to be located in a place accessible by all users, which is currently within the office installation. This place is usually restricted in terms of who is allowed to modify (write, delete) files. Therefore, only people with the proper file access rights, who usually have an administrator or root account, can add, remove, update (disabling is not supported) extensions in the shared repository.

Warning.svg

Warning:
Before modifying a shared extension one must make sure that all other LibreOffice processes, that is, LibreOffice run by other users, are shut down.


Bundled repository

This repository holds the bundled extensions. They resemble to a certain degree the shared extensions. That is, they are usable by all users. But only those with the proper file access rights, can add and remove them. Bundled extensions are, as the name suggest, bundled with LibreOffice. That is, they are part of the installation set and are already available after installation. Contrary to shared and user extensions, they are exclusively maintained by the installation program (aka setup) of LibreOffice. Although they are visible in the extension manager's dialog, they cannot be modified by the extension manager, even by administrators. The setup program may offer to choose which extensions are to be installed. To later add or remove a bundled extension, one needs to restart the setup program and determine the wanted extensions. Before running the setup program one must make sure that all LibreOffice processes are shut down. When the office is being uninstalled then all bundled extensions are removed.

The Active Extension

(As of OpenOffice.org 3.3)

If the same extension is installed in the user and shared repository, then it is expected that only the one in the user repository is used. In other words it "hides" the one in the shared repository. This, however, was never the case (until now). It only worked if both extensions contained exactly the same items, such as services, configuration items, etc. However, two different versions of an extension often have different contents. For example, they may have different menu entries. Then the entries from both extensions would be displayed.

In case of having the same contents, one could rely on the underlying facilities, such as configuration manager, service manager, type description provider, to prefer the respective contents of the user extension over those of the shared extension. However, these facilities have no notion of an extension. For example, if the user and shared extension (having the same extension identifier) contain a service Foo, then the service manager will provide the service Foo of the user extension. If the shared extension also contained the service Bar, which is not in the user extension, then the service manager must not offer this service. To do this, the service manager would need the information, which extension contains the service. Similar all other facilities which process extension contents, like the configuration manager, need the association between a particular item and the respective extension.

The active extension solves this "hiding problem". It makes sure that an extension in a repository with a higher priority completely hides the same extension in a repository with a lower priority. This is done without extending all those facilities in a way so that they can deal with extensions. Instead, the extension manager determines which extension may be used an which not. And this is how it works exactly:

There is a fixed hierarchy of repositories:

  1. user (highest priority)
  2. shared
  3. bundled (lowest priority)

This means that there can be three extensions with the same identifier. But only the one in the repository with the highest priority can be the active extension, unless it is disabled. Only disabling of user extensions is supported.

Only the active extension can be used in LibreOffice. For example, if one extension is in all repositories, then only the services from the active extension will be used. In the following explanation we assume that extensions have the same identifier, when speaking of extensions in different repositories, unless otherwise noted.

A user extension hides the shared extension, which in turn hides the bundled extension. If there is a user extension then it is always the active extension independent of its version and if there are extensions in the shared or bundled repository.

A shared extension can only be an active extension if there is no or a disabled user extension. Again, the version of the shared extension and the fact that there may be a bundled extension do not matter. A bundled extension can only be an active extension if there are no shared or user extension. There may be, however, a disabled user extension.

An extension becomes active if it is registered (<idlm>com.sun.star.deployment.XPackage:registerPackage</idlm>) after having revoked (<idlm>com.sun.star.deployment.XPackage:revokePackage</idlm>) those extensions with the same identifier in other repositories. This means that of all the extensions with the same identifier in the different repositories, there is at most one registered at a time. For example:

  1. user extension: registered
  2. shared extension: revoked
  3. bundled extension: revoked

or

  1. no user extension
  2. shared extension: registered
  3. bundled extension: revoked

or

  1. no user extension
  2. no shared extension
  3. bundled extension: registered

Updating Extensions

(As of OpenOffice.org 3.3)

An extension can be updated by either explicitly installing the extension or by running the update function in the extension manager. Updating an extension consisted originally in removing the installed extension and then installing the new version of the extension in the same repository. This meant that it was necessary to have write permission for the extensions folder of the shared repository for updating shared extensions.

Updating shared extensions is now possible. If the user has no write permission, then the update will be installed in the user repository. The actual shared extension will then remain unchanged, but it will be superseded by the update, because it has a higher priority.

Bundled extensions cannot be updated by the extension manager (see Setup - Bundled Extensions). However, users can still download and install these extension themselves.

Users will be informed if there are newer versions of extensions, which they currently use, are available. It does not matter if the currently used extensions are from the user or shared repository. If there is a better version, then it is offered as an update. This allows users to always obtain the latest versions of their extensions.

The updates, that is, the extensions with a newer version, will either be obtained from an online repository or locally, depending on which ones have the highest versions. In the latter case, the newer version will come from the shared or bundled repository if there is one available. If the version of an extension in a local repository is the same as the one from the online repository, then the local one is used.

Using a local repository (bundled or shared) as update source is useful for the case when a user has installed an user extension and then a more current version is installed in the shared or bundled repository. The user would then use the "old" version, although a better one is already available.

The following tables show scenarios where the user can update extensions. If a cell contains a “1” then this simple means there is an extensions installed. If it contains a “2” then this represents an extension with the same identifier then “1” but with a higher version. If there are several “2”s under each other, then it is sufficient if just one of them has a higher version than “1”.

The first table shows the case where a user has no write permission in the shared folder. In all cases the update is installed in the user repository. The new version comes either from the shared, bundled or online repository, depending on which has the best version.

Case a Case b
User repository - 1
Shared repository 1 2
Bundled repository 2 2
Online repository 2 2

In words: An update (installing in the user repository) is possible, if

  1. there is shared extension (no user) and the bundled or online repository contains a higher version
  2. there is a user extension and the shared, bundled or online repository contains a higher version

The next table shows the case where the user has write permission in the shared folder. In all those cases the update/installation will happen in the shared repository. If a user extension exists does not matter.

Case a
Shared repository 1
Bundled repository 2
Online repository 2

In words: An update (installing in shared repository) is possible, if there is shared extension and the bundled or online repository contains a higher version

This also means that, if there is only a user extension (no shared, no bundled) and there is one with a higher version in the online repository, then no update is done in the shared repository.

The next table shows the case where the user has write permission in the shared folder. In all those cases the update/installation will happen in the user repository.

Case a
User repository 1
Shared repository 2
Bundled repository 2
Online repository 2

In words: An update (installing in user repository) is possible, if there is a user extension and the shared, bundled, or online repository contains a higher version.

Processing Extensions

(As of OpenOffice.org 3.3)

Checking for modification during start-up

Every time LibreOffice is started it needs to check if extensions were removed from or added to the shared or bundled repository. If this is the case, then the registration data of the repository must be updated accordingly and LibreOffice is restarted.

Because LibreOffice checks for added or removed extensions at start-up, this must be very quick. To achieve this, LibreOffice compares the modification time of extension folders of the repositories with the time of the most recently performed check. This time is “saved” in the file lastsynchronized in the registration data folder of the repositories. In fact, the modification time of the file is used rather then a value is written into the file. If the modification time of the extension folder is more recent, then the extension manager will check if extensions were added or removed and update its database accordingly. This is of course an implementation detail and may change at any time.

Restarting LibreOffice

The restart is necessary to avoid that users use removed shared extensions. Removing a shared extensions does not entail the immediate removal of the the extension files. This is necessary to allow live deployment. For details see Adding a shared extension and Removing a shared extension.

The extension manager updates its databases during start-up of LibreOffice an removes entries of removed extension, so that they cannot be used anymore. However, until this has happened services from the removed extension may have been started and menus added to the menu bar.

Even if the extensions are completely removed from the disk, as is the case with removed bundled extensions, remnants can still be visible for the user. This applies particularly for configuration data. The extension manager processes them and creates additional data (registration data), that is configuration data containing expanded URLs. LibreOffice only uses the registration data, which is stored in the user data. Removing the bundled extension, which is done by the installer, does not remove the registration data. This is only done during the start-up when the extension manager runs. But by this time, the configuration manager has already loaded the registration data of the removed extension. Live removal of menu entries does not work yet. Therefore artifacts of removed extensions may still be seen when LibreOffice is run the first after removing the extension.

There are also extensions which do not work well right after they are installed. In other words, they do not support live deployment. This is typically caused by issues in LibreOffice and/or the extension. After restarting LibreOffice, the extensions perform as expected.

Although live deployment issues can be fixed, there is still the problem of removed shared extensions. This could only be solved if one would not allow live deployment of shared extensions.

Adding

Adding a user extension

A user extension is extracted into a folder with a unique name which in turn is contained in the extensions folder. The additional folder with the unique name is necessary to make live deployment possible. When removing an extension, it is only marked as “unusable” and the extension is not deleted, because it may still be in use. If now the same extension is installed again in the same process, then it will be extracted to another folder with a unique name. This prevents a name clash with the folder name of the previously “removed” extension. The user can now repeat to remove and install the extension in the same process. This results in having the same extension multiple times in the extensions folder of the repository, but everyone is contained in a folder with a unique name. The folders of the “removed” extensions, as well as the folders which contain them, are only removed after running the extension manager after restarting LibreOffice.

The extension database of repository is updated and the new active extension is determined.

Adding a shared extension

For the user who adds a shared extension, the extension is processed the same way as a user extension.

For all other users, the extension manager processes the extension the next time they start LibreOffice. The extension database of repository is updated and the new active extension is determined.

Adding a bundled extension

A bundled extension is installed by the installation program of LibreOffice. The folder of the extension, including all files contained therein, is copied into the extensions folder of the bundled repository. Only when a user starts LibreOffice, then the extension manager processes the extension. The extension database of repository is updated and the new active extension is determined.

Removing

Removing a user extension

The entry in the extension database of the repository referring to this extension will be deleted and the active extension will be determined. The extension will be deleted when the user runs the extension manager after restarting LibreOffice.

Removing a shared extension

The extension will be revoked if it was registered. To prevent other users from using the extension it is marked to be deleted. This is done by creating a tag file, the "extension removed file", next to the folder with the unique name. This way, the information that the extension was deleted can be accessed by all users. If this file does not exist and there is no entry in the extensions database, then it is assumed that the extension was added. The file will be deleted when the extension is deleted. The file also contains the name of the user who removed the extension. If the same user runs the extension manager after restarting LibreOffice, then the extension, the unique folder, temp file and the "extensions removed file are deleted. Why it must be the same user shows the following scenario:

A user starts LibreOffice and uses the extension. The extension will then be removed and LibreOffice keeps running. Now a second user, who also has write access to the extensions folder, starts LibreOffice. When the extension manager starts it would properly delete extensions which were previously removed. However in this case this would affect the office of the first user which still uses that extension. The same user cannot start another instance, because this is prevented by the lock file.

For all other users, the extension manager processes the extension the next time they start LibreOffice. This includes the detection of the removed extensions. An extension is regarded as removed if:

  • The unique folder, tmp file, extension folder do not exist anymore.
  • There is a "extension removed file"
  • The extension exists but has a different identifier or version than the one which was previously at the same location. This may happen when the extension was updated, or a previous office update removed the extension and another with the same name was added in the latest office update.

The extension could also have the same file name. The entry in the extension database of the repository referring to this extension will be deleted and the active extension will be determined.

Removing a bundled extension

A bundled extension can only be removed by the set-up. The folder of the extension, including all files contained in it, is deleted from the extension folder of the bundled repository.

If a user starts LibreOffice, then the extension manager processes the removed extension. This includes the detection of the removed extensions. An extension is regarded as removed if:

  • The extension folder do not exist anymore.
  • The extension exists but has a different identifier or version than the one which was previously at the same location.

The entry in the extension database of the repository referring to this extension will be deleted and the active extension will be determined.

Updating

An update consists of first removing the extension and then adding the new extension. That is, this is already covered by these two scenarios.

Packaging Notes For Bundled Extensions

(As of OOo 3.3)

Bundled extensions are installed during LibreOffice's setup, and only the setup can install or remove them. That is, they cannot be modified by the extension manager, which would change the installation. The installer copies the extensions into the extensions directory of the "share" folder. For example, C:\Program Files (x86)\OpenOffice 4\share\extensions on Windows. Contrary to versions previous to OpenOffice.org 3.3, unopkg need NOT be run as a post install step. To uninstall the extensions, the installer can simply delete the directories of the respective extensions.

Extensions are packaged as directories instead of ".oxt" files. For example, there is an extension Foo.oxt containing the files f1 and f2. The installer package must then contain the "unzipped" extension, that is a directory Foo (oxt can be ommitted). After installation, the file structure must be:

+ Office/share/extensions
  + Foo
  |- f1
  |- f2

During the packaging process one must make sure that all bundled extensions have different directory names, so as to prevent overwriting other extensions. In case of a name clash, one needs to modify the directory name of the extension appropriately.

The name of the folder should still reflect the name of the original extension file. This makes it easier to locate the extension in the file system. This name will also be used by the extension manager dialog if the extension does not provide a display name. Further details can be found here: Extensions_Integration_into_Installation_Set.

Bundled extension MUST have an identifier and version, otherwise LibreOffice cannot determine reliably if a bundled extension was removed.

Executables contained in the extension must have the executable flag set. This is usually done by the extension manager, but not in case of bundled extensions.

Deployment Items

The Extension Manager can be used to deploy various types of files. It is primarily used for extensions. The latest incarnation of an extensions is the .oxt file, which has superseded .uno.pkg and .zip.

Apart from extensions the Extension Manager can also manage these types:

  • Configuration data (.xcu, .xcs)
  • UNO Libraries (.dll / .so)
  • JARs (.jar)
  • Type libraries (.rdb)

Installing Extensions for All or a Single User

When installing an extension, one has to decide if all possible users can use it (shared extension) or only oneself (user extension). In the first case, users cannot modify (enable, disable, remove) the extension unless they have write access to the share directory in the office installation.

A shared extension may change the appearance of the GUI (toolbars, menu bar, etc.) for all users. They can, however, configure their office so that particular menu or toolbar items are not shown. There is currently no way to centrally install an extension for particular user groups.

Whether an extension is to be installed for all users, or only for the single user, is determined during installation. The person performing the install must select, in the Extension Manager dialog, either My Extensions or LibreOffice Extensions before pressing the Add... button. In the first case, the extension will only be installed for the current user, whereas in the latter case it will be installed for all users.

When running unopkg in a windowless mode, the option --shared determines if an extension can be used by all users. For example:

 [<OfficePath>/program] $ unopkg add --shared my_extension.oxt

would install my_extension so that it can be used by all users.

Extension Manager in LibreOffice

Within a running office, the Extension Manager is started through the menu item Tools ▸ Extension Manager…. When started in this way, extensions can only be installed as user extensions. All items deployed under LibreOffice Extensions cannot be modified. But it is possible to export them.

unopkg

The unopkg executable offers another way to start the Extension Manager. It supersedes the pkgchk executable which was used in OpenOffice.org 1.1.0 and older versions and which no longer works.

In contrast to the Extension Manager in LibreOffice unopkg can also manage shared extensions. For example:

 [<OfficePath>/program] $ unopkg add --shared my_extension.oxt

installs my_extension.oxt for all users.

unopkg offers a windowless mode in which all interactions occurs through the console. This is the default. If unopkg is started with the subcommand gui then the Extension Manager dialog appears which is exactly the same as the one in LibreOffice.

 [<OfficePath>/program] $ unopkg gui

The difference is that in the dialog all items deployed under LibreOffice Extensions can be modified and new items can be added there as well. All actions, that is, adding, removing, etc. can be done in the dialog. Therefore unopkg gui does not require any more parameters.

Following is a short overview of what can be done with unopkg. Since there are many more commands, have a look at the help text that can be obtained by calling unopkg -h.

First of all, open a console and change into the program directory of the office installation.

Adding an extension for a single user:

 [<OfficePath>/program] $ unopkg add my_extension.oxt

Adding an extension for all users:

 [<OfficePath>/program] $ unopkg add --shared my_extension.oxt

Removing a user extension is done via the identifier of the extension (see Extension Identifiers):

 [<OfficePath>/program] $ unopkg remove my.domain.my_extension-id

Remove a shared extension:

 [<OfficePath>/program] $ unopkg remove --shared my.domain.my_extension-id

Before you install an extension or other item for all users, make absolutely sure there are no running instances of LibreOffice. unopkg cannot recognize if there are running instances of LibreOffice from different users. Installing into a running office installation might cause inconsistencies and destroy your installation!

When a user starts LibreOffice and then starts unopkg, then the Extension Manager from the office is used and unopkg terminates. Then, however, no shared extensions and other shared items can be modified.

Warning.svg

Warning:
Although it is now possible to deploy “live” into a running LibreOffice process, there are some limitations you should be aware of: Removing a type library from a running process is not possible, because this may lead to crashes when the type is needed. Thus if you, for example, uninstall a package that comes with a UNO type library, these types will vanish upon next process startup, but not before.

There may also be problems with cached configuration data, because parts of the running process do not listen for configuration updates (for example, menu bars). Most often, those parts read the configuration just once upon startup.


Location of Installed Extensions

Sometimes an extension developer needs to know the path to the root of his installed extension e.g. to load some additional data. You can use the singleton PackageInformationProvider to get an URL for an installed extension with a given Extension Identifier. For more information about Extension Identifiers see Extension Identifiers. For more information see the singleton <idl>com.sun.star.deployment.PackageInformationProvider</idl> and its interface <idl>com.sun.star.deployment.XPackageInformationProvider</idl>.

Here is a C++ example:

...
namespace css = com::sun::star;
css::uno::Reference< css::uno::XComponentContext > mxContext;
...
css::uno::Reference< css::deployment::XPackageInformationProvider >
xInfoProvider( css::deployment::PackageInformationProvider::get( mxContext ) );

// "MY_PACKAGE_ID" is the identifier of the package whose location we want
rtl::OUString sLocation = xInfoProvider->getPackageLocation(
rtl::OUString::createFromAscii( "MY_PACKAGE_ID" ) );
...

You will find a Java example at page : ReadFilesFromOxt

Here is an example in Basic:

Dim pip As Object, extensionLocation As String
Const extensionIdentifier = "org.toto.test3"

pip = GetDefaultContext.getByName("/singletons/com.sun.star.deployment.PackageInformationProvider")
extensionLocation = pip.getPackageLocation(extensionIdentifier)
' display a human readable address
MsgBox(ConvertFromURL(extensionLocation))

Warning.svg

Warning:
The package location address finishes like this : /extensionName.oxt

Remember it is a folder, not the extension package itself!


Extension Layers

The Extension Manager uses two extension layers, the user and the shared layer. Extensions installed in the shared layer can be used by all users whereas those in the user layer can only be used by the user who installed them. An extension can be installed in either layer or both.

This system was developed many years ago. Since then new features were added, such as extension versioning, online update, bundled extensions, etc. During that time it became obvious that there are some problems with the original design. This document tries to explain them.

Current problems

Hiding

The idea of layers is that extensions in the top layer hide the same extensions in the layer below. In other words, the extension in the highest layer will be given preference, when resources from the extension are requested. Because the user layer is the top layer, a shared extension is only used when the same extension does not exist in the user layer. The version of the extension has no influence.

This, however, is only a simplified model. An extension is never used as a whole. Instead clients access the particular contents, such as components, configuration data, etc. And it is also these contents which obscure the same content in a lower layer. For example, an extension contains a UNO service, which is installed in both layers. When the client instantiates it then the one from the top layer is used.

There are situations where contents of the shared extensions are still used, although they should be hidden and therefore not usable. Assuming there are version 1 and version 2 of an extension. Then the different versions may have different content. For example, version 1 may contain a service foo which is not contained in version 2. If now version 1 is installed in the shared layer and version 2 in the user layer then foo is still around and can be instantiated. Foo may access resources which are also delivered with this extension and are also available in version 2 which is in the user layer. Resources in version 2, which hide those from version 1 (same node in xcu or same service), can be incompatible, so that foo fails.

A service may also need files which are part of the same extension and which are only directly accessible. That is there is no API, such as the registry or service manager, which can provide the content. A "running" service does not know from which layer it comes from. To get the install location it can use the PackageInformationProvider. This service will first look into user layer and if the extension cannot be found, then it will look into the shared layer. If now the service from the shared layer needs some file from its extension, then it may accidentally use the one from a different version of the extension in the user layer. This file can be incompatible or not even exist and make the service fail.

Another example can be constructed using registry values, which are contained in xcu files. Let's assume that version 1 of an extension adds a menu foo and version 2 adds menu bar. If one extension is installed in the share and the other in the user layer, then both menus appear. This is because the nodes of the configuration files are merged into the configuration and not the xcu files itself. That is, if the extensions contain an xcu file with the same name, then the xcu file of the last installed extension will NOT replace the one from the other extension. The second xcu file can, however, change the values for nodes which have been defined by the first xcu.

These examples prove that the layering (in terms of hiding the items from the layer below) on extension level does not work, although it does for the respective deployment items. And it may be safe to say, that this goes against the user's expectation.

Unclear behavior

It may be unclear to users how the behavior is when the same extension is installed as user and shared extension. If there is a shared extension and the same extension is installed as user extension, then the user should be informed that the currently installed extension will become 'inactive'. Then, when the user extension is uninstalled, the user should be informed that the shared extension will become 'active' again.

Extension versions

As already observed, the version of an extension has no influence on the decision if the extension from the user or the shared layer is used. At the time of developing the extension framework there was no support for versioned extensions, so one did not have to care about them. Later, we recognized that an extension version is useful for users. For example, if one has an extension installed and is installing the same extension again, then one is being informed about what extension has the later version (so one can decide which is the best for them).

I think the general expectation is that the latest version is the best. Therefore it should be natural that LibreOffice uses always that one. However, this is not the case and users may unintentionally prevent, that they use the latest extension. For example, LibreOffice comes with a bundled English dictionary. Now a user installs, intentionally or not, the same extension and forgets about it. The administrator updates LibreOffice regularly, which also updates the dictionary with the latest version. But the user would still use the old dictionary, because that is the one from the user layer which hides the one from the shared layer.

One could think now of a notification for the user but there are some questions:

  • when does LibreOffice check for this situation?
  • how can this be explained to the user?

The first question, is easily answered: At every start-up.

The second question is more difficult.Think about a message, such as: "Dear user. You cannot use the latest version of the French dictionary, which is installed in the shared/bundled layer. Instead, the dictionary which you installed earlier is used. You need to uninstall it, if you want the new version ...." Many users would probably not understand this. So the easiest and natural way is simply to use the latest version of the extension independent of what layer it is installed in. The term layer should then be replaced, by repository, indicating that the location does not include a preference – just think about the picture of one layer above the other compared to repositories which are arranged in one row.

Layer location

The shared layer is located within the LibreOffice installation. The benefit is that normal users cannot modify it. The layer will be modified after installation of OOo when an extensions is added, deleted, enabled or disabled. Depending on the operation system, this may be against the system guidelines. For example, it would be not allowed on Windows Vista/ 7. However, to allow “legacy application” to work Microsoft invented the virtual store.

Clean uninstallation

Modification of the shared layer will also prevent the clean uninstallation of LibreOffice. This is because the native installer does not “own” the files/folder which were added by the extension manager. For example, on Linux the path /opt/openoffice.org3/share/uno_packages will remain on the system. On OpenSolaris the shared extensions (with the same directory structure) will be copied to /var/lost+found on uninstallation.

No Separation of shared and bundled extensions

The rule is, whatever a user installed should not be removed by LibreOffice. This is also valid for shared extensions. Today a bundled extension is nothing else than a shared extension and therefore can be modified by users, provided they have sufficient file access rights. They can install the same extension as one, which was installed as bundled extension, in the shared layer. This means that the original bundled extension is replaced by the recently installed shared extension. This can happen in two scenarios. The first one is during an online update of the extension and the second is when the extension is “manually” installed. In these cases users intentionally choose to replace the original extension. Therefore this extension may not be deleted when uninstalling LibreOffice. This, however, requires to know that the bundled extension was replaced, but there is currently no way to recognize this.

Selection of extensions in setup

Like many other features, bundled extensions should be selectable in the dialog of the installer. The installer can also be invoked later to add or remove particular features. The problem is, that the extensions which the installer “sees” are not the same as “seen” by the extension manager. The setup copies an oxt file to ../share/extension/install and then invokes a post-install script (Unix) or a custom action (Windows) which runs unopkg to install the extension properly. The extension is then unzipped to ../share/uno_packages/cache/uno_packages and some data are added to the extension manager database. The installer only knows the file that it copied to the ../share/extension/install folder. If it is there then it is regarded as installed – otherwise not.

For an extension to show up in the dialog of the extension manager, there are two conditions which need to be met. First, it must be unpacked in the uno_packages folder. And second, there must be an appropriate entry in the extension manager database, indicating that it is installed. Therefore it would be possible that the installer displayed an extension as being installed but the extension manager did not. This may of course happen the other way round as well.

Even if the installer would copy the uncompressed extension directly to ../share/uno_packages/cache/uno_packages, both dialogs could show a different status. One could assume that the installer and extension manager regard an extension as installed if the the respective folder exists. But this does not work, because the extension manager does not remove immediately the extension when it is being uninstalled. The reason is, that files from the extension can still be in use in the process. Only after restarting OOo and the extension manager, the extension's folder will be finally removed.

Code execution during installation

Copying extensions into the office installation during setup is not sufficient to make them usable. Only after the extension manager has processed them, OOo can make use of them. Therefore the installer invokes the extension manager after an extension has been copied. Unfortunately this requires different solutions for different platforms. RPM (Linux) and PKG (Solaris) use post-install/pre-install scripts which are executed after the copying step. But there are installation scenarios on Solaris, where code execution is not allowed during the setup. For example, the installer is running on Solaris Sparc and installs LibreOffice on a Solaris Intel Machine machine. Therefore LibreOffice uses a “postrun” service which invokes the extension manager when the system is rebooted. OpenSolaris prohibits code execution completely. The setup needs to install an SMF service which is triggered after the setup. Windows needs a 'custom action' which is compiled native code. The Mac dmg file already contains the processed extensions. That is, the extension manager processes the extension while building the dmg file.

It is obvious that the current situation is complex and one understands why testing and bug finding are rather difficult.

Extension Manager database

The database is created when the extension manager is started for the first time. The database contains different files and folders which are completely unknown to the installer. Therefore the installer will not remove them. This is another reason why the OpenOffice.org folder remains on the system after uninstalling LibreOffice.

License of shared extension cannot be displayed to users

Only the one who installs the extension can see the license.

Checklist for Writing Extensions

This page gives a brief overview about what is important when developing an extension.

  1. Use the .oxt file extension and provide description.xml. Look here why you should use .oxt.
  2. Provide an extension identifier. [go to XML description]
  3. Provide a version. [go to XML description]
  4. Provide a display name. [go to XML description]
  5. Provide an icon. [go to XML description]
  6. Provide a description. [go to XML description]
  7. Specify the target platforms. [go to XML description]
  8. You may provide dependencies. [go to XML description]. For example, if your extension requires a 'minimal version' of LibreOffice then use the OpenOffice.org-minimal-version dependency.
  9. You may provide a license. [go to XML description]
  10. You may customize the 'online update' of your extension unless the update facility of the LibreOffice extension repository does not fit your needs. [go to XML description].
  11. You may provide options pages in case the extension needs to be configured. [go to XML description].
  12. Provide publisher information. [go to XML description].
  13. Provide links to release notes (TODO: more documentation) in the description.xml. Currently, only used within the 'update dialog'. When using the LibreOffice extension repository, then the link to the release notes is automatically generated from the information entered on the web site. That is, when using the repository, one does not need to provide this information in the description.xml.
  14. Provide content for help system if necessary.

File Format

An extension is a zip file having a name that ends in .oxt (formerly .uno.pkg or .zip). The file extension .oxt is associated with the MIME / media type vnd.openofficeorg.extension. The .oxt file serves as a wrapper for all the items of an extension, and contains files which list and describe those items. An extension can contain UNO components, type libraries, configuration files, dialog or basic libraries, etc.

An extension should contain a description.xml (see description.xml) and must contain a directory META-INF (all uppercase). The META-INF directory contains a manifest.xml which lists all items and their media-type.

Note pin.svg

Note:
For backward compatibility, legacy bundles (extension .uno.pkg, .zip) that have been formerly deployed using pkgchk are deployable, too. Migrate legacy bundles to the current .oxt format. This can easily be done using the GUI, exporting a legacy bundle as an .oxt file. When a legacy bundle is exported, a manifest.xml file is generated, enumerating the detected items of the bundle. Note: Legacy bundles will be deprecated in OpenOffice 4.0, and support for them could be dropped in a future release; please convert them to .oxt.

Depending on its media-type, the respective file needs to be treated particularly. For example a UNO component needs to be registered before it can be used. All media types which do not require a particular handling of the file are ignored currently (and actually need not be contained in the manifest.xml).

Runtime Libraries

The extensions need to bring their own runtime libraries, such as C or C++, when it is not guaranteed that these libraries are shipped with LibreOffice or are installed on the users' systems. They typically need to reside in the same directory as the libraries containing the UNO components.

Please note that there is a peculiarity of libraries build with MS Visual Studio 2008. The runtime libraries (C, C++) need either be installed in the system or must reside next to the library in order to be found. It does not matter if the runtime libraries are contained in the LibreOffice installation (<office>/OpenOffice.org/Basis/program). As a consequence extensions need always contain the runtime libraries which they use.

The runtime libraries must not be referenced by the manifest.xml.

Shared Library UNO Components

The media-type for a shared library UNO component is application/vnd.sun.star.uno-component;type=native, for example,

<manifest:file-entry manifest:media-type="application/vnd.sun.star.uno-component;type=native"
                    manifest:full-path="myComponent.uno.so"/>

Shared Library UNO Components for particular Platforms

When you implement a UNO native component, for example, a .dll or .so file, then this file is only deployable on that specific platform. It is often convenient to package a bundle for different platforms. For instance, you compile your component for x86 Linux, Solaris SPARC and Windows. You have to tell the Extension Manager which version of your component file corresponds to which platform via a platform attribute supplied with the media-type, for example,

<manifest:file-entry manifest:media-type= "application/vnd.sun.star.uno-component;type=native;platform=Windows"
                    manifest:full-path="windows/mycomp.uno.dll"/>
<manifest:file-entry manifest:media-type= "application/vnd.sun.star.uno-component;type=native;platform=Linux_x86"
                    manifest:full-path="linux/myComp.uno.so"/>
<manifest:file-entry manifest:media-type= "application/vnd.sun.star.uno-component;type=native;platform=Solaris_SPARC"
                    manifest:full-path="solsparc/myComp.uno.so"/>

Uno Jar Components

The media-type for a UNO Jar component is application/vnd.sun.star.uno-component;type=Java, for example,

<manifest:file-entry manifest:media-type="application/vnd.sun.star.uno-component;type=Java"
                    manifest:full-path="myComponent.uno.jar"/>

UNO Python Components

unopkg now supports registration of Python components (.py files). Those files are registered using the com.sun.star.loader.Python loader. For details concerning Python-UNO, please refer to https://www.openoffice.org/udk/python/python-bridge.html. The media-type for a UNO Python component is application/vnd.sun.star.uno-component;type=Python, for example,

<manifest:file-entry manifest:media-type="application/vnd.sun.star.uno-component;type=Python"
                    manifest:full-path="myComponent.uno.py"/>

Passively Registered UNO Components

Beginning with LibreOffice 3.4, UNO components can use passive registration, see Passive_Component_Registration. Components written that way are no longer individually listed in the manifest.xml file. Instead, one or more XML files (with root element <components>) containing the relevant information are registered in the manifest.xml file with media type application/vnd.sun.star.uno_components, optionally giving a platform attribute. For an example extension containing passively registered UNO components, see desktop/test/deployment/passive/.

RDB Type Library

The media-type for a UNO RDB type library is application/vnd.sun.star.uno-typelibrary;type=RDB, for example,

<manifest:file-entry manifest:media-type="application/vnd.sun.star.uno-typelibrary;type=RDB"
                    manifest:full-path="myTypes.uno.rdb"/>

Jar Type Library

The media-type for a UNO Jar typelibrary is application/vnd.sun.star.uno-typelibrary;type=Java, for example,

<manifest:file-entry manifest:media-type="application/vnd.sun.star.uno-typelibrary;type=Java"
                    manifest:full-path="myTypes.uno.jar"/>

Keep in mind that the RDB variant of that type library must be deployed also. This is currently necessary, because your Java UNO types may be referenced from native UNO code.

LibreOffice Basic Libraries

LibreOffice Basic libraries are linked to the basic library container files. Refer to LibreOffice Basic for additional information. The media-type for a LibreOffice Basic Library is "application/vnd.sun.star.basic-library", for example,

<manifest:file-entry manifest:media-type="application/vnd.sun.star.basic-library"
                    manifest:full-path="myBasicLib/"/>

Dialog Libraries

Dialog libraries are linked to the basic dialog library container files. Refer to LibreOffice Basic for additional information. The media-type for a dialog library is application/vnd.sun.star.dialog-library, for example,

<manifest:file-entry manifest:media-type="application/vnd.sun.star.dialog-library"
                    manifest:full-path="myDialog/"/>

Configuration Data Files

The media-type for a configuration data file is application/vnd.sun.star.configuration-data, for example,

<manifest:file-entry manifest:media-type="application/vnd.sun.star.configuration-data"
                    manifest:full-path="myData.xcu"/>

Configuration Schema Files

The media-type for a configuration schema file is application/vnd.sun.star.configuration-schema, for example,

<manifest:file-entry manifest:media-type="application/vnd.sun.star.configuration-schema"
                    manifest:full-path="mySchema.xcs"/>

Be careful not to install schemata (.xcs files) which contain the same elements (oor:package, oor:name) but have different definitions.

Extension Tooltip Description

Deprecated as of OopenOffice.org 3.1. See Extension Description for details.

If you want to add a tooltip description (which shows up in the balloon help of a bundle node in the Extension Manager dialog), then you can do so by specifying localized UTF-8 files, for example,

<manifest:file-entry manifest:media-type="application/vnd.sun.star.package-bundle-description;locale=en"
                    manifest:full-path="readme.en" />
<manifest:file-entry manifest:media-type="application/vnd.sun.star.package-bundle-description;locale=de"
                    manifest:full-path="readme.de" />
<manifest:file-entry manifest:media-type="application/vnd.sun.star.package-bundle-description"
                    manifest:full-path="readme.txt" />

The best matching locale (against the current installation's locale) is taken. The locale is of the form locale=language-country-variant.

Executable Files

The media type for an executable is application/vnd.sun.star.executable. This media type is new since OpenOffice.org 2.4. If an extension uses it then it should also use the dependency OpenOffice.org-minimal-version set to 2.4.

This media type was introduced to work-around the problem that the executable attribute (Unix files, does not apply for Windows files) will not be preserved when LibreOffice extracts a zipped extension. Also, when an extension was zipped on Windows, then the attribute is lost. Using the media type for a Windows executable does not have an effect since there is no such file attribute on Windows. On Unix the executable attribute is set when the extension is being installed. If it is installed for the current user then the user executable flag is set and if it is installed for all users, that is as shared extension, then the executable flag is set for user,group and other.

Example:

<manifest:file-entry manifest:media-type="application/vnd.sun.star.executable"
                    manifest:full-path="hello"/>

All other contents of the extension are simply copied into the Extension Manager cache. You can, for instance, deploy an image for add-on menus within a package, or any other file needed by your component. The LibreOffice configuration is used to find out in which path this file is located in a particular installation. When you define a package containing additional files, include an .xcu configuration data file, which points to your files. Use a variable %origin% as a placeholder for the exact path where the file will be copied by the Extension Manager. When unopkg installs the data, it replaces the path with an URL containing a macro and writes into the configuration. This URL has to be expanded before it is a valid file URL. This can be done using the <idl>com.sun.star.util.MacroExpander</idl> service. The %origin% variable is, for instance, used by the ImageIdentifier property of add-on menus and toolbar items, which is described in the Configuration section.

description.xml

The description.xml is a means to provide additional useful information, such as dependencies, license and update information. It will be extended to support new features in the future. The file must be located in the root of the extension and the name is case sensitive.

Warning.svg

Warning:
The description.xml is searched case sensitive in an oxt package. This is important to know when you package your extensions content into a new oxt package.


Description of XML Elements

Element /description

<description> is the root element of the description.xml.

Attributes of <description>
Attribute Description
xmlns The default namespace of element description and all children must be defined as

http://openoffice.org/extensions/description/2006

xmlns:dep The namespace for dependency information must also be defined as

http://openoffice.org/extensions/description/2006

xmlns:xlink The xlink namespace must be defined as

http://www.w3.org/1999/xlink

other namespace definitions Other namespaces can be added as necessary

Element /description/identifier

Attributes of <identifier>
Attribute Description
value Required. The extension identifier.

See also

Element /description/version

Attributes of <version>
Attribute Description
value Required. A textual representation of the extension version.

See also

Element /description/platform

Attributes of <platform>
Attribute Description
value Required. A token representing a particular platform. See Target Platform for details.

See also

Element /description/registration

The registration element currently only contains the <simple-license> element. If the <registration> element exists, then it must have a child element.

See also

Element /description/registration/simple-license

Parent: <registration>

The element contains the <license-text> elements, determines if all user must agree to the license, or just the person who installs it, and determines a default <license-text> element. The mechanism for determining a "default" has changed in OpenOffice.org 2.4. See Documentation/DevGuide/Extensions#Localized XML Elements for details. This element is disregarded for bundled extensions.

If the <simple-license> element exists, then it must have at least one child element.

Attributes of <simple-license>
Attribute Description
accept-by Required.Value is either “user” or “admin”. “user” means that every user has to agree to the license. That is, the extension can only be installed as user extension but not as shared extension. If it has the value “admin” then it can be deployed as shared extension as well. In that case only the person who installs it has to agree to the license. Individual users will not be asked to accept the license. They can use the extension right away. In case the value is “user” and the extension is being installed as user extension then the user must always agree to the license. By using suppress-if-required=“true“ it is, however, possible to install an extension for all users, that is, as shared extension, even if accept-by=“user“.
default-license-id Required. Since OpenOffice.org 2.4 this attribute is obsolete. If the extension should work properly (regarding showing the proper license during installation) on versions prior 2.4 one must use this attribute. If one does not use this attribute then one should use the OpenOffice.org-minimal-version dependency with a value of '2.4'.

The attribute determines what <license-text> is used if no <license-text> element has a lang attribute whose value matches the locals of LibreOffice. There must always be exactly one <license-text> element with a license-id attribute whose value matches that of the default-license-id. The type is xsd:IDREF.

suppress-on-update Optional. When the attribute is not provided then the value “false” is assumed. The value true indicates that the license for this extension will not be displayed during installation when the same extension (same id but probably different version) is already installed. This applies for the automatic update as well as for manually installing an extension. The version of the already installed extension does not matter. Suppressing the license can be useful during the online update, because otherwise the update operation could be interrupted by many license dialogs.
suppress-if-required Optional. Since 3.3. When the attribute is not provided then the value “false” is assumed. The value “true“ indicates that the license will not be displayed during installation if this is desired, which can be expressed by the unopkg switch --suppress-license and is implicitely assumed for bundled extensions.

See also

Element /description/registration/simple-license/license-text

The element contains information about where to find the file containing the license text, which language it uses, and if this element is the “default” <license-text>. The mechanism for determining a "default" has changed in OOo 2.4. See Documentation/DevGuide/Extensions#Localized XML Elements for details.

Attributes of <license-text>
Attribute Description
xlink:href Required. The value is a relative URL to the license file within the extension.
lang Required. A language identifier according to RFC 3066. See also Documentation/DevGuide/Extensions#Localized XML Elements.
license-id Optional. Since OOo 2.4 this attribute is obsolete. If the extension should work properly (regarding showing the proper license during installation) on versions prior 2.4 one must use this attribute. If one does not use this attribute then one should use the OpenOffice.org-minimal-version dependency with a value of '2.4'.

One license-text element must have this attribute and the value must match the value of the default-license-id attribute of the <simple-license> element. The type is xsd:ID.

See also

Element /description/dependencies

See also

Element /description/dependencies/OpenOffice.org-minimal-version

Attributes of <OpenOffice.org-minimal-version>
Attribute Description
xmlns:dep The namespace for dependency information (inherited from <description>) must be defined as

http://openoffice.org/extensions/description/2006

dep:name Required. Descriptive string that will be displayed. Example : “LibreOffice value” (where value is the value of the attribute value).
dep:OpenOffice.org-minimal-version Optional. This attribute should never be used with this element.
value Required. String. The required underlying software version (“2.1”, “2.2”, etc.), starting with OpenOffice.org 2.1. You should not specify a particular micro version, e.g. “2.2.1”, use “2.2” instead.

See also

Element /description/dependencies/OpenOffice.org-maximal-version

Attributes of <OpenOffice.org-maximal-version>
Attribute Description
xmlns:dep The namespace for dependency information (inherited from <description>) must be defined as

http://openoffice.org/extensions/description/2006

dep:name Required. Descriptive string that will be displayed. Example : “LibreOffice value or older” (where value is the value of the attribute value).
dep:OpenOffice.org-maximal-version Optional. This attribute should always be defined as “2.3” for this element.
value Required. The required maximal underlying OpenOffice.org version (“3.0”, “3.1”, etc.). You must not specify a particular micro version, e.g. “3.1.1”, use “3.1” instead.

See also

Element /description/update-information

<update-information> must have at least one <src> child element. The second, third, etc. element are regarded as fallback, that is, the elements provide URLs to mirrors. The Extension Manager will try to get update information by using a URL and only use a different URL if an error occurred. That is, if for example the first URL references an atom feed that does not contain any references at all, but is a valid feed, then the Extension Manager assumes that there are no update information available. Then URLs from other <src> elements are not examined. Therefore the update information referenced by every URL must be identical.

See also

Element /description/update-information/src

Attributes of <src>
Attribute Description
xlink:href Required. The value is a URL which provides the update information directly or an atom feed which in turn references the update information. The URL can point directly to a file or it may invoke code, such as a servlet, cgi, etc, that returns the atom feed or the update information.

See also

Element /description/publisher

<publisher> must have at least one <name> child element. The children of this element provide a localized publisher name together with a URL.

See also

Element /description/publisher/name

  • Parent: <publisher>
  • Children: Text
  • Since: OpenOffice.org 2.4

The text value of this element is the publisher name.

Attributes of <name>
Attribute Description
xlink:href Required. The value is a URL to the web site of the publisher of the extension.
lang Required. A language identifier according to RFC 3066. See also Documentation/DevGuide/Extensions#Localized XML Elements.

See also

Element /description/release-notes

<release-notes> must have at least one <src> child element. The children provide URLs to localized release-notes.

Currently this element and its children are not used by the Extension Manager.

See also

Element /description/release-notes/src

The element contains a URL to the release notes.

Attributes of <src>
Attribute Description
xlink:href Required. The value is a URL to the release notes of the extension.
lang Required. A language identifier according to RFC 3066. See also Documentation/DevGuide/Extensions#Localized XML Elements.

See also

Element /description/display-name

<display-name> must have at least one <name> child element. The children of this element provide a localized display name. The name will be used in the GUI instead of the file name.

See also

Element /description/display-name/name

The text value of this element is the display name of the extension. The string length must be greater then null and must not only contain white space characters.

Attributes of <name>
Attribute Description
lang Required. A language identifier according to RFC 3066. See also Documentation/DevGuide/Extensions#Localized XML Elements.

See also

Element /description/icon

See also

Element /description/icon/default

  • Parent: <icon>
  • Children: none
  • Since: OpenOffice.org 3.0
Attributes of <default>
Attribute Description
xlink:href The value is a relative URL to the icon file within the extension.

See also

Element /description/icon/high-contrast

  • Parent: <icon>
  • Children: none
  • Since: OpenOffice.org 3.0
Attributes of <high-contrast>
Attribute Description
xlink:href The value is a relative URL to the icon file within the extension.

See also

Element /description/extension-description

Parent: <description>

  • Children:
  • Since: OpenOffice.org 3.1

<extension-description> must have at least on <src> child element. Every child references a localized description. See Documentation/DevGuide/Extensions#Localized XML Elements for details.

See also

Element /description/extension-description/src

The element contains information about where to find the file containing the description text and which language it uses.

Attributes of <src>
Attribute Description
xlink:href Required. The value is a relative URL to the description file within the extension.
lang Required. A language identifier according to RFC 3066. See also Documentation/DevGuide/Extensions#Localized XML Elements.

See also

Localized XML Elements

Some information contained in the description.xml are localized. When the Extension Manager uses them then it picks out the part which matches best the locale of LibreOffice. Localized data is used for

  • License files (simple license, since OpenOffice.org 2.0.4)
  • Publisher name and URL for publisher homepage (since OpenOffice.org 2.4)
  • URL for release notes (since OpenOffice.org 2.4)

Localized data is typically expressed in this format:

<parent>
  <child lang="en-US" />
  <child lang="en-GB" />
</parent>

The children of <parent> have all a lang attribute, which have a language string according to RFC 3066. The following restrictions currently apply:

  • LibreOffice uses only lang-country
  • the language string can only have one variant, for example de-DE-mitte
  • LibreOffice uses uppercase letters for the country. The language strings must also use uppercase letters for the country. That is, the comparison between the LibreOffice's locale and the locale strings of the elements is case-sensitive.

Let's assume that LibreOffice uses British English (en-GB) end the extension has two license text files, one in German (de) and the other in English from New Zealand (en-NZ). Obviously there is no perfect match, since en-GB is not en-NZ. We would then probably prefer en-NZ over de. The algorithm tries to find a locale which is closest to the office's locale. Here is the algorithm:

Input to the algorithm:

  • The <parent> element
  • The locale of the office

Requirements:

  • The <parent> element has at least on child
  • All children have the same element name and namespace
  • All children have a lang attribute

Output of the algorithm:

  • A child element (immediate child) of parent.

Algorithm:

  1. The language, country and variant part of the office's locale are used to find a matching child element. If there is an exact match then the respective child element is selected as output, and we are done. Only the first match is used.
  2. The language and country part of the office's locale are used to find a matching license-text. If there is an exact match then the respective child element is selected as output, and we are done.
  3. The language and country part of the office's locale are used to find a matching child element. This time, we try to match only the language and country parts. For example, the office locale strings “en-US”, “en-US-east” match the lang attribute with the values “en-US-north”, “en-US-south”, etc. The first child element with a matching lang attribute is selected as output. If there is a match then we are done.
  4. Only the language part of the office's locale is used to find a matching child element. If there is an exact match then the respective child element is selected as output, and we are done. Only the first match is used.
  5. Only the language part of the office's locale is used to find a matching child element. This time, we try to match only the language part. For example, the office locale strings “en”, “en-US”, “en-US-east” match the lang attribute with the values “en-GB”,“en-GB-north”, etc. The first child element with a matching lang attributed is selected as output. If there is a match then we are done.
  6. The first child element will be selected as output.

Please note that before OpenOffice.org 2.4 the algorithm for determining the default child element for the <simple-license> element was different. Then the parent element, which was <simple-license> had the attribute default-license-id (type IDREF)and one child, that is a <license-text> element, must have had a license-id (type ID) attribute. Both attributes needed to have the same value. While this was easy to be verified with a schema it was quite cumbersome to write. With OOo 2.4 the first child element is the default in case there was no other child with a matching locale. The old "style" will still be supported but it should not be used anymore for new extensions. It is also recommended using the new "style" when providing a new version of an extension, where the previous version used the old "style".

The following examples show what values would match with a piece of a description.xml. The locale of LibreOffice is assumed to be en-US.

Example 1:

<simple-license accept-by="user" >
  <license-text xlink:href="lic_en" lang="en" />
  <license-text xlink:href="lic_en-GB" lang="en-GB" />
  <license-text xlink:href="lic_en-US" lang="en-US" />
  <license-text xlink:href="lic_en-US-region1" lang="en-US-region1" />
</simple-license>

The <license-text> with lang=”en-US” will be selected. This is a perfect match.

Example 2:

<simple-license accept-by="user" >
  <license-text xlink:href="lic_en" lang="en" />
  <license-text xlink:href="lic_en-GB" lang="en-GB" />
  <license-text xlink:href="lic_en-NZ" lang="en-NZ" />
  <license-text xlink:href="lic_en-US-region1" lang="en-US-region1" />
  <license-text xlink:href="lic_en-US-region2" lang="en-US-region2" />
</simple-license>

The <license-text> with lang="en-US-region1" will be selected. There is no "en-US" and the algorithm looks for any variant of "en-US" and takes the first one it finds.

Example 3:

<simple-license accept-by="user" >
  <license-text xlink:href="lic_en" lang="en" />
  <license-text xlink:href="lic_en-GB" lang="en-GB" />
  <license-text xlink:href="lic_en-NZ" lang="en-NZ" />
  <license-text xlink:href="lic_en-region2" lang="en-region2" />
</simple-license>

The <license-text> with lang="en" will be selected. There is no "en-US" nor the same locale with a variant, for example "en-US-region1". The next step is to compare only the language part. Then there is a match with lang="en".

Example 4:

<simple-license accept-by="user">
  <license-text xlink:href="lic_de" lang="de" />
  <license-text xlink:href="lic_en-GB" lang="en-GB" />
  <license-text xlink:href="lic_en-NZ" lang="en-NZ" />
  <license-text xlink:href="lic_en-region2" lang="en-region2" />
</simple-license>

The <license-text> with lang="en-GB" will be selected. There is no "en-US" nor the same locale with a variant, for example "en-US-region1". There is also no match when using only the language part. Then we look for a value that matches at least the language no matter what country or variant is used. The first locale that matches is "en-GB".

Example 5:

<simple-license accept-by="user" >
  <license-text xlink:href="lic_de" lang="de" />
  <license-text xlink:href="lic_de-DE" lang="de-DE" />
  <license-text xlink:href="lic_fr" lang="fr" />
</simple-license>

The <license-text> with lang="de" will be selected. There is no locale which even has "en" as language, so we just use the first entry as a default.

Example 6:

<simple-license accept-by="user" default-license-id="de" >
 <license-text xlink:href="lic_es" lang="es" />
 <license-text xlink:href="lic_fr" lang="fr" />
 <license-text xlink:href="lic_de" lang="de" license-id="de" />
</simple-license>

The <license-text> with lang="de" will be selected. This example uses the old method of determining the default locale. Starting with OpenOffice.org 2.4 one should not use the default-license-id and license-id anymore.

Example

<?xml version="1.0" encoding="UTF-8"?>
<description xmlns="http://openoffice.org/extensions/description/2006"
xmlns:d="http://openoffice.org/extensions/description/2006"
xmlns:xlink="http://www.w3.org/1999/xlink">

  <version value="1.0" />

  <identifier value="com.mycompany.extensions.my_extension" />

  <platform value="windows_x86,solaris_sparc" />

  <dependencies>
    <OpenOffice.org-minimal-version value="3.3" d:name="OpenOffice.org 3.3"/>
  </dependencies>

  <update-information>
    <src xlink:href="http://extensions.openoffice.org/testarea/desktop/license/update/lic3.update.xml" />
  </update-information>

  <registration>
    <simple-license accept-by="admin" suppress-on-update="true" >
      <license-text xlink:href="registration/license_de-DE.txt" lang="de-DE" />
      <license-text xlink:href="registration/license_en-GB.txt" lang="en-GB" />
      <license-text xlink:href="registration/license_en-NZ.txt" lang="en-NZ" />
      <license-text xlink:href="registration/license_en-US.txt" lang="en-US" />
    </simple-license>
  </registration>

  <publisher>
    <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_de.html"
    lang="de">My OpenOffice de</name>
    <name xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/publisher_en.html"
    lang="en">My OpenOffice en</name>
  </publisher>

  <release-notes>
    <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_de.txt" lang="de" />
    <src xlink:href="http://extensions.openoffice.org/testarea/desktop/publisher/release-notes_en.txt" lang="en" />
  </release-notes>

  <display-name>
    <name lang="de">Meine tolle extensions</name>
    <name lang="en">My great extension</name>
  </display-name>

  <icon>
    <default xlink:href="images/icon.png" />
    <high-contrast xlink:href="images/icon_hc.png" />
  </icon>

  <extension-description>
    <src xlink:href="description/desc_de.txt" lang="de" />
    <src xlink:href="description/desc_en.txt" lang="en" />
  </extension-description>

</description>

This description.xml contains:

Please note that the <publisher> and <release-notes> elements in a description.xml are currently not used by the Extension Manager. However, when uploading the extension to the extensions repository (https://extensions.openoffice.org) then these elements are used for generating update information. When the Extension Manager looks for updates then it displays this information (since OpenOffice.org 2.4).

Display Name

An extension can have a display name. This name is defined in the description.xml. The name is optional. If it is provided then the Extension Manager will show it in the dialog, otherwise the file name will be displayed.

The display name is localized. The description.xml may provide additional names in different languages. The Extension Manager will then choose a language which matches most closely the locale of the office. How this exactly works is explained at Localized XML Elements.

See also

Icon

The Extension Manager displays a default icon for each extension, unless an extension provides its own icon. The icon must be declared in the description.xml.

The icon must be provided as png or jpeg and it should have a size of 42x42 pixels.

See also

Extension Description

The Extension Manager can display a small text for each extension in the dialog. The full text will become visible when the extension is being selected. Originally, this text was displayed in a 'tool tip' in versions of OpenOffice.org previous v3.0.

Versions of LibreOffice, earlier than 3.1, use the "Tooltip Description" of an extension which is kept in the manifest.xml. Using the "Tooltip Description" is deprecated as of OpenOffice.org 3.1 and that functionality will not be maintained. Since then the description is specified in the description.xml. OOo 3.1 and later will first try to get the description from the description.xml. If there is none then it will try to get the tooltip description. That is, extensions can start to use this feature and need not necessarily require OpenOffice.org 3.1. However, developers should be aware that the tooltip description may be removed in the future.

Using a description is similar to the use of a license. One can put a couple of localized description files into the extension and reference them in the description.xml. The description using a locale which matches that of the office will be displayed. See Documentation/DevGuide/Extensions#Localized XML Elements for how the localization works. The description files must contain UTF-8 encoded text.

See also

Extension Identifiers

Extensions now have unique identifiers. This removes the previous restriction that no two extensions with identical file names can be deployed. That is, two extensions with same file name but different identifiers can be installed but two extensions with the same identifiers cannot be installed at the same time.

The identifier plays also an important role for the on-line update. When running the on-line update, then the Extension Manager downloads a list of available extensions from the extension repository or from the location determined by the installed extension. The Extension Manager then tries to find an extension with a newer version in this list by comparing the identifier of the installed extensions with the identifier contained in the update information. Therefore the identifier MUST be unique. This is also the case when two extensions are logically the same but support different platforms. In this case one can achieve this uniqueness by adding a platform string to identifier. The 'Target Platform' section describes how this string can be generated in the build environment.

Technically, an extension identifier is a finite sequence of Unicode scalar values. Identifier identity is element-by-element identity of the sequences (no case folding, no normalization, etc.). It is assumed that extension writers cooperate to keep extension identifiers unique. By convention, use lowercase reversed-domain-name syntax (e.g., org.openoffice.) prefixes to generate unique (but still humanly comprehensible) identifiers. When you write an extension, use the reversed domain name of a site you control (and not org.openoffice.) as prefix. Identifiers starting with the prefix org.openoffice.legacy. are reserved for legacy extensions (see next). The same procedure used to avoid name clashes for extension identifier applies to all unique names in configuration files of extensions (configuration nodes in extendable lists etc.).

The extension identifier is obtained from the description.xml contained in the extension. If the extension does not specify such an explicit identifier, then an implicit identifier is generated by prepending org.openoffice.legacy. to the (obvious sequence of Unicode scalar values representing the) file name of the extension. (Uniqueness of identifiers is then guaranteed by the assumption underlying legacy extension management that no two legacy extensions have the same file name.)

As of OOo 3.3 providing an identifier is necessary for bundled and shared extensions (see Processing Extensions).

See also

Extension Versions

Extensions are often improved over time. That is, publishers want to ship new versions of the same extension with added functionality and/or bug fixes. Adding extension versions allows publishers to ship new versions, and allows LibreOffice to detect and handle the case that an extension installed by the user is an update of an existing extension.

Technically, an extension version v is defined as an infinite sequence of non-negative integers v = ‹v0, v1, ...› where all but a finite number of elements have the value zero. A total order is defined on versions via lexicographical comparison. A textual representation of a version v = ‹v0, v1, ...› is a finite string built from the BNF

 version ::= [element (“.” element)*]
 element ::= (“0” | “1” | “2” | “3” | “4” | “5” | “6” | “7” | “8” | “9”)+

of n ≥ 0 elements where each element is a decimal representation of vi for 0 ≤ i < n, and each vi = 0 for i ≥ n.

The extension version is obtained from the description.xml contained in the extension. If the extension does not specify such an explicit version, then an implicit textual version representation of the empty string (representing a version of all zeroes) is assumed.

No general semantics are prescribed to versions, other than the total order which determines whether one version is less than, equal to, or greater than another version, respectively. However, extension publishers are encouraged to use the widely accepted three-level scheme of major (incompatible changes), minor (compatible changes), micro (bug fixes) where applicable.

As of OOo 3.3 providing a version is necessary for bundled and shared extensions (see Processing Extensions).

See also

Target Platform

[Since OpenOffice.org 3.0] It is possible to determine a target platform for an extension by providing a platform tag in the platform tag in the description.xml.

Please notice that this platform tag is ONLY used during installation in order to see if the user's platform is usable. It is NOT used for identifying an extension. Please read the identifier documentation for details.

If the platform is not present then it is assumed that all platforms are suitable. This corresponds to explicitly specifying all platforms:

<platform value="all" />

The attribute value may contain one to many platform tokens separated by commas:

<platform value="windows_x86,linux_x86,solaris_sparc" />

An extension can only be installed if one of the provided platform tokens matches the user's platform. The special 'all' platform represents all platforms. That is, the extension can be installed everywhere. Whereas

<platform value=" " />

would prevent installation on all platforms.

The tokens need to be defined by LibreOffice. Every token can represent a particular platform constellation. The currently defined tokens are listed in the paragraph Platform Tokens

Do not confuse these tokens with those used to specify a native library in the manifest.xml. We will unify the use of the tokens in one of the future releases.

Platform Tokens

Token Description
all Represents all platforms.
freebsd_x86 FreeBSD operating system running on x86 compatible CPU.
freebsd_x86_64 FreeBSD operating system running on x86 compatible 64bit CPU.
linux_arm_eabi (Not yet supported) Linux operating system running on ARM CPU using 'EABI'.
linux_arm_oabi (Not yet supported) Linux operating system running on ARM CPU using 'OABI'.
linux_ia64 Linux operating system running on an ia64 CPU.
linux_mips_eb (Not yet supported) Linux operating system running on a MIPS CPU using 'EB' ABI.
linux_mips_el (Not yet supported) Linux operating system running on a MIPS CPU using 'EL' ABI.
linux_powerpc Linux operating system running on a POWERPC CPU.
linux_powerpc64 (Not yet supported) Linux operating system running on a POWERPC 64bit CPU.
linux_s390 Linux operating system running on a s390 CPU.
linux_s390x (Not yet supported) Linux operating system running on a s390x CPU.
linux_sparc Linux operating system running on a SPARC CPU.
linux_x86 Linux operating system running on a x86 CPU.
linux_x86_64 Linux operating system running on a x86 64 bit CPU.
macosx_powerpc Mac X operating system running on a POWERPC CPU.
macosx_x86 Mac X operating system running on a x86 CPU.
macosx_x86_64 Mac X operating system running on a x86-64 CPU.
os2_x86 OS/2 operating system running on a x86 CPU.
solaris_sparc Solaris operating system running on a SPARC CPU.
solaris_x86 Solaris operating system running on a x86 CPU.
windows_x86 Windows operating system running on a x86 CPU.

The tokens with the description 'Not yet supported' will be supported at a later time (see issue 88578).

Backward Compatibility of Platforms

Often an LibreOffice built for 32bit platform will run on the respective 64bit platform as well. In this case the platform element would only need the value of the 32bit platform. The reason is, that the extension manager compares the platform of the extension with the platform for which LibreOffice was built. Here are some examples:

Extension platform LibreOffice build for platform Platform where LibreOffice runs Result
Linux_x86 Linux_x86 Linux_x86 ok
Linux_x86 Linux_x86 Linux_x86_64 ok
Linux_x86 Linux_x86_64 Linux_x86_64 fails
Linux_x86_64 Linux_x86 Linux_x86_64 fails

Generating the Platform String in the Build Environment

When it is necessary to generate the description.xml and the platform string during in the build process one can utilize the rtlbootstrap.mk which is contained in the inc folder. rtlbootstrap.mk is build in module sal and contains values for the operating system and processor. For example:

RTL_OS:=Linux
RTL_ARCH:=x86

These values correspond to the values of the bootstrap variable ${_OS} and ${_ARCH}. The file can be included in the makefile which generates the description.xml and use

PLATFORMSTRING = $(RTL_OS:l)_$(RTL_ARCH:l)

to form the string.

See also

Simple License

This feature is about displaying a license text to the user during installation. This is not supported for bundled extensions. Distributors of LibreOffice need to clarify license details with the publishers of extensions, which they want to bundle with LibreOffice.

The user can agree or decline the license, where in the latter case the installation will be aborted. It is called “Simple License” because there is no tamper resistant mechanism that prevents the installation in case the user does not agree to the license. It also does not do anything more than just displaying a license text. The text can be localized (see Documentation/DevGuide/Extensions#Localized XML Elements).

The license text is displayed either in a dialog or in the console dependent on the way the Extension Manager was started. When it was started by the Tools - Extension Manager menu item or by invoking unopkg gui in the console then a dialog is used. By using unopkg add the license text will be displayed in the console and user input has to be done through the same.

The license dialog or the license text in the console is displayed when the extension is being installed. Currently, there are two modes to install extensions, user mode and shared mode. An extension that was installed in user mode (let's call it a user extension) can only be used by just that person who installed it. If the extension was installed in shared mode (let's call it a shared extension), then it can be used by all users. Since the license text is only displayed during installation, all users who are using a shared extension will not see any license text (except the user who installed this shared extension). However, the publisher of the extension may think it necessary that everyone who wants to use it has to agree to the license first. For this purpose, he can mark the extension accordingly. This extension can then only be installed in user mode and not in shared mode. Likewise, the extension can be marked indicating that only the person who installs it needs to agree to the license. Such an extension can be installed in both modes. But when installing in user mode then every user has to agree to the license nonetheless.

As of OOo 3.3 shared extension can also require that every user accepts the license. This happens the first time a user starts LibreOffice after the shared extension was installed. If the user declines the license, then the extension is not usable for this user. It will, however, be displayed in the dialog, where the user can accept the license later. Similarly, a user can accept the license by running unopkg validate. For example

unopkg validate --shared org.openoffice.legacy.ShortLicense.oxt

Here is an example of the description.xml:

<?xml version="1.0" encoding="UTF-8"?>
<description xmlns="http://openoffice.org/extensions/description/2006"
xmlns:xlink="http://www.w3.org/1999/xlink">
<registration>
    <simple-license accept-by="user" >
    <license-text xlink:href="registration/license_de.txt" lang="de" />
    <license-text xlink:href="registration/license_en_US.txt" lang="en-US" />
    </simple-license>
</registration>
</description>

In this example, the license would have to be agreed to by all users (that means no shared mode installation). This is indicated by the value user of the attribute accept-by in the <simple-license> element. The attribute could also have the value admin, which would indicate that the license needs only be agreed to by the person who installs it.

The <license-text> elements contain information about the files which contain the text that is displayed. The content of these files must be UTF-8 encoded. It is displayed exactly as it is in the file. That is, no formatting occurs. There can be one to many <license-text> elements, where each element provides information about a different language of the license text. The attribute xlink:href contains a relative URL (relative to the root directory of the extension) which points to a file which contains the license text in exactly one language. Which language is indicated by lang attribute.

If the package manager does not find a <license-text> element which matches the locale of LibreOffice then it will pick the first <license-text> element as the default. Prior to OpenOffice.org 2.4, the default entry needed to marked explicitly by license-id attribute.

Important Issues

  • (Obsolete as of OpenOffice.org 3.3) Using the simple license in extensions always requires user interaction. Using accept-by="user" in the <license-text> prevents this extension from being installed centrally, that is as shared extensions. Both issues can prevent the extension from being used by companies, which centrally maintain software.
  • (Obsolete as of OpenOffice.org 3.3. Feature is not supported, but installation will not hang when using extensions with a license) The 'simple license' does not work for bundled extensions (installation will probably hang as user input is required). If one wants to build an LibreOffice installation set with bundled extensions, then they must not use this feature.

To get around these limitations, extensions need to provide the attribute suppress-if-required="true".

See also

Dependencies

One can imagine a large variety of dependencies an extension can have on its environment: availability of certain UNO types and services, availability of features only present since some specific version of LibreOffice, availability of other installed extensions, availability of third-party libraries, etc.

To support this, a mechanism is introduced so that extensions can bring along a specification of their dependencies. When a user wants to install an extension, the application first checks whether all dependencies are met. If not, an error dialog is displayed informing the user that the extension could not be installed.

The only actual dependencies currently defined are <OpenOffice.org-minimal-version value="X">, where X is the required underlying OpenOffice.org version (“2.1”, “2.2”, etc.), starting with OpenOffice.org 2.1; and a corresponding, rarely needed <OpenOffice.org-maximal-version value="X">, introduced in OpenOffice.org 3.1. (Even if an extension is installed in a derived product like StarOffice, these dependencies are on the underlying LibreOffice version.)

OpenOffice.org 2.0.3 and earlier are not prepared to correctly handle extensions with dependencies. In OpenOffice.org 2.0.3 and earlier, if a .uno.pkg (or .zip) extension specifies any dependencies, they are effectively ignored and the extension is installed nonetheless. An .oxt extension cannot be installed at all in OpenOffice.org 2.0.3 and earlier. So, if an extension shall run in any LibreOffice version, it should be named .uno.pkg and should not specify any dependencies; if an extension shall only run in OpenOffice.org 2.0.4 and later, it should be named .oxt and should not specify any dependencies; and if an extension shall only run in a future OpenOffice.org version, it should be named .oxt and should specify the appropriate dependencies (which will be defined by the time the given LibreOffice version is available).

There is a certain dilemma: On the one hand, nothing is yet known about the kinds of dependencies that will be defined in the future. On the other hand, at least some information about the unsatisfied dependencies of a future extension must be displayed in OpenOffice.org 2.0.4. Therefore, each dependency specified by an extension must contain a human-readable (non-localized, English) name that can be displayed to the user, conveying at least rudimentary information about the nature of the unsatisfied dependency. Future versions of OpenOffice.org that already know a certain kind of dependency are expected to display more detailed information.

Likewise, when new dependencies are defined over time, old versions of LibreOffice will not know about them. Those old versions will thus reject extensions making use of those dependencies, even if the old version would actually satisfy the dependencies. Therefore, each dependency specified by an extension may optionally contain an OpenOffice.org-minimal-version attribute that specifies the minimal version of LibreOffice that would satisfy the dependency. Old versions of LibreOffice that do not know the given dependency will then check for the optional attribute and, if present, nevertheless accept the dependency if the given version is large enough. This feature is only supported since OpenOffice.org 2.3.

Within the description.xml, dependencies are recorded as follows: An XML element whose name consists of the namespace name http://openoffice.org/extensions/description/2006 and the local part dependencies may appear at most once as a child of the root element. This element has as its element content an arbitrary number of child elements that are not further constrained except for the following: Each such child element should have an attribute whose name consists of the namespace name http://openoffice.org/extensions/description/2006 and the local part name, and it may optionally have an attribute whose name consists of the namespace http://openoffice.org/extensions/description/2006 and the local part OpenOffice.org-minimal-version. Each such child element represents one dependency, and the value of its name attribute shall contain the human-readable dependency name (and the value, after normalization, should not be empty).

If an extension is either not of type .oxt, .uno.pkg, or .zip, or does not contain a description.xml, or the description.xml does not contain a dependencies' element, or the dependencies' element does not contain any child elements, then the extension does not specify any dependencies.

See also

Publisher Information

The publisher information consists of a name and a URL. The name should be a company name and the URL should point to a website of that company. This information is currently used in two places, the Extension Manager dialog (since OpenOffice.org 3.0) and the update dialog for extensions (since OpenOffice.org 2.4).

The publisher information is specified in the description.xml of an extension. The extension repository will use the information in the update information. That is, if someone checks for extension updates, than the update dialog will show this publisher information for this extension.

See also

System Integration

When installing LibreOffice, the installation routine is adding information to the system which can be used by other software products to install extensions. For example, double-clicking on an extension in a file browser should start the Extension Manager and install the extension. Also mail clients and web browser should offer a way of installing the extension, when it comes as an attachment of an e-mail or is the target of a link.

Extension which are installed by way of using the system integration are always installed as user extensions.

The system integration is available since OpenOffice.org 2.2.0.

Online Update of Extensions

Extensions are often improved over a period of time. That is, publishers ship new versions of the same extension with added functionality and/or bug fixes. Currently users must update their extensions manually, that is, find out where to get updates, obtain the updates, remove the old extensions, install the new extension. This feature will make updating easier. Users can run the update mechanism from the Extension Manager. A dialog will show available updates and the user will be able to choose which to install.

More particular information for this feature can be found in the specification at:

http://specs.openoffice.org/appwide/packagemanager/online_update_for_extensions.odt

Currently the update mechanism completely replaces an installed extension. That is, the update is actually a complete new extension which could also be installed separately without replacing an earlier version of this extension.

See also

Running Online - Update

The update procedure needs to be started by the user in the Extension Manager. One can update all installed extensions by pressing the Updates button or select particular extensions, press the right mouse button and select Update in the context menu. The extension manager will then try to obtain update information for the affected extension. If it finds that a new version of an extension is available then it will be displayed in the update window.

In some cases an update cannot be installed, for example because the installed extension is shared by all users and the current user does not have permission to manage shared extensions. In this case a message to this regard is displayed in the window. To update shared extensions one needs to close LibreOffice and run unopkg gui. Then the user has access to all extensions.

An extension may also not be installable, because it has unfulfilled dependencies. For example, the extensions requires a particular version of LibreOffice.

The user can determine which of the updates he wants to install by checking them. When the Download and Installation button is pressed then, as the name suggests, the extensions are being downloaded and installed.

Concept

The actual download location of an update is contained in the update information which is typically a xml file which is hosted on a server. Every update information contains only information for exactly one extension. The most important information are the location of the update and the version of this extension.

The Extension Manager needs to get hold of the update information in order to decide if the respective extension is a valid update. For example, it only makes sense to take a version into account that is greater than the version of the already installed extension. The information where the update information is located is contained in the description.xml of each extension. In particular the children of the <update-information> element, contain URLs which reference the update information. The Extension Manager uses these URLs to download the update information and later uses the information in the update information to download the respective extension.

In case that an extension does not contain a description.xml or the description.xml does not contain the <update-information> element, the Extension Manager uses a default location to get update information. This location is build-in, and is therefore determined by the publisher of LibreOffice. Currently, this information is contained in the version.(ini|rc) of the office installation.

One piece of update information only contains the information for exactly one extension. To bundle several update information one can use an XML atom feed. For example a feed could reference multiple update information, which refer all to an extension with the same Id but have different versions. It could also contain references to update information of distinct extensions (different Id). Then the Extensions Manager will pick out the information it needs.

Not only the built-in URL can reference an atom feed but also every extension.

To find a suitable update, the Extension Manager, gathers in a first step all pieces of update information which have the same identifier as one of the installed extensions. Then it determines the latest version for an extension from those update information. If the latest version is greater than the version of the corresponding installed extension, then the respective extension is offered as an update.

Example Scenario for Providing Updates

Using an Atom Feed

By using an atom feed one has greater flexibility in terms of where the actual updates are hosted. For example, a company which has published many extensions, may utilize just one atom feed which are referenced by all extensions. The location of this atom feed must be well chosen, because changing it may break the automatic update. Then the Extension Manager cannot obtain update information for these extensions anymore. For this reason, the company could set up a dedicated server which is guaranteed to be available in the foreseeable future. The actual extensions can then be hosted on different servers. The atom feed file needs only be edited if an update information file is moved to a different place, or when update information for new extensions become available.

If no actual update is available, for example, there is just version 1.0, then the update information could still refer to this extension. This does not do any harm because the Extension Manager compares the version number of the installed extension and the version which is contained in the update information, in order to display only real updates.

The location of the extension used as update could be the same as the location where customers download the extension for the first time. For example, there could be a web site which contains links to extensions. Let's assume one link is:

 http://mycomp/extension.oxt

The extension references a feed at:

 http://openoffice.org/extensions/updatefeed.xml

The feed contains the reference to the update information:

 http://mycomp/updates/extension.update.xml

and this file refers to the update which is again:

 http://mycomp/extension.oxt

If now version 2.0 of the extension becomes available, then the publisher could simply replace the extension at http://mycomp/extension.oxt and change the update information so that it reflects the new version. This way, users download always the latest version from the website and the Extension Manager can use this extension as update for an older version which is already installed.

Migration of Update Information

It could become necessary to change the server which hosts the update feed or update information. If this results in a different URL for these files, then the automatic update will not work. Therefore the following procedure is recommended.

  1. Plan for a transition period, that is long enough for most users to get a new update.
  2. Set up the new server, or the locations for hosting the update information, and run both servers in parallel. That is, the same update information and updates should be available from both servers.
  3. Prepare new versions for extensions that contain an URL to the new server.
  4. Switch off the old server after the transition period. Users, which have obtained the update, will be able to use the update mechanism as before. All other users will not be able to get an update anymore.

Description of the Update Information

The update information can be contained in a file which can be directly accessed through a URL or it can be generated on demand (HTTP get request). If it is a file then it could be named according to this pattern:

 <extension_file_name>.update.xml

For example, the update information file for the extension myextension.oxt is myextension.update.xml. The .oxt file extension is not used.

It follows the description of the XML structure of the update information data:

Element /description

<description> is the root element of the update information XML document.

Attribute <description>
Attribute Description
xmlns The default namespace of element description and all children must be defined as

https://openoffice.org/extensions/update/2006

xmlns:dep The namespace for dependency information must be defined as

https://openoffice.org/extensions/description/2006

xmlns:xlink The xlink namespace must be defined as

https://www.w3.org/1999/xlink

other namespace definitions Other namespaces can be added as necessary.

Element /description/identifier

Attributes of <identifier>
Attribute Description
value Required. The extension identifier.

Element /description/version

Attributes of <version>
Attribute Description
value Required. A textual representation of the extension version.

Element /description/update-download

<update-download> must have at least one <src> child element. If <update-download> is provided then there must not be a <update-website> element.

The second, third, etc. <src> element are regarded as fallback, that is, the elements provide URLs to mirrors. The Extension Manager will try to download the extension by using the first URL and only uses the next URL if an error occurred, for example because the the connection was interrupted.

Example:

<update-download>
  <src xlink:href="http://my.company.com/product/download/extension.oxt" />
  <src xlink:href="http://my.company.com/product/download-mirror/extension.oxt" />
</update-download>

Element /description/update-download/src

Attributes of <src>
Attribute Description
xlink:href Required. The value is a URL which refers to the extension

Element /description/update-website

<update-website> must have at least one <src> child element. If <update-website> is provided then there must not be a <update-download> element.

The children of this element provide a URL to the web site where the new version of the extension can be downloaded. There can be multiple localized web sites with different URLs. Each URL can be provided by a separate <src> element. See Localized XML Elements about how the Extension Manager chooses the proper <src> element.

Example:

<update-website>
  <src xlink:href="http://my.company.com/product/download_de.html" lang="de" />
  <src xlink:href="http://my.company.com/product/download_en.html" lang="en" />
  <src xlink:href="http://my.company.com/product/download_de-DE.html" lang="de-DE" />
</update-website>

Element /description/update-website/src

Attributes of <src>
Attribute Description
xlink:href Required. The value is a URL to the web site of the publisher of the extension.
lang Required. A language identifier according to RFC 3066. See also Documentation/DevGuide/Extensions#Localized XML Elements.

Element /description/dependencies

Example:

<dependencies xmlns:dep="http://openoffice.org/extensions/description/2006">
  <dep:OpenOffice.org-minimal-version value="2.1" dep:name="OpenOffice.org 2.1"/>
</dependencies>

Element /description/dependencies/OpenOffice.org-minimal-version

The namespace of this element must be http://openoffice.org/extensions/description/2006.

Attributes of <OpenOffice.org-minimal-version>
Attribute Description
xmlns:dep The namespace for dependency information (inherited from <description>) must be defined as

http://openoffice.org/extensions/description/2006

dep:name Required. The string “OpenOffice value” (where value is the value of the attribute value).
value Required. The required underlying OpenOffice version (“2.1”, “2.2”, etc.), starting with OpenOffice.org 2.1.

Element /description/dependencies/OpenOffice.org-maximal-version

The namespace of this element must be http://openoffice.org/extensions/description/2006.

Attributes of <OpenOffice.org-maximal-version>
Attribute Description
xmlns:dep The namespace for dependency information (inherited from <description>) must be defined as

http://openoffice.org/extensions/description/2006

dep:name Required. The string “OpenOffice. value or older” (where value is the value of the attribute value).
value Required. The required maximal underlying OpenOffice version (“3.0”, “3.1”, etc.).

Element /description/publisher

The children of this element provide a localized publisher name together with a URL. <publisher> must have at least one <name> child element.

The publisher name is displayed in the update dialog of the Extension Manager. Clicking on the name causes the web browser to be opened which navigates to the web site determined by the <name> element.

Example:

<publisher>
  <name xlink:href="http://my.company.com/product/download_de.html" lang="de">Meine Firma</name>
  <name xlink:href="http://my.company.com/product/download_en.html" lang="en">My Company</name>
  <name xlink:href="http://my.company.com/produc/download_de-DE.html" lang="de-DE">Meine Firma</name>
</publisher>

Element /description/publisher/name

The text value of this element is the publisher name.

Attributes of <name>
Attribute Description
xlink:href Required. The value is a URL to the web site of the publisher of the extension.
lang Required. A language identifier according to RFC 3066. See also Documentation/DevGuide/Extensions#Localized XML Elements.

Element /description/release-notes

The children of this element provide URLs to localized release-notes. <release-notes> must have at least one <src> child element.

The update dialog of the Extension Manager shows a link to the release notes if this element is provided. Clicking on it will cause a web browser to be opened which navigates to the release notes page using the URL provided by the respective <src> element.

Example:

<release-notes>
  <src xlink:href="http://my.company.com/product/release-notes_de.txt" lang="de" />
  <src xlink:href="http://my.company.com/product/release-notes_en.txt" lang="en" />
</release-notes>

Element /description/release-notes/src

The element contains a URL to the release notes.

Attributes of <src>
Attribute Description
xlink:href Required. The value is a URL to the release notes of the extension.
lang Required. A language identifier according to RFC 3066. See also Documentation/DevGuide/Extensions#Localized XML Elements.

Description of Atom Feed

The atom feed is described at: Update Notification Protocol

Examples

description.xml Containing Direct Reference to the Update Information

The following content of a description.xml directly references update information:

<?xml version="1.0" encoding="UTF-8"?>
<description xmlns="https://openoffice.org/extensions/description/2006"
  xmlns:xlink="https://www.w3.org/1999/xlink">
  <version value="1.0" />

  <update-information>
    <src xlink:href="http://my.company.com/update/extension.update.xml" />
    <src xlink:href="http://my.company-mirror.com/update/extension.update.xml" />
  </update-information>
</description>

The second src element contains a URL to a mirror which will be used by the Extension Manager if the location referenced by the URL in the first src element cannot be reached.

This is the content of extension.update.xml:

<?xml version="1.0" encoding="UTF-8"?>
<description xmlns="https://openoffice.org/extensions/update/2006"
  xmlns:xlink="https://www.w3.org/1999/xlink">
  <identifier value="org.openoffice.legacy.extension.oxt"/>
  <version value="2.0" />
  <update-download>
    <src xlink:href="http://my.company.com/update/extension.oxt" />
  </update-download>
</description>

The src element contains a URL to version 2.0 of extension.oxt. extension.oxt has the identifier org.openoffice.legacy.extension.oxt because it does not define an identifier in its description.xml. Otherwise the identifier would be the same as the one in the description.xml.

description.xml OpenOffice.org 3.x update information example

The following content of a description.xml directly references update information for a German Windows OpenOffice.org 3.3.0:

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="https://www.w3.org/2005/Atom" xml:lang="en-US">
<title>Product Update Feed</title>
<link rel="alternate" type="text/html" href="http://update.services.openoffice.org/ooo/snapshot.html"/>
<updated>2011-01-01T08:00:02Z</updated>
<author>
        <name>The OpenOffice Project</name>
        <uri>https://openoffice.org</uri>
        <email>updatefeed@openoffice.org</email>
</author>
<id>urn:uuid:76676544</id>
<entry>
        <title>Update available</title>
        <link rel="alternate" type="text/html" href="http://update.services.openoffice.org/ooo/snapshot.html"/>
        <id>urn:uuid:76676544</id>
        <!-- IMPORTANT: category MUST be equal with UpdateID in version.ini -->
        <category term="OpenOffice.org_3_de" label="OpenOffice.org update"/>
        <updated>2011-01-01T08:00:02Z</updated>
        <summary>Text below download information (should be localized)</summary>
        <content type="application/xml">
                <inst:description xmlns:inst="http://installation.openoffice.org/description">
                        <inst:id>OpenOffice.org_3_de</inst:id>
                        <inst:version>'new version information'</inst:version>
                        <!-- buildid must be higher as installed version -->
                        <inst:buildid>9580</inst:buildid>
                        <inst:os>Windows</inst:os>
                        <inst:arch>x86</inst:arch>
                        <!-- app/octet-stream generates direct download as background-process -->
                        <!--<inst:update type="application/octet-stream" src="http://download.services.openoffice.org/files/localized/de/3.3.0/OOo_3.3.0_Win_x86_install-wJRE_de.exe"/>-->
                        <!-- text/html opens src-link in default browser -->
                        <inst:update type="text/html" src="http://download.openoffice.org"/>
                </inst:description>
        </content>
</entry>
</feed>

Using the Atom Feed

This is the content of the description.xml of feed1.oxt which references an atom feed:

<?xml version="1.0" encoding="UTF-8"?>
<description xmlns="https://openoffice.org/extensions/description/2006"
  xmlns:xlink="http://www.w3.org/1999/xlink">
  <version value="1.0" />
  <update-information>
    <src xlink:href="http://my.company.com/update/feed1.xml" />
  </update-information>
</description>

The feed:

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="https://www.w3.org/2005/Atom" xml:lang="en-US">

  <title>Extensions Update Feed</title>
  <link rel="alternate" type="text/html" href="http://update.services.openoffice.org/ooo/snapshot.html"/>
  <updated>2006-11-06T18:30:02Z</updated>
  <author>
    <name>The OpenOffice.org Project</name>
    <uri>https://openoffice.org</uri>
    <email>updatefeed@openoffice.org</email>
  </author>
  <id>urn:uuid:a4ccd383-1dd1-11b2-a95c-0003ba566e9d</id>
  <entry>
    <title>feed1.oxt version 2.0 available</title>
    <link rel="alternate" type="text/html"
      href="https://extensions.openoffice.org"/>
    <id>urn:uuid:a4ccd383-1dd1-11b2-a95c-0003ba566e9f</id>
    <category term="org.openoffice.legacy.feed1.oxt" label="feed1.oxt" />
    <updated>2006-11-06T18:30:02Z</updated>
    <summary>Click here to go to the download page.</summary>
    <content type="application/xml" src="http://extensions.openoffice.org/testarea/desktop/updatefeed/update/feed1.update.xml" />
  </entry>
  <entry>
    <title>feed2.oxt version 2.0 available</title>
    <link rel="alternate" type="text/html"
      href="https://extensions.openoffice.org"/>
    <id>urn:uuid:a4ccd383-1dd1-11b2-a95c-0003ba566eaf</id>
    <category term="org.openoffice.legacy.feed2.oxt" label="feed2.oxt" />
    <updated>2006-11-06T18:30:02Z</updated>
    <summary>Click here to go to the download page.</summary>
    <content type="application/xml" src="https://extensions.openoffice.org/testarea/desktop/updatefeed/update/feed2.update.xml" />
  </entry>
</feed>

The feed contains two entry elements and each references the update information for a different extensions. It could, however, also reference the update information for two different versions of the same extension.

The update information for the version of feed1.oxt:

<?xml version="1.0" encoding="UTF-8"?>
<description xmlns="https://openoffice.org/extensions/update/2006"
  xmlns:xlink="https://www.w3.org/1999/xlink">
  <identifier value="org.openoffice.legacy.feed1.oxt"/>
  <version value="2.0" />
  <update-download>
    <src xlink:href="https://extensions.openoffice.org/testarea/desktop/updatefeed/update/feed1.oxt" />
  </update-download>
</description>

Options Dialog

Extensions can add options pages to LibreOffice's options dialog. They should not create their own menu or menu entries for the sole purpose of configuring the extension. This is what the options dialog is for.

It is also possible to start an options dialog from within the Extension Manager on behalf of a particular extension. An options page represents a child window that is displayed within the options dialog. An extension can provide multiple options pages. It can determine that they can be added to already existing nodes, such as “OpenOffice Writer” or “Internet Settings”. It is also possible to create completely new nodes.

The specification for this feature can be found at: http://specs.openoffice.org/appwide/packagemanager/options_dialog_for_extensions.odt

In the following paragraphs we will show what has to be done in order to add options pages to an extension. Along the way we will go into some details where necessary. It is assumed that the reader has already knowledge about extension programming and that he or she knows how the LibreOffice's registry (including xcs and xcu files) works.

A note about writing some terms. When we refer to elements from the configuration schema of LibreOffice then we use the respective uppercase names, for example Node, Module. The plural will expressed by adding a pipe symbol and the respective postfix, for example Nodes, Modules.

Available samples

Creating the GUI of the Options Page

The GUI of an options page needs to be created by the dialog editor of LibreOffice. Exporting the dialog will result in saving a .xdl file and perhaps multiple .properties files. The xdl file contains the description of the dialog in XML whereas the properties files contain localized strings. For example, if the dialog is named Dialog1 and it contains strings which are localized for German and US – English, then you will obtain these files:

  • Dialog1.xdl
  • Dialog1_de_DE.properties
  • Dialog1_en-US.properties

Please make sure that you have set the property “With title bar” to “no” for the whole dialog. Note that the maximum size that the dialog may have in the options page is a width of 260 and a height of 185.

The exported files can be anywhere in the extensions, except in META-INF. They must also be in the same directory.

The options dialog will use the service <idl>com.sun.star.awt.ContainerWindowProvider</idl> to create the options pages. The service constructors takes an URL to the xdl file and an an event handler component. The latter will be used to process events which have been defined in the dialog editor for particular controls. It is also used for saving and loading the data of the controls which are on the options pages.

Saving and Reading Data for the Options Page

An options page typically allows the user to enter some data, which of course must be saved when the user presses the OK button. When the options page is displayed it should show the data which the user entered previously. In case nothing has ever been entered, the options page could show some “default” data or nothing at all.

How the data is saved and where it is stored is not covered by the specification. It only defines the events “ok”, “initialize”, and “back” which the extension needs to process in order to save the entered data, initialize the controls with data, or restore the state of the controls with the previously saved data. The “ok” and “back” events are triggered by the “OK” and “Back” button of the options dialog. “initialize” is called before the options page is being displayed. In most cases “initialize” and “back” have the same meaning.

In order to receive these events one has to provide a service that implements the interface <idl>com.sun.star.awt.XContainerWindowEventHandler</idl>. The component is then installed like any other component. That is, one provides for example a jar file or a dll and adds the proper entries to the manifest.xml.

The action events are processed in the <idlm>com.sun.star.awt.XContainerWindowEventHandler:callHandlerMethod</idlm>. This method takes three parameters. The first is a <idls>com.sun.star.awt.XWindow</idls> which represents the dialog for which the event is called. The second is an com.sun.star.uno.Any, which describes the actual event. Therefore the IDL calls it the “EventObject”. The last parameter is a string which contains a “method name”. This method may not exists, but the name identifies an action which should be invoked.

In case of our previously mentioned events the method is called with the respective <idls>com.sun.star.awt.XWindow</idls> interface, a method name of “external_event”, and an any containing either “ok”, “back”, or “initialize”. For example, the java code could look like this:

public boolean callHandlerMethod(com.sun.star.awt.XWindow aWindow,
  Object aEventObject, String sMethod)
  throws WrappedTargetException {
  if (sMethod.equals("external_event") ) {
    try {
      return handleExternalEvent(aWindow, aEventObject);
    } catch (com.sun.star.uno.RuntimeException re) {
    throw re;
    } catch (com.sun.star.uno.Exception e) {
      throw new WrappedTargetException(sMethod, this, e);
    }
  } else if (sMethod.equals("another_method_name") ) {
  ...
  }

  return false;
}

private boolean handleExternalEvent(com.sun.star.awt.XWindow aWindow, Object aEventObject)
  throws com.sun.star.uno.Exception {
  try {
    String sMethod = AnyConverter.toString(aEventObject);
    if (sMethod.equals("ok")) {
      saveData(aWindow);
    } else if (sMethod.equals("back") || sMethod.equals("initialize")) {
      loadData(aWindow);
    }
  } catch (com.sun.star.lang.IllegalArgumentException e) {
    throw new com.sun.star.lang.IllegalArgumentException(
      "Method external_event requires a string in the event object argument.", this,
      (short) -1);
  }
}

The method saveData and loadData need to be implemented according to where the data actually is stored. In most cases the LibreOffice's registry is a suitable place. Then, of course, one needs to provide a configuration schema (requires an appropriate entry in the manifest.xml as well).

For example:

<?xml version="1.0" encoding="UTF-8"?>
<oor:component-schema xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" oor:name="ExtensionData" oor:package="org.openoffice.desktop.deployment.options" xml:lang="en-US">
  <info>
    <author></author>
    <desc>Contains the options data used for the test extensions.</desc>
  </info>
  <templates>
    <group oor:name="Leaf">
      <info>
        <desc>The data for one leaf.</desc>
      </info>
      <prop oor:name="String0" oor:type="xs:string">
        <value></value>
      </prop>
      <prop oor:name="String1" oor:type="xs:string">
        <value></value>
      </prop>
      <prop oor:name="String2" oor:type="xs:string">
        <value></value>
      </prop>
      <prop oor:name="String3" oor:type="xs:string">
        <value></value>
      </prop>
      <prop oor:name="String4" oor:type="xs:string">
        <value></value>
      </prop>
    </group>
  </templates>
  <component>
    <group oor:name="Leaves">
      <node-ref oor:name="Writer1" oor:node-type="Leaf" />
      <node-ref oor:name="Writer2" oor:node-type="Leaf" />
      <!-- .... -->
    </group>
  </component>
</oor:component-schema>

Please make sure that the package (oor:package) together with the name (oor:name) for this schema are unique. For example, it should start with YOUR reversed domain name (do not use org.openoffice in your code), followed by the product name and other values which together uniquely identify this registry node.

In the example I have defined a group “Leaves”, which contains several entries and which are all of the same type. Each entry holds the data for one options page. In this case, each options page may provide five different strings.

If a new version of the extension uses the same schema, then data, which have been entered by a user for the previous version, will be automatically applied for the new version. If this is not wanted then one need to provide a new schema. In our case we could just change the attribute oor:component-schema@ oor:name to a value, for example, ExtensionData2.

Now the question is, how one can access the controls on the options page in order to set the data or read from them. The following code example shows the whole service as Java implementation. Please have look at the loadData and saveData method. Please be aware that is is only an example and may need to be adapted to personal needs.

package com.sun.star.comp.extensionoptions;

import com.sun.star.lib.uno.helper.Factory;
import com.sun.star.lib.uno.helper.WeakBase;
import com.sun.star.lang.XMultiComponentFactory;
import com.sun.star.lang.XSingleComponentFactory;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.lang.WrappedTargetException;
import com.sun.star.lang.IllegalArgumentException;
import com.sun.star.lang.XInitialization;
import com.sun.star.lang.XTypeProvider;
import com.sun.star.lang.XServiceInfo;
import com.sun.star.lang.WrappedTargetException;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.Any;
import com.sun.star.uno.AnyConverter;
import com.sun.star.uno.XComponentContext;
import com.sun.star.uno.Exception;
import com.sun.star.registry.XRegistryKey;
import com.sun.star.awt.XContainerWindowEventHandler;
import com.sun.star.awt.XControl;
import com.sun.star.awt.XControlModel;
import com.sun.star.awt.XControlContainer;
import com.sun.star.container.XNameAccess;
import com.sun.star.container.NoSuchElementException;
import com.sun.star.beans.PropertyValue;
import com.sun.star.beans.PropertyState;
import com.sun.star.beans.XPropertySet;
import com.sun.star.beans.UnknownPropertyException;
import com.sun.star.beans.PropertyVetoException;
import com.sun.star.util.XChangesBatch;

/** A handler which supports multiple options pages which all
* have the same controls.
*/
public class OptionsEventHandler {

public static class _OptionsEventHandler extends WeakBase
implements XServiceInfo, XContainerWindowEventHandler {

static private final String __serviceName =
"com.sun.star.comp.extensionoptions.OptionsEventHandler";

private XComponentContext m_cmpCtx;

private XMultiComponentFactory m_xMCF;

private XNameAccess m_xAccessLeaves;

/**Names of supported options pages.
*/
private String[] m_arWindowNames = {
"Writer1", "Writer2", "Writer3", "Calc1", "Calc2", "Calc3",
"Draw1", "Draw2", "Draw3", "Node1_1", "Node1_2", "Node1_3",
"Node2_1", "Node2_2", "Node2_3", "Node3_1", "Node3_2", "Node3_3"};

/**Names of the controls which are supported by this handler. All these
*controls must have a "Text" property.
*/
private String[] m_arStringControls = {
"String0", "String1", "String2", "String3", "String4"};

public _OptionsEventHandler(XComponentContext xCompContext) {
  m_cmpCtx = xCompContext;
  m_xMCF = m_cmpCtx.getServiceManager();

  //Create the com.sun.star.configuration.ConfigurationUpdateAccess
  //for the registry node which contains the data for our option
  //pages.
  XMultiServiceFactory xConfig;
  try {
    xConfig = (XMultiServiceFactory) UnoRuntime.queryInterface(
      XMultiServiceFactory.class,
      m_cmpCtx.getServiceManager().createInstanceWithContext(
        "com.sun.star.configuration.ConfigurationProvider", m_cmpCtx));
  } catch (com.sun.star.uno.Exception e) {
    e.printStackTrace();
    return;
  }

  //One argument for creating the ConfigurationUpdateAccess is the "nodepath".
  //Our nodepath point to the node of which the direct subnodes represent the
  //different options pages.
  Object[] args = new Object[1];
  args[0] = new PropertyValue(
    "nodepath", 0, "/org.openoffice.desktop.deployment.options.ExtensionData/Leaves",
    PropertyState.DIRECT_VALUE);

  //We get the com.sun.star.container.XNameAccess from the instance of
  //ConfigurationUpdateAccess and save it for later use.
  try {
    m_xAccessLeaves = (XNameAccess) UnoRuntime.queryInterface(
      XNameAccess.class, xConfig.createInstanceWithArguments(
        "com.sun.star.configuration.ConfigurationUpdateAccess", args));

  } catch (com.sun.star.uno.Exception e) {
    e.printStackTrace();
    return;
  }
}

/** This method returns an array of all supported service names.
* @return Array of supported service names.
*/
public String[] getSupportedServiceNames() {
  return getServiceNames();
}

/** This method is a simple helper function to used in the
* static component initialisation functions as well as in
* getSupportedServiceNames.
*/
public static String[] getServiceNames() {
  String[] sSupportedServiceNames = { __serviceName };
  return sSupportedServiceNames;
}

/** This method returns true, if the given service will be
* supported by the component.
* @param sServiceName Service name.
* @return True, if the given service name will be supported.
*/
public boolean supportsService( String sServiceName ) {
  return sServiceName.equals( __serviceName );
}

/** Return the class name of the component.
* @return Class name of the component.
*/
public String getImplementationName() {
  return _OptionsEventHandler.class.getName();
}

//XContainerWindowEventHandler
public boolean callHandlerMethod(com.sun.star.awt.XWindow aWindow,
  Object aEventObject, String sMethod)
  throws WrappedTargetException {
  if (sMethod.equals("external_event") ) {
    try {
      return handleExternalEvent(aWindow, aEventObject);
    } catch (com.sun.star.uno.RuntimeException re) {
      throw re;
    } catch (com.sun.star.uno.Exception e) {
      throw new WrappedTargetException(sMethod, this, e);
    }
  }

  // return false when event was not handled
  return false;
}

//XContainerWindowEventHandler
public String[] getSupportedMethodNames() {
  return new String[] {"external_event"};
}

private boolean handleExternalEvent(com.sun.star.awt.XWindow aWindow, Object aEventObject)
  throws com.sun.star.uno.Exception {
  try {
    String sMethod = AnyConverter.toString(aEventObject);
    if (sMethod.equals("ok")) {
      saveData(aWindow);
    } else if (sMethod.equals("back") || sMethod.equals("initialize")) {
      loadData(aWindow);
    }
    } catch (com.sun.star.lang.IllegalArgumentException e) {
      throw new com.sun.star.lang.IllegalArgumentException(
        "Method external_event requires a string in the event object argument.",
        this, (short) -1);
    }
  return true;
}

private void saveData(com.sun.star.awt.XWindow aWindow)
  throws com.sun.star.lang.IllegalArgumentException, com.sun.star.uno.Exception {

  //Determine the name of the options page. This serves two purposes. First, if this
  //options page is supported by this handler and second we use the name two locate
  //the corresponding data in the registry.
  String sWindowName = getWindowName(aWindow);
  if (sWindowName == null)
    throw new com.sun.star.lang.IllegalArgumentException(
      "This window is not supported by this handler", this, (short) -1);

  //To access the separate controls of the window we need to obtain the
  //XControlContainer from the window implementation
  XControlContainer xContainer = (XControlContainer) UnoRuntime.queryInterface(
    XControlContainer.class, aWindow);
  if (xContainer == null)
    throw new com.sun.star.uno.Exception(
      "Could not get XControlContainer from window.", this);

  //This is an implementation which will be used for several options pages
  //which all have the same controls. m_arStringControls is an array which
  //contains the names.
  for (int i = 0; i < m_arStringControls.length; i++) {
    //To obtain the data from the controls we need to get their model.
    //First get the respective control from the XControlContainer.
    XControl xControl = xContainer.getControl(m_arStringControls[i]);

    //This generic handler and the corresponding registry schema support
    //up to five text controls. However, if a options page does not use all
    //five controls then we will not complain here.
    if (xControl == null)
      continue;

    //From the control we get the model, which in turn supports the
    //XPropertySet interface, which we finally use to get the data from
    //the control.
    XPropertySet xProp = (XPropertySet) UnoRuntime.queryInterface(
      XPropertySet.class, xControl.getModel());

    if (xProp == null)
      throw new com.sun.star.uno.Exception(
        "Could not get XPropertySet from control.", this);
    //Get the "Text" property.
    Object aText = xProp.getPropertyValue("Text");
    String sValue = null;

    //The value is still contained in a com.sun.star.uno.Any - so convert it.
    try {
      sValue = AnyConverter.toString(aText);
    } catch (com.sun.star.lang.IllegalArgumentException e) {
      throw new com.sun.star.lang.IllegalArgumentException(
        "Wrong property type.", this, (short) -1);
    }

    //Now we have the actual string value of the control. What we need now is
    //the XPropertySet of the respective property in the registry, so that we
    //can store the value.
    //To access the registry we have previously created a service instance
    //of com.sun.star.configuration.ConfigurationUpdateAccess which supports
    //com.sun.star.container.XNameAccess. The XNameAccess is used to get the
    //particular registry node which represents this options page.
    //Fortunately the name of the window is the same as the registry node.
    XPropertySet xLeaf = (XPropertySet) UnoRuntime.queryInterface(
      XPropertySet.class, m_xAccessLeaves.getByName(sWindowName));
    if (xLeaf == null)
      throw new com.sun.star.uno.Exception(
        "XPropertySet not supported.", this);

    //Finally we can set the value
    xLeaf.setPropertyValue(m_arStringControls[i], sValue);
  }
  //Committing the changes will cause or changes to be written to the registry.
  XChangesBatch xUpdateCommit =
  (XChangesBatch) UnoRuntime.queryInterface(XChangesBatch.class, m_xAccessLeaves);
  xUpdateCommit.commitChanges();
}

private void loadData(com.sun.star.awt.XWindow aWindow)
  throws com.sun.star.uno.Exception {

  //Determine the name of the window. This serves two purposes. First, if this
  //window is supported by this handler and second we use the name two locate
  //the corresponding data in the registry.
  String sWindowName = getWindowName(aWindow);
  if (sWindowName == null)
    throw new com.sun.star.lang.IllegalArgumentException(
      "The window is not supported by this handler", this, (short) -1);

  //To acces the separate controls of the window we need to obtain the
  //XControlContainer from window implementation
  XControlContainer xContainer = (XControlContainer) UnoRuntime.queryInterface(
    XControlContainer.class, aWindow);
  if (xContainer == null)
    throw new com.sun.star.uno.Exception(
      "Could not get XControlContainer from window.", this);

  //This is an implementation which will be used for several options pages
  //which all have the same controls. m_arStringControls is an array which
  //contains the names.
  for (int i = 0; i < m_arStringControls.length; i++) {
    //load the values from the registry
    //To access the registry we have previously created a service instance
    //of com.sun.star.configuration.ConfigurationUpdateAccess which supports
    //com.sun.star.container.XNameAccess. We obtain now the section
    //of the registry which is assigned to this options page.
    XPropertySet xLeaf = (XPropertySet) UnoRuntime.queryInterface(
      XPropertySet.class, m_xAccessLeaves.getByName(sWindowName));
    if (xLeaf == null)
      throw new com.sun.star.uno.Exception(
        "XPropertySet not supported.", this);

    //The properties in the registry have the same name as the respective
    //controls. We use the names now to obtain the property values.
    Object aValue = xLeaf.getPropertyValue(m_arStringControls[i]);

    //Now that we have the value we need to set it at the corresponding
    //control in the window. The XControlContainer, which we obtained earlier
    //is the means to get hold of all the controls.
    XControl xControl = xContainer.getControl(m_arStringControls[i]);

    //This generic handler and the corresponding registry schema support
    //up to five text controls. However, if a options page does not use all
    //five controls then we will not complain here.
    if (xControl == null)
      continue;

    //From the control we get the model, which in turn supports the
    //XPropertySet interface, which we finally use to set the data at the
    //control
    XPropertySet xProp = (XPropertySet) UnoRuntime.queryInterface(
      XPropertySet.class, xControl.getModel());

    if (xProp == null)
      throw new com.sun.star.uno.Exception(
        "Could not get XPropertySet from control.", this);

    //This handler supports only text controls, which are named "Pattern Field"
    //in the dialog editor. We set the "Text" property.
    xProp.setPropertyValue("Text", aValue);
  }
}

//Checks if the name property of the window is one of the supported names and returns
//always a valid string or null
private String getWindowName(com.sun.star.awt.XWindow aWindow)
throws com.sun.star.uno.Exception {

  if (aWindow == null)
    new com.sun.star.lang.IllegalArgumentException(
      "Method external_event requires that a window is passed as argument",
      this, (short) -1);

  //We need to get the control model of the window. Therefore the first step is
  //to query for it.
  XControl xControlDlg = (XControl) UnoRuntime.queryInterface(
    XControl.class, aWindow);

  if (xControlDlg == null)
    throw new com.sun.star.uno.Exception(
      "Cannot obtain XControl from XWindow in method external_event.");
  //Now get model
  XControlModel xModelDlg = xControlDlg.getModel();

  if (xModelDlg == null)
    throw new com.sun.star.uno.Exception(
      "Cannot obtain XControlModel from XWindow in method external_event.", this);
  //The model itself does not provide any information except that its
  //implementation supports XPropertySet which is used to access the data.
  XPropertySet xPropDlg = (XPropertySet) UnoRuntime.queryInterface(
    XPropertySet.class, xModelDlg);
  if (xPropDlg == null)
    throw new com.sun.star.uno.Exception(
      "Cannot obtain XPropertySet from window in method external_event.", this);

  //Get the "Name" property of the window
  Object aWindowName = xPropDlg.getPropertyValue("Name");

  //Get the string from the returned com.sun.star.uno.Any
  String sName = null;
  try {
    sName = AnyConverter.toString(aWindowName);
  } catch (com.sun.star.lang.IllegalArgumentException e) {
    throw new com.sun.star.uno.Exception(
      "Name - property of window is not a string.", this);
  }

  //Eventually we can check if we this handler can "handle" this options page.
  //The class has a member m_arWindowNames which contains all names of windows
  //for which it is intended
  for (int i = 0; i < m_arWindowNames.length; i++) {
    if (m_arWindowNames[i].equals(sName)) {
      return sName;
    }
  }
  return null;
}
}

/**
* Gives a factory for creating the service.
* This method is called by the <code>JavaLoader</code>
* <p>
* @return returns a <code>XSingleComponentFactory</code> for creating
* the component
* @param sImplName the name of the implementation for which a
* service is desired
* @see com.sun.star.comp.loader.JavaLoader
*/
public static XSingleComponentFactory __getComponentFactory(String sImplName)
{
XSingleComponentFactory xFactory = null;

if ( sImplName.equals( _OptionsEventHandler.class.getName() ) )
xFactory = Factory.createComponentFactory(_OptionsEventHandler.class,
_OptionsEventHandler.getServiceNames());

return xFactory;
}

/**
* Writes the service information into the given registry key.
* This method is called by the <code>JavaLoader</code>
* <p>
* @return returns true if the operation succeeded
* @param regKey the registryKey
* @see com.sun.star.comp.loader.JavaLoader
*/
public static boolean __writeRegistryServiceInfo(XRegistryKey regKey) {
return Factory.writeRegistryServiceInfo(_OptionsEventHandler.class.getName(),
_OptionsEventHandler.getServiceNames(),
regKey);
}

/** This method is a member of the interface for initializing an object
* directly after its creation.
* @param object This array of arbitrary objects will be passed to the
* component after its creation.
* @throws Exception Every exception will not be handled, but will be
* passed to the caller.
*/
public void initialize( Object[] object )
throws com.sun.star.uno.Exception {
}

}

Defining the Usage of Options Pages

It needs to be defined somewhere how the options dialog shall display the options pages for particular extensions. This information needs to be provided by the extensions in form of a xcu file which contains the appropriate registry entries. The schema is already provided in the office installation. The file is: <office>/share/registry/schema/org/openoffice/Office/OptionsDialog.xcs. Here are the relevant parts for the options pages:

<oor:component-schema
  xmlns:oor="http://openoffice.org/2001/registry"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  oor:name="OptionsDialog"
  oor:package="org.openoffice.Office"
  xml:lang="en-US">

  <templates>
    <group oor:name="Module">
      <info>
        <desc>Defines a Module. The options dialog displays entries in its tree
          view only if they are defined to belong to the Module for which the
          options dialog is being displayed. The exception is the options dialog
          which is invoked from the Extension Manager, where the Module is
          irrelevant.</desc>
      </info>
      <set oor:name="Nodes" oor:node-type="OrderedNode">
        <info>
          <desc>A set member countains a Node (see type Node) which has been
            assigned to this Module. Also entities which do not own the Module
            may add members to the set. Please look at the specification for
            applying restrictions
            (http://specs.openoffice.org/appwide/packagemanager/options_dialog_for_extensions.odt)
            The actual Node|s are kept in a separate set (OptionsDialog/Nodes),
            to prevent redundancy, because a Node can be assigned to several
            Module|s. The position of a node (the tree view element) within the
            tree view of the options dialog is determined by the property Index
            of each set member. The position can be different dependent on the
            Module. Therefore the order is determined per Module. Only the owner
            of the Module should set the position (property Index).
            The order is undefined if two or more members have the same value for
            the Index property.
            See also the description for OrderedNode.
          </desc>
        </info>
      </set>
    </group>

    <group oor:name="Node">
      <info>
        <desc>Defines a node (the tree view element) which can be displayed in
          the tree view of the options dialog.</desc>
      </info>
      <prop oor:name="Label" oor:type="xs:string" oor:localized="true"
        oor:nillable="false">
        <info>
          <desc>The localized name which is displayed next to the node in the
            options dialog. If two different nodes (the tree view element) happen
            to have the same localized name then both are displayed.</desc>
        </info>
      </prop>
      <prop oor:name="OptionsPage" oor:type="xs:string">
        <info>
          <desc>URL which references the dialog editor resource. This options
            page should only contain information for the user and should not
            accept input.</desc>
        </info>
      </prop>
      <prop oor:name="AllModules" oor:type="xs:boolean">
        <info>
          <desc>If true then this Node shall be displayed independent of the
            Module for which the options dialog is displayed - that is, always.
            In this case it must not be assigned directly to one ore more
            Module|s. That is, it must not exist as member in Module/Nodes of no
            existing Module.
            This is an optimization so that an extension need not provide
            entries for all Module|s.
            The default value is false.
          </desc>
        </info>
        <value>false</value>
      </prop>
      <prop oor:name="GroupId" oor:type="xs:string">
        <info>
          <desc>Identifies a group of Node|s. All nodes (the tree view elements)
            which refer to the same group are displayed next to each other. If
            this property is set then GroupIndex must also have a value. The
            group will be used by Node|s which have been added to existing
            Module|s. If one defines a Module AND Node|s which are used in this
            Module, then GroupId must not be set. In this case one uses
            Module/Nodes/Index to determine the position of the nodes (the tree
            view element).
            The empty string is no valid identifier and represents nil.
            See also GroupIndex.
          </desc>
        </info>
        <value></value>
      </prop>
      <prop oor:name="GroupIndex" oor:type="xs:int">
        <info>
          <desc>The index of the Node within the group. The index starts with
            null. If this property is set then property GroupId must also have
            a value. If a Node of this group is displayed depends on the
            respective Module to which it is assigned. For example, if there are
            the Nodes A(index 0), B (index 1), and C (index 2) in a group,
            and B is assigned to a different Module, then only A and
            B are displayed together although there indices are 0 and 2 (B has 1).
            The value -1 is regarded as nil.
            Actually one could have allowed to define a group per Module, so that
            the order could be different depending on the Module. But this would
            have added more complexity to the data structures.
          </desc>
        </info>
        <value>-1</value>
      </prop>
      <set oor:name="Leaves" oor:node-type="Leaf">
        <info>
          <desc>Leaves which are assigned to this node. Extensions can add to this
            set new Leaf|s.</desc>
        </info>
      </set>
    </group>

    <group oor:name="Leaf">
      <info>
        <desc>Defines a leaf (the tree view element). A Leaf must be assigned to
          exactly one Node. </desc>
      </info>
      <prop oor:name="Id" oor:type="xs:string">
        <info>
          <desc>The unique identifier of the Leaf. This must be the extension
           identifier if this Leaf was defined by an extension.</desc>
        </info>
      </prop>
      <prop oor:name="Label" oor:type="xs:string" oor:localized="true"
        oor:nillable="false">
        <info>
          <desc>The localized name that is displayed for the leaf in the options
            dialog.</desc>
        </info>
      </prop>
      <prop oor:name="OptionsPage" oor:type="xs:string" oor:nillable="false">
        <info>
          <desc>A URL which references the window resource.</desc>
        </info>
      </prop>
      <prop oor:name="EventHandlerService" oor:type="xs:string" oor:nillable="false">
        <info>
          <desc>Name of the event handler service which is used as argument in
            com.sun.star.awt.XContainerWindowProvider.createContainerWindow. This
            handler does not even process events from the dialog but also
            provides for saving and reading the respective values.</desc>
        </info>
        </prop>
        <prop oor:name="GroupId" oor:type="xs:string">
          <info>
            <desc>Identifies a group of Leaf|s. All Leaf|s which have the same
              GroupId form a group and the corresponding entries in the tree view
              are displayed next to each other.
              If this property is set then the property GroupIndex must
              also be set.
              The GroupIndex represents the position within the group. If one
              owns the Node (e.g. defined it oneself) then one can set the GroupId
              to the name of the Node to which this Leaf is assigned. The Leaf|s of
              this particular group are displayed first under the node (the tree
              view element) before any other groups.
              If one does not own the Node then one should not use the Node name as
              group id.
              The empty string is regarded as nil.
            </desc>
          </info>
          <value></value>
        </prop>
        <prop oor:name="GroupIndex" oor:type="xs:int">
          <info>
            <desc>The index of the Leaf within the group. The index starts with
              null. If this property is set then GroupId must also have
              a value. If there are two Leaf|s with the same index in the same
              group then then their order is undefined.
              The value -1 is regarded as nil.</desc>
          </info>
          <value>-1</value>
        </prop>
      </group>

      <group oor:name="OrderedNode">
        <info>
          <desc>Represents a reference to a Node and an optional index.
            Instances of this type are used in a set (see Module). The name of the
            set member must be the same as the one of the respective Node. The
            index determines the position of the node (the tree view element) in
            the options dialog for a particular Module.</desc>
        </info>
        <prop oor:name="Index" oor:type="xs:int">
          <info>
            <desc>Determines the position of the node in the options dialog.
              The index starts with null. That is, the corresponding node
              appears at the first position in the tree view of the options dialog.
              If the value is -1 then the index is undefined. If one adds
              Node|s to a Module which one does not define oneself (e.g. the Module
              is owned by someone else), then one must not set the Index. This
              applies, for example, to extensions which add their Node|s to existing
              Module|s.
              The value -1 is regarded as nil.
            </desc>
          </info>
          <value>-1</value>
        </prop>
      </group>
  </templates>

  <component>
    <set oor:name="Modules" oor:node-type="Module">
      <info>
        <desc>List of availabe application contexts</desc>
      </info>
    </set>
    <set oor:name="Nodes" oor:node-type="Node">
      <info>
        <desc>List of available nodes.</desc>
      </info>
    </set>
  </component>
</oor:component-schema>

Adding a Leaf to an Existing Node

Let us start with something simple and assume that we want to add a leaf under the "OpenOffice.org" writer node. The leaves and nodes, which we are talking about, appear in the tree view of the options dialog on the left side. A leaf has the meaning of an entry which cannot be expanded further. Selecting a leaf will cause the options page being displayed on the right side. Please do not confuse these node and leaves with the elements from the schema. The latter use uppercase names and the plural is indicated using a pipe symbol, such as "Node|s". There is also a xml element "node" in the xcu file. In case the meaning is unclear in the respective context, we will add small note.

The schema in the OptionsDialog.xcs defines two sets which we can add to. One contains Modules, which we do not need at the moment, and the other contains Nodes, which is the place we will add to. As the name suggest, it contains Nodes and not Leafs. But every Node contains a set of Leafs, which is named Leaves. This means, that we have to add a Leaf to the Leaves set of the writer Node. This is done by putting the following xcu file into the extension (do not forget to add the corresponding entry into the manifest.xml) .

<?xml version='1.0' encoding='UTF-8'?>

<oor:component-data oor:name="OptionsDialog" oor:package="org.openoffice.Office"
  xmlns:oor="http://openoffice.org/2001/registry"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <node oor:name="Nodes">
    <node oor:name="Writer" oor:op="fuse">
      <node oor:name="Leaves">
        <node oor:name="org.openoffice.framework.desktop.test.deployment.options.leaf1"
          oor:op="fuse">
          <prop oor:name="Id">
            <value>org.openoffice/framework/desktop/test/deployment/options/leaf1</value>
          </prop>
          <prop oor:name="Label">
            <value xml:lang="en-US">Writer leaf1 en-US</value>
          </prop>
          <prop oor:name="OptionsPage">
            <value>%origin%/dialogs/writer1/Writer1.xdl</value>
          </prop>
          <prop oor:name="EventHandlerService">
            <value>com.sun.star.comp.extensionoptions.DialogHandler</value>
          </prop>
        </node>
      </node>
    </node>
  </node>
</oor:component-data>

In the following examples I will leave out the xml, doctype and root element so we can focus on the relevant parts. In the previous example we see the lines:

<node oor:name="Nodes">
<node oor:name="Writer" oor:op="fuse">
<node oor:name="Leaves">

The first line represents the set Nodes. The second line shows that we add to the writer Node. And the last line represents the Leaves set within the writer Node. The names of Node|s should be unique, as we will see later. The name is the value of the oor:name attribute of the set entry. That is, there is no special property "name". The names for already existing Node|s and Module|s are rather short and are reserved exclusively for the office. The list of those names can be found here.

Currently the already existing nodes are not defined in the registry (but may be in future versions). Therefore, the node Writer, which is a set entry, may not exist yet, unless another extension has already added to this node. To make sure that there is this node we use the operation fuse. Leaves is the set within a Node (the type defined in the templates section of the schema) to which we add our leaf:

<node oor:name="Leaves">
  <node oor:name="org.openoffice.framework.desktop.test.deployment.options.leaf1"
    oor:op="fuse">
    <prop oor:name="Id">
      <value>org.openoffice/framework/desktop/test/deployment/options/leaf1</value>
    </prop>
    <prop oor:name="Label">
      <value xml:lang="en-US">Writer leaf1 en-US</value>
    </prop>
    <prop oor:name="OptionsPage">
      <value>%origin%/dialogs/writer1/Writer1.xdl</value>
    </prop>
    <prop oor:name="EventHandlerService">
      <value>com.sun.star.comp.extensionoptions.DialogHandler</value>
    </prop>
  </node>
</node>

When we add to a set then we must provide a unique name for the oor:name attribute of the node element. We did this by calling it: org.openoffice.framework.desktop.test.deployment.options.leaf1

It is always good to use long names to ensure uniqueness. Starting with the reversed domain name is a good practice, because most developers or companies own a domain, which is already unique. Node names must use ASCII letters and special characters must be "xml encoded". That is signs, such as "<", ">", "&", etc must be replaced by "&lt;", "&gt;", "&amp;", etc. This is also valid for all other xml attribute values or the text between enclosing xml elements. Our leaf node also uses the fuse operator, to ensure that it is added to the set.

It is also useful to avoid "/" within oor:name. This will make it harder when using the API to access the values (see <idl>com.sun.star.container.XHierarchicalNameAccess</idl>). Then one need to encode the name in a particular way. See chapter Configuration Management.

The value of the property Id must be same as the extension identifier. The identifier is used to locate the leaves which belong to a particular extension. This happens when the options dialog is started in the Extension Manager. In this case only the entries for the selected extension are displayed.

The property Label contains the string which appears in the tree view. One can provide many different localized strings. However it is good to have at least an en-US string, which will be used as default in case there is no string which locale matches that of the office.

The property OptionsPage contains the URL to the xdl file of the dialog which shall appear when the user selects the corresponding entry in the tree view. Please note that it always starts with %origin% and is followed by the relative path to the file.

The property EventHandlerService contains the service name of the handler, which is also contained in the extension. One should take care to choose a unique name. It is not necessary to provide any IDL description or type library for the service.

Adding Several Leaves

It may be necessary to add more then one leaf. This is easily done by just writing the next leaf definition after the previous:

<node oor:name="Nodes">
  <node oor:name="Writer" oor:op="fuse">
    <node oor:name="Leaves">
      <node oor:name="oorg.openoffice.framework.desktop.test.deployment.options.leaves3.Writer.3"
oor:op="fuse">
        <!-- leaving out the properties for the sake of brevity -->
      </node>
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.leaves3.Writer.1"
oor:op="fuse">
        <!-- leaving out the properties for the sake of brevity -->
      </node>
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.leaves3.Writer.2"
oor:op="fuse"></node>
    </node>
  </node>
  <node oor:name="Calc" oor:op="fuse">
    <node oor:name="Leaves">
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.leaves3.Calc.3"
oor:op="fuse">
        <!-- leaving out the properties for the sake of brevity -->
      </node>
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.leaves3.Calc.1"
oor:op="fuse">
        <!-- leaving out the properties for the sake of brevity -->
      </node>
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.leaves3.Calc.2"
oor:op="fuse">
        <!-- leaving out the properties for the sake of brevity -->
      </node>
    </node>
  </node>
</node>

In the example we have also added three other Leafs to the Calc Node.

Warning.svg

Warning:
One Leaf can only be assigned to exactly one Node. If there is no suitable existing Node which is displayed in all options dialogs where one needs to display the own Leaf, then one should define a Node oneself and assign the Leaf to it.


Grouping of Leaves

When we add several Leafs to the same Node then we may wish to determine in which order they appear. This is done by using the properties GroupId and GroupIndex in Leaf. The GroupId is used to define a group. All Leafs with the same GroupId form this group. The GroupIndex determines the position of the Leaf with regard to this group. If there are multiple groups of Leafs assigned to the same Node, then it is undefined in which order the groups are displayed. However, the members of a group are always displayed contiguously .

The value for the GroupId must be unique. One can use the same patter here as for the names of Leafs, Nodes, etc.

One special GroupId is the one which has the same value as the Node name. This group is always displayed first under the node in the tree view. We will get to that later.

Usually, when an extension provides Leafs which are all assigned to the same Node, then one makes them belong to the same group.

Warning.svg

Warning:
Do not add to groups which you have not defined yourself. This may lead to clashing indices and is generally bad style.

Grouping Leafs is optional. But when one does it, then all Leafs should be part of a group.


<node oor:name="Nodes">
  <node oor:name="Writer" oor:op="fuse">
    <node oor:name="Leaves">
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.leaves2.Writer.3"
oor:op="fuse">
        <prop oor:name="Id">
          <value>org.openoffice/framework/desktop/test/deployment/options/leaves2</value>
        </prop>
        <!-- leaving out some properties for the sake of brevity -->
        <prop oor:name="GroupId">
          <value>org.openoffice.framework.desktop.test.deployment.options.leaves2.Writer.group</value>
        </prop>
        <prop oor:name="GroupIndex">
          <value>2</value>
        </prop>
      </node>
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.leaves2.Writer.1"
oor:op="fuse">
        <prop oor:name="Id">
          <value>org.openoffice/framework/desktop/test/deployment/options/leaves2</value>
        </prop>
        <!-- leaving out some properties for the sake of brevity -->
        <prop oor:name="GroupId">
          <value>org.openoffice.framework.desktop.test.deployment.options.leaves2.Writer.group</value>
        </prop>
        <prop oor:name="GroupIndex">
          <value>0</value>
        </prop>
      </node>
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.leaves2.Writer.2"
oor:op="fuse">
        <prop oor:name="Id">
          <value>org.openoffice/framework/desktop/test/deployment/options/leaves2</value>
        </prop>
        <!-- leaving out some properties for the sake of brevity -->
        <prop oor:name="GroupId">
          <value>org.openoffice.framework.desktop.test.deployment.options.leaves2.Writer.group</value>
        </prop>
        <prop oor:name="GroupIndex">
          <value>1</value>
        </prop>
      </node>
    </node>
  </node>
</node>

Adding Nodes

Apart from Leafs one can also define one's own Nodes. First we would like to define one simple Node along with a few Leafs:

<node oor:name="Nodes">
  <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes2.node 3"
oor:op="fuse">
    <prop oor:name="Label">
      <value xml:lang="en-US">nodes2 node 3 en-US</value>
      <value xml:lang="de">nodes2 node 3 de</value>
    </prop>
    <prop oor:name="OptionsPage">
      <value>%origin%/dialogs/node3/page/node3.xdl</value>
    </prop>
    <prop oor:name="AllModules">
      <value>true</value>
    </prop>
    <node oor:name="Leaves">
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes2.node 3.leaf 3"
oor:op="fuse">
        <!-- leaving out some properties for the sake of brevity -->
      </node>
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes2.node 3.leaf 1"
oor:op="fuse">
        <!-- leaving out some properties for the sake of brevity -->
      </node>
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes2.node 3.leaf 2"
oor:op="fuse">
        <!-- leaving out some properties for the sake of brevity -->
      </node>
    </node>
  </node>
</node>

Tip.svg

Tip:
Nodes without any leaves are not displayed.


Because a Node is represented in the tree view with a string, it needs to provide a localized name the same as Leafs do. To do this we add under the property Label several localized values.

The property OptionsPage has the same meaning as the same property in Leaf. There is, however, the restriction that the options page must not take user input. Instead is should contain some explanatory words about the node.

The property AllModules needs to be set to true when one wants that the Node appears in the options dialog from all applications. Now, let me explain shortly the meaning of Module, because it will be important for the definition of Nodes. The options dialog opened from the Tools - Options... menu entry refers always to a particular Module, which depends on the current application. All Nodes can be assigned to one or multiple Modules. That is, a Node may be defined to appear only in the options dialog of the Writer and Calc application. How this is done will be explained later. However, often one wishes to have the Node displayed in every options dialog. To save the effort of assigning a Node to all existing Module|s separately, one can do this just once by setting the property of AllModules to true. This is also useful in the case where new Modules are added later, because these new Modules would not know about those Nodes.

Tip.svg

Tip:
Only Nodes which are assigned to a Module are displayed. The assignment is done by adding a member to set Module/Nodes (this will be explained later) or by setting Node/AllModules to true. The latter assigns the Node to all Modules.


Adding Several Nodes

As one might have imagined, adding several nodes is similar to adding various leaves. The definitions are just written one after the other:

<node oor:name="Nodes">
  <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes2.node 3"
oor:op="fuse">
    <!-- leaving out all properties and leaves for the sake of brevity -->
  </node>
  <node oor:name="oorg.openoffice.framework.desktop.test.deployment.options.nodes2.node 1"
oor:op="fuse">
    <!-- leaving out all properties and leaves for the sake of brevity -->
  </node>
  <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes2.node 2"
oor:op="fuse">
    <!-- leaving out all properties and leaves for the sake of brevity -->
  </node>
</node>

Absolute Position of Leaves

When one defines a Node and Leaf|s for this Node at the same time then one would like to specify the position of those leaves as well. This can be done be defining group of Leafs which has the same name as the Node to which they are assigned. This group is special because its Leafs are the first which appear under the corresponding node in the tree view. Otherwise the use of GroupIndex and GroupId is identical as explained in “Grouping of Leaves”.

<node oor:name="Nodes">
  <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes2.node 3"
oor:op="fuse">
    <!-- leaving out some properties for the sake of brevity -->
    <node oor:name="Leaves">
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes2.node 3.leaf 3"
oor:op="fuse">
        <prop oor:name="GroupId">
          <value>org.openoffice.framework.desktop.test.deployment.options.nodes2.node 3</value>
        </prop>
        <prop oor:name="GroupIndex">
          <value>2</value>
        </prop>
      </node>
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes2.node 3.leaf 1" oor:op="fuse">
        <prop oor:name="GroupId">
          <value>org.openoffice.framework.desktop.test.deployment.options.nodes2.node 3</value>
        </prop>
        <prop oor:name="GroupIndex">
          <value>0</value>
        </prop>
      </node>
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes2.node 3.leaf 2"
oor:op="fuse">
        <prop oor:name="GroupId">
          <value>org.openoffice.framework.desktop.test.deployment.options.nodes2.node 3</value>
        </prop>
        <prop oor:name="GroupIndex">
          <value>2</value>
        </prop>
      </node>
    </node>
  </node>
</node>

Grouping of Nodes

Similar to Leafs, Nodes can be grouped as well. For example, an extension would like to add three nodes to the options dialog of the Writer. Then one may want that these nodes are displayed contiguously. This will also be achieved by defining a group with the property GroupId and an index with the property GroupIndex. The index only determines the position within the group.

Warning.svg

Warning:
Do not add to groups which you have not defined yourself. This may lead to clashing indices and is generally bad style.

Grouping Nodes is optional. But when one does it, then all Nodes should be part of a group.


<node oor:name="Nodes">
  <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes2.node 3"
oor:op="fuse">
    <!-- leaving out some properties for the sake of brevity -->
    <prop oor:name="GroupId">
      <value>org.openoffice.framework.desktop.test.deployment.options.nodes2.group</value>
    </prop>
    <prop oor:name="GroupIndex">
      <value>2</value>
    </prop>
    <!-- Next Node -->
    <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes2.node 1"
oor:op="fuse">
      <!-- leaving out some properties for the sake of brevity -->
      <prop oor:name="GroupId">
        <value>org.openoffice.framework.desktop.test.deployment.options.nodes2.group</value>
      </prop>
      <prop oor:name="GroupIndex">
        <value>0</value>
      </prop>
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes2.node 2"
oor:op="fuse">
        <!-- leaving out some properties for the sake of brevity -->
        <prop oor:name="GroupId">
          <value>org.openoffice.framework.desktop.test.deployment.options.nodes2.group</value>
        </prop>
        <prop oor:name="GroupIndex">
          <value>1</value>
        </prop>
      </node>
    </node>

The ordering of Nodes within a group is basically the same as with Leafs. But there is a small difference. In contrast to Leafs, Nodes can be assigned to various Modules, whereas as Leaf can only be assigned to one Node. So actually one could define an order of Nodes per Module. The order could then differ depending on the Module. For example, we define Node A and B which are assigned to the “Writer” Module (actually the names for the modules are longer, for example, com.sun.star.text.TextDocument) and the “Calc” Module. We could define that in the options dialog of the Writer node A is before B and in the options dialog of Calc B is before A.

This would have added some more complexity to the data structures. With respect to the ease of use we decided for a compromise. One can only define one order independent of the Module. In the previous example the nodes A and B would have the same order in the Writer's and Calc's options dialog. But what if one Node is not assigned to a particular Module but the others are? For example, there are the Nodes A, B, C which have the indices 0, 1, 2. Only A and C are assigned to the “Writer” Module. Then in the options dialog of the Writer the node A would immediately followed by C.

Assigning Nodes to Modules

In the previous paragraphs we have explained what has to be done so that a node appears in the options dialog of an application no matter what the application is. Now we will explain how one can pick out the application where the node should appear.

Having a look at the schema of the OtionsDialog.xcs one notices that there is another set, named Modules, to which we can add entries which then determine where the nodes are displayed. If one does not care for a particular application then one should use the AllModules property of the Node, so that they are always displayed.

<node oor:name="Modules">
  <node oor:name="com.sun.star.text.TextDocument" oor:op="fuse">
    <node oor:name="Nodes">
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes4.node 1"
oor:op="fuse"></node>
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes4.node 2"
oor:op="fuse"></node>
    </node>
  </node>
  <node oor:name="com.sun.star.sheet.SpreadsheetDocument" oor:op="fuse">
    <node oor:name="Nodes">
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes4.node 1"
oor:op="fuse"></node>
    </node>
  </node>
</node>
<node oor:name="Nodes">
  <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes4.node 1"
oor:op="fuse">
    <!-- leaving out some properties for the sake of brevity -->
  </node>
  <node oor:name="org.openoffice.framework.desktop.test.deployment.options.nodes4.node 2"
oor:op="fuse">
    <!-- leaving out some properties for the sake of brevity -->
  </node>
</node>

The example shows the definition of two Node|s. Both are displayed in the options dialog of the Writer but only “node 1” is displayed in the options dialog of Calc. This demonstrates also that a node can be assigned to various Modules.

The names of the application contexts defined by LibreOffice can be found here: Framework/Article/Options_Dialog_Configuration

Defining a Module

It is possible to define a Module of one's own. How an extension can provide a real module (not the type Module) is not part of this documentation. However, if this module does not exist, there won´t be an options dialog on behalf of this module and the Nodes assigned to it are not displayed. Only the options dialog from the Extension Manager may show the nodes because it does not depend on a particular module.

Here is an example of defining a Module.

<node oor:name="Modules">
  <node oor:name="org.openoffice.framework.desktop.test.deployment.options.modules1.module1"
oor:op="fuse">
    <node oor:name="org.openoffice.framework.desktop.test.deployment.options.modules1.module2"
oor:op="fuse"></node>
  </node>

Absolute Position of Nodes

If one defines an own Module, then it is possible to define an order for the Nodes which one assigns to it. These Nodes will be the first which are displayed in the options dialog followed by the Nodes which have been assigned by others.

For example:

<node oor:name="Modules">
  <node oor:name="org.openoffice.framework.desktop.test.deployment.options.modules1.module1"
oor:op="fuse">
    <node oor:name="Nodes">
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.modules1.node 1"
oor:op="fuse">
        <prop oor:name="Index">
          <value>0</value>
        </prop>
      </node>
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.modules1.node 2"
oor:op="fuse">
        <prop oor:name="Index">
          <value>1</value>
        </prop>
      </node>
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.modules1.node 3"
oor:op="fuse">
        <prop oor:name="Index">
          <value>2</value>
        </prop>
      </node>
    </node>
  </node>
  <node oor:name="org.openoffice.framework.desktop.test.deployment.options.modules1.module2"
oor:op="fuse">
    <node oor:name="Nodes">
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.modules1.node 1"
oor:op="fuse">
        <prop oor:name="Index">
          <value>0</value>
        </prop>
      </node>
      <node oor:name="org.openoffice.framework.desktop.test.deployment.options.modules1.node 3"
oor:op="fuse">
        <prop oor:name="Index">
          <value>1</value>
        </prop>
      </node>
    </node>
  </node>
</node>
<node oor:name="Nodes">
  <node oor:name="org.openoffice.framework.desktop.test.deployment.options.modules1.node 3"
oor:op="fuse">
    <!-- leaving out some properties for the sake of brevity -->
  </node>
  <node oor:name="org.openoffice.framework.desktop.test.deployment.options.modules1.node 1"
oor:op="fuse">
    <!-- leaving out some properties for the sake of brevity -->
  </node>
  <node oor:name="org.openoffice.framework.desktop.test.deployment.options.modules1.node 2"
oor:op="fuse">
    <!-- leaving out some properties for the sake of brevity -->
  </node>
</node>

As you can see, every Node which is assigned to a Module can be paired with an index (property Index).

Note pin.svg

Note:
If one decides to provide an index then one should do this for all Nodes.

If a particular index is not used, for example, only index 0, 1, 5 are used, then the missing indices are left out when the nodes are displayed. That is, the tree view will not show gaps for the missing indices 2,3, and 4.

The Options Dialog of the Extension Manager

The options dialog which is invoked from the Extension Manager only shows nodes with leaves which have been added by the currently selected extensions. The dialog has no particular application context, so that all nodes are displayed, independent of the application contexts which they are assigned to.

Help Content

Starting with OpenOffice.org 2.4 extensions can contain help content and extend the LibreOffice's installed help content. The feature supports:

  • Adding extension related help pages
  • Extending the help index to refer to these pages
  • Extended tool tips in UNO dialogs and menus
  • Accessing extension help content by pressing F1 in UNO dialogs and menus.

Extension help format

The help content is located in a folder inside the extension oxt file. Usually the folder will be named “help”, but any other name can be used as long as it's assigned to the media type "application/vnd.sun.star.help" in /META-INF/manifest.xml. In this chapter the help folder name will always be “help” and all descriptions refer to the SDK sample extension DialogWithHelp.oxt developed by the fictional “Foo Corporation”.

Inside the help folder there's one folder for each supported language. The folders are named according to the common language codes en, de, fr etc. as they are also used inside the LibreOffice help folder. Inside each language folder there has to be another folder named like the extension identifier that has to be specified in the /description.xml file for each extension. As the identifier will be encoded to be used as folder name it's recommended to follow the naming scheme described in the Developers Guide Extension Identifiers,

e.g. “com.foocorp.foo-ext” to avoid differences between identifier and encoded identifier.

The identifier is part of the path to access the extension help files. The reason for this rule is to get an unique path to each help file inside the extension: a path that cannot clash with paths to help files in the installed help or in other extensions. Unfortunately – due to the design of the extension manager, the help sub package has no access to its parent package bundle – it's not possible to check at help compile time (see Deployment) if this identifier rule is violated and so no error message will be shown in this case. Nevertheless, help pages inside “illegal” folders will be suppressed at runtime, so the extension author should detect the problem when testing the extension. If a help package is not part of a parent bundle package, the identifier rule does not apply.

The following tree shows the structure of the DialogWithHelp.oxt help folder containing the languages en and de with three help pages each, one of those in a sub folder. The file / folder structure for all languages should be same, e.g. to make bookmarks pointing to extension help pages the same for all languages.

The help content will be compiled for all language when the extension is deployed. At runtime the help system tries to find the same language inside the extensions' help content that is also used to access the installed help. In case the extension's help doesn't support this language, the help system will always try to find English (en) help inside the extension and use this as a fallback.

Example:

DialogWithHelp.oxt
 │
 ├─ DialogWithHelp
 │   │
 │   └─ ... Dialog Library
 │
 ├─ META-INF
 │   │
 │   └─ manifest.xml (assigning “help” folder to media type
 │                    "application/vnd.sun.star.help")
 ├─ help
 │   │
 │   ├─ en
 │   │   │
 │   │   └─ com.foocorp.foo-ext
 │   │       │
 │   │       ├─ page1.xhp
 │   │       │
 │   │       ├─ page2.xhp
 │   │       │
 │   │       └─ subfolder
 │   │           │
 │   │           └─ anotherpage.xhp
 │   │
 │   └─ de
 │       │
 │       └─ com.foocorp.foo-ext
 │           │
 │           ├─ page1.xhp
 │           │
 │           ├─ page2.xhp
 │           │
 │           └─ subfolder
 │               │
 │               └─ anotherpage.xhp
 │
 ├─ Addons.xcu (Menu/Toolbar specification)
 │
 └─ description.xml (contains identifier)

Deployment

Extension help content is deployed like any other extension content using the OpenOffice Extension Manager or the unopkg command line tool, usually as sub package inside a package bundle. All help files are detected automatically during extension deployment, no additional configuration file is needed to list them. The help files are compiled then similar to the installed help content being compiled during the OpenOffice.org build process. The compile process itself doesn't differ from the one in helpcontent2, but instead of referring to the main OpenOffice.org help modules like Writer, Calc etc. each extension is handled as its own small module.

All Help Compiler output is created inside the extension's cache folder. So it's guaranteed that all help content will be removed together with the extension. Help content of disabled extensions is ignored. Due to caching mechanisms used by the HelpProvider implementation changes in the extension state may not be visible before the next start of OpenOffice.

Error handling

During the deployment the extension's help files are compiled. If an error is found during compilation – usually this will be a XML parsing error - a corresponding error message will be displayed. End users shouldn't be affected by these errors as the extension developer should fix all problems before releasing the extension.

Only the first line of the error message is localized. It's followed by details in English directly using the error information provided by the Help Compiler respectively the XML parser. The following lines show an example for a XML parsing error. The first line is a localized string, the following lines are always in English.

The extension will not be installed because an error occurred in the help files:
attributes construct error in file:///D:/OOo24/user_data/uno_packages/cache/uno_packages/15A7.tmp
/DialogWithHelp.oxt/en/com.foocorp.foo-ext/page1.xhp, line 49

Integration into the OpenOffice help system

There are different ways to access extension help content respectively to integrate it into the installed help content.

Help module list box

Extensions don't have its own help module, neither as single extension nor for all extensions together. So the help module list box won't be affected by extension help content.

Help Viewer Contents page

Adding content to the Contents tree is supported from OpenOffice.org version 3.0.0. See specification http://specs.openoffice.org/appwide/help/ExtensibleHelp.odt

Help Viewer Index page

Adding index entries can be done in the same way as it's done in the installed help files (see File format). There's a difference concerning the way the index entries are assigned to the different help modules. In the OpenOffice.org installed help this is handled via the help file path. As extension help shouldn't be forced to use the same file naming scheme, instead a bookmark naming scheme is used here. The following example shows a bookmark defining an index entry (branch=”index”) visible for all modules:

<bookmark xml-lang="en-US" branch="index" id="bm_foo001">
<bookmark_value>foo This is a single level entry</bookmark_value>
<bookmark_value>foo first level;second level</bookmark_value>
</bookmark>

If the index entries should only be visible for (a) special module(s) the module(s) it should be visible in can be added to the bookmark id using _ as separator, examples:

<bookmark xml-lang="en-US" branch="index" id="bm_foo001_swriter">
...

will make all following bookmark_value entries only visible in the Writer index.

<bookmark xml-lang="en-US" branch="index" id="bm_foo001_scalc_sdraw">
...

will make all following bookmark_value entries only visible in Calc and Draw index.

The module names to be used here are the same as in the installed help:

sbasic, scalc. schart, sdatabase, sdraw, simpress, smath, swriter.

Help Viewer Find page

Starting with OpenOffice.org 3.1 full text search is also supported for extension help. The extension developer doesn't have to care about this. During extension installation a help index will be created automatically. Search results found inside the extension help will be shown together with the results found in the LibreOffice help files ordered according to their score.

Help Viewer Bookmarks page

Bookmarks to extension help pages can be added. When the corresponding package is disabled or removed later the bookmark will not automatically be removed.

Context sensitive help and extended tool tips

Context sensitive help is based on Help Ids that are assigned to toolbar items, menu items and UNO Dialogs / Dialog controls. Toolbar and menu items do specify a Command URL to bind the item to an action to be taken when the item is executed. It's important to choose a unique Command URL, e.g. by following a similar scheme like for the extension identifier or to refer to a service implementing the functionality. Example Command URL: com.foocorp.foo:DoTheFooAction

This Command URL is also used as Help Id, both for extended tool tips as for assigning a help page to F1. Defining help Ids in xhp files is also done with the bookmark element (see File Format):

<bookmark xml-lang="en-US" branch="hid/com.foocorp.foo:DoTheFooAction" id="bm_foo02" localize="false"/>
<paragraph role="paragraph" id="hd_id4711" xml-lang="en-US"><ahelp hid="com.foocorp.foo:DoTheFooAction" visibility="hidden">This is the extended tool tip for DoTheFooAction</ahelp></paragraph>
<paragraph role="paragraph" id="hd_id4712" xml-lang="en-US">This is the help Text for DoTheFooAction. … </paragraph>

The Command URL has to be used both for the branch value after hid/ and in the ahelp / hid value.

In the same way bookmarks can be bound to Dialogs / Controls created with the OpenOffice Dialog Editor. The user just has to assign a unique Help Id to a Dialog's or Control's Help URL property and define a corresponding bookmark as shown above. If a control has the focus, its Help URL has priority about the Dialog Help URL.

Links

Links to extension help pages are defined in the same way as in the LibreOffice help, only the path differs. Example:

<link href="com.foocorp.foo-ext/subfolder/anotherpage.xhp">Link to foo</link>

Images

Starting with OOo 3.1 extension help can provide own pictures. In the LibreOffice help image tags always refer to a set of images distributed with the office product in the images.zip file. The following help image tag addresses an icon located there:

<image id="img_id0001" src="res/commandimagelist/sc_paralefttoright.png"
width="0.222inch" height="0.222inch">

The width / height attributes are optional. The width and height attributes are currently (year 2009) ignored by the help system.

Tags like this of course also can be used in extension help files (even before OOo 3.1). To address an image inside the extension's help the src path has to start with the extension's id. So this will show the image located inside the extension's help at foo.oxt/help/en/com.foocorp.foo-ext/foo.png:

<image id="img_id0002" src="com.foocorp.foo-ext/foo.png"></image>

Images can also be placed in subfolders:

<image id="img_id0003" src="com.foocorp.foo-ext/subfolder/foo2.png"></image>

A consequence of this location is that you need to store an image for each language of help.

Images can be placed anywhere inside the extension package, not necessarily inside the extension's help tree. With another location, if the same image is used for different languages the help files will reference the same image file. In this example the image foo3.png is in the folder Images of the extension package.

<image id="img_id0004" src="com.foocorp.foo-ext/../../../Images/foo3.png"></image>

File Format

The extension help content uses the XML based xhp file format that is also used for the LibreOffice installed help. A description can be found here:

http://documentation.openoffice.org/online_help/OOo2HelpAuthoring.pdf

Paths start with the Identifier folder inside the language folder. This is also reflected in the xhp files' <filename> tag, example:

<helpdocument version="1.0">
...
<filename>/com.foocorp.foo-ext/page1.xhp</filename>
Heckert GNU white.svg

Content on this page is licensed under the Public Documentation License (PDL).