diff --git a/src/main/java/io/xlate/edi/internal/stream/StaEDIXMLStreamReader.java b/src/main/java/io/xlate/edi/internal/stream/StaEDIXMLStreamReader.java index 434ac004..27c6c997 100644 --- a/src/main/java/io/xlate/edi/internal/stream/StaEDIXMLStreamReader.java +++ b/src/main/java/io/xlate/edi/internal/stream/StaEDIXMLStreamReader.java @@ -21,7 +21,12 @@ import java.util.ArrayDeque; import java.util.Arrays; import java.util.Base64; +import java.util.Collections; import java.util.Deque; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Queue; @@ -31,6 +36,7 @@ import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; +import io.xlate.edi.stream.EDIStreamConstants.Namespaces; import io.xlate.edi.stream.EDIStreamEvent; import io.xlate.edi.stream.EDIStreamException; import io.xlate.edi.stream.EDIStreamReader; @@ -39,13 +45,15 @@ public class StaEDIXMLStreamReader implements XMLStreamReader { private static final Location location = new DefaultLocation(); private static final QName DUMMY_QNAME = new QName("DUMMY"); - private static final QName INTERCHANGE = new QName("INTERCHANGE"); + private static final QName INTERCHANGE = new QName(Namespaces.LOOPS, "INTERCHANGE", prefixOf(Namespaces.LOOPS)); private final EDIStreamReader ediReader; private final Queue eventQueue = new ArrayDeque<>(3); private final Queue elementQueue = new ArrayDeque<>(3); + private final Deque elementStack = new ArrayDeque<>(); + private NamespaceContext namespaceContext; private final StringBuilder cdataBuilder = new StringBuilder(); private char[] cdata; @@ -66,24 +74,28 @@ public Object getProperty(String name) { } private boolean isEvent(int... eventTypes) { - return Arrays.stream(eventTypes).anyMatch(eventQueue.element()::equals); + return Arrays.stream(eventTypes).anyMatch(eventQueue.element()::equals); + } + + private QName buildName(QName parent, String namespace) { + return buildName(parent, namespace, null); } - private QName deriveName(QName parent, String hint) { - String name = hint; + private QName buildName(QName parent, String namespace, String name) { + String prefix = prefixOf(namespace); if (name == null) { final io.xlate.edi.stream.Location l = ediReader.getLocation(); final int componentPosition = l.getComponentPosition(); if (componentPosition > 0) { - name = String.format("%s-%d", parent, componentPosition); + name = String.format("%s-%d", parent.getLocalPart(), componentPosition); } else { - name = String.format("%s%02d", parent, l.getElementPosition()); + name = String.format("%s%02d", parent.getLocalPart(), l.getElementPosition()); } } - return new QName(name); + return new QName(namespace, name, prefix); } private void enqueueEvent(int xmlEvent, QName element, boolean remember) { @@ -107,7 +119,7 @@ private void enqueueEvent(EDIStreamEvent ediEvent) throws XMLStreamException { switch (ediEvent) { case ELEMENT_DATA: - name = deriveName(elementStack.getFirst(), null); + name = buildName(elementStack.getFirst(), Namespaces.ELEMENTS); enqueueEvent(START_ELEMENT, name, false); enqueueEvent(CHARACTERS, DUMMY_QNAME, false); enqueueEvent(END_ELEMENT, name, false); @@ -118,7 +130,7 @@ private void enqueueEvent(EDIStreamEvent ediEvent) throws XMLStreamException { * This section will read the binary data and Base64 the stream * into an XML CDATA section. * */ - name = deriveName(elementStack.getFirst(), null); + name = buildName(elementStack.getFirst(), Namespaces.ELEMENTS); enqueueEvent(START_ELEMENT, name, false); enqueueEvent(CDATA, DUMMY_QNAME, false); @@ -149,26 +161,29 @@ public void write(int b) throws IOException { case START_INTERCHANGE: enqueueEvent(START_DOCUMENT, DUMMY_QNAME, false); enqueueEvent(START_ELEMENT, INTERCHANGE, true); + namespaceContext = new DocumentNamespaceContext(); break; case START_SEGMENT: - enqueueEvent(START_ELEMENT, new QName(ediReader.getText()), true); + name = buildName(elementStack.getFirst(), Namespaces.SEGMENTS, ediReader.getText()); + enqueueEvent(START_ELEMENT, name, true); break; case START_GROUP: case START_TRANSACTION: case START_LOOP: - name = deriveName(elementStack.getFirst(), ediReader.getText()); + name = buildName(elementStack.getFirst(), Namespaces.LOOPS, ediReader.getText()); enqueueEvent(START_ELEMENT, name, true); break; case START_COMPOSITE: - name = deriveName(elementStack.getFirst(), ediReader.getReferenceCode()); + name = buildName(elementStack.getFirst(), Namespaces.COMPOSITES, ediReader.getReferenceCode()); enqueueEvent(START_ELEMENT, name, true); break; case END_INTERCHANGE: enqueueEvent(END_ELEMENT, elementStack.removeFirst(), false); + namespaceContext = null; enqueueEvent(END_DOCUMENT, DUMMY_QNAME, false); break; @@ -181,7 +196,9 @@ public void write(int b) throws IOException { break; case SEGMENT_ERROR: - throw new XMLStreamException(String.format("Segment %s has error %s", ediReader.getText(), ediReader.getErrorType())); + throw new XMLStreamException(String.format("Segment %s has error %s", + ediReader.getText(), + ediReader.getErrorType())); default: throw new IllegalStateException("Unknown state: " + ediEvent); @@ -224,7 +241,8 @@ public void require(int type, String namespaceURI, String localName) throws XMLS final String currentLocalPart = name.getLocalPart(); if (!localName.equals(currentLocalPart)) { - throw new XMLStreamException("Current localPart " + currentLocalPart + " does not match required localName " + localName); + throw new XMLStreamException("Current localPart " + currentLocalPart + + " does not match required localName " + localName); } } @@ -364,22 +382,32 @@ public boolean isAttributeSpecified(int index) { @Override public int getNamespaceCount() { + if (INTERCHANGE.equals(elementQueue.element())) { + return Namespaces.all().size(); + } return 0; } @Override public String getNamespacePrefix(int index) { - throw new UnsupportedOperationException(); + if (INTERCHANGE.equals(elementQueue.element())) { + String namespace = Namespaces.all().get(index); + return prefixOf(namespace); + } + return null; } @Override public String getNamespaceURI(int index) { - throw new UnsupportedOperationException(); + if (INTERCHANGE.equals(elementQueue.element())) { + return Namespaces.all().get(index); + } + return null; } @Override public NamespaceContext getNamespaceContext() { - throw new UnsupportedOperationException(); + return this.namespaceContext; } @Override @@ -392,7 +420,7 @@ public String getText() { requireCharacters(); if (cdataBuilder.length() > 0) { - if (cdata == null) { + if (cdata == null) { cdata = new char[cdataBuilder.length()]; cdataBuilder.getChars(0, cdataBuilder.length(), cdata, 0); } @@ -505,7 +533,10 @@ public boolean hasName() { @Override public String getNamespaceURI() { - throw new UnsupportedOperationException(); + if (hasName()) { + return elementQueue.element().getNamespaceURI(); + } + return null; } @Override @@ -543,6 +574,10 @@ public String getPIData() { throw new UnsupportedOperationException(); } + static String prefixOf(String namespace) { + return String.valueOf(namespace.substring(namespace.lastIndexOf(':') + 1).charAt(0)); + } + private static class DefaultLocation implements Location { @Override public int getLineNumber() { @@ -569,4 +604,37 @@ public String getSystemId() { return null; } } + + private static class DocumentNamespaceContext implements NamespaceContext { + private final Map namespaces; + + DocumentNamespaceContext() { + List names = Namespaces.all(); + namespaces = new HashMap<>(names.size()); + for (String namespace : names) { + String prefix = prefixOf(namespace); + namespaces.put(namespace, prefix); + } + } + + @Override + public String getNamespaceURI(String prefix) { + return namespaces.entrySet() + .stream() + .filter(e -> e.getValue().equals(prefix)) + .map(Map.Entry::getKey) + .findFirst() + .orElse(null); + } + + @Override + public String getPrefix(String namespaceURI) { + return namespaces.get(namespaceURI); + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + return Collections.singletonList(namespaces.get(namespaceURI)).iterator(); + } + } } diff --git a/src/main/java/io/xlate/edi/stream/EDIStreamConstants.java b/src/main/java/io/xlate/edi/stream/EDIStreamConstants.java index 535a08dc..5256d31a 100644 --- a/src/main/java/io/xlate/edi/stream/EDIStreamConstants.java +++ b/src/main/java/io/xlate/edi/stream/EDIStreamConstants.java @@ -15,6 +15,9 @@ ******************************************************************************/ package io.xlate.edi.stream; +import java.util.Arrays; +import java.util.List; + public interface EDIStreamConstants { public static class Standards { @@ -36,4 +39,18 @@ private Delimiters() { public static final String DECIMAL = "io.xlate.edi.stream.delim.decimal"; public static final String RELEASE = "io.xlate.edi.stream.delim.release"; } + + public static class Namespaces { + private Namespaces() { + } + + public static final String LOOPS = "urn:xlate.io:staedi:names:loops"; + public static final String SEGMENTS = "urn:xlate.io:staedi:names:segments"; + public static final String COMPOSITES = "urn:xlate.io:staedi:names:composites"; + public static final String ELEMENTS = "urn:xlate.io:staedi:names:elements"; + + public static final List all() { + return Arrays.asList(LOOPS, SEGMENTS, COMPOSITES, ELEMENTS); + } + } } diff --git a/src/test/java/io/xlate/edi/internal/stream/StaEDIXMLStreamReaderTest.java b/src/test/java/io/xlate/edi/internal/stream/StaEDIXMLStreamReaderTest.java index fba420c9..b211cc31 100644 --- a/src/test/java/io/xlate/edi/internal/stream/StaEDIXMLStreamReaderTest.java +++ b/src/test/java/io/xlate/edi/internal/stream/StaEDIXMLStreamReaderTest.java @@ -30,6 +30,7 @@ import java.util.Base64; import java.util.concurrent.atomic.AtomicReference; +import javax.xml.namespace.NamespaceContext; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; @@ -50,6 +51,7 @@ import io.xlate.edi.schema.Schema; import io.xlate.edi.schema.SchemaFactory; import io.xlate.edi.stream.EDIInputFactory; +import io.xlate.edi.stream.EDIStreamConstants.Namespaces; import io.xlate.edi.stream.EDIStreamFilter; import io.xlate.edi.stream.EDIStreamReader; @@ -150,6 +152,61 @@ public void testGetElementText() throws Exception { assertEquals("00", xmlReader.getElementText()); } + @Test + public void testNamespaces() throws Exception { + XMLStreamReader xmlReader = getXmlReader(TINY_X12); + + assertEquals(XMLStreamConstants.START_DOCUMENT, xmlReader.next()); + assertEquals(XMLStreamConstants.START_ELEMENT, xmlReader.next()); + assertEquals("INTERCHANGE", xmlReader.getLocalName()); + assertEquals(Namespaces.LOOPS, xmlReader.getName().getNamespaceURI()); + assertEquals("l", xmlReader.getName().getPrefix()); + assertEquals(4, xmlReader.getNamespaceCount()); + assertEquals(Namespaces.LOOPS, xmlReader.getNamespaceURI()); + + assertSegmentBoundaries(xmlReader, "ISA", 16); + + NamespaceContext context = xmlReader.getNamespaceContext(); + assertEquals("s", context.getPrefix(Namespaces.SEGMENTS)); + assertEquals("s", context.getPrefixes(Namespaces.SEGMENTS).next()); + assertEquals(Namespaces.SEGMENTS, context.getNamespaceURI("s")); + assertNull(context.getNamespaceURI("x")); + assertEquals(Namespaces.SEGMENTS, xmlReader.getNamespaceURI()); + + assertEquals(XMLStreamConstants.START_ELEMENT, xmlReader.next()); + assertEquals("IEA", xmlReader.getLocalName()); + assertEquals(Namespaces.SEGMENTS, xmlReader.getNamespaceURI()); + + // No namespaces declared on the segment + assertEquals(0, xmlReader.getNamespaceCount()); + assertNull(xmlReader.getNamespacePrefix(0)); + assertNull(xmlReader.getNamespaceURI(0)); + + assertElement(xmlReader, "IEA01", "1"); + assertEquals(Namespaces.ELEMENTS, xmlReader.getNamespaceURI()); + + // No namespaces declared on the element + assertEquals(0, xmlReader.getNamespaceCount()); + assertNull(xmlReader.getNamespacePrefix(0)); + assertNull(xmlReader.getNamespaceURI(0)); + + assertElement(xmlReader, "IEA02", "508121953"); + assertEquals(Namespaces.ELEMENTS, xmlReader.getNamespaceURI()); + + assertEquals(XMLStreamConstants.END_ELEMENT, xmlReader.next()); + assertEquals("IEA", xmlReader.getLocalName()); + assertEquals(Namespaces.SEGMENTS, xmlReader.getNamespaceURI()); + + assertEquals(XMLStreamConstants.END_ELEMENT, xmlReader.next()); + assertEquals("INTERCHANGE", xmlReader.getLocalName()); + assertEquals(Namespaces.LOOPS, xmlReader.getName().getNamespaceURI()); + assertEquals("l", xmlReader.getName().getPrefix()); + assertEquals(4, xmlReader.getNamespaceCount()); + assertEquals(Namespaces.LOOPS, xmlReader.getNamespaceURI()); + + assertEquals(XMLStreamConstants.END_DOCUMENT, xmlReader.next()); + } + private void assertElement(XMLStreamReader xmlReader, String tag, String value) throws Exception { assertEquals(XMLStreamConstants.START_ELEMENT, xmlReader.next()); assertEquals(tag, xmlReader.getLocalName()); @@ -283,10 +340,6 @@ public boolean accept(EDIStreamReader reader) { public void testUnsupportedOperations() throws Exception { EDIStreamReader ediReader = Mockito.mock(EDIStreamReader.class); XMLStreamReader xmlReader = new StaEDIXMLStreamReader(ediReader); - try { - xmlReader.getNamespaceURI(); - fail("UnsupportedOperationExpected"); - } catch (UnsupportedOperationException e) {} try { xmlReader.getAttributeValue("", ""); fail("UnsupportedOperationExpected"); @@ -319,22 +372,6 @@ public void testUnsupportedOperations() throws Exception { xmlReader.isAttributeSpecified(0); fail("UnsupportedOperationExpected"); } catch (UnsupportedOperationException e) {} - try { - xmlReader.getNamespacePrefix(0); - fail("UnsupportedOperationExpected"); - } catch (UnsupportedOperationException e) {} - try { - xmlReader.getNamespaceURI(0); - fail("UnsupportedOperationExpected"); - } catch (UnsupportedOperationException e) {} - try { - xmlReader.getNamespaceContext(); - fail("UnsupportedOperationExpected"); - } catch (UnsupportedOperationException e) {} - try { - xmlReader.getNamespaceURI(""); - fail("UnsupportedOperationExpected"); - } catch (UnsupportedOperationException e) {} try { xmlReader.getPITarget(); fail("UnsupportedOperationExpected"); diff --git a/src/test/java/io/xlate/edi/internal/stream/tokenization/LexerTest.java b/src/test/java/io/xlate/edi/internal/stream/tokenization/LexerTest.java index bf3b8c8e..0026a33b 100644 --- a/src/test/java/io/xlate/edi/internal/stream/tokenization/LexerTest.java +++ b/src/test/java/io/xlate/edi/internal/stream/tokenization/LexerTest.java @@ -312,7 +312,7 @@ public void testParseTagsEDIFACTB() throws EDIException, IOException { } @Test - public void testRejectedX12Dialect() throws EDIException, IOException { + public void testRejectedX12Dialect() { InputStream stream = new ByteArrayInputStream("ISA*00? *00* *ZZ*ReceiverID *ZZ*Sender *050812*1953*^*00501*508121953*0*P*:~".getBytes()); TestLexerEventHandler eventHandler = new TestLexerEventHandler(); final StaEDIStreamLocation location = new StaEDIStreamLocation(); @@ -322,7 +322,7 @@ public void testRejectedX12Dialect() throws EDIException, IOException { } @Test - public void testInvalidCharacter() throws EDIException, IOException { + public void testInvalidCharacter() { InputStream stream = new ByteArrayInputStream("ISA*00*\u0008 *00* *ZZ*ReceiverID *ZZ*Sender *050812*1953*^*00501*508121953*0*P*:~".getBytes()); TestLexerEventHandler eventHandler = new TestLexerEventHandler(); final StaEDIStreamLocation location = new StaEDIStreamLocation(); diff --git a/src/test/resources/x12/extraDelimiter997.xml b/src/test/resources/x12/extraDelimiter997.xml index a103e865..4dc681e8 100644 --- a/src/test/resources/x12/extraDelimiter997.xml +++ b/src/test/resources/x12/extraDelimiter997.xml @@ -1,97 +1,97 @@ - - - 00 - - 00 - - ZZ - ReceiverID - ZZ - Sender - 050812 - 1953 - ^ - 00501 - 508121953 - 0 - P - : - - - - FA - ReceiverDept - SenderDept - 20050812 - 195335 - 000005 - X - 005010X230 - - - - 997 - 0001 - - - HC - 000001 - - - 837 - 0021 - - - NM1 - AK302-R1 - AK302-R2 - - AK302-R3-COMP1 - AK302-R3-COMP2 - - - AK304-R1 - AK304-R2 - AK304-R3 - - - 8 - 66 - 7 - - AK404-R1-COMP1 - AK404-R1-COMP2 - AK404-R1-COMP3 - - - AK404-R2-COMP1 - AK404-R2-COMP2 - - - - R - 5 - - - R - 1 - 1 - 0 - - - 8 - 0001 - - - - 1 - 000005 - - - - 1 - 508121953 - - + + + 00 + + 00 + + ZZ + ReceiverID + ZZ + Sender + 050812 + 1953 + ^ + 00501 + 508121953 + 0 + P + : + + + + FA + ReceiverDept + SenderDept + 20050812 + 195335 + 000005 + X + 005010X230 + + + + 997 + 0001 + + + HC + 000001 + + + 837 + 0021 + + + NM1 + AK302-R1 + AK302-R2 + + AK302-R3-COMP1 + AK302-R3-COMP2 + + + AK304-R1 + AK304-R2 + AK304-R3 + + + 8 + 66 + 7 + + AK404-R1-COMP1 + AK404-R1-COMP2 + AK404-R1-COMP3 + + + AK404-R2-COMP1 + AK404-R2-COMP2 + + + + R + 5 + + + R + 1 + 1 + 0 + + + 8 + 0001 + + + + 1 + 000005 + + + + 1 + 508121953 + +