recover a wrong closed tag
This commit is contained in:
1 parent
fd3a74f6d6
commit
958b9db9af
2 files changed
+99
-7
No files matched your search
@@ -337,15 +337,99 @@ defmodule FeedReader.Parser do
|
|||||||
defp flush_entities({_mode, pending}), do: [pending]
|
defp flush_entities({_mode, pending}), do: [pending]
|
||||||
|
|
||||||
defp normalize_attribute_quotes(input) do
|
defp normalize_attribute_quotes(input) do
|
||||||
Stream.map(input, fn chunk ->
|
Stream.transform(
|
||||||
Regex.replace(
|
input,
|
||||||
~r/(=\")([^\"]*)(\")([^\"]*)(\")(\")(?=\s*(?:\/>|>))/,
|
fn -> <<>> end,
|
||||||
chunk,
|
fn chunk, pending ->
|
||||||
"\\1\\2"\\4"\\6"
|
{output, pending} = scan_attribute_markup(pending <> chunk, [])
|
||||||
)
|
{if(output == [], do: [], else: [IO.iodata_to_binary(Enum.reverse(output))]), pending}
|
||||||
end)
|
end,
|
||||||
|
fn
|
||||||
|
<<>> -> []
|
||||||
|
pending -> [pending]
|
||||||
|
end
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp scan_attribute_markup(<<>>, output), do: {output, <<>>}
|
||||||
|
|
||||||
|
defp scan_attribute_markup(data, output) do
|
||||||
|
case :binary.match(data, "<") do
|
||||||
|
:nomatch ->
|
||||||
|
{[data | output], <<>>}
|
||||||
|
|
||||||
|
{position, 1} when position > 0 ->
|
||||||
|
{prefix, rest} = :erlang.split_binary(data, position)
|
||||||
|
scan_attribute_markup(rest, [prefix | output])
|
||||||
|
|
||||||
|
{0, 1} ->
|
||||||
|
case markup_end(data) do
|
||||||
|
:incomplete -> {output, data}
|
||||||
|
{markup, rest} -> scan_attribute_markup(rest, [repair_markup_quotes(markup) | output])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp markup_end(<<"<![CDATA[", _rest::binary>> = data), do: special_markup_end(data, "]]>")
|
||||||
|
defp markup_end(<<"<!--", _rest::binary>> = data), do: special_markup_end(data, "-->")
|
||||||
|
|
||||||
|
defp markup_end(data) do
|
||||||
|
case :binary.match(data, ">") do
|
||||||
|
:nomatch -> :incomplete
|
||||||
|
{position, 1} -> :erlang.split_binary(data, position + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp special_markup_end(data, closing) do
|
||||||
|
case :binary.match(data, closing) do
|
||||||
|
:nomatch -> :incomplete
|
||||||
|
{position, length} -> :erlang.split_binary(data, position + length)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp repair_markup_quotes(<<"<![CDATA[", _rest::binary>> = markup), do: markup
|
||||||
|
defp repair_markup_quotes(<<"<!--", _rest::binary>> = markup), do: markup
|
||||||
|
|
||||||
|
defp repair_markup_quotes(<<"<", rest::binary>>),
|
||||||
|
do: "<" <> repair_tag_quotes(rest, nil, false, [])
|
||||||
|
|
||||||
|
defp repair_tag_quotes(<<>>, _quote, _expect, output),
|
||||||
|
do: IO.iodata_to_binary(Enum.reverse(output))
|
||||||
|
|
||||||
|
defp repair_tag_quotes(<<char, rest::binary>>, nil, expect, output)
|
||||||
|
when char in [32, 9, 10, 13],
|
||||||
|
do: repair_tag_quotes(rest, nil, expect, [<<char>> | output])
|
||||||
|
|
||||||
|
defp repair_tag_quotes(<<char, rest::binary>>, nil, true, output)
|
||||||
|
when char in [34, 39],
|
||||||
|
do: repair_tag_quotes(rest, {char, false}, false, [<<char>> | output])
|
||||||
|
|
||||||
|
defp repair_tag_quotes(<<char, rest::binary>>, nil, _expect, output),
|
||||||
|
do: repair_tag_quotes(rest, nil, char == ?=, [<<char>> | output])
|
||||||
|
|
||||||
|
defp repair_tag_quotes(<<quote, rest::binary>>, {quote, has_content}, _expect, output) do
|
||||||
|
cond do
|
||||||
|
delimiter?(rest) ->
|
||||||
|
repair_tag_quotes(rest, nil, false, [<<quote>> | output])
|
||||||
|
|
||||||
|
empty_attribute_quote?(rest, quote, has_content) ->
|
||||||
|
<<_quote, after_quote::binary>> = rest
|
||||||
|
repair_tag_quotes(after_quote, nil, false, [<<quote>> | output])
|
||||||
|
|
||||||
|
true ->
|
||||||
|
repair_tag_quotes(rest, {quote, true}, false, [""" | output])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp repair_tag_quotes(<<char, rest::binary>>, {quote, _has_content}, _expect, output),
|
||||||
|
do: repair_tag_quotes(rest, {quote, true}, false, [<<char>> | output])
|
||||||
|
|
||||||
|
defp delimiter?(<<char, _rest::binary>>) when char in [32, 9, 10, 13, 47, 62, 63], do: true
|
||||||
|
defp delimiter?(_rest), do: false
|
||||||
|
|
||||||
|
defp empty_attribute_quote?(<<quote, rest::binary>>, quote, false), do: delimiter?(rest)
|
||||||
|
defp empty_attribute_quote?(_rest, _quote, _has_content), do: false
|
||||||
|
|
||||||
defp event_stream(input, format, opts, chunk_size) do
|
defp event_stream(input, format, opts, chunk_size) do
|
||||||
Stream.resource(
|
Stream.resource(
|
||||||
fn -> start_parser(input, format, opts, chunk_size) end,
|
fn -> start_parser(input, format, opts, chunk_size) end,
|
||||||
|
|||||||
@@ -91,6 +91,14 @@ defmodule FeedReader.CollectorTest do
|
|||||||
FeedReader.parse_binary(xml, max_items: 1)
|
FeedReader.parse_binary(xml, max_items: 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "does not repair quotes across adjacent XML elements" do
|
||||||
|
xml =
|
||||||
|
~s(<rss version="2.0"><channel><title>Example</title><item><source url="">Source</source><category domain="region">News</category></item></channel></rss>)
|
||||||
|
|
||||||
|
assert {:ok, %ParseResult{items: [%Item{title: nil}]}} =
|
||||||
|
FeedReader.parse_binary(xml, max_items: 1, chunk_size: 1)
|
||||||
|
end
|
||||||
|
|
||||||
test "rejects invalid chunks and binary arguments" do
|
test "rejects invalid chunks and binary arguments" do
|
||||||
assert {:error, :invalid_chunk} =
|
assert {:error, :invalid_chunk} =
|
||||||
FeedReader.parse_stream([@xml, 123], max_items: 2)
|
FeedReader.parse_stream([@xml, 123], max_items: 2)
|
||||||
|
|||||||
Reference in new issue
Block a user