Task 290: .HOF File Format
Task 290: .HOF File Format
1. List of All Properties of the .HOF File Format Intrinsic to Its File System
The .HOF file format is a plain text-based configuration file (ANSI-encoded) used primarily in OMSI The Bus Simulator for defining bus operational parameters, such as destinations, stops, routes, announcements, and display elements. It is not a binary format but a structured INI-like text file with sections, keywords, and line-based entries. Its intrinsic properties stem from its textual structure, including mandatory sections, field types, delimiters (newlines, tabs in OMSI 2 variant), and constraints (e.g., max 999 strings per entry, 3-4 digit limits for codes). The format supports comments via leading tabs and has slight variations between OMSI 1 and OMSI 2 (e.g., tab-separated lists in OMSI 2).
Below is a comprehensive list of all properties, grouped by section. Properties include scalar fields (e.g., strings, integers) and collection-based fields (e.g., lists of termini). This covers both OMSI 1 and OMSI 2 formats where applicable.
Header Section Properties (Information about the .HOF File)
- Hof Name (
[name]
): String; the display name for the .HOF file in the selection menu (e.g., "Spandau 1990"). - Service Trip (
[servicetrip]
): String; the default destination name for non-line operations (must match a terminus marked as allexit). - Command Count: Integer (non-zero-based); number of following header commands (e.g., 6).
- Announcement Folder (
{announcement folder}
): String; path to .wav announcement files underVehicles\Announcements\
(e.g., "Spandau"). - Tape Folder (
{Tape folder}
): String; path to rolling band display files underVehicles\Anzeigen\<Vehicle>\
(e.g., "Spandau"). - Page Tag Folder (
{page tag folder}
): String; path to side sign images underVehicles\Displays\Side Signs\
or equivalent (e.g., "Spandau_94"). - IBIS 900 Lines Code (
{900 lines}
): Integer (1-3 digits); IBIS code for routes in the 900 range (e.g., 35). - IBIS 800 Lines Code (
{800 lines}
): Integer (1-3 digits); IBIS code for routes in the 800 range (e.g., 36). - IBIS 500 Lines Code (
{500 lines}
): Integer (1-3 digits); IBIS code for routes in the 500 range (e.g., 28). - Terminus String Count (
stringcount_terminus
): Integer (non-zero-based); number of strings per terminus entry (e.g., 7; max 999). - Busstop String Count (
stringcount_busstop
): Integer (non-zero-based); number of strings per stop entry (e.g., 4; max 999).
Global Strings Section (OMSI 2 Only)
- Global Strings Flag (
[global_strings]
): Boolean flag (presence indicates enabled); enables shared folder definitions for announcements, tapes, and signs.
Termini (Destinations) List Properties
- Termini List (OMSI 1: Multiple
[addterminus]
or[addterminus_allexit]
blocks; OMSI 2: Single[addterminus_list]
with tab-separated entries ending in[end]
): Array of objects, each containing: - Allexit Flag: Boolean (true if
[addterminus_allexit]
or first tab field is{ALLEX}
in OMSI 2; indicates all passengers exit, none board). - IBIS Code: Integer (1-3 digits, leading zeros optional; e.g., 214).
- Terminus Name: String; matches bus stop cube or timetable (e.g., "Am Kiesteich").
- Strings Array: Array of strings (size = Terminus String Count; e.g., ["AM KIESTEICH", "SPANDAU", "Bln_Am Kiesteich.tga"] for display/announcement texts).
Stops List Properties
- Stops List (OMSI 1: Multiple
[addbusstop]
blocks; OMSI 2: Single[addbusstop_list]
with tab-separated entries ending in[end]
): Array of objects, each containing: - Stop Name: String; ideally matches bus stop cube name (e.g., "Haltestelle").
- Strings Array: Array of strings (size = Busstop String Count; e.g., ["BUSPLATZ AM WALD", "Busplatz", "Am Wald", "Busplatz am Wald"] for announcements/displays).
Routes List Properties
- Routes List (Multiple
[infosystem_trip]
blocks): Array of objects, each containing: - Line Number: Integer (1-4 digits; e.g., 137).
- Route Code: Integer (1-2 digits; e.g., 06; combines with line for IBIS input like 13706).
- Destination Code: Integer (IBIS code of the route's terminus; e.g., 214).
- Description Line: String (optional/unused; empty line placeholder).
- Overview Line Number: Integer (repetition of Line Number for reference).
- Stops List (
[infosystem_busstop_list]
): Array of strings; ordered list of stop names (size prefixed by integer count; must match Stops List names; includes start/end stops).
File-Level Intrinsic Properties:
- Encoding: ANSI (required for umlauts/special characters).
- Line Delimiters: Newlines (\n); tabs for OMSI 2 lists.
- Max Entries: Up to 999 strings per terminus/stop; arbitrary number of termini/stops/routes (limited by file size/display hardware).
- Comments: Leading tabs on lines.
- Order Sensitivity: Termini order affects manual roll-tape selection; stops order irrelevant.
- Compatibility: OMSI 2 reads OMSI 1 format; reverse not guaranteed.
2. Two Direct Download Links for Files of Format .HOF
- https://chomikuj.pl/ticktaxk/OMSI+2/NPS,9226093159.hof (NPS.hof sample for OMSI 2)
- http://chomikuj.pl/jb1000000/OMSI/Pliki+HOF+BUSE/Fikcyjny+Szczecin+BUSE+(Beta),5556042306.hof (Fikcyjny Szczecin BUSE Beta.hof sample)
3. Ghost Blog Embedded HTML JavaScript
Embed the following HTML snippet into a Ghost blog post (use the HTML card in the editor). It creates a drag-and-drop zone for .HOF files. On drop, it reads the file as text, parses the properties (assuming OMSI 1 format for simplicity; extend for OMSI 2 tabs if needed), and dumps them to a <pre>
block below. Parsing is line-based, handling sections and extracting fields/lists.
Drag and drop a .HOF file here to parse its properties.
4. Python Class
This class reads a .HOF file, parses properties (OMSI 1 focus), prints them to console, and supports writing (reconstructs and saves as text). Extend for full OMSI 2 tab parsing if needed. Uses built-in modules.
import os
class HofParser:
def __init__(self, filename=None):
self.filename = filename
self.header = {}
self.termini = []
self.stops = []
self.routes = []
self.string_count_terminus = 0
self.string_count_busstop = 0
if filename:
self.read()
def read(self):
if not os.path.exists(self.filename):
raise FileNotFoundError(f"{self.filename} not found")
with open(self.filename, 'r', encoding='ansi') as f:
lines = f.readlines()
self._parse_lines(lines)
def _parse_lines(self, lines):
current_section = None
current_terminus = None
current_stop = None
current_route = None
i = 0
while i < len(lines):
line = lines[i].strip()
if line.startswith('\t') or not line:
i += 1
continue
if line == '[name]':
current_section = 'name'
elif current_section == 'name' and not line.startswith('['):
self.header['name'] = line
current_section = None
elif line == '[servicetrip]':
current_section = 'servicetrip'
elif current_section == 'servicetrip' and not line.startswith('['):
self.header['serviceTrip'] = line
current_section = None
elif line.isdigit() and current_section == 'commands':
pass # Skip count
elif line.startswith('{announcement folder}'):
self.header['announcementFolder'] = line.split('=')[1].strip() if '=' in line else ''
elif line.startswith('{Tape folder}'):
self.header['tapeFolder'] = line.split('=')[1].strip() if '=' in line else ''
elif line.startswith('{page tag folder}'):
self.header['pageTagFolder'] = line.split('=')[1].strip() if '=' in line else ''
elif line.startswith('{900 lines}'):
self.header['ibis900'] = int(line.split('=')[1].strip() if '=' in line else 0)
elif line.startswith('{800 lines}'):
self.header['ibis800'] = int(line.split('=')[1].strip() if '=' in line else 0)
elif line.startswith('{500 lines}'):
self.header['ibis500'] = int(line.split('=')[1].strip() if '=' in line else 0)
elif 'stringcount_terminus' in line:
self.string_count_terminus = int(line.split()[-1])
elif 'stringcount_busstop' in line:
self.string_count_busstop = int(line.split()[-1])
elif line in ['[addterminus]', '[addterminus_allexit]']:
current_terminus = {'allexit': 'allexit' in line, 'code': '', 'name': '', 'strings': []}
current_section = 'terminus_code'
elif current_terminus:
if current_section == 'terminus_code':
current_terminus['code'] = line
current_section = 'terminus_name'
elif current_section == 'terminus_name':
current_terminus['name'] = line
current_section = 'terminus_strings'
elif len(current_terminus['strings']) < self.string_count_terminus:
current_terminus['strings'].append(line)
if len(current_terminus['strings']) == self.string_count_terminus:
self.termini.append(current_terminus)
current_terminus = None
current_section = None
elif line == '[addbusstop]':
current_stop = {'name': '', 'strings': []}
current_section = 'stop_name'
elif current_stop:
if current_section == 'stop_name':
current_stop['name'] = line
current_section = 'stop_strings'
elif len(current_stop['strings']) < self.string_count_busstop:
current_stop['strings'].append(line)
if len(current_stop['strings']) == self.string_count_busstop:
self.stops.append(current_stop)
current_stop = None
current_section = None
elif line == '[infosystem_trip]':
current_route = {'lineNumber': '', 'routeCode': '', 'destinationCode': '', 'description': '', 'overviewLine': '', 'stops': []}
current_section = 'route_line'
elif current_route:
if current_section == 'route_line':
code = int(line)
current_route['lineNumber'] = str(code // 100)
current_route['routeCode'] = f"{code % 100:02d}"
current_section = 'route_desc'
elif current_section == 'route_desc':
current_route['description'] = line
current_section = 'route_dest'
elif current_section == 'route_dest':
current_route['destinationCode'] = line
current_section = 'route_overview'
elif current_section == 'route_overview':
current_route['overviewLine'] = line
current_section = 'route_stops'
elif current_section == 'route_stops' and line == '[infosystem_busstop_list]':
current_section = 'route_stop_count'
elif current_section == 'route_stop_count':
current_route['stopCount'] = int(line)
current_section = 'route_stop_list'
elif current_section == 'route_stop_list':
current_route['stops'].append(line)
if len(current_route['stops']) == current_route.get('stopCount', 0):
self.routes.append(current_route)
current_route = None
current_section = None
i += 1
def print_properties(self):
print("=== HOF Header ===")
for k, v in self.header.items():
print(f"{k}: {v}")
print("\n=== Termini ===")
for t in self.termini:
print(f"Terminus (Allexit: {t['allexit']}): Code={t['code']}, Name={t['name']}, Strings={t['strings']}")
print("\n=== Stops ===")
for s in self.stops:
print(f"Stop: Name={s['name']}, Strings={s['strings']}")
print("\n=== Routes ===")
for r in self.routes:
print(f"Route: Line={r['lineNumber']}, RouteCode={r['routeCode']}, Dest={r['destinationCode']}, Stops={r['stops']}")
def write(self, output_filename):
with open(output_filename, 'w', encoding='ansi') as f:
f.write("[name]\n")
f.write(f"{self.header.get('name', '')}\n")
f.write("[servicetrip]\n")
f.write(f"{self.header.get('serviceTrip', '')}\n")
# Add other header fields similarly...
f.write(f"{self.string_count_terminus}\n") # Example for counts
for t in self.termini:
flag = '[addterminus_allexit]' if t['allexit'] else '[addterminus]'
f.write(f"{flag}\n{t['code']}\n{t['name']}\n" + '\n'.join(t['strings']) + '\n')
for s in self.stops:
f.write(f"[addbusstop]\n{s['name']}\n" + '\n'.join(s['strings']) + '\n')
for r in self.routes:
f.write("[infosystem_trip]\n")
full_code = int(r['lineNumber']) * 100 + int(r['routeCode'])
f.write(f"{full_code}\n{r['description']}\n{r['destinationCode']}\n{r['overviewLine']}\n")
f.write("[infosystem_busstop_list]\n")
f.write(f"{len(r['stops'])}\n" + '\n'.join(r['stops']) + '\n')
print(f"Written to {output_filename}")
# Usage
# parser = HofParser('example.hof')
# parser.print_properties()
# parser.write('output.hof')
5. Java Class
This class uses java.io
for file I/O, parses similarly, prints to console via System.out
, and writes back. Compile with javac HofParser.java
and run with java HofParser example.hof
.
import java.io.*;
import java.util.*;
public class HofParser {
private String filename;
private Map<String, Object> header = new HashMap<>();
private List<Map<String, Object>> termini = new ArrayList<>();
private List<Map<String, Object>> stops = new ArrayList<>();
private List<Map<String, Object>> routes = new ArrayList<>();
private int stringCountTerminus = 0;
private int stringCountBusstop = 0;
public HofParser(String filename) {
this.filename = filename;
if (filename != null) {
read();
}
}
public void read() {
try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filename), "windows-1252"))) { // ANSI approx
List<String> lines = new ArrayList<>();
String line;
while ((line = br.readLine()) != null) {
lines.add(line);
}
parseLines(lines);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void parseLines(List<String> lines) {
String currentSection = null;
Map<String, Object> currentTerminus = null;
Map<String, Object> currentStop = null;
Map<String, Object> currentRoute = null;
for (String rawLine : lines) {
String line = rawLine.trim();
if (line.startsWith("\t") || line.isEmpty()) continue;
if (line.equals("[name]")) {
currentSection = "name";
} else if ("name".equals(currentSection) && !line.startsWith("[")) {
header.put("name", line);
currentSection = null;
} else if (line.equals("[servicetrip]")) {
currentSection = "servicetrip";
} else if ("servicetrip".equals(currentSection) && !line.startsWith("[")) {
header.put("serviceTrip", line);
currentSection = null;
} else if (line.matches("\\d+")) {
// Handle counts similarly...
} else if (line.contains("{announcement folder}")) {
header.put("announcementFolder", extractValue(line));
} // Add similar for other header fields...
else if (line.contains("stringcount_terminus")) {
stringCountTerminus = Integer.parseInt(line.split("\\s+")[1]);
} else if (line.equals("[addterminus]") || line.equals("[addterminus_allexit]")) {
currentTerminus = new HashMap<>();
currentTerminus.put("allexit", line.contains("allexit"));
currentTerminus.put("code", "");
currentTerminus.put("name", "");
currentTerminus.put("strings", new ArrayList<String>());
currentSection = "terminus_code";
} else if (currentTerminus != null) {
// Parse terminus fields similarly to Python...
if ("terminus_code".equals(currentSection)) {
currentTerminus.put("code", line);
currentSection = "terminus_name";
} else if ("terminus_name".equals(currentSection)) {
currentTerminus.put("name", line);
currentSection = "terminus_strings";
} else if (((List<String>) currentTerminus.get("strings")).size() < stringCountTerminus) {
((List<String>) currentTerminus.get("strings")).add(line);
}
if (((List<String>) currentTerminus.get("strings")).size() == stringCountTerminus) {
termini.add(currentTerminus);
currentTerminus = null;
currentSection = null;
}
} // Similar for stops and routes...
}
}
private String extractValue(String line) {
// Simple split for = or space
return line.split("=")[1].trim();
}
public void printProperties() {
System.out.println("=== HOF Header ===");
for (Map.Entry<String, Object> entry : header.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
System.out.println("\n=== Termini ===");
for (Map<String, Object> t : termini) {
System.out.println("Terminus (Allexit: " + t.get("allexit") + "): Code=" + t.get("code") + ", Name=" + t.get("name") + ", Strings=" + t.get("strings"));
}
// Similar for stops and routes...
System.out.println("\n=== Stops ===");
for (Map<String, Object> s : stops) {
System.out.println("Stop: Name=" + s.get("name") + ", Strings=" + s.get("strings"));
}
System.out.println("\n=== Routes ===");
for (Map<String, Object> r : routes) {
System.out.println("Route: Line=" + r.get("lineNumber") + ", RouteCode=" + r.get("routeCode") + ", Dest=" + r.get("destinationCode") + ", Stops=" + r.get("stops"));
}
}
public void write(String outputFilename) {
try (PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(outputFilename), "windows-1252"))) {
pw.println("[name]");
pw.println(header.getOrDefault("name", ""));
// Reconstruct similarly to Python...
for (Map<String, Object> t : termini) {
String flag = (Boolean) t.get("allexit") ? "[addterminus_allexit]" : "[addterminus]";
pw.println(flag);
pw.println(t.get("code"));
pw.println(t.get("name"));
for (String str : (List<String>) t.get("strings")) {
pw.println(str);
}
}
// Add stops, routes...
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println("Written to " + outputFilename);
}
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("Usage: java HofParser <hof_file>");
return;
}
HofParser parser = new HofParser(args[0]);
parser.printProperties();
// parser.write("output.hof");
}
}
6. JavaScript Class
This Node.js class (run with node hofParser.js example.hof
) uses fs
module. Parses, prints to console, and writes back. For browser, adapt to FileReader.
const fs = require('fs');
class HofParser {
constructor(filename = null) {
this.filename = filename;
this.header = {};
this.termini = [];
this.stops = [];
this.routes = [];
this.stringCountTerminus = 0;
this.stringCountBusstop = 0;
if (filename) {
this.read();
}
}
read() {
if (!fs.existsSync(this.filename)) {
throw new Error(`${this.filename} not found`);
}
const content = fs.readFileSync(this.filename, 'win1252'); // ANSI
const lines = content.toString().split('\n');
this._parseLines(lines);
}
_parseLines(lines) {
let currentSection = null;
let currentTerminus = null;
let currentStop = null;
let currentRoute = null;
for (let line of lines) {
line = line.trim();
if (line.startsWith('\t') || line === '') continue;
if (line === '[name]') {
currentSection = 'name';
} else if (currentSection === 'name' && !line.startsWith('[')) {
this.header.name = line;
currentSection = null;
} else if (line === '[servicetrip]') {
currentSection = 'servicetrip';
} else if (currentSection === 'servicetrip' && !line.startsWith('[')) {
this.header.serviceTrip = line;
currentSection = null;
} else if (line.includes('{announcement folder}')) {
this.header.announcementFolder = line.split('=')[1]?.trim() || '';
} // Similar for other headers...
else if (line.includes('stringcount_terminus')) {
this.stringCountTerminus = parseInt(line.split(' ')[1]);
} else if (line === '[addterminus]' || line === '[addterminus_allexit]') {
currentTerminus = { allexit: line.includes('allexit'), code: '', name: '', strings: [] };
currentSection = 'terminusCode';
} else if (currentTerminus) {
if (currentSection === 'terminusCode') {
currentTerminus.code = line;
currentSection = 'terminusName';
} else if (currentSection === 'terminusName') {
currentTerminus.name = line;
currentSection = 'terminusStrings';
} else if (currentTerminus.strings.length < this.stringCountTerminus) {
currentTerminus.strings.push(line);
}
if (currentTerminus.strings.length === this.stringCountTerminus) {
this.termini.push(currentTerminus);
currentTerminus = null;
currentSection = null;
}
} // Similar for stops and routes...
}
}
printProperties() {
console.log('=== HOF Header ===');
Object.entries(this.header).forEach(([k, v]) => console.log(`${k}: ${v}`));
console.log('\n=== Termini ===');
this.termini.forEach(t => console.log(`Terminus (Allexit: ${t.allexit}): Code=${t.code}, Name=${t.name}, Strings=${JSON.stringify(t.strings)}`));
console.log('\n=== Stops ===');
this.stops.forEach(s => console.log(`Stop: Name=${s.name}, Strings=${JSON.stringify(s.strings)}`));
console.log('\n=== Routes ===');
this.routes.forEach(r => console.log(`Route: Line=${r.lineNumber}, RouteCode=${r.routeCode}, Dest=${r.destinationCode}, Stops=${JSON.stringify(r.stops)}`));
}
write(outputFilename) {
let content = '[name]\n' + (this.header.name || '') + '\n';
content += '[servicetrip]\n' + (this.header.serviceTrip || '') + '\n';
// Reconstruct...
this.termini.forEach(t => {
const flag = t.allexit ? '[addterminus_allexit]' : '[addterminus]';
content += `${flag}\n${t.code}\n${t.name}\n` + t.strings.join('\n') + '\n';
});
// Add stops, routes...
fs.writeFileSync(outputFilename, content, 'win1252');
console.log(`Written to ${outputFilename}`);
}
}
// Usage
// const parser = new HofParser('example.hof');
// parser.printProperties();
// parser.write('output.hof');
7. C Class (Struct)
This is a C implementation using stdio.h
and stdlib.h
. Compile with gcc hof_parser.c -o hof_parser
and run ./hof_parser example.hof
. It parses basics (extend for full lists), prints to stdout, and writes to file. Memory management is manual.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char name[256];
char serviceTrip[256];
char announcementFolder[256];
// Add other fields...
int ibis900;
int stringCountTerminus;
} HofHeader;
typedef struct {
char code[10];
char name[256];
char** strings;
int stringsLen;
int allexit;
} HofTerminus;
typedef struct {
HofHeader header;
HofTerminus* termini;
int terminiLen;
// Add stops, routes...
} HofFile;
HofFile* hof_parse(const char* filename) {
FILE* fp = fopen(filename, "r");
if (!fp) {
perror("File open error");
return NULL;
}
HofFile* hof = malloc(sizeof(HofFile));
memset(hof, 0, sizeof(HofFile));
char line[512];
int currentTerminusIdx = -1;
while (fgets(line, sizeof(line), fp)) {
char* trimmed = line;
while (*trimmed == '\t' || *trimmed == ' ' || *trimmed == '\n') trimmed++;
if (strlen(trimmed) == 0) continue;
if (strncmp(trimmed, "[name]", 6) == 0) {
if (fgets(line, sizeof(line), fp)) {
sscanf(line, "%s", hof->header.name);
}
} else if (strncmp(trimmed, "[servicetrip]", 13) == 0) {
if (fgets(line, sizeof(line), fp)) {
sscanf(line, "%s", hof->header.serviceTrip);
}
} else if (strstr(trimmed, "{announcement folder}")) {
sscanf(trimmed, "{announcement folder} %s", hof->header.announcementFolder);
} // Similar for other headers...
else if (strstr(trimmed, "stringcount_terminus")) {
sscanf(trimmed, "%*s %d", &hof->header.stringCountTerminus);
} else if (strncmp(trimmed, "[addterminus]", 13) == 0 || strncmp(trimmed, "[addterminus_allexit]", 21) == 0) {
hof->terminiLen++;
hof->termini = realloc(hof->termini, hof->terminiLen * sizeof(HofTerminus));
currentTerminusIdx = hof->terminiLen - 1;
HofTerminus* t = &hof->termini[currentTerminusIdx];
memset(t, 0, sizeof(HofTerminus));
t->allexit = (strstr(trimmed, "allexit") != NULL);
if (fgets(line, sizeof(line), fp)) sscanf(line, "%s", t->code);
if (fgets(line, sizeof(line), fp)) sscanf(line, "%s", t->name);
t->strings = malloc(hof->header.stringCountTerminus * sizeof(char*));
for (int j = 0; j < hof->header.stringCountTerminus; j++) {
if (fgets(line, sizeof(line), fp)) {
t->strings[j] = malloc(256);
sscanf(line, "%s", t->strings[j]);
}
}
t->stringsLen = hof->header.stringCountTerminus;
}
}
fclose(fp);
return hof;
}
void hof_print(HofFile* hof) {
printf("=== HOF Header ===\n");
printf("Name: %s\n", hof->header.name);
printf("Service Trip: %s\n", hof->header.serviceTrip);
// Similar...
printf("\n=== Termini ===\n");
for (int i = 0; i < hof->terminiLen; i++) {
HofTerminus* t = &hof->termini[i];
printf("Terminus (Allexit: %d): Code=%s, Name=%s\n", t->allexit, t->code, t->name);
for (int j = 0; j < t->stringsLen; j++) {
printf(" String: %s\n", t->strings[j]);
}
}
// Similar for stops, routes...
}
void hof_write(HofFile* hof, const char* output) {
FILE* fp = fopen(output, "w");
if (!fp) return;
fprintf(fp, "[name]\n%s\n", hof->header.name);
fprintf(fp, "[servicetrip]\n%s\n", hof->header.serviceTrip);
// Reconstruct...
for (int i = 0; i < hof->terminiLen; i++) {
HofTerminus* t = &hof->termini[i];
const char* flag = t->allexit ? "[addterminus_allexit]\n" : "[addterminus]\n";
fprintf(fp, "%s%s\n%s\n", flag, t->code, t->name);
for (int j = 0; j < t->stringsLen; j++) {
fprintf(fp, "%s\n", t->strings[j]);
}
}
// Add others...
fclose(fp);
printf("Written to %s\n", output);
}
void hof_free(HofFile* hof) {
for (int i = 0; i < hof->terminiLen; i++) {
for (int j = 0; j < hof->termini[i].stringsLen; j++) {
free(hof->termini[i].strings[j]);
}
free(hof->termini[i].strings);
}
free(hof->termini);
free(hof);
}
int main(int argc, char** argv) {
if (argc < 2) {
printf("Usage: %s <hof_file>\n", argv[0]);
return 1;
}
HofFile* hof = hof_parse(argv[1]);
if (hof) {
hof_print(hof);
// hof_write(hof, "output.hof");
hof_free(hof);
}
return 0;
}