source: trunk/src/atom/atom.lisp

Last change on this file was 328, checked in by lgiessmann, 14 years ago

fixed ticket #75 --> changed license terms from LGPL to LLGPL in the trunk tree

  • Property svn:eol-style set to native
File size: 6.7 KB
Line 
1;;+-----------------------------------------------------------------------------
2;;+  Isidorus
3;;+  (c) 2008-2010 Marc Kuester, Christoph Ludwig, Lukas Georgieff
4;;+
5;;+  Isidorus is freely distributable under the LLGPL license.
6;;+  You can find a detailed description in trunk/docs/LLGPL-LICENSE.txt and
7;;+  trunk/docs/LGPL-LICENSE.txt.
8;;+-----------------------------------------------------------------------------
9
10(defpackage :atom
11  (:use :cl :cxml :constants :xml-tools :datamodel :drakma :isidorus-threading)
12  (:export :collection-feed
13           :defsite
14           :dependency
15           :entries
16           :feed
17           :feed-to-elem
18           :feed-to-string
19           :fragments-feed
20           :id
21           :link
22           :parse-fragments-feed
23           :parse-snapshots-feed
24           :path
25           :psi
26           :snapshots-feed
27           :source-locator-prefix
28           :subfeeds
29           :tm-id
30           :tm-id-p
31           :updated
32           :*base-url*
33           :*tm-feed*))
34
35(in-package :atom)
36
37;;General configuration options
38(defparameter *base-url* "") ;*base-url* is set by hunchentoot
39(defvar *author*)
40(defvar *source-locator*)
41(defparameter *tm-feed* nil)
42(defvar *testtm-snapshotfeed*)
43(defvar *testtm-fragmentfeed*)
44(defvar *testtm-feed*)
45(defvar *testtm-toplevel*)
46(defvar *testtm-snapshots-feed*)
47(defvar *testtm-fragments-feed*)
48
49(defun datetime-in-iso-format (&optional (seconds-since-epoch (get-universal-time)))
50  "Formats a time (seconds since epoch) in ISO format. If no parameter
51  is given, return the current time in ISO format"
52  (multiple-value-bind
53        (second minute hour date month year day daylight-p zone)
54      (decode-universal-time seconds-since-epoch)
55    (declare (ignore day))
56    (let*
57        ((offset
58          (if daylight-p 0 -1))
59         (timezone
60          (if (>= (+ zone offset) 0)
61              (format nil "+~2,'0d:00" zone)
62              (format nil "-~2,'0d:00" (abs zone))))) 
63         (format nil "~d-~2,'0d-~2,'0dT~2,'0d:~2,'0d:~2,'0d~@d"
64                 year month date hour minute second timezone))))
65
66(defun get-most-recent-datetime ()
67  "Gets the most datetime of the most recent revision"
68  (datetime-in-iso-format (apply #'max (get-all-revisions))))
69
70(defun get-most-recent-datetime-for-tm (tm-id)
71  "Gets the most datetime of the most recent revision for a given TM"
72  (datetime-in-iso-format (apply #'max (get-all-revisions-for-tm tm-id))))
73
74(defun to-link (href rel &optional application-type)
75  "Generates an Atom link element. The function is expected to be used
76in the in-feed macro"
77  (cxml:with-element "a:link"
78    (cxml:attribute "rel" rel)
79    ;goes around a stupid IE bug
80    (when application-type
81      (cxml:attribute "type" application-type))
82    (cxml:attribute "href" href)))
83
84(defun to-elem (qname text)
85  (cxml:with-element qname (if (typep text 'string) (cxml:text text) (format nil "~a" text))))
86
87(defmacro in-feed ((title subtitle author link) &body body)
88  "Builds the element structure for the outer elements of an Atom feed. Expects a list of entry elements in the body"
89  `(cxml:with-namespace ("a" *atom-ns*)
90     (cxml:with-namespace ("e" *egovpt-ns*)
91       (cxml:with-element "a:feed"
92         (to-elem "a:title" ,title)
93         (when ,subtitle
94           (to-elem "a:subtitle" ,subtitle))
95         (to-elem "a:id" ,link)
96         (cxml:with-element "a:author"
97           (to-elem "a:name" ,author))
98         (to-link ,link "self")
99         ,@body))))
100
101(defmacro feed-to-string (&body body)
102  "Serialize a feed as a string"
103  `(cxml:with-xml-output (cxml:make-string-sink :canonical t)
104    ,@body))
105
106(defclass atom-element () 
107  ((title :accessor title :initarg :title)
108   (updated :accessor updated :initarg :updated :initform nil)
109   (id :accessor id :initarg :id :initform (error "An atom element must always have an id"))
110   (path :accessor path :initarg :path)
111   (link :initarg :link)))
112
113(defgeneric link (ae)
114  (:documentation "calculate the link of an atom element (entry or feed). The link can be realized as a self link (for feeds) or an alternate link (for entries)"))
115
116(defmethod link ((ae atom-element))
117  (if (slot-boundp ae 'link)
118      (slot-value ae 'link)
119      (format nil "~a/~a" *base-url* (path ae))))
120
121(defclass feed (atom-element)
122  ((subtitle :accessor subtitle :initarg :subtitle :initform nil)
123   (author :accessor author :initarg :author)   
124   (entries :accessor entries :initarg :entries :initform nil
125            :type list)
126   (subfeeds :accessor subfeeds :initarg :subfeeds :initform nil
127             :type list))
128  (:documentation "abstract class for atom feeds"))
129
130
131(defclass entry (atom-element) 
132  ()
133  (:documentation "Class that represents a minimalistic entry in an
134  Atom feed. Concrete classes implement the correct behaviour for the
135  individual entry types in the Atom protocol"))
136
137(defgeneric register-entry (feed entry)
138  (:documentation "Register an entry for a given feed"))
139
140(defmethod register-entry ((feed feed) (entry entry))
141  (push entry (slot-value feed 'entries)))
142
143(defgeneric register-subfeed (feed subfeed)
144  (:documentation "Register a subfeed for a given feed"))
145
146(defmethod register-subfeed ((feed feed) (subfeed feed))
147  (push subfeed (subfeeds feed)))
148
149(defgeneric entry-to-elem (entry)
150  (:documentation "build an entry element"))
151
152(defmethod entry-to-elem :around ((entry entry))
153  (cxml:with-namespace ("a" *atom-ns*)
154    (cxml:with-namespace ("e" *egovpt-ns*)
155      (cxml:with-element "a:entry"
156        (to-elem "a:title" (title entry))
157        (to-elem "a:id" (id entry))
158        (to-link (link entry) "alternate") ;this version of the alternate link works around an IE bug
159        (call-next-method)
160        (to-elem "a:updated" (updated entry))))))
161
162(defmethod entry-to-elem ((entry entry))
163  ;do nothing
164  )
165
166(defmethod id ((entry entry))
167  ;TODO: consider to revisit that convention
168  (format nil "~a/~a" (slot-value entry 'id) "entry"))
169
170(defclass overview-entry (entry)
171  ((author :accessor author :initarg :author))
172  (:documentation "Class that represents an entry for a collection in an overview feed"))
173
174(defmethod entry-to-elem ((entry overview-entry))
175  (to-link (link entry) "alternate" "application/atom+xml")
176  (cxml:with-element "a:author"
177    (to-elem "a:name" (author entry)))
178  (setf (updated entry) (get-most-recent-datetime-for-tm (slot-value entry 'id)))
179  (to-link (link entry) "http://www.egovpt.org/sdshare/collectionfeed" "application/atom+xml"))
180 
181(defgeneric feed-to-elem (feed)
182  (:documentation "Render an Atom feed to XML"))
183
184(defmethod feed-to-elem :around ((feed feed))
185   (in-feed
186       ((title feed) (subtitle feed) (author feed) (link feed))
187     (call-next-method)
188     (to-elem "a:updated" (updated feed))
189     (map 'list #'entry-to-elem (entries feed))))
190
191(defmethod feed-to-elem ((feed feed))
192  (setf (updated feed) (get-most-recent-datetime)))
193
194;; (defun build-overview-feed ()
195;;   (feed-to-elem *tm-feed*))
Note: See TracBrowser for help on using the repository browser.