Page MenuHomePhorge

No OneTemporary

Size
22 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/bbcode/parser.ex b/lib/bbcode/parser.ex
index aa0b882..d6adff0 100644
--- a/lib/bbcode/parser.ex
+++ b/lib/bbcode/parser.ex
@@ -1,233 +1,318 @@
# SPDX-FileCopyrightText: 2019-2022 Pleroma Authors
# SPDX-License-Identifier: LGPL-3.0-only
defmodule BBCode.Parser do
import NimbleParsec
@moduledoc """
Parse BBCode into an abstract tree.
"""
text = utf8_string([not: ?[, not: ?], not: ?\r, not: ?\n], min: 1)
# block tags
quote_tag = string("quote")
ul_tag = string("ul")
ol_tag = string("ol")
li_tag = string("li")
code_tag = string("code")
table_tag = string("table")
tr_tag = string("tr")
th_tag = string("th")
td_tag = string("td")
# span tags
b_tag = string("b")
i_tag = string("i")
u_tag = string("u")
s_tag = string("s")
url_tag = string("url")
ruby_tag = string("ruby")
img_tag = string("img")
# WARNING: Order of span tags matters. In particular img has to be before i and url before u
@block_tags [quote_tag, ul_tag, ol_tag, li_tag, code_tag, table_tag, tr_tag, th_tag, td_tag]
@span_tags [b_tag, img_tag, i_tag, url_tag, u_tag, s_tag, ruby_tag]
@tags @block_tags ++ @span_tags
end_tag =
ignore(string("[/"))
|> choice(@tags)
|> ignore(string("]"))
+ li_end_tag = ignore(string("[/li]"))
+ ul_end_tag = ignore(string("[/ul]"))
+ ol_end_tag = ignore(string("[/ol]"))
+
# special tags
star_tag = ignore(string("[*]"))
# newline
newline = utf8_char([?\r, ?\n])
+ defcombinatorp(
+ :ol_block_tag,
+ ignore(string("["))
+ |> concat(ol_tag)
+ |> ignore(string("]"))
+ |> ignore(optional(utf8_string([?\n, ?\r], min: 1, max: 2)))
+ )
+
+ defcombinatorp(
+ :ul_block_tag,
+ ignore(string("["))
+ |> concat(ul_tag)
+ |> ignore(string("]"))
+ |> ignore(optional(utf8_string([?\n, ?\r], min: 1, max: 2)))
+ )
+
+ defcombinatorp(
+ :list_element_tag,
+ ignore(string("["))
+ |> concat(li_tag)
+ |> ignore(string("]"))
+ |> ignore(optional(utf8_string([?\n, ?\r], min: 1, max: 2)))
+ )
+
+ defcombinatorp(
+ :li_block_stanza,
+ parsec(:list_element_tag)
+ |> repeat(lookahead_not(li_end_tag) |> choice([parsec(:child_stanza), text]))
+ |> concat(li_end_tag)
+ |> ignore(optional(utf8_string([?\n, ?\r], min: 1, max: 2)))
+ |> post_traverse(:emit_tree_node_list)
+ )
+
+ defcombinatorp(
+ :ul_block_tag_stanza,
+ parsec(:ul_block_tag)
+ |> repeat(
+ lookahead_not(ul_end_tag)
+ |> choice([parsec(:li_block_stanza), parsec(:star_stanza)])
+ )
+ |> concat(ul_end_tag)
+ |> ignore(optional(utf8_string([?\n, ?\r], min: 1, max: 2)))
+ |> post_traverse(:emit_tree_node_block)
+ )
+
+ defcombinatorp(
+ :ol_block_tag_stanza,
+ parsec(:ol_block_tag)
+ |> repeat(
+ lookahead_not(ol_end_tag)
+ |> choice([parsec(:li_block_stanza), parsec(:star_stanza)])
+ )
+ |> concat(ol_end_tag)
+ |> ignore(optional(utf8_string([?\n, ?\r], min: 1, max: 2)))
+ |> post_traverse(:emit_tree_node_block)
+ )
+
defcombinatorp(
:block_tag,
ignore(string("["))
- |> choice([quote_tag, ul_tag, ol_tag, li_tag, code_tag, table_tag, tr_tag, th_tag, td_tag])
+ |> choice([quote_tag, code_tag, table_tag, tr_tag, th_tag, td_tag])
|> ignore(string("]"))
|> ignore(optional(utf8_string([?\n, ?\r], min: 1, max: 2)))
)
defcombinatorp(
:block_stanza,
parsec(:block_tag)
|> repeat(lookahead_not(end_tag) |> choice([parsec(:child_stanza), text]))
|> wrap()
|> concat(end_tag)
|> ignore(optional(utf8_string([?\n, ?\r], min: 1, max: 2)))
|> post_traverse(:emit_tree_node)
)
defcombinatorp(
:span_tag,
ignore(string("["))
|> choice([url_tag, img_tag, b_tag, i_tag, u_tag, s_tag])
|> ignore(string("]"))
|> ignore(optional(utf8_string([?\n, ?\r], min: 1, max: 2)))
)
defcombinatorp(
:span_tag_with_property,
ignore(string("["))
|> choice([url_tag, ruby_tag])
|> ignore(string("="))
|> concat(text)
|> ignore(string("]"))
|> ignore(optional(utf8_string([?\n, ?\r], min: 1, max: 2)))
)
defcombinatorp(
:img_tag_with_size_property,
ignore(string("["))
|> concat(img_tag)
|> ignore(string("="))
|> integer(min: 1)
|> ignore(string("x"))
|> integer(min: 1)
|> ignore(string("]"))
|> ignore(optional(utf8_string([?\n, ?\r], min: 1, max: 2)))
)
defcombinatorp(
:span_stanza,
parsec(:span_tag)
|> repeat(lookahead_not(end_tag) |> choice([parsec(:child_stanza), text]))
|> wrap()
|> concat(end_tag)
|> post_traverse(:emit_tree_node)
)
defcombinatorp(
:text_stanza,
text
|> wrap()
|> post_traverse(:emit_tree_node)
)
defcombinatorp(
:star_stanza,
star_tag
|> repeat(
- lookahead_not(string("\n"))
+ lookahead_not(choice([ul_end_tag, ol_end_tag, star_tag, string("\n")]))
|> choice([parsec(:child_stanza), text])
)
|> wrap()
|> concat(ignore(optional(utf8_string([?\n, ?\r], min: 1, max: 2))))
|> post_traverse(:emit_tree_node_star)
)
defcombinatorp(
:span_stanza_with_property,
parsec(:span_tag_with_property)
|> repeat(lookahead_not(end_tag) |> choice([parsec(:child_stanza), text]))
|> wrap()
|> concat(end_tag)
|> post_traverse(:emit_tree_node_property)
)
defcombinatorp(
:img_stanza_with_size_property,
parsec(:img_tag_with_size_property)
|> repeat(lookahead_not(end_tag) |> choice([parsec(:child_stanza), text]))
|> wrap()
|> concat(end_tag)
|> post_traverse(:emit_tree_node_size_property)
)
defcombinatorp(
:newline_stanza,
newline
|> post_traverse(:emit_tree_node_newline)
)
defcombinatorp(
:bracket_text_stanza,
string("[")
|> optional(string("/"))
|> concat(text)
|> string("]")
|> reduce({List, :to_string, []})
|> wrap()
|> post_traverse(:emit_tree_node)
)
defcombinatorp(
:stray_bracket_stanza,
utf8_string([?[, ?]], min: 1)
|> optional(choice([utf8_char([?\s]), text]))
|> wrap()
|> reduce({List, :to_string, []})
)
defcombinatorp(
:child_stanza,
choice([
parsec(:newline_stanza),
- parsec(:star_stanza),
+ parsec(:ul_block_tag_stanza),
+ parsec(:ol_block_tag_stanza),
parsec(:block_stanza),
parsec(:img_stanza_with_size_property),
parsec(:span_stanza_with_property),
parsec(:span_stanza),
parsec(:bracket_text_stanza),
parsec(:stray_bracket_stanza)
])
)
defcombinatorp(
:root_stanza,
choice([parsec(:child_stanza), parsec(:text_stanza)])
)
defparsecp(
:parse_tree,
repeat(parsec(:root_stanza)) |> eos()
)
defp emit_tree_node_newline(rest, _args, context, _line, _offset),
do: {rest, [{:br}], context}
+ defp emit_tree_node_star(rest, [nodes], context, _line, _offset) when length(nodes) == 1,
+ do: {rest, [{:li, List.first(nodes)}], context}
+
defp emit_tree_node_star(rest, [nodes], context, _line, _offset),
do: {rest, [{:li, nodes}], context}
+ defp emit_tree_node_list(rest, [inside | _], context, _line, _offset) do
+ {rest, [{:li, inside}], context}
+ end
+
defp emit_tree_node_size_property(
rest,
[tag, [tag, width, height, inside]],
context,
_line,
_offset
),
do: {rest, [{String.to_atom(tag), width, height, inside}], context}
defp emit_tree_node_property(rest, [tag, [tag, property, inside]], context, _line, _offset),
do: {rest, [{String.to_atom(tag), property, inside}], context}
defp emit_tree_node_property(rest, [tag, [tag, property | nodes]], context, _line, _offset),
do: {rest, [{String.to_atom(tag), property, nodes}], context}
+ defp emit_tree_node_block(rest, nodes, context, _line, _offset) do
+ # nodes is a Keyword list of [li] blocks in reverse order with the list type at the end.
+ # To fix this, the list is reversed and the now first element is popped into the tag.
+ nodes = Enum.reverse(nodes)
+ {tag, nodes} = List.pop_at(nodes, 0)
+
+ # Expected output of lists with a single item is a tuple instead of a list.
+ # The incoming list is a Keyword, so the first element is actually a tuple, which we need.
+ nodes = if length(nodes) == 1, do: List.first(nodes), else: nodes
+
+ {rest, [{String.to_atom(tag), nodes}], context}
+ end
+
defp emit_tree_node(rest, [tag, [tag, inside]], context, _line, _offset),
do: {rest, [{String.to_atom(tag), inside}], context}
defp emit_tree_node(rest, [tag, [tag | nodes]], context, _line, _offset),
do: {rest, [{String.to_atom(tag), nodes}], context}
defp emit_tree_node(rest, [[text]], context, _line, _offset),
do: {rest, [text], context}
+ defp emit_tree_node(rest, [[tag | nodes]], context, _line, _offset),
+ do: {rest, [{String.to_atom(tag), nodes}], context}
+
defp emit_tree_node(rest, [["[", text, "]"]], context, _line, _offset),
do: {rest, ["[" <> text <> "]"], context}
def parse(text) do
with {:ok, nodes, _, _, _, _} <- parse_tree(text) do
{:ok, nodes}
else
{:error, e, _, _, _, _} ->
{:error, e}
end
end
end
diff --git a/test/bbcode/generator_test.exs b/test/bbcode/generator_test.exs
index 52ca657..6d095d0 100644
--- a/test/bbcode/generator_test.exs
+++ b/test/bbcode/generator_test.exs
@@ -1,192 +1,205 @@
# SPDX-FileCopyrightText: 2019-2022 Pleroma Authors
# SPDX-License-Identifier: LGPL-3.0-only
defmodule BBCode.Generator.Test do
use ExUnit.Case
describe "simple tags" do
test "[b] tags are translated to <strong>" do
assert {:ok, "<strong>testing</strong>"} = BBCode.to_html("[b]testing[/b]")
end
test "[i] tags are translated to <em>" do
assert {:ok, "<em>testing</em>"} = BBCode.to_html("[i]testing[/i]")
end
test "[u] tags are translated to <u>" do
assert {:ok, "<u>testing</u>"} = BBCode.to_html("[u]testing[/u]")
end
test "[s] tags are translated to <del>" do
assert {:ok, "<del>testing</del>"} = BBCode.to_html("[s]testing[/s]")
end
test "[code] tags are translated to <pre>" do
assert {:ok, "<pre>testing</pre>"} = BBCode.to_html("[code]testing[/code]")
end
test "[quote] tags are translated to <blockquote>" do
assert {:ok, "<blockquote>testing</blockquote>"} = BBCode.to_html("[quote]testing[/quote]")
end
test "compounding simple tags works as expected" do
assert {:ok, "<strong><em>testing</em></strong>"} = BBCode.to_html("[b][i]testing[/i][/b]")
end
test "[ruby] tags are translated to ruby-text" do
assert {:ok, "<ruby>X<rp>(</rp><rt>eks</rt><rp>)</rp></ruby>"} =
BBCode.to_html("[ruby=eks]X[/ruby]")
end
end
describe "lists" do
test "[ul] lists are rendered properly" do
data = """
[ul]
[*]a
[*]b
[*]c
[/ul]
"""
expected = "<ul><li>a</li><li>b</li><li>c</li></ul>"
assert {:ok, ^expected} = BBCode.to_html(data)
+
+ assert {:ok, "<ul><li>a</li></ul>"} = BBCode.to_html("[ul][li]a[/li][/ul]")
+ assert {:ok, "<ul><li>a</li></ul>"} = BBCode.to_html("[ul][*]a[/ul]")
end
test "[ol] lists are rendered properly" do
data = """
[ol]
[*]a
[*]b
[*]c
[/ol]
"""
expected = "<ol><li>a</li><li>b</li><li>c</li></ol>"
assert {:ok, ^expected} = BBCode.to_html(data)
+
+ assert {:ok, "<ol><li>a</li></ol>"} = BBCode.to_html("[ol][li]a[/li][/ol]")
+ assert {:ok, "<ol><li>a</li></ol>"} = BBCode.to_html("[ol][*]a[/ol]")
end
end
describe "tables" do
test "[table] tables are rendered properly" do
data = """
[table]
[tr]
[th]header[/th]
[/tr]
[tr]
[td]cell[/td]
[/tr]
[/table]
"""
expected = "<table><tr><th>header</th></tr><tr><td>cell</td></tr></table>"
assert {:ok, ^expected} = BBCode.to_html(data)
end
end
describe "links" do
test "bare [url] links are rendered properly" do
data = """
[url]http://example.com[/url]
"""
expected = "<a href=\"http://example.com\">http://example.com</a><br>"
assert {:ok, ^expected} = BBCode.to_html(data)
end
test "named [url] links are rendered properly" do
data = """
[url=http://example.com]Example[/url]
"""
expected = "<a href=\"http://example.com\">Example</a><br>"
assert {:ok, ^expected} = BBCode.to_html(data)
end
end
describe "images" do
test "bare [img] links are rendered properly" do
data = """
[img]http://example.com/image.jpg[/img]
"""
expected = "<img src=\"http://example.com/image.jpg\"><br>"
assert {:ok, ^expected} = BBCode.to_html(data)
end
test "sized [img] links are rendered properly" do
data = """
[img=32x32]http://example.com/image.jpg[/img]
"""
expected = "<img src=\"http://example.com/image.jpg\" width=\"32\" height=\"32\"><br>"
assert {:ok, ^expected} = BBCode.to_html(data)
end
end
describe "documents" do
test "it correctly renders a complex document" do
data = """
[quote]
A multiline quote.
This is the second line.
[/quote]
[ul]
[*]a
[*]b
[*]c
[/ul]
[b]bold[/b]
[i]italic[/i]
[u]underline[/u]
[s]strikethrough[/s]
[url=http://example.com]a link[/url]
@kaniini (a mention)
[code]
[lain@navi ~/.local/src/git/bbcode]$ hacking on the [b]BBCode[/b] parser using [asdf]as the version manager[/asdf]
[/code]
"""
{:ok, output} = BBCode.to_html(data)
assert output ==
"<blockquote>A multiline quote.<br>This is the second line.<br></blockquote><ul><li>a</li><li>b</li><li>c</li></ul><strong>bold</strong><br><em>italic</em><br><u>underline</u><br><del>strikethrough</del><br><br><a href=\"http://example.com\">a link</a><br><br>@kaniini (a mention)<br><br><pre>[lain@navi ~/.local/src/git/bbcode]$ hacking on the <strong>BBCode</strong> parser using [asdf]as the version manager[/asdf]<br></pre>"
end
end
describe "unknown tags" do
test "it continues when tag is encountered" do
assert {:ok, "<pre>[asdf] valid input</pre> and <strong>bold</strong> text"} =
BBCode.to_html("[code][asdf] valid input[/code] and [b]bold[/b] text")
end
test "it continues when closing tag is encountered" do
assert {:ok, "[/asdf]"} = BBCode.to_html("[/asdf]")
assert {:ok, "[/asdf] text"} = BBCode.to_html("[/asdf] text")
assert {:ok, "<strong>[/asdf]</strong> text"} = BBCode.to_html("[b][/asdf][/b] text")
end
test "it continues when opening and closing tag is encountered" do
assert {:ok, "asdf [asdf]#cofe and #hambaga[/asdf] rest of text"} =
BBCode.to_html("asdf [asdf]#cofe and #hambaga[/asdf] rest of text")
end
test "it continues when stray brackets are encountered" do
assert {:ok, "<pre> a[ b] [ ] [] valid input</pre> and <strong>bold</strong> text"} =
BBCode.to_html("[code] a[ b] [ ] [] valid input[/code] and [b]bold[/b] text")
end
end
+
+ describe "malformed input" do
+ test "it it ignores list items outside of list blocks" do
+ assert {:ok, "[li]test[/li]"} = BBCode.to_html("[li]test[/li]")
+ assert {:ok, "[*]test"} = BBCode.to_html("[*]test")
+ end
+ end
end
diff --git a/test/bbcode/parser_test.exs b/test/bbcode/parser_test.exs
index fbc3f3d..f6e6f0a 100644
--- a/test/bbcode/parser_test.exs
+++ b/test/bbcode/parser_test.exs
@@ -1,160 +1,209 @@
# SPDX-FileCopyrightText: 2019-2022 Pleroma Authors
# SPDX-License-Identifier: LGPL-3.0-only
defmodule BBCode.Parser.Test do
use ExUnit.Case
alias BBCode.Parser
describe "simple tags" do
test "it parses [b] tags correctly" do
assert {:ok, [b: "testing"]} = Parser.parse("[b]testing[/b]")
end
test "it parses [i] tags correctly" do
assert {:ok, [i: "testing"]} = Parser.parse("[i]testing[/i]")
end
test "it parses [u] tags correctly" do
assert {:ok, [u: "testing"]} = Parser.parse("[u]testing[/u]")
end
test "it parses [s] tags correctly" do
assert {:ok, [s: "testing"]} = Parser.parse("[s]testing[/s]")
end
test "it parses [code] tags correctly" do
assert {:ok, [code: "testing"]} = Parser.parse("[code]testing[/code]")
end
test "it parses [quote] tags correctly" do
assert {:ok, [quote: "testing"]} = Parser.parse("[quote]testing[/quote]")
end
test "it parses [img] tags correctly" do
assert {:ok, [img: "https://example.com"]} = Parser.parse("[img]https://example.com[/img]")
assert {:ok, [{:img, 64, 64, "https://example.com"}]} =
Parser.parse("[img=64x64]https://example.com[/img]")
end
test "it parses [url] tags correctly" do
assert {:ok, [url: "https://example.com"]} = Parser.parse("[url]https://example.com[/url]")
assert {:ok, [{:url, "https://example.com", {:b, "Example.com"}}]} =
Parser.parse("[url=https://example.com][b]Example.com[/b][/url]")
end
end
describe "nested tags" do
test "it parses [ul] lists correctly" do
+ assert {:ok, [{:ul, {:li, "a"}}]} =
+ Parser.parse("[ul][li]a[/li][/ul]")
+
assert {:ok, [{:ul, [{:li, "a"}, {:li, "b"}]}]} =
Parser.parse("[ul][li]a[/li][li]b[/li][/ul]")
+
+ assert {:ok, [{:ul, {:li, "a"}}]} =
+ Parser.parse("[ul][*]a[/ul]")
+
+ assert {:ok, [{:ul, [{:li, "a"}, {:li, "b"}]}]} =
+ Parser.parse("[ul][*]a[*]b[/ul]")
end
test "it parses [ol] lists correctly" do
+ assert {:ok, [{:ol, {:li, "a"}}]} =
+ Parser.parse("[ol][li]a[/li][/ol]")
+
assert {:ok, [{:ol, [{:li, "a"}, {:li, "b"}]}]} =
Parser.parse("[ol][li]a[/li][li]b[/li][/ol]")
+
+ assert {:ok, [{:ol, {:li, "a"}}]} =
+ Parser.parse("[ol][*]a[/ol]")
+
+ assert {:ok, [{:ol, [{:li, "a"}, {:li, "b"}]}]} =
+ Parser.parse("[ol][*]a[*]b[/ol]")
end
end
describe "multiline" do
test "it parses a multiline [li] list" do
data = """
[ul]
[li]a[/li]
[li]b[/li]
[/ul]
"""
assert {:ok, [{:ul, [{:li, "a"}, {:li, "b"}]}]} = Parser.parse(data)
end
test "it parses a multiline [*] list" do
data = """
[ul]
[*]a
[*]b
[/ul]
"""
- assert {:ok, [{:ul, [{:li, ["a"]}, {:li, ["b"]}]}]} = Parser.parse(data)
+ assert {:ok, [{:ul, [{:li, "a"}, {:li, "b"}]}]} = Parser.parse(data)
end
test "it parses a multiline [*] list with children" do
data = """
[ul]
[*][url=http://example.com]Example[/url]
[/ul]
"""
- assert {:ok, [{:ul, {:li, [{:url, "http://example.com", "Example"}]}}]} = Parser.parse(data)
+ assert {:ok, [{:ul, {:li, {:url, "http://example.com", "Example"}}}]} = Parser.parse(data)
end
end
describe "property tags" do
test "it parses [url=] tags correctly" do
assert {:ok, [{:url, "http://example.com", "Example"}]} =
Parser.parse("[url=http://example.com]Example[/url]")
end
end
describe "non-tags" do
test "it properly handles bracket text" do
data = "oh no! [swearing intensifies]"
assert {:ok, ["oh no! ", "[swearing intensifies]"]} = Parser.parse(data)
end
test "it continues when unknown tag is encountered" do
assert {:ok, ["[asdf]", " valid text input and ", {:b, "bold"}, " text"]} =
Parser.parse("[asdf] valid text input and [b]bold[/b] text")
end
test "it continues when unknown closing tag is encountered" do
assert {:ok, ["[/asdf]"]} = Parser.parse("[/asdf]")
assert {:ok, ["[/asdf]", " text"]} = Parser.parse("[/asdf] text")
assert {:ok, [b: ["[/asdf]", " text"]]} = Parser.parse("[b][/asdf] text[/b]")
end
test "it continues when known closing-only tag is encountered" do
assert {:ok, ["[/url]"]} = Parser.parse("[/url]")
assert {:ok, ["[/url]", " text"]} = Parser.parse("[/url] text")
assert {:ok, ["text ", "[/url]"]} = Parser.parse("text [/url]")
end
test "it continues when opening and closing tag is encountered" do
assert {:ok, ["asdf ", "[asdf]", "#cofe and #hambaga", "[/asdf]", " rest of text"]} =
Parser.parse("asdf [asdf]#cofe and #hambaga[/asdf] rest of text")
end
test "it works with nested tags" do
assert {:ok,
[
code: [
"[lain@navi ~/.local/src/git/bbcode]",
"$ hacking on the ",
{:b, "BBCode"},
" parser using ",
"[asdf]",
"as the version manager",
"[/asdf]"
]
]} =
Parser.parse(
"[code][lain@navi ~/.local/src/git/bbcode]$ hacking on the [b]BBCode[/b] parser using [asdf]as the version manager[/asdf][/code]"
)
end
test "it continues when stray brackets are encountered" do
assert {:ok,
[
{:code, [" a", "[ b]", " ", "[ ]", " ", "[] ", "valid input"]},
" and ",
{:b, "bold"},
" text ",
"]"
]} =
Parser.parse("[code] a[ b] [ ] [] valid input[/code] and [b]bold[/b] text ]")
end
end
+
+ describe "malformed input" do
+ test "it it ignores list items outside of list blocks" do
+ assert {:ok, ["[li]", "test", "[/li]"]} = Parser.parse("[li]test[/li]")
+ assert {:ok, ["[*]", "test"]} = Parser.parse("[*]test")
+ end
+
+ test "it works with mismatched list blocks" do
+ assert {:ok, ["[ul]", "[li]", "test1", "[/li]", "[li]", "test2", "[/li]", "[/ol]"] } = Parser.parse("[ul][li]test1[/li][li]test2[/li][/ol]")
+ assert {:ok, ["[ul]", "[*]", "test1", "[*]", "test2", "[/ol]"]} = Parser.parse("[ul][*]test1[*]test2[/ol]")
+ assert {:ok, ["[ol]", "[li]", "test", "[/li]", "[/ul]"] } = Parser.parse("[ol][li]test[/li][/ul]")
+ assert {:ok, ["[ol]", "[*]", "test", "[/ul]"]} = Parser.parse("[ol][*]test[/ul]")
+
+ data_star = """
+ [ul]
+ [*]test1
+ [*]test2
+ [/ol]
+ """
+
+ data_li = """
+ [ul]
+ [li]test1[/li]
+ [li]test2[/li]
+ [/ol]
+ """
+
+ assert {:ok, ["[ul]", {:br}, "[*]", "test1", {:br}, "[*]", "test2", {:br}, "[/ol]", {:br}]} = Parser.parse(data_star)
+ assert {:ok, ["[ul]", {:br}, "[li]", "test1", "[/li]", {:br}, "[li]", "test2", "[/li]", {:br}, "[/ol]", {:br}]} = Parser.parse(data_li)
+ end
+ end
end

File Metadata

Mime Type
text/x-diff
Expires
Fri, Oct 9, 12:39 PM (1 d, 23 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1784733
Default Alt Text
(22 KB)

Event Timeline