• 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

76.67
/src/main/java/de/gwdg/metadataqa/marc/analysis/validator/Marc21LinkageHandler.java
1
package de.gwdg.metadataqa.marc.analysis.validator;
2

3
import de.gwdg.metadataqa.marc.MarcSubfield;
4
import de.gwdg.metadataqa.marc.dao.DataField;
5
import de.gwdg.metadataqa.marc.definition.TagDefinitionLoader;
6
import de.gwdg.metadataqa.marc.definition.general.Linkage;
7
import de.gwdg.metadataqa.marc.definition.general.parser.LinkageParser;
8
import de.gwdg.metadataqa.marc.definition.general.parser.ParserException;
9
import de.gwdg.metadataqa.marc.definition.structure.DataFieldDefinition;
10
import de.gwdg.metadataqa.marc.model.validation.ErrorsCollector;
11
import de.gwdg.metadataqa.marc.model.validation.ValidationError;
12
import de.gwdg.metadataqa.marc.model.validation.ValidationErrorType;
13

14
import java.util.ArrayList;
15
import java.util.List;
16

17
import static de.gwdg.metadataqa.marc.model.validation.ValidationErrorType.FIELD_MISSING_REFERENCE_SUBFIELD;
18
import static de.gwdg.metadataqa.marc.model.validation.ValidationErrorType.RECORD_AMBIGUOUS_LINKAGE;
19
import static de.gwdg.metadataqa.marc.model.validation.ValidationErrorType.RECORD_INVALID_LINKAGE;
20

21
/**
22
 * A handler for linkage validation. Currently, handles only the MARC21 field 880, subfield 6. It is not clear
23
 * whether similar fields exist in UNIMARC and PICA.
24
 */
25
public class Marc21LinkageHandler {
26
  private boolean isAmbiguousLinkage = false;
1✔
27
  private final ErrorsCollector errorsCollector;
28
  private final ValidatorConfiguration configuration;
29
  public Marc21LinkageHandler(ValidatorConfiguration configuration, ErrorsCollector errorsCollector) {
1✔
30
    this.configuration = configuration;
1✔
31
    this.errorsCollector = errorsCollector;
1✔
32
  }
1✔
33

34
  /**
35
   * Validates the linkage of the field 880. Returns the associated field information if the linkage is valid.
36
   * Otherwise, returns null.
37
   * In addition:
38
   * - adds errors to the errorsCollector
39
   * - sets the ambiguousLinkage flag to true if the linkage is ambiguous
40
   * <p>
41
   * <b>Note:</b> Linkage means that the field 880 is linked to another field specified in the subfield $6. In that
42
   * case, the subfields of the field 880 are the same as the subfields of the linked (i.e. <b>associated</b>) field.
43
   * </p>
44
   * @param field The field for which the linkage is to be validated and handled
45
   * @param definition The definition of the field
46
   * @return The associated field if the linkage is valid, otherwise null
47
   */
48
  public DataField handleLinkage(DataField field,
49
                                 DataFieldDefinition definition) {
50
    if (!field.getTag().equals("880")) {
1✔
51
      return null;
1✔
52
    }
53
    String recordId = field.getBibliographicRecord() == null ? null : field.getBibliographicRecord().getId();
1✔
54

55
    List<MarcSubfield> subfield6s = field.getSubfield("6");
1✔
56
    if (subfield6s == null || subfield6s.isEmpty()) {
1✔
NEW
57
      addError(recordId,
×
NEW
58
          definition.getExtendedTag(),
×
59
          FIELD_MISSING_REFERENCE_SUBFIELD,
60
          "$6",
NEW
61
          definition.getDescriptionUrl());
×
NEW
62
      return null;
×
63
    }
64

65
    if (subfield6s.size() > 1) {
1✔
66
      addError(recordId,
1✔
67
          definition.getTag() + "$6",
1✔
68
          RECORD_AMBIGUOUS_LINKAGE,
69
          "There are multiple $6",
70
          definition.getDescriptionUrl());
1✔
71
      this.isAmbiguousLinkage = true;
1✔
72
      return null;
1✔
73
    }
74

75
    MarcSubfield subfield6 = subfield6s.get(0);
1✔
76

77
    Linkage linkage;
78
    try {
79
      linkage = LinkageParser.getInstance().create(subfield6.getValue());
1✔
NEW
80
    } catch (ParserException e) {
×
NEW
81
      addError(recordId, definition.getTag() + "$6", RECORD_INVALID_LINKAGE, e.getMessage(), definition.getDescriptionUrl());
×
NEW
82
      return null;
×
83
    }
1✔
84

85
    if (linkage == null || linkage.getLinkingTag() == null) {
1✔
NEW
86
      String message = String.format("Unparseable reference: '%s'", subfield6.getValue());
×
NEW
87
      addError(recordId, definition.getExtendedTag(), RECORD_INVALID_LINKAGE, message, definition.getDescriptionUrl());
×
NEW
88
      return null;
×
89
    }
90

91
    DataFieldDefinition referencedDefinition = TagDefinitionLoader
1✔
92
        .load(linkage.getLinkingTag(), configuration.getMarcVersion());
1✔
93

94
    if (referencedDefinition == null) {
1✔
NEW
95
      String message = String.format("refers to field %s, which is not defined", linkage.getLinkingTag());
×
NEW
96
      addError(recordId, definition.getTag() + "$6", RECORD_INVALID_LINKAGE, message, definition.getDescriptionUrl());
×
NEW
97
      return null;
×
98
    }
99

100
    List<MarcSubfield> associatedSubfields = new ArrayList<>();
1✔
101
    for (MarcSubfield subfield : field.getSubfields()) {
1✔
102
      MarcSubfield associatedSubfield = new MarcSubfield(
1✔
103
          definition.getSubfield(subfield.getCode()),
1✔
104
          subfield.getCode(),
1✔
105
          subfield.getValue()
1✔
106
      );
107
      associatedSubfield.setField(field);
1✔
108
      associatedSubfield.setMarcRecord(field.getBibliographicRecord());
1✔
109
      associatedSubfield.setLinkage(linkage);
1✔
110
      associatedSubfield.setReferencePath(definition.getTag());
1✔
111
      associatedSubfields.add(associatedSubfield);
1✔
112
    }
1✔
113

114
    DataField associatedField = new DataField(referencedDefinition, field.getInd1(), field.getInd2());
1✔
115

116
    associatedField.setSubfields(associatedSubfields);
1✔
117
    return associatedField;
1✔
118
  }
119

120
  public List<ValidationError> getErrors() {
121
    return errorsCollector.getErrors();
1✔
122
  }
123

124
  public boolean isAmbiguousLinkage() {
125
    return isAmbiguousLinkage;
1✔
126
  }
127

128
  private void addError(String recordId, String path, ValidationErrorType type, String message, String url) {
129
    if (!isIgnorableType(type)) {
1✔
130
      errorsCollector.add(recordId, path, type, message, url);
1✔
131
    }
132
  }
1✔
133

134
  private boolean isIgnorableType(ValidationErrorType type) {
135
    return (
1✔
136
        configuration.getIgnorableIssueTypes() != null
1✔
NEW
137
            && !configuration.getIgnorableIssueTypes().isEmpty()
×
138
            && configuration.getIgnorableIssueTypes().contains(type)
1✔
139
    );
140
  }
141
}
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