Dbfreader is a tcl-only package to read dbase III files. It is used as a component in the tclshapefile package. It is based on code published by Vitus Wagner in the 1990ies. ---- # $Id: dbfreader.tcl,v 1.3 2012/01/02 16:17:40 joheid Exp $ # dbase III file reader package # returns the data as dict, keys are the record numbers # based on the original code of Vitus Wagner # rewritten as snit component # (C) Vitus Wagner, Joachim Heidemeier # published under the Tcl License # # public methods # readData read the datafile into the dict # # package require snit package provide dbasereader 0.5 snit::type dbasefile { # # daten as list of lists variable fData # array holding information like nrow ncol variable info variable fh "none" ;# filehandle, wird in OpenDbf gesetzt # # normally used as component option -partof # option -filename {} # encoding option -encoding -default iso8859-1 -validatemethod encodingAvailable # constructor {args} { set info(read) 0 $self configurelist $args } # method encodingAvailable {option value} { if {[lsearch -exact [encoding names] $value] == -1} { error "requested encoding $value not available" } } # # local methods # file == filehandle # method ReadHeader {} { fconfigure $fh -translation binary set rec [read $fh 32] binary scan $rec "cccciss" info(version) year month day info(nRow) info(offset) info(reclen) catch {set info(datestamp) [clock scan "$month/$day/[expr 1900 + $year]"]} set info(nCol) [expr {($info(offset)-1)/32-1}] set recoffset 1 for {set i 1} {$i<=$info(nCol)} {incr i} { set rec [read $fh 32] binary scan $rec "A11Ax4cc" info(name,$i) info(type,$i) info(len,$i) \ info(dec,$i) # # convert info(len) into unsigned int # set info(len,$i) [expr $info(len,$i) & 0xff] set info(ofs,$i) $recoffset incr recoffset $info(len,$i) } read $fh 1 } # # Opens DBF file with given name. # Fills all neccesary information # sets file descriptor # and reads file header # method OpenDbf {} { set info(read) 0 if {[catch {open $options(-filename)} res]} { return -code error "$res" } set fh $res $self ReadHeader return } # # Position dbf file to given record # method Dbfseek {recno} { if {![array exists info]||![info exists info(version)]} { return -code error "No structure information exists for $info(fh)" } if {$recno>$info(nRow)||$recno<0} { return -code error "No such record: $recno" } seek $fh [expr {$info(offset)+$info(reclen)*(int($recno)-1)}] } # # Get and decode next record # method GetNextRec {} { if {![array exists info]||![info exists info(version)]} { return -code error "No structure information exists for $info(fh)" } set rec [read $fh $info(reclen)] set first 1 set last 0 set reclist {} for {set i 1} {$i<=$info(nCol)} {incr i} { set len $info(len,$i) incr last $len set tmp [string trim [string range $rec $first $last]] incr first $len switch "$info(type,$i)" { "D" { if {[regexp {([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])} \ $tmp jnk y m d]} { set tmp "$m/$d/$y" } } "N" { if {![string length $tmp]} { set tmp 0 } } "L" { if {[string length $tmp]} { set tmp [regexp {^[YyTt1]} $tmp] } } "C" { set tmp [encoding convertfrom $options(-encoding) $tmp] } } lappend reclist $tmp } return $reclist } # # checkdbf - checks if structure matches given list of fields and types # Raises error and returns message if something doesn't match # method checkdbf {fieldlist typelist } { set i 1 foreach f $fieldlist t $typelist { if {![info exists info(name,$i)]} { return -code error "Not enough fields. $f expected" } if {"$info(name,$i)"!="$f"} { return -code error "Field # $i doesn't match: $info(name,$i) while $f expected" } if {"$info(type,$i)"!="[lindex $t 0]"} { return -code error "Field $f type mismatch $info(type,$i) found [lindex $t 0] expected." } if {$info(len,$i)!=[lindex $t 1]} { return -code error "Field $f size mismatch $info(len,$i) found [lindex $t 1] expected." } if {"$info(type,$i)"=="N"&&[lindex $t 2]!=$info(dec,$i)} { return -code error "Field $f decimal places mismatch $info(dec,$i) found [lindex $t 2] expected." } incr i } if {$i<=$info(nCol)} { return -code error "Extra field $info(name,$i)" } return "" } # # closedbf - closes file and frees data structure # method CloseDbf {} { if {(![info exists fh]) || ($fh ne $fh)} { return -code error "$info(fh) is not a dBase file" } if {[catch {close $fh} msg]} { unset fh return -code error -errorcode $errorCode $msh } } method AddRow {n datalist} { dict append fData $n $datalist } # # # readData # reads all files # and all field values to it. method readData {} { if {!$info(read)} { set fData [dict create] if {$fh eq "none"} {$self OpenDbf} $self Dbfseek 1 set n 1 while {$n<=$info(nRow)} { $self AddRow $n [$self GetNextRec] incr n } $self CloseDbf set info(read) 1 return 0 } else { return {file already read} } } # schreibt Spalten und Zeileninfos sowie Datum raus method dbfInfo {} { if {!$info(read)} { return {no dbf-file read} } else { set colInfo [list] for {set i 1} {$i <= $info(nCol)} {incr i} { lappend colInfo [list "col $i" $info(name,$i) $info(type,$i) $info(len,$i) \ $info(dec,$i)] } return [list $info(nRow) $info(nCol) $colInfo $info(datestamp) ] } } # return one or all records method data {{recnr all}} { if {"$recnr" eq "all"} { return $fData } elseif {[string is integer $recnr] && $recnr <= $info(nRow)} { return [dict filter $fData key $recnr] } else {error "Record $recnr not available"} } } # testscript if {0} { dbasefile d1 -filename [tk_getOpenFile] d1 readData set x [d1 dbfInfo] set y [d1 data] } ----