From f348b5f4537b3700d7e4d0ed284def231c37e4b5 Mon Sep 17 00:00:00 2001 From: Peter Palaga Date: Fri, 12 Jan 2024 23:19:10 +0100 Subject: [PATCH 1/5] Assert specific validation error messages to get more certainty that the expected error occurred --- .../failing/TestW3CSchemaNillable179.java | 194 +++++++++--------- src/test/java/stax2/BaseStax2Test.java | 7 + .../vwstream/TestAttributeValidation.java | 10 +- .../stax2/vwstream/TestOutputValidation.java | 30 ++- .../vwstream/TestStructuralValidation.java | 16 +- src/test/java/wstxtest/msv/TestW3CSchema.java | 5 +- src/test/java/wstxtest/stream/TestXmlId.java | 2 +- .../vstream/TestInvalidAttributeValue.java | 4 + .../vstream/TestW3CSchemaComplexTypes.java | 19 +- .../java/wstxtest/vstream/TestXmlSpace.java | 4 +- 10 files changed, 149 insertions(+), 142 deletions(-) diff --git a/src/test/java/failing/TestW3CSchemaNillable179.java b/src/test/java/failing/TestW3CSchemaNillable179.java index 07de251c..33085493 100644 --- a/src/test/java/failing/TestW3CSchemaNillable179.java +++ b/src/test/java/failing/TestW3CSchemaNillable179.java @@ -1,81 +1,115 @@ package failing; // ^^^ Move under "wstxtest/msv" once passing -import java.io.IOException; -import java.io.InputStream; +import java.io.StringReader; import java.io.StringWriter; -import javax.xml.XMLConstants; -import javax.xml.transform.Source; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import javax.xml.validation.Validator; - import org.codehaus.stax2.*; import org.codehaus.stax2.validation.*; -import org.xml.sax.SAXException; import wstxtest.vstream.BaseValidationTest; /** - * Test whether MSV validator behaves the same w.r.t. nillable elements as javax.xml.validation validator. + * Test whether nillable elements are handled correctly by both reader and writer. * A reproducer for https://github.com/FasterXML/woodstox/issues/179. */ public class TestW3CSchemaNillable179 extends BaseValidationTest { + private static final String SCHEMA = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; // for [woodstox-core#179] public void testNillableDateTime() throws Exception { - /* - - - - */ - testNillable("wstxtest/msv/nillableDateTime.xml"); + final String xmlDocument = + "\n" + + " \n" + + ""; + testNillable(xmlDocument, true, true); + + // The same document without xsi:nil="true" must fail for both reader and writer validation + try { + testNillable(xmlDocument.replace(" xsi:nil=\"true\"", ""), true, false); + fail("Expected a LocalValidationError"); + } catch (LocalValidationError expected) { + assertEquals("Unknown reason (at end element )", expected.problem.getMessage()); + } + try{ + testNillable(xmlDocument.replace(" xsi:nil=\"true\"", ""), false, true); + fail("Expected a LocalValidationError"); + } catch (LocalValidationError expected) { + assertEquals("Unknown reason (at end element )", expected.problem.getMessage()); + } + } // for [woodstox-core#179] public void testNillableInt() throws Exception { - /* - - - - */ - testNillable("wstxtest/msv/nillableInt.xml"); + final String xmlDocument = + "\n" + + " \n" + + ""; + testNillable(xmlDocument, true, true); + + // The same document without xsi:nil="true" must fail for both reader and writer validation + try { + testNillable(xmlDocument.replace(" xsi:nil=\"true\"", ""), true, false); + fail("Expected a LocalValidationError"); + } catch (LocalValidationError expected) { + assertEquals("Unknown reason (at end element )", expected.problem.getMessage()); + } + try{ + testNillable(xmlDocument.replace(" xsi:nil=\"true\"", ""), false, true); + fail("Expected a LocalValidationError"); + } catch (LocalValidationError expected) { + assertEquals("Unknown reason (at end element )", expected.problem.getMessage()); + } + } // for [woodstox-core#179] public void testNillableString() throws Exception { - /* - - - - */ - testNillable("wstxtest/msv/nillableString.xml"); + final String xmlDocument = + "\n" + + " \n" + + ""; + testNillable(xmlDocument, true, true); + + // Empty strings are legal + testNillable(xmlDocument.replace(" xsi:nil=\"true\"", ""), true, false); + testNillable(xmlDocument.replace(" xsi:nil=\"true\"", ""), false, true); + } - void testNillable(String xmlResource) throws Exception + void testNillable(String xmlDocument, boolean validateReader, boolean validateWriter) throws Exception { - boolean woodstoxPassed = true; - Exception woodstoxE = null; - // Woodstox - final String xsdResource = "wstxtest/msv/nillable.xsd"; - { + StringWriter writer = new StringWriter(); + XMLStreamReader2 xmlReader = null; + XMLStreamWriter2 xmlWriter = null; + try { XMLValidationSchemaFactory schF = XMLValidationSchemaFactory.newInstance(XMLValidationSchema.SCHEMA_ID_W3C_SCHEMA); - InputStream schemaInput = getClass().getClassLoader().getResourceAsStream(xsdResource); - InputStream xmlInput = getClass().getClassLoader().getResourceAsStream(xmlResource); - try { - XMLValidationSchema schema = schF.createSchema(schemaInput); - XMLInputFactory2 f = getInputFactory(); - setValidating(f, false); + XMLValidationSchema schema = schF.createSchema(new StringReader(SCHEMA)); + XMLInputFactory2 f = getInputFactory(); + setValidating(f, validateReader); - XMLStreamReader2 xmlReader = (XMLStreamReader2) f.createXMLStreamReader(xmlInput); + xmlReader = (XMLStreamReader2) f.createXMLStreamReader(new StringReader(xmlDocument)); - /* the validation exception is only thrown from the writer + if (validateReader) { xmlReader.setValidationProblemHandler(new ValidationProblemHandler() { @Override public void reportProblem(XMLValidationProblem problem) @@ -84,10 +118,11 @@ public void reportProblem(XMLValidationProblem problem) } }); xmlReader.validateAgainst(schema); - */ + } - StringWriter writer = new StringWriter(); - XMLStreamWriter2 xmlWriter = (XMLStreamWriter2) getOutputFactory().createXMLStreamWriter(writer); + xmlWriter = (XMLStreamWriter2) getOutputFactory().createXMLStreamWriter(writer); + + if (validateWriter) { xmlWriter.setValidationProblemHandler(new ValidationProblemHandler() { @Override public void reportProblem(XMLValidationProblem problem) @@ -96,67 +131,22 @@ public void reportProblem(XMLValidationProblem problem) } }); xmlWriter.validateAgainst(schema); - - try { - xmlWriter.copyEventFromReader(xmlReader, false); - while (xmlReader.hasNext()) { - xmlReader.next(); - xmlWriter.copyEventFromReader(xmlReader, false); - } - } catch (LocalValidationError e) { - woodstoxPassed = false; - woodstoxE = e; - } - } finally { - if (xmlInput != null) { - xmlInput.close(); - } - if (schemaInput != null) { - schemaInput.close(); - } } - } - // javax.xml.validation - boolean javaxXmlValidationPassed = true; - { - InputStream schemaInput = getClass().getClassLoader().getResourceAsStream(xsdResource); - InputStream xmlInput = getClass().getClassLoader().getResourceAsStream(xmlResource); - SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - try { - Source schemaFile = new StreamSource(schemaInput); - Schema schema = factory.newSchema(schemaFile); - Validator validator = schema.newValidator(); - try { - validator.validate(new StreamSource(xmlInput)); - } catch (SAXException e) { - javaxXmlValidationPassed = false; - } catch (IOException e) { - throw new RuntimeException(e); - } - } finally { - if (xmlInput != null) { - xmlInput.close(); - } - if (schemaInput != null) { - schemaInput.close(); - } + xmlWriter.copyEventFromReader(xmlReader, false); + while (xmlReader.hasNext()) { + xmlReader.next(); + xmlWriter.copyEventFromReader(xmlReader, false); } - } - - if (woodstoxPassed != javaxXmlValidationPassed) { - if (woodstoxPassed) { - fail("Woodstox MSV validator passed" - + " but javax.xml.validation validator did not pass" - + " for " + xsdResource + " and "+ xmlResource); - - } else { - fail("Woodstox MSV validator did not pass" - + " but javax.xml.validation validator passed" - + " for " + xsdResource + " and "+ xmlResource - +".\nFailure: "+woodstoxE); + } finally { + if (xmlReader != null) { + xmlReader.close(); + } + if (xmlWriter != null) { + xmlWriter.close(); } } + assertEquals(xmlDocument, writer.toString()); } /* diff --git a/src/test/java/stax2/BaseStax2Test.java b/src/test/java/stax2/BaseStax2Test.java index 86c2db93..767d14a7 100644 --- a/src/test/java/stax2/BaseStax2Test.java +++ b/src/test/java/stax2/BaseStax2Test.java @@ -513,6 +513,13 @@ protected static void assertNoAttrNamespace(String attrNsURI) } } + protected static void assertMessageContains(Throwable e, String substring) { + final String actual = e.getMessage(); + if (!actual.contains(substring)) { + fail("Message '"+ actual +"' should contain '"+ substring +"'"); + } + } + protected static void failStrings(String msg, String exp, String act) { // !!! TODO: Indicate position where Strings differ diff --git a/src/test/java/stax2/vwstream/TestAttributeValidation.java b/src/test/java/stax2/vwstream/TestAttributeValidation.java index 74ff5d36..d4793a1d 100644 --- a/src/test/java/stax2/vwstream/TestAttributeValidation.java +++ b/src/test/java/stax2/vwstream/TestAttributeValidation.java @@ -84,7 +84,7 @@ private void _testInvalidFixedAttr(boolean nsAware, boolean repairing) sw.writeAttribute("fixAttr", "otherValue"); fail(modeDesc+" Expected a validation exception when trying to add a #FIXED attribute with 'wrong' value"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Value of attribute \"fixAttr\" (element ) not \"fixedValue\" as expected, but \"otherValue\""); } // Should not close, since stream is invalid now... @@ -96,7 +96,7 @@ private void _testInvalidFixedAttr(boolean nsAware, boolean repairing) sw.writeAttribute("fixAttr", ""); fail(modeDesc+" Expected a validation exception when trying to add a #FIXED attribute with an empty value"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Value of attribute \"fixAttr\" (element ) not \"fixedValue\" as expected, but \"\""); } // And finally, same for empty elem in case impl. is different @@ -107,7 +107,7 @@ private void _testInvalidFixedAttr(boolean nsAware, boolean repairing) sw.writeAttribute("fixAttr", "foobar"); fail(modeDesc+" Expected a validation exception when trying to add a #FIXED attribute with an empty value"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Value of attribute \"fixAttr\" (element ) not \"fixedValue\" as expected, but \"foobar\""); } } @@ -173,7 +173,7 @@ public void _testInvalidRequiredAttr(boolean nsAware, boolean repairing) sw.writeEndElement(); fail(modeDesc+" Expected a validation exception when omitting a #REQUIRED attribute"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Required attribute \"reqAttr\" missing from element "); } // Should not close, since stream is invalid now... } @@ -229,7 +229,7 @@ private void _testInvalidNsAttr(boolean repairing) throws XMLStreamException sw.writeAttribute(NS_PREFIX2, NS_URI, "attr", "value"); fail(modeDesc+" Expected a validation exception when trying to add an attribute with wrong ns prefix"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Element has no attribute \"ns2:attr\""); } // Should not close, since stream is invalid now... } diff --git a/src/test/java/stax2/vwstream/TestOutputValidation.java b/src/test/java/stax2/vwstream/TestOutputValidation.java index 17f08e6b..f88c208f 100644 --- a/src/test/java/stax2/vwstream/TestOutputValidation.java +++ b/src/test/java/stax2/vwstream/TestOutputValidation.java @@ -59,7 +59,7 @@ public void testInvalidMixedContent() sw.writeCharacters("Illegal text!"); fail("Expected a validation exception for non-whitespace text output on non-mixed element content"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Element has non-mixed content specification; can not contain non-white space text, or any CDATA sections"); } } } @@ -149,7 +149,7 @@ public void testInvalidEmptyContent() sw.writeStartElement("leaf"); fail(modeDesc+" Expected a validation exception when trying to add an element into EMPTY content model"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Validation error, encountered element as a child of : No elements allowed in pure #PCDATA content model"); } sw.close(); @@ -160,7 +160,7 @@ public void testInvalidEmptyContent() sw.writeEmptyElement("leaf"); fail(modeDesc+" Expected a validation exception when trying to add an element into EMPTY content model"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Validation error, encountered element as a child of : No elements allowed in pure #PCDATA content model"); } sw.close(); @@ -170,7 +170,9 @@ public void testInvalidEmptyContent() try { sw.writeCharacters(" "); fail(modeDesc+" Expected a validation exception when trying to any text into EMPTY content model"); - } catch (XMLValidationException vex) { } + } catch (XMLValidationException vex) { + assertMessageContains(vex, "Element has EMPTY content specification; can not contain CHARACTERS"); + } sw.close(); // Then CDATA @@ -179,7 +181,9 @@ public void testInvalidEmptyContent() try { sw.writeCData("foo"); fail(modeDesc+" Expected a validation exception when trying to add CDATA into EMPTY content model"); - } catch (XMLValidationException vex) { } + } catch (XMLValidationException vex) { + assertMessageContains(vex, "Element has EMPTY content specification; can not contain CDATA"); + } sw.close(); // Then ENTITY @@ -188,7 +192,9 @@ public void testInvalidEmptyContent() try { sw.writeEntityRef("amp"); fail(modeDesc+" Expected a validation exception when trying to add CDATA into EMPTY content model"); - } catch (XMLValidationException vex) { } + } catch (XMLValidationException vex) { + assertMessageContains(vex, "Element has EMPTY content specification; can not contain ENTITY_REFERENCE"); + } sw.close(); // Then comment @@ -197,7 +203,9 @@ public void testInvalidEmptyContent() try { sw.writeComment("comment"); fail(modeDesc+" Expected a validation exception when trying to add comment into EMPTY content model"); - } catch (XMLValidationException vex) { } + } catch (XMLValidationException vex) { + assertMessageContains(vex, "Element has EMPTY content specification; can not contain COMMENT"); + } sw.close(); // Then proc. instr. @@ -206,7 +214,9 @@ public void testInvalidEmptyContent() try { sw.writeProcessingInstruction("target", "data"); fail(modeDesc+" Expected a validation exception when trying to add processing instruction into EMPTY content model"); - } catch (XMLValidationException vex) { } + } catch (XMLValidationException vex) { + assertMessageContains(vex, "Element has EMPTY content specification; can not contain PROCESSING_INSTRUCTION"); + } sw.close(); } } @@ -301,7 +311,7 @@ public void testInvalidAnyContent() sw.writeStartElement("unknown"); fail(modeDesc+" Expected a validation exception when trying to add an undeclared element"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Undefined element encountered"); } sw.close(); @@ -312,7 +322,7 @@ public void testInvalidAnyContent() sw.writeAttribute("unknown", "value"); fail(modeDesc+" Expected a validation exception when trying to add an undeclared attribute"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Element has no attribute \"unknown\""); } sw.close(); } diff --git a/src/test/java/stax2/vwstream/TestStructuralValidation.java b/src/test/java/stax2/vwstream/TestStructuralValidation.java index 7ce4ea33..d0beae51 100644 --- a/src/test/java/stax2/vwstream/TestStructuralValidation.java +++ b/src/test/java/stax2/vwstream/TestStructuralValidation.java @@ -64,7 +64,7 @@ public void testInvalidRootElem() sw.writeStartElement("branch"); fail(modeDesc+" Expected a validation exception when trying to write wrong root element"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Unexpected root element ; expected as per DOCTYPE declaration"); } // should not continue after exception; state may not be valid @@ -74,7 +74,7 @@ public void testInvalidRootElem() sw.writeStartElement("undefined"); fail(modeDesc+" Expected a validation exception when trying to write an undefined root element"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Undefined element encountered"); } // and same for explicitly empty element; wrong root @@ -84,7 +84,7 @@ public void testInvalidRootElem() sw.writeEmptyElement("branch"); fail(modeDesc+" Expected a validation exception when trying to write wrong root element"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Unexpected root element ; expected as per DOCTYPE declaration"); } } } @@ -156,7 +156,7 @@ public void testInvalidStructure() sw.writeEndElement(); // for root fail(modeDesc+" Expected a validation exception when omitting non-optional element"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Validation error, element : Expected or "); } // should not continue after exception; state may not be valid @@ -169,7 +169,7 @@ public void testInvalidStructure() sw.writeEmptyElement("end"); fail(modeDesc+" Expected a validation exception when omitting non-optional element"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Validation error, encountered element as a child of : Expected "); } } } @@ -234,7 +234,7 @@ public void testInvalidNsElem() sw.writeStartElement(NS_PREFIX2, "root", NS_URI); fail(modeDesc+" Expected a validation exception when passing wrong (unexpected) ns for element"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Undefined element encountered"); } // should not continue after exception; state may not be valid @@ -245,7 +245,7 @@ public void testInvalidNsElem() sw.writeEmptyElement(NS_PREFIX2, NS_URI, "root"); fail(modeDesc+" Expected a validation exception when passing wrong (unexpected) ns for element"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Undefined element encountered"); } // Oh, and finally, using non-ns DTD: @@ -255,7 +255,7 @@ public void testInvalidNsElem() sw.writeEmptyElement(NS_PREFIX, NS_URI, "root"); fail(modeDesc+" Expected a validation exception when passing wrong (unexpected) ns for element"); } catch (XMLValidationException vex) { - // expected... + assertMessageContains(vex, "Undefined element encountered"); } } } diff --git a/src/test/java/wstxtest/msv/TestW3CSchema.java b/src/test/java/wstxtest/msv/TestW3CSchema.java index 579d6967..1dc918df 100644 --- a/src/test/java/wstxtest/msv/TestW3CSchema.java +++ b/src/test/java/wstxtest/msv/TestW3CSchema.java @@ -318,15 +318,14 @@ public void reportProblem(XMLValidationProblem problem) } }); sr.validateAgainst(schema); - boolean threw = false; try { while (sr.hasNext()) { /* int type = */sr.next(); } + fail("Expected LocalValidationError"); } catch (LocalValidationError lve) { - threw = true; + assertEquals("tag name \"foobar\" is not allowed. Possible tag names are: ", lve.problem.getMessage()); } - assertTrue(threw); } /* diff --git a/src/test/java/wstxtest/stream/TestXmlId.java b/src/test/java/wstxtest/stream/TestXmlId.java index c55370a8..0a3dbc5c 100644 --- a/src/test/java/wstxtest/stream/TestXmlId.java +++ b/src/test/java/wstxtest/stream/TestXmlId.java @@ -159,7 +159,7 @@ private void doTestInvalid(boolean nsAware, boolean coal) assertTokenType(START_ELEMENT, sr.next()); fail("Expected a validation exception for invalid Xml:id attribute declaration"); } catch (XMLValidationException vex) { - //System.err.println("VLD exc -> "+vex); + assertMessageContains(vex, "Attribute xml:id has to have attribute type of ID, as per Xml:id specification"); } } diff --git a/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java b/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java index 0bf9554a..06a9f812 100644 --- a/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java +++ b/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java @@ -53,5 +53,9 @@ public void reportProblem(XMLValidationProblem problem) final String verboseValue = sr.getAttributeValue(null, "verbose"); assertEquals("yes", verboseValue); + + assertEquals(1, probs.size()); + assertEquals("Element has no attribute \"verbose\"", probs.get(0).getMessage()); + } } diff --git a/src/test/java/wstxtest/vstream/TestW3CSchemaComplexTypes.java b/src/test/java/wstxtest/vstream/TestW3CSchemaComplexTypes.java index 23c1c32b..cdf58b69 100644 --- a/src/test/java/wstxtest/vstream/TestW3CSchemaComplexTypes.java +++ b/src/test/java/wstxtest/vstream/TestW3CSchemaComplexTypes.java @@ -50,17 +50,14 @@ public void testMSVGithubIssue2() throws Exception +""); sr.validateAgainst(schema); - try { - assertTokenType(START_ELEMENT, sr.next()); - assertEquals("Root", sr.getLocalName()); - assertTokenType(START_ELEMENT, sr.next()); - assertEquals("Child", sr.getLocalName()); - assertTokenType(END_ELEMENT, sr.next()); - assertTokenType(END_ELEMENT, sr.next()); - assertTokenType(END_DOCUMENT, sr.next()); - } catch (XMLValidationException vex) { - fail("Did not expect validation exception, got: " + vex); - } + assertTokenType(START_ELEMENT, sr.next()); + assertEquals("Root", sr.getLocalName()); + assertTokenType(START_ELEMENT, sr.next()); + assertEquals("Child", sr.getLocalName()); + assertTokenType(END_ELEMENT, sr.next()); + assertTokenType(END_ELEMENT, sr.next()); + assertTokenType(END_DOCUMENT, sr.next()); + assertTokenType(END_DOCUMENT, sr.getEventType()); } diff --git a/src/test/java/wstxtest/vstream/TestXmlSpace.java b/src/test/java/wstxtest/vstream/TestXmlSpace.java index f1f6b325..b818e591 100644 --- a/src/test/java/wstxtest/vstream/TestXmlSpace.java +++ b/src/test/java/wstxtest/vstream/TestXmlSpace.java @@ -55,7 +55,7 @@ public void testSimpleNonNs() assertTokenType(START_ELEMENT, type); fail("Expected a validity exception for invalid xml:space declaration (ns-aware: "+nsAware+")"); } catch (XMLValidationException vex) { - ; // good + assertMessageContains(vex, "Attribute xml:space has to be defined of type enumerated, and have 1 or 2 values, 'default' and/or 'preserve'"); } sr.close(); @@ -71,7 +71,7 @@ public void testSimpleNonNs() assertTokenType(START_ELEMENT, type); fail("Expected a validity exception for invalid xml:space declaration (ns-aware: "+nsAware+")"); } catch (XMLValidationException vex) { - ; // good + assertMessageContains(vex, "Attribute xml:space has to be defined of type enumerated, and have 1 or 2 values, 'default' and/or 'preserve'"); } sr.close(); } From 88f3b36a57233d5ebc91fd98d7c43a287c6b701c Mon Sep 17 00:00:00 2001 From: Peter Palaga Date: Sat, 13 Jan 2024 15:03:18 +0100 Subject: [PATCH 2/5] Extend XMLStreamWriter validation test coverage by checking the writer validation wherever we test the reader validation --- .../com/ctc/wstx/sw/SimpleOutputElement.java | 6 +- src/test/java/stax2/BaseStax2Test.java | 52 +++- src/test/java/wstxtest/BaseWstxTest.java | 32 -- src/test/java/wstxtest/msv/TestW3CSchema.java | 104 +++++-- .../java/wstxtest/msv/TestW3CSchemaTypes.java | 13 +- .../wstxtest/vstream/BaseValidationTest.java | 70 ++++- src/test/java/wstxtest/vstream/TestDTD.java | 40 +-- .../TestDTDErrorCollection104Test.java | 148 +++++---- .../vstream/TestInvalidAttributeValue.java | 21 +- .../java/wstxtest/vstream/TestRelaxNG.java | 284 +++++++++--------- .../vstream/TestW3CSchemaComplexTypes.java | 49 ++- 11 files changed, 500 insertions(+), 319 deletions(-) diff --git a/src/main/java/com/ctc/wstx/sw/SimpleOutputElement.java b/src/main/java/com/ctc/wstx/sw/SimpleOutputElement.java index bdf6c715..596025a4 100644 --- a/src/main/java/com/ctc/wstx/sw/SimpleOutputElement.java +++ b/src/main/java/com/ctc/wstx/sw/SimpleOutputElement.java @@ -246,7 +246,11 @@ public String getNamespaceURI() { } public QName getName() { - return QNameCreator.create(mURI, mLocalName, mPrefix); + if (mPrefix != null) { + return QNameCreator.create(mURI, mLocalName, mPrefix); + } else { + return new QName(mURI, mLocalName); + } } /* diff --git a/src/test/java/stax2/BaseStax2Test.java b/src/test/java/stax2/BaseStax2Test.java index 767d14a7..526764d8 100644 --- a/src/test/java/stax2/BaseStax2Test.java +++ b/src/test/java/stax2/BaseStax2Test.java @@ -1,7 +1,9 @@ package stax2; import java.io.*; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import junit.framework.TestCase; @@ -12,6 +14,12 @@ import org.codehaus.stax2.evt.*; import org.codehaus.stax2.ri.Stax2ReaderAdapter; +import org.codehaus.stax2.validation.ValidationProblemHandler; +import org.codehaus.stax2.validation.XMLValidationException; +import org.codehaus.stax2.validation.XMLValidationProblem; +import org.codehaus.stax2.validation.XMLValidationSchema; + +import com.ctc.wstx.stax.WstxOutputFactory; /** * Base unit test class to be inherited by all unit tests that test @@ -186,6 +194,14 @@ protected XMLStreamReader2 constructNonNsStreamReader(String content, boolean co return (XMLStreamReader2) f.createXMLStreamReader(new StringReader(content)); } + protected static XMLStreamWriter2 constructStreamWriter(Writer writer, boolean nsSupported, boolean repairing) throws XMLStreamException { + WstxOutputFactory f = new WstxOutputFactory(); + f.getConfig().doSupportNamespaces(nsSupported); + f.getConfig().enableAutomaticNamespaces(repairing); + return (XMLStreamWriter2) f.createXMLStreamWriter(writer); + } + + /** * Method to force constructing a wrapper for given stream reader. * Have to use this method to work around natural resistance by @@ -328,7 +344,7 @@ protected static boolean isNamespaceAware(XMLOutputFactory f) * @return Dummy value calculated on contents; used to make sure * no dead code is eliminated */ - protected int streamThrough(XMLStreamReader sr) + protected static int streamThrough(XMLStreamReader sr) throws XMLStreamException { int result = 0; @@ -578,6 +594,40 @@ protected void verifyException(Throwable e, String match) } } + protected static void validateWriter(final String DOC, final List probs, XMLInputFactory f, + XMLValidationSchema schema, StringWriter writer, XMLStreamWriter2 sw) throws XMLStreamException { + sw.validateAgainst(schema); + final List writerProbs = new ArrayList(); + sw.setValidationProblemHandler(new ValidationProblemHandler() { + + @Override + public void reportProblem(XMLValidationProblem problem) throws XMLValidationException { + writerProbs.add(problem); + } + }); + + XMLStreamReader2 sr = (XMLStreamReader2)f.createXMLStreamReader( + new StringReader(DOC)); + + sw.copyEventFromReader(sr, false); + while (sr.hasNext()) { + /* int type = */sr.next(); + sw.copyEventFromReader(sr, false); + } + sr.close(); + sw.close(); + assertEquals(DOC, writer.toString()); + + assertEquals(probs.size(), writerProbs.size()); + for (int i = 0; i < probs.size(); i++) { + XMLValidationProblem expected = probs.get(i); + XMLValidationProblem actual = writerProbs.get(i); + assertEquals(expected.getMessage(), actual.getMessage()); + assertEquals(expected.getSeverity(), actual.getSeverity()); + } + } + + /* /////////////////////////////////////////////////////////// // Debug/output helpers diff --git a/src/test/java/wstxtest/BaseWstxTest.java b/src/test/java/wstxtest/BaseWstxTest.java index 8bcb5966..0361333e 100644 --- a/src/test/java/wstxtest/BaseWstxTest.java +++ b/src/test/java/wstxtest/BaseWstxTest.java @@ -234,38 +234,6 @@ protected static void setFixContent(XMLOutputFactory f, boolean state) ////////////////////////////////////////////////// */ - /** - * Method that will iterate through contents of an XML document - * using specified stream reader; will also access some of data - * to make sure reader reads most of lazy-loadable data. - * Method is usually called to try to get an exception for invalid - * content. - * - * @return Dummy value calculated on contents; used to make sure - * no dead code is eliminated - */ - @Override - protected int streamThrough(XMLStreamReader sr) - throws XMLStreamException - { - int result = 0; - - while (sr.hasNext()) { - int type = sr.next(); - result += type; - if (sr.hasText()) { - // will also do basic verification for text content, to - // see that all text accessor methods return same content - result += getAndVerifyText(sr).hashCode(); - } - if (sr.hasName()) { - result += sr.getName().hashCode(); - } - } - - return result; - } - @Override protected int streamThroughFailing(XMLInputFactory f, String contents, String msg) diff --git a/src/test/java/wstxtest/msv/TestW3CSchema.java b/src/test/java/wstxtest/msv/TestW3CSchema.java index 1dc918df..45de8834 100644 --- a/src/test/java/wstxtest/msv/TestW3CSchema.java +++ b/src/test/java/wstxtest/msv/TestW3CSchema.java @@ -1,10 +1,17 @@ package wstxtest.msv; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; + import javax.xml.stream.*; import org.codehaus.stax2.*; import org.codehaus.stax2.validation.*; +import com.ctc.wstx.stax.WstxInputFactory; +import com.ctc.wstx.stax.WstxOutputFactory; + import wstxtest.vstream.BaseValidationTest; /** @@ -78,10 +85,10 @@ public class TestW3CSchema final static String SIMPLE_XML = "" + "" + " " + "FamilyFred" + " " - + " " + " " + + " " + " " + " " + " BlowJoe" - + " " + " " + " " + ""; + + " " + " " + " " + ""; /** * Test validation against a simple document valid according to a very @@ -90,40 +97,59 @@ public class TestW3CSchema public void testSimpleNonNs() throws XMLStreamException { XMLValidationSchema schema = parseW3CSchema(SIMPLE_NON_NS_SCHEMA); - XMLStreamReader2 sr = getReader(SIMPLE_XML); + String XML = SIMPLE_XML; + XMLStreamReader2 sr = getReader(XML); sr.validateAgainst(schema); + StringWriter writer = new StringWriter(); + XMLStreamWriter2 sw = (XMLStreamWriter2) getOutputFactory().createXMLStreamWriter(writer); + sw.validateAgainst(schema); + sw.copyEventFromReader(sr, false); + try { assertTokenType(START_ELEMENT, sr.next()); assertEquals("personnel", sr.getLocalName()); + sw.copyEventFromReader(sr, false); while (sr.hasNext()) { /* int type = */sr.next(); + sw.copyEventFromReader(sr, false); } } catch (XMLValidationException vex) { fail("Did not expect validation exception, got: " + vex); } assertTokenType(END_DOCUMENT, sr.getEventType()); + assertEquals(XML.replace("'", "\""), writer.toString()); } public void testSimplePartialNonNs() throws XMLStreamException { XMLValidationSchema schema = parseW3CSchema(SIMPLE_NON_NS_SCHEMA); - XMLStreamReader2 sr = getReader(SIMPLE_XML); + String XML = SIMPLE_XML; + XMLStreamReader2 sr = getReader(XML); + StringWriter writer = new StringWriter(); + XMLStreamWriter2 sw = (XMLStreamWriter2) getOutputFactory().createXMLStreamWriter(writer); + sw.copyEventFromReader(sr, false); + assertTokenType(START_ELEMENT, sr.next()); assertEquals("personnel", sr.getLocalName()); + sw.copyEventFromReader(sr, false); sr.validateAgainst(schema); + sw.validateAgainst(schema); try { assertTokenType(START_ELEMENT, sr.next()); assertEquals("person", sr.getLocalName()); + sw.copyEventFromReader(sr, false); while (sr.hasNext()) { /* int type = */sr.next(); + sw.copyEventFromReader(sr, false); } } catch (XMLValidationException vex) { fail("Did not expect validation exception, got: " + vex); } assertTokenType(END_DOCUMENT, sr.getEventType()); + assertEquals(XML.replace("'", "\""), writer.toString()); } /** @@ -192,6 +218,18 @@ public void testSimpleDataTypes() throws XMLStreamException } sr.close(); + // validate the same document on the writer side + sr = getReader(XML); + StringWriter writer = new StringWriter(); + XMLStreamWriter2 sw = (XMLStreamWriter2) getOutputFactory().createXMLStreamWriter(writer); + sw.validateAgainst(schema); + sw.copyEventFromReader(sr, true); + while (sr.hasNext()) { + /* int type = */sr.next(); + sw.copyEventFromReader(sr, true); + } + assertEquals(XML.replace("\r", ""), writer.toString()); + // Then invalid (wrong type for value) XML = "34b1.00"; sr.validateAgainst(schema); @@ -224,33 +262,28 @@ public void testSimpleText() throws XMLStreamException + ""; XMLValidationSchema schema = parseW3CSchema(SCHEMA); - // First, 3 valid docs: - String XML = "xyz"; - XMLStreamReader2 sr = getReader(XML); - sr.validateAgainst(schema); - streamThrough(sr); - sr.close(); - - XML = ""; - sr = getReader(XML); - sr.validateAgainst(schema); - streamThrough(sr); - sr.close(); + String XML = null; + for (ValidationMode mode : ValidationMode.values()) { + // First, 3 valid docs: + XML = "xyz"; + mode.validate(schema, XML); + + XML = ""; + mode.validate(schema, XML, ""); + + XML = ""; + mode.validate(schema, XML, ""); + } - XML = ""; - sr = getReader(XML); - sr.validateAgainst(schema); - streamThrough(sr); - sr.close(); // Then invalid? XML = ""; - sr = getReader(XML); + XMLStreamReader2 sr = getReader(XML); sr.validateAgainst(schema); verifyFailure(XML, schema, "should warn about wrong root element", "tag name \"foobar\" is not allowed", false); } - + /** * Test for reproducing [WSTX-191] */ @@ -283,20 +316,24 @@ public void testConstrainedText() throws XMLStreamException ""); _testValidDesc(schema, "??"); - _testValidDesc(schema, ""); - _testValidDesc(schema, ""); + _testValidDesc(schema, "", ""); + _testValidDesc(schema, "", ""); _testValidDesc(schema, ""); } - private void _testValidDesc(XMLValidationSchema schema, String descSnippet) throws XMLStreamException + private void _testValidDesc(XMLValidationSchema schema, String descSnippet) throws XMLStreamException { + _testValidDesc(schema, descSnippet, descSnippet); + } + private void _testValidDesc(XMLValidationSchema schema, String descSnippet, String expectedSnippet) throws XMLStreamException { - // These should all be valid according to the schema - String XML = "" - + descSnippet + ""; - XMLStreamReader2 sr = getReader(XML); - sr.validateAgainst(schema); - streamThrough(sr); - sr.close(); + for (ValidationMode mode : ValidationMode.values()) { + // These should all be valid according to the schema + String XML = "" + + descSnippet + ""; + String expectedXML = "" + + expectedSnippet + ""; + mode.validate(schema, XML, expectedXML.replace("'", "\"")); + } } public void testValidationHandler() throws XMLStreamException @@ -361,4 +398,5 @@ public XMLValidationProblem getProblem() { return problem; } } + } diff --git a/src/test/java/wstxtest/msv/TestW3CSchemaTypes.java b/src/test/java/wstxtest/msv/TestW3CSchemaTypes.java index 3295b62d..d669de53 100644 --- a/src/test/java/wstxtest/msv/TestW3CSchemaTypes.java +++ b/src/test/java/wstxtest/msv/TestW3CSchemaTypes.java @@ -6,6 +6,7 @@ import org.codehaus.stax2.validation.*; import wstxtest.vstream.BaseValidationTest; +import wstxtest.vstream.BaseValidationTest.ValidationMode; import java.io.StringWriter; @@ -45,9 +46,9 @@ public class TestW3CSchemaTypes public void testSimpleValidInt() throws Exception { XMLValidationSchema schema = parseW3CSchema(SCHEMA_INT); - XMLStreamReader2 sr = getReader("129"); - sr.validateAgainst(schema); - streamThrough(sr); + for (ValidationMode mode : ValidationMode.values()) { + mode.validate(schema, "129"); + } } public void testSimpleInvalidInt() throws Exception @@ -62,9 +63,9 @@ public void testSimpleInvalidInt() throws Exception public void testSimpleValidFloat() throws Exception { XMLValidationSchema schema = parseW3CSchema(SCHEMA_FLOAT); - XMLStreamReader2 sr = getReader("1.00"); - sr.validateAgainst(schema); - streamThrough(sr); + for (ValidationMode mode : ValidationMode.values()) { + mode.validate(schema, "1.00"); + } } public void testSimpleInvalidFloat() throws Exception diff --git a/src/test/java/wstxtest/vstream/BaseValidationTest.java b/src/test/java/wstxtest/vstream/BaseValidationTest.java index b9d0c544..4bdf6492 100644 --- a/src/test/java/wstxtest/vstream/BaseValidationTest.java +++ b/src/test/java/wstxtest/vstream/BaseValidationTest.java @@ -1,13 +1,18 @@ package wstxtest.vstream; import java.io.StringReader; +import java.io.StringWriter; import java.net.URL; import javax.xml.stream.XMLStreamException; import org.codehaus.stax2.XMLStreamReader2; +import org.codehaus.stax2.XMLStreamWriter2; import org.codehaus.stax2.validation.*; +import com.ctc.wstx.stax.WstxInputFactory; +import com.ctc.wstx.stax.WstxOutputFactory; + public abstract class BaseValidationTest extends wstxtest.stream.BaseStreamTest { @@ -51,13 +56,32 @@ protected void verifyFailure(String xml, XMLValidationSchema schema, String fail } protected void verifyFailure(String xml, XMLValidationSchema schema, String failMsg, - String failPhrase, boolean strict) throws XMLStreamException + String failPhrase, boolean strict) throws XMLStreamException + { + verifyFailure(xml, schema, failMsg, failPhrase, strict, true, false); + verifyFailure(xml, schema, failMsg, failPhrase, strict, false, true); + } + + protected void verifyFailure(String xml, XMLValidationSchema schema, String failMsg, + String failPhrase, boolean strict, + boolean validateReader, boolean validateWriter) throws XMLStreamException { XMLStreamReader2 sr = constructStreamReader(getInputFactory(), xml); - sr.validateAgainst(schema); + if (validateReader) { + sr.validateAgainst(schema); + } + XMLStreamWriter2 sw = null; + if (validateWriter) { + sw = (XMLStreamWriter2) getOutputFactory().createXMLStreamWriter(new StringWriter()); + sw.validateAgainst(schema); + sw.copyEventFromReader(sr, false); + } try { while (sr.hasNext()) { /* int type = */sr.next(); + if (validateWriter) { + sw.copyEventFromReader(sr, false); + } } fail("Expected validity exception for " + failMsg); } catch (XMLValidationException vex) { @@ -79,4 +103,46 @@ protected void verifyFailure(String xml, XMLValidationSchema schema, String fail + "; instead got " + sex.getMessage()); } } + + public enum ValidationMode { + reader() { + @Override + public void validate(XMLValidationSchema schema, String XML, String expectedXML) throws XMLStreamException { + XMLStreamReader2 sr = (XMLStreamReader2) new WstxInputFactory().createXMLStreamReader(new StringReader(XML)); + sr.validateAgainst(schema); + streamThrough(sr); + assertTokenType(END_DOCUMENT, sr.getEventType()); + sr.close(); + } + + }, + writerNsSimple() { + + @Override + public void validate(XMLValidationSchema schema, String XML, String expectedXML) + throws XMLStreamException { + XMLStreamReader2 sr = (XMLStreamReader2) new WstxInputFactory().createXMLStreamReader(new StringReader(XML)); + + StringWriter writer = new StringWriter(); + WstxOutputFactory f = new WstxOutputFactory(); + f.getConfig().doSupportNamespaces(true); + f.getConfig().enableAutomaticNamespaces(false); + XMLStreamWriter2 sw = (XMLStreamWriter2) f.createXMLStreamWriter(writer); + sw.validateAgainst(schema); + sw.copyEventFromReader(sr, false); + while (sr.hasNext()) { + /* int type = */sr.next(); + sw.copyEventFromReader(sr, false); + } + assertTokenType(END_DOCUMENT, sr.getEventType()); + sr.close(); + sw.close(); + assertEquals(expectedXML, writer.toString()); + } + }; + public void validate(XMLValidationSchema schema, String XML) throws XMLStreamException { + validate(schema, XML, XML); + } + public abstract void validate(XMLValidationSchema schema, String XML, String expectedXML) throws XMLStreamException; + } } diff --git a/src/test/java/wstxtest/vstream/TestDTD.java b/src/test/java/wstxtest/vstream/TestDTD.java index b1d10ba3..2f96f7d5 100644 --- a/src/test/java/wstxtest/vstream/TestDTD.java +++ b/src/test/java/wstxtest/vstream/TestDTD.java @@ -57,10 +57,9 @@ public void testFullValidationOk() throws XMLStreamException { String XML = ""; XMLValidationSchema schema = parseDTDSchema(SIMPLE_DTD); - XMLStreamReader2 sr = getReader(XML); - sr.validateAgainst(schema); - while (sr.next() != END_DOCUMENT) { } - sr.close(); + for (ValidationMode mode : ValidationMode.values()) { + mode.validate(schema, XML, ""); + } } // [woodstox#23] @@ -70,7 +69,6 @@ public void testFullValidationIssue23() throws XMLStreamException +"\n" ; String XML = "foobar"; - XMLInputFactory f = getInputFactory(); /* Resolver resolver = new XMLResolver() { @@ -85,19 +83,9 @@ public Object resolveEntity(String publicID, String systemID, String baseURI, St XMLValidationSchemaFactory schemaFactory = XMLValidationSchemaFactory.newInstance(XMLValidationSchema.SCHEMA_ID_DTD); XMLValidationSchema schema = schemaFactory.createSchema(new StringReader(INPUT_DTD)); - XMLStreamReader2 sr = (XMLStreamReader2)f.createXMLStreamReader( - new StringReader(XML)); - - sr.validateAgainst(schema); - while (sr.next() != END_DOCUMENT) { - /* - System.err.println("Curr == "+sr.getEventType()); - if (sr.getEventType() == CHARACTERS) { - System.err.println(" text: "+sr.getText()); - } - */ + for (ValidationMode mode : ValidationMode.values()) { + mode.validate(schema, XML, "foobar"); } - sr.close(); } /** @@ -107,13 +95,11 @@ public Object resolveEntity(String publicID, String systemID, String baseURI, St public void testPartialValidationOk() throws XMLStreamException { - String XML = ""; + String XML = ""; XMLValidationSchema schema = parseDTDSchema(SIMPLE_DTD); - XMLStreamReader2 sr = getReader(XML); - assertTokenType(START_ELEMENT, sr.next()); - sr.validateAgainst(schema); - while (sr.next() != END_DOCUMENT) { } - sr.close(); + for (ValidationMode mode : ValidationMode.values()) { + mode.validate(schema, XML); + } } /* @@ -168,14 +154,6 @@ public void testSchemaWithFunnyFilename() ////////////////////////////////////////////////////// */ - private XMLStreamReader2 getReader(String contents) - throws XMLStreamException - { - XMLInputFactory f = getInputFactory(); - setValidating(f, false); - return constructStreamReader(f, contents); - } - private XMLStreamReader getValidatingReader(String contents, XMLReporter rep) throws XMLStreamException { diff --git a/src/test/java/wstxtest/vstream/TestDTDErrorCollection104Test.java b/src/test/java/wstxtest/vstream/TestDTDErrorCollection104Test.java index 739f42bf..0e0ce4fe 100644 --- a/src/test/java/wstxtest/vstream/TestDTDErrorCollection104Test.java +++ b/src/test/java/wstxtest/vstream/TestDTDErrorCollection104Test.java @@ -3,18 +3,27 @@ import stax2.BaseStax2Test; import java.io.StringReader; +import java.io.StringWriter; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import javax.xml.stream.*; import org.codehaus.stax2.XMLStreamReader2; +import org.codehaus.stax2.XMLStreamWriter2; import org.codehaus.stax2.validation.ValidationProblemHandler; import org.codehaus.stax2.validation.XMLValidationException; import org.codehaus.stax2.validation.XMLValidationProblem; import org.codehaus.stax2.validation.XMLValidationSchema; import org.codehaus.stax2.validation.XMLValidationSchemaFactory; +import com.ctc.wstx.stax.WstxOutputFactory; +import com.ctc.wstx.sw.RepairingNsStreamWriter; +import com.ctc.wstx.sw.SimpleNsStreamWriter; + public class TestDTDErrorCollection104Test extends BaseStax2Test { @@ -26,78 +35,97 @@ public void testValidationBeyondUnknownElement() throws Exception " \n" + " \n" + " \n" + - "\n"; + ""; final String INPUT_DTD = "\n" +"\n" ; + final List probs = new ArrayList(); + XMLInputFactory f = getInputFactory(); setCoalescing(f, true); XMLValidationSchemaFactory schemaFactory = XMLValidationSchemaFactory.newInstance(XMLValidationSchema.SCHEMA_ID_DTD); XMLValidationSchema schema = schemaFactory.createSchema(new StringReader(INPUT_DTD)); - XMLStreamReader2 sr = (XMLStreamReader2)f.createXMLStreamReader( - new StringReader(DOC)); - - final List probs = new ArrayList(); - - sr.validateAgainst(schema); - sr.setValidationProblemHandler(new ValidationProblemHandler() { - @Override - public void reportProblem(XMLValidationProblem problem) - throws XMLValidationException { - probs.add(problem); - } - }); - - assertTokenType(START_ELEMENT, sr.next()); - assertEquals("map", sr.getLocalName()); - - sr.next(); // SPACE or CHARACTERS - assertTokenType(START_ELEMENT, sr.next()); - assertEquals("val", sr.getLocalName()); - assertEquals(1, probs.size()); - assertEquals("Undefined element encountered", - probs.get(0).getMessage()); - - sr.next(); // SPACE or CHARACTERS - assertEquals(1, probs.size()); - - // From this point on, however, behavior bit unclear except - // that for DTD I guess we can at least check for undefined - // cases.... + { + XMLStreamReader2 sr = (XMLStreamReader2)f.createXMLStreamReader( + new StringReader(DOC)); + + sr.validateAgainst(schema); + sr.setValidationProblemHandler(new ValidationProblemHandler() { + @Override + public void reportProblem(XMLValidationProblem problem) + throws XMLValidationException { + probs.add(problem); + } + }); + + assertTokenType(START_ELEMENT, sr.next()); + assertEquals("map", sr.getLocalName()); + + sr.next(); // SPACE or CHARACTERS + assertTokenType(START_ELEMENT, sr.next()); + assertEquals("val", sr.getLocalName()); + assertEquals(1, probs.size()); + assertEquals("Undefined element encountered", + probs.get(0).getMessage()); + + sr.next(); // SPACE or CHARACTERS + assertEquals(1, probs.size()); + + // From this point on, however, behavior bit unclear except + // that for DTD I guess we can at least check for undefined + // cases.... + + assertTokenType(START_ELEMENT, sr.next()); + assertEquals("prop", sr.getLocalName()); + // not defined either so: + assertEquals(2, probs.size()); + assertEquals("Undefined element encountered", + probs.get(1).getMessage()); + + assertTokenType(END_ELEMENT, sr.next()); + assertEquals("prop", sr.getLocalName()); + assertEquals(2, probs.size()); + + sr.next(); // SPACE or CHARACTERS + assertEquals(2, probs.size()); + assertTokenType(END_ELEMENT, sr.next()); + assertEquals("val", sr.getLocalName()); + assertEquals(2, probs.size()); + + sr.next(); // SPACE or CHARACTERS + assertTokenType(END_ELEMENT, sr.next()); + assertEquals("map", sr.getLocalName()); + assertEquals(3, probs.size()); + assertEquals("Validation error, element : Expected at least one element ", + probs.get(2).getMessage()); + + // Plus content model now missing (s) + assertTokenType(END_DOCUMENT, sr.next()); + assertEquals(3, probs.size()); + + sr.close(); + } - assertTokenType(START_ELEMENT, sr.next()); - assertEquals("prop", sr.getLocalName()); - // not defined either so: - assertEquals(2, probs.size()); - assertEquals("Undefined element encountered", - probs.get(1).getMessage()); - - assertTokenType(END_ELEMENT, sr.next()); - assertEquals("prop", sr.getLocalName()); - assertEquals(2, probs.size()); - - sr.next(); // SPACE or CHARACTERS - assertEquals(2, probs.size()); - assertTokenType(END_ELEMENT, sr.next()); - assertEquals("val", sr.getLocalName()); - assertEquals(2, probs.size()); - - sr.next(); // SPACE or CHARACTERS - assertTokenType(END_ELEMENT, sr.next()); - assertEquals("map", sr.getLocalName()); - assertEquals(3, probs.size()); - assertEquals("Validation error, element : Expected at least one element ", - probs.get(2).getMessage()); - - // Plus content model now missing (s) - assertTokenType(END_DOCUMENT, sr.next()); - assertEquals(3, probs.size()); - - sr.close(); + // now do the same on the writer side + // and make sure that the reported problems are the same + + { + // SimpleNsStreamWriter + StringWriter writer = new StringWriter(); + SimpleNsStreamWriter sw = (SimpleNsStreamWriter) stax2.BaseStax2Test.constructStreamWriter(writer, true, false); + validateWriter(DOC, probs, f, schema, writer, sw); + } + { + // RepairingNsStreamWriter + StringWriter writer = new StringWriter(); + RepairingNsStreamWriter sw = (RepairingNsStreamWriter) stax2.BaseStax2Test.constructStreamWriter(writer, true, true); + validateWriter(DOC, probs, f, schema, writer, sw); + } } + } diff --git a/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java b/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java index 06a9f812..4f89cc03 100644 --- a/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java +++ b/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java @@ -3,6 +3,7 @@ import stax2.BaseStax2Test; import java.io.StringReader; +import java.io.StringWriter; import java.util.ArrayList; import java.util.List; @@ -15,12 +16,15 @@ import org.codehaus.stax2.validation.XMLValidationSchema; import org.codehaus.stax2.validation.XMLValidationSchemaFactory; +import com.ctc.wstx.sw.RepairingNsStreamWriter; +import com.ctc.wstx.sw.SimpleNsStreamWriter; + public class TestInvalidAttributeValue extends BaseStax2Test { public void testInvalidAttributeValue() throws Exception { - final String DOC = "\n"; + final String DOC = ""; final String INPUT_DTD = "\n" @@ -57,5 +61,20 @@ public void reportProblem(XMLValidationProblem problem) assertEquals(1, probs.size()); assertEquals("Element has no attribute \"verbose\"", probs.get(0).getMessage()); + // now do the same on the writer side + // and make sure that the reported problems are the same + { + // SimpleNsStreamWriter + StringWriter writer = new StringWriter(); + SimpleNsStreamWriter sw = (SimpleNsStreamWriter) stax2.BaseStax2Test.constructStreamWriter(writer, true, false); + validateWriter(DOC, probs, f, schema, writer, sw); + } + { + // RepairingNsStreamWriter + StringWriter writer = new StringWriter(); + RepairingNsStreamWriter sw = (RepairingNsStreamWriter) stax2.BaseStax2Test.constructStreamWriter(writer, true, true); + validateWriter(DOC, probs, f, schema, writer, sw); + } + } } diff --git a/src/test/java/wstxtest/vstream/TestRelaxNG.java b/src/test/java/wstxtest/vstream/TestRelaxNG.java index 705ab693..6fac4b17 100644 --- a/src/test/java/wstxtest/vstream/TestRelaxNG.java +++ b/src/test/java/wstxtest/vstream/TestRelaxNG.java @@ -1,10 +1,16 @@ package wstxtest.vstream; +import java.io.StringWriter; + import javax.xml.stream.*; import org.codehaus.stax2.*; import org.codehaus.stax2.validation.*; +import com.ctc.wstx.sw.NonNsStreamWriter; +import com.ctc.wstx.sw.RepairingNsStreamWriter; +import com.ctc.wstx.sw.SimpleNsStreamWriter; + /** * This is a simple base-line "smoke test" that checks that RelaxNG * validation works at least minimally. @@ -58,11 +64,11 @@ public void testSimpleNonNs() String XML = "" +"\n" - +" \n" + +" \n" +" foobar\n" +" Foo Bar\n" +" " - +" \n" + +" \n" +" fuzzy\n" +" adjective\n" +" " @@ -70,21 +76,9 @@ public void testSimpleNonNs() ; XMLValidationSchema schema = parseRngSchema(SIMPLE_RNG_SCHEMA); - XMLStreamReader2 sr = getReader(XML); - sr.validateAgainst(schema); - - try { - assertTokenType(START_ELEMENT, sr.next()); - assertEquals("dict", sr.getLocalName()); - - while (sr.hasNext()) { - sr.next(); - } - } catch (XMLValidationException vex) { - fail("Did not expect validation exception, got: "+vex); + for (ValidationMode mode : ValidationMode.values()) { + mode.validate(schema, XML); } - - assertTokenType(END_DOCUMENT, sr.getEventType()); } /** @@ -102,7 +96,7 @@ public void testInvalidNonNs() +" foobar\n" +" Foo Bar\n" +""; - verifyRngFailure(XML, schema, "wrong root element", + verifyFailure(XML, schema, "wrong root element", "is not allowed. Possible tag names are"); // Then, wrong child ordering: @@ -111,21 +105,21 @@ public void testInvalidNonNs() +" Foo Bar\n" +" foobar\n" +""; - verifyRngFailure(XML, schema, "illegal child element ordering", + verifyFailure(XML, schema, "illegal child element ordering", "tag name \"description\" is not allowed. Possible tag names are"); // Then, missing children: XML = "\n" +"\n" +""; - verifyRngFailure(XML, schema, "missing children", + verifyFailure(XML, schema, "missing children", "uncompleted content model. expecting: "); XML = "\n" +"\n" +"word" +""; - verifyRngFailure(XML, schema, "incomplete children", + verifyFailure(XML, schema, "incomplete children", "uncompleted content model. expecting: "); // Then illegal text in non-mixed element @@ -134,7 +128,7 @@ public void testInvalidNonNs() +" foobar\n" +" Foo Bar\n" +""; - verifyRngFailure(XML, schema, "invalid non-whitespace text", + verifyFailure(XML, schema, "invalid non-whitespace text", "Element has non-mixed content specification; can not contain non-white space text"); // missing attribute @@ -149,14 +143,14 @@ public void testInvalidNonNs() +" foobar\n" +" Foo Bar\n" +""; - verifyRngFailure(XML, schema, "undeclared attribute", + verifyFailure(XML, schema, "undeclared attribute", "unexpected attribute \"attr\""); XML = "\n" +"" +" foobar\n" +" Foo Bar\n" +""; - verifyRngFailure(XML, schema, "undeclared attribute", + verifyFailure(XML, schema, "undeclared attribute", "unexpected attribute \"type\""); } @@ -164,27 +158,15 @@ public void testSimpleNs() throws XMLStreamException { String XML = "\n" - +" \n" - +" \n" + +" \n" + +" \n" +"" ; XMLValidationSchema schema = parseRngSchema(SIMPLE_RNG_NS_SCHEMA); - XMLStreamReader2 sr = getReader(XML); - sr.validateAgainst(schema); - - try { - assertTokenType(START_ELEMENT, sr.next()); - assertEquals("root", sr.getLocalName()); - - while (sr.hasNext()) { - sr.next(); - } - } catch (XMLValidationException vex) { - fail("Did not expect validation exception, got: "+vex); + for (ValidationMode mode : ValidationMode.values()) { + mode.validate(schema, XML); } - - assertTokenType(END_DOCUMENT, sr.getEventType()); } /** @@ -200,21 +182,21 @@ public void testInvalidNs() String XML = "\n" +"\n" +""; - verifyRngFailure(XML, schema, "wrong root element", + verifyFailure(XML, schema, "wrong root element", "namespace URI of tag \"root\" is wrong"); // Wrong child namespace XML = "\n" +"\n" +""; - verifyRngFailure(XML, schema, "wrong child element namespace", + verifyFailure(XML, schema, "wrong child element namespace", "namespace URI of tag \"leaf\" is wrong."); // Wrong attribute namespace XML = "\n" +"\n" +""; - verifyRngFailure(XML, schema, "wrong attribute namespace", + verifyFailure(XML, schema, "wrong attribute namespace", "unexpected attribute \"attr1\""); } @@ -232,66 +214,87 @@ public void testSimplePartialNonNs() String XML = "" +"" - +"" + +"" +"" +"" ; XMLValidationSchema schema = parseRngSchema(SIMPLE_RNG_SCHEMA); + for (StopValidatingMethod method : StopValidatingMethod.values()) { + { + StringWriter writer = new StringWriter(); + SimpleNsStreamWriter sw = (SimpleNsStreamWriter) constructStreamWriter(writer, true, false); + _testSimplePartialNonNS(XML, schema, sw, writer, method); + } + { + StringWriter writer = new StringWriter(); + RepairingNsStreamWriter sw = (RepairingNsStreamWriter) constructStreamWriter(writer, true, true); + _testSimplePartialNonNS(XML, schema, sw, writer, method); + } + { + StringWriter writer = new StringWriter(); + NonNsStreamWriter sw = (NonNsStreamWriter) constructStreamWriter(writer, false, false); + _testSimplePartialNonNS(XML, schema, sw, writer, method); + } + } + } + + private enum StopValidatingMethod {schema, validator} + + private void _testSimplePartialNonNS(String XML, XMLValidationSchema schema, XMLStreamWriter2 sw, + StringWriter writer, StopValidatingMethod method) throws XMLStreamException { XMLStreamReader2 sr = getReader(XML); XMLValidator vtor = sr.validateAgainst(schema); + + XMLValidator wVtor = sw.validateAgainst(schema); + sw.copyEventFromReader(sr, false); assertTokenType(START_ELEMENT, sr.next()); assertEquals("dict", sr.getLocalName()); + sw.copyEventFromReader(sr, false); assertTokenType(START_ELEMENT, sr.next()); assertEquals("term", sr.getLocalName()); + sw.copyEventFromReader(sr, false); /* So far so good; but here we'd get an exception... so * let's stop validating */ - assertSame(vtor, sr.stopValidatingAgainst(schema)); - try { - // And should be good to go - assertTokenType(START_ELEMENT, sr.next()); - assertEquals("invalid", sr.getLocalName()); - assertTokenType(END_ELEMENT, sr.next()); - assertEquals("invalid", sr.getLocalName()); - assertTokenType(END_ELEMENT, sr.next()); - assertEquals("term", sr.getLocalName()); - assertTokenType(END_ELEMENT, sr.next()); - assertEquals("dict", sr.getLocalName()); - assertTokenType(END_DOCUMENT, sr.next()); - } catch (XMLValidationException vex) { - fail("Did not expect validation exception after stopping validation, got: "+vex); + switch (method) { + case schema: + assertSame(vtor, sr.stopValidatingAgainst(schema)); + assertSame(wVtor, sw.stopValidatingAgainst(schema)); + break; + case validator: + assertSame(vtor, sr.stopValidatingAgainst(vtor)); + assertSame(wVtor, sw.stopValidatingAgainst(wVtor)); + break; + default: + throw new IllegalStateException(); } - sr.close(); - - // And let's do the same, just using the other stopValidatingAgainst method - sr = getReader(XML); - vtor = sr.validateAgainst(schema); - - assertTokenType(START_ELEMENT, sr.next()); - assertEquals("dict", sr.getLocalName()); - assertTokenType(START_ELEMENT, sr.next()); - assertEquals("term", sr.getLocalName()); - - assertSame(vtor, sr.stopValidatingAgainst(vtor)); try { // And should be good to go assertTokenType(START_ELEMENT, sr.next()); assertEquals("invalid", sr.getLocalName()); + sw.copyEventFromReader(sr, false); assertTokenType(END_ELEMENT, sr.next()); assertEquals("invalid", sr.getLocalName()); + sw.copyEventFromReader(sr, false); assertTokenType(END_ELEMENT, sr.next()); assertEquals("term", sr.getLocalName()); + sw.copyEventFromReader(sr, false); assertTokenType(END_ELEMENT, sr.next()); assertEquals("dict", sr.getLocalName()); + sw.copyEventFromReader(sr, false); assertTokenType(END_DOCUMENT, sr.next()); + sw.copyEventFromReader(sr, false); } catch (XMLValidationException vex) { fail("Did not expect validation exception after stopping validation, got: "+vex); } sr.close(); + sw.close(); + + assertEquals(XML, writer.toString()); } public void testSimpleEnumAttr() @@ -311,15 +314,14 @@ public void testSimpleEnumAttr() XMLValidationSchema schema = parseRngSchema(schemaDef); // First, simple valid document - String XML = ""; - XMLStreamReader2 sr = getReader(XML); - sr.validateAgainst(schema); - while (sr.next() != END_DOCUMENT) { } - sr.close(); + String XML = ""; + for (ValidationMode mode : ValidationMode.values()) { + mode.validate(schema, XML); + } // And then invalid one, with unrecognized value XML = ""; - verifyRngFailure(XML, schema, "enum attribute with unknown value", + verifyFailure(XML, schema, "enum attribute with unknown value", "attribute \"enumAttr\" has a bad value"); } @@ -351,19 +353,18 @@ public void testSimpleIdAttrs() // First, a simple valid document String XML = "" - +" \n" - +" \n" - +" \n" + +" \n" + +" \n" + +" \n" +"" ; - XMLStreamReader2 sr = getReader(XML); - sr.validateAgainst(schema); - while (sr.next() != END_DOCUMENT) { } - sr.close(); + for (ValidationMode mode : ValidationMode.values()) { + mode.validate(schema, XML); + } // Then one with malformed id XML = ""; - verifyRngFailure(XML, schema, "malformed id", + verifyFailure(XML, schema, "malformed id", "attribute \"id\" has a bad value"); // Then with malformed IDREF value (would be valid IDREFS) @@ -372,7 +373,7 @@ public void testSimpleIdAttrs() +" \n" +"" ; - verifyRngFailure(XML, schema, "malformed id", + verifyFailure(XML, schema, "malformed id", "attribute \"ref\" has a bad value"); // And then invalid one, with dangling ref @@ -380,16 +381,16 @@ public void testSimpleIdAttrs() +" \n" +"" ; - verifyRngFailure(XML, schema, "reference to undefined id", - "Undefined ID"); + verifyFailure(XML, schema, "reference to undefined id", + "Undefined ID", true, true, false); // and another one with some of refs undefined XML = "" +" \n" +"" ; - verifyRngFailure(XML, schema, "reference to undefined id", - "Undefined ID"); + verifyFailure(XML, schema, "reference to undefined id", + "Undefined ID", true, true, false); } public void testSimpleIntAttr() @@ -407,25 +408,25 @@ public void testSimpleIntAttr() XMLValidationSchema schema = parseRngSchema(schemaDef); // First, a simple valid document - XMLStreamReader2 sr = getReader(""); - sr.validateAgainst(schema); - while (sr.next() != END_DOCUMENT) { } - sr.close(); + String XML = ""; + for (ValidationMode mode : ValidationMode.values()) { + mode.validate(schema, XML); + } // Then one with invalid element value - verifyRngFailure("", + verifyFailure("", schema, "invalid integer attribute value", "does not satisfy the \"integer\" type"); // And missing attribute - verifyRngFailure("", + verifyFailure("", schema, "missing integer attribute value", "is missing \"nr\" attribute"); // And then two variations of having empty value - verifyRngFailure("", + verifyFailure("", schema, "missing integer attribute value", "does not satisfy the \"integer\" type"); - verifyRngFailure("", + verifyFailure("", schema, "missing integer attribute value", "does not satisfy the \"integer\" type"); } @@ -450,19 +451,19 @@ public void testSimpleBooleanElem2() XMLValidationSchema schema = parseRngSchema(schemaDef); // First, a simple valid document - XMLStreamReader2 sr = getReader("abctrue"); - sr.validateAgainst(schema); - while (sr.next() != END_DOCUMENT) { } - sr.close(); + String XML = "abctrue"; + for (ValidationMode mode : ValidationMode.values()) { + mode.validate(schema, XML); + } // Then another valid, but with empty tag for leaf1 - sr = getReader("false"); - sr.validateAgainst(schema); - while (sr.next() != END_DOCUMENT) { } - sr.close(); + XML = "false"; + for (ValidationMode mode : ValidationMode.values()) { + mode.validate(schema, XML); + } // And then one more invalid case - verifyRngFailure("true false", + verifyFailure("true false", schema, "missing boolean element value", "does not satisfy the \"boolean\" type"); } @@ -478,20 +479,42 @@ public void testPartialValidationOk() * wrap the doc... */ String XML = - "\n" - +"\n" - +"\n" - +" foobar\n" - +" Foo Bar\n" - +"\n" - +"" - ; + "\n" + +"\n" + +"\n" + +" foobar\n" + +" Foo Bar\n" + +"\n" + +"" + ; XMLValidationSchema schema = parseRngSchema(SIMPLE_RNG_SCHEMA); + { + StringWriter writer = new StringWriter(); + SimpleNsStreamWriter sw = (SimpleNsStreamWriter) constructStreamWriter(writer, true, false); + _testPartialValidationOk(XML, schema, sw, writer); + } + { + StringWriter writer = new StringWriter(); + NonNsStreamWriter sw = (NonNsStreamWriter) constructStreamWriter(writer, false, false); + _testPartialValidationOk(XML, schema, sw, writer); + } + } + + private void _testPartialValidationOk(String XML, XMLValidationSchema schema, XMLStreamWriter2 sw, StringWriter writer) throws XMLStreamException { XMLStreamReader2 sr = getReader(XML); assertTokenType(START_ELEMENT, sr.next()); + sw.copyEventFromReader(sr, false); + sr.validateAgainst(schema); - while (sr.next() != END_DOCUMENT) { } + sw.validateAgainst(schema); + while (sr.hasNext()) { + sr.next(); + sw.copyEventFromReader(sr, false); + } sr.close(); + sw.close(); + + assertEquals(XML, writer.toString()); } @@ -508,37 +531,4 @@ private XMLStreamReader2 getReader(String contents) throws XMLStreamException return constructStreamReader(f, contents); } - private void verifyRngFailure(String xml, XMLValidationSchema schema, String failMsg, String failPhrase) - throws XMLStreamException - { - // By default, yes we are strict... - verifyRngFailure(xml, schema, failMsg, failPhrase, true); - } - - private void verifyRngFailure(String xml, XMLValidationSchema schema, String failMsg, String failPhrase, - boolean strict) - throws XMLStreamException - { - XMLStreamReader2 sr = getReader(xml); - sr.validateAgainst(schema); - try { - while (sr.hasNext()) { - /*int type =*/ sr.next(); - } - fail("Expected validity exception for "+failMsg); - } catch (XMLValidationException vex) { - String origMsg = vex.getMessage(); - String msg = (origMsg == null) ? "" : origMsg.toLowerCase(); - if (msg.indexOf(failPhrase.toLowerCase()) < 0) { - String actualMsg = "Expected validation exception for "+failMsg+", containing phrase '"+failPhrase+"': got '"+origMsg+"'"; - if (strict) { - fail(actualMsg); - } - warn("suppressing failure due to MSV bug, failure: '"+actualMsg+"'"); - } - // should get this specific type; not basic stream exception - } catch (XMLStreamException sex) { - fail("Expected XMLValidationException for "+failMsg); - } - } } diff --git a/src/test/java/wstxtest/vstream/TestW3CSchemaComplexTypes.java b/src/test/java/wstxtest/vstream/TestW3CSchemaComplexTypes.java index cdf58b69..bc84f9dd 100644 --- a/src/test/java/wstxtest/vstream/TestW3CSchemaComplexTypes.java +++ b/src/test/java/wstxtest/vstream/TestW3CSchemaComplexTypes.java @@ -1,12 +1,18 @@ package wstxtest.vstream; +import java.io.StringWriter; + import javax.xml.stream.XMLStreamException; import org.codehaus.stax2.XMLInputFactory2; import org.codehaus.stax2.XMLStreamReader2; -import org.codehaus.stax2.validation.XMLValidationException; +import org.codehaus.stax2.XMLStreamWriter2; import org.codehaus.stax2.validation.XMLValidationSchema; +import com.ctc.wstx.sw.NonNsStreamWriter; +import com.ctc.wstx.sw.RepairingNsStreamWriter; +import com.ctc.wstx.sw.SimpleNsStreamWriter; + public class TestW3CSchemaComplexTypes extends BaseValidationTest { @@ -44,21 +50,54 @@ public void testMSVGithubIssue2() throws Exception +"" +"" +""); - XMLStreamReader2 sr = getReader("" - +"" + String XML = "" + +"" +"" - +""); + +""; + + { + StringWriter writer = new StringWriter(); + SimpleNsStreamWriter sw = (SimpleNsStreamWriter) constructStreamWriter(writer, true, false); + _testMSVGithubIssue2(schema, XML, sw, writer); + } + { + StringWriter writer = new StringWriter(); + RepairingNsStreamWriter sw = (RepairingNsStreamWriter) constructStreamWriter(writer, true, true); + _testMSVGithubIssue2(schema, XML, sw, writer); + } + } + + private void _testMSVGithubIssue2(XMLValidationSchema schema, String XML, XMLStreamWriter2 sw, StringWriter writer) throws XMLStreamException { + XMLStreamReader2 sr = getReader(XML); sr.validateAgainst(schema); + sw.validateAgainst(schema); + assertTokenType(START_ELEMENT, sr.next()); assertEquals("Root", sr.getLocalName()); + sw.copyEventFromReader(sr, false); + assertTokenType(START_ELEMENT, sr.next()); assertEquals("Child", sr.getLocalName()); + sw.copyEventFromReader(sr, false); + assertTokenType(END_ELEMENT, sr.next()); + sw.copyEventFromReader(sr, false); + assertTokenType(END_ELEMENT, sr.next()); + sw.copyEventFromReader(sr, false); + assertTokenType(END_DOCUMENT, sr.next()); + sw.copyEventFromReader(sr, false); + + assertTokenType(END_DOCUMENT, sr.getEventType()); + + sr.close(); + sw.close(); - assertTokenType(END_DOCUMENT, sr.getEventType()); + // the writers collapse empty elements + String expectedXML = XML.replace(">", "/>"); + assertEquals(expectedXML, writer.toString()); } /* From 59a7c5a312d60d142b06cb91a68471641091e401 Mon Sep 17 00:00:00 2001 From: Peter Palaga Date: Sat, 13 Jan 2024 17:37:31 +0100 Subject: [PATCH 3/5] Add a reproducer for #190 'Element has no attribute "verbose"' not thrown from RepairingNsStreamWriter when validating against a DTD schema --- .../failing/TestInvalidAttributeValue190.java | 74 +++++++++++++++++++ src/test/java/failing/TestRelaxNG190.java | 43 +++++++++++ .../vstream/TestInvalidAttributeValue.java | 8 -- .../java/wstxtest/vstream/TestRelaxNG.java | 4 +- 4 files changed, 119 insertions(+), 10 deletions(-) create mode 100644 src/test/java/failing/TestInvalidAttributeValue190.java create mode 100644 src/test/java/failing/TestRelaxNG190.java diff --git a/src/test/java/failing/TestInvalidAttributeValue190.java b/src/test/java/failing/TestInvalidAttributeValue190.java new file mode 100644 index 00000000..edac3cb4 --- /dev/null +++ b/src/test/java/failing/TestInvalidAttributeValue190.java @@ -0,0 +1,74 @@ +package failing; + +import stax2.BaseStax2Test; + +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.stream.*; + +import org.codehaus.stax2.XMLStreamReader2; +import org.codehaus.stax2.validation.ValidationProblemHandler; +import org.codehaus.stax2.validation.XMLValidationException; +import org.codehaus.stax2.validation.XMLValidationProblem; +import org.codehaus.stax2.validation.XMLValidationSchema; +import org.codehaus.stax2.validation.XMLValidationSchemaFactory; + +import com.ctc.wstx.sw.RepairingNsStreamWriter; + +public class TestInvalidAttributeValue190 + extends BaseStax2Test +{ + /* A reproducer for https://github.com/FasterXML/woodstox/issues/190 */ + public void testInvalidAttributeValue() throws Exception + { + final String DOC = ""; + + final String INPUT_DTD = +"\n" ++"\n" +; + + XMLInputFactory f = getInputFactory(); + setCoalescing(f, true); + + XMLValidationSchemaFactory schemaFactory = + XMLValidationSchemaFactory.newInstance(XMLValidationSchema.SCHEMA_ID_DTD); + XMLValidationSchema schema = schemaFactory.createSchema(new StringReader(INPUT_DTD)); + XMLStreamReader2 sr = (XMLStreamReader2)f.createXMLStreamReader( + new StringReader(DOC)); + + final List probs = new ArrayList(); + + sr.validateAgainst(schema); + sr.setValidationProblemHandler(new ValidationProblemHandler() { + @Override + public void reportProblem(XMLValidationProblem problem) + throws XMLValidationException { + probs.add(problem); + } + }); + + assertTokenType(START_ELEMENT, sr.next()); + assertEquals("root", sr.getLocalName()); + + final String verboseValue = sr.getAttributeValue(null, "verbose"); + + assertEquals("yes", verboseValue); + + assertEquals(1, probs.size()); + assertEquals("Element has no attribute \"verbose\"", probs.get(0).getMessage()); + + // now do the same on the writer side + // and make sure that the reported problems are the same + { + // RepairingNsStreamWriter + StringWriter writer = new StringWriter(); + RepairingNsStreamWriter sw = (RepairingNsStreamWriter) stax2.BaseStax2Test.constructStreamWriter(writer, true, true); + validateWriter(DOC, probs, f, schema, writer, sw); + } + + } +} diff --git a/src/test/java/failing/TestRelaxNG190.java b/src/test/java/failing/TestRelaxNG190.java new file mode 100644 index 00000000..658e2745 --- /dev/null +++ b/src/test/java/failing/TestRelaxNG190.java @@ -0,0 +1,43 @@ +package failing; + +import java.io.StringWriter; + +import javax.xml.stream.*; + +import org.codehaus.stax2.validation.*; + +import com.ctc.wstx.sw.RepairingNsStreamWriter; + +/** + * A reproducer for https://github.com/FasterXML/woodstox/issues/190 + * Move to {@link wstxtest.vstream.TestRelaxNG} once fixed. + */ +public class TestRelaxNG190 + extends wstxtest.vstream.TestRelaxNG +{ + + public void testPartialValidationOk() + throws XMLStreamException + { + /* Hmmh... RelaxNG does define expected root. So need to + * wrap the doc... + */ + String XML = + "\n" + +"\n" + +"\n" + +" foobar\n" + +" Foo Bar\n" + +"\n" + +"" + ; + XMLValidationSchema schema = parseRngSchema(SIMPLE_RNG_SCHEMA); + { + StringWriter writer = new StringWriter(); + RepairingNsStreamWriter sw = (RepairingNsStreamWriter) constructStreamWriter(writer, true, true); + _testPartialValidationOk(XML, schema, sw, writer); + } + } + + +} diff --git a/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java b/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java index 4f89cc03..cd9c4001 100644 --- a/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java +++ b/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java @@ -16,7 +16,6 @@ import org.codehaus.stax2.validation.XMLValidationSchema; import org.codehaus.stax2.validation.XMLValidationSchemaFactory; -import com.ctc.wstx.sw.RepairingNsStreamWriter; import com.ctc.wstx.sw.SimpleNsStreamWriter; public class TestInvalidAttributeValue @@ -69,12 +68,5 @@ public void reportProblem(XMLValidationProblem problem) SimpleNsStreamWriter sw = (SimpleNsStreamWriter) stax2.BaseStax2Test.constructStreamWriter(writer, true, false); validateWriter(DOC, probs, f, schema, writer, sw); } - { - // RepairingNsStreamWriter - StringWriter writer = new StringWriter(); - RepairingNsStreamWriter sw = (RepairingNsStreamWriter) stax2.BaseStax2Test.constructStreamWriter(writer, true, true); - validateWriter(DOC, probs, f, schema, writer, sw); - } - } } diff --git a/src/test/java/wstxtest/vstream/TestRelaxNG.java b/src/test/java/wstxtest/vstream/TestRelaxNG.java index 6fac4b17..9817d318 100644 --- a/src/test/java/wstxtest/vstream/TestRelaxNG.java +++ b/src/test/java/wstxtest/vstream/TestRelaxNG.java @@ -18,7 +18,7 @@ public class TestRelaxNG extends BaseValidationTest { - final static String SIMPLE_RNG_SCHEMA = + protected final static String SIMPLE_RNG_SCHEMA = "\n" +" \n" +" \n" @@ -500,7 +500,7 @@ public void testPartialValidationOk() } } - private void _testPartialValidationOk(String XML, XMLValidationSchema schema, XMLStreamWriter2 sw, StringWriter writer) throws XMLStreamException { + protected void _testPartialValidationOk(String XML, XMLValidationSchema schema, XMLStreamWriter2 sw, StringWriter writer) throws XMLStreamException { XMLStreamReader2 sr = getReader(XML); assertTokenType(START_ELEMENT, sr.next()); sw.copyEventFromReader(sr, false); From e9d41a2f11c4caa9bc706965a7842a7805c05845 Mon Sep 17 00:00:00 2001 From: Peter Palaga Date: Sat, 13 Jan 2024 15:51:36 +0100 Subject: [PATCH 4/5] Add a reproducer for #189 'Undefined ID' not thrown when validating with SimpleNsStreamWriter --- src/test/java/failing/TestRelaxNG189.java | 62 +++++++++++++++++++ src/test/java/failing/TestW3CSchema189.java | 29 +++++++++ src/test/java/wstxtest/msv/TestW3CSchema.java | 4 +- 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 src/test/java/failing/TestRelaxNG189.java create mode 100644 src/test/java/failing/TestW3CSchema189.java diff --git a/src/test/java/failing/TestRelaxNG189.java b/src/test/java/failing/TestRelaxNG189.java new file mode 100644 index 00000000..45a6e34f --- /dev/null +++ b/src/test/java/failing/TestRelaxNG189.java @@ -0,0 +1,62 @@ +package failing; + +import javax.xml.stream.*; + +import org.codehaus.stax2.validation.*; + +import wstxtest.vstream.BaseValidationTest; + +/** + * A reproducer for https://github.com/FasterXML/woodstox/issues/189 + * Move to {@link wstxtest.vstream.TestRelaxNG} once fixed. + */ +public class TestRelaxNG189 + extends BaseValidationTest +{ + + /** + * Test case for testing handling ID/IDREF/IDREF attributes, using + * schema datatype library. + */ + public void testSimpleIdAttrsWriter() + throws XMLStreamException + { + final String schemaDef = + "\n" + +" \n" + +" \n" + +" \n" + +" \n" + +" \n" + +" \n" + +" \n" + +" \n" + +" \n" + +" \n" + +" \n" + +"" + ; + + XMLValidationSchema schema = parseRngSchema(schemaDef); + + String XML; + + // And then invalid one, with dangling ref + XML = "" + +" \n" + +"" + ; + verifyFailure(XML, schema, "reference to undefined id", + "Undefined ID", true, false, true); + + // and another one with some of refs undefined + XML = "" + +" \n" + +"" + ; + verifyFailure(XML, schema, "reference to undefined id", + "Undefined ID", true, false, true); + } + +} diff --git a/src/test/java/failing/TestW3CSchema189.java b/src/test/java/failing/TestW3CSchema189.java new file mode 100644 index 00000000..a505652e --- /dev/null +++ b/src/test/java/failing/TestW3CSchema189.java @@ -0,0 +1,29 @@ +package failing; + +import javax.xml.stream.XMLStreamException; + +import org.codehaus.stax2.validation.XMLValidationSchema; + +import wstxtest.msv.TestW3CSchema; + +/** + */ +public class TestW3CSchema189 + extends TestW3CSchema +{ + + /** + * A reproducer for https://github.com/FasterXML/woodstox/issues/189 + * Move to {@link TestW3CSchema} once fixed. + */ + public void testSimpleNonNsUndefinedIdWriter189() throws XMLStreamException + { + XMLValidationSchema schema = parseW3CSchema(SIMPLE_NON_NS_SCHEMA); + String XML = "" + + "FG" + + ""; + verifyFailure(XML, schema, "undefined referenced id ('m3')", + "Undefined ID 'm3'", true, false, true); + } + +} diff --git a/src/test/java/wstxtest/msv/TestW3CSchema.java b/src/test/java/wstxtest/msv/TestW3CSchema.java index 45de8834..d894cba5 100644 --- a/src/test/java/wstxtest/msv/TestW3CSchema.java +++ b/src/test/java/wstxtest/msv/TestW3CSchema.java @@ -24,7 +24,7 @@ public class TestW3CSchema /** * Sample schema, using sample 'personal.xsd' found from the web */ - final static String SIMPLE_NON_NS_SCHEMA = "\n" + protected final static String SIMPLE_NON_NS_SCHEMA = "\n" + "\n" + "\n" + "\n" @@ -173,7 +173,7 @@ public void testSimpleNonNsUndefinedId() throws XMLStreamException + "FG" + ""; verifyFailure(XML, schema, "undefined referenced id ('m3')", - "Undefined ID 'm3'"); + "Undefined ID 'm3'", true, true, false); } public void testSimpleDataTypes() throws XMLStreamException From 47b32c7733e2626a00ca1de0706579bb89bf4f3b Mon Sep 17 00:00:00 2001 From: Peter Palaga Date: Sun, 14 Jan 2024 02:13:54 +0100 Subject: [PATCH 5/5] WstxValidationException: Unknown reason (at end element ) when validating a document with nillable elements fix #179 fix #190 --- .../com/ctc/wstx/sw/BaseNsStreamWriter.java | 206 ++++++++---- .../com/ctc/wstx/sw/BaseStreamWriter.java | 111 ++++--- .../com/ctc/wstx/sw/NonNsStreamWriter.java | 298 ++++++++++++++---- .../ctc/wstx/sw/RepairingNsStreamWriter.java | 23 +- .../com/ctc/wstx/sw/SimpleNsStreamWriter.java | 11 +- .../com/ctc/wstx/sw/SimpleOutputElement.java | 218 +++++++++++-- .../failing/TestInvalidAttributeValue190.java | 74 ----- src/test/java/failing/TestRelaxNG190.java | 43 --- .../failing/TestW3CSchemaNillable179.java | 177 ----------- .../vwstream/TestAttributeValidation.java | 60 ++-- .../stax2/vwstream/TestOutputValidation.java | 12 +- .../vwstream/TestStructuralValidation.java | 15 +- .../vstream/TestInvalidAttributeValue.java | 7 + .../java/wstxtest/vstream/TestRelaxNG.java | 12 + .../vstream/TestW3CSchemaNillable.java | 173 ++++++++++ src/test/resources/wstxtest/msv/nillable.xsd | 14 - .../wstxtest/msv/nillableDateTime.xml | 3 - .../resources/wstxtest/msv/nillableInt.xml | 3 - .../resources/wstxtest/msv/nillableString.xml | 3 - 19 files changed, 910 insertions(+), 553 deletions(-) delete mode 100644 src/test/java/failing/TestInvalidAttributeValue190.java delete mode 100644 src/test/java/failing/TestRelaxNG190.java delete mode 100644 src/test/java/failing/TestW3CSchemaNillable179.java create mode 100644 src/test/java/wstxtest/vstream/TestW3CSchemaNillable.java delete mode 100644 src/test/resources/wstxtest/msv/nillable.xsd delete mode 100644 src/test/resources/wstxtest/msv/nillableDateTime.xml delete mode 100644 src/test/resources/wstxtest/msv/nillableInt.xml delete mode 100644 src/test/resources/wstxtest/msv/nillableString.xml diff --git a/src/main/java/com/ctc/wstx/sw/BaseNsStreamWriter.java b/src/main/java/com/ctc/wstx/sw/BaseNsStreamWriter.java index 403e53b7..5dee4438 100644 --- a/src/main/java/com/ctc/wstx/sw/BaseNsStreamWriter.java +++ b/src/main/java/com/ctc/wstx/sw/BaseNsStreamWriter.java @@ -24,11 +24,14 @@ import javax.xml.stream.events.StartElement; import org.codehaus.stax2.ri.typed.AsciiValueEncoder; +import org.codehaus.stax2.validation.XMLValidationException; +import org.codehaus.stax2.validation.XMLValidationSchema; +import org.codehaus.stax2.validation.XMLValidator; import com.ctc.wstx.api.EmptyElementHandler; import com.ctc.wstx.api.WriterConfig; +import com.ctc.wstx.api.WstxInputProperties; import com.ctc.wstx.cfg.ErrorConsts; -import com.ctc.wstx.cfg.XmlConsts; import com.ctc.wstx.exc.WstxIOException; import com.ctc.wstx.util.DefaultXmlSymbolTable; @@ -246,9 +249,6 @@ public void writeEmptyElement(String localName) throws XMLStreamException { checkStartElement(localName, null); - if (mValidator != null) { - mValidator.validateElementStart(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); - } mEmptyElement = true; if (mOutputElemPool != null) { SimpleOutputElement newCurr = mOutputElemPool; @@ -294,9 +294,6 @@ public void writeStartElement(String localName) throws XMLStreamException { checkStartElement(localName, null); - if (mValidator != null) { - mValidator.validateElementStart(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); - } mEmptyElement = false; if (mOutputElemPool != null) { SimpleOutputElement newCurr = mOutputElemPool; @@ -334,11 +331,11 @@ protected void writeTypedAttribute(String prefix, String nsURI, String localName if (!mStartElementOpen) { throwOutputError(ErrorConsts.WERR_ATTR_NO_ELEM); } - if (mCheckAttrs) { // still need to ensure no duplicate attrs? - mCurrElem.checkAttrWrite(nsURI, localName); - } try { if (mValidator == null) { + if (mCheckAttrs) { // still need to ensure no duplicate attrs? + mCurrElem.addAttribute(nsURI, localName, null, null); + } if (prefix == null || prefix.length() == 0) { mWriter.writeTypedAttribute(localName, enc); } else { @@ -346,7 +343,7 @@ protected void writeTypedAttribute(String prefix, String nsURI, String localName } } else { mWriter.writeTypedAttribute - (prefix, localName, nsURI, enc, mValidator, getCopyBuffer()); + (prefix, localName, nsURI, enc, mCurrElem.getAttributeCollector(), getCopyBuffer()); } } catch (IOException ioe) { throw new WstxIOException(ioe); @@ -430,7 +427,16 @@ protected void closeStartElement(boolean emptyElem) } if (mValidator != null) { - mVldContent = mValidator.validateElementAndAttributes(); + try { + mVldContent = mCurrElem.validateElementStartAndAttributes(); + if (emptyElem) { + mVldContent = mValidator.validateElementEnd + (mCurrElem.getLocalName(), mCurrElem.getNamespaceURI(), mCurrElem.getPrefix()); + } + } catch (XMLValidationException e) { + mVldException = e; + throw e; + } } // Need bit more special handling for empty elements... @@ -440,10 +446,6 @@ protected void closeStartElement(boolean emptyElem) if (mCurrElem.isRoot()) { // Did we close the root? (isRoot() returns true for the virtual "document node") mState = STATE_EPILOG; } - if (mValidator != null) { - mVldContent = mValidator.validateElementEnd - (curr.getLocalName(), curr.getNamespaceURI(), curr.getPrefix()); - } if (mPoolSize < MAX_POOL_SIZE) { curr.addToPool(mOutputElemPool); mOutputElemPool = curr; @@ -471,6 +473,9 @@ protected String getTopElementDesc() { protected void checkStartElement(String localName, String prefix) throws XMLStreamException { + if (mVldException != null) { + throw new XMLStreamException("Cannot start an element after a validation error", mVldException); + } // Need to finish an open start element? if (mStartElementOpen) { closeStartElement(mEmptyElement); @@ -493,13 +498,14 @@ protected final void doWriteAttr(String localName, String nsURI, String prefix, String value) throws XMLStreamException { - if (mCheckAttrs) { // still need to ensure no duplicate attrs? - mCurrElem.checkAttrWrite(nsURI, localName); - } - if (mValidator != null) { - // No need to get it normalized... even if validator does normalize - // it, we don't use that for anything - mValidator.validateAttribute(localName, nsURI, prefix, value); + if (mCheckAttrs) { + // ensure no duplicate attrs and possibly pass them to validator when closing the start element + try { + mCurrElem.addAttribute(nsURI, localName, prefix, value); + } catch (XMLValidationException e) { + mVldException = e; + throw e; + } } try { int vlen = value.length(); @@ -532,29 +538,6 @@ protected final void doWriteAttr(String localName, String nsURI, String prefix, } } - protected final void doWriteAttr(String localName, String nsURI, String prefix, - char[] buf, int start, int len) - throws XMLStreamException - { - if (mCheckAttrs) { // still need to ensure no duplicate attrs? - mCurrElem.checkAttrWrite(nsURI, localName); - } - if (mValidator != null) { - // No need to get it normalized... even if validator does normalize - // it, we don't use that for anything - mValidator.validateAttribute(localName, nsURI, prefix, buf, start, len); - } - try { - if (prefix != null && prefix.length() > 0) { - mWriter.writeAttribute(prefix, localName, buf, start, len); - } else { - mWriter.writeAttribute(localName, buf, start, len); - } - } catch (IOException ioe) { - throw new WstxIOException(ioe); - } - } - protected void doWriteNamespace(String prefix, String nsURI) throws XMLStreamException { @@ -653,11 +636,28 @@ protected void doWriteEndTag(QName expName, boolean allowEmpty) } // Better have something to close... (to figure out what to close) - if (mState != STATE_TREE) { + if (mVldException != null) { + throw new XMLStreamException("Cannot start an element after a validation error", mVldException); + } else if (mState != STATE_TREE) { // Have to always throw exception... don't necessarily know the name reportNwfStructure("No open start element, when trying to write end element"); } + if (mStartElementOpen) { + if (mValidator != null) { + // We need to validate here, before we move the mCurrElem + try { + /* Note: return value is not of much use, since the + * element will be closed right away... + */ + mVldContent = mCurrElem.validateElementStartAndAttributes(); + } catch (XMLValidationException e) { + mVldException = e; + throw e; + } + } + } + SimpleOutputElement thisElem = mCurrElem; String prefix = thisElem.getPrefix(); String localName = thisElem.getLocalName(); @@ -691,12 +691,6 @@ protected void doWriteEndTag(QName expName, boolean allowEmpty) /* Can't/shouldn't call closeStartElement, but need to do same * processing. Thus, this is almost identical to closeStartElement: */ - if (mValidator != null) { - /* Note: return value is not of much use, since the - * element will be closed right away... - */ - mVldContent = mValidator.validateElementAndAttributes(); - } mStartElementOpen = false; try { //If an EmptyElementHandler is provided use it to determine if allowEmpty is set @@ -710,7 +704,12 @@ protected void doWriteEndTag(QName expName, boolean allowEmpty) mState = STATE_EPILOG; } if (mValidator != null) { - mVldContent = mValidator.validateElementEnd(localName, nsURI, prefix); + try { + mVldContent = mValidator.validateElementEnd(localName, nsURI, prefix); + } catch (XMLValidationException e) { + mVldException = e; + throw e; + } } return; } @@ -733,7 +732,12 @@ protected void doWriteEndTag(QName expName, boolean allowEmpty) // Ok, time to validate... if (mValidator != null) { - mVldContent = mValidator.validateElementEnd(localName, nsURI, prefix); + try { + mVldContent = mValidator.validateElementEnd(localName, nsURI, prefix); + } catch (XMLValidationException e) { + mVldException = e; + throw e; + } } } @@ -763,4 +767,96 @@ protected abstract void writeStartOrEmpty(String localName, String nsURI) protected abstract void writeStartOrEmpty(String prefix, String localName, String nsURI) throws XMLStreamException; + + /* + /////////////////////////////////////////////////////////////////////// + // Attribute access + /////////////////////////////////////////////////////////////////////// + */ + + @Override + public int getAttributeCount() + { + return mCurrElem.getAttributeCount(); + } + + @Override + public String getAttributeLocalName(int index) + { + return mCurrElem.getAttributeLocalName(index); + } + + @Override + public String getAttributeNamespace(int index) + { + return mCurrElem.getAttributeNamespace(index); + } + + @Override + public String getAttributePrefix(int index) + { + return mCurrElem.getAttributePrefix(index); + } + + @Override + public String getAttributeValue(int index) + { + return mCurrElem.getAttributeValue(index); + } + + @Override + public String getAttributeValue(String nsURI, String localName) + { + return mCurrElem.getAttributeValue(nsURI, localName); + } + + @Override + public String getAttributeType(int index) { + return (mValidator == null) ? WstxInputProperties.UNKNOWN_ATTR_TYPE : + mValidator.getAttributeType(index); + } + + @Override + public int findAttributeIndex(String nsURI, String localName) + { + return mCurrElem.findAttributeIndex(nsURI, localName); + } + + /* + /////////////////////////////////////////////////////////////////////// + // Overrides to keep the validator up to date in SimpleOutputElement instances + /////////////////////////////////////////////////////////////////////// + */ + + @Override + public XMLValidator validateAgainst(XMLValidationSchema schema) throws XMLStreamException { + final XMLValidator validateAgainst = super.validateAgainst(schema); + mCurrElem.setValidator(mValidator); + if (mOutputElemPool != null) { + mOutputElemPool.setValidator(mValidator); + } + return validateAgainst; + } + + @Override + public XMLValidator stopValidatingAgainst(XMLValidationSchema schema) throws XMLStreamException { + final XMLValidator result = super.stopValidatingAgainst(schema); + mCurrElem.setValidator(mValidator); + if (mOutputElemPool != null) { + mOutputElemPool.setValidator(mValidator); + } + return result; + } + + @Override + public XMLValidator stopValidatingAgainst(XMLValidator validator) throws XMLStreamException { + final XMLValidator result = super.stopValidatingAgainst(validator); + mCurrElem.setValidator(mValidator); + if (mOutputElemPool != null) { + mOutputElemPool.setValidator(mValidator); + } + return result; + } + + } diff --git a/src/main/java/com/ctc/wstx/sw/BaseStreamWriter.java b/src/main/java/com/ctc/wstx/sw/BaseStreamWriter.java index da6bca6c..dd033806 100644 --- a/src/main/java/com/ctc/wstx/sw/BaseStreamWriter.java +++ b/src/main/java/com/ctc/wstx/sw/BaseStreamWriter.java @@ -194,6 +194,12 @@ public abstract class BaseStreamWriter */ protected int mVldContent = XMLValidator.CONTENT_ALLOW_ANY_TEXT; + /** + * If set, any attempts to write something must fail or be avoided + * (e.g. when auto-closing the tree). + */ + protected XMLValidationException mVldException; + /** * Value passed as the expected root element, when using the multiple * argument {@link #writeDTD} method. Will be used in structurally @@ -331,9 +337,14 @@ public void writeCData(String data) verifyWriteCData(); if (mVldContent == XMLValidator.CONTENT_ALLOW_VALIDATABLE_TEXT && mValidator != null) { - // Last arg is false, since we do not know if more text - // may be added with additional calls - mValidator.validateText(data, false); + try { + // Last arg is false, since we do not know if more text + // may be added with additional calls + mValidator.validateText(data, false); + } catch (XMLValidationException e) { + mVldException = e; + throw e; + } } int ix; try { @@ -375,9 +386,14 @@ public void writeCharacters(char[] text, int start, int len) } } else if (mVldContent == XMLValidator.CONTENT_ALLOW_VALIDATABLE_TEXT) { if (mValidator != null) { - // Last arg is false, since we do not know if more text - // may be added with additional calls - mValidator.validateText(text, start, start + len, false); + try { + // Last arg is false, since we do not know if more text + // may be added with additional calls + mValidator.validateText(text, start, start + len, false); + } catch (XMLValidationException e) { + mVldException = e; + throw e; + } } } @@ -431,10 +447,15 @@ public void writeCharacters(String text) } } else if (mVldContent == XMLValidator.CONTENT_ALLOW_VALIDATABLE_TEXT) { if (mValidator != null) { - /* Last arg is false, since we do not know if more text - * may be added with additional calls - */ - mValidator.validateText(text, false); + try { + /* Last arg is false, since we do not know if more text + * may be added with additional calls + */ + mValidator.validateText(text, false); + } catch (XMLValidationException e) { + mVldException = e; + throw e; + } } } @@ -1079,10 +1100,15 @@ public void writeCData(char[] cbuf, int start, int len) verifyWriteCData(); if (mVldContent == XMLValidator.CONTENT_ALLOW_VALIDATABLE_TEXT && mValidator != null) { - /* Last arg is false, since we do not know if more text - * may be added with additional calls - */ - mValidator.validateText(cbuf, start, start + len, false); + try { + /* Last arg is false, since we do not know if more text + * may be added with additional calls + */ + mValidator.validateText(cbuf, start, start + len, false); + } catch (XMLValidationException e) { + mVldException = e; + throw e; + } } int ix; try { @@ -1277,42 +1303,6 @@ public int addDefaultAttribute(String localName, String uri, String prefix, @Override public boolean isUnparsedEntityDeclared(String name) { return false; } - // // // Attribute access: not yet implemented: - - /* !!! TODO: Implement attribute access (iff validate-attributes - * enabled? - */ - - @Override - public int getAttributeCount() { return 0; } - - @Override - public String getAttributeLocalName(int index) { return null; } - - @Override - public String getAttributeNamespace(int index) { return null; } - - @Override - public String getAttributePrefix(int index) { return null; } - - @Override - public String getAttributeValue(int index) { return null; } - - @Override - public String getAttributeValue(String nsURI, String localName) { - return null; - } - - @Override - public String getAttributeType(int index) { - return ""; - } - - @Override - public int findAttributeIndex(String nsURI, String localName) { - return -1; - } - /* /////////////////////////////////////////////////////////// // Package methods (ie not part of public API) @@ -1409,10 +1399,15 @@ public void writeCharacters(Characters ch) } } else if (mVldContent == XMLValidator.CONTENT_ALLOW_VALIDATABLE_TEXT) { if (mValidator != null) { - /* Last arg is false, since we do not know if more text - * may be added with additional calls - */ - mValidator.validateText(ch.getData(), false); + try { + /* Last arg is false, since we do not know if more text + * may be added with additional calls + */ + mValidator.validateText(ch.getData(), false); + } catch (XMLValidationException e) { + mVldException = e; + throw e; + } } } @@ -1445,8 +1440,8 @@ protected final boolean inPrologOrEpilog() { private final void _finishDocument(boolean forceRealClose) throws XMLStreamException { - // Is tree still open? - if (mState != STATE_EPILOG) { + // Is tree still open or worth to write anything? + if (mState != STATE_EPILOG && mVldException == null) { if (mCheckStructure && mState == STATE_PROLOG) { reportNwfStructure("Trying to write END_DOCUMENT when document has no root (ie. trying to output empty document)."); } @@ -1459,10 +1454,10 @@ private final void _finishDocument(boolean forceRealClose) /* 17-Nov-2008, TSa: that is, if we are allowed to do it * (see [WSTX-165]) */ - if (mState != STATE_EPILOG && mConfig.automaticEndElementsEnabled()) { + if (mState != STATE_EPILOG && mVldException == null && mConfig.automaticEndElementsEnabled()) { do { writeEndElement(); - } while (mState != STATE_EPILOG); + } while (mState != STATE_EPILOG && mVldException == null); } } diff --git a/src/main/java/com/ctc/wstx/sw/NonNsStreamWriter.java b/src/main/java/com/ctc/wstx/sw/NonNsStreamWriter.java index a0030dd1..6542f492 100644 --- a/src/main/java/com/ctc/wstx/sw/NonNsStreamWriter.java +++ b/src/main/java/com/ctc/wstx/sw/NonNsStreamWriter.java @@ -27,8 +27,12 @@ import javax.xml.stream.events.StartElement; import org.codehaus.stax2.ri.typed.AsciiValueEncoder; +import org.codehaus.stax2.validation.XMLValidationException; +import org.codehaus.stax2.validation.XMLValidationSchema; +import org.codehaus.stax2.validation.XMLValidator; import com.ctc.wstx.api.WriterConfig; +import com.ctc.wstx.api.WstxInputProperties; import com.ctc.wstx.cfg.ErrorConsts; import com.ctc.wstx.cfg.XmlConsts; import com.ctc.wstx.sr.AttributeCollector; @@ -58,14 +62,13 @@ public class NonNsStreamWriter final StringVector mElements; /** - * Container for attribute names for current element; used only - * if uniqueness of attribute names is to be enforced. - *

- * TreeSet is used mostly because clearing it up is faster than - * clearing up HashSet, and the only access is done by - * adding entries and see if an value was already set. + * Attributes of the current element; used + * if uniqueness of attribute names is to be enforced + * or if a validator is set. */ - TreeSet mAttrNames; + private HashMap mAttrMap; + private ArrayList mAttrList; + private AttrCollector mAttrCollector; /* //////////////////////////////////////////////////// @@ -125,20 +128,9 @@ public void writeAttribute(String localName, String value) /* 11-Dec-2005, TSa: Should use a more efficient Set/Map value * for this in future. */ - if (mAttrNames == null) { - mAttrNames = new TreeSet(); - } - if (!mAttrNames.add(localName)) { - reportNwfAttr("Trying to write attribute '"+localName+"' twice"); - } + addAttribute(localName, value); } - if (mValidator != null) { - /* No need to get it normalized... even if validator does normalize - * it, we don't use that for anything - */ - mValidator.validateAttribute(localName, XmlConsts.ATTR_NO_NS_URI, XmlConsts.ATTR_NO_PREFIX, value); - } - + try { mWriter.writeAttribute(localName, value); } catch (IOException ioe) { @@ -263,9 +255,9 @@ public void writeStartElement(StartElement elem) QName name = elem.getName(); writeStartElement(name.getLocalPart()); @SuppressWarnings("unchecked") - Iterator it = elem.getAttributes(); + Iterator it = elem.getAttributes(); while (it.hasNext()) { - Attribute attr = it.next(); + javax.xml.stream.events.Attribute attr = it.next(); name = attr.getName(); writeAttribute(name.getLocalPart(), attr.getValue()); } @@ -293,20 +285,17 @@ protected void writeTypedAttribute(String prefix, String nsURI, String localName if (!mStartElementOpen && mCheckStructure) { reportNwfStructure(ErrorConsts.WERR_ATTR_NO_ELEM); } - if (mCheckAttrs) { // doh. Not good, need to construct non-transient value... - if (mAttrNames == null) { - mAttrNames = new TreeSet(); - } - if (!mAttrNames.add(localName)) { - reportNwfAttr("Trying to write attribute '"+localName+"' twice"); - } - } try { - if (mValidator == null) { + if (!mCheckAttrs) { mWriter.writeTypedAttribute(localName, enc); } else { - mWriter.writeTypedAttribute(null, localName, null, enc, mValidator, getCopyBuffer()); + if (mAttrCollector == null) { + mAttrCollector = new AttrCollector(); + } + // mAttributes just checks the uniqueness of the attribute name and stores the name and value. + // We will pass it to the real validator later + mWriter.writeTypedAttribute(null, localName, null, enc, mAttrCollector, getCopyBuffer()); } } catch (IOException ioe) { throwFromIOE(ioe); @@ -324,9 +313,6 @@ protected void closeStartElement(boolean emptyElem) throws XMLStreamException { mStartElementOpen = false; - if (mAttrNames != null) { - mAttrNames.clear(); - } try { if (emptyElem) { @@ -339,18 +325,27 @@ protected void closeStartElement(boolean emptyElem) } if (mValidator != null) { - mVldContent = mValidator.validateElementAndAttributes(); + String localName = mElements.getLastString(); + try { + mVldContent = validateElementStartAndAttributes(localName); + if (emptyElem) { + mVldContent = mValidator.validateElementEnd(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); + } + } catch (XMLValidationException e) { + mVldException = e; + throw e; + } + } else if (mCheckAttrs) { + mAttrMap = null; + mAttrList = null; } // Need bit more special handling for empty elements... if (emptyElem) { - String localName = mElements.removeLast(); + mElements.removeLast(); if (mElements.isEmpty()) { mState = STATE_EPILOG; } - if (mValidator != null) { - mVldContent = mValidator.validateElementEnd(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); - } } } @@ -406,7 +401,14 @@ public void copyStartElement(InputElementStack elemStack, if (attrCount > 0) { for (int i = 0; i < attrCount; ++i) { - attrCollector.writeAttribute(i, mWriter, mValidator); + if (mCheckAttrs) { + if (mAttrCollector == null) { + mAttrCollector = new AttrCollector(); + } + attrCollector.writeAttribute(i, mWriter, mAttrCollector); + } else { + attrCollector.writeAttribute(i, mWriter, null); + } } } } @@ -422,6 +424,51 @@ public String validateQNamePrefix(QName name) { return name.getPrefix(); } + // // // Attribute access + + @Override + public int getAttributeCount() { + return mAttrList == null ? 0 : mAttrList.size(); + } + + @Override + public String getAttributeLocalName(int index) { + return mAttrList == null ? null : mAttrList.get(index).mLocalName; + } + + @Override + public String getAttributeNamespace(int index) + { + return XmlConsts.ATTR_NO_NS_URI; + } + + @Override + public String getAttributePrefix(int index) + { + return XmlConsts.ATTR_NO_PREFIX; + } + + @Override + public String getAttributeValue(int index) { + return mAttrList == null ? null : mAttrList.get(index).mValue; + } + + @Override + public String getAttributeValue(String nsURI, String localName) { + return mAttrMap == null ? null : mAttrMap.get(localName).mValue; + } + + @Override + public int findAttributeIndex(String nsURI, String localName) { + return mAttrMap == null ? null : mAttrMap.get(localName).mIndex; + } + + @Override + public String getAttributeType(int index) { + return (mValidator == null) ? WstxInputProperties.UNKNOWN_ATTR_TYPE : + mValidator.getAttributeType(index); + } + /* //////////////////////////////////////////////////// // Internal methods @@ -431,6 +478,9 @@ public String validateQNamePrefix(QName name) { private void doWriteStartElement(String localName) throws XMLStreamException { + if (mVldException != null) { + throw new XMLStreamException("Cannot start an element after a validation error", mVldException); + } mAnyOutput = true; // Need to finish an open start element? if (mStartElementOpen) { @@ -452,9 +502,6 @@ private void doWriteStartElement(String localName) /*if (mVldContent == XMLValidator.CONTENT_ALLOW_NONE) { // EMPTY content reportInvalidContent(START_ELEMENT); }*/ - if (mValidator != null) { - mValidator.validateElementStart(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); - } mStartElementOpen = true; mElements.addString(localName); @@ -479,6 +526,9 @@ private void doWriteStartElement(String localName) private void doWriteEndTag(String expName, boolean allowEmpty) throws XMLStreamException { + if (mVldException != null) { + throw new XMLStreamException("Cannot start an element after a validation error", mVldException); + } /* First of all, do we need to close up an earlier empty element? * (open start element that was not created via call to * writeEmptyElement gets handled later on) @@ -517,16 +567,7 @@ private void doWriteEndTag(String expName, boolean allowEmpty) /* Can't/shouldn't call closeStartElement, but need to do same * processing. Thus, this is almost identical to closeStartElement: */ - if (mValidator != null) { - /* Note: return value is not of much use, since the - * element will be closed right away... - */ - mVldContent = mValidator.validateElementAndAttributes(); - } mStartElementOpen = false; - if (mAttrNames != null) { - mAttrNames.clear(); - } try { // We could write an empty element, implicitly? if (allowEmpty) { @@ -535,10 +576,33 @@ private void doWriteEndTag(String expName, boolean allowEmpty) mState = STATE_EPILOG; } if (mValidator != null) { - mVldContent = mValidator.validateElementEnd(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); + try { + /* Note: return value is not of much use, since the + * element will be closed right away... + */ + mVldContent = validateElementStartAndAttributes(localName); + mVldContent = mValidator.validateElementEnd(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); + } catch (XMLValidationException e) { + mVldException = e; + throw e; + } } return; } + if (mValidator != null) { + try { + /* Note: return value is not of much use, since the + * element will be closed right away... + */ + mVldContent = validateElementStartAndAttributes(localName); + } catch (XMLValidationException e) { + mVldException = e; + throw e; + } + } else if (mCheckAttrs) { + mAttrMap = null; + mAttrList = null; + } // Nah, need to close open elem, and then output close elem mWriter.writeStartTagEnd(); } catch (IOException ioe) { @@ -558,7 +622,131 @@ private void doWriteEndTag(String expName, boolean allowEmpty) // Ok, time to validate... if (mValidator != null) { - mVldContent = mValidator.validateElementEnd(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); + try { + mVldContent = mValidator.validateElementEnd(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); + } catch (XMLValidationException e) { + mVldException = e; + throw e; + } + } + } + + private int validateElementStartAndAttributes(String localName) throws XMLStreamException { + final XMLValidator vld = mValidator; + vld.validateElementStart(localName, XmlConsts.ELEM_NO_NS_URI, XmlConsts.ELEM_NO_PREFIX); + ArrayList attrList = mAttrList; + if (attrList != null) { + mAttrMap = null; + mAttrList = null; + if (!attrList.isEmpty()) { + for (Attribute attr : attrList) { + vld.validateAttribute(attr.mLocalName, XmlConsts.ATTR_NO_NS_URI, XmlConsts.ATTR_NO_PREFIX, attr.mValue); + } + } + } + return vld.validateElementAndAttributes(); + } + + private void addAttribute(String localName, String value) throws XMLStreamException { + if (mAttrMap == null) { + mAttrMap = new HashMap(); + mAttrList = new ArrayList(); + } + Attribute attr = new Attribute(mAttrMap.size(), localName, value); + if (mAttrMap.put(localName, attr) != null) { + reportNwfAttr("Trying to write attribute '"+localName+"' twice"); + } else { + mAttrList.add(attr); } } + + final class AttrCollector extends XMLValidator { + + AttrCollector() { + super(); + } + + @Override + public String getSchemaType() { + throw new UnsupportedOperationException(); + } + + @Override + public XMLValidationSchema getSchema() { + throw new UnsupportedOperationException(); + } + + @Override + public void validateElementStart(String localName, String uri, String prefix) throws XMLStreamException { + throw new UnsupportedOperationException(); + } + + @Override + public String validateAttribute(String localName, String uri, String prefix, String value) throws XMLStreamException { + addAttribute(localName, value); + return value; + } + + @Override + public String validateAttribute(String localName, String uri, String prefix, char[] valueChars, int valueStart, + int valueEnd) throws XMLStreamException { + final String value = new String(valueChars, valueStart, valueEnd - valueStart); + addAttribute(localName, value); + return value; + } + + @Override + public int validateElementAndAttributes() throws XMLStreamException { + throw new UnsupportedOperationException(); + } + + @Override + public int validateElementEnd(String localName, String uri, String prefix) throws XMLStreamException { + throw new UnsupportedOperationException(); + } + + @Override + public void validateText(String text, boolean lastTextSegment) throws XMLStreamException { + throw new UnsupportedOperationException(); + } + + @Override + public void validateText(char[] cbuf, int textStart, int textEnd, boolean lastTextSegment) throws XMLStreamException { + throw new UnsupportedOperationException(); + } + + @Override + public void validationCompleted(boolean eod) throws XMLStreamException { + throw new UnsupportedOperationException(); + } + + @Override + public String getAttributeType(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public int getIdAttrIndex() { + throw new UnsupportedOperationException(); + } + + @Override + public int getNotationAttrIndex() { + throw new UnsupportedOperationException(); + } + + } + + static final class Attribute { + private final int mIndex; + private final String mLocalName; + private final String mValue; + public Attribute(int mIndex, String mLocalName, String mValue) { + super(); + this.mIndex = mIndex; + this.mLocalName = mLocalName; + this.mValue = mValue; + } + } + } diff --git a/src/main/java/com/ctc/wstx/sw/RepairingNsStreamWriter.java b/src/main/java/com/ctc/wstx/sw/RepairingNsStreamWriter.java index 995e7c83..b338bd29 100644 --- a/src/main/java/com/ctc/wstx/sw/RepairingNsStreamWriter.java +++ b/src/main/java/com/ctc/wstx/sw/RepairingNsStreamWriter.java @@ -27,6 +27,7 @@ import javax.xml.stream.events.StartElement; import org.codehaus.stax2.ri.typed.AsciiValueEncoder; +import org.codehaus.stax2.validation.XMLValidator; import com.ctc.wstx.api.WriterConfig; import com.ctc.wstx.cfg.ErrorConsts; @@ -279,15 +280,9 @@ protected void writeStartOrEmpty(String localName, String nsURI) } if (prefix != null) { // prefix ok, easy, no need to overwrite - if (mValidator != null) { - mValidator.validateElementStart(localName, nsURI, prefix); - } doWriteStartTag(prefix, localName); } else { // no prefix, more work prefix = generateElemPrefix(null, nsURI, mCurrElem); - if (mValidator != null) { - mValidator.validateElementStart(localName, nsURI, prefix); - } mCurrElem.setPrefix(prefix); doWriteStartTag(prefix, localName); if (prefix == null || prefix.length() == 0) { // def NS @@ -309,9 +304,6 @@ protected void writeStartOrEmpty(String suggPrefix, String localName, String nsU // In repairing mode, better ensure validity: String actPrefix = validateElemPrefix(suggPrefix, nsURI, mCurrElem); if (actPrefix != null) { // fine, an existing binding we can use: - if (mValidator != null) { - mValidator.validateElementStart(localName, nsURI, actPrefix); - } if (mOutputElemPool != null) { SimpleOutputElement newCurr = mOutputElemPool; mOutputElemPool = newCurr.reuseAsChild(mCurrElem, actPrefix, localName, nsURI); @@ -330,9 +322,6 @@ protected void writeStartOrEmpty(String suggPrefix, String localName, String nsU suggPrefix = ""; } actPrefix = generateElemPrefix(suggPrefix, nsURI, mCurrElem); - if (mValidator != null) { - mValidator.validateElementStart(localName, nsURI, actPrefix); - } if (mOutputElemPool != null) { SimpleOutputElement newCurr = mOutputElemPool; mOutputElemPool = newCurr.reuseAsChild(mCurrElem, actPrefix, localName, nsURI); @@ -399,6 +388,7 @@ public final void copyStartElement(InputElementStack elemStack, AttributeCollect * prefixes have been remapped... so need to be bit more careful. */ if (attrCount > 0) { + XMLValidator vld = mCheckAttrs ? mCurrElem.getAttributeCollector() : null; for (int i = 0; i < attrCount; ++i) { // First; need to make sure that the prefix-to-ns mapping // attribute has is valid... and can not output anything @@ -420,10 +410,15 @@ public final void copyStartElement(InputElementStack elemStack, AttributeCollect * collector has, we can not use pass-through method of * the collector, but need to call XmlWriter directly: */ + String localName = ac.getLocalName(i); + String value = ac.getValue(i); if (prefix == null || prefix.length() == 0) { - mWriter.writeAttribute(ac.getLocalName(i), ac.getValue(i)); + mWriter.writeAttribute(localName, value); } else { - mWriter.writeAttribute(prefix, ac.getLocalName(i), ac.getValue(i)); + mWriter.writeAttribute(prefix, localName, value); + } + if (vld != null) { + vld.validateAttribute(localName, uri, prefix, value); } } } diff --git a/src/main/java/com/ctc/wstx/sw/SimpleNsStreamWriter.java b/src/main/java/com/ctc/wstx/sw/SimpleNsStreamWriter.java index f5c3bb6f..73e11fae 100644 --- a/src/main/java/com/ctc/wstx/sw/SimpleNsStreamWriter.java +++ b/src/main/java/com/ctc/wstx/sw/SimpleNsStreamWriter.java @@ -26,6 +26,8 @@ import javax.xml.stream.events.Namespace; import javax.xml.stream.events.StartElement; +import org.codehaus.stax2.validation.XMLValidator; + import com.ctc.wstx.api.WriterConfig; import com.ctc.wstx.cfg.ErrorConsts; import com.ctc.wstx.sr.AttributeCollector; @@ -235,9 +237,6 @@ protected void writeStartOrEmpty(String localName, String nsURI) throw new XMLStreamException("Unbound namespace URI '"+nsURI+"'"); } checkStartElement(localName, prefix); - if (mValidator != null) { - mValidator.validateElementStart(localName, nsURI, prefix); - } if (mOutputElemPool != null) { SimpleOutputElement newCurr = mOutputElemPool; @@ -255,9 +254,6 @@ protected void writeStartOrEmpty(String prefix, String localName, String nsURI) throws XMLStreamException { checkStartElement(localName, prefix); - if (mValidator != null) { - mValidator.validateElementStart(localName, nsURI, prefix); - } if (mOutputElemPool != null) { SimpleOutputElement newCurr = mOutputElemPool; @@ -327,8 +323,9 @@ public final void copyStartElement(InputElementStack elemStack, attrCollector.getSpecifiedCount(); if (attrCount > 0) { + XMLValidator vld = mCheckAttrs ? mCurrElem.getAttributeCollector() : null; for (int i = 0; i < attrCount; ++i) { - attrCollector.writeAttribute(i, mWriter, mValidator); + attrCollector.writeAttribute(i, mWriter, vld); } } } diff --git a/src/main/java/com/ctc/wstx/sw/SimpleOutputElement.java b/src/main/java/com/ctc/wstx/sw/SimpleOutputElement.java index 596025a4..178941af 100644 --- a/src/main/java/com/ctc/wstx/sw/SimpleOutputElement.java +++ b/src/main/java/com/ctc/wstx/sw/SimpleOutputElement.java @@ -21,6 +21,11 @@ import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; +import org.codehaus.stax2.validation.ValidationContext; +import org.codehaus.stax2.validation.XMLValidationSchema; +import org.codehaus.stax2.validation.XMLValidator; + +import com.ctc.wstx.api.WstxInputProperties; import com.ctc.wstx.compat.QNameCreator; import com.ctc.wstx.util.BijectiveNsMap; @@ -83,7 +88,14 @@ public final class SimpleOutputElement * Map used to check for duplicate attribute declarations, if * feature is enabled. */ - protected HashSet mAttrSet = null; + /* 13-Dec-2005, TSa: Should use a more efficient Set/Map value + * for this in future -- specifically one that could use + * ns/local-name pairs without intermediate objects + */ + private HashMap mAttrMap; + private ArrayList mAttrList; + private AttributeCollector mAttributeCollector; + private XMLValidator mValidator; /* /////////////////////////////////////////////////////////////////////// @@ -112,6 +124,7 @@ private SimpleOutputElement(SimpleOutputElement parent, mPrefix = prefix; mLocalName = localName; mURI = uri; + mValidator = parent.mValidator; } /** @@ -132,6 +145,7 @@ private void relink(SimpleOutputElement parent, mNsMapShared = (mNsMapping != null); mDefaultNsURI = parent.mDefaultNsURI; mRootNsContext = parent.mRootNsContext; + mValidator = parent.mValidator; } public static SimpleOutputElement createRoot() @@ -150,7 +164,8 @@ protected SimpleOutputElement createChild(String localName) * that when a child element has been opened, no more attributes * can be output. */ - mAttrSet = null; + mAttrList = null; + mAttrMap = null; return new SimpleOutputElement(this, null, localName, mDefaultNsURI, mNsMapping); } @@ -161,7 +176,8 @@ protected SimpleOutputElement createChild(String localName) protected SimpleOutputElement reuseAsChild(SimpleOutputElement parent, String localName) { - mAttrSet = null; + mAttrList = null; + mAttrMap = null; SimpleOutputElement poolHead = mParent; relink(parent, null, localName, mDefaultNsURI); return poolHead; @@ -171,7 +187,8 @@ protected SimpleOutputElement reuseAsChild(SimpleOutputElement parent, String prefix, String localName, String uri) { - mAttrSet = null; + mAttrList = null; + mAttrMap = null; SimpleOutputElement poolHead = mParent; relink(parent, prefix, localName, uri); return poolHead; @@ -188,7 +205,8 @@ protected SimpleOutputElement createChild(String prefix, String localName, * that when a child element has been opened, no more attributes * can be output. */ - mAttrSet = null; + mAttrList = null; + mAttrMap = null; return new SimpleOutputElement(this, prefix, localName, uri, mNsMapping); } @@ -199,6 +217,8 @@ protected SimpleOutputElement createChild(String prefix, String localName, protected void addToPool(SimpleOutputElement poolHead) { mParent = poolHead; + mAttrList = null; + mAttrMap = null; } /* @@ -259,20 +279,18 @@ public QName getName() { /////////////////////////////////////////////////////////////////////// */ - public void checkAttrWrite(String nsURI, String localName) + public void addAttribute(String nsURI, String localName, String prefix, String value) throws XMLStreamException { - AttrName an = new AttrName(nsURI, localName); - if (mAttrSet == null) { - /* 13-Dec-2005, TSa: Should use a more efficient Set/Map value - * for this in future -- specifically one that could use - * ns/local-name pairs without intermediate objects - */ - mAttrSet = new HashSet(); + if (mAttrList == null) { + mAttrList = new ArrayList(); + mAttrMap = new HashMap(); } - if (!mAttrSet.add(an)) { + Attribute an = new Attribute(nsURI, localName, prefix, value, mAttrList.size()); + if (mAttrMap.put(an, an) != null) { throw new XMLStreamException("Duplicate attribute write for attribute '"+an+"'"); } + mAttrList.add(an); } /* @@ -290,6 +308,13 @@ public void setDefaultNsUri(String uri) { mDefaultNsURI = uri; } + void setValidator(XMLValidator validator) { + mValidator = validator; + if (mParent != null) { + mParent.setValidator(validator); + } + } + /** * Note: this method can and will only be called before outputting * the root element. @@ -304,6 +329,74 @@ protected final void setRootNsContext(NamespaceContext ctxt) mDefaultNsURI = defURI; } } + + public int getAttributeCount() { + return mAttrList == null ? 0 : mAttrList.size(); + } + + public String getAttributeLocalName(int index) { + return mAttrList == null ? null : mAttrList.get(index).mLocalName; + } + + public String getAttributeNamespace(int index) { + return mAttrList == null ? null : mAttrList.get(index).mNsURI; + } + + public String getAttributePrefix(int index) { + return mAttrList == null ? null : mAttrList.get(index).mPrefix; + } + + public String getAttributeValue(int index) { + return mAttrList == null ? null : mAttrList.get(index).mValue; + } + + public String getAttributeValue(String nsURI, String localName) { + if (mAttrMap == null) { + return null; + } + final Attribute attr = mAttrMap.get(new Attribute(nsURI, localName, null, null, -1)); + return attr == null ? null : attr.mValue; + } + + public String getAttributeType(int index) { + return (mValidator == null) ? WstxInputProperties.UNKNOWN_ATTR_TYPE : + mValidator.getAttributeType(index); + } + + public int findAttributeIndex(String nsURI, String localName) { + if (mAttrMap == null) { + return -1; + } + final Attribute attr = mAttrMap.get(new Attribute(nsURI, localName, null, null, -1)); + return attr == null ? -1 : attr.mIndex; + } + + /** + * Returns the {@link XMLValidator} set via {@link #setValidator(XMLValidator)} wrapped in a + * {@link AttributeCollector} to be able to record the attribute values received through + * {@link XMLValidator#validateAttribute(String, String, String, char[], int, int)} + * and {@link XMLValidator#validateAttribute(String, String, String, String)}. + * + * @return an instance of {@link AttributeCollector} + */ + XMLValidator getAttributeCollector() { + AttributeCollector coll = mAttributeCollector; + if (coll == null) { + coll = mAttributeCollector = new AttributeCollector(); + } + return coll; + } + + public int validateElementStartAndAttributes() throws XMLStreamException { + XMLValidator vld = mValidator; + vld.validateElementStart(mLocalName, mURI, mPrefix); + if (mAttrList != null && mAttrList.size() > 0) { + for (Attribute attr : mAttrList) { + vld.validateAttribute(attr.mLocalName, attr.mNsURI, attr.mPrefix, attr.mValue); + } + } + return vld.validateElementAndAttributes(); + } /* /////////////////////////////////////////////////////////////////////// @@ -311,15 +404,97 @@ protected final void setRootNsContext(NamespaceContext ctxt) /////////////////////////////////////////////////////////////////////// */ + + final class AttributeCollector extends XMLValidator { + + public AttributeCollector() { + super(); + } + + @Override + public String getSchemaType() { + throw new UnsupportedOperationException(); + } + + @Override + public XMLValidationSchema getSchema() { + throw new UnsupportedOperationException(); + } + + @Override + public void validateElementStart(String localName, String uri, String prefix) throws XMLStreamException { + throw new UnsupportedOperationException(); + } + + @Override + public String validateAttribute(String localName, String uri, String prefix, String value) throws XMLStreamException { + addAttribute(uri, localName, prefix, value); + return value; + } + + @Override + public String validateAttribute(String localName, String uri, String prefix, char[] valueChars, int valueStart, + int valueEnd) throws XMLStreamException { + final String value = new String(valueChars, valueStart, valueEnd - valueStart); + addAttribute(uri, localName, prefix, value); + return value; + } + + @Override + public int validateElementAndAttributes() throws XMLStreamException { + throw new UnsupportedOperationException(); + } + + @Override + public int validateElementEnd(String localName, String uri, String prefix) throws XMLStreamException { + throw new UnsupportedOperationException(); + } + + @Override + public void validateText(String text, boolean lastTextSegment) throws XMLStreamException { + throw new UnsupportedOperationException(); + } + + @Override + public void validateText(char[] cbuf, int textStart, int textEnd, boolean lastTextSegment) throws XMLStreamException { + throw new UnsupportedOperationException(); + } + + @Override + public void validationCompleted(boolean eod) throws XMLStreamException { + throw new UnsupportedOperationException(); + } + + @Override + public String getAttributeType(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public int getIdAttrIndex() { + throw new UnsupportedOperationException(); + } + + @Override + public int getNotationAttrIndex() { + throw new UnsupportedOperationException(); + } + + } + /** * Simple key class used to represent two-piece (attribute) names; * first part being optional (URI), and second non-optional (local name). */ - final static class AttrName - implements Comparable + final static class Attribute + implements Comparable { final String mNsURI; final String mLocalName; + // mPrefix, mValue and mIndex are intentionally not a part of {@link #equals(Object)} and {@link #hashCode()} + final String mPrefix; + final String mValue; + final int mIndex; /** * Let's cache the hash code, since although hash calculation is @@ -328,10 +503,13 @@ final static class AttrName */ final int mHashCode; - public AttrName(String nsURI, String localName) { + public Attribute(String nsURI, String localName, String prefix, String value, int index) { mNsURI = (nsURI == null) ? "" : nsURI; mLocalName = localName; mHashCode = mNsURI.hashCode() * 31 ^ mLocalName.hashCode(); + mPrefix = prefix; + mValue = value; + mIndex = index; } @Override @@ -339,10 +517,10 @@ public boolean equals(Object o) { if (o == this) { return true; } - if (!(o instanceof AttrName)) { + if (!(o instanceof Attribute)) { return false; } - AttrName other = (AttrName) o; + Attribute other = (Attribute) o; String otherLN = other.mLocalName; // Local names are shorter, more varying: if (otherLN != mLocalName && !otherLN.equals(mLocalName)) { @@ -366,7 +544,7 @@ public int hashCode() { } @Override - public int compareTo(AttrName other) { + public int compareTo(Attribute other) { // Let's first order by namespace: int result = mNsURI.compareTo(other.mNsURI); if (result == 0) { diff --git a/src/test/java/failing/TestInvalidAttributeValue190.java b/src/test/java/failing/TestInvalidAttributeValue190.java deleted file mode 100644 index edac3cb4..00000000 --- a/src/test/java/failing/TestInvalidAttributeValue190.java +++ /dev/null @@ -1,74 +0,0 @@ -package failing; - -import stax2.BaseStax2Test; - -import java.io.StringReader; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.List; - -import javax.xml.stream.*; - -import org.codehaus.stax2.XMLStreamReader2; -import org.codehaus.stax2.validation.ValidationProblemHandler; -import org.codehaus.stax2.validation.XMLValidationException; -import org.codehaus.stax2.validation.XMLValidationProblem; -import org.codehaus.stax2.validation.XMLValidationSchema; -import org.codehaus.stax2.validation.XMLValidationSchemaFactory; - -import com.ctc.wstx.sw.RepairingNsStreamWriter; - -public class TestInvalidAttributeValue190 - extends BaseStax2Test -{ - /* A reproducer for https://github.com/FasterXML/woodstox/issues/190 */ - public void testInvalidAttributeValue() throws Exception - { - final String DOC = ""; - - final String INPUT_DTD = -"\n" -+"\n" -; - - XMLInputFactory f = getInputFactory(); - setCoalescing(f, true); - - XMLValidationSchemaFactory schemaFactory = - XMLValidationSchemaFactory.newInstance(XMLValidationSchema.SCHEMA_ID_DTD); - XMLValidationSchema schema = schemaFactory.createSchema(new StringReader(INPUT_DTD)); - XMLStreamReader2 sr = (XMLStreamReader2)f.createXMLStreamReader( - new StringReader(DOC)); - - final List probs = new ArrayList(); - - sr.validateAgainst(schema); - sr.setValidationProblemHandler(new ValidationProblemHandler() { - @Override - public void reportProblem(XMLValidationProblem problem) - throws XMLValidationException { - probs.add(problem); - } - }); - - assertTokenType(START_ELEMENT, sr.next()); - assertEquals("root", sr.getLocalName()); - - final String verboseValue = sr.getAttributeValue(null, "verbose"); - - assertEquals("yes", verboseValue); - - assertEquals(1, probs.size()); - assertEquals("Element has no attribute \"verbose\"", probs.get(0).getMessage()); - - // now do the same on the writer side - // and make sure that the reported problems are the same - { - // RepairingNsStreamWriter - StringWriter writer = new StringWriter(); - RepairingNsStreamWriter sw = (RepairingNsStreamWriter) stax2.BaseStax2Test.constructStreamWriter(writer, true, true); - validateWriter(DOC, probs, f, schema, writer, sw); - } - - } -} diff --git a/src/test/java/failing/TestRelaxNG190.java b/src/test/java/failing/TestRelaxNG190.java deleted file mode 100644 index 658e2745..00000000 --- a/src/test/java/failing/TestRelaxNG190.java +++ /dev/null @@ -1,43 +0,0 @@ -package failing; - -import java.io.StringWriter; - -import javax.xml.stream.*; - -import org.codehaus.stax2.validation.*; - -import com.ctc.wstx.sw.RepairingNsStreamWriter; - -/** - * A reproducer for https://github.com/FasterXML/woodstox/issues/190 - * Move to {@link wstxtest.vstream.TestRelaxNG} once fixed. - */ -public class TestRelaxNG190 - extends wstxtest.vstream.TestRelaxNG -{ - - public void testPartialValidationOk() - throws XMLStreamException - { - /* Hmmh... RelaxNG does define expected root. So need to - * wrap the doc... - */ - String XML = - "\n" - +"\n" - +"\n" - +" foobar\n" - +" Foo Bar\n" - +"\n" - +"" - ; - XMLValidationSchema schema = parseRngSchema(SIMPLE_RNG_SCHEMA); - { - StringWriter writer = new StringWriter(); - RepairingNsStreamWriter sw = (RepairingNsStreamWriter) constructStreamWriter(writer, true, true); - _testPartialValidationOk(XML, schema, sw, writer); - } - } - - -} diff --git a/src/test/java/failing/TestW3CSchemaNillable179.java b/src/test/java/failing/TestW3CSchemaNillable179.java deleted file mode 100644 index 33085493..00000000 --- a/src/test/java/failing/TestW3CSchemaNillable179.java +++ /dev/null @@ -1,177 +0,0 @@ -package failing; -// ^^^ Move under "wstxtest/msv" once passing - -import java.io.StringReader; -import java.io.StringWriter; - -import org.codehaus.stax2.*; -import org.codehaus.stax2.validation.*; - -import wstxtest.vstream.BaseValidationTest; - -/** - * Test whether nillable elements are handled correctly by both reader and writer. - * A reproducer for https://github.com/FasterXML/woodstox/issues/179. - */ -public class TestW3CSchemaNillable179 - extends BaseValidationTest -{ - private static final String SCHEMA = - "\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + ""; - // for [woodstox-core#179] - public void testNillableDateTime() throws Exception - { - final String xmlDocument = - "\n" - + " \n" - + ""; - testNillable(xmlDocument, true, true); - - // The same document without xsi:nil="true" must fail for both reader and writer validation - try { - testNillable(xmlDocument.replace(" xsi:nil=\"true\"", ""), true, false); - fail("Expected a LocalValidationError"); - } catch (LocalValidationError expected) { - assertEquals("Unknown reason (at end element )", expected.problem.getMessage()); - } - try{ - testNillable(xmlDocument.replace(" xsi:nil=\"true\"", ""), false, true); - fail("Expected a LocalValidationError"); - } catch (LocalValidationError expected) { - assertEquals("Unknown reason (at end element )", expected.problem.getMessage()); - } - - } - - // for [woodstox-core#179] - public void testNillableInt() throws Exception - { - final String xmlDocument = - "\n" - + " \n" - + ""; - testNillable(xmlDocument, true, true); - - // The same document without xsi:nil="true" must fail for both reader and writer validation - try { - testNillable(xmlDocument.replace(" xsi:nil=\"true\"", ""), true, false); - fail("Expected a LocalValidationError"); - } catch (LocalValidationError expected) { - assertEquals("Unknown reason (at end element )", expected.problem.getMessage()); - } - try{ - testNillable(xmlDocument.replace(" xsi:nil=\"true\"", ""), false, true); - fail("Expected a LocalValidationError"); - } catch (LocalValidationError expected) { - assertEquals("Unknown reason (at end element )", expected.problem.getMessage()); - } - - } - - // for [woodstox-core#179] - public void testNillableString() throws Exception - { - final String xmlDocument = - "\n" - + " \n" - + ""; - testNillable(xmlDocument, true, true); - - // Empty strings are legal - testNillable(xmlDocument.replace(" xsi:nil=\"true\"", ""), true, false); - testNillable(xmlDocument.replace(" xsi:nil=\"true\"", ""), false, true); - - } - - void testNillable(String xmlDocument, boolean validateReader, boolean validateWriter) throws Exception - { - StringWriter writer = new StringWriter(); - XMLStreamReader2 xmlReader = null; - XMLStreamWriter2 xmlWriter = null; - try { - XMLValidationSchemaFactory schF = XMLValidationSchemaFactory.newInstance(XMLValidationSchema.SCHEMA_ID_W3C_SCHEMA); - XMLValidationSchema schema = schF.createSchema(new StringReader(SCHEMA)); - XMLInputFactory2 f = getInputFactory(); - setValidating(f, validateReader); - - xmlReader = (XMLStreamReader2) f.createXMLStreamReader(new StringReader(xmlDocument)); - - if (validateReader) { - xmlReader.setValidationProblemHandler(new ValidationProblemHandler() { - @Override - public void reportProblem(XMLValidationProblem problem) - throws XMLValidationException { - throw new LocalValidationError(problem); - } - }); - xmlReader.validateAgainst(schema); - } - - xmlWriter = (XMLStreamWriter2) getOutputFactory().createXMLStreamWriter(writer); - - if (validateWriter) { - xmlWriter.setValidationProblemHandler(new ValidationProblemHandler() { - @Override - public void reportProblem(XMLValidationProblem problem) - throws XMLValidationException { - throw new LocalValidationError(problem); - } - }); - xmlWriter.validateAgainst(schema); - } - - xmlWriter.copyEventFromReader(xmlReader, false); - while (xmlReader.hasNext()) { - xmlReader.next(); - xmlWriter.copyEventFromReader(xmlReader, false); - } - } finally { - if (xmlReader != null) { - xmlReader.close(); - } - if (xmlWriter != null) { - xmlWriter.close(); - } - } - assertEquals(xmlDocument, writer.toString()); - } - - /* - /////////////////////////////////////////////////////////////////////// - // Helper classes - /////////////////////////////////////////////////////////////////////// - */ - - public static class LocalValidationError extends RuntimeException - { - private static final long serialVersionUID = 1L; - - protected XMLValidationProblem problem; - - LocalValidationError(XMLValidationProblem problem) { - this.problem = problem; - } - - public XMLValidationProblem getProblem() { - return problem; - } - - @Override - public String toString() { - return problem.getMessage(); - } - } -} diff --git a/src/test/java/stax2/vwstream/TestAttributeValidation.java b/src/test/java/stax2/vwstream/TestAttributeValidation.java index d4793a1d..2f76dca8 100644 --- a/src/test/java/stax2/vwstream/TestAttributeValidation.java +++ b/src/test/java/stax2/vwstream/TestAttributeValidation.java @@ -62,14 +62,16 @@ private void _testValidFixedAttr(boolean nsAware, boolean repairing) throws XMLS public void testInvalidFixedAttr() throws XMLStreamException { - if (HAS_NON_NS_MODE) { // only test non-ns mode if supported - _testInvalidFixedAttr(true, false); + for (WriterValidationTrigger trigger : WriterValidationTrigger.values()) { + if (HAS_NON_NS_MODE) { // only test non-ns mode if supported + _testInvalidFixedAttr(true, false, trigger); + } + _testInvalidFixedAttr(true, false, trigger); + _testInvalidFixedAttr(true, true, trigger); } - _testInvalidFixedAttr(true, false); - _testInvalidFixedAttr(true, true); } - private void _testInvalidFixedAttr(boolean nsAware, boolean repairing) + private void _testInvalidFixedAttr(boolean nsAware, boolean repairing, WriterValidationTrigger trigger) throws XMLStreamException { String modeDesc = String.format("[ns-aware? %s, repairing? %s]", nsAware, repairing); @@ -80,8 +82,9 @@ private void _testInvalidFixedAttr(boolean nsAware, boolean repairing) StringWriter strw = new StringWriter(); XMLStreamWriter2 sw = getDTDValidatingWriter(strw, FIXED_DTD_STR, nsAware, repairing); sw.writeStartElement("root"); + sw.writeAttribute("fixAttr", "otherValue"); try { - sw.writeAttribute("fixAttr", "otherValue"); + trigger.run(sw); fail(modeDesc+" Expected a validation exception when trying to add a #FIXED attribute with 'wrong' value"); } catch (XMLValidationException vex) { assertMessageContains(vex, "Value of attribute \"fixAttr\" (element ) not \"fixedValue\" as expected, but \"otherValue\""); @@ -92,8 +95,9 @@ private void _testInvalidFixedAttr(boolean nsAware, boolean repairing) strw = new StringWriter(); sw = getDTDValidatingWriter(strw, FIXED_DTD_STR, nsAware, repairing); sw.writeStartElement("root"); + sw.writeAttribute("fixAttr", ""); try { - sw.writeAttribute("fixAttr", ""); + trigger.run(sw); fail(modeDesc+" Expected a validation exception when trying to add a #FIXED attribute with an empty value"); } catch (XMLValidationException vex) { assertMessageContains(vex, "Value of attribute \"fixAttr\" (element ) not \"fixedValue\" as expected, but \"\""); @@ -103,8 +107,9 @@ private void _testInvalidFixedAttr(boolean nsAware, boolean repairing) strw = new StringWriter(); sw = getDTDValidatingWriter(strw, FIXED_DTD_STR, nsAware, repairing); sw.writeEmptyElement("root"); + sw.writeAttribute("fixAttr", "foobar"); try { - sw.writeAttribute("fixAttr", "foobar"); + trigger.run(sw); fail(modeDesc+" Expected a validation exception when trying to add a #FIXED attribute with an empty value"); } catch (XMLValidationException vex) { assertMessageContains(vex, "Value of attribute \"fixAttr\" (element ) not \"fixedValue\" as expected, but \"foobar\""); @@ -153,14 +158,16 @@ private void _testValidRequiredAttr(boolean nsAware, boolean repairing) public void testInvalidRequiredAttr() throws XMLStreamException { - if (HAS_NON_NS_MODE) { // only test non-ns mode if supported - _testInvalidRequiredAttr(true, false); + for (WriterValidationTrigger trigger : WriterValidationTrigger.values()) { + if (HAS_NON_NS_MODE) { // only test non-ns mode if supported + _testInvalidRequiredAttr(true, false, trigger); + } + _testInvalidRequiredAttr(true, false, trigger); + _testInvalidRequiredAttr(true, true, trigger); } - _testInvalidRequiredAttr(true, false); - _testInvalidRequiredAttr(true, true); } - public void _testInvalidRequiredAttr(boolean nsAware, boolean repairing) + public void _testInvalidRequiredAttr(boolean nsAware, boolean repairing, WriterValidationTrigger trigger) throws XMLStreamException { String modeDesc = String.format("[ns-aware? %s, repairing? %s]", nsAware, repairing); @@ -170,7 +177,7 @@ public void _testInvalidRequiredAttr(boolean nsAware, boolean repairing) XMLStreamWriter2 sw = getDTDValidatingWriter(strw, REQUIRED_DTD_STR, nsAware, repairing); sw.writeStartElement("root"); try { - sw.writeEndElement(); + trigger.run(sw); fail(modeDesc+" Expected a validation exception when omitting a #REQUIRED attribute"); } catch (XMLValidationException vex) { assertMessageContains(vex, "Required attribute \"reqAttr\" missing from element "); @@ -208,11 +215,13 @@ private void _testValidNsAttr(boolean repairing) throws XMLStreamException public void testInvalidNsAttr() throws XMLStreamException { - _testInvalidNsAttr(false); - _testInvalidNsAttr(true); + for (WriterValidationTrigger trigger : WriterValidationTrigger.values()) { + _testInvalidNsAttr(false, trigger); + _testInvalidNsAttr(true, trigger); + } } - private void _testInvalidNsAttr(boolean repairing) throws XMLStreamException + private void _testInvalidNsAttr(boolean repairing, WriterValidationTrigger trigger) throws XMLStreamException { String modeDesc = "[namespace-aware, repairing? "+repairing+"]"; @@ -225,12 +234,27 @@ private void _testInvalidNsAttr(boolean repairing) throws XMLStreamException sw.writeNamespace(NS_PREFIX, NS_URI); } // prefix, uri, localname (for attrs!) + sw.writeAttribute(NS_PREFIX2, NS_URI, "attr", "value"); try { - sw.writeAttribute(NS_PREFIX2, NS_URI, "attr", "value"); + trigger.run(sw); fail(modeDesc+" Expected a validation exception when trying to add an attribute with wrong ns prefix"); } catch (XMLValidationException vex) { assertMessageContains(vex, "Element has no attribute \"ns2:attr\""); } // Should not close, since stream is invalid now... } + + enum WriterValidationTrigger { + writeEndElement() { + void run(XMLStreamWriter2 sw) throws XMLStreamException { + sw.writeEndElement(); + } + }, + close() { + void run(XMLStreamWriter2 sw) throws XMLStreamException { + sw.close(); + } + }; + abstract void run(XMLStreamWriter2 sw) throws XMLStreamException; + } } diff --git a/src/test/java/stax2/vwstream/TestOutputValidation.java b/src/test/java/stax2/vwstream/TestOutputValidation.java index f88c208f..1a2551fa 100644 --- a/src/test/java/stax2/vwstream/TestOutputValidation.java +++ b/src/test/java/stax2/vwstream/TestOutputValidation.java @@ -145,8 +145,9 @@ public void testInvalidEmptyContent() XMLStreamWriter2 sw = getDTDValidatingWriter(strw, dtdStr, nsAware, repairing); sw.writeStartElement("root"); + sw.writeStartElement("leaf"); try { - sw.writeStartElement("leaf"); + sw.writeEndElement(); fail(modeDesc+" Expected a validation exception when trying to add an element into EMPTY content model"); } catch (XMLValidationException vex) { assertMessageContains(vex, "Validation error, encountered element as a child of : No elements allowed in pure #PCDATA content model"); @@ -156,8 +157,9 @@ public void testInvalidEmptyContent() // Then with an empty child sw = getDTDValidatingWriter(strw, dtdStr, nsAware, repairing); sw.writeStartElement("root"); + sw.writeEmptyElement("leaf"); try { - sw.writeEmptyElement("leaf"); + sw.writeEndElement(); fail(modeDesc+" Expected a validation exception when trying to add an element into EMPTY content model"); } catch (XMLValidationException vex) { assertMessageContains(vex, "Validation error, encountered element as a child of : No elements allowed in pure #PCDATA content model"); @@ -307,8 +309,9 @@ public void testInvalidAnyContent() * may be redundant to some degree) */ sw.writeStartElement("root"); + sw.writeStartElement("unknown"); try { - sw.writeStartElement("unknown"); + sw.writeEndElement(); fail(modeDesc+" Expected a validation exception when trying to add an undeclared element"); } catch (XMLValidationException vex) { assertMessageContains(vex, "Undefined element encountered"); @@ -318,8 +321,9 @@ public void testInvalidAnyContent() // undecl attr: sw = getDTDValidatingWriter(strw, dtdStr, nsAware, repairing); sw.writeStartElement("root"); + sw.writeAttribute("unknown", "value"); try { - sw.writeAttribute("unknown", "value"); + sw.writeEndElement(); fail(modeDesc+" Expected a validation exception when trying to add an undeclared attribute"); } catch (XMLValidationException vex) { assertMessageContains(vex, "Element has no attribute \"unknown\""); diff --git a/src/test/java/stax2/vwstream/TestStructuralValidation.java b/src/test/java/stax2/vwstream/TestStructuralValidation.java index d0beae51..02f8a930 100644 --- a/src/test/java/stax2/vwstream/TestStructuralValidation.java +++ b/src/test/java/stax2/vwstream/TestStructuralValidation.java @@ -70,8 +70,9 @@ public void testInvalidRootElem() // And then undeclared root: sw = getDTDValidatingWriter(strw, SIMPLE_DTD, nsAware, repairing); + sw.writeStartElement("undefined"); try { - sw.writeStartElement("undefined"); + sw.writeEndElement(); fail(modeDesc+" Expected a validation exception when trying to write an undefined root element"); } catch (XMLValidationException vex) { assertMessageContains(vex, "Undefined element encountered"); @@ -165,8 +166,9 @@ public void testInvalidStructure() sw.writeStartElement("root"); sw.writeCharacters(" "); // imitating indentation sw.writeComment("comment"); + sw.writeEmptyElement("end"); try { - sw.writeEmptyElement("end"); + sw.writeEndElement(); fail(modeDesc+" Expected a validation exception when omitting non-optional element"); } catch (XMLValidationException vex) { assertMessageContains(vex, "Validation error, encountered element as a child of : Expected "); @@ -230,8 +232,9 @@ public void testInvalidNsElem() XMLStreamWriter2 sw = getDTDValidatingWriter(strw, SIMPLE_NS_DTD, true, repairing); // prefix, local name, uri (for elems) + sw.writeStartElement(NS_PREFIX2, "root", NS_URI); try { - sw.writeStartElement(NS_PREFIX2, "root", NS_URI); + sw.writeEndElement(); fail(modeDesc+" Expected a validation exception when passing wrong (unexpected) ns for element"); } catch (XMLValidationException vex) { assertMessageContains(vex, "Undefined element encountered"); @@ -241,8 +244,9 @@ public void testInvalidNsElem() // and then the same for empty elem sw = getDTDValidatingWriter(strw, SIMPLE_NS_DTD, true, repairing); // prefix, local name, uri (for elems) + sw.writeEmptyElement(NS_PREFIX2, NS_URI, "root"); try { - sw.writeEmptyElement(NS_PREFIX2, NS_URI, "root"); + sw.writeEndElement(); fail(modeDesc+" Expected a validation exception when passing wrong (unexpected) ns for element"); } catch (XMLValidationException vex) { assertMessageContains(vex, "Undefined element encountered"); @@ -251,8 +255,9 @@ public void testInvalidNsElem() // Oh, and finally, using non-ns DTD: sw = getDTDValidatingWriter(strw, SIMPLE_DTD, true, repairing); // prefix, local name, uri (for elems) + sw.writeEmptyElement(NS_PREFIX, NS_URI, "root"); try { - sw.writeEmptyElement(NS_PREFIX, NS_URI, "root"); + sw.writeEndElement(); fail(modeDesc+" Expected a validation exception when passing wrong (unexpected) ns for element"); } catch (XMLValidationException vex) { assertMessageContains(vex, "Undefined element encountered"); diff --git a/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java b/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java index cd9c4001..72b80b89 100644 --- a/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java +++ b/src/test/java/wstxtest/vstream/TestInvalidAttributeValue.java @@ -16,6 +16,7 @@ import org.codehaus.stax2.validation.XMLValidationSchema; import org.codehaus.stax2.validation.XMLValidationSchemaFactory; +import com.ctc.wstx.sw.RepairingNsStreamWriter; import com.ctc.wstx.sw.SimpleNsStreamWriter; public class TestInvalidAttributeValue @@ -68,5 +69,11 @@ public void reportProblem(XMLValidationProblem problem) SimpleNsStreamWriter sw = (SimpleNsStreamWriter) stax2.BaseStax2Test.constructStreamWriter(writer, true, false); validateWriter(DOC, probs, f, schema, writer, sw); } + { + // RepairingNsStreamWriter + StringWriter writer = new StringWriter(); + RepairingNsStreamWriter sw = (RepairingNsStreamWriter) stax2.BaseStax2Test.constructStreamWriter(writer, true, true); + validateWriter(DOC, probs, f, schema, writer, sw); + } } } diff --git a/src/test/java/wstxtest/vstream/TestRelaxNG.java b/src/test/java/wstxtest/vstream/TestRelaxNG.java index 9817d318..32dbb1a9 100644 --- a/src/test/java/wstxtest/vstream/TestRelaxNG.java +++ b/src/test/java/wstxtest/vstream/TestRelaxNG.java @@ -493,6 +493,11 @@ public void testPartialValidationOk() SimpleNsStreamWriter sw = (SimpleNsStreamWriter) constructStreamWriter(writer, true, false); _testPartialValidationOk(XML, schema, sw, writer); } + { + StringWriter writer = new StringWriter(); + RepairingNsStreamWriter sw = (RepairingNsStreamWriter) constructStreamWriter(writer, true, true); + _testPartialValidationOk(XML, schema, sw, writer); + } { StringWriter writer = new StringWriter(); NonNsStreamWriter sw = (NonNsStreamWriter) constructStreamWriter(writer, false, false); @@ -506,7 +511,14 @@ protected void _testPartialValidationOk(String XML, XMLValidationSchema schema, sw.copyEventFromReader(sr, false); sr.validateAgainst(schema); + + // writer validation has to be turned on one event later because + // it does not trigger the validation of an element before its start tag is closed + // which happens upon the next event + assertTokenType(CHARACTERS, sr.next()); + sw.copyEventFromReader(sr, false); sw.validateAgainst(schema); + while (sr.hasNext()) { sr.next(); sw.copyEventFromReader(sr, false); diff --git a/src/test/java/wstxtest/vstream/TestW3CSchemaNillable.java b/src/test/java/wstxtest/vstream/TestW3CSchemaNillable.java new file mode 100644 index 00000000..c03b0de5 --- /dev/null +++ b/src/test/java/wstxtest/vstream/TestW3CSchemaNillable.java @@ -0,0 +1,173 @@ +package wstxtest.vstream; +// ^^^ Move under "wstxtest/msv" once passing + +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; + +/** + * Test whether nillable elements are handled correctly by both reader and writer. + * A reproducer for https://github.com/FasterXML/woodstox/issues/179. + */ +public class TestW3CSchemaNillable + extends BaseValidationTest +{ + private static final String SCHEMA = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + // for [woodstox-core#179] + public void testNillableString() throws Exception + { + XMLValidationSchema schema = parseW3CSchema(SCHEMA); + final String XML = + "\n" + + " \n" + + ""; + // 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] + public void testNillableDateTime() throws Exception + { + XMLValidationSchema schema = parseW3CSchema(SCHEMA); + final String XML = + "\n" + + " \n" + + ""; + + _testNillable(schema, XML, "nillableDateTime"); + + } + + // for [woodstox-core#179] + public void testNillableInt() throws Exception + { + XMLValidationSchema schema = parseW3CSchema(SCHEMA); + final String XML = + "\n" + + " \n" + + ""; + + _testNillable(schema, XML, "nillableInt"); + + } + + private void _testNillable(XMLValidationSchema schema, final String XML, String localName) 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, "Unknown reason (at end element )"); + } + + // 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, "Unknown reason (at end element )"); + } + + // 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, "Unknown reason (at end element )"); + } + } + + 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()); + } + } + +} diff --git a/src/test/resources/wstxtest/msv/nillable.xsd b/src/test/resources/wstxtest/msv/nillable.xsd deleted file mode 100644 index 1b21dd53..00000000 --- a/src/test/resources/wstxtest/msv/nillable.xsd +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - diff --git a/src/test/resources/wstxtest/msv/nillableDateTime.xml b/src/test/resources/wstxtest/msv/nillableDateTime.xml deleted file mode 100644 index 0bbefaa8..00000000 --- a/src/test/resources/wstxtest/msv/nillableDateTime.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/test/resources/wstxtest/msv/nillableInt.xml b/src/test/resources/wstxtest/msv/nillableInt.xml deleted file mode 100644 index f3f406a6..00000000 --- a/src/test/resources/wstxtest/msv/nillableInt.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/test/resources/wstxtest/msv/nillableString.xml b/src/test/resources/wstxtest/msv/nillableString.xml deleted file mode 100644 index a8e0de4d..00000000 --- a/src/test/resources/wstxtest/msv/nillableString.xml +++ /dev/null @@ -1,3 +0,0 @@ - - -