;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-USER; Base: 10 -*- ;;; $Header: src/translators.lisp $ ;;; Copyright (c) 2008, Andrea Chiumenti. All rights reserved. ;;; Redistribution and use in source and binary forms, with or without ;;; modification, are permitted provided that the following conditions ;;; are met: ;;; * Redistributions of source code must retain the above copyright ;;; notice, this list of conditions and the following disclaimer. ;;; * Redistributions in binary form must reproduce the above ;;; copyright notice, this list of conditions and the following ;;; disclaimer in the documentation and/or other materials ;;; provided with the distribution. ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. (in-package :claw) (defmethod translator-value-encode ((translator translator) value) (format nil "~a" value)) (defmethod translator-value-type-to-string ((translator translator) value) (translator-value-encode translator value)) (defmethod translator-encode ((translator translator) (wcomponent base-cinput)) (let* ((page (htcomponent-page wcomponent)) (visit-object (or (cinput-visit-object wcomponent) page)) (accessor (cinput-accessor wcomponent)) (reader (cinput-reader wcomponent)) (value (page-req-parameter page (name-attr wcomponent) nil))) (if (component-validation-errors wcomponent) value (progn (setf value (cond ((and (null reader) accessor) (funcall (fdefinition accessor) visit-object)) (t (funcall (fdefinition reader) visit-object)))) (translator-value-encode translator value))))) (defmethod translator-type-to-string ((translator translator) (wcomponent cinput)) (translator-encode translator wcomponent)) (defmethod translator-value-decode ((translator translator) value &optional client-id label) (declare (ignore client-id label)) value) (defmethod translator-value-string-to-type ((translator translator) value &optional client-id label) (translator-value-decode translator value client-id label)) (defmethod translator-decode ((translator translator) (wcomponent wcomponent)) (multiple-value-bind (client-id value) (component-id-and-value wcomponent) (translator-value-decode translator value client-id (label wcomponent)))) (defmethod translator-string-to-type ((translator translator) (wcomponent wcomponent)) (translator-decode translator wcomponent)) (setf *simple-translator* (make-instance 'translator)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;; Integer translator ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defclass translator-integer (translator) ((thousand-separator :initarg :thousand-separator :reader translator-thousand-separator :documentation "If specified (as character), it is the thousands separator. Despite of its name, grouping is done following the TRANSLATOR-GROUPING-SIZE, so it's not a real 'tousands' separator") (always-show-signum :initarg :always-show-signum :reader translator-always-show-signum :documentation "When true the signum is used also for displaying positive numbers.") (grouping-size :initarg :grouping-size :reader translator-grouping-size :documentation "Used only if TRANSLATOR-THOUSAND-SEPARATOR is defined. Default to 3")) (:default-initargs :thousand-separator nil :grouping-size 3 :always-show-signum nil) (:documentation "A translator object encodes and decodes integer values passed to a html input component")) (defmethod translator-value-encode ((translator translator-integer) value) (let* ((grouping-size (translator-grouping-size translator)) (thousand-separator (translator-thousand-separator translator)) (signum-directive (if (translator-always-show-signum translator) "@" "")) (control-string (if thousand-separator (format nil "~~~d,',v:~aD" grouping-size signum-directive) (format nil "~~~ad" signum-directive)))) (if thousand-separator (string-trim " " (format nil control-string thousand-separator value)) (format nil control-string value)))) (defmethod translator-value-decode ((translator translator-integer) value &optional client-id label) (let ((thousand-separator (translator-thousand-separator translator))) (handler-case (if thousand-separator (parse-integer (regex-replace-all (format nil "~a" thousand-separator) value "")) (parse-integer value)) (error () (progn (when label (add-exception client-id (format nil (do-message "VALIDATE-INTEGER" "Field ~a is not a valid integer.") label))) value))))) (defvar *integer-translator* (make-instance 'translator-integer)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;Folating point number translator ;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defclass translator-number (translator-integer) ((decimals-separator :initarg :decimals-separator :reader translator-decimals-separator :documentation "The decimal separator of the rendered number. Default to #\.") (decimal-digits :initarg :decimal-digits :reader translator-decimal-digits :documentation "force the rendering of the value to a fixed number of decimal digits") (coerce :initarg :coerce :accessor translator-coerce :documentation "Coerces the decoded input value to the given value type")) (:default-initargs :decimals-separator #\. :decimal-digits nil :coerce 'ratio) (:documentation "a translator object encodes and decodes integer values passed to a html input component")) (defmethod translator-value-encode ((translator translator-number) value) (let* ((thousand-separator (translator-thousand-separator translator)) (grouping-size (translator-grouping-size translator)) (decimal-digits (translator-decimal-digits translator)) (decimals-separator (translator-decimals-separator translator)) (signum-directive (if (translator-always-show-signum translator) "@" "")) (integer-control-string (if thousand-separator (format nil "~~~d,',v:~aD" grouping-size signum-directive) (format nil "~~~ad" signum-directive)))) (multiple-value-bind (int-value dec-value) (floor value) (setf dec-value (coerce dec-value 'float)) (format nil "~a~a" (if thousand-separator (string-trim " " (format nil integer-control-string thousand-separator int-value)) (format nil integer-control-string int-value)) (cond ((and (= 0.0 (coerce dec-value 'double-float)) decimal-digits) (format nil "~a~a" decimals-separator (make-string decimal-digits :initial-element #\0))) (decimal-digits (let ((frac-part (subseq (format nil "~f" dec-value) 2))) (if (> (length frac-part) decimal-digits) (setf frac-part (subseq frac-part 0 decimal-digits)) (setf frac-part (concatenate 'string frac-part (make-string (- decimal-digits (length frac-part)) :initial-element #\0)))) (format nil "~a~a" decimals-separator frac-part))) (t (format nil "~a~a" decimals-separator (subseq (format nil "~f" dec-value) 2)))))))) (defmethod translator-value-decode ((translator translator-number) value &optional client-id label) (let ((thousand-separator (translator-thousand-separator translator)) (type (translator-coerce translator)) (new-value)) (if thousand-separator (setf new-value (regex-replace-all (format nil "~a" thousand-separator) value "")) (setf new-value value)) (handler-case (let* ((decomposed-string (all-matches-as-strings "[0-9]+" new-value)) (int-value (parse-integer (concatenate 'string (first decomposed-string) (second decomposed-string)))) (dec-value (expt 10 (length (second decomposed-string)))) (result (/ int-value dec-value))) (if (integerp result) result (coerce result type))) (error () (progn (when label (add-exception client-id (format nil (do-message "VALIDATE-NUMBER" "Field ~a is not a valid number.") label))) value))))) (defvar *number-translator* (make-instance 'translator-number)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;; Dates translator ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defclass translator-date (translator) ((local-time-format :initarg :local-time-format :reader translator-local-time-format :documentation "Sets the format of a date using a list where element are joined together and :DATE :MONTH and :YEAR are expanded into day of the month for :DATE, month number for :MONTH and the year for :YEAR. The Default is the list '(:month \"/\" :date \"/\" :year)")) (:default-initargs :local-time-format '(:year "-" :month "-" :date)) (:documentation "A translator object encodes and decodes local-date object value passed to a html input component. When decoding the input compoenent value string to a local-time instance if the date is expressed in a wrong format or is not valid, a localizable message \"Field ~a is not a valid date or wrong format: ~a\" is sent with key \"VALIDATE-DATE\". The argument for the message will be the :label attribute of the COMPONENT and the input component string value.")) (defmethod translator-value-encode ((translator translator-date) value) (let* ((local-time-format (translator-local-time-format translator))) (if (and value (not (stringp value))) (local-time-to-string value local-time-format) value))) (defmethod translator-value-decode ((translator translator-date) value &optional client-id label) (let ((date-format (translator-local-time-format translator)) (sec 0) (min 0) (hour 0) (day 1) (month 1) (year 0) (old-value)) (when (and value (string-not-equal value "")) (setf old-value value) (loop for element in date-format do (if (stringp element) (setf value (subseq value (length element))) (ccase element (:second (multiple-value-bind (curr-value size) (parse-integer value :junk-allowed t) (setf value (subseq value size)) (setf sec curr-value))) (:minute (multiple-value-bind (curr-value size) (parse-integer value :junk-allowed t) (setf value (subseq value size)) (setf min curr-value))) (:hour (multiple-value-bind (curr-value size) (parse-integer value :junk-allowed t) (setf value (subseq value size)) (setf hour curr-value))) (:date (multiple-value-bind (curr-value size) (parse-integer value :junk-allowed t) (setf value (subseq value size)) (setf day curr-value))) (:month (multiple-value-bind (curr-value size) (parse-integer value :junk-allowed t) (setf value (subseq value size)) (setf month curr-value))) (:year (multiple-value-bind (curr-value size) (parse-integer value :junk-allowed t) (setf value (subseq value size)) (setf year curr-value)))))) (if (and (string-equal value "") (>= sec 0) (>= min 0) (>= hour 0) (and (> month 0) (<= month 12)) (and (> day 0) (<= day (days-in-month month year)))) (encode-local-time 0 sec min hour day month year) (progn (when label (add-exception client-id (format nil (do-message "VALIDATE-DATE" "Field ~a is not a valid date or wrong format: ~a") label old-value))) value))))) (defvar *date-translator-ymd* (make-instance 'translator-date)) (defvar *date-translator-time* (make-instance 'translator-date :local-time-format '("T" :hour ":" :minute ":" :second))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;; Boolean translator ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defclass translator-boolean (translator) () (:documentation "a translator object encodes and decodes boolean values passed to a html input component")) (defmethod translator-value-encode ((translator translator-boolean) value) (format nil "~a" value)) (defmethod translator-value-decode ((translator translator-boolean) value &optional client-id label) (if (string-equal value "NIL") nil t)) (defvar *boolean-translator* (make-instance 'translator-boolean)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;; File translator ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defclass translator-file (translator) () (:documentation "a translator object encodes and decodes file values passed to a html input component of type file")) (defmethod translator-value-encode ((translator translator-file) value) (cond ((null value) "") ((stringp value) value) ((pathnamep value) (format nil "~a.~a" (pathname-name value) (pathname-type value))) (t (second value)))) (defmethod translator-value-decode ((translator translator-file) value &optional client-id label) value) (setf *file-translator* (make-instance 'translator-file))