tDOM and Coroutines

AMG: Coroutines are especially well suited to parsing XML in SAX mode, i.e. as a stream of events. Here's one way to do it with tDOM:

# parseXmlFile --
# Parses an XML file using the supplied event dispatch table.
# Source: https://wiki.tcl-lang.org/49298
proc parseXmlFile {dispatch type input args} {
    # Determine the parse method.
    set method [dict get {
        -file parsefile -channel parsechannel -data parse
    } [tcl::prefix match {-file -channel -data} $type]]

    # Create parser coroutine.
    for {set i 0} {[info commands [set coro ::Parse$i]] ne {}} {incr i} {}
    coroutine $coro apply {{Dispatch} {
        while {1} {
            # Yield to the Expat parser, which will invoke this coroutine again
            # the next time a registered event occurs.  The return value of
            # [yieldto] will be the event type and arguments.
            set Args [lrange [set Key [yieldto return -level 0]] 1 end]

            # Run all applicable event handlers, starting with most specific.
            for {} {$Key ne {}} {set Key [lreplace $Key end end]} {
                if {[dict exists $Dispatch $Key]} {
                    lassign $Args {*}[lindex [dict get $Dispatch $Key] 0]
                    eval [lindex [dict get $Dispatch $Key] 1]
                }
            }
        }
    }} $dispatch

    # Create Expat parser and glue to the parser coroutine.
    set parser [expat]
    foreach event [lsort -unique [lmap i [dict keys $dispatch] {lindex $i 0}]] {
        if {[string index $event 0] eq "-"} {
            $parser configure $event [list $coro $event]
        }
    }

    # Enable TNC to automatically validate against DTD schema.
    catch {tnc $parser enable}

    # Initialize parser coroutine.
    if {[dict exists $dispatch initialize]} {
        $coro initialize $input {*}$args
    }

    # Run Expat parser, which will exercise the coroutine.
    $parser $method $input
    $parser free

    # Finalize parser coroutine.
    if {[dict exists $dispatch finalize]} {
        return [$coro finalize]
    } else {
        rename $coro {}
    }
}

Examples to come.