add Recovery.Prefix module

This commit is contained in:
mmacia committed 2026-09-08 23:16:19 +00:00
1 parent 50fb5b5363
commit 6292f38993
2 files changed
+73

No files matched your search

+45
View File
@@ -0,0 +1,45 @@
defmodule FeedReader.Recovery.Prefix do
@moduledoc """
Recovers the beginning of an XML document before SAX parsing.
Feeds commonly contain a UTF-8 BOM or whitespace before the XML declaration,
while Saxy expects the document to begin at the XML prolog or root element.
This module removes those prefixes incrementally and retains incomplete BOM
bytes across chunks, so it is safe for arbitrarily small stream chunks.
"""
def normalize(input) do
Stream.transform(input, fn -> {:leading, <<>>} end, &normalize_chunk/2, &flush/1)
end
defp normalize_chunk(chunk, {:leading, prefix}) do
data = prefix <> chunk
if bom_prefix?(data) do
{[], {:leading, data}}
else
output =
if starts_with_bom?(data), do: binary_part(data, 3, byte_size(data) - 3), else: data
output = trim_whitespace(output)
if output == <<>>, do: {[], {:leading, <<>>}}, else: {[output], :ready}
end
end
defp normalize_chunk(chunk, :ready), do: {[chunk], :ready}
defp bom_prefix?(data) when byte_size(data) < 3,
do: data == binary_part(<<0xEF, 0xBB, 0xBF>>, 0, byte_size(data))
defp bom_prefix?(_data), do: false
defp starts_with_bom?(<<0xEF, 0xBB, 0xBF, _rest::binary>>), do: true
defp starts_with_bom?(_data), do: false
defp trim_whitespace(<<char, rest::binary>>) when char in [9, 10, 13, 32],
do: trim_whitespace(rest)
defp trim_whitespace(data), do: data
defp flush({:leading, <<>>}), do: []
defp flush({:leading, prefix}), do: [prefix]
defp flush(:ready), do: []
end