├── .editorconfig
├── .gitattributes
├── .gitignore
├── .vscode
├── launch.json
└── tasks.json
├── OWL2OAS.sln
├── OWL2OAS
├── DotNetRdfExtensions.cs
├── OASDocument.cs
├── OWL2OAS.csproj
├── OntologyRestriction.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Relationship.cs
├── VocabularyHelper.cs
└── examples
│ ├── rec-core-v3.0.rdf
│ └── rec-core-v3.0.yaml
├── README.md
└── docs
├── _config.yml
└── index.md
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{cs,vb}]
2 |
3 | # IDE0017: Simplify object initialization
4 | dotnet_style_object_initializer = false:suggestion
5 |
6 | # CA1062: Do not validate 'this' parameter in extension methods
7 | dotnet_diagnostic.CA1062.exclude_extension_method_this_parameter = true
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
262 | .DS_Store
263 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for C# debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceFolder}/OWL2OAS/bin/Debug/netcoreapp3.0/OWL2OAS.dll",
14 | "args": [],
15 | "cwd": "${workspaceFolder}/OWL2OAS",
16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
17 | "console": "internalConsole",
18 | "stopAtEntry": false
19 | },
20 | {
21 | "name": ".NET Core Attach",
22 | "type": "coreclr",
23 | "request": "attach",
24 | "processId": "${command:pickProcess}"
25 | }
26 | ]
27 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/OWL2OAS/OWL2OAS.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/OWL2OAS/OWL2OAS.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "${workspaceFolder}/OWL2OAS/OWL2OAS.csproj",
36 | "/property:GenerateFullPaths=true",
37 | "/consoleloggerparameters:NoSummary"
38 | ],
39 | "problemMatcher": "$msCompile"
40 | }
41 | ]
42 | }
--------------------------------------------------------------------------------
/OWL2OAS.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29215.179
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OWL2OAS", "OWL2OAS\OWL2OAS.csproj", "{2AE0F52D-31B3-4C9A-994D-937EC35AE4F3}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2F6511A7-CEFE-4B93-BEEF-63F58496FCDE}"
9 | ProjectSection(SolutionItems) = preProject
10 | .editorconfig = .editorconfig
11 | README.md = README.md
12 | EndProjectSection
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {2AE0F52D-31B3-4C9A-994D-937EC35AE4F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {2AE0F52D-31B3-4C9A-994D-937EC35AE4F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {2AE0F52D-31B3-4C9A-994D-937EC35AE4F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {2AE0F52D-31B3-4C9A-994D-937EC35AE4F3}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {1CFCE062-E420-43BC-A2AC-CA64FA13CCFB}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/OWL2OAS/DotNetRdfExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.IO;
5 | using System.Linq;
6 | using VDS.RDF;
7 | using VDS.RDF.Ontology;
8 | using VDS.RDF.Parsing;
9 |
10 | namespace OWL2OAS
11 | {
12 | ///
13 | /// Various extensions to DotNetRdf, particularly relating to the VDS.RDF.Ontology functionality.
14 | ///
15 | public static class DotNetRdfExtensions
16 | {
17 |
18 | // Used in string handling etc
19 | private static readonly CultureInfo invariantCulture = CultureInfo.InvariantCulture;
20 |
21 | #region Shared
22 | ///
23 | /// Custom comparer for OntologyResource objects, that simply
24 | /// defers to comparison of nested INodes.
25 | ///
26 | class OntologyResourceComparer : IEqualityComparer
27 | {
28 | public bool Equals(OntologyResource x, OntologyResource y)
29 | {
30 | return x.Resource == y.Resource;
31 | }
32 |
33 | public int GetHashCode(OntologyResource obj)
34 | {
35 | return obj.Resource.GetHashCode();
36 | }
37 | }
38 | #endregion
39 |
40 | #region INode/ILiteralNode/IUriNode extensions
41 | public static bool IsLiteral(this INode node)
42 | {
43 | return node.NodeType.Equals(NodeType.Literal);
44 | }
45 |
46 | public static bool IsUri(this INode node)
47 | {
48 | return node.NodeType.Equals(NodeType.Uri);
49 | }
50 |
51 | public static IUriNode AsUriNode(this INode node)
52 | {
53 | if (!node.IsUri())
54 | {
55 | throw new RdfException($"Node {node} is not an URI node.");
56 | }
57 | return node as IUriNode;
58 | }
59 |
60 | public static bool IsEnglish(this ILiteralNode node)
61 | {
62 | return node.Language.Equals("en", StringComparison.Ordinal) || node.Language.StartsWith("en-", StringComparison.Ordinal);
63 | }
64 |
65 | public static bool HasLanguage(this ILiteralNode node)
66 | {
67 | return !string.IsNullOrEmpty(node.Language);
68 | }
69 |
70 | public static bool IsInteger(this ILiteralNode node)
71 | {
72 | string datatype = node.DataType.ToString();
73 | return datatype.StartsWith(XmlSpecsHelper.NamespaceXmlSchema, StringComparison.Ordinal)
74 | && (datatype.EndsWith("Integer", StringComparison.Ordinal) || datatype.EndsWith("Int", StringComparison.Ordinal));
75 | }
76 |
77 |
78 | public static bool IsDataProperty(this INode propertyNode)
79 | {
80 | IGraph graph = propertyNode.Graph;
81 | IUriNode rdfType = graph.CreateUriNode(new Uri(RdfSpecsHelper.RdfType));
82 | IUriNode dataProperty = graph.CreateUriNode(new Uri(OntologyHelper.OwlDatatypeProperty));
83 | return graph.ContainsTriple(new Triple(propertyNode, rdfType, dataProperty));
84 | }
85 |
86 | public static bool IsObjectProperty(this INode propertyNode)
87 | {
88 | IGraph graph = propertyNode.Graph;
89 | IUriNode rdfType = graph.CreateUriNode(new Uri(RdfSpecsHelper.RdfType));
90 | IUriNode objectProperty = graph.CreateUriNode(new Uri(OntologyHelper.OwlObjectProperty));
91 | return graph.ContainsTriple(new Triple(propertyNode, rdfType, objectProperty));
92 | }
93 |
94 | public static bool IsAnnotationProperty(this INode propertyNode)
95 | {
96 | IGraph graph = propertyNode.Graph;
97 | IUriNode rdfType = graph.CreateUriNode(new Uri(RdfSpecsHelper.RdfType));
98 | IUriNode annotationProperty = graph.CreateUriNode(new Uri(OntologyHelper.OwlAnnotationProperty));
99 | return graph.ContainsTriple(new Triple(propertyNode, rdfType, annotationProperty));
100 | }
101 |
102 | public static bool IsOntologyProperty(this INode node)
103 | {
104 | return node.IsAnnotationProperty() || node.IsDataProperty() || node.IsObjectProperty();
105 | }
106 |
107 | public static string GetLocalName(this IUriNode node)
108 | {
109 | if (node.Uri.Fragment.Length > 0)
110 | {
111 | return node.Uri.Fragment.Trim('#');
112 | }
113 | return Path.GetFileName(node.Uri.AbsolutePath);
114 | }
115 |
116 | public static Uri GetNamespace(this IUriNode node)
117 | {
118 | if (node.Uri.Fragment.Length > 0)
119 | {
120 | return new Uri(node.Uri.GetLeftPart(UriPartial.Path) + "#");
121 | }
122 | string nodeUriPath = node.Uri.GetLeftPart(UriPartial.Path);
123 | if (nodeUriPath.Count(x => x == '/') >= 3)
124 | {
125 | string nodeUriBase = nodeUriPath.Substring(0, nodeUriPath.LastIndexOf("/", StringComparison.Ordinal) + 1);
126 | return new Uri(nodeUriBase);
127 | }
128 | throw new UriFormatException($"The Uri {node.Uri} doesn't contain a namespace/local name separator.");
129 | }
130 | #endregion
131 |
132 | #region OntologyGraph extensions
133 | public static bool HasBaseUriOntology(this OntologyGraph graph)
134 | {
135 | IUriNode baseUriNode = graph.CreateUriNode(graph.BaseUri);
136 | IUriNode rdfType = graph.CreateUriNode(new Uri(RdfSpecsHelper.RdfType));
137 | IUriNode owlOntology = graph.CreateUriNode(new Uri(OntologyHelper.OwlOntology));
138 | return graph.ContainsTriple(new Triple(baseUriNode, rdfType, owlOntology));
139 | }
140 |
141 | public static Ontology GetBaseUriOntology(this OntologyGraph graph)
142 | {
143 | IUriNode ontologyUriNode = graph.CreateUriNode(graph.BaseUri);
144 | return new Ontology(ontologyUriNode, graph);
145 | }
146 |
147 | ///
148 | /// Gets all owl:Ontology nodes declared in the graph, packaged as Ontology objects.
149 | ///
150 | ///
151 | ///
152 | public static IEnumerable GetOntologies(this OntologyGraph graph)
153 | {
154 | IUriNode rdfType = graph.CreateUriNode(new Uri(RdfSpecsHelper.RdfType));
155 | IUriNode owlOntology = graph.CreateUriNode(new Uri(OntologyHelper.OwlOntology));
156 | IEnumerable ontologyNodes = graph.GetTriplesWithPredicateObject(rdfType, owlOntology)
157 | .Select(triple => triple.Subject)
158 | .UriNodes();
159 | return ontologyNodes.Select(node => new Ontology(node, graph));
160 | }
161 |
162 | ///
163 | /// Returns the "main" owl:Ontology declared in the the graph. Will
164 | /// return the owl:Ontology whose identifier matches the RDF graph base
165 | /// URI; if no such owl:Ontology is present, will return the first declared
166 | /// owl:Ontology; if there are none, will throw an RdfException.
167 | ///
168 | ///
169 | ///
170 | public static Ontology GetOntology(this OntologyGraph graph)
171 | {
172 | if (graph.HasBaseUriOntology())
173 | {
174 | return graph.GetBaseUriOntology();
175 | }
176 | IEnumerable graphOntologies = graph.GetOntologies();
177 | if (graphOntologies.Any())
178 | {
179 | return graphOntologies.First();
180 | }
181 | throw new RdfException($"The graph {graph} doesn't contain any owl:Ontology declarations.");
182 | }
183 | #endregion
184 |
185 | #region OntologyResource extensions
186 | public static IEnumerable GetNodesViaPredicate(this OntologyResource resource, INode predicate)
187 | {
188 | return resource.Graph.GetTriplesWithSubjectPredicate(resource.Resource, predicate).Select(triple => triple.Object);
189 | }
190 |
191 | public static bool IsNamed(this OntologyResource ontResource)
192 | {
193 | return ontResource.Resource.IsUri();
194 | }
195 |
196 | public static IUriNode GetUriNode(this OntologyResource ontResource)
197 | {
198 | if (!ontResource.IsNamed())
199 | {
200 | throw new RdfException($"Ontology resource {ontResource} does not have an IRI.");
201 | }
202 | return ontResource.Resource.AsUriNode();
203 | }
204 |
205 | public static Uri GetIri(this OntologyResource ontResource)
206 | {
207 | return ontResource.GetUriNode().Uri;
208 | }
209 |
210 | public static string GetLocalName(this OntologyResource ontResource)
211 | {
212 | return ontResource.GetUriNode().GetLocalName();
213 | }
214 |
215 | public static Uri GetNamespace(this OntologyResource ontResource)
216 | {
217 | return ontResource.GetUriNode().GetNamespace();
218 | }
219 |
220 | public static IEnumerable GetNodesViaProperty(this OntologyResource resource, INode property)
221 | {
222 | return resource.Graph.GetTriplesWithSubjectPredicate(resource.Resource, property).Select(triple => triple.Object);
223 | }
224 |
225 | public static bool IsDeprecated(this OntologyResource resource)
226 | {
227 | IUriNode deprecated = resource.Graph.CreateUriNode(VocabularyHelper.OWL.deprecated);
228 | return resource.GetNodesViaProperty(deprecated).LiteralNodes().Any(node => node.Value == "true");
229 | }
230 |
231 | public static OntologyGraph OntologyGraph(this OntologyResource resource)
232 | {
233 | return resource.Graph as OntologyGraph;
234 | }
235 | #endregion
236 |
237 | #region Ontology extensions
238 | public static bool HasVersionIri(this Ontology ontology)
239 | {
240 | IUriNode versionIri = ontology.Graph.CreateUriNode(VocabularyHelper.OWL.versionIRI);
241 | return ontology.GetNodesViaProperty(versionIri).UriNodes().Any();
242 | }
243 |
244 | public static Uri GetVersionIri(this Ontology ontology)
245 | {
246 | if (!ontology.HasVersionIri())
247 | {
248 | throw new RdfException($"Ontology {ontology} does not have an owl:versionIRI annotation");
249 | }
250 |
251 | IUriNode versionIri = ontology.Graph.CreateUriNode(VocabularyHelper.OWL.versionIRI);
252 | return ontology.GetNodesViaProperty(versionIri).UriNodes().First().Uri;
253 | }
254 |
255 | ///
256 | /// Gets the version IRI of the ontology, if it is defined, or the ontology
257 | /// IRI if it is not. If neither is defined (i.e. the ontology is anonymous),
258 | /// throws an exception.
259 | ///
260 | ///
261 | ///
262 | public static Uri GetVersionOrOntologyIri(this Ontology ontology)
263 | {
264 | if (ontology.HasVersionIri())
265 | {
266 | return ontology.GetVersionIri();
267 | }
268 | return ontology.GetIri();
269 | }
270 |
271 | ///
272 | /// Gets a short name representation for an ontology, based on the last segment
273 | /// of the ontology IRI or (in the case of anonymous ontologies) the ontology hash.
274 | /// Useful for qname prefixes.
275 | ///
276 | ///
277 | ///
278 | public static string GetShortName(this Ontology ontology)
279 | {
280 | // Fallback way of getting a persistent short identifier in the
281 | // (unlikely?) case that we are dealing w/ an anonymous ontology
282 | if (!ontology.IsNamed())
283 | {
284 | return ontology.GetHashCode().ToString(invariantCulture);
285 | }
286 |
287 | // This is a simple string handling thing
288 | string ontologyUriString = ontology.GetIri().ToString();
289 |
290 | // Trim any occurences of entity separation characters
291 | if (ontologyUriString.EndsWith("/", StringComparison.Ordinal) || ontologyUriString.EndsWith("#", StringComparison.Ordinal))
292 | {
293 | char[] trimChars = { '/', '#' };
294 | ontologyUriString = ontologyUriString.Trim(trimChars);
295 | }
296 |
297 | // Get the last bit of the string
298 | ontologyUriString = ontologyUriString.Substring(ontologyUriString.LastIndexOf('/') + 1);
299 |
300 | // If the string contains dots, treat them as file ending delimiter and get rid of them
301 | // one at a time
302 | while (ontologyUriString.Contains('.', StringComparison.Ordinal))
303 | {
304 | ontologyUriString = ontologyUriString.Substring(0, ontologyUriString.LastIndexOf('.'));
305 | }
306 |
307 | return ontologyUriString;
308 | }
309 | #endregion
310 |
311 | #region OntologyClass extensions
312 | public static Uri GetUri(this OntologyResource ontResource)
313 | {
314 | return ontResource.GetUriNode().Uri;
315 | }
316 |
317 | public static bool IsOwlThing(this OntologyClass oClass)
318 | {
319 | return oClass.IsNamed() && oClass.GetUri().AbsoluteUri.Equals(VocabularyHelper.OWL.Thing.AbsoluteUri);
320 | }
321 |
322 | public static bool IsRdfsLiteral(this OntologyClass oClass)
323 | {
324 | return oClass.IsNamed() && oClass.GetUri().AbsoluteUri.Equals(VocabularyHelper.OWL.Thing.AbsoluteUri);
325 | }
326 |
327 |
328 | public static bool IsRdfsDatatype(this OntologyClass oClass)
329 | {
330 | return oClass.Types.UriNodes().Any(classType => classType.Uri.AbsoluteUri.Equals(VocabularyHelper.RDFS.Datatype.AbsoluteUri));
331 | }
332 |
333 | public static bool IsRestriction(this OntologyClass oClass)
334 | {
335 | return oClass.Types.UriNodes().Any(classType => classType.Uri.AbsoluteUri.Equals(VocabularyHelper.OWL.Restriction.AbsoluteUri));
336 | }
337 |
338 | public static bool IsDatatype(this OntologyClass oClass)
339 | {
340 | return oClass.IsXsdDatatype() || oClass.Types.UriNodes().Any(classType => classType.Uri.AbsoluteUri.Equals(VocabularyHelper.RDFS.Datatype.AbsoluteUri));
341 | }
342 |
343 | public static bool IsEnumerationDatatype(this OntologyClass oClass)
344 | {
345 | INode oneOf = oClass.Graph.CreateUriNode(VocabularyHelper.OWL.oneOf);
346 | if (oClass.IsDatatype())
347 | {
348 | if (oClass.EquivalentClasses.Count() == 1)
349 | {
350 | return oClass.EquivalentClasses.Single().GetNodesViaPredicate(oneOf).Count() == 1;
351 | }
352 | else
353 | {
354 | return oClass.GetNodesViaPredicate(oneOf).Count() == 1;
355 | }
356 | }
357 |
358 | return false;
359 | }
360 |
361 | public static bool IsSimpleXsdWrapper(this OntologyClass oClass)
362 | {
363 | if (oClass.IsDatatype() && oClass.EquivalentClasses.Count() == 1)
364 | {
365 | return oClass.EquivalentClasses.Single().IsXsdDatatype();
366 | }
367 | return false;
368 | }
369 |
370 | public static bool HasRestrictionProperty(this OntologyClass oClass)
371 | {
372 | IUriNode onProperty = oClass.Graph.CreateUriNode(VocabularyHelper.OWL.onProperty);
373 | return oClass.GetNodesViaProperty(onProperty).UriNodes().Any(node => node.IsOntologyProperty());
374 | }
375 |
376 | public static IUriNode GetRestrictionProperty(this OntologyClass oClass)
377 | {
378 | if (!oClass.HasRestrictionProperty())
379 | {
380 | throw new RdfException($"Ontology class {oClass} does not have a restriction property.");
381 | }
382 | IUriNode onProperty = oClass.Graph.CreateUriNode(VocabularyHelper.OWL.onProperty);
383 | return oClass.GetNodesViaProperty(onProperty).UriNodes().First(node => node.IsOntologyProperty());
384 | }
385 |
386 | public static bool IsXsdDatatype(this OntologyClass oClass)
387 | {
388 | if (oClass.IsNamed())
389 | {
390 | return oClass.GetIri().ToString().StartsWith(XmlSpecsHelper.NamespaceXmlSchema, StringComparison.Ordinal);
391 | }
392 | return false;
393 | }
394 |
395 | public static IEnumerable IsScopedDomainOf(this OntologyClass cls)
396 | {
397 | OntologyGraph graph = cls.Graph as OntologyGraph;
398 | IUriNode onProperty = graph.CreateUriNode(new Uri("http://www.w3.org/2002/07/owl#onProperty"));
399 | IEnumerable propertyNodes = cls.DirectSuperClasses.Where(superClass => superClass.IsRestriction())
400 | .SelectMany(restriction => restriction.GetNodesViaProperty(onProperty)).UriNodes();
401 | return propertyNodes.Select(node => graph.CreateOntologyProperty(node));
402 | }
403 |
404 | public static IEnumerable IsExhaustiveDomainOf(this OntologyClass oClass)
405 | {
406 | IEnumerable indirectScopedDomainProperties = oClass.SuperClasses.SelectMany(cls => cls.IsScopedDomainOf());
407 | IEnumerable scopedDomainProperties = oClass.IsScopedDomainOf();
408 | IEnumerable indirectDomainProperties = oClass.SuperClasses.SelectMany(cls => cls.IsDomainOf);
409 | IEnumerable directDomainProperties = oClass.IsDomainOf;
410 | // The order is intentional -- the restrictions come before rdfs:domains, and more generic ones go first
411 | IEnumerable allDomainProperties = indirectScopedDomainProperties.Union(scopedDomainProperties).Union(indirectDomainProperties).Union(directDomainProperties);
412 | return allDomainProperties.Distinct(new OntologyResourceComparer()).Select(ontResource => ontResource as OntologyProperty);
413 | }
414 |
415 | public static IEnumerable IsExhaustiveDomainOfUniques(this OntologyClass oClass)
416 | {
417 | IEnumerable allProperties = oClass.IsExhaustiveDomainOf();
418 | IEnumerable allUniqueProperties = allProperties.GroupBy(property => property.GetIri().ToString()).Select(group => group.First());
419 | return allUniqueProperties;
420 | }
421 |
422 | public static IEnumerable GetRelationships(this OntologyClass cls)
423 | {
424 | List relationships = new List();
425 |
426 | // Start w/ rdfs:domain declarations. At this time we only consider no-range (i.e.,
427 | // range is owl:Thing) or named singleton ranges
428 | IEnumerable rdfsDomainProperties = cls.IsDomainOf.Where(
429 | property =>
430 | property.Ranges.Count() == 0 ||
431 | (property.Ranges.Count() == 1 && (property.Ranges.First().IsNamed() || property.Ranges.First().IsDatatype())));
432 | foreach (OntologyProperty property in rdfsDomainProperties)
433 | {
434 | Relationship newRelationship;
435 | if (property.Ranges.Count() == 0)
436 | {
437 | OntologyGraph oGraph = cls.Graph as OntologyGraph;
438 | OntologyClass target;
439 | if (property.IsObjectProperty())
440 | {
441 | target = oGraph.CreateOntologyClass(VocabularyHelper.OWL.Thing);
442 | }
443 | else
444 | {
445 | target = oGraph.CreateOntologyClass(VocabularyHelper.RDFS.Literal);
446 | }
447 | newRelationship = new Relationship(property, target);
448 | }
449 | else
450 | {
451 | OntologyClass range = property.Ranges.First();
452 | newRelationship = new Relationship(property, range);
453 | }
454 |
455 | if (property.IsFunctional())
456 | {
457 | newRelationship.ExactCount = 1;
458 | }
459 |
460 | relationships.Add(newRelationship);
461 | }
462 |
463 | // Continue w/ OWL restrictions on the class
464 | IEnumerable ontologyRestrictions = cls.DirectSuperClasses
465 | .Where(superClass => superClass.IsRestriction())
466 | .Select(superClass => new OntologyRestriction(superClass));
467 | foreach (OntologyRestriction ontologyRestriction in ontologyRestrictions)
468 | {
469 |
470 |
471 | OntologyProperty restrictionProperty = ontologyRestriction.OnProperty;
472 | OntologyClass restrictionClass = ontologyRestriction.OnClass;
473 | if (restrictionProperty.IsNamed() && (restrictionClass.IsNamed() || restrictionClass.IsDatatype()))
474 | {
475 | Relationship newRelationship = new Relationship(restrictionProperty, restrictionClass);
476 |
477 | int min = ontologyRestriction.MinimumCardinality;
478 | int exactly = ontologyRestriction.ExactCardinality;
479 | int max = ontologyRestriction.MaximumCardinality;
480 |
481 | if (min != 0)
482 | newRelationship.MinimumCount = min;
483 | if (exactly != 0)
484 | newRelationship.ExactCount = exactly;
485 | if (max != 0)
486 | newRelationship.MaximumCount = max;
487 |
488 | relationships.Add(newRelationship);
489 | }
490 | }
491 |
492 | // 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
493 | Dictionary relationshipsDict = new Dictionary(new OntologyResourceComparer());
494 | foreach (Relationship relationship in relationships)
495 | {
496 | OntologyProperty property = relationship.Property;
497 | OntologyResource target = relationship.Target;
498 | // If we already have this property listed in the dictionary, first narrow down the relationship by combining it with the old copy
499 | if (relationshipsDict.ContainsKey(property))
500 | {
501 | Relationship oldRelationship = relationshipsDict[property];
502 | relationship.MergeWith(oldRelationship);
503 | }
504 | // Put relationship in the dictionary
505 | relationshipsDict[property] = relationship;
506 | }
507 |
508 | // Return the values
509 | return relationshipsDict.Values;
510 | }
511 |
512 | public static IEnumerable SuperClassesWithOwlThing(this OntologyClass cls)
513 | {
514 | IGraph graph = cls.Graph;
515 | IUriNode owlThing = graph.CreateUriNode(VocabularyHelper.OWL.Thing);
516 | OntologyClass owlThingClass = new OntologyClass(owlThing, graph);
517 | return cls.SuperClasses.Append(owlThingClass);
518 | }
519 | #endregion
520 |
521 | #region OntologyProperty extensions
522 | public static bool IsObjectProperty(this OntologyProperty property)
523 | {
524 | return property.Types.UriNodes().Any(propertyType => propertyType.Uri.ToString().Equals(OntologyHelper.OwlObjectProperty, StringComparison.Ordinal));
525 | }
526 |
527 | public static bool IsDataProperty(this OntologyProperty property)
528 | {
529 | return property.Types.UriNodes().Any(propertyType => propertyType.Uri.ToString().Equals(OntologyHelper.OwlDatatypeProperty, StringComparison.Ordinal));
530 | }
531 |
532 | public static bool IsAnnotationProperty(this OntologyProperty property)
533 | {
534 | return property.Types.UriNodes().Any(propertyType => propertyType.Uri.ToString().Equals(OntologyHelper.OwlAnnotationProperty, StringComparison.Ordinal));
535 | }
536 |
537 | public static bool IsFunctional(this OntologyProperty property)
538 | {
539 | // Note the toString-based comparison; because .NET Uri class does not differentiate by Uri fragment!
540 | return property.Types.UriNodes().Any(propertyType => propertyType.Uri.ToString().Equals(VocabularyHelper.OWL.FunctionalProperty.ToString()));
541 | }
542 |
543 | public static IEnumerable DataProperties(this IEnumerable properties)
544 | {
545 | return properties.Where(property => property.IsDataProperty());
546 | }
547 |
548 | public static IEnumerable ObjectProperties(this IEnumerable properties)
549 | {
550 | return properties.Where(property => property.IsObjectProperty());
551 | }
552 | #endregion
553 | }
554 | }
555 |
--------------------------------------------------------------------------------
/OWL2OAS/OASDocument.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using YamlDotNet.Core;
4 | using YamlDotNet.Serialization;
5 |
6 | namespace OWL2OAS
7 | {
8 | class OASDocument
9 | {
10 | public readonly string openapi = "3.0.2";
11 | public Info info;
12 | public Components components;
13 |
14 | public Dictionary paths;
15 | public List> servers;
16 |
17 | public class Info
18 | {
19 | [YamlMember(ScalarStyle = ScalarStyle.DoubleQuoted, Alias = "version")]
20 | public string Version { get; set; }
21 | public string title;
22 | public License license;
23 | public string description;
24 | }
25 |
26 | public class License
27 | {
28 | public string name;
29 | public string url;
30 | }
31 |
32 | public class Parameter
33 | {
34 | private string _referenceTo;
35 | [YamlMember(Alias = "$ref")]
36 | public string ReferenceTo
37 | {
38 | get
39 | {
40 | return _referenceTo;
41 | }
42 | set
43 | {
44 | _referenceTo = "#/components/parameters/" + value;
45 | }
46 | }
47 |
48 | public enum InFieldValues
49 | {
50 | path = 1,
51 | query = 2,
52 | header = 3,
53 | cookie = 4
54 | }
55 |
56 | public string name;
57 | [YamlMember(Alias = "in")]
58 | public InFieldValues InField { get; set; }
59 | public string description;
60 | public bool required;
61 | public Schema schema;
62 | public string style;
63 | }
64 |
65 | public class Components
66 | {
67 | public Dictionary parameters = new Dictionary {
68 | { "pageParam", new Parameter
69 | {
70 | name = "page",
71 | description = "If the result set is large, pagination across the results can be employed; in that case, this parameter defines the page number (zero-indexed) that is requested by the client. The number of items included in each page is defined by the 'size' parameter. Default is 0, i.e., the first results page is returned.",
72 | InField = Parameter.InFieldValues.query,
73 | required = false,
74 | schema = new PrimitiveSchema {
75 | type = "integer",
76 | format = "int32",
77 | minimum = "0",
78 | DefaultValue = "0"
79 | }
80 | }
81 | },
82 | {
83 | "sizeParam", new Parameter
84 | {
85 | name = "size",
86 | description = "The number of items to display on a returned results page (see the page parameter).",
87 | InField = Parameter.InFieldValues.query,
88 | required = false,
89 | schema = new PrimitiveSchema {
90 | type = "integer",
91 | format = "int32",
92 | minimum = "0",
93 | maximum = 100,
94 | DefaultValue = "20"
95 | }
96 | }
97 | },
98 | {
99 | "sortParam", new Parameter
100 | {
101 | name = "sort",
102 | description = "The field and direction to sort results on.",
103 | InField = Parameter.InFieldValues.query,
104 | required = false,
105 | schema = new ReferenceSchema("SortingSchema"),
106 | style = "deepObject"
107 | }
108 | }
109 | };
110 | public Dictionary schemas = new Dictionary
111 | {
112 | // Add Hydra Colletions wrapper schema
113 | {"HydraCollectionWrapper",
114 | new ComplexSchema
115 | {
116 | required = new List
117 | {
118 | "@context",
119 | "@type",
120 | "hydra:member"
121 | },
122 | properties = new Dictionary
123 | {
124 | {"@context", new ReferenceSchema("Context") },
125 | {"@type", new PrimitiveSchema { type="string", DefaultValue="hydra:Collection"} },
126 | {"hydra:totalItems", new PrimitiveSchema { type="integer" } },
127 | {"hydra:view", new ComplexSchema
128 | {
129 | properties = new Dictionary
130 | {
131 | {"@id", new PrimitiveSchema { type="string", format="uri"} },
132 | {"@type", new PrimitiveSchema { type="string", DefaultValue="hydra:PartialCollectionView"} },
133 | {"hydra:first", new PrimitiveSchema { type="string"} },
134 | {"hydra:previous", new PrimitiveSchema { type="string"} },
135 | {"hydra:next", new PrimitiveSchema { type="string"} },
136 | {"hydra:last", new PrimitiveSchema { type="string"} },
137 | }
138 | } }
139 | }
140 | }
141 | },
142 | // Add the default query operator filter schemas
143 | {"IntegerFilter", new ComplexSchema {
144 | properties = new Dictionary
145 | {
146 | {"eq", new PrimitiveSchema {type="integer"} },
147 | {"lt", new PrimitiveSchema {type="integer"} },
148 | {"lte", new PrimitiveSchema {type="integer"} },
149 | {"gt", new PrimitiveSchema {type="integer"} },
150 | {"gte", new PrimitiveSchema {type="integer"} }
151 | }
152 | }},
153 | {"NumberFilter", new ComplexSchema {
154 | properties = new Dictionary
155 | {
156 | {"eq", new PrimitiveSchema {type="number"} },
157 | {"lt", new PrimitiveSchema {type="number"} },
158 | {"lte", new PrimitiveSchema {type="number"} },
159 | {"gt", new PrimitiveSchema {type="number"} },
160 | {"gte", new PrimitiveSchema {type="number"} }
161 | }
162 | }},
163 | {"StringFilter", new ComplexSchema {
164 | properties = new Dictionary
165 | {
166 | {"eq", new PrimitiveSchema {type="string"} },
167 | {"contains", new PrimitiveSchema {type="string"} },
168 | {"regex", new PrimitiveSchema {type="string"} }
169 | }
170 | }},
171 | {"DateTimeFilter", new ComplexSchema {
172 | properties = new Dictionary
173 | {
174 | {"eq", new PrimitiveSchema {type="string", format="date-time"} },
175 | {"starting", new PrimitiveSchema {type="string", format="date-time"} },
176 | {"ending", new PrimitiveSchema {type="string", format="date-time"} },
177 | {"before", new PrimitiveSchema {type="string", format="date-time"} },
178 | {"after", new PrimitiveSchema {type="string", format="date-time"} },
179 | {"latest", new PrimitiveSchema {type="boolean" } }
180 | }
181 | }},
182 | // And the sort operators schema
183 | {"SortingSchema", new ComplexSchema {
184 | properties = new Dictionary
185 | {
186 | {"asc", new PrimitiveSchema {type="string"} },
187 | {"desc", new PrimitiveSchema {type="string"} }
188 | }
189 | }}
190 | };
191 | }
192 |
193 | public class Schema
194 | {
195 |
196 | }
197 |
198 | public class ComplexSchema: Schema
199 | {
200 | public readonly string type = "object";
201 | public List required;
202 | public Dictionary properties;
203 | public int maxProperties;
204 | public int minProperties;
205 | }
206 |
207 | public class PrimitiveSchema: Schema
208 | {
209 | public string type;
210 | public string format;
211 | public string minimum;
212 | public int maximum;
213 | [YamlMember(Alias = "default")]
214 | public string DefaultValue { get; set; }
215 | [YamlMember(Alias = "enum")]
216 | public string[] Enumeration { get; set; }
217 | }
218 |
219 | public class ReferenceSchema: Schema
220 | {
221 | public ReferenceSchema(string referenceType)
222 | {
223 | Reference = "#/components/schemas/" + referenceType.Replace(":", "_", StringComparison.Ordinal);
224 | }
225 | [YamlMember(Alias = "$ref")]
226 | public string Reference { get; set; }
227 | }
228 |
229 | public class AllOfSchema: Schema
230 | {
231 | public Schema[] allOf;
232 | }
233 |
234 | public class ArraySchema: Schema
235 | {
236 | public readonly string type = "array";
237 | public Schema items;
238 | public int maxItems;
239 | public int minItems;
240 | }
241 |
242 | public class Path
243 | {
244 | public Operation get;
245 | public Operation put;
246 | public Operation patch;
247 | public Operation post;
248 | public Operation delete;
249 | }
250 |
251 | public class Operation
252 | {
253 | public string summary;
254 | public List parameters = new List();
255 | public RequestBody requestBody;
256 | public Dictionary responses = new Dictionary();
257 | public List tags = new List();
258 | }
259 |
260 | public class RequestBody
261 | {
262 | public string description;
263 | public bool required;
264 | public Dictionary content;
265 | }
266 |
267 | public class Response
268 | {
269 | public string description;
270 | public Dictionary content;
271 | }
272 |
273 | public class Content
274 | {
275 | public Schema schema;
276 | }
277 | }
278 | }
279 |
--------------------------------------------------------------------------------
/OWL2OAS/OWL2OAS.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.0
6 |
7 |
8 |
9 | Project
10 | -c DefaultExclude -u https://w3id.org/rec/full/3.3/ -o /Users/karl/Scratch/GitHub/RealEstateCore/rec/api/REST/rec-swagger-api.yaml
11 | true
12 |
13 |
14 |
15 |
16 | all
17 | runtime; build; native; contentfiles; analyzers; buildtransitive
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/OWL2OAS/OntologyRestriction.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Globalization;
3 | using System.Linq;
4 | using VDS.RDF;
5 | using VDS.RDF.Ontology;
6 |
7 | namespace OWL2OAS
8 | {
9 | ///
10 | /// (Partial) representation of an owl:Restriction.
11 | ///
12 | public class OntologyRestriction
13 | {
14 | public OntologyProperty OnProperty
15 | {
16 | get
17 | {
18 | IUriNode onProperty = _graph.CreateUriNode(VocabularyHelper.OWL.onProperty);
19 | IEnumerable properties = _wrappedClass.GetNodesViaPredicate(onProperty).UriNodes();
20 | if (properties.Count() != 1)
21 | {
22 | throw new RdfException("A restriction must be on exactly one property.");
23 | }
24 | return _graph.CreateOntologyProperty(properties.First());
25 | }
26 | }
27 |
28 | public OntologyClass OnClass
29 | {
30 | get
31 | {
32 | IUriNode onClass = _graph.CreateUriNode(VocabularyHelper.OWL.onClass);
33 | IUriNode allValuesFrom = _graph.CreateUriNode(VocabularyHelper.OWL.allValuesFrom);
34 | IUriNode someValuesFrom = _graph.CreateUriNode(VocabularyHelper.OWL.someValuesFrom);
35 | IEnumerable classes = _wrappedClass.GetNodesViaPredicate(onClass)
36 | .Union(_wrappedClass.GetNodesViaPredicate(someValuesFrom))
37 | .Union(_wrappedClass.GetNodesViaPredicate(allValuesFrom));
38 | if (!classes.Any())
39 | {
40 | if (OnProperty.IsObjectProperty())
41 | {
42 | return _graph.CreateOntologyClass(VocabularyHelper.OWL.Thing);
43 | }
44 | else
45 | {
46 | return _graph.CreateOntologyClass(VocabularyHelper.RDFS.Literal);
47 | }
48 | }
49 | else if (classes.Count() == 1)
50 | {
51 | return _graph.CreateOntologyClass(classes.First());
52 | }
53 | throw new RdfException("A restriction must be on at most one class.");
54 | }
55 | }
56 |
57 | public int MinimumCardinality
58 | {
59 | get
60 | {
61 | IUriNode someValuesFrom = _graph.CreateUriNode(VocabularyHelper.OWL.someValuesFrom);
62 | IUriNode minCardinality = _graph.CreateUriNode(VocabularyHelper.OWL.minCardinality);
63 | IUriNode minQualifiedCardinality = _graph.CreateUriNode(VocabularyHelper.OWL.minQualifiedCardinality);
64 |
65 | IEnumerable minCardinalities = _wrappedClass.GetNodesViaPredicate(minCardinality).Union(_wrappedClass.GetNodesViaPredicate(minQualifiedCardinality));
66 | if (minCardinalities.LiteralNodes().Count() == 1 &&
67 | minCardinalities.LiteralNodes().First().IsInteger())
68 | {
69 | return int.Parse(minCardinalities.LiteralNodes().First().Value, CultureInfo.InvariantCulture);
70 | }
71 |
72 | if (_wrappedClass.GetNodesViaPredicate(someValuesFrom).Count() == 1)
73 | {
74 | return 1;
75 | }
76 | return 0;
77 | }
78 | }
79 |
80 | public int ExactCardinality
81 | {
82 | get
83 | {
84 | IUriNode cardinality = _graph.CreateUriNode(VocabularyHelper.OWL.cardinality);
85 | IUriNode qualifiedCardinality = _graph.CreateUriNode(VocabularyHelper.OWL.qualifiedCardinality);
86 |
87 | IEnumerable exactCardinalities = _wrappedClass.GetNodesViaPredicate(cardinality).Union(_wrappedClass.GetNodesViaPredicate(qualifiedCardinality));
88 | if (exactCardinalities.LiteralNodes().Count() == 1 &&
89 | exactCardinalities.LiteralNodes().First().IsInteger())
90 | {
91 | return int.Parse(exactCardinalities.LiteralNodes().First().Value, CultureInfo.InvariantCulture);
92 | }
93 | return 0;
94 | }
95 | }
96 |
97 | public int MaximumCardinality
98 | {
99 | get
100 | {
101 | IUriNode maxCardinality = _graph.CreateUriNode(VocabularyHelper.OWL.maxCardinality);
102 | IUriNode maxQualifiedCardinality = _graph.CreateUriNode(VocabularyHelper.OWL.maxQualifiedCardinality);
103 |
104 | IEnumerable maxCardinalities = _wrappedClass.GetNodesViaPredicate(maxCardinality).Union(_wrappedClass.GetNodesViaPredicate(maxQualifiedCardinality));
105 | if (maxCardinalities.LiteralNodes().Count() == 1 &&
106 | maxCardinalities.LiteralNodes().First().IsInteger())
107 | {
108 | return int.Parse(maxCardinalities.LiteralNodes().First().Value, CultureInfo.InvariantCulture);
109 | }
110 | return 0;
111 | }
112 | }
113 |
114 | public readonly OntologyGraph _graph;
115 | public readonly OntologyClass _wrappedClass;
116 |
117 | ///
118 | /// Wrapper around an ontology class that exposes some methods particular to ontology restrictions.
119 | ///
120 | ///
121 | public OntologyRestriction(OntologyClass wrappedClass)
122 | {
123 | _wrappedClass = wrappedClass;
124 | _graph = wrappedClass.Graph as OntologyGraph;
125 | }
126 |
127 |
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/OWL2OAS/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Globalization;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using CommandLine;
8 | using VDS.RDF;
9 | using VDS.RDF.Nodes;
10 | using VDS.RDF.Ontology;
11 | using VDS.RDF.Parsing;
12 | using YamlDotNet.Serialization;
13 |
14 | namespace OWL2OAS
15 | {
16 | class Program
17 | {
18 | public enum EntityInclusionPolicy
19 | {
20 | DefaultInclude,
21 | DefaultExclude
22 | }
23 |
24 | public class Options
25 | {
26 | [Option('c', "ClassInclusionPolicy", Default = EntityInclusionPolicy.DefaultInclude, HelpText = "Whether to include all classes by default (overridden by o2o:included annotation). Valid options: DefaultInclude or DefaultExclude.")]
27 | public EntityInclusionPolicy ClassInclusionPolicy { get; set; }
28 | [Option('p', "PropertyInclusionPolicy", Default = EntityInclusionPolicy.DefaultInclude, HelpText = "Whether to include all properties by default (overridden by o2o:included annotation). Valid options: DefaultInclude or DefaultExclude.")]
29 | public EntityInclusionPolicy PropertyInclusionPolicy { get; set; }
30 | [Option('n', "no-imports", Required = false, HelpText = "Sets program to not follow owl:Imports declarations.")]
31 | public bool NoImports { get; set; }
32 | [Option('s', "server", Default = "http://localhost:8080/", Required = false, HelpText = "The server URL (where presumably an API implementation is running).")]
33 | public string Server { get; set; }
34 | [Option('f', "file-path", Required = true, HelpText = "The path to the on-disk root ontology file to translate.", SetName = "fileOntology")]
35 | public string FilePath { get; set; }
36 | [Option('u', "uri-path", Required = true, HelpText = "The URI of the root ontology file to translate.", SetName = "uriOntology")]
37 | public string UriPath { get; set; }
38 | [Option('o', "outputPath", Required = true, HelpText = "The path at which to put the generated OAS file.")]
39 | public string OutputPath { get; set; }
40 | }
41 |
42 | ///
43 | /// Custom comparer for Ontology objects, based on W3C OWL2 specification for version IRIs.
44 | /// See https://www.w3.org/TR/owl2-syntax/#Ontology_IRI_and_Version_IRI
45 | ///
46 | class OntologyComparer : IEqualityComparer
47 | {
48 |
49 | public bool Equals(Ontology x, Ontology y)
50 | {
51 | return
52 | !x.HasVersionIri() && !y.HasVersionIri() && (x.GetIri() == y.GetIri()) ||
53 | x.HasVersionIri() && y.HasVersionIri() && (x.GetIri() == y.GetIri()) && (x.GetVersionIri() == y.GetVersionIri());
54 | }
55 |
56 | // Method borrowed from https://stackoverflow.com/a/263416
57 | public int GetHashCode(Ontology x)
58 | {
59 | // Generate partial hashes from identify-carrying fields, i.e., ontology IRI
60 | // and version IRI; if no version IRI exists, default to partial hash of 0.
61 | int oidHash = x.GetIri().GetHashCode();
62 | int vidHash = x.HasVersionIri() ? x.GetVersionIri().GetHashCode() : 0;
63 |
64 | //
65 | int hash = 23;
66 | hash = hash * 37 + oidHash;
67 | hash = hash * 37 + vidHash;
68 | return hash;
69 | }
70 | }
71 |
72 | ///
73 | /// The ontology being parsed.
74 | ///
75 | private static Ontology rootOntology;
76 |
77 | ///
78 | /// The joint ontology graph into which all imported ontologies are merged
79 | /// and upon which this tool subsequently operates.
80 | ///
81 | private static readonly OntologyGraph _ontologyGraph = new OntologyGraph();
82 |
83 | ///
84 | /// The generated output OAS document.
85 | ///
86 | private static OASDocument _document;
87 |
88 | ///
89 | /// Set of transitively imported child ontologies.
90 | ///
91 | private static readonly HashSet importedOntologies = new HashSet(new OntologyComparer());
92 |
93 | private static readonly Dictionary> requiredPropertiesForEachClass = new Dictionary>();
94 |
95 | private static readonly Dictionary namespacePrefixes = new Dictionary();
96 |
97 | // Used in string handling etc
98 | private static readonly CultureInfo invariantCulture = CultureInfo.InvariantCulture;
99 |
100 | // Various configuration fields
101 | private static string _server;
102 | private static bool _noImports;
103 | private static bool _localOntology;
104 | private static string _ontologyPath;
105 | private static bool _defaultIncludeClasses;
106 | private static bool _defaultIncludeProperties;
107 | private static string _outputPath;
108 |
109 | ///
110 | /// Dictionary mapping some common XSD data types to corresponding OSA data types and formats, see
111 | /// https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#dataTypeFormat
112 | ///
113 | static readonly Dictionary xsdOsaMappings = new Dictionary
114 | {
115 | {"boolean",("boolean","") },
116 | {"byte",("string","byte") },
117 | {"base64Binary",("string","byte") },
118 | {"dateTime",("string","date-time") },
119 | {"dateTimeStamp",("string","date-time") },
120 | {"double",("number","double") },
121 | {"float",("number","float") },
122 | {"int",("integer","int32") },
123 | {"integer",("integer","int32") },
124 | {"long",("integer","int64") },
125 | {"string",("string","") }
126 | };
127 |
128 | ///
129 | /// A struct representing cardinality constraints on a property.
130 | ///
131 | public struct PropertyCardinalityConstraints
132 | {
133 | public int min;
134 | public int max;
135 | public int exactly;
136 | public bool AllowsMultiple()
137 | {
138 | return !MaxOne();
139 | }
140 | public bool MaxOne()
141 | {
142 | return (exactly == 1 || max == 1);
143 | }
144 | public bool IsRequired()
145 | {
146 | return (min == 1 || exactly == 1);
147 | }
148 | }
149 |
150 | ///
151 | /// Checks whether an ontology class or property should be included in the output specification, based on a) the run time
152 | /// "ClassInclusionPolicy" and "PropertyInclusionPolicy" options, and b) any explicit annotations, if present, on the resource
153 | /// in question using the https://karlhammar.com/owl2oas/o2o.owl#included annotation property.
154 | ///
155 | ///
156 | /// True iff the resource is annotated with included=true OR the relevant entity inclusion policy is DefaultInclude AND there is no included=false annotation on the entity.
157 | private static bool IsIncluded(OntologyResource resource)
158 | {
159 | // Do not include deprecated entities
160 | if (resource.IsDeprecated())
161 | {
162 | return false;
163 | }
164 |
165 | // Check which inclusion policy applies; also, if resource is not class or property, throw exception right away
166 | bool includeByDefault;
167 | switch (resource)
168 | {
169 | case OntologyClass oClass:
170 | includeByDefault = _defaultIncludeClasses;
171 | break;
172 | case OntologyProperty oProp:
173 | includeByDefault = _defaultIncludeProperties;
174 | break;
175 | default:
176 | throw new RdfException($"Resource {resource} is neither an OntologyClass nor OntologyResource.");
177 | }
178 |
179 | // Check for existence of an o2o:included annotation; if so return based on this
180 | IUriNode includeAnnotationProperty = rootOntology.OntologyGraph().CreateUriNode(VocabularyHelper.O2O.included);
181 | IEnumerable includeAnnotationValues = resource.GetNodesViaProperty(includeAnnotationProperty).LiteralNodes();
182 | if (includeAnnotationValues.Count() == 1)
183 | {
184 | bool resourceIncluded = includeAnnotationValues.First().AsValuedNode().AsBoolean();
185 | return resourceIncluded;
186 | }
187 |
188 | // If no annotation, go by the default policy
189 | return includeByDefault;
190 | }
191 |
192 | static void Main(string[] args)
193 | {
194 | Parser.Default.ParseArguments(args)
195 | .WithParsed(o =>
196 | {
197 | _noImports = o.NoImports;
198 | _server = o.Server;
199 | _outputPath = o.OutputPath;
200 | if (o.FilePath != null)
201 | {
202 | _localOntology = true;
203 | _ontologyPath = o.FilePath;
204 | }
205 | else
206 | {
207 | _localOntology = false;
208 | _ontologyPath = o.UriPath;
209 | }
210 | _defaultIncludeClasses = (o.ClassInclusionPolicy == EntityInclusionPolicy.DefaultInclude ? true : false);
211 | _defaultIncludeProperties = (o.PropertyInclusionPolicy == EntityInclusionPolicy.DefaultInclude ? true : false);
212 | })
213 | .WithNotParsed((errs) =>
214 | {
215 | Environment.Exit(1);
216 | });
217 |
218 | // Clear cache from any prior runs
219 | UriLoader.Cache.Clear();
220 |
221 | // Load ontology graph from local or remote path
222 | if (_localOntology)
223 | {
224 | FileLoader.Load(_ontologyGraph, _ontologyPath);
225 | }
226 | else
227 | {
228 | UriLoader.Load(_ontologyGraph, new Uri(_ontologyPath));
229 | }
230 |
231 | // Get the main ontology defined in the graph.
232 | rootOntology = _ontologyGraph.GetOntology();
233 |
234 | // If configured for it, parse owl:Imports transitively
235 | if (!_noImports)
236 | {
237 | foreach (Ontology import in rootOntology.Imports)
238 | {
239 | LoadImport(import);
240 | }
241 | }
242 |
243 | // Create OAS object, create OAS info header, server block, (empty) components/schemas structure, and LoadedOntologies endpoint
244 | _document = new OASDocument
245 | {
246 | info = GenerateDocumentInfo(),
247 | servers = new List> { new Dictionary { { "url", _server } } },
248 | components = new OASDocument.Components(),
249 | paths = new Dictionary
250 | {
251 | { "/LoadedOntologies", GenerateLoadedOntologiesPath() }
252 | }
253 | };
254 |
255 | // Parse OWL classes.For each class, create a schema and a path
256 | GenerateAtomicClassSchemas();
257 | GenerateClassPaths();
258 |
259 | // Generate and add the Context schema
260 | // This is done after class schemas are generated, in case the former causes new prefixes to be added
261 | // to the context
262 | _document.components.schemas.Add("Context", GenerateContextSchema());
263 |
264 | // Dispose all open graphs
265 | _ontologyGraph.Dispose();
266 |
267 | // Dump output as YAML
268 | var stringBuilder = new StringBuilder();
269 | var serializerBuilder = new SerializerBuilder().DisableAliases();
270 | var serializer = serializerBuilder.Build();
271 | stringBuilder.AppendLine(serializer.Serialize(_document));
272 | File.WriteAllText(_outputPath, stringBuilder.ToString());
273 | }
274 |
275 | private static OASDocument.Path GenerateLoadedOntologiesPath()
276 | {
277 | OASDocument.Path loadedOntologiesPath = new OASDocument.Path
278 | {
279 | get = new OASDocument.Operation
280 | {
281 | summary = "Get the set of ontologies that were imported by the root ontology when the API was generated.",
282 | responses = new Dictionary
283 | {
284 | { "200", new OASDocument.Response
285 | {
286 | description = "A list of ontologies used to generate this API. Note that while the prefix names used here correspond with the ones given in the JSON-LD @context for the supported data types, the prefix mapping in the API " +
287 | "is based on the Ontology IRIs given in those @context blocks, which may differ from the values given here (which give priority to version IRIs).",
288 | content = new Dictionary
289 | {
290 | { "application/json", new OASDocument.Content
291 | {
292 | schema = GenerateLoadedOntologiesSchema()
293 | }
294 | }
295 | }
296 | }
297 | }
298 | }
299 | }
300 | };
301 | return loadedOntologiesPath;
302 | }
303 |
304 | private static OASDocument.Schema GenerateLoadedOntologiesSchema()
305 | {
306 | OASDocument.ComplexSchema loadedOntologiesSchema = new OASDocument.ComplexSchema
307 | {
308 | properties = new Dictionary(),
309 | required = new List()
310 | };
311 |
312 | OASDocument.PrimitiveSchema rootOntologySchema = new OASDocument.PrimitiveSchema
313 | {
314 | type = "string",
315 | format = "uri",
316 | Enumeration = new string[] { rootOntology.GetVersionOrOntologyIri().ToString() }
317 | };
318 | loadedOntologiesSchema.properties.Add("", rootOntologySchema);
319 | loadedOntologiesSchema.required.Add("");
320 |
321 | // Add each imported ontology to the loaded ontologies schema
322 | foreach (Ontology importedOntology in importedOntologies)
323 | {
324 | OASDocument.PrimitiveSchema importedOntologySchema = new OASDocument.PrimitiveSchema
325 | {
326 | type = "string",
327 | format = "uri",
328 | Enumeration = new string[] { importedOntology.GetVersionOrOntologyIri().ToString() }
329 | };
330 |
331 | // Fetch shortname as generated at import load time
332 | string ontologyShortname = namespacePrefixes[importedOntology.GetIri()];
333 | loadedOntologiesSchema.properties.Add(ontologyShortname, importedOntologySchema);
334 | loadedOntologiesSchema.required.Add(ontologyShortname);
335 | }
336 |
337 | return loadedOntologiesSchema;
338 | }
339 |
340 | private static OASDocument.Schema GenerateContextSchema()
341 | {
342 | // Set @context/@vocab based on the ontology IRI
343 | OASDocument.PrimitiveSchema vocabularySchema = new OASDocument.PrimitiveSchema
344 | {
345 | type = "string",
346 | format = "uri",
347 | DefaultValue = rootOntology.GetIri().ToString()
348 | };
349 | // Set @context/@base (default data namespace)
350 | OASDocument.PrimitiveSchema baseNamespaceSchema = new OASDocument.PrimitiveSchema
351 | {
352 | type = "string",
353 | format = "uri"
354 | };
355 | // Hardcoded Hydra
356 | OASDocument.PrimitiveSchema hydraSchema = new OASDocument.PrimitiveSchema
357 | {
358 | type = "string",
359 | format = "uri",
360 | DefaultValue = "http://www.w3.org/ns/hydra/core#"
361 | };
362 | // Hardcoded rdfs:label for @context
363 | OASDocument.PrimitiveSchema labelContextSchema = new OASDocument.PrimitiveSchema
364 | {
365 | type = "string",
366 | format = "uri",
367 | DefaultValue = VocabularyHelper.RDFS.label.ToString()
368 | };
369 | // Mash it all together into a @context block
370 | OASDocument.ComplexSchema contextSchema = new OASDocument.ComplexSchema
371 | {
372 | required = new List { "@vocab", "@base", "hydra" },
373 | properties = new Dictionary {
374 | { "@vocab", vocabularySchema },
375 | { "@base", baseNamespaceSchema },
376 | { "hydra", hydraSchema },
377 | { "label", labelContextSchema }
378 | }
379 | };
380 | // Add each prefix to the @context (sorting by shortname, e.g., dictionary value, for usability)
381 | List> prefixMappingsList = namespacePrefixes.ToList();
382 | prefixMappingsList.Sort((pair1, pair2) => string.CompareOrdinal(pair1.Value, pair2.Value));
383 | foreach (KeyValuePair prefixMapping in prefixMappingsList)
384 | {
385 | OASDocument.PrimitiveSchema importedVocabularySchema = new OASDocument.PrimitiveSchema
386 | {
387 | type = "string",
388 | format = "uri",
389 | DefaultValue = prefixMapping.Key.ToString()
390 | };
391 | contextSchema.properties.Add(prefixMapping.Value, importedVocabularySchema);
392 | contextSchema.required.Add(prefixMapping.Value);
393 | }
394 |
395 | return contextSchema;
396 | }
397 |
398 | private static OASDocument.Info GenerateDocumentInfo()
399 | {
400 | OASDocument.Info docInfo = new OASDocument.Info();
401 |
402 | OntologyGraph rootOntologyGraph = rootOntology.OntologyGraph();
403 |
404 | // Check for mandatory components (dc:title, version info, cc:license).
405 | IUriNode dcTitle = rootOntologyGraph.CreateUriNode(VocabularyHelper.DC.title);
406 | if (!rootOntology.GetNodesViaProperty(dcTitle).LiteralNodes().Any())
407 | {
408 | throw new RdfException($"Ontology <{rootOntology}> does not have an annotation.");
409 | }
410 | if (!rootOntology.VersionInfo.Any())
411 | {
412 | throw new RdfException($"Ontology <{rootOntology}> does not have an annotation.");
413 | }
414 | IUriNode ccLicense = rootOntologyGraph.CreateUriNode(VocabularyHelper.CC.license);
415 | if (!rootOntology.GetNodesViaProperty(ccLicense).Any(objNode => objNode.IsLiteral() || objNode.IsUri()))
416 | {
417 | throw new RdfException($"Ontology <{rootOntology}> does not have an annotation that is a URI or literal.");
418 | }
419 | docInfo.title = rootOntology.GetNodesViaProperty(dcTitle).LiteralNodes().OrderBy(title => title.HasLanguage()).First().Value;
420 | docInfo.Version = rootOntology.VersionInfo.OrderBy(versionInfo => versionInfo.HasLanguage()).First().Value;
421 | docInfo.license = new OASDocument.License();
422 | INode licenseNode = rootOntology.GetNodesViaProperty(ccLicense).OrderBy(node => node.NodeType).First();
423 | if (licenseNode.IsUri())
424 | {
425 | docInfo.license.name = ((UriNode)licenseNode).GetLocalName();
426 | docInfo.license.url = ((UriNode)licenseNode).Uri.ToString();
427 | }
428 | else
429 | {
430 | docInfo.license.name = ((LiteralNode)licenseNode).Value;
431 | }
432 |
433 | // Non-mandatory info components, e.g., rdfs:comment
434 | IUriNode dcDescription = rootOntologyGraph.CreateUriNode(VocabularyHelper.DC.description);
435 | if (rootOntology.GetNodesViaProperty(dcDescription).LiteralNodes().Any())
436 | {
437 | ILiteralNode ontologyDescriptionLiteral = rootOntology.GetNodesViaProperty(dcDescription).LiteralNodes().OrderBy(description => description.HasLanguage()).First();
438 | string ontologyDescription = ontologyDescriptionLiteral.Value.Trim().Replace("\r\n", "\n", StringComparison.Ordinal).Replace("\n", "
", StringComparison.Ordinal);
439 | docInfo.description = $"The documentation below is automatically extracted from a annotation on the ontology {rootOntology}:
*{ontologyDescription}*";
440 | }
441 |
442 | return docInfo;
443 | }
444 |
445 | private static string GetKeyNameForResource(OntologyResource resource)
446 | {
447 | if (!resource.IsNamed())
448 | {
449 | throw new RdfException($"Could not generate key name for OntologyResource '{resource}'; resource is anonymous.");
450 | }
451 |
452 | Uri resourceNamespace = resource.GetNamespace();
453 |
454 | // If the concept is defined in the root ontology (which is default @vocab), return the local identifier
455 | if (resourceNamespace.Equals(rootOntology.GetIri()))
456 | {
457 | return resource.GetLocalName();
458 | }
459 |
460 | // If the resource is in an ontology that has been parsed and thus added to known namespace prefixes, return prefixed qname
461 | if (namespacePrefixes.ContainsKey(resourceNamespace))
462 | {
463 | return $"{namespacePrefixes[resourceNamespace]}:{resource.GetLocalName()}";
464 | }
465 |
466 | // Fallback option -- add this thing to the namespace mapper and return it
467 | char[] trimChars = { '#', '/' };
468 | string namespaceShortName = resourceNamespace.ToString().Trim(trimChars).Split('/').Last();
469 | namespacePrefixes[resourceNamespace] = namespaceShortName;
470 | return $"{namespacePrefixes[resourceNamespace]}:{resource.GetLocalName()}";
471 | }
472 |
473 | private static string GetEndpointName(OntologyResource cls)
474 | {
475 | IUriNode endpoint = _ontologyGraph.CreateUriNode(VocabularyHelper.O2O.endpoint);
476 | IEnumerable endpoints = cls.GetNodesViaProperty(endpoint).LiteralNodes();
477 | if (endpoints.Any())
478 | {
479 | return endpoints.First().AsValuedNode().AsString();
480 | }
481 | return GetKeyNameForResource(cls);
482 | }
483 |
484 | private static void GenerateAtomicClassSchemas()
485 | {
486 | // Iterate over all non-deprecated classes that are either explicitly included, or that have subclasses that are included
487 | // The latter is to ensure that schema subsumption using allOf works
488 | foreach (OntologyClass oClass in _ontologyGraph.OwlClasses.Where(oClass => oClass.IsNamed() &&
489 | !oClass.IsDeprecated() &&
490 | (IsIncluded(oClass) || oClass.SubClasses.Any(subClass => IsIncluded(subClass)))))
491 | {
492 | // Get key name for API
493 | string classLabel = GetKeyNameForResource(oClass);
494 |
495 | // Create schema for class and corresponding properties dict
496 | OASDocument.ComplexSchema schema = new OASDocument.ComplexSchema();
497 | schema.properties = new Dictionary();
498 |
499 | // Set up the required properties set, used in subsequently generating HTTP operations
500 | requiredPropertiesForEachClass.Add(classLabel, new HashSet());
501 |
502 | // Add @id for all entries
503 | OASDocument.PrimitiveSchema idSchema = new OASDocument.PrimitiveSchema();
504 | idSchema.type = "string";
505 | schema.properties.Add("@id", idSchema);
506 |
507 | // Add @type for all entries
508 | OASDocument.PrimitiveSchema typeSchema = new OASDocument.PrimitiveSchema
509 | {
510 | type = "string",
511 | DefaultValue = GetKeyNameForResource(oClass)
512 | };
513 | schema.properties.Add("@type", typeSchema);
514 |
515 | // @context is mandatory
516 | schema.required = new List() { "@context" };
517 |
518 | // Label is an option for all entries
519 | OASDocument.PrimitiveSchema labelSchema = new OASDocument.PrimitiveSchema();
520 | labelSchema.type = "string";
521 | schema.properties.Add("label", labelSchema);
522 |
523 | // Iterate over all (local) relationships and add them as properties
524 | foreach (Relationship relationship in oClass.GetRelationships().Where(relationship =>
525 | IsIncluded(relationship.Property) &&
526 | (relationship.Property.IsObjectProperty() || relationship.Property.IsDataProperty()) &&
527 | !relationship.Property.IsDeprecated() &&
528 | !relationship.Target.IsDeprecated() &&
529 | !relationship.Target.IsOwlThing() &&
530 | !relationship.Target.IsRdfsLiteral()
531 | ))
532 | {
533 | OntologyClass range = relationship.Target;// property.Ranges.First();
534 | OntologyProperty property = relationship.Property;
535 |
536 | // Used for lookups against constraints dict
537 | UriNode propertyNode = ((UriNode)property.Resource);
538 |
539 | // Used to allocate property to schema.properties dictionary
540 | string propertyLabel = GetKeyNameForResource(property);
541 |
542 | // The return value: a property block to be added to the output document
543 | OASDocument.Schema outputSchema;
544 |
545 | // Check if multiple values are allowed for this property. By default they are.
546 | //bool propertyAllowsMultipleValues = !property.IsFunctional();
547 |
548 | // If this is a data property
549 | if (property.IsDataProperty())
550 | {
551 | // Set up the (possibly later on nested) property block
552 | OASDocument.PrimitiveSchema dataPropertySchema = new OASDocument.PrimitiveSchema();
553 |
554 | // Fall back to string representation for unknown types
555 | dataPropertySchema.type = "string";
556 |
557 | // Check that range is an XSD type that can be parsed into
558 | // an OAS type and format (note: not all XSD types are covered)
559 | if (range.IsXsdDatatype() || range.IsSimpleXsdWrapper()) {
560 | string rangeXsdType = "";
561 | if (range.IsXsdDatatype()) {
562 | rangeXsdType = ((UriNode)range.Resource).GetLocalName();
563 | }
564 | else {
565 | rangeXsdType = range.EquivalentClasses.First().GetUriNode().GetLocalName();
566 | }
567 | if (xsdOsaMappings.ContainsKey(rangeXsdType))
568 | {
569 | dataPropertySchema.type = xsdOsaMappings[rangeXsdType].Item1;
570 | string format = xsdOsaMappings[rangeXsdType].Item2;
571 | if (format.Length > 0)
572 | {
573 | dataPropertySchema.format = format;
574 | }
575 | }
576 | }
577 |
578 | // Assign return value
579 | outputSchema = dataPropertySchema;
580 | }
581 | else
582 | {
583 | // This is an Object property
584 | // Set up the (possibly later on nested) property block
585 | OASDocument.Schema uriPropertySchema;
586 |
587 | // Set the type of the property; locally defined named classes can be either URI or full schema representation
588 | uriPropertySchema = new OASDocument.ComplexSchema
589 | {
590 | properties = new Dictionary {
591 | { "@id", new OASDocument.PrimitiveSchema { type = "string" } },
592 | { "@type", new OASDocument.PrimitiveSchema { type = "string", DefaultValue = GetKeyNameForResource(range) } }
593 | },
594 | required = new List { "@id" }
595 | };
596 |
597 | outputSchema = uriPropertySchema;
598 | }
599 |
600 | if (relationship.MinimumCount > 0) {
601 | requiredPropertiesForEachClass[classLabel].Add(propertyLabel);
602 | }
603 |
604 |
605 | if (property.IsFunctional() || relationship.ExactCount == 1)
606 | {
607 | schema.properties[propertyLabel] = outputSchema;
608 | }
609 | else
610 | {
611 | OASDocument.ArraySchema propertyArraySchema = new OASDocument.ArraySchema();
612 | propertyArraySchema.items = outputSchema;
613 | if (relationship.MinimumCount.HasValue)
614 | {
615 | propertyArraySchema.minItems = relationship.MinimumCount.Value;
616 | }
617 | if (relationship.MaximumCount.HasValue)
618 | {
619 | propertyArraySchema.maxItems = relationship.MaximumCount.Value;
620 | }
621 | schema.properties[propertyLabel] = propertyArraySchema;
622 |
623 | }
624 | }
625 |
626 | IUriNode rdfsSubClassOf = _ontologyGraph.CreateUriNode(VocabularyHelper.RDFS.subClassOf);
627 | IEnumerable namedSuperClasses = oClass.DirectSuperClasses.Where(superClass =>
628 | superClass.IsNamed() &&
629 | !superClass.IsOwlThing() &&
630 | !superClass.IsDeprecated() &&
631 | !PropertyAssertionIsDeprecated(oClass.GetUriNode(), rdfsSubClassOf, superClass.GetUriNode())
632 | );
633 | if (namedSuperClasses.Any())
634 | {
635 | OASDocument.AllOfSchema inheritanceSchema = new OASDocument.AllOfSchema();
636 | OASDocument.Schema[] superClassSchemaReferences = namedSuperClasses.Select(superClass => new OASDocument.ReferenceSchema(GetKeyNameForResource(superClass))).ToArray();
637 | inheritanceSchema.allOf = new OASDocument.Schema[namedSuperClasses.Count() + 1];
638 | for (int i = 0; i < namedSuperClasses.Count(); i++)
639 | {
640 | inheritanceSchema.allOf[i] = superClassSchemaReferences[i];
641 | }
642 | inheritanceSchema.allOf[namedSuperClasses.Count()] = schema;
643 | _document.components.schemas.Add(classLabel.Replace(":", "_", StringComparison.Ordinal), inheritanceSchema);
644 | }
645 | else
646 | {
647 | _document.components.schemas.Add(classLabel.Replace(":", "_", StringComparison.Ordinal), schema);
648 | }
649 | }
650 | }
651 |
652 | // TODO: move this into the DotNetRdfExtensions class
653 | private static bool PropertyAssertionIsDeprecated(INode subj, IUriNode pred, INode obj)
654 | {
655 | IUriNode owlAnnotatedSource = _ontologyGraph.CreateUriNode(VocabularyHelper.OWL.annotatedSource);
656 | IUriNode owlAnnotatedProperty = _ontologyGraph.CreateUriNode(VocabularyHelper.OWL.annotatedProperty);
657 | IUriNode owlAnnotatedTarget = _ontologyGraph.CreateUriNode(VocabularyHelper.OWL.annotatedTarget);
658 | IUriNode owlDeprecated = _ontologyGraph.CreateUriNode(VocabularyHelper.OWL.deprecated);
659 |
660 | IEnumerable axiomAnnotations = _ontologyGraph.Nodes
661 | .Where(node => _ontologyGraph.ContainsTriple(new Triple(node, owlAnnotatedSource, subj)))
662 | .Where(node => _ontologyGraph.ContainsTriple(new Triple(node, owlAnnotatedProperty, pred)))
663 | .Where(node => _ontologyGraph.ContainsTriple(new Triple(node, owlAnnotatedTarget, obj)));
664 |
665 | foreach (INode axiomAnnotation in axiomAnnotations)
666 | {
667 | foreach (Triple deprecationAssertion in _ontologyGraph.GetTriplesWithSubjectPredicate(axiomAnnotation, owlDeprecated).Where(trip => trip.Object.NodeType == NodeType.Literal))
668 | {
669 | IValuedNode deprecationValue = deprecationAssertion.Object.AsValuedNode();
670 | try
671 | {
672 | if (deprecationValue.AsBoolean())
673 | {
674 | return true;
675 | }
676 | }
677 | catch
678 | {
679 |
680 | }
681 | }
682 | }
683 | return false;
684 | }
685 |
686 | private static void GenerateClassPaths()
687 | {
688 | // Iterate over all classes
689 | foreach (OntologyClass oClass in _ontologyGraph.OwlClasses.Where(oClass => oClass.IsNamed() && IsIncluded(oClass)))
690 | {
691 | // Get key name for API
692 | string classLabel = GetKeyNameForResource(oClass);
693 | string endpointName = GetEndpointName(oClass);
694 |
695 | // Create paths and corresponding operations for class
696 | _document.paths.Add($"/{endpointName}", new OASDocument.Path
697 | {
698 | get = GenerateGetEntitiesOperation(endpointName, classLabel, oClass),
699 | post = GeneratePostEntityOperation(endpointName, classLabel)
700 | });
701 | _document.paths.Add($"/{endpointName}/{{id}}", new OASDocument.Path
702 | {
703 | get = GenerateGetEntityByIdOperation(endpointName, classLabel),
704 | patch = GeneratePatchToIdOperation(endpointName, classLabel),
705 | put = GeneratePutToIdOperation(endpointName, classLabel),
706 | delete = GenerateDeleteByIdOperation(endpointName, classLabel)
707 | });
708 | }
709 | }
710 |
711 | private static OASDocument.Operation GenerateDeleteByIdOperation(string endpointName, string classLabel)
712 | {
713 | OASDocument.Operation deleteOperation = new OASDocument.Operation();
714 | deleteOperation.summary = $"Delete a '{classLabel}' object.";
715 | deleteOperation.tags.Add(endpointName);
716 |
717 | // Add the ID parameter
718 | OASDocument.Parameter idParameter = new OASDocument.Parameter
719 | {
720 | name = "id",
721 | description = $"Id of '{classLabel}' to delete.",
722 | InField = OASDocument.Parameter.InFieldValues.path,
723 | required = true,
724 | schema = new OASDocument.PrimitiveSchema
725 | {
726 | type = "string"
727 | }
728 | };
729 | deleteOperation.parameters.Add(idParameter);
730 |
731 | // Create each of the HTTP response types
732 | OASDocument.Response response404 = new OASDocument.Response();
733 | response404.description = $"An object of type '{classLabel}' with the specified ID was not found.";
734 | deleteOperation.responses.Add("404", response404);
735 |
736 | OASDocument.Response response500 = new OASDocument.Response();
737 | response500.description = "Internal Server Error";
738 | deleteOperation.responses.Add("500", response500);
739 |
740 | OASDocument.Response response200 = new OASDocument.Response();
741 | response200.description = $"'{classLabel}' entity was successfully deleted.";
742 | deleteOperation.responses.Add("200", response200);
743 |
744 | return deleteOperation;
745 | }
746 |
747 | private static OASDocument.Operation GeneratePostEntityOperation(string endpointName, string classLabel)
748 | {
749 | OASDocument.Operation postOperation = new OASDocument.Operation();
750 | postOperation.summary = $"Create a new '{classLabel}' object.";
751 | postOperation.tags.Add(endpointName);
752 |
753 | // Create request body
754 | OASDocument.RequestBody body = new OASDocument.RequestBody
755 | {
756 | description = $"New '{classLabel}' entity that is to be added.",
757 | required = true,
758 | content = new Dictionary
759 | {
760 | {
761 | "application/ld+json", new OASDocument.Content
762 | {
763 | schema = MergeAtomicSchemaWithContext(classLabel)
764 | }
765 | }
766 | }
767 | };
768 | postOperation.requestBody = body;
769 |
770 | // Create each of the HTTP response types
771 | OASDocument.Response response500 = new OASDocument.Response();
772 | response500.description = "Internal Server Error";
773 | postOperation.responses.Add("500", response500);
774 |
775 | OASDocument.Response response400 = new OASDocument.Response();
776 | response400.description = "Bad Request";
777 | postOperation.responses.Add("400", response400);
778 |
779 | OASDocument.Response response201 = new OASDocument.Response();
780 | response201.description = "Entity was successfully created (new representation returned).";
781 | postOperation.responses.Add("201", response201);
782 |
783 | response201.content = new Dictionary();
784 | OASDocument.Content content201 = new OASDocument.Content();
785 | response201.content.Add("application/ld+json", content201);
786 |
787 | // Response is per previously defined schema
788 | content201.schema = MergeAtomicSchemaWithContextAndRequiredProperties(classLabel);
789 |
790 | return postOperation;
791 | }
792 |
793 | private static OASDocument.Operation GenerateGetEntityByIdOperation(string endpointName, string classLabel)
794 | {
795 | OASDocument.Operation getOperation = new OASDocument.Operation();
796 | getOperation.summary = $"Get a specific '{classLabel}' object.";
797 | getOperation.tags.Add(endpointName);
798 |
799 | // Add the ID parameter
800 | OASDocument.Parameter idParameter = new OASDocument.Parameter
801 | {
802 | name = "id",
803 | description = $"Id of '{classLabel}' to return.",
804 | InField = OASDocument.Parameter.InFieldValues.path,
805 | required = true,
806 | schema = new OASDocument.PrimitiveSchema
807 | {
808 | type = "string"
809 | }
810 | };
811 | getOperation.parameters.Add(idParameter);
812 |
813 | // Create each of the HTTP response types
814 | OASDocument.Response response404 = new OASDocument.Response();
815 | response404.description = $"An object of type '{classLabel}' with the specified ID was not found.";
816 | getOperation.responses.Add("404", response404);
817 |
818 | OASDocument.Response response500 = new OASDocument.Response();
819 | response500.description = "Internal Server Error";
820 | getOperation.responses.Add("500", response500);
821 |
822 | OASDocument.Response response200 = new OASDocument.Response();
823 | response200.description = $"A '{classLabel}' object.";
824 | getOperation.responses.Add("200", response200);
825 |
826 | response200.content = new Dictionary();
827 | OASDocument.Content content200 = new OASDocument.Content();
828 | response200.content.Add("application/ld+json", content200);
829 |
830 | // Response is per previously defined schema
831 | content200.schema = MergeAtomicSchemaWithContextAndRequiredProperties(classLabel);
832 |
833 | return getOperation;
834 | }
835 |
836 | private static OASDocument.Operation GenerateGetEntitiesOperation(string endpointName, string classLabel, OntologyClass oClass)
837 | {
838 |
839 | // Create Get
840 | OASDocument.Operation getOperation = new OASDocument.Operation();
841 | getOperation.summary = "Get '" + classLabel + "' entities.";
842 | getOperation.tags.Add(endpointName);
843 |
844 | // Add pagination parameters
845 | getOperation.parameters.Add(new OASDocument.Parameter { ReferenceTo = "pageParam" });
846 | getOperation.parameters.Add(new OASDocument.Parameter { ReferenceTo = "sizeParam" });
847 |
848 | // Add sort param
849 | getOperation.parameters.Add(new OASDocument.Parameter { ReferenceTo = "sortParam" });
850 |
851 | // Add parameters for each property field that can be expressed on this class
852 | foreach (OntologyProperty property in oClass.IsExhaustiveDomainOfUniques()
853 | .Where(property => property.IsDataProperty() || property.IsObjectProperty())
854 | .Where(property => property.Ranges.Count() == 1)
855 | .Where(property => IsIncluded(property)))
856 | {
857 | string propertyLabel = GetKeyNameForResource(property);
858 |
859 | // Fall back to string representation and no format for object properties
860 | // abd data properties w/ unknown types
861 | string propertyType = "string";
862 | string propertyFormat = "";
863 |
864 | // Check that range is an XSD type that can be parsed into
865 | // an OAS type and format (note: not all XSD types are covered)
866 | OntologyClass range = property.Ranges.First();
867 | if (range.IsNamed()) {
868 | if (range.IsXsdDatatype() || range.IsSimpleXsdWrapper()) {
869 | string rangeXsdType = "";
870 | if (range.IsXsdDatatype()) {
871 | rangeXsdType = ((UriNode)range.Resource).GetLocalName();
872 | }
873 | else {
874 | rangeXsdType = range.EquivalentClasses.First().GetUriNode().GetLocalName();
875 | }
876 | if (xsdOsaMappings.ContainsKey(rangeXsdType))
877 | {
878 | propertyType = xsdOsaMappings[rangeXsdType].Item1;
879 | string format = xsdOsaMappings[rangeXsdType].Item2;
880 | if (format.Length > 0)
881 | {
882 | propertyFormat = format;
883 | }
884 | }
885 | }
886 | }
887 |
888 | // Select a filter schema to use for parameter formats where it is applicable
889 | string filterSchema = "";
890 | switch (propertyType)
891 | {
892 | case "string":
893 | filterSchema = propertyFormat switch
894 | {
895 | "date-time" => "DateTimeFilter",
896 | _ => "StringFilter",
897 | };
898 | break;
899 |
900 | case "integer":
901 | filterSchema = "IntegerFilter";
902 | break;
903 |
904 | case "number":
905 | filterSchema = "NumberFilter";
906 | break;
907 | }
908 |
909 | // Base the property schema on the filter, if one was selected above
910 | // Otherwise, just do a simple type-based schema, possibly with format if one was found
911 | OASDocument.Schema propertySchema;
912 | if (filterSchema.Length > 0)
913 | {
914 | propertySchema = new OASDocument.ReferenceSchema(filterSchema);
915 | }
916 | else
917 | {
918 | if (propertyFormat.Length > 0)
919 | {
920 | propertySchema = new OASDocument.PrimitiveSchema
921 | {
922 | type = propertyType,
923 | format = propertyFormat
924 | };
925 | }
926 | else
927 | {
928 | propertySchema = new OASDocument.PrimitiveSchema
929 | {
930 | type = propertyType
931 | };
932 | }
933 | }
934 |
935 | OASDocument.Parameter parameter = new OASDocument.Parameter
936 | {
937 | name = propertyLabel,
938 | description = $"Filter value on property '{propertyLabel}'.",
939 | required = false,
940 | schema = propertySchema,
941 | InField = OASDocument.Parameter.InFieldValues.query
942 | };
943 |
944 | if (filterSchema.Length > 0)
945 | {
946 | parameter.style = "deepObject";
947 | }
948 |
949 | getOperation.parameters.Add(parameter);
950 | }
951 |
952 | // Create each of the HTTP response types
953 | OASDocument.Response response400 = new OASDocument.Response();
954 | response400.description = "Bad Request";
955 | getOperation.responses.Add("400", response400);
956 |
957 | OASDocument.Response response500 = new OASDocument.Response();
958 | response500.description = "Internal Server Error";
959 | getOperation.responses.Add("500", response500);
960 |
961 | OASDocument.Response response200 = new OASDocument.Response();
962 | response200.description = "An array of '" + classLabel + "' objects.";
963 | getOperation.responses.Add("200", response200);
964 |
965 | response200.content = new Dictionary();
966 | OASDocument.Content content200 = new OASDocument.Content();
967 | response200.content.Add("application/ld+json", content200);
968 |
969 | // Generate schema with required fields propped on via allOf (if any required fields exist)
970 | OASDocument.Schema classSchemaWithRequiredProperties = MergeAtomicSchemaWithRequiredProperties(classLabel);
971 |
972 | // Generate wrapper Hydra schema (https://www.hydra-cg.com/spec/latest/core/)
973 | OASDocument.Schema hydraSchema = new OASDocument.AllOfSchema
974 | {
975 | allOf = new OASDocument.Schema[]
976 | {
977 | new OASDocument.ReferenceSchema("HydraCollectionWrapper"),
978 | new OASDocument.ComplexSchema
979 | {
980 | properties = new Dictionary
981 | {
982 | {"hydra:member", new OASDocument.ArraySchema {
983 | items = classSchemaWithRequiredProperties
984 | } }
985 | }
986 | }
987 | }
988 | };
989 |
990 | // Wrap responses in array
991 | content200.schema = hydraSchema;
992 |
993 | // Return
994 | return getOperation;
995 | }
996 |
997 | private static OASDocument.Schema MergeAtomicSchemaWithRequiredProperties(string classLabel)
998 | {
999 | OASDocument.Schema itemSchema;
1000 | if (requiredPropertiesForEachClass[classLabel].Count == 0)
1001 | {
1002 | itemSchema = new OASDocument.ReferenceSchema(classLabel);
1003 | }
1004 | else
1005 | {
1006 | itemSchema = new OASDocument.AllOfSchema
1007 | {
1008 | allOf = new OASDocument.Schema[] {
1009 | new OASDocument.ReferenceSchema(classLabel),
1010 | new OASDocument.ComplexSchema {
1011 | required = requiredPropertiesForEachClass[classLabel].ToList()
1012 | }
1013 | }
1014 | };
1015 | }
1016 | return itemSchema;
1017 | }
1018 |
1019 | private static OASDocument.Schema MergeAtomicSchemaWithContext(string classLabel)
1020 | {
1021 | OASDocument.AllOfSchema itemSchema = new OASDocument.AllOfSchema();
1022 | OASDocument.ReferenceSchema classSchema = new OASDocument.ReferenceSchema(classLabel);
1023 | OASDocument.ReferenceSchema contextReferenceSchema = new OASDocument.ReferenceSchema("Context");
1024 | OASDocument.ComplexSchema contextPropertySchema = new OASDocument.ComplexSchema
1025 | {
1026 | required = new List { "@context" },
1027 | properties = new Dictionary() { { "@context", contextReferenceSchema } }
1028 | };
1029 |
1030 | // Otherwise merge only with context
1031 | itemSchema.allOf = new OASDocument.Schema[]
1032 | {
1033 | contextPropertySchema,
1034 | classSchema
1035 | };
1036 | return itemSchema;
1037 | }
1038 |
1039 | private static OASDocument.Schema MergeAtomicSchemaWithContextAndRequiredProperties(string classLabel)
1040 | {
1041 | OASDocument.AllOfSchema itemSchema = new OASDocument.AllOfSchema();
1042 | OASDocument.ReferenceSchema classSchema = new OASDocument.ReferenceSchema(classLabel);
1043 | OASDocument.ReferenceSchema contextReferenceSchema = new OASDocument.ReferenceSchema("Context");
1044 | OASDocument.ComplexSchema contextPropertySchema = new OASDocument.ComplexSchema
1045 | {
1046 | required = new List { "@context" },
1047 | properties = new Dictionary() { { "@context", contextReferenceSchema } }
1048 | };
1049 |
1050 | // If there are required properties, merge them also
1051 | if (requiredPropertiesForEachClass[classLabel].Count != 0)
1052 | {
1053 | OASDocument.ComplexSchema requiredPropertiesSchema = new OASDocument.ComplexSchema { required = requiredPropertiesForEachClass[classLabel].ToList() };
1054 | itemSchema.allOf = new OASDocument.Schema[]
1055 | {
1056 | contextPropertySchema,
1057 | classSchema,
1058 | requiredPropertiesSchema
1059 | };
1060 | return itemSchema;
1061 | }
1062 |
1063 | // Otherwise merge only with context
1064 | itemSchema.allOf = new OASDocument.Schema[]
1065 | {
1066 | contextPropertySchema,
1067 | classSchema
1068 | };
1069 | return itemSchema;
1070 | }
1071 |
1072 | private static OASDocument.Operation GeneratePatchToIdOperation(string endpointName, string classLabel)
1073 | {
1074 | OASDocument.Operation patchOperation = new OASDocument.Operation();
1075 | patchOperation.summary = $"Update a single property on a specific '{classLabel}' object.";
1076 | patchOperation.tags.Add(endpointName);
1077 |
1078 | // Add the ID parameter
1079 | OASDocument.Parameter idParameter = new OASDocument.Parameter
1080 | {
1081 | name = "id",
1082 | description = $"Id of '{classLabel}' to update.",
1083 | InField = OASDocument.Parameter.InFieldValues.path,
1084 | required = true,
1085 | schema = new OASDocument.PrimitiveSchema
1086 | {
1087 | type = "string"
1088 | }
1089 | };
1090 | patchOperation.parameters.Add(idParameter);
1091 |
1092 | // Create patch schema
1093 | OASDocument.ReferenceSchema contextReferenceSchema = new OASDocument.ReferenceSchema("Context");
1094 | OASDocument.ComplexSchema contextPropertySchema = new OASDocument.ComplexSchema
1095 | {
1096 | required = new List { "@context" },
1097 | properties = new Dictionary() { { "@context", contextReferenceSchema } }
1098 | };
1099 |
1100 | OASDocument.Schema patchSchema = new OASDocument.AllOfSchema
1101 | {
1102 | allOf = new OASDocument.Schema[] {
1103 | contextPropertySchema,
1104 | new OASDocument.ReferenceSchema(classLabel),
1105 | new OASDocument.ComplexSchema {
1106 | minProperties = 2,
1107 | maxProperties = 2
1108 | }
1109 | }
1110 | };
1111 |
1112 | // Add request body
1113 | OASDocument.RequestBody body = new OASDocument.RequestBody
1114 | {
1115 | description = "A single JSON key-value pair (plus @context), indicating the property to update and its new value. Note that the Swagger UI does not properly show the size constraint on this parameter; but the underlying OpenAPI Specification document does.",
1116 | required = true,
1117 | content = new Dictionary
1118 | {
1119 | {
1120 | "application/ld+json", new OASDocument.Content
1121 | {
1122 | schema = patchSchema
1123 | }
1124 | }
1125 | }
1126 | };
1127 | patchOperation.requestBody = body;
1128 |
1129 | // Create each of the HTTP response types
1130 | OASDocument.Response response400 = new OASDocument.Response();
1131 | response400.description = "Bad Request";
1132 | patchOperation.responses.Add("400", response400);
1133 |
1134 | OASDocument.Response response404 = new OASDocument.Response();
1135 | response404.description = $"An object of type '{classLabel}' with the specified ID was not found.";
1136 | patchOperation.responses.Add("404", response404);
1137 |
1138 | OASDocument.Response response500 = new OASDocument.Response();
1139 | response500.description = "Internal Server Error";
1140 | patchOperation.responses.Add("500", response500);
1141 |
1142 | OASDocument.Response response200 = new OASDocument.Response();
1143 | response200.description = "Entity was updated successfully (new representation returned).";
1144 | patchOperation.responses.Add("200", response200);
1145 |
1146 | response200.content = new Dictionary();
1147 | OASDocument.Content content200 = new OASDocument.Content();
1148 | response200.content.Add("application/ld+json", content200);
1149 |
1150 | // Response is per previously defined schema
1151 | content200.schema = MergeAtomicSchemaWithContextAndRequiredProperties(classLabel);
1152 |
1153 | return patchOperation;
1154 | }
1155 |
1156 | private static OASDocument.Operation GeneratePutToIdOperation(string endpointName, string classLabel)
1157 | {
1158 | OASDocument.Operation putOperation = new OASDocument.Operation();
1159 | putOperation.summary = $"Update an existing '{classLabel}' entity.";
1160 | putOperation.tags.Add(endpointName);
1161 |
1162 | // Add the ID parameter
1163 | OASDocument.Parameter idParameter = new OASDocument.Parameter
1164 | {
1165 | name = "id",
1166 | description = $"Id of '{classLabel}' to update.",
1167 | InField = OASDocument.Parameter.InFieldValues.path,
1168 | required = true,
1169 | schema = new OASDocument.PrimitiveSchema
1170 | {
1171 | type = "string"
1172 | }
1173 | };
1174 | putOperation.parameters.Add(idParameter);
1175 |
1176 | // Add request body
1177 | OASDocument.RequestBody body = new OASDocument.RequestBody
1178 | {
1179 | description = $"Updated data for '{classLabel}' entity.",
1180 | required = true,
1181 | content = new Dictionary
1182 | {
1183 | {
1184 | "application/ld+json", new OASDocument.Content
1185 | {
1186 | schema = MergeAtomicSchemaWithContext(classLabel)
1187 | }
1188 | }
1189 | }
1190 | };
1191 | putOperation.requestBody = body;
1192 |
1193 | // Create each of the HTTP response types
1194 | OASDocument.Response response400 = new OASDocument.Response();
1195 | response400.description = "Bad Request";
1196 | putOperation.responses.Add("400", response400);
1197 |
1198 | OASDocument.Response response404 = new OASDocument.Response();
1199 | response404.description = $"An object of type '{classLabel}' with the specified ID was not found.";
1200 | putOperation.responses.Add("404", response404);
1201 |
1202 | OASDocument.Response response500 = new OASDocument.Response();
1203 | response500.description = "Internal Server Error";
1204 | putOperation.responses.Add("500", response500);
1205 |
1206 | OASDocument.Response response200 = new OASDocument.Response();
1207 | response200.description = "Entity was updated successfully (new representation returned).";
1208 | putOperation.responses.Add("200", response200);
1209 |
1210 | response200.content = new Dictionary();
1211 | OASDocument.Content content200 = new OASDocument.Content();
1212 | response200.content.Add("application/ld+json", content200);
1213 |
1214 | // Response is per previously defined schema
1215 | content200.schema = MergeAtomicSchemaWithContextAndRequiredProperties(classLabel);
1216 |
1217 | return putOperation;
1218 | }
1219 |
1220 | private static int GetMinCardinality(OntologyClass restriction)
1221 | {
1222 | OntologyGraph graph = restriction.Graph as OntologyGraph;
1223 | IUriNode someValuesFrom = graph.CreateUriNode(VocabularyHelper.OWL.someValuesFrom);
1224 | IUriNode minCardinality = graph.CreateUriNode(VocabularyHelper.OWL.minCardinality);
1225 | IUriNode minQualifiedCardinality = graph.CreateUriNode(VocabularyHelper.OWL.minQualifiedCardinality);
1226 |
1227 | IEnumerable minCardinalities = restriction.GetNodesViaProperty(minCardinality).Union(restriction.GetNodesViaProperty(minQualifiedCardinality));
1228 | if (minCardinalities.LiteralNodes().Count() == 1 &&
1229 | minCardinalities.LiteralNodes().First().IsInteger())
1230 | {
1231 | return int.Parse(minCardinalities.LiteralNodes().First().Value, invariantCulture);
1232 | }
1233 |
1234 | if (restriction.GetNodesViaProperty(someValuesFrom).Count() == 1)
1235 | {
1236 | return 1;
1237 | }
1238 |
1239 | return 0;
1240 | }
1241 |
1242 | private static int GetExactCardinality(OntologyClass restriction)
1243 | {
1244 | OntologyGraph graph = restriction.Graph as OntologyGraph;
1245 | IUriNode cardinality = graph.CreateUriNode(VocabularyHelper.OWL.cardinality);
1246 | IUriNode qualifiedCardinality = graph.CreateUriNode(VocabularyHelper.OWL.qualifiedCardinality);
1247 | IUriNode onClass = graph.CreateUriNode(VocabularyHelper.OWL.onClass);
1248 |
1249 | IEnumerable exactCardinalities = restriction.GetNodesViaProperty(cardinality);
1250 | if (exactCardinalities.LiteralNodes().Count() == 1 &&
1251 | exactCardinalities.LiteralNodes().First().IsInteger())
1252 | {
1253 | return int.Parse(exactCardinalities.LiteralNodes().First().Value, invariantCulture);
1254 | }
1255 |
1256 | IEnumerable exactQualifiedCardinalities = restriction.GetNodesViaProperty(qualifiedCardinality);
1257 | if (exactQualifiedCardinalities.LiteralNodes().Count() == 1 &&
1258 | exactQualifiedCardinalities.LiteralNodes().First().IsInteger())
1259 | {
1260 | IEnumerable qualifierClasses = restriction.GetNodesViaProperty(onClass).UriNodes();
1261 | if (qualifierClasses.Count() == 1 && qualifierClasses.First().Uri.Equals(VocabularyHelper.OWL.Thing))
1262 | {
1263 | return int.Parse(exactQualifiedCardinalities.LiteralNodes().First().Value, invariantCulture);
1264 | }
1265 | }
1266 |
1267 | return 0;
1268 | }
1269 |
1270 | private static int GetMaxCardinality(OntologyClass restriction)
1271 | {
1272 | OntologyGraph graph = restriction.Graph as OntologyGraph;
1273 | IUriNode maxCardinality = graph.CreateUriNode(VocabularyHelper.OWL.maxCardinality);
1274 | IUriNode maxQualifiedCardinality = graph.CreateUriNode(VocabularyHelper.OWL.maxQualifiedCardinality);
1275 | IUriNode onClass = graph.CreateUriNode(VocabularyHelper.OWL.onClass);
1276 |
1277 | IEnumerable maxCardinalities = restriction.GetNodesViaProperty(maxCardinality);
1278 | if (maxCardinalities.LiteralNodes().Count() == 1 &&
1279 | maxCardinalities.LiteralNodes().First().IsInteger())
1280 | {
1281 | return int.Parse(maxCardinalities.LiteralNodes().First().Value, invariantCulture);
1282 | }
1283 |
1284 | IEnumerable maxQualifiedCardinalities = restriction.GetNodesViaProperty(maxQualifiedCardinality);
1285 | if (maxQualifiedCardinalities.LiteralNodes().Count() == 1 &&
1286 | maxQualifiedCardinalities.LiteralNodes().First().IsInteger())
1287 | {
1288 | IEnumerable qualifierClasses = restriction.GetNodesViaProperty(onClass).UriNodes();
1289 | if (qualifierClasses.Count() == 1 && qualifierClasses.First().Uri.Equals(VocabularyHelper.OWL.Thing))
1290 | {
1291 | return int.Parse(maxQualifiedCardinalities.LiteralNodes().First().Value, invariantCulture);
1292 | }
1293 | }
1294 |
1295 | return 0;
1296 | }
1297 |
1298 | ///
1299 | /// Loads imported ontologies transitively. Each imported ontology is added
1300 | /// to the static set importedOntologies.
1301 | ///
1302 | /// The ontology to import.
1303 | private static void LoadImport(Ontology importedOntology)
1304 | {
1305 | // We only deal with named ontologies
1306 | if (importedOntology.IsNamed())
1307 | {
1308 |
1309 | // Parse and load ontology from the stated import URI
1310 | Uri importUri = importedOntology.GetIri();
1311 |
1312 | //Uri importedOntologyUri = ((IUriNode)importedOntology.Resource).Uri;
1313 | OntologyGraph fetchedOntologyGraph = new OntologyGraph();
1314 |
1315 | try
1316 | {
1317 | UriLoader.Load(fetchedOntologyGraph, importUri);
1318 | }
1319 | catch (RdfParseException e)
1320 | {
1321 | Console.Write(e.Message);
1322 | Console.Write(e.StackTrace);
1323 | }
1324 |
1325 | // Set up a new ontology metadata object from the retrieved ontology graph.
1326 | // This is needed since this ontology's self-defined IRI or version IRI often
1327 | // differs from the IRI through which it was imported (i.e., importedOntology in
1328 | // this method's signature), due to .htaccess redirects, version URIs, etc.
1329 | Ontology importedOntologyFromFetchedGraph = fetchedOntologyGraph.GetOntology();
1330 |
1331 | // Only proceed if the retrieved ontology has an IRI
1332 | if (importedOntologyFromFetchedGraph.IsNamed())
1333 | {
1334 | // Only proceed if we have not seen this fetched ontology before, otherwise we risk
1335 | // unecessary fetches and computation, and possibly import loops.
1336 | // Note that importedOntologies uses a custom comparer from DotNetRdfExtensions,
1337 | // since the Ontology class does not implement IComparable
1338 | if (!importedOntologies.Contains(importedOntologyFromFetchedGraph))
1339 | {
1340 | // Add the fetched ontology to the namespace prefix index
1341 | // (tacking on _1, _2, etc. to the shortname if it exists since before,
1342 | // since we need all prefix names to be unique).
1343 | string importedOntologyShortname = importedOntologyFromFetchedGraph.GetShortName();
1344 | int i = 1;
1345 | while (namespacePrefixes.ContainsValue(importedOntologyShortname))
1346 | {
1347 | importedOntologyShortname = importedOntologyShortname.Split('_')[0] + "_" + i;
1348 | i++;
1349 | }
1350 | namespacePrefixes.Add(importedOntologyFromFetchedGraph.GetIri(), importedOntologyShortname);
1351 |
1352 | // Merge the fetch graph with the joint ontology graph the tool operates on
1353 | _ontologyGraph.Merge(fetchedOntologyGraph);
1354 |
1355 | // Add imported ontology to the global imports collection and traverse its
1356 | // import hierarchy transitively
1357 | importedOntologies.Add(importedOntologyFromFetchedGraph);
1358 | foreach (Ontology subImport in importedOntologyFromFetchedGraph.Imports)
1359 | {
1360 | LoadImport(subImport);
1361 | }
1362 | }
1363 | }
1364 |
1365 | // Dispose graph before returning
1366 | fetchedOntologyGraph.Dispose();
1367 | }
1368 | }
1369 | }
1370 | }
--------------------------------------------------------------------------------
/OWL2OAS/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "OWL2OAS": {
4 | "commandName": "Project",
5 | "commandLineArgs": "-c DefaultExclude -u https://w3id.org/rec/full/3.1.1/ > %HOMEPATH%/OWL2OAS_OUTPUT.yaml"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/OWL2OAS/Relationship.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using VDS.RDF;
5 | using VDS.RDF.Ontology;
6 |
7 | namespace OWL2OAS
8 | {
9 | ///
10 | /// A representation of the relationships (data properties or object properties) that can be expressed on an OWL class.
11 | /// TODO Here be gremlins! Thid code is ugly and written in a hurry. It should probably be signficantly refactored and cleaned up,
12 | /// if not removed entirely. The relationship merge logic is particularly hairy.
13 | ///
14 | public class Relationship
15 | {
16 | public OntologyProperty Property
17 | { get; }
18 |
19 | public OntologyClass Target
20 | { get; set; }
21 |
22 | public int? MinimumCount
23 | { get; set; }
24 |
25 | public int? MaximumCount
26 | { get; set; }
27 |
28 | public int? ExactCount
29 | {
30 | get
31 | {
32 | if (MinimumCount.HasValue && MaximumCount.HasValue && MinimumCount == MaximumCount)
33 | {
34 | return MinimumCount;
35 | }
36 | return null;
37 | }
38 | set
39 | {
40 | MinimumCount = value;
41 | MaximumCount = value;
42 | }
43 | }
44 |
45 | public Relationship(OntologyProperty property, OntologyClass target)
46 | {
47 | if (!property.IsNamed() || !(target.IsNamed() || target.IsDatatype()))
48 | {
49 | throw new ArgumentException("Only named properties and named or datatype targets allowed.");
50 | }
51 | Property = property;
52 | Target = target;
53 | }
54 |
55 | ///
56 | /// Merge another Relationship into this one.
57 | /// This only works when the properties of the relationships are identical; else an exception is thrown.
58 | /// If the two relationships are to different target classes of which one subsumes the other, then the
59 | /// most specific relationship is kept. If the classes do not subsume one another, OWL thing is set as target.
60 | /// If the relationship targets are the same, then the narrower cardinality restrictions are kept.
61 | ///
62 | /// Another Relationship
63 | public void MergeWith(Relationship other)
64 | {
65 | if (!other.Property.Resource.Equals(this.Property.Resource))
66 | {
67 | throw new Exception("Properties are not identical");
68 | }
69 |
70 | // If target classes are not the same, then we need to keep the most specific one
71 | if (!other.Target.Resource.Equals(this.Target.Resource))
72 | {
73 | // If both target classes are both datatypes,
74 | if (this.Target.IsDatatype() && other.Target.IsDatatype())
75 | {
76 | // If the two targets are of same type but not the same, fall back to rdf:Literal
77 | if (
78 | (this.Target.IsEnumerationDatatype() && other.Target.IsEnumerationDatatype()) ||
79 | (this.Target.IsSimpleXsdWrapper() && other.Target.IsSimpleXsdWrapper()) ||
80 | (this.Target.IsXsdDatatype() && other.Target.IsXsdDatatype()))
81 | {
82 | IGraph targetsGraph = this.Target.Graph;
83 | IUriNode rdfLiteral = targetsGraph.CreateUriNode(VocabularyHelper.RDFS.Literal);
84 | this.Target = new OntologyClass(rdfLiteral, targetsGraph);
85 | this.MinimumCount = null;
86 | this.MaximumCount = null;
87 | return;
88 | }
89 | else
90 | {
91 | // Preference order is enumeration, custom xsd wrapper type, built-in xsd type
92 | List relationshipCandidates = new List() { this, other };
93 | relationshipCandidates.OrderBy(candidate => !candidate.Target.IsEnumerationDatatype())
94 | .ThenBy(candidate => !candidate.Target.IsSimpleXsdWrapper())
95 | .ThenBy(candidate => !candidate.Target.IsXsdDatatype());
96 | if (relationshipCandidates.First() == this)
97 | {
98 | return;
99 | }
100 | else
101 | {
102 | this.Target = other.Target;
103 | this.MinimumCount = other.MinimumCount;
104 | this.MaximumCount = other.MaximumCount;
105 | return;
106 | }
107 | }
108 | }
109 |
110 | // If this relationship has the more specific target class, keep it as-is, i.e., return
111 | if (this.Target.SuperClassesWithOwlThing().Contains(other.Target))
112 | {
113 | return;
114 | }
115 | // If the other relationhip has the more specific target class, keep it instead and return
116 | else if (other.Target.SuperClassesWithOwlThing().Contains(this.Target))
117 | {
118 | this.Target = other.Target;
119 | this.MinimumCount = other.MinimumCount;
120 | this.MaximumCount = other.MaximumCount;
121 | return;
122 | }
123 | // The classes do not subsume one another; fall back to OWL:Thing as target class
124 | else
125 | {
126 | IGraph targetsGraph = this.Target.Graph;
127 | IUriNode owlThing = targetsGraph.CreateUriNode(VocabularyHelper.OWL.Thing);
128 | this.Target = new OntologyClass(owlThing, targetsGraph);
129 | this.MinimumCount = null;
130 | this.MaximumCount = null;
131 | return;
132 | }
133 | }
134 |
135 | // If either restriction has an exact count, then that is the most specific restriction possible and min/max need not be inspected
136 | if (ExactCount.HasValue || other.ExactCount.HasValue)
137 | {
138 | // If both restrictions have exact values they either diverge (e.g., caused by an inconsistent ontology) or they converge (no change is required)
139 | if (ExactCount.HasValue && other.ExactCount.HasValue)
140 | {
141 | if (ExactCount.Value != other.ExactCount.Value)
142 | {
143 | throw new Exception("Conflicting ExactCounts");
144 | }
145 | // The exact value is identical; simply return
146 | return;
147 | }
148 | // Assign exact count from other only if our own is null, else keep our old exactcount
149 | ExactCount ??= other.ExactCount.Value;
150 | return;
151 | }
152 |
153 | int? newMinimum = new int?();
154 | if (MinimumCount.HasValue || other.MinimumCount.HasValue)
155 | {
156 | // If both have minimum counts, keep the larger one
157 | if (MinimumCount.HasValue && other.MinimumCount.HasValue)
158 | {
159 | newMinimum = MinimumCount > other.MinimumCount ? MinimumCount : other.MinimumCount;
160 | }
161 | else
162 | {
163 | // Else, keep whichever is non-null
164 | newMinimum = MinimumCount.HasValue ? MinimumCount : other.MinimumCount;
165 | }
166 | }
167 |
168 | int? newMaximum = new int?();
169 | if (MaximumCount.HasValue || other.MaximumCount.HasValue)
170 | {
171 | // If both have maximum counts, keep the smaller one
172 | if (MaximumCount.HasValue && other.MaximumCount.HasValue)
173 | {
174 | newMaximum = MaximumCount < other.MaximumCount ? MaximumCount : other.MaximumCount;
175 | }
176 | else
177 | {
178 | // Else, keep whichever is non-null
179 | newMaximum = MaximumCount.HasValue ? MaximumCount : other.MaximumCount;
180 | }
181 | }
182 |
183 | // At this point newMinimum is maximized or null and newMaximum is minimized or null
184 | // If they are inconsistent, i.e., newMinimum is larger than new Maximum, the model is inconsistent;
185 | // throw an error; else store
186 | if (newMinimum.HasValue && newMaximum.HasValue && newMinimum > newMaximum)
187 | {
188 | throw new Exception("Resulting min > resulting max");
189 | }
190 | MinimumCount = newMinimum;
191 | MaximumCount = newMaximum;
192 | }
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/OWL2OAS/VocabularyHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace OWL2OAS.VocabularyHelper
4 | {
5 | public static class O2O
6 | {
7 | public static readonly Uri included = new Uri("https://karlhammar.com/owl2oas/o2o.owl#included");
8 | public static readonly Uri endpoint = new Uri("https://karlhammar.com/owl2oas/o2o.owl#endpoint");
9 | }
10 |
11 | public static class DC
12 | {
13 | public static readonly Uri title = new Uri("http://purl.org/dc/elements/1.1/title");
14 | public static readonly Uri description = new Uri("http://purl.org/dc/elements/1.1/description");
15 | }
16 |
17 | public static class CC
18 | {
19 | public static readonly Uri license = new Uri("http://creativecommons.org/ns#license");
20 | }
21 |
22 | public static class RDFS
23 | {
24 | public static readonly Uri label = new Uri("http://www.w3.org/2000/01/rdf-schema#label");
25 | public static readonly Uri subClassOf = new Uri("http://www.w3.org/2000/01/rdf-schema#subClassOf");
26 | public static readonly Uri Datatype = new Uri("http://www.w3.org/2000/01/rdf-schema#Datatype");
27 | public static readonly Uri Literal = new Uri("http://www.w3.org/2000/01/rdf-schema#Literal");
28 | }
29 |
30 | public static class OWL
31 | {
32 | public static readonly Uri Thing = new Uri("http://www.w3.org/2002/07/owl#Thing");
33 | public static readonly Uri Restriction = new Uri("http://www.w3.org/2002/07/owl#Restriction");
34 | public static readonly Uri FunctionalProperty = new Uri("http://www.w3.org/2002/07/owl#FunctionalProperty");
35 | public static readonly Uri versionIRI = new Uri("http://www.w3.org/2002/07/owl#versionIRI");
36 | public static readonly Uri deprecated = new Uri("http://www.w3.org/2002/07/owl#deprecated");
37 | public static readonly Uri oneOf = new Uri("http://www.w3.org/2002/07/owl#oneOf");
38 | public static readonly Uri annotatedSource = new Uri("http://www.w3.org/2002/07/owl#annotatedSource");
39 | public static readonly Uri annotatedProperty = new Uri("http://www.w3.org/2002/07/owl#annotatedProperty");
40 | public static readonly Uri annotatedTarget = new Uri("http://www.w3.org/2002/07/owl#annotatedTarget");
41 |
42 | #region Restrictions
43 | public static readonly Uri onProperty = new Uri("http://www.w3.org/2002/07/owl#onProperty");
44 | public static readonly Uri onClass = new Uri("http://www.w3.org/2002/07/owl#onClass");
45 | public static readonly Uri cardinality = new Uri("http://www.w3.org/2002/07/owl#cardinality");
46 | public static readonly Uri qualifiedCardinality = new Uri("http://www.w3.org/2002/07/owl#qualifiedCardinality");
47 | public static readonly Uri allValuesFrom = new Uri("http://www.w3.org/2002/07/owl#allValuesFrom");
48 | public static readonly Uri someValuesFrom = new Uri("http://www.w3.org/2002/07/owl#someValuesFrom");
49 | public static readonly Uri minCardinality = new Uri("http://www.w3.org/2002/07/owl#minCardinality");
50 | public static readonly Uri minQualifiedCardinality = new Uri("http://www.w3.org/2002/07/owl#minQualifiedCardinality");
51 | public static readonly Uri maxCardinality = new Uri("http://www.w3.org/2002/07/owl#maxCardinality");
52 | public static readonly Uri maxQualifiedCardinality = new Uri("http://www.w3.org/2002/07/owl#maxQualifiedCardinality");
53 | #endregion
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # The OWL2OAS Converter
3 |
4 | **Author:** [Karl Hammar](https://karlhammar.com)
5 |
6 | This is a converter for translating [OWL ontologies](https://www.w3.org/TR/owl2-overview/) into
7 | [OpenAPI Specification](https://swagger.io/specification/) documents. OWL is a formal language for expressing concepts and their
8 | relations on the web, based on description logic. OpenAPI Specification is a standard for describing REST endpoints and operations.
9 | This tool generates REST endpoints for the classes (i.e., concepts) declared in an ontology, and generates [JSON-LD](https://json-ld.org)
10 | schemas for those classes based on the object and data properties that they are linked to in the ontology (either via
11 | rdfs:domain/rdfs:range, or via property restrictions).
12 |
13 | ## Usage
14 |
15 | The generated OAS document is printed to stdout.
16 |
17 | To translate a local file, use the `-f`option, as follows:
18 |
19 | ```
20 | ./OWL2OAS -f /path/to/ontology.rdf
21 | ```
22 |
23 | To translate a remote file, use the `-u` option, as follows:
24 | ```
25 | ./OWL2OAS -u http://example.com/path/to/ontology.rdf
26 | ```
27 |
28 | ## Options
29 |
30 | ```
31 | -c, --ClassInclusionPolicy (Default: DefaultInclude) Whether to include
32 | all classes by default (overridden by
33 | o2o:included annotation). Valid options:
34 | DefaultInclude or DefaultExclude.
35 |
36 | -p, --PropertyInclusionPolicy (Default: DefaultInclude) Whether to include
37 | all properties by default (overridden by
38 | o2o:included annotation). Valid options:
39 | DefaultInclude or DefaultExclude.
40 |
41 | -n, --no-imports Sets program to not follow owl:Imports
42 | declarations.
43 |
44 | -s, --server (Default: http://localhost:8080/) The server
45 | URL (where presumably an API implementation
46 | is running).
47 |
48 | -f, --file-path Required. The path to the on-disk root
49 | ontology file to translate.
50 |
51 | -u, --uri-path Required. The URI of the root ontology file
52 | to translate.
53 |
54 | --help Display this help screen.
55 |
56 | --version Display version information.
57 | ```
58 |
59 | ## Entity inclusion policy
60 |
61 | By default we include all classes and properties found in the parsed ontologies. If you want to specifically exclude some entity from the generated output, apply the `https://karlhammar.com/owl2oas/o2o.owl#included` annotation property on it, with the boolean value `false`. If you want to reverse this inclusion policy, for classes or properties, use the command line options `--ClassInclusionPolicy` or `--PropertyInclusionPolicy` with the value `DefaultExclude`; then, *only* the entities of that kind which have been tagged with `https://karlhammar.com/owl2oas/o2o.owl#included` and the value `true` will be included.
62 |
63 | ## OWL Entity Equivalence
64 |
65 | We do not support equivalence between classes or between properties. Strange things will probably happen if you run this tool over ontologies that depend on equivalence axioms. Feel free to try it out though!
66 |
67 | ## Examples
68 |
69 | See the [examples directory](https://github.com/hammar/OWL2OAS/tree/master/OWL2OAS/examples).
70 |
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-slate
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # OWL2OAS Documentation
2 |
3 | This is a placeholder.
4 |
--------------------------------------------------------------------------------