Skip to content

Commit 1789b95

Browse files
ihermanTallTed
andauthored
Update the formal vocabulary files for v2.
* Updated the class description per @pchampin * Updated the descriptions * JsonSchemaValidator2018 is a subclass of CredentialSchema * Update vocab/credentials/v2/README.md * Removed the version entry from json * Removed the leftovers of context from the vocabulary * removed external context and renamed the files Co-authored-by: Ted Thibodeau Jr <tthibodeau@openlinksw.com>
1 parent 5f62aed commit 1789b95

File tree

8 files changed

+1526
-0
lines changed

8 files changed

+1526
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
.idea/vc-data-model.iml
66
.idea/vcs.xml
77
.idea/workspace.xml
8+
.vscode

vocab/credentials/v2/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Updating vocabulary
2+
3+
Vocabulary definitions are managed in `vocab.csv`. Add or change entries within this file. Regenerate `vocabulary.ttl`, `vocabulary.jsonld`, and `vocabulary.html` by running `mk_vocab.rb`.

vocab/credentials/v2/mk_vocab.rb

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
#! /usr/bin/env ruby
2+
# Parse vocabulary definition in CSV to generate Context+Vocabulary in JSON-LD or Turtle
3+
4+
require 'getoptlong'
5+
require 'csv'
6+
require 'json'
7+
require 'erubis'
8+
9+
class Vocab
10+
JSON_STATE = JSON::State.new(
11+
:indent => " ",
12+
:space => " ",
13+
:space_before => "",
14+
:object_nl => "\n",
15+
:array_nl => "\n"
16+
)
17+
18+
TITLE = "Verifiable Credentials Vocabulary v2.0".freeze
19+
DESCRIPTION = %(This document describes the RDFS vocabulary description used for Verifiable Credentials [[VC-DATA-MODEL]].).freeze
20+
attr_accessor :prefixes, :terms, :properties, :classes, :contexts, :instances, :datatypes,
21+
:imports, :date, :commit, :seeAlso
22+
23+
def initialize
24+
path = File.expand_path("../vocab.csv", __FILE__)
25+
csv = CSV.new(File.open(path))
26+
@prefixes, @terms, @properties, @classes, @datatypes, @instances = {}, {}, {}, {}, {}, {}
27+
@contexts, @imports, @seeAlso = [], [], []
28+
#git_info = %x{git log -1 #{path}}.split("\n")
29+
#@commit = "https://github.com/w3c/vc-vocab/commit/" + (git_info[0] || 'uncommitted').split.last
30+
date_line = nil #git_info.detect {|l| l.start_with?("Date:")}
31+
@date = Date.parse((date_line || Date.today.to_s).split(":",2).last).strftime("%Y-%m-%d")
32+
33+
columns = []
34+
csv.shift.each_with_index {|c, i| columns[i] = c.to_sym if c}
35+
36+
csv.sort_by(&:to_s).each do |line|
37+
entry = {}
38+
# Create entry as object indexed by symbolized column name
39+
line.each_with_index {|v, i| entry[columns[i]] = v ? v.gsub("\r", "\n").gsub("\\", "\\\\") : nil}
40+
41+
case entry[:type]
42+
when '@context' then (@contexts ||= []) << entry[:subClassOf]
43+
when 'prefix' then @prefixes[entry[:id]] = entry
44+
when 'term' then @terms[entry[:id]] = entry
45+
when 'rdf:Property' then @properties[entry[:id]] = entry
46+
when 'rdfs:Class' then @classes[entry[:id]] = entry
47+
when 'rdfs:Datatype' then @datatypes[entry[:id]] = entry
48+
when 'owl:imports' then @imports << entry[:subClassOf]
49+
when 'rdfs:seeAlso' then @seeAlso << entry[:subClassOf]
50+
else @instances[entry[:id]] = entry
51+
end
52+
end
53+
54+
end
55+
56+
def namespaced(term)
57+
term.include?(":") ? term : "cred:#{term}"
58+
end
59+
60+
# The full JSON-LD file without the RDFS parts
61+
def to_context
62+
ctx = JSON.parse(to_jsonld)
63+
ctx.delete('@graph')
64+
ctx.to_json(JSON_STATE)
65+
end
66+
67+
def to_jsonld
68+
rdfs_context = {}
69+
context = ::JSON.parse %({
70+
"dc": "http://purl.org/dc/terms/",
71+
"owl": "http://www.w3.org/2002/07/owl#",
72+
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
73+
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
74+
"dc:title": {"@container": "@language"},
75+
"dc:description": {"@container": "@language"},
76+
"dc:date": {"@type": "xsd:date"},
77+
"rdfs:comment": {"@container": "@language"},
78+
"rdfs:domain": {"@type": "@id"},
79+
"rdfs:label": {"@container": "@language"},
80+
"rdfs:range": {"@type": "@id"},
81+
"rdfs:seeAlso": {"@type": "@id"},
82+
"rdfs:subClassOf": {"@type": "@id"},
83+
"rdfs:subPropertyOf": {"@type": "@id"},
84+
"owl:equivalentClass": {"@type": "@vocab"},
85+
"owl:equivalentProperty": {"@type": "@vocab"},
86+
"owl:oneOf": {"@container": "@list", "@type": "@vocab"},
87+
"owl:imports": {"@type": "@id"},
88+
"owl:versionInfo": {"@type": "@id"},
89+
"owl:inverseOf": {"@type": "@vocab"},
90+
"owl:unionOf": {"@type": "@vocab", "@container": "@list"},
91+
"rdfs_classes": {"@reverse": "rdfs:isDefinedBy", "@type": "@id"},
92+
"rdfs_properties": {"@reverse": "rdfs:isDefinedBy", "@type": "@id"},
93+
"rdfs_datatypes": {"@reverse": "rdfs:isDefinedBy", "@type": "@id"},
94+
"rdfs_instances": {"@reverse": "rdfs:isDefinedBy", "@type": "@id"}
95+
})
96+
rdfs_classes, rdfs_properties, rdfs_datatypes, rdfs_instances = [], [], [], []
97+
98+
prefixes.each do |id, entry|
99+
context[id] = entry[:subClassOf]
100+
end
101+
102+
terms.each do |id, entry|
103+
next if entry[:@type] == '@null'
104+
context[id] = if [:@container, :@type].any? {|k| entry[k]}
105+
{'@id' => entry[:subClassOf]}.
106+
merge(entry[:@container] ? {'@container' => entry[:@container]} : {}).
107+
merge(entry[:@type] ? {'@type' => entry[:@type]} : {})
108+
else
109+
entry[:subClassOf]
110+
end
111+
end
112+
113+
classes.each do |id, entry|
114+
term = entry[:term] || id
115+
# context[term] = namespaced(id) unless entry[:@type] == '@null'
116+
117+
# Class definition
118+
node = {
119+
'@id' => namespaced(id),
120+
'@type' => 'rdfs:Class',
121+
'rdfs:label' => {"en" => entry[:label].to_s},
122+
'rdfs:comment' => {"en" => entry[:comment].to_s},
123+
}
124+
node['rdfs:subClassOf'] = namespaced(entry[:subClassOf]) if entry[:subClassOf]
125+
rdfs_classes << node
126+
end
127+
128+
properties.each do |id, entry|
129+
defn = {"@id" => namespaced(id)}
130+
case entry[:range]
131+
when "xsd:string" then defn['@language'] = nil
132+
when /xsd:/ then defn['@type'] = entry[:range].split(',').first
133+
when nil,
134+
'rdfs:Literal' then ;
135+
else defn['@type'] = '@id'
136+
end
137+
138+
defn['@container'] = entry[:@container] if entry[:@container]
139+
defn['@type'] = entry[:@type] if entry[:@type]
140+
141+
term = entry[:term] || id
142+
# context[term] = defn unless entry[:@type] == '@null'
143+
144+
# Property definition
145+
node = {
146+
'@id' => namespaced(id),
147+
'@type' => 'rdf:Property',
148+
'rdfs:label' => {"en" => entry[:label].to_s},
149+
'rdfs:comment' => {"en" => entry[:comment].to_s},
150+
}
151+
node['rdfs:subPropertyOf'] = namespaced(entry[:subClassOf]) if entry[:subClassOf]
152+
153+
domains = entry[:domain].to_s.split(',')
154+
case domains.length
155+
when 0 then ;
156+
when 1 then node['rdfs:domain'] = namespaced(domains.first)
157+
else node['rdfs:domain'] = {'owl:unionOf' => domains.map {|d| namespaced(d)}}
158+
end
159+
160+
ranges = entry[:range].to_s.split(',')
161+
case ranges.length
162+
when 0 then ;
163+
when 1 then node['rdfs:range'] = namespaced(ranges.first)
164+
else node['rdfs:range'] = {'owl:unionOf' => ranges.map {|r| namespaced(r)}}
165+
end
166+
167+
rdfs_properties << node
168+
end
169+
170+
datatypes.each do |id, entry|
171+
# context[id] = namespaced(id) unless entry[:@type] == '@null'
172+
173+
# Datatype definition
174+
node = {
175+
'@id' => namespaced(id),
176+
'@type' => 'rdfs:Datatype',
177+
'rdfs:label' => {"en" => entry[:label].to_s},
178+
'rdfs:comment' => {"en" => entry[:comment].to_s},
179+
}
180+
node['rdfs:subClassOf'] = namespaced(entry[:subClassOf]) if entry[:subClassOf]
181+
rdfs_datatypes << node
182+
end
183+
184+
instances.each do |id, entry|
185+
# context[id] = namespaced(id) unless entry[:@type] == '@null'
186+
# Instance definition
187+
rdfs_instances << {
188+
'@id' => namespaced(id),
189+
'@type' => entry[:type],
190+
'rdfs:label' => {"en" => entry[:label].to_s},
191+
'rdfs:comment' => {"en" => entry[:comment].to_s},
192+
}
193+
end
194+
195+
# Use separate rdfs context so as not to polute the context.
196+
ontology = {
197+
"@context" => rdfs_context,
198+
"@id" => prefixes["cred"][:subClassOf],
199+
"@type" => "owl:Ontology",
200+
"dc:title" => {"en" => TITLE},
201+
"dc:description" => {"en" => DESCRIPTION},
202+
"dc:date" => date,
203+
"owl:imports" => imports,
204+
#"owl:versionInfo" => commit,
205+
"rdfs:seeAlso" => seeAlso,
206+
"rdfs_classes" => rdfs_classes,
207+
"rdfs_properties" => rdfs_properties,
208+
"rdfs_datatypes" => rdfs_datatypes,
209+
"rdfs_instances" => rdfs_instances
210+
}.delete_if {|k,v| Array(v).empty?}
211+
212+
# Imported contexts
213+
context = contexts + [context]unless contexts.empty?
214+
215+
{
216+
"@context" => context,
217+
"@graph" => ontology
218+
}.to_json(JSON_STATE)
219+
end
220+
221+
def to_html
222+
json = JSON.parse(to_jsonld)
223+
eruby = Erubis::Eruby.new(File.read("template.html"))
224+
eruby.result(ont: json['@graph'], context: json['@context'].is_a?(Array) ? json['@context'].last : json['@context'])
225+
end
226+
227+
def to_ttl
228+
output = []
229+
230+
prefixes = {
231+
"dc" => {subClassOf: "http://purl.org/dc/terms/"},
232+
"owl" => {subClassOf: "http://www.w3.org/2002/07/owl#"},
233+
"rdf" => {subClassOf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#"},
234+
"rdfs" => {subClassOf: "http://www.w3.org/2000/01/rdf-schema#"},
235+
}.merge(@prefixes).dup
236+
prefixes.each {|id, entry| output << "@prefix #{id}: <#{entry[:subClassOf]}> ."}
237+
238+
output << "\n# CSVM Ontology definition"
239+
output << "cred: a owl:Ontology;"
240+
output << %( dc:title "#{TITLE}"@en;)
241+
output << %( dc:description """#{DESCRIPTION}"""@en;)
242+
output << %( dc:date "#{date}"^^xsd:date;)
243+
#output << %( dc:imports #{imports.map {|i| '<' + i + '>'}.join(", ")};)
244+
#output << %( owl:versionInfo <#{commit}>;)
245+
output << %( rdfs:seeAlso #{seeAlso.map {|i| '<' + i + '>'}.join(", ")};)
246+
output << "."
247+
248+
output << "\n# Class definitions" unless @classes.empty?
249+
@classes.each do |id, entry|
250+
output << "cred:#{id} a rdfs:Class;"
251+
output << %( rdfs:label "#{entry[:label]}"@en;)
252+
output << %( rdfs:comment """#{entry[:comment]}"""@en;)
253+
output << %( rdfs:subClassOf #{namespaced(entry[:subClassOf])};) if entry[:subClassOf]
254+
output << %( rdfs:isDefinedBy cred: .)
255+
end
256+
257+
output << "\n# Property definitions" unless @properties.empty?
258+
@properties.each do |id, entry|
259+
output << "cred:#{id} a rdf:Property;"
260+
output << %( rdfs:label "#{entry[:label]}"@en;)
261+
output << %( rdfs:comment """#{entry[:comment]}"""@en;)
262+
output << %( rdfs:subPropertyOf #{namespaced(entry[:subClassOf])};) if entry[:subClassOf]
263+
domains = entry[:domain].to_s.split(',')
264+
case domains.length
265+
when 0 then ;
266+
when 1 then output << %( rdfs:domain #{namespaced(entry[:domain])};)
267+
else
268+
output << %( rdfs:domain [ owl:unionOf (#{domains.map {|d| namespaced(d)}.join(' ')})];)
269+
end
270+
271+
ranges = entry[:range].to_s.split(',')
272+
case ranges.length
273+
when 0 then ;
274+
when 1 then output << %( rdfs:range #{namespaced(entry[:range])};)
275+
else
276+
output << %( rdfs:range [ owl:unionOf (#{ranges.map {|d| namespaced(d)}.join(' ')})];)
277+
end
278+
output << %( rdfs:isDefinedBy cred: .)
279+
end
280+
281+
output << "\n# Datatype definitions" unless @datatypes.empty?
282+
@datatypes.each do |id, entry|
283+
output << "cred:#{id} a rdfs:Datatype;"
284+
output << %( rdfs:label "#{entry[:label]}"@en;)
285+
output << %( rdfs:comment """#{entry[:comment]}"""@en;)
286+
output << %( rdfs:subClassOf #{namespaced(entry[:subClassOf])};) if entry[:subClassOf]
287+
output << %( rdfs:isDefinedBy cred: .)
288+
end
289+
290+
output << "\n# Instance definitions" unless @instances.empty?
291+
@instances.each do |id, entry|
292+
output << "cred:#{id} a #{namespaced(entry[:type])};"
293+
output << %( rdfs:label "#{entry[:label]}"@en;)
294+
output << %( rdfs:comment """#{entry[:comment]}"""@en;)
295+
output << %( rdfs:isDefinedBy cred: .)
296+
end
297+
298+
output.join("\n")
299+
end
300+
end
301+
302+
options = {
303+
output: $stdout
304+
}
305+
306+
OPT_ARGS = [
307+
["--format", "-f", GetoptLong::REQUIRED_ARGUMENT,"Output format, default #{options[:format].inspect}"],
308+
["--output", "-o", GetoptLong::REQUIRED_ARGUMENT,"Output to the specified file path"],
309+
["--quiet", GetoptLong::NO_ARGUMENT, "Supress most output other than progress indicators"],
310+
["--help", "-?", GetoptLong::NO_ARGUMENT, "This message"]
311+
]
312+
def usage
313+
STDERR.puts %{Usage: #{$0} [options] URL ...}
314+
width = OPT_ARGS.map do |o|
315+
l = o.first.length
316+
l += o[1].length + 2 if o[1].is_a?(String)
317+
l
318+
end.max
319+
OPT_ARGS.each do |o|
320+
s = " %-*s " % [width, (o[1].is_a?(String) ? "#{o[0,2].join(', ')}" : o[0])]
321+
s += o.last
322+
STDERR.puts s
323+
end
324+
exit(1)
325+
end
326+
327+
opts = GetoptLong.new(*OPT_ARGS.map {|o| o[0..-2]})
328+
329+
opts.each do |opt, arg|
330+
case opt
331+
when '--format' then options[:format] = arg.to_sym
332+
when '--output' then options[:output] = File.open(arg, "w")
333+
when '--quiet' then options[:quiet] = true
334+
when '--help' then usage
335+
end
336+
end
337+
338+
vocab = Vocab.new
339+
case options[:format]
340+
when :context then options[:output].puts(vocab.to_context)
341+
when :jsonld then options[:output].puts(vocab.to_jsonld)
342+
when :ttl then options[:output].puts(vocab.to_ttl)
343+
when :html then options[:output].puts(vocab.to_html)
344+
else
345+
[:jsonld, :ttl, :html].each do |format|
346+
fn = {jsonld: "vocabulary.jsonld", ttl: "vocabulary.ttl", html: "vocabulary.html"}[format]
347+
File.open(fn, "w") do |output|
348+
output.puts(vocab.send("to_#{format}".to_sym))
349+
end
350+
end
351+
end

0 commit comments

Comments
 (0)