OWL2DTDL/DotNetRdfExtensions.cs (451 lines of code) (raw):

using OWL2DTDL.VocabularyHelper; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using VDS.RDF; using VDS.RDF.Ontology; using VDS.RDF.Parsing; namespace OWL2DTDL { /// <summary> /// Various extensions to DotNetRdf, particularly relating to the <c>VDS.RDF.Ontology</c> functionality. /// </summary> public static class DotNetRdfExtensions { // Used in string handling etc private static readonly CultureInfo invariantCulture = CultureInfo.InvariantCulture; #region Shared /// <summary> /// Custom comparer for OntologyResource objects, that simply /// defers to comparison of nested INodes. /// </summary> class OntologyResourceComparer : IEqualityComparer<OntologyResource> { public bool Equals(OntologyResource x, OntologyResource y) { return x.Resource.Equals(y.Resource); } public int GetHashCode(OntologyResource obj) { return obj.Resource.GetHashCode(); } } #endregion #region INode/ILiteralNode/IUriNode extensions public static bool IsLiteral(this INode node) { return node.NodeType.Equals(NodeType.Literal); } public static bool IsUri(this INode node) { return node.NodeType.Equals(NodeType.Uri); } public static IUriNode AsUriNode(this INode node) { if (!node.IsUri()) { throw new RdfException($"Node {node} is not an URI node."); } return node as IUriNode; } public static string GetLocalName(this IUriNode node) { if (node.Uri.Fragment.Length > 0) { return node.Uri.Fragment.Trim('#'); } return Path.GetFileName(node.Uri.AbsolutePath); } public static IEnumerable<string> DtdlTypes(this IUriNode node) { IGraph g = node.Graph; IUriNode dtdlType = g.CreateUriNode(new Uri("https://w3id.org/rec/metadata/dtdlType")); return g.GetTriplesWithSubjectPredicate(node, dtdlType).Select(trip => trip.Object).LiteralNodes().Select(litNode => litNode.Value); } // TODO This should probably be fixed to handle URN namespaces properly.. public static Uri GetNamespace(this IUriNode node) { if (node.Uri.Fragment.Length > 0) { return new Uri(node.Uri.GetLeftPart(UriPartial.Path) + "#"); } string nodeUriPath = node.Uri.GetLeftPart(UriPartial.Path); if (nodeUriPath.Count(x => x == '/') >= 3) { string nodeUriBase = nodeUriPath.Substring(0, nodeUriPath.LastIndexOf("/", StringComparison.Ordinal) + 1); return new Uri(nodeUriBase); } throw new UriFormatException($"The Uri {node.Uri} doesn't contain a namespace/local name separator."); } public static bool IsInteger(this ILiteralNode node) { Uri dataTypeUri = node.DataType; if (dataTypeUri == null) { return false; } string datatype = dataTypeUri.AbsoluteUri; return datatype.StartsWith(XmlSpecsHelper.NamespaceXmlSchema, StringComparison.Ordinal) && (datatype.EndsWith("Integer", StringComparison.Ordinal) || datatype.EndsWith("Int", StringComparison.Ordinal)); } #endregion #region OntologyResource extensions public static bool HasOwlVersionInfo(this OntologyResource resource) { INode owlVersionInfo = resource.Graph.CreateUriNode(VocabularyHelper.OWL.versionInfo); IEnumerable<INode> owlVersionInfos = resource.GetNodesViaPredicate(owlVersionInfo); if (owlVersionInfos.LiteralNodes().Any()) { return true; } return false; } public static string GetOwlVersionInfo(this OntologyResource resource) { if (!resource.HasOwlVersionInfo()) { throw new RdfException($"Resource {resource} does not have an owl:versionInfo annotation"); } INode owlVersionInfo = resource.Graph.CreateUriNode(VocabularyHelper.OWL.versionInfo); return resource.GetNodesViaPredicate(owlVersionInfo).LiteralNodes().First().Value; } public static bool IsNamed(this OntologyResource ontResource) { return ontResource.Resource.IsUri(); } public static bool IsBuiltIn(this OntologyResource ontologyResource) { if (!ontologyResource.IsNamed()) { return false; } HashSet<string> builtIns = new HashSet<string>() { "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "http://www.w3.org/2000/01/rdf-schema#", "http://www.w3.org/2002/07/owl#", "http://www.w3.org/2001/XMLSchema#" }; return builtIns.Any(builtin => ontologyResource.GetUri().AbsoluteUri.Contains(builtin)); } public static IEnumerable<INode> GetNodesViaPredicate(this OntologyResource resource, INode predicate) { return resource.Graph.GetTriplesWithSubjectPredicate(resource.Resource, predicate).Select(triple => triple.Object); } public static IUriNode GetUriNode(this OntologyResource ontResource) { if (!ontResource.IsNamed()) { throw new RdfException($"Ontology resource {ontResource} does not have an IRI."); } return ontResource.Resource.AsUriNode(); } public static Uri GetUri(this OntologyResource ontResource) { return ontResource.GetUriNode().Uri; } public static Uri GetNamespace(this OntologyResource ontResource) { return ontResource.GetUriNode().GetNamespace(); } public static string GetLocalName(this OntologyResource ontResource) { return ontResource.GetUriNode().GetLocalName(); } public static bool IsDeprecated(this OntologyResource resource) { IUriNode deprecated = resource.Graph.CreateUriNode(OWL.deprecated); return resource.GetNodesViaPredicate(deprecated).LiteralNodes().Any(node => node.Value == "true"); } #endregion #region OntologyClass extensions public static int Depth(this OntologyClass oClass) { int largestParentDepth = 0; foreach (OntologyClass superClass in oClass.DirectSuperClasses) { int superClassDepth = superClass.Depth(); if (superClassDepth > largestParentDepth) { largestParentDepth = superClassDepth; } } return largestParentDepth + 1; } public static List<string> LongestParentPathToOwlThing(this OntologyClass oClass) { IUriNode rdfsSubClassOf = oClass.Graph.CreateUriNode(RDFS.subClassOf); IEnumerable<OntologyClass> directSuperClasses = oClass.DirectSuperClasses.Where( parentClass => parentClass.IsNamed() && !parentClass.IsDeprecated() && !Program.PropertyAssertionIsDeprecated(oClass.GetUriNode(), rdfsSubClassOf, parentClass.GetUriNode()) ); // If we have no superclass or one of our superclasses is OWL:Thing, then we have reached the top level; return if (directSuperClasses.Count() < 1 || directSuperClasses.Any(superClass => superClass.IsOwlThing())) { return new List<string>(); } else { // Assume the first parent has the longest path; if not, it will be replaced in subsequent foreach OntologyClass longestParent = directSuperClasses.First(); List<string> longestParentPath = longestParent.LongestParentPathToOwlThing(); // Iterate through the other parents to see if any is longer foreach (OntologyClass possibleSuperClass in directSuperClasses.Skip(1)) { List<string> possibleSuperClassParents = possibleSuperClass.LongestParentPathToOwlThing(); if (possibleSuperClassParents.Count() > longestParentPath.Count()) { longestParent = possibleSuperClass; longestParentPath = possibleSuperClassParents; } } // At this point shortestParentPath + shortestParent should together contain the shortest path to the root; return them longestParentPath.Add(longestParent.GetLocalName()); return longestParentPath; } } public static List<string> ShortestParentPathToOwlThing(this OntologyClass oClass) { IUriNode rdfsSubClassOf = oClass.Graph.CreateUriNode(RDFS.subClassOf); IEnumerable<OntologyClass> directSuperClasses = oClass.DirectSuperClasses.Where( parentClass => parentClass.IsNamed() && !parentClass.IsDeprecated() && !Program.PropertyAssertionIsDeprecated(oClass.GetUriNode(), rdfsSubClassOf, parentClass.GetUriNode()) ); // If we have no superclass or one of our superclasses is OWL:Thing, then we have reached the top level; return if (directSuperClasses.Count() < 1 || directSuperClasses.Any(superClass => superClass.IsOwlThing())) { return new List<string>(); } else { // Assume the first parent has the shortest path; if not, it will be replaced in subsequent foreach OntologyClass shortestParent = directSuperClasses.First(); List<string> shortestParentPath = shortestParent.ShortestParentPathToOwlThing(); // Iterate through the other parents to see if any is shorter foreach (OntologyClass possibleSuperClass in directSuperClasses.Skip(1)) { List<string> possibleSuperClassParents = possibleSuperClass.ShortestParentPathToOwlThing(); if (possibleSuperClassParents.Count() < shortestParentPath.Count()) { shortestParent = possibleSuperClass; shortestParentPath = possibleSuperClassParents; } } // At this point shortestParentPath + shortestParent should together contain the shortest path to the root; return them shortestParentPath.Add(shortestParent.GetLocalName()); return shortestParentPath; } } public static bool IsRestriction(this OntologyClass oClass) { return oClass.Types.UriNodes().Any(classType => classType.Uri.ToString().Equals(VocabularyHelper.OWL.Restriction.ToString())); } public static bool IsDatatype(this OntologyClass oClass) { return oClass.IsXsdDatatype() || oClass.Types.UriNodes().Any(classType => classType.Uri.AbsoluteUri.Equals(VocabularyHelper.RDFS.Datatype.AbsoluteUri)); } public static bool IsEnumerationDatatype(this OntologyClass oClass) { INode oneOf = oClass.Graph.CreateUriNode(VocabularyHelper.OWL.oneOf); if (oClass.IsDatatype()) { if (oClass.EquivalentClasses.Count() == 1) { return oClass.EquivalentClasses.Single().GetNodesViaPredicate(oneOf).Count() == 1; } else { return oClass.GetNodesViaPredicate(oneOf).Count() == 1; } } return false; } public static bool IsSimpleXsdWrapper(this OntologyClass oClass) { if (oClass.IsDatatype() && oClass.EquivalentClasses.Count() == 1) { return oClass.EquivalentClasses.Single().IsXsdDatatype(); } return false; } public static bool IsQudtUnit(this OntologyClass oClass) { return oClass.Types.UriNodes().Any(t => t.Uri.AbsoluteUri.Equals(VocabularyHelper.QUDT.Unit.AbsoluteUri)); } public static IEnumerable<INode> AsEnumeration(this OntologyClass oClass) { INode oneOf = oClass.Graph.CreateUriNode(VocabularyHelper.OWL.oneOf); INode list = oClass.EquivalentClasses.Append(oClass).SelectMany(equiv => equiv.GetNodesViaPredicate(oneOf)).First(); return oClass.Graph.GetListItems(list); } public static bool IsOwlThing(this OntologyClass oClass) { return oClass.IsNamed() && oClass.GetUri().AbsoluteUri.Equals(OWL.Thing.AbsoluteUri); } public static bool IsXsdDatatype(this OntologyClass oClass) { if (oClass.IsNamed()) { return oClass.GetUri().AbsoluteUri.StartsWith(XmlSpecsHelper.NamespaceXmlSchema, StringComparison.Ordinal); } return false; } public static IEnumerable<Relationship> GetRelationships(this OntologyClass cls) { List<Relationship> relationships = new List<Relationship>(); // Start w/ rdfs:domain declarations. At this time we only consider no-range (i.e., // range is owl:Thing) or named singleton ranges IEnumerable<OntologyProperty> rdfsDomainProperties = cls.IsDomainOf.Where( property => property.Ranges.Count() == 0 || (property.Ranges.Count() == 1 && (property.Ranges.First().IsNamed() || property.Ranges.First().IsDatatype()))); foreach (OntologyProperty property in rdfsDomainProperties) { Relationship newRelationship; if (property.Ranges.Count() == 0) { OntologyGraph oGraph = cls.Graph as OntologyGraph; OntologyClass target; if (property.IsObjectProperty()) { target = oGraph.CreateOntologyClass(VocabularyHelper.OWL.Thing); } else { target = oGraph.CreateOntologyClass(VocabularyHelper.RDFS.Literal); } newRelationship = new Relationship(property, target); } else { OntologyClass range = property.Ranges.First(); newRelationship = new Relationship(property, range); } if (property.IsFunctional()) { newRelationship.ExactCount = 1; } relationships.Add(newRelationship); } // Continue w/ OWL restrictions on the class IEnumerable<OntologyRestriction> ontologyRestrictions = cls.DirectSuperClasses .Where(superClass => superClass.IsRestriction()) .Select(superClass => new OntologyRestriction(superClass)); foreach (OntologyRestriction ontologyRestriction in ontologyRestrictions) { OntologyProperty restrictionProperty = ontologyRestriction.OnProperty; OntologyClass restrictionClass = ontologyRestriction.OnClass; if (restrictionProperty.IsNamed() && (restrictionClass.IsNamed() || restrictionClass.IsDatatype())) { Relationship newRelationship = new Relationship(restrictionProperty, restrictionClass); int min = ontologyRestriction.MinimumCardinality; int exactly = ontologyRestriction.ExactCardinality; int max = ontologyRestriction.MaximumCardinality; if (min != 0) newRelationship.MinimumCount = min; if (exactly != 0) newRelationship.ExactCount = exactly; if (max != 0) newRelationship.MaximumCount = max; relationships.Add(newRelationship); } } // Iterate over the gathered list of Relationships and narrow down to the most specific ones, using a Dictionary for lookup and the MergeWith() method for in-place narrowing Dictionary<OntologyProperty, Relationship> relationshipsDict = new Dictionary<OntologyProperty, Relationship>(new OntologyResourceComparer()); foreach (Relationship relationship in relationships) { OntologyProperty property = relationship.Property; OntologyResource target = relationship.Target; // If we already have this property listed in the dictionary, first narrow down the relationship by combining it with the old copy if (relationshipsDict.ContainsKey(property)) { Relationship oldRelationship = relationshipsDict[property]; relationship.MergeWith(oldRelationship); } // Put relationship in the dictionary relationshipsDict[property] = relationship; } // Return the values return relationshipsDict.Values; } public static IEnumerable<string> DtdlTypes(this OntologyClass oClass) { if (oClass.IsNamed()) { return oClass.GetUriNode().DtdlTypes(); } return new List<string>(); } public static IEnumerable<OntologyClass> SuperClassesWithOwlThing(this OntologyClass cls) { IGraph graph = cls.Graph; IUriNode owlThing = graph.CreateUriNode(VocabularyHelper.OWL.Thing); OntologyClass owlThingClass = new OntologyClass(owlThing, graph); return cls.SuperClasses.Append(owlThingClass); } #endregion #region OntologyProperty extensions public static bool IsFunctional(this OntologyProperty property) { // Note the toString-based comparison; because .NET Uri class does not differentiate by Uri fragment! return property.Types.UriNodes().Any(propertyType => propertyType.Uri.ToString().Equals(VocabularyHelper.OWL.FunctionalProperty.ToString())); } public static bool IsObjectProperty(this OntologyProperty property) { return property.Types.UriNodes().Any(propertyType => propertyType.Uri.ToString().Equals(OntologyHelper.OwlObjectProperty, StringComparison.Ordinal)); } public static bool IsDataProperty(this OntologyProperty property) { return property.Types.UriNodes().Any(propertyType => propertyType.Uri.ToString().Equals(OntologyHelper.OwlDatatypeProperty, StringComparison.Ordinal)); } public static bool IsAnnotationProperty(this OntologyProperty property) { return property.Types.UriNodes().Any(propertyType => propertyType.Uri.ToString().Equals(OntologyHelper.OwlAnnotationProperty, StringComparison.Ordinal)); } #endregion #region OntologyGraph extensions public static Ontology GetOntology(this OntologyGraph graph) { IUriNode rdfType = graph.CreateUriNode(new Uri(RdfSpecsHelper.RdfType)); IUriNode owlOntology = graph.CreateUriNode(new Uri(OntologyHelper.OwlOntology)); IEnumerable<IUriNode> ontologyNodes = graph.GetTriplesWithPredicateObject(rdfType, owlOntology) .Select(triple => triple.Subject) .UriNodes(); switch (ontologyNodes.Count()) { case 0: throw new RdfException($"The graph {graph} doesn't contain any owl:Ontology declarations."); case 1: return new Ontology(ontologyNodes.Single(), graph); default: IUriNode ontologyNode = ontologyNodes.Where(node => node.Uri.AbsoluteUri.Equals(graph.BaseUri.AbsoluteUri)).DefaultIfEmpty(ontologyNodes.First()).First(); return new Ontology(ontologyNode, graph); } } public static IEnumerable<Individual> GetIndividuals(this OntologyGraph graph, OntologyClass ontologyClass) { IUriNode classNode = ontologyClass.GetUriNode(); IUriNode rdfType = graph.CreateUriNode(UriFactory.Create(RdfSpecsHelper.RdfType)); return graph.GetTriplesWithPredicateObject(rdfType, classNode) .Where(triple => triple.Subject.IsUri()) .Select(triple => new Individual(triple.Subject, graph)); } public static IEnumerable<OntologyClass> GetDatatypes(this OntologyGraph graph) { INode rdfsDatatype = graph.CreateUriNode(VocabularyHelper.RDFS.Datatype); return graph.GetClasses(rdfsDatatype); } #endregion #region Ontology extensions public static bool HasVersionUri(this Ontology ontology) { IUriNode versionIri = ontology.Graph.CreateUriNode(VocabularyHelper.OWL.versionIRI); return ontology.GetNodesViaPredicate(versionIri).UriNodes().Any(); } public static Uri GetVersionUri(this Ontology ontology) { if (!ontology.HasVersionUri()) { throw new RdfException($"Ontology {ontology} does not have an owl:versionIRI annotation"); } IUriNode versionIri = ontology.Graph.CreateUriNode(VocabularyHelper.OWL.versionIRI); return ontology.GetNodesViaPredicate(versionIri).UriNodes().First().Uri; } /// <summary> /// Gets a short name representation for an ontology, based on the last segment /// of the ontology IRI or (in the case of anonymous ontologies) the ontology hash. /// Useful for qname prefixes. /// </summary> /// <param name="ontology"></param> /// <returns></returns> public static string GetShortName(this Ontology ontology) { // Fallback way of getting a persistent short identifier in the // (unlikely?) case that we are dealing w/ an anonymous ontology if (!ontology.IsNamed()) { return ontology.GetHashCode().ToString(invariantCulture); } // This is a simple string handling thing string ontologyUriString = ontology.GetUri().AbsoluteUri; // Trim any occurences of entity separation characters if (ontologyUriString.EndsWith("/", StringComparison.Ordinal) || ontologyUriString.EndsWith("#", StringComparison.Ordinal)) { char[] trimChars = { '/', '#' }; ontologyUriString = ontologyUriString.Trim(trimChars); } // Get the last bit of the string, after the last slash ontologyUriString = ontologyUriString.Substring(ontologyUriString.LastIndexOf('/') + 1); // If the string contains dots, treat them as file ending delimiter and get rid of them // one at a time while (ontologyUriString.Contains('.', StringComparison.Ordinal)) { ontologyUriString = ontologyUriString.Substring(0, ontologyUriString.LastIndexOf('.')); } return ontologyUriString; } #endregion } }