diff --git a/YKWIM/generateJSON.py b/YKWIM/generateJSON.py index 57836401c334e9e0d1b147b019b85e467dbd642a..0f174cbbc0a91f94e46678f2785c804c59a18b3d 100644 --- a/YKWIM/generateJSON.py +++ b/YKWIM/generateJSON.py @@ -59,6 +59,7 @@ def generateJSON(file, path=app.config["UPLOAD_FOLDER"]): element["name"]= enum[0] element["definition"]= enum[2] element["IRI"]= enum[1] + element["source"]=enum[3] list.append(element) d["enumerations"]=list enum_exist = True if (len(list)!=0) else False diff --git a/YKWIM/generateOntology.py b/YKWIM/generateOntology.py new file mode 100644 index 0000000000000000000000000000000000000000..95f9a752d498c446c89b4ebff190b5f79f55cea3 --- /dev/null +++ b/YKWIM/generateOntology.py @@ -0,0 +1,123 @@ +from YKWIM import helpers as h +import json +from YKWIM import app +from rdflib.namespace import RDF, RDFS, OWL, SKOS +from rdflib import Graph, Literal, URIRef, Namespace + +def generateOntology(path=app.config["UPLOAD_FOLDER"],ontology_namespace = "https://data.grandlyon.com/onto/",vocabulary_namespace ="https://data.grandlyon.com/vocab/"): + """generate ontology files from JSON""" + f=open(path + "parsing_result.json") + + VS = Namespace("http://www.w3.org/2003/06/sw-vocab-status/ns#") + d=json.load(f) + g = Graph() + g2 = Graph () + + #------------------------ + #Classes + #------------------------ + for cl in filter (lambda value: False if value["IRI"]!='' else True,d["classes"]): + classeURI = URIRef(ontology_namespace + h.convertToPascalcase(cl["name"])) + + #update json file + list_of_all_values = [elem["name"] for elem in d["classes"]] + index= list_of_all_values.index(cl["name"]) + d["classes"][index]["IRI"]=str(classeURI) + + g.add((classeURI,RDF.type, OWL.Class)) + g.add((classeURI,RDFS.label, Literal(cl["name"]))) + g.add((classeURI,RDFS.comment, Literal(cl["definition"]))) + g.add((classeURI,RDFS.isDefinedBy, URIRef(ontology_namespace))) + g.add((classeURI,VS.term_status, Literal("testing"))) + + #------------------------ + #Data Properties + #------------------------ + for cl in d["classes"]: + for attr in filter (lambda value: False if value["IRI"]!='' else True,cl["attributes"]): + attributeURI = URIRef(ontology_namespace + h.convertToCamelcase(attr["name"])) + + #update json file + list_of_all_values1 = [elem["name"] for elem in d["classes"]] + index1= list_of_all_values1.index(cl["name"]) + + list_of_all_values2 = [elem["name"] for elem in d["classes"][index1]["attributes"]] + index2= list_of_all_values2.index(attr["name"]) + d["classes"][index1]["attributes"][index2]["IRI"]=str(attributeURI) + + g.add((attributeURI,RDF.type, OWL.DatatypeProperty)) + g.add((attributeURI,RDFS.label, Literal(attr["name"]))) + g.add((attributeURI,RDFS.comment, Literal(attr["definition"]))) + g.add((attributeURI,RDFS.isDefinedBy, URIRef(ontology_namespace))) + g.add((attributeURI,VS.term_status, Literal("testing"))) + + #------------------------ + #Object Properties + #------------------------ + for ass in filter (lambda value: False if value["IRI"]!='' else True,d["associations"]): + associationURI = URIRef(ontology_namespace + h.convertToCamelcase(ass["name"])) + + #update json file + list_of_all_values = [elem["name"] for elem in d["associations"]] + index= list_of_all_values.index(ass["name"]) + d["associations"][index]["IRI"]=str(associationURI) + + g.add((associationURI,RDF.type, OWL.ObjectProperty)) + g.add((associationURI,RDFS.label, Literal(ass["name"]))) + g.add((associationURI,RDFS.comment, Literal(ass["definition"]))) + g.add((associationURI,RDFS.isDefinedBy, URIRef(ontology_namespace))) + g.add((associationURI,VS.term_status, Literal("testing"))) + + #------------------------ + #Individuals + #------------------------ + index = None + for enum in d["enumerations"]: + if enum["IRI"]!="": + enumerationURI = URIRef(enum["IRI"]) + else : + enumerationURI = URIRef(vocabulary_namespace + h.convertToPascalcase(enum["name"])) + + #update json file + list_of_all_values = [elem["name"] for elem in d["enumerations"]] + index= list_of_all_values.index(enum["name"]) + d["enumerations"][index]["IRI"]=str(enumerationURI) + + g2.add((enumerationURI, RDF.type, SKOS.ConceptScheme)) + g2.add((enumerationURI,SKOS.prefLabel, Literal(enum["name"]))) + g2.add((enumerationURI,SKOS.definition, Literal(enum["definition"]))) + g2.add((enumerationURI,RDFS.isDefinedBy, URIRef(vocabulary_namespace))) + g2.add((enumerationURI,VS.term_status, Literal("testing"))) + + for val in filter(lambda value: False if value["IRI"]!="" else True, enum["values"]): + valueURI = URIRef(vocabulary_namespace + h.convertToSnakecase(val["name"])) + + if index!=None : #previous creation of an enumeration URI, the index is already known + list_of_all_values2 = [elem["name"] for elem in d["enumerations"][index]["values"]] + index2= list_of_all_values2.index(val["name"]) + d["enumerations"][index]["values"][index2]["IRI"]=str(valueURI) + else : + list_of_all_values = [elem["name"] for elem in d["enumerations"]] + index= list_of_all_values.index(enum["name"]) + + list_of_all_values2 = [elem["name"] for elem in d["enumerations"][index]["values"]] + index2= list_of_all_values2.index(val["name"]) + d["enumerations"][index]["values"][index2]["IRI"]=str(valueURI) + + g2.add((valueURI,RDF.type, SKOS.Concept)) + g2.add((valueURI,SKOS.prefLabel, Literal(val["name"]))) + g2.add((valueURI,SKOS.definition, Literal(val["definition"]))) + g2.add((valueURI,RDFS.isDefinedBy, URIRef(vocabulary_namespace))) + g2.add((valueURI,SKOS.inScheme, enumerationURI)) + g2.add((valueURI,VS.term_status, Literal("testing"))) + + ontology_path = path + "ontology.ttl" + vocabulary_path = path + "vocabulary.ttl" + json_path = path + "parsing_result_completed.json" + with open(ontology_path, 'w') as fo, open(vocabulary_path, 'w') as fv, open(json_path, 'w') as fp : + fo.write(g.serialize(format="turtle")) + fv.write(g2.serialize(format="turtle")) + json.dump(d,fp) + + return g.serialize(format="turtle") + g2.serialize(format="turtle") + diff --git a/YKWIM/generateSparqlGenerateQuery.py b/YKWIM/generateSparqlGenerateQuery.py index 08276e87b0ff222d9058cf4946c245aa9ff8e720..beee448f79a7aaebcde48b733b9dc831e2c2d159 100644 --- a/YKWIM/generateSparqlGenerateQuery.py +++ b/YKWIM/generateSparqlGenerateQuery.py @@ -1,11 +1,7 @@ -##### to review completly -import sys -import subprocess -import generateJSON as g -import uuid -import os +from YKWIM import app, helpers as h +import json -def generateSparqlGenerateQuery (d, dataset) : +def generateSparqlGenerateQuery (dataset,path=app.config["UPLOAD_FOLDER"],vocabulary_namespace ="https://data.grandlyon.com/vocab/", instances_namespace = "https://data.grandlyon.com/id/") : """generate SPARQL Generate query""" s = """PREFIX iter: <http://w3id.org/sparql-generate/iter/> @@ -14,51 +10,37 @@ def generateSparqlGenerateQuery (d, dataset) : PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> GENERATE {\n""" + f=open(path + "parsing_result_completed.json") + d=json.load(f) + #iterate over classes for list in d["classes"]: - if (list["IRI"]!=""): - s+=("?{} a <{}>".format(list["name"],list["IRI"]))+ ";\n" - else: s+=("?{} a <http://data.grandlyon.com/ontology/{}".format(list["name"],list["name"]))+ ">;\n" - + s+=("?{} a <{}>".format(h.convertToPascalcase(list["name"]),list["IRI"]))+ ";\n" + #iterate over attributes l=len(list["attributes"]) for i , attr in enumerate(list["attributes"]): - if (attr["IRI"]!=""): - s+=("\t<{}> ?{}".format(attr["IRI"],attr["name"])) - else: s+=("\t<http://data.grandlyon.com/ontology/{}> ?{}".format(attr["name"],attr["name"])) + s+=("\t<{}> ?{}".format(attr["IRI"],h.convertToPascalcase(attr["name"]))) s+=";\n" if (i<l-1) else ".\n" #iterate over associations for list in d["associations"]: - if (list["IRI"]!=""): - s += ("?{} <{}> ?{}".format(list["source"],list["IRI"],list["destination"])) + ".\n" - else : s += ("?{} <http://data.grandlyon.com/ontology/{}> ?{}".format(list["source"],list["name"],list["destination"])) + ".\n" - - #iterate over enumerations - for list in d["enumerations"]: - if (list["IRI"]!=""): - s+=("?{} a <{}>".format(list["name"],list["IRI"]))+ ".\n" - else: s+=("?{} a <http://data.grandlyon.com/vocabulary/{}".format(list["name"],list["name"]))+ ">.\n" + s += ("?{} <{}> ?{}".format(h.convertToPascalcase(list["source"]),list["IRI"],h.convertToPascalcase(list["destination"]))) + ".\n" s+=("}} \n SOURCE <{}> AS ?source \nITERATOR iter:GeoJSON(?source) AS ?geometricCoordinates ?properties \n WHERE {{\n".format(dataset)) #bindings for list in d["classes"]: for attr in list["attributes"]: - s+=('BIND (fun:JSONPath(?properties,"$.{}") AS ?{})\n'.format(attr["source"], attr["name"])) + s+=('BIND (fun:JSONPath(?properties,"$.{}") AS ?{})\n'.format(attr["source"], h.convertToPascalcase(attr["name"]))) if (attr["id"]=="oui") : - s+=('BIND(IRI(CONCAT("http://data.grandlyon.com/id/{}/",fun:JSONPath(?properties,"$.{}"))) AS ?{})\n'.format(list["name"],attr["source"],list["name"])) - + s+=('BIND(IRI(CONCAT("{}/",fun:JSONPath(?properties,"$.{}"))) AS ?{})\n'.format(instances_namespace+ h.convertToPascalcase(list["name"]),attr["source"],h.convertToPascalcase(list["name"]))) + for enum in d["enumerations"]: - s+=('BIND(IRI("http://data.grandlyon.com/vocabulary/Cadres_administratifs") AS ?{})\n'.format(enum["name"])) + s+=('BIND(IRI(CONCAT("{}",REPLACE(LCASE(fun:JSONPath(?properties,"$.{}"))," ","_"))) AS ?{})\n'.format(vocabulary_namespace,enum["source"],h.convertToPascalcase(enum["name"]))) s+= "}\n" - base_name = "query_"+str(uuid.uuid4()) - query_path = base_name + ".rq" + query_path = path + "query.rq" with open(query_path, 'w') as fp: - fp.write(s) - return query_path - -if __name__ == "__main__": - #dataset = "https://download.data.grandlyon.com/wfs/grandlyon?SERVICE=WFS&VERSION=2.0.0&request=GetFeature&typename=adr_voie_lieu.adrcomgl&outputFormat=application/json;%20subtype=geojson&SRSNAME=EPSG:4171" - print (generateSparqlGenerateQuery(g.generateJSON(sys.argv[1]), sys.argv[2])) \ No newline at end of file + fp.write(s) + return query_path \ No newline at end of file diff --git a/YKWIM/helpers.py b/YKWIM/helpers.py new file mode 100644 index 0000000000000000000000000000000000000000..58a6973edb383ecd50b7ca3b984c06718facd88c --- /dev/null +++ b/YKWIM/helpers.py @@ -0,0 +1,23 @@ +from re import sub + +def convertToSnakecase(s): + ''' + >>> ConvertToSnakecase("Hello World Python Programming") + hello_world_python_programming + ''' + return '_'.join(sub('([A-Z][a-z]+)', r' \1',sub('([A-Z]+)', r' \1',s.replace('-', ' '))).split()).lower() + +def convertToCamelcase(s): + ''' + >>> ConvertToCamelcase("Hello World Python Programming") + helloWorldPythonProgramming + ''' + s = sub(r"(_|-)+", " ", s).title().replace(" ", "") + return ''.join([s[0].lower(), s[1:]]) + +def convertToPascalcase(s): + ''' + >>> convertToPascalcase("Hello World Python Programming") + HelloWorldPythonProgramming + ''' + return "".join(w[0].upper() + w[1:].lower() for w in s.split()) \ No newline at end of file diff --git a/YKWIM/static/jar/lutra.jar b/YKWIM/static/jar/lutra.jar new file mode 100644 index 0000000000000000000000000000000000000000..67130a9da717a2bbcc99267d6c6e21caca5b1de3 Binary files /dev/null and b/YKWIM/static/jar/lutra.jar differ diff --git a/YKWIM/templates/index.html b/YKWIM/templates/index.html index d1f1447fea27b982b0301acb689008b00c8ccdf6..557fe0276bbc16a7613bdac27610260dfcba142b 100644 --- a/YKWIM/templates/index.html +++ b/YKWIM/templates/index.html @@ -51,7 +51,7 @@ <div class="col-8"> <div class="row pt-3"> <div class="col-8 offset-7"> - <a href="{{url_for('static',filename='/doc/template.ods')}}"> + <a href="{{url_for('static',filename='/doc/template.xlsx')}}"> <img src="{{url_for('static',filename='/img/spreadsheet.ico')}}" alt="Lien de téléchargement du Template" class="img-thumbnail img-fluid col-2"> </a> </div> @@ -92,7 +92,11 @@ {% if uml_image %} <div class="container py-3"> <div class="row justify-content-center"> - <p class="col-6 border">Données RDF</p> + <p class="col-6 border"> + {% for line in b_lines %} + {{ line }} + {% endfor %} + </p> <img src="{{url_for('getDocumentLink',document_link=uml_image)}}" class="col-6 border" alt="UML class diagram"> </div> </div> @@ -103,12 +107,26 @@ <div class="row justify-content-center"> <form class="col-8"> <div class="row py-3"> - <label for="ontologyURL" class="col-4 form-label col-form-label">URL</label> + <label for="vocabularyURL" class="col-4 form-label col-form-label">URL de base des vocabulaires contrôlés</label> <div class="col-8"> - <input type="url" id="ontologyURL" class="form-control" aria-describedby="ontologyURLHelp"> + <input type="url" id="vocabularyURL" class="form-control" value="{{vocabulary_namespace}}" aria-describedby="vocabularyURLHelp"> + <span id="vocabularyURLHelp" class="form-text visually-hidden"></span> + </div> + </div> + <div class="row py-3"> + <label for="ontologyURL" class="col-4 form-label col-form-label">URL de base des ontologies</label> + <div class="col-8"> + <input type="url" id="ontologyURL" class="form-control" value="{{ontology_namespace}}" aria-describedby="ontologyURLHelp"> <span id="ontologyURLHelp" class="form-text visually-hidden"></span> </div> </div> + <div class="row py-3"> + <label for="intancesURL" class="col-4 form-label col-form-label">URL de base des instances</label> + <div class="col-8"> + <input type="url" id="intancesURL" class="form-control" value="{{instances_namespace}}" aria-describedby="instancesURLHelp"> + <span id="instancesURLHelp" class="form-text visually-hidden"></span> + </div> + </div> </form> </div> </div> diff --git a/YKWIM/validateTemplate.py b/YKWIM/validateTemplate.py index b91597515e85213463bbcae2d34f43d474efb735..4fd66e2b68f2e94b20f5f20d3e879afc3f9b26c9 100644 --- a/YKWIM/validateTemplate.py +++ b/YKWIM/validateTemplate.py @@ -1,4 +1,5 @@ import pyexcel as p +from YKWIM import helpers as h def validateTemplate(file): @@ -19,6 +20,9 @@ def validateTemplate(file): #attributes sheet validation #------------------------ + attribute_total_number = 0 + set_of_attributes = set() + #0. Le nom de la classe du 1er attribut est obligatoire if (book["Attributs"][1][0]=='') : return "Le nom de la classe du 1er attribut est obligatoire" @@ -37,9 +41,14 @@ def validateTemplate(file): classe=attr[0] list=[] list.append(attr[5]) + attribute_total_number+=1 + set_of_attributes.add(h.convertToCamelcase(attr[1])) # vérifier l'identifiant de la dernière classe if ("oui" not in list) : return f"la classe {classe} n'a pas d'identifiant" + + #3. Pas d'attributs avec le même nom dans le modèle UML + if len(set_of_attributes)< attribute_total_number : return f"Des attributs avec le même nom existent dans votre modèle UML" #------------------------ #associations sheet validation @@ -52,10 +61,13 @@ def validateTemplate(file): #------------------------ #enumerations sheet validation #------------------------ - + enum_exit=False #0. Chaque énumération doit avoir un lien de référence ou une définition for enum in filter(lambda value:True if value[0]!='' else False, book["Énumérations"][1:]): - if (enum[1]=='' and enum[2]==''): return f"L'énumération {enum[0]} doit avoir un lien de référence ou une définition" + if (enum[1]=='' and enum[2]==''): return f"L'énumération {enum[0]} doit avoir un lien de référence ou une définition" + + #1. La source de chaque énumération est obligatoire + if (enum[3]==''): return f"L'énumération {enum[0]} n'a pas de source" enum_exit=True #une énumération existe dans le diagramme UML #------------------------ diff --git a/config.py b/config.py index e8045d5e218000400fe640640c73caca6785c82b..555e3800b2bffc3d7760091f02d3721561a19828 100644 --- a/config.py +++ b/config.py @@ -7,6 +7,9 @@ class Config(object): DEBUG = False TESTING = False UPLOAD_FOLDER = "" + ONTOLOGY_NAMESPACE = "https://data.grandlyon.com/onto/" + VOCABULARY_NAMESPACE ="https://data.grandlyon.com/vocab/" + INSTANCES_NAMESPACE = "https://data.grandlyon.com/id/" class ProductionConfig(Config): UPLOAD_FOLDER = MYDIR + "/tmp/"