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>"

Reply via email to