1##
2#    Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved.
3#
4#    Licensed under the Apache License, Version 2.0 (the "License");
5#    you may not use this file except in compliance with the License.
6#    You may obtain a copy of the License at
7#
8#        http://www.apache.org/licenses/LICENSE-2.0
9#
10#    Unless required by applicable law or agreed to in writing, software
11#    distributed under the License is distributed on an "AS IS" BASIS,
12#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13#    See the License for the specific language governing permissions and
14#    limitations under the License.
15##
16
17from cStringIO import StringIO
18from pycalendar.componentbase import PyCalendarComponentBase
19from pycalendar.exceptions import PyCalendarInvalidData
20from pycalendar.parser import ParserContext
21from pycalendar.utils import readFoldedLine
22from pycalendar.vcard import definitions
23from pycalendar.vcard.definitions import VCARD, Property_VERSION,\
24    Property_PRODID, Property_UID
25from pycalendar.vcard.property import Property
26
27class Card(PyCalendarComponentBase):
28
29    sProdID = "-//mulberrymail.com//Mulberry v4.0//EN"
30    sDomain = "mulberrymail.com"
31
32    @staticmethod
33    def setPRODID(prodid):
34        Card.sProdID = prodid
35
36    @staticmethod
37    def setDomain(domain):
38        Card.sDomain = domain
39
40    def __init__(self, add_defaults=True):
41        super(Card, self).__init__()
42
43        if add_defaults:
44            self.addDefaultProperties()
45
46    def duplicate(self):
47        return super(Card, self).duplicate()
48
49    def getType(self):
50        return VCARD
51
52    def finalise(self):
53        pass
54
55    def sortedPropertyKeyOrder(self):
56        return (
57            Property_VERSION,
58            Property_PRODID,
59            Property_UID,
60        )
61
62    @staticmethod
63    def parseMultiple(ins):
64
65        results = []
66
67        card = Card(add_defaults=False)
68
69        LOOK_FOR_VCARD = 0
70        GET_PROPERTY = 1
71
72        state = LOOK_FOR_VCARD
73
74        # Get lines looking for start of calendar
75        lines = [None, None]
76
77        while readFoldedLine(ins, lines):
78
79            line = lines[0]
80            if state == LOOK_FOR_VCARD:
81
82                # Look for start
83                if line == card.getBeginDelimiter():
84                    # Next state
85                    state = GET_PROPERTY
86
87                # Handle blank line
88                elif len(line) == 0:
89                    # Raise if requested, otherwise just ignore
90                    if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
91                        raise PyCalendarInvalidData("vCard data has blank lines")
92
93                # Unrecognized data
94                else:
95                    raise PyCalendarInvalidData("vCard data not recognized", line)
96
97            elif state == GET_PROPERTY:
98
99                # Look for end of object
100                if line == card.getEndDelimiter():
101
102                    # Finalise the current calendar
103                    card.finalise()
104
105                    # Validate some things
106                    if not card.hasProperty("VERSION"):
107                        raise PyCalendarInvalidData("vCard missing VERSION", "")
108
109                    results.append(card)
110
111                    # Change state
112                    card = Card(add_defaults=False)
113                    state = LOOK_FOR_VCARD
114
115                # Blank line
116                elif len(line) == 0:
117                    # Raise if requested, otherwise just ignore
118                    if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
119                        raise PyCalendarInvalidData("vCard data has blank lines")
120
121                # Must be a property
122                else:
123
124                    # Parse attribute/value for top-level calendar item
125                    prop = Property()
126                    if prop.parse(line):
127
128                        # Check for valid property
129                        if not card.validProperty(prop):
130                            raise PyCalendarInvalidData("Invalid property", str(prop))
131                        else:
132                            card.addProperty(prop)
133
134        # Check for truncated data
135        if state != LOOK_FOR_VCARD:
136            raise PyCalendarInvalidData("vCard data not complete")
137
138        return results
139
140    @staticmethod
141    def parseText(data):
142
143        cal = Card(add_defaults=False)
144        if cal.parse(StringIO(data)):
145            return cal
146        else:
147            return None
148
149    def parse(self, ins):
150
151        result = False
152
153        self.setProperties({})
154
155        LOOK_FOR_VCARD = 0
156        GET_PROPERTY = 1
157
158        state = LOOK_FOR_VCARD
159
160        # Get lines looking for start of calendar
161        lines = [None, None]
162
163        while readFoldedLine(ins, lines):
164
165            line = lines[0]
166            if state == LOOK_FOR_VCARD:
167
168                # Look for start
169                if line == self.getBeginDelimiter():
170                    # Next state
171                    state = GET_PROPERTY
172
173                    # Indicate success at this point
174                    result = True
175
176                # Handle blank line
177                elif len(line) == 0:
178                    # Raise if requested, otherwise just ignore
179                    if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
180                        raise PyCalendarInvalidData("vCard data has blank lines")
181
182                # Unrecognized data
183                else:
184                    raise PyCalendarInvalidData("vCard data not recognized", line)
185
186            elif state == GET_PROPERTY:
187
188                # Look for end of object
189                if line == self.getEndDelimiter():
190
191                    # Finalise the current calendar
192                    self.finalise()
193
194                    # Change state
195                    state = LOOK_FOR_VCARD
196
197                # Blank line
198                elif len(line) == 0:
199                    # Raise if requested, otherwise just ignore
200                    if ParserContext.BLANK_LINES_IN_DATA == ParserContext.PARSER_RAISE:
201                        raise PyCalendarInvalidData("vCard data has blank lines")
202
203                # Must be a property
204                else:
205
206                    # Parse attribute/value for top-level calendar item
207                    prop = Property()
208                    try:
209                        if prop.parse(line):
210
211                            # Check for valid property
212                            if not self.validProperty(prop):
213                                raise PyCalendarInvalidData("Invalid property", str(prop))
214                            else:
215                                self.addProperty(prop)
216                    except IndexError:
217                        print line
218
219        # Check for truncated data
220        if state != LOOK_FOR_VCARD:
221            raise PyCalendarInvalidData("vCard data not complete", "")
222
223        # Validate some things
224        if result and not self.hasProperty("VERSION"):
225            raise PyCalendarInvalidData("vCard missing VERSION", "")
226
227        return result
228
229    def addDefaultProperties(self):
230        self.addProperty(Property(definitions.Property_PRODID, Card.sProdID))
231        self.addProperty(Property(definitions.Property_VERSION, "3.0"))
232
233    def validProperty(self, prop):
234        if prop.getName() == definitions.Property_VERSION:
235
236            tvalue = prop.getValue()
237            if ((tvalue is None) or (tvalue.getValue() != "3.0")):
238                return False
239
240        return True
241