From 0d07192add58870af73daf8cc0d6e8f3666269ef Mon Sep 17 00:00:00 2001 From: TobiasNx Date: Fri, 10 Apr 2026 17:31:31 +0200 Subject: [PATCH 1/7] Add true picaxml handler #530 Rename old PicaXmlHandler to PpXmlHandler. Then handler names follow the naming convention as stated here: https://format.gbv.de/pica --- .../biblio/pica/PicaXmlHandler.java | 104 +++++++++++++----- .../metafacture/biblio/pica/PpXmlHandler.java | 100 +++++++++++++++++ .../main/resources/flux-commands.properties | 1 + 3 files changed, 178 insertions(+), 27 deletions(-) create mode 100644 metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PpXmlHandler.java diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java index 4435aa4fa..ad34adaa9 100644 --- a/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java +++ b/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 hbz, Pascal Christoph + * Copyright 2013, 2014 Deutsche Nationalbibliothek * * Licensed under the Apache License, Version 2.0 the "License"; * you may not use this file except in compliance with the License. @@ -29,26 +29,30 @@ import java.text.Normalizer; + /** - * A pica xml handler. - * - * @author Pascal Christoph (dr0i) + * A Pica xml reader. To read marc data without namespace specification set option `namespace=""` or to null when using JAVA code. + * @author Tobias Bülte * */ -@Description("A pica xml reader") +@Description("A Pica XML reader. To read pica data without namespace specification set option `namespace=\"\"`. To ignore namespace specification set option `ignorenamespace=\"true\".") @In(XmlReceiver.class) @Out(StreamReceiver.class) @FluxCommand("handle-picaxml") public final class PicaXmlHandler extends DefaultXmlPipe { - private static final String SUBFIELD = "subf"; - private static final String DATAFIELD = "tag"; + public static final String NAMESPACE = "info:srw/schema/5/picaXML-v1.0"; + + private static final String SUBFIELD = "subfield"; + private static final String DATAFIELD = "datafield"; + private static final String CONTROLFIELD = "controlfield"; private static final String RECORD = "record"; - private static final String NAMESPACE = - "http://www.oclcpica.org/xmlns/ppxml-1.0"; - private static final String LEADER = "global"; + + private String attributeMarker = DEFAULT_ATTRIBUTE_MARKER; private String currentTag = ""; + private String namespace = NAMESPACE; private StringBuilder builder = new StringBuilder(); + private boolean ignoreNamespace; /** * Creates an instance of {@link PicaXmlHandler}. @@ -56,44 +60,90 @@ public final class PicaXmlHandler extends DefaultXmlPipe { public PicaXmlHandler() { } + /** + * Sets the namespace. + * + * Default value: {@value #NAMESPACE} + * + * @param namespace the namespace. Set to null if namespace shouldn't be checked. Set to empty string + * if the namespace is missing in the data. + */ + public void setNamespace(final String namespace) { + this.namespace = namespace; + } + + /** + * Sets whether to ignore the namespace. + * + * Default value: false + * + * @param ignoreNamespace true if the namespace should be ignored + */ + public void setIgnoreNamespace(final boolean ignoreNamespace) { + this.ignoreNamespace = ignoreNamespace; + } + + private boolean checkNamespace(final String uri) { + return namespace == null || ignoreNamespace || namespace.equals(uri); + } + + /** + * Sets the attribute marker. + * + * Default value: + * {@value org.metafacture.framework.helpers.DefaultXmlPipe#DEFAULT_ATTRIBUTE_MARKER} + * + * @param attributeMarker the attribute marker + */ + public void setAttributeMarker(final String attributeMarker) { + this.attributeMarker = attributeMarker; + } + + /** + * Gets the attribute marker. + * + * @return the attribute marker + */ + public String getAttributeMarker() { + return attributeMarker; + } + @Override - public void startElement(final String uri, final String localName, - final String qName, final Attributes attributes) throws SAXException { + public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) throws SAXException { if (SUBFIELD.equals(localName)) { builder = new StringBuilder(); - currentTag = attributes.getValue("id"); + currentTag = attributes.getValue("code"); } else if (DATAFIELD.equals(localName)) { - getReceiver().startEntity( - attributes.getValue("id") + attributes.getValue("occ")); - } - else if (RECORD.equals(localName) && NAMESPACE.equals(uri)) { - getReceiver().startRecord(""); + getReceiver().startEntity(attributes.getValue("tag")); } - else if (LEADER.equals(localName)) { + else if (CONTROLFIELD.equals(localName)) { builder = new StringBuilder(); - currentTag = LEADER; + currentTag = attributes.getValue("tag"); + } + else if (RECORD.equals(localName) && checkNamespace(uri)) { + getReceiver().startRecord(""); } } @Override - public void endElement(final String uri, final String localName, - final String qName) throws SAXException { + public void endElement(final String uri, final String localName, final String qName) throws SAXException { if (SUBFIELD.equals(localName)) { - getReceiver().literal(currentTag, - Normalizer.normalize(builder.toString().trim(), Normalizer.Form.NFC)); + getReceiver().literal(currentTag, Normalizer.normalize(builder.toString().trim(), Normalizer.Form.NFC)); } else if (DATAFIELD.equals(localName)) { getReceiver().endEntity(); } - else if (RECORD.equals(localName) && NAMESPACE.equals(uri)) { + else if (CONTROLFIELD.equals(localName)) { + getReceiver().literal(currentTag, builder.toString()); + } + else if (RECORD.equals(localName) && checkNamespace(uri)) { getReceiver().endRecord(); } } @Override - public void characters(final char[] chars, final int start, final int length) - throws SAXException { + public void characters(final char[] chars, final int start, final int length) throws SAXException { builder.append(chars, start, length); } diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PpXmlHandler.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PpXmlHandler.java new file mode 100644 index 000000000..491150f45 --- /dev/null +++ b/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PpXmlHandler.java @@ -0,0 +1,100 @@ +/* + * Copyright 2014 hbz, Pascal Christoph + * + * 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.pica; + +import org.metafacture.framework.FluxCommand; +import org.metafacture.framework.StreamReceiver; +import org.metafacture.framework.XmlReceiver; +import org.metafacture.framework.annotations.Description; +import org.metafacture.framework.annotations.In; +import org.metafacture.framework.annotations.Out; +import org.metafacture.framework.helpers.DefaultXmlPipe; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import java.text.Normalizer; + +/** + * A pica ppxml handler. + * + * @author Pascal Christoph (dr0i) + * + */ +@Description("A pica ppxml reader") +@In(XmlReceiver.class) +@Out(StreamReceiver.class) +@FluxCommand("handle-ppxml") +public final class PpXmlHandler extends DefaultXmlPipe { + + private static final String SUBFIELD = "subf"; + private static final String DATAFIELD = "tag"; + private static final String RECORD = "record"; + private static final String NAMESPACE = + "http://www.oclcpica.org/xmlns/ppxml-1.0"; + private static final String LEADER = "global"; + private String currentTag = ""; + private StringBuilder builder = new StringBuilder(); + + /** + * Creates an instance of {@link PpXmlHandler}. + */ + public PpXmlHandler() { + } + + @Override + public void startElement(final String uri, final String localName, + final String qName, final Attributes attributes) throws SAXException { + if (SUBFIELD.equals(localName)) { + builder = new StringBuilder(); + currentTag = attributes.getValue("id"); + } + else if (DATAFIELD.equals(localName)) { + getReceiver().startEntity( + attributes.getValue("id") + attributes.getValue("occ")); + } + else if (RECORD.equals(localName) && NAMESPACE.equals(uri)) { + getReceiver().startRecord(""); + } + else if (LEADER.equals(localName)) { + builder = new StringBuilder(); + currentTag = LEADER; + } + } + + @Override + public void endElement(final String uri, final String localName, + final String qName) throws SAXException { + if (SUBFIELD.equals(localName)) { + getReceiver().literal(currentTag, + Normalizer.normalize(builder.toString().trim(), Normalizer.Form.NFC)); + } + else if (DATAFIELD.equals(localName)) { + getReceiver().endEntity(); + } + else if (RECORD.equals(localName) && NAMESPACE.equals(uri)) { + getReceiver().endRecord(); + } + } + + @Override + public void characters(final char[] chars, final int start, final int length) + throws SAXException { + builder.append(chars, start, length); + } + +} diff --git a/metafacture-biblio/src/main/resources/flux-commands.properties b/metafacture-biblio/src/main/resources/flux-commands.properties index 6a4b86b84..dd840652c 100644 --- a/metafacture-biblio/src/main/resources/flux-commands.properties +++ b/metafacture-biblio/src/main/resources/flux-commands.properties @@ -22,6 +22,7 @@ decode-pica org.metafacture.biblio.pica.PicaDecoder encode-pica org.metafacture.biblio.pica.PicaEncoder remodel-pica-multiscript org.metafacture.biblio.pica.PicaMultiscriptRemodeler handle-picaxml org.metafacture.biblio.pica.PicaXmlHandler +handle-ppxml org.metafacture.biblio.pica.PpXmlHandler handle-mabxml org.metafacture.biblio.AlephMabXmlHandler handle-comarcxml org.metafacture.biblio.ComarcXmlHandler From beaab85b3f3634ff3f4311b29532d4b3fa72ee84 Mon Sep 17 00:00:00 2001 From: TobiasNx Date: Wed, 15 Apr 2026 18:16:37 +0200 Subject: [PATCH 2/7] Remove controlfields and add occurence to fieldname #530 --- .../org/metafacture/biblio/pica/PicaXmlHandler.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java index ad34adaa9..445935805 100644 --- a/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java +++ b/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java @@ -45,7 +45,6 @@ public final class PicaXmlHandler extends DefaultXmlPipe { private static final String SUBFIELD = "subfield"; private static final String DATAFIELD = "datafield"; - private static final String CONTROLFIELD = "controlfield"; private static final String RECORD = "record"; private String attributeMarker = DEFAULT_ATTRIBUTE_MARKER; @@ -115,11 +114,7 @@ public void startElement(final String uri, final String localName, final String currentTag = attributes.getValue("code"); } else if (DATAFIELD.equals(localName)) { - getReceiver().startEntity(attributes.getValue("tag")); - } - else if (CONTROLFIELD.equals(localName)) { - builder = new StringBuilder(); - currentTag = attributes.getValue("tag"); + getReceiver().startEntity( attributes.getValue("tag") + attributes.getValue("occurrence")); } else if (RECORD.equals(localName) && checkNamespace(uri)) { getReceiver().startRecord(""); @@ -134,9 +129,6 @@ public void endElement(final String uri, final String localName, final String qN else if (DATAFIELD.equals(localName)) { getReceiver().endEntity(); } - else if (CONTROLFIELD.equals(localName)) { - getReceiver().literal(currentTag, builder.toString()); - } else if (RECORD.equals(localName) && checkNamespace(uri)) { getReceiver().endRecord(); } From 563f8b096f845207e01378c641acd78493df0a39 Mon Sep 17 00:00:00 2001 From: TobiasNx Date: Wed, 15 Apr 2026 18:16:47 +0200 Subject: [PATCH 3/7] Add tests #530 --- .../biblio/pica/PicaXmlHandlerTest.java | 289 ++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PicaXmlHandlerTest.java diff --git a/metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PicaXmlHandlerTest.java b/metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PicaXmlHandlerTest.java new file mode 100644 index 000000000..d3b037e0a --- /dev/null +++ b/metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PicaXmlHandlerTest.java @@ -0,0 +1,289 @@ +/* + * Copyright 2014 Deutsche Nationalbibliothek + * + * 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.pica; + +import org.metafacture.framework.StreamReceiver; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +/** + * Tests for class {@link PicaXmlHandler}. + * + * @author Tobias Bülte + * + */ +public final class PicaXmlHandlerTest { + + private static final String NAMESPACE = "info:srw/schema/5/picaXML-v1.0"; + private static final String RECORD = "record"; + private static final String DATAFIELD = "datafield"; + private static final String SUBFIELD = "subfield"; + + private PicaXmlHandler picaXmlHandler; + + @Mock + private StreamReceiver receiver; + + public PicaXmlHandlerTest() { + } + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + picaXmlHandler = new PicaXmlHandler(); + picaXmlHandler.setReceiver(receiver); + } + + @After + public void cleanup() { + picaXmlHandler.closeStream(); + } + + @Test + public void shouldLabelDataFieldWithAndWithoutOccuence() + throws SAXException { + final AttributesImpl attributes = new AttributesImpl(); + + final String fieldValue1 = "1234"; + final String fieldValue2 = "utf-8"; + + picaXmlHandler.startElement(NAMESPACE, RECORD, "", attributes); + attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); + picaXmlHandler.startElement(null, DATAFIELD, "", attributes); + attributes.clear(); + attributes.addAttribute(null, "code", "code", "CDATA", "0"); + picaXmlHandler.startElement(null, SUBFIELD, "", attributes); + picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); + picaXmlHandler.endElement(null, SUBFIELD, ""); + picaXmlHandler.endElement(null, DATAFIELD, ""); + attributes.addAttribute(null, "tag", "tag", "CDATA", "201U"); + attributes.addAttribute(null, "occurence", "occurence", "CDATA", "01"); + picaXmlHandler.startElement(null, DATAFIELD, "", attributes); + attributes.clear(); + attributes.addAttribute(null, "code", "code", "CDATA", "0"); + picaXmlHandler.startElement(null, SUBFIELD, "", attributes); + picaXmlHandler.characters(fieldValue2.toCharArray(), 0, fieldValue2.length()); + picaXmlHandler.endElement(null, SUBFIELD, ""); + picaXmlHandler.endElement(null, DATAFIELD, ""); + picaXmlHandler.endElement(NAMESPACE, RECORD, ""); + + + final InOrder ordered = Mockito.inOrder(receiver); + ordered.verify(receiver).startRecord(""); + ordered.verify(receiver).startEntity("003@"); + ordered.verify(receiver).literal("0", fieldValue1); + ordered.verify(receiver).endEntity(); + ordered.verify(receiver).startEntity("201U01"); + ordered.verify(receiver).literal("0", fieldValue2); + ordered.verify(receiver).endEntity(); + ordered.verify(receiver).endRecord(); + ordered.verifyNoMoreInteractions(); + } + + @Test + public void shouldRecognizeRecordsWithNamespace() + throws SAXException { + final AttributesImpl attributes = new AttributesImpl(); + + final String fieldValue1 = "1234"; + + picaXmlHandler.startElement(NAMESPACE, RECORD, "", attributes); + attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); + picaXmlHandler.startElement(null, DATAFIELD, "", attributes); + attributes.clear(); + attributes.addAttribute(null, "code", "code", "CDATA", "0"); + picaXmlHandler.startElement(null, SUBFIELD, "", attributes); + picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); + picaXmlHandler.endElement(null, SUBFIELD, ""); + picaXmlHandler.endElement(null, DATAFIELD, ""); + + final InOrder ordered = Mockito.inOrder(receiver); + ordered.verify(receiver).startRecord(""); + ordered.verify(receiver).startEntity("003@"); + ordered.verify(receiver).literal("0", fieldValue1); + ordered.verify(receiver).endEntity(); + ordered.verify(receiver).endRecord(); + ordered.verifyNoMoreInteractions(); + } + + @Test + public void shouldNotRecognizeRecordsWithoutNamespace() + throws SAXException { + final AttributesImpl attributes = new AttributesImpl(); + + final String fieldValue1 = "1234"; + + picaXmlHandler.startElement(null, RECORD, "", attributes); + attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); + picaXmlHandler.startElement(null, DATAFIELD, "", attributes); + attributes.clear(); + attributes.addAttribute(null, "code", "code", "CDATA", "0"); + picaXmlHandler.startElement(null, SUBFIELD, "", attributes); + picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); + picaXmlHandler.endElement(null, SUBFIELD, ""); + picaXmlHandler.endElement(null, DATAFIELD, ""); + picaXmlHandler.endElement(null, RECORD, ""); + + Mockito.verifyNoMoreInteractions(receiver); + } + + @Test + public void shouldRecognizeRecordsWithoutNamespace() + throws SAXException { + final AttributesImpl attributes = new AttributesImpl(); + + final String fieldValue1 = "1234"; + + picaXmlHandler.setNamespace(""); + picaXmlHandler.startElement(null, RECORD, "", attributes); + attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); + picaXmlHandler.startElement(null, DATAFIELD, "", attributes); + attributes.clear(); + attributes.addAttribute(null, "code", "code", "CDATA", "0"); + picaXmlHandler.startElement(null, SUBFIELD, "", attributes); + picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); + picaXmlHandler.endElement(null, SUBFIELD, ""); + picaXmlHandler.endElement(null, DATAFIELD, ""); + picaXmlHandler.endElement(null, RECORD, ""); + + final InOrder ordered = Mockito.inOrder(receiver); + ordered.verify(receiver).startRecord(""); + ordered.verify(receiver).startEntity("003@"); + ordered.verify(receiver).literal("0", fieldValue1); + ordered.verify(receiver).endEntity(); + ordered.verify(receiver).endRecord(); + ordered.verifyNoMoreInteractions(); + } + + @Test + public void shouldNotRecognizeRecordsWithNamespaceWhenOptionallyWithoutNamespace() + throws SAXException { + final AttributesImpl attributes = new AttributesImpl(); + + final String fieldValue1 = "1234"; + + picaXmlHandler.setNamespace(""); + picaXmlHandler.startElement(NAMESPACE, RECORD, "", attributes); + attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); + picaXmlHandler.startElement(null, DATAFIELD, "", attributes); + attributes.clear(); + attributes.addAttribute(null, "code", "code", "CDATA", "0"); + picaXmlHandler.startElement(null, SUBFIELD, "", attributes); + picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); + picaXmlHandler.endElement(null, SUBFIELD, ""); + picaXmlHandler.endElement(null, DATAFIELD, ""); + picaXmlHandler.endElement(null, RECORD, ""); + + final InOrder ordered = Mockito.inOrder(receiver); + ordered.verify(receiver).startRecord(""); + ordered.verify(receiver).startEntity("003@"); + ordered.verify(receiver).literal("0", fieldValue1); + ordered.verify(receiver).endEntity(); + ordered.verify(receiver).endRecord(); + ordered.verifyNoMoreInteractions(); + } + + @Test + public void issue569ShouldRecognizeRecordsWithAndWithoutNamespace() + throws SAXException { + final AttributesImpl attributes = new AttributesImpl(); + + final String fieldValue1 = "1234"; + + picaXmlHandler.setIgnoreNamespace(true); + picaXmlHandler.startElement(NAMESPACE, RECORD, "", attributes); + attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); + picaXmlHandler.startElement(null, DATAFIELD, "", attributes); + attributes.clear(); + attributes.addAttribute(null, "code", "code", "CDATA", "0"); + picaXmlHandler.startElement(null, SUBFIELD, "", attributes); + picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); + picaXmlHandler.endElement(null, SUBFIELD, ""); + picaXmlHandler.endElement(null, DATAFIELD, ""); + picaXmlHandler.endElement(null, RECORD, ""); + + final InOrder ordered = Mockito.inOrder(receiver); + ordered.verify(receiver).startRecord(""); + ordered.verify(receiver).startEntity("003@"); + ordered.verify(receiver).literal("0", fieldValue1); + ordered.verify(receiver).endEntity(); + ordered.verify(receiver).endRecord(); + ordered.verifyNoMoreInteractions(); + } + + @Test + public void issue569ShouldRecognizeRecordsWithAndWithoutNamespaceOrderIndependently() + throws SAXException { + final AttributesImpl attributes = new AttributesImpl(); + + final String fieldValue1 = "1234"; + + picaXmlHandler.setIgnoreNamespace(true); + picaXmlHandler.setNamespace(""); + picaXmlHandler.startElement(null, RECORD, "", attributes); + attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); + picaXmlHandler.startElement(null, DATAFIELD, "", attributes); + attributes.clear(); + attributes.addAttribute(null, "code", "code", "CDATA", "0"); + picaXmlHandler.startElement(null, SUBFIELD, "", attributes); + picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); + picaXmlHandler.endElement(null, SUBFIELD, ""); + picaXmlHandler.endElement(null, DATAFIELD, ""); + picaXmlHandler.endElement(NAMESPACE, RECORD, ""); + + final InOrder ordered = Mockito.inOrder(receiver); + ordered.verify(receiver).startRecord(""); + ordered.verify(receiver).startEntity("003@"); + ordered.verify(receiver).literal("0", fieldValue1); + ordered.verify(receiver).endEntity(); + ordered.verify(receiver).endRecord(); + ordered.verifyNoMoreInteractions(); + } + + @Test + public void issue569ShouldNotRecognizeRecordsWithAndWithoutNamespace() + throws SAXException { + final AttributesImpl attributes = new AttributesImpl(); + + final String fieldValue1 = "1234"; + + picaXmlHandler.setIgnoreNamespace(false); + picaXmlHandler.startElement(null, RECORD, "", attributes); + attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); + picaXmlHandler.startElement(null, DATAFIELD, "", attributes); + attributes.clear(); + attributes.addAttribute(null, "code", "code", "CDATA", "0"); + picaXmlHandler.startElement(null, SUBFIELD, "", attributes); + picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); + picaXmlHandler.endElement(null, SUBFIELD, ""); + picaXmlHandler.endElement(null, DATAFIELD, ""); + picaXmlHandler.endElement(NAMESPACE, RECORD, ""); + + Mockito.verify(receiver).endRecord(); + + Mockito.verifyNoMoreInteractions(receiver); + } + +} From 54ada1250371c485bcf6346021cfa92401ce7d58 Mon Sep 17 00:00:00 2001 From: TobiasNx Date: Thu, 16 Apr 2026 16:33:39 +0200 Subject: [PATCH 4/7] Add tests for picaXmlHandler #530 --- .../biblio/pica/PicaXmlHandlerTest.java | 159 +++++------------- 1 file changed, 44 insertions(+), 115 deletions(-) diff --git a/metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PicaXmlHandlerTest.java b/metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PicaXmlHandlerTest.java index d3b037e0a..2564729b5 100644 --- a/metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PicaXmlHandlerTest.java +++ b/metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PicaXmlHandlerTest.java @@ -62,12 +62,11 @@ public void cleanup() { } @Test - public void shouldLabelDataFieldWithAndWithoutOccuence() + public void shouldLabelDataFieldWithoutOccurrenceAttribute() throws SAXException { final AttributesImpl attributes = new AttributesImpl(); - final String fieldValue1 = "1234"; - final String fieldValue2 = "utf-8"; + final String fieldValue = "1234"; picaXmlHandler.startElement(NAMESPACE, RECORD, "", attributes); attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); @@ -75,75 +74,68 @@ public void shouldLabelDataFieldWithAndWithoutOccuence() attributes.clear(); attributes.addAttribute(null, "code", "code", "CDATA", "0"); picaXmlHandler.startElement(null, SUBFIELD, "", attributes); - picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); - picaXmlHandler.endElement(null, SUBFIELD, ""); - picaXmlHandler.endElement(null, DATAFIELD, ""); - attributes.addAttribute(null, "tag", "tag", "CDATA", "201U"); - attributes.addAttribute(null, "occurence", "occurence", "CDATA", "01"); - picaXmlHandler.startElement(null, DATAFIELD, "", attributes); - attributes.clear(); - attributes.addAttribute(null, "code", "code", "CDATA", "0"); - picaXmlHandler.startElement(null, SUBFIELD, "", attributes); - picaXmlHandler.characters(fieldValue2.toCharArray(), 0, fieldValue2.length()); + picaXmlHandler.characters(fieldValue.toCharArray(), 0, fieldValue.length()); picaXmlHandler.endElement(null, SUBFIELD, ""); picaXmlHandler.endElement(null, DATAFIELD, ""); picaXmlHandler.endElement(NAMESPACE, RECORD, ""); - final InOrder ordered = Mockito.inOrder(receiver); ordered.verify(receiver).startRecord(""); ordered.verify(receiver).startEntity("003@"); - ordered.verify(receiver).literal("0", fieldValue1); - ordered.verify(receiver).endEntity(); - ordered.verify(receiver).startEntity("201U01"); - ordered.verify(receiver).literal("0", fieldValue2); + ordered.verify(receiver).literal("0", fieldValue); ordered.verify(receiver).endEntity(); ordered.verify(receiver).endRecord(); ordered.verifyNoMoreInteractions(); } @Test - public void shouldRecognizeRecordsWithNamespace() + public void shouldLabelDataFieldWithOccurrenceAttribute() throws SAXException { final AttributesImpl attributes = new AttributesImpl(); - final String fieldValue1 = "1234"; + final String fieldValue = "utf-8"; picaXmlHandler.startElement(NAMESPACE, RECORD, "", attributes); - attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); + attributes.addAttribute(null, "tag", "tag", "CDATA", "201U"); + attributes.addAttribute(null, "occurrence", "occurrence", "CDATA", "01"); picaXmlHandler.startElement(null, DATAFIELD, "", attributes); attributes.clear(); attributes.addAttribute(null, "code", "code", "CDATA", "0"); picaXmlHandler.startElement(null, SUBFIELD, "", attributes); - picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); + picaXmlHandler.characters(fieldValue.toCharArray(), 0, fieldValue.length()); picaXmlHandler.endElement(null, SUBFIELD, ""); picaXmlHandler.endElement(null, DATAFIELD, ""); + picaXmlHandler.endElement(NAMESPACE, RECORD, ""); final InOrder ordered = Mockito.inOrder(receiver); ordered.verify(receiver).startRecord(""); - ordered.verify(receiver).startEntity("003@"); - ordered.verify(receiver).literal("0", fieldValue1); + ordered.verify(receiver).startEntity("201U01"); + ordered.verify(receiver).literal("0", fieldValue); ordered.verify(receiver).endEntity(); ordered.verify(receiver).endRecord(); ordered.verifyNoMoreInteractions(); } @Test - public void shouldNotRecognizeRecordsWithoutNamespace() + public void shouldRecognizeRecordsWithNamespace() throws SAXException { final AttributesImpl attributes = new AttributesImpl(); - final String fieldValue1 = "1234"; + picaXmlHandler.startElement(NAMESPACE, RECORD, "", attributes); + picaXmlHandler.endElement(NAMESPACE, RECORD, ""); + + Mockito.verify(receiver).startRecord(""); + Mockito.verify(receiver).endRecord(); + + Mockito.verifyNoMoreInteractions(receiver); + } + + @Test + public void shouldNotRecognizeRecordsWithoutNamespace() + throws SAXException { + final AttributesImpl attributes = new AttributesImpl(); picaXmlHandler.startElement(null, RECORD, "", attributes); - attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); - picaXmlHandler.startElement(null, DATAFIELD, "", attributes); - attributes.clear(); - attributes.addAttribute(null, "code", "code", "CDATA", "0"); - picaXmlHandler.startElement(null, SUBFIELD, "", attributes); - picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); - picaXmlHandler.endElement(null, SUBFIELD, ""); - picaXmlHandler.endElement(null, DATAFIELD, ""); picaXmlHandler.endElement(null, RECORD, ""); Mockito.verifyNoMoreInteractions(receiver); @@ -154,27 +146,14 @@ public void shouldRecognizeRecordsWithoutNamespace() throws SAXException { final AttributesImpl attributes = new AttributesImpl(); - final String fieldValue1 = "1234"; - picaXmlHandler.setNamespace(""); - picaXmlHandler.startElement(null, RECORD, "", attributes); - attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); - picaXmlHandler.startElement(null, DATAFIELD, "", attributes); - attributes.clear(); - attributes.addAttribute(null, "code", "code", "CDATA", "0"); - picaXmlHandler.startElement(null, SUBFIELD, "", attributes); - picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); - picaXmlHandler.endElement(null, SUBFIELD, ""); - picaXmlHandler.endElement(null, DATAFIELD, ""); - picaXmlHandler.endElement(null, RECORD, ""); + picaXmlHandler.startElement("", RECORD, "", attributes); + picaXmlHandler.endElement("", RECORD, ""); - final InOrder ordered = Mockito.inOrder(receiver); - ordered.verify(receiver).startRecord(""); - ordered.verify(receiver).startEntity("003@"); - ordered.verify(receiver).literal("0", fieldValue1); - ordered.verify(receiver).endEntity(); - ordered.verify(receiver).endRecord(); - ordered.verifyNoMoreInteractions(); + Mockito.verify(receiver).startRecord(""); + Mockito.verify(receiver).endRecord(); + + Mockito.verifyNoMoreInteractions(receiver); } @Test @@ -182,27 +161,11 @@ public void shouldNotRecognizeRecordsWithNamespaceWhenOptionallyWithoutNamespace throws SAXException { final AttributesImpl attributes = new AttributesImpl(); - final String fieldValue1 = "1234"; - picaXmlHandler.setNamespace(""); picaXmlHandler.startElement(NAMESPACE, RECORD, "", attributes); - attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); - picaXmlHandler.startElement(null, DATAFIELD, "", attributes); - attributes.clear(); - attributes.addAttribute(null, "code", "code", "CDATA", "0"); - picaXmlHandler.startElement(null, SUBFIELD, "", attributes); - picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); - picaXmlHandler.endElement(null, SUBFIELD, ""); - picaXmlHandler.endElement(null, DATAFIELD, ""); - picaXmlHandler.endElement(null, RECORD, ""); + picaXmlHandler.endElement(NAMESPACE, RECORD, ""); - final InOrder ordered = Mockito.inOrder(receiver); - ordered.verify(receiver).startRecord(""); - ordered.verify(receiver).startEntity("003@"); - ordered.verify(receiver).literal("0", fieldValue1); - ordered.verify(receiver).endEntity(); - ordered.verify(receiver).endRecord(); - ordered.verifyNoMoreInteractions(); + Mockito.verifyNoMoreInteractions(receiver); } @Test @@ -210,27 +173,14 @@ public void issue569ShouldRecognizeRecordsWithAndWithoutNamespace() throws SAXException { final AttributesImpl attributes = new AttributesImpl(); - final String fieldValue1 = "1234"; - picaXmlHandler.setIgnoreNamespace(true); - picaXmlHandler.startElement(NAMESPACE, RECORD, "", attributes); - attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); - picaXmlHandler.startElement(null, DATAFIELD, "", attributes); - attributes.clear(); - attributes.addAttribute(null, "code", "code", "CDATA", "0"); - picaXmlHandler.startElement(null, SUBFIELD, "", attributes); - picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); - picaXmlHandler.endElement(null, SUBFIELD, ""); - picaXmlHandler.endElement(null, DATAFIELD, ""); - picaXmlHandler.endElement(null, RECORD, ""); + picaXmlHandler.startElement(null, RECORD, "", attributes); + picaXmlHandler.endElement(NAMESPACE, RECORD, ""); - final InOrder ordered = Mockito.inOrder(receiver); - ordered.verify(receiver).startRecord(""); - ordered.verify(receiver).startEntity("003@"); - ordered.verify(receiver).literal("0", fieldValue1); - ordered.verify(receiver).endEntity(); - ordered.verify(receiver).endRecord(); - ordered.verifyNoMoreInteractions(); + Mockito.verify(receiver).startRecord(""); + Mockito.verify(receiver).endRecord(); + + Mockito.verifyNoMoreInteractions(receiver); } @Test @@ -243,23 +193,12 @@ public void issue569ShouldRecognizeRecordsWithAndWithoutNamespaceOrderIndependen picaXmlHandler.setIgnoreNamespace(true); picaXmlHandler.setNamespace(""); picaXmlHandler.startElement(null, RECORD, "", attributes); - attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); - picaXmlHandler.startElement(null, DATAFIELD, "", attributes); - attributes.clear(); - attributes.addAttribute(null, "code", "code", "CDATA", "0"); - picaXmlHandler.startElement(null, SUBFIELD, "", attributes); - picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); - picaXmlHandler.endElement(null, SUBFIELD, ""); - picaXmlHandler.endElement(null, DATAFIELD, ""); picaXmlHandler.endElement(NAMESPACE, RECORD, ""); - final InOrder ordered = Mockito.inOrder(receiver); - ordered.verify(receiver).startRecord(""); - ordered.verify(receiver).startEntity("003@"); - ordered.verify(receiver).literal("0", fieldValue1); - ordered.verify(receiver).endEntity(); - ordered.verify(receiver).endRecord(); - ordered.verifyNoMoreInteractions(); + Mockito.verify(receiver).startRecord(""); + Mockito.verify(receiver).endRecord(); + + Mockito.verifyNoMoreInteractions(receiver); } @Test @@ -267,18 +206,8 @@ public void issue569ShouldNotRecognizeRecordsWithAndWithoutNamespace() throws SAXException { final AttributesImpl attributes = new AttributesImpl(); - final String fieldValue1 = "1234"; - picaXmlHandler.setIgnoreNamespace(false); picaXmlHandler.startElement(null, RECORD, "", attributes); - attributes.addAttribute(null, "tag", "tag", "CDATA", "003@"); - picaXmlHandler.startElement(null, DATAFIELD, "", attributes); - attributes.clear(); - attributes.addAttribute(null, "code", "code", "CDATA", "0"); - picaXmlHandler.startElement(null, SUBFIELD, "", attributes); - picaXmlHandler.characters(fieldValue1.toCharArray(), 0, fieldValue1.length()); - picaXmlHandler.endElement(null, SUBFIELD, ""); - picaXmlHandler.endElement(null, DATAFIELD, ""); picaXmlHandler.endElement(NAMESPACE, RECORD, ""); Mockito.verify(receiver).endRecord(); From a9c48163530ecdc4793d6fe2959610730992c7db Mon Sep 17 00:00:00 2001 From: TobiasNx Date: Thu, 16 Apr 2026 16:34:49 +0200 Subject: [PATCH 5/7] Only use occurence if it exists #530 Also improve some documentation --- .../metafacture/biblio/pica/PicaXmlHandler.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java index 445935805..799759d99 100644 --- a/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java +++ b/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java @@ -31,11 +31,12 @@ /** - * A Pica xml reader. To read marc data without namespace specification set option `namespace=""` or to null when using JAVA code. + * A Pica XML reader. To read marc data without namespace specification set option `namespace=""` or to null when using JAVA code. * @author Tobias Bülte + * @author Markus Michael Geipel * */ -@Description("A Pica XML reader. To read pica data without namespace specification set option `namespace=\"\"`. To ignore namespace specification set option `ignorenamespace=\"true\".") +@Description("A Pica XML reader. To read pica data without namespace specification set option `namespace=\"\"`. To ignore namespace specification set option `ignorenamespace=\"true\". For PPXML see `handle-ppxml`") @In(XmlReceiver.class) @Out(StreamReceiver.class) @FluxCommand("handle-picaxml") @@ -114,7 +115,14 @@ public void startElement(final String uri, final String localName, final String currentTag = attributes.getValue("code"); } else if (DATAFIELD.equals(localName)) { - getReceiver().startEntity( attributes.getValue("tag") + attributes.getValue("occurrence")); + final String tag = attributes.getValue("tag"); + final String occurence = attributes.getValue("occurrence"); + if (occurence != null) { + getReceiver().startEntity(tag + occurence); + } + else { + getReceiver().startEntity(tag); + } } else if (RECORD.equals(localName) && checkNamespace(uri)) { getReceiver().startRecord(""); From c726cf9637211a86763ac835bba48437e6bf94b6 Mon Sep 17 00:00:00 2001 From: TobiasNx <61879957+TobiasNx@users.noreply.github.com> Date: Thu, 23 Apr 2026 10:02:48 +0200 Subject: [PATCH 6/7] Apply suggestion from @TobiasNx --- .../main/java/org/metafacture/biblio/pica/PicaXmlHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java index 799759d99..57bc61881 100644 --- a/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java +++ b/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2013, 2014 Deutsche Nationalbibliothek + * Copyright 2026 hbz * * Licensed under the Apache License, Version 2.0 the "License"; * you may not use this file except in compliance with the License. From daa9e5f175da12d72f836ccaea067ceab61ed381 Mon Sep 17 00:00:00 2001 From: TobiasNx Date: Tue, 28 Apr 2026 10:56:55 +0200 Subject: [PATCH 7/7] Conform occurence to picaDecoder #530 In order to encode pica data the handling of occurence must be adjusted and add an slash. --- .../biblio/pica/PicaXmlHandler.java | 2 +- .../metafacture/biblio/pica/PpXmlHandler.java | 13 +- .../biblio/pica/PicaXmlHandlerTest.java | 2 +- .../biblio/pica/PpXmlHandlerTest.java | 148 ++++++++++++++++++ 4 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PpXmlHandlerTest.java diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java index 57bc61881..1eb8383d3 100644 --- a/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java +++ b/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PicaXmlHandler.java @@ -118,7 +118,7 @@ else if (DATAFIELD.equals(localName)) { final String tag = attributes.getValue("tag"); final String occurence = attributes.getValue("occurrence"); if (occurence != null) { - getReceiver().startEntity(tag + occurence); + getReceiver().startEntity(tag + "/" + occurence); } else { getReceiver().startEntity(tag); diff --git a/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PpXmlHandler.java b/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PpXmlHandler.java index 491150f45..f558c196d 100644 --- a/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PpXmlHandler.java +++ b/metafacture-biblio/src/main/java/org/metafacture/biblio/pica/PpXmlHandler.java @@ -64,8 +64,17 @@ public void startElement(final String uri, final String localName, currentTag = attributes.getValue("id"); } else if (DATAFIELD.equals(localName)) { - getReceiver().startEntity( - attributes.getValue("id") + attributes.getValue("occ")); + final String id = attributes.getValue("id"); + final String occurence = attributes.getValue("occ"); + if (occurence.matches("[1-9]")) { + getReceiver().startEntity(id + "/0" + occurence); + } + else if (occurence.isEmpty()) { + getReceiver().startEntity(id); + } + else { + getReceiver().startEntity(id + "/" + occurence); + } } else if (RECORD.equals(localName) && NAMESPACE.equals(uri)) { getReceiver().startRecord(""); diff --git a/metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PicaXmlHandlerTest.java b/metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PicaXmlHandlerTest.java index 2564729b5..91f893ab8 100644 --- a/metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PicaXmlHandlerTest.java +++ b/metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PicaXmlHandlerTest.java @@ -109,7 +109,7 @@ public void shouldLabelDataFieldWithOccurrenceAttribute() final InOrder ordered = Mockito.inOrder(receiver); ordered.verify(receiver).startRecord(""); - ordered.verify(receiver).startEntity("201U01"); + ordered.verify(receiver).startEntity("201U/01"); ordered.verify(receiver).literal("0", fieldValue); ordered.verify(receiver).endEntity(); ordered.verify(receiver).endRecord(); diff --git a/metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PpXmlHandlerTest.java b/metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PpXmlHandlerTest.java new file mode 100644 index 000000000..261580c8d --- /dev/null +++ b/metafacture-biblio/src/test/java/org/metafacture/biblio/pica/PpXmlHandlerTest.java @@ -0,0 +1,148 @@ +/* + * Copyright 2014 Deutsche Nationalbibliothek + * + * 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.pica; + +import org.metafacture.framework.StreamReceiver; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +/** + * Tests for class {@link PpXmlHandler}. + * + * @author Tobias Bülte + * + */ +public final class PpXmlHandlerTest { + + private static final String NAMESPACE = "http://www.oclcpica.org/xmlns/ppxml-1.0"; + private static final String RECORD = "record"; + private static final String DATAFIELD = "tag"; + private static final String SUBFIELD = "subf"; + + private PpXmlHandler ppXmlHandler; + + @Mock + private StreamReceiver receiver; + + public PpXmlHandlerTest() { + } + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + ppXmlHandler = new PpXmlHandler(); + ppXmlHandler.setReceiver(receiver); + } + + @After + public void cleanup() { + ppXmlHandler.closeStream(); + } + + @Test + public void shouldLabelDataFieldWithoutOccurrenceAttribute() + throws SAXException { + final AttributesImpl attributes = new AttributesImpl(); + + final String fieldValue = "1234"; + + ppXmlHandler.startElement(NAMESPACE, RECORD, "", attributes); + attributes.addAttribute(null, "id", "id", "CDATA", "003@"); + attributes.addAttribute(null, "occ", "occ", "CDATA", ""); + ppXmlHandler.startElement(null, DATAFIELD, "", attributes); + attributes.clear(); + attributes.addAttribute(null, "id", "id", "CDATA", "0"); + ppXmlHandler.startElement(null, SUBFIELD, "", attributes); + ppXmlHandler.characters(fieldValue.toCharArray(), 0, fieldValue.length()); + ppXmlHandler.endElement(null, SUBFIELD, ""); + ppXmlHandler.endElement(null, DATAFIELD, ""); + ppXmlHandler.endElement(NAMESPACE, RECORD, ""); + + final InOrder ordered = Mockito.inOrder(receiver); + ordered.verify(receiver).startRecord(""); + ordered.verify(receiver).startEntity("003@"); + ordered.verify(receiver).literal("0", fieldValue); + ordered.verify(receiver).endEntity(); + ordered.verify(receiver).endRecord(); + ordered.verifyNoMoreInteractions(); + } + + @Test + public void shouldLabelDataFieldWithOneDigitOccurrenceAttribute() + throws SAXException { + final AttributesImpl attributes = new AttributesImpl(); + + final String fieldValue = "utf-8"; + + ppXmlHandler.startElement(NAMESPACE, RECORD, "", attributes); + attributes.addAttribute(null, "id", "id", "CDATA", "201U"); + attributes.addAttribute(null, "occ", "occ", "CDATA", "1"); + ppXmlHandler.startElement(null, DATAFIELD, "", attributes); + attributes.clear(); + attributes.addAttribute(null, "id", "id", "CDATA", "0"); + ppXmlHandler.startElement(null, SUBFIELD, "", attributes); + ppXmlHandler.characters(fieldValue.toCharArray(), 0, fieldValue.length()); + ppXmlHandler.endElement(null, SUBFIELD, ""); + ppXmlHandler.endElement(null, DATAFIELD, ""); + ppXmlHandler.endElement(NAMESPACE, RECORD, ""); + + final InOrder ordered = Mockito.inOrder(receiver); + ordered.verify(receiver).startRecord(""); + ordered.verify(receiver).startEntity("201U/01"); + ordered.verify(receiver).literal("0", fieldValue); + ordered.verify(receiver).endEntity(); + ordered.verify(receiver).endRecord(); + ordered.verifyNoMoreInteractions(); + } + + @Test + public void shouldLabelDataFieldWithTwoDigitOccurrenceAttribute() + throws SAXException { + final AttributesImpl attributes = new AttributesImpl(); + + final String fieldValue = "utf-8"; + + ppXmlHandler.startElement(NAMESPACE, RECORD, "", attributes); + attributes.addAttribute(null, "id", "id", "CDATA", "201U"); + attributes.addAttribute(null, "occ", "occ", "CDATA", "01"); + ppXmlHandler.startElement(null, DATAFIELD, "", attributes); + attributes.clear(); + attributes.addAttribute(null, "id", "id", "CDATA", "0"); + ppXmlHandler.startElement(null, SUBFIELD, "", attributes); + ppXmlHandler.characters(fieldValue.toCharArray(), 0, fieldValue.length()); + ppXmlHandler.endElement(null, SUBFIELD, ""); + ppXmlHandler.endElement(null, DATAFIELD, ""); + ppXmlHandler.endElement(NAMESPACE, RECORD, ""); + + final InOrder ordered = Mockito.inOrder(receiver); + ordered.verify(receiver).startRecord(""); + ordered.verify(receiver).startEntity("201U/01"); + ordered.verify(receiver).literal("0", fieldValue); + ordered.verify(receiver).endEntity(); + ordered.verify(receiver).endRecord(); + ordered.verifyNoMoreInteractions(); + } + +}