Task 570: .PROPERTIES File Format

Task 570: .PROPERTIES File Format

File Format Specifications for .PROPERTIES

The .PROPERTIES file format is a plain text format commonly used in Java applications for storing configuration data as key-value pairs. It is defined by the java.util.Properties class in the Java standard library. The format supports simple string-based keys and values, comments, multiline values, and escape sequences for special characters. It does not have a binary structure or fixed fields; instead, it relies on text parsing rules.

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

  • Character encoding: ISO-8859-1 (Latin-1), with support for Unicode escapes via \uXXXX sequences.
  • Entry structure: Each property is a key-value pair on a separate line.
  • Key-value separators: Equals sign (=), colon (:), or whitespace.
  • Comment lines: Lines starting with # or ! are ignored as comments.
  • Line continuation: A backslash () at the end of a line appends the next line to the current one, ignoring the backslash and newline.
  • Escape sequences: Backslash () escapes special characters like =, :, #, !, space, or newline within keys or values.
  • Empty values: If no separator is present, the line is treated as a key with an empty value.
  • Whitespace handling: Leading/trailing whitespace around keys and values is trimmed, but whitespace after the separator is preserved if escaped.
  • File extension: Typically .properties, but not enforced by the format.
  • No header or magic number: Pure text-based, no binary identifiers.

Two direct download links for files of format .PROPERTIES:

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

.PROPERTIES File Dumper
Drag and drop a .properties file here
  1. Python class for .PROPERTIES handling:
import re
import os

class PropertiesFile:
    def __init__(self, filepath):
        self.filepath = filepath
        self.properties = {}

    def read(self):
        if not os.path.exists(self.filepath):
            raise FileNotFoundError(f"File {self.filepath} not found.")
        with open(self.filepath, 'r', encoding='iso-8859-1') as f:
            content = f.read()
            lines = content.splitlines()
            current_line = ''
            for line in lines:
                line = line.strip()
                if line.startswith('#') or line.startswith('!'):
                    continue
                if line.endswith('\\'):
                    current_line += line[:-1]
                    continue
                current_line += line
                if current_line:
                    match = re.match(r'([^=:\s]+)\s*[=:\s]\s*(.*)', current_line)
                    if match:
                        key = self._unescape(match.group(1).strip())
                        value = self._unescape(match.group(2).strip())
                        self.properties[key] = value
                    else:
                        key = self._unescape(current_line.strip())
                        self.properties[key] = ''
                current_line = ''

    def _unescape(self, s):
        s = re.sub(r'\\u([0-9a-fA-F]{4})', lambda m: chr(int(m.group(1), 16)), s)
        s = re.sub(r'\\(.)', r'\1', s)
        return s

    def write(self, new_properties=None):
        if new_properties:
            self.properties.update(new_properties)
        with open(self.filepath, 'w', encoding='iso-8859-1') as f:
            for key, value in self.properties.items():
                escaped_key = self._escape(key)
                escaped_value = self._escape(value)
                f.write(f"{escaped_key}={escaped_value}\n")

    def _escape(self, s):
        s = s.replace('\\', '\\\\').replace(':', '\\:').replace('=', '\\=').replace(' ', '\\ ')
        return s

    def print_properties(self):
        for key, value in self.properties.items():
            print(f"{key}: {value}")

# Example usage:
# pf = PropertiesFile('example.properties')
# pf.read()
# pf.print_properties()
# pf.write({'new_key': 'new_value'})
  1. Java class for .PROPERTIES handling:
import java.io.*;
import java.util.Properties;

public class PropertiesFile {
    private String filepath;
    private Properties properties;

    public PropertiesFile(String filepath) {
        this.filepath = filepath;
        this.properties = new Properties();
    }

    public void read() throws IOException {
        try (InputStream input = new FileInputStream(filepath)) {
            properties.load(input);
        }
    }

    public void write() throws IOException {
        try (OutputStream output = new FileOutputStream(filepath)) {
            properties.store(output, null);
        }
    }

    public void printProperties() {
        properties.forEach((key, value) -> System.out.println(key + ": " + value));
    }

    // Example to add property
    public void addProperty(String key, String value) {
        properties.setProperty(key, value);
    }

    // Main for testing
    public static void main(String[] args) throws IOException {
        PropertiesFile pf = new PropertiesFile("example.properties");
        pf.read();
        pf.printProperties();
        pf.addProperty("new_key", "new_value");
        pf.write();
    }
}
  1. JavaScript class for .PROPERTIES handling (Node.js compatible):
const fs = require('fs');

class PropertiesFile {
    constructor(filepath) {
        this.filepath = filepath;
        this.properties = {};
    }

    read() {
        if (!fs.existsSync(this.filepath)) {
            throw new Error(`File ${this.filepath} not found.`);
        }
        const content = fs.readFileSync(this.filepath, 'latin1'); // ISO-8859-1
        const lines = content.split(/\r?\n/);
        let currentLine = '';
        for (let line of lines) {
            line = line.trim();
            if (line.startsWith('#') || line.startsWith('!')) continue;
            if (line.endsWith('\\')) {
                currentLine += line.slice(0, -1);
                continue;
            }
            currentLine += line;
            if (currentLine) {
                const separatorIndex = currentLine.search(/[=:\s]/);
                if (separatorIndex !== -1) {
                    let key = currentLine.substring(0, separatorIndex).trim();
                    let value = currentLine.substring(separatorIndex + 1).trim();
                    key = this.unescape(key);
                    value = this.unescape(value);
                    this.properties[key] = value;
                } else {
                    this.properties[currentLine.trim()] = '';
                }
            }
            currentLine = '';
        }
    }

    unescape(str) {
        return str.replace(/\\u([0-9a-fA-F]{4})/g, (_, code) => String.fromCharCode(parseInt(code, 16)))
                  .replace(/\\(.)/g, '$1');
    }

    write() {
        let content = '';
        for (let [key, value] of Object.entries(this.properties)) {
            const escapedKey = this.escape(key);
            const escapedValue = this.escape(value);
            content += `${escapedKey}=${escapedValue}\n`;
        }
        fs.writeFileSync(this.filepath, content, 'latin1');
    }

    escape(s) {
        return s.replace(/\\/g, '\\\\').replace(/:/g, '\\:').replace(/=/g, '\\=').replace(/ /g, '\\ ');
    }

    printProperties() {
        for (let [key, value] of Object.entries(this.properties)) {
            console.log(`${key}: ${value}`);
        }
    }
}

// Example usage:
// const pf = new PropertiesFile('example.properties');
// pf.read();
// pf.printProperties();
// pf.properties['new_key'] = 'new_value';
// pf.write();
  1. C++ class for .PROPERTIES handling (using std::string, fstream for simplicity; basic parser):
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <regex>

class PropertiesFile {
private:
    std::string filepath;
    std::map<std::string, std::string> properties;

    std::string unescape(const std::string& s) {
        std::string result;
        for (size_t i = 0; i < s.length(); ++i) {
            if (s[i] == '\\' && i + 1 < s.length()) {
                if (s[i+1] == 'u' && i + 5 < s.length()) {
                    std::string code = s.substr(i+2, 4);
                    char ch = static_cast<char>(std::stoi(code, nullptr, 16));
                    result += ch;
                    i += 5;
                } else {
                    result += s[i+1];
                    ++i;
                }
            } else {
                result += s[i];
            }
        }
        return result;
    }

    std::string escape(const std::string& s) {
        std::string result;
        for (char c : s) {
            if (c == '\\' || c == ':' || c == '=' || c == ' ') {
                result += '\\';
            }
            result += c;
        }
        return result;
    }

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

    void read() {
        std::ifstream file(filepath, std::ios::in | std::ios::binary); // For ISO-8859-1, treat as binary
        if (!file.is_open()) {
            throw std::runtime_error("File not found: " + filepath);
        }
        std::string line, current_line;
        while (std::getline(file, line)) {
            line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); // Handle CR
            std::string trimmed = line;
            trimmed.erase(0, trimmed.find_first_not_of(" \t"));
            if (trimmed.empty() || trimmed[0] == '#' || trimmed[0] == '!') continue;
            if (!line.empty() && line.back() == '\\') {
                current_line += line.substr(0, line.size() - 1);
                continue;
            }
            current_line += line;
            if (!current_line.empty()) {
                std::smatch match;
                if (std::regex_match(current_line, match, std::regex(R"(([^=:\s]+)\s*[=:\s]\s*(.*))"))) {
                    std::string key = unescape(match[1].str());
                    std::string value = unescape(match[2].str());
                    properties[key] = value;
                } else {
                    std::string key = unescape(current_line);
                    properties[key] = "";
                }
            }
            current_line = "";
        }
        file.close();
    }

    void write() {
        std::ofstream file(filepath);
        if (!file.is_open()) {
            throw std::runtime_error("Cannot write to file: " + filepath);
        }
        for (const auto& pair : properties) {
            std::string escaped_key = escape(pair.first);
            std::string escaped_value = escape(pair.second);
            file << escaped_key << "=" << escaped_value << "\n";
        }
        file.close();
    }

    void printProperties() {
        for (const auto& pair : properties) {
            std::cout << pair.first << ": " << pair.second << std::endl;
        }
    }
};

// Example usage:
// int main() {
//     try {
//         PropertiesFile pf("example.properties");
//         pf.read();
//         pf.printProperties();
//         // Add property: pf.properties["new_key"] = "new_value";
//         // pf.write();
//     } catch (const std::exception& e) {
//         std::cerr << e.what() << std::endl;
//     }
//     return 0;
// }