mdld-parse 0.1.0
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 +374 -0
- package/index.js +882 -0
- package/package.json +39 -0
- package/tests.js +409 -0
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mdld-parse",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js",
|
|
9
|
+
"./tests": "./tests.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"index.js",
|
|
13
|
+
"tests.js"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node tests.js"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"mdld",
|
|
20
|
+
"markdown",
|
|
21
|
+
"rdf",
|
|
22
|
+
"semantic-web",
|
|
23
|
+
"linked-data",
|
|
24
|
+
"n3",
|
|
25
|
+
"rdfjs",
|
|
26
|
+
"browser",
|
|
27
|
+
"web-worker",
|
|
28
|
+
"parser"
|
|
29
|
+
],
|
|
30
|
+
"author": "davay42",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/davay42/mdld-parse.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://davay42.github.io/mdld-parse/",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/davay42/mdld-parse/issues"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/tests.js
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MD-LD Parser Test Suite
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive tests for MD-LD specification compliance
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const testDocuments = {
|
|
8
|
+
minimal: {
|
|
9
|
+
name: 'Minimal Markdown (no frontmatter)',
|
|
10
|
+
markdown: `# Hello World
|
|
11
|
+
|
|
12
|
+
This is a simple document.`,
|
|
13
|
+
expectedQuads: 2,
|
|
14
|
+
checks: [
|
|
15
|
+
{ subject: '', predicate: 'label', object: 'Hello World' },
|
|
16
|
+
{ subject: '', predicate: 'description', object: 'This is a simple document.' }
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
basicFrontmatter: {
|
|
21
|
+
name: 'Basic YAML-LD Frontmatter',
|
|
22
|
+
markdown: `---
|
|
23
|
+
'@context':
|
|
24
|
+
'@vocab': 'http://schema.org/'
|
|
25
|
+
'@id': '#doc'
|
|
26
|
+
'@type': Article
|
|
27
|
+
name: 'Test Article'
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
# Test Article`,
|
|
31
|
+
expectedQuads: 2,
|
|
32
|
+
checks: [
|
|
33
|
+
{ subject: '#doc', predicate: 'type', object: 'Article' },
|
|
34
|
+
{ subject: '#doc', predicate: 'label', object: 'Test Article' }
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
subjectDeclaration: {
|
|
39
|
+
name: 'Subject Declaration with typeof',
|
|
40
|
+
markdown: `---
|
|
41
|
+
'@context':
|
|
42
|
+
'@vocab': 'http://schema.org/'
|
|
43
|
+
'@id': '#doc'
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Alice Johnson
|
|
47
|
+
{#alice typeof="Person"}
|
|
48
|
+
|
|
49
|
+
Alice is a developer.`,
|
|
50
|
+
expectedQuads: 3,
|
|
51
|
+
checks: [
|
|
52
|
+
{ subject: '#alice', predicate: 'type', object: 'Person' },
|
|
53
|
+
{ subject: '#alice', predicate: 'label', literal: 'Alice Johnson' }
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
literalProperty: {
|
|
58
|
+
name: 'Literal Property',
|
|
59
|
+
markdown: `---
|
|
60
|
+
'@context':
|
|
61
|
+
'@vocab': 'http://schema.org/'
|
|
62
|
+
'@id': '#alice'
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
# Alice
|
|
66
|
+
|
|
67
|
+
[Alice Johnson]{property="name"}`,
|
|
68
|
+
expectedQuads: 2,
|
|
69
|
+
checks: [
|
|
70
|
+
{ subject: '#alice', predicate: 'name', literal: 'Alice Johnson' }
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
objectProperty: {
|
|
75
|
+
name: 'Object Property (rel)',
|
|
76
|
+
markdown: `---
|
|
77
|
+
'@context':
|
|
78
|
+
'@vocab': 'http://schema.org/'
|
|
79
|
+
'@id': '#alice'
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
# Alice
|
|
83
|
+
|
|
84
|
+
Works at [Tech Corp](#company){rel="worksFor"}`,
|
|
85
|
+
expectedQuads: 2,
|
|
86
|
+
checks: [
|
|
87
|
+
{ subject: '#alice', predicate: 'worksFor', object: '#company' }
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
typedLiteral: {
|
|
92
|
+
name: 'Typed Literal with datatype',
|
|
93
|
+
markdown: `---
|
|
94
|
+
'@context':
|
|
95
|
+
'@vocab': 'http://schema.org/'
|
|
96
|
+
xsd: 'http://www.w3.org/2001/XMLSchema#'
|
|
97
|
+
'@id': '#data'
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
# Data
|
|
101
|
+
|
|
102
|
+
Age: [30]{property="age" datatype="xsd:integer"}`,
|
|
103
|
+
expectedQuads: 2,
|
|
104
|
+
checks: [
|
|
105
|
+
{ subject: '#data', predicate: 'age', literal: '30', datatype: 'integer' }
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
multipleTypes: {
|
|
110
|
+
name: 'Multiple Types',
|
|
111
|
+
markdown: `---
|
|
112
|
+
'@context':
|
|
113
|
+
'@vocab': 'http://schema.org/'
|
|
114
|
+
'@id': '#doc'
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Resource
|
|
118
|
+
{#res typeof="Person Organization"}`,
|
|
119
|
+
expectedQuads: 2,
|
|
120
|
+
checks: [
|
|
121
|
+
{ subject: '#res', predicate: 'type', object: 'Person' },
|
|
122
|
+
{ subject: '#res', predicate: 'type', object: 'Organization' }
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
taskList: {
|
|
127
|
+
name: 'Task List to Actions',
|
|
128
|
+
markdown: `---
|
|
129
|
+
'@context':
|
|
130
|
+
'@vocab': 'http://schema.org/'
|
|
131
|
+
'@id': '#doc'
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
# Tasks
|
|
135
|
+
|
|
136
|
+
- [x] Complete review
|
|
137
|
+
- [ ] Submit paper`,
|
|
138
|
+
expectedQuads: 8,
|
|
139
|
+
checks: [
|
|
140
|
+
{ predicate: 'type', object: 'Action' },
|
|
141
|
+
{ predicate: 'actionStatus', object: 'CompletedActionStatus' },
|
|
142
|
+
{ predicate: 'actionStatus', object: 'PotentialActionStatus' }
|
|
143
|
+
]
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
nestedSubjects: {
|
|
147
|
+
name: 'Nested Subject Declarations',
|
|
148
|
+
markdown: `---
|
|
149
|
+
'@context':
|
|
150
|
+
'@vocab': 'http://schema.org/'
|
|
151
|
+
'@id': '#doc'
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Company
|
|
155
|
+
{#company typeof="Organization"}
|
|
156
|
+
|
|
157
|
+
[Tech Corp]{property="name"}
|
|
158
|
+
|
|
159
|
+
### Employee
|
|
160
|
+
{#alice typeof="Person"}
|
|
161
|
+
|
|
162
|
+
[Alice]{property="name"}`,
|
|
163
|
+
expectedQuads: 4,
|
|
164
|
+
checks: [
|
|
165
|
+
{ subject: '#company', predicate: 'type', object: 'Organization' },
|
|
166
|
+
{ subject: '#company', predicate: 'name', literal: 'Tech Corp' },
|
|
167
|
+
{ subject: '#alice', predicate: 'type', object: 'Person' },
|
|
168
|
+
{ subject: '#alice', predicate: 'name', literal: 'Alice' }
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
blankNode: {
|
|
173
|
+
name: 'Blank Node Creation',
|
|
174
|
+
markdown: `---
|
|
175
|
+
'@context':
|
|
176
|
+
'@vocab': 'http://schema.org/'
|
|
177
|
+
'@id': '#alice'
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
# Alice
|
|
181
|
+
|
|
182
|
+
Works at [Tech Corp]{rel="worksFor" typeof="Organization"}`,
|
|
183
|
+
expectedQuads: 3,
|
|
184
|
+
checks: [
|
|
185
|
+
{ subject: '#alice', predicate: 'worksFor' },
|
|
186
|
+
{ predicate: 'type', object: 'Organization' }
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
multiplePrefixes: {
|
|
191
|
+
name: 'Multiple Prefix Contexts',
|
|
192
|
+
markdown: `---
|
|
193
|
+
'@context':
|
|
194
|
+
'@vocab': 'http://schema.org/'
|
|
195
|
+
foaf: 'http://xmlns.com/foaf/0.1/'
|
|
196
|
+
dct: 'http://purl.org/dc/terms/'
|
|
197
|
+
'@id': '#alice'
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
# Alice
|
|
201
|
+
|
|
202
|
+
[Alice Johnson]{property="foaf:name"}
|
|
203
|
+
|
|
204
|
+
[Developer]{property="dct:title"}`,
|
|
205
|
+
expectedQuads: 3,
|
|
206
|
+
checks: [
|
|
207
|
+
{ predicate: 'foaf:name', literal: 'Alice Johnson' },
|
|
208
|
+
{ predicate: 'dct:title', literal: 'Developer' }
|
|
209
|
+
]
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
descriptionFromParagraph: {
|
|
213
|
+
name: 'Description from First Paragraph',
|
|
214
|
+
markdown: `---
|
|
215
|
+
'@context':
|
|
216
|
+
'@vocab': 'http://schema.org/'
|
|
217
|
+
'@id': '#doc'
|
|
218
|
+
'@type': Article
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
# My Article
|
|
222
|
+
|
|
223
|
+
This is the abstract of my article.
|
|
224
|
+
|
|
225
|
+
More content follows.`,
|
|
226
|
+
expectedQuads: 3,
|
|
227
|
+
checks: [
|
|
228
|
+
{ subject: '#doc', predicate: 'label', literal: 'My Article' },
|
|
229
|
+
{ subject: '#doc', predicate: 'description', literal: 'This is the abstract of my article.' }
|
|
230
|
+
]
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
semanticLinkList: {
|
|
234
|
+
name: 'Semantic Links in Bullet List',
|
|
235
|
+
markdown: `---
|
|
236
|
+
'@context':
|
|
237
|
+
'@vocab': 'http://schema.org/'
|
|
238
|
+
'@id': '#doc'
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
- [What it is!](#what-is){rel="hasPart"}`,
|
|
242
|
+
expectedQuads: 1,
|
|
243
|
+
checks: [
|
|
244
|
+
{ subject: '#doc', predicate: 'hasPart', object: '#what-is' }
|
|
245
|
+
]
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
codeBlock: {
|
|
249
|
+
name: 'Code Block as SoftwareSourceCode',
|
|
250
|
+
markdown: `---
|
|
251
|
+
'@context':
|
|
252
|
+
'@vocab': 'http://schema.org/'
|
|
253
|
+
'@id': '#doc'
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
# Example
|
|
257
|
+
|
|
258
|
+
\`\`\`sparql
|
|
259
|
+
SELECT * WHERE { ?s ?p ?o }
|
|
260
|
+
\`\`\``,
|
|
261
|
+
expectedQuads: 4,
|
|
262
|
+
checks: [
|
|
263
|
+
{ predicate: 'programmingLanguage', literal: 'sparql' },
|
|
264
|
+
{ predicate: 'text', literal: 'SELECT * WHERE { ?s ?p ?o }' },
|
|
265
|
+
{ subject: '#doc', predicate: 'hasPart' },
|
|
266
|
+
{ predicate: 'type', object: 'SoftwareSourceCode' }
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// ============================================================================
|
|
272
|
+
// Test Runner
|
|
273
|
+
// ============================================================================
|
|
274
|
+
|
|
275
|
+
export async function runTests(options = {}) {
|
|
276
|
+
const { silent = false, onResult = null } = options;
|
|
277
|
+
|
|
278
|
+
let passed = 0;
|
|
279
|
+
let failed = 0;
|
|
280
|
+
const results = [];
|
|
281
|
+
|
|
282
|
+
function log(...args) {
|
|
283
|
+
if (!silent) console.log(...args);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function testQuads(quads, doc) {
|
|
287
|
+
const checks = doc.checks || [];
|
|
288
|
+
const failures = [];
|
|
289
|
+
|
|
290
|
+
for (const check of checks) {
|
|
291
|
+
let found = false;
|
|
292
|
+
|
|
293
|
+
for (const quad of quads) {
|
|
294
|
+
const subjectMatch = !check.subject ||
|
|
295
|
+
quad.subject.value.includes(check.subject);
|
|
296
|
+
|
|
297
|
+
const predicateMatch = !check.predicate ||
|
|
298
|
+
quad.predicate.value.includes(check.predicate);
|
|
299
|
+
|
|
300
|
+
const objectMatch = check.literal
|
|
301
|
+
? (quad.object.termType === 'Literal' &&
|
|
302
|
+
quad.object.value === check.literal)
|
|
303
|
+
: (!check.object || quad.object.value.includes(check.object));
|
|
304
|
+
|
|
305
|
+
const datatypeMatch = !check.datatype ||
|
|
306
|
+
(quad.object.datatype &&
|
|
307
|
+
quad.object.datatype.value &&
|
|
308
|
+
quad.object.datatype.value.includes(check.datatype));
|
|
309
|
+
|
|
310
|
+
if (subjectMatch && predicateMatch && objectMatch && datatypeMatch) {
|
|
311
|
+
found = true;
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (!found) {
|
|
317
|
+
failures.push(check);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return { passed: failures.length === 0, failures };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
log('\n๐งช MD-LD Parser Test Suite\n');
|
|
325
|
+
|
|
326
|
+
for (const [key, doc] of Object.entries(testDocuments)) {
|
|
327
|
+
let testPassed = false;
|
|
328
|
+
let errorMsg = null;
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
// Dynamic import for Node.js vs Browser
|
|
332
|
+
let parseMDLD;
|
|
333
|
+
if (typeof require !== 'undefined') {
|
|
334
|
+
parseMDLD = require('./index.js').parseMDLD;
|
|
335
|
+
} else {
|
|
336
|
+
// In browser, assume it's already imported
|
|
337
|
+
parseMDLD = window.parseMDLD || (await import('./index.js')).parseMDLD;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const quads = parseMDLD(doc.markdown, {
|
|
341
|
+
baseIRI: 'http://example.org/doc'
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const checkResult = testQuads(quads, doc);
|
|
345
|
+
|
|
346
|
+
if (checkResult.passed) {
|
|
347
|
+
testPassed = true;
|
|
348
|
+
passed++;
|
|
349
|
+
log(`โ
${doc.name}`);
|
|
350
|
+
} else {
|
|
351
|
+
failed++;
|
|
352
|
+
errorMsg = `Failed checks: ${JSON.stringify(checkResult.failures, null, 2)}`;
|
|
353
|
+
log(`โ ${doc.name}`);
|
|
354
|
+
log(` ${errorMsg}`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
results.push({
|
|
358
|
+
name: doc.name,
|
|
359
|
+
passed: testPassed,
|
|
360
|
+
quads: quads.length,
|
|
361
|
+
expected: doc.expectedQuads,
|
|
362
|
+
error: errorMsg
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
if (onResult) {
|
|
366
|
+
onResult({
|
|
367
|
+
name: doc.name,
|
|
368
|
+
passed: testPassed,
|
|
369
|
+
error: errorMsg
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
} catch (error) {
|
|
374
|
+
failed++;
|
|
375
|
+
testPassed = false;
|
|
376
|
+
errorMsg = error.message;
|
|
377
|
+
log(`โ ${doc.name}`);
|
|
378
|
+
log(` Error: ${error.message}`);
|
|
379
|
+
|
|
380
|
+
results.push({
|
|
381
|
+
name: doc.name,
|
|
382
|
+
passed: false,
|
|
383
|
+
error: errorMsg
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
if (onResult) {
|
|
387
|
+
onResult({
|
|
388
|
+
name: doc.name,
|
|
389
|
+
passed: false,
|
|
390
|
+
error: errorMsg
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
log(`\n๐ Results: ${passed} passed, ${failed} failed (${passed + failed} total)\n`);
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
results: { passed, failed, total: passed + failed },
|
|
400
|
+
testResults: results
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Run tests if executed directly in Node.js
|
|
405
|
+
if (typeof require !== 'undefined' && require.main === module) {
|
|
406
|
+
runTests();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export default { testDocuments, runTests };
|