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

knowledgepixels / nanodash / 25678638590

11 May 2026 03:08PM UTC coverage: 20.512% (+2.2%) from 18.296%
25678638590

push

github

web-flow
Merge pull request #455 from knowledgepixels/fix-repetition-resets-edited-values

fix: don't reseed shared model from URL param on repetition (#271)

1013 of 6236 branches covered (16.24%)

Branch coverage included in aggregate %.

2592 of 11339 relevant lines covered (22.86%)

3.28 hits per line

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

21.4
src/main/java/com/knowledgepixels/nanodash/component/IriTextfieldItem.java
1
package com.knowledgepixels.nanodash.component;
2

3
import com.knowledgepixels.nanodash.LocalUri;
4
import com.knowledgepixels.nanodash.Utils;
5
import com.knowledgepixels.nanodash.template.Template;
6
import com.knowledgepixels.nanodash.template.TemplateContext;
7
import com.knowledgepixels.nanodash.template.UnificationException;
8
import net.trustyuri.TrustyUriUtils;
9
import org.apache.wicket.AttributeModifier;
10
import org.apache.wicket.Component;
11
import org.apache.wicket.ajax.AjaxRequestTarget;
12
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
13
import org.apache.wicket.behavior.AttributeAppender;
14
import org.apache.wicket.markup.html.basic.Label;
15
import org.apache.wicket.markup.html.form.TextField;
16
import org.apache.wicket.markup.html.link.ExternalLink;
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
import org.slf4j.Logger;
29
import org.slf4j.LoggerFactory;
30

31
import java.net.URISyntaxException;
32

33
/**
34
 * A text field for entering IRIs, with a prefix label and validation.
35
 */
36
public class IriTextfieldItem extends AbstractContextComponent {
37

38
    private String prefix;
39
    private TextField<String> textfield;
40
    private IRI iri;
41
    private static final Logger logger = LoggerFactory.getLogger(IriTextfieldItem.class);
12✔
42

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

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

117
        });
118
        if (template.isIntroducedResource(iri) || template.isEmbeddedResource(iri)) {
30!
119
            textfield.add(AttributeAppender.append("class", "introduced"));
×
120
        }
121
        add(textfield);
30✔
122
    }
3✔
123

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

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

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

182
    /**
183
     * Validator class for validating IRI text fields.
184
     */
185
    protected static class Validator extends InvalidityHighlighting implements IValidator<String> {
186

187
        private IRI iri;
188
        private Template template;
189
        private String prefix;
190
        private TemplateContext context;
191

192
        /**
193
         * Constructor for creating a validator for an IRI text field.
194
         *
195
         * @param iri      the IRI placeholder for this item
196
         * @param template the template containing validation rules
197
         * @param prefix   the prefix to be used in validation
198
         * @param context  the template context containing models and components
199
         */
200
        public Validator(IRI iri, Template template, String prefix, TemplateContext context) {
6✔
201
            this.iri = iri;
9✔
202
            this.template = template;
9✔
203
            this.prefix = prefix;
9✔
204
            this.context = context;
9✔
205
        }
3✔
206

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

265
    }
266

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

274
    /**
275
     * {@inheritDoc}
276
     */
277
    @Override
278
    public void finalizeValues() {
279
        Value defaultValue = context.getTemplate().getDefault(iri);
×
280
        if (isUnifiableWith(defaultValue)) {
×
281
            try {
282
                unifyWith(defaultValue);
×
283
            } catch (UnificationException ex) {
×
284
                logger.error("Could not unify default value {} with text field {}", defaultValue, this, ex);
×
285
            }
×
286
        }
287
    }
×
288

289
    /**
290
     * <p>toString.</p>
291
     *
292
     * @return a {@link java.lang.String} object
293
     */
294
    public String toString() {
295
        return "[IRI textfield item: " + iri + "]";
×
296
    }
297

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