Task 770: .VC File Format

Task 770: .VC File Format

File Format Specifications for the .VC File Format

The .VC file format is the native save format used by VisiCalc, the first spreadsheet program for personal computers, developed in 1979. It is a text-based format designed to store spreadsheet data in a manner that allows VisiCalc to reload it by simulating user input commands. The format consists of ASCII text with carriage return (CR, 0x0D) line endings, and it typically ends with a null character (0x00). Each line represents a command or data entry, enabling the reconstruction of the spreadsheet's cells, formats, values, formulas, and other settings upon loading.

  1. List of All Properties of This File Format Intrinsic to Its File System

The .VC file format does not have a rigid binary structure or header; instead, it is a sequence of text commands that mirror user keystrokes in VisiCalc. The intrinsic properties of the format, which define its structure and content within the file system, include the following:

  • Text Encoding: ASCII (7-bit characters).
  • Line Endings: Carriage return (CR, 0x0D) as the line terminator.
  • Command Structure: Each line begins with a '>' followed by a cell address (e.g., A1, B5), a colon ':', and then the entry string. The entry string contains the cell's content, which may include format specifiers, values, labels, or formulas.
  • Cell Content Types:
  • Labels: Prefixed with '"' for left-justified, "'" for right-justified, or '^' for centered (e.g., "TRUE).
  • Values: Numeric values (e.g., 123, 0.23).
  • Formulas: Prefixed with '+' (e.g., +B3+1).
  • Functions: Prefixed with '@' (e.g., @SUM(B2...B5), @PI).
  • Format Specifiers: Embedded commands like /FL (format left-justified), /FR (format right-justified), /F- (default format), or others for cell formatting (e.g., /FL"TRUE sets a left-justified label).
  • End Marker: The file concludes with a null character (0x00) to indicate the end of data.
  • Global Settings: May include commands for column widths (/GC for global column width), recalculation order (/OR for row-major, /OC for column-major), or other spreadsheet configurations, if present in the command sequence.
  • File Size and Integrity: Variable length, dependent on spreadsheet complexity; no checksum or version header is intrinsic.

These properties ensure the file can be processed as a stream of input to VisiCalc, recreating the spreadsheet without recalculation during load (using undocumented commands internally).

  1. Two Direct Download Links for Files of Format .VC

Upon thorough search of available web resources, direct download links for authentic .VC files are not readily available, as the format is obsolete and samples are typically embedded in disk images or archives of VisiCalc software. Historical .VC files are rare in public repositories due to their age. However, sample .VC content can be recreated manually using examples from documentation, such as the one provided in section 3 below.

  1. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .VC File Dump

The following is a self-contained HTML page with embedded JavaScript that allows a user to drag and drop a .VC file. Upon drop, it reads the file as text, parses the commands based on the format described, extracts the cell properties (address, content, type), and dumps them to the screen in a structured list. The page can be saved as an HTML file and opened in a web browser.

.VC File Dumper

Drag and Drop .VC File

Drop .VC file here
  1. Python Class for .VC File Handling

The following Python class can open, decode, read, write, and print the properties of a .VC file to the console. It parses the file into a dictionary of cells (address: {'type': str, 'content': str}), handles the end marker, and supports writing back to a new file.

import os

class VCFileHandler:
    def __init__(self, filepath):
        self.filepath = filepath
        self.cells = {}  # address: {'type': str, 'content': str}

    def read(self):
        if not os.path.exists(self.filepath):
            raise FileNotFoundError(f"File {self.filepath} not found.")
        with open(self.filepath, 'rb') as f:
            content = f.read().decode('ascii', errors='ignore').replace('\r', '\n').rstrip('\x00')
        lines = [line.strip() for line in content.split('\n') if line.strip()]
        for line in lines:
            if line.startswith('>'):
                parts = line.split(':', 1)
                address = parts[0][1:]
                entry = parts[1] if len(parts) > 1 else ''
                entry_type = 'Unknown'
                if entry.startswith(('"', "'", '^')):
                    entry_type = 'Label'
                elif entry.startswith('+'):
                    entry_type = 'Formula'
                elif entry.startswith('@'):
                    entry_type = 'Function'
                elif entry.replace('.', '', 1).isdigit() or entry.lstrip('-').replace('.', '', 1).isdigit():
                    entry_type = 'Value'
                self.cells[address] = {'type': entry_type, 'content': entry}

    def print_properties(self):
        print("Dumped .VC Properties:")
        for address, data in self.cells.items():
            print(f"Cell: {address} | Type: {data['type']} | Content: {data['content']}")

    def write(self, new_filepath):
        with open(new_filepath, 'w', encoding='ascii') as f:
            for address, data in self.cells.items():
                f.write(f">{address}:{data['content']}\n")
            f.write('\x00')

# Example usage:
# handler = VCFileHandler('sample.vc')
# handler.read()
# handler.print_properties()
# handler.write('new.vc')
  1. Java Class for .VC File Handling

The following Java class performs similar operations: opening, decoding, reading, writing, and printing properties to the console. It uses a HashMap for cell data.

import java.io.*;
import java.util.*;

public class VCFileHandler {
    private String filepath;
    private Map<String, Map<String, String>> cells = new HashMap<>();

    public VCFileHandler(String filepath) {
        this.filepath = filepath;
    }

    public void read() throws IOException {
        File file = new File(filepath);
        if (!file.exists()) {
            throw new FileNotFoundException("File " + filepath + " not found.");
        }
        StringBuilder content = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
            String line;
            while ((line = reader.readLine()) != null) {
                content.append(line).append("\n");
            }
        }
        String cleaned = content.toString().replace("\r", "\n").replaceAll("\0$", "");
        String[] lines = cleaned.split("\n");
        for (String line : lines) {
            line = line.trim();
            if (line.startsWith(">")) {
                String[] parts = line.split(":", 2);
                String address = parts[0].substring(1);
                String entry = parts.length > 1 ? parts[1] : "";
                String type = "Unknown";
                if (entry.startsWith("\"") || entry.startsWith("'") || entry.startsWith("^")) {
                    type = "Label";
                } else if (entry.startsWith("+")) {
                    type = "Formula";
                } else if (entry.startsWith("@")) {
                    type = "Function";
                } else {
                    try {
                        Double.parseDouble(entry);
                        type = "Value";
                    } catch (NumberFormatException ignored) {}
                }
                Map<String, String> data = new HashMap<>();
                data.put("type", type);
                data.put("content", entry);
                cells.put(address, data);
            }
        }
    }

    public void printProperties() {
        System.out.println("Dumped .VC Properties:");
        for (Map.Entry<String, Map<String, String>> entry : cells.entrySet()) {
            String address = entry.getKey();
            Map<String, String> data = entry.getValue();
            System.out.println("Cell: " + address + " | Type: " + data.get("type") + " | Content: " + data.get("content"));
        }
    }

    public void write(String newFilepath) throws IOException {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(newFilepath))) {
            for (Map.Entry<String, Map<String, String>> entry : cells.entrySet()) {
                String address = entry.getKey();
                String content = entry.getValue().get("content");
                writer.write(">" + address + ":" + content + "\n");
            }
            writer.write("\0");
        }
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     VCFileHandler handler = new VCFileHandler("sample.vc");
    //     handler.read();
    //     handler.printProperties();
    //     handler.write("new.vc");
    // }
}
  1. JavaScript Class for .VC File Handling

The following JavaScript class can be used in a Node.js environment (requires 'fs' module). It opens, decodes, reads, writes, and prints properties to the console.

const fs = require('fs');

class VCFileHandler {
    constructor(filepath) {
        this.filepath = filepath;
        this.cells = {}; // address: {type: string, content: string}
    }

    read() {
        if (!fs.existsSync(this.filepath)) {
            throw new Error(`File ${this.filepath} not found.`);
        }
        let content = fs.readFileSync(this.filepath, 'ascii').replace(/\r/g, '\n').replace(/\0$/, '');
        const lines = content.split('\n').filter(line => line.trim());
        lines.forEach(line => {
            if (line.startsWith('>')) {
                const parts = line.split(':');
                const address = parts[0].slice(1);
                const entry = parts[1] || '';
                let type = 'Unknown';
                if (['"', "'", '^'].includes(entry[0])) type = 'Label';
                else if (entry.startsWith('+')) type = 'Formula';
                else if (entry.startsWith('@')) type = 'Function';
                else if (!isNaN(parseFloat(entry))) type = 'Value';
                this.cells[address] = { type, content: entry };
            }
        });
    }

    printProperties() {
        console.log('Dumped .VC Properties:');
        for (const [address, data] of Object.entries(this.cells)) {
            console.log(`Cell: ${address} | Type: ${data.type} | Content: ${data.content}`);
        }
    }

    write(newFilepath) {
        let output = '';
        for (const [address, data] of Object.entries(this.cells)) {
            output += `>${address}:${data.content}\n`;
        }
        output += '\x00';
        fs.writeFileSync(newFilepath, output, 'ascii');
    }
}

// Example usage:
// const handler = new VCFileHandler('sample.vc');
// handler.read();
// handler.printProperties();
// handler.write('new.vc');
  1. C++ Class for .VC File Handling

The following C++ class (using standard library) opens, decodes, reads, writes, and prints properties to the console. It uses a map for cell data.

#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include <sstream>
#include <cctype>

class VCFileHandler {
private:
    std::string filepath;
    std::map<std::string, std::pair<std::string, std::string>> cells; // address: {type, content}

public:
    VCFileHandler(const std::string& fp) : filepath(fp) {}

    void read() {
        std::ifstream file(filepath);
        if (!file.is_open()) {
            throw std::runtime_error("File " + filepath + " not found.");
        }
        std::stringstream content;
        std::string line;
        while (std::getline(file, line, '\r')) { // Handle CR endings
            content << line << '\n';
        }
        file.close();
        std::string cleaned = content.str();
        cleaned.erase(std::remove(cleaned.begin(), cleaned.end(), '\0'), cleaned.end()); // Remove null
        std::stringstream ss(cleaned);
        while (std::getline(ss, line, '\n')) {
            line.erase(std::remove(line.begin(), line.end(), '\r'), line.end());
            if (!line.empty() && line[0] == '>') {
                size_t colonPos = line.find(':');
                std::string address = line.substr(1, colonPos - 1);
                std::string entry = (colonPos != std::string::npos) ? line.substr(colonPos + 1) : "";
                std::string type = "Unknown";
                if (!entry.empty()) {
                    char first = entry[0];
                    if (first == '"' || first == '\'' || first == '^') type = "Label";
                    else if (first == '+') type = "Formula";
                    else if (first == '@') type = "Function";
                    else {
                        bool isNum = true;
                        for (char c : entry) {
                            if (!std::isdigit(c) && c != '.' && c != '-') { isNum = false; break; }
                        }
                        if (isNum) type = "Value";
                    }
                }
                cells[address] = {type, entry};
            }
        }
    }

    void printProperties() {
        std::cout << "Dumped .VC Properties:" << std::endl;
        for (const auto& pair : cells) {
            std::string address = pair.first;
            auto data = pair.second;
            std::cout << "Cell: " << address << " | Type: " << data.first << " | Content: " << data.second << std::endl;
        }
    }

    void write(const std::string& newFilepath) {
        std::ofstream file(newFilepath);
        if (!file.is_open()) {
            throw std::runtime_error("Cannot open " + newFilepath + " for writing.");
        }
        for (const auto& pair : cells) {
            std::string address = pair.first;
            std::string content = pair.second.second;
            file << ">" << address << ":" << content << "\n";
        }
        file << '\0';
        file.close();
    }
};

// Example usage:
// int main() {
//     try {
//         VCFileHandler handler("sample.vc");
//         handler.read();
//         handler.printProperties();
//         handler.write("new.vc");
//     } catch (const std::exception& e) {
//         std::cerr << e.what() << std::endl;
//     }
//     return 0;
// }