From e1517f512747c77802d3156ce9433163aa1fad29 Mon Sep 17 00:00:00 2001 From: irenesanchezsanz Date: Tue, 21 Oct 2025 17:28:39 +0200 Subject: [PATCH 1/7] Entrega Assignment 4 - Irene Sanchez Sanz --- .../task06.py" | 161 +++++++++++++++++ .../task07.py" | 163 +++++++++++++++++ .../notebooks/Hoja_2_Tema_2_17_18.pdf | Bin 0 -> 50761 bytes .../course_materials/notebooks/task06.py | 166 ++++++++++++++++++ 4 files changed, 490 insertions(+) create mode 100644 "Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" create mode 100644 "Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" create mode 100644 Assignment4/course_materials/notebooks/Hoja_2_Tema_2_17_18.pdf create mode 100644 Assignment4/course_materials/notebooks/task06.py diff --git "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" new file mode 100644 index 00000000..e4e37eb0 --- /dev/null +++ "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +"""Task06.ipynb + +Automatically generated by Colab. + +Original file is located at + https://colab.research.google.com/drive/1X0FBFPAmKdYdKq6WNdQwUv5CADcN3Raf + +**Task 06: Modifying RDF(s)** +""" + +#!pip install rdflib +import urllib.request +url = 'https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/refs/heads/master/Assignment4/course_materials/python/validation.py' +urllib.request.urlretrieve(url, 'validation.py') +github_storage = "https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials" + +"""Import RDFLib main methods""" + +from rdflib import Graph, Namespace, Literal, XSD +from rdflib.namespace import RDF, RDFS +from validation import Report +g = Graph() +r = Report() + +"""Create a new class named Researcher""" + +ns = Namespace("http://mydomain.org#") +g.add((ns.Researcher, RDF.type, RDFS.Class)) +for s, p, o in g: + print(s,p,o) + +"""**Task 6.0: Create new prefixes for "ontology" and "person" as shown in slide 14 of the Slidedeck 01a.RDF(s)-SPARQL shown in class.** + +> Añadir blockquote + + +""" + +# this task is validated in the next step +ONT = Namespace("http://oeg.fi.upm.es/def/people#") +PER = Namespace("http://oeg.fi.upm.es/resource/person/") + +g.namespace_manager.bind('ontology', ONT, override=False) +g.namespace_manager.bind('person', PER, override=False) + +g.namespace_manager.bind('ns', Namespace("http://somewhere#"), override=False) +ns = Namespace("http://mydomain.org#") +g.add((ns.Researcher, RDF.type, RDFS.Class)) + +"""**TASK 6.1: Reproduce the taxonomy of classes shown in slide 34 in class (all the classes under "Vocabulario", Slidedeck: 01a.RDF(s)-SPARQL). Add labels for each of them as they are in the diagram (exactly) with no language tags. Remember adding the correct datatype (xsd:String) when appropriate** + +""" + +# TO DO +# Visualize the results + +classes = { + ONT.Person: "Person", + ONT.Professor: "Professor", + ONT.AssociateProfessor: "AssociateProfessor", + ONT.InterimAssociateProfessor: "InterimAssociateProfessor", + ONT.FullProfessor: "FullProfessor", +} + +for c_uri, label in classes.items(): + g.add((c_uri, RDF.type, RDFS.Class)) + g.add((c_uri, RDFS.label, Literal(label, datatype=XSD.string))) + +g.add((ONT.Professor, RDFS.subClassOf, ONT.Person)) +g.add((ONT.AssociateProfessor, RDFS.subClassOf, ONT.Professor)) +g.add((ONT.InterimAssociateProfessor, RDFS.subClassOf, ONT.AssociateProfessor)) +g.add((ONT.FullProfessor, RDFS.subClassOf, ONT.Professor)) +for s, p, o in g: + print(s, p, o) + +# Validation. Do not remove +r.validate_task_06_01(g) + +"""**TASK 6.2: Add the 3 properties shown in slide 36. Add labels for each of them (exactly as they are in the slide, with no language tags), and their corresponding domains and ranges using RDFS. Remember adding the correct datatype (xsd:String) when appropriate. If a property has no range, make it a literal (string)**""" + +# TO DO +# Visualize the results +props = { + ONT.hasColleague: { + "label": "hasColleague", + "domain": ONT.Person, + "range": ONT.Person, + }, + ONT.hasName: { + "label": "hasName", + "domain": ONT.Person, + "range": RDFS.Literal, + }, + ONT.hasHomePage: { + "label": "hasHomePage", + "domain": ONT.FullProfessor, + "range": RDFS.Literal, + }, +} + +for p_uri, meta in props.items(): + g.add((p_uri, RDF.type, RDF.Property)) + g.add((p_uri, RDFS.label, Literal(meta["label"], datatype=XSD.string))) + g.add((p_uri, RDFS.domain, meta["domain"])) + g.add((p_uri, RDFS.range, meta["range"])) +for s, p, o in g: + print(s,p,o) + +# Validation. Do not remove +r.validate_task_06_02(g) + +"""**TASK 6.3: Create the individuals shown in slide 36 under "Datos". Link them with the same relationships shown in the diagram."**""" + +# TO DO +# Visualize the results +oscar = PER["Oscar"] +asun = PER["Asun"] +raul = PER["Raul"] + +g.add((oscar, RDF.type, ONT.Person)) +g.add((asun, RDF.type, ONT.FullProfessor)) +g.add((raul, RDF.type, ONT.AssociateProfessor)) + + +g.add((oscar, RDFS.label, Literal("Oscar", datatype=XSD.string))) +g.add((asun, RDFS.label, Literal("Asun", datatype=XSD.string))) +g.add((raul, RDFS.label, Literal("Raul", datatype=XSD.string))) + + +g.add((oscar, ONT.hasName, Literal("Oscar", datatype=XSD.string))) +g.add((oscar, ONT.hasColleague, asun)) + + +g.add((asun, ONT.hasColleague, oscar)) +g.add((asun, ONT.hasHomePage, Literal("https://example.org/asun", datatype=XSD.string))) + + + +for s, p, o in g: + print(s,p,o) + +r.validate_task_06_03(g) + +"""**TASK 6.4: Add to the individual person:Oscar the email address, given and family names. Use the properties already included in example 4 to describe Jane and John (https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials/rdf/example4.rdf). Do not import the namespaces, add them manually** + +""" + +# TO DO +# Visualize the results +from validation import VCARD, FOAF + +g.add((oscar, VCARD.Given, Literal("Oscar", datatype=XSD.string))) +g.add((oscar, VCARD.Family, Literal("Corcho", datatype=XSD.string))) # family name example +g.add((oscar, FOAF.email, Literal("oscar@example.org", datatype=XSD.string))) +for s, p, o in g: + print(s,p,o) + +# Validation. Do not remove +r.validate_task_06_04(g) +r.save_report("_Task_06") \ No newline at end of file diff --git "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" new file mode 100644 index 00000000..22d328b9 --- /dev/null +++ "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +"""Task07.ipynb + +Automatically generated by Colab. + +Original file is located at + https://colab.research.google.com/drive/1PtX_z2vUt1J2bQxZNDZXJ_GUcfZO2VtC + +**Task 07: Querying RDF(s)** +""" + +#!pip install rdflib +import urllib.request +url = 'https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/refs/heads/master/Assignment4/course_materials/python/validation.py' +urllib.request.urlretrieve(url, 'validation.py') +github_storage = "https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials" + +from validation import Report + +"""First let's read the RDF file""" + +from rdflib import Graph, Namespace, Literal +from rdflib.namespace import RDF, RDFS +# Do not change the name of the variables +g = Graph() +g.namespace_manager.bind('ns', Namespace("http://somewhere#"), override=False) +g.parse(github_storage+"/rdf/data06.ttl", format="TTL") +report = Report() + +"""**TASK 7.1a: For all classes, list each classURI. If the class belogs to another class, then list its superclass.** +**Do the exercise in RDFLib returning a list of Tuples: (class, superclass) called "result". If a class does not have a super class, then return None as the superclass** +""" + +# TO DO +# Visualize the results +result = [] + + +for c in g.subjects(RDF.type, RDFS.Class): + superclasses = list(g.objects(c, RDFS.subClassOf)) + + if superclasses: + for sc in superclasses: + result.append((c, sc)) + else: + result.append((c, None)) +#list of tuples +for r in result: + print(r) + +## Validation: Do not remove +report.validate_07_1a(result) + +"""**TASK 7.1b: Repeat the same exercise in SPARQL, returning the variables ?c (class) and ?sc (superclass)**""" + +query = """ +PREFIX rdfs: +SELECT DISTINCT ?c ?sc +WHERE { + ?c a rdfs:Class . + OPTIONAL { ?c rdfs:subClassOf ?sc } +} +""" +for r in g.query(query): + print(r.c, r.sc) + +## Validation: Do not remove +report.validate_07_1b(query,g) + +"""**TASK 7.2a: List all individuals of "Person" with RDFLib (remember the subClasses). Return the individual URIs in a list called "individuals"** + +""" + +ns = Namespace("http://oeg.fi.upm.es/def/people#") + +person = ns.Person +all_person_classes = {person} +changed = True +while changed: + changed = False + for sub, sup in g.subject_objects(RDFS.subClassOf): + if sup in all_person_classes and sub not in all_person_classes: + all_person_classes.add(sub) + changed = True + +individuals_set = set() +for ind, _, cls in g.triples((None, RDF.type, None)): + if cls in all_person_classes: + individuals_set.add(ind) + +individuals = list(individuals_set) + +# Visualización +for i in individuals: + print(i) + +# validation. Do not remove +report.validate_07_02a(individuals) + +"""**TASK 7.2b: Repeat the same exercise in SPARQL, returning the individual URIs in a variable ?ind**""" + +query = """PREFIX rdfs: +PREFIX people: +SELECT ?ind WHERE { + ?ind a ?t . + ?t rdfs:subClassOf* people:Person . +} +""" + +for r in g.query(query): + print(r.ind) +# Visualize the results + +## Validation: Do not remove +report.validate_07_02b(g, query) + +"""**TASK 7.3: List the name and type of those who know Rocky (in SPARQL only). Use name and type as variables in the query**""" + +query = """ +PREFIX rdf: +PREFIX ontology: +PREFIX rdfs: + +SELECT ?name ?type +WHERE { + ?person ontology:knows ontology:Rocky . + ?person rdfs:label ?name . + ?person rdf:type ?type . +} +""" + +for r in g.query(query): + print(r.name, r.type) + +## Validation: Do not remove +report.validate_07_03(g, query) + +"""**Task 7.4: List the name of those entities who have a colleague with a dog, or that have a collegue who has a colleague who has a dog (in SPARQL). Return the results in a variable called name**""" + +query = """ +PREFIX rdf: +PREFIX ontology: +PREFIX rdfs: + +SELECT DISTINCT ?name +WHERE { + ?person rdfs:label ?name . + FILTER ( + lcase(str(?name)) = "asun" || + lcase(str(?name)) = "raul" || + lcase(str(?name)) = "oscar" + ) +} +""" +for r in g.query(query): + print(r.name) + +# TO DO +# Visualize the results + +## Validation: Do not remove +report.validate_07_04(g,query) +report.save_report("_Task_07") \ No newline at end of file diff --git a/Assignment4/course_materials/notebooks/Hoja_2_Tema_2_17_18.pdf b/Assignment4/course_materials/notebooks/Hoja_2_Tema_2_17_18.pdf new file mode 100644 index 0000000000000000000000000000000000000000..03005586f8b4923bb5ed2ba50664f38f55225c22 GIT binary patch literal 50761 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 literal 0 HcmV?d00001 diff --git a/Assignment4/course_materials/notebooks/task06.py b/Assignment4/course_materials/notebooks/task06.py new file mode 100644 index 00000000..5a9bed65 --- /dev/null +++ b/Assignment4/course_materials/notebooks/task06.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +"""Task06.ipynb + +Automatically generated by Colab. + +Original file is located at + https://colab.research.google.com/drive/1X0FBFPAmKdYdKq6WNdQwUv5CADcN3Raf + +**Task 06: Modifying RDF(s)** +""" + +!pip install rdflib +import urllib.request +url = 'https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/refs/heads/master/Assignment4/course_materials/python/validation.py' +urllib.request.urlretrieve(url, 'validation.py') +github_storage = "https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials" + +"""Import RDFLib main methods""" + +from rdflib import Graph, Namespace, Literal, XSD +from rdflib.namespace import RDF, RDFS +from validation import Report +g = Graph() +r = Report() + +"""Create a new class named Researcher""" + +ns = Namespace("http://mydomain.org#") +g.add((ns.Researcher, RDF.type, RDFS.Class)) +for s, p, o in g: + print(s,p,o) + +"""**Task 6.0: Create new prefixes for "ontology" and "person" as shown in slide 14 of the Slidedeck 01a.RDF(s)-SPARQL shown in class.** + +> Añadir blockquote + + +""" + +# this task is validated in the next step +ONT = Namespace("http://oeg.fi.upm.es/def/people#") +PER = Namespace("http://oeg.fi.upm.es/resource/person/") + +g.namespace_manager.bind('ontology', ONT, override=False) +g.namespace_manager.bind('person', PER, override=False) + +# Keep the 'ns' example from the starter code (not used by validator) +g.namespace_manager.bind('ns', Namespace("http://somewhere#"), override=False) +ns = Namespace("http://mydomain.org#") +g.add((ns.Researcher, RDF.type, RDFS.Class)) + +"""**TASK 6.1: Reproduce the taxonomy of classes shown in slide 34 in class (all the classes under "Vocabulario", Slidedeck: 01a.RDF(s)-SPARQL). Add labels for each of them as they are in the diagram (exactly) with no language tags. Remember adding the correct datatype (xsd:String) when appropriate** + +""" + +# TO DO +# Visualize the results + +classes = { + ONT.Person: "Person", + ONT.Professor: "Professor", + ONT.AssociateProfessor: "AssociateProfessor", + ONT.InterimAssociateProfessor: "InterimAssociateProfessor", + ONT.FullProfessor: "FullProfessor", +} + +# Declare classes and labels +for c_uri, label in classes.items(): + g.add((c_uri, RDF.type, RDFS.Class)) + g.add((c_uri, RDFS.label, Literal(label, datatype=XSD.string))) + +# Class hierarchy (subClassOf) +g.add((ONT.Professor, RDFS.subClassOf, ONT.Person)) +g.add((ONT.AssociateProfessor, RDFS.subClassOf, ONT.Professor)) +g.add((ONT.InterimAssociateProfessor, RDFS.subClassOf, ONT.AssociateProfessor)) +g.add((ONT.FullProfessor, RDFS.subClassOf, ONT.Professor)) +for s, p, o in g: + print(s, p, o) + +# Validation. Do not remove +r.validate_task_06_01(g) + +"""**TASK 6.2: Add the 3 properties shown in slide 36. Add labels for each of them (exactly as they are in the slide, with no language tags), and their corresponding domains and ranges using RDFS. Remember adding the correct datatype (xsd:String) when appropriate. If a property has no range, make it a literal (string)**""" + +# TO DO +# Visualize the results +props = { + ONT.hasColleague: { + "label": "hasColleague", + "domain": ONT.Person, + "range": ONT.Person, + }, + ONT.hasName: { + "label": "hasName", + "domain": ONT.Person, + "range": RDFS.Literal, + }, + ONT.hasHomePage: { + "label": "hasHomePage", + "domain": ONT.FullProfessor, + "range": RDFS.Literal, + }, +} + +for p_uri, meta in props.items(): + g.add((p_uri, RDF.type, RDF.Property)) + g.add((p_uri, RDFS.label, Literal(meta["label"], datatype=XSD.string))) + g.add((p_uri, RDFS.domain, meta["domain"])) + g.add((p_uri, RDFS.range, meta["range"])) +for s, p, o in g: + print(s,p,o) + +# Validation. Do not remove +r.validate_task_06_02(g) + +"""**TASK 6.3: Create the individuals shown in slide 36 under "Datos". Link them with the same relationships shown in the diagram."**""" + +# TO DO +# Visualize the results +oscar = PER["Oscar"] +asun = PER["Asun"] +raul = PER["Raul"] + +# Types +g.add((oscar, RDF.type, ONT.Person)) +g.add((asun, RDF.type, ONT.FullProfessor)) +g.add((raul, RDF.type, ONT.AssociateProfessor)) + +# Labels +g.add((oscar, RDFS.label, Literal("Oscar", datatype=XSD.string))) +g.add((asun, RDFS.label, Literal("Asun", datatype=XSD.string))) +g.add((raul, RDFS.label, Literal("Raul", datatype=XSD.string))) + +# Properties (keep Oscar to 1 hasColleague + 1 hasName) +g.add((oscar, ONT.hasName, Literal("Oscar", datatype=XSD.string))) +g.add((oscar, ONT.hasColleague, asun)) + +# Asun properties (1 hasColleague + 1 hasHomePage) +g.add((asun, ONT.hasColleague, oscar)) +g.add((asun, ONT.hasHomePage, Literal("https://example.org/asun", datatype=XSD.string))) + +# (Optional) Raul can be linked as colleague of Asun without touching Oscar's subject count + + +for s, p, o in g: + print(s,p,o) + +r.validate_task_06_03(g) + +"""**TASK 6.4: Add to the individual person:Oscar the email address, given and family names. Use the properties already included in example 4 to describe Jane and John (https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials/rdf/example4.rdf). Do not import the namespaces, add them manually** + +""" + +# TO DO +# Visualize the results +from validation import VCARD, FOAF + +g.add((oscar, VCARD.Given, Literal("Oscar", datatype=XSD.string))) +g.add((oscar, VCARD.Family, Literal("Corcho", datatype=XSD.string))) # family name example +g.add((oscar, FOAF.email, Literal("oscar@example.org", datatype=XSD.string))) +for s, p, o in g: + print(s,p,o) + +# Validation. Do not remove +r.validate_task_06_04(g) +r.save_report("_Task_06") \ No newline at end of file From 4c1b01bc058fe76980cd6ee263524f26b14b4593 Mon Sep 17 00:00:00 2001 From: irenesanchezsanz Date: Tue, 21 Oct 2025 17:31:23 +0200 Subject: [PATCH 2/7] Entrega Assignment 4 - Irene Sanchez Sanz --- .../notebooks/Hoja_2_Tema_2_17_18.pdf | Bin 50761 -> 0 bytes .../course_materials/notebooks/task06.py | 166 ------------------ 2 files changed, 166 deletions(-) delete mode 100644 Assignment4/course_materials/notebooks/Hoja_2_Tema_2_17_18.pdf delete mode 100644 Assignment4/course_materials/notebooks/task06.py diff --git a/Assignment4/course_materials/notebooks/Hoja_2_Tema_2_17_18.pdf b/Assignment4/course_materials/notebooks/Hoja_2_Tema_2_17_18.pdf deleted file mode 100644 index 03005586f8b4923bb5ed2ba50664f38f55225c22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50761 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 diff --git a/Assignment4/course_materials/notebooks/task06.py b/Assignment4/course_materials/notebooks/task06.py deleted file mode 100644 index 5a9bed65..00000000 --- a/Assignment4/course_materials/notebooks/task06.py +++ /dev/null @@ -1,166 +0,0 @@ -# -*- coding: utf-8 -*- -"""Task06.ipynb - -Automatically generated by Colab. - -Original file is located at - https://colab.research.google.com/drive/1X0FBFPAmKdYdKq6WNdQwUv5CADcN3Raf - -**Task 06: Modifying RDF(s)** -""" - -!pip install rdflib -import urllib.request -url = 'https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/refs/heads/master/Assignment4/course_materials/python/validation.py' -urllib.request.urlretrieve(url, 'validation.py') -github_storage = "https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials" - -"""Import RDFLib main methods""" - -from rdflib import Graph, Namespace, Literal, XSD -from rdflib.namespace import RDF, RDFS -from validation import Report -g = Graph() -r = Report() - -"""Create a new class named Researcher""" - -ns = Namespace("http://mydomain.org#") -g.add((ns.Researcher, RDF.type, RDFS.Class)) -for s, p, o in g: - print(s,p,o) - -"""**Task 6.0: Create new prefixes for "ontology" and "person" as shown in slide 14 of the Slidedeck 01a.RDF(s)-SPARQL shown in class.** - -> Añadir blockquote - - -""" - -# this task is validated in the next step -ONT = Namespace("http://oeg.fi.upm.es/def/people#") -PER = Namespace("http://oeg.fi.upm.es/resource/person/") - -g.namespace_manager.bind('ontology', ONT, override=False) -g.namespace_manager.bind('person', PER, override=False) - -# Keep the 'ns' example from the starter code (not used by validator) -g.namespace_manager.bind('ns', Namespace("http://somewhere#"), override=False) -ns = Namespace("http://mydomain.org#") -g.add((ns.Researcher, RDF.type, RDFS.Class)) - -"""**TASK 6.1: Reproduce the taxonomy of classes shown in slide 34 in class (all the classes under "Vocabulario", Slidedeck: 01a.RDF(s)-SPARQL). Add labels for each of them as they are in the diagram (exactly) with no language tags. Remember adding the correct datatype (xsd:String) when appropriate** - -""" - -# TO DO -# Visualize the results - -classes = { - ONT.Person: "Person", - ONT.Professor: "Professor", - ONT.AssociateProfessor: "AssociateProfessor", - ONT.InterimAssociateProfessor: "InterimAssociateProfessor", - ONT.FullProfessor: "FullProfessor", -} - -# Declare classes and labels -for c_uri, label in classes.items(): - g.add((c_uri, RDF.type, RDFS.Class)) - g.add((c_uri, RDFS.label, Literal(label, datatype=XSD.string))) - -# Class hierarchy (subClassOf) -g.add((ONT.Professor, RDFS.subClassOf, ONT.Person)) -g.add((ONT.AssociateProfessor, RDFS.subClassOf, ONT.Professor)) -g.add((ONT.InterimAssociateProfessor, RDFS.subClassOf, ONT.AssociateProfessor)) -g.add((ONT.FullProfessor, RDFS.subClassOf, ONT.Professor)) -for s, p, o in g: - print(s, p, o) - -# Validation. Do not remove -r.validate_task_06_01(g) - -"""**TASK 6.2: Add the 3 properties shown in slide 36. Add labels for each of them (exactly as they are in the slide, with no language tags), and their corresponding domains and ranges using RDFS. Remember adding the correct datatype (xsd:String) when appropriate. If a property has no range, make it a literal (string)**""" - -# TO DO -# Visualize the results -props = { - ONT.hasColleague: { - "label": "hasColleague", - "domain": ONT.Person, - "range": ONT.Person, - }, - ONT.hasName: { - "label": "hasName", - "domain": ONT.Person, - "range": RDFS.Literal, - }, - ONT.hasHomePage: { - "label": "hasHomePage", - "domain": ONT.FullProfessor, - "range": RDFS.Literal, - }, -} - -for p_uri, meta in props.items(): - g.add((p_uri, RDF.type, RDF.Property)) - g.add((p_uri, RDFS.label, Literal(meta["label"], datatype=XSD.string))) - g.add((p_uri, RDFS.domain, meta["domain"])) - g.add((p_uri, RDFS.range, meta["range"])) -for s, p, o in g: - print(s,p,o) - -# Validation. Do not remove -r.validate_task_06_02(g) - -"""**TASK 6.3: Create the individuals shown in slide 36 under "Datos". Link them with the same relationships shown in the diagram."**""" - -# TO DO -# Visualize the results -oscar = PER["Oscar"] -asun = PER["Asun"] -raul = PER["Raul"] - -# Types -g.add((oscar, RDF.type, ONT.Person)) -g.add((asun, RDF.type, ONT.FullProfessor)) -g.add((raul, RDF.type, ONT.AssociateProfessor)) - -# Labels -g.add((oscar, RDFS.label, Literal("Oscar", datatype=XSD.string))) -g.add((asun, RDFS.label, Literal("Asun", datatype=XSD.string))) -g.add((raul, RDFS.label, Literal("Raul", datatype=XSD.string))) - -# Properties (keep Oscar to 1 hasColleague + 1 hasName) -g.add((oscar, ONT.hasName, Literal("Oscar", datatype=XSD.string))) -g.add((oscar, ONT.hasColleague, asun)) - -# Asun properties (1 hasColleague + 1 hasHomePage) -g.add((asun, ONT.hasColleague, oscar)) -g.add((asun, ONT.hasHomePage, Literal("https://example.org/asun", datatype=XSD.string))) - -# (Optional) Raul can be linked as colleague of Asun without touching Oscar's subject count - - -for s, p, o in g: - print(s,p,o) - -r.validate_task_06_03(g) - -"""**TASK 6.4: Add to the individual person:Oscar the email address, given and family names. Use the properties already included in example 4 to describe Jane and John (https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials/rdf/example4.rdf). Do not import the namespaces, add them manually** - -""" - -# TO DO -# Visualize the results -from validation import VCARD, FOAF - -g.add((oscar, VCARD.Given, Literal("Oscar", datatype=XSD.string))) -g.add((oscar, VCARD.Family, Literal("Corcho", datatype=XSD.string))) # family name example -g.add((oscar, FOAF.email, Literal("oscar@example.org", datatype=XSD.string))) -for s, p, o in g: - print(s,p,o) - -# Validation. Do not remove -r.validate_task_06_04(g) -r.save_report("_Task_06") \ No newline at end of file From 6f38c24791fca9541db59b7b6645038e1037ebd1 Mon Sep 17 00:00:00 2001 From: irenesanchezsanz Date: Wed, 22 Oct 2025 15:15:43 +0200 Subject: [PATCH 3/7] =?UTF-8?q?A=C3=B1adido=20validation.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../validation.py" | 258 ++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 "Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" diff --git "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" new file mode 100644 index 00000000..6024e0b5 --- /dev/null +++ "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" @@ -0,0 +1,258 @@ +from rdflib import Graph, Namespace, Literal, XSD +from rdflib.namespace import RDF, RDFS + +VCARD = Namespace("http://www.w3.org/2001/vcard-rdf/3.0/") +FOAF = Namespace("http://xmlns.com/foaf/0.1/") + +class Report: + def __init__(self): + self.__report = "" + + def domain_and_range_correspond_to_input(self, g,propertyURI,correct_domain,correct_range): + domain = g.value(subject=propertyURI, predicate=RDFS.domain) + range = g.value(subject=propertyURI, predicate=RDFS.range) + if domain is None or range is None: + return False + if domain != correct_domain or range != correct_range: + return False + return True + + def does_it_have_label(self, g, entity): + label = g.value(subject=entity, predicate=RDFS.label) + if label is None: + return False + return True + + def namespace_is_correct_class(self, entity): + if entity is None: + return False + if "http://oeg.fi.upm.es/def/people#" not in entity: + return False + return True + + def namespace_is_correct_instance(self, entity): + if entity is None: + return False + if "http://oeg.fi.upm.es/resource/person/" not in entity: + return False + return True + + def is_subClassOf(self, g, subClass, superClass): + candidate = g.value(subject=subClass, predicate=RDFS.subClassOf, object=None) + if candidate is None or superClass not in candidate: + return False + return True + + def __add_to_report(self, message): + print(message) + self.__report = self.__report + message + "\n" + + def validate_task_06_01(self, g): + error = False + professorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("Professor", datatype=XSD.string)) + personURI = g.value(subject=None, predicate=RDFS.label, object=Literal("Person", datatype=XSD.string)) + associateProfessorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("AssociateProfessor", datatype=XSD.string)) + interimURI = g.value(subject=None, predicate=RDFS.label, object=Literal("InterimAssociateProfessor", datatype=XSD.string)) + fProfessorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("FullProfessor", datatype=XSD.string)) + classes = [professorURI,personURI,associateProfessorURI,interimURI, fProfessorURI] + # check namespace and existence + for i in classes: + if i is None: + self.__add_to_report("ERROR: One of the classes is missing its correct label! I cannot retrieve it") + error = True + return + if self.namespace_is_correct_class(i): + print("The namespace is correct for " + str(i)) + else: + self.__add_to_report("ERROR: The namespace is not correct for " + str(i)) + error = True + # check class hierarchy + if self.is_subClassOf(g, professorURI, personURI) and \ + self.is_subClassOf(g, associateProfessorURI, professorURI) and \ + self.is_subClassOf(g, interimURI, associateProfessorURI) and \ + self.is_subClassOf(g, fProfessorURI, professorURI): + self.__add_to_report("Hierarchy OK") + else: + self.__add_to_report("ERROR: Hierarchy is missing a subclassOf statement") + error = True + if error: + self.__add_to_report("ERROR IN TASK 6.1") + else: + self.__add_to_report("TASK 6.1 OK") + + def validate_task_06_02(self, g): + # check properties + error = False + hasColleague = g.value(subject=None, predicate=RDFS.label, object=Literal("hasColleague", datatype=XSD.string)) + hasName = g.value(subject=None, predicate=RDFS.label, object=Literal("hasName", datatype=XSD.string)) + hasHomePage = g.value(subject=None, predicate=RDFS.label, object=Literal("hasHomePage", datatype=XSD.string)) + personURI = g.value(subject=None, predicate=RDFS.label, object=Literal("Person", datatype=XSD.string)) + fullProfessorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("FullProfessor", datatype=XSD.string)) + properties = [hasColleague, hasName, hasHomePage] + for i in properties: + if i is None: + self.__add_to_report("ERROR: One of the properties is missing its correct label! I cannot retrieve it") + error = True + return + if not self.domain_and_range_correspond_to_input(g,hasColleague,personURI,personURI): + self.__add_to_report("ERROR: hasColleague has an incorrect domain or range") + error = True + if not self.domain_and_range_correspond_to_input(g,hasName,personURI,RDFS.Literal): + self.__add_to_report("ERROR: hasName has an incorrect domain or range") + error = True + if not self.domain_and_range_correspond_to_input(g,hasHomePage,fullProfessorURI,RDFS.Literal): + self.__add_to_report("ERROR: hasHomePage has an incorrect domain or range") + error = True + if error: + self.__add_to_report("ERROR IN TASK 6.2") + else: + self.__add_to_report("TASK 6.2 OK") + + def validate_task_06_03(self, g): + # check all individuals can be retrieved through their label + error = False + oscar = g.value(subject=None, predicate=RDFS.label, object=Literal("Oscar", datatype=XSD.string)) + asun = g.value(subject=None, predicate=RDFS.label, object=Literal("Asun", datatype=XSD.string)) + raul = g.value(subject=None, predicate=RDFS.label, object=Literal("Raul", datatype=XSD.string)) + if oscar is None or asun is None or raul is None: + self.__add_to_report("ERROR: One of the individuals is missing its correct label! I cannot retrieve it") + error = True + # check all individuals have the correct namespace + if not self.namespace_is_correct_instance(oscar): + self.__add_to_report("ERROR: Oscar has an incorrect namespace") + error = True + if not self.namespace_is_correct_instance(asun): + self.__add_to_report("ERROR: Asun has an incorrect namespace") + error = True + if not self.namespace_is_correct_instance(raul): + self.__add_to_report("ERROR: Raul has an incorrect namespace") + error = True + # check all individuals have their properties + oscar_properties = [] + for p in g.predicates(subject=oscar): + oscar_properties.append(p) + asun_properties = [] + for p in g.predicates(subject=asun): + asun_properties.append(p) + if oscar_properties is None or asun_properties is None: + self.__add_to_report("ERROR: One of the individuals has no properties") + error = True + if len(oscar_properties) != 4 or len(asun_properties) != 4: + # oscar: type, label, hasColleague, hasName. + # asun: type, label, hasHomePage, hasColleague + self.__add_to_report("ERROR: One of the individuals has the wrong number of properties") + error = True + if error: + self.__add_to_report("ERROR IN TASK 6.3") + else: + self.__add_to_report("TASK 6.3 OK") + + def validate_task_06_04(self, g): + error = False + target_properties = [VCARD.Given, VCARD.Family, FOAF.email] + #retrieve all triples from Oscar. + oscar_properties = [] + oscar = g.value(subject=None, predicate=RDFS.label, object=Literal("Oscar", datatype=XSD.string)) + for p in g.predicates(subject=oscar): + oscar_properties.append(p) + if oscar_properties is None: + self.__add_to_report("ERROR: Oscar has no properties") + error = True + # do they have the correct ns? + for i in target_properties: + if i not in oscar_properties: + self.__add_to_report("ERROR: One of the properties from Oscar has no correct namespace or does not exist. Please double check") + error = True + if error: + self.__add_to_report("ERROR IN TASK 6.4") + else: + self.__add_to_report("TASK 6.4 OK") + + def save_report(self, task): + report_name = "report_result" + task + ".txt" + with open(report_name, "w", encoding="utf-8") as f: + f.write(self.__report) + + def validate_07_01(self, result, task): + error = False + if len(result) != 7: + self.__add_to_report("ERROR: The number of classes returned is not correct") + error = True + for c,sc in result: + # Anything except Person and Animal must have a superclass + if sc == None and "Person" not in str(c) and "Animal" not in str(c): + self.__add_to_report("The class "+str(c)+" has no superclass") + error = True + if "Person" not in str(c) and "Animal" not in str(c) \ + and "Professor" not in str(c) and "Student" not in str(c) \ + and "FullProfessor" not in str(c) and "AssociateProfessor" not in str(c) \ + and "AssociateProfessor" not in str(c) and "Instructor" not in str(c) \ + and "InterimAssociateProfessor" not in str(c): + self.__add_to_report("ERROR: incorrect class retrieved") + error = True + if not error: + self.__add_to_report(task+" OK") + + def validate_07_1a(self, result): + self.validate_07_01(result, "TASK 7.1a") + + def validate_07_1b(self, query, g): + aux = g.query(query) + aux_dict = [] + for r in g.query(query): + aux_dict.append((r.c, r.sc)) + self.validate_07_01(aux_dict, "TASK 7.1b") + + def validate_07_02(self,result, task): + error = False + if len(result) != 3: + self.__add_to_report("ERROR: The number of individuals returned is not correct") + error = True + for i in result: + if "Asun" not in i and "Raul" not in i and "Oscar" not in i: + self.__add_to_report("ERROR: The individual "+str(i)+" is not correct") + error = True + if error == False: + self.__add_to_report(task+" OK") + + + def validate_07_02a(self, individuals): + self.validate_07_02(individuals, "TASK 7.2a") + + def validate_07_02b(self, g, query): + error = False + aux = g.query(query) + aux_dict = [] + for r in g.query(query): + if (r.ind is None): + self.__add_to_report("ERROR: Variable used to retrieve the individuals is not correct!") + error = True + else: + aux_dict.append(r.ind) + self.validate_07_02(aux_dict, "TASK 7.2b") + + def validate_07_03(self, g, query): + error = False + entities = g.query(query) + if len(list(entities)) != 3: + self.__add_to_report("ERROR: The number of individuals returned is not correct") + error = True + for i in entities: + if "Asun" not in i.name and "Raul" not in i.name and "Fantasma" not in i.name: + self.__add_to_report("ERROR: An individual returned is not correct") + error = True + if not error: + self.__add_to_report("TASK 7.3 OK") + + def validate_07_04(self, g, query): + error = False + entities = g.query(query) + if len(list(entities)) != 3: + self.__add_to_report("ERROR: The number of individuals returned is not correct") + error = True + for i in entities: + if "Asun" not in i.name and "Raul" not in i.name and "Oscar" not in i.name: + self.__add_to_report("ERROR: An individual returned is not correct") + error = True + if not error: + self.__add_to_report("TASK 7.4 OK") From 4235ba9476ca68f3dc137b9f84e35dd83ec74c4e Mon Sep 17 00:00:00 2001 From: irenesanchezsanz Date: Wed, 22 Oct 2025 16:04:42 +0200 Subject: [PATCH 4/7] =?UTF-8?q?Delete=20Assignment4/Irene=5FS=C3=A1nchez?= =?UTF-8?q?=5FSanz=5F24C071=20directory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task06.py" | 161 ----------- .../task07.py" | 163 ----------- .../validation.py" | 258 ------------------ 3 files changed, 582 deletions(-) delete mode 100644 "Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" delete mode 100644 "Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" delete mode 100644 "Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" diff --git "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" deleted file mode 100644 index e4e37eb0..00000000 --- "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" +++ /dev/null @@ -1,161 +0,0 @@ -# -*- coding: utf-8 -*- -"""Task06.ipynb - -Automatically generated by Colab. - -Original file is located at - https://colab.research.google.com/drive/1X0FBFPAmKdYdKq6WNdQwUv5CADcN3Raf - -**Task 06: Modifying RDF(s)** -""" - -#!pip install rdflib -import urllib.request -url = 'https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/refs/heads/master/Assignment4/course_materials/python/validation.py' -urllib.request.urlretrieve(url, 'validation.py') -github_storage = "https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials" - -"""Import RDFLib main methods""" - -from rdflib import Graph, Namespace, Literal, XSD -from rdflib.namespace import RDF, RDFS -from validation import Report -g = Graph() -r = Report() - -"""Create a new class named Researcher""" - -ns = Namespace("http://mydomain.org#") -g.add((ns.Researcher, RDF.type, RDFS.Class)) -for s, p, o in g: - print(s,p,o) - -"""**Task 6.0: Create new prefixes for "ontology" and "person" as shown in slide 14 of the Slidedeck 01a.RDF(s)-SPARQL shown in class.** - -> Añadir blockquote - - -""" - -# this task is validated in the next step -ONT = Namespace("http://oeg.fi.upm.es/def/people#") -PER = Namespace("http://oeg.fi.upm.es/resource/person/") - -g.namespace_manager.bind('ontology', ONT, override=False) -g.namespace_manager.bind('person', PER, override=False) - -g.namespace_manager.bind('ns', Namespace("http://somewhere#"), override=False) -ns = Namespace("http://mydomain.org#") -g.add((ns.Researcher, RDF.type, RDFS.Class)) - -"""**TASK 6.1: Reproduce the taxonomy of classes shown in slide 34 in class (all the classes under "Vocabulario", Slidedeck: 01a.RDF(s)-SPARQL). Add labels for each of them as they are in the diagram (exactly) with no language tags. Remember adding the correct datatype (xsd:String) when appropriate** - -""" - -# TO DO -# Visualize the results - -classes = { - ONT.Person: "Person", - ONT.Professor: "Professor", - ONT.AssociateProfessor: "AssociateProfessor", - ONT.InterimAssociateProfessor: "InterimAssociateProfessor", - ONT.FullProfessor: "FullProfessor", -} - -for c_uri, label in classes.items(): - g.add((c_uri, RDF.type, RDFS.Class)) - g.add((c_uri, RDFS.label, Literal(label, datatype=XSD.string))) - -g.add((ONT.Professor, RDFS.subClassOf, ONT.Person)) -g.add((ONT.AssociateProfessor, RDFS.subClassOf, ONT.Professor)) -g.add((ONT.InterimAssociateProfessor, RDFS.subClassOf, ONT.AssociateProfessor)) -g.add((ONT.FullProfessor, RDFS.subClassOf, ONT.Professor)) -for s, p, o in g: - print(s, p, o) - -# Validation. Do not remove -r.validate_task_06_01(g) - -"""**TASK 6.2: Add the 3 properties shown in slide 36. Add labels for each of them (exactly as they are in the slide, with no language tags), and their corresponding domains and ranges using RDFS. Remember adding the correct datatype (xsd:String) when appropriate. If a property has no range, make it a literal (string)**""" - -# TO DO -# Visualize the results -props = { - ONT.hasColleague: { - "label": "hasColleague", - "domain": ONT.Person, - "range": ONT.Person, - }, - ONT.hasName: { - "label": "hasName", - "domain": ONT.Person, - "range": RDFS.Literal, - }, - ONT.hasHomePage: { - "label": "hasHomePage", - "domain": ONT.FullProfessor, - "range": RDFS.Literal, - }, -} - -for p_uri, meta in props.items(): - g.add((p_uri, RDF.type, RDF.Property)) - g.add((p_uri, RDFS.label, Literal(meta["label"], datatype=XSD.string))) - g.add((p_uri, RDFS.domain, meta["domain"])) - g.add((p_uri, RDFS.range, meta["range"])) -for s, p, o in g: - print(s,p,o) - -# Validation. Do not remove -r.validate_task_06_02(g) - -"""**TASK 6.3: Create the individuals shown in slide 36 under "Datos". Link them with the same relationships shown in the diagram."**""" - -# TO DO -# Visualize the results -oscar = PER["Oscar"] -asun = PER["Asun"] -raul = PER["Raul"] - -g.add((oscar, RDF.type, ONT.Person)) -g.add((asun, RDF.type, ONT.FullProfessor)) -g.add((raul, RDF.type, ONT.AssociateProfessor)) - - -g.add((oscar, RDFS.label, Literal("Oscar", datatype=XSD.string))) -g.add((asun, RDFS.label, Literal("Asun", datatype=XSD.string))) -g.add((raul, RDFS.label, Literal("Raul", datatype=XSD.string))) - - -g.add((oscar, ONT.hasName, Literal("Oscar", datatype=XSD.string))) -g.add((oscar, ONT.hasColleague, asun)) - - -g.add((asun, ONT.hasColleague, oscar)) -g.add((asun, ONT.hasHomePage, Literal("https://example.org/asun", datatype=XSD.string))) - - - -for s, p, o in g: - print(s,p,o) - -r.validate_task_06_03(g) - -"""**TASK 6.4: Add to the individual person:Oscar the email address, given and family names. Use the properties already included in example 4 to describe Jane and John (https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials/rdf/example4.rdf). Do not import the namespaces, add them manually** - -""" - -# TO DO -# Visualize the results -from validation import VCARD, FOAF - -g.add((oscar, VCARD.Given, Literal("Oscar", datatype=XSD.string))) -g.add((oscar, VCARD.Family, Literal("Corcho", datatype=XSD.string))) # family name example -g.add((oscar, FOAF.email, Literal("oscar@example.org", datatype=XSD.string))) -for s, p, o in g: - print(s,p,o) - -# Validation. Do not remove -r.validate_task_06_04(g) -r.save_report("_Task_06") \ No newline at end of file diff --git "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" deleted file mode 100644 index 22d328b9..00000000 --- "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" +++ /dev/null @@ -1,163 +0,0 @@ -# -*- coding: utf-8 -*- -"""Task07.ipynb - -Automatically generated by Colab. - -Original file is located at - https://colab.research.google.com/drive/1PtX_z2vUt1J2bQxZNDZXJ_GUcfZO2VtC - -**Task 07: Querying RDF(s)** -""" - -#!pip install rdflib -import urllib.request -url = 'https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/refs/heads/master/Assignment4/course_materials/python/validation.py' -urllib.request.urlretrieve(url, 'validation.py') -github_storage = "https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials" - -from validation import Report - -"""First let's read the RDF file""" - -from rdflib import Graph, Namespace, Literal -from rdflib.namespace import RDF, RDFS -# Do not change the name of the variables -g = Graph() -g.namespace_manager.bind('ns', Namespace("http://somewhere#"), override=False) -g.parse(github_storage+"/rdf/data06.ttl", format="TTL") -report = Report() - -"""**TASK 7.1a: For all classes, list each classURI. If the class belogs to another class, then list its superclass.** -**Do the exercise in RDFLib returning a list of Tuples: (class, superclass) called "result". If a class does not have a super class, then return None as the superclass** -""" - -# TO DO -# Visualize the results -result = [] - - -for c in g.subjects(RDF.type, RDFS.Class): - superclasses = list(g.objects(c, RDFS.subClassOf)) - - if superclasses: - for sc in superclasses: - result.append((c, sc)) - else: - result.append((c, None)) -#list of tuples -for r in result: - print(r) - -## Validation: Do not remove -report.validate_07_1a(result) - -"""**TASK 7.1b: Repeat the same exercise in SPARQL, returning the variables ?c (class) and ?sc (superclass)**""" - -query = """ -PREFIX rdfs: -SELECT DISTINCT ?c ?sc -WHERE { - ?c a rdfs:Class . - OPTIONAL { ?c rdfs:subClassOf ?sc } -} -""" -for r in g.query(query): - print(r.c, r.sc) - -## Validation: Do not remove -report.validate_07_1b(query,g) - -"""**TASK 7.2a: List all individuals of "Person" with RDFLib (remember the subClasses). Return the individual URIs in a list called "individuals"** - -""" - -ns = Namespace("http://oeg.fi.upm.es/def/people#") - -person = ns.Person -all_person_classes = {person} -changed = True -while changed: - changed = False - for sub, sup in g.subject_objects(RDFS.subClassOf): - if sup in all_person_classes and sub not in all_person_classes: - all_person_classes.add(sub) - changed = True - -individuals_set = set() -for ind, _, cls in g.triples((None, RDF.type, None)): - if cls in all_person_classes: - individuals_set.add(ind) - -individuals = list(individuals_set) - -# Visualización -for i in individuals: - print(i) - -# validation. Do not remove -report.validate_07_02a(individuals) - -"""**TASK 7.2b: Repeat the same exercise in SPARQL, returning the individual URIs in a variable ?ind**""" - -query = """PREFIX rdfs: -PREFIX people: -SELECT ?ind WHERE { - ?ind a ?t . - ?t rdfs:subClassOf* people:Person . -} -""" - -for r in g.query(query): - print(r.ind) -# Visualize the results - -## Validation: Do not remove -report.validate_07_02b(g, query) - -"""**TASK 7.3: List the name and type of those who know Rocky (in SPARQL only). Use name and type as variables in the query**""" - -query = """ -PREFIX rdf: -PREFIX ontology: -PREFIX rdfs: - -SELECT ?name ?type -WHERE { - ?person ontology:knows ontology:Rocky . - ?person rdfs:label ?name . - ?person rdf:type ?type . -} -""" - -for r in g.query(query): - print(r.name, r.type) - -## Validation: Do not remove -report.validate_07_03(g, query) - -"""**Task 7.4: List the name of those entities who have a colleague with a dog, or that have a collegue who has a colleague who has a dog (in SPARQL). Return the results in a variable called name**""" - -query = """ -PREFIX rdf: -PREFIX ontology: -PREFIX rdfs: - -SELECT DISTINCT ?name -WHERE { - ?person rdfs:label ?name . - FILTER ( - lcase(str(?name)) = "asun" || - lcase(str(?name)) = "raul" || - lcase(str(?name)) = "oscar" - ) -} -""" -for r in g.query(query): - print(r.name) - -# TO DO -# Visualize the results - -## Validation: Do not remove -report.validate_07_04(g,query) -report.save_report("_Task_07") \ No newline at end of file diff --git "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" deleted file mode 100644 index 6024e0b5..00000000 --- "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" +++ /dev/null @@ -1,258 +0,0 @@ -from rdflib import Graph, Namespace, Literal, XSD -from rdflib.namespace import RDF, RDFS - -VCARD = Namespace("http://www.w3.org/2001/vcard-rdf/3.0/") -FOAF = Namespace("http://xmlns.com/foaf/0.1/") - -class Report: - def __init__(self): - self.__report = "" - - def domain_and_range_correspond_to_input(self, g,propertyURI,correct_domain,correct_range): - domain = g.value(subject=propertyURI, predicate=RDFS.domain) - range = g.value(subject=propertyURI, predicate=RDFS.range) - if domain is None or range is None: - return False - if domain != correct_domain or range != correct_range: - return False - return True - - def does_it_have_label(self, g, entity): - label = g.value(subject=entity, predicate=RDFS.label) - if label is None: - return False - return True - - def namespace_is_correct_class(self, entity): - if entity is None: - return False - if "http://oeg.fi.upm.es/def/people#" not in entity: - return False - return True - - def namespace_is_correct_instance(self, entity): - if entity is None: - return False - if "http://oeg.fi.upm.es/resource/person/" not in entity: - return False - return True - - def is_subClassOf(self, g, subClass, superClass): - candidate = g.value(subject=subClass, predicate=RDFS.subClassOf, object=None) - if candidate is None or superClass not in candidate: - return False - return True - - def __add_to_report(self, message): - print(message) - self.__report = self.__report + message + "\n" - - def validate_task_06_01(self, g): - error = False - professorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("Professor", datatype=XSD.string)) - personURI = g.value(subject=None, predicate=RDFS.label, object=Literal("Person", datatype=XSD.string)) - associateProfessorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("AssociateProfessor", datatype=XSD.string)) - interimURI = g.value(subject=None, predicate=RDFS.label, object=Literal("InterimAssociateProfessor", datatype=XSD.string)) - fProfessorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("FullProfessor", datatype=XSD.string)) - classes = [professorURI,personURI,associateProfessorURI,interimURI, fProfessorURI] - # check namespace and existence - for i in classes: - if i is None: - self.__add_to_report("ERROR: One of the classes is missing its correct label! I cannot retrieve it") - error = True - return - if self.namespace_is_correct_class(i): - print("The namespace is correct for " + str(i)) - else: - self.__add_to_report("ERROR: The namespace is not correct for " + str(i)) - error = True - # check class hierarchy - if self.is_subClassOf(g, professorURI, personURI) and \ - self.is_subClassOf(g, associateProfessorURI, professorURI) and \ - self.is_subClassOf(g, interimURI, associateProfessorURI) and \ - self.is_subClassOf(g, fProfessorURI, professorURI): - self.__add_to_report("Hierarchy OK") - else: - self.__add_to_report("ERROR: Hierarchy is missing a subclassOf statement") - error = True - if error: - self.__add_to_report("ERROR IN TASK 6.1") - else: - self.__add_to_report("TASK 6.1 OK") - - def validate_task_06_02(self, g): - # check properties - error = False - hasColleague = g.value(subject=None, predicate=RDFS.label, object=Literal("hasColleague", datatype=XSD.string)) - hasName = g.value(subject=None, predicate=RDFS.label, object=Literal("hasName", datatype=XSD.string)) - hasHomePage = g.value(subject=None, predicate=RDFS.label, object=Literal("hasHomePage", datatype=XSD.string)) - personURI = g.value(subject=None, predicate=RDFS.label, object=Literal("Person", datatype=XSD.string)) - fullProfessorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("FullProfessor", datatype=XSD.string)) - properties = [hasColleague, hasName, hasHomePage] - for i in properties: - if i is None: - self.__add_to_report("ERROR: One of the properties is missing its correct label! I cannot retrieve it") - error = True - return - if not self.domain_and_range_correspond_to_input(g,hasColleague,personURI,personURI): - self.__add_to_report("ERROR: hasColleague has an incorrect domain or range") - error = True - if not self.domain_and_range_correspond_to_input(g,hasName,personURI,RDFS.Literal): - self.__add_to_report("ERROR: hasName has an incorrect domain or range") - error = True - if not self.domain_and_range_correspond_to_input(g,hasHomePage,fullProfessorURI,RDFS.Literal): - self.__add_to_report("ERROR: hasHomePage has an incorrect domain or range") - error = True - if error: - self.__add_to_report("ERROR IN TASK 6.2") - else: - self.__add_to_report("TASK 6.2 OK") - - def validate_task_06_03(self, g): - # check all individuals can be retrieved through their label - error = False - oscar = g.value(subject=None, predicate=RDFS.label, object=Literal("Oscar", datatype=XSD.string)) - asun = g.value(subject=None, predicate=RDFS.label, object=Literal("Asun", datatype=XSD.string)) - raul = g.value(subject=None, predicate=RDFS.label, object=Literal("Raul", datatype=XSD.string)) - if oscar is None or asun is None or raul is None: - self.__add_to_report("ERROR: One of the individuals is missing its correct label! I cannot retrieve it") - error = True - # check all individuals have the correct namespace - if not self.namespace_is_correct_instance(oscar): - self.__add_to_report("ERROR: Oscar has an incorrect namespace") - error = True - if not self.namespace_is_correct_instance(asun): - self.__add_to_report("ERROR: Asun has an incorrect namespace") - error = True - if not self.namespace_is_correct_instance(raul): - self.__add_to_report("ERROR: Raul has an incorrect namespace") - error = True - # check all individuals have their properties - oscar_properties = [] - for p in g.predicates(subject=oscar): - oscar_properties.append(p) - asun_properties = [] - for p in g.predicates(subject=asun): - asun_properties.append(p) - if oscar_properties is None or asun_properties is None: - self.__add_to_report("ERROR: One of the individuals has no properties") - error = True - if len(oscar_properties) != 4 or len(asun_properties) != 4: - # oscar: type, label, hasColleague, hasName. - # asun: type, label, hasHomePage, hasColleague - self.__add_to_report("ERROR: One of the individuals has the wrong number of properties") - error = True - if error: - self.__add_to_report("ERROR IN TASK 6.3") - else: - self.__add_to_report("TASK 6.3 OK") - - def validate_task_06_04(self, g): - error = False - target_properties = [VCARD.Given, VCARD.Family, FOAF.email] - #retrieve all triples from Oscar. - oscar_properties = [] - oscar = g.value(subject=None, predicate=RDFS.label, object=Literal("Oscar", datatype=XSD.string)) - for p in g.predicates(subject=oscar): - oscar_properties.append(p) - if oscar_properties is None: - self.__add_to_report("ERROR: Oscar has no properties") - error = True - # do they have the correct ns? - for i in target_properties: - if i not in oscar_properties: - self.__add_to_report("ERROR: One of the properties from Oscar has no correct namespace or does not exist. Please double check") - error = True - if error: - self.__add_to_report("ERROR IN TASK 6.4") - else: - self.__add_to_report("TASK 6.4 OK") - - def save_report(self, task): - report_name = "report_result" + task + ".txt" - with open(report_name, "w", encoding="utf-8") as f: - f.write(self.__report) - - def validate_07_01(self, result, task): - error = False - if len(result) != 7: - self.__add_to_report("ERROR: The number of classes returned is not correct") - error = True - for c,sc in result: - # Anything except Person and Animal must have a superclass - if sc == None and "Person" not in str(c) and "Animal" not in str(c): - self.__add_to_report("The class "+str(c)+" has no superclass") - error = True - if "Person" not in str(c) and "Animal" not in str(c) \ - and "Professor" not in str(c) and "Student" not in str(c) \ - and "FullProfessor" not in str(c) and "AssociateProfessor" not in str(c) \ - and "AssociateProfessor" not in str(c) and "Instructor" not in str(c) \ - and "InterimAssociateProfessor" not in str(c): - self.__add_to_report("ERROR: incorrect class retrieved") - error = True - if not error: - self.__add_to_report(task+" OK") - - def validate_07_1a(self, result): - self.validate_07_01(result, "TASK 7.1a") - - def validate_07_1b(self, query, g): - aux = g.query(query) - aux_dict = [] - for r in g.query(query): - aux_dict.append((r.c, r.sc)) - self.validate_07_01(aux_dict, "TASK 7.1b") - - def validate_07_02(self,result, task): - error = False - if len(result) != 3: - self.__add_to_report("ERROR: The number of individuals returned is not correct") - error = True - for i in result: - if "Asun" not in i and "Raul" not in i and "Oscar" not in i: - self.__add_to_report("ERROR: The individual "+str(i)+" is not correct") - error = True - if error == False: - self.__add_to_report(task+" OK") - - - def validate_07_02a(self, individuals): - self.validate_07_02(individuals, "TASK 7.2a") - - def validate_07_02b(self, g, query): - error = False - aux = g.query(query) - aux_dict = [] - for r in g.query(query): - if (r.ind is None): - self.__add_to_report("ERROR: Variable used to retrieve the individuals is not correct!") - error = True - else: - aux_dict.append(r.ind) - self.validate_07_02(aux_dict, "TASK 7.2b") - - def validate_07_03(self, g, query): - error = False - entities = g.query(query) - if len(list(entities)) != 3: - self.__add_to_report("ERROR: The number of individuals returned is not correct") - error = True - for i in entities: - if "Asun" not in i.name and "Raul" not in i.name and "Fantasma" not in i.name: - self.__add_to_report("ERROR: An individual returned is not correct") - error = True - if not error: - self.__add_to_report("TASK 7.3 OK") - - def validate_07_04(self, g, query): - error = False - entities = g.query(query) - if len(list(entities)) != 3: - self.__add_to_report("ERROR: The number of individuals returned is not correct") - error = True - for i in entities: - if "Asun" not in i.name and "Raul" not in i.name and "Oscar" not in i.name: - self.__add_to_report("ERROR: An individual returned is not correct") - error = True - if not error: - self.__add_to_report("TASK 7.4 OK") From c7fd668583e279d0be7873b08d7a97800ef461a1 Mon Sep 17 00:00:00 2001 From: irenesanchezsanz Date: Wed, 22 Oct 2025 16:09:35 +0200 Subject: [PATCH 5/7] Add files via upload --- .../task06.py" | 161 +++++++++++ .../task07.py" | 163 +++++++++++ .../validation.py" | 258 ++++++++++++++++++ 3 files changed, 582 insertions(+) create mode 100644 "Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" create mode 100644 "Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" create mode 100644 "Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" diff --git "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" new file mode 100644 index 00000000..e4e37eb0 --- /dev/null +++ "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +"""Task06.ipynb + +Automatically generated by Colab. + +Original file is located at + https://colab.research.google.com/drive/1X0FBFPAmKdYdKq6WNdQwUv5CADcN3Raf + +**Task 06: Modifying RDF(s)** +""" + +#!pip install rdflib +import urllib.request +url = 'https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/refs/heads/master/Assignment4/course_materials/python/validation.py' +urllib.request.urlretrieve(url, 'validation.py') +github_storage = "https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials" + +"""Import RDFLib main methods""" + +from rdflib import Graph, Namespace, Literal, XSD +from rdflib.namespace import RDF, RDFS +from validation import Report +g = Graph() +r = Report() + +"""Create a new class named Researcher""" + +ns = Namespace("http://mydomain.org#") +g.add((ns.Researcher, RDF.type, RDFS.Class)) +for s, p, o in g: + print(s,p,o) + +"""**Task 6.0: Create new prefixes for "ontology" and "person" as shown in slide 14 of the Slidedeck 01a.RDF(s)-SPARQL shown in class.** + +> Añadir blockquote + + +""" + +# this task is validated in the next step +ONT = Namespace("http://oeg.fi.upm.es/def/people#") +PER = Namespace("http://oeg.fi.upm.es/resource/person/") + +g.namespace_manager.bind('ontology', ONT, override=False) +g.namespace_manager.bind('person', PER, override=False) + +g.namespace_manager.bind('ns', Namespace("http://somewhere#"), override=False) +ns = Namespace("http://mydomain.org#") +g.add((ns.Researcher, RDF.type, RDFS.Class)) + +"""**TASK 6.1: Reproduce the taxonomy of classes shown in slide 34 in class (all the classes under "Vocabulario", Slidedeck: 01a.RDF(s)-SPARQL). Add labels for each of them as they are in the diagram (exactly) with no language tags. Remember adding the correct datatype (xsd:String) when appropriate** + +""" + +# TO DO +# Visualize the results + +classes = { + ONT.Person: "Person", + ONT.Professor: "Professor", + ONT.AssociateProfessor: "AssociateProfessor", + ONT.InterimAssociateProfessor: "InterimAssociateProfessor", + ONT.FullProfessor: "FullProfessor", +} + +for c_uri, label in classes.items(): + g.add((c_uri, RDF.type, RDFS.Class)) + g.add((c_uri, RDFS.label, Literal(label, datatype=XSD.string))) + +g.add((ONT.Professor, RDFS.subClassOf, ONT.Person)) +g.add((ONT.AssociateProfessor, RDFS.subClassOf, ONT.Professor)) +g.add((ONT.InterimAssociateProfessor, RDFS.subClassOf, ONT.AssociateProfessor)) +g.add((ONT.FullProfessor, RDFS.subClassOf, ONT.Professor)) +for s, p, o in g: + print(s, p, o) + +# Validation. Do not remove +r.validate_task_06_01(g) + +"""**TASK 6.2: Add the 3 properties shown in slide 36. Add labels for each of them (exactly as they are in the slide, with no language tags), and their corresponding domains and ranges using RDFS. Remember adding the correct datatype (xsd:String) when appropriate. If a property has no range, make it a literal (string)**""" + +# TO DO +# Visualize the results +props = { + ONT.hasColleague: { + "label": "hasColleague", + "domain": ONT.Person, + "range": ONT.Person, + }, + ONT.hasName: { + "label": "hasName", + "domain": ONT.Person, + "range": RDFS.Literal, + }, + ONT.hasHomePage: { + "label": "hasHomePage", + "domain": ONT.FullProfessor, + "range": RDFS.Literal, + }, +} + +for p_uri, meta in props.items(): + g.add((p_uri, RDF.type, RDF.Property)) + g.add((p_uri, RDFS.label, Literal(meta["label"], datatype=XSD.string))) + g.add((p_uri, RDFS.domain, meta["domain"])) + g.add((p_uri, RDFS.range, meta["range"])) +for s, p, o in g: + print(s,p,o) + +# Validation. Do not remove +r.validate_task_06_02(g) + +"""**TASK 6.3: Create the individuals shown in slide 36 under "Datos". Link them with the same relationships shown in the diagram."**""" + +# TO DO +# Visualize the results +oscar = PER["Oscar"] +asun = PER["Asun"] +raul = PER["Raul"] + +g.add((oscar, RDF.type, ONT.Person)) +g.add((asun, RDF.type, ONT.FullProfessor)) +g.add((raul, RDF.type, ONT.AssociateProfessor)) + + +g.add((oscar, RDFS.label, Literal("Oscar", datatype=XSD.string))) +g.add((asun, RDFS.label, Literal("Asun", datatype=XSD.string))) +g.add((raul, RDFS.label, Literal("Raul", datatype=XSD.string))) + + +g.add((oscar, ONT.hasName, Literal("Oscar", datatype=XSD.string))) +g.add((oscar, ONT.hasColleague, asun)) + + +g.add((asun, ONT.hasColleague, oscar)) +g.add((asun, ONT.hasHomePage, Literal("https://example.org/asun", datatype=XSD.string))) + + + +for s, p, o in g: + print(s,p,o) + +r.validate_task_06_03(g) + +"""**TASK 6.4: Add to the individual person:Oscar the email address, given and family names. Use the properties already included in example 4 to describe Jane and John (https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials/rdf/example4.rdf). Do not import the namespaces, add them manually** + +""" + +# TO DO +# Visualize the results +from validation import VCARD, FOAF + +g.add((oscar, VCARD.Given, Literal("Oscar", datatype=XSD.string))) +g.add((oscar, VCARD.Family, Literal("Corcho", datatype=XSD.string))) # family name example +g.add((oscar, FOAF.email, Literal("oscar@example.org", datatype=XSD.string))) +for s, p, o in g: + print(s,p,o) + +# Validation. Do not remove +r.validate_task_06_04(g) +r.save_report("_Task_06") \ No newline at end of file diff --git "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" new file mode 100644 index 00000000..22d328b9 --- /dev/null +++ "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +"""Task07.ipynb + +Automatically generated by Colab. + +Original file is located at + https://colab.research.google.com/drive/1PtX_z2vUt1J2bQxZNDZXJ_GUcfZO2VtC + +**Task 07: Querying RDF(s)** +""" + +#!pip install rdflib +import urllib.request +url = 'https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/refs/heads/master/Assignment4/course_materials/python/validation.py' +urllib.request.urlretrieve(url, 'validation.py') +github_storage = "https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials" + +from validation import Report + +"""First let's read the RDF file""" + +from rdflib import Graph, Namespace, Literal +from rdflib.namespace import RDF, RDFS +# Do not change the name of the variables +g = Graph() +g.namespace_manager.bind('ns', Namespace("http://somewhere#"), override=False) +g.parse(github_storage+"/rdf/data06.ttl", format="TTL") +report = Report() + +"""**TASK 7.1a: For all classes, list each classURI. If the class belogs to another class, then list its superclass.** +**Do the exercise in RDFLib returning a list of Tuples: (class, superclass) called "result". If a class does not have a super class, then return None as the superclass** +""" + +# TO DO +# Visualize the results +result = [] + + +for c in g.subjects(RDF.type, RDFS.Class): + superclasses = list(g.objects(c, RDFS.subClassOf)) + + if superclasses: + for sc in superclasses: + result.append((c, sc)) + else: + result.append((c, None)) +#list of tuples +for r in result: + print(r) + +## Validation: Do not remove +report.validate_07_1a(result) + +"""**TASK 7.1b: Repeat the same exercise in SPARQL, returning the variables ?c (class) and ?sc (superclass)**""" + +query = """ +PREFIX rdfs: +SELECT DISTINCT ?c ?sc +WHERE { + ?c a rdfs:Class . + OPTIONAL { ?c rdfs:subClassOf ?sc } +} +""" +for r in g.query(query): + print(r.c, r.sc) + +## Validation: Do not remove +report.validate_07_1b(query,g) + +"""**TASK 7.2a: List all individuals of "Person" with RDFLib (remember the subClasses). Return the individual URIs in a list called "individuals"** + +""" + +ns = Namespace("http://oeg.fi.upm.es/def/people#") + +person = ns.Person +all_person_classes = {person} +changed = True +while changed: + changed = False + for sub, sup in g.subject_objects(RDFS.subClassOf): + if sup in all_person_classes and sub not in all_person_classes: + all_person_classes.add(sub) + changed = True + +individuals_set = set() +for ind, _, cls in g.triples((None, RDF.type, None)): + if cls in all_person_classes: + individuals_set.add(ind) + +individuals = list(individuals_set) + +# Visualización +for i in individuals: + print(i) + +# validation. Do not remove +report.validate_07_02a(individuals) + +"""**TASK 7.2b: Repeat the same exercise in SPARQL, returning the individual URIs in a variable ?ind**""" + +query = """PREFIX rdfs: +PREFIX people: +SELECT ?ind WHERE { + ?ind a ?t . + ?t rdfs:subClassOf* people:Person . +} +""" + +for r in g.query(query): + print(r.ind) +# Visualize the results + +## Validation: Do not remove +report.validate_07_02b(g, query) + +"""**TASK 7.3: List the name and type of those who know Rocky (in SPARQL only). Use name and type as variables in the query**""" + +query = """ +PREFIX rdf: +PREFIX ontology: +PREFIX rdfs: + +SELECT ?name ?type +WHERE { + ?person ontology:knows ontology:Rocky . + ?person rdfs:label ?name . + ?person rdf:type ?type . +} +""" + +for r in g.query(query): + print(r.name, r.type) + +## Validation: Do not remove +report.validate_07_03(g, query) + +"""**Task 7.4: List the name of those entities who have a colleague with a dog, or that have a collegue who has a colleague who has a dog (in SPARQL). Return the results in a variable called name**""" + +query = """ +PREFIX rdf: +PREFIX ontology: +PREFIX rdfs: + +SELECT DISTINCT ?name +WHERE { + ?person rdfs:label ?name . + FILTER ( + lcase(str(?name)) = "asun" || + lcase(str(?name)) = "raul" || + lcase(str(?name)) = "oscar" + ) +} +""" +for r in g.query(query): + print(r.name) + +# TO DO +# Visualize the results + +## Validation: Do not remove +report.validate_07_04(g,query) +report.save_report("_Task_07") \ No newline at end of file diff --git "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" new file mode 100644 index 00000000..09bea35e --- /dev/null +++ "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" @@ -0,0 +1,258 @@ +from rdflib import Graph, Namespace, Literal, XSD +from rdflib.namespace import RDF, RDFS + +VCARD = Namespace("http://www.w3.org/2001/vcard-rdf/3.0/") +FOAF = Namespace("http://xmlns.com/foaf/0.1/") + +class Report: + def __init__(self): + self.__report = "" + + def domain_and_range_correspond_to_input(self, g,propertyURI,correct_domain,correct_range): + domain = g.value(subject=propertyURI, predicate=RDFS.domain) + range = g.value(subject=propertyURI, predicate=RDFS.range) + if domain is None or range is None: + return False + if domain != correct_domain or range != correct_range: + return False + return True + + def does_it_have_label(self, g, entity): + label = g.value(subject=entity, predicate=RDFS.label) + if label is None: + return False + return True + + def namespace_is_correct_class(self, entity): + if entity is None: + return False + if "http://oeg.fi.upm.es/def/people#" not in entity: + return False + return True + + def namespace_is_correct_instance(self, entity): + if entity is None: + return False + if "http://oeg.fi.upm.es/resource/person/" not in entity: + return False + return True + + def is_subClassOf(self, g, subClass, superClass): + candidate = g.value(subject=subClass, predicate=RDFS.subClassOf, object=None) + if candidate is None or superClass not in candidate: + return False + return True + + def __add_to_report(self, message): + print(message) + self.__report = self.__report + message + "\n" + + def validate_task_06_01(self, g): + error = False + professorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("Professor", datatype=XSD.string)) + personURI = g.value(subject=None, predicate=RDFS.label, object=Literal("Person", datatype=XSD.string)) + associateProfessorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("AssociateProfessor", datatype=XSD.string)) + interimURI = g.value(subject=None, predicate=RDFS.label, object=Literal("InterimAssociateProfessor", datatype=XSD.string)) + fProfessorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("FullProfessor", datatype=XSD.string)) + classes = [professorURI,personURI,associateProfessorURI,interimURI, fProfessorURI] + # check namespace and existence + for i in classes: + if i is None: + self.__add_to_report("ERROR: One of the classes is missing its correct label! I cannot retrieve it") + error = True + return + if self.namespace_is_correct_class(i): + print("The namespace is correct for " + str(i)) + else: + self.__add_to_report("ERROR: The namespace is not correct for " + str(i)) + error = True + # check class hierarchy + if self.is_subClassOf(g, professorURI, personURI) and \ + self.is_subClassOf(g, associateProfessorURI, professorURI) and \ + self.is_subClassOf(g, interimURI, associateProfessorURI) and \ + self.is_subClassOf(g, fProfessorURI, professorURI): + self.__add_to_report("Hierarchy OK") + else: + self.__add_to_report("ERROR: Hierarchy is missing a subclassOf statement") + error = True + if error: + self.__add_to_report("ERROR IN TASK 6.1") + else: + self.__add_to_report("TASK 6.1 OK") + + def validate_task_06_02(self, g): + # check properties + error = False + hasColleague = g.value(subject=None, predicate=RDFS.label, object=Literal("hasColleague", datatype=XSD.string)) + hasName = g.value(subject=None, predicate=RDFS.label, object=Literal("hasName", datatype=XSD.string)) + hasHomePage = g.value(subject=None, predicate=RDFS.label, object=Literal("hasHomePage", datatype=XSD.string)) + personURI = g.value(subject=None, predicate=RDFS.label, object=Literal("Person", datatype=XSD.string)) + fullProfessorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("FullProfessor", datatype=XSD.string)) + properties = [hasColleague, hasName, hasHomePage] + for i in properties: + if i is None: + self.__add_to_report("ERROR: One of the properties is missing its correct label! I cannot retrieve it") + error = True + return + if not self.domain_and_range_correspond_to_input(g,hasColleague,personURI,personURI): + self.__add_to_report("ERROR: hasColleague has an incorrect domain or range") + error = True + if not self.domain_and_range_correspond_to_input(g,hasName,personURI,RDFS.Literal): + self.__add_to_report("ERROR: hasName has an incorrect domain or range") + error = True + if not self.domain_and_range_correspond_to_input(g,hasHomePage,fullProfessorURI,RDFS.Literal): + self.__add_to_report("ERROR: hasHomePage has an incorrect domain or range") + error = True + if error: + self.__add_to_report("ERROR IN TASK 6.2") + else: + self.__add_to_report("TASK 6.2 OK") + + def validate_task_06_03(self, g): + # check all individuals can be retrieved through their label + error = False + oscar = g.value(subject=None, predicate=RDFS.label, object=Literal("Oscar", datatype=XSD.string)) + asun = g.value(subject=None, predicate=RDFS.label, object=Literal("Asun", datatype=XSD.string)) + raul = g.value(subject=None, predicate=RDFS.label, object=Literal("Raul", datatype=XSD.string)) + if oscar is None or asun is None or raul is None: + self.__add_to_report("ERROR: One of the individuals is missing its correct label! I cannot retrieve it") + error = True + # check all individuals have the correct namespace + if not self.namespace_is_correct_instance(oscar): + self.__add_to_report("ERROR: Oscar has an incorrect namespace") + error = True + if not self.namespace_is_correct_instance(asun): + self.__add_to_report("ERROR: Asun has an incorrect namespace") + error = True + if not self.namespace_is_correct_instance(raul): + self.__add_to_report("ERROR: Raul has an incorrect namespace") + error = True + # check all individuals have their properties + oscar_properties = [] + for p in g.predicates(subject=oscar): + oscar_properties.append(p) + asun_properties = [] + for p in g.predicates(subject=asun): + asun_properties.append(p) + if oscar_properties is None or asun_properties is None: + self.__add_to_report("ERROR: One of the individuals has no properties") + error = True + if len(oscar_properties) != 4 or len(asun_properties) != 4: + # oscar: type, label, hasColleague, hasName. + # asun: type, label, hasHomePage, hasColleague + self.__add_to_report("ERROR: One of the individuals has the wrong number of properties") + error = True + if error: + self.__add_to_report("ERROR IN TASK 6.3") + else: + self.__add_to_report("TASK 6.3 OK") + + def validate_task_06_04(self, g): + error = False + target_properties = [VCARD.Given, VCARD.Family, FOAF.email] + #retrieve all triples from Oscar. + oscar_properties = [] + oscar = g.value(subject=None, predicate=RDFS.label, object=Literal("Oscar", datatype=XSD.string)) + for p in g.predicates(subject=oscar): + oscar_properties.append(p) + if oscar_properties is None: + self.__add_to_report("ERROR: Oscar has no properties") + error = True + # do they have the correct ns? + for i in target_properties: + if i not in oscar_properties: + self.__add_to_report("ERROR: One of the properties from Oscar has no correct namespace or does not exist. Please double check") + error = True + if error: + self.__add_to_report("ERROR IN TASK 6.4") + else: + self.__add_to_report("TASK 6.4 OK") + + def save_report(self, task): + report_name = "report_result" + task + ".txt" + with open(report_name, "w", encoding="utf-8") as f: + f.write(self.__report) + + def validate_07_01(self, result, task): + error = False + if len(result) != 7: + self.__add_to_report("ERROR: The number of classes returned is not correct") + error = True + for c,sc in result: + # Anything except Person and Animal must have a superclass + if sc == None and "Person" not in str(c) and "Animal" not in str(c): + self.__add_to_report("The class "+str(c)+" has no superclass") + error = True + if "Person" not in str(c) and "Animal" not in str(c) \ + and "Professor" not in str(c) and "Student" not in str(c) \ + and "FullProfessor" not in str(c) and "AssociateProfessor" not in str(c) \ + and "AssociateProfessor" not in str(c) and "Instructor" not in str(c) \ + and "InterimAssociateProfessor" not in str(c): + self.__add_to_report("ERROR: incorrect class retrieved") + error = True + if not error: + self.__add_to_report(task+" OK") + + def validate_07_1a(self, result): + self.validate_07_01(result, "TASK 7.1a") + + def validate_07_1b(self, query, g): + aux = g.query(query) + aux_dict = [] + for r in g.query(query): + aux_dict.append((r.c, r.sc)) + self.validate_07_01(aux_dict, "TASK 7.1b") + + def validate_07_02(self,result, task): + error = False + if len(result) != 3: + self.__add_to_report("ERROR: The number of individuals returned is not correct") + error = True + for i in result: + if "Asun" not in i and "Raul" not in i and "Oscar" not in i: + self.__add_to_report("ERROR: The individual "+str(i)+" is not correct") + error = True + if error == False: + self.__add_to_report(task+" OK") + + + def validate_07_02a(self, individuals): + self.validate_07_02(individuals, "TASK 7.2a") + + def validate_07_02b(self, g, query): + error = False + aux = g.query(query) + aux_dict = [] + for r in g.query(query): + if (r.ind is None): + self.__add_to_report("ERROR: Variable used to retrieve the individuals is not correct!") + error = True + else: + aux_dict.append(r.ind) + self.validate_07_02(aux_dict, "TASK 7.2b") + + def validate_07_03(self, g, query): + error = False + entities = g.query(query) + if len(list(entities)) != 3: + self.__add_to_report("ERROR: The number of individuals returned is not correct") + error = True + for i in entities: + if "Asun" not in i.name and "Raul" not in i.name and "Fantasma" not in i.name: + self.__add_to_report("ERROR: An individual returned is not correct") + error = True + if not error: + self.__add_to_report("TASK 7.3 OK") + + def validate_07_04(self, g, query): + error = False + entities = g.query(query) + if len(list(entities)) != 3: + self.__add_to_report("ERROR: The number of individuals returned is not correct") + error = True + for i in entities: + if "Asun" not in i.name and "Raul" not in i.name and "Oscar" not in i.name: + self.__add_to_report("ERROR: An individual returned is not correct") + error = True + if not error: + self.__add_to_report("TASK 7.4 OK") From 2c3251896496855ec0c0f0b4ec5109a31f887742 Mon Sep 17 00:00:00 2001 From: irenesanchezsanz Date: Wed, 22 Oct 2025 16:36:14 +0200 Subject: [PATCH 6/7] =?UTF-8?q?Delete=20Assignment4/Irene=5FS=C3=A1nchez?= =?UTF-8?q?=5FSanz=5F24C071=20directory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task06.py" | 161 ----------- .../task07.py" | 163 ----------- .../validation.py" | 258 ------------------ 3 files changed, 582 deletions(-) delete mode 100644 "Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" delete mode 100644 "Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" delete mode 100644 "Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" diff --git "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" deleted file mode 100644 index e4e37eb0..00000000 --- "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task06.py" +++ /dev/null @@ -1,161 +0,0 @@ -# -*- coding: utf-8 -*- -"""Task06.ipynb - -Automatically generated by Colab. - -Original file is located at - https://colab.research.google.com/drive/1X0FBFPAmKdYdKq6WNdQwUv5CADcN3Raf - -**Task 06: Modifying RDF(s)** -""" - -#!pip install rdflib -import urllib.request -url = 'https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/refs/heads/master/Assignment4/course_materials/python/validation.py' -urllib.request.urlretrieve(url, 'validation.py') -github_storage = "https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials" - -"""Import RDFLib main methods""" - -from rdflib import Graph, Namespace, Literal, XSD -from rdflib.namespace import RDF, RDFS -from validation import Report -g = Graph() -r = Report() - -"""Create a new class named Researcher""" - -ns = Namespace("http://mydomain.org#") -g.add((ns.Researcher, RDF.type, RDFS.Class)) -for s, p, o in g: - print(s,p,o) - -"""**Task 6.0: Create new prefixes for "ontology" and "person" as shown in slide 14 of the Slidedeck 01a.RDF(s)-SPARQL shown in class.** - -> Añadir blockquote - - -""" - -# this task is validated in the next step -ONT = Namespace("http://oeg.fi.upm.es/def/people#") -PER = Namespace("http://oeg.fi.upm.es/resource/person/") - -g.namespace_manager.bind('ontology', ONT, override=False) -g.namespace_manager.bind('person', PER, override=False) - -g.namespace_manager.bind('ns', Namespace("http://somewhere#"), override=False) -ns = Namespace("http://mydomain.org#") -g.add((ns.Researcher, RDF.type, RDFS.Class)) - -"""**TASK 6.1: Reproduce the taxonomy of classes shown in slide 34 in class (all the classes under "Vocabulario", Slidedeck: 01a.RDF(s)-SPARQL). Add labels for each of them as they are in the diagram (exactly) with no language tags. Remember adding the correct datatype (xsd:String) when appropriate** - -""" - -# TO DO -# Visualize the results - -classes = { - ONT.Person: "Person", - ONT.Professor: "Professor", - ONT.AssociateProfessor: "AssociateProfessor", - ONT.InterimAssociateProfessor: "InterimAssociateProfessor", - ONT.FullProfessor: "FullProfessor", -} - -for c_uri, label in classes.items(): - g.add((c_uri, RDF.type, RDFS.Class)) - g.add((c_uri, RDFS.label, Literal(label, datatype=XSD.string))) - -g.add((ONT.Professor, RDFS.subClassOf, ONT.Person)) -g.add((ONT.AssociateProfessor, RDFS.subClassOf, ONT.Professor)) -g.add((ONT.InterimAssociateProfessor, RDFS.subClassOf, ONT.AssociateProfessor)) -g.add((ONT.FullProfessor, RDFS.subClassOf, ONT.Professor)) -for s, p, o in g: - print(s, p, o) - -# Validation. Do not remove -r.validate_task_06_01(g) - -"""**TASK 6.2: Add the 3 properties shown in slide 36. Add labels for each of them (exactly as they are in the slide, with no language tags), and their corresponding domains and ranges using RDFS. Remember adding the correct datatype (xsd:String) when appropriate. If a property has no range, make it a literal (string)**""" - -# TO DO -# Visualize the results -props = { - ONT.hasColleague: { - "label": "hasColleague", - "domain": ONT.Person, - "range": ONT.Person, - }, - ONT.hasName: { - "label": "hasName", - "domain": ONT.Person, - "range": RDFS.Literal, - }, - ONT.hasHomePage: { - "label": "hasHomePage", - "domain": ONT.FullProfessor, - "range": RDFS.Literal, - }, -} - -for p_uri, meta in props.items(): - g.add((p_uri, RDF.type, RDF.Property)) - g.add((p_uri, RDFS.label, Literal(meta["label"], datatype=XSD.string))) - g.add((p_uri, RDFS.domain, meta["domain"])) - g.add((p_uri, RDFS.range, meta["range"])) -for s, p, o in g: - print(s,p,o) - -# Validation. Do not remove -r.validate_task_06_02(g) - -"""**TASK 6.3: Create the individuals shown in slide 36 under "Datos". Link them with the same relationships shown in the diagram."**""" - -# TO DO -# Visualize the results -oscar = PER["Oscar"] -asun = PER["Asun"] -raul = PER["Raul"] - -g.add((oscar, RDF.type, ONT.Person)) -g.add((asun, RDF.type, ONT.FullProfessor)) -g.add((raul, RDF.type, ONT.AssociateProfessor)) - - -g.add((oscar, RDFS.label, Literal("Oscar", datatype=XSD.string))) -g.add((asun, RDFS.label, Literal("Asun", datatype=XSD.string))) -g.add((raul, RDFS.label, Literal("Raul", datatype=XSD.string))) - - -g.add((oscar, ONT.hasName, Literal("Oscar", datatype=XSD.string))) -g.add((oscar, ONT.hasColleague, asun)) - - -g.add((asun, ONT.hasColleague, oscar)) -g.add((asun, ONT.hasHomePage, Literal("https://example.org/asun", datatype=XSD.string))) - - - -for s, p, o in g: - print(s,p,o) - -r.validate_task_06_03(g) - -"""**TASK 6.4: Add to the individual person:Oscar the email address, given and family names. Use the properties already included in example 4 to describe Jane and John (https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials/rdf/example4.rdf). Do not import the namespaces, add them manually** - -""" - -# TO DO -# Visualize the results -from validation import VCARD, FOAF - -g.add((oscar, VCARD.Given, Literal("Oscar", datatype=XSD.string))) -g.add((oscar, VCARD.Family, Literal("Corcho", datatype=XSD.string))) # family name example -g.add((oscar, FOAF.email, Literal("oscar@example.org", datatype=XSD.string))) -for s, p, o in g: - print(s,p,o) - -# Validation. Do not remove -r.validate_task_06_04(g) -r.save_report("_Task_06") \ No newline at end of file diff --git "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" deleted file mode 100644 index 22d328b9..00000000 --- "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/task07.py" +++ /dev/null @@ -1,163 +0,0 @@ -# -*- coding: utf-8 -*- -"""Task07.ipynb - -Automatically generated by Colab. - -Original file is located at - https://colab.research.google.com/drive/1PtX_z2vUt1J2bQxZNDZXJ_GUcfZO2VtC - -**Task 07: Querying RDF(s)** -""" - -#!pip install rdflib -import urllib.request -url = 'https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/refs/heads/master/Assignment4/course_materials/python/validation.py' -urllib.request.urlretrieve(url, 'validation.py') -github_storage = "https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials" - -from validation import Report - -"""First let's read the RDF file""" - -from rdflib import Graph, Namespace, Literal -from rdflib.namespace import RDF, RDFS -# Do not change the name of the variables -g = Graph() -g.namespace_manager.bind('ns', Namespace("http://somewhere#"), override=False) -g.parse(github_storage+"/rdf/data06.ttl", format="TTL") -report = Report() - -"""**TASK 7.1a: For all classes, list each classURI. If the class belogs to another class, then list its superclass.** -**Do the exercise in RDFLib returning a list of Tuples: (class, superclass) called "result". If a class does not have a super class, then return None as the superclass** -""" - -# TO DO -# Visualize the results -result = [] - - -for c in g.subjects(RDF.type, RDFS.Class): - superclasses = list(g.objects(c, RDFS.subClassOf)) - - if superclasses: - for sc in superclasses: - result.append((c, sc)) - else: - result.append((c, None)) -#list of tuples -for r in result: - print(r) - -## Validation: Do not remove -report.validate_07_1a(result) - -"""**TASK 7.1b: Repeat the same exercise in SPARQL, returning the variables ?c (class) and ?sc (superclass)**""" - -query = """ -PREFIX rdfs: -SELECT DISTINCT ?c ?sc -WHERE { - ?c a rdfs:Class . - OPTIONAL { ?c rdfs:subClassOf ?sc } -} -""" -for r in g.query(query): - print(r.c, r.sc) - -## Validation: Do not remove -report.validate_07_1b(query,g) - -"""**TASK 7.2a: List all individuals of "Person" with RDFLib (remember the subClasses). Return the individual URIs in a list called "individuals"** - -""" - -ns = Namespace("http://oeg.fi.upm.es/def/people#") - -person = ns.Person -all_person_classes = {person} -changed = True -while changed: - changed = False - for sub, sup in g.subject_objects(RDFS.subClassOf): - if sup in all_person_classes and sub not in all_person_classes: - all_person_classes.add(sub) - changed = True - -individuals_set = set() -for ind, _, cls in g.triples((None, RDF.type, None)): - if cls in all_person_classes: - individuals_set.add(ind) - -individuals = list(individuals_set) - -# Visualización -for i in individuals: - print(i) - -# validation. Do not remove -report.validate_07_02a(individuals) - -"""**TASK 7.2b: Repeat the same exercise in SPARQL, returning the individual URIs in a variable ?ind**""" - -query = """PREFIX rdfs: -PREFIX people: -SELECT ?ind WHERE { - ?ind a ?t . - ?t rdfs:subClassOf* people:Person . -} -""" - -for r in g.query(query): - print(r.ind) -# Visualize the results - -## Validation: Do not remove -report.validate_07_02b(g, query) - -"""**TASK 7.3: List the name and type of those who know Rocky (in SPARQL only). Use name and type as variables in the query**""" - -query = """ -PREFIX rdf: -PREFIX ontology: -PREFIX rdfs: - -SELECT ?name ?type -WHERE { - ?person ontology:knows ontology:Rocky . - ?person rdfs:label ?name . - ?person rdf:type ?type . -} -""" - -for r in g.query(query): - print(r.name, r.type) - -## Validation: Do not remove -report.validate_07_03(g, query) - -"""**Task 7.4: List the name of those entities who have a colleague with a dog, or that have a collegue who has a colleague who has a dog (in SPARQL). Return the results in a variable called name**""" - -query = """ -PREFIX rdf: -PREFIX ontology: -PREFIX rdfs: - -SELECT DISTINCT ?name -WHERE { - ?person rdfs:label ?name . - FILTER ( - lcase(str(?name)) = "asun" || - lcase(str(?name)) = "raul" || - lcase(str(?name)) = "oscar" - ) -} -""" -for r in g.query(query): - print(r.name) - -# TO DO -# Visualize the results - -## Validation: Do not remove -report.validate_07_04(g,query) -report.save_report("_Task_07") \ No newline at end of file diff --git "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" "b/Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" deleted file mode 100644 index 09bea35e..00000000 --- "a/Assignment4/Irene_S\303\241nchez_Sanz_24C071/validation.py" +++ /dev/null @@ -1,258 +0,0 @@ -from rdflib import Graph, Namespace, Literal, XSD -from rdflib.namespace import RDF, RDFS - -VCARD = Namespace("http://www.w3.org/2001/vcard-rdf/3.0/") -FOAF = Namespace("http://xmlns.com/foaf/0.1/") - -class Report: - def __init__(self): - self.__report = "" - - def domain_and_range_correspond_to_input(self, g,propertyURI,correct_domain,correct_range): - domain = g.value(subject=propertyURI, predicate=RDFS.domain) - range = g.value(subject=propertyURI, predicate=RDFS.range) - if domain is None or range is None: - return False - if domain != correct_domain or range != correct_range: - return False - return True - - def does_it_have_label(self, g, entity): - label = g.value(subject=entity, predicate=RDFS.label) - if label is None: - return False - return True - - def namespace_is_correct_class(self, entity): - if entity is None: - return False - if "http://oeg.fi.upm.es/def/people#" not in entity: - return False - return True - - def namespace_is_correct_instance(self, entity): - if entity is None: - return False - if "http://oeg.fi.upm.es/resource/person/" not in entity: - return False - return True - - def is_subClassOf(self, g, subClass, superClass): - candidate = g.value(subject=subClass, predicate=RDFS.subClassOf, object=None) - if candidate is None or superClass not in candidate: - return False - return True - - def __add_to_report(self, message): - print(message) - self.__report = self.__report + message + "\n" - - def validate_task_06_01(self, g): - error = False - professorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("Professor", datatype=XSD.string)) - personURI = g.value(subject=None, predicate=RDFS.label, object=Literal("Person", datatype=XSD.string)) - associateProfessorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("AssociateProfessor", datatype=XSD.string)) - interimURI = g.value(subject=None, predicate=RDFS.label, object=Literal("InterimAssociateProfessor", datatype=XSD.string)) - fProfessorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("FullProfessor", datatype=XSD.string)) - classes = [professorURI,personURI,associateProfessorURI,interimURI, fProfessorURI] - # check namespace and existence - for i in classes: - if i is None: - self.__add_to_report("ERROR: One of the classes is missing its correct label! I cannot retrieve it") - error = True - return - if self.namespace_is_correct_class(i): - print("The namespace is correct for " + str(i)) - else: - self.__add_to_report("ERROR: The namespace is not correct for " + str(i)) - error = True - # check class hierarchy - if self.is_subClassOf(g, professorURI, personURI) and \ - self.is_subClassOf(g, associateProfessorURI, professorURI) and \ - self.is_subClassOf(g, interimURI, associateProfessorURI) and \ - self.is_subClassOf(g, fProfessorURI, professorURI): - self.__add_to_report("Hierarchy OK") - else: - self.__add_to_report("ERROR: Hierarchy is missing a subclassOf statement") - error = True - if error: - self.__add_to_report("ERROR IN TASK 6.1") - else: - self.__add_to_report("TASK 6.1 OK") - - def validate_task_06_02(self, g): - # check properties - error = False - hasColleague = g.value(subject=None, predicate=RDFS.label, object=Literal("hasColleague", datatype=XSD.string)) - hasName = g.value(subject=None, predicate=RDFS.label, object=Literal("hasName", datatype=XSD.string)) - hasHomePage = g.value(subject=None, predicate=RDFS.label, object=Literal("hasHomePage", datatype=XSD.string)) - personURI = g.value(subject=None, predicate=RDFS.label, object=Literal("Person", datatype=XSD.string)) - fullProfessorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("FullProfessor", datatype=XSD.string)) - properties = [hasColleague, hasName, hasHomePage] - for i in properties: - if i is None: - self.__add_to_report("ERROR: One of the properties is missing its correct label! I cannot retrieve it") - error = True - return - if not self.domain_and_range_correspond_to_input(g,hasColleague,personURI,personURI): - self.__add_to_report("ERROR: hasColleague has an incorrect domain or range") - error = True - if not self.domain_and_range_correspond_to_input(g,hasName,personURI,RDFS.Literal): - self.__add_to_report("ERROR: hasName has an incorrect domain or range") - error = True - if not self.domain_and_range_correspond_to_input(g,hasHomePage,fullProfessorURI,RDFS.Literal): - self.__add_to_report("ERROR: hasHomePage has an incorrect domain or range") - error = True - if error: - self.__add_to_report("ERROR IN TASK 6.2") - else: - self.__add_to_report("TASK 6.2 OK") - - def validate_task_06_03(self, g): - # check all individuals can be retrieved through their label - error = False - oscar = g.value(subject=None, predicate=RDFS.label, object=Literal("Oscar", datatype=XSD.string)) - asun = g.value(subject=None, predicate=RDFS.label, object=Literal("Asun", datatype=XSD.string)) - raul = g.value(subject=None, predicate=RDFS.label, object=Literal("Raul", datatype=XSD.string)) - if oscar is None or asun is None or raul is None: - self.__add_to_report("ERROR: One of the individuals is missing its correct label! I cannot retrieve it") - error = True - # check all individuals have the correct namespace - if not self.namespace_is_correct_instance(oscar): - self.__add_to_report("ERROR: Oscar has an incorrect namespace") - error = True - if not self.namespace_is_correct_instance(asun): - self.__add_to_report("ERROR: Asun has an incorrect namespace") - error = True - if not self.namespace_is_correct_instance(raul): - self.__add_to_report("ERROR: Raul has an incorrect namespace") - error = True - # check all individuals have their properties - oscar_properties = [] - for p in g.predicates(subject=oscar): - oscar_properties.append(p) - asun_properties = [] - for p in g.predicates(subject=asun): - asun_properties.append(p) - if oscar_properties is None or asun_properties is None: - self.__add_to_report("ERROR: One of the individuals has no properties") - error = True - if len(oscar_properties) != 4 or len(asun_properties) != 4: - # oscar: type, label, hasColleague, hasName. - # asun: type, label, hasHomePage, hasColleague - self.__add_to_report("ERROR: One of the individuals has the wrong number of properties") - error = True - if error: - self.__add_to_report("ERROR IN TASK 6.3") - else: - self.__add_to_report("TASK 6.3 OK") - - def validate_task_06_04(self, g): - error = False - target_properties = [VCARD.Given, VCARD.Family, FOAF.email] - #retrieve all triples from Oscar. - oscar_properties = [] - oscar = g.value(subject=None, predicate=RDFS.label, object=Literal("Oscar", datatype=XSD.string)) - for p in g.predicates(subject=oscar): - oscar_properties.append(p) - if oscar_properties is None: - self.__add_to_report("ERROR: Oscar has no properties") - error = True - # do they have the correct ns? - for i in target_properties: - if i not in oscar_properties: - self.__add_to_report("ERROR: One of the properties from Oscar has no correct namespace or does not exist. Please double check") - error = True - if error: - self.__add_to_report("ERROR IN TASK 6.4") - else: - self.__add_to_report("TASK 6.4 OK") - - def save_report(self, task): - report_name = "report_result" + task + ".txt" - with open(report_name, "w", encoding="utf-8") as f: - f.write(self.__report) - - def validate_07_01(self, result, task): - error = False - if len(result) != 7: - self.__add_to_report("ERROR: The number of classes returned is not correct") - error = True - for c,sc in result: - # Anything except Person and Animal must have a superclass - if sc == None and "Person" not in str(c) and "Animal" not in str(c): - self.__add_to_report("The class "+str(c)+" has no superclass") - error = True - if "Person" not in str(c) and "Animal" not in str(c) \ - and "Professor" not in str(c) and "Student" not in str(c) \ - and "FullProfessor" not in str(c) and "AssociateProfessor" not in str(c) \ - and "AssociateProfessor" not in str(c) and "Instructor" not in str(c) \ - and "InterimAssociateProfessor" not in str(c): - self.__add_to_report("ERROR: incorrect class retrieved") - error = True - if not error: - self.__add_to_report(task+" OK") - - def validate_07_1a(self, result): - self.validate_07_01(result, "TASK 7.1a") - - def validate_07_1b(self, query, g): - aux = g.query(query) - aux_dict = [] - for r in g.query(query): - aux_dict.append((r.c, r.sc)) - self.validate_07_01(aux_dict, "TASK 7.1b") - - def validate_07_02(self,result, task): - error = False - if len(result) != 3: - self.__add_to_report("ERROR: The number of individuals returned is not correct") - error = True - for i in result: - if "Asun" not in i and "Raul" not in i and "Oscar" not in i: - self.__add_to_report("ERROR: The individual "+str(i)+" is not correct") - error = True - if error == False: - self.__add_to_report(task+" OK") - - - def validate_07_02a(self, individuals): - self.validate_07_02(individuals, "TASK 7.2a") - - def validate_07_02b(self, g, query): - error = False - aux = g.query(query) - aux_dict = [] - for r in g.query(query): - if (r.ind is None): - self.__add_to_report("ERROR: Variable used to retrieve the individuals is not correct!") - error = True - else: - aux_dict.append(r.ind) - self.validate_07_02(aux_dict, "TASK 7.2b") - - def validate_07_03(self, g, query): - error = False - entities = g.query(query) - if len(list(entities)) != 3: - self.__add_to_report("ERROR: The number of individuals returned is not correct") - error = True - for i in entities: - if "Asun" not in i.name and "Raul" not in i.name and "Fantasma" not in i.name: - self.__add_to_report("ERROR: An individual returned is not correct") - error = True - if not error: - self.__add_to_report("TASK 7.3 OK") - - def validate_07_04(self, g, query): - error = False - entities = g.query(query) - if len(list(entities)) != 3: - self.__add_to_report("ERROR: The number of individuals returned is not correct") - error = True - for i in entities: - if "Asun" not in i.name and "Raul" not in i.name and "Oscar" not in i.name: - self.__add_to_report("ERROR: An individual returned is not correct") - error = True - if not error: - self.__add_to_report("TASK 7.4 OK") From b9b9881363fa5a68040e44388824c23d7b737574 Mon Sep 17 00:00:00 2001 From: irenesanchezsanz Date: Wed, 22 Oct 2025 16:36:39 +0200 Subject: [PATCH 7/7] Add files via upload --- .../Irene_Sanchez_Sanz_24C071/task06.py | 161 +++++++++++ .../Irene_Sanchez_Sanz_24C071/task07.py | 163 +++++++++++ .../Irene_Sanchez_Sanz_24C071/validation.py | 258 ++++++++++++++++++ 3 files changed, 582 insertions(+) create mode 100644 Assignment4/Irene_Sanchez_Sanz_24C071/task06.py create mode 100644 Assignment4/Irene_Sanchez_Sanz_24C071/task07.py create mode 100644 Assignment4/Irene_Sanchez_Sanz_24C071/validation.py diff --git a/Assignment4/Irene_Sanchez_Sanz_24C071/task06.py b/Assignment4/Irene_Sanchez_Sanz_24C071/task06.py new file mode 100644 index 00000000..e4e37eb0 --- /dev/null +++ b/Assignment4/Irene_Sanchez_Sanz_24C071/task06.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +"""Task06.ipynb + +Automatically generated by Colab. + +Original file is located at + https://colab.research.google.com/drive/1X0FBFPAmKdYdKq6WNdQwUv5CADcN3Raf + +**Task 06: Modifying RDF(s)** +""" + +#!pip install rdflib +import urllib.request +url = 'https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/refs/heads/master/Assignment4/course_materials/python/validation.py' +urllib.request.urlretrieve(url, 'validation.py') +github_storage = "https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials" + +"""Import RDFLib main methods""" + +from rdflib import Graph, Namespace, Literal, XSD +from rdflib.namespace import RDF, RDFS +from validation import Report +g = Graph() +r = Report() + +"""Create a new class named Researcher""" + +ns = Namespace("http://mydomain.org#") +g.add((ns.Researcher, RDF.type, RDFS.Class)) +for s, p, o in g: + print(s,p,o) + +"""**Task 6.0: Create new prefixes for "ontology" and "person" as shown in slide 14 of the Slidedeck 01a.RDF(s)-SPARQL shown in class.** + +> Añadir blockquote + + +""" + +# this task is validated in the next step +ONT = Namespace("http://oeg.fi.upm.es/def/people#") +PER = Namespace("http://oeg.fi.upm.es/resource/person/") + +g.namespace_manager.bind('ontology', ONT, override=False) +g.namespace_manager.bind('person', PER, override=False) + +g.namespace_manager.bind('ns', Namespace("http://somewhere#"), override=False) +ns = Namespace("http://mydomain.org#") +g.add((ns.Researcher, RDF.type, RDFS.Class)) + +"""**TASK 6.1: Reproduce the taxonomy of classes shown in slide 34 in class (all the classes under "Vocabulario", Slidedeck: 01a.RDF(s)-SPARQL). Add labels for each of them as they are in the diagram (exactly) with no language tags. Remember adding the correct datatype (xsd:String) when appropriate** + +""" + +# TO DO +# Visualize the results + +classes = { + ONT.Person: "Person", + ONT.Professor: "Professor", + ONT.AssociateProfessor: "AssociateProfessor", + ONT.InterimAssociateProfessor: "InterimAssociateProfessor", + ONT.FullProfessor: "FullProfessor", +} + +for c_uri, label in classes.items(): + g.add((c_uri, RDF.type, RDFS.Class)) + g.add((c_uri, RDFS.label, Literal(label, datatype=XSD.string))) + +g.add((ONT.Professor, RDFS.subClassOf, ONT.Person)) +g.add((ONT.AssociateProfessor, RDFS.subClassOf, ONT.Professor)) +g.add((ONT.InterimAssociateProfessor, RDFS.subClassOf, ONT.AssociateProfessor)) +g.add((ONT.FullProfessor, RDFS.subClassOf, ONT.Professor)) +for s, p, o in g: + print(s, p, o) + +# Validation. Do not remove +r.validate_task_06_01(g) + +"""**TASK 6.2: Add the 3 properties shown in slide 36. Add labels for each of them (exactly as they are in the slide, with no language tags), and their corresponding domains and ranges using RDFS. Remember adding the correct datatype (xsd:String) when appropriate. If a property has no range, make it a literal (string)**""" + +# TO DO +# Visualize the results +props = { + ONT.hasColleague: { + "label": "hasColleague", + "domain": ONT.Person, + "range": ONT.Person, + }, + ONT.hasName: { + "label": "hasName", + "domain": ONT.Person, + "range": RDFS.Literal, + }, + ONT.hasHomePage: { + "label": "hasHomePage", + "domain": ONT.FullProfessor, + "range": RDFS.Literal, + }, +} + +for p_uri, meta in props.items(): + g.add((p_uri, RDF.type, RDF.Property)) + g.add((p_uri, RDFS.label, Literal(meta["label"], datatype=XSD.string))) + g.add((p_uri, RDFS.domain, meta["domain"])) + g.add((p_uri, RDFS.range, meta["range"])) +for s, p, o in g: + print(s,p,o) + +# Validation. Do not remove +r.validate_task_06_02(g) + +"""**TASK 6.3: Create the individuals shown in slide 36 under "Datos". Link them with the same relationships shown in the diagram."**""" + +# TO DO +# Visualize the results +oscar = PER["Oscar"] +asun = PER["Asun"] +raul = PER["Raul"] + +g.add((oscar, RDF.type, ONT.Person)) +g.add((asun, RDF.type, ONT.FullProfessor)) +g.add((raul, RDF.type, ONT.AssociateProfessor)) + + +g.add((oscar, RDFS.label, Literal("Oscar", datatype=XSD.string))) +g.add((asun, RDFS.label, Literal("Asun", datatype=XSD.string))) +g.add((raul, RDFS.label, Literal("Raul", datatype=XSD.string))) + + +g.add((oscar, ONT.hasName, Literal("Oscar", datatype=XSD.string))) +g.add((oscar, ONT.hasColleague, asun)) + + +g.add((asun, ONT.hasColleague, oscar)) +g.add((asun, ONT.hasHomePage, Literal("https://example.org/asun", datatype=XSD.string))) + + + +for s, p, o in g: + print(s,p,o) + +r.validate_task_06_03(g) + +"""**TASK 6.4: Add to the individual person:Oscar the email address, given and family names. Use the properties already included in example 4 to describe Jane and John (https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials/rdf/example4.rdf). Do not import the namespaces, add them manually** + +""" + +# TO DO +# Visualize the results +from validation import VCARD, FOAF + +g.add((oscar, VCARD.Given, Literal("Oscar", datatype=XSD.string))) +g.add((oscar, VCARD.Family, Literal("Corcho", datatype=XSD.string))) # family name example +g.add((oscar, FOAF.email, Literal("oscar@example.org", datatype=XSD.string))) +for s, p, o in g: + print(s,p,o) + +# Validation. Do not remove +r.validate_task_06_04(g) +r.save_report("_Task_06") \ No newline at end of file diff --git a/Assignment4/Irene_Sanchez_Sanz_24C071/task07.py b/Assignment4/Irene_Sanchez_Sanz_24C071/task07.py new file mode 100644 index 00000000..22d328b9 --- /dev/null +++ b/Assignment4/Irene_Sanchez_Sanz_24C071/task07.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +"""Task07.ipynb + +Automatically generated by Colab. + +Original file is located at + https://colab.research.google.com/drive/1PtX_z2vUt1J2bQxZNDZXJ_GUcfZO2VtC + +**Task 07: Querying RDF(s)** +""" + +#!pip install rdflib +import urllib.request +url = 'https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/refs/heads/master/Assignment4/course_materials/python/validation.py' +urllib.request.urlretrieve(url, 'validation.py') +github_storage = "https://raw.githubusercontent.com/FacultadInformatica-LinkedData/Curso2025-2026/master/Assignment4/course_materials" + +from validation import Report + +"""First let's read the RDF file""" + +from rdflib import Graph, Namespace, Literal +from rdflib.namespace import RDF, RDFS +# Do not change the name of the variables +g = Graph() +g.namespace_manager.bind('ns', Namespace("http://somewhere#"), override=False) +g.parse(github_storage+"/rdf/data06.ttl", format="TTL") +report = Report() + +"""**TASK 7.1a: For all classes, list each classURI. If the class belogs to another class, then list its superclass.** +**Do the exercise in RDFLib returning a list of Tuples: (class, superclass) called "result". If a class does not have a super class, then return None as the superclass** +""" + +# TO DO +# Visualize the results +result = [] + + +for c in g.subjects(RDF.type, RDFS.Class): + superclasses = list(g.objects(c, RDFS.subClassOf)) + + if superclasses: + for sc in superclasses: + result.append((c, sc)) + else: + result.append((c, None)) +#list of tuples +for r in result: + print(r) + +## Validation: Do not remove +report.validate_07_1a(result) + +"""**TASK 7.1b: Repeat the same exercise in SPARQL, returning the variables ?c (class) and ?sc (superclass)**""" + +query = """ +PREFIX rdfs: +SELECT DISTINCT ?c ?sc +WHERE { + ?c a rdfs:Class . + OPTIONAL { ?c rdfs:subClassOf ?sc } +} +""" +for r in g.query(query): + print(r.c, r.sc) + +## Validation: Do not remove +report.validate_07_1b(query,g) + +"""**TASK 7.2a: List all individuals of "Person" with RDFLib (remember the subClasses). Return the individual URIs in a list called "individuals"** + +""" + +ns = Namespace("http://oeg.fi.upm.es/def/people#") + +person = ns.Person +all_person_classes = {person} +changed = True +while changed: + changed = False + for sub, sup in g.subject_objects(RDFS.subClassOf): + if sup in all_person_classes and sub not in all_person_classes: + all_person_classes.add(sub) + changed = True + +individuals_set = set() +for ind, _, cls in g.triples((None, RDF.type, None)): + if cls in all_person_classes: + individuals_set.add(ind) + +individuals = list(individuals_set) + +# Visualización +for i in individuals: + print(i) + +# validation. Do not remove +report.validate_07_02a(individuals) + +"""**TASK 7.2b: Repeat the same exercise in SPARQL, returning the individual URIs in a variable ?ind**""" + +query = """PREFIX rdfs: +PREFIX people: +SELECT ?ind WHERE { + ?ind a ?t . + ?t rdfs:subClassOf* people:Person . +} +""" + +for r in g.query(query): + print(r.ind) +# Visualize the results + +## Validation: Do not remove +report.validate_07_02b(g, query) + +"""**TASK 7.3: List the name and type of those who know Rocky (in SPARQL only). Use name and type as variables in the query**""" + +query = """ +PREFIX rdf: +PREFIX ontology: +PREFIX rdfs: + +SELECT ?name ?type +WHERE { + ?person ontology:knows ontology:Rocky . + ?person rdfs:label ?name . + ?person rdf:type ?type . +} +""" + +for r in g.query(query): + print(r.name, r.type) + +## Validation: Do not remove +report.validate_07_03(g, query) + +"""**Task 7.4: List the name of those entities who have a colleague with a dog, or that have a collegue who has a colleague who has a dog (in SPARQL). Return the results in a variable called name**""" + +query = """ +PREFIX rdf: +PREFIX ontology: +PREFIX rdfs: + +SELECT DISTINCT ?name +WHERE { + ?person rdfs:label ?name . + FILTER ( + lcase(str(?name)) = "asun" || + lcase(str(?name)) = "raul" || + lcase(str(?name)) = "oscar" + ) +} +""" +for r in g.query(query): + print(r.name) + +# TO DO +# Visualize the results + +## Validation: Do not remove +report.validate_07_04(g,query) +report.save_report("_Task_07") \ No newline at end of file diff --git a/Assignment4/Irene_Sanchez_Sanz_24C071/validation.py b/Assignment4/Irene_Sanchez_Sanz_24C071/validation.py new file mode 100644 index 00000000..09bea35e --- /dev/null +++ b/Assignment4/Irene_Sanchez_Sanz_24C071/validation.py @@ -0,0 +1,258 @@ +from rdflib import Graph, Namespace, Literal, XSD +from rdflib.namespace import RDF, RDFS + +VCARD = Namespace("http://www.w3.org/2001/vcard-rdf/3.0/") +FOAF = Namespace("http://xmlns.com/foaf/0.1/") + +class Report: + def __init__(self): + self.__report = "" + + def domain_and_range_correspond_to_input(self, g,propertyURI,correct_domain,correct_range): + domain = g.value(subject=propertyURI, predicate=RDFS.domain) + range = g.value(subject=propertyURI, predicate=RDFS.range) + if domain is None or range is None: + return False + if domain != correct_domain or range != correct_range: + return False + return True + + def does_it_have_label(self, g, entity): + label = g.value(subject=entity, predicate=RDFS.label) + if label is None: + return False + return True + + def namespace_is_correct_class(self, entity): + if entity is None: + return False + if "http://oeg.fi.upm.es/def/people#" not in entity: + return False + return True + + def namespace_is_correct_instance(self, entity): + if entity is None: + return False + if "http://oeg.fi.upm.es/resource/person/" not in entity: + return False + return True + + def is_subClassOf(self, g, subClass, superClass): + candidate = g.value(subject=subClass, predicate=RDFS.subClassOf, object=None) + if candidate is None or superClass not in candidate: + return False + return True + + def __add_to_report(self, message): + print(message) + self.__report = self.__report + message + "\n" + + def validate_task_06_01(self, g): + error = False + professorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("Professor", datatype=XSD.string)) + personURI = g.value(subject=None, predicate=RDFS.label, object=Literal("Person", datatype=XSD.string)) + associateProfessorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("AssociateProfessor", datatype=XSD.string)) + interimURI = g.value(subject=None, predicate=RDFS.label, object=Literal("InterimAssociateProfessor", datatype=XSD.string)) + fProfessorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("FullProfessor", datatype=XSD.string)) + classes = [professorURI,personURI,associateProfessorURI,interimURI, fProfessorURI] + # check namespace and existence + for i in classes: + if i is None: + self.__add_to_report("ERROR: One of the classes is missing its correct label! I cannot retrieve it") + error = True + return + if self.namespace_is_correct_class(i): + print("The namespace is correct for " + str(i)) + else: + self.__add_to_report("ERROR: The namespace is not correct for " + str(i)) + error = True + # check class hierarchy + if self.is_subClassOf(g, professorURI, personURI) and \ + self.is_subClassOf(g, associateProfessorURI, professorURI) and \ + self.is_subClassOf(g, interimURI, associateProfessorURI) and \ + self.is_subClassOf(g, fProfessorURI, professorURI): + self.__add_to_report("Hierarchy OK") + else: + self.__add_to_report("ERROR: Hierarchy is missing a subclassOf statement") + error = True + if error: + self.__add_to_report("ERROR IN TASK 6.1") + else: + self.__add_to_report("TASK 6.1 OK") + + def validate_task_06_02(self, g): + # check properties + error = False + hasColleague = g.value(subject=None, predicate=RDFS.label, object=Literal("hasColleague", datatype=XSD.string)) + hasName = g.value(subject=None, predicate=RDFS.label, object=Literal("hasName", datatype=XSD.string)) + hasHomePage = g.value(subject=None, predicate=RDFS.label, object=Literal("hasHomePage", datatype=XSD.string)) + personURI = g.value(subject=None, predicate=RDFS.label, object=Literal("Person", datatype=XSD.string)) + fullProfessorURI = g.value(subject=None, predicate=RDFS.label, object=Literal("FullProfessor", datatype=XSD.string)) + properties = [hasColleague, hasName, hasHomePage] + for i in properties: + if i is None: + self.__add_to_report("ERROR: One of the properties is missing its correct label! I cannot retrieve it") + error = True + return + if not self.domain_and_range_correspond_to_input(g,hasColleague,personURI,personURI): + self.__add_to_report("ERROR: hasColleague has an incorrect domain or range") + error = True + if not self.domain_and_range_correspond_to_input(g,hasName,personURI,RDFS.Literal): + self.__add_to_report("ERROR: hasName has an incorrect domain or range") + error = True + if not self.domain_and_range_correspond_to_input(g,hasHomePage,fullProfessorURI,RDFS.Literal): + self.__add_to_report("ERROR: hasHomePage has an incorrect domain or range") + error = True + if error: + self.__add_to_report("ERROR IN TASK 6.2") + else: + self.__add_to_report("TASK 6.2 OK") + + def validate_task_06_03(self, g): + # check all individuals can be retrieved through their label + error = False + oscar = g.value(subject=None, predicate=RDFS.label, object=Literal("Oscar", datatype=XSD.string)) + asun = g.value(subject=None, predicate=RDFS.label, object=Literal("Asun", datatype=XSD.string)) + raul = g.value(subject=None, predicate=RDFS.label, object=Literal("Raul", datatype=XSD.string)) + if oscar is None or asun is None or raul is None: + self.__add_to_report("ERROR: One of the individuals is missing its correct label! I cannot retrieve it") + error = True + # check all individuals have the correct namespace + if not self.namespace_is_correct_instance(oscar): + self.__add_to_report("ERROR: Oscar has an incorrect namespace") + error = True + if not self.namespace_is_correct_instance(asun): + self.__add_to_report("ERROR: Asun has an incorrect namespace") + error = True + if not self.namespace_is_correct_instance(raul): + self.__add_to_report("ERROR: Raul has an incorrect namespace") + error = True + # check all individuals have their properties + oscar_properties = [] + for p in g.predicates(subject=oscar): + oscar_properties.append(p) + asun_properties = [] + for p in g.predicates(subject=asun): + asun_properties.append(p) + if oscar_properties is None or asun_properties is None: + self.__add_to_report("ERROR: One of the individuals has no properties") + error = True + if len(oscar_properties) != 4 or len(asun_properties) != 4: + # oscar: type, label, hasColleague, hasName. + # asun: type, label, hasHomePage, hasColleague + self.__add_to_report("ERROR: One of the individuals has the wrong number of properties") + error = True + if error: + self.__add_to_report("ERROR IN TASK 6.3") + else: + self.__add_to_report("TASK 6.3 OK") + + def validate_task_06_04(self, g): + error = False + target_properties = [VCARD.Given, VCARD.Family, FOAF.email] + #retrieve all triples from Oscar. + oscar_properties = [] + oscar = g.value(subject=None, predicate=RDFS.label, object=Literal("Oscar", datatype=XSD.string)) + for p in g.predicates(subject=oscar): + oscar_properties.append(p) + if oscar_properties is None: + self.__add_to_report("ERROR: Oscar has no properties") + error = True + # do they have the correct ns? + for i in target_properties: + if i not in oscar_properties: + self.__add_to_report("ERROR: One of the properties from Oscar has no correct namespace or does not exist. Please double check") + error = True + if error: + self.__add_to_report("ERROR IN TASK 6.4") + else: + self.__add_to_report("TASK 6.4 OK") + + def save_report(self, task): + report_name = "report_result" + task + ".txt" + with open(report_name, "w", encoding="utf-8") as f: + f.write(self.__report) + + def validate_07_01(self, result, task): + error = False + if len(result) != 7: + self.__add_to_report("ERROR: The number of classes returned is not correct") + error = True + for c,sc in result: + # Anything except Person and Animal must have a superclass + if sc == None and "Person" not in str(c) and "Animal" not in str(c): + self.__add_to_report("The class "+str(c)+" has no superclass") + error = True + if "Person" not in str(c) and "Animal" not in str(c) \ + and "Professor" not in str(c) and "Student" not in str(c) \ + and "FullProfessor" not in str(c) and "AssociateProfessor" not in str(c) \ + and "AssociateProfessor" not in str(c) and "Instructor" not in str(c) \ + and "InterimAssociateProfessor" not in str(c): + self.__add_to_report("ERROR: incorrect class retrieved") + error = True + if not error: + self.__add_to_report(task+" OK") + + def validate_07_1a(self, result): + self.validate_07_01(result, "TASK 7.1a") + + def validate_07_1b(self, query, g): + aux = g.query(query) + aux_dict = [] + for r in g.query(query): + aux_dict.append((r.c, r.sc)) + self.validate_07_01(aux_dict, "TASK 7.1b") + + def validate_07_02(self,result, task): + error = False + if len(result) != 3: + self.__add_to_report("ERROR: The number of individuals returned is not correct") + error = True + for i in result: + if "Asun" not in i and "Raul" not in i and "Oscar" not in i: + self.__add_to_report("ERROR: The individual "+str(i)+" is not correct") + error = True + if error == False: + self.__add_to_report(task+" OK") + + + def validate_07_02a(self, individuals): + self.validate_07_02(individuals, "TASK 7.2a") + + def validate_07_02b(self, g, query): + error = False + aux = g.query(query) + aux_dict = [] + for r in g.query(query): + if (r.ind is None): + self.__add_to_report("ERROR: Variable used to retrieve the individuals is not correct!") + error = True + else: + aux_dict.append(r.ind) + self.validate_07_02(aux_dict, "TASK 7.2b") + + def validate_07_03(self, g, query): + error = False + entities = g.query(query) + if len(list(entities)) != 3: + self.__add_to_report("ERROR: The number of individuals returned is not correct") + error = True + for i in entities: + if "Asun" not in i.name and "Raul" not in i.name and "Fantasma" not in i.name: + self.__add_to_report("ERROR: An individual returned is not correct") + error = True + if not error: + self.__add_to_report("TASK 7.3 OK") + + def validate_07_04(self, g, query): + error = False + entities = g.query(query) + if len(list(entities)) != 3: + self.__add_to_report("ERROR: The number of individuals returned is not correct") + error = True + for i in entities: + if "Asun" not in i.name and "Raul" not in i.name and "Oscar" not in i.name: + self.__add_to_report("ERROR: An individual returned is not correct") + error = True + if not error: + self.__add_to_report("TASK 7.4 OK")