This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ws-xmlschema.git
The following commit(s) were added to refs/heads/master by this push:
new 3634e4f8 Allow an xs:all alongside an empty particle in a complex
content extension (#171)
3634e4f8 is described below
commit 3634e4f8c0512fad069a54e9a2fa9dd414209dfd
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu Sep 24 11:58:41 2026 +0100
Allow an xs:all alongside an empty particle in a complex content extension
(#171)
---
.../ws/commons/schema/walker/XmlSchemaScope.java | 40 +++++++-
.../walker/ComplexContentExtensionWalkerTest.java | 101 +++++++++++++++++++--
2 files changed, 127 insertions(+), 14 deletions(-)
diff --git
a/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/walker/XmlSchemaScope.java
b/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/walker/XmlSchemaScope.java
index 08be6e43..fb835948 100644
---
a/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/walker/XmlSchemaScope.java
+++
b/xmlschema-walker/src/main/java/org/apache/ws/commons/schema/walker/XmlSchemaScope.java
@@ -32,12 +32,14 @@ import java.util.Set;
import javax.xml.namespace.QName;
import org.apache.ws.commons.schema.XmlSchema;
+import org.apache.ws.commons.schema.XmlSchemaAll;
import org.apache.ws.commons.schema.XmlSchemaAnyAttribute;
import org.apache.ws.commons.schema.XmlSchemaAttribute;
import org.apache.ws.commons.schema.XmlSchemaAttributeGroup;
import org.apache.ws.commons.schema.XmlSchemaAttributeGroupMember;
import org.apache.ws.commons.schema.XmlSchemaAttributeGroupRef;
import org.apache.ws.commons.schema.XmlSchemaAttributeOrGroupRef;
+import org.apache.ws.commons.schema.XmlSchemaChoice;
import org.apache.ws.commons.schema.XmlSchemaComplexContent;
import org.apache.ws.commons.schema.XmlSchemaComplexContentExtension;
import org.apache.ws.commons.schema.XmlSchemaComplexContentRestriction;
@@ -407,10 +409,18 @@ final class XmlSchemaScope {
|| !(ext.getParticle() instanceof
XmlSchemaSequenceMember)) {
// Only an xs:all is not a sequence member, and XML Schema 1.0
// does not allow one to be combined with any other particle.
- throw new XmlSchemaException("An extension of " +
ext.getBaseTypeName()
- + " adds content to it, but one
of the two content"
- + " models is an xs:all group,
which cannot be"
- + " combined with other
particles.");
+ // An empty particle adds no content, though, so an xs:all
+ // alongside one is the whole content model.
+ if (isEmptyParticle(ext.getParticle())) {
+ child = baseParticle;
+ } else if (isEmptyParticle(baseParticle)) {
+ child = ext.getParticle();
+ } else {
+ throw new XmlSchemaException("An extension of " +
ext.getBaseTypeName()
+ + " adds content to it, but
one of the two content"
+ + " models is an xs:all
group, which cannot be"
+ + " combined with other
particles.");
+ }
} else {
XmlSchemaSequence seq = new XmlSchemaSequence();
seq.getItems().add((XmlSchemaSequenceMember)baseParticle);
@@ -838,6 +848,28 @@ final class XmlSchemaScope {
return (parent == null) ? null : parent.getUserRecognizedType();
}
+ /**
+ * Whether a particle can match nothing at all: one that may not occur, an
+ * xs:all or xs:sequence with no particles, or an optional xs:choice with
+ * none. These are the particles XML Schema treats as empty content when it
+ * builds the content model of an extension.
+ */
+ private static boolean isEmptyParticle(XmlSchemaParticle particle) {
+ if (particle.getMaxOccurs() == 0) {
+ return true;
+ }
+ if (particle instanceof XmlSchemaAll) {
+ return ((XmlSchemaAll)particle).getItems().isEmpty();
+ }
+ if (particle instanceof XmlSchemaSequence) {
+ return ((XmlSchemaSequence)particle).getItems().isEmpty();
+ }
+ if (particle instanceof XmlSchemaChoice) {
+ return ((XmlSchemaChoice)particle).getItems().isEmpty() &&
particle.getMinOccurs() == 0;
+ }
+ return false;
+ }
+
private static boolean isAnyNamespace(String namespace) {
return (namespace == null) || "##any".equals(namespace.trim());
}
diff --git
a/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/walker/ComplexContentExtensionWalkerTest.java
b/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/walker/ComplexContentExtensionWalkerTest.java
index 2d9faecf..03242850 100644
---
a/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/walker/ComplexContentExtensionWalkerTest.java
+++
b/xmlschema-walker/src/test/java/org/apache/ws/commons/schema/walker/ComplexContentExtensionWalkerTest.java
@@ -19,6 +19,7 @@
package org.apache.ws.commons.schema.walker;
import java.io.StringReader;
+import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
@@ -41,6 +42,31 @@ import org.junit.Test;
public class ComplexContentExtensionWalkerTest extends Assert {
private static List<String> walkRoot(String body) {
+ // Records the namespace of every attribute wildcard visited.
+ final List<String> anyAttributeNamespaces = new ArrayList<String>();
+ walk(body, (proxy, method, args) -> {
+ if ("onVisitAnyAttribute".equals(method.getName())) {
+
anyAttributeNamespaces.add(((XmlSchemaAnyAttribute)args[1]).getNamespace());
+ }
+ return null;
+ });
+ return anyAttributeNamespaces;
+ }
+
+ /**
+ * Walks the root element and returns the name of every visitor callback,
+ * in order.
+ */
+ private static List<String> walkEvents(String body) {
+ final List<String> events = new ArrayList<String>();
+ walk(body, (proxy, method, args) -> {
+ events.add(method.getName());
+ return null;
+ });
+ return events;
+ }
+
+ private static void walk(String body, InvocationHandler handler) {
XmlSchemaCollection collection = new XmlSchemaCollection();
collection.read(new StringReader(
"<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"
xmlns:tns=\"urn:ext\""
@@ -50,18 +76,9 @@ public class ComplexContentExtensionWalkerTest extends
Assert {
XmlSchemaElement root = collection.getElementByQName(new
QName("urn:ext", "root"));
assertNotNull("the schema under test must declare a 'root' element",
root);
- // Records the namespace of every attribute wildcard visited.
- final List<String> anyAttributeNamespaces = new ArrayList<String>();
XmlSchemaVisitor visitor = (XmlSchemaVisitor)Proxy.newProxyInstance(
- XmlSchemaVisitor.class.getClassLoader(), new Class<?>[]
{XmlSchemaVisitor.class},
- (proxy, method, args) -> {
- if ("onVisitAnyAttribute".equals(method.getName())) {
-
anyAttributeNamespaces.add(((XmlSchemaAnyAttribute)args[1]).getNamespace());
- }
- return null;
- });
+ XmlSchemaVisitor.class.getClassLoader(), new Class<?>[]
{XmlSchemaVisitor.class}, handler);
new XmlSchemaWalker(collection, visitor).walk(root);
- return anyAttributeNamespaces;
}
@Test
@@ -81,6 +98,70 @@ public class ComplexContentExtensionWalkerTest extends
Assert {
}
}
+ /**
+ * An empty particle adds no content, so XML Schema allows it alongside an
+ * xs:all: the xs:all is then the whole content model.
+ */
+ @Test
+ public void testExtendingAnAllGroupWithAnEmptySequenceWalks() {
+ assertAllGroupWalked("<xs:complexType name=\"base\"><xs:all>"
+ + "<xs:element name=\"a\" type=\"xs:string\"/>"
+ + "<xs:element name=\"b\" type=\"xs:string\"/>"
+ + "</xs:all></xs:complexType>"
+ + "<xs:complexType
name=\"derived\"><xs:complexContent>"
+ + "<xs:extension base=\"tns:base\"><xs:sequence/>"
+ + "<xs:attribute name=\"id\" type=\"xs:string\"/>"
+ +
"</xs:extension></xs:complexContent></xs:complexType>"
+ + "<xs:element name=\"root\"
type=\"tns:derived\"/>");
+ }
+
+ @Test
+ public void testExtendingAnAllGroupWithAChoiceThatCannotOccurWalks() {
+ assertAllGroupWalked("<xs:complexType name=\"base\"><xs:all>"
+ + "<xs:element name=\"a\" type=\"xs:string\"/>"
+ + "</xs:all></xs:complexType>"
+ + "<xs:complexType
name=\"derived\"><xs:complexContent>"
+ + "<xs:extension base=\"tns:base\">"
+ + "<xs:choice minOccurs=\"0\" maxOccurs=\"0\"/>"
+ +
"</xs:extension></xs:complexContent></xs:complexType>"
+ + "<xs:element name=\"root\"
type=\"tns:derived\"/>");
+ }
+
+ @Test
+ public void testExtendingAnEmptySequenceWithAnAllGroupWalks() {
+ assertAllGroupWalked("<xs:complexType name=\"base\"><xs:sequence/>"
+ + "<xs:attribute name=\"id\" type=\"xs:string\"/>"
+ + "</xs:complexType>"
+ + "<xs:complexType
name=\"derived\"><xs:complexContent>"
+ + "<xs:extension base=\"tns:base\"><xs:all>"
+ + "<xs:element name=\"a\" type=\"xs:string\"/>"
+ +
"</xs:all></xs:extension></xs:complexContent></xs:complexType>"
+ + "<xs:element name=\"root\"
type=\"tns:derived\"/>");
+ }
+
+ @Test
+ public void testExtendingANonEmptySequenceWithAnAllGroupIsRejected() {
+ try {
+ walkRoot("<xs:complexType name=\"base\"><xs:sequence>"
+ + "<xs:element name=\"a\" type=\"xs:string\"/>"
+ + "</xs:sequence></xs:complexType>"
+ + "<xs:complexType name=\"derived\"><xs:complexContent>"
+ + "<xs:extension base=\"tns:base\"><xs:all>"
+ + "<xs:element name=\"b\" type=\"xs:string\"/>"
+ +
"</xs:all></xs:extension></xs:complexContent></xs:complexType>"
+ + "<xs:element name=\"root\" type=\"tns:derived\"/>");
+ fail("expected an xs:all combined with other content to be
rejected");
+ } catch (XmlSchemaException expected) {
+ assertTrue(expected.getMessage(),
expected.getMessage().contains("xs:all"));
+ }
+ }
+
+ private static void assertAllGroupWalked(String body) {
+ List<String> events = walkEvents(body);
+ assertTrue(events.toString(), events.contains("onEnterAllGroup"));
+ assertFalse(events.toString(),
events.contains("onEnterSequenceGroup"));
+ }
+
@Test
public void testExtendingASequenceWithASequenceStillWalks() {
walkRoot("<xs:complexType name=\"base\"><xs:sequence>"