Beispiel #1
0
        def _doDuplicateRoundtrip(caldata, result):
            card = Card()
            card.parse(StringIO.StringIO(caldata))
            card = card.duplicate()

            s = StringIO.StringIO()
            card.generate(s)
            test = s.getvalue()
            self.assertEqual(test, result, "\n".join(difflib.unified_diff(test.splitlines(), result.splitlines())))
Beispiel #2
0
        def _doDuplicateRoundtrip(caldata, result):
            card = Card()
            card.parse(StringIO.StringIO(caldata))
            card = card.duplicate()

            s = StringIO.StringIO()
            card.generate(s)
            test = s.getvalue()
            self.assertEqual(
                test, result, "\n".join(
                    difflib.unified_diff(test.splitlines(),
                                         result.splitlines())))
Beispiel #3
0
class Component (object):
    """
    X{vCard} component.
    """
    @classmethod
    def allFromString(clazz, string):
        """
        FIXME: Just default to reading a single VCARD - actually need more
        """
        if type(string) is unicode:
            string = string.encode("utf-8")
        else:
            # Valid utf-8 please
            string.decode("utf-8")
        
        # No BOMs please
        if string[:3] == codecs.BOM_UTF8:
            string = string[3:]

        return clazz.allFromStream(StringIO.StringIO(string))

    @classmethod
    def allFromStream(clazz, stream):
        """
        FIXME: Just default to reading a single VCARD - actually need more
        """
        try:
            results = Card.parseMultiple(stream)
        except PyCalendarError:
            results = None
        if not results:
            stream.seek(0)
            raise InvalidVCardDataError("%s" % (stream.read(),))
        return [clazz(None, pycard=result) for result in results]

    @classmethod
    def fromString(clazz, string):
        """
        Construct a L{Component} from a string.
        @param string: a string containing vCard data.
        @return: a L{Component} representing the first component described by
            C{string}.
        """
        if type(string) is unicode:
            string = string.encode("utf-8")
        else:
            # Valid utf-8 please
            string.decode("utf-8")
        
        # No BOMs please
        if string[:3] == codecs.BOM_UTF8:
            string = string[3:]

        return clazz.fromStream(StringIO.StringIO(string))

    @classmethod
    def fromStream(clazz, stream):
        """
        Construct a L{Component} from a stream.
        @param stream: a C{read()}able stream containing vCard data.
        @return: a L{Component} representing the first component described by
            C{stream}.
        """
        cal = Card()
        try:
            result = cal.parse(stream)
        except PyCalendarError:
            result = None
        if not result:
            stream.seek(0)
            raise InvalidVCardDataError("%s" % (stream.read(),))
        return clazz(None, pycard=cal)

    @classmethod
    def fromIStream(clazz, stream):
        """
        Construct a L{Component} from a stream.
        @param stream: an L{IStream} containing vCard data.
        @return: a deferred returning a L{Component} representing the first
            component described by C{stream}.
        """
        #
        # FIXME:
        #   This reads the request body into a string and then parses it.
        #   A better solution would parse directly and incrementally from the
        #   request stream.
        #
        def parse(data): return clazz.fromString(data)
        return allDataFromStream(IStream(stream), parse)

    def __init__(self, name, **kwargs):
        """
        Use this constructor to initialize an empty L{Component}.
        To create a new L{Component} from X{vCard} data, don't use this
        constructor directly.  Use one of the factory methods instead.
        @param name: the name (L{str}) of the X{iCalendar} component type for the
            component.
        """
        if name is None:
            if "pycard" in kwargs:
                pyobj = kwargs["pycard"]

                if pyobj is not None:
                    if not isinstance(pyobj, PyCalendarComponentBase):
                        raise TypeError("Not a PyCalendarComponentBase: %r" % (pyobj,))

                self._pycard = pyobj
            else:
                raise AssertionError("name may not be None")

            # FIXME: _parent is not use internally, and appears to be used elsewhere,
            # even though it's names as a private variable.
            if "parent" in kwargs:
                parent = kwargs["parent"]
                
                if parent is not None:
                    if not isinstance(parent, Component):
                        raise TypeError("Not a Component: %r" % (parent,))
                    
                self._parent = parent
            else:
                self._parent = None
        elif name == "VCARD":
            self._pycard = Card(add_defaults=False)
            self._parent = None
        else:
            raise ValueError("VCards have no child components")

    def __str__ (self): return str(self._pycard)
    def __repr__(self): return "<%s: %r>" % (self.__class__.__name__, str(self._pycard))

    def __hash__(self):
        return hash(str(self))

    def __ne__(self, other): return not self.__eq__(other)
    def __eq__(self, other):
        if not isinstance(other, Component):
            return False
        return self._pycard == other._pycard

    # FIXME: Should this not be in __eq__?
    def same(self, other):
        return self._pycard == other._pycard
    
    def name(self):
        """
        @return: the name of the iCalendar type of this component.
        """
        return self._pycard.getType()

    def duplicate(self):
        """
        Duplicate this object and all its contents.
        @return: the duplicated vcard.
        """
        return Component(None, pycard=self._pycard.duplicate())
        
    def hasProperty(self, name):
        """
        @param name: the name of the property whose existence is being tested.
        @return: True if the named property exists, False otherwise.
        """
        return self._pycard.hasProperty(name)

    def getProperty(self, name):
        """
        Get one property from the property list.
        @param name: the name of the property to get.
        @return: the L{Property} found or None.
        @raise: L{ValueError} if there is more than one property of the given name.
        """
        properties = tuple(self.properties(name))
        if len(properties) == 1: return properties[0]
        if len(properties) > 1: raise InvalidVCardDataError("More than one %s property in component %r" % (name, self))
        return None
        
    def properties(self, name=None):
        """
        @param name: if given and not C{None}, restricts the returned properties
            to those with the given C{name}.
        @return: an iterable of L{Property} objects, one for each property of
            this component.
        """
        properties = []
        if name is None:
            [properties.extend(i) for i in self._pycard.getProperties().values()]
        elif self._pycard.countProperty(name) > 0:
            properties = self._pycard.getProperties(name)

        return (
            Property(None, None, None, pycard=p)
            for p in properties
        )

    def propertyValue(self, name):
        properties = tuple(self.properties(name))
        if len(properties) == 1:
            return properties[0].value()
        if len(properties) > 1:
            raise InvalidVCardDataError("More than one %s property in component %r" % (name, self))
        return None


    def addProperty(self, property):
        """
        Adds a property to this component.
        @param property: the L{Property} to add to this component.
        """
        self._pycard.addProperty(property._pycard)
        self._pycard.finalise()

    def removeProperty(self, property):
        """
        Remove a property from this component.
        @param property: the L{Property} to remove from this component.
        """
        self._pycard.removeProperty(property._pycard)
        self._pycard.finalise()

    def replaceProperty(self, property):
        """
        Add or replace a property in this component.
        @param property: the L{Property} to add or replace in this component.
        """
        
        # Remove all existing ones first
        self._pycard.removeProperties(property.name())
        self.addProperty(property)

    def resourceUID(self):
        """
        @return: the UID of the subcomponents in this component.
        """
        assert self.name() == "VCARD", "Not a vcard: %r" % (self,)

        if not hasattr(self, "_resource_uid"):
            self._resource_uid = self.propertyValue("UID")

        return self._resource_uid

    def validVCardData(self, doFix=True, doRaise=True):
        """
        @return: tuple of fixed, unfixed issues
        @raise InvalidVCardDataError: if the given vcard data is not valid.
        """
        if self.name() != "VCARD":
            log.debug("Not a vcard: %s" % (self,))
            raise InvalidVCardDataError("Not a vcard")

        # Do underlying vCard library validation with data fix
        fixed, unfixed = self._pycard.validate(doFix=doFix)
        if unfixed:
            log.debug("vCard data had unfixable problems:\n  %s" % ("\n  ".join(unfixed),))
            if doRaise:
                raise InvalidVCardDataError("Calendar data had unfixable problems:\n  %s" % ("\n  ".join(unfixed),))
        if fixed:
            log.debug("vCard data had fixable problems:\n  %s" % ("\n  ".join(fixed),))
        
        return fixed, unfixed

    def validForCardDAV(self):
        """
        @raise ValueError: if the given vcard data is not valid.
        """
        if self.name() != "VCARD": raise InvalidVCardDataError("Not a vcard")

        version = self.propertyValue("VERSION")
        if version != "3.0":
            raise InvalidVCardDataError("Not a version 2.0 vCard (version=%s)" % (version,))

        uid = self.propertyValue("UID")
        if uid is None:
            raise InvalidVCardDataError("All vCards must have UIDs")
        
        # Control character check - only HTAB, CR, LF allowed for characters in the range 0x00-0x1F
        s = str(self)
        if len(s.translate(None, "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F")) != len(s):
            raise InvalidVCardDataError("vCard contains illegal control character")