Fertsch.
[cxml-rng.git] / types.lisp
blob8e2b4df84c875cfffa7f933ecc7391dfa081766e
1 ;;; -*- show-trailing-whitespace: t; indent-tabs: nil -*-
2 ;;;
3 ;;; Copyright (c) 2007 David Lichteblau. All rights reserved.
5 ;;; Redistribution and use in source and binary forms, with or without
6 ;;; modification, are permitted provided that the following conditions
7 ;;; are met:
8 ;;;
9 ;;; * Redistributions of source code must retain the above copyright
10 ;;; notice, this list of conditions and the following disclaimer.
11 ;;;
12 ;;; * Redistributions in binary form must reproduce the above
13 ;;; copyright notice, this list of conditions and the following
14 ;;; disclaimer in the documentation and/or other materials
15 ;;; provided with the distribution.
16 ;;;
17 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
18 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
21 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 (in-package :cxml-types)
31 (defclass data-type () ()
32 (:documentation
33 "@short{The abstract superclass of all types.}
35 Each type belongs to a datatype library, named by a keyword. In each
36 library, the types are named by strings.
38 @see{find-type}
39 @see{type-name}
40 @see{type-library}
41 @see{type-context-dependent-p}
42 @see{parse}
43 @see{equal-using-type}
44 @see{validp}"))
46 (defgeneric find-type (library name &key &allow-other-keys)
47 (:documentation
48 "@arg[library]{datatype library, a keyword symbol}
49 @arg[name]{the type's name, a string}
50 @arg[args]{type parameters, strings named by keyword arguments}
51 @return{an instance of @class{data-type}, or @code{nil}}
52 @short{Look up the type named @em{name} in datatype library @em{library}.}
54 Return a type instance for this type and the additional parameters,
55 or @code{nil} if the type does not exist.
57 Additional parameters (knows as facets in XSD) can be passed to specify
58 or restrict the type for the purposes of @fun{validp}.
60 @see{data-type}"))
62 (defgeneric type-library (type)
63 (:documentation
64 "@arg[type]{an instance of @class{data-type}}
65 @return{library name, a keyword}
66 @short{Return the name of the library this type belongs to.}
68 @see{type-name}
69 @see{type-context-dependent-p}"))
71 (defgeneric type-name (type)
72 (:documentation
73 "@arg[type]{an instance of @class{data-type}}
74 @return{type name, a string}
75 @short{Return the name this type has within its library.}
77 @see{type-library}
78 @see{type-context-dependent-p}"))
80 (defmethod find-type ((library t) name &key &allow-other-keys)
81 (declare (ignore name))
82 nil)
84 (defgeneric type-context-dependent-p (type)
85 (:documentation
86 "@arg[type]{an instance of @class{data-type}}
87 @return{a boolean}
88 @short{Return true if parsing and validation of values by this type
89 depends on the validation context.}
91 In this case, the optional @code{context} argument to @fun{parse} and
92 @fun{validp} is required, and an error will be signalled if it is missing.
94 @see{type-name}
95 @see{type-library}
96 @see{type-context-dependent-p}"))
98 (defmethod type-context-dependent-p ((type data-type))
99 nil)
101 (defgeneric equal-using-type (type u v)
102 (:documentation
103 "@arg[type]{an instance of @class{data-type}}
104 @arg[u]{a parsed value as returned by @fun{parse}}
105 @arg[v]{a parsed value as returned by @fun{parse}}
106 @return{a boolean}
107 @short{Compare the @emph{values} @code{u} and @code{v} using a
108 data-type-dependent equality function.}
110 @see{parse}
111 @see{validp}"))
113 (defgeneric parse (type e &optional context)
114 (:documentation
115 "@arg[type]{an instance of @class{data-type}}
116 @arg[e]{a string}
117 @arg[context]{an instance of @class{validation-context}}
118 @return{an object}
119 @short{Parse string @code{e} and return a representation of its value
120 as defined by the data type.}
122 The @code{context} argument is required if @fun{type-context-dependent-p}
123 is true for @code{type}, and will be ignored otherwise.
125 @see{equal-using-type}
126 @see{validp}"))
128 (defgeneric validp (type e &optional context)
129 (:documentation
130 "@arg[type]{an instance of @class{data-type}}
131 @arg[e]{a string}
132 @arg[context]{an instance of @class{validation-context}}
133 @return{a boolean}
134 @short{Determine whether a string is a valid lexical representation
135 for a type.}
137 The @code{context} argument is required if @fun{type-context-dependent-p}
138 is true for @code{type}, and will be ignored otherwise.
140 @see{parse}
141 @see{equal-using-type}"))
144 ;;; Validation context
146 (defclass validation-context () ())
148 (defgeneric context-find-namespace-binding (context prefix)
149 (:documentation
150 "@arg[context]{an instance of @class{validation-context}}
151 @arg[prefix]{qname prefix, a string}
152 @return{the namespace URI as a string, or NIL}"))
154 (defclass klacks-validation-context (validation-context)
155 ((source :initarg :source :accessor context-source)))
157 (defun make-klacks-validation-context (source)
158 (make-instance 'klacks-validation-context :source source))
160 (defmethod context-find-namespace-binding
161 ((context klacks-validation-context) prefix)
162 (klacks:find-namespace-binding prefix (context-source context)))
164 (defclass sax-validation-context-mixin (validation-context)
165 ((stack :initform nil :accessor context-stack)))
167 (defmethod sax:start-prefix-mapping
168 ((handler sax-validation-context-mixin) prefix uri)
169 (push (cons prefix uri) (context-stack handler)))
171 (defmethod sax:end-prefix-mapping
172 ((handler sax-validation-context-mixin) prefix)
173 (setf (context-stack handler)
174 (remove prefix
175 (context-stack handler)
176 :count 1
177 :key #'car
178 :test #'equal)))
180 (defmethod context-find-namespace-binding
181 ((context sax-validation-context-mixin) prefix)
182 (cdr (assoc prefix (context-stack context) :test #'equal)))
185 ;;; Relax NG built-in type library
187 (defclass rng-type (data-type) ())
188 (defclass string-type (rng-type) ())
189 (defclass token-type (rng-type) ())
191 (defmethod type-library ((type rng-type))
192 :||)
194 (defvar *string-data-type* (make-instance 'string-type))
195 (defvar *token-data-type* (make-instance 'token-type))
197 (defmethod find-type ((library (eql :||)) name &rest args &key)
198 (cond
199 ((eq name :probe) t)
200 (args nil)
201 ((equal name "string") *string-data-type*)
202 ((equal name "token") *token-data-type*)
203 (t nil)))
205 (defmethod equal-using-type ((type rng-type) u v)
206 (equal u v))
208 (defmethod validp ((type rng-type) e &optional context)
209 (declare (ignore e context))
212 (defmethod type-name ((type string-type)) "string")
213 (defmethod type-name ((type token-type)) "token")
215 (defmethod parse ((type string-type) e &optional context)
216 (declare (ignore context))
219 (defmethod parse ((type token-type) e &optional context)
220 (declare (ignore context))
221 (normalize-whitespace e))
223 (eval-when (:compile-toplevel :load-toplevel :execute)
224 (defparameter *whitespace*
225 (format nil "~C~C~C~C"
226 (code-char 9)
227 (code-char 32)
228 (code-char 13)
229 (code-char 10))))
231 (defun normalize-whitespace (str)
232 (cl-ppcre:regex-replace-all #.(format nil "[~A]+" *whitespace*)
233 (string-trim *whitespace* str)
234 " "))
237 ;;; XML Schema Part 2: Datatypes Second Edition
239 (defclass xsd-type (data-type)
240 ((min-length :initarg :min-length :accessor min-length)
241 (max-length :initarg :max-length :accessor max-length)
242 (exact-length :initarg :exact-length :accessor exact-length)))
244 (defmethod initialize-instance ((instance xsd-type)
245 &rest args
246 &key ((:|minLength| min-length))
247 ((:|maxLength| max-length))
248 ((:|length| exact-length)))
249 (apply #'call-next-method
250 instance
251 :min-length (when min-length
252 ;; fixme: richtigen fehler
253 (parse-integer min-length))
254 :max-length (when max-length
255 ;; fixme: richtigen fehler
256 (parse-integer max-length))
257 :exact-length (when exact-length
258 ;; fixme: richtigen fehler
259 (parse-integer exact-length))
260 args))
262 (defmethod type-library ((type xsd-type))
263 :|http://www.w3.org/2001/XMLSchema-datatypes|)
265 (defmethod find-type
266 ((library (eql :|http://www.w3.org/2001/XMLSchema-datatypes|))
267 name
268 &rest args &key)
269 args ;fixme
270 (if (eq name :probe)
272 (let ((class
273 (case (find-symbol name :keyword)
274 (:|QName| 'qname-type)
275 (:|NCName| 'ncname-type)
276 (:|anyURI| 'any-uri-type)
277 (:|string| 'xsd-string-type)
278 (t nil))))
279 (if class
280 (apply #'make-instance class args)
281 nil))))
283 (defgeneric %parse (type e context))
285 (defmethod validp ((type xsd-type) e &optional context)
286 (not (eq :error (%parse type e context))))
288 (defmethod parse ((type xsd-type) e &optional context)
289 (let ((result (%parse type e context)))
290 (when (eq result :error)
291 (error "not valid for data type ~A: ~S" type e))
292 result))
295 ;;; QName
297 (defclass qname-type (xsd-type) ())
299 (defmethod type-name ((type qname-type))
300 "QName")
302 (defstruct (qname (:constructor make-qname (uri lname)))
304 lname)
306 (defmethod equal-using-type ((type qname-type) u v)
307 (and (equal (qname-uri u) (qname-uri v))
308 (equal (qname-lname u) (qname-lname v))))
310 (defun namep (str)
311 (and (not (zerop (length str)))
312 (cxml::name-start-rune-p (elt str 0))
313 (every #'cxml::name-rune-p str)))
315 (defmethod %parse ((type qname-type) e context)
316 (setf e (normalize-whitespace e))
317 (handler-case
318 (if (namep e)
319 (multiple-value-bind (prefix local-name) (cxml::split-qname e)
320 (let ((uri (when prefix
321 (context-find-namespace-binding context prefix))))
322 (if (and prefix (not uri))
323 :error
324 (make-qname uri local-name))))
325 :error)
326 (cxml:well-formedness-violation ()
327 :error)))
330 ;;; NCName
332 (defclass ncname-type (xsd-type) ())
334 (defmethod type-name ((type ncname-type))
335 "NCName")
337 (defmethod equal-using-type ((type ncname-type) u v)
338 (equal u v))
340 (defun nc-name-p (str)
341 (and (namep str) (cxml::nc-name-p str)))
343 (defmethod %parse ((type ncname-type) e context)
344 (setf e (normalize-whitespace e))
345 (if (nc-name-p e)
347 :error))
350 ;;; AnyURi
352 (defclass any-uri-type (xsd-type) ())
354 (defmethod type-name ((type any-uri-type))
355 "AnyURI")
357 (defmethod equal-using-type ((type any-uri-type) u v)
358 (equal u v))
360 (defmethod %parse ((type any-uri-type) e context)
361 (cxml-rng::escape-uri (normalize-whitespace e)))
364 ;;; string
366 (defclass xsd-string-type (xsd-type) ())
368 (defmethod type-name ((type xsd-string-type))
369 "string")
371 (defmethod equal-using-type ((type xsd-string-type) u v)
372 (equal u v))
374 (defmethod %parse ((type xsd-string-type) e context)
375 (if (or (and (min-length type) (< (length e) (min-length type)))
376 (and (max-length type) (> (length e) (max-length type)))
377 (and (exact-length type) (/= (length e) (exact-length type))))
378 :error