From 840eb306ce249844feb9282b507bede09ab122bc Mon Sep 17 00:00:00 2001 From: viacheslav_kolesnyk Date: Fri, 14 Aug 2026 14:16:50 +0200 Subject: [PATCH 1/2] fix(normalization): Fix 035 being split on update when same $a in incoming and existing Update 035 deduplication logic to delete duplicates from fields with less subfields Refs: MODSOURCE-1014 --- .../processing/util/MarcRecordNormalizer.java | 41 ++- .../util/MarcRecordNormalizerTest.java | 310 +++++++++++------- 2 files changed, 228 insertions(+), 123 deletions(-) diff --git a/src/main/java/org/folio/processing/util/MarcRecordNormalizer.java b/src/main/java/org/folio/processing/util/MarcRecordNormalizer.java index f3175432..59f665c6 100644 --- a/src/main/java/org/folio/processing/util/MarcRecordNormalizer.java +++ b/src/main/java/org/folio/processing/util/MarcRecordNormalizer.java @@ -1,7 +1,9 @@ package org.folio.processing.util; import java.util.ArrayList; +import java.util.IdentityHashMap; import java.util.List; +import java.util.Map; import java.util.regex.Pattern; import org.marc4j.marc.DataField; import org.marc4j.marc.Record; @@ -49,11 +51,11 @@ private static List get035SubfieldOclcValues(Record marcRecord) { private static void formatOclc(List subfields) { for (Subfield subfield : subfields) { - subfield.setData(formatOclcValue(subfield.getData())); + subfield.setData(normalizeOclcValue(subfield.getData())); } } - private static String formatOclcValue(String data) { + public static String normalizeOclcValue(String data) { data = DOT_OR_WHITESPACE_PATTERN.matcher(data).replaceAll(""); var matcher = OCLC_COMPILED.matcher(data); if (!matcher.find()) { @@ -77,11 +79,22 @@ private static String formatOclcValue(String data) { } private static void deduplicateOclc(Record marcRecord, List subfields) { - List subfieldsToDelete = new ArrayList<>(); + var parentMap = new IdentityHashMap(); + marcRecord.getVariableFields(TAG_035).forEach(vf -> { + if (vf instanceof DataField df) { + df.getSubfields().forEach(sf -> parentMap.put(sf, df)); + } + }); - for (Subfield subfield : new ArrayList<>(subfields)) { - if (subfields.stream().anyMatch(s -> isDuplicate(subfield, s))) { - subfieldsToDelete.add(subfield); + var subfieldsToDelete = new ArrayList(); + for (var subfield : new ArrayList<>(subfields)) { + var duplicate = subfields.stream().filter(s -> isDuplicate(subfield, s)).findFirst(); + if (duplicate.isPresent()) { + // Prefer to keep the subfield in the field that would be orphaned without it + // (sole subfield of its code in a multi-subfield field), e.g. $a in a 035 that also has $z. + boolean preferKeepCurrent = isSoleOfCodeInMultiSubfieldField(subfield, parentMap) + && !isSoleOfCodeInMultiSubfieldField(duplicate.get(), parentMap); + subfieldsToDelete.add(preferKeepCurrent ? duplicate.get() : subfield); subfields.remove(subfield); } } @@ -91,11 +104,25 @@ private static void deduplicateOclc(Record marcRecord, List subfields) } private static boolean isDuplicate(Subfield s1, Subfield s2) { - return !s1.equals(s2) + return s1 != s2 && s1.getData().equals(s2.getData()) && s1.getCode() == s2.getCode(); } + /** + * Returns true when {@code subfield} is the only subfield of its code in its parent DataField + * and the parent has other subfields of different codes. Removing such a subfield would leave + * the parent without its primary marker (e.g. $a) while other subfields ($z, $b, …) remain, + * which creates an incomplete 035 field. + */ + private static boolean isSoleOfCodeInMultiSubfieldField(Subfield subfield, + Map parentMap) { + var parent = parentMap.get(subfield); + return parent != null + && parent.getSubfields(subfield.getCode()).size() == 1 + && parent.getSubfields().size() > 1; + } + private static void removeSubfieldIfExist(Record marcRecord, VariableField field, Subfield subfieldToDelete) { if (field instanceof DataField dataField && dataField.getSubfields().contains(subfieldToDelete)) { diff --git a/src/test/java/org/folio/processing/util/MarcRecordNormalizerTest.java b/src/test/java/org/folio/processing/util/MarcRecordNormalizerTest.java index e4b081b2..2d3b11be 100644 --- a/src/test/java/org/folio/processing/util/MarcRecordNormalizerTest.java +++ b/src/test/java/org/folio/processing/util/MarcRecordNormalizerTest.java @@ -6,7 +6,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.stream.Stream; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -43,6 +42,8 @@ class MarcRecordNormalizerTest { private static final String OCLC_OCN_SPACE_987654 = OCLC_PREFIX + " ocn 00987654"; private static final MarcFactory FACTORY = MarcFactory.newInstance(); + // ── Single-field formatting ────────────────────────────────────────────────── + @ParameterizedTest(name = "{0}") @MethodSource("singleFieldFormattingParameters") void normalize035Field_singleFieldFormatting(String testName, String input, String expected) { @@ -51,167 +52,194 @@ void normalize035Field_singleFieldFormatting(String testName, String input, Stri assertEquals(expected, firstSubfieldData(marcRecord)); } - @Test - void normalize035Field_nullRecord_doesNotThrow() { - assertDoesNotThrow(() -> MarcRecordNormalizer.normalize035Field(null)); - } - - @Test - void normalize035Field_noFields_recordUnchanged() { - var marcRecord = FACTORY.newRecord(); - MarcRecordNormalizer.normalize035Field(marcRecord); - assertTrue(marcRecord.getVariableFields(TAG_035).isEmpty()); + private static Stream singleFieldFormattingParameters() { + return Stream.of( + Arguments.of("non-OCLC prefix unchanged", "(NLC)123456", "(NLC)123456"), + Arguments.of("blank data unchanged", " ", " "), + Arguments.of("ocm prefix with leading zeros stripped", OCLC_OCM_00123456, OCLC_123456), + Arguments.of("ocm prefix no leading zeros stripped", OCLC_OCM_987654321, OCLC_987654321), + Arguments.of("ocn prefix with leading zeros stripped", OCLC_OCN_0000987654, OCLC_987654), + Arguments.of("on prefix with leading zeros stripped", OCLC_ON_001234567, OCLC_1234567), + Arguments.of("plain OCLC number leading zeros removed", OCLC_0012345, OCLC_12345), + Arguments.of("plain OCLC number no leading zeros unchanged", OCLC_12345, OCLC_12345), + Arguments.of("alphabetic prefix digits removed zeros stripped", OCLC_TFE_00123, OCLC_TFE123), + Arguments.of("mixed alphabetic prefix digits unchanged", OCLC_AB2C_00456, OCLC_AB2C_00456), + Arguments.of("dots removed before formatting", OCLC_OCM_DOT_00123456, OCLC_123456), + Arguments.of("whitespace removed before formatting", OCLC_OCN_SPACE_987654, OCLC_987654), + Arguments.of("leading whitespace before OCLC prefix trimmed", " " + OCLC_OCM_00123456, OCLC_123456), + Arguments.of("numeric part with trailing chars preserved", OCLC_OCM_00123456_ABC, OCLC_123456ABC), + Arguments.of("no matching OCLC pattern subfield unchanged", OCLC_NO_DIGITS, OCLC_NO_DIGITS) + ); } - @Test - void normalize035Field_no035Fields_recordUnchanged() { - var marcRecord = FACTORY.newRecord(); - var field = FACTORY.newDataField("100", ' ', ' '); - field.addSubfield(FACTORY.newSubfield('a', OCLC_12345)); - marcRecord.addVariableField(field); - - MarcRecordNormalizer.normalize035Field(marcRecord); + // ── Deduplication ──────────────────────────────────────────────────────────── + // + // Covers: no-op when there are no OCLC subfields, duplicate removal between + // separate fields, duplicate removal within a single field, and the case where + // values are equal across fields but not identical in string form before + // normalization. - assertTrue(marcRecord.getVariableFields(TAG_035).isEmpty()); + @ParameterizedTest(name = "{0}") + @MethodSource("deduplicationParameters") + void normalize035Field_deduplication(String testName, Record input, + int expectedFieldCount, String expectedFirstValue) { + MarcRecordNormalizer.normalize035Field(input); + var remaining = input.getVariableFields(TAG_035); + assertEquals(expectedFieldCount, remaining.size()); + if (expectedFirstValue != null) { + assertEquals(expectedFirstValue, ((DataField) remaining.getFirst()).getSubfield('a').getData()); + } } - @Test - void normalize035Field_twoDuplicate035Fields_oneRemoved() { - var marcRecord = FACTORY.newRecord(); - - var field1 = FACTORY.newDataField(TAG_035, ' ', ' '); - field1.addSubfield(FACTORY.newSubfield('a', OCLC_0012345)); - - var field2 = FACTORY.newDataField(TAG_035, ' ', ' '); - field2.addSubfield(FACTORY.newSubfield('a', OCLC_12345)); - - marcRecord.addVariableField(field1); - marcRecord.addVariableField(field2); - - MarcRecordNormalizer.normalize035Field(marcRecord); - - var remaining = marcRecord.getVariableFields(TAG_035); - assertEquals(1, remaining.size()); - assertEquals(OCLC_12345, ((DataField) remaining.getFirst()).getSubfield('a').getData()); + private static Stream deduplicationParameters() { + return Stream.of( + Arguments.of("no fields in record — record unchanged", + FACTORY.newRecord(), 0, null), + Arguments.of("non-035 field only — record unchanged", + recordWithNon035Field(), 0, null), + Arguments.of("two standalones normalizing to same value — one removed", + recordWithTwoDuplicate035Fields(), 1, OCLC_12345), + Arguments.of("internal duplicate subfield removed, field kept", + recordWithInternalDuplicateSubfields(), 1, OCLC_12345), + Arguments.of("standalone field removed entirely when sole duplicate", + recordWithDuplicateStandaloneField(), 1, OCLC_99999), + Arguments.of("two standalones with different values — both kept", + recordWithTwoDifferentValues(), 2, null) + ); } - @Test - void normalize035Field_duplicateSubfieldInSameField_subfieldRemovedFieldKept() { + // ── Multi-subfield preservation ─────────────────────────────────────────────── + // + // When a 035 field carries $a plus other subfields ($z, etc.) and a standalone + // 035 field contains only a duplicate of the $a, the multi-subfield field must + // survive intact. The standalone must be removed rather than stripping $a from + // the richer field (which would leave an orphaned $z-only 035). + + @ParameterizedTest(name = "{0}") + @MethodSource("multiSubfieldPreservationParameters") + void normalize035Field_multiSubfieldPreservation(String testName, String inputValue, + String expectedValue) { + var multiField = FACTORY.newDataField(TAG_035, ' ', ' '); + multiField.addSubfield(FACTORY.newSubfield('a', inputValue)); + multiField.addSubfield(FACTORY.newSubfield('z', "(OCoLC)1079294651")); + multiField.addSubfield(FACTORY.newSubfield('z', "(OCoLC)1130250129")); + var standalone = FACTORY.newDataField(TAG_035, ' ', ' '); + standalone.addSubfield(FACTORY.newSubfield('a', "(OCoLC)1299112")); var marcRecord = FACTORY.newRecord(); - var field = FACTORY.newDataField(TAG_035, ' ', ' '); - field.addSubfield(FACTORY.newSubfield('a', OCLC_12345)); - field.addSubfield(FACTORY.newSubfield('a', OCLC_0012345)); - marcRecord.addVariableField(field); + marcRecord.addVariableField(multiField); + marcRecord.addVariableField(standalone); MarcRecordNormalizer.normalize035Field(marcRecord); var remaining = marcRecord.getVariableFields(TAG_035); assertEquals(1, remaining.size()); - var subfields = ((DataField) remaining.getFirst()).getSubfields('a'); - assertEquals(1, subfields.size()); - assertEquals(OCLC_12345, subfields.getFirst().getData()); + var df = (DataField) remaining.getFirst(); + assertEquals(expectedValue, df.getSubfield('a').getData()); + assertEquals(2, df.getSubfields('z').size()); + assertEquals("(OCoLC)1079294651", df.getSubfields('z').get(0).getData()); + assertEquals("(OCoLC)1130250129", df.getSubfields('z').get(1).getData()); } - @Test - void normalize035Field_duplicateSubfieldOnlySubfieldInField_wholeFieldRemoved() { - var marcRecord = FACTORY.newRecord(); - - var field1 = FACTORY.newDataField(TAG_035, ' ', ' '); - field1.addSubfield(FACTORY.newSubfield('a', OCLC_OCM_00099999)); - - var field2 = FACTORY.newDataField(TAG_035, ' ', ' '); - field2.addSubfield(FACTORY.newSubfield('a', OCLC_99999)); - - marcRecord.addVariableField(field1); - marcRecord.addVariableField(field2); + private static Stream multiSubfieldPreservationParameters() { + return Stream.of( + Arguments.of("already-normalized $a — standalone duplicate removed", + "(OCoLC)1299112", "(OCoLC)1299112"), + Arguments.of("non-normalized $a — normalized after dedup, standalone removed", + "(OCoLC)ocm1299112", "(OCoLC)1299112") + ); + } - MarcRecordNormalizer.normalize035Field(marcRecord); + // ── Individual tests (unique assertion shapes) ──────────────────────────────── - var remaining = marcRecord.getVariableFields(TAG_035); - assertEquals(1, remaining.size()); + @Test + void normalize035Field_nullRecord_doesNotThrow() { + assertDoesNotThrow(() -> MarcRecordNormalizer.normalize035Field(null)); } @Test - void normalize035Field_twoDifferentOclcValues_bothKept() { + void normalize035Field_mixedSubfieldsInSameField_onlyOclcSubfieldFormatted() { + var field = FACTORY.newDataField(TAG_035, ' ', ' '); + field.addSubfield(FACTORY.newSubfield('a', OCLC_OCM_00123456)); + field.addSubfield(FACTORY.newSubfield('z', "(NLC)999")); var marcRecord = FACTORY.newRecord(); - - var field1 = FACTORY.newDataField(TAG_035, ' ', ' '); - field1.addSubfield(FACTORY.newSubfield('a', OCLC_11111)); - - var field2 = FACTORY.newDataField(TAG_035, ' ', ' '); - field2.addSubfield(FACTORY.newSubfield('a', OCLC_22222)); - - marcRecord.addVariableField(field1); - marcRecord.addVariableField(field2); + marcRecord.addVariableField(field); MarcRecordNormalizer.normalize035Field(marcRecord); - var remaining = marcRecord.getVariableFields(TAG_035); - assertEquals(2, remaining.size()); + var df = (DataField) marcRecord.getVariableFields(TAG_035).getFirst(); + assertEquals(OCLC_123456, df.getSubfield('a').getData()); + assertEquals("(NLC)999", df.getSubfield('z').getData()); } @Test - void shouldHandleMultipleSubfieldsIn035() { - var basicRecord = FACTORY.newRecord(); - + void normalize035Field_multipleOclcSubfieldKinds_onlyDuplicatesRemoved() { var field1 = FACTORY.newDataField(TAG_035, ' ', ' '); field1.addSubfield(FACTORY.newSubfield('a', "(OCoLC)64758")); - basicRecord.addVariableField(field1); - - var field2 = FACTORY.newDataField("035", ' ', ' '); + var field2 = FACTORY.newDataField(TAG_035, ' ', ' '); field2.addSubfield(FACTORY.newSubfield('a', "(OCoLC)ocm000064758")); field2.addSubfield(FACTORY.newSubfield('k', "(OCoLC)976939443")); field2.addSubfield(FACTORY.newSubfield('k', "(OCoLC)1001261435")); field2.addSubfield(FACTORY.newSubfield('k', "(OCoLC)120194933")); - basicRecord.addVariableField(field2); + var marcRecord = FACTORY.newRecord(); + marcRecord.addVariableField(field1); + marcRecord.addVariableField(field2); - MarcRecordNormalizer.normalize035Field(basicRecord); + MarcRecordNormalizer.normalize035Field(marcRecord); - var updatedField = (DataField) basicRecord.getVariableField("035"); + var updatedField = (DataField) marcRecord.getVariableField(TAG_035); assertNotNull(updatedField); - Assertions.assertEquals(4, updatedField.getSubfields().size()); - Assertions.assertEquals("(OCoLC)64758", updatedField.getSubfield('a').getData()); + assertEquals(4, updatedField.getSubfields().size()); + assertEquals("(OCoLC)64758", updatedField.getSubfield('a').getData()); var subfieldsK = updatedField.getSubfields('k'); - Assertions.assertEquals(3, subfieldsK.size()); - Assertions.assertEquals("(OCoLC)976939443", subfieldsK.get(0).getData()); - Assertions.assertEquals("(OCoLC)1001261435", subfieldsK.get(1).getData()); - Assertions.assertEquals("(OCoLC)120194933", subfieldsK.get(2).getData()); + assertEquals(3, subfieldsK.size()); + assertEquals("(OCoLC)976939443", subfieldsK.get(0).getData()); + assertEquals("(OCoLC)1001261435", subfieldsK.get(1).getData()); + assertEquals("(OCoLC)120194933", subfieldsK.get(2).getData()); } @Test - void normalize035Field_mixedSubfieldsInSameField_onlyOclcSubfieldFormatted() { + void normalize035Field_fieldWithInternalDuplicatesAndBsubfield_innerSubfieldsAreStripped() { + // 035[$a(OCoLC)ocn0001234, $a(OCoLC)ocn1234, $b(OCoLC)ocn1234] — internal $a duplicates + $b + // 035[$a(OCoLC)ocm1234] — standalone, same OCLC number + // 035[$a(OCoLC)ocn00098765] — standalone, different number + // 035[$a(OCoLC)ocn0001234] — standalone, same (the keeper) + var complexField = FACTORY.newDataField(TAG_035, ' ', ' '); + complexField.addSubfield(FACTORY.newSubfield('a', "(OCoLC)ocn0001234")); + complexField.addSubfield(FACTORY.newSubfield('a', "(OCoLC)ocn1234")); + complexField.addSubfield(FACTORY.newSubfield('b', "(OCoLC)ocn1234")); + var standalone1 = FACTORY.newDataField(TAG_035, ' ', ' '); + standalone1.addSubfield(FACTORY.newSubfield('a', "(OCoLC)ocm1234")); + var standalone2 = FACTORY.newDataField(TAG_035, ' ', ' '); + standalone2.addSubfield(FACTORY.newSubfield('a', "(OCoLC)ocn00098765")); + var standalone3 = FACTORY.newDataField(TAG_035, ' ', ' '); + standalone3.addSubfield(FACTORY.newSubfield('a', "(OCoLC)ocn0001234")); var marcRecord = FACTORY.newRecord(); - var field = FACTORY.newDataField(TAG_035, ' ', ' '); - field.addSubfield(FACTORY.newSubfield('a', OCLC_OCM_00123456)); - field.addSubfield(FACTORY.newSubfield('z', "(NLC)999")); - marcRecord.addVariableField(field); + marcRecord.addVariableField(complexField); + marcRecord.addVariableField(standalone1); + marcRecord.addVariableField(standalone2); + marcRecord.addVariableField(standalone3); MarcRecordNormalizer.normalize035Field(marcRecord); - var df = (DataField) marcRecord.getVariableFields(TAG_035).getFirst(); - assertEquals(OCLC_123456, df.getSubfield('a').getData()); - assertEquals("(NLC)999", df.getSubfield('z').getData()); + var remaining = marcRecord.getVariableFields(TAG_035); + // complexField stripped to [$b(OCoLC)1234], standalone2 [$a(OCoLC)98765], one [$a(OCoLC)1234] + assertEquals(3, remaining.size()); + var fieldWithBsubfield = remaining.stream() + .map(f -> (DataField) f) + .filter(f -> f.getSubfield('b') != null && f.getSubfield('a') == null) + .findFirst(); + assertTrue(fieldWithBsubfield.isPresent(), "Expected a $b-only 035 field"); + assertEquals("(OCoLC)1234", fieldWithBsubfield.get().getSubfield('b').getData()); + var fieldsWithA = remaining.stream() + .map(f -> (DataField) f) + .filter(f -> f.getSubfield('a') != null) + .toList(); + assertEquals(2, fieldsWithA.size()); + assertTrue(fieldsWithA.stream().anyMatch(f -> "(OCoLC)98765".equals(f.getSubfield('a').getData()))); + assertTrue(fieldsWithA.stream().anyMatch(f -> "(OCoLC)1234".equals(f.getSubfield('a').getData()))); } - private static Stream singleFieldFormattingParameters() { - return Stream.of( - Arguments.of("non-OCLC prefix unchanged", "(NLC)123456", "(NLC)123456"), - Arguments.of("blank data unchanged", " ", " "), - Arguments.of("ocm prefix with leading zeros stripped", OCLC_OCM_00123456, OCLC_123456), - Arguments.of("ocm prefix no leading zeros stripped", OCLC_OCM_987654321, OCLC_987654321), - Arguments.of("ocn prefix with leading zeros stripped", OCLC_OCN_0000987654, OCLC_987654), - Arguments.of("on prefix with leading zeros stripped", OCLC_ON_001234567, OCLC_1234567), - Arguments.of("plain OCLC number leading zeros removed", OCLC_0012345, OCLC_12345), - Arguments.of("plain OCLC number no leading zeros unchanged", OCLC_12345, OCLC_12345), - Arguments.of("alphabetic prefix digits removed zeros stripped", OCLC_TFE_00123, OCLC_TFE123), - Arguments.of("mixed alphabetic prefix digits unchanged", OCLC_AB2C_00456, OCLC_AB2C_00456), - Arguments.of("dots removed before formatting", OCLC_OCM_DOT_00123456, OCLC_123456), - Arguments.of("whitespace removed before formatting", OCLC_OCN_SPACE_987654, OCLC_987654), - Arguments.of("leading whitespace before OCLC prefix trimmed", " " + OCLC_OCM_00123456, OCLC_123456), - Arguments.of("numeric part with trailing chars preserved", OCLC_OCM_00123456_ABC, OCLC_123456ABC), - Arguments.of("no matching OCLC pattern subfield unchanged", OCLC_NO_DIGITS, OCLC_NO_DIGITS) - ); - } + // ── Private helpers ─────────────────────────────────────────────────────────── private Record recordWith035(String subfieldValue) { var field = FACTORY.newDataField(TAG_035, ' ', ' '); @@ -230,4 +258,54 @@ private String firstSubfieldData(Record marcRecord) { var sf = df.getSubfield('a'); return sf == null ? null : sf.getData(); } + + private static Record recordWithNon035Field() { + var field = FACTORY.newDataField("100", ' ', ' '); + field.addSubfield(FACTORY.newSubfield('a', OCLC_12345)); + var marcRecord = FACTORY.newRecord(); + marcRecord.addVariableField(field); + return marcRecord; + } + + private static Record recordWithTwoDuplicate035Fields() { + var field1 = FACTORY.newDataField(TAG_035, ' ', ' '); + field1.addSubfield(FACTORY.newSubfield('a', OCLC_0012345)); + var field2 = FACTORY.newDataField(TAG_035, ' ', ' '); + field2.addSubfield(FACTORY.newSubfield('a', OCLC_12345)); + var marcRecord = FACTORY.newRecord(); + marcRecord.addVariableField(field1); + marcRecord.addVariableField(field2); + return marcRecord; + } + + private static Record recordWithInternalDuplicateSubfields() { + var field = FACTORY.newDataField(TAG_035, ' ', ' '); + field.addSubfield(FACTORY.newSubfield('a', OCLC_12345)); + field.addSubfield(FACTORY.newSubfield('a', OCLC_0012345)); + var marcRecord = FACTORY.newRecord(); + marcRecord.addVariableField(field); + return marcRecord; + } + + private static Record recordWithDuplicateStandaloneField() { + var field1 = FACTORY.newDataField(TAG_035, ' ', ' '); + field1.addSubfield(FACTORY.newSubfield('a', OCLC_OCM_00099999)); + var field2 = FACTORY.newDataField(TAG_035, ' ', ' '); + field2.addSubfield(FACTORY.newSubfield('a', OCLC_99999)); + var marcRecord = FACTORY.newRecord(); + marcRecord.addVariableField(field1); + marcRecord.addVariableField(field2); + return marcRecord; + } + + private static Record recordWithTwoDifferentValues() { + var field1 = FACTORY.newDataField(TAG_035, ' ', ' '); + field1.addSubfield(FACTORY.newSubfield('a', OCLC_11111)); + var field2 = FACTORY.newDataField(TAG_035, ' ', ' '); + field2.addSubfield(FACTORY.newSubfield('a', OCLC_22222)); + var marcRecord = FACTORY.newRecord(); + marcRecord.addVariableField(field1); + marcRecord.addVariableField(field2); + return marcRecord; + } } From c372f07df5487bc4e681f54c7a0fa286f53289ae Mon Sep 17 00:00:00 2001 From: viacheslav_kolesnyk Date: Fri, 14 Aug 2026 14:31:58 +0200 Subject: [PATCH 2/2] Update NEWS --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index 561a2342..20ca72ad 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,7 @@ * Description ([ISSUE](https://folio-org.atlassian.net/browse/ISSUE)) ### Bug fixes * DELETE_INCOMING repeatable field action not removing duplicate scalar values from string arrays ([MODDICORE-507](https://folio-org.atlassian.net/browse/MODDICORE-507)) +* Fix 035 being split on update when same $a in incoming and existing ([MODSOURCE-1014](https://folio-org.atlassian.net/browse/MODSOURCE-1014)) ### Tech Dept * Migrate tests from JUnit 4 to JUnit 6