diff lisp/nxml/rng-valid.el @ 86361:38f93f3d00a2

Initial merge of nxml
author Mark A. Hershberger <mah@everybody.org>
date Fri, 23 Nov 2007 06:58:00 +0000
parents
children 2ac1a9b70580
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/nxml/rng-valid.el	Fri Nov 23 06:58:00 2007 +0000
@@ -0,0 +1,1467 @@
+;;; rng-valid.el --- real-time validation of XML using RELAX NG
+
+;; Copyright (C) 2003 Free Software Foundation, Inc.
+
+;; Author: James Clark
+;; Keywords: XML, RelaxNG
+
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the GNU General Public License as
+;; published by the Free Software Foundation; either version 2 of
+;; the License, or (at your option) any later version.
+
+;; This program is distributed in the hope that it will be
+;; useful, but WITHOUT ANY WARRANTY; without even the implied
+;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+;; PURPOSE.  See the GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public
+;; License along with this program; if not, write to the Free
+;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+;; MA 02111-1307 USA
+
+;;; Commentary:
+
+;; For usage information, see the documentation for rng-validate-mode.
+;;
+;; This file provides a minor mode that continually validates a buffer
+;; against a RELAX NG schema. The validation state is used to support
+;; schema-sensitive editing as well as validation. Validation is
+;; performed while Emacs is idle.  XML parsing is done using
+;; xmltok.el. This file is responsible for checking that end-tags
+;; match their start-tags.  Namespace processing is handled by
+;; nxml-ns.el. The RELAX NG Compact Syntax schema is parsed into
+;; internal form by rng-cmpct.el.  This internal form is described by
+;; rng-pttrn.el.  Validation of the document by matching against this
+;; internal form is done by rng-match.el. Handling of W3C XML Schema
+;; datatypes is delegated by rng-match.el to rng-xsd.el.  The minor
+;; mode is intended to be used in conjunction with the nxml major
+;; mode, but does not have to be.
+;;
+;; The major responsibility of this file is to allow validation to
+;; happen incrementally.  If a buffer has been validated and is then
+;; changed, we can often revalidate it without having to completely
+;; parse and validate it from start to end.  As we parse and validate
+;; the buffer, we periodically cache the state.  The state has three
+;; components: the stack of open elements, the namespace processing
+;; state and the RELAX NG validation state. The state is cached as the
+;; value of the rng-state text property on the closing greater-than of
+;; tags (but at intervals, not on every tag).  We keep track of the
+;; position up to which cached state is known to be correct by adding
+;; a function to the buffer's after-change-functions. This is stored
+;; in the rng-validate-up-to-date-end variable.  The first way in
+;; which we make validation incremental is obvious: we start
+;; validation from the first cached state before
+;; rng-validate-up-to-date-end.
+;;
+;; To make this work efficiently, we have to be able to copy the
+;; current parsing and validation state efficiently.  We do this by
+;; minimizing destructive changes to the objects storing the state.
+;; When state is changed, we use the old state to create new objects
+;; representing the new state rather than destructively modifying the
+;; objects representing the old state. Copying the state is just a
+;; matter of making a list of three objects, one for each component of
+;; the state; the three objects themselves can be shared and do not
+;; need to be copied.
+;;
+;; There's one other idea that is used to make validation incremental.
+;; Suppose we have a buffer that's 4000 bytes long and suppose we
+;; validated it, caching state at positions 1000, 2000 and 3000.  Now
+;; suppose we make a change at position 1500 inserting 100 characters.
+;; rng-validate-up-to-date-end will be changed to 1500.  When Emacs
+;; becomes idle and we revalidate, validation will restart using the
+;; cached state at position 1000.  However, we take advantage of the
+;; cached state beyond rng-validate-up-to-date-end as follows.  When
+;; our validation reaches position 2100 (the current position of the
+;; character that was at 2000), we compare our current state with the
+;; cached state.  If they are the same, then we can stop parsing
+;; immediately and set rng-validate-up-to-date-end to the end of the
+;; buffer: we already know that the state cached at position 3100 is
+;; correct.  If they are not the same, then we have to continue
+;; parsing.  After the change, but before revalidation, we call the
+;; region from 1600 to the end of the buffer "conditionally
+;; up-to-date".
+;;
+;; As well as the cached parsing and validation state, we also keep
+;; track of the errors in the file.  Errors are stored as overlays
+;; with a category of rng-error.  The number of such overlays in the
+;; buffer must always be equal to rng-error-count.
+
+;;; Code:
+
+(require 'xmltok)
+(require 'nxml-enc)
+(require 'nxml-util)
+(require 'nxml-ns)
+(require 'rng-match)
+(require 'rng-util)
+(require 'rng-loc)
+
+;;; Customizable variables
+
+(defgroup relax-ng nil
+  "Validation of XML using RELAX NG."
+  :group 'wp
+  :group 'nxml
+  :group 'languages)
+
+(defface rng-error-face '((t (:underline "red")))
+  "Face for highlighting XML errors."
+  :group 'relax-ng)
+
+(defcustom rng-state-cache-distance 2000
+  "*Distance in characters between each parsing and validation state cache."
+  :type 'integer
+  :group 'relax-ng)
+
+(defcustom rng-validate-chunk-size 8000
+  "*Number of characters in a RELAX NG validation chunk.
+A validation chunk will be the smallest chunk that is at least this
+size and ends with a tag.  After validating a chunk, validation will
+continue only if Emacs is still idle."
+  :type 'integer
+  :group 'relax-ng)
+
+(defcustom rng-validate-delay 1.5
+  "*Time in seconds that Emacs must be idle before starting a full validation.
+A full validation continues until either validation is up to date
+or Emacs is no longer idle."
+  :type 'number
+  :group 'relax-ng)
+
+(defcustom rng-validate-quick-delay 0.3
+  "*Time in seconds that Emacs must be idle before starting a quick validation.
+A quick validation validates at most one chunk."
+  :type 'number
+  :group 'relax-ng)
+
+;; Global variables
+
+(defvar rng-validate-timer nil)
+(make-variable-buffer-local 'rng-validate-timer)
+;; ensure that we can cancel the timer even after a kill-all-local-variables
+(put 'rng-validate-timer 'permanent-local t)
+
+(defvar rng-validate-quick-timer nil)
+(make-variable-buffer-local 'rng-validate-quick-timer)
+;; ensure that we can cancel the timer even after a kill-all-local-variables
+(put 'rng-validate-quick-timer 'permanent-local t)
+
+(defvar rng-error-count nil
+  "Number of errors in the current buffer.  Always equal to number of
+overlays with category rng-error.")
+(make-variable-buffer-local 'rng-error-count)
+
+(defvar rng-message-overlay nil
+  "Overlay in this buffer whose help-echo property was last printed.
+Nil if none.")
+(make-variable-buffer-local 'rng-message-overlay)
+
+(defvar rng-message-overlay-inhibit-point nil
+  "Position at which message from overlay should be inhibited.
+If point is equal to this and the error overlay around
+point is `rng-message-overlay', then the `help-echo' property
+of the error overlay should not be printed with `message'.")
+(make-variable-buffer-local 'rng-message-overlay-inhibit-point)
+
+(defvar rng-message-overlay-current nil
+  "Non-nil if `rng-message-overlay' is still the current message.")
+(make-variable-buffer-local 'rng-message-overlay-current)
+
+(defvar rng-open-elements nil
+  "Stack of names of open elements represented as a list.
+Each member of the list is either t or a (PREFIX . LOCAL-NAME) pair.
+\(PREFIX . LOCAL-NAME) is pushed for a start-tag; t is pushed
+for a mismatched end-tag.")
+
+(defvar rng-pending-contents nil
+  "Text content of current element that has yet to be processed.
+Value is a list of segments (VALUE START END) positions in reverse
+order.  VALUE is a string or nil.  If VALUE is nil, then the value is
+the string between START and END.  A segment can also be nil
+indicating an unresolvable entity or character reference.")
+
+(defvar rng-collecting-text nil)
+
+(defvar rng-validate-up-to-date-end nil
+  "Last position where validation is known to be up to date.")
+(make-variable-buffer-local 'rng-validate-up-to-date-end)
+
+(defvar rng-conditional-up-to-date-start nil
+  "Marker for the start of the conditionally up-to-date region.
+Nil if there is no conditionally up-to-date region.  The conditionally
+up-to-date region must be such that for any cached state S with
+position P in the conditionally up-to-date region, if at some point it
+is determined that S becomes correct for P, then all states with
+position >= P in the conditionally up to date region must also then be
+correct and all errors between P and the end of the region must then
+be correctly marked.")
+(make-variable-buffer-local 'rng-conditional-up-to-date-start)
+
+(defvar rng-conditional-up-to-date-end nil
+  "Marker for the end of the conditionally up-to-date region.
+Nil if there is no conditionally up-to-date region.  See the variable
+`rng-conditional-up-to-date-start'.")
+(make-variable-buffer-local 'rng-conditional-up-to-date-end)
+
+(defvar rng-parsing-for-state nil
+  "Non-nil means we are currently parsing just to compute the state.
+Should be dynamically bound.")
+
+(defvar rng-validate-mode nil)
+(make-variable-buffer-local 'rng-validate-mode)
+
+(defvar rng-dtd nil)
+(make-variable-buffer-local 'rng-dtd)
+
+;;;###autoload
+(defun rng-validate-mode (&optional arg no-change-schema)
+  "Minor mode performing continual validation against a RELAX NG schema.
+
+Checks whether the buffer is a well-formed XML 1.0 document,
+conforming to the XML Namespaces Recommendation and valid against a
+RELAX NG schema. The mode-line indicates whether it is or not.  Any
+parts of the buffer that cause it not to be are considered errors and
+are highlighted with `rng-error-face'. A description of each error is
+available as a tooltip.  \\[rng-next-error] goes to the next error
+after point. Clicking mouse-1 on the word `Invalid' in the mode-line
+goes to the first error in the buffer. If the buffer changes, then it
+will be automatically rechecked when Emacs becomes idle; the
+rechecking will be paused whenever there is input pending..
+
+By default, uses a vacuous schema that allows any well-formed XML
+document. A schema can be specified explictly using
+\\[rng-set-schema-file-and-validate], or implicitly based on the buffer's
+file name or on the root element name.  In each case the schema must
+be a RELAX NG schema using the compact schema \(such schemas
+conventionally have a suffix of `.rnc').  The variable
+`rng-schema-locating-files' specifies files containing rules
+to use for finding the schema."
+  (interactive "P")
+  (setq rng-validate-mode
+	(if (null arg)
+	    (not rng-validate-mode)
+	  (> (prefix-numeric-value arg) 0)))
+  (save-restriction
+    (widen)
+    (nxml-with-unmodifying-text-property-changes
+      (rng-clear-cached-state (point-min) (point-max)))
+    ;; 1+ to clear empty overlays at (point-max)
+    (rng-clear-overlays (point-min) (1+ (point-max))))
+  (setq rng-validate-up-to-date-end 1)
+  (rng-clear-conditional-region)
+  (setq rng-error-count 0)
+  ;; do this here to avoid infinite loop if we set the schema
+  (remove-hook 'rng-schema-change-hook 'rng-validate-clear t)
+  (cond (rng-validate-mode
+	 (unwind-protect
+	     (save-excursion
+	       ;; An error can change the current buffer
+	       (when (or (not rng-current-schema)
+			 (and (eq rng-current-schema rng-any-element)
+			      (not no-change-schema)))
+		 (rng-auto-set-schema t)))
+	   (unless rng-current-schema (rng-set-schema-file-1 nil))
+	   (add-hook 'rng-schema-change-hook 'rng-validate-clear nil t)
+	   (add-hook 'after-change-functions 'rng-after-change-function nil t)
+	   (add-hook 'kill-buffer-hook 'rng-kill-timers nil t)
+	   (add-hook 'echo-area-clear-hook 'rng-echo-area-clear-function nil t)
+	   (add-hook 'post-command-hook 'rng-maybe-echo-error-at-point nil t)
+	   (rng-match-init-buffer)
+	   (rng-activate-timers)
+	   ;; Start validating right away if the buffer is visible.
+	   ;; If it's not visible, don't do this, because the user
+	   ;; won't get any progress indication. When the user finds
+	   ;; a new file, then the buffer won't be visible
+	   ;; when this is invoked.
+	   (when (get-buffer-window (current-buffer) 'visible)
+	     (rng-validate-while-idle (current-buffer)))))
+	(t
+	 (rng-cancel-timers)
+	 (force-mode-line-update)
+	 (remove-hook 'kill-buffer-hook 'rng-cancel-timers t)
+	 (remove-hook 'post-command-hook 'rng-maybe-echo-error-at-point t)
+	 (remove-hook 'echo-area-clear-hook 'rng-echo-area-clear-function t)
+	 (remove-hook 'after-change-functions 'rng-after-change-function t))))
+
+(defun rng-set-schema-file-and-validate (filename)
+  "Sets the schema and turns on `rng-validate-mode' if not already on.
+The schema is set like `rng-set-schema'."
+  (interactive "fSchema file: ")
+  (rng-set-schema-file filename)
+  (or rng-validate-mode (rng-validate-mode)))
+
+(defun rng-set-document-type-and-validate (type-id)
+  (interactive (list (rng-read-type-id)))
+  (and (rng-set-document-type type-id)
+       (or rng-validate-mode (rng-validate-mode))))
+    
+(defun rng-auto-set-schema-and-validate ()
+  "Set the schema for this buffer automatically and turn on `rng-validate-mode'.
+The schema is set like `rng-auto-set-schema'."
+  (interactive)
+  (rng-auto-set-schema)
+  (or rng-validate-mode (rng-validate-mode)))
+
+(defun rng-after-change-function (start end pre-change-len)
+  ;; Work around bug in insert-file-contents.
+  (when (> end (1+ (buffer-size)))
+    (setq start 1)
+    (setq end (1+ (buffer-size))))
+  (setq rng-message-overlay-inhibit-point nil)
+  (nxml-with-unmodifying-text-property-changes
+    (rng-clear-cached-state start end))
+  ;; rng-validate-up-to-date-end holds the position before the change
+  ;; Adjust it to reflect the change.
+  (if (< start rng-validate-up-to-date-end)
+      (setq rng-validate-up-to-date-end
+	    (if (<= (+ start pre-change-len) rng-validate-up-to-date-end)
+		(+ rng-validate-up-to-date-end
+		   (- end start pre-change-len))
+	      start)))
+  ;; Adjust the conditional zone
+  (cond (rng-conditional-up-to-date-start
+	 (when (< rng-conditional-up-to-date-start end)
+	   (if (< end rng-conditional-up-to-date-end)
+	       (set-marker rng-conditional-up-to-date-start end)
+	     (rng-clear-conditional-region))))
+	((< end rng-validate-up-to-date-end)
+	 (setq rng-conditional-up-to-date-end
+	       (copy-marker rng-validate-up-to-date-end nil))
+	 (setq rng-conditional-up-to-date-start
+	       (copy-marker end t))))
+  ;; Adjust rng-validate-up-to-date-end
+  (if (< start rng-validate-up-to-date-end)
+      (setq rng-validate-up-to-date-end start))
+  ;; Must make rng-validate-up-to-date-end < point-max
+  ;; (unless the buffer is empty).
+  ;; otherwise validate-prepare will say there's nothing to do.
+  ;; Don't use (point-max) because we may be narrowed.
+  (if (> rng-validate-up-to-date-end (buffer-size))
+      (setq rng-validate-up-to-date-end
+	    (max 1 (1- rng-validate-up-to-date-end))))
+  ;; Arrange to revalidate
+  (rng-activate-timers)
+  ;; Need to do this after activating the timer
+  (force-mode-line-update))
+
+(defun rng-compute-mode-line-string ()
+  (cond (rng-validate-timer
+	 (concat " Validated:"
+		 (number-to-string
+		  ;; Use floor rather than round because we want
+		  ;; to show 99% rather than 100% for changes near
+		  ;; the end.
+		  (floor (if (eq (buffer-size) 0)
+			     0.0
+			   (/ (* (- rng-validate-up-to-date-end 1) 100.0)
+			      (buffer-size)))))
+		 "%%"))
+	((> rng-error-count 0)
+	 (concat " "
+		 (propertize "Invalid"
+			     'help-echo "mouse-1: go to first error"
+			     'local-map (make-mode-line-mouse-map
+					 'mouse-1
+					 'rng-mouse-first-error))))
+	(t " Valid")))
+   
+(defun rng-cancel-timers ()
+  (let ((inhibit-quit t))
+    (when rng-validate-timer
+      (cancel-timer rng-validate-timer)
+      (setq rng-validate-timer nil))
+    (when rng-validate-quick-timer
+      (cancel-timer rng-validate-quick-timer)
+      (setq rng-validate-quick-timer nil))))
+
+(defun rng-kill-timers ()
+  ;; rng-validate-timer and rng-validate-quick-timer have the
+  ;; permanent-local property, so that the timers can be
+  ;; cancelled even after changing mode.
+  ;; This function takes care of cancelling the timers and
+  ;; then killing the local variables.
+  (when (local-variable-p 'rng-validate-timer)
+    (when rng-validate-timer
+      (cancel-timer rng-validate-timer))
+    (kill-local-variable 'rng-validate-timer))
+  (when (local-variable-p 'rng-validate-quick-timer)
+    (when rng-validate-quick-timer
+      (cancel-timer rng-validate-quick-timer))
+    (kill-local-variable 'rng-validate-quick-timer)))
+      
+(defun rng-activate-timers ()
+  (unless rng-validate-timer
+    (let ((inhibit-quit t))
+      (setq rng-validate-timer
+	    (run-with-idle-timer rng-validate-delay
+				 t
+				 'rng-validate-while-idle
+				 (current-buffer)))
+      (setq rng-validate-quick-timer
+	    (run-with-idle-timer rng-validate-quick-delay
+				 t
+				 'rng-validate-quick-while-idle
+				 (current-buffer))))))
+
+(defun rng-validate-clear ()
+  (rng-validate-mode 1 t))
+
+;; These two variables are dynamically bound and used
+;; to pass information between rng-validate-while-idle
+;; and rng-validate-while-idle-continue-p.
+
+(defvar rng-validate-display-point nil)
+(defvar rng-validate-display-modified-p nil)
+
+(defun rng-validate-while-idle-continue-p ()
+  ;; input-pending-p and sit-for run timers that are
+  ;; ripe.  Binding timer-idle-list to nil prevents
+  ;; this.  If we don't do this, then any ripe timers
+  ;; will get run, and we won't get any chance to
+  ;; validate until Emacs becomes idle again or until
+  ;; the other lower priority timers finish (which
+  ;; can take a very long time in the case of
+  ;; jit-lock).
+  (let ((timer-idle-list nil))
+    (and (not (input-pending-p))
+	 ;; Fake rng-validate-up-to-date-end so that the mode line
+	 ;; shows progress.  Also use this to save point.
+	 (let ((rng-validate-up-to-date-end (point)))
+	   (goto-char rng-validate-display-point)
+	   (when (not rng-validate-display-modified-p)
+	     (restore-buffer-modified-p nil))
+	   (force-mode-line-update)
+	   (let ((continue (sit-for 0)))
+	     (goto-char rng-validate-up-to-date-end)
+	     continue)))))
+
+;; Calling rng-do-some-validation once with a continue-p function, as
+;; opposed to calling it repeatedly, helps on initial validation of a
+;; large buffer with lots of errors.  The overlays for errors will all
+;; get added when rng-do-some-validation returns and won't slow the
+;; validation process down.
+
+(defun rng-validate-while-idle (buffer)
+  (with-current-buffer buffer
+    (if rng-validate-mode
+	(if (let ((rng-validate-display-point (point))
+		  (rng-validate-display-modified-p (buffer-modified-p)))
+	      (rng-do-some-validation 'rng-validate-while-idle-continue-p))
+	    (force-mode-line-update)
+	  (rng-validate-done))
+      ;; must have done kill-all-local-variables
+      (rng-kill-timers))))
+
+(defun rng-validate-quick-while-idle (buffer)
+  (with-current-buffer buffer
+    (if rng-validate-mode
+	(if (rng-do-some-validation)
+	    (force-mode-line-update)
+	  (rng-validate-done))
+      ;; must have done kill-all-local-variables
+      (rng-kill-timers))))
+
+(defun rng-validate-done ()
+  (when (or (not (current-message))
+	    (rng-current-message-from-error-overlay-p))
+    (rng-error-overlay-message (or (rng-error-overlay-after (point)) 
+				   (rng-error-overlay-after (1- (point))))))
+  (rng-cancel-timers)
+  (force-mode-line-update))
+
+(defun rng-do-some-validation (&optional continue-p-function)
+  "Do some validation work. Return t if more to do, nil otherwise."
+  (save-excursion
+    (save-restriction
+      (widen)
+      (nxml-with-invisible-motion
+	(condition-case err
+	    (and (rng-validate-prepare)
+		 (let ((rng-dt-namespace-context-getter '(nxml-ns-get-context)))
+		   (nxml-with-unmodifying-text-property-changes
+		     (rng-do-some-validation-1 continue-p-function))))
+	  ;; errors signalled from a function run by an idle timer
+	  ;; are ignored; if we don't catch them, validation
+	  ;; will get mysteriously stuck at a single place
+	  (rng-compile-error
+	   (message "Incorrect schema. %s" (nth 1 err))
+	   (rng-validate-mode 0)
+	   nil)
+	  (error
+	   (message "Internal error in rng-validate-mode triggered at buffer position %d. %s"
+		    (point)
+		    (error-message-string err))
+	   (rng-validate-mode 0)
+	   nil))))))
+
+(defun rng-validate-prepare ()
+  "Prepare to do some validation, initializing point and the state.
+Return t if there is work to do, nil otherwise."
+  (cond ((= rng-validate-up-to-date-end (point-min))
+	 (rng-set-initial-state)
+	 t)
+	((= rng-validate-up-to-date-end (point-max))
+	 nil)
+	(t (let ((state (get-text-property (1- rng-validate-up-to-date-end)
+					   'rng-state)))
+	     (cond (state
+		    (rng-restore-state state)
+		    (goto-char rng-validate-up-to-date-end))
+		   (t
+		    (let ((pos (previous-single-property-change
+				rng-validate-up-to-date-end
+				'rng-state)))
+		      (cond (pos
+			     (rng-restore-state
+			      (or (get-text-property (1- pos) 'rng-state)
+				  (error "Internal error: state null")))
+			     (goto-char pos))
+			    (t (rng-set-initial-state))))))))))
+
+
+(defun rng-do-some-validation-1 (&optional continue-p-function)
+  (let ((limit (+ rng-validate-up-to-date-end
+		  rng-validate-chunk-size))
+	(remove-start rng-validate-up-to-date-end)
+	(next-cache-point (+ (point) rng-state-cache-distance))
+	(continue t)
+	(xmltok-dtd rng-dtd)
+	have-remaining-chars
+	xmltok-type
+	xmltok-start
+	xmltok-name-colon
+	xmltok-name-end
+	xmltok-replacement
+	xmltok-attributes
+	xmltok-namespace-attributes
+	xmltok-dependent-regions
+	xmltok-errors)
+    (when (= (point) 1)
+      (let ((regions (xmltok-forward-prolog)))
+	(rng-clear-overlays 1 (point))
+	(while regions
+	  (when (eq (aref (car regions) 0) 'encoding-name)
+	    (rng-process-encoding-name (aref (car regions) 1)
+				       (aref (car regions) 2)))
+	  (setq regions (cdr regions))))
+      (unless (equal rng-dtd xmltok-dtd)
+	(rng-clear-conditional-region))
+      (setq rng-dtd xmltok-dtd))
+    (while continue
+      (setq have-remaining-chars (rng-forward))
+      (let ((pos (point)))
+	(setq continue
+	      (and have-remaining-chars
+		   (or (< pos limit)
+		       (and continue-p-function
+			    (funcall continue-p-function)
+			    (setq limit (+ limit rng-validate-chunk-size))
+			    t))))
+	(cond ((and rng-conditional-up-to-date-start
+		    ;; > because we are getting the state from (1- pos)
+		    (> pos rng-conditional-up-to-date-start)
+		    (< pos rng-conditional-up-to-date-end)
+		    (rng-state-matches-current (get-text-property (1- pos)
+								  'rng-state)))
+	       (when (< remove-start (1- pos))
+		 (rng-clear-cached-state remove-start (1- pos)))
+	       ;; sync up with cached validation state
+	       (setq continue nil)
+	       ;; do this before settting rng-validate-up-to-date-end
+	       ;; in case we get a quit
+	       (rng-mark-xmltok-errors)
+	       (rng-mark-xmltok-dependent-regions)
+	       (setq rng-validate-up-to-date-end
+		     (marker-position rng-conditional-up-to-date-end))
+	       (rng-clear-conditional-region)
+	       (setq have-remaining-chars
+		     (< rng-validate-up-to-date-end (point-max))))
+	      ((or (>= pos next-cache-point)
+		   (not continue))
+	       (setq next-cache-point (+ pos rng-state-cache-distance))
+	       (rng-clear-cached-state remove-start pos)
+	       (when have-remaining-chars
+		 (rng-cache-state (1- pos)))
+	       (setq remove-start pos)
+	       (unless continue
+		 ;; if we have just blank chars skip to the end
+		 (when have-remaining-chars
+		   (skip-chars-forward " \t\r\n")
+		   (when (= (point) (point-max))
+		     (rng-clear-overlays pos (point))
+		     (rng-clear-cached-state pos (point))
+		     (setq have-remaining-chars nil)
+		     (setq pos (point))))
+		 (when (not have-remaining-chars)
+		   (rng-process-end-document))
+		 (rng-mark-xmltok-errors)
+		 (rng-mark-xmltok-dependent-regions)
+		 (setq rng-validate-up-to-date-end pos)
+		 (when rng-conditional-up-to-date-end
+		   (cond ((<= rng-conditional-up-to-date-end pos)
+			  (rng-clear-conditional-region))
+			 ((< rng-conditional-up-to-date-start pos)
+			  (set-marker rng-conditional-up-to-date-start
+				      pos)))))))))
+    have-remaining-chars))
+    
+(defun rng-clear-conditional-region ()
+  (when rng-conditional-up-to-date-start
+    (set-marker rng-conditional-up-to-date-start nil)
+    (setq rng-conditional-up-to-date-start nil))
+  (when rng-conditional-up-to-date-end
+    (set-marker rng-conditional-up-to-date-end nil)
+    (setq rng-conditional-up-to-date-end nil)))
+
+(defun rng-clear-cached-state (start end)
+  "Clear cached state between START and END."
+  (remove-text-properties start end '(rng-state nil)))
+
+(defun rng-cache-state (pos)
+  "Save the current state in a text property on the character at pos."
+  (put-text-property pos
+		     (1+ pos)
+		     'rng-state
+		     (rng-get-state)))
+
+(defun rng-state-matches-current (state)
+  (and state
+       (rng-match-state-equal (car state))
+       (nxml-ns-state-equal (nth 1 state))
+       (equal (nth 2 state) rng-open-elements)))
+
+(defun rng-get-state ()
+  (list (rng-match-state)
+	(nxml-ns-state)
+	rng-open-elements))
+
+(defun rng-restore-state (state)
+  (rng-set-match-state (car state))
+  (setq state (cdr state))
+  (nxml-ns-set-state (car state))
+  (setq rng-open-elements (cadr state))
+  (setq rng-pending-contents nil)
+  (setq rng-collecting-text (rng-match-text-typed-p)))
+
+(defun rng-set-initial-state ()
+  (nxml-ns-init)
+  (rng-match-start-document)
+  (setq rng-open-elements nil)
+  (setq rng-pending-contents nil)
+  (goto-char (point-min)))
+
+(defun rng-clear-overlays (beg end)
+  (unless rng-parsing-for-state
+    (let ((overlays (overlays-in beg end)))
+      (while overlays
+	(let* ((overlay (car overlays))
+	       (category (overlay-get overlay 'category)))
+	  (cond ((eq category 'rng-error)
+		 (let ((inhibit-quit t))
+		   (when (eq overlay rng-message-overlay)
+		     (rng-error-overlay-message nil))
+		   (delete-overlay overlay)
+		   ;; rng-error-count could be nil
+		   ;; if overlays left over from a previous use
+		   ;; of rng-validate-mode that ended with a change of mode
+		   (when rng-error-count
+		     (setq rng-error-count (1- rng-error-count)))))
+		((and (eq category 'rng-dependent)
+		      (<= beg (overlay-start overlay)))
+		 (delete-overlay overlay))))
+	(setq overlays (cdr overlays))))))
+
+;;; Dependent regions
+
+(defun rng-mark-xmltok-dependent-regions ()
+  (while xmltok-dependent-regions
+    (apply 'rng-mark-xmltok-dependent-region
+	   (car xmltok-dependent-regions))
+    (setq xmltok-dependent-regions
+	  (cdr xmltok-dependent-regions))))
+
+(defun rng-mark-xmltok-dependent-region (fun start end &rest args)
+  (let ((overlay (make-overlay start end nil t t)))
+    (overlay-put overlay 'category 'rng-dependent)
+    (overlay-put overlay 'rng-funargs (cons fun args))))
+
+(put 'rng-dependent 'evaporate t)
+(put 'rng-dependent 'modification-hooks '(rng-dependent-region-changed))
+(put 'rng-dependent 'insert-behind-hooks '(rng-dependent-region-changed))
+
+(defun rng-dependent-region-changed (overlay
+				     after-p
+				     change-start
+				     change-end
+				     &optional pre-change-length)
+  (when (and after-p
+	     ;; Emacs sometimes appears to call deleted overlays
+	     (overlay-start overlay)
+	     (let ((funargs (overlay-get overlay 'rng-funargs)))
+	       (save-match-data
+		 (save-excursion
+		   (save-restriction
+		     (widen)
+		     (apply (car funargs)
+			    (append (list change-start
+					  change-end
+					  pre-change-length
+					  (overlay-start overlay)
+					  (overlay-end overlay))
+				    (cdr funargs))))))))
+    (rng-after-change-function (overlay-start overlay)
+			       change-end
+			       (+ pre-change-length
+				  (- (overlay-start overlay)
+				     change-start)))
+    (delete-overlay overlay)))
+
+;;; Error state
+
+(defun rng-mark-xmltok-errors ()
+  (while xmltok-errors
+    (let ((err (car xmltok-errors)))
+      (rng-mark-not-well-formed (xmltok-error-message err)
+				(xmltok-error-start err)
+				(xmltok-error-end err)))
+    (setq xmltok-errors (cdr xmltok-errors))))
+
+(defun rng-mark-invalid (message beg end)
+  (rng-mark-error message beg end))
+
+(defun rng-mark-not-well-formed (message beg end)
+  ;; Don't try to validate further
+  ;;(rng-set-match-state rng-not-allowed-ipattern)
+  (rng-mark-error message beg end))
+
+(defun rng-mark-error (message beg end)
+  (unless rng-parsing-for-state
+    (let ((overlays (overlays-in beg end)))
+      (while (and overlays message)
+	(let ((o (car overlays)))
+	  (when (and (eq (overlay-get o 'category) 'rng-error)
+		     (= (overlay-start o) beg)
+		     (= (overlay-end o) end))
+	    (overlay-put o
+			 'help-echo
+			 (concat (overlay-get o 'help-echo)
+				 "\n"
+				 message))
+	    (setq message nil)))
+	(setq overlays (cdr overlays))))
+    (when message
+      (let ((inhibit-quit t))
+	(setq rng-error-count (1+ rng-error-count))
+	(let ((overlay
+	       (make-overlay beg end nil t
+			     ;; Need to make the rear delimiter advance
+			     ;; with the front delimiter when the overlay
+			     ;; is empty, otherwise the front delimiter
+			     ;; will move past the rear delimiter.
+			     (= beg end))))
+	  ;; Ensure when we have two overlapping messages, the help-echo
+	  ;; of the one that starts first is shown
+	  (overlay-put overlay 'priority beg)
+	  (overlay-put overlay 'category 'rng-error)
+	  (overlay-put overlay 'help-echo message))))))
+
+(put 'rng-error 'face 'rng-error-face)
+(put 'rng-error 'modification-hooks '(rng-error-modified))
+
+;; If we don't do this, then the front delimiter can move
+;; past the end delimiter.
+(defun rng-error-modified (overlay after-p beg end &optional pre-change-len)
+  (when (and after-p
+	     (overlay-start overlay)	; check not deleted
+	     (>= (overlay-start overlay)
+		 (overlay-end overlay)))
+    (let ((inhibit-quit t))
+      (delete-overlay overlay)
+      (setq rng-error-count (1- rng-error-count)))))
+
+(defun rng-echo-area-clear-function ()
+  (setq rng-message-overlay-current nil))
+
+;;; Error navigation
+	     
+(defun rng-maybe-echo-error-at-point ()
+  (when (or (not (current-message))
+	    (rng-current-message-from-error-overlay-p))
+    (rng-error-overlay-message (rng-error-overlay-after (point)))))
+
+(defun rng-error-overlay-after (pos)
+  (let ((overlays (overlays-in pos (1+ pos)))
+	(best nil))
+    (while overlays
+      (let ((overlay (car overlays)))
+	(when (and (eq (overlay-get overlay 'category)
+		       'rng-error)
+		   (or (not best)
+		       (< (overlay-start best)
+			  (overlay-start overlay))))
+	  (setq best overlay)))
+      (setq overlays (cdr overlays)))
+    best))
+
+(defun rng-first-error ()
+  "Go to the first validation error.
+Turn on `rng-validate-mode' if it is not already on."
+  (interactive)
+  (or rng-validate-mode (rng-validate-mode))
+  (when (and (eq rng-validate-up-to-date-end 1)
+	     (< rng-validate-up-to-date-end (point-max)))
+    (rng-do-some-validation))
+  (let ((err (rng-find-next-error-overlay (1- (point-min)))))
+    (if err
+	(rng-goto-error-overlay err)
+      (let ((pos (save-excursion
+		   (goto-char (point-min))
+		   (rng-next-error 1))))
+	(when pos
+	  (goto-char pos))))))
+
+(defun rng-mouse-first-error (event)
+  "Go to the first validation error from a mouse click."
+  (interactive "e")
+  (select-window (posn-window (event-start event)))
+  (rng-first-error))
+
+(defun rng-next-error (arg)
+  "Go to the next validation error after point.
+Turn on `rng-validate-mode' if it is not already on.
+A prefix ARG specifies how many errors to move. A negative ARG
+moves backwards. Just \\[universal-argument] as a prefix
+means goto the first error."
+  (interactive "P")
+  (if (consp arg)
+      (rng-first-error)
+    (or rng-validate-mode (rng-validate-mode))
+    (setq arg (prefix-numeric-value arg))
+    (if (< arg 0)
+	(rng-previous-error-1 (- arg))
+      (rng-next-error-1 arg))))
+
+(defun rng-previous-error (arg)
+  "Go to the previous validation error before point.
+Turn on `rng-validate-mode' if it is not already on.
+A prefix ARG specifies how many errors to move. A negative ARG
+moves forwards. Just \\[universal-argument] as a prefix
+means goto the first error."
+  (interactive "P")
+  (if (consp arg)
+      (rng-first-error)
+    (or rng-validate-mode (rng-validate-mode))
+    (setq arg (prefix-numeric-value arg))
+    (if (< arg 0)
+	(rng-next-error-1 (- arg))
+      (rng-previous-error-1 arg))))
+
+(defun rng-next-error-1 (arg)
+  (let* ((pos (point))
+	 err last-err)
+    (while (and (> arg 0)
+		(setq err (rng-find-next-error-overlay pos)))
+      (setq arg (1- arg))
+      (setq last-err err)
+      (setq pos (overlay-start err)))
+    (when (> arg 0)
+      (setq pos (max pos (1- rng-validate-up-to-date-end)))      
+      (when (< rng-validate-up-to-date-end (point-max))
+	(message "Parsing...")
+	(while (let ((more-to-do (rng-do-some-validation)))
+		 (while (and (> arg 0)
+			     (setq err (rng-find-next-error-overlay pos)))
+		   (setq arg (1- arg))
+		   (setq last-err err)
+		   (setq pos (overlay-start err)))
+		 (when (and (> arg 0)
+			    more-to-do
+			    (< rng-validate-up-to-date-end (point-max)))
+		   ;; Display percentage validated.
+		   (force-mode-line-update)
+		   ;; Force redisplay but don't allow idle timers to run.
+		   (let ((timer-idle-list nil))
+		     (sit-for 0))
+		   (setq pos
+			 (max pos (1- rng-validate-up-to-date-end)))
+		   t)))))
+    (if last-err
+	(rng-goto-error-overlay last-err)
+      (message "No more errors")
+      nil)))
+
+(defun rng-previous-error-1 (arg)
+  (let* ((pos (point))
+	 err last-err)
+    (while (and (> arg 0)
+		(setq err (rng-find-previous-error-overlay pos)))
+      (setq pos (overlay-start err))
+      (setq last-err err)
+      (setq arg (1- arg)))
+    (when (and (> arg 0)
+	       (< rng-validate-up-to-date-end (min pos (point-max))))
+      (message "Parsing...")
+      (while (and (rng-do-some-validation)
+		  (< rng-validate-up-to-date-end (min pos (point-max))))
+	(force-mode-line-update)
+	;; Force redisplay but don't allow idle timers to run.
+	(let ((timer-idle-list nil))
+	  (sit-for 0)))
+      (while (and (> arg 0)
+		  (setq err (rng-find-previous-error-overlay pos)))
+	(setq pos (overlay-start err))
+	(setq last-err err)
+	(setq arg (1- arg))))
+    (if last-err
+	(rng-goto-error-overlay last-err)
+      (message "No previous errors")
+      nil)))
+      
+(defun rng-goto-error-overlay (err)
+  "Goto the start of error overlay ERR and print its message."
+  (goto-char (overlay-start err))
+  (setq rng-message-overlay-inhibit-point nil)
+  (rng-error-overlay-message err))
+
+(defun rng-error-overlay-message (err)
+  (if err
+      (unless (or (and (eq rng-message-overlay-inhibit-point (point))
+		       (eq rng-message-overlay err))
+		  (= (point-max) 1))
+	(message "%s" (overlay-get err 'help-echo))
+	(setq rng-message-overlay-current t)
+	(setq rng-message-overlay-inhibit-point (point)))
+    (when (rng-current-message-from-error-overlay-p)
+      (message nil))
+    (setq rng-message-overlay-inhibit-point nil))
+  (setq rng-message-overlay err))
+
+(defun rng-current-message-from-error-overlay-p ()
+  (and rng-message-overlay-current
+       rng-message-overlay
+       (equal (overlay-get rng-message-overlay 'help-echo)
+	      (current-message))))
+
+(defun rng-find-next-error-overlay (pos)
+  "Return the overlay for the next error starting after POS.
+Return nil if there is no such overlay or it is out of date.
+Do not do any additional validation."
+  (when rng-error-count
+    (let (done found overlays)
+      (while (not done)
+	(cond (overlays
+	       (let ((overlay (car overlays)))
+		 (setq overlays (cdr overlays))
+		 (when (and (eq (overlay-get overlay 'category) 'rng-error)
+			    ;; Is it the first?
+			    (= (overlay-start overlay) pos)
+			    ;; Is it up to date?
+			    (<= (overlay-end overlay)
+				rng-validate-up-to-date-end))
+		   (setq done t)
+		   (setq found overlay))))
+	      ((or (= pos (point-max))
+		   (> (setq pos (next-overlay-change pos))
+		      rng-validate-up-to-date-end))
+	       (setq done t))
+	      (t (setq overlays (overlays-in pos (1+ pos))))))
+      found)))
+
+(defun rng-find-previous-error-overlay (pos)
+  "Return the overlay for the last error starting before POS.
+Return nil if there is no such overlay or it is out of date.
+Do not do any additional validation."
+  (when (and rng-error-count
+	     (<= pos rng-validate-up-to-date-end))
+    (let (done found overlays)
+      (while (not done)
+	(cond (overlays
+	       (let ((overlay (car overlays)))
+		 (setq overlays (cdr overlays))
+		 (when (and (eq (overlay-get overlay 'category) 'rng-error)
+			    ;; Is it the first?
+			    (= (overlay-start overlay) pos))
+		   (setq done t)
+		   (setq found overlay))))
+	      ((= pos (point-min))
+	       (setq done t))
+	      (t
+	       (setq pos (previous-overlay-change pos))
+	       (setq overlays (overlays-in pos (1+ pos))))))
+      found)))
+
+;;; Parsing
+
+(defun rng-forward (&optional limit)
+  "Move forward over one or more tokens updating the state.
+If LIMIT is nil, stop after tags.
+If LIMIT is non-nil, stop when end of last token parsed is >= LIMIT.
+Return nil at end of buffer, t otherwise."
+  (let (type)
+    (while (progn
+	     (setq type (xmltok-forward))
+	     (rng-clear-overlays xmltok-start (point))
+	     (let ((continue
+		    (cond ((eq type 'start-tag)
+			   (rng-process-start-tag 'start-tag)
+			   nil)
+			  ((eq type 'end-tag)
+			   (rng-process-end-tag)
+			   nil)
+			  ((eq type 'empty-element)
+			   (rng-process-start-tag 'empty-element)
+			   nil)
+			  ((eq type 'space)
+			   (rng-process-text xmltok-start nil t)
+			   t)
+			  ((eq type 'data)
+			   (rng-process-text xmltok-start nil nil)
+			   t)
+			  ((memq type '(entity-ref char-ref))
+			   (cond (xmltok-replacement
+				  (rng-process-text xmltok-start
+						    nil
+						    'maybe
+						    xmltok-replacement))
+				 ((eq type 'char-ref)
+				  (rng-process-unknown-char))
+				 (t
+				  (rng-process-unknown-entity)))
+			   t)
+			  ((eq type 'cdata-section)
+			   (rng-process-text (+ xmltok-start 9)	; "<![CDATA["
+					     (- (point) 3) ; "]]>"
+					     'maybe)
+			   t)
+			  ((eq type 'partial-start-tag)
+			   (rng-process-start-tag 'partial-start-tag)
+			   t)
+			  ((eq type 'partial-empty-element)
+			   (rng-process-start-tag 'empty-element)
+			   t)
+			  ((eq type 'partial-end-tag)
+			   (rng-process-end-tag 'partial)
+			   t)
+			  (t type))))
+	       (if limit
+		   (< (point) limit)
+		 continue))))
+    (and type t)))
+
+(defun rng-process-start-tag (tag-type)
+  "TAG-TYPE is `start-tag' for a start-tag, `empty-element' for
+an empty element.  partial-empty-element should be passed
+as empty-element."
+  (and rng-collecting-text (rng-flush-text))
+  (setq rng-collecting-text nil)
+  (setq rng-pending-contents nil)
+  (rng-process-namespaces)
+  (let ((tag (rng-process-tag-name)))
+    (rng-process-attributes)
+    ;; set the state appropriately
+    (cond ((eq tag-type 'empty-element)
+	   (rng-process-start-tag-close)
+	   ;; deal with missing content with empty element
+	   (when (not (rng-match-empty-content))
+	     (rng-match-after)
+	     (rng-mark-start-tag-close "Empty content not allowed"))
+	   (nxml-ns-pop-state))
+	  ((eq tag-type 'start-tag)
+	   (rng-process-start-tag-close)
+	   (setq rng-collecting-text (rng-match-text-typed-p))
+	   (rng-push-tag tag))
+	  ((eq tag-type 'partial-start-tag)
+	   (rng-process-start-tag-close)
+	   (rng-match-after)
+	   (nxml-ns-pop-state)))))
+
+(defun rng-process-namespaces ()
+  (let ((nsatts xmltok-namespace-attributes)
+	prefixes)
+    (nxml-ns-push-state)
+    (while nsatts
+      (let* ((att (car nsatts))
+	     (value (xmltok-attribute-value att)))
+	(when value
+	  (let ((ns (nxml-make-namespace value))
+		(prefix (and (xmltok-attribute-prefix att)
+			     (xmltok-attribute-local-name att))))
+	    (cond ((member prefix prefixes)
+		   (rng-mark-invalid "Duplicate namespace declaration"
+				     (xmltok-attribute-name-start att)
+				     (xmltok-attribute-name-end att)))
+		  ((not prefix)
+		   (nxml-ns-set-default ns))
+		  (ns
+		   (nxml-ns-set-prefix prefix ns))
+		  (t
+		   ;; cannot have xmlns:foo=""
+		   (rng-mark-invalid "Namespace prefix cannot be undeclared"
+				     (1- (xmltok-attribute-value-start att))
+				     (1+ (xmltok-attribute-value-end att)))))
+	    (setq prefixes (cons prefix prefixes)))))
+      (setq nsatts (cdr nsatts)))))
+
+(defun rng-process-tag-name ()
+  (let* ((prefix (xmltok-start-tag-prefix))
+	 (local-name (xmltok-start-tag-local-name))
+	 (name
+	  (if prefix
+	      (let ((ns (nxml-ns-get-prefix prefix)))
+		(cond (ns (cons ns local-name))
+		      ((and (setq ns
+				  (rng-match-infer-start-tag-namespace
+				   local-name))
+			    (rng-match-start-tag-open (cons ns local-name)))
+		       (nxml-ns-set-prefix prefix ns)
+		       (rng-mark-start-tag-close "Missing xmlns:%s=\"%s\""
+						 prefix
+						 (nxml-namespace-name ns))
+		       nil)
+		      (t
+		       (rng-recover-bad-element-prefix)
+		       nil)))
+	    (cons (nxml-ns-get-default) local-name))))
+    (when (and name
+	       (not (rng-match-start-tag-open name)))
+      (unless (and (not (car name))
+		   (let ((ns (rng-match-infer-start-tag-namespace (cdr name))))
+		     (and ns
+			  (rng-match-start-tag-open (cons ns local-name))
+			  (progn
+			    (nxml-ns-set-default ns)
+			    ;; XXX need to check we don't have xmlns=""
+			    (rng-mark-start-tag-close "Missing xmlns=\"%s\""
+						      (nxml-namespace-name ns))
+			    t))))
+	(rng-recover-start-tag-open name)))
+    (cons prefix local-name)))
+
+(defun rng-process-attributes ()
+  (let ((atts xmltok-attributes)
+	names)
+    (while atts
+      (let* ((att (car atts))
+	     (prefix (xmltok-attribute-prefix att))
+	     (local-name (xmltok-attribute-local-name att))
+	     (name
+	      (if prefix
+		  (let ((ns (nxml-ns-get-prefix prefix)))
+		    (and ns
+			 (cons ns local-name)))
+		(cons nil local-name))))
+	(cond ((not name)
+	       (rng-recover-bad-attribute-prefix att))
+	      ((member name names)
+	       (rng-recover-duplicate-attribute-name att))
+	      ((not (rng-match-attribute-name name))
+	       (rng-recover-attribute-name att))
+	      ((rng-match-text-typed-p)
+	       (let ((value (xmltok-attribute-value att)))
+		 (if value
+		     (or (rng-match-attribute-value value)
+			 (rng-recover-attribute-value att))
+		   (rng-match-after))))
+	      (t (or (rng-match-end-tag)
+		     (error "Internal error:\
+ invalid on untyped attribute value"))))
+	(setq names (cons name names)))
+      (setq atts (cdr atts)))))
+
+(defun rng-process-start-tag-close ()
+  ;; deal with missing attributes
+  (unless (rng-match-start-tag-close)
+    (rng-mark-start-tag-close (rng-missing-attributes-message))
+    (rng-match-ignore-attributes)))
+
+(defun rng-mark-start-tag-close (&rest args)
+  (when (not (eq xmltok-type 'partial-start-tag))
+    (rng-mark-invalid (apply 'format args)
+		      (- (point)
+			 (if (eq xmltok-type 'empty-element)
+			     2
+			   1))
+		      (point))))
+
+(defun rng-recover-bad-element-prefix ()
+  (rng-mark-invalid "Prefix not declared"
+		    (1+ xmltok-start)
+		    xmltok-name-colon)
+  (rng-match-unknown-start-tag-open))
+
+(defun rng-recover-bad-attribute-prefix (att)
+  (rng-mark-invalid "Prefix not declared"
+		    (xmltok-attribute-name-start att)
+		    (xmltok-attribute-name-colon att)))
+
+(defun rng-recover-duplicate-attribute-name (att)
+  (rng-mark-invalid "Duplicate attribute"
+		    (xmltok-attribute-name-start att)
+		    (xmltok-attribute-name-end att)))
+
+(defun rng-recover-start-tag-open (name)
+  (let ((required (rng-match-required-element-name)))
+    (cond ((and required
+		(rng-match-start-tag-open required)
+		(rng-match-after)
+		(rng-match-start-tag-open name))
+	   (rng-mark-invalid (concat "Missing element "
+				     (rng-quote-string
+				      (rng-name-to-string required)))
+			     xmltok-start
+			     (1+ xmltok-start)))
+	  ((and (rng-match-optionalize-elements)
+		(rng-match-start-tag-open name))
+	   (rng-mark-invalid "Required elements missing"
+			     xmltok-start
+			     (1+ xmltok-start)))
+	  ((rng-match-out-of-context-start-tag-open name)
+	   (rng-mark-invalid "Element not allowed in this context"
+			     (1+ xmltok-start)
+			     xmltok-name-end))
+	  (t
+	   (rng-match-unknown-start-tag-open)
+	   (rng-mark-invalid "Unknown element"
+			     (1+ xmltok-start)
+			     xmltok-name-end)))))
+
+(defun rng-recover-attribute-value (att)
+  (let ((start (xmltok-attribute-value-start att))
+	(end (xmltok-attribute-value-end att)))
+    (if (= start end)
+	(rng-mark-invalid "Empty attribute value invalid" start (1+ end))
+      (rng-mark-invalid "Attribute value invalid" start end)))
+  (rng-match-after))
+
+(defun rng-recover-attribute-name (att)
+  (rng-mark-invalid "Attribute not allowed"
+		    (xmltok-attribute-name-start att)
+		    (xmltok-attribute-name-end att)))
+
+(defun rng-missing-attributes-message ()
+  (let ((required-attributes
+	 (rng-match-required-attribute-names)))
+    (cond ((not required-attributes)
+	   "Required attributes missing")
+	  ((not (cdr required-attributes))
+	   (concat "Missing attribute "
+		   (rng-quote-string
+		    (rng-name-to-string (car required-attributes) t))))
+	  (t
+	   (concat "Missing attributes "
+		   (mapconcat (lambda (nm)
+				(rng-quote-string
+				 (rng-name-to-string nm t)))
+			      required-attributes
+			      ", "))))))
+      
+(defun rng-process-end-tag (&optional partial)
+  (cond ((not rng-open-elements)
+	 (rng-mark-not-well-formed "Extra end-tag"
+				   xmltok-start
+				   (point)))
+	((or partial
+	     (equal (cons (xmltok-end-tag-prefix)
+			  (xmltok-end-tag-local-name))
+		    (car rng-open-elements)))
+	 (rng-end-element))
+	(t (rng-recover-mismatched-end-tag))))
+
+(defun rng-end-element ()
+  (if rng-collecting-text
+      (let ((contents (rng-contents-string)))
+	(cond ((not contents) (rng-match-after))
+	      ((not (rng-match-element-value contents))
+	       (let* ((region (rng-contents-region)))
+		 (if (not region)
+		     (rng-mark-invalid "Empty content not allowed"
+				       xmltok-start
+				       (+ xmltok-start 2))
+		   (rng-mark-invalid "Invalid data"
+				     (car region)
+				     (cdr region))))
+	       (rng-match-after)))
+	(setq rng-collecting-text nil)
+	(setq rng-pending-contents nil))
+    (unless (rng-match-end-tag)
+       (rng-mark-invalid (rng-missing-element-message)
+			 xmltok-start
+			 (+ xmltok-start 2))
+       (rng-match-after)))
+  (nxml-ns-pop-state)
+  (when (eq (car rng-open-elements) t)
+    (rng-pop-tag))
+  (rng-pop-tag))
+
+(defun rng-missing-element-message ()
+  (let ((element (rng-match-required-element-name)))
+    (if element
+	(concat "Missing element "
+		(rng-quote-string (rng-name-to-string element)))
+      "Required child elements missing")))
+
+(defun rng-recover-mismatched-end-tag ()
+  (let* ((name (cons (xmltok-end-tag-prefix)
+		     (xmltok-end-tag-local-name))))
+    (cond ((member name (cdr rng-open-elements))
+	   (let* ((suppress-error (eq (car rng-open-elements) t))
+		  missing top)
+	     (while (progn
+		      (setq top (car rng-open-elements))
+		      (rng-pop-tag)
+		      (unless (eq top t)
+			(setq missing (cons top missing))
+			(nxml-ns-pop-state)
+			(rng-match-after))
+		      (not (equal top name))))
+	     (unless suppress-error
+	       (rng-mark-missing-end-tags (cdr missing)))))
+	  ((rng-match-empty-before-p)
+	   (rng-mark-mismatched-end-tag)
+	   (rng-end-element))
+	  (t (rng-mark-mismatched-end-tag)
+	     (setq rng-open-elements
+		   (cons t rng-open-elements))))))
+
+(defun rng-mark-missing-end-tags (missing)
+  (rng-mark-not-well-formed
+   (format "Missing end-tag%s %s"
+	   (if (null (cdr missing)) "" "s")
+	   (mapconcat (lambda (name)
+			(rng-quote-string
+			 (if (car name)
+			     (concat (car name)
+				     ":"
+				     (cdr name))
+			   (cdr name))))
+		      missing
+		      ", "))
+   xmltok-start
+   (+ xmltok-start 2)))
+
+(defun rng-mark-mismatched-end-tag ()
+  (rng-mark-not-well-formed "Mismatched end-tag"
+			    (+ xmltok-start 2)
+			    xmltok-name-end))
+
+(defun rng-push-tag (prefix-local-name)
+  (setq rng-open-elements
+	(cons prefix-local-name rng-open-elements)))
+
+(defun rng-pop-tag ()
+  (setq rng-open-elements (cdr rng-open-elements)))
+
+(defun rng-contents-string ()
+  (let ((contents rng-pending-contents))
+    (cond ((not contents) "")
+	  ((memq nil contents) nil)
+	  ((not (cdr contents))
+	   (rng-segment-string (car contents)))
+	  (t (apply 'concat
+		    (nreverse (mapcar 'rng-segment-string
+				      contents)))))))
+
+(defun rng-segment-string (segment)
+  (or (car segment)
+      (apply 'buffer-substring-no-properties
+	     (cdr segment))))
+
+(defun rng-segment-blank-p (segment)
+  (if (car segment)
+      (rng-blank-p (car segment))
+    (apply 'rng-region-blank-p
+	   (cdr segment))))
+
+(defun rng-contents-region ()
+  (if (null rng-pending-contents)
+      nil
+    (let* ((contents rng-pending-contents)
+	   (head (cdar contents))
+	   (start (car head))
+	   (end (cadr head)))
+      (while (setq contents (cdr contents))
+	(setq start (car (cdar contents))))
+      (cons start end))))
+
+(defun rng-process-text (start end whitespace &optional value)
+  "Process characters between position START and END as text.
+END nil means point. WHITESPACE t means known to be whitespace, nil
+means known not to be, anything else means unknown whether whitespace
+or not. END must not be nil if WHITESPACE is neither t nor nil.
+VALUE is a string or nil; nil means the value is equal to the
+string between START and END."
+  (cond (rng-collecting-text
+	 (setq rng-pending-contents (cons (list value start (or end (point)))
+					  rng-pending-contents)))
+	((not (or (and whitespace
+		       (or (eq whitespace t)
+			   (if value
+			       (rng-blank-p value)
+			     (rng-region-blank-p start end))))
+		  (rng-match-mixed-text)))
+	 (rng-mark-invalid "Text not allowed" start (or end (point))))))
+
+(defun rng-process-unknown-char ()
+  (when rng-collecting-text
+    (setq rng-pending-contents
+	  (cons nil rng-pending-contents))))
+
+(defun rng-process-unknown-entity ()
+  (rng-process-unknown-char)
+  (rng-match-optionalize-elements))
+
+(defun rng-region-blank-p (beg end)
+  (save-excursion
+    (goto-char beg)
+    (= (skip-chars-forward " \n\r\t" end)
+       (- end beg))))
+
+(defun rng-flush-text ()
+  (while rng-pending-contents
+    (let ((segment (car rng-pending-contents)))
+      (unless (or (rng-segment-blank-p segment)
+		  (rng-match-mixed-text))
+	(let ((region (cdr segment)))
+	  (rng-mark-invalid "In this context text cannot be mixed with elements"
+			    (car region)
+			    (cadr region)))))
+    (setq rng-pending-contents (cdr rng-pending-contents))))
+
+(defun rng-process-end-document ()
+  ;; this is necessary to clear empty overlays at (point-max)
+  (rng-clear-overlays (point) (point))
+  (let ((start (save-excursion
+		 (skip-chars-backward " \t\r\n")
+		 (point))))
+    (cond (rng-open-elements
+	   (unless (eq (car rng-open-elements) t)
+	     (rng-mark-not-well-formed "Missing end-tag"
+				       start
+				       (point))))
+	  ((not (rng-match-nullable-p))
+	   (rng-mark-not-well-formed "No document element"
+				     start
+				     (point))))))
+
+(defun rng-process-encoding-name (beg end)
+  (unless (let ((charset (buffer-substring-no-properties beg end)))
+	    (or (nxml-mime-charset-coding-system charset)
+		(string= (downcase charset) "utf-16")))
+    (rng-mark-not-well-formed "Unsupported encoding" beg end)))
+
+(defun rng-name-to-string (name &optional attributep)
+  (let ((ns (car name))
+	(local-name (cdr name)))
+    (if (or (not ns)
+	    (and (not attributep)
+		 (eq (nxml-ns-get-default) ns)))
+	local-name
+      (let ((prefix (nxml-ns-prefix-for ns)))
+	(if prefix
+	    (concat prefix ":" local-name)
+	  (concat "{" (symbol-name ns) "}" local-name))))))
+
+(provide 'rng-valid)
+
+;;; rng-valid.el ends here