Skip to content

Commit

Permalink
XML improvements, bugfixes
Browse files Browse the repository at this point in the history
Resolves #745: Cardinality of XML elements in sequences confusing in documentation
  • Loading branch information
sstenchlak committed Jan 7, 2025
1 parent 732d6e2 commit 7fb7e38
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class CreateNewClassInOr implements ComplexOperation {
const semanticModelRelationship = await this.store.readResource(dataPsmAssociationEnd.dataPsmInterpretation as string) as SemanticModelRelationship;
const end = semanticModelRelationship.ends[1]; // todo: find correct end
const typedPimClass = await this.store.readResource(end.concept as string) as SemanticModelClass;
pimSchema = this.store.getSchemaForResource(typedPimClass.iri as string) as string;
pimSchema = this.store.getSchemaForResource(typedPimClass.id as string) as string;


const sourceSemanticClass = this.sourceSemanticModel.find((entity) => entity.id === typedPimClass.iri) as SemanticModelClass;
Expand Down
66 changes: 34 additions & 32 deletions packages/xml/src/xml-schema/xml-schema-documentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,39 +35,41 @@ function traverseXmlSchemaComplexContainer(container: XmlSchemaComplexContainer,
if (!skipElement) {
elements.push(element);
// @ts-ignore
element.cardinalityFromParentContainer = {
min: content.cardinalityMin,
max: content.cardinalityMax,
element.effectiveCardinalityFromParentContainer = {
min: content.effectiveCardinalityMin,
max: content.effectiveCardinalityMax,
};

// Prepare path to parent entity in more human readable form
const semanticPath = [];
for (let i = 0; i < content.semanticRelationToParentElement.length; i++) {
const step = content.semanticRelationToParentElement[i];
const nextStep = content.semanticRelationToParentElement[i + 1] ?? null;

if (step.type === "class") {
semanticPath.push({
type: "class",
entity: step.class,
})
} else if (step.type === "property") {
semanticPath.push({
type: "property",
entity: step.property,
})
} else if (step.type === "generalization" && nextStep?.type === "class") {
semanticPath.push({
type: "generalization",
entity: nextStep.class,
});
i++;
} else if (step.type === "specialization" && nextStep?.type === "class") {
semanticPath.push({
type: "specialization",
entity: nextStep.class,
});
i++;
if (content.semanticRelationToParentElement) {
for (let i = 0; i < content.semanticRelationToParentElement.length; i++) {
const step = content.semanticRelationToParentElement[i];
const nextStep = content.semanticRelationToParentElement[i + 1] ?? null;

if (step.type === "class") {
semanticPath.push({
type: "class",
entity: step.class,
})
} else if (step.type === "property") {
semanticPath.push({
type: "property",
entity: step.property,
})
} else if (step.type === "generalization" && nextStep?.type === "class") {
semanticPath.push({
type: "generalization",
entity: nextStep.class,
});
i++;
} else if (step.type === "specialization" && nextStep?.type === "class") {
semanticPath.push({
type: "specialization",
entity: nextStep.class,
});
i++;
}
}
}
// @ts-ignore
Expand Down Expand Up @@ -546,10 +548,10 @@ export const DEFAULT_TEMPLATE = `
{{/each}}
</dd>
{{#cardinalityFromParentContainer}}
<dt>Kardinalita elementu v nadřazeném kontejneru</dt>
{{#effectiveCardinalityFromParentContainer}}
<dt>Efektivní kardinalita elementu vůči nadřazeném elementu</dt>
<dd>{{min}}..{{#if max}}{{max}}{{else}}*{{/if}}</dd>
{{/cardinalityFromParentContainer}}
{{/effectiveCardinalityFromParentContainer}}
{{#parentEntityInDocumentation}}
<dt>Nadřazený element</dt>
<dd><a href="{{xml-href .}}"></a></dd>
Expand Down
103 changes: 68 additions & 35 deletions packages/xml/src/xml-schema/xml-schema-model-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ import { commonXmlNamespace, commonXmlPrefix, iriElementName, langStringName, QN
import { XML_COMMON_SCHEMA_GENERATOR } from "../xml-common-schema/index";
import { structureModelAddXmlProperties } from "../xml-structure-model/add-xml-properties";

function multiplyMinCardinality(a: number, b: number): number {
return a * b;
}

function multiplyMaxCardinality(a: number | null, b: number | null): number | null {
if (a === null || b === null) {
return null;
}
return a * b;
}


/**
* Converts a {@link StructureModel} to an {@link XmlSchema}.
*/
Expand Down Expand Up @@ -58,7 +70,9 @@ export async function structureModelToXmlSchema(
*/
const iriProperty: XmlSchemaComplexContentElement = {
cardinalityMin: 0,
effectiveCardinalityMin: 0,
cardinalityMax: 1,
effectiveCardinalityMax: 1,
semanticRelationToParentElement: null,
element: {
entityType: "element",
Expand Down Expand Up @@ -124,9 +138,14 @@ class XmlSchemaAdapter {
this.groups = {};
this.types = {};
this.wasCommonXmlImportUsed = false;
const elements = (await Promise.all(roots
.map(this.rootToElement, this)))
.map(this.extractTypeFromRoot, this);

const elements: XmlSchemaElement[] = [];
for (const root of roots) {
let rootElement = await this.rootToElement(root);
rootElement = this.extractTypesIfConfigured(rootElement);
elements.push(rootElement);
}

if (this.wasCommonXmlImportUsed) {
this.imports[commonXmlNamespace] = {
namespace: commonXmlNamespace,
Expand All @@ -148,29 +167,27 @@ class XmlSchemaAdapter {
}

/**
* If allowed, registers the type of the root element and uses it by name.
* If allowed, registers the type of the root element and other elements and uses it by name.
*/
extractTypeFromRoot(
element: XmlSchemaElement
extractTypesIfConfigured(
rootElement: XmlSchemaElement
): XmlSchemaElement {
if (this.options.rootClass.extractType) {
const extractType = (element: XmlSchemaElement) => {
const typeName = element.name[1];
const type = element.type;
type.name = [null, typeName];
this.types[typeName] = type;

return {
entityType: "element",
name: element.name,
type: {
entityType: "type",
name: [this.model.namespacePrefix, typeName],
annotation: null
},
annotation: element.annotation,
} satisfies XmlSchemaElement;
element.type = {
entityType: "type",
name: [this.model.namespacePrefix, typeName],
annotation: null
};
}
if (this.options.rootClass.extractType) {
extractType(rootElement);
}
return element;
return rootElement;
}

findArtefactForImport(
Expand Down Expand Up @@ -346,28 +363,35 @@ class XmlSchemaAdapter {
} as XmlSchemaElement;
}

/**
* Returns a single XmlSchemaElement from a root structure model.
* Root can be either a single class or a set of classes in OR relation.
* This does not handle the type extraction, this only returns XmlSchemaElement.
*/
async rootToElement(root: StructureModelSchemaRoot): Promise<XmlSchemaElement> {
const classes = root.classes;

if (classes.length === 1 && !root.isInOr) {
// Single class - return the element
return await this.classToElement(classes[0]);
}

const [el, _] = await this.oRToSingleType(classes, true, undefined, undefined, root.isInOr);
const [el] = await this.oRToSingleType(classes, true, undefined, undefined, root.isInOr);
const complexType = {
entityType: "type",
name: [null, root.orTechnicalLabel],
//name: [null, root.orTechnicalLabel],
name: null,
complexDefinition: el,
annotation: null,
} as XmlSchemaComplexType;
this.types[root.orTechnicalLabel] = complexType;
//!this.types[root.orTechnicalLabel] = complexType;

// Return element that references the type even if the type is not extracted
// todo this is weird.
return {
entityType: "element",
name: [null, root.orTechnicalLabel],
type: {
name: [this.model.namespacePrefix, root.orTechnicalLabel],
annotation: null
},
type: complexType,
annotation: null,
} as XmlSchemaElement;
}
Expand Down Expand Up @@ -466,6 +490,8 @@ class XmlSchemaAdapter {
const elementContent: XmlSchemaComplexContentElement = {
cardinalityMin: propertyData.cardinalityMin ?? 0,
cardinalityMax: propertyData.cardinalityMax,
effectiveCardinalityMin: propertyData.cardinalityMin ?? 0,
effectiveCardinalityMax: propertyData.cardinalityMax,
semanticRelationToParentElement: propertyData.semanticPath ?? [], // It is a relation from complex content to this relation
element: await this.propertyToElement(propertyData),
};
Expand All @@ -479,16 +505,23 @@ class XmlSchemaAdapter {
item: type.complexDefinition,
};

// // Propagate semantic relation to parent element by finding all elements
// const lookupContents = [...(item.item as XmlSchemaComplexContainer)?.contents];
// for (const content of lookupContents) {
// if (xmlSchemaComplexContentIsElement(content)) {
// content.semanticRelationToParentElement = [StructureModelProperty, ...content.semanticRelationToParentElement];
// } else if (xmlSchemaComplexContentIsItem(content)) {
// const lookup = [...(content.item as XmlSchemaComplexContainer)?.contents];
// lookupContents.push(...lookup);
// }
// }
// Propagate effective cardinality by finding all elements
const lookupContents = [...(item.item as XmlSchemaComplexContainer)?.contents];
for (const content of lookupContents) {
if (xmlSchemaComplexContentIsElement(content)) {
content.effectiveCardinalityMin = multiplyMinCardinality(
content.effectiveCardinalityMin,
item.cardinalityMin
);
content.effectiveCardinalityMax = multiplyMaxCardinality(
content.effectiveCardinalityMax,
item.cardinalityMax
);
} else if (xmlSchemaComplexContentIsItem(content)) {
const lookup = [...(content.item as XmlSchemaComplexContainer)?.contents];
lookupContents.push(...lookup);
}
}

// This will take the constructed item and changes the container type
if (propertyData.propertyAsContainer) {
Expand Down
31 changes: 25 additions & 6 deletions packages/xml/src/xml-schema/xml-schema-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,28 @@ export class XmlSchema {
imports: XmlSchemaImportDeclaration[];

/**
* The array of defined types.
* Location of XML file containing shared things for XML.
*/
commonXmlSchemaLocation: string;

/**
* The array of defined types in the root of the schema.
* Such as xs:complexType and xs:simpleType.
*/
types: XmlSchemaType[];

/**
* The array of defined groups.
* Such as xs:group.
* todo: There should be no reason to use groups.
*/
groups: XmlSchemaGroupDefinition[];

/**
* The array of root elements.
* Such as xs:element.
*/
elements: XmlSchemaElement[];

/**
* Location of XML file containing shared things for XML.
*/
commonXmlSchemaLocation: string;
}

/**
Expand Down Expand Up @@ -352,6 +356,21 @@ export class XmlSchemaComplexContentElement extends XmlSchemaComplexContent {
* Defines how the parent complex content is related to this element on a semantic level.
*/
semanticRelationToParentElement: SemanticPathStep[] | null = null;

/**
* The effective cardinality of the element, taking into account the parent container.
*
* For documentation purposes only.
*/
effectiveCardinalityMin: number;

/**
* The effective maximum cardinality of the element, taking into account the parent
* container.
*
* For documentation purposes only.
*/
effectiveCardinalityMax: number | null;
}

/**
Expand Down

0 comments on commit 7fb7e38

Please sign in to comment.