1.1 --- a/EventAggregatorSupport/Types.py Tue Jan 28 01:46:59 2014 +0100
1.2 +++ b/EventAggregatorSupport/Types.py Tue Jan 28 23:30:33 2014 +0100
1.3 @@ -28,6 +28,13 @@
1.4 except NameError:
1.5 from sets import Set as set
1.6
1.7 +# Import libxml2dom for xCalendar parsing.
1.8 +
1.9 +try:
1.10 + import libxml2dom
1.11 +except ImportError:
1.12 + libxml2dom = None
1.13 +
1.14 # Page parsing.
1.15
1.16 definition_list_regexp = re.compile(ur'(?P<wholeterm>^(?P<optcomment>#*)\s+(?P<term>.*?):: )(?P<desc>.*?)$', re.UNICODE | re.MULTILINE)
1.17 @@ -46,6 +53,11 @@
1.18 if page.getFormat() == "calendar":
1.19 return parseEventsInCalendar(text)
1.20
1.21 + # xCalendar-format pages are parsed directly by the iCalendar parser.
1.22 +
1.23 + elif page.getFormat() == "xcalendar":
1.24 + return parseEventsInXMLCalendar(text)
1.25 +
1.26 # Wiki-format pages are parsed region-by-region using the special markup.
1.27
1.28 elif page.getFormat() == "wiki":
1.29 @@ -78,6 +90,18 @@
1.30 calendar = parseEventsInCalendarFromResource(StringIO(text.encode(encoding)), encoding)
1.31 return calendar.getEvents()
1.32
1.33 +def parseEventsInXMLCalendar(text):
1.34 +
1.35 + """
1.36 + Parse events in xCalendar format from the given 'text'.
1.37 + """
1.38 +
1.39 + # Fill the StringIO with encoded plain string data.
1.40 +
1.41 + encoding = "utf-8"
1.42 + calendar = parseEventsInXMLCalendarFromResource(StringIO(text.encode(encoding)), encoding)
1.43 + return calendar.getEvents()
1.44 +
1.45 def parseEventsInCalendarFromResource(f, encoding=None, url=None, metadata=None):
1.46
1.47 """
1.48 @@ -94,6 +118,19 @@
1.49 finally:
1.50 uf.close()
1.51
1.52 +def parseEventsInXMLCalendarFromResource(f, encoding=None, url=None, metadata=None):
1.53 +
1.54 + """
1.55 + Parse events in xCalendar format from the given file-like object 'f', with
1.56 + content having any specified 'encoding' and being described by the given
1.57 + 'url' and 'metadata'.
1.58 + """
1.59 +
1.60 + if libxml2dom is not None:
1.61 + return EventXMLCalendar(url or "", libxml2dom.parse(f), metadata or {})
1.62 + else:
1.63 + return None
1.64 +
1.65 def parseEvents(text, event_page, fragment=None):
1.66
1.67 """
1.68 @@ -254,13 +291,12 @@
1.69
1.70 return fmt.text(text)
1.71
1.72 -class EventCalendar(EventResource):
1.73 -
1.74 - "An iCalendar resource."
1.75 +class EventCalendarResource(EventResource):
1.76
1.77 - def __init__(self, url, calendar, metadata):
1.78 + "A generic calendar resource."
1.79 +
1.80 + def __init__(self, url, metadata):
1.81 EventResource.__init__(self, url)
1.82 - self.calendar = calendar
1.83 self.metadata = metadata
1.84 self.events = None
1.85
1.86 @@ -280,6 +316,24 @@
1.87
1.88 return self.metadata
1.89
1.90 +class EventCalendar(EventCalendarResource):
1.91 +
1.92 + "An iCalendar resource."
1.93 +
1.94 + def __init__(self, url, calendar, metadata):
1.95 + EventCalendarResource.__init__(self, url, metadata)
1.96 + self.calendar = calendar
1.97 +
1.98 + def getMetadata(self):
1.99 +
1.100 + """
1.101 + Return a dictionary containing items describing the page's "created"
1.102 + time, "last-modified" time, "sequence" (or revision number) and the
1.103 + "last-comment" made about the last edit.
1.104 + """
1.105 +
1.106 + return self.metadata
1.107 +
1.108 def getEvents(self):
1.109
1.110 "Return a list of events from this resource."
1.111 @@ -348,6 +402,110 @@
1.112
1.113 return self.events
1.114
1.115 +class EventXMLCalendar(EventCalendarResource):
1.116 +
1.117 + "An xCalendar resource."
1.118 +
1.119 + XCAL = {"xcal" : "urn:ietf:params:xml:ns:icalendar-2.0"}
1.120 +
1.121 + # See: http://tools.ietf.org/html/draft-daboo-et-al-icalendar-in-xml-11#section-3.4
1.122 +
1.123 + properties = [
1.124 + ("summary", "xcal:properties/xcal:summary", "getText"),
1.125 + ("location", "xcal:properties/xcal:location", "getText"),
1.126 + ("start", "xcal:properties/xcal:dtstart", "getDateTime"),
1.127 + ("end", "xcal:properties/xcal:dtend", "getDateTime"),
1.128 + ("created", "xcal:properties/xcal:created", "getDateTime"),
1.129 + ("dtstamp", "xcal:properties/xcal:dtstamp", "getDateTime"),
1.130 + ("last-modified", "xcal:properties/xcal:last-modified", "getDateTime"),
1.131 + ("sequence", "xcal:properties/xcal:sequence", "getInteger"),
1.132 + ("categories", "xcal:properties/xcal:categories", "getCollection"),
1.133 + ("geo", "xcal:properties/xcal:geo", "getGeo"),
1.134 + ("url", "xcal:properties/xcal:url", "getURI"),
1.135 + ]
1.136 +
1.137 + def __init__(self, url, doc, metadata):
1.138 + EventCalendarResource.__init__(self, url, metadata)
1.139 + self.doc = doc
1.140 +
1.141 + def getEvents(self):
1.142 +
1.143 + "Return a list of events from this resource."
1.144 +
1.145 + if self.events is None:
1.146 + self.events = []
1.147 +
1.148 + for event in self.doc.xpath("//xcal:vevent", namespaces=self.XCAL):
1.149 + details = {}
1.150 +
1.151 + for property, path, converter in self.properties:
1.152 + values = event.xpath(path, namespaces=self.XCAL)
1.153 +
1.154 + try:
1.155 + value = getattr(self, converter)(property, values)
1.156 + details[property] = value
1.157 + except (IndexError, ValueError):
1.158 + pass
1.159 +
1.160 + self.events.append(CalendarEvent(self, details))
1.161 +
1.162 + return self.events
1.163 +
1.164 + def _getValue(self, values, type):
1.165 + for element in values[0].xpath("xcal:%s" % type, namespaces=self.XCAL):
1.166 + return element.textContent
1.167 + else:
1.168 + return None
1.169 +
1.170 + def getText(self, property, values):
1.171 + return self._getValue(values, "text")
1.172 +
1.173 + def getDateTime(self, property, values):
1.174 + element = values[0]
1.175 + for dtelement in element.xpath("xcal:date-time|xcal:date", namespaces=self.XCAL):
1.176 + dt = getDateTimeFromISO8601(dtelement.textContent)
1.177 + break
1.178 + else:
1.179 + return None
1.180 +
1.181 + tzid = self._getValue(element.xpath("xcal:parameters", namespaces=self.XCAL), "tzid")
1.182 + if tzid and isinstance(dt, DateTime):
1.183 + zone = "/".join(tzid.rsplit("/", 2)[-2:])
1.184 + dt.set_time_zone(zone)
1.185 +
1.186 + if dtelement.localName == "date" and property == "end":
1.187 + dt = dt.previous_day()
1.188 +
1.189 + return dt
1.190 +
1.191 + def getInteger(self, property, values):
1.192 + value = self._getValue(values, "integer")
1.193 + if value is not None:
1.194 + return int(value)
1.195 + else:
1.196 + return None
1.197 +
1.198 + def getCollection(self, property, values):
1.199 + return [n.textContent for n in values[0].xpath("xcal:text", namespaces=self.XCAL)]
1.200 +
1.201 + def getGeo(self, property, values):
1.202 + geo = [None, None]
1.203 +
1.204 + for geoelement in values[0].xpath("xcal:latitude|xcal:longitude", namespaces=self.XCAL):
1.205 + value = geoelement.textContent
1.206 + if geoelement.localName == "latitude":
1.207 + geo[0] = value
1.208 + else:
1.209 + geo[1] = value
1.210 +
1.211 + if None not in geo:
1.212 + return map(getMapReferenceFromDecimal, geo)
1.213 + else:
1.214 + return None
1.215 +
1.216 + def getURI(self, property, values):
1.217 + return self._getValue(values, "uri")
1.218 +
1.219 class EventPage:
1.220
1.221 "An event page acting as an event resource."