mm2ttl¶
This file mindmap to turtle (mm2ttl) contains all functions to translate a gpdscl-annotated freeplane mindmap to OWL 2 in turtle format.
Johannes Busse, http://www.jbusse.de/gpdscl, email: jbusse at jbusse dot de
Version 0.26, 2021-02-16
This early version of the documentation still contains a significant amount of text in DE. We ask for your understanding.
generate .py version with jupytext:
pair ipynb with percent script to get a .py file which can be imported
pair ipynb with Myst-Markdown, get .md file
edit .md file in an external editor
reload .ipynb, edit minimally, save: jupytext automatically writes also .md and .py
import xml.etree.ElementTree as ET
from xml.etree.ElementTree import Element, SubElement
from datetime import datetime
from xml.sax.saxutils import escape, unescape, quoteattr
#classificationAxioms = False
#restrictionSomeAxioms = False
#owl2punning = True
Einführung (DE)¶
GPDSCL:
Genus Proximus
Differentia Specifica
Clasification Language
Mit der GPDSCL kann eine Klassifikation nach dem Modellierungs-Pattern Genus proximum et differentia specifica (a) anwenderfreundlich in einer Mindmap notiert und (b) in verschiedene Ontologiesprachen (insbesondere SKOS und OWL) übersetzt werden.
GPDSCL ist eine domänenspezifische Sprache (DSL) und damit eine “höhere Sprache”, die “die in Abstraktion und Komplexität von der Ebene der Maschinensprachen … entfernt ist” (Wikipedia > Domänenspezifische Sprache).
Notiert wird GPDSCL im RDF-Datenmodell als “gestreifter” Baum, aus dem mittels eines Code-Generators eine OWL-Ontologie erzeugt werden kann.
Für den Prototyp wählen wir folgende Technik:
Wir bauen den RDF-Graphen als Mindmap auf.
Für das Editieren der Mindmap verwenden wir die Open Source Software freeplane.org. Der Code-Generator dockt allerdings nicht an freeplane an, sondern wertet Dateien ausschließlich freeplane Datenformat
.mm
(ein XML-Format) aus. Damit kann jede andere Mindmap-Software verwendet werden, die das.mm
-Format erzeugen kann.Wir erzeugen OWL im Format Turtle.
basic utility functions¶
def test_button_cancel(node):
return 'button_cancel' in [ icon.attrib['BUILTIN'] for icon in node.findall("icon") ]
def makeIri(text, startwith=0):
iri = escape("_".join(text.split()[startwith:]))
if ":" not in iri:
iri = ":" + iri
return iri
def makeIriFromNode(node, startwith=0):
if 'TEXT' in node.attrib:
myText = node.attrib['TEXT']
else:
myText = 'ERROR: NO_TEXT_ATTRIBUTE'
if myText == '_':
myText = node.attrib['ID']
#myText = node.attrib['TEXT'] if 'TEXT' in node.attrib else "_"
#print(f'TEXT={myText}')
#myText = node.attrib['ID'] if myText == '_' else myText
myTextList = myText.split()
myTag = myTextList[0]
myIri = makeIri(myText, startwith) # escape("_".join(myTextList[startwith:]))
return myTag, myIri
def listOfValidChildren(node):
return [n for n in node.findall('node') if not(test_button_cancel(n))]
def listOfChildIris(node):
return [ makeIriFromNode(n, 0) for n in listOfValidChildren(node) ]
def attachToNode(node, text, highlight, codeType = 'owl' ):
"""add <pre>text</pre> to existing node/richcontent[@TYPE='NOTE']/html/body.
highlight ... a string like 'predicate', 'class' etc.
codeType ... a string like 'owl', 'intersection' etc.
"""
if highlight == 'predicate':
ET.SubElement(node, 'font', {'ITALIC': 'true'})
elif highlight == 'class':
ET.SubElement(node, 'font', {'BOLD': 'true'})
# node.attrib['STYLE'] = 'bubble'
elif highlight == 'example':
node.attrib['STYLE'] = "fork"
# node.attrib['COLOR'] = "#666666"
elif highlight == 'text':
node.attrib['COLOR'] = "#666666"
elif highlight == 'WARNING':
node.attrib['BACKGROUND_COLOR'] = "#ff0000"
node.attrib['COLOR'] = "#000000"
body = node.find('richcontent[@TYPE="NOTE"]/html/body')
pre = ET.SubElement(body, 'pre', {'codeType': codeType} )
pre.text = text
def verbose(node, text, verboseLevel):
"""print mindmap node ID and ISO version of timestamp"""
verbosity = 2 # TBD: use class attribute etc.
ts = int(node.attrib['MODIFIED']) // 1000
tsIso = datetime.utcfromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
nodeId = node.attrib['ID']
return f"\n# {text} {nodeId} {tsIso}\n" if verboseLevel <= verbosity else ""
def WARNING(node, elementType, *, gp, dsa, dt):
# WARNING:
owlCode = f"# T-box warning: unknown {elementType}: {node.get('TEXT')}"
attachToNode(node, owlCode, 'WARNING')
for n in listOfValidChildren(node):
searchForOntology(n)
def WARNING2(node, elementType, *, gp, dsa, dt):
# WARNING:
owlCode = f"# A-Box warning 2: unknown {elementType}: {node.get('TEXT')}"
attachToNode(node, owlCode, 'WARNING')
for n in listOfValidChildren(node):
searchForOntology(n)
def ERROR(node, text):
print(makeIriFromNode, text)
Depth first tree traversal¶
Traverse the xml file of a freeplane mindmap in depth first order. If we find a node which stars with “ONTOLOGY ” we start to interpret the subtree as a gpdscl annotated mindmap.
def searchForOntology(node, baseUri):
"""walk mindmap, search for nodes with tag 'ONTOLOGY' """
if test_button_cancel(node): return
myTag, myIri = makeIriFromNode(node, 1)
if myTag == 'ONTOLOGY':
ONTOLOGY(node, 'predicate', gp=None, dsa=None, dt=None, baseUri = baseUri)
else:
for n in node.findall("node"):
searchForOntology(n, baseUri)
The current node is a rdf resource. Starting from here we expect the child nodes being rdf predicates.
def walkPredicates(node, *, gp, dsa, dt):
"""walk mindmap, expect predicates"""
for n in listOfValidChildren(node):
myTag, myIri = makeIriFromNode(n, 1)
template = predicateTemplates.get(myTag, WARNING)
template(n, 'predicate', gp=gp, dsa=dsa, dt=dt)
The current node is a rdf resource. Starting from here we expect the child nodes being rdf predicates.
def walkPredicateInstances(node, *, s, p, o):
"""walk mindmap, expect predicate instances"""
for n in listOfValidChildren(node):
myTag, myIri = makeIriFromNode(n, 1)
template = predicateInstanceTemplates.get(myTag, WARNING2)
template(n, 'predicate', s=s, p=p, o=o)
T-Box templates¶
function parameter:
node … an ElemTree XML node with the XML name “node”
element type: one of {‘predicate’, ‘object’}
gp … genus proximum
dsa … differentia specifica attribute
dt … definition term
dsv … differentia specifica value
ONTOLOGY¶
def ONTOLOGY(node, elementType, *, gp, dsa, dt, baseUri):
if test_button_cancel(node): return
# ONTOLOGY source (predicate, new dsa) # we are called here
# milk (object) # and will also process these nodes
# BY has_Source (predicate) # we will call this level next
myTag, myIri = makeIriFromNode(node, 1)
ontologyIri = f"<{baseUri}{myIri[1:]}#>"
owlCode = verbose(node, "ONTOLOGY, predicate", 2)
owlCode += f"""\n@prefix : {ontologyIri} .
@base {ontologyIri} .
{ontologyIri} rdf:type owl:Ontology .
"""
attachToNode(node, owlCode, 'predicate')
for n in listOfValidChildren(node):
_, myIri = makeIriFromNode(n, 0)
owlCode = verbose(n, "ONTOLOGY, object", 2)
owlCode += f"""\n{myIri} a owl:Class."""
attachToNode(n, owlCode, 'class')
walkPredicates(n, gp='OntologyTopConcept', dsa='OntologyTopProperty', dt=myIri)
BY¶
Milch
BY hat_Quelle
Kuhmilch
Ziegenmilch
BY hat_Verpachung
Flaschenmilch
Tetrapack-Milch
Die SKOS-Semantik ist angelehnt an https://www.w3.org/TR/skos-primer#seccollections:
milk (skos:Concept)
<milk by source animal> (skos:Collection)
cow milk (skos:Concept)
goat milk (skos:Concept)
buffalo milk (skos:Concept)
def BY(node, elementType, *, gp, dsa, dt):
"""
BY: differentia specifica
# liquid (object, gp)
# ... (predicate, former dsa)
# milk (object, dt)
# BY source (predicate, new dsa) # we are called here
# cow milk (object) # and will also process these nodes
"""
myTag, myIri = makeIriFromNode(node, 1)
dsa = myIri if len(myIri) >0 else ':myTopObjectProperty' # set dsa to new differentia specifica attribute
owlCode = verbose(node, "BY, predicate", 2)
owlCode += f"""\n{dsa} a owl:ObjectProperty ."""
attachToNode(node, owlCode, 'predicate')
for n in listOfValidChildren(node):
if n.get('TEXT') != '_':
_, myIri = makeIriFromNode(n, 0)
else:
# generate a nice readable IRI
childIriSome = [ makeIriFromNode(child, 1)[1] \
for child in listOfValidChildren(n)\
if makeIriFromNode(child, 1)[0] == 'SOME']
# print(childIriSome)
if len(childIriSome) > 0:
if childIriSome[0] != "":
myIri = f"{dt}_{dsa[1:]}_SOME_{childIriSome[0][1:]}"
owlCode = verbose(n, "BY, object", 2)
owlCode += f"""\n{myIri} a owl:Class ;
rdfs:subClassOf {dt} ."""
attachToNode(n, owlCode, 'class')
walkPredicates(n, gp=dt, dsa=dsa, dt=myIri) # parameter shift here
ISA, Teilmenge¶
Tier
ISA
Kuh
Semantik:
Jedes Ding, das Element der Menge Kuh ist, ist auch Element der Menge Tier.
def ISA(node, elementType, *, gp, dsa, dt):
"""
ISA: subclass
# liquid (object, gp)
# ... (predicate, former dsa)
# milk (object, dt)
# ISA # we are called here
# cow milk (object)
"""
# myTag, myIri = makeIriFromNode(node, 1)
# dsa = myIri if len(myIri) >0 else ':myTopObjectProperty' # set dsa to new differentia specifica attribute
owlCode = verbose(node, "ISA, predicate: nothing to do here", 2)
attachToNode(node, owlCode, 'predicate')
for n in listOfValidChildren(node):
_, myIri = makeIriFromNode(n, 0)
owlCode = verbose(n, "ISA, object", 2)
owlCode += f"""\n{myIri} a owl:Class ;
rdfs:subClassOf {dt} ."""
attachToNode(n, owlCode, 'class')
walkPredicates(n, gp=dt, dsa=dsa, dt=myIri) # parameter shift here
SOME: OWL existential restriction ∃¶
Milch
BY hat_Quelle
Kuhmilch
SOME Kuh
Dieses 4-Tupel lässt sich als Pattern verstehen für ein Inferencing in zwei Richtungen.
Richtung 1: single Subclass -> multiple Superclasses¶
Inferencing:
Schließe von Subclass (hier: Kuhmilch) auf Superclasses (hier: Kuh, ∃ hat_Quelle.Kuh )
Gegeben: Von einem Ding sei bekannt:
Das Ding ist ein Element der Menge Kuhmilch.
Dann sind das hinreichende Angaben, um mit obiger Notation zweierlei daraus schließen zu können:
Dieses Ding ist auch Element der Menge Milch.
Dieses Ding auch Element der Menge derjenigen Dinge, die - neben möglicherweise anderen Quellen- mindestens auch eine Kuh als Quelle haben.
(“Hinreichend” sind die Angaben, weil allein sie ausreichend sind, diese zwei Schlüsse zu ziehen. Wir benötigen keine zusätzlichen notwendigen weitere Angaben.)
ACHTUNG: Die Notation täuscht hier etwas: Durch den Baum wird suggeriert, dass die Baum-Node SOME Kuh
(∃ hat_Quelle.Kuh) eine Subklasse von Kuhmilch ist. Das ist nicht gemeint. Tatsächlich ist obige Notation eine verdichtete Baum-Notation, die man - bezogen auf Richtung 1 - auch so notieren könnte:
Milch
Kuhmilch
∃ hat_Quelle.Kuh
Kuhmilch
Wichtig zu sehen ist hier, dass hier kein AND oder Ähnliches vorliegt, sondern dass hier zwei separate Subclass-Aussagen modelliert werden, die ein Reasoner unabhängig voneinander ausführen kann: Wenn bekannt ist, dass ein Ding eine Kuhmilch ist, dass lassen sich auch beide Oberklassen (“Es ist eine Milch!”; “Es kommt von der Kuh!”) ableiten.
Von Protege exportiert nach OWL/RDF (http://jbusse.de/ontology/Milch):
<!-- http://jbusse.de/ontology/Milch#Kuhmilch -->
<owl:Class rdf:about="http://jbusse.de/ontology/Milch#Kuhmilch">
<rdfs:subClassOf rdf:resource="http://jbusse.de/ontology/Milch#Milch"/>
<rdfs:subClassOf rdf:resource="http://jbusse.de/ontology/Milch#SOME_hat_Quelle_Kuh"/>
<rdfs:comment>Milch, die von der Kuh kommt</rdfs:comment>
</owl:Class>
<!-- http://jbusse.de/ontology/Milch#SOME_hat_Quelle_Kuh -->
<owl:Class rdf:about="http://jbusse.de/ontology/Milch#SOME_hat_Quelle_Kuh">
<owl:equivalentClass>
<owl:Restriction>
<owl:onProperty rdf:resource="http://jbusse.de/ontology/Milch#hat_Quelle"/>
<owl:someValuesFrom rdf:resource="http://jbusse.de/ontology/Milch#Kuh"/>
</owl:Restriction>
</owl:equivalentClass>
<rdfs:subClassOf rdf:resource="http://jbusse.de/ontology/Milch#RestrictionClasses"/>
<rdfs:comment>Die Menge aller Dinge, die eine Kuh als Quelle haben</rdfs:comment>
</owl:Class>
Richtung 2: Superclass 1 AND … Superclass n -> Subclass¶
Inferencing:
Schließe aus einem AND von mehreren Superclasses (hier: Milch, ∃ hat_Quelle.Kuh) auf eine gemeinsame Subclass (hier: Kuhmilch)
Gegeben: Von einem Ding sei bekannt:
Das Ding ist Element der Menge Milch, und
das Ding ist Element der Menge derjenigen Dinge, die eine Kuh als Quelle haben.
Dann seien uns diese Information hinreichend für den Schluss:
Dieses Ding ist ein Element der Menge Kuhmilch.
In DL:
Kuhmilch ⊇ AND_Class_Kuhmilch
AND_Class_Kuhmilch == Milch ∧ ∃ hat_Quelle.Kuh
in Mengenschreibweise:
Kuhmilch ⊇ {x | Milch(x) ∧ ∃y[hat_Quelle(x,y) ∧ Kuh(y)] }
in First Order Logic (FOL):
∀x[Kuhmilch(x) ← Milch(x) ∧ ∃y[hat_Quelle(x,y) ∧ Kuh(y)]
Um Richtung 2 (also solch ein Superclass –> Subclass-Inferencing) in einer Mindmap darstellen zu können, wäre ein AND hilfreich. Wir benötigen es aber nicht unbedingt explizit, sondern bauen es implizit in unser Pfad-Pattern mit der hier angegebenen Superclasses -> Subclass-Semantik ein.
In OWL verwendet man hier eine owl:intersectionOf
.
<!-- http://jbusse.de/ontology/Milch#AND_Class_Kuhmilch -->
<owl:Class rdf:about="http://jbusse.de/ontology/Milch#AND_Class_Kuhmilch">
<owl:equivalentClass>
<owl:Class>
<owl:intersectionOf rdf:parseType="Collection">
<rdf:Description rdf:about="http://jbusse.de/ontology/Milch#Kuh"/>
<rdf:Description rdf:about="http://jbusse.de/ontology/Milch#SOME_hat_Quelle_Kuh"/>
</owl:intersectionOf>
</owl:Class>
</owl:equivalentClass>
<rdfs:subClassOf rdf:resource="http://jbusse.de/ontology/Milch#AND_Classes"/>
</owl:Class>
(vgl. auch das strukturäquivalende Beispiel aus https://www.w3.org/TR/owl-xmlsyntax/apd-example.html#subapd-eg41): “Without such a definition it is possible to know that white wines are wines and white, but not vice-versa. This is an important tool for categorizing individuals.”
Diskussion: Durch obige Formulierung “seien uns diese Information hinreichend” wollen wir (in Umkehrung von Richtung 1 (eine Subclass -> mehrere Superclasses) hier in Richtung 2 annehmen, dass die Klasse Kuhmilch durch die drei anderen Angaben im Pfad Milch, BY hat_Quelle, _ , SOME Kuh
hinreichend (d.h. vollständig, umfassend, ohne dass weitere notwendige Bedingungen gelten müssen) bestimmt ist.
Dies ist eine vergleichsweise starke Annahme, die in Protege extra modelliert werden muss (d.h. nicht automatisch angelegt wird, wenn man zu einer Klasse einer Restriction hinzufügt). In welchen Kontexten können wir diese Annahme unterstützen wollen, und wo stört uns solch eine Annahme eher?
Um allgemeine OWL-Ontologien zu modellieren ist diese Annahme sicherlich zu stark.
Um eine Mindmap-basierte Sprache zu definieren, um gemäß dem genus proximum, differentia specifica-Pattern eine Facetten-Klassifikation zu notieren, dürfte diese Annahme ein ganz guter Kompromiss sein.
Diskutieren: Wenn man erlaubt, dass dasselbe Konzept an verschiedenen Stellen der Ontologie stückweise defininiert wird:
sind das dann verschiedene, alternative lokale Stories?
oder sollten für das Richtung-2-Inferencing alle Konjunktionsglieder eingesammelt werden? Damit lässt sich das Richtung-2-Inferencing nicht mehr durch eine rein lokale Code-Generierung handhaben.
Richtung 1 + Richtung 2¶
Die hier angegebenen OWL/XML-Auszüge sind nicht falsch oder korrekt, sondern sie definieren die Semantik des bislang rein syntaktisch beschriebenen Teilpfades ... BY ... SOME ...
einer Mindmap.
Es stellen sich allerdings sofort die folgenden Fragen:
Wären andere Definitionen sinnvoller?
Wollen wir OWL verwenden, oder “reicht” und RDF(S)?
Die Antwort auf diese Fragen hängt davon ab, welches Inferencing wir im Kopf haben.
In der hier vorgeschlagenen Semantik für einen Teilpfad der Form Pfad Milch, BY hat_Quelle, Kuhmilch, SOME Kuh
wird ein Inferncing ermöglicht, mit dem ein Ding anhand seiner charakteristischen Eigenschaften vom Allgemeinen immer spezieller zum Besonderen hin klassifiziert werden kann.
def SOME(node, elementType, *, gp, dsa, dt):
"""
SOME
# milk (object, gp)
# BY has_Source (predicate, dsa)
# cow milk (object, dt) # we are called here
# SOME cow (predicate, dsv)
"""
myTag, myIri = makeIriFromNode(node, 1)
dsv = myIri # differentia specifica value
# be careful: construct IRI without leading ":", attach later
# eliminate ":" from dsa and dsv
someClass = f"SOME_{dsa[1:]}_IS_{dsv[1:]}"
andClass = f"{gp}_AND_{someClass}" # IRI gets the leading ":" from gp
owlCode = verbose(node, "SOME, predicate", 2)
attachToNode(node, owlCode, 'predicate', 'verbose')
owlCode = f"""\n:{someClass} a owl:Class ;
owl:equivalentClass [ a owl:Restriction ;
owl:onProperty {dsa} ;
owl:someValuesFrom {dsv} ] ."""
attachToNode(node, owlCode, 'predicate', 'restriction')
owlCode = f"""\n{andClass} a owl:Class ;
rdfs:subClassOf {dt} ;
owl:equivalentClass [ a owl:Class ;
owl:intersectionOf ( {gp} :{someClass} ) ] ."""
attachToNode(node, owlCode, 'predicate', 'intersection')
owlCode = f"""\n# owl 2 punning
{dsv} a owl:Class ;
rdfs:subClassOf {dsa} .
{dsv} rdf:type owl:NamedIndividual ;
a {dsv} ."""
attachToNode(node, owlCode, 'predicate', 'punning')
for n in listOfValidChildren(node):
_, myIri = makeIriFromNode(n, 0)
owlCode = verbose(n, "SOME, object", 2)
owlCode += f"""\n{myIri} a owl:Class ;
rdfs:subClassOf {dt} ."""
attachToNode(n, owlCode,'class')
walkPredicates(n, gp=dt, dsa=dsa, dt=myIri) # parameter shift here
ALL: OWL for all restriction¶
nicht implementiert
SUP, Super-Set, Obermenge¶
Tier
ISA
Hengst
SUP
Maskulinum
Pferd
Semantik wie bei ISA, nur in die andere Richtung notiert: Tier ist eine Obermenge von Kuh. (Zugegeben, das erscheint ungewohnt. Aber das ist der Tribut dafür, dass wir mit Mindmaps und Bäumen arbeiten wollen statt mit Graph-Tools. )
Mit SUP lässt sich in einer Mindmap die “untere Hälfte”, genauer: der poly-hierarchische Teil einer FCA in einer Mindmap notieren.
Semantik Teil 1 (“Richtung 1”), Oder-Verknüpfung von ISA und SUP wie oben beschrieben:
Hengst ist Teilmenge von Tier (siehe oben, ISA)
Hengst ist Teilmenge von Maskulinum und Pferd (siehe oben, SUP)
Semantik Teil 2 (“Richtung 2”) - und wesentliche Design-Entscheidung in GPDSCL:
Wenn wir von einem Ding wissen, dass es (a) ein Tier und (b) ein Maskulinum und ein Pferd ist
und wir diese Information in einer Ontologie in unmittelbarer Nachbarschaft als ein ISA - SUP Quintupel notieren
dann mögen uns diese Informationen genügen daraus zu schließen, dass dieses Ding ein Hengst ist.
WICHTIG:
Die Kombination von ISA und SUP stellt in GPDSCL ein wesentliches Sprachelement dar, das die zunehmend genauere Klassierung von Beispielen anhand mehrerer Oberklassen ermöglicht.
In F-Logic würde man schreiben:
Hengst(X) <- Tier(X) AND Maskulinum(X) AND Pferd(X)
: SUP ist gleichbedeutend mit konjunktiven Regeln.
def SUP(node, elementType, *, gp, dsa, dt):
"""
SUP: Superclass. Similar to ISA, but reverse notation order in the mindmap
# milk (gp)
# BY something
# bottled cow milk (object, dt) # superclass 1
# SUP (predicate) # we are called here
# cow # superclass 2
# bottle # superclass 3 etc.
"""
myTag, myIri = makeIriFromNode(node, 1)
dsv = myIri # differentia specifica value
SupClass = ':SUP_'+node.get('ID')
superclassIRIs = " ".join([ makeIriFromNode(n,0)[1]
for n in listOfValidChildren(node) ])
owlCode = verbose(node, f"SUP, predicate, gp=={gp}, dt=={dt}", 2)
owlCode += f"""\n{SupClass} a owl:Class ;
rdfs:subClassOf {dt} ;
owl:equivalentClass [ a owl:Class ;
owl:intersectionOf
( {superclassIRIs} ) ] .""" # {gp}
attachToNode(node, owlCode, 'predicate')
for n in listOfValidChildren(node):
_, myIri = makeIriFromNode(n, 0)
owlCode = verbose(n, "SUP, object", 2)
owlCode += f"""\n{dt} a owl:Class ;
rdfs:subClassOf {myIri} ."""
attachToNode(n, owlCode,'class')
walkPredicates(n, gp=':myTopObject', dsa=':myTopDataProperty', dt=myIri) # parameter shift here
EX, Example¶
Kuh
EX
Elsa_12345
Semantik:
Elsa_12345 ist ein Element der Menge Kuh.
Beispiele von Beispielen sind in RDF übrigens perfekt möglich. In der Mathematik ist diese Idee trivialerweise als Potenzmenge bekannt. Problem: In Bezug auf Inferencing verlassen wir hier die First Order Logic, unser Inferencing wird unentscheidbar.
Boeing 747
EX
Air Force One
EX
VC-25A-33A275R3
RDFS-Interpretation:
Die Air Force One ist ein Element der Menge Boeing 747, und das Flugzeug mit der ID VC-25A-33A275R3 ist ein Element der Menge Air Force One.
In GPDSCL wollen wir keine Mengen von Mengen haben. Unklar, wie wir solche eine Modellierung übersetzen wollen.
def EX(node, elementType, *, gp, dsa, dt):
"""
EX: example, instance of
# milk (object, dt)
# EX (predicate, new dsa) # we are called here
# ID_123456 (example object) # and will also process these nodes
"""
owlCode = verbose(node, "EX, predicate", 2)
attachToNode(node, owlCode,'predicate')
for n in listOfValidChildren(node):
_, myIri = makeIriFromNode(n, 0)
owlCode = verbose(n, "EX, object", 2)
owlCode += f"""\n{myIri} a {dt} ."""
attachToNode(n, owlCode, 'example')
walkPredicateInstances(n, s=None, p=None, o=myIri)
AP¶
Annotation Property
def AP(node, elementType, *, gp, dsa, dt):
"""
AP: annotation property
# milk (object, dt)
# AP skos:definition (predicate, new ) # we are called here
# some text# and will also process these nodes
"""
myTag, myIri = makeIriFromNode(node, 1)
ap = myIri if myIri != ':' else 'rdfs:comment'
owlCode = verbose(node, "AP, predicate", 2)
owlCode += f"""\n{ap} a owl:AnnotationProperty ."""
attachToNode(node, owlCode, 'predicate')
for n in listOfValidChildren(node):
owlCode = verbose(n, "AP, literal", 2)
owlCode = f"""\n{dt} {ap} {quoteattr(n.attrib['TEXT'])} ."""
attachToNode(n, owlCode, 'text')
# do not walkPredicates(...): We are at a dead end here.
# instead search for ONTOLOGY:
for n2 in listOfValidChildren(n):
searchForOntology(n2)
DEF¶
Definition (a specific annotation property)
def DEF(node, elementType, *, gp, dsa, dt):
"""
DEF: definition, our most important annotation property
# milk (object, dt)
# DEF (predicate, new ) # we are called here
# some text
"""
myTag, myIri = makeIriFromNode(node, 1)
ap = myIri if myIri != ':' else 'skos:definition'
owlCode = verbose(node, "AP, predicate", 2)
owlCode += f"""\n{ap} a owl:AnnotationProperty ."""
attachToNode(node, owlCode, 'predicate')
for n in listOfValidChildren(node):
owlCode = verbose(n, "DEF, literal", 2)
owlCode = f"""\n{dt} {ap} {quoteattr(n.attrib['TEXT'])} ."""
attachToNode(n, owlCode, 'text')
# do not walkPredicates(...): We are at a dead end here.
# instead search for ONTOLOGY:
for n2 in listOfValidChildren(n):
searchForOntology(n2)
# used by function walkPredicates()
predicateTemplates = {
'WARNING': WARNING,
'ONTOLOGY': ONTOLOGY,
'BY': BY,
'SOME': SOME,
'SUP': SUP,
'EX': EX,
'AP': AP,
'ISA': ISA,
'DEF': DEF,
#'ASI': ... use SUP instead
}
A-Box templates¶
DP¶
Data Property
def DP(node, elementType, *, s, p, o):
"""DP: data property
# milk (o object)
# DP hat_Fettgehalt # we are called here
# 1.5 # and will also process these nodes
"""
myTag, myIri = makeIriFromNode(node, 1)
dp = myIri # TBD set default: if myIri != ':' else 'rdfs:comment'
owlCode = verbose(node, "DP, predicate", 2)
owlCode += f"""\n{dp} a owl:DatatypeProperty ."""
attachToNode(node, owlCode, 'predicate')
for n in listOfValidChildren(node):
owlCode = verbose(n, "DP, literal", 2)
owlCode = f"""\n{o} {dp} {quoteattr(node.attrib['TEXT'])} ."""
attachToNode(n, owlCode, 'text')
# do not walkPredicates(...): We are at a dead end here.
# instead search for ONTOLOGY:
for n2 in listOfValidChildren(n):
searchForOntology(n2)
OP¶
Object Property
def OP(node, elementType, *, s, p, o):
"""OP: object property
# milk_1234 (o object)
# OP hat_Hersteller # we are called here
# Weideglück # and will also process these nodes
"""
myTag, myIri = makeIriFromNode(node, 1)
op = myIri # TBD set default: if myIri != ':' else 'skos:narrower'
owlCode = verbose(node, "OP, predicate", 2)
owlCode += f"""\n{op} a owl:ObjectProperty ."""
attachToNode(node, owlCode, 'predicate')
for n in listOfValidChildren(node):
_, childIri = makeIriFromNode(n, 0)
owlCode = verbose(n, "OP, literal", 2)
owlCode = f"""\n{o} {op} {childIri} ."""
attachToNode(n, owlCode, 'text')
walkPredicateInstances(n, s=0, p=op, o=childIri)
XE, elpmaxe¶
EX (example), in umgekehrter Leserichtung geschrieben: EX, elpmaxe. Anwendung dort, wo ein Exemplar gleichzeitig als Element von mehreren Mengen gekennzeichnet werden soll.
Kuh
EX
Elsa_12345
XE
Femininum
Fleckvieh
Semantik:
Elsa_12345 ist ein Element der Menge Kuh (aus EX)
Elsa_12345 ist außerdem ein Element der Menge Femininum und der Menge Fleckvieh (aus XE)
def XE(node, elementType, *, s, p, o):
"""XE: example, the other way round: the parent node is the example, the child nodes the respective classes.
# ID_123456 (example object)
# XE (predicate, new dsa) # we are called here
# milk (object, dt) # and will also process these nodes
"""
attachToNode(node, '','predicate')
for n in listOfValidChildren(node):
_, myIri = makeIriFromNode(n, 0)
owlCode = verbose(n, "XE, object", 2)
owlCode += f"""\n{o} a {myIri} ."""
attachToNode(n, owlCode, 'class')
walkPredicates(n, gp=':myTopObject', dsa=':myTopObjectProperty', dt=':myTopObject') # parameter shift here
AP and DEF (abox)¶
AP und DEF sind auch für Objekte aus der A-box definiert.
def APabox(node, elementType, *, s, p, o):
AP(node, elementType, gp = s, dsa = p, dt = o )
def DEFabox(node, elementType, *, s, p, o):
DEF(node, elementType, gp = s, dsa = p, dt = o )
# used by walkPredicateInstances()
predicateInstanceTemplates = {
'DP': DP,
'OP': OP,
'XE': XE,
# we also allow some predicates from the T-box
'DEF': DEFabox,
'AP': APabox
}
Generate serialization¶
Walk through the mindmap graph and collect all richcontent[@TYPE="NOTE"]/html/body/pre
-elements into the dict owlEntries
.
def cleanMindmap(node, removeHook = False):
"""remove node decorations and richtext stemming from earlier runs of mm2ttl"""
if test_button_cancel(node): return
# if removeHook == True:
# remove all style information from map - are you sure?
# [ node.remove(hook) for hook in node.findall('hook') ]
[ node.remove(font) for font in node.findall('font') ]
[ node.remove(r) for r in node.findall('richcontent[@TYPE="NOTE"]') ]
# guarantee that there is a richcontent node
richcontent = ET.SubElement(node, 'richcontent', attrib = {'TYPE': 'NOTE'})
html = ET.SubElement(richcontent, 'html')
body = ET.SubElement(html, 'body')
for n in node.findall('node'):
cleanMindmap(n)
def collectOwlEntries(owlEntries, node, codeTypeList = []):
if test_button_cancel(node): return
if len(codeTypeList) == 0: # get all pre elements
code = ""
for p in node.findall('richcontent[@TYPE="NOTE"]/html/body/pre'):
code += p.text
owlEntries[f'{node.get("ID")}'] = code
else: # get only pre elements which are in the nodeTypeList
for nt in codeTypeList:
code = ""
for p in node.findall('richcontent[@TYPE="NOTE"]/html/body/pre'):
if p.get('codeType') == nt:
code += p.text
owlEntries[f'{node.get("ID")}_{nt}'] = code
for n in node.findall('node'):
collectOwlEntries(owlEntries, n, codeTypeList)
Join the dict owlEntries
to get a string version of the ontology.
def mm2turtle(node, baseUri, *, verbosity=0, codeTypeList = []):
cleanMindmap(node)
searchForOntology(node, baseUri)
owlEntries = {}
collectOwlEntries(owlEntries, node, codeTypeList)
joinedCollectedOwlEntries = "\n".join(owlEntries.values())
return joinedCollectedOwlEntries
# ontologyString = f"""@prefix : <{baseUri}#> .
#@base <{baseUri}> .
#<{baseUri}> rdf:type owl:Ontology .
#
#{joinedCollectedOwlEntries}
# """
# # print(f"ontologyString: {ontologyString}")
# return ontologyString
# return owlEntries