Skip to content

Commit 42d97ad

Browse files
committed
Format selection in web-app
Refactor
1 parent 146df45 commit 42d97ad

File tree

6 files changed

+203
-163
lines changed

6 files changed

+203
-163
lines changed

LidoRDFConverter.py

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,19 @@
44
import os
55
import shutil
66
import rdflib as RF
7+
from rdflib.namespace import NamespaceManager
78
import urllib.request as ulr
89
import urllib.parse as ulp
910
from lxml import etree
1011

12+
def p_log(f):
13+
'''Decorator for logging function output'''
14+
def wrapped(*args, **kwargs):
15+
t = f(*args, **kwargs)
16+
print(t)
17+
return t
18+
return wrapped
19+
1120

1221
# prefix namespace mapping
1322
NAMESPACE_MAP = {
@@ -22,15 +31,6 @@
2231
RESUMPTION_TAG = f'{{{OAI_SCHEMA_URL}}}resumptionToken'
2332

2433

25-
def make_short_uri(uri: str, **kw) -> RF.term.URIRef:
26-
'''Returns URI' like e.g. crm:Enn_cccc'''
27-
# tag = kw.get('tag',None)
28-
for k, v in NAMESPACE_MAP.items():
29-
if uri.startswith(f"{k}:"):
30-
return v[uri.split(':')[-1]]
31-
return RF.term.URIRef(uri)
32-
33-
3434
def isURI(uri: str) -> bool:
3535
'''Checks if a string is a valid URI'''
3636
return ulp.urlparse(uri).netloc != ''
@@ -100,14 +100,31 @@ def strip_schema(url: str) -> str:
100100
"""Strips the schema from a URL"""
101101
return re.sub(r"^https?:", '', url).strip().strip('/')
102102

103+
def make_curie_uri(uri: str, **kw) -> RF.term.URIRef:
104+
'''Creates a URIRef from a CURIE or URI string'''
105+
try:
106+
nsm = kw.get('graph').namespace_manager
107+
return nsm.expand_curie(uri)
108+
except:
109+
return RF.term.URIRef(uri)
110+
111+
def make_id_node(id_str: str, **kw) -> RF.URIRef:
112+
'''Creates an RDF node (BNode or URIRef) from id string and a mode'''
113+
'''Gets the ID mode and graph from kw arguments'''
114+
mode = kw.get('mode', x3ml.IDMode.LIDO_ID)
115+
if mode == x3ml.IDMode.PATH:
116+
return RF.BNode(hash(id_str))
117+
uri = f'n4o:{hash(id_str)}'
118+
try:
119+
nsm = kw.get('graph').namespace_manager
120+
return nsm.expand_curie(uri)
121+
except:
122+
return RF.term.URIRef(uri)
103123

104-
def make_n4o_id(text: str, **kw) -> RF.URIRef:
105-
"""Creates a N4O ID from text"""
106-
return NAMESPACE_MAP['n4o'][f"{hash(text)}"]
107124

108125

109-
def make_object(info) -> RF.URIRef | RF.Literal:
110-
"""Creates an RDF object (URIRef or Literal) from info and text"""
126+
def make_plain_node(info) -> RF.URIRef | RF.Literal:
127+
"""Creates an RDF node (URIRef or Literal) from info"""
111128
if isURI(info.text):
112129
return RF.URIRef(proper_uri(info.text))
113130
return RF.Literal(info.text, lang=info.lang)
@@ -119,10 +136,11 @@ def make_object(info) -> RF.URIRef | RF.Literal:
119136
def add_triples(graph, mapping: x3ml.Mapping, recID: str, **kw) -> None:
120137
'''Add triples to the graph'''
121138
info = mapping.info
122-
if id_S := info.id.strip():
123-
id_S = recID + '-' + id_S
124-
S = make_n4o_id(id_S, tag='S')
125-
triples = [(S, RF.RDF.type, make_short_uri(mapping.S.entity, tag='S'))]
139+
if id_S := info.get_id(recID):
140+
kw.update({'graph':graph,'mode':info.mode,'tag':'S'})
141+
S = make_id_node(id_S, **kw)
142+
triples = [(S, RF.RDF.type, make_curie_uri(mapping.S.entity, **kw))]
143+
126144
for po in mapping.POs:
127145
triples += get_po_triples(S, recID, po, **kw)
128146
if len(triples) > 1: # at least one PO triple
@@ -134,19 +152,21 @@ def get_po_triples(S, recID, po: x3ml.PO, **kw) -> list:
134152
'''Compile triples from PO data'''
135153
triples = []
136154
if po.valid:
137-
P = make_short_uri(po.P.entity, tag='P')
155+
kw.update({'tag':'P'})
156+
P = make_curie_uri(po.P.entity, **kw)
157+
kw.update({'tag':'O'})
138158
for info in po.infos:
139159
if info.hasID():
160+
kw.update({'mode':info.mode})
140161
id_O = info.get_id(recID)
141-
O = make_n4o_id(id_O, tag='O')
162+
O = make_id_node(id_O, **kw)
142163
if (O != S):
143-
Pt = RF.RDF.type
144-
Ot = make_short_uri(po.O.entity, tag='O')
145-
triples.append((S, Pt, Ot))
146164
triples.append((S, P, O))
165+
Ot = make_curie_uri(po.O.entity, **kw)
166+
triples.append((O, RF.RDF.type, Ot))
147167
else:
148168
if info.text:
149-
O = make_object(info)
169+
O = make_plain_node(info)
150170
triples.append((S, P, O))
151171
return triples
152172

app.py

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
import xml.etree.ElementTree as ET
22
import LidoRDFConverter as LRC
33
from pathlib import Path
4-
from x3ml_classes import X3ml
5-
from flask import Flask, render_template, request, jsonify, make_response
4+
from x3ml_classes import X3ml
5+
from flask import Flask, render_template, request, jsonify, make_response
66
import logging
77
import json
8+
from dataclasses import dataclass
89

910
from waitress import serve
1011
from argparse import ArgumentParser, BooleanOptionalAction
1112

12-
app = Flask(__name__,template_folder='templates', static_folder='static', static_url_path='/assets')
13+
app = Flask(__name__, template_folder='templates', static_folder='static', static_url_path='/assets')
14+
1315

1416
def dlftMappingFile():
1517
return Path('./defaultMapping.x3ml')
1618

19+
1720
def dlftLidoFile():
1821
return Path('./defaultLido.xml')
1922

20-
def convert_lido_str(lido_str, x3ml_str,format='turtle'):
23+
24+
def convert_lido_str(lido_str, x3ml_str, format='turtle'):
2125
'''Converts LIDO XML string to RDF using the provided X3ML mapping string.
2226
Returns the RDF string in the specified format.'''
2327
if lido_str:
@@ -26,36 +30,31 @@ def convert_lido_str(lido_str, x3ml_str,format='turtle'):
2630
return graph.serialize(format=format)
2731
return ''
2832

29-
def get_version_data():
30-
version = Path('./version.txt')
31-
if version.exists():
32-
ver_str = version.read_text()
33-
last_line = ver_str.split('\n')[-1]
34-
tokens = last_line.split(':', 3)
35-
if len(tokens) == 3:
36-
return {'date':tokens[0],'commit':tokens[1],'version':tokens[2]}
37-
return {'date':'','commit':'','version':'0'}
38-
return 'unkown'
39-
40-
VERSION_DATA = get_version_data()
41-
4233
#############################################################################
4334

35+
4436
@app.route('/')
4537
def index():
46-
return render_template('index.html', version=VERSION_DATA.get('version','unkown'))
38+
return render_template('index.html', version=app.config['version'].value)
39+
40+
41+
@app.route('/version')
42+
def version():
43+
return jsonify(app.config['version'])
44+
4745

4846
@app.route('/barriere')
4947
def barriere():
5048
return render_template('barriere.html')
5149

50+
5251
@app.route('/json_to_x3ml', methods=['POST'])
5352
def json_to_x3ml():
5453
parm = request.get_json()
5554
if js := parm.get('x3ml'):
5655
model = X3ml.fromJSON(js)
5756
x3ml_str = model.to_str()
58-
return jsonify({'status': 'success', 'x3ml': x3ml_str})
57+
return jsonify({'status': 'success', 'x3ml': x3ml_str})
5958
return jsonify({'status': 'failed', 'message': 'No mapping data provided!'})
6059

6160

@@ -92,14 +91,16 @@ def upload_mapping():
9291
def run_mappings():
9392
response_object = {'status': 'success'}
9493
parm = request.get_json()
95-
lido_data = parm['data']
94+
lido_data = parm.get('data','')
9695
if js := parm.get('x3ml'):
9796
model = X3ml.fromJSON(js)
98-
response_object = {'status': 'success', 'message': 'Mappings applied to Lido!'}
99-
response_object['text'] = convert_lido_str(lido_data, model.to_str())
97+
format = parm.get('format','turtle')
98+
response_object = {'status': 'success', 'message': 'Mappings applied to Lido!', 'format':format}
99+
response_object['text'] = convert_lido_str(lido_data, model.to_str(),format=format)
100100
return jsonify(response_object)
101101
return jsonify({'status': 'failed', 'message': 'No Lido data provided!'})
102102

103+
103104
@app.route('/convert', methods=['POST'])
104105
def convert():
105106
# TODO: catch error and provide better error response e.g. code 400 for malformed LIDO
@@ -113,19 +114,52 @@ def convert():
113114
else:
114115
mapping_data = dlftMappingFile().read_text()
115116
lido_data = request.files['file'].read().decode('utf-8')
116-
format = request.form.get('format','turtle')
117+
format = request.form.get('format', 'turtle')
117118
else:
118119
mapping_data = dlftMappingFile().read_text()
119120
lido_data = request.get_data()
120-
format ='turtle'
121+
format = 'turtle'
121122
try:
122-
rdf_str = convert_lido_str(lido_data, mapping_data,format=format)
123+
rdf_str = convert_lido_str(lido_data, mapping_data, format=format)
123124
response = make_response(rdf_str, 200)
124125
response.mime_type = f"text/{format}"
125126
except Exception as e:
126127
return jsonify({'error': str(e)}), 400
127128
return response
128129

130+
131+
@dataclass
132+
class Version():
133+
date: str = ''
134+
commit: str = ''
135+
value: str = '???'
136+
137+
def update(self, tokens:list):
138+
''' Updates attributes from tokens '''
139+
data = {'date': tokens[0], 'commit': tokens[1], 'value': tokens[2]}
140+
self.__dict__.update(data)
141+
return self
142+
143+
@classmethod
144+
def from_tokens(cls, tokens):
145+
''' Creates an object from tokens '''
146+
if len(tokens) == 3:
147+
return cls().update(tokens)
148+
return cls()
149+
150+
151+
def get_version_data():
152+
''' Reads version data from file into the app configuration'''
153+
tokens = []
154+
ver_file = Path('./version.txt')
155+
if ver_file.exists():
156+
# Get last line and split it into 3 tokens
157+
text = ver_file.read_text()
158+
last_line = text.splitlines()[-1]
159+
tokens = last_line.split(':', 3)
160+
app.config['version'] = Version.from_tokens(tokens)
161+
162+
129163
if __name__ == '__main__':
130164
parser = ArgumentParser()
131165
parser.add_argument('-w', '--wsgi', action=BooleanOptionalAction, help="Use WSGI server")
@@ -135,8 +169,9 @@ def convert():
135169

136170
if args.log:
137171
logging.basicConfig(filename='app.log', level=logging.INFO)
138-
139-
172+
173+
get_version_data()
174+
140175
if args.wsgi:
141176
print(f"Starting WSGI server at http://localhost:{args.port}/")
142177
serve(app, host="0.0.0.0", port=args.port)

static/lidostyle.css

Lines changed: 8 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -23,73 +23,13 @@
2323
font-family: monospace;
2424
}
2525

26-
/* CSS */
27-
.button-blue {
28-
background-color: #0095ff;
29-
border: 1px solid transparent;
30-
border-radius: 3px;
31-
box-shadow: rgba(255, 255, 255, 0.4) 0 1px 0 0 inset;
32-
box-sizing: border-box;
33-
color: #fff;
34-
cursor: pointer;
35-
display: inline-block;
36-
font-family: -apple-system, system-ui, "Segoe UI", "Liberation Sans",
37-
sans-serif;
38-
font-size: 10px;
39-
font-weight: 400;
40-
line-height: 1;
41-
margin: 2px;
42-
outline: none;
43-
padding: 8px 0.8em;
44-
position: relative;
45-
text-align: center;
46-
text-decoration: none;
47-
user-select: none;
48-
-webkit-user-select: none;
49-
touch-action: manipulation;
50-
vertical-align: baseline;
51-
white-space: nowrap;
52-
}
53-
54-
.button-blue:hover,
55-
.button-blue:focus {
56-
background-color: #07c;
57-
}
58-
59-
.button-blue:focus {
60-
box-shadow: 0 0 0 4px rgba(0, 149, 255, 0.15);
61-
}
62-
63-
.button-blue:active {
64-
background-color: #0064bd;
65-
box-shadow: none;
66-
}
67-
68-
.visible-scrollbar,
69-
.invisible-scrollbar,
70-
.mostly-customized-scrollbar {
71-
display: block;
72-
width: 10em;
73-
overflow: auto;
74-
height: 2em;
75-
padding: 1em;
76-
margin: 1em auto;
77-
outline: 2px dashed cornflowerblue;
78-
}
79-
80-
.invisible-scrollbar::-webkit-scrollbar {
81-
display: none;
82-
}
83-
84-
/* Demonstrate a "mostly customized" scrollbar
85-
* (won't be visible otherwise if width/height is specified) */
86-
.mostly-customized-scrollbar::-webkit-scrollbar {
87-
width: 5px;
88-
height: 8px;
89-
background-color: #aaaaaa; /* or add it to the track */
26+
.styled select {
27+
width: 300px;
28+
height: 34px;
29+
font-size: 12px;
9030
}
9131

92-
/* Add a thumb */
93-
.mostly-customized-scrollbar::-webkit-scrollbar-thumb {
94-
background: black;
95-
}
32+
.styled {
33+
width: 500px;
34+
margin-bottom: 5px;
35+
}

0 commit comments

Comments
 (0)