Skip to content

Commit

Permalink
Updated Ruby RDF vocabulary handling.
Browse files Browse the repository at this point in the history
Reworked vocabulary registration to work with recent upstream changes.
Updated local vocabularies.
Added a couple social vocabularies that we will be using in the future.
Added some tests (per issue #88).
  • Loading branch information
cdchapman committed Jan 17, 2022
1 parent e47d5b7 commit a00e54f
Show file tree
Hide file tree
Showing 50 changed files with 6,497 additions and 30,638 deletions.
17 changes: 9 additions & 8 deletions commands/gen-vocabs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def run
end

vocabs.each do |id, v|
next if v[:alias]
next if v[:alias] || v[:skip]

puts "Generate lib/vocabs/#{id}.rb"

Expand All @@ -39,23 +39,24 @@ def run
File.open("lib/vocabs/#{id}.rb_p", 'w') { |f| f.write v[:patch] }
cmd += " patch --patch-file lib/vocabs/#{id}.rb_p"
end
cmd += " serialize --uri '#{v[:uri]}' --output-format vocabulary"
cmd += " serialize --uri '#{v[:uri]}' --output-format vocabulary --ordered"
cmd += " --module-name #{v.fetch(:module_name, 'RDF::Vocab')}"
cmd += " --class-name #{v[:class_name] || id.to_s.upcase}"
cmd += ' --strict' if v.fetch(:strict, true)
cmd += " --extra #{URI.encode(v[:extra].to_json)}" if v[:extra]
cmd += ' --noDoc'
cmd += " --extra #{URI.encode_www_form_component(v[:extra].to_json)}" if v[:extra]
cmd += " -o lib/vocabs/#{id}.rb_t"
cmd += ' "' + v.fetch(:source, v[:uri]) + '"'

puts " #{cmd}"

begin
`#{cmd} && mv lib/vocabs/#{id}.rb_t lib/vocabs/#{id}.rb`
%x{#{cmd} && sed 's/\r//g' lib/vocabs/#{id}.rb_t > lib/vocabs/#{id}.rb}
rescue
require 'English'
puts "Failed to load #{id}: #{$ERROR_INFO.message}"
ensure
`rm -f lib/vocabs/#{id}.rb_t lib/vocabs/#{id}.rb_p`
%x{rm -f lib/vocabs/#{id}.rb_t lib/vocabs/#{id}.rb_p}
end
end
end
Expand Down Expand Up @@ -88,9 +89,9 @@ def self.env_for_site(site)
view_context = view_context_for(site)

{
items: Nanoc::ItemCollectionWithRepsView.new(site.items, view_context),
layouts: Nanoc::LayoutCollectionView.new(site.layouts, view_context),
config: Nanoc::ConfigView.new(site.config, view_context),
items: Nanoc::Core::ItemCollectionWithRepsView.new(site.items, view_context),
layouts: Nanoc::Core::LayoutCollectionView.new(site.layouts, view_context),
config: Nanoc::Core::ConfigView.new(site.config, view_context),
}
end
end
Expand Down
24 changes: 7 additions & 17 deletions etc/vocabs_additional.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ c4o:
uri: "http://purl.org/spar/c4o/"
source: "http://www.sparontologies.net/ontologies/c4o/source.ttl"

cert:
uri: "https://www.w3.org/ns/auth/cert#"
source: "http://www.w3.org/ns/auth/cert.n3"

co:
uri: "http://purl.org/co/"
source: "var/vocabs/collections.owl"
Expand All @@ -31,10 +27,6 @@ ctag:
uri: "http://commontag.org/ns#"
source: "var/vocabs/ctag_2009-06-08.n3"

dbo:
uri: "http://dbpedia.org/ontology/"
source: "http://mappings.dbpedia.org/server/ontology/dbpedia.owl"

dbr:
uri: "http://dbpedia.org/resource/"
strict: false
Expand Down Expand Up @@ -65,25 +57,25 @@ essglobal:
uri: "http://purl.org/essglobal/standard/activities/"
source: "http://www.maltas.org/ess/standard/activities.skos"
strict: false
class_name: "ESSGLOBALActivities"
class_name: "ESSGlobalActivities"

"essglobal_legalform":
uri: "http://purl.org/essglobal/standard/legal-form/"
source: "http://www.maltas.org/ess/standard/legal-form.skos"
strict: false
class_name: "ESSGLOBALLegalform"
class_name: "ESSGlobalLegalform"

"essglobal_qualifiers":
uri: "http://purl.org/essglobal/standard/qualifiers/"
source: "http://www.maltas.org/ess/standard/qualifiers.skos"
strict: false
class_name: "ESSGLOBALQualifiers"
class_name: "ESSGlobalQualifiers"

"essglobal_typeoflabour":
uri: "http://purl.org/essglobal/standard/type-of-labour/"
source: "http://www.maltas.org/ess/standard/type-of-labour.skos"
strict: false
class_name: "ESSGLOBALTypeoflabour"
class_name: "ESSGlobalTypeoflabour"

fabio:
uri: "http://purl.org/spar/fabio/"
Expand Down Expand Up @@ -111,9 +103,6 @@ profile:
rov:
uri: "http://www.w3.org/ns/regorg#"

sioct:
uri: "http://rdfs.org/sioc/types#"

vann:
uri: "http://purl.org/vocab/vann/"
source: "var/vocabs/vann.ttl"
Expand All @@ -133,16 +122,17 @@ pentandra:
uri: <%= "#{@config[:base_url]}#{@config[:company_root]}#" %>
source: <%= "#{@config[:output_dir]}#{@config[:static_root]}#{@config[:company_root]}/index.html" %>
strict: false
class_name: Pentandra

"pentandra_blog":
uri: <%= "#{@config[:base_url]}#{@config[:blog_root]}#" %>
source: <%= "#{@config[:output_dir]}#{@config[:static_root]}#{@config[:blog_root]}/index.html" %>
strict: false
class_name: PENTANDRABlog
class_name: PentandraBlog

"pentandra_website":
uri: <%= "#{@config[:base_url]}#" %>
source: <%= "#{@config[:output_dir]}#{@config[:static_root]}/index.html" %>
strict: false
class_name: PENTANDRAWebsite
class_name: PentandraWebsite
...
2 changes: 1 addition & 1 deletion layouts/blog/blogpost.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<% render('/default.*') do %>
<article class="group h-entry" prefix="<%= prefix_mappings_for(:sioct) %>" resource="#blogpost" typeof="cc:Work schema:BlogPosting sioct:BlogPost ctag:TaggedContent" property="schema:mainEntity">
<article class="group h-entry" prefix="<%= prefix_mappings_for(:sioctypes) %>" resource="#blogpost" typeof="cc:Work schema:BlogPosting sioctypes:BlogPost ctag:TaggedContent" property="schema:mainEntity">
<header class="section">
<h1 class="p-name" property="dc:title schema:headline" id="title"><%= @item[:title] %></h1>
<div class="group meta" id="document-context">
Expand Down
2 changes: 1 addition & 1 deletion layouts/blog/note.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<% render('/default.*') do %>
<article class="group h-entry" prefix="<%= prefix_mappings_for(:sioct) %>" resource="#blogpost" typeof="cc:Work schema:BlogPosting sioct:BlogPost ctag:TaggedContent" property="schema:mainEntity">
<article class="group h-entry" prefix="<%= prefix_mappings_for(:sioctypes) %>" resource="#blogpost" typeof="cc:Work schema:BlogPosting sioctypes:BlogPost ctag:TaggedContent" property="schema:mainEntity">
<header class="section">
<h1 class="p-name" property="dc:title schema:headline" id="title"><%= @item[:title] %></h1>
<div class="group meta" id="document-context">
Expand Down
2 changes: 1 addition & 1 deletion layouts/partials/vocab_metadata.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<ul class="none">
<% vocabularies_for(group).each do |vocab| %>
<% vocabularies_for(category).each do |vocab| %>
<li>
<dl resource="<%= h vocab.fetch(:vocab_uri) %>" property="void:vocabulary" typeof="voaf:Vocabulary">
<dt><a property="vann:preferredNamespaceUri" content="<%= h vocab.fetch(:namespace_uri) %>" href="<%= h vocab.fetch(:vocab_uri) %>"><code class="prefix" property="vann:preferredNamespacePrefix"><%= h vocab.fetch(:prefix) %></code></a>: <span class="foreign" property="dc:title"><%= h vocab.fetch(:title) %></span></dt>
Expand Down
2 changes: 1 addition & 1 deletion layouts/specifications/vocabulary.haml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
%title
= ont["rdfs:label"].first['@value']
%link{rel: "stylesheet", href: "/css/specifications.css"}
- pfx = RDF::Vocabulary.find(ont['@id']).__name__.split(':').last.downcase
- pfx = ont['vann:preferredNamespacePrefix']&.first['@value'] || RDF::Vocabulary.find(ont['@id']).__name__.split(':').last.downcase
- pfxs = prefixes.inject([]) {|m, (k,v)| m << "#{k}: #{v}"}.join(' ')
- typeof = ont['@type'].map {|t| RDF::URI(t).pname}.join(' ')
%body{resource: ont['@id'], typeof: typeof, prefix: pfxs}
Expand Down
139 changes: 111 additions & 28 deletions lib/data_sources/vocabularies.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,43 @@ module DataSources
class Vocabularies < Nanoc::DataSource
identifier :vocabularies

class UnknownVocabularyPrefix < ::Nanoc::Core::Error
def initialize(prefix)
super("A vocabulary with the prefix “#{prefix}” (specified in the site's configuration file) was not found.")
end
end

def up
@voaf_metadata =
File.open('var/voaf_metadata.yaml') do |file|
YAML.safe_load(file.read,
filename: file.path,
permitted_classes: [Symbol])
end
load_extra_metadata(@config[:extra_metadata])

local_vocabs =
LifePreserver::Vocab.constants
.map(&LifePreserver::Vocab.method(:const_get))
.select { |constant| constant.is_a? Class }
register_vocabularies(local_vocabs, @config[:prefix_overrides])

if (categories = @config[:categories])
prefixes = categories.values.flatten
RDF::Vocabulary.limit_vocabs(*prefixes.map(&:to_sym))
end
end

# Returns the collection of Vocabulary items, one per vocabulary prefix.
#
# @raise [UnknownVocabularyPrefix] if a vocabulary prefix specified in site config
# does not have a mapping to an {RDF::Vocabulary} class.
# @return [Enumerable] The collection of items.
def items
items = []

@config[:prefixes_used].each do |group_name, group|
group.each do |prefix|
vocab = RDF::Vocabulary.find_by_prefix(prefix)
items << vocabulary_to_item(vocab, group_name)
@config.fetch(:categories, {}).each do |category, prefixes|
prefixes.each do |prefix|
# TODO: submit patch for RDF::Vocabulary to handle this edge case?
vocab = RDF::Vocabulary.vocab_map[prefix == 'rdf' ? :rdfv : prefix.to_sym]
raise UnknownVocabularyPrefix.new(prefix) unless vocab

vocab_class = vocab.fetch(:class, RDF::Vocabulary.from_sym(vocab.fetch(:class_name).to_sym))
items << vocabulary_to_item(prefix, vocab_class, category)
end
end

Expand All @@ -34,43 +55,36 @@ def items

protected

def vocabulary_to_item(vocab, group_name)
prefix = vocab.__prefix__
metadata = extract_metadata_from(vocab)
def vocabulary_to_item(prefix, vocab, category)
metadata = find_vocab_metadata(vocab, @extra_metadata)

attributes = {
kind: 'vocabulary',
prefix: prefix,
group: group_name,
category: category,
namespace_uri: vocab.to_uri.value,
is_hidden: true,
}

new_item(
'',
attributes.merge(metadata),
File.join(File::SEPARATOR, group_name.to_s.parameterize, prefix.to_s.parameterize),
File.join(File::SEPARATOR, category.to_s.parameterize, prefix.to_s.parameterize),
)
end

# @return [Hash]
def extract_metadata_from(vocab)
vocab_uri = vocab_uri(vocab)
metadata = Hash(@voaf_metadata[vocab_uri.to_sym] || @voaf_metadata[vocab_uri.split(%r{[/#]$})[0].to_sym])
metadata[:vocab_uri] = vocab_uri

if metadata.key?(:description)
metadata[:description] = cleanup(metadata[:description])
end

metadata
end

# Clean up punctuation of given text. Start with capital letter
# and end with a full stop if it doesn't already end with some sort of
# terminal punctuation.
def cleanup(text)
text = text.upcase_first
text[/[.!?]$/] ? text : text << '.'
end

# Return the URI of the given vocabulary.
#
# @param vocab [RDF::Vocabulary] The vocabulary.
# @return [String]
def vocab_uri(vocab)
# HACK: until we have better handling of ontology definitions at a
# different uri than the vocabulary namespace.
Expand All @@ -80,6 +94,75 @@ def vocab_uri(vocab)
vocab.ontology ? vocab.ontology.value : vocab.to_uri.value
end
end

# Find the descriptive metadata of the given vocabulary, searching
# within the given (normalized) metadata.
#
# Provided metadata is expected to have the following form:
#
# @example YAML vocabulary metadata format
# vocab_uri:
# title: Vocab title
# description: Vocab description
#
# @param vocab [RDF::Vocabulary] The vocabulary.
# @return [Hash] Descriptive metadata for the given vocabulary, if found.
# Otherwise, an empty Hash.
def find_vocab_metadata(vocab, extra_metadata = {})
vocab_uri = vocab_uri(vocab)

metadata = Hash(extra_metadata[vocab_uri.to_sym] || extra_metadata[vocab_uri.split(%r{[/#]$})[0].to_sym])
metadata[:vocab_uri] = vocab_uri

if metadata.key?(:description)
metadata[:description] = cleanup(metadata[:description])
end

metadata
end

# Load descriptive metadata for RDF vocabularies into the class.
#
# @note called by {#up} when the site is loading.
# @param metadata [Hash,String] the extra metadata. If a String, a
# relative path to a YAML file, which will be loaded. If Hash, is used
# directly as metadata.
# @return [Hash{Symbol=>Symbol}] the extra metadata.
def load_extra_metadata(metadata)
return if metadata.nil? || metadata.empty?

@extra_metadata =
case metadata
when String
File.open(metadata) do |file|
YAML.safe_load(file.read,
filename: file.path,
permitted_classes: [Symbol])
end
when Hash
metadata
end
end

# Register local vocabularies with the Ruby RDF framework.
#
# Sets the vocabulary class name if specified in the {:prefix_overrides}
# key of the data source configuration.
#
# @note called by {#up} when the site is loading.
#
# @param vocabs [Array<RDF::Vocabulary>] Vocabulary classes to register.
# @param prefixes [Hash{Symbol=>String}] Optional prefixes with to register
# the vocabularies.
#
# @return [void]
def register_vocabularies(vocabs, prefixes = {})
Array(vocabs).each do |vocab|
class_name = vocab.__name__.to_s.demodulize
prefix = prefixes[class_name.to_sym] || class_name.downcase
RDF::Vocabulary.register(prefix.to_sym, vocab, class_name: class_name)
end
end
end
end
end
6 changes: 3 additions & 3 deletions lib/filters/rdf_distiller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def run(_content, params = {})

repository = RDF::Repository.new

RDF::Reader.for(input.to_sym).new(+assigns[:content], options) { |reader| repository << reader }
RDF::Reader.for(input.to_sym).new(+assigns[:content], **options) { |reader| repository << reader }

if repository.has_statement?(RDF::Statement(RDF::URI.new(base_uri), RDF.type, RDF::OWL.Ontology))
vocab = RDF::Vocabulary.find(base_uri) || RDF::Vocabulary.from_graph(repository, url: base_uri, class_name: prefix.to_s.upcase)
Expand All @@ -76,10 +76,10 @@ def run(_content, params = {})

vocab.to_html(graph: repository, prefixes: prefixes, template: assigns[:layout][:filename])
else
repository.dump(output_format, options)
repository.dump(output_format, **options)
end
else
repository.dump(output_format, options)
repository.dump(output_format, **options)
end
end

Expand Down
6 changes: 3 additions & 3 deletions lib/helpers/vocabulary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ def prefix_mappings_for(*args)
# Finds a vocabulary or vocabularies for the given name.
#
# @param [Array<String, Symbol>] args The vocabulary prefixes or prefix
# groups. If a group name, it should return the group. If a prefix,
# it should return a single vocabulary.
# categories. If a category name, it should return the entire category of
# vocabularies. If a prefix, it should return a single vocabulary.
#
# @return [Array<Nanoc::Core::BasicItemView>] Any applicable items of kind +vocabulary+.
def vocabularies_for(*args)
Expand All @@ -37,7 +37,7 @@ def vocabularies_for(*args)
Array(args).flatten.each do |arg|
vocabularies_root = @config.fetch(:vocabularies_root)
vocabs = @items.find_all(File.join(vocabularies_root, arg.to_s, '*'))
# Look for a single prefix if no group is found
# Look for a single prefix if no category is found
vocabs = vocabs.present? ? vocabs : @items[File.join(vocabularies_root, '*', arg.to_s)]

if vocabs.blank?
Expand Down
Loading

0 comments on commit a00e54f

Please sign in to comment.