package require csv
package require struct::matrix
::struct::matrix m
m add columns 2
set chan [open data.csv]
::csv::read2matrix $chan m
close $chan
lassign [m get row 0] header1 header2
for {set r 1} {$r < [m rows]} {incr r} {
puts -nonewline [format {%s = %-30s } $header1 [m get cell 0 $r]]
puts [format {%s = %s} $header2 [m get cell 1 $r]]
}
m destroy
I find that the easiest way to deal with csv data sets is by using a matrix
. A matrix
is sort of a two-dimensional vector with built-ins for searching, sorting and rearranging columns and rows.
First, create a matrix and call it m
. It will have two columns from the beginning, but no rows yet.
::struct::matrix m
m add columns 2
Open a channel to read the data file. Pass the channel and the matrix name to the ::csv::read2matrix
command. This command will read the csv data and create a matrix row for each data row. The data fields are stored in the columns.
set chan [open data.csv]
::csv::read2matrix $chan m
close $chan
To get the header strings, retrieve row 0.
lassign [m get row 0] header1 header2
To iterate over the data rows, go from 1 (if we didn't have headers, 0) to just under m rows
, which is the number of rows in the matrix.
There is a handy report
facility that works well with matrices, but I'll just use a for
loop here. I'm guessing how you want the data presented:
for {set r 1} {$r < [m rows]} {incr r} {
puts -nonewline [format {%s = %-30s } $header1 [m get cell 0 $r]]
puts [format {%s = %s} $header2 [m get cell 1 $r]]
}
If you're done with the matrix, you might as well destroy it.
m destroy
Solution for the specific problem in the comments.
package require csv
package require struct::matrix
::struct::matrix m
set chan [open foo.csv]
::csv::read2matrix $chan m , auto
close $chan
set f1 [m search column 0 "Result ID"]
set headerRow [lindex $f1 0 1]
set f2 [m search rect 0 $headerRow 0 [expr {[m rows] - 1}] ""]
set f3 [m search row $headerRow "Horizontal-1 Acc. Filename"]
set f4 [m search row $headerRow "Horizontal-2 Acc. Filename"]
set top [expr {$headerRow + 1}]
set bottom [expr {[lindex $f2 0 1] - 1}]
set left [lindex $f3 0 0]
set right [lindex $f4 0 0]
puts [format {Vector=[ %s ]} [concat {*}[m get rect $left $top $right $bottom]]]
m destroy
Obviously, you need to change the filename to the correct name. There is no error handling: in such a simple script it's better to just have the script fail and correct whatever went wrong.
Solution to the second problem, comments below:
package require csv
package require struct::matrix
::struct::matrix m
set chan [open _SearchResults.csv]
::csv::read2matrix $chan m , auto
close $chan
set f1 [m search column 0 {Result ID}]
set headerRow [lindex $f1 0 1]
set f2 [m search -glob rect 0 $headerRow 0 [expr {[m rows] - 1}] { These*}]
set numofRow [lindex $f2 0 1]
set headercol1 [m search row $headerRow { Horizontal-1 Acc. Filename}]
set headercol2 [m search row $headerRow { Horizontal-2 Acc. Filename}]
set indexheaderH1col [lindex $headercol1 0 0]
set indexheaderH2col [lindex $headercol2 0 0]
set rows [m get rect $indexheaderH1col [expr {$headerRow+1}] $indexheaderH2col [expr {$numofRow-1}]]
set rows [lmap row $rows {
lassign $row a b
list [string trim $a] [string trim $b]
}]
foreach row $rows {
puts [format {%-30s %s} {*}$row]
}
puts [format {Vector=[ %s ]} [concat {*}$rows]]
Comments:
- You don't need to set the number of columns if you use
read2matrix
with auto
- In this file, there is no empty cell after the table. Instead, we need to search for a string beginning with " These"
- Since each cell holds a space character followed by the value, we need to trim off space around the value, otherwise the concatenation will go wrong. The part with the
lmap
command fixes that
- Always brace your expressions
Documentation:
+ (operator),
- (operator),
< (operator),
chan,
close,
concat,
csv (package),
expr,
for,
format,
incr,
lassign,
lindex,
lmap (for Tcl 8.5),
lmap,
open,
package,
puts,
set,
struct::matrix (package),
{*} (syntax)