epcEdit can run scripts in the Tcl language.
The system architecture of epcEdit is based on three main components: The user interface, the Tcl interpreter, and the widget library.
The epcEdit user interface contains the menus, dialog boxes, and much of the editing functionality for epcEdit. This user interface is implemented in the Tcl programming language.
An shell program for the Tcl programming language is used to execute the Tcl code. In addition to the basic functionality to run Tcl scripts, the Tcl in and
The TkSGML widget library extends the built-in widget classes of the Tcl interpreter by adding a new widget that is capable of loading, creating, modifying, and saving XML and SGML documents. When epcEdit is started (or when a new epcEdit window is opened), a new instance of the SGML widget is created. This widget is maintained by the TkSGML library in cooperation with the Tk components of the Tcl interpreter.
The epcEdit scripts are loaded into the Tcl interpreter when epcEdit is started. When epcEdit runs, these scripts send commands for evaluation to the Tcl interpreter that is running the script. The Tcl interpreter examines the commands and dispatches them to the appropriate component of the Tcl system. Many commands in the epcEdit scripts are sent to the TkSGML library for processing.
For example: If you select the menu item from the main menu, epcEdit will send the command element delete to the SGML widget. The SGML widget will in turn process the command and delete the element from the document.
Scripts for epcEdit are written in the Tcl programming language. In order to write a script, you will need some basic knowledge of Tcl and the TkSGML library. Depending on the task that your script must accomplish, you may need to use some widgets from the Tk widget set to provide a graphical user interface for your script.
The Tcl programming language is easy to learn; several excellent introductions to Tcl are available on the net and as printed books.
A script for epcEdit consists of Tcl commands and procedures that are stored in a script file. Script files for epcEdit reside in the extensions subdirectory underneath the epcEdit library directory.
When epcEdit is started (either by clicking on the epcEdit icon, by invoking the appropriate entry in the program menu, or by entering the epcedit command), it checks the extensions directory for the existence of files with the extension .tcl. Each Tcl file that is found is loaded into epcEdit during the initialization of the program. All Tcl-commands that are contained in the script files are evaluated and all procedures in the script files are defined in this early stage of the program startup process.
The files in the extensions directory are expected to contain Tcl Scripts that add new functionality to epcEdit by providing new features or installation-specific enhancements that are not part of the standard distribution. Because the extension scripts are executed when epcEdit is loaded, almost every aspect of epcEdit can be customized by extension scripts.
There is, however, a class of extension scripts that is particularly useful for augmenting the functionality of epcEdit: Interactive scripts that are run on demand by the user when the menu item in the menu is invoked.
The remainder of this section describes how such scripts are written and installed . It explains the process of writing, installing, and running an extension script by using a simple example script that creates and displays a list of all elements in the document and the number of times each element occurs in the document.
Interactive scripts contain a number of Tcl procedures that form the body of the script. These procedures are normal Tcl procedures that can perform any desired operation;epcEdit does not restrict the functionality of the Tcl procedures or the Tcl commands that are used inside these procedures: If you want to create a script that removes the complete content of the current document; epcEdit will not prevent you from doing so. On the other hand, if you want to write a script that connects to a database, executes a data base query, and inserts the result into the current document, then the rich functionality of epcEdit, the Tcl programming language, and the available Tcl extensions will allow your script to perform these tasks with a minimum of additional effort.
Interactive scripts are invoked when the user selects the script name from the dialog box that appears after choosing the Run Script menu item from the Extra menu. When the script is invoked, epcEdit calls the start procedure for this script.
Start procedures for interactive scripts are normal Tcl procedures (or commands) that are registered (together with the script name) when the extension is loaded. See Registering interactive scripts below for more details. Start procedures are invoked by epcEdit with a single argument. This argument is the name of the SGML widget for which the script is invoked.
The Tcl procedures and variables that you you define in your extension scripts should be contained in their own namespace to avoid conflicts with other procedures or variables that are defined by epcEdit or other extensions.
Ideally, each extension script should use its own namespace with the possible exception of closely related scripts that need to share a common code base or common variables.
To create a namespace (and initially populate it with the namespace variables that your script will use), your script should start with a namespace evalstatement. In our example, we will use the elementcount namespace for our variables and procedures. To create that namespace and an initial namespace variable, we start out script with the following statements:
namespace eval ::elementcount {
variable elements
}The elements variable that we create here will be used as an Tcl array that is indexed by element names. The values stored in this array will be the number of occurrences for each element.
When the script is invoked by epcEdit, it will call the start procedure that is registered for the script name. The start procedure will receive the name of an SGML widget as its only argument.
To start the element counting script, we will define the procedure
invokethat lives in the global namespace elementcount. The following code fragment implements this start procedure:
proc ::elementcount::invoke { widget } {
variable elements
if { [ info exists elements ] } { unset elements }
foreach name [ $widget element defined ] {
set elements($name) 0
}
countElements $widget [ $widget document element ]
showResults $widget
}The start procedure is prefixed by the namespace so that it will be created in the namespace elementcount. It declares the variable elements so that the namespace variable is referenced and unset in the following statement. The unset command removes any previous values that might still be stored in the elements variable if the variable still exists.
The
unsetcommand is followed by a foreach loop that iterates over the list of all defined elements and binds each element to the name variable. The list of defined elements is retrieved from the SGML widget with the element defined widget command. The available widget commands are described in the TkSGML widget reference manual that came with the epcEdit installation package. In the body of the foreach loop, the element array is initialized with a value of 0 for each element that is defined by the DTD.
After the elements array has been initialized, the countElements procedure is invoked with the SGML widget (as the first argument) and a handle for the top level element as the second argument. (The handle for the document is retrieved with the document element widget command.) The procedure countElements will be described in the next section.
Finally, after countElements has completed its work, the results will be displayed by the showResults command (described in section Displaying the results).
The workhorse of the element counting script is the countElements procedure. This procedure recursively descends the document tree and increments the appropriate field of the elements array for element that it encounters.
proc ::elementcount::countElements { widget handle } {
variable elements
set name [ $widget element name $handle ]
incr elements($name)
foreach part [ $widget element content $handle ] {
set type [ lindex $part 0 ]
if { $type == "E" } {
set child [ lindex $part 1 ]
countElements $widget $child
}
}
}After declaring the elements namespace variable, the procedure retrieves the name of the current element with the element name widget command. Using the element name as an index for the array, it increments the occurrence counter for elements with this name before it processes the content of the element.
To process the element content, the procedure retrieves the content of the current element using the element content widget command and loops over the list returned by this command, binding each element to the variable part.
Since each part of the content list is a list itself, the procedure extracts the type (which is the first element of each sublist) and compares it with the character E that designates embedded elements. If the current part of the content list describes an embedded element, the handle for the embedded element is extracted from the sublist and the procedure invokes itself recursively with the SGML widget and the handle for the embedded element.
After the countElements procedure has processed the document content, the results are available in the elements array. The showResults procedure opens a dialog window and displays the results in a list box inside this dialog.
proc ::elementcount::showResults { widget } {
variable elements
set dlg [ Dialog $widget.ecdlg -modal local ]
$dlg add -text "Ok" -command "destroy $dlg"
set f [ $dlg getframe ]
set lb [ listbox $f.lb ]
pack $lb
foreach name [ lsort [ array names elements ] ] {
$lb insert end [format "%5d %s" $elements($name) $name]
}
$dlg draw
}The procedure uses the BWidget package that is included in the epcEdit distribution to simplify dialog creation. It creates a modal dialog box (using the SGML widget as the parent window) and assigns the window name to the dlg variable. It then adds an button to the dialog. The command bound to the button will simply destroy the dialog.
A BWidget dialog contains a frame that should be used for the user-defined parts of the dialog. This frame is retrieved with the getframe widget command of the dialog and used as the parent window for a list box that will contain the results.
After the list box has been created, if is filled with the result from the elements array by retrieving a sorted lists of all keys in the array with the array names command. For each key, the number of occurrences (stored as the value of this key in the elements array) and the element name are formatted into a string which is added to the list box.
Finally, the dialog is drawn using the draw widget command for the dialog window. It will remain visible until the user presses the button.
The dialog box that displays the results is missing several useful components, e.g. a scrollbar for the list box. It is left as an exercise to the reader to add these components (for example by creating a ScrolledWindow from the BWidget package inside the user frame of the dialog).
To register the start procedure for an interactive script, the extension script must call the procedure ::extensions::register with two arguments: The name of the script and the name of the start procedure.
In the case of our example, the script name will be Count Elements and the name of the start procedure is ::elementcount::invoke. The name of the script can be freely chosen, but must be unique among all interactive scripts. The name of the start procedure, of course, is the name of the procedure that should be invoked when the script is started by the user.
For interactive scripts, it is normally the best approach to have the ::extension::register as the last command in the script file so that any errors that occur while the script is loaded will prevent the registration of the script.
To register our script as an interactive script, we put the following command on the last line of the file containing the script:::extension::register "Count Elements" ::elementcount::invoke
Note that this command is the only command that is directly executed when the script is loaded. All other Tcl commands occur inside procedures that are invoked when the script is run.
The complete script is shown below. Some comments have been added for the sake of readability.
namespace eval ::elementcount {
variable elements
}
# invoke --
# the procedure that is called when the user
# selects the script from the Run Script dialog.
proc ::elementcount::invoke { widget } {
variable elements
if { [ info exists elements ] } { unset elements }
foreach name [ $widget element defined ] {
set elements($name) 0
}
countElements $widget [ $widget document element ]
showResults $widget
}
#
# countElements --
# the recursive procedure that traverses the document
# tree and collect the occurrence information.
proc ::elementcount::countElements { widget handle } {
variable elements
set name [ $widget element name $handle ]
incr elements($name)
foreach part [ $widget element content $handle ] {
set type [ lindex $part 0 ]
if { $type == "E" } {
set child [ lindex $part 1 ]
countElements $widget $child
}
}
}
# showResults --
# the procedure that displays a dialog box with
# the results.
proc ::elementcount::showResults { widget } {
variable elements
set dlg [ Dialog $widget.ecdlg -modal local ]
$dlg add -text "Ok" -command "destroy $dlg"
set f [ $dlg getframe ]
set lb [ listbox $f.lb ]
pack $lb
foreach name [ lsort [ array names elements ] ] {
$lb insert end [format "%5d %s" $elements($name) $name]
}
$dlg draw
}
# here we register our start procedure
::extension::register "Count Elements" ::elementcount::invoke
# eofIn order to run your script, it must first be installed in epcEdit's script directory. For this purpose, just copy (or move) the file containing the script into the extensions directory that is located in the library directory of epcEdit.
On a Unix system, you will copy the script into the directory /usr/local/tksgml/lib/epcEdit-1.1/extensions. If you have installed epcEdit in a different location, you will need to copy the script into the directory lib/epcEdit-1.1/extensions relative to the directory in which you have installed epcEdit.
On a Windows system, the default extension directory is C:/Program files/Tcl/lib/epcEdit-1.1/extensions. If you have installed epcEdit into a different directory, you will need to copy the script into the directory relative to the directory in which you have installed epcEdit.
After copying (or moving) the script file into the extension directory, you will have to restart epcEdit in order to recognize the new extension script.
To run the script after it has been installed in the extensions directory, restart epcEdit, load a document, and select Extra > Run Script from the main menu. A dialog box will appear that contains the names of all registered scripts. Click on Count Elements, then press the OK button.
epcEdit will invoke the start procedure for your script and display the results in a dialog box. To terminate your script, simply click on the OK button in the dialog box.