Task 709: .SXD File Format

Task 709: .SXD File Format

1. List of Properties Intrinsic to the .SXD File Format

The .SXD file format is a ZIP-compressed package containing XML-based subdocuments and resources, used for vector drawings in OpenOffice.org Draw (version 1.x) and StarOffice Draw. It is the precursor to the OpenDocument Graphics (.ODG) format. The specifications are defined in the OpenOffice.org XML File Format Technical Reference Manual (December 2002), which outlines the package structure, XML elements, attributes, and DTD entities. The format uses namespaces like office, draw, style, presentation, dr3d, and svg. Key intrinsic properties (structural components, MIME type, validation rules, and core elements/attributes) are listed below. These are "intrinsic to its file system" in the sense of the format's internal structure, MIME identification, and XML schema properties that define how the file is organized, validated, and processed on disk or in memory.

Package-Level Properties (ZIP Structure)

  • Container Format: ZIP archive (magic bytes: PK\003\004).
  • MIME Type: application/vnd.sun.xml.draw (stored in plain text file mimetype as the first entry in the ZIP, uncompressed).
  • Root Document Class: office:class="drawing" (in root elements of main XML files).
  • Version Attribute: office:version="1.0" (for DTD validation; forward-compatible processing ignores unknown elements/attributes).
  • Required Subfiles:
  • mimetype: Plain text MIME type.
  • content.xml: Main drawing content (root: <office:document-content>).
  • styles.xml: Styles and graphic definitions (root: <office:document-styles>).
  • meta.xml: Metadata (root: <office:document-meta>).
  • settings.xml: Application/view settings (root: <office:document-settings>).
  • META-INF/manifest.xml: Package manifest (root: <manifest:manifest>, lists files with MIME types, optional encryption).
  • Optional Directories/Subfiles:
  • Pictures/: Embedded binary images (e.g., PNG, JPEG; referenced via xlink:href).
  • Basic/: StarBasic macros (XML scripts).
  • Dialogs/: Custom dialogs (XML).
  • thumbnails/thumbnail.png: Preview image.
  • Obj*/: Embedded objects (e.g., charts in XML or binary).
  • Encryption Support: Optional per-file (in manifest: <manifest:encryption-data>, algorithms like Blowfish, key derivation like PBKDF2).
  • White-Space/EOL Handling: XML 1.0 compliant; spaces normalized to #x20 in attributes; EOL to LF (#xA); CR as .
  • Encoding: UTF-8 (multilingual support).

XML Schema Properties (Key Elements and Attributes)

These define the drawing-specific content, styles, and metadata. DTD entities include types like %coordinate;, %length;, %points;, %color;, %boolean;, etc. Core elements are from the draw namespace.

  • Metadata Properties (meta.xml):
  • <dc:title>, <dc:creator>, <dc:date>, <dc:language>, <meta:creation-date>, <meta:document-statistic> (attributes: meta:object-count, meta:page-count).
  • Settings Properties (settings.xml):
  • <config:config-item-set> for views (e.g., window position, zoom), printers, presentation configs (e.g., animation speed).
  • Styles Properties (styles.xml):
  • <office:styles>, <office:automatic-styles>, <office:master-styles>.
  • Graphic styles: <style:style style:family="graphic"> with <style:properties> (e.g., draw:fill-color, draw:stroke, svg:stroke-width).
  • Layer set: <draw:layer-set> <draw:layer draw:name="..." draw:protected="..."/>.
  • Master page: <style:master-page style:name="..." draw:style-name="..." style:page-master-name="..."> (includes headers, footers, backgrounds).
  • Graphic types: <draw:gradient>, <draw:hatch>, <draw:marker>, <draw:stroke-dash>.
  • Content Properties (content.xml):
  • <office:body> containing <draw:page draw:name="..." draw:master-page-name="..." presentation:presentation-page-layout-name="...">.
  • Shapes: %shapes; entity includes <draw:rect>, <draw:line>, <draw:polyline>, <draw:polygon>, <draw:path>, <draw:circle>, <draw:ellipse>, <draw:connector>, <draw:caption>, <draw:measure>, <draw:control>, <draw:page-thumbnail>, <draw:g> (grouping).
  • Common shape attributes: svg:x/y (position), svg:width/height (size), draw:transform (rotation/scale), draw:style-name, draw:layer-name, draw:z-index, draw:id.
  • Presentation elements: <presentation:animations>, <presentation:notes>, <presentation:show-shape>, etc. (attributes: presentation:effect, presentation:speed).
  • 3D elements: <dr3d:scene>, <dr3d:light>, <dr3d:sphere>, <dr3d:cube>, <dr3d:extrude>.
  • Text in shapes: %draw-text; entity (e.g., <text:p>, <text:list>).
  • Forms: <office:forms> <form:form>.
  • Manifest Properties (META-INF/manifest.xml):
  • <manifest:file-entry manifest:media-type="..." manifest:full-path="..."> for each file.
  • Validation Properties:
  • DTD-based (office.dtd implied); entities for types (e.g., %vector3D; for 3D positions).
  • Forward compatibility: Ignore unknown content; preserve alien attributes in <style:properties>.

These properties ensure the file's integrity, portability, and editability without proprietary tools.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .SXD File Dump

Here's an embedded HTML page with JavaScript that allows drag-and-drop of a .SXD file. It uses JSZip to unzip the file and DOMParser to parse XML, then dumps all properties (package files, key XML elements/attributes from meta/content/styles/settings) to the screen in a readable format.

.SXD File Dumper

Drag and Drop .SXD File Here

Drop .SXD file

4. Python Class for .SXD Handling

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

class SXDHandler:
    def __init__(self, filepath):
        self.filepath = filepath
        self.zip = None
        self.properties = {}

    def open(self):
        self.zip = zipfile.ZipFile(self.filepath, 'r')

    def decode_read(self):
        if not self.zip:
            self.open()
        self.properties = {
            'package_files': self.zip.namelist(),
            'mimetype': self.zip.read('mimetype').decode('utf-8').strip() if 'mimetype' in self.zip.namelist() else None,
        }
        for fname in ['meta.xml', 'content.xml', 'styles.xml', 'settings.xml']:
            if fname in self.zip.namelist():
                xml_content = self.zip.read(fname)
                root = ET.fromstring(xml_content)
                self.properties[fname] = {
                    'root': root.tag,
                    'attributes': dict(root.attrib),
                }
                # Extract sample props
                if fname == 'meta.xml':
                    self.properties[fname]['title'] = root.find('.//{http://purl.org/dc/elements/1.1/}title').text if root.find('.//{http://purl.org/dc/elements/1.1/}title') is not None else None
                    self.properties[fname]['creator'] = root.find('.//{http://purl.org/dc/elements/1.1/}creator').text if root.find('.//{http://purl.org/dc/elements/1.1/}creator') is not None else None
                elif fname == 'content.xml':
                    pages = root.findall('.//{http://openoffice.org/2000/drawing}page')
                    self.properties[fname]['page_count'] = len(pages)
                    self.properties[fname]['shapes_count'] = len(root.findall('.//{http://openoffice.org/2000/drawing}*'))
                elif fname == 'styles.xml':
                    masters = root.findall('.//{http://openoffice.org/2000/style}master-page')
                    self.properties[fname]['master_pages'] = len(masters)
                elif fname == 'settings.xml':
                    configs = root.findall('.//{http://openoffice.org/2000/office}config-item-set')
                    self.properties[fname]['config_sets'] = len(configs)

    def print_properties(self):
        if not self.properties:
            self.decode_read()
        for key, value in self.properties.items():
            print(f"{key}: {value}")

    def write(self, new_filepath=None):
        if not self.zip:
            raise ValueError("No file opened")
        # For simplicity, copy existing and modify example (e.g., add meta title)
        with zipfile.ZipFile(new_filepath or self.filepath, 'w') as new_zip:
            for item in self.zip.infolist():
                data = self.zip.read(item.filename)
                if item.filename == 'meta.xml':
                    root = ET.fromstring(data)
                    title_el = root.find('.//{http://purl.org/dc/elements/1.1/}title')
                    if title_el is not None:
                        title_el.text = 'Modified Title'
                    data = ET.tostring(root, encoding='utf-8', xml_declaration=True)
                new_zip.writestr(item, data)

    def close(self):
        if self.zip:
            self.zip.close()

# Usage example:
# handler = SXDHandler('example.sxd')
# handler.open()
# handler.decode_read()
# handler.print_properties()
# handler.write('modified.sxd')
# handler.close()

5. Java Class for .SXD Handling

import java.io.*;
import java.util.zip.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

public class SXDHandler {
    private String filepath;
    private ZipFile zip;
    private java.util.Map<String, Object> properties = new java.util.HashMap<>();

    public SXDHandler(String filepath) {
        this.filepath = filepath;
    }

    public void open() throws IOException {
        zip = new ZipFile(filepath);
    }

    public void decodeRead() throws IOException, ParserConfigurationException, SAXException {
        if (zip == null) open();
        java.util.Enumeration<? extends ZipEntry> entries = zip.entries();
        java.util.List<String> packageFiles = new java.util.ArrayList<>();
        while (entries.hasMoreElements()) {
            packageFiles.add(entries.nextElement().getName());
        }
        properties.put("package_files", packageFiles);

        ZipEntry mimetypeEntry = zip.getEntry("mimetype");
        if (mimetypeEntry != null) {
            BufferedReader reader = new BufferedReader(new InputStreamReader(zip.getInputStream(mimetypeEntry)));
            properties.put("mimetype", reader.readLine().trim());
        }

        String[] filesToParse = {"meta.xml", "content.xml", "styles.xml", "settings.xml"};
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        DocumentBuilder builder = factory.newDocumentBuilder();

        for (String fname : filesToParse) {
            ZipEntry entry = zip.getEntry(fname);
            if (entry != null) {
                Document doc = builder.parse(zip.getInputStream(entry));
                Element root = doc.getDocumentElement();
                java.util.Map<String, Object> fileProps = new java.util.HashMap<>();
                fileProps.put("root", root.getTagName());
                NamedNodeMap attrs = root.getAttributes();
                java.util.Map<String, String> attrMap = new java.util.HashMap<>();
                for (int i = 0; i < attrs.getLength(); i++) {
                    Node attr = attrs.item(i);
                    attrMap.put(attr.getNodeName(), attr.getNodeValue());
                }
                fileProps.put("attributes", attrMap);

                // Extract sample props
                if (fname.equals("meta.xml")) {
                    fileProps.put("title", getTextContent(doc, "dc:title"));
                    fileProps.put("creator", getTextContent(doc, "dc:creator"));
                } else if (fname.equals("content.xml")) {
                    NodeList pages = doc.getElementsByTagNameNS("http://openoffice.org/2000/drawing", "page");
                    fileProps.put("page_count", pages.getLength());
                    NodeList shapes = doc.getElementsByTagNameNS("http://openoffice.org/2000/drawing", "*");
                    fileProps.put("shapes_count", shapes.getLength());
                } else if (fname.equals("styles.xml")) {
                    NodeList masters = doc.getElementsByTagNameNS("http://openoffice.org/2000/style", "master-page");
                    fileProps.put("master_pages", masters.getLength());
                } else if (fname.equals("settings.xml")) {
                    NodeList configs = doc.getElementsByTagNameNS("http://openoffice.org/2000/office", "config-item-set");
                    fileProps.put("config_sets", configs.getLength());
                }

                properties.put(fname, fileProps);
            }
        }
    }

    private String getTextContent(Document doc, String tag) {
        NodeList nodes = doc.getElementsByTagName(tag);
        return (nodes.getLength() > 0) ? nodes.item(0).getTextContent() : null;
    }

    public void printProperties() {
        if (properties.isEmpty()) {
            try {
                decodeRead();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        properties.forEach((key, value) -> System.out.println(key + ": " + value));
    }

    public void write(String newFilepath) throws IOException, ParserConfigurationException, SAXException {
        if (zip == null) throw new IllegalStateException("No file opened");
        try (ZipOutputStream newZip = new ZipOutputStream(new FileOutputStream(newFilepath))) {
            java.util.Enumeration<? extends ZipEntry> entries = zip.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                newZip.putNextEntry(new ZipEntry(entry.getName()));
                InputStream is = zip.getInputStream(entry);
                if (entry.getName().equals("meta.xml")) {
                    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                    DocumentBuilder builder = factory.newDocumentBuilder();
                    Document doc = builder.parse(is);
                    Node title = doc.getElementsByTagName("dc:title").item(0);
                    if (title != null) title.setTextContent("Modified Title");
                    javax.xml.transform.TransformerFactory tf = javax.xml.transform.TransformerFactory.newInstance();
                    javax.xml.transform.Transformer trans = tf.newTransformer();
                    javax.xml.transform.dom.DOMSource source = new javax.xml.transform.dom.DOMSource(doc);
                    java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
                    trans.transform(source, new javax.xml.transform.stream.StreamResult(baos));
                    newZip.write(baos.toByteArray());
                } else {
                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = is.read(buffer)) > 0) {
                        newZip.write(buffer, 0, len);
                    }
                }
                newZip.closeEntry();
                is.close();
            }
        }
    }

    public void close() throws IOException {
        if (zip != null) zip.close();
    }

    // Usage example:
    // public static void main(String[] args) throws Exception {
    //     SXDHandler handler = new SXDHandler("example.sxd");
    //     handler.open();
    //     handler.decodeRead();
    //     handler.printProperties();
    //     handler.write("modified.sxd");
    //     handler.close();
    // }
}

6. JavaScript Class for .SXD Handling

const JSZip = require('jszip'); // For Node.js; use browser script for client-side
const { DOMParser } = require('xmldom'); // For Node.js

class SXDHandler {
    constructor(filepath) {
        this.filepath = filepath;
        this.zip = null;
        this.properties = {};
    }

    async open() {
        const fs = require('fs');
        const data = fs.readFileSync(this.filepath);
        this.zip = await JSZip.loadAsync(data);
    }

    async decodeRead() {
        if (!this.zip) await this.open();
        this.properties.package_files = Object.keys(this.zip.files);

        const mimetype = await this.zip.file('mimetype')?.async('string');
        if (mimetype) this.properties.mimetype = mimetype.trim();

        const parser = new DOMParser();
        const filesToParse = ['meta.xml', 'content.xml', 'styles.xml', 'settings.xml'];
        for (const fname of filesToParse) {
            const content = await this.zip.file(fname)?.async('string');
            if (content) {
                const doc = parser.parseFromString(content, 'application/xml');
                const root = doc.documentElement;
                this.properties[fname] = {
                    root: root.tagName,
                    attributes: {},
                };
                for (let i = 0; i < root.attributes.length; i++) {
                    const attr = root.attributes[i];
                    this.properties[fname].attributes[attr.name] = attr.value;
                }

                // Extract sample props
                if (fname === 'meta.xml') {
                    this.properties[fname].title = doc.getElementsByTagName('dc:title')[0]?.textContent || null;
                    this.properties[fname].creator = doc.getElementsByTagName('dc:creator')[0]?.textContent || null;
                } else if (fname === 'content.xml') {
                    const pages = doc.getElementsByTagNameNS('http://openoffice.org/2000/drawing', 'page');
                    this.properties[fname].page_count = pages.length;
                    const shapes = doc.getElementsByTagNameNS('http://openoffice.org/2000/drawing', '*');
                    this.properties[fname].shapes_count = shapes.length;
                } else if (fname === 'styles.xml') {
                    const masters = doc.getElementsByTagNameNS('http://openoffice.org/2000/style', 'master-page');
                    this.properties[fname].master_pages = masters.length;
                } else if (fname === 'settings.xml') {
                    const configs = doc.getElementsByTagNameNS('http://openoffice.org/2000/office', 'config-item-set');
                    this.properties[fname].config_sets = configs.length;
                }
            }
        }
    }

    printProperties() {
        if (Object.keys(this.properties).length === 0) {
            this.decodeRead().then(() => console.log(this.properties));
        } else {
            console.log(this.properties);
        }
    }

    async write(newFilepath) {
        if (!this.zip) throw new Error('No file opened');
        // Modify example: change meta title
        let metaContent = await this.zip.file('meta.xml')?.async('string');
        if (metaContent) {
            const doc = new DOMParser().parseFromString(metaContent, 'application/xml');
            const title = doc.getElementsByTagName('dc:title')[0];
            if (title) title.textContent = 'Modified Title';
            const serializer = new (require('xmldom').XMLSerializer)();
            metaContent = serializer.serializeToString(doc);
            this.zip.file('meta.xml', metaContent);
        }
        const buffer = await this.zip.generateAsync({type: 'nodebuffer'});
        const fs = require('fs');
        fs.writeFileSync(newFilepath, buffer);
    }

    close() {
        this.zip = null;
    }
}

// Usage example (Node.js):
// const handler = new SXDHandler('example.sxd');
// await handler.open();
// await handler.decodeRead();
// handler.printProperties();
// await handler.write('modified.sxd');
// handler.close();

7. C Class (Struct) for .SXD Handling

Note: C implementation uses libzip and libxml2 (assume installed). This is a basic struct-based class-like approach.

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

typedef struct {
    char* filepath;
    zip_t* zip;
    // Simplified properties: use a map-like array for demo
    char* properties[10][2]; // key-value pairs, limited
    int prop_count;
} SXDHandler;

SXDHandler* sxd_create(const char* filepath) {
    SXDHandler* handler = malloc(sizeof(SXDHandler));
    handler->filepath = strdup(filepath);
    handler->zip = NULL;
    handler->prop_count = 0;
    return handler;
}

int sxd_open(SXDHandler* handler) {
    int err = 0;
    handler->zip = zip_open(handler->filepath, ZIP_RDONLY, &err);
    return (handler->zip != NULL) ? 0 : -1;
}

void add_property(SXDHandler* handler, const char* key, const char* value) {
    if (handler->prop_count < 10) {
        handler->properties[handler->prop_count][0] = strdup(key);
        handler->properties[handler->prop_count][1] = strdup(value);
        handler->prop_count++;
    }
}

int sxd_decode_read(SXDHandler* handler) {
    if (!handler->zip) if (sxd_open(handler) != 0) return -1;

    // Package files (simplified, print count)
    zip_int64_t num_entries = zip_get_num_entries(handler->zip, 0);
    char buf[32];
    snprintf(buf, sizeof(buf), "%lld", num_entries);
    add_property(handler, "package_files_count", buf);

    // Mimetype
    zip_file_t* f = zip_fopen(handler->zip, "mimetype", 0);
    if (f) {
        char mime[256];
        zip_fread(f, mime, 255);
        mime[255] = '\0';
        add_property(handler, "mimetype", strtok(mime, "\n"));
        zip_fclose(f);
    }

    // Parse XML files (simplified, check existence and root)
    const char* files[] = {"meta.xml", "content.xml", "styles.xml", "settings.xml"};
    for (int i = 0; i < 4; i++) {
        zip_stat_t sb;
        if (zip_stat(handler->zip, files[i], 0, &sb) == 0) {
            char* content = malloc(sb.size + 1);
            f = zip_fopen(handler->zip, files[i], 0);
            zip_fread(f, content, sb.size);
            content[sb.size] = '\0';
            zip_fclose(f);

            xmlDocPtr doc = xmlReadMemory(content, sb.size, NULL, NULL, 0);
            if (doc) {
                xmlNodePtr root = xmlDocGetRootElement(doc);
                if (root) {
                    char key[64];
                    snprintf(key, sizeof(key), "%s_root", files[i]);
                    add_property(handler, key, (char*)root->name);

                    // Sample: for meta, get title
                    if (strcmp(files[i], "meta.xml") == 0) {
                        xmlNodePtr cur = root->children;
                        while (cur) {
                            if (xmlStrcmp(cur->name, (const xmlChar*)"title") == 0) {
                                add_property(handler, "meta_title", (char*)xmlNodeGetContent(cur));
                                break;
                            }
                            cur = cur->next;
                        }
                    }
                }
                xmlFreeDoc(doc);
            }
            free(content);
        }
    }
    return 0;
}

void sxd_print_properties(SXDHandler* handler) {
    if (handler->prop_count == 0) sxd_decode_read(handler);
    for (int i = 0; i < handler->prop_count; i++) {
        printf("%s: %s\n", handler->properties[i][0], handler->properties[i][1]);
    }
}

int sxd_write(SXDHandler* handler, const char* new_filepath) {
    if (!handler->zip) return -1;

    zip_t* new_zip = zip_open(new_filepath, ZIP_CREATE | ZIP_TRUNCATE, NULL);
    if (!new_zip) return -1;

    zip_int64_t num = zip_get_num_entries(handler->zip, 0);
    for (zip_int64_t i = 0; i < num; i++) {
        const char* name = zip_get_name(handler->zip, i, 0);
        zip_stat_t sb;
        zip_stat_index(handler->zip, i, 0, &sb);

        char* data = malloc(sb.size);
        zip_file_t* f = zip_fopen_index(handler->zip, i, 0);
        zip_fread(f, data, sb.size);
        zip_fclose(f);

        if (strcmp(name, "meta.xml") == 0) {
            // Modify title
            xmlDocPtr doc = xmlReadMemory(data, sb.size, NULL, NULL, 0);
            xmlNodePtr title = xmlNewNode(NULL, (xmlChar*)"title");
            xmlNodeSetContent(title, (xmlChar*)"Modified Title");
            // Simplified: replace entire meta.xml with new minimal one for demo
            xmlFreeDoc(doc);
            free(data);
            data = strdup("<office:document-meta><dc:title>Modified Title</dc:title></office:document-meta>");
            sb.size = strlen(data);
        }

        zip_source_t* src = zip_source_buffer(new_zip, data, sb.size, 1);
        zip_file_add(new_zip, name, src, ZIP_FL_OVERWRITE);
    }

    zip_close(new_zip);
    return 0;
}

void sxd_close(SXDHandler* handler) {
    if (handler->zip) zip_close(handler->zip);
    for (int i = 0; i < handler->prop_count; i++) {
        free(handler->properties[i][0]);
        free(handler->properties[i][1]);
    }
    free(handler->filepath);
    free(handler);
}

// Usage example:
// int main() {
//     SXDHandler* handler = sxd_create("example.sxd");
//     sxd_open(handler);
//     sxd_decode_read(handler);
//     sxd_print_properties(handler);
//     sxd_write(handler, "modified.sxd");
//     sxd_close(handler);
//     return 0;
// }