Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/main/java/org/rumbledb/xml/schema/XmlSchemaCatalog.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.xml.validation.Schema;

Expand Down Expand Up @@ -65,10 +66,11 @@ public final class XmlSchemaCatalog {
private final XercesTypedValueConverter typedValueConverter;
private final XercesSimpleTypeCaster simpleTypeCaster;

XmlSchemaCatalog(@NonNull XSModel schemaModel, @NonNull Schema validationSchema) {
XmlSchemaCatalog(
@NonNull XSModel schemaModel, @NonNull Schema validationSchema, Map<String, String> namespacePrefixes) {
this.schemaModel = schemaModel;
this.validationSchema = validationSchema;
this.typeMapper = new XmlSchemaTypeMapper();
this.typeMapper = new XmlSchemaTypeMapper(namespacePrefixes);
this.typedValueConverter = new XercesTypedValueConverter(this.typeMapper);
this.simpleTypeCaster = new XercesSimpleTypeCaster(this.typeMapper, this.typedValueConverter);
}
Expand All @@ -79,14 +81,15 @@ public Optional<XSTypeDefinition> getTypeDefinition(@NonNull Name name) {

/**
* Returns all global named types in the catalog, including built-in types, lists, unions, and complex types.
* The names have no prefix; callers can use {@link #isSchemaCastTarget(Name)} to select constructor candidates.
* Names retain schema-import prefixes; callers can use {@link #isSchemaCastTarget(Name)} to select constructor
* candidates.
*/
public List<Name> getNamedTypeNames() {
XSNamedMap types = this.schemaModel.getComponents(XSConstants.TYPE_DEFINITION);
List<Name> names = new ArrayList<>();
for (int index = 0; index < types.getLength(); index++) {
XSTypeDefinition type = (XSTypeDefinition) types.item(index);
names.add(new Name(type.getNamespace(), null, type.getName()));
names.add(this.typeMapper.nameOf(type));
}
return List.copyOf(names);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public static XmlSchemaCatalog loadBuiltInCatalog() {
source.setSystemId(EMPTY_SCHEMA_SYSTEM_ID);

LoadedSchema loadedSchema = loadedSchema(schemaFactory.newSchema(source), errorHandler, metadata);
return new XmlSchemaCatalog(loadedSchema.schemaModel(), loadedSchema.validationSchema());
return new XmlSchemaCatalog(loadedSchema.schemaModel(), loadedSchema.validationSchema(), Map.of());
} catch (SAXException | RuntimeException exception) {
throw new SchemaImportException(
"Unable to initialize the built-in XML Schema environment: " + exception.getMessage(),
Expand Down Expand Up @@ -120,7 +120,14 @@ public static Optional<XmlSchemaCatalog> load(
metadata,
schemaResourceObserver);
LoadedSchema loadedSchema = loadSchema(resolvedImports.locations(), resolver, metadata);
XmlSchemaCatalog catalog = new XmlSchemaCatalog(loadedSchema.schemaModel(), loadedSchema.validationSchema());
Map<String, String> namespacePrefixes = new LinkedHashMap<>();
for (SchemaImport schemaImport : schemaImports) {
if (schemaImport.getBindingKind() == SchemaImport.BindingKind.PREFIX) {
namespacePrefixes.put(schemaImport.getTargetNamespace(), schemaImport.getPrefix());
}
}
XmlSchemaCatalog catalog =
new XmlSchemaCatalog(loadedSchema.schemaModel(), loadedSchema.validationSchema(), namespacePrefixes);
verifyImportedNamespaces(schemaImports, catalog);

return Optional.of(catalog);
Expand Down
20 changes: 17 additions & 3 deletions src/main/java/org/rumbledb/xml/schema/XmlSchemaTypeMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ final class XmlSchemaTypeMapper {
private final Map<XSTypeDefinition, XmlSchemaTypeAnnotation> mappedAnnotations;
private final Map<XSTypeDefinition, Name> anonymousTypeNames;
private final Set<XSTypeDefinition> typesBeingMapped;
private final Map<String, String> namespacePrefixes;

XmlSchemaTypeMapper() {
XmlSchemaTypeMapper(Map<String, String> namespacePrefixes) {
this.namespacePrefixes = Map.copyOf(namespacePrefixes);
this.builtinTypeMapper = new XercesBuiltinAtomicTypeMapper();
this.mappedTypes = new IdentityHashMap<>();
this.mappedAnnotations = new IdentityHashMap<>();
Expand Down Expand Up @@ -240,13 +242,25 @@ private static boolean isPureUnion(XSSimpleTypeDefinition unionType) {
* Anonymous definitions keep distinct internal names for the lifetime of this catalog;
* they are not added to the query's in-scope named schema types.
*/
private Name nameOf(XSTypeDefinition schemaType) {
Name nameOf(XSTypeDefinition schemaType) {
if (schemaType.getAnonymous() || schemaType.getName() == null) {
return this.anonymousTypeNames.computeIfAbsent(
schemaType, type -> new Name(ANONYMOUS_TYPE_NAMESPACE, null, "anonymousType-" + UUID.randomUUID()));
}
String namespace = schemaType.getNamespace();
return new Name(namespace, Name.XS_NS.equals(namespace) ? "xs" : null, schemaType.getName());
return new Name(namespace, prefixOf(namespace), schemaType.getName());
}

/** The prefix the query's schema imports bind to the namespace, or null if there is none. */
private String prefixOf(String namespace) {
if (Name.XS_NS.equals(namespace)) {
return "xs";
}
if (namespace == null) {
// Types in no namespace have no prefix; the immutable map also rejects null lookups.
return null;
}
return this.namespacePrefixes.get(namespace);
}

private static void insertBefore(List<Name> names, Name name, Name successor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.rumbledb.xml.schema;

import java.io.File;
import java.util.Map;

import org.apache.xerces.dom.DOMInputImpl;
import org.apache.xerces.impl.xs.XMLSchemaLoader;
Expand Down Expand Up @@ -50,7 +51,7 @@ void sharesNamesAcrossNodeAndAtomicMappingWithoutMergingDistinctDefinitions() {
model.getElementDeclaration("count", "urn:anonymous-types").getTypeDefinition();
XSTypeDefinition second =
model.getElementDeclaration("otherCount", "urn:anonymous-types").getTypeDefinition();
XmlSchemaTypeMapper mapper = new XmlSchemaTypeMapper();
XmlSchemaTypeMapper mapper = new XmlSchemaTypeMapper(Map.of());
// Map the first definition as a node annotation first, and the second as an atomic type first.
var annotation = mapper.mapTypeAnnotation(first);
var atomic = mapper.mapGeneralizedAtomicType(first).orElseThrow();
Expand Down Expand Up @@ -92,7 +93,7 @@ void retainsAnonymousBaseTypesInAnnotationHierarchy() {
var simple = complex.getSimpleType();
assertTrue(simple.getAnonymous());
assertTrue(simple.getBaseType().getAnonymous());
XmlSchemaTypeMapper mapper = new XmlSchemaTypeMapper();
XmlSchemaTypeMapper mapper = new XmlSchemaTypeMapper(Map.of());
var base = mapper.mapGeneralizedAtomicType(simple.getBaseType()).orElseThrow();
var derived = mapper.mapGeneralizedAtomicType(simple).orElseThrow();
assertTrue(derived.isSubtypeOf(base));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
public class XercesTypedValueConverterTest {

private static final XercesBuiltinAtomicTypeMapper TYPE_MAPPER = new XercesBuiltinAtomicTypeMapper();
private static final XercesTypedValueConverter CONVERTER = new XercesTypedValueConverter(new XmlSchemaTypeMapper());
private static final XercesTypedValueConverter CONVERTER =
new XercesTypedValueConverter(new XmlSchemaTypeMapper(Map.of()));

@Test
public void mapsBuiltInAtomicTypesByExpandedName() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import org.rumbledb.api.Item;
import org.rumbledb.api.Rumble;
import org.rumbledb.bindings.ExternalBindings;
import org.rumbledb.compiler.CompilationPipeline;
import org.rumbledb.config.CompilationConfiguration;
Expand All @@ -45,6 +47,30 @@ public class XmlSchemaCatalogLoaderTest {

private static final String NAMESPACE = "urn:test";

@Test
public void typesKeepTheImportPrefix(@TempDir Path directory) throws Exception {
Files.writeString(
directory.resolve("types.xsd"),
schema(NAMESPACE, "<xs:simpleType name=\"Code\"><xs:restriction base=\"xs:string\"/></xs:simpleType>"));
URI queryUri = directory.resolve("query.xq").toUri();
// Two prefixes for the same schema show that the prefix comes from the import.
for (String prefix : List.of("t", "app")) {
String query = "import schema namespace " + prefix + " = \"urn:test\" at \"types.xsd\"; ";
MainModule module = compile(query + "()", queryUri, new ResourceResolver());
Assertions.assertEquals(
prefix + ":Code",
module.getStaticContext()
.getInScopeSchemaTypes()
.getInScopeSchemaType(new Name(NAMESPACE, null, "Code"))
.toString());
Item value = new Rumble(RumbleConfiguration.builder().build())
.runQuery(query + prefix + ":Code(\"hello\")", queryUri)
.getAsList()
.get(0);
Assertions.assertEquals(prefix, value.getDynamicType().getName().getPrefix());
}
}

@Test
public void usesResourceMappingsWhenTheImportHasNoLocationHint(@TempDir Path directory) throws Exception {
URI logicalSchemaUri = URI.create(NAMESPACE);
Expand Down
Loading