From bff54dafa95066ecdf10467206573236f25355d7 Mon Sep 17 00:00:00 2001 From: Pascal Christoph Date: Mon, 6 May 2024 10:02:07 +0200 Subject: [PATCH 1/8] Add Marc21XmlEncoder (#527) Marc21XmlEncoder acts as a wrapper. It makes use of Marc21Encoder, Marc21Decoder and MarcXmlEncoder to ensure a proper MarcXml, especially regarding the leader. Also - in contrast to MarcXmlEncoder - the record id (field 001) is mandatory. --- .../biblio/marc21/AbstractMarcXmlEncoder.java | 10 ++ .../biblio/marc21/Marc21XmlEncoder.java | 115 ++++++++++++++++++ .../biblio/marc21/MarcXmlEncoder.java | 34 ++++-- .../marc21/MarcXmlEncoderInterface.java | 49 ++++++++ .../biblio/marc21/Marc21XmlEncoderTest.java | 37 ++++++ .../biblio/marc21/MarcXmlEncoderTest.java | 56 ++++++++- 6 files changed, 287 insertions(+), 14 deletions(-) create mode 100644 metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/AbstractMarcXmlEncoder.java create mode 100644 metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java create mode 100644 metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoderInterface.java create mode 100644 metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/Marc21XmlEncoderTest.java diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/AbstractMarcXmlEncoder.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/AbstractMarcXmlEncoder.java new file mode 100644 index 000000000..a54d5b455 --- /dev/null +++ b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/AbstractMarcXmlEncoder.java @@ -0,0 +1,10 @@ +package org.metafacture.biblio.marc21; + +import org.metafacture.framework.ObjectReceiver; +import org.metafacture.framework.helpers.DefaultStreamPipe; + +public abstract class AbstractMarcXmlEncoder extends DefaultStreamPipe> implements MarcXmlEncoderInterface { + + protected void onResetStream() { + } +} diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java new file mode 100644 index 000000000..445bce46e --- /dev/null +++ b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java @@ -0,0 +1,115 @@ +/* + * Copyright 2024 hbz + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.metafacture.biblio.marc21; + +import org.metafacture.framework.FluxCommand; +import org.metafacture.framework.StreamReceiver; +import org.metafacture.framework.annotations.Description; +import org.metafacture.framework.annotations.In; +import org.metafacture.framework.annotations.Out; + +/** + * Acts as a wrapper: pipes input to Marc21Encoder which output is piped to Marc21Decoder which output is piped to MarcXmlEncoder. + * + * @author Pascal Christoph (dr0i) + * + */ +@In(StreamReceiver.class) +@Out(String.class) +@Description("Encodes MARC21 records as MARCXML. It wraps 'encode-marc21 | decode-marc21 | encode-marcxml ' to generate MARCXML more safely, especially when the building the 'leader'.") +@FluxCommand("encode-marc21xml") +public class Marc21XmlEncoder extends AbstractMarcXmlEncoder { + private final Marc21Decoder marc21Decoder = new Marc21Decoder(); + private final Marc21Encoder marc21Encoder = new Marc21Encoder(); + private final MarcXmlEncoder marcXmlEncoder = new MarcXmlEncoder(); + + /** + * Creates an instance of {@link Marc21XmlEncoder}. + */ + public Marc21XmlEncoder() { + marc21Decoder.setEmitLeaderAsWhole(true); + + marc21Encoder.setReceiver(marc21Decoder); + marc21Decoder.setReceiver(marcXmlEncoder); + } + + @Override + protected void onSetReceiver() { + marcXmlEncoder.setReceiver(getReceiver()); + } + + @Override + public void startRecord(final String identifier) { + marc21Encoder.startRecord(identifier); + } + + @Override + public void endRecord() { + marc21Encoder.endRecord(); + } + + @Override + public void startEntity(final String name) { + marc21Encoder.startEntity(name); + } + + @Override + public void endEntity() { + marc21Encoder.endEntity(); + } + + @Override + public void literal(final String name, final String value) { + marc21Encoder.literal(name, value); + } + + @Override + protected void onCloseStream() { + marc21Encoder.closeStream(); + } + + @Override + public void onResetStream() { + marc21Encoder.resetStream(); + } + + @Override + public void setEmitNamespace(final boolean emitNamespace) { + marcXmlEncoder.setEmitNamespace(emitNamespace); + } + + @Override + public void omitXmlDeclaration(final boolean currentOmitXmlDeclaration) { + marcXmlEncoder.omitXmlDeclaration(currentOmitXmlDeclaration); + } + + @Override + public void setXmlVersion(final String xmlVersion) { + marcXmlEncoder.setXmlVersion(xmlVersion); + } + + @Override + public void setXmlEncoding(final String xmlEncoding) { + marcXmlEncoder.setXmlEncoding(xmlEncoding); + } + + @Override + public void setFormatted(final boolean formatted) { + marcXmlEncoder.setFormatted(formatted); + } +} + 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 0b8202ec0..74279fcf0 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 @@ -18,12 +18,10 @@ import org.metafacture.commons.XmlUtil; import org.metafacture.framework.FluxCommand; import org.metafacture.framework.MetafactureException; -import org.metafacture.framework.ObjectReceiver; import org.metafacture.framework.StreamReceiver; import org.metafacture.framework.annotations.Description; import org.metafacture.framework.annotations.In; import org.metafacture.framework.annotations.Out; -import org.metafacture.framework.helpers.DefaultStreamPipe; import java.util.Arrays; import java.util.Collections; @@ -36,11 +34,11 @@ * @author Pascal Christoph (dr0i) dug it up again */ -@Description("Encodes a stream into MARCXML.") +@Description("Encodes a stream into MARCXML. Use this only if you can ensure valid MARC21. Also, the leader must be correct and set as one literal. You may want to use encode-marc21xml instead (which can cope with e.g. an irregular leader).") @In(StreamReceiver.class) @Out(String.class) @FluxCommand("encode-marcxml") -public final class MarcXmlEncoder extends DefaultStreamPipe> { +public class MarcXmlEncoder extends AbstractMarcXmlEncoder { public static final String NAMESPACE_NAME = "marc"; public static final String XML_ENCODING = "UTF-8"; @@ -106,6 +104,7 @@ public String close(final Object[] args) { private final StringBuilder builder = new StringBuilder(); + private final StringBuilder builderLeader = new StringBuilder(); private boolean atStreamStart = true; private boolean omitXmlDeclaration = OMIT_XML_DECLARATION; @@ -206,6 +205,9 @@ public void startRecord(final String identifier) { @Override public void endRecord() { + if (builderLeader.length() > 0) { + writeLeader(); + } decrementIndentationLevel(); prettyPrintIndentation(); writeTag(Tag.record::close); @@ -315,6 +317,15 @@ private void writeRaw(final String str) { builder.append(str); } + /** + * Writes an unescaped sequence to the leader literal. + * + * @param str the unescaped sequence to be written + */ + private void writeRawLeader(final String str) { + builderLeader.append(str); + } + /** * Writes an escaped sequence. * @@ -324,14 +335,17 @@ private void writeEscaped(final String str) { builder.append(XmlUtil.escape(str, false)); } + private void writeLeader() { + prettyPrintIndentation(); + writeTag(Tag.leader::open); + writeRaw(builderLeader.toString()); + writeTag(Tag.leader::close); + prettyPrintNewLine(); + } + private boolean writeLeader(final String name, final String value) { if (name.equals(Marc21EventNames.LEADER_ENTITY)) { - prettyPrintIndentation(); - writeTag(Tag.leader::open); - writeRaw(value); - writeTag(Tag.leader::close); - prettyPrintNewLine(); - + writeRawLeader(value); return true; } else { diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoderInterface.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoderInterface.java new file mode 100644 index 000000000..b3e2f8840 --- /dev/null +++ b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoderInterface.java @@ -0,0 +1,49 @@ +package org.metafacture.biblio.marc21; + +public interface MarcXmlEncoderInterface { + + /** + * Sets the flag to decide whether to emit the {@value MarcXmlEncoder#NAMESPACE_NAME} + * namespace + * + * @param emitNamespace true if the namespace is emitted, otherwise false + */ + void setEmitNamespace(boolean emitNamespace); + + /** + * Sets the flag to decide whether to omit the XML declaration. + * + * Default value: {@value MarcXmlEncoder#OMIT_XML_DECLARATION} + * + * @param currentOmitXmlDeclaration true if the XML declaration is omitted, otherwise + * false + */ + void omitXmlDeclaration(boolean currentOmitXmlDeclaration); + + /** + * Sets the XML version. + * + * Default value: {@value MarcXmlEncoder#XML_VERSION} + * + * @param xmlVersion the XML version + */ + void setXmlVersion(String xmlVersion); + + /** + * Sets the XML encoding. + * + * Default value: {@value MarcXmlEncoder#XML_ENCODING} + * + * @param xmlEncoding the XML encoding + */ + void setXmlEncoding(String xmlEncoding); + + /** + * Formats the resulting xml by indentation. Aka "pretty printing". + * + * Default value: {@value MarcXmlEncoder#PRETTY_PRINTED} + * + * @param formatted true if formatting is activated, otherwise false + */ + void setFormatted(boolean formatted); +} diff --git a/metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/Marc21XmlEncoderTest.java b/metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/Marc21XmlEncoderTest.java new file mode 100644 index 000000000..9ef77f973 --- /dev/null +++ b/metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/Marc21XmlEncoderTest.java @@ -0,0 +1,37 @@ +package org.metafacture.biblio.marc21; + +import org.junit.Before; +import org.junit.Test; +import org.metafacture.framework.FormatException; +import org.metafacture.framework.MissingIdException; + +public class Marc21XmlEncoderTest { + MarcXmlEncoderTest marcXmlEncoderTest = new MarcXmlEncoderTest(); + + @Before + public void setUp() { + marcXmlEncoderTest.encoder=new Marc21XmlEncoder(); + marcXmlEncoderTest.initializeEncoder(); + } + + @Test(expected = FormatException.class) + public void createAnRecordWithLeader() { + marcXmlEncoderTest.createAnRecordWithLeader(); + } + + @Test(expected = FormatException.class) + public void issue336_createRecordWithTopLevelLeader() { + marcXmlEncoderTest.issue336_createRecordWithTopLevelLeader(); + } + + @Test + public void issue336_createRecordWithTopLevelLeader_Marc21Xml() { + marcXmlEncoderTest.issue336_createRecordWithTopLevelLeader_Marc21Xml(); + } + + @Test(expected = MissingIdException.class) + public void issue527ShouldEmitLeaderAlwaysAsWholeString() { + marcXmlEncoderTest.issue527ShouldEmitLeaderAlwaysAsWholeString(); + } + +} 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 bc6fb0d49..aa8ab45ea 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 @@ -16,12 +16,20 @@ package org.metafacture.biblio.marc21; +import org.junit.After; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; - -import org.junit.After; import org.junit.Before; +import org.junit.ComparisonFailure; import org.junit.Test; +import static org.metafacture.biblio.marc21.Marc21EventNames.BIBLIOGRAPHIC_LEVEL_LITERAL; +import static org.metafacture.biblio.marc21.Marc21EventNames.CATALOGING_FORM_LITERAL; +import static org.metafacture.biblio.marc21.Marc21EventNames.CHARACTER_CODING_LITERAL; +import static org.metafacture.biblio.marc21.Marc21EventNames.ENCODING_LEVEL_LITERAL; +import static org.metafacture.biblio.marc21.Marc21EventNames.MULTIPART_LEVEL_LITERAL; +import static org.metafacture.biblio.marc21.Marc21EventNames.RECORD_STATUS_LITERAL; +import static org.metafacture.biblio.marc21.Marc21EventNames.RECORD_TYPE_LITERAL; +import static org.metafacture.biblio.marc21.Marc21EventNames.TYPE_OF_CONTROL_LITERAL; import org.metafacture.framework.MetafactureException; import org.metafacture.framework.helpers.DefaultObjectReceiver; @@ -48,11 +56,15 @@ public class MarcXmlEncoderTest { private static final String RECORD_ID = "92005291"; private static StringBuilder resultCollector; - private static MarcXmlEncoder encoder; + AbstractMarcXmlEncoder encoder; @Before public void setUp() { encoder = new MarcXmlEncoder(); + initializeEncoder(); + } + + void initializeEncoder() { encoder.setFormatted(false); encoder.setReceiver(new DefaultObjectReceiver() { @Override @@ -67,7 +79,7 @@ public void process(final String obj) { public void tearDown() { } - private void addOneRecord(MarcXmlEncoder encoder) { + private void addOneRecord(AbstractMarcXmlEncoder encoder) { encoder.startRecord(RECORD_ID); encoder.literal("001", RECORD_ID); encoder.startEntity("010 "); @@ -212,6 +224,42 @@ public void issue336_createRecordWithTopLevelLeader() { assertEquals(expected, actual); } + @Test(expected = ComparisonFailure.class) + public void issue336_createRecordWithTopLevelLeader_Marc21Xml() { + encoder.startRecord("1"); + encoder.literal("001", "8u3287432"); + encoder.literal(Marc21EventNames.LEADER_ENTITY, "00000naa a2200000uc 4500"); + encoder.endRecord(); + encoder.closeStream(); + String expected = XML_DECLARATION + XML_ROOT_OPEN + + "8u3287432" + + "00048naa a2200037uc 4500" + XML_MARC_COLLECTION_END_TAG; + String actual = resultCollector.toString(); + assertEquals(expected, actual); + } + + @Test + public void issue527ShouldEmitLeaderAlwaysAsWholeString() { + encoder.startRecord("1"); + encoder.startEntity(Marc21EventNames.LEADER_ENTITY); + encoder.literal(RECORD_STATUS_LITERAL, "a"); + + encoder.literal(RECORD_TYPE_LITERAL, "o"); + encoder.literal(BIBLIOGRAPHIC_LEVEL_LITERAL, "a"); + encoder.literal(TYPE_OF_CONTROL_LITERAL, " "); + encoder.literal(CHARACTER_CODING_LITERAL, "a"); + encoder.literal(ENCODING_LEVEL_LITERAL, "z"); + encoder.literal(CATALOGING_FORM_LITERAL, "u"); + encoder.literal(MULTIPART_LEVEL_LITERAL, " "); + encoder.endEntity(); + encoder.endRecord(); + encoder.closeStream(); + String expected = XML_DECLARATION + XML_ROOT_OPEN + + "aoa azu " + XML_MARC_COLLECTION_END_TAG; + String actual = resultCollector.toString(); + assertEquals(expected, actual); + } + @Test public void sendDataAndClearWhenRecordStartedAndStreamResets() { encoder.startRecord("1"); From 2690ceb43d367db7027001b105dda9864fc1af61 Mon Sep 17 00:00:00 2001 From: Pascal Christoph Date: Mon, 6 May 2024 16:56:17 +0200 Subject: [PATCH 2/8] Make classes final (#527) --- .../java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java | 2 +- .../main/java/org/metafacture/biblio/marc21/MarcXmlEncoder.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java index 445bce46e..6b94e9bc4 100644 --- a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java +++ b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java @@ -32,7 +32,7 @@ @Out(String.class) @Description("Encodes MARC21 records as MARCXML. It wraps 'encode-marc21 | decode-marc21 | encode-marcxml ' to generate MARCXML more safely, especially when the building the 'leader'.") @FluxCommand("encode-marc21xml") -public class Marc21XmlEncoder extends AbstractMarcXmlEncoder { +public final class Marc21XmlEncoder extends AbstractMarcXmlEncoder { private final Marc21Decoder marc21Decoder = new Marc21Decoder(); private final Marc21Encoder marc21Encoder = new Marc21Encoder(); private final MarcXmlEncoder marcXmlEncoder = new MarcXmlEncoder(); 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 74279fcf0..0ab48e3e0 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 @@ -38,7 +38,7 @@ @In(StreamReceiver.class) @Out(String.class) @FluxCommand("encode-marcxml") -public class MarcXmlEncoder extends AbstractMarcXmlEncoder { +public final class MarcXmlEncoder extends AbstractMarcXmlEncoder { public static final String NAMESPACE_NAME = "marc"; public static final String XML_ENCODING = "UTF-8"; From b5342df2b1c2f9bba67c3b5ab992a8a0cbbac7e8 Mon Sep 17 00:00:00 2001 From: Pascal Christoph Date: Tue, 7 May 2024 13:09:56 +0200 Subject: [PATCH 3/8] Update metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java Co-authored-by: Jens Wille --- .../java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java index 6b94e9bc4..503705200 100644 --- a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java +++ b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java @@ -30,7 +30,7 @@ */ @In(StreamReceiver.class) @Out(String.class) -@Description("Encodes MARC21 records as MARCXML. It wraps 'encode-marc21 | decode-marc21 | encode-marcxml ' to generate MARCXML more safely, especially when the building the 'leader'.") +@Description("Encodes MARC21 records as MARCXML. It wraps `encode-marc21 | decode-marc21 | encode-marcxml` to generate MARCXML more safely, especially when building the `leader`.") @FluxCommand("encode-marc21xml") public final class Marc21XmlEncoder extends AbstractMarcXmlEncoder { private final Marc21Decoder marc21Decoder = new Marc21Decoder(); From f29808450ca3b1e2117719a50fc41aaf7d2af082 Mon Sep 17 00:00:00 2001 From: Pascal Christoph Date: Tue, 7 May 2024 13:10:17 +0200 Subject: [PATCH 4/8] Update metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoder.java Co-authored-by: Jens Wille --- .../main/java/org/metafacture/biblio/marc21/MarcXmlEncoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0ab48e3e0..5edcf3796 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 @@ -34,7 +34,7 @@ * @author Pascal Christoph (dr0i) dug it up again */ -@Description("Encodes a stream into MARCXML. Use this only if you can ensure valid MARC21. Also, the leader must be correct and set as one literal. You may want to use encode-marc21xml instead (which can cope with e.g. an irregular leader).") +@Description("Encodes a stream into MARCXML. Use this only if you can ensure valid MARC21. Also, the leader must be correct and set as one literal. You may want to use `encode-marc21xml` instead (which can cope with e.g. an irregular leader).") @In(StreamReceiver.class) @Out(String.class) @FluxCommand("encode-marcxml") From 2baa328f3205fce7cc36f6cce2f7affc11c41a97 Mon Sep 17 00:00:00 2001 From: Pascal Christoph Date: Tue, 7 May 2024 13:10:31 +0200 Subject: [PATCH 5/8] Update metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoder.java Co-authored-by: Jens Wille --- .../main/java/org/metafacture/biblio/marc21/MarcXmlEncoder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5edcf3796..40288f2d0 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 @@ -104,7 +104,7 @@ public String close(final Object[] args) { private final StringBuilder builder = new StringBuilder(); - private final StringBuilder builderLeader = new StringBuilder(); + private final StringBuilder builderLeader = new StringBuilder(); private boolean atStreamStart = true; private boolean omitXmlDeclaration = OMIT_XML_DECLARATION; From fde6ba13dbedd4cf87a0c8c3df5d8fae1649a373 Mon Sep 17 00:00:00 2001 From: Pascal Christoph Date: Tue, 7 May 2024 16:21:28 +0200 Subject: [PATCH 6/8] WIP incorporate marc21XmlEncoder into marcXmlEncoder --- .../biblio/marc21/AbstractMarcXmlEncoder.java | 10 - .../biblio/marc21/Marc21XmlEncoder.java | 115 --------- .../biblio/marc21/MarcXmlEncoder.java | 239 ++++++++++++------ .../marc21/MarcXmlEncoderInterface.java | 49 ---- .../biblio/marc21/Marc21XmlEncoderTest.java | 37 --- .../biblio/marc21/MarcXmlEncoderTest.java | 72 +++++- 6 files changed, 226 insertions(+), 296 deletions(-) delete mode 100644 metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/AbstractMarcXmlEncoder.java delete mode 100644 metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java delete mode 100644 metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoderInterface.java delete mode 100644 metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/Marc21XmlEncoderTest.java diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/AbstractMarcXmlEncoder.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/AbstractMarcXmlEncoder.java deleted file mode 100644 index a54d5b455..000000000 --- a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/AbstractMarcXmlEncoder.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.metafacture.biblio.marc21; - -import org.metafacture.framework.ObjectReceiver; -import org.metafacture.framework.helpers.DefaultStreamPipe; - -public abstract class AbstractMarcXmlEncoder extends DefaultStreamPipe> implements MarcXmlEncoderInterface { - - protected void onResetStream() { - } -} diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java deleted file mode 100644 index 503705200..000000000 --- a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/Marc21XmlEncoder.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2024 hbz - * - * Licensed under the Apache License, Version 2.0 the "License"; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.metafacture.biblio.marc21; - -import org.metafacture.framework.FluxCommand; -import org.metafacture.framework.StreamReceiver; -import org.metafacture.framework.annotations.Description; -import org.metafacture.framework.annotations.In; -import org.metafacture.framework.annotations.Out; - -/** - * Acts as a wrapper: pipes input to Marc21Encoder which output is piped to Marc21Decoder which output is piped to MarcXmlEncoder. - * - * @author Pascal Christoph (dr0i) - * - */ -@In(StreamReceiver.class) -@Out(String.class) -@Description("Encodes MARC21 records as MARCXML. It wraps `encode-marc21 | decode-marc21 | encode-marcxml` to generate MARCXML more safely, especially when building the `leader`.") -@FluxCommand("encode-marc21xml") -public final class Marc21XmlEncoder extends AbstractMarcXmlEncoder { - private final Marc21Decoder marc21Decoder = new Marc21Decoder(); - private final Marc21Encoder marc21Encoder = new Marc21Encoder(); - private final MarcXmlEncoder marcXmlEncoder = new MarcXmlEncoder(); - - /** - * Creates an instance of {@link Marc21XmlEncoder}. - */ - public Marc21XmlEncoder() { - marc21Decoder.setEmitLeaderAsWhole(true); - - marc21Encoder.setReceiver(marc21Decoder); - marc21Decoder.setReceiver(marcXmlEncoder); - } - - @Override - protected void onSetReceiver() { - marcXmlEncoder.setReceiver(getReceiver()); - } - - @Override - public void startRecord(final String identifier) { - marc21Encoder.startRecord(identifier); - } - - @Override - public void endRecord() { - marc21Encoder.endRecord(); - } - - @Override - public void startEntity(final String name) { - marc21Encoder.startEntity(name); - } - - @Override - public void endEntity() { - marc21Encoder.endEntity(); - } - - @Override - public void literal(final String name, final String value) { - marc21Encoder.literal(name, value); - } - - @Override - protected void onCloseStream() { - marc21Encoder.closeStream(); - } - - @Override - public void onResetStream() { - marc21Encoder.resetStream(); - } - - @Override - public void setEmitNamespace(final boolean emitNamespace) { - marcXmlEncoder.setEmitNamespace(emitNamespace); - } - - @Override - public void omitXmlDeclaration(final boolean currentOmitXmlDeclaration) { - marcXmlEncoder.omitXmlDeclaration(currentOmitXmlDeclaration); - } - - @Override - public void setXmlVersion(final String xmlVersion) { - marcXmlEncoder.setXmlVersion(xmlVersion); - } - - @Override - public void setXmlEncoding(final String xmlEncoding) { - marcXmlEncoder.setXmlEncoding(xmlEncoding); - } - - @Override - public void setFormatted(final boolean formatted) { - marcXmlEncoder.setFormatted(formatted); - } -} - 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 40288f2d0..42fca105d 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 @@ -18,10 +18,12 @@ import org.metafacture.commons.XmlUtil; import org.metafacture.framework.FluxCommand; import org.metafacture.framework.MetafactureException; +import org.metafacture.framework.ObjectReceiver; import org.metafacture.framework.StreamReceiver; import org.metafacture.framework.annotations.Description; import org.metafacture.framework.annotations.In; import org.metafacture.framework.annotations.Out; +import org.metafacture.framework.helpers.DefaultStreamPipe; import java.util.Arrays; import java.util.Collections; @@ -34,17 +36,18 @@ * @author Pascal Christoph (dr0i) dug it up again */ -@Description("Encodes a stream into MARCXML. Use this only if you can ensure valid MARC21. Also, the leader must be correct and set as one literal. You may want to use `encode-marc21xml` instead (which can cope with e.g. an irregular leader).") +@Description("Encodes a stream into MARCXML. If you can't ensure valid MARC21 (e.g. the leader isn't correct or not set as one literal) then set the parameter `ensureCorrectMarc21Xml` to `true`.") @In(StreamReceiver.class) @Out(String.class) @FluxCommand("encode-marcxml") -public final class MarcXmlEncoder extends AbstractMarcXmlEncoder { +public final class MarcXmlEncoder extends DefaultStreamPipe> { public static final String NAMESPACE_NAME = "marc"; public static final String XML_ENCODING = "UTF-8"; public static final String XML_VERSION = "1.0"; public static final boolean PRETTY_PRINTED = true; public static final boolean OMIT_XML_DECLARATION = false; + public static final boolean ENSURE_CORRECT_MARC21_XML = false; private static final String ROOT_OPEN = ""; private static final String ROOT_CLOSE = ""; @@ -102,6 +105,8 @@ public String close(final Object[] args) { private static final int TAG_BEGIN = 0; private static final int TAG_END = 3; + private static boolean isInstanced; + private final StringBuilder builder = new StringBuilder(); private final StringBuilder builderLeader = new StringBuilder(); @@ -120,10 +125,23 @@ public String close(final Object[] args) { private boolean formatted = PRETTY_PRINTED; private int recordAttributeOffset; + private boolean ensureCorrectMarc21Xml = ENSURE_CORRECT_MARC21_XML; + private final Marc21Decoder marc21Decoder = new Marc21Decoder(); + private final Marc21Encoder marc21Encoder = new Marc21Encoder(); + private MarcXmlEncoder marcXmlEncoder; /** * Creates an instance of {@link MarcXmlEncoder}. */ + public MarcXmlEncoder() { + if (!isInstanced) { + isInstanced = true; + marcXmlEncoder = new MarcXmlEncoder(); + marc21Decoder.setEmitLeaderAsWhole(true); + + marc21Encoder.setReceiver(marc21Decoder); + marc21Decoder.setReceiver(marcXmlEncoder); + } } /** @@ -135,11 +153,13 @@ public MarcXmlEncoder() { public void setEmitNamespace(final boolean emitNamespace) { this.emitNamespace = emitNamespace; namespacePrefix = new Object[]{emitNamespace ? NAMESPACE_PREFIX : EMPTY}; + if (marcXmlEncoder != null) { + marcXmlEncoder.setEmitNamespace(emitNamespace); + } } /** * Sets the flag to decide whether to omit the XML declaration. - * * Default value: {@value #OMIT_XML_DECLARATION} * * @param currentOmitXmlDeclaration true if the XML declaration is omitted, otherwise @@ -147,145 +167,217 @@ public void setEmitNamespace(final boolean emitNamespace) { */ public void omitXmlDeclaration(final boolean currentOmitXmlDeclaration) { omitXmlDeclaration = currentOmitXmlDeclaration; + if (marcXmlEncoder != null) { + marcXmlEncoder.omitXmlDeclaration(currentOmitXmlDeclaration); + } } /** * Sets the XML version. - * * Default value: {@value #XML_VERSION} * * @param xmlVersion the XML version */ public void setXmlVersion(final String xmlVersion) { this.xmlVersion = xmlVersion; + this.xmlVersion = xmlVersion; + if (marcXmlEncoder != null) { + marcXmlEncoder.setXmlVersion(xmlVersion); + } } /** * Sets the XML encoding. - * * Default value: {@value #XML_ENCODING} * * @param xmlEncoding the XML encoding */ public void setXmlEncoding(final String xmlEncoding) { this.xmlEncoding = xmlEncoding; + if (marcXmlEncoder != null) { + marcXmlEncoder.setXmlEncoding(xmlEncoding); + } } /** - * Formats the resulting xml by indentation. Aka "pretty printing". + * Sets to ensure correct MARC21 XML. + * If true, the input data is validated to ensure correct MARC21. Also the leader may be generated. + * It acts as a wrapper: the input is piped to {@link org.metafacture.biblio.marc21.Marc21Encoder}, whose output is piped to {@link org.metafacture.biblio.marc21.Marc21Decoder}, whose output is piped to {@link org.metafacture.biblio.marc21.MarcXmlEncoder}. + * This validation and treatment of the leader is more safe but comes with a performance impact. * + * Default value: {@value #ENSURE_CORRECT_MARC21_XML} + * + * @param ensureCorrectMarc21Xml if true the input data is validated to ensure correct MARC21. Also the leader may be generated. + */ + public void setEnsureCorrectMarc21Xml(final boolean ensureCorrectMarc21Xml) { + this.ensureCorrectMarc21Xml = ensureCorrectMarc21Xml; + isInstanced = true; + marcXmlEncoder = new MarcXmlEncoder(); + marc21Decoder.setEmitLeaderAsWhole(true); + + marc21Encoder.setReceiver(marc21Decoder); + marc21Decoder.setReceiver(marcXmlEncoder); + } + + /** + * Formats the resulting xml by indentation. Aka "pretty printing". * Default value: {@value #PRETTY_PRINTED} * * @param formatted true if formatting is activated, otherwise false */ public void setFormatted(final boolean formatted) { this.formatted = formatted; + if (marcXmlEncoder != null) { + marcXmlEncoder.setFormatted(formatted); + } + } + + @Override + protected void onSetReceiver() { + if (marcXmlEncoder != null) { + marcXmlEncoder.setReceiver(getReceiver()); + } } @Override public void startRecord(final String identifier) { - if (atStreamStart) { - if (!omitXmlDeclaration) { - writeHeader(); + if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { + marc21Encoder.startRecord(identifier); + } + else { + if (atStreamStart) { + if (!omitXmlDeclaration) { + writeHeader(); + prettyPrintNewLine(); + } + writeTag(Tag.collection::open, emitNamespace ? NAMESPACE_SUFFIX : EMPTY, emitNamespace ? SCHEMA_ATTRIBUTES : EMPTY); prettyPrintNewLine(); + incrementIndentationLevel(); } - writeTag(Tag.collection::open, emitNamespace ? NAMESPACE_SUFFIX : EMPTY, emitNamespace ? SCHEMA_ATTRIBUTES : EMPTY); + atStreamStart = false; + + prettyPrintIndentation(); + writeTag(Tag.record::open); + recordAttributeOffset = builder.length() - 1; prettyPrintNewLine(); + incrementIndentationLevel(); } - atStreamStart = false; - - prettyPrintIndentation(); - writeTag(Tag.record::open); - recordAttributeOffset = builder.length() - 1; - prettyPrintNewLine(); - - incrementIndentationLevel(); } @Override public void endRecord() { - if (builderLeader.length() > 0) { - writeLeader(); + if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { + marc21Encoder.endRecord(); + } + else { + if (builderLeader.length() > 0) { + writeLeader(); + } + decrementIndentationLevel(); + prettyPrintIndentation(); + writeTag(Tag.record::close); + prettyPrintNewLine(); + sendAndClearData(); } - decrementIndentationLevel(); - prettyPrintIndentation(); - writeTag(Tag.record::close); - prettyPrintNewLine(); - sendAndClearData(); } @Override public void startEntity(final String name) { - currentEntity = name; - if (!name.equals(Marc21EventNames.LEADER_ENTITY)) { - if (name.length() != LEADER_ENTITY_LENGTH) { - final String message = String.format("Entity too short." + "Got a string ('%s') of length %d." + - "Expected a length of " + LEADER_ENTITY_LENGTH + " (field + indicators).", name, name.length()); - throw new MetafactureException(message); - } + if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { + marc21Encoder.startEntity(name); + } + else { + currentEntity = name; + if (!name.equals(Marc21EventNames.LEADER_ENTITY)) { + if (name.length() != LEADER_ENTITY_LENGTH) { + final String message = String.format("Entity too short." + "Got a string ('%s') of length %d." + + "Expected a length of " + LEADER_ENTITY_LENGTH + " (field + indicators).", name, name.length()); + throw new MetafactureException(message); + } - final String tag = name.substring(TAG_BEGIN, TAG_END); - final String ind1 = name.substring(IND1_BEGIN, IND1_END); - final String ind2 = name.substring(IND2_BEGIN, IND2_END); - prettyPrintIndentation(); - writeTag(Tag.datafield::open, tag, ind1, ind2); - prettyPrintNewLine(); - incrementIndentationLevel(); + final String tag = name.substring(TAG_BEGIN, TAG_END); + final String ind1 = name.substring(IND1_BEGIN, IND1_END); + final String ind2 = name.substring(IND2_BEGIN, IND2_END); + prettyPrintIndentation(); + writeTag(Tag.datafield::open, tag, ind1, ind2); + prettyPrintNewLine(); + incrementIndentationLevel(); + } } } @Override public void endEntity() { - if (!currentEntity.equals(Marc21EventNames.LEADER_ENTITY)) { - decrementIndentationLevel(); - prettyPrintIndentation(); - writeTag(Tag.datafield::close); - prettyPrintNewLine(); + if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { + marc21Encoder.endEntity(); + } + else { + if (!currentEntity.equals(Marc21EventNames.LEADER_ENTITY)) { + decrementIndentationLevel(); + prettyPrintIndentation(); + writeTag(Tag.datafield::close); + prettyPrintNewLine(); + } + currentEntity = ""; } - currentEntity = ""; } @Override public void literal(final String name, final String value) { - if ("".equals(currentEntity)) { - if (name.equals(Marc21EventNames.MARCXML_TYPE_LITERAL)) { - if (value != null) { - builder.insert(recordAttributeOffset, String.format(ATTRIBUTE_TEMPLATE, name, value)); + if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { + marc21Encoder.literal(name, value); + } + else { + if ("".equals(currentEntity)) { + if (name.equals(Marc21EventNames.MARCXML_TYPE_LITERAL)) { + if (value != null) { + builder.insert(recordAttributeOffset, String.format(ATTRIBUTE_TEMPLATE, name, value)); + } + } + else if (!writeLeader(name, value)) { + prettyPrintIndentation(); + writeTag(Tag.controlfield::open, name); + if (value != null) { + writeEscaped(value.trim()); + } + writeTag(Tag.controlfield::close); + prettyPrintNewLine(); } } - else if (!writeLeader(name, value)) { + else if (!writeLeader(currentEntity, value)) { prettyPrintIndentation(); - writeTag(Tag.controlfield::open, name); - if (value != null) { - writeEscaped(value.trim()); - } - writeTag(Tag.controlfield::close); + writeTag(Tag.subfield::open, name); + writeEscaped(value.trim()); + writeTag(Tag.subfield::close); prettyPrintNewLine(); } } - else if (!writeLeader(currentEntity, value)) { - prettyPrintIndentation(); - writeTag(Tag.subfield::open, name); - writeEscaped(value.trim()); - writeTag(Tag.subfield::close); - prettyPrintNewLine(); - } } @Override protected void onResetStream() { - if (!atStreamStart) { - writeFooter(); + if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { + marc21Encoder.resetStream(); + } + else { + if (!atStreamStart) { + writeFooter(); + } + sendAndClearData(); + atStreamStart = true; } - sendAndClearData(); - atStreamStart = true; } @Override protected void onCloseStream() { - writeFooter(); - sendAndClearData(); + if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { + marc21Encoder.closeStream(); + } + else { + writeFooter(); + sendAndClearData(); + } } /** Increments the indentation level by one */ @@ -373,9 +465,14 @@ private void prettyPrintNewLine() { } private void sendAndClearData() { - getReceiver().process(builder.toString()); - builder.delete(0, builder.length()); - recordAttributeOffset = 0; + if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { + marcXmlEncoder.sendAndClearData(); + } + else { + getReceiver().process(builder.toString()); + builder.delete(0, builder.length()); + recordAttributeOffset = 0; + } } } diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoderInterface.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoderInterface.java deleted file mode 100644 index b3e2f8840..000000000 --- a/metafacture-biblio/src/main/java/org/metafacture/biblio/marc21/MarcXmlEncoderInterface.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.metafacture.biblio.marc21; - -public interface MarcXmlEncoderInterface { - - /** - * Sets the flag to decide whether to emit the {@value MarcXmlEncoder#NAMESPACE_NAME} - * namespace - * - * @param emitNamespace true if the namespace is emitted, otherwise false - */ - void setEmitNamespace(boolean emitNamespace); - - /** - * Sets the flag to decide whether to omit the XML declaration. - * - * Default value: {@value MarcXmlEncoder#OMIT_XML_DECLARATION} - * - * @param currentOmitXmlDeclaration true if the XML declaration is omitted, otherwise - * false - */ - void omitXmlDeclaration(boolean currentOmitXmlDeclaration); - - /** - * Sets the XML version. - * - * Default value: {@value MarcXmlEncoder#XML_VERSION} - * - * @param xmlVersion the XML version - */ - void setXmlVersion(String xmlVersion); - - /** - * Sets the XML encoding. - * - * Default value: {@value MarcXmlEncoder#XML_ENCODING} - * - * @param xmlEncoding the XML encoding - */ - void setXmlEncoding(String xmlEncoding); - - /** - * Formats the resulting xml by indentation. Aka "pretty printing". - * - * Default value: {@value MarcXmlEncoder#PRETTY_PRINTED} - * - * @param formatted true if formatting is activated, otherwise false - */ - void setFormatted(boolean formatted); -} diff --git a/metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/Marc21XmlEncoderTest.java b/metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/Marc21XmlEncoderTest.java deleted file mode 100644 index 9ef77f973..000000000 --- a/metafacture-biblio/src/test/java/org/metafacture/biblio/marc21/Marc21XmlEncoderTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.metafacture.biblio.marc21; - -import org.junit.Before; -import org.junit.Test; -import org.metafacture.framework.FormatException; -import org.metafacture.framework.MissingIdException; - -public class Marc21XmlEncoderTest { - MarcXmlEncoderTest marcXmlEncoderTest = new MarcXmlEncoderTest(); - - @Before - public void setUp() { - marcXmlEncoderTest.encoder=new Marc21XmlEncoder(); - marcXmlEncoderTest.initializeEncoder(); - } - - @Test(expected = FormatException.class) - public void createAnRecordWithLeader() { - marcXmlEncoderTest.createAnRecordWithLeader(); - } - - @Test(expected = FormatException.class) - public void issue336_createRecordWithTopLevelLeader() { - marcXmlEncoderTest.issue336_createRecordWithTopLevelLeader(); - } - - @Test - public void issue336_createRecordWithTopLevelLeader_Marc21Xml() { - marcXmlEncoderTest.issue336_createRecordWithTopLevelLeader_Marc21Xml(); - } - - @Test(expected = MissingIdException.class) - public void issue527ShouldEmitLeaderAlwaysAsWholeString() { - marcXmlEncoderTest.issue527ShouldEmitLeaderAlwaysAsWholeString(); - } - -} 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 aa8ab45ea..f57b87e3f 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 @@ -30,7 +30,9 @@ import static org.metafacture.biblio.marc21.Marc21EventNames.RECORD_STATUS_LITERAL; import static org.metafacture.biblio.marc21.Marc21EventNames.RECORD_TYPE_LITERAL; import static org.metafacture.biblio.marc21.Marc21EventNames.TYPE_OF_CONTROL_LITERAL; +import org.metafacture.framework.FormatException; import org.metafacture.framework.MetafactureException; +import org.metafacture.framework.MissingIdException; import org.metafacture.framework.helpers.DefaultObjectReceiver; /** @@ -56,17 +58,22 @@ public class MarcXmlEncoderTest { private static final String RECORD_ID = "92005291"; private static StringBuilder resultCollector; - AbstractMarcXmlEncoder encoder; + private static MarcXmlEncoder encoder; + private static MarcXmlEncoder encoder_ensureCorrectMarc21Xml; + @Before public void setUp() { encoder = new MarcXmlEncoder(); - initializeEncoder(); + initializeEncoder(encoder); + encoder_ensureCorrectMarc21Xml = new MarcXmlEncoder(); + encoder_ensureCorrectMarc21Xml.setEnsureCorrectMarc21Xml(true); + initializeEncoder(encoder_ensureCorrectMarc21Xml); } - void initializeEncoder() { - encoder.setFormatted(false); - encoder.setReceiver(new DefaultObjectReceiver() { + private void initializeEncoder(MarcXmlEncoder enc) { + enc.setFormatted(false); + enc.setReceiver(new DefaultObjectReceiver() { @Override public void process(final String obj) { resultCollector.append(obj); @@ -79,7 +86,7 @@ public void process(final String obj) { public void tearDown() { } - private void addOneRecord(AbstractMarcXmlEncoder encoder) { + private void addOneRecord(MarcXmlEncoder encoder) { encoder.startRecord(RECORD_ID); encoder.literal("001", RECORD_ID); encoder.startEntity("010 "); @@ -199,13 +206,23 @@ public void emitExceptionWhenEntityLengthNot5() { } @Test - public void createAnRecordWithLeader() { - encoder.startRecord("1"); - encoder.startEntity(Marc21EventNames.LEADER_ENTITY); - encoder.literal(Marc21EventNames.LEADER_ENTITY, "dummy"); - encoder.endEntity(); - encoder.endRecord(); - encoder.closeStream(); + public void createAnRecordWithLeader(){ + createAnRecordWithLeader(encoder); + } + + @Test(expected = FormatException.class) + public void createAnRecordWithLeader_ensureCorrectMarc21Xml() { + encoder_ensureCorrectMarc21Xml.setEnsureCorrectMarc21Xml(true); + createAnRecordWithLeader(encoder_ensureCorrectMarc21Xml); + } + + private void createAnRecordWithLeader(MarcXmlEncoder enc) { + enc.startRecord("1"); + enc.startEntity(Marc21EventNames.LEADER_ENTITY); + enc.literal(Marc21EventNames.LEADER_ENTITY, "dummy"); + enc.endEntity(); + enc.endRecord(); + enc.closeStream(); String expected = XML_DECLARATION + XML_ROOT_OPEN + "dummy" + XML_MARC_COLLECTION_END_TAG; String actual = resultCollector.toString(); @@ -224,8 +241,23 @@ public void issue336_createRecordWithTopLevelLeader() { assertEquals(expected, actual); } + @Test + public void issue336_createRecordWithTopLevelLeader_ensureCorrectMarc21Xml() { + issue336_createRecordWithTopLevelLeader_correctMarc21Xml(encoder_ensureCorrectMarc21Xml); + } + + @Test(expected = NullPointerException.class) + public void setParameterAfterSettingReceiver_ensureCorrectMarc21Xml() { + encoder_ensureCorrectMarc21Xml.setEnsureCorrectMarc21Xml(true); + issue336_createRecordWithTopLevelLeader_correctMarc21Xml(encoder_ensureCorrectMarc21Xml); + } + @Test(expected = ComparisonFailure.class) - public void issue336_createRecordWithTopLevelLeader_Marc21Xml() { + public void issue336_createRecordWithTopLevelLeader_correctMarc21Xml() { + issue336_createRecordWithTopLevelLeader_correctMarc21Xml(encoder); + } + + private void issue336_createRecordWithTopLevelLeader_correctMarc21Xml(MarcXmlEncoder encoder) { encoder.startRecord("1"); encoder.literal("001", "8u3287432"); encoder.literal(Marc21EventNames.LEADER_ENTITY, "00000naa a2200000uc 4500"); @@ -240,6 +272,16 @@ public void issue336_createRecordWithTopLevelLeader_Marc21Xml() { @Test public void issue527ShouldEmitLeaderAlwaysAsWholeString() { + issue527ShouldEmitLeaderAlwaysAsWholeString(encoder); + } + + @Test(expected = MissingIdException.class) + public void issue527ShouldEmitLeaderAlwaysAsWholeString_ensureCorrectMarc21Xml() { + encoder_ensureCorrectMarc21Xml.setEnsureCorrectMarc21Xml(true); + this.issue527ShouldEmitLeaderAlwaysAsWholeString(encoder_ensureCorrectMarc21Xml); + } + + private void issue527ShouldEmitLeaderAlwaysAsWholeString(MarcXmlEncoder encoder) { encoder.startRecord("1"); encoder.startEntity(Marc21EventNames.LEADER_ENTITY); encoder.literal(RECORD_STATUS_LITERAL, "a"); @@ -260,6 +302,8 @@ public void issue527ShouldEmitLeaderAlwaysAsWholeString() { assertEquals(expected, actual); } + + @Test public void sendDataAndClearWhenRecordStartedAndStreamResets() { encoder.startRecord("1"); From e75fd57531c41effbde59945f89780ed0307deb3 Mon Sep 17 00:00:00 2001 From: Pascal Christoph Date: Mon, 13 May 2024 13:49:38 +0200 Subject: [PATCH 7/8] Take hints from @blackwinter into account (#531) --- .../biblio/marc21/MarcXmlEncoder.java | 26 +++++++-------- .../biblio/marc21/MarcXmlEncoderTest.java | 32 ++++++++----------- 2 files changed, 27 insertions(+), 31 deletions(-) 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 42fca105d..3ab8cea04 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 @@ -335,7 +335,7 @@ public void literal(final String name, final String value) { builder.insert(recordAttributeOffset, String.format(ATTRIBUTE_TEMPLATE, name, value)); } } - else if (!writeLeader(name, value)) { + else if (!appendLeader(name, value)) { prettyPrintIndentation(); writeTag(Tag.controlfield::open, name); if (value != null) { @@ -345,7 +345,7 @@ else if (!writeLeader(name, value)) { prettyPrintNewLine(); } } - else if (!writeLeader(currentEntity, value)) { + else if (!appendLeader(currentEntity, value)) { prettyPrintIndentation(); writeTag(Tag.subfield::open, name); writeEscaped(value.trim()); @@ -414,10 +414,20 @@ private void writeRaw(final String str) { * * @param str the unescaped sequence to be written */ - private void writeRawLeader(final String str) { + private void appendLeader(final String str) { builderLeader.append(str); } + private boolean appendLeader(final String name, final String value) { + if (name.equals(Marc21EventNames.LEADER_ENTITY)) { + appendLeader(value); + return true; + } + else { + return false; + } + } + /** * Writes an escaped sequence. * @@ -435,16 +445,6 @@ private void writeLeader() { prettyPrintNewLine(); } - private boolean writeLeader(final String name, final String value) { - if (name.equals(Marc21EventNames.LEADER_ENTITY)) { - writeRawLeader(value); - return true; - } - else { - return false; - } - } - private void writeTag(final Function function, final Object... args) { final Object[] allArgs = Arrays.copyOf(namespacePrefix, namespacePrefix.length + args.length); System.arraycopy(args, 0, allArgs, namespacePrefix.length, args.length); 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 f57b87e3f..fa258cf29 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 @@ -20,7 +20,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.junit.Before; -import org.junit.ComparisonFailure; import org.junit.Test; import static org.metafacture.biblio.marc21.Marc21EventNames.BIBLIOGRAPHIC_LEVEL_LITERAL; import static org.metafacture.biblio.marc21.Marc21EventNames.CATALOGING_FORM_LITERAL; @@ -58,15 +57,13 @@ public class MarcXmlEncoderTest { private static final String RECORD_ID = "92005291"; private static StringBuilder resultCollector; - private static MarcXmlEncoder encoder; - private static MarcXmlEncoder encoder_ensureCorrectMarc21Xml; + private final MarcXmlEncoder encoder = new MarcXmlEncoder(); + private final MarcXmlEncoder encoder_ensureCorrectMarc21Xml = new MarcXmlEncoder(); @Before public void setUp() { - encoder = new MarcXmlEncoder(); initializeEncoder(encoder); - encoder_ensureCorrectMarc21Xml = new MarcXmlEncoder(); encoder_ensureCorrectMarc21Xml.setEnsureCorrectMarc21Xml(true); initializeEncoder(encoder_ensureCorrectMarc21Xml); } @@ -243,21 +240,15 @@ public void issue336_createRecordWithTopLevelLeader() { @Test public void issue336_createRecordWithTopLevelLeader_ensureCorrectMarc21Xml() { - issue336_createRecordWithTopLevelLeader_correctMarc21Xml(encoder_ensureCorrectMarc21Xml); + createRecordWithTopLevelLeader(encoder_ensureCorrectMarc21Xml, "00048naa a2200037uc 4500"); } - @Test(expected = NullPointerException.class) - public void setParameterAfterSettingReceiver_ensureCorrectMarc21Xml() { - encoder_ensureCorrectMarc21Xml.setEnsureCorrectMarc21Xml(true); - issue336_createRecordWithTopLevelLeader_correctMarc21Xml(encoder_ensureCorrectMarc21Xml); - } - - @Test(expected = ComparisonFailure.class) - public void issue336_createRecordWithTopLevelLeader_correctMarc21Xml() { - issue336_createRecordWithTopLevelLeader_correctMarc21Xml(encoder); + @Test + public void issue336_createRecordWithTopLevelLeader_defaultMarc21Xml() { + createRecordWithTopLevelLeader(encoder,"00000naa a2200000uc 4500"); } - private void issue336_createRecordWithTopLevelLeader_correctMarc21Xml(MarcXmlEncoder encoder) { + private void createRecordWithTopLevelLeader(final MarcXmlEncoder encoder, final String expectedLeader) { encoder.startRecord("1"); encoder.literal("001", "8u3287432"); encoder.literal(Marc21EventNames.LEADER_ENTITY, "00000naa a2200000uc 4500"); @@ -265,11 +256,17 @@ private void issue336_createRecordWithTopLevelLeader_correctMarc21Xml(MarcXmlEnc encoder.closeStream(); String expected = XML_DECLARATION + XML_ROOT_OPEN + "8u3287432" + - "00048naa a2200037uc 4500" + XML_MARC_COLLECTION_END_TAG; + "" + expectedLeader + "" + XML_MARC_COLLECTION_END_TAG; String actual = resultCollector.toString(); assertEquals(expected, actual); } + @Test(expected = NullPointerException.class) + public void ensureCorrectMarc21XmlParameterAfterSettingReceiver() { + encoder_ensureCorrectMarc21Xml.setEnsureCorrectMarc21Xml(true); + createRecordWithTopLevelLeader(encoder_ensureCorrectMarc21Xml,"ignored"); + } + @Test public void issue527ShouldEmitLeaderAlwaysAsWholeString() { issue527ShouldEmitLeaderAlwaysAsWholeString(encoder); @@ -285,7 +282,6 @@ private void issue527ShouldEmitLeaderAlwaysAsWholeString(MarcXmlEncoder encoder) encoder.startRecord("1"); encoder.startEntity(Marc21EventNames.LEADER_ENTITY); encoder.literal(RECORD_STATUS_LITERAL, "a"); - encoder.literal(RECORD_TYPE_LITERAL, "o"); encoder.literal(BIBLIOGRAPHIC_LEVEL_LITERAL, "a"); encoder.literal(TYPE_OF_CONTROL_LITERAL, " "); From c1f8f9880332f05918b018e44f898463c7aee026 Mon Sep 17 00:00:00 2001 From: Jens Wille Date: Tue, 14 May 2024 10:11:35 +0200 Subject: [PATCH 8/8] Refactor `MarcXmlEncoder` towards clearer separation of concern. (#531) The outer class is responsible for routing the stream into the appropriate pipe, the inner class contains the actual (previous) implementation. --- .../biblio/marc21/MarcXmlEncoder.java | 370 +++++++++--------- .../biblio/marc21/MarcXmlEncoderTest.java | 98 ++--- 2 files changed, 222 insertions(+), 246 deletions(-) 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 3ab8cea04..a2517c02a 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 @@ -105,43 +105,23 @@ public String close(final Object[] args) { private static final int TAG_BEGIN = 0; private static final int TAG_END = 3; - private static boolean isInstanced; + private final Encoder encoder = new Encoder(); + private final Marc21Decoder decoder = new Marc21Decoder(); + private final Marc21Encoder wrapper = new Marc21Encoder(); - private final StringBuilder builder = new StringBuilder(); + private DefaultStreamPipe> pipe; - private final StringBuilder builderLeader = new StringBuilder(); - private boolean atStreamStart = true; - - private boolean omitXmlDeclaration = OMIT_XML_DECLARATION; - private String xmlVersion = XML_VERSION; - private String xmlEncoding = XML_ENCODING; - - private String currentEntity = ""; - - private boolean emitNamespace = true; - private Object[] namespacePrefix = new Object[]{emitNamespace ? NAMESPACE_PREFIX : EMPTY}; - - private int indentationLevel; - private boolean formatted = PRETTY_PRINTED; - private int recordAttributeOffset; - - private boolean ensureCorrectMarc21Xml = ENSURE_CORRECT_MARC21_XML; - private final Marc21Decoder marc21Decoder = new Marc21Decoder(); - private final Marc21Encoder marc21Encoder = new Marc21Encoder(); - private MarcXmlEncoder marcXmlEncoder; /** * Creates an instance of {@link MarcXmlEncoder}. */ - public MarcXmlEncoder() { - if (!isInstanced) { - isInstanced = true; - marcXmlEncoder = new MarcXmlEncoder(); - marc21Decoder.setEmitLeaderAsWhole(true); + decoder.setEmitLeaderAsWhole(true); - marc21Encoder.setReceiver(marc21Decoder); - marc21Decoder.setReceiver(marcXmlEncoder); - } + wrapper + .setReceiver(decoder) + .setReceiver(encoder); + + setEnsureCorrectMarc21Xml(ENSURE_CORRECT_MARC21_XML); } /** @@ -151,52 +131,41 @@ public MarcXmlEncoder() { * @param emitNamespace true if the namespace is emitted, otherwise false */ public void setEmitNamespace(final boolean emitNamespace) { - this.emitNamespace = emitNamespace; - namespacePrefix = new Object[]{emitNamespace ? NAMESPACE_PREFIX : EMPTY}; - if (marcXmlEncoder != null) { - marcXmlEncoder.setEmitNamespace(emitNamespace); - } + encoder.setEmitNamespace(emitNamespace); } /** * Sets the flag to decide whether to omit the XML declaration. + * * Default value: {@value #OMIT_XML_DECLARATION} * * @param currentOmitXmlDeclaration true if the XML declaration is omitted, otherwise * false */ public void omitXmlDeclaration(final boolean currentOmitXmlDeclaration) { - omitXmlDeclaration = currentOmitXmlDeclaration; - if (marcXmlEncoder != null) { - marcXmlEncoder.omitXmlDeclaration(currentOmitXmlDeclaration); - } + encoder.omitXmlDeclaration(currentOmitXmlDeclaration); } /** * Sets the XML version. + * * Default value: {@value #XML_VERSION} * * @param xmlVersion the XML version */ public void setXmlVersion(final String xmlVersion) { - this.xmlVersion = xmlVersion; - this.xmlVersion = xmlVersion; - if (marcXmlEncoder != null) { - marcXmlEncoder.setXmlVersion(xmlVersion); - } + encoder.setXmlVersion(xmlVersion); } /** * Sets the XML encoding. + * * Default value: {@value #XML_ENCODING} * * @param xmlEncoding the XML encoding */ public void setXmlEncoding(final String xmlEncoding) { - this.xmlEncoding = xmlEncoding; - if (marcXmlEncoder != null) { - marcXmlEncoder.setXmlEncoding(xmlEncoding); - } + encoder.setXmlEncoding(xmlEncoding); } /** @@ -210,41 +179,106 @@ public void setXmlEncoding(final String xmlEncoding) { * @param ensureCorrectMarc21Xml if true the input data is validated to ensure correct MARC21. Also the leader may be generated. */ public void setEnsureCorrectMarc21Xml(final boolean ensureCorrectMarc21Xml) { - this.ensureCorrectMarc21Xml = ensureCorrectMarc21Xml; - isInstanced = true; - marcXmlEncoder = new MarcXmlEncoder(); - marc21Decoder.setEmitLeaderAsWhole(true); - - marc21Encoder.setReceiver(marc21Decoder); - marc21Decoder.setReceiver(marcXmlEncoder); + pipe = ensureCorrectMarc21Xml ? wrapper : encoder; } /** * Formats the resulting xml by indentation. Aka "pretty printing". + * * Default value: {@value #PRETTY_PRINTED} * * @param formatted true if formatting is activated, otherwise false */ public void setFormatted(final boolean formatted) { - this.formatted = formatted; - if (marcXmlEncoder != null) { - marcXmlEncoder.setFormatted(formatted); - } + encoder.setFormatted(formatted); } @Override - protected void onSetReceiver() { - if (marcXmlEncoder != null) { - marcXmlEncoder.setReceiver(getReceiver()); - } + public void startRecord(final String identifier) { + pipe.startRecord(identifier); } @Override - public void startRecord(final String identifier) { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marc21Encoder.startRecord(identifier); + public void endRecord() { + pipe.endRecord(); + } + + @Override + public void startEntity(final String name) { + pipe.startEntity(name); + } + + @Override + public void endEntity() { + pipe.endEntity(); + } + + @Override + public void literal(final String name, final String value) { + pipe.literal(name, value); + } + + @Override + protected void onResetStream() { + pipe.resetStream(); + } + + @Override + protected void onCloseStream() { + pipe.closeStream(); + } + + @Override + protected void onSetReceiver() { + encoder.setReceiver(getReceiver()); + } + + private static class Encoder extends DefaultStreamPipe> { + + private final StringBuilder builder = new StringBuilder(); + private final StringBuilder leaderBuilder = new StringBuilder(); + + private boolean atStreamStart = true; + + private boolean omitXmlDeclaration = OMIT_XML_DECLARATION; + private String xmlVersion = XML_VERSION; + private String xmlEncoding = XML_ENCODING; + + private String currentEntity = ""; + + private boolean emitNamespace = true; + private Object[] namespacePrefix = new Object[]{emitNamespace ? NAMESPACE_PREFIX : EMPTY}; + + private int indentationLevel; + private boolean formatted = PRETTY_PRINTED; + private int recordAttributeOffset; + + private Encoder() { + } + + public void setEmitNamespace(final boolean emitNamespace) { + this.emitNamespace = emitNamespace; + namespacePrefix = new Object[]{emitNamespace ? NAMESPACE_PREFIX : EMPTY}; + } + + public void omitXmlDeclaration(final boolean currentOmitXmlDeclaration) { + omitXmlDeclaration = currentOmitXmlDeclaration; + } + + public void setXmlVersion(final String xmlVersion) { + this.xmlVersion = xmlVersion; + } + + public void setXmlEncoding(final String xmlEncoding) { + this.xmlEncoding = xmlEncoding; } - else { + + public void setFormatted(final boolean formatted) { + this.formatted = formatted; + } + + @Override + public void startRecord(final String identifier) { if (atStreamStart) { if (!omitXmlDeclaration) { writeHeader(); @@ -263,15 +297,10 @@ public void startRecord(final String identifier) { incrementIndentationLevel(); } - } - @Override - public void endRecord() { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marc21Encoder.endRecord(); - } - else { - if (builderLeader.length() > 0) { + @Override + public void endRecord() { + if (leaderBuilder.length() > 0) { writeLeader(); } decrementIndentationLevel(); @@ -280,14 +309,9 @@ public void endRecord() { prettyPrintNewLine(); sendAndClearData(); } - } - @Override - public void startEntity(final String name) { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marc21Encoder.startEntity(name); - } - else { + @Override + public void startEntity(final String name) { currentEntity = name; if (!name.equals(Marc21EventNames.LEADER_ENTITY)) { if (name.length() != LEADER_ENTITY_LENGTH) { @@ -305,14 +329,9 @@ public void startEntity(final String name) { incrementIndentationLevel(); } } - } - @Override - public void endEntity() { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marc21Encoder.endEntity(); - } - else { + @Override + public void endEntity() { if (!currentEntity.equals(Marc21EventNames.LEADER_ENTITY)) { decrementIndentationLevel(); prettyPrintIndentation(); @@ -321,14 +340,9 @@ public void endEntity() { } currentEntity = ""; } - } - @Override - public void literal(final String name, final String value) { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marc21Encoder.literal(name, value); - } - else { + @Override + public void literal(final String name, final String value) { if ("".equals(currentEntity)) { if (name.equals(Marc21EventNames.MARCXML_TYPE_LITERAL)) { if (value != null) { @@ -353,126 +367,112 @@ else if (!appendLeader(currentEntity, value)) { prettyPrintNewLine(); } } - } - @Override - protected void onResetStream() { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marc21Encoder.resetStream(); - } - else { + @Override + protected void onResetStream() { if (!atStreamStart) { writeFooter(); } sendAndClearData(); atStreamStart = true; } - } - @Override - protected void onCloseStream() { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marc21Encoder.closeStream(); - } - else { + @Override + protected void onCloseStream() { writeFooter(); sendAndClearData(); } - } - - /** Increments the indentation level by one */ - private void incrementIndentationLevel() { - indentationLevel += 1; - } - - /** Decrements the indentation level by one */ - private void decrementIndentationLevel() { - indentationLevel -= 1; - } - /** Adds a XML Header */ - private void writeHeader() { - writeRaw(String.format(XML_DECLARATION_TEMPLATE, xmlVersion, xmlEncoding)); - } + /** Increments the indentation level by one */ + private void incrementIndentationLevel() { + indentationLevel += 1; + } - /** Closes the root tag */ - private void writeFooter() { - writeTag(Tag.collection::close); - } + /** Decrements the indentation level by one */ + private void decrementIndentationLevel() { + indentationLevel -= 1; + } - /** - * Writes an unescaped sequence. - * - * @param str the unescaped sequence to be written - */ - private void writeRaw(final String str) { - builder.append(str); - } + /** Adds a XML Header */ + private void writeHeader() { + writeRaw(String.format(XML_DECLARATION_TEMPLATE, xmlVersion, xmlEncoding)); + } - /** - * Writes an unescaped sequence to the leader literal. - * - * @param str the unescaped sequence to be written - */ - private void appendLeader(final String str) { - builderLeader.append(str); - } + /** Closes the root tag */ + private void writeFooter() { + writeTag(Tag.collection::close); + } - private boolean appendLeader(final String name, final String value) { - if (name.equals(Marc21EventNames.LEADER_ENTITY)) { - appendLeader(value); - return true; + /** + * Writes an unescaped sequence. + * + * @param str the unescaped sequence to be written + */ + private void writeRaw(final String str) { + builder.append(str); } - else { - return false; + + /** + * Writes an unescaped sequence to the leader literal. + * + * @param str the unescaped sequence to be written + */ + private void appendLeader(final String str) { + leaderBuilder.append(str); } - } - /** - * Writes an escaped sequence. - * - * @param str the unescaped sequence to be written - */ - private void writeEscaped(final String str) { - builder.append(XmlUtil.escape(str, false)); - } + private boolean appendLeader(final String name, final String value) { + if (name.equals(Marc21EventNames.LEADER_ENTITY)) { + appendLeader(value); + return true; + } + else { + return false; + } + } - private void writeLeader() { - prettyPrintIndentation(); - writeTag(Tag.leader::open); - writeRaw(builderLeader.toString()); - writeTag(Tag.leader::close); - prettyPrintNewLine(); - } + /** + * Writes an escaped sequence. + * + * @param str the unescaped sequence to be written + */ + private void writeEscaped(final String str) { + builder.append(XmlUtil.escape(str, false)); + } - private void writeTag(final Function function, final Object... args) { - final Object[] allArgs = Arrays.copyOf(namespacePrefix, namespacePrefix.length + args.length); - System.arraycopy(args, 0, allArgs, namespacePrefix.length, args.length); - writeRaw(function.apply(allArgs)); - } + private void writeLeader() { + prettyPrintIndentation(); + writeTag(Tag.leader::open); + writeRaw(leaderBuilder.toString()); + writeTag(Tag.leader::close); + prettyPrintNewLine(); + } - private void prettyPrintIndentation() { - if (formatted) { - final String prefix = String.join("", Collections.nCopies(indentationLevel, INDENT)); - builder.append(prefix); + private void writeTag(final Function function, final Object... args) { + final Object[] allArgs = Arrays.copyOf(namespacePrefix, namespacePrefix.length + args.length); + System.arraycopy(args, 0, allArgs, namespacePrefix.length, args.length); + writeRaw(function.apply(allArgs)); } - } - private void prettyPrintNewLine() { - if (formatted) { - builder.append(NEW_LINE); + private void prettyPrintIndentation() { + if (formatted) { + final String prefix = String.join("", Collections.nCopies(indentationLevel, INDENT)); + builder.append(prefix); + } } - } - private void sendAndClearData() { - if (ensureCorrectMarc21Xml && marcXmlEncoder != null) { - marcXmlEncoder.sendAndClearData(); + private void prettyPrintNewLine() { + if (formatted) { + builder.append(NEW_LINE); + } } - else { + + private void sendAndClearData() { getReceiver().process(builder.toString()); builder.delete(0, builder.length()); recordAttributeOffset = 0; } + } } 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 fa258cf29..7d7b97364 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 @@ -16,24 +16,17 @@ package org.metafacture.biblio.marc21; -import org.junit.After; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import org.junit.Before; -import org.junit.Test; -import static org.metafacture.biblio.marc21.Marc21EventNames.BIBLIOGRAPHIC_LEVEL_LITERAL; -import static org.metafacture.biblio.marc21.Marc21EventNames.CATALOGING_FORM_LITERAL; -import static org.metafacture.biblio.marc21.Marc21EventNames.CHARACTER_CODING_LITERAL; -import static org.metafacture.biblio.marc21.Marc21EventNames.ENCODING_LEVEL_LITERAL; -import static org.metafacture.biblio.marc21.Marc21EventNames.MULTIPART_LEVEL_LITERAL; -import static org.metafacture.biblio.marc21.Marc21EventNames.RECORD_STATUS_LITERAL; -import static org.metafacture.biblio.marc21.Marc21EventNames.RECORD_TYPE_LITERAL; -import static org.metafacture.biblio.marc21.Marc21EventNames.TYPE_OF_CONTROL_LITERAL; import org.metafacture.framework.FormatException; import org.metafacture.framework.MetafactureException; import org.metafacture.framework.MissingIdException; import org.metafacture.framework.helpers.DefaultObjectReceiver; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + /** * Tests for class {@link MarcXmlEncoder}. * @@ -56,34 +49,24 @@ public class MarcXmlEncoderTest { private static final String XML_MARC_COLLECTION_END_TAG = ""; private static final String RECORD_ID = "92005291"; - private static StringBuilder resultCollector; - private final MarcXmlEncoder encoder = new MarcXmlEncoder(); - private final MarcXmlEncoder encoder_ensureCorrectMarc21Xml = new MarcXmlEncoder(); - + private static StringBuilder resultCollector = new StringBuilder(); + private static MarcXmlEncoder encoder; @Before public void setUp() { - initializeEncoder(encoder); - encoder_ensureCorrectMarc21Xml.setEnsureCorrectMarc21Xml(true); - initializeEncoder(encoder_ensureCorrectMarc21Xml); - } - - private void initializeEncoder(MarcXmlEncoder enc) { - enc.setFormatted(false); - enc.setReceiver(new DefaultObjectReceiver() { + encoder = new MarcXmlEncoder(); + encoder.setFormatted(false); + encoder.setReceiver(new DefaultObjectReceiver() { @Override public void process(final String obj) { resultCollector.append(obj); } }); - resultCollector = new StringBuilder(); - } - @After - public void tearDown() { + resultCollector.delete(0, resultCollector.length()); } - private void addOneRecord(MarcXmlEncoder encoder) { + private void addOneRecord(final MarcXmlEncoder encoder) { encoder.startRecord(RECORD_ID); encoder.literal("001", RECORD_ID); encoder.startEntity("010 "); @@ -209,17 +192,17 @@ public void createAnRecordWithLeader(){ @Test(expected = FormatException.class) public void createAnRecordWithLeader_ensureCorrectMarc21Xml() { - encoder_ensureCorrectMarc21Xml.setEnsureCorrectMarc21Xml(true); - createAnRecordWithLeader(encoder_ensureCorrectMarc21Xml); + encoder.setEnsureCorrectMarc21Xml(true); + createAnRecordWithLeader(encoder); } - private void createAnRecordWithLeader(MarcXmlEncoder enc) { - enc.startRecord("1"); - enc.startEntity(Marc21EventNames.LEADER_ENTITY); - enc.literal(Marc21EventNames.LEADER_ENTITY, "dummy"); - enc.endEntity(); - enc.endRecord(); - enc.closeStream(); + private void createAnRecordWithLeader(final MarcXmlEncoder encoder) { + encoder.startRecord("1"); + encoder.startEntity(Marc21EventNames.LEADER_ENTITY); + encoder.literal(Marc21EventNames.LEADER_ENTITY, "dummy"); + encoder.endEntity(); + encoder.endRecord(); + encoder.closeStream(); String expected = XML_DECLARATION + XML_ROOT_OPEN + "dummy" + XML_MARC_COLLECTION_END_TAG; String actual = resultCollector.toString(); @@ -239,13 +222,14 @@ public void issue336_createRecordWithTopLevelLeader() { } @Test - public void issue336_createRecordWithTopLevelLeader_ensureCorrectMarc21Xml() { - createRecordWithTopLevelLeader(encoder_ensureCorrectMarc21Xml, "00048naa a2200037uc 4500"); + public void issue336_createRecordWithTopLevelLeader_defaultMarc21Xml() { + createRecordWithTopLevelLeader(encoder, "00000naa a2200000uc 4500"); } @Test - public void issue336_createRecordWithTopLevelLeader_defaultMarc21Xml() { - createRecordWithTopLevelLeader(encoder,"00000naa a2200000uc 4500"); + public void issue336_createRecordWithTopLevelLeader_ensureCorrectMarc21Xml() { + encoder.setEnsureCorrectMarc21Xml(true); + createRecordWithTopLevelLeader(encoder, "00048naa a2200037uc 4500"); } private void createRecordWithTopLevelLeader(final MarcXmlEncoder encoder, final String expectedLeader) { @@ -261,12 +245,6 @@ private void createRecordWithTopLevelLeader(final MarcXmlEncoder encoder, final assertEquals(expected, actual); } - @Test(expected = NullPointerException.class) - public void ensureCorrectMarc21XmlParameterAfterSettingReceiver() { - encoder_ensureCorrectMarc21Xml.setEnsureCorrectMarc21Xml(true); - createRecordWithTopLevelLeader(encoder_ensureCorrectMarc21Xml,"ignored"); - } - @Test public void issue527ShouldEmitLeaderAlwaysAsWholeString() { issue527ShouldEmitLeaderAlwaysAsWholeString(encoder); @@ -274,21 +252,21 @@ public void issue527ShouldEmitLeaderAlwaysAsWholeString() { @Test(expected = MissingIdException.class) public void issue527ShouldEmitLeaderAlwaysAsWholeString_ensureCorrectMarc21Xml() { - encoder_ensureCorrectMarc21Xml.setEnsureCorrectMarc21Xml(true); - this.issue527ShouldEmitLeaderAlwaysAsWholeString(encoder_ensureCorrectMarc21Xml); + encoder.setEnsureCorrectMarc21Xml(true); + issue527ShouldEmitLeaderAlwaysAsWholeString(encoder); } private void issue527ShouldEmitLeaderAlwaysAsWholeString(MarcXmlEncoder encoder) { encoder.startRecord("1"); encoder.startEntity(Marc21EventNames.LEADER_ENTITY); - encoder.literal(RECORD_STATUS_LITERAL, "a"); - encoder.literal(RECORD_TYPE_LITERAL, "o"); - encoder.literal(BIBLIOGRAPHIC_LEVEL_LITERAL, "a"); - encoder.literal(TYPE_OF_CONTROL_LITERAL, " "); - encoder.literal(CHARACTER_CODING_LITERAL, "a"); - encoder.literal(ENCODING_LEVEL_LITERAL, "z"); - encoder.literal(CATALOGING_FORM_LITERAL, "u"); - encoder.literal(MULTIPART_LEVEL_LITERAL, " "); + encoder.literal(Marc21EventNames.RECORD_STATUS_LITERAL, "a"); + encoder.literal(Marc21EventNames.RECORD_TYPE_LITERAL, "o"); + encoder.literal(Marc21EventNames.BIBLIOGRAPHIC_LEVEL_LITERAL, "a"); + encoder.literal(Marc21EventNames.TYPE_OF_CONTROL_LITERAL, " "); + encoder.literal(Marc21EventNames.CHARACTER_CODING_LITERAL, "a"); + encoder.literal(Marc21EventNames.ENCODING_LEVEL_LITERAL, "z"); + encoder.literal(Marc21EventNames.CATALOGING_FORM_LITERAL, "u"); + encoder.literal(Marc21EventNames.MULTIPART_LEVEL_LITERAL, " "); encoder.endEntity(); encoder.endRecord(); encoder.closeStream(); @@ -298,8 +276,6 @@ private void issue527ShouldEmitLeaderAlwaysAsWholeString(MarcXmlEncoder encoder) assertEquals(expected, actual); } - - @Test public void sendDataAndClearWhenRecordStartedAndStreamResets() { encoder.startRecord("1");