86361
|
1 ;;; rng-valid.el --- real-time validation of XML using RELAX NG
|
|
2
|
86555
|
3 ;; Copyright (C) 2003, 2007 Free Software Foundation, Inc.
|
86361
|
4
|
|
5 ;; Author: James Clark
|
|
6 ;; Keywords: XML, RelaxNG
|
|
7
|
86555
|
8 ;; This file is part of GNU Emacs.
|
|
9
|
|
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
|
|
11 ;; it under the terms of the GNU General Public License as published by
|
|
12 ;; the Free Software Foundation; either version 3, or (at your option)
|
|
13 ;; any later version.
|
86361
|
14
|
86555
|
15 ;; GNU Emacs is distributed in the hope that it will be useful,
|
|
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
18 ;; GNU General Public License for more details.
|
86361
|
19
|
86555
|
20 ;; You should have received a copy of the GNU General Public License
|
|
21 ;; along with GNU Emacs; see the file COPYING. If not, write to the
|
|
22 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
23 ;; Boston, MA 02110-1301, USA.
|
86361
|
24
|
|
25 ;;; Commentary:
|
|
26
|
|
27 ;; For usage information, see the documentation for rng-validate-mode.
|
|
28 ;;
|
|
29 ;; This file provides a minor mode that continually validates a buffer
|
|
30 ;; against a RELAX NG schema. The validation state is used to support
|
|
31 ;; schema-sensitive editing as well as validation. Validation is
|
|
32 ;; performed while Emacs is idle. XML parsing is done using
|
|
33 ;; xmltok.el. This file is responsible for checking that end-tags
|
|
34 ;; match their start-tags. Namespace processing is handled by
|
|
35 ;; nxml-ns.el. The RELAX NG Compact Syntax schema is parsed into
|
|
36 ;; internal form by rng-cmpct.el. This internal form is described by
|
|
37 ;; rng-pttrn.el. Validation of the document by matching against this
|
|
38 ;; internal form is done by rng-match.el. Handling of W3C XML Schema
|
|
39 ;; datatypes is delegated by rng-match.el to rng-xsd.el. The minor
|
|
40 ;; mode is intended to be used in conjunction with the nxml major
|
|
41 ;; mode, but does not have to be.
|
|
42 ;;
|
|
43 ;; The major responsibility of this file is to allow validation to
|
|
44 ;; happen incrementally. If a buffer has been validated and is then
|
|
45 ;; changed, we can often revalidate it without having to completely
|
|
46 ;; parse and validate it from start to end. As we parse and validate
|
|
47 ;; the buffer, we periodically cache the state. The state has three
|
|
48 ;; components: the stack of open elements, the namespace processing
|
|
49 ;; state and the RELAX NG validation state. The state is cached as the
|
|
50 ;; value of the rng-state text property on the closing greater-than of
|
|
51 ;; tags (but at intervals, not on every tag). We keep track of the
|
|
52 ;; position up to which cached state is known to be correct by adding
|
|
53 ;; a function to the buffer's after-change-functions. This is stored
|
|
54 ;; in the rng-validate-up-to-date-end variable. The first way in
|
|
55 ;; which we make validation incremental is obvious: we start
|
|
56 ;; validation from the first cached state before
|
|
57 ;; rng-validate-up-to-date-end.
|
|
58 ;;
|
|
59 ;; To make this work efficiently, we have to be able to copy the
|
|
60 ;; current parsing and validation state efficiently. We do this by
|
|
61 ;; minimizing destructive changes to the objects storing the state.
|
|
62 ;; When state is changed, we use the old state to create new objects
|
|
63 ;; representing the new state rather than destructively modifying the
|
|
64 ;; objects representing the old state. Copying the state is just a
|
|
65 ;; matter of making a list of three objects, one for each component of
|
|
66 ;; the state; the three objects themselves can be shared and do not
|
|
67 ;; need to be copied.
|
|
68 ;;
|
|
69 ;; There's one other idea that is used to make validation incremental.
|
|
70 ;; Suppose we have a buffer that's 4000 bytes long and suppose we
|
|
71 ;; validated it, caching state at positions 1000, 2000 and 3000. Now
|
|
72 ;; suppose we make a change at position 1500 inserting 100 characters.
|
|
73 ;; rng-validate-up-to-date-end will be changed to 1500. When Emacs
|
|
74 ;; becomes idle and we revalidate, validation will restart using the
|
|
75 ;; cached state at position 1000. However, we take advantage of the
|
|
76 ;; cached state beyond rng-validate-up-to-date-end as follows. When
|
|
77 ;; our validation reaches position 2100 (the current position of the
|
|
78 ;; character that was at 2000), we compare our current state with the
|
|
79 ;; cached state. If they are the same, then we can stop parsing
|
|
80 ;; immediately and set rng-validate-up-to-date-end to the end of the
|
|
81 ;; buffer: we already know that the state cached at position 3100 is
|
|
82 ;; correct. If they are not the same, then we have to continue
|
|
83 ;; parsing. After the change, but before revalidation, we call the
|
|
84 ;; region from 1600 to the end of the buffer "conditionally
|
|
85 ;; up-to-date".
|
|
86 ;;
|
|
87 ;; As well as the cached parsing and validation state, we also keep
|
|
88 ;; track of the errors in the file. Errors are stored as overlays
|
|
89 ;; with a category of rng-error. The number of such overlays in the
|
|
90 ;; buffer must always be equal to rng-error-count.
|
|
91
|
|
92 ;;; Code:
|
|
93
|
|
94 (require 'xmltok)
|
|
95 (require 'nxml-enc)
|
|
96 (require 'nxml-util)
|
|
97 (require 'nxml-ns)
|
|
98 (require 'rng-match)
|
|
99 (require 'rng-util)
|
|
100 (require 'rng-loc)
|
|
101
|
|
102 ;;; Customizable variables
|
|
103
|
|
104 (defgroup relax-ng nil
|
|
105 "Validation of XML using RELAX NG."
|
|
106 :group 'wp
|
|
107 :group 'nxml
|
|
108 :group 'languages)
|
|
109
|
|
110 (defface rng-error-face '((t (:underline "red")))
|
|
111 "Face for highlighting XML errors."
|
|
112 :group 'relax-ng)
|
|
113
|
|
114 (defcustom rng-state-cache-distance 2000
|
|
115 "*Distance in characters between each parsing and validation state cache."
|
|
116 :type 'integer
|
|
117 :group 'relax-ng)
|
|
118
|
|
119 (defcustom rng-validate-chunk-size 8000
|
|
120 "*Number of characters in a RELAX NG validation chunk.
|
|
121 A validation chunk will be the smallest chunk that is at least this
|
|
122 size and ends with a tag. After validating a chunk, validation will
|
|
123 continue only if Emacs is still idle."
|
|
124 :type 'integer
|
|
125 :group 'relax-ng)
|
|
126
|
|
127 (defcustom rng-validate-delay 1.5
|
|
128 "*Time in seconds that Emacs must be idle before starting a full validation.
|
|
129 A full validation continues until either validation is up to date
|
|
130 or Emacs is no longer idle."
|
|
131 :type 'number
|
|
132 :group 'relax-ng)
|
|
133
|
|
134 (defcustom rng-validate-quick-delay 0.3
|
|
135 "*Time in seconds that Emacs must be idle before starting a quick validation.
|
|
136 A quick validation validates at most one chunk."
|
|
137 :type 'number
|
|
138 :group 'relax-ng)
|
|
139
|
|
140 ;; Global variables
|
|
141
|
|
142 (defvar rng-validate-timer nil)
|
|
143 (make-variable-buffer-local 'rng-validate-timer)
|
|
144 ;; ensure that we can cancel the timer even after a kill-all-local-variables
|
|
145 (put 'rng-validate-timer 'permanent-local t)
|
|
146
|
|
147 (defvar rng-validate-quick-timer nil)
|
|
148 (make-variable-buffer-local 'rng-validate-quick-timer)
|
|
149 ;; ensure that we can cancel the timer even after a kill-all-local-variables
|
|
150 (put 'rng-validate-quick-timer 'permanent-local t)
|
|
151
|
|
152 (defvar rng-error-count nil
|
|
153 "Number of errors in the current buffer. Always equal to number of
|
|
154 overlays with category rng-error.")
|
|
155 (make-variable-buffer-local 'rng-error-count)
|
|
156
|
|
157 (defvar rng-message-overlay nil
|
|
158 "Overlay in this buffer whose help-echo property was last printed.
|
|
159 Nil if none.")
|
|
160 (make-variable-buffer-local 'rng-message-overlay)
|
|
161
|
|
162 (defvar rng-message-overlay-inhibit-point nil
|
|
163 "Position at which message from overlay should be inhibited.
|
|
164 If point is equal to this and the error overlay around
|
|
165 point is `rng-message-overlay', then the `help-echo' property
|
|
166 of the error overlay should not be printed with `message'.")
|
|
167 (make-variable-buffer-local 'rng-message-overlay-inhibit-point)
|
|
168
|
|
169 (defvar rng-message-overlay-current nil
|
|
170 "Non-nil if `rng-message-overlay' is still the current message.")
|
|
171 (make-variable-buffer-local 'rng-message-overlay-current)
|
|
172
|
|
173 (defvar rng-open-elements nil
|
|
174 "Stack of names of open elements represented as a list.
|
|
175 Each member of the list is either t or a (PREFIX . LOCAL-NAME) pair.
|
|
176 \(PREFIX . LOCAL-NAME) is pushed for a start-tag; t is pushed
|
|
177 for a mismatched end-tag.")
|
|
178
|
|
179 (defvar rng-pending-contents nil
|
|
180 "Text content of current element that has yet to be processed.
|
|
181 Value is a list of segments (VALUE START END) positions in reverse
|
|
182 order. VALUE is a string or nil. If VALUE is nil, then the value is
|
|
183 the string between START and END. A segment can also be nil
|
|
184 indicating an unresolvable entity or character reference.")
|
|
185
|
|
186 (defvar rng-collecting-text nil)
|
|
187
|
|
188 (defvar rng-validate-up-to-date-end nil
|
|
189 "Last position where validation is known to be up to date.")
|
|
190 (make-variable-buffer-local 'rng-validate-up-to-date-end)
|
|
191
|
|
192 (defvar rng-conditional-up-to-date-start nil
|
|
193 "Marker for the start of the conditionally up-to-date region.
|
|
194 Nil if there is no conditionally up-to-date region. The conditionally
|
|
195 up-to-date region must be such that for any cached state S with
|
|
196 position P in the conditionally up-to-date region, if at some point it
|
|
197 is determined that S becomes correct for P, then all states with
|
|
198 position >= P in the conditionally up to date region must also then be
|
|
199 correct and all errors between P and the end of the region must then
|
|
200 be correctly marked.")
|
|
201 (make-variable-buffer-local 'rng-conditional-up-to-date-start)
|
|
202
|
|
203 (defvar rng-conditional-up-to-date-end nil
|
|
204 "Marker for the end of the conditionally up-to-date region.
|
|
205 Nil if there is no conditionally up-to-date region. See the variable
|
|
206 `rng-conditional-up-to-date-start'.")
|
|
207 (make-variable-buffer-local 'rng-conditional-up-to-date-end)
|
|
208
|
|
209 (defvar rng-parsing-for-state nil
|
|
210 "Non-nil means we are currently parsing just to compute the state.
|
|
211 Should be dynamically bound.")
|
|
212
|
|
213 (defvar rng-validate-mode nil)
|
|
214 (make-variable-buffer-local 'rng-validate-mode)
|
|
215
|
|
216 (defvar rng-dtd nil)
|
|
217 (make-variable-buffer-local 'rng-dtd)
|
|
218
|
|
219 ;;;###autoload
|
|
220 (defun rng-validate-mode (&optional arg no-change-schema)
|
|
221 "Minor mode performing continual validation against a RELAX NG schema.
|
|
222
|
|
223 Checks whether the buffer is a well-formed XML 1.0 document,
|
|
224 conforming to the XML Namespaces Recommendation and valid against a
|
|
225 RELAX NG schema. The mode-line indicates whether it is or not. Any
|
|
226 parts of the buffer that cause it not to be are considered errors and
|
|
227 are highlighted with `rng-error-face'. A description of each error is
|
|
228 available as a tooltip. \\[rng-next-error] goes to the next error
|
|
229 after point. Clicking mouse-1 on the word `Invalid' in the mode-line
|
|
230 goes to the first error in the buffer. If the buffer changes, then it
|
|
231 will be automatically rechecked when Emacs becomes idle; the
|
|
232 rechecking will be paused whenever there is input pending..
|
|
233
|
|
234 By default, uses a vacuous schema that allows any well-formed XML
|
|
235 document. A schema can be specified explictly using
|
|
236 \\[rng-set-schema-file-and-validate], or implicitly based on the buffer's
|
|
237 file name or on the root element name. In each case the schema must
|
|
238 be a RELAX NG schema using the compact schema \(such schemas
|
|
239 conventionally have a suffix of `.rnc'). The variable
|
|
240 `rng-schema-locating-files' specifies files containing rules
|
|
241 to use for finding the schema."
|
|
242 (interactive "P")
|
|
243 (setq rng-validate-mode
|
|
244 (if (null arg)
|
|
245 (not rng-validate-mode)
|
|
246 (> (prefix-numeric-value arg) 0)))
|
|
247 (save-restriction
|
|
248 (widen)
|
|
249 (nxml-with-unmodifying-text-property-changes
|
|
250 (rng-clear-cached-state (point-min) (point-max)))
|
|
251 ;; 1+ to clear empty overlays at (point-max)
|
|
252 (rng-clear-overlays (point-min) (1+ (point-max))))
|
|
253 (setq rng-validate-up-to-date-end 1)
|
|
254 (rng-clear-conditional-region)
|
|
255 (setq rng-error-count 0)
|
|
256 ;; do this here to avoid infinite loop if we set the schema
|
|
257 (remove-hook 'rng-schema-change-hook 'rng-validate-clear t)
|
|
258 (cond (rng-validate-mode
|
|
259 (unwind-protect
|
|
260 (save-excursion
|
|
261 ;; An error can change the current buffer
|
|
262 (when (or (not rng-current-schema)
|
|
263 (and (eq rng-current-schema rng-any-element)
|
|
264 (not no-change-schema)))
|
|
265 (rng-auto-set-schema t)))
|
|
266 (unless rng-current-schema (rng-set-schema-file-1 nil))
|
|
267 (add-hook 'rng-schema-change-hook 'rng-validate-clear nil t)
|
|
268 (add-hook 'after-change-functions 'rng-after-change-function nil t)
|
|
269 (add-hook 'kill-buffer-hook 'rng-kill-timers nil t)
|
|
270 (add-hook 'echo-area-clear-hook 'rng-echo-area-clear-function nil t)
|
|
271 (add-hook 'post-command-hook 'rng-maybe-echo-error-at-point nil t)
|
|
272 (rng-match-init-buffer)
|
|
273 (rng-activate-timers)
|
|
274 ;; Start validating right away if the buffer is visible.
|
|
275 ;; If it's not visible, don't do this, because the user
|
|
276 ;; won't get any progress indication. When the user finds
|
|
277 ;; a new file, then the buffer won't be visible
|
|
278 ;; when this is invoked.
|
|
279 (when (get-buffer-window (current-buffer) 'visible)
|
|
280 (rng-validate-while-idle (current-buffer)))))
|
|
281 (t
|
|
282 (rng-cancel-timers)
|
|
283 (force-mode-line-update)
|
|
284 (remove-hook 'kill-buffer-hook 'rng-cancel-timers t)
|
|
285 (remove-hook 'post-command-hook 'rng-maybe-echo-error-at-point t)
|
|
286 (remove-hook 'echo-area-clear-hook 'rng-echo-area-clear-function t)
|
|
287 (remove-hook 'after-change-functions 'rng-after-change-function t))))
|
|
288
|
|
289 (defun rng-set-schema-file-and-validate (filename)
|
|
290 "Sets the schema and turns on `rng-validate-mode' if not already on.
|
|
291 The schema is set like `rng-set-schema'."
|
|
292 (interactive "fSchema file: ")
|
|
293 (rng-set-schema-file filename)
|
|
294 (or rng-validate-mode (rng-validate-mode)))
|
|
295
|
|
296 (defun rng-set-document-type-and-validate (type-id)
|
|
297 (interactive (list (rng-read-type-id)))
|
|
298 (and (rng-set-document-type type-id)
|
|
299 (or rng-validate-mode (rng-validate-mode))))
|
|
300
|
|
301 (defun rng-auto-set-schema-and-validate ()
|
|
302 "Set the schema for this buffer automatically and turn on `rng-validate-mode'.
|
|
303 The schema is set like `rng-auto-set-schema'."
|
|
304 (interactive)
|
|
305 (rng-auto-set-schema)
|
|
306 (or rng-validate-mode (rng-validate-mode)))
|
|
307
|
|
308 (defun rng-after-change-function (start end pre-change-len)
|
|
309 ;; Work around bug in insert-file-contents.
|
|
310 (when (> end (1+ (buffer-size)))
|
|
311 (setq start 1)
|
|
312 (setq end (1+ (buffer-size))))
|
|
313 (setq rng-message-overlay-inhibit-point nil)
|
|
314 (nxml-with-unmodifying-text-property-changes
|
|
315 (rng-clear-cached-state start end))
|
|
316 ;; rng-validate-up-to-date-end holds the position before the change
|
|
317 ;; Adjust it to reflect the change.
|
|
318 (if (< start rng-validate-up-to-date-end)
|
|
319 (setq rng-validate-up-to-date-end
|
|
320 (if (<= (+ start pre-change-len) rng-validate-up-to-date-end)
|
|
321 (+ rng-validate-up-to-date-end
|
|
322 (- end start pre-change-len))
|
|
323 start)))
|
|
324 ;; Adjust the conditional zone
|
|
325 (cond (rng-conditional-up-to-date-start
|
|
326 (when (< rng-conditional-up-to-date-start end)
|
|
327 (if (< end rng-conditional-up-to-date-end)
|
|
328 (set-marker rng-conditional-up-to-date-start end)
|
|
329 (rng-clear-conditional-region))))
|
|
330 ((< end rng-validate-up-to-date-end)
|
|
331 (setq rng-conditional-up-to-date-end
|
|
332 (copy-marker rng-validate-up-to-date-end nil))
|
|
333 (setq rng-conditional-up-to-date-start
|
|
334 (copy-marker end t))))
|
|
335 ;; Adjust rng-validate-up-to-date-end
|
|
336 (if (< start rng-validate-up-to-date-end)
|
|
337 (setq rng-validate-up-to-date-end start))
|
|
338 ;; Must make rng-validate-up-to-date-end < point-max
|
|
339 ;; (unless the buffer is empty).
|
|
340 ;; otherwise validate-prepare will say there's nothing to do.
|
|
341 ;; Don't use (point-max) because we may be narrowed.
|
|
342 (if (> rng-validate-up-to-date-end (buffer-size))
|
|
343 (setq rng-validate-up-to-date-end
|
|
344 (max 1 (1- rng-validate-up-to-date-end))))
|
|
345 ;; Arrange to revalidate
|
|
346 (rng-activate-timers)
|
|
347 ;; Need to do this after activating the timer
|
|
348 (force-mode-line-update))
|
|
349
|
|
350 (defun rng-compute-mode-line-string ()
|
|
351 (cond (rng-validate-timer
|
|
352 (concat " Validated:"
|
|
353 (number-to-string
|
|
354 ;; Use floor rather than round because we want
|
|
355 ;; to show 99% rather than 100% for changes near
|
|
356 ;; the end.
|
|
357 (floor (if (eq (buffer-size) 0)
|
|
358 0.0
|
|
359 (/ (* (- rng-validate-up-to-date-end 1) 100.0)
|
|
360 (buffer-size)))))
|
|
361 "%%"))
|
|
362 ((> rng-error-count 0)
|
|
363 (concat " "
|
|
364 (propertize "Invalid"
|
|
365 'help-echo "mouse-1: go to first error"
|
|
366 'local-map (make-mode-line-mouse-map
|
|
367 'mouse-1
|
|
368 'rng-mouse-first-error))))
|
|
369 (t " Valid")))
|
|
370
|
|
371 (defun rng-cancel-timers ()
|
|
372 (let ((inhibit-quit t))
|
|
373 (when rng-validate-timer
|
|
374 (cancel-timer rng-validate-timer)
|
|
375 (setq rng-validate-timer nil))
|
|
376 (when rng-validate-quick-timer
|
|
377 (cancel-timer rng-validate-quick-timer)
|
|
378 (setq rng-validate-quick-timer nil))))
|
|
379
|
|
380 (defun rng-kill-timers ()
|
|
381 ;; rng-validate-timer and rng-validate-quick-timer have the
|
|
382 ;; permanent-local property, so that the timers can be
|
|
383 ;; cancelled even after changing mode.
|
|
384 ;; This function takes care of cancelling the timers and
|
|
385 ;; then killing the local variables.
|
|
386 (when (local-variable-p 'rng-validate-timer)
|
|
387 (when rng-validate-timer
|
|
388 (cancel-timer rng-validate-timer))
|
|
389 (kill-local-variable 'rng-validate-timer))
|
|
390 (when (local-variable-p 'rng-validate-quick-timer)
|
|
391 (when rng-validate-quick-timer
|
|
392 (cancel-timer rng-validate-quick-timer))
|
|
393 (kill-local-variable 'rng-validate-quick-timer)))
|
|
394
|
|
395 (defun rng-activate-timers ()
|
|
396 (unless rng-validate-timer
|
|
397 (let ((inhibit-quit t))
|
|
398 (setq rng-validate-timer
|
|
399 (run-with-idle-timer rng-validate-delay
|
|
400 t
|
|
401 'rng-validate-while-idle
|
|
402 (current-buffer)))
|
|
403 (setq rng-validate-quick-timer
|
|
404 (run-with-idle-timer rng-validate-quick-delay
|
|
405 t
|
|
406 'rng-validate-quick-while-idle
|
|
407 (current-buffer))))))
|
|
408
|
|
409 (defun rng-validate-clear ()
|
|
410 (rng-validate-mode 1 t))
|
|
411
|
|
412 ;; These two variables are dynamically bound and used
|
|
413 ;; to pass information between rng-validate-while-idle
|
|
414 ;; and rng-validate-while-idle-continue-p.
|
|
415
|
|
416 (defvar rng-validate-display-point nil)
|
|
417 (defvar rng-validate-display-modified-p nil)
|
|
418
|
|
419 (defun rng-validate-while-idle-continue-p ()
|
|
420 ;; input-pending-p and sit-for run timers that are
|
|
421 ;; ripe. Binding timer-idle-list to nil prevents
|
|
422 ;; this. If we don't do this, then any ripe timers
|
|
423 ;; will get run, and we won't get any chance to
|
|
424 ;; validate until Emacs becomes idle again or until
|
|
425 ;; the other lower priority timers finish (which
|
|
426 ;; can take a very long time in the case of
|
|
427 ;; jit-lock).
|
|
428 (let ((timer-idle-list nil))
|
|
429 (and (not (input-pending-p))
|
|
430 ;; Fake rng-validate-up-to-date-end so that the mode line
|
|
431 ;; shows progress. Also use this to save point.
|
|
432 (let ((rng-validate-up-to-date-end (point)))
|
|
433 (goto-char rng-validate-display-point)
|
|
434 (when (not rng-validate-display-modified-p)
|
|
435 (restore-buffer-modified-p nil))
|
|
436 (force-mode-line-update)
|
|
437 (let ((continue (sit-for 0)))
|
|
438 (goto-char rng-validate-up-to-date-end)
|
|
439 continue)))))
|
|
440
|
|
441 ;; Calling rng-do-some-validation once with a continue-p function, as
|
|
442 ;; opposed to calling it repeatedly, helps on initial validation of a
|
|
443 ;; large buffer with lots of errors. The overlays for errors will all
|
|
444 ;; get added when rng-do-some-validation returns and won't slow the
|
|
445 ;; validation process down.
|
|
446
|
|
447 (defun rng-validate-while-idle (buffer)
|
|
448 (with-current-buffer buffer
|
|
449 (if rng-validate-mode
|
|
450 (if (let ((rng-validate-display-point (point))
|
|
451 (rng-validate-display-modified-p (buffer-modified-p)))
|
|
452 (rng-do-some-validation 'rng-validate-while-idle-continue-p))
|
|
453 (force-mode-line-update)
|
|
454 (rng-validate-done))
|
|
455 ;; must have done kill-all-local-variables
|
|
456 (rng-kill-timers))))
|
|
457
|
|
458 (defun rng-validate-quick-while-idle (buffer)
|
|
459 (with-current-buffer buffer
|
|
460 (if rng-validate-mode
|
|
461 (if (rng-do-some-validation)
|
|
462 (force-mode-line-update)
|
|
463 (rng-validate-done))
|
|
464 ;; must have done kill-all-local-variables
|
|
465 (rng-kill-timers))))
|
|
466
|
|
467 (defun rng-validate-done ()
|
|
468 (when (or (not (current-message))
|
|
469 (rng-current-message-from-error-overlay-p))
|
|
470 (rng-error-overlay-message (or (rng-error-overlay-after (point))
|
|
471 (rng-error-overlay-after (1- (point))))))
|
|
472 (rng-cancel-timers)
|
|
473 (force-mode-line-update))
|
|
474
|
|
475 (defun rng-do-some-validation (&optional continue-p-function)
|
|
476 "Do some validation work. Return t if more to do, nil otherwise."
|
|
477 (save-excursion
|
|
478 (save-restriction
|
|
479 (widen)
|
|
480 (nxml-with-invisible-motion
|
|
481 (condition-case err
|
|
482 (and (rng-validate-prepare)
|
|
483 (let ((rng-dt-namespace-context-getter '(nxml-ns-get-context)))
|
|
484 (nxml-with-unmodifying-text-property-changes
|
|
485 (rng-do-some-validation-1 continue-p-function))))
|
|
486 ;; errors signalled from a function run by an idle timer
|
|
487 ;; are ignored; if we don't catch them, validation
|
|
488 ;; will get mysteriously stuck at a single place
|
|
489 (rng-compile-error
|
|
490 (message "Incorrect schema. %s" (nth 1 err))
|
|
491 (rng-validate-mode 0)
|
|
492 nil)
|
|
493 (error
|
|
494 (message "Internal error in rng-validate-mode triggered at buffer position %d. %s"
|
|
495 (point)
|
|
496 (error-message-string err))
|
|
497 (rng-validate-mode 0)
|
|
498 nil))))))
|
|
499
|
|
500 (defun rng-validate-prepare ()
|
|
501 "Prepare to do some validation, initializing point and the state.
|
|
502 Return t if there is work to do, nil otherwise."
|
|
503 (cond ((= rng-validate-up-to-date-end (point-min))
|
|
504 (rng-set-initial-state)
|
|
505 t)
|
|
506 ((= rng-validate-up-to-date-end (point-max))
|
|
507 nil)
|
|
508 (t (let ((state (get-text-property (1- rng-validate-up-to-date-end)
|
|
509 'rng-state)))
|
|
510 (cond (state
|
|
511 (rng-restore-state state)
|
|
512 (goto-char rng-validate-up-to-date-end))
|
|
513 (t
|
|
514 (let ((pos (previous-single-property-change
|
|
515 rng-validate-up-to-date-end
|
|
516 'rng-state)))
|
|
517 (cond (pos
|
|
518 (rng-restore-state
|
|
519 (or (get-text-property (1- pos) 'rng-state)
|
|
520 (error "Internal error: state null")))
|
|
521 (goto-char pos))
|
|
522 (t (rng-set-initial-state))))))))))
|
|
523
|
|
524
|
|
525 (defun rng-do-some-validation-1 (&optional continue-p-function)
|
|
526 (let ((limit (+ rng-validate-up-to-date-end
|
|
527 rng-validate-chunk-size))
|
|
528 (remove-start rng-validate-up-to-date-end)
|
|
529 (next-cache-point (+ (point) rng-state-cache-distance))
|
|
530 (continue t)
|
|
531 (xmltok-dtd rng-dtd)
|
|
532 have-remaining-chars
|
|
533 xmltok-type
|
|
534 xmltok-start
|
|
535 xmltok-name-colon
|
|
536 xmltok-name-end
|
|
537 xmltok-replacement
|
|
538 xmltok-attributes
|
|
539 xmltok-namespace-attributes
|
|
540 xmltok-dependent-regions
|
|
541 xmltok-errors)
|
|
542 (when (= (point) 1)
|
|
543 (let ((regions (xmltok-forward-prolog)))
|
|
544 (rng-clear-overlays 1 (point))
|
|
545 (while regions
|
|
546 (when (eq (aref (car regions) 0) 'encoding-name)
|
|
547 (rng-process-encoding-name (aref (car regions) 1)
|
|
548 (aref (car regions) 2)))
|
|
549 (setq regions (cdr regions))))
|
|
550 (unless (equal rng-dtd xmltok-dtd)
|
|
551 (rng-clear-conditional-region))
|
|
552 (setq rng-dtd xmltok-dtd))
|
|
553 (while continue
|
|
554 (setq have-remaining-chars (rng-forward))
|
|
555 (let ((pos (point)))
|
|
556 (setq continue
|
|
557 (and have-remaining-chars
|
|
558 (or (< pos limit)
|
|
559 (and continue-p-function
|
|
560 (funcall continue-p-function)
|
|
561 (setq limit (+ limit rng-validate-chunk-size))
|
|
562 t))))
|
|
563 (cond ((and rng-conditional-up-to-date-start
|
|
564 ;; > because we are getting the state from (1- pos)
|
|
565 (> pos rng-conditional-up-to-date-start)
|
|
566 (< pos rng-conditional-up-to-date-end)
|
|
567 (rng-state-matches-current (get-text-property (1- pos)
|
|
568 'rng-state)))
|
|
569 (when (< remove-start (1- pos))
|
|
570 (rng-clear-cached-state remove-start (1- pos)))
|
|
571 ;; sync up with cached validation state
|
|
572 (setq continue nil)
|
|
573 ;; do this before settting rng-validate-up-to-date-end
|
|
574 ;; in case we get a quit
|
|
575 (rng-mark-xmltok-errors)
|
|
576 (rng-mark-xmltok-dependent-regions)
|
|
577 (setq rng-validate-up-to-date-end
|
|
578 (marker-position rng-conditional-up-to-date-end))
|
|
579 (rng-clear-conditional-region)
|
|
580 (setq have-remaining-chars
|
|
581 (< rng-validate-up-to-date-end (point-max))))
|
|
582 ((or (>= pos next-cache-point)
|
|
583 (not continue))
|
|
584 (setq next-cache-point (+ pos rng-state-cache-distance))
|
|
585 (rng-clear-cached-state remove-start pos)
|
|
586 (when have-remaining-chars
|
|
587 (rng-cache-state (1- pos)))
|
|
588 (setq remove-start pos)
|
|
589 (unless continue
|
|
590 ;; if we have just blank chars skip to the end
|
|
591 (when have-remaining-chars
|
|
592 (skip-chars-forward " \t\r\n")
|
|
593 (when (= (point) (point-max))
|
|
594 (rng-clear-overlays pos (point))
|
|
595 (rng-clear-cached-state pos (point))
|
|
596 (setq have-remaining-chars nil)
|
|
597 (setq pos (point))))
|
|
598 (when (not have-remaining-chars)
|
|
599 (rng-process-end-document))
|
|
600 (rng-mark-xmltok-errors)
|
|
601 (rng-mark-xmltok-dependent-regions)
|
|
602 (setq rng-validate-up-to-date-end pos)
|
|
603 (when rng-conditional-up-to-date-end
|
|
604 (cond ((<= rng-conditional-up-to-date-end pos)
|
|
605 (rng-clear-conditional-region))
|
|
606 ((< rng-conditional-up-to-date-start pos)
|
|
607 (set-marker rng-conditional-up-to-date-start
|
|
608 pos)))))))))
|
|
609 have-remaining-chars))
|
|
610
|
|
611 (defun rng-clear-conditional-region ()
|
|
612 (when rng-conditional-up-to-date-start
|
|
613 (set-marker rng-conditional-up-to-date-start nil)
|
|
614 (setq rng-conditional-up-to-date-start nil))
|
|
615 (when rng-conditional-up-to-date-end
|
|
616 (set-marker rng-conditional-up-to-date-end nil)
|
|
617 (setq rng-conditional-up-to-date-end nil)))
|
|
618
|
|
619 (defun rng-clear-cached-state (start end)
|
|
620 "Clear cached state between START and END."
|
|
621 (remove-text-properties start end '(rng-state nil)))
|
|
622
|
|
623 (defun rng-cache-state (pos)
|
|
624 "Save the current state in a text property on the character at pos."
|
|
625 (put-text-property pos
|
|
626 (1+ pos)
|
|
627 'rng-state
|
|
628 (rng-get-state)))
|
|
629
|
|
630 (defun rng-state-matches-current (state)
|
|
631 (and state
|
|
632 (rng-match-state-equal (car state))
|
|
633 (nxml-ns-state-equal (nth 1 state))
|
|
634 (equal (nth 2 state) rng-open-elements)))
|
|
635
|
|
636 (defun rng-get-state ()
|
|
637 (list (rng-match-state)
|
|
638 (nxml-ns-state)
|
|
639 rng-open-elements))
|
|
640
|
|
641 (defun rng-restore-state (state)
|
|
642 (rng-set-match-state (car state))
|
|
643 (setq state (cdr state))
|
|
644 (nxml-ns-set-state (car state))
|
|
645 (setq rng-open-elements (cadr state))
|
|
646 (setq rng-pending-contents nil)
|
|
647 (setq rng-collecting-text (rng-match-text-typed-p)))
|
|
648
|
|
649 (defun rng-set-initial-state ()
|
|
650 (nxml-ns-init)
|
|
651 (rng-match-start-document)
|
|
652 (setq rng-open-elements nil)
|
|
653 (setq rng-pending-contents nil)
|
|
654 (goto-char (point-min)))
|
|
655
|
|
656 (defun rng-clear-overlays (beg end)
|
|
657 (unless rng-parsing-for-state
|
|
658 (let ((overlays (overlays-in beg end)))
|
|
659 (while overlays
|
|
660 (let* ((overlay (car overlays))
|
|
661 (category (overlay-get overlay 'category)))
|
|
662 (cond ((eq category 'rng-error)
|
|
663 (let ((inhibit-quit t))
|
|
664 (when (eq overlay rng-message-overlay)
|
|
665 (rng-error-overlay-message nil))
|
|
666 (delete-overlay overlay)
|
|
667 ;; rng-error-count could be nil
|
|
668 ;; if overlays left over from a previous use
|
|
669 ;; of rng-validate-mode that ended with a change of mode
|
|
670 (when rng-error-count
|
|
671 (setq rng-error-count (1- rng-error-count)))))
|
|
672 ((and (eq category 'rng-dependent)
|
|
673 (<= beg (overlay-start overlay)))
|
|
674 (delete-overlay overlay))))
|
|
675 (setq overlays (cdr overlays))))))
|
|
676
|
|
677 ;;; Dependent regions
|
|
678
|
|
679 (defun rng-mark-xmltok-dependent-regions ()
|
|
680 (while xmltok-dependent-regions
|
|
681 (apply 'rng-mark-xmltok-dependent-region
|
|
682 (car xmltok-dependent-regions))
|
|
683 (setq xmltok-dependent-regions
|
|
684 (cdr xmltok-dependent-regions))))
|
|
685
|
|
686 (defun rng-mark-xmltok-dependent-region (fun start end &rest args)
|
|
687 (let ((overlay (make-overlay start end nil t t)))
|
|
688 (overlay-put overlay 'category 'rng-dependent)
|
|
689 (overlay-put overlay 'rng-funargs (cons fun args))))
|
|
690
|
|
691 (put 'rng-dependent 'evaporate t)
|
|
692 (put 'rng-dependent 'modification-hooks '(rng-dependent-region-changed))
|
|
693 (put 'rng-dependent 'insert-behind-hooks '(rng-dependent-region-changed))
|
|
694
|
|
695 (defun rng-dependent-region-changed (overlay
|
|
696 after-p
|
|
697 change-start
|
|
698 change-end
|
|
699 &optional pre-change-length)
|
|
700 (when (and after-p
|
|
701 ;; Emacs sometimes appears to call deleted overlays
|
|
702 (overlay-start overlay)
|
|
703 (let ((funargs (overlay-get overlay 'rng-funargs)))
|
|
704 (save-match-data
|
|
705 (save-excursion
|
|
706 (save-restriction
|
|
707 (widen)
|
|
708 (apply (car funargs)
|
|
709 (append (list change-start
|
|
710 change-end
|
|
711 pre-change-length
|
|
712 (overlay-start overlay)
|
|
713 (overlay-end overlay))
|
|
714 (cdr funargs))))))))
|
|
715 (rng-after-change-function (overlay-start overlay)
|
|
716 change-end
|
|
717 (+ pre-change-length
|
|
718 (- (overlay-start overlay)
|
|
719 change-start)))
|
|
720 (delete-overlay overlay)))
|
|
721
|
|
722 ;;; Error state
|
|
723
|
|
724 (defun rng-mark-xmltok-errors ()
|
|
725 (while xmltok-errors
|
|
726 (let ((err (car xmltok-errors)))
|
|
727 (rng-mark-not-well-formed (xmltok-error-message err)
|
|
728 (xmltok-error-start err)
|
|
729 (xmltok-error-end err)))
|
|
730 (setq xmltok-errors (cdr xmltok-errors))))
|
|
731
|
|
732 (defun rng-mark-invalid (message beg end)
|
|
733 (rng-mark-error message beg end))
|
|
734
|
|
735 (defun rng-mark-not-well-formed (message beg end)
|
|
736 ;; Don't try to validate further
|
|
737 ;;(rng-set-match-state rng-not-allowed-ipattern)
|
|
738 (rng-mark-error message beg end))
|
|
739
|
|
740 (defun rng-mark-error (message beg end)
|
|
741 (unless rng-parsing-for-state
|
|
742 (let ((overlays (overlays-in beg end)))
|
|
743 (while (and overlays message)
|
|
744 (let ((o (car overlays)))
|
|
745 (when (and (eq (overlay-get o 'category) 'rng-error)
|
|
746 (= (overlay-start o) beg)
|
|
747 (= (overlay-end o) end))
|
|
748 (overlay-put o
|
|
749 'help-echo
|
|
750 (concat (overlay-get o 'help-echo)
|
|
751 "\n"
|
|
752 message))
|
|
753 (setq message nil)))
|
|
754 (setq overlays (cdr overlays))))
|
|
755 (when message
|
|
756 (let ((inhibit-quit t))
|
|
757 (setq rng-error-count (1+ rng-error-count))
|
|
758 (let ((overlay
|
|
759 (make-overlay beg end nil t
|
|
760 ;; Need to make the rear delimiter advance
|
|
761 ;; with the front delimiter when the overlay
|
|
762 ;; is empty, otherwise the front delimiter
|
|
763 ;; will move past the rear delimiter.
|
|
764 (= beg end))))
|
|
765 ;; Ensure when we have two overlapping messages, the help-echo
|
|
766 ;; of the one that starts first is shown
|
|
767 (overlay-put overlay 'priority beg)
|
|
768 (overlay-put overlay 'category 'rng-error)
|
|
769 (overlay-put overlay 'help-echo message))))))
|
|
770
|
|
771 (put 'rng-error 'face 'rng-error-face)
|
|
772 (put 'rng-error 'modification-hooks '(rng-error-modified))
|
|
773
|
|
774 ;; If we don't do this, then the front delimiter can move
|
|
775 ;; past the end delimiter.
|
|
776 (defun rng-error-modified (overlay after-p beg end &optional pre-change-len)
|
|
777 (when (and after-p
|
|
778 (overlay-start overlay) ; check not deleted
|
|
779 (>= (overlay-start overlay)
|
|
780 (overlay-end overlay)))
|
|
781 (let ((inhibit-quit t))
|
|
782 (delete-overlay overlay)
|
|
783 (setq rng-error-count (1- rng-error-count)))))
|
|
784
|
|
785 (defun rng-echo-area-clear-function ()
|
|
786 (setq rng-message-overlay-current nil))
|
|
787
|
|
788 ;;; Error navigation
|
|
789
|
|
790 (defun rng-maybe-echo-error-at-point ()
|
|
791 (when (or (not (current-message))
|
|
792 (rng-current-message-from-error-overlay-p))
|
|
793 (rng-error-overlay-message (rng-error-overlay-after (point)))))
|
|
794
|
|
795 (defun rng-error-overlay-after (pos)
|
|
796 (let ((overlays (overlays-in pos (1+ pos)))
|
|
797 (best nil))
|
|
798 (while overlays
|
|
799 (let ((overlay (car overlays)))
|
|
800 (when (and (eq (overlay-get overlay 'category)
|
|
801 'rng-error)
|
|
802 (or (not best)
|
|
803 (< (overlay-start best)
|
|
804 (overlay-start overlay))))
|
|
805 (setq best overlay)))
|
|
806 (setq overlays (cdr overlays)))
|
|
807 best))
|
|
808
|
|
809 (defun rng-first-error ()
|
|
810 "Go to the first validation error.
|
|
811 Turn on `rng-validate-mode' if it is not already on."
|
|
812 (interactive)
|
|
813 (or rng-validate-mode (rng-validate-mode))
|
|
814 (when (and (eq rng-validate-up-to-date-end 1)
|
|
815 (< rng-validate-up-to-date-end (point-max)))
|
|
816 (rng-do-some-validation))
|
|
817 (let ((err (rng-find-next-error-overlay (1- (point-min)))))
|
|
818 (if err
|
|
819 (rng-goto-error-overlay err)
|
|
820 (let ((pos (save-excursion
|
|
821 (goto-char (point-min))
|
|
822 (rng-next-error 1))))
|
|
823 (when pos
|
|
824 (goto-char pos))))))
|
|
825
|
|
826 (defun rng-mouse-first-error (event)
|
|
827 "Go to the first validation error from a mouse click."
|
|
828 (interactive "e")
|
|
829 (select-window (posn-window (event-start event)))
|
|
830 (rng-first-error))
|
|
831
|
|
832 (defun rng-next-error (arg)
|
|
833 "Go to the next validation error after point.
|
|
834 Turn on `rng-validate-mode' if it is not already on.
|
|
835 A prefix ARG specifies how many errors to move. A negative ARG
|
|
836 moves backwards. Just \\[universal-argument] as a prefix
|
|
837 means goto the first error."
|
|
838 (interactive "P")
|
|
839 (if (consp arg)
|
|
840 (rng-first-error)
|
|
841 (or rng-validate-mode (rng-validate-mode))
|
|
842 (setq arg (prefix-numeric-value arg))
|
|
843 (if (< arg 0)
|
|
844 (rng-previous-error-1 (- arg))
|
|
845 (rng-next-error-1 arg))))
|
|
846
|
|
847 (defun rng-previous-error (arg)
|
|
848 "Go to the previous validation error before point.
|
|
849 Turn on `rng-validate-mode' if it is not already on.
|
|
850 A prefix ARG specifies how many errors to move. A negative ARG
|
|
851 moves forwards. Just \\[universal-argument] as a prefix
|
|
852 means goto the first error."
|
|
853 (interactive "P")
|
|
854 (if (consp arg)
|
|
855 (rng-first-error)
|
|
856 (or rng-validate-mode (rng-validate-mode))
|
|
857 (setq arg (prefix-numeric-value arg))
|
|
858 (if (< arg 0)
|
|
859 (rng-next-error-1 (- arg))
|
|
860 (rng-previous-error-1 arg))))
|
|
861
|
|
862 (defun rng-next-error-1 (arg)
|
|
863 (let* ((pos (point))
|
|
864 err last-err)
|
|
865 (while (and (> arg 0)
|
|
866 (setq err (rng-find-next-error-overlay pos)))
|
|
867 (setq arg (1- arg))
|
|
868 (setq last-err err)
|
|
869 (setq pos (overlay-start err)))
|
|
870 (when (> arg 0)
|
|
871 (setq pos (max pos (1- rng-validate-up-to-date-end)))
|
|
872 (when (< rng-validate-up-to-date-end (point-max))
|
|
873 (message "Parsing...")
|
|
874 (while (let ((more-to-do (rng-do-some-validation)))
|
|
875 (while (and (> arg 0)
|
|
876 (setq err (rng-find-next-error-overlay pos)))
|
|
877 (setq arg (1- arg))
|
|
878 (setq last-err err)
|
|
879 (setq pos (overlay-start err)))
|
|
880 (when (and (> arg 0)
|
|
881 more-to-do
|
|
882 (< rng-validate-up-to-date-end (point-max)))
|
|
883 ;; Display percentage validated.
|
|
884 (force-mode-line-update)
|
|
885 ;; Force redisplay but don't allow idle timers to run.
|
|
886 (let ((timer-idle-list nil))
|
|
887 (sit-for 0))
|
|
888 (setq pos
|
|
889 (max pos (1- rng-validate-up-to-date-end)))
|
|
890 t)))))
|
|
891 (if last-err
|
|
892 (rng-goto-error-overlay last-err)
|
|
893 (message "No more errors")
|
|
894 nil)))
|
|
895
|
|
896 (defun rng-previous-error-1 (arg)
|
|
897 (let* ((pos (point))
|
|
898 err last-err)
|
|
899 (while (and (> arg 0)
|
|
900 (setq err (rng-find-previous-error-overlay pos)))
|
|
901 (setq pos (overlay-start err))
|
|
902 (setq last-err err)
|
|
903 (setq arg (1- arg)))
|
|
904 (when (and (> arg 0)
|
|
905 (< rng-validate-up-to-date-end (min pos (point-max))))
|
|
906 (message "Parsing...")
|
|
907 (while (and (rng-do-some-validation)
|
|
908 (< rng-validate-up-to-date-end (min pos (point-max))))
|
|
909 (force-mode-line-update)
|
|
910 ;; Force redisplay but don't allow idle timers to run.
|
|
911 (let ((timer-idle-list nil))
|
|
912 (sit-for 0)))
|
|
913 (while (and (> arg 0)
|
|
914 (setq err (rng-find-previous-error-overlay pos)))
|
|
915 (setq pos (overlay-start err))
|
|
916 (setq last-err err)
|
|
917 (setq arg (1- arg))))
|
|
918 (if last-err
|
|
919 (rng-goto-error-overlay last-err)
|
|
920 (message "No previous errors")
|
|
921 nil)))
|
|
922
|
|
923 (defun rng-goto-error-overlay (err)
|
|
924 "Goto the start of error overlay ERR and print its message."
|
|
925 (goto-char (overlay-start err))
|
|
926 (setq rng-message-overlay-inhibit-point nil)
|
|
927 (rng-error-overlay-message err))
|
|
928
|
|
929 (defun rng-error-overlay-message (err)
|
|
930 (if err
|
|
931 (unless (or (and (eq rng-message-overlay-inhibit-point (point))
|
|
932 (eq rng-message-overlay err))
|
|
933 (= (point-max) 1))
|
|
934 (message "%s" (overlay-get err 'help-echo))
|
|
935 (setq rng-message-overlay-current t)
|
|
936 (setq rng-message-overlay-inhibit-point (point)))
|
|
937 (when (rng-current-message-from-error-overlay-p)
|
|
938 (message nil))
|
|
939 (setq rng-message-overlay-inhibit-point nil))
|
|
940 (setq rng-message-overlay err))
|
|
941
|
|
942 (defun rng-current-message-from-error-overlay-p ()
|
|
943 (and rng-message-overlay-current
|
|
944 rng-message-overlay
|
|
945 (equal (overlay-get rng-message-overlay 'help-echo)
|
|
946 (current-message))))
|
|
947
|
|
948 (defun rng-find-next-error-overlay (pos)
|
|
949 "Return the overlay for the next error starting after POS.
|
|
950 Return nil if there is no such overlay or it is out of date.
|
|
951 Do not do any additional validation."
|
|
952 (when rng-error-count
|
|
953 (let (done found overlays)
|
|
954 (while (not done)
|
|
955 (cond (overlays
|
|
956 (let ((overlay (car overlays)))
|
|
957 (setq overlays (cdr overlays))
|
|
958 (when (and (eq (overlay-get overlay 'category) 'rng-error)
|
|
959 ;; Is it the first?
|
|
960 (= (overlay-start overlay) pos)
|
|
961 ;; Is it up to date?
|
|
962 (<= (overlay-end overlay)
|
|
963 rng-validate-up-to-date-end))
|
|
964 (setq done t)
|
|
965 (setq found overlay))))
|
|
966 ((or (= pos (point-max))
|
|
967 (> (setq pos (next-overlay-change pos))
|
|
968 rng-validate-up-to-date-end))
|
|
969 (setq done t))
|
|
970 (t (setq overlays (overlays-in pos (1+ pos))))))
|
|
971 found)))
|
|
972
|
|
973 (defun rng-find-previous-error-overlay (pos)
|
|
974 "Return the overlay for the last error starting before POS.
|
|
975 Return nil if there is no such overlay or it is out of date.
|
|
976 Do not do any additional validation."
|
|
977 (when (and rng-error-count
|
|
978 (<= pos rng-validate-up-to-date-end))
|
|
979 (let (done found overlays)
|
|
980 (while (not done)
|
|
981 (cond (overlays
|
|
982 (let ((overlay (car overlays)))
|
|
983 (setq overlays (cdr overlays))
|
|
984 (when (and (eq (overlay-get overlay 'category) 'rng-error)
|
|
985 ;; Is it the first?
|
|
986 (= (overlay-start overlay) pos))
|
|
987 (setq done t)
|
|
988 (setq found overlay))))
|
|
989 ((= pos (point-min))
|
|
990 (setq done t))
|
|
991 (t
|
|
992 (setq pos (previous-overlay-change pos))
|
|
993 (setq overlays (overlays-in pos (1+ pos))))))
|
|
994 found)))
|
|
995
|
|
996 ;;; Parsing
|
|
997
|
|
998 (defun rng-forward (&optional limit)
|
|
999 "Move forward over one or more tokens updating the state.
|
|
1000 If LIMIT is nil, stop after tags.
|
|
1001 If LIMIT is non-nil, stop when end of last token parsed is >= LIMIT.
|
|
1002 Return nil at end of buffer, t otherwise."
|
|
1003 (let (type)
|
|
1004 (while (progn
|
|
1005 (setq type (xmltok-forward))
|
|
1006 (rng-clear-overlays xmltok-start (point))
|
|
1007 (let ((continue
|
|
1008 (cond ((eq type 'start-tag)
|
|
1009 (rng-process-start-tag 'start-tag)
|
|
1010 nil)
|
|
1011 ((eq type 'end-tag)
|
|
1012 (rng-process-end-tag)
|
|
1013 nil)
|
|
1014 ((eq type 'empty-element)
|
|
1015 (rng-process-start-tag 'empty-element)
|
|
1016 nil)
|
|
1017 ((eq type 'space)
|
|
1018 (rng-process-text xmltok-start nil t)
|
|
1019 t)
|
|
1020 ((eq type 'data)
|
|
1021 (rng-process-text xmltok-start nil nil)
|
|
1022 t)
|
|
1023 ((memq type '(entity-ref char-ref))
|
|
1024 (cond (xmltok-replacement
|
|
1025 (rng-process-text xmltok-start
|
|
1026 nil
|
|
1027 'maybe
|
|
1028 xmltok-replacement))
|
|
1029 ((eq type 'char-ref)
|
|
1030 (rng-process-unknown-char))
|
|
1031 (t
|
|
1032 (rng-process-unknown-entity)))
|
|
1033 t)
|
|
1034 ((eq type 'cdata-section)
|
|
1035 (rng-process-text (+ xmltok-start 9) ; "<![CDATA["
|
|
1036 (- (point) 3) ; "]]>"
|
|
1037 'maybe)
|
|
1038 t)
|
|
1039 ((eq type 'partial-start-tag)
|
|
1040 (rng-process-start-tag 'partial-start-tag)
|
|
1041 t)
|
|
1042 ((eq type 'partial-empty-element)
|
|
1043 (rng-process-start-tag 'empty-element)
|
|
1044 t)
|
|
1045 ((eq type 'partial-end-tag)
|
|
1046 (rng-process-end-tag 'partial)
|
|
1047 t)
|
|
1048 (t type))))
|
|
1049 (if limit
|
|
1050 (< (point) limit)
|
|
1051 continue))))
|
|
1052 (and type t)))
|
|
1053
|
|
1054 (defun rng-process-start-tag (tag-type)
|
|
1055 "TAG-TYPE is `start-tag' for a start-tag, `empty-element' for
|
|
1056 an empty element. partial-empty-element should be passed
|
|
1057 as empty-element."
|
|
1058 (and rng-collecting-text (rng-flush-text))
|
|
1059 (setq rng-collecting-text nil)
|
|
1060 (setq rng-pending-contents nil)
|
|
1061 (rng-process-namespaces)
|
|
1062 (let ((tag (rng-process-tag-name)))
|
|
1063 (rng-process-attributes)
|
|
1064 ;; set the state appropriately
|
|
1065 (cond ((eq tag-type 'empty-element)
|
|
1066 (rng-process-start-tag-close)
|
|
1067 ;; deal with missing content with empty element
|
|
1068 (when (not (rng-match-empty-content))
|
|
1069 (rng-match-after)
|
|
1070 (rng-mark-start-tag-close "Empty content not allowed"))
|
|
1071 (nxml-ns-pop-state))
|
|
1072 ((eq tag-type 'start-tag)
|
|
1073 (rng-process-start-tag-close)
|
|
1074 (setq rng-collecting-text (rng-match-text-typed-p))
|
|
1075 (rng-push-tag tag))
|
|
1076 ((eq tag-type 'partial-start-tag)
|
|
1077 (rng-process-start-tag-close)
|
|
1078 (rng-match-after)
|
|
1079 (nxml-ns-pop-state)))))
|
|
1080
|
|
1081 (defun rng-process-namespaces ()
|
|
1082 (let ((nsatts xmltok-namespace-attributes)
|
|
1083 prefixes)
|
|
1084 (nxml-ns-push-state)
|
|
1085 (while nsatts
|
|
1086 (let* ((att (car nsatts))
|
|
1087 (value (xmltok-attribute-value att)))
|
|
1088 (when value
|
|
1089 (let ((ns (nxml-make-namespace value))
|
|
1090 (prefix (and (xmltok-attribute-prefix att)
|
|
1091 (xmltok-attribute-local-name att))))
|
|
1092 (cond ((member prefix prefixes)
|
|
1093 (rng-mark-invalid "Duplicate namespace declaration"
|
|
1094 (xmltok-attribute-name-start att)
|
|
1095 (xmltok-attribute-name-end att)))
|
|
1096 ((not prefix)
|
|
1097 (nxml-ns-set-default ns))
|
|
1098 (ns
|
|
1099 (nxml-ns-set-prefix prefix ns))
|
|
1100 (t
|
|
1101 ;; cannot have xmlns:foo=""
|
|
1102 (rng-mark-invalid "Namespace prefix cannot be undeclared"
|
|
1103 (1- (xmltok-attribute-value-start att))
|
|
1104 (1+ (xmltok-attribute-value-end att)))))
|
|
1105 (setq prefixes (cons prefix prefixes)))))
|
|
1106 (setq nsatts (cdr nsatts)))))
|
|
1107
|
|
1108 (defun rng-process-tag-name ()
|
|
1109 (let* ((prefix (xmltok-start-tag-prefix))
|
|
1110 (local-name (xmltok-start-tag-local-name))
|
|
1111 (name
|
|
1112 (if prefix
|
|
1113 (let ((ns (nxml-ns-get-prefix prefix)))
|
|
1114 (cond (ns (cons ns local-name))
|
|
1115 ((and (setq ns
|
|
1116 (rng-match-infer-start-tag-namespace
|
|
1117 local-name))
|
|
1118 (rng-match-start-tag-open (cons ns local-name)))
|
|
1119 (nxml-ns-set-prefix prefix ns)
|
|
1120 (rng-mark-start-tag-close "Missing xmlns:%s=\"%s\""
|
|
1121 prefix
|
|
1122 (nxml-namespace-name ns))
|
|
1123 nil)
|
|
1124 (t
|
|
1125 (rng-recover-bad-element-prefix)
|
|
1126 nil)))
|
|
1127 (cons (nxml-ns-get-default) local-name))))
|
|
1128 (when (and name
|
|
1129 (not (rng-match-start-tag-open name)))
|
|
1130 (unless (and (not (car name))
|
|
1131 (let ((ns (rng-match-infer-start-tag-namespace (cdr name))))
|
|
1132 (and ns
|
|
1133 (rng-match-start-tag-open (cons ns local-name))
|
|
1134 (progn
|
|
1135 (nxml-ns-set-default ns)
|
|
1136 ;; XXX need to check we don't have xmlns=""
|
|
1137 (rng-mark-start-tag-close "Missing xmlns=\"%s\""
|
|
1138 (nxml-namespace-name ns))
|
|
1139 t))))
|
|
1140 (rng-recover-start-tag-open name)))
|
|
1141 (cons prefix local-name)))
|
|
1142
|
|
1143 (defun rng-process-attributes ()
|
|
1144 (let ((atts xmltok-attributes)
|
|
1145 names)
|
|
1146 (while atts
|
|
1147 (let* ((att (car atts))
|
|
1148 (prefix (xmltok-attribute-prefix att))
|
|
1149 (local-name (xmltok-attribute-local-name att))
|
|
1150 (name
|
|
1151 (if prefix
|
|
1152 (let ((ns (nxml-ns-get-prefix prefix)))
|
|
1153 (and ns
|
|
1154 (cons ns local-name)))
|
|
1155 (cons nil local-name))))
|
|
1156 (cond ((not name)
|
|
1157 (rng-recover-bad-attribute-prefix att))
|
|
1158 ((member name names)
|
|
1159 (rng-recover-duplicate-attribute-name att))
|
|
1160 ((not (rng-match-attribute-name name))
|
|
1161 (rng-recover-attribute-name att))
|
|
1162 ((rng-match-text-typed-p)
|
|
1163 (let ((value (xmltok-attribute-value att)))
|
|
1164 (if value
|
|
1165 (or (rng-match-attribute-value value)
|
|
1166 (rng-recover-attribute-value att))
|
|
1167 (rng-match-after))))
|
|
1168 (t (or (rng-match-end-tag)
|
|
1169 (error "Internal error:\
|
|
1170 invalid on untyped attribute value"))))
|
|
1171 (setq names (cons name names)))
|
|
1172 (setq atts (cdr atts)))))
|
|
1173
|
|
1174 (defun rng-process-start-tag-close ()
|
|
1175 ;; deal with missing attributes
|
|
1176 (unless (rng-match-start-tag-close)
|
|
1177 (rng-mark-start-tag-close (rng-missing-attributes-message))
|
|
1178 (rng-match-ignore-attributes)))
|
|
1179
|
|
1180 (defun rng-mark-start-tag-close (&rest args)
|
|
1181 (when (not (eq xmltok-type 'partial-start-tag))
|
|
1182 (rng-mark-invalid (apply 'format args)
|
|
1183 (- (point)
|
|
1184 (if (eq xmltok-type 'empty-element)
|
|
1185 2
|
|
1186 1))
|
|
1187 (point))))
|
|
1188
|
|
1189 (defun rng-recover-bad-element-prefix ()
|
|
1190 (rng-mark-invalid "Prefix not declared"
|
|
1191 (1+ xmltok-start)
|
|
1192 xmltok-name-colon)
|
|
1193 (rng-match-unknown-start-tag-open))
|
|
1194
|
|
1195 (defun rng-recover-bad-attribute-prefix (att)
|
|
1196 (rng-mark-invalid "Prefix not declared"
|
|
1197 (xmltok-attribute-name-start att)
|
|
1198 (xmltok-attribute-name-colon att)))
|
|
1199
|
|
1200 (defun rng-recover-duplicate-attribute-name (att)
|
|
1201 (rng-mark-invalid "Duplicate attribute"
|
|
1202 (xmltok-attribute-name-start att)
|
|
1203 (xmltok-attribute-name-end att)))
|
|
1204
|
|
1205 (defun rng-recover-start-tag-open (name)
|
|
1206 (let ((required (rng-match-required-element-name)))
|
|
1207 (cond ((and required
|
|
1208 (rng-match-start-tag-open required)
|
|
1209 (rng-match-after)
|
|
1210 (rng-match-start-tag-open name))
|
|
1211 (rng-mark-invalid (concat "Missing element "
|
|
1212 (rng-quote-string
|
|
1213 (rng-name-to-string required)))
|
|
1214 xmltok-start
|
|
1215 (1+ xmltok-start)))
|
|
1216 ((and (rng-match-optionalize-elements)
|
|
1217 (rng-match-start-tag-open name))
|
|
1218 (rng-mark-invalid "Required elements missing"
|
|
1219 xmltok-start
|
|
1220 (1+ xmltok-start)))
|
|
1221 ((rng-match-out-of-context-start-tag-open name)
|
|
1222 (rng-mark-invalid "Element not allowed in this context"
|
|
1223 (1+ xmltok-start)
|
|
1224 xmltok-name-end))
|
|
1225 (t
|
|
1226 (rng-match-unknown-start-tag-open)
|
|
1227 (rng-mark-invalid "Unknown element"
|
|
1228 (1+ xmltok-start)
|
|
1229 xmltok-name-end)))))
|
|
1230
|
|
1231 (defun rng-recover-attribute-value (att)
|
|
1232 (let ((start (xmltok-attribute-value-start att))
|
|
1233 (end (xmltok-attribute-value-end att)))
|
|
1234 (if (= start end)
|
|
1235 (rng-mark-invalid "Empty attribute value invalid" start (1+ end))
|
|
1236 (rng-mark-invalid "Attribute value invalid" start end)))
|
|
1237 (rng-match-after))
|
|
1238
|
|
1239 (defun rng-recover-attribute-name (att)
|
|
1240 (rng-mark-invalid "Attribute not allowed"
|
|
1241 (xmltok-attribute-name-start att)
|
|
1242 (xmltok-attribute-name-end att)))
|
|
1243
|
|
1244 (defun rng-missing-attributes-message ()
|
|
1245 (let ((required-attributes
|
|
1246 (rng-match-required-attribute-names)))
|
|
1247 (cond ((not required-attributes)
|
|
1248 "Required attributes missing")
|
|
1249 ((not (cdr required-attributes))
|
|
1250 (concat "Missing attribute "
|
|
1251 (rng-quote-string
|
|
1252 (rng-name-to-string (car required-attributes) t))))
|
|
1253 (t
|
|
1254 (concat "Missing attributes "
|
|
1255 (mapconcat (lambda (nm)
|
|
1256 (rng-quote-string
|
|
1257 (rng-name-to-string nm t)))
|
|
1258 required-attributes
|
|
1259 ", "))))))
|
|
1260
|
|
1261 (defun rng-process-end-tag (&optional partial)
|
|
1262 (cond ((not rng-open-elements)
|
|
1263 (rng-mark-not-well-formed "Extra end-tag"
|
|
1264 xmltok-start
|
|
1265 (point)))
|
|
1266 ((or partial
|
|
1267 (equal (cons (xmltok-end-tag-prefix)
|
|
1268 (xmltok-end-tag-local-name))
|
|
1269 (car rng-open-elements)))
|
|
1270 (rng-end-element))
|
|
1271 (t (rng-recover-mismatched-end-tag))))
|
|
1272
|
|
1273 (defun rng-end-element ()
|
|
1274 (if rng-collecting-text
|
|
1275 (let ((contents (rng-contents-string)))
|
|
1276 (cond ((not contents) (rng-match-after))
|
|
1277 ((not (rng-match-element-value contents))
|
|
1278 (let* ((region (rng-contents-region)))
|
|
1279 (if (not region)
|
|
1280 (rng-mark-invalid "Empty content not allowed"
|
|
1281 xmltok-start
|
|
1282 (+ xmltok-start 2))
|
|
1283 (rng-mark-invalid "Invalid data"
|
|
1284 (car region)
|
|
1285 (cdr region))))
|
|
1286 (rng-match-after)))
|
|
1287 (setq rng-collecting-text nil)
|
|
1288 (setq rng-pending-contents nil))
|
|
1289 (unless (rng-match-end-tag)
|
|
1290 (rng-mark-invalid (rng-missing-element-message)
|
|
1291 xmltok-start
|
|
1292 (+ xmltok-start 2))
|
|
1293 (rng-match-after)))
|
|
1294 (nxml-ns-pop-state)
|
|
1295 (when (eq (car rng-open-elements) t)
|
|
1296 (rng-pop-tag))
|
|
1297 (rng-pop-tag))
|
|
1298
|
|
1299 (defun rng-missing-element-message ()
|
|
1300 (let ((element (rng-match-required-element-name)))
|
|
1301 (if element
|
|
1302 (concat "Missing element "
|
|
1303 (rng-quote-string (rng-name-to-string element)))
|
|
1304 "Required child elements missing")))
|
|
1305
|
|
1306 (defun rng-recover-mismatched-end-tag ()
|
|
1307 (let* ((name (cons (xmltok-end-tag-prefix)
|
|
1308 (xmltok-end-tag-local-name))))
|
|
1309 (cond ((member name (cdr rng-open-elements))
|
|
1310 (let* ((suppress-error (eq (car rng-open-elements) t))
|
|
1311 missing top)
|
|
1312 (while (progn
|
|
1313 (setq top (car rng-open-elements))
|
|
1314 (rng-pop-tag)
|
|
1315 (unless (eq top t)
|
|
1316 (setq missing (cons top missing))
|
|
1317 (nxml-ns-pop-state)
|
|
1318 (rng-match-after))
|
|
1319 (not (equal top name))))
|
|
1320 (unless suppress-error
|
|
1321 (rng-mark-missing-end-tags (cdr missing)))))
|
|
1322 ((rng-match-empty-before-p)
|
|
1323 (rng-mark-mismatched-end-tag)
|
|
1324 (rng-end-element))
|
|
1325 (t (rng-mark-mismatched-end-tag)
|
|
1326 (setq rng-open-elements
|
|
1327 (cons t rng-open-elements))))))
|
|
1328
|
|
1329 (defun rng-mark-missing-end-tags (missing)
|
|
1330 (rng-mark-not-well-formed
|
|
1331 (format "Missing end-tag%s %s"
|
|
1332 (if (null (cdr missing)) "" "s")
|
|
1333 (mapconcat (lambda (name)
|
|
1334 (rng-quote-string
|
|
1335 (if (car name)
|
|
1336 (concat (car name)
|
|
1337 ":"
|
|
1338 (cdr name))
|
|
1339 (cdr name))))
|
|
1340 missing
|
|
1341 ", "))
|
|
1342 xmltok-start
|
|
1343 (+ xmltok-start 2)))
|
|
1344
|
|
1345 (defun rng-mark-mismatched-end-tag ()
|
|
1346 (rng-mark-not-well-formed "Mismatched end-tag"
|
|
1347 (+ xmltok-start 2)
|
|
1348 xmltok-name-end))
|
|
1349
|
|
1350 (defun rng-push-tag (prefix-local-name)
|
|
1351 (setq rng-open-elements
|
|
1352 (cons prefix-local-name rng-open-elements)))
|
|
1353
|
|
1354 (defun rng-pop-tag ()
|
|
1355 (setq rng-open-elements (cdr rng-open-elements)))
|
|
1356
|
|
1357 (defun rng-contents-string ()
|
|
1358 (let ((contents rng-pending-contents))
|
|
1359 (cond ((not contents) "")
|
|
1360 ((memq nil contents) nil)
|
|
1361 ((not (cdr contents))
|
|
1362 (rng-segment-string (car contents)))
|
|
1363 (t (apply 'concat
|
|
1364 (nreverse (mapcar 'rng-segment-string
|
|
1365 contents)))))))
|
|
1366
|
|
1367 (defun rng-segment-string (segment)
|
|
1368 (or (car segment)
|
|
1369 (apply 'buffer-substring-no-properties
|
|
1370 (cdr segment))))
|
|
1371
|
|
1372 (defun rng-segment-blank-p (segment)
|
|
1373 (if (car segment)
|
|
1374 (rng-blank-p (car segment))
|
|
1375 (apply 'rng-region-blank-p
|
|
1376 (cdr segment))))
|
|
1377
|
|
1378 (defun rng-contents-region ()
|
|
1379 (if (null rng-pending-contents)
|
|
1380 nil
|
|
1381 (let* ((contents rng-pending-contents)
|
|
1382 (head (cdar contents))
|
|
1383 (start (car head))
|
|
1384 (end (cadr head)))
|
|
1385 (while (setq contents (cdr contents))
|
|
1386 (setq start (car (cdar contents))))
|
|
1387 (cons start end))))
|
|
1388
|
|
1389 (defun rng-process-text (start end whitespace &optional value)
|
|
1390 "Process characters between position START and END as text.
|
|
1391 END nil means point. WHITESPACE t means known to be whitespace, nil
|
|
1392 means known not to be, anything else means unknown whether whitespace
|
|
1393 or not. END must not be nil if WHITESPACE is neither t nor nil.
|
|
1394 VALUE is a string or nil; nil means the value is equal to the
|
|
1395 string between START and END."
|
|
1396 (cond (rng-collecting-text
|
|
1397 (setq rng-pending-contents (cons (list value start (or end (point)))
|
|
1398 rng-pending-contents)))
|
|
1399 ((not (or (and whitespace
|
|
1400 (or (eq whitespace t)
|
|
1401 (if value
|
|
1402 (rng-blank-p value)
|
|
1403 (rng-region-blank-p start end))))
|
|
1404 (rng-match-mixed-text)))
|
|
1405 (rng-mark-invalid "Text not allowed" start (or end (point))))))
|
|
1406
|
|
1407 (defun rng-process-unknown-char ()
|
|
1408 (when rng-collecting-text
|
|
1409 (setq rng-pending-contents
|
|
1410 (cons nil rng-pending-contents))))
|
|
1411
|
|
1412 (defun rng-process-unknown-entity ()
|
|
1413 (rng-process-unknown-char)
|
|
1414 (rng-match-optionalize-elements))
|
|
1415
|
|
1416 (defun rng-region-blank-p (beg end)
|
|
1417 (save-excursion
|
|
1418 (goto-char beg)
|
|
1419 (= (skip-chars-forward " \n\r\t" end)
|
|
1420 (- end beg))))
|
|
1421
|
|
1422 (defun rng-flush-text ()
|
|
1423 (while rng-pending-contents
|
|
1424 (let ((segment (car rng-pending-contents)))
|
|
1425 (unless (or (rng-segment-blank-p segment)
|
|
1426 (rng-match-mixed-text))
|
|
1427 (let ((region (cdr segment)))
|
|
1428 (rng-mark-invalid "In this context text cannot be mixed with elements"
|
|
1429 (car region)
|
|
1430 (cadr region)))))
|
|
1431 (setq rng-pending-contents (cdr rng-pending-contents))))
|
|
1432
|
|
1433 (defun rng-process-end-document ()
|
|
1434 ;; this is necessary to clear empty overlays at (point-max)
|
|
1435 (rng-clear-overlays (point) (point))
|
|
1436 (let ((start (save-excursion
|
|
1437 (skip-chars-backward " \t\r\n")
|
|
1438 (point))))
|
|
1439 (cond (rng-open-elements
|
|
1440 (unless (eq (car rng-open-elements) t)
|
|
1441 (rng-mark-not-well-formed "Missing end-tag"
|
|
1442 start
|
|
1443 (point))))
|
|
1444 ((not (rng-match-nullable-p))
|
|
1445 (rng-mark-not-well-formed "No document element"
|
|
1446 start
|
|
1447 (point))))))
|
|
1448
|
|
1449 (defun rng-process-encoding-name (beg end)
|
|
1450 (unless (let ((charset (buffer-substring-no-properties beg end)))
|
|
1451 (or (nxml-mime-charset-coding-system charset)
|
|
1452 (string= (downcase charset) "utf-16")))
|
|
1453 (rng-mark-not-well-formed "Unsupported encoding" beg end)))
|
|
1454
|
|
1455 (defun rng-name-to-string (name &optional attributep)
|
|
1456 (let ((ns (car name))
|
|
1457 (local-name (cdr name)))
|
|
1458 (if (or (not ns)
|
|
1459 (and (not attributep)
|
|
1460 (eq (nxml-ns-get-default) ns)))
|
|
1461 local-name
|
|
1462 (let ((prefix (nxml-ns-prefix-for ns)))
|
|
1463 (if prefix
|
|
1464 (concat prefix ":" local-name)
|
|
1465 (concat "{" (symbol-name ns) "}" local-name))))))
|
|
1466
|
|
1467 (provide 'rng-valid)
|
|
1468
|
86379
|
1469 ;; arch-tag: 7dd846d3-519d-4a6d-8107-4ff0024a60ef
|
86361
|
1470 ;;; rng-valid.el ends here
|