Mercurial > emacs
view lisp/textmodes/xml-lite.el @ 44380:aa703590324a
*** empty log message ***
author | Richard M. Stallman <rms@gnu.org> |
---|---|
date | Wed, 03 Apr 2002 15:33:17 +0000 |
parents | a87795faf65b |
children |
line wrap: on
line source
;;; xml-lite.el --- an indentation-engine for XML ;; Copyright (C) 2002 Free Software Foundation, Inc. ;; Author: Mike Williams <mdub@bigfoot.com> ;; Created: February 2001 ;; Keywords: xml ;; This file is part of GNU Emacs. ;; 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 GNU Emacs; see the file COPYING. If not, write to the ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, ;; Boston, MA 02111-1307, USA. ;;; Commentary: ;; ;; This package provides a simple indentation engine for XML. It is ;; intended for use in situations where the full power of the popular PSGML ;; package (DTD parsing, syntax checking) is not required. ;; ;; xml-lite is designed to be used in conjunction with the default GNU ;; Emacs sgml-mode, to provide a lightweight XML-editing environment. ;;; Thanks: ;; ;; Jens Schmidt <Jens.Schmidt@oracle.com> ;; for his feedback and suggestions ;; PLEASE NOTE! ;; xml-lite is on it's way out, as functionality is merged into ;; sgml-mode. ;;; Code: (eval-when-compile (require 'cl)) (require 'sgml-mode) ;; Syntax analysis (defsubst xml-lite-at-indentation-p () "Return true if point is at the first non-whitespace character on the line." (save-excursion (skip-chars-backward " \t") (bolp))) ;; Parsing (defstruct (xml-lite-tag (:constructor xml-lite-make-tag (type start end name))) type start end name) (defsubst xml-lite-parse-tag-name () "Skip past a tag-name, and return the name." (buffer-substring-no-properties (point) (progn (skip-syntax-forward "w_") (point)))) (defsubst xml-lite-looking-back-at (s) (let ((limit (max (- (point) (length s)) (point-min)))) (equal s (buffer-substring-no-properties limit (point))))) (defsubst xml-lite-looking-at (s) (let ((limit (min (+ (point) (length s))))) (equal s (buffer-substring-no-properties (point) limit)))) (defun xml-lite-parse-tag-backward () "Parse an SGML tag backward, and return information about the tag. Assume that parsing starts from within a textual context. Leave point at the beginning of the tag." (let (tag-type tag-start tag-end name) (search-backward ">") (setq tag-end (1+ (point))) (cond ((xml-lite-looking-back-at "--") ; comment (setq tag-type 'comment tag-start (search-backward "<!--" nil t))) ((xml-lite-looking-back-at "]]") ; cdata (setq tag-type 'cdata tag-start (search-backward "<![CDATA[" nil t))) (t (setq tag-start (with-syntax-table sgml-tag-syntax-table (goto-char tag-end) (backward-sexp) (point))) (goto-char (1+ tag-start)) (case (char-after) (?! ; declaration (setq tag-type 'decl)) (?? ; processing-instruction (setq tag-type 'pi)) (?/ ; close-tag (forward-char 1) (setq tag-type 'close name (xml-lite-parse-tag-name))) ((?% ?#) ; JSP tags etc (setq tag-type 'unknown)) (t ; open or empty tag (setq tag-type 'open name (xml-lite-parse-tag-name)) (if (or (eq ?/ (char-before (- tag-end 1))) (sgml-empty-tag-p name)) (setq tag-type 'empty)))))) (goto-char tag-start) (xml-lite-make-tag tag-type tag-start tag-end name))) (defsubst xml-lite-inside-tag-p (tag-info &optional point) "Return true if TAG-INFO contains the POINT." (let ((end (xml-lite-tag-end tag-info)) (point (or point (point)))) (or (null end) (> end point)))) (defun xml-lite-get-context (&optional full) "Determine the context of the current position. If FULL is `empty', return even if the context is empty (i.e. we just skipped over some element and got to a beginning of line). If FULL is non-nil, parse back to the beginning of the buffer, otherwise parse until we find a start-tag as the first thing on a line. The context is a list of tag-info structures. The last one is the tag immediately enclosing the current position." (let ((here (point)) (ignore nil) (context nil) tag-info) ;; CONTEXT keeps track of the tag-stack ;; IGNORE keeps track of the nesting level of point relative to the ;; first (outermost) tag on the context. This is the list of ;; enclosing start-tags we'll have to ignore. (skip-chars-backward " \t\n") ; Make sure we're not at indentation. (while (and (or ignore (not (if full (eq full 'empty) context)) (not (xml-lite-at-indentation-p)) (and context (/= (point) (xml-lite-tag-start (car context))) (sgml-unclosed-tag-p (xml-lite-tag-name (car context))))) (setq tag-info (ignore-errors (xml-lite-parse-tag-backward)))) ;; This tag may enclose things we thought were tags. If so, ;; discard them. (while (and context (> (xml-lite-tag-end tag-info) (xml-lite-tag-end (car context)))) (setq context (cdr context))) (cond ;; inside a tag ... ((xml-lite-inside-tag-p tag-info here) (push tag-info context)) ;; start-tag ((eq (xml-lite-tag-type tag-info) 'open) (cond ((null ignore) (if (and context (sgml-unclosed-tag-p (xml-lite-tag-name tag-info)) (eq t (compare-strings (xml-lite-tag-name tag-info) nil nil (xml-lite-tag-name (car context)) nil nil t))) ;; There was an implicit end-tag. nil (push tag-info context))) ((eq t (compare-strings (xml-lite-tag-name tag-info) nil nil (car ignore) nil nil t)) (setq ignore (cdr ignore))) (t ;; The open and close tags don't match. (if (not sgml-xml-mode) ;; Assume the open tag is simply not closed. (unless (sgml-unclosed-tag-p (xml-lite-tag-name tag-info)) (message "Unclosed tag <%s>" (xml-lite-tag-name tag-info))) (message "Unmatched tags <%s> and </%s>" (xml-lite-tag-name tag-info) (pop ignore)))))) ;; end-tag ((eq (xml-lite-tag-type tag-info) 'close) (if (sgml-empty-tag-p (xml-lite-tag-name tag-info)) (message "Spurious </%s>: empty tag" (xml-lite-tag-name tag-info)) (push (xml-lite-tag-name tag-info) ignore))) )) ;; return context context)) (defun xml-lite-show-context (&optional full) "Display the current context. If FULL is non-nil, parse back to the beginning of the buffer." (interactive "P") (with-output-to-temp-buffer "*XML Context*" (pp (save-excursion (xml-lite-get-context full))))) ;; Editing shortcuts (defun xml-lite-insert-end-tag () "Insert an end-tag for the current element." (interactive) (let* ((context (save-excursion (xml-lite-get-context))) (tag-info (car (last context))) (type (and tag-info (xml-lite-tag-type tag-info)))) (cond ((null context) (error "Nothing to close")) ;; inside a tag ((xml-lite-inside-tag-p tag-info) (insert (cond ((eq type 'open) " />") ((eq type 'comment) " -->") ((eq type 'cdata) "]]>") ((eq type 'jsp) "%>") ((eq type 'pi) "?>") (t ">")))) ;; inside an element ((eq type 'open) (insert "</" (xml-lite-tag-name tag-info) ">") (indent-according-to-mode)) (t (error "Nothing to close"))))) (defun xml-lite-slash (arg) "Insert ARG slash characters. Behaves electrically if `xml-lite-electric-slash' is non-nil." (interactive "p") (cond ((not (and (eq (char-before) ?<) (= arg 1))) (insert-char ?/ arg)) ((eq xml-lite-electric-slash 'indent) (insert-char ?/ 1) (indent-according-to-mode)) ((eq xml-lite-electric-slash 'close) (delete-backward-char 1) (xml-lite-insert-end-tag)) (t (insert-char ?/ arg)))) (provide 'xml-lite) ;;; xml-lite.el ends here