summaryrefslogtreecommitdiff
path: root/lisp/nxml/rng-loc.el
diff options
context:
space:
mode:
authorMark A. Hershberger <mah@everybody.org>2007-11-23 06:58:00 +0000
committerMark A. Hershberger <mah@everybody.org>2007-11-23 06:58:00 +0000
commit8cd39fb3c4cf47d2464f00eaa69c587e17dd11cc (patch)
tree7bcd47a7dcbbad100dd3e8f8a7e08b48353c58a8 /lisp/nxml/rng-loc.el
parentf7cf8b2009b0bc2526d50c3455f737a543122dd4 (diff)
downloademacs-8cd39fb3c4cf47d2464f00eaa69c587e17dd11cc.tar.gz
Initial merge of nxml
Diffstat (limited to 'lisp/nxml/rng-loc.el')
-rw-r--r--lisp/nxml/rng-loc.el548
1 files changed, 548 insertions, 0 deletions
diff --git a/lisp/nxml/rng-loc.el b/lisp/nxml/rng-loc.el
new file mode 100644
index 00000000000..b81bfe009f8
--- /dev/null
+++ b/lisp/nxml/rng-loc.el
@@ -0,0 +1,548 @@
+;;; rng-loc.el --- locate the schema to use for validation
+
+;; 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:
+
+;;; Code:
+
+(require 'nxml-util)
+(require 'nxml-parse)
+(require 'rng-parse)
+(require 'rng-uri)
+(require 'rng-util)
+(require 'xmltok)
+
+(defvar rng-current-schema-file-name nil
+ "Filename of schema being used for current buffer.
+Nil if using a vacuous schema.")
+(make-variable-buffer-local 'rng-current-schema-file-name)
+
+(defvar rng-schema-locating-files-default nil
+ "Default value for variable `rng-schema-locating-files'.")
+
+(defvar rng-schema-locating-file-schema-file nil
+ "File containing schema for schema locating files.")
+
+(defvar rng-schema-locating-file-schema nil
+ "Schema for schema locating files or nil if not yet loaded.")
+
+(defcustom rng-schema-locating-files rng-schema-locating-files-default
+ "*List of schema locating files."
+ :type '(repeat file)
+ :group 'relax-ng)
+
+(defvar rng-schema-loader-alist nil
+ "Alist of schema extensions vs schema loader functions.")
+
+(defvar rng-cached-document-element nil)
+
+(defvar rng-document-type-history nil)
+
+(defun rng-set-document-type (type-id)
+ (interactive (list (rng-read-type-id)))
+ (condition-case err
+ (when (not (string= type-id ""))
+ (let ((schema-file (rng-locate-schema-file type-id)))
+ (unless schema-file
+ (error "Could not locate schema for type id `%s'" type-id))
+ (rng-set-schema-file-1 schema-file))
+ (rng-save-schema-location-1 t type-id)
+ (rng-what-schema))
+ (nxml-file-parse-error
+ (nxml-display-file-parse-error err))))
+
+(defun rng-read-type-id ()
+ (condition-case err
+ (let ((type-ids (rng-possible-type-ids))
+ (completion-ignore-case nil))
+ (completing-read "Document type id: "
+ (mapcar (lambda (x) (cons x nil))
+ type-ids)
+ nil
+ t
+ nil
+ 'rng-document-type-history))
+ (nxml-file-parse-error
+ (nxml-display-file-parse-error err))))
+
+(defun rng-set-schema-file (filename)
+ "Set the schema for the current buffer to the schema in FILENAME.
+FILENAME must be the name of a file containing a schema.
+The extension of FILENAME is used to determine what kind of schema it
+is. The variable `rng-schema-loader-alist' maps from schema
+extensions to schema loader functions. The function
+`rng-c-load-schema' is the loader for RELAX NG compact syntax. The
+association is between the buffer and the schema: the association is
+lost when the buffer is killed."
+ (interactive "fSchema file: ")
+ (condition-case err
+ (progn
+ (rng-set-schema-file-1 filename)
+ (rng-save-schema-location-1 t))
+ (nxml-file-parse-error
+ (nxml-display-file-parse-error err))))
+
+(defun rng-set-vacuous-schema ()
+ "Set the schema for the current buffer to allow any well-formed XML."
+ (interactive)
+ (rng-set-schema-file-1 nil)
+ (rng-what-schema))
+
+(defun rng-set-schema-file-1 (filename)
+ (setq filename (and filename (expand-file-name filename)))
+ (setq rng-current-schema
+ (if filename
+ (rng-load-schema filename)
+ rng-any-element))
+ (setq rng-current-schema-file-name filename)
+ (run-hooks 'rng-schema-change-hook))
+
+(defun rng-load-schema (filename)
+ (let* ((extension (file-name-extension filename))
+ (loader (cdr (assoc extension rng-schema-loader-alist))))
+ (or loader
+ (if extension
+ (error "No schema loader available for file extension `%s'"
+ extension)
+ (error "No schema loader available for null file extension")))
+ (funcall loader filename)))
+
+(defun rng-what-schema ()
+ "Display a message saying what schema `rng-validate-mode' is using."
+ (interactive)
+ (if rng-current-schema-file-name
+ (message "Using schema %s"
+ (abbreviate-file-name rng-current-schema-file-name))
+ (message "Using vacuous schema")))
+
+(defun rng-auto-set-schema (&optional no-display-error)
+ "Set the schema for this buffer based on the buffer's contents and file-name."
+ (interactive)
+ (condition-case err
+ (progn
+ (rng-set-schema-file-1 (rng-locate-schema-file))
+ (rng-what-schema))
+ (nxml-file-parse-error
+ (if no-display-error
+ (error "%s at position %s in %s"
+ (nth 3 err)
+ (nth 2 err)
+ (abbreviate-file-name (nth 1 err)))
+ (nxml-display-file-parse-error err)))))
+
+(defun rng-locate-schema-file (&optional type-id)
+ "Return the file-name of the schema to use for the current buffer.
+Return nil if no schema could be located.
+If TYPE-ID is non-nil, then locate the schema for this TYPE-ID."
+ (let* ((rng-cached-document-element nil)
+ (schema
+ (if type-id
+ (cons type-id nil)
+ (rng-locate-schema-file-using rng-schema-locating-files)))
+ files type-ids)
+ (while (consp schema)
+ (setq files rng-schema-locating-files)
+ (setq type-id (car schema))
+ (setq schema nil)
+ (when (member type-id type-ids)
+ (error "Type-id loop for type-id `%s'" type-id))
+ (setq type-ids (cons type-id type-ids))
+ (while (and files (not schema))
+ (setq schema
+ (rng-locate-schema-file-from-type-id type-id
+ (car files)))
+ (setq files (cdr files))))
+ (and schema
+ (rng-uri-file-name schema))))
+
+(defun rng-possible-type-ids ()
+ "Return a list of the known type IDs."
+ (let ((files rng-schema-locating-files)
+ type-ids)
+ (while files
+ (setq type-ids (rng-possible-type-ids-using (car files) type-ids))
+ (setq files (cdr files)))
+ (rng-uniquify-equal (sort type-ids 'string<))))
+
+(defun rng-locate-schema-file-using (files)
+ "Locate a schema using the schema locating files FILES.
+FILES is a list of file-names.
+Return either a URI, a list (TYPE-ID) where TYPE-ID is a string
+or nil."
+ (let (rules
+ ;; List of types that override normal order-based
+ ;; priority, most important first
+ preferred-types
+ ;; Best result found so far; same form as return value.
+ best-so-far)
+ (while (and (progn
+ (while (and (not rules) files)
+ (setq rules (rng-get-parsed-schema-locating-file
+ (car files)))
+ (setq files (cdr files)))
+ rules)
+ (or (not best-so-far) preferred-types))
+ (let* ((rule (car rules))
+ (rule-type (car rule))
+ (rule-matcher (get rule-type 'rng-rule-matcher)))
+ (setq rules (cdr rules))
+ (cond (rule-matcher
+ (when (and (or (not best-so-far)
+ (memq rule-type preferred-types)))
+ (setq best-so-far
+ (funcall rule-matcher (cdr rule)))
+ preferred-types)
+ (setq preferred-types
+ (nbutlast preferred-types
+ (length (memq rule-type preferred-types)))))
+ ((eq rule-type 'applyFollowingRules)
+ (when (not best-so-far)
+ (let ((prefer (cdr (assq 'ruleType (cdr rule)))))
+ (when (and prefer
+ (not (memq (setq prefer (intern prefer))
+ preferred-types)))
+ (setq preferred-types
+ (nconc preferred-types (list prefer)))))))
+ ((eq rule-type 'include)
+ (let ((uri (cdr (assq 'rules (cdr rule)))))
+ (when uri
+ (setq rules
+ (append (rng-get-parsed-schema-locating-file
+ (rng-uri-file-name uri))
+ rules))))))))
+ best-so-far))
+
+(put 'documentElement 'rng-rule-matcher 'rng-match-document-element-rule)
+(put 'namespace 'rng-rule-matcher 'rng-match-namespace-rule)
+(put 'uri 'rng-rule-matcher 'rng-match-uri-rule)
+(put 'transformURI 'rng-rule-matcher 'rng-match-transform-uri-rule)
+(put 'default 'rng-rule-matcher 'rng-match-default-rule)
+
+(defun rng-match-document-element-rule (props)
+ (let ((document-element (rng-document-element))
+ (prefix (cdr (assq 'prefix props)))
+ (local-name (cdr (assq 'localName props))))
+ (and (or (not prefix)
+ (if (= (length prefix) 0)
+ (not (nth 1 document-element))
+ (string= prefix (nth 1 document-element))))
+ (or (not local-name)
+ (string= local-name
+ (nth 2 document-element)))
+ (rng-match-default-rule props))))
+
+(defun rng-match-namespace-rule (props)
+ (let ((document-element (rng-document-element))
+ (ns (cdr (assq 'ns props))))
+ (and document-element
+ ns
+ (eq (nth 0 document-element)
+ (if (string= ns "")
+ nil
+ (nxml-make-namespace ns)))
+ (rng-match-default-rule props))))
+
+(defun rng-document-element ()
+ "Return a list (NS PREFIX LOCAL-NAME).
+NS is t if the document has a non-nil, but not otherwise known namespace."
+ (or rng-cached-document-element
+ (setq rng-cached-document-element
+ (save-excursion
+ (save-restriction
+ (widen)
+ (goto-char (point-min))
+ (let (xmltok-dtd)
+ (xmltok-save
+ (xmltok-forward-prolog)
+ (xmltok-forward)
+ (when (memq xmltok-type '(start-tag
+ partial-start-tag
+ empty-element
+ partial-empty-element))
+ (list (rng-get-start-tag-namespace)
+ (xmltok-start-tag-prefix)
+ (xmltok-start-tag-local-name))))))))))
+
+(defun rng-get-start-tag-namespace ()
+ (let ((prefix (xmltok-start-tag-prefix))
+ namespace att value)
+ (while xmltok-namespace-attributes
+ (setq att (car xmltok-namespace-attributes))
+ (setq xmltok-namespace-attributes (cdr xmltok-namespace-attributes))
+ (when (if prefix
+ (and (xmltok-attribute-prefix att)
+ (string= (xmltok-attribute-local-name att)
+ prefix))
+ (not (xmltok-attribute-prefix att)))
+ (setq value (xmltok-attribute-value att))
+ (setq namespace (if value (nxml-make-namespace value) t))))
+ (if (and prefix (not namespace))
+ t
+ namespace)))
+
+(defun rng-match-transform-uri-rule (props)
+ (let ((from-pattern (cdr (assq 'fromPattern props)))
+ (to-pattern (cdr (assq 'toPattern props)))
+ (file-name (buffer-file-name)))
+ (and file-name
+ (setq file-name (expand-file-name file-name))
+ (rng-file-name-matches-uri-pattern-p file-name from-pattern)
+ (condition-case ()
+ (let ((new-file-name
+ (replace-match
+ (save-match-data
+ (rng-uri-pattern-file-name-replace-match to-pattern))
+ t
+ nil
+ file-name)))
+ (and (file-name-absolute-p new-file-name)
+ (file-exists-p new-file-name)
+ (rng-file-name-uri new-file-name)))
+ (rng-uri-error nil)))))
+
+(defun rng-match-uri-rule (props)
+ (let ((resource (cdr (assq 'resource props)))
+ (pattern (cdr (assq 'pattern props)))
+ (file-name (buffer-file-name)))
+ (and file-name
+ (setq file-name (expand-file-name file-name))
+ (cond (resource
+ (condition-case ()
+ (eq (compare-strings (rng-uri-file-name resource)
+ 0
+ nil
+ (expand-file-name file-name)
+ 0
+ nil
+ nxml-file-name-ignore-case)
+ t)
+ (rng-uri-error nil)))
+ (pattern
+ (rng-file-name-matches-uri-pattern-p file-name
+ pattern)))
+ (rng-match-default-rule props))))
+
+(defun rng-file-name-matches-uri-pattern-p (file-name pattern)
+ (condition-case ()
+ (and (let ((case-fold-search nxml-file-name-ignore-case))
+ (string-match (rng-uri-pattern-file-name-regexp pattern)
+ file-name))
+ t)
+ (rng-uri-error nil)))
+
+(defun rng-match-default-rule (props)
+ (or (cdr (assq 'uri props))
+ (let ((type-id (cdr (assq 'typeId props))))
+ (and type-id
+ (cons (rng-collapse-space type-id) nil)))))
+
+(defun rng-possible-type-ids-using (file type-ids)
+ (let ((rules (rng-get-parsed-schema-locating-file file))
+ rule)
+ (while rules
+ (setq rule (car rules))
+ (setq rules (cdr rules))
+ (cond ((eq (car rule) 'typeId)
+ (let ((id (cdr (assq 'id (cdr rule)))))
+ (when id
+ (setq type-ids
+ (cons (rng-collapse-space id)
+ type-ids)))))
+ ((eq (car rule) 'include)
+ (let ((uri (cdr (assq 'rules (cdr rule)))))
+ (when uri
+ (setq type-ids
+ (rng-possible-type-ids-using
+ (rng-get-parsed-schema-locating-file
+ (rng-uri-file-name uri))
+ type-ids)))))))
+ type-ids))
+
+(defun rng-locate-schema-file-from-type-id (type-id file)
+ "Locate the schema for type id TYPE-ID using schema locating file FILE.
+Return either a URI, a list (TYPE-ID) where TYPE-ID is a string
+or nil."
+ (let ((rules (rng-get-parsed-schema-locating-file file))
+ schema rule)
+ (while (and rules (not schema))
+ (setq rule (car rules))
+ (setq rules (cdr rules))
+ (cond ((and (eq (car rule) 'typeId)
+ (let ((id (assq 'id (cdr rule))))
+ (and id
+ (string= (rng-collapse-space (cdr id)) type-id))))
+ (setq schema (rng-match-default-rule (cdr rule))))
+ ((eq (car rule) 'include)
+ (let ((uri (cdr (assq 'rules (cdr rule)))))
+ (when uri
+ (setq schema
+ (rng-locate-schema-file-from-type-id
+ type-id
+ (rng-uri-file-name uri))))))))
+ schema))
+
+(defvar rng-schema-locating-file-alist nil)
+
+(defun rng-get-parsed-schema-locating-file (file)
+ "Return a list of rules for the schema locating file FILE."
+ (setq file (expand-file-name file))
+ (let ((cached (assoc file rng-schema-locating-file-alist))
+ (mtime (nth 5 (file-attributes file)))
+ parsed)
+ (cond ((not mtime)
+ (when cached
+ (setq rng-schema-locating-file-alist
+ (delq cached rng-schema-locating-file-alist)))
+ nil)
+ ((and cached (equal (nth 1 cached) mtime))
+ (nth 2 cached))
+ (t
+ (setq parsed (rng-parse-schema-locating-file file))
+ (if cached
+ (setcdr cached (list mtime parsed))
+ (setq rng-schema-locating-file-alist
+ (cons (list file mtime parsed)
+ rng-schema-locating-file-alist)))
+ parsed))))
+
+(defconst rng-locate-namespace-uri
+ (nxml-make-namespace "http://thaiopensource.com/ns/locating-rules/1.0"))
+
+(defun rng-parse-schema-locating-file (file)
+ "Return list of rules.
+Each rule has the form (TYPE (ATTR . VAL) ...), where
+TYPE is a symbol for the element name, ATTR is a symbol for the attribute
+and VAL is a string for the value.
+Attribute values representing URIs are made absolute and xml:base
+attributes are removed."
+ (when (and (not rng-schema-locating-file-schema)
+ rng-schema-locating-file-schema-file)
+ (setq rng-schema-locating-file-schema
+ (rng-load-schema rng-schema-locating-file-schema-file)))
+ (let* ((element
+ (if rng-schema-locating-file-schema
+ (rng-parse-validate-file rng-schema-locating-file-schema
+ file)
+ (nxml-parse-file file)))
+ (children (cddr element))
+ (base-uri (rng-file-name-uri file))
+ child name rules atts att props prop-name prop-value)
+ (when (equal (car element)
+ (cons rng-locate-namespace-uri "locatingRules"))
+ (while children
+ (setq child (car children))
+ (setq children (cdr children))
+ (when (consp child)
+ (setq name (car child))
+ (when (eq (car name) rng-locate-namespace-uri)
+ (setq atts (cadr child))
+ (setq props nil)
+ (while atts
+ (setq att (car atts))
+ (when (stringp (car att))
+ (setq prop-name (intern (car att)))
+ (setq prop-value (cdr att))
+ (when (memq prop-name '(uri rules resource))
+ (setq prop-value
+ (rng-uri-resolve prop-value base-uri)))
+ (setq props (cons (cons prop-name prop-value)
+ props)))
+ (setq atts (cdr atts)))
+ (setq rules
+ (cons (cons (intern (cdr name)) (nreverse props))
+ rules))))))
+ (nreverse rules)))
+
+(defun rng-save-schema-location ()
+ "Save the association between the buffer's file and the current schema.
+This ensures that the schema that is currently being used will be used
+if the file is edited in a future session. The association will be
+saved to the first writable file in `rng-schema-locating-files'."
+ (interactive)
+ (rng-save-schema-location-1 nil))
+
+(defun rng-save-schema-location-1 (prompt &optional type-id)
+ (unless (or rng-current-schema-file-name type-id)
+ (error "Buffer is using a vacuous schema"))
+ (let ((files rng-schema-locating-files)
+ (document-file-name (buffer-file-name))
+ (schema-file-name rng-current-schema-file-name)
+ file)
+ (while (and files (not file))
+ (if (file-writable-p (car files))
+ (setq file (expand-file-name (car files)))
+ (setq files (cdr files))))
+ (cond ((not file)
+ (if prompt
+ nil
+ (error "No writable schema locating file configured")))
+ ((not document-file-name)
+ (if prompt
+ nil
+ (error "Buffer does not have a filename")))
+ ((and prompt
+ (not (y-or-n-p (format "Save %s to %s "
+ (if type-id
+ "type identifier"
+ "schema location")
+ file)))))
+ (t
+ (save-excursion
+ (set-buffer (find-file-noselect file))
+ (let ((modified (buffer-modified-p)))
+ (if (> (buffer-size) 0)
+ (let (xmltok-dtd)
+ (goto-char (point-min))
+ (xmltok-save
+ (xmltok-forward-prolog)
+ (xmltok-forward)
+ (unless (eq xmltok-type 'start-tag)
+ (error "Locating file `%s' invalid" file))))
+ (insert "<?xml version=\"1.0\"?>\n"
+ "<locatingRules xmlns=\""
+ (nxml-namespace-name rng-locate-namespace-uri)
+ "\">")
+ (let ((pos (point)))
+ (insert "\n</locatingRules>\n")
+ (goto-char pos)))
+ (insert "\n")
+ (insert (let ((locating-file-uri (rng-file-name-uri file)))
+ (format "<uri resource=\"%s\" %s=\"%s\"/>"
+ (rng-escape-string
+ (rng-relative-uri
+ (rng-file-name-uri document-file-name)
+ locating-file-uri))
+ (if type-id "typeId" "uri")
+ (rng-escape-string
+ (or type-id
+ (rng-relative-uri
+ (rng-file-name-uri schema-file-name)
+ locating-file-uri))))))
+ (indent-according-to-mode)
+ (when (or (not modified)
+ (y-or-n-p (format "Save file %s "
+ (buffer-file-name))))
+ (save-buffer))))))))
+
+(provide 'rng-loc)
+
+;;; rng-loc.el ends here