# `Statifier.Lowering.Builders`
[🔗](https://github.com/riddler/statifier-ex/blob/v2.0.0/lib/statifier/lowering/builders.ex#L1)

One `build_*` function per supported SCXML element, reached through
`Statifier.Lowering`'s dispatch map - the structural fix for v1's
903-line, 73-clause `state_stack.ex` (`docs/architecture.md`,
"adding an element touches one builder").

Every builder lowers its own children first, through
`Statifier.Lowering.walk_children/2`, before building its own struct, so
errors accumulate in document order regardless of nesting depth. No
builder here takes a parent element name as an argument: placement of a
tagged child result into a parent's slot is each container's own
`place/3`, never a fact the child itself needs to know.

# `build_assign`

```elixir
@spec build_assign(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:content_node, Statifier.Document.Assign.t()} | nil,
   [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Assign{}` from an `<assign>` element, tagged
`{:content_node, assign}`.

Reads `location` (required - spec 5.4.2) and `expr`, both raw strings -
neither is resolved or compiled here (`Statifier.Document.Assign`'s
moduledoc forbids `Predicator` under `lib/statifier/document/`). A missing
`location` means no struct can be built at all (`:location` is
`@enforce_keys`'d on `Assign`), following `build_raise/2`'s
required-attribute pattern: `{nil, [Error.missing_attribute("assign",
"location", element.location)]}`.

`text` is set to `Statifier.Parser.DOM.text/1`'s verbatim, untrimmed
concatenation of `<assign>`'s direct text children (verbatim except for
the parser's XML 1.0 2.11 line-break fold - ADR-0045) - spec 5.4.2's other
value source, an `<assign>`'s children "provide an in-line specification
of the legal data value" (5.4.2, 5.9.3), so this builder reads
`element.children` directly rather than calling
`Statifier.Lowering.walk_children/2`, the same stray-text exemption
`build_data/2` takes. An element child is not silently dropped: each one
produces `{:misplaced_element, name, "assign"}` instead of a build
attempt of its own - `<assign>` does not hold a markup subtree.

# `build_cancel`

```elixir
@spec build_cancel(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:content_node, Statifier.Document.Cancel.t()},
   [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Cancel{}` from a `<cancel>` element (spec
6.3), tagged `{:content_node, cancel}` - `<cancel>` is executable content
exactly as `<send>` is, so it lands in a block's `content` list through
the existing `{:content_node, _}` placement clauses.

Reads both of 6.3.1's own attributes, each raw and nilable
(`Statifier.Document.Cancel`'s moduledoc: `sendid`/`sendidexpr` are
simultaneously representable so the validator can report the shape).
`<cancel>` has no slot for any child - unlike `build_send/2` it owns no
`place/3`/`reverse_lists/1` clause of its own, so any child it is given
falls through to `place/3`'s catch-all and comes back a
`{:misplaced_element, _, "cancel"}` error, the same way a `<log>` or
`<raise>` child does. `walk_children/2` and `place_children/3` are still
called, purely to surface that error - `<cancel>` reads no child result
of its own.

# `build_content`

```elixir
@spec build_content(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:content, Statifier.Document.Content.t()}, [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Content{}` from a `<content>` element,
tagged `{:content, content}`.

Reads `expr` and sets `text` to `Statifier.Parser.DOM.text/1`'s
concatenation of `<content>`'s own direct text children, **verbatim and
untrimmed except for the parser's XML 1.0 2.11 line-break fold**
(ADR-0045) - the struct's own moduledoc defines `text` as exactly that
concatenation. `<content>` is the one element exempt from the stray-text
rule (its text *is* its payload), so this builder reads `element.children`
directly rather than calling `Statifier.Lowering.walk_children/2`, which
would otherwise flag that same text as a stray-text error. An element
child is no longer misplaced (ADR-0041): `markup`/`markup_location` are
set from a raw slice of `ctx.source` when `<content>` has at least one
element child - unlike `text`, this slice does **not** fold line breaks;
it is opaque source bytes, CR included, at this layer (ADR-0045 decision
item 5) - and the child is never dispatched or walked, so no
`foreign_element` or `misplaced_element` error is possible here either.

# `build_data`

```elixir
@spec build_data(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:data, Statifier.Document.Data.t()} | nil, [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Data{}` from a `<data>` element, tagged
`{:data, data}`.

Reads `id` (required - spec 5.3.1), `expr`, and `src`, all raw strings, and
sets `text` to `Statifier.Parser.DOM.text/1`'s verbatim, untrimmed
concatenation of `<data>`'s direct text children (verbatim except for the
parser's XML 1.0 2.11 line-break fold - ADR-0045) - a `<data>`'s text *is*
its payload (spec 5.3.2), so this builder reads `element.children`
directly rather than calling `Statifier.Lowering.walk_children/2`, the
same stray-text exemption `<content>`'s own builder takes (ADR-0041). An
element child is not silently dropped: each one produces
`{:misplaced_element, name, "data"}` instead of a build attempt of its
own - `<data>` does not hold a markup subtree.

A missing `id` means no struct can be built at all (`:id` is
`@enforce_keys`'d on `Data`), following `build_raise/2`'s required-attribute
pattern: `{nil, [Error.missing_attribute("data", "id", element.location)]}`.

# `build_datamodel`

```elixir
@spec build_datamodel(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:datamodel, Statifier.Document.Datamodel.t()},
   [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Datamodel{}` from a `<datamodel>` element,
tagged `{:datamodel, datamodel}`.

`<datamodel>` has no attributes of its own (spec 5.2.1). Its only
children are `<data>` elements, placed into `Datamodel.data` in document
order via `place/3` and `reverse_lists/1`, the same fold every other
list-valued builder uses. `<datamodel>` is legal at both the document root
and on a `:state`/`:parallel` state (`place/3` handles both), and lowering
does not reject a `<datamodel>` on a `:final` or `:history` state either -
that placement rule belongs to `Statifier.Validator.Checks.Data`, since one
`%State{}` struct covers all four kinds and lowering has no way to
distinguish them here.

# `build_donedata`

```elixir
@spec build_donedata(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:donedata, Statifier.Document.Donedata.t()}, [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Donedata{}` from a `<donedata>` element,
tagged `{:donedata, donedata}`.

`content` stays `nil` when `<donedata>` has no `<content>` child and
becomes a `%Statifier.Document.Content{}` when it does. `params` holds
however many `<param>` children are present, in document order - both
slots are built here regardless of spec 5.5's content-model rule, which is
`Statifier.Validator.Checks.Donedata`'s to report
(`Statifier.Document.Donedata`'s moduledoc).

# `build_else`

```elixir
@spec build_else(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:else, Statifier.Document.If.Branch.t()}, [Statifier.Lowering.Error.t()]}
```

Builds the partitioning tag for an `<else>` element (spec 4.5), tagged
`{:else, branch}`. Same placement rule as `build_elseif/2`'s doc describes
- `place/3` accepts this tag only inside an `%If{}`.

`<else>` takes no attributes at all (4.5.2 gives it none): any attribute
written is reported as `{:unexpected_attribute, "else", name}` rather than
silently ignored, though the branch (with `cond: nil`) still builds either
way - an author who mistyped `cond` on an `<else>` should see both "this
attribute is not allowed" and get the `<else>` behavior 4.5.1 defines
regardless. Takes no element children either, the same exemption
`build_elseif/2` takes.

# `build_elseif`

```elixir
@spec build_elseif(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:elseif, Statifier.Document.If.Branch.t()} | nil,
   [Statifier.Lowering.Error.t()]}
```

Builds the partitioning tag for an `<elseif>` element (spec 4.4), tagged
`{:elseif, branch}` for `build_if/2`'s own fold to consume.
`place/3` has no clause accepting this tag for any parent but `%If{}`
(Decision 7 of the plan cited on `Statifier.Document.If`) - anywhere else,
the generic catch-all reports `{:misplaced_element, "elseif", parent_name}`
with no code of its own, reading the branch's own `location` field the
same way it reads every other tagged struct's.

Reads the required `cond` (4.4.1) - following `build_raise/2`'s
required-attribute pattern: a missing `cond` means no branch can be built
at all. `<elseif>` takes no children of its own (4.4.1 gives it none, the
same "empty element" shape `<else>` has); an element child is reported as
`{:misplaced_element, name, "elseif"}` rather than built, the same
exemption `build_data/2` takes for its own childless/text-only content
model.

# `build_final`

```elixir
@spec build_final(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:state, Statifier.Document.State.t()}, [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.State{}` with `kind: :final` from `<final>`.

# `build_finalize`

```elixir
@spec build_finalize(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:finalize, Statifier.Document.Block.t()}, [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Block{}` from a `<finalize>` element (spec
6.5), tagged `{:finalize, block}`.

Mirrors `build_onentry/2`/`build_onexit/2`'s shared `build_block/3` - one
`Block` per `<finalize>`, carrying its own `location` - but is not routed
through that helper since its tag is `:finalize`, not `:onentry`/`:onexit`.
An `<invoke>` with no `<finalize>` child leaves `Invoke.finalize` at its
struct default of `nil`; a written but childless `<finalize/>` still
builds a `%Block{content: []}` here, which is how `Invoke.finalize`
distinguishes absent from empty (6.5, `Statifier.Document.Invoke`'s
moduledoc).

# `build_foreach`

```elixir
@spec build_foreach(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:content_node, Statifier.Document.Foreach.t()} | nil,
   [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Foreach{}` from a `<foreach>` element (spec
4.6), tagged `{:content_node, foreach_node}`.

Reads the two required attributes, `array` and `item` (4.6.2), following
`build_raise/2`'s required-attribute shape - a missing one means no
struct can be built at all. Unlike `build_raise/2`, **both** are checked
before failing: a `<foreach>` missing both reports two
`{:missing_attribute, "foreach", _}` errors rather than stopping at the
first, since neither attribute's presence depends on the other and two
errors is strictly more useful to a document author fixing both at once.
`item`'s *legality* as a variable name is a runtime check, not
lowering's concern - only its presence is checked here.

The optional `index` (4.6.2) is read unconditionally and defaults to
`nil` when absent, following `Statifier.Document.Foreach`'s own default.

Otherwise walks children through the shared dispatch, then places each
tagged result into `content` via `place/3`'s `%Foreach{}` clause - no
partitioning tags are produced or consumed here, unlike `<if>`, since
`<foreach>` has exactly one child block (Decision 8).

# `build_history`

```elixir
@spec build_history(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:state, Statifier.Document.State.t()}, [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.State{}` with `kind: :history` from
`<history>`.

Additionally reads `type`, mapped to `:shallow | :deep` with `:shallow` as
the default (spec 3.10) - an out-of-range value (`type="sideways"`) lowers
to `:shallow` and still keeps its `attribute_locations` entry, rather than
erroring, so a future validator check can point at the offending text.

# `build_if`

```elixir
@spec build_if(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:content_node, Statifier.Document.If.t()} | nil,
   [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.If{}` from an `<if>` element (spec 4.3),
tagged `{:content_node, if_node}`.

Reads the required `cond` (4.3.1: `cond` is `required="true"` on `<if>`) -
following `build_raise/2`'s required-attribute pattern: a missing `cond`
means no struct can be built at all,
`{nil, [Error.missing_attribute("if", "cond", element.location)]}`.

Otherwise walks children through the shared dispatch, then folds the
tagged results through `place/3`'s `%If{}`-specific clauses - spec
4.3.2's partition definition, mechanically: one open branch starts,
carrying `<if>`'s own `cond` and this element's own `location`; each
`{:elseif, branch}` / `{:else, branch}` result closes the currently open
branch and opens the next; each `{:content_node, node}` result appends to
whichever branch is currently open. `reverse_lists/1`'s `%If{}` clause
restores document order once the fold finishes (branches were built
newest-first, the same convention every other list-valued builder here
follows).

# `build_initial`

```elixir
@spec build_initial(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:initial, Statifier.Document.Initial.t()}, [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Initial{}` from `<initial>`.

`transitions` holds however many `<transition>` children are present,
including zero and two - lowering builds what is written; the transition
count is the validator's check to make (`Statifier.Document.Initial`'s
moduledoc).

# `build_invoke`

```elixir
@spec build_invoke(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:invoke, Statifier.Document.Invoke.t()}, [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Invoke{}` from an `<invoke>` element (spec
6.4), tagged `{:invoke, invoke}`.

Reads all eight 6.4.1 attributes with `Attributes.value/2` (`namelist`
with `Attributes.list/2`, `autoforward` with `Attributes.atom/4` against
`"true"`/`"false"`, defaulting `false` per 6.4.1's own stated default) and
records every one's span in `attribute_locations`. Like `<param>`'s
deliberate expr/location pair, every mutually exclusive attribute pairing
6.4.1 names (`type`/`typeexpr`, `src`/`srcexpr`, `id`/`idlocation`,
`namelist`/`<param>`, and `src`|`srcexpr`/`<content>`) is lowered rather
than refused - `Statifier.Validator.Checks.Invoke` reports the shape.

`<param>` children accumulate into `params`, a `<content>` child sets
`content`, and a `<finalize>` child sets `finalize` - all three via
`place_children/3`'s own `%Invoke{}` clauses below.

# `build_log`

```elixir
@spec build_log(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:content_node, Statifier.Document.Log.t()}, [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Log{}` from a `<log>` element, tagged
`{:content_node, log}`.

Reads `label` and `expr`, both nilable, both raw strings - neither is
tokenized or compiled here.

# `build_onentry`

```elixir
@spec build_onentry(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:onentry, Statifier.Document.Block.t()}, [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Block{}` from an `<onentry>` element, tagged
`{:onentry, block}`.

Each `<onentry>` element becomes **one** `Block` with its own `location` -
three `<onentry>` elements under one `<state>` become three entries in
`State.onentry`, never one flattened list (spec 3.8/3.9, section 4's
error-isolation rule).

# `build_onexit`

```elixir
@spec build_onexit(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:onexit, Statifier.Document.Block.t()}, [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Block{}` from an `<onexit>` element, tagged
`{:onexit, block}`. See `build_onentry/2` - same shared `build_block/3`,
same one-block-per-element rule.

# `build_parallel`

```elixir
@spec build_parallel(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:state, Statifier.Document.State.t()}, [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.State{}` with `kind: :parallel` from
`<parallel>`.

# `build_param`

```elixir
@spec build_param(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:param, Statifier.Document.Param.t()} | nil, [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Param{}` from a `<param>` element, tagged
`{:param, param}`.

Reads `name` (required - spec 5.7.1), `expr`, and `location`. `expr` and
`location` are both read as raw nilable strings - neither is tokenized or
compiled here (`Statifier.Document.Param`'s moduledoc forbids `Predicator`
under `lib/statifier/document/`, the same rule every other Document node
follows). A missing `name` means no struct can be built at all, following
`build_raise/2`'s required-attribute pattern:
`{nil, [Error.missing_attribute("param", "name", element.location)]}`.

# `build_raise`

```elixir
@spec build_raise(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:content_node, Statifier.Document.Raise.t()} | nil,
   [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Raise{}` from a `<raise>` element, tagged
`{:content_node, raise}`.

`event` is read as a **single unsplit string**
(`Statifier.Document.Raise`'s moduledoc) - deliberately not tokenized the
way `<transition>`'s `event` is. `event` is required (`:event` is
`@enforce_keys`'d on `Raise`); when absent, no struct can be built and this
returns `{nil, [%Error{reason: {:missing_attribute, "raise", "event"}}]}`.

# `build_script`

```elixir
@spec build_script(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:content_node, Statifier.Document.Script.t()} | nil,
   [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Script{}` from a `<script>` element (spec
5.8), tagged `{:content_node, script}`.

On a written `src`, no struct is built at all: this reports
`{:unsupported_attribute, "script", "src"}` (Decision 5, ADR-0026 decision
2 - no external fetch) and stops there, following `build_raise/2`'s
"cannot build" pattern. `src` plus child text is deliberately **not** a
second error - spec 5.8.2 forbids the pair, but the document is already
rejected on `src` alone, so a second error would add noise without adding
information.

Otherwise `text` is set to `Statifier.Parser.DOM.text/1`'s verbatim,
untrimmed concatenation of `<script>`'s direct text children (verbatim
except for the parser's XML 1.0 2.11 line-break fold - ADR-0045) - the
predicator program body (5.8.2). This builder reads `element.children`
directly rather than calling `Statifier.Lowering.walk_children/2`, the
same stray-text exemption `build_data/2`/`build_assign/2` take for their
own text-only content models. An element child is not silently dropped:
each one produces `{:misplaced_element, name, "script"}` instead of a
build attempt of its own - `<script>` does not hold a markup subtree.

# `build_scxml`

```elixir
@spec build_scxml(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {Statifier.Document.t(), [Statifier.Lowering.Error.t()]}
```

Builds the `%Statifier.Document{}` root from `<scxml>`.

Reads `initial` (whitespace-split), `name`, `datamodel`, `binding` (atom,
default `:early`), `version`, and `xmlns`. `<state>`, `<parallel>`, and
`<final>` children are placed into `Document.states`, in document order;
any other child misses `Document`'s one slot and is reported via `place/3`
as `{:misplaced_element, name, "scxml"}`.

# `build_send`

```elixir
@spec build_send(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:content_node, Statifier.Document.Send.t()}, [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Send{}` from a `<send>` element (spec 6.2),
tagged `{:content_node, send}` - unlike `<invoke>`, `<send>` is executable
content, so it lands in a block's `content` list through the existing
`{:content_node, _}` placement clauses rather than a dedicated state-level
slot.

Reads all eleven of 6.2.1's own attributes, each raw and nilable
(`Statifier.Document.Send`'s moduledoc: mutually exclusive pairs are
simultaneously representable so the validator can report the shape).
`<param>` and `<content>` children are placed onto `Send.params`/`.content`
by `place/3`'s own `%Send{}` clauses, mirroring `%Invoke{}`'s.

# `build_state`

```elixir
@spec build_state(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:state, Statifier.Document.State.t()}, [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.State{}` with `kind: :state` from `<state>`.

# `build_transition`

```elixir
@spec build_transition(element :: Statifier.Parser.DOM.Element.t(), ctx :: map()) ::
  {{:transition, Statifier.Document.Transition.t()},
   [Statifier.Lowering.Error.t()]}
```

Builds a `%Statifier.Document.Transition{}` from `<transition>`.

Reads `event` and `target` (both whitespace-split), `cond` (raw source
string), and `type` (atom, default `:external`). An out-of-range `type`
value lowers to `:external` and still keeps its `attribute_locations`
entry, so a future validator check can point at the offending text, the
same rule `<history>`'s `type` follows.

A `<transition>`'s executable content children (`<raise>`, `<log>`) are
placed directly into `Transition.content`, unwrapped - a transition has no
`<onentry>`-like element in the source to give a block its own location
(`Statifier.Document.Block`'s moduledoc).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
