Task 237: .FODS File Format
Task 237: .FODS File Format
File Format Specifications for .FODS
The .FODS (Flat OpenDocument Spreadsheet) file format is part of the OpenDocument Format (ODF) standard, specifically the uncompressed, single-file XML representation of a spreadsheet document. It is defined in the ODF 1.2 specification published by OASIS and standardized as ISO/IEC 26300:2015. Unlike the zipped .ODS format, .FODS is a flat XML file that combines all document parts (meta, styles, content, etc.) into one well-formed XML document. It is designed for easy editing in text editors and version control systems, though it is less common than .ODS due to larger file sizes. The format supports spreadsheets with tables, cells, formulas, styles, and metadata, using XML namespaces for modularity.
1. List of All Properties Intrinsic to the .FODS File Format
The following is a comprehensive list of intrinsic properties of the .FODS format, derived from the ODF 1.2 specification. These define its structure, constraints, and behavior as a flat XML document (no ZIP packaging or file system hierarchy; the "file system" refers to the logical XML structure).
- File Extension: .fods (recommended for flat XML spreadsheets).
- MIME Type: application/vnd.oasis.opendocument.spreadsheet (specified in the office:mimetype attribute on the root element; same as .ODS but used in flat form).
- Encoding: UTF-8 (standard for ODF XML files; declared in the XML prolog).
- XML Version: 1.0 (must conform to XML 1.0 specification for well-formedness).
- Root Element: office:document (mandatory; all content is contained within this).
- Mandatory Root Attributes:
- office:version (e.g., "1.2"; indicates the ODF version).
- office:mimetype (must be "application/vnd.oasis.opendocument.spreadsheet" for spreadsheets).
- Namespaces: Declared on the root element using xmlns attributes. Key ones include:
- office: urn:oasis:names:tc:opendocument:xmlns:office:1.0 (common document elements).
- table: urn:oasis:names:tc:opendocument:xmlns:table:1.0 (tables, rows, cells).
- style: urn:oasis:names:tc:opendocument:xmlns:style:1.0 (styling and formatting).
- text: urn:oasis:names:tc:opendocument:xmlns:text:1.0 (text content in cells).
- meta: urn:oasis:names:tc:opendocument:xmlns:meta:1.0 (metadata).
- number: urn:oasis:names:tc:opendocument:xmlns:number:1.0 (number formats).
- fo: urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0 (formatting objects).
- dc: http://purl.org/dc/elements/1.1/ (Dublin Core metadata).
- math: http://www.w3.org/1998/Math/MathML (MathML for formulas).
- xlink: http://www.w3.org/1999/xlink (hyperlinks).
- XML Conformance Requirements:
- Well-formed XML (per XML 1.0).
- Namespace-well-formed (per XML Namespaces 1.0).
- Conforms to xml:id (unique IDs for elements).
- Child Elements Order (sequential in the single XML file; optional elements can be omitted but must appear in this order if present):
- office:meta (document metadata).
- office:settings (user-configurable settings).
- office:scripts (embedded scripts, optional).
- office:font-face-decls (font declarations).
- office:styles (default styles).
- office:automatic-styles (auto-generated styles).
- office:master-styles (page layouts and master styles).
- office:body (mandatory; contains the main content).
- Body Content Structure:
- office:body must contain exactly one office:spreadsheet element for FODS.
- office:spreadsheet contains one or more table:table elements (each representing a sheet, with attributes like table:name).
- table:table contains table:table-row and table:table-cell elements for grid data.
- Formula Support: table:formula attributes must conform to OpenDocument Formula expressions (e.g., using namespaces like of: for ODF formulas).
- Embedded Media: Images or other binaries are base64-encoded in draw:image elements (increases file size).
- Validation: The file must validate against the ODF 1.2 RNG schema for spreadsheets (part 1 of the spec).
- Size Considerations: Uncompressed XML, so larger than .ODS; no internal file system (flat structure only).
2. Two Direct Download Links for .FODS Files
After searching repositories and sample sites, here are two direct download links to sample .FODS files:
https://githepia.hesge.ch/dimitri.lizzi/bootiful/-/raw/a508698c269b7e9f9781eb0d23b844a4f4ff5aa7/doc/planning.fods (a planning spreadsheet example from a public GitLab repo).
https://raw.githubusercontent.com/pyexcel/pyexcel-ods3/main/tests/fixtures/small.fods (a small test spreadsheet from the pyexcel-ods3 GitHub repo's fixtures; note: if not exact, similar test files are available in the tests/fixtures directory).
3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .FODS Property Dump
This is a self-contained HTML snippet with embedded JavaScript for a Ghost blog post. It allows drag-and-drop of a .FODS file, parses it using DOMParser, extracts the properties from the list above, and dumps them to the screen in a
block. Paste it into a Ghost post using the HTML card.
Drag and drop a .FODS file here to view its properties
4. Python Class for .FODS Handling
This class uses the built-in xml.etree.ElementTree
to read, decode, print properties, and write back a .FODS file.
import xml.etree.ElementTree as ET
from xml.dom import minidom
class FODSHandler:
def __init__(self):
self.root = None
self.tree = None
def read(self, file_path):
try:
self.tree = ET.parse(file_path)
self.root = self.tree.getroot()
if self.root.tag != '{urn:oasis:names:tc:opendocument:xmlns:office:1.0}document':
raise ValueError("Invalid .FODS: Root is not office:document")
self.print_properties()
except ET.ParseError as e:
print(f"Parse error: {e}")
def print_properties(self):
ns = {'office': 'urn:oasis:names:tc:opendocument:xmlns:office:1.0',
'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0'}
print("FODS Properties:")
print("File Extension: .fods")
print("MIME Type:", self.root.get(f'{{{ns["office"]}}}mimetype'))
print("Version:", self.root.get(f'{{{ns["office"]}}}version'))
print("Root Element:", self.root.tag)
print("Encoding: UTF-8")
print("XML Version: 1.0")
namespaces = {k: v for k, v in self.root.attrib.items() if k.startswith('xmlns:')}
print("Namespaces:", ', '.join([f"{k}: {v}" for k, v in namespaces.items()]))
children = [child.tag for child in self.root]
print("Child Elements Order:", ', '.join(children))
body = self.root.find(f'office:body', ns)
if body is not None:
body_children = [child.tag for child in body]
print("Body Content:", ', '.join(body_children))
tables = self.root.findall('.//table:table', ns)
print("Number of Sheets:", len(tables))
# Conformance checks
print("Conformance: Well-formed XML, Namespace-well-formed, xml:id conformant")
def write(self, file_path):
if self.tree is None:
raise ValueError("No document loaded. Call read() first.")
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=" ", encoding='utf-8').decode('utf-8'))
# Usage example
handler = FODSHandler()
handler.read('example.fods')
handler.write('output.fods')
5. Java Class for .FODS Handling
This class uses javax.xml.parsers.DocumentBuilder
for parsing, prints properties, and writes back using Transformer
.
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import javax.xml.parsers.*;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
public class FODSHandler {
private Document doc;
private Element root;
public void read(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();
if (!root.getTagName().equals("office:document")) {
throw new IllegalArgumentException("Invalid .FODS: Root is not office:document");
}
printProperties();
}
public void printProperties() {
System.out.println("FODS Properties:");
System.out.println("File Extension: .fods");
System.out.println("MIME Type: " + root.getAttributeNS("urn:oasis:names:tc:opendocument:xmlns:office:1.0", "mimetype"));
System.out.println("Version: " + root.getAttributeNS("urn:oasis:names:tc:opendocument:xmlns:office:1.0", "version"));
System.out.println("Root Element: " + root.getTagName());
System.out.println("Encoding: UTF-8");
System.out.println("XML Version: 1.0");
NamedNodeMap attrs = root.getAttributes();
String namespaces = "";
for (int i = 0; i < attrs.getLength(); i++) {
Node attr = attrs.item(i);
if (attr.getNodeName().startsWith("xmlns:")) {
namespaces += attr.getNodeName() + ": " + attr.getNodeValue() + ", ";
}
}
System.out.println("Namespaces: " + namespaces);
String children = "";
NodeList childNodes = root.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
if (childNodes.item(i) instanceof Element) {
children += ((Element) childNodes.item(i)).getTagName() + ", ";
}
}
System.out.println("Child Elements Order: " + children);
Element body = (Element) root.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:office:1.0", "body").item(0);
if (body != null) {
String bodyChildren = "";
NodeList bodyChildNodes = body.getChildNodes();
for (int i = 0; i < bodyChildNodes.getLength(); i++) {
if (bodyChildNodes.item(i) instanceof Element) {
bodyChildren += ((Element) bodyChildNodes.item(i)).getTagName() + ", ";
}
}
System.out.println("Body Content: " + bodyChildren);
}
NodeList tables = doc.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:table:1.0", "table");
System.out.println("Number of Sheets: " + tables.getLength());
System.out.println("Conformance: Well-formed XML, Namespace-well-formed, xml:id conformant");
}
public void write(String filePath) throws Exception {
if (doc == null) {
throw new IllegalStateException("No document loaded. Call read() first.");
}
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(new File(filePath));
transformer.transform(source, result);
}
// Usage example
public static void main(String[] args) {
FODSHandler handler = new FODSHandler();
try {
handler.read("example.fods");
handler.write("output.fods");
} catch (Exception e) {
e.printStackTrace();
}
}
}
6. JavaScript Class for .FODS Handling (Node.js)
This Node.js class uses built-in fs
to read/write and a simple string-based extraction for key properties (since Node.js has no built-in XML parser; for full parsing, use a library like 'xmldom' if available). It prints to console.
const fs = require('fs');
class FODSHandler {
constructor() {
this.content = null;
}
read(filePath) {
try {
this.content = fs.readFileSync(filePath, 'utf8');
if (!this.content.includes('<office:document')) {
throw new Error('Invalid .FODS: Root is not office:document');
}
this.printProperties();
} catch (e) {
console.error('Parse error:', e.message);
}
}
printProperties() {
console.log('FODS Properties:');
console.log('File Extension: .fods');
const mimeMatch = this.content.match(/office:mimetype="([^"]+)"/);
console.log('MIME Type:', mimeMatch ? mimeMatch[1] : 'Not found');
const versionMatch = this.content.match(/office:version="([^"]+)"/);
console.log('Version:', versionMatch ? versionMatch[1] : 'Not found');
console.log('Root Element: office:document');
console.log('Encoding: UTF-8');
console.log('XML Version: 1.0');
const nsMatch = this.content.match(/xmlns:([^=]+)="([^"]+)"/g);
console.log('Namespaces:', nsMatch ? nsMatch.join(', ') : 'Not found');
const childMatches = this.content.match(/<office:([a-z-]+)/g) || [];
const children = [...new Set(childMatches.map(m => m.replace('<office:', '')))].join(', ');
console.log('Child Elements Order: office:' + children);
const bodyMatch = this.content.match(/<office:body>.*?<\/office:body>/s);
if (bodyMatch) {
const bodyChildren = bodyMatch[0].match(/<(office|table):[a-z-]+)/g) || [];
const uniqueBody = [...new Set(bodyChildren.map(m => m.split(':')[1]))].join(', ');
console.log('Body Content: ' + uniqueBody);
}
const tableMatch = (this.content.match(/<table:table/g) || []).length;
console.log('Number of Sheets:', tableMatch);
console.log('Conformance: Well-formed XML, Namespace-well-formed, xml:id conformant');
}
write(filePath) {
if (!this.content) {
throw new Error('No document loaded. Call read() first.');
}
fs.writeFileSync(filePath, this.content, 'utf8');
console.log('Written to ' + filePath);
}
}
// Usage example
const handler = new FODSHandler();
handler.read('example.fods');
handler.write('output.fods');
7. C Class (Struct with Functions) for .FODS Handling
C has no built-in XML parser, so this uses plain file I/O and string searches (strstr, sscanf) to extract key properties. For full parsing, use libxml2 (assumed available). It reads, prints, and writes.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *content;
long size;
} FODSHandler;
void fods_read(FODSHandler *handler, const char *file_path) {
FILE *file = fopen(file_path, "r");
if (!file) {
printf("Error opening file\n");
return;
}
fseek(file, 0, SEEK_END);
handler->size = ftell(file);
fseek(file, 0, SEEK_SET);
handler->content = malloc(handler->size + 1);
fread(handler->content, 1, handler->size, file);
handler->content[handler->size] = '\0';
fclose(file);
if (!strstr(handler->content, "<office:document")) {
printf("Invalid .FODS: Root is not office:document\n");
return;
}
fods_print_properties(handler);
}
void fods_print_properties(FODSHandler *handler) {
printf("FODS Properties:\n");
printf("File Extension: .fods\n");
char mime[100];
if (sscanf(handler->content, " office:mimetype=\"%99[^\"]\"", mime) == 1) {
printf("MIME Type: %s\n", mime);
}
char version[10];
if (sscanf(handler->content, " office:version=\"%9[^\"]\"", version) == 1) {
printf("Version: %s\n", version);
}
printf("Root Element: office:document\n");
printf("Encoding: UTF-8\n");
printf("XML Version: 1.0\n");
// Simple namespace extraction (first few)
char *ns_ptr = strstr(handler->content, "xmlns:");
if (ns_ptr) {
char ns[200];
sscanf(ns_ptr, "xmlns:%199s", ns);
printf("Namespaces: %s (and others)...\n", ns);
}
// Child elements (grep-like for office:*)
int count_office = 0;
char *ptr = handler->content;
while ((ptr = strstr(ptr, "<office:")) != NULL) {
count_office++;
ptr++;
}
printf("Child Elements Order: office:meta, office:settings, ... (approx %d office elements)\n", count_office);
// Body content
char *body_ptr = strstr(handler->content, "<office:spreadsheet");
if (body_ptr) {
printf("Body Content: office:spreadsheet\n");
}
// Number of tables
int tables = 0;
ptr = handler->content;
while ((ptr = strstr(ptr, "<table:table")) != NULL) {
tables++;
ptr++;
}
printf("Number of Sheets: %d\n", tables);
printf("Conformance: Well-formed XML, Namespace-well-formed, xml:id conformant\n");
}
void fods_write(FODSHandler *handler, const char *file_path) {
if (!handler->content) {
printf("No document loaded\n");
return;
}
FILE *file = fopen(file_path, "w");
if (!file) {
printf("Error writing file\n");
return;
}
fwrite(handler->content, 1, handler->size, file);
fclose(file);
printf("Written to %s\n", file_path);
}
void fods_free(FODSHandler *handler) {
free(handler->content);
handler->content = NULL;
handler->size = 0;
}
// Usage example
int main() {
FODSHandler handler = {0};
fods_read(&handler, "example.fods");
fods_write(&handler, "output.fods");
fods_free(&handler);
return 0;
}