├── csv.nimble ├── README.md ├── LICENSE.md └── csv.nim /csv.nimble: -------------------------------------------------------------------------------- 1 | [Package] 2 | name = "csv" 3 | version = "0.3" 4 | author = "Adam Chesak" 5 | description = "procedures for reading, writing, parsing, and stringifying CSV (comma separated value) files" 6 | license = "MIT" 7 | 8 | [Deps] 9 | requires: "nim >= 0.18" 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | nim-csv is a Nim module for working with CSV (comma separated values) files. 5 | 6 | Run `nim doc csv.nim` to generate a documentation file. 7 | 8 | License 9 | ======= 10 | 11 | nim-csv is released under the MIT open source license. 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2018 Adam Chesak 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /csv.nim: -------------------------------------------------------------------------------- 1 | # Nim CSV module. 2 | 3 | # Written by Adam Chesak. 4 | # Code released under the MIT open source license. 5 | 6 | 7 | # Import modules. 8 | import parsecsv 9 | import streams 10 | import strutils 11 | 12 | 13 | proc checkEnding(csv: string, separator: char): bool = 14 | ## Check if the lines end with the separating character. 15 | 16 | let lines: seq[string] = csv.splitLines() 17 | for line in lines: 18 | if not line.endsWith("" & separator): 19 | return false 20 | return true 21 | 22 | 23 | proc quoteItemIfNecessary(s: string): string = 24 | ## Quote item if it contains a space and does not start with a quote. 25 | ## This is ``quoteIfContainsWhite`` but reimplemented to 26 | ## remove deprecation warning 27 | 28 | if find(s, {' ', '\t'}) >= 0 and s[0] != '"': 29 | return '"' & s & '"' 30 | else: 31 | return s 32 | 33 | 34 | proc parseAll*( 35 | csv: string, filenameOut: string, separator: char = ',', quote: char = '\"', 36 | escape: char = '\0', skipInitialSpace: bool = false, skipBlankLast: bool = false 37 | ): seq[seq[string]] = 38 | ## Parses the CSV and returns it as a sequence of sequences. 39 | ## 40 | ## * ``filenameOut`` is only used for error messages. 41 | ## * If ``skipBlankLast`` is true, then if every line ends with ``separator`` 42 | ## there will not be a blank field at the end of every row. 43 | ## * See Nim's ``parsecsv`` docs for information on other parameters. 44 | 45 | var csvSeq: seq[seq[string]] = @[] 46 | 47 | # Clean the CSV: remove blank lines 48 | var csvLines: seq[string] = csv.splitLines() 49 | var cleanedLines: seq[string] = @[] 50 | for line in csvLines: 51 | if line.strip() != "": 52 | cleanedLines.add(line) 53 | var cleanedcsv: string = cleanedLines.join("\n") 54 | 55 | if cleanedcsv == nil or cleanedcsv.strip() == "": 56 | return csvSeq 57 | 58 | # Check if the CSV has a blank field on every row: 59 | var newcsv: string = cleanedcsv.strip(trailing = true) 60 | if skipBlankLast and checkEnding(cleanedcsv, separator): 61 | 62 | # Remove the last separator from every line. 63 | var lines: seq[string] = newcsv.splitLines() 64 | for i in 0..high(lines): 65 | lines[i] = lines[i][0..len(lines[i])-2] 66 | newcsv = lines.join("\n") 67 | 68 | # Remember which lines ended with an empty item. 69 | var endings: seq[int] = @[] 70 | let endlines: seq[string] = newcsv.splitlines() 71 | for i in 0..high(endlines): 72 | if checkEnding(endlines[i], separator): 73 | endings.add(i) 74 | 75 | let stream: StringStream = newStringStream(newcsv) 76 | var csvParser: CsvParser 77 | csvParser.open( 78 | stream, filenameOut, skipInitialSpace = skipInitialSpace, 79 | separator = separator, quote = quote, escape = escape 80 | ) 81 | 82 | var c: int = 0 83 | while readRow(csvParser): 84 | var row = newSeq[string](len(csvParser.row)) 85 | if c in endings: 86 | row.add("") 87 | for i in 0..high(csvParser.row): 88 | row[i] = csvParser.row[i] 89 | csvSeq.add(row) 90 | c += 1 91 | 92 | return csvSeq 93 | 94 | 95 | proc readAll*( 96 | filename: string, filenameOut: string, separator: char = ',', quote: char = '\"', 97 | escape: char = '\0', skipInitialSpace: bool = false, skipBlankLast: bool = false 98 | ): seq[seq[string]] = 99 | ## Reads the CSV from the file, parses it, and returns it as a sequence of sequences. 100 | ## 101 | ## * ``filenameOut`` is only used for error messages. 102 | ## * If ``skipBlankLast`` is true, then if every line ends with ``separator`` 103 | ## there will not be a blank field at the end of every row. 104 | ## * See Nim's ``parsecsv`` docs for information on other parameters.. 105 | 106 | let csv: string = readFile(filename).strip(trailing = true) 107 | return parseAll( 108 | csv, filenameOut, separator = separator, quote = quote, escape = escape, 109 | skipInitialSpace = skipInitialSpace, skipBlankLast = skipBlankLast 110 | ) 111 | 112 | 113 | proc stringifyAll*( 114 | csv: seq[seq[string]], separator: string = ",", escapeQuotes: bool = true, 115 | quoteAlways: bool = false, spaceBetweenFields: bool = false 116 | ): string = 117 | ## Converts the CSV to a string and returns it. 118 | ## 119 | ## * ``separator`` is the string used as the separating character between fields. 120 | ## * If ``escapeQuotes`` is ``true``, ``"`` will be replaced with ``\"`` 121 | ## and ``'`` will be replaced with ``\'``. 122 | ## * If ``quoteAlways`` is ``true``, it will always add quotes around the item. 123 | ## If it is ``false``, quotes will only be added if the item contains quotes, 124 | ## whitespace, or the separator character. 125 | ## * If ``spaceBetweenFields`` is ``true``, an extra space will be added after 126 | ## the separator character. 127 | 128 | var delimiter: string 129 | if spaceBetweenFields: 130 | delimiter = separator & " " 131 | else: 132 | delimiter = separator 133 | 134 | var csvStr: string = "" 135 | for i in 0..high(csv): 136 | 137 | var csvStrRow: string = "" 138 | for j in 0..high(csv[i]): 139 | 140 | # Escape the quotes, if the user wants that. 141 | var item: string = csv[i][j] 142 | if escapeQuotes and (item.contains("\"") or item.contains("'")): 143 | item = item.replace("\"", "\\\"").replace("'", "\\'") 144 | 145 | # Quote always if the user wants that, otherwise only do it if necessary. 146 | if quoteAlways: 147 | item = "\"" & item & "\"" 148 | elif item.contains("\"") or item.contains("'") or item.contains(separator): 149 | item = "\"" & item & "\"" 150 | else: 151 | item = quoteItemIfNecessary(item) 152 | 153 | csvStrRow &= item 154 | 155 | # Only add a separator if it isn't the last item. 156 | if j != high(csv[i]): 157 | csvStrRow &= delimiter 158 | 159 | csvStr &= csvStrRow 160 | 161 | # Only add a newline if it isn't the last row. 162 | if i != high(csv): 163 | csvStr &= "\n" 164 | 165 | return csvStr 166 | 167 | 168 | proc writeAll*( 169 | filename: string, csv: seq[seq[string]], separator: string = ",", 170 | escapeQuotes: bool = true, quoteAlways: bool = false, 171 | spaceBetweenFields: bool = false 172 | ): string = 173 | ## Converts the CSV to a string and writes it to the file. Returns the CSV as a string. 174 | ## 175 | ## * ``separator`` is the string used as the separating character between fields. 176 | ## * If ``escapeQuotes`` is ``true``, ``"`` will be replaced with ``\"`` and 177 | ## ``'`` will be replaced with ``\'``. 178 | ## * If ``quoteAlways`` is ``true``, it will always add quotes around the item. 179 | ## If it is ``false``, quotes will only be added if the item contains quotes, 180 | ## whitespace, or the separator character. 181 | ## * If ``spaceBetweenFields`` is ``true``, an extra space will be added after 182 | ## the separator character. 183 | 184 | let csvStr: string = stringifyAll( 185 | csv, separator = separator, escapeQuotes = escapeQuotes, 186 | quoteAlways = quoteAlways, spaceBetweenFields = spaceBetweenFields 187 | ) 188 | writeFile(filename, csvStr) 189 | return csvStr 190 | --------------------------------------------------------------------------------