Skip to content

Commit 042e967

Browse files
committed
fix(datamapper): scope schema elements to root include chain (#3462)
Problem When multiple XSD files are loaded into the DataMapper, unrelated schemas can shadow elements/types if they have the same name as the ones in the root element's include chain. This happens because populateNamedTypeFragments and populateGlobalElementFragmentsForSchema iterated through all loaded schemas. Solution - Performed a BFS traversal from the root element's parent schema using getExternals() to find only reachable schemas. - Scoped the schema loop in populateNamedTypeFragments to just these reachable schemas. - If unreachable schemas define elements/types with the same name as the reachable ones, we now generate a warning. - Added a test case that ensures shadowing is avoided and reachable schemas' types are used. Fixes #3462
1 parent 12a4c4b commit 042e967

2 files changed

Lines changed: 149 additions & 4 deletions

File tree

packages/ui/src/services/document/xml-schema/xml-schema-document.service.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,85 @@ describe('XmlSchemaDocumentService', () => {
11171117
expect(rootElement.fields[0].name).toBe('partA');
11181118
expect(rootElement.fields[1].name).toBe('partB');
11191119
});
1120+
1121+
it('should detect shadowed elements and only use reachable schema definitions', () => {
1122+
const mainXsd = `<?xml version="1.0" encoding="UTF-8"?>
1123+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
1124+
elementFormDefault="qualified">
1125+
<xs:include schemaLocation="MainTypes.xsd"/>
1126+
<xs:element name="Root">
1127+
<xs:complexType>
1128+
<xs:sequence>
1129+
<xs:element ref="Order" minOccurs="0" maxOccurs="unbounded"/>
1130+
</xs:sequence>
1131+
</xs:complexType>
1132+
</xs:element>
1133+
</xs:schema>`;
1134+
const mainTypesXsd = `<?xml version="1.0" encoding="UTF-8"?>
1135+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
1136+
elementFormDefault="qualified">
1137+
<xs:complexType name="BaseObject" abstract="true">
1138+
<xs:sequence>
1139+
<xs:element name="ObjectId" type="xs:string" minOccurs="0"/>
1140+
</xs:sequence>
1141+
</xs:complexType>
1142+
<xs:element name="Order">
1143+
<xs:complexType>
1144+
<xs:complexContent>
1145+
<xs:extension base="BaseObject">
1146+
<xs:sequence>
1147+
<xs:element name="OrderItem" type="xs:string"/>
1148+
<xs:element name="Quantity" type="xs:int"/>
1149+
<xs:element name="Price" type="xs:double"/>
1150+
</xs:sequence>
1151+
</xs:extension>
1152+
</xs:complexContent>
1153+
</xs:complexType>
1154+
</xs:element>
1155+
</xs:schema>`;
1156+
const unrelatedXsd = `<?xml version="1.0" encoding="UTF-8"?>
1157+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
1158+
elementFormDefault="qualified">
1159+
<xs:element name="Order">
1160+
<xs:complexType>
1161+
<xs:sequence>
1162+
<xs:element name="Status" type="xs:string"/>
1163+
<xs:element name="Action" type="xs:string"/>
1164+
</xs:sequence>
1165+
</xs:complexType>
1166+
</xs:element>
1167+
</xs:schema>`;
1168+
1169+
const definition = new DocumentDefinition(
1170+
DocumentType.SOURCE_BODY,
1171+
DocumentDefinitionType.XML_SCHEMA,
1172+
'test-doc',
1173+
{
1174+
'Main.xsd': mainXsd,
1175+
'MainTypes.xsd': mainTypesXsd,
1176+
'Unrelated.xsd': unrelatedXsd,
1177+
},
1178+
);
1179+
definition.rootElementChoice = { name: 'Root', namespaceUri: '' };
1180+
1181+
const result = XmlSchemaDocumentService.createXmlSchemaDocument(definition);
1182+
expect(result.validationStatus).toBe('warning');
1183+
expect(result.warnings).toHaveLength(1);
1184+
expect(result.warnings![0].message).toContain("Element 'Order'");
1185+
expect(result.warnings![0].filePath).toBe('Unrelated.xsd');
1186+
1187+
const document = result.document as XmlSchemaDocument;
1188+
expect(document).toBeDefined();
1189+
1190+
const rootElement = document.fields[0];
1191+
expect(rootElement.name).toBe('Root');
1192+
1193+
const orderElement = rootElement.fields.find(f => f.name === 'Order');
1194+
expect(orderElement).toBeDefined();
1195+
1196+
const orderFields = orderElement!.fields.map(f => f.name);
1197+
expect(orderFields).toEqual(['ObjectId', 'OrderItem', 'Quantity', 'Price']);
1198+
});
11201199
});
11211200

11221201
describe('with xs:import', () => {

packages/ui/src/services/document/xml-schema/xml-schema-document.service.ts

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export class XmlSchemaDocumentService {
9999

100100
const document = new XmlSchemaDocument(definition, collection, rootElement);
101101

102-
XmlSchemaDocumentService.populateNamedTypeFragments(document);
102+
XmlSchemaDocumentService.populateNamedTypeFragments(document, analysis.warnings);
103103
XmlSchemaDocumentService.populateElement(document, document.fields, document.rootElement!);
104104

105105
DocumentUtilService.processOverrides(
@@ -276,7 +276,7 @@ export class XmlSchemaDocumentService {
276276
const collection = document.xmlSchemaCollection;
277277
collection.getSchemaResolver().addFiles(additionalFiles);
278278
XmlSchemaDocumentUtilService.loadXmlSchemaFiles(collection, additionalFiles);
279-
XmlSchemaDocumentService.populateNamedTypeFragments(document);
279+
XmlSchemaDocumentService.populateNamedTypeFragments(document, undefined, false);
280280
XmlSchemaDocumentService.rebuildAbstractWrapperChildren(document);
281281
}
282282

@@ -504,8 +504,74 @@ export class XmlSchemaDocumentService {
504504
* Must be called before processing elements to ensure base types are available for extensions and restrictions.
505505
* @param document - The document whose schema collection will be traversed and whose namedTypeFragments will be populated
506506
*/
507-
static populateNamedTypeFragments(document: XmlSchemaDocument) {
508-
const schemas = document.xmlSchemaCollection.getUserSchemas();
507+
static populateNamedTypeFragments(
508+
document: XmlSchemaDocument,
509+
warnings?: Array<{ message: string; filePath?: string }>,
510+
filterUnreachable: boolean = true,
511+
) {
512+
let schemas = document.xmlSchemaCollection.getUserSchemas();
513+
514+
if (filterUnreachable && document.rootElement) {
515+
const parentSchema = document.rootElement.getParent();
516+
const reachableSchemas = new Set<XmlSchema>();
517+
const queue: XmlSchema[] = [parentSchema];
518+
519+
while (queue.length > 0) {
520+
const current = queue.shift()!;
521+
if (!reachableSchemas.has(current)) {
522+
reachableSchemas.add(current);
523+
const externals = current.getExternals();
524+
for (const ext of externals) {
525+
const schema = ext.getSchema();
526+
if (schema) {
527+
queue.push(schema);
528+
}
529+
}
530+
}
531+
}
532+
533+
const reachableArray = schemas.filter((s) => reachableSchemas.has(s));
534+
const nonReachableArray = schemas.filter((s) => !reachableSchemas.has(s));
535+
536+
if (warnings) {
537+
const reachableElements = new Set<string>();
538+
const reachableTypes = new Set<string>();
539+
540+
for (const rs of reachableArray) {
541+
for (const elemQName of rs.getElements().keys()) {
542+
reachableElements.add(elemQName.toString());
543+
}
544+
for (const typeQName of rs.getSchemaTypes().keys()) {
545+
reachableTypes.add(typeQName.toString());
546+
}
547+
}
548+
549+
for (const nrs of nonReachableArray) {
550+
const conflicts = [];
551+
for (const elemQName of nrs.getElements().keys()) {
552+
if (reachableElements.has(elemQName.toString())) {
553+
conflicts.push(`Element '${elemQName.getLocalPart()}'`);
554+
}
555+
}
556+
for (const typeQName of nrs.getSchemaTypes().keys()) {
557+
if (reachableTypes.has(typeQName.toString())) {
558+
conflicts.push(`Type '${typeQName.getLocalPart()}'`);
559+
}
560+
}
561+
562+
if (conflicts.length > 0) {
563+
const filePath = nrs.getSourceURI() ?? undefined;
564+
warnings.push({
565+
message: `Unrelated schema shadows reachable schema definitions. ${conflicts.join(', ')} will be ignored.`,
566+
filePath,
567+
});
568+
}
569+
}
570+
}
571+
572+
schemas = reachableArray;
573+
}
574+
509575
for (const schema of schemas) {
510576
XmlSchemaDocumentService.populateNamedTypeFragmentsForSchema(document, schema);
511577
}

0 commit comments

Comments
 (0)