xsl:number fixes: grouping-size
[xuriella.git] / number.lisp
blob988157507d0f4bf27940d7d8bed57f802ab37a7c
1 ;;; -*- show-trailing-whitespace: t; indent-tabs-mode: nil -*-
3 ;;; Copyright (c) 2007,2008 David Lichteblau, Ivan Shvedunov.
4 ;;; All rights reserved.
6 ;;; Redistribution and use in source and binary forms, with or without
7 ;;; modification, are permitted provided that the following conditions
8 ;;; are met:
9 ;;;
10 ;;; * Redistributions of source code must retain the above copyright
11 ;;; notice, this list of conditions and the following disclaimer.
12 ;;;
13 ;;; * Redistributions in binary form must reproduce the above
14 ;;; copyright notice, this list of conditions and the following
15 ;;; disclaimer in the documentation and/or other materials
16 ;;; provided with the distribution.
17 ;;;
18 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
19 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
22 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
24 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 (in-package :xuriella)
32 (define-instruction xsl:number (args env)
33 (destructuring-bind (&key level count from value format lang letter-value
34 grouping-separator grouping-size)
35 args
36 (let ((count (and count (compile-pattern count env)))
37 (from (and from (compile-pattern from env)))
38 (value (and value (compile-xpath value env)))
39 (format (compile-avt (or format "1") env))
40 (lang (compile-avt (or lang "") env))
41 (letter-value (compile-avt (or letter-value "foo") env))
42 (grouping-separator
43 (and grouping-separator (compile-avt grouping-separator env)))
44 (grouping-size (and grouping-size (compile-avt grouping-size env))))
45 (lambda (ctx)
46 (let ((value (when value
47 (round (xpath:number-value
48 (funcall value ctx)))))
49 (format (funcall format ctx))
50 (lang (funcall lang ctx))
51 (letter-value (funcall letter-value ctx))
52 (grouping-separator (when grouping-separator
53 (funcall grouping-separator ctx)))
54 (grouping-size (when grouping-size
55 (xpath:number-value
56 (funcall grouping-size ctx)))))
57 (write-text
58 (format-number-list
59 (if value
60 (list value)
61 (compute-number-list (or level "single")
62 (xpath::context-node ctx)
63 count
64 from))
65 format
66 lang
67 letter-value
68 grouping-separator
69 grouping-size)))))))
71 (defun compile-pattern (str env)
72 (compile-xpath
73 `(xpath:xpath
74 (:union
75 ,@(mapcar #'naive-pattern-expression (parse-pattern str))))
76 env))
78 (defun pattern-thunk-matches-p (pattern-thunk node)
79 (find node
80 (xpath:all-nodes (funcall pattern-thunk (xpath:make-context node)))))
82 (defun ancestors-using-count-and-from (node count from)
83 (let ((ancestors
84 (xpath::force
85 (funcall (xpath::axis-function :ancestor-or-self) node))))
86 (remove-if-not (lambda (ancestor)
87 (pattern-thunk-matches-p count ancestor))
88 (if from
89 (loop
90 for a in ancestors
91 when (pattern-thunk-matches-p from a)
92 do (return result)
93 collect a into result
94 finally (return nil))
95 ancestors))))
97 (defun node-position-among-siblings (node count)
98 (1+
99 (count-if (lambda (sibling)
100 (pattern-thunk-matches-p count sibling))
101 (xpath::force
102 (funcall (xpath::axis-function :preceding-sibling) node)))))
104 (defun compute-number-list (level node count from)
105 (unless count
106 (setf count
107 (let ((uri (xpath-protocol:namespace-uri node))
108 (lname (xpath-protocol:local-name node)))
109 (lambda (ctx)
110 (let ((node (xpath:context-node ctx)))
111 (xpath-sys:make-node-set
112 (if (and (xpath-protocol:node-type-p node :element)
113 (equal (xpath-protocol:namespace-uri node) uri)
114 (equal (xpath-protocol:local-name node) lname))
115 (list node)
116 nil)))))))
117 (cond
118 ((equal level "single")
119 (let ((ancestor (car (ancestors-using-count-and-from node count from))))
120 (if ancestor
121 (list (node-position-among-siblings ancestor count))
122 nil)))
123 ((equal level "multiple")
124 (mapcar (lambda (ancestor)
125 (node-position-among-siblings ancestor count))
126 (reverse
127 (ancestors-using-count-and-from node count from))))
128 ((equal level "any")
129 (destructuring-bind (root)
130 (xpath::force (funcall (xpath::axis-function :root) node))
131 (let ((nodes (xpath::force
132 (xpath::append-pipes
133 (xpath::subpipe-before
134 node
135 (funcall (xpath::axis-function :descendant-or-self) root))
136 (list node)))))
137 (when from
138 (loop
139 for (current . rest) on nodes
140 when (pattern-thunk-matches-p from current)
142 (setf nodes rest)))
143 (list
144 (loop
145 for n in nodes
146 count (pattern-thunk-matches-p count n))))))
148 (xslt-error "invalid number level: ~A" level))))
150 (xpath::deflexer (format-lexer :ignore-whitespace nil)
151 ("([a-zA-Z0-9]+)" (x) (values :format x))
152 ("([^a-zA-Z0-9]+)" (x) (values :text x)))
154 (defun format-number-token (str n)
155 (cond
156 ((or (equal str "a") (equal str "A"))
157 (let ((start (if (equal str "a") 96 64)))
158 (if (zerop n)
159 (code-char (1+ start))
160 (nreverse
161 (with-output-to-string (r)
162 (loop
163 for m = n then rest
164 while (plusp m)
165 for (rest digit) = (multiple-value-list
166 (truncate m 26))
168 (write-char (code-char (+ start digit)) r)))))))
169 ((equal str "i")
170 (format nil "~(~@R~)" n))
171 ((equal str "I")
172 (format nil "~@R" n))
174 (unless (cl-ppcre:all-matches "^0*1$" str)
175 ;; unsupported format
176 (setf str "1"))
177 (format nil "~v,'0D" (length str) n))))
179 (defun group-numbers (str separator size stream)
180 (loop
181 for c across str
182 for i from (1- (length str)) downto 0
184 (write-char c stream)
185 (when (and (zerop (mod i size)) (plusp i))
186 (write-string separator stream))))
188 ;;; fixme: unicode support
189 (defun format-number-list
190 (list format lang letter-value grouping-separator grouping-size)
191 (declare (ignore lang letter-value))
192 (multiple-value-bind (prefix pairs suffix)
193 (parse-number-format format)
194 (with-output-to-string (s)
195 (write-string prefix s)
196 (loop
197 for (separator . subformat) in pairs
198 for n in list
199 for formatted = (format-number-token subformat n)
201 (when separator
202 (write-string separator s))
203 (if (and grouping-separator
204 grouping-size)
205 (group-numbers formatted
206 grouping-separator
207 grouping-size
209 (write-string formatted s)))
210 (write-string suffix s))))
212 (defun parse-number-format (format)
213 (let ((lexer (format-lexer format))
214 (prefix "")
215 (conses '())
216 (suffix "")
217 (current-text nil))
218 (loop
219 (multiple-value-bind (type str) (funcall lexer)
220 (ecase type
221 ((nil :eof)
222 (return))
223 (:text
224 (if conses
225 (setf current-text str)
226 (setf prefix str)))
227 (:format
228 (push (cons (if conses
229 (or current-text ".")
230 nil)
231 str)
232 conses)
233 (setf current-text nil)))))
234 (when current-text
235 (setf suffix current-text))
236 (unless conses
237 (setf conses (list (cons nil "1"))))
238 (let ((tail conses))
239 (setf conses (nreverse conses))
240 (setf (cdr tail) tail))
241 (values prefix conses suffix)))