Task 836: .XLSX File Format

Task 836: .XLSX File Format

File Format Specifications for .XLSX

The .XLSX file format is part of the Office Open XML (OOXML) standards, standardized as ECMA-376 (by Ecma International) and ISO/IEC 29500. It is a ZIP-based package format containing XML files, binary data (e.g., images), and relationships, following the Open Packaging Conventions (OPC). The structure includes mandatory parts like [Content_Types].xml, _rels/.rels, and xl/workbook.xml, with optional parts for worksheets, styles, and metadata. Core specifications are detailed in ECMA-376 Part 2 (Open Packaging Conventions) for packaging and properties, and Part 1 for SpreadsheetML specifics. The format supports spreadsheets with cells, formulas, charts, and more, ensuring compatibility across applications like Microsoft Excel.

List of Properties Intrinsic to the .XLSX File Format
These properties refer to the metadata stored within the file's structure (in docProps/core.xml for core properties and docProps/app.xml for extended properties), which are intrinsic to how the format interacts with file systems and applications. They include creation/modification details, authorship, and document-specific attributes. Core properties are based on Dublin Core metadata, while extended properties are application-specific (e.g., for Excel).

Core Properties (from docProps/core.xml):

  • category: A categorization of the content.
  • contentStatus: The status of the content (e.g., "Draft").
  • contentType: The type of content.
  • created: The creation date/time.
  • creator: The name of the creator/author.
  • description: A textual description of the content.
  • identifier: A unique identifier for the resource.
  • keywords: Keywords associated with the document.
  • language: The language of the content.
  • lastModifiedBy: The user who last modified the document.
  • lastPrinted: The date/time the document was last printed.
  • modified: The last modification date/time.
  • revision: The revision number.
  • subject: The subject of the document.
  • title: The title of the document.
  • version: The version number.

Extended Properties (from docProps/app.xml, specific to XLSX/SpreadsheetML):

  • Application: The application that created the file (e.g., "Microsoft Excel").
  • AppVersion: The version of the application.
  • Company: The company or organization.
  • DocSecurity: Document security level (e.g., 0 for none).
  • HeadingPairs: A vector describing headings like "Worksheets" and their counts (e.g., number of sheets).
  • HyperlinkBase: Base URL for hyperlinks.
  • HyperlinksChanged: Boolean indicating if hyperlinks have changed.
  • LinksUpToDate: Boolean indicating if links are up to date.
  • Manager: The manager's name.
  • ScaleCrop: Boolean for scaling/cropping.
  • SharedDoc: Boolean indicating if the document is shared.
  • TitlesOfParts: A list of sheet names or part titles.
  • TotalTime: Total editing time (optional, in minutes).
  • Template: The template used (optional).
  • Pages: Number of pages (optional, may relate to print settings).
  • Words: Word count (optional).
  • Characters: Character count (optional).
  • Lines: Line count (optional).
  • Paragraphs: Paragraph count (optional, less common in XLSX).

These properties are stored as XML elements and can be read/written without affecting the main content.

Two Direct Download Links for .XLSX Files

Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .XLSX Property Dump
This is an embeddable HTML snippet with JavaScript for a Ghost blog post. It creates a drop zone where users can drag a .XLSX file. It uses JSZip (include via CDN) to unzip, parses the XML with DOMParser, extracts core and extended properties, and dumps them to the screen. Assume JSZip is loaded.

Drag and drop .XLSX file here


Python Class for .XLSX Properties
This class uses zipfile and xml.etree.ElementTree to open, read, modify (write), and print properties. It decodes the ZIP, parses XML, and allows updating properties before writing back.

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

class XLSXProperties:
    def __init__(self, file_path):
        self.file_path = file_path
        self.core_props = {}
        self.ext_props = {}
        self.zip_file = zipfile.ZipFile(file_path, 'r')
        self._read_properties()

    def _read_properties(self):
        # Read core.xml
        if 'docProps/core.xml' in self.zip_file.namelist():
            with self.zip_file.open('docProps/core.xml') as f:
                tree = ET.parse(f)
                ns = {'cp': 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties',
                      'dc': 'http://purl.org/dc/elements/1.1/',
                      'dcterms': 'http://purl.org/dc/terms/'}
                for prop in ['category', 'contentStatus', 'contentType', 'created', 'creator', 'description', 'identifier', 'keywords', 'language', 'lastModifiedBy', 'lastPrinted', 'modified', 'revision', 'subject', 'title', 'version']:
                    elem = tree.find(f'.//cp:{prop}', ns) or tree.find(f'.//dc:{prop}', ns) or tree.find(f'.//dcterms:{prop}', ns)
                    if elem is not None:
                        self.core_props[prop] = elem.text
        # Read app.xml
        if 'docProps/app.xml' in self.zip_file.namelist():
            with self.zip_file.open('docProps/app.xml') as f:
                tree = ET.parse(f)
                ns = {'ap': 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties'}
                for prop in ['Application', 'AppVersion', 'Company', 'DocSecurity', 'HyperlinkBase', 'HyperlinksChanged', 'LinksUpToDate', 'Manager', 'ScaleCrop', 'SharedDoc', 'Template', 'TotalTime', 'Pages', 'Words', 'Characters', 'Lines', 'Paragraphs']:
                    elem = tree.find(f'.//ap:{prop}', ns)
                    if elem is not None:
                        self.ext_props[prop] = elem.text
                # HeadingPairs and TitlesOfParts
                heading = tree.find('.//ap:HeadingPairs', ns)
                if heading is not None:
                    self.ext_props['HeadingPairs'] = ET.tostring(heading, encoding='unicode')
                titles = tree.find('.//ap:TitlesOfParts', ns)
                if titles is not None:
                    self.ext_props['TitlesOfParts'] = ET.tostring(titles, encoding='unicode')

    def print_properties(self):
        print("Core Properties:")
        for k, v in self.core_props.items():
            print(f"{k}: {v}")
        print("\nExtended Properties:")
        for k, v in self.ext_props.items():
            print(f"{k}: {v}")

    def update_property(self, prop_type, key, value):
        if prop_type == 'core':
            self.core_props[key] = value
        elif prop_type == 'extended':
            self.ext_props[key] = value

    def write_properties(self, output_path=None):
        if not output_path:
            output_path = self.file_path
        with zipfile.ZipFile(output_path, 'w') as new_zip:
            for item in self.zip_file.infolist():
                if item.filename == 'docProps/core.xml':
                    core_xml = self._generate_core_xml()
                    new_zip.writestr(item.filename, core_xml)
                elif item.filename == 'docProps/app.xml':
                    app_xml = self._generate_app_xml()
                    new_zip.writestr(item.filename, app_xml)
                else:
                    new_zip.writestr(item, self.zip_file.read(item.filename))

    def _generate_core_xml(self):
        ns = {'cp': 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties',
              'dc': 'http://purl.org/dc/elements/1.1/',
              'dcterms': 'http://purl.org/dc/terms/',
              'xsi': 'http://www.w3.org/2001/XMLSchema-instance'}
        root = ET.Element('cp:coreProperties', nsmap=ns)
        for key, value in self.core_props.items():
            if key in ['created', 'modified']:
                elem = ET.SubElement(root, f'dcterms:{key}', {'xsi:type': 'dcterms:W3CDTF'})
            elif key in ['creator', 'description', 'identifier', 'language', 'subject', 'title']:
                elem = ET.SubElement(root, f'dc:{key}')
            else:
                elem = ET.SubElement(root, f'cp:{key}')
            elem.text = value
        return ET.tostring(root, encoding='utf-8', xml_declaration=True)

    def _generate_app_xml(self):
        ns = {'ap': 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties',
              'vt': 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'}
        root = ET.Element('ap:Properties', nsmap=ns)
        for key, value in self.ext_props.items():
            if key in ['HeadingPairs', 'TitlesOfParts']:
                # Assume value is XML string, parse and append
                sub_tree = ET.fromstring(value)
                root.append(sub_tree)
            else:
                elem = ET.SubElement(root, f'ap:{key}')
                elem.text = value
        return ET.tostring(root, encoding='utf-8', xml_declaration=True)

# Example usage:
# props = XLSXProperties('example.xlsx')
# props.print_properties()
# props.update_property('core', 'title', 'New Title')
# props.write_properties('modified.xlsx')

Java Class for .XLSX Properties
This class uses java.util.zip and javax.xml.parsers to handle ZIP and XML. It reads, updates, and writes properties.

import java.io.*;
import java.util.*;
import java.util.zip.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;

public class XLSXProperties {
    private String filePath;
    private Map<String, String> coreProps = new HashMap<>();
    private Map<String, String> extProps = new HashMap<>();
    private ZipFile zipFile;

    public XLSXProperties(String filePath) throws IOException {
        this.filePath = filePath;
        this.zipFile = new ZipFile(filePath);
        readProperties();
    }

    private void readProperties() {
        // Read core.xml
        ZipEntry coreEntry = zipFile.getEntry("docProps/core.xml");
        if (coreEntry != null) {
            try (InputStream is = zipFile.getInputStream(coreEntry)) {
                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                dbf.setNamespaceAware(true);
                Document doc = dbf.newDocumentBuilder().parse(is);
                String[] props = {"category", "contentStatus", "contentType", "created", "creator", "description", "identifier", "keywords", "language", "lastModifiedBy", "lastPrinted", "modified", "revision", "subject", "title", "version"};
                for (String prop : props) {
                    Node node = doc.getElementsByTagNameNS("http://schemas.openxmlformats.org/package/2006/metadata/core-properties", prop).item(0);
                    if (node == null) node = doc.getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", prop).item(0);
                    if (node == null) node = doc.getElementsByTagNameNS("http://purl.org/dc/terms/", prop).item(0);
                    if (node != null) coreProps.put(prop, node.getTextContent());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // Read app.xml
        ZipEntry appEntry = zipFile.getEntry("docProps/app.xml");
        if (appEntry != null) {
            try (InputStream is = zipFile.getInputStream(appEntry)) {
                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                dbf.setNamespaceAware(true);
                Document doc = dbf.newDocumentBuilder().parse(is);
                String[] props = {"Application", "AppVersion", "Company", "DocSecurity", "HyperlinkBase", "HyperlinksChanged", "LinksUpToDate", "Manager", "ScaleCrop", "SharedDoc", "Template", "TotalTime", "Pages", "Words", "Characters", "Lines", "Paragraphs"};
                for (String prop : props) {
                    Node node = doc.getElementsByTagNameNS("http://schemas.openxmlformats.org/officeDocument/2006/extended-properties", prop).item(0);
                    if (node != null) extProps.put(prop, node.getTextContent());
                }
                // HeadingPairs and TitlesOfParts
                Node heading = doc.getElementsByTagNameNS("http://schemas.openxmlformats.org/officeDocument/2006/extended-properties", "HeadingPairs").item(0);
                if (heading != null) extProps.put("HeadingPairs", heading.getTextContent()); // Simplified, use XML string
                Node titles = doc.getElementsByTagNameNS("http://schemas.openxmlformats.org/officeDocument/2006/extended-properties", "TitlesOfParts").item(0);
                if (titles != null) extProps.put("TitlesOfParts", titles.getTextContent());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void printProperties() {
        System.out.println("Core Properties:");
        coreProps.forEach((k, v) -> System.out.println(k + ": " + v));
        System.out.println("\nExtended Properties:");
        extProps.forEach((k, v) -> System.out.println(k + ": " + v));
    }

    public void updateProperty(String propType, String key, String value) {
        if ("core".equals(propType)) {
            coreProps.put(key, value);
        } else if ("extended".equals(propType)) {
            extProps.put(key, value);
        }
    }

    public void writeProperties(String outputPath) throws IOException {
        if (outputPath == null) outputPath = filePath;
        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outputPath))) {
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                if ("docProps/core.xml".equals(entry.getName())) {
                    zos.putNextEntry(new ZipEntry(entry.getName()));
                    zos.write(generateCoreXml().getBytes("UTF-8"));
                } else if ("docProps/app.xml".equals(entry.getName())) {
                    zos.putNextEntry(new ZipEntry(entry.getName()));
                    zos.write(generateAppXml().getBytes("UTF-8"));
                } else {
                    zos.putNextEntry(entry);
                    try (InputStream is = zipFile.getInputStream(entry)) {
                        byte[] buffer = new byte[1024];
                        int len;
                        while ((len = is.read(buffer)) > 0) {
                            zos.write(buffer, 0, len);
                        }
                    }
                }
                zos.closeEntry();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String generateCoreXml() {
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);
            Document doc = dbf.newDocumentBuilder().newDocument();
            Element root = doc.createElementNS("http://schemas.openxmlformats.org/package/2006/metadata/core-properties", "cp:coreProperties");
            doc.appendChild(root);
            coreProps.forEach((key, value) -> {
                Element elem;
                if (key.equals("created") || key.equals("modified")) {
                    elem = doc.createElementNS("http://purl.org/dc/terms/", "dcterms:" + key);
                    elem.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "xsi:type", "dcterms:W3CDTF");
                } else if (Arrays.asList("creator", "description", "identifier", "language", "subject", "title").contains(key)) {
                    elem = doc.createElementNS("http://purl.org/dc/elements/1.1/", "dc:" + key);
                } else {
                    elem = doc.createElementNS("http://schemas.openxmlformats.org/package/2006/metadata/core-properties", "cp:" + key);
                }
                elem.setTextContent(value);
                root.appendChild(elem);
            });
            Transformer tf = TransformerFactory.newInstance().newTransformer();
            tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            tf.setOutputProperty(OutputKeys.INDENT, "yes");
            StringWriter sw = new StringWriter();
            tf.transform(new DOMSource(doc), new StreamResult(sw));
            return sw.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    private String generateAppXml() {
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);
            Document doc = dbf.newDocumentBuilder().newDocument();
            Element root = doc.createElementNS("http://schemas.openxmlformats.org/officeDocument/2006/extended-properties", "ap:Properties");
            doc.appendChild(root);
            extProps.forEach((key, value) -> {
                if (key.equals("HeadingPairs") || key.equals("TitlesOfParts")) {
                    // Assume value is text, create simple element
                    Element elem = doc.createElementNS("http://schemas.openxmlformats.org/officeDocument/2006/extended-properties", "ap:" + key);
                    elem.setTextContent(value);
                    root.appendChild(elem);
                } else {
                    Element elem = doc.createElementNS("http://schemas.openxmlformats.org/officeDocument/2006/extended-properties", "ap:" + key);
                    elem.setTextContent(value);
                    root.appendChild(elem);
                }
            });
            Transformer tf = TransformerFactory.newInstance().newTransformer();
            tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            tf.setOutputProperty(OutputKeys.INDENT, "yes");
            StringWriter sw = new StringWriter();
            tf.transform(new DOMSource(doc), new StreamResult(sw));
            return sw.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     XLSXProperties props = new XLSXProperties("example.xlsx");
    //     props.printProperties();
    //     props.updateProperty("core", "title", "New Title");
    //     props.writeProperties("modified.xlsx");
    // }
}

JavaScript Class for .XLSX Properties
This class is for Node.js (uses fs and adm-zip library for ZIP, xml2js for XML). Install via npm: npm install adm-zip xml2js. It reads, updates, and writes properties, printing to console.

const fs = require('fs');
const AdmZip = require('adm-zip');
const xml2js = require('xml2js');

class XLSXProperties {
    constructor(filePath) {
        this.filePath = filePath;
        this.coreProps = {};
        this.extProps = {};
        this.zip = new AdmZip(filePath);
        this.readProperties();
    }

    async readProperties() {
        // Read core.xml
        const coreXml = this.zip.readAsText('docProps/core.xml');
        if (coreXml) {
            const parser = new xml2js.Parser({ explicitArray: false });
            const result = await parser.parseStringPromise(coreXml);
            const cp = result['cp:coreProperties'];
            ['category', 'contentStatus', 'contentType', 'dcterms:created', 'dc:creator', 'dc:description', 'dc:identifier', 'cp:keywords', 'dc:language', 'cp:lastModifiedBy', 'cp:lastPrinted', 'dcterms:modified', 'cp:revision', 'dc:subject', 'dc:title', 'cp:version'].forEach(prop => {
                const parts = prop.split(':');
                if (parts.length > 1 && cp[prop]) this.coreProps[parts[1]] = cp[prop];
                else if (cp[prop]) this.coreProps[prop] = cp[prop];
            });
        }
        // Read app.xml
        const appXml = this.zip.readAsText('docProps/app.xml');
        if (appXml) {
            const parser = new xml2js.Parser({ explicitArray: false });
            const result = await parser.parseStringPromise(appXml);
            const ap = result.Properties;
            ['Application', 'AppVersion', 'Company', 'DocSecurity', 'HyperlinkBase', 'HyperlinksChanged', 'LinksUpToDate', 'Manager', 'ScaleCrop', 'SharedDoc', 'Template', 'TotalTime', 'Pages', 'Words', 'Characters', 'Lines', 'Paragraphs'].forEach(prop => {
                if (ap[prop]) this.extProps[prop] = ap[prop];
            });
            if (ap.HeadingPairs) this.extProps.HeadingPairs = JSON.stringify(ap.HeadingPairs);
            if (ap.TitlesOfParts) this.extProps.TitlesOfParts = JSON.stringify(ap.TitlesOfParts);
        }
    }

    printProperties() {
        console.log('Core Properties:');
        console.log(this.coreProps);
        console.log('\nExtended Properties:');
        console.log(this.extProps);
    }

    updateProperty(propType, key, value) {
        if (propType === 'core') {
            this.coreProps[key] = value;
        } else if (propType === 'extended') {
            this.extProps[key] = value;
        }
    }

    async writeProperties(outputPath = this.filePath) {
        this.zip.deleteFile('docProps/core.xml');
        this.zip.deleteFile('docProps/app.xml');
        const coreXml = await this.generateCoreXml();
        this.zip.addFile('docProps/core.xml', Buffer.from(coreXml));
        const appXml = await this.generateAppXml();
        this.zip.addFile('docProps/app.xml', Buffer.from(appXml));
        this.zip.writeZip(outputPath);
    }

    async generateCoreXml() {
        const builder = new xml2js.Builder({ renderOpts: { pretty: true, indent: '  ', newline: '\n' }, xmldec: { version: '1.0', encoding: 'UTF-8' } });
        const root = { 'cp:coreProperties': {} };
        Object.entries(this.coreProps).forEach(([key, value]) => {
            if (['created', 'modified'].includes(key)) root['cp:coreProperties'][`dcterms:${key}`] = { $: { 'xsi:type': 'dcterms:W3CDTF' }, _: value };
            else if (['creator', 'description', 'identifier', 'language', 'subject', 'title'].includes(key)) root['cp:coreProperties'][`dc:${key}`] = value;
            else root['cp:coreProperties'][`cp:${key}`] = value;
        });
        return builder.buildObject(root);
    }

    async generateAppXml() {
        const builder = new xml2js.Builder({ renderOpts: { pretty: true, indent: '  ', newline: '\n' }, xmldec: { version: '1.0', encoding: 'UTF-8' } });
        const root = { Properties: {} };
        Object.entries(this.extProps).forEach(([key, value]) => {
            if (['HeadingPairs', 'TitlesOfParts'].includes(key)) root.Properties[key] = JSON.parse(value); // Assume JSON string
            else root.Properties[key] = value;
        });
        return builder.buildObject(root);
    }
}

// Example usage:
// const props = new XLSXProperties('example.xlsx');
// props.printProperties();
// props.updateProperty('core', 'title', 'New Title');
// await props.writeProperties('modified.xlsx');

C Class for .XLSX Properties
This is a C struct-based "class" (since C has no classes; use functions). It uses libzip for ZIP and libxml2 for XML (assume installed). It reads, updates, prints, and writes properties. Compile with -lzip -lxml2.

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

typedef struct {
    char *filePath;
    xmlChar *coreProps[16]; // Array for core props values
    xmlChar *extProps[17];  // Array for ext props values
    const char *coreKeys[16];
    const char *extKeys[17];
    zip_t *zip;
} XLSXProperties;

void initProps(XLSXProperties *props) {
    const char *ck[] = {"category", "contentStatus", "contentType", "created", "creator", "description", "identifier", "keywords", "language", "lastModifiedBy", "lastPrinted", "modified", "revision", "subject", "title", "version"};
    const char *ek[] = {"Application", "AppVersion", "Company", "DocSecurity", "HyperlinkBase", "HyperlinksChanged", "LinksUpToDate", "Manager", "ScaleCrop", "SharedDoc", "Template", "TotalTime", "Pages", "Words", "Characters", "Lines", "Paragraphs"};
    memcpy(props->coreKeys, ck, sizeof(ck));
    memcpy(props->extKeys, ek, sizeof(ek));
    for (int i = 0; i < 16; i++) props->coreProps[i] = NULL;
    for (int i = 0; i < 17; i++) props->extProps[i] = NULL;
}

XLSXProperties *XLSXProperties_new(const char *filePath) {
    XLSXProperties *props = malloc(sizeof(XLSXProperties));
    props->filePath = strdup(filePath);
    int err = 0;
    props->zip = zip_open(filePath, 0, &err);
    if (!props->zip) {
        fprintf(stderr, "Failed to open ZIP\n");
        free(props);
        return NULL;
    }
    initProps(props);
    // Read core.xml
    zip_file_t *coreFile = zip_fopen(props->zip, "docProps/core.xml", 0);
    if (coreFile) {
        zip_stat_t stat;
        zip_stat(props->zip, "docProps/core.xml", 0, &stat);
        char *buf = malloc(stat.size + 1);
        zip_fread(coreFile, buf, stat.size);
        buf[stat.size] = '\0';
        xmlDocPtr doc = xmlReadMemory(buf, stat.size, NULL, NULL, 0);
        if (doc) {
            xmlNode *root = xmlDocGetRootElement(doc);
            for (int i = 0; i < 16; i++) {
                xmlNode *node = root->children;
                while (node) {
                    if (!xmlStrcmp(node->name, (const xmlChar *)props->coreKeys[i])) {
                        props->coreProps[i] = xmlNodeGetContent(node);
                        break;
                    }
                    node = node->next;
                }
            }
            xmlFreeDoc(doc);
        }
        free(buf);
        zip_fclose(coreFile);
    }
    // Read app.xml (similar, simplified namespace ignoring for brevity)
    zip_file_t *appFile = zip_fopen(props->zip, "docProps/app.xml", 0);
    if (appFile) {
        zip_stat_t stat;
        zip_stat(props->zip, "docProps/app.xml", 0, &stat);
        char *buf = malloc(stat.size + 1);
        zip_fread(appFile, buf, stat.size);
        buf[stat.size] = '\0';
        xmlDocPtr doc = xmlReadMemory(buf, stat.size, NULL, NULL, 0);
        if (doc) {
            xmlNode *root = xmlDocGetRootElement(doc);
            for (int i = 0; i < 17; i++) {
                xmlNode *node = root->children;
                while (node) {
                    if (!xmlStrcmp(node->name, (const xmlChar *)props->extKeys[i])) {
                        props->extProps[i] = xmlNodeGetContent(node);
                        break;
                    }
                    node = node->next;
                }
            }
            // HeadingPairs and TitlesOfParts (dump as content)
            xmlNode *heading = xmlGetProp(root, (xmlChar *)"HeadingPairs"); // Simplified
            if (heading) props->extProps[17] = xmlNodeGetContent(heading); // Extra slot if needed
            xmlFreeDoc(doc);
        }
        free(buf);
        zip_fclose(appFile);
    }
    return props;
}

void XLSXProperties_print(XLSXProperties *props) {
    printf("Core Properties:\n");
    for (int i = 0; i < 16; i++) {
        if (props->coreProps[i]) printf("%s: %s\n", props->coreKeys[i], props->coreProps[i]);
    }
    printf("\nExtended Properties:\n");
    for (int i = 0; i < 17; i++) {
        if (props->extProps[i]) printf("%s: %s\n", props->extKeys[i], props->extProps[i]);
    }
}

void XLSXProperties_update(XLSXProperties *props, const char *propType, const char *key, const char *value) {
    int index = -1;
    if (strcmp(propType, "core") == 0) {
        for (int i = 0; i < 16; i++) {
            if (strcmp(props->coreKeys[i], key) == 0) {
                index = i;
                break;
            }
        }
        if (index != -1) {
            if (props->coreProps[index]) xmlFree(props->coreProps[index]);
            props->coreProps[index] = xmlStrdup((xmlChar *)value);
        }
    } else if (strcmp(propType, "extended") == 0) {
        for (int i = 0; i < 17; i++) {
            if (strcmp(props->extKeys[i], key) == 0) {
                index = i;
                break;
            }
        }
        if (index != -1) {
            if (props->extProps[index]) xmlFree(props->extProps[index]);
            props->extProps[index] = xmlStrdup((xmlChar *)value);
        }
    }
}

void XLSXProperties_write(XLSXProperties *props, const char *outputPath) {
    zip_t *newZip = zip_open(outputPath, ZIP_CREATE | ZIP_TRUNCATE, NULL);
    int num = zip_get_num_entries(props->zip, 0);
    for (int i = 0; i < num; i++) {
        const char *name = zip_get_name(props->zip, i, 0);
        if (strcmp(name, "docProps/core.xml") == 0) {
            // Generate new core.xml
            xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
            xmlNodePtr root = xmlNewNode(NULL, BAD_CAST "cp:coreProperties");
            xmlDocSetRootElement(doc, root);
            for (int j = 0; j < 16; j++) {
                if (props->coreProps[j]) {
                    xmlNewChild(root, NULL, BAD_CAST props->coreKeys[j], props->coreProps[j]);
                }
            }
            xmlChar *xmlbuff;
            int buffersize;
            xmlDocDumpFormatMemory(doc, &xmlbuff, &buffersize, 1);
            zip_source_t *src = zip_source_buffer(newZip, xmlbuff, buffersize, 0);
            zip_file_add(newZip, name, src, ZIP_FL_OVERWRITE);
            xmlFreeDoc(doc);
        } else if (strcmp(name, "docProps/app.xml") == 0) {
            // Similar for app.xml
            xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
            xmlNodePtr root = xmlNewNode(NULL, BAD_CAST "Properties");
            xmlDocSetRootElement(doc, root);
            for (int j = 0; j < 17; j++) {
                if (props->extProps[j]) {
                    xmlNewChild(root, NULL, BAD_CAST props->extKeys[j], props->extProps[j]);
                }
            }
            xmlChar *xmlbuff;
            int buffersize;
            xmlDocDumpFormatMemory(doc, &xmlbuff, &buffersize, 1);
            zip_source_t *src = zip_source_buffer(newZip, xmlbuff, buffersize, 0);
            zip_file_add(newZip, name, src, ZIP_FL_OVERWRITE);
            xmlFreeDoc(doc);
        } else {
            zip_source_t *src = zip_source_zip(newZip, props->zip, i, 0, 0, -1);
            zip_file_add(newZip, name, src, ZIP_FL_OVERWRITE);
        }
    }
    zip_close(newZip);
}

void XLSXProperties_free(XLSXProperties *props) {
    for (int i = 0; i < 16; i++) if (props->coreProps[i]) xmlFree(props->coreProps[i]);
    for (int i = 0; i < 17; i++) if (props->extProps[i]) xmlFree(props->extProps[i]);
    zip_close(props->zip);
    free(props->filePath);
    free(props);
}

// Example usage:
// int main() {
//     XLSXProperties *props = XLSXProperties_new("example.xlsx");
//     XLSXProperties_print(props);
//     XLSXProperties_update(props, "core", "title", "New Title");
//     XLSXProperties_write(props, "modified.xlsx");
//     XLSXProperties_free(props);
//     return 0;
// }