Task 810: .WKS File Format
Task 810: .WKS File Format
1. List of Properties of the .WKS File Format Intrinsic to Its File Structure
The .WKS file format, used by Lotus 1-2-3 Release 1A, is a binary, record-based structure with little-endian byte order. The intrinsic properties include the following key elements, derived from its specifications:
- File Signature: The first 6 bytes are 0x00 0x00 0x02 0x00 0x04 0x06, representing the BOF record with version indicator.
- Version Indicator: Encoded in the BOF record data (0x0406 for Lotus 1-2-3 Release 1A).
- Byte Order: Little-endian for all multi-byte values.
- Record Structure: Each record consists of a 2-byte opcode, a 2-byte length (excluding header), followed by the specified length of data bytes.
- Record Types (Opcodes): The format supports a set of opcodes, including but not limited to:
- 0x0000: BOF (Beginning of File)
- 0x0001: EOF (End of File)
- 0x0002: CALCMODE (Calculation mode)
- 0x0003: CALCORDER (Calculation order)
- 0x0004: SPLIT (Window split type)
- 0x0005: SYNC (Window sync)
- 0x0006: RANGE (Worksheet dimensions: min/max columns and rows)
- 0x0007: WINDOW1 (Window settings 1)
- 0x0008: COLW1 (Column widths 1)
- 0x0009: WINTWO (Window settings 2)
- 0x000A: COLW2 (Column widths 2)
- 0x000B: NAME (Named range)
- 0x000C: BLANK (Blank cell)
- 0x000D: INTEGER (Integer cell value)
- 0x000E: NUMBER (Floating-point cell value)
- 0x000F: LABEL (Text label cell)
- 0x0010: FORMULA (Formula cell)
- Additional opcodes for headers, footers, margins, protection, and embedded graphics.
- Embedded Graphics Compression: LZW algorithm with adaptive code size (9-12 bits), maximum table size of 4096 entries, Clear code at 4094, optional horizontal predictor for differencing.
- Cell Data Encoding: Cells include column (1 byte), row (2 bytes), format (1 byte), and value (variable based on type).
- Endianness Independence for Codes: Raw values in LZW streams, supporting both II and MM byte orders.
- Planar Configuration Support: Single stream for multi-sample pixels (e.g., RGB), regardless of configuration.
These properties define the format's structure and behavior, enabling parsing and generation of .WKS files.
2. Two Direct Download Links for .WKS Files
- http://sembiance.com/fileFormatSamples/document/lotus123/GRAPH.WKS
- http://sembiance.com/fileFormatSamples/document/lotus123/TILE.WKS
3. HTML with Embedded JavaScript for Drag-and-Drop .WKS File Dump
The following is an HTML page with embedded JavaScript that can be embedded in a Ghost blog. It allows users to drag and drop a .WKS file, parses the file, and dumps the properties (including version, dimensions, and record details) to the screen.
4. Python Class for .WKS File Handling
The following Python class can open, decode, read, write, and print the properties of a .WKS file to the console.
import struct
import sys
class WKSFile:
def __init__(self, filename=None):
self.filename = filename
self.records = []
self.version = None
self.dimensions = None
if filename:
self.read(filename)
def read(self, filename):
self.filename = filename
with open(filename, 'rb') as f:
data = f.read()
offset = 0
while offset < len(data):
if offset + 4 > len(data): break
opcode, length = struct.unpack('<HH', data[offset:offset+4])
offset += 4
record_data = data[offset:offset+length]
self.records.append((opcode, length, record_data))
offset += length
if opcode == 0x0000: # BOF
(ver,) = struct.unpack('<H', record_data)
self.version = hex(ver)
elif opcode == 0x0006: # RANGE
min_col, min_row, max_col, max_row = struct.unpack('<HHHH', record_data)
self.dimensions = (min_col, min_row, max_col, max_row)
if opcode == 0x0001: break # EOF
def print_properties(self):
print("Properties of the .WKS file:")
print(f"File Signature: 00 00 02 00 04 06")
print(f"Version: {self.version} (Lotus 1-2-3 Release 1A)")
print("Byte Order: Little-endian")
print("Record Structure: 2-byte opcode, 2-byte length, data")
if self.dimensions:
print(f"Dimensions: Columns {self.dimensions[0]}-{self.dimensions[2]}, Rows {self.dimensions[1]}-{self.dimensions[3]}")
print("Records:")
for opcode, length, data in self.records:
print(f"Opcode: 0x{opcode:04X}, Length: {length}")
if opcode == 0x000F: # LABEL
col = data[0]
row, = struct.unpack('<H', data[1:3])
fmt = data[3]
label = data[4:].decode('ascii', errors='ignore').rstrip('\x00')
print(f" - Label at Col {col}, Row {row}: {label}")
elif opcode == 0x000E: # NUMBER
col = data[0]
row, = struct.unpack('<H', data[1:3])
fmt = data[3]
value, = struct.unpack('<d', data[4:12])
print(f" - Number at Col {col}, Row {row}: {value}")
# Add more decoders as needed
print("Embedded Graphics Compression: LZW (adaptive 9-12 bits, max table 4096, Clear code 4094)")
def write(self, filename):
with open(filename, 'wb') as f:
# Write BOF
f.write(struct.pack('<HH', 0x0000, 0x0002))
f.write(struct.pack('<H', 0x0406))
# Write RANGE (example: 0-255 cols, 0-8191 rows)
f.write(struct.pack('<HH', 0x0006, 0x0008))
f.write(struct.pack('<HHHH', 0, 0, 255, 8191))
# Write a sample label
f.write(struct.pack('<HH', 0x000F, 10)) # Length example
f.write(b'\x00\x00\x00\x27Test\0') # Col 0, Row 0, format ', label 'Test
# Write EOF
f.write(struct.pack('<HH', 0x0001, 0x0000))
if __name__ == '__main__':
if len(sys.argv) > 1:
wks = WKSFile(sys.argv[1])
wks.print_properties()
5. Java Class for .WKS File Handling
The following Java class can open, decode, read, write, and print the properties of a .WKS file to the console.
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class WKSFile {
private String filename;
private String version;
private int[] dimensions;
// List of records as byte arrays for simplicity
private byte[][] records;
public WKSFile(String filename) throws IOException {
this.filename = filename;
read(filename);
}
public WKSFile() {}
private void read(String filename) throws IOException {
try (DataInputStream dis = new DataInputStream(new FileInputStream(filename))) {
byte[] data = dis.readAllBytes();
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
int offset = 0;
while (offset < data.length) {
if (offset + 4 > data.length) break;
short opcode = bb.getShort(offset);
short length = bb.getShort(offset + 2);
offset += 4;
byte[] recordData = new byte[length];
bb.position(offset);
bb.get(recordData);
offset += length;
// Store record
// For demonstration, process specific
if (opcode == 0x0000) { // BOF
ByteBuffer rbb = ByteBuffer.wrap(recordData).order(ByteOrder.LITTLE_ENDIAN);
version = Integer.toHexString(rbb.getShort(0) & 0xFFFF).toUpperCase();
} else if (opcode == 0x0006) { // RANGE
ByteBuffer rbb = ByteBuffer.wrap(recordData).order(ByteOrder.LITTLE_ENDIAN);
dimensions = new int[] {rbb.getShort(0) & 0xFFFF, rbb.getShort(2) & 0xFFFF, rbb.getShort(4) & 0xFFFF, rbb.getShort(6) & 0xFFFF};
}
if (opcode == 0x0001) break; // EOF
}
}
}
public void printProperties() {
System.out.println("Properties of the .WKS file:");
System.out.println("File Signature: 00 00 02 00 04 06");
System.out.println("Version: " + version + " (Lotus 1-2-3 Release 1A)");
System.out.println("Byte Order: Little-endian");
System.out.println("Record Structure: 2-byte opcode, 2-byte length, data");
if (dimensions != null) {
System.out.println("Dimensions: Columns " + dimensions[0] + "-" + dimensions[2] + ", Rows " + dimensions[1] + "-" + dimensions[3]);
}
System.out.println("Embedded Graphics Compression: LZW (adaptive 9-12 bits, max table 4096, Clear code 4094)");
// Add more as needed
}
public void write(String filename) throws IOException {
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(filename))) {
// BOF
dos.writeShort(0x0000);
dos.writeShort(0x0002);
dos.writeShort(0x0406);
// RANGE example
dos.writeShort(0x0006);
dos.writeShort(0x0008);
dos.writeShort(0);
dos.writeShort(0);
dos.writeShort(255);
dos.writeShort(8191);
// Sample label
dos.writeShort(0x000F);
dos.writeShort(10);
dos.writeByte(0); // col
dos.writeShort(0); // row
dos.writeByte(39); // format '
dos.writeBytes("Test\0");
// EOF
dos.writeShort(0x0001);
dos.writeShort(0x0000);
}
}
public static void main(String[] args) throws IOException {
if (args.length > 0) {
WKSFile wks = new WKSFile(args[0]);
wks.printProperties();
}
}
}
6. JavaScript Class for .WKS File Handling
The following JavaScript class can open (via ArrayBuffer), decode, read, write (generate Blob), and print the properties of a .WKS file to the console.
class WKSFile {
constructor(buffer = null) {
this.version = null;
this.dimensions = null;
this.records = [];
if (buffer) this.read(buffer);
}
read(buffer) {
const view = new DataView(buffer);
let offset = 0;
while (offset < buffer.byteLength) {
if (offset + 4 > buffer.byteLength) break;
const opcode = view.getUint16(offset, true);
const length = view.getUint16(offset + 2, true);
offset += 4;
const recordData = new Uint8Array(buffer, offset, length);
this.records.push({opcode, length, data: recordData});
offset += length;
if (opcode === 0x0000) { // BOF
this.version = view.getUint16(offset - length, true).toString(16).toUpperCase();
} else if (opcode === 0x0006) { // RANGE
this.dimensions = [
view.getUint16(offset - length, true),
view.getUint16(offset - length + 2, true),
view.getUint16(offset - length + 4, true),
view.getUint16(offset - length + 6, true)
];
}
if (opcode === 0x0001) break; // EOF
}
}
printProperties() {
console.log('Properties of the .WKS file:');
console.log('File Signature: 00 00 02 00 04 06');
console.log(`Version: ${this.version} (Lotus 1-2-3 Release 1A)`);
console.log('Byte Order: Little-endian');
console.log('Record Structure: 2-byte opcode, 2-byte length, data');
if (this.dimensions) {
console.log(`Dimensions: Columns ${this.dimensions[0]}-${this.dimensions[2]}, Rows ${this.dimensions[1]}-${this.dimensions[3]}`);
}
console.log('Records:');
this.records.forEach(r => {
console.log(`Opcode: 0x${r.opcode.toString(16).padStart(4, '0').toUpperCase()}, Length: ${r.length}`);
// Decode more as needed
});
console.log('Embedded Graphics Compression: LZW (adaptive 9-12 bits, max table 4096, Clear code 4094)');
}
write() {
const buffer = new ArrayBuffer(100); // Approximate size
const view = new DataView(buffer);
let offset = 0;
// BOF
view.setUint16(offset, 0x0000, true); offset += 2;
view.setUint16(offset, 0x0002, true); offset += 2;
view.setUint16(offset, 0x0406, true); offset += 2;
// RANGE
view.setUint16(offset, 0x0006, true); offset += 2;
view.setUint16(offset, 0x0008, true); offset += 2;
view.setUint16(offset, 0, true); offset += 2;
view.setUint16(offset, 0, true); offset += 2;
view.setUint16(offset, 255, true); offset += 2;
view.setUint16(offset, 8191, true); offset += 2;
// Sample label
view.setUint16(offset, 0x000F, true); offset += 2;
view.setUint16(offset, 10, true); offset += 2;
view.setUint8(offset, 0); offset += 1;
view.setUint16(offset, 0, true); offset += 2;
view.setUint8(offset, 39); offset += 1;
const label = new TextEncoder().encode('Test\0');
for (let i = 0; i < label.length; i++) {
view.setUint8(offset + i, label[i]);
}
offset += label.length;
// EOF
view.setUint16(offset, 0x0001, true); offset += 2;
view.setUint16(offset, 0x0000, true); offset += 2;
return new Blob([buffer.slice(0, offset)]);
}
}
// Example usage
// const wks = new WKSFile(arrayBuffer);
// wks.printProperties();
// const blob = wks.write();
// // Save blob as file
7. C++ Class for .WKS File Handling
The following C++ class can open, decode, read, write, and print the properties of a .WKS file to the console.
#include <fstream>
#include <iostream>
#include <vector>
#include <cstring>
#include <cstdint>
#include <iomanip>
struct Record {
uint16_t opcode;
uint16_t length;
std::vector<uint8_t> data;
};
class WKSFile {
private:
std::string filename;
std::string version;
int dimensions[4];
std::vector<Record> records;
public:
WKSFile(const std::string& fn = "") : filename(fn) {
memset(dimensions, 0, sizeof(dimensions));
if (!fn.empty()) read(fn);
}
void read(const std::string& fn) {
filename = fn;
std::ifstream file(fn, std::ios::binary);
if (!file) return;
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0);
std::vector<uint8_t> data(size);
file.read(reinterpret_cast<char*>(data.data()), size);
size_t offset = 0;
while (offset < size) {
if (offset + 4 > size) break;
uint16_t opcode, length;
memcpy(&opcode, data.data() + offset, 2);
memcpy(&length, data.data() + offset + 2, 2);
offset += 4;
std::vector<uint8_t> rec_data(data.begin() + offset, data.begin() + offset + length);
records.push_back({opcode, length, rec_data});
offset += length;
if (opcode == 0x0000) { // BOF
uint16_t ver;
memcpy(&ver, rec_data.data(), 2);
version = std::to_string(ver);
} else if (opcode == 0x0006) { // RANGE
memcpy(dimensions, rec_data.data(), sizeof(dimensions));
}
if (opcode == 0x0001) break; // EOF
}
}
void printProperties() const {
std::cout << "Properties of the .WKS file:" << std::endl;
std::cout << "File Signature: 00 00 02 00 04 06" << std::endl;
std::cout << "Version: " << version << " (Lotus 1-2-3 Release 1A)" << std::endl;
std::cout << "Byte Order: Little-endian" << std::endl;
std::cout << "Record Structure: 2-byte opcode, 2-byte length, data" << std::endl;
if (dimensions[0] || dimensions[1] || dimensions[2] || dimensions[3]) {
std::cout << "Dimensions: Columns " << dimensions[0] << "-" << dimensions[2] << ", Rows " << dimensions[1] << "-" << dimensions[3] << std::endl;
}
std::cout << "Records:" << std::endl;
for (const auto& r : records) {
std::cout << "Opcode: 0x" << std::setfill('0') << std::setw(4) << std::hex << std::uppercase << r.opcode << ", Length: " << std::dec << r.length << std::endl;
// Decode more as needed
}
std::cout << "Embedded Graphics Compression: LZW (adaptive 9-12 bits, max table 4096, Clear code 4094)" << std::endl;
}
void write(const std::string& fn) {
std::ofstream file(fn, std::ios::binary);
if (!file) return;
// BOF
uint16_t bof[] = {0x0000, 0x0002, 0x0406};
file.write(reinterpret_cast<char*>(bof), sizeof(bof));
// RANGE
uint16_t range_hdr[] = {0x0006, 0x0008};
file.write(reinterpret_cast<char*>(range_hdr), sizeof(range_hdr));
uint16_t range_data[] = {0, 0, 255, 8191};
file.write(reinterpret_cast<char*>(range_data), sizeof(range_data));
// Sample label
uint16_t label_hdr[] = {0x000F, 10};
file.write(reinterpret_cast<char*>(label_hdr), sizeof(label_hdr));
uint8_t label_data[] = {0, 0, 0, 39, 'T', 'e', 's', 't', 0}; // Col, row low, row high, format, string
file.write(reinterpret_cast<char*>(label_data), sizeof(label_data));
// EOF
uint16_t eof[] = {0x0001, 0x0000};
file.write(reinterpret_cast<char*>(eof), sizeof(eof));
}
};
int main(int argc, char** argv) {
if (argc > 1) {
WKSFile wks(argv[1]);
wks.printProperties();
}
return 0;
}