Skip to content

Instantly share code, notes, and snippets.

@tpluscode
Last active August 9, 2021 07:45
Show Gist options
  • Save tpluscode/5d9c6983004c1c9ec91f to your computer and use it in GitHub Desktop.
Save tpluscode/5d9c6983004c1c9ec91f to your computer and use it in GitHub Desktop.
OWL => C# T4 template
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<!-- This way the FOAF.tt will generate a Foaf class with fields for prefix, base URI and all vocabulary members -->
<Compile Include="Ontologies\FOAF.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>FOAF.tt</DependentUpon>
</Compile>
</ItemGroup>
</Project>
<# // Create exact file grouped with your OWL/XML file. See example below of csproj structure #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ import namespace="System.Xml" #>
<#@ include file="Vocabulary.tt" #><#
CreateVocabulary();
#>
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ output extension=".cs" #><#+
public void CreateVocabulary()
{
string prefix;
string ns;
string uri;
string className;
XmlNamespaceManager namespaceManager;
bool isRDFMode;
XmlDocument ontology=Initialize(out prefix,out uri,out ns,out className,out namespaceManager,out isRDFMode);
#>
// <auto-generated />
using System;
namespace <#=ns #>
{
/// <summary><#=GetTitle(ontology.DocumentElement,namespaceManager,isRDFMode) #> (<#=uri #>).</summary>
public static partial class <#=className #>
{
/// <summary>
/// <#=className.ToLower() #>
/// </summary>
public const string Prefix="<#=className.ToLower() #>";
/// <summary>
/// <#=uri #>
/// </summary>
public const string BaseUri="<#=uri #>";
<#+
IEnumerable<XmlNode> classes=GetNodes(ontology.DocumentElement,namespaceManager,"rdfs:Class",isRDFMode).Union(
GetNodes(ontology.DocumentElement,namespaceManager,"owl:Class",isRDFMode));
foreach (XmlNode classNode in classes)
{#>
/// <summary>
/// <#=GetDescription(classNode, namespaceManager) #>
/// </summary>
public const string <#=GetTermName(classNode,namespaceManager) #> = BaseUri + "<#=GetTermName(classNode,namespaceManager) #>";
<#+ }
IEnumerable<XmlNode> properties=GetNodes(ontology.DocumentElement,namespaceManager,"rdf:Property",isRDFMode).Union(
GetNodes(ontology.DocumentElement,namespaceManager,"owl:DatatypeProperty",isRDFMode)).Union(
GetNodes(ontology.DocumentElement,namespaceManager,"owl:ObjectProperty",isRDFMode)).Union(
GetNodes(ontology.DocumentElement,namespaceManager,"owl:AnnotationProperty",isRDFMode)).Union(
GetNodes(ontology.DocumentElement,namespaceManager,"hydra:Link",isRDFMode));
foreach (XmlNode propertyNode in properties)
{#>
/// <summary>
/// <#=GetDescription(propertyNode, namespaceManager) #>
/// </summary>
public const string <#=GetTermName(propertyNode,namespaceManager) #> = BaseUri + "<#=GetTermName(propertyNode,namespaceManager).Replace("@",System.String.Empty) #>";
<#+ }
string rdf=namespaceManager.LookupNamespace("rdf");
foreach (XmlNode classNode in classes)
{
string typeName=StandardizeTerm((classNode.Attributes["about",rdf]!=null?classNode.Attributes["about",rdf]:classNode.Attributes["ID",rdf]).Value,namespaceManager);
if ((typeName!="rdfs:Class")&&(typeName!="rdf:Property")&&(typeName!="owl:Class")&&((!typeName.StartsWith("owl:"))&&(!typeName.EndsWith("Property"))))
{
IEnumerable<XmlNode> namedInstances=GetNodes(ontology.DocumentElement,namespaceManager,typeName,isRDFMode);
foreach (XmlNode namedInstanceNode in namedInstances)
{#>
/// <summary>
/// <#=GetDescription(namedInstanceNode, namespaceManager) #>
/// </summary>
public const string <#=GetTermName(namedInstanceNode,namespaceManager) #> = BaseUri + "<#=GetTermName(namedInstanceNode,namespaceManager).Replace("@",System.String.Empty) #>";
<#+ }
}
}
#>
}
}<#+
}
private XmlDocument Initialize(out string prefix,out string uri,out string ns,out string className,out XmlNamespaceManager namespaceManager,out bool isRDFMode)
{
prefix=System.String.Empty;
uri=System.String.Empty;
ns=System.String.Empty;
className=System.String.Empty;
namespaceManager=null;
isRDFMode=false;
string fileName;
string rdfFile=System.IO.Path.ChangeExtension(this.Host.TemplateFile, "rdf");
string owlFile=System.IO.Path.ChangeExtension(this.Host.TemplateFile, "owl");
if(System.IO.File.Exists(rdfFile))
{
fileName = rdfFile;
}
else if(System.IO.File.Exists(owlFile))
{
fileName = owlFile;
}
else
{
throw new Exception(string.Format("Cannot find ontology file. Tried {0} and {1}", owlFile, rdfFile));
}
XmlDocument result=new XmlDocument();
result.Load(fileName);
prefix=System.IO.Path.GetFileNameWithoutExtension(this.Host.TemplateFile);
className=(prefix.Count(item => Char.IsUpper(item))==prefix.Length?prefix.Substring(0,1).ToUpper()+prefix.Substring(1).ToLower():prefix);
prefix=prefix.ToLower();
IDictionary<string,string> namespaces=result.DocumentElement.CreateNavigator().GetNamespacesInScope(XmlNamespaceScope.All);
uri=namespaces[prefix];
var nsHint = System.Runtime.Remoting.Messaging.CallContext.LogicalGetData("NamespaceHint");
ns = (nsHint ?? "Vocab").ToString();
isRDFMode=(System.IO.Path.GetExtension(fileName).Substring(1).ToLower()=="rdf");
namespaceManager=new XmlNamespaceManager(result.NameTable);
namespaceManager.AddNamespace("rdf","http://www.w3.org/1999/02/22-rdf-syntax-ns#");
namespaceManager.AddNamespace("rdfs","http://www.w3.org/2000/01/rdf-schema#");
namespaceManager.AddNamespace("owl","http://www.w3.org/2002/07/owl#");
namespaceManager.AddNamespace("dc","http://purl.org/dc/elements/1.1/");
namespaceManager.AddNamespace("dcterms","http://purl.org/dc/terms/");
namespaceManager.AddNamespace("skos","http://www.w3.org/2004/02/skos/core#");
namespaceManager.AddNamespace("hydra","http://www.w3.org/ns/hydra/core#");
foreach (KeyValuePair<string,string> @namespace in namespaces)
{
if (System.String.IsNullOrEmpty(namespaceManager.LookupPrefix(@namespace.Value)))
{
namespaceManager.AddNamespace(@namespace.Key,@namespace.Value);
}
}
namespaceManager.AddNamespace("base",namespaceManager.LookupNamespace(prefix));
return result;
}
private string GetTermName(XmlNode node,XmlNamespaceManager namespaceManager)
{
string result=null;
string rdf=namespaceManager.LookupNamespace("rdf");
XmlAttribute attribute=node.Attributes["about",rdf];
if (attribute==null)
{
result=namespaceManager.LookupNamespace("base")+node.Attributes["ID",rdf].Value;
}
else
{
result=attribute.Value;
}
if (result.IndexOf('#')!=-1)
{
result=result.Substring(result.IndexOf('#')+1);
}
else if(result.Contains(":"))
{
result=new Uri(result).Segments.Last();
}
string[] keywords=new string[] { "object","class","readonly","abstract","private","default","as","using","event" };
if (keywords.Contains(result))
{
result="@"+result;
}
return result;
}
private string GetDescription(XmlNode node,XmlNamespaceManager namespaceManager)
{
string result = null;
XmlNode title=node.SelectSingleNode("*[(self::rdfs:comment) or (self::skos:definition)]",namespaceManager);
if (title!=null)
{
result=title.InnerText;
}
else
{
XmlAttribute attribute=node.Attributes["comment",namespaceManager.LookupNamespace("rdfs")]??(node.Attributes["definition",namespaceManager.LookupNamespace("skos")]);
if (attribute!=null)
{
result=attribute.Value;
}
}
result = result ?? GetTermName(node, namespaceManager);
if(result != null)
{
result = result.Replace(Environment.NewLine, " ").Replace("\n", " ");
}
return result;
}
private string GetTitle(XmlNode documentElement,XmlNamespaceManager namespaceManager,bool isRDFMode)
{
XmlNode ontology=documentElement.SelectSingleNode((isRDFMode?"rdf:Description[rdf:type[@rdf:resource=\"http://www.w3.org/2002/07/owl#Ontology\"]]":"owl:Ontology"),namespaceManager);
string result=GetTermName(ontology,namespaceManager);
XmlNode title=ontology.SelectSingleNode("*[(self::rdfs:label) or (self::dc:title) or (self::dcterms:title)]",namespaceManager);
if (title!=null)
{
result=title.InnerText;
}
else
{
XmlAttribute attribute=ontology.Attributes["label",namespaceManager.LookupNamespace("rdfs")]??(ontology.Attributes["title",namespaceManager.LookupNamespace("dc")]??documentElement.Attributes["title",namespaceManager.LookupNamespace("dcterms")]);
if (attribute!=null)
{
result=attribute.Value;
}
}
return result;
}
private IEnumerable<XmlNode> GetNodes(XmlNode node,XmlNamespaceManager namespaceManager,string name,bool isRDFMode)
{
name=StandardizeTerm(name,namespaceManager);
XmlNodeList result=null;
if (isRDFMode)
{
result=node.SelectNodes("rdf:Description[@rdf:about and rdf:type[@rdf:resource=\""+namespaceManager.LookupNamespace(name.Split(':')[0])+name.Split(':')[1]+"\"]]",namespaceManager);
}
else
{
result=node.SelectNodes(name,namespaceManager);
}
string rdf = namespaceManager.LookupNamespace("rdf");
string ns = namespaceManager.LookupNamespace("base");
return from item in result.Cast<XmlNode>()
where HasAbout(item, rdf, ns) || HasID(item, rdf)
select item;
}
private static bool HasAbout(XmlNode item, string rdf, string ns)
{
var about = item.Attributes["about", rdf];
var isRelativeUri = about.Value.Contains(":") == false;
return about.Value.StartsWith(ns) || isRelativeUri;
}
private static bool HasID(XmlNode item, string rdf)
{
return item.Attributes["ID", rdf] != null;
}
private string StandardizeTerm(string term,XmlNamespaceManager namespaceManager)
{
if ((!Regex.IsMatch(term,"[^:]+://"))&&(!Regex.IsMatch(term,"[^:]+:[a-zA-Z]")))
{
term=namespaceManager.LookupNamespace("base")+term;
}
namespaceManager.Cast<string>().Where(item => item.Length>0).Select(item => term=term.Replace(namespaceManager.LookupNamespace(item),item+":")).ToList();
return term;
}
#>
@tpluscode
Copy link
Author

I never really attempted to make the script reusable. It is part of the build and the Rdf.Vocabularies package is only a way to distribute what was generated

TL;DR; of what's happening:

  1. run scripty during build: Rdf.Vocabularies/Rdf.Vocabularies.csproj#L26
  2. Rdf.Vocabularies/Vocabs.csx is a meta script which simply run transformation for every source file
    • can be any format parsed by dotNetRDF
    • note there are some parameters because vocabulary files are not made equals
  3. Rdf.Vocabularies/MakeVocab.csx script iterated the vocabulary terms and generates a C# class

You should have no issues just reusing those scripts in your project. Or you could submit a PR to the package if yours is a shared vocabulary.

@binhtq1987
Copy link

Thanks so much, Tomasz!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment