transform

A channel transform is a mapping applied at the byte-level to a channel. It operates on bytes rather than strings. Tcl converts to/from the channel's encoding at a higher level (closer to the script level) than transforms operate at.

See Also

reflected channel
the Tcl mechanism used by channel transforms.

Examples

Trf
Profides transformations for compression, charset recording, error correction, and hash generation.
TLS
uses transforms internally.

Description

A transform(ation) can be implemented as a stacked channel .

PT: Here is an example of a base64 channel transform using Tcl 8.6 features. You push this transform onto any other sort of channel and it will base64 encode the data as it passes through.

# A base64 channel transform.
#
# usage:  chan push $channel ::tcl::binary::transform_base64
#
package require Tcl 8.6

proc ::tcl::binary::K {a b} {set a}

proc ::tcl::binary::transform_base64 {cmd handle args} {
    upvar #0 [namespace current]::_transform_r_$handle rstate
    upvar #0 [namespace current]::_transform_w_$handle wstate
    switch -exact -- $cmd {
        initialize {
            set rstate {}; set wstate {} 
            return [list initialize finalize read write flush drain]
        }
        finalize {
            unset rstate wstate
            return
        }
        write {
            foreach buffer $args break
            append wstate $buffer
            set len [string length $wstate]
            set end [expr {$len - ($len % 3)}]
            set edge [expr {$end - 1}]
            set res [binary encode base64 [string range $wstate 0 $edge]]
            set wstate [string range $wstate $end end]
            return $res
        }
        flush {
            set buffer [K $wstate [set wstate {}]]
            set res [binary encode base64 $buffer]
            return $res
        }
        read {
            foreach buffer $args break
            append rstate $buffer
            set len [string length $rstate]
            set end [expr {$len - ($len % 4)}]
            set edge [expr {$end - 1}]
            set res [string range $rstate 0 $edge]
            set rstate [string range $rstate $end end]
            set resx [binary decode base64 $res]
            if {[string length $resx] % 3 != 0} {
                puts stderr "alert: $res"
            }
            return $resx
        }
        drain {
            set res [K [binary decode base64 $rstate] [set rstate {}]]
            return $res
        }
    }
}

The above channel transform will not wrap lines, so you end up with a file with one line of base64 encoded data. It is more common to see such files broken into lines every 60 characters or so. As channel transforms are stackable we can choose to have a second transform deal with writing such files.

proc ::tcl::binary::maxlen {maxlen wrapchar cmd handle args} {
    upvar #0 [namespace current]::_maxlen_$handle state
    switch -exact -- $cmd {
        initialize {
            set state {} 
            return [list initialize finalize write flush read]
        }
        finalize {unset state}
        write {
            foreach data $args break
            append state $data
            set mark 0
            set edge [expr {$maxlen - 1}]
            set res {} 
            while {([string length $state] - $mark) > $maxlen} {
                append res [string range $state $mark $edge]$wrapchar
                incr mark $maxlen
                incr edge $maxlen
            }
            set state [string range $state $mark end]
            return $res
        }
        flush {return [K $state [set state {}]]}
        read {return [lindex $args 0]}
    }
}

This transform buffers the output data, injecting wrapchar every maxlen characters. To use this, we first stack this transform and then the base64 transform onto the primary channel. For instance:

set f [open $filename w]
chan push $f [list ::tcl::binary::maxlen 60 \n]
chan push $f [list ::tcl::binary::transform_base64]
puts $f $data
seek $f 0
read $f
close $f

Lars H: Here is a rewrite of the above maxlen as an ensemble with parameters:

namespace eval ::tcl::binary::maxlen {
    namespace export initialize finalize write flush read
    namespace ensemble create -parameters {maxlen wrapchar}
    proc initialize {maxlen wrapchar handle mode} {
        variable $handle {} 
        return [namespace export]
    }
    proc finalize {maxlen wrapchar handle} {
        unset [namespace current]::$handle
    }
    proc write {maxlen wrapchar handle data} {
        namespace upvar ::tcl::binary::maxlen $handle state
        append state $data
        set mark 0
        set edge [expr {$maxlen - 1}]
        set res {} 
        while {([string length $state] - $mark) > $maxlen} {
            append res [string range $state $mark $edge]$wrapchar
            incr mark $maxlen
            incr edge $maxlen
        }
        set state [string range $state $mark end]
        return $res
    }
    proc flush {maxlen wrapchar handle} {
        namespace upvar ::tcl::binary::maxlen $handle state
        return $state[set state {}]
    }
    proc read {maxlen wrapchar handle data} {return $data}
}