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.
Files changed (4) hide show
  1. package/README.md +374 -0
  2. package/index.js +882 -0
  3. package/package.json +39 -0
  4. 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 };