Candy Logo Copyright © 2025-2026 Candy Arts

Sanitization
Values should not start with syntax symbols, which is easy for you to avoid as a developer. Values that are meant to display in spoken text should also avoid matching Lexicon keywords when the intent isn't to format them as such. However, this can be harder to avoid at runtime, such as when your game prompts players to enter text somewhere, or for values that originate in external files, like edited saves or player-made Dialogues. For values entered by players at runtime, the risk is limited to the following: • Players writing the reference_symbol at the start of any text they enter. • Players writing the symbols for text variables, word substitution, text events, or lexicon escape at the start of values that will be displayed in a Spoken_Line. • Players accidentally writing a value that matches a Lexicon keyword. • Players writing the role_symbol at the start of a value that will become an Actor Reference. Note that the vardict_symbol and the role_symbol (default € and °) are not at risk in CandyRef expressions, as CandyRef ignores them inside string values. The other symbols simply have no meaning inside CandyRef expressions. The separator_symbol (default ¦) is not at risk in spoken text, as it only has meaning when inside the word substitution symbols (default •). This document provides several solutions you can use. Blacklist The default Input Menu provided with Candy Dialogue Engine contains code for blacklisting words or symbols. You could simply add all the symbols to the Blacklist, but that would have two limitations: • It would prevent using them anywhere inside input text, not just at the start. • It could be restricting if your symbols are characters that players could be expected to type. One way to avoid these restrictions is to create separate blacklist logic specifically for the first character in a text input. Shipping Symbols Sanitization neutralizes the syntax symbols in a value, so that the value is displayed and read exactly as written. The Engine identifies its syntax symbols (see Symbols) in every value it reads, wherever the value comes from. A value that contains them is treated like text written in a Dialogue: a text variable (¤) displays the value it names, and evaluates the expression it contains, BBCode tags format it, Text Event markers (‡) run their Events, Word Substitutions (•, ¦) are replaced, Lexicon keywords are formatted, a leading reference symbol (€, °, $) makes it a variable or a Role, and a leading ¢ makes the whole value an expression, evaluated instead of displayed. The template Input Menu sanitizes what the player types (see Input Menus). For any other value, call the functions of Candy_Sanitizer.gd (the CandySanitizer class, in Scripts/Classes). Sanitizing a Value CandySanitizer.sanitize(text) runs every sanitization, in the order they must run, and returns the sanitized value:
globals.player_name = CandySanitizer.sanitize(typed_name)
Sanitizations can be skipped: after the text argument, sanitize() accepts one boolean argument per sanitization, true by default, and false skips it. The arguments are, in order: text_variables, text_events, substitutions, bbcode, references, lexicon and expressions. For example, to preserve a value's BBCode and sanitize the rest:
CandySanitizer.sanitize(text, true, true, true, false)
The functions are static, so a Dialogue can call them in an expression too, for example in a Set command:
¢CandySanitizer.sanitize(globals.server_reply)
Sanitizations Each sanitization is a separate function, which can be called individually. A symbol is escaped by doubling it, as in a Dialogue:
FunctionWhat it does to the value
text_variables(text)Escapes the text variable symbols: they are displayed as written, and the expression between them is never evaluated.
text_events(text)Escapes the Text Event symbols: they are displayed as written, and no Event runs.
substitutions(text)Escapes the Word Substitution symbols: they are displayed as written.
bbcode(text)Escapes every [ as [lb]: tags are displayed as written.
references(text)Removes the reference symbols the value starts with (€, °, $, and any registered with CandyRef), so a value read as a speaker or an Actor Reference isn't read as a Role or a variable.
lexicon(text)Escapes the lexicon escape symbols, then encloses the whole value in them (¬Alice¬): the Lexicon never formats it. An empty value stays empty.
expressions(text)Escapes a leading ¢: the value is read as text, and never evaluated.
The Engine reads the value a text variable displays for text variables again, so that a value can display another value. A value from a player is therefore never safe without text_variables(): a name typed as ¤¢globals.gold¤ would be displayed as the player's gold, and an expression that calls a function would call it. Order sanitize() runs the sanitizations in the order of the table above. When calling them one by one, call them in the same order, for two reasons:
  • references() must run before lexicon(): once the value starts with the escape symbol, its leading reference symbol is no longer found.
  • lexicon() must run before expressions(): an escaped value doesn't start with ¢, so it's never evaluated, while a ¢ escaped inside the escaped value would be displayed twice.
Displaying a Sanitized Value The symbols a sanitization adds remain in the value. In Spoken Lines, the Engine reads and removes them, and it removes the lexicon escape symbols from the speaker's name too. Anywhere else, the value displays them: a plain Label displays [lb] and ¬Alice¬ as they are. To display a sanitized value in your own UI, remove the lexicon escape with CandySanitizer.remove_lexicon_escapes(text):
name_label.text = CandySanitizer.remove_lexicon_escapes(globals.player_name)
It returns "Alice" for ¬Alice¬, and "¬" for ¬¬. Compare a sanitized value the same way: globals.player_name == "Alice" is false once the name is ¬Alice¬. The other sanitizations only change a value that contained Candy Dialogue Engine syntax, so an ordinary name is restored whole by remove_lexicon_escapes(). Literal Variables A literal variable is a variable whose value the Engine reads as written, such as a name the player typed. Its value is stored without sanitization, so your game compares, saves and displays it exactly as typed, and the Engine protects it wherever it reads it: • An expression never evaluates a ¢ at the start of the value, nor of anything computed from it: ¢globals.player_name + "!" is text. • A text variable that displays the value, or anything computed from it, escapes its syntax symbols and BBCode: ¤¢globals.player_name¤ is displayed as typed. • A speaker's name read from it displays its BBCode as written. • A leading ° in the value isn't read as a Role where the value names an Actor. The literal variables are listed in literal_variables, in Literal_Variables.gd (User_Data/Data_Tables):
var literal_variables = [ "globals.player_name", 'candy_de.actors["Player_*"].DisplayName', ["globals.player_motto", ["substitutions"]], ]
The Engine identifies a variable by where its value is stored, not by how a reference is written: with globals.player_name listed, ¤¢globals["player_name"]¤ is protected too. Allowing Syntax An entry written as [variable, [allowed]] lets the value retain some syntax. The allowed names are:
NameWhat the value retains
expressionsA leading ¢ is evaluated.
text_variablesText variables are resolved.
substitutionsWord Substitutions are replaced.
text_eventsText Event markers run their Events.
lexiconLexicon keywords are formatted, and lexicon escapes are read.
bbcodeBBCode tags format the text.
referencesA leading ° is read as a Role where the value names an Actor.
The Engine reports an unknown name, or an entry it can't read, when it starts. Patterns and Filters A key in brackets can match several variables:
KeyMatches
["Player_*"]The keys matching the pattern: * stands for any characters, ? for one.
[String]The keys of that type: String, int, float...
[globals.slot]The key the expression gives, evaluated each time an expression reads a variable. Use expressions that only read values: a function called here is called that often.
Patterns and filters apply to Dictionary keys and Array indexes. An object's properties can only be named: globals.* isn't a valid entry. Adding Variables at Runtime The Input Menu's "Default_L" mode adds its variable to the list when it stores the input (see Input Menus). Your code can too, with CandySanitizer.add_literal_variable(variable, allowed). Variables added while the game runs are lost when it closes, unless your game saves the list. Variables written in the script are always listed.