44"""
55
66import os
7+ import string
78import uuid
9+ import warnings
810
911from io import StringIO
1012from rdflib import Graph , Literal , URIRef
1113from rdflib .graph import Seq
12- from rdflib .namespace import XSD , RDF
14+ from rdflib .namespace import XSD , RDF , RDFS
1315
1416import yaml
1517
@@ -57,14 +59,32 @@ class RDFWriter(object):
5759 """
5860 A writer to parse odML files into RDF documents.
5961
62+ Use the 'rdf_subclassing' flag to disable default usage of Section type conversion to
63+ RDF Subclasses.
64+ Provide a custom Section type to RDF Subclass Name mapping dictionary via the
65+ 'custom_subclasses' attribute to add custom or overwrite default RDF Subclass mappings.
66+
6067 Usage:
6168 RDFWriter(odml_docs).get_rdf_str('turtle')
6269 RDFWriter(odml_docs).write_file("/output_path", "rdf_format")
70+
71+ RDFWriter(odml_docs, rdf_subclassing=False).write_file("path", "rdf_format")
72+ RDFWriter(odml_docs, custom_subclasses=custom_dict).write_file("path", "rdf_format")
6373 """
6474
65- def __init__ (self , odml_documents ):
75+ def __init__ (self , odml_documents , rdf_subclassing = True , custom_subclasses = None ):
6676 """
6777 :param odml_documents: list of odML documents
78+ :param rdf_subclassing: Flag whether Section types should be converted to RDF Subclasses
79+ for enhanced SPARQL queries. Default is 'True'.
80+ :param custom_subclasses: A dict where the keys reference a Section type and the
81+ corresponding values reference an RDF Class Name. When exporting
82+ a Section of a type contained in this dict, the resulting RDF
83+ Instance will be of the corresponding Class and this Class will
84+ be added as a Subclass of RDF Class "odml:Section" to the
85+ RDF document.
86+ Key:value pairs of the "custom_subclasses" dict will overwrite
87+ existing key:value pairs of the default subclassing dict.
6888 """
6989 if not isinstance (odml_documents , list ):
7090 odml_documents = [odml_documents ]
@@ -74,7 +94,13 @@ def __init__(self, odml_documents):
7494 self .graph = Graph ()
7595 self .graph .bind ("odml" , ODML_NS )
7696
97+ self .rdf_subclassing = rdf_subclassing
98+
7799 self .section_subclasses = load_rdf_subclasses ()
100+ # If a custom Section type to RDF Subclass dict has been provided,
101+ # parse it and update the default section_subclasses dict with the content.
102+ if custom_subclasses and isinstance (custom_subclasses , dict ):
103+ self ._parse_custom_subclasses (custom_subclasses )
78104
79105 def convert_to_rdf (self ):
80106 """
@@ -221,10 +247,16 @@ def save_section(self, sec, curr_node):
221247
222248 # Add type of current node to the RDF graph
223249 curr_type = fmt .rdf_type
250+
224251 # Handle section subclass types
225- sub_sec = self ._get_section_subclass (sec )
226- if sub_sec :
227- curr_type = sub_sec
252+ if self .rdf_subclassing :
253+ sub_sec = self ._get_section_subclass (sec )
254+ if sub_sec :
255+ curr_type = sub_sec
256+ self .graph .add ((URIRef (fmt .rdf_type ), RDF .type , RDFS .Class ))
257+ self .graph .add ((URIRef (curr_type ), RDF .type , RDFS .Class ))
258+ self .graph .add ((URIRef (curr_type ), RDFS .subClassOf , URIRef (fmt .rdf_type )))
259+
228260 self .graph .add ((curr_node , RDF .type , URIRef (curr_type )))
229261
230262 for k in fmt .rdf_map_keys :
@@ -294,6 +326,33 @@ class Section.
294326
295327 return None
296328
329+ def _parse_custom_subclasses (self , custom_subclasses ):
330+ """
331+ Parses a provided dictionary of "Section type": "RDF Subclass name"
332+ key value pairs and adds the pairs to the parsers' 'section_subclasses'
333+ default dictionary. Existing key:value pairs will be overwritten
334+ with provided custom key:value pairs and a Warning will be issued.
335+ Dictionary values containing whitespaces will raise a ValueError.
336+
337+ :param custom_subclasses: dictionary of "Section type": "RDF Subclass name" key value pairs.
338+ Values must not contain whitespaces, a ValueError will be raised
339+ otherwise.
340+ """
341+
342+ # Do not allow any whitespace characters in values
343+ vals = "" .join (custom_subclasses .values ()).encode ()
344+ if vals != vals .translate (None , string .whitespace .encode ()):
345+ msg = "Custom RDF Subclass names must not contain any whitespace characters."
346+ raise ValueError (msg )
347+
348+ for k in custom_subclasses :
349+ val = custom_subclasses [k ]
350+ if k in self .section_subclasses :
351+ msg = "RDFWriter custom subclasses: Key '%s' already exists. " % k
352+ msg += "Value '%s' replaces default value '%s'." % (val , self .section_subclasses [k ])
353+ warnings .warn (msg , stacklevel = 2 )
354+ self .section_subclasses [k ] = val
355+
297356 def __str__ (self ):
298357 return self .convert_to_rdf ().serialize (format = 'turtle' ).decode ("utf-8" )
299358
0 commit comments