Chris@0
|
1 #!/usr/bin/python
|
Chris@0
|
2 # -*- coding: utf8 -*-
|
Chris@0
|
3 #
|
Chris@0
|
4 # SpecGen v5, ontology specification generator tool
|
Chris@0
|
5 # <http://forge.morfeo-project.org/wiki_en/index.php/SpecGen>
|
Chris@0
|
6 #
|
Chris@0
|
7 # Copyright (c) 2003-2008 Christopher Schmidt <crschmidt@crschmidt.net>
|
Chris@0
|
8 # Copyright (c) 2005-2008 Uldis Bojars <uldis.bojars@deri.org>
|
Chris@0
|
9 # Copyright (c) 2007-2008 Sergio Fernández <sergio.fernandez@fundacionctic.org>
|
Chris@0
|
10 #
|
Chris@0
|
11 # Previous versions of SpecGen:
|
Chris@0
|
12 # v1,2,3 by Christopher Schmidt <http://crschmidt.net/semweb/redland>
|
Chris@0
|
13 # v4 by Uldis Bojars <http://sioc-project.org/specgen>
|
Chris@0
|
14 #
|
Chris@0
|
15 # This software is licensed under the terms of the MIT License.
|
Chris@0
|
16 #
|
Chris@0
|
17 # Permission is hereby granted, free of charge, to any person obtaining a copy
|
Chris@0
|
18 # of this software and associated documentation files (the "Software"), to deal
|
Chris@0
|
19 # in the Software without restriction, including without limitation the rights
|
Chris@0
|
20 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
Chris@0
|
21 # copies of the Software, and to permit persons to whom the Software is
|
Chris@0
|
22 # furnished to do so, subject to the following conditions:
|
Chris@0
|
23 #
|
Chris@0
|
24 # The above copyright notice and this permission notice shall be included in
|
Chris@0
|
25 # all copies or substantial portions of the Software.
|
Chris@0
|
26 #
|
Chris@0
|
27 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
Chris@0
|
28 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
Chris@0
|
29 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
Chris@0
|
30 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
Chris@0
|
31 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
Chris@0
|
32 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
Chris@0
|
33 # THE SOFTWARE.
|
Chris@0
|
34
|
Chris@0
|
35 __version__ = "5.4.2"
|
Chris@0
|
36 __authors__ = "Christopher Schmidt, Uldis Bojars, Sergio Fernández"
|
Chris@0
|
37 __license__ = "MIT License <http://www.opensource.org/licenses/mit-license.php>"
|
Chris@0
|
38 __contact__ = "specgen-devel@lists.morfeo-project.org"
|
Chris@0
|
39 __date__ = "2008-12-02"
|
Chris@0
|
40
|
Chris@0
|
41 import os
|
Chris@0
|
42 import sys
|
Chris@0
|
43 import time
|
Chris@0
|
44 import re
|
Chris@0
|
45 import urllib
|
Chris@0
|
46
|
Chris@0
|
47 try:
|
Chris@0
|
48 import RDF
|
Chris@0
|
49 except ImportError:
|
Chris@0
|
50 version = sys.version.split(" ")[0]
|
Chris@0
|
51 if version.startswith("2.5"):
|
Chris@0
|
52 sys.path.append("/usr/lib/python2.4/site-packages/")
|
Chris@0
|
53 else:
|
Chris@0
|
54 sys.path.append("/usr/lib/python2.5/site-packages/")
|
Chris@0
|
55 try:
|
Chris@0
|
56 import RDF
|
Chris@0
|
57 except:
|
Chris@0
|
58 sys.exit("Error importing RedLand bindings for Python; check if it is installed correctly")
|
Chris@0
|
59
|
Chris@0
|
60 #global vars
|
Chris@0
|
61 classranges = {}
|
Chris@0
|
62 classdomains = {}
|
Chris@0
|
63 spec_url = None
|
Chris@0
|
64 spec_ns = None
|
Chris@0
|
65 spec_pre = None
|
Chris@0
|
66 ns_list = { "http://www.w3.org/1999/02/22-rdf-syntax-ns#" : "rdf",
|
Chris@0
|
67 "http://www.w3.org/2000/01/rdf-schema#" : "rdfs",
|
Chris@0
|
68 "http://www.w3.org/2002/07/owl#" : "owl",
|
Chris@0
|
69 "http://www.w3.org/2001/XMLSchema#" : "xsd",
|
Chris@0
|
70 "http://rdfs.org/sioc/ns#" : "sioc",
|
Chris@0
|
71 "http://xmlns.com/foaf/0.1/" : "foaf",
|
Chris@0
|
72 "http://purl.org/dc/elements/1.1/" : "dc",
|
Chris@0
|
73 "http://purl.org/dc/terms/" : "dct",
|
Chris@0
|
74 "http://usefulinc.com/ns/doap#" : "doap",
|
Chris@0
|
75 "http://www.w3.org/2003/06/sw-vocab-status/ns#" : "status",
|
Chris@0
|
76 "http://purl.org/rss/1.0/modules/content/" : "content",
|
Chris@0
|
77 "http://www.w3.org/2003/01/geo/wgs84_pos#" : "geo",
|
Chris@0
|
78 "http://www.w3.org/2004/02/skos/core#" : "skos"
|
Chris@0
|
79 }
|
Chris@0
|
80
|
Chris@0
|
81 rdf = RDF.NS('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
|
Chris@0
|
82 rdfs = RDF.NS('http://www.w3.org/2000/01/rdf-schema#')
|
Chris@0
|
83 owl = RDF.NS('http://www.w3.org/2002/07/owl#')
|
Chris@0
|
84 vs = RDF.NS('http://www.w3.org/2003/06/sw-vocab-status/ns#')
|
Chris@0
|
85
|
Chris@0
|
86 termdir = './doc' #TODO
|
Chris@0
|
87
|
Chris@0
|
88
|
Chris@0
|
89 def niceName(uri):
|
Chris@0
|
90 regexp = re.compile( "^(.*[/#])([^/#]+)$" )
|
Chris@0
|
91 rez = regexp.search( uri )
|
Chris@0
|
92 pref = rez.group(1)
|
Chris@0
|
93 #return ns_list.get(pref, pref) + ":" + rez.group(2)
|
Chris@0
|
94 if ns_list.has_key(pref):
|
Chris@0
|
95 return ns_list.get(pref, pref) + ":" + rez.group(2)
|
Chris@0
|
96 else:
|
Chris@0
|
97 return uri
|
Chris@0
|
98
|
Chris@0
|
99
|
Chris@0
|
100 def setTermDir(directory):
|
Chris@0
|
101 global termdir
|
Chris@0
|
102 termdir = directory
|
Chris@0
|
103
|
Chris@0
|
104
|
Chris@0
|
105 def termlink(string):
|
Chris@0
|
106 """FOAF specific: function which replaces <code>foaf:*</code> with a
|
Chris@0
|
107 link to the term in the document."""
|
Chris@0
|
108 return re.sub(r"<code>" + spec_pre + r":(\w+)</code>", r"""<code><a href="#term_\1">""" + spec_pre + r""":\1</a></code>""", string)
|
Chris@0
|
109
|
Chris@0
|
110
|
Chris@0
|
111 def return_name(m, urinode):
|
Chris@0
|
112 "Trims the FOAF namespace out of a term to give a name to the term."
|
Chris@0
|
113 return str(urinode.uri).replace(spec_url, "")
|
Chris@0
|
114
|
Chris@0
|
115
|
Chris@0
|
116 def get_rdfs(m, urinode):
|
Chris@0
|
117 "Returns label and comment given an RDF.Node with a URI in it"
|
Chris@0
|
118 comment = ''
|
Chris@0
|
119 label = ''
|
Chris@0
|
120 if (type(urinode)==str):
|
Chris@0
|
121 urinode = RDF.Uri(urinode)
|
Chris@0
|
122 l = m.find_statements(RDF.Statement(urinode, rdfs.label, None))
|
Chris@0
|
123 if l.current():
|
Chris@0
|
124 label = l.current().object.literal_value['string']
|
Chris@0
|
125 c = m.find_statements(RDF.Statement(urinode, rdfs.comment, None))
|
Chris@0
|
126 if c.current():
|
Chris@0
|
127 comment = c.current().object.literal_value['string']
|
Chris@0
|
128 return label, comment
|
Chris@0
|
129
|
Chris@0
|
130
|
Chris@0
|
131 def get_status(m, urinode):
|
Chris@0
|
132 "Returns the status text for a term."
|
Chris@0
|
133 status = ''
|
Chris@0
|
134 s = m.find_statements(RDF.Statement(urinode, vs.term_status, None))
|
Chris@0
|
135 if s.current():
|
Chris@0
|
136 return s.current().object.literal_value['string']
|
Chris@0
|
137
|
Chris@0
|
138
|
Chris@0
|
139 def htmlDocInfo( t ):
|
Chris@0
|
140 """Opens a file based on the term name (t) and termdir directory (global).
|
Chris@0
|
141 Reads in the file, and returns a linkified version of it."""
|
Chris@0
|
142 doc = ""
|
Chris@0
|
143 try:
|
Chris@0
|
144 f = open("%s/%s.en" % (termdir, t), "r")
|
Chris@0
|
145 doc = f.read()
|
Chris@0
|
146 doc = termlink(doc)
|
Chris@0
|
147 except:
|
Chris@0
|
148 return "" # "<p>No detailed documentation for this term.</p>"
|
Chris@0
|
149 return doc
|
Chris@0
|
150
|
Chris@0
|
151
|
Chris@0
|
152 def owlVersionInfo(m):
|
Chris@0
|
153 v = m.find_statements(RDF.Statement(None, owl.versionInfo, None))
|
Chris@0
|
154 if v.current():
|
Chris@0
|
155 return v.current().object.literal_value['string']
|
Chris@0
|
156 else:
|
Chris@0
|
157 return ""
|
Chris@0
|
158
|
Chris@0
|
159
|
Chris@0
|
160 def rdfsPropertyInfo(term,m):
|
Chris@0
|
161 """Generate HTML for properties: Domain, range, status."""
|
Chris@0
|
162 global classranges
|
Chris@0
|
163 global classdomains
|
Chris@0
|
164 doc = ""
|
Chris@0
|
165 range = ""
|
Chris@0
|
166 domain = ""
|
Chris@0
|
167
|
Chris@0
|
168 #find subPropertyOf information
|
Chris@0
|
169 o = m.find_statements( RDF.Statement(term, rdfs.subPropertyOf, None) )
|
Chris@0
|
170 if o.current():
|
Chris@0
|
171 rlist = ''
|
Chris@0
|
172 for st in o:
|
Chris@0
|
173 k = getTermLink(str(st.object.uri))
|
Chris@0
|
174 rlist += "<dd>%s</dd>" % k
|
Chris@0
|
175 doc += "<dt>sub-property-of:</dt> %s" % rlist
|
Chris@0
|
176
|
Chris@0
|
177 #domain stuff
|
Chris@0
|
178 domains = m.find_statements(RDF.Statement(term, rdfs.domain, None))
|
Chris@0
|
179 domainsdoc = ""
|
Chris@0
|
180 for d in domains:
|
Chris@0
|
181 collection = m.find_statements(RDF.Statement(d.object, owl.unionOf, None))
|
Chris@0
|
182 if collection.current():
|
Chris@0
|
183 uris = parseCollection(m, collection)
|
Chris@0
|
184 for uri in uris:
|
Chris@0
|
185 domainsdoc += "<dd>%s</dd>" % getTermLink(uri)
|
Chris@0
|
186 add(classdomains, uri, term.uri)
|
Chris@0
|
187 else:
|
Chris@0
|
188 if not d.object.is_blank():
|
Chris@0
|
189 domainsdoc += "<dd>%s</dd>" % getTermLink(str(d.object.uri))
|
Chris@0
|
190 if (len(domainsdoc)>0):
|
Chris@0
|
191 doc += "<dt>Domain:</dt> %s" % domainsdoc
|
Chris@0
|
192
|
Chris@0
|
193 #range stuff
|
Chris@0
|
194 ranges = m.find_statements(RDF.Statement(term, rdfs.range, None))
|
Chris@0
|
195 rangesdoc = ""
|
Chris@0
|
196 for r in ranges:
|
Chris@0
|
197 collection = m.find_statements(RDF.Statement(r.object, owl.unionOf, None))
|
Chris@0
|
198 if collection.current():
|
Chris@0
|
199 uris = parseCollection(m, collection)
|
Chris@0
|
200 for uri in uris:
|
Chris@0
|
201 rangesdoc += "<dd>%s</dd>" % getTermLink(uri)
|
Chris@0
|
202 add(classranges, uri, term.uri)
|
Chris@0
|
203 else:
|
Chris@0
|
204 if not r.object.is_blank():
|
Chris@0
|
205 rangesdoc += "<dd>%s</dd>" % getTermLink(str(r.object.uri))
|
Chris@0
|
206 if (len(rangesdoc)>0):
|
Chris@0
|
207 doc += "<dt>Range:</dt> %s" % rangesdoc
|
Chris@0
|
208
|
Chris@0
|
209 return doc
|
Chris@0
|
210
|
Chris@0
|
211
|
Chris@0
|
212 def parseCollection(model, collection):
|
Chris@0
|
213 # #propertyA a rdf:Property ;
|
Chris@0
|
214 # rdfs:domain [
|
Chris@0
|
215 # a owl:Class ;
|
Chris@0
|
216 # owl:unionOf [
|
Chris@0
|
217 # rdf:parseType Collection ;
|
Chris@0
|
218 # #Foo a owl:Class ;
|
Chris@0
|
219 # #Bar a owl:Class
|
Chris@0
|
220 # ]
|
Chris@0
|
221 # ]
|
Chris@0
|
222 #
|
Chris@0
|
223 # seeAlso "Collections in RDF"
|
Chris@0
|
224
|
Chris@0
|
225 uris = []
|
Chris@0
|
226
|
Chris@0
|
227 rdflist = model.find_statements(RDF.Statement(collection.current().object, None, None))
|
Chris@0
|
228 while rdflist and rdflist.current() and not rdflist.current().object.is_blank():
|
Chris@0
|
229 one = rdflist.current()
|
Chris@0
|
230 if not one.object.is_blank():
|
Chris@0
|
231 uris.append(str(one.object.uri))
|
Chris@0
|
232 rdflist.next()
|
Chris@0
|
233 one = rdflist.current()
|
Chris@0
|
234 if one.predicate == rdf.rest:
|
Chris@0
|
235 rdflist = model.find_statements(RDF.Statement(one.object, None, None))
|
Chris@0
|
236
|
Chris@0
|
237 return uris
|
Chris@0
|
238
|
Chris@0
|
239
|
Chris@0
|
240 def getTermLink(uri):
|
Chris@0
|
241 uri = str(uri)
|
Chris@0
|
242 if (uri.startswith(spec_url)):
|
Chris@0
|
243 return '<a href="#term_%s" style="font-family: monospace;">%s</a>' % (uri.replace(spec_url, ""), niceName(uri))
|
Chris@0
|
244 else:
|
Chris@0
|
245 return '<a href="%s" style="font-family: monospace;">%s</a>' % (uri, niceName(uri))
|
Chris@0
|
246
|
Chris@0
|
247
|
Chris@0
|
248 def rdfsClassInfo(term,m):
|
Chris@0
|
249 """Generate rdfs-type information for Classes: ranges, and domains."""
|
Chris@0
|
250 global classranges
|
Chris@0
|
251 global classdomains
|
Chris@0
|
252 doc = ""
|
Chris@0
|
253
|
Chris@0
|
254 #patch to control incoming strings (FIXME, why??? drop it!)
|
Chris@0
|
255 try:
|
Chris@0
|
256 term.uri
|
Chris@0
|
257 except:
|
Chris@0
|
258 term = RDF.Node(RDF.Uri(term))
|
Chris@0
|
259
|
Chris@0
|
260 # Find subClassOf information
|
Chris@0
|
261 o = m.find_statements( RDF.Statement(term, rdfs.subClassOf, None) )
|
Chris@0
|
262 if o.current():
|
Chris@0
|
263 doc += "<dt>sub-class-of:</dt>"
|
Chris@0
|
264 superclasses = []
|
Chris@0
|
265 for st in o:
|
Chris@0
|
266 if not st.object.is_blank():
|
Chris@0
|
267 uri = str(st.object.uri)
|
Chris@0
|
268 if (not uri in superclasses):
|
Chris@0
|
269 superclasses.append(uri)
|
Chris@0
|
270 for superclass in superclasses:
|
Chris@0
|
271 doc += "<dd>%s</dd>" % getTermLink(superclass)
|
Chris@0
|
272
|
Chris@0
|
273 # Find out about properties which have rdfs:domain of t
|
Chris@0
|
274 d = classdomains.get(str(term.uri), "")
|
Chris@0
|
275 if d:
|
Chris@0
|
276 dlist = ''
|
Chris@0
|
277 for k in d:
|
Chris@0
|
278 dlist += "<dd>%s</dd>" % getTermLink(k)
|
Chris@0
|
279 doc += "<dt>in-domain-of:</dt>" + dlist
|
Chris@0
|
280
|
Chris@0
|
281 # Find out about properties which have rdfs:range of t
|
Chris@0
|
282 r = classranges.get(str(term.uri), "")
|
Chris@0
|
283 if r:
|
Chris@0
|
284 rlist = ''
|
Chris@0
|
285 for k in r:
|
Chris@0
|
286 rlist += "<dd>%s</dd>" % getTermLink(k)
|
Chris@0
|
287 doc += "<dt>in-range-of:</dt>" + rlist
|
Chris@0
|
288
|
Chris@0
|
289 return doc
|
Chris@0
|
290
|
Chris@0
|
291 def rdfsInstanceInfo(term,m):
|
Chris@0
|
292 """Generate rdfs-type information for instances"""
|
Chris@0
|
293 doc = ""
|
Chris@0
|
294
|
Chris@0
|
295 t = m.find_statements( RDF.Statement(RDF.Node(RDF.Uri(term)), rdf.type, None) )
|
Chris@0
|
296 if t.current():
|
Chris@0
|
297 doc += "<dt>RDF Type:</dt>"
|
Chris@0
|
298 while t.current():
|
Chris@0
|
299 doc += "<dd>%s</dd>" % getTermLink(str(t.current().object.uri))
|
Chris@0
|
300 t.next()
|
Chris@0
|
301
|
Chris@0
|
302 return doc
|
Chris@0
|
303
|
Chris@0
|
304
|
Chris@0
|
305 def owlInfo(term,m):
|
Chris@0
|
306 """Returns an extra information that is defined about a term (an RDF.Node()) using OWL."""
|
Chris@0
|
307 res = ''
|
Chris@0
|
308
|
Chris@0
|
309 # FIXME: refactor this code
|
Chris@0
|
310
|
Chris@0
|
311 # Inverse properties ( owl:inverseOf )
|
Chris@0
|
312 o = m.find_statements( RDF.Statement(term, owl.inverseOf, None) )
|
Chris@0
|
313 if o.current():
|
Chris@0
|
314 res += "<dt>Inverse:</dt>"
|
Chris@0
|
315 for st in o:
|
Chris@0
|
316 res += "<dd>%s</dd>" % getTermLink(str(st.object.uri))
|
Chris@0
|
317
|
Chris@0
|
318 # Datatype Property ( owl.DatatypeProperty )
|
Chris@0
|
319 o = m.find_statements( RDF.Statement(term, rdf.type, owl.DatatypeProperty) )
|
Chris@0
|
320 if o.current():
|
Chris@0
|
321 res += "<dt>OWL Type:</dt><dd>DatatypeProperty</dd>\n"
|
Chris@0
|
322
|
Chris@0
|
323 # Object Property ( owl.ObjectProperty )
|
Chris@0
|
324 o = m.find_statements( RDF.Statement(term, rdf.type, owl.ObjectProperty) )
|
Chris@0
|
325 if o.current():
|
Chris@0
|
326 res += "<dt>OWL Type:</dt><dd>ObjectProperty</dd>\n"
|
Chris@0
|
327
|
Chris@0
|
328 # Annotation Property ( owl.AnnotationProperty )
|
Chris@0
|
329 o = m.find_statements( RDF.Statement(term, rdf.type, owl.AnnotationProperty) )
|
Chris@0
|
330 if o.current():
|
Chris@0
|
331 res += "<dt>OWL Type:</dt><dd>AnnotationProperty</dd>\n"
|
Chris@0
|
332
|
Chris@0
|
333 # IFPs ( owl.InverseFunctionalProperty )
|
Chris@0
|
334 o = m.find_statements( RDF.Statement(term, rdf.type, owl.InverseFunctionalProperty) )
|
Chris@0
|
335 if o.current():
|
Chris@0
|
336 res += "<dt>OWL Type:</dt><dd>InverseFunctionalProperty (uniquely identifying property)</dd>\n"
|
Chris@0
|
337
|
Chris@0
|
338 # Symmertic Property ( owl.SymmetricProperty )
|
Chris@0
|
339 o = m.find_statements( RDF.Statement(term, rdf.type, owl.SymmetricProperty) )
|
Chris@0
|
340 if o.current():
|
Chris@0
|
341 res += "<dt>OWL Type:</dt><dd>SymmetricProperty</dd>\n"
|
Chris@0
|
342
|
Chris@0
|
343 return res
|
Chris@0
|
344
|
Chris@0
|
345
|
Chris@0
|
346 def docTerms(category, list, m):
|
Chris@0
|
347 """
|
Chris@0
|
348 A wrapper class for listing all the terms in a specific class (either
|
Chris@0
|
349 Properties, or Classes. Category is 'Property' or 'Class', list is a
|
Chris@0
|
350 list of term names (strings), return value is a chunk of HTML.
|
Chris@0
|
351 """
|
Chris@0
|
352 doc = ""
|
Chris@0
|
353 nspre = spec_pre
|
Chris@0
|
354 for t in list:
|
Chris@0
|
355 if (t.startswith(spec_url)) and (len(t[len(spec_url):].split("/"))<2):
|
Chris@0
|
356 term = t
|
Chris@0
|
357 t = t.split(spec_url[-1])[-1]
|
Chris@0
|
358 curie = t#"%s:%s" % (nspre, t)
|
Chris@0
|
359 else:
|
Chris@0
|
360 if t.startswith("http://"):
|
Chris@0
|
361 term = t
|
Chris@0
|
362 curie = getShortName(t)
|
Chris@0
|
363 t = getAnchor(t)
|
Chris@0
|
364 else:
|
Chris@0
|
365 term = spec_ns[t]
|
Chris@0
|
366 curie = "%s:%s" % (nspre, t)
|
Chris@0
|
367
|
Chris@0
|
368 doc += """<div class="specterm" id="term_%s">\n<h3>%s: %s</h3>\n""" % (t, category, curie)
|
Chris@0
|
369 try:
|
Chris@0
|
370 term_uri = term.uri
|
Chris@0
|
371 except:
|
Chris@0
|
372 term_uri = term
|
Chris@0
|
373 doc += """<p style="font-family:monospace; font-size:0.em;">URI: <a href="%s">%s</a></p>""" % (term_uri, term_uri)
|
Chris@0
|
374 label, comment = get_rdfs(m, term)
|
Chris@0
|
375 status = get_status(m, term)
|
Chris@0
|
376 doc += "<p><em>%s</em> - %s </p>" % (label, comment)
|
Chris@0
|
377 terminfo = ""
|
Chris@0
|
378 if category=='Property':
|
Chris@0
|
379 terminfo += owlInfo(term,m)
|
Chris@0
|
380 terminfo += rdfsPropertyInfo(term,m)
|
Chris@0
|
381 if category=='Class':
|
Chris@0
|
382 terminfo += rdfsClassInfo(term,m)
|
Chris@0
|
383 if category=='Instance':
|
Chris@0
|
384 terminfo += rdfsInstanceInfo(term,m)
|
Chris@0
|
385 if (len(terminfo)>0): #to prevent empty list (bug #882)
|
Chris@0
|
386 doc += "\n<dl>%s</dl>\n" % terminfo
|
Chris@0
|
387 doc += htmlDocInfo(t)
|
Chris@0
|
388 doc += "<p style=\"float: right; font-size: small;\">[<a href=\"#sec-glance\">back to top</a>]</p>\n\n"
|
Chris@0
|
389 doc += "\n\n</div>\n\n"
|
Chris@0
|
390
|
Chris@0
|
391 return doc
|
Chris@0
|
392
|
Chris@0
|
393
|
Chris@0
|
394 def getShortName(uri):
|
Chris@0
|
395 if ("#" in uri):
|
Chris@0
|
396 return uri.split("#")[-1]
|
Chris@0
|
397 else:
|
Chris@0
|
398 return uri.split("/")[-1]
|
Chris@0
|
399
|
Chris@0
|
400
|
Chris@0
|
401 def getAnchor(uri):
|
Chris@0
|
402 if (uri.startswith(spec_url)):
|
Chris@0
|
403 return uri[len(spec_url):].replace("/","_")
|
Chris@0
|
404 else:
|
Chris@0
|
405 return getShortName(uri)
|
Chris@0
|
406
|
Chris@0
|
407
|
Chris@0
|
408 def buildazlist(classlist, proplist, instalist=None):
|
Chris@0
|
409 """
|
Chris@0
|
410 Builds the A-Z list of terms. Args are a list of classes (strings) and
|
Chris@0
|
411 a list of props (strings)
|
Chris@0
|
412 """
|
Chris@0
|
413 azlist = '<div style="padding: 1em; border: dotted; background-color: #ddd;">'
|
Chris@0
|
414
|
Chris@0
|
415 if (len(classlist)>0):
|
Chris@0
|
416 azlist += "<p>Classes: "
|
Chris@0
|
417 classlist.sort()
|
Chris@0
|
418 for c in classlist:
|
Chris@0
|
419 if c.startswith(spec_url):
|
Chris@0
|
420 c = c.split(spec_url[-1])[1]
|
Chris@0
|
421 azlist = """%s <a href="#term_%s">%s</a>, """ % (azlist, c, c)
|
Chris@0
|
422 azlist = """%s\n</p>""" % azlist
|
Chris@0
|
423
|
Chris@0
|
424 if (len(proplist)>0):
|
Chris@0
|
425 azlist += "<p>Properties: "
|
Chris@0
|
426 proplist.sort()
|
Chris@0
|
427 for p in proplist:
|
Chris@0
|
428 if p.startswith(spec_url):
|
Chris@0
|
429 p = p.split(spec_url[-1])[1]
|
Chris@0
|
430 azlist = """%s <a href="#term_%s">%s</a>, """ % (azlist, p, p)
|
Chris@0
|
431 azlist = """%s\n</p>""" % azlist
|
Chris@0
|
432
|
Chris@0
|
433 if (instalist!=None and len(instalist)>0):
|
Chris@0
|
434 azlist += "<p>Instances: "
|
Chris@0
|
435 for i in instalist:
|
Chris@0
|
436 p = getShortName(i)
|
Chris@0
|
437 anchor = getAnchor(i)
|
Chris@0
|
438 azlist = """%s <a href="#term_%s">%s</a>, """ % (azlist, anchor, p)
|
Chris@0
|
439 azlist = """%s\n</p>""" % azlist
|
Chris@0
|
440
|
Chris@0
|
441 azlist = """%s\n</div>""" % azlist
|
Chris@0
|
442 return azlist
|
Chris@0
|
443
|
Chris@0
|
444
|
Chris@0
|
445 def build_simple_list(classlist, proplist, instalist=None):
|
Chris@0
|
446 """
|
Chris@0
|
447 Builds a simple <ul> A-Z list of terms. Args are a list of classes (strings) and
|
Chris@0
|
448 a list of props (strings)
|
Chris@0
|
449 """
|
Chris@0
|
450
|
Chris@0
|
451 azlist = """<div style="padding: 5px; border: dotted; background-color: #ddd;">"""
|
Chris@0
|
452 azlist = """%s\n<p>Classes:""" % azlist
|
Chris@0
|
453 azlist += """\n<ul>"""
|
Chris@0
|
454
|
Chris@0
|
455 classlist.sort()
|
Chris@0
|
456 for c in classlist:
|
Chris@0
|
457 azlist += """\n <li><a href="#term_%s">%s</a></li>""" % (c.replace(" ", ""), c)
|
Chris@0
|
458 azlist = """%s\n</ul></p>""" % azlist
|
Chris@0
|
459
|
Chris@0
|
460 azlist = """%s\n<p>Properties:""" % azlist
|
Chris@0
|
461 azlist += """\n<ul>"""
|
Chris@0
|
462 proplist.sort()
|
Chris@0
|
463 for p in proplist:
|
Chris@0
|
464 azlist += """\n <li><a href="#term_%s">%s</a></li>""" % (p.replace(" ", ""), p)
|
Chris@0
|
465 azlist = """%s\n</ul></p>""" % azlist
|
Chris@0
|
466
|
Chris@0
|
467 #FIXME: instances
|
Chris@0
|
468
|
Chris@0
|
469 azlist = """%s\n</div>""" % azlist
|
Chris@0
|
470 return azlist
|
Chris@0
|
471
|
Chris@0
|
472
|
Chris@0
|
473 def add(where, key, value):
|
Chris@0
|
474 if not where.has_key(key):
|
Chris@0
|
475 where[key] = []
|
Chris@0
|
476 if not value in where[key]:
|
Chris@0
|
477 where[key].append(value)
|
Chris@0
|
478
|
Chris@0
|
479
|
Chris@0
|
480 def specInformation(m, ns):
|
Chris@0
|
481 """
|
Chris@0
|
482 Read through the spec (provided as a Redland model) and return classlist
|
Chris@0
|
483 and proplist. Global variables classranges and classdomains are also filled
|
Chris@0
|
484 as appropriate.
|
Chris@0
|
485 """
|
Chris@0
|
486 global classranges
|
Chris@0
|
487 global classdomains
|
Chris@0
|
488
|
Chris@0
|
489 # Find the class information: Ranges, domains, and list of all names.
|
Chris@0
|
490 classtypes = [rdfs.Class, owl.Class]
|
Chris@0
|
491 classlist = []
|
Chris@0
|
492 for onetype in classtypes:
|
Chris@0
|
493 for classStatement in m.find_statements(RDF.Statement(None, rdf.type, onetype)):
|
Chris@0
|
494 for range in m.find_statements(RDF.Statement(None, rdfs.range, classStatement.subject)):
|
Chris@0
|
495 if not m.contains_statement( RDF.Statement( range.subject, rdf.type, owl.DeprecatedProperty )):
|
Chris@0
|
496 if not classStatement.subject.is_blank():
|
Chris@0
|
497 add(classranges, str(classStatement.subject.uri), str(range.subject.uri))
|
Chris@0
|
498 for domain in m.find_statements(RDF.Statement(None, rdfs.domain, classStatement.subject)):
|
Chris@0
|
499 if not m.contains_statement( RDF.Statement( domain.subject, rdf.type, owl.DeprecatedProperty )):
|
Chris@0
|
500 if not classStatement.subject.is_blank():
|
Chris@0
|
501 add(classdomains, str(classStatement.subject.uri), str(domain.subject.uri))
|
Chris@0
|
502 if not classStatement.subject.is_blank():
|
Chris@0
|
503 uri = str(classStatement.subject.uri)
|
Chris@0
|
504 name = return_name(m, classStatement.subject)
|
Chris@0
|
505 if name not in classlist and uri.startswith(spec_url):
|
Chris@0
|
506 classlist.append(return_name(m, classStatement.subject))
|
Chris@0
|
507
|
Chris@0
|
508 # Create a list of properties in the schema.
|
Chris@0
|
509 proptypes = [rdf.Property, owl.ObjectProperty, owl.DatatypeProperty, owl.AnnotationProperty]
|
Chris@0
|
510 proplist = []
|
Chris@0
|
511 for onetype in proptypes:
|
Chris@0
|
512 for propertyStatement in m.find_statements(RDF.Statement(None, rdf.type, onetype)):
|
Chris@0
|
513 uri = str(propertyStatement.subject.uri)
|
Chris@0
|
514 name = return_name(m, propertyStatement.subject)
|
Chris@0
|
515 if uri.startswith(ns) and not name in proplist:
|
Chris@0
|
516 proplist.append(name)
|
Chris@0
|
517
|
Chris@0
|
518 return classlist, proplist
|
Chris@0
|
519
|
Chris@0
|
520 def getInstances(model, classes, properties):
|
Chris@0
|
521 """
|
Chris@0
|
522 Extract all resources instanced in the ontology
|
Chris@0
|
523 (aka "everything that is not a class or a property")
|
Chris@0
|
524 """
|
Chris@0
|
525 instances = []
|
Chris@0
|
526 for one in classes:
|
Chris@0
|
527 for i in model.find_statements(RDF.Statement(None, rdf.type, spec_ns[one])):
|
Chris@0
|
528 uri = str(i.subject.uri)
|
Chris@0
|
529 if not uri in instances:
|
Chris@0
|
530 instances.append(uri)
|
Chris@0
|
531 for i in model.find_statements(RDF.Statement(None, rdfs.isDefinedBy, RDF.Uri(spec_url))):
|
Chris@0
|
532 uri = str(i.subject.uri)
|
Chris@0
|
533 if (uri.startswith(spec_url)):
|
Chris@0
|
534 uri = uri[len(spec_url):]
|
Chris@0
|
535 if ((not uri in instances) and (not uri in classes)):
|
Chris@0
|
536 instances.append(uri)
|
Chris@0
|
537 return instances
|
Chris@0
|
538
|
Chris@0
|
539
|
Chris@0
|
540 def specgen(specloc, template, instances=False, mode="spec"):
|
Chris@0
|
541 """The meat and potatoes: Everything starts here."""
|
Chris@0
|
542
|
Chris@0
|
543 global spec_url
|
Chris@0
|
544 global spec_ns
|
Chris@0
|
545 global ns_list
|
Chris@0
|
546
|
Chris@0
|
547 m = RDF.Model()
|
Chris@0
|
548 p = RDF.Parser()
|
Chris@0
|
549 try:
|
Chris@0
|
550 p.parse_into_model(m, specloc)
|
Chris@0
|
551 except IOError, e:
|
Chris@0
|
552 print "Error reading from ontology:", str(e)
|
Chris@0
|
553 usage()
|
Chris@0
|
554 except RDF.RedlandError, e:
|
Chris@0
|
555 print "Error parsing the ontology"
|
Chris@0
|
556
|
Chris@0
|
557 spec_url = getOntologyNS(m)
|
Chris@0
|
558 spec_ns = RDF.NS(spec_url)
|
Chris@0
|
559 ns_list[spec_url] = spec_pre
|
Chris@0
|
560
|
Chris@0
|
561 classlist, proplist = specInformation(m, spec_url)
|
Chris@0
|
562 classlist = sorted(classlist)
|
Chris@0
|
563 proplist = sorted(proplist)
|
Chris@0
|
564
|
Chris@0
|
565 instalist = None
|
Chris@0
|
566 if instances:
|
Chris@0
|
567 instalist = getInstances(m, classlist, proplist)
|
Chris@0
|
568 instalist.sort(lambda x, y: cmp(getShortName(x).lower(), getShortName(y).lower()))
|
Chris@0
|
569
|
Chris@0
|
570 if mode == "spec":
|
Chris@0
|
571 # Build HTML list of terms.
|
Chris@0
|
572 azlist = buildazlist(classlist, proplist, instalist)
|
Chris@0
|
573 elif mode == "list":
|
Chris@0
|
574 # Build simple <ul> list of terms.
|
Chris@0
|
575 azlist = build_simple_list(classlist, proplist, instalist)
|
Chris@0
|
576
|
Chris@0
|
577 # Generate Term HTML
|
Chris@0
|
578 termlist = docTerms('Property', proplist, m)
|
Chris@0
|
579 termlist = docTerms('Class', classlist, m) + termlist
|
Chris@0
|
580 if instances:
|
Chris@0
|
581 termlist += docTerms('Instance', instalist, m)
|
Chris@0
|
582
|
Chris@0
|
583 # Generate RDF from original namespace.
|
Chris@0
|
584 u = urllib.urlopen(specloc)
|
Chris@0
|
585 rdfdata = u.read()
|
Chris@0
|
586 rdfdata = re.sub(r"(<\?xml version.*\?>)", "", rdfdata)
|
Chris@0
|
587 rdfdata = re.sub(r"(<!DOCTYPE[^]]*]>)", "", rdfdata)
|
Chris@0
|
588 #rdfdata.replace("""<?xml version="1.0"?>""", "")
|
Chris@0
|
589
|
Chris@0
|
590 # print template % (azlist.encode("utf-8"), termlist.encode("utf-8"), rdfdata.encode("ISO-8859-1"))
|
Chris@0
|
591 template = re.sub(r"^#format \w*\n", "", template)
|
Chris@0
|
592 template = re.sub(r"\$VersionInfo\$", owlVersionInfo(m).encode("utf-8"), template)
|
Chris@0
|
593
|
Chris@0
|
594 # NOTE: This works with the assumtpion that all "%" in the template are escaped to "%%" and it
|
Chris@0
|
595 # contains the same number of "%s" as the number of parameters in % ( ...parameters here... )
|
Chris@0
|
596 template = template % (azlist, termlist.encode("utf-8"));
|
Chris@0
|
597 template += "<!-- specification regenerated by SpecGen5 at " + time.strftime('%X %x %Z') + " -->"
|
Chris@0
|
598
|
Chris@0
|
599 return template
|
Chris@0
|
600
|
Chris@0
|
601
|
Chris@0
|
602 def save(path, text):
|
Chris@0
|
603 try:
|
Chris@0
|
604 f = open(path, "w")
|
Chris@0
|
605 f.write(text)
|
Chris@0
|
606 f.flush()
|
Chris@0
|
607 f.close()
|
Chris@0
|
608 except Exception, e:
|
Chris@0
|
609 print "Error writting in file \"" + path + "\": " + str(e)
|
Chris@0
|
610
|
Chris@0
|
611
|
Chris@0
|
612 def getOntologyNS(m):
|
Chris@0
|
613 ns = None
|
Chris@0
|
614 o = m.find_statements(RDF.Statement(None, rdf.type, owl.Ontology))
|
Chris@0
|
615 if o.current():
|
Chris@0
|
616 s = o.current().subject
|
Chris@0
|
617 if (not s.is_blank()):
|
Chris@0
|
618 ns = str(s.uri)
|
Chris@0
|
619 if (ns[-1]!="/" and ns[-1]!="#"):
|
Chris@0
|
620 ns += "#"
|
Chris@0
|
621
|
Chris@0
|
622 if (ns == None):
|
Chris@0
|
623 sys.exit("Impossible to get ontology's namespace")
|
Chris@0
|
624 else:
|
Chris@0
|
625 return ns
|
Chris@0
|
626
|
Chris@0
|
627
|
Chris@0
|
628 def __getScriptPath():
|
Chris@0
|
629 path = sys.argv[0]
|
Chris@0
|
630 if path.startswith("./"):
|
Chris@0
|
631 return path
|
Chris@0
|
632 else:
|
Chris@0
|
633 base = "/".join(path.split("/")[:-1])
|
Chris@0
|
634 for one in os.environ["PATH"].split(":"):
|
Chris@0
|
635 if base == one:
|
Chris@0
|
636 return path.split("/")[-1]
|
Chris@0
|
637 return path
|
Chris@0
|
638
|
Chris@0
|
639
|
Chris@0
|
640 def usage():
|
Chris@0
|
641 script = __getScriptPath()
|
Chris@0
|
642 print """Usage:
|
Chris@0
|
643 %s ontology prefix template destination [flags]
|
Chris@0
|
644
|
Chris@0
|
645 ontology : path to ontology file
|
Chris@0
|
646 prefix : prefix for CURIEs
|
Chris@0
|
647 template : HTML template path
|
Chris@0
|
648 destination : specification destination (by default)
|
Chris@0
|
649
|
Chris@0
|
650 optional flags:
|
Chris@0
|
651 -i : add instances on the specification (disabled by default)
|
Chris@0
|
652
|
Chris@0
|
653 examples:
|
Chris@0
|
654 %s example.owl ex template.html example.owl.html -i
|
Chris@0
|
655
|
Chris@0
|
656 """ % (script, script)
|
Chris@0
|
657 sys.exit(-1)
|
Chris@0
|
658
|
Chris@0
|
659 if __name__ == "__main__":
|
Chris@0
|
660 """Ontology specification generator tool"""
|
Chris@0
|
661
|
Chris@0
|
662 args = sys.argv[1:]
|
Chris@0
|
663 if (len(args) < 4):
|
Chris@0
|
664 usage()
|
Chris@0
|
665 else:
|
Chris@0
|
666
|
Chris@0
|
667 #ontology
|
Chris@0
|
668 specloc = "file:" + str(args[0])
|
Chris@0
|
669 spec_pre = args[1]
|
Chris@0
|
670
|
Chris@0
|
671 #template
|
Chris@0
|
672 temploc = args[2]
|
Chris@0
|
673 template = None
|
Chris@0
|
674 try:
|
Chris@0
|
675 f = open(temploc, "r")
|
Chris@0
|
676 template = f.read()
|
Chris@0
|
677 except Exception, e:
|
Chris@0
|
678 print "Error reading from template \"" + temploc + "\": " + str(e)
|
Chris@0
|
679 usage()
|
Chris@0
|
680
|
Chris@0
|
681 #destination
|
Chris@0
|
682 dest = args[3]
|
Chris@0
|
683
|
Chris@0
|
684 #flags
|
Chris@0
|
685 instances = False
|
Chris@0
|
686 if len(args) > 3:
|
Chris@0
|
687 flags = args[3:]
|
Chris@0
|
688 if '-i' in flags:
|
Chris@0
|
689 instances = True
|
Chris@0
|
690
|
Chris@0
|
691 save(dest, specgen(specloc, template, instances=instances))
|
Chris@0
|
692
|