Working with CSV files
Overview
A CSV (comma-separated values) file is a table, where each row is a record, and each column is a field. The file typically has a header row, where each field in the row describes the data in its column. The fields are typically separated by a comma. For example:
"name","type","breed","age"
"Spot","dog","Collie",3
"Rover","dog","Boxer",5
"Felix","cat","Malicious",7
CSV files are often used to manually transfer data between systems. Spreadsheet applications such as Microsoft Excel and LibreOffice Math are common sources. CSV files are an ideal data source for creating tables in documentation, and for performing queries.
Do not place CSV files in Hugo’s
data
directory. You may place JSON, TOML, XML, and YAML files in thedata
directory, but no other file types.
With the transform.Unmarshal
function Hugo can unmarshal CSV data to an array of arrays. From this starting point we can use a shortcode to insert an HTML table, or a partial to access the data for queries and iteration.
Insert a table
Use a shortcode to insert a table on a page.
Arguments
path
- (
string
) The path to the CSV file, either a page resource, or a global resource in theassets
directory. caption
- (
string
) Optional. The table caption. class
- (
string
) Optional. Theclass
attribute of the table element. hasHeaderRow
- (
bool
) Optional. Set tofalse
if the CSV file does not have a header row. The default value istrue
. id
- (
string
) Optional. Theid
attribute of the table element. delimiter
- (
string
) Optional. The delimiting character between the fields in each row. The default value is a comma.
Example
{{< csv-to-table path="pets.csv" >}}
name | type | breed | age |
---|---|---|---|
Spot | dog | Collie | 3 |
Rover | dog | Boxer | 5 |
Felix | cat | Malicious | 7 |
Source code
layouts/shortcodes/csv-to-table.html
{{- /* Last modified: 2023-06-30T12:24:14-07:00 */}}
{{- /*
Copyright 2023 Veriphor LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
*/}}
{{- /*
Renders an HTML table from a CSV file.
The CSV file must be a page resouce, or a global resource in the assets
directory.
@context {string} Inner The content between the opening and closing shortcode tags.
@context {string} InnerDeindent The content between the opening and closing shortcode tags with indentation removed.
@context {string} Name The file name of the shortcode template, excluding the extension.
@context {int} Ordinal The zero-based ordinal of the shortcode on the page, or within its parent shortcode.
@context {page} Page A reference to the page containing the shortcode.
@context {map} Params The parameters specified in the opening shortcode tag.
@context {hugolib.ShortcodeWithPage} Parent The context of the parent shortcode.
@context {text.Position} Position The position of the shortcode within the page content.
@method {any} Get Returns the parameter value for the given key (for named parameters) or position (for positional parameters).
@mathod {bool} IsNamedParams Returns true if the shortcode is called with named instead of positional parameters.
@method {maps.Scratch) Scratch Returns a writable Scratch to store and manipulate data.
@param {string} Params.path The path to the CSV file.
@param {string} [Params.caption] The table caption.
@param {string} [Params.class] The class attribute of the table element.
@param {string} [Params.delimiter=","] The delimiting character between the fields in each row.
@param {bool} [Params.hasHeaderRow=true] Set to true if the CSV file has a header row.
@param {string} [Params.id] The id attribute of the table element.
@returns {template.html}
@example {{< csv-to-table file="data.csv" >}}
@example {{< csv-to-table file="data.csv" hasHeaderRow=false >}}
@example {{< csv-to-table file="data.csv" caption="Annual rainfall by region" >}}
*/}}
{{- /* Verify minimum required version. */}}
{{- $minHugoVersion := "0.114.0" }}
{{- if lt hugo.Version $minHugoVersion }}
{{- errorf "The %s shortcode requires Hugo v%s or later." .Name $minHugoVersion }}
{{- end }}
{{- /* Get context. */}}
{{- $name := .Name }}
{{- $ordinal := .Ordinal }}
{{- $position := .Position }}
{{- /* Get parameters. */}}
{{- $caption := or (.Get "caption") "" }}
{{- $class := or (.Get "class") "" }}
{{- $delimiter := or (.Get "delimiter") "," }}
{{- $id := or (.Get "id") (printf "h-sc-%s-%d" $name $ordinal) }}
{{- $path := .Get "path" }}
{{- if not $path }}
{{- errorf "The %q shortcode requires a path parameter. See %s" $name $position }}
{{- end }}
{{- $hasHeaderRow := true }}
{{- if isset .Params "hasHeaderRow" }}
{{- if in (slice false "false") (.Get "hasHeaderRow") }}
{{- $hasHeaderRow = false }}
{{- end }}
{{- end }}
{{- /* Define attributes map. */}}
{{- $attrs := dict "class" $class "id" $id }}
{{- /* Get data. */}}
{{- $r := "" }}
{{- with .Page.Resources.Get $path }}
{{- $r = . }}
{{- else }}
{{- with resources.Get $path }}
{{- $r = . }}
{{- else }}
{{- errorf "The %q shortcode was unable to find %q. See %s" $name $position }}
{{- end }}
{{- end }}
{{- $data := unmarshal (dict "delimiter" $delimiter) $r }}
{{- /* Render.*/}}
<table
{{- range $k, $v := $attrs }}
{{- if $v }}
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
{{- end }}
{{- end -}}
>
{{- with $caption }}
<caption>{{ . }}</caption>
{{- end }}
{{- $rows := slice }}
{{- if $hasHeaderRow }}
{{- $headerRow := index $data 0 }}
{{- $rows = after 1 $data }}
<thead>
<tr>
{{- range $headerRow }}
<th>{{ . }}</th>
{{- end }}
</tr>
</thead>
{{- else }}
{{- $rows = $data }}
{{- end }}
<tbody>
{{- range $i, $row := $rows }}
<tr>
{{- range $row }}
<td>{{ . }}</td>
{{- end }}
</tr>
{{- end }}
</tbody>
</table>
Access the data
Hugo unmarshals CSV data to an array of arrays. To query or iterate over the data, use a partial to transform CSV data into a usable structure—an array of maps. The CSV file must have a header row.
Arguments
page
- (
page
) A page reference, typically the current page. path
- (
string
) The path to the CSV file, either a page resource, or a global resource in theassets
directory. delimiter
- (
string
) Optional. The delimiting character between the fields in each row. The default value is a comma.
Example
{{ $options := dict "page" . "path" "pets.csv" }}
{{ $data := partial "csv-to-data.html" $options }}
The resulting maps include a key/value pair indicating the original position in the CSV file.
[
{
"age": "3",
"breed": "Collie",
"name": "Spot",
"row": 1,
"type": "dog"
},
{
"age": "5",
"breed": "Boxer",
"name": "Rover",
"row": 2,
"type": "dog"
},
{
"age": "7",
"breed": "Malicious",
"name": "Felix",
"row": 3,
"type": "cat"
}
]
Now you can query and iterate over the data.
{{ range where $data "type" "dog" }}
{{ .name }}
{{ end }}
Source code
layouts/partials/csv-to-data.html
{{- /* Last modified: 2023-06-30T12:24:14-07:00 */}}
{{- /*
Copyright 2023 Veriphor LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
*/}}
{{- /*
Returns a slice of maps from a CSV file.
The CSV file must be a page resouce, or a global resource in the assets
directory, and it must have a header row.
@context {page} page A reference to the page with the page resource, or the current page.
@context {string} path The path to the CSV file.
@context {string} [delimiter=","] The delimiting character between the fields in each row.
@returns {template.html}
@example {{ $data := partial "csv-to-map.html" $options (dict "page" . "path" "pets.csv") }}
*/}}
{{- /* Initialize. */}}
{{- $partialName := "csv-to-data" }}
{{- /* Verify minimum required version. */}}
{{- $minHugoVersion := "0.114.0" }}
{{- if lt hugo.Version $minHugoVersion }}
{{- errorf "The %q partial requires Hugo v%s or later." $partialName $minHugoVersion }}
{{- end }}
{{- /* Get context. */}}
{{- $page := .page }}
{{- $path := .path }}
{{- $delimiter := or .delimiter "," }}
{{- /* Validate path. */}}
{{- if not $path }}
{{- errorf "The %q partial requires a path parameter" $partialName }}
{{- end }}
{{- /* Get resource. */}}
{{- $r := "" }}
{{- with $page.Resources.Get $path }}
{{- $r = . }}
{{- else }}
{{- with resources.Get $path }}
{{- $r = . }}
{{- else }}
{{- errorf "The %q partial was unable to find %q" $partialName $path }}
{{- end }}
{{- end }}
{{- /* Unmarshal resource. */}}
{{- $data := unmarshal (dict "delimiter" $delimiter) $r }}
{{- $headerRow := index $data 0 }}
{{- $s := slice }}
{{- range $k, $v := $data }}
{{- if $k }}
{{- $m := dict "row" $k }}
{{- range $k, $v := . }}
{{- $m = merge $m (dict (index $headerRow $k) $v) }}
{{- end }}
{{- $s = $s | append $m }}
{{- end }}
{{- end }}
{{- $data = $s }}
{{- /* Return data. */}}
{{- return $data }}