• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knowledgepixels / nanodash / 28937277207

08 Jul 2026 10:55AM UTC coverage: 28.314% (+0.2%) from 28.115%
28937277207

push

github

web-flow
Merge pull request #545 from knowledgepixels/feat/template-embedded-identity

feat: identify templates by embedded IRIs with dual-mode parser

1838 of 7333 branches covered (25.06%)

Branch coverage included in aggregate %.

3749 of 12399 relevant lines covered (30.24%)

4.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.8
src/main/java/com/knowledgepixels/nanodash/template/Template.java
1
package com.knowledgepixels.nanodash.template;
2

3
import com.knowledgepixels.nanodash.LookupApis;
4
import com.knowledgepixels.nanodash.Utils;
5
import net.trustyuri.TrustyUriUtils;
6
import org.eclipse.rdf4j.common.exception.RDF4JException;
7
import org.eclipse.rdf4j.model.*;
8
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
9
import org.eclipse.rdf4j.model.util.Literals;
10
import org.eclipse.rdf4j.model.vocabulary.DCTERMS;
11
import org.eclipse.rdf4j.model.vocabulary.RDF;
12
import org.eclipse.rdf4j.model.vocabulary.RDFS;
13
import org.eclipse.rdf4j.model.vocabulary.SHACL;
14
import org.nanopub.Nanopub;
15
import org.nanopub.NanopubUtils;
16
import org.nanopub.vocabulary.NTEMPLATE;
17
import com.knowledgepixels.nanodash.vocabulary.KPXL_TERMS;
18
import org.slf4j.Logger;
19
import org.slf4j.LoggerFactory;
20

21
import java.io.Serializable;
22
import java.util.*;
23

24
/**
25
 * Represents a template for creating nanopublications.
26
 */
27
public class Template implements Serializable {
28

29
    private static final ValueFactory vf = SimpleValueFactory.getInstance();
6✔
30
    private static final Logger logger = LoggerFactory.getLogger(Template.class);
9✔
31

32
    /**
33
     * Default target namespace for templates.
34
     */
35
    public static final String DEFAULT_TARGET_NAMESPACE = "https://w3id.org/np/";
36

37
    // TODO Move this to the other ntemplate vocabulary terms in nanopub-java:
38
    private static final IRI ADVANCED_STATEMENT = vf.createIRI("https://w3id.org/np/o/ntemplate/AdvancedStatement");
15✔
39

40
    private final Nanopub nanopub;
41
    private String label;
42
    private String description;
43

44
    // TODO: Make all these maps more generic and the code simpler:
45
    private IRI templateIri;
46
    private boolean embeddedIdentity = false;
9✔
47
    private IRI templateKindIri;
48
    private Map<IRI, List<IRI>> typeMap = new HashMap<>();
15✔
49
    private Map<IRI, List<Value>> possibleValueMap = new HashMap<>();
15✔
50
    private Map<IRI, List<IRI>> possibleValuesToLoadMap = new HashMap<>();
15✔
51
    private Map<IRI, List<String>> apiMap = new HashMap<>();
15✔
52
    private Map<IRI, String> labelMap = new HashMap<>();
15✔
53
    private Map<IRI, IRI> datatypeMap = new HashMap<>();
15✔
54
    private Map<IRI, String> languageTagMap = new HashMap<>();
15✔
55
    private Map<IRI, String> prefixMap = new HashMap<>();
15✔
56
    private Map<IRI, String> prefixLabelMap = new HashMap<>();
15✔
57
    private Map<IRI, String> regexMap = new HashMap<>();
15✔
58
    private Map<IRI, List<IRI>> statementMap = new HashMap<>();
15✔
59
    private Map<IRI, IRI> statementSubjects = new HashMap<>();
15✔
60
    private Map<IRI, IRI> statementPredicates = new HashMap<>();
15✔
61
    private Map<IRI, Value> statementObjects = new HashMap<>();
15✔
62
    private Map<IRI, Integer> statementOrder = new HashMap<>();
15✔
63
    private IRI defaultProvenance;
64
    private List<IRI> requiredPubInfoElements = new ArrayList<>();
15✔
65
    private String tag = null;
9✔
66
    private Map<IRI, Value> defaultValues = new HashMap<>();
15✔
67
    private String targetNamespace = null;
9✔
68
    private String nanopubLabelPattern;
69
    private List<IRI> targetNanopubTypes = new ArrayList<>();
15✔
70

71
    /**
72
     * Creates a Template object from a template id.
73
     *
74
     * @param templateId the id of the template, which is the URI of a nanopublication that contains the template definition.
75
     * @throws RDF4JException             if there is an error retrieving the nanopublication.
76
     * @throws MalformedTemplateException if the template is malformed or not a valid nanopub template.
77
     */
78
    Template(String templateId) throws RDF4JException, MalformedTemplateException {
79
        this(Utils.getNanopub(templateId));
12✔
80
    }
3✔
81

82
    /**
83
     * Creates a Template object from a Nanopub object.
84
     *
85
     * @param np the Nanopub object containing the template definition.
86
     * @throws MalformedTemplateException if the template is malformed or not a valid nanopub template.
87
     */
88
    Template(Nanopub np) throws MalformedTemplateException {
6✔
89
        nanopub = np;
9✔
90
        processTemplate(nanopub);
12✔
91
    }
3✔
92

93
    /**
94
     * Checks if the template is unlisted.
95
     *
96
     * @return true if the template is unlisted, false otherwise.
97
     */
98
    public boolean isUnlisted() {
99
        List<IRI> types = typeMap.get(templateIri);
21✔
100
        return types != null && types.contains(NTEMPLATE.UNLISTED_TEMPLATE);
30!
101
    }
102

103
    /**
104
     * Returns the Nanopub object representing the template.
105
     *
106
     * @return the Nanopub object of the template.
107
     */
108
    public Nanopub getNanopub() {
109
        return nanopub;
9✔
110
    }
111

112
    /**
113
     * Returns the ID of the template: for a template with embedded identity (the
114
     * template node is a regular embedded IRI in the assertion), that embedded IRI;
115
     * for a legacy template (the template node is the assertion graph URI), the URI
116
     * of the nanopublication.
117
     *
118
     * @return the ID of the template as a string.
119
     */
120
    public String getId() {
121
        if (embeddedIdentity) return templateIri.stringValue();
21✔
122
        return nanopub.getUri().toString();
15✔
123
    }
124

125
    /**
126
     * Checks whether the given ID refers to this template, in either form: the
127
     * template's canonical ID ({@link #getId()}) or its nanopublication URI. Use
128
     * this instead of comparing against {@link #getId()} when the ID to compare
129
     * comes from outside (page parameters, published references), as those may
130
     * carry either form.
131
     *
132
     * @param id the ID to check
133
     * @return true if the ID refers to this template
134
     */
135
    public boolean hasId(String id) {
136
        return id != null && (id.equals(getId()) || id.equals(nanopub.getUri().stringValue()));
54!
137
    }
138

139
    /**
140
     * Returns the template kind IRI, i.e. the stable identifier this template
141
     * version declares itself a version of ({@code dct:isVersionOf} on the template
142
     * node), or null if it doesn't declare one.
143
     *
144
     * @return the template kind IRI, or null
145
     */
146
    public IRI getTemplateKindIri() {
147
        return templateKindIri;
9✔
148
    }
149

150
    /**
151
     * Returns the label of the template.
152
     *
153
     * @return the label of the template, or a default label if not set.
154
     */
155
    public String getLabel() {
156
        if (label == null) {
9!
157
            return "Template " + TrustyUriUtils.getArtifactCode(nanopub.getUri().stringValue()).substring(0, 10);
×
158
        }
159
        return label;
9✔
160
    }
161

162
    /**
163
     * Returns the description of the template.
164
     *
165
     * @return the description of the template, or an empty string if not set.
166
     */
167
    public String getDescription() {
168
        return description;
9✔
169
    }
170

171
    /**
172
     * Returns the IRI of the template.
173
     *
174
     * @param iri the IRI to transform.
175
     * @return the transformed IRI, or the original IRI if no transformation is needed.
176
     */
177
    public String getLabel(IRI iri) {
178
        iri = transform(iri);
12✔
179
        return labelMap.get(iri);
18✔
180
    }
181

182
    /**
183
     * Returns the IRI of the template, transforming it if necessary.
184
     *
185
     * @param iri the IRI to transform.
186
     * @return the transformed IRI, or the original IRI if no transformation is needed.
187
     */
188
    public IRI getFirstOccurrence(IRI iri) {
189
        iri = transform(iri);
12✔
190
        for (IRI i : getStatementIris()) {
33!
191
            if (statementMap.containsKey(i)) {
15✔
192
                // grouped statement
193
                for (IRI g : getStatementIris(i)) {
36✔
194
                    if (iri.equals(statementSubjects.get(g))) return g;
21!
195
                    if (iri.equals(statementPredicates.get(g))) return g;
27✔
196
                    if (iri.equals(statementObjects.get(g))) return g;
27✔
197
                }
6✔
198
            } else {
199
                // non-grouped statement
200
                if (iri.equals(statementSubjects.get(i))) return i;
27✔
201
                if (iri.equals(statementPredicates.get(i))) return i;
27✔
202
                if (iri.equals(statementObjects.get(i))) return i;
27✔
203
            }
204
        }
3✔
205
        return null;
×
206
    }
207

208
    /**
209
     * Returns the datatype for the given literal placeholder IRI.
210
     *
211
     * @param iri the literal placeholder IRI.
212
     * @return the datatype for the literal.
213
     */
214
    public IRI getDatatype(IRI iri) {
215
        iri = transform(iri);
12✔
216
        return datatypeMap.get(iri);
18✔
217
    }
218

219
    /**
220
     * Returns the language tag for the given literal placeholder IRI.
221
     *
222
     * @param iri the literal placeholder IRI.
223
     * @return the language tag for the literal.
224
     */
225
    public String getLanguageTag(IRI iri) {
226
        iri = transform(iri);
12✔
227
        return languageTagMap.get(iri);
18✔
228
    }
229

230
    /**
231
     * Returns the prefix for the given IRI.
232
     *
233
     * @param iri the IRI.
234
     * @return the prefix for the IRI.
235
     */
236
    public String getPrefix(IRI iri) {
237
        iri = transform(iri);
12✔
238
        return prefixMap.get(iri);
18✔
239
    }
240

241
    /**
242
     * Returns the prefix label for a given IRI.
243
     *
244
     * @param iri the IRI to get the prefix label for.
245
     * @return the prefix label for the IRI, or null if not found.
246
     */
247
    public String getPrefixLabel(IRI iri) {
248
        iri = transform(iri);
12✔
249
        return prefixLabelMap.get(iri);
18✔
250
    }
251

252
    /**
253
     * Returns the regex pattern for a given IRI.
254
     *
255
     * @param iri the IRI to get the regex for.
256
     * @return the regex pattern for the IRI, or null if not found.
257
     */
258
    public String getRegex(IRI iri) {
259
        iri = transform(iri);
12✔
260
        return regexMap.get(iri);
18✔
261
    }
262

263
    /**
264
     * Transforms an IRI by removing the artifact code if it is present.
265
     *
266
     * @param iri the IRI to transform.
267
     * @return the transformed IRI, or the original IRI if no transformation is needed.
268
     */
269
    public Value getDefault(IRI iri) {
270
        if (iri.stringValue().matches(".*__[0-9]+")) {
15✔
271
            String baseIri = iri.stringValue().replaceFirst("__[0-9]+$", "");
18✔
272
            Value v = defaultValues.get(vf.createIRI(baseIri));
24✔
273
            if (v instanceof IRI vIri) {
9!
274
                // Append repeat suffix only for local URIs; fixed URIs stay unchanged
275
                if (vIri.stringValue().startsWith(nanopub.getUri().stringValue())) {
×
276
                    int repeatSuffix = Integer.parseInt(iri.stringValue().replaceFirst("^.*__([0-9]+)$", "$1"));
×
277
                    return vf.createIRI(vIri.stringValue() + (repeatSuffix + 1));
×
278
                }
279
                return vIri;
×
280
            }
281
        }
282
        iri = transform(iri);
12✔
283
        return defaultValues.get(iri);
18✔
284
    }
285

286
    /**
287
     * Returns the statement IRIs associated with the template.
288
     *
289
     * @return the list of statement IRIs for the template.
290
     */
291
    public List<IRI> getStatementIris() {
292
        return statementMap.get(templateIri);
21✔
293
    }
294

295
    /**
296
     * Returns the statement IRIs associated with a specific group IRI.
297
     *
298
     * @param groupIri the IRI of the group for which to retrieve statement IRIs.
299
     * @return the list of statement IRIs for the specified group IRI, or null if no statements are associated with that group.
300
     */
301
    public List<IRI> getStatementIris(IRI groupIri) {
302
        return statementMap.get(groupIri);
18✔
303
    }
304

305
    /**
306
     * Returns the subject, predicate, and object of a statement given its IRI.
307
     *
308
     * @param statementIri the IRI of the statement to retrieve.
309
     * @return the subject, predicate, and object of the statement as a triple.
310
     */
311
    public IRI getSubject(IRI statementIri) {
312
        return statementSubjects.get(statementIri);
18✔
313
    }
314

315
    /**
316
     * Returns the predicate of a statement given its IRI.
317
     *
318
     * @param statementIri the IRI of the statement to retrieve.
319
     * @return the predicate of the statement, or null if not found.
320
     */
321
    public IRI getPredicate(IRI statementIri) {
322
        return statementPredicates.get(statementIri);
18✔
323
    }
324

325
    /**
326
     * Returns the object of a statement given its IRI.
327
     *
328
     * @param statementIri the IRI of the statement to retrieve.
329
     * @return the object of the statement, or null if not found.
330
     */
331
    public Value getObject(IRI statementIri) {
332
        return statementObjects.get(statementIri);
18✔
333
    }
334

335
    /**
336
     * Checks if the template is a local resource.
337
     *
338
     * @param iri the IRI to check.
339
     * @return true if the IRI is a local resource, false otherwise.
340
     */
341
    public boolean isLocalResource(IRI iri) {
342
        iri = transform(iri);
12✔
343
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(NTEMPLATE.LOCAL_RESOURCE);
51✔
344
    }
345

346
    /**
347
     * Checks if the template is an introduced resource.
348
     *
349
     * @param iri the IRI to check.
350
     * @return true if the IRI is an introduced resource, false otherwise.
351
     */
352
    public boolean isIntroducedResource(IRI iri) {
353
        iri = transform(iri);
12✔
354
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(NTEMPLATE.INTRODUCED_RESOURCE);
51✔
355
    }
356

357
    /**
358
     * Returns the role-instantiation direction pin declared on a (predicate) IRI, if
359
     * any: {@link KPXL_TERMS#INVERSE_ROLE_PROPERTY} or
360
     * {@link KPXL_TERMS#REGULAR_ROLE_PROPERTY}. Templates declare the pin as an
361
     * {@code rdf:type} on the predicate (constant or placeholder); nanodash lifts it into
362
     * pubinfo at publish time so nanopub-query can resolve a custom role predicate's
363
     * direction (see #525).
364
     *
365
     * @param iri the IRI to check (a predicate constant or placeholder).
366
     * @return the pin class, or null if the IRI carries no role-direction pin.
367
     */
368
    public IRI getRoleDirectionPin(IRI iri) {
369
        iri = transform(iri);
12✔
370
        if (!typeMap.containsKey(iri)) return null;
21✔
371
        List<IRI> types = typeMap.get(iri);
18✔
372
        if (types.contains(KPXL_TERMS.INVERSE_ROLE_PROPERTY)) return KPXL_TERMS.INVERSE_ROLE_PROPERTY;
12!
373
        if (types.contains(KPXL_TERMS.REGULAR_ROLE_PROPERTY)) return KPXL_TERMS.REGULAR_ROLE_PROPERTY;
12!
374
        return null;
6✔
375
    }
376

377
    /**
378
     * Checks if the template is an embedded resource.
379
     *
380
     * @param iri the IRI to check.
381
     * @return true if the IRI is an embedded resource, false otherwise.
382
     */
383
    public boolean isEmbeddedResource(IRI iri) {
384
        iri = transform(iri);
12✔
385
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(NTEMPLATE.EMBEDDED_RESOURCE);
45!
386
    }
387

388
    /**
389
     * Checks if the IRI is a value placeholder.
390
     *
391
     * @param iri the IRI to check.
392
     * @return true if the IRI is a value placeholder, false otherwise.
393
     */
394
    public boolean isValuePlaceholder(IRI iri) {
395
        iri = transform(iri);
12✔
396
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(NTEMPLATE.VALUE_PLACEHOLDER);
21!
397
    }
398

399
    /**
400
     * Checks if the IRI is a URI placeholder.
401
     *
402
     * @param iri the IRI to check.
403
     * @return true if the IRI is a URI placeholder, false otherwise.
404
     */
405
    public boolean isUriPlaceholder(IRI iri) {
406
        iri = transform(iri);
12✔
407
        if (!typeMap.containsKey(iri)) return false;
21✔
408
        for (IRI t : typeMap.get(iri)) {
42✔
409
            if (t.equals(NTEMPLATE.URI_PLACEHOLDER)) return true;
18✔
410
            if (t.equals(NTEMPLATE.EXTERNAL_URI_PLACEHOLDER)) return true;
18✔
411
            if (t.equals(NTEMPLATE.TRUSTY_URI_PLACEHOLDER)) return true;
18✔
412
            if (t.equals(NTEMPLATE.AUTO_ESCAPE_URI_PLACEHOLDER)) return true;
12!
413
            if (t.equals(NTEMPLATE.RESTRICTED_CHOICE_PLACEHOLDER)) return true;
12!
414
            if (t.equals(NTEMPLATE.GUIDED_CHOICE_PLACEHOLDER)) return true;
18✔
415
            if (t.equals(NTEMPLATE.AGENT_PLACEHOLDER)) return true;
12!
416
        }
3✔
417
        return false;
6✔
418
    }
419

420
    /**
421
     * Checks if the IRI is an external URI placeholder.
422
     *
423
     * @param iri the IRI to check.
424
     * @return true if the IRI is an external URI placeholder, false otherwise.
425
     */
426
    public boolean isExternalUriPlaceholder(IRI iri) {
427
        iri = transform(iri);
12✔
428
        if (!typeMap.containsKey(iri)) return false;
15!
429
        for (IRI t : typeMap.get(iri)) {
42✔
430
            if (t.equals(NTEMPLATE.EXTERNAL_URI_PLACEHOLDER)) return true;
18✔
431
            if (t.equals(NTEMPLATE.TRUSTY_URI_PLACEHOLDER)) return true;
12!
432
        }
3✔
433
        return false;
6✔
434
    }
435

436
    /**
437
     * Checks if the IRI is a trusty URI placeholder.
438
     *
439
     * @param iri the IRI to check.
440
     * @return true if the IRI is a trusty URI placeholder, false otherwise.
441
     */
442
    public boolean isTrustyUriPlaceholder(IRI iri) {
443
        iri = transform(iri);
12✔
444
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(NTEMPLATE.TRUSTY_URI_PLACEHOLDER);
45!
445
    }
446

447
    /**
448
     * Checks if the IRI is an auto-escape URI placeholder.
449
     *
450
     * @param iri the IRI to check.
451
     * @return true if the IRI is an auto-escape URI placeholder, false otherwise.
452
     */
453
    public boolean isAutoEscapePlaceholder(IRI iri) {
454
        iri = transform(iri);
12✔
455
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(NTEMPLATE.AUTO_ESCAPE_URI_PLACEHOLDER);
45!
456
    }
457

458
    /**
459
     * Checks if the IRI is a literal placeholder.
460
     *
461
     * @param iri the IRI to check.
462
     * @return true if the IRI is a literal placeholder, false otherwise.
463
     */
464
    public boolean isLiteralPlaceholder(IRI iri) {
465
        iri = transform(iri);
12✔
466
        return typeMap.containsKey(iri) && (typeMap.get(iri).contains(NTEMPLATE.LITERAL_PLACEHOLDER) || typeMap.get(iri).contains(NTEMPLATE.LONG_LITERAL_PLACEHOLDER));
75✔
467
    }
468

469
    /**
470
     * Checks if the IRI is a long literal placeholder.
471
     *
472
     * @param iri the IRI to check.
473
     * @return true if the IRI is a long literal placeholder, false otherwise.
474
     */
475
    public boolean isLongLiteralPlaceholder(IRI iri) {
476
        iri = transform(iri);
12✔
477
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(NTEMPLATE.LONG_LITERAL_PLACEHOLDER);
51✔
478
    }
479

480
    /**
481
     * Checks if the IRI is a restricted choice placeholder.
482
     *
483
     * @param iri the IRI to check.
484
     * @return true if the IRI is a restricted choice placeholder, false otherwise.
485
     */
486
    public boolean isRestrictedChoicePlaceholder(IRI iri) {
487
        iri = transform(iri);
12✔
488
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(NTEMPLATE.RESTRICTED_CHOICE_PLACEHOLDER);
51✔
489
    }
490

491
    /**
492
     * Checks if the IRI is a guided choice placeholder.
493
     *
494
     * @param iri the IRI to check.
495
     * @return true if the IRI is a guided choice placeholder, false otherwise.
496
     */
497
    public boolean isGuidedChoicePlaceholder(IRI iri) {
498
        iri = transform(iri);
12✔
499
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(NTEMPLATE.GUIDED_CHOICE_PLACEHOLDER);
51✔
500
    }
501

502
    /**
503
     * Checks if the IRI is an agent placeholder.
504
     *
505
     * @param iri the IRI to check.
506
     * @return true if the IRI is an agent placeholder, false otherwise.
507
     */
508
    public boolean isAgentPlaceholder(IRI iri) {
509
        iri = transform(iri);
12✔
510
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(NTEMPLATE.AGENT_PLACEHOLDER);
51✔
511
    }
512

513
    /**
514
     * Checks if the IRI is a sequence element placeholder.
515
     *
516
     * @param iri the IRI to check.
517
     * @return true if the IRI is a sequence element placeholder, false otherwise.
518
     */
519
    public boolean isSequenceElementPlaceholder(IRI iri) {
520
        iri = transform(iri);
12✔
521
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(NTEMPLATE.SEQUENCE_ELEMENT_PLACEHOLDER);
45!
522
    }
523

524
    /**
525
     * Checks if the IRI is a root nanopub placeholder.
526
     *
527
     * @param iri the IRI to check.
528
     * @return true if the IRI is a root nanopub placeholder, false otherwise.
529
     */
530
    public boolean isRootNanopubPlaceholder(IRI iri) {
531
        iri = transform(iri);
12✔
532
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(NTEMPLATE.ROOT_NANOPUB_PLACEHOLDER);
51✔
533
    }
534

535
    /**
536
     * Checks if the IRI is a placeholder of any type.
537
     *
538
     * @param iri the IRI to check.
539
     * @return true if the IRI is a placeholder, false otherwise.
540
     */
541
    public boolean isPlaceholder(IRI iri) {
542
        iri = transform(iri);
12✔
543
        if (!typeMap.containsKey(iri)) return false;
21✔
544
        for (IRI t : typeMap.get(iri)) {
42✔
545
            if (t.equals(NTEMPLATE.VALUE_PLACEHOLDER)) return true;
18✔
546
            if (t.equals(NTEMPLATE.URI_PLACEHOLDER)) return true;
18✔
547
            if (t.equals(NTEMPLATE.EXTERNAL_URI_PLACEHOLDER)) return true;
18✔
548
            if (t.equals(NTEMPLATE.TRUSTY_URI_PLACEHOLDER)) return true;
18✔
549
            if (t.equals(NTEMPLATE.AUTO_ESCAPE_URI_PLACEHOLDER)) return true;
12!
550
            if (t.equals(NTEMPLATE.RESTRICTED_CHOICE_PLACEHOLDER)) return true;
18✔
551
            if (t.equals(NTEMPLATE.GUIDED_CHOICE_PLACEHOLDER)) return true;
18✔
552
            if (t.equals(NTEMPLATE.AGENT_PLACEHOLDER)) return true;
18✔
553
            if (t.equals(NTEMPLATE.LITERAL_PLACEHOLDER)) return true;
18✔
554
            if (t.equals(NTEMPLATE.LONG_LITERAL_PLACEHOLDER)) return true;
18✔
555
            if (t.equals(NTEMPLATE.SEQUENCE_ELEMENT_PLACEHOLDER)) return true;
12!
556
            if (t.equals(NTEMPLATE.ROOT_NANOPUB_PLACEHOLDER)) return true;
18✔
557
        }
3✔
558
        return false;
6✔
559
    }
560

561
    /**
562
     * Checks if the IRI is an optional statement.
563
     *
564
     * @param iri the IRI to check.
565
     * @return true if the IRI is an optional statement, false otherwise.
566
     */
567
    public boolean isOptionalStatement(IRI iri) {
568
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(NTEMPLATE.OPTIONAL_STATEMENT);
51✔
569
    }
570

571
    /**
572
     * Whether the placeholder with the given parameter name is a required field of
573
     * this template: it appears (as subject or object) in at least one non-optional
574
     * statement — i.e. a value must be provided to publish. A repetition-index
575
     * suffix on the name (e.g. {@code public-key__.1}) is ignored, matching the base
576
     * placeholder. Used to decide whether an empty mapped value should hide a view
577
     * action's button (see docs/magic-query-params.md).
578
     *
579
     * @param paramName the parameter name (placeholder local name, optionally with a
580
     *                  {@code __.N} repetition suffix)
581
     * @return true if the placeholder is required (appears in a non-optional statement)
582
     */
583
    public boolean isRequiredField(String paramName) {
584
        if (paramName == null) return false;
×
585
        String base = paramName.replaceFirst("__\\..*$", "");
×
586
        for (IRI top : getStatementIris()) {
×
587
            List<IRI> members = getStatementIris(top);
×
588
            if (members != null) {
×
589
                if (isOptionalStatement(top)) continue; // whole group optional
×
590
                for (IRI st : members) {
×
591
                    if (!isOptionalStatement(st) && statementReferencesPlaceholder(st, base)) return true;
×
592
                }
×
593
            } else if (!isOptionalStatement(top) && statementReferencesPlaceholder(top, base)) {
×
594
                return true;
×
595
            }
596
        }
×
597
        return false;
×
598
    }
599

600
    private boolean statementReferencesPlaceholder(IRI statementIri, String localName) {
601
        return matchesPlaceholderLocalName(getSubject(statementIri), localName)
×
602
                || matchesPlaceholderLocalName(getObject(statementIri), localName);
×
603
    }
604

605
    private boolean matchesPlaceholderLocalName(Value value, String localName) {
606
        if (!(value instanceof IRI iri) || !isPlaceholder(iri)) return false;
×
607
        String s = iri.stringValue();
×
608
        int i = Math.max(s.lastIndexOf('/'), s.lastIndexOf('#'));
×
609
        return localName.equals(i < 0 ? s : s.substring(i + 1));
×
610
    }
611

612
    /**
613
     * Checks if the IRI is an advanced statement, which are only show upon expanding to full view in form mode.
614
     *
615
     * @param iri the IRI to check.
616
     * @return true if the IRI is an advanced statement, false otherwise.
617
     */
618
    public boolean isAdvancedStatement(IRI iri) {
619
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(ADVANCED_STATEMENT);
×
620
    }
621

622
    /**
623
     * Checks if the IRI is a grouped statement.
624
     *
625
     * @param iri the IRI to check.
626
     * @return true if the IRI is a grouped statement, false otherwise.
627
     */
628
    public boolean isGroupedStatement(IRI iri) {
629
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(NTEMPLATE.GROUPED_STATEMENT);
51✔
630
    }
631

632
    /**
633
     * Checks if the IRI is a repeatable statement.
634
     *
635
     * @param iri the IRI to check.
636
     * @return true if the IRI is a repeatable statement, false otherwise.
637
     */
638
    public boolean isRepeatableStatement(IRI iri) {
639
        return typeMap.containsKey(iri) && typeMap.get(iri).contains(NTEMPLATE.REPEATABLE_STATEMENT);
51✔
640
    }
641

642
    /**
643
     * Returns the possible values for a given IRI.
644
     *
645
     * @param iri the IRI for which to get possible values.
646
     * @return a list of possible values for the IRI. If no values are found, an empty list is returned.
647
     */
648
    public List<Value> getPossibleValues(IRI iri) {
649
        iri = transform(iri);
12✔
650
        List<Value> l = possibleValueMap.get(iri);
18✔
651
        if (l == null) {
6✔
652
            l = new ArrayList<>();
12✔
653
            possibleValueMap.put(iri, l);
18✔
654
        }
655
        List<IRI> nanopubList = possibleValuesToLoadMap.get(iri);
18✔
656
        if (nanopubList != null) {
6!
657
            for (IRI npIri : new ArrayList<>(nanopubList)) {
×
658
                try {
659
                    Nanopub valuesNanopub = Utils.getNanopub(npIri.stringValue());
×
660
                    for (Statement st : valuesNanopub.getAssertion()) {
×
661
                        if (st.getPredicate().equals(RDFS.LABEL)) {
×
662
                            l.add((IRI) st.getSubject());
×
663
                        }
664
                    }
×
665
                    nanopubList.remove(npIri);
×
666
                } catch (Exception ex) {
×
667
                    logger.error("Could not load possible values from nanopub {}", npIri.stringValue(), ex);
×
668
                }
×
669
            }
×
670
        }
671
        return l;
6✔
672
    }
673

674
    /**
675
     * Returns the IRI of the default provenance for the template.
676
     *
677
     * @return the IRI of the default provenance, or null if not set.
678
     */
679
    public IRI getDefaultProvenance() {
680
        return defaultProvenance;
×
681
    }
682

683
    /**
684
     * Returns the target namespace for the template.
685
     *
686
     * @return the target namespace as a string, or null if not set.
687
     */
688
    public String getTargetNamespace() {
689
        return targetNamespace;
×
690
    }
691

692
    /**
693
     * Returns the nanopub label pattern.
694
     *
695
     * @return the nanopub label pattern as a string, or null if not set.
696
     */
697
    public String getNanopubLabelPattern() {
698
        return nanopubLabelPattern;
9✔
699
    }
700

701
    /**
702
     * Returns the list of target nanopub types.
703
     *
704
     * @return a list of IRI objects representing the target nanopub types.
705
     */
706
    public List<IRI> getTargetNanopubTypes() {
707
        return targetNanopubTypes;
×
708
    }
709

710
    /**
711
     * Returns the list of the required pubInfo elements for the template.
712
     *
713
     * @return a list of IRI objects representing the required pubInfo elements.
714
     */
715
    public List<IRI> getRequiredPubInfoElements() {
716
        return requiredPubInfoElements;
×
717
    }
718

719
    /**
720
     * Returns the possible values from an API for a given IRI and search term.
721
     *
722
     * @param iri        the IRI for which to get possible values from the API.
723
     * @param searchTerm the search term to filter the possible values.
724
     * @param labelMap   a map to store labels for the possible values.
725
     * @return a list of possible values from the API, filtered by the search term.
726
     */
727
    public List<String> getPossibleValuesFromApi(IRI iri, String searchTerm, Map<String, String> labelMap) {
728
        iri = transform(iri);
12✔
729
        List<String> values = new ArrayList<>();
12✔
730
        List<String> apiList = apiMap.get(iri);
18✔
731
        if (apiList != null) {
6!
732
            for (String apiString : apiList) {
30✔
733
                LookupApis.getPossibleValues(apiString, searchTerm, labelMap, values);
15✔
734
            }
3✔
735
        }
736
        return values;
6✔
737
    }
738

739
    /**
740
     * Returns the tag associated with the template.
741
     *
742
     * @return the tag as a string, or null if not set.
743
     */
744
    public String getTag() {
745
        return tag;
9✔
746
    }
747

748
    private void processTemplate(Nanopub templateNp) throws MalformedTemplateException {
749
        IRI assertionUri = templateNp.getAssertionUri();
9✔
750
        Set<IRI> templateNodes = new LinkedHashSet<>();
12✔
751
        for (Statement st : templateNp.getAssertion()) {
33✔
752
            if (!st.getPredicate().equals(RDF.TYPE)) continue;
18✔
753
            if (!(st.getSubject() instanceof IRI subjIri)) continue;
27!
754
            if (st.getObject().equals(NTEMPLATE.ASSERTION_TEMPLATE) || st.getObject().equals(NTEMPLATE.PROVENANCE_TEMPLATE) || st.getObject().equals(NTEMPLATE.PUBINFO_TEMPLATE)) {
45✔
755
                templateNodes.add(subjIri);
12✔
756
            }
757
        }
3✔
758

759
        if (templateNodes.contains(assertionUri)) {
12✔
760
            // Legacy shape (template node = assertion graph URI) takes precedence
761
            // over any other typed node, so every template that parsed before
762
            // embedded identity existed keeps parsing identically.
763
            templateIri = assertionUri;
9✔
764
            processNpTemplate(templateNp);
12✔
765
        } else if (templateNodes.size() == 1) {
12✔
766
            IRI node = templateNodes.iterator().next();
15✔
767
            if (!node.stringValue().startsWith(templateNp.getUri().stringValue())) {
21✔
768
                throw new MalformedTemplateException("Embedded template node must be in the nanopub's namespace: " + node);
21✔
769
            }
770
            templateIri = node;
9✔
771
            embeddedIdentity = true;
9✔
772
            processNpTemplate(templateNp);
9✔
773
        } else if (templateNodes.size() > 1) {
15✔
774
            throw new MalformedTemplateException("Multiple embedded template nodes found");
15✔
775
        } else {
776
            // Experimental SHACL-based template:
777
            processShaclTemplate(templateNp);
9✔
778
        }
779
    }
3✔
780

781
    private void processNpTemplate(Nanopub templateNp) {
782
        for (Statement st : templateNp.getAssertion()) {
33✔
783
            final IRI subj = (IRI) st.getSubject();
12✔
784
            final IRI pred = st.getPredicate();
9✔
785
            final Value obj = st.getObject();
9✔
786
            final String objS = obj.stringValue();
9✔
787

788
            if (subj.equals(templateIri)) {
15✔
789
                if (pred.equals(RDFS.LABEL)) {
12✔
790
                    label = objS;
12✔
791
                } else if (pred.equals(DCTERMS.DESCRIPTION)) {
12✔
792
                    description = Utils.sanitizeHtml(objS);
15✔
793
                } else if (obj instanceof IRI objIri) {
18✔
794
                    if (pred.equals(NTEMPLATE.HAS_DEFAULT_PROVENANCE)) {
12✔
795
                        defaultProvenance = objIri;
12✔
796
                    } else if (pred.equals(NTEMPLATE.HAS_REQUIRED_PUBINFO_ELEMENT)) {
12✔
797
                        requiredPubInfoElements.add(objIri);
18✔
798
                    } else if (pred.equals(NTEMPLATE.HAS_TARGET_NAMESPACE)) {
12✔
799
                        targetNamespace = objS;
12✔
800
                    } else if (pred.equals(NTEMPLATE.HAS_TARGET_NANOPUB_TYPE)) {
12✔
801
                        targetNanopubTypes.add(objIri);
18✔
802
                    } else if (pred.equals(DCTERMS.IS_VERSION_OF)) {
12✔
803
                        templateKindIri = objIri;
12✔
804
                    }
805
                } else if (obj instanceof Literal) {
9!
806
                    if (pred.equals(NTEMPLATE.HAS_TAG)) {
12✔
807
                        // TODO This should be replaced at some point with a more sophisticated mechanism based on classes.
808
                        // We are assuming that there is at most one tag.
809
                        this.tag = objS;
12✔
810
                    } else if (pred.equals(NTEMPLATE.HAS_NANOPUB_LABEL_PATTERN)) {
12!
811
                        nanopubLabelPattern = objS;
9✔
812
                    }
813
                }
814
            }
815
            if (pred.equals(RDF.TYPE) && obj instanceof IRI objIri) {
30!
816
                addType(subj, objIri);
15✔
817
            } else if (pred.equals(NTEMPLATE.HAS_STATEMENT) && obj instanceof IRI objIri) {
30!
818
                List<IRI> l = statementMap.get(subj);
18✔
819
                if (l == null) {
6✔
820
                    l = new ArrayList<>();
12✔
821
                    statementMap.put(subj, l);
18✔
822
                }
823
                l.add((IRI) objIri);
12✔
824
            } else if (pred.equals(NTEMPLATE.POSSIBLE_VALUE)) {
15✔
825
                List<Value> l = possibleValueMap.get(subj);
18✔
826
                if (l == null) {
6✔
827
                    l = new ArrayList<>();
12✔
828
                    possibleValueMap.put(subj, l);
18✔
829
                }
830
                l.add(obj);
12✔
831
            } else if (pred.equals(NTEMPLATE.POSSIBLE_VALUES_FROM)) {
15✔
832
                List<IRI> l = possibleValuesToLoadMap.get(subj);
18✔
833
                if (l == null) {
6!
834
                    l = new ArrayList<>();
12✔
835
                    possibleValuesToLoadMap.put(subj, l);
18✔
836
                }
837
                if (obj instanceof IRI objIri) {
18!
838
                    l.add(objIri);
12✔
839
                    Nanopub valuesNanopub = Utils.getNanopub(objS);
9✔
840
                    for (Statement s : valuesNanopub.getAssertion()) {
33✔
841
                        if (s.getPredicate().equals(RDFS.LABEL)) {
15!
842
                            labelMap.put((IRI) s.getSubject(), s.getObject().stringValue());
30✔
843
                        }
844
                    }
3✔
845
                }
846
            } else if (pred.equals(NTEMPLATE.POSSIBLE_VALUES_FROM_API)) {
15✔
847
                List<String> l = apiMap.get(subj);
18✔
848
                if (l == null) {
6✔
849
                    l = new ArrayList<>();
12✔
850
                    apiMap.put(subj, l);
18✔
851
                }
852
                if (obj instanceof Literal) {
9!
853
                    l.add(objS);
12✔
854
                }
855
            } else if (pred.equals(RDFS.LABEL) && obj instanceof Literal) {
24!
856
                labelMap.put(subj, objS);
21✔
857
            } else if (pred.equals(NTEMPLATE.HAS_DATATYPE) && obj instanceof IRI objIri) {
30!
858
                datatypeMap.put(subj, objIri);
21✔
859
            } else if (pred.equals(NTEMPLATE.HAS_LANGUAGE_TAG) && obj instanceof Literal) {
21!
860
                languageTagMap.put(subj, Literals.normalizeLanguageTag(objS));
24✔
861
            } else if (pred.equals(NTEMPLATE.HAS_PREFIX) && obj instanceof Literal) {
21!
862
                prefixMap.put(subj, objS);
21✔
863
            } else if (pred.equals(NTEMPLATE.HAS_PREFIX_LABEL) && obj instanceof Literal) {
21!
864
                prefixLabelMap.put(subj, objS);
21✔
865
            } else if (pred.equals(NTEMPLATE.HAS_REGEX) && obj instanceof Literal) {
21!
866
                regexMap.put(subj, objS);
21✔
867
            } else if (pred.equals(RDF.SUBJECT) && obj instanceof IRI objIri) {
30!
868
                statementSubjects.put(subj, objIri);
21✔
869
            } else if (pred.equals(RDF.PREDICATE) && obj instanceof IRI objIri) {
30!
870
                statementPredicates.put(subj, objIri);
21✔
871
            } else if (pred.equals(RDF.OBJECT)) {
12✔
872
                statementObjects.put(subj, obj);
21✔
873
            } else if (pred.equals(NTEMPLATE.HAS_DEFAULT_VALUE)) {
12✔
874
                defaultValues.put(subj, obj);
21✔
875
            } else if (pred.equals(NTEMPLATE.STATEMENT_ORDER)) {
12✔
876
                if (obj instanceof Literal && objS.matches("[0-9]+")) {
21!
877
                    statementOrder.put(subj, Integer.valueOf(objS));
21✔
878
                }
879
            }
880
        }
3✔
881
//                List<IRI> assertionTypes = typeMap.get(templateIri);
882
//                if (assertionTypes == null || (!assertionTypes.contains(NTEMPLATE.ASSERTION_TEMPLATE) &&
883
//                                !assertionTypes.contains(NTEMPLATE.PROVENANCE_TEMPLATE) && !assertionTypes.contains(PUBINFO_TEMPLATE))) {
884
//                        throw new MalformedTemplateException("Unknown template type");
885
//                }
886
        // Grouped IRI are added via group, so direct link from template is redundant:
887
        for (IRI iri : typeMap.keySet()) {
36✔
888
            if (!typeMap.get(iri).contains(NTEMPLATE.GROUPED_STATEMENT)) continue;
27✔
889
            for (IRI groupedIri : getStatementIris(iri)) {
36✔
890
                statementMap.get(templateIri).remove(groupedIri);
27✔
891
            }
3✔
892
        }
3✔
893
        for (List<IRI> l : statementMap.values()) {
36✔
894
            l.sort(statementComparator);
12✔
895
        }
3✔
896
    }
3✔
897

898
    private void processShaclTemplate(Nanopub templateNp) throws MalformedTemplateException {
899
        templateIri = null;
9✔
900
        for (Statement st : templateNp.getAssertion()) {
33!
901
            if (st.getPredicate().equals(SHACL.TARGET_CLASS)) {
15✔
902
                templateIri = (IRI) st.getSubject();
15✔
903
                break;
3✔
904
            }
905
        }
3✔
906
        if (templateIri == null) {
9!
907
            throw new MalformedTemplateException("Base node shape not found");
×
908
        }
909

910
        IRI baseSubj = vf.createIRI(templateIri.stringValue() + "+subj");
21✔
911
        addType(baseSubj, NTEMPLATE.INTRODUCED_RESOURCE);
12✔
912

913
        List<IRI> statementList = new ArrayList<>();
12✔
914
        Map<IRI, Integer> minCounts = new HashMap<>();
12✔
915
        Map<IRI, Integer> maxCounts = new HashMap<>();
12✔
916

917
        for (Statement st : templateNp.getAssertion()) {
33✔
918
            final IRI subj = (IRI) st.getSubject();
12✔
919
            final IRI pred = st.getPredicate();
9✔
920
            final Value obj = st.getObject();
9✔
921
            final String objS = obj.stringValue();
9✔
922

923
            if (subj.equals(templateIri)) {
15✔
924
                if (pred.equals(RDFS.LABEL)) {
12!
925
                    label = objS;
×
926
                } else if (pred.equals(DCTERMS.DESCRIPTION)) {
12!
927
                    description = Utils.sanitizeHtml(objS);
×
928
                } else if (obj instanceof IRI objIri) {
18✔
929
                    if (pred.equals(NTEMPLATE.HAS_DEFAULT_PROVENANCE)) {
12!
930
                        defaultProvenance = objIri;
×
931
                    } else if (pred.equals(NTEMPLATE.HAS_REQUIRED_PUBINFO_ELEMENT)) {
12!
932
                        requiredPubInfoElements.add(objIri);
×
933
                    } else if (pred.equals(NTEMPLATE.HAS_TARGET_NAMESPACE)) {
12!
934
                        targetNamespace = objS;
×
935
                    } else if (pred.equals(NTEMPLATE.HAS_TARGET_NANOPUB_TYPE)) {
12!
936
                        targetNanopubTypes.add(objIri);
×
937
                    }
938
                } else if (obj instanceof Literal) {
9!
939
                    if (pred.equals(NTEMPLATE.HAS_TAG)) {
12!
940
                        // TODO This should be replaced at some point with a more sophisticated mechanism based on classes.
941
                        // We are assuming that there is at most one tag.
942
                        this.tag = objS;
×
943
                    } else if (pred.equals(NTEMPLATE.HAS_NANOPUB_LABEL_PATTERN)) {
12!
944
                        nanopubLabelPattern = objS;
×
945
                    }
946
                }
947
            }
948
            if (pred.equals(RDF.TYPE) && obj instanceof IRI objIri) {
30!
949
                addType(subj, objIri);
15✔
950
            } else if (pred.equals(SHACL.PROPERTY) && obj instanceof IRI objIri) {
30!
951
                statementList.add(objIri);
12✔
952
                List<IRI> l = statementMap.get(subj);
18✔
953
                if (l == null) {
6✔
954
                    l = new ArrayList<>();
12✔
955
                    statementMap.put(subj, l);
18✔
956
                }
957
                l.add((IRI) objIri);
12✔
958
                IRI stSubjIri = vf.createIRI(subj.stringValue() + "+subj");
18✔
959
                statementSubjects.put(objIri, stSubjIri);
18✔
960
                addType(stSubjIri, NTEMPLATE.LOCAL_RESOURCE);
12✔
961
                addType(stSubjIri, NTEMPLATE.URI_PLACEHOLDER);
12✔
962
            } else if (pred.equals(SHACL.PATH) && obj instanceof IRI objIri) {
33!
963
                statementPredicates.put(subj, objIri);
21✔
964
            } else if (pred.equals(SHACL.HAS_VALUE) && obj instanceof IRI objIri) {
30!
965
                statementObjects.put(subj, objIri);
21✔
966
            } else if (pred.equals(SHACL.TARGET_CLASS) && obj instanceof IRI objIri) {
30!
967
                IRI stIri = vf.createIRI(templateNp.getUri() + "/$type");
21✔
968
                statementList.add(stIri);
12✔
969
                List<IRI> l = statementMap.get(subj);
18✔
970
                if (l == null) {
6✔
971
                    l = new ArrayList<>();
12✔
972
                    statementMap.put(subj, l);
18✔
973
                }
974
                l.add((IRI) stIri);
12✔
975
                statementSubjects.put(stIri, baseSubj);
18✔
976
                statementPredicates.put(stIri, RDF.TYPE);
18✔
977
                statementObjects.put(stIri, objIri);
18✔
978
            } else if (pred.equals(NTEMPLATE.POSSIBLE_VALUE)) {
15!
979
                List<Value> l = possibleValueMap.get(subj);
×
980
                if (l == null) {
×
981
                    l = new ArrayList<>();
×
982
                    possibleValueMap.put(subj, l);
×
983
                }
984
                l.add(obj);
×
985
            } else if (pred.equals(NTEMPLATE.POSSIBLE_VALUES_FROM)) {
12!
986
                List<IRI> l = possibleValuesToLoadMap.get(subj);
×
987
                if (l == null) {
×
988
                    l = new ArrayList<>();
×
989
                    possibleValuesToLoadMap.put(subj, l);
×
990
                }
991
                if (obj instanceof IRI objIri) {
×
992
                    l.add(objIri);
×
993
                    Nanopub valuesNanopub = Utils.getNanopub(objS);
×
994
                    for (Statement s : valuesNanopub.getAssertion()) {
×
995
                        if (s.getPredicate().equals(RDFS.LABEL)) {
×
996
                            labelMap.put((IRI) s.getSubject(), s.getObject().stringValue());
×
997
                        }
998
                    }
×
999
                }
1000
            } else if (pred.equals(NTEMPLATE.POSSIBLE_VALUES_FROM_API)) {
12!
1001
                List<String> l = apiMap.get(subj);
×
1002
                if (l == null) {
×
1003
                    l = new ArrayList<>();
×
1004
                    apiMap.put(subj, l);
×
1005
                }
1006
                if (obj instanceof Literal) {
×
1007
                    l.add(objS);
×
1008
                }
1009
            } else if (pred.equals(RDFS.LABEL) && obj instanceof Literal) {
21!
1010
                labelMap.put(subj, objS);
21✔
1011
            } else if (pred.equals(NTEMPLATE.HAS_DATATYPE) && obj instanceof IRI objIri) {
12!
1012
                datatypeMap.put(subj, objIri);
×
1013
            } else if (pred.equals(NTEMPLATE.HAS_LANGUAGE_TAG) && obj instanceof Literal) {
12!
1014
                languageTagMap.put(subj, Literals.normalizeLanguageTag(objS));
×
1015
            } else if (pred.equals(NTEMPLATE.HAS_PREFIX) && obj instanceof Literal) {
12!
1016
                prefixMap.put(subj, objS);
×
1017
            } else if (pred.equals(NTEMPLATE.HAS_PREFIX_LABEL) && obj instanceof Literal) {
12!
1018
                prefixLabelMap.put(subj, objS);
×
1019
            } else if (pred.equals(NTEMPLATE.HAS_REGEX) && obj instanceof Literal) {
12!
1020
                regexMap.put(subj, objS);
×
1021
//                        } else if (pred.equals(RDF.SUBJECT) && obj instanceof IRI objIri) {
1022
//                                statementSubjects.put(subj, objIri);
1023
//                        } else if (pred.equals(RDF.PREDICATE) && obj instanceof IRI objIri) {
1024
//                                statementPredicates.put(subj, objIri);
1025
//                        } else if (pred.equals(RDF.OBJECT)) {
1026
//                                statementObjects.put(subj, obj);
1027
            } else if (pred.equals(NTEMPLATE.HAS_DEFAULT_VALUE)) {
12!
1028
                defaultValues.put(subj, obj);
×
1029
            } else if (pred.equals(NTEMPLATE.STATEMENT_ORDER)) {
12!
1030
                if (obj instanceof Literal && objS.matches("[0-9]+")) {
×
1031
                    statementOrder.put(subj, Integer.valueOf(objS));
×
1032
                }
1033
            } else if (pred.equals(SHACL.MIN_COUNT)) {
12✔
1034
                try {
1035
                    minCounts.put(subj, Integer.parseInt(obj.stringValue()));
24✔
1036
                } catch (NumberFormatException ex) {
×
1037
                    logger.error("Could not parse <{}> value: {}", SHACL.MIN_COUNT, obj.stringValue());
×
1038
                }
3✔
1039
            } else if (pred.equals(SHACL.MAX_COUNT)) {
12✔
1040
                try {
1041
                    maxCounts.put(subj, Integer.parseInt(obj.stringValue()));
24✔
1042
                } catch (NumberFormatException ex) {
×
1043
                    logger.error("Could not parse <{}> value: {}", SHACL.MAX_COUNT, obj.stringValue());
×
1044
                }
3✔
1045
            }
1046
        }
3✔
1047
        for (List<IRI> l : statementMap.values()) {
36✔
1048
            l.sort(statementComparator);
12✔
1049
        }
3✔
1050
        for (IRI iri : statementList) {
30✔
1051
            if (!statementObjects.containsKey(iri)) {
15✔
1052
                IRI stObjIri = vf.createIRI(iri.stringValue() + "+obj");
18✔
1053
                statementObjects.put(iri, stObjIri);
18✔
1054
                addType(stObjIri, NTEMPLATE.VALUE_PLACEHOLDER);
12✔
1055
                if (!minCounts.containsKey(iri) || minCounts.get(iri) <= 0) {
30✔
1056
                    addType(iri, NTEMPLATE.OPTIONAL_STATEMENT);
12✔
1057
                }
1058
                if (!maxCounts.containsKey(iri) || maxCounts.get(iri) > 1) {
33!
1059
                    addType(iri, NTEMPLATE.REPEATABLE_STATEMENT);
12✔
1060
                }
1061
            }
1062
        }
3✔
1063
        if (!labelMap.containsKey(baseSubj) && typeMap.get(baseSubj).contains(NTEMPLATE.URI_PLACEHOLDER) && typeMap.get(baseSubj).contains(NTEMPLATE.LOCAL_RESOURCE)) {
63!
1064
            labelMap.put(baseSubj, "short ID as URI suffix");
18✔
1065
        }
1066

1067
        if (label == null) {
9!
1068
            label = NanopubUtils.getLabel(templateNp);
12✔
1069
        }
1070
    }
3✔
1071

1072
    public void addToLabelMap(Object key, String label) {
1073
        if (key instanceof IRI iri) {
×
1074
            labelMap.put(iri, label);
×
1075
        } else {
1076
            labelMap.put(vf.createIRI(key.toString()), label);
×
1077
        }
1078
    }
×
1079

1080
    private void addType(IRI thing, IRI type) {
1081
        List<IRI> l = typeMap.get(thing);
18✔
1082
        if (l == null) {
6✔
1083
            l = new ArrayList<>();
12✔
1084
            typeMap.put(thing, l);
18✔
1085
        }
1086
        l.add(type);
12✔
1087
    }
3✔
1088

1089
    private IRI transform(IRI iri) {
1090
        String s = iri.stringValue();
9✔
1091
        // Map a rendered IRI back to its template form for map lookups (label, type,
1092
        // datatype, …). RepetitionGroup.transform expands the template's "~~ARTIFACTCODE~~"
1093
        // placeholder to the target-namespace "~~~ARTIFACTCODE~~~" form and appends a "__N"
1094
        // repetition suffix; both must be undone here or the lookup keyed on the template
1095
        // form (e.g. an attached rdfs:label) is missed.
1096
        if (s.contains("~~~ARTIFACTCODE~~~")) {
12✔
1097
            s = s.replace("~~~ARTIFACTCODE~~~", "~~ARTIFACTCODE~~");
15✔
1098
        }
1099
        if (s.matches(".*__[0-9]+")) {
12✔
1100
            // TODO: Check that this double-underscore pattern isn't used otherwise:
1101
            s = s.replaceFirst("__[0-9]+$", "");
15✔
1102
        }
1103
        if (s.equals(iri.stringValue())) return iri;
21✔
1104
        return vf.createIRI(s);
12✔
1105
    }
1106

1107
    private StatementComparator statementComparator = new StatementComparator();
18✔
1108

1109
    private class StatementComparator implements Comparator<IRI>, Serializable {
18✔
1110

1111
        /**
1112
         * Compares two IRIs based on their order in the template.
1113
         *
1114
         * @param arg0 the first object to be compared.
1115
         * @param arg1 the second object to be compared.
1116
         * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
1117
         */
1118
        @Override
1119
        public int compare(IRI arg0, IRI arg1) {
1120
            Integer i0 = statementOrder.get(arg0);
21✔
1121
            Integer i1 = statementOrder.get(arg1);
21✔
1122
            if (i0 == null && i1 == null) return arg0.stringValue().compareTo(arg1.stringValue());
30✔
1123
            if (i0 == null) return 1;
12✔
1124
            if (i1 == null) return -1;
12✔
1125
            return i0 - i1;
18✔
1126
        }
1127

1128
    }
1129

1130
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc