add parse_file, parse_binary and parse_stream

This commit is contained in:
mmacia committed 2026-05-13 23:32:52 +00:00
1 parent 65bfb4b9a8
commit 714a2415b7
1 file changed
+91 -25
+91 -25
View File
@@ -2,7 +2,8 @@ defmodule FeedReader do
@moduledoc """ @moduledoc """
Standalone feed fetching and parsing library. Standalone feed fetching and parsing library.
This module exposes the public format detection and validation helpers. This module exposes format detection, validation, local parsing, and streaming
helpers.
""" """
@typedoc "`:rss`, `:atom`, `:rdf`, `:unknown`, or a custom format's atom ID." @typedoc "`:rss`, `:atom`, `:rdf`, `:unknown`, or a custom format's atom ID."
@@ -12,9 +13,11 @@ defmodule FeedReader do
Detects a format from a bounded binary prefix. Detects a format from a bounded binary prefix.
## Options ## Options
- `:max_detection_bytes` — `pos_integer()`; default `65_536`; invalid values yield `:unknown`. - `:max_detection_bytes` — positive byte limit for the inspected prefix;
- `:formats` — list of modules, checked after RSS, Atom, and RDF in list order; duplicates removed. defaults to `65_536`. Invalid values yield `:unknown`.
Custom modules provide `matches_prefix?/1` and `id/0`. - `:formats` — custom format modules checked after RSS, Atom, and RDF in list
order. Duplicates are removed. Modules provide `matches_prefix?/1` and
`id/0`.
""" """
@spec detect_format(binary(), keyword()) :: format_id() @spec detect_format(binary(), keyword()) :: format_id()
def detect_format(content, opts \\ []) do def detect_format(content, opts \\ []) do
@@ -25,9 +28,11 @@ defmodule FeedReader do
Checks only the bounded-prefix signature for a format ID or module. Checks only the bounded-prefix signature for a format ID or module.
## Options ## Options
- `:max_detection_bytes` — `pos_integer()`, default `65_536`; invalid values yield `false`. - `:max_detection_bytes` — positive byte limit for the inspected prefix;
- `:formats` — custom modules used to resolve custom format IDs. Modules provide defaults to `65_536`. Invalid values yield `false`.
`matches_prefix?/1` and `id/0`; passing the format module directly also works. - `:formats` — custom format modules used to resolve custom format IDs.
Modules provide `matches_prefix?/1` and `id/0`; passing the format module
directly also works.
""" """
@spec validate(binary(), atom() | module(), keyword()) :: boolean() @spec validate(binary(), atom() | module(), keyword()) :: boolean()
def validate(content, format, opts \\ []) do def validate(content, format, opts \\ []) do
@@ -47,27 +52,28 @@ defmodule FeedReader do
- `:formats` — a list of additional format modules. They are checked after - `:formats` — a list of additional format modules. They are checked after
the built-in RSS, Atom, and RDF formats and may be selected with `:format`. the built-in RSS, Atom, and RDF formats and may be selected with `:format`.
Custom modules must implement `FeedReader.Format`. Custom modules must implement `FeedReader.Format`.
- `:chunk_size` — positive integer number of bytes supplied to the XML parser - `:chunk_size` — positive number of bytes supplied to the XML parser per
per chunk. Defaults to `65_536`. chunk. Defaults to `65_536`.
- `:max_detection_bytes` — positive integer limit for the prefix inspected - `:max_detection_bytes` — positive byte limit for format detection. Defaults
during format detection. Defaults to `65_536`. Invalid values produce to `65_536`. Invalid values produce `{:error, :unknown}` unless a valid
`{:error, :unknown}` unless a valid explicit `:format` is supplied. explicit `:format` is supplied.
- `:feed_url` — optional source URL passed to format normalizers and stored in - `:feed_url` — optional source URL passed to format normalizers and stored in
the normalized feed metadata. Defaults to `nil`. the normalized feed metadata. Defaults to `nil`.
- `:max_xml_events` — non-negative integer maximum SAX events. Defaults to - `:formats` — additional format modules checked after the built-in formats.
- `:max_xml_events` — non-negative maximum SAX events. Defaults to
`1_000_000`. `1_000_000`.
- `:max_xml_depth` — non-negative integer maximum XML nesting depth. Defaults - `:max_xml_depth` — non-negative maximum XML nesting depth. Defaults to
to `256`. `256`.
- `:max_attributes_per_element` — non-negative integer maximum attributes on - `:max_attributes_per_element` — non-negative maximum attributes on one
one element. Defaults to `128`. element. Defaults to `128`.
- `:max_attribute_bytes` — non-negative integer maximum bytes used by one - `:max_attribute_bytes` — non-negative maximum bytes used by one element's
element's attributes. Defaults to `65_536`. attributes. Defaults to `65_536`.
- `:max_feed_metadata_bytes` — non-negative integer maximum retained feed - `:max_feed_metadata_bytes` — non-negative maximum retained feed metadata
metadata bytes. Defaults to `1_048_576`. bytes. Defaults to `1_048_576`.
- `:max_item_bytes` — non-negative integer maximum retained bytes for one - `:max_item_bytes` — non-negative maximum retained bytes for one item.
item. Defaults to `4_194_304`. Defaults to `4_194_304`.
- `:max_queue_bytes` — non-negative integer maximum serialized size of one - `:max_queue_bytes` — non-negative maximum serialized size of one pending
pending event. Defaults to `:max_item_bytes`. event. Defaults to `:max_item_bytes`.
Zero is valid for resource limits. Missing or invalid resource-limit values Zero is valid for resource limits. Missing or invalid resource-limit values
use their defaults. Additional options are passed through to the selected use their defaults. Additional options are passed through to the selected
@@ -77,4 +83,64 @@ defmodule FeedReader do
def stream_file(path, opts \\ []) do def stream_file(path, opts \\ []) do
FeedReader.Parser.stream_file(path, opts) FeedReader.Parser.stream_file(path, opts)
end end
@doc """
Collects a caller-owned local XML file into a bounded parse result.
`:max_items` is required and must be a positive integer. The parser continues
through the end of the source so malformed tails are reported; an item
accepted beyond the limit returns `{:error, {:max_items_exceeded, max_items}}`.
`:item_filter`, when supplied, must be a one-argument function and is applied
before counting and collecting items.
## Options
Accepts all `stream_file/2` parsing options, plus:
- `:max_items` — required positive integer limit for accepted items. The
collector continues parsing after reaching the limit and returns
`{:error, {:max_items_exceeded, max_items}}` if another item is accepted.
- `:item_filter` — optional one-argument function applied before counting and
retaining an item.
"""
@spec parse_file(Path.t(), keyword()) :: {:ok, FeedReader.ParseResult.t()} | {:error, term()}
def parse_file(path, opts \\ []) do
FeedReader.Collector.collect(path, opts)
end
@doc """
Collects XML binary content into a bounded parse result.
## Options
Accepts the same parsing and collection options as `parse_file/2`, including
required `:max_items` and optional `:item_filter`.
"""
@spec parse_binary(binary(), keyword()) ::
{:ok, FeedReader.ParseResult.t()} | {:error, term()}
def parse_binary(content, opts \\ [])
def parse_binary(content, opts) when is_binary(content) do
FeedReader.Collector.parse_stream([content], opts)
end
def parse_binary(_content, _opts), do: {:error, :invalid_binary}
@doc """
Collects an enumerable of binary XML chunks into a bounded parse result.
Chunks are written incrementally to a temporary file and then parsed through
the same event stream used by `parse_file/2`. The temporary file is removed
on success and failure.
## Options
Accepts the same parsing and collection options as `parse_file/2`, including
required `:max_items` and optional `:item_filter`.
"""
@spec parse_stream(Enumerable.t(), keyword()) ::
{:ok, FeedReader.ParseResult.t()} | {:error, term()}
def parse_stream(chunks, opts \\ []) do
FeedReader.Collector.parse_stream(chunks, opts)
end
end end