Task 311: .INI File Format

Task 311: .INI File Format

1. List of Properties of the .INI File Format Intrinsic to Its File System

The .INI file format is a plain text-based configuration format with no binary structure or magic numbers. It is not tightly bound to specific file system attributes beyond standard file metadata, as it relies on text parsing rather than file system-specific intrinsics. However, based on common specifications (e.g., from Wikipedia and other sources), the key properties intrinsic to how .INI files interact with or are handled by file systems include:

  • File Extension: Typically .ini, but can vary (e.g., .cfg in some cases); case-insensitive on most file systems.
  • MIME Type: Often text/plain or application/x-ini; file systems may associate it with text editors.
  • Encoding: Usually ANSI (Windows-1252), UTF-8, or ASCII; file systems treat it as text, with no inherent byte order mark (BOM).
  • Line Endings: CRLF (\r\n) on Windows file systems for compatibility, but LF (\n) is tolerated on Unix-like systems.
  • File Size: No inherent limit, but typically small (kilobytes) as configuration files; file systems handle as variable-length text.
  • Creation/Modification/Access Timestamps: Standard file system metadata (e.g., NTFS, ext4); no format-specific timestamps.
  • Permissions/Ownership: Standard read/write/execute bits or ACLs; often readable by all users for configuration purposes.
  • No Magic Number or Header: Pure text; file systems identify via extension or content inspection.
  • Hierarchical Structure: Sections ([section]), key-value pairs (key=value), comments (; or #); parsed at application level, not file system.
  • Case Sensitivity: Keys and sections are often case-insensitive on Windows file systems but case-sensitive on others (e.g., Linux).
  • Whitespace Handling: Leading/trailing spaces around keys/values may be trimmed; equals sign (=) or colon (:) as separators in some variants.

These are derived from common usage, as .INI has no official standard (originated from MS-DOS/Windows).

3. Ghost Blog Embedded HTML JavaScript for Drag and Drop .INI File Dump

Here's an HTML page with embedded JavaScript that allows drag-and-drop of a .INI file. It parses the file (assuming standard .INI structure) and dumps the properties (sections, keys, values, comments, and basic file metadata) to the screen.

INI File Property Dumper

Drag and Drop .INI File

Drop .INI file here

4. Python Class for .INI Handling

import os
import re

class IniHandler:
    def __init__(self, filepath):
        self.filepath = filepath
        self.sections = {}
        self.globals = {}
        self.comments = []
        self.metadata = {}

    def read(self):
        if not os.path.exists(self.filepath):
            raise FileNotFoundError(f"File {self.filepath} not found.")
        with open(self.filepath, 'r', encoding='utf-8') as f:
            content = f.read()
        self._parse(content)
        self._get_metadata()

    def _parse(self, content):
        lines = content.splitlines()
        current_section = 'globals'
        for line in lines:
            line = line.strip()
            if not line or line.startswith(';') or line.startswith('#'):
                if line: self.comments.append(line)
                continue
            if re.match(r'^\[.*\]$', line):
                current_section = line[1:-1].strip()
                if current_section not in self.sections:
                    self.sections[current_section] = {}
                continue
            match = re.match(r'^([^=|:]+)[=|:](.*)$', line)
            if match:
                key, value = match.group(1).strip(), match.group(2).strip()
                if current_section == 'globals':
                    self.globals[key] = value
                else:
                    self.sections[current_section][key] = value

    def _get_metadata(self):
        stat = os.stat(self.filepath)
        self.metadata = {
            'name': os.path.basename(self.filepath),
            'size': stat.st_size,
            'created': stat.st_ctime,
            'modified': stat.st_mtime,
            'accessed': stat.st_atime,
            'mode': oct(stat.st_mode)
        }

    def print_properties(self):
        print("File Metadata:")
        for k, v in self.metadata.items():
            print(f"- {k}: {v}")
        print("\nGlobals:")
        for k, v in self.globals.items():
            print(f"{k} = {v}")
        print("\nSections:")
        for section, props in self.sections.items():
            print(f"[{section}]")
            for k, v in props.items():
                print(f"{k} = {v}")
        print("\nComments:")
        for comment in self.comments:
            print(comment)

    def write(self, new_filepath=None):
        filepath = new_filepath or self.filepath
        with open(filepath, 'w', encoding='utf-8') as f:
            # Write globals
            for k, v in self.globals.items():
                f.write(f"{k} = {v}\n")
            # Write sections
            for section, props in self.sections.items():
                f.write(f"\n[{section}]\n")
                for k, v in props.items():
                    f.write(f"{k} = {v}\n")
            # Append comments at end for simplicity
            if self.comments:
                f.write("\n; Comments:\n")
                for comment in self.comments:
                    f.write(f"{comment}\n")

# Example usage:
# handler = IniHandler('sample.ini')
# handler.read()
# handler.print_properties()
# handler.write('output.ini')

5. Java Class for .INI Handling

import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.util.*;
import java.util.regex.*;

public class IniHandler {
    private String filepath;
    private Map<String, String> globals = new LinkedHashMap<>();
    private Map<String, Map<String, String>> sections = new LinkedHashMap<>();
    private List<String> comments = new ArrayList<>();
    private Map<String, Object> metadata = new HashMap<>();

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

    public void read() throws IOException {
        Path path = Paths.get(filepath);
        if (!Files.exists(path)) {
            throw new FileNotFoundException("File " + filepath + " not found.");
        }
        String content = new String(Files.readAllBytes(path), "UTF-8");
        parse(content);
        getMetadata(path);
    }

    private void parse(String content) {
        String[] lines = content.split("\\r?\\n");
        String currentSection = "globals";
        Pattern sectionPattern = Pattern.compile("^\\[(.*)\\]$");
        Pattern kvPattern = Pattern.compile("^([^=|:]+)[=|:](.*)$");
        for (String line : lines) {
            line = line.trim();
            if (line.isEmpty() || line.startsWith(";") || line.startsWith("#")) {
                if (!line.isEmpty()) comments.add(line);
                continue;
            }
            Matcher sectionMatcher = sectionPattern.matcher(line);
            if (sectionMatcher.matches()) {
                currentSection = sectionMatcher.group(1).trim();
                sections.putIfAbsent(currentSection, new LinkedHashMap<>());
                continue;
            }
            Matcher kvMatcher = kvPattern.matcher(line);
            if (kvMatcher.matches()) {
                String key = kvMatcher.group(1).trim();
                String value = kvMatcher.group(2).trim();
                if ("globals".equals(currentSection)) {
                    globals.put(key, value);
                } else {
                    sections.get(currentSection).put(key, value);
                }
            }
        }
    }

    private void getMetadata(Path path) throws IOException {
        BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
        metadata.put("name", path.getFileName().toString());
        metadata.put("size", attrs.size());
        metadata.put("created", attrs.creationTime());
        metadata.put("modified", attrs.lastModifiedTime());
        metadata.put("accessed", attrs.lastAccessTime());
        PosixFileAttributes posix = null;
        try {
            posix = Files.readAttributes(path, PosixFileAttributes.class);
            metadata.put("mode", posix.permissions());
            metadata.put("owner", posix.owner().getName());
        } catch (UnsupportedOperationException e) {
            // Fallback for non-Posix systems
            metadata.put("mode", "N/A");
            metadata.put("owner", "N/A");
        }
    }

    public void printProperties() {
        System.out.println("File Metadata:");
        metadata.forEach((k, v) -> System.out.println("- " + k + ": " + v));
        System.out.println("\nGlobals:");
        globals.forEach((k, v) -> System.out.println(k + " = " + v));
        System.out.println("\nSections:");
        sections.forEach((section, props) -> {
            System.out.println("[" + section + "]");
            props.forEach((k, v) -> System.out.println(k + " = " + v));
        });
        System.out.println("\nComments:");
        comments.forEach(System.out::println);
    }

    public void write(String newFilepath) throws IOException {
        String outPath = (newFilepath != null) ? newFilepath : filepath;
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(outPath))) {
            // Write globals
            for (Map.Entry<String, String> entry : globals.entrySet()) {
                writer.write(entry.getKey() + " = " + entry.getValue() + "\n");
            }
            // Write sections
            for (Map.Entry<String, Map<String, String>> sectionEntry : sections.entrySet()) {
                writer.write("\n[" + sectionEntry.getKey() + "]\n");
                for (Map.Entry<String, String> prop : sectionEntry.getValue().entrySet()) {
                    writer.write(prop.getKey() + " = " + prop.getValue() + "\n");
                }
            }
            // Append comments
            if (!comments.isEmpty()) {
                writer.write("\n; Comments:\n");
                for (String comment : comments) {
                    writer.write(comment + "\n");
                }
            }
        }
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     IniHandler handler = new IniHandler("sample.ini");
    //     handler.read();
    //     handler.printProperties();
    //     handler.write("output.ini");
    // }
}

6. JavaScript Class for .INI Handling (Node.js)

const fs = require('fs');
const path = require('path');

class IniHandler {
    constructor(filepath) {
        this.filepath = filepath;
        this.sections = {};
        this.globals = {};
        this.comments = [];
        this.metadata = {};
    }

    read() {
        if (!fs.existsSync(this.filepath)) {
            throw new Error(`File ${this.filepath} not found.`);
        }
        const content = fs.readFileSync(this.filepath, 'utf-8');
        this._parse(content);
        this._getMetadata();
    }

    _parse(content) {
        const lines = content.split(/\r?\n/);
        let currentSection = 'globals';
        lines.forEach(line => {
            line = line.trim();
            if (!line || line.startsWith(';') || line.startsWith('#')) {
                if (line) this.comments.push(line);
                return;
            }
            if (line.match(/^\[.*\]$/)) {
                currentSection = line.slice(1, -1).trim();
                if (!this.sections[currentSection]) this.sections[currentSection] = {};
                return;
            }
            const match = line.match(/^([^=|:]+)[=|:](.*)$/);
            if (match) {
                const key = match[1].trim();
                const value = match[2].trim();
                if (currentSection === 'globals') {
                    this.globals[key] = value;
                } else {
                    this.sections[currentSection][key] = value;
                }
            }
        });
    }

    _getMetadata() {
        const stat = fs.statSync(this.filepath);
        this.metadata = {
            name: path.basename(this.filepath),
            size: stat.size,
            created: stat.birthtime,
            modified: stat.mtime,
            accessed: stat.atime,
            mode: stat.mode.toString(8)
        };
    }

    printProperties() {
        console.log('File Metadata:');
        Object.entries(this.metadata).forEach(([k, v]) => console.log(`- ${k}: ${v}`));
        console.log('\nGlobals:');
        Object.entries(this.globals).forEach(([k, v]) => console.log(`${k} = ${v}`));
        console.log('\nSections:');
        Object.entries(this.sections).forEach(([section, props]) => {
            console.log(`[${section}]`);
            Object.entries(props).forEach(([k, v]) => console.log(`${k} = ${v}`));
        });
        console.log('\nComments:');
        this.comments.forEach(comment => console.log(comment));
    }

    write(newFilepath = null) {
        const outPath = newFilepath || this.filepath;
        let output = '';
        // Write globals
        Object.entries(this.globals).forEach(([k, v]) => {
            output += `${k} = ${v}\n`;
        });
        // Write sections
        Object.entries(this.sections).forEach(([section, props]) => {
            output += `\n[${section}]\n`;
            Object.entries(props).forEach(([k, v]) => {
                output += `${k} = ${v}\n`;
            });
        });
        // Append comments
        if (this.comments.length) {
            output += '\n; Comments:\n';
            this.comments.forEach(comment => {
                output += `${comment}\n`;
            });
        }
        fs.writeFileSync(outPath, output, 'utf-8');
    }
}

// Example usage:
// const handler = new IniHandler('sample.ini');
// handler.read();
// handler.printProperties();
// handler.write('output.ini');

7. C++ Class for .INI Handling

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <regex>
#include <sys/stat.h>
#include <ctime>

class IniHandler {
private:
    std::string filepath;
    std::map<std::string, std::string> globals;
    std::map<std::string, std::map<std::string, std::string>> sections;
    std::vector<std::string> comments;
    std::map<std::string, std::string> metadata;

public:
    IniHandler(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::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        file.close();
        parse(content);
        getMetadata();
    }

private:
    void parse(const std::string& content) {
        std::istringstream iss(content);
        std::string line;
        std::string currentSection = "globals";
        std::regex sectionRegex(R"(^\[(.*)\]$)");
        std::regex kvRegex(R"(^([^=|:]+)[=|:](.*)$)");
        std::smatch match;
        while (std::getline(iss, line)) {
            line = std::regex_replace(line, std::regex("^ +| +$|( )+"), "$1"); // trim
            if (line.empty() || line[0] == ';' || line[0] == '#') {
                if (!line.empty()) comments.push_back(line);
                continue;
            }
            if (std::regex_match(line, match, sectionRegex)) {
                currentSection = match[1].str();
                // trim currentSection
                currentSection.erase(0, currentSection.find_first_not_of(" \t"));
                currentSection.erase(currentSection.find_last_not_of(" \t") + 1);
                if (sections.find(currentSection) == sections.end()) {
                    sections[currentSection] = {};
                }
                continue;
            }
            if (std::regex_match(line, match, kvRegex)) {
                std::string key = match[1].str();
                std::string value = match[2].str();
                // trim key and value
                key.erase(0, key.find_first_not_of(" \t"));
                key.erase(key.find_last_not_of(" \t") + 1);
                value.erase(0, value.find_first_not_of(" \t"));
                value.erase(value.find_last_not_of(" \t") + 1);
                if (currentSection == "globals") {
                    globals[key] = value;
                } else {
                    sections[currentSection][key] = value;
                }
            }
        }
    }

    void getMetadata() {
        struct stat statbuf;
        if (stat(filepath.c_str(), &statbuf) == 0) {
            metadata["name"] = filepath.substr(filepath.find_last_of("/\\") + 1);
            metadata["size"] = std::to_string(statbuf.st_size);
            metadata["created"] = std::ctime(&statbuf.st_ctime);
            metadata["modified"] = std::ctime(&statbuf.st_mtime);
            metadata["accessed"] = std::ctime(&statbuf.st_atime);
            metadata["mode"] = std::to_string(statbuf.st_mode);
        }
    }

public:
    void printProperties() {
        std::cout << "File Metadata:" << std::endl;
        for (const auto& [k, v] : metadata) {
            std::cout << "- " << k << ": " << v;
        }
        std::cout << "\nGlobals:" << std::endl;
        for (const auto& [k, v] : globals) {
            std::cout << k << " = " << v << std::endl;
        }
        std::cout << "\nSections:" << std::endl;
        for (const auto& [section, props] : sections) {
            std::cout << "[" << section << "]" << std::endl;
            for (const auto& [k, v] : props) {
                std::cout << k << " = " << v << std::endl;
            }
        }
        std::cout << "\nComments:" << std::endl;
        for (const auto& comment : comments) {
            std::cout << comment << std::endl;
        }
    }

    void write(const std::string& newFilepath = "") {
        std::string outPath = newFilepath.empty() ? filepath : newFilepath;
        std::ofstream out(outPath);
        if (!out.is_open()) {
            throw std::runtime_error("Cannot write to " + outPath);
        }
        // Write globals
        for (const auto& [k, v] : globals) {
            out << k << " = " << v << "\n";
        }
        // Write sections
        for (const auto& [section, props] : sections) {
            out << "\n[" << section << "]\n";
            for (const auto& [k, v] : props) {
                out << k << " = " << v << "\n";
            }
        }
        // Append comments
        if (!comments.empty()) {
            out << "\n; Comments:\n";
            for (const auto& comment : comments) {
                out << comment << "\n";
            }
        }
        out.close();
    }
};

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