1 ;;; -*- show-trailing-whitespace: t; indent-tabs-mode: nil -*-
3 ;;; Copyright (c) 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
10 ;;; * Redistributions of source code must retain the above copyright
11 ;;; notice, this list of conditions and the following disclaimer.
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.
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 (defstruct (decimal-format (:conc-name
"DF/"))
33 ;; picture string and output syntax:
34 (decimal-separator #\.
) ;active
35 (grouping-separator #\
,) ;active
36 (zero-digit #\
0) ;active
38 (per-mille (code-char #x2030
))
40 ;; picture string syntax only
42 (pattern-separator #\
;) ;active
44 ;; output syntax only:
49 (defstruct (picture (:conc-name
"PIC/"))
54 integer-part-grouping-positions
55 minimum-integer-part-size
56 fractional-part-grouping-positions
57 minimum-fractional-part-size
58 maximum-fractional-part-size
)
60 (defun df/active-characters
(df)
61 (format nil
"~C~C~C~C~C"
62 (df/decimal-separator df
)
63 (df/grouping-separator df
)
66 (df/pattern-separator df
)))
69 (let ((result (make-array 10))
70 (start (char-code (df/zero-digit df
))))
74 (setf (elt result i
) (code-char (+ start i
))))
77 (defun find-decimal-format (lname uri stylesheet
&optional
(errorp t
))
78 (or (gethash (cons lname uri
)
79 (stylesheet-decimal-formats stylesheet
))
81 (xslt-error "decimal format not found: ~A/~A" lname uri
))))
83 (defun (setf find-decimal-format
) (newval lname uri stylesheet
)
84 (setf (gethash (cons lname uri
)
85 (stylesheet-decimal-formats stylesheet
))
88 (defun decimal-format= (a b
)
89 (every (lambda (accessor)
90 (equal (funcall accessor a
)
91 (funcall accessor b
)))
92 (list #'df
/decimal-separator
93 #'df
/grouping-separator
98 #'df
/pattern-separator
103 (xpath-sys:define-xpath-function
/lazy
105 (value picture
&optional format-name
)
106 (let ((namespaces *namespaces
*))
110 (let ((qname (funcall format-name ctx
)))
111 (multiple-value-bind (local-name uri
)
112 (decode-qname/runtime qname namespaces nil
)
113 (find-decimal-format local-name
116 (find-decimal-format "" "" *stylesheet
*))))
117 (multiple-value-bind (pos neg
)
118 (parse-picture (xpath:string-value
(funcall picture ctx
)) df
)
119 (format-number (float (xpath:number-value
(funcall value ctx
))
125 (defun test-format-number (value picture
)
126 (let ((df (make-decimal-format)))
127 (multiple-value-bind (pos neg
)
128 (parse-picture picture df
)
129 (format-number value pos neg df
))))
131 (defun parse-picture (picture df
)
132 (destructuring-bind (&optional positive negative
&rest erroneous
)
133 (split-sequence:split-sequence
134 (df/pattern-separator df
)
136 (unless (and positive
(not erroneous
))
137 (xpath:xpath-error
"invalid pattern separators"))
139 (setf negative
(concatenate 'string
140 (string (df/minus-sign df
))
142 (values (parse-sub-picture positive df
)
143 (parse-sub-picture negative df
))))
145 (defmacro df
/case
(df form
&rest clauses
)
150 for
(accessor . body
) in clauses
151 collect
`((eql (,accessor .df
) .form
) ,@body
)))))
153 (defun parse-integer-picture (picture df start end
)
154 (let ((integer-part-grouping-positions '())
155 (minimum-integer-part-size 0)
158 for i from start below end
159 for c
= (elt picture i
)
160 until
(eql c
(df/decimal-separator df
))
163 (df/grouping-separator
164 (push 0 integer-part-grouping-positions
))
168 "digit not allowed after zero-digit in integer picture"))
169 (when integer-part-grouping-positions
170 (incf (car integer-part-grouping-positions
))))
172 (setf zero-digit-p t
)
173 (when integer-part-grouping-positions
174 (incf (car integer-part-grouping-positions
)))
175 (incf minimum-integer-part-size
)))
177 (when integer-part-grouping-positions
178 ;; zzz I wrote the above algorithm based on the XSLT 2.0 spec,
179 ;; only to find out that the test suite doesn't want
180 ;; multiple INTEGER-PART-GROUPING-POSITIONS. Sun says
181 ;; that only the last one is used:
182 ;; http://java.sun.com/j2se/1.3/docs/api/java/text/DecimalFormat.html
183 (setf integer-part-grouping-positions
184 (list (car integer-part-grouping-positions
))))
187 for pos in integer-part-grouping-positions
188 for accum
= pos then
(+ accum pos
)
190 minimum-integer-part-size
)))))
192 (defun parse-fractional-picture (picture df start end
)
193 (let ((fractional-part-grouping-positions '())
194 (minimum-fractional-part-size 0)
195 (maximum-fractional-part-size 0)
199 for i from start below end
200 for c
= (elt picture i
)
203 (df/grouping-separator
204 (push current-grouping fractional-part-grouping-positions
))
207 (incf current-grouping
)
208 (incf maximum-fractional-part-size
))
212 "zero-digit not allowed after digit in fractional picture"))
213 (incf current-grouping
)
214 (incf minimum-fractional-part-size
)
215 (incf maximum-fractional-part-size
))
216 (df/decimal-separator
))
218 (return (values (nreverse fractional-part-grouping-positions
)
219 minimum-fractional-part-size
220 maximum-fractional-part-size
)))))
222 (defun parse-sub-picture (picture df
)
223 (let ((active (df/active-characters df
)))
224 (flet ((activep (x) (find x active
)))
225 (let ((start (position-if #'activep picture
))
226 (last (position-if #'activep picture
:from-end t
)))
228 (xpath:xpath-error
"no digit-sign or zero-digit sign found"))
229 (let* ((end (1+ last
))
230 (result (make-picture
231 :percentp
(find (df/percent df
) picture
)
232 :per-mille-p
(find (df/per-mille df
) picture
)
233 :prefix
(subseq picture
0 start
)
234 :suffix
(subseq picture end
))))
236 (pic/integer-part-grouping-positions result
)
237 (pic/minimum-integer-part-size result
))
238 (parse-integer-picture picture df start end
))
239 (setf (values (pic/fractional-part-grouping-positions result
)
240 (pic/minimum-fractional-part-size result
)
241 (pic/maximum-fractional-part-size result
))
242 (parse-fractional-picture picture df start end
))
245 (defun format-number (value positive-picture negative-picture df
)
246 (if (xpath::nan-p value
)
248 (let ((picture (if (minusp value
) negative-picture positive-picture
)))
249 (if (xpath::inf-p value
)
253 (pic/suffix picture
))
254 (format-ordinary-number value picture df
)))))
256 (defun format-number-~f
(number picture df
)
257 (let* ((str (format nil
"~,vF"
258 (pic/maximum-fractional-part-size picture
)
260 (str (string-trim (string (df/zero-digit df
)) str
)) ;for 0.0
261 (digits (df/digits df
)))
265 (df/decimal-separator df
)
266 (elt digits
(- (char-code x
) #.
(char-code #\
0)))))
269 (defun make-grouping-test (positions)
271 (let ((first (car positions
)))
273 for expected
= first then
(+ expected first
)
275 always
(eql pos expected
))))
276 (let ((first (car positions
)))
278 (and (plusp x
) (zerop (mod x first
)))))
280 (and (plusp x
) (find x positions
)))))
282 (defun format-ordinary-number (value picture df
)
283 (let* ((adjusted-number
285 ((pic/percentp picture
)
287 ((pic/per-mille-p picture
)
291 (str (format-number-~f
(abs adjusted-number
) picture df
))
292 (left (position (df/decimal-separator df
) str
))
293 (right (1- (- (length str
) left
)))
294 (wanted-left (max left
(pic/minimum-integer-part-size picture
)))
295 (wanted-right (max right
(pic/minimum-fractional-part-size picture
)))
296 (zero (df/zero-digit df
))
297 (left-test (make-grouping-test
298 (pic/integer-part-grouping-positions picture
)))
299 (right-test (make-grouping-test
300 (pic/fractional-part-grouping-positions picture
))))
301 (with-output-to-string (s)
302 (write-string (pic/prefix picture
) s
)
304 for i from
(1- wanted-left
) downto
0
305 for index from
(- left wanted-left
)
308 (write-char (elt str index
) s
)
310 (when (funcall left-test i
)
311 (write-char (df/grouping-separator df
) s
)))
312 (when (plusp wanted-right
)
313 (write-char (df/decimal-separator df
) s
)
315 for i from
0 below wanted-right
316 for index from
(+ left
1)
318 (when (funcall right-test i
)
319 (write-char (df/grouping-separator df
) s
))
321 (write-char (elt str index
) s
)
322 (write-char zero s
))))
323 (write-string (pic/suffix picture
) s
))))