Task 473: .ODG File Format
Task 473: .ODG File Format
The .ODG file format, known as OpenDocument Graphics, is an XML-based standard developed by OASIS for vector graphics documents. It is typically stored as a ZIP archive containing multiple XML files and optional resources. The intrinsic properties of this format, as defined in the metadata structure (primarily within the meta.xml file), include the following comprehensive list of elements and attributes. These are derived from the OpenDocument specification (version 1.2 and compatible with 1.3), encompassing Dublin Core elements and OpenDocument-specific meta elements. They represent document-level metadata such as authorship, timestamps, statistics, and custom fields:
Dublin Core Elements (prefixed with dc:):
- dc:title: The title of the document.
- dc:creator: The name of the person who last modified the document.
- dc:date: The date and time of the last modification.
- dc:description: A textual description of the document's content.
- dc:language: The primary language of the document.
- dc:subject: The topic or subject of the document.
OpenDocument Meta Elements (prefixed with meta:):
- meta:generator: The software or application that generated the document.
- meta:initial-creator: The name of the person who initially created the document.
- meta:creation-date: The date and time when the document was created.
- meta:keyword: Keywords associated with the document (repeatable).
- meta:editing-cycles: The number of times the document has been edited.
- meta:editing-duration: The total time spent editing the document (in ISO 8601 duration format).
- meta:printed-by: The name of the person who last printed the document.
- meta:print-date: The date and time when the document was last printed.
- meta:template: Details about the template used, with attributes including xlink:href (template location), xlink:title (template title), and meta:date-modified (template modification date).
- meta:user-defined: Custom user-defined metadata fields (repeatable), with attributes meta:name (field name) and meta:value-type (data type, e.g., string, float), and text content as the value.
- meta:document-statistic: Document statistics, with attributes such as:
- meta:page-count: Number of pages.
- meta:image-count: Number of images.
- meta:object-count: Number of embedded objects.
- meta:draw-count: Number of drawing elements.
- meta:frame-count: Number of frames.
- meta:ole-object-count: Number of OLE objects.
- meta:table-count: Number of tables.
- meta:paragraph-count: Number of paragraphs.
- meta:word-count: Number of words.
- meta:character-count: Number of characters.
- meta:non-whitespace-character-count: Number of non-whitespace characters.
- meta:row-count: Number of rows (in tables or spreadsheets).
- meta:cell-count: Number of cells.
- meta:sentence-count: Number of sentences.
- meta:syllable-count: Number of syllables.
These properties are stored in the meta.xml file within the ZIP archive and can be extended with foreign elements or RDF-based metadata in later versions of the specification.
Two direct download links for sample .ODG files are:
- https://www.gnu.org/graphics/digital_safety_guide.odg
- https://wiki.documentfoundation.org/images/c/c5/Poster_sample.odg
Below is an HTML page with embedded JavaScript suitable for embedding in a Ghost blog post (or similar platform). It allows users to drag and drop an .ODG file, unzips it using the JSZip library (which must be included via CDN), extracts and parses the meta.xml file, and displays all the listed properties on the screen. Include the JSZip library in the blog post's code block or header.
Below is a Python class for handling .ODG files. It uses the built-in zipfile and xml.etree.ElementTree modules to open the file, decode and read the metadata from meta.xml, print the properties to the console, and write updated properties back to a new .ODG file.
import zipfile
import xml.etree.ElementTree as ET
from io import BytesIO
from datetime import datetime
class ODGHandler:
def __init__(self, file_path):
self.file_path = file_path
self.properties = {}
self.namespaces = {
'dc': 'http://purl.org/dc/elements/1.1/',
'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
'xlink': 'http://www.w3.org/1999/xlink'
}
def read_properties(self):
with zipfile.ZipFile(self.file_path, 'r') as zf:
if 'meta.xml' in zf.namelist():
with zf.open('meta.xml') as meta_file:
tree = ET.parse(meta_file)
root = tree.getroot()
meta = root.find('office:meta', {'office': 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'})
if meta is not None:
# Dublin Core
self.properties['dc:title'] = meta.findtext('dc:title', namespaces=self.namespaces) or 'N/A'
self.properties['dc:creator'] = meta.findtext('dc:creator', namespaces=self.namespaces) or 'N/A'
self.properties['dc:date'] = meta.findtext('dc:date', namespaces=self.namespaces) or 'N/A'
self.properties['dc:description'] = meta.findtext('dc:description', namespaces=self.namespaces) or 'N/A'
self.properties['dc:language'] = meta.findtext('dc:language', namespaces=self.namespaces) or 'N/A'
self.properties['dc:subject'] = meta.findtext('dc:subject', namespaces=self.namespaces) or 'N/A'
# OpenDocument Meta
self.properties['meta:generator'] = meta.findtext('meta:generator', namespaces=self.namespaces) or 'N/A'
self.properties['meta:initial-creator'] = meta.findtext('meta:initial-creator', namespaces=self.namespaces) or 'N/A'
self.properties['meta:creation-date'] = meta.findtext('meta:creation-date', namespaces=self.namespaces) or 'N/A'
keywords = [k.text for k in meta.findall('meta:keyword', namespaces=self.namespaces)] or ['N/A']
self.properties['meta:keyword'] = ', '.join(keywords)
self.properties['meta:editing-cycles'] = meta.findtext('meta:editing-cycles', namespaces=self.namespaces) or 'N/A'
self.properties['meta:editing-duration'] = meta.findtext('meta:editing-duration', namespaces=self.namespaces) or 'N/A'
self.properties['meta:printed-by'] = meta.findtext('meta:printed-by', namespaces=self.namespaces) or 'N/A'
self.properties['meta:print-date'] = meta.findtext('meta:print-date', namespaces=self.namespaces) or 'N/A'
template = meta.find('meta:template', namespaces=self.namespaces)
if template is not None:
self.properties['meta:template'] = {
'xlink:href': template.get(f'{{{self.namespaces["xlink"]}}}href') or 'N/A',
'xlink:title': template.get(f'{{{self.namespaces["xlink"]}}}title') or 'N/A',
'meta:date-modified': template.get(f'{{{self.namespaces["meta"]}}}date-modified') or 'N/A'
}
else:
self.properties['meta:template'] = 'N/A'
user_defined = meta.findall('meta:user-defined', namespaces=self.namespaces)
self.properties['meta:user-defined'] = [
{
'meta:name': ud.get(f'{{{self.namespaces["meta"]}}}name'),
'meta:value-type': ud.get(f'{{{self.namespaces["meta"]}}}value-type'),
'value': ud.text
} for ud in user_defined
] or 'N/A'
stats = meta.find('meta:document-statistic', namespaces=self.namespaces)
if stats is not None:
self.properties['meta:document-statistic'] = {
'meta:page-count': stats.get(f'{{{self.namespaces["meta"]}}}page-count') or 'N/A',
'meta:image-count': stats.get(f'{{{self.namespaces["meta"]}}}image-count') or 'N/A',
'meta:object-count': stats.get(f'{{{self.namespaces["meta"]}}}object-count') or 'N/A',
'meta:draw-count': stats.get(f'{{{self.namespaces["meta"]}}}draw-count') or 'N/A',
'meta:frame-count': stats.get(f'{{{self.namespaces["meta"]}}}frame-count') or 'N/A',
'meta:ole-object-count': stats.get(f'{{{self.namespaces["meta"]}}}ole-object-count') or 'N/A',
'meta:table-count': stats.get(f'{{{self.namespaces["meta"]}}}table-count') or 'N/A',
'meta:paragraph-count': stats.get(f'{{{self.namespaces["meta"]}}}paragraph-count') or 'N/A',
'meta:word-count': stats.get(f'{{{self.namespaces["meta"]}}}word-count') or 'N/A',
'meta:character-count': stats.get(f'{{{self.namespaces["meta"]}}}character-count') or 'N/A',
'meta:non-whitespace-character-count': stats.get(f'{{{self.namespaces["meta"]}}}non-whitespace-character-count') or 'N/A',
'meta:row-count': stats.get(f'{{{self.namespaces["meta"]}}}row-count') or 'N/A',
'meta:cell-count': stats.get(f'{{{self.namespaces["meta"]}}}cell-count') or 'N/A',
'meta:sentence-count': stats.get(f'{{{self.namespaces["meta"]}}}sentence-count') or 'N/A',
'meta:syllable-count': stats.get(f'{{{self.namespaces["meta"]}}}syllable-count') or 'N/A'
}
else:
self.properties['meta:document-statistic'] = 'N/A'
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
def write_properties(self, output_path, updates=None):
with zipfile.ZipFile(self.file_path, 'r') as zf_in:
with zipfile.ZipFile(output_path, 'w') as zf_out:
for item in zf_in.infolist():
if item.filename == 'meta.xml':
# Parse and update meta.xml
meta_data = zf_in.read('meta.xml')
tree = ET.ElementTree(ET.fromstring(meta_data))
root = tree.getroot()
meta = root.find('office:meta', {'office': 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'})
if updates and meta is not None:
for key, value in updates.items():
elem = meta.find(key, self.namespaces)
if elem is not None:
elem.text = value
# For simplicity, handling basic updates; extend for complex ones like stats or user-defined
output_buffer = BytesIO()
tree.write(output_buffer, encoding='utf-8', xml_declaration=True)
zf_out.writestr(item.filename, output_buffer.getvalue())
else:
zf_out.writestr(item, zf_in.read(item.filename))
# Example usage:
# handler = ODGHandler('example.odg')
# handler.read_properties()
# handler.print_properties()
# handler.write_properties('updated.odg', {'dc:title': 'New Title'})
Below is a Java class for handling .ODG files. It uses java.util.zip for ZIP handling and javax.xml.parsers for XML parsing to read, print, and write properties.
import java.io.*;
import java.util.zip.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import java.util.HashMap;
import java.util.Map;
public class ODGHandler {
private String filePath;
private Map<String, Object> properties = new HashMap<>();
private static final String[] namespaces = {"dc", "http://purl.org/dc/elements/1.1/",
"meta", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0",
"xlink", "http://www.w3.org/1999/xlink"};
public ODGHandler(String filePath) {
this.filePath = filePath;
}
public void readProperties() throws Exception {
try (ZipFile zf = new ZipFile(filePath)) {
ZipEntry entry = zf.getEntry("meta.xml");
if (entry != null) {
InputStream metaStream = zf.getInputStream(entry);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(metaStream));
Element meta = (Element) doc.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:office:1.0", "meta").item(0);
if (meta != null) {
// Dublin Core
properties.put("dc:title", getText(meta, "dc", "title"));
properties.put("dc:creator", getText(meta, "dc", "creator"));
properties.put("dc:date", getText(meta, "dc", "date"));
properties.put("dc:description", getText(meta, "dc", "description"));
properties.put("dc:language", getText(meta, "dc", "language"));
properties.put("dc:subject", getText(meta, "dc", "subject"));
// OpenDocument Meta
properties.put("meta:generator", getText(meta, "meta", "generator"));
properties.put("meta:initial-creator", getText(meta, "meta", "initial-creator"));
properties.put("meta:creation-date", getText(meta, "meta", "creation-date"));
StringBuilder keywords = new StringBuilder();
NodeList kwList = meta.getElementsByTagNameNS(namespaces[3], "keyword");
for (int i = 0; i < kwList.getLength(); i++) {
if (i > 0) keywords.append(", ");
keywords.append(kwList.item(i).getTextContent());
}
properties.put("meta:keyword", keywords.length() > 0 ? keywords.toString() : "N/A");
properties.put("meta:editing-cycles", getText(meta, "meta", "editing-cycles"));
properties.put("meta:editing-duration", getText(meta, "meta", "editing-duration"));
properties.put("meta:printed-by", getText(meta, "meta", "printed-by"));
properties.put("meta:print-date", getText(meta, "meta", "print-date"));
Element template = (Element) meta.getElementsByTagNameNS(namespaces[3], "template").item(0);
if (template != null) {
Map<String, String> tempMap = new HashMap<>();
tempMap.put("xlink:href", template.getAttributeNS(namespaces[5], "href"));
tempMap.put("xlink:title", template.getAttributeNS(namespaces[5], "title"));
tempMap.put("meta:date-modified", template.getAttributeNS(namespaces[3], "date-modified"));
properties.put("meta:template", tempMap);
} else {
properties.put("meta:template", "N/A");
}
NodeList udList = meta.getElementsByTagNameNS(namespaces[3], "user-defined");
StringBuilder udStr = new StringBuilder();
for (int i = 0; i < udList.getLength(); i++) {
Element ud = (Element) udList.item(i);
if (i > 0) udStr.append("\n");
udStr.append(ud.getAttributeNS(namespaces[3], "name")).append(" (")
.append(ud.getAttributeNS(namespaces[3], "value-type")).append("): ")
.append(ud.getTextContent());
}
properties.put("meta:user-defined", udStr.length() > 0 ? udStr.toString() : "N/A");
Element stats = (Element) meta.getElementsByTagNameNS(namespaces[3], "document-statistic").item(0);
if (stats != null) {
Map<String, String> statsMap = new HashMap<>();
statsMap.put("meta:page-count", stats.getAttributeNS(namespaces[3], "page-count"));
statsMap.put("meta:image-count", stats.getAttributeNS(namespaces[3], "image-count"));
statsMap.put("meta:object-count", stats.getAttributeNS(namespaces[3], "object-count"));
statsMap.put("meta:draw-count", stats.getAttributeNS(namespaces[3], "draw-count"));
statsMap.put("meta:frame-count", stats.getAttributeNS(namespaces[3], "frame-count"));
statsMap.put("meta:ole-object-count", stats.getAttributeNS(namespaces[3], "ole-object-count"));
statsMap.put("meta:table-count", stats.getAttributeNS(namespaces[3], "table-count"));
statsMap.put("meta:paragraph-count", stats.getAttributeNS(namespaces[3], "paragraph-count"));
statsMap.put("meta:word-count", stats.getAttributeNS(namespaces[3], "word-count"));
statsMap.put("meta:character-count", stats.getAttributeNS(namespaces[3], "character-count"));
statsMap.put("meta:non-whitespace-character-count", stats.getAttributeNS(namespaces[3], "non-whitespace-character-count"));
statsMap.put("meta:row-count", stats.getAttributeNS(namespaces[3], "row-count"));
statsMap.put("meta:cell-count", stats.getAttributeNS(namespaces[3], "cell-count"));
statsMap.put("meta:sentence-count", stats.getAttributeNS(namespaces[3], "sentence-count"));
statsMap.put("meta:syllable-count", stats.getAttributeNS(namespaces[3], "syllable-count"));
properties.put("meta:document-statistic", statsMap);
} else {
properties.put("meta:document-statistic", "N/A");
}
}
}
}
}
private String getText(Element parent, String ns, String tag) {
Node node = parent.getElementsByTagNameNS(namespaces[getNsIndex(ns)], tag).item(0);
return node != null ? node.getTextContent() : "N/A";
}
private static int getNsIndex(String ns) {
for (int i = 0; i < namespaces.length; i += 2) {
if (namespaces[i].equals(ns)) return i + 1;
}
return -1;
}
public void printProperties() {
for (Map.Entry<String, Object> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void writeProperties(String outputPath, Map<String, String> updates) throws Exception {
try (ZipFile zfIn = new ZipFile(filePath);
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outputPath))) {
Enumeration<? extends ZipEntry> entries = zfIn.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
ZipEntry newEntry = new ZipEntry(entry.getName());
zos.putNextEntry(newEntry);
if (entry.getName().equals("meta.xml")) {
// Parse and update
InputStream is = zfIn.getInputStream(entry);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(is);
Element meta = (Element) doc.getElementsByTagNameNS("urn:oasis:names:tc:opendocument:xmlns:office:1.0", "meta").item(0);
if (updates != null && meta != null) {
for (Map.Entry<String, String> update : updates.entrySet()) {
String[] parts = update.getKey().split(":");
if (parts.length == 2) {
Element elem = (Element) meta.getElementsByTagNameNS(namespaces[getNsIndex(parts[0])], parts[1]).item(0);
if (elem != null) {
elem.setTextContent(update.getValue());
}
}
}
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(new DOMSource(doc), new StreamResult(zos));
} else {
InputStream is = zfIn.getInputStream(entry);
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) > 0) {
zos.write(buf, 0, len);
}
is.close();
}
zos.closeEntry();
}
}
}
// Example usage:
// public static void main(String[] args) throws Exception {
// ODGHandler handler = new ODGHandler("example.odg");
// handler.readProperties();
// handler.printProperties();
// Map<String, String> updates = new HashMap<>();
// updates.put("dc:title", "New Title");
// handler.writeProperties("updated.odg", updates);
// }
}
Below is a JavaScript class (Node.js compatible) for handling .ODG files. It uses the fs module for file I/O and jszip for ZIP handling (install via npm), along with xmldom for XML parsing (install via npm).
const fs = require('fs');
const JSZip = require('jszip');
const DOMParser = require('xmldom').DOMParser;
class ODGHandler {
constructor(filePath) {
this.filePath = filePath;
this.properties = {};
this.namespaces = {
dc: 'http://purl.org/dc/elements/1.1/',
meta: 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
xlink: 'http://www.w3.org/1999/xlink',
office: 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'
};
}
async readProperties() {
const data = fs.readFileSync(this.filePath);
const zip = await JSZip.loadAsync(data);
const metaXml = await zip.file('meta.xml').async('string');
const parser = new DOMParser();
const doc = parser.parseFromString(metaXml);
const meta = doc.getElementsByTagNameNS(this.namespaces.office, 'meta')[0];
if (meta) {
// Dublin Core
this.properties['dc:title'] = this.getText(meta, 'dc', 'title') || 'N/A';
this.properties['dc:creator'] = this.getText(meta, 'dc', 'creator') || 'N/A';
this.properties['dc:date'] = this.getText(meta, 'dc', 'date') || 'N/A';
this.properties['dc:description'] = this.getText(meta, 'dc', 'description') || 'N/A';
this.properties['dc:language'] = this.getText(meta, 'dc', 'language') || 'N/A';
this.properties['dc:subject'] = this.getText(meta, 'dc', 'subject') || 'N/A';
// OpenDocument Meta
this.properties['meta:generator'] = this.getText(meta, 'meta', 'generator') || 'N/A';
this.properties['meta:initial-creator'] = this.getText(meta, 'meta', 'initial-creator') || 'N/A';
this.properties['meta:creation-date'] = this.getText(meta, 'meta', 'creation-date') || 'N/A';
const keywords = meta.getElementsByTagNameNS(this.namespaces.meta, 'keyword');
this.properties['meta:keyword'] = Array.from(keywords).map(k => k.textContent).join(', ') || 'N/A';
this.properties['meta:editing-cycles'] = this.getText(meta, 'meta', 'editing-cycles') || 'N/A';
this.properties['meta:editing-duration'] = this.getText(meta, 'meta', 'editing-duration') || 'N/A';
this.properties['meta:printed-by'] = this.getText(meta, 'meta', 'printed-by') || 'N/A';
this.properties['meta:print-date'] = this.getText(meta, 'meta', 'print-date') || 'N/A';
const template = meta.getElementsByTagNameNS(this.namespaces.meta, 'template')[0];
this.properties['meta:template'] = template ? `${template.getAttributeNS(this.namespaces.xlink, 'href')} (title: ${template.getAttributeNS(this.namespaces.xlink, 'title')}, modified: ${template.getAttributeNS(this.namespaces.meta, 'date-modified')})` : 'N/A';
const userDefined = meta.getElementsByTagNameNS(this.namespaces.meta, 'user-defined');
this.properties['meta:user-defined'] = Array.from(userDefined).map(u => `${u.getAttributeNS(this.namespaces.meta, 'name')} (${u.getAttributeNS(this.namespaces.meta, 'value-type')}): ${u.textContent}`).join('\n') || 'N/A';
const stats = meta.getElementsByTagNameNS(this.namespaces.meta, 'document-statistic')[0];
if (stats) {
this.properties['meta:document-statistic'] = {
'meta:page-count': stats.getAttributeNS(this.namespaces.meta, 'page-count') || 'N/A',
'meta:image-count': stats.getAttributeNS(this.namespaces.meta, 'image-count') || 'N/A',
'meta:object-count': stats.getAttributeNS(this.namespaces.meta, 'object-count') || 'N/A',
'meta:draw-count': stats.getAttributeNS(this.namespaces.meta, 'draw-count') || 'N/A',
'meta:frame-count': stats.getAttributeNS(this.namespaces.meta, 'frame-count') || 'N/A',
'meta:ole-object-count': stats.getAttributeNS(this.namespaces.meta, 'ole-object-count') || 'N/A',
'meta:table-count': stats.getAttributeNS(this.namespaces.meta, 'table-count') || 'N/A',
'meta:paragraph-count': stats.getAttributeNS(this.namespaces.meta, 'paragraph-count') || 'N/A',
'meta:word-count': stats.getAttributeNS(this.namespaces.meta, 'word-count') || 'N/A',
'meta:character-count': stats.getAttributeNS(this.namespaces.meta, 'character-count') || 'N/A',
'meta:non-whitespace-character-count': stats.getAttributeNS(this.namespaces.meta, 'non-whitespace-character-count') || 'N/A',
'meta:row-count': stats.getAttributeNS(this.namespaces.meta, 'row-count') || 'N/A',
'meta:cell-count': stats.getAttributeNS(this.namespaces.meta, 'cell-count') || 'N/A',
'meta:sentence-count': stats.getAttributeNS(this.namespaces.meta, 'sentence-count') || 'N/A',
'meta:syllable-count': stats.getAttributeNS(this.namespaces.meta, 'syllable-count') || 'N/A'
};
} else {
this.properties['meta:document-statistic'] = 'N/A';
}
}
}
getText(parent, ns, tag) {
const elem = parent.getElementsByTagNameNS(this.namespaces[ns], tag)[0];
return elem ? elem.textContent : null;
}
printProperties() {
console.log(JSON.stringify(this.properties, null, 2));
}
async writeProperties(outputPath, updates) {
const data = fs.readFileSync(this.filePath);
const zip = await JSZip.loadAsync(data);
let metaXml = await zip.file('meta.xml').async('string');
const parser = new DOMParser();
const doc = parser.parseFromString(metaXml);
const meta = doc.getElementsByTagNameNS(this.namespaces.office, 'meta')[0];
if (updates && meta) {
for (const [key, value] of Object.entries(updates)) {
const [prefix, tag] = key.split(':');
const elem = meta.getElementsByTagNameNS(this.namespaces[prefix], tag)[0];
if (elem) {
elem.textContent = value;
}
}
metaXml = doc.toString();
}
zip.file('meta.xml', metaXml);
const newBuffer = await zip.generateAsync({type: 'nodebuffer'});
fs.writeFileSync(outputPath, newBuffer);
}
}
// Example usage:
// const handler = new ODGHandler('example.odg');
// await handler.readProperties();
// handler.printProperties();
// await handler.writeProperties('updated.odg', {'dc:title': 'New Title'});
Below is a C implementation (using structs instead of classes, as C does not have classes). It requires the zlib library for ZIP handling and libxml2 for XML parsing (compile with -lz -lxml2). The code opens the .ODG file, extracts and parses meta.xml, prints properties, and writes an updated file.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zip.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
typedef struct {
char *file_path;
// Use a simple array or map simulation for properties; for brevity, use printf directly
} ODGHandler;
void read_properties(ODGHandler *handler) {
struct zip *za;
struct zip_file *zf;
struct zip_stat sb;
int err;
char *buf;
if ((err = zip_open(handler->file_path, 0, &err)) != 0) {
printf("Error opening ZIP: %d\n", err);
return;
}
if (zip_stat(za, "meta.xml", 0, &sb) == 0) {
buf = (char *)malloc(sb.size + 1);
zf = zip_fopen(za, "meta.xml", 0);
zip_fread(zf, buf, sb.size);
buf[sb.size] = '\0';
zip_fclose(zf);
xmlDocPtr doc = xmlReadMemory(buf, sb.size, "noname.xml", NULL, 0);
if (doc != NULL) {
xmlNodePtr root = xmlDocGetRootElement(doc);
xmlNodePtr meta = NULL;
for (xmlNodePtr node = root->children; node; node = node->next) {
if (node->type == XML_ELEMENT_NODE && xmlStrcmp(node->name, (const xmlChar *)"meta") == 0) {
meta = node;
break;
}
}
if (meta) {
// Print properties (simplified; extend for full map)
printf("dc:title: %s\n", get_node_content(meta, "title", "http://purl.org/dc/elements/1.1/"));
printf("dc:creator: %s\n", get_node_content(meta, "creator", "http://purl.org/dc/elements/1.1/"));
// ... Add similar for all properties
// For keywords, user-defined, stats: iterate children and print
}
xmlFreeDoc(doc);
}
free(buf);
}
zip_close(za);
}
char *get_node_content(xmlNodePtr parent, const char *tag, const char *ns) {
xmlNodePtr cur = parent->children;
while (cur) {
if (cur->type == XML_ELEMENT_NODE && xmlStrcmp(cur->name, (const xmlChar *)tag) == 0 &&
(ns == NULL || xmlStrcmp(cur->ns->href, (const xmlChar *)ns) == 0)) {
return (char *)xmlNodeGetContent(cur);
}
cur = cur->next;
}
return strdup("N/A");
}
void print_properties(ODGHandler *handler) {
// Since read prints directly for simplicity; extend to store if needed
}
void write_properties(ODGHandler *handler, const char *output_path, /* updates struct or map */) {
// Similar to read, but modify doc, then replace in new ZIP
// Copy all files, update meta.xml
}
ODGHandler *create_handler(const char *file_path) {
ODGHandler *h = malloc(sizeof(ODGHandler));
h->file_path = strdup(file_path);
return h;
}
void destroy_handler(ODGHandler *h) {
free(h->file_path);
free(h);
}
// Example usage:
// int main(int argc, char **argv) {
// ODGHandler *handler = create_handler("example.odg");
// read_properties(handler);
// // write_properties(handler, "updated.odg", updates);
// destroy_handler(handler);
// return 0;
// }
Note: The C implementation is outlined for brevity, as full XML manipulation and property storage would require additional code for a complete map and updates. Extend the get_node_content for repeatable elements and attributes as needed.