Skip to content

Commit

Permalink
Fixes: #278
Browse files Browse the repository at this point in the history
  • Loading branch information
RobertDober committed Sep 23, 2019
1 parent 09548fd commit 0c1859b
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 52 deletions.
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,25 @@ default to left.
| Texas | TX | Austin |
| Maine | ME | Augusta |

Currently we assume there are always spaces around interior vertical
bars. It isn't clear what the expectation is.
Currently we assume there are always spaces around interior vertical unless
there are exterior bars.

However in order to be more GFM compatible the `gfm_tables: true` option
can be used to interpret only interior vertical bars as a table if a seperation
line is given, therefor

Language|Rating
--------|------
Elixir | awesome

is a table (iff `gfm_tables: true`) while

Language|Rating
Elixir | awesome

never is.



### Adding HTML attributes with the IAL extension

Expand Down
21 changes: 19 additions & 2 deletions lib/earmark.ex
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,25 @@ defmodule Earmark do
| Texas | TX | Austin |
| Maine | ME | Augusta |
Currently we assume there are always spaces around interior vertical
bars. It isn't clear what the expectation is.
Currently we assume there are always spaces around interior vertical unless
there are exterior bars.
However in order to be more GFM compatible the `gfm_tables: true` option
can be used to interpret only interior vertical bars as a table if a seperation
line is given, therefor
Language|Rating
--------|------
Elixir | awesome
is a table (iff `gfm_tables: true`) while
Language|Rating
Elixir | awesome
never is.
### Adding HTML attributes with the IAL extension
Expand Down
2 changes: 1 addition & 1 deletion lib/earmark/line.ex
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ defmodule Earmark.Line do

defmodule TableLine do
@moduledoc false
defstruct(lnb: 0, line: "", content: "", columns: 0, inside_code: false)
defstruct(lnb: 0, line: "", content: "", columns: 0, inside_code: false, is_header: false, needs_header: false)
end

defmodule Ial do
Expand Down
13 changes: 11 additions & 2 deletions lib/earmark/line_scanner.ex
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,15 @@ defmodule Earmark.LineScanner do
|> String.trim("|")

columns = split_table_columns(body)
%Line.TableLine{content: line, columns: columns}
%Line.TableLine{content: line, columns: columns, is_header: _determine_if_header(columns)}

line =~ ~r/ \s \| \s /x ->
columns = split_table_columns(line)
%Line.TableLine{content: line, columns: columns}
%Line.TableLine{content: line, columns: columns, is_header: _determine_if_header(columns)}

line =~ ~r/ \| /x && options.gfm_tables ->
columns = split_table_columns(line)
%Line.TableLine{content: line, columns: columns, is_header: _determine_if_header(columns), needs_header: true}

match = Regex.run(~r/^(=|-)+\s*$/, line) ->
[_, type] = match
Expand Down Expand Up @@ -229,6 +233,11 @@ defmodule Earmark.LineScanner do
|> Enum.into(MapSet.new())
defp block_tag?(tag), do: MapSet.member?(@block_tags, tag)

@column_rgx ~r{\A[\s|:-]+\z}
defp _determine_if_header(columns) do
columns
|> Enum.all?(fn col -> Regex.run(@column_rgx, col) end)
end
defp split_table_columns(line) do
line
|> String.split(~r{(?<!\\)\|})
Expand Down
1 change: 1 addition & 0 deletions lib/earmark/options.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defmodule Earmark.Options do
defstruct renderer: Earmark.HtmlRenderer,
# Inline style options
gfm: true,
gfm_tables: false,
breaks: false,
pedantic: false,
smartypants: true,
Expand Down
15 changes: 13 additions & 2 deletions lib/earmark/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ defmodule Earmark.Parser do
# Table #
#########

defp _parse( lines = [ %Line.TableLine{columns: cols1, lnb: lnb1},
defp _parse( lines = [ %Line.TableLine{columns: cols1, lnb: lnb1, needs_header: false},
%Line.TableLine{columns: cols2}
| _rest
], result, options)
Expand All @@ -138,6 +138,17 @@ defmodule Earmark.Parser do
_parse(rest, [ table1 | result ], options)
end

defp _parse( lines = [ %Line.TableLine{columns: cols1, lnb: lnb1, needs_header: true},
%Line.TableLine{columns: cols2, is_header: true}
| _rest
], result, options)
when length(cols1) == length(cols2)
do
columns = length(cols1)
{ table, rest } = read_table(lines, columns, Block.Table.new_for_columns(columns))
table1 = %{table | lnb: lnb1}
_parse(rest, [ table1 | result ], options)
end
#############
# Paragraph #
#############
Expand Down Expand Up @@ -390,14 +401,14 @@ defmodule Earmark.Parser do
# Read in a table (consecutive TableLines with
# the same number of columns)

defp read_table(lines, col_count, into_table)
defp read_table([ %Line.TableLine{columns: cols} | rest ],
col_count,
table = %Block.Table{})
when length(cols) == col_count
do
read_table(rest, col_count, update_in(table.rows, &[ cols | &1 ]))
end

defp read_table( rest, col_count, %Block.Table{rows: rows}) do
rows = Enum.reverse(rows)
table = Block.Table.new_for_columns(col_count)
Expand Down
27 changes: 26 additions & 1 deletion test/acceptance/html/table_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Acceptance.Html.TableTest do
use ExUnit.Case, async: true

import Support.Helpers, only: [as_html: 1]
import Support.Helpers, only: [as_html: 1, as_html: 2]

describe "complex rendering inside tables:" do

Expand Down Expand Up @@ -58,6 +58,31 @@ defmodule Acceptance.Html.TableTest do
assert as_html(markdown) == {:ok, html, messages}
end
end

describe "GFM Tables" do
test "do not need spaces around mid `\|`" do
markdown = "a|b\n-|-\nd|e\n"
html = "<table>\n<thead>\n<tr>\n<th style=\"text-align: left\">a</th><th style=\"text-align: left\">b</th>\n</tr>\n</thead>\n<tr>\n<td style=\"text-align: left\">d</td><td style=\"text-align: left\">e</td>\n</tr>\n</table>\n"
messages = []

assert as_html(markdown, gfm_tables: true) == {:ok, html, messages}
end

test "do not need spaces around mid `\|` but w/o gfm_tables this is no good" do
markdown = "a|b\n-|-\nd|e\n"
html = "<p>a|b\n-|-\nd|e</p>\n"
messages = []

assert as_html(markdown, gfm_tables: false) == {:ok, html, messages}
end
test "however a header line needs to be used" do
markdown = "a|b\nd|e\n"
html = "<p>a|b\nd|e</p>\n"
messages = []

assert as_html(markdown, gfm_tables: true) == {:ok, html, messages}
end
end
end

# SPDX-License-Identifier: Apache-2.0
84 changes: 42 additions & 42 deletions test/support/html1_helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ defmodule Support.Html1Helpers do
end


def construct(constructions) do
def construct(constructions, indent \\ 2) do
result =
_construct(constructions, 0, []) |> IO.iodata_to_binary
_construct(constructions, 0, [], indent) |> IO.iodata_to_binary
if System.get_env("DEBUG") do
IO.inspect({:constructed, result})
end
Expand All @@ -34,71 +34,71 @@ defmodule Support.Html1Helpers do
{:th, ~s{style="text-align: #{style};"}, content}
end

defp _construct(constructions, indent, open)
defp _construct([], _indent, []), do: []
defp _construct([], indent, [open|rest]) do
[_indent(indent - 2), "</", to_string(open), ">\n", _construct([], indent - 2, rest) ]
defp _construct(constructions, indent, open, iby)
defp _construct([], _indent, [], _iby), do: []
defp _construct([], indent, [open|rest], iby) do
[_indent(indent - iby), "</", to_string(open), ">\n", _construct([], indent - iby, rest, iby) ]
end
defp _construct([:POP|rest], indent, [tag|rest1]) do
[_indent(indent-2), "</", to_string(tag), ">\n", _construct(rest, indent - 2, rest1)]
defp _construct([:POP|rest], indent, [tag|rest1], iby) do
[_indent(indent-iby), "</", to_string(tag), ">\n", _construct(rest, indent - iby, rest1, iby)]
end
defp _construct(head, indent, open) when is_tuple(head) do
_construct([head], indent, open)
defp _construct(head, indent, open, iby) when is_tuple(head) do
_construct([head], indent, open, iby)
end
defp _construct([:br | rest], indent, open) do
_void_tag("<br />\n", rest, indent, open)
defp _construct([:br | rest], indent, open, iby) do
_void_tag("<br />\n", rest, indent, open, iby)
end
defp _construct([:hr | rest], indent, open) do
_void_tag("<hr />\n", rest, indent, open)
defp _construct([:hr | rest], indent, open, iby) do
_void_tag("<hr />\n", rest, indent, open, iby)
end
defp _construct([:wbr | rest], indent, open) do
_void_tag("<wbr />\n", rest, indent, open)
defp _construct([:wbr | rest], indent, open, iby) do
_void_tag("<wbr />\n", rest, indent, open, iby)
end
defp _construct([{:area, atts} | rest], indent, open) do
_void_tag_with_atts("<area ", atts, rest, indent, open)
defp _construct([{:area, atts} | rest], indent, open, iby) do
_void_tag_with_atts("<area ", atts, rest, indent, open, iby)
end
defp _construct([{:hr, atts} | rest], indent, open) do
_void_tag_with_atts("<hr ", atts, rest, indent, open)
defp _construct([{:hr, atts} | rest], indent, open, iby) do
_void_tag_with_atts("<hr ", atts, rest, indent, open, iby)
end
defp _construct([{:img, atts} | rest], indent, open) do
_void_tag_with_atts("<img ", atts, rest, indent, open)
defp _construct([{:img, atts} | rest], indent, open, iby) do
_void_tag_with_atts("<img ", atts, rest, indent, open, iby)
end
defp _construct([tag | rest], indent, open) when is_atom(tag) do
[_indent(indent), "<", to_string(tag), ">", "\n", _construct(rest, indent + 2, [tag | open])]
defp _construct([tag | rest], indent, open, iby) when is_atom(tag) do
[_indent(indent), "<", to_string(tag), ">", "\n", _construct(rest, indent + iby, [tag | open], iby)]
end
defp _construct([content|rest], indent, open) when is_binary(content) do
[_indent(indent), content, "\n", _construct(rest, indent, open)]
defp _construct([content|rest], indent, open, iby) when is_binary(content) do
[_indent(indent), content, "\n", _construct(rest, indent, open, iby)]
end
defp _construct([{tag, content}|rest], indent, open) when is_tuple(content), do: _construct([{tag, nil, content}|rest], indent, open)
defp _construct([{tag, content}|rest], indent, open) when is_list(content), do: _construct([{tag, nil, content}|rest], indent, open)
defp _construct([{tag, atts}|rest], indent, open) do
[_indent(indent), "<", to_string(tag), " ", atts, ">", "\n", _construct(rest, indent + 2, [tag | open])]
defp _construct([{tag, content}|rest], indent, open, iby) when is_tuple(content), do: _construct([{tag, nil, content}|rest], indent, open, iby)
defp _construct([{tag, content}|rest], indent, open, iby) when is_list(content), do: _construct([{tag, nil, content}|rest], indent, open, iby)
defp _construct([{tag, atts}|rest], indent, open, iby) do
[_indent(indent), "<", to_string(tag), " ", atts, ">", "\n", _construct(rest, indent + iby, [tag | open], iby)]
end
defp _construct([{tag, atts, content}|rest], indent, open) when is_binary(content) do
_construct([{tag, atts, [content]}|rest], indent, open)
defp _construct([{tag, atts, content}|rest], indent, open, iby) when is_binary(content) do
_construct([{tag, atts, [content]}|rest], indent, open, iby)
end
defp _construct([{tag, nil, content}|rest], indent, open) do
defp _construct([{tag, nil, content}|rest], indent, open, iby) do
[_indent(indent), "<", to_string(tag), ">",
"\n",
_construct(content, indent + 2, []),
_construct(content, indent + iby, [], iby),
_indent(indent), "</", to_string(tag), ">\n",
_construct(rest, indent, open)]
_construct(rest, indent, open, iby)]
end
defp _construct([{tag, atts, content}|rest], indent, open) do
defp _construct([{tag, atts, content}|rest], indent, open, iby) do
[_indent(indent), "<", to_string(tag), " ", atts, ">",
"\n",
_construct(content, indent + 2, []),
_construct(content, indent + iby, [], iby),
_indent(indent), "</", to_string(tag), ">\n",
_construct(rest, indent, open)]
_construct(rest, indent, open, iby)]
end

defp _indent(n), do: Stream.cycle([" "]) |> Enum.take(n)

defp _void_tag( tag, rest, indent, open) do
[_indent(indent), tag, _construct(rest, indent, open)]
defp _void_tag( tag, rest, indent, open, iby) do
[_indent(indent), tag, _construct(rest, indent, open, iby)]
end

defp _void_tag_with_atts(tag, atts, rest, indent, open) do
[_indent(indent), tag, atts, " />", "\n", _construct(rest, indent, open)]
defp _void_tag_with_atts(tag, atts, rest, indent, open, iby) do
[_indent(indent), tag, atts, " />", "\n", _construct(rest, indent, open, iby)]
end
end

0 comments on commit 0c1859b

Please sign in to comment.