Current section

Files

Jump to
html_to_markdown lib html_to_markdown metadata_config.ex
Raw

lib/html_to_markdown/metadata_config.ex

defmodule HtmlToMarkdown.MetadataConfig do
@moduledoc """
Configuration for metadata extraction.
This controls which metadata sections are extracted during `convert_with_metadata/3`.
"""
@default_max_structured_data_size 1_000_000
defstruct extract_document: true,
extract_headers: true,
extract_links: true,
extract_images: true,
extract_structured_data: true,
max_structured_data_size: @default_max_structured_data_size
@type t :: %__MODULE__{
extract_document: boolean(),
extract_headers: boolean(),
extract_links: boolean(),
extract_images: boolean(),
extract_structured_data: boolean(),
max_structured_data_size: pos_integer()
}
@spec new(t() | map() | keyword() | nil) :: t()
def new(nil), do: %__MODULE__{}
def new(%__MODULE__{} = cfg), do: cfg
def new(attrs) when is_list(attrs) or is_map(attrs) do
attrs =
attrs
|> Map.new()
|> Enum.reduce(%{}, fn {key, value}, acc ->
case normalize_key(key) do
nil -> acc
normalized -> Map.put(acc, normalized, value)
end
end)
struct(%__MODULE__{}, attrs)
end
defp normalize_key(key) when is_atom(key) do
if key in [
:extract_document,
:extract_headers,
:extract_links,
:extract_images,
:extract_structured_data,
:max_structured_data_size
],
do: key,
else: nil
end
defp normalize_key(key) when is_binary(key) do
key
|> String.trim()
|> String.replace("-", "_")
|> case do
"extract_document" -> :extract_document
"extract_headers" -> :extract_headers
"extract_links" -> :extract_links
"extract_images" -> :extract_images
"extract_structured_data" -> :extract_structured_data
"max_structured_data_size" -> :max_structured_data_size
_ -> nil
end
end
defp normalize_key(_), do: nil
@doc false
@spec to_map_result(t() | map() | keyword() | nil) :: {:ok, map()} | {:error, String.t()}
def to_map_result(nil), do: to_map_result(%__MODULE__{})
def to_map_result(%__MODULE__{} = cfg), do: config_to_map(cfg)
def to_map_result(attrs) when is_list(attrs) or is_map(attrs),
do: attrs |> new() |> config_to_map()
defp config_to_map(%__MODULE__{} = cfg) do
with {:ok, extract_document} <- ensure_boolean(cfg.extract_document, :extract_document),
{:ok, extract_headers} <- ensure_boolean(cfg.extract_headers, :extract_headers),
{:ok, extract_links} <- ensure_boolean(cfg.extract_links, :extract_links),
{:ok, extract_images} <- ensure_boolean(cfg.extract_images, :extract_images),
{:ok, extract_structured_data} <-
ensure_boolean(cfg.extract_structured_data, :extract_structured_data),
{:ok, max_size} <-
positive_integer(cfg.max_structured_data_size, :max_structured_data_size) do
{:ok,
%{
"extract_document" => extract_document,
"extract_headers" => extract_headers,
"extract_links" => extract_links,
"extract_images" => extract_images,
"extract_structured_data" => extract_structured_data,
"max_structured_data_size" => max_size
}}
end
end
defp ensure_boolean(value, _field) when is_boolean(value), do: {:ok, value}
defp ensure_boolean(_value, field), do: {:error, error_message(field, "must be a boolean")}
defp positive_integer(value, _field) when is_integer(value) and value > 0, do: {:ok, value}
defp positive_integer(value, field) when is_binary(value) do
value
|> String.trim()
|> Integer.parse()
|> case do
{parsed, ""} when parsed > 0 -> {:ok, parsed}
_ -> {:error, error_message(field, "must be a positive integer")}
end
end
defp positive_integer(_value, field),
do: {:error, error_message(field, "must be a positive integer")}
defp error_message(field, message) when is_atom(field), do: "#{field}: #{message}"
defp error_message(field, message), do: "#{field}: #{message}"
end