Repository: xlate/staedi (java)

Issue:
Add support for obtaining enumeration title values from schema for the element_data
Add a way to obtain title value from schema while reading(for the) ELEMENT_DATA.
So that we don't need to put every possible definition programmatically in code and map it, but just read it from schema instead.
For e.g. If we have value 24 for NM1 segment, element 108 in EDI, I would like to get "Employer's Identification Number" title in my program:
```
<loop code="2100A" type="2100">
                        <sequence>
                            <segment type="NM1" minOccurs="1" maxOccurs="1">
                                <sequence>
                                    <element position="1"/>
                                    <element position="2"/>
                                    <element position="3"/>
                                    <element position="4"/>
                                    <element position="5"/>
                                    <element position="6"/>
                                    <element position="7"/>
                                    <element position="8">
                                        <enumeration>
                                            <value title="Employer's Identification Number">24</value>
                                            <value title="Electronic Transmitter Identification Number (ETIN)">46</value>
                                            <value title="Federal Taxpayer's Identification Number">FI</value>
                                            <value title="National Association of Insurance Commissioners (NAIC) Identification">NI</value>
                                            <value title="Payor Identification">PI</value>
                                            <value title="Centers for Medicare and Medicaid Services PlanID">XV</value>
                                            <value title="Centers for Medicare and Medicaid Services National Provider Identifier">XX</value>
                                        </enumeration>
                                    </element>
                                </sequence>
                            </segment>
                            <segment type="PER" maxOccurs="3"/>
                            <segment type="AAA" maxOccurs="9"/>
                        </sequence>
                    </loop>

Pull request:
Store and provide access to the titles of enumerated element values
Closes #302

Patch:
diff --git a/src/main/java/io/xlate/edi/internal/schema/ElementType.java b/src/main/java/io/xlate/edi/internal/schema/ElementType.java
index 22ae394..0e1a415 100644
--- a/src/main/java/io/xlate/edi/internal/schema/ElementType.java
+++ b/src/main/java/io/xlate/edi/internal/schema/ElementType.java
@@ -17,9 +17,9 @@ package io.xlate.edi.internal.schema;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Set;
+import java.util.Map;
 import java.util.function.BiFunction;
 import java.util.function.Supplier;
 
@@ -38,15 +38,15 @@ class ElementType extends BasicType implements EDISimpleType {
     final int number;
     final long minLength;
     final long maxLength;
-    final Set<String> values;
+    final Map<String, String> values;
     final List<Version> versions;
 
     static class Version extends VersionedProperty {
         final Long minLength;
         final Long maxLength;
-        final Set<String> values;
+        final Map<String, String> values;
 
-        Version(String minVersion, String maxVersion, Long minLength, Long maxLength, Set<String> values) {
+        Version(String minVersion, String maxVersion, Long minLength, Long maxLength, Map<String, String> values) {
             super(minVersion, maxVersion);
             this.minLength = minLength;
             this.maxLength = maxLength;
@@ -61,12 +61,12 @@ class ElementType extends BasicType implements EDISimpleType {
             return maxLength != null ? maxLength.longValue() : defaultElement.getMaxLength();
         }
 
-        public Set<String> getValueSet(ElementType defaultElement) {
-            return values != null ? values : defaultElement.getValueSet();
+        public Map<String, String> getValues(ElementType defaultElement) {
+            return values != null ? values : defaultElement.getValues();
         }
     }
 
-    ElementType(String id, Base base, int scale, String code, int number, long minLength, long maxLength, Set<String> values, List<Version> versions, String title, String description) {
+    ElementType(String id, Base base, int scale, String code, int number, long minLength, long maxLength, Map<String, String> values, List<Version> versions, String title, String description) {
         super(id, Type.ELEMENT, title, description);
         this.base = base;
         this.scale = scale;
@@ -74,7 +74,7 @@ class ElementType extends BasicType implements EDISimpleType {
         this.number = number;
         this.minLength = minLength;
         this.maxLength = maxLength;
-        this.values = Collections.unmodifiableSet(new LinkedHashSet<>(values));
+        this.values = Collections.unmodifiableMap(new LinkedHashMap<>(values));
         this.versions = Collections.unmodifiableList(new ArrayList<>(versions));
     }
 
@@ -150,12 +150,12 @@ class ElementType extends BasicType implements EDISimpleType {
     }
 
     @Override
-    public Set<String> getValueSet() {
+    public Map<String, String> getValues() {
         return values;
     }
 
     @Override
-    public Set<String> getValueSet(String version) {
-        return getVersionAttribute(version, Version::getValueSet, this::getValueSet);
+    public Map<String, String> getValues(String version) {
+        return getVersionAttribute(version, Version::getValues, this::getValues);
     }
 }
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 44bddb9..88daba9 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.Objects.requireNonNull;
 import static java.util.stream.Collectors.toList;
 
 import java.math.BigDecimal;
@@ -10,13 +11,14 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedHashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.function.Function;
+import java.util.function.Supplier;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.stream.IntStream;
@@ -69,7 +71,7 @@ abstract class SchemaReaderBase implements SchemaReader {
                                                              0,
                                                              0,
                                                              99_999,
-                                                             Collections.emptySet(),
+                                                             Collections.emptyMap(),
                                                              Collections.emptyList(),
                                                              null,
                                                              null);
@@ -666,19 +668,19 @@ abstract class SchemaReaderBase implements SchemaReader {
         String title = parseAttribute(reader, ATTR_TITLE, String::valueOf, null);
         String descr = readDescription(reader);
 
-        final Set<String> values;
+        final Map<String, String> values;
         final List<ElementType.Version> versions;
 
         // Reader was advanced by `readDescription`, check the current state to proceed.
         if (reader.getEventType() == XMLStreamConstants.END_ELEMENT) {
-            values = Collections.emptySet();
+            values = Collections.emptyMap();
             versions = Collections.emptyList();
         } else {
             if (qnEnumeration.equals(reader.getName())) {
                 values = readEnumerationValues(reader);
                 nextTag(reader, "reading after elementType enumeration");
             } else {
-                values = Collections.emptySet();
+                values = Collections.emptyMap();
             }
 
             if (qnVersion.equals(reader.getName())) {
@@ -702,7 +704,7 @@ abstract class SchemaReaderBase implements SchemaReader {
         Long minLength = parseAttribute(reader, "minLength", Long::valueOf, null);
         Long maxLength = parseAttribute(reader, "maxLength", Long::valueOf, null);
 
-        Set<String> values;
+        Map<String, String> values;
 
         if (nextTag(reader, "reading elementType version contents") == XMLStreamConstants.END_ELEMENT) {
             // Set to null instead of empty to indicate that no enumeration is present in this version
@@ -717,8 +719,8 @@ abstract class SchemaReaderBase implements SchemaReader {
         return new ElementType.Version(minVersion, maxVersion, minLength, maxLength, values);
     }
 
-    Set<String> readEnumerationValues(XMLStreamReader reader) {
-        Set<String> values = null;
+    Map<String, String> readEnumerationValues(XMLStreamReader reader) {
+        Map<String, String> values = null;
         boolean endOfEnumeration = false;
 
         while (!endOfEnumeration) {
@@ -736,15 +738,16 @@ abstract class SchemaReaderBase implements SchemaReader {
             }
         }
 
-        return values != null ? values : Collections.emptySet();
+        return requireNonNullElseGet(values, Collections::emptyMap);
     }
 
-    Set<String> readEnumerationValue(XMLStreamReader reader, Set<String> values) {
+    Map<String, String> readEnumerationValue(XMLStreamReader reader, Map<String, String> values) {
         if (values == null) {
-            values = new LinkedHashSet<>();
+            values = new LinkedHashMap<>();
         }
 
-        values.add(getElementText(reader, "enumeration value"));
+        String title = parseAttribute(reader, ATTR_TITLE, String::valueOf, null);
+        values.put(getElementText(reader, "enumeration value"), title);
 
         return values;
     }
@@ -888,6 +891,14 @@ abstract class SchemaReaderBase implements SchemaReader {
         return new ElementPosition(elementPosition, componentPosition);
     }
 
+    /**
+     * Replace with Objects#requireNonNullElseGet when migrating to Java 11.
+     */
+    static <T> T requireNonNullElseGet(T obj, Supplier<? extends T> supplier) {
+        return (obj != null) ? obj
+                : requireNonNull(requireNonNull(supplier, "supplier").get(), "supplier.get()");
+    }
+
     protected abstract String readReferencedId(XMLStreamReader reader);
 
     protected abstract void readInclude(XMLStreamReader reader, Map<String, EDIType> types) throws EDISchemaException;
diff --git a/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV3.java b/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV3.java
index 6520d94..3c2387c 100644
--- a/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV3.java
+++ b/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV3.java
@@ -54,14 +54,14 @@ class SchemaReaderV3 extends SchemaReaderBase implements SchemaReader {
     final Deque<EDITypeImplementation> implementedTypes = new LinkedList<>();
 
     static class ValueSet {
-        Set<String> value;
+        Map<String, String> value;
 
-        void set(Set<String> value) {
+        void set(Map<String, String> value) {
             this.value = value;
         }
 
-        Set<String> get() {
-            return this.value != null ? this.value : Collections.emptySet();
+        Map<String, String> get() {
+            return requireNonNullElseGet(value, Collections::emptyMap);
         }
 
         void clear() {
diff --git a/src/main/java/io/xlate/edi/internal/schema/implementation/ElementImpl.java b/src/main/java/io/xlate/edi/internal/schema/implementation/ElementImpl.java
index 1175d43..1be114a 100644
--- a/src/main/java/io/xlate/edi/internal/schema/implementation/ElementImpl.java
+++ b/src/main/java/io/xlate/edi/internal/schema/implementation/ElementImpl.java
@@ -1,7 +1,9 @@
 package io.xlate.edi.internal.schema.implementation;
 
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 
 import io.xlate.edi.schema.EDIReference;
 import io.xlate.edi.schema.EDISimpleType;
@@ -11,13 +13,13 @@ public class ElementImpl extends BaseImpl<EDISimpleType> implements ElementImple
 
     private static final String TOSTRING_FORMAT = "typeId: %s, minOccurs: %d, maxOccurs: %d, position: %d, values: %s, standard: { %s }";
     private final int position;
-    private final Set<String> values;
+    private final Map<String, String> values;
 
     public ElementImpl(int minOccurs,
             int maxOccurs,
             String typeId,
             int position,
-            Set<String> values,
+            Map<String, String> values,
             String title,
             String description) {
         super(title, description);
@@ -25,7 +27,7 @@ public class ElementImpl extends BaseImpl<EDISimpleType> implements ElementImple
         super.maxOccurs = maxOccurs;
         super.typeId = typeId;
         this.position = position;
-        this.values = values;
+        this.values = Collections.unmodifiableMap(new LinkedHashMap<>(values));
     }
 
     public ElementImpl(EDIReference standardReference, int position) {
@@ -33,7 +35,7 @@ public class ElementImpl extends BaseImpl<EDISimpleType> implements ElementImple
         this.setStandardReference(standardReference);
         this.typeId = standard.getId();
         this.position = position;
-        this.values = standard.getValueSet();
+        this.values = standard.getValues();
     }
 
     @Override
@@ -54,7 +56,7 @@ public class ElementImpl extends BaseImpl<EDISimpleType> implements ElementImple
     }
 
     @Override
-    public Set<String> getValueSet() {
+    public Map<String, String> getValues() {
         return values;
     }
 
diff --git a/src/main/java/io/xlate/edi/schema/EDISimpleType.java b/src/main/java/io/xlate/edi/schema/EDISimpleType.java
index cc1ff7f..abac150 100644
--- a/src/main/java/io/xlate/edi/schema/EDISimpleType.java
+++ b/src/main/java/io/xlate/edi/schema/EDISimpleType.java
@@ -15,6 +15,7 @@
  ******************************************************************************/
 package io.xlate.edi.schema;
 
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -135,7 +136,9 @@ public interface EDISimpleType extends EDIType {
      *
      * @return the set of enumerated values allowed for this element
      */
-    Set<String> getValueSet();
+    default Set<String> getValueSet() {
+        return getValues().keySet();
+    }
 
     /**
      * Retrieve the set of enumerated values allowed for this element for a
@@ -146,12 +149,40 @@ public interface EDISimpleType extends EDIType {
      *
      * @param version
      *            the version to select
-     * @return the set of enumerated values allowed for this element for version
+     * @return the set of enumerated values allowed for this element for the
+     *         given version
      *
      * @since 1.8
      */
     default Set<String> getValueSet(String version) {
-        return getValueSet();
+        return getValues(version).keySet();
     }
 
+    /**
+     * Retrieve the mapping of enumerated values allowed for this element with
+     * the associated title values.
+     *
+     * @return the map of enumerated values and titles allowed for this element
+     *
+     * @since 1.22
+     */
+    Map<String, String> getValues();
+
+    /**
+     * Retrieve the mapping of enumerated values allowed for this element with
+     * the associated title values for a particular version of the element.
+     *
+     * The default implementation returns the default (un-versioned) values for
+     * the element.
+     *
+     * @param version
+     *            the version to select
+     * @return the map of enumerated values and titles allowed for this element
+     *         for the given version
+     *
+     * @since 1.22
+     */
+    default Map<String, String> getValues(String version) {
+        return getValues();
+    }
 }
diff --git a/src/main/java/io/xlate/edi/schema/implementation/ElementImplementation.java b/src/main/java/io/xlate/edi/schema/implementation/ElementImplementation.java
index 19c9095..08901ba 100644
--- a/src/main/java/io/xlate/edi/schema/implementation/ElementImplementation.java
+++ b/src/main/java/io/xlate/edi/schema/implementation/ElementImplementation.java
@@ -15,15 +15,10 @@
  ******************************************************************************/
 package io.xlate.edi.schema.implementation;
 
-import java.util.Set;
-
 import io.xlate.edi.schema.EDISimpleType;
 
 public interface ElementImplementation extends EDITypeImplementation, EDISimpleType {
 
-    @Override
-    Set<String> getValueSet();
-
     @Override
     default Type getType() {
         return Type.ELEMENT;


Test patch:
diff --git a/src/test/java/io/xlate/edi/internal/schema/ElementTypeTest.java b/src/test/java/io/xlate/edi/internal/schema/ElementTypeTest.java
index 9436ea0..6f2f381 100644
--- a/src/test/java/io/xlate/edi/internal/schema/ElementTypeTest.java
+++ b/src/test/java/io/xlate/edi/internal/schema/ElementTypeTest.java
@@ -6,10 +6,10 @@ import static org.junit.jupiter.api.Assertions.assertNull;
 
 import java.io.ByteArrayInputStream;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.junit.jupiter.api.Test;
 
@@ -30,14 +30,17 @@ class ElementTypeTest {
 
     @Test
     void testElementContructorNoValues() {
-        ElementType e = new ElementType("E1", Base.STRING, -1, "1", 1, 0L, 5L, Collections.emptySet(), Collections.emptyList(), null, null);
-        assertEquals("id: E1, type: ELEMENT, base: STRING, code: 1, minLength: 0, maxLength: 5, values: []", e.toString());
+        ElementType e = new ElementType("E1", Base.STRING, -1, "1", 1, 0L, 5L, Collections.emptyMap(), Collections.emptyList(), null, null);
+        assertEquals("id: E1, type: ELEMENT, base: STRING, code: 1, minLength: 0, maxLength: 5, values: {}", e.toString());
     }
 
     @Test
     void testElementContructorWithValues() {
-        ElementType e = new ElementType("E1", Base.STRING, -1, "1", 1, 0L, 5L, new HashSet<>(Arrays.asList("ABCDE", "FGHIJ")), Collections.emptyList(), null, null);
-        assertEquals("id: E1, type: ELEMENT, base: STRING, code: 1, minLength: 0, maxLength: 5, values: [ABCDE, FGHIJ]", e.toString());
+        Map<String, String> values = new HashMap<>();
+        values.put("ABCDE", "Title 1");
+        values.put("FGHIJ", "Title 2");
+        ElementType e = new ElementType("E1", Base.STRING, -1, "1", 1, 0L, 5L, values, Collections.emptyList(), null, null);
+        assertEquals("id: E1, type: ELEMENT, base: STRING, code: 1, minLength: 0, maxLength: 5, values: {ABCDE=Title 1, FGHIJ=Title 2}", e.toString());
     }
 
     @SuppressWarnings("deprecation")
diff --git a/src/test/java/io/xlate/edi/internal/schema/implementation/ElementImplTest.java b/src/test/java/io/xlate/edi/internal/schema/implementation/ElementImplTest.java
index 4dcc0ca..ff59eb1 100644
--- a/src/test/java/io/xlate/edi/internal/schema/implementation/ElementImplTest.java
+++ b/src/test/java/io/xlate/edi/internal/schema/implementation/ElementImplTest.java
@@ -10,6 +10,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.junit.jupiter.api.Test;
@@ -32,7 +33,7 @@ class ElementImplTest {
     final int DFLT_MAX_OCCURS = 9;
     final String DFLT_ID = "E0001";
     final int DFLT_POSITION = 1;
-    final Set<String> DFLT_VALUES = Collections.emptySet();
+    final Map<String, String> DFLT_VALUES = Collections.emptyMap();
     final String DFLT_TITLE = "Test Element";
     final String DFLT_DESCR = "Just for testing";
 
@@ -78,8 +79,8 @@ class ElementImplTest {
 
     @Test
     void testEquals_DifferentValueSet() {
-        Set<String> v1 = Collections.singleton("A");
-        Set<String> v2 = Collections.singleton("B");
+        Map<String, String> v1 = Collections.singletonMap("A", "Title A");
+        Map<String, String> v2 = Collections.singletonMap("B", "Title B");
         ElementImpl e1 = new ElementImpl(DFLT_MIN_OCCURS, DFLT_MAX_OCCURS, DFLT_ID, DFLT_POSITION, v1, DFLT_TITLE, DFLT_DESCR);
         ElementImpl e2 = new ElementImpl(DFLT_MIN_OCCURS, DFLT_MAX_OCCURS, DFLT_ID, DFLT_POSITION, v2, DFLT_TITLE, DFLT_DESCR);
         assertNotEquals(e1, e2);
@@ -144,6 +145,11 @@ class ElementImplTest {
                 return Collections.emptySet();
             }
 
+            @Override
+            public Map<String, String> getValues() {
+                return Collections.emptyMap();
+            }
+
             @Override
             public String getTitle() {
                 return null;
diff --git a/src/test/java/io/xlate/edi/internal/schema/implementation/SegmentImplTest.java b/src/test/java/io/xlate/edi/internal/schema/implementation/SegmentImplTest.java
index fad81f0..184fb0e 100644
--- a/src/test/java/io/xlate/edi/internal/schema/implementation/SegmentImplTest.java
+++ b/src/test/java/io/xlate/edi/internal/schema/implementation/SegmentImplTest.java
@@ -4,13 +4,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import java.io.ByteArrayInputStream;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import org.junit.jupiter.api.Test;
 
 import io.xlate.edi.schema.EDIReference;
 import io.xlate.edi.schema.EDISchemaException;
 import io.xlate.edi.schema.SchemaFactory;
+import io.xlate.edi.schema.implementation.ElementImplementation;
+import io.xlate.edi.schema.implementation.SegmentImplementation;
 import io.xlate.edi.stream.EDIInputFactory;
 import io.xlate.edi.stream.EDIStreamException;
 import io.xlate.edi.stream.EDIStreamReader;
@@ -64,11 +68,18 @@ class SegmentImplTest {
         assertEquals(2, nm1references.size());
         // Values set in 837_loop1000_only.xml
         assertEquals("Submitter Name", nm1references.get(0).getTitle());
+        assertEquals(Collections.singletonMap("41", "Submitter"), getElementValues(nm1references.get(0), 0));
         assertEquals("Receiver Name", nm1references.get(1).getTitle());
+        assertEquals(Collections.singletonMap("40", "Receiver"), getElementValues(nm1references.get(1), 0));
 
         // Values of NM1 standard set in 837.xml
         assertEquals("Individual or Organizational Name", nm1references.get(0).getReferencedType().getTitle());
         assertEquals("Individual or Organizational Name", nm1references.get(1).getReferencedType().getTitle());
     }
 
+    Map<String, String> getElementValues(EDIReference segRef, int position) {
+        SegmentImplementation impl = (SegmentImplementation) segRef;
+        ElementImplementation ele = (ElementImplementation) impl.getSequence().get(position);
+        return ele.getValues();
+    }
 }
diff --git a/src/test/java/io/xlate/edi/internal/stream/validation/AlphaNumericValidatorTest.java b/src/test/java/io/xlate/edi/internal/stream/validation/AlphaNumericValidatorTest.java
index 147a89f..aecff41 100644
--- a/src/test/java/io/xlate/edi/internal/stream/validation/AlphaNumericValidatorTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/validation/AlphaNumericValidatorTest.java
@@ -69,10 +69,11 @@ class AlphaNumericValidatorTest implements ValueSetTester {
         when(element.getMinLength(anyString())).thenCallRealMethod();
         when(element.getMaxLength(anyString())).thenCallRealMethod();
         when(element.getValueSet(anyString())).thenCallRealMethod();
+        when(element.getValues(anyString())).thenCallRealMethod();
 
         when(element.getMinLength()).thenReturn(4L);
         when(element.getMaxLength()).thenReturn(5L);
-        when(element.getValueSet()).thenReturn(setOf("VAL1", "VAL2"));
+        when(element.getValues()).thenReturn(mapOf("VAL1", "Title1", "VAL2", "Title2"));
         ElementValidator v = AlphaNumericValidator.getInstance();
         List<EDIStreamValidationError> errors = new ArrayList<>();
         v.validate(dialect, element, "TEST", errors);
@@ -118,10 +119,11 @@ class AlphaNumericValidatorTest implements ValueSetTester {
         when(element.getMinLength(anyString())).thenCallRealMethod();
         when(element.getMaxLength(anyString())).thenCallRealMethod();
         when(element.getValueSet(anyString())).thenCallRealMethod();
+        when(element.getValues(anyString())).thenCallRealMethod();
 
         when(element.getMinLength()).thenReturn(4L);
         when(element.getMaxLength()).thenReturn(8L);
-        when(element.getValueSet()).thenReturn(setOf("VAL1", "VAL2"));
+        when(element.getValues()).thenReturn(mapOf("VAL1", "Title1", "VAL2", "Title2"));
         ElementValidator v = AlphaNumericValidator.getInstance();
         StringBuilder output = new StringBuilder();
         v.format(dialect, element, "TESTTEST", output);
diff --git a/src/test/java/io/xlate/edi/internal/stream/validation/ValueSetTester.java b/src/test/java/io/xlate/edi/internal/stream/validation/ValueSetTester.java
index af003a8..0e3d81f 100644
--- a/src/test/java/io/xlate/edi/internal/stream/validation/ValueSetTester.java
+++ b/src/test/java/io/xlate/edi/internal/stream/validation/ValueSetTester.java
@@ -5,8 +5,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import io.xlate.edi.internal.stream.tokenization.Dialect;
@@ -23,6 +25,14 @@ interface ValueSetTester {
         return new HashSet<>(Arrays.asList(values));
     }
 
+    default Map<String, String> mapOf(String... values) {
+        Map<String, String> map = new HashMap<>(values.length / 2);
+        for (int i = 0; i < values.length; i+=2) {
+            map.put(values[i], values[i + 1]);
+        }
+        return map;
+    }
+
     default void assertHasError(ElementValidator v, Dialect dialect, EDISimpleType element, CharSequence value, EDIStreamValidationError expected) {
         List<EDIStreamValidationError> errors = new ArrayList<>();
         v.validate(dialect, element, value, errors);
diff --git a/src/test/resources/x12/005010X222/837_loop1000_only.xml b/src/test/resources/x12/005010X222/837_loop1000_only.xml
index 8a28455..f2b8561 100644
--- a/src/test/resources/x12/005010X222/837_loop1000_only.xml
+++ b/src/test/resources/x12/005010X222/837_loop1000_only.xml
@@ -13,7 +13,7 @@
             <sequence>
               <element position="1" title="Entity Identifier Code (Submitter)">
                 <enumeration>
-                  <value>41</value>
+                  <value title="Submitter">41</value>
                 </enumeration>
               </element>
               <element position="2"/>
@@ -34,7 +34,7 @@
             <sequence>
               <element position="1" title="Entity Identifier Code (Receiver)">
                 <enumeration>
-                  <value>40</value>
+                  <value title="Receiver">40</value>
                 </enumeration>
               </element>
               <element position="2"/>