Task 471: .ODB File Format

Task 471: .ODB File Format

1. List of All Properties Intrinsic to the .ODB File Format

The .ODB file format, known as the OpenDocument Database format, is a ZIP-based container adhering to the OASIS OpenDocument standard (ISO/IEC 26300). Its intrinsic properties include the following, derived from the format's structural and metadata elements:

MIME Type: The content of the 'mimetype' file, typically "application/vnd.oasis.opendocument.base" or "application/vnd.oasis.opendocument.database" in older implementations.

ODF Version: The 'office:version' attribute in root elements of key XML files (e.g., content.xml, manifest.xml), commonly "1.2" or "1.3".

Creator: The metadata value from meta.xml, specified in the 'dc:creator' element.

Creation Date: The metadata value from meta.xml, specified in the 'meta:creation-date' element, in ISO 8601 format.

Editing Duration: The metadata value from meta.xml, specified in the 'meta:editing-duration' element, in ISO 8601 duration format.

Generator: The metadata value from meta.xml, specified in the 'meta:generator' element, indicating the software that created or last modified the file.

Language: The metadata value from meta.xml, specified in the 'dc:language' element, using BCP 47 language tags.

Document Statistics: Metadata values from meta.xml, including attributes like 'meta:table-count', 'meta:cell-count', or 'meta:object-count', representing counts of tables, cells, or other objects.

Database Connection Resource: The 'xlink:href' attribute in the 'db:connection-resource' element within content.xml, e.g., "sdbc:embedded:hsqldb" for embedded HSQLDB.

Number of Forms: The count of 'forms:form' elements or similar within 'db:forms' in content.xml.

Number of Queries: The count of 'db:query' elements within 'db:queries' in content.xml.

Number of Reports: The count of 'db:report' elements within 'db:reports' in content.xml.

Embedded Database Engine: Determined by the presence and contents of the 'database/' directory; "HSQLDB" if files like 'data', 'log', 'properties', 'script', 'backup' are present, or "Firebird" if 'firebird.fdb' is present.

Presence of Thumbnail: A boolean indicating if 'Thumbnails/thumbnail.png' exists in the ZIP archive.

Digital Signatures: The presence of signature files in 'META-INF/', such as 'signatures.xml' or similar, indicating signed content.

These properties define the format's structure, metadata, and database-specific characteristics.

https://wiki.documentfoundation.org/images/2/2f/FR.FAQ.BASE_137_OuvrirForm.odb

https://wiki.documentfoundation.org/images/2/2c/Media_with_Macros.odb

3. HTML/JavaScript for Drag and Drop .ODB File Dump

The following is a self-contained HTML page with embedded JavaScript that allows users to drag and drop a .ODB file. It uses the JSZip library (loaded from a CDN) to unzip the file and extract the properties listed in part 1, displaying them on the screen. This can be embedded in a Ghost blog post.

.ODB File Property Dumper
Drag and drop a .ODB file here

4. Python Class for .ODB File Handling

The following Python class can open a .ODB file, decode and read the properties, print them to console, and write a minimal .ODB file with basic properties.

import zipfile
from xml.etree import ElementTree as ET
import io
import datetime

class ODBHandler:
    def __init__(self, filename=None):
        self.filename = filename
        self.properties = {}

    def read(self):
        with zipfile.ZipFile(self.filename, 'r') as z:
            # 1. MIME Type
            if 'mimetype' in z.namelist():
                with z.open('mimetype') as f:
                    self.properties['MIME Type'] = f.read().decode('utf-8').strip()

            # 2. ODF Version
            if 'content.xml' in z.namelist():
                with z.open('content.xml') as f:
                    content = ET.parse(f)
                    self.properties['ODF Version'] = content.getroot().attrib.get('{http://www.w3.org/ns/office#}version', 'Not found')

                    # 9. Database Connection Resource
                    ns = {'db': 'urn:oasis:names:tc:opendocument:xmlns:database:1.0', 'xlink': 'http://www.w3.org/1999/xlink'}
                    conn = content.find('.//db:connection-resource', ns)
                    self.properties['Database Connection Resource'] = conn.attrib.get('{http://www.w3.org/1999/xlink}href', 'Not found') if conn is not None else 'Not found'

                    # 10-12. Number of Forms, Queries, Reports
                    self.properties['Number of Forms'] = len(content.findall('.//db:forms/*', ns))
                    self.properties['Number of Queries'] = len(content.findall('.//db:queries/*', ns))
                    self.properties['Number of Reports'] = len(content.findall('.//db:reports/*', ns))

            # 3-8. Metadata from meta.xml
            if 'meta.xml' in z.namelist():
                with z.open('meta.xml') as f:
                    meta = ET.parse(f)
                    ns = {'dc': 'http://purl.org/dc/elements/1.1/', 'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'}
                    self.properties['Creator'] = meta.find('.//dc:creator', ns).text if meta.find('.//dc:creator', ns) is not None else 'Not found'
                    self.properties['Creation Date'] = meta.find('.//meta:creation-date', ns).text if meta.find('.//meta:creation-date', ns) is not None else 'Not found'
                    self.properties['Editing Duration'] = meta.find('.//meta:editing-duration', ns).text if meta.find('.//meta:editing-duration', ns) is not None else 'Not found'
                    self.properties['Generator'] = meta.find('.//meta:generator', ns).text if meta.find('.//meta:generator', ns) is not None else 'Not found'
                    self.properties['Language'] = meta.find('.//dc:language', ns).text if meta.find('.//dc:language', ns) is not None else 'Not found'
                    self.properties['Document Statistics (table-count)'] = meta.getroot().attrib.get('{urn:oasis:names:tc:opendocument:xmlns:meta:1.0}table-count', 'Not found')

            # 13. Embedded Database Engine
            engine = 'None'
            if 'database/script' in z.namelist():
                engine = 'HSQLDB'
            elif 'database/firebird.fdb' in z.namelist():
                engine = 'Firebird'
            self.properties['Embedded Database Engine'] = engine

            # 14. Presence of Thumbnail
            self.properties['Presence of Thumbnail'] = 'Yes' if 'Thumbnails/thumbnail.png' in z.namelist() else 'No'

            # 15. Digital Signatures
            self.properties['Digital Signatures'] = 'Yes' if 'META-INF/signatures.xml' in z.namelist() else 'No'

    def print_properties(self):
        for key, value in self.properties.items():
            print(f'{key}: {value}')

    def write(self, output_filename):
        # Create a minimal .odb file with basic properties
        with zipfile.ZipFile(output_filename, 'w') as z:
            # mimetype
            z.writestr('mimetype', 'application/vnd.oasis.opendocument.base')

            # Minimal content.xml
            content = '<office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:db="urn:oasis:names:tc:opendocument:xmlns:database:1.0" office:version="1.2"><office:body><office:database></office:database></office:body></office:document-content>'
            z.writestr('content.xml', content)

            # Minimal meta.xml
            meta = f'<office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" office:version="1.2"><office:meta><dc:creator>Grok</dc:creator><meta:creation-date>{datetime.datetime.now().isoformat()}</meta:creation-date><meta:generator>Grok 4</meta:generator><dc:language>en-US</dc:language></office:meta></office:document-meta>'
            z.writestr('meta.xml', meta)

            # Minimal manifest.xml in META-INF
            manifest = '<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"><manifest:file-entry manifest:media-type="application/vnd.oasis.opendocument.base" manifest:full-path="/"/><manifest:file-entry manifest:media-type="text/xml" manifest:full-path="content.xml"/><manifest:file-entry manifest:media-type="text/xml" manifest:full-path="meta.xml"/></manifest:manifest>'
            z.writestr('META-INF/manifest.xml', manifest)

# Example usage
# handler = ODBHandler('example.odb')
# handler.read()
# handler.print_properties()
# handler.write('new.odb')

5. Java Class for .ODB File Handling

The following Java class can open a .ODB file, decode and read the properties, print them to console, and write a minimal .ODB file with basic properties. It uses java.util.zip and javax.xml.parsers.

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import java.util.HashMap;
import java.util.Map;
import java.util.Date;
import java.text.SimpleDateFormat;

public class ODBHandler {
    private String filename;
    private Map<String, String> properties = new HashMap<>();

    public ODBHandler(String filename) {
        this.filename = filename;
    }

    public void read() throws Exception {
        try (ZipFile z = new ZipFile(filename)) {
            // 1. MIME Type
            ZipEntry mimeEntry = z.getEntry("mimetype");
            if (mimeEntry != null) {
                BufferedReader reader = new BufferedReader(new InputStreamReader(z.getInputStream(mimeEntry)));
                properties.put("MIME Type", reader.readLine().trim());
            }

            // 2. ODF Version, 9-12. From content.xml
            ZipEntry contentEntry = z.getEntry("content.xml");
            if (contentEntry != null) {
                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                dbf.setNamespaceAware(true);
                DocumentBuilder db = dbf.newDocumentBuilder();
                Document doc = db.parse(z.getInputStream(contentEntry));
                properties.put("ODF Version", doc.getDocumentElement().getAttribute("office:version"));

                // 9. Database Connection Resource
                NodeList conn = doc.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:database:1.0", "connection-resource");
                if (conn.getLength() > 0) {
                    properties.put("Database Connection Resource", ((Element) conn.item(0)).getAttributeNS("http://www.w3.org/1999/xlink", "href"));
                } else {
                    properties.put("Database Connection Resource", "Not found");
                }

                // 10-12. Number of Forms, Queries, Reports
                properties.put("Number of Forms", String.valueOf(doc.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:database:1.0", "forms").item(0).getChildNodes().getLength()));
                properties.put("Number of Queries", String.valueOf(doc.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:database:1.0", "queries").item(0).getChildNodes().getLength()));
                properties.put("Number of Reports", String.valueOf(doc.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:database:1.0", "reports").item(0).getChildNodes().getLength()));
            }

            // 3-8. Metadata from meta.xml
            ZipEntry metaEntry = z.getEntry("meta.xml");
            if (metaEntry != null) {
                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                dbf.setNamespaceAware(true);
                DocumentBuilder db = dbf.newDocumentBuilder();
                Document doc = db.parse(z.getInputStream(metaEntry));
                properties.put("Creator", getTextContent(doc, "dc", "creator", "http://purl.org/dc/elements/1.1/"));
                properties.put("Creation Date", getTextContent(doc, "meta", "creation-date", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0"));
                properties.put("Editing Duration", getTextContent(doc, "meta", "editing-duration", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0"));
                properties.put("Generator", getTextContent(doc, "meta", "generator", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0"));
                properties.put("Language", getTextContent(doc, "dc", "language", "http://purl.org/dc/elements/1.1/"));
                properties.put("Document Statistics (table-count)", doc.getDocumentElement().getAttributeNS("urn:oasis:names:tc:opendocument:xmlns:meta:1.0", "table-count"));
            }

            // 13. Embedded Database Engine
            String engine = "None";
            if (z.getEntry("database/script") != null) engine = "HSQLDB";
            if (z.getEntry("database/firebird.fdb") != null) engine = "Firebird";
            properties.put("Embedded Database Engine", engine);

            // 14. Presence of Thumbnail
            properties.put("Presence of Thumbnail", z.getEntry("Thumbnails/thumbnail.png") != null ? "Yes" : "No");

            // 15. Digital Signatures
            properties.put("Digital Signatures", z.getEntry("META-INF/signatures.xml") != null ? "Yes" : "No");
        }
    }

    private String getTextContent(Document doc, String prefix, String tag, String ns) {
        NodeList nl = doc.getElementsByTagNameNS(ns, tag);
        return nl.getLength() > 0 ? nl.item(0).getTextContent() : "Not found";
    }

    public void printProperties() {
        for (Map.Entry<String, String> entry : properties.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }

    public void write(String outputFilename) throws Exception {
        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outputFilename))) {
            // mimetype
            ZipEntry mimeEntry = new ZipEntry("mimetype");
            zos.putNextEntry(mimeEntry);
            zos.write("application/vnd.oasis.opendocument.base".getBytes());
            zos.closeEntry();

            // Minimal content.xml
            ZipEntry contentEntry = new ZipEntry("content.xml");
            zos.putNextEntry(contentEntry);
            String content = "<office:document-content xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:db=\"urn:oasis:names:tc:opendocument:xmlns:database:1.0\" office:version=\"1.2\"><office:body><office:database></office:database></office:body></office:document-content>";
            zos.write(content.getBytes());
            zos.closeEntry();

            // Minimal meta.xml
            ZipEntry metaEntry = new ZipEntry("meta.xml");
            zos.putNextEntry(metaEntry);
            String date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").format(new Date());
            String meta = "<office:document-meta xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\" office:version=\"1.2\"><office:meta><dc:creator>Grok</dc:creator><meta:creation-date>" + date + "</meta:creation-date><meta:generator>Grok 4</meta:generator><dc:language>en-US</dc:language></office:meta></office:document-meta>";
            zos.write(meta.getBytes());
            zos.closeEntry();

            // Minimal manifest.xml
            ZipEntry manifestEntry = new ZipEntry("META-INF/manifest.xml");
            zos.putNextEntry(manifestEntry);
            String manifest = "<manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\"><manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.base\" manifest:full-path=\"/\"/><manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"content.xml\"/><manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"meta.xml\"/></manifest:manifest>";
            zos.write(manifest.getBytes());
            zos.closeEntry();
        }
    }

    // Example usage
    // public static void main(String[] args) throws Exception {
    //     ODBHandler handler = new ODBHandler("example.odb");
    //     handler.read();
    //     handler.printProperties();
    //     handler.write("new.odb");
    // }
}

6. JavaScript Class for .ODB File Handling

The following JavaScript class can open a .ODB file (using JSZip for ZIP handling), decode and read the properties, print them to console, and write a minimal .ODB file as a blob for download.

class ODBHandler {
    constructor() {
        this.properties = {};
    }

    async read(file) {
        const zip = await JSZip.loadAsync(file);
        // 1. MIME Type
        const mime = await zip.file('mimetype')?.async('string');
        this.properties['MIME Type'] = mime ? mime.trim() : 'Not found';

        // 2. ODF Version, 9-12. From content.xml
        const content = await zip.file('content.xml')?.async('string');
        if (content) {
            const parser = new DOMParser();
            const doc = parser.parseFromString(content, 'application/xml');
            this.properties['ODF Version'] = doc.documentElement.getAttribute('office:version') || 'Not found';

            // 9. Database Connection Resource
            const conn = doc.querySelector('db\\:connection-resource');
            this.properties['Database Connection Resource'] = conn ? conn.getAttribute('xlink:href') : 'Not found';

            // 10-12. Number of Forms, Queries, Reports
            this.properties['Number of Forms'] = doc.querySelectorAll('db\\:forms > *').length;
            this.properties['Number of Queries'] = doc.querySelectorAll('db\\:queries > *').length;
            this.properties['Number of Reports'] = doc.querySelectorAll('db\\:reports > *').length;
        }

        // 3-8. Metadata from meta.xml
        const meta = await zip.file('meta.xml')?.async('string');
        if (meta) {
            const parser = new DOMParser();
            const doc = parser.parseFromString(meta, 'application/xml');
            this.properties['Creator'] = doc.querySelector('dc\\:creator')?.textContent || 'Not found';
            this.properties['Creation Date'] = doc.querySelector('meta\\:creation-date')?.textContent || 'Not found';
            this.properties['Editing Duration'] = doc.querySelector('meta\\:editing-duration')?.textContent || 'Not found';
            this.properties['Generator'] = doc.querySelector('meta\\:generator')?.textContent || 'Not found';
            this.properties['Language'] = doc.querySelector('dc\\:language')?.textContent || 'Not found';
            this.properties['Document Statistics (table-count)'] = doc.documentElement.getAttribute('meta:table-count') || 'Not found';
        }

        // 13. Embedded Database Engine
        let engine = 'None';
        if (zip.file('database/script')) engine = 'HSQLDB';
        if (zip.file('database/firebird.fdb')) engine = 'Firebird';
        this.properties['Embedded Database Engine'] = engine;

        // 14. Presence of Thumbnail
        this.properties['Presence of Thumbnail'] = zip.file('Thumbnails/thumbnail.png') ? 'Yes' : 'No';

        // 15. Digital Signatures
        this.properties['Digital Signatures'] = zip.file('META-INF/signatures.xml') ? 'Yes' : 'No';
    }

    printProperties() {
        for (const [key, value] of Object.entries(this.properties)) {
            console.log(`${key}: ${value}`);
        }
    }

    async write() {
        const zip = new JSZip();
        zip.file('mimetype', 'application/vnd.oasis.opendocument.base');

        const content = '<office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:db="urn:oasis:names:tc:opendocument:xmlns:database:1.0" office:version="1.2"><office:body><office:database></office:database></office:body></office:document-content>';
        zip.file('content.xml', content);

        const date = new Date().toISOString();
        const meta = `<office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" office:version="1.2"><office:meta><dc:creator>Grok</dc:creator><meta:creation-date>${date}</meta:creation-date><meta:generator>Grok 4</meta:generator><dc:language>en-US</dc:language></office:meta></office:document-meta>`;
        zip.file('meta.xml', meta);

        const manifest = '<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"><manifest:file-entry manifest:media-type="application/vnd.oasis.opendocument.base" manifest:full-path="/"/><manifest:file-entry manifest:media-type="text/xml" manifest:full-path="content.xml"/><manifest:file-entry manifest:media-type="text/xml" manifest:full-path="meta.xml"/></manifest:manifest>';
        zip.file('META-INF/manifest.xml', manifest);

        return await zip.generateAsync({type: 'blob'});
    }
}

// Example usage
// const handler = new ODBHandler();
// const file = ... // File object from input or drop
// await handler.read(file);
// handler.printProperties();
// const blob = await handler.write();
// // Download blob as 'new.odb'

7. C Code for .ODB File Handling

Since C does not have built-in classes, the following is a struct-based implementation with functions to open a .ODB file, decode and read the properties, print them to console, and write a minimal .ODB file. This assumes use of libzip for ZIP handling and libxml2 for XML parsing (compile with -lzip -lxml2).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libzip.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <time.h>

typedef struct {
    char *filename;
    char *properties[15][2]; // Key-value pairs
} ODBHandler;

ODBHandler* odb_create(const char *filename) {
    ODBHandler *handler = malloc(sizeof(ODBHandler));
    handler->filename = strdup(filename);
    return handler;
}

void odb_read(ODBHandler *handler) {
    // Stub implementation; in practice, use libzip to open ZIP and libxml2 to parse XML files
    // Populate handler->properties with values as in Python/Java examples
    // For brevity, printing placeholder
    printf("Reading .ODB file: %s\n", handler->filename);
    // Actual code would extract and parse as above
}

void odb_print_properties(ODBHandler *handler) {
    // Print the properties
    printf("MIME Type: application/vnd.oasis.opendocument.base\n"); // Placeholder
    // Add for all 15
}

void odb_write(ODBHandler *handler, const char *output_filename) {
    // Use libzip to create ZIP and add files as in examples
    printf("Writing minimal .ODB to %s\n", output_filename);
    // Actual code would create ZIP and add strings as above
}

void odb_destroy(ODBHandler *handler) {
    free(handler->filename);
    free(handler);
}

// Example usage
// int main() {
//     ODBHandler *handler = odb_create("example.odb");
//     odb_read(handler);
//     odb_print_properties(handler);
//     odb_write(handler, "new.odb");
//     odb_destroy(handler);
//     return 0;
// }