2 # dbf package - read and write dbf file
6 # Read header of file and return list of fields. Also fill global array
7 # with name same as file handle
9 proc dbf_readheader {file} {
12 set rec [read $file 32]
13 binary scan $rec "cccciss" info(version) year month day info(reccount) info(offset) info(reclen)
14 set info(datestamp) [clock scan "$month/$day/$year"]
15 set info(fieldcount) [expr {($info(offset)-1)/32-1}]
17 for {set i 1} {$i<=$info(fieldcount)} {incr i} {
18 set rec [read $file 32]
19 binary scan $rec "a11ax4cc" info(name,$i) info(type,$i) info(len,$i) \
21 set info(ofs,$i) $recoffset
22 incr recoffset $info(len,$i)
28 # Opens DBF file with given name.
29 # Fills all neccesary information
30 # and returns file descriptor
32 proc opendbf filename {
34 if [catch {open $filename r+} res] {
35 return -code error -errorcode $errorCode $res
37 fconfigure $res -encoding cp866 -translation binary
43 # Position dbf file to given record
45 proc dbf_seek {file recno} {
47 if {![array exists info]||![info exists info(version)]} {
48 return -code error "No structure information exists for $file"
50 if {$recno>$info(reccount)||$recno<0} {
51 return -code error "No such record: $recno"
53 seek $file [expr {$info(offset)+$info(reclen)*(int($recno)-1)}]
59 proc dbf_getrec {file} {
61 if {![array exists info]||![info exists info(version)]} {
62 return -code error "No structure information exists for $file"
64 set rec [read $file $info(reclen)]
68 for {set i 1} {$i<=$info(fieldcount)} {incr i} {
71 set tmp [string trim [string range $rec $first $last]]
73 switch "$info(type,$i)" {
75 if [regexp {([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])} \
81 if ![string length $tmp] {
85 "L" { if [string length $tmp] {
86 set tmp [regexp {^[YyTt1]} $tmp]
90 set tmp [encoding convertfrom cp866 $tmp]
99 # checkdbf - checks if structure matches given list of fields and types
100 # Raises error and returns message if something doesn't match
102 proc checkdbf {file fieldlist typelist } {
105 foreach f $fieldlist t $typelist {
106 if {![info exists info(name,$i)]} {
107 return -code error "Not enough fields. $f expected"
109 if {"$info(name,$i)"!="$f"} {
110 return -code error "Field # $i doesn't match: $info(name,$i) while $f expected"
112 if {"$info(type,$i)"!="[lindex $t 0]"} {
113 return -code error "Field $f type mismatch $info(type,$i) found [lindex $t 0] expected."
115 if {$info(len,$i)!=[lindex $t 1]} {
116 return -code error "Field $f size mismatch $info(len,$i) found [lindex $t 1] expected."
118 if {"$info(type,$i)"=="N"&&[lindex $t 2]!=$info(dec,$i)} {
119 return -code error "Field $f decimal places mismatch $info(dec,$i) found [lindex $t 2] expected."
123 if {$i<=$info(fieldcount)} {
124 return -code error "Extra field $info(name,$i)"
130 # reccount - returns count of records
134 if ![info exist info(reccount)] {
135 return -code error "$f is not a dBase file"
137 return $info(reccount)
141 # fieldlist - return list of fields
143 proc fieldlist file {
146 for {set i 1} {$i<=$info(fieldcount)} {incr i} {
147 lappend list $info(name,$i)
153 # closedbf - closes file and frees data structure
155 proc closedbf {file} {
158 if ![info exists info] {
159 return -code error "$file is not a dBase file"
162 puts -nonewline $file [binary format i $info(reccount)]
163 if [catch {close $file} msg] {
164 return -code error -errorcode $errorCode $msh
166 uplevel #0 unset $file
170 # scandbf file script
171 # executes script for each record in the file, appending record number
172 # and all field values to it.
173 proc scandbf {file script} {
177 while {$n<=$info(reccount)} {
178 uplevel $script $n [dbf_getrec $file]
184 # dbf_addrec file value value value ...
186 proc dbf_addrec {file args} {
188 if ![info exists info] {
189 return -code error "$file is not a dBase file"
191 if {[llength $args] < $info(fieldcount)} {
192 return -code error -errorcode "Too not enouth fields in fieldlist" "Too not enouth fields in fieldlist"
194 seek $file [expr {$info(offset)+$info(reclen)*$info(reccount)}]
196 for {set i 1} {$i<=$info(fieldcount)} {incr i} {
197 set field [lindex $args [expr $i - 1]]
198 set len $info(len,$i)
199 set type $info(type,$i)
200 switch "$info(type,$i)" {
202 if {![catch {clock scan $field}]} {
203 append rec [clock format [clock scan $field] -format {%Y%m%d}]
205 return -code error -errorcode "Bad value $field in fieltype $type" "Bad value $field in fieldtype $type"
209 if {[regexp {(^[0-9\.]+$)|()} $field]} {
210 if {[string length $field] <= $len} {
211 append rec [format %#-${len}s $field]
213 append rec [format %#-.${len}s $field]
216 return -code error -errorcode "Bad value $field in fieltype $type" "Bad value $field in fieldtype $type"
220 if {[regexp {^[YyTt1]} $field]} {
221 append rec [format %#-1 [regexp {^[YyTt1]} $field]]
223 return -code error -errorcode "Bad value $field in fieltype $type" "Bad value $field in fieldtype $type"
227 if {[string length $field] <= $len} {
228 append rec [format %#-${len}s $field]
230 append rec [format %#-.${len}s $field]
235 puts -nonewline $file "[encoding convertto cp866 $rec]\x1A"
240 # delete record from dbf file
241 # dbf_delrec file recnumber recnumber ...
243 proc dbf_delrec {file args} {
245 if ![info exists info] {
246 return -code error "$file is not a dBase file"
248 if ![llength $args] {
252 if {![regexp {[0-9]} $rec]} {
253 return -code error "Not valid number recodr $rec"
255 if {$rec > $info(reccount)} {
256 return -code error "Cannot delete record $rec"
258 seek $file [expr {$info(offset)+$info(reclen)*($rec-1)}]
259 puts -nonewline $file "*"
264 # undelete record from dbf file
265 # dbf_undelrec file recnumber recnumber ...
267 proc dbf_undelrec {file args} {
269 if ![info exists info] {
270 return -code error "$file is not a dBase file"
272 if ![llength $args] {
276 if {![regexp {[0-9]} $rec]} {
277 return -code error "Not valid number recodr $rec"
279 if {$rec > $info(reccount)} {
280 return -code error "Cannot undelete record $rec"
282 seek $file [expr {$info(offset)+$info(reclen)*($rec-1)}]
283 puts -nonewline $file "\x20"
289 # createdbf filename fieldname fieldtype ?width? ?decimal? fieldname fieldtype ...
291 proc createdbf {filename args} {
292 if ![catch {glob $filename}] {
293 return -code error "File $filename alredy exists in current directory"
295 if [catch {open $filename w+} res] {
296 return -code error -errorcode $errorCode $res
298 fconfigure $res -encoding cp866 -translation binary
299 set year [string trimleft [clock format [clock seconds] -format %y] 0]
300 set month [string trimleft [clock format [clock seconds] -format %m] 0]
301 set day [string trimleft [clock format [clock seconds] -format %d] 0]
302 append header \x03 [binary format cccx28 $year $month $day]
306 while {$i < [llength $args]} {
307 set name [string toupper [lindex $args $i]]
308 set type [string toupper [lindex $args [expr {$i+1}]]]
310 append header [binary format a10xaicx15 $name $type $offset 8]
312 set offset [expr {$offset + 8}]
313 } elseif {$type == "C"} {
314 set width [lindex $args [expr {$i+2}]]
315 append header [binary format a10xaicx15 $name $type $offset $width]
317 set offset [expr {$offset + $width}]
318 } elseif {$type == "N"} {
319 set width [lindex $args [expr {$i+2}]]
320 set decimal [lindex $args [expr {$i+3}]]
321 append header [binary format a10xaiccx14 $name $type $offset $width $decimal]
323 set offset [expr {$offset + $width}]
324 } elseif {$type == "L"} {
325 append header [binary format a10xaicx15 $name $type $offset 1]
327 set offset [expr {$offset + 1}]
330 file delete $filename
331 return -code error -errorcode "Unknown field type $type" "Unknown field type $type"
336 puts -nonewline $res "$header\x0D"
338 puts -nonewline $res [binary format ss [expr {33+$n*32}] $offset]
343 package provide dbf "0.1"