changed README.md
 
@@ -23,7 23,7 @@ Via hex, in mix.exs:
23
23
24
24
```Elixir
25
25
defp deps do
26
- [{:elixlsx, "~> 0.4.1"}]
26
[{:elixlsx, "~> 0.4.2"}]
27
27
end
28
28
```
changed hex_metadata.config
 
@@ -18,10 18,11 @@
18
18
<<"lib/elixlsx/style/cell_style.ex">>,<<"lib/elixlsx/style/fill.ex">>,
19
19
<<"lib/elixlsx/style/font.ex">>,<<"lib/elixlsx/style/num_fmt.ex">>,
20
20
<<"lib/elixlsx/util.ex">>,<<"lib/elixlsx/workbook.ex">>,
21
- <<"lib/elixlsx/writer.ex">>,<<"lib/elixlsx/xml_templates.ex">>,
22
- <<"mix.exs">>,<<"README.md">>,<<"LICENSE">>]}.
21
<<"lib/elixlsx/writer.ex">>,<<"lib/elixlsx/xml.ex">>,
22
<<"lib/elixlsx/xml_templates.ex">>,<<"mix.exs">>,<<"README.md">>,
23
<<"LICENSE">>]}.
23
24
{<<"licenses">>,[<<"MIT">>]}.
24
25
{<<"links">>,[{<<"GitHub">>,<<"https://github.com/xou/elixlsx">>}]}.
25
26
{<<"name">>,<<"elixlsx">>}.
26
27
{<<"requirements">>,[]}.
27
- {<<"version">>,<<"0.4.1">>}.
28
{<<"version">>,<<"0.4.2">>}.
changed lib/elixlsx/compiler.ex
 
@@ -25,7 25,7 @@ defmodule Elixlsx.Compiler do
25
25
26
26
def compinfo_cell_pass_value wci, value do
27
27
cond do
28
- is_binary(value) && String.valid?(value)
28
is_binary(value) && XML.valid?(value)
29
29
-> update_in wci.stringdb, &StringDB.register_string(&1, value)
30
30
true -> wci
31
31
end
changed lib/elixlsx/compiler/cell_style_db.ex
 
@@ -46,29 46,29 @@ defmodule Elixlsx.Compiler.CellStyleDB do
46
46
@spec register_all(WorkbookCompInfo.t) :: WorkbookCompInfo.t
47
47
def register_all(wci) do
48
48
Enum.reduce wci.cellstyledb.cellstyles, wci, fn ({style, _}, wci) ->
49
- wci = if is_nil(style.font) do
50
- wci
51
- else
52
- update_in(wci.fontdb, &(FontDB.register_font &1, style.font))
53
- end
54
- wci = if is_nil(style.fill) do
55
- wci
56
- else
57
- update_in(wci.filldb, &(FillDB.register_fill &1, style.fill))
58
- end
59
- wci = if is_nil(style.numfmt) do
60
- wci
61
- else
62
- update_in(wci.numfmtdb, &(NumFmtDB.register_numfmt &1, style.numfmt))
63
- end
64
- wci = if is_nil(style.border) do
65
- wci
66
- else
67
- update_in(wci.borderstyledb, &(BorderStyleDB.register_border &1, style.border))
68
- end
69
-
70
49
wci
50
|> update_font(style.font)
51
|> update_fill(style.fill)
52
|> update_numfmt(style.numfmt)
53
|> update_border(style.border)
71
54
# TODO: update_in wci.borderstyledb ...; wci.fillstyledb...
72
55
end
73
56
end
57
58
defp update_border(wci, nil), do: wci
59
60
defp update_border(wci, border), do: update_in(wci.borderstyledb, &(BorderStyleDB.register_border &1, border))
61
62
defp update_fill(wci, nil), do: wci
63
64
defp update_fill(wci, fill), do: update_in(wci.filldb, &(FillDB.register_fill &1, fill))
65
66
defp update_font(wci, nil), do: wci
67
68
defp update_font(wci, font), do: update_in(wci.fontdb, &(FontDB.register_font &1, font))
69
70
defp update_numfmt(wci, nil), do: wci
71
72
defp update_numfmt(wci, numfmt), do: update_in(wci.numfmtdb, &(NumFmtDB.register_numfmt &1, numfmt))
73
74
74
end
changed lib/elixlsx/compiler/string_db.ex
 
@@ -23,8 23,14 @@ defmodule Elixlsx.Compiler.StringDB do
23
23
def get_id(stringdb, s) do
24
24
case Map.fetch(stringdb.strings, s) do
25
25
:error ->
26
- raise %ArgumentError{
27
- message: "Invalid key provided for StringDB.get_id: " <> inspect(s)}
26
if XML.valid?(s) do
27
raise %ArgumentError{
28
message: "Invalid key provided for StringDB.get_id: " <> inspect(s)}
29
else
30
# if the xml is invalid, then we never wanted it in the stringdb to
31
# begin with
32
-1
33
end
28
34
{:ok, id} ->
29
35
id
30
36
end
changed lib/elixlsx/sheet.ex
 
@@ -16,16 16,19 @@ defmodule Elixlsx.Sheet do
16
16
The property list describes formatting options for that
17
17
cell. See Font.from_props/1 for a list of options.
18
18
"""
19
- defstruct name: "", rows: [], col_widths: %{}, row_heights: %{}, merge_cells: [], pane_freeze: nil, show_grid_lines: true
19
defstruct name: "", rows: [], col_widths: %{}, row_heights: %{}, group_cols: [], group_rows: [], merge_cells: [], pane_freeze: nil, show_grid_lines: true
20
20
@type t :: %Sheet {
21
21
name: String.t,
22
22
rows: list(list(any())),
23
23
col_widths: %{pos_integer => number},
24
24
row_heights: %{pos_integer => number},
25
- merge_cells: [],
25
group_cols: list(rowcol_group),
26
group_rows: list(rowcol_group),
27
merge_cells: [{String.t, String.t}],
26
28
pane_freeze: {number, number} | nil,
27
29
show_grid_lines: boolean()
28
30
}
31
@type rowcol_group :: Range.t | {Range.t, opts :: keyword}
29
32
30
33
@doc ~S"""
31
34
Create a sheet with a sheet name.
 
@@ -140,6 143,36 @@ defmodule Elixlsx.Sheet do
140
143
&(Map.put &1, row_idx, height)
141
144
end
142
145
146
@spec group_cols(Sheet.t, String.t, String.t) :: Sheet.t
147
@doc ~S"""
148
Group given column range. (i.e. increase outline level by one)
149
Column is indexed by name ("A", ...)
150
151
## Options
152
153
- `collapsed`: if true, collapse this group.
154
"""
155
def group_cols(sheet, first_col, last_col, opts \\ []) do
156
col_range = Range.new(Util.decode_col(first_col), Util.decode_col(last_col))
157
new_group = if opts === [], do: col_range, else: {col_range, opts}
158
update_in(sheet.group_cols, fn groups -> groups [new_group] end)
159
end
160
161
@spec group_rows(Sheet.t, pos_integer, pos_integer) :: Sheet.t
162
@doc ~S"""
163
Group given row range. (i.e. increase outline level by one)
164
Row is indexed starting from 1.
165
166
## Options
167
168
- `collapsed`: if true, collapse this group.
169
"""
170
def group_rows(sheet, first_row_idx, last_row_idx, opts \\ []) do
171
row_range = Range.new(first_row_idx, last_row_idx)
172
new_group = if opts === [], do: row_range, else: {row_range, opts}
173
update_in(sheet.group_rows, fn groups -> groups [new_group] end)
174
end
175
143
176
@spec set_pane_freeze(Sheet.t, number, number) :: Sheet.t
144
177
@doc ~S"""
145
178
Set the pane freeze at the given row and column. Row and column are indexed starting from 1.
changed lib/elixlsx/style/num_fmt.ex
 
@@ -12,7 12,7 @@ defmodule Elixlsx.Style.NumFmt do
12
12
props[:yyyymmdd] -> date_yyyy_mm_dd()
13
13
props[:datetime] -> date_datetime()
14
14
props[:num_format] ->
15
- if String.valid? props[:num_format] do
15
if XML.valid? props[:num_format] do
16
16
%NumFmt{format: props[:num_format]}
17
17
else
18
18
raise %ArgumentError{
changed lib/elixlsx/util.ex
 
@@ -1,5 1,5 @@
1
1
defmodule Elixlsx.Util do
2
- @col_alphabet to_string(Enum.to_list(?A..?Z))
2
@col_alphabet Enum.to_list(?A..?Z)
3
3
4
4
@doc ~S"""
5
5
returns the column letter(s) associated with a column index. Col idx starts at 1.
 
@@ -16,14 16,17 @@ defmodule Elixlsx.Util do
16
16
@spec encode_col(non_neg_integer) :: String.t
17
17
def encode_col(0), do: ""
18
18
def encode_col(num) when num <= 26, do: <<num 64>>
19
- def encode_col(num) do
19
20
def encode_col(num, suffix \\ "")
21
def encode_col(num, suffix) when num <= 26, do: <<num 64>> <> suffix
22
def encode_col(num, suffix) do
20
23
mod = div(num, 26)
21
24
rem = rem(num, 26)
22
25
23
26
if rem == 0 do
24
- encode_col(mod - 1) <> encode_col(26)
27
encode_col(mod - 1, "Z" <> suffix)
25
28
else
26
- encode_col(mod) <> encode_col(rem)
29
encode_col(mod, <<rem 64>> <> suffix)
27
30
end
28
31
end
29
32
 
@@ -40,29 43,22 @@ defmodule Elixlsx.Util do
40
43
41
44
"""
42
45
@spec decode_col(list(char()) | String.t) :: non_neg_integer
43
- def decode_col s do
44
- cond do
45
- is_list s -> decode_col(to_string s)
46
- String.valid? s -> decode_col_ s
47
- true -> raise %ArgumentError{message: "decode_col expects string or charlist, got "
48
- <> inspect s}
46
def decode_col(s) when is_list(s), do: decode_col(to_string s)
47
def decode_col(""), do: 0
48
def decode_col(s) when is_binary(s) do
49
case String.match? s, ~r/^[A-Z]*$/ do
50
false ->
51
raise %ArgumentError{message: "Invalid column string: " <> inspect s}
52
53
true ->
54
# translate list of strings to the base-26 value they represent
55
Enum.map(String.to_charlist(s), (fn x -> :string.chr(@col_alphabet, x) end)) |>
56
# multiply and aggregate them
57
List.foldl(0, (fn (x, acc) -> x 26 * acc end))
49
58
end
50
59
end
51
-
52
-
53
- @spec decode_col_(String.t) :: non_neg_integer
54
- defp decode_col_("") do 0 end
55
- defp decode_col_(s) do
56
- alphabet_list = String.to_charlist @col_alphabet
57
-
58
- if !String.match? s, ~r/^[A-Z]*$/ do
59
- raise %ArgumentError{message: "Invalid column string: " <> inspect s}
60
- end
61
-
62
- # translate list of strings to the base-26 value they represent
63
- Enum.map(String.to_charlist(s), (fn x -> :string.chr(alphabet_list, x) end)) |>
64
- # multiply and aggregate them
65
- List.foldl(0, (fn (x, acc) -> x 26 * acc end))
60
def decode_col(s) do
61
raise %ArgumentError{message: "decode_col expects string or charlist, got " <> inspect s}
66
62
end
67
63
68
64
 
@@ -167,7 163,7 @@ defmodule Elixlsx.Util do
167
163
iso_from_datetime(:calendar.now_to_universal_time({div(input, 1000000), rem(input, 1000000), 0}))
168
164
# TODO this case should parse the string i guess
169
165
# TODO also prominently absent: [char].
170
- String.valid? input ->
166
XML.valid? input ->
171
167
input
172
168
true -> raise "Invalid input to iso_timestamp." <> (inspect input)
173
169
end
 
@@ -252,4 248,3 @@ defmodule Elixlsx.Util do
252
248
String.replace(@version, ~r/(\d )\.(\d )\.(\d )/, "\\1.\\2\\3")
253
249
end
254
250
end
255
-
added lib/elixlsx/xml.ex
 
@@ -0,0 1,19 @@
1
defmodule XML do
2
@xml_chars_block_1 [9, 10, 13]
3
@xml_chars_block_2 32..55_295
4
@xml_chars_block_3 57_344..65_533
5
@xml_chars_block_4 65_536..1_114_111
6
7
# From the xml spec 1.0: https://www.w3.org/TR/REC-xml/#charsets
8
# Character Range
9
# any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.
10
# Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
11
def valid?(<<h::utf8, t::binary>>)
12
when h in @xml_chars_block_1 or h in @xml_chars_block_2
13
or h in @xml_chars_block_3 or h in @xml_chars_block_4 do
14
valid?(t)
15
end
16
17
def valid?(<<>>), do: true
18
def valid?(_), do: false
19
end
changed lib/elixlsx/xml_templates.ex
 
@@ -142,24 142,18 @@ defmodule Elixlsx.XMLTemplates do
142
142
### xl/worksheet/sheet*.xml
143
143
###
144
144
145
- defp split_into_content_style(cell, wci) do
146
- cond do
147
- is_list(cell) ->
148
- cellstyle = CellStyle.from_props (tl cell)
149
- {
150
- hd(cell),
151
- CellStyleDB.get_id(wci.cellstyledb, cellstyle),
152
- cellstyle
153
- }
154
- true ->
155
- {
156
- cell,
157
- 0,
158
- nil
159
- }
160
- end
145
defp split_into_content_style([h | t], wci) do
146
cellstyle = CellStyle.from_props(t)
147
148
{
149
h,
150
CellStyleDB.get_id(wci.cellstyledb, cellstyle),
151
cellstyle
152
}
161
153
end
162
154
155
defp split_into_content_style(cell, _wci), do: {cell, 0, nil}
156
163
157
defp get_content_type_value(content, wci) do
164
158
case content do
165
159
{:excelts, num} ->
 
@@ -171,7 165,13 @@ defmodule Elixlsx.XMLTemplates do
171
165
x when is_number(x) ->
172
166
{"n", to_string(x)}
173
167
x when is_binary(x) ->
174
- {"s", to_string(StringDB.get_id wci.stringdb, x)}
168
id = StringDB.get_id(wci.stringdb, x)
169
170
if id == -1 do
171
{:empty, :empty}
172
else
173
{"s", to_string(id)}
174
end
175
175
x when is_boolean(x) ->
176
176
{"b", if x do "1" else "0" end}
177
177
:empty ->
 
@@ -184,59 184,76 @@ defmodule Elixlsx.XMLTemplates do
184
184
185
185
# TODO i know now about string interpolation, i should probably clean this up. ;)
186
186
defp xl_sheet_cols(row, rowidx, wci) do
187
- Enum.zip(row, 1 .. length row) |>
188
- Enum.map(
189
- fn {cell, colidx} ->
187
{updated_row, _id} =
188
row
189
|> List.foldl({"", 1}, fn cell, {acc, colidx} ->
190
190
{content, styleID, cellstyle} = split_into_content_style(cell, wci)
191
191
192
if is_nil(content) do
192
- ""
193
{acc, colidx 1}
193
194
else
194
- content = if CellStyle.is_date? cellstyle do
195
- U.to_excel_datetime content
196
- else
197
- content
198
- end
195
content =
196
if CellStyle.is_date?(cellstyle) do
197
U.to_excel_datetime(content)
198
else
199
content
200
end
199
201
200
202
cv = get_content_type_value(content, wci)
203
201
204
{content_type, content_value, content_opts} =
202
- case cv do
203
- {t, v} -> {t, v, []}
204
- {t, v, opts} -> {t, v, opts}
205
- :error -> raise %ArgumentError{
206
- message: "Invalid column content at " <>
207
- U.to_excel_coords(rowidx, colidx) <> ": "
208
- <> (inspect content)
209
- }
210
- end
205
case cv do
206
{t, v} ->
207
{t, v, []}
211
208
212
- case content_type do
213
- :formula ->
214
- value = if not is_nil(content_opts[:value]), do: "<v>#{content_opts[:value]}</v>", else: ""
209
{t, v, opts} ->
210
{t, v, opts}
215
211
216
- """
217
- <c r="#{U.to_excel_coords(rowidx, colidx)}"
218
- s="#{styleID}">
219
- <f>#{content_value}</f>
220
- #{value}
221
- </c>
222
- """
223
- :empty ->
224
- """
225
- <c r="#{U.to_excel_coords(rowidx, colidx)}"
226
- s="#{styleID}">
227
- </c>
228
- """
229
- type ->
230
- """
231
- <c r="#{U.to_excel_coords(rowidx, colidx)}"
232
- s="#{styleID}" t="#{type}">
233
- <v>#{content_value}</v>
234
- </c>
235
- """
236
- end
237
- end
238
- end) |>
239
- List.foldr("", &<>/2)
212
:error ->
213
raise %ArgumentError{
214
message:
215
"Invalid column content at " <>
216
U.to_excel_coords(rowidx, colidx) <> ": " <> inspect(content)
217
}
218
end
219
220
cell_xml =
221
case content_type do
222
:formula ->
223
value =
224
if not is_nil(content_opts[:value]),
225
do: "<v>#{content_opts[:value]}</v>",
226
else: ""
227
228
"""
229
<c r="#{U.to_excel_coords(rowidx, colidx)}"
230
s="#{styleID}">
231
<f>#{content_value}</f>
232
#{value}
233
</c>
234
"""
235
236
:empty ->
237
"""
238
<c r="#{U.to_excel_coords(rowidx, colidx)}"
239
s="#{styleID}">
240
</c>
241
"""
242
243
type ->
244
"""
245
<c r="#{U.to_excel_coords(rowidx, colidx)}"
246
s="#{styleID}" t="#{type}">
247
<v>#{content_value}</v>
248
</c>
249
"""
250
end
251
252
{acc <> cell_xml, colidx 1}
253
end
254
end)
255
256
updated_row
240
257
end
241
258
242
259
defp xl_merge_cells([]) do
 
@@ -253,14 270,23 @@ defmodule Elixlsx.XMLTemplates do
253
270
"""
254
271
end
255
272
256
- defp xl_sheet_rows(data, row_heights, wci) do
257
- Enum.zip(data, 1 .. length data) |>
258
- Enum.map_join(fn {row, rowidx} ->
273
defp xl_sheet_rows(data, row_heights, grouping_info, wci) do
274
rows =
275
Enum.zip(data, 1 .. length data) |>
276
Enum.map_join(fn {row, rowidx} ->
259
277
"""
260
- <row r="#{rowidx}" #{get_row_height_attr(row_heights, rowidx)}>
278
<row r="#{rowidx}" #{get_row_height_attr(row_heights, rowidx)}#{get_row_grouping_attr(grouping_info, rowidx)}>
261
279
#{xl_sheet_cols(row, rowidx, wci)}
262
280
</row>
263
281
""" end)
282
283
if (length(data) 1) in grouping_info.collapsed_idxs do
284
rows <> """
285
<row r="#{length(data) 1}" collapsed="1"></row>
286
"""
287
else
288
rows
289
end
264
290
end
265
291
266
292
defp get_row_height_attr(row_heights, rowidx) do
 
@@ -272,15 298,85 @@ defmodule Elixlsx.XMLTemplates do
272
298
end
273
299
end
274
300
275
- defp make_col_width({k, v}) do
276
- '<col min="#{k}" max="#{k}" width="#{v}" customWidth="1" />'
301
defp get_row_grouping_attr(gr_info, rowidx) do
302
outline_level = Map.get(gr_info.outline_lvs, rowidx)
303
(if outline_level, do: " outlineLevel=\"#{outline_level}\"", else: "")
304
<>
305
(if rowidx in gr_info.hidden_idxs, do: " hidden=\"1\"", else: "")
306
<>
307
(if rowidx in gr_info.collapsed_idxs, do: " collapsed=\"1\"", else: "")
277
308
end
278
309
279
- defp make_col_widths(sheet) do
280
- if Kernel.map_size(sheet.col_widths) != 0 do
281
- cols = Map.to_list(sheet.col_widths)
282
- |> Enum.sort
283
- |> Enum.map_join(&make_col_width/1)
310
@typep grouping_info :: %{
311
outline_lvs: %{optional(idx :: pos_integer) => lv :: pos_integer},
312
hidden_idxs: MapSet.t(pos_integer),
313
collapsed_idxs: MapSet.t(pos_integer)
314
}
315
@spec get_grouping_info([Sheet.rowcol_group]) :: grouping_info
316
defp get_grouping_info(groups) do
317
ranges =
318
Enum.map(groups, fn
319
{%Range{} = range, _opts} -> range
320
%Range{} = range -> range
321
end)
322
323
collapsed_ranges =
324
groups
325
|> Enum.filter(fn
326
{%Range{} = _range, opts} -> opts[:collapsed]
327
%Range{} = _range -> false
328
end)
329
|> Enum.map(fn {range, _opts} -> range end)
330
331
# see ECMA Office Open XML Part1, 18.3.1.73 Row -> attributes -> collapsed for examples
332
%{
333
outline_lvs:
334
ranges
335
|> Stream.concat()
336
|> Enum.group_by(& &1)
337
|> Map.new(fn {k, v} -> {k, length(v)} end),
338
hidden_idxs:
339
collapsed_ranges |> Stream.concat() |> MapSet.new(),
340
collapsed_idxs:
341
collapsed_ranges |> Enum.map(& &1.last 1) |> MapSet.new()
342
}
343
end
344
345
defp make_col({k, width, outline_level, hidden, collapsed}) do
346
width_attr =
347
if width, do: " width=\"#{width}\" customWidth=\"1\"", else: ""
348
hidden_attr = if hidden, do: " hidden=\"1\"", else: ""
349
outline_level_attr =
350
if outline_level, do: " outlineLevel=\"#{outline_level}\"", else: ""
351
collapsed_attr =
352
if collapsed, do: " collapsed=\"1\"", else: ""
353
354
'<col min="#{k}" max="#{k}"#{width_attr}#{hidden_attr}#{outline_level_attr}#{collapsed_attr} />'
355
end
356
357
defp make_cols(sheet) do
358
grouping_info = get_grouping_info(sheet.group_cols)
359
col_indices =
360
Stream.concat([
361
Map.keys(sheet.col_widths),
362
Map.keys(grouping_info.outline_lvs),
363
grouping_info.hidden_idxs,
364
grouping_info.collapsed_idxs
365
])
366
|> Enum.sort()
367
|> Enum.dedup()
368
369
unless Enum.empty?(col_indices) do
370
cols =
371
col_indices
372
|> Stream.map(&({
373
&1,
374
Map.get(sheet.col_widths, &1),
375
Map.get(grouping_info.outline_lvs, &1),
376
&1 in grouping_info.hidden_idxs,
377
&1 in grouping_info.collapsed_idxs
378
}))
379
|> Enum.map_join(&make_col/1)
284
380
285
381
"<cols>#{cols}</cols>"
286
382
else
 
@@ -288,11 384,25 @@ defmodule Elixlsx.XMLTemplates do
288
384
end
289
385
end
290
386
387
defp make_max_outline_level_row(row_outline_levels) do
388
unless row_outline_levels === %{} do
389
max_outline_level_row =
390
Map.values(row_outline_levels)
391
|> Enum.max()
392
393
" outlineLevelRow=\"#{max_outline_level_row}\""
394
else
395
""
396
end
397
end
398
291
399
@spec make_sheet(Sheet.t, WorkbookCompInfo.t) :: String.t
292
400
@doc ~S"""
293
401
Returns the XML content for single sheet.
294
402
"""
295
403
def make_sheet(sheet, wci) do
404
grouping_info = get_grouping_info(sheet.group_rows)
405
296
406
~S"""
297
407
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
298
408
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
 
@@ -311,14 421,18 @@ defmodule Elixlsx.XMLTemplates do
311
421
"""
312
422
</sheetView>
313
423
</sheetViews>
314
- <sheetFormatPr defaultRowHeight="12.8"/>
424
<sheetFormatPr defaultRowHeight="12.8"
315
425
"""
316
- <> make_col_widths(sheet) <>
426
<> make_max_outline_level_row(grouping_info.outline_lvs) <>
427
"""
428
/>
429
"""
430
<> make_cols(sheet) <>
317
431
"""
318
432
<sheetData>
319
433
"""
320
434
<>
321
- xl_sheet_rows(sheet.rows, sheet.row_heights, wci)
435
xl_sheet_rows(sheet.rows, sheet.row_heights, grouping_info, wci)
322
436
<>
323
437
~S"""
324
438
</sheetData>
changed mix.exs
 
@@ -3,7 3,7 @@ defmodule Elixlsx.Mixfile do
3
3
4
4
def project do
5
5
[app: :elixlsx,
6
- version: "0.4.1",
6
version: "0.4.2",
7
7
elixir: "~> 1.3",
8
8
package: package(),
9
9
description: "a writer for XLSX spreadsheet files",
 
@@ -19,7 19,7 @@ defmodule Elixlsx.Mixfile do
19
19
defp deps do
20
20
[
21
21
{:excheck, "~> 0.5", only: :test},
22
- {:triq, git: "https://gitlab.com/triq/triq.git", ref: "2c497398e020e06db8496f1d89f12481cc5adab9", only: :test},
22
{:triq, "~> 1.0", only: :test},
23
23
{:credo, "~> 0.5", only: [:dev, :test]},
24
24
{:ex_doc, "~> 0.19", only: [:dev]},
25
25
{:dialyxir, "~> 0.5", only: [:dev], runtime: false}