Task 235: .FODG File Format

Task 235: .FODG File Format

File Format Specifications for .FODG

The .FODG file format is the flat XML representation of the OpenDocument Graphics (drawing) format, part of the OpenDocument Format (ODF) family. It is defined in the OASIS OpenDocument Format for Office Applications (OpenDocument) Version 1.3 specification (ISO/IEC 26300:2015). The format is an XML-based vector graphics file used for storing drawings, diagrams, and illustrations, primarily in applications like LibreOffice Draw and Apache OpenOffice Draw. Unlike the zipped .ODG format, .FODG is a single, uncompressed XML file for easier parsing and editing.

The specification is available in four parts:

  • Part 1: Introduction (overview).
  • Part 2: Packages (packaging and manifest for flat XML).
  • Part 3: Schema (detailed XML schema, including the draw namespace).
  • Part 4: Accessibility.

The full specification can be downloaded from the OASIS website: OpenDocument v1.3 Part 1, Part 2, Part 3, Part 4.

1. List of All Properties Intrinsic to Its File System

The .FODG format is XML-based, so its "file system" is the XML structure itself (no internal file system like ZIP in .ODG). Intrinsic properties include the MIME type, structure, namespaces, root elements, and key elements/attributes from the draw namespace (for graphics) and related namespaces. Below is a complete list based on the ODF 1.3 schema (Part 3, Chapter 19: Drawing):

General Properties:

  • MIME type: application/vnd.oasis.opendocument.graphics-flat-xml
  • File extension: .fodg
  • XML version: 1.0 (or later)
  • Character encoding: UTF-8
  • Office version attribute: office:version="1.3" (required on root element)
  • RelaxNG schema location: Defined in ODF Part 3

Namespaces (required for parsing):

  • office: urn:oasis:names:tc:opendocument:xmlns:office:1.0
  • style: urn:oasis:names:tc:opendocument:xmlns:style:1.0
  • draw: urn:oasis:names:tc:opendocument:xmlns:drawing:1.0
  • svg: http://www.w3.org/2000/svg
  • fo: http://www.w3.org/1999/XSL/Format
  • dc: http://purl.org/dc/elements/1.1/
  • meta: urn:oasis:names:tc:opendocument:xmlns:meta:1.0
  • number: urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0
  • presentation: urn:oasis:names:tc:opendocument:xmlns:presentation:1.0
  • chart: urn:oasis:names:tc:opendocument:xmlns:chart:1.0
  • dr3d: urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0
  • math: http://www.w3.org/1998/Math/MathML
  • form: urn:oasis:names:tc:opendocument:xmlns:form:1.0
  • script: urn:oasis:names:tc:opendocument:xmlns:script:1.0
  • ooo: http://openoffice.org/2004/office
  • ooow: http://openoffice.org/2004/writer
  • oooc: http://openoffice.org/2004/calc
  • officeooo: http://openoffice.org/2004/office
  • rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns#
  • rdfa: http://www.w3.org/ns/rdfa#
  • xlink: http://www.w3.org/1999/xlink
  • manifest: urn:oasis:names:tc:opendocument:xmlns:manifest:1.0

Root Element and Structure:

  • Root: <office:document>
  • Child sections (in order):
  • <office:meta>: Metadata (e.g., <meta:generator>, <dc:title>, <dc:creator>)
  • <office:scripts>: Embedded scripts
  • <office:font-face-decls>: Font declarations (e.g., <style:font-face>)
  • <office:styles>: Default styles (e.g., <style:style style:family="graphic">)
  • <office:automatic-styles>: Auto-generated styles
  • <office:master-styles>: Master pages (e.g., <style:master-page style:name="Standard" style:page-layout-name="...">)
  • <office:body>: Main content
  • <office:draw>: Drawing container
  • <draw:page draw:name="Page1" draw:style-name="dp1" draw:master-page-name="Standard">: Page elements
  • <draw:layer draw:name="layout">: Layer for shapes
  • Shapes and groups (see below)

Key Elements in draw Namespace (all intrinsic to graphics):

  • draw:page
  • draw:layer
  • draw:layer-set
  • draw:frame
  • draw:group
  • draw:line
  • draw:rect
  • draw:ellipse
  • draw:circle
  • draw:polygon
  • draw:polyline
  • draw:path
  • draw:caption
  • draw:custom-shape
  • draw:connector
  • draw:measure
  • draw:control
  • draw:text-box
  • draw:image
  • draw:chart
  • draw:object
  • draw:object-ole
  • draw:applet
  • draw:floating-frame
  • draw:plugin
  • draw:contour-polygon
  • draw:enhanced-geometry
  • draw:glue-point
  • draw:handle
  • draw:equation
  • draw:annotation
  • draw:line-style
  • draw:marker
  • draw:gradient
  • draw:stroke-dash
  • draw:hatch
  • draw:fill-image
  • draw:opacity
  • draw:bitmap-table
  • draw:param
  • draw:models
  • draw:scene
  • draw:light
  • draw:cube
  • draw:sphere
  • draw:extrude
  • draw:rotate
  • draw:lathe

Common Attributes in draw, style, svg Namespaces (used on elements for positioning, styling, etc.):

  • Positioning: svg:x, svg:y, svg:width, svg:height, svg:viewBox, svg:min-x, svg:min-y, svg:transform
  • Styling: draw:style-name, draw:text-style-name, style:family, draw:fill, draw:fill-color, draw:fill-gradient-name, draw:fill-hatch-name, draw:fill-image-name, draw:fill-opacity, draw:stroke, draw:color, draw:line-width, draw:line-style, draw:line-cap, draw:line-join, draw:marker-start, draw:marker-end, draw:opacity, draw:background-color, draw:shadow-offset-x, draw:shadow-offset-y, draw:mirror-horizontal, draw:mirror-vertical, draw:rotation-angle
  • Layering: draw:layer, draw:z-index
  • Linking: xlink:href, xlink:type, xlink:show, xlink:actuate
  • Text: draw:text-anchor-type, fo:wrap-option, style:text-outline, style:text-line-through-type
  • Advanced: draw:chain-next-name, draw:glue-point-type, draw:math-type, draw:display-name, draw:protected, draw:print, draw:wrap-contour, draw:leader-style, draw:transition-type, draw:visibility, draw:bookmark, draw:perspective-3d (and sub-attributes like draw:perspective-3d-shade-mode, draw:perspective-3d-texture-mode)

These properties define the core structure and capabilities for vector graphics in .FODG.

After searching, sample .FODG files are rare because the format is less common than .ODG. Here are two direct downloads from public repositories and test suites:

3. Ghost Blog Embedded HTML JavaScript for Drag 'n' Drop .FODG Property Dump

This is a self-contained HTML snippet with JavaScript for embedding in a Ghost blog post (use the HTML card in Ghost editor). It allows drag-and-drop of a .FODG file, parses the XML using DOMParser, extracts and dumps all properties from the list above to the screen (in a

block for readability). It focuses on structural properties, namespaces, elements, and attributes.

Drag and drop a .FODG file here to dump its properties



4. Python Class for .FODG Handling

This class uses xml.etree.ElementTree to open, decode (parse), read (extract properties), write (save modified XML), and print all properties to console. It traverses the XML to collect all listed properties.

import xml.etree.ElementTree as ET
from xml.dom import minidom

class FODGHandler:
    def __init__(self, file_path=None):
        self.tree = None
        self.root = None
        if file_path:
            self.load(file_path)

    def load(self, file_path):
        self.tree = ET.parse(file_path)
        self.root = self.tree.getroot()
        print("Loaded .FODG file:", file_path)

    def extract_properties(self):
        props = {}
        # General
        props['mime_type'] = 'application/vnd.oasis.opendocument.graphics-flat-xml'
        props['extension'] = '.fodg'
        props['office_version'] = self.root.get('office:version', 'Unknown')
        # Namespaces
        props['namespaces'] = dict((k, v) for k, v in self.root.attrib.items() if k.startswith('xmlns:'))
        # Structure
        props['root_element'] = self.root.tag
        props['meta_present'] = bool(self.root.find('office:meta'))
        props['scripts_present'] = bool(self.root.find('office:scripts'))
        props['font_face_count'] = len(self.root.findall('.//style:font-face', self.root.nsmap or {}))
        props['styles_count'] = len(self.root.findall('.//style:style', self.root.nsmap or {}))
        props['automatic_styles_count'] = len(self.root.findall('.//office:automatic-styles/style:style', self.root.nsmap or {}))
        props['master_styles_count'] = len(self.root.findall('.//style:master-page', self.root.nsmap or {}))
        # Pages
        ns = {'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0'}
        props['pages'] = []
        for page in self.root.findall('.//draw:page', ns):
            props['pages'].append({
                'name': page.get('draw:name'),
                'style_name': page.get('draw:style-name'),
                'master_page_name': page.get('draw:master-page-name')
            })
        # Draw elements
        draw_elements = ['frame', 'group', 'line', 'rect', 'ellipse', 'circle', 'polygon', 'polyline', 'path', 'custom-shape', 'connector', 'text-box', 'image']
        props['draw_elements'] = {el: len(self.root.findall(f'.//draw:{el}', ns)) for el in draw_elements}
        # Sample attributes from first shape
        first_shape = self.root.find('.//draw:rect', ns) or self.root.find('.//draw:line', ns) or self.root.find('.//draw:frame', ns)
        if first_shape is not None:
            props['sample_attributes'] = dict(first_shape.attrib)
        return props

    def print_properties(self):
        props = self.extract_properties()
        import json
        print(json.dumps(props, indent=2))

    def write(self, file_path, modified_props=None):
        if modified_props:
            # Example: Modify a property, e.g., add version if missing
            if 'office_version' in modified_props:
                self.root.set('office:version', modified_props['office_version'])
        rough_string = ET.tostring(self.root, 'unicode')
        reparsed = minidom.parseString(rough_string)
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(reparsed.toprettyxml(indent='  '))
        print("Wrote .FODG file:", file_path)

# Usage example:
# handler = FODGHandler('sample.fodg')
# handler.print_properties()
# handler.write('modified.fodg', {'office_version': '1.3'})

5. Java Class for .FODG Handling

This class uses javax.xml.parsers.DocumentBuilder for parsing, extracts properties, prints to console, and writes back using Transformer. Requires Java 8+.

import org.w3c.dom.*;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class FODGHandler {
    private Document doc;
    private Element root;

    public FODGHandler(String filePath) throws ParserConfigurationException, SAXException, IOException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        DocumentBuilder builder = factory.newDocumentBuilder();
        this.doc = builder.parse(new File(filePath));
        this.root = doc.getDocumentElement();
        System.out.println("Loaded .FODG file: " + filePath);
    }

    public Map<String, Object> extractProperties() {
        Map<String, Object> props = new HashMap<>();
        // General
        props.put("mimeType", "application/vnd.oasis.opendocument.graphics-flat-xml");
        props.put("extension", ".fodg");
        props.put("officeVersion", root.getAttribute("office:version"));
        // Namespaces
        Map<String, String> namespaces = new HashMap<>();
        NamedNodeMap attrs = root.getAttributes();
        for (int i = 0; i < attrs.getLength(); i++) {
            Attr attr = (Attr) attrs.item(i);
            if (attr.getName().startsWith("xmlns:")) {
                namespaces.put(attr.getName().substring(6), attr.getValue());
            }
        }
        props.put("namespaces", namespaces);
        // Structure
        props.put("rootElement", root.getTagName());
        props.put("metaPresent", root.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:office:1.0", "meta").getLength() > 0);
        props.put("scriptsPresent", root.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:office:1.0", "scripts").getLength() > 0);
        props.put("fontFaceCount", root.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:style:1.0", "font-face").getLength());
        props.put("stylesCount", root.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:style:1.0", "style").getLength());
        // Pages
        NodeList pages = root.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0", "page");
        Map<String, Object> pageList = new HashMap<>();
        for (int i = 0; i < pages.getLength(); i++) {
            Element page = (Element) pages.item(i);
            pageList.put("page" + i, Map.of(
                "name", page.getAttribute("draw:name"),
                "styleName", page.getAttribute("draw:style-name"),
                "masterPageName", page.getAttribute("draw:master-page-name")
            ));
        }
        props.put("pages", pageList);
        // Draw elements (example for a few)
        String[] drawEls = {"frame", "group", "line", "rect", "ellipse", "circle", "polygon", "polyline", "path", "custom-shape", "connector", "text-box", "image"};
        Map<String, Integer> drawElements = new HashMap<>();
        for (String el : drawEls) {
            drawElements.put(el, root.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0", el).getLength());
        }
        props.put("drawElements", drawElements);
        // Sample attributes
        Element firstShape = (Element) root.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0", "rect").item(0);
        if (firstShape == null) firstShape = (Element) root.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0", "line").item(0);
        if (firstShape != null) {
            Map<String, String> sampleAttrs = new HashMap<>();
            for (int j = 0; j < firstShape.getAttributes().getLength(); j++) {
                Attr attr = (Attr) firstShape.getAttributes().item(j);
                sampleAttrs.put(attr.getName(), attr.getValue());
            }
            props.put("sampleAttributes", sampleAttrs);
        }
        return props;
    }

    public void printProperties() {
        System.out.println(new com.google.gson.GsonBuilder().setPrettyPrinting().create().toJson(extractProperties()));
    }

    public void write(String filePath, Map<String, Object> modifiedProps) throws TransformerException {
        if (modifiedProps.containsKey("officeVersion")) {
            root.setAttribute("office:version", (String) modifiedProps.get("officeVersion"));
        }
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        DOMSource source = new DOMSource(doc);
        StreamResult result = new StreamResult(new File(filePath));
        transformer.transform(source, result);
        System.out.println("Wrote .FODG file: " + filePath);
    }

    // Usage: FODGHandler handler = new FODGHandler("sample.fodg");
    // handler.printProperties();
    // handler.write("modified.fodg", Map.of("officeVersion", "1.3"));
}

6. JavaScript Class for .FODG Handling

This class uses DOMParser for browser/Node.js, extracts properties, prints to console, and writes (as string, for Node.js use fs to save). Assumes browser environment for console.

class FODGHandler {
  constructor(filePath = null) {
    this.doc = null;
    this.root = null;
    if (filePath) {
      this.load(filePath);
    }
  }

  async load(filePath) {
    const response = await fetch(filePath);
    const xmlText = await response.text();
    const parser = new DOMParser();
    this.doc = parser.parseFromString(xmlText, 'text/xml');
    this.root = this.doc.documentElement;
    if (this.doc.querySelector('parsererror')) {
      throw new Error('Invalid XML');
    }
    console.log('Loaded .FODG file:', filePath);
  }

  extractProperties() {
    const props = {};
    // General
    props.mimeType = 'application/vnd.oasis.opendocument.graphics-flat-xml';
    props.extension = '.fodg';
    props.officeVersion = this.root.getAttribute('office:version') || 'Unknown';
    // Namespaces
    props.namespaces = {};
    for (let attr of this.root.attributes) {
      if (attr.name.startsWith('xmlns:')) {
        props.namespaces[attr.name.substring(6)] = attr.value;
      }
    }
    // Structure
    props.rootElement = this.root.tagName;
    props.metaPresent = !!this.root.querySelector('office\\:meta');
    props.scriptsPresent = !!this.root.querySelector('office\\:scripts');
    props.fontFaceCount = this.root.querySelectorAll('style\\:font-face').length;
    props.stylesCount = this.root.querySelectorAll('style\\:style').length;
    props.automaticStylesCount = this.root.querySelectorAll('office\\:automatic-styles style\\:style').length;
    props.masterStylesCount = this.root.querySelectorAll('style\\:master-page').length;
    // Pages
    const nsResolver = prefix => {
      const ns = { 'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0' };
      return ns[prefix] || null;
    };
    props.pages = Array.from(this.root.evaluate('.//draw:page', this.root, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)).map((page, i) => ({
      name: page.getAttribute('draw:name'),
      styleName: page.getAttribute('draw:style-name'),
      masterPageName: page.getAttribute('draw:master-page-name')
    }));
    // Draw elements
    const drawElements = ['frame', 'group', 'line', 'rect', 'ellipse', 'circle', 'polygon', 'polyline', 'path', 'custom-shape', 'connector', 'text-box', 'image'];
    props.drawElements = drawElements.reduce((acc, el) => {
      acc[el] = this.root.evaluate(`.//draw:${el}`, this.root, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotLength;
      return acc;
    }, {});
    // Sample attributes
    const firstShape = this.root.evaluate('.//draw:rect | .//draw:line | .//draw:frame', this.root, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    if (firstShape) {
      props.sampleAttributes = Array.from(firstShape.attributes).reduce((acc, attr) => {
        acc[attr.name] = attr.value;
        return acc;
      }, {});
    }
    return props;
  }

  printProperties() {
    console.log(JSON.stringify(this.extractProperties(), null, 2));
  }

  write(filePath, modifiedProps = {}) {
    if (modifiedProps.officeVersion) {
      this.root.setAttribute('office:version', modifiedProps.officeVersion);
    }
    const serializer = new XMLSerializer();
    const xmlStr = '<?xml version="1.0" encoding="UTF-8"?>\n' + serializer.serializeToString(this.doc);
    // In Node.js, use fs.writeFileSync(filePath, xmlStr);
    console.log('XML content for write:', xmlStr.substring(0, 500) + '...');
    console.log('Wrote .FODG file:', filePath);
  }
}

// Usage (browser/Node):
// const handler = new FODGHandler('sample.fodg');
// handler.printProperties();
// handler.write('modified.fodg', { officeVersion: '1.3' });

7. C Class for .FODG Handling

This C struct/class uses libxml2 for parsing (compile with gcc -o fodg_handler fodg_handler.c -lxml2). It extracts properties, prints to stdout, and writes back. Assume libxml2 installed.

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

typedef struct {
    xmlDocPtr doc;
    xmlNodePtr root;
} FODGHandler;

FODGHandler* fodg_load(const char* file_path) {
    FODGHandler* handler = malloc(sizeof(FODGHandler));
    handler->doc = xmlReadFile(file_path, NULL, 0);
    if (handler->doc == NULL) {
        fprintf(stderr, "Error loading file\n");
        free(handler);
        return NULL;
    }
    handler->root = xmlDocGetRootElement(handler->doc);
    printf("Loaded .FODG file: %s\n", file_path);
    return handler;
}

void fodg_extract_and_print_properties(FODGHandler* handler) {
    if (!handler || !handler->root) return;
    // General
    printf("MIME Type: application/vnd.oasis.opendocument.graphics-flat-xml\n");
    printf("Extension: .fodg\n");
    xmlChar* version = xmlGetProp(handler->root, (xmlChar*)"office:version");
    printf("Office Version: %s\n", version ? (char*)version : "Unknown");
    xmlFree(version);
    // Namespaces (print first few)
    printf("Namespaces:\n");
    for (xmlAttrPtr attr = handler->root->properties; attr; attr = attr->next) {
        if (attr->name && strncmp((char*)attr->name, "xmlns:", 6) == 0) {
            printf("  %s: %s\n", attr->name, xmlGetProp(handler->root, attr->name));
        }
    }
    // Structure (use XPath for counts)
    xmlXPathContextPtr context = xmlXPathNewContext(handler->doc);
    context->node = (xmlNodePtr) handler->root;
    // Meta
    xmlXPathObjectPtr meta = xmlXPathEvalExpression((xmlChar*)"//office:meta", context);
    printf("Meta Present: %s\n", meta->nodesetval ? "Yes" : "No");
    xmlXPathFreeObject(meta);
    // Styles count
    xmlXPathObjectPtr styles = xmlXPathEvalExpression((xmlChar*)"//style:style", context);
    printf("Styles Count: %d\n", styles->nodesetval ? styles->nodesetval->nodeNr : 0);
    xmlXPathFreeObject(styles);
    // Pages
    xmlXPathObjectPtr pages = xmlXPathEvalExpression((xmlChar*)"//draw:page", context);
    printf("Pages Count: %d\n", pages->nodesetval ? pages->nodesetval->nodeNr : 0);
    for (int i = 0; i < (pages->nodesetval ? pages->nodesetval->nodeNr : 0); i++) {
        xmlNodePtr page = pages->nodesetval->nodeTab[i];
        xmlChar* name = xmlGetProp(page, (xmlChar*)"draw:name");
        printf("  Page %d Name: %s\n", i, name ? (char*)name : "Unknown");
        xmlFree(name);
    }
    xmlXPathFreeObject(pages);
    // Draw elements (example rect)
    xmlXPathObjectPtr rects = xmlXPathEvalExpression((xmlChar*)"//draw:rect", context);
    printf("Rect Count: %d\n", rects->nodesetval ? rects->nodesetval->nodeNr : 0);
    xmlXPathFreeObject(rects);
    // Sample attribute
    xmlChar* x = xmlGetProp(pages->nodesetval ? pages->nodesetval->nodeTab[0] : NULL, (xmlChar*)"svg:x");
    printf("Sample Attribute svg:x: %s\n", x ? (char*)x : "None");
    xmlFree(x);
    xmlXPathFreeContext(context);
}

void fodg_write(FODGHandler* handler, const char* file_path, const char* mod_version) {
    if (mod_version) {
        xmlSetProp(handler->root, (xmlChar*)"office:version", (xmlChar*)mod_version);
    }
    int success = xmlSaveFormatFileEnc(file_path, handler->doc, "UTF-8", 1);
    if (success != -1) {
        printf("Wrote .FODG file: %s\n", file_path);
    } else {
        fprintf(stderr, "Write failed\n");
    }
}

void fodg_free(FODGHandler* handler) {
    if (handler) {
        xmlFreeDoc(handler->doc);
        free(handler);
    }
}

int main(int argc, char** argv) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s <fodg_file>\n", argv[0]);
        return 1;
    }
    FODGHandler* handler = fodg_load(argv[1]);
    if (handler) {
        fodg_extract_and_print_properties(handler);
        fodg_write(handler, "modified.fodg", "1.3");
        fodg_free(handler);
    }
    xmlCleanupParser();
    return 0;
}