mdld-parse 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +54 -11
  2. package/index.js +60 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -14,8 +14,11 @@ MD-LD allows you to author RDF graphs directly in Markdown using explicit `{...}
14
14
  # Apollo 11 {=ex:apollo11 .SpaceMission}
15
15
 
16
16
  Launch: [1969-07-16] {startDate ^^xsd:date}
17
- Crew: [Neil Armstrong](ex:armstrong) {?crewMember}
17
+ Crew: [Neil Armstrong] {=?ex:armstrong ?crewMember fullName}
18
18
  Description: [First crewed Moon landing] {description}
19
+
20
+ [Section] {=?#overview ?hasPart}
21
+ Overview: [Mission summary] {description}
19
22
  ```
20
23
 
21
24
  Generates valid RDF triples:
@@ -25,18 +28,20 @@ ex:apollo11 a schema:SpaceMission ;
25
28
  schema:startDate "1969-07-16"^^xsd:date ;
26
29
  schema:crewMember ex:armstrong ;
27
30
  schema:description "First crewed Moon landing" .
28
- ```
29
31
 
30
- ## Core Guarantees
32
+ ex:armstrong schema:fullName "Neil Armstrong" .
33
+ ```
31
34
 
32
- MD-LD v0.2 provides strict semantic guarantees:
35
+ ## Core Features
33
36
 
34
- 1. **CommonMark-preserving** Removing `{...}` yields valid Markdown
35
- 2. **Explicit semantics** Every quad originates from explicit `{...}`
36
- 3. **Single-pass parsing** Streaming-friendly, deterministic
37
- 4. **No blank nodes** All subjects are stable IRIs
38
- 5. **Complete traceability** Every quad maps to source location
39
- 6. **Round-trip capable** Markdown RDF Markdown preserves structure
37
+ - **Subject declarations**: `{=IRI}` and `{=#fragment}` for context setting
38
+ - **Object IRIs**: `{=?IRI}` and `{=?#fragment}` for temporary object declarations
39
+ - **Four predicate forms**: `p` (S→L), `?p` (S→O), `^p` (L→S), `^?p` (O→S)
40
+ - **Type declarations**: `.Class` for rdf:type triples
41
+ - **Datatypes & language**: `^^xsd:date` and `@en` support
42
+ - **Lists**: Explicit subject declarations for structured data
43
+ - **Fragments**: Built-in document structuring with `{=#fragment}`
44
+ - **Round-trip serialization**: Markdown ↔ RDF ↔ Markdown preserves structure
40
45
 
41
46
  ## Installation
42
47
 
@@ -246,6 +251,7 @@ ex:book schema:hasPart ex:part .
246
251
  ```markdown
247
252
  [ex] {: http://example.org/}
248
253
  [foaf] {: http://xmlns.com/foaf/0.1/}
254
+ [@vocab] {: http://schema.org/}
249
255
 
250
256
  # Person {=ex:alice .foaf:Person}
251
257
  ```
@@ -385,6 +391,41 @@ MD-LD explicitly forbids to ensure deterministic parsing:
385
391
  - ❌ Predicate guessing from context
386
392
  - ❌ Multi-pass or backtracking parsers
387
393
 
394
+ Below is a **tight, README-ready refinement** of the Algebra section.
395
+ It keeps the math precise, examples exhaustive, and language compact.
396
+
397
+ ---
398
+
399
+ ## Algebra
400
+
401
+ > Every RDF triple `(s, p, o)` can be authored **explicitly, deterministically, and locally**, with no inference, guessing, or reordering.
402
+
403
+ MD-LD models RDF authoring as a **closed edge algebra** over a small, explicit state. To be algebraically complete for RDF triple construction, a syntax must support:
404
+
405
+ * Binding a **subject** `S`
406
+ * Binding an **object** `O`
407
+ * Emitting predicates in **both directions**
408
+ * Distinguishing **IRI nodes** from **literal nodes**
409
+ * Operating with **no implicit state or inference**
410
+
411
+ MD-LD satisfies these requirements with four explicit operators.
412
+
413
+ Each predicate is partitioned by **direction** and **node kind**:
414
+
415
+ | Predicate form | Emitted triple |
416
+ | -------------- | -------------- |
417
+ | `p` | `S ─p→ L` |
418
+ | `?p` | `S ─p→ O` |
419
+ | `^p` | `L ─p→ S` |
420
+ | `^?p` | `O ─p→ S` |
421
+
422
+ This spans all **2 × 2** combinations of:
423
+
424
+ * source ∈ {subject, object/literal}
425
+ * target ∈ {subject, object/literal}
426
+
427
+ Therefore, the algebra is **closed**.
428
+
388
429
  ## Use Cases
389
430
 
390
431
  ### Personal Knowledge Management
@@ -456,8 +497,10 @@ Contributions welcome! Please:
456
497
 
457
498
  ## Acknowledgments
458
499
 
500
+ Developed by [Denis Starov](https://github.com/davay42).
501
+
459
502
  Inspired by:
460
- - Thomas Francart's [Semantic Markdown](https://blog.sparna.fr/2020/02/20/semantic-markdown/)
503
+ - Thomas Francart's [Semantic Markdown](https://blog.sparna.fr/2020/02/20/semantic-markdown/) article
461
504
  - RDFa decades of structured data experience
462
505
  - CommonMark's rigorous parsing approach
463
506
 
package/index.js CHANGED
@@ -76,6 +76,13 @@ export function parseSemanticBlock(raw) {
76
76
  continue;
77
77
  }
78
78
 
79
+ if (token.startsWith('=?#')) {
80
+ const fragment = token.substring(3);
81
+ result.object = `#${fragment}`;
82
+ result.entries.push({ kind: 'softFragment', fragment, relRange: { start: relStart, end: relEnd }, raw: token });
83
+ continue;
84
+ }
85
+
79
86
  if (token.startsWith('=?')) {
80
87
  const iri = token.substring(2);
81
88
  result.object = iri;
@@ -500,7 +507,17 @@ function processAnnotation(carrier, sem, state) {
500
507
 
501
508
  if (sem.object) {
502
509
  // Handle soft IRI object declaration - local to this annotation only
503
- localObject = state.df.namedNode(expandIRI(sem.object, state.ctx));
510
+ if (sem.object.startsWith('#')) {
511
+ // Soft fragment - resolve against current subject base
512
+ const fragment = sem.object.substring(1);
513
+ if (state.currentSubject) {
514
+ const baseIRI = state.currentSubject.value.split('#')[0];
515
+ localObject = state.df.namedNode(`${baseIRI}#${fragment}`);
516
+ }
517
+ } else {
518
+ // Regular soft IRI
519
+ localObject = state.df.namedNode(expandIRI(sem.object, state.ctx));
520
+ }
504
521
  }
505
522
 
506
523
  if (newSubject) state.currentSubject = newSubject;
@@ -753,6 +770,16 @@ function removeObjectToken(tokens, iri) {
753
770
  return removeOneToken(tokens, t => t === objectToken);
754
771
  }
755
772
 
773
+ function addSoftFragmentToken(tokens, fragment) {
774
+ const fragmentToken = `=?#${fragment}`;
775
+ return tokens.includes(fragmentToken) ? tokens : [...tokens, fragmentToken];
776
+ }
777
+
778
+ function removeSoftFragmentToken(tokens, fragment) {
779
+ const fragmentToken = `=?#${fragment}`;
780
+ return removeOneToken(tokens, t => t === fragmentToken);
781
+ }
782
+
756
783
  function sanitizeCarrierValueForBlock(block, raw) {
757
784
  const s = String(raw ?? '');
758
785
  const t = block?.carrierType;
@@ -1057,6 +1084,17 @@ export function serialize({ text, diff, origin, options = {} }) {
1057
1084
  return;
1058
1085
  }
1059
1086
 
1087
+ // Handle soft fragment token removal
1088
+ if (entry?.kind === 'softFragment') {
1089
+ const fragment = entry.fragment;
1090
+ const { tokens: updated, removed } = removeSoftFragmentToken(tokens, fragment);
1091
+ if (!removed) return;
1092
+
1093
+ const newAttrs = updated.length === 0 ? '{}' : writeAttrsTokens(updated);
1094
+ edits.push({ start: span.start, end: span.end, text: newAttrs });
1095
+ return;
1096
+ }
1097
+
1060
1098
  const tokens = normalizeAttrsTokens(span.text);
1061
1099
  let updated = tokens;
1062
1100
  let removed = false;
@@ -1151,20 +1189,31 @@ export function serialize({ text, diff, origin, options = {} }) {
1151
1189
  const objectShort = shortenIRI(full, ctx);
1152
1190
  const predShort = shortenIRI(quad.predicate.value, ctx);
1153
1191
 
1154
- // Check if this is a ?predicate form (should use object IRI)
1155
- const span = readSpan(targetBlock, text, 'attrs');
1156
- const tokens = blockTokensFromEntries(targetBlock) || normalizeAttrsTokens(span.text);
1157
- const hasObjectToken = tokens.some(t => t.startsWith('=?'));
1192
+ // Check if this is a soft fragment
1193
+ const isSoftFragment = full.includes('#') && anchored?.entry?.kind === 'softFragment';
1158
1194
 
1159
- if (hasObjectToken || anchored?.entry?.form === '?') {
1160
- // Add object token if not present
1161
- const updated = addObjectToken(tokens, objectShort);
1162
- if (updated.length !== tokens.length) {
1163
- edits.push({ start: span.start, end: span.end, text: writeAttrsTokens(updated) });
1195
+ if (isSoftFragment || anchored?.entry?.form === '?') {
1196
+ // Add soft fragment token if not present
1197
+ if (isSoftFragment) {
1198
+ const fragment = full.split('#')[1];
1199
+ const updated = addSoftFragmentToken(tokens, fragment);
1200
+ if (updated.length !== tokens.length) {
1201
+ edits.push({ start: span.start, end: span.end, text: writeAttrsTokens(updated) });
1202
+ }
1203
+ } else {
1204
+ const updated = addObjectToken(tokens, objectShort);
1205
+ if (updated.length !== tokens.length) {
1206
+ edits.push({ start: span.start, end: span.end, text: writeAttrsTokens(updated) });
1207
+ }
1164
1208
  }
1165
1209
  } else {
1166
1210
  // Create new annotation with object token
1167
- edits.push({ start: result.length, end: result.length, text: `\n[${objectShort}] {=?${objectShort} ?${predShort}}` });
1211
+ if (isSoftFragment) {
1212
+ const fragment = full.split('#')[1];
1213
+ edits.push({ start: result.length, end: result.length, text: `\n[${objectShort}] {=?#${fragment} ?${predShort}}` });
1214
+ } else {
1215
+ edits.push({ start: result.length, end: result.length, text: `\n[${objectShort}] {=?${objectShort} ?${predShort}}` });
1216
+ }
1168
1217
  }
1169
1218
  return;
1170
1219
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdld-parse",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "A standards-compliant parser for **MD-LD (Markdown-Linked Data)** — a human-friendly RDF authoring format that extends Markdown with semantic annotations.",
5
5
  "type": "module",
6
6
  "main": "index.js",