root/trunk/projects/bos/web/dictionary.lisp

Revision 3660, 5.9 kB (checked in by hans, 4 months ago)

Use TRUENAME for XML file to parse in as cxml does not support logical
path names.

Line 
1 (in-package :bos.web)
2
3 (defvar *dictionary* (make-hash-table :test #'equal))
4 (defvar *dictionary-last-reads-alist* nil)
5 (defvar *dictionary-keys* nil
6   "An alist of key compile-file-pathnames pairs that is
7   automatically build at compile-time from all occurences of
8   DICTIONARY-ENTRY in user-code.")
9
10 (defmacro dictionary-directory ()
11   "The xml files containing dictionary definitions for a
12   particular language are stored under
13   (dictionary-directory)/<language>/dictionary.xml."
14   '*website-directory*)
15
16 (deftype dictionary-language ()
17   'keyword)
18
19 (deftype dictionary-language-designator ()
20   '(or string dictionary-language))
21
22 (defun dictionary-language (language-designator)
23   (declare (dictionary-language-designator language-designator))
24   (typecase language-designator
25     (string (intern (string-upcase language-designator) #.(find-package "KEYWORD")))
26     (keyword language-designator)))
27
28 (defun dictionary-key-occurences (key)
29   (cdr (assoc key *dictionary-keys* :test #'string=)))
30
31 (defun (setf dictionary-key-occurences) (value key)
32   (let ((cons (assoc key *dictionary-keys* :test #'string=)))
33     (if cons
34         (rplacd cons value)
35         (push (cons key value) *dictionary-keys*))))
36
37 (defun %dictionary-entry (key language)
38   (let ((language (dictionary-language language)))
39     (load-dictionary-if-needed language)
40     (or (cdr (assoc language (gethash key *dictionary*)))
41         key)))
42
43 (defun (setf %dictionary-entry) (value key language)
44   (let* ((language (dictionary-language language))
45          (it (assoc language (gethash key *dictionary*))))
46     (if it
47         (rplacd it value)
48         (push (cons language value) (gethash key *dictionary*)))))
49
50 (defmacro dictionary-entry (key language)
51   (flet ((pathname-equal (a b)
52            (equal (namestring a) (namestring b))))
53     (when (constantp key)
54       (check-type key string)
55       (when *compile-file-pathname*
56         (pushnew *compile-file-pathname* (dictionary-key-occurences key)
57                  :test #'pathname-equal)))
58     `(%dictionary-entry ,key ,language)))
59
60 (defun dictionary-clear-entries-by-language (language)
61   (declare (dictionary-language language))
62   (maphash (lambda (key value)
63              ;; (setf gethash) with key explicitly allowed by ANSI CL
64              (setf (gethash key *dictionary*)
65                    (remove language value :key #'car)))
66            *dictionary*))
67
68 (defun dictionary-last-read (language)
69   (declare (dictionary-language language))
70   (let ((time (cdr (assoc language *dictionary-last-reads-alist*))))
71     (if time
72         time
73         0)))
74
75 (defun (setf dictionary-last-read) (value language)
76   (declare (dictionary-language language))
77   (let ((cons (assoc language *dictionary-last-reads-alist*)))
78     (if cons
79         (rplacd cons value)
80         (push (cons language value) *dictionary-last-reads-alist*))))
81
82 (defun dictionary-xml-path (language)
83   (declare (dictionary-language language))
84   (merge-pathnames (make-pathname :name "dictionary"
85                                   :type "xml"
86                                   :directory (list :relative "templates" (string-downcase (string language))))
87                    (dictionary-directory)))
88
89 (defun load-dictionary (language xml-path)
90   (declare (dictionary-language language))
91   (labels ((trim-whitespace (string)
92              (string-trim '(#\space #\newline #\tab) string))
93            (load-language (language xml-path)
94              (handler-case
95                  (let ((xmls (cxml:parse-file (truename xml-path) (cxml-xmls:make-xmls-builder))))
96                    (assert (equal "dictionary" (cxml-xmls:node-name xmls)) nil
97                            "root element should be \"dictionary\"")
98                    (dolist (element (cxml-xmls:node-children xmls))
99                      (when (consp element)
100                        (assert (equal "entry" (cxml-xmls:node-name element)) nil
101                                "expected element \"entry\"")
102                        (let ((key-value (remove-if #'atom (cxml-xmls:node-children element))))
103                          (assert (equal "key" (cxml-xmls:node-name (first key-value))) nil
104                                  "expected element \"key\"")
105                          (assert (equal "value" (cxml-xmls:node-name (second key-value))) nil
106                                  "expected element \"value\"")
107                          (let ((key (first (cxml-xmls:node-children (first key-value))))
108                                (value (first (cxml-xmls:node-children (second key-value)))))
109                            (when value
110                              (let ((key (trim-whitespace key))
111                                    (value (trim-whitespace value)))
112                                (assert (stringp key))
113                                (assert (stringp value))
114                                (unless (zerop (length value))
115                                  (setf (%dictionary-entry key language) value)))))))))
116                (error (c)
117                  (error "Error while loading ~a:~%~a"
118                         (enough-namestring xml-path (dictionary-directory)) c)))))
119     (dictionary-clear-entries-by-language language)
120     (load-language language xml-path)
121     (setf (dictionary-last-read language) (get-universal-time))
122     *dictionary*))
123
124 (defun load-dictionary-if-needed (language)
125   (declare (dictionary-language language))
126   (let ((xml-path (dictionary-xml-path language)))
127     (when (> (file-write-date xml-path)
128              (dictionary-last-read language))
129       (load-dictionary language xml-path))))
130
131 (defun dictionary-write-template (&optional (stream *standard-output*))
132   (cxml:with-xml-output (make-character-stream-sink stream :canonical nil :indentation 2)
133     (with-element "dictionary"
134       (loop for (key . paths) in (sort (copy-list *dictionary-keys*) #'string< :key #'car)
135          do (cxml:comment (format nil " in ~A ~{~%~8T~A  ~}" (first paths) (rest paths)))
136          do (with-element "entry"
137               (with-element "key" (text key))
138               (with-element "value" (text "")))))))
Note: See TracBrowser for help on using the browser.