jay
jay

Reputation: 79

Parse a CSV file to TCL

I have a file as below:

a, b, c, d, e
S, 1.0, 100, F, fast
T, 2.0, 200, S, slow

First ROW is header only (a, b, c, d, e) and 2nd, 3rd row is the value (S, 1.0, 100, F, fast) correspond to the header.

I would like to read the file below into tcl and puts out the values (ie: row 2, column 5 -> fast)

I wrote the below script but doesnt seem to work:

proc game {name infile outfile} {

    set csv [open $infile r]
    set csv_lines [read $csv]
    set out [open $outfile w]
    set info [split $csv "\n"]

    set infocount [llength $info]
    set line 1

    foreach line $info {
        set values [split $line ","]
        set firstline [lindex $values 0]
        set secondline [lindex $values 1]


    ### HOW DO I PUTS OUT ROW2 COL5 or ROW1 COL3 ###
       puts $outfile "$firstline"
    }

    close $infile
    close $outfile
}

Want outfile to be as below:

a: S b: 1.0 c: 100 d: F e: fast
a: T b: 2.0 c: 200 d: S e: slow

or

a: T b: 2.0 c: 100 d: F e: slow
a: S b: 1.0 c: 200 d: F e: fast

Upvotes: 1

Views: 2167

Answers (3)

Shawn
Shawn

Reputation: 52539

Using the csv package from tcllib is the way to go for robustness, but on trivial data like this, split will work.

#!/usr/bin/env tclsh

proc game {name infile outfile} {
    set in [open $infile r]
    set out [open $outfile w]
    set header [split [gets $in] ,]
    while {[gets $in line] > 0} {
        foreach col $header val [split $line ,] {
            puts -nonewline $out "$col: $val "
        }
        puts $out ""
    }
    close $in
    close $out
}

game foo input.csv output.txt

Upvotes: 3

glenn jackman
glenn jackman

Reputation: 247012

You might do:

package require csv

proc splitline {fh} {
    if {[gets $fh line] != -1} {
        set fields [csv::split $line]
        return [lmap field $fields {string trimleft $field}]
    }
}

proc transform {file} {
    set fh [open $file r]
    set head [splitline $fh]
    while {[set fields [splitline $fh]] ne ""} {
        puts [join [lmap h $head f $fields {string cat $h ":" $f}]]
    }
    close $fh
}

transform "file.csv"
a:S b:1.0 c:100 d:F e:fast
a:T b:2.0 c:200 d:S e:slow

Upvotes: 2

Jerry
Jerry

Reputation: 71578

You could use a dict to store the data of the csv file:

proc game {name inFile} {
    upvar csv_data csv_data        
    set csv [open $inFile r]
    set csv_lines [read $csv]

    set row 0

    foreach line [split $csv_lines "\n"] {
        set values [split $line ","]
        for {set col 0} {$col < [llength $values]} {incr col} {
            dict set csv_data $row [expr {$col+1}] [string trim [lindex $values $col]]
        }
        incr row
    }

    close $csv
}

set csv_data {}
game foo input.csv

Now you can read from the dict like the below, where row 0 contains the headers, and col 1 is the one with a as header:

# To get row 2 col 5:
puts [dict get $csv_data 2 5]
# => slow

# To get row 1 col 3:
puts [dict get $csv_data 1 3]
# => 100

To print in the other format you asked, you'll need to do a little more work:

set outFile [open output.txt w]
for {set row 1} {$row < [llength [dict keys $csv_data]]} {incr row} {
    set lineOut ""
    foreach {- header} [dict get $csv_data 0] {- value} [dict get $csv_data $row] {
        lappend lineOut "$header: $value"
    }
    puts $outFile [join $lineOut " "]
}
close $outFile

output.txt:

a: S b: 1.0 c: 100 d: F e: fast  
a: T b: 2.0 c: 200 d: S e: slow

Upvotes: 2

Related Questions