1 | ;;+----------------------------------------------------------------------------- |
---|
2 | ;;+ Isidorus |
---|
3 | ;;+ (c) 2008-2009 Marc Kuester, Christoph Ludwig, Lukas Giessmann |
---|
4 | ;;+ |
---|
5 | ;;+ Isidorus is freely distributable under the LGPL license. |
---|
6 | ;;+ You can find a detailed description in trunk/docs/LGPL-LICENSE.txt. |
---|
7 | ;;+----------------------------------------------------------------------------- |
---|
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 |
---|
76 | in 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*)) |
---|