Task 568: .PPTX File Format
Task 568: .PPTX File Format
File Format Specifications for .PPTX
The .PPTX file format is part of the Office Open XML (OOXML) standard, defined in ECMA-376 and ISO/IEC 29500. It is a ZIP-based container using Open Packaging Conventions (OPC), with content structured in XML files for presentations, including slides, metadata, and media. The primary markup languages are PresentationML for structure and DrawingML for graphics.
List of Properties Intrinsic to the .PPTX File Format
These properties include both static format identifiers and extractable metadata/structure from the file's ZIP container and XML parts. They are "intrinsic to its file system" in the sense that they are embedded in the file's package structure (e.g., ZIP headers, mandatory XML files like core.xml and app.xml).
- File Extension: .pptx
- MIME Type: application/vnd.openxmlformats-officedocument.presentationml.presentation
- Magic Number (File Signature): 50 4B 03 04 (hex for PK\x03\x04, indicating ZIP container)
- Core Metadata Properties (from docProps/core.xml):
- Title
- Subject
- Creator
- Keywords
- Description
- Last Modified By
- Revision
- Created Date
- Modified Date
- Extended Metadata Properties (from docProps/app.xml):
- Application
- App Version
- Company
- Number of Slides
- Number of Hidden Slides
- Number of Notes
- Number of Paragraphs
- Number of Words
- Number of Characters
- Template
- Structural Properties:
- Number of Files in ZIP Archive
- Presence of Mandatory Parts: [Content_Types].xml, _rels/.rels, ppt/presentation.xml, docProps/core.xml
- Number of Slides (cross-referenced from ppt/presentation.xml slide list)
Two Direct Download Links for .PPTX Files
- https://scholar.harvard.edu/files/torman_personal/files/samplepptx.pptx
- https://www.suu.edu/webservices/styleguide/example-files/example.pptx
Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .PPTX Property Dump
This is a self-contained HTML page with embedded JavaScript (using JSZip library for ZIP handling; include it via CDN). It allows drag-and-drop of a .PPTX file and dumps the properties to the screen.
Python Class for .PPTX Properties
This class uses built-in zipfile and xml.etree.ElementTree to read, print, and write properties. Writing updates core/app.xml and saves a new file.
import zipfile
import xml.etree.ElementTree as ET
from io import BytesIO
from datetime import datetime
class PPTXHandler:
def __init__(self, filepath):
self.filepath = filepath
self.zip = zipfile.ZipFile(filepath, 'r')
self.properties = self._read_properties()
def _read_properties(self):
properties = {
'File Extension': '.pptx',
'MIME Type': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'Magic Number': '50 4B 03 04',
'Number of Files in ZIP Archive': len(self.zip.namelist()),
}
mandatory_parts = ['[Content_Types].xml', '_rels/.rels', 'ppt/presentation.xml', 'docProps/core.xml']
properties['Mandatory Parts Present'] = all(part in self.zip.namelist() for part in mandatory_parts)
# Core Metadata
if 'docProps/core.xml' in self.zip.namelist():
core_xml = self.zip.read('docProps/core.xml').decode('utf-8')
root = ET.fromstring(core_xml)
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/'}
properties['Title'] = root.find('dc:title', ns).text if root.find('dc:title', ns) is not None else 'N/A'
properties['Subject'] = root.find('cp:subject', ns).text if root.find('cp:subject', ns) is not None else 'N/A'
properties['Creator'] = root.find('dc:creator', ns).text if root.find('dc:creator', ns) is not None else 'N/A'
properties['Keywords'] = root.find('cp:keywords', ns).text if root.find('cp:keywords', ns) is not None else 'N/A'
properties['Description'] = root.find('dc:description', ns).text if root.find('dc:description', ns) is not None else 'N/A'
properties['Last Modified By'] = root.find('cp:lastModifiedBy', ns).text if root.find('cp:lastModifiedBy', ns) is not None else 'N/A'
properties['Revision'] = root.find('cp:revision', ns).text if root.find('cp:revision', ns) is not None else 'N/A'
properties['Created Date'] = root.find('dcterms:created', ns).text if root.find('dcterms:created', ns) is not None else 'N/A'
properties['Modified Date'] = root.find('dcterms:modified', ns).text if root.find('dcterms:modified', ns) is not None else 'N/A'
# Extended Metadata
if 'docProps/app.xml' in self.zip.namelist():
app_xml = self.zip.read('docProps/app.xml').decode('utf-8')
root = ET.fromstring(app_xml)
ns = {'': 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties'}
properties['Application'] = root.find('Application', ns).text if root.find('Application', ns) is not None else 'N/A'
properties['App Version'] = root.find('AppVersion', ns).text if root.find('AppVersion', ns) is not None else 'N/A'
properties['Company'] = root.find('Company', ns).text if root.find('Company', ns) is not None else 'N/A'
properties['Number of Slides'] = root.find('Slides', ns).text if root.find('Slides', ns) is not None else 'N/A'
properties['Number of Hidden Slides'] = root.find('HiddenSlides', ns).text if root.find('HiddenSlides', ns) is not None else 'N/A'
properties['Number of Notes'] = root.find('Notes', ns).text if root.find('Notes', ns) is not None else 'N/A'
properties['Number of Paragraphs'] = root.find('Paragraphs', ns).text if root.find('Paragraphs', ns) is not None else 'N/A'
properties['Number of Words'] = root.find('Words', ns).text if root.find('Words', ns) is not None else 'N/A'
properties['Number of Characters'] = root.find('Characters', ns).text if root.find('Characters', ns) is not None else 'N/A'
properties['Template'] = root.find('Template', ns).text if root.find('Template', ns) is not None else 'N/A'
# Number of Slides from presentation.xml
if 'ppt/presentation.xml' in self.zip.namelist():
pres_xml = self.zip.read('ppt/presentation.xml').decode('utf-8')
root = ET.fromstring(pres_xml)
ns = {'p': 'http://schemas.openxmlformats.org/presentationml/2006/main'}
properties['Number of Slides (from presentation.xml)'] = len(root.findall('.//p:sldId', ns))
return properties
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
def write_properties(self, new_properties, output_path):
with zipfile.ZipFile(output_path, 'w') as new_zip:
for item in self.zip.infolist():
data = self.zip.read(item.filename)
if item.filename == 'docProps/core.xml' and 'core' in new_properties:
root = ET.fromstring(data)
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 key, value in new_properties['core'].items():
elem = root.find(f'.//{key}', ns)
if elem is not None:
elem.text = value
else:
# Add if missing (simplified)
ET.SubElement(root, key, xmlns=ns['dc' if key in ['title', 'creator', 'description'] else 'cp']).text = value
data = ET.tostring(root, encoding='utf-8')
elif item.filename == 'docProps/app.xml' and 'app' in new_properties:
root = ET.fromstring(data)
ns = {'': 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties'}
for key, value in new_properties['app'].items():
elem = root.find(key, ns)
if elem is not None:
elem.text = value
else:
ET.SubElement(root, key).text = value
data = ET.tostring(root, encoding='utf-8')
new_zip.writestr(item, data)
Java Class for .PPTX Properties
This class uses java.util.zip and javax.xml.parsers 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 PPTXHandler {
private String filepath;
private ZipFile zip;
private Map<String, String> properties;
public PPTXHandler(String filepath) throws IOException, Exception {
this.filepath = filepath;
this.zip = new ZipFile(filepath);
this.properties = readProperties();
}
private Map<String, String> readProperties() throws Exception {
Map<String, String> props = new HashMap<>();
props.put("File Extension", ".pptx");
props.put("MIME Type", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
props.put("Magic Number", "50 4B 03 04");
props.put("Number of Files in ZIP Archive", String.valueOf(zip.size()));
String[] mandatoryParts = {"[Content_Types].xml", "_rels/.rels", "ppt/presentation.xml", "docProps/core.xml"};
boolean allPresent = true;
for (String part : mandatoryParts) {
if (zip.getEntry(part) == null) {
allPresent = false;
break;
}
}
props.put("Mandatory Parts Present", allPresent ? "Yes" : "No");
// Core Metadata
ZipEntry coreEntry = zip.getEntry("docProps/core.xml");
if (coreEntry != null) {
InputStream is = zip.getInputStream(coreEntry);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(new InputSource(is));
props.put("Title", getNodeValue(doc, "//*[local-name()='title']"));
props.put("Subject", getNodeValue(doc, "//*[local-name()='subject']"));
props.put("Creator", getNodeValue(doc, "//*[local-name()='creator']"));
props.put("Keywords", getNodeValue(doc, "//*[local-name()='keywords']"));
props.put("Description", getNodeValue(doc, "//*[local-name()='description']"));
props.put("Last Modified By", getNodeValue(doc, "//*[local-name()='lastModifiedBy']"));
props.put("Revision", getNodeValue(doc, "//*[local-name()='revision']"));
props.put("Created Date", getNodeValue(doc, "//*[local-name()='created']"));
props.put("Modified Date", getNodeValue(doc, "//*[local-name()='modified']"));
}
// Extended Metadata
ZipEntry appEntry = zip.getEntry("docProps/app.xml");
if (appEntry != null) {
InputStream is = zip.getInputStream(appEntry);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(new InputSource(is));
props.put("Application", getNodeValue(doc, "//*[local-name()='Application']"));
props.put("App Version", getNodeValue(doc, "//*[local-name()='AppVersion']"));
props.put("Company", getNodeValue(doc, "//*[local-name()='Company']"));
props.put("Number of Slides", getNodeValue(doc, "//*[local-name()='Slides']"));
props.put("Number of Hidden Slides", getNodeValue(doc, "//*[local-name()='HiddenSlides']"));
props.put("Number of Notes", getNodeValue(doc, "//*[local-name()='Notes']"));
props.put("Number of Paragraphs", getNodeValue(doc, "//*[local-name()='Paragraphs']"));
props.put("Number of Words", getNodeValue(doc, "//*[local-name()='Words']"));
props.put("Number of Characters", getNodeValue(doc, "//*[local-name()='Characters']"));
props.put("Template", getNodeValue(doc, "//*[local-name()='Template']"));
}
// Number of Slides from presentation.xml
ZipEntry presEntry = zip.getEntry("ppt/presentation.xml");
if (presEntry != null) {
InputStream is = zip.getInputStream(presEntry);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(new InputSource(is));
NodeList slides = doc.getElementsByTagNameNS("http://schemas.openxmlformats.org/presentationml/2006/main", "sldId");
props.put("Number of Slides (from presentation.xml)", String.valueOf(slides.getLength()));
}
return props;
}
private String getNodeValue(Document doc, String xpath) throws Exception {
javax.xml.xpath.XPath xp = javax.xml.xpath.XPathFactory.newInstance().newXPath();
Node node = (Node) xp.evaluate(xpath, doc, javax.xml.xpath.XPathConstants.NODE);
return node != null ? node.getTextContent() : "N/A";
}
public void printProperties() {
for (Map.Entry<String, String> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void writeProperties(Map<String, Map<String, String>> newProperties, String outputPath) throws Exception {
try (ZipOutputStream newZip = new ZipOutputStream(new FileOutputStream(outputPath))) {
byte[] buffer = new byte[1024];
for (java.util.Enumeration<? extends ZipEntry> e = zip.entries(); e.hasMoreElements(); ) {
ZipEntry entry = e.nextElement();
InputStream is = zip.getInputStream(entry);
newZip.putNextEntry(new ZipEntry(entry.getName()));
if (entry.getName().equals("docProps/core.xml") && newProperties.containsKey("core")) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(is);
for (Map.Entry<String, String> prop : newProperties.get("core").entrySet()) {
Node node = doc.getElementsByTagNameNS("*", prop.getKey()).item(0);
if (node != null) {
node.setTextContent(prop.getValue());
} else {
// Add if missing (simplified)
Element newElem = doc.createElement(prop.getKey());
newElem.setTextContent(prop.getValue());
doc.getDocumentElement().appendChild(newElem);
}
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
t.transform(new DOMSource(doc), new StreamResult(baos));
byte[] data = baos.toByteArray();
newZip.write(data, 0, data.length);
} else if (entry.getName().equals("docProps/app.xml") && newProperties.containsKey("app")) {
// Similar logic for app.xml
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().parse(is);
for (Map.Entry<String, String> prop : newProperties.get("app").entrySet()) {
Node node = doc.getElementsByTagName(prop.getKey()).item(0);
if (node != null) {
node.setTextContent(prop.getValue());
} else {
Element newElem = doc.createElement(prop.getKey());
newElem.setTextContent(prop.getValue());
doc.getDocumentElement().appendChild(newElem);
}
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
t.transform(new DOMSource(doc), new StreamResult(baos));
byte[] data = baos.toByteArray();
newZip.write(data, 0, data.length);
} else {
int len;
while ((len = is.read(buffer)) > 0) {
newZip.write(buffer, 0, len);
}
}
newZip.closeEntry();
is.close();
}
}
}
}
JavaScript Class for .PPTX Properties
This class is for Node.js (uses jszip and fs; install jszip via npm). It reads, prints, and writes properties.
const fs = require('fs');
const JSZip = require('jszip');
const { DOMParser, XMLSerializer } = require('xmldom');
class PPTXHandler {
constructor(filepath) {
this.filepath = filepath;
this.properties = null;
}
async readProperties() {
const data = fs.readFileSync(this.filepath);
const zip = await JSZip.loadAsync(data);
this.properties = {
'File Extension': '.pptx',
'MIME Type': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'Magic Number': '50 4B 03 04',
'Number of Files in ZIP Archive': Object.keys(zip.files).length,
};
const mandatoryParts = ['[Content_Types].xml', '_rels/.rels', 'ppt/presentation.xml', 'docProps/core.xml'];
this.properties['Mandatory Parts Present'] = mandatoryParts.every(part => zip.file(part) !== null) ? 'Yes' : 'No';
// Core Metadata
const coreXml = await zip.file('docProps/core.xml')?.async('text');
if (coreXml) {
const doc = new DOMParser().parseFromString(coreXml);
this.properties['Title'] = doc.getElementsByTagName('dc:title')[0]?.textContent || 'N/A';
this.properties['Subject'] = doc.getElementsByTagName('cp:subject')[0]?.textContent || 'N/A';
this.properties['Creator'] = doc.getElementsByTagName('dc:creator')[0]?.textContent || 'N/A';
this.properties['Keywords'] = doc.getElementsByTagName('cp:keywords')[0]?.textContent || 'N/A';
this.properties['Description'] = doc.getElementsByTagName('dc:description')[0]?.textContent || 'N/A';
this.properties['Last Modified By'] = doc.getElementsByTagName('cp:lastModifiedBy')[0]?.textContent || 'N/A';
this.properties['Revision'] = doc.getElementsByTagName('cp:revision')[0]?.textContent || 'N/A';
this.properties['Created Date'] = doc.getElementsByTagName('dcterms:created')[0]?.textContent || 'N/A';
this.properties['Modified Date'] = doc.getElementsByTagName('dcterms:modified')[0]?.textContent || 'N/A';
}
// Extended Metadata
const appXml = await zip.file('docProps/app.xml')?.async('text');
if (appXml) {
const doc = new DOMParser().parseFromString(appXml);
this.properties['Application'] = doc.getElementsByTagName('Application')[0]?.textContent || 'N/A';
this.properties['App Version'] = doc.getElementsByTagName('AppVersion')[0]?.textContent || 'N/A';
this.properties['Company'] = doc.getElementsByTagName('Company')[0]?.textContent || 'N/A';
this.properties['Number of Slides'] = doc.getElementsByTagName('Slides')[0]?.textContent || 'N/A';
this.properties['Number of Hidden Slides'] = doc.getElementsByTagName('HiddenSlides')[0]?.textContent || 'N/A';
this.properties['Number of Notes'] = doc.getElementsByTagName('Notes')[0]?.textContent || 'N/A';
this.properties['Number of Paragraphs'] = doc.getElementsByTagName('Paragraphs')[0]?.textContent || 'N/A';
this.properties['Number of Words'] = doc.getElementsByTagName('Words')[0]?.textContent || 'N/A';
this.properties['Number of Characters'] = doc.getElementsByTagName('Characters')[0]?.textContent || 'N/A';
this.properties['Template'] = doc.getElementsByTagName('Template')[0]?.textContent || 'N/A';
}
// Number of Slides from presentation.xml
const presXml = await zip.file('ppt/presentation.xml')?.async('text');
if (presXml) {
const doc = new DOMParser().parseFromString(presXml);
this.properties['Number of Slides (from presentation.xml)'] = doc.getElementsByTagName('p:sldId').length;
}
return this.properties;
}
printProperties() {
if (!this.properties) {
console.log('Properties not read yet.');
return;
}
Object.entries(this.properties).forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
}
async writeProperties(newProperties, outputPath) {
const data = fs.readFileSync(this.filepath);
const zip = await JSZip.loadAsync(data);
if (newProperties.core) {
let coreXml = await zip.file('docProps/core.xml')?.async('text');
if (coreXml) {
const doc = new DOMParser().parseFromString(coreXml);
for (const [key, value] of Object.entries(newProperties.core)) {
const node = doc.getElementsByTagNameNS('*', key)[0];
if (node) {
node.textContent = value;
} else {
const newElem = doc.createElement(key);
newElem.textContent = value;
doc.documentElement.appendChild(newElem);
}
}
coreXml = new XMLSerializer().serializeToString(doc);
zip.file('docProps/core.xml', coreXml);
}
}
if (newProperties.app) {
let appXml = await zip.file('docProps/app.xml')?.async('text');
if (appXml) {
const doc = new DOMParser().parseFromString(appXml);
for (const [key, value] of Object.entries(newProperties.app)) {
const node = doc.getElementsByTagName(key)[0];
if (node) {
node.textContent = value;
} else {
const newElem = doc.createElement(key);
newElem.textContent = value;
doc.documentElement.appendChild(newElem);
}
}
appXml = new XMLSerializer().serializeToString(doc);
zip.file('docProps/app.xml', appXml);
}
}
const newBuffer = await zip.generateAsync({ type: 'nodebuffer' });
fs.writeFileSync(outputPath, newBuffer);
}
}
C++ Class for .PPTX Properties
This class uses libzip and tinyxml2 (assume installed) for ZIP and XML handling. It reads, prints, and writes properties.
#include <iostream>
#include <string>
#include <map>
#include <zip.h>
#include <tinyxml2.h>
class PPTXHandler {
private:
std::string filepath;
std::map<std::string, std::string> properties;
std::string readZipFile(zip_t* zip, const std::string& name) {
zip_file_t* file = zip_fopen(zip, name.c_str(), 0);
if (!file) return "";
zip_stat_t stat;
zip_stat_init(&stat);
zip_stat(zip, name.c_str(), 0, &stat);
char* buffer = new char[stat.size + 1];
zip_fread(file, buffer, stat.size);
buffer[stat.size] = '\0';
std::string content(buffer);
delete[] buffer;
zip_fclose(file);
return content;
}
public:
PPTXHandler(const std::string& filepath) : filepath(filepath) {}
void readProperties() {
int err = 0;
zip_t* zip = zip_open(filepath.c_str(), ZIP_RDONLY, &err);
if (!zip) return;
properties["File Extension"] = ".pptx";
properties["MIME Type"] = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
properties["Magic Number"] = "50 4B 03 04";
properties["Number of Files in ZIP Archive"] = std::to_string(zip_get_num_entries(zip, 0));
std::string mandatoryParts[4] = {"[Content_Types].xml", "_rels/.rels", "ppt/presentation.xml", "docProps/core.xml"};
bool allPresent = true;
for (const auto& part : mandatoryParts) {
if (zip_name_locate(zip, part.c_str(), 0) < 0) {
allPresent = false;
break;
}
}
properties["Mandatory Parts Present"] = allPresent ? "Yes" : "No";
// Core Metadata
std::string coreXml = readZipFile(zip, "docProps/core.xml");
if (!coreXml.empty()) {
tinyxml2::XMLDocument doc;
doc.Parse(coreXml.c_str());
properties["Title"] = doc.FirstChildElement()->FirstChildElement("dc:title") ? doc.FirstChildElement()->FirstChildElement("dc:title")->GetText() : "N/A";
properties["Subject"] = doc.FirstChildElement()->FirstChildElement("cp:subject") ? doc.FirstChildElement()->FirstChildElement("cp:subject")->GetText() : "N/A";
properties["Creator"] = doc.FirstChildElement()->FirstChildElement("dc:creator") ? doc.FirstChildElement()->FirstChildElement("dc:creator")->GetText() : "N/A";
properties["Keywords"] = doc.FirstChildElement()->FirstChildElement("cp:keywords") ? doc.FirstChildElement()->FirstChildElement("cp:keywords")->GetText() : "N/A";
properties["Description"] = doc.FirstChildElement()->FirstChildElement("dc:description") ? doc.FirstChildElement()->FirstChildElement("dc:description")->GetText() : "N/A";
properties["Last Modified By"] = doc.FirstChildElement()->FirstChildElement("cp:lastModifiedBy") ? doc.FirstChildElement()->FirstChildElement("cp:lastModifiedBy")->GetText() : "N/A";
properties["Revision"] = doc.FirstChildElement()->FirstChildElement("cp:revision") ? doc.FirstChildElement()->FirstChildElement("cp:revision")->GetText() : "N/A";
properties["Created Date"] = doc.FirstChildElement()->FirstChildElement("dcterms:created") ? doc.FirstChildElement()->FirstChildElement("dcterms:created")->GetText() : "N/A";
properties["Modified Date"] = doc.FirstChildElement()->FirstChildElement("dcterms:modified") ? doc.FirstChildElement()->FirstChildElement("dcterms:modified")->GetText() : "N/A";
}
// Extended Metadata
std::string appXml = readZipFile(zip, "docProps/app.xml");
if (!appXml.empty()) {
tinyxml2::XMLDocument doc;
doc.Parse(appXml.c_str());
properties["Application"] = doc.FirstChildElement()->FirstChildElement("Application") ? doc.FirstChildElement()->FirstChildElement("Application")->GetText() : "N/A";
properties["App Version"] = doc.FirstChildElement()->FirstChildElement("AppVersion") ? doc.FirstChildElement()->FirstChildElement("AppVersion")->GetText() : "N/A";
properties["Company"] = doc.FirstChildElement()->FirstChildElement("Company") ? doc.FirstChildElement()->FirstChildElement("Company")->GetText() : "N/A";
properties["Number of Slides"] = doc.FirstChildElement()->FirstChildElement("Slides") ? doc.FirstChildElement()->FirstChildElement("Slides")->GetText() : "N/A";
properties["Number of Hidden Slides"] = doc.FirstChildElement()->FirstChildElement("HiddenSlides") ? doc.FirstChildElement()->FirstChildElement("HiddenSlides")->GetText() : "N/A";
properties["Number of Notes"] = doc.FirstChildElement()->FirstChildElement("Notes") ? doc.FirstChildElement()->FirstChildElement("Notes")->GetText() : "N/A";
properties["Number of Paragraphs"] = doc.FirstChildElement()->FirstChildElement("Paragraphs") ? doc.FirstChildElement()->FirstChildElement("Paragraphs")->GetText() : "N/A";
properties["Number of Words"] = doc.FirstChildElement()->FirstChildElement("Words") ? doc.FirstChildElement()->FirstChildElement("Words")->GetText() : "N/A";
properties["Number of Characters"] = doc.FirstChildElement()->FirstChildElement("Characters") ? doc.FirstChildElement()->FirstChildElement("Characters")->GetText() : "N/A";
properties["Template"] = doc.FirstChildElement()->FirstChildElement("Template") ? doc.FirstChildElement()->FirstChildElement("Template")->GetText() : "N/A";
}
// Number of Slides from presentation.xml
std::string presXml = readZipFile(zip, "ppt/presentation.xml");
if (!presXml.empty()) {
tinyxml2::XMLDocument doc;
doc.Parse(presXml.c_str());
int count = 0;
for (tinyxml2::XMLElement* elem = doc.FirstChildElement()->FirstChildElement("p:sldIdLst")->FirstChildElement("p:sldId"); elem; elem = elem->NextSiblingElement("p:sldId")) {
count++;
}
properties["Number of Slides (from presentation.xml)"] = std::to_string(count);
}
zip_close(zip);
}
void printProperties() {
for (const auto& pair : properties) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
}
void writeProperties(const std::map<std::string, std::map<std::string, std::string>>& newProperties, const std::string& outputPath) {
int err = 0;
zip_t* srcZip = zip_open(filepath.c_str(), ZIP_RDONLY, &err);
zip_t* destZip = zip_open(outputPath.c_str(), ZIP_CREATE | ZIP_TRUNCATE, &err);
for (int i = 0; i < zip_get_num_entries(srcZip, 0); ++i) {
const char* name = zip_get_name(srcZip, i, 0);
std::string content = readZipFile(srcZip, name);
if (std::string(name) == "docProps/core.xml" && newProperties.find("core") != newProperties.end()) {
tinyxml2::XMLDocument doc;
doc.Parse(content.c_str());
for (const auto& prop : newProperties.at("core")) {
tinyxml2::XMLElement* elem = doc.FirstChildElement()->FirstChildElement(prop.first.c_str());
if (elem) {
elem->SetText(prop.second.c_str());
} else {
tinyxml2::XMLElement* newElem = doc.NewElement(prop.first.c_str());
newElem->SetText(prop.second.c_str());
doc.FirstChildElement()->InsertEndChild(newElem);
}
}
tinyxml2::XMLPrinter printer;
doc.Print(&printer);
content = printer.CStr();
} else if (std::string(name) == "docProps/app.xml" && newProperties.find("app") != newProperties.end()) {
tinyxml2::XMLDocument doc;
doc.Parse(content.c_str());
for (const auto& prop : newProperties.at("app")) {
tinyxml2::XMLElement* elem = doc.FirstChildElement()->FirstChildElement(prop.first.c_str());
if (elem) {
elem->SetText(prop.second.c_str());
} else {
tinyxml2::XMLElement* newElem = doc.NewElement(prop.first.c_str());
newElem->SetText(prop.second.c_str());
doc.FirstChildElement()->InsertEndChild(newElem);
}
}
tinyxml2::XMLPrinter printer;
doc.Print(&printer);
content = printer.CStr();
}
zip_source_t* source = zip_source_buffer(destZip, content.c_str(), content.size(), 0);
zip_file_add(destZip, name, source, ZIP_FL_OVERWRITE);
}
zip_close(srcZip);
zip_close(destZip);
}
};