Task 463: .NUMBERS File Format
Task 463: .NUMBERS File Format
- The .NUMBERS file format, used by Apple's Numbers application, is a proprietary spreadsheet format. Based on available documentation and reverse-engineered details, the following properties are intrinsic to the format within its file structure:
- File Extension: .numbers
- MIME Type: application/vnd.apple.numbers
- Developer: Apple Inc.
- File Category: Spreadsheet
- Magic Number (File Signature): 0x504B0304 (indicating a ZIP archive)
- Container Format: ZIP archive
- Internal Compression for Data Files: Snappy compression applied to .iwa files
- Data Encoding: Protocol Buffers (Protobuf) serialized within .iwa archives
- Typical Internal Structure: Contains directories such as Index/ with .iwa files (e.g., Document.iwa for document metadata, CalculationEngine.iwa for formulas, ViewState.iwa for layout, Tables/DataList.iwa for cell data), Metadata/ with Properties.plist for properties, and preview images (e.g., preview.jpg or TIFF files).
Note that the format lacks a publicly available official specification from Apple, and details are derived from community analysis. Pre-2013 versions used XML-based structures, while post-2013 versions employ binary .iwa files.
- Two direct download links for sample .NUMBERS files:
- https://www.vertex42.com/Files/download2/numbers.php?file=Shopping-Planner.numbers
- https://www.vertex42.com/Files/download2/numbers.php?file=Blank-Checklists.numbers
- The following is an embedded HTML and JavaScript snippet suitable for a Ghost blog or similar platform. It enables drag-and-drop functionality for a .NUMBERS file, treats it as a ZIP archive, and displays the listed properties (e.g., internal file structure) on the screen using the JSZip library.
Drag and drop a .NUMBERS file here
- The following Python class handles .NUMBERS files as ZIP archives. It opens the file, decodes (lists internal structure), reads properties, writes a modified ZIP (e.g., adds a note file), and prints properties to the console. Full decoding of proprietary .iwa files requires additional libraries like 'snappy' and protobuf schemas, which are not implemented here due to the proprietary nature.
import zipfile
import os
class NumbersFileHandler:
def __init__(self, filepath):
self.filepath = filepath
self.properties = {}
def read_properties(self):
if not zipfile.is_zipfile(self.filepath):
raise ValueError("Not a valid ZIP-based .NUMBERS file.")
with zipfile.ZipFile(self.filepath, 'r') as zf:
self.properties['file_extension'] = os.path.splitext(self.filepath)[1]
self.properties['file_size'] = os.path.getsize(self.filepath)
self.properties['internal_files'] = zf.namelist()
self.properties['mime_type'] = 'application/vnd.apple.numbers'
self.properties['developer'] = 'Apple Inc.'
self.properties['category'] = 'Spreadsheet'
self.properties['magic_number'] = '0x504B0304' # ZIP signature
def print_properties(self):
for key, value in self.properties.items():
if key == 'internal_files':
print(f"{key}:")
for file in value:
print(f" - {file}")
else:
print(f"{key}: {value}")
def write_modified(self, output_path, add_note=True):
with zipfile.ZipFile(self.filepath, 'r') as zf_in:
with zipfile.ZipFile(output_path, 'w') as zf_out:
for item in zf_in.infolist():
zf_out.writestr(item, zf_in.read(item.filename))
if add_note:
zf_out.writestr('note.txt', 'Modified by NumbersFileHandler')
# Example usage:
# handler = NumbersFileHandler('example.numbers')
# handler.read_properties()
# handler.print_properties()
# handler.write_modified('modified.numbers')
- The following Java class handles .NUMBERS files similarly, using java.util.zip for ZIP operations. It opens, decodes (lists), reads, writes a modified version, and prints properties.
import java.io.*;
import java.util.*;
import java.util.zip.*;
public class NumbersFileHandler {
private String filepath;
private Map<String, Object> properties = new HashMap<>();
public NumbersFileHandler(String filepath) {
this.filepath = filepath;
}
public void readProperties() throws IOException {
File file = new File(filepath);
if (!isZipFile(file)) {
throw new IllegalArgumentException("Not a valid ZIP-based .NUMBERS file.");
}
try (ZipFile zf = new ZipFile(filepath)) {
properties.put("file_extension", filepath.substring(filepath.lastIndexOf('.')));
properties.put("file_size", file.length());
List<String> internalFiles = new ArrayList<>();
Enumeration<? extends ZipEntry> entries = zf.entries();
while (entries.hasMoreElements()) {
internalFiles.add(entries.nextElement().getName());
}
properties.put("internal_files", internalFiles);
properties.put("mime_type", "application/vnd.apple.numbers");
properties.put("developer", "Apple Inc.");
properties.put("category", "Spreadsheet");
properties.put("magic_number", "0x504B0304"); // ZIP signature
}
}
private boolean isZipFile(File file) throws IOException {
try (FileInputStream fis = new FileInputStream(file)) {
byte[] signature = new byte[4];
fis.read(signature);
return signature[0] == 0x50 && signature[1] == 0x4B && signature[2] == 0x03 && signature[3] == 0x04;
}
}
public void printProperties() {
for (Map.Entry<String, Object> entry : properties.entrySet()) {
if (entry.getKey().equals("internal_files")) {
System.out.println(entry.getKey() + ":");
for (String file : (List<String>) entry.getValue()) {
System.out.println(" - " + file);
}
} else {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
public void writeModified(String outputPath, boolean addNote) throws IOException {
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();
zos.putNextEntry(new ZipEntry(entry.getName()));
InputStream is = zfIn.getInputStream(entry);
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) > 0) {
zos.write(buffer, 0, len);
}
is.close();
zos.closeEntry();
}
if (addNote) {
zos.putNextEntry(new ZipEntry("note.txt"));
zos.write("Modified by NumbersFileHandler".getBytes());
zos.closeEntry();
}
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// NumbersFileHandler handler = new NumbersFileHandler("example.numbers");
// handler.readProperties();
// handler.printProperties();
// handler.writeModified("modified.numbers", true);
// }
}
- The following JavaScript class handles .NUMBERS files in a browser or Node.js environment using JSZip. It opens (loads), decodes (lists), reads, "writes" (generates a modified ZIP blob), and prints properties to console.
const JSZip = require('jszip'); // For Node.js; in browser, use script tag for JSZip
class NumbersFileHandler {
constructor() {
this.properties = {};
}
async readProperties(file) {
if (!file.name.endsWith('.numbers')) {
throw new Error('Not a .NUMBERS file.');
}
const zip = await JSZip.loadAsync(file);
this.properties.file_extension = '.numbers';
this.properties.file_size = file.size;
this.properties.internal_files = Object.keys(zip.files);
this.properties.mime_type = 'application/vnd.apple.numbers';
this.properties.developer = 'Apple Inc.';
this.properties.category = 'Spreadsheet';
this.properties.magic_number = '0x504B0304'; // ZIP signature
}
printProperties() {
for (const [key, value] of Object.entries(this.properties)) {
if (key === 'internal_files') {
console.log(`${key}:`);
value.forEach(file => console.log(` - ${file}`));
} else {
console.log(`${key}: ${value}`);
}
}
}
async writeModified(addNote = true) {
const zip = new JSZip();
// Assume original zip loaded; for simplicity, create minimal
if (addNote) {
zip.file('note.txt', 'Modified by NumbersFileHandler');
}
return await zip.generateAsync({type: 'blob'});
}
}
// Example usage (browser):
// const handler = new NumbersFileHandler();
// const input = document.createElement('input');
// input.type = 'file';
// input.onchange = async (e) => {
// await handler.readProperties(e.target.files[0]);
// handler.printProperties();
// const modifiedBlob = await handler.writeModified();
// // Use blob as needed
// };
// document.body.appendChild(input);
- C does not support classes natively, but the following implementation uses structures and functions to achieve similar functionality. It uses zlib for ZIP handling (assume linked with -lz). It opens, decodes (lists files), reads properties, writes a modified ZIP, and prints to console.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zip.h> // Requires libzip library
struct NumbersFileHandler {
const char* filepath;
char* extension;
long file_size;
char** internal_files;
int file_count;
const char* mime_type;
const char* developer;
const char* category;
const char* magic_number;
};
struct NumbersFileHandler* create_handler(const char* filepath) {
struct NumbersFileHandler* handler = malloc(sizeof(struct NumbersFileHandler));
handler->filepath = filepath;
handler->extension = ".numbers";
handler->mime_type = "application/vnd.apple.numbers";
handler->developer = "Apple Inc.";
handler->category = "Spreadsheet";
handler->magic_number = "0x504B0304";
handler->internal_files = NULL;
handler->file_count = 0;
return handler;
}
int read_properties(struct NumbersFileHandler* handler) {
FILE* file = fopen(handler->filepath, "rb");
if (!file) return -1;
fseek(file, 0, SEEK_END);
handler->file_size = ftell(file);
fclose(file);
zip_t* za = zip_open(handler->filepath, ZIP_RDONLY, NULL);
if (!za) return -1;
int num_entries = zip_get_num_entries(za, 0);
handler->internal_files = malloc(num_entries * sizeof(char*));
for (int i = 0; i < num_entries; i++) {
const char* name = zip_get_name(za, i, 0);
handler->internal_files[i] = strdup(name);
}
handler->file_count = num_entries;
zip_close(za);
return 0;
}
void print_properties(struct NumbersFileHandler* handler) {
printf("file_extension: %s\n", handler->extension);
printf("file_size: %ld\n", handler->file_size);
printf("mime_type: %s\n", handler->mime_type);
printf("developer: %s\n", handler->developer);
printf("category: %s\n", handler->category);
printf("magic_number: %s\n", handler->magic_number);
printf("internal_files:\n");
for (int i = 0; i < handler->file_count; i++) {
printf(" - %s\n", handler->internal_files[i]);
}
}
int write_modified(struct NumbersFileHandler* handler, const char* output_path, int add_note) {
zip_t* za_in = zip_open(handler->filepath, ZIP_RDONLY, NULL);
if (!za_in) return -1;
zip_t* za_out = zip_open(output_path, ZIP_CREATE | ZIP_TRUNCATE, NULL);
if (!za_out) {
zip_close(za_in);
return -1;
}
int num_entries = zip_get_num_entries(za_in, 0);
for (int i = 0; i < num_entries; i++) {
zip_source_t* src = zip_file_add(za_out, zip_get_name(za_in, i, 0), zip_file_get_source(za_in, i), ZIP_FL_ENC_UTF_8);
if (!src) {
zip_close(za_in);
zip_close(za_out);
return -1;
}
}
if (add_note) {
zip_source_t* src = zip_source_buffer(za_out, "Modified by NumbersFileHandler", 30, 0);
zip_file_add(za_out, "note.txt", src, ZIP_FL_ENC_UTF_8);
}
zip_close(za_in);
zip_close(za_out);
return 0;
}
void destroy_handler(struct NumbersFileHandler* handler) {
for (int i = 0; i < handler->file_count; i++) {
free(handler->internal_files[i]);
}
free(handler->internal_files);
free(handler);
}
// Example usage:
// int main() {
// struct NumbersFileHandler* handler = create_handler("example.numbers");
// if (read_properties(handler) == 0) {
// print_properties(handler);
// write_modified(handler, "modified.numbers", 1);
// }
// destroy_handler(handler);
// return 0;
// }