diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoder.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoder.java index 9fe831575..bb81741d5 100644 --- a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoder.java +++ b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoder.java @@ -175,7 +175,8 @@ public void literal(final String name, final String value) { if (currentEntity.equals("")) { prettyPrintIndentation(); writeRaw(String.format(CONTROLFIELD_OPEN_TEMPLATE, name)); - writeEscaped(value.trim()); + if (value != null) + writeEscaped(value.trim()); writeRaw(CONTROLFIELD_CLOSE); prettyPrintNewLine(); } else if (!currentEntity.equals(Marc21EventNames.LEADER_ENTITY)) { diff --git a/metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/MarcXmlEncoderTest.java b/metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/MarcXmlEncoderTest.java index 2500918ae..5f4d90e01 100644 --- a/metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/MarcXmlEncoderTest.java +++ b/metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/MarcXmlEncoderTest.java @@ -204,4 +204,17 @@ public void sendAndClearDataWhenOnResetStream() { String actual = resultCollector.toString(); assertEquals(expected, actual); } + + @Test + public void shouldIgnoreNullValueOfLiteral() { + encoder.startRecord(RECORD_ID); + encoder.literal("type", null); + encoder.endRecord(); + encoder.closeStream(); + String expected = XML_DECLARATION + XML_ROOT_OPEN + + "" + + XML_MARC_COLLECTION_END_TAG; + String actual = resultCollector.toString(); + assertEquals(expected, actual); + } } diff --git a/metamorph/build.gradle b/metamorph/build.gradle index 5ed1bcee9..a14f783d3 100644 --- a/metamorph/build.gradle +++ b/metamorph/build.gradle @@ -24,8 +24,10 @@ dependencies { implementation project(':metafacture-flowcontrol') implementation project(':metafacture-mangling') implementation project(':metafacture-javaintegration') + implementation 'org.slf4j:slf4j-api:1.7.21' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.5.5' + testRuntimeOnly 'org.slf4j:slf4j-simple:1.7.21' } sourceSets { diff --git a/metamorph/src/main/java/org/metafacture/metamorph/Metamorph.java b/metamorph/src/main/java/org/metafacture/metamorph/Metamorph.java index 2a141c7fa..4abe8026a 100644 --- a/metamorph/src/main/java/org/metafacture/metamorph/Metamorph.java +++ b/metamorph/src/main/java/org/metafacture/metamorph/Metamorph.java @@ -28,6 +28,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import org.metafacture.commons.ResourceUtil; import org.metafacture.framework.FluxCommand; @@ -48,6 +49,8 @@ import org.metafacture.metamorph.api.NamedValueReceiver; import org.metafacture.metamorph.api.NamedValueSource; import org.metafacture.metamorph.api.SourceLocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.xml.sax.InputSource; /** @@ -64,7 +67,9 @@ @FluxCommand("morph") public final class Metamorph implements StreamPipe, NamedValuePipe, Maps { + private static final String ELSE_NESTED_KEYWORD = "_elseNested"; public static final String ELSE_KEYWORD = "_else"; + public static final String ELSE_FLATTENED_KEYWORD = "_elseFlattened"; public static final char FEEDBACK_CHAR = '@'; public static final char ESCAPE_CHAR = '\\'; public static final String METADATA = "__meta"; @@ -94,6 +99,9 @@ public final class Metamorph implements StreamPipe, NamedValuePi private MorphErrorHandler errorHandler = new DefaultErrorHandler(); private int recordCount; private final List recordEndListener = new ArrayList<>(); + private boolean elseNested; + final private Pattern literalPatternOfEntityMarker = Pattern.compile(flattener.getEntityMarker(), Pattern.LITERAL); + private static final Logger LOG = LoggerFactory.getLogger(Metamorph.class); protected Metamorph() { // package private @@ -215,8 +223,15 @@ public void setErrorHandler(final MorphErrorHandler errorHandler) { } protected void registerNamedValueReceiver(final String source, final NamedValueReceiver data) { - if (ELSE_KEYWORD.equals(source)) { - elseSources.add(data); + if (ELSE_NESTED_KEYWORD.equals(source)) { + this.elseNested = true; + } + if (ELSE_KEYWORD.equals(source) || ELSE_FLATTENED_KEYWORD.equals(source) || elseNested) { + if (elseSources.isEmpty()) + elseSources.add(data); + else + LOG.warn( + "Only one of '_else', '_elseFlattened' and '_elseNested' is allowed. Ignoring the superflous ones."); } else { dataRegistry.register(source, data); } @@ -268,9 +283,6 @@ public void startEntity(final String name) { entityCountStack.push(Integer.valueOf(entityCount)); flattener.startEntity(name); - - - } @Override @@ -306,28 +318,38 @@ public void closeStream() { outputStreamReceiver.closeStream(); } - protected void dispatch(final String path, final String value, final List fallback) { - final List matchingData = findMatchingData(path, fallback); - if (null != matchingData) { - send(path, value, matchingData); - } - } - - private List findMatchingData(final String path, final List fallback) { - final List matchingData = dataRegistry.get(path); + protected void dispatch(final String path, final String value, final List fallbackReceiver) { + List matchingData = dataRegistry.get(path); + boolean fallback = false; if (matchingData == null || matchingData.isEmpty()) { - return fallback; + fallback = true; + matchingData = fallbackReceiver; + } + if (null != matchingData) { + send(path, value, matchingData, fallback); } - return matchingData; } - private void send(final String key, final String value, final List dataList) { + private void send(final String path, final String value, final List dataList, + final boolean fallback) { for (final NamedValueReceiver data : dataList) { + String key = path; + if (fallback && elseNested) { + if (flattener.getCurrentEntityName() != null) { + outputStreamReceiver.startEntity(flattener.getCurrentEntityName()); + key = literalPatternOfEntityMarker.split(path)[1]; + } + } try { data.receive(key, value, null, recordCount, currentEntityCount); } catch (final RuntimeException e) { errorHandler.error(e); } + if (fallback && elseNested) { + if (flattener.getCurrentEntityName() != null) { + outputStreamReceiver.endEntity(); + } + } } } diff --git a/metamorph/src/test/java/org/metafacture/metamorph/MetamorphTest.java b/metamorph/src/test/java/org/metafacture/metamorph/MetamorphTest.java index 8e0715229..88f659c39 100644 --- a/metamorph/src/test/java/org/metafacture/metamorph/MetamorphTest.java +++ b/metamorph/src/test/java/org/metafacture/metamorph/MetamorphTest.java @@ -52,6 +52,9 @@ public final class MetamorphTest { @Mock private NamedValueReceiver namedValueReceiver; + @Mock + private DefaultStreamReceiver receiver = new DefaultStreamReceiver(); + private Metamorph metamorph; @Before @@ -60,6 +63,7 @@ public void createSystemUnderTest() { metamorph.setReceiver(new DefaultStreamReceiver()); } + @Test public void shouldMapMatchingPath() { setupSimpleMappingMorph(); diff --git a/metamorph/src/test/java/org/metafacture/metamorph/TestMetamorphBasics.java b/metamorph/src/test/java/org/metafacture/metamorph/TestMetamorphBasics.java index d7de30de3..a564bed85 100644 --- a/metamorph/src/test/java/org/metafacture/metamorph/TestMetamorphBasics.java +++ b/metamorph/src/test/java/org/metafacture/metamorph/TestMetamorphBasics.java @@ -86,6 +86,78 @@ public void shouldHandleUnmatchedLiteralsInElseSource() { ordered.verify(receiver).endRecord(); } + @Test + public void shouldHandleUnmatchedLiteralsAndEntitiesInElseSource() { + metamorph = InlineMorph.in(this) // + .with("") // + .with(" ")// + .with("")// + .createConnectedTo(receiver); + testElseData(); + } + + @Test + public void shouldHandleUnmatchedLiteralsAndEntitiesInElseFlattenedSource() { + metamorph = InlineMorph.in(this) // + .with("") // + .with(" ")// + .with("")// + .createConnectedTo(receiver); + testElseData(); + } + private void testElseData() { + metamorph.startRecord("1"); + metamorph.literal("Shikotan", "Aekap"); + metamorph.startEntity("Germany"); + metamorph.literal("Langeoog", "Moin"); + metamorph.endEntity(); + metamorph.startEntity("Germany"); + metamorph.literal("Baltrum", "Moin Moin"); + metamorph.endEntity(); + metamorph.endRecord(); + + final InOrder ordered = inOrder(receiver); + ordered.verify(receiver).startRecord("1"); + ordered.verify(receiver).literal("Shikotan", "Aekap"); + ordered.verify(receiver).literal("Germany.Langeoog", "Moin"); + ordered.verify(receiver).literal("Germany.Baltrum", "Moin Moin"); + ordered.verify(receiver).endRecord(); + } + + @Test + public void shouldHandleUnmatchedLiteralsAndEntitiesInElseNestedSource() { + metamorph = InlineMorph.in(this).with("")// + .with(" ")// + .with(" ") + .with(" ")// + .with(" ")// + .with("")// + .createConnectedTo(receiver); + + metamorph.startRecord("1"); + metamorph.literal("Shikotan", "Aekap"); + metamorph.startEntity("Germany"); + metamorph.literal("Langeoog", "Moin"); + metamorph.literal("Baltrum", "Moin Moin"); + metamorph.endEntity(); + metamorph.startEntity("USA"); + metamorph.literal("Sylt", "Aloha"); + metamorph.endEntity(); + metamorph.endRecord(); + + final InOrder ordered = inOrder(receiver); + ordered.verify(receiver).startRecord("1"); + ordered.verify(receiver).literal("Shikotan", "Aekap"); + ordered.verify(receiver).startEntity("Germany"); + ordered.verify(receiver).literal("Langeoog", "Moin"); + ordered.verify(receiver).literal("Baltrum", "Moin Moin"); + ordered.verify(receiver).endEntity(); + ordered.verify(receiver).startEntity("USA"); + ordered.verify(receiver).literal("Hawaii", "Aloha"); + ordered.verify(receiver).endEntity(); + ordered.verify(receiver).endRecord(); + } + @Test public void shouldMatchCharacterWithQuestionMarkWildcard() { metamorph = InlineMorph.in(this)