diff --git a/src/main/java/org/xmlet/xsdparser/core/XsdParserCore.java b/src/main/java/org/xmlet/xsdparser/core/XsdParserCore.java index 4e1ff66..83955b0 100644 --- a/src/main/java/org/xmlet/xsdparser/core/XsdParserCore.java +++ b/src/main/java/org/xmlet/xsdparser/core/XsdParserCore.java @@ -57,9 +57,9 @@ public abstract class XsdParserCore { private List parserUnsolvedElementsMap = new ArrayList<>(); /** - * A {@link List} containing the paths of files that were present in either {@link XsdInclude} or {@link XsdImport} - * objects that are present in the original or subsequent files. These paths are stored to be parsed as well, the - * parsing process only ends when all the files present in this {@link List} are parsed. + * A {@link List} containing the paths of files that were present in either {@link XsdInclude}, {@link XsdImport}, + * or {@link XsdRedefine} objects that are present in the original or subsequent files. These paths are stored to + * be parsed as well, the parsing process only ends when all the files present in this {@link List} are parsed. */ List schemaLocations = new ArrayList<>(); Map schemaLocationsMap = new HashMap<>(); @@ -143,7 +143,8 @@ private void resolveMemberTypes(XsdUnion union) { } /** - * Returns the first matching {@link XsdAbstractElement} from the {@link XsdSchema} and all {@link XsdInclude}'s with the given name. + * Returns the first matching {@link XsdAbstractElement} from the {@link XsdSchema} and all {@link XsdInclude}'s + * and {@link XsdRedefine}'s with the given name. Redefines take priority over includes. * * @param schema The initial {@link XsdSchema} to look up the file. * @param name The name of the element which is searched. @@ -153,31 +154,87 @@ private XsdAbstractElement findElement(XsdSchema schema, String name) { if (schema == null || name == null) { return null; } + + // First check local schema definitions XsdAbstractElement element = getElementFromSchema(schema, name); - if (element == null) { - List includesFromSchema = - schema.getChildrenIncludes().collect(Collectors.toList()); - Set visitedIncludes = new HashSet<>(); - while (!includesFromSchema.isEmpty()) { - final XsdInclude topSchemaInclude = includesFromSchema.remove(0); - XsdSchema resolvedSchema = getSchema(getSchemaLocation(topSchemaInclude)); - if (resolvedSchema != null && visitedIncludes.add(topSchemaInclude)) { - element = getElementFromSchema(resolvedSchema, name); - if (element == null) { - resolvedSchema.getChildrenIncludes().forEach(inc -> { - if (!visitedIncludes.add(inc)) { - includesFromSchema.add(0, inc); - } - }); - } else { - includesFromSchema.clear(); - } + if (element != null) { + return element; + } + + // Then check redefines (they have priority over includes) + element = findElementInRedefines(schema, name); + if (element != null) { + return element; + } + + // Finally check includes + List includesFromSchema = + schema.getChildrenIncludes().collect(Collectors.toList()); + Set visitedIncludes = new HashSet<>(); + while (!includesFromSchema.isEmpty()) { + final XsdInclude topSchemaInclude = includesFromSchema.remove(0); + XsdSchema resolvedSchema = getSchema(getSchemaLocation(topSchemaInclude)); + if (resolvedSchema != null && visitedIncludes.add(topSchemaInclude)) { + element = getElementFromSchema(resolvedSchema, name); + if (element == null) { + resolvedSchema.getChildrenIncludes().forEach(inc -> { + if (!visitedIncludes.add(inc)) { + includesFromSchema.add(0, inc); + } + }); + } else { + includesFromSchema.clear(); } } } return element; } + /** + * Finds an element in redefine declarations. Redefines contain both the redefined type + * and reference to the original schema, so redefined types take priority. + * + * @param schema The schema to search for redefines + * @param name The name of the element to find + * @return The redefined element or null if not found + */ + private XsdAbstractElement findElementInRedefines(XsdSchema schema, String name) { + if (schema == null || name == null) { + return null; + } + + List redefinesFromSchema = schema.getChildrenRedefines().collect(Collectors.toList()); + + for (XsdRedefine redefine : redefinesFromSchema) { + // First check if the element is redefined locally in the redefine element itself + XsdAbstractElement redefinedElement = redefine.getXsdElements() + .filter(elem -> { + if (elem instanceof XsdNamedElements) { + String elementName = ((XsdNamedElements) elem).getName(); + return name.equals(elementName); + } + return false; + }) + .findFirst() + .orElse(null); + + if (redefinedElement != null) { + return redefinedElement.clone(redefinedElement.getAttributesMap(), redefinedElement.getParent()); + } + + // If not in the redefine's local definitions, check the redefined schema + XsdSchema redefinedSchema = getSchema(redefine.getSchemaLocation()); + if (redefinedSchema != null) { + XsdAbstractElement element = getElementFromSchema(redefinedSchema, name); + if (element != null) { + return element; + } + } + } + + return null; + } + private XsdAbstractElement getElementFromSchema(XsdSchema schema, String name) { if (name == null || schema == null) { return null; @@ -202,21 +259,25 @@ private XsdAbstractElement getElementFromSchema(XsdSchema schema, String name) { } /** - * Get the SchemaLocation of an {@link XsdImport} or {@link XsdInclude}. + * Get the SchemaLocation of an {@link XsdImport}, {@link XsdInclude}, or {@link XsdRedefine}. * - * @param importOrInclude A XsdAbtractElement to get the SchemaLocation. + * @param importOrIncludeOrRedefine A XsdAbstractElement to get the SchemaLocation. * @return the SchemaLocation or null */ - private static String getSchemaLocation(XsdAbstractElement importOrInclude) { + private static String getSchemaLocation(XsdAbstractElement importOrIncludeOrRedefine) { String schemaLocation = null; - if (importOrInclude instanceof XsdInclude) { - XsdInclude include = (XsdInclude) importOrInclude; + if (importOrIncludeOrRedefine instanceof XsdInclude) { + XsdInclude include = (XsdInclude) importOrIncludeOrRedefine; schemaLocation = include.getSchemaLocation(); } - if (importOrInclude instanceof XsdImport) { - XsdImport xsdImport = (XsdImport) importOrInclude; + if (importOrIncludeOrRedefine instanceof XsdImport) { + XsdImport xsdImport = (XsdImport) importOrIncludeOrRedefine; schemaLocation = xsdImport.getSchemaLocation(); } + if (importOrIncludeOrRedefine instanceof XsdRedefine) { + XsdRedefine redefine = (XsdRedefine) importOrIncludeOrRedefine; + schemaLocation = redefine.getSchemaLocation(); + } return schemaLocation; } @@ -334,22 +395,41 @@ private List findElementTree(Map ns, Strin .findFirst() .orElse(null)))); - List schemaIncludes = importedElements.stream() - .filter(referenceBase -> referenceBase instanceof ConcreteElement && referenceBase.getElement() instanceof XsdInclude) - .map(referenceBase -> (XsdInclude) referenceBase.getElement()) + // Process both includes and redefines + List schemaIncludesAndRedefines = importedElements.stream() + .filter(referenceBase -> referenceBase instanceof ConcreteElement && + (referenceBase.getElement() instanceof XsdInclude || + referenceBase.getElement() instanceof XsdRedefine)) + .map(referenceBase -> referenceBase.getElement()) .collect(Collectors.toList()); List includedSchemaLocations = new ArrayList<>(); - while (!schemaIncludes.isEmpty()){ - XsdInclude xsdInclude = schemaIncludes.get(0); - XsdSchema xsdSchema = getSchema(xsdInclude.getSchemaLocation()); - includedSchemaLocations.add(xsdInclude.getSchemaLocation()); - - importedElements.addAll(xsdSchema.getElements()); + while (!schemaIncludesAndRedefines.isEmpty()){ + XsdAbstractElement includeOrRedefine = schemaIncludesAndRedefines.remove(0); + String schemaLocation = getSchemaLocation(includeOrRedefine); + XsdSchema xsdSchema = getSchema(schemaLocation); + includedSchemaLocations.add(schemaLocation); + + if (xsdSchema != null) { + if (includeOrRedefine instanceof XsdRedefine) { + // For redefines, add both the redefined types and the base schema elements + XsdRedefine redefine = (XsdRedefine) includeOrRedefine; + // Add redefined elements first (they take priority) + redefine.getXsdElements().forEach(elem -> + importedElements.add(ReferenceBase.createFromXsd(elem)) + ); + } - schemaIncludes.remove(0); + importedElements.addAll(xsdSchema.getElements()); - schemaIncludes.addAll(xsdSchema.getChildrenIncludes().filter(moreXsdInclude -> !includedSchemaLocations.contains(moreXsdInclude.getSchemaLocation())).collect(Collectors.toList())); + // Add more includes and redefines to process + xsdSchema.getChildrenIncludes() + .filter(moreInclude -> !includedSchemaLocations.contains(moreInclude.getSchemaLocation())) + .forEach(schemaIncludesAndRedefines::add); + xsdSchema.getChildrenRedefines() + .filter(moreRedefine -> !includedSchemaLocations.contains(moreRedefine.getSchemaLocation())) + .forEach(schemaIncludesAndRedefines::add); + } } return importedElements; @@ -427,7 +507,13 @@ private void resolveInnerRefs() { includedFiles.add(fileName); findTransitiveDependencies(fileName, includedFiles); - includedFiles.addAll(getResultXsdSchemas().filter(schema -> schema.getChildrenIncludes().anyMatch(xsdInclude -> xsdInclude.getSchemaLocation().equals(fileName))).map(XsdSchema::getFilePath).distinct().collect(Collectors.toList())); + // Add files that include or redefine this file + includedFiles.addAll(getResultXsdSchemas() + .filter(schema -> schema.getChildrenIncludes().anyMatch(xsdInclude -> xsdInclude.getSchemaLocation().equals(fileName)) || + schema.getChildrenRedefines().anyMatch(xsdRedefine -> xsdRedefine.getSchemaLocation().equals(fileName))) + .map(XsdSchema::getFilePath) + .distinct() + .collect(Collectors.toList())); List includedElements = new ArrayList<>(parseElements.get(fileName)); @@ -486,11 +572,23 @@ private void resolveInnerRefs() { } private void findTransitiveDependencies(String fileName, Set dependencies) { + // Collect both includes and redefines List includedFiles = parseElements.get(fileName) .stream() - .filter(referenceBase -> referenceBase instanceof ConcreteElement && referenceBase.getElement() instanceof XsdInclude) - .map(referenceBase -> (((XsdInclude) referenceBase.getElement()).getSchemaLocation())) + .filter(referenceBase -> referenceBase instanceof ConcreteElement && + (referenceBase.getElement() instanceof XsdInclude || + referenceBase.getElement() instanceof XsdRedefine)) + .map(referenceBase -> { + XsdAbstractElement element = referenceBase.getElement(); + if (element instanceof XsdInclude) { + return ((XsdInclude) element).getSchemaLocation(); + } else if (element instanceof XsdRedefine) { + return ((XsdRedefine) element).getSchemaLocation(); + } + return null; + }) + .filter(Objects::nonNull) .map(schemaLocation -> toRealFileName(fileName, schemaLocation)) .filter(Optional::isPresent) .map(Optional::get) diff --git a/src/main/java/org/xmlet/xsdparser/core/utils/DefaultParserConfig.java b/src/main/java/org/xmlet/xsdparser/core/utils/DefaultParserConfig.java index 9faca44..4e9a8ea 100644 --- a/src/main/java/org/xmlet/xsdparser/core/utils/DefaultParserConfig.java +++ b/src/main/java/org/xmlet/xsdparser/core/utils/DefaultParserConfig.java @@ -169,6 +169,7 @@ public Map getParseMappers() { parseMappers.put(XsdGroup.TAG, new ConfigEntryData(XsdGroup::parse, elem -> new XsdGroupVisitor((XsdGroup) elem))); parseMappers.put(XsdInclude.TAG, new ConfigEntryData(XsdInclude::parse, elem -> new XsdAnnotatedElementsVisitor((XsdInclude) elem))); parseMappers.put(XsdImport.TAG, new ConfigEntryData(XsdImport::parse, elem -> new XsdAnnotatedElementsVisitor((XsdImport) elem))); + parseMappers.put(XsdRedefine.TAG, new ConfigEntryData(XsdRedefine::parse, elem -> new XsdRedefineVisitor((XsdRedefine) elem))); parseMappers.put(XsdSequence.TAG, new ConfigEntryData(XsdSequence::parse, elem -> new XsdSequenceVisitor((XsdSequence) elem))); parseMappers.put(XsdSimpleType.TAG, new ConfigEntryData(XsdSimpleType::parse, elem -> new XsdSimpleTypeVisitor((XsdSimpleType) elem))); parseMappers.put(XsdList.TAG, new ConfigEntryData(XsdList::parse, elem -> new XsdListVisitor((XsdList) elem))); @@ -208,6 +209,7 @@ public Map getParseMappers() { parseMappers.put(XsdGroup.XS_TAG, new ConfigEntryData(XsdGroup::parse, elem -> new XsdGroupVisitor((XsdGroup) elem))); parseMappers.put(XsdInclude.XS_TAG, new ConfigEntryData(XsdInclude::parse, elem -> new XsdAnnotatedElementsVisitor((XsdInclude) elem))); parseMappers.put(XsdImport.XS_TAG, new ConfigEntryData(XsdImport::parse, elem -> new XsdAnnotatedElementsVisitor((XsdImport) elem))); + parseMappers.put(XsdRedefine.XS_TAG, new ConfigEntryData(XsdRedefine::parse, elem -> new XsdRedefineVisitor((XsdRedefine) elem))); parseMappers.put(XsdSequence.XS_TAG, new ConfigEntryData(XsdSequence::parse, elem -> new XsdSequenceVisitor((XsdSequence) elem))); parseMappers.put(XsdSimpleType.XS_TAG, new ConfigEntryData(XsdSimpleType::parse, elem -> new XsdSimpleTypeVisitor((XsdSimpleType) elem))); parseMappers.put(XsdList.XS_TAG, new ConfigEntryData(XsdList::parse, elem -> new XsdListVisitor((XsdList) elem))); @@ -247,6 +249,7 @@ public Map getParseMappers() { parseMappers.put(XsdGroup.XSD_TAG, new ConfigEntryData(XsdGroup::parse, elem -> new XsdGroupVisitor((XsdGroup) elem))); parseMappers.put(XsdInclude.XSD_TAG, new ConfigEntryData(XsdInclude::parse, elem -> new XsdAnnotatedElementsVisitor((XsdInclude) elem))); parseMappers.put(XsdImport.XSD_TAG, new ConfigEntryData(XsdImport::parse, elem -> new XsdAnnotatedElementsVisitor((XsdImport) elem))); + parseMappers.put(XsdRedefine.XSD_TAG, new ConfigEntryData(XsdRedefine::parse, elem -> new XsdRedefineVisitor((XsdRedefine) elem))); parseMappers.put(XsdSequence.XSD_TAG, new ConfigEntryData(XsdSequence::parse, elem -> new XsdSequenceVisitor((XsdSequence) elem))); parseMappers.put(XsdSimpleType.XSD_TAG, new ConfigEntryData(XsdSimpleType::parse, elem -> new XsdSimpleTypeVisitor((XsdSimpleType) elem))); parseMappers.put(XsdList.XSD_TAG, new ConfigEntryData(XsdList::parse, elem -> new XsdListVisitor((XsdList) elem))); diff --git a/src/main/java/org/xmlet/xsdparser/xsdelements/XsdGroup.java b/src/main/java/org/xmlet/xsdparser/xsdelements/XsdGroup.java index 32be5eb..3b97a73 100644 --- a/src/main/java/org/xmlet/xsdparser/xsdelements/XsdGroup.java +++ b/src/main/java/org/xmlet/xsdparser/xsdelements/XsdGroup.java @@ -69,22 +69,22 @@ public void validateSchemaRules() { } /** - * Asserts if the current object has the name attribute when not being a direct child of the XsdSchema element, which is - * not allowed, throwing an exception in that case. + * Asserts if the current object has the name attribute when not being a direct child of the XsdSchema or XsdRedefine + * element, which is not allowed, throwing an exception in that case. */ private void rule2() { - if (!(parent instanceof XsdSchema) && name != null){ - throw new ParsingException(XSD_TAG + " element: The " + NAME_TAG + " should only be used when the parent of the " + XSD_TAG + " is the " + XsdSchema.XSD_TAG + " element." ); + if (!(parent instanceof XsdSchema) && !(parent instanceof XsdRedefine) && name != null){ + throw new ParsingException(XSD_TAG + " element: The " + NAME_TAG + " should only be used when the parent of the " + XSD_TAG + " is the " + XsdSchema.XSD_TAG + " or " + XsdRedefine.XSD_TAG + " element." ); } } /** - * Asserts if the current has no value for its name attribute while being a direct child of the top level XsdSchema element, - * which is required. Throws an exception if no name is present. + * Asserts if the current has no value for its name attribute while being a direct child of the top level XsdSchema + * or XsdRedefine element, which is required. Throws an exception if no name is present. */ private void rule3() { - if (parent instanceof XsdSchema && name == null){ - throw new ParsingException(XSD_TAG + " element: The " + NAME_TAG + " should is required the parent of the " + XSD_TAG + " is the " + XsdSchema.XSD_TAG + " element." ); + if ((parent instanceof XsdSchema || parent instanceof XsdRedefine) && name == null){ + throw new ParsingException(XSD_TAG + " element: The " + NAME_TAG + " should is required the parent of the " + XSD_TAG + " is the " + XsdSchema.XSD_TAG + " or " + XsdRedefine.XSD_TAG + " element." ); } } diff --git a/src/main/java/org/xmlet/xsdparser/xsdelements/XsdRedefine.java b/src/main/java/org/xmlet/xsdparser/xsdelements/XsdRedefine.java new file mode 100644 index 0000000..c92f8da --- /dev/null +++ b/src/main/java/org/xmlet/xsdparser/xsdelements/XsdRedefine.java @@ -0,0 +1,142 @@ +package org.xmlet.xsdparser.xsdelements; + +import org.xmlet.xsdparser.core.XsdParserCore; +import org.xmlet.xsdparser.core.utils.ParseData; +import org.xmlet.xsdparser.xsdelements.elementswrapper.ReferenceBase; +import org.xmlet.xsdparser.xsdelements.visitors.XsdAbstractElementVisitor; + +import jakarta.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * A class representing the xsd:redefine element. + * + * The xsd:redefine element allows you to redefine simple and complex types, groups, and attribute groups + * from an external schema file. It is similar to xsd:include but allows modifications to the included components. + * + * @see xsd:redefine description at W3C + */ +public class XsdRedefine extends XsdAnnotatedElements { + + public static final String XSD_TAG = "xsd:redefine"; + public static final String XS_TAG = "xs:redefine"; + public static final String TAG = "redefine"; + + /** + * Specifies the URI to the schema to be redefined. + * This attribute is used to specify another file location that contains element definitions + * that will be redefined in this element. + */ + private String schemaLocation; + + /** + * The redefinition elements contained in this {@link XsdRedefine} element. + * These can be simpleType, complexType, group, or attributeGroup definitions that + * override or extend the definitions in the included schema. + */ + private List redefinitions = new ArrayList<>(); + + private XsdRedefine(@NotNull XsdParserCore parser, @NotNull Map attributesMap, @NotNull Function visitorFunction) { + super(parser, attributesMap, visitorFunction); + + this.schemaLocation = attributesMap.getOrDefault(SCHEMA_LOCATION, schemaLocation); + + if (this.schemaLocation != null){ + parser.addFileToParse(this.schemaLocation); + } + } + + public static ReferenceBase parse(@NotNull ParseData parseData){ + return xsdParseSkeleton(parseData.node, new XsdRedefine(parseData.parserInstance, convertNodeMap(parseData.node.getAttributes()), parseData.visitorFunction)); + } + + @Override + public void accept(XsdAbstractElementVisitor visitorParam) { + super.accept(visitorParam); + visitorParam.visit(this); + } + + @Override + public Stream getXsdElements() { + return redefinitions.stream(); + } + + public void add(XsdSimpleType element) { + redefinitions.add(element); + } + + public void add(XsdComplexType element) { + redefinitions.add(element); + } + + public void add(XsdGroup element) { + redefinitions.add(element); + } + + public void add(XsdAttributeGroup element) { + redefinitions.add(element); + } + + public void add(XsdAnnotation element) { + redefinitions.add(element); + } + + @SuppressWarnings("unused") + public String getSchemaLocation() { + return schemaLocation; + } + + /** + * @return The redefined simple types. + */ + @SuppressWarnings("unused") + public Stream getChildrenSimpleTypes(){ + return getXsdElements() + .filter(element -> element instanceof XsdSimpleType) + .map(element -> (XsdSimpleType) element); + } + + /** + * @return The redefined complex types. + */ + @SuppressWarnings("unused") + public Stream getChildrenComplexTypes(){ + return getXsdElements() + .filter(element -> element instanceof XsdComplexType) + .map(element -> (XsdComplexType) element); + } + + /** + * @return The redefined groups. + */ + @SuppressWarnings("unused") + public Stream getChildrenGroups(){ + return getXsdElements() + .filter(element -> element instanceof XsdGroup) + .map(element -> (XsdGroup) element); + } + + /** + * @return The redefined attribute groups. + */ + @SuppressWarnings("unused") + public Stream getChildrenAttributeGroups(){ + return getXsdElements() + .filter(element -> element instanceof XsdAttributeGroup) + .map(element -> (XsdAttributeGroup) element); + } + + /** + * @return The annotations within this redefine element. + */ + @SuppressWarnings("unused") + public Stream getChildrenAnnotations(){ + return getXsdElements() + .filter(element -> element instanceof XsdAnnotation) + .map(element -> (XsdAnnotation) element); + } +} diff --git a/src/main/java/org/xmlet/xsdparser/xsdelements/XsdSchema.java b/src/main/java/org/xmlet/xsdparser/xsdelements/XsdSchema.java index 5863b77..3833901 100644 --- a/src/main/java/org/xmlet/xsdparser/xsdelements/XsdSchema.java +++ b/src/main/java/org/xmlet/xsdparser/xsdelements/XsdSchema.java @@ -142,6 +142,10 @@ public void add(XsdImport element) { elements.add(element); } + public void add(XsdRedefine element) { + elements.add(element); + } + public void add(XsdAnnotation element) { elements.add(element); } @@ -219,6 +223,16 @@ public Stream getChildrenImports(){ .map(element -> (XsdImport) element); } + /** + * @return The children elements that are of the type {@link XsdRedefine}. + */ + @SuppressWarnings("unused") + public Stream getChildrenRedefines(){ + return getXsdElements() + .filter(element -> element instanceof XsdRedefine) + .map(element -> (XsdRedefine) element); + } + /** * @return The children elements that are of the type {@link XsdAnnotation}. */ diff --git a/src/main/java/org/xmlet/xsdparser/xsdelements/XsdSimpleType.java b/src/main/java/org/xmlet/xsdparser/xsdelements/XsdSimpleType.java index d86fc13..f2a09a2 100644 --- a/src/main/java/org/xmlet/xsdparser/xsdelements/XsdSimpleType.java +++ b/src/main/java/org/xmlet/xsdparser/xsdelements/XsdSimpleType.java @@ -74,22 +74,22 @@ public void validateSchemaRules() { } /** - * Asserts that the current object has the required name attribute when not being a direct child of the XsdSchema element. - * Throws an exception if the required attribute is not present. + * Asserts that the current object has the required name attribute when not being a direct child of the XsdSchema + * or XsdRedefine element. Throws an exception if the required attribute is not present. */ private void rule2() { - if (!(parent instanceof XsdSchema) && name != null){ - throw new ParsingException(XSD_TAG + " element: The " + NAME_TAG + " should only be used when the parent of the " + XSD_TAG + " is the " + XsdSchema.XSD_TAG + " element." ); + if (!(parent instanceof XsdSchema) && !(parent instanceof XsdRedefine) && name != null){ + throw new ParsingException(XSD_TAG + " element: The " + NAME_TAG + " should only be used when the parent of the " + XSD_TAG + " is the " + XsdSchema.XSD_TAG + " or " + XsdRedefine.XSD_TAG + " element." ); } } /** - * Asserts if the current has no value for its name attribute while being a direct child of the top level XsdSchema element, - * which is required. Throws an exception if no name is present. + * Asserts if the current has no value for its name attribute while being a direct child of the top level XsdSchema + * or XsdRedefine element, which is required. Throws an exception if no name is present. */ private void rule3() { - if (parent instanceof XsdSchema && name == null){ - throw new ParsingException(XSD_TAG + " element: The " + NAME_TAG + " should is required the parent of the " + XSD_TAG + " is the " + XsdSchema.XSD_TAG + " element." ); + if ((parent instanceof XsdSchema || parent instanceof XsdRedefine) && name == null){ + throw new ParsingException(XSD_TAG + " element: The " + NAME_TAG + " should is required the parent of the " + XSD_TAG + " is the " + XsdSchema.XSD_TAG + " or " + XsdRedefine.XSD_TAG + " element." ); } } diff --git a/src/main/java/org/xmlet/xsdparser/xsdelements/visitors/XsdAbstractElementVisitor.java b/src/main/java/org/xmlet/xsdparser/xsdelements/visitors/XsdAbstractElementVisitor.java index c9a4b15..b1b80ec 100644 --- a/src/main/java/org/xmlet/xsdparser/xsdelements/visitors/XsdAbstractElementVisitor.java +++ b/src/main/java/org/xmlet/xsdparser/xsdelements/visitors/XsdAbstractElementVisitor.java @@ -99,6 +99,8 @@ default void visit(XsdImport xsdImport) {} default void visit(XsdInclude xsdInclude) {} + default void visit(XsdRedefine xsdRedefine) {} + default void visit(XsdAny xsdAny) {} XsdAbstractElement getOwner(); diff --git a/src/main/java/org/xmlet/xsdparser/xsdelements/visitors/XsdRedefineVisitor.java b/src/main/java/org/xmlet/xsdparser/xsdelements/visitors/XsdRedefineVisitor.java new file mode 100644 index 0000000..45111f7 --- /dev/null +++ b/src/main/java/org/xmlet/xsdparser/xsdelements/visitors/XsdRedefineVisitor.java @@ -0,0 +1,52 @@ +package org.xmlet.xsdparser.xsdelements.visitors; + +import org.xmlet.xsdparser.xsdelements.*; + +/** + * Visitor for {@link XsdRedefine} elements. Handles child elements (simpleType, complexType, group, attributeGroup) + * that are redefinitions of components from the included schema. + */ +public class XsdRedefineVisitor extends AttributesVisitor { + + private XsdRedefine owner; + + public XsdRedefineVisitor(XsdRedefine owner) { + super(owner); + this.owner = owner; + } + + @Override + public XsdRedefine getOwner() { + return owner; + } + + @Override + public void visit(XsdAnnotation element) { + super.visit(element); + owner.add(element); + } + + @Override + public void visit(XsdSimpleType element) { + super.visit(element); + owner.add(element); + } + + @Override + public void visit(XsdComplexType element) { + super.visit(element); + owner.add(element); + } + + @Override + public void visit(XsdGroup element) { + super.visit(element); + owner.add(element); + } + + @Override + public void visit(XsdAttributeGroup element) { + super.visit(element); + owner.add(element); + } +} diff --git a/src/main/java/org/xmlet/xsdparser/xsdelements/visitors/XsdSchemaVisitor.java b/src/main/java/org/xmlet/xsdparser/xsdelements/visitors/XsdSchemaVisitor.java index 7e6edfc..2b04dd2 100644 --- a/src/main/java/org/xmlet/xsdparser/xsdelements/visitors/XsdSchemaVisitor.java +++ b/src/main/java/org/xmlet/xsdparser/xsdelements/visitors/XsdSchemaVisitor.java @@ -30,6 +30,13 @@ public void visit(XsdImport element) { owner.add(element); } + @Override + public void visit(XsdRedefine element) { + super.visit(element); + + owner.add(element); + } + @Override public void visit(XsdAnnotation element) { super.visit(element); diff --git a/src/test/java/org/xmlet/xsdparser/RedefineTest.java b/src/test/java/org/xmlet/xsdparser/RedefineTest.java new file mode 100644 index 0000000..3720c5d --- /dev/null +++ b/src/test/java/org/xmlet/xsdparser/RedefineTest.java @@ -0,0 +1,126 @@ +package org.xmlet.xsdparser; + +import static org.junit.Assert.*; + +import java.net.URL; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.Test; +import org.xmlet.xsdparser.core.XsdParser; +import org.xmlet.xsdparser.core.utils.UnsolvedReferenceItem; +import org.xmlet.xsdparser.xsdelements.*; + +/** + * Tests for xsd:redefine support + */ +public class RedefineTest { + + @Test + public void testBasicRedefine() { + String filePath = getFilePath("redefine_test_main.xsd"); + XsdParser parser = new XsdParser(filePath); + + // Check that there are no unsolved references + List unsolvedReferenceItemList = parser.getUnsolvedReferences(); + assertTrue("There should be no unsolved references", unsolvedReferenceItemList.isEmpty()); + + // Get the schemas + List schemas = parser.getResultXsdSchemas().collect(Collectors.toList()); + assertFalse("Should have parsed schemas", schemas.isEmpty()); + + // Find the main schema + XsdSchema mainSchema = schemas.stream() + .filter(schema -> schema.getFilePath().endsWith("redefine_test_main.xsd")) + .findFirst() + .orElse(null); + assertNotNull("Main schema should be found", mainSchema); + + // Check that redefine elements are present + List redefines = mainSchema.getChildrenRedefines().collect(Collectors.toList()); + assertFalse("Should have redefine elements", redefines.isEmpty()); + assertEquals("Should have exactly one redefine", 1, redefines.size()); + + XsdRedefine redefine = redefines.get(0); + assertEquals("redefine_test_base.xsd", redefine.getSchemaLocation()); + + // Check that redefined types are present + List redefinedComplexTypes = redefine.getChildrenComplexTypes().collect(Collectors.toList()); + assertFalse("Should have redefined complex types", redefinedComplexTypes.isEmpty()); + + List redefinedSimpleTypes = redefine.getChildrenSimpleTypes().collect(Collectors.toList()); + assertFalse("Should have redefined simple types", redefinedSimpleTypes.isEmpty()); + + List redefinedGroups = redefine.getChildrenGroups().collect(Collectors.toList()); + assertFalse("Should have redefined groups", redefinedGroups.isEmpty()); + + List redefinedAttributeGroups = redefine.getChildrenAttributeGroups().collect(Collectors.toList()); + assertFalse("Should have redefined attribute groups", redefinedAttributeGroups.isEmpty()); + } + + @Test + public void testChainedRedefine() { + String filePath = getFilePath("redefine_test_chained_top.xsd"); + XsdParser parser = new XsdParser(filePath); + + // Check that there are no unsolved references + List unsolvedReferenceItemList = parser.getUnsolvedReferences(); + assertTrue("There should be no unsolved references in chained redefines", + unsolvedReferenceItemList.isEmpty()); + + // Get all schemas + List schemas = parser.getResultXsdSchemas().collect(Collectors.toList()); + + // Should have parsed all three schemas (top, middle, base) + assertTrue("Should have parsed at least 3 schemas", schemas.size() >= 3); + + // Verify the top schema has a redefine + XsdSchema topSchema = schemas.stream() + .filter(schema -> schema.getFilePath().endsWith("redefine_test_chained_top.xsd")) + .findFirst() + .orElse(null); + assertNotNull("Top schema should be found", topSchema); + + List topRedefines = topSchema.getChildrenRedefines().collect(Collectors.toList()); + assertEquals("Top schema should have one redefine", 1, topRedefines.size()); + } + + @Test + public void testRedefineWithElements() { + String filePath = getFilePath("redefine_test_main.xsd"); + XsdParser parser = new XsdParser(filePath); + + List schemas = parser.getResultXsdSchemas().collect(Collectors.toList()); + XsdSchema mainSchema = schemas.stream() + .filter(schema -> schema.getFilePath().endsWith("redefine_test_main.xsd")) + .findFirst() + .orElse(null); + + assertNotNull("Main schema should be found", mainSchema); + + // Check that elements using redefined types are present + List elements = mainSchema.getChildrenElements().collect(Collectors.toList()); + assertFalse("Should have elements defined", elements.isEmpty()); + + // Find the Person element + XsdElement personElement = elements.stream() + .filter(el -> "Person".equals(el.getName())) + .findFirst() + .orElse(null); + assertNotNull("Person element should be found", personElement); + } + + /** + * @param fileName The name of the test file + * @return Obtains the filePath of the file from the test resources folder + */ + private static String getFilePath(String fileName) { + URL resource = RedefineTest.class.getClassLoader().getResource(fileName); + + if (resource != null) { + return resource.getPath(); + } else { + throw new RuntimeException("The " + fileName + " file is missing from the XsdParser resource folder."); + } + } +} diff --git a/src/test/resources/redefine_test_base.xsd b/src/test/resources/redefine_test_base.xsd new file mode 100644 index 0000000..7d22ed2 --- /dev/null +++ b/src/test/resources/redefine_test_base.xsd @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/redefine_test_chained_middle.xsd b/src/test/resources/redefine_test_chained_middle.xsd new file mode 100644 index 0000000..6835dec --- /dev/null +++ b/src/test/resources/redefine_test_chained_middle.xsd @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/redefine_test_chained_top.xsd b/src/test/resources/redefine_test_chained_top.xsd new file mode 100644 index 0000000..10e8fef --- /dev/null +++ b/src/test/resources/redefine_test_chained_top.xsd @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/redefine_test_main.xsd b/src/test/resources/redefine_test_main.xsd new file mode 100644 index 0000000..558141e --- /dev/null +++ b/src/test/resources/redefine_test_main.xsd @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +