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
(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.
Summary
Functions
A zero-width span at offset, counting newlines and codepoints over
binary_part(source, 0, offset).
value with XML 1.0 3.3.3 attribute-value normalization applied.
value with XML 1.0 2.11 line-break folding applied, guided by the raw
source.
The absolute document span of the subexpression span covers.
The raw source bytes location spans, sliced out of source.
Types
@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() }
Functions
@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).
@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 	/ / , 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.
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 
, 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 (& 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.
@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
(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 (<, ) 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.
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.