2.1 --- a/EventAggregatorSupport/Types.py Tue Jan 28 01:46:59 2014 +0100
2.2 +++ b/EventAggregatorSupport/Types.py Tue Jan 28 23:30:33 2014 +0100
2.3 @@ -28,6 +28,13 @@
2.4 except NameError:
2.5 from sets import Set as set
2.6
2.7 +# Import libxml2dom for xCalendar parsing.
2.8 +
2.9 +try:
2.10 + import libxml2dom
2.11 +except ImportError:
2.12 + libxml2dom = None
2.13 +
2.14 # Page parsing.
2.15
2.16 definition_list_regexp = re.compile(ur'(?P<wholeterm>^(?P<optcomment>#*)\s+(?P<term>.*?):: )(?P<desc>.*?)$', re.UNICODE | re.MULTILINE)
2.17 @@ -46,6 +53,11 @@
2.18 if page.getFormat() == "calendar":
2.19 return parseEventsInCalendar(text)
2.20
2.21 + # xCalendar-format pages are parsed directly by the iCalendar parser.
2.22 +
2.23 + elif page.getFormat() == "xcalendar":
2.24 + return parseEventsInXMLCalendar(text)
2.25 +
2.26 # Wiki-format pages are parsed region-by-region using the special markup.
2.27
2.28 elif page.getFormat() == "wiki":
2.29 @@ -78,6 +90,18 @@
2.30 calendar = parseEventsInCalendarFromResource(StringIO(text.encode(encoding)), encoding)
2.31 return calendar.getEvents()
2.32
2.33 +def parseEventsInXMLCalendar(text):
2.34 +
2.35 + """
2.36 + Parse events in xCalendar format from the given 'text'.
2.37 + """
2.38 +
2.39 + # Fill the StringIO with encoded plain string data.
2.40 +
2.41 + encoding = "utf-8"
2.42 + calendar = parseEventsInXMLCalendarFromResource(StringIO(text.encode(encoding)), encoding)
2.43 + return calendar.getEvents()
2.44 +
2.45 def parseEventsInCalendarFromResource(f, encoding=None, url=None, metadata=None):
2.46
2.47 """
2.48 @@ -94,6 +118,19 @@
2.49 finally:
2.50 uf.close()
2.51
2.52 +def parseEventsInXMLCalendarFromResource(f, encoding=None, url=None, metadata=None):
2.53 +
2.54 + """
2.55 + Parse events in xCalendar format from the given file-like object 'f', with
2.56 + content having any specified 'encoding' and being described by the given
2.57 + 'url' and 'metadata'.
2.58 + """
2.59 +
2.60 + if libxml2dom is not None:
2.61 + return EventXMLCalendar(url or "", libxml2dom.parse(f), metadata or {})
2.62 + else:
2.63 + return None
2.64 +
2.65 def parseEvents(text, event_page, fragment=None):
2.66
2.67 """
2.68 @@ -254,13 +291,12 @@
2.69
2.70 return fmt.text(text)
2.71
2.72 -class EventCalendar(EventResource):
2.73 -
2.74 - "An iCalendar resource."
2.75 +class EventCalendarResource(EventResource):
2.76
2.77 - def __init__(self, url, calendar, metadata):
2.78 + "A generic calendar resource."
2.79 +
2.80 + def __init__(self, url, metadata):
2.81 EventResource.__init__(self, url)
2.82 - self.calendar = calendar
2.83 self.metadata = metadata
2.84 self.events = None
2.85
2.86 @@ -280,6 +316,24 @@
2.87
2.88 return self.metadata
2.89
2.90 +class EventCalendar(EventCalendarResource):
2.91 +
2.92 + "An iCalendar resource."
2.93 +
2.94 + def __init__(self, url, calendar, metadata):
2.95 + EventCalendarResource.__init__(self, url, metadata)
2.96 + self.calendar = calendar
2.97 +
2.98 + def getMetadata(self):
2.99 +
2.100 + """
2.101 + Return a dictionary containing items describing the page's "created"
2.102 + time, "last-modified" time, "sequence" (or revision number) and the
2.103 + "last-comment" made about the last edit.
2.104 + """
2.105 +
2.106 + return self.metadata
2.107 +
2.108 def getEvents(self):
2.109
2.110 "Return a list of events from this resource."
2.111 @@ -348,6 +402,110 @@
2.112
2.113 return self.events
2.114
2.115 +class EventXMLCalendar(EventCalendarResource):
2.116 +
2.117 + "An xCalendar resource."
2.118 +
2.119 + XCAL = {"xcal" : "urn:ietf:params:xml:ns:icalendar-2.0"}
2.120 +
2.121 + # See: http://tools.ietf.org/html/draft-daboo-et-al-icalendar-in-xml-11#section-3.4
2.122 +
2.123 + properties = [
2.124 + ("summary", "xcal:properties/xcal:summary", "getText"),
2.125 + ("location", "xcal:properties/xcal:location", "getText"),
2.126 + ("start", "xcal:properties/xcal:dtstart", "getDateTime"),
2.127 + ("end", "xcal:properties/xcal:dtend", "getDateTime"),
2.128 + ("created", "xcal:properties/xcal:created", "getDateTime"),
2.129 + ("dtstamp", "xcal:properties/xcal:dtstamp", "getDateTime"),
2.130 + ("last-modified", "xcal:properties/xcal:last-modified", "getDateTime"),
2.131 + ("sequence", "xcal:properties/xcal:sequence", "getInteger"),
2.132 + ("categories", "xcal:properties/xcal:categories", "getCollection"),
2.133 + ("geo", "xcal:properties/xcal:geo", "getGeo"),
2.134 + ("url", "xcal:properties/xcal:url", "getURI"),
2.135 + ]
2.136 +
2.137 + def __init__(self, url, doc, metadata):
2.138 + EventCalendarResource.__init__(self, url, metadata)
2.139 + self.doc = doc
2.140 +
2.141 + def getEvents(self):
2.142 +
2.143 + "Return a list of events from this resource."
2.144 +
2.145 + if self.events is None:
2.146 + self.events = []
2.147 +
2.148 + for event in self.doc.xpath("//xcal:vevent", namespaces=self.XCAL):
2.149 + details = {}
2.150 +
2.151 + for property, path, converter in self.properties:
2.152 + values = event.xpath(path, namespaces=self.XCAL)
2.153 +
2.154 + try:
2.155 + value = getattr(self, converter)(property, values)
2.156 + details[property] = value
2.157 + except (IndexError, ValueError):
2.158 + pass
2.159 +
2.160 + self.events.append(CalendarEvent(self, details))
2.161 +
2.162 + return self.events
2.163 +
2.164 + def _getValue(self, values, type):
2.165 + for element in values[0].xpath("xcal:%s" % type, namespaces=self.XCAL):
2.166 + return element.textContent
2.167 + else:
2.168 + return None
2.169 +
2.170 + def getText(self, property, values):
2.171 + return self._getValue(values, "text")
2.172 +
2.173 + def getDateTime(self, property, values):
2.174 + element = values[0]
2.175 + for dtelement in element.xpath("xcal:date-time|xcal:date", namespaces=self.XCAL):
2.176 + dt = getDateTimeFromISO8601(dtelement.textContent)
2.177 + break
2.178 + else:
2.179 + return None
2.180 +
2.181 + tzid = self._getValue(element.xpath("xcal:parameters", namespaces=self.XCAL), "tzid")
2.182 + if tzid and isinstance(dt, DateTime):
2.183 + zone = "/".join(tzid.rsplit("/", 2)[-2:])
2.184 + dt.set_time_zone(zone)
2.185 +
2.186 + if dtelement.localName == "date" and property == "end":
2.187 + dt = dt.previous_day()
2.188 +
2.189 + return dt
2.190 +
2.191 + def getInteger(self, property, values):
2.192 + value = self._getValue(values, "integer")
2.193 + if value is not None:
2.194 + return int(value)
2.195 + else:
2.196 + return None
2.197 +
2.198 + def getCollection(self, property, values):
2.199 + return [n.textContent for n in values[0].xpath("xcal:text", namespaces=self.XCAL)]
2.200 +
2.201 + def getGeo(self, property, values):
2.202 + geo = [None, None]
2.203 +
2.204 + for geoelement in values[0].xpath("xcal:latitude|xcal:longitude", namespaces=self.XCAL):
2.205 + value = geoelement.textContent
2.206 + if geoelement.localName == "latitude":
2.207 + geo[0] = value
2.208 + else:
2.209 + geo[1] = value
2.210 +
2.211 + if None not in geo:
2.212 + return map(getMapReferenceFromDecimal, geo)
2.213 + else:
2.214 + return None
2.215 +
2.216 + def getURI(self, property, values):
2.217 + return self._getValue(values, "uri")
2.218 +
2.219 class EventPage:
2.220
2.221 "An event page acting as an event resource."
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/parsers/xcalendar.py Tue Jan 28 23:30:33 2014 +0100
3.3 @@ -0,0 +1,83 @@
3.4 +# -*- coding: iso-8859-1 -*-
3.5 +"""
3.6 + MoinMoin - xcalendar (EventAggregator)
3.7 +
3.8 + @copyright: 2012, 2013, 2014 by Paul Boddie <paul@boddie.org.uk>
3.9 + @license: GNU GPL (v2 or later), see COPYING.txt for details.
3.10 +"""
3.11 +
3.12 +from MoinSupport import parseAttributes, RawParser
3.13 +from EventAggregatorSupport.Formatting import formatEventsForOutputType, \
3.14 + formatEvent
3.15 +from EventAggregatorSupport.Types import parseEventsInPage, EventPage, \
3.16 + parseEventsInXMLCalendar
3.17 +
3.18 +Dependencies = ["pages"]
3.19 +
3.20 +# Parser support.
3.21 +
3.22 +class Parser:
3.23 +
3.24 + "Interpret and show calendar information in different ways."
3.25 +
3.26 + Dependencies = Dependencies
3.27 + extensions = [".xcs"]
3.28 +
3.29 + # Input content types understood by this parser.
3.30 +
3.31 + input_mimetypes = ["application/calendar+xml"]
3.32 +
3.33 + # Output content types preferred by this parser.
3.34 +
3.35 + output_mimetypes = ["text/html", "application/calendar+xml"]
3.36 +
3.37 + def __init__(self, raw, request, **kw):
3.38 +
3.39 + """
3.40 + Initialise the parser with the given 'raw' data, 'request' and any
3.41 + keyword arguments that may have been supplied.
3.42 + """
3.43 +
3.44 + self.raw = raw
3.45 + self.request = request
3.46 + attrs = parseAttributes(kw.get("format_args", ""), False)
3.47 +
3.48 + self.fragment = attrs.get("fragment")
3.49 +
3.50 + def format(self, fmt, write=None):
3.51 +
3.52 + """
3.53 + Format a calendar using the given formatter 'fmt'. If the 'write'
3.54 + parameter is specified, use it to write output; otherwise, write output
3.55 + using the request.
3.56 + """
3.57 +
3.58 + for event in parseEventsInXMLCalendar(self.raw):
3.59 + formatEvent(event, self.request, fmt, write=write, parser_cls=RawParser)
3.60 +
3.61 + # Extra API methods.
3.62 +
3.63 + def formatForOutputType(self, mimetype, write=None):
3.64 +
3.65 + """
3.66 + Format a calendar for the given 'mimetype'. If the 'write' parameter is
3.67 + specified, use it to write output; otherwise, write output using the
3.68 + request.
3.69 + """
3.70 +
3.71 + # Write raw calendar information unchanged.
3.72 +
3.73 + if mimetype == "application/calendar+xml":
3.74 + (write or request.write)(self.raw)
3.75 + else:
3.76 + events = parseEventsInXMLCalendar(self.raw)
3.77 + formatEventsForOutputType(events, self.request, mimetype, write=write)
3.78 +
3.79 + # Class methods.
3.80 +
3.81 + def getOutputTypes(self):
3.82 + return self.output_mimetypes
3.83 +
3.84 + getOutputTypes = classmethod(getOutputTypes)
3.85 +
3.86 +# vim: tabstop=4 expandtab shiftwidth=4