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

knowledgepixels / nanodash / 17302137193

28 Aug 2025 04:35PM UTC coverage: 11.965% (-0.4%) from 12.355%
17302137193

Pull #244

github

web-flow
Merge 4e969b0ee into 3323a35f1
Pull Request #244: Use vocabularies with latest version of `nanopub-java`

331 of 3840 branches covered (8.62%)

Branch coverage included in aggregate %.

943 of 6808 relevant lines covered (13.85%)

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/IriTextfieldItem.java
1
package com.knowledgepixels.nanodash.component;
2

3
import com.knowledgepixels.nanodash.Utils;
4
import com.knowledgepixels.nanodash.template.Template;
5
import com.knowledgepixels.nanodash.template.TemplateContext;
6
import com.knowledgepixels.nanodash.template.UnificationException;
7
import net.trustyuri.TrustyUriUtils;
8
import org.apache.wicket.AttributeModifier;
9
import org.apache.wicket.Component;
10
import org.apache.wicket.ajax.AjaxRequestTarget;
11
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
12
import org.apache.wicket.behavior.AttributeAppender;
13
import org.apache.wicket.markup.html.basic.Label;
14
import org.apache.wicket.markup.html.form.TextField;
15
import org.apache.wicket.markup.html.link.ExternalLink;
16
import org.apache.wicket.markup.html.panel.Panel;
17
import org.apache.wicket.model.IModel;
18
import org.apache.wicket.model.Model;
19
import org.apache.wicket.validation.IValidatable;
20
import org.apache.wicket.validation.IValidator;
21
import org.apache.wicket.validation.Validatable;
22
import org.apache.wicket.validation.ValidationError;
23
import org.eclipse.rdf4j.common.net.ParsedIRI;
24
import org.eclipse.rdf4j.model.IRI;
25
import org.eclipse.rdf4j.model.Value;
26
import org.nanopub.SimpleCreatorPattern;
27
import org.nanopub.vocabulary.NTEMPLATE;
28

29
import java.net.URISyntaxException;
30

31
/**
32
 * A text field for entering IRIs, with a prefix label and validation.
33
 */
34
public class IriTextfieldItem extends Panel implements ContextComponent {
35

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

38
    private static final long serialVersionUID = 1L;
39

40
    private String prefix;
41
    private TemplateContext context;
42
    private TextField<String> textfield;
43
    private IRI iri;
44

45
    /**
46
     * Constructor for creating an IRI text field item.
47
     *
48
     * @param id       the component ID
49
     * @param parentId the parent ID (e.g., "subj", "pred", "obj")
50
     * @param iriP     the IRI placeholder for this item
51
     * @param optional whether the field is optional
52
     * @param context  the template context containing models and components
53
     */
54
    public IriTextfieldItem(String id, String parentId, final IRI iriP, boolean optional, final TemplateContext context) {
55
        super(id);
×
56
        this.context = context;
×
57
        this.iri = iriP;
×
58
        final Template template = context.getTemplate();
×
59
        IModel<String> model = context.getComponentModels().get(iri);
×
60
        if (model == null) {
×
61
            model = Model.of("");
×
62
            context.getComponentModels().put(iri, model);
×
63
        }
64
        String postfix = Utils.getUriPostfix(iri);
×
65
        if (context.hasParam(postfix)) {
×
66
            model.setObject(context.getParam(postfix));
×
67
        }
68
        prefix = template.getPrefix(iri);
×
69
        if (prefix == null) prefix = "";
×
70
        if (template.isLocalResource(iri)) {
×
71
            prefix = Utils.getUriPrefix(iri);
×
72
        }
73
        String prefixLabel = template.getPrefixLabel(iri);
×
74
        Label prefixLabelComp;
75
        if (prefixLabel == null) {
×
76
            prefixLabelComp = new Label("prefix", "");
×
77
            prefixLabelComp.setVisible(false);
×
78
        } else {
79
            if (prefixLabel.length() > 0 && parentId.equals("subj") && !prefixLabel.matches("https?://.*")) {
×
80
                // Capitalize first letter of label if at subject position:
81
                prefixLabel = prefixLabel.substring(0, 1).toUpperCase() + prefixLabel.substring(1);
×
82
            }
83
            prefixLabelComp = new Label("prefix", prefixLabel);
×
84
        }
85
        add(prefixLabelComp);
×
86
        String prefixTooltip = prefix;
×
87
        if (!prefix.isEmpty()) {
×
88
            prefixTooltip += "...";
×
89
            if (template.isLocalResource(iri)) {
×
90
                prefixTooltip = "local:...";
×
91
            }
92
        }
93
        add(new ExternalLink("prefixtooltiptext", "", prefixTooltip));
×
94
        textfield = new TextField<>("textfield", model);
×
95
        if (!optional) textfield.setRequired(true);
×
96
        if (template.isLocalResource(iri)) {
×
97
            textfield.add(new AttributeAppender("class", " local"));
×
98
        }
99
        textfield.add(new Validator(iri, template, prefix, context));
×
100
        context.getComponents().add(textfield);
×
101
        if (template.getLabel(iri) != null) {
×
102
            textfield.add(new AttributeModifier("placeholder", template.getLabel(iri).replaceFirst(" - .*$", "")));
×
103
            textfield.setLabel(Model.of(template.getLabel(iri)));
×
104
        }
105
        textfield.add(new OnChangeAjaxBehavior() {
×
106

107
            private static final long serialVersionUID = 1L;
108

109
            @Override
110
            protected void onUpdate(AjaxRequestTarget target) {
111
                for (Component c : context.getComponents()) {
×
112
                    if (c == textfield) continue;
×
113
                    if (c.getDefaultModel() == textfield.getModel()) {
×
114
                        c.modelChanged();
×
115
                        target.add(c);
×
116
                    }
117
                }
×
118
            }
×
119

120
        });
121
        if (template.isIntroducedResource(iri) || template.isEmbeddedResource(iri)) {
×
122
            textfield.add(AttributeAppender.append("class", "introduced"));
×
123
        }
124
        add(textfield);
×
125
    }
×
126

127
    /**
128
     * {@inheritDoc}
129
     */
130
    @Override
131
    public void removeFromContext() {
132
        context.getComponents().remove(textfield);
×
133
    }
×
134

135
    /**
136
     * {@inheritDoc}
137
     */
138
    @Override
139
    public boolean isUnifiableWith(Value v) {
140
        if (v == null) return true;
×
141
        if (v instanceof IRI) {
×
142
            String vs = v.stringValue();
×
143
            if (vs.startsWith(prefix)) vs = vs.substring(prefix.length());
×
144
            if (vs.startsWith("local:")) vs = vs.replaceFirst("^local:", "");
×
145
            if (context.getTemplate().isAutoEscapePlaceholder(iri)) {
×
146
                vs = Utils.urlDecode(vs);
×
147
            }
148
            Validatable<String> validatable = new Validatable<>(vs);
×
149
            if (context.getTemplate().isLocalResource(iri) && !Utils.isUriPostfix(vs)) {
×
150
                vs = Utils.getUriPostfix(vs);
×
151
            }
152
            new Validator(iri, context.getTemplate(), prefix, context).validate(validatable);
×
153
            if (!validatable.isValid()) {
×
154
                return false;
×
155
            }
156
            if (textfield.getModelObject().isEmpty()) {
×
157
                return true;
×
158
            }
159
            return vs.equals(textfield.getModelObject());
×
160
        }
161
        return false;
×
162
    }
163

164
    /**
165
     * {@inheritDoc}
166
     */
167
    @Override
168
    public void unifyWith(Value v) throws UnificationException {
169
        if (v == null) return;
×
170
        String vs = v.stringValue();
×
171
        if (!isUnifiableWith(v)) throw new UnificationException(vs);
×
172
        if (!prefix.isEmpty() && vs.startsWith(prefix)) {
×
173
            vs = vs.substring(prefix.length());
×
174
        } else if (vs.startsWith("local:")) {
×
175
            vs = vs.replaceFirst("^local:", "");
×
176
        } else if (context.getTemplate().isLocalResource(iri) && !Utils.isUriPostfix(vs)) {
×
177
            vs = Utils.getUriPostfix(vs);
×
178
        }
179
        if (context.getTemplate().isAutoEscapePlaceholder(iri)) {
×
180
            vs = Utils.urlDecode(vs);
×
181
        }
182
        textfield.setModelObject(vs);
×
183
    }
×
184

185
    /**
186
     * Validator class for validating IRI text fields.
187
     */
188
    protected static class Validator extends InvalidityHighlighting implements IValidator<String> {
189

190
        private static final long serialVersionUID = 1L;
191

192
        private IRI iri;
193
        private Template template;
194
        private String prefix;
195
        private TemplateContext context;
196

197
        /**
198
         * Constructor for creating a validator for an IRI text field.
199
         *
200
         * @param iri      the IRI placeholder for this item
201
         * @param template the template containing validation rules
202
         * @param prefix   the prefix to be used in validation
203
         * @param context  the template context containing models and components
204
         */
205
        public Validator(IRI iri, Template template, String prefix, TemplateContext context) {
×
206
            this.iri = iri;
×
207
            this.template = template;
×
208
            this.prefix = prefix;
×
209
            this.context = context;
×
210
        }
×
211

212
        @Override
213
        public void validate(IValidatable<String> s) {
214
            String sv = s.getValue();
×
215
            String p = prefix;
×
216
            if (template.isAutoEscapePlaceholder(iri)) {
×
217
                sv = Utils.urlEncode(sv);
×
218
            }
219
            if (sv.matches("https?://.+")) {
×
220
                p = "";
×
221
            } else if (sv.contains(":")) {
×
222
                s.error(new ValidationError("Colon character is not allowed in postfix"));
×
223
            }
224
            String iriString = p + sv;
×
225
            if (iriString.matches("[^:# ]+")) {
×
226
                p = "local:";
×
227
                iriString = p + sv;
×
228
            }
229
            try {
230
                ParsedIRI piri = new ParsedIRI(iriString);
×
231
                if (!piri.isAbsolute()) {
×
232
                    s.error(new ValidationError("IRI not well-formed"));
×
233
                }
234
                if (p.isEmpty() && !sv.startsWith("local:") && !sv.matches("https?://.+")) {
×
235
                    s.error(new ValidationError("Only http(s):// IRIs are allowed here"));
×
236
                }
237
            } catch (URISyntaxException ex) {
×
238
                s.error(new ValidationError("IRI not well-formed"));
×
239
            }
×
240
            String regex = template.getRegex(iri);
×
241
            if (regex != null) {
×
242
                if (!sv.matches(regex)) {
×
243
                    s.error(new ValidationError("Value '" + sv + "' doesn't match the pattern '" + regex + "'"));
×
244
                }
245
            }
246
            if (template.isExternalUriPlaceholder(iri)) {
×
247
                if (!iriString.matches("https?://.+")) {
×
248
                    s.error(new ValidationError("Not an external IRI"));
×
249
                }
250
            }
251
            if (template.isTrustyUriPlaceholder(iri)) {
×
252
                if (!TrustyUriUtils.isPotentialTrustyUri(iriString)) {
×
253
                    s.error(new ValidationError("Not a trusty URI"));
×
254
                }
255
            }
256
            if (iri.equals(NTEMPLATE.CREATOR_PLACEHOLDER) && context.getExistingNanopub() != null) {
×
257
                boolean found = false;
×
258
                for (IRI creator : SimpleCreatorPattern.getCreators(context.getExistingNanopub())) {
×
259
                    if (creator.stringValue().equals(iriString)) {
×
260
                        found = true;
×
261
                        break;
×
262
                    }
263
                }
×
264
                if (!found) {
×
265
                    s.error(new ValidationError("Not a creator of nanopub"));
×
266
                }
267
            }
268
        }
×
269

270
    }
271

272
    /**
273
     * {@inheritDoc}
274
     */
275
    @Override
276
    public void fillFinished() {
277
    }
×
278

279
    /**
280
     * {@inheritDoc}
281
     */
282
    @Override
283
    public void finalizeValues() {
284
        Value defaultValue = context.getTemplate().getDefault(iri);
×
285
        if (isUnifiableWith(defaultValue)) {
×
286
            try {
287
                unifyWith(defaultValue);
×
288
            } catch (UnificationException ex) {
×
289
                ex.printStackTrace();
×
290
            }
×
291
        }
292
    }
×
293

294
    /**
295
     * <p>toString.</p>
296
     *
297
     * @return a {@link java.lang.String} object
298
     */
299
    public String toString() {
300
        return "[IRI textfield item: " + iri + "]";
×
301
    }
302

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