A technology adapter for working with CSV files in the OpenFlexo/FML ecosystem.
Status: Working MVP - core functionality is stable, see KNOWN_ISSUES.md for limitations
Lets you manipulate CSV files through FML . You can:
- Load CSV documents as FML resources
- Add/modify rows and cells programmatically
- Query data with SelectCSVRow/SelectCSVCell
- Integrate CSV data into your virtual models
- Persist changes back to disk
Basically, if you need to work with CSV data in OpenFlexo, this is how you do it.
- Clone this repo into your OpenFlexo workspace
- In openflexo-dev/settings.gradle uncomment (or add if it doesnt exist)
includeBuild '../openflexo-csv'- Build with Gradle:
./gradlew build
- The adapter will be picked up automatically by OpenFlexo's technology adapter service
Here's a simple FML model that loads a CSV and does stuff with it:
use org.openflexo.ta.csv.CSVModelSlot as CSV;
import org.openflexo.ta.csv.model.CSVDocument;
import org.openflexo.ta.csv.model.CSVRow;
import org.openflexo.ta.csv.model.CSVCell;
public model MyModel {
public CSVDocument data with CSV::CSVModelSlot();
create(required Resource<CSVDocument> resource) {
connect data using parameters.resource;
// Add a new row
CSVRow newRow = CSV::AddCSVRow(
csvDocument=this.data,
rowIndex=100
) in data;
// Add cells to that row
CSVCell cell = CSV::AddCSVCell(
row=newRow,
columnIndex=0,
value="Hello World"
) in data;
// Query all rows
List<CSVRow> allRows = CSV::SelectCSVRow(
csvDocument=this.data
) from data;
log "Total rows: " + allRows.size();
}
}// In your FML script
csvDoc = load -r ["http://your.resource.center/path/to/file.csv"]
// Or in your model
connect csvData using parameters.resource;Creates a new row in the document.
CSVRow row = CSV::AddCSVRow(
csvDocument=this.data,
rowIndex=42 // optional - will append if not specified
) in data;Adds a cell to a row (or updates existing cell).
CSVCell cell = CSV::AddCSVCell(
row=myRow, // the CSVRow to add to
columnIndex=3, // which column
value="some value" // what to put in it
) in data;Alternate form using row index:
CSVCell cell = CSV::AddCSVCell(
csvDocument=this.data,
rowIndex=5,
columnIndex=2,
value="data"
) in data;Query rows from the document.
// Get all rows
List<CSVRow> allRows = CSV::SelectCSVRow(
csvDocument=this.data
) from data;
// Get specific row by index
List<CSVRow> rows = CSV::SelectCSVRow(
csvDocument=this.data,
rowIndex=5
) from data;Query cells from rows or the whole document.
// Get all cells from a specific row
List<CSVCell> cells = CSV::SelectCSVCell(
csvDocument=this.data,
csvRow=myRow
) from data;
// Get all cells in the document
List<CSVCell> allCells = CSV::SelectCSVCell(
csvDocument=this.data
) from data;csvDoc.hasHeader // boolean - does first row contain headers?
csvDoc.delimiter // string - usually ","
csvDoc.rows.size() // int - how many rowsrow.rowIndex // int - position in document (0-based header is always -1 if it exist)
row.cells // List<CSVCell> - all cells in this row
row.csvDocument // reference back to parent documentcell.value // string - the actual data
cell.columnIndex // int - which column (0-based)
cell.csvRow // reference back to parent rowCSV ModelSlot can be configured with:
public CSVDocument data with CSV::CSVModelSlot(
delimiter=";", // default: ","
encoding="UTF-8", // default: "UTF-8"
hasHeader=true // default: true
);create(required Resource<CSVDocument> csvFile) {
connect data using parameters.csvFile;
List<CSVRow> rows = CSV::SelectCSVRow(csvDocument=this.data) from data;
for (CSVRow row : rows) {
List<CSVCell> cells = CSV::SelectCSVCell(
csvDocument=this.data,
csvRow=row
) from data;
log "Row " + row.rowIndex + " has " + cells.size() + " cells";
}
}create(required Resource<CSVDocument> csvFile) {
connect data using parameters.csvFile;
// Get first row (after header)
List<CSVRow> rows = CSV::SelectCSVRow(
csvDocument=this.data
) from data;
if (rows.size() > 0) {
CSVRow firstRow = rows.get(1);//header is the 0 idx in the list of all rows
// Update a cell
CSV::AddCSVCell(
row=firstRow,
columnIndex=2,
value="Updated Value"
) in data;
}
}// Note: You'd need to create a CSVResource first
// This example assumes you have one
create(required Resource<CSVDocument> newCsv) {
connect data using parameters.newCsv;
// Add header row
CSVRow header = CSV::AddCSVRow(csvDocument=this.data, rowIndex=0) in data;
CSV::AddCSVCell(row=header, columnIndex=0, value="Name") in data;
CSV::AddCSVCell(row=header, columnIndex=1, value="Age") in data;
CSV::AddCSVCell(row=header, columnIndex=2, value="City") in data;
// Add data rows
CSVRow row1 = CSV::AddCSVRow(csvDocument=this.data, rowIndex=1) in data;
CSV::AddCSVCell(row=row1, columnIndex=0, value="Alice") in data;
CSV::AddCSVCell(row=row1, columnIndex=1, value="30") in data;
CSV::AddCSVCell(row=row1, columnIndex=2, value="NYC") in data;
CSVRow row2 = CSV::AddCSVRow(csvDocument=this.data, rowIndex=2) in data;
CSV::AddCSVCell(row=row2, columnIndex=0, value="Bob") in data;
CSV::AddCSVCell(row=row2, columnIndex=1, value="25") in data;
CSV::AddCSVCell(row=row2, columnIndex=2, value="SF") in data;
}Run the full test suite:
./gradlew :openflexo-csv:testOr just the core adapter tests:
./gradlew :openflexo-csv:csv-ta-test:testThe project includes 113+ passing unit tests covering:
- CSV Resource Management - loading, saving, resource lifecycle
- CSV Model Operations - document, row, and cell manipulation
- FML Integration - all edition actions and select operations
- CSV Converter - parsing, serialization, different formats
- Data Persistence - round-trip save/load verification
- Role Bindings - CSVDocumentRole, CSVRowRole, CSVCellRole
- FML Scripts - comprehensive end-to-end scenarios
There's a particularly good integration test in BasicModel.fml that demonstrates all the main operations in a realistic workflow - worth checking out if you're learning how to use the adapter.
Current Status: 113/120 tests passing (see KNOWN_ISSUES.md for details on the failing ones)
This adapter follows the same patterns as the Excel adapter (openflexo-xlsx). If you've worked with that, this should feel familiar.
- In-memory model - whole CSV loaded into memory (see performance notes in KNOWN_ISSUES.md)
- PAMELA framework - all model objects use PAMELA annotations
- Lazy loading - CSV resources load on first access
- Bidirectional relationships - cells know their rows, rows know their document, etc.
- OpenFlexo core (flexo-foundation, fml-parser, etc.)
- Apache Commons CSV (for parsing)
- PAMELA framework
Make sure the CSV technology adapter is loaded. Check the logs for:
Load CSV technology adapter as class org.openflexo.ta.csv.CSVTechnologyAdapter
Check that:
- The CSV file exists in your resource center
- The URI is correct (should be like
http://your.rc/path/to/file.csv) - The resource center is properly registered
Check delimiter and encoding settings on your CSVModelSlot. Default is comma-delimited UTF-8.
Make sure you're working with a writable resource center (not a JAR-based one).
Found a bug? Want to add a feature?
- Check KNOWN_ISSUES.md first
- Write a test case that demonstrates the issue
- Fix it
- Make sure all tests still pass
- Submit a PR
Please try to follow the existing code style and patterns.
Dual-licensed under:
- European Union Public License (EUPL) v1.1+
- GNU General Public License (GPL) v3+
See individual file headers for details.
Ping me (Mouad) or check the OpenFlexo documentation.
The implementation is based on the PAMELA framework and FML specifications - those docs are your friends if you want to understand how this works under the hood.
- Mouad, December 2025