Skip to content

Commit

Permalink
Merge 1abd6ed into 9b4f001
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeEdgar committed Jul 3, 2022
2 parents 9b4f001 + 1abd6ed commit 98a3382
Show file tree
Hide file tree
Showing 11 changed files with 294 additions and 39 deletions.
Expand Up @@ -45,6 +45,7 @@ public StaEDIInputFactory() {

supportedProperties.add(XML_DECLARE_TRANSACTION_XMLNS);
supportedProperties.add(XML_WRAP_TRANSACTION_CONTENTS);
supportedProperties.add(XML_USE_SEGMENT_IMPLEMENTATION_CODES);

supportedProperties.add(JSON_NULL_EMPTY_ELEMENTS);
supportedProperties.add(JSON_OBJECT_ELEMENTS);
Expand Down
Expand Up @@ -54,11 +54,13 @@ final class StaEDIXMLStreamReader implements XMLStreamReader {
private final Map<String, Object> properties;
private final boolean transactionDeclaresXmlns;
private final boolean wrapTransactionContents;
private final boolean useSegmentImplementationCodes;
private final Location location = new ProxyLocation();

private final Queue<Integer> eventQueue = new ArrayDeque<>(3);
private final Queue<QName> elementQueue = new ArrayDeque<>(3);
private final Deque<QName> elementStack = new ArrayDeque<>(5);
private final Deque<QName> standardNameStack = new ArrayDeque<>(5);

private boolean withinTransaction = false;
private boolean transactionWrapperEnqueued = false;
Expand All @@ -85,6 +87,7 @@ public void write(int b) throws IOException {
this.properties = new HashMap<>(properties);
transactionDeclaresXmlns = Boolean.valueOf(String.valueOf(properties.get(EDIInputFactory.XML_DECLARE_TRANSACTION_XMLNS)));
wrapTransactionContents = Boolean.valueOf(String.valueOf(properties.get(EDIInputFactory.XML_WRAP_TRANSACTION_CONTENTS)));
useSegmentImplementationCodes = Boolean.valueOf(String.valueOf(properties.get(EDIInputFactory.XML_USE_SEGMENT_IMPLEMENTATION_CODES)));

if (ediReader.getEventType() == EDIStreamEvent.START_INTERCHANGE) {
enqueueEvent(EDIStreamEvent.START_INTERCHANGE);
Expand Down Expand Up @@ -138,15 +141,29 @@ private QName buildName(QName parent, String namespace, String name) {
}

private void enqueueEvent(int xmlEvent, QName element, boolean remember) {
enqueueEvent(xmlEvent, element, element, remember);
}

private void enqueueEvent(int xmlEvent, QName element, QName standardName, boolean remember) {
LOGGER.finer(() -> "Enqueue XML event: " + xmlEvent + ", element: " + element);
eventQueue.add(xmlEvent);
elementQueue.add(element);

if (remember) {
elementStack.addFirst(element);
standardNameStack.addFirst(standardName);
}
}

QName parentName() {
return standardNameStack.getFirst();
}

QName popElement() {
standardNameStack.removeFirst();
return elementStack.removeFirst();
}

private void advanceEvent() {
currentEvent = eventQueue.remove();
currentElement = elementQueue.remove();
Expand All @@ -161,7 +178,7 @@ private void enqueueEvent(EDIStreamEvent ediEvent) throws XMLStreamException {

switch (ediEvent) {
case ELEMENT_DATA:
name = buildName(elementStack.getFirst(), EDINamespaces.ELEMENTS);
name = buildName(parentName(), EDINamespaces.ELEMENTS);
enqueueEvent(START_ELEMENT, name, false);
enqueueEvent(CHARACTERS, DUMMY_QNAME, false);
enqueueEvent(END_ELEMENT, name, false);
Expand All @@ -172,7 +189,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 = buildName(elementStack.getFirst(), EDINamespaces.ELEMENTS);
name = buildName(parentName(), EDINamespaces.ELEMENTS);
enqueueEvent(START_ELEMENT, name, false);
enqueueEvent(CDATA, DUMMY_QNAME, false);

Expand Down Expand Up @@ -201,47 +218,48 @@ private void enqueueEvent(EDIStreamEvent ediEvent) throws XMLStreamException {
case START_SEGMENT:
readerText = ediReader.getText();
performTransactionWrapping(readerText);
name = buildName(elementStack.getFirst(), EDINamespaces.SEGMENTS, readerText);
enqueueEvent(START_ELEMENT, name, true);
QName standardName = buildName(parentName(), EDINamespaces.SEGMENTS, readerText);
name = useSegmentImplementationCodes ? buildName(parentName(), EDINamespaces.SEGMENTS, ediReader.getReferenceCode()) : standardName;
enqueueEvent(START_ELEMENT, name, standardName, true);
break;

case START_TRANSACTION:
withinTransaction = true;
name = buildName(elementStack.getFirst(), EDINamespaces.LOOPS, ediReader.getReferenceCode());
name = buildName(parentName(), EDINamespaces.LOOPS, ediReader.getReferenceCode());
enqueueEvent(START_ELEMENT, name, true);
determineTransactionSegments();
break;

case START_GROUP:
case START_LOOP:
name = buildName(elementStack.getFirst(), EDINamespaces.LOOPS, ediReader.getReferenceCode());
name = buildName(parentName(), EDINamespaces.LOOPS, ediReader.getReferenceCode());
enqueueEvent(START_ELEMENT, name, true);
break;

case START_COMPOSITE:
compositeCode = ediReader.getReferenceCode();
name = buildName(elementStack.getFirst(), EDINamespaces.COMPOSITES);
name = buildName(parentName(), EDINamespaces.COMPOSITES);
enqueueEvent(START_ELEMENT, name, true);
break;

case END_INTERCHANGE:
enqueueEvent(END_ELEMENT, elementStack.removeFirst(), false);
enqueueEvent(END_ELEMENT, popElement(), false);
namespaceContext = null;
enqueueEvent(END_DOCUMENT, DUMMY_QNAME, false);
break;

case END_TRANSACTION:
withinTransaction = false;
compositeCode = null;
enqueueEvent(END_ELEMENT, elementStack.removeFirst(), false);
enqueueEvent(END_ELEMENT, popElement(), false);
break;

case END_GROUP:
case END_LOOP:
case END_SEGMENT:
case END_COMPOSITE:
compositeCode = null;
enqueueEvent(END_ELEMENT, elementStack.removeFirst(), false);
enqueueEvent(END_ELEMENT, popElement(), false);
break;

case SEGMENT_ERROR:
Expand Down Expand Up @@ -278,7 +296,7 @@ private void performTransactionWrapping(String readerText) {
if (withinTransaction && wrapTransactionContents) {
if (transactionWrapperEnqueued) {
if (readerText.equals(this.transactionEndSegment)) {
enqueueEvent(END_ELEMENT, elementStack.removeFirst(), false);
enqueueEvent(END_ELEMENT, popElement(), false);
transactionWrapperEnqueued = false;
}
} else {
Expand Down Expand Up @@ -402,6 +420,7 @@ public void close() throws XMLStreamException {
eventQueue.clear();
elementQueue.clear();
elementStack.clear();
standardNameStack.clear();
ediReader.close();
} catch (IOException e) {
throw new XMLStreamException(e);
Expand Down
Expand Up @@ -356,7 +356,6 @@ public boolean compositeEnd(boolean isNil) {
public boolean elementData(char[] text, int start, int length) {
boolean derivedComposite;
EDIReference typeReference;
boolean eventsReady = true;
final boolean compositeFromStream = location.getComponentPosition() > -1;

elementHolder.set(text, start, length);
Expand All @@ -368,41 +367,44 @@ public boolean elementData(char[] text, int start, int length) {
setLevelIdentifiers();
}

/*
* The first component of a composite was the only element received
* for the composite. It was found to be a composite via the schema
* and the composite begin/end events must be generated.
**/
final boolean componentReceivedAsSimple;

if (validator != null) {
derivedComposite = !compositeFromStream && validator.isComposite(dialect, location);
componentReceivedAsSimple = derivedComposite && text != null;

if (componentReceivedAsSimple) {
this.compositeBegin(elementHolder.length() == 0);
location.incrementComponentPosition();
}

valid = validator.validateElement(dialect, location, elementHolder, null);
derivedComposite = !compositeFromStream && validator.isComposite();
typeReference = validator.getElementReference();
enqueueElementOccurrenceErrors(validator, valid);
} else {
valid = true;
derivedComposite = false;
componentReceivedAsSimple = false;
typeReference = null;
}

/*
* The first component of a composite was the only element received
* for the composite. It was found to be a composite via the schema
* and the composite begin/end events must be generated.
**/
final boolean componentReceivedAsSimple = derivedComposite && text != null;

if (componentReceivedAsSimple) {
this.compositeBegin(elementHolder.length() == 0);
location.incrementComponentPosition();
}

enqueueElementErrors(validator, valid);

boolean eventsReady = true;

if (text != null && (!derivedComposite || length > 0) /* Not an inferred element */) {
enqueueEvent(EDIStreamEvent.ELEMENT_DATA,
EDIStreamValidationError.NONE,
elementHolder,
typeReference,
location);

if (validator != null && validator.isPendingDiscrimination()) {
eventsReady = validator.selectImplementation(eventQueue, this);
}
eventsReady = selectImplementationIfPending(validator, eventsReady);
}

if (componentReceivedAsSimple) {
Expand All @@ -413,6 +415,14 @@ public boolean elementData(char[] text, int start, int length) {
return !levelCheckPending && eventsReady;
}

boolean selectImplementationIfPending(Validator validator, boolean eventsReadyDefault) {
if (validator != null && validator.isPendingDiscrimination()) {
return validator.selectImplementation(eventQueue, this);
}

return eventsReadyDefault;
}

void clearLevelCheck() {
levelCheckPending = false;
currentSegmentBegin = null;
Expand Down
Expand Up @@ -1022,10 +1022,30 @@ public boolean validCompositeOccurrences(Dialect dialect, Location position) {
return elementErrors.isEmpty();
}

public boolean isComposite() {
boolean isComposite() {
return isComposite(this.composite);
}

static boolean isComposite(UsageNode composite) {
return composite != null && !StaEDISchema.ANY_COMPOSITE_ID.equals(composite.getId());
}

public boolean isComposite(Dialect dialect, StaEDIStreamLocation position) {
if (!segmentExpected) {
return false;
}

final String version = dialect.getTransactionVersionString();
final int elementPosition = position.getElementPosition() - 1;

if (elementPosition < segment.getChildren(version).size()) {
UsageNode candidate = segment.getChild(version, elementPosition);
return candidate.isNodeType(EDIType.Type.COMPOSITE) && isComposite(candidate);
}

return false;
}

public boolean validateElement(Dialect dialect, StaEDIStreamLocation position, CharSequence value, StringBuilder formattedValue) {
if (!segmentExpected) {
return true;
Expand Down Expand Up @@ -1104,7 +1124,7 @@ void validateComponentElement(Dialect dialect, int componentIndex, boolean value
String version = dialect.getTransactionVersionString();

if (componentIndex < element.getChildren(version).size()) {
if (valueReceived || componentIndex != 0 /* Derived component*/) {
if (valueReceived || componentIndex != 0 /* Derived component */) {
this.element = this.element.getChild(version, componentIndex);

if (isImplElementSelected()) {
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/io/xlate/edi/stream/EDIInputFactory.java
Expand Up @@ -53,6 +53,16 @@ public abstract class EDIInputFactory extends PropertySupport {
*/
public static final String XML_WRAP_TRANSACTION_CONTENTS = "io.xlate.edi.stream.XML_WRAP_TRANSACTION_CONTENTS";

/**
* When set to true, the XML elements representing segments in an EDI implementation schema
* will be named according to the schema-defined {@code code} attribute for the segment.
*
* Default value: false
*
* @since 1.21
*/
public static final String XML_USE_SEGMENT_IMPLEMENTATION_CODES = "io.xlate.edi.stream.XML_USE_SEGMENT_IMPLEMENTATION_CODES";

/**
* When set to true, non-graphical, control characters will be ignored in
* the EDI input stream. This includes characters ranging from 0x00 through
Expand Down
8 changes: 8 additions & 0 deletions src/main/resources/schema/EDISchema-v3.xsd
Expand Up @@ -241,6 +241,14 @@
</element>
</sequence>
<attribute name="type" type="IDREF" use="required" />
<attribute name="code" type="NMTOKEN" use="optional">
<annotation>
<documentation>
Code used to identify a segment within a loop. May be useful when occurrences of a
standard segment contain distinct information.
</documentation>
</annotation>
</attribute>
<attributeGroup ref="tns:implementationAttributeGroup" />
<attribute name="discriminator" type="tns:discriminatorType" use="optional">
<annotation>
Expand Down
Expand Up @@ -945,4 +945,40 @@ void testWrappedTransactionElements() throws Exception {
assertTrue(!d.hasDifferences(), () -> "XML unexpectedly different:\n" + d.toString(new DefaultComparisonFormatter()));
}

@Test
void testImplementationSegmentCodeUsage() throws Exception {
EDIInputFactory ediFactory = EDIInputFactory.newFactory();
ediFactory.setProperty(EDIInputFactory.XML_USE_SEGMENT_IMPLEMENTATION_CODES, Boolean.TRUE);
ediFactory.setErrorReporter((errorType, reader) -> {
// NO-OP
});
SchemaFactory schemaFactory = SchemaFactory.newFactory();

InputStream stream = getClass().getResourceAsStream("/x12/simple999.edi");
ediReader = ediFactory.createEDIStreamReader(stream);
Schema schema = schemaFactory.createSchema(getClass().getResource("/x12/IG-999-standard-included.xml"));

ediReader = ediFactory.createFilteredReader(ediReader, (reader) -> {
if (reader.getEventType() == EDIStreamEvent.START_TRANSACTION) {
reader.setTransactionSchema(schema);
}
return true;
});

XMLStreamReader xmlReader = ediFactory.createXMLStreamReader(ediReader);

xmlReader.next(); // Per StAXSource JavaDoc, put in START_DOCUMENT state
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
StringWriter result = new StringWriter();
transformer.transform(new StAXSource(xmlReader), new StreamResult(result));
String resultString = result.toString();
System.out.println(resultString);
Diff d = DiffBuilder.compare(Input.fromFile("src/test/resources/x12/impl-ack-with-segment-impl-codes.xml"))
.withTest(resultString).build();
assertTrue(!d.hasDifferences(), () -> "XML unexpectedly different:\n" + d.toString(new DefaultComparisonFormatter()));
}

}
6 changes: 3 additions & 3 deletions src/test/resources/x12/IG-999-standard-included-relative.xml
Expand Up @@ -30,7 +30,7 @@
<element position="4" minOccurs="1" />
</sequence>
</segment>
<segment type="CTX" discriminator="01.01" maxOccurs="9">
<segment type="CTX" discriminator="01.01" maxOccurs="9" code="SegmentContext">
<sequence>
<composite position="1">
<sequence>
Expand All @@ -52,7 +52,7 @@
</composite>
</sequence>
</segment>
<segment type="CTX" maxOccurs="1">
<segment type="CTX" maxOccurs="1" code="BusinessUnitIdentifier">
<sequence>
<composite position="1">
<sequence>
Expand All @@ -68,7 +68,7 @@
<segment type="IK4">
<!-- Unchanged from standard -->
</segment>
<segment type="CTX" maxOccurs="10">
<segment type="CTX" maxOccurs="10" code="ElementContext">
<sequence>
<composite position="1">
<sequence>
Expand Down

0 comments on commit 98a3382

Please sign in to comment.