PureXML/Sources/Model/TreeNodeSpan.swift
Mihaela Mihaljevic 4659ea33c8 feat(editor): source spans on the tree, mapping findings to positions
Slice 1 of #94 (editor integration). The keystone: a located validation finding
can now be mapped back to a source range.

- TreeNode gains a sourceRange (start/end Mark); SourceRange is a half-open span.
- PureXML.readTree builds a mutable, parent-aware, best-effort tree whose every
  node carries its span (an element from the < of its start tag to past its end
  tag; leaves over the text they consumed). Never throws, recovers in place,
  returns located diagnostics.
- TreeNode.node(at:) navigates a validation coding path to its node using the
  same canonical convention the validators build paths with (a sibling index
  only when a name repeats), and sourceRange(at:) returns the span. So
  Document.validate's ValidationError.codingPath resolves to characters in the
  document.

Tests prove the spans, the navigation, the end-to-end bridge (an XSD finding to
a source range), and that ranged reading of invalid input never crashes.
2026-06-08 13:14:29 +02:00

25 lines
1.3 KiB
Swift

public extension PureXML.Model.TreeNode {
/// Resolves a validation coding path to the node it addresses, navigating from
/// this node (typically the document) down by element name, using a sibling
/// index only when a name repeats. The same convention the validators build
/// paths with, so a ``PureXML/Validation/ValidationError``'s `codingPath` maps
/// to a node here, and the node's `sourceRange` gives the span to highlight.
/// An attribute step (`@name`) resolves to its owning element.
func node(at path: [PureXML.Validation.PathKey]) -> PureXML.Model.TreeNode? {
var current: PureXML.Model.TreeNode? = self
for key in path {
guard let node = current else { return nil }
if key.stringValue.hasPrefix("@") { return node }
let matches = node.children.filter { $0.kind == .element && $0.name?.description == key.stringValue }
let index = (key.intValue ?? 1) - 1
current = matches.indices.contains(index) ? matches[index] : nil
}
return current
}
/// The source span of the node a validation coding path addresses, when known.
func sourceRange(at path: [PureXML.Validation.PathKey]) -> PureXML.Parsing.SourceRange? {
node(at: path)?.sourceRange
}
}