[CalendarServer-changes] [11614] PyCalendar/branches/json-2/src/pycalendar

source_changes at macosforge.org source_changes at macosforge.org
Sun Aug 18 18:31:02 PDT 2013


Revision: 11614
          http://trac.calendarserver.org//changeset/11614
Author:   cdaboo at apple.com
Date:     2013-08-18 18:31:02 -0700 (Sun, 18 Aug 2013)
Log Message:
-----------
Updated jcal support.

Modified Paths:
--------------
    PyCalendar/branches/json-2/src/pycalendar/__init__.py
    PyCalendar/branches/json-2/src/pycalendar/componentbase.py
    PyCalendar/branches/json-2/src/pycalendar/datetime.py
    PyCalendar/branches/json-2/src/pycalendar/datetimevalue.py
    PyCalendar/branches/json-2/src/pycalendar/duration.py
    PyCalendar/branches/json-2/src/pycalendar/durationvalue.py
    PyCalendar/branches/json-2/src/pycalendar/exceptions.py
    PyCalendar/branches/json-2/src/pycalendar/icalendar/calendar.py
    PyCalendar/branches/json-2/src/pycalendar/icalendar/property.py
    PyCalendar/branches/json-2/src/pycalendar/icalendar/recurrence.py
    PyCalendar/branches/json-2/src/pycalendar/icalendar/recurrencevalue.py
    PyCalendar/branches/json-2/src/pycalendar/icalendar/requeststatusvalue.py
    PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_property.py
    PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_recurrence.py
    PyCalendar/branches/json-2/src/pycalendar/icalendar/xmldefinitions.py
    PyCalendar/branches/json-2/src/pycalendar/integervalue.py
    PyCalendar/branches/json-2/src/pycalendar/multivalue.py
    PyCalendar/branches/json-2/src/pycalendar/parameter.py
    PyCalendar/branches/json-2/src/pycalendar/period.py
    PyCalendar/branches/json-2/src/pycalendar/periodvalue.py
    PyCalendar/branches/json-2/src/pycalendar/plaintextvalue.py
    PyCalendar/branches/json-2/src/pycalendar/property.py
    PyCalendar/branches/json-2/src/pycalendar/utcoffsetvalue.py
    PyCalendar/branches/json-2/src/pycalendar/value.py
    PyCalendar/branches/json-2/src/pycalendar/valueutils.py
    PyCalendar/branches/json-2/src/pycalendar/vcard/adr.py
    PyCalendar/branches/json-2/src/pycalendar/vcard/adrvalue.py
    PyCalendar/branches/json-2/src/pycalendar/vcard/card.py
    PyCalendar/branches/json-2/src/pycalendar/vcard/n.py
    PyCalendar/branches/json-2/src/pycalendar/vcard/nvalue.py
    PyCalendar/branches/json-2/src/pycalendar/vcard/orgvalue.py
    PyCalendar/branches/json-2/src/pycalendar/vcard/property.py
    PyCalendar/branches/json-2/src/pycalendar/xmldefinitions.py

Added Paths:
-----------
    PyCalendar/branches/json-2/src/pycalendar/floatvalue.py
    PyCalendar/branches/json-2/src/pycalendar/geovalue.py
    PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_json.py

Modified: PyCalendar/branches/json-2/src/pycalendar/__init__.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/__init__.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/__init__.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -20,6 +20,8 @@
 import caladdressvalue
 import datetimevalue
 import durationvalue
+import floatvalue
+import geovalue
 import icalendar.recurrencevalue
 import icalendar.requeststatusvalue
 import integervalue

Modified: PyCalendar/branches/json-2/src/pycalendar/componentbase.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/componentbase.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/componentbase.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -20,6 +20,7 @@
 from pycalendar.periodvalue import PeriodValue
 from pycalendar.value import Value
 import xml.etree.cElementTree as XML
+from pycalendar.exceptions import InvalidComponent, ErrorBase
 
 class ComponentBase(object):
 
@@ -359,6 +360,60 @@
         self.writeComponentsFilteredXML(comp, namespace, filter)
 
 
+    @classmethod
+    def parseJSON(cls, jobject, parent, comp=None):
+        """
+        Parse the JSON object which has the form:
+
+        [name, properties, subcomponents]
+
+        @param jobject: a JSON array
+        @type jobject: C{list}
+        """
+        # [name, properties, subcomponents]
+
+        try:
+            if comp is None:
+                comp = cls.sComponentType.makeComponent(jobject[0].upper(), parent)
+            for prop in jobject[1]:
+                comp.addProperty(cls.sPropertyType.parseJSON(prop))
+            for subcomp in jobject[2]:
+                comp.addComponent(cls.sComponentType.parseJSON(subcomp, comp))
+            comp.finalise()
+            return comp
+        except ErrorBase:
+            raise
+        except Exception:
+            raise InvalidComponent("Invalid component", jobject)
+
+
+    def writeJSON(self, jobject):
+
+        # Component element
+        comp = [self.getType().lower(), [], []]
+
+        # Each property
+        self.writePropertiesJSON(comp[1])
+
+        # Each component
+        self.writeComponentsJSON(comp[2])
+
+        jobject.append(comp)
+
+
+    def writeJSONFiltered(self, jobject, filter):
+        # Component element
+        comp = [self.getType().lower(), [], []]
+
+        # Each property
+        self.writePropertiesFilteredJSON(comp[1], filter)
+
+        # Each component
+        self.writeComponentsFilteredJSON(comp[2], filter)
+
+        jobject.append(comp)
+
+
     def sortedComponents(self):
 
         components = self.mComponents[:]
@@ -431,6 +486,27 @@
                         subcomp.writeXMLFiltered(comps, namespace, subfilter)
 
 
+    def writeComponentsJSON(self, jobject):
+
+        if self.mComponents:
+            # Write out the remainder
+            for component in self.sortedComponents():
+                component.writeJSON(jobject)
+
+
+    def writeComponentsFilteredJSON(self, jobject, filter):
+
+        if self.mComponents:
+            # Shortcut for all sub-components
+            if filter.isAllSubComponents():
+                self.writeJSON(jobject)
+            elif filter.hasSubComponentFilters():
+                for subcomp in self.sortedcomponents():
+                    subfilter = filter.getSubComponentFilter(subcomp.getType())
+                    if subfilter is not None:
+                        subcomp.writeJSONFiltered(jobject, subfilter)
+
+
     def loadValue(self, value_name):
         if self.hasProperty(value_name):
             return self.findFirstProperty(value_name)
@@ -603,6 +679,32 @@
                     prop.writeXMLFiltered(props, namespace, filter)
 
 
+    def writePropertiesJSON(self, jobject):
+
+        # Sort properties by name
+        keys = self.sortedPropertyKeys()
+        for key in keys:
+            props = self.mProperties[key]
+            for prop in props:
+                prop.writeJSON(jobject)
+
+
+    def writePropertiesFilteredJSON(self, jobject, filter):
+
+        # Sort properties by name
+        keys = self.sortedPropertyKeys()
+
+        # Shortcut for all properties
+        if filter.isAllProperties():
+            for key in keys:
+                for prop in self.getProperties(key):
+                    prop.writeJSON(jobject)
+        elif filter.hasPropertyFilters():
+            for key in keys:
+                for prop in self.getProperties(key):
+                    prop.writeJSONFiltered(jobject, filter)
+
+
     def loadPrivateValue(self, value_name):
         # Read it in from properties list and then delete the property from the
         # main list

Modified: PyCalendar/branches/json-2/src/pycalendar/datetime.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/datetime.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/datetime.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -934,6 +934,7 @@
             else:
                 return "%04d-%02d-%02dT%02d:%02d:%02d" % (self.mYear, self.mMonth, self.mDay, self.mHours, self.mMinutes, self.mSeconds)
 
+    getJSONText = getXMLText
 
     @classmethod
     def parseText(cls, data, fullISO=False):
@@ -1030,7 +1031,7 @@
 
         # iCalendar:
         #   parse format YYYYMMDD[THHMMSS[Z]]
-        # vCard (fullISO)
+        # vCard (fullISO), jCal
         #   parse format YYYY[-]MM[-]DD[THH[:]MM[:]SS[(Z/(+/-)HHMM]]
 
         try:
@@ -1092,6 +1093,14 @@
         value.text = self.getXMLText()
 
 
+    def parseJSON(self, jobject):
+        self.parse(str(jobject), True)
+
+
+    def writeJSON(self, jobject):
+        jobject.append(self.getJSONText())
+
+
     def normalise(self):
         # Normalise seconds
         normalised_secs = self.mSeconds % 60

Modified: PyCalendar/branches/json-2/src/pycalendar/datetimevalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/datetimevalue.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/datetimevalue.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -21,10 +21,9 @@
 
 class DateTimeValue(WrapperValue, Value):
 
-    def __init__(self, value=None):
-        self.mValue = value if value is not None else DateTime()
+    _wrappedClass = DateTime
+    _wrappedType = None # Depends on actual value
 
-
     def getType(self):
         return  (Value.VALUETYPE_DATETIME, Value.VALUETYPE_DATE)[self.mValue.isDateOnly()]
 

Modified: PyCalendar/branches/json-2/src/pycalendar/duration.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/duration.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/duration.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -270,3 +270,11 @@
 
     def writeXML(self, node, namespace):
         node.text = self.getText()
+
+
+    def parseJSON(self, jobject):
+        self.parse(str(jobject))
+
+
+    def writeJSON(self, jobject):
+        jobject.append(self.getText())

Modified: PyCalendar/branches/json-2/src/pycalendar/durationvalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/durationvalue.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/durationvalue.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -21,11 +21,7 @@
 
 class DurationValue(WrapperValue, Value):
 
-    def __init__(self, value=None):
-        self.mValue = value if value is not None else Duration()
+    _wrappedClass = Duration
+    _wrappedType = Value.VALUETYPE_DURATION
 
-
-    def getType(self):
-        return Value.VALUETYPE_DURATION
-
 Value.registerType(Value.VALUETYPE_DURATION, DurationValue, xmldefinitions.value_duration)

Modified: PyCalendar/branches/json-2/src/pycalendar/exceptions.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/exceptions.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/exceptions.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -27,6 +27,11 @@
 
 
 
+class InvalidComponent(ErrorBase):
+    pass
+
+
+
 class InvalidProperty(ErrorBase):
     pass
 

Added: PyCalendar/branches/json-2/src/pycalendar/floatvalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/floatvalue.py	                        (rev 0)
+++ PyCalendar/branches/json-2/src/pycalendar/floatvalue.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -0,0 +1,68 @@
+##
+#    Copyright (c) 2007-2012 Cyrus Daboo. All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+##
+
+# iCalendar float value
+
+from pycalendar import xmldefinitions
+from pycalendar.value import Value
+
+class FloatValue(Value):
+
+    def __init__(self, value=None):
+        self.mValue = value if value is not None else 0.0
+
+
+    def duplicate(self):
+        return FloatValue(self.mValue)
+
+
+    def getType(self):
+        return Value.VALUETYPE_FLOAT
+
+
+    def parse(self, data, variant):
+        self.mValue = float(data)
+
+
+    # os - StringIO object
+    def generate(self, os):
+        try:
+            os.write(str(self.mValue))
+        except:
+            pass
+
+
+    def writeXML(self, node, namespace):
+        value = self.getXMLNode(node, namespace)
+        value.text = str(self.mValue)
+
+
+    def parseJSONValue(self, jobject):
+        self.mValue = float(jobject)
+
+
+    def writeJSONValue(self, jobject):
+        jobject.append(self.mValue)
+
+
+    def getValue(self):
+        return self.mValue
+
+
+    def setValue(self, value):
+        self.mValue = value
+
+Value.registerType(Value.VALUETYPE_FLOAT, FloatValue, xmldefinitions.value_float)

Added: PyCalendar/branches/json-2/src/pycalendar/geovalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/geovalue.py	                        (rev 0)
+++ PyCalendar/branches/json-2/src/pycalendar/geovalue.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -0,0 +1,88 @@
+##
+#    Copyright (c) 2011-2013 Cyrus Daboo. All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+##
+
+# iCalendar REQUEST-STATUS value
+
+from pycalendar import xmlutils
+from pycalendar.exceptions import InvalidData
+from pycalendar.icalendar import xmldefinitions
+from pycalendar.value import Value
+from pycalendar import xmldefinitions as xmldefinitions_top
+import xml.etree.cElementTree as XML
+
+class GeoValue(Value):
+    """
+    The value is a list of 2 floats
+    """
+
+    def __init__(self, value=None):
+        self.mValue = value if value is not None else [0.0, 0.0]
+
+
+    def __hash__(self):
+        return hash(tuple(self.mValue))
+
+
+    def duplicate(self):
+        return GeoValue(self.mValue[:])
+
+
+    def getType(self):
+        return Value.VALUETYPE_GEO
+
+
+    def parse(self, data, variant="icalendar"):
+
+        splits = data.split(";")
+        if len(splits) != 2:
+            raise InvalidData("GEO value incorrect", data)
+        try:
+            self.mValue = [float(splits[0]), float(splits[1])]
+        except ValueError:
+            raise InvalidData("GEO value incorrect", data)
+
+
+    # os - StringIO object
+    def generate(self, os):
+        os.write("%s;%s" % (self.mValue[0], self.mValue[1],))
+
+
+    def writeXML(self, node, namespace):
+        value = self.getXMLNode(node, namespace)
+
+        latitude = XML.SubElement(value, xmlutils.makeTag(namespace, xmldefinitions.geo_latitude))
+        latitude.text = self.mValue[0]
+
+        longitude = XML.SubElement(value, xmlutils.makeTag(namespace, xmldefinitions.geo_longitude))
+        longitude.text = self.mValue[1]
+
+
+    def parseJSONValue(self, jobject):
+        self.mValue = jobject
+
+
+    def writeJSONValue(self, jobject):
+        jobject.append(list(self.mValue))
+
+
+    def getValue(self):
+        return self.mValue
+
+
+    def setValue(self, value):
+        self.mValue = value
+
+Value.registerType(Value.VALUETYPE_GEO, GeoValue, xmldefinitions.geo, xmldefinitions_top.value_float)

Modified: PyCalendar/branches/json-2/src/pycalendar/icalendar/calendar.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/calendar.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/calendar.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -30,6 +30,7 @@
 from pycalendar.period import Period
 from pycalendar.utils import readFoldedLine
 import collections
+import json
 import xml.etree.cElementTree as XML
 
 class Calendar(ComponentBase):
@@ -499,6 +500,28 @@
         return root
 
 
+    @staticmethod
+    def parseJSONText(data):
+
+        return Calendar.parseJSON(json.loads(data), None, Calendar(add_defaults=False))
+
+
+    def getTextJSON(self, includeTimezones=False):
+        jobject = self.writeJSON(includeTimezones)
+        return json.dumps(jobject, indent=2, separators=(',', ':'))
+
+
+    def writeJSON(self, includeTimezones=False):
+        # Make sure all required timezones are in this object
+        if includeTimezones:
+            self.includeTimezones()
+
+        # Root node structure
+        vcalendar = []
+        super(Calendar, self).writeJSON(vcalendar)
+        return vcalendar[0]
+
+
     # Get expanded components
     def getVEvents(self, period, list, all_day_at_top=True):
         # Look at each VEvent

Modified: PyCalendar/branches/json-2/src/pycalendar/icalendar/property.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/property.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/property.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -46,7 +46,7 @@
         definitions.cICalProperty_CLASS            : Value.VALUETYPE_TEXT,
         definitions.cICalProperty_COMMENT          : Value.VALUETYPE_TEXT,
         definitions.cICalProperty_DESCRIPTION      : Value.VALUETYPE_TEXT,
-        definitions.cICalProperty_GEO              : Value.VALUETYPE_GEO,
+        definitions.cICalProperty_GEO              : Value.VALUETYPE_FLOAT,
         definitions.cICalProperty_LOCATION         : Value.VALUETYPE_TEXT,
         definitions.cICalProperty_PERCENT_COMPLETE : Value.VALUETYPE_INTEGER,
         definitions.cICalProperty_PRIORITY         : Value.VALUETYPE_INTEGER,
@@ -97,7 +97,7 @@
         definitions.cICalProperty_SEQUENCE      : Value.VALUETYPE_INTEGER,
 
         # 5545 Section 3.8.8
-        definitions.cICalProperty_REQUEST_STATUS : Value.VALUETYPE_REQUEST_STATUS,
+        definitions.cICalProperty_REQUEST_STATUS : Value.VALUETYPE_TEXT,
 
         # Extensions: draft-daboo-valarm-extensions-03
         definitions.cICalProperty_ACKNOWLEDGED   : Value.VALUETYPE_DATETIME,
@@ -161,6 +161,11 @@
         definitions.cICalProperty_RDATE,
     ))
 
+    sSpecialVariants = {
+        definitions.cICalProperty_GEO : Value.VALUETYPE_GEO,
+        definitions.cICalProperty_REQUEST_STATUS: Value.VALUETYPE_REQUEST_STATUS,
+    }
+
     sVariant = "ical"
 
     sValue = definitions.cICalParameter_VALUE

Modified: PyCalendar/branches/json-2/src/pycalendar/icalendar/recurrence.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/recurrence.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/recurrence.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -720,6 +720,77 @@
                 child.text = str(item)
 
 
+    def parseJSON(self, jobject):
+        """
+        jCal splits the value into components. We need to convert that back to the
+        iCalendar string format, then parse it.
+        """
+
+        items = []
+        for name, value in jobject.items():
+            if name in (
+                "bysecond", "byminute", "byhour",
+                "bymonthday", "byyearday", "byweekno",
+                "bymonth", "bysetpos", "byday",
+            ):
+                if not isinstance(value, str) and not isinstance(value, unicode) and not isinstance(value, int):
+                    value = ",".join(map(str, value))
+            items.append("%s=%s" % (name.upper(), value,))
+        self.parse(";".join(items))
+
+
+    def writeJSON(self, jobject):
+        """
+        jCal splits the value into components. We need to convert that back to the
+        iCalendar string format, then parse it.
+        """
+        jdict = {}
+
+        jdict[xmldefinitions.recur_freq] = self.cFreqToXMLMap[self.mFreq]
+
+        if self.mUseCount:
+            jdict[xmldefinitions.recur_count] = self.mCount
+        elif self.mUseUntil:
+            jdict[xmldefinitions.recur_until] = self.mUntil.getXMLText()
+
+        if self.mInterval > 1:
+            jdict[xmldefinitions.recur_interval] = self.mInterval
+
+        if self.mBySeconds:
+            jdict[xmldefinitions.recur_bysecond] = self.mBySeconds
+        if self.mByMinutes:
+            jdict[xmldefinitions.recur_byminute] = self.mByMinutes
+        if self.mByHours:
+            jdict[xmldefinitions.recur_byhour] = self.mByHours
+
+        if self.mByDay is not None and len(self.mByDay) != 0:
+            items = []
+            for iter in self.mByDay:
+                data = ""
+                if iter[0] != 0:
+                    data = str(iter[0])
+                data += self.cWeekdayRecurMap.get(iter[1], "")
+                items.append(data)
+            jdict[xmldefinitions.recur_byday] = items
+
+        if self.mByMonthDay:
+            jdict[xmldefinitions.recur_bymonthday] = self.mByMonthDay
+        if self.mByYearDay:
+            jdict[xmldefinitions.recur_byyearday] = self.mByYearDay
+        if self.mByWeekNo:
+            jdict[xmldefinitions.recur_byweekno] = self.mByWeekNo
+        if self.mByMonth:
+            jdict[xmldefinitions.recur_bymonth] = self.mByMonth
+        if self.mBySetPos:
+            jdict[xmldefinitions.recur_bysetpos] = self.mBySetPos
+
+        # MO is the default so we do not need it
+        if self.mWeekstart != definitions.eRecurrence_WEEKDAY_MO:
+            jdict[xmldefinitions.recur_wkst] = self.cWeekdayRecurMap.get(self.mWeekstart, definitions.cICalValue_RECUR_WEEKDAY_MO)
+
+        jobject.append(jdict)
+
+
     def hasBy(self):
         return (self.mBySeconds is not None) and (len(self.mBySeconds) != 0) \
                 or (self.mByMinutes is not None) and (len(self.mByMinutes) != 0) \

Modified: PyCalendar/branches/json-2/src/pycalendar/icalendar/recurrencevalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/recurrencevalue.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/recurrencevalue.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -21,14 +21,9 @@
 
 class RecurrenceValue(WrapperValue, Value):
 
-    def __init__(self, value=None):
-        self.mValue = value if value is not None else Recurrence()
+    _wrappedClass = Recurrence
+    _wrappedType = Value.VALUETYPE_RECUR
 
-
-    def getType(self):
-        return Value.VALUETYPE_RECUR
-
-
     def writeXML(self, node, namespace):
         self.mValue.writeXML(node, namespace)
 

Modified: PyCalendar/branches/json-2/src/pycalendar/icalendar/requeststatusvalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/requeststatusvalue.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/requeststatusvalue.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -82,17 +82,30 @@
 
 
     def writeXML(self, node, namespace):
-        code = XML.SubElement(node, xmlutils.makeTag(namespace, xmldefinitions.req_status_code))
+        value = self.getXMLNode(node, namespace)
+
+        code = XML.SubElement(value, xmlutils.makeTag(namespace, xmldefinitions.req_status_code))
         code.text = self.mValue[0]
 
-        description = XML.SubElement(node, xmlutils.makeTag(namespace, xmldefinitions.req_status_description))
+        description = XML.SubElement(value, xmlutils.makeTag(namespace, xmldefinitions.req_status_description))
         description.text = self.mValue[1]
 
         if len(self.mValue) == 3 and self.mValue[2]:
-            data = XML.SubElement(node, xmlutils.makeTag(namespace, xmldefinitions.req_status_data))
-            data.text = self.mValue[1]
+            data = XML.SubElement(value, xmlutils.makeTag(namespace, xmldefinitions.req_status_data))
+            data.text = self.mValue[2]
 
 
+    def parseJSONValue(self, jobject):
+        self.mValue = jobject
+
+
+    def writeJSONValue(self, jobject):
+        value = [self.mValue[0], self.mValue[1]]
+        if len(self.mValue) == 3:
+            value.append(self.mValue[2])
+        jobject.append(value)
+
+
     def getValue(self):
         return self.mValue
 

Added: PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_json.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_json.py	                        (rev 0)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_json.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -0,0 +1,629 @@
+##
+#    Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+##
+
+from pycalendar.icalendar.calendar import Calendar
+import difflib
+import unittest
+
+class TestJSON(unittest.TestCase):
+
+    data = (
+                (
+"""BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20080205T191224Z
+DTSTART;VALUE=DATE:20081006
+SUMMARY:Planning meeting
+UID:4088E990AD89CB3DBB484909
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""[
+  "vcalendar",
+  [
+    [
+      "version",
+      {},
+      "text",
+      "2.0"
+    ],
+    [
+      "calscale",
+      {},
+      "text",
+      "GREGORIAN"
+    ],
+    [
+      "prodid",
+      {},
+      "text",
+      "-//Example Inc.//Example Calendar//EN"
+    ]
+  ],
+  [
+    [
+      "vevent",
+      [
+        [
+          "uid",
+          {},
+          "text",
+          "4088E990AD89CB3DBB484909"
+        ],
+        [
+          "dtstart",
+          {},
+          "date",
+          "2008-10-06"
+        ],
+        [
+          "dtstamp",
+          {},
+          "date-time",
+          "2008-02-05T19:12:24Z"
+        ],
+        [
+          "summary",
+          {},
+          "text",
+          "Planning meeting"
+        ]
+      ],
+      []
+    ]
+  ]
+]""",
+                ),
+                (
+"""BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Example Corp.//Example Client//EN
+BEGIN:VTIMEZONE
+LAST-MODIFIED:20040110T032845Z
+TZID:US/Eastern
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20060206T001121Z
+DTSTART;TZID=US/Eastern:20060102T120000
+DURATION:PT1H
+GEO:-123.45;67.89
+RRULE:FREQ=DAILY;COUNT=5
+RDATE;TZID=US/Eastern;VALUE=PERIOD:20060102T150000/PT2H
+SUMMARY:Event #2
+DESCRIPTION:We are having a meeting all this week at 12 pm fo
+ r one hour\\, with an additional meeting on the first day 2 h
+ ours long.\\nPlease bring your own lunch for the 12 pm meetin
+ gs.
+UID:00959BC664CA650E933C892C at example.com
+END:VEVENT
+BEGIN:VEVENT
+DTSTAMP:20060206T001121Z
+DTSTART;TZID=US/Eastern:20060104T140000
+DURATION:PT1H
+RECURRENCE-ID;TZID=US/Eastern:20060104T120000
+SUMMARY:Event #2 bis
+UID:00959BC664CA650E933C892C at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"),
+
+"""[
+  "vcalendar",
+  [
+    [
+      "version",
+      {},
+      "text",
+      "2.0"
+    ],
+    [
+      "prodid",
+      {},
+      "text",
+      "-//Example Corp.//Example Client//EN"
+    ]
+  ],
+  [
+    [
+      "vtimezone",
+      [
+        [
+          "tzid",
+          {},
+          "text",
+          "US/Eastern"
+        ],
+        [
+          "last-modified",
+          {},
+          "date-time",
+          "2004-01-10T03:28:45Z"
+        ]
+      ],
+      [
+        [
+          "daylight",
+          [
+            [
+              "dtstart",
+              {},
+              "date-time",
+              "2000-04-04T02:00:00"
+            ],
+            [
+              "rrule",
+              {},
+              "recur",
+              {
+                "bymonth":[
+                  4
+                ],
+                "freq":"YEARLY",
+                "byday":[
+                  "1SU"
+                ]
+              }
+            ],
+            [
+              "tzname",
+              {},
+              "text",
+              "EDT"
+            ],
+            [
+              "tzoffsetfrom",
+              {},
+              "utc-offset",
+              "-05:00"
+            ],
+            [
+              "tzoffsetto",
+              {},
+              "utc-offset",
+              "-04:00"
+            ]
+          ],
+          []
+        ],
+        [
+          "standard",
+          [
+            [
+              "dtstart",
+              {},
+              "date-time",
+              "2000-10-26T02:00:00"
+            ],
+            [
+              "rrule",
+              {},
+              "recur",
+              {
+                "bymonth":[
+                  10
+                ],
+                "freq":"YEARLY",
+                "byday":[
+                  "-1SU"
+                ]
+              }
+            ],
+            [
+              "tzname",
+              {},
+              "text",
+              "EST"
+            ],
+            [
+              "tzoffsetfrom",
+              {},
+              "utc-offset",
+              "-04:00"
+            ],
+            [
+              "tzoffsetto",
+              {},
+              "utc-offset",
+              "-05:00"
+            ]
+          ],
+          []
+        ]
+      ]
+    ],
+    [
+      "vevent",
+      [
+        [
+          "uid",
+          {},
+          "text",
+          "00959BC664CA650E933C892C at example.com"
+        ],
+        [
+          "dtstart",
+          {
+            "tzid":"US/Eastern"
+          },
+          "date-time",
+          "2006-01-02T12:00:00"
+        ],
+        [
+          "duration",
+          {},
+          "duration",
+          "PT1H"
+        ],
+        [
+          "description",
+          {},
+          "text",
+          "We are having a meeting all this week at 12 pm for one hour, with an additional meeting on the first day 2 hours long.\\nPlease bring your own lunch for the 12 pm meetings."
+        ],
+        [
+          "dtstamp",
+          {},
+          "date-time",
+          "2006-02-06T00:11:21Z"
+        ],
+        [
+          "geo",
+          {},
+          "float",
+          [
+            -123.45,
+            67.89
+          ]
+        ],
+        [
+          "rdate",
+          {
+            "tzid":"US/Eastern"
+          },
+          "period",
+          [
+            "2006-01-02T15:00:00",
+            "PT2H"
+          ]
+        ],
+        [
+          "rrule",
+          {},
+          "recur",
+          {
+            "count":5,
+            "freq":"DAILY"
+          }
+        ],
+        [
+          "summary",
+          {},
+          "text",
+          "Event #2"
+        ]
+      ],
+      []
+    ],
+    [
+      "vevent",
+      [
+        [
+          "uid",
+          {},
+          "text",
+          "00959BC664CA650E933C892C at example.com"
+        ],
+        [
+          "recurrence-id",
+          {
+            "tzid":"US/Eastern"
+          },
+          "date-time",
+          "2006-01-04T12:00:00"
+        ],
+        [
+          "dtstart",
+          {
+            "tzid":"US/Eastern"
+          },
+          "date-time",
+          "2006-01-04T14:00:00"
+        ],
+        [
+          "duration",
+          {},
+          "duration",
+          "PT1H"
+        ],
+        [
+          "dtstamp",
+          {},
+          "date-time",
+          "2006-02-06T00:11:21Z"
+        ],
+        [
+          "summary",
+          {},
+          "text",
+          "Event #2 bis"
+        ]
+      ],
+      []
+    ]
+  ]
+]""",
+            ),
+        )
+
+    def testGenerateJSON(self):
+
+        def _doRoundtrip(caldata, resultdata):
+            test1 = resultdata
+
+            cal = Calendar.parseText(caldata)
+
+            test2 = cal.getTextJSON()
+            self.assertEqual(
+                test1,
+                test2,
+                "\n".join(difflib.unified_diff(str(test1).splitlines(), test2.splitlines()))
+            )
+
+        for item1, item2 in self.data:
+            _doRoundtrip(item1, item2)
+
+
+    def testParseJSON(self):
+
+        def _doRoundtrip(caldata, jcaldata):
+            cal1 = Calendar.parseText(caldata)
+            test1 = cal1.getText()
+
+            cal2 = Calendar.parseJSONText(jcaldata)
+            test2 = cal2.getText()
+
+            self.assertEqual(
+                test1,
+                test2,
+                "\n".join(difflib.unified_diff(str(test1).splitlines(), test2.splitlines()))
+            )
+
+        for item1, item2 in self.data:
+            _doRoundtrip(item1, item2)
+
+
+    def testjCalExample1(self):
+
+        jcaldata = """["vcalendar",
+  [
+    ["calscale", {}, "text", "GREGORIAN"],
+    ["prodid", {}, "text", "-//Example Inc.//Example Calendar//EN"],
+    ["version", {}, "text", "2.0"]
+  ],
+  [
+    ["vevent",
+      [
+        ["dtstamp", {}, "date-time", "2008-02-05T19:12:24Z"],
+        ["dtstart", {}, "date", "2008-10-06"],
+        ["summary", {}, "text", "Planning meeting"],
+        ["uid", {}, "text", "4088E990AD89CB3DBB484909"]
+      ],
+      []
+    ]
+  ]
+]
+"""
+
+        icaldata = """BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+PRODID:-//Example Inc.//Example Calendar//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTAMP:20080205T191224Z
+DTSTART;VALUE=DATE:20081006
+SUMMARY:Planning meeting
+UID:4088E990AD89CB3DBB484909
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+        cal1 = Calendar.parseText(icaldata)
+        test1 = cal1.getText()
+
+        cal2 = Calendar.parseJSONText(jcaldata)
+        test2 = cal2.getText()
+
+        self.assertEqual(
+            test1,
+            test2,
+            "\n".join(difflib.unified_diff(str(test1).splitlines(), test2.splitlines()))
+        )
+
+
+    def testjCalExample2(self):
+
+        jcaldata = """["vcalendar",
+  [
+    ["prodid", {}, "text", "-//Example Corp.//Example Client//EN"],
+    ["version", {}, "text", "2.0"]
+  ],
+  [
+    ["vtimezone",
+      [
+        ["last-modified", {}, "date-time", "2004-01-10T03:28:45Z"],
+        ["tzid", {}, "text", "US/Eastern"]
+      ],
+      [
+        ["daylight",
+          [
+            ["dtstart", {}, "date-time", "2000-04-04T02:00:00"],
+            ["rrule",
+              {},
+              "recur",
+              {
+                "freq": "YEARLY",
+                "byday": "1SU",
+                "bymonth": 4
+              }
+            ],
+            ["tzname", {}, "text", "EDT"],
+            ["tzoffsetfrom", {}, "utc-offset", "-05:00"],
+            ["tzoffsetto", {}, "utc-offset", "-04:00"]
+          ],
+          []
+        ],
+        ["standard",
+          [
+            ["dtstart", {}, "date-time", "2000-10-26T02:00:00"],
+            ["rrule",
+              {},
+              "recur",
+              {
+                "freq": "YEARLY",
+                "byday": "-1SU",
+                "bymonth": 10
+              }
+            ],
+            ["tzname", {}, "text", "EST"],
+            ["tzoffsetfrom", {}, "utc-offset", "-04:00"],
+            ["tzoffsetto", {}, "utc-offset", "-05:00"]
+          ],
+          []
+        ]
+      ]
+    ],
+    ["vevent",
+      [
+        ["dtstamp", {}, "date-time", "2006-02-06T00:11:21Z"],
+        ["dtstart",
+          { "tzid": "US/Eastern" },
+          "date-time",
+          "2006-01-02T12:00:00"
+        ],
+        ["duration", {}, "duration", "PT1H"],
+        ["rrule", {}, "recur", { "freq": "DAILY", "count": 5 } ],
+        ["rdate",
+          { "tzid": "US/Eastern" },
+          "period",
+          ["2006-01-02T15:00:00", "PT2H"]
+        ],
+        ["summary", {}, "text", "Event #2"],
+        ["description",
+         {},
+         "text",
+         "We are having a meeting all this week at 12 pm for one hour, with an additional meeting on the first day 2 hours long.\\nPlease bring your own lunch for the 12 pm meetings."
+        ],
+        ["uid", {}, "text", "00959BC664CA650E933C892C at example.com"]
+      ],
+      []
+    ],
+    ["vevent",
+      [
+        ["dtstamp", {}, "date-time", "2006-02-06T00:11:21Z"],
+        ["dtstart",
+          { "tzid": "US/Eastern" },
+          "date-time",
+          "2006-01-04T14:00:00"
+        ],
+        ["duration", {}, "duration", "PT1H"],
+        ["recurrence-id",
+          { "tzid": "US/Eastern" },
+          "date-time",
+          "2006-01-04T12:00:00"
+        ],
+        ["summary", {}, "text", "Event #2 bis"],
+        ["uid", {}, "text", "00959BC664CA650E933C892C at example.com"]
+      ],
+      []
+    ]
+  ]
+]
+"""
+
+        icaldata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Example Corp.//Example Client//EN
+BEGIN:VTIMEZONE
+LAST-MODIFIED:20040110T032845Z
+TZID:US/Eastern
+BEGIN:DAYLIGHT
+DTSTART:20000404T020000
+RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
+TZNAME:EDT
+TZOFFSETFROM:-0500
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20001026T020000
+RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
+TZNAME:EST
+TZOFFSETFROM:-0400
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+DTSTAMP:20060206T001121Z
+DTSTART;TZID=US/Eastern:20060102T120000
+DURATION:PT1H
+RRULE:FREQ=DAILY;COUNT=5
+RDATE;TZID=US/Eastern;VALUE=PERIOD:20060102T150000/PT2H
+SUMMARY:Event #2
+DESCRIPTION:We are having a meeting all this week at 12 pm fo
+ r one hour\\, with an additional meeting on the first day 2 h
+ ours long.\\nPlease bring your own lunch for the 12 pm meetin
+ gs.
+UID:00959BC664CA650E933C892C at example.com
+END:VEVENT
+BEGIN:VEVENT
+DTSTAMP:20060206T001121Z
+DTSTART;TZID=US/Eastern:20060104T140000
+DURATION:PT1H
+RECURRENCE-ID;TZID=US/Eastern:20060104T120000
+SUMMARY:Event #2 bis
+UID:00959BC664CA650E933C892C at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+        cal1 = Calendar.parseText(icaldata)
+        test1 = cal1.getText()
+
+        cal2 = Calendar.parseJSONText(jcaldata)
+        test2 = cal2.getText()
+
+        self.assertEqual(
+            test1,
+            test2,
+            "\n".join(difflib.unified_diff(str(test1).splitlines(), test2.splitlines()))
+        )

Modified: PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_property.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_property.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_property.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -40,8 +40,10 @@
         "SUMMARY:Some \\ntext",
         "RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=-1",
         "REQUEST-STATUS:2.0;Success",
+        "GEO:-2.1;3.2",
         "URI:http://www.example.com",
         "TZOFFSETFROM:-0500",
+        "X-FOO;VALUE=FLOAT:-1.23",
         "X-Test:Some\, text.",
         "X-Test:Some:, text.",
         "X-APPLE-STRUCTURED-LOCATION;VALUE=URI:geo:123.123,123.123",
@@ -130,7 +132,7 @@
 
     def testGEOValueRoundtrip(self):
 
-        data = "GEO:123.456,789.101"
+        data = "GEO:123.456;789.101"
         prop = Property()
         prop.parse(data)
         self.assertEqual(str(prop), data + "\r\n")

Modified: PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_recurrence.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_recurrence.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_recurrence.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -137,7 +137,6 @@
                 DateTime(2016, 1, 15, 0, 0, 0),
             ],
         )
-        print items
 
 
     def testClearOnChange(self):

Modified: PyCalendar/branches/json-2/src/pycalendar/icalendar/xmldefinitions.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/xmldefinitions.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/xmldefinitions.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -46,6 +46,11 @@
 recur_bysetpos = "bysetpos"
 recur_wkst = "wkst"
 
+req_status = "request-status"
 req_status_code = "code"
 req_status_description = "description"
 req_status_data = "data"
+
+geo = "geo"
+geo_longitude = "longitude"
+geo_latitude = "latitude"

Modified: PyCalendar/branches/json-2/src/pycalendar/integervalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/integervalue.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/integervalue.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -50,6 +50,14 @@
         value.text = str(self.mValue)
 
 
+    def parseJSONValue(self, jobject):
+        self.mValue = int(jobject)
+
+
+    def writeJSONValue(self, jobject):
+        jobject.append(self.mValue)
+
+
     def getValue(self):
         return self.mValue
 

Modified: PyCalendar/branches/json-2/src/pycalendar/multivalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/multivalue.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/multivalue.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -93,4 +93,16 @@
         for iter in self.mValues:
             iter.writeXML(node, namespace)
 
+
+    def parseJSONValue(self, jobject):
+        for jvalue in jobject:
+            value = Value.createFromType(self.mType)
+            value.parseJSONValue(jvalue)
+            self.mValues.append(value)
+
+
+    def writeJSONValue(self, jobject):
+        for iter in self.mValues:
+            iter.writeJSONValue(jobject)
+
 Value.registerType(Value.VALUETYPE_MULTIVALUE, MultiValue, None)

Modified: PyCalendar/branches/json-2/src/pycalendar/parameter.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/parameter.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/parameter.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -126,3 +126,7 @@
             # TODO: need to figure out proper value types
             text = XML.SubElement(param, xmlutils.makeTag(namespace, xmldefinitions.value_text))
             text.text = value
+
+
+    def writeJSON(self, jobject):
+        jobject[self.getName().lower()] = self.mValues if len(self.mValues) != 1 else self.mValues[0]

Modified: PyCalendar/branches/json-2/src/pycalendar/period.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/period.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/period.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -78,19 +78,19 @@
         return period
 
 
-    def parse(self, data):
+    def parse(self, data, fullISO=False):
         splits = data.split('/', 1)
         if len(splits) == 2:
             start = splits[0]
             end = splits[1]
 
-            self.mStart.parse(start)
+            self.mStart.parse(start, fullISO)
             if end[0] == 'P':
                 self.mDuration.parse(end)
                 self.mUseDuration = True
                 self.mEnd = self.mStart + self.mDuration
             else:
-                self.mEnd.parse(end)
+                self.mEnd.parse(end, fullISO)
                 self.mUseDuration = False
                 self.mDuration = self.mEnd - self.mStart
         else:
@@ -121,6 +121,26 @@
             end.text = self.mEnd.getXMLText()
 
 
+    def parseJSON(self, jobject):
+        """
+        jCal encodes this as an array of two values. We convert back into a single "/"
+        separated string and parse as normal.
+        """
+        self.parse("%s/%s" % tuple(jobject), True)
+
+
+    def writeJSON(self, jobject):
+        """
+        jCal encodes this value as an array with two components.
+        """
+        value = [self.mStart.getXMLText(), ]
+        if self.mUseDuration:
+            value.append(self.mDuration.getText())
+        else:
+            value.append(self.mEnd.getXMLText())
+        jobject.append(value)
+
+
     def getStart(self):
         return self.mStart
 

Modified: PyCalendar/branches/json-2/src/pycalendar/periodvalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/periodvalue.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/periodvalue.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -21,11 +21,7 @@
 
 class PeriodValue(WrapperValue, Value):
 
-    def __init__(self, value=None):
-        self.mValue = value if value is not None else Period()
+    _wrappedClass = Period
+    _wrappedType = Value.VALUETYPE_PERIOD
 
-
-    def getType(self):
-        return Value.VALUETYPE_PERIOD
-
 Value.registerType(Value.VALUETYPE_PERIOD, PeriodValue, xmldefinitions.value_period)

Modified: PyCalendar/branches/json-2/src/pycalendar/plaintextvalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/plaintextvalue.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/plaintextvalue.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -47,6 +47,14 @@
         value.text = self.mValue
 
 
+    def parseJSONValue(self, jobject):
+        self.mValue = str(jobject)
+
+
+    def writeJSONValue(self, jobject):
+        jobject.append(self.mValue)
+
+
     def getValue(self):
         return self.mValue
 

Modified: PyCalendar/branches/json-2/src/pycalendar/property.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/property.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/property.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -40,7 +40,7 @@
     sValueTypeMap = {}
     sTypeValueMap = {}
     sMultiValues = set()
-    sTextVariants = set()
+    sSpecialVariants = {}
 
     sUsesGroup = False
 
@@ -390,6 +390,93 @@
             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()
+            prop.mName = jobject[0].upper()
+            if jobject[1]:
+                for name, value in jobject[1].items():
+                    # Now add parameter value
+                    name = name.upper()
+                    attrvalue = Parameter(name=name, value=value)
+                    prop.mParameters.setdefault(name, []).append(attrvalue)
+
+            value_type = cls.sValueTypeMap.get(jobject[2].upper(), Value.VALUETYPE_UNKNOWN)
+
+            # Get default value type from property name and insert a VALUE parameter if current value type is not default
+            default_type = cls.sDefaultValueTypeMap.get(prop.mName.upper(), Value.VALUETYPE_UNKNOWN)
+            if default_type != value_type:
+                attrvalue = Parameter(name=cls.sValue, value=value_type)
+                prop.mParameters.setdefault(cls.sValue, []).append(attrvalue)
+
+            # Check for specials
+            if prop.mName.upper() in cls.sSpecialVariants:
+                # Make sure we have the default value for the special
+                if value_type == cls.sDefaultValueTypeMap.get(prop.mName.upper(), Value.VALUETYPE_UNKNOWN):
+                    value_type = cls.sSpecialVariants[prop.mName.upper()]
+
+            # 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 createValue(self, data):
         # Tidy first
         self.mValue = None
@@ -400,9 +487,15 @@
         # Check whether custom value is set
         if self.sValue in self.mParameters:
             attr = self.getParameterValue(self.sValue)
-            if attr != self.sText or self.mName.upper() not in self.sTextVariants:
+            if attr != self.sText:
                 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()]
+
         # Check for multivalued
         if self.mName.upper() in self.sMultiValues:
             self.mValue = MultiValue(value_type)
@@ -464,7 +557,10 @@
         # See if current type is default for this property. If there is no mapping available,
         # then always add VALUE if it is not TEXT.
         default_type = self.sDefaultValueTypeMap.get(self.mName.upper())
-        actual_type = self.mValue.getType()
+        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:
             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):

Modified: PyCalendar/branches/json-2/src/pycalendar/utcoffsetvalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/utcoffsetvalue.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/utcoffsetvalue.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -67,29 +67,24 @@
 
 
     # os - StringIO object
-    def generate(self, os):
+    def generate(self, os, fullISO=False):
         try:
             abs_value = self.mValue
-            if self.mValue < 0 :
-                os.write("-")
+            if abs_value < 0 :
+                sign = "-"
                 abs_value = -self.mValue
             else:
-                os.write("+")
+                sign = "+"
 
             secs = abs_value % 60
             mins = (abs_value / 60) % 60
             hours = abs_value / (60 * 60)
 
-            if (hours < 10):
-                os.write("0")
-            os.write(str(hours))
-            if (mins < 10):
-                os.write("0")
-            os.write(str(mins))
+            s = ("%s%02d:%02d" if fullISO else "%s%02d%02d") % (sign, hours, mins,)
             if (secs != 0):
-                if (secs < 10):
-                    os.write("0")
-                os.write(str(secs))
+                s = ("%s:%s" if fullISO else "%s%s") % (secs,)
+
+            os.write(s)
         except:
             pass
 
@@ -105,6 +100,18 @@
         value.text = text
 
 
+    def parseJSONValue(self, jobject):
+        self.parse(str(jobject), variant="vcard")
+
+
+    def writeJSONValue(self, jobject):
+
+        os = StringIO()
+        self.generate(os, fullISO=True)
+        text = os.getvalue()
+        jobject.append(text)
+
+
     def getValue(self):
         return self.mValue
 

Modified: PyCalendar/branches/json-2/src/pycalendar/value.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/value.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/value.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -50,6 +50,7 @@
 
     _typeMap = {}
     _xmlMap = {}
+    _jsonMap = {}
 
 
     def __hash__(self):
@@ -67,9 +68,10 @@
 
 
     @classmethod
-    def registerType(clz, type, cls, xmlNode):
+    def registerType(clz, type, cls, xmlNode, jsonNode=None):
         clz._typeMap[type] = cls
         clz._xmlMap[type] = xmlNode
+        clz._jsonMap[type] = xmlNode if jsonNode is None else jsonNode
 
 
     @classmethod
@@ -106,5 +108,18 @@
         raise NotImplementedError
 
 
+    def parseJSONValue(self, jobject):
+        raise NotImplementedError
+
+
+    def writeJSON(self, jobject):
+        jobject.append(self._jsonMap[self.getType()])
+        self.writeJSONValue(jobject)
+
+
+    def writeJSONValue(self, jobject):
+        raise NotImplementedError
+
+
     def getXMLNode(self, node, namespace):
         return XML.SubElement(node, xmlutils.makeTag(namespace, self._xmlMap[self.getType()]))

Modified: PyCalendar/branches/json-2/src/pycalendar/valueutils.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/valueutils.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/valueutils.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -52,20 +52,35 @@
         raise NotImplementedError
 
 
+    def parseJSON(self, jobject):
+        raise NotImplementedError
 
+
+    def writeJSON(self, jobject):
+        raise NotImplementedError
+
+
+
 class WrapperValue(object):
     """
     Mix-in for Value derived classes that wrap a value-specific class.
     """
 
-    def duplicate(self):
-        return self.__class__(self.mValue.duplicate())
+    _wrappedClass = None
+    _wrappedType = None
 
+    def __init__(self, value=None):
+        self.mValue = value if value is not None else self._wrappedClass()
 
+
     def getType(self):
-        raise NotImplementedError
+        return self._wrappedType
 
 
+    def duplicate(self):
+        return self.__class__(self.mValue.duplicate())
+
+
     def parse(self, data, variant):
         self.mValue.parse(data)
 
@@ -79,6 +94,14 @@
         value.text = self.mValue.writeXML()
 
 
+    def parseJSONValue(self, jobject):
+        self.mValue.parseJSON(jobject)
+
+
+    def writeJSONValue(self, jobject):
+        self.mValue.writeJSON(jobject)
+
+
     def getValue(self):
         return self.mValue
 

Modified: PyCalendar/branches/json-2/src/pycalendar/vcard/adr.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/adr.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/adr.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -119,6 +119,14 @@
         utils.generateDoubleNestedList(os, self.mValue)
 
 
+    def parseJSON(self, jobject):
+        self.mValue = tuple(jobject)
+
+
+    def writeJSON(self, jobject):
+        jobject.append(list(self.mValue))
+
+
     def getValue(self):
         return self.mValue
 

Modified: PyCalendar/branches/json-2/src/pycalendar/vcard/adrvalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/adrvalue.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/adrvalue.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -22,11 +22,7 @@
 
 class AdrValue(WrapperValue, Value):
 
-    def __init__(self, value=None):
-        self.mValue = value if value is not None else Adr()
+    _wrappedClass = Adr
+    _wrappedType = Value.VALUETYPE_ADR
 
-
-    def getType(self):
-        return Value.VALUETYPE_ADR
-
 Value.registerType(Value.VALUETYPE_ADR, AdrValue, None)

Modified: PyCalendar/branches/json-2/src/pycalendar/vcard/card.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/card.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/card.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -25,6 +25,7 @@
     Property_PRODID, Property_UID
 from pycalendar.vcard.property import Property
 from pycalendar.vcard.validation import VCARD_VALUE_CHECKS
+import json
 
 class Card(ComponentBase):
 
@@ -273,6 +274,20 @@
         return result
 
 
+    @staticmethod
+    def parseJSONText(data):
+
+        try:
+            return Card.parseJSON(json.loads(data), None, Card(add_defaults=False))
+        except Exception:
+            return None
+
+
+    def getTextJSON(self, includeTimezones=False):
+        jobject = self.writeJSON(includeTimezones)
+        return json.dumps(jobject, indent=2, separators=(',', ':'))
+
+
     def addDefaultProperties(self):
         self.addProperty(Property(definitions.Property_PRODID, Card.sProdID))
         self.addProperty(Property(definitions.Property_VERSION, "3.0"))

Modified: PyCalendar/branches/json-2/src/pycalendar/vcard/n.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/n.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/n.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -116,6 +116,14 @@
         utils.generateDoubleNestedList(os, self.mValue)
 
 
+    def parseJSON(self, jobject):
+        self.mValue = tuple(jobject)
+
+
+    def writeJSON(self, jobject):
+        jobject.append(list(self.mValue))
+
+
     def getValue(self):
         return self.mValue
 

Modified: PyCalendar/branches/json-2/src/pycalendar/vcard/nvalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/nvalue.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/nvalue.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -22,11 +22,7 @@
 
 class NValue(WrapperValue, Value):
 
-    def __init__(self, value=None):
-        self.mValue = value if value is not None else N()
+    _wrappedClass = N
+    _wrappedType = Value.VALUETYPE_N
 
-
-    def getType(self):
-        return Value.VALUETYPE_N
-
 Value.registerType(Value.VALUETYPE_N, NValue, None)

Modified: PyCalendar/branches/json-2/src/pycalendar/vcard/orgvalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/orgvalue.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/orgvalue.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -44,6 +44,14 @@
         utils.generateTextList(os, self.mValue, ';')
 
 
+    def parseJSONValue(self, jobject):
+        self.mValue = tuple(jobject)
+
+
+    def writeJSONValue(self, jobject):
+        jobject.append(list(self.mValue))
+
+
     def getValue(self):
         return self.mValue
 

Modified: PyCalendar/branches/json-2/src/pycalendar/vcard/property.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/property.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/property.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -47,13 +47,13 @@
 
         #     2426 Section 3.1
         definitions.Property_FN       : Value.VALUETYPE_TEXT,
-        definitions.Property_N        : Value.VALUETYPE_N,
+        definitions.Property_N        : Value.VALUETYPE_TEXT,
         definitions.Property_NICKNAME : Value.VALUETYPE_TEXT,
         definitions.Property_PHOTO    : Value.VALUETYPE_BINARY,
         definitions.Property_BDAY     : Value.VALUETYPE_DATE,
 
         #     2426 Section 3.2
-        definitions.Property_ADR   : Value.VALUETYPE_ADR,
+        definitions.Property_ADR   : Value.VALUETYPE_TEXT,
         definitions.Property_LABEL : Value.VALUETYPE_TEXT,
 
         #     2426 Section 3.3
@@ -63,14 +63,14 @@
 
         #     2426 Section 3.4
         definitions.Property_TZ  : Value.VALUETYPE_UTC_OFFSET,
-        definitions.Property_GEO : Value.VALUETYPE_GEO,
+        definitions.Property_GEO : Value.VALUETYPE_FLOAT,
 
         #     2426 Section 3.5
         definitions.Property_TITLE : Value.VALUETYPE_TEXT,
         definitions.Property_ROLE  : Value.VALUETYPE_TEXT,
         definitions.Property_LOGO  : Value.VALUETYPE_BINARY,
         definitions.Property_AGENT : Value.VALUETYPE_VCARD,
-        definitions.Property_ORG   : Value.VALUETYPE_ORG,
+        definitions.Property_ORG   : Value.VALUETYPE_TEXT,
 
         #     2426 Section 3.6
         definitions.Property_CATEGORIES  : Value.VALUETYPE_TEXT,
@@ -125,11 +125,12 @@
         definitions.Property_CATEGORIES,
     ))
 
-    sTextVariants = set((
-        definitions.Property_ADR,
-        definitions.Property_N,
-        definitions.Property_ORG,
-    ))
+    sSpecialVariants = {
+        definitions.Property_ADR : Value.VALUETYPE_ADR,
+        definitions.Property_GEO : Value.VALUETYPE_GEO,
+        definitions.Property_N: Value.VALUETYPE_N,
+        definitions.Property_ORG: Value.VALUETYPE_ORG,
+    }
 
     sUsesGroup = True
 

Modified: PyCalendar/branches/json-2/src/pycalendar/xmldefinitions.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/xmldefinitions.py	2013-08-16 18:14:50 UTC (rev 11613)
+++ PyCalendar/branches/json-2/src/pycalendar/xmldefinitions.py	2013-08-19 01:31:02 UTC (rev 11614)
@@ -28,6 +28,7 @@
 value_date = "date"
 value_date_time = "date-time"
 value_duration = "duration"
+value_float = "float"
 value_integer = "integer"
 value_period = "period"
 value_text = "text"
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130818/1471c9ee/attachment-0001.html>


More information about the calendarserver-changes mailing list