#!/usr/bin/env ruby #= Synopsis # #vocdoc creates simple latex and html documentation for a given OWL ontology file. #At the moment, the assumption is that the source file is encoded in Turtle syntax. # #== Examples #To create latex documentation (a class and a properties file) for "ontology.n3" in "destination/folder/path": # ruby vocdoc.rb --latex ontology.n3 destination/folder/path #To create html documentation (one large html file) in the current directory # ruby vocdoc.rb --html ontology.n3 # #== Usage # vocdoc $Id$ # ruby vocdoc.rb [options] source_file target_folder # #The default for target_folder is the current folder # #For help use: vocdoc.rb -h # #== Options # -h, --help Displays help message # -v, --version Display the version, then exit # -q, --quiet Output as little as possible, overrides verbose # -V, --verbose Verbose output # --latex output latex source into target folder, spread into "classes.tex" # and "properties.tex". This is the default. # --html output html source ("ontology.html") into target folder # #== Author #Knud Hinnerk Möller, knud.moeller@deri.org # #== Copyright #Copyright (c) 2009 Knud Hinnerk Möller. #Digital Enterprise Research Institute (DERI) #National University of Ireland, Galway (NUIG) #$Id$ # #== Configuration File #vocdoc will try to read configuration from a file "owl_doc.conf" (or one which is specified as #a parameter), which consists of simple attribute value pairs. #The following attributes can be defined: #* +ontology_namespace+: The namespace of the ontology for which documentation is created #* +doc_root+: The root directory of where the documentation and ontology are located #* +alphabetise+: defines whether or not the term lists in the documentation should be alphabetised. Possible values are "YES" and "NO" #* +GA_code+: your google analytics code, if you want to gather statistics for your vocabulary # #* +term_level+: the Latex section type used for individual terms (e.g. "subsubsection") #* +term_numbering+: YES or NO, whether or not the sections for individual terms will be numbered # #== Adjusting the Ontology Source #vocdoc makes a lot of assumptions about how the format of ontology source when it tries to #generate the documentation. This is particularly true for any kind of metadata, which does not #influence the semantics of the ontology or vocabulary in any way, but which is used to come up #with an informative and nice-looking documentation. Most of the metadata is currently only used #in the HTML output. # #An example for everything described below can be found in the SWC ontology at http://data.semanticweb.org/ns/swc/ontology . # #=== Namespaces #For the metadata, I use terms from the following namespaces: # rdfs: # owl: # dcterms: # foaf: # doap: # vs: # sioc: # #=== Title and Logo #* +rdfs+:+label+ - to specify the name of the vocabulary, which will appear as the title and first header on the HTML document. #* +foaf+:+logo+ - to specify the URL of a logo image, which will be shown in the upper right corner of the HTML document. # #=== Dates #* +dcterms+:+created+ - to specify the date when the vocabulary was first created. Will appear in the preamble section. #* +dcterms+:+modified+ - to specify the date when the vocabulary was last change. Will appear in the preamble section. I use a versioning system variable for this, e.g. $Date$ on SVN. # #=== Versions and Revisions #* +doap+:+revision+ or +owl+:+versionInfo+ - to specify the revision/version number of the vocabulary. Will appear in the preamble section. #* +sioc+:+earlier_version+ or +owl+:+priorVersion+ - to specify the preceeding version of the vocabulary. Will appear in the preamble section. Note that we may be misusing owl:priorVersion - we are pointing to a previous version of the _document_, not the ontology namespace # #=== Authors #* +dcterms+:+creator+ - to specify the authors of the vocabulary. Will appear in the preamble section. Values of must be instances of +foaf+:+Person+, which must have +foaf+:+name+, +foaf+:+surname+ and +foaf+:+homepage+ specified. Optionally, it is also possible to instantiate a +foaf+:+Organization+ with name and homepage and say that the person is +foaf+:+member+ of it. This will result in the person's affiliation showing up in parentheses behind their name. #* +dcterms+:+contributor+ - to specify other contributors to the vocabulary. Will appear in the preamble section. Works just like +dcterms+:+creator+. However, it is possible to have any kind of +foaf+:+Agent+ as the value. This way, it is possible to have a group as a contributor, e.g., "members of the FooBar mailing list". # #=== Licensing Information #* you can use the property +http+://data.semanticweb.org/ns/misc#licenseDoc to point to a resource which contains human-readable lincensing information. The licensing information can contain any kind of HTML, as long as you wrap it as CDATA (provided that you are using rdf/xml). The following triples do the trick # # ontology misc:licenseDoc licenseDoc . # licenseDoc rdfs:comment "This work is licensed under a Creative Commons Attribution License. ..." # #=== General Documentation #* you can add an arbitrary number of sections containing further human-readable documentation. Each section will be listed between the preamble and the class and property overview. For each section, create an RDF resource (of arbitrary type) and give it an +rdfs+:+label+ (the header) and +rdfs+:+comment+ (the content). Link these sections to the ontology in the desired order using the +vs+:+userDocs+ property. The value of that property shoudl be a list construct with the sections in the desired order. # #=== Term Documentation #* for individual terms (classes and properties), the formal defintions from the +owl+ and +rdfs+ namespaces are reflected in the documentation (e.g., +rdfs+:+subClassOf+, +owl+:+TransitiveProperty+, ...) #* the label of the term should be defined with +rdfs+:+label+ #* additional human-readable documentation should be given with +rdfs+:+comment+. If the value is enclosed in a CDATA element, arbitrary HTML can be used for documentation. require 'pp' require 'optparse' require 'rdoc/usage' require 'ostruct' require 'date' require 'uri' require 'set' require 'parseconfig' require 'rdf/redland' require 'rdf/redland/schemas/rdfs' require 'rdf/redland/schemas/owl' $DCTERMS = Redland::Namespace.new("http://purl.org/dc/terms/") $DOAP = Redland::Namespace.new("http://usefulinc.com/ns/doap#") $FOAF = Redland::Namespace.new("http://xmlns.com/foaf/0.1/") $VS = Redland::Namespace.new("http://www.w3.org/2003/06/sw-vocab-status/ns#") $HELP = Redland::Namespace.new("http://data.semanticweb.org/ns/misc#") $SIOC = Redland::Namespace.new("http://rdfs.org/sioc/ns#") $SWRC_EXT = Redland::Namespace.new("http://www.cs.vu.nl/~mcaklein/onto/swrc_ext/2005/05#") # This is the main class of VocDoc, and will be instantiated once every time the programme is run. class OwlDoc VERSION = '0.0.1' LATEX = 'latex' HTML = 'html' DOT = 'dot' DEPRECATED = "deprecated" attr_reader :options, :config # Causes options to be read from configuration file, and the Redland parser and model to be set up. def initialize(arguments) @arguments = arguments @config = ParseConfig.new('owl_doc.conf') unless !File.exist?('owl_doc.conf') # Set defaults @options = OpenStruct.new @options.verbose = false @options.quiet = false @options.output_format = LATEX @parser = Redland::Parser.turtle @output_folder = "." $model = Redland::Model.new(Redland::TripleStore.new) $ONT_NS = @config.instance_variable_get("@ontology_namespace") @options.alphabetise = @config.instance_variable_get("@alphabetise") @options.doc_root = @config.instance_variable_get("@doc_root") end # Parse options, check arguments, then process the command def run if parsed_options? && arguments_valid? puts "Start at #{DateTime.now}\n\n" if @options.verbose output_options if @options.verbose # [Optional] process_arguments process_command puts "\nFinished at #{DateTime.now}" if @options.verbose else output_usage end end protected def parsed_options? # Specify options opts = OptionParser.new opts.on('-v', '--version') { output_version ; exit 0 } opts.on('-h', '--help') { output_help } opts.on('-V', '--verbose') { @options.verbose = true } opts.on('-q', '--quiet') { @options.quiet = true } opts.on('--latex') { @options.output_format = LATEX} opts.on('--html') { @options.output_format = HTML} opts.on('--dot') { @options.output_format = DOT} opts.parse!(@arguments) rescue return false process_options true end # Performs post-parse processing on options def process_options @options.verbose = false if @options.quiet end def output_options puts "Options:\n" @options.marshal_dump.each do |name, val| puts " #{name} = #{val}" end end # True if required arguments were provided def arguments_valid? # TO DO - implement your real logic here true if @arguments.length > 0 end # Setup the arguments def process_arguments puts "Processing arguments..." if @options.verbose @owl_file_name = @arguments[0] if @arguments.length > 1 @output_folder = @arguments[1] end puts "done!\n\n" if @options.verbose end def output_help output_version RDoc::usage() #exits app end def output_usage RDoc::usage('usage') # gets usage from comments above end def output_version puts "#{File.basename(__FILE__)} version #{VERSION}" end def process_command parse_prefixes parse_owl_file if (@options.output_format == LATEX) produce_latex_output elsif (@options.output_format == HTML) produce_html_output elsif (@options.output_format == DOT) produce_dot_output else puts "Nothing to do, no output format given..." end end # ------------------------------- # Parses the owl file into Ruby objects such as OWL_Resource or FOAF_Agent def parse_owl_file puts "Parsing owl file..." if @options.verbose @parser.parse_into_model($model, "file:" + @owl_file_name) puts $model.to_string @ontology = nil; # get the ontology resource itself (let's hope there is only one...) $model.find(nil, Redland::TYPE, Redland::OWL::OWLNS['Ontology']) { |s, p, o| if $ONT_NS.match(s.uri.to_s) @ontology = OWL_Ontology.new(s) end } @classes = [] @external_classes = [] # find all classes $model.find(nil, Redland::TYPE, Redland::OWL::OWL_CLASS) { |s, p, o| a_class = OWL_Class.new(s) if s.uri.to_s.match($ONT_NS) @classes << a_class else @external_classes << a_class end } @classes.sort! {|x,y| x.name <=> y.name } if @options.alphabetise @classes_by_topic = Hash.new @classes.each do |a_class| @classes_by_topic[a_class.topic] = [] unless @classes_by_topic[a_class.topic] @classes_by_topic[a_class.topic] << a_class end @external_classes.sort! {|x,y| x.name <=> y.name } if @options.alphabetise puts "counted #{@classes.size} non-deprecated classes in the local namespace." if @options.verbose puts "counted #{@external_classes.size} classes in other namespaces." if @options.verbose @deprecated_classes = [] $model.find(nil, Redland::TYPE, Redland::OWL::OWLNS['DeprecatedClass']) { |s, p, o| if s.uri.to_s.match($ONT_NS) a_class = OWL_Class.new(s) @deprecated_classes << a_class end } @deprecated_classes.sort! {|x,y| x.name <=> y.name } if @options.alphabetise puts "counted #{@deprecated_classes.size} deprecated classes." if @options.verbose @properties = [] # We should be able to do this with SPARQL UNION, but UNION currently isn't # supported by Redland (it's in SVN already, apparently, but not in the released code) # query = Redland::Query.new("PREFIX owl: " + # "SELECT DISTINCT ?property WHERE { " + # "{ ?property a owl:DatatypeProperty } UNION " + # "{ ?property a owl:ObjectProperty } " + # "FILTER regex(str(?property), '^#{$ONT_NS}', 'i') }") # results = $model.query_execute(query) # puts results.to_string # puts $ONT_NS # find all object_properties $model.find(nil, Redland::TYPE, Redland::OWL::OWL_OBJECT_PROPERTY) { |s, p, o| if s.uri.to_s.match($ONT_NS) a_property = OWL_Property.new(s) @properties << a_property end } # find all datatype_properties $model.find(nil, Redland::TYPE, Redland::OWL::OWL_DATATYPE_PROPERTY) { |s, p, o| if s.uri.to_s.match($ONT_NS) a_property = OWL_Property.new(s) @properties << a_property end } # find all functional properties $model.find(nil, Redland::TYPE, Redland::OWL::OWLNS['FunctionalProperty']) { |s, p, o| if s.uri.to_s.match($ONT_NS) a_property = OWL_Property.new(s) @properties << a_property end } # find all inverse-functional properties $model.find(nil, Redland::TYPE, Redland::OWL::OWLNS['InverseFunctionalProperty']) { |s, p, o| if s.uri.to_s.match($ONT_NS) a_property = OWL_Property.new(s) @properties << a_property end } # find all transitive properties $model.find(nil, Redland::TYPE, Redland::OWL::OWLNS['TransitiveProperty']) { |s, p, o| if s.uri.to_s.match($ONT_NS) a_property = OWL_Property.new(s) @properties << a_property end } # find all symmetric properties $model.find(nil, Redland::TYPE, Redland::OWL::OWLNS['SymmetricProperty']) { |s, p, o| if s.uri.to_s.match($ONT_NS) a_property = OWL_Property.new(s) @properties << a_property end } @properties.uniq! @properties.sort! {|x,y| x.name <=> y.name } if @options.alphabetise @deprecated_properties = [] $model.find(nil, Redland::TYPE, Redland::OWL::OWLNS['DeprecatedProperty']) { |s, p, o| if s.uri.to_s.match($ONT_NS) a_property = OWL_Property.new(s) @deprecated_properties << a_property end } @deprecated_properties.sort! {|x,y| x.name <=> y.name } if @options.alphabetise @properties = @properties - @deprecated_properties puts "counted #{@properties.size} non-deprecated properties." if @options.verbose puts "counted #{@deprecated_properties.size} deprecated properties." if @options.verbose @individuals = []; # we look for instances of non-deprecated classes in our ontology @classes.each do |a_class| $model.find(nil, Redland::TYPE, Redland::Resource.new(a_class.uri)) { |s, p, o| if s.resource? if s.uri.to_s.match($ONT_NS) something = OWL_Resource.new(s) @individuals << something end end } end puts "counted #{@individuals.size} instances of non-deprecated classes of this ontology." if @options.verbose puts "done!\n\n" if @options.verbose end # Generates dot files of the class hierarchy and the properties def produce_dot_output @class_dot_file = File.open(File.join(@output_folder, "classes.dot"), "w") # @property_dot_file = File.open(File.join(@output_folder, "classes.dot"), "w") class_hierarchy = Set.new resources = Set.new local_prefix = $prefix_mapping[$ONT_NS] @class_dot_file.puts "digraph CLASS_HIERARCHY {" @class_dot_file.puts @class_dot_file.puts "node [ shape = \"record\" ]" @class_dot_file.puts "edge [ arrowhead = \"empty\" ]" @class_dot_file.puts # output class details and collect class hierarchy [@classes, @external_classes].each do |class_collection| class_collection.each do |a_class| name = a_class.name prefix = a_class.namespace_prefix short_name = "#{prefix}:#{name}" if (prefix == local_prefix) @class_dot_file.puts "\"#{short_name}\" [ label=\"{\\<\\\\>\\n#{short_name}|" a_class.in_domain.each { |key, value| @class_dot_file.puts "#{key} : #{value.join(', ')}\n" } @class_dot_file.puts "}\" ]" end # if (a_class.sub_classes.size > 0 || a_class.super_classes.size > 0) # resources << short_name # end # if (prefix != local_prefix) # class_hierarchy << "\"#{prefix}:#{name}\" [style=filled,fillcolor=lightgray];" # end a_class.sub_classes.each do |sub_class| # resources << sub_class class_hierarchy << "\"#{short_name}\" -> \"#{sub_class}\";" end a_class.super_classes.each do |super_class| # resources << super_class class_hierarchy << "\"#{super_class}\" -> \"#{short_name}\";" end end end # the properties # @properties.each do |property| # name = property.name # prefix = property.namespace_prefix # property.domain.each do |in_domain| # resources << in_domain # property.range.each do |in_range| # resources << in_range # @class_dot_file.puts "\"#{in_domain}\" -> \"#{in_range}\" [label=\"#{prefix}:#{name}\",style=dotted,fontsize=10];" # end # end # end # resources.each do |resource| # resource.match(/(.*?):(.*?)/) # prefix = $1 # # if (prefix != local_prefix) # class_hierarchy << "\"#{resource}\" [style=filled,fillcolor=lightgray];" # else # @class_dot_file.puts "\"#{resource}\" [ label=\"\\<\\\\>\\n#{resource}\" ]" # end # end @class_dot_file.puts class_hierarchy.each do |relation| @class_dot_file.puts relation end @class_dot_file.puts "}" end # Generates documentation in LaTeX syntax (only class and property lists, no general documentation), # which will be written into the file "classes.tex" # and "properties.tex" in the output folder specified in the command line arguments. # The LaTeX files cannot be compiled on their own, but need to be included in another LaTeX document. def produce_latex_output @class_file = File.open(File.join(@output_folder, "classes.tex"), "w") @properties_file = File.open(File.join(@output_folder, "properties.tex"), "w") term_level = "subsubsection" if @options.term_level term_level = @options.term_level end term_numbering = "" if @options.term_numbering term_level = "#{term_level}#{term_numbering}" puts "generating LaTeX output for classes" if @options.verbose @classes.each do |a_class| puts "\tgenerating LaTeX output for #{a_class.name}" if @options.verbose @class_file.puts "\\#{term_level}{Class: \\texttt{#{a_class.namespace_prefix}:#{escape_latex(a_class.name)}}}" @class_file.puts "\\label{subs:#{remove_underscore(a_class.name)}}" @class_file.puts "\\begin{tabular}{| >{\\columncolor{fast@lightgrey}}p{2.5cm}|p{12cm}|}" @class_file.puts "\\hline" @class_file.puts "\\textcolor{white}{\\textbf{label}} & #{escape_latex(a_class.label)} \\\\ \\hline" if a_class.label @class_file.puts "\\textcolor{white}{\\textbf{description}} & #{change_to_latex_quotes(escape_latex(a_class.comment))} \\\\ \\hline" if a_class.comment if (a_class.sub_classes.length > 0) @class_file.puts "\\textcolor{white}{\\textbf{super\\_class\\_of}} & #{escape_latex(format_array(a_class.sub_classes).join(', '))} \\\\ \\hline" end if (a_class.super_classes.length > 0) @class_file.puts "\\textcolor{white}{\\textbf{sub\\_class\\_of}} & #{escape_latex(format_array(a_class.super_classes).join(', '))} \\\\ \\hline" end if (a_class.in_domain.length > 0) @class_file.puts "\\textcolor{white}{\\textbf{in\\_domain\\_of}} & #{escape_latex(format_array(a_class.in_domain).join(', '))} \\\\ \\hline" end if (a_class.in_range.length > 0) @class_file.puts "\\textcolor{white}{\\textbf{in\\_range\\_of}} & #{escape_latex(format_array(a_class.in_range).join(', '))} \\\\ \\hline" end if (a_class.union_of.length > 0) pp a_class.union_of if @options.verbose @class_file.puts "\\textcolor{white}{\\textbf{unionOf}} & #{escape_latex(format_array(a_class.union_of).join(', '))} \\\\ \\hline" end @class_file.puts "\\end{tabular}" end puts "generating LaTeX output for properties" if @options.verbose @properties.each do |a_property| puts "\tgenerating LaTeX output for #{a_property.name}" if @options.verbose @properties_file.puts "\\#{term_level}{Property: \\texttt{#{a_property.namespace_prefix}:#{escape_latex(a_property.name)}}}" @properties_file.puts "\\label{subs:#{remove_underscore(a_property.name)}}" @properties_file.puts "\\begin{tabular}{| >{\\columncolor{fast@lightgrey}}p{2.5cm}|p{12cm}|}" @properties_file.puts "\\hline" @properties_file.puts "\\textcolor{white}{\\textbf{label}} & #{escape_latex(a_property.label)} \\\\ \\hline" @properties_file.puts "\\textcolor{white}{\\textbf{description}} & #{change_to_latex_quotes(escape_latex(a_property.comment))} \\\\ \\hline" @properties_file.puts "\\textcolor{white}{\\textbf{type}} & #{escape_latex(format_array(a_property.short_type).join(', '))} \\\\ \\hline" if (a_property.domain.length > 0) @properties_file.puts "\\textcolor{white}{\\textbf{domain}} & #{escape_latex(format_array(a_property.domain).join(', '))} \\\\ \\hline" end if (a_property.range.length > 0) @properties_file.puts "\\textcolor{white}{\\textbf{range}} & #{escape_latex(format_array(a_property.range).join(', '))} \\\\ \\hline" end @properties_file.puts "\\end{tabular}" end end # Generates complete documentation in one big HTML file "ontology.html". def produce_html_output @html_output = File.open(@output_folder + "/ontology.html", "w") @html_output.puts "" @html_output.puts "" @html_output.puts "" @html_output.puts "#{@ontology.label}" @html_output.puts "" @html_output.puts "" @html_output.puts "" @html_output.puts "" if (@ontology.logo) @html_output.puts "" end @html_output.puts "

#{@ontology.label}

" @html_output.puts "
" this_version = @options.doc_root + remove_extension(@owl_file_name) this_version_html = this_version + ".html" this_version_rdf = this_version + ".rdf" @html_output.puts "
This version:
" @html_output.puts "
#{this_version_html} (RDF)
" latest = @ontology.uri @html_output.puts "
Latest version:
" @html_output.puts "
#{latest}
" if (@ontology.previous_version) previous_version = @options.doc_root + remove_extension(File.basename(@ontology.previous_version)) previous_version_html = previous_version + ".html" previous_version_rdf = previous_version + ".rdf" @html_output.puts "
Previous version:
" @html_output.puts "
#{previous_version_html} (RDF)
" end @html_output.puts "
Revision:
" @html_output.puts "
#{@ontology.revision}
" @html_output.puts "
First created:
" @html_output.puts "
#{@ontology.creation_date}
" @html_output.puts "
Last change:
" @html_output.puts "
#{@ontology.mod_date}
" @html_output.puts "
Main authors:
" creators = [] @ontology.creators.each do |creator| member_in = creator.member_in orgs = [] member_in.each do |org| orgs << org.html_link end creators << creator.html_link + " (#{orgs.join(", ")})" end @html_output.puts "
#{creators.join(", ")}
" contributors = [] if (@ontology.contributors.length > 0) @html_output.puts "
Contributors:
" @ontology.contributors.each do |contributor| orgs = [] if (contributor.respond_to?('member_in')) member_in = contributor.member_in member_in.each do |org| orgs << org.html_link end end entry = orgs.length > 0 ? contributor.html_link + " (#{orgs.join(", ")})" : contributor.html_link contributors << entry end @html_output.puts "
#{contributors.join(", ")}
" @html_output.puts "
" end @html_output.puts @ontology.license_statement @html_output.puts "
" @ontology.documentation.each do |section| $model.find(section, Redland::RDFS::RDFS_LABEL, nil) { |s, p, o| @html_output.puts "

#{o.value}

" } $model.find(section, Redland::RDFS::RDFS_COMMENT, nil) { |s, p, o| @html_output.puts o.value } end @html_output.puts "

#{@ontology.comment}

" @html_output.puts "

Overview

" @html_output.puts '
' @html_output.puts "

Classes: |" @classes.each do |a_class| reference = "#{a_class.name}" @html_output.puts "#{reference} |" end @html_output.puts "

" @html_output.puts "

Properties: |" @properties.each do |a_property| reference = "#{a_property.name}" @html_output.puts "#{reference} |" end @html_output.puts "

" @html_output.puts "

Individuals: |" @individuals.each do |a_individual| reference = "#{a_individual.name}" @html_output.puts "#{reference} |" end @html_output.puts "

" @html_output.puts "
" @html_output.puts "

Terms grouped in broad categories

" count = 0 @html_output.puts "" @html_output.puts "" @classes_by_topic.keys.sort.each do |topic| # @html_output.puts "
" if count.modulo(4) == 0 @html_output.puts "" if count != 0 && count.modulo(3) == 0 @html_output.puts "" count += 1 end @html_output.puts "" @html_output.puts "
" @html_output.puts "
" @html_output.puts "

#{topic}

" @html_output.puts "
    " term_group = @classes_by_topic[topic] term_group.each do |a_term| reference = "#{a_term.name}" @html_output.puts "
  • #{reference}
  • " end @html_output.puts "
" @html_output.puts "
" @html_output.puts "
" @html_output.puts "
" @html_output.puts "

Classes

" @classes.each do |a_class| produce_html_output_class(a_class, "specterm") end @html_output.puts "

Properties

" @properties.each do |a_property| produce_html_output_property(a_property, "specterm") end @html_output.puts "

Individuals

" @individuals.each do |a_individual| produce_html_output_individual(a_individual, "specterm") end @html_output.puts "
" @html_output.puts "

Deprecated Classes

" @deprecated_classes.each do |a_class| produce_html_output_class(a_class, "specterm_deprecated") end @html_output.puts "

Deprecated Properties

" @deprecated_properties.each do |a_property| produce_html_output_property(a_property, "specterm_deprecated") end google_analytics_code = '' File.open("google_analytics_code.html", "r") { |f| google_analytics_code = f.read } google_analytics_code.sub!("***YOUR GA CODE***", @config.instance_variable_get("@GA_code")) @html_output.puts google_analytics_code @html_output.puts "" @html_output.puts "" end # Generate the HTML output for an individual class. def produce_html_output_class(a_class, style) @html_output.puts "
" reference = "#{a_class.name}" @html_output.puts "

Class: #{reference}

" @html_output.puts "" @html_output.puts "" if (a_class.sub_classes.length > 0) @html_output.puts "" end if (a_class.super_classes.length > 0) @html_output.puts "" end if (a_class.union_of.length > 0) @html_output.puts "" end if (a_class.intersection_of.length > 0) @html_output.puts "" end if (a_class.one_of.length > 0) @html_output.puts "" end if (a_class.in_domain.length > 0) @html_output.puts "" end if (a_class.in_range.length > 0) @html_output.puts "" end @html_output.puts "" @html_output.puts "
Label#{a_class.label}
super-class-of#{format_array(a_class.sub_classes).join(', ')}
sub-class-of#{format_array(a_class.super_classes).join(', ')}
union-of#{format_array(a_class.union_of).join(', ')}
intersection-of#{format_array(a_class.intersection_of).join(', ')}
one-of#{format_array(a_class.one_of).join(', ')}
in-domain-of#{format_array(a_class.in_domain).join(', ')}
in-range-of#{format_array(a_class.in_range).join(', ')}
status#{a_class.status}
" @html_output.puts "

#{a_class.comment}

" sindice_search_term = 'http://sindice.com/search?q=' + URI.escape("* <#{a_class.uri}>") + "&qt=advanced" @html_output.puts "

Look for usage of this class on Sindice.

" @html_output.puts "
" @html_output.puts "
" end # Generate the HTML output for an individual. def produce_html_output_individual(a_individual, style) @html_output.puts "
" reference = "#{a_individual.name}" @html_output.puts "

Class: #{reference}

" @html_output.puts "" @html_output.puts "" @html_output.puts "" @html_output.puts "" @html_output.puts "
Label#{a_individual.label}
Type#{format_array(a_individual.types).join(', ')}
status#{a_individual.status}
" @html_output.puts "

#{a_individual.comment}

" sindice_search_term = 'http://sindice.com/search?q=' + URI.escape("* <#{a_individual.uri}>") + "&qt=advanced" @html_output.puts "

Look for usage of this class on Sindice.

" @html_output.puts "
" @html_output.puts "
" end # Generate the HTML output for an individual property. def produce_html_output_property(a_property, style) @html_output.puts "
" reference = "#{a_property.name}" @html_output.puts "

Property: #{reference}

" @html_output.puts "" @html_output.puts "" # @html_output.puts "" @html_output.puts "" if (a_property.sub_property_of.length > 0) @html_output.puts "" end if (a_property.super_property_of.length > 0) @html_output.puts "" end if (a_property.domain.length > 0) @html_output.puts "" end if (a_property.range.length > 0) @html_output.puts "" end if (a_property.inverse_of.length > 0) @html_output.puts "" end @html_output.puts "" @html_output.puts "
Label#{a_property.label}
Type#{codify(a_property.short_type)}
Type#{format_array(a_property.short_type).join(', ')}
sub_property_of#{format_array(a_property.sub_property_of).join(', ')}
super_property_of#{format_array(a_property.super_property_of).join(', ')}
domain#{format_array(a_property.domain).join(', ')}
range#{format_array(a_property.range).join(', ')}
inverse_of#{format_array(a_property.inverse_of).join(', ')}
status#{a_property.status}
" @html_output.puts "

#{a_property.comment}

" sindice_search_term = 'http://sindice.com/search?q=' + URI.escape("* <#{a_property.uri}> *") + "&qt=advanced" @html_output.puts "

Look for usage of this property on Sindice.

" @html_output.puts "
" @html_output.puts "
" end # transforms an array of "prefix:name" strings, using the methods codify and linkify. # Terms in the ontology namespace will be hyperlinked, external terms won't. # TODO: External terms should be linked as well. def format_array(array) ont_prefix = $prefix_mapping[$ONT_NS] array.each_index do |index| prefix = array[index].split(":")[0] name = array[index].split(":")[1] # only link to stuff in this ontology array[index] = codify(array[index]) if (prefix == ont_prefix) array[index] = linkify(array[index], name, prefix) end end return array end # Transforms a string into code format. The actual return value of this method depends on which output format was # set in the command line arguments (e.g. html or latex). def codify(string) if @options.output_format == LATEX string = "\\texttt{#{string}}" elsif @options.output_format == HTML string = "#{string}" end return string end # Generates a hyperlink within the documentation based on the input string. # The actual return value of this method depends on which output format was # set in the command line arguments (e.g. html or latex). def linkify(ont_ref, name, prefix) if @options.output_format == LATEX ont_ref = "\\htmlref{#{ont_ref}}{subs:#{remove_underscore(name)}}" elsif @options.output_format == HTML ont_ref = "#{ont_ref}" end return ont_ref end # removes the extension from a filename def remove_extension(file_name) extension = File.extname(file_name) File.basename(file_name, extension) end # escapes characters which are reserved in LaTeX syntax (e.g. '_') def escape_latex(text) text.gsub('_', '\\_') end # removes underscores from text def remove_underscore(text) text.gsub('_', '') end # changes all quotes in text to correct LaTeX quotes (`') def change_to_latex_quotes(text) text.gsub(/'(.*?)'/, '`\1\'') end # parses the prefix definitions from the ontology source # TODO: currently only works with Turtle syntax, should implement other syntaxes as well (especially rdf/xml). def parse_prefixes puts "Parsing prefixes..." if @options.verbose # @prefix fast: . prefix_array = File.open(@owl_file_name).read.scan(/@prefix (.*?): <(.*?)> ./) $prefix_mapping = Hash.new prefix_array.each do |pair| $prefix_mapping[pair[1]] = pair[0] end pp $prefix_mapping if @options.verbose puts "done!\n\n" if @options.verbose end end # The abstract super-class for OWL resources (ontology, class and property). class OWL_Resource attr_reader :label, :comment, :uri, :status, :topic, :name, :namespace_prefix, :types def initialize(ont_resource) @uri = ont_resource.uri.to_s @name = OWL_Resource. get_name(@uri) @namespace_prefix = $prefix_mapping[OWL_Resource. get_namespace(@uri)] @status = "testing" @topic = "other" @label = nil @comment = nil @types = [] $model.find(ont_resource, Redland::TYPE, nil) { |s, p, o| if o.uri.to_s.match($ONT_NS) @types << OWL_Resource.build_short_name(o) end } $model.find(ont_resource, Redland::RDFS::RDFS_LABEL, nil) { |s, p, o| @label = o.value } $model.find(ont_resource, Redland::RDFS::RDFS_COMMENT, nil) { |s, p, o| @comment = o.value } $model.find(ont_resource, $VS['term_status'], nil) { |s, p, o| @status = o.value } $model.find(ont_resource, $DCTERMS['subject'], nil) { |s, p, o| $model.find(o, Redland::RDFS::RDFS_LABEL, nil) { |s_2, p_2, o_2| @topic = o_2.value } } end def OWL_Resource.get_namespace(uri) parts = uri.split('#') if (parts.length == 2) return parts[0] + "\#" end slash_index = uri.rindex('/') return uri[0..slash_index] end def OWL_Resource.get_name(uri) parts = uri.split('#') if (parts.length == 2) return parts[1] end slash_index = uri.rindex('/') return uri[slash_index+1..uri.length] end def OWL_Resource.build_short_name(resource) if (resource.resource?) uri = resource.uri.to_s text_name = OWL_Resource. get_name(uri) prefix = $prefix_mapping[OWL_Resource. get_namespace(uri)] return "#{prefix}:#{text_name}" end end def collect_list_of_names(list) objects = [] $model.find(list, Redland::FIRST, nil) { |s, p, o| objects << OWL_Resource.build_short_name(o) } $model.find(list, Redland::REST, nil) { |s, p, o| if (o != Redland::RDFNS['nil']) objects = objects + collect_list_of_names(o) end } return objects end def collect_list_of_objects(list) objects = [] $model.find(list, Redland::FIRST, nil) { |s, p, o| objects << o } $model.find(list, Redland::REST, nil) { |s, p, o| if (o != Redland::RDFNS['nil']) objects = objects + collect_list_of_objects(o) end } return objects end def to_s uri end # The hash of a OWL_Resource = the hash of its URI. def hash return uri.hash end # Two OWL_Resource objects are equal when their URIs are equal. def eql?(other) return uri == other.uri end end class OWL_Ontology < OWL_Resource attr_reader :creation_date, :mod_date, :revision, :creators, :contributors, :previous_version, :license_statement, :logo, :documentation def initialize(ont_resource) puts "Creating a new ontology object: #{@uri}" if $app.options.verbose super @creation_date = nil @mod_date = nil @revision = nil @creators = [] @contributors = [] @previous_version = nil @license_statement = nil @logo = nil @documentation = [] $model.find(ont_resource, $DCTERMS['created'], nil) { |s, p, o| @creation_date = o.value } $model.find(ont_resource, $DCTERMS['modified'], nil) { |s, p, o| @mod_date = o.value } # for the version number, use either doap:revision or owl:versionInfo $model.find(ont_resource, $DOAP['revision'], nil) { |s, p, o| @revision = o.value } unless @revision $model.find(ont_resource, Redland::OWL::OWLNS['versionInfo'], nil) { |s, p, o| @revision = o.value } end $model.find(ont_resource, $SWRC_EXT['authorList'], nil) { |s, p, o| $model.find(o, nil, nil) { |s2, p2, o2 | index = p2.uri.to_s.slice(-1,1).to_i - 1 @creators[index] = FOAF_Person.new(o2) } } $model.find(ont_resource, $DCTERMS['contributor'], nil) { |s, p, o| $model.find(o, Redland::TYPE, nil) { |s_2, p_2, o_2 | if (o_2 == $FOAF['Person']) @contributors << FOAF_Person.new(o) else @contributors << FOAF_Agent.new(o) end } } # for specifying a prior version, use either sioc:earlier_version or owl:priorVersion # note that we may be misusing owl:priorVersion - we are pointing to a previous version of # the _document_, not the ontology namespace... $model.find(ont_resource, $SIOC['earlier_version'], nil) { |s, p, o| @previous_version = o.uri.to_s } unless @previous_version $model.find(ont_resource, Redland::OWL::OWLNS['priorVersion'], nil) { |s, p, o| @previous_version = o.uri.to_s } end $model.find(ont_resource, $HELP['licenseDoc'], nil) { |s, p, o| $model.find(o, Redland::RDFS::RDFS_COMMENT, nil) { |s_2, p_2, o_2| @license_statement = o_2.value } } $model.find(ont_resource, $FOAF['logo'], nil) { |s, p, o| @logo = o.uri.to_s } $model.find(ont_resource, $VS['userdocs'], nil) { |s, p, o| @documentation = collect_list_of_objects(o) } end def to_s return "[#{@uri},\n #{@label},\n #{@comment},\n #{@creation_date},\n #{@mod_date},\n #{@revision}]" end end class OWL_Class < OWL_Resource attr_reader :sub_classes, :super_classes, :in_domain, :in_range, :union_of, :intersection_of, :one_of def initialize(a_class) super @sub_classes = [] @super_classes = [] @in_domain = {} @in_range = [] @union_of = [] @intersection_of = [] @one_of = [] $model.find(nil, Redland::RDFS::RDFS_SUBCLASSOF, a_class) { |s, p, o| $model.find(s, Redland::TYPE, nil) { |s2, p2, type| if (type != Redland::OWL::OWLNS['DeprecatedClass']) @sub_classes << OWL_Resource.build_short_name(s) end } } @sub_classes.sort! if $app.options.alphabetise $model.find(a_class, Redland::RDFS::RDFS_SUBCLASSOF, nil) { |s, p, o| @super_classes << OWL_Resource.build_short_name(o) } @super_classes.sort! if $app.options.alphabetise $model.find(nil, Redland::RDFS::RDFS_DOMAIN, a_class) { |s, p, o| range = [] $model.find(s, Redland::RDFS::RDFS_RANGE, nil) { |s2, p2, o2| range << OWL_Resource.build_short_name(o2) } @in_domain[OWL_Resource.build_short_name(s)] = range } # @in_domain.sort! if $app.options.alphabetise $model.find(nil, Redland::RDFS::RDFS_RANGE, a_class) { |s, p, o| @in_range << OWL_Resource.build_short_name(s) } @in_range.sort! if $app.options.alphabetise $model.find(a_class, Redland::OWL::OWLNS['unionOf'], nil) { |s, p, o| @union_of = collect_list_of_names(o) } @union_of.sort! if $app.options.alphabetise $model.find(a_class, Redland::OWL::OWLNS['intersectionOf'], nil) { |s, p, o| @union_of = collect_list_of_names(o) } @union_of.sort! if $app.options.alphabetise $model.find(a_class, Redland::OWL::OWLNS['oneOf'], nil) { |s, p, o| @one_of = collect_list_of_names(o) } @one_of.sort! if $app.options.alphabetise end end class OWL_Property < OWL_Resource attr_reader :domain, :range, :inverse_of, :sub_property_of, :super_property_of, :type, :short_type def initialize(a_property) super @domain = [] @range = [] @super_property_of = [] @sub_property_of = [] @inverse_of = [] @type = [] @short_type = [] $model.find(a_property, Redland::RDFS::RDFS_LABEL, nil) { |s, p, o| @label = o.value } $model.find(a_property, Redland::RDFS::RDFS_COMMENT, nil) { |s, p, o| @comment = o.value } $model.find(a_property, Redland::TYPE, nil) { |s, p, o| a_type = o.uri.to_s @type << a_type @short_type << $prefix_mapping[OWL_Resource. get_namespace(a_type)] + ":" + OWL_Resource. get_name(a_type) } $model.find(a_property, Redland::RDFS::RDFS_DOMAIN, nil) { |s, p, o| @domain << OWL_Resource.build_short_name(o) } @domain.sort! if $app.options.alphabetise $model.find(a_property, Redland::RDFS::RDFS_RANGE, nil) { |s, p, o| @range << OWL_Resource.build_short_name(o) } @range.sort! if $app.options.alphabetise $model.find(a_property, Redland::OWL::OWLNS['inverseOf'], nil) { |s, p, o| @inverse_of << OWL_Resource.build_short_name(o) } @inverse_of.sort! if $app.options.alphabetise $model.find(a_property, Redland::RDFS::RDFS_SUBPROPERTYOF, nil) { |s, p, o| @sub_property_of << OWL_Resource.build_short_name(o) } $model.find(nil, Redland::RDFS::RDFSNS['superPropertyOf'], a_property) { |s, p, o| @sub_property_of << OWL_Resource.build_short_name(s) } @sub_property_of.sort! if $app.options.alphabetise @sub_property_of.uniq! $model.find(a_property, Redland::RDFS::RDFSNS['superPropertyOf'], nil) { |s, p, o| @super_property_of << OWL_Resource.build_short_name(o) } $model.find(nil, Redland::RDFS::RDFS_SUBPROPERTYOF, a_property) { |s, p, o| @super_property_of << OWL_Resource.build_short_name(s) } @super_property_of.sort! if $app.options.alphabetise @super_property_of.uniq! end end class FOAF_Agent attr_reader :uri, :name, :homepage def initialize(foaf_agent_resource) @uri = foaf_agent_resource.uri.to_s $model.find(foaf_agent_resource, $FOAF['name'], nil) { |s, p, o| @name = o.value } $model.find(foaf_agent_resource, $FOAF['homepage'], nil) { |s, p, o| @homepage = o.uri.to_s } end def to_s return "[uri: #{@uri}, name: #{@name}, homepage: #{@homepage}]" end def html_link if (@homepage) return "#{@name}" else return @name end end end class FOAF_Person < FOAF_Agent attr_reader :uri, :name, :homepage, :member_in, :surname def initialize(foaf_person_resource) super(foaf_person_resource) @member_in = [] @surname = "unknown" $model.find(nil, $FOAF['member'], foaf_person_resource) { |s, p, o| @member_in << FOAF_Organization.new(s) } $model.find(foaf_person_resource, $FOAF['surname'], nil) { |s, p, o| @surname = o.value } end def to_s return super + "\n member in: #{@member_in.join(', ')}" end end class FOAF_Organization < FOAF_Agent end # Create and run the application $app = OwlDoc.new(ARGV) $app.run