Task 809: .WK3 File Format

Task 809: .WK3 File Format

File Format Specifications for .WK3

The .WK3 file format is a binary spreadsheet format used by Lotus 1-2-3 version 3. It consists of a sequence of variable-length records, each starting with a 4-byte header: 2 bytes for the opcode (record type, little-endian), 2 bytes for the data length (little-endian), followed by the data. The file begins with a BOF (Beginning of File) record and ends with an EOF (End of File) record. The magic number (file signature) for .WK3 files is typically 00 00 1A 00 00 10 04 00 00 00 00 00 (hex) at offset 0, representing the BOF record with version-specific data.

List of all the properties of this file format intrinsic to its file system:

  • Beginning of File (BOF): Indicates the start of the file and includes version information.
  • End of File (EOF): Marks the end of the file.
  • Calculation Mode (CALCMODE): Defines if calculations are automatic or manual.
  • Calculation Order (CALCORDER): Specifies the order of recalculations (e.g., natural, row-major).
  • Split Window Type (SPLIT): Type of window split (none, vertical, horizontal).
  • Split Window Sync (SYNC): Whether split windows are synchronized.
  • Active Worksheet Range (RANGE): The currently active cell range in the worksheet.
  • Window 1 Record (WINDOW1): Settings for the first window, including position, size, and display options.
  • Column Width for Window 1 (COLW1): Column widths in the first window.
  • Window 2 Record (WINTWO): Settings for the second window.
  • Column Width for Window 2 (COLW2): Column widths in the second window.
  • Named Range (NAME): Defined named ranges in the spreadsheet.
  • Global Protection (PROTEC): Whether the worksheet is protected.
  • Print Footer (FOOTER): Footer text for printing.
  • Print Header (HEADER): Header text for printing.
  • Print Setup (SETUP): Print configuration, including page size and orientation.
  • Print Margins (MARGINS): Margin settings for printing.
  • Print Range (PRANGE): Range to print.
  • Sort Range (SRANGE): Range for sorting operations.
  • Fill Range (FRANGE): Range for fill operations.
  • Primary Sort Key Range (KRANGE1): Primary key for sorting.
  • Secondary Sort Key Range (KRANGE2): Secondary key for sorting.
  • Query Range (ORANGE): Range for data queries.
  • Data Table Range (TABLE): Range for data tables.
  • Distribution Range (HRANGE): Range for frequency distributions.
  • Cell Data Properties: Includes types like Blank (BLANK), Integer (INTEGER), Floating Point Number (NUMBER), Label (LABEL), and Formula (FORMULA), which define cell contents.

Two direct download links for .WK3 files:

Ghost blog embedded HTML JavaScript for drag-and-drop .WK3 file dump:

WK3 File Properties Dumper
Drag and drop a .WK3 file here
  1. Python class for .WK3 handling:
import struct
import binascii

class WK3Parser:
    def __init__(self, filename):
        self.filename = filename
        self.properties = []

    def get_property_name(self, opcode):
        prop_map = {
            0: 'Beginning of File (BOF)',
            1: 'End of File (EOF)',
            2: 'Calculation Mode (CALCMODE)',
            3: 'Calculation Order (CALCORDER)',
            4: 'Split Window Type (SPLIT)',
            5: 'Split Window Sync (SYNC)',
            6: 'Active Worksheet Range (RANGE)',
            7: 'Window 1 Record (WINDOW1)',
            8: 'Column Width for Window 1 (COLW1)',
            9: 'Window 2 Record (WINTWO)',
            10: 'Column Width for Window 2 (COLW2)',
            11: 'Named Range (NAME)',
            12: 'Blank Cell (BLANK)',
            13: 'Integer Cell (INTEGER)',
            14: 'Floating Point Number (NUMBER)',
            15: 'Label Cell (LABEL)',
            16: 'Formula Cell (FORMULA)',
            24: 'Data Table Range (TABLE)',
            25: 'Query Range (ORANGE)',
            26: 'Print Range (PRANGE)',
            27: 'Sort Range (SRANGE)',
            28: 'Fill Range (FRANGE)',
            29: 'Primary Sort Key Range (KRANGE1)',
            32: 'Distribution Range (HRANGE)',
            35: 'Secondary Sort Key Range (KRANGE2)',
            36: 'Global Protection (PROTEC)',
            37: 'Print Footer (FOOTER)',
            38: 'Print Header (HEADER)',
            39: 'Print Setup (SETUP)',
            40: 'Print Margins (MARGINS)'
        }
        return prop_map.get(opcode, f'Unknown Opcode {opcode}')

    def read(self):
        with open(self.filename, 'rb') as f:
            data = f.read()
            offset = 0
            while offset < len(data):
                opcode, length = struct.unpack_from('<HH', data, offset)
                offset += 4
                record_data = data[offset:offset + length]
                prop_name = self.get_property_name(opcode)
                print(f'{prop_name} (Opcode: 0x{opcode:04x}, Length: {length}) - Data: {binascii.hexlify(record_data).decode()}')
                self.properties.append((prop_name, opcode, length, record_data))
                offset += length
                if opcode == 1:  # EOF
                    break

    def write(self, output_filename):
        # Write a simple minimal .WK3 file with BOF and EOF
        with open(output_filename, 'wb') as f:
            # BOF record (example minimal for WK3)
            bof = struct.pack('<HH', 0, 26) + b'\x00\x10\x04\x00\x00\x00\x00\x00' + b'\x00' * 18  # Padded example data
            f.write(bof)
            # EOF record
            eof = struct.pack('<HH', 1, 0)
            f.write(eof)
        print(f'Wrote minimal .WK3 file to {output_filename}')

# Example usage:
# parser = WK3Parser('example.wk3')
# parser.read()
# parser.write('output.wk3')
  1. Java class for .WK3 handling:
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class WK3Parser {
    private String filename;
    private String[] properties; // For simplicity, store as strings

    public WK3Parser(String filename) {
        this.filename = filename;
    }

    private String getPropertyName(int opcode) {
        switch (opcode) {
            case 0: return "Beginning of File (BOF)";
            case 1: return "End of File (EOF)";
            case 2: return "Calculation Mode (CALCMODE)";
            case 3: return "Calculation Order (CALCORDER)";
            case 4: return "Split Window Type (SPLIT)";
            case 5: return "Split Window Sync (SYNC)";
            case 6: return "Active Worksheet Range (RANGE)";
            case 7: return "Window 1 Record (WINDOW1)";
            case 8: return "Column Width for Window 1 (COLW1)";
            case 9: return "Window 2 Record (WINTWO)";
            case 10: return "Column Width for Window 2 (COLW2)";
            case 11: return "Named Range (NAME)";
            case 12: return "Blank Cell (BLANK)";
            case 13: return "Integer Cell (INTEGER)";
            case 14: return "Floating Point Number (NUMBER)";
            case 15: return "Label Cell (LABEL)";
            case 16: return "Formula Cell (FORMULA)";
            case 24: return "Data Table Range (TABLE)";
            case 25: return "Query Range (ORANGE)";
            case 26: return "Print Range (PRANGE)";
            case 27: return "Sort Range (SRANGE)";
            case 28: return "Fill Range (FRANGE)";
            case 29: return "Primary Sort Key Range (KRANGE1)";
            case 32: return "Distribution Range (HRANGE)";
            case 35: return "Secondary Sort Key Range (KRANGE2)";
            case 36: return "Global Protection (PROTEC)";
            case 37: return "Print Footer (FOOTER)";
            case 38: return "Print Header (HEADER)";
            case 39: return "Print Setup (SETUP)";
            case 40: return "Print Margins (MARGINS)";
            default: return "Unknown Opcode " + opcode;
        }
    }

    public void read() throws IOException {
        try (FileInputStream fis = new FileInputStream(filename);
             BufferedInputStream bis = new BufferedInputStream(fis)) {
            byte[] data = bis.readAllBytes();
            ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
            int offset = 0;
            while (offset < data.length) {
                int opcode = bb.getShort(offset) & 0xFFFF;
                offset += 2;
                int length = bb.getShort(offset) & 0xFFFF;
                offset += 2;
                byte[] recordData = new byte[length];
                bb.position(offset);
                bb.get(recordData);
                String propName = getPropertyName(opcode);
                String hexData = javax.xml.bind.DatatypeConverter.printHexBinary(recordData);
                System.out.println(propName + " (Opcode: 0x" + Integer.toHexString(opcode).toUpperCase() + ", Length: " + length + ") - Data: " + hexData);
                offset += length;
                if (opcode == 1) break; // EOF
            }
        }
    }

    public void write(String outputFilename) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
            // BOF record (example minimal for WK3)
            ByteBuffer bof = ByteBuffer.allocate(30).order(ByteOrder.LITTLE_ENDIAN);
            bof.putShort((short) 0); // Opcode
            bof.putShort((short) 26); // Length
            bof.put(new byte[] {0x00, 0x10, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00});
            bof.put(new byte[18]); // Padding
            fos.write(bof.array());
            // EOF record
            ByteBuffer eof = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
            eof.putShort((short) 1); // Opcode
            eof.putShort((short) 0); // Length
            fos.write(eof.array());
        }
        System.out.println("Wrote minimal .WK3 file to " + outputFilename);
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     WK3Parser parser = new WK3Parser("example.wk3");
    //     parser.read();
    //     parser.write("output.wk3");
    // }
}
  1. JavaScript class for .WK3 handling (Node.js compatible):
const fs = require('fs');

class WK3Parser {
    constructor(filename) {
        this.filename = filename;
    }

    getPropertyName(opcode) {
        const propMap = {
            0: 'Beginning of File (BOF)',
            1: 'End of File (EOF)',
            2: 'Calculation Mode (CALCMODE)',
            3: 'Calculation Order (CALCORDER)',
            4: 'Split Window Type (SPLIT)',
            5: 'Split Window Sync (SYNC)',
            6: 'Active Worksheet Range (RANGE)',
            7: 'Window 1 Record (WINDOW1)',
            8: 'Column Width for Window 1 (COLW1)',
            9: 'Window 2 Record (WINTWO)',
            10: 'Column Width for Window 2 (COLW2)',
            11: 'Named Range (NAME)',
            12: 'Blank Cell (BLANK)',
            13: 'Integer Cell (INTEGER)',
            14: 'Floating Point Number (NUMBER)',
            15: 'Label Cell (LABEL)',
            16: 'Formula Cell (FORMULA)',
            24: 'Data Table Range (TABLE)',
            25: 'Query Range (ORANGE)',
            26: 'Print Range (PRANGE)',
            27: 'Sort Range (SRANGE)',
            28: 'Fill Range (FRANGE)',
            29: 'Primary Sort Key Range (KRANGE1)',
            32: 'Distribution Range (HRANGE)',
            35: 'Secondary Sort Key Range (KRANGE2)',
            36: 'Global Protection (PROTEC)',
            37: 'Print Footer (FOOTER)',
            38: 'Print Header (HEADER)',
            39: 'Print Setup (SETUP)',
            40: 'Print Margins (MARGINS)'
        };
        return propMap[opcode] || `Unknown Opcode ${opcode}`;
    }

    read() {
        const data = fs.readFileSync(this.filename);
        const view = new DataView(data.buffer);
        let offset = 0;
        while (offset < data.length) {
            const opcode = view.getUint16(offset, true);
            offset += 2;
            const length = view.getUint16(offset, true);
            offset += 2;
            const recordData = data.slice(offset, offset + length);
            const propName = this.getPropertyName(opcode);
            const hexData = Array.from(recordData).map(b => b.toString(16).padStart(2, '0')).join('');
            console.log(`${propName} (Opcode: 0x${opcode.toString(16).padStart(4, '0')}, Length: ${length}) - Data: ${hexData}`);
            offset += length;
            if (opcode === 1) break; // EOF
        }
    }

    write(outputFilename) {
        const buffer = Buffer.alloc(34);
        const view = new DataView(buffer.buffer);
        // BOF record
        view.setUint16(0, 0, true); // Opcode
        view.setUint16(2, 26, true); // Length
        buffer[4] = 0x00; buffer[5] = 0x10; buffer[6] = 0x04; buffer[7] = 0x00;
        // Padding zeros for example
        // EOF record
        view.setUint16(30, 1, true); // Opcode
        view.setUint16(32, 0, true); // Length
        fs.writeFileSync(outputFilename, buffer);
        console.log(`Wrote minimal .WK3 file to ${outputFilename}`);
    }
}

// Example usage:
// const parser = new WK3Parser('example.wk3');
// parser.read();
// parser.write('output.wk3');
  1. C++ class for .WK3 handling:
#include <iostream>
#include <fstream>
#include <vector>
#include <iomanip>
#include <string>

class WK3Parser {
private:
    std::string filename;

    std::string getPropertyName(uint16_t opcode) {
        switch (opcode) {
            case 0: return "Beginning of File (BOF)";
            case 1: return "End of File (EOF)";
            case 2: return "Calculation Mode (CALCMODE)";
            case 3: return "Calculation Order (CALCORDER)";
            case 4: return "Split Window Type (SPLIT)";
            case 5: return "Split Window Sync (SYNC)";
            case 6: return "Active Worksheet Range (RANGE)";
            case 7: return "Window 1 Record (WINDOW1)";
            case 8: return "Column Width for Window 1 (COLW1)";
            case 9: return "Window 2 Record (WINTWO)";
            case 10: return "Column Width for Window 2 (COLW2)";
            case 11: return "Named Range (NAME)";
            case 12: return "Blank Cell (BLANK)";
            case 13: return "Integer Cell (INTEGER)";
            case 14: return "Floating Point Number (NUMBER)";
            case 15: return "Label Cell (LABEL)";
            case 16: return "Formula Cell (FORMULA)";
            case 24: return "Data Table Range (TABLE)";
            case 25: return "Query Range (ORANGE)";
            case 26: return "Print Range (PRANGE)";
            case 27: return "Sort Range (SRANGE)";
            case 28: return "Fill Range (FRANGE)";
            case 29: return "Primary Sort Key Range (KRANGE1)";
            case 32: return "Distribution Range (HRANGE)";
            case 35: return "Secondary Sort Key Range (KRANGE2)";
            case 36: return "Global Protection (PROTEC)";
            case 37: return "Print Footer (FOOTER)";
            case 38: return "Print Header (HEADER)";
            case 39: return "Print Setup (SETUP)";
            case 40: return "Print Margins (MARGINS)";
            default: return "Unknown Opcode " + std::to_string(opcode);
        }
    }

public:
    WK3Parser(const std::string& fn) : filename(fn) {}

    void read() {
        std::ifstream file(filename, std::ios::binary);
        if (!file) {
            std::cerr << "Cannot open file!" << std::endl;
            return;
        }
        std::vector<char> data((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        size_t offset = 0;
        while (offset < data.size()) {
            uint16_t opcode = *reinterpret_cast<uint16_t*>(&data[offset]);
            offset += 2;
            uint16_t length = *reinterpret_cast<uint16_t*>(&data[offset]);
            offset += 2;
            std::string hexData;
            for (size_t i = 0; i < length; ++i) {
                hexData += std::to_string(static_cast<unsigned char>(data[offset + i]) & 0xFF) + " ";
            }
            std::string propName = getPropertyName(opcode);
            std::cout << propName << " (Opcode: 0x" << std::hex << std::setw(4) << std::setfill('0') << opcode
                      << ", Length: " << std::dec << length << ") - Data: " << hexData << std::endl;
            offset += length;
            if (opcode == 1) break; // EOF
        }
    }

    void write(const std::string& outputFilename) {
        std::ofstream file(outputFilename, std::ios::binary);
        if (!file) {
            std::cerr << "Cannot create file!" << std::endl;
            return;
        }
        // BOF record (example minimal)
        uint16_t bofOpcode = 0;
        uint16_t bofLength = 26;
        file.write(reinterpret_cast<const char*>(&bofOpcode), 2);
        file.write(reinterpret_cast<const char*>(&bofLength), 2);
        char bofData[26] = {0x00, 0x10, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00};
        file.write(bofData, 26);
        // EOF record
        uint16_t eofOpcode = 1;
        uint16_t eofLength = 0;
        file.write(reinterpret_cast<const char*>(&eofOpcode), 2);
        file.write(reinterpret_cast<const char*>(&eofLength), 2);
        std::cout << "Wrote minimal .WK3 file to " << outputFilename << std::endl;
    }
};

// Example usage:
// int main() {
//     WK3Parser parser("example.wk3");
//     parser.read();
//     parser.write("output.wk3");
//     return 0;
// }