;;;; xslide.el --- XSL Integrated Development Environment ;; $Id: xslide.el,v 1.11 2003/07/18 23:27:13 tonygraham Exp $ ;; Copyright (C) 1998, 1999, 2000, 2001, 2003 Tony Graham ;; Author: Tony Graham ;; Contributors: Simon Brooke, Girard Milmeister, Norman Walsh, ;; Moritz Maass, Lassi Tuura, Simon Wright, KURODA Akira, ;; Ville Skyttä, Glen Peterson ;; Created: 21 August 1998 ;; Version: $Revision: 1.11 $ ;; Keywords: languages, xsl, xml ;;; This file is not 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 this program; if not, write to the Free Software ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ;;;; Commentary: ;; Functions for editing XSL stylesheets ;; Requires xslide-font.el, xslide-data.el, xslide-abbrev.el, xslide-process.el ;; Requires 'etags for `find-tag-default' ;; Requires 'reporter for `xsl-submit-bug-report' ;; Requires 'imenu for "Goto" menu ;; ;; Send bugs to xslide-bug@menteith.com ;; Use `xsl-submit-bug-report' for bug reports ;;;; Code: (provide 'xslide) (require 'cl) (require 'compile) (require 'font-lock) ;; XEmacs users don't always have imenu.el installed, so use ;; condition-case to cope if xslide causes an error by requiring imenu. (eval-and-compile (condition-case nil (require 'imenu) (error nil))) ;; Need etags for `find-tag-default' (require 'etags) (require 'xslide-data "xslide-data") (require 'xslide-abbrev "xslide-abbrev") (require 'xslide-font "xslide-font") (require 'xslide-process "xslide-process") ;; Work out if using XEmacs or Emacs ;; Inspired by 'vm' (defconst xsl-xemacs-p nil) (defconst xsl-fsfemacs-p nil) (defun xsl-xemacs-p () xsl-xemacs-p) (defun xsl-fsfemacs-p () xsl-fsfemacs-p) (defun xsl-note-emacs-version () (setq xsl-xemacs-p (string-match "XEmacs" emacs-version) xsl-fsfemacs-p (not xsl-xemacs-p))) (xsl-note-emacs-version) ;; Define core `xsl' group. (defgroup xsl nil "Major mode for editing XSL." :prefix "xsl-" :group 'languages) (defgroup xsl-faces nil "Font faces used in XSL mode." :group 'xsl :group 'faces) (defgroup xsl-process nil "Running XSL processors from XSL mode." :group 'xsl) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Version information (defconst xslide-version "0.2.2" "Version number of xslide XSL mode.") (defun xslide-version () "Return the value of the variable `xslide-version'." xslide-version) (defconst xslide-maintainer-address "xslide-bug@menteith.com") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Variables (defvar xsl-indent-tabs-mode nil "*Initial value of `indent-tabs-mode' on entering `xsl-mode'.") (defvar xsl-default-filespec "*.xsl" "*Initial prompt value for `xsl-etags''s FILESPEC argument.") (defvar xsl-filespec-history (list xsl-default-filespec) "Minibuffer history list for `xsl-etags' and `xsl-grep''s FILESPEC argument.") (defvar xsl-grep-pattern-history nil "Minibuffer history list for `xsl-grep''s PATTERN argument.") (defvar xsl-grep-case-sensitive-flag nil "*Non-nil disables case insensitive searches by `xsl-grep'.") (defvar xsl-comment-start "" "*Comment end character sequence.") (defvar xsl-comment-max-column 70 "*Maximum column number for text in a comment.") (defcustom xsl-initial-stylesheet-file (locate-library "xslide-initial.xsl" t) "*File containing initial stylesheet inserted into empty XSL buffers." :type '(choice (file :must-match t) (const :tag "No initial stylesheet" nil)) :group 'xsl) (defcustom xsl-initial-stylesheet-initial-point 0 "*Initial position of point in initial stylesheet." :type '(integer) :group 'xsl) (defcustom xsl-initial-fo-file (locate-library "xslide-initial.fo" t) "*File containing initial FO stylesheet inserted into empty XSL buffers." :type '(choice (file :must-match t) (const :tag "No initial FO file" nil)) :group 'xsl) (defcustom xsl-initial-fo-initial-point 0 "*Initial position of point in initial FO stylesheet." :type '(integer) :group 'xsl) (defcustom xsl-indent-attributes nil "*Whether to indent attributes on lines following an open tag. If non-nil, attributes will be aligned with the space after the element name, otherwise by two spaces." :type '(choice (const :tag "Yes" t) (const :tag "No" nil)) :group 'xsl) (defcustom xsl-element-indent-step 2 "*Amount by which to indent success levels of nested elements." :type '(integer) :group 'xsl) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; functions (defun xsl-read-from-minibuffer (prompt default history) "Read from minibuffer with default and command history." (let ((value nil)) (if (string-equal "" (setq value (read-from-minibuffer (if default (format "%s(default `%s') " prompt default) (format "%s" prompt)) nil nil nil history))) default value))) ;; XSLIDE house style puts all comments starting on a favourite column (defun xsl-comment (comment) "Insert COMMENT starting at the usual column. With a prefix argument, e.g. \\[universal-argument] \\[xsl-comment], insert separator comment lines above and below COMMENT in the manner of `xsl-big-comment'." (interactive "sComment: ") (insert "\n") (backward-char) (xsl-electric-tab) (let ((fill-column (1- xsl-comment-max-column)) (fill-prefix (make-string (1+ (length xsl-comment-start)) ?\ )) ;; (comment-start xsl-init-comment-fill-prefix) (saved-auto-fill-function auto-fill-function)) (auto-fill-mode 1) (insert xsl-comment-start) (insert " ") (indent-to (length fill-prefix)) (fill-region (point) (save-excursion (insert comment) (point)) nil 1 1) ;; The fill does the right thing, but it always ends with ;; an extra newline, so delete the newline. (delete-backward-char 1) (if (not saved-auto-fill-function) (auto-fill-mode 0)) (insert " ") (insert xsl-comment-end) (insert "\n") (if font-lock-mode (save-excursion (font-lock-fontify-keywords-region (xsl-font-lock-region-point-min) (xsl-font-lock-region-point-max)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Mode map stuff (defvar xsl-mode-map nil "Keymap for XSL mode.") (if xsl-mode-map () (setq xsl-mode-map (make-sparse-keymap)) (define-key xsl-mode-map [tab] 'xsl-electric-tab) ;; (define-key xsl-mode-map "\M-\t" 'xsl-complete) (define-key xsl-mode-map [(meta tab)] 'xsl-complete) ;; (define-key xsl-mode-map "\"" 'xsl-electric-quote) ;; (define-key xsl-mode-map "'" 'xsl-electric-apos) (define-key xsl-mode-map "/" 'xsl-electric-slash) (define-key xsl-mode-map "<" 'xsl-electric-less-than) (define-key xsl-mode-map ">" 'xsl-electric-greater-than) ;; (define-key xsl-mode-map "[" 'xsl-electric-lsqb) ;; (define-key xsl-mode-map "(" 'xsl-electric-lpar) ;; (define-key xsl-mode-map "{" 'xsl-electric-lcub) (define-key xsl-mode-map [(control c) (control c)] 'xsl-comment) (define-key xsl-mode-map [(control c) (control p)] 'xsl-process) (define-key xsl-mode-map [(control o)] 'xsl-open-line) (define-key xsl-mode-map "\C-c<" 'xsl-insert-tag) (define-key xsl-mode-map "\C-c\C-t" 'xsl-if-to-choose) ;; (define-key xsl-mode-map [(control m)] 'xsl-electric-return) ;; (define-key xsl-mode-map \10 'xsl-electric-return) (define-key xsl-mode-map "\177" 'backward-delete-char-untabify) ;; (define-key xsl-mode-map "\M-\C-e" 'xsl-next-rule) ;; (define-key xsl-mode-map "\M-\C-a" 'xsl-previous-rule) ;; (define-key xsl-mode-map "\M-\C-h" 'mark-xsl-rule) ) (defun xsl-if-to-choose () "Converts to . Works on a single 'ifs' or on a region. So:

It's five!

Becomes:

It's five!

If you put your cursor inside the open-tag of the if, it will work on that tag only. If you highlight a region, it will convert every 'if' whose start tag is within that region. It is very easy to convert consecutive 'if's to a single choose by deleting the appropriate lines after executing this command. Bound to C-c C-t by default." (interactive) (let ( (single-if (not (mark))) (the-start (point)) (the-end (if (mark) (mark) (point))) ) (if (and (not (null (mark))) (< (mark) (point))) (progn (exchange-point-and-mark) (setq the-start (point)) (setq the-end (mark)) ) ) (save-excursion (if single-if (progn (search-backward "<" nil t) ; (message "xsl-if-to-choose: single if mode") (xsl-convert-if-to-choose-slave) ) (save-excursion ; (message ; (concat "xsl-if-to-choose: Region mode: " ; (int-to-string the-start) ; " " ; (int-to-string the-end) ; ) ; ) (goto-char the-end) (if (save-excursion (search-backward " within the selected region.") ) ) ) ) ) ) (defun xsl-convert-if-to-choose-slave () (if (looking-at "\n" nil t) (backward-delete-char 9) (insert "\n\n
") (indent-region start (point) nil) ) (message "xsl-if-to-choose error: point is not within the start tag of an .") ) ) (defun xsl-electric-greater-than (arg) "Insert a \">\" and, optionally, insert a matching end-tag. If the \">\" closes a start-tag and the start-tag is the last thing on the line, `xsl-electric-greater-than' inserts the matching end-tag. Providing a prefix argument, e.g., \\[universal-argument] \\[xsl-electric-greater-than], stops the inserting of the matching end-tag. If the element being terminated is listed as a block element in `xsl-all-elements-alist', then point is left on the next line at the correct indent and the end-tag is inserted on the following line at the correct indent. `xsl-electric-greater-than' also fontifies the region around the current line." (interactive "P") (insert ">") (if (and (not arg) (looking-at "$") (save-excursion (let ((limit (point))) (backward-char) (search-backward "<") ;; (message "%s:%s" (point) limit) (and (looking-at "<\\(\\(\\sw\\|\\s_\\)+\\)\\(\\s-+\\(\\sw\\|\\s_\\)+[ ]*=[ ]*\\('[^']*'\\|\"[^\"]*\"\\)\\)*\\s-*\\(/?\\)>") ;; (message "%s:%s" limit (match-end 0)) (= (match-end 0) limit) ;; (message ":%s:" (match-string 6)) (not (string-equal (match-string 6) "/")) (not (save-match-data (string-match "^/" (match-string 1)))))))) (if (string-equal (nth 1 (assoc (match-string 1) xsl-all-elements-alist)) "block") (progn (xsl-electric-return) (save-excursion (insert "\n<") (xsl-electric-slash))) (save-excursion (insert (format "" (match-string 1)))))) (if font-lock-mode (save-excursion (font-lock-fontify-region (xsl-font-lock-region-point-min) (xsl-font-lock-region-point-max))))) (defun xsl-electric-apos () "Function called when \"'\" is pressed in XSL mode." (interactive) (insert "'") (if (looking-at "\\([\"/})]\\|$\\)") (save-excursion (insert "'")))) (defun xsl-electric-quote () "Function called when '\"' is pressed in XSL mode." (interactive) (insert "\"") (if (looking-at "\\(['/})]\\|$\\)") (save-excursion (insert "\"")))) (defun xsl-electric-lsqb () "Function called when \"[\" is pressed in XSL mode." (interactive) (insert "[") (if (looking-at "\\([\"'/})]\\|$\\)") (save-excursion (insert "]")))) (defun xsl-electric-lpar () "Function called when \"(\" is pressed in XSL mode." (interactive) (insert "(") (if (looking-at "\\([\]\"'/}]\\|$\\)") (save-excursion (insert ")")))) (defun xsl-electric-lcub () "Function called when \"{\" is pressed in XSL mode." (interactive) (insert "{") (if (looking-at "\\([\])\"'/}]\\|$\\)") (save-excursion (insert "}")))) (defun xsl-electric-less-than () "Function called when \"<\" is pressed in XSL mode." (interactive) (insert "<") (xsl-electric-tab)) (defun xsl-match-opening-tag (a) "Function called to match the next opening tag to a closing tag." (if (looking-at "]") nil t) (cond ((looking-at (concat " \t]+\\)>") ;; (message "End tag: %s" (match-string 1)) ; find matching tag: (xsl-match-opening-tag (match-string 1))) ;;original ;; (re-search-backward ;; (concat "<" (match-string 1) "[ \t\n\r>]") nil t)) ((looking-at "<\\(\\([^/>]\\|/[^>]\\)+\\)/>")) ;; (message "Empty tag: %s" (match-string 1))) ((looking-at "")) ;; skip CDATA sections ((looking-at " \n\t]+\\)") ;; (message "Start tag: %s" (match-string 1)) (throw 'start-tag (match-string 1))) ((bobp) (throw 'start-tag nil))))) nil)))) (if element-name (progn (insert element-name) (insert ">") (if font-lock-mode (save-excursion (font-lock-fontify-region (xsl-font-lock-region-point-min) (xsl-font-lock-region-point-max))))))))) (defun xsl-electric-return () "Function called when RET is pressed in XSL mode." (interactive) (insert "\n") (xsl-electric-tab)) (defun xsl-open-line (arg) (interactive "p") (if (not arg) (setq arg 1)) (save-excursion (while (> arg 0) (setq arg (1- arg)) (insert "\n")) (if (looking-at "<") (xsl-electric-tab)))) (defun xsl-electric-tab () "Function called when TAB is pressed in XSL mode." (interactive) (save-excursion (beginning-of-line) (delete-horizontal-space) (if (looking-at ") (setq open (1- open))) ((eq here '?\<) (setq open (1+ open))) ) ) (forward-char) ) (< open 0) ; true if we've counted more ; closes than opens ) ) ) (defun xsl-calculate-indent () "Calculate what the indent should be for the current line." (let* ((limit (point)) (name "[^<>=\"' \t\n]+") (string "\\(\"[^<>\"]*\"\\|'[^<>']*'\\)") (ostring "\\(\"[^<>\"]*\\|'[^<>']*\\)") (attval (concat name "=" string)) (oattval (concat name "=" ostring)) (element (concat "<\\(" name "\\)" "\\([ \t\n]+" attval "\\)*[ \t\n]*")) (meta (concat " stay put ((save-excursion (re-search-forward "" limit t)) (current-column)) ;; open comment => indent by five ((looking-at "") (make-local-variable 'comment-indent-function) (setq comment-indent-function 'xsl-electric-tab) (make-local-variable 'comment-start-skip) ;; This will allow existing comments within declarations to be ;; recognized. [Does not work well with auto-fill, Lst/940205] ;;(setq comment-start-skip "--[ \t]*") (setq comment-start-skip "