Skip to content

Origin System

Fresh

The 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, with prevSpanId/nextSpanId links
  • 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.