@unrdf/kgn 5.0.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.
- package/LICENSE +21 -0
- package/README.md +210 -0
- package/package.json +90 -0
- package/src/MIGRATION_COMPLETE.md +186 -0
- package/src/PORT-MAP.md +302 -0
- package/src/base/filter-templates.js +479 -0
- package/src/base/index.js +92 -0
- package/src/base/injection-targets.js +583 -0
- package/src/base/macro-templates.js +298 -0
- package/src/base/macro-templates.js.bak +461 -0
- package/src/base/shacl-templates.js +617 -0
- package/src/base/template-base.js +388 -0
- package/src/core/attestor.js +381 -0
- package/src/core/filters.js +518 -0
- package/src/core/index.js +21 -0
- package/src/core/kgen-engine.js +372 -0
- package/src/core/parser.js +447 -0
- package/src/core/post-processor.js +313 -0
- package/src/core/renderer.js +469 -0
- package/src/doc-generator/cli.mjs +122 -0
- package/src/doc-generator/index.mjs +28 -0
- package/src/doc-generator/mdx-generator.mjs +71 -0
- package/src/doc-generator/nav-generator.mjs +136 -0
- package/src/doc-generator/parser.mjs +291 -0
- package/src/doc-generator/rdf-builder.mjs +306 -0
- package/src/doc-generator/scanner.mjs +189 -0
- package/src/engine/index.js +42 -0
- package/src/engine/pipeline.js +448 -0
- package/src/engine/renderer.js +604 -0
- package/src/engine/template-engine.js +566 -0
- package/src/filters/array.js +436 -0
- package/src/filters/data.js +479 -0
- package/src/filters/index.js +270 -0
- package/src/filters/rdf.js +264 -0
- package/src/filters/text.js +369 -0
- package/src/index.js +109 -0
- package/src/inheritance/index.js +40 -0
- package/src/injection/api.js +260 -0
- package/src/injection/atomic-writer.js +327 -0
- package/src/injection/constants.js +136 -0
- package/src/injection/idempotency-manager.js +295 -0
- package/src/injection/index.js +28 -0
- package/src/injection/injection-engine.js +378 -0
- package/src/injection/integration.js +339 -0
- package/src/injection/modes/index.js +341 -0
- package/src/injection/rollback-manager.js +373 -0
- package/src/injection/target-resolver.js +323 -0
- package/src/injection/tests/atomic-writer.test.js +382 -0
- package/src/injection/tests/injection-engine.test.js +611 -0
- package/src/injection/tests/integration.test.js +392 -0
- package/src/injection/tests/run-tests.js +283 -0
- package/src/injection/validation-engine.js +547 -0
- package/src/linter/determinism-linter.js +473 -0
- package/src/linter/determinism.js +410 -0
- package/src/linter/index.js +6 -0
- package/src/linter/test-doubles.js +475 -0
- package/src/parser/frontmatter.js +228 -0
- package/src/parser/variables.js +344 -0
- package/src/renderer/deterministic.js +245 -0
- package/src/renderer/index.js +6 -0
- package/src/templates/latex/academic-paper.njk +186 -0
- package/src/templates/latex/index.js +104 -0
- package/src/templates/nextjs/app-page.njk +66 -0
- package/src/templates/nextjs/index.js +80 -0
- package/src/templates/office/docx/document.njk +368 -0
- package/src/templates/office/index.js +79 -0
- package/src/templates/office/word-report.njk +129 -0
- package/src/utils/template-utils.js +426 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KGEN SHACL Templates - RDF validation shape templates
|
|
3
|
+
*
|
|
4
|
+
* Provides templates for creating SHACL (Shapes Constraint Language) shapes
|
|
5
|
+
* for validating RDF knowledge graphs used in KGEN template generation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class KGenSHACLTemplates {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.options = {
|
|
11
|
+
deterministicMode: options.deterministicMode !== false,
|
|
12
|
+
staticBuildTime: options.staticBuildTime || '2024-01-01T00:00:00.000Z',
|
|
13
|
+
namespace: options.namespace || 'http://kgen.ai/shacl#',
|
|
14
|
+
baseIRI: options.baseIRI || 'http://kgen.ai/',
|
|
15
|
+
...options
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
this.shapeTemplates = new Map();
|
|
19
|
+
this.initializeSHACLTemplates();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Initialize built-in SHACL shape templates
|
|
24
|
+
*/
|
|
25
|
+
initializeSHACLTemplates() {
|
|
26
|
+
// Basic node shape template
|
|
27
|
+
this.registerShapeTemplate('basic_node_shape', {
|
|
28
|
+
name: '{{ shapeName }}Shape',
|
|
29
|
+
description: 'Basic node shape for {{ targetClass | default("Resource") }}',
|
|
30
|
+
template: `@prefix sh: <http://www.w3.org/ns/shacl#> .
|
|
31
|
+
@prefix kgen: <${this.options.namespace}> .
|
|
32
|
+
@prefix ex: <${this.options.baseIRI}> .
|
|
33
|
+
|
|
34
|
+
ex:{{ shapeName }}Shape
|
|
35
|
+
a sh:NodeShape ;
|
|
36
|
+
sh:targetClass ex:{{ targetClass | default(shapeName) }} ;
|
|
37
|
+
{% if description -%}
|
|
38
|
+
rdfs:comment "{{ description }}" ;
|
|
39
|
+
{% endif -%}
|
|
40
|
+
{% for property in properties | default([]) -%}
|
|
41
|
+
sh:property [
|
|
42
|
+
sh:path ex:{{ property.path }} ;
|
|
43
|
+
{% if property.datatype -%}
|
|
44
|
+
sh:datatype {{ property.datatype }} ;
|
|
45
|
+
{% endif -%}
|
|
46
|
+
{% if property.minCount -%}
|
|
47
|
+
sh:minCount {{ property.minCount }} ;
|
|
48
|
+
{% endif -%}
|
|
49
|
+
{% if property.maxCount -%}
|
|
50
|
+
sh:maxCount {{ property.maxCount }} ;
|
|
51
|
+
{% endif -%}
|
|
52
|
+
{% if property.pattern -%}
|
|
53
|
+
sh:pattern "{{ property.pattern }}" ;
|
|
54
|
+
{% endif -%}
|
|
55
|
+
{% if property.message -%}
|
|
56
|
+
sh:message "{{ property.message }}" ;
|
|
57
|
+
{% endif -%}
|
|
58
|
+
] ;
|
|
59
|
+
{% endfor -%}
|
|
60
|
+
.`
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Property shape template
|
|
64
|
+
this.registerShapeTemplate('property_shape', {
|
|
65
|
+
name: '{{ shapeName }}PropertyShape',
|
|
66
|
+
description: 'Property shape for {{ propertyPath }}',
|
|
67
|
+
template: `@prefix sh: <http://www.w3.org/ns/shacl#> .
|
|
68
|
+
@prefix kgen: <${this.options.namespace}> .
|
|
69
|
+
@prefix ex: <${this.options.baseIRI}> .
|
|
70
|
+
|
|
71
|
+
ex:{{ shapeName }}PropertyShape
|
|
72
|
+
a sh:PropertyShape ;
|
|
73
|
+
sh:path ex:{{ propertyPath }} ;
|
|
74
|
+
{% if datatype -%}
|
|
75
|
+
sh:datatype {{ datatype }} ;
|
|
76
|
+
{% endif -%}
|
|
77
|
+
{% if nodeKind -%}
|
|
78
|
+
sh:nodeKind {{ nodeKind }} ;
|
|
79
|
+
{% endif -%}
|
|
80
|
+
{% if minCount !== undefined -%}
|
|
81
|
+
sh:minCount {{ minCount }} ;
|
|
82
|
+
{% endif -%}
|
|
83
|
+
{% if maxCount !== undefined -%}
|
|
84
|
+
sh:maxCount {{ maxCount }} ;
|
|
85
|
+
{% endif -%}
|
|
86
|
+
{% if minLength -%}
|
|
87
|
+
sh:minLength {{ minLength }} ;
|
|
88
|
+
{% endif -%}
|
|
89
|
+
{% if maxLength -%}
|
|
90
|
+
sh:maxLength {{ maxLength }} ;
|
|
91
|
+
{% endif -%}
|
|
92
|
+
{% if pattern -%}
|
|
93
|
+
sh:pattern "{{ pattern }}" ;
|
|
94
|
+
{% endif -%}
|
|
95
|
+
{% if in -%}
|
|
96
|
+
sh:in ( {{ in | join(' ') }} ) ;
|
|
97
|
+
{% endif -%}
|
|
98
|
+
{% if message -%}
|
|
99
|
+
sh:message "{{ message }}" ;
|
|
100
|
+
{% endif -%}
|
|
101
|
+
{% if severity -%}
|
|
102
|
+
sh:severity {{ severity }} ;
|
|
103
|
+
{% endif -%}
|
|
104
|
+
.`
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Template validation shape
|
|
108
|
+
this.registerShapeTemplate('template_shape', {
|
|
109
|
+
name: 'TemplateShape',
|
|
110
|
+
description: 'Validation shape for KGEN templates',
|
|
111
|
+
template: `@prefix sh: <http://www.w3.org/ns/shacl#> .
|
|
112
|
+
@prefix kgen: <${this.options.namespace}> .
|
|
113
|
+
@prefix ex: <${this.options.baseIRI}> .
|
|
114
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
115
|
+
|
|
116
|
+
kgen:TemplateShape
|
|
117
|
+
a sh:NodeShape ;
|
|
118
|
+
sh:targetClass kgen:Template ;
|
|
119
|
+
rdfs:comment "Validation shape for KGEN templates" ;
|
|
120
|
+
|
|
121
|
+
# Template must have a name
|
|
122
|
+
sh:property [
|
|
123
|
+
sh:path kgen:templateName ;
|
|
124
|
+
sh:datatype xsd:string ;
|
|
125
|
+
sh:minCount 1 ;
|
|
126
|
+
sh:maxCount 1 ;
|
|
127
|
+
sh:pattern "^[a-zA-Z][a-zA-Z0-9_-]*$" ;
|
|
128
|
+
sh:message "Template name must be a valid identifier" ;
|
|
129
|
+
] ;
|
|
130
|
+
|
|
131
|
+
# Template must have content
|
|
132
|
+
sh:property [
|
|
133
|
+
sh:path kgen:templateContent ;
|
|
134
|
+
sh:datatype xsd:string ;
|
|
135
|
+
sh:minCount 1 ;
|
|
136
|
+
sh:maxCount 1 ;
|
|
137
|
+
sh:minLength 1 ;
|
|
138
|
+
sh:message "Template must have content" ;
|
|
139
|
+
] ;
|
|
140
|
+
|
|
141
|
+
# Template version
|
|
142
|
+
sh:property [
|
|
143
|
+
sh:path kgen:version ;
|
|
144
|
+
sh:datatype xsd:string ;
|
|
145
|
+
sh:pattern "^\\d+\\.\\d+\\.\\d+$" ;
|
|
146
|
+
sh:message "Version must follow semantic versioning" ;
|
|
147
|
+
] ;
|
|
148
|
+
|
|
149
|
+
# Template category
|
|
150
|
+
sh:property [
|
|
151
|
+
sh:path kgen:category ;
|
|
152
|
+
sh:datatype xsd:string ;
|
|
153
|
+
sh:in ( "component" "layout" "module" "utility" "custom" ) ;
|
|
154
|
+
sh:message "Category must be one of: component, layout, module, utility, custom" ;
|
|
155
|
+
] ;
|
|
156
|
+
|
|
157
|
+
# Deterministic flag
|
|
158
|
+
sh:property [
|
|
159
|
+
sh:path kgen:isDeterministic ;
|
|
160
|
+
sh:datatype xsd:boolean ;
|
|
161
|
+
sh:maxCount 1 ;
|
|
162
|
+
] ;
|
|
163
|
+
.`
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Filter validation shape
|
|
167
|
+
this.registerShapeTemplate('filter_shape', {
|
|
168
|
+
name: 'FilterShape',
|
|
169
|
+
description: 'Validation shape for KGEN filters',
|
|
170
|
+
template: `@prefix sh: <http://www.w3.org/ns/shacl#> .
|
|
171
|
+
@prefix kgen: <${this.options.namespace}> .
|
|
172
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
173
|
+
|
|
174
|
+
kgen:FilterShape
|
|
175
|
+
a sh:NodeShape ;
|
|
176
|
+
sh:targetClass kgen:Filter ;
|
|
177
|
+
rdfs:comment "Validation shape for KGEN filters" ;
|
|
178
|
+
|
|
179
|
+
# Filter must have a name
|
|
180
|
+
sh:property [
|
|
181
|
+
sh:path kgen:filterName ;
|
|
182
|
+
sh:datatype xsd:string ;
|
|
183
|
+
sh:minCount 1 ;
|
|
184
|
+
sh:maxCount 1 ;
|
|
185
|
+
sh:pattern "^[a-zA-Z][a-zA-Z0-9_]*$" ;
|
|
186
|
+
sh:message "Filter name must be a valid JavaScript identifier" ;
|
|
187
|
+
] ;
|
|
188
|
+
|
|
189
|
+
# Filter function code
|
|
190
|
+
sh:property [
|
|
191
|
+
sh:path kgen:filterCode ;
|
|
192
|
+
sh:datatype xsd:string ;
|
|
193
|
+
sh:minCount 1 ;
|
|
194
|
+
sh:maxCount 1 ;
|
|
195
|
+
sh:message "Filter must have implementation code" ;
|
|
196
|
+
] ;
|
|
197
|
+
|
|
198
|
+
# Filter must be deterministic
|
|
199
|
+
sh:property [
|
|
200
|
+
sh:path kgen:isDeterministic ;
|
|
201
|
+
sh:datatype xsd:boolean ;
|
|
202
|
+
sh:hasValue true ;
|
|
203
|
+
sh:message "All KGEN filters must be deterministic" ;
|
|
204
|
+
] ;
|
|
205
|
+
.`
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Knowledge graph shape
|
|
209
|
+
this.registerShapeTemplate('knowledge_graph_shape', {
|
|
210
|
+
name: 'KnowledgeGraphShape',
|
|
211
|
+
description: 'Validation shape for KGEN knowledge graphs',
|
|
212
|
+
template: `@prefix sh: <http://www.w3.org/ns/shacl#> .
|
|
213
|
+
@prefix kgen: <${this.options.namespace}> .
|
|
214
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
215
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
216
|
+
|
|
217
|
+
kgen:KnowledgeGraphShape
|
|
218
|
+
a sh:NodeShape ;
|
|
219
|
+
sh:targetClass kgen:KnowledgeGraph ;
|
|
220
|
+
rdfs:comment "Validation shape for KGEN knowledge graphs" ;
|
|
221
|
+
|
|
222
|
+
# Graph must have an identifier
|
|
223
|
+
sh:property [
|
|
224
|
+
sh:path kgen:graphId ;
|
|
225
|
+
sh:datatype xsd:string ;
|
|
226
|
+
sh:minCount 1 ;
|
|
227
|
+
sh:maxCount 1 ;
|
|
228
|
+
sh:pattern "^[a-zA-Z][a-zA-Z0-9_-]*$" ;
|
|
229
|
+
sh:message "Graph ID must be a valid identifier" ;
|
|
230
|
+
] ;
|
|
231
|
+
|
|
232
|
+
# Graph version
|
|
233
|
+
sh:property [
|
|
234
|
+
sh:path kgen:graphVersion ;
|
|
235
|
+
sh:datatype xsd:string ;
|
|
236
|
+
sh:pattern "^\\d+\\.\\d+\\.\\d+$" ;
|
|
237
|
+
sh:message "Graph version must follow semantic versioning" ;
|
|
238
|
+
] ;
|
|
239
|
+
|
|
240
|
+
# Creation timestamp
|
|
241
|
+
sh:property [
|
|
242
|
+
sh:path kgen:createdAt ;
|
|
243
|
+
sh:datatype xsd:dateTime ;
|
|
244
|
+
sh:maxCount 1 ;
|
|
245
|
+
] ;
|
|
246
|
+
|
|
247
|
+
# Modification timestamp
|
|
248
|
+
sh:property [
|
|
249
|
+
sh:path kgen:modifiedAt ;
|
|
250
|
+
sh:datatype xsd:dateTime ;
|
|
251
|
+
sh:maxCount 1 ;
|
|
252
|
+
] ;
|
|
253
|
+
.`
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Component shape template
|
|
257
|
+
this.registerShapeTemplate('component_shape', {
|
|
258
|
+
name: 'ComponentShape',
|
|
259
|
+
description: 'Validation shape for template components',
|
|
260
|
+
template: `@prefix sh: <http://www.w3.org/ns/shacl#> .
|
|
261
|
+
@prefix kgen: <${this.options.namespace}> .
|
|
262
|
+
@prefix ex: <${this.options.baseIRI}> .
|
|
263
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
264
|
+
|
|
265
|
+
ex:{{ shapeName | default('Component') }}Shape
|
|
266
|
+
a sh:NodeShape ;
|
|
267
|
+
sh:targetClass ex:{{ targetClass | default('Component') }} ;
|
|
268
|
+
rdfs:comment "{{ description | default('Validation shape for components') }}" ;
|
|
269
|
+
|
|
270
|
+
{% for property in requiredProperties | default([]) -%}
|
|
271
|
+
# Required property: {{ property.name }}
|
|
272
|
+
sh:property [
|
|
273
|
+
sh:path ex:{{ property.name }} ;
|
|
274
|
+
{% if property.datatype -%}
|
|
275
|
+
sh:datatype {{ property.datatype }} ;
|
|
276
|
+
{% endif -%}
|
|
277
|
+
sh:minCount 1 ;
|
|
278
|
+
{% if property.maxCount -%}
|
|
279
|
+
sh:maxCount {{ property.maxCount }} ;
|
|
280
|
+
{% endif -%}
|
|
281
|
+
{% if property.pattern -%}
|
|
282
|
+
sh:pattern "{{ property.pattern }}" ;
|
|
283
|
+
{% endif -%}
|
|
284
|
+
sh:message "{{ property.message | default('Property ' + property.name + ' is required') }}" ;
|
|
285
|
+
] ;
|
|
286
|
+
|
|
287
|
+
{% endfor -%}
|
|
288
|
+
{% for property in optionalProperties | default([]) -%}
|
|
289
|
+
# Optional property: {{ property.name }}
|
|
290
|
+
sh:property [
|
|
291
|
+
sh:path ex:{{ property.name }} ;
|
|
292
|
+
{% if property.datatype -%}
|
|
293
|
+
sh:datatype {{ property.datatype }} ;
|
|
294
|
+
{% endif -%}
|
|
295
|
+
{% if property.maxCount -%}
|
|
296
|
+
sh:maxCount {{ property.maxCount }} ;
|
|
297
|
+
{% endif -%}
|
|
298
|
+
{% if property.pattern -%}
|
|
299
|
+
sh:pattern "{{ property.pattern }}" ;
|
|
300
|
+
{% endif -%}
|
|
301
|
+
] ;
|
|
302
|
+
|
|
303
|
+
{% endfor -%}
|
|
304
|
+
.`
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Validation rule template
|
|
308
|
+
this.registerShapeTemplate('validation_rule', {
|
|
309
|
+
name: 'ValidationRuleShape',
|
|
310
|
+
description: 'Template for custom validation rules',
|
|
311
|
+
template: `@prefix sh: <http://www.w3.org/ns/shacl#> .
|
|
312
|
+
@prefix kgen: <${this.options.namespace}> .
|
|
313
|
+
@prefix ex: <${this.options.baseIRI}> .
|
|
314
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
315
|
+
|
|
316
|
+
ex:{{ ruleName }}Rule
|
|
317
|
+
a sh:NodeShape ;
|
|
318
|
+
{% if targetClass -%}
|
|
319
|
+
sh:targetClass ex:{{ targetClass }} ;
|
|
320
|
+
{% endif -%}
|
|
321
|
+
{% if targetNode -%}
|
|
322
|
+
sh:targetNode ex:{{ targetNode }} ;
|
|
323
|
+
{% endif -%}
|
|
324
|
+
rdfs:comment "{{ description | default('Custom validation rule') }}" ;
|
|
325
|
+
|
|
326
|
+
{% if sparqlConstraint -%}
|
|
327
|
+
sh:sparql [
|
|
328
|
+
sh:message "{{ sparqlMessage | default('SPARQL constraint violation') }}" ;
|
|
329
|
+
sh:prefixes [
|
|
330
|
+
sh:declare [
|
|
331
|
+
sh:prefix "kgen" ;
|
|
332
|
+
sh:namespace "${this.options.namespace}" ;
|
|
333
|
+
] ;
|
|
334
|
+
sh:declare [
|
|
335
|
+
sh:prefix "ex" ;
|
|
336
|
+
sh:namespace "${this.options.baseIRI}" ;
|
|
337
|
+
] ;
|
|
338
|
+
] ;
|
|
339
|
+
sh:select """
|
|
340
|
+
{{ sparqlConstraint }}
|
|
341
|
+
""" ;
|
|
342
|
+
] ;
|
|
343
|
+
{% endif -%}
|
|
344
|
+
.`
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Register a SHACL shape template
|
|
350
|
+
*/
|
|
351
|
+
registerShapeTemplate(name, template) {
|
|
352
|
+
this.shapeTemplates.set(name, {
|
|
353
|
+
...template,
|
|
354
|
+
registered: this.options.staticBuildTime,
|
|
355
|
+
namespace: this.options.namespace
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Generate SHACL shape from template
|
|
361
|
+
*/
|
|
362
|
+
generateShape(templateName, context = {}) {
|
|
363
|
+
const template = this.shapeTemplates.get(templateName);
|
|
364
|
+
if (!template) {
|
|
365
|
+
throw new Error(`SHACL template '${templateName}' not found`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Simple template substitution for SHACL
|
|
369
|
+
let shapeContent = template.template;
|
|
370
|
+
|
|
371
|
+
// Add standard prefixes if not present
|
|
372
|
+
if (!shapeContent.includes('@prefix sh:')) {
|
|
373
|
+
const prefixes = [
|
|
374
|
+
'@prefix sh: <http://www.w3.org/ns/shacl#> .',
|
|
375
|
+
`@prefix kgen: <${this.options.namespace}> .`,
|
|
376
|
+
`@prefix ex: <${this.options.baseIRI}> .`,
|
|
377
|
+
'@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .',
|
|
378
|
+
'@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .',
|
|
379
|
+
''
|
|
380
|
+
].join('\n');
|
|
381
|
+
|
|
382
|
+
shapeContent = prefixes + shapeContent;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Replace template variables
|
|
386
|
+
for (const [key, value] of Object.entries(context)) {
|
|
387
|
+
const regex = new RegExp(`{{\\s*${key}\\s*(?:\\|[^}]*)?\\s*}}`, 'g');
|
|
388
|
+
|
|
389
|
+
const matches = shapeContent.match(regex);
|
|
390
|
+
if (matches) {
|
|
391
|
+
for (const match of matches) {
|
|
392
|
+
const defaultMatch = match.match(/\|\s*default\('([^']*)'\)/);
|
|
393
|
+
const replacement = value !== undefined ? value : (defaultMatch ? defaultMatch[1] : match);
|
|
394
|
+
shapeContent = shapeContent.replace(match, String(replacement));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Process simple loops and conditions
|
|
400
|
+
shapeContent = this.processTemplateLogic(shapeContent, context);
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
name: this.interpolate(template.name, context),
|
|
404
|
+
description: this.interpolate(template.description, context),
|
|
405
|
+
content: shapeContent,
|
|
406
|
+
metadata: {
|
|
407
|
+
template: templateName,
|
|
408
|
+
generated: this.options.staticBuildTime,
|
|
409
|
+
namespace: this.options.namespace,
|
|
410
|
+
context: Object.keys(context)
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Process template logic (loops and conditions)
|
|
417
|
+
*/
|
|
418
|
+
processTemplateLogic(content, context) {
|
|
419
|
+
let result = content;
|
|
420
|
+
|
|
421
|
+
// Process {% for %} loops
|
|
422
|
+
const forLoopRegex = /{%\s*for\s+(\w+)\s+in\s+([\w.\[\]|()]+)\s*%}([\s\S]*?){%\s*endfor\s*%}/g;
|
|
423
|
+
|
|
424
|
+
result = result.replace(forLoopRegex, (match, itemVar, listExpr, loopContent) => {
|
|
425
|
+
const listValue = this.evaluateExpression(listExpr, context);
|
|
426
|
+
if (!Array.isArray(listValue)) {
|
|
427
|
+
return '';
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return listValue.map((item, index) => {
|
|
431
|
+
const loopContext = {
|
|
432
|
+
...context,
|
|
433
|
+
[itemVar]: item,
|
|
434
|
+
loop: {
|
|
435
|
+
index: index,
|
|
436
|
+
first: index === 0,
|
|
437
|
+
last: index === listValue.length - 1
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
return this.interpolateWithContext(loopContent, loopContext);
|
|
442
|
+
}).join('');
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Process {% if %} conditions
|
|
446
|
+
const ifRegex = /{%\s*if\s+([\w.\[\]|()\s!]+)\s*%}([\s\S]*?)(?:{%\s*else\s*%}([\s\S]*?))?{%\s*endif\s*%}/g;
|
|
447
|
+
|
|
448
|
+
result = result.replace(ifRegex, (match, condition, ifContent, elseContent) => {
|
|
449
|
+
const conditionValue = this.evaluateExpression(condition, context);
|
|
450
|
+
return conditionValue ? ifContent : (elseContent || '');
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
return result;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Evaluate simple expressions
|
|
458
|
+
*/
|
|
459
|
+
evaluateExpression(expr, context) {
|
|
460
|
+
// Handle default expressions
|
|
461
|
+
const defaultMatch = expr.match(/(\w+)\s*\|\s*default\('([^']*)'\)/);
|
|
462
|
+
if (defaultMatch) {
|
|
463
|
+
const value = this.getNestedValue(context, defaultMatch[1]);
|
|
464
|
+
return value !== undefined ? value : defaultMatch[2];
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Handle simple property access
|
|
468
|
+
return this.getNestedValue(context, expr.trim());
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Get nested value from object
|
|
473
|
+
*/
|
|
474
|
+
getNestedValue(obj, path) {
|
|
475
|
+
return path.split('.').reduce((current, key) => {
|
|
476
|
+
return current && current[key] !== undefined ? current[key] : undefined;
|
|
477
|
+
}, obj);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Interpolate template with context
|
|
482
|
+
*/
|
|
483
|
+
interpolateWithContext(template, context) {
|
|
484
|
+
let result = template;
|
|
485
|
+
|
|
486
|
+
for (const [key, value] of Object.entries(context)) {
|
|
487
|
+
if (typeof value === 'object' && value !== null) {
|
|
488
|
+
// Handle nested objects
|
|
489
|
+
for (const [nestedKey, nestedValue] of Object.entries(value)) {
|
|
490
|
+
const regex = new RegExp(`{{\\s*${key}\\.${nestedKey}\\s*}}`, 'g');
|
|
491
|
+
result = result.replace(regex, String(nestedValue));
|
|
492
|
+
}
|
|
493
|
+
} else {
|
|
494
|
+
const regex = new RegExp(`{{\\s*${key}\\s*}}`, 'g');
|
|
495
|
+
result = result.replace(regex, String(value || ''));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return result;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Simple template interpolation
|
|
504
|
+
*/
|
|
505
|
+
interpolate(template, context) {
|
|
506
|
+
let result = template;
|
|
507
|
+
|
|
508
|
+
for (const [key, value] of Object.entries(context)) {
|
|
509
|
+
const regex = new RegExp(`{{\\s*${key}\\s*(?:\\|[^}]*)?\\s*}}`, 'g');
|
|
510
|
+
|
|
511
|
+
const matches = result.match(regex);
|
|
512
|
+
if (matches) {
|
|
513
|
+
for (const match of matches) {
|
|
514
|
+
const defaultMatch = match.match(/\|\s*default\('([^']*)'\)/);
|
|
515
|
+
const replacement = value !== undefined ? value : (defaultMatch ? defaultMatch[1] : match);
|
|
516
|
+
result = result.replace(match, replacement);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return result;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Generate complete SHACL validation file
|
|
526
|
+
*/
|
|
527
|
+
generateValidationFile(shapes = [], filename = 'validation.ttl') {
|
|
528
|
+
const header = [
|
|
529
|
+
'# KGEN SHACL Validation Shapes',
|
|
530
|
+
`# Generated: ${this.options.staticBuildTime}`,
|
|
531
|
+
`# Namespace: ${this.options.namespace}`,
|
|
532
|
+
'',
|
|
533
|
+
'@prefix sh: <http://www.w3.org/ns/shacl#> .',
|
|
534
|
+
`@prefix kgen: <${this.options.namespace}> .`,
|
|
535
|
+
`@prefix ex: <${this.options.baseIRI}> .`,
|
|
536
|
+
'@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .',
|
|
537
|
+
'@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .',
|
|
538
|
+
''
|
|
539
|
+
];
|
|
540
|
+
|
|
541
|
+
const generatedShapes = shapes.map(shape =>
|
|
542
|
+
this.generateShape(shape.template, shape.context)
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
const content = [
|
|
546
|
+
...header,
|
|
547
|
+
...generatedShapes.map(shape => [
|
|
548
|
+
`# ${shape.description}`,
|
|
549
|
+
shape.content,
|
|
550
|
+
''
|
|
551
|
+
]).flat()
|
|
552
|
+
];
|
|
553
|
+
|
|
554
|
+
return {
|
|
555
|
+
filename,
|
|
556
|
+
content: content.join('\n'),
|
|
557
|
+
shapes: generatedShapes,
|
|
558
|
+
metadata: {
|
|
559
|
+
shapeCount: generatedShapes.length,
|
|
560
|
+
generated: this.options.staticBuildTime,
|
|
561
|
+
namespace: this.options.namespace
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Get available shape template names
|
|
568
|
+
*/
|
|
569
|
+
getTemplateNames() {
|
|
570
|
+
return Array.from(this.shapeTemplates.keys()).sort();
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Get shape template by name
|
|
575
|
+
*/
|
|
576
|
+
getTemplate(name) {
|
|
577
|
+
return this.shapeTemplates.get(name);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Export SHACL templates
|
|
582
|
+
*/
|
|
583
|
+
exportTemplates(templateNames = []) {
|
|
584
|
+
const templatesToExport = templateNames.length > 0
|
|
585
|
+
? templateNames.filter(name => this.shapeTemplates.has(name))
|
|
586
|
+
: Array.from(this.shapeTemplates.keys());
|
|
587
|
+
|
|
588
|
+
const exported = {
|
|
589
|
+
format: 'kgen-shacl-templates',
|
|
590
|
+
version: '1.0.0',
|
|
591
|
+
generated: this.options.staticBuildTime,
|
|
592
|
+
namespace: this.options.namespace,
|
|
593
|
+
baseIRI: this.options.baseIRI,
|
|
594
|
+
templates: {}
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
for (const name of templatesToExport) {
|
|
598
|
+
exported.templates[name] = this.shapeTemplates.get(name);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return exported;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Get template statistics
|
|
606
|
+
*/
|
|
607
|
+
getStats() {
|
|
608
|
+
return {
|
|
609
|
+
totalTemplates: this.shapeTemplates.size,
|
|
610
|
+
namespace: this.options.namespace,
|
|
611
|
+
baseIRI: this.options.baseIRI,
|
|
612
|
+
templates: Array.from(this.shapeTemplates.keys()).sort()
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
export default KGenSHACLTemplates;
|