Skip to content

Commit 0005f65

Browse files
authored
Merge pull request #111 from Jindequan/master
Fill empty cells when write ets
2 parents 4385475 + 3aa8d91 commit 0005f65

File tree

3 files changed

+123
-73
lines changed

3 files changed

+123
-73
lines changed

lib/xlsxir.ex

Lines changed: 23 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,9 @@ defmodule Xlsxir do
129129
end
130130

131131
defp row_data_to_list(row_data) do
132-
# [["A1", 1], ["C1", 2]]
133-
row_data
134-
# [["A1", 1], ["B1", nil], ["C1", 2]]
135-
|> do_get_row()
136-
# [1, nil, 2]
137-
|> Enum.map(fn [_ref, val] -> val end)
132+
# from: [["A1", 1], ["B1", nil], ["C1", 2]]
133+
# to: [1, nil, 2]
134+
Enum.map(row_data, fn [_ref, val] -> val end)
138135
end
139136

140137
@doc """
@@ -279,9 +276,7 @@ defmodule Xlsxir do
279276
:ets.match(tid, {:"$1", :"$2"})
280277
|> Enum.sort()
281278
|> Enum.map(fn [_num, row] ->
282-
row
283-
|> do_get_row()
284-
|> Enum.map(fn [_ref, val] -> val end)
279+
Enum.map(row, fn [_ref, val] -> val end)
285280
end)
286281
end
287282

@@ -315,7 +310,6 @@ defmodule Xlsxir do
315310
:ets.match(tid, {:"$1", :"$2"})
316311
|> Enum.reduce(%{}, fn [_num, row], acc ->
317312
row
318-
|> do_get_row()
319313
|> Enum.reduce(%{}, fn [ref, val], acc2 -> Map.put(acc2, ref, val) end)
320314
|> Enum.into(acc)
321315
end)
@@ -370,7 +364,6 @@ defmodule Xlsxir do
370364
add_row =
371365
h
372366
|> Enum.at(1)
373-
|> do_get_row()
374367
|> Enum.reduce({%{}, 0}, fn cell, {acc, index} ->
375368
{Map.put(acc, index, Enum.at(cell, 1)), index + 1}
376369
end)
@@ -450,70 +443,11 @@ defmodule Xlsxir do
450443
"""
451444
def get_row(tid, row) do
452445
case :ets.match(tid, {row, :"$1"}) do
453-
[[row]] -> row |> do_get_row() |> Enum.map(fn [_ref, val] -> val end)
446+
[[row]] -> row |> Enum.map(fn [_ref, val] -> val end)
454447
[] -> []
455448
end
456449
end
457450

458-
defp do_get_row(row) do
459-
row
460-
|> Enum.reduce({[], nil}, fn [ref, val], {values, previous} ->
461-
line = ~r/\d+$/ |> Regex.run(ref) |> List.first()
462-
463-
empty_cells =
464-
cond do
465-
is_nil(previous) && String.first(ref) != "A" ->
466-
fill_empty_cells("A#{line}", ref, line, [])
467-
468-
!is_nil(previous) && !is_next_col(ref, previous) ->
469-
fill_empty_cells(next_col(previous), ref, line, [])
470-
471-
true ->
472-
[]
473-
end
474-
475-
{values ++ empty_cells ++ [[ref, val]], ref}
476-
end)
477-
|> elem(0)
478-
end
479-
480-
defp column_from_index(index, column) when index > 0 do
481-
modulo = rem(index - 1, 26)
482-
column = [65 + modulo | column]
483-
column_from_index(div(index - modulo, 26), column)
484-
end
485-
486-
defp column_from_index(_, column), do: to_string(column)
487-
488-
defp is_next_col(current, previous) do
489-
current == next_col(previous)
490-
end
491-
492-
defp next_col(ref) do
493-
[chars, line] = Regex.run(~r/^([A-Z]+)(\d+)/, ref, capture: :all_but_first)
494-
chars = chars |> String.to_charlist()
495-
496-
col_index =
497-
Enum.reduce(chars, 0, fn char, acc ->
498-
acc = acc * 26
499-
acc + char - 65 + 1
500-
end)
501-
502-
"#{column_from_index(col_index + 1, '')}#{line}"
503-
end
504-
505-
defp fill_empty_cells(from, from, _line, cells), do: Enum.reverse(cells)
506-
507-
defp fill_empty_cells(from, to, line, cells) do
508-
next_ref = next_col(from)
509-
510-
if next_ref == to do
511-
fill_empty_cells(to, to, line, [[from, nil] | cells])
512-
else
513-
fill_empty_cells(next_ref, to, line, [[from, nil] | cells])
514-
end
515-
end
516-
517451
@doc """
518452
Accesses `tid` ETS process and returns values of specified column in a `list`.
519453
@@ -549,7 +483,6 @@ defmodule Xlsxir do
549483
|> Enum.sort()
550484
|> Enum.map(fn [_num, row] ->
551485
row
552-
|> do_get_row()
553486
|> Enum.filter(fn [ref, _val] -> Regex.scan(~r/[A-Z]+/i, ref) == [[col]] end)
554487
|> Enum.map(fn [_ref, val] -> val end)
555488
end)
@@ -624,6 +557,24 @@ defmodule Xlsxir do
624557
end)
625558
end
626559

560+
@doc """
561+
Fill every row with `nil` cells at the end.
562+
Cells quantity determined by the max length of all rows.
563+
564+
## Example
565+
Extract first worksheet in an example file named `test.xlsx` located in `./test/test_data`:
566+
iex> {:ok, tid} = Xlsxir.extract("./test/test_data/test.xlsx", 10)
567+
iex> Xlsxir.set_empty_cells_to_fill_rows(tid)
568+
{:ok, tid}
569+
iex> Xlsxir.get_row(tid, 1)
570+
[1, nil, 1, nil, 1, nil, nil, 1, nil, nil]
571+
iex> Xlsxir.get_col(tid, "J")
572+
[nil, nil, 1, nil]
573+
"""
574+
def set_empty_cells_to_fill_rows(tid) do
575+
Xlsxir.XlsxFile.set_empty_cells_to_fill_rows(tid)
576+
end
577+
627578
@doc """
628579
Deletes ETS process `tid` and returns `:ok` if successful.
629580

lib/xlsxir/parse_worksheet.ex

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ defmodule Xlsxir.ParseWorksheet do
122122
unless Enum.empty?(state.row) do
123123
[[row]] = ~r/\d+/ |> Regex.scan(state.row |> List.first() |> List.first())
124124
row = row |> String.to_integer()
125-
value = state.row |> Enum.reverse()
125+
value = state.row |> Enum.reverse() |> fill_nil()
126126

127127
:ets.insert(tid, {row, value})
128128
if !is_nil(max_rows) and row == max_rows, do: raise(SaxError, state: state)
@@ -133,6 +133,64 @@ defmodule Xlsxir.ParseWorksheet do
133133

134134
def sax_event_handler(_, state, _, _), do: state
135135

136+
defp fill_nil(rows) do
137+
Enum.reduce(rows, {[], nil}, fn [ref, val], {values, previous} ->
138+
line = ~r/\d+$/ |> Regex.run(ref) |> List.first()
139+
140+
empty_cells =
141+
cond do
142+
is_nil(previous) && String.first(ref) != "A" ->
143+
fill_empty_cells("A#{line}", ref, line, [])
144+
145+
!is_nil(previous) && !is_next_col(ref, previous) ->
146+
fill_empty_cells(next_col(previous), ref, line, [])
147+
148+
true ->
149+
[]
150+
end
151+
152+
{values ++ empty_cells ++ [[ref, val]], ref}
153+
end)
154+
|> elem(0)
155+
end
156+
157+
def column_from_index(index, column) when index > 0 do
158+
modulo = rem(index - 1, 26)
159+
column = [65 + modulo | column]
160+
column_from_index(div(index - modulo, 26), column)
161+
end
162+
163+
def column_from_index(_, column), do: to_string(column)
164+
165+
defp is_next_col(current, previous) do
166+
current == next_col(previous)
167+
end
168+
169+
def next_col(ref) do
170+
[chars, line] = Regex.run(~r/^([A-Z]+)(\d+)/, ref, capture: :all_but_first)
171+
chars = chars |> String.to_charlist()
172+
173+
col_index =
174+
Enum.reduce(chars, 0, fn char, acc ->
175+
acc = acc * 26
176+
acc + char - 65 + 1
177+
end)
178+
179+
"#{column_from_index(col_index + 1, '')}#{line}"
180+
end
181+
182+
def fill_empty_cells(from, from, _line, cells), do: Enum.reverse(cells)
183+
184+
def fill_empty_cells(from, to, line, cells) do
185+
next_ref = next_col(from)
186+
187+
if next_ref == to do
188+
fill_empty_cells(to, to, line, [[from, nil] | cells])
189+
else
190+
fill_empty_cells(next_ref, to, line, [[from, nil] | cells])
191+
end
192+
end
193+
136194
defp format_cell_value(%{shared_strings: strings_tid}, list) do
137195
case list do
138196
# Cell with no value attribute

lib/xlsxir/xlsx_file.ex

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,47 @@ defmodule Xlsxir.XlsxFile do
9595
{:ok, tid}
9696
end
9797

98+
@doc """
99+
Fill every row with `nil` cells at the end.
100+
Cells quantity determined by the max length of all rows.
101+
"""
102+
def set_empty_cells_to_fill_rows(tid) do
103+
max_len =
104+
:ets.match(tid, {:"$1", :"$2"})
105+
|> Enum.map(fn [_num, row] -> Enum.count(row) end)
106+
|> Enum.max()
107+
108+
end_column = Xlsxir.ParseWorksheet.column_from_index(max_len + 1, "")
109+
110+
first_key = :ets.first(tid)
111+
fill_empty_cells_at_end(tid, end_column, first_key)
112+
end
113+
114+
defp fill_empty_cells_at_end(tid, _, :"$end_of_table"), do: {:ok, tid}
115+
116+
defp fill_empty_cells_at_end(tid, end_column, index) when is_integer(index) do
117+
build_and_replace(tid, end_column, index)
118+
nex_index= :ets.next(tid, index)
119+
fill_empty_cells_at_end(tid, end_column, nex_index)
120+
end
121+
122+
defp fill_empty_cells_at_end(tid, end_column, index) do
123+
nex_index = :ets.next(tid, index)
124+
fill_empty_cells_at_end(tid, end_column, nex_index)
125+
end
126+
127+
defp build_and_replace(tid, end_column, index) do
128+
[{index, cells}] = :ets.lookup(tid, index)
129+
[last_ref, _] = List.last(cells)
130+
from = Xlsxir.ParseWorksheet.next_col(last_ref)
131+
to = end_column <> Integer.to_string(index)
132+
133+
empty_cells = Xlsxir.ParseWorksheet.fill_empty_cells(from, to, index, [])
134+
new_cells = cells ++ empty_cells
135+
136+
true = :ets.insert(tid, {index, new_cells})
137+
end
138+
98139
@doc """
99140
Parse all worksheets of the XlsxFile and store their content in ETS tables.
100141
returns `[{:ok, worksheet_1_table_id}, ..., {:ok, worksheet_n_table_id}]` when `timer` is `false`

0 commit comments

Comments
 (0)