Repository: xlate/staedi (java)

Issue:
Add support for partial validation
It would be appreciated if the transaction schema could support something like "wildcard sequences" inside a `segmentType` declaration.

The idea is to skip validation of elements/composites of some segments. This can be useful when you are dealing with big "models", and you are only interested in parse and validate part of the "model". 

The syntax could be something like:

```xml
<segmentType name="FOO">
    <sequence>
    	<any/>
    </sequence>
</segmentType>
```
It's similar to the "any" element in XSD (with processContents="skip"): https://www.w3schools.com/xml/el_any.asp

Thanks in advance


Pull request:
Support for lax validation of simple elements and composite elements
Fixes #23 

Patch:
diff --git a/src/main/java/io/xlate/edi/internal/schema/SchemaReaderBase.java b/src/main/java/io/xlate/edi/internal/schema/SchemaReaderBase.java
index 7a8ff80..65c1293 100644
--- a/src/main/java/io/xlate/edi/internal/schema/SchemaReaderBase.java
+++ b/src/main/java/io/xlate/edi/internal/schema/SchemaReaderBase.java
@@ -3,6 +3,7 @@ package io.xlate.edi.internal.schema;
 import static io.xlate.edi.internal.schema.StaEDISchemaFactory.schemaException;
 import static io.xlate.edi.internal.schema.StaEDISchemaFactory.unexpectedElement;
 import static io.xlate.edi.internal.schema.StaEDISchemaFactory.unexpectedEvent;
+import static java.util.stream.Collectors.toList;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -15,23 +16,47 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.Function;
+import java.util.stream.IntStream;
 
 import javax.xml.namespace.QName;
 import javax.xml.stream.XMLStreamConstants;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamReader;
 
+import io.xlate.edi.schema.EDIComplexType;
 import io.xlate.edi.schema.EDIReference;
 import io.xlate.edi.schema.EDISchemaException;
 import io.xlate.edi.schema.EDISimpleType;
+import io.xlate.edi.schema.EDISimpleType.Base;
 import io.xlate.edi.schema.EDISyntaxRule;
 import io.xlate.edi.schema.EDIType;
+import io.xlate.edi.schema.EDIType.Type;
 
 abstract class SchemaReaderBase implements SchemaReader {
 
     static final String REFERR_UNDECLARED = "Type %s references undeclared %s with ref='%s'";
     static final String REFERR_ILLEGAL = "Type '%s' must not be referenced as '%s' in definition of type '%s'";
 
+    static final EDIReference ANY_ELEMENT_REF_OPT = new Reference(StaEDISchema.ANY_ELEMENT_ID, "element", 0, 1);
+    static final EDIReference ANY_COMPOSITE_REF_OPT = new Reference(StaEDISchema.ANY_COMPOSITE_ID, "composite", 0, 99);
+
+    static final EDIReference ANY_ELEMENT_REF_REQ = new Reference(StaEDISchema.ANY_ELEMENT_ID, "element", 1, 1);
+    static final EDIReference ANY_COMPOSITE_REF_REQ = new Reference(StaEDISchema.ANY_COMPOSITE_ID, "composite", 1, 99);
+
+    static final EDISimpleType ANY_ELEMENT = new ElementType(StaEDISchema.ANY_ELEMENT_ID,
+                                                             Base.STRING,
+                                                             0,
+                                                             0,
+                                                             99_999,
+                                                             Collections.emptySet());
+
+    static final EDIComplexType ANY_COMPOSITE = new StructureType(StaEDISchema.ANY_COMPOSITE_ID,
+                                                                  Type.COMPOSITE,
+                                                                  "ANY",
+                                                                  IntStream.rangeClosed(0, 99).mapToObj(i -> ANY_ELEMENT_REF_OPT)
+                                                                           .collect(toList()),
+                                                                  Collections.emptyList());
+
     final String xmlns;
 
     final QName qnSchema;
@@ -48,6 +73,7 @@ abstract class SchemaReaderBase implements SchemaReader {
     final QName qnSequence;
     final QName qnEnumeration;
     final QName qnValue;
+    final QName qnAny;
 
     final QName qnCompositeType;
     final QName qnElementType;
@@ -75,6 +101,7 @@ abstract class SchemaReaderBase implements SchemaReader {
         qnSequence = new QName(xmlns, "sequence");
         qnEnumeration = new QName(xmlns, "enumeration");
         qnValue = new QName(xmlns, "value");
+        qnAny = new QName(xmlns, "any");
 
         qnCompositeType = new QName(xmlns, "compositeType");
         qnElementType = new QName(xmlns, "elementType");
@@ -115,6 +142,9 @@ abstract class SchemaReaderBase implements SchemaReader {
     public Map<String, EDIType> readTypes() throws EDISchemaException {
         Map<String, EDIType> types = new HashMap<>(100);
 
+        types.put(StaEDISchema.ANY_ELEMENT_ID, ANY_ELEMENT);
+        types.put(StaEDISchema.ANY_COMPOSITE_ID, ANY_COMPOSITE);
+
         try {
             if (isInterchangeSchema(reader)) {
                 readInterchange(reader, types);
@@ -181,7 +211,7 @@ abstract class SchemaReaderBase implements SchemaReader {
         refs.add(headerRef);
 
         while (qnSegment.equals(element)) {
-            refs.add(readReference(reader, types));
+            addReferences(reader, EDIType.Type.SEGMENT, refs, readReference(reader, types));
             reader.nextTag(); // Advance to end element
             reader.nextTag(); // Advance to next start element
             element = reader.getName();
@@ -203,10 +233,10 @@ abstract class SchemaReaderBase implements SchemaReader {
         refs.add(trailerRef);
 
         StructureType interchange = new StructureType(qnInterchange.toString(),
-                                              EDIType.Type.INTERCHANGE,
-                                              "INTERCHANGE",
-                                              refs,
-                                              Collections.emptyList());
+                                                      EDIType.Type.INTERCHANGE,
+                                                      "INTERCHANGE",
+                                                      refs,
+                                                      Collections.emptyList());
 
         types.put(interchange.getId(), interchange);
     }
@@ -214,7 +244,8 @@ abstract class SchemaReaderBase implements SchemaReader {
     Reference readControlStructure(XMLStreamReader reader,
                                    QName element,
                                    QName subelement,
-                                   Map<String, EDIType> types) throws XMLStreamException {
+                                   Map<String, EDIType> types)
+            throws XMLStreamException {
         int minOccurs = 0;
         int maxOccurs = 99999;
         String use = parseAttribute(reader, "use", String::valueOf, "optional");
@@ -250,10 +281,10 @@ abstract class SchemaReaderBase implements SchemaReader {
         refs.add(trailerRef);
 
         StructureType struct = new StructureType(element.toString(),
-                                         complex.get(element),
-                                         complex.get(element).toString(),
-                                         refs,
-                                         Collections.emptyList());
+                                                 complex.get(element),
+                                                 complex.get(element).toString(),
+                                                 refs,
+                                                 Collections.emptyList());
 
         types.put(struct.getId(), struct);
 
@@ -326,8 +357,9 @@ abstract class SchemaReaderBase implements SchemaReader {
     }
 
     StructureType readComplexType(XMLStreamReader reader,
-                               QName complexType,
-                               Map<String, EDIType> types) throws XMLStreamException {
+                                  QName complexType,
+                                  Map<String, EDIType> types)
+            throws XMLStreamException {
 
         final EDIType.Type type = complex.get(complexType);
         final String id;
@@ -354,7 +386,7 @@ abstract class SchemaReaderBase implements SchemaReader {
 
         readDescription(reader);
         requireElementStart(qnSequence, reader);
-        readReferences(reader, types, refs);
+        readReferences(reader, types, type, refs);
 
         int event = reader.nextTag();
 
@@ -374,14 +406,15 @@ abstract class SchemaReaderBase implements SchemaReader {
     }
 
     void readReferences(XMLStreamReader reader,
-                       Map<String, EDIType> types,
-                       List<EDIReference> refs) {
+                        Map<String, EDIType> types,
+                        EDIType.Type parentType,
+                        List<EDIReference> refs) {
 
         try {
             while (reader.hasNext()) {
                 switch (reader.next()) {
                 case XMLStreamConstants.START_ELEMENT:
-                    refs.add(readReference(reader, types));
+                    addReferences(reader, parentType, refs, readReference(reader, types));
                     break;
 
                 case XMLStreamConstants.END_ELEMENT:
@@ -400,11 +433,42 @@ abstract class SchemaReaderBase implements SchemaReader {
         }
     }
 
+    void addReferences(XMLStreamReader reader, EDIType.Type parentType, List<EDIReference> refs, Reference reference) {
+        if ("ANY".equals(reference.getRefId())) {
+            final EDIReference optRef;
+            final EDIReference reqRef;
+
+            switch (parentType) {
+            case SEGMENT:
+                optRef = ANY_COMPOSITE_REF_OPT;
+                reqRef = ANY_COMPOSITE_REF_REQ;
+                break;
+            case COMPOSITE:
+                optRef = ANY_ELEMENT_REF_OPT;
+                reqRef = ANY_ELEMENT_REF_REQ;
+                break;
+            default:
+                throw schemaException("Element " + qnAny + " may only be present for segmentType and compositeType", reader);
+            }
+
+            final int min = reference.getMinOccurs();
+            final int max = reference.getMaxOccurs();
+
+            for (int i = 0; i < max; i++) {
+                refs.add(i < min ? reqRef : optRef);
+            }
+        } else {
+            refs.add(reference);
+        }
+    }
+
     Reference readReference(XMLStreamReader reader, Map<String, EDIType> types) {
         QName element = reader.getName();
         String refId = null;
 
-        if (references.contains(element)) {
+        if (qnAny.equals(element)) {
+            refId = "ANY";
+        } else if (references.contains(element)) {
             refId = readReferencedId(reader);
             Objects.requireNonNull(refId);
         } else if (qnLoop.equals(element)) {
diff --git a/src/main/java/io/xlate/edi/internal/schema/StaEDISchema.java b/src/main/java/io/xlate/edi/internal/schema/StaEDISchema.java
index 69a0ca5..b3765cc 100644
--- a/src/main/java/io/xlate/edi/internal/schema/StaEDISchema.java
+++ b/src/main/java/io/xlate/edi/internal/schema/StaEDISchema.java
@@ -25,7 +25,10 @@ import io.xlate.edi.schema.EDIType;
 import io.xlate.edi.schema.Schema;
 import io.xlate.edi.schema.implementation.LoopImplementation;
 
-class StaEDISchema implements Schema {
+public class StaEDISchema implements Schema {
+
+    public static final String ANY_ELEMENT_ID = "io.xlate.edi.internal.schema.ANY_ELEMENT";
+    public static final String ANY_COMPOSITE_ID = "io.xlate.edi.internal.schema.ANY_COMPOSITE";
 
     final String interchangeName;
     final String transactionStandardName;
diff --git a/src/main/java/io/xlate/edi/internal/stream/validation/Validator.java b/src/main/java/io/xlate/edi/internal/stream/validation/Validator.java
index 58daa70..bda8f35 100644
--- a/src/main/java/io/xlate/edi/internal/stream/validation/Validator.java
+++ b/src/main/java/io/xlate/edi/internal/stream/validation/Validator.java
@@ -23,6 +23,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.logging.Logger;
 
+import io.xlate.edi.internal.schema.StaEDISchema;
 import io.xlate.edi.internal.stream.StaEDIStreamLocation;
 import io.xlate.edi.internal.stream.tokenization.Dialect;
 import io.xlate.edi.internal.stream.tokenization.ElementDataHandler;
@@ -765,7 +766,7 @@ public class Validator {
     }
 
     public boolean isComposite() {
-        return composite != null;
+        return composite != null && !StaEDISchema.ANY_COMPOSITE_ID.equals(composite.getId());
     }
 
     public boolean validateElement(Dialect dialect, StaEDIStreamLocation position, CharSequence value) {
diff --git a/src/main/resources/schema/EDISchema-v3.xsd b/src/main/resources/schema/EDISchema-v3.xsd
index 9e37844..47c1273 100644
--- a/src/main/resources/schema/EDISchema-v3.xsd
+++ b/src/main/resources/schema/EDISchema-v3.xsd
@@ -77,6 +77,19 @@
     <attribute name="title" type="string" use="optional" />
   </complexType>
 
+  <complexType name="anyElementType">
+    <attribute name="minOccurs" type="nonNegativeInteger" default="0">
+      <annotation>
+        <documentation>The minimum number of times an undefined element may occur at the declared location in the EDI structure.</documentation>
+      </annotation>
+    </attribute>
+    <attribute name="maxOccurs" type="nonNegativeInteger" default="1">
+      <annotation>
+        <documentation>The maximum number of times an undefined element may occur at the declared location in the EDI structure.</documentation>
+      </annotation>
+    </attribute>
+  </complexType>
+
   <complexType name="controlType" abstract="true">
     <complexContent>
       <extension base="tns:baseType">
@@ -511,9 +524,18 @@
                 </documentation>
               </annotation>
               <complexType>
-                <sequence>
+                <choice minOccurs="1" maxOccurs="unbounded">
                   <element name="element" type="tns:elementStandard" minOccurs="1" maxOccurs="unbounded" />
-                </sequence>
+                  <element name="any" type="tns:anyElementType">
+                    <annotation>
+                      <documentation>
+                        May be used to declare that any component element may occur in this composite type up
+                        to the maximum number given by maxOccurs. The value of minOccurs defines the number of
+                        undefined component elements that MUST occur in the composite.
+                      </documentation>
+                    </annotation>
+                  </element>
+                </choice>
               </complexType>
             </element>
             <element ref="tns:syntax" minOccurs="0" maxOccurs="unbounded" />
@@ -543,6 +565,16 @@
                 <choice minOccurs="0" maxOccurs="unbounded">
                   <element name="element" type="tns:elementStandard" />
                   <element name="composite" type="tns:compositeStandard" />
+                  <element name="any" type="tns:anyElementType">
+                    <annotation>
+                      <documentation>
+                        May be used to declare that any element or composite may occur in this segment type up
+                        to the maximum number given by maxOccurs. The value of minOccurs defines the number of
+                        undefined elements that MUST occur in the segment at this position. Elements that repeat
+                        may occur up to 99 times and are not affected by the value of the maxOccurs attribute.
+                      </documentation>
+                    </annotation>
+                  </element>
                 </choice>
               </complexType>
             </element>


Test patch:
diff --git a/src/test/java/io/xlate/edi/internal/schema/StaEDISchemaFactoryTest.java b/src/test/java/io/xlate/edi/internal/schema/StaEDISchemaFactoryTest.java
index d218235..557104f 100644
--- a/src/test/java/io/xlate/edi/internal/schema/StaEDISchemaFactoryTest.java
+++ b/src/test/java/io/xlate/edi/internal/schema/StaEDISchemaFactoryTest.java
@@ -36,12 +36,12 @@ import io.xlate.edi.stream.EDIStreamConstants.Standards;
 @SuppressWarnings("resource")
 public class StaEDISchemaFactoryTest {
 
-    final String INTERCHANGE_V2 = "{http://xlate.io/EDISchema/v2}interchange";
-    final String TRANSACTION_V2 = "{http://xlate.io/EDISchema/v2}transaction";
+    final String INTERCHANGE_V2 = '{' + StaEDISchemaFactory.XMLNS_V2 + "}interchange";
+    final String TRANSACTION_V2 = '{' + StaEDISchemaFactory.XMLNS_V2 + "}transaction";
 
-    final String INTERCHANGE_V3 = "{http://xlate.io/EDISchema/v3}interchange";
-    final String STANDARD_V3 = "{http://xlate.io/EDISchema/v3}transaction";
-    final String IMPL_V3 = "{http://xlate.io/EDISchema/v3}implementation";
+    final String INTERCHANGE_V3 = '{' + StaEDISchemaFactory.XMLNS_V3 + "}interchange";
+    final String STANDARD_V3 = '{' + StaEDISchemaFactory.XMLNS_V3 + "}transaction";
+    final String IMPL_V3 = '{' + StaEDISchemaFactory.XMLNS_V3 + "}implementation";
 
     @Test
     public void testCreateSchemaByURL() throws EDISchemaException {
@@ -409,4 +409,86 @@ public class StaEDISchemaFactoryTest {
         assertEquals("Invalid segment name [sg1]",
                      thrown.getOriginalMessage());
     }
+
+    @Test
+    public void testAnyCompositeType() throws EDISchemaException {
+        SchemaFactory factory = SchemaFactory.newFactory();
+        InputStream stream = new ByteArrayInputStream((""
+                + "<schema xmlns='" + StaEDISchemaFactory.XMLNS_V3 + "'>"
+                + "<interchange header='SG1' trailer='SG2'>"
+                + "<sequence>"
+                + "  <group header='SG3' trailer='SG4'>"
+                + "    <transaction header='SG5' trailer='SG6'></transaction>"
+                + "  </group>"
+                + "</sequence>"
+                + "</interchange>"
+                + "<elementType name=\"E1\" base=\"string\" maxLength=\"5\" />"
+                + "<segmentType name=\"SG1\"><sequence>"
+                + "<element type='E1'/>"
+                + "<any minOccurs='0' maxOccurs='2'/>"
+                + "</sequence></segmentType>"
+                + "<segmentType name=\"SG2\"><sequence><element type='E1'/></sequence></segmentType>"
+                + "<segmentType name=\"SG3\"><sequence><element type='E1'/></sequence></segmentType>"
+                + "<segmentType name=\"SG4\"><sequence><element type='E1'/></sequence></segmentType>"
+                + "<segmentType name=\"SG5\"><sequence><element type='E1'/></sequence></segmentType>"
+                + "<segmentType name=\"SG6\"><sequence><element type='E1'/></sequence></segmentType>"
+                + "</schema>").getBytes());
+        Schema schema = factory.createSchema(stream);
+        EDIComplexType segmentSG1 = (EDIComplexType) schema.getType("SG1");
+        assertEquals(3, segmentSG1.getReferences().size());
+        // Two "ANY" references refer to the same object
+        assertTrue(segmentSG1.getReferences().get(1) == segmentSG1.getReferences().get(2));
+    }
+
+    @Test
+    public void testAnyElementType() throws EDISchemaException {
+        SchemaFactory factory = SchemaFactory.newFactory();
+        InputStream stream = new ByteArrayInputStream((""
+                + "<schema xmlns='" + StaEDISchemaFactory.XMLNS_V3 + "'>"
+                + "<interchange header='SG1' trailer='SG2'>"
+                + "<sequence>"
+                + "  <group header='SG3' trailer='SG4'>"
+                + "    <transaction header='SG5' trailer='SG6'></transaction>"
+                + "  </group>"
+                + "</sequence>"
+                + "</interchange>"
+                + "<elementType name=\"E1\" base=\"string\" maxLength=\"5\" />"
+                + "<compositeType name=\"C001\"><sequence><any maxOccurs='5'/></sequence></compositeType>"
+                + "<segmentType name=\"SG1\"><sequence><element type='E1'/></sequence></segmentType>"
+                + "<segmentType name=\"SG2\"><sequence><element type='E1'/></sequence></segmentType>"
+                + "<segmentType name=\"SG3\"><sequence><element type='E1'/></sequence></segmentType>"
+                + "<segmentType name=\"SG4\"><sequence><element type='E1'/></sequence></segmentType>"
+                + "<segmentType name=\"SG5\"><sequence><element type='E1'/></sequence></segmentType>"
+                + "<segmentType name=\"SG6\"><sequence><composite type='C001'/></sequence></segmentType>"
+                + "</schema>").getBytes());
+        Schema schema = factory.createSchema(stream);
+        EDIComplexType segmentSG6 = (EDIComplexType) schema.getType("SG6");
+        assertEquals(1, segmentSG6.getReferences().size());
+        // Two "ANY" references refer to the same object
+        assertEquals("C001", segmentSG6.getReferences().get(0).getReferencedType().getId());
+        assertEquals(5, ((EDIComplexType) segmentSG6.getReferences().get(0).getReferencedType()).getReferences().size());
+    }
+
+    @Test
+    public void testAnySegmentTypeInvalid() throws EDISchemaException {
+        SchemaFactory factory = SchemaFactory.newFactory();
+        InputStream stream = new ByteArrayInputStream((""
+                + "<schema xmlns='" + StaEDISchemaFactory.XMLNS_V3 + "'>"
+                + "<transaction header='SG1' trailer='SG2'>"
+                + "<sequence>"
+                + "  <segment type='SG3'/>"
+                + "  <any maxOccurs='2' />"
+                + "</sequence>"
+                + "</transaction>"
+                + "<elementType name=\"E1\" base=\"string\" maxLength=\"5\" />"
+                + "<segmentType name=\"SG1\"><sequence><element type='E1'/></sequence></segmentType>"
+                + "<segmentType name=\"SG2\"><sequence><element type='E1'/></sequence></segmentType>"
+                + "<segmentType name=\"SG3\"><sequence><element type='E1'/></sequence></segmentType>"
+                + "<segmentType name=\"SG4\"><sequence><element type='E1'/></sequence></segmentType>"
+                + "<segmentType name=\"SG5\"><sequence><element type='E1'/></sequence></segmentType>"
+                + "</schema>").getBytes());
+        EDISchemaException thrown = assertThrows(EDISchemaException.class, () -> factory.createSchema(stream));
+        assertEquals("Element {" + StaEDISchemaFactory.XMLNS_V3 + "}any may only be present for segmentType and compositeType",
+                     thrown.getOriginalMessage());
+    }
 }
diff --git a/src/test/java/io/xlate/edi/internal/stream/ErrorEventsTest.java b/src/test/java/io/xlate/edi/internal/stream/ErrorEventsTest.java
index cbdc9ce..fa3dbd1 100644
--- a/src/test/java/io/xlate/edi/internal/stream/ErrorEventsTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/ErrorEventsTest.java
@@ -373,4 +373,244 @@ public class ErrorEventsTest {
         assertTrue(reader.hasNext(), "Expected error missing");
         assertEquals(EDIStreamValidationError.TOO_MANY_DATA_ELEMENTS, reader.getErrorType());
     }
+
+    @Test
+    public void testExtraAnyElementAllowedInAK1() throws EDISchemaException, EDIStreamException {
+        EDIInputFactory factory = EDIInputFactory.newFactory();
+        InputStream stream = new ByteArrayInputStream((""
+                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *050812*1953*^*00501*508121953*0*P*:~"
+                + "GS*FA*ReceiverDept*SenderDept*20050812*195335*000005*X*005010X230~"
+                + "ST*997*0001~"
+                + "AK1*HC*000001**ANY1*ANY2~"
+                + "AK9*A*1*1*1~"
+                + "SE*4*0001~"
+                + "GE*1*000005~"
+                + "IEA*1*508121953~").getBytes());
+
+        EDIStreamReader reader = factory.createEDIStreamReader(stream);
+        reader = factory.createFilteredReader(reader, (r) -> {
+            switch (r.getEventType()) {
+            case SEGMENT_ERROR:
+            case ELEMENT_DATA_ERROR:
+            case ELEMENT_OCCURRENCE_ERROR:
+            case START_TRANSACTION: // To set the schema
+            case START_COMPOSITE: // To ensure no composites signaled for "any" elements
+                return true;
+            default:
+                break;
+            }
+            return false;
+        });
+
+        assertEquals(EDIStreamEvent.START_TRANSACTION, reader.next(), "Expecting start of transaction");
+        SchemaFactory schemaFactory = SchemaFactory.newFactory();
+        URL schemaLocation = getClass().getResource("/x12/EDISchema997_support_any_elements.xml");
+        Schema schema = schemaFactory.createSchema(schemaLocation);
+        reader.setTransactionSchema(schema);
+        assertFalse(reader.hasNext(), "Unexpected errors in transaction");
+    }
+
+    @Test
+    public void testTooManyExtraAnyElementAllowedInAK1() throws EDISchemaException, EDIStreamException {
+        EDIInputFactory factory = EDIInputFactory.newFactory();
+        InputStream stream = new ByteArrayInputStream((""
+                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *050812*1953*^*00501*508121953*0*P*:~"
+                + "GS*FA*ReceiverDept*SenderDept*20050812*195335*000005*X*005010X230~"
+                + "ST*997*0001~"
+                + "AK1*HC*000001**ANY1*ANY2*ANY3~"
+                + "AK9*A*1*1*1~"
+                + "SE*4*0001~"
+                + "GE*1*000005~"
+                + "IEA*1*508121953~").getBytes());
+
+        EDIStreamReader reader = factory.createEDIStreamReader(stream);
+        reader = factory.createFilteredReader(reader, (r) -> {
+            switch (r.getEventType()) {
+            case SEGMENT_ERROR:
+            case ELEMENT_DATA_ERROR:
+            case ELEMENT_OCCURRENCE_ERROR:
+            case START_TRANSACTION: // To set the schema
+            case START_COMPOSITE: // To ensure no composites signaled for "any" elements
+                return true;
+            default:
+                break;
+            }
+            return false;
+        });
+
+        assertEquals(EDIStreamEvent.START_TRANSACTION, reader.next(), "Expecting start of transaction");
+        SchemaFactory schemaFactory = SchemaFactory.newFactory();
+        URL schemaLocation = getClass().getResource("/x12/EDISchema997_support_any_elements.xml");
+        Schema schema = schemaFactory.createSchema(schemaLocation);
+        reader.setTransactionSchema(schema);
+
+        assertTrue(reader.hasNext(), "Expected error missing");
+        assertEquals(EDIStreamEvent.ELEMENT_OCCURRENCE_ERROR, reader.getEventType());
+        assertEquals(EDIStreamValidationError.TOO_MANY_DATA_ELEMENTS, reader.getErrorType());
+        assertEquals("AK1", reader.getLocation().getSegmentTag());
+        assertEquals(6, reader.getLocation().getElementPosition());
+        assertEquals(1, reader.getLocation().getElementOccurrence());
+        assertEquals(-1, reader.getLocation().getComponentPosition());
+    }
+
+    @Test
+    public void testNoExtraAnyElementInAK1() throws EDISchemaException, EDIStreamException {
+        EDIInputFactory factory = EDIInputFactory.newFactory();
+        InputStream stream = new ByteArrayInputStream((""
+                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *050812*1953*^*00501*508121953*0*P*:~"
+                + "GS*FA*ReceiverDept*SenderDept*20050812*195335*000005*X*005010X230~"
+                + "ST*997*0001~"
+                + "AK1*HC*000001~"
+                + "AK9*A*1*1*1~"
+                + "SE*4*0001~"
+                + "GE*1*000005~"
+                + "IEA*1*508121953~").getBytes());
+
+        EDIStreamReader reader = factory.createEDIStreamReader(stream);
+        reader = factory.createFilteredReader(reader, (r) -> {
+            switch (r.getEventType()) {
+            case SEGMENT_ERROR:
+            case ELEMENT_DATA_ERROR:
+            case ELEMENT_OCCURRENCE_ERROR:
+            case START_TRANSACTION: // To set the schema
+            case START_COMPOSITE: // To ensure no composites signaled for "any" elements
+                return true;
+            default:
+                break;
+            }
+            return false;
+        });
+
+        assertEquals(EDIStreamEvent.START_TRANSACTION, reader.next(), "Expecting start of transaction");
+        SchemaFactory schemaFactory = SchemaFactory.newFactory();
+        URL schemaLocation = getClass().getResource("/x12/EDISchema997_support_any_elements.xml");
+        Schema schema = schemaFactory.createSchema(schemaLocation);
+        reader.setTransactionSchema(schema);
+
+        assertFalse(reader.hasNext(), "Unexpected errors in transaction");
+    }
+
+    @Test
+    public void testCompositesSupportedInAnyElementInAK1() throws EDISchemaException, EDIStreamException {
+        EDIInputFactory factory = EDIInputFactory.newFactory();
+        InputStream stream = new ByteArrayInputStream((""
+                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *050812*1953*^*00501*508121953*0*P*:~"
+                + "GS*FA*ReceiverDept*SenderDept*20050812*195335*000005*X*005010X230~"
+                + "ST*997*0001~"
+                + "AK1*HC*000001**ANY1:ANY2~"
+                + "AK9*A*1*1*1~"
+                + "SE*4*0001~"
+                + "GE*1*000005~"
+                + "IEA*1*508121953~").getBytes());
+
+        EDIStreamReader reader = factory.createEDIStreamReader(stream);
+        reader = factory.createFilteredReader(reader, (r) -> {
+            switch (r.getEventType()) {
+            case SEGMENT_ERROR:
+            case ELEMENT_DATA_ERROR:
+            case ELEMENT_OCCURRENCE_ERROR:
+            case START_TRANSACTION: // To set the schema
+                return true;
+            default:
+                break;
+            }
+            return false;
+        });
+
+        assertEquals(EDIStreamEvent.START_TRANSACTION, reader.next(), "Expecting start of transaction");
+        SchemaFactory schemaFactory = SchemaFactory.newFactory();
+        URL schemaLocation = getClass().getResource("/x12/EDISchema997_support_any_elements.xml");
+        Schema schema = schemaFactory.createSchema(schemaLocation);
+        reader.setTransactionSchema(schema);
+
+        assertFalse(reader.hasNext(), "Unexpected errors in transaction");
+    }
+
+    @Test
+    public void testRequiredComponentInC030InAnyElementInAK4() throws EDISchemaException, EDIStreamException {
+        EDIInputFactory factory = EDIInputFactory.newFactory();
+        InputStream stream = new ByteArrayInputStream((""
+                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *050812*1953*^*00501*508121953*0*P*:~"
+                + "GS*FA*ReceiverDept*SenderDept*20050812*195335*000005*X*005010X230~"
+                + "ST*997*0001~"
+                + "AK1*HC*000001~"
+                + "AK2*837*0021~"
+                + "AK3*NM1*8**8~"
+                + "AK4*8:::ANYCOMPONENT*66*7*MI~"
+                + "AK5*R*5~"
+                + "AK9*R*1*1*0~"
+                + "SE*4*0001~"
+                + "GE*1*000005~"
+                + "IEA*1*508121953~").getBytes());
+
+        EDIStreamReader reader = factory.createEDIStreamReader(stream);
+        reader = factory.createFilteredReader(reader, (r) -> {
+            switch (r.getEventType()) {
+            case SEGMENT_ERROR:
+            case ELEMENT_DATA_ERROR:
+            case ELEMENT_OCCURRENCE_ERROR:
+            case START_TRANSACTION: // To set the schema
+                return true;
+            default:
+                break;
+            }
+            return false;
+        });
+
+        assertEquals(EDIStreamEvent.START_TRANSACTION, reader.next(), "Expecting start of transaction");
+        SchemaFactory schemaFactory = SchemaFactory.newFactory();
+        URL schemaLocation = getClass().getResource("/x12/EDISchema997_support_any_elements.xml");
+        Schema schema = schemaFactory.createSchema(schemaLocation);
+        reader.setTransactionSchema(schema);
+
+        assertFalse(reader.hasNext(), "Unexpected errors in transaction");
+    }
+
+    @Test
+    public void testMissingRequiredComponentInC030InAnyElementInAK4() throws EDISchemaException, EDIStreamException {
+        EDIInputFactory factory = EDIInputFactory.newFactory();
+        InputStream stream = new ByteArrayInputStream((""
+                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *050812*1953*^*00501*508121953*0*P*:~"
+                + "GS*FA*ReceiverDept*SenderDept*20050812*195335*000005*X*005010X230~"
+                + "ST*997*0001~"
+                + "AK1*HC*000001~"
+                + "AK2*837*0021~"
+                + "AK3*NM1*8**8~"
+                + "AK4*8:::*66*7*MI~"
+                + "AK5*R*5~"
+                + "AK9*R*1*1*0~"
+                + "SE*4*0001~"
+                + "GE*1*000005~"
+                + "IEA*1*508121953~").getBytes());
+
+        EDIStreamReader reader = factory.createEDIStreamReader(stream);
+        reader = factory.createFilteredReader(reader, (r) -> {
+            switch (r.getEventType()) {
+            case SEGMENT_ERROR:
+            case ELEMENT_DATA_ERROR:
+            case ELEMENT_OCCURRENCE_ERROR:
+            case START_TRANSACTION: // To set the schema
+                return true;
+            default:
+                break;
+            }
+            return false;
+        });
+
+        assertEquals(EDIStreamEvent.START_TRANSACTION, reader.next(), "Expecting start of transaction");
+        SchemaFactory schemaFactory = SchemaFactory.newFactory();
+        URL schemaLocation = getClass().getResource("/x12/EDISchema997_support_any_elements.xml");
+        Schema schema = schemaFactory.createSchema(schemaLocation);
+        reader.setTransactionSchema(schema);
+
+        assertTrue(reader.hasNext(), "Expected error missing");
+        assertEquals(EDIStreamEvent.ELEMENT_OCCURRENCE_ERROR, reader.getEventType());
+        assertEquals(EDIStreamValidationError.REQUIRED_DATA_ELEMENT_MISSING, reader.getErrorType());
+        assertEquals("AK4", reader.getLocation().getSegmentTag());
+        assertEquals(1, reader.getLocation().getElementPosition());
+        assertEquals(1, reader.getLocation().getElementOccurrence());
+        assertEquals(4, reader.getLocation().getComponentPosition());
+
+        assertFalse(reader.hasNext(), "Unexpected errors in transaction");
+    }
 }
diff --git a/src/test/resources/x12/EDISchema997_support_any_elements.xml b/src/test/resources/x12/EDISchema997_support_any_elements.xml
new file mode 100644
index 0000000..af7930e
--- /dev/null
+++ b/src/test/resources/x12/EDISchema997_support_any_elements.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+  Copyright 2020 xlate.io LLC, http://www.xlate.io
+
+  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.
+-->
+<schema xmlns="http://xlate.io/EDISchema/v3">
+  <transaction>
+    <sequence>
+      <segment type="AK1" minOccurs="1" />
+      <loop code="L2000" maxOccurs="999999">
+        <sequence>
+          <segment type="AK2" />
+          <loop code="L2100" maxOccurs="999999">
+            <sequence>
+              <segment type="AK3" />
+              <segment type="AK4" maxOccurs="99" />
+            </sequence>
+          </loop>
+          <segment type="AK5" minOccurs="1" />
+        </sequence>
+      </loop>
+      <segment type="AK9" minOccurs="1" />
+    </sequence>
+  </transaction>
+
+  <elementType name="E0002" number="2" base="numeric" maxLength="6" />
+  <elementType name="E0028" number="28" base="numeric" maxLength="9" />
+  <elementType name="E0097" number="97" base="numeric" maxLength="6" />
+  <elementType name="E0123" number="123" base="numeric" maxLength="6" />
+  <elementType name="E0143" number="143" base="string" minLength="3" maxLength="3"/>
+  <elementType name="E0329" number="329" base="string" minLength="4" maxLength="9" />
+  <elementType name="E0447" number="447" base="string" maxLength="4" />
+  <elementType name="E0479" number="479" base="string" minLength="2" maxLength="2"/>
+  <elementType name="E0480" number="480" base="string" maxLength="12" />
+  <elementType name="E0715" number="715" base="string"/>
+  <elementType name="E0716" number="716" base="string" maxLength="3"/>
+  <elementType name="E0717" number="717" base="string"/>
+  <elementType name="E0718" number="718" base="string" maxLength="3"/>
+  <elementType name="E0719" number="719" base="numeric" maxLength="10" />
+  <elementType name="E0720" number="720" base="string" maxLength="3"/>
+  <elementType name="E0721" number="721" base="string" minLength="2" maxLength="3" />
+  <elementType name="E0722" number="722" base="numeric" maxLength="2" />
+  <elementType name="E0723" number="723" base="string" maxLength="3"/>
+  <elementType name="E0724" number="724" base="string" maxLength="99" />
+  <elementType name="E0725" number="725" base="numeric" maxLength="4" />
+  <elementType name="E1528" number="1528" base="numeric" maxLength="2" />
+  <elementType name="E1686" number="1686" base="numeric" maxLength="4" />
+  <elementType name="E1705" number="1705" base="string" maxLength="35" />
+  <compositeType name="C030">
+    <sequence>
+      <element type="E0722" minOccurs="1" />
+      <element type="E1528" />
+      <element type="E1686" />
+      <any minOccurs="1" maxOccurs="2" />
+    </sequence>
+  </compositeType>
+  <segmentType name="AK1">
+    <sequence>
+      <element type="E0479" minOccurs="1" />
+      <element type="E0028" minOccurs="1" />
+      <element type="E0480" />
+      <any minOccurs="0" maxOccurs="2" />
+    </sequence>
+  </segmentType>
+  <segmentType name="AK2">
+    <sequence>
+      <element type="E0143" minOccurs="1" />
+      <element type="E0329" minOccurs="1" />
+      <element type="E1705" />
+    </sequence>
+  </segmentType>
+  <segmentType name="AK3">
+    <sequence>
+      <element type="E0721" minOccurs="1" />
+      <element type="E0719" minOccurs="1" />
+      <element type="E0447" />
+      <element type="E0720" />
+    </sequence>
+  </segmentType>
+  <segmentType name="AK4">
+    <sequence>
+      <composite type="C030" minOccurs="1" />
+      <element type="E0725" />
+      <element type="E0723" minOccurs="1" />
+      <element type="E0724" />
+    </sequence>
+  </segmentType>
+  <segmentType name="AK5">
+    <sequence>
+      <element type="E0717" minOccurs="1" />
+      <element type="E0718" />
+      <element type="E0718" />
+      <element type="E0718" />
+      <element type="E0718" />
+      <element type="E0718" />
+    </sequence>
+  </segmentType>
+  <segmentType name="AK9">
+    <sequence>
+      <element type="E0715" minOccurs="1" />
+      <element type="E0097" minOccurs="1" />
+      <element type="E0123" minOccurs="1" />
+      <element type="E0002" minOccurs="1" />
+      <element type="E0716" />
+      <element type="E0716" />
+      <element type="E0716" />
+      <element type="E0716" />
+      <element type="E0716" />
+    </sequence>
+  </segmentType>
+</schema>