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:
- https://doc.casthighlight.com/tools/cli/sample-highlight.properties
- https://raw.githubusercontent.com/Jasig/cas/v3.5.2/cas-server-webapp/src/main/webapp/WEB-INF/cas.properties
Ghost blog embedded HTML JavaScript for drag-and-drop .PROPERTIES file dump:
Drag and drop a .properties file here
- 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'})
- 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();
}
}
- 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();
- 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;
// }