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

knowledgepixels / nanodash / 17265760757

27 Aug 2025 11:45AM UTC coverage: 12.362% (-0.04%) from 12.401%
17265760757

push

github

tkuhn
Some minor refactoring and improvements of literal handling

332 of 3834 branches covered (8.66%)

Branch coverage included in aggregate %.

989 of 6852 relevant lines covered (14.43%)

0.64 hits per line

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

0.0
src/main/java/com/knowledgepixels/nanodash/component/ReadonlyItem.java
1
package com.knowledgepixels.nanodash.component;
2

3
import java.net.URISyntaxException;
4
import java.net.URLEncoder;
5
import java.util.HashMap;
6
import java.util.Map;
7

8
import org.apache.commons.codec.Charsets;
9
import org.apache.wicket.behavior.AttributeAppender;
10
import org.apache.wicket.markup.html.basic.Label;
11
import org.apache.wicket.markup.html.link.ExternalLink;
12
import org.apache.wicket.markup.html.panel.Panel;
13
import org.apache.wicket.model.IModel;
14
import org.apache.wicket.model.Model;
15
import org.apache.wicket.validation.IValidatable;
16
import org.apache.wicket.validation.IValidator;
17
import org.apache.wicket.validation.Validatable;
18
import org.apache.wicket.validation.ValidationError;
19
import org.eclipse.rdf4j.common.net.ParsedIRI;
20
import org.eclipse.rdf4j.model.IRI;
21
import org.eclipse.rdf4j.model.Literal;
22
import org.eclipse.rdf4j.model.Value;
23
import org.eclipse.rdf4j.model.ValueFactory;
24
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
25
import org.eclipse.rdf4j.model.util.Literals;
26
import org.eclipse.rdf4j.model.vocabulary.XSD;
27
import org.nanopub.Nanopub;
28
import org.nanopub.SimpleCreatorPattern;
29

30
import com.knowledgepixels.nanodash.RestrictedChoice;
31
import com.knowledgepixels.nanodash.User;
32
import com.knowledgepixels.nanodash.Utils;
33
import com.knowledgepixels.nanodash.component.StatementItem.RepetitionGroup;
34
import com.knowledgepixels.nanodash.page.ExplorePage;
35
import com.knowledgepixels.nanodash.page.UserPage;
36
import com.knowledgepixels.nanodash.template.ContextType;
37
import com.knowledgepixels.nanodash.template.Template;
38
import com.knowledgepixels.nanodash.template.TemplateContext;
39
import com.knowledgepixels.nanodash.template.UnificationException;
40

41
import net.trustyuri.TrustyUriUtils;
42

43
/**
44
 * ReadonlyItem is a component that displays a read-only item in the form.
45
 */
46
public class ReadonlyItem extends Panel implements ContextComponent {
47

48
    // TODO: Make ContextComponent an abstract class with superclass Panel, and move the common code of the form items there.
49

50
    private static final long serialVersionUID = 1L;
51

52
    private IModel<String> model;
53
    private TemplateContext context;
54
    private String prefix;
55
    private ExternalLink linkComp;
56
    private Label extraComp, languageComp, datatypeComp;
57
    private IModel<String> extraModel, languageModel, datatypeModel;
58
    private IRI iri;
59
    private RestrictedChoice restrictedChoice;
60
    private final Template template;
61

62
    /**
63
     * Constructor for ReadonlyItem.
64
     *
65
     * @param id              the component id
66
     * @param parentId        the parent id (e.g., "subj", "obj")
67
     * @param iriP            the IRI of the item
68
     * @param objectPosition  whether this is an object position
69
     * @param statementPartId the statement part ID
70
     * @param rg              the repetition group
71
     */
72
    public ReadonlyItem(String id, String parentId, final IRI iriP, boolean objectPosition, IRI statementPartId, final RepetitionGroup rg) {
73
        super(id);
×
74
        context = rg.getContext();
×
75
        this.iri = iriP;
×
76
        template = context.getTemplate();
×
77
        model = context.getComponentModels().get(iri);
×
78
        if (model == null) {
×
79
            model = Model.of("");
×
80
            context.getComponentModels().put(iri, model);
×
81
        }
82
        String postfix = Utils.getUriPostfix(iri);
×
83
        if (context.hasParam(postfix)) {
×
84
            model.setObject(context.getParam(postfix));
×
85
        }
86

87
        final Map<String, String> foafNameMap;
88
        if (context.getExistingNanopub() == null) {
×
89
            foafNameMap = new HashMap<>();
×
90
        } else {
91
            foafNameMap = Utils.getFoafNameMap(context.getExistingNanopub());
×
92
        }
93

94
        prefix = template.getPrefix(iri);
×
95
        if (prefix == null) prefix = "";
×
96
        if (template.isRestrictedChoicePlaceholder(iri)) {
×
97
            restrictedChoice = new RestrictedChoice(iri, context);
×
98
        }
99
        add(new Label("prefix", new Model<String>() {
×
100

101
            private static final long serialVersionUID = 1L;
102

103
            @Override
104
            public String getObject() {
105
                String prefixLabel = template.getPrefixLabel(iri);
×
106
                String v = getFullValue();
×
107
                if (prefixLabel == null || User.isUser(v) || foafNameMap.containsKey(v)) {
×
108
                    return "";
×
109
                } else {
110
                    if (prefixLabel.length() > 0 && parentId.equals("subj") && !prefixLabel.matches("https?://.*")) {
×
111
                        // Capitalize first letter of label if at subject position:
112
                        prefixLabel = prefixLabel.substring(0, 1).toUpperCase() + prefixLabel.substring(1);
×
113
                    }
114
                    return prefixLabel;
×
115
                }
116
            }
117

118
        }));
119

120
        linkComp = new ExternalLink("link", new Model<String>() {
×
121

122
            private static final long serialVersionUID = 1L;
123

124
            @Override
125
            public String getObject() {
126
                String obj = getFullValue();
×
127
                if (obj == null) return "";
×
128
                if (obj.equals("local:nanopub")) {
×
129
                    if (context.getExistingNanopub() != null) {
×
130
                        obj = context.getExistingNanopub().getUri().stringValue();
×
131
                        return ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(obj, Charsets.UTF_8);
×
132
                    } else {
133
                        return "";
×
134
                    }
135
                } else if (obj.equals("local:assertion")) {
×
136
                    if (context.getExistingNanopub() != null) {
×
137
                        obj = context.getExistingNanopub().getAssertionUri().stringValue();
×
138
                        return ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(obj, Charsets.UTF_8);
×
139
                    } else {
140
                        return "";
×
141
                    }
142
                } else if (User.isUser(obj)) {
×
143
                    return UserPage.MOUNT_PATH + "?id=" + URLEncoder.encode(obj, Charsets.UTF_8);
×
144
                } else if (obj.matches("https?://.+")) {
×
145
                    return ExplorePage.MOUNT_PATH + "?id=" + URLEncoder.encode(obj, Charsets.UTF_8);
×
146
                } else {
147
                    return "";
×
148
                }
149
            }
150

151
        }, new Model<String>() {
×
152

153
            private static final long serialVersionUID = 1L;
154

155
            @Override
156
            public String getObject() {
157
                String obj = getFullValue();
×
158
                if (obj != null && obj.matches("https?://.+")) {
×
159
                    IRI objIri = vf.createIRI(obj);
×
160
                    if (iri.equals(Template.CREATOR_PLACEHOLDER)) {
×
161
                        if (objectPosition) {
×
162
                            return "me (" + User.getShortDisplayName(objIri) + ")";
×
163
                        } else {
164
                            return "I (" + User.getShortDisplayName(objIri) + ")";
×
165
                        }
166
                    } else if (isAssertionValue(objIri)) {
×
167
                        if (context.getType() == ContextType.ASSERTION) {
×
168
                            return "this assertion";
×
169
                        } else {
170
                            return "the assertion above";
×
171
                        }
172
                    } else if (isNanopubValue(objIri)) {
×
173
                        return "this nanopublication";
×
174
                    } else if (User.isUser(obj)) {
×
175
                        return User.getShortDisplayName(objIri);
×
176
                    } else if (foafNameMap.containsKey(obj)) {
×
177
                        return foafNameMap.get(obj);
×
178
                    }
179
                    return getLabelString(objIri);
×
180
                }
181
                return obj;
×
182
            }
183

184
        });
185
        if (template.isIntroducedResource(iri) || template.isEmbeddedResource(iri)) {
×
186
            linkComp.add(AttributeAppender.append("class", "introduced"));
×
187
        }
188
        add(linkComp);
×
189
        add(new Label("description", new Model<String>() {
×
190

191
            private static final long serialVersionUID = 1L;
192

193
            @Override
194
            public String getObject() {
195
                String obj = getFullValue();
×
196
                if (obj != null && obj.matches("https?://.+")) {
×
197
                    IRI objIri = vf.createIRI(obj);
×
198
                    if (isAssertionValue(objIri)) {
×
199
                        return "This is the identifier for the assertion of this nanopublication.";
×
200
                    } else if (isNanopubValue(objIri)) {
×
201
                        return "This is the identifier for this whole nanopublication.";
×
202
                    } else if (context.isReadOnly() && obj.startsWith(context.getExistingNanopub().getUri().stringValue())) {
×
203
                        return "This is a local identifier minted within the nanopublication.";
×
204
                    }
205
                    String labelString = getLabelString(objIri);
×
206
                    String description = "";
×
207
                    if (labelString.contains(" - ")) description = labelString.replaceFirst("^.* - ", "");
×
208
                    return description;
×
209
                } else if (obj != null && obj.startsWith("\"")) {
×
210
                    return "(this is a literal)";
×
211
                }
212
                return "";
×
213
            }
214

215
        }));
216
        Model<String> uriModel = new Model<String>() {
×
217

218
            private static final long serialVersionUID = 1L;
219

220
            @Override
221
            public String getObject() {
222
                String obj = getFullValue();
×
223
                if (obj != null && obj.startsWith("\"")) return "";
×
224
                if (isAssertionValue(obj)) {
×
225
                    return getAssertionValue();
×
226
                } else if (isNanopubValue(obj)) {
×
227
                    return getNanopubValue();
×
228
                }
229
                return obj;
×
230
            }
231

232
        };
233
        add(Utils.getUriLink("uri", uriModel));
×
234
        extraModel = Model.of("");
×
235
        extraComp = new Label("extra", extraModel);
×
236
        extraComp.setVisible(false);
×
237
        add(extraComp);
×
238
        languageModel = Model.of("");
×
239
        languageComp = new Label("language", languageModel);
×
240
        languageComp.setVisible(false);
×
241
        add(languageComp);
×
242
        datatypeModel = Model.of("");
×
243
        datatypeComp = new Label("datatype", datatypeModel);
×
244
        datatypeComp.setVisible(false);
×
245
        add(datatypeComp);
×
246
    }
×
247

248
    /**
249
     * {@inheritDoc}
250
     */
251
    @Override
252
    public void fillFinished() {
253
        String obj = getFullValue();
×
254
        if (obj != null) {
×
255
            if (isAssertionValue(obj)) {
×
256
                linkComp.add(new AttributeAppender("class", "this-assertion"));
×
257
            } else if (isNanopubValue(obj)) {
×
258
                linkComp.add(new AttributeAppender("class", "this-nanopub"));
×
259
            } else if (context.getExistingNanopub() != null) {
×
260
                Nanopub np = context.getExistingNanopub();
×
261
                if (Utils.getIntroducedIriIds(np).contains(obj) || Utils.getEmbeddedIriIds(np).contains(obj)) {
×
262
                    linkComp.add(AttributeAppender.append("class", "introduced"));
×
263
                }
264
            }
265
        }
266
    }
×
267

268
    /**
269
     * {@inheritDoc}
270
     */
271
    @Override
272
    public void finalizeValues() {
273
    }
×
274

275
    private String getLabelString(IRI iri) {
276
        if (template.getLabel(iri) != null) {
×
277
            return template.getLabel(iri).replaceFirst(" - .*$", "");
×
278
        } else if (context.getLabel(iri) != null) {
×
279
            return context.getLabel(iri).replaceFirst(" - .*$", "");
×
280
        } else {
281
            return IriItem.getShortNameFromURI(iri.stringValue());
×
282
        }
283
    }
284

285
    /**
286
     * {@inheritDoc}
287
     */
288
    @Override
289
    public void removeFromContext() {
290
        // Nothing to be done here.
291
    }
×
292

293
    private String getFullValue() {
294
        String s = model.getObject();
×
295
        if (s == null) return null;
×
296
        if (template.isAutoEscapePlaceholder(iri)) {
×
297
            s = Utils.urlEncode(s);
×
298
        }
299
        if (!prefix.isEmpty()) {
×
300
            s = prefix + s;
×
301
        }
302
        return s;
×
303
    }
304

305
    private boolean isNanopubValue(Object obj) {
306
        if (obj == null) return false;
×
307
        if (obj.toString().equals("local:nanopub")) return true;
×
308
        if (context.getExistingNanopub() == null) return false;
×
309
        return obj.toString().equals(context.getExistingNanopub().getUri().stringValue());
×
310
    }
311

312
    private String getNanopubValue() {
313
        if (context.getExistingNanopub() != null) {
×
314
            return context.getExistingNanopub().getUri().stringValue();
×
315
        } else {
316
            return "local:nanopub";
×
317
        }
318
    }
319

320
    private boolean isAssertionValue(Object obj) {
321
        if (obj == null) return false;
×
322
        if (obj.toString().equals("local:assertion")) return true;
×
323
        if (context.getExistingNanopub() == null) return false;
×
324
        return obj.toString().equals(context.getExistingNanopub().getAssertionUri().stringValue());
×
325
    }
326

327
    private String getAssertionValue() {
328
        if (context.getExistingNanopub() != null) {
×
329
            return context.getExistingNanopub().getAssertionUri().stringValue();
×
330
        } else {
331
            return "local:assertion";
×
332
        }
333
    }
334

335
    /**
336
     * {@inheritDoc}
337
     */
338
    @Override
339
    public boolean isUnifiableWith(Value v) {
340
        if (v == null) return true;
×
341
        if (v instanceof IRI) {
×
342
            String vs = v.stringValue();
×
343
            if (vs.equals("local:nanopub")) {
×
344
                vs = getNanopubValue();
×
345
            } else if (vs.equals("local:assertion")) {
×
346
                vs = getAssertionValue();
×
347
            }
348
            if (vs.startsWith(prefix)) vs = vs.substring(prefix.length());
×
349
//                        if (vs.startsWith("local:")) vs = vs.replaceFirst("^local:", "");
350
            if (template.isAutoEscapePlaceholder(iri)) {
×
351
                vs = Utils.urlDecode(vs);
×
352
            }
353
            Validatable<String> validatable = new Validatable<>(vs);
×
354
//                        if (template.isLocalResource(iri) && !Utils.isUriPostfix(vs)) {
355
//                                vs = Utils.getUriPostfix(vs);
356
//                        }
357
            new Validator().validate(validatable);
×
358
            if (!validatable.isValid()) {
×
359
                return false;
×
360
            }
361
            if (model.getObject().isEmpty()) {
×
362
                return true;
×
363
            }
364
            return vs.equals(model.getObject());
×
365
        } else if (v instanceof Literal vL) {
×
366
            if (template.getRegex(iri) != null && !v.stringValue().matches(template.getRegex(iri))) {
×
367
                return false;
×
368
            }
369
            String languagetag = template.getLanguageTag(iri);
×
370
            IRI datatype = template.getDatatype(iri);
×
371
            if (languagetag != null) {
×
372
                if (!vL.getLanguage().isPresent() || !Literals.normalizeLanguageTag(vL.getLanguage().get()).equals(languagetag)) {
×
373
                    return false;
×
374
                }
375
            } else if (datatype != null) {
×
376
                if (!vL.getDatatype().equals(datatype)) {
×
377
                    return false;
×
378
                }
379
            }
380
            if (linkComp.getDefaultModelObject() == null || linkComp.getDefaultModelObject().toString().isEmpty()) {
×
381
                return true;
×
382
            }
383
            return linkComp.getDefaultModelObject().equals("\"" + v.stringValue() + "\"");
×
384
        }
385
        return false;
×
386
    }
387

388
    /**
389
     * {@inheritDoc}
390
     */
391
    @Override
392
    public void unifyWith(Value v) throws UnificationException {
393
        if (v == null) return;
×
394
        String vs = v.stringValue();
×
395
        if (!isUnifiableWith(v)) throw new UnificationException(vs);
×
396
        if (v instanceof IRI) {
×
397
            if (vs.equals("local:nanopub")) {
×
398
                vs = getNanopubValue();
×
399
            } else if (vs.equals("local:assertion")) {
×
400
                vs = getAssertionValue();
×
401
            }
402
            if (!prefix.isEmpty() && vs.startsWith(prefix)) {
×
403
                vs = vs.substring(prefix.length());
×
404
                // With read-only items, we don't need preliminary local identifiers:
405
//                        } else if (vs.startsWith("local:")) {
406
//                                vs = vs.replaceFirst("^local:", "");
407
//                        } else if (template.isLocalResource(iri) && !Utils.isUriPostfix(vs)) {
408
//                                vs = Utils.getUriPostfix(vs);
409
            }
410
            if (template.isAutoEscapePlaceholder(iri)) {
×
411
                vs = Utils.urlDecode(vs);
×
412
            }
413
            model.setObject(vs);
×
414
        } else if (v instanceof Literal vL) {
×
415
            if (vL.getLanguage().isPresent()) {
×
416
                model.setObject("\"" + vs + "\"");
×
417
                languageModel.setObject("(" + Literals.normalizeLanguageTag(vL.getLanguage().get()) + ")");
×
418
                languageComp.setVisible(true);
×
419
            } else if (!vL.getDatatype().equals(XSD.STRING)) {
×
420
                model.setObject("\"" + vs + "\"");
×
421
                datatypeModel.setObject("(" + vL.getDatatype().stringValue().replace(XSD.NAMESPACE, "xsd:") + ")");
×
422
                datatypeComp.setVisible(true);
×
423
            } else {
424
                model.setObject("\"" + vs + "\"");
×
425
            }
426
            // TODO Didn't manage to encode this into a working regex:
427
            if (vs.startsWith("<p>") || vs.startsWith("<p ") || vs.startsWith("<div>") || vs.startsWith("<div ") || vs.startsWith("<span>") || vs.startsWith("<span ") || vs.startsWith("<img ")) {
×
428
                linkComp.setVisible(false);
×
429
                extraModel.setObject("<span class=\"internal\">" + Utils.sanitizeHtml(vs) + "</span>");
×
430
                extraComp.setEscapeModelStrings(false);
×
431
                extraComp.setVisible(true);
×
432
            }
433
        }
434
    }
×
435

436

437
    protected class Validator extends InvalidityHighlighting implements IValidator<String> {
438

439
        private static final long serialVersionUID = 1L;
440

441
        /**
442
         * Default constructor for Validator.
443
         */
444
        public Validator() {
×
445
        }
×
446

447
        @Override
448
        public void validate(IValidatable<String> s) {
449
            String sv = s.getValue();
×
450
            String p = prefix;
×
451
            if (template.isAutoEscapePlaceholder(iri)) {
×
452
                sv = Utils.urlEncode(sv);
×
453
            }
454
            if (sv.matches("https?://.+")) {
×
455
                p = "";
×
456
            } else if (sv.contains(":")) {
×
457
                s.error(new ValidationError("Colon character is not allowed in postfix"));
×
458
            }
459
            String iriString = p + sv;
×
460
            if (iriString.matches("[^:# ]+")) {
×
461
                p = "local:";
×
462
                iriString = p + sv;
×
463
            }
464
            try {
465
                ParsedIRI piri = new ParsedIRI(iriString);
×
466
                if (!piri.isAbsolute()) {
×
467
                    s.error(new ValidationError("IRI not well-formed"));
×
468
                }
469
                if (p.isEmpty() && !sv.startsWith("local:") && !sv.matches("https?://.+")) {
×
470
                    s.error(new ValidationError("Only http(s):// IRIs are allowed here"));
×
471
                }
472
            } catch (URISyntaxException ex) {
×
473
                s.error(new ValidationError("IRI not well-formed"));
×
474
            }
×
475
            String regex = template.getRegex(iri);
×
476
            if (regex != null) {
×
477
                if (!sv.matches(regex)) {
×
478
                    s.error(new ValidationError("Value '" + sv + "' doesn't match the pattern '" + regex + "'"));
×
479
                }
480
            }
481
            if (template.isRestrictedChoicePlaceholder(iri)) {
×
482
                if (!restrictedChoice.getPossibleValues().contains(iriString) && !restrictedChoice.hasPossibleRefValues()) {
×
483
                    // not checking the possible ref values can overgenerate, but normally works
484
                    s.error(new ValidationError("Invalid choice"));
×
485
                }
486
            }
487
            if (template.isExternalUriPlaceholder(iri)) {
×
488
                if (!iriString.matches("https?://.+")) {
×
489
                    s.error(new ValidationError("Not an external IRI"));
×
490
                }
491
            }
492
            if (template.isTrustyUriPlaceholder(iri)) {
×
493
                if (!TrustyUriUtils.isPotentialTrustyUri(iriString)) {
×
494
                    s.error(new ValidationError("Not a trusty URI"));
×
495
                }
496
            }
497
            if (iri.equals(Template.CREATOR_PLACEHOLDER) && context.getExistingNanopub() != null) {
×
498
                boolean found = false;
×
499
                for (IRI creator : SimpleCreatorPattern.getCreators(context.getExistingNanopub())) {
×
500
                    if (creator.stringValue().equals(iriString)) {
×
501
                        found = true;
×
502
                        break;
×
503
                    }
504
                }
×
505
                if (!found) {
×
506
                    s.error(new ValidationError("Not a creator of nanopub"));
×
507
                }
508
            }
509
        }
×
510

511
    }
512

513
    /**
514
     * <p>toString.</p>
515
     *
516
     * @return a {@link java.lang.String} object
517
     */
518
    public String toString() {
519
        return "[read-only IRI item: " + iri + "]";
×
520
    }
521

522
    static final ValueFactory vf = SimpleValueFactory.getInstance();
×
523

524
}
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