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

knowledgepixels / nanodash / 19258500990

11 Nov 2025 07:36AM UTC coverage: 13.997% (+0.2%) from 13.774%
19258500990

Pull #283

github

web-flow
Merge 4461730d4 into 089c63acd
Pull Request #283: Fix the HTML rendering in `QueryResultList`

515 of 4684 branches covered (10.99%)

Branch coverage included in aggregate %.

1355 of 8676 relevant lines covered (15.62%)

0.7 hits per line

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

71.11
src/main/java/com/knowledgepixels/nanodash/Utils.java
1
package com.knowledgepixels.nanodash;
2

3
import com.google.common.hash.Hashing;
4
import net.trustyuri.TrustyUriUtils;
5
import org.apache.commons.codec.Charsets;
6
import org.apache.commons.exec.environment.EnvironmentUtils;
7
import org.apache.commons.lang.StringUtils;
8
import org.apache.http.client.utils.URIBuilder;
9
import org.apache.wicket.markup.html.link.ExternalLink;
10
import org.apache.wicket.model.IModel;
11
import org.apache.wicket.request.mapper.parameter.PageParameters;
12
import org.apache.wicket.util.string.StringValue;
13
import org.eclipse.rdf4j.model.IRI;
14
import org.eclipse.rdf4j.model.Literal;
15
import org.eclipse.rdf4j.model.Statement;
16
import org.eclipse.rdf4j.model.ValueFactory;
17
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
18
import org.eclipse.rdf4j.model.util.Literals;
19
import org.eclipse.rdf4j.model.vocabulary.FOAF;
20
import org.eclipse.rdf4j.model.vocabulary.XSD;
21
import org.nanopub.Nanopub;
22
import org.nanopub.NanopubUtils;
23
import org.nanopub.extra.security.KeyDeclaration;
24
import org.nanopub.extra.security.MalformedCryptoElementException;
25
import org.nanopub.extra.security.NanopubSignatureElement;
26
import org.nanopub.extra.security.SignatureUtils;
27
import org.nanopub.extra.server.GetNanopub;
28
import org.nanopub.extra.services.ApiResponseEntry;
29
import org.nanopub.extra.setting.IntroNanopub;
30
import org.nanopub.vocabulary.FIP;
31
import org.nanopub.vocabulary.NPX;
32
import org.owasp.html.HtmlPolicyBuilder;
33
import org.owasp.html.PolicyFactory;
34
import org.slf4j.Logger;
35
import org.slf4j.LoggerFactory;
36
import org.wicketstuff.select2.Select2Choice;
37

38
import java.io.IOException;
39
import java.io.Serializable;
40
import java.net.URISyntaxException;
41
import java.net.URLDecoder;
42
import java.net.URLEncoder;
43
import java.nio.charset.StandardCharsets;
44
import java.util.*;
45
import java.util.regex.Pattern;
46

47
import static java.nio.charset.StandardCharsets.UTF_8;
48

49
/**
50
 * Utility class providing various helper methods for handling nanopublications, URIs, and other related functionalities.
51
 */
52
public class Utils {
53

54
    private Utils() {
55
    }  // no instances allowed
56

57
    /**
58
     * ValueFactory instance for creating RDF model objects.
59
     */
60
    public static final ValueFactory vf = SimpleValueFactory.getInstance();
2✔
61
    private static final Logger logger = LoggerFactory.getLogger(Utils.class);
3✔
62
    private static final Pattern LEADING_TAG = Pattern.compile("^\\s*<(p|div|span|img)(\\s|>|/).*", Pattern.CASE_INSENSITIVE);
4✔
63

64
    /**
65
     * Generates a short name from a given IRI object.
66
     *
67
     * @param uri the IRI object
68
     * @return a short representation of the URI
69
     */
70
    public static String getShortNameFromURI(IRI uri) {
71
        return getShortNameFromURI(uri.stringValue());
4✔
72
    }
73

74
    /**
75
     * Generates a short name from a given URI string.
76
     *
77
     * @param uri the URI string
78
     * @return a short representation of the URI
79
     */
80
    public static String getShortNameFromURI(String uri) {
81
        if (uri.startsWith("https://doi.org/") || uri.startsWith("http://dx.doi.org/")) {
8✔
82
            return uri.replaceFirst("^https?://(dx\\.)?doi.org/", "doi:");
5✔
83
        }
84
        uri = uri.replaceFirst("\\?.*$", "");
5✔
85
        uri = uri.replaceFirst("[/#]$", "");
5✔
86
        uri = uri.replaceFirst("^.*[/#]([^/#]*)[/#]([0-9]+)$", "$1/$2");
5✔
87
        if (uri.contains("#")) {
4✔
88
            uri = uri.replaceFirst("^.*#(.*[^0-9].*)$", "$1");
6✔
89
        } else {
90
            uri = uri.replaceFirst("^.*/([^/]*[^0-9/][^/]*)$", "$1");
5✔
91
        }
92
        uri = uri.replaceFirst("((^|[^A-Za-z0-9\\-_])RA[A-Za-z0-9\\-_]{8})[A-Za-z0-9\\-_]{35}$", "$1");
5✔
93
        uri = uri.replaceFirst("(^|[^A-Za-z0-9\\-_])RA[A-Za-z0-9\\-_]{43}[^A-Za-z0-9\\-_](.+)$", "$2");
5✔
94
        uri = URLDecoder.decode(uri, UTF_8);
4✔
95
        return uri;
2✔
96
    }
97

98
    /**
99
     * Generates a short nanopublication ID from a given nanopublication ID or URI.
100
     *
101
     * @param npId the nanopublication ID or URI
102
     * @return the first 10 characters of the artifact code
103
     */
104
    public static String getShortNanopubId(Object npId) {
105
        return TrustyUriUtils.getArtifactCode(npId.toString()).substring(0, 10);
7✔
106
    }
107

108
    private static Map<String, Nanopub> nanopubs = new HashMap<>();
4✔
109

110
    /**
111
     * Retrieves a Nanopub object based on the given URI or artifact code.
112
     *
113
     * @param uriOrArtifactCode the URI or artifact code of the nanopublication
114
     * @return the Nanopub object, or null if not found
115
     */
116
    public static Nanopub getNanopub(String uriOrArtifactCode) {
117
        String artifactCode = GetNanopub.getArtifactCode(uriOrArtifactCode);
3✔
118
        if (!nanopubs.containsKey(artifactCode)) {
4✔
119
            for (int i = 0; i < 3; i++) {  // Try 3 times to get nanopub
5!
120
                Nanopub np = GetNanopub.get(artifactCode);
3✔
121
                if (np != null) {
2!
122
                    nanopubs.put(artifactCode, np);
5✔
123
                    break;
1✔
124
                }
125
            }
126
        }
127
        return nanopubs.get(artifactCode);
5✔
128
    }
129

130
    /**
131
     * URL-encodes the string representation of the given object using UTF-8 encoding.
132
     *
133
     * @param o the object to be URL-encoded
134
     * @return the URL-encoded string
135
     */
136
    public static String urlEncode(Object o) {
137
        return URLEncoder.encode((o == null ? "" : o.toString()), Charsets.UTF_8);
9✔
138
    }
139

140
    /**
141
     * URL-decodes the string representation of the given object using UTF-8 encoding.
142
     *
143
     * @param o the object to be URL-decoded
144
     * @return the URL-decoded string
145
     */
146
    public static String urlDecode(Object o) {
147
        return URLDecoder.decode((o == null ? "" : o.toString()), Charsets.UTF_8);
9✔
148
    }
149

150
    /**
151
     * Generates a URL with the given base and appends the provided PageParameters as query parameters.
152
     *
153
     * @param base       the base URL
154
     * @param parameters the PageParameters to append
155
     * @return the complete URL with parameters
156
     */
157
    public static String getUrlWithParameters(String base, PageParameters parameters) {
158
        try {
159
            URIBuilder u = new URIBuilder(base);
5✔
160
            for (String key : parameters.getNamedKeys()) {
11✔
161
                for (StringValue value : parameters.getValues(key)) {
12✔
162
                    if (!value.isNull()) u.addParameter(key, value.toString());
9!
163
                }
1✔
164
            }
1✔
165
            return u.build().toString();
4✔
166
        } catch (URISyntaxException ex) {
1✔
167
            logger.error("Could not build URL with parameters: {} {}", base, parameters, ex);
17✔
168
            return "/";
2✔
169
        }
170
    }
171

172
    /**
173
     * Generates a short name for a public key or public key hash.
174
     *
175
     * @param pubkeyOrPubkeyhash the public key (64 characters) or public key hash (40 characters)
176
     * @return a short representation of the public key or public key hash
177
     */
178
    public static String getShortPubkeyName(String pubkeyOrPubkeyhash) {
179
        if (pubkeyOrPubkeyhash.length() == 64) {
4!
180
            return pubkeyOrPubkeyhash.replaceFirst("^(.{8}).*$", "$1");
×
181
        } else {
182
            return pubkeyOrPubkeyhash.replaceFirst("^(.).{39}(.{5}).*$", "$1..$2..");
5✔
183
        }
184
    }
185

186
    /**
187
     * Generates a short label for a public key or public key hash, including its status (local or approved).
188
     *
189
     * @param pubkeyOrPubkeyhash the public key (64 characters) or public key hash (40 characters)
190
     * @param user               the IRI of the user associated with the public key
191
     * @return a short label indicating the public key and its status
192
     */
193
    public static String getShortPubkeyhashLabel(String pubkeyOrPubkeyhash, IRI user) {
194
        String s = getShortPubkeyName(pubkeyOrPubkeyhash);
×
195
        NanodashSession session = NanodashSession.get();
×
196
        List<String> l = new ArrayList<>();
×
197
        if (pubkeyOrPubkeyhash.equals(session.getPubkeyString()) || pubkeyOrPubkeyhash.equals(session.getPubkeyhash()))
×
198
            l.add("local");
×
199
        // TODO: Make this more efficient:
200
        String hashed = Utils.createSha256HexHash(pubkeyOrPubkeyhash);
×
201
        if (User.getPubkeyhashes(user, true).contains(pubkeyOrPubkeyhash) || User.getPubkeyhashes(user, true).contains(hashed))
×
202
            l.add("approved");
×
203
        if (!l.isEmpty()) s += " (" + String.join("/", l) + ")";
×
204
        return s;
×
205
    }
206

207
    /**
208
     * Retrieves the name of the public key location based on the public key.
209
     *
210
     * @param pubkeyhash the public key string
211
     * @return the name of the public key location
212
     */
213
    public static String getPubkeyLocationName(String pubkeyhash) {
214
        return getPubkeyLocationName(pubkeyhash, getShortPubkeyName(pubkeyhash));
×
215
    }
216

217
    /**
218
     * Retrieves the name of the public key location, or returns a fallback name if not found.
219
     * If the key location is localhost, it returns "localhost".
220
     *
221
     * @param pubkeyhash the public key string
222
     * @param fallback   the fallback name to return if the key location is not found
223
     * @return the name of the public key location or the fallback name
224
     */
225
    public static String getPubkeyLocationName(String pubkeyhash, String fallback) {
226
        IRI keyLocation = User.getUserData().getKeyLocationForPubkeyhash(pubkeyhash);
×
227
        if (keyLocation == null) return fallback;
×
228
        if (keyLocation.stringValue().equals("http://localhost:37373/")) return "localhost";
×
229
        return keyLocation.stringValue().replaceFirst("https?://(nanobench\\.)?(nanodash\\.)?(.*[^/])/?$", "$3");
×
230
    }
231

232
    /**
233
     * Generates a short label for a public key location, including its status (local or approved).
234
     *
235
     * @param pubkeyhash the public key string
236
     * @param user       the IRI of the user associated with the public key
237
     * @return a short label indicating the public key location and its status
238
     */
239
    public static String getShortPubkeyLocationLabel(String pubkeyhash, IRI user) {
240
        String s = getPubkeyLocationName(pubkeyhash);
×
241
        NanodashSession session = NanodashSession.get();
×
242
        List<String> l = new ArrayList<>();
×
243
        if (pubkeyhash.equals(session.getPubkeyhash())) l.add("local");
×
244
        // TODO: Make this more efficient:
245
        if (User.getPubkeyhashes(user, true).contains(pubkeyhash)) l.add("approved");
×
246
        if (!l.isEmpty()) s += " (" + String.join("/", l) + ")";
×
247
        return s;
×
248
    }
249

250
    /**
251
     * Checks if a given public key has a Nanodash location.
252
     * A Nanodash location is identified by specific keywords in the key location.
253
     *
254
     * @param pubkeyhash the public key to check
255
     * @return true if the public key has a Nanodash location, false otherwise
256
     */
257
    public static boolean hasNanodashLocation(String pubkeyhash) {
258
        IRI keyLocation = User.getUserData().getKeyLocationForPubkeyhash(pubkeyhash);
×
259
        if (keyLocation == null) return true; // potentially a Nanodash location
×
260
        if (keyLocation.stringValue().contains("nanodash")) return true;
×
261
        if (keyLocation.stringValue().contains("nanobench")) return true;
×
262
        if (keyLocation.stringValue().contains(":37373")) return true;
×
263
        return false;
×
264
    }
265

266
    /**
267
     * Retrieves the short ORCID ID from an IRI object.
268
     *
269
     * @param orcidIri the IRI object representing the ORCID ID
270
     * @return the short ORCID ID as a string
271
     */
272
    public static String getShortOrcidId(IRI orcidIri) {
273
        return orcidIri.stringValue().replaceFirst("^https://orcid.org/", "");
6✔
274
    }
275

276
    /**
277
     * Retrieves the URI postfix from a given URI object.
278
     *
279
     * @param uri the URI object from which to extract the postfix
280
     * @return the URI postfix as a string
281
     */
282
    public static String getUriPostfix(Object uri) {
283
        String s = uri.toString();
3✔
284
        if (s.contains("#")) return s.replaceFirst("^.*#(.*)$", "$1");
9✔
285
        return s.replaceFirst("^.*/(.*)$", "$1");
5✔
286
    }
287

288
    /**
289
     * Retrieves the URI prefix from a given URI object.
290
     *
291
     * @param uri the URI object from which to extract the prefix
292
     * @return the URI prefix as a string
293
     */
294
    public static String getUriPrefix(Object uri) {
295
        String s = uri.toString();
3✔
296
        if (s.contains("#")) return s.replaceFirst("^(.*#).*$", "$1");
9✔
297
        return s.replaceFirst("^(.*/).*$", "$1");
5✔
298
    }
299

300
    /**
301
     * Checks if a given string is a valid URI postfix.
302
     * A valid URI postfix does not contain a colon (":").
303
     *
304
     * @param s the string to check
305
     * @return true if the string is a valid URI postfix, false otherwise
306
     */
307
    public static boolean isUriPostfix(String s) {
308
        return !s.contains(":");
8✔
309
    }
310

311
    /**
312
     * Retrieves the location of a given IntroNanopub.
313
     *
314
     * @param inp the IntroNanopub from which to extract the location
315
     * @return the IRI location of the nanopublication, or null if not found
316
     */
317
    public static IRI getLocation(IntroNanopub inp) {
318
        NanopubSignatureElement el = getNanopubSignatureElement(inp);
×
319
        for (KeyDeclaration kd : inp.getKeyDeclarations()) {
×
320
            if (el.getPublicKeyString().equals(kd.getPublicKeyString())) {
×
321
                return kd.getKeyLocation();
×
322
            }
323
        }
×
324
        return null;
×
325
    }
326

327
    /**
328
     * Retrieves the NanopubSignatureElement from a given IntroNanopub.
329
     *
330
     * @param inp the IntroNanopub from which to extract the signature element
331
     * @return the NanopubSignatureElement associated with the nanopublication
332
     */
333
    public static NanopubSignatureElement getNanopubSignatureElement(IntroNanopub inp) {
334
        try {
335
            return SignatureUtils.getSignatureElement(inp.getNanopub());
×
336
        } catch (MalformedCryptoElementException ex) {
×
337
            throw new RuntimeException(ex);
×
338
        }
339
    }
340

341
    /**
342
     * Retrieves a Nanopub object from a given URI if it is a potential Trusty URI.
343
     *
344
     * @param uri the URI to check and retrieve the Nanopub from
345
     * @return the Nanopub object if found, or null if not a known nanopublication
346
     */
347
    public static Nanopub getAsNanopub(String uri) {
348
        if (TrustyUriUtils.isPotentialTrustyUri(uri)) {
3!
349
            try {
350
                return Utils.getNanopub(uri);
3✔
351
            } catch (Exception ex) {
×
352
                logger.error("The given URI is not a known nanopublication: {}", uri, ex);
×
353
            }
354
        }
355
        return null;
×
356
    }
357

358
    private static final PolicyFactory htmlSanitizePolicy = new HtmlPolicyBuilder()
3✔
359
            .allowCommonBlockElements()
1✔
360
            .allowCommonInlineFormattingElements()
15✔
361
            .allowUrlProtocols("https", "http", "mailto")
7✔
362
            .allowElements("a")
7✔
363
            .allowAttributes("href").onElements("a")
14✔
364
            .allowElements("img")
7✔
365
            .allowAttributes("src").onElements("img")
8✔
366
            .requireRelNofollowOnLinks()
1✔
367
            .toFactory();
2✔
368

369
    /**
370
     * Sanitizes raw HTML input to ensure safe rendering.
371
     *
372
     * @param rawHtml the raw HTML input to sanitize
373
     * @return sanitized HTML string
374
     */
375
    public static String sanitizeHtml(String rawHtml) {
376
        return htmlSanitizePolicy.sanitize(rawHtml);
4✔
377
    }
378

379
    /**
380
     * Checks if a given string is likely to be HTML content.
381
     *
382
     * @param value the string to check
383
     * @return true if the given string is HTML content, false otherwise
384
     */
385
    public static boolean looksLikeHtml(String value) {
386
        return LEADING_TAG.matcher(value).find();
×
387
    }
388

389
    /**
390
     * Converts PageParameters to a URL-encoded string representation.
391
     *
392
     * @param params the PageParameters to convert
393
     * @return a string representation of the parameters in URL-encoded format
394
     */
395
    public static String getPageParametersAsString(PageParameters params) {
396
        String s = "";
2✔
397
        for (String n : params.getNamedKeys()) {
11✔
398
            if (!s.isEmpty()) s += "&";
6✔
399
            s += n + "=" + URLEncoder.encode(params.get(n).toString(), Charsets.UTF_8);
10✔
400
        }
1✔
401
        return s;
2✔
402
    }
403

404
    /**
405
     * Sets a minimal escape markup function for a Select2Choice component.
406
     * This function replaces certain characters and formats the display of choices.
407
     *
408
     * @param selectItem the Select2Choice component to set the escape markup for
409
     */
410
    public static void setSelect2ChoiceMinimalEscapeMarkup(Select2Choice<?> selectItem) {
411
        selectItem.getSettings().setEscapeMarkup("function(markup) {" +
×
412
                                                 "return markup" +
413
                                                 ".replaceAll('<','&lt;').replaceAll('>', '&gt;')" +
414
                                                 ".replace(/^(.*?) - /, '<span class=\"term\">$1</span><br>')" +
415
                                                 ".replace(/\\((https?:[\\S]+)\\)$/, '<br><code>$1</code>')" +
416
                                                 ".replace(/^([^<].*)$/, '<span class=\"term\">$1</span>')" +
417
                                                 ";}"
418
        );
419
    }
×
420

421
    /**
422
     * Checks if a nanopublication is of a specific class.
423
     *
424
     * @param np       the nanopublication to check
425
     * @param classIri the IRI of the class to check against
426
     * @return true if the nanopublication is of the specified class, false otherwise
427
     */
428
    public static boolean isNanopubOfClass(Nanopub np, IRI classIri) {
429
        return NanopubUtils.getTypes(np).contains(classIri);
5✔
430
    }
431

432
    /**
433
     * Checks if a nanopublication uses a specific predicate in its assertion.
434
     *
435
     * @param np           the nanopublication to check
436
     * @param predicateIri the IRI of the predicate to look for
437
     * @return true if the predicate is used in the assertion, false otherwise
438
     */
439
    public static boolean usesPredicateInAssertion(Nanopub np, IRI predicateIri) {
440
        for (Statement st : np.getAssertion()) {
11✔
441
            if (predicateIri.equals(st.getPredicate())) {
5✔
442
                return true;
2✔
443
            }
444
        }
1✔
445
        return false;
2✔
446
    }
447

448
    /**
449
     * Retrieves a map of FOAF names from the nanopublication's pubinfo.
450
     *
451
     * @param np the nanopublication from which to extract FOAF names
452
     * @return a map where keys are subjects and values are FOAF names
453
     */
454
    public static Map<String, String> getFoafNameMap(Nanopub np) {
455
        Map<String, String> foafNameMap = new HashMap<>();
4✔
456
        for (Statement st : np.getPubinfo()) {
11✔
457
            if (st.getPredicate().equals(FOAF.NAME) && st.getObject() instanceof Literal objL) {
14✔
458
                foafNameMap.put(st.getSubject().stringValue(), objL.stringValue());
8✔
459
            }
460
        }
1✔
461
        return foafNameMap;
2✔
462
    }
463

464
    /**
465
     * Creates an SHA-256 hash of the string representation of an object and returns it as a hexadecimal string.
466
     *
467
     * @param obj the object to hash
468
     * @return the SHA-256 hash of the object's string representation in hexadecimal format
469
     */
470
    public static String createSha256HexHash(Object obj) {
471
        return Hashing.sha256().hashString(obj.toString(), StandardCharsets.UTF_8).toString();
7✔
472
    }
473

474
    /**
475
     * Gets the types of a nanopublication.
476
     *
477
     * @param np the nanopublication from which to extract types
478
     * @return a list of IRI types associated with the nanopublication
479
     */
480
    public static List<IRI> getTypes(Nanopub np) {
481
        List<IRI> l = new ArrayList<>();
4✔
482
        for (IRI t : NanopubUtils.getTypes(np)) {
11✔
483
            if (t.equals(FIP.AVAILABLE_FAIR_ENABLING_RESOURCE)) continue;
5✔
484
            if (t.equals(FIP.FAIR_ENABLING_RESOURCE_TO_BE_DEVELOPED))
4✔
485
                continue;
1✔
486
            if (t.equals(FIP.AVAILABLE_FAIR_SUPPORTING_RESOURCE)) continue;
4!
487
            if (t.equals(FIP.FAIR_SUPPORTING_RESOURCE_TO_BE_DEVELOPED))
4!
488
                continue;
×
489
            l.add(t);
4✔
490
        }
1✔
491
        return l;
2✔
492
    }
493

494
    /**
495
     * Gets a label for a type IRI.
496
     *
497
     * @param typeIri the IRI of the type
498
     * @return a label for the type, potentially truncated
499
     */
500
    public static String getTypeLabel(IRI typeIri) {
501
        if (typeIri.equals(FIP.FAIR_ENABLING_RESOURCE)) return "FER";
6✔
502
        if (typeIri.equals(FIP.FAIR_SUPPORTING_RESOURCE)) return "FSR";
6✔
503
        if (typeIri.equals(FIP.FAIR_IMPLEMENTATION_PROFILE)) return "FIP";
6✔
504
        if (typeIri.equals(NPX.DECLARED_BY)) return "user intro";
6✔
505
        String l = typeIri.stringValue();
3✔
506
        l = l.replaceFirst("^.*[/#]([^/#]+)[/#]?$", "$1");
5✔
507
        l = l.replaceFirst("^(.+)Nanopub$", "$1");
5✔
508
        if (l.length() > 25) l = l.substring(0, 20) + "...";
10✔
509
        return l;
2✔
510
    }
511

512
    /**
513
     * Gets a label for a URI.
514
     *
515
     * @param uri the URI to get the label from
516
     * @return a label for the URI, potentially truncated
517
     */
518
    public static String getUriLabel(String uri) {
519
        if (uri == null) return "";
4✔
520
        String uriLabel = uri;
2✔
521
        if (uriLabel.matches(".*[^A-Za-z0-9-_]RA[A-Za-z0-9-_]{43}([^A-Za-z0-9-_].*)?")) {
4✔
522
            String newUriLabel = uriLabel.replaceFirst("(.*[^A-Za-z0-9-_]RA[A-Za-z0-9-_]{8})[A-Za-z0-9-_]{35}([^A-Za-z0-9-_].*)?", "$1...$2");
5✔
523
            if (newUriLabel.length() <= 70) return newUriLabel;
6!
524
        }
525
        if (uriLabel.length() > 70) return uri.substring(0, 30) + "..." + uri.substring(uri.length() - 30);
16✔
526
        return uriLabel;
2✔
527
    }
528

529
    /**
530
     * Gets an ExternalLink with a URI label.
531
     *
532
     * @param markupId the markup ID for the link
533
     * @param uri      the URI to link to
534
     * @return an ExternalLink with the URI label
535
     */
536
    public static ExternalLink getUriLink(String markupId, String uri) {
537
        return new ExternalLink(markupId, (Utils.isLocalURI(uri) ? "" : uri), getUriLabel(uri));
13✔
538
    }
539

540
    /**
541
     * Gets an ExternalLink with a model for the URI label.
542
     *
543
     * @param markupId the markup ID for the link
544
     * @param model    the model containing the URI
545
     * @return an ExternalLink with the URI label
546
     */
547
    public static ExternalLink getUriLink(String markupId, IModel<String> model) {
548
        return new ExternalLink(markupId, model, new UriLabelModel(model));
×
549
    }
550

551
    private static class UriLabelModel implements IModel<String> {
552

553
        private IModel<String> uriModel;
554

555
        public UriLabelModel(IModel<String> uriModel) {
×
556
            this.uriModel = uriModel;
×
557
        }
×
558

559
        @Override
560
        public String getObject() {
561
            return getUriLabel(uriModel.getObject());
×
562
        }
563

564
    }
565

566
    /**
567
     * Creates a sublist from a list based on the specified indices.
568
     *
569
     * @param list      the list from which to create the sublist
570
     * @param fromIndex the starting index (inclusive) for the sublist
571
     * @param toIndex   the ending index (exclusive) for the sublist
572
     * @param <E>       the type of elements in the list
573
     * @return an ArrayList containing the elements from the specified range
574
     */
575
    public static <E> ArrayList<E> subList(List<E> list, long fromIndex, long toIndex) {
576
        // So the resulting list is serializable:
577
        return new ArrayList<E>(list.subList((int) fromIndex, (int) toIndex));
×
578
    }
579

580
    /**
581
     * Creates a sublist from an array based on the specified indices.
582
     *
583
     * @param array     the array from which to create the sublist
584
     * @param fromIndex the starting index (inclusive) for the sublist
585
     * @param toIndex   the ending index (exclusive) for the sublist
586
     * @param <E>       the type of elements in the array
587
     * @return an ArrayList containing the elements from the specified range
588
     */
589
    public static <E> ArrayList<E> subList(E[] array, long fromIndex, long toIndex) {
590
        return subList(Arrays.asList(array), fromIndex, toIndex);
×
591
    }
592

593
    /**
594
     * Comparator for sorting ApiResponseEntry objects based on a specified field.
595
     */
596
    // TODO Move this to ApiResponseEntry class?
597
    public static class ApiResponseEntrySorter implements Comparator<ApiResponseEntry>, Serializable {
598

599
        private String field;
600
        private boolean descending;
601

602
        /**
603
         * Constructor for ApiResponseEntrySorter.
604
         *
605
         * @param field      the field to sort by
606
         * @param descending if true, sorts in descending order; if false, sorts in ascending order
607
         */
608
        public ApiResponseEntrySorter(String field, boolean descending) {
×
609
            this.field = field;
×
610
            this.descending = descending;
×
611
        }
×
612

613
        /**
614
         * Compares two ApiResponseEntry objects based on the specified field.
615
         *
616
         * @param o1 the first object to be compared.
617
         * @param o2 the second object to be compared.
618
         * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
619
         */
620
        @Override
621
        public int compare(ApiResponseEntry o1, ApiResponseEntry o2) {
622
            if (descending) {
×
623
                return o2.get(field).compareTo(o1.get(field));
×
624
            } else {
625
                return o1.get(field).compareTo(o2.get(field));
×
626
            }
627
        }
628

629
    }
630

631
    /**
632
     * MIME type for TriG RDF format.
633
     */
634
    public static final String TYPE_TRIG = "application/trig";
635

636
    /**
637
     * MIME type for Jelly RDF format.
638
     */
639
    public static final String TYPE_JELLY = "application/x-jelly-rdf";
640

641
    /**
642
     * MIME type for JSON-LD format.
643
     */
644
    public static final String TYPE_JSONLD = "application/ld+json";
645

646
    /**
647
     * MIME type for N-Quads format.
648
     */
649
    public static final String TYPE_NQUADS = "application/n-quads";
650

651
    /**
652
     * MIME type for Trix format.
653
     */
654
    public static final String TYPE_TRIX = "application/trix";
655

656
    /**
657
     * MIME type for HTML format.
658
     */
659
    public static final String TYPE_HTML = "text/html";
660

661
    /**
662
     * Comma-separated list of supported MIME types for nanopublications.
663
     */
664
    public static final String SUPPORTED_TYPES =
665
            TYPE_TRIG + "," +
666
            TYPE_JELLY + "," +
667
            TYPE_JSONLD + "," +
668
            TYPE_NQUADS + "," +
669
            TYPE_TRIX + "," +
670
            TYPE_HTML;
671

672
    /**
673
     * List of supported MIME types for nanopublications.
674
     */
675
    public static final List<String> SUPPORTED_TYPES_LIST = Arrays.asList(StringUtils.split(SUPPORTED_TYPES, ','));
6✔
676

677
    // TODO Move these to nanopub-java library:
678

679
    /**
680
     * Retrieves a set of introduced IRI IDs from the nanopublication.
681
     *
682
     * @param np the nanopublication from which to extract introduced IRI IDs
683
     * @return a set of introduced IRI IDs
684
     */
685
    public static Set<String> getIntroducedIriIds(Nanopub np) {
686
        Set<String> introducedIriIds = new HashSet<>();
4✔
687
        for (Statement st : np.getPubinfo()) {
11✔
688
            if (!st.getSubject().equals(np.getUri())) continue;
7✔
689
            if (!st.getPredicate().equals(NPX.INTRODUCES)) continue;
6✔
690
            if (st.getObject() instanceof IRI obj) introducedIriIds.add(obj.stringValue());
14✔
691
        }
1✔
692
        return introducedIriIds;
2✔
693
    }
694

695
    /**
696
     * Retrieves a set of embedded IRI IDs from the nanopublication.
697
     *
698
     * @param np the nanopublication from which to extract embedded IRI IDs
699
     * @return a set of embedded IRI IDs
700
     */
701
    public static Set<String> getEmbeddedIriIds(Nanopub np) {
702
        Set<String> embeddedIriIds = new HashSet<>();
4✔
703
        for (Statement st : np.getPubinfo()) {
11✔
704
            if (!st.getSubject().equals(np.getUri())) continue;
7✔
705
            if (!st.getPredicate().equals(NPX.EMBEDS)) continue;
6✔
706
            if (st.getObject() instanceof IRI obj) embeddedIriIds.add(obj.stringValue());
14✔
707
        }
1✔
708
        return embeddedIriIds;
2✔
709
    }
710

711
    /**
712
     * Returns the URL of the default Nanopub Registry as configured by the given instance.
713
     *
714
     * @return Nanopub Registry URL
715
     */
716
    public static String getMainRegistryUrl() {
717
        try {
718
            return EnvironmentUtils.getProcEnvironment().getOrDefault("NANODASH_MAIN_REGISTRY", "https://registry.knowledgepixels.com/");
6✔
719
        } catch (IOException ex) {
×
720
            logger.error("Could not get NANODASH_MAIN_REGISTRY environment variable, using default.", ex);
×
721
            return "https://registry.knowledgepixels.com/";
×
722
        }
723
    }
724

725
    private static final String PLAIN_LITERAL_PATTERN = "^\"(([^\\\\\\\"]|\\\\\\\\|\\\\\")*)\"";
726
    private static final String LANGTAG_LITERAL_PATTERN = "^\"(([^\\\\\\\"]|\\\\\\\\|\\\\\")*)\"@([0-9a-zA-Z-]{2,})$";
727
    private static final String DATATYPE_LITERAL_PATTERN = "^\"(([^\\\\\\\"]|\\\\\\\\|\\\\\")*)\"\\^\\^<([^ ><\"^]+)>";
728

729
    /**
730
     * Checks whether string is valid literal serialization.
731
     *
732
     * @param literalString the literal string
733
     * @return true if valid
734
     */
735
    public static boolean isValidLiteralSerialization(String literalString) {
736
        if (literalString.matches(PLAIN_LITERAL_PATTERN)) {
4✔
737
            return true;
2✔
738
        } else if (literalString.matches(LANGTAG_LITERAL_PATTERN)) {
4✔
739
            return true;
2✔
740
        } else if (literalString.matches(DATATYPE_LITERAL_PATTERN)) {
4✔
741
            return true;
2✔
742
        }
743
        return false;
2✔
744
    }
745

746
    /**
747
     * Returns a serialized version of the literal.
748
     *
749
     * @param literal the literal
750
     * @return the String serialization of the literal
751
     */
752
    public static String getSerializedLiteral(Literal literal) {
753
        if (literal.getLanguage().isPresent()) {
4✔
754
            return "\"" + getEscapedLiteralString(literal.stringValue()) + "\"@" + Literals.normalizeLanguageTag(literal.getLanguage().get());
10✔
755
        } else if (literal.getDatatype().equals(XSD.STRING)) {
5✔
756
            return "\"" + getEscapedLiteralString(literal.stringValue()) + "\"";
5✔
757
        } else {
758
            return "\"" + getEscapedLiteralString(literal.stringValue()) + "\"^^<" + literal.getDatatype() + ">";
8✔
759
        }
760
    }
761

762
    /**
763
     * Parses a serialized literal into a Literal object.
764
     *
765
     * @param serializedLiteral The serialized String of the literal
766
     * @return The parse Literal object
767
     */
768
    public static Literal getParsedLiteral(String serializedLiteral) {
769
        if (serializedLiteral.matches(PLAIN_LITERAL_PATTERN)) {
4✔
770
            return vf.createLiteral(getUnescapedLiteralString(serializedLiteral.replaceFirst(PLAIN_LITERAL_PATTERN, "$1")));
8✔
771
        } else if (serializedLiteral.matches(LANGTAG_LITERAL_PATTERN)) {
4✔
772
            String langtag = serializedLiteral.replaceFirst(LANGTAG_LITERAL_PATTERN, "$3");
5✔
773
            return vf.createLiteral(getUnescapedLiteralString(serializedLiteral.replaceFirst(LANGTAG_LITERAL_PATTERN, "$1")), langtag);
9✔
774
        } else if (serializedLiteral.matches(DATATYPE_LITERAL_PATTERN)) {
4✔
775
            IRI datatype = vf.createIRI(serializedLiteral.replaceFirst(DATATYPE_LITERAL_PATTERN, "$3"));
7✔
776
            return vf.createLiteral(getUnescapedLiteralString(serializedLiteral.replaceFirst(DATATYPE_LITERAL_PATTERN, "$1")), datatype);
9✔
777
        }
778
        throw new IllegalArgumentException("Not a valid literal serialization: " + serializedLiteral);
6✔
779
    }
780

781
    /**
782
     * Escapes quotes (") and slashes (/) of a literal string.
783
     *
784
     * @param unescapedString un-escaped string
785
     * @return escaped string
786
     */
787
    public static String getEscapedLiteralString(String unescapedString) {
788
        return unescapedString.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\"");
8✔
789
    }
790

791
    /**
792
     * Un-escapes quotes (") and slashes (/) of a literal string.
793
     *
794
     * @param escapedString escaped string
795
     * @return un-escaped string
796
     */
797
    public static String getUnescapedLiteralString(String escapedString) {
798
        return escapedString.replaceAll("\\\\(\\\\|\\\")", "$1");
5✔
799
    }
800

801
    /**
802
     * Checks if a given IRI is a local URI.
803
     *
804
     * @param uri the IRI to check
805
     * @return true if the IRI is a local URI, false otherwise
806
     */
807
    public static boolean isLocalURI(IRI uri) {
808
        return uri != null && isLocalURI(uri.stringValue());
10✔
809
    }
810

811
    /**
812
     * Checks if a given string is a local URI.
813
     *
814
     * @param uriAsString the string to check
815
     * @return true if the string is a local URI, false otherwise
816
     */
817
    public static boolean isLocalURI(String uriAsString) {
818
        return !uriAsString.isBlank() && uriAsString.startsWith(LocalUri.PREFIX);
11✔
819
    }
820

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