Documentation
¶
Overview ¶
© 2017-2026 Osvaldo Gago
The Go package simplecsv is a library to handle csv files without using a database.
Some notes:
- all read methods return the value in the csv and a second true/false value that is true if the value exists
- all write methods that change the csv return the changed csv and a true/false value if the operation was successful
- all cells are strings
- methods that return a SimpleCsv never modify the original csv and share no data with it: the returned csv is an independent copy, and get methods like GetRow and GetHeaders return copies
- Simplecsv works with comma separated csv files.
- header names (the first row) must be unique, like database columns. Creating a csv with duplicate headers, reading a file whose first row has duplicate headers, or renaming a header to a name that already exists is rejected. This guarantees that name-based lookups (GetCellByField, FindInField, SortByField, GetRowAsMap, the *FromMap functions, etc.) address exactly one column.
- all rows have the same number of cells as the header row (uniform row width). Reading a ragged file (rows with more or fewer fields than the header row) is rejected with an error, and Write methods that add or set rows reject a row whose length is not the header width. This makes "the number of columns" unambiguous and keeps AddRow, SetRow, the *FromMap functions, joins, OnlyThisFields, etc. consistent.
- ReadCsv, ReadCsvFile, ReadCsvFileE and ReadCsvFileComma read the whole input into memory. Use ReadCsvLimit with positive record and byte limits for large or attacker-controlled input.
- find and match (FindInColumn, FindInField, MatchInColumn, MatchInField) are case-insensitive by default: Foo = foo = FOO. Use the *CaseSensitive variants (FindInColumnCaseSensitive, FindInFieldCaseSensitive, MatchInColumnCaseSensitive, MatchInFieldCaseSensitive) when case matters.
- header names require at least one column: CreateEmptyCsv and OnlyThisFields reject an empty header list.
- CSV / formula injection: values that begin with =, +, -, @, a tab, a carriage return, a line feed, or the full-width variants =, +, -, @ are interpreted as formulas by spreadsheet applications (Excel, LibreOffice Calc, Google Sheets). The Write* functions write values verbatim and so do not neutralize such values, because escaping changes data. When the csv may contain attacker-controlled data (exported logs, form input, scraped content) and may be opened in a spreadsheet, call SanitizeFormulas first to prefix those cells with a single quote '; callers that do not want their bytes changed should not call it.
- writes are atomic and not world-readable: WriteCsvFile / WriteCsvFileE / WriteCsvFileComma write the csv to a temporary file in the same directory as the destination and then rename it over the destination, so a failed or interrupted write cannot truncate or corrupt the destination. The file (new or overwritten) ends up with mode 0600 (owner-readable only); callers that need different permissions can os.Chmod the result. Because the destination is replaced with os.Rename rather than opened, a symlink planted at the destination is replaced instead of followed.
* CSV FILE *
READ ¶
Reads file and parses as a SimpleCsv object. `fileRead` is false if there's an error reading the file or parsing the CSV, if the file's header row contains duplicate names (headers must be unique), or if the file is ragged (rows with a different number of fields than the header row are rejected; uniform row width is an invariant).
var x simplecsv.SimpleCsv
x, fileRead = simplecsv.ReadCsvFile("my1file.csv")
All read functions (file-based and reader-based, limited or not) strip a UTF-8 byte order mark (BOM) from the first cell of the first row if present. Excel and many Windows tools write one (EF BB BF) at the start of the file; without stripping, the first header would become "\ufeffid" and every field lookup would silently fail. Only the first cell of the first row is touched.
CREATE ¶
Create empty file and define csv headers. Header names must be unique; if they are not, the returned error is non-nil:
var u simplecsv.SimpleCsv
var err error
u, err = simplecsv.CreateEmptyCsv([]string{"Age", "Gender", "ID"})
if err != nil {
log.Fatal(err)
}
`MustCreateEmptyCsv` is the same but panics when the headers are invalid (empty or duplicated). Handy in tests and small scripts where a valid header list is part of the program, not a runtime condition:
u = simplecsv.MustCreateEmptyCsv([]string{"Age", "Gender", "ID"})
WRITE ¶
Write the SimpleCsv object to my2file.csv. If there's an error, `wasWritten` is false.
wasWritten := u.WriteCsvFile("my2file.csv")
The write is atomic (the csv is written to a temp file in the same directory and renamed over the destination, so a failed or interrupted write cannot corrupt the destination), the file ends up with mode 0600 (owner-readable only), and a symlink at the destination is replaced rather than followed.
SANITIZE (CSV / FORMULA INJECTION)
The Write* functions write values verbatim. A value that begins with `=`, `+`, `-`, `@`, a tab, a carriage return, a line feed, or the full-width variants `=`, `+`, `-`, `@` is interpreted as a formula by spreadsheet applications (Excel, LibreOffice Calc, Google Sheets), and can be used to execute commands or read other cells when the file is opened. This is a real risk when the csv contains data that may be attacker-controlled (exported logs, form input, scraped content) and is later opened in a spreadsheet.
`SanitizeFormulas` returns an independent copy of the csv in which every such cell is prefixed with a single quote `'` (the spreadsheet convention), so the value is treated as text. Escaping changes data (for example `-5` becomes `'-5`, though the spreadsheet hides the quote and displays `-5` as text), so sanitizing is opt-in. The original csv is not modified.
safe := u.SanitizeFormulas()
wasWritten := safe.WriteCsvFile("export.csv")
err := safe.WriteTo(os.Stdout, ',')
ERRORS ¶
`ReadCsvFileE` and `WriteCsvFileE` work like `ReadCsvFile` and `WriteCsvFile` but return an error with the reason of the failure:
x, err = simplecsv.ReadCsvFileE("my1file.csv")
err := u.WriteCsvFileE("my2file.csv")
The joins, `GroupBy` and `Concat` have their own `E` variants; see ERROR VARIANTS below.
`ReadCsv` and the file-reading wrappers read the whole input into memory. For large or attacker-controlled input, `ReadCsvLimit` reads one record at a time and rejects input over the configured limits. `maxRecords` includes the header row, `maxBytes` limits input bytes, zero disables the corresponding limit, and negative limits are rejected:
file, err := os.Open("large.csv")
if err != nil {
log.Fatal(err)
}
defer file.Close()
x, err := simplecsv.ReadCsvLimit(file, ',', 10000, 10*1024*1024)
if err != nil {
log.Fatal(err)
}
DELIMITERS, READERS AND WRITERS ¶
Simplecsv uses `,` as the default field separator, but it can read and write files with other separators, like `;` or tabs, and it can read from any io.Reader and write to any io.Writer:
x, err = simplecsv.ReadCsvFileComma("semicolonfile.csv", ';')
x, err = simplecsv.ReadCsv(os.Stdin, '\t')
err = x.WriteCsvFileComma("semicolonfile.csv", ';')
err = x.WriteTo(os.Stdout, ',')
* HEADERS *
The cells of the first row are considered headers.
GET ¶
Get all headers:
headers := x.GetHeaders()
Get header at position one (second position as it starts from 0):
headerName, headerExists := x.GetHeader(1)
Get header position: (it returns `-1` if the header does not exist)
position := x.GetHeaderPosition("Gender")
RENAME ¶
Rename header: (old header, new header)
x, headerExists := x.RenameHeader("ID", "IDnumber")
`headerExists` is false if the old header does not exist, or if `IDnumber` already exists as another column: header names must stay unique, so a rename that would produce a duplicate is rejected. Renaming a header to its own name is a no-op and is allowed.
* ROWS *
GET ¶
Get number of rows:
numberOfRows := x.GetNumberRows()
Get second row:
row, rowExists := x.GetRow(1)
Get second row as a map:
row, rowExists := x.GetRowAsMap(1)
ADD ¶
Add a slice to a row. The slice must have the same size as the CSV number of columns. If not wasSuccessful is false.
x, wasSuccessful = x.AddRow([]string{"24", "M", "2986732"})
Add row from map: (If the map keys don't exist as columns, the value will be discarded. If a key does not exist, it will create empty cells.)
mymap := make(map[string]string) mymap["Age"] = "62" mymap["Gender"] = "F" mymap["ID"] = "6463246" x, wasAdded = x.AddRowFromMap(mymap)
SET ¶
Set second row (1) from a slice. The length of the slice must be the same as the number of columns and the row must already exist. If there’s an error `wasSet` is false.
x, wasSet = x.SetRow(1, []string{"45", "F", "8356138"})
Set second row from map: If the map keys don't exist as columns, the value will be discarded. If a key does not exist, it will create empty cells.)
mymap2 := make(map[string]string) mymap2["Age"] = "62" mymap2["Gender"] = "F" mymap2["ID"] = "6463246" x, wasAdded = x.SetRowFromMap(1, mymap2)
UPDATE ¶
Unlike `SetRowFromMap`, `UpdateRowCellsFromMap` does not erase the cells value just because the column names are not keys in the map. It updates the cells that have the column name in the map and maintains the value of all the others.
To update the age in row 1:
mymap3 := make(map[string]string) mymap3["Age"] = "63" x, wasUpdated = x.UpdateRowCellsFromMap(1, mymap3)
DELETE ¶
Delete second row: (If the row number is invalid, `wasDeleted` is false)
x, wasDeleted = x.DeleteRow(1)
DATA ROWS ¶
Get the number of data rows, excluding the header row (unlike `GetNumberRows`, which includes it):
numberOfDataRows := x.GetNumberDataRows()
Get all the data rows as copies (the header row is not included; changes to the returned rows don't affect the csv):
dataRows := x.GetDataRows()
Iterate over the data rows without touching the header: `EachDataRow` calls `fn` once per data row, in csv order, with the row index (starting at 1), a copy of the row, and a map view of it (header name to cell value). Mutating the row or the map inside `fn` does not affect the csv. `fn` may return `false` to stop the iteration early:
x.EachDataRow(func(rowIndex int, row []string, asMap map[string]string) bool {
if asMap["status"] == "stop" {
return false // stop the iteration
}
fmt.Printf("row %d: %v\n", rowIndex, row)
return true
})
SLICE ¶
`Head` returns the header row plus the first `n` data rows (or all of them if there are fewer). `n` negative or zero means no data rows:
sample := x.Head(10)
`Tail` returns the header row plus the last `n` data rows:
recent := x.Tail(10)
`SliceRows` returns the header row plus the data rows whose csv row index is in `[start, end)` (half-open, data rows start at index 1). `end` beyond the last row is clamped. `wasSliced` is false if the csv is empty, `start < 1` or `end < start`:
x, wasSliced = x.SliceRows(1, 3) // data rows 1 and 2
APPEND ¶
`AppendRows` appends the data rows of another csv with the same headers (same names, same order). `wasAppended` is false if either csv is empty or the headers differ:
x, wasAppended = x.AppendRows(anotherCsv)
CONCAT ¶
`Concat` stacks several csvs vertically in one call: the first non-empty csv supplies the headers and every csv with a header row must have the same headers (same names, same order); empty csvs are skipped. If there is no non-empty csv or the headers differ, it returns nil and false:
combined, ok := simplecsv.Concat(day1, day2, day3)
CONCAT BY NAME ¶
Monthly or vendor exports often add or reorder columns, and strict `Concat` rejects them. `ConcatByName` stacks csvs vertically aligning the columns by header name instead of by position: the result header row is the header row of the first non-empty csv, followed by the header names that only appear in later csvs, in order of first appearance. Each data row is copied with the value of each column placed under its header name, and an empty string where the csv has no column with that name. `ok` is false if there is no non-empty csv, or a csv has duplicate header names (the result would be ambiguous). When every csv has the same headers in the same order, `ConcatByName` is equivalent to `Concat`:
january, _ := simplecsv.ReadCsvFile("january.csv") // columns: id, name
february, _ := simplecsv.ReadCsvFile("february.csv") // columns: name, bonus
combined, ok := simplecsv.ConcatByName(january, february)
DEDUPLICATE ¶
`Unique` removes exact duplicate data rows: for each full row (all columns) only the first occurrence is kept, in csv order. The header row is kept:
unique := x.Unique()
`UniqueByFields` removes data rows that share the same key built from the named columns; the first row per key is kept. `wasUnique` is false if the csv is empty, no fields are given, or a field name does not exist:
x, wasUnique = x.UniqueByFields("email")
x, wasUnique = x.UniqueByFields("country", "city")
* DISTINCT AND COUNTS *
`Distinct` returns the distinct values of the data cells of a column, in order of first appearance. The header cell is never included and the returned slice is a copy:
values, wasRead := x.Distinct("Country")
`wasRead` is false if the csv is empty or the field name does not exist. A csv with only the header row returns an empty slice and true.
`ValueCounts` returns a new csv with the fixed headers `value` and `count`: one row per distinct value of the column, in order of first appearance, with the number of data rows that have that value, as a decimal string. The header cell is never counted and the original csv is not modified:
counts, wasRead := x.ValueCounts("Country")
`wasRead` is false if the csv is empty or the field name does not exist; in that case a copy of the csv is returned.
* GROUP BY *
`GroupBy` collapses the data rows into one output row per group, where a group is all the data rows whose key fields have the same values. The output header row is the key field names followed by the `As` name of each aggregation; each output data row is the key values followed by one cell per aggregation. Groups appear in order of first occurrence:
type Agg struct {
Field string // source column; ignored for AggCount
Op AggOp
As string // output header; required; must be unique among keys+As
}
The aggregation operations are:
- `AggCount` counts the data rows of the group. `Field` is ignored and may be empty.
- `AggSum` adds the `Field` cells parsed with `strconv.ParseFloat`; cells that don't parse as numbers count as 0.
- `AggMin` / `AggMax` use only finite numeric cells (NaN and infinities are skipped); a group without any finite numeric cell produces an empty cell.
- `AggFirst` / `AggLast` take the `Field` value of the first and last data row of the group.
- `AggJoin` joins the non-empty `Field` values of the group with ",".
Numeric results are formatted with `strconv.FormatFloat(f, 'f', -1, 64)`, so integers have no decimal noise:
summary, wasGrouped := sales.GroupBy(
[]string{"country"},
[]Agg{{Op: AggCount, As: "orders"}, {Field: "amount", Op: AggSum, As: "total"}},
)
`wasGrouped` is false if the csv is empty, no keys or no aggregations are given, a key or aggregation field does not exist, an `As` name is empty or duplicates another key or `As` name, or an aggregation operation is not one of the `AggOp` constants; in that case a copy of the csv is returned.
* CELLS *
GET ¶
Get value of the cell in the second column, second row:
cellValue, cellExists := x.GetCell(1, 1)
Get the value of the cell in the column "Age", second row:
cellValue, cellExists := x.GetCellByField("Age", 1)
SET ¶
Change the value of the cell in the first column (0) and the second row (1) to "27":
x, wasChanged = x.SetCell(0, 1, "27")
The same, using the column name instead of the column position:
x, wasChanged = x.SetCellByField("Age", 1, "27")
REPLACE IN FIELD ¶
Replace the data cells of a column whose value is exactly equal to `old` (case-sensitive, whole cell) with `new`. The header cell is not modified. Useful to fix known bad values (`N/A` to empty) or rename codes:
x, wasReplaced = x.ReplaceInField("Status", "N/A", "")
TRIM ¶
Remove the surrounding whitespace of every cell, including the header cells, with strings.TrimSpace:
x, wasTrimmed = x.TrimSpace()
`wasTrimmed` is false if trimming makes two header names equal: header names must stay unique.
* COLUMNS *
ADD ¶
Add a column at the end of the CSV:
x, wasSuccessful = x.AddEmptyColumn("NewColumn")
REMOVE ¶
Remove the column at position 1 (second column, because it's zero based):
x, wasRemoved = x.RemoveColumn(1)
Remove a column by name:
x, wasRemoved = x.RemoveColumnByName("Gender")
GET COLUMN VALUES ¶
Get a copy of all the data cells of a column, skipping the header cell. The returned slice is a copy: changes to it don't affect the csv. `columnExists` is false if the csv is empty or the column position is not valid:
columnCells, columnExists := x.GetColumn(1)
columnCells, fieldExists := x.GetColumnByField("Age")
SET COLUMN VALUES ¶
Replace the data cells of a column with a slice. The slice must have the same length as the number of data rows (the csv length minus the header row), because every data row must keep exactly one cell in the column. The header cell is not modified:
x, wasSet = x.SetColumn(1, []string{"24", "62", "45"})
x, wasSet = x.SetColumnByField("Age", []string{"24", "62", "45"})
MAP COLUMN VALUES ¶
Transform the data cells of a column with a function. The function receives the cell value and the csv row index (1 for the first data row); the header cell is not transformed. The original csv is not modified:
x, wasMapped = x.MapColumnByField("Name", func(value string, row int) string {
return strings.ToUpper(value)
})
FILL COLUMN VALUES ¶
Set every data cell of a column to the same value. Useful for constants and defaults:
x, wasFilled = x.FillColumnByField("Source", "import")
* COLUMN SHAPE *
ADD AT POSITION ¶
`AddEmptyColumnAt` inserts an empty column at a position: `index` is the position of the new column and must be between 0 and the number of columns (inclusive; inserting at the number of columns appends, like `AddEmptyColumn`). The header cell of the new column is `columnName` and every data cell is empty:
x, wasAdded = x.AddEmptyColumnAt("Source", 0)
x, wasAdded = x.AddEmptyColumnAt("Notes", 3)
`wasAdded` is false if the csv is empty, the column name already exists or `index` is out of range.
ADD WITH VALUES ¶
`AddColumnByField` appends a column with the header name `columnName` and the given data cells. The values slice must have the same length as the number of data rows (the csv length minus the header row), because every data row must keep exactly one cell in the column; a csv with only the header row takes an empty values slice. The values are copied:
x, wasAdded = x.AddColumnByField("Total", []string{"24", "62", "45"})
`wasAdded` is false if the csv is empty, the column name already exists or the length of the values slice does not match the number of data rows.
COMPUTE FROM ROW MAP ¶
`AddComputedColumn` appends a column whose data cells are computed by a function from the values of the other columns. The function is called once per data row, in csv order, with a map from header name to cell value: the map is built fresh for every row and is a copy, so the callback can read field names instead of column indexes, and mutating the map does not affect the csv. The string the function returns becomes the cell of the new column:
x, wasComputed = x.AddComputedColumn("full_name", func(row map[string]string) string {
return row["first"] + " " + row["last"]
})
`wasComputed` is false if the csv is empty or the column name already exists; in that case a copy of the csv is returned. A csv with only the header row takes the new header cell only and the function is not called.
RENAME BATCH ¶
`RenameHeaders` renames several headers in one call. Every key of the map must be an existing header, and the result must not contain duplicate header names: a rename fails if a key does not exist, two keys map to the same new name, or a renamed column collides with a header that is not renamed. The renames are applied simultaneously, so a swap ("a" -> "b", "b" -> "a") is applied correctly and map iteration order does not matter:
x, wasRenamed = x.RenameHeaders(map[string]string{"ID": "IdNumber", "Date": "Fecha"})
`wasRenamed` is false if the csv is empty or the renamed headers would contain duplicates. Renaming a header to its own name is a no-op for that column, and an empty map is a no-op that succeeds.
MOVE ¶
`MoveColumn` moves the column with the header name `columnName` to position `newIndex` (0 to the number of columns minus 1), shifting the columns in between: the header and data cells of the moved column move together:
x, wasMoved = x.MoveColumn("Age", 0)
`wasMoved` is false if the csv is empty, the column name does not exist or `newIndex` is out of range. Moving a column to its own position is a no-op and succeeds.
SPLIT A FIELD ¶
`SplitField` replaces the column with the header name `name` by `len(newNames)` new columns in its position. Every data cell is split on `sep` with `strings.Split`, and the parts fill the new columns in order: if a cell has fewer parts than `newNames`, the remaining new columns get empty cells; if it has more parts, the extra parts are dropped. The original column is removed, so a new name may reuse it but must not collide with any other header:
x, wasSplit = x.SplitField("coord", ",", []string{"lat", "lon"})
x, wasSplit = x.SplitField("last;first", ";", []string{"last", "first"})
`wasSplit` is false if the csv is empty, the field name does not exist, `newNames` is empty or contains duplicates, or a new name collides with another header.
COMBINE FIELDS ¶
`CombineFields` appends a column with the header name `as` whose data cells join the values of the named columns with `sep` (via `strings.Join`). The source columns stay in place:
x, wasCombined = x.CombineFields([]string{"first", "last"}, " ", "full_name")
x, wasCombined = x.CombineFields([]string{"country", "sku"}, "-", "country_sku")
`wasCombined` is false if the csv is empty, `names` is empty, a name does not exist, `names` repeats a field, or `as` already exists as a header.
* FIND *
FIND IN COLUMN ¶
Find the word "27" in the first column (column 0):
rowsWithWord, validColumn := x.FindInColumn(0, "27")
It returns a slice of row numbers (int) where you can find the word in the column position. Please note that in simplecsv all cells are strings.
If it doesn't find the value, it returns an empty slice.
In `FindInColumn` and in `FindInField` case does not matter. Foo = foo = FOO.
FIND IN FIELD ¶
The same as `FindInColumn` but using a column/field name instead of position. Please note that `FindInField`, unlike `FindInColumn` never includes the header in the search result.
rowsWithWord, validFieldName := x.FindInField("Age", "27")
If the field name does not exist, the second value returned (`validFieldName`) is false.
MATCH IN COLUMN ¶
Find where results match a regular expression in the third column (column 2):
rowsWithWord, areParamsOk := x.MatchInColumn(2, "p([a-z]+)ch$")
Use ^ and $ in the regular expression to match exact results.
Matching is case-insensitive by default: Foo = foo = FOO. (This is achieved by prefixing the (?i) flag to the expression; you can still re-enable case-sensitivity inside the pattern with (?-i), or use MatchInColumnCaseSensitive for the whole match.)
MATCH IN FIELD ¶
Same as with MatchInColumn, but with a field (column name). Find where results match a regular expression in the column "ID":
rowsWithWord, areParamsOk := x.MatchInField("ID", "p([a-z]+)ch$")
Please note that `MatchInField`, unlike `MatchInColumn` never includes the header in the search result.
Matching is case-insensitive by default: Foo = foo = FOO.
CASE SENSITIVE FIND ¶
`FindInColumn`, `FindInField`, `MatchInColumn` and `MatchInField` ignore case by default. Use the `*CaseSensitive` variants when case matters:
rowsWithWord, validFieldName = x.FindInFieldCaseSensitive("Name", "Ana")
rowsWithWord, areParamsOk = x.MatchInFieldCaseSensitive("ID", "p([a-z]+)ch$")
* SORT *
SORT BY FIELD ¶
Sorts the csv by a column name and returns a new sorted csv. The header row stays at the top and the original csv is not modified. Finite numbers are sorted numerically among themselves and appear before non-numeric values (including NaN and Inf), which are sorted as strings. The sort is stable.
sortedCsv, fieldExists := x.SortByField("Age", true)
SORT BY COLUMN ¶
The same as `SortByField` but using a column position. It sorts all the rows, including the first one:
sortedCsv, validColumn := x.SortByColumn(0, true)
SORT BY MULTIPLE FIELDS ¶
Sorts the csv by several columns, one level per field in order, like `ORDER BY` in SQL: a later field is only compared when every earlier field compares equal. The header row stays at the top and the original csv is not modified. Each field uses the same ordering as `SortByField` (finite numbers numerically, other values as strings), and the sort is stable. An empty `ascending` sorts every field ascending; otherwise it has one bool per field (`true` ascending, `false` descending):
sortedCsv, wereFieldsFound := x.SortByFields([]string{"Country", "City"}, nil)
sortedCsv, wereFieldsFound = x.SortByFields([]string{"Country", "City"}, []bool{true, false})
`wereFieldsFound` is false if the csv is empty, no fields are given, a field name does not exist, or the length of `ascending` is neither 0 nor the number of fields; in that case a copy of the csv is returned.
SORT AN INDEX ¶
`SortIndex` returns a sorted copy of an index:
sortedIndex := simplecsv.SortIndex([]int{5, 1, 3})
* FILTER ROWS *
`FilterRows` returns a new csv with the rows where the predicate function returns true. If `header` is true, the first row is kept as the header:
adults := x.FilterRows(func(row []string) bool {
age, _ := strconv.Atoi(row[1])
return age >= 18
}, true)
FIELD FILTERS ¶
`FilterByField` keeps the data rows where the predicate returns true for the named column. The header cell is never passed to the predicate, and the field name is used instead of a column index:
adults, wasFiltered := x.FilterByField("age", func(value string) bool {
n, _ := strconv.Atoi(value)
return n >= 18
})
`Where` keeps the data rows where the named column is exactly equal to `value` (case-sensitive). `wasWhere` is false if the csv is empty or the field name does not exist:
active, wasWhere := x.Where("status", "active")
`WhereFold` is the same but case-insensitive: `Active`, `active` and `ACTIVE` all match:
active, wasWhereFold := x.WhereFold("status", "active")
* JOIN *
INNER JOIN ¶
`JoinByField` joins two csvs by a common field and returns a new csv with the rows where the field has the same value in both csvs. The result has all the columns of the first csv, followed by the columns of the second csv except the join column. Non-join columns of the second csv must not share names with columns of the first; a collision is rejected so headers stay unique. If a join value shows up more than once, all combinations of rows are in the result. Values are compared exactly: case matters. The source csvs are not modified.
people, _ := simplecsv.ReadCsvFile("people.csv") // columns: ID, Name
ages, _ := simplecsv.ReadCsvFile("ages.csv") // columns: ID, Age
joined, fieldExistsInBoth := people.JoinByField(ages, "ID")
`fieldExistsInBoth` is false if the field does not exist in one of the csvs.
LEFT JOIN ¶
`LeftJoinByField` is like `JoinByField`, but the rows of the first csv without a match in the second one are also in the result, with empty cells in the columns of the second csv:
joined, fieldExistsInBoth := people.LeftJoinByField(ages, "ID")
JOIN ON DIFFERENT KEY NAMES ¶
Real files rarely share the same key name (`id` vs `customer_id`). `Join` and `LeftJoin` join by one field name per csv, so no rename is needed: the result is the same as joining on a common name. The join column of the second csv is dropped from the result; the join column of the first csv is kept.
orders, _ := simplecsv.ReadCsvFile("orders.csv") // columns: id, amount
joined, bothFieldsExist := people.Join(orders, "id", "customer_id")
`bothFieldsExist` is false if one of the fields does not exist in its csv. `LeftJoin` is like `Join`, but the rows of the first csv without a match in the second one are also in the result, with empty cells in the columns of the second csv:
joined, bothFieldsExist := people.LeftJoin(orders, "id", "customer_id")
RIGHT JOIN ¶
`RightJoin` is like `Join`, but every row of the second csv is in the result: rows without a match in the first csv are included with empty cells in the columns of the first csv, and rows of the first csv without a match are dropped. The rows of the second csv that had no match are appended at the end. Use it when the second file is the driver (e.g. a master product list):
joined, bothFieldsExist := people.RightJoin(orders, "id", "customer_id")
FULL JOIN ¶
`FullJoin` is like `Join`, but every row of both csvs is in the result: rows without a match are included with empty cells in the columns of the other side. Use it to find unmatched keys on either side without two passes:
joined, bothFieldsExist := people.FullJoin(orders, "id", "customer_id")
SAME NAME JOIN ¶
`RightJoinByField` and `FullJoinByField` are the same-name variants of `RightJoin` and `FullJoin`, like `JoinByField`:
joined, fieldExistsInBoth := people.RightJoinByField(ages, "ID") joined, fieldExistsInBoth := people.FullJoinByField(ages, "ID")
JOIN ON MULTIPLE COLUMNS ¶
`JoinOn`, `LeftJoinOn`, `RightJoinOn` and `FullJoinOn` join on several columns at once, for composite keys like `country` + `sku` or `date` + `store`. The i-th name of the first list must be a column of the first csv, the i-th name of the second list a column of the second csv, and the two lists must have the same length (at least one name). A row of the first csv is joined to a row of the second when the values of all its left key columns are exactly equal to the values of the corresponding right key columns (case matters). The join key is built with a length-prefixed encoding, so values containing separators cannot collide. All the right key columns are dropped from the result; the left key columns are kept. `wasJoined` is false if a csv is empty, the lists have different lengths or are empty, a field does not exist in its csv, or the result headers would collide:
orders, _ := simplecsv.ReadCsvFile("orders.csv") // columns: country, sku, amount
prices, _ := simplecsv.ReadCsvFile("prices.csv") // columns: country, sku, price
joined, wasJoined := orders.JoinOn(prices, []string{"country", "sku"}, []string{"country", "sku"})
`LeftJoinOn` is like `JoinOn`, but the rows of the first csv without a match are also in the result, with empty cells in the columns of the second csv. `RightJoinOn` keeps every row of the second csv, appending the unmatched ones at the end with empty cells in the columns of the first csv. `FullJoinOn` keeps every row of both csvs, with empty cells on the unmatched side. When both field lists have a single name, the `*On` functions are equivalent to their `Join` / `LeftJoin` / `RightJoin` / `FullJoin` counterparts.
MERGE COLUMNS ¶
Sometimes two csvs are already row-aligned (same order, same length) and only need their columns glued side by side; a join would be wrong or wasteful. `MergeColumns` places the columns of other next to the columns of s, row by row: the result header row is the header row of s followed by the header row of other, and each result data row is the data row of s followed by the data row of other in the same position. Both csvs must have the same number of rows (header included); padding is not allowed. No header name of other may collide with a header name of s, so header names stay unique. `wasMerged` is false if a csv is empty, the row counts differ, or the result headers would collide:
customers, _ := simplecsv.ReadCsvFile("customers.csv") // columns: id, name
emails, _ := simplecsv.ReadCsvFile("emails.csv") // columns: email, verified
merged, wasMerged := customers.MergeColumns(emails)
DIFF ¶
"What changed between yesterday's export and today's?" `DiffByKey` compares the data rows of s (left) and other (right), keyed by the values of the column with the header name key. It returns three new csvs:
- `added`: the data rows of other whose key is not present in s, with other's header row
- `removed`: the data rows of s whose key is not present in other, with s's header row
- `changed`: the data rows of s whose key is present in other but whose full row differs, with s's header row and s's values (join on the key if you need the right-side values)
Each result keeps the rows in order of appearance in its source csv and is an independent csv, so it can be written out or inspected on its own. The key values must be unique within each csv: a key repeated in s or in other makes the diff ambiguous, and it returns nil, nil, nil and false. It also returns nil, nil, nil and false if either csv is empty or the key is not a header name in either csv. A csv with only the header row is a valid input: all the other side's data rows are then `added` or `removed`:
yesterday, _ := simplecsv.ReadCsvFile("yesterday.csv")
today, _ := simplecsv.ReadCsvFile("today.csv")
added, removed, changed, ok := yesterday.DiffByKey(today, "id")
* ERROR VARIANTS *
A boolean `ok` tells you that something failed but not why: "did the join fail because a field is missing or because the result headers would collide?" The `E` variants of the functions where the reason matters most return an error instead, with a message prefixed with `simplecsv:` that describes the problem. The bool-returning functions are unchanged, and the `E` variants return the same csv the bool variants return on failure (an independent copy of the receiver for methods; nil for `ConcatE` and `ConcatByNameE`):
joined, err := people.JoinE(orders, "id", "customer_id")
if err != nil {
log.Fatal(err) // e.g. simplecsv: cannot join: key field "customer_id" not found in the right csv
}
`JoinE` / `LeftJoinE` work like `Join` / `LeftJoin`. The error says which csv is empty, which key field is missing in which csv, or which right columns would collide with left columns:
joined, err := people.LeftJoinE(orders, "id", "customer_id") if err != nil { log.Fatal(err) }
`GroupByE` works like `GroupBy`. The error says whether the csv is empty, no keys or aggregations are given, a key appears more than once, a key or aggregation field does not exist, an `As` name is empty or duplicates another key or `As` name, or an aggregation operation is invalid:
summary, err := sales.GroupByE( []string{"country"}, []Agg{{Field: "amount", Op: AggSum, As: "total"}}, ) if err != nil { log.Fatal(err) }
`ConcatE` / `ConcatByNameE` work like `Concat` / `ConcatByName`. The error says whether no csv has a header row, a csv has different headers (`ConcatE`), or a csv has duplicate header names (`ConcatByNameE`):
combined, err := simplecsv.ConcatE(day1, day2, day3) if err != nil { log.Fatal(err) }
* BOOLEAN FUNCTIONS *
Use boolean functions AND, OR and NOT to combine indexes and produce other complex indexes that point to rows in the csv. Very useful to produce search results.
Indexes are slices of row numbers (integers between 0 or 1 and the length of the csv -1). The indexes returned by OrIndex, AndIndex and NotIndex are sorted in ascending order.
OR ¶
The `OrIndex` function accepts any number of operands. With no operands it returns an empty slice; with one operand it returns a sorted, de-duplicated copy. With 2 or more operands it returns their union:
var w []int w = simplecsv.OrIndex(a, b) w = simplecsv.OrIndex(a, b, c, d, e)
AND ¶
The `AndIndex` function accepts any number of operands. With no operands it returns an empty slice; with one operand it returns a sorted, de-duplicated copy. With 2 or more operands it returns their intersection:
var p []int p = simplecsv.AndIndex(a, b) p = simplecsv.AndIndex(a, b, c, d, e)
NOT ¶
The code below returns the negative of the index `g`, between row 1 and row 4. If `g` is an index with the values `{1, 2}` the negative of `g` is `{3, 4}`. Because 3 and 4 are the integers between 1 and 4 that are not in `g`.
var g []int min := 1 max := 4 p = simplecsv.NotIndex(g, min, max)
Note: For csvs with headers the min value is usually 1 and for csvs without headers the min value is usually 0.
* ONLY *
Only are 2 functions to simplify and sort a csv.
ONLY THIS ROWS ¶
It removes rows that are not in the index and reorders a CSV by the index order. If header is true, it starts by the csv header. Note: if header is true and the index contains 0, the header row appears twice in the result (once as the header, once as row 0).
newIndex := []int{1, 3}
header := true
x, _ = x.OnlyThisRows(newIndex, header)
ONLY THIS FIELDS ¶
Removes fields that are not in the list of fields, reorders the CSV by the list of fields and adds fields that do not exist as blank fields.
fieldsList := []string{"Age", "ID"}
x, _ = x.OnlyThisFields(fieldsList)
Index ¶
- func AndIndex(indexes ...[]int) []int
- func NotIndex(index []int, min, max int) []int
- func OrIndex(indexes ...[]int) []int
- func SortIndex(index []int) []int
- type Agg
- type AggOp
- type SimpleCsv
- func Concat(csvs ...SimpleCsv) (SimpleCsv, bool)
- func ConcatByName(csvs ...SimpleCsv) (SimpleCsv, bool)
- func ConcatByNameE(csvs ...SimpleCsv) (SimpleCsv, error)
- func ConcatE(csvs ...SimpleCsv) (SimpleCsv, error)
- func CreateEmptyCsv(columnNames []string) (SimpleCsv, error)
- func CreateEmpyCsv(columnNames []string) (SimpleCsv, error)deprecated
- func MustCreateEmptyCsv(headers []string) SimpleCsv
- func ReadCsv(r io.Reader, comma rune) (SimpleCsv, error)
- func ReadCsvFile(filename string) (SimpleCsv, bool)
- func ReadCsvFileComma(filename string, comma rune) (SimpleCsv, error)
- func ReadCsvFileE(filename string) (SimpleCsv, error)
- func ReadCsvLimit(r io.Reader, comma rune, maxRecords, maxBytes int64) (SimpleCsv, error)
- func (s SimpleCsv) AddColumnByField(columnName string, values []string) (SimpleCsv, bool)
- func (s SimpleCsv) AddComputedColumn(columnName string, fn func(row map[string]string) string) (SimpleCsv, bool)
- func (s SimpleCsv) AddEmptyColumn(columnName string) (SimpleCsv, bool)
- func (s SimpleCsv) AddEmptyColumnAt(columnName string, index int) (SimpleCsv, bool)
- func (s SimpleCsv) AddRow(rowValue []string) (SimpleCsv, bool)
- func (s SimpleCsv) AddRowFromMap(rowValue map[string]string) (SimpleCsv, bool)
- func (s SimpleCsv) AppendRows(other SimpleCsv) (SimpleCsv, bool)
- func (s SimpleCsv) CombineFields(names []string, sep, as string) (SimpleCsv, bool)
- func (s SimpleCsv) DeleteRow(rowNumber int) (SimpleCsv, bool)
- func (s SimpleCsv) DiffByKey(other SimpleCsv, key string) (added, removed, changed SimpleCsv, ok bool)
- func (s SimpleCsv) Distinct(field string) ([]string, bool)
- func (s SimpleCsv) EachDataRow(fn func(rowIndex int, row []string, asMap map[string]string) bool)
- func (s SimpleCsv) FillColumnByField(columnName string, value string) (SimpleCsv, bool)
- func (s SimpleCsv) FilterByField(name string, pred func(value string) bool) (SimpleCsv, bool)
- func (s SimpleCsv) FilterRows(predicate func(row []string) bool, header bool) SimpleCsv
- func (s SimpleCsv) FindInColumn(columnPosition int, word string) ([]int, bool)
- func (s SimpleCsv) FindInColumnCaseSensitive(columnPosition int, word string) ([]int, bool)
- func (s SimpleCsv) FindInField(columnName string, word string) ([]int, bool)
- func (s SimpleCsv) FindInFieldCaseSensitive(columnName string, word string) ([]int, bool)
- func (s SimpleCsv) FullJoin(other SimpleCsv, leftField, rightField string) (SimpleCsv, bool)
- func (s SimpleCsv) FullJoinByField(other SimpleCsv, fieldName string) (SimpleCsv, bool)
- func (s SimpleCsv) FullJoinOn(other SimpleCsv, leftFields, rightFields []string) (SimpleCsv, bool)
- func (s SimpleCsv) GetCell(column int, row int) (string, bool)
- func (s SimpleCsv) GetCellByField(columnName string, row int) (string, bool)
- func (s SimpleCsv) GetColumn(column int) ([]string, bool)
- func (s SimpleCsv) GetColumnByField(columnName string) ([]string, bool)
- func (s SimpleCsv) GetDataRows() [][]string
- func (s SimpleCsv) GetHeader(columnPosition int) (string, bool)
- func (s SimpleCsv) GetHeaderPosition(columnName string) int
- func (s SimpleCsv) GetHeaders() []string
- func (s SimpleCsv) GetNumberDataRows() int
- func (s SimpleCsv) GetNumberRows() int
- func (s SimpleCsv) GetRow(rowNumber int) ([]string, bool)
- func (s SimpleCsv) GetRowAsMap(rowNumber int) (map[string]string, bool)
- func (s SimpleCsv) GroupBy(keys []string, aggs []Agg) (SimpleCsv, bool)
- func (s SimpleCsv) GroupByE(keys []string, aggs []Agg) (SimpleCsv, error)
- func (s SimpleCsv) Head(n int) SimpleCsv
- func (s SimpleCsv) Join(other SimpleCsv, leftField, rightField string) (SimpleCsv, bool)
- func (s SimpleCsv) JoinByField(other SimpleCsv, fieldName string) (SimpleCsv, bool)
- func (s SimpleCsv) JoinE(other SimpleCsv, leftField, rightField string) (SimpleCsv, error)
- func (s SimpleCsv) JoinOn(other SimpleCsv, leftFields, rightFields []string) (SimpleCsv, bool)
- func (s SimpleCsv) LeftJoin(other SimpleCsv, leftField, rightField string) (SimpleCsv, bool)
- func (s SimpleCsv) LeftJoinByField(other SimpleCsv, fieldName string) (SimpleCsv, bool)
- func (s SimpleCsv) LeftJoinE(other SimpleCsv, leftField, rightField string) (SimpleCsv, error)
- func (s SimpleCsv) LeftJoinOn(other SimpleCsv, leftFields, rightFields []string) (SimpleCsv, bool)
- func (s SimpleCsv) MapColumnByField(columnName string, fn func(value string, row int) string) (SimpleCsv, bool)
- func (s SimpleCsv) MatchInColumn(columnPosition int, regularexpression string) ([]int, bool)
- func (s SimpleCsv) MatchInColumnCaseSensitive(columnPosition int, regularexpression string) ([]int, bool)
- func (s SimpleCsv) MatchInField(columnName string, regularexpression string) ([]int, bool)
- func (s SimpleCsv) MatchInFieldCaseSensitive(columnName string, regularexpression string) ([]int, bool)
- func (s SimpleCsv) MergeColumns(other SimpleCsv) (SimpleCsv, bool)
- func (s SimpleCsv) MoveColumn(columnName string, newIndex int) (SimpleCsv, bool)
- func (s SimpleCsv) OnlyThisFields(fields []string) (SimpleCsv, bool)
- func (s SimpleCsv) OnlyThisRows(rowsIndex []int, header bool) (SimpleCsv, bool)
- func (s SimpleCsv) RemoveColumn(columnPosition int) (SimpleCsv, bool)
- func (s SimpleCsv) RemoveColumnByName(columnName string) (SimpleCsv, bool)
- func (s SimpleCsv) RenameHeader(oldHeader string, newHeader string) (SimpleCsv, bool)
- func (s SimpleCsv) RenameHeaders(pairs map[string]string) (SimpleCsv, bool)
- func (s SimpleCsv) ReplaceInField(columnName string, old string, new string) (SimpleCsv, bool)
- func (s SimpleCsv) RightJoin(other SimpleCsv, leftField, rightField string) (SimpleCsv, bool)
- func (s SimpleCsv) RightJoinByField(other SimpleCsv, fieldName string) (SimpleCsv, bool)
- func (s SimpleCsv) RightJoinOn(other SimpleCsv, leftFields, rightFields []string) (SimpleCsv, bool)
- func (s SimpleCsv) SanitizeFormulas() SimpleCsv
- func (s SimpleCsv) SetCell(column int, row int, value string) (SimpleCsv, bool)
- func (s SimpleCsv) SetCellByField(columnName string, row int, value string) (SimpleCsv, bool)
- func (s SimpleCsv) SetColumn(column int, values []string) (SimpleCsv, bool)
- func (s SimpleCsv) SetColumnByField(columnName string, values []string) (SimpleCsv, bool)
- func (s SimpleCsv) SetRow(rowNumber int, rowValue []string) (SimpleCsv, bool)
- func (s SimpleCsv) SetRowFromMap(rowNumber int, rowValue map[string]string) (SimpleCsv, bool)
- func (s SimpleCsv) SliceRows(start, end int) (SimpleCsv, bool)
- func (s SimpleCsv) SortByColumn(columnPosition int, ascending bool) (SimpleCsv, bool)
- func (s SimpleCsv) SortByField(columnName string, ascending bool) (SimpleCsv, bool)
- func (s SimpleCsv) SortByFields(fields []string, ascending []bool) (SimpleCsv, bool)
- func (s SimpleCsv) SplitField(name, sep string, newNames []string) (SimpleCsv, bool)
- func (s SimpleCsv) Tail(n int) SimpleCsv
- func (s SimpleCsv) TrimSpace() (SimpleCsv, bool)
- func (s SimpleCsv) Unique() SimpleCsv
- func (s SimpleCsv) UniqueByFields(fields ...string) (SimpleCsv, bool)
- func (s SimpleCsv) UpdateRowCellsFromMap(rowNumber int, rowValue map[string]string) (SimpleCsv, bool)
- func (s SimpleCsv) ValueCounts(field string) (SimpleCsv, bool)
- func (s SimpleCsv) Where(name, value string) (SimpleCsv, bool)
- func (s SimpleCsv) WhereFold(name, value string) (SimpleCsv, bool)
- func (s SimpleCsv) WriteCsvFile(filename string) bool
- func (s SimpleCsv) WriteCsvFileComma(filename string, comma rune) error
- func (s SimpleCsv) WriteCsvFileE(filename string) error
- func (s SimpleCsv) WriteTo(w io.Writer, comma rune) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AndIndex ¶
AndIndex operates an AND operator in the indexes: it returns the values present in every index, treating each index as a set (duplicates inside one index do not count as presence in the others) With no operands it returns an empty slice; with one operand it returns a sorted, de-duplicated copy. The result is sorted in ascending order
func NotIndex ¶
NotIndex negates the index between a min value and a max value The min value tipically is either 0 or 1 (without and with headers) The max value tipically is the csv length - 1 (number of rows -1). A table with a header and 2 rows (length = 3) can have indexes with values of 1 and 2
Types ¶
type Agg ¶
type Agg struct {
Field string // source column; ignored for AggCount
Op AggOp
As string // output header; required; must be unique among keys+As
}
Agg describes one aggregation: it produces one output column for every group, aggregated from the Field column of the source csv with the operation Op. As is the output header: it must be non-empty and unique among the key field names and the other As names.
type AggOp ¶
type AggOp int
AggOp describes the operation an aggregation applies to the Field column of the source csv.
const ( // AggCount counts the data rows of the group. Field is ignored and may // be empty. AggCount AggOp = iota // AggSum adds the Field cells parsed with strconv.ParseFloat; cells // that don't parse as numbers count as 0. AggSum // AggMin is the smallest finite numeric Field cell of the group; a // group without finite numeric cells produces an empty cell. AggMin // AggMax is the largest finite numeric Field cell of the group; a // group without finite numeric cells produces an empty cell. AggMax // AggFirst is the Field value of the first data row of the group. AggFirst // AggLast is the Field value of the last data row of the group. AggLast // AggJoin joins the non-empty Field values of the group with ",". AggJoin )
type SimpleCsv ¶
type SimpleCsv [][]string
SimpleCsv is the type for simple csv
func Concat ¶
Concat returns a new csv with the data rows of all the csvs stacked vertically: the result header row is the header row of the first non-empty csv, followed by copies of the data rows of every csv in argument order. Every csv with a header row must have exactly the same header names in the same order; empty csvs (without a header row) are skipped. The original csvs are not modified and the result shares no data with them. If there is no non-empty csv, or two csvs have different headers, it returns nil and false.
func ConcatByName ¶
ConcatByName returns a new csv with the data rows of all the csvs stacked vertically, aligning the columns by header name instead of by position: the result header row is the header row of the first non-empty csv, followed by the header names that only appear in later csvs, in order of first appearance. Each data row of every csv is copied into the result with the value of each column placed under its header name, and an empty string where the csv has no column with that name. Empty csvs (without a header row) are skipped. The original csvs are not modified and the result shares no data with them. If there is no non-empty csv, or any csv has duplicate header names (the result would be ambiguous and break the unique-header invariant), it returns nil and false.
func ConcatByNameE ¶
ConcatByNameE is like ConcatByName, but it returns an error describing the reason of the failure instead of false. On failure it returns nil.
func ConcatE ¶
ConcatE is like Concat, but it returns an error describing the reason of the failure instead of false. On failure it returns nil.
func CreateEmptyCsv ¶
CreateEmptyCsv creates an empty CSV with the headers passed as a slice. The headers are copied, changes to the original slice don't affect the csv. At least one header is required. Header names must be unique, like database columns: if columnNames is empty or contains duplicates the returned error is non-nil and the returned csv is nil.
func CreateEmpyCsv
deprecated
func MustCreateEmptyCsv ¶
MustCreateEmptyCsv is like CreateEmptyCsv but panics if the headers are invalid (empty or duplicated). It is handy in tests and small scripts where the header list is part of the program, not a runtime condition:
people := simplecsv.MustCreateEmptyCsv([]string{"id", "name"})
func ReadCsv ¶
ReadCsv reads all the records from r using comma as the field separator and returns a SimpleCsv or an error. The entire input is read into memory; use ReadCsvLimit for bounded reads.
The first record is treated as the header row. Header names must be unique, like database columns: a file with duplicate headers in the first row is rejected with an error. All rows must have the same number of fields as the header row: a ragged file is rejected with an error. This makes uniform row width an invariant of a SimpleCsv, alongside the unique-headers invariant.
A UTF-8 byte order mark (BOM, written by Excel and many Windows tools at the start of the file) is stripped from the first cell of the first row, so the first header is "id" and not "\ufeffid".
func ReadCsvFile ¶
ReadCsvFile reads a file and returns a SimpleCsv. The second value is false if there's an error reading the file or parsing the CSV.
func ReadCsvFileComma ¶
ReadCsvFileComma reads a delimited file (for example a semicolon or tab separated file) and returns a SimpleCsv or an error.
func ReadCsvFileE ¶
ReadCsvFileE reads a file and returns a SimpleCsv or an error.
func ReadCsvLimit ¶
ReadCsvLimit reads records from r using comma as the field separator and returns a SimpleCsv or an error. maxRecords is the maximum number of CSV records, including the header row, and maxBytes is the maximum number of input bytes passed to the CSV parser. A zero limit disables that limit; negative limits are rejected. If a limit is exceeded, the returned csv is nil.
Unlike ReadCsv, this function does not use csv.Reader.ReadAll. Use positive limits when reading input that may be large or attacker-controlled.
Like ReadCsv, a UTF-8 byte order mark (BOM) is stripped from the first cell of the first row if present.
func (SimpleCsv) AddColumnByField ¶
AddColumnByField appends a column with the header name columnName and the data cells values, and returns a new csv. len(values) must equal the number of data rows (len(s)-1), because every data row must keep exactly one cell in the column; a csv with only the header row takes an empty values slice. The values are copied: changes to the original slice don't affect the csv. The original csv is not modified and the result shares no data with it. If the csv is empty, the column name already exists or the length of values does not match, it returns a copy of the csv and false.
func (SimpleCsv) AddComputedColumn ¶
func (s SimpleCsv) AddComputedColumn(columnName string, fn func(row map[string]string) string) (SimpleCsv, bool)
AddComputedColumn appends a column with the header name columnName whose data cells are computed by fn from the values of the other columns, and returns a new csv. fn is called once per data row, in csv order, with a map from header name to cell value: the map is built fresh for every row and is a copy, so the callback can read field names instead of column indexes, and mutating the map does not affect the csv. The string fn returns becomes the cell of the new column. A csv with only the header row takes the new header cell only and fn is not called. The original csv is not modified and the result shares no data with it. If the csv is empty or the column name already exists, it returns a copy of the csv and false.
func (SimpleCsv) AddEmptyColumn ¶
AddEmptyColumn adds an empty column at the end of the csv and returns a new csv. The original csv is not modified. columnName must not already exist as a header.
func (SimpleCsv) AddEmptyColumnAt ¶
AddEmptyColumnAt inserts an empty column at position index and returns a new csv. index must be between 0 and the number of columns (inclusive): inserting at the number of columns appends, like AddEmptyColumn. The header cell of the new column is columnName and every data cell is empty. The original csv is not modified and the result shares no data with it. If the csv is empty, the column name already exists or index is out of range, it returns a copy of the csv and false.
func (SimpleCsv) AddRow ¶
AddRow adds a row at the end of the csv and returns a new csv The row is copied, changes to the original slice don't affect the csv The original csv is not modified
func (SimpleCsv) AddRowFromMap ¶
AddRowFromMap adds map values to a row. Ignores keys with unexisting headers Fills blank where the key does not exists
func (SimpleCsv) AppendRows ¶
AppendRows returns a new csv with the data rows of other appended after the data rows of s. other must have exactly the same header names in the same order as s (other's header row itself is not appended). The original csvs are not modified and the result shares no data with them. If s or other is empty, or their headers differ, it returns a copy of s and false.
func (SimpleCsv) CombineFields ¶
CombineFields appends a column with the header name as whose data cells join the values of the named columns with sep, and returns a new csv. The source columns stay in place; the new column is appended at the end. Every name in names must exist as a header, and names must not repeat a field. The header cells are never joined. The original csv is not modified and the result shares no data with it. If the csv is empty, names is empty, a name does not exist, names repeats a field, or as already exists as a header, it returns a copy of the csv and false.
func (SimpleCsv) DeleteRow ¶
DeleteRow deletes the row and returns a new csv The original csv is not modified. Deleting row 0 is allowed only when the promoted row (if any) has unique header names.
func (SimpleCsv) DiffByKey ¶
func (s SimpleCsv) DiffByKey(other SimpleCsv, key string) (added, removed, changed SimpleCsv, ok bool)
DiffByKey compares the data rows of s (left) and other (right), keyed by the values of the column with the header name key. It returns three new csvs:
- added: the data rows of other whose key is not present in s, with other's header row
- removed: the data rows of s whose key is not present in other, with s's header row
- changed: the data rows of s whose key is present in other but whose full row differs, with s's header row and s's values (callers that want the right-side values can join on the key)
The rows keep their order of appearance in the source csv. The key values must be unique within each csv: a key repeated in s or in other makes the diff ambiguous, and it returns nil, nil, nil and false. It also returns nil, nil, nil and false if either csv is empty or the key is not a header name in either csv. A csv with only the header row is a valid input (all the other side's rows are then added or removed). The original csvs are not modified and the results share no data with them.
func (SimpleCsv) Distinct ¶
Distinct returns the distinct values of the data cells of the column with the header name field, in order of first appearance. The header cell in row 0 is never included, and the returned slice is a copy: changes to it don't affect the csv. If the csv is empty or the field name does not exist, it returns an empty slice and false. A csv with only the header row returns an empty slice and true.
func (SimpleCsv) EachDataRow ¶
EachDataRow calls fn for each data row (rowIndex >= 1), in csv order, with a copy of the row and a map view of it (header name to cell value). The row is a copy, so mutating it inside fn does not affect the csv; the map is rebuilt per row and is independent too. fn may return false to stop the iteration early. The header row is never passed to fn: an empty csv or a csv with only the header row calls fn zero times.
func (SimpleCsv) FillColumnByField ¶
FillColumnByField returns a new csv where every data cell of the column with the header name columnName is set to value. The header cell in row 0 is not modified and the original csv is not modified. If the csv is empty or the field name does not exist, it returns a copy of the csv and false.
func (SimpleCsv) FilterByField ¶
FilterByField returns a new csv with the header row and the data rows where pred(value) returns true for the value of the named column. The predicate is called once per data row, in csv order, and never with the header cell. The original csv is not modified and the result shares no data with it. If the csv is empty or the field name does not exist, it returns a copy of the csv and false.
func (SimpleCsv) FilterRows ¶
FilterRows returns a new csv with copies of the rows where predicate returns true. If header is true, the first row is kept as the header and is not filtered. The original csv is not modified. The predicate receives a copy of each row and cannot mutate the source.
func (SimpleCsv) FindInColumn ¶
FindInColumn returns a slice with the rownumbers where the "word" is in the columnPosition If the column is not valid it returns an empty slice and a second false value The search includes row 0 (the header row), unlike FindInField. Case does not matter: Foo = foo = FOO.
func (SimpleCsv) FindInColumnCaseSensitive ¶
FindInColumnCaseSensitive is like FindInColumn but case matters: Foo != foo. The search includes row 0 (the header row), unlike FindInFieldCaseSensitive.
func (SimpleCsv) FindInField ¶
FindInField returns a slice with the rownumbers where the "word" is in the column name If the column is not valid it returns an empty slice and a second false value It never includes the header row in the search result. Case does not matter: Foo = foo = FOO.
func (SimpleCsv) FindInFieldCaseSensitive ¶
FindInFieldCaseSensitive is like FindInField but case matters: Foo != foo.
func (SimpleCsv) FullJoin ¶
FullJoin returns a new csv with the full (outer) join of s and other by the column leftField of s and the column rightField of other, which may have different names. Every row of s and every row of other is in the result: rows with a matching join value are joined to all their matches, and rows without a match are included with empty strings in the columns of the other side.
Join values are compared exactly: case matters ("Ana" != "ana"). Rows shorter than the join column position are treated as having an empty string in that cell, and cells beyond the header width are not in the result.
The result headers are the headers of s followed by the headers of other excluding the join column rightField (the left join column is kept). Non-join columns of other must not share names with columns of s; a collision is rejected so header names stay unique.
The result rows are in the order of s, with the rows of other that had no match appended at the end.
If leftField or rightField does not exist in its csv, or if the result would have duplicate headers, it returns a copy of s and false. The source csvs are not modified and the result shares no rows with them. When leftField and rightField have the same name, FullJoin is equivalent to FullJoinByField.
func (SimpleCsv) FullJoinByField ¶
FullJoinByField is like FullJoin, joining by the column fieldName, which must exist in both csvs.
func (SimpleCsv) FullJoinOn ¶
FullJoinOn is like JoinOn, but every row of s and every row of other is in the result: rows with a matching join key are joined to all their matches, and rows without a match are included with empty strings in the columns of the other side.
func (SimpleCsv) GetCell ¶
GetCell Returns the value of a cell by position. If the cell does not exist, it returns the second value as false.
func (SimpleCsv) GetCellByField ¶
GetCellByField returns the value of a cell by column name. If the cell does not exist, it returns the second value as false.
func (SimpleCsv) GetColumn ¶
GetColumn returns a copy of the data cells of the column in position column, skipping the header cell in row 0. The caller can mutate the returned slice without affecting the csv. If the csv is empty or the column position is not valid, it returns an empty slice and false.
func (SimpleCsv) GetColumnByField ¶
GetColumnByField returns a copy of the data cells of the column with the header name columnName, skipping the header cell in row 0. The caller can mutate the returned slice without affecting the csv. If the csv is empty or the field name does not exist, it returns an empty slice and false.
func (SimpleCsv) GetDataRows ¶
GetDataRows returns copies of all the data rows (rows 1 to len(s)-1), excluding the header row. The caller can mutate the returned rows without affecting the csv. If the csv is empty or has only the header row, it returns an empty slice.
func (SimpleCsv) GetHeader ¶
GetHeader returns the header name in a position Returns false as the second value if it does not exist
func (SimpleCsv) GetHeaderPosition ¶
GetHeaderPosition returns the header position, returns -1 if it does not exist
func (SimpleCsv) GetHeaders ¶
GetHeaders returns a copy of the headers as a slice If the csv is empty it returns an empty slice
func (SimpleCsv) GetNumberDataRows ¶
GetNumberDataRows returns the number of data rows, excluding the header row. For an empty csv or a csv with only the header row it returns 0. Use this instead of GetNumberRows when counting data: the header row is not data.
func (SimpleCsv) GetNumberRows ¶
GetNumberRows returns the number of rows, including the header row
func (SimpleCsv) GetRow ¶
GetRow returns a copy of the row rowNumber If rowNumber does not exist, it returns an empty slice and false
func (SimpleCsv) GetRowAsMap ¶
GetRowAsMap returns the row as a map If the row does not exist, returns nil and false If the row is shorter than the headers, missing cells are empty strings
func (SimpleCsv) GroupBy ¶
GroupBy collapses the data rows of s into one output row per group, where a group is all the data rows whose key fields have the same values. The output header row is the key field names followed by the aggregation As names, and each output data row is the key values followed by one cell per aggregation, in the order of aggs. Groups appear in order of first occurrence (the csv order of their first row) and aggregations process the rows of a group in csv order.
AggCount counts the data rows of the group. AggSum adds the Field cells parsed with strconv.ParseFloat: cells that don't parse as numbers count as 0. AggMin and AggMax use only finite numeric cells (NaN and infinities are skipped); a group without any finite numeric cell produces an empty cell. AggFirst and AggLast take the Field value of the first and last data row of the group. AggJoin joins the non-empty Field values with ",". Numeric results are formatted with strconv.FormatFloat(f, 'f', -1, 64), so integers have no decimal noise.
The original csv is not modified and the result shares no data with it. ok is false if the csv is empty, no keys or no aggregations are given, a key or aggregation field does not exist, an As name is empty or duplicates another key or As name, or an aggregation operation is not one of the AggOp constants. On failure a copy of the csv is returned.
func (SimpleCsv) GroupByE ¶
GroupByE is like GroupBy, but it returns an error describing the reason of the failure instead of false. On failure it returns an independent copy of s.
func (SimpleCsv) Head ¶
Head returns a new csv with the header row and the first n data rows, or all the data rows if there are fewer than n. If n is negative or zero, the result has no data rows (the header row only, if present). The original csv is not modified and the result shares no data with it.
func (SimpleCsv) Join ¶
Join returns a new csv with the inner join of s and other by the column leftField of s and the column rightField of other, which may have different names. Only the rows whose join value exists in both csvs are in the result. If the same join value shows up more than once, all combinations of rows are in the result.
Join values are compared exactly: case matters ("Ana" != "ana"). Rows shorter than the join column position are treated as having an empty string in that cell, and cells beyond the header width are not in the result.
The result headers are the headers of s followed by the headers of other excluding the join column rightField (the left join column is kept). Non-join columns of other must not share names with columns of s; a collision is rejected so header names stay unique.
If leftField or rightField does not exist in its csv, or if the result would have duplicate headers, it returns a copy of s and false. The source csvs are not modified and the result shares no rows with them. When leftField and rightField have the same name, Join is equivalent to JoinByField.
func (SimpleCsv) JoinByField ¶
JoinByField returns a new csv with the inner join of s and other by the column fieldName, which must exist in both csvs. Only the rows whose join value exists in both csvs are in the result. If the same join value shows up more than once, all combinations of rows are in the result.
Join values are compared exactly: case matters ("Ana" != "ana"). Rows shorter than the join column position are treated as having an empty string in that cell, and cells beyond the header width are not in the result.
The result headers are the headers of s followed by the headers of other excluding the join column. Non-join columns of other must not share names with columns of s; a collision is rejected so header names stay unique.
If fieldName does not exist in one of the csvs, or if the result would have duplicate headers, it returns a copy of s and false. The source csvs are not modified and the result shares no rows with them.
func (SimpleCsv) JoinE ¶
JoinE is like Join, but it returns an error describing the reason of the failure instead of false. On failure it returns an independent copy of s.
func (SimpleCsv) JoinOn ¶
JoinOn returns a new csv with the inner join of s and other by several columns at once. The i-th name of leftFields must be a column of s and the i-th name of rightFields a column of other: a row of s is joined to a row of other when the values of all its left key columns are exactly equal to the values of the corresponding right key columns. Only the rows whose join key exists in both csvs are in the result. If the same join key shows up more than once, all combinations of rows are in the result.
Join values are compared exactly: case matters ("Ana" != "ana"). Rows shorter than a join column position are treated as having an empty string in that cell, and cells beyond the header width are not in the result. The join key is built with a length-prefixed encoding, so values containing separators cannot collide.
The result headers are the headers of s followed by the headers of other excluding all the right key columns (the left key columns are kept). Non-key columns of other must not share names with columns of s; a collision is rejected so header names stay unique.
If s or other is empty, leftFields and rightFields have different lengths or are empty, one of the fields does not exist in its csv, or the result would have duplicate headers, it returns a copy of s and false. The source csvs are not modified and the result shares no rows with them. When both field lists have a single name, JoinOn is equivalent to Join.
func (SimpleCsv) LeftJoin ¶
LeftJoin is like Join, but the rows of s without a match in other are also in the result, with empty strings in the columns of other.
func (SimpleCsv) LeftJoinByField ¶
LeftJoinByField is like JoinByField, but the rows of s without a match in other are also in the result, with empty strings in the columns of other.
func (SimpleCsv) LeftJoinE ¶
LeftJoinE is like LeftJoin, but it returns an error describing the reason of the failure instead of false. On failure it returns an independent copy of s.
func (SimpleCsv) LeftJoinOn ¶
LeftJoinOn is like JoinOn, but the rows of s without a match in other are also in the result, with empty strings in the columns of other.
func (SimpleCsv) MapColumnByField ¶
func (s SimpleCsv) MapColumnByField(columnName string, fn func(value string, row int) string) (SimpleCsv, bool)
MapColumnByField returns a new csv where every data cell of the column with the header name columnName is replaced by fn(value, row), called once per data row in csv order. row is the csv row index (1 for the first data row). The header cell in row 0 is not mapped and the original csv is not modified. If the csv is empty or the field name does not exist, it returns a copy of the csv and false.
func (SimpleCsv) MatchInColumn ¶
MatchInColumn returns a slice with the rownumbers where the regular expression applies in the columnPosition. Matching is case-insensitive by default: Foo = foo = FOO. Use MatchInColumnCaseSensitive when case matters. If the column or regular expression are not valid it returns an empty slice and a second false value. The search includes row 0 (the header row), unlike MatchInField.
func (SimpleCsv) MatchInColumnCaseSensitive ¶
func (s SimpleCsv) MatchInColumnCaseSensitive(columnPosition int, regularexpression string) ([]int, bool)
MatchInColumnCaseSensitive is like MatchInColumn but case matters: Foo != foo. The search includes row 0 (the header row), unlike MatchInFieldCaseSensitive. If the column or regular expression are not valid it returns an empty slice and a second false value.
func (SimpleCsv) MatchInField ¶
MatchInField returns a slice with the rownumbers where the regular expression applies in the column name. Matching is case-insensitive by default: Foo = foo = FOO. Use MatchInFieldCaseSensitive when case matters. It never includes the header row in the search result. If the field name or regular expression are not valid it returns an empty slice and a second false value.
func (SimpleCsv) MatchInFieldCaseSensitive ¶
func (s SimpleCsv) MatchInFieldCaseSensitive(columnName string, regularexpression string) ([]int, bool)
MatchInFieldCaseSensitive is like MatchInField but case matters: Foo != foo. It never includes the header row in the search result. If the field name or regular expression are not valid it returns an empty slice and a second false value.
func (SimpleCsv) MergeColumns ¶
MergeColumns returns a new csv with the columns of other placed side by side with the columns of s: the result header row is the header row of s followed by the header row of other, and each result data row is the data row of s followed by the data row of other in the same position. Both csvs must have the same number of rows (header included) and no header name of other may collide with a header name of s, so header names stay unique. Padding is not allowed: row counts must match exactly. The original csvs are not modified and the result shares no data with them. If s or other is empty, their row counts differ, or the result would have duplicate headers, it returns a copy of s and false.
func (SimpleCsv) MoveColumn ¶
MoveColumn moves the column with the header name columnName to position newIndex and returns a new csv. The columns between the old and the new position shift by one, and every row keeps its cells: the header and data cells of the moved column move together. newIndex must be a valid column position (0 to len(headers)-1); moving a column to its own position is a no-op and is allowed. The original csv is not modified and the result shares no data with it. If the csv is empty, the column name does not exist or newIndex is out of range, it returns a copy of the csv and false.
func (SimpleCsv) OnlyThisFields ¶
OnlyThisFields returns a simplecsv with the fields. At least one field name is required (empty or nil fields is rejected).
func (SimpleCsv) OnlyThisRows ¶
OnlyThisRows removes all rows that are not in the index and sorts the csv by the index order All rows must exist or it fails If header is true, the header row is always the first row of the result, so including 0 in rowsIndex duplicates the header row in the result
func (SimpleCsv) RemoveColumn ¶
RemoveColumn removes the column in position X and returns a new csv. The original csv is not modified
func (SimpleCsv) RemoveColumnByName ¶
RemoveColumnByName removes column by name
func (SimpleCsv) RenameHeader ¶
RenameHeader renames the header and returns a new csv Returns the second value as false if it did't found the oldHeader or if newHeader already exists as a different column: the library requires header names to be unique, so a rename that would produce a duplicate is rejected. Renaming a header to its own name is a no-op and is allowed. The original csv is not modified
func (SimpleCsv) RenameHeaders ¶
RenameHeaders renames several headers in one call and returns a new csv. Every key of pairs must be an existing header, and the result must not contain duplicate header names: a rename fails if a key does not exist, two keys map to the same new name, or a renamed column collides with a header that is not renamed. The renames are applied simultaneously: the new name of every column is looked up from the original header row, so a swap ("a" -> "b", "b" -> "a") is applied correctly and map iteration order does not matter. Renaming a header to its own name is a no-op for that column. An empty pairs map is a no-op that returns a copy of the csv and true. The original csv is not modified and the result shares no data with it. If the csv is empty, it returns a copy of the csv and false.
func (SimpleCsv) ReplaceInField ¶
ReplaceInField returns a new csv where every data cell of the column with the header name columnName whose value is exactly equal to old (case-sensitive, whole cell) is replaced by new. The header cell in row 0 is not modified and the original csv is not modified. If the csv is empty or the field name does not exist, it returns a copy of the csv and false.
func (SimpleCsv) RightJoin ¶
RightJoin returns a new csv with the right join of s and other by the column leftField of s and the column rightField of other, which may have different names. Every row of other is in the result: rows with a matching join value in s are joined to all their matches, and rows of other without a match are included with empty strings in the columns of s. The rows of s without a match in other are not in the result.
Join values are compared exactly: case matters ("Ana" != "ana"). Rows shorter than the join column position are treated as having an empty string in that cell, and cells beyond the header width are not in the result.
The result headers are the headers of s followed by the headers of other excluding the join column rightField (the left join column is kept). Non-join columns of other must not share names with columns of s; a collision is rejected so header names stay unique.
The result rows are in the order of s, with the rows of other that had no match appended at the end.
If leftField or rightField does not exist in its csv, or if the result would have duplicate headers, it returns a copy of s and false. The source csvs are not modified and the result shares no rows with them. When leftField and rightField have the same name, RightJoin is equivalent to RightJoinByField.
func (SimpleCsv) RightJoinByField ¶
RightJoinByField is like RightJoin, joining by the column fieldName, which must exist in both csvs.
func (SimpleCsv) RightJoinOn ¶
RightJoinOn is like JoinOn, but every row of other is in the result: rows with a matching join key in s are joined to all their matches, and rows of other without a match are included with empty strings in the columns of s. The rows of s without a match in other are not in the result. The rows of other that had no match are appended at the end.
func (SimpleCsv) SanitizeFormulas ¶
SanitizeFormulas returns a copy of the csv where every cell whose first rune is one of the formula-injection trigger characters =, +, -, @, tab, carriage return, line feed, or the full-width variants =, +, -, @ is prefixed with a single quote ' (the Excel and LibreOffice Calc convention), so that spreadsheet applications treat the value as text instead of interpreting it as a formula.
This mitigates CSV / formula injection: a cell whose value is, for example, "=cmd|'/c calc'!A1" triggers command execution when the file is opened in a spreadsheet, because encoding/csv does not neutralize such values on write (it only quotes fields that contain the delimiter, the quote character or a newline). Sanitizing is recommended whenever the file may contain attacker-controlled data (exported logs, form input, scraped content, etc.) and may later be opened in a spreadsheet.
Escaping changes data: "=cmd|'/c calc'!A1" becomes "'=cmd|'/c calc'!A1" and a negative number like "-5" becomes "'-5" (the leading ' is hidden by the spreadsheet, which displays -5 as text). It is therefore opt-in: callers that do not want their bytes changed should not call it, and the unsanitized WriteTo / WriteCsvFile* functions write values verbatim by design. The written bytes are only changed when SanitizeFormulas is applied first, for example:
err = s.SanitizeFormulas().WriteCsvFileE("export.csv")
err = s.SanitizeFormulas().WriteTo(os.Stdout, ',')
SanitizeFormulas returns an independent copy: the original csv is not modified and shares no data with the returned csv, like the other methods that return a SimpleCsv.
func (SimpleCsv) SetCell ¶
SetCell changes the value of a cell and returns a new csv The original csv is not modified. Setting a header cell (row 0) that would produce duplicate header names is rejected.
func (SimpleCsv) SetCellByField ¶
SetCellByField changes the value of a cell with a specific column
func (SimpleCsv) SetColumn ¶
SetColumn replaces the data cells of the column in position column with the values slice and returns a new csv. The header cell in row 0 is not modified and the original csv is not modified. len(values) must equal the number of data rows (len(s)-1), because every data row must keep exactly one cell in the column. The values are copied, changes to the original slice don't affect the csv. If the csv is empty, the column position is not valid or the length of values does not match, it returns a copy of the csv and false.
func (SimpleCsv) SetColumnByField ¶
SetColumnByField is SetColumn for the column with the header name columnName. If the csv is empty or the field name does not exist, it returns a copy of the csv and false.
func (SimpleCsv) SetRow ¶
SetRow updates a row in the csv and returns a new csv The row is copied, changes to the original slice don't affect the csv The original csv is not modified. Setting row 0 to a value with duplicate header names is rejected.
func (SimpleCsv) SetRowFromMap ¶
SetRowFromMap replaces a row by the maps value Ignores keys with unexisting headers Fills blank where the key does not exists
func (SimpleCsv) SliceRows ¶
SliceRows returns a new csv with the header row and the data rows whose csv row index is in the half-open range [start, end) (end is not included; data rows have csv index >= 1). If end is beyond the last row, all the available data rows are included. An empty range (end == start) is valid and returns the header row only. The original csv is not modified and the result shares no data with it. If the csv is empty, start is less than 1 or end is less than start, it returns a copy of the csv and false.
func (SimpleCsv) SortByColumn ¶
SortByColumn sorts all the rows of the csv by the column in columnPosition and returns a new sorted csv. The original csv is not modified, and later changes to the sorted csv do not affect it. All rows are sorted, including the header row (row 0), unlike SortByField. Finite numbers are sorted numerically among themselves and appear before non-numeric values (including NaN and Inf), which are sorted as strings. The sort is stable. If the column position is not valid it returns a copy of the csv and false.
func (SimpleCsv) SortByField ¶
SortByField sorts the csv by the column with the name columnName and returns a new sorted csv. The header row stays at the top and the original csv is not modified, and later changes to the sorted csv do not affect it. Finite numbers are sorted numerically among themselves and appear before non-numeric values (including NaN and Inf), which are sorted as strings. The sort is stable. If the column name does not exist it returns a copy of the csv and false.
func (SimpleCsv) SortByFields ¶
SortByFields sorts the csv by several columns, one level per field in order, and returns a new sorted csv. The header row stays at the top and the original csv is not modified, and later changes to the sorted csv do not affect it. A later field is only compared when every earlier field compares equal, like ORDER BY in SQL. Each field is sorted according to the corresponding bool in ascending: true for ascending, false for descending. If ascending is empty, every field is sorted ascending; otherwise its length must equal len(fields). Each field uses the same ordering as SortByField: finite numbers are sorted numerically among themselves and appear before non-numeric values (including NaN and Inf), which are sorted as strings. The sort is stable: data rows that compare equal on every field keep their csv order. ok is false if the csv is empty, no fields are given, a field name does not exist, or len(ascending) is neither 0 nor len(fields). On failure a copy of the csv is returned.
func (SimpleCsv) SplitField ¶
SplitField replaces the column with the header name name by len(newNames) new columns in its position, and returns a new csv. Every data cell of the original column is split on sep with strings.Split, and the parts fill the new columns in order: if a cell has fewer parts than newNames, the remaining new columns get empty cells; if it has more parts, the extra parts are dropped. The original column is removed, so a new name may be the same as it but must not collide with any other header and the names in newNames must be unique. The header cell is replaced, never split. The original csv is not modified and the result shares no data with it. If the csv is empty, the field name does not exist, newNames is empty or contains duplicates, or a new name collides with another header, it returns a copy of the csv and false.
func (SimpleCsv) Tail ¶
Tail returns a new csv with the header row and the last n data rows, or all the data rows if there are fewer than n. If n is negative or zero, the result has no data rows (the header row only, if present). The original csv is not modified and the result shares no data with it.
func (SimpleCsv) TrimSpace ¶
TrimSpace returns a new csv where the surrounding whitespace of every cell, including the header cells in row 0, has been removed with strings.TrimSpace. The original csv is not modified. Trimming can merge header names, so if the trimmed header row contains duplicate names the operation is rejected (the library requires unique headers): it returns a copy of the csv and false.
func (SimpleCsv) Unique ¶
Unique returns a new csv with the same header row and only the first occurrence of each full data row (all columns), in csv order. The original csv is not modified and the result shares no data with it. An empty csv or a csv with only the header row is returned as an independent copy.
func (SimpleCsv) UniqueByFields ¶
UniqueByFields returns a new csv with the same header row and, for each key built from the values of the named columns, only the first data row with that key is kept, in csv order. The original csv is not modified and the result shares no data with it. If the csv is empty, no fields are given, or a field name does not exist as a header, it returns a copy of the csv and false.
func (SimpleCsv) UpdateRowCellsFromMap ¶
func (s SimpleCsv) UpdateRowCellsFromMap(rowNumber int, rowValue map[string]string) (SimpleCsv, bool)
UpdateRowCellsFromMap updates the cells whose column name is a key in the map and maintains the value of all the others. Ignores keys with unexisting headers. A key with an empty string value sets the cell to an empty string.
func (SimpleCsv) ValueCounts ¶
ValueCounts returns a new csv with the fixed headers value and count: one row per distinct value of the data cells of the column with the header name field, in order of first appearance, with the number of data rows that have that value as a decimal string. The header cell in row 0 is never counted. The original csv is not modified and the result shares no data with it. If the csv is empty or the field name does not exist, it returns a copy of the csv and false. A csv with only the header row yields a csv with just the value,count headers and true.
func (SimpleCsv) Where ¶
Where returns a new csv with the header row and the data rows where the named column is exactly equal to value. The comparison is case-sensitive: "Active" does not match "active". The header cell is never matched. The original csv is not modified and the result shares no data with it. If the csv is empty or the field name does not exist, it returns a copy of the csv and false.
func (SimpleCsv) WhereFold ¶
WhereFold is like Where but the comparison is case-insensitive (strings.EqualFold): "Active", "active" and "ACTIVE" all match. The header cell is never matched. The original csv is not modified and the result shares no data with it. If the csv is empty or the field name does not exist, it returns a copy of the csv and false.
func (SimpleCsv) WriteCsvFile ¶
WriteCsvFile writes the SimpleCsv to a file. Returns false if there's an error creating or writing the file.
func (SimpleCsv) WriteCsvFileComma ¶
WriteCsvFileComma writes the SimpleCsv to a file using comma as the field separator and returns an error on failure.
The write is atomic: the csv is first written to a temporary file in the same directory as filename and then renamed over it, so a failed or interrupted write cannot truncate or corrupt the destination. The temporary file (and therefore the destination after the rename) is created with mode 0600 (owner-readable only), so written files are not world-readable by default; callers that need different permissions can os.Chmod the result. Because the destination is replaced with os.Rename rather than opened, a symlink planted at filename is replaced instead of followed: the write does not follow a symlink at the destination.
func (SimpleCsv) WriteCsvFileE ¶
WriteCsvFileE writes the SimpleCsv to a file and returns an error on failure.