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

source_changes at macosforge.org source_changes at macosforge.org
Mon Aug 19 10:21:45 PDT 2013


Revision: 11615
          http://trac.calendarserver.org//changeset/11615
Author:   cdaboo at apple.com
Date:     2013-08-19 10:21:45 -0700 (Mon, 19 Aug 2013)
Log Message:
-----------
More re-factoring to clean-up some api.

Modified Paths:
--------------
    PyCalendar/branches/json-2/src/pycalendar/icalendar/calendar.py
    PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_property.py
    PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_requeststatus.py
    PyCalendar/branches/json-2/src/pycalendar/property.py
    PyCalendar/branches/json-2/src/pycalendar/tests/test_urivalue.py
    PyCalendar/branches/json-2/src/pycalendar/tests/test_validation.py
    PyCalendar/branches/json-2/src/pycalendar/vcard/card.py
    PyCalendar/branches/json-2/src/pycalendar/vcard/property.py
    PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_adrvalue.py
    PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_nvalue.py
    PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_orgvalue.py
    PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_property.py

Added Paths:
-----------
    PyCalendar/branches/json-2/src/pycalendar/containerbase.py

Added: PyCalendar/branches/json-2/src/pycalendar/containerbase.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/containerbase.py	                        (rev 0)
+++ PyCalendar/branches/json-2/src/pycalendar/containerbase.py	2013-08-19 17:21:45 UTC (rev 11615)
@@ -0,0 +1,215 @@
+##
+#    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.
+##
+
+from cStringIO import StringIO
+from pycalendar.componentbase import ComponentBase
+from pycalendar.exceptions import InvalidData, ValidationError
+from pycalendar.parser import ParserContext
+from pycalendar.utils import readFoldedLine
+import json
+
+class ContainerBase(ComponentBase):
+    """
+    Represents the top-level component (i.e., VCALENDAR or vCARD)
+    """
+
+    sProdID = "-//mulberrymail.com//Mulberry v4.0//EN"
+    sDomain = "mulberrymail.com"
+
+    # These must be set by derived classes
+    sContainerDescriptor = None
+    sComponentType = None
+    sPropertyType = None
+
+    @classmethod
+    def setPRODID(cls, prodid):
+        cls.sProdID = prodid
+
+
+    @classmethod
+    def setDomain(cls, domain):
+        cls.sDomain = domain
+
+
+    def __init__(self, add_defaults=True):
+        super(ContainerBase, self).__init__()
+
+        if add_defaults:
+            self.addDefaultProperties()
+
+
+    def duplicate(self):
+        return super(ContainerBase, self).duplicate()
+
+
+    def getType(self):
+        raise NotImplementedError
+
+
+    def finalise(self):
+        pass
+
+
+    def validate(self, doFix=False, doRaise=False):
+        """
+        Validate the data in this component and optionally fix any problems. Return
+        a tuple containing two lists: the first describes problems that were fixed, the
+        second problems that were not fixed. Caller can then decide what to do with unfixed
+        issues.
+        """
+
+        # Optional raise behavior
+        fixed, unfixed = super(ContainerBase, self).validate(doFix)
+        if doRaise and unfixed:
+            raise ValidationError(";".join(unfixed))
+        return fixed, unfixed
+
+
+    @classmethod
+    def parseText(cls, data):
+
+        cal = cls(add_defaults=False)
+        if cal.parse(StringIO(data)):
+            return cal
+        else:
+            return None
+
+
+    def parse(self, ins):
+
+        result = False
+
+        self.setProperties({})
+
+        LOOK_FOR_CONTAINER = 0
+        GET_PROPERTY_OR_COMPONENT = 1
+
+        state = LOOK_FOR_CONTAINER
+
+        # Get lines looking for start of calendar
+        lines = [None, None]
+        comp = self
+        compend = None
+        componentstack = []
+
+        while readFoldedLine(ins, lines):
+
+            line = lines[0]
+
+            if state == LOOK_FOR_CONTAINER:
+
+                # Look for start
+                if line == self.getBeginDelimiter():
+                    # Next state
+                    state = GET_PROPERTY_OR_COMPONENT
+
+                    # Indicate success at this point
+                    result = True
+
+                # Handle blank line
+                elif len(line) == 0:
+                    # Raise if requested, otherwise just ignore
+                    if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
+                        raise InvalidData("%s data has blank lines" % (self.sContainerDescriptor,))
+
+                # Unrecognized data
+                else:
+                    raise InvalidData("%s data not recognized" % (self.sContainerDescriptor,), line)
+
+            elif state == GET_PROPERTY_OR_COMPONENT:
+
+                # Parse property or look for start of component
+                if line.startswith("BEGIN:") and self.sComponentType is not None:
+
+                    # Push previous details to stack
+                    componentstack.append((comp, compend,))
+
+                    # Start a new component
+                    comp = self.sComponentType.makeComponent(line[6:], comp)
+                    compend = comp.getEndDelimiter()
+
+                # Look for end of object
+                elif line == self.getEndDelimiter():
+
+                    # Finalise the current calendar
+                    self.finalise()
+
+                    # Change state
+                    state = LOOK_FOR_CONTAINER
+
+                # Look for end of current component
+                elif line == compend:
+
+                    # Finalise the component (this caches data from the properties)
+                    comp.finalise()
+
+                    # Embed component in parent and reset to use parent
+                    componentstack[-1][0].addComponent(comp)
+                    comp, compend = componentstack.pop()
+
+                # Blank line
+                elif len(line) == 0:
+                    # Raise if requested, otherwise just ignore
+                    if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
+                        raise InvalidData("%s data has blank lines" % (self.sContainerDescriptor,))
+
+                # Must be a property
+                else:
+
+                    # Parse parameter/value for top-level calendar item
+                    prop = self.sPropertyType.parseText(line)
+
+                    # Check for valid property
+                    if comp is self:
+                        if not comp.validProperty(prop):
+                            raise InvalidData("Invalid property", str(prop))
+                        else:
+                            comp.addProperty(prop)
+                    else:
+                        comp.addProperty(prop)
+
+        # Check for truncated data
+        if state != LOOK_FOR_CONTAINER:
+            raise InvalidData("%s data not complete" % (self.sContainerDescriptor,))
+
+        # Validate some things
+        if result and not self.hasProperty("VERSION"):
+            raise InvalidData("%s missing VERSION" % (self.sContainerDescriptor,))
+
+        return result
+
+
+    @classmethod
+    def parseJSONText(cls, data):
+
+        try:
+            return cls.parseJSON(json.loads(data), None, cls(add_defaults=False))
+        except Exception:
+            return None
+
+
+    def getTextJSON(self):
+        jobject = []
+        self.writeJSON(jobject)
+        return json.dumps(jobject[0], indent=2, separators=(',', ':'))
+
+
+    def addDefaultProperties(self):
+        raise NotImplementedError
+
+
+    def validProperty(self, prop):
+        raise NotImplementedError

Modified: PyCalendar/branches/json-2/src/pycalendar/icalendar/calendar.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/calendar.py	2013-08-19 01:31:02 UTC (rev 11614)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/calendar.py	2013-08-19 17:21:45 UTC (rev 11615)
@@ -16,9 +16,9 @@
 
 from cStringIO import StringIO
 from pycalendar import xmlutils
-from pycalendar.componentbase import ComponentBase
+from pycalendar.containerbase import ContainerBase
 from pycalendar.datetime import DateTime
-from pycalendar.exceptions import InvalidData, ValidationError
+from pycalendar.exceptions import InvalidData
 from pycalendar.icalendar import definitions, xmldefinitions
 from pycalendar.icalendar.component import Component
 from pycalendar.icalendar.componentexpanded import ComponentExpanded
@@ -33,7 +33,7 @@
 import json
 import xml.etree.cElementTree as XML
 
-class Calendar(ComponentBase):
+class Calendar(ContainerBase):
 
     REMOVE_ALL = 0
     REMOVE_ONLY_THIS = 1
@@ -45,18 +45,10 @@
     sProdID = "-//mulberrymail.com//Mulberry v4.0//EN"
     sDomain = "mulberrymail.com"
 
+    sContainerDescriptor = "iCalendar"
     sComponentType = Component
     sPropertyType = Property
 
-    @staticmethod
-    def setPRODID(prodid):
-        Calendar.sProdID = prodid
-
-
-    @staticmethod
-    def setDomain(domain):
-        Calendar.sDomain = domain
-
     propertyCardinality_1 = (
         definitions.cICalProperty_PRODID,
         definitions.cICalProperty_VERSION,
@@ -77,10 +69,7 @@
         self.mMasterComponentsByTypeAndUID = collections.defaultdict(lambda: collections.defaultdict(list))
         self.mOverriddenComponentsByUID = collections.defaultdict(list)
 
-        if add_defaults:
-            self.addDefaultProperties()
 
-
     def duplicate(self):
         other = super(Calendar, self).duplicate()
         other.mName = self.mName
@@ -182,21 +171,6 @@
             self.mDescription = temps
 
 
-    def validate(self, doFix=False, doRaise=False):
-        """
-        Validate the data in this component and optionally fix any problems. Return
-        a tuple containing two lists: the first describes problems that were fixed, the
-        second problems that were not fixed. Caller can then decide what to do with unfixed
-        issues.
-        """
-
-        # Optional raise behavior
-        fixed, unfixed = super(Calendar, self).validate(doFix)
-        if doRaise and unfixed:
-            raise ValidationError(";".join(unfixed))
-        return fixed, unfixed
-
-
     def sortedComponentNames(self):
         return (
             definitions.cICalComponent_VTIMEZONE,
@@ -217,122 +191,14 @@
         )
 
 
-    @staticmethod
-    def parseText(data):
-
-        cal = Calendar(add_defaults=False)
-        if cal.parse(StringIO(data)):
-            return cal
-        else:
-            return None
-
-
     def parse(self, ins):
 
-        result = False
+        result = super(Calendar, self).parse(ins)
 
-        self.setProperties({})
-
-        LOOK_FOR_VCALENDAR = 0
-        GET_PROPERTY_OR_COMPONENT = 1
-
-        state = LOOK_FOR_VCALENDAR
-
-        # Get lines looking for start of calendar
-        lines = [None, None]
-        comp = self
-        compend = None
-        componentstack = []
-
-        while readFoldedLine(ins, lines):
-
-            line = lines[0]
-
-            if state == LOOK_FOR_VCALENDAR:
-
-                # Look for start
-                if line == self.getBeginDelimiter():
-                    # Next state
-                    state = GET_PROPERTY_OR_COMPONENT
-
-                    # Indicate success at this point
-                    result = True
-
-                # Handle blank line
-                elif len(line) == 0:
-                    # Raise if requested, otherwise just ignore
-                    if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
-                        raise InvalidData("iCalendar data has blank lines")
-
-                # Unrecognized data
-                else:
-                    raise InvalidData("iCalendar data not recognized", line)
-
-            elif state == GET_PROPERTY_OR_COMPONENT:
-
-                # Parse property or look for start of component
-                if line.startswith("BEGIN:"):
-
-                    # Push previous details to stack
-                    componentstack.append((comp, compend,))
-
-                    # Start a new component
-                    comp = Component.makeComponent(line[6:], comp)
-                    compend = comp.getEndDelimiter()
-
-                # Look for end of object
-                elif line == self.getEndDelimiter():
-
-                    # Finalise the current calendar
-                    self.finalise()
-
-                    # Change state
-                    state = LOOK_FOR_VCALENDAR
-
-                # Look for end of current component
-                elif line == compend:
-
-                    # Finalise the component (this caches data from the properties)
-                    comp.finalise()
-
-                    # Embed component in parent and reset to use parent
-                    componentstack[-1][0].addComponent(comp)
-                    comp, compend = componentstack.pop()
-
-                # Blank line
-                elif len(line) == 0:
-                    # Raise if requested, otherwise just ignore
-                    if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
-                        raise InvalidData("iCalendar data has blank lines")
-
-                # Must be a property
-                else:
-
-                    # Parse parameter/value for top-level calendar item
-                    prop = Property()
-                    if prop.parse(line):
-
-                        # Check for valid property
-                        if comp is self:
-                            if not comp.validProperty(prop):
-                                raise InvalidData("Invalid property", str(prop))
-                            elif not comp.ignoreProperty(prop):
-                                comp.addProperty(prop)
-                        else:
-                            comp.addProperty(prop)
-
-        # Check for truncated data
-        if state != LOOK_FOR_VCALENDAR:
-            raise InvalidData("iCalendar data not complete")
-
         # We need to store all timezones in the static object so they can be accessed by any date object
         from pycalendar.timezonedb import TimezoneDatabase
         TimezoneDatabase.mergeTimezones(self, self.getComponents(definitions.cICalComponent_VTIMEZONE))
 
-        # Validate some things
-        if result and not self.hasProperty("VERSION"):
-            raise InvalidData("iCalendar missing VERSION")
-
         return result
 
 
@@ -421,12 +287,11 @@
                 else:
 
                     # Parse parameter/value for top-level calendar item
-                    prop = Property()
-                    if prop.parse(lines[0]):
+                    prop = Property.parseText(lines[0])
 
-                        # Check for valid property
-                        if comp is not self:
-                            comp.addProperty(prop)
+                    # Check for valid property
+                    if comp is not self:
+                        comp.addProperty(prop)
 
             # Exit if we have one - ignore all the rest
             if state == GOT_VCALENDAR:
@@ -500,26 +365,19 @@
         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=(',', ':'))
+        jobject = []
+        self.writeJSON(jobject, includeTimezones)
+        return json.dumps(jobject[0], indent=2, separators=(',', ':'))
 
 
-    def writeJSON(self, includeTimezones=False):
+    def writeJSON(self, jobject, 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]
+        super(Calendar, self).writeJSON(jobject)
 
 
     # Get expanded components
@@ -724,10 +582,6 @@
         return True
 
 
-    def ignoreProperty(self, prop):
-        return False #prop.getName() in (definitions.cICalProperty_VERSION, definitions.cICalProperty_CALSCALE, definitions.cICalProperty_PRODID)
-
-
     def includeTimezones(self):
         # Get timezone names from each component
         tzids = set()

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-19 01:31:02 UTC (rev 11614)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_property.py	2013-08-19 17:21:45 UTC (rev 11615)
@@ -62,8 +62,7 @@
     def testParseGenerate(self):
 
         for data in TestProperty.test_data:
-            prop = Property()
-            prop.parse(data)
+            prop = Property.parseText(data)
             propstr = str(prop).replace("\r\n ", "")
             self.assertEqual(propstr[:-2], data, "Failed parse/generate: %s to %s" % (data, propstr,))
 
@@ -71,10 +70,8 @@
     def testEquality(self):
 
         for data in TestProperty.test_data:
-            prop1 = Property()
-            prop1.parse(data)
-            prop2 = Property()
-            prop2.parse(data)
+            prop1 = Property.parseText(data)
+            prop2 = Property.parseText(data)
             self.assertEqual(prop1, prop2, "Failed equality: %s" % (data,))
 
 
@@ -98,8 +95,7 @@
         save = ParserContext.INVALID_ESCAPE_SEQUENCES
         for data in test_bad_data:
             ParserContext.INVALID_ESCAPE_SEQUENCES = ParserContext.PARSER_RAISE
-            prop = Property()
-            self.assertRaises(InvalidProperty, prop.parse, data)
+            self.assertRaises(InvalidProperty, Property.parseText, data)
         ParserContext.INVALID_ESCAPE_SEQUENCES = save
 
 
@@ -107,8 +103,7 @@
 
         hashes = []
         for item in TestProperty.test_data:
-            prop = Property()
-            prop.parse(item)
+            prop = Property.parseText(item)
             hashes.append(hash(prop))
         hashes.sort()
         for i in range(1, len(hashes)):
@@ -133,24 +128,21 @@
     def testGEOValueRoundtrip(self):
 
         data = "GEO:123.456;789.101"
-        prop = Property()
-        prop.parse(data)
+        prop = Property.parseText(data)
         self.assertEqual(str(prop), data + "\r\n")
 
 
     def testUnknownValueRoundtrip(self):
 
         data = "X-FOO:Text, not escaped"
-        prop = Property()
-        prop.parse(data)
+        prop = Property.parseText(data)
         self.assertEqual(str(prop), data + "\r\n")
 
         prop = Property("X-FOO", "Text, not escaped")
         self.assertEqual(str(prop), data + "\r\n")
 
         data = "X-FOO:Text\\, escaped\\n"
-        prop = Property()
-        prop.parse(data)
+        prop = Property.parseText(data)
         self.assertEqual(str(prop), data + "\r\n")
 
         prop = Property("X-FOO", "Text\\, escaped\\n")
@@ -162,8 +154,7 @@
         Property.registerDefaultValue("X-SPECIAL-REGISTRATION", Value.VALUETYPE_TEXT)
 
         data = "X-SPECIAL-REGISTRATION:Text\\, escaped\\n"
-        prop = Property()
-        prop.parse(data)
+        prop = Property.parseText(data)
         self.assertEqual(str(prop), "X-SPECIAL-REGISTRATION:Text\\, escaped\\n\r\n")
 
         prop = Property("X-SPECIAL-REGISTRATION", "Text, escaped\n")
@@ -180,12 +171,10 @@
         self.assertEqual(str(prop), "X-FOO;X-BAR=^'Check^';X-BAR2=Check^nThis\tOut^n:Test\r\n")
 
         data = "X-FOO;X-BAR=^'Check^':Test"
-        prop = Property()
-        prop.parse(data)
+        prop = Property.parseText(data)
         self.assertEqual(prop.getParameterValue("X-BAR"), "\"Check\"")
 
         data = "X-FOO;X-BAR=^'Check^';X-BAR2=Check^nThis\tOut^n:Test"
-        prop = Property()
-        prop.parse(data)
+        prop = Property.parseText(data)
         self.assertEqual(prop.getParameterValue("X-BAR"), "\"Check\"")
         self.assertEqual(prop.getParameterValue("X-BAR2"), "Check\nThis\tOut\n")

Modified: PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_requeststatus.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_requeststatus.py	2013-08-19 01:31:02 UTC (rev 11614)
+++ PyCalendar/branches/json-2/src/pycalendar/icalendar/tests/test_requeststatus.py	2013-08-19 17:21:45 UTC (rev 11615)
@@ -90,6 +90,5 @@
         )
 
         for item in items:
-            req = Property()
-            req.parse(item)
+            req = Property.parseText(item)
             self.assertEqual(req.getText(), item + "\r\n", "Failed to parse and re-generate '%s'" % (item,))

Modified: PyCalendar/branches/json-2/src/pycalendar/property.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/property.py	2013-08-19 01:31:02 UTC (rev 11614)
+++ PyCalendar/branches/json-2/src/pycalendar/property.py	2013-08-19 17:21:45 UTC (rev 11615)
@@ -207,38 +207,65 @@
             return None
 
 
-    def parse(self, data):
-        # Look for parameter or value delimiter
-        prop_name, txt = stringutils.strduptokenstr(data, ";:")
-        if not prop_name:
-            raise InvalidProperty("Invalid property", data)
+    @classmethod
+    def parseText(cls, data):
+        """
+        Parse the text format data and return a L{Property}
 
-        # We have the name
-        if self.sUsesGroup:
-            # Check for group prefix
-            splits = prop_name.split(".", 1)
-            if len(splits) == 2:
-                # We have both group and name
-                self.mGroup = splits[0]
-                self.mName = splits[1]
+        @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:
-                # We have the name
-                self.mName = prop_name
-        else:
-            self.mName = prop_name
+                prop.mName = prop_name
 
-        # Now loop getting data
-        txt = self.parseParameters(txt, data)
-        self.createValue(txt)
+            # Get the parameters
+            txt = prop.parseTextParameters(txt, data)
 
-        # We must have a value of some kind
-        if self.mValue is None:
+            # 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)
 
-        return True
 
-
-    def parseParameters(self, txt, data):
+    def parseTextParameters(self, txt, data):
         """
         Parse parameters, return string point at value.
         """
@@ -403,7 +430,11 @@
 
         try:
             prop = cls()
+
+            # Get the name
             prop.mName = jobject[0].upper()
+
+            # Get the parameters
             if jobject[1]:
                 for name, value in jobject[1].items():
                     # Now add parameter value
@@ -411,19 +442,15 @@
                     attrvalue = Parameter(name=name, value=value)
                     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)
-
-            # 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)
+                attrvalue = Parameter(name=cls.sValue, value=jobject[2].upper())
                 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()]
+            # Get value type from property name
+            value_type = prop.determineValueType()
 
             # Check for multivalued
             values = jobject[3:]
@@ -477,10 +504,7 @@
             self.mValue.writeJSON(prop)
 
 
-    def createValue(self, data):
-        # Tidy first
-        self.mValue = None
-
+    def determineValueType(self):
         # Get value type from property name
         value_type = self.sDefaultValueTypeMap.get(self.mName.upper(), Value.VALUETYPE_UNKNOWN)
 
@@ -496,6 +520,16 @@
             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)

Modified: PyCalendar/branches/json-2/src/pycalendar/tests/test_urivalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/tests/test_urivalue.py	2013-08-19 01:31:02 UTC (rev 11614)
+++ PyCalendar/branches/json-2/src/pycalendar/tests/test_urivalue.py	2013-08-19 17:21:45 UTC (rev 11615)
@@ -68,8 +68,7 @@
         )
 
         for item, result in items:
-            prop = Property()
-            prop.parse(item)
+            prop = Property.parseText(item)
             test = prop.getText()
             self.assertEqual(test, result + "\r\n", "Failed to parse and re-generate '%s'" % (item,))
 
@@ -81,7 +80,6 @@
         )
 
         for item, result in items:
-            prop = Property()
-            prop.parse(item)
+            prop = Property.parseText(item)
             test = prop.getText()
             self.assertEqual(test, result + "\r\n", "Failed to parse and re-generate '%s'" % (item,))

Modified: PyCalendar/branches/json-2/src/pycalendar/tests/test_validation.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/tests/test_validation.py	2013-08-19 01:31:02 UTC (rev 11614)
+++ PyCalendar/branches/json-2/src/pycalendar/tests/test_validation.py	2013-08-19 17:21:45 UTC (rev 11615)
@@ -40,8 +40,7 @@
         )
 
         for prop, test, result in props:
-            property = Property()
-            property.parse(prop)
+            property = Property.parseText(prop)
             self.assertEqual(PropertyValueChecks.stringValue(test, property), result)
 
 
@@ -55,8 +54,7 @@
         )
 
         for prop, result in props:
-            property = Property()
-            property.parse(prop)
+            property = Property.parseText(prop)
             self.assertEqual(PropertyValueChecks.alwaysUTC(property), result)
 
 
@@ -72,8 +70,7 @@
         )
 
         for prop, low, high, result in props:
-            property = Property()
-            property.parse(prop)
+            property = Property.parseText(prop)
             self.assertEqual(PropertyValueChecks.numericRange(low, high, property), result)
 
 
@@ -87,6 +84,5 @@
         )
 
         for prop, result in props:
-            property = Property()
-            property.parse(prop)
+            property = Property.parseText(prop)
             self.assertEqual(PropertyValueChecks.positiveIntegerOrZero(property), result)

Modified: PyCalendar/branches/json-2/src/pycalendar/vcard/card.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/card.py	2013-08-19 01:31:02 UTC (rev 11614)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/card.py	2013-08-19 17:21:45 UTC (rev 11615)
@@ -14,10 +14,8 @@
 #    limitations under the License.
 ##
 
-from cStringIO import StringIO
-from pycalendar.componentbase import ComponentBase
-from pycalendar.exceptions import InvalidData, \
-    ValidationError
+from pycalendar.containerbase import ContainerBase
+from pycalendar.exceptions import InvalidData
 from pycalendar.parser import ParserContext
 from pycalendar.utils import readFoldedLine
 from pycalendar.vcard import definitions
@@ -25,25 +23,13 @@
     Property_PRODID, Property_UID
 from pycalendar.vcard.property import Property
 from pycalendar.vcard.validation import VCARD_VALUE_CHECKS
-import json
 
-class Card(ComponentBase):
+class Card(ContainerBase):
 
-    sProdID = "-//mulberrymail.com//Mulberry v4.0//EN"
-    sDomain = "mulberrymail.com"
-
+    sContainerDescriptor = "vCard"
     sComponentType = None
     sPropertyType = Property
 
-    @staticmethod
-    def setPRODID(prodid):
-        Card.sProdID = prodid
-
-
-    @staticmethod
-    def setDomain(domain):
-        Card.sDomain = domain
-
     propertyCardinality_1 = (
         definitions.Property_VERSION,
         definitions.Property_N,
@@ -62,13 +48,6 @@
 
     propertyValueChecks = VCARD_VALUE_CHECKS
 
-    def __init__(self, add_defaults=True):
-        super(Card, self).__init__()
-
-        if add_defaults:
-            self.addDefaultProperties()
-
-
     def duplicate(self):
         return super(Card, self).duplicate()
 
@@ -77,25 +56,6 @@
         return VCARD
 
 
-    def finalise(self):
-        pass
-
-
-    def validate(self, doFix=False, doRaise=False):
-        """
-        Validate the data in this component and optionally fix any problems. Return
-        a tuple containing two lists: the first describes problems that were fixed, the
-        second problems that were not fixed. Caller can then decide what to do with unfixed
-        issues.
-        """
-
-        # Optional raise behavior
-        fixed, unfixed = super(Card, self).validate(doFix)
-        if doRaise and unfixed:
-            raise ValidationError(";".join(unfixed))
-        return fixed, unfixed
-
-
     def sortedPropertyKeyOrder(self):
         return (
             Property_VERSION,
@@ -167,14 +127,13 @@
                 else:
 
                     # Parse parameter/value for top-level calendar item
-                    prop = Property()
-                    if prop.parse(line):
+                    prop = Property.parseText(line)
 
-                        # Check for valid property
-                        if not card.validProperty(prop):
-                            raise InvalidData("Invalid property", str(prop))
-                        else:
-                            card.addProperty(prop)
+                    # Check for valid property
+                    if not card.validProperty(prop):
+                        raise InvalidData("Invalid property", str(prop))
+                    else:
+                        card.addProperty(prop)
 
         # Check for truncated data
         if state != LOOK_FOR_VCARD:
@@ -183,111 +142,6 @@
         return results
 
 
-    @staticmethod
-    def parseText(data):
-
-        cal = Card(add_defaults=False)
-        if cal.parse(StringIO(data)):
-            return cal
-        else:
-            return None
-
-
-    def parse(self, ins):
-
-        result = False
-
-        self.setProperties({})
-
-        LOOK_FOR_VCARD = 0
-        GET_PROPERTY = 1
-
-        state = LOOK_FOR_VCARD
-
-        # Get lines looking for start of calendar
-        lines = [None, None]
-
-        while readFoldedLine(ins, lines):
-
-            line = lines[0]
-            if state == LOOK_FOR_VCARD:
-
-                # Look for start
-                if line == self.getBeginDelimiter():
-                    # Next state
-                    state = GET_PROPERTY
-
-                    # Indicate success at this point
-                    result = True
-
-                # Handle blank line
-                elif len(line) == 0:
-                    # Raise if requested, otherwise just ignore
-                    if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
-                        raise InvalidData("vCard data has blank lines")
-
-                # Unrecognized data
-                else:
-                    raise InvalidData("vCard data not recognized", line)
-
-            elif state == GET_PROPERTY:
-
-                # Look for end of object
-                if line == self.getEndDelimiter():
-
-                    # Finalise the current calendar
-                    self.finalise()
-
-                    # Change state
-                    state = LOOK_FOR_VCARD
-
-                # Blank line
-                elif len(line) == 0:
-                    # Raise if requested, otherwise just ignore
-                    if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
-                        raise InvalidData("vCard data has blank lines")
-
-                # Must be a property
-                else:
-
-                    # Parse parameter/value for top-level calendar item
-                    prop = Property()
-                    try:
-                        if prop.parse(line):
-
-                            # Check for valid property
-                            if not self.validProperty(prop):
-                                raise InvalidData("Invalid property", str(prop))
-                            else:
-                                self.addProperty(prop)
-                    except IndexError:
-                        print line
-
-        # Check for truncated data
-        if state != LOOK_FOR_VCARD:
-            raise InvalidData("vCard data not complete", "")
-
-        # Validate some things
-        if result and not self.hasProperty("VERSION"):
-            raise InvalidData("vCard missing VERSION", "")
-
-        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/property.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/property.py	2013-08-19 01:31:02 UTC (rev 11614)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/property.py	2013-08-19 17:21:45 UTC (rev 11615)
@@ -209,7 +209,7 @@
         )
 
 
-    def parseParameters(self, txt, data):
+    def parseTextParameters(self, txt, data):
         """
         Parse parameters, return string point at value.
         """

Modified: PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_adrvalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_adrvalue.py	2013-08-19 01:31:02 UTC (rev 11614)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_adrvalue.py	2013-08-19 17:21:45 UTC (rev 11615)
@@ -56,7 +56,6 @@
         )
 
         for item, result in items:
-            prop = Property()
-            prop.parse(item)
+            prop = Property.parseText(item)
             test = prop.getText()
             self.assertEqual(test, result + "\r\n", "Failed to parse and re-generate '%s'" % (item,))

Modified: PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_nvalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_nvalue.py	2013-08-19 01:31:02 UTC (rev 11614)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_nvalue.py	2013-08-19 17:21:45 UTC (rev 11615)
@@ -53,7 +53,6 @@
         )
 
         for item, result in items:
-            prop = Property()
-            prop.parse(item)
+            prop = Property.parseText(item)
             test = prop.getText()
             self.assertEqual(test, result + "\r\n", "Failed to parse and re-generate '%s'" % (item,))

Modified: PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_orgvalue.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_orgvalue.py	2013-08-19 01:31:02 UTC (rev 11614)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_orgvalue.py	2013-08-19 17:21:45 UTC (rev 11615)
@@ -47,7 +47,6 @@
         )
 
         for item, result in items:
-            prop = Property()
-            prop.parse(item)
+            prop = Property.parseText(item)
             test = prop.getText()
             self.assertEqual(test, result + "\r\n", "Failed to parse and re-generate '%s'" % (item,))

Modified: PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_property.py
===================================================================
--- PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_property.py	2013-08-19 01:31:02 UTC (rev 11614)
+++ PyCalendar/branches/json-2/src/pycalendar/vcard/tests/test_property.py	2013-08-19 17:21:45 UTC (rev 11615)
@@ -41,8 +41,7 @@
     def testParseGenerate(self):
 
         for data in TestProperty.test_data:
-            prop = Property()
-            prop.parse(data)
+            prop = Property.parseText(data)
             propstr = str(prop)
             self.assertEqual(propstr[:-2], data, "Failed parse/generate: %s to %s" % (data, propstr,))
 
@@ -50,10 +49,8 @@
     def testEquality(self):
 
         for data in TestProperty.test_data:
-            prop1 = Property()
-            prop1.parse(data)
-            prop2 = Property()
-            prop2.parse(data)
+            prop1 = Property.parseText(data)
+            prop2 = Property.parseText(data)
             self.assertEqual(prop1, prop2, "Failed equality: %s" % (data,))
 
 
@@ -66,8 +63,7 @@
         save = ParserContext.INVALID_ESCAPE_SEQUENCES
         for data in test_bad_data:
             ParserContext.INVALID_ESCAPE_SEQUENCES = ParserContext.PARSER_RAISE
-            prop = Property()
-            self.assertRaises(InvalidProperty, prop.parse, data)
+            self.assertRaises(InvalidProperty, Property.parseText, data)
         ParserContext.INVALID_ESCAPE_SEQUENCES = save
 
 
@@ -75,8 +71,7 @@
 
         hashes = []
         for item in TestProperty.test_data:
-            prop = Property()
-            prop.parse(item)
+            prop = Property.parseText(item)
             hashes.append(hash(prop))
         hashes.sort()
         for i in range(1, len(hashes)):
@@ -107,12 +102,10 @@
         self.assertEqual(str(prop), "X-FOO;X-BAR=^'Check^';X-BAR2=Check^nThis\tOut^n:Test\r\n")
 
         data = "X-FOO;X-BAR=^'Check^':Test"
-        prop = Property()
-        prop.parse(data)
+        prop = Property.parseText(data)
         self.assertEqual(prop.getParameterValue("X-BAR"), "\"Check\"")
 
         data = "X-FOO;X-BAR=^'Check^';X-BAR2=Check^nThis\tOut^n:Test"
-        prop = Property()
-        prop.parse(data)
+        prop = Property.parseText(data)
         self.assertEqual(prop.getParameterValue("X-BAR"), "\"Check\"")
         self.assertEqual(prop.getParameterValue("X-BAR2"), "Check\nThis\tOut\n")
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20130819/6d5c230c/attachment-0001.html>


More information about the calendarserver-changes mailing list