" csv.vim: Some simple CSV routines " Maintainer: Preben "Peppe" Guldberg " Version: 1 " Date: May 26, 2002 " Handy variables to match fields and add/remove some markup let s:csvfield = '\%([^,"]*\|"\%([^"]*""\)*[^"]*"\)' let s:csvfield_anchored = '\%(^\|,\)\@<=' . s:csvfield . '\%(,\|$\)\@=' " CSVCheck() checks if all lines in a range have the same number of fields. " The default range is all lines in the buffer. " The function returns the number of fields in each line or -1 on failure. fun! CSVCheck(...) if a:0 == 0 let l1 = 1 let l2 = line("$") elseif a:0 == 2 let l1 = a:1 let l2 = a:2 else echoerr "Bad usage: CSVCheck([line1, line2]) called with " \ . a:0 . " arguments" return -1 endif let seps = CSVFieldsNr(getline(l1)) while l1 < l2 let l1 = l1 + 1 if CSVFieldsNr(getline(l1)) != seps return -1 endif endwhile return seps endfun " CSVFieldsNr() returns the number of fields there are in a given string fun! CSVFieldsNr(str) return strlen(substitute(a:str, s:csvfield_anchored, '', 'g')) + 1 endfun " CSVInsertField() inserts a string in a new field on all lines in a range. " The default range is all lines in the buffer. " If necessary, empty fields are added. " If the new field number is less than two, the new fields is inserted at the " start of the lines. " The function returns the number of added fields on succes and -1 on errors. fun! CSVInsertField(index, str, ...) if a:0 == 0 let l1 = 1 let l2 = line("$") elseif a:0 == 2 let l1 = a:1 let l2 = a:2 else echoerr "Bad usage: CSVInsertField(index, str [, line1, line2])" \ . " called with " . a:0 . " optional arguments" return -1 endif let fields = CSVCheck(l1, l2) if fields == -1 echoerr "The lines " . l1 . " to " . l2 \ . " do not have the same number of fields" return -1 endif if a:index <= fields + 1 let commas = 0 else let commas = a:index - fields - 1 let commastr = "," let i = 1 while i < commas let commastr = commastr . "," let i = i + 1 endwhile silent exec l1 . ',' . l2 . 's/$/' . commastr . '/' endif let str = escape(a:str, '/\') if a:index <= 1 let lhs = '^' let rhs = str . ',' elseif a:index > fields let lhs = '$' let rhs = ',' . str else let lhs = '^\%(' . s:csvfield . ',\)\{' . (a:index - 1) . '}' let rhs = '&' . str . ',' endif silent exec l1 . ',' . l2 . 's/' . lhs . '/' . rhs . '/' return commas + 1 endfun " CSVDeleteField() deletes a field in a range. " The default range is all lines in the buffer. " Returns the new number of fields on succes and 0 on failure. fun! CSVDeleteField(index, ...) if a:0 == 0 let l1 = 1 let l2 = line("$") elseif a:0 == 2 let l1 = a:1 let l2 = a:2 else echoerr "Bad usage: CSVDeleteField(index [, line1, line2])" \ . " called with " . a:0 . " optional arguments" return -1 endif let fields = CSVCheck(l1, l2) if fields == -1 echoerr "The lines " . l1 . " to " . l2 \ . " do not have the same number of fields" return -1 endif if a:index < 1 || a:index > fields echoerr "No such field (" . a:index . ") - only " . fields \ . " available" return -1 endif silent exec l1 . ',' . l2 . 's/^/,/' silent exec l1 . ',' . l2 . 's/$/,/' let pat = '\(\%(' . s:csvfield . ',\)\{' . a:index . '}\)' silent exec l1 . ',' . l2 . 's/^' . pat . s:csvfield . ',/\1/' silent exec l1 . ',' . l2 . 's/,//' silent exec l1 . ',' . l2 . 's/,$//' return fields - 1 endfun " CSVMoveField() moves the contents of field from one position to another. " The default range is all lines in the buffer. " If the new fiels is less than two, it is inserted at the start of the line. " If necessary, empty fields are added. " The function returns the new number of fields or -1 on failure. fun! CSVMoveField(index1, index2, ...) if a:0 == 0 let l1 = 1 let l2 = line("$") elseif a:0 == 2 let l1 = a:1 let l2 = a:2 else echoerr "Bad usage: CSVInsertField(index1, index2 [, line1, line2])" \ . " called with " . a:0 . " optional arguments" return 0 endif let fields = CSVCheck(l1, l2) if fields == -1 echoerr "The lines " . l1 . " to " . l2 \ . " do not have the same number of fields" return -1 endif if a:index1 < 1 || a:index1 > fields echoerr "No such field (" . a:index1 . ") - only " . fields \ . " available" return -1 endif if a:index1 == a:index2 return fields endif if a:index2 > fields call CSVInsertField(a:index2, '', l1, l2) let fields = a:index2 endif if a:index1 > 1 silent exec l1 . ',' . l2 . 's/^/,/' let pat = '\(\%(,' . s:csvfield . '\)\{' . (a:index1 - 1) .'}\)' let pat = pat . ',\(' . s:csvfield_anchored . '\)' silent exec l1 . ',' . l2 . 's/^' . pat . '/\2\1/' endif if a:index2 > 1 silent exec l1 . ',' . l2 . 's/$/,/' let pat = '\(\%(' . s:csvfield . ',\)\{' . (a:index2 - 1) .'}\)' let pat = '\(' . s:csvfield . ',\)' . pat silent exec l1 . ',' . l2 . 's/^' . pat . '/\2\1/' silent exec l1 . ',' . l2 . 's/,$//' endif return fields endfun " CSVSwapFields() swaps two fields " The default range is all lines in the buffer. " The function returns the new number of fields or -1 on failure. fun! CSVSwapFields(index1, index2, ...) if a:0 == 0 let l1 = 1 let l2 = line("$") elseif a:0 == 2 let l1 = a:1 let l2 = a:2 else echoerr "Bad usage: CSVSwapFields(index1, index2 [, line1, line2])" \ . " called with " . a:0 . " optional arguments" return -1 endif if CSVCheck(l1, l2) == 0 echoerr "The lines " . l1 . " to " . l2 \ . " do not have the same number of fields" return -1 endif let fields = CSVFieldsNr(getline(l1)) if a:index1 < 1 || a:index1 > fields echoerr "No such field (" . a:index1 . ") - only " . fields \ . " available" return -1 endif if a:index2 < 1 || a:index2 > fields echoerr "No such field (" . a:index1 . ") - only " . fields \ . " available" return -1 endif if a:index1 == a:index2 return fields endif if a:index1 <= a:index2 let index1 = a:index1 let index2 = a:index2 else let index1 = a:index2 let index2 = a:index1 endif if index1 + index2 == 3 call CSVMoveField(1, 2, l1, l2) else call CSVMoveField(index2, 1, l1, l2) call CSVMoveField(index1 + 1, 1, l1, l2) call CSVMoveField(1, index2, l1, l2) call CSVMoveField(1, index1, l1, l2) endif return fields endfun " CSVSetKey() sets the value of the Nth (existing) field in a string. " This function handles proper quoting of the string - just provide the value. " The function returns the resulting string or an empty string on errors. " The recipient is supposed to know if an empty string is valid. fun! CSVSetKey(str, index, value) let fields = CSVFieldsNr(a:str) if a:index < 1 || a:index > fields echoerr "No such field (" . a:index . ") - only " . fields \ . " available" return "" endif let value = a:value if value =~ '[",]' let value = '"' . substitute(value, '"', '""', 'g') . '"' endif let lhs = '^\(\%(' . s:csvfield . ',\)\{' . a:index . '}\)' let lhs = lhs . '\(' . s:csvfield . '\),' let rhs = '\1,' . value . ',' let str = substitute(',' . a:str . ',', lhs, rhs, '') return substitute(str, '\(^,\|,$\)', '', 'g') endfun " CSVGetKey() returns the unquoted value of the Nth field in a string. " If the index is out of bounds, an empty string is returned. " The recipient is supposed to supply a valid index. fun! CSVGetKey(str, index) let fields = CSVFieldsNr(a:str) if a:index < 1 || a:index > fields echoerr "No such field (" . a:index . ") - only " . fields \ . " available" return "" endif let pat = '^\%(' . s:csvfield . ',\)\{' . a:index . '}' let pat = pat . '\(' . s:csvfield . '\),.*' let val = substitute(',' . a:str . ',', pat, '\1', '') if val[0] == '"' let val = substitute(val, '""', '"', 'g') let val = substitute(val, '\(^"\|"$\)', '', 'g') endif return val endfun