# `Statifier.Parser.Location`
[🔗](https://github.com/riddler/statifier-ex/blob/v2.0.0/lib/statifier/parser/location.ex#L1)

A source span: 1-based line/column (columns counted in Unicode codepoints),
0-based byte offsets, exclusive end - the shape ADR-0014 fixed for
expression spans, used here for XML nodes so the two compose.

Keeping both line/column and byte offsets is what lets an ADR-0014
expression span be resolved into an absolute document span: the line/column
half is the coordinate system a predicator span speaks
(`t:Predicator.Types.span/0` is a pair of 1-based `{line, column}`
positions - there is no offset in it, so no arithmetic on `start_offset`
stands in for the composition), and the offset half is what makes the
result sliceable back out of the source. `resolve_span/4` does the
composition, accounting for entity references in the raw text.

# `t`

```elixir
@type t() :: %Statifier.Parser.Location{
  end_column: pos_integer(),
  end_line: pos_integer(),
  end_offset: non_neg_integer(),
  start_column: pos_integer(),
  start_line: pos_integer(),
  start_offset: non_neg_integer()
}
```

# `at_offset`

```elixir
@spec at_offset(source :: binary(), offset :: non_neg_integer()) :: t()
```

A zero-width span at `offset`, counting newlines and codepoints over
`binary_part(source, 0, offset)`.

Used to convert a `Saxy.ParseError` byte offset into a location; `offset`
must not exceed `byte_size(source)`.

# `normalize_attribute_value`

```elixir
@spec normalize_attribute_value(
  value_location :: t(),
  value :: binary(),
  source :: binary()
) ::
  binary()
```

`value` with XML 1.0 3.3.3 attribute-value normalization applied.

`value_location` is the raw-source span of the attribute's value and `value`
is Saxy's entity-expanded string. Saxy applies neither 3.3.3 nor 2.11, so a
literal TAB/LF/CR survives into `value` verbatim - indistinguishable, in that
string alone, from an expanded `&#9;`/`&#10;`/`&#13;`, which 3.3.3 requires be
kept as-is. The raw slice is what draws the distinction, so the two are walked
in lockstep by the same unit rule `resolve_span/4` uses.

Literal `#x20`/`#x9`/`#xA`/`#xD` each append one space; a literal `\r\n`
pair appends one space between them (2.11 folds before 3.3.3 maps); a
reference appends its decoded character verbatim. CDATA treatment only -
statifier reads no attribute declarations, so nothing is trimmed or
collapsed (ADR-0043).

Degrades rather than raising: if the raw slice and `value` desync, `value` is
returned unnormalized.

# `normalize_character_data`

```elixir
@spec normalize_character_data(location :: t(), value :: binary(), source :: binary()) ::
  binary()
```

`value` with XML 1.0 2.11 line-break folding applied, guided by the raw
source.

`location` is the raw-source span of a character-data run (a text node's
whole run, coalesced across split events exactly as `Handler.add_text/2`
recomputes it) and `value` is Saxy's entity-expanded string for that run.
Saxy applies no 2.11 (same finding as `normalize_attribute_value/3`), so a
literal CRLF or lone CR survives into `value` verbatim - indistinguishable,
in that string alone, from a `\r` decoded from `&#xD;`, which 2.11 must
never fold. XML 1.0 2.11 (End-of-Line Handling), quoted from
https://www.w3.org/TR/xml/#sec-line-ends as recorded in ADR-0045 (no local
cache holds the XML 1.0 REC - `mise run spec:fetch` only populates the
SCXML REC and its Appendix D extract - so this quote is carried from the
ADR's own fetch rather than re-fetched here):

> To simplify the tasks of applications, the XML processor MUST behave as
> if it normalized all line breaks in external parsed entities (including
> the document entity) on input, before parsing, by translating both the
> two-character sequence #xD #xA and any #xD that is not followed by #xA
> to a single #xA character.

Unlike `normalize_attribute_value/3`, this applies no 3.3.3
whitespace-to-space mapping - that rule is attribute-specific, so a TAB and
a folded newline are both kept in character data (ADR-0045 item 1).

The raw run can straddle constructs the scanner elides from `value`
entirely - `<!--`...`-->`, `<?`...`?>`, and the `<![CDATA[`/`]]>` delimiters
(`Markup.scan/1`, ADR-0045 item 2) - which contribute nothing to the
expanded side and must not be mistaken for character data. A CDATA
section's *interior* is not one of those: it is real character data, walked
verbatim with no reference decoding (`&amp;` inside a CDATA section is five
literal characters on both sides, never `"&"`), so a literal CR inside it
folds exactly as one outside does.

Degrades rather than raising: if the raw slice and `value` desync, `value`
is returned unfolded, the same posture `normalize_attribute_value/3` and
`resolve_span/4` take.

# `resolve_span`

```elixir
@spec resolve_span(
  value_location :: t(),
  span :: Predicator.Types.span(),
  value :: binary(),
  source :: binary()
) :: t()
```

The absolute document span of the subexpression `span` covers.

`value_location` is the raw-source span of an attribute's value (the text
inside the quotes); `value` is the entity-expanded string that was handed to
predicator - `Machine.expr()`'s `{:compiled, _, source}` third element, or
`Statifier.Evaluator.Error`'s `:source` - and `span` is a predicator span
over `value`, 1-based line/column with an exclusive end
(`t:Predicator.Types.span/0`). The returned location's end is exclusive too.

`value` is required rather than reconstructed: predicator counted columns in
that exact string, and a reference in the raw source (`&lt;`, `&#10;`) makes
raw and expanded coordinates diverge. Walking the two together is what keeps
the result exact; re-deriving the expansion here would only model it. The raw
text is *not* required, because `slice/2` recovers it from `source`.

Requires `value`'s position `{1, 1}` to be `value_location`'s start - true of
every attribute-sourced expression, since
`Statifier.Compiler.Expressions.compile/3` does not trim. A caller that
trimmed before compiling must adjust the anchor itself.

Degrades rather than raising: a position past the end of `value` clamps to
`value_location`'s end, and a `value` that does not describe the same text as
the raw slice returns `value_location` whole - underlining the entire
attribute value instead of a subexpression.

A `nil` `value_location` or a `nil` span has nothing to resolve; the caller
falls back to the owning node's own `location` rather than calling this.

`value` is `Attribute.value`, which by the time this is called is already
XML 1.0 3.3.3-normalized (`normalize_attribute_value/3`, ADR-0043): a
literal TAB/LF/CR in the raw slice appears here as a space, a raw CRLF pair
as one space, and a character reference as its decoded character. This walk
pairs both against the raw slice exactly as the normalization walk does,
which is what keeps a normalized span resolving to the right raw text.

# `slice`

```elixir
@spec slice(location :: t(), source :: binary()) :: binary()
```

The raw source bytes `location` spans, sliced out of `source`.

The primitive the location-accuracy sweep is built on: slicing a node's
span back out of the source and asserting it starts with the expected text
catches an off-by-one anywhere in the producer, without hardcoding a line
number.

---

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