zip

Difference between version 15 and 16 - Previous - Next
**File format**[[...]]
*****Compress/Decompress strings***


[HaO] 2020-11-24:
TCL 8.6 has the [zlib] command to compress or decompress strings using the zip methods.

*****Read/Write zip files*****

To read or write zip-files, one may use the [TCLLIB] modules [zipfile::mkzip] and [zipfile::decode] as follows:
<<discussion>>
======
# zip files in folder "config" to file test.zip
package require zipfile::mkzip
zipfile::mkzip::mkzip test.zip -directory config
# unzip file test.zip to folder config
package require zipfile::decode
::zipfile::decode::open test.zip
set archiveDict [::zipfile::decode::archive]
::zipfile::decode::unzip $archiveDict config
::zipfile::decode::close
======
<<enddiscussion>>

*****Tools for zip handling*****

[Steve Cassidy]:  "[Trf] lets you zip up a bit of data but doesn't provide the machinery to zip
directories or files.  mkZipLib [http://mkextensions.sourceforge.net/]
provides such an interface as does zvfs
[http://www.hwaci.com/sw/tobe/zvfs.html].  More up to date is the zipvfs
package which is part of [tclvfs] but that doesn't seem to be working cleanly
yet and requires tcl8.4 features."

http://www.equi4.com/critlib/zipper.README
http://www.equi4.com/critlib/zlib.README

See also [Using a zip file as a Tcl Module].

See also [Using zipper to create zip files].

Also, explain pkware vs. winzip vs. zLibDll.

***ZIP reader class***
[DKF]: Here's a little [TclOO] class to read from ZIP files (it only handles very basic cases, but that's good enough for what I wanted it for):
<<discussion>>
======
package require Tcl 8.6

oo::class create Zip {
    variable comment directory fd
    constructor {filename} {
        set fd [open $filename rb]
        my ReadDirectory
    }
    destructor {
        close $fd
    }

    method ReadDirectory {} {
        set off -22
        while 1 {
            seek $fd $off end
            binary scan [read $fd 4] i sig
            if {$sig == 0x06054b50} {
                seek $fd $off end
                break
            }
            incr off -1
        }
        binary scan [read $fd 22] issssiis sig disk cddisk nrecd nrec \
                dirsize diroff clen
        if {$clen > 0} {
            set comment [read $fd $clen]
        } else {
            set comment ""
        }
        if {$disk != 0} {
            error "multi-file zip not supported"
        }
        seek $fd $diroff
        for {set i 0} {$i < $nrec} {incr i} {
            binary scan [read $fd 46] issssssiiisssssii \
                sig ver mver flag method time date crc csz usz n m k d ia ea \
                off
            if {$sig != 0x02014b50} {
                error "bad directory entry"
            }
            set name [read $fd $n]
            set extra [read $fd $m]
            if {$k == 0} {
                set c ""
            } else {
                set c [read $fd $k]
            }
            set directory($name) [dict create timestamp [list $date $time] \
                    size $csz disksize $usz offset $off method $method \
                    extra $extra comment $c]
        }
    }

    method names {} {
        lsort [array names directory]
    }
    method comment {{name {}}} {
        if {$name eq ""} {
            return $comment
        }
        return [dict get $directory($name) comment]
    }
    method info {name {field ""}} {
        if {$field ne ""} {
            return [dict get $directory($name) $field]
        }
        return $directory($name)
    }
    method contents {name} {
        dict with directory($name) {}
        seek $fd $offset
        binary scan [read $fd 30] isssssiiiss sig - - - - - - - - nlen xlen
        if {$sig != 0x04034b50} {
            error "not a file record"
        }
        seek $fd [expr {$nlen + $xlen}] current
        set data [read $fd $size]
        if {[string length $data] != $size} {
            error "read length mismatch: $size expected"
        }
        if {$method == 0} {
            return $data
        } elseif {$method == 8} {
            return [zlib inflate $data]
        } else {
            error "unsupported method: $method"
        }
    }
}
======
An example of use:
======
set z [Zip new [lindex $argv 0]]
if {$argc > 1} {
    puts Comment:[$z comment [lindex $argv 1]]
    puts Contents:\n[$z contents [lindex $argv 1]]
} else {
    puts Comment:[$z comment]
    foreach n [$z names] {
        puts "$n ([$z info $n disksize] bytes)"
    }
}
$z destroy
======<<enddiscussion>>

----
**Functional operation**
[NEM] zip is also the name of another [functional programming] classic. You can think of it as a
function that takes a bunch of "columns" (think relational) and returns a list of "rows":

======
proc zip {cola colb} {
    set ret [list]
    foreach a $cola b $colb { lappend ret [list $a $b] }
    return $ret
}
zip {1 2 3} {a b c} ;# returns {{1 a} {2 b} {3 c}}
======

You can generalise zip to zipWith which applies an arbitrary function on each pair of values from
the columns:

======
proc apply {func args} { uplevel #0 $func $args }
proc zipWith {f cola colb} {
    set ret [list]
    foreach a $cola b $colb { lappend ret [apply $f $a $b] }
    return $ret
}
interp alias {} zip {} zipWith list
======

You could further generalise the function to take an arbitrary number of columns. I'll leave that,
and the reverse unzip operation as exercises. See also [fold], [filter], [iterators] and [map].

''[Lars H]: Isn't that more commonly known as "transposing"? See [Transposing a matrix].''

[NEM]: Depends who you ask. `zipWith` is more general, though.

[AMG]: "list" isn't a valid lambda, so it's not directly usable with the Tcl 8.5 [[[apply]]] command.  Here's an 8.5-compatible version.  It uses single-argument [[[lindex]]] instead of [[[list]]] to avoid adding an extra level of list nesting.  (Single-argument [[lindex]] simply returns its argument, even if its argument isn't a valid list.)

======
interp alias "" zip "" zipWith {{args} {lindex $args}}
======

----
[AMG]: Implementation using [lcomp]:

======
proc zip {cola colb} {
    lcomp {[list $a $b]} for a in $cola and b in $colb
}
======

Here's a version that handles an arbitrary number of columns:

======
proc zip {args} {
    if {[llength $args]} {
        for {set i 0} {$i < [llength $args]} {incr i} {
            append expression " \$$i"
            lappend operations and $i in [lindex $args $i]
        }
        lset operations 0 for
        lcomp \[list$expression\] {*}$operations
    }
}
======

Also, I'll take your unzip challenge. ;^)

======
interp alias "" unzip "" zip
======

As [Lars H] pointed out, zip is transpose, so using it twice gives back the original input.

Examples:

======
% zip {a 1} {b 2} {c 3}
{a b c} {1 2 3}
% unzip {a b c} {1 2 3}
{a 1} {b 2} {c 3}
======

<<categories>> Compression | Functional Programming