Statifier.Parser.Location (Statifier v2.0.0)

Copy Markdown View Source

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

t()

@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

at_offset(source, offset)

@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(value_location, value, 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.

normalize_character_data(location, value, source)

@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 
, 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(value_location, span, value, source)

@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 (&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(location, source)

@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.