DatatypeHandling.py

  1# -*- coding: utf-8 -*-
  2#
  3"""
  4Most of the XSD datatypes are handled directly by RDFLib. However, in some cases, that is not good enough. There are two
  5major reasons for this:
  6
  7#. Some datatypes are missing from RDFLib and required by OWL 2 RL and/or RDFS.
  8#. In other cases, though the datatype is present, RDFLib is fairly lax in checking the lexical value of those datatypes. Typical case is boolean.
  9
 10Some of these deficiencies are handled by this module. All the functions convert the lexical value into a
 11python datatype (or return the original string if this is not possible) which will be used, e.g.,
 12for comparisons (equalities). If the lexical value constraints are not met, exceptions are raised.
 13
 14**Requires**: `RDFLib`_, 4.0.0 and higher.
 15
 16.. _RDFLib: https://github.com/RDFLib/rdflib
 17
 18**License**: This software is available for use under the `W3C Software License`_.
 19
 20.. _W3C Software License: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231
 21
 22**Organization**: `World Wide Web Consortium`_
 23
 24.. _World Wide Web Consortium: http://www.w3.org
 25
 26**Author**: `Ivan Herman`_
 27
 28.. _Ivan Herman: http://www.w3.org/People/Ivan/
 29"""
 30
 31__author__ = 'Ivan Herman'
 32__contact__ = 'Ivan Herman, ivan@w3.org'
 33__license__ = 'W3C® SOFTWARE NOTICE AND LICENSE, http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231'
 34
 35# noinspection PyPep8Naming
 36from owlrl.RDFS import RDFNS as ns_rdf
 37
 38from rdflib.term import XSDToPython, Literal, _toPythonMapping
 39# noinspection PyPep8Naming
 40from rdflib.namespace import XSD as ns_xsd
 41
 42import datetime, time, re
 43from decimal import Decimal
 44
 45
 46# noinspection PyMissingConstructor,PyPep8Naming
 47class _namelessTZ(datetime.tzinfo):
 48    """
 49    (Nameless) timezone object. The python datetime object requires timezones as
 50    a specific object added to the conversion, rather than the explicit hour and minute
 51    difference used by XSD. This class is used to wrap around the hour/minute values.
 52
 53    :param hours: Hour offset.
 54    :param minutes: Minute offset
 55    """
 56    def __init__(self, hours, minutes):
 57        """
 58        @param hours: hour offset
 59        @param minutes: minute offset
 60        """
 61        self.__offset = datetime.timedelta(hours=hours, minutes=minutes)
 62        self.__name = "nameless"
 63
 64    def utcoffset(self, dt):
 65        return self.__offset
 66
 67    def tzname(self, dt):
 68        return self.__name
 69
 70    def dst(self, dt):
 71        return datetime.timedelta(0)
 72
 73
 74# noinspection PyPep8Naming
 75def _returnTimeZone(incoming_v):
 76    """Almost all time/date related methods require the extraction of an optional time zone information.
 77    @param incoming_v: the time/date string
 78    @return (v,timezone) tuple; 'v' is the input string with the timezone info cut off, 'timezone' is a L{_namelessTZ}
 79    instance or None
 80    """
 81    if incoming_v[-1] == 'Z':
 82        v = incoming_v[:-1]
 83        tzone = _namelessTZ(0, 0)
 84    else:
 85        pattern = ".*(\+|-)([0-9][0-9]):([0-9][0-9])"
 86        match = re.match(pattern, incoming_v)
 87        if match is None:
 88            v = incoming_v
 89            tzone = None
 90        else:
 91            hours = int(match.groups()[1])
 92            if match.groups()[0] == '-':
 93                hours = -hours - 1
 94            minutes = int(match.groups()[2])
 95            v = incoming_v[:-6]
 96            tzone = _namelessTZ(hours, minutes)
 97    return v, tzone
 98
 99
100# Booleans ##################################################
101# noinspection PyPep8Naming
102def _strToBool(v):
103    """The built-in conversion to boolean is way too lax. The xsd specification requires that only true, false, 1 or 0 should be used...
104    @param v: the literal string defined as boolean
105    @return corresponding boolean value
106    @raise ValueError: invalid boolean values
107    """
108    if v.lower() == "true" or v.lower() == "1":
109        return True
110    elif v.lower() == "false" or v.lower() == "0":
111        return False
112    else:
113        raise ValueError("Invalid boolean literal value %s" % v)
114
115
116# Decimals ##################################################
117# noinspection PyPep8Naming
118def _strToDecimal(v):
119    """The built in datatype handling for RDFLib maps a decimal number to float, but the python version 2.4 and upwards
120    also has a Decimal number. Better make use of that to use very high numbers.
121    However, there is also a big difference between Python's decimal and XSD's decimal, because the latter does not
122    allow for an exponential normal form (why???). This must be filtered out.
123    @param v: the literal string defined as decimal
124    @return Decimal
125    @raise ValueError: invalid decimal value
126    """
127    # check whether the lexical form of 'v' is o.k.
128    if v.find('E') != -1 or v.find('e') != -1:
129        # this is an invalid lexical form, though would be accepted by Python
130        raise ValueError("Invalid decimal literal value %s" % v)
131    else:
132        return Decimal(v)
133
134
135# ANY URIS ##################################################
136#: set of characters allowed in a hexadecimal number
137_hexc = ['A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f']
138#: set of numerals
139_numb = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']
140# noinspection PyPep8Naming
141def _strToAnyURI(v):
142    """Rudimentary test for the AnyURI value. If it is a relative URI, then some tests are done to filter out
143    mistakes. I am not sure this is the full implementation of the RFC, though, may have to be checked at some point
144    later.
145    @param v: the literal string defined as a URI
146    @return the incoming value
147    @raise ValueError: invalid URI value
148    """
149    import urllib.parse
150    if len(v) == 0:
151        return v
152    if urllib.parse.urlsplit(v)[0] != "":
153        # this means that there is a proper scheme, the URI should be kosher
154        return v
155    else:
156        # this is meant to be a relative URI.
157        # If I am correct, that cannot begin with one or more "?" or ":" characters
158        # all others are o.k.
159        # if it begins with a % then it should be followed by two hexa characters,
160        # otherwise it is also a bug
161        if v[0] == '%':
162            if len(v) >= 3 and (v[1] in _hexc or v[1] in _numb) and (v[2] in _hexc or v[2] in _numb):
163                return v
164            else:
165                raise ValueError("Invalid IRI %s" % v)
166        elif v[0] == '?' or v[0] == ':':
167            raise ValueError("Invalid IRI %s" % v)
168        else:
169            return v
170
171
172# Base64Binary ##################################################
173# noinspection PyPep8Naming
174def _strToBase64Binary(v):
175    """Rudimentary test for the base64Binary value. The problem is that the built-in b64 module functions ignore the
176    fact that only a certain family of characters are allowed to appear in the lexical value, so this is checked first.
177    @param v: the literal string defined as a base64encoded string
178    @return the decoded (binary) content
179    @raise ValueError: invalid base 64 binary value
180    """
181    import base64
182    if v.replace('=', 'x').replace('+', 'y').replace('/', 'z').isalnum():
183        try:
184            return base64.standard_b64decode(v)
185        except:
186            raise ValueError("Invalid Base64Binary %s" % v)
187    else:
188        raise ValueError("Invalid Base64Binary %s" % v)
189
190
191# Numerical types ##################################################
192#: limits for unsigned bytes
193_limits_unsignedByte = [-1, 256]
194
195
196#: limits for bytes
197_limits_byte = [-129, 128]
198
199
200#: limits for unsigned int
201_limits_unsignedInt = [-1, 4294967296]
202
203
204#: limits for int
205_limits_int = [-2147483649, 2147483648]
206
207
208#: limits for unsigned short
209_limits_unsignedShort = [-1, 65536]
210
211
212#: limits for short
213_limits_short = [-32769, 32768]
214
215
216#: limits for unsigned long
217_limits_unsignedLong = [-1, 18446744073709551616]
218
219
220#: limits for long
221_limits_long = [-9223372036854775809, 9223372036854775808]
222
223
224#: limits for positive integer
225_limits_positiveInteger = [0, None]
226
227
228#: limits for non positive integer
229_limits_nonPositiveInteger = [None, 1]
230
231
232#: limits for non negative integer
233_limits_nonNegativeInteger = [-1, None]
234
235
236#: limits for negative integer
237_limits_negativeInteger = [None, 0]
238
239
240# noinspection PyPep8Naming,PyBroadException
241def _strToBoundNumeral(v, interval, conversion):
242    """Test (and convert) a generic numerical type, with a check against a lower and upper limit.
243    @param v: the literal string to be converted
244    @param interval: lower and upper bounds (non inclusive). If the value is None, no comparison should be done
245    @param conversion: conversion function, ie, int, long, etc
246    @raise ValueError: invalid value
247    """
248    try:
249        i = conversion(v)
250        if (interval[0] is None or interval[0] < i) and (interval[1] is None or i < interval[1]):
251            return i
252    except:
253        pass
254    raise ValueError("Invalid numerical value %s" % v)
255
256
257# Double and float ##################################################
258# noinspection PyPep8Naming
259def _strToDouble(v):
260    """Test and convert a double value into a Decimal or float. Raises an exception if the number is outside the
261    permitted range, ie, 1.0E+310 and 1.0E-330. To be on the safe side (python does not have double!) Decimals are used
262    if possible. Upper and lower values, as required by xsd, are checked (and these fixed values are the reasons
263    why Decimal is used!)
264
265    @param v: the literal string defined as a double
266    @return Decimal
267    @raise ValueError: invalid value
268    """
269    try:
270        value = Decimal(v)
271        upper = Decimal("1.0E+310")
272        lower = Decimal("1.0E-330")
273        if lower < abs(value) < upper:
274            # bingo
275            return value
276        else:
277            raise ValueError("Invalid double %s" % v)
278    except:
279        # there was a problem in creating a decimal...
280        raise ValueError("Invalid double %s" % v)
281
282
283# noinspection PyPep8Naming
284def _strToFloat(v):
285    """Test and convert a float value into Decimal or (python) float. Raises an exception if the number is outside the
286    permitted range, ie, 1.0E+40 and 1.0E-50. (And these fixed values are the reasons why Decimal is used!)
287
288    @param v: the literal string defined as a float
289    @return Decimal if the local python version is >= 2.4, float otherwise
290    @raise ValueError: invalid value
291    """
292    try:
293        value = Decimal(v)
294        upper = Decimal("1.0E+40")
295        lower = Decimal("1.0E-50")
296        if lower < abs(value) < upper:
297            # bingo
298            return value
299        else:
300            raise ValueError("Invalid float %s" % v)
301    except:
302        # there was a problem in creating a decimal...
303        raise ValueError("Invalid float %s" % v)
304
305
306# hexa ##################################################
307# noinspection PyPep8Naming
308def _strToHexBinary(v):
309    """Test (and convert) hexa integer values. The number of characters should be even.
310    @param v: the literal string defined as a hexa number
311    @return long value
312    @raise ValueError: invalid value
313    """
314    # first of all, the number of characters must be even according to the xsd spec:
315    length = len(v)
316    if (length / 2) * 2 != length:
317        raise ValueError("Invalid hex binary number %s" % v)
318    return int(v, 16)
319
320
321# Datetime, date timestamp, etc ################################
322# noinspection PyPep8Naming
323def _strToDateTimeAndStamp(incoming_v, timezone_required=False):
324    """Test (and convert) datetime and date timestamp values.
325    @param incoming_v: the literal string defined as the date and time
326    @param timezone_required: whether the timezone is required (ie, for date timestamp) or not
327    @return datetime
328    @rtype: datetime.datetime
329    @raise ValueError: invalid datetime or date timestamp
330    """
331
332    # First, handle the timezone portion, if there is any
333    (v, tzone) = _returnTimeZone(incoming_v)
334
335    # Check on the timezone. For time date stamp object it is required
336    if timezone_required and tzone is None:
337        raise ValueError("Invalid datetime %s" % incoming_v)
338
339    # The microseconds should be handled here...
340    final_v = v
341    milliseconds = 0
342    milpattern = "(.*)(\.)([0-9]*)"
343    match = re.match(milpattern, v)
344    if match is not None:
345        # we have a millisecond portion...
346        try:
347            final_v = match.groups()[0]
348            milliseconds = int(match.groups()[2])
349        except:
350            raise ValueError("Invalid datetime %s" % incoming_v)
351    #
352    # By now, the pattern should be clear
353    # This may raise an exception...
354    try:
355        tstr = time.strptime(final_v, "%Y-%m-%dT%H:%M:%S")
356        if tzone is not None:
357            return datetime.datetime(tstr.tm_year, tstr.tm_mon, tstr.tm_mday, tstr.tm_hour, tstr.tm_min, tstr.tm_sec,
358                                     milliseconds, tzone)
359        else:
360            return datetime.datetime(tstr.tm_year, tstr.tm_mon, tstr.tm_mday, tstr.tm_hour, tstr.tm_min, tstr.tm_sec,
361                                     milliseconds)
362    except:
363        raise ValueError("Invalid datetime %s" % incoming_v)
364
365
366# noinspection PyPep8Naming
367def _strToTime(incoming_v):
368    """Test (and convert) time values.
369    @param incoming_v: the literal string defined as time value
370    @return time
371    @rtype datetime.time
372    @raise ValueError: invalid datetime or date timestamp
373    """
374
375    # First, handle the timezone portion, if there is any
376    (v, tzone) = _returnTimeZone(incoming_v)
377
378    # The microseconds should be handled here...
379    final_v = v
380    milliseconds = 0
381    milpattern = "(.*)(\.)([0-9]*)"
382    match = re.match(milpattern, v)
383    if match is not None:
384        # we have a millisecond portion...
385        try:
386            final_v = match.groups()[0]
387            milliseconds = int(match.groups()[2])
388        except:
389            raise ValueError("Invalid datetime %s" % incoming_v)
390    #
391    # By now, the pattern should be clear
392    # This may raise an exception...
393    try:
394        tstr = time.strptime(final_v, "%H:%M:%S")
395        if tzone is not None:
396            return datetime.time(tstr.tm_hour, tstr.tm_min, tstr.tm_sec, milliseconds, tzone)
397        else:
398            return datetime.time(tstr.tm_hour, tstr.tm_min, tstr.tm_sec, milliseconds)
399    except:
400        raise ValueError("Invalid time %s" % incoming_v)
401
402
403# noinspection PyPep8Naming
404def _strToDate(incoming_v):
405    """Test (and convert) date values.
406    @param incoming_v: the literal string defined as date (in iso format)
407    @return date
408    @return datetime.date
409    @raise ValueError: invalid datetime or date timestamp
410    """
411
412    # First, handle the timezone portion, if there is any
413    (final_v, tzone) = _returnTimeZone(incoming_v)
414
415    # This may raise an exception...
416    try:
417        tstr = time.strptime(final_v,"%Y-%m-%d")
418        return datetime.date(tstr.tm_year, tstr.tm_mon, tstr.tm_mday)
419    except:
420        raise ValueError("Invalid date %s" % incoming_v)
421
422
423# The 'g' series for dates ############################
424# The 'g' datatypes (eg, gYear) cannot be directly represented as a python datatype
425# the series of methods below simply check whether the incoming string is o.k., but the
426# returned value is the same as the original
427# noinspection PyPep8Naming
428def _strTogYearMonth(v):
429    """Test gYearMonth value
430    @param v: the literal string
431    @return v
432    @raise ValueError: invalid value
433    """
434    try:
435        time.strptime(v+"-01", "%Y-%m-%d")
436        return v
437    except:
438        raise ValueError("Invalid gYearMonth %s" % v)
439
440
441# noinspection PyPep8Naming
442def _strTogYear(v):
443    """Test gYear value
444    @param v: the literal string
445    @return v
446    @raise ValueError: invalid value
447    """
448    try:
449        time.strptime(v+"-01-01", "%Y-%m-%d")
450        return v
451    except:
452        raise ValueError("Invalid gYear %s" % v)
453
454
455# noinspection PyPep8Naming
456def _strTogMonthDay(v):
457    """Test gYearMonth value
458    @param v: the literal string
459    @return v
460    @raise ValueError: invalid value
461    """
462    try:
463        time.strptime("2008-" + v, "%Y-%m-%d")
464        return v
465    except:
466        raise ValueError("Invalid gMonthDay %s" % v)
467
468
469# noinspection PyPep8Naming
470def _strTogDay(v):
471    """Test gYearMonth value
472    @param v: the literal string
473    @return v
474    @raise ValueError: invalid value
475    """
476    try:
477        time.strptime("2001-01-" + v, "%Y-%m-%d")
478        return v
479    except:
480        raise ValueError("Invalid gDay %s" % v)
481
482
483# noinspection PyPep8Naming
484def _strTogMonth(v):
485    """Test gYearMonth value
486    @param v: the literal string
487    @return v
488    @raise ValueError: invalid value
489    """
490    try:
491        time.strptime("2001-" + v + "-01", "%Y-%m-%d")
492        return v
493    except:
494        raise ValueError("Invalid gMonth %s" % v)
495
496
497# XML Literal #########################################
498# noinspection PyPep8Naming
499def _strToXMLLiteral(v):
500    """Test (and convert) XML Literal values.
501    @param v: the literal string defined as an xml literal
502    @return the canonical version of the same xml text
503    @raise ValueError: incorrect xml string
504    """
505    import xml.dom.minidom
506    try:
507        dom = xml.dom.minidom.parseString(v)
508        return dom.toxml()
509    except:
510        raise ValueError("Invalid XML Literal %s" % v)
511
512
513# language, NMTOKEN, NAME, etc #########################
514#: regular expression for a 'language' datatype
515_re_language = "[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*"
516
517
518#: regexp for NMTOKEN. It must be used with a re.U flag (the '(?U' regexp form did not work. It may depend on the
519# locale...)
520_re_NMTOKEN = "[\w:_.\-]+"
521
522
523#: characters not permitted at a starting position for Name (otherwise Name is like NMTOKEN
524_re_Name_ex = ['.', '-'] + _numb
525
526
527#: regexp for NCName. It must be used with a re.U flag (the '(?U' regexp form did not work. It may depend on the
528# locale...)
529_re_NCName = "[\w_.\-]+"
530
531
532#: characters not permitted at a starting position for NCName
533_re_NCName_ex = ['.', '-'] + _numb
534
535
536# noinspection PyDefaultArgument,PyPep8Naming,PyPep8Naming
537def _strToVal_Regexp(v, regexp, flag=0, excludeStart=[]):
538    """Test (and convert) a generic string type, with a check against a regular expression.
539    @param v: the literal string to be converted
540    @param regexp: the regular expression to check against
541    @param flag: flags to be used in the regular expression
542    @param excludeStart: array of characters disallowed in the first position
543    @return original string
544    @raise ValueError: invalid value
545    """
546    match = re.match(regexp, v, flag)
547    if match is None or match.end() != len(v):
548        raise ValueError("Invalid literal %s" % v)
549    else:
550        if len(excludeStart) > 0 and v[0] in excludeStart:
551            raise ValueError("Invalid literal %s" % v)
552        return v
553
554
555#: Disallowed characters in a token or a normalized string, as a regexp
556_re_token = "[^\n\t\r]+"
557
558
559# noinspection PyPep8Naming
560def _strToToken(v):
561    """Test (and convert) a string to a token.
562    @param v: the literal string to be converted
563    @return original string
564    @raise ValueError: invalid value
565    """
566    if len(v) == 0:
567        return v
568    # filter out the case when there are new lines and similar (if there is a problem, an exception is raised)
569    _strToVal_Regexp(v, _re_token)
570    v1 = ' '.join(v.strip().split())
571    # normalize the string, and see if the result is the same:
572    if len(v1) == len(v):
573        # no characters lost, ie, no unnecessary spaces
574        return v
575    else:
576        raise ValueError("Invalid literal %s" % v)
577
578
579# plain literal ########################################
580# noinspection PyPep8Naming
581def _strToPlainLiteral(v):
582    """Test (and convert) a plain literal
583    @param v: the literal to be converted
584    @return a new RDFLib Literal with language tag
585    @raise ValueError: invalid value
586    """
587    reg = "(.*)@([^@]*)"
588    # a plain literal must match this regexp!
589    match = re.match(reg,v)
590    if match is None:
591        raise ValueError("Invalid plain literal %s" % v)
592    else:
593        lit  = match.groups()[0]
594        if len(match.groups()) == 1 or match.groups()[1] == "":
595            # no language tag
596            return Literal(lit)
597        else:
598            lang = match.groups()[1]
599            # check if this is a correct language tag. Note that can raise an exception!
600            try:
601                lang = _strToVal_Regexp(lang, _re_language)
602                return Literal(lit,lang=lang.lower())
603            except:
604                raise ValueError("Invalid plain literal %s" % v)
605
606
607#####################################################################################
608#: Replacement of RDFLib's conversion function. Each entry assigns a function to an XSD datatype, attempting to convert
609#: a string to a Python datatype (or raise an exception if some problem is found)
610AltXSDToPYTHON = {
611    ns_xsd["language"]: lambda v: _strToVal_Regexp(v, _re_language),
612    ns_xsd["NMTOKEN"]: lambda v: _strToVal_Regexp(v, _re_NMTOKEN, re.U),
613    ns_xsd["Name"]: lambda v: _strToVal_Regexp(v, _re_NMTOKEN, re.U, _re_Name_ex),
614    ns_xsd["NCName"]: lambda v: _strToVal_Regexp(v, _re_NCName, re.U, _re_NCName_ex),
615    ns_xsd["token"]: _strToToken,
616    ns_rdf["PlainLiteral"]: _strToPlainLiteral,
617    ns_xsd["boolean"]: _strToBool,
618    ns_xsd["decimal"]: _strToDecimal,
619    ns_xsd["anyURI"]: _strToAnyURI,
620    ns_xsd["base64Binary"]: _strToBase64Binary,
621    ns_xsd["double"]: _strToDouble,
622    ns_xsd["float"]: _strToFloat,
623    ns_xsd["byte"]: lambda v: _strToBoundNumeral(v, _limits_byte, int),
624    ns_xsd["int"]: lambda v: _strToBoundNumeral(v, _limits_int, int),
625    ns_xsd["long"]: lambda v: _strToBoundNumeral(v, _limits_long, int),
626    ns_xsd["positiveInteger"]: lambda v: _strToBoundNumeral(v, _limits_positiveInteger, int),
627    ns_xsd["nonPositiveInteger"]: lambda v: _strToBoundNumeral(v, _limits_nonPositiveInteger, int),
628    ns_xsd["negativeInteger"]: lambda v: _strToBoundNumeral(v, _limits_negativeInteger, int),
629    ns_xsd["nonNegativeInteger"]: lambda v: _strToBoundNumeral(v, _limits_nonNegativeInteger, int),
630    ns_xsd["short"]: lambda v: _strToBoundNumeral(v, _limits_short, int),
631    ns_xsd["unsignedByte"]: lambda v: _strToBoundNumeral(v, _limits_unsignedByte, int),
632    ns_xsd["unsignedShort"]: lambda v: _strToBoundNumeral(v, _limits_unsignedShort, int),
633    ns_xsd["unsignedInt"]: lambda v: _strToBoundNumeral(v, _limits_unsignedInt, int),
634    ns_xsd["unsignedLong"]: lambda v: _strToBoundNumeral(v, _limits_unsignedLong, int),
635    ns_xsd["hexBinary"]: _strToHexBinary,
636    ns_xsd["dateTime"]: lambda v: _strToDateTimeAndStamp(v, False),
637    ns_xsd["dateTimeStamp"]: lambda v: _strToDateTimeAndStamp(v, True),
638    ns_rdf["XMLLiteral"]: _strToXMLLiteral,
639    ns_xsd["integer"]: int,
640    ns_xsd["string"]: lambda v: v,
641    ns_rdf["HTML"]: lambda v: v,
642    ns_xsd["normalizedString"]: lambda v: _strToVal_Regexp(v, _re_token),
643
644    # These are RDFS specific...
645    ns_xsd["time"]: _strToTime,
646    ns_xsd["date"]: _strToDate,
647    ns_xsd["gYearMonth"]: _strTogYearMonth,
648    ns_xsd["gYear"]: _strTogYear,
649    ns_xsd["gMonthDay"]: _strTogMonthDay,
650    ns_xsd["gDay"]: _strTogDay,
651    ns_xsd["gMonth"]: _strTogMonth,
652}
653
654
655def use_Alt_lexical_conversions():
656    """
657    Registering the datatypes item for RDFLib, ie, bind the dictionary values. The 'bind' method of RDFLib adds
658    extra datatypes to the registered ones in RDFLib, though the table used here (I.e., :py:data:`.AltXSDToPYTHON`) actually
659    overrides all of the default conversion routines. The method also add a Decimal entry to the :code:`PythonToXSD` list of
660    RDFLib.
661    """
662    _toPythonMapping.update(AltXSDToPYTHON)
663
664
665def use_RDFLib_lexical_conversions():
666    """
667    Restore the original (ie, RDFLib) set of lexical conversion routines.
668    """
669    _toPythonMapping.update(XSDToPython)
670
671#######################################################################################
672# This module can pretty much tested individually...
673
674
675if __name__ == '__main__':
676    import sys
677    dtype = sys.argv[1]
678    string = sys.argv[2]
679    datatype = ns_xsd[dtype]
680    result = AltXSDToPYTHON[datatype](string)
681    print(type(result))
682    print(result)