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>