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)