Repository: xlate/staedi
Pull request #132 (2020-10-10 13:43:42)

Add `scale` attribute to schema element type (non-validating)


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 a0c3d2c..22ae394 100644
--- a/src/main/java/io/xlate/edi/internal/schema/ElementType.java
+++ b/src/main/java/io/xlate/edi/internal/schema/ElementType.java
@@ -33,6 +33,7 @@ class ElementType extends BasicType implements EDISimpleType {
     private static final String TOSTRING_FORMAT = "id: %s, type: %s, base: %s, code: %s, minLength: %d, maxLength: %d, values: %s";
 
     final Base base;
+    final int scale;
     final String code;
     final int number;
     final long minLength;
@@ -65,9 +66,10 @@ class ElementType extends BasicType implements EDISimpleType {
         }
     }
 
-    ElementType(String id, Base base, 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, Set<String> values, List<Version> versions, String title, String description) {
         super(id, Type.ELEMENT, title, description);
         this.base = base;
+        this.scale = scale;
         this.code = code;
         this.number = number;
         this.minLength = minLength;
@@ -96,6 +98,16 @@ class ElementType extends BasicType implements EDISimpleType {
         return base;
     }
 
+    @Override
+    public Integer getScale() {
+        if (scale > -1) {
+            return Integer.valueOf(scale);
+        }
+
+        // Use the default value otherwise
+        return EDISimpleType.super.getScale();
+    }
+
     @Override
     public String getCode() {
         return code;
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 bcf8a41..1294b64 100644
--- a/src/main/java/io/xlate/edi/internal/schema/SchemaReaderBase.java
+++ b/src/main/java/io/xlate/edi/internal/schema/SchemaReaderBase.java
@@ -50,6 +50,7 @@ abstract class SchemaReaderBase implements SchemaReader {
 
     static final EDISimpleType ANY_ELEMENT = new ElementType(StaEDISchema.ANY_ELEMENT_ID,
                                                              Base.STRING,
+                                                             -1,
                                                              "ANY",
                                                              0,
                                                              0,
@@ -100,7 +101,7 @@ abstract class SchemaReaderBase implements SchemaReader {
     protected XMLStreamReader reader;
     protected Map<String, Object> properties;
 
-    public SchemaReaderBase(String xmlns, XMLStreamReader reader, Map<String, Object> properties) {
+    protected SchemaReaderBase(String xmlns, XMLStreamReader reader, Map<String, Object> properties) {
         this.xmlns = xmlns;
         qnSchema = new QName(xmlns, "schema");
         qnInclude = new QName(xmlns, "include");
@@ -587,6 +588,7 @@ abstract class SchemaReaderBase implements SchemaReader {
         String name = parseAttribute(reader, "name", String::valueOf);
         String code = parseAttribute(reader, "code", String::valueOf, name);
         Base base = parseAttribute(reader, "base", val -> Base.valueOf(val.toUpperCase()), Base.STRING);
+        int scale = (Base.NUMERIC == base) ? parseAttribute(reader, "scale", Integer::parseInt, 0) : -1;
         int number = parseAttribute(reader, "number", Integer::parseInt, -1);
         long minLength = parseAttribute(reader, "minLength", Long::parseLong, 1L);
         long maxLength = parseAttribute(reader, "maxLength", Long::parseLong, 1L);
@@ -619,7 +621,7 @@ abstract class SchemaReaderBase implements SchemaReader {
             }
         }
 
-        return new ElementType(name, base, code, number, minLength, maxLength, values, versions, title, descr);
+        return new ElementType(name, base, scale, code, number, minLength, maxLength, values, versions, title, descr);
     }
 
     ElementType.Version readSimpleTypeVersion(XMLStreamReader reader) {
diff --git a/src/main/java/io/xlate/edi/schema/EDISimpleType.java b/src/main/java/io/xlate/edi/schema/EDISimpleType.java
index 5c9de75..4594136 100644
--- a/src/main/java/io/xlate/edi/schema/EDISimpleType.java
+++ b/src/main/java/io/xlate/edi/schema/EDISimpleType.java
@@ -34,6 +34,20 @@ public interface EDISimpleType extends EDIType {
 
     Base getBase();
 
+    /**
+     * Returns the <i>scale</i> of this {@code EDISimpleType} when the
+     * <i>base</i> is <i>NUMERIC</i>. The scale is the number of digits to the
+     * right of an implied decimal point.
+     *
+     * @return the scale of this {@code EDISimpleType} if <i>NUMERIC</i>,
+     *         otherwise null.
+     *
+     * @since 1.13
+     */
+    default Integer getScale() {
+        return null;
+    }
+
     /**
      * Returns true if this element has additional version(s) defined beyond the
      * default. Versions may be used to specify different minimum/maximum length
diff --git a/src/main/resources/schema/EDISchema-v4.xsd b/src/main/resources/schema/EDISchema-v4.xsd
index 988f30d..f7d7a91 100644
--- a/src/main/resources/schema/EDISchema-v4.xsd
+++ b/src/main/resources/schema/EDISchema-v4.xsd
@@ -677,6 +677,15 @@
             </annotation>
           </attribute>
 
+          <attribute name="scale" use="optional" type="nonNegativeInteger" default="0">
+            <annotation>
+              <documentation>
+                For numeric base types only, scale is the number of digits to the right of an implied decimal point. When not specified, the
+                default value of zero is used (i.e. the data type is integer)
+              </documentation>
+            </annotation>
+          </attribute>
+
           <attributeGroup ref="tns:lengthAttributeGroup"/>
         </extension>
       </complexContent>


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 7b1a9ee..76101d1 100644
--- a/src/test/java/io/xlate/edi/internal/schema/ElementTypeTest.java
+++ b/src/test/java/io/xlate/edi/internal/schema/ElementTypeTest.java
@@ -30,13 +30,13 @@ class ElementTypeTest {
 
     @Test
     void testElementContructorNoValues() {
-        ElementType e = new ElementType("E1", Base.STRING, "1", 1, 0L, 5L, Collections.emptySet(), Collections.emptyList(), null, null);
+        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());
     }
 
     @Test
     void testElementContructorWithValues() {
-        ElementType e = new ElementType("E1", Base.STRING, "1", 1, 0L, 5L, new HashSet<>(Arrays.asList("ABCDE", "FGHIJ")), Collections.emptyList(), null, null);
+        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());
     }
 
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 9993b0f..7b11de4 100644
--- a/src/test/java/io/xlate/edi/internal/schema/StaEDISchemaFactoryTest.java
+++ b/src/test/java/io/xlate/edi/internal/schema/StaEDISchemaFactoryTest.java
@@ -706,4 +706,15 @@ class StaEDISchemaFactoryTest {
         }
         assertTrue(root instanceof IOException);
     }
+
+    @Test
+    void testControlNumbersHaveZeroScaleByDefault() throws EDISchemaException {
+        SchemaFactory factory = SchemaFactory.newFactory();
+        Schema schema = factory.getControlSchema(Standards.X12, new String[] { "00501" });
+        assertNotNull(schema);
+        // ISA13 = N0
+        assertEquals(Integer.valueOf(0), ((EDISimpleType) ((EDIComplexType) schema.getType("ISA")).getReferences().get(12).getReferencedType()).getScale());
+        // GS06 = N0
+        assertEquals(Integer.valueOf(0), ((EDISimpleType) ((EDIComplexType) schema.getType("GS")).getReferences().get(5).getReferencedType()).getScale());
+    }
 }
diff --git a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java
index 9f55f1d..3c35dfd 100644
--- a/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java
+++ b/src/test/java/io/xlate/edi/internal/stream/StaEDIStreamReaderTest.java
@@ -27,6 +27,8 @@ import static org.junit.jupiter.api.Assertions.fail;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -50,8 +52,10 @@ 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.EDIType;
 import io.xlate.edi.schema.Schema;
 import io.xlate.edi.schema.SchemaFactory;
+import io.xlate.edi.schema.EDIType.Type;
 import io.xlate.edi.stream.EDIInputFactory;
 import io.xlate.edi.stream.EDIStreamConstants.Delimiters;
 import io.xlate.edi.stream.EDIStreamEvent;
@@ -1794,4 +1798,69 @@ class StaEDIStreamReaderTest implements ConstantsTest {
         assertEquals(0, unexpected.size());
     }
 
+    @Test
+    void testDecimalScaleAvailableFromSchema() throws EDISchemaException, IOException {
+        EDIInputFactory factory = EDIInputFactory.newFactory();
+        Schema transSchema = SchemaFactory.newFactory().createSchema(getClass().getResourceAsStream("/x12/EDISchema810.xml"));
+        EDIStreamReader reader = factory.createEDIStreamReader(getClass().getResourceAsStream("/x12/invoice810_po850_dual.edi"));
+        boolean startTx = false;
+        BigDecimal tds01 = null;
+        boolean inTransaction = false;
+
+        try {
+            while (reader.hasNext()) {
+                switch (reader.next()) {
+                case START_TRANSACTION:
+                    inTransaction = true;
+                    startTx = true;
+                    break;
+                case END_TRANSACTION:
+                    inTransaction = false;
+                    break;
+                case ELEMENT_DATA:
+                    Location l = reader.getLocation();
+
+                    if (startTx && l.getElementPosition() == 1) {
+                        if ("810".equals(reader.getText())) {
+                            reader.setTransactionSchema(transSchema);
+                        } else {
+                            reader.setTransactionSchema(null);
+                        }
+                    } else if ("TDS".equals(l.getSegmentTag()) && l.getElementPosition() == 1) {
+                        BigInteger unscaled = new BigInteger(reader.getText());
+                        EDIType tds01Type = reader.getSchemaTypeReference().getReferencedType();
+                        tds01 = new BigDecimal(unscaled, ((EDISimpleType) tds01Type).getScale());
+                    } else if (inTransaction && !reader.getText().isEmpty()) {
+                        // Only check for unexpected scaled numerics within the transaction
+                        EDIReference ref = reader.getSchemaTypeReference();
+                        if (ref != null && ref.getReferencedType().isType(Type.ELEMENT)) {
+                            if (((EDISimpleType) ref.getReferencedType()).getScale() != null) {
+                                // SE01 is an expected N0 element
+                                if (!"SE".equals(l.getSegmentTag()) || l.getElementPosition() != 1) {
+                                    fail("Unexpected non-null scale " + l);
+                                }
+                            }
+                        }
+                    }
+                    break;
+                case END_SEGMENT:
+                    startTx = false;
+                    break;
+                case SEGMENT_ERROR:
+                case ELEMENT_OCCURRENCE_ERROR:
+                case ELEMENT_DATA_ERROR:
+                    fail("Unexpected error condition: " + reader.getErrorType() + " " + reader.getLocation());
+                    break;
+                default:
+                    break;
+                }
+            }
+        } catch (Exception e) {
+            fail("Unexpected exception: " + e.getMessage() + " " + reader.getLocation(), e);
+        } finally {
+            reader.close();
+        }
+
+        assertEquals(new BigDecimal("2554.38"), tds01);
+    }
 }
diff --git a/src/test/resources/x12/EDISchema810.xml b/src/test/resources/x12/EDISchema810.xml
index cc4dfe0..6836cb0 100644
--- a/src/test/resources/x12/EDISchema810.xml
+++ b/src/test/resources/x12/EDISchema810.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<schema xmlns="http://xlate.io/EDISchema/v3">
+<schema xmlns="http://xlate.io/EDISchema/v4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xlate.io/EDISchema/v4 https://www.xlate.io/EDISchema/EDISchema-v4.xsd">
     <!-- Version 004010 -->
     <transaction>
         <sequence>
@@ -331,9 +331,9 @@
     <elementType name="ITD05" base="decimal" maxLength="3"/>
     <elementType name="ITD06" base="date" minLength="8" maxLength="8"/>
     <elementType name="ITD07" base="decimal" maxLength="3"/>
-    <elementType name="ITD08" base="numeric" maxLength="10"/>
+    <elementType name="ITD08" base="numeric" scale="2" maxLength="10"/>
     <elementType name="ITD09" base="date" minLength="8" maxLength="8"/>
-    <elementType name="ITD10" base="numeric" maxLength="10"/>
+    <elementType name="ITD10" base="numeric" scale="2" maxLength="10"/>
     <elementType name="ITD11" base="decimal" maxLength="5"/>
     <elementType name="ITD12" base="string" maxLength="80"/>
     <elementType name="ITD13" base="decimal" maxLength="2"/>
@@ -539,7 +539,7 @@
     <elementType name="SAC02" base="string" minLength="4" maxLength="4"/>
     <elementType name="SAC03" base="string" minLength="2" maxLength="2"/>
     <elementType name="SAC04" base="string" maxLength="10"/>
-    <elementType name="SAC05" base="numeric" maxLength="15"/>
+    <elementType name="SAC05" base="numeric" scale="2" maxLength="15"/>
     <elementType name="SAC06" base="string"/>
     <elementType name="SAC07" base="decimal" maxLength="6"/>
     <elementType name="SAC08" base="decimal" maxLength="9"/>
@@ -629,10 +629,10 @@
     <elementType name="SR11" base="string" maxLength="48"/>
     <elementType name="TC201" base="string"/>
     <elementType name="TC202" base="string" maxLength="30"/>
-    <elementType name="TDS01" base="numeric" maxLength="15"/>
-    <elementType name="TDS02" base="numeric" maxLength="15"/>
-    <elementType name="TDS03" base="numeric" maxLength="15"/>
-    <elementType name="TDS04" base="numeric" maxLength="15"/>
+    <elementType name="TDS01" base="numeric" scale="2" maxLength="15"/>
+    <elementType name="TDS02" base="numeric" scale="2" maxLength="15"/>
+    <elementType name="TDS03" base="numeric" scale="2" maxLength="15"/>
+    <elementType name="TDS04" base="numeric" scale="2" maxLength="15"/>
     <elementType name="TXI01" base="string" minLength="2" maxLength="2"/>
     <elementType name="TXI02" base="decimal" maxLength="18"/>
     <elementType name="TXI03" base="decimal" maxLength="10"/>
diff --git a/src/test/resources/x12/EDISchema850.xml b/src/test/resources/x12/EDISchema850.xml
index 6e75f24..21414eb 100644
--- a/src/test/resources/x12/EDISchema850.xml
+++ b/src/test/resources/x12/EDISchema850.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<schema xmlns="http://xlate.io/EDISchema/v3">
+<schema xmlns="http://xlate.io/EDISchema/v4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xlate.io/EDISchema/v4 https://www.xlate.io/EDISchema/EDISchema-v4.xsd">
     <!-- Version 003040 -->
     <transaction>
         <sequence>
@@ -165,7 +165,7 @@
     <elementType name="BEG08" base="string" minLength="3" maxLength="3"/>
     <elementType name="CSH01" base="string" maxLength="2"/>
     <elementType name="CSH02" base="string"/>
-    <elementType name="CSH03" base="numeric" minLength="2" maxLength="9"/>
+    <elementType name="CSH03" base="numeric" scale="2" minLength="2" maxLength="9"/>
     <elementType name="CSH04" base="string" maxLength="35"/>
     <elementType name="CSH05" base="date" minLength="6" maxLength="6"/>
     <elementType name="CSH06" base="string" minLength="2" maxLength="2"/>
@@ -177,7 +177,7 @@
     <elementType name="CTB03" base="string" minLength="2" maxLength="2"/>
     <elementType name="CTB04" base="decimal" maxLength="15"/>
     <elementType name="CTB05" base="string" maxLength="2"/>
-    <elementType name="CTB06" base="numeric" maxLength="15"/>
+    <elementType name="CTB06" base="numeric" scale="2" maxLength="15"/>
     <elementType name="CTP01" base="string" minLength="2" maxLength="2"/>
     <elementType name="CTP02" base="string" minLength="3" maxLength="3"/>
     <elementType name="CTP03" base="decimal" maxLength="14"/>
@@ -249,7 +249,7 @@
     <elementType name="IT305" base="string" minLength="2" maxLength="2"/>
     <elementType name="IT801" base="string" maxLength="2"/>
     <elementType name="IT802" base="string"/>
-    <elementType name="IT803" base="numeric" minLength="2" maxLength="9"/>
+    <elementType name="IT803" base="numeric" scale="2" minLength="2" maxLength="9"/>
     <elementType name="IT804" base="string" maxLength="35"/>
     <elementType name="IT805" base="date" minLength="6" maxLength="6"/>
     <elementType name="IT806" base="string" minLength="2" maxLength="2"/>
@@ -281,9 +281,9 @@
     <elementType name="ITD05" base="decimal" maxLength="3"/>
     <elementType name="ITD06" base="date" minLength="6" maxLength="6"/>
     <elementType name="ITD07" base="decimal" maxLength="3"/>
-    <elementType name="ITD08" base="numeric" maxLength="10"/>
+    <elementType name="ITD08" base="numeric" scale="2" maxLength="10"/>
     <elementType name="ITD09" base="date" minLength="6" maxLength="6"/>
-    <elementType name="ITD10" base="numeric" maxLength="10"/>
+    <elementType name="ITD10" base="numeric" scale="2" maxLength="10"/>
     <elementType name="ITD11" base="decimal" maxLength="5"/>
     <elementType name="ITD12" base="string" maxLength="80"/>
     <elementType name="ITD13" base="decimal" maxLength="2"/>
@@ -467,7 +467,7 @@
     <elementType name="SAC02" base="string" minLength="4" maxLength="4"/>
     <elementType name="SAC03" base="string" minLength="2" maxLength="2"/>
     <elementType name="SAC04" base="string" maxLength="10"/>
-    <elementType name="SAC05" base="numeric" maxLength="15"/>
+    <elementType name="SAC05" base="numeric" scale="2" maxLength="15"/>
     <elementType name="SAC06" base="string"/>
     <elementType name="SAC07" base="decimal" maxLength="6"/>
     <elementType name="SAC08" base="decimal" maxLength="9"/>