Task 501: .OXT File Format
Task 501: .OXT File Format
File Format Specifications for .OXT
The .OXT file format is used for extensions (add-ons or plug-ins) in Apache OpenOffice and LibreOffice applications. It is essentially a standard ZIP archive with a specific internal structure and required files, designed to package code, data, configurations, libraries, and metadata for easy installation via the Extension Manager. The MIME type is application/vnd.openofficeorg.extension. It supersedes older formats like .uno.pkg and .zip, though those are still partially supported for backward compatibility. The format ensures platform compatibility, dependency checking, versioning, and live deployment (adding/removing without always requiring a restart).
Key aspects include:
- Container: ZIP archive (standard compression, forward slashes for paths, no special character restrictions beyond XML encoding needs).
- Required Root Files/Directories:
description.xml: Provides metadata like identifier, version, dependencies, etc.META-INF/manifest.xml: Lists all contents with media types for processing during installation.- Optional Contents: UNO components (e.g., .jar, .so, .py), configuration files (.xcu, .xcs), Basic/dialog libraries (directories), executables, help content (directories with .xhp files), images, and arbitrary data.
- Installation/Processing: Extracted to a cache directory (e.g., user/uno_packages/cache). Media types in manifest.xml determine handling (e.g., registration of components). Dependencies and platforms are validated before installation.
- Versioning and Updates: Supports online updates via URLs in
description.xml. Versions are textual but compared as integer sequences (e.g., 1.0.0 < 1.1). - Platform Restrictions: Specified via tokens (e.g.,
linux_x86_64,windows_x86) indescription.xmlor manifest entries. - Localization: Uses RFC 3066 locales (e.g., en-US) for elements like names, licenses, and descriptions.
- Security/Validation: Unknown dependencies or unmet versions block installation. Licenses can require acceptance.
For full details, see the LibreOffice Developer's Guide on Extensions.
- List of All Properties Intrinsic to the .OXT File Format
Based on the specifications, the intrinsic properties refer to the structural and metadata elements that define the format at the file system level (e.g., ZIP headers, required files, XML schemas, media types, and file attributes preserved during extraction/installation). These are derived from the ZIP container and the mandated XML files (manifest.xml and description.xml). I've listed them categorically, focusing on those tied to the file's integrity, parsing, and system interactions (e.g., executable flags on Unix).
ZIP-Level Properties (File System Fundamentals):
- Magic Number:
PK\003\004(local file header signature). - Central Directory Signature:
PK\001\002. - End of Central Directory Signature:
PK\005\006. - Compression Method: Typically DEFLATE (code 8), but others like STORE (0) allowed.
- File Attributes: External attributes preserve Unix permissions (e.g., executable bit set via media type
application/vnd.sun.star.executableduring extraction; user/group/other flags applied based on installation scope). - Path Separators: Forward slashes (
/); case-sensitive. - Timestamp: MS-DOS format (last modification time/date in local headers).
- Number of Entries: Variable (typically 7-37 files, but no limit).
- MIME Type:
application/zip(underlying), with extension-specificapplication/vnd.openofficeorg.extension.
Required Structural Properties (Root Files/Directories):
META-INF/manifest.xml: Must exist; declares all entries with media types.description.xml: Must exist; root element<description>with namespaces (e.g.,http://openoffice.org/extensions/description/2006).
Manifest.xml Properties (Namespace: urn:oasis:names:tc:opendocument:xmlns:manifest:1.0):
- Root Element:
<manifest:manifest>withmanifest:version(e.g., "1.2"). - File Entries:
<manifest:file-entry>elements with: manifest:full-path: Relative path (e.g., "/").manifest:media-type: Type for processing (e.g.,application/vnd.sun.star.uno-component;type=native,application/vnd.sun.star.configuration-data).manifest:version: Optional for versioned items.platform: Optional token (e.g.,linux_x86_64).- Media Types (key property for system handling):
application/vnd.sun.star.uno-component;type=native(native libraries, e.g., .so).application/vnd.sun.star.uno-component;type=Java(.jar).application/vnd.sun.star.uno-component;type=Python(.py).application/vnd.sun.star.uno_components(XML components list).application/vnd.sun.star.uno-typelibrary;type=RDB(.rdb).application/vnd.sun.star.uno-typelibrary;type=Java(.jar types).application/vnd.sun.star.basic-library(Basic library directory).application/vnd.sun.star.dialog-library(Dialog library directory).application/vnd.sun.star.configuration-data(.xcu).application/vnd.sun.star.configuration-schema(.xcs).application/vnd.sun.star.executable(executables; preserves Unix executable flag).application/vnd.sun.star.help(help directories).text/xml(for description.xml).- Deprecated:
application/vnd.sun.star.package-bundle-description;locale=XX(tooltips).
Description.xml Properties (Namespace: http://openoffice.org/extensions/description/2006):
- Identifier:
<identifier value="unique.string"/>(e.g.,com.example.extension; required for uniqueness). - Version:
<version value="textual.version"/>(e.g., "1.0"; compared as integers). - Platform:
<platform value="tokens"/>(e.g., "linux_x86_64,windows_x86"; or "all"). - Dependencies:
<dependencies>with children like<OpenOffice.org-minimal-version value="X" d:name="desc"/>,<LibreOffice-maximal-version value="X" lo:name="desc"/>. - License:
<registration><simple-license accept-by="user|admin" suppress-on-update="true|false" suppress-if-required="true|false"><license-text xlink:href="path" lang="locale"/></simple-license></registration>. - Update Information:
<update-information><src xlink:href="URL"/></update-information>(for feeds/mirrors). - Publisher:
<publisher><name xlink:href="URL" lang="locale">Name</name></publisher>. - Release Notes:
<release-notes><src xlink:href="URL" lang="locale"/></release-notes>. - Display Name:
<display-name><name lang="locale">Name</name></display-name>. - Icon:
<icon><default xlink:href="path"/><high-contrast xlink:href="path"/></icon>. - Extension Description:
<extension-description><src xlink:href="path" lang="locale"/></extension-description>.
Other Intrinsic Properties:
- Unique Installation Folder: Generated during extraction (e.g., to avoid clashes).
- %origin% Placeholder: Used in configs for runtime path resolution.
- Localization Matching: Prioritizes exact locale, then language-country, etc.
- Executable Flag Preservation: On Unix, for files with
application/vnd.sun.star.executable(set to user/group/other based on scope).
These properties ensure the file's parseability, installation integrity, and system compatibility.
Two Direct Download Links for .OXT Files
- https://wiki.documentfoundation.org/images/0/01/Sample_LO_configuration_extension.oxt (Sample configuration extension from LibreOffice wiki; 9 KB).
- https://extensions.libreoffice.org/assets/downloads/z/mri-1-1-4.oxt (MRI object introspection tool extension; 195 KB).
HTML JavaScript for Drag-and-Drop .OXT File Dump (Embedded in a Ghost Blog Post)
Ghost is a blogging platform that supports embedding HTML/JS. Below is a complete HTML snippet (with embedded JS) that can be pasted into a Ghost post's code injection or HTML card. It creates a drop zone; on drag-and-drop of a .OXT file, it reads it as ZIP (using JSZip library), extracts description.xml and META-INF/manifest.xml, parses them as XML, and dumps all properties from the list above to the screen (as JSON-like output). Include JSZip via CDN for simplicity.
Drag and Drop .OXT File Here
- Python Class for .OXT Handling
This class uses zipfile and xml.etree.ElementTree to open, decode/read properties from description.xml and manifest.xml, print them, and write a new .OXT with modified properties.
import zipfile
import xml.etree.ElementTree as ET
from io import BytesIO
class OXTHandler:
def __init__(self, filepath):
self.filepath = filepath
self.properties = {}
self._decode()
def _decode(self):
with zipfile.ZipFile(self.filepath, 'r') as zf:
# ZIP-level properties
self.properties['zip_magic'] = 'PK\x03\x04' # Assumed
self.properties['num_entries'] = len(zf.namelist())
# description.xml
if 'description.xml' in zf.namelist():
desc_xml = zf.read('description.xml').decode('utf-8')
root = ET.fromstring(desc_xml)
ns = {'default': 'http://openoffice.org/extensions/description/2006',
'd': 'http://openoffice.org/extensions/description/2006',
'lo': 'http://libreoffice.org/extensions/description/2011',
'xlink': 'http://www.w3.org/1999/xlink'}
self.properties['identifier'] = root.find('.//default:identifier', ns).attrib.get('value')
self.properties['version'] = root.find('.//default:version', ns).attrib.get('value')
self.properties['platform'] = root.find('.//default:platform', ns).attrib.get('value')
deps = root.findall('.//default:dependencies/*', ns)
self.properties['dependencies'] = [{el.tag: el.attrib} for el in deps]
self.properties['license'] = ET.tostring(root.find('.//default:simple-license', ns)).decode() if root.find('.//default:simple-license', ns) else None
self.properties['update_info'] = [el.attrib.get('{http://www.w3.org/1999/xlink}href') for el in root.findall('.//default:update-information/default:src', ns)]
self.properties['publisher'] = root.find('.//default:publisher/default:name', ns).text if root.find('.//default:publisher/default:name', ns) else None
self.properties['release_notes'] = [el.attrib.get('{http://www.w3.org/1999/xlink}href') for el in root.findall('.//default:release-notes/default:src', ns)]
self.properties['display_name'] = root.find('.//default:display-name/default:name', ns).text if root.find('.//default:display-name/default:name', ns) else None
self.properties['icon'] = root.find('.//default:icon/default:default', ns).attrib.get('{http://www.w3.org/1999/xlink}href') if root.find('.//default:icon/default:default', ns) else None
self.properties['extension_desc'] = [el.attrib.get('{http://www.w3.org/1999/xlink}href') for el in root.findall('.//default:extension-description/default:src', ns)]
# manifest.xml
if 'META-INF/manifest.xml' in zf.namelist():
manifest_xml = zf.read('META-INF/manifest.xml').decode('utf-8')
root = ET.fromstring(manifest_xml)
mns = {'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0'}
self.properties['manifest_namespace'] = root.attrib.get('xmlns:manifest')
self.properties['manifest_version'] = root.attrib.get('manifest:version')
entries = root.findall('.//manifest:file-entry', mns)
self.properties['file_entries'] = [{
'full_path': el.attrib.get('{urn:oasis:names:tc:opendocument:xmlns:manifest:1.0}full-path'),
'media_type': el.attrib.get('{urn:oasis:names:tc:opendocument:xmlns:manifest:1.0}media-type'),
'version': el.attrib.get('{urn:oasis:names:tc:opendocument:xmlns:manifest:1.0}version'),
'platform': el.attrib.get('platform')
} for el in entries]
self.properties['has_executable'] = any(e['media_type'] == 'application/vnd.sun.star.executable' for e in self.properties['file_entries'])
def print_properties(self):
import json
print(json.dumps(self.properties, indent=4))
def write(self, new_filepath, modified_properties=None):
if modified_properties:
self.properties.update(modified_properties)
with zipfile.ZipFile(self.filepath, 'r') as zf_in:
with zipfile.ZipFile(new_filepath, 'w') as zf_out:
for item in zf_in.infolist():
data = zf_in.read(item.filename)
if item.filename == 'description.xml':
# Rebuild XML with modified properties (simplified; full rebuild would require more logic)
data = self._build_description_xml().encode('utf-8')
elif item.filename == 'META-INF/manifest.xml':
data = self._build_manifest_xml().encode('utf-8')
zf_out.writestr(item, data)
def _build_description_xml(self):
# Simplified rebuild; in production, use full ET build based on properties
return '<description xmlns="http://openoffice.org/extensions/description/2006"><!-- Rebuilt --></description>'
def _build_manifest_xml(self):
return '<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"><!-- Rebuilt --></manifest:manifest>'
# Example usage:
# handler = OXTHandler('example.oxt')
# handler.print_properties()
# handler.write('new.oxt', {'version': '2.0'})
- Java Class for .OXT Handling
This class uses java.util.zip and javax.xml.parsers to handle .OXT files similarly.
import java.io.*;
import java.util.*;
import java.util.zip.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.*;
public class OXTHandler {
private String filepath;
private Map<String, Object> properties = new HashMap<>();
public OXTHandler(String filepath) {
this.filepath = filepath;
decode();
}
private void decode() {
try (ZipFile zf = new ZipFile(filepath)) {
// ZIP-level
properties.put("zip_magic", "PK\u0003\u0004");
properties.put("num_entries", zf.size());
// description.xml
ZipEntry descEntry = zf.getEntry("description.xml");
if (descEntry != null) {
InputStream is = zf.getInputStream(descEntry);
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
properties.put("identifier", doc.getElementsByTagName("identifier").item(0).getAttributes().getNamedItem("value").getNodeValue());
properties.put("version", doc.getElementsByTagName("version").item(0).getAttributes().getNamedItem("value").getNodeValue());
// Add similar for other elements (omitted for brevity; use getElementsByTagName and extract)
}
// manifest.xml
ZipEntry manifestEntry = zf.getEntry("META-INF/manifest.xml");
if (manifestEntry != null) {
InputStream is = zf.getInputStream(manifestEntry);
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
properties.put("manifest_namespace", doc.getDocumentElement().getAttribute("xmlns:manifest"));
// Add file entries list, etc. (omitted; similar logic)
}
// Other (e.g., has_executable from media types)
} catch (Exception e) {
e.printStackTrace();
}
}
public void printProperties() {
System.out.println(properties);
}
public void write(String newFilepath, Map<String, Object> modifiedProperties) {
if (modifiedProperties != null) {
properties.putAll(modifiedProperties);
}
try (ZipFile zfIn = new ZipFile(filepath);
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(newFilepath))) {
Enumeration<? extends ZipEntry> entries = zfIn.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
InputStream is = zfIn.getInputStream(entry);
byte[] data = is.readAllBytes();
if (entry.getName().equals("description.xml")) {
data = buildDescriptionXml().getBytes(); // Rebuild
} else if (entry.getName().equals("META-INF/manifest.xml")) {
data = buildManifestXml().getBytes();
}
ZipEntry newEntry = new ZipEntry(entry.getName());
zos.putNextEntry(newEntry);
zos.write(data);
zos.closeEntry();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private String buildDescriptionXml() {
return "<description><!-- Rebuilt --></description>";
}
private String buildManifestXml() {
return "<manifest:manifest><!-- Rebuilt --></manifest:manifest>";
}
// Example usage:
// public static void main(String[] args) {
// OXTHandler handler = new OXTHandler("example.oxt");
// handler.printProperties();
// handler.write("new.oxt", Map.of("version", "2.0"));
// }
}
(Note: Full XML parsing/rebuilding omitted for brevity; expand with DOM manipulation for each property.)
- JavaScript Class for .OXT Handling
This uses JSZip for browser/Node. For Node, require 'jszip' and 'fs'. Dumps to console.
const JSZip = require('jszip'); // For Node; use import or CDN for browser
const fs = require('fs'); // Node only
const { DOMParser } = require('xmldom'); // For Node; browser has built-in
class OXTHandler {
constructor(filepath) {
this.filepath = filepath;
this.properties = {};
this.decode();
}
async decode() {
const data = fs.readFileSync(this.filepath);
const zip = await JSZip.loadAsync(data);
this.properties.zip_magic = 'PK\x03\x04';
this.properties.num_entries = Object.keys(zip.files).length;
// description.xml
const descXml = await zip.file('description.xml')?.async('string');
if (descXml) {
const doc = new DOMParser().parseFromString(descXml);
this.properties.identifier = doc.getElementsByTagName('identifier')[0]?.getAttribute('value');
this.properties.version = doc.getElementsByTagName('version')[0]?.getAttribute('value');
// Add similar for others (omitted)
}
// manifest.xml
const manifestXml = await zip.file('META-INF/manifest.xml')?.async('string');
if (manifestXml) {
const doc = new DOMParser().parseFromString(manifestXml);
this.properties.manifest_namespace = doc.documentElement.getAttribute('xmlns:manifest');
// Add entries (omitted)
}
}
printProperties() {
console.log(JSON.stringify(this.properties, null, 2));
}
async write(newFilepath, modifiedProperties) {
if (modifiedProperties) Object.assign(this.properties, modifiedProperties);
const zip = new JSZip();
// Re-add files, rebuild XMLs (simplified)
zip.file('description.xml', this.buildDescriptionXml());
zip.file('META-INF/manifest.xml', this.buildManifestXml());
const content = await zip.generateAsync({type: 'nodebuffer'});
fs.writeFileSync(newFilepath, content);
}
buildDescriptionXml() {
return '<description><!-- Rebuilt --></description>';
}
buildManifestXml() {
return '<manifest:manifest><!-- Rebuilt --></manifest:manifest>';
}
}
// Example:
// const handler = new OXTHandler('example.oxt');
// handler.printProperties();
// await handler.write('new.oxt', {version: '2.0'});
(Note: For browser, remove fs, use File/Blob for read/write.)
- C++ Class for .OXT Handling
This uses minizip (or similar ZIP lib; assume included) and tinyxml2 for XML. Prints to console.
#include <iostream>
#include <map>
#include <string>
#include <zip.h> // minizip or libzip
#include <tinyxml2.h> // For XML
class OXTHandler {
private:
std::string filepath;
std::map<std::string, std::string> properties; // Simplified; use structs for lists
public:
OXTHandler(const std::string& fp) : filepath(fp) {
decode();
}
void decode() {
zip_t* za = zip_open(filepath.c_str(), ZIP_RDONLY, nullptr);
if (!za) return;
// ZIP-level
properties["zip_magic"] = "PK\x03\x04";
int num = zip_get_num_entries(za, 0);
properties["num_entries"] = std::to_string(num);
// description.xml
zip_file_t* descFile = zip_fopen(za, "description.xml", 0);
if (descFile) {
// Read buffer, parse with tinyxml2
char buffer[1024*1024]; // Assume size
zip_int64_t len = zip_fread(descFile, buffer, sizeof(buffer));
buffer[len] = '\0';
tinyxml2::XMLDocument doc;
doc.Parse(buffer);
auto elem = doc.FirstChildElement("description")->FirstChildElement("identifier");
if (elem) properties["identifier"] = elem->Attribute("value");
// Add others similarly
zip_fclose(descFile);
}
// manifest.xml (similar)
zip_close(za);
}
void printProperties() {
for (const auto& p : properties) {
std::cout << p.first << ": " << p.second << std::endl;
}
}
void write(const std::string& newFilepath, const std::map<std::string, std::string>& modProps) {
// Similar to decode, but create new ZIP, rebuild XML strings
zip_t* zaOut = zip_open(newFilepath.c_str(), ZIP_CREATE | ZIP_TRUNCATE, nullptr);
// Add files, with rebuilt XML
std::string descXml = buildDescriptionXml();
zip_source_t* src = zip_source_buffer(zaOut, descXml.c_str(), descXml.size(), 0);
zip_add(zaOut, "description.xml", src);
// Etc.
zip_close(zaOut);
}
std::string buildDescriptionXml() {
return "<description><!-- Rebuilt --></description>";
}
// Similar for manifest
};
// Example:
// int main() {
// OXTHandler handler("example.oxt");
// handler.printProperties();
// handler.write("new.oxt", {{"version", "2.0"}});
// return 0;
// }
(Note: Full implementation requires handling lists/maps for complex properties; libraries like minizip/tinyxml2 need inclusion.)