Repository: xlate/staedi (java)

Issue:
Allow SchemaFactory property for context of relative schemaLocation URLs
Add property to allow resolution of relative `schemaLocation` URLs given in schema `include` elements.

`SchemaFactory.SCHEMA_LOCATION_URL_CONTEXT`

Pull request:
Add SchemaFactory.SCHEMA_LOCATION_URL_CONTEXT property
Fixes #40 

Allows for the relative URLs (besides relative to working directory) to be used in the `schemaLocation` attribute of the `include` element.

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 43cba2b..bd972ac 100644
--- a/src/main/java/io/xlate/edi/internal/schema/SchemaReaderBase.java
+++ b/src/main/java/io/xlate/edi/internal/schema/SchemaReaderBase.java
@@ -90,8 +90,9 @@ abstract class SchemaReaderBase implements SchemaReader {
     final Set<QName> references;
 
     protected XMLStreamReader reader;
+    protected Map<String, Object> properties;
 
-    public SchemaReaderBase(String xmlns, XMLStreamReader reader) {
+    public SchemaReaderBase(String xmlns, XMLStreamReader reader, Map<String, Object> properties) {
         this.xmlns = xmlns;
         qnSchema = new QName(xmlns, "schema");
         qnInclude = new QName(xmlns, "include");
@@ -134,6 +135,7 @@ abstract class SchemaReaderBase implements SchemaReader {
         references.add(qnElement);
 
         this.reader = reader;
+        this.properties = properties;
     }
 
     @Override
diff --git a/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV2.java b/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV2.java
index 77e0297..efd9607 100644
--- a/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV2.java
+++ b/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV2.java
@@ -11,8 +11,8 @@ import io.xlate.edi.schema.EDIType;
 
 class SchemaReaderV2 extends SchemaReaderBase implements SchemaReader {
 
-    public SchemaReaderV2(XMLStreamReader reader) {
-        super(StaEDISchemaFactory.XMLNS_V2, reader);
+    public SchemaReaderV2(XMLStreamReader reader, Map<String, Object> properties) {
+        super(StaEDISchemaFactory.XMLNS_V2, reader, properties);
     }
 
     @Override
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 e4bf6ce..950bfed 100644
--- a/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV3.java
+++ b/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV3.java
@@ -73,13 +73,13 @@ class SchemaReaderV3 extends SchemaReaderBase implements SchemaReader {
 
     final ValueSet valueSet = new ValueSet();
 
-    protected SchemaReaderV3(String xmlns, XMLStreamReader reader) {
-        super(xmlns, reader);
+    protected SchemaReaderV3(String xmlns, XMLStreamReader reader, Map<String, Object> properties) {
+        super(xmlns, reader, properties);
         qnImplementation = new QName(xmlns, "implementation");
     }
 
-    public SchemaReaderV3(XMLStreamReader reader) {
-        this(StaEDISchemaFactory.XMLNS_V3, reader);
+    public SchemaReaderV3(XMLStreamReader reader, Map<String, Object> properties) {
+        this(StaEDISchemaFactory.XMLNS_V3, reader, properties);
     }
 
     @Override
diff --git a/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV4.java b/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV4.java
index f9a2184..495a6f9 100644
--- a/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV4.java
+++ b/src/main/java/io/xlate/edi/internal/schema/SchemaReaderV4.java
@@ -9,11 +9,12 @@ import javax.xml.stream.XMLStreamReader;
 
 import io.xlate.edi.schema.EDISchemaException;
 import io.xlate.edi.schema.EDIType;
+import io.xlate.edi.schema.SchemaFactory;
 
 public class SchemaReaderV4 extends SchemaReaderV3 {
 
-    public SchemaReaderV4(XMLStreamReader reader) {
-        super(StaEDISchemaFactory.XMLNS_V4, reader);
+    public SchemaReaderV4(XMLStreamReader reader, Map<String, Object> properties) {
+        super(StaEDISchemaFactory.XMLNS_V4, reader, properties);
     }
 
     @Override
@@ -24,9 +25,21 @@ public class SchemaReaderV4 extends SchemaReaderV3 {
     @Override
     protected void readInclude(XMLStreamReader reader, Map<String, EDIType> types) throws EDISchemaException {
         String location = parseAttribute(reader, "schemaLocation", String::valueOf);
+        URL context = null;
 
         try {
-            types.putAll(StaEDISchemaFactory.readSchemaTypes(new URL(location)));
+            if (properties.containsKey(SchemaFactory.SCHEMA_LOCATION_URL_CONTEXT)) {
+                Object ctx = properties.get(SchemaFactory.SCHEMA_LOCATION_URL_CONTEXT);
+
+                if (ctx instanceof URL) {
+                    context = (URL) ctx;
+                } else {
+                    context = new URL(String.valueOf(ctx));
+                }
+            }
+
+            URL schemaLocation = context != null ? new URL(context, location) : new URL(location);
+            types.putAll(StaEDISchemaFactory.readSchemaTypes(schemaLocation, super.properties));
             reader.nextTag(); // End of include
         } catch (Exception e) {
             throw schemaException("Exception reading included schema", reader, e);
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 d501de4..21585e9 100644
--- a/src/main/java/io/xlate/edi/internal/schema/StaEDISchema.java
+++ b/src/main/java/io/xlate/edi/internal/schema/StaEDISchema.java
@@ -18,6 +18,8 @@ package io.xlate.edi.internal.schema;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 
 import io.xlate.edi.schema.EDIComplexType;
 import io.xlate.edi.schema.EDISchemaException;
@@ -38,6 +40,8 @@ public class StaEDISchema implements Schema {
     public static final String ANY_ELEMENT_ID = ID_PREFIX + "ANY_ELEMENT";
     public static final String ANY_COMPOSITE_ID = ID_PREFIX + "ANY_COMPOSITE";
 
+    private volatile Integer hash = null;
+
     final String interchangeName;
     final String transactionStandardName;
     final String implementationName;
@@ -56,6 +60,35 @@ public class StaEDISchema implements Schema {
         this(interchangeName, transactionStandardName, null);
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof Schema) {
+            Schema other = (Schema) o;
+
+            // Count the differences of each entry
+            return StreamSupport.stream(spliterator(), false)
+                                .filter(type -> {
+                                    final EDIType otherType = other.getType(type.getId());
+                                    return !type.equals(otherType);
+                                })
+                                .count() == 0;
+        }
+
+        return false;
+    }
+
+    @Override
+    public synchronized int hashCode() {
+        Integer localHash = hash;
+
+        if (localHash == null) {
+            localHash = hash = StreamSupport.stream(spliterator(), false)
+                                            .collect(Collectors.summingInt(EDIType::hashCode));
+        }
+
+        return localHash.intValue();
+    }
+
     @Override
     public EDIComplexType getMainLoop() {
         return getStandard();
diff --git a/src/main/java/io/xlate/edi/internal/schema/StaEDISchemaFactory.java b/src/main/java/io/xlate/edi/internal/schema/StaEDISchemaFactory.java
index 2f0ecce..7b077d3 100644
--- a/src/main/java/io/xlate/edi/internal/schema/StaEDISchemaFactory.java
+++ b/src/main/java/io/xlate/edi/internal/schema/StaEDISchemaFactory.java
@@ -22,7 +22,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
+import java.util.function.BiFunction;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -47,24 +47,22 @@ public class StaEDISchemaFactory implements SchemaFactory {
     static final String XMLNS_V3 = "http://xlate.io/EDISchema/v3";
     static final String XMLNS_V4 = "http://xlate.io/EDISchema/v4";
 
-    static final Map<QName, Function<XMLStreamReader, SchemaReader>> readerFactories = new HashMap<>(3);
+    static final Map<QName, BiFunction<XMLStreamReader, Map<String, Object>, SchemaReader>> readerFactories = new HashMap<>(3);
+    static final Set<String> supportedProperties = new HashSet<>();
 
     static {
         readerFactories.put(new QName(XMLNS_V2, SCHEMA_TAG), SchemaReaderV2::new);
         readerFactories.put(new QName(XMLNS_V3, SCHEMA_TAG), SchemaReaderV3::new);
         readerFactories.put(new QName(XMLNS_V4, SCHEMA_TAG), SchemaReaderV4::new);
-    }
-
-    private final Set<String> supportedProperties;
 
-    public StaEDISchemaFactory() {
-        // Create a `properties` HashMap when supported properties are added
-        supportedProperties = new HashSet<>();
+        supportedProperties.add(SCHEMA_LOCATION_URL_CONTEXT);
     }
 
+    private final Map<String, Object> properties = new HashMap<>();
+
     @Override
     public Schema createSchema(InputStream stream) throws EDISchemaException {
-        Map<String, EDIType> types = readSchemaTypes(stream);
+        Map<String, EDIType> types = readSchemaTypes(stream, properties);
 
         StaEDISchema schema = new StaEDISchema(StaEDISchema.INTERCHANGE_ID,
                                                StaEDISchema.TRANSACTION_ID,
@@ -100,35 +98,45 @@ public class StaEDISchemaFactory implements SchemaFactory {
 
     @Override
     public Object getProperty(String name) {
-        // When a supported property is added, only get if `isPropertySupported` returns true
-        throw new IllegalArgumentException("Unsupported property: " + name);
+        if (isPropertySupported(name)) {
+            return properties.get(name);
+        } else {
+            throw new IllegalArgumentException("Unsupported property: " + name);
+        }
     }
 
     @Override
     public void setProperty(String name, Object value) {
-        // When a supported property is added, only set if `isPropertySupported` returns true
-        throw new IllegalArgumentException("Unsupported property: " + name);
+        if (isPropertySupported(name)) {
+            if (value != null) {
+                properties.put(name, value);
+            } else {
+                properties.remove(name);
+            }
+        } else {
+            throw new IllegalArgumentException("Unsupported property: " + name);
+        }
     }
 
-    static Map<String, EDIType> readSchemaTypes(URL location) throws EDISchemaException {
+    static Map<String, EDIType> readSchemaTypes(URL location, Map<String, Object> properties) throws EDISchemaException {
         LOGGER.fine(() -> "Reading schema from URL: " + location);
 
         try (InputStream stream = location.openStream()) {
-            return readSchemaTypes(stream);
+            return readSchemaTypes(stream, properties);
         } catch (IOException e) {
             throw new EDISchemaException("Unable to read URL stream", e);
         }
     }
 
-    static Map<String, EDIType> readSchemaTypes(InputStream stream) throws EDISchemaException {
+    static Map<String, EDIType> readSchemaTypes(InputStream stream, Map<String, Object> properties) throws EDISchemaException {
         try {
-            return readSchema(stream).readTypes();
+            return getReader(stream, properties).readTypes();
         } catch (StaEDISchemaReadException e) {
             throw wrapped(e);
         }
     }
 
-    private static SchemaReader readSchema(InputStream stream) throws EDISchemaException {
+    private static SchemaReader getReader(InputStream stream, Map<String, Object> properties) throws EDISchemaException {
         QName schemaElement;
 
         try {
@@ -139,7 +147,7 @@ public class StaEDISchemaFactory implements SchemaFactory {
             schemaElement = reader.getName();
 
             if (readerFactories.containsKey(schemaElement)) {
-                return readerFactories.get(schemaElement).apply(reader);
+                return readerFactories.get(schemaElement).apply(reader, properties);
             }
 
             throw unexpectedElement(schemaElement, reader);
diff --git a/src/main/java/io/xlate/edi/schema/SchemaFactory.java b/src/main/java/io/xlate/edi/schema/SchemaFactory.java
index c0f54f3..c58f170 100644
--- a/src/main/java/io/xlate/edi/schema/SchemaFactory.java
+++ b/src/main/java/io/xlate/edi/schema/SchemaFactory.java
@@ -18,8 +18,19 @@ package io.xlate.edi.schema;
 import java.io.InputStream;
 import java.net.URL;
 
+@SuppressWarnings("java:S1214") // Allow constant string value to be used in this interface
 public interface SchemaFactory {
 
+    /**
+     * Property key for a URL (<code>java.lang.String</code> or
+     * <code>java.net.URL</code>) to be used with relative file URLs in
+     * <code>&lt;include schemaLocation="..."&gt;</code> element/attributes
+     * processed by a SchemaFactory. Besides a java.net.URL, any class with a
+     * toString method that returns a valid URL-formated String may be used as
+     * the value for this property.
+     */
+    public static final String SCHEMA_LOCATION_URL_CONTEXT = "io.xlate.edi.schema.SCHEMA_LOCATION_URL_CONTEXT";
+
     /**
      * Create a new instance of the factory. This static method creates a new
      * factory instance.
@@ -38,13 +49,16 @@ public interface SchemaFactory {
     public abstract Schema createSchema(InputStream stream) throws EDISchemaException;
 
     /**
-     * Retrieve the control schema for the provided standard and version. This method
-     * loads an internal, immutable schema provided by StAEDI.
+     * Retrieve the control schema for the provided standard and version. This
+     * method loads an internal, immutable schema provided by StAEDI.
      *
-     * @param standard the standard, e.g. X12 or EDIFACT
-     * @param version the version of the standard
+     * @param standard
+     *            the standard, e.g. X12 or EDIFACT
+     * @param version
+     *            the version of the standard
      * @return the control schema corresponding to the standard and version
-     * @throws EDISchemaException when the schema can not be loaded.
+     * @throws EDISchemaException
+     *             when the schema can not be loaded.
      *
      * @since 1.5
      */


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 955600a..a634daa 100644
--- a/src/test/java/io/xlate/edi/internal/schema/StaEDISchemaFactoryTest.java
+++ b/src/test/java/io/xlate/edi/internal/schema/StaEDISchemaFactoryTest.java
@@ -16,6 +16,7 @@
 package io.xlate.edi.internal.schema;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
@@ -23,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.ByteArrayInputStream;
+import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -83,13 +85,28 @@ class StaEDISchemaFactoryTest {
     }
 
     @Test
-    void testIsPropertyUnsupported() {
+    void testIsPropertySupportedTrue() {
+        SchemaFactory factory = SchemaFactory.newFactory();
+        assertTrue(factory.isPropertySupported(SchemaFactory.SCHEMA_LOCATION_URL_CONTEXT));
+    }
+
+    @Test
+    void testIsPropertySupportedFalse() {
         SchemaFactory factory = SchemaFactory.newFactory();
         assertTrue(!factory.isPropertySupported("FOO"), "FOO *is* supported");
     }
 
     @Test
-    void testGetProperty() {
+    void testPropertySupported() throws MalformedURLException {
+        SchemaFactory factory = SchemaFactory.newFactory();
+        URL workDir = new File(System.getProperty("user.dir")).toURI().toURL();
+        // Set to current working directory
+        factory.setProperty(SchemaFactory.SCHEMA_LOCATION_URL_CONTEXT, new File("").toURI().toURL());
+        assertEquals(workDir, factory.getProperty(SchemaFactory.SCHEMA_LOCATION_URL_CONTEXT));
+    }
+
+    @Test
+    void testGetPropertyUnsupported() {
         SchemaFactory factory = SchemaFactory.newFactory();
         assertThrows(IllegalArgumentException.class, () -> factory.getProperty("FOO"));
     }
@@ -196,6 +213,34 @@ class StaEDISchemaFactoryTest {
         assertTrue(missingV3.isEmpty(), () -> "V4 schema contains types not in V3: " + missingV3);
     }
 
+    @SuppressWarnings("deprecation")
+    @Test
+    void testCreateSchemaByStreamV4_with_include_relative_equals_V3Schema() throws Exception {
+        SchemaFactory factory = SchemaFactory.newFactory();
+
+        // Reading URL as String
+        factory.setProperty(SchemaFactory.SCHEMA_LOCATION_URL_CONTEXT, new File("src/test/resources/x12/").toURI().toString());
+        Schema schema1 = factory.createSchema(getClass().getResourceAsStream("/x12/IG-999-standard-included-relative.xml"));
+
+        // Reading URL
+        factory.setProperty(SchemaFactory.SCHEMA_LOCATION_URL_CONTEXT, new File("src/test/resources/x12/").toURI().toURL());
+        Schema schema2 = factory.createSchema(getClass().getResourceAsStream("/x12/IG-999-standard-included-relative.xml"));
+
+        factory.setProperty(SchemaFactory.SCHEMA_LOCATION_URL_CONTEXT, null);
+        Schema schema3 = factory.createSchema(getClass().getResourceAsStream("/x12/IG-999.xml"));
+
+        assertNotEquals(schema1, factory);
+        assertNotEquals(factory, schema1);
+
+        assertEquals(schema3, schema1);
+        assertEquals(schema1, schema2);
+
+        assertEquals(schema1.hashCode(), schema2.hashCode());
+        assertEquals(schema2.hashCode(), schema3.hashCode());
+
+        assertEquals(schema3.getMainLoop(), schema1.getStandard());
+    }
+
     @Test
     void testDuplicateImplPositionSpecified() {
         SchemaFactory factory = SchemaFactory.newFactory();
diff --git a/src/test/java/io/xlate/edi/internal/schema/StaEDISchemaTest.java b/src/test/java/io/xlate/edi/internal/schema/StaEDISchemaTest.java
index 548537e..33d1890 100644
--- a/src/test/java/io/xlate/edi/internal/schema/StaEDISchemaTest.java
+++ b/src/test/java/io/xlate/edi/internal/schema/StaEDISchemaTest.java
@@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.io.InputStream;
+import java.util.Collections;
 import java.util.Map;
 
 import javax.xml.stream.FactoryConfigurationError;
@@ -48,7 +49,7 @@ class StaEDISchemaTest {
         InputStream schemaStream = getClass().getResourceAsStream("/X12/v00200.xml");
         XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(schemaStream);
         reader.nextTag(); // Pass by <schema> element
-        Map<String, EDIType> types = new SchemaReaderV4(reader).readTypes();
+        Map<String, EDIType> types = new SchemaReaderV4(reader, Collections.emptyMap()).readTypes();
         schema.setTypes(types);
 
         assertEquals(EDIType.Type.INTERCHANGE, schema.getType(StaEDISchema.INTERCHANGE_ID).getType());
@@ -60,7 +61,7 @@ class StaEDISchemaTest {
         InputStream schemaStream = getClass().getResourceAsStream("/X12/v00402.xml");
         XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(schemaStream);
         reader.nextTag(); // Pass by <schema> element
-        Map<String, EDIType> types = new SchemaReaderV4(reader).readTypes();
+        Map<String, EDIType> types = new SchemaReaderV4(reader, Collections.emptyMap()).readTypes();
         schema.setTypes(types);
 
         assertEquals(EDIType.Type.INTERCHANGE, schema.getType(StaEDISchema.INTERCHANGE_ID).getType());
@@ -72,7 +73,7 @@ class StaEDISchemaTest {
         InputStream schemaStream = getClass().getResourceAsStream("/EDIFACT/CONTRL-v4r02.xml");
         XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(schemaStream);
         reader.nextTag(); // Pass by <schema> element
-        Map<String, EDIType> types = new SchemaReaderV3(reader).readTypes();
+        Map<String, EDIType> types = new SchemaReaderV3(reader, Collections.emptyMap()).readTypes();
         schema.setTypes(types);
 
         assertEquals(EDIType.Type.TRANSACTION, schema.getType(StaEDISchema.TRANSACTION_ID).getType());
@@ -87,7 +88,7 @@ class StaEDISchemaTest {
         InputStream schemaStream = getClass().getResourceAsStream("/x12/IG-999-standard-included.xml");
         XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(schemaStream);
         reader.nextTag(); // Pass by <schema> element
-        Map<String, EDIType> types = new SchemaReaderV4(reader).readTypes();
+        Map<String, EDIType> types = new SchemaReaderV4(reader, Collections.emptyMap()).readTypes();
         schema.setTypes(types);
 
         assertEquals(EDIType.Type.TRANSACTION, schema.getType(StaEDISchema.TRANSACTION_ID).getType());
diff --git a/src/test/resources/x12/IG-999-standard-included-relative.xml b/src/test/resources/x12/IG-999-standard-included-relative.xml
new file mode 100644
index 0000000..538d277
--- /dev/null
+++ b/src/test/resources/x12/IG-999-standard-included-relative.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<schema xmlns="http://xlate.io/EDISchema/v4">
+  <include schemaLocation="file:./EDISchema999.xml" />
+
+  <implementation>
+    <sequence>
+      <segment type="AK1">
+        <sequence>
+          <element position="2" minOccurs="1">
+            <description>Element AK102</description>
+          </element>
+          <element position="1" minOccurs="1" />
+          <element position="3" minOccurs="1" />
+        </sequence>
+      </segment>
+
+      <loop code="AK2" type="L0001" title="Transaction Set Response Header">
+        <sequence>
+          <segment type="AK2">
+          <!-- Unchanged from standard -->
+          </segment>
+
+          <loop code="IK3" type="L0002" title="Error Identification">
+            <sequence>
+              <segment type="IK3">
+                <sequence>
+                  <element position="1" minOccurs="1" />
+                  <element position="2" minOccurs="1" />
+                  <element position="3" minOccurs="0" />
+                  <element position="4" minOccurs="1" />
+                </sequence>
+              </segment>
+              <segment type="CTX" discriminator="01.01" maxOccurs="9">
+                <sequence>
+                  <composite position="1">
+                    <sequence>
+                      <element position="1">
+                        <enumeration>
+                          <value>SITUATIONAL TRIGGER</value>
+                        </enumeration>
+                      </element>
+                    </sequence>
+                  </composite>
+                  <element position="2" minOccurs="1" />
+                  <element position="3" minOccurs="1" />
+                  <element position="4" />
+                  <composite position="5" />
+                  <composite position="6">
+                    <sequence>
+                      <element position="1" />
+                    </sequence>
+                  </composite>
+                </sequence>
+              </segment>
+              <segment type="CTX" maxOccurs="1">
+                <sequence>
+                  <composite position="1">
+                    <sequence>
+                      <element position="1" minOccurs="1" />
+                      <element position="2" minOccurs="1" />
+                    </sequence>
+                  </composite>
+                    <!-- Remaining elements are not used. -->
+                </sequence>
+              </segment>
+              <loop code="IK4" type="L0003" title="Implementation Data Element Note">
+                <sequence>
+                  <segment type="IK4">
+                      <!-- Unchanged from standard -->
+                  </segment>
+                  <segment type="CTX" maxOccurs="10">
+                    <sequence>
+                      <composite position="1">
+                        <sequence>
+                          <element position="1">
+                            <enumeration>
+                              <value>SITUATIONAL TRIGGER</value>
+                            </enumeration>
+                          </element>
+                        </sequence>
+                      </composite>
+                      <element position="2" minOccurs="1" />
+                      <element position="3" minOccurs="1" />
+                      <element position="4" />
+                      <composite position="5" minOccurs="1" />
+                      <composite position="6">
+                        <sequence>
+                          <element position="1" />
+                        </sequence>
+                      </composite>
+                    </sequence>
+                  </segment>
+                </sequence>
+              </loop>
+            </sequence>
+          </loop>
+          <segment type="IK5">
+              <!-- Unchanged from standard -->
+          </segment>
+        </sequence>
+      </loop>
+      <segment type="AK9">
+          <!-- Unchanged from standard -->
+      </segment>
+    </sequence>
+  </implementation>
+</schema>