Current section

Files

Jump to
xlsx_reader lib xlsx_reader.ex
Raw

lib/xlsx_reader.ex

defmodule XlsxReader do
@moduledoc """
Opens XLSX workbook and reads its worksheets.
## Example
```elixir
{:ok, package} = XlsxReader.open("test.xlsx")
XlsxReader.sheet_names(package)
# ["Sheet 1", "Sheet 2", "Sheet 3"]
{:ok, rows} = XlsxReader.sheet(package, "Sheet 1")
# [
# ["Date", "Temperature"],
# [~D[2019-11-01], 8.4],
# [~D[2019-11-02], 7.5],
# ...
# ]
```
## Sheet contents
Sheets are loaded on-demand by `sheet/3` and `sheets/2`.
The sheet contents is returned as a list of lists:
```elixir
[
["A1", "B1", "C1" | _],
["A2", "B2", "C2" | _],
["A3", "B3", "C3" | _],
| _
]
```
The behavior of the sheet parser can be customized for each
individual sheet, see `sheet/3`.
"""
alias XlsxReader.{PackageLoader, ZipArchive}
@typedoc """
Source for the XLSX file: file system (`:path`) or in-memory (`:binary`)
"""
@type source :: :path | :binary
@typedoc """
Option to specify the XLSX file source
"""
@type source_option :: {:source, source()}
@typedoc """
List of cell values
"""
@type row :: list(any())
@typedoc """
List of rows
"""
@type rows :: list(row())
@typedoc """
Sheet name
"""
@type sheet_name :: String.t()
@typedoc """
Error tuple with message describing the cause of the error
"""
@type error :: {:error, String.t()}
@doc """
Opens an XLSX file located on the file system (default) or from memory.
## Examples
### Opening XLSX file on the file system
```elixir
{:ok, package} = XlsxReader.open("test.xlsx")
```
### Opening XLSX file from memory
```elixir
blob = File.read!("test.xlsx")
{:ok, package} = XlsxReader.open(blob, source: :binary)
```
## Options
* `source`: `:path` (on the file system, default) or `:binary` (in memory)
"""
@spec open(String.t() | binary(), [source_option]) ::
{:ok, XlsxReader.Package.t()} | error()
def open(file, options \\ []) do
file
|> ZipArchive.handle(Keyword.get(options, :source, :path))
|> PackageLoader.open()
end
@doc """
Lists the names of the sheets in the package's workbook
"""
@spec sheet_names(XlsxReader.Package.t()) :: [sheet_name()]
def sheet_names(package) do
for %{name: name} <- package.workbook.sheets, do: name
end
@doc """
Loads the sheet with the given name (see `sheet_names/1`)
## Options
* `type_conversion` - boolean (default: `true`)
* `blank_value` - placeholder value for empty cells (default: `""`)
* `empty_rows` - include empty rows (default: `true`)
* `number_type` - type used for numeric conversion :`Integer`, 'Decimal' or `Float` (default: `Float`)
The `Decimal` type requires the [decimal](https://github.com/ericmj/decimal) library.
"""
@spec sheet(XlsxReader.Package.t(), sheet_name(), Keyword.t()) :: {:ok, rows()}
def sheet(package, sheet_name, options \\ []) do
PackageLoader.load_sheet_by_name(package, sheet_name, options)
end
@doc """
Loads all the sheets in the workbook.
On success, returns a list of `{sheet_name, rows}` pairs.
## Options
See `sheet/2`.
"""
@spec sheets(XlsxReader.Package.t(), Keyword.t()) ::
{:ok, list({sheet_name(), rows()})} | error()
def sheets(package, options \\ []) do
load_all_sheets(package, options, package.workbook.sheets, [])
end
##
@spec load_all_sheets(
XlsxReader.Package.t(),
Keyword.t(),
list(XlsxReader.Sheet.t()),
list({sheet_name(), rows()})
) ::
{:ok, list({sheet_name(), rows()})} | error()
defp load_all_sheets(_package, _options, [], acc), do: {:ok, Enum.reverse(acc)}
defp load_all_sheets(package, options, [sheet | sheets], acc) do
case PackageLoader.load_sheet_by_rid(package, sheet.rid, options) do
{:ok, rows} ->
load_all_sheets(package, options, sheets, [{sheet.name, rows} | acc])
error ->
error
end
end
end