Packages

phoenix_kit

1.7.49
1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules publishing page_builder.ex
Raw

lib/modules/publishing/page_builder.ex

defmodule PhoenixKit.Modules.Publishing.PageBuilder do
@moduledoc """
Rendering pipeline for .phk (PhoenixKit) page files.
Processes component-based page definitions through:
1. Read .phk file
2. Parse XML to AST
3. Inject dynamic data ({{variable}} placeholders)
4. Resolve components (map to actual component modules)
5. Apply theme/variants
6. Render to HTML
"""
alias PhoenixKit.Modules.Publishing.PageBuilder.Parser
alias PhoenixKit.Modules.Publishing.PageBuilder.Renderer
@type assigns :: map()
@type ast :: map()
@type render_result :: {:ok, Phoenix.LiveView.Rendered.t()} | {:error, term()}
@doc """
Renders a .phk page file to HTML.
## Examples
iex> PageBuilder.render_page("/path/to/page.phk", %{user: %{name: "Alice"}})
{:ok, rendered_html}
"""
@spec render_page(String.t(), assigns()) :: render_result()
def render_page(page_path, assigns \\ %{}) do
with {:ok, content} <- read_page_file(page_path),
{:ok, ast} <- parse_to_ast(content),
{:ok, ast_with_data} <- inject_dynamic_data(ast, assigns),
{:ok, resolved} <- resolve_components(ast_with_data),
{:ok, themed} <- apply_theme(resolved, assigns),
{:ok, html} <- render_to_html(themed, assigns) do
{:ok, html}
else
{:error, reason} -> {:error, reason}
end
end
@doc """
Renders .phk content directly (without file path).
"""
@spec render_content(String.t(), assigns()) :: render_result()
def render_content(content, assigns \\ %{}) do
with {:ok, ast} <- parse_to_ast(content),
{:ok, ast_with_data} <- inject_dynamic_data(ast, assigns),
{:ok, resolved} <- resolve_components(ast_with_data),
{:ok, themed} <- apply_theme(resolved, assigns),
{:ok, html} <- render_to_html(themed, assigns) do
{:ok, html}
else
{:error, reason} -> {:error, reason}
end
end
# Step 1: Read .phk file
defp read_page_file(page_path) do
case File.read(page_path) do
{:ok, content} -> {:ok, content}
{:error, reason} -> {:error, {:file_read_error, reason}}
end
end
# Step 2: Parse XML to AST
defp parse_to_ast(content) do
Parser.parse(content)
end
# Step 3: Inject dynamic data (replace {{variable}} placeholders)
defp inject_dynamic_data(ast, assigns) do
{:ok, inject_assigns(ast, assigns)}
end
# Step 4: Resolve components (map XML tags to actual component modules)
defp resolve_components(ast) do
{:ok, ast}
end
# Step 5: Apply theme/variant settings
defp apply_theme(ast, _assigns) do
{:ok, ast}
end
# Step 6: Render to HTML
defp render_to_html(ast, assigns) do
Renderer.render(ast, assigns)
end
# Recursively inject assigns into AST nodes
defp inject_assigns(ast, assigns) when is_map(ast) do
ast
|> Map.update(:content, nil, &inject_assigns(&1, assigns))
|> Map.update(:attributes, %{}, &inject_assigns(&1, assigns))
|> Map.update(:children, [], &inject_assigns(&1, assigns))
end
defp inject_assigns(ast, assigns) when is_list(ast) do
Enum.map(ast, &inject_assigns(&1, assigns))
end
defp inject_assigns(content, assigns) when is_binary(content) do
interpolate_string(content, assigns)
end
defp inject_assigns(value, _assigns), do: value
# Interpolate {{variable}} placeholders
defp interpolate_string(string, assigns) do
Regex.replace(~r/\{\{([^}]+)\}\}/, string, fn _, path ->
get_nested_value(assigns, String.trim(path)) |> to_string()
end)
end
# Get nested value from assigns (e.g., "user.name" -> assigns.user.name)
defp get_nested_value(map, path) do
path
|> String.split(".")
|> Enum.reduce(map, fn key, acc ->
case acc do
%{} -> Map.get(acc, key) || Map.get(acc, String.to_existing_atom(key))
_ -> nil
end
end)
rescue
_ -> ""
end
end