detect encode and recode to utf8
This commit is contained in:
1 parent
37ef12206b
commit
7a4475b989
1 file changed
+67
-1
@@ -47,7 +47,7 @@ defmodule FeedReader.Parser do
|
|||||||
|
|
||||||
format ->
|
format ->
|
||||||
input = File.stream!(path, Keyword.get(opts, :chunk_size, @default_chunk_size), [:raw])
|
input = File.stream!(path, Keyword.get(opts, :chunk_size, @default_chunk_size), [:raw])
|
||||||
{:ok, format, normalize_xml_prefix(input)}
|
{:ok, format, normalize_xml_input(input, declared_encoding(prefix))}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -67,6 +67,29 @@ defmodule FeedReader.Parser do
|
|||||||
|
|
||||||
defp format_module(_format, _opts), do: nil
|
defp format_module(_format, _opts), do: nil
|
||||||
|
|
||||||
|
defp declared_encoding(prefix) do
|
||||||
|
case Regex.run(~r/<\?xml\b[^>]*\bencoding\s*=\s*["']([^"']+)["']/is, prefix,
|
||||||
|
capture: :all_but_first
|
||||||
|
) do
|
||||||
|
[encoding] -> normalize_encoding(encoding)
|
||||||
|
_none -> :utf8
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_encoding(encoding) do
|
||||||
|
case String.downcase(encoding) do
|
||||||
|
value when value in ["iso-8859-1", "latin1", "latin-1"] -> :latin1
|
||||||
|
_utf8 -> :utf8
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_xml_input(input, encoding) do
|
||||||
|
input
|
||||||
|
|> normalize_xml_prefix()
|
||||||
|
|> transcode_input(encoding)
|
||||||
|
|> normalize_encoding_declaration(encoding)
|
||||||
|
end
|
||||||
|
|
||||||
defp normalize_xml_prefix(input) do
|
defp normalize_xml_prefix(input) do
|
||||||
Stream.transform(
|
Stream.transform(
|
||||||
input,
|
input,
|
||||||
@@ -116,6 +139,49 @@ defmodule FeedReader.Parser do
|
|||||||
defp flush_prefix({:leading, prefix}), do: [prefix]
|
defp flush_prefix({:leading, prefix}), do: [prefix]
|
||||||
defp flush_prefix(:ready), do: []
|
defp flush_prefix(:ready), do: []
|
||||||
|
|
||||||
|
defp transcode_input(input, :utf8), do: input
|
||||||
|
|
||||||
|
defp transcode_input(input, :latin1),
|
||||||
|
do: Stream.map(input, fn chunk -> :unicode.characters_to_binary(chunk, :latin1, :utf8) end)
|
||||||
|
|
||||||
|
defp normalize_encoding_declaration(input, :utf8), do: input
|
||||||
|
|
||||||
|
defp normalize_encoding_declaration(input, :latin1) do
|
||||||
|
Stream.transform(
|
||||||
|
input,
|
||||||
|
fn -> {:prolog, <<>>} end,
|
||||||
|
&rewrite_declaration_chunk/2,
|
||||||
|
&flush_declaration/1
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp rewrite_declaration_chunk(chunk, {:prolog, prefix}) do
|
||||||
|
data = prefix <> chunk
|
||||||
|
|
||||||
|
case :binary.match(data, "?>") do
|
||||||
|
{position, 2} ->
|
||||||
|
{declaration, rest} = :erlang.split_binary(data, position + 2)
|
||||||
|
{[rewrite_declaration(declaration) <> rest], :ready}
|
||||||
|
|
||||||
|
:nomatch ->
|
||||||
|
{[], {:prolog, data}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp rewrite_declaration_chunk(chunk, :ready), do: {[chunk], :ready}
|
||||||
|
|
||||||
|
defp rewrite_declaration(declaration) do
|
||||||
|
Regex.replace(
|
||||||
|
~r/(encoding\s*=\s*["'])[^"']+(["'])/i,
|
||||||
|
declaration,
|
||||||
|
"\\1UTF-8\\2",
|
||||||
|
global: false
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp flush_declaration({:prolog, prefix}), do: [prefix]
|
||||||
|
defp flush_declaration(:ready), do: []
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
Reference in new issue
Block a user