TestW3CSchemaNillable.java

package wstxtest.vstream;

import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.stream.XMLStreamException;

import org.codehaus.stax2.*;
import org.codehaus.stax2.validation.*;

import com.ctc.wstx.sw.RepairingNsStreamWriter;
import com.ctc.wstx.sw.SimpleNsStreamWriter;
import org.junit.jupiter.api.Test;

/**
 * Test whether nillable elements are handled correctly by both reader and writer.
 * A reproducer for <a href="https://github.com/FasterXML/woodstox/issues/179">https://github.com/FasterXML/woodstox/issues/179</a>.
 */
public class TestW3CSchemaNillable
    extends BaseValidationTest
{
    private static final String SCHEMA = 
            "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"\n"
            + "           xmlns=\"http://server.hw.demo/nillable\"\n"
            + "           targetNamespace=\"http://server.hw.demo/nillable\"\n"
            + "           elementFormDefault=\"qualified\" attributeFormDefault=\"qualified\">\n"
            + "  <xs:element name=\"nillableParent\">\n"
            + "    <xs:complexType>\n"
            + "      <xs:sequence>\n"
            + "        <xs:element name=\"nillableDateTime\" type=\"xs:dateTime\" nillable=\"true\" minOccurs=\"0\"/>\n"
            + "        <xs:element name=\"nillableInt\" type=\"xs:int\" nillable=\"true\" minOccurs=\"0\"/>\n"
            + "        <xs:element name=\"nillableString\" type=\"xs:string\" nillable=\"true\"  minOccurs=\"0\"/>\n"
            + "      </xs:sequence>\n"
            + "    </xs:complexType>\n"
            + "  </xs:element>\n"
            + "</xs:schema>";

    // for [woodstox-core#179]
    @Test
    public void testNillableString() throws Exception
    {
        XMLValidationSchema schema = parseW3CSchema(SCHEMA);
        final String XML = 
                "<nl:nillableParent xmlns:nl=\"http://server.hw.demo/nillable\">\n"
                + "    <nl:nillableString xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:nil=\"true\"/>\n"
                + "</nl:nillableParent>";
        // A document with xsi:nil="true" should pass for both reader and writer side validation
        // Reader and SimpleNsStreamWriter validation with xsi:nil="true"
        {
            StringWriter writer = new StringWriter();
            SimpleNsStreamWriter sw = (SimpleNsStreamWriter) constructStreamWriter(writer, true, false);
            _testNillable(XML, schema, true, sw, writer);
        }
        // Reader and RepairingNsStreamWriter validation with xsi:nil="true"
        {
            StringWriter writer = new StringWriter();
            RepairingNsStreamWriter sw = (RepairingNsStreamWriter) constructStreamWriter(writer, true, true);
            _testNillable(XML, schema, true, sw, writer);
        }
    
        // Empty strings are legal
        {
            StringWriter writer = new StringWriter();
            SimpleNsStreamWriter sw = (SimpleNsStreamWriter) constructStreamWriter(writer, true, false);
            _testNillable(XML.replace(" xsi:nil=\"true\"", ""), schema, true, sw, writer);
        }
        {
            StringWriter writer = new StringWriter();
            RepairingNsStreamWriter sw = (RepairingNsStreamWriter) constructStreamWriter(writer, true, true);
            _testNillable(XML.replace(" xsi:nil=\"true\"", ""), schema, true, sw, writer);
        }
    
    }

    // for [woodstox-core#179]
    @Test
    public void testNillableDateTime() throws Exception
    {
        XMLValidationSchema schema = parseW3CSchema(SCHEMA);
        final String XML = 
                "<nl:nillableParent xmlns:nl=\"http://server.hw.demo/nillable\">\n"
                + "    <nl:nillableDateTime xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:nil=\"true\"/>\n"
                + "</nl:nillableParent>";
        
        _testNillable(schema, XML, "nillableDateTime", "dateTime");

    }

    // for [woodstox-core#179]
    @Test
    public void testNillableInt() throws Exception
    {
        XMLValidationSchema schema = parseW3CSchema(SCHEMA);
        final String XML =
                "<nl:nillableParent xmlns:nl=\"http://server.hw.demo/nillable\">\n"
                + "    <nl:nillableInt xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:nil=\"true\"/>\n"
                + "</nl:nillableParent>";

        _testNillable(schema, XML, "nillableInt", "int");

    }

    private void _testNillable(XMLValidationSchema schema, final String XML, String localName, String typeName) throws XMLStreamException, Exception {
        // A document with xsi:nil="true" should pass for both reader and writer side validation
        // Reader and SimpleNsStreamWriter validation with xsi:nil="true"
        {
            StringWriter writer = new StringWriter();
            SimpleNsStreamWriter sw = (SimpleNsStreamWriter) constructStreamWriter(writer, true, false);
            _testNillable(XML, schema, true, sw, writer);
        }
        // Reader and RepairingNsStreamWriter validation with xsi:nil="true"
        {
            StringWriter writer = new StringWriter();
            RepairingNsStreamWriter sw = (RepairingNsStreamWriter) constructStreamWriter(writer, true, true);
            _testNillable(XML, schema, true, sw, writer);
        }
        
        
        // The same document without xsi:nil="true" must fail for both reader and writer validation
    
        // reader only validation without xsi:nil="true"
        try {
            _testNillable(XML.replace(" xsi:nil=\"true\"", ""), schema, true, null, null);
            fail("Expected a LocalValidationError");
        } catch (XMLValidationException vex) {
            assertMessageContains(vex, "does not satisfy the \""+ typeName +"\" type");
        }
        
        // SimpleNsStreamWriter validation without xsi:nil="true"
        try {
            StringWriter writer = new StringWriter();
            SimpleNsStreamWriter sw = (SimpleNsStreamWriter) constructStreamWriter(writer, true, false);
            _testNillable(XML.replace(" xsi:nil=\"true\"", ""), schema, false, sw, writer);
            fail("Expected a LocalValidationError");
        } catch (XMLValidationException vex) {
            assertMessageContains(vex, "does not satisfy the \""+ typeName +"\" type");
        }
        
        // RepairingNsStreamWriter validation without xsi:nil="true"
        try {
            StringWriter writer = new StringWriter();
            RepairingNsStreamWriter sw = (RepairingNsStreamWriter) constructStreamWriter(writer, true, true);
            _testNillable(XML.replace(" xsi:nil=\"true\"", ""), schema, false, sw, writer);
            fail("Expected a LocalValidationError");
        } catch (XMLValidationException vex) {
            assertMessageContains(vex, "does not satisfy the \""+ typeName +"\" type");
        }
    }

    private void _testNillable(String XML, XMLValidationSchema schema, boolean validateReader, XMLStreamWriter2 sw, StringWriter writer) throws Exception
    {
        XMLStreamReader2 sr = (XMLStreamReader2) getInputFactory().createXMLStreamReader(new StringReader(XML));

        if (validateReader) {
            sr.validateAgainst(schema);
        }

        if (sw != null) {
            sw.validateAgainst(schema);
            sw.copyEventFromReader(sr, false);
        }

        while (sr.hasNext()) {
            sr.next();
            if (sw != null) {
                sw.copyEventFromReader(sr, false);
            }
        }
        sr.close();
        if (sw != null) {
            sw.close();
            assertEquals(XML, writer.toString());
        }
    }

}