text: #!/usr/bin/env python
  # -*- coding: utf-8 -*-
  # pylint: disable-msg=C0103 
  
  ################################################################################
  # Copyright (c) 2006-2017 Franz Inc.  
  # All rights reserved. This program and the accompanying materials are
  # made available under the terms of the MIT License which accompanies
  # this distribution, and is available at http://opensource.org/licenses/MIT
  ################################################################################
  
  from __future__ import absolute_import
  from __future__ import division
  from __future__ import unicode_literals
  
  import iso8601
  from decimal import Decimal
  
  from future.backports import OrderedDict
  from future.utils import python_2_unicode_compatible
  
  from past.builtins import long, unicode
  
  from .value import LITERAL_CMP_KEY, URI, Value
  from ..exceptions import IllegalArgumentException
  from ..vocabulary.xmlschema import XMLSchema
  from ..util import strings
  
  import datetime
  from collections import defaultdict
  
  
  # Needed to temporarily convert times to datetimes to do arithmetic.
  # Python time objects do not support adding/subtracting time deltas,
  # but datetimes do. We need that operation to normalize times to UTC.
  RANDOM_DAY = datetime.date(1984, 8, 26)
  RANDOM_DAY_STRING = RANDOM_DAY.isoformat()
  
  def datatype_from_python(value, datatype):
      """
      If 'value' is not a string, convert it into one, and infer its
      datatype, unless 'datatype' is set (i.e., overrides it).
      """
      if isinstance(value, unicode):
          return value, datatype
  
      if isinstance(value, bytes):
          return unicode(value, "utf-8"), datatype
  
      ## careful: test for 'bool' must precede test for 'int':
      if isinstance(value, bool):
          return unicode(value).lower(), datatype or XMLSchema.BOOLEAN
  
      if isinstance(value, int):
          return unicode(value), datatype or XMLSchema.INTEGER
  
      if isinstance(value, long):
          return unicode(value), datatype or XMLSchema.LONG
  
      if isinstance(value, float):
          return unicode(value), datatype or XMLSchema.DOUBLE
  
      if isinstance(value, datetime.datetime):
          # There is an ambiguity for times that occur twice due to
          # DST switches, but that is a problem with Python's standard
          # library and there is nothing we can do about it here.
          if value.utcoffset() is not None:
              value = value.replace(tzinfo=None) - value.utcoffset()
          str_value = value.isoformat() + 'Z'
          return str_value, datatype or XMLSchema.DATETIME
  
      if isinstance(value, datetime.time):
          if value.utcoffset() is not None:
              dt = datetime.datetime.combine(RANDOM_DAY, value)
              dt -= value.utcoffset()
              # Note: this will strip TZ
              value = dt.time()
          str_value = value.isoformat() + 'Z'
          return str_value, datatype or XMLSchema.TIME
  
      if isinstance(value, datetime.date):
          return value.isoformat(), datatype or XMLSchema.DATE
  
      if isinstance(value, Decimal):
          return unicode(value), datatype or XMLSchema.DECIMAL
  
      return unicode(value), datatype
  
  
  class Literal(Value):
      """
      Implementation of the Literal class.
      """
      
      def __init__(self, label, datatype=None, language=None):
          Value.__init__(self)
          
          # Uses the properties to set the real values
          self.label, self.datatype = datatype_from_python(label, datatype)
          self.language = language
  
      def getDatatype(self):
          """The URI representing the datatype for this literal, if there is one""" 
          return self._datatype
      
      def setDatatype(self, datatype):
          """Sets the datatype of the value"""
          if isinstance(datatype, bytes):
              datatype = unicode(datatype, "utf-8")
          if isinstance(datatype, unicode):
              if datatype[0] == '<':
                  datatype = datatype[1:-1]
              datatype = URI(datatype)
          elif datatype is not None:
              if not isinstance(datatype, URI):
                  datatype = URI(datatype)
              elif datatype.uri is None:
                  datatype = None
  
          self._datatype = datatype # pylint: disable-msg=W0201
  
      datatype = property(getDatatype, setDatatype)
  
      def getLanguage(self):
          """The language for this Literal"""
          return self._language
      
      def setLanguage(self, language):
          """Set the language for this Literal"""
          self._language = language.lower() if language else None # pylint: disable-msg=W0201
  
      language = property(getLanguage, setLanguage)
  
      def getLabel(self):
          """The label/value for this Literal"""
          return self._label
      
      def setLabel(self, label):
          """Set the label for this Literal"""
          self._label = label # pylint: disable-msg=W0201
      
      def getValue(self):
          """The label/value"""
          return self.label
  
      label = property(getLabel, setLabel)
  
      def get_cmp_key(self):
          return LITERAL_CMP_KEY, self.label, self.datatype, self.language
      
      def intValue(self):
          """Convert to int"""
          return int(self._label)
      
      def longValue(self):
          """Convert to long"""
          return long(self._label)
      
      def floatValue(self):
          """Convert to float"""
          return float(self._label)
      
      def booleanValue(self):
          """Convert to bool"""
          return self._label == 'true'
      
      def decimalValue(self):
          """Convert to a decimal"""
          return Decimal(self._label)
      
      def dateValue(self):
          """Convert to date"""
          # iso8601 can parse a date, but it will still be
          # returned as a datetime.datetime object. So we have
          # to extract the date (as a datetime.date object) from it.
          return iso8601.parse_date(self._label).date()
  
      def datetimeValue(self):
          """Convert to datetime"""
          return iso8601.parse_date(self._label, default_timezone=None)
  
      def timeValue(self):
          """Convert to time"""
          # iso8601 can't parse a time
          # We trick it by prepending a random date,
          # parsing the result as a datetime and extracting
          # the time from that.
          label = RANDOM_DAY_STRING + 'T' + self._label
          # Return a naive time if there is no timezone.
          # The default is UTC.
          dt = iso8601.parse_date(label, default_timezone=None)
          # Note that time() strips the time zone
          return dt.time().replace(tzinfo=dt.tzinfo)
  
      ## Returns the {@link XMLGregorianCalendar} value of this literal. A calendar
      ## representation can be given for literals whose label conforms to the
      ## syntax of the following <a href="http://www.w3.org/TR/xmlschema-2/">XML
      ## Schema datatypes</a>: <tt>dateTime</tt>, <tt>time</tt>,
      ## <tt>date</tt>, <tt>gYearMonth</tt>, <tt>gMonthDay</tt>,
      ## <tt>gYear</tt>, <tt>gMonth</tt> or <tt>gDay</tt>.
      def calendarValue(self):
          """calendarValue not useful for Python."""
          raise NotImplementedError("calendarValue")
  
      def toNTriples(self):
          """
          Return an NTriples representation for this Literal.
          """
          sb = []
          sb.append('"')
          sb.append(strings.encode_ntriple_string(self.getLabel()))
          sb.append('"')
          if self.language:
              sb.append('@')
              sb.append(self.language)
          if self.datatype:
              sb.append("^^")
              sb.append(self.datatype.toNTriples())
          return ''.join(sb)
  
      def to_json_ld(self):
          """ Converts to an object to be used as a JSON-LD value. """
          if self.language:
              return OrderedDict([
                  ('@value', self.label),
                  ('@language', self.language)
              ])
  
          if self.datatype is None or self.datatype == XMLSchema.STRING:
              return self.label
  
          if self.datatype == XMLSchema.INTEGER:
              return int(self.label)
  
          return OrderedDict([
              ('@value', self.label),
              ('@type', self.datatype.to_json_ld_key())
          ])
  
  ###############################################################################
  ## Automatic conversion from Literal to Python object
  ###############################################################################
      def toPython(self):
          """
          Return a Python object representation of this literal.   
          Slightly silly implementation because we implement a conversion table
          and then don't use the conversion functions.     
          """
          return XSDToPython[getattr(self.datatype, "uri", None)](self)
  
  
  XSDToPython = defaultdict(lambda: Literal.getValue, [
                  (XMLSchema.INT.uri, Literal.intValue),
                  (XMLSchema.FLOAT.uri, Literal.floatValue), 
                  (XMLSchema.DOUBLE.uri, Literal.floatValue), 
                  (XMLSchema.LONG.uri, Literal.longValue),
                  (XMLSchema.INTEGER.uri, Literal.intValue),
                  (XMLSchema.BOOLEAN.uri, Literal.booleanValue),
                  (XMLSchema.DATETIME.uri, Literal.datetimeValue),
                  (XMLSchema.DATE.uri, Literal.dateValue),
                  (XMLSchema.TIME.uri, Literal.timeValue),
                  (XMLSchema.DECIMAL.uri, Literal.decimalValue)])
  
  
  ###############################################################################
  # Extension to RDF4J API
  ###############################################################################
  
  class CompoundLiteral(Literal):
      """
      A compound literal represents a range, a geospatial coordinate,
      or other useful compound structure.
      TODO: FIGURE OUT SYNTAX FOR OTHER TYPES. INSURE THAT
      THE SYNTAX FOR A RANGE DOESN'T CONFLICT/OVERLAP
      """
      RANGE_LITERAL = 'rangeLiteral'
      def __init__(self, choice, lowerBound=None, upperBound=None):
          self.choice = choice
          if choice == CompoundLiteral.RANGE_LITERAL:
              self.lowerBound = lowerBound # should be a LiteralImpl
              self.upperBound = upperBound # should be a LiteralImpl
          ## other compound types go here.
          else:
              raise IllegalArgumentException("Can't interpret the choice '%s' of a compound literal." % choice)
      
      def isRangeLiteral(self):
          return self.choice == CompoundLiteral.RANGE_LITERAL
      
      def getLowerBound(self):
          return self.lowerBound
      
      def getUpperBound(self):
          return self.upperBound
      
  class RangeLiteral(CompoundLiteral):
      """
      A range represents an interval between to scalar values.
      """
      def __init__(self, lowerBound=None, upperBound=None):
          self.lowerBound = lowerBound # should be a LiteralImpl
          self.upperBound = upperBound # should be a LiteralImpl
      
      def getLowerBound(self):
          return self.lowerBound
      
      def getUpperBound(self):
          return self.upperBound
  
  @python_2_unicode_compatible
  class GeoCoordinate(CompoundLiteral):
      """
      Define either a cartesian coordinate or a spherical coordinate.  For the
      latter, nit can be 'km', 'mile', 'radian', or 'degree'
      """
      def __init__(self, x, y, unit=None, geoType=None):
          self.xcoor = x
          self.ycoor = y
          self.unit = unit
          self.geoType = geoType
      
      def __str__(self):
          return "|COOR|(%i, %i)" % (self.xcoor, self.ycoor)
      
  
  class GeoSpatialRegion(CompoundLiteral):
      pass
  
  
  @python_2_unicode_compatible
  class GeoBox(GeoSpatialRegion):
      def __init__(self, xMin, xMax, yMin, yMax, unit=None, geoType=None):
          self.xMin = xMin
          self.xMax = xMax
          self.yMin = yMin
          self.yMax = yMax
          self.unit = unit
          self.geoType = geoType
      
      def __str__(self): return "|Box|%s,%s %s,%s" % (self.xMin, self.xMax, self.yMin, self.yMax)
  
  
  @python_2_unicode_compatible
  class GeoCircle(GeoSpatialRegion):
      def __init__(self, x, y, radius, unit=None, geoType=None):
          self.x = x
          self.y = y
          self.radius = radius
          self.unit = unit
          self.geoType=geoType
          
      def __str__(self): return "|Circle|%i,%i, radius=%i" % (self.x, self.y, self.radius)
  
  
  @python_2_unicode_compatible
  class GeoPolygon(GeoSpatialRegion):
      def __init__(self, vertices, uri=None, geoType=None):
          self.vertices = vertices
          self.geoType = geoType
          self.uri = uri
          self.resource = None
          self.miniPolygon = None
          
      def getVertices(self): return self.vertices
      
      def getResource(self): return self.resource
      
      def __str__(self): return "|Polygon|%s" % self.vertices
  