diff --git a/README.md b/README.md index ed8cdde..3df292d 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,8 @@ The type of script is determined by the line (meta tag) at the beginning of the * 4.4.0 - markdown support; * 4.4.1 - rich text editor support; * 4.4.2 - added `fmt` and `html` utility modules; -* 4.4.4 - added `tasker` module. +* 4.4.4 - added `tasker` module; +* 4.4.4 - added `csv` module. # Widget scripts @@ -469,6 +470,7 @@ AIO Launcher also includes: * [fmt](lib/fmt.lua) - HTML formatting module; * [utf8](https://gist.github.com/Stepets/3b4dbaf5e6e6a60f3862) - UTF-8 module from Lua 5.3; * [json.lua](https://github.com/rxi/json.lua) - JSON parser; +* [csv.lua](lib/csv.lua) - CSV parser; * [Lua-Simple-XML-Parser](https://github.com/Cluain/Lua-Simple-XML-Parser) - XML parser (see example `xml-test.lua`). * [luaDate](https://github.com/Tieske/date) - time functions; * [LuaFun](https://github.com/luafun/luafun) - high-performance functional programming library for Lua; diff --git a/lib/csv.lua b/lib/csv.lua new file mode 100644 index 0000000..4806a18 --- /dev/null +++ b/lib/csv.lua @@ -0,0 +1,100 @@ +-- type = "module" +-- CSV Module +-- http://help.interfaceware.com/kb/parsing-csv-files + +local function parse_line(line,sep) + local res = {} + local pos = 1 + sep = sep or ',' + while true do + local c = string.sub(line,pos,pos) + if (c == "") then break end + local posn = pos + local ctest = string.sub(line,pos,pos) + + while ctest == ' ' do + -- handle space(s) at the start of the line (with quoted values) + posn = posn + 1 + ctest = string.sub(line,posn,posn) + if ctest == '"' then + pos = posn + c = ctest + end + end + if (c == '"') then + -- quoted value (ignore separator within) + local txt = "" + repeat + local startp,endp = string.find(line,'^%b""',pos) + txt = txt..string.sub(line,startp+1,endp-1) + pos = endp + 1 + c = string.sub(line,pos,pos) + if (c == '"') then + txt = txt..'"' + -- check first char AFTER quoted string, if it is another + -- quoted string without separator, then append it + -- this is the way to "escape" the quote char in a quote. example: + -- value1,"blub""blip""boing",value3 will result in blub"blip"boing for the middle + elseif c == ' ' then + -- handle space(s) before the delimiter (with quoted values) + while c == ' ' do + pos = pos + 1 + c = string.sub(line,pos,pos) + end + end + until (c ~= '"') + table.insert(res,txt) + + if not (c == sep or c == "") then + error("ERROR: Invalid CSV field - near character "..pos.." in this line of the CSV file: \n"..line, 3) + end + pos = pos + 1 + posn = pos + ctest = string.sub(line,pos,pos) + + while ctest == ' ' do + -- handle space(s) after the delimiter (with quoted values) + posn = posn + 1 + ctest = string.sub(line,posn,posn) + if ctest == '"' then + pos = posn + c = ctest + end + end + else + -- no quotes used, just look for the first separator + local startp,endp = string.find(line,sep,pos) + if (startp) then + table.insert(res,string.sub(line,pos,startp-1)) + pos = endp + 1 + else + -- no separator found -> use rest of string and terminate + table.insert(res,string.sub(line,pos)) + break + end + end + end + return res +end + +------------------------------------ +---- Module Interface functions ---- +------------------------------------ +local csv = {} + +function csv.parse(data, separator) + -- handle '\r\n\' as line separator + data = data:gsub('\r\n','\n') + -- handle '\r' (bad form) as line separator + data = data:gsub('\r','\n') + local result={} + + for line in data:gmatch("([^\n]+)") do + local parsed_line = parse_line(line, separator) + table.insert(result, parsed_line) + end + + return result +end + +return csv \ No newline at end of file