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

knowledgepixels / nanodash / 17547864661

08 Sep 2025 10:32AM UTC coverage: 11.923% (-0.2%) from 12.079%
17547864661

push

github

web-flow
Merge pull request #251 from knowledgepixels/133-improve-display-of-long-literals

Improve display of long literals

334 of 3882 branches covered (8.6%)

Branch coverage included in aggregate %.

960 of 6971 relevant lines covered (13.77%)

0.61 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 com.knowledgepixels.nanodash.RestrictedChoice;
4
import com.knowledgepixels.nanodash.User;
5
import com.knowledgepixels.nanodash.Utils;
6
import com.knowledgepixels.nanodash.component.StatementItem.RepetitionGroup;
7
import com.knowledgepixels.nanodash.page.ExplorePage;
8
import com.knowledgepixels.nanodash.page.UserPage;
9
import com.knowledgepixels.nanodash.template.ContextType;
10
import com.knowledgepixels.nanodash.template.Template;
11
import com.knowledgepixels.nanodash.template.TemplateContext;
12
import com.knowledgepixels.nanodash.template.UnificationException;
13
import net.trustyuri.TrustyUriUtils;
14
import org.apache.commons.codec.Charsets;
15
import org.apache.wicket.behavior.AttributeAppender;
16
import org.apache.wicket.markup.html.basic.Label;
17
import org.apache.wicket.markup.html.link.ExternalLink;
18
import org.apache.wicket.markup.html.panel.Panel;
19
import org.apache.wicket.model.IModel;
20
import org.apache.wicket.model.Model;
21
import org.apache.wicket.validation.IValidatable;
22
import org.apache.wicket.validation.IValidator;
23
import org.apache.wicket.validation.Validatable;
24
import org.apache.wicket.validation.ValidationError;
25
import org.eclipse.rdf4j.common.net.ParsedIRI;
26
import org.eclipse.rdf4j.model.IRI;
27
import org.eclipse.rdf4j.model.Literal;
28
import org.eclipse.rdf4j.model.Value;
29
import org.eclipse.rdf4j.model.ValueFactory;
30
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
31
import org.eclipse.rdf4j.model.util.Literals;
32
import org.eclipse.rdf4j.model.vocabulary.XSD;
33
import org.nanopub.Nanopub;
34
import org.nanopub.SimpleCreatorPattern;
35
import org.nanopub.vocabulary.NTEMPLATE;
36
import org.slf4j.Logger;
37
import org.slf4j.LoggerFactory;
38

39
import java.net.URISyntaxException;
40
import java.net.URLEncoder;
41
import java.util.HashMap;
42
import java.util.Map;
43

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

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

51
    private static final long serialVersionUID = 1L;
52
    private static final int LONG_LITERAL_LENGTH = 100;
53
    private static final Logger logger = LoggerFactory.getLogger(ReadonlyItem.class);
×
54
    private static final ValueFactory vf = SimpleValueFactory.getInstance();
×
55

56
    private IModel<String> model;
57
    private TemplateContext context;
58
    private String prefix;
59
    private ExternalLink linkComp;
60
    private Label extraComp, languageComp, datatypeComp;
61
    private IModel<String> extraModel, languageModel, datatypeModel;
62
    private IRI iri;
63
    private RestrictedChoice restrictedChoice;
64
    private Label showMoreLabelLiteral, showMoreLabelHTML;
65
    private final Template template;
66

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

92
        final Map<String, String> foafNameMap;
93
        if (context.getExistingNanopub() == null) {
×
94
            foafNameMap = new HashMap<>();
×
95
        } else {
96
            foafNameMap = Utils.getFoafNameMap(context.getExistingNanopub());
×
97
        }
98

99
        prefix = template.getPrefix(iri);
×
100
        if (prefix == null) prefix = "";
×
101
        if (template.isRestrictedChoicePlaceholder(iri)) {
×
102
            restrictedChoice = new RestrictedChoice(iri, context);
×
103
        }
104
        add(new Label("prefix", new Model<String>() {
×
105

106
            private static final long serialVersionUID = 1L;
107

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

123
        }));
124

125
        linkComp = new ExternalLink("link", new Model<String>() {
×
126

127
            private static final long serialVersionUID = 1L;
128

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

156
        }, new Model<String>() {
×
157

158
            private static final long serialVersionUID = 1L;
159

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

189
        });
190
        if (template.isIntroducedResource(iri) || template.isEmbeddedResource(iri)) {
×
191
            linkComp.add(AttributeAppender.append("class", "introduced"));
×
192
        }
193
        add(linkComp);
×
194
        add(new Label("description", new Model<String>() {
×
195

196
            private static final long serialVersionUID = 1L;
197

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

220
        }));
221
        Model<String> uriModel = new Model<String>() {
×
222

223
            private static final long serialVersionUID = 1L;
224

225
            @Override
226
            public String getObject() {
227
                String obj = getFullValue();
×
228
                if (obj != null && obj.startsWith("\"")) return "";
×
229
                if (isAssertionValue(obj)) {
×
230
                    return getAssertionValue();
×
231
                } else if (isNanopubValue(obj)) {
×
232
                    return getNanopubValue();
×
233
                }
234
                return obj;
×
235
            }
236

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

252
        showMoreLabelLiteral = new Label("show-more-literal", "");
×
253
        add(showMoreLabelLiteral);
×
254
        showMoreLabelLiteral.setVisible(false);
×
255

256
        showMoreLabelHTML = new Label("show-more-html", "");
×
257
        add(showMoreLabelHTML);
×
258
        showMoreLabelHTML.setVisible(false);
×
259
    }
×
260

261
    /**
262
     * {@inheritDoc}
263
     */
264
    @Override
265
    public void fillFinished() {
266
        String obj = getFullValue();
×
267
        if (obj != null) {
×
268
            if (isAssertionValue(obj)) {
×
269
                linkComp.add(new AttributeAppender("class", "this-assertion"));
×
270
            } else if (isNanopubValue(obj)) {
×
271
                linkComp.add(new AttributeAppender("class", "this-nanopub"));
×
272
            } else if (context.getExistingNanopub() != null) {
×
273
                Nanopub np = context.getExistingNanopub();
×
274
                if (Utils.getIntroducedIriIds(np).contains(obj) || Utils.getEmbeddedIriIds(np).contains(obj)) {
×
275
                    linkComp.add(AttributeAppender.append("class", "introduced"));
×
276
                }
277
            }
278
        }
279
    }
×
280

281
    /**
282
     * {@inheritDoc}
283
     */
284
    @Override
285
    public void finalizeValues() {
286
    }
×
287

288
    private String getLabelString(IRI iri) {
289
        if (template.getLabel(iri) != null) {
×
290
            return template.getLabel(iri).replaceFirst(" - .*$", "");
×
291
        } else if (context.getLabel(iri) != null) {
×
292
            return context.getLabel(iri).replaceFirst(" - .*$", "");
×
293
        } else {
294
            return IriItem.getShortNameFromURI(iri.stringValue());
×
295
        }
296
    }
297

298
    /**
299
     * {@inheritDoc}
300
     */
301
    @Override
302
    public void removeFromContext() {
303
        // Nothing to be done here.
304
    }
×
305

306
    private String getFullValue() {
307
        String s = model.getObject();
×
308
        if (s == null) return null;
×
309
        if (template.isAutoEscapePlaceholder(iri)) {
×
310
            s = Utils.urlEncode(s);
×
311
        }
312
        if (!prefix.isEmpty()) {
×
313
            s = prefix + s;
×
314
        }
315
        return s;
×
316
    }
317

318
    private boolean isNanopubValue(Object obj) {
319
        if (obj == null) return false;
×
320
        if (obj.toString().equals("local:nanopub")) return true;
×
321
        if (context.getExistingNanopub() == null) return false;
×
322
        return obj.toString().equals(context.getExistingNanopub().getUri().stringValue());
×
323
    }
324

325
    private String getNanopubValue() {
326
        if (context.getExistingNanopub() != null) {
×
327
            return context.getExistingNanopub().getUri().stringValue();
×
328
        } else {
329
            return "local:nanopub";
×
330
        }
331
    }
332

333
    private boolean isAssertionValue(Object obj) {
334
        if (obj == null) return false;
×
335
        if (obj.toString().equals("local:assertion")) return true;
×
336
        if (context.getExistingNanopub() == null) return false;
×
337
        return obj.toString().equals(context.getExistingNanopub().getAssertionUri().stringValue());
×
338
    }
339

340
    private String getAssertionValue() {
341
        if (context.getExistingNanopub() != null) {
×
342
            return context.getExistingNanopub().getAssertionUri().stringValue();
×
343
        } else {
344
            return "local:assertion";
×
345
        }
346
    }
347

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

401
    /**
402
     * {@inheritDoc}
403
     */
404
    @Override
405
    public void unifyWith(Value v) throws UnificationException {
406
        if (v == null) return;
×
407
        String vs = v.stringValue();
×
408
        if (!isUnifiableWith(v)) {
×
409
            logger.error("Cannot unify {}", v);
×
410
            throw new UnificationException(vs);
×
411
        }
412
        if (v instanceof IRI) {
×
413
            if (vs.equals("local:nanopub")) {
×
414
                vs = getNanopubValue();
×
415
            } else if (vs.equals("local:assertion")) {
×
416
                vs = getAssertionValue();
×
417
            }
418
            if (!prefix.isEmpty() && vs.startsWith(prefix)) {
×
419
                vs = vs.substring(prefix.length());
×
420
                // With read-only items, we don't need preliminary local identifiers:
421
//                        } else if (vs.startsWith("local:")) {
422
//                                vs = vs.replaceFirst("^local:", "");
423
//                        } else if (template.isLocalResource(iri) && !Utils.isUriPostfix(vs)) {
424
//                                vs = Utils.getUriPostfix(vs);
425
            }
426
            if (template.isAutoEscapePlaceholder(iri)) {
×
427
                vs = Utils.urlDecode(vs);
×
428
            }
429
            model.setObject(vs);
×
430
        } else if (v instanceof Literal vL) {
×
431
            if (vs.length() >= LONG_LITERAL_LENGTH) {
×
432
                linkComp.add(AttributeAppender.append("class", "long-literal collapsed"));
×
433
                showMoreLabelLiteral.setVisible(true);
×
434
            }
435
            if (vL.getLanguage().isPresent()) {
×
436
                model.setObject("\"" + vs + "\"");
×
437
                languageModel.setObject("(" + Literals.normalizeLanguageTag(vL.getLanguage().get()) + ")");
×
438
                languageComp.setVisible(true);
×
439
            } else if (!vL.getDatatype().equals(XSD.STRING)) {
×
440
                model.setObject("\"" + vs + "\"");
×
441
                datatypeModel.setObject("(" + vL.getDatatype().stringValue().replace(XSD.NAMESPACE, "xsd:") + ")");
×
442
                datatypeComp.setVisible(true);
×
443
            } else {
444
                model.setObject("\"" + vs + "\"");
×
445
            }
446
            // TODO Didn't manage to encode this into a working regex:
447
            if (vs.startsWith("<p>") || vs.startsWith("<p ") || vs.startsWith("<div>") || vs.startsWith("<div ") || vs.startsWith("<span>") || vs.startsWith("<span ") || vs.startsWith("<img ")) {
×
448
                linkComp.setVisible(false);
×
449
                extraModel.setObject(Utils.sanitizeHtml(vs));
×
450
                extraComp.setEscapeModelStrings(false);
×
451
                extraComp.setVisible(true);
×
452
                showMoreLabelLiteral.setVisible(false);
×
453
                showMoreLabelHTML.setVisible(true);
×
454
            }
455
        }
456
    }
×
457

458
    protected class Validator extends InvalidityHighlighting implements IValidator<String> {
459

460
        private static final long serialVersionUID = 1L;
461

462
        /**
463
         * Default constructor for Validator.
464
         */
465
        public Validator() {
×
466
        }
×
467

468
        @Override
469
        public void validate(IValidatable<String> s) {
470
            String sv = s.getValue();
×
471
            String p = prefix;
×
472
            if (template.isAutoEscapePlaceholder(iri)) {
×
473
                sv = Utils.urlEncode(sv);
×
474
            }
475
            if (sv.matches("https?://.+")) {
×
476
                p = "";
×
477
            } else if (sv.contains(":")) {
×
478
                s.error(new ValidationError("Colon character is not allowed in postfix"));
×
479
            }
480
            String iriString = p + sv;
×
481
            if (iriString.matches("[^:# ]+")) {
×
482
                p = "local:";
×
483
                iriString = p + sv;
×
484
            }
485
            try {
486
                ParsedIRI piri = new ParsedIRI(iriString);
×
487
                if (!piri.isAbsolute()) {
×
488
                    s.error(new ValidationError("IRI not well-formed"));
×
489
                }
490
                if (p.isEmpty() && !sv.startsWith("local:") && !sv.matches("https?://.+")) {
×
491
                    s.error(new ValidationError("Only http(s):// IRIs are allowed here"));
×
492
                }
493
            } catch (URISyntaxException ex) {
×
494
                s.error(new ValidationError("IRI not well-formed"));
×
495
            }
×
496
            String regex = template.getRegex(iri);
×
497
            if (regex != null) {
×
498
                if (!sv.matches(regex)) {
×
499
                    s.error(new ValidationError("Value '" + sv + "' doesn't match the pattern '" + regex + "'"));
×
500
                }
501
            }
502
            if (template.isRestrictedChoicePlaceholder(iri)) {
×
503
                if (!restrictedChoice.getPossibleValues().contains(iriString) && !restrictedChoice.hasPossibleRefValues()) {
×
504
                    // not checking the possible ref values can overgenerate, but normally works
505
                    s.error(new ValidationError("Invalid choice"));
×
506
                }
507
            }
508
            if (template.isExternalUriPlaceholder(iri)) {
×
509
                if (!iriString.matches("https?://.+")) {
×
510
                    s.error(new ValidationError("Not an external IRI"));
×
511
                }
512
            }
513
            if (template.isTrustyUriPlaceholder(iri)) {
×
514
                if (!TrustyUriUtils.isPotentialTrustyUri(iriString)) {
×
515
                    s.error(new ValidationError("Not a trusty URI"));
×
516
                }
517
            }
518
            if (iri.equals(NTEMPLATE.CREATOR_PLACEHOLDER) && context.getExistingNanopub() != null) {
×
519
                boolean found = false;
×
520
                for (IRI creator : SimpleCreatorPattern.getCreators(context.getExistingNanopub())) {
×
521
                    if (creator.stringValue().equals(iriString)) {
×
522
                        found = true;
×
523
                        break;
×
524
                    }
525
                }
×
526
                if (!found) {
×
527
                    s.error(new ValidationError("Not a creator of nanopub"));
×
528
                }
529
            }
530
        }
×
531

532
    }
533

534
    /**
535
     * <p>toString.</p>
536
     *
537
     * @return a {@link java.lang.String} object
538
     */
539
    public String toString() {
540
        return "[read-only IRI item: " + iri + "]";
×
541
    }
542

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