Task 236: .FODP File Format

Task 236: .FODP File Format

  1. The .FODP file format is a flat XML representation of an OpenDocument Presentation, as defined in the OASIS OpenDocument standard. The properties intrinsic to this format, derived from its XML structure and schema, include the following key attributes and metadata elements. These are extracted from the root element and the office:meta section, which define the document's core characteristics and metadata:
  • Document Version: The value of the office:version attribute on the root office:document element, indicating the OpenDocument specification version (e.g., "1.3").
  • MIME Type: The value of the office:mimetype attribute on the root office:document element, typically "application/vnd.oasis.opendocument.presentation" for .FODP files.
  • Generator: The text content of the meta:generator element, specifying the software that created or last modified the document.
  • Creation Date: The text content of the meta:creation-date element, representing the date and time the document was created.
  • Initial Creator: The text content of the meta:initial-creator element, identifying the original author of the document.
  • Last Modified Date: The text content of the dc:date element, indicating the date and time of the last modification.
  • Last Modified By: The text content of the dc:creator element, identifying the user who last modified the document.
  • Print Date: The text content of the meta:print-date element, recording the date and time the document was last printed.
  • Printed By: The text content of the meta:printed-by element, identifying the user who last printed the document.
  • Title: The text content of the dc:title element, providing the document's title.
  • Subject: The text content of the dc:subject element, describing the document's subject.
  • Description: The text content of the dc:description element, offering a brief description or comments about the document.
  • Keywords: The text content of one or more meta:keyword elements, listing keywords associated with the document.
  • Language: The text content of the dc:language element, specifying the primary language used in the document.
  • Editing Cycles: The text content of the meta:editing-cycles element, indicating the number of editing sessions.
  • Editing Duration: The text content of the meta:editing-duration element, representing the total time spent editing the document.
  • User-Defined Metadata: The text content and meta:name attributes of one or more meta:user-defined elements, allowing custom metadata fields.
  • Document Statistics: The attributes of the meta:document-statistic element, including meta:page-count (number of pages/slides), meta:object-count (number of objects), meta:image-count (number of images), meta:table-count (number of tables), and similar counts for other elements.

Two direct download links for .FODP files are as follows:

The following is an HTML document with embedded JavaScript suitable for embedding in a Ghost blog post. It enables users to drag and drop a .FODP file, parses the XML, extracts the properties listed above, and displays them on the screen.

FODP Property Dumper

Drag and Drop .FODP File to View Properties

Drop .FODP file here
  1. The following Python class handles .FODP files by opening, parsing the XML, extracting and printing the properties to the console, and writing the file (potentially after modifications, though this example focuses on read/print with a basic write capability).
import xml.etree.ElementTree as ET

class FODPHandler:
    def __init__(self, filepath):
        self.tree = ET.parse(filepath)
        self.root = self.tree.getroot()
        self.namespaces = {
            'office': 'urn:oasis:names:tc:opendocument:xmlns:office:1.0',
            'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
            'dc': 'http://purl.org/dc/elements/1.1/'
        }

    def get_and_print_properties(self):
        print("Extracted Properties:")
        print(f"Document Version: {self.root.get(f'{{{self.namespaces['office']}}}version', 'N/A')}")
        print(f"MIME Type: {self.root.get(f'{{{self.namespaces['office']}}}mimetype', 'N/A')}")
        
        meta = self.root.find('office:meta', self.namespaces)
        if meta is not None:
            print(f"Generator: {meta.find('meta:generator', self.namespaces).text if meta.find('meta:generator', self.namespaces) is not None else 'N/A'}")
            print(f"Creation Date: {meta.find('meta:creation-date', self.namespaces).text if meta.find('meta:creation-date', self.namespaces) is not None else 'N/A'}")
            print(f"Initial Creator: {meta.find('meta:initial-creator', self.namespaces).text if meta.find('meta:initial-creator', self.namespaces) is not None else 'N/A'}")
            print(f"Last Modified Date: {meta.find('dc:date', self.namespaces).text if meta.find('dc:date', self.namespaces) is not None else 'N/A'}")
            print(f"Last Modified By: {meta.find('dc:creator', self.namespaces).text if meta.find('dc:creator', self.namespaces) is not None else 'N/A'}")
            print(f"Print Date: {meta.find('meta:print-date', self.namespaces).text if meta.find('meta:print-date', self.namespaces) is not None else 'N/A'}")
            print(f"Printed By: {meta.find('meta:printed-by', self.namespaces).text if meta.find('meta:printed-by', self.namespaces) is not None else 'N/A'}")
            print(f"Title: {meta.find('dc:title', self.namespaces).text if meta.find('dc:title', self.namespaces) is not None else 'N/A'}")
            print(f"Subject: {meta.find('dc:subject', self.namespaces).text if meta.find('dc:subject', self.namespaces) is not None else 'N/A'}")
            print(f"Description: {meta.find('dc:description', self.namespaces).text if meta.find('dc:description', self.namespaces) is not None else 'N/A'}")
            
            keywords = [kw.text for kw in meta.findall('meta:keyword', self.namespaces)]
            print(f"Keywords: {', '.join(keywords) if keywords else 'N/A'}")
            
            print(f"Language: {meta.find('dc:language', self.namespaces).text if meta.find('dc:language', self.namespaces) is not None else 'N/A'}")
            print(f"Editing Cycles: {meta.find('meta:editing-cycles', self.namespaces).text if meta.find('meta:editing-cycles', self.namespaces) is not None else 'N/A'}")
            print(f"Editing Duration: {meta.find('meta:editing-duration', self.namespaces).text if meta.find('meta:editing-duration', self.namespaces) is not None else 'N/A'}")
            
            user_defined = meta.findall('meta:user-defined', self.namespaces)
            if user_defined:
                print("User-Defined Metadata:")
                for ud in user_defined:
                    print(f"  {ud.get(f'{{{self.namespaces['meta']}}}name')}: {ud.text}")
            else:
                print("User-Defined Metadata: N/A")
            
            stats = meta.find('meta:document-statistic', self.namespaces)
            if stats is not None:
                print("Document Statistics:")
                print(f"  Page Count: {stats.get(f'{{{self.namespaces['meta']}}}page-count', 'N/A')}")
                print(f"  Object Count: {stats.get(f'{{{self.namespaces['meta']}}}object-count', 'N/A')}")
                print(f"  Image Count: {stats.get(f'{{{self.namespaces['meta']}}}image-count', 'N/A')}")
                # Additional stats attributes can be added similarly
            else:
                print("Document Statistics: N/A")

    def write_file(self, new_filepath):
        self.tree.write(new_filepath, encoding='utf-8', xml_declaration=True)

# Example usage:
# handler = FODPHandler('example.fodp')
# handler.get_and_print_properties()
# handler.write_file('modified.fodp')
  1. The following Java class handles .FODP files by opening, parsing the XML using DocumentBuilder, extracting and printing the properties to the console, and writing the file (with basic write capability via Transformer).
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import java.io.File;

public class FODPHandler {
    private Document document;

    public FODPHandler(String filepath) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        DocumentBuilder builder = factory.newDocumentBuilder();
        this.document = builder.parse(new File(filepath));
    }

    public void getAndPrintProperties() {
        System.out.println("Extracted Properties:");
        Element root = document.getDocumentElement();
        System.out.println("Document Version: " + root.getAttributeNS("urn:oasis:names:tc:opendocument:xmlns:office:1.0", "version"));
        System.out.println("MIME Type: " + root.getAttributeNS("urn:oasis:names:tc:opendocument:xmlns:office:1.0", "mimetype"));

        Element meta = (Element) root.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:office:1.0", "meta").item(0);
        if (meta != null) {
            System.out.println("Generator: " + getElementText(meta, "meta", "generator"));
            System.out.println("Creation Date: " + getElementText(meta, "meta", "creation-date"));
            System.out.println("Initial Creator: " + getElementText(meta, "meta", "initial-creator"));
            System.out.println("Last Modified Date: " + getElementText(meta, "dc", "date"));
            System.out.println("Last Modified By: " + getElementText(meta, "dc", "creator"));
            System.out.println("Print Date: " + getElementText(meta, "meta", "print-date"));
            System.out.println("Printed By: " + getElementText(meta, "meta", "printed-by"));
            System.out.println("Title: " + getElementText(meta, "dc", "title"));
            System.out.println("Subject: " + getElementText(meta, "dc", "subject"));
            System.out.println("Description: " + getElementText(meta, "dc", "description"));

            NodeList keywords = meta.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:meta:1.0", "keyword");
            StringBuilder kwBuilder = new StringBuilder();
            for (int i = 0; i < keywords.getLength(); i++) {
                if (i > 0) kwBuilder.append(", ");
                kwBuilder.append(keywords.item(i).getTextContent());
            }
            System.out.println("Keywords: " + (kwBuilder.length() > 0 ? kwBuilder.toString() : "N/A"));

            System.out.println("Language: " + getElementText(meta, "dc", "language"));
            System.out.println("Editing Cycles: " + getElementText(meta, "meta", "editing-cycles"));
            System.out.println("Editing Duration: " + getElementText(meta, "meta", "editing-duration"));

            NodeList userDefined = meta.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:meta:1.0", "user-defined");
            if (userDefined.getLength() > 0) {
                System.out.println("User-Defined Metadata:");
                for (int i = 0; i < userDefined.getLength(); i++) {
                    Element ud = (Element) userDefined.item(i);
                    System.out.println("  " + ud.getAttributeNS("urn:oasis:names:tc:opendocument:xmlns:meta:1.0", "name") + ": " + ud.getTextContent());
                }
            } else {
                System.out.println("User-Defined Metadata: N/A");
            }

            Element stats = (Element) meta.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:meta:1.0", "document-statistic").item(0);
            if (stats != null) {
                System.out.println("Document Statistics:");
                System.out.println("  Page Count: " + stats.getAttributeNS("urn:oasis:names:tc:opendocument:xmlns:meta:1.0", "page-count"));
                System.out.println("  Object Count: " + stats.getAttributeNS("urn:oasis:names:tc:opendocument:xmlns:meta:1.0", "object-count"));
                System.out.println("  Image Count: " + stats.getAttributeNS("urn:oasis:names:tc:opendocument:xmlns:meta:1.0", "image-count"));
                // Additional stats attributes can be added similarly
            } else {
                System.out.println("Document Statistics: N/A");
            }
        }
    }

    private String getElementText(Element parent, String nsPrefix, String localName) {
        String ns = "meta".equals(nsPrefix) ? "urn:oasis:names:tc:opendocument:xmlns:meta:1.0" : "http://purl.org/dc/elements/1.1/";
        Element elem = (Element) parent.getElementsByTagNameNS(ns, localName).item(0);
        return elem != null ? elem.getTextContent() : "N/A";
    }

    public void writeFile(String newFilepath) throws Exception {
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        DOMSource source = new DOMSource(document);
        StreamResult result = new StreamResult(new File(newFilepath));
        transformer.transform(source, result);
    }

    // Example usage:
    // public static void main(String[] args) throws Exception {
    //     FODPHandler handler = new FODPHandler("example.fodp");
    //     handler.getAndPrintProperties();
    //     handler.writeFile("modified.fodp");
    // }
}
  1. The following JavaScript class (for browser environments) handles .FODP files by parsing the XML content (assuming file text is provided), extracting and printing the properties to the console, and providing a basic write method (which logs modified XML, as browser JS cannot directly write files without additional APIs).
class FODPHandler {
    constructor(fileText) {
        const parser = new DOMParser();
        this.document = parser.parseFromString(fileText, 'application/xml');
        this.namespaces = {
            office: 'urn:oasis:names:tc:opendocument:xmlns:office:1.0',
            meta: 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
            dc: 'http://purl.org/dc/elements/1.1/'
        };
    }

    getAndPrintProperties() {
        console.log('Extracted Properties:');
        const root = this.document.documentElement;
        console.log(`Document Version: ${root.getAttributeNS(this.namespaces.office, 'version') || 'N/A'}`);
        console.log(`MIME Type: ${root.getAttributeNS(this.namespaces.office, 'mimetype') || 'N/A'}`);

        const meta = this.document.querySelector('office\\:meta');
        if (meta) {
            console.log(`Generator: ${meta.querySelector('meta\\:generator')?.textContent || 'N/A'}`);
            console.log(`Creation Date: ${meta.querySelector('meta\\:creation-date')?.textContent || 'N/A'}`);
            console.log(`Initial Creator: ${meta.querySelector('meta\\:initial-creator')?.textContent || 'N/A'}`);
            console.log(`Last Modified Date: ${meta.querySelector('dc\\:date')?.textContent || 'N/A'}`);
            console.log(`Last Modified By: ${meta.querySelector('dc\\:creator')?.textContent || 'N/A'}`);
            console.log(`Print Date: ${meta.querySelector('meta\\:print-date')?.textContent || 'N/A'}`);
            console.log(`Printed By: ${meta.querySelector('meta\\:printed-by')?.textContent || 'N/A'}`);
            console.log(`Title: ${meta.querySelector('dc\\:title')?.textContent || 'N/A'}`);
            console.log(`Subject: ${meta.querySelector('dc\\:subject')?.textContent || 'N/A'}`);
            console.log(`Description: ${meta.querySelector('dc\\:description')?.textContent || 'N/A'}`);

            const keywords = Array.from(meta.querySelectorAll('meta\\:keyword')).map(k => k.textContent).join(', ');
            console.log(`Keywords: ${keywords || 'N/A'}`);

            console.log(`Language: ${meta.querySelector('dc\\:language')?.textContent || 'N/A'}`);
            console.log(`Editing Cycles: ${meta.querySelector('meta\\:editing-cycles')?.textContent || 'N/A'}`);
            console.log(`Editing Duration: ${meta.querySelector('meta\\:editing-duration')?.textContent || 'N/A'}`);

            const userDefined = Array.from(meta.querySelectorAll('meta\\:user-defined')).map(ud => {
                return `${ud.getAttributeNS(this.namespaces.meta, 'name')}: ${ud.textContent}`;
            }).join('\n');
            console.log(`User-Defined Metadata:\n${userDefined || 'N/A'}`);

            const stats = meta.querySelector('meta\\:document-statistic');
            if (stats) {
                console.log('Document Statistics:');
                console.log(`  Page Count: ${stats.getAttributeNS(this.namespaces.meta, 'page-count') || 'N/A'}`);
                console.log(`  Object Count: ${stats.getAttributeNS(this.namespaces.meta, 'object-count') || 'N/A'}`);
                console.log(`  Image Count: ${stats.getAttributeNS(this.namespaces.meta, 'image-count') || 'N/A'}`);
                // Additional stats attributes can be added similarly
            } else {
                console.log('Document Statistics: N/A');
            }
        }
    }

    writeFile() {
        const serializer = new XMLSerializer();
        const xmlString = serializer.serializeToString(this.document);
        console.log('Modified XML for writing:\n' + xmlString);
        // In a browser, use Blob and URL.createObjectURL for download, or integrate with FileSaver.js
    }
}

// Example usage (assuming fileText is loaded via FileReader):
// const handler = new FODPHandler(fileText);
// handler.getAndPrintProperties();
// handler.writeFile();
  1. The following C++ class handles .FODP files by opening, parsing the XML (using a simple string-based approach for demonstration, as standard C++ lacks built-in XML parsing; in practice, use a library like TinyXML or pugixml), extracting and printing the properties to the console, and writing the file (by copying, with potential for modification).
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>

// Note: This is a simplified implementation using string parsing for key tags.
// For robust XML parsing, integrate a library like TinyXML-2.

class FODPHandler {
private:
    std::string xmlContent;

    std::string extractAttribute(const std::string& tag, const std::string& attr) {
        size_t tagPos = xmlContent.find("<" + tag);
        if (tagPos == std::string::npos) return "N/A";
        size_t attrPos = xmlContent.find(attr + "=\"", tagPos);
        if (attrPos == std::string::npos) return "N/A";
        size_t start = attrPos + attr.length() + 2;
        size_t end = xmlContent.find("\"", start);
        return xmlContent.substr(start, end - start);
    }

    std::string extractText(const std::string& tag) {
        size_t startTag = xmlContent.find("<" + tag + ">");
        if (startTag == std::string::npos) return "N/A";
        size_t contentStart = startTag + tag.length() + 2;
        size_t endTag = xmlContent.find("</" + tag + ">", contentStart);
        return xmlContent.substr(contentStart, endTag - contentStart);
    }

    // Simplified; assumes single occurrence for most, handles multiples where needed
    void extractUserDefined(std::vector<std::pair<std::string, std::string>>& userDefs) {
        size_t pos = 0;
        while ((pos = xmlContent.find("<meta:user-defined", pos)) != std::string::npos) {
            size_t namePos = xmlContent.find("meta:name=\"", pos);
            size_t nameStart = namePos + 11;
            size_t nameEnd = xmlContent.find("\"", nameStart);
            std::string name = xmlContent.substr(nameStart, nameEnd - nameStart);

            size_t contentStart = xmlContent.find(">", nameEnd) + 1;
            size_t contentEnd = xmlContent.find("</meta:user-defined>", contentStart);
            std::string value = xmlContent.substr(contentStart, contentEnd - contentStart);

            userDefs.emplace_back(name, value);
            pos = contentEnd;
        }
    }

    std::map<std::string, std::string> extractStats() {
        std::map<std::string, std::string> stats;
        size_t pos = xmlContent.find("<meta:document-statistic");
        if (pos == std::string::npos) return stats;
        size_t end = xmlContent.find(">", pos);
        std::string attrs = xmlContent.substr(pos, end - pos);
        // Parse attributes simplistically
        size_t pagePos = attrs.find("meta:page-count=\"");
        if (pagePos != std::string::npos) {
            size_t start = pagePos + 17;
            size_t len = attrs.find("\"", start) - start;
            stats["page-count"] = attrs.substr(start, len);
        }
        // Add similar for object-count, image-count, etc.
        return stats;
    }

public:
    FODPHandler(const std::string& filepath) {
        std::ifstream file(filepath);
        if (file) {
            xmlContent = std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        } else {
            std::cerr << "Failed to open file." << std::endl;
        }
    }

    void getAndPrintProperties() {
        std::cout << "Extracted Properties:" << std::endl;
        std::cout << "Document Version: " << extractAttribute("office:document", "office:version") << std::endl;
        std::cout << "MIME Type: " << extractAttribute("office:document", "office:mimetype") << std::endl;
        std::cout << "Generator: " << extractText("meta:generator") << std::endl;
        std::cout << "Creation Date: " << extractText("meta:creation-date") << std::endl;
        std::cout << "Initial Creator: " << extractText("meta:initial-creator") << std::endl;
        std::cout << "Last Modified Date: " << extractText("dc:date") << std::endl;
        std::cout << "Last Modified By: " << extractText("dc:creator") << std::endl;
        std::cout << "Print Date: " << extractText("meta:print-date") << std::endl;
        std::cout << "Printed By: " << extractText("meta:printed-by") << std::endl;
        std::cout << "Title: " << extractText("dc:title") << std::endl;
        std::cout << "Subject: " << extractText("dc:subject") << std::endl;
        std::cout << "Description: " << extractText("dc:description") << std::endl;

        // Keywords: Find all <meta:keyword>
        std::string keywords;
        size_t kwPos = 0;
        while ((kwPos = xmlContent.find("<meta:keyword>", kwPos)) != std::string::npos) {
            size_t start = kwPos + 14;
            size_t end = xmlContent.find("</meta:keyword>", start);
            if (!keywords.empty()) keywords += ", ";
            keywords += xmlContent.substr(start, end - start);
            kwPos = end;
        }
        std::cout << "Keywords: " << (keywords.empty() ? "N/A" : keywords) << std::endl;

        std::cout << "Language: " << extractText("dc:language") << std::endl;
        std::cout << "Editing Cycles: " << extractText("meta:editing-cycles") << std::endl;
        std::cout << "Editing Duration: " << extractText("meta:editing-duration") << std::endl;

        std::vector<std::pair<std::string, std::string>> userDefs;
        extractUserDefined(userDefs);
        if (!userDefs.empty()) {
            std::cout << "User-Defined Metadata:" << std::endl;
            for (const auto& ud : userDefs) {
                std::cout << "  " << ud.first << ": " << ud.second << std::endl;
            }
        } else {
            std::cout << "User-Defined Metadata: N/A" << std::endl;
        }

        auto stats = extractStats();
        if (!stats.empty()) {
            std::cout << "Document Statistics:" << std::endl;
            std::cout << "  Page Count: " << stats["page-count"] << std::endl;
            // Add others similarly
        } else {
            std::cout << "Document Statistics: N/A" << std::endl;
        }
    }

    void writeFile(const std::string& newFilepath) {
        std::ofstream outFile(newFilepath);
        if (outFile) {
            outFile << xmlContent;
        } else {
            std::cerr << "Failed to write file." << std::endl;
        }
    }
};

// Example usage:
// int main() {
//     FODPHandler handler("example.fodp");
//     handler.getAndPrintProperties();
//     handler.writeFile("modified.fodp");
//     return 0;
// }