Arun Palanisamy
Arun Palanisamy

Reputation: 5459

Setting String with comma into a single variable in CSV file using TCL

I'm having a Csv file like below:

Production,FALSE,Other Line,Release,UOF-919 BASE,3A001A11,9X999,PC,"Jap,Ind",006

And i'm reading and setting the values to a variable using tcl like below:

set fileIn [open "C:/myfile.csv" r]

        while {[gets $fileIn sLine] >= 0} {
            set lsLine [split $sLine ","]
            set sType "Hardware"
            set sName [lindex $lsLine 1]
            set Sdev [lindex $lsLine 2]
            set spara [lindex $lsLine 3]
            set sDescription [lindex $lsLine 4]
            set sManage [lindex $lsLine 5]
            set sconnect [lindex $lsLine 6]
            set sUOM [lindex $lsLine 7]
            set sCountry [lindex $lsLine 8]

            #my operations
            }
    flush $fileId
    close $fileId
    }

Here i'm not able to set "Jap,Ind" to sCountry because it already has one more comma inside the quotes. Can anybody help me to set that? I'm new in TCL.

Upvotes: 0

Views: 516

Answers (3)

Jerry
Jerry

Reputation: 71538

You can use the csv package (it is a package that has been included in the default libraries for a while now):

set fileIn [open "C:/myfile.csv" r]
package require csv
while {[gets $fileIn sLine] >= 0} {
    set lsLine [::csv::split $sLine]  # I'd use the -alternate 
                                      # switch if you can have empty elements
    set sType "Hardware"
    set sName [lindex $lsLine 0]
    set Sdev [lindex $lsLine 1]
    set spara [lindex $lsLine 2]
    set sDescription [lindex $lsLine 3]
    set sManage [lindex $lsLine 4]
    set sconnect [lindex $lsLine 5]
    set sUOM [lindex $lsLine 6]
    set sPin [lindex $lsLine 7]
    set sCountry [lindex $lsLine 8]

    #my operations
}

flush $fileId
close $fileId

Note that I also changed the indices. Tcl lists are 0-based, meaning that the first element of a list has the index 0. [lindex $lsLine 0] thus gives the first element from the list $lsLine.

And maybe if you want to make the code shorter, you could use lassign (as of Tcl 8.5)

set fileIn [open "C:/myfile.csv" r]
package require csv
while {[gets $fileIn sLine] >= 0} {
    set lsLine [::csv::split $sLine]
    set sType "Hardware"
    lassign $lsLine sName Sdev spara sDescription sManage sconnect sUOM sPin sCountry

    #my operations
}

flush $fileId
close $fileId

Alternate solution if csv is not available which works for most cases (Tcl 8.6):

set lsLine [lmap {a b} [regexp -all -inline -- {("[^\"]+"|[^,]*)(?:$|,)} $sLine] {set b}]

Tcl 8.5:

set matches [regexp -all -inline -- {("[^\"]+"|[^,]*)(?:$|,)} $sLine]
set lsLine {}
foreach {a b} $matches {lappend lsLine $b}

The \" can be replaced with a simple " but I usually insert it if there's an issue with the code editor's syntax highlighting.

For more complex cases where escaped characters with a backslash can be involved (Tcl 8.6):

set lsLine [lmap {a b} [regexp -all -inline -- {("(?:\\.|[^\"])+"|(?:\\.|[^,])*)(?:$|,)} $sLine] {set b}]

Tcl 8.5:

set matches [regexp -all -inline -- {("(?:\\.|[^\"])+"|(?:\\.|[^,])*)(?:$|,)} $sLine]
set lsLine {}
foreach {a b} $matches {lappend lsLine $b}

Upvotes: 2

Peter Lewerin
Peter Lewerin

Reputation: 13252

package require csv

set fileIn [open C:/myfile.csv r]

    while {[gets $fileIn sLine] >= 0} {
        set lsLine [csv::split $sLine]
        lassign $lsLine - sName Sdev spara sDescription sManage sConnect sUOM sCountry
    }

    close $fileId
}

Never try to parse CSV data with split. It will end in tears.

Note that this assumes that you want to assign to sName from index 1, i.e. the second element. The first element is assigned to the dummy variable -.

For sparse assignment, you can either use (assuming you want #8 rather than #9)

lassign $lsLine - - Sdev - - sManage sConnect sUOM sCountry

or

foreach idx {2 5 6 7 8} name {Sdev sManage sConnect sUOM sCountry} {
    set $name [lindex $lsLine $idx]
}

If you don't have csv installed, you can use teacup install csv from the command line to get it.

Documentation: close, csv package, gets, lassign, open, package, set, while

Upvotes: 1

Dinesh
Dinesh

Reputation: 16428

Since you are using split to extract the variable (yes, your input is based on the comma, so we obviously go to that approach), the input's value should not have a 'comma' in it. To avoid that, we can replace it for a while and revert back wherever needed.

set fileIn [open "file.csv" r]

while {[gets $fileIn sLine] >= 0} {
    # Replacing the 'comma' with 'colon' and saving it into the save variable
    regsub {"(.*?),(.*?)"} $sLine {\1:\2} sLine
    set lsLine [split $sLine ","]

    # Your other indices can be processed and saved here


    # Getting the country values
    set sCountry [lindex $lsLine 8]; # Yes, the country value is available in '8th' index only, not on 9th. (Index starts with '0')

    # Replacing the 'colon' with 'comma' back again
    regsub : $sCountry , sCountry
    puts "Country Value : $sCountry"
}
close $fileIn

Upvotes: 1

Related Questions