2
# -*- coding: utf-8 -*-
4
## This file is part of the Nepomuk KDE project.
5
## Copyright (C) 2011 Sebastian Trueg <trueg@kde.org>
6
## Copyright (C) 2011 Serebriyskiy Artem <v.for.vandal@gmail.com>
8
## This library is free software; you can redistribute it and/or
9
## modify it under the terms of the GNU Lesser General Public
10
## License as published by the Free Software Foundation; either
11
## version 2.1 of the License, or (at your option) version 3, or any
12
## later version accepted by the membership of KDE e.V. (or its
13
## successor approved by the membership of KDE e.V.), which shall
14
## act as a proxy defined in Section 6 of version 3 of the license.
16
## This library is distributed in the hope that it will be useful,
17
## but WITHOUT ANY WARRANTY; without even the implied warranty of
18
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19
## Lesser General Public License for more details.
21
## You should have received a copy of the GNU Lesser General Public
22
## License along with this library. If not, see <http://www.gnu.org/licenses/>.
28
from PyKDE4.soprano import Soprano
29
from PyQt4 import QtCore
31
output_path = os.getcwd()
34
# A list of C++ reserved keywords which we need to handle
35
cppKeywords = ['class', 'int', 'float', 'double']
37
def normalizeName(name):
38
"Normalize a class or property name to be used as a C++ entity."
39
name.replace('-', '_')
40
name.replace('.', '_')
43
def extractNameFromUri(uri):
44
"Extract the class or property name from an entity URI. This is the last section of the URI"
45
name = uri.toString().mid(uri.toString().lastIndexOf(QtCore.QRegExp('[#/:]'))+1)
46
return normalizeName(name)
48
def makeFancy(name, cardinality):
49
if name.startsWith("has"):
50
name = name[3].toLower() + name.mid(4)
52
if name.endsWith('s'):
57
return normalizeName(name)
59
def extractOntologyName(uri):
60
"The name of the ontology is typically the section before the name of the entity"
61
return uri.toString().section(QtCore.QRegExp('[#/:]'), -2, -2)
64
"Create a folder and all its missing parent folders"
67
except OSError as exc: # Python >2.5
68
if exc.errno == errno.EEXIST:
72
def typeString(rdfType, cardinality):
74
Construct the C++/Qt type to be used for the given type and cardinality.
75
Uses QUrl for all non-literal types
77
if (rdfType == Soprano.Vocabulary.XMLSchema.string() or rdfType == Soprano.Vocabulary.RDFS.Literal()) and cardinality != 1:
81
if rdfType == Soprano.Vocabulary.XMLSchema.integer(): simpleType = "qint64"
82
elif rdfType == Soprano.Vocabulary.XMLSchema.negativeInteger(): simpleType = "qint64"
83
elif rdfType == Soprano.Vocabulary.XMLSchema.nonNegativeInteger(): simpleType = "quint64"
84
elif rdfType == Soprano.Vocabulary.XMLSchema.xsdLong(): simpleType = "qint64"
85
elif rdfType == Soprano.Vocabulary.XMLSchema.unsignedLong(): simpleType = "quint64"
86
elif rdfType == Soprano.Vocabulary.XMLSchema.xsdInt(): simpleType = "qint32"
87
elif rdfType == Soprano.Vocabulary.XMLSchema.unsignedInt(): simpleType = "quint32"
88
elif rdfType == Soprano.Vocabulary.XMLSchema.xsdShort(): simpleType = "qint16"
89
elif rdfType == Soprano.Vocabulary.XMLSchema.unsignedShort(): simpleType = "quint16"
90
elif rdfType == Soprano.Vocabulary.XMLSchema.xsdFloat(): simpleType = "double"
91
elif rdfType == Soprano.Vocabulary.XMLSchema.xsdDouble(): simpleType = "double"
92
elif rdfType == Soprano.Vocabulary.XMLSchema.boolean(): simpleType = "bool"
93
elif rdfType == Soprano.Vocabulary.XMLSchema.date(): simpleType = "QDate"
94
elif rdfType == Soprano.Vocabulary.XMLSchema.time(): simpleType = "QTime"
95
elif rdfType == Soprano.Vocabulary.XMLSchema.dateTime(): simpleType = "QDateTime"
96
elif rdfType == Soprano.Vocabulary.XMLSchema.string(): simpleType = "QString"
97
elif rdfType == Soprano.Vocabulary.RDFS.Literal(): simpleType = "QString"
98
else: simpleType = 'QUrl'
101
return 'QList<%s>' % simpleType
106
class OntologyParser():
108
self.model = Soprano.createModel()
110
def parseFile(self, path):
111
parser = Soprano.PluginManager.instance().discoverParserForSerialization(Soprano.SerializationTrig)
114
it = parser.parseFile(path, QtCore.QUrl("dummy"), Soprano.SerializationTrig)
116
self.model.addStatement(it.current())
117
if parser.lastError():
124
# add rdfs:Resource as domain for all properties without a domain
125
query = 'select ?p where { ?p a %s . OPTIONAL { ?p %s ?d . } . FILTER(!BOUND(?d)) . }' \
126
% (Soprano.Node.resourceToN3(Soprano.Vocabulary.RDF.Property()), \
127
Soprano.Node.resourceToN3(Soprano.Vocabulary.RDFS.domain()))
128
nodes = self.model.executeQuery(query, Soprano.Query.QueryLanguageSparql).iterateBindings(0).allNodes()
130
self.model.addStatement(p, Soprano.Node(Soprano.Vocabulary.RDFS.domain()), Soprano.Node(Soprano.Vocabulary.RDFS.Resource()))
132
# cache a few values we need more than once
133
self.rdfsResourceProperties = self.getPropertiesForClass(Soprano.Vocabulary.RDFS.Resource())
135
query = 'select distinct ?uri ?label ?comment where {{ ?uri a {0} . ?uri {1} ?label . OPTIONAL {{ ?uri {2} ?comment . }} . }}' \
136
.format(Soprano.Node.resourceToN3(Soprano.Vocabulary.RDFS.Class()), \
137
Soprano.Node.resourceToN3(Soprano.Vocabulary.RDFS.label()), \
138
Soprano.Node.resourceToN3(Soprano.Vocabulary.RDFS.comment()))
139
it = self.model.executeQuery(query, Soprano.Query.QueryLanguageSparql)
142
uri = it['uri'].uri()
144
print "Parsing class: ", uri
145
ns = self.getNamespaceAbbreviationForUri(uri)
146
name = extractNameFromUri(uri)
147
self.writeHeader(uri, ns, name, it['label'].toString(), it['comment'].toString())
150
def getNamespaceAbbreviationForUri(self, uri):
151
query = "select ?ns where { graph ?g { %s ?p ?o . } . ?g %s ?ns . } LIMIT 1" \
152
% (Soprano.Node.resourceToN3(uri), \
153
Soprano.Node.resourceToN3(Soprano.Vocabulary.NAO.hasDefaultNamespaceAbbreviation()))
154
it = self.model.executeQuery(query, Soprano.Query.QueryLanguageSparql)
156
return it[0].toString().toLower()
158
return extractOntologyName(uri)
160
def getParentClasses(self, uri):
162
Returns a dict which maps parent class URIs to a dict containing keys 'ns' and 'name'
163
Only parent classes that are actually generated are returned.
165
query = "select distinct ?uri where {{ {0} {1} ?uri . ?uri a {2} . }}" \
166
.format(Soprano.Node.resourceToN3(uri), \
167
Soprano.Node.resourceToN3(Soprano.Vocabulary.RDFS.subClassOf()), \
168
Soprano.Node.resourceToN3(Soprano.Vocabulary.RDFS.Class()))
169
it = self.model.executeQuery(query, Soprano.Query.QueryLanguageSparql)
172
puri = it['uri'].uri()
173
if puri != Soprano.Vocabulary.RDFS.Resource():
175
cd['ns'] = self.getNamespaceAbbreviationForUri(puri)
176
cd['name'] = extractNameFromUri(puri)
177
classes[puri.toString()] = cd
180
def getFullParentHierarchy(self, uri, currentParents, result):
182
Returns a list of dicts containing keys 'ns' and 'name'.
183
currentParents is a running variable used to avoid endless loops when recursing. It should
184
always be set to the empty list [].
185
result is another running variable which stores the final result set. It should also be set
186
to the empty list [].
188
# we perform a depth-first search for the most general type
189
directParents = self.getParentClasses(uri)
190
for p in directParents.keys():
191
if not p in currentParents:
192
currentParents.append(p)
193
self.getFullParentHierarchy(QtCore.QUrl(p), currentParents, result)
194
result.append(directParents[p])
197
def getPropertiesForClass(self, uri):
198
query = "select distinct ?p ?range ?comment ?c ?mc where { ?p a %s . ?p %s %s . ?p %s ?range . OPTIONAL { ?p %s ?comment . } . OPTIONAL { ?p %s ?c . } . OPTIONAL { ?p %s ?mc . } . }" \
199
% (Soprano.Node.resourceToN3(Soprano.Vocabulary.RDF.Property()),
200
Soprano.Node.resourceToN3(Soprano.Vocabulary.RDFS.domain()),
201
Soprano.Node.resourceToN3(uri),
202
Soprano.Node.resourceToN3(Soprano.Vocabulary.RDFS.range()),
203
Soprano.Node.resourceToN3(Soprano.Vocabulary.RDFS.comment()),
204
Soprano.Node.resourceToN3(Soprano.Vocabulary.NRL.cardinality()),
205
Soprano.Node.resourceToN3(Soprano.Vocabulary.NRL.maxCardinality()))
206
it = self.model.executeQuery(query, Soprano.Query.QueryLanguageSparql)
207
#print "Property query done."
211
r = it['range'].uri()
212
comment = it['comment'].toString()
214
if it['c'].isValid():
215
c = it['c'].literal().toInt()
217
c = it['mc'].literal().toInt()
218
properties[p] = dict([('range', r), ('cardinality', c), ('comment', comment)])
221
def writeComment(self, theFile, text, indent):
224
theFile.write(' ' * indent*4)
225
theFile.write("/**\n")
226
theFile.write(' ' * (indent*4+1))
229
words = QtCore.QString(text).split( QtCore.QRegExp("\\s"), QtCore.QString.SkipEmptyParts )
231
for i in range(words.count()):
234
theFile.write(' ' * (indent*4+1))
237
theFile.write(words[i])
239
cnt += words[i].length()
242
theFile.write(' ' * (indent*4+1))
243
theFile.write("*/\n")
245
def writeGetter(self, theFile, prop, name, propRange, cardinality):
246
fancyName = makeFancy(name, cardinality)
247
if fancyName in cppKeywords:
248
fancyName = 'get' + fancyName[0].toUpper() + fancyName.mid(1)
249
theFile.write(' %s %s() const {\n' % (typeString(propRange, cardinality), fancyName))
250
theFile.write(' %s value;\n' % typeString(propRange, cardinality))
252
theFile.write(' if(contains(QUrl::fromEncoded("%s", QUrl::StrictMode)))\n' % prop.toString())
253
theFile.write(' value = property(QUrl::fromEncoded("{0}", QUrl::StrictMode)).first().value<{1}>();\n'.format(prop.toString(), typeString(propRange, 1)))
255
theFile.write(' foreach(const QVariant& v, property(QUrl::fromEncoded("%s", QUrl::StrictMode)))\n' % prop.toString())
256
theFile.write(' value << v.value<{0}>();\n'.format(typeString(propRange, 1)))
257
theFile.write(' return value;\n')
258
theFile.write(' }\n')
260
def writeSetter(self, theFile, prop, name, propRange, cardinality):
261
theFile.write(' void set%s%s(const %s& value) {\n' % (makeFancy(name, cardinality)[0].toUpper(), makeFancy(name, cardinality).mid(1), typeString(propRange, cardinality)))
262
theFile.write(' QVariantList values;\n')
264
theFile.write(' values << value;\n')
266
theFile.write(' foreach(const %s& v, value)\n' % typeString(propRange, 1))
267
theFile.write(' values << v;\n')
268
theFile.write(' setProperty(QUrl::fromEncoded("%s", QUrl::StrictMode), values);\n' % prop.toString())
269
theFile.write(' }\n')
271
def writeAdder(self, theFile, prop, name, propRange, cardinality):
272
theFile.write(' void add%s%s(const %s& value) {\n' % (makeFancy(name, 1)[0].toUpper(), makeFancy(name, 1).mid(1), typeString(propRange, 1)))
273
theFile.write(' addProperty(QUrl::fromEncoded("%s", QUrl::StrictMode), value);\n' % prop.toString())
274
theFile.write(' }\n')
276
def writeHeader(self, uri, nsAbbr, className, label, comment):
278
relative_path = nsAbbr + '/' + className.toLower() + '.h'
279
folder = output_path + '/' + nsAbbr
280
filePath = output_path + '/' + relative_path
283
print "Writing header file: %s" % filePath
285
# Create the containing folder
286
mkdir_p(QtCore.QFile.encodeName(folder).data())
288
# open the header file
289
header = open(filePath, 'w')
291
# get all direct base classes
292
parentClasses = self.getParentClasses(uri)
294
# write protecting ifdefs
295
header_protect = '_%s_%s_H_' % (nsAbbr.toUpper(), className.toUpper())
296
header.write('#ifndef %s\n' % header_protect)
297
header.write('#define %s\n' % header_protect)
300
# write default includes
301
header.write('#include <QtCore/QVariant>\n')
302
header.write('#include <QtCore/QStringList>\n')
303
header.write('#include <QtCore/QUrl>\n')
304
header.write('#include <QtCore/QDate>\n')
305
header.write('#include <QtCore/QTime>\n')
306
header.write('#include <QtCore/QDateTime>\n')
309
# all classes need the SimpleResource include
310
header.write('#include <nepomuk/simpleresource.h>\n\n')
312
# write includes for the parent classes
313
parentClassNames = []
314
for parent in parentClasses.keys():
315
header.write('#include "%s/%s.h"\n' % (parentClasses[parent]['ns'], parentClasses[parent]['name'].toLower()))
316
parentClassNames.append("%s::%s" %(parentClasses[parent]['ns'].toUpper(), parentClasses[parent]['name']))
318
# get all base classes which we require due to the virtual base class constructor ordering in C++
319
# We inverse the order to match the virtual inheritance constructor calling order
320
fullParentHierarchyNames = []
321
for parent in self.getFullParentHierarchy(uri, [], []):
322
fullParentHierarchyNames.append("%s::%s" %(parent['ns'].toUpper(), parent['name']))
324
if len(parentClassNames) > 0:
327
# write the class namespace
328
header.write('namespace Nepomuk {\n')
329
header.write('namespace %s {\n' % nsAbbr.toUpper())
331
# write the class + parent classes
332
# We use virtual inheritance when deriving from SimpleResource since our ontologies
333
# make use of multi-inheritance and without it the compiler would not know which
334
# addProperty and friends to call.
335
# We need to do the same with all parent classes since some classes like
336
# nco:CellPhoneNumber as derived from other classes that have yet another parent
337
# class in common which is not SimpleResource.
338
self.writeComment(header, comment, 0)
339
header.write('class %s' % className)
341
header.write(', '.join(['public virtual %s' % (p) for p in parentClassNames]))
342
if len(parentClassNames) == 0:
343
header.write('public virtual Nepomuk::SimpleResource');
344
header.write('\n{\n')
345
header.write('public:\n')
347
# write the default constructor
348
# We directly set the type of the class to the SimpleResource. If the class is a base class
349
# not derived from any other classes then we set the type directly. Otherwise we use the
350
# protected constructor defined below which takes a type as parameter making sure that we
351
# only add one type instead of the whole hierarchy
352
header.write(' %s(const QUrl& uri = QUrl())\n' % className)
354
header.write('SimpleResource(uri)')
355
if len(parentClassNames) > 0:
357
header.write(', '.join([('%s(uri, QUrl::fromEncoded("' + uri.toString().toUtf8().data() + '", QUrl::StrictMode))') % p for p in fullParentHierarchyNames]))
359
if len(parentClassNames) == 0:
360
header.write(' addType(QUrl::fromEncoded("%s", QUrl::StrictMode));\n' % uri.toString())
361
header.write(' }\n\n')
363
# write the copy constructor
364
header.write(' %s(const SimpleResource& res)\n' % className)
366
header.write('SimpleResource(res)')
367
if len(parentClassNames) > 0:
369
header.write(', '.join([('%s(res, QUrl::fromEncoded("' + uri.toString().toUtf8().data() + '", QUrl::StrictMode))') % p for p in fullParentHierarchyNames]))
371
if len(parentClassNames) == 0:
372
header.write(' addType(QUrl::fromEncoded("%s", QUrl::StrictMode));\n' % uri.toString())
373
header.write(' }\n\n')
375
# write the assignment operator
376
header.write(' %s& operator=(const SimpleResource& res) {\n' % className)
377
header.write(' SimpleResource::operator=(res);\n')
378
header.write(' addType(QUrl::fromEncoded("%s", QUrl::StrictMode));\n' % uri.toString())
379
header.write(' return *this;\n')
380
header.write(' }\n\n')
382
# Write getter and setter methods for all properties
383
# This includes the properties that have domain rdfs:Resource on base classes, ie.
384
# those that are not derived from any other class. That way these properties are
385
# accessible from all classes.
386
properties = self.getPropertiesForClass(uri)
387
if len(parentClassNames) == 0:
388
properties.update(self.rdfsResourceProperties)
390
# There could be properties with the same name - in that case we give the methods a prefix
391
for p in properties.keys():
392
name = extractNameFromUri(p)
394
# search for the same name again
395
for op in properties.keys():
396
if extractNameFromUri(op) == name:
399
name = self.getNamespaceAbbreviationForUri(p).toLower() + name[0].toUpper() + name.mid(1)
400
properties[p]['name'] = name;
402
for p in properties.keys():
403
self.writeComment(header, 'Get property %s. %s' % (p.toString(), properties[p]['comment']), 1)
404
self.writeGetter(header, p, properties[p]['name'], properties[p]['range'], properties[p]['cardinality'])
406
self.writeComment(header, 'Set property %s. %s' % (p.toString(), properties[p]['comment']), 1)
407
self.writeSetter(header, p, properties[p]['name'], properties[p]['range'], properties[p]['cardinality'])
409
self.writeComment(header, 'Add value to property %s. %s' % (p.toString(), properties[p]['comment']), 1)
410
self.writeAdder(header, p, properties[p]['name'], properties[p]['range'], properties[p]['cardinality'])
413
# write the protected constructors which avoid adding the whole type hierarchy
414
header.write('protected:\n')
415
header.write(' %s(const QUrl& uri, const QUrl& type)\n' % className)
417
header.write('SimpleResource(uri)')
418
if len(parentClassNames) > 0:
420
header.write(', '.join(['%s(uri, type)' % p for p in fullParentHierarchyNames]))
422
if len(parentClassNames) == 0:
423
header.write(' addType(type);\n')
426
header.write(' %s(const SimpleResource& res, const QUrl& type)\n' % className)
428
header.write('SimpleResource(res)')
429
if len(parentClassNames) > 0:
431
header.write(', '.join(['%s(res, type)' % p for p in fullParentHierarchyNames]))
433
if len(parentClassNames) == 0:
434
header.write(' addType(type);\n')
440
# write the closing parenthesis for the namespaces
441
header.write('}\n}\n')
443
# write the closing preprocessor thingi
444
header.write('\n#endif\n')
451
usage = "Usage: %prog [options] ontologyfile1 ontologyfile2 ..."
452
optparser = argparse.ArgumentParser(description="Nepomuk SimpleResource code generator. It will generate a hierarchy of simple wrapper classes around Nepomuk::SimpleResource which provide convinience methods to get and set properties of those classes. Each wrapper class will be defined in its own header file and be written to a subdirectory named as the default ontology prefix. Example: the header file for nao:Tag would be written to nao/tag.h and be defined in the namespace Nepomuk::NAO.")
453
optparser.add_argument('--output', '-o', type=str, nargs=1, metavar='PATH', dest='output', help='The destination folder')
454
optparser.add_argument('--quiet', '-q', action="store_false", dest="verbose", default=True, help="don't print status messages to stdout")
455
optparser.add_argument("ontologies", type=str, nargs='+', metavar="ONTOLOGY", help="Ontology files to use")
457
args = optparser.parse_args()
459
output_path = args.output[0]
461
verbose = args.verbose
464
print 'Generating from ontology files %s' % ','.join(args.ontologies)
465
print 'Writing files to %s.' % output_path
467
# Parse all ontology files
468
ontoParser = OntologyParser()
469
for f in args.ontologies:
471
print "Reading ontology '%s'" % f
472
ontoParser.parseFile(f)
474
print "All ontologies read. Generating code..."
476
# Get all classes and handle them one by one
477
ontoParser.writeAll()
479
if __name__ == "__main__":