mdld-parse 0.7.6 → 0.7.7
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 +11 -3
- package/package.json +1 -1
- package/src/generate.js +13 -7
- package/src/merge.js +8 -1
- package/src/parse.js +8 -1
package/README.md
CHANGED
|
@@ -198,13 +198,14 @@ Parse MD-LD markdown and return RDF quads with lean origin tracking.
|
|
|
198
198
|
- `context` (object) — Prefix mappings (default: `{ '@vocab': 'http://www.w3.org/2000/01/rdf-schema#', rdf, rdfs, xsd, sh, prov }`)
|
|
199
199
|
- `dataFactory` (object) — Custom RDF/JS DataFactory
|
|
200
200
|
|
|
201
|
-
**Returns:** `{ quads, remove, statements, origin, context }`
|
|
201
|
+
**Returns:** `{ quads, remove, statements, origin, context, primarySubject }`
|
|
202
202
|
|
|
203
203
|
- `quads` — Array of RDF/JS Quads (final resolved graph state)
|
|
204
204
|
- `remove` — Array of RDF/JS Quads (external retractions targeting prior state)
|
|
205
205
|
- `statements` — Array of elevated RDF/JS Quads extracted from rdf:Statement patterns
|
|
206
206
|
- `origin` — Lean origin tracking object with quadIndex for UI navigation
|
|
207
207
|
- `context` — Final context used (includes prefixes)
|
|
208
|
+
- `primarySubject` — String IRI or null (first non-fragment subject declaration)
|
|
208
209
|
|
|
209
210
|
### `merge(docs, options)`
|
|
210
211
|
|
|
@@ -215,15 +216,22 @@ Merge multiple MDLD documents with diff polarity resolution.
|
|
|
215
216
|
- `options` (object, optional):
|
|
216
217
|
- `context` (object) — Prefix mappings (merged with DEFAULT_CONTEXT)
|
|
217
218
|
|
|
218
|
-
**Returns:** `{ quads, remove, origin, context }`
|
|
219
|
+
**Returns:** `{ quads, remove, origin, context, primarySubjects }`
|
|
219
220
|
|
|
220
|
-
|
|
221
|
+
- `quads` — Array of RDF/JS Quads (final resolved graph state)
|
|
222
|
+
- `remove` — Array of RDF/JS Quads (external retractions targeting prior state)
|
|
223
|
+
- `origin` — Merge origin tracking with document index and polarity
|
|
224
|
+
- `context` — Final merged context
|
|
225
|
+
- `primarySubjects` — Array of string IRIs (primary subjects from each document, in merge order)
|
|
226
|
+
|
|
227
|
+
### `generate(quads, context, primarySubject)`
|
|
221
228
|
|
|
222
229
|
Generate deterministic MDLD from RDF quads.
|
|
223
230
|
|
|
224
231
|
**Parameters:**
|
|
225
232
|
- `quads` (array) — Array of RDF/JS Quads to convert
|
|
226
233
|
- `context` (object, optional) — Prefix mappings (default: `{}`)
|
|
234
|
+
- `primarySubject` (string, optional) — String IRI to place first in output (ensures round-trip safety)
|
|
227
235
|
|
|
228
236
|
**Returns:** `{ text, context }`
|
|
229
237
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mdld-parse",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7",
|
|
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",
|
package/src/generate.js
CHANGED
|
@@ -34,17 +34,17 @@ export function extractLocalName(iri, ctx = {}) {
|
|
|
34
34
|
/**
|
|
35
35
|
* Generate deterministic MDLD from RDF quads
|
|
36
36
|
* Purpose: TTL→MDLD conversion with canonical structure
|
|
37
|
-
* Input: RDF quads + context
|
|
37
|
+
* Input: RDF quads + context + optional primarySubject (string IRI)
|
|
38
38
|
* Output: MDLD text
|
|
39
39
|
*/
|
|
40
|
-
export function generate(quads, context = {}) {
|
|
40
|
+
export function generate(quads, context = {}, primarySubject = null) {
|
|
41
41
|
const fullContext = { ...DEFAULT_CONTEXT, ...context };
|
|
42
42
|
|
|
43
43
|
const normalizedQuads = normalizeAndSortQuads(quads);
|
|
44
44
|
|
|
45
45
|
const subjectGroups = groupQuadsBySubject(normalizedQuads);
|
|
46
46
|
|
|
47
|
-
const { text } = buildDeterministicMDLD(subjectGroups, fullContext);
|
|
47
|
+
const { text } = buildDeterministicMDLD(subjectGroups, fullContext, primarySubject);
|
|
48
48
|
|
|
49
49
|
return text;
|
|
50
50
|
}
|
|
@@ -86,7 +86,7 @@ function groupQuadsBySubject(quads) {
|
|
|
86
86
|
return groups;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
function buildDeterministicMDLD(subjectGroups, context) {
|
|
89
|
+
function buildDeterministicMDLD(subjectGroups, context, primarySubject = null) {
|
|
90
90
|
let text = '';
|
|
91
91
|
const usedPrefixes = collectUsedPrefixes(subjectGroups, context);
|
|
92
92
|
|
|
@@ -103,10 +103,16 @@ function buildDeterministicMDLD(subjectGroups, context) {
|
|
|
103
103
|
text += '\n';
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
// Process subjects in deterministic order
|
|
106
|
+
// Process subjects in deterministic order, with primary subject first
|
|
107
107
|
const sortedSubjects = Array.from(subjectGroups.keys()).sort();
|
|
108
|
+
const primarySubjectIRI = primarySubject; // Already a string IRI
|
|
108
109
|
|
|
109
|
-
|
|
110
|
+
// If primary subject exists, place it first
|
|
111
|
+
const orderedSubjects = primarySubjectIRI
|
|
112
|
+
? [primarySubjectIRI, ...sortedSubjects.filter(s => s !== primarySubjectIRI)]
|
|
113
|
+
: sortedSubjects;
|
|
114
|
+
|
|
115
|
+
for (const subjectIRI of orderedSubjects) {
|
|
110
116
|
const subjectQuads = subjectGroups.get(subjectIRI);
|
|
111
117
|
const shortSubject = shortenIRI(subjectIRI, context);
|
|
112
118
|
|
|
@@ -119,7 +125,7 @@ function buildDeterministicMDLD(subjectGroups, context) {
|
|
|
119
125
|
? ' ' + types.map(t => '.' + shortenIRI(t.object.value, context)).sort().join(' ')
|
|
120
126
|
: '';
|
|
121
127
|
|
|
122
|
-
text += `# ${localSubjectName} {=${shortSubject}${typeAnnotations}}\n`;
|
|
128
|
+
text += `# ${localSubjectName} {=${shortSubject}${typeAnnotations}}\n\n`;
|
|
123
129
|
|
|
124
130
|
// Add literals and objects using shared utilities
|
|
125
131
|
sortQuadsByPredicate(literals).forEach(quad => {
|
package/src/merge.js
CHANGED
|
@@ -42,6 +42,7 @@ export function merge(docs, options = {}) {
|
|
|
42
42
|
const quadIndex = new Map();
|
|
43
43
|
const allStatements = []; // Collect statements from all documents
|
|
44
44
|
const accumulatedContext = new Map(); // Track all unique prefixes across documents
|
|
45
|
+
const primarySubjects = []; // Collect primary subjects from all documents
|
|
45
46
|
|
|
46
47
|
// Process each document in order
|
|
47
48
|
for (let i = 0; i < docs.length; i++) {
|
|
@@ -78,6 +79,11 @@ export function merge(docs, options = {}) {
|
|
|
78
79
|
allStatements.push(...doc.statements);
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
// Collect primary subject from this document (already a string IRI)
|
|
83
|
+
if (doc.primarySubject) {
|
|
84
|
+
primarySubjects.push(doc.primarySubject);
|
|
85
|
+
}
|
|
86
|
+
|
|
81
87
|
// Fold assertions into session buffer
|
|
82
88
|
for (const quad of doc.quads) {
|
|
83
89
|
const key = quadKey(quad);
|
|
@@ -144,6 +150,7 @@ export function merge(docs, options = {}) {
|
|
|
144
150
|
remove: filteredRemove,
|
|
145
151
|
statements: allStatements, // Include all collected statements
|
|
146
152
|
origin: mergeOrigin,
|
|
147
|
-
context: finalContext
|
|
153
|
+
context: finalContext,
|
|
154
|
+
primarySubjects: primarySubjects // Include all collected primary subjects
|
|
148
155
|
};
|
|
149
156
|
}
|
package/src/parse.js
CHANGED
|
@@ -49,6 +49,7 @@ export function parse(text, options = {}) {
|
|
|
49
49
|
const state = {
|
|
50
50
|
ctx: { ...DEFAULT_CONTEXT, ...(options.context || {}) },
|
|
51
51
|
df: options.dataFactory || DataFactory,
|
|
52
|
+
graph: DataFactory.namedNode(options.graph) || DataFactory.defaultGraph(),
|
|
52
53
|
quads: [],
|
|
53
54
|
quadBuffer: new Map(),
|
|
54
55
|
removeSet: new Set(),
|
|
@@ -58,6 +59,7 @@ export function parse(text, options = {}) {
|
|
|
58
59
|
documentStructure: []
|
|
59
60
|
},
|
|
60
61
|
currentSubject: null,
|
|
62
|
+
primarySubject: null,
|
|
61
63
|
tokens: null,
|
|
62
64
|
currentTokenIndex: -1,
|
|
63
65
|
statements: [],
|
|
@@ -107,7 +109,7 @@ export function parse(text, options = {}) {
|
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
|
|
110
|
-
return { quads: state.quads, remove: filteredRemove, statements: state.statements, origin: state.origin, context: state.ctx };
|
|
112
|
+
return { quads: state.quads, remove: filteredRemove, statements: state.statements, origin: state.origin, context: state.ctx, primarySubject: state.primarySubject };
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
|
|
@@ -387,6 +389,11 @@ function processAnnotationWithBlockTracking(carrier, sem, state, options = {}) {
|
|
|
387
389
|
const newSubject = resolveSubject(sem, state);
|
|
388
390
|
const localObject = resolveObject(sem, state);
|
|
389
391
|
|
|
392
|
+
// Track primary subject: first non-fragment subject declaration (fixed once detected)
|
|
393
|
+
if (newSubject && !state.primarySubject && !sem.subject.startsWith('=#')) {
|
|
394
|
+
state.primarySubject = newSubject.value; // Store as string IRI
|
|
395
|
+
}
|
|
396
|
+
|
|
390
397
|
const effectiveSubject = implicitSubject || (newSubject && !preserveGlobalSubject ? newSubject : previousSubject);
|
|
391
398
|
if (newSubject && !preserveGlobalSubject && !implicitSubject) {
|
|
392
399
|
state.currentSubject = newSubject;
|