source: tol/tolsh/tolsh.vfs/lib/tcllib1.10/csv/csv.tcl @ 8227d4c

1941-docker-image-to-testdevel
Last change on this file since 8227d4c was 8597867d, checked in by Jorge Pérez <josp.jorge@…>, 16 years ago

closes #976, se incluye tcllib1.10, base64 debe eliminarse del raiz de lib ya que esta incluido en tcllib

git-svn-id: ​https://www.tol-project.org/svn/tolp/trunk@2556 be4846ae-f051-483a-831f-b64e7d08527f

  • Property mode set to 100644
File size: 18.3 KB
Line 
1# csv.tcl --
2#
3#       Tcl implementations of CSV reader and writer
4#
5# Copyright (c) 2001      by Jeffrey Hobbs
6# Copyright (c) 2001-2005 by Andreas Kupries <andreas_kupries@users.sourceforge.net>
7#
8# See the file "license.terms" for information on usage and redistribution
9# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10#
11# RCS: @(#) $Id: csv.tcl,v 1.25 2006/09/19 23:36:15 andreas_kupries Exp $
12
13package require Tcl 8.3
14package provide csv 0.7
15
16namespace eval ::csv {
17    namespace export join joinlist read2matrix read2queue report
18    namespace export split split2matrix split2queue writematrix writequeue
19}
20
21# ::csv::join --
22#
23#       Takes a list of values and generates a string in CSV format.
24#
25# Arguments:
26#       values          A list of the values to join
27#       sepChar         The separator character, defaults to comma
28#
29# Results:
30#       A string containing the values in CSV format.
31
32proc ::csv::join {values {sepChar ,} {delChar \"}} {
33    set out ""
34    set sep {}
35    foreach val $values {
36        if {[string match "*\[${delChar}$sepChar\]*" $val]} {
37            append out $sep${delChar}[string map [list $delChar ${delChar}${delChar}] $val]${delChar}
38        } else {
39            append out $sep${val}
40        }
41        set sep $sepChar
42    }
43    return $out
44}
45
46# ::csv::joinlist --
47#
48#       Takes a list of lists of values and generates a string in CSV
49#       format. Each item in the list is made into a single CSV
50#       formatted record in the final string, the records being
51#       separated by newlines.
52#
53# Arguments:
54#       values          A list of the lists of the values to join
55#       sepChar         The separator character, defaults to comma
56#
57# Results:
58#       A string containing the values in CSV format, the records
59#       separated by newlines.
60
61proc ::csv::joinlist {values {sepChar ,} {delChar \"}} {
62    set out ""
63    foreach record $values {
64        # note that this is ::csv::join
65        append out "[join $record $sepChar $delChar]\n"
66    }
67    return $out
68}
69
70# ::csv::joinmatrix --
71#
72#       Takes a matrix object following the API specified for the
73#       struct::matrix package. Each row of the matrix is converted
74#       into a single CSV formatted record in the final string, the
75#       records being separated by newlines.
76#
77# Arguments:
78#       matrix          Matrix object command.
79#       sepChar         The separator character, defaults to comma
80#
81# Results:
82#       A string containing the values in CSV format, the records
83#       separated by newlines.
84
85proc ::csv::joinmatrix {matrix {sepChar ,} {delChar \"}} {
86    return [joinlist [$matrix get rect 0 0 end end] $sepChar $delChar]
87}
88
89# ::csv::iscomplete --
90#
91#       A predicate checking if the argument is a complete csv record.
92#
93# Arguments
94#       data    The (partial) csv record to check.
95#
96# Results:
97#       A boolean flag indicating the completeness of the data. The
98#       result is true if the data is complete.
99
100proc ::csv::iscomplete {data} {
101    expr {1 - [regexp -all \" $data] % 2}
102}
103
104# ::csv::read2matrix --
105#
106#       A wrapper around "Split2matrix" reading CSV formatted
107#       lines from the specified channel and adding it to the given
108#       matrix.
109#
110# Arguments:
111#       m               The matrix to add the read data too.
112#       chan            The channel to read from.
113#       sepChar         The separator character, defaults to comma
114#       expand          The expansion mode. The default is none
115#
116# Results:
117#       A list of the values in 'line'.
118
119proc ::csv::read2matrix {args} {
120    # FR #481023
121    # See 'split2matrix' for the available expansion modes.
122
123    # Argument syntax:
124    #
125    #2)            chan m
126    #3)            chan m sepChar
127    #3) -alternate chan m
128    #4) -alternate chan m sepChar
129    #4)            chan m sepChar expand
130    #5) -alternate chan m sepChar expand
131
132    set alternate 0
133    set sepChar   ,
134    set expand    none
135
136    switch -exact -- [llength $args] {
137        2 {
138            foreach {chan m} $args break
139        }
140        3 {
141            foreach {a b c} $args break
142            if {[string equal $a "-alternate"]} {
143                set alternate 1
144                set chan      $b
145                set m         $c
146            } else {
147                set chan    $a
148                set m       $b
149                set sepChar $c
150            }
151        }
152        4 {
153            foreach {a b c d} $args break
154            if {[string equal $a "-alternate"]} {
155                set alternate 1
156                set chan      $b
157                set m         $c
158                set sepChar   $d
159            } else {
160                set chan    $a
161                set m       $b
162                set sepChar $c
163                set expand  $d
164            }
165        }
166        5 {
167            foreach {a b c d e} $args break
168            if {![string equal $a "-alternate"]} {
169                return -code error "wrong#args: Should be ?-alternate? chan m ?separator? ?expand?"
170            }
171            set alternate 1
172
173            set chan    $b
174            set m       $c
175            set sepChar $d
176            set expand  $e
177        }
178        0 - 1 -
179        default {
180            return -code error "wrong#args: Should be ?-alternate? chan m ?separator? ?expand?"
181        }
182    }
183
184    set data ""
185    while {![eof $chan]} {
186        if {[gets $chan line] < 0} {continue}
187
188        # Why skip empty lines? They may be in data. Except if the
189        # buffer is empty, i.e. we are between records.
190        if {$line == {} && $data == {}} {continue}
191
192       append data $line
193       if {![iscomplete $data]} {
194           # Odd number of quotes - must have embedded newline
195           append data \n
196           continue
197       }
198
199       Split2matrix $alternate $m $data $sepChar $expand
200       set data ""
201    }
202    return
203}
204
205# ::csv::read2queue --
206#
207#       A wrapper around "::csv::split2queue" reading CSV formatted
208#       lines from the specified channel and adding it to the given
209#       queue.
210#
211# Arguments:
212#       q               The queue to add the read data too.
213#       chan            The channel to read from.
214#       sepChar         The separator character, defaults to comma
215#
216# Results:
217#       A list of the values in 'line'.
218
219proc ::csv::read2queue {args} {
220    # Argument syntax:
221    #
222    #2)            chan q
223    #3)            chan q sepChar
224    #3) -alternate chan q
225    #4) -alternate chan q sepChar
226
227    set alternate 0
228    set sepChar   ,
229
230    switch -exact -- [llength $args] {
231        2 {
232            foreach {chan q} $args break
233        }
234        3 {
235            foreach {a b c} $args break
236            if {[string equal $a "-alternate"]} {
237                set alternate 1
238                set chan      $b
239                set q         $c
240            } else {
241                set chan    $a
242                set q       $b
243                set sepChar $c
244            }
245        }
246        4 {
247            foreach {a b c d} $args break
248            if {![string equal $a "-alternate"]} {
249                return -code error "wrong#args: Should be ?-alternate? chan q ?separator?"
250            }
251            set alternate 1
252            set chan    $b
253            set q       $c
254            set sepChar $d
255        }
256        0 - 1 -
257        default {
258            return -code error "wrong#args: Should be ?-alternate? chan q ?separator?"
259        }
260    }
261
262    set data ""
263    while {![eof $chan]} {
264        if {[gets $chan line] < 0} {continue}
265
266        # Why skip empty lines? They may be in data. Except if the
267        # buffer is empty, i.e. we are between records.
268        if {$line == {} && $data == {}} {continue}
269
270        append data $line
271        if {![iscomplete $data]} {
272            # Odd number of quotes - must have embedded newline
273            append data \n
274            continue
275        }
276
277        $q put [Split $alternate $line $sepChar]
278        set data ""
279    }
280    return
281}
282
283# ::csv::report --
284#
285#       A report command which can be used by the matrix methods
286#       "format-via" and "format2chan-via". For the latter this
287#       command delegates the work to "::csv::writematrix". "cmd" is
288#       expected to be either "printmatrix" or
289#       "printmatrix2channel". The channel argument, "chan", has to
290#       be present for the latter and must not be present for the first.
291#
292# Arguments:
293#       cmd             Either 'printmatrix' or 'printmatrix2channel'
294#       matrix          The matrix to format.
295#       args            0 (chan): The channel to write to
296#
297# Results:
298#       None for 'printmatrix2channel', else the CSV formatted string.
299
300proc ::csv::report {cmd matrix args} {
301    switch -exact -- $cmd {
302        printmatrix {
303            if {[llength $args] > 0} {
304                return -code error "wrong # args:\
305                        ::csv::report printmatrix matrix"
306            }
307            return [joinlist [$matrix get rect 0 0 end end]]
308        }
309        printmatrix2channel {
310            if {[llength $args] != 1} {
311                return -code error "wrong # args:\
312                        ::csv::report printmatrix2channel matrix chan"
313            }
314            writematrix $matrix [lindex $args 0]
315            return ""
316        }
317        default {
318            return -code error "Unknown method $cmd"
319        }
320    }
321}
322
323# ::csv::split --
324#
325#       Split a string according to the rules for CSV processing.
326#       This assumes that the string contains a single line of CSVs
327#
328# Arguments:
329#       line            The string to split
330#       sepChar         The separator character, defaults to comma
331#
332# Results:
333#       A list of the values in 'line'.
334
335proc ::csv::split {args} {
336    # Argument syntax:
337    #
338    # (1)            line
339    # (2)            line sepChar
340    # (2) -alternate line
341    # (3) -alternate line sepChar
342
343    # (3)            line sepChar delChar
344    # (4) -alternate line sepChar delChar
345
346    set alternate 0
347    set sepChar   ,
348    set delChar   \"
349
350    switch -exact -- [llength $args] {
351        1 {
352            set line [lindex $args 0]
353        }
354        2 {
355            foreach {a b} $args break
356            if {[string equal $a "-alternate"]} {
357                set alternate 1
358                set line     $b
359            } else {
360                set line    $a
361                set sepChar $b
362            }
363        }
364        3 {
365            foreach {a b c} $args break
366            if {[string equal $a "-alternate"]} {
367                set alternate 1
368                set line    $b
369                set sepChar $c
370            } else {
371                set line    $a
372                set sepChar $b
373                set delChar $c
374            }
375        }
376        4 {
377            foreach {a b c d} $args break
378            if {![string equal $a "-alternate"]} {
379                return -code error "wrong#args: Should be ?-alternate? line ?separator? ?delimiter?"
380            }
381            set alternate 1
382            set line    $b
383            set sepChar $c
384            set delChar $d
385        }
386        0 -
387        default {
388            return -code error "wrong#args: Should be ?-alternate? line ?separator? ?delimiter?"
389        }
390    }
391
392    if {[string length $sepChar] < 1} {
393        return -code error "illegal separator character ${delChar}$sepChar${delChar}, is empty"
394    } elseif {[string length $sepChar] > 1} {
395        return -code error "illegal separator character ${delChar}$sepChar${delChar}, is a string"
396    }
397
398    if {[string length $delChar] < 1} {
399        return -code error "illegal separator character \"$delChar\", is empty"
400    } elseif {[string length $delChar] > 1} {
401        return -code error "illegal separator character \"$delChar\", is a string"
402    }
403
404    return [Split $alternate $line $sepChar $delChar]
405}
406
407proc ::csv::Split {alternate line sepChar {delChar \"}} {
408    # Protect the sepchar from special interpretation by
409    # the regex calls below.
410
411    set sepRE \\$sepChar
412    set delRE \\$delChar
413
414    if {$alternate} {
415        # The alternate syntax requires a different parser.
416        # A variation of the string map / regsub parser for the
417        # regular syntax was tried but does not handle embedded
418        # doubled " well (testcase csv-91.3 was 'knownBug', sole
419        # one, still a bug). Now we just tokenize the input into
420        # the primary parts (sep char, "'s and the rest) and then
421        # use an explicitly coded state machine (DFA) to parse
422        # and convert token sequences.
423
424        ## puts 1->>$line<<
425        set line [string map [list \
426                $sepChar \0$sepChar\0 \
427                $delChar \0${delChar}\0 \
428                ] $line]
429
430        ## puts 2->>$line<<
431        set line [string map [list \0\0 \0] $line]
432        regsub "^\0" $line {} line
433        regsub "\0$" $line {} line
434
435        ## puts 3->>$line<<
436
437        set val ""
438        set res ""
439        set state base
440
441        ## puts 4->>[::split $line \0]
442        foreach token [::split $line \0] {
443
444            ## puts "\t*= $state\t>>$token<<"
445            switch -exact -- $state {
446                base {
447                    if {[string equal $token "${delChar}"]} {
448                        set state qvalue
449                        continue
450                    }
451                    if {[string equal $token $sepChar]} {
452                        lappend res $val
453                        set val ""
454                        continue
455                    }
456                    append val $token
457                }
458                qvalue {
459                    if {[string equal $token "${delChar}"]} {
460                        # May end value, may be a doubled "
461                        set state endordouble
462                        continue
463                    }
464                    append val $token
465                }
466                endordouble {
467                    if {[string equal $token "${delChar}"]} {
468                        # Doubled ", append to current value
469                        append val ${delChar}
470                        set state qvalue
471                        continue
472                    }
473                    # Last " was end of quoted value. Close it.
474                    # We expect current as $sepChar
475
476                    lappend res $val
477                    set          val ""
478                    set state base
479
480                    if {[string equal $token $sepChar]} {continue}
481
482                    # Undoubled " in middle of text. Just assume that
483                    # remainder is another qvalue.
484                    set state qvalue
485                }
486                default {
487                    return -code error "Internal error, illegal parsing state"
488                }
489            }
490        }
491
492        ## puts "/= $state\t>>$val<<"
493
494        lappend res $val
495
496        ## puts 5->>$res<<
497        return $res
498    } else {
499        regsub -- "$sepRE${delRE}${delRE}$" $line $sepChar\0${delChar}${delChar}\0 line
500        regsub -- "^${delRE}${delRE}$sepRE" $line \0${delChar}${delChar}\0$sepChar line
501        regsub -all -- {(^${delChar}|${delChar}$)} $line \0 line
502
503        set line [string map [list \
504                $sepChar${delChar}${delChar}${delChar} $sepChar\0${delChar} \
505                ${delChar}${delChar}${delChar}$sepChar ${delChar}\0$sepChar \
506                ${delChar}${delChar}           ${delChar} \
507                ${delChar}             \0 \
508                ] $line]
509
510        set end 0
511        while {[regexp -indices -start $end -- {(\0)[^\0]*(\0)} $line \
512                -> start end]} {
513            set start [lindex $start 0]
514            set end   [lindex $end 0]
515            set range [string range $line $start $end]
516            if {[string first $sepChar $range] >= 0} {
517                set line [string replace $line $start $end \
518                        [string map [list $sepChar \1] $range]]
519            }
520            incr end
521        }
522        set line [string map [list $sepChar \0 \1 $sepChar \0 {} ] $line]
523        return [::split $line \0]
524
525    }
526}
527
528# ::csv::split2matrix --
529#
530#       Split a string according to the rules for CSV processing.
531#       This assumes that the string contains a single line of CSVs.
532#       The resulting list of values is appended to the specified
533#       matrix, as a new row. The code assumes that the matrix provides
534#       the same interface as the queue provided by the 'struct'
535#       module of tcllib, "add row" in particular.
536#
537# Arguments:
538#       m               The matrix to write the resulting list to.
539#       line            The string to split
540#       sepChar         The separator character, defaults to comma
541#       expand          The expansion mode. The default is none
542#
543# Results:
544#       A list of the values in 'line', written to 'q'.
545
546proc ::csv::split2matrix {args} {
547    # FR #481023
548
549    # Argument syntax:
550    #
551    #2)            m line
552    #3)            m line sepChar
553    #3) -alternate m line
554    #4) -alternate m line sepChar
555    #4)            m line sepChar expand
556    #5) -alternate m line sepChar expand
557
558    set alternate 0
559    set sepChar   ,
560    set expand    none
561
562    switch -exact -- [llength $args] {
563        2 {
564            foreach {m line} $args break
565        }
566        3 {
567            foreach {a b c} $args break
568            if {[string equal $a "-alternate"]} {
569                set alternate 1
570                set m         $b
571                set line      $c
572            } else {
573                set m       $a
574                set line    $b
575                set sepChar $c
576            }
577        }
578        4 {
579            foreach {a b c d} $args break
580            if {[string equal $a "-alternate"]} {
581                set alternate 1
582                set m         $b
583                set line      $c
584                set sepChar   $d
585            } else {
586                set m       $a
587                set line    $b
588                set sepChar $c
589                set expand  $d
590            }
591        }
592        4 {
593            foreach {a b c d e} $args break
594            if {![string equal $a "-alternate"]} {
595                return -code error "wrong#args: Should be ?-alternate? m line ?separator? ?expand?"
596            }
597            set alternate 1
598
599            set m       $b
600            set line    $c
601            set sepChar $d
602            set expand  $e
603        }
604        0 - 1 -
605        default {
606            return -code error "wrong#args: Should be ?-alternate? m line ?separator? ?expand?"
607        }
608    }
609
610    Split2matrix $alternate $m $line $sepChar $expand
611    return
612}
613
614proc ::csv::Split2matrix {alternate m line sepChar expand} {
615    set csv [Split $alternate $line $sepChar]
616
617    # Expansion modes
618    # - none  : default, behaviour of original implementation.
619    #           no expansion is done, lines are silently truncated
620    #           to the number of columns in the matrix.
621    #
622    # - empty : A matrix without columns is expanded to the number
623    #           of columns in the first line added to it. All
624    #           following lines are handled as if "mode == none"
625    #           was set.
626    #
627    # - auto  : Full auto-mode. The matrix is expanded as needed to
628    #           hold all columns of all lines.
629
630    switch -exact -- $expand {
631        none {}
632        empty {
633            if {[$m columns] == 0} {
634                $m add columns [llength $csv]
635            }
636        }
637        auto {
638            if {[$m columns] < [llength $csv]} {
639                $m add columns [expr {[llength $csv] - [$m columns]}]
640            }
641        }
642    }
643    $m add row $csv
644    return
645}
646
647# ::csv::split2queue --
648#
649#       Split a string according to the rules for CSV processing.
650#       This assumes that the string contains a single line of CSVs.
651#       The resulting list of values is appended to the specified
652#       queue, as a single item. IOW each item in the queue represents
653#       a single CSV record. The code assumes that the queue provides
654#       the same interface as the queue provided by the 'struct'
655#       module of tcllib, "put" in particular.
656#
657# Arguments:
658#       q               The queue to write the resulting list to.
659#       line            The string to split
660#       sepChar         The separator character, defaults to comma
661#
662# Results:
663#       A list of the values in 'line', written to 'q'.
664
665proc ::csv::split2queue {args} {
666    # Argument syntax:
667    #
668    #2)            q line
669    #3)            q line sepChar
670    #3) -alternate q line
671    #4) -alternate q line sepChar
672
673    set alternate 0
674    set sepChar   ,
675
676    switch -exact -- [llength $args] {
677        2 {
678            foreach {q line} $args break
679        }
680        3 {
681            foreach {a b c} $args break
682            if {[string equal $a "-alternate"]} {
683                set alternate 1
684                set q         $b
685                set line      $c
686            } else {
687                set q       $a
688                set line    $b
689                set sepChar $c
690            }
691        }
692        4 {
693            foreach {a b c d} $args break
694            if {![string equal $a "-alternate"]} {
695                return -code error "wrong#args: Should be ?-alternate? q line ?separator?"
696            }
697            set alternate 1
698
699            set q       $b
700            set line    $c
701            set sepChar $d
702        }
703        0 - 1 -
704        default {
705            return -code error "wrong#args: Should be ?-alternate? q line ?separator?"
706        }
707    }
708
709    $q put [Split $alternate $line $sepChar]
710    return
711}
712
713# ::csv::writematrix --
714#
715#       A wrapper around "::csv::join" taking the rows in a matrix and
716#       writing them as CSV formatted lines into the channel.
717#
718# Arguments:
719#       m               The matrix to take the data to write from.
720#       chan            The channel to write into.
721#       sepChar         The separator character, defaults to comma
722#
723# Results:
724#       None.
725
726proc ::csv::writematrix {m chan {sepChar ,} {delChar \"}} {
727    set n [$m rows]
728    for {set r 0} {$r < $n} {incr r} {
729        puts $chan [join [$m get row $r] $sepChar $delChar]
730    }
731
732    # Memory intensive alternative:
733    # puts $chan [joinlist [m get rect 0 0 end end] $sepChar $delChar]
734    return
735}
736
737# ::csv::writequeue --
738#
739#       A wrapper around "::csv::join" taking the rows in a queue and
740#       writing them as CSV formatted lines into the channel.
741#
742# Arguments:
743#       q               The queue to take the data to write from.
744#       chan            The channel to write into.
745#       sepChar         The separator character, defaults to comma
746#
747# Results:
748#       None.
749
750proc ::csv::writequeue {q chan {sepChar ,} {delChar \"}} {
751    while {[$q size] > 0} {
752        puts $chan [join [$q get] $sepChar $delChar]
753    }
754
755    # Memory intensive alternative:
756    # puts $chan [joinlist [$q get [$q size]] $sepChar $delChar]
757    return
758}
759
Note: See TracBrowser for help on using the repository browser.