in OWL2DTDL/Program.cs [253:628]
private static void GenerateInterfaces()
{
// Working graph
Graph dtdlModel = new Graph();
// A whole bunch of language definitions.
// TODO Extract all of these (often reused) node definitions into statics.
// RDF/OWL specs
IUriNode rdfType = dtdlModel.CreateUriNode(UriFactory.Create(RdfSpecsHelper.RdfType));
// DTDL classes
IUriNode dtdl_Interface = dtdlModel.CreateUriNode(DTDL.Interface);
IUriNode dtdl_Property = dtdlModel.CreateUriNode(DTDL.Property);
IUriNode dtdl_Relationship = dtdlModel.CreateUriNode(DTDL.Relationship);
IUriNode dtdl_Telemetry = dtdlModel.CreateUriNode(DTDL.Telemetry);
IUriNode dtdl_Component = dtdlModel.CreateUriNode(DTDL.Component);
IUriNode dtdl_Enum = dtdlModel.CreateUriNode(DTDL.Enum);
IUriNode dtdl_EnumValue = dtdlModel.CreateUriNode(DTDL.EnumValue);
IUriNode dtdl_Map = dtdlModel.CreateUriNode(DTDL.Map);
// DTDL properties
IUriNode dtdl_contents = dtdlModel.CreateUriNode(DTDL.contents);
IUriNode dtdl_name = dtdlModel.CreateUriNode(DTDL.name);
IUriNode dtdl_displayName = dtdlModel.CreateUriNode(DTDL.displayName);
IUriNode dtdl_properties = dtdlModel.CreateUriNode(DTDL.properties);
IUriNode dtdl_mapKey = dtdlModel.CreateUriNode(DTDL.mapKey);
IUriNode dtdl_mapValue = dtdlModel.CreateUriNode(DTDL.mapValue);
IUriNode dtdl_extends = dtdlModel.CreateUriNode(DTDL.extends);
IUriNode dtdl_maxMultiplicity = dtdlModel.CreateUriNode(DTDL.maxMultiplicity);
IUriNode dtdl_minMultiplicity = dtdlModel.CreateUriNode(DTDL.minMultiplicity);
IUriNode dtdl_target = dtdlModel.CreateUriNode(DTDL.target);
IUriNode dtdl_schema = dtdlModel.CreateUriNode(DTDL.schema);
IUriNode dtdl_valueSchema = dtdlModel.CreateUriNode(DTDL.valueSchema);
IUriNode dtdl_writable = dtdlModel.CreateUriNode(DTDL.writable);
IUriNode dtdl_enumValue = dtdlModel.CreateUriNode(DTDL.enumValue);
IUriNode dtdl_enumValues = dtdlModel.CreateUriNode(DTDL.enumValues);
// Used to sort JObjects for merged array output, if selected as runtime option
Dictionary<JObject, int> interfaceDepths = new Dictionary<JObject, int>();
Console.WriteLine();
Console.WriteLine("Generating DTDL Interface declarations: ");
// Start looping through named, non-deprecated, non-ignored classes
foreach (OntologyClass oClass in _ontologyGraph.OwlClasses.Where(oClass => oClass.IsNamed() && !oClass.IsDeprecated() && !IsIgnored(oClass) && !oClass.SuperClasses.Any(parent => parent.IsNamed() && IsIgnored(parent))))
{
// Create Interface
string interfaceDtmi = GetDTMI(oClass);
Console.WriteLine($"\t* {interfaceDtmi}");
IUriNode interfaceNode = dtdlModel.CreateUriNode(UriFactory.Create(interfaceDtmi));
dtdlModel.Assert(new Triple(interfaceNode, rdfType, dtdl_Interface));
// If there are rdfs:labels, use them for DTDL displayName
if (oClass.Label.Any()) {
dtdlModel.Assert(GetDtdlDisplayNameTriples(oClass, interfaceNode));
}
// If there are rdfs:comments, generate and assert DTDL description triples from them
if (oClass.Comment.Any())
{
dtdlModel.Assert(GetDtdlDescriptionTriples(oClass, interfaceNode));
}
// If the class is a top-level class, i.e., it has zero superclasses that are
// not owl:Thing, implement a generic 'name' property and externalIds alignment property
IEnumerable<OntologyClass> namedDirectSuperClasses = oClass.DirectSuperClasses.Where(superClass => superClass.IsNamed());
if (!namedDirectSuperClasses.Where(superClass => !superClass.IsOwlThing()).Any())
{
// Create name property node and name
IBlankNode namePropertyNode = dtdlModel.CreateBlankNode();
dtdlModel.Assert(new Triple(interfaceNode, dtdl_contents, namePropertyNode));
dtdlModel.Assert(new Triple(namePropertyNode, rdfType, dtdl_Property));
ILiteralNode namePropertyDtdlNameNode = dtdlModel.CreateLiteralNode("name");
dtdlModel.Assert(new Triple(namePropertyNode, dtdl_name, namePropertyDtdlNameNode));
// Name is string
IUriNode namePropertySchemaNode = dtdlModel.CreateUriNode(DTDL._string);
dtdlModel.Assert(new Triple(namePropertyNode, dtdl_schema, namePropertySchemaNode));
// Display name (of name property) is hardcoded to "name".
ILiteralNode namePropertyDisplayNameNode = dtdlModel.CreateLiteralNode("name", "en");
dtdlModel.Assert(new Triple(namePropertyNode, dtdl_displayName, namePropertyDisplayNameNode));
// Name is writeable
ILiteralNode namePropertyTrueNode = dtdlModel.CreateLiteralNode("true", new Uri(XmlSpecsHelper.XmlSchemaDataTypeBoolean));
dtdlModel.Assert(new Triple(namePropertyNode, dtdl_writable, namePropertyTrueNode));
// Create externalIds
IBlankNode externalIds = dtdlModel.CreateBlankNode();
dtdlModel.Assert(new Triple(interfaceNode, dtdl_contents, externalIds));
dtdlModel.Assert(new Triple(externalIds, rdfType, dtdl_Property));
ILiteralNode externalIdsDtdlName = dtdlModel.CreateLiteralNode("externalIds");
dtdlModel.Assert(new Triple(externalIds, dtdl_name, externalIdsDtdlName));
// External ids is map
IBlankNode schemaNode = dtdlModel.CreateBlankNode();
dtdlModel.Assert(new Triple(schemaNode, rdfType, dtdl_Map));
// Map key
IBlankNode schemaMapKey = dtdlModel.CreateBlankNode();
dtdlModel.Assert(new Triple(schemaNode, dtdl_mapKey, schemaMapKey));
ILiteralNode schemaMapKeyName = dtdlModel.CreateLiteralNode("externalIdName");
dtdlModel.Assert(new Triple(schemaMapKey, dtdl_name, schemaMapKeyName));
IUriNode schemaMapKeySchema = dtdlModel.CreateUriNode(DTDL._string);
dtdlModel.Assert(new Triple(schemaMapKey, dtdl_schema, schemaMapKeySchema));
// Map value
IBlankNode schemaMapValue = dtdlModel.CreateBlankNode();
dtdlModel.Assert(new Triple(schemaNode, dtdl_mapValue, schemaMapValue));
ILiteralNode schemaMapValueName = dtdlModel.CreateLiteralNode("externalIdValue");
dtdlModel.Assert(new Triple(schemaMapValue, dtdl_name, schemaMapValueName));
IUriNode schemaMapValueSchema = dtdlModel.CreateUriNode(DTDL._string);
dtdlModel.Assert(new Triple(schemaMapValue, dtdl_schema, schemaMapValueSchema));
dtdlModel.Assert(new Triple(externalIds, dtdl_schema, schemaNode));
// Display name of external ids is hardcoded to "External IDs".
ILiteralNode externalIdsDisplayName = dtdlModel.CreateLiteralNode("External IDs", "en");
dtdlModel.Assert(new Triple(externalIds, dtdl_displayName, externalIdsDisplayName));
// Name is writeable
ILiteralNode externalIdsTrue = dtdlModel.CreateLiteralNode("true", new Uri(XmlSpecsHelper.XmlSchemaDataTypeBoolean));
dtdlModel.Assert(new Triple(externalIds, dtdl_writable, externalIdsTrue));
// Create customTags
IBlankNode customTags = dtdlModel.CreateBlankNode();
dtdlModel.Assert(new Triple(interfaceNode, dtdl_contents, customTags));
dtdlModel.Assert(new Triple(customTags, rdfType, dtdl_Property));
ILiteralNode customTagsDtdlName = dtdlModel.CreateLiteralNode("customTags");
dtdlModel.Assert(new Triple(customTags, dtdl_name, customTagsDtdlName));
// Custom tags is map
IBlankNode customTagsSchemaNode = dtdlModel.CreateBlankNode();
dtdlModel.Assert(new Triple(customTagsSchemaNode, rdfType, dtdl_Map));
// Map key
IBlankNode customTagsSchemaMapKey = dtdlModel.CreateBlankNode();
dtdlModel.Assert(new Triple(customTagsSchemaNode, dtdl_mapKey, customTagsSchemaMapKey));
ILiteralNode customTagsSchemaMapKeyName = dtdlModel.CreateLiteralNode("tagName");
dtdlModel.Assert(new Triple(customTagsSchemaMapKey, dtdl_name, customTagsSchemaMapKeyName));
IUriNode customTagsSchemaMapKeySchema = dtdlModel.CreateUriNode(DTDL._string);
dtdlModel.Assert(new Triple(customTagsSchemaMapKey, dtdl_schema, customTagsSchemaMapKeySchema));
// Map value
IBlankNode customTagsSchemaMapValue = dtdlModel.CreateBlankNode();
dtdlModel.Assert(new Triple(customTagsSchemaNode, dtdl_mapValue, customTagsSchemaMapValue));
ILiteralNode customTagsSchemaMapValueName = dtdlModel.CreateLiteralNode("tagValue");
dtdlModel.Assert(new Triple(customTagsSchemaMapValue, dtdl_name, customTagsSchemaMapValueName));
IUriNode customTagsSchemaMapValueSchema = dtdlModel.CreateUriNode(DTDL._string);
dtdlModel.Assert(new Triple(customTagsSchemaMapValue, dtdl_schema, customTagsSchemaMapValueSchema));
dtdlModel.Assert(new Triple(customTags, dtdl_schema, customTagsSchemaNode));
// Display name of custom tags is hardcoded to "Custom Tags".
ILiteralNode customTagsDisplayName = dtdlModel.CreateLiteralNode("Custom Tags", "en");
dtdlModel.Assert(new Triple(customTags, dtdl_displayName, customTagsDisplayName));
// Name is writeable
ILiteralNode customTagsTrue = dtdlModel.CreateLiteralNode("true", new Uri(XmlSpecsHelper.XmlSchemaDataTypeBoolean));
dtdlModel.Assert(new Triple(customTags, dtdl_writable, customTagsTrue));
}
// If the class has direct superclasses, implement DTDL extends (for at most two, see limitation in DTDL spec)
IEnumerable<OntologyClass> namedSuperClasses = oClass.DirectSuperClasses.Where(superClass => superClass.IsNamed() && !superClass.IsOwlThing() && !superClass.IsDeprecated());
if (namedSuperClasses.Any())
{
foreach (OntologyClass superClass in namedSuperClasses.Take(2))
{
// Only include non-deprecated subclass relations
IUriNode rdfsSubClassOf = _ontologyGraph.CreateUriNode(RDFS.subClassOf);
if (PropertyAssertionIsDeprecated(oClass.GetUriNode(), rdfsSubClassOf, superClass.GetUriNode()))
{
continue;
}
string superInterfaceDTMI = GetDTMI(superClass);
IUriNode superInterfaceNode = dtdlModel.CreateUriNode(UriFactory.Create(superInterfaceDTMI));
dtdlModel.Assert(new Triple(interfaceNode, dtdl_extends, superInterfaceNode));
}
}
// For any outgoing object properties from the class to other classes, create corresponding DTDL Relationships
foreach (Relationship relationship in oClass.GetRelationships().Where(relationship => relationship.Property.IsObjectProperty() &&
!relationship.Property.IsDeprecated() &&
!relationship.Target.IsDeprecated() &&
!IsIgnored(relationship.Target) &&
!relationship.Target.SuperClasses.Any(parent => parent.IsNamed() && IsIgnored(parent)) &&
!IsIgnored(relationship.Property)))
{
OntologyProperty oProperty = relationship.Property;
// Only include relationships with valid names
string relationshipName = string.Concat(oProperty.GetLocalName().Take(64));
if (!IsCompliantDtdlName(relationshipName))
{
Console.Error.WriteLine($"Unable to generate Relationship '{relationshipName}' on Interface '{interfaceDtmi}'; underlying property name does not adhere to DTDL regex.");
continue;
}
// If we have seen this relationship linked from a subclass, skip it; DTDL does not allow subinterfaces
// to specialise properties/relationships
if (PropertyIsDefinedOnChildClass(oClass, oProperty))
{
continue;
}
// Define the Relationship and its name
IBlankNode relationshipNode = dtdlModel.CreateBlankNode();
dtdlModel.Assert(new Triple(interfaceNode, dtdl_contents, relationshipNode));
ILiteralNode relationshipNameNode = dtdlModel.CreateLiteralNode(relationshipName);
dtdlModel.Assert(new Triple(relationshipNode, dtdl_name, relationshipNameNode));
// If there are rdfs:labels, use them for DTDL displayName
if (oProperty.Label.Any())
{
dtdlModel.Assert(GetDtdlDisplayNameTriples(oProperty, relationshipNode));
}
// If there are rdfs:comments, generate and assert DTDL description triples from them
if (oProperty.Comment.Any())
{
dtdlModel.Assert(GetDtdlDescriptionTriples(oProperty, relationshipNode));
}
// If this relationship is annotated with the dtdlType annotation,
// and that type is a component, then assert the relationship is a component, set its schema, and go to next relationship.
if (!relationship.Target.IsOwlThing() && relationship.Target.IsNamed() && relationship.Target.DtdlTypes().Contains("component"))
{
dtdlModel.Assert(new Triple(relationshipNode, rdfType, dtdl_Component));
string schemaDtmi = GetDTMI(relationship.Target);
IUriNode schemaInterfaceNode = dtdlModel.CreateUriNode(UriFactory.Create(schemaDtmi));
dtdlModel.Assert(new Triple(relationshipNode, dtdl_schema, schemaInterfaceNode));
continue;
}
// Assert target (if defined)
if (!relationship.Target.IsOwlThing())
{
string targetDtmi = GetDTMI(relationship.Target);
IUriNode targetInterfaceNode = dtdlModel.CreateUriNode(UriFactory.Create(targetDtmi));
dtdlModel.Assert(new Triple(relationshipNode, dtdl_target, targetInterfaceNode));
}
// Assert that this is indeed a Relationship
dtdlModel.Assert(new Triple(relationshipNode, rdfType, dtdl_Relationship));
// Assert min multiplicity. Hardcoded: per DTDL v2 spec: "For this release, minMultiplicity must always be 0"
if (relationship.MinimumCount.HasValue)
{
ILiteralNode minMultiplicityNode = dtdlModel.CreateLiteralNode("0", UriFactory.Create(XmlSpecsHelper.XmlSchemaDataTypeInteger));
dtdlModel.Assert(new Triple(relationshipNode, dtdl_minMultiplicity, minMultiplicityNode));
}
// Assert max multiplicity
if (relationship.MaximumCount.HasValue)
{
ILiteralNode maxMultiplicityNode = dtdlModel.CreateLiteralNode(relationship.MaximumCount.Value.ToString(), UriFactory.Create(XmlSpecsHelper.XmlSchemaDataTypeInteger));
dtdlModel.Assert(new Triple(relationshipNode, dtdl_maxMultiplicity, maxMultiplicityNode));
}
// Extract annotations on object properties -- these become DTDL Relationship Properties
// We only support annotations w/ singleton ranges (though those singletons may be enumerations)
IEnumerable<OntologyProperty> annotationsOnRelationship = _ontologyGraph.OwlAnnotationProperties
.Where(annotationProp => annotationProp.Ranges.Count() == 1)
.Where(annotationProp => annotationProp.Domains.Select(annotationDomain => annotationDomain.Resource).Contains(oProperty.Resource));
foreach (OntologyProperty annotationProperty in annotationsOnRelationship) {
// Define nested Property and its name
IBlankNode nestedPropertyNode = dtdlModel.CreateBlankNode();
dtdlModel.Assert(new Triple(nestedPropertyNode, rdfType, dtdl_Property));
dtdlModel.Assert(new Triple(relationshipNode, dtdl_properties, nestedPropertyNode));
string nestedPropertyName = string.Concat(annotationProperty.GetLocalName().Take(64));
ILiteralNode nestedPropertyNameNode = dtdlModel.CreateLiteralNode(nestedPropertyName);
dtdlModel.Assert(new Triple(nestedPropertyNode, dtdl_name, nestedPropertyNameNode));
// Assert that the nested property is writable
ILiteralNode trueNode = dtdlModel.CreateLiteralNode("true", new Uri(XmlSpecsHelper.XmlSchemaDataTypeBoolean));
dtdlModel.Assert(new Triple(nestedPropertyNode, dtdl_writable, trueNode));
// If there are rdfs:labels, use them for DTDL displayName
if (annotationProperty.Label.Any())
{
dtdlModel.Assert(GetDtdlDisplayNameTriples(annotationProperty, nestedPropertyNode));
}
// If there are rdfs:comments, generate and assert DTDL description triples from them
if (oProperty.Comment.Any())
{
dtdlModel.Assert(GetDtdlDescriptionTriples(annotationProperty, nestedPropertyNode));
}
// Set range
OntologyClass annotationPropertyRange = annotationProperty.Ranges.First();
HashSet<Triple> schemaTriples = GetDtdlTriplesForRange(annotationPropertyRange, nestedPropertyNode);
dtdlModel.Assert(schemaTriples);
}
}
// For any outgoing data properties from the class to datatypes, create corresponding DTDL Properties
foreach (Relationship relationship in oClass.GetRelationships().Where(relationship => relationship.Property.IsDataProperty() && !relationship.Property.IsDeprecated() && !IsIgnored(relationship.Property)))
{
OntologyProperty oProperty = relationship.Property;
// Only include properties with valid names
string propertyName = string.Concat(oProperty.GetLocalName().Take(64));
if (!IsCompliantDtdlName(propertyName))
{
Console.Error.WriteLine($"Unable to generate Property '{propertyName}' on Interface '{interfaceDtmi}'; underlying property name does not adhere to DTDL regex.");
continue;
}
// If we have seen this relationship linked from a subclass, skip it; DTDL does not allow subinterfaces
// to specialise properties/relationships
if (PropertyIsDefinedOnChildClass(oClass, oProperty))
{
continue;
}
// Create Property node and name
IBlankNode propertyNode = dtdlModel.CreateBlankNode();
dtdlModel.Assert(new Triple(interfaceNode, dtdl_contents, propertyNode));
dtdlModel.Assert(new Triple(propertyNode, rdfType, dtdl_Property));
ILiteralNode propertyNameNode = dtdlModel.CreateLiteralNode(propertyName);
dtdlModel.Assert(new Triple(propertyNode, dtdl_name, propertyNameNode));
// Assert that the property is writable
ILiteralNode trueNode = dtdlModel.CreateLiteralNode("true", new Uri(XmlSpecsHelper.XmlSchemaDataTypeBoolean));
dtdlModel.Assert(new Triple(propertyNode, dtdl_writable, trueNode));
// Extract and populate schema
HashSet<Triple> propertySchemaTriples = GetDtdlTriplesForRange(relationship.Target, propertyNode);
dtdlModel.Assert(propertySchemaTriples);
// If there are rdfs:labels, use them for DTDL displayName
if (oProperty.Label.Any())
{
dtdlModel.Assert(GetDtdlDisplayNameTriples(oProperty, propertyNode));
}
// If there are rdfs:comments, generate and assert DTDL description triples from them
if (oProperty.Comment.Any())
{
dtdlModel.Assert(GetDtdlDescriptionTriples(oProperty, propertyNode));
}
}
// Write JSON-LD to target file.
JObject modelAsJsonLd = ToJsonLd(dtdlModel);
interfaceDepths.Add(modelAsJsonLd, oClass.Depth());
if (!_mergedOutput) {
// Create model output directory based on output path and longest parent path
// I.e., we put multi-inheritance classes as far down as possible in their respective hierarchy
List<string> parentDirectories = oClass.LongestParentPathToOwlThing();
string modelPath = string.Join("/", parentDirectories);
string modelOutputPath = $"{_outputPath}/{modelPath}/";
// If the class has subclasses, place it with them
if (oClass.DirectSubClasses.Any()) { modelOutputPath += $"{oClass.GetLocalName()}/"; }
Directory.CreateDirectory(modelOutputPath);
string outputFileName = modelOutputPath + oClass.GetLocalName() + ".json";
using (StreamWriter file = File.CreateText(outputFileName))
using (JsonTextWriter writer = new JsonTextWriter(file) { Formatting = Formatting.Indented })
{
modelAsJsonLd.WriteTo(writer);
}
}
// Clear the working graph for next iteration
dtdlModel.Clear();
}
// Sort and generate merged output file
// TODO remember what I actually did here and document it
if (_mergedOutput)
{
List<KeyValuePair<JObject, int>> interfacesAndDepths = interfaceDepths.ToList();
interfacesAndDepths.Sort((pair1, pair2) => pair1.Value.CompareTo(pair2.Value));
IEnumerable<JObject> interfaces = interfacesAndDepths.Select(pair => pair.Key);
JArray interfaceArray = new JArray(interfaces);
Directory.CreateDirectory(_outputPath);
string outputFileName = _outputPath + "FullBuildingModels.json";
using (StreamWriter file = File.CreateText(outputFileName))
using (JsonTextWriter writer = new JsonTextWriter(file) { Formatting = Formatting.Indented })
{
interfaceArray.WriteTo(writer);
}
}
}