mdld-parse 0.2.10 → 0.3.1

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 (4) hide show
  1. package/LICENCE +1 -1
  2. package/README.md +10 -11
  3. package/index.js +57 -21
  4. package/package.json +1 -1
package/LICENCE CHANGED
@@ -1,6 +1,6 @@
1
1
  # MDLD / MD-LD Community License
2
2
 
3
- Version 0.2 — 2025
3
+ Version 0.3 — 2025
4
4
 
5
5
  Copyright © 2025–present
6
6
  **Denis Starov**
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # MD-LD Parse v0.2
1
+ # MD-LD Parse v0.3
2
2
 
3
3
  **Markdown-Linked Data (MD-LD)** — a deterministic, streaming-friendly RDF authoring format that extends Markdown with explicit `{...}` annotations.
4
4
 
@@ -14,10 +14,10 @@ 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 name}
17
+ Crew: [Neil Armstrong] {+ex:armstrong ?crewMember name}
18
18
  Description: [First crewed Moon landing] {description}
19
19
 
20
- [Section] {=?#overview ?hasPart}
20
+ [Section] {+#overview ?hasPart}
21
21
  Overview: [Mission summary] {description}
22
22
  ```
23
23
 
@@ -35,8 +35,8 @@ ex:armstrong schema:name "Neil Armstrong" .
35
35
  ## Core Features
36
36
 
37
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)
38
+ - **Object IRIs**: `{+IRI}` and `{+#fragment}` for temporary object declarations
39
+ - **Four predicate forms**: `p` (S→L), `?p` (S→O), `!p` (O→S)
40
40
  - **Type declarations**: `.Class` for rdf:type triples
41
41
  - **Datatypes & language**: `^^xsd:date` and `@en` support
42
42
  - **Lists**: Explicit subject declarations for structured data
@@ -92,8 +92,7 @@ Each predicate form determines the graph edge:
92
92
  |-------|---------|------------------------------|------------------|
93
93
  | `p` | S → L | `[Alice] {name}` | literal property |
94
94
  | `?p` | S → O | `[NASA](ex:nasa) {?org}` | object property |
95
- | `^p` | *(none)*| *(literals can't be subjects)* | reverse literal |
96
- | `^?p` | O → S | `[Parent](ex:p) {^?hasPart}` | reverse object |
95
+ | `!p` | O S | `[Parent](ex:p) {!hasPart}` | reverse object |
97
96
 
98
97
  ## Syntax Reference
99
98
 
@@ -237,7 +236,7 @@ Reverse the relationship direction:
237
236
  ```markdown
238
237
  # Part {=ex:part}
239
238
 
240
- Part of: {^?hasPart}
239
+ Part of: {!hasPart}
241
240
 
242
241
  - Book {=ex:book}
243
242
  ```
@@ -416,8 +415,8 @@ Each predicate is partitioned by **direction** and **node kind**:
416
415
  | -------------- | -------------- |
417
416
  | `p` | `S ─p→ L` |
418
417
  | `?p` | `S ─p→ O` |
419
- | `^p` | `L ─p→ S` |
420
- | `^?p` | `O ─p→ S` |
418
+ | not allowed | `L ─p→ S` |
419
+ | `!p` | `O ─p→ S` |
421
420
 
422
421
  This spans all **2 × 2** combinations of:
423
422
 
@@ -480,7 +479,7 @@ npm test
480
479
 
481
480
  Tests validate:
482
481
  - Subject declaration and context
483
- - All predicate forms (p, ?p, ^p, ^?p)
482
+ - All predicate forms (p, ?p, !p)
484
483
  - Datatypes and language tags
485
484
  - List processing
486
485
  - Code blocks and blockquotes
package/index.js CHANGED
@@ -75,15 +75,15 @@ export function parseSemanticBlock(raw) {
75
75
  continue;
76
76
  }
77
77
 
78
- if (token.startsWith('=?#')) {
79
- const fragment = token.substring(3);
78
+ if (token.startsWith('+#')) {
79
+ const fragment = token.substring(2);
80
80
  result.object = `#${fragment}`;
81
81
  result.entries.push({ kind: 'softFragment', fragment, relRange: { start: relStart, end: relEnd }, raw: token });
82
82
  continue;
83
83
  }
84
84
 
85
- if (token.startsWith('=?')) {
86
- const iri = token.substring(2);
85
+ if (token.startsWith('+')) {
86
+ const iri = token.substring(1);
87
87
  result.object = iri;
88
88
  result.entries.push({ kind: 'object', iri, relRange: { start: relStart, end: relEnd }, raw: token });
89
89
  continue;
@@ -118,10 +118,10 @@ export function parseSemanticBlock(raw) {
118
118
  continue;
119
119
  }
120
120
 
121
- if (token.startsWith('^?')) {
122
- const iri = token.substring(2);
123
- result.predicates.push({ iri, form: '^?', entryIndex });
124
- result.entries.push({ kind: 'property', iri, form: '^?', relRange: { start: relStart, end: relEnd }, raw: token });
121
+ if (token.startsWith('!')) {
122
+ const iri = token.substring(1);
123
+ result.predicates.push({ iri, form: '!', entryIndex });
124
+ result.entries.push({ kind: 'property', iri, form: '!', relRange: { start: relStart, end: relEnd }, raw: token });
125
125
  continue;
126
126
  }
127
127
 
@@ -224,7 +224,7 @@ function scanTokens(text) {
224
224
  continue;
225
225
  }
226
226
 
227
- const listMatch = line.match(/^(\s*)([-*+]|\d+\.)\s+(.+?)(?:\s*(\{[^}]+\}))?$/);
227
+ const listMatch = line.match(/^(\s*)([-*+]|\d+\.)\s+(.+?)(?:\s*(\{[^}]+\}))?\s*$/);
228
228
  if (listMatch) {
229
229
  const attrs = listMatch[4] || null;
230
230
  const attrsStartInLine = attrs ? line.lastIndexOf(attrs) : -1;
@@ -593,7 +593,7 @@ function processAnnotation(carrier, sem, state) {
593
593
  // L —p→ S (use soft IRI object as subject if available, otherwise current subject)
594
594
  const subjectIRI = localObject || S;
595
595
  emitQuad(state.quads, state.origin.quadIndex, block.id, L, P, subjectIRI, state.df, { kind: 'pred', token, form: pred.form, expandedPredicate: P.value, entryIndex: pred.entryIndex });
596
- } else if (pred.form === '^?') {
596
+ } else if (pred.form === '!') {
597
597
  // O —p→ S (use previous subject as object, newSubject as subject)
598
598
  const objectIRI = newSubject ? previousSubject : S;
599
599
  const subjectIRI = localObject || newSubject || carrierO;
@@ -661,9 +661,9 @@ function processListContext(contextSem, listTokens, state, contextSubject = null
661
661
  contextSem.predicates.forEach(pred => {
662
662
  const P = state.df.namedNode(expandIRI(pred.iri, state.ctx));
663
663
 
664
- // According to MD-LD spec: list predicates that connect to item subjects MUST use object predicate forms (?p or ^?p)
665
- // Literal predicate forms (p, ^p) in list scope emit no quads
666
- if (pred.form === '^?') {
664
+ // According to MD-LD spec: list predicates that connect to item subjects MUST use object predicate forms (?p or !p)
665
+ // Literal predicate forms (p) in list scope emit no quads
666
+ if (pred.form === '!') {
667
667
  // Reverse object property: O —p→ S
668
668
  emitQuad(state.quads, state.origin.quadIndex, 'list-context', itemSubject, P, contextSubject, state.df);
669
669
  } else if (pred.form === '?') {
@@ -676,8 +676,44 @@ function processListContext(contextSem, listTokens, state, contextSubject = null
676
676
  const prevSubject = state.currentSubject;
677
677
  state.currentSubject = itemSubject;
678
678
 
679
+ // Check if item has its own predicates
680
+ let hasOwnPredicates = false;
681
+ let itemSem = null;
682
+
679
683
  if (listToken.attrs) {
680
- const itemSem = parseSemanticBlock(listToken.attrs);
684
+ itemSem = parseSemanticBlock(listToken.attrs);
685
+ if (itemSem.predicates.length > 0) {
686
+ hasOwnPredicates = true;
687
+ }
688
+ }
689
+
690
+ if (!hasOwnPredicates) {
691
+ // Check inline carriers for predicates
692
+ for (const carrier of carriers) {
693
+ if (carrier.attrs) {
694
+ const carrierSem = parseSemanticBlock(carrier.attrs);
695
+ if (carrierSem.predicates.length > 0) {
696
+ hasOwnPredicates = true;
697
+ break;
698
+ }
699
+ }
700
+ }
701
+ }
702
+
703
+ // If item has no predicates, inherit literal predicates from context
704
+ if (!hasOwnPredicates) {
705
+ const inheritedPredicates = contextSem.predicates.filter(p => p.form === '');
706
+ if (inheritedPredicates.length > 0 && listToken.text) {
707
+ // Create inherited annotation block
708
+ const inheritedTokens = inheritedPredicates.map(p => p.iri).join(' ');
709
+ const inheritedSem = parseSemanticBlock(`{${inheritedTokens}}`);
710
+ const carrier = { type: 'list', text: listToken.text, range: listToken.range, attrsRange: listToken.attrsRange || null, valueRange: listToken.valueRange || null };
711
+ processAnnotation(carrier, inheritedSem, state);
712
+ }
713
+ }
714
+
715
+ if (listToken.attrs) {
716
+ if (!itemSem) itemSem = parseSemanticBlock(listToken.attrs);
681
717
  const carrier = { type: 'list', text: listToken.text, range: listToken.range, attrsRange: listToken.attrsRange || null, valueRange: listToken.valueRange || null };
682
718
  processAnnotation(carrier, itemSem, state);
683
719
  }
@@ -797,22 +833,22 @@ function removeOneToken(tokens, matchFn) {
797
833
  }
798
834
 
799
835
  function addObjectToken(tokens, iri) {
800
- const objectToken = `=?${iri}`;
836
+ const objectToken = `+${iri}`;
801
837
  return tokens.includes(objectToken) ? tokens : [...tokens, objectToken];
802
838
  }
803
839
 
804
840
  function removeObjectToken(tokens, iri) {
805
- const objectToken = `=?${iri}`;
841
+ const objectToken = `+${iri}`;
806
842
  return removeOneToken(tokens, t => t === objectToken);
807
843
  }
808
844
 
809
845
  function addSoftFragmentToken(tokens, fragment) {
810
- const fragmentToken = `=?#${fragment}`;
846
+ const fragmentToken = `+#${fragment}`;
811
847
  return tokens.includes(fragmentToken) ? tokens : [...tokens, fragmentToken];
812
848
  }
813
849
 
814
850
  function removeSoftFragmentToken(tokens, fragment) {
815
- const fragmentToken = `=?#${fragment}`;
851
+ const fragmentToken = `+#${fragment}`;
816
852
  return removeOneToken(tokens, t => t === fragmentToken);
817
853
  }
818
854
 
@@ -1203,7 +1239,7 @@ export function serialize({ text, diff, origin, options = {} }) {
1203
1239
  const full = quad.object.value;
1204
1240
  const label = shortenIRI(full, ctx);
1205
1241
  const objectShort = shortenIRI(full, ctx);
1206
- edits.push({ start: result.length, end: result.length, text: `\n[${label}] {=?${objectShort} ?${predShort}}` });
1242
+ edits.push({ start: result.length, end: result.length, text: `\n[${label}] {+${objectShort} ?${predShort}}` });
1207
1243
  }
1208
1244
  return;
1209
1245
  }
@@ -1246,9 +1282,9 @@ export function serialize({ text, diff, origin, options = {} }) {
1246
1282
  // Create new annotation with object token
1247
1283
  if (isSoftFragment) {
1248
1284
  const fragment = full.split('#')[1];
1249
- edits.push({ start: result.length, end: result.length, text: `\n[${objectShort}] {=?#${fragment} ?${predShort}}` });
1285
+ edits.push({ start: result.length, end: result.length, text: `\n[${objectShort}] {+#${fragment} ?${predShort}}` });
1250
1286
  } else {
1251
- edits.push({ start: result.length, end: result.length, text: `\n[${objectShort}] {=?${objectShort} ?${predShort}}` });
1287
+ edits.push({ start: result.length, end: result.length, text: `\n[${objectShort}] {+${objectShort} ?${predShort}}` });
1252
1288
  }
1253
1289
  }
1254
1290
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdld-parse",
3
- "version": "0.2.10",
3
+ "version": "0.3.1",
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",