Example #1
0
class PropertyBase(object):

    # Mappings between various tokens and internal definitions

    # Map a property name to its default value type
    sDefaultValueTypeMap = {}

    # Properties whose value parameter is always written out
    # even if equal to the default
    sAlwaysValueTypes = set()

    # Map a value name to a value type constant
    sValueTypeMap = {}

    # Map a value type constant to a value name
    sTypeValueMap = {}

    # Properties that are multi-valued
    sMultiValues = set()

    # Properties whose value needs special handling
    sSpecialVariants = {}

    sUsesGroup = False

    sVariant = "none"   # Used to differentiate different forms of text parsing

    sValue = None
    sText = None

    @classmethod
    def registerDefaultValue(cls, propname, valuetype, always_write_value=False):
        """
        Allow custom properties to be used by defining their name/value mappings.

        @param propname: name of the new property
        @type propname: L{str}
        @param valuetype: the L{Value} type used as the default value
        @type valuetype: L{int}
        @param always_write_value: if L{True} always write a VALUE= parameter even
            when the default value is used
        @type always_write_value: L{bool}
        """
        if propname not in cls.sDefaultValueTypeMap:
            cls.sDefaultValueTypeMap[propname] = valuetype
        if always_write_value:
            cls.sAlwaysValueTypes.add(propname)

    def __init__(self, name=None, value=None, valuetype=None):

        self.mName = name if name is not None else ""
        self.mParameters = {}
        self.mValue = None

    def duplicate(self):
        raise NotImplementedError

    def __hash__(self):
        raise NotImplementedError

    def __ne__(self, other):
        return not self.__eq__(other)

    def __eq__(self, other):
        raise NotImplementedError

    def __repr__(self):
        return "Property: %s" % (self.getText(),)

    def __str__(self):
        return self.getText()

    def getGroup(self):
        return self.mGroup if self.sUsesGroup else None

    def setGroup(self, group):
        if self.sUsesGroup:
            self.mGroup = group

    def getName(self):
        return self.mName

    def setName(self, name):
        self.mName = name

    def getParameters(self):
        return self.mParameters

    def setParameters(self, parameters):
        self.mParameters = dict([(k.upper(), v) for k, v in parameters.iteritems()])

    def hasParameter(self, attr):
        return attr.upper() in self.mParameters

    def getParameterValue(self, attr):
        return self.mParameters[attr.upper()][0].getFirstValue()

    def getParameterValues(self, attr):
        return self.mParameters[attr.upper()][0].getValues()

    def addParameter(self, attr):
        self.mParameters.setdefault(attr.getName().upper(), []).append(attr)

    def replaceParameter(self, attr):
        self.mParameters[attr.getName().upper()] = [attr]

    def removeParameters(self, attr):
        if attr.upper() in self.mParameters:
            del self.mParameters[attr.upper()]

    def getValue(self):
        return self.mValue

    def getBinaryValue(self):

        if isinstance(self.mValue, BinaryValue):
            return self.mValue
        else:
            return None

    def getCalAddressValue(self):

        if isinstance(self.mValue, CalAddressValue):
            return self.mValue
        else:
            return None

    def getDateTimeValue(self):

        if isinstance(self.mValue, DateTimeValue):
            return self.mValue
        else:
            return None

    def getDurationValue(self):

        if isinstance(self.mValue, DurationValue):
            return self.mValue
        else:
            return None

    def getIntegerValue(self):

        if isinstance(self.mValue, IntegerValue):
            return self.mValue
        else:
            return None

    def getMultiValue(self):

        if isinstance(self.mValue, MultiValue):
            return self.mValue
        else:
            return None

    def getPeriodValue(self):

        if isinstance(self.mValue, PeriodValue):
            return self.mValue
        else:
            return None

    def getTextValue(self):

        if isinstance(self.mValue, PlainTextValue):
            return self.mValue
        else:
            return None

    def getURIValue(self):

        if isinstance(self.mValue, URIValue):
            return self.mValue
        else:
            return None

    def getUTCOffsetValue(self):

        if isinstance(self.mValue, UTCOffsetValue):
            return self.mValue
        else:
            return None

    @classmethod
    def parseText(cls, data):
        """
        Parse the text format data and return a L{Property}

        @param data: text data
        @type data: C{str}
        """

        try:
            prop = cls()

            # Look for parameter or value delimiter
            prop_name, txt = stringutils.strduptokenstr(data, ";:")
            if not prop_name:
                raise InvalidProperty("Invalid property: empty name", data)

            # Get the name
            if prop.sUsesGroup:
                # Check for group prefix
                splits = prop_name.split(".", 1)
                if len(splits) == 2:
                    # We have both group and name
                    prop.mGroup = splits[0]
                    prop.mName = splits[1]
                else:
                    # We have the name
                    prop.mName = prop_name
            else:
                prop.mName = prop_name

            # Get the parameters
            txt = prop.parseTextParameters(txt, data)

            # Tidy first
            prop.mValue = None

            # Get value type from property name
            value_type = prop.determineValueType()

            # Check for multivalued
            if prop.mName.upper() in prop.sMultiValues:
                prop.mValue = MultiValue(value_type)
            else:
                # Create the type
                prop.mValue = Value.createFromType(value_type)

            # Now parse the data
            prop.mValue.parse(txt, prop.sVariant)

            prop._postCreateValue(value_type)

            return prop

        except Exception as e:
            raise InvalidProperty("Invalid property: '{}'".format(e), data)

    def parseTextParameters(self, txt, data):
        """
        Parse parameters, return string point at value.
        """

        try:
            while txt:
                if txt[0] == ';':
                    # Parse parameter

                    # Move past delimiter
                    txt = txt[1:]

                    # Get quoted string or token
                    parameter_name, txt = stringutils.strduptokenstr(txt, "=")
                    if parameter_name is None:
                        raise InvalidProperty("Invalid property: empty parameter name", data)
                    txt = txt[1:]
                    parameter_value, txt = stringutils.strduptokenstr(txt, ":;,")
                    if parameter_value is None:
                        raise InvalidProperty("Invalid property: empty parameter value", data)

                    # Now add parameter value (decode ^-escaping)
                    attrvalue = Parameter(name=parameter_name, value=decodeParameterValue(parameter_value))
                    self.mParameters.setdefault(parameter_name.upper(), []).append(attrvalue)

                    # Look for additional values
                    while txt[0] == ',':
                        txt = txt[1:]
                        parameter_value2, txt = stringutils.strduptokenstr(txt, ":;,")
                        if parameter_value2 is None:
                            raise InvalidProperty("Invalid property: empty parameter multi-value", data)
                        attrvalue.addValue(decodeParameterValue(parameter_value2))
                elif txt[0] == ':':
                    return txt[1:]
                else:
                    # We should never get here but if we do we need to terminate the loop
                    raise InvalidProperty("Invalid property: missing value separator", data)

        except IndexError:
            raise InvalidProperty("Invalid property: 'parameter index error'", data)

    def getText(self):
        os = StringIO.StringIO()
        self.generate(os)
        return os.getvalue()

    def generate(self, os):

        # Write it out always with value
        self.generateValue(os, False)

    def generateFiltered(self, os, filter):

        # Check for property in filter and whether value is written out
        test, novalue = filter.testPropertyValue(self.mName.upper())
        if test:
            self.generateValue(os, novalue)

    # Write out the actual property, possibly skipping the value
    def generateValue(self, os, novalue):

        self.setupValueParameter()

        # Must write to temp buffer and then wrap
        sout = StringIO.StringIO()
        if self.sUsesGroup and self.mGroup:
            sout.write(self.mGroup + ".")
        sout.write(self.mName)

        # Write all parameters
        for key in sorted(self.mParameters.keys()):
            for attr in self.mParameters[key]:
                sout.write(";")
                attr.generate(sout)

        # Write value
        sout.write(":")
        if self.mValue and not novalue:
            self.mValue.generate(sout)

        # Get string text
        temp = sout.getvalue()
        sout.close()

        # Look for line length exceed
        if len(temp) < 75:
            os.write(temp)
        else:
            # Look for valid utf8 range and write that out
            start = 0
            written = 0
            lineWrap = 74
            while written < len(temp):
                # Start 74 chars on from where we are
                offset = start + lineWrap
                if offset >= len(temp):
                    line = temp[start:]
                    os.write(line)
                    written = len(temp)
                else:
                    # Check whether next char is valid utf8 lead byte
                    while (temp[offset] > 0x7F) and ((ord(temp[offset]) & 0xC0) == 0x80):
                        # Step back until we have a valid char
                        offset -= 1

                    line = temp[start:offset]
                    os.write(line)
                    os.write("\r\n ")
                    lineWrap = 73   # We are now adding a space at the start
                    written += offset - start
                    start = offset

        os.write("\r\n")

    def writeXML(self, node, namespace):

        # Write it out always with value
        self.generateValueXML(node, namespace, False)

    def writeXMLFiltered(self, node, namespace, filter):

        # Check for property in filter and whether value is written out
        test, novalue = filter.testPropertyValue(self.mName.upper())
        if test:
            self.generateValueXML(node, namespace, novalue)

    # Write out the actual property, possibly skipping the value
    def generateValueXML(self, node, namespace, novalue):

        prop = XML.SubElement(node, xmlutils.makeTag(namespace, self.getName()))

        # Write all parameters
        if len(self.mParameters):
            params = XML.SubElement(prop, xmlutils.makeTag(namespace, xmldefinitions.parameters))
            for key in sorted(self.mParameters.keys()):
                for attr in self.mParameters[key]:
                    if attr.getName().lower() != "value":
                        attr.writeXML(params, namespace)

        # Write value
        if self.mValue and not novalue:
            self.mValue.writeXML(prop, namespace)

    @classmethod
    def parseJSON(cls, jobject):
        """
        Parse a JSON property of the form:

        [name, attrs, type, value1, value2, ...]

        @param jobject: a JSON array
        @type jobject: C{list}
        """

        try:
            prop = cls()

            # Get the name
            prop.mName = jobject[0].encode("utf-8").upper()

            # Get the parameters
            if jobject[1]:
                for name, value in jobject[1].items():
                    # Now add parameter value
                    name = name.upper()
                    attrvalue = Parameter(name=name.encode("utf-8"), value=value.encode("utf-8"))
                    prop.mParameters.setdefault(name, []).append(attrvalue)

            # Get default value type from property name and insert a VALUE parameter if current value type is not default
            value_type = cls.sValueTypeMap.get(jobject[2].upper(), Value.VALUETYPE_UNKNOWN)
            default_type = cls.sDefaultValueTypeMap.get(prop.mName.upper(), Value.VALUETYPE_UNKNOWN)
            if default_type != value_type:
                attrvalue = Parameter(name=cls.sValue, value=jobject[2].encode("utf-8").upper())
                prop.mParameters.setdefault(cls.sValue, []).append(attrvalue)

            # Get value type from property name
            value_type = prop.determineValueType()

            # Check for multivalued
            values = jobject[3:]
            if prop.mName.upper() in cls.sMultiValues:
                prop.mValue = MultiValue(value_type)
                prop.mValue.parseJSONValue(values)
            else:
                # Create the type
                prop.mValue = Value.createFromType(value_type)
                prop.mValue.parseJSONValue(values[0])

            # Special post-create for some types
            prop._postCreateValue(value_type)

            return prop

        except Exception as e:
            raise InvalidProperty("Invalid property: '{}'".format(e), jobject)

    def writeJSON(self, jobject):

        # Write it out always with value
        self.generateValueJSON(jobject, False)

    def writeJSONFiltered(self, jobject, filter):

        # Check for property in filter and whether value is written out
        test, novalue = filter.testPropertyValue(self.mName.upper())
        if test:
            self.generateValueJSON(jobject, novalue)

    def generateValueJSON(self, jobject, novalue):

        prop = [
            self.getName().lower(),
            {},
        ]
        jobject.append(prop)

        # Write all parameters
        for key in sorted(self.mParameters.keys()):
            for attr in self.mParameters[key]:
                if attr.getName().lower() != "value":
                    attr.writeJSON(prop[1])

        # Write value
        if self.mValue and not novalue:
            self.mValue.writeJSON(prop)

    def determineValueType(self):
        # Get value type from property name
        value_type = self.sDefaultValueTypeMap.get(self.mName.upper(), Value.VALUETYPE_UNKNOWN)

        # Check whether custom value is set
        if self.sValue in self.mParameters:
            attr = self.getParameterValue(self.sValue)
            value_type = self.sValueTypeMap.get(attr, value_type)

        # Check for specials
        if self.mName.upper() in self.sSpecialVariants:
            # Make sure we have the default value for the special
            if value_type == self.sDefaultValueTypeMap.get(self.mName.upper(), Value.VALUETYPE_UNKNOWN):
                value_type = self.sSpecialVariants[self.mName.upper()]

        return value_type

    def createValue(self, data):
        # Tidy first
        self.mValue = None

        # Get value type from property name
        value_type = self.determineValueType()

        # Check for multivalued
        if self.mName.upper() in self.sMultiValues:
            self.mValue = MultiValue(value_type)
        else:
            # Create the type
            self.mValue = Value.createFromType(value_type)

        # Now parse the data
        try:
            self.mValue.parse(data, self.sVariant)
        except ValueError as e:
            raise InvalidProperty("Invalid property value: '{}'".format(e), data)

        self._postCreateValue(value_type)

    def _postCreateValue(self, value_type):
        """
        Do some extra work after creating a value in this property.

        @param value_type: the iCalendar VALUE type for this property
        @type value_type: C{str}
        """
        pass

    def setValue(self, value):
        # Tidy first
        self.mValue = None

        # Get value type from property name
        value_type = self.sDefaultValueTypeMap.get(self.mName.upper(), Value.VALUETYPE_TEXT)

        # Check whether custom value is set
        if self.sValue in self.mParameters:
            value_type = self.sValueTypeMap.get(self.getParameterValue(self.sValue), value_type)

        # Check for multivalued
        if self.mName.upper() in self.sMultiValues:
            self.mValue = MultiValue(value_type)
        else:
            # Create the type
            self.mValue = Value.createFromType(value_type)

        self.mValue.setValue(value)

        # Special post-create for some types
        self._postCreateValue(value_type)

    def setupValueParameter(self):
        if self.sValue in self.mParameters:
            del self.mParameters[self.sValue]

        # Only if we have a value right now
        if self.mValue is None:
            return

        # See if current type is default for this property. If there is no mapping available,
        # then always add VALUE if it is not TEXT. Always add the value if listed in the
        # C{sAlwaysValueTypes} attribute.
        default_type = self.sDefaultValueTypeMap.get(self.mName.upper())
        if self.mName.upper() in self.sSpecialVariants:
            actual_type = default_type
        else:
            actual_type = self.mValue.getType()
        if default_type is None or default_type != actual_type or self.mName.upper() in self.sAlwaysValueTypes:
            actual_value = self.sTypeValueMap.get(actual_type)
            if actual_value is not None and (default_type is not None or actual_type != Value.VALUETYPE_TEXT):
                self.mParameters.setdefault(self.sValue, []).append(Parameter(name=self.sValue, value=actual_value))

    # Creation
    def _init_attr_value_int(self, ival):
        # Value
        self.mValue = IntegerValue(value=ival)

        # Parameters
        self.setupValueParameter()

    def _init_attr_value_text(self, txt, value_type):
        # Value
        self.mValue = Value.createFromType(value_type)
        if isinstance(self.mValue, PlainTextValue) or isinstance(self.mValue, UnknownValue):
            self.mValue.setValue(txt)

        # Parameters
        self.setupValueParameter()

    def _init_attr_value_datetime(self, dt):
        # Value
        self.mValue = DateTimeValue(value=dt)

        # Parameters
        self.setupValueParameter()

    def _init_attr_value_utcoffset(self, utcoffset):
        # Value
        self.mValue = UTCOffsetValue()
        self.mValue.setValue(utcoffset.getValue())

        # Parameters
        self.setupValueParameter()
Example #2
0
class PropertyBase(object):

    # Mappings between various tokens and internal definitions

    # Map a property name to its default value type
    sDefaultValueTypeMap = {}

    # Properties whose value parameter is always written out
    # even if equal to the default
    sAlwaysValueTypes = set()

    # Map a value name to a value type constant
    sValueTypeMap = {}

    # Map a value type constant to a value name
    sTypeValueMap = {}

    # Properties that are multi-valued
    sMultiValues = set()

    # Properties whose value needs special handling
    sSpecialVariants = {}

    sUsesGroup = False

    sVariant = "none"   # Used to differentiate different forms of text parsing

    sValue = None
    sText = None

    @classmethod
    def registerDefaultValue(cls, propname, valuetype, always_write_value=False):
        """
        Allow custom properties to be used by defining their name/value mappings.

        @param propname: name of the new property
        @type propname: L{str}
        @param valuetype: the L{Value} type used as the default value
        @type valuetype: L{int}
        @param always_write_value: if L{True} always write a VALUE= parameter even
            when the default value is used
        @type always_write_value: L{bool}
        """
        if propname not in cls.sDefaultValueTypeMap:
            cls.sDefaultValueTypeMap[propname] = valuetype
        if always_write_value:
            cls.sAlwaysValueTypes.add(propname)


    def __init__(self, name=None, value=None, valuetype=None):

        self.mName = name if name is not None else ""
        self.mParameters = {}
        self.mValue = None


    def duplicate(self):
        raise NotImplementedError


    def __hash__(self):
        raise NotImplementedError


    def __ne__(self, other):
        return not self.__eq__(other)


    def __eq__(self, other):
        raise NotImplementedError


    def __repr__(self):
        return "Property: %s" % (self.getText(),)


    def __str__(self):
        return self.getText()


    def getGroup(self):
        return self.mGroup if self.sUsesGroup else None


    def setGroup(self, group):
        if self.sUsesGroup:
            self.mGroup = group


    def getName(self):
        return self.mName


    def setName(self, name):
        self.mName = name


    def getParameters(self):
        return self.mParameters


    def setParameters(self, parameters):
        self.mParameters = dict([(k.upper(), v) for k, v in parameters.iteritems()])


    def hasParameter(self, attr):
        return attr.upper() in self.mParameters


    def getParameterValue(self, attr):
        return self.mParameters[attr.upper()][0].getFirstValue()


    def getParameterValues(self, attr):
        return self.mParameters[attr.upper()][0].getValues()


    def addParameter(self, attr):
        self.mParameters.setdefault(attr.getName().upper(), []).append(attr)


    def replaceParameter(self, attr):
        self.mParameters[attr.getName().upper()] = [attr]


    def removeParameters(self, attr):
        if attr.upper() in self.mParameters:
            del self.mParameters[attr.upper()]


    def getValue(self):
        return self.mValue


    def getBinaryValue(self):

        if isinstance(self.mValue, BinaryValue):
            return self.mValue
        else:
            return None


    def getCalAddressValue(self):

        if isinstance(self.mValue, CalAddressValue):
            return self.mValue
        else:
            return None


    def getDateTimeValue(self):

        if isinstance(self.mValue, DateTimeValue):
            return self.mValue
        else:
            return None


    def getDurationValue(self):

        if isinstance(self.mValue, DurationValue):
            return self.mValue
        else:
            return None


    def getIntegerValue(self):

        if isinstance(self.mValue, IntegerValue):
            return self.mValue
        else:
            return None


    def getMultiValue(self):

        if isinstance(self.mValue, MultiValue):
            return self.mValue
        else:
            return None


    def getPeriodValue(self):

        if isinstance(self.mValue, PeriodValue):
            return self.mValue
        else:
            return None


    def getTextValue(self):

        if isinstance(self.mValue, PlainTextValue):
            return self.mValue
        else:
            return None


    def getURIValue(self):

        if isinstance(self.mValue, URIValue):
            return self.mValue
        else:
            return None


    def getUTCOffsetValue(self):

        if isinstance(self.mValue, UTCOffsetValue):
            return self.mValue
        else:
            return None


    @classmethod
    def parseText(cls, data):
        """
        Parse the text format data and return a L{Property}

        @param data: text data
        @type data: C{str}
        """

        try:
            prop = cls()

            # Look for parameter or value delimiter
            prop_name, txt = stringutils.strduptokenstr(data, ";:")
            if not prop_name:
                raise InvalidProperty("Invalid property", data)

            # Get the name
            if prop.sUsesGroup:
                # Check for group prefix
                splits = prop_name.split(".", 1)
                if len(splits) == 2:
                    # We have both group and name
                    prop.mGroup = splits[0]
                    prop.mName = splits[1]
                else:
                    # We have the name
                    prop.mName = prop_name
            else:
                prop.mName = prop_name

            # Get the parameters
            txt = prop.parseTextParameters(txt, data)

            # Tidy first
            prop.mValue = None

            # Get value type from property name
            value_type = prop.determineValueType()

            # Check for multivalued
            if prop.mName.upper() in prop.sMultiValues:
                prop.mValue = MultiValue(value_type)
            else:
                # Create the type
                prop.mValue = Value.createFromType(value_type)

            # Now parse the data
            prop.mValue.parse(txt, prop.sVariant)

            prop._postCreateValue(value_type)

            return prop

        except Exception:
            raise InvalidProperty("Invalid property", data)


    def parseTextParameters(self, txt, data):
        """
        Parse parameters, return string point at value.
        """

        try:
            while txt:
                if txt[0] == ';':
                    # Parse parameter

                    # Move past delimiter
                    txt = txt[1:]

                    # Get quoted string or token
                    parameter_name, txt = stringutils.strduptokenstr(txt, "=")
                    if parameter_name is None:
                        raise InvalidProperty("Invalid property", data)
                    txt = txt[1:]
                    parameter_value, txt = stringutils.strduptokenstr(txt, ":;,")
                    if parameter_value is None:
                        raise InvalidProperty("Invalid property", data)

                    # Now add parameter value (decode ^-escaping)
                    attrvalue = Parameter(name=parameter_name, value=decodeParameterValue(parameter_value))
                    self.mParameters.setdefault(parameter_name.upper(), []).append(attrvalue)

                    # Look for additional values
                    while txt[0] == ',':
                        txt = txt[1:]
                        parameter_value2, txt = stringutils.strduptokenstr(txt, ":;,")
                        if parameter_value2 is None:
                            raise InvalidProperty("Invalid property", data)
                        attrvalue.addValue(decodeParameterValue(parameter_value2))
                elif txt[0] == ':':
                    return txt[1:]
                else:
                    # We should never get here but if we do we need to terminate the loop
                    raise InvalidProperty("Invalid property", data)

        except IndexError:
            raise InvalidProperty("Invalid property", data)


    def getText(self):
        os = StringIO.StringIO()
        self.generate(os)
        return os.getvalue()


    def generate(self, os):

        # Write it out always with value
        self.generateValue(os, False)


    def generateFiltered(self, os, filter):

        # Check for property in filter and whether value is written out
        test, novalue = filter.testPropertyValue(self.mName.upper())
        if test:
            self.generateValue(os, novalue)


    # Write out the actual property, possibly skipping the value
    def generateValue(self, os, novalue):

        self.setupValueParameter()

        # Must write to temp buffer and then wrap
        sout = StringIO.StringIO()
        if self.sUsesGroup and self.mGroup:
            sout.write(self.mGroup + ".")
        sout.write(self.mName)

        # Write all parameters
        for key in sorted(self.mParameters.keys()):
            for attr in self.mParameters[key]:
                sout.write(";")
                attr.generate(sout)

        # Write value
        sout.write(":")
        if self.mValue and not novalue:
            self.mValue.generate(sout)

        # Get string text
        temp = sout.getvalue()
        sout.close()

        # Look for line length exceed
        if len(temp) < 75:
            os.write(temp)
        else:
            # Look for valid utf8 range and write that out
            start = 0
            written = 0
            lineWrap = 74
            while written < len(temp):
                # Start 74 chars on from where we are
                offset = start + lineWrap
                if offset >= len(temp):
                    line = temp[start:]
                    os.write(line)
                    written = len(temp)
                else:
                    # Check whether next char is valid utf8 lead byte
                    while (temp[offset] > 0x7F) and ((ord(temp[offset]) & 0xC0) == 0x80):
                        # Step back until we have a valid char
                        offset -= 1

                    line = temp[start:offset]
                    os.write(line)
                    os.write("\r\n ")
                    lineWrap = 73   # We are now adding a space at the start
                    written += offset - start
                    start = offset

        os.write("\r\n")


    def writeXML(self, node, namespace):

        # Write it out always with value
        self.generateValueXML(node, namespace, False)


    def writeXMLFiltered(self, node, namespace, filter):

        # Check for property in filter and whether value is written out
        test, novalue = filter.testPropertyValue(self.mName.upper())
        if test:
            self.generateValueXML(node, namespace, novalue)


    # Write out the actual property, possibly skipping the value
    def generateValueXML(self, node, namespace, novalue):

        prop = XML.SubElement(node, xmlutils.makeTag(namespace, self.getName()))

        # Write all parameters
        if len(self.mParameters):
            params = XML.SubElement(prop, xmlutils.makeTag(namespace, xmldefinitions.parameters))
            for key in sorted(self.mParameters.keys()):
                for attr in self.mParameters[key]:
                    if attr.getName().lower() != "value":
                        attr.writeXML(params, namespace)

        # Write value
        if self.mValue and not novalue:
            self.mValue.writeXML(prop, namespace)


    @classmethod
    def parseJSON(cls, jobject):
        """
        Parse a JSON property of the form:

        [name, attrs, type, value1, value2, ...]

        @param jobject: a JSON array
        @type jobject: C{list}
        """

        try:
            prop = cls()

            # Get the name
            prop.mName = jobject[0].encode("utf-8").upper()

            # Get the parameters
            if jobject[1]:
                for name, value in jobject[1].items():
                    # Now add parameter value
                    name = name.upper()
                    attrvalue = Parameter(name=name.encode("utf-8"), value=value.encode("utf-8"))
                    prop.mParameters.setdefault(name, []).append(attrvalue)

            # Get default value type from property name and insert a VALUE parameter if current value type is not default
            value_type = cls.sValueTypeMap.get(jobject[2].upper(), Value.VALUETYPE_UNKNOWN)
            default_type = cls.sDefaultValueTypeMap.get(prop.mName.upper(), Value.VALUETYPE_UNKNOWN)
            if default_type != value_type:
                attrvalue = Parameter(name=cls.sValue, value=jobject[2].encode("utf-8").upper())
                prop.mParameters.setdefault(cls.sValue, []).append(attrvalue)

            # Get value type from property name
            value_type = prop.determineValueType()

            # Check for multivalued
            values = jobject[3:]
            if prop.mName.upper() in cls.sMultiValues:
                prop.mValue = MultiValue(value_type)
                prop.mValue.parseJSONValue(values)
            else:
                # Create the type
                prop.mValue = Value.createFromType(value_type)
                prop.mValue.parseJSONValue(values[0])

            # Special post-create for some types
            prop._postCreateValue(value_type)

            return prop

        except Exception:
            raise InvalidProperty("Invalid property", jobject)


    def writeJSON(self, jobject):

        # Write it out always with value
        self.generateValueJSON(jobject, False)


    def writeJSONFiltered(self, jobject, filter):

        # Check for property in filter and whether value is written out
        test, novalue = filter.testPropertyValue(self.mName.upper())
        if test:
            self.generateValueJSON(jobject, novalue)


    def generateValueJSON(self, jobject, novalue):

        prop = [
            self.getName().lower(),
            {},
        ]
        jobject.append(prop)

        # Write all parameters
        for key in sorted(self.mParameters.keys()):
            for attr in self.mParameters[key]:
                if attr.getName().lower() != "value":
                    attr.writeJSON(prop[1])

        # Write value
        if self.mValue and not novalue:
            self.mValue.writeJSON(prop)


    def determineValueType(self):
        # Get value type from property name
        value_type = self.sDefaultValueTypeMap.get(self.mName.upper(), Value.VALUETYPE_UNKNOWN)

        # Check whether custom value is set
        if self.sValue in self.mParameters:
            attr = self.getParameterValue(self.sValue)
            value_type = self.sValueTypeMap.get(attr, value_type)

        # Check for specials
        if self.mName.upper() in self.sSpecialVariants:
            # Make sure we have the default value for the special
            if value_type == self.sDefaultValueTypeMap.get(self.mName.upper(), Value.VALUETYPE_UNKNOWN):
                value_type = self.sSpecialVariants[self.mName.upper()]

        return value_type


    def createValue(self, data):
        # Tidy first
        self.mValue = None

        # Get value type from property name
        value_type = self.determineValueType()

        # Check for multivalued
        if self.mName.upper() in self.sMultiValues:
            self.mValue = MultiValue(value_type)
        else:
            # Create the type
            self.mValue = Value.createFromType(value_type)

        # Now parse the data
        try:
            self.mValue.parse(data, self.sVariant)
        except ValueError:
            raise InvalidProperty("Invalid property value", data)

        self._postCreateValue(value_type)


    def _postCreateValue(self, value_type):
        """
        Do some extra work after creating a value in this property.

        @param value_type: the iCalendar VALUE type for this property
        @type value_type: C{str}
        """
        pass


    def setValue(self, value):
        # Tidy first
        self.mValue = None

        # Get value type from property name
        value_type = self.sDefaultValueTypeMap.get(self.mName.upper(), Value.VALUETYPE_TEXT)

        # Check whether custom value is set
        if self.sValue in self.mParameters:
            value_type = self.sValueTypeMap.get(self.getParameterValue(self.sValue), value_type)

        # Check for multivalued
        if self.mName.upper() in self.sMultiValues:
            self.mValue = MultiValue(value_type)
        else:
            # Create the type
            self.mValue = Value.createFromType(value_type)

        self.mValue.setValue(value)

        # Special post-create for some types
        self._postCreateValue(value_type)


    def setupValueParameter(self):
        if self.sValue in self.mParameters:
            del self.mParameters[self.sValue]

        # Only if we have a value right now
        if self.mValue is None:
            return

        # See if current type is default for this property. If there is no mapping available,
        # then always add VALUE if it is not TEXT. Always add the value if listed in the
        # C{sAlwaysValueTypes} attribute.
        default_type = self.sDefaultValueTypeMap.get(self.mName.upper())
        if self.mName.upper() in self.sSpecialVariants:
            actual_type = default_type
        else:
            actual_type = self.mValue.getType()
        if default_type is None or default_type != actual_type or self.mName.upper() in self.sAlwaysValueTypes:
            actual_value = self.sTypeValueMap.get(actual_type)
            if actual_value is not None and (default_type is not None or actual_type != Value.VALUETYPE_TEXT):
                self.mParameters.setdefault(self.sValue, []).append(Parameter(name=self.sValue, value=actual_value))


    # Creation
    def _init_attr_value_int(self, ival):
        # Value
        self.mValue = IntegerValue(value=ival)

        # Parameters
        self.setupValueParameter()


    def _init_attr_value_text(self, txt, value_type):
        # Value
        self.mValue = Value.createFromType(value_type)
        if isinstance(self.mValue, PlainTextValue) or isinstance(self.mValue, UnknownValue):
            self.mValue.setValue(txt)

        # Parameters
        self.setupValueParameter()


    def _init_attr_value_datetime(self, dt):
        # Value
        self.mValue = DateTimeValue(value=dt)

        # Parameters
        self.setupValueParameter()


    def _init_attr_value_utcoffset(self, utcoffset):
        # Value
        self.mValue = UTCOffsetValue()
        self.mValue.setValue(utcoffset.getValue())

        # Parameters
        self.setupValueParameter()