Ootempl.Conditional (ootempl v0.3.0)
Detects and parses conditional markers in text for conditional section processing.
Conditional markers allow showing or hiding document sections based on data conditions. Markers follow the syntax:
{{if variable}}- Start of conditional section{{else}}- Alternative content when condition is false (optional){{endif}}- End of conditional section
Markers are case-insensitive and support nested data paths using dot notation.
Examples
iex> Ootempl.Conditional.detect_conditionals("Hello {{if name}} world {{endif}}")
[
%{type: :if, condition: "name", path: ["name"], position: 6},
%{type: :endif, condition: nil, path: nil, position: 24}
]
iex> Ootempl.Conditional.detect_conditionals("{{if customer.active}} content {{endif}}")
[
%{type: :if, condition: "customer.active", path: ["customer", "active"], position: 0},
%{type: :endif, condition: nil, path: nil, position: 31}
]
iex> Ootempl.Conditional.detect_conditionals("{{if premium}} VIP {{else}} Standard {{endif}}")
[
%{type: :if, condition: "premium", path: ["premium"], position: 0},
%{type: :else, condition: nil, path: nil, position: 19},
%{type: :endif, condition: nil, path: nil, position: 37}
]
Summary
Functions
Collects all XML nodes between two boundary nodes, inclusive.
Detects all conditional markers in the given text.
Evaluates a conditional expression by checking if the data path resolves to a truthy value.
Finds section boundaries in an XML document for a conditional section.
Validates that all conditional markers are properly paired.
Types
Functions
@spec collect_section_nodes( Ootempl.Xml.xml_element(), Ootempl.Xml.xml_element(), Ootempl.Xml.xml_element() ) :: {:ok, [Ootempl.Xml.xml_node()]} | {:error, :boundaries_not_found}
Collects all XML nodes between two boundary nodes, inclusive.
Returns all nodes from the start node to the end node, including both boundaries. Handles sections spanning single or multiple paragraphs.
Parameters
xml_element- The XML element containing the sectionstart_node- The starting boundary nodeend_node- The ending boundary node
Returns
{:ok, nodes}- List of all nodes in the section (inclusive of boundaries){:error, :boundaries_not_found}- If start or end nodes are not found in the element
Examples
# See integration tests for XML examples
@spec detect_conditionals(String.t()) :: [conditional()]
Detects all conditional markers in the given text.
Returns a list of conditional markers in order of appearance with their positions.
Parameters
text- The text to scan for conditional markers
Returns
A list of conditional maps, each containing:
:type- Either:if,:else, or:endif:condition- The condition variable (only for:ifmarkers):path- Parsed path segments (only for:ifmarkers):position- Character position in the text
Examples
iex> Ootempl.Conditional.detect_conditionals("{{if active}}content{{endif}}")
[
%{type: :if, condition: "active", path: ["active"], position: 0},
%{type: :endif, condition: nil, path: nil, position: 20}
]
iex> Ootempl.Conditional.detect_conditionals("no markers here")
[]
iex> Ootempl.Conditional.detect_conditionals("{{if show}}yes{{else}}no{{endif}}")
[
%{type: :if, condition: "show", path: ["show"], position: 0},
%{type: :else, condition: nil, path: nil, position: 14},
%{type: :endif, condition: nil, path: nil, position: 24}
]
Evaluates a conditional expression by checking if the data path resolves to a truthy value.
Uses Ootempl.DataAccess.get_value/2 for case-insensitive nested data access,
then evaluates the result using truthiness rules.
Parameters
path- List of path segments to navigate (e.g.,["customer", "active"])data- The data map to evaluate against
Returns
{:ok, true}if the condition evaluates to truthy{:ok, false}if the condition evaluates to falsy{:error, reason}if the path cannot be resolved
Examples
iex> Ootempl.Conditional.evaluate_condition(["active"], %{"active" => true})
{:ok, true}
iex> Ootempl.Conditional.evaluate_condition(["active"], %{"active" => false})
{:ok, false}
iex> Ootempl.Conditional.evaluate_condition(["count"], %{"count" => 0})
{:ok, false}
iex> Ootempl.Conditional.evaluate_condition(["count"], %{"count" => 5})
{:ok, true}
iex> Ootempl.Conditional.evaluate_condition(["name"], %{"name" => ""})
{:ok, false}
iex> Ootempl.Conditional.evaluate_condition(["name"], %{"name" => "John"})
{:ok, true}
iex> Ootempl.Conditional.evaluate_condition(["customer", "active"], %{"customer" => %{"active" => true}})
{:ok, true}
iex> Ootempl.Conditional.evaluate_condition(["missing"], %{"name" => "John"})
{:error, {:path_not_found, ["missing"]}}
@spec find_section_boundaries(Ootempl.Xml.xml_element(), String.t(), String.t()) :: {:ok, {Ootempl.Xml.xml_element(), Ootempl.Xml.xml_element()}} | {:error, :if_marker_not_found | :endif_marker_not_found}
Finds section boundaries in an XML document for a conditional section.
Locates the paragraph containing the {{if}} marker (start boundary) and the
paragraph containing the {{endif}} marker (end boundary).
Parameters
xml_element- The XML element to search withinif_marker- The full if marker text to find (e.g., "{{if active}}")endif_marker- The endif marker text to find (e.g., "{{endif}}")
Returns
{:ok, {start_paragraph, end_paragraph}}if both boundaries found{:error, :if_marker_not_found}if the if marker is not found{:error, :endif_marker_not_found}if the endif marker is not found
Examples
# See integration tests for XML examples
@spec validate_pairs([conditional()]) :: :ok | {:error, String.t()}
Validates that all conditional markers are properly paired.
Ensures each {{if}} has a corresponding {{endif}} and detects orphaned markers.
Also validates that {{else}} markers are properly placed within if/endif blocks
and that there is at most one {{else}} per block.
Parameters
conditionals- List of conditional markers fromdetect_conditionals/1
Returns
:okif all markers are properly paired{:error, reason}if validation fails
Examples
iex> Ootempl.Conditional.validate_pairs([
...> %{type: :if, condition: "name", path: ["name"], position: 0},
...> %{type: :endif, condition: nil, path: nil, position: 10}
...> ])
:ok
iex> Ootempl.Conditional.validate_pairs([
...> %{type: :if, condition: "name", path: ["name"], position: 0}
...> ])
{:error, "Unmatched {{if name}} at position 0"}
iex> Ootempl.Conditional.validate_pairs([
...> %{type: :endif, condition: nil, path: nil, position: 0}
...> ])
{:error, "Orphan {{endif}} at position 0 (no matching {{if}})"}
iex> Ootempl.Conditional.validate_pairs([
...> %{type: :else, condition: nil, path: nil, position: 0}
...> ])
{:error, "Orphan {{else}} at position 0 (no matching {{if}})"}
iex> Ootempl.Conditional.validate_pairs([
...> %{type: :if, condition: "x", path: ["x"], position: 0},
...> %{type: :else, condition: nil, path: nil, position: 10},
...> %{type: :else, condition: nil, path: nil, position: 20},
...> %{type: :endif, condition: nil, path: nil, position: 30}
...> ])
{:error, "Multiple {{else}} markers in conditional block starting at position 0"}