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

pkiraly / metadata-qa-marc / #1527

22 Aug 2025 02:21PM UTC coverage: 90.345%. Remained the same
#1527

push

pkiraly
Improve timeline handling

5191 of 6416 new or added lines in 219 files covered. (80.91%)

886 existing lines in 78 files now uncovered.

36717 of 40641 relevant lines covered (90.34%)

0.9 hits per line

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

88.31
/src/main/java/de/gwdg/metadataqa/marc/analysis/validator/SubfieldValidator.java
1
package de.gwdg.metadataqa.marc.analysis.validator;
2

3
import de.gwdg.metadataqa.marc.MarcSubfield;
4
import de.gwdg.metadataqa.marc.definition.ValidatorResponse;
5
import de.gwdg.metadataqa.marc.definition.general.parser.ParserException;
6
import de.gwdg.metadataqa.marc.definition.general.parser.SubfieldContentParser;
7
import de.gwdg.metadataqa.marc.definition.structure.ControlfieldPositionDefinition;
8
import de.gwdg.metadataqa.marc.definition.structure.DataFieldDefinition;
9
import de.gwdg.metadataqa.marc.definition.structure.SubfieldDefinition;
10
import de.gwdg.metadataqa.marc.model.validation.ErrorsCollector;
11
import de.gwdg.metadataqa.marc.model.validation.ValidationErrorType;
12

13
import java.util.ArrayList;
14

15
import static de.gwdg.metadataqa.marc.model.validation.ValidationErrorType.SUBFIELD_NULL_CODE;
16
import static de.gwdg.metadataqa.marc.model.validation.ValidationErrorType.SUBFIELD_UNDEFINED;
17
import static de.gwdg.metadataqa.marc.model.validation.ValidationErrorType.SUBFIELD_UNPARSABLE_CONTENT;
18

19
public class SubfieldValidator extends AbstractValidator {
20

21
  private ErrorsCollector errors;
22
  private SubfieldDefinition definition;
23
  private DataFieldDefinition fieldDefinition;
24
  private MarcSubfield subfield;
25

26
  public SubfieldValidator() {
27
    super(new ValidatorConfiguration());
1✔
28
  }
1✔
29

30
  public SubfieldValidator(ValidatorConfiguration configuration) {
31
    super(configuration);
1✔
32
  }
1✔
33

34
  public boolean validate(MarcSubfield subfield) {
35
    this.subfield = subfield;
1✔
36

37
    errors = new ErrorsCollector();
1✔
38
    validationErrors = new ArrayList<>();
1✔
39
    definition = subfield.getDefinition();
1✔
40
    fieldDefinition = subfield.getField().getDefinition();
1✔
41

42
    if (definition == null) {
1✔
43
      addError(fieldDefinition.getExtendedTag(), SUBFIELD_UNDEFINED, subfield.getCode());
1✔
44
      validationErrors.addAll(errors.getErrors());
1✔
45
      return false;
1✔
46
    }
47

48
    if (subfield.getCode() == null) {
1✔
NEW
49
      addError(subfield.getField().getDefinition().getTag(), SUBFIELD_NULL_CODE, subfield.getCode());
×
NEW
50
      validationErrors.addAll(errors.getErrors());
×
NEW
51
      return false;
×
52
    }
53

54
    if (definition.isDisallowedIn(configuration.getMarcVersion())) {
1✔
55
      addError(subfield.getField().getDefinition().getTag(), SUBFIELD_UNDEFINED, subfield.getCode());
1✔
56
      validationErrors.addAll(errors.getErrors());
1✔
57
      return false;
1✔
58
    }
59

60
    if (definition.hasValidator()) {
1✔
61
      validateWithValidator();
1✔
62
    } else if (definition.hasContentParser()) {
1✔
63
      validateWithParser();
1✔
64
    } else if (definition.getCodes() != null && !definition.getCodes().isEmpty() && definition.getCode(subfield.getValue()) == null) {
1✔
65
      // If a subfield has a list of codes defined, and the value is not in the codelist, then it is invalid
66
      String message = subfield.getValue();
1✔
67
      if (subfield.getReferencePath() != null) {
1✔
NEW
68
        message += String.format(" (the field is embedded in %s)", subfield.getReferencePath());
×
69
      }
70
      String path = subfield.getReferencePath() == null
1✔
71
        ? definition.getPath()
1✔
72
        : subfield.getReferencePath() + "->" + definition.getPath();
1✔
73
      addError(path, ValidationErrorType.SUBFIELD_INVALID_VALUE, message);
1✔
74
    /*
75
    } else if (definition.getCodeList() != null &&
76
               !definition.getCodeList().isValid(value)) {
77
      String message = value;
78
      if (referencePath != null) {
79
        message += String.format(" (the field is embedded in %s)", referencePath);
80
      }
81
      String path = (referencePath == null
82
        ? definition.getPath()
83
        : referencePath + "->" + definition.getPath());
84
      addError(path, ValidationErrorType.SUBFIELD_INVALID_VALUE, message);
85
      isValid = false;
86
    */
87
    } else if (definition.hasPositions()) {
1✔
88
      validatePositions();
1✔
89
    }
90

91
    validationErrors.addAll(errors.getErrors());
1✔
92
    return errors.isEmpty();
1✔
93
  }
94

95
  private void validatePositions() {
96
    for (ControlfieldPositionDefinition positionDefinition : definition.getPositions()) {
1✔
97
      validatePosition(positionDefinition);
1✔
98
    }
1✔
99
  }
1✔
100

101
  private void validatePosition(ControlfieldPositionDefinition positionDefinition) {
102
    String subfieldValue = subfield.getValue();
1✔
103
    // If the subfield value is shorter than the position definition, then it is invalid
104
    if (subfieldValue.length() < positionDefinition.getPositionEnd()) {
1✔
NEW
105
      String path = String.format("%s/%s", definition.getPath(), positionDefinition.formatPositon());
×
NEW
106
      String errorMessage = String.format("invalid code for '%s': '%s' position %s out of range",
×
NEW
107
          positionDefinition.getLabel(), subfieldValue, positionDefinition.formatPositon());
×
108

NEW
109
      addError(path, ValidationErrorType.SUBFIELD_INVALID_VALUE, errorMessage);
×
NEW
110
      return;
×
111
    }
112
    String positionValue = subfield.getValue().substring(positionDefinition.getPositionStart(), positionDefinition.getPositionEnd());
1✔
113
    boolean isPositionDefinitionValid = positionDefinition.validate(positionValue);
1✔
114
    if (isPositionDefinitionValid) {
1✔
115
      return;
1✔
116
    }
117

118
    String path = String.format("%s/%s", definition.getPath(), positionDefinition.formatPositon());
1✔
119
    String errorMessage = String.format("invalid code for '%s': '%s' at position %s in '%s'",
1✔
120
        positionDefinition.getLabel(), positionValue, positionDefinition.formatPositon(), subfield.getValue());
1✔
121

122
    addError(path, ValidationErrorType.SUBFIELD_INVALID_VALUE, errorMessage);
1✔
123
  }
1✔
124

125
  /**
126
   * Validates a subfield by the given subfield validator which is assigned to the subfield definition in code.
127
   * @return True if the subfield is valid, false otherwise.
128
   */
129
  private boolean validateWithValidator() {
130
    de.gwdg.metadataqa.marc.definition.general.validator.SubfieldValidator validator = definition.getValidator();
1✔
131
    ValidatorResponse response = validator.isValid(subfield);
1✔
132
    if (!response.isValid()) {
1✔
133
      errors.addAll(response.getValidationErrors());
1✔
134
    }
135
    return response.isValid();
1✔
136
  }
137

138
  /**
139
   * Uses a similar approach as the subfield validator in the subfield definition trying to parse the subfield content.
140
   * @return True if the subfield is valid, false otherwise.
141
   */
142
  private boolean validateWithParser() {
143
    var isValid = true;
1✔
144
    SubfieldContentParser parser = definition.getContentParser();
1✔
145
    try {
146
      parser.parse(subfield.getValue());
1✔
147
    } catch (ParserException e) {
1✔
148
      addError(SUBFIELD_UNPARSABLE_CONTENT, e.getMessage());
1✔
149
      isValid = false;
1✔
150
    }
1✔
151
    return isValid;
1✔
152
  }
153

154
  private void addError(ValidationErrorType type, String message) {
155
    addError(definition.getPath(), type, message);
1✔
156
  }
1✔
157

158
  private void addError(String path, ValidationErrorType type, String message) {
159
    if (!isIgnorableType(type)) {
1✔
160
      String id = subfield.getMarcRecord() == null ? null : subfield.getMarcRecord().getId();
1✔
161
      String url = fieldDefinition.getDescriptionUrl();
1✔
162
      errors.add(id, path, type, message, url);
1✔
163
    }
164
  }
1✔
165
}
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