Data Sources — GDScript

VCE scope only. The study design names exactly three data sources: plain text (TXT), delimited (CSV) and XML files. JSON, databases (SQLite), REST APIs and Godot's own .tres/.res resources are out of scope — they do not earn source codes, however natural they feel in Godot.

Skill codes on this page

Code Level Skill
C644 7–8 uses appropriate data types, data structures and data sources — "including plain text, delimited and XML files"
C645 7–8 describes, in the internal documentation, why the selected data sources were used
C657 9–10 uses a range of data types, data structures and data sources
C658 9–10 explains, in the internal documentation, why the selected data types, data structures and data sources were used

Before you build: a data layer built on JSON, SQLite or .tres earns none of C644–C658, however natural those feel in Godot — and graders (human or AI) apply that rule mechanically. The sources you demonstrate must be TXT, CSV and/or XML.

The three sources

Definitions.

  • Plain text (TXT) file: a structured file that contains characters of readable data.

  • CSV: a comma-separated value file — a delimited file separated by commas. The delimiter is the character separating the values. A CSV is a flat table: rows, each row the same fields.

  • XML: Extensible Markup Language — data in elements marked by opening and closing tags. Elements can hold other elements, so the data is nested rather than flat: a tree with a root element at the top, and a prolog (version, encoding) before the content.

Know the difference: res:// is your project's read-only data; user:// is the writable save location. Naming that in validation is easy marks. club-ladder runs on exactly this split — the bundled season ships in res://seasons/, the player's copy lives at user://ladder.xml, and load_season() picks between them with one if FileAccess.file_exists(save_path).

TXT in Godot

# C644 — TXT source: write
var file = FileAccess.open("user://balance.txt", FileAccess.WRITE)
file.store_line(str(balance))
# C644 — TXT source: read
var file = FileAccess.open("user://balance.txt", FileAccess.READ)
balance = float(file.get_line())

CSV in Godot

FileAccess speaks CSV natively — get_csv_line() and store_csv_line() handle the delimiter for you.

# C644 — CSV source: read journeys.csv, one journey per row (zone,fare)
var journeys: Array[Dictionary] = []
var file = FileAccess.open("user://journeys.csv", FileAccess.READ)
file.get_csv_line()   # skip the header row
while not file.eof_reached():
    var row: PackedStringArray = file.get_csv_line()
    if row.size() >= 2:
        journeys.append({"zone": int(row[0]), "fare": float(row[1])})
# C644 — CSV source: write journeys.csv
var file = FileAccess.open("user://journeys.csv", FileAccess.WRITE)
file.store_csv_line(PackedStringArray(["zone", "fare"]))   # header
for journey in journeys:
    file.store_csv_line(PackedStringArray([str(journey["zone"]), str(journey["fare"])]))

In tile-matching-game (deck.gd) — the whole game is driven by one CSV read; each row becomes a record in an array:

# tile-matching-game — deck.gd
var file := FileAccess.open(path, FileAccess.READ)   # C644 — CSV source
var header := file.get_csv_line()   # skip the pair,text,image header
while not file.eof_reached():
    var row := file.get_csv_line()
    if row.size() < 2:
        continue
    cards.append({"pair": row[0].strip_edges(), "text": row[1].strip_edges()})

The real file (decks/spanish.csv):

csv
pair,text,image
dog,perro,
dog,dog,
cat,gato,
cat,cat,

Defensive touches worth copying: check FileAccess.file_exists(path) first, skip short rows, strip_edges() every field. And a Godot trap: .csv imports as a translation file by default — select the file, Import tab → Keep File (exported as is) → Reimport, or deck loading breaks in exported builds.

XML in Godot

Read XML with the built-in XMLParser. There is no built-in XML writer — either write your outputs as TXT/CSV, or build the XML text yourself as club-ladder does below.

<?xml version="1.0" encoding="UTF-8"?>
<fares>
    <fare zone="1" cost="5.30"/>
    <fare zone="2" cost="8.00"/>
</fares>
# C644 — XML source: read fares.xml into a Dictionary (zone → cost)
var fares: Dictionary = {}
var parser = XMLParser.new()
parser.open("res://data/fares.xml")
while parser.read() == OK:
    if parser.get_node_type() == XMLParser.NODE_ELEMENT \
            and parser.get_node_name() == "fare":
        var zone: int = int(parser.get_named_attribute_value("zone"))
        fares[zone] = float(parser.get_named_attribute_value("cost"))

Here <fares> is the root element and each <fare> is a child element carrying its data in attributes.

In club-ladder (season.gd) — XML both ways, in a studied project. Reading, with a _attribute() helper that falls back when a value is missing ("real files are often missing things"):

# club-ladder — season.gd
while parser.read() == OK:   # C644 — XML source
    if parser.get_node_type() != XMLParser.NODE_ELEMENT:
        continue
    match parser.get_node_name():
        "season":
            season.title = _attribute(parser, "title", "Season")
        "team":
            var team_name := _attribute(parser, "name", "")
            if team_name == "":
                continue   # a team with no name is not worth keeping
            season.teams.append(Team.new(team_name,
                int(_attribute(parser, "wins", "0")),
                int(_attribute(parser, "draws", "0")),
                int(_attribute(parser, "losses", "0"))))

And writing — the season builds the text line by line, escaping XML's five special characters first:

# club-ladder — season.gd
lines.append('<season title="%s">' % _escape(title))   # C644 — XML written by hand
for team in teams:
    lines.append('\t<team name="%s" wins="%d" draws="%d" losses="%d"/>'
        % [_escape(team.team_name), team.wins, team.draws, team.losses])
lines.append("</season>")
file.store_string("\n".join(lines) + "\n")

A team called "Smith & Sons" would corrupt the file without _escape() — and the & must be replaced first, or it would mangle the entities just added.

C644 — Uses appropriate types, structures and sources

Level 7–8 is the three layers working together: a file (source) parsed into a structure of typed values — like the CSV reader above (journeys.csv → Array[Dictionary] → int and float fields), labelled:

# C644 — CSV source → Array[Dictionary] structure → typed values

C645 — Why these data sources

Level 7–8 asks you to describe the reason — a sentence or two per source, in internal documentation:

# C645 — CSV for journeys: one journey per row with the same two fields;
# a flat table that also opens in Excel for checking

Described in the wild: tile-matching-game's README describes its source choice at exactly this level — the game "reads its cards from a CSV file, so the same game can be re-skinned for any subject … without changing a line of code." One sentence that says why CSV earns its place.

C657 and C658 — Range, and the full explanation

C657 (9–10): a range — several types, several structures, several sources, each doing a job the others could not. A Myki-app spread using only VCE sources: XML fare table in res:// (nested: zones × day types), CSV journey log in user:// (flat table), TXT save file in user:// (one balance value).

C658 (9–10): explains why — reasons that weigh alternatives:

# C658 — chose CSV for the journey log: flat table, one row per journey;
# XML would wrap every value in tags for no gain. Kept the fare table in
# XML: fares nest by zone then day type, and nesting is what XML does
# that CSV cannot. TXT for the save file: one value needs no structure.

Explained in the wild: club-ladder/season.gd's header comment explains its XML design by weighing the alternative — every value is an attribute because "one call gets each one", whereas nested <name> tags would mean "tracking which tag you are inside while text arrives separately". That is C658-grade reasoning, written where the marker will find it.

The verb ladder (outlines → identifies → describes → explains) is in the C06 home page.

Check Your Understanding

  1. Which sources earn C644–C658?
Answer

Only TXT, CSV and XML. JSON, SQLite and .tres earn nothing here.

  1. What reads XML in Godot, and what is missing?
Answer

XMLParser reads it; there is no built-in writer — build the text yourself, escaping & first.

  1. res:// vs user://?
Answer

res:// is the project's read-only data; user:// is the writable save location.

  1. You add fares.csv to your project and loading breaks in the exported build. First thing to check?
Answer

The Import setting — Godot treats .csv as a translation file by default. Set Import As: Keep File (exported as is) and reimport.