Appearance
Origin System
FreshThe origin system provides complete provenance tracking — every quad traces back to its source text. This enables UI navigation, source attribution, and round-trip document regeneration without any overhead in the document itself.
What Origin Tracking Provides
After parsing, result.origin contains:
javascript
{
quadIndex: Map, // quad key → origin entry
blocks: Map, // block id → block object (semantic anchors)
spans: Map, // span id → span object (textual topology)
documentStructure: [] // ordered list of block and span ids
}This dual structure captures both the semantic graph and the textual topology of the document.
Blocks and Spans
The parser constructs a complete document chain as a side effect of parsing:
[Block] --(Span)-- [Block] --(Span)-- [Block]- Blocks (
origin.blocks) — semantic anchors: tokens that produced RDF quads, withprevSpanId/nextSpanIdlinks - Spans (
origin.spans) — textual observations: raw byte ranges between blocks, with bidirectional block and span links
Spans store no text themselves — content is always recovered via sourceText.slice(span.range[0], span.range[1]). This keeps the origin object lean while still enabling any text-range operation.
Origin Entry Structure
Each quad has an entry in quadIndex:
javascript
const originEntry = {
blockId: 'block-identifier', // Unique block identifier
range: [start, end], // Character range in source text
carrierType: 'heading', // 'heading' | 'blockquote' | 'para' | 'span' | 'list'
subject: 'http://example.org/alice', // Subject IRI
predicate: 'http://example.org/name', // Predicate IRI
context: { my: 'tag:...', ...}, // Prefix context at this point
polarity: '+', // '+' for assertions, '-' for retractions
value: 'Alice' // Raw carrier text content
};Using the locate() Function
The locate() function provides a simple interface for finding where a quad came from:
javascript
import { parse, locate } from 'mdld-parse';
const result = parse({
text: `[ex] <http://example.org/>
# Alice {=ex:alice .prov:Person label}
[alice@example.com] {ex:email}`,
context: { ex: 'http://example.org/' }
});
const emailQuad = result.quads.find(q =>
q.predicate.value === 'http://example.org/email'
);
const location = locate(emailQuad, result.origin);
console.log(location.range); // [start, end] character positions
console.log(location.value); // "alice@example.com"
console.log(location.carrierType); // "para" or "span"
console.log(location.polarity); // "+"Use Cases for Origin Tracking
UI Navigation
When a user clicks on a fact in a knowledge graph interface, use locate() to scroll the editor to the exact character range where that fact was authored.
javascript
function navigateToQuad(quad, origin, editorInstance) {
const location = locate(quad, origin);
if (location) {
editorInstance.setCursor(location.range[0]);
editorInstance.scrollIntoView(location.range);
}
}Source Attribution
Display where each fact in a knowledge graph came from:
javascript
const quadsWithOrigins = result.quads.map(quad => {
const location = locate(quad, result.origin);
return {
quad,
source: location ? result.text.slice(location.range[0], location.range[1]) : null,
line: location ? result.text.slice(0, location.range[0]).split('\n').length : null
};
});Context Recovery
The span chain enables neighborhood retrieval — find the text surrounding a semantic block without parsing the whole document again:
javascript
function getContext(blockId, origin, sourceText) {
const block = origin.blocks.get(blockId);
if (!block) return null;
const prevSpan = block.prevSpanId ? origin.spans.get(block.prevSpanId) : null;
const nextSpan = block.nextSpanId ? origin.spans.get(block.nextSpanId) : null;
return {
before: prevSpan ? sourceText.slice(prevSpan.range[0], prevSpan.range[1]) : '',
block: sourceText.slice(block.range[0], block.range[1]),
after: nextSpan ? sourceText.slice(nextSpan.range[0], nextSpan.range[1]) : ''
};
}Round-Trip Verification
Use origin data to verify that the generated document matches the source:
javascript
const result = parse({ text: original });
const { text: regenerated } = generate({
quads: result.quads,
context: result.context
});
const result2 = parse({ text: regenerated });
// Verify same quad count
console.log(result.quads.length === result2.quads.length);updateValue()
Use the updateValue() function to modify a literal value in the source text, using the origin to find exactly where to make the edit:
javascript
import { parse, updateValue } from 'mdld-parse';
const result = parse({ text: original });
const emailQuad = result.quads.find(q =>
q.predicate.value.endsWith('email')
);
const updatedText = updateValue({
text: original,
quad: emailQuad,
value: 'newemail@example.com',
origin: result.origin
});This enables editor applications to modify semantic values without re-parsing the whole document. The function is fail-safe — it returns the original text unchanged if the quad cannot be located.
Lean Design
The origin system is designed to add minimal overhead to parsing:
- No text copying — spans store only byte ranges, not text content
- Lazy context resolution — contexts are only expanded when needed
- Single-pass construction — blocks and spans are built during parsing, not in a second pass
- Flat structure — quadIndex is a simple Map, not a nested tree
The result is that origin tracking adds virtually no parsing overhead while enabling rich UI and tooling features.