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.
- package/README.md +54 -11
- package/index.js +60 -11
- 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]
|
|
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
|
-
|
|
32
|
+
ex:armstrong schema:fullName "Neil Armstrong" .
|
|
33
|
+
```
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
## Core Features
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
|
1155
|
-
const
|
|
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 (
|
|
1160
|
-
// Add
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|