After exploring XML I am not yet ready to enter the XML realm. JSON, neh. I've never felt the rave for.
As I am dynamically creating HTML content, I wanted something based on a flatfile and something that would enable execution of TCL commands or be rigged to execute whatever based on placing the command to be executed. This introduces the %[command]% syntax.
I used % as an additional tag as I may have dialogue text where by [text may exist within] which I don't want executed as a command.
With this, it enables you to dynamically call a function, retrieve from dictionaries and the likes when rendering parses the file. In this example, I have designed it with elements (easy extensible) one: a fully fledged HTML button, two: text, three: icon.
Which are both assigned to a key; so lets start.
Firstly, we create a prompt script like below
# Texts
# Key | Type (of element) | Icon (to be displayed next to the text) | Text to display
{"%_title_%"}, {"text"}, {""}, {"The example"}
{"%_synopsis_%"}, {"text"}, {""}, {"of an Text File Parser"}
#Buttons
# Key | Type (of element) | Button Name | CSS Class | formaction (url to post to) | value of the button | button text
{"%_button_%"}, {"button"}, {"my_tcl_version"}, {"tcl"}, {"/tcl"}, {"%[info tclversion]%"}, {"TCL Version: %[info tclversion]%"}
# Icons
# Key | Type (of element) | Icon
{"%_icon_%"}, {"icon"}, {"📜"}Save it somewhere, as we will be calling it within the code below.
Now that we have our prompt file. We can feed it in to our parser: [read_prompt "script"]
proc morphSet {data elements} {
variable morph
# loop for the amount of elements in our data
for { set i 0 } { $i < [llength $data] } {incr i} {
# Set the dictionary to the name of our element
# Strip the {" and "} from our line_data
# Store that to the dictionary
dict set morph [lindex $elements $i] [string range [lindex $data $i] 2 end-2]
} ;# end for
} ;# end proc
proc setDict {data} {
variable morph ;# Make our dictionary visible to others
set morph [dict create] ;# Create our dictionary
# Strip the {" and "} from our line_data of element 1
set element_type [string range [lindex $data 1] 2 end-2]
# switch $element_type
# Switches between elements
# Cases: button, text, icon
# construct a list of elements to dictonarise
# store the line elements to the elements
# pass to our dictionary creation procedure
switch $element_type {
button {
set elements [list element_key \
element_type \
element_name \
element_class \
element_formaction \
element_value \
element_text]
morphSet $data $elements
}
text {
set elements [list element_key \
element_type \
element_icon \
element_text]
morphSet $data $elements
}
icon {
set elements [list element_key \
element_type \
element_icon]
morphSet $data $elements
}
} ;#end switch
} ;# end proc
proc generateElement {args} {
set options {} ;# Initialize an empty dictionary
foreach {key value} $args {
set cleanKey [string trimleft $key "-"] ;# Remove leading "-"
dict set options $cleanKey $value ;# Create our key and value
}
switch [dict get $options type] {
button {
set button {<button name="[dict get $options name]" class="[dict get $options class]" formaction="[dict get $options formaction]" value="[dict get $options value]">[dict get $options text]</button>}
return [string trim [subst "$button"]]
}
text {
set text {[dict get $options icon] [dict get $options text]}
return [string trim [subst "$text"]]
}
icon {
set icon {[dict get $options icon]}
return [string trim [subst "$icon"]]
}
} ;# end switch
} ;# end proc
proc evalCode { element data } {
variable morph
set pairs {}
set startIndex 0
while {1} {
# Get the index of the opening delimiter [% starting from startIndex
set startPos [string first "%\[" $data $startIndex]
# Find the closing delimiter %] starting after the [% index
set endPos [string first "\]%" $data [expr {$startPos + 1}]]
# switch $startPos
# Case: -1
# We don't have a valid position
# Break
# Case: default
# We located our starting delimiter %[
# Create switch
#
# switch $endPos
# Case: -1:
# We don't have an ending delimiter
# Break
# Case: default
# We have our ending delimiter
# Create our interpreter switch
#
# switch [string range $tcl_cmd 0 1]
# Scan the first two characters of our command
# Allows us to create custom actions based on the first two
# Case: default
# Create our interpreter as safe
# store our eval of our command in interpreter
# Delete our interpreter
# Replace the data in the string with the returned result
switch $startPos {
-1 { break }
default {
switch $endPos {
-1 { break }
default {
set tcl_cmd [string range $data $startPos+2 $endPos-1]
# With the switch below can check the first two characters
# Execute whatever based upon
# Reserved for future use
switch [string range $tcl_cmd 0 1] {
default {
#####
## Create our safe-interp
set interp [interp create -safe]
# Expose our commands
# interp alias $interp info {} info
## Execute our command in interpreter
set cmd_result [$interp eval $tcl_cmd]
## Destroy
# puts "[interp slaves] :: slaves"
interp delete $interp ;# Tidy Up
# puts "[interp slaves] :: slaves after tidyup"
#####
set data [string replace $data $startPos $endPos+1 "$cmd_result"]
}
}
}
}
}
} ;# end switch
} ;# end while
dict set morph $element "$data"
} ;# end proc
proc composePrompt { } {
variable morph ;# Gain access to our dictionary
# This works in conjunction with our PromptFile
# Are we a button, text? icon?
# Obtain this from the second element which defines what element we are
# Switch: $type
# Switch to generate our HTML element types
# Case: button
# Setup the HTML button string
# Case: text
# Setup the HTML text string
# Case: icon
# Setup the HTML icon string
set type "[dict get $morph element_type]"
##
switch $type {
button {
set element_key [dict get $morph element_key]
evalCode "element_text" [dict get $morph element_text]
evalCode "element_value" [dict get $morph element_value]
##
set element [generateElement \
-name "[dict get $morph element_name]" \
-type "[dict get $morph element_type]" \
-class "[dict get $morph element_class]" \
-formaction "[dict get $morph element_formaction]" \
-value "[dict get $morph element_value]" \
-text "[dict get $morph element_text]"]
return [list $element_key [string trim "$element"]]
}
text {
set element_key [dict get $morph element_key]
evalCode "element_text" [dict get $morph element_text]
##
set element [generateElement \
-type "[dict get $morph element_type]" \
-icon "[dict get $morph element_icon]" \
-text "[dict get $morph element_text]"]
return [list $element_key [string trim "$element"]]
}
icon {
set element_key [dict get $morph element_key]
##
set element [generateElement \
-type "[dict get $morph element_type]" \
-icon "[dict get $morph element_icon]"]
return [list $element_key [string trim "$element"]]
}
default { return }
} ;#end switch
} ;# end procedure
proc read_prompt { file_input } {
variable morph ;# Get access to our dictonary
# Open our file
set file [open "$file_input" r]
while { [gets $file data] >= 0 } {
# Split each line by a comma
set parts [string trimleft [split $data ","]]
# Get the first occurrence of our {
set startPos [string first "{" $data]
# Get the last occurrence of our }
set endPos [string last "}" $data]
# Create an empty list to store our data parts
set line_data {}
switch $startPos {
0 {
for { set i 0 } { $i < [llength $parts] } {incr i} {
lappend line_data [string trim [lindex $parts $i]]
} ;# end for
}
} ;# end switch
setDict $line_data ;# Create our dictionary based on our line_data
# Switch $morph
# is the length of the key is greater than 0, compose our prompt
# Case: 1
# the morph is not empty, has data, append to a list our composed prompt result
switch [expr {[llength $morph] > 0}] {
1 { lappend myPrompt [composePrompt] }
} ;# end switch
} ;# end while
close $file;
# puts "$myPrompt :: elements"
return $myPrompt
} ;# end proc
puts [read_prompt "script"]Result
{%_title_% {The example}}
{%_synopsis_% {of an Text File Parser}}
{%_button_% {<button name="my_tcl_version" class="tcl" formaction="/tcl" value="9.0">TCL Version: 9.0</button>}}
{%_icon_% {📜}}Add a string map,
set template {%_icon_% %_title_% %_synopsis_%
Coded with: %_button_%}
foreach item [read_prompt "script"] { lappend mapList [lindex $item 0] [lindex $item 1] }
puts [string map $mapList $template]And now you have something abstract like this:
📜 The example of an Text File Parser Coded with: <button name="my_tcl_version" class="tcl" formaction="/tcl" value="9.0">TCL Version: 9.0</button>