Skip to main content

ControlForge CSV & INI Parsing Guide

James M. Belcher Founder, JMB Technical Services LLC April 2026 | ControlForge v1.0.535


1. Overview

ControlForge provides 20 built-in functions for working with two common data file formats from Structured Text:

FormatFunctionsUse Case
CSV10Data import/export, recipes, tag lists, report generation
INI10Configuration files, device settings, recipe parameters

Both support in-memory parsing (string → handle → query/modify → string) and CSV/INI files can be combined with the FILE_* functions for disk operations.


2. CSV Functions

Parse CSV Data

(* Parse a CSV string — first row treated as headers *)
data := CSV_PARSE('name,temp,pressure
Pump-1,72.5,45.3
Pump-2,68.1,42.0
Pump-3,71.8,44.7');

rows := CSV_ROW_COUNT(data); (* 4 — includes header row *)
cols := CSV_COL_COUNT(data); (* 3 *)
ParamTypeDescription
textSTRINGCSV text to parse
delimiterSTRINGOptional — custom delimiter (default: comma)

Returns: handle for use with other CSV_* functions.

Read Fields

(* By row/column index — 0-based *)
name := CSV_GET_FIELD(data, 1, 0); (* "Pump-1" — row 1, col 0 *)
temp := CSV_GET_FIELD(data, 1, 1); (* "72.5" *)

(* Get entire row as array *)
row := CSV_GET_ROW(data, 2); (* ["Pump-2", "68.1", "42.0"] *)

(* Get headers *)
headers := CSV_GET_HEADER(data); (* ["name", "temp", "pressure"] *)

(* Find column index by header name *)
temp_col := CSV_FIND_COL(data, 'temp'); (* 1 *)
press_col := CSV_FIND_COL(data, 'pressure'); (* 2 *)

(* Name-based access: find column, then read *)
temp_val := CSV_GET_FIELD(data, 1, CSV_FIND_COL(data, 'temp'));

Modify CSV

(* Update a field *)
data := CSV_SET_FIELD(data, 1, 1, '75.0');

(* Add a new row *)
data := CSV_ADD_ROW(data, 'Pump-4', '69.5', '41.2');

Export to String

csv_text := CSV_TO_STRING(data);

(* With custom delimiter *)
tsv_text := CSV_TO_STRING(data, CHR(9)); (* Tab-separated *)

Parse with Custom Delimiter

(* Tab-separated *)
data := CSV_PARSE(tsv_content, CHR(9));

(* Semicolon-separated (European format) *)
data := CSV_PARSE(euro_content, ';');

Example: Load Recipe from CSV File

PROGRAM POU_CSVRecipe
VAR
state : INT := 0;
csv_text : STRING;
data : STRING;
recipe_count : INT;
i : INT;
name : STRING;
temp : REAL;
pressure : REAL;
time_sec : INT;
name_col : INT;
temp_col : INT;
press_col : INT;
time_col : INT;
END_VAR

CASE state OF
0: (* Read CSV file *)
IF FILE_EXISTS('/data/recipes.csv') THEN
csv_text := FILE_READ('/data/recipes.csv');
data := CSV_PARSE(csv_text);

(* Find columns by header name *)
name_col := CSV_FIND_COL(data, 'name');
temp_col := CSV_FIND_COL(data, 'temperature');
press_col := CSV_FIND_COL(data, 'pressure');
time_col := CSV_FIND_COL(data, 'time');

recipe_count := CSV_ROW_COUNT(data) - 1; (* Exclude header *)
state := 10;
END_IF;

10: (* Process recipes — skip row 0 (headers) *)
FOR i := 1 TO recipe_count DO
name := CSV_GET_FIELD(data, i, name_col);
temp := STRING_TO_REAL(CSV_GET_FIELD(data, i, temp_col));
pressure := STRING_TO_REAL(CSV_GET_FIELD(data, i, press_col));
time_sec := STRING_TO_INT(CSV_GET_FIELD(data, i, time_col));
(* Use recipe values... *)
END_FOR;
END_CASE;
END_PROGRAM

Example: Generate CSV Report

PROGRAM POU_CSVReport
VAR
data : STRING;
initialized : BOOL := FALSE;
scan_count : DINT := 0;
temperature : REAL;
pressure : REAL;
END_VAR

IF NOT initialized THEN
(* Create CSV with headers *)
data := CSV_PARSE('timestamp,temperature,pressure');
initialized := TRUE;
END_IF;

scan_count := scan_count + 1;

(* Add a row every 100 scans *)
IF (scan_count MOD 100) = 0 THEN
data := CSV_ADD_ROW(data,
DT_TO_STRING(NOW()),
REAL_TO_STRING(temperature),
REAL_TO_STRING(pressure)
);

(* Save to file every 1000 scans *)
IF (scan_count MOD 1000) = 0 THEN
FILE_WRITE('/data/report.csv', CSV_TO_STRING(data));
END_IF;
END_IF;
END_PROGRAM

3. INI Functions

INI files use a [section] + key=value format common in device configuration.

Two Access Patterns

PatternFunctionsDescription
File I/OINI_READ, INI_WRITERead/write directly to disk
In-MemoryINI_PARSE → query/modify → INI_TO_STRINGParse string, manipulate, serialize

Direct File Access

(* Read a value from an INI file *)
host := INI_READ('/data/config.ini', 'modbus', 'host', '10.0.0.50');
port := INI_READ('/data/config.ini', 'modbus', 'port', '502');

(* Write a value to an INI file *)
INI_WRITE('/data/config.ini', 'modbus', 'host', '10.0.0.51');
INI_WRITE('/data/config.ini', 'runtime', 'scan_ms', '50');
ParamTypeDescription
filepathSTRINGPath to .ini file
sectionSTRINGSection name (without brackets)
keySTRINGKey name
defaultSTRINGINI_READ only — returned if key not found

INI_WRITE creates the file, section, and key if they don't exist.

In-Memory Parsing

(* Parse INI text *)
cfg := INI_PARSE('
[modbus]
host=10.0.0.50
port=502
unit_id=1

[mqtt]
broker=tcp://10.0.0.144:1883
topic_prefix=plant/data
');

(* Read values *)
host := INI_GET(cfg, 'modbus', 'host', ''); (* "10.0.0.50" *)
port := INI_GET(cfg, 'modbus', 'port', '502'); (* "502" *)
broker := INI_GET(cfg, 'mqtt', 'broker', ''); (* "tcp://10.0.0.144:1883" *)

(* List sections and keys *)
sections := INI_SECTIONS(cfg); (* ["modbus", "mqtt"] *)
keys := INI_KEYS(cfg, 'modbus'); (* ["host", "port", "unit_id"] *)

Modify In-Memory

(* Set/update values *)
cfg := INI_SET(cfg, 'modbus', 'host', '10.0.0.51');
cfg := INI_SET(cfg, 'modbus', 'timeout_ms', '5000'); (* Adds new key *)
cfg := INI_SET(cfg, 'logging', 'level', 'info'); (* Creates new section *)

(* Delete *)
cfg := INI_DELETE_KEY(cfg, 'modbus', 'timeout_ms');
cfg := INI_DELETE_SECTION(cfg, 'logging');

(* Serialize back to string *)
ini_text := INI_TO_STRING(cfg);

(* Save to file *)
FILE_WRITE('/data/config.ini', ini_text);

Example: Device Configuration Manager

PROGRAM POU_DeviceConfig
VAR
state : INT := 0;
cfg : STRING;
ini_text : STRING;

(* Loaded config *)
mb_host : STRING;
mb_port : INT;
mb_unit : INT;
scan_ms : INT;
log_level : STRING;
END_VAR

CASE state OF
0: (* Load config file *)
IF FILE_EXISTS('/data/device.ini') THEN
ini_text := FILE_READ('/data/device.ini');
cfg := INI_PARSE(ini_text);
ELSE
(* Create default config *)
cfg := INI_PARSE('');
cfg := INI_SET(cfg, 'modbus', 'host', '10.0.0.50');
cfg := INI_SET(cfg, 'modbus', 'port', '502');
cfg := INI_SET(cfg, 'modbus', 'unit_id', '1');
cfg := INI_SET(cfg, 'runtime', 'scan_ms', '50');
cfg := INI_SET(cfg, 'runtime', 'log_level', 'info');
FILE_WRITE('/data/device.ini', INI_TO_STRING(cfg));
END_IF;

(* Apply config *)
mb_host := INI_GET(cfg, 'modbus', 'host', '10.0.0.50');
mb_port := STRING_TO_INT(INI_GET(cfg, 'modbus', 'port', '502'));
mb_unit := STRING_TO_INT(INI_GET(cfg, 'modbus', 'unit_id', '1'));
scan_ms := STRING_TO_INT(INI_GET(cfg, 'runtime', 'scan_ms', '50'));
log_level := INI_GET(cfg, 'runtime', 'log_level', 'info');
state := 10;

10: (* Running — config applied *)
END_CASE;
END_PROGRAM

Appendix A: Quick Reference

CSV Functions (10)

FunctionParametersReturnsDescription
CSV_PARSE(text [, delim])1-2HandleParse CSV string
CSV_GET_FIELD(h, row, col)3STRINGRead field by index
CSV_GET_ROW(h, row)2ARRAYGet entire row
CSV_GET_HEADER(h)1ARRAYGet first row (headers)
CSV_FIND_COL(h, name)2INTFind column by header name (-1 if missing)
CSV_ROW_COUNT(h)1INTNumber of rows (including header)
CSV_COL_COUNT(h [, row])1-2INTNumber of columns
CSV_SET_FIELD(h, row, col, val)4HandleUpdate field
CSV_ADD_ROW(h, val1, val2, ...)2+HandleAppend row
CSV_TO_STRING(h [, delim])1-2STRINGSerialize to CSV text

INI Functions (10)

FunctionParametersReturnsDescription
INI_READ(path, section, key [, default])3-4STRINGRead from file
INI_WRITE(path, section, key, value)4BOOLWrite to file
INI_PARSE(text)1HandleParse INI string
INI_GET(h, section, key [, default])3-4STRINGRead from handle
INI_SET(h, section, key, value)4HandleSet value in handle
INI_SECTIONS(h)1ARRAYList section names
INI_KEYS(h, section)2ARRAYList keys in section
INI_TO_STRING(h)1STRINGSerialize to INI text
INI_DELETE_KEY(h, section, key)3HandleRemove key
INI_DELETE_SECTION(h, section)2HandleRemove section

ControlForge v1.0.535 | 20 CSV & INI Functions | Data Import/Export & Configuration

© 2026 JMB Technical Services LLC. All rights reserved. Back to All Guides