Mercurial > hg > xemacs-beta
diff lisp/psgml/psgml-edit.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | ac2d302a0011 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/psgml/psgml-edit.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,1693 @@ +;;; psgml-edit.el --- Editing commands for SGML-mode with parsing support +;; $Id: psgml-edit.el,v 1.1.1.1 1996/12/18 03:35:19 steve Exp $ + +;; Copyright (C) 1994, 1995, 1996 Lennart Staflin + +;; Author: Lennart Staflin <lenst@lysator.liu.se> + +;; 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: + +;; Part of major mode for editing the SGML document-markup language. + + +;;;; Code: + +(provide 'psgml-edit) +(require 'psgml) +(require 'psgml-parse) + + +;;;; Variables + +(defvar sgml-split-level nil + "Used by sgml-split-element") + + +;;;; SGML mode: structure editing + +(defun sgml-last-element () + "Return the element where last command left point. +This either uses the save value in `sgml-last-element' or parses the buffer +to find current open element." + (setq sgml-markup-type nil) + (if (and (memq last-command sgml-users-of-last-element) + sgml-last-element) ; Don't return nil + sgml-last-element + (setq sgml-last-element (sgml-find-context-of (point)))) ) + +(defun sgml-set-last-element (&optional el) + (if el (setq sgml-last-element el)) + (sgml-show-context sgml-last-element)) + +(defun sgml-beginning-of-element () + "Move to after the start-tag of the current element. +If the start-tag is implied, move to the start of the element." + (interactive) + (goto-char (sgml-element-stag-end (sgml-last-element))) + (sgml-set-last-element (if (sgml-element-empty sgml-last-element) + (sgml-element-parent sgml-last-element)))) + +(defun sgml-end-of-element () + "Move to before the end-tag of the current element." + (interactive) + (goto-char (sgml-element-etag-start (sgml-last-element))) + (sgml-set-last-element (if (sgml-element-empty sgml-last-element) + (sgml-element-parent sgml-last-element)))) + +(defun sgml-backward-up-element () + "Move backward out of this element level. +That is move to before the start-tag or where a start-tag is implied." + (interactive) + (goto-char (sgml-element-start (sgml-last-element))) + (sgml-set-last-element (sgml-element-parent sgml-last-element))) + +(defun sgml-up-element () + "Move forward out of this element level. +That is move to after the end-tag or where an end-tag is implied." + (interactive) + (goto-char (sgml-element-end (sgml-last-element))) + (sgml-set-last-element (sgml-element-parent sgml-last-element))) + +(defun sgml-forward-element () + "Move forward over next element." + (interactive) + (let ((next + (sgml-find-element-after (point) (sgml-last-element)))) + (goto-char (sgml-element-end next)) + (sgml-set-last-element (sgml-element-parent next)))) + +(defun sgml-backward-element () + "Move backward over previous element at this level. +With implied tags this is ambigous." + (interactive) + (let ((prev ; previous element + (sgml-find-previous-element (point) (sgml-last-element)))) + (goto-char (sgml-element-start prev)) + (sgml-set-last-element (sgml-element-parent prev)))) + +(defun sgml-down-element () + "Move forward and down one level in the element structure." + (interactive) + (let ((to + (sgml-find-element-after (point) (sgml-last-element)))) + (goto-char (sgml-element-stag-end to)) + (sgml-set-last-element (if (sgml-element-empty to) + (sgml-element-parent to) + to)))) + + +(defun sgml-kill-element () + "Kill the element following the cursor." + (interactive "*") + (sgml-parse-to-here) + (when sgml-markup-type + (error "Point is inside markup")) + (kill-region (point) + (sgml-element-end (sgml-find-element-after (point))))) + +(defun sgml-transpose-element () + "Interchange element before point with element after point, leave point after." + (interactive "*") + (let ((pre (sgml-find-previous-element (point))) + (next (sgml-find-element-after (point))) + s1 s2 m2) + (goto-char (sgml-element-start next)) + (setq m2 (point-marker)) + (setq s2 (buffer-substring (point) + (sgml-element-end next))) + (delete-region (point) (sgml-element-end next)) + (goto-char (sgml-element-start pre)) + (setq s1 (buffer-substring (point) (sgml-element-end pre))) + (delete-region (point) (sgml-element-end pre)) + (insert-before-markers s2) + (goto-char m2) + (insert s1) + (sgml-message ""))) + +(defun sgml-mark-element () + "Set mark after next element." + (interactive) + (push-mark (sgml-element-end (sgml-find-element-after (point))) nil t)) + +(defun sgml-mark-current-element () + "Set mark at end of current element, and leave point before current element." + (interactive) + (let ((el (sgml-find-element-of (point)))) + (goto-char (sgml-element-start el)) + (push-mark (sgml-element-end el) nil t))) + + +(defun sgml-change-element-name (gi) + "Replace the name of the current element with a new name. +Eventual attributes of the current element will be translated if +possible." + (interactive + (list (let ((el (sgml-find-element-of (point)))) + (goto-char (sgml-element-start el)) + (sgml-read-element-name + (format "Change %s to: " (sgml-element-name el)))))) + (when (or (null gi) (equal gi "")) + (error "Illegal name")) + (let* ((element (sgml-find-element-of (point))) + (attspec (sgml-element-attribute-specification-list element)) + (oldattlist (sgml-element-attlist element))) + (unless (sgml-element-empty element) + (goto-char (sgml-element-end element)) + (delete-char (- (sgml-element-etag-len element))) + (insert (sgml-end-tag-of gi))) + (goto-char (sgml-element-start element)) + (delete-char (sgml-element-stag-len element)) + (insert (sgml-start-tag-of gi)) + (forward-char -1) + (let* ((newel (sgml-find-element-of (point))) + (newattlist (sgml-element-attlist newel)) + (newasl (sgml-translate-attribute-specification-list + attspec oldattlist newattlist))) + (sgml-insert-attributes newasl newattlist)))) + +(defun sgml-translate-attribute-specification-list (values from to) + "Translate attribute specification from one element type to another. +Input attribute values in VALUES using attlist FROM is translated into +a list using attlist TO." + (let ((new-values nil) + (sgml-show-warnings t) + tem) + (loop for attspec in values + as from-decl = (sgml-lookup-attdecl (sgml-attspec-name attspec) from) + as to-decl = (sgml-lookup-attdecl (sgml-attspec-name attspec) to) + do + (cond + ;; Special case ID attribute + ((and (eq 'id (sgml-attdecl-declared-value from-decl)) + (setq tem (sgml-attribute-with-declared-value to 'id))) + (push + (sgml-make-attspec (sgml-attdecl-name tem) + (sgml-attspec-attval attspec)) + new-values)) + ;; Use attribute with same name if compatible type + ((equal (sgml-attdecl-declared-value from-decl) + (sgml-attdecl-declared-value to-decl)) + (push attspec new-values)) + (to-decl + (sgml-log-warning + "Attribute %s has new declared-value" + (sgml-attspec-name attspec)) + (push attspec new-values)) + (t + (sgml-log-warning "Can't translate attribute %s = %s" + (sgml-attspec-name attspec) + (sgml-attspec-attval attspec))))) + new-values)) + +(defun sgml-untag-element () + "Remove tags from current element." + (interactive "*") + (let ((el (sgml-find-element-of (point)))) + (when (or (sgml-strict-epos-p (sgml-element-stag-epos el)) + (sgml-strict-epos-p (sgml-element-etag-epos el))) + (error "Current element has some tag inside an entity reference")) + (goto-char (sgml-element-etag-start el)) + (delete-char (sgml-element-etag-len el)) + (goto-char (sgml-element-start el)) + (delete-char (sgml-element-stag-len el)))) + +(defun sgml-kill-markup () + "Kill next tag, markup declaration or process instruction." + (interactive "*") + (let ((start (point))) + (sgml-with-parser-syntax + (sgml-parse-s) + (setq sgml-markup-start (point)) + (cond ((sgml-parse-markup-declaration 'ignore)) + ((sgml-parse-processing-instruction)) + ((sgml-skip-tag))) + (kill-region start (point))))) + + +;;;; SGML mode: folding + +(defun sgml-fold-region (beg end &optional unhide) + "Hide (or if prefixarg unhide) region. +If called from a program first two arguments are start and end of +region. And optional third argument true unhides." + (interactive "r\nP") + (let ((mp (buffer-modified-p)) + (inhibit-read-only t) ; + (buffer-read-only nil) ; should not need this, but + ; perhaps some old version of + ; emacs does not understand + ; inhibit-read-only + (before-change-function nil) + (after-change-function nil)) + (setq selective-display t) + (unwind-protect + (subst-char-in-region beg end + (if unhide ?\r ?\n) + (if unhide ?\n ?\r) + 'noundo) + (when sgml-buggy-subst-char-in-region + (set-buffer-modified-p mp))))) + +(defun sgml-fold-element () + "Fold the lines comprising the current element, leaving the first line visible. +This uses the selective display feature." + (interactive) + (sgml-parse-to-here) + (cond ((and (eq sgml-current-tree sgml-top-tree) ; outside document element + sgml-markup-type) + (sgml-fold-region sgml-markup-start + (save-excursion + (sgml-parse-to (point)) + (point)))) + ((and (eq sgml-current-tree sgml-top-tree) ; outside document element + (looking-at " *<!")) + (sgml-fold-region (point) + (save-excursion + (skip-chars-forward " \t") + (sgml-parse-to (1+ (point))) + (point)))) + + (t + (let ((el (sgml-find-element-of (point)))) + (when (eq el sgml-top-tree) + (error "No element here")) + (save-excursion + (goto-char (sgml-element-end el)) + (when (zerop (sgml-element-etag-len el)) + (skip-chars-backward " \t\n")) + (sgml-fold-region (sgml-element-start el) + (point))))))) + +(defun sgml-fold-subelement () + "Fold all elements current elements content, leaving the first lines visible. +This uses the selective display feature." + (interactive) + (let* ((el (sgml-find-element-of (point))) + (start (sgml-element-start el)) + (end (sgml-element-end el)) + (c (sgml-element-content el))) + (while c + (sgml-fold-region (sgml-element-start c) + (sgml-element-end c)) + (setq c (sgml-element-next c))))) + +(defun sgml-unfold-line () + "Show hidden lines in current line." + (interactive) + (let ((op (point))) + (beginning-of-line) + (push-mark) + (end-of-line) + (exchange-point-and-mark) + (sgml-fold-region (point) (mark) 'unhide) + (goto-char op))) + +(defun sgml-unfold-element () + "Show all hidden lines in current element." + (interactive) + (let* ((element (sgml-find-element-of (point)))) + (sgml-fold-region (sgml-element-start element) + (sgml-element-end element) + 'unfold))) + +(defun sgml-expand-element () + "As sgml-fold-subelement, but unfold first." + (interactive) + (sgml-unfold-element) + (sgml-fold-subelement)) + +(defun sgml-unfold-all () + "Show all hidden lines in buffer." + (interactive) + (sgml-fold-region (point-min) + (point-max) + 'unfold)) + +;;;; SGML mode: indentation and movement + +(defun sgml-indent-line (&optional col element) + "Indent line, calling parser to determine level unless COL or ELEMENT +is given. If COL is given it should be the column to indent to. If +ELEMENT is given it should be a parse tree node, from which the level +is determined." + (when sgml-indent-step + (let ((here (point-marker))) + (back-to-indentation) + (unless (or col element) + ;; Determine element + (setq element + (let ((sgml-throw-on-error 'parse-error)) + (catch sgml-throw-on-error + (if (eobp) + (sgml-find-context-of (point)) + (sgml-find-element-of (point))))))) + (when (eq element sgml-top-tree) ; not in a element at all + (setq element nil) ; forget element + (goto-char here)) ; insert normal tab insted) + (when element + (sgml-with-parser-syntax + (let ((stag (sgml-is-start-tag)) + (etag (sgml-is-end-tag))) + (when (and + (not (member* (sgml-element-gi + (if (or stag etag) + (sgml-element-parent element) + element)) + sgml-inhibit-indent-tags + :test #'equalp)) + (or sgml-indent-data + (not (sgml-element-data-p + (if stag + (sgml-element-parent element) + element))))) + (setq col + (* sgml-indent-step + (+ (if (or stag etag) -1 0) + (sgml-element-level element)))))))) + (when (and col (/= col (current-column))) + (beginning-of-line 1) + (delete-horizontal-space) + (indent-to col)) + (when (< (point) here) + (goto-char here)) + col))) + +(defun sgml-next-data-field () + "Move forward to next point where data is allowed." + (interactive) + (when (eobp) + (error "End of buffer")) + (let ((sgml-throw-on-warning 'next-data) + (avoid-el (sgml-last-element))) + ;; Avoid stopping in current element, unless point is in the start + ;; tag of the element + (when (< (point) (sgml-element-stag-end avoid-el)) + (setq avoid-el nil)) + (catch sgml-throw-on-warning + (while (progn + (sgml-parse-to (1+ (point))) + (setq sgml-last-element + (if (not (eq ?< (following-char))) + (sgml-find-element-of (point)) + sgml-current-tree)) + (or (eq sgml-last-element avoid-el) + (not (sgml-element-data-p sgml-last-element))))) + (sgml-set-last-element)))) + +(defun sgml-next-trouble-spot () + "Move forward to next point where something is amiss with the structure." + (interactive) + (push-mark) + (sgml-note-change-at (point)) ; Prune the parse tree + (sgml-parse-to (point)) + (let ((sgml-last-trouble-spot (point)) + (sgml-throw-on-warning 'trouble)) + (or (catch sgml-throw-on-warning + (sgml-parse-until-end-of nil t)) + (message "Ok")))) + + + +;;;; SGML mode: information display + +(defun sgml-list-valid-tags () + "Display a list of the contextually valid tags." + (interactive) + (sgml-parse-to-here) + (let ((model (sgml-element-model sgml-current-tree))) + (with-output-to-temp-buffer "*Tags*" + (princ (format "Current element: %s %s\n" + (sgml-element-name sgml-current-tree) + (if (sgml-eltype-defined + (sgml-element-eltype sgml-current-tree)) + "" + "[UNDEFINED]"))) + (princ (format "Element content: %s %s\n" + (cond ((or (sgml-current-mixed-p) (eq model sgml-any)) + "mixed") + ((sgml-model-group-p model) + "element") + (t + model)) + (if (eq model sgml-any) + "[ANY]" ""))) + + (cond ((sgml-final-p sgml-current-state) + (princ "Valid end-tags : ") + (loop for e in (sgml-current-list-of-endable-eltypes) + do (princ (sgml-end-tag-of e)) (princ " ")) + (terpri)) + (t + (princ "Current element can not end here\n"))) +;;; (let ((s (sgml-tree-shortmap sgml-current-tree))) +;;; (when s +;;; (princ (format "Current shortref map: %s\n" s)))) + (princ "Valid start-tags\n") + (sgml-print-valid-tags "In current element:" + sgml-current-tree sgml-current-state)))) + +(defun sgml-print-valid-tags (prompt tree state &optional exclude omitted-stag) + (if (not (sgml-model-group-p state)) + (princ (format "%s (in %s)\n" prompt state)) + (let* ((req (sgml-required-tokens state)) + (elems (nconc req + (delq sgml-pcdata-token + (sgml-optional-tokens state)))) + (in (sgml-tree-includes tree)) + (ex (append exclude (sgml-tree-excludes tree)))) + ;; Modify for exceptions + (while in + (unless (memq (car in) elems) + (setq elems (nconc elems (list (car in))))) + (setq in (cdr in))) + (while ex + (setq elems (delq (car ex) elems)) + (setq ex (cdr ex))) + ;; + (setq elems (sort elems (function string-lessp))) + (sgml-print-list-of-tags prompt elems) + ;; Check for omissable start-tags + (when (and req (null (cdr req))) + ;; *** Assumes tokens are eltypes + (let ((el (sgml-fake-open-element tree (car req)))) + (when (sgml-element-stag-optional el) + (sgml-print-valid-tags + (format "If omitting %s:" (sgml-start-tag-of el)) + el + (sgml-element-model el) + (append exclude elems) + 'omitted-stag)))) + ;; Check for omissable end-tag + (when (and (not omitted-stag) + (sgml-final-p state) + (sgml-element-etag-optional tree)) + (sgml-print-valid-tags + (format "If omitting %s:" (sgml-end-tag-of tree)) + (sgml-element-parent tree) + (sgml-element-pstate tree) + (append exclude elems)))))) + +(defun sgml-print-list-of-tags (prompt list) + (when list + (princ prompt) + (let ((col (length prompt)) + (w (1- (frame-width)))) + (loop for e in list + as str = (sgml-start-tag-of e) + do + (setq col (+ col (length str) 2)) + (cond ((>= col w) + (setq col (+ (length str) 2)) + (terpri))) + (princ " ") + (princ str)) + (terpri)))) + +(defun sgml-show-context (&optional element) + "Display where the cursor is in the element hierarchy." + (interactive) + (let* ((el (or element (sgml-last-element))) + (model (sgml-element-model el))) + (sgml-message "%s %s" + (cond + ((and (null element) ; Don't trust sgml-markup-type if + ; explicit element is given as argument + sgml-markup-type)) + ((sgml-element-mixed el) + "#PCDATA") + ((not (sgml-model-group-p model)) + model) + (t "")) + (if (eq el sgml-top-tree) + "in empty context" + (sgml-element-context-string el))))) + +(defun sgml-what-element () + "Display what element is under the cursor." + (interactive) + (let ((el (sgml-find-element-of (point)))) + (assert (not (null el))) + (message "%s %s" + (cond ((eq el sgml-top-tree) + "outside document element") + ((< (point) (sgml-element-stag-end el)) + "start-tag") + ((>= (point) (sgml-element-etag-start el)) + "end-tag") + (t + "content")) + (sgml-element-context-string el)))) + +;;;; SGML mode: keyboard inserting + +(defun sgml-insert-tag (tag &optional silent no-nl-after) + "Insert a tag, reading tag name in minibuffer with completion. +If the variable sgml-balanced-tag-edit is t, also inserts the +corresponding end tag. If sgml-leave-point-after-insert is t, the point +is left after the inserted tag(s), unless the element has som required +content. If sgml-leave-point-after-insert is nil the point is left +after the first tag inserted." + (interactive + (list + (completing-read "Tag: " (sgml-completion-table) nil t "<" ))) + (sgml-find-context-of (point)) + (assert (null sgml-markup-type)) + ;; Fix white-space before tag + (unless (sgml-element-data-p (sgml-parse-to-here)) + (skip-chars-backward " \t") + (cond ((bolp) + (if (looking-at "^\\s-*$") + (fixup-whitespace))) + (t + (insert "\n")))) + (insert tag) + (sgml-indent-line) + (unless no-nl-after + (save-excursion + (unless (sgml-element-data-p (sgml-parse-to-here)) + (unless (eolp) + (save-excursion (insert "\n")))))) + (or silent (sgml-show-context))) + +(defvar sgml-new-attribute-list-function + (function sgml-default-asl)) + +(defun sgml-insert-element (name &optional after silent) + "Reads element name from minibuffer and inserts start and end tags." + (interactive (list (sgml-read-element-name "Element: ") + sgml-leave-point-after-insert)) + (let (newpos ; position to leave cursor at + element ; inserted element + (sgml-show-warnings nil)) + (when (and name (not (equal name ""))) + (sgml-insert-tag (sgml-start-tag-of name) 'silent) + (forward-char -1) + (setq element (sgml-find-element-of (point))) + (sgml-insert-attributes (funcall sgml-new-attribute-list-function + element) + (sgml-element-attlist element)) + (forward-char 1) + (when (not (sgml-element-empty element)) + (when (and sgml-auto-insert-required-elements + (sgml-model-group-p sgml-current-state)) + (let (tem) + (while (and (setq tem (sgml-required-tokens sgml-current-state)) + (null (cdr tem))) + (setq tem (sgml-insert-element (car tem) t t)) + (setq newpos (or newpos tem)) + (sgml-parse-to-here)) + (when tem ; more than one req elem + (insert "\n") + (when sgml-insert-missing-element-comment + (insert (format "<!-- one of %s -->" tem)) + (sgml-indent-line nil element))))) + (setq newpos (or newpos (point))) + (when sgml-insert-end-tag-on-new-line + (insert "\n")) + (sgml-insert-tag (sgml-end-tag-of name) 'silent) + (unless after + (goto-char newpos)) + (unless silent (sgml-show-context))) + newpos))) + +(defun sgml-default-asl (element) + (loop for attdecl in (sgml-element-attlist element) + when (sgml-default-value-type-p (sgml-attdecl-default-value attdecl) + 'required) + collect + (sgml-make-attspec + (sgml-attdecl-name attdecl) + (sgml-read-attribute-value attdecl nil)))) + +(defun sgml-tag-region (element start end) + "Reads element name from minibuffer and inserts start and end tags." + (interactive + (list + (save-excursion (goto-char (region-beginning)) + (sgml-read-element-name "Tag region with element: ")) + (region-beginning) + (region-end))) + (save-excursion + (when (and element (not (equal element ""))) + (goto-char end) + (insert (sgml-end-tag-of element)) + (goto-char start) + (sgml-insert-tag (sgml-start-tag-of element))))) + +(defun sgml-insert-attributes (avl attlist) + "Insert the attributes with values AVL and declarations ATTLIST. +AVL should be a assoc list mapping symbols to strings." + (let (name val dcl def tem) + (loop for attspec in attlist do + (setq name (sgml-attspec-name attspec) + val (cdr-safe (sgml-lookup-attspec name avl)) + dcl (sgml-attdecl-declared-value attspec) + def (sgml-attdecl-default-value attspec)) + (unless val ; no value given + ;; Supply the default value if a value is needed + (cond ((sgml-default-value-type-p 'required def) + (setq val "")) + ((and (not (or sgml-omittag sgml-shorttag)) + (consp def)) + (setq val (sgml-default-value-attval def))))) + (cond + ((null val)) ; Ignore + ;; Ignore attributes with default value + ((and (consp def) + (eq sgml-minimize-attributes 'max) + (or sgml-omittag sgml-shorttag) + (equal val (sgml-default-value-attval def)))) + ;; No attribute name for token groups + ((and sgml-minimize-attributes sgml-shorttag + (member (sgml-general-case val) + (sgml-declared-value-token-group dcl))) + (insert " " val)) + (t + (insert " " name "=" (sgml-quote-attribute-value val))))) + (when auto-fill-function + (funcall auto-fill-function)))) + + +(defun sgml-quote-attribute-value (value) + "Add quotes to the string VALUE unless minimization is on." + (let ((quote "")) + (cond ((and (not sgml-always-quote-attributes) + sgml-shorttag + (string-match "\\`[.A-Za-z0-9---]+\\'" value)) + ) ; no need to quote + ((not (string-match "\"" value)) ; can use "" quotes + (setq quote "\"")) + (t ; use '' quotes + (setq quote "'"))) + (concat quote value quote))) + +(defun sgml-completion-table (&optional avoid-tags-in-cdata) + (sgml-parse-to-here) + (when sgml-markup-type + (error "No tags allowed")) + (cond ((or (sgml-model-group-p sgml-current-state) + (eq sgml-current-state sgml-any)) + (append + (mapcar (function (lambda (x) (cons (sgml-end-tag-of x) x))) + (sgml-current-list-of-endable-eltypes)) + (mapcar (function (lambda (x) (cons (sgml-start-tag-of x) x))) + (sgml-current-list-of-valid-eltypes)))) + (t + (sgml-message "%s" sgml-current-state) + nil))) + +(defun sgml-insert-end-tag () + "Insert end-tag for the current open element." + (interactive "*") + (sgml-parse-to-here) + (cond + ((eq sgml-current-tree sgml-top-tree) + (sgml-error "No open element")) + ((not (sgml-final-p sgml-current-state)) + (sgml-error "Can`t end element here")) + (t + (when (and sgml-indent-step + (not (sgml-element-data-p sgml-current-tree))) + (delete-horizontal-space) + (unless (bolp) + (insert "\n"))) + (when (prog1 (bolp) + (insert (if (eq t (sgml-element-net-enabled sgml-current-tree)) + "/" + ;; wing change: If there is more than one endable + ;; tag, we probably want the outermost one rather + ;; than the innermost one. Thus, we end a </ul> + ;; even when a </li> is possible. + (sgml-end-tag-of + (car (last (sgml-current-list-of-endable-eltypes))))))) + (sgml-indent-line))))) + +(defun sgml-insert-start-tag (name asl attlist &optional net) + (insert "<" name) + (sgml-insert-attributes asl attlist) + (insert (if net "/" ">"))) + +(defun sgml-change-start-tag (element asl) + (let ((name (sgml-element-gi element)) + (attlist (sgml-element-attlist element))) + (assert (sgml-bpos-p (sgml-element-stag-epos element))) + (goto-char (sgml-element-start element)) + (delete-char (sgml-element-stag-len element)) + (sgml-insert-start-tag name asl attlist + (eq t (sgml-element-net-enabled element))))) + +(defun sgml-read-attribute-value (attdecl curvalue) + "Return the attribute value read from user. +ATTDECL is the attribute declaration for the attribute to read. +CURVALUE is nil or a string that will be used as default value." + (assert attdecl) + (let* ((name (sgml-attdecl-name attdecl)) + (dv (sgml-attdecl-declared-value attdecl)) + (tokens (sgml-declared-value-token-group dv)) + (notations (sgml-declared-value-notation dv)) + (type (cond (tokens "token") + (notations "notation") + (t (symbol-name dv)))) + (prompt + (format "Value for %s (%s%s): " + name type + (if curvalue + (format " Default: %s" curvalue) + ""))) + value) + (setq value + (if (or tokens notations) + (completing-read prompt + (mapcar 'list (or tokens notations)) + nil t) + (read-string prompt))) + (if (and curvalue (equal value "")) + curvalue value))) + +(defun sgml-non-fixed-attributes (attlist) + (loop for attdecl in attlist + unless (sgml-default-value-type-p 'fixed + (sgml-attdecl-default-value attdecl)) + collect attdecl)) + +(defun sgml-insert-attribute (name value) + "Read attribute name and value from minibuffer and insert attribute spec." + (interactive + (let* ((el (sgml-find-attribute-element)) + (name + (completing-read + "Attribute name: " + (mapcar (function (lambda (a) (list (sgml-attdecl-name a)))) + (sgml-non-fixed-attributes (sgml-element-attlist el))) + nil t))) + (list name + (sgml-read-attribute-value + (sgml-lookup-attdecl name (sgml-element-attlist el)) + (sgml-element-attval el name))))) + ;; Body + (assert (stringp name)) + (assert (or (null value) (stringp value))) + (let* ((el (sgml-find-attribute-element)) + (asl (cons (sgml-make-attspec name value) + (sgml-element-attribute-specification-list el))) + (in-tag (< (point) (sgml-element-stag-end el)))) + (sgml-change-start-tag el asl) + (when in-tag (forward-char -1)))) + +(defun sgml-split-element () + "Split the current element at point. +If repeated, the containing element will be split before the beginning +of then current element." + (interactive "*") + (setq sgml-split-level + (if (eq this-command last-command) + (1+ sgml-split-level) + 0)) + (let ((u (sgml-find-context-of (point))) + (start (point-marker)) + before) + (loop repeat sgml-split-level do + (goto-char (sgml-element-start u)) + (setq u (sgml-element-parent u))) + ;; Verify that a new element can be started + (unless (and (sgml-element-pstate u) ; in case of top element + (sgml-get-move (sgml-element-pstate u) + (sgml-element-name u))) + + (sgml-error "The %s element can't be split" + (sgml-element-name u))) + ;; Do the split + (sgml-insert-end-tag) + (sgml-insert-tag (sgml-start-tag-of u) 'silent) + (skip-chars-forward " \t\n") + (sgml-indent-line) + (when (> sgml-split-level 0) + (goto-char start)) + (or (eq sgml-top-tree + (setq u (sgml-element-parent u))) + (sgml-message + "Repeat the command to split the containing %s element" + (sgml-element-name u))))) + +;;; David Megginson's custom menus for keys + +(defun sgml-custom-dtd (doctype) + "Insert a DTD declaration from the sgml-custom-dtd alist." + (interactive + (list (completing-read "Insert DTD: " sgml-custom-dtd nil t))) + (let ((entry (assoc doctype sgml-custom-dtd))) + (sgml-doctype-insert (second entry) (cddr entry)))) + +(defun sgml-custom-markup (markup) + "Insert markup from the sgml-custom-markup alist." + (interactive + (list (completing-read "Insert Markup: " sgml-custom-markup nil t))) + (sgml-insert-markup (cadr (assoc markup sgml-custom-markup)))) + + +;;;; SGML mode: Menu inserting + +(defun sgml-tags-menu (event) + "Pop up a menu with valid tags and insert the choosen tag. +If the variable sgml-balanced-tag-edit is t, also inserts the +corresponding end tag. If sgml-leave-point-after-insert is t, the point +is left after the inserted tag(s), unless the element has som required +content. If sgml-leave-point-after-insert is nil the point is left +after the first tag inserted." + (interactive "*e") + (let ((end (sgml-mouse-region))) + (sgml-parse-to-here) + (cond + ((eq sgml-markup-type 'start-tag) + (sgml-attrib-menu event)) + (t + (let ((what + (sgml-menu-ask event (if (or end sgml-balanced-tag-edit) + 'element 'tags)))) + (cond + ((null what)) + (end + (sgml-tag-region what (point) end)) + (sgml-balanced-tag-edit + (sgml-insert-element what)) + (t + (sgml-insert-tag what)))))))) + +(defun sgml-element-menu (event) + "Pop up a menu with valid elements and insert choice. +If sgml-leave-point-after-insert is nil the point is left after the first +tag inserted." + (interactive "*e") + (let ((what (sgml-menu-ask event 'element))) + (and what (sgml-insert-element what)))) + +(defun sgml-start-tag-menu (event) + "Pop up a menu with valid start-tags and insert choice." + (interactive "*e") + (let ((what (sgml-menu-ask event 'start-tag))) + (and what (sgml-insert-tag what)))) + +(defun sgml-end-tag-menu (event) + "Pop up a menu with valid end-tags and insert choice." + (interactive "*e") + (let ((what (sgml-menu-ask event 'end-tag))) + (and what (sgml-insert-tag what)))) + +(defun sgml-tag-region-menu (event) + "Pop up a menu with valid elements and tag current region with the choice." + (interactive "*e") + (let ((what (sgml-menu-ask event 'element))) + (and what (sgml-tag-region what + (region-beginning) + (region-end))))) + +(defun sgml-menu-ask (event type) + (sgml-parse-to-here) + (let (tab + (title (capitalize (symbol-name type)))) + (cond + (sgml-markup-type) + ((eq type 'element) + (setq tab + (mapcar (function symbol-name) + (sgml-current-list-of-valid-eltypes)))) + (t + (unless (eq type 'start-tag) + (setq tab + (mapcar (function sgml-end-tag-of) + (sgml-current-list-of-endable-eltypes)))) + (unless (eq type 'end-tag) + (setq tab + (nconc tab + (mapcar (function sgml-start-tag-of) + (sgml-current-list-of-valid-eltypes))))))) + (or tab + (error "No valid %s at this point" type)) + (or + (sgml-popup-menu event + title + (mapcar (function (lambda (x) (cons x x))) + tab)) + (message nil)))) + +(defun sgml-entities-menu (event) + (interactive "*e") + (sgml-need-dtd) + (let ((menu + (mapcar (function (lambda (x) (cons x x))) + (sort (sgml-map-entities (function sgml-entity-name) + (sgml-dtd-entities sgml-dtd-info) + t) + (function string-lessp)))) + choice) + (unless menu + (error "No entities defined")) + (setq choice (sgml-popup-menu event "Entities" menu)) + (when choice + (insert "&" choice ";")))) + +(defun sgml-doctype-insert (doctype vars) + "Insert string DOCTYPE (ignored if nil) and set variables in &rest VARS. +VARS should be a list of variables and values. +For backward compatibility a singel string instead of a variable is +assigned to sgml-default-dtd-file. +All variables are made buffer local and are also added to the +buffers local variables list." + (when doctype + (unless (bolp) + (insert "\n")) + (unless (eolp) + (insert "\n") + (forward-char -1)) + (sgml-insert-markup doctype)) + (while vars + (cond ((stringp (car vars)) + (sgml-set-local-variable 'sgml-default-dtd-file (car vars)) + (setq vars (cdr vars))) + ((car vars) ; Avoid nil + (sgml-set-local-variable (car vars) (cadr vars)) + (setq vars (cddr vars))))) + (setq sgml-top-tree nil)) + +(defun sgml-attrib-menu (event) + "Pop up a menu of the attributes of the current element +\(or the element whith start-tag before point)." + (interactive "e") + (let* ((el (sgml-find-attribute-element)) + (attlist (sgml-non-fixed-attributes (sgml-element-attlist el))) + tokens menu other) + (or attlist + (error "No non-fixed attributes for element")) + (setq menu + (loop for attdecl in attlist + for name = (sgml-attdecl-name attdecl) + for defval = (sgml-attdecl-default-value attdecl) + for tokens = (or (sgml-declared-value-token-group + (sgml-attdecl-declared-value attdecl)) + (sgml-declared-value-notation + (sgml-attdecl-declared-value attdecl))) + collect + (cons + (sgml-attdecl-name attdecl) + (nconc + (if tokens + (loop for val in tokens collect + (list val + (list 'sgml-insert-attribute name val))) + (list + (list "Set attribute value" + (list 'sgml-insert-attribute + (sgml-attdecl-name attdecl) + (list 'sgml-read-attribute-value + (list 'quote attdecl) + (sgml-element-attval el name)))))) + (if (sgml-default-value-type-p 'required defval) + nil + (list "--" + (list (if (sgml-default-value-type-p nil defval) + (format "Default: %s" + (sgml-default-value-attval defval)) + "#IMPLIED") + (list 'sgml-insert-attribute name nil)))))))) + (sgml-popup-multi-menu event "Attributes" menu))) + +;;;; SGML mode: Fill + +(defun sgml-fill-element (element) + "Fill bigest enclosing element with mixed content. +If current element has pure element content, recursively fill the +subelements." + (interactive (list (sgml-find-element-of (point)))) + ;; + (message "Filling...") + (when (sgml-element-mixed element) + ;; Find bigest enclosing element with mixed content + (while (sgml-element-mixed (sgml-element-parent element)) + (setq element (sgml-element-parent element)))) + ;; + (sgml-do-fill element) + (sgml-message "Done")) + +(defun sgml-do-fill (element) + (when sgml-debug + (goto-char (sgml-element-start element)) + (sit-for 0)) + (save-excursion + (cond + ((sgml-element-mixed element) + (let (last-pos + (c (sgml-element-content element)) + (agenda nil)) ; regions to fill later + (goto-char (sgml-element-stag-end element)) + (when (eolp) (forward-char 1)) + (setq last-pos (point)) + (while c + (cond + ((sgml-element-mixed c)) + (t + ;; Put region before element on agenda. Can't fill it now + ;; that would mangel the parse tree that is beeing traversed. + (push (cons last-pos (sgml-element-start c)) + agenda) + (goto-char (sgml-element-start c)) + (sgml-do-fill c) + ;; Fill may change parse tree, get a fresh + (setq c (sgml-find-element-of (point))) + (setq last-pos (sgml-element-end c)))) + (setq c (sgml-element-next c))) + ;; Fill the last region in content of element, + ;; but get a fresh parse tree, if it has change due to other fills. + (sgml-fill-region last-pos + (sgml-element-etag-start + (sgml-find-element-of + (sgml-element-start element)))) + (while agenda + (sgml-fill-region (caar agenda) (cdar agenda)) + (setq agenda (cdr agenda))))) + (t + ;; If element is not mixed, fill subelements recursively + (let ((c (sgml-element-content element))) + (while c + (goto-char (sgml-element-start c)) + (sgml-do-fill c) + (setq c (sgml-element-next (sgml-find-element-of (point)))))))))) + +(defun sgml-fill-region (start end) + (sgml-message "Filling...") + (save-excursion + (goto-char end) + (skip-chars-backward " \t\n") + (while (progn (beginning-of-line 1) + (< start (point))) + (delete-horizontal-space) + (delete-char -1) + (insert " ")) + (end-of-line 1) + (let (give-up prev-column opoint) + (while (and (not give-up) (> (current-column) fill-column)) + (setq prev-column (current-column)) + (setq opoint (point)) + (move-to-column (1+ fill-column)) + (skip-chars-backward "^ \t\n") + (if (bolp) + (re-search-forward "[ \t]" opoint t)) + (setq opoint (point)) + (skip-chars-backward " \t") + (if (bolp) + (setq give-up t) + (delete-region (point) opoint) + (newline) + (sgml-indent-line) + (end-of-line 1) + (setq give-up (>= (current-column) prev-column))))))) + +;;;; SGML mode: Attribute editing + +(defvar sgml-start-attributes nil) +(defvar sgml-main-buffer nil) +(defvar sgml-attlist nil) + +(defun sgml-edit-attributes () + "Edit attributes of current element. +Editing is done in a separate window." + (interactive) + (let ((element (sgml-find-attribute-element))) + (unless (sgml-bpos-p (sgml-element-stag-epos element)) + (error "Element's start-tag is not in the buffer")) + (push-mark) + (goto-char (sgml-element-start element)) + (let* ((start (point-marker)) + (asl (sgml-element-attribute-specification-list element)) + (cb (current-buffer)) + (quote sgml-always-quote-attributes)) + (switch-to-buffer-other-window + (sgml-attribute-buffer element asl)) + (sgml-edit-attrib-mode) + (make-local-variable 'sgml-attlist) + (setq sgml-attlist (sgml-element-attlist element)) + (make-local-variable 'sgml-start-attributes) + (setq sgml-start-attributes start) + (make-local-variable 'sgml-always-quote-attributes) + (setq sgml-always-quote-attributes quote) + (make-local-variable 'sgml-main-buffer) + (setq sgml-main-buffer cb)))) + +(defun sgml-attribute-buffer (element asl) + (let ((bname "*Edit attributes*") + (buf nil) + (inhibit-read-only t)) + (save-excursion + (when (setq buf (get-buffer bname)) + (kill-buffer buf)) + (setq buf (get-buffer-create bname)) + (set-buffer buf) + (erase-buffer) + (sgml-insert '(read-only t rear-nonsticky (read-only)) + "<%s -- Edit values and finish with C-c C-c --\n" + (sgml-element-name element)) + (loop + for attr in (sgml-element-attlist element) do + ;; Produce text like + ;; name = value + ;; -- declaration : default -- + (let* ((aname (sgml-attdecl-name attr)) + (dcl-value (sgml-attdecl-declared-value attr)) + (def-value (sgml-attdecl-default-value attr)) + (cur-value (sgml-lookup-attspec aname asl))) + (sgml-insert ; atribute name + '(read-only t rear-nonsticky (read-only)) + " %s = " aname) + (cond ; attribute value + ((sgml-default-value-type-p 'fixed def-value) + (sgml-insert '(read-only t category sgml-fixed + rear-nonsticky (category)) + "#FIXED %s" + (sgml-default-value-attval def-value))) + ((and (null cur-value) + (or (memq def-value '(implied conref current)) + (sgml-default-value-attval def-value))) + (sgml-insert '(category sgml-default rear-nonsticky (category)) + "#DEFAULT")) + ((not (null cur-value)) + (sgml-insert nil "%s" (sgml-attspec-attval cur-value)))) + (sgml-insert + '(read-only 1) + "\n\t-- %s: %s --\n" + (cond ((sgml-declared-value-token-group dcl-value)) + ((sgml-declared-value-notation dcl-value) + (format "NOTATION %s" + (sgml-declared-value-notation dcl-value))) + (t + dcl-value)) + (cond ((sgml-default-value-attval def-value)) + (t + (concat "#" (upcase (symbol-name def-value)))))))) + (sgml-insert '(read-only t) ">") + (goto-char (point-min)) + (sgml-edit-attrib-next)) + buf)) + +(defvar sgml-edit-attrib-mode-map (make-sparse-keymap)) +(define-key sgml-edit-attrib-mode-map "\C-c\C-c" 'sgml-edit-attrib-finish) +(define-key sgml-edit-attrib-mode-map "\C-c\C-d" 'sgml-edit-attrib-default) +(define-key sgml-edit-attrib-mode-map "\C-c\C-k" 'sgml-edit-attrib-clear) + +(define-key sgml-edit-attrib-mode-map "\C-a" 'sgml-edit-attrib-field-start) +(define-key sgml-edit-attrib-mode-map "\C-e" 'sgml-edit-attrib-field-end) +(define-key sgml-edit-attrib-mode-map "\t" 'sgml-edit-attrib-next) + +(defun sgml-edit-attrib-mode () + "Major mode to edit attribute specification list.\\<sgml-edit-attrib-mode-map> +Use \\[sgml-edit-attrib-next] to move between input fields. Use +\\[sgml-edit-attrib-default] to make an attribute have its default +value. To abort edit kill buffer (\\[kill-buffer]) and remove window +(\\[delete-window]). To finsh edit use \\[sgml-edit-attrib-finish]. + +\\{sgml-edit-attrib-mode-map}" + (kill-all-local-variables) + (setq mode-name "SGML edit attributes" + major-mode 'sgml-edit-attrib-mode) + (use-local-map sgml-edit-attrib-mode-map) + (run-hooks 'text-mode-hook 'sgml-edit-attrib-mode-hook)) + +(defun sgml-edit-attrib-finish () + "Finish editing and insert attribute values in original buffer." + (interactive) + (let ((cb (current-buffer)) + (asl (sgml-edit-attrib-specification-list)) + ;; save buffer local variables + (start sgml-start-attributes)) + (when (markerp start) + (delete-windows-on cb) + (switch-to-buffer (marker-buffer start)) + (kill-buffer cb) + (goto-char start) + (let ((element (sgml-find-element-of start))) + ;; *** Should the it be verified that this element + ;; is the one edited? + (sgml-change-start-tag element asl))))) + + +(defun sgml-edit-attrib-specification-list () + (goto-char (point-min)) + (forward-line 1) + (sgml-with-parser-syntax + (let ((asl nil) + (al sgml-attlist)) + (while (not (eq ?> (following-char))) + (sgml-parse-s) + (let ((name (sgml-check-nametoken))) + (forward-char 3) + (unless (memq (get-text-property (point) 'category) + '(sgml-default sgml-fixed)) + (push + (sgml-make-attspec (sgml-attdecl-name (car al)) + (sgml-extract-attribute-value + (sgml-attdecl-declared-value (car al)))) + asl)) + (while (progn (beginning-of-line 2) + (or (eolp) + (not (get-text-property (point) 'read-only)))))) + ; was (eq t) + (forward-line 1) + (setq al (cdr al))) + asl))) + + +(defun sgml-extract-attribute-value (type) + (save-excursion + (save-restriction + (narrow-to-region (point) + (progn (sgml-edit-attrib-field-end) + (point))) + (unless (eq type 'cdata) + (subst-char-in-region (point-min) (point-max) ?\n ? ) + (goto-char (point-min)) + (delete-horizontal-space)) + (goto-char (point-min)) + (when (search-forward "\"" nil t) ; don't allow both " and ' + (goto-char (point-min)) + (while (search-forward "'" nil t) ; replace ' with char ref + (replace-match "'"))) + (buffer-string)))) + +(defun sgml-edit-attrib-default () + "Set current attribute value to default." + (interactive) + (sgml-edit-attrib-clear) + (save-excursion + (sgml-insert '(category sgml-default) + "#DEFAULT"))) + +(defun sgml-edit-attrib-clear () + "Kill the value of current attribute." + (interactive) + (kill-region + (progn (sgml-edit-attrib-field-start) (point)) + (progn (sgml-edit-attrib-field-end) (point)))) + +(defun sgml-edit-attrib-field-start () + "Go to the start of the attribute value field." + (interactive) + (let (start) + (beginning-of-line 1) + (while (not (eq t (get-text-property (point) 'read-only))) + (beginning-of-line 0)) + (setq start (next-single-property-change (point) 'read-only)) + (unless start (error "No attribute value here")) + (assert (number-or-marker-p start)) + (goto-char start))) + +(defun sgml-edit-attrib-field-end () + "Go to the end of the attribute value field." + (interactive) + (sgml-edit-attrib-field-start) + (let ((end (if (and (eolp) + (get-text-property (1+ (point)) 'read-only)) + (point) + (next-single-property-change (point) 'read-only)))) + (assert (number-or-marker-p end)) + (goto-char end))) + +(defun sgml-edit-attrib-next () + "Move to next attribute value." + (interactive) + (or (search-forward-regexp "^ *[.A-Za-z0-9---]+ *= ?" nil t) + (goto-char (point-min)))) + + +;;;; SGML mode: Hiding tags/attributes + +(defconst sgml-tag-regexp + "\\(</?>\\|</?[A-Za-z][---A-Za-z0-9.]*\\(\\([^'\"></]\\|'[^']*'\\|\"[^\"]*\"\\)*\\)>?\\)") + +(defun sgml-operate-on-tags (action &optional attr-p) + (let ((buffer-modified-p (buffer-modified-p)) + (inhibit-read-only t) + (buffer-read-only nil) + (before-change-function nil) + (markup-index ; match-data index in tag regexp + (if attr-p 2 1)) + (tagcount ; number tags to give them uniq + ; invisible properties + 1)) + (unwind-protect + (save-excursion + (goto-char (point-min)) + (while (re-search-forward sgml-tag-regexp nil t) + (cond + ((eq action 'hide) + (let ((tag (downcase + (buffer-substring (1+ (match-beginning 0)) + (match-beginning 1))))) + (if (or attr-p (not (member tag sgml-exposed-tags))) + (add-text-properties + (match-beginning markup-index) (match-end markup-index) + (list 'invisible tagcount + 'rear-nonsticky '(invisible face)))))) + ((eq action 'show) ; ignore markup-index + (remove-text-properties (match-beginning 0) (match-end 0) + '(invisible nil))) + (t (error "Invalid action: %s" action))) + (incf tagcount))) + (set-buffer-modified-p buffer-modified-p)))) + +(defun sgml-hide-tags () + "Hide all tags in buffer." + (interactive) + (sgml-operate-on-tags 'hide)) + +(defun sgml-show-tags () + "Show hidden tags in buffer." + (interactive) + (sgml-operate-on-tags 'show)) + +(defun sgml-hide-attributes () + "Hide all attribute specifications in the buffer." + (interactive) + (sgml-operate-on-tags 'hide 'attributes)) + +(defun sgml-show-attributes () + "Show all attribute specifications in the buffer." + (interactive) + (sgml-operate-on-tags 'show 'attributes)) + + +;;;; SGML mode: Normalize (and misc manipulations) + +(defun sgml-expand-shortref-to-text (name) + (let (before-change-function + (entity (sgml-lookup-entity name (sgml-dtd-entities sgml-dtd-info)))) + (cond + ((null entity) (sgml-error "Undefined entity %s" name)) + ((sgml-entity-data-p entity) + (sgml-expand-shortref-to-entity name)) + (t + (delete-region sgml-markup-start (point)) + (sgml-entity-insert-text entity) + (setq sgml-goal (point-max)) ; May have changed size of buffer + ;; now parse the entity text + (goto-char (setq sgml-rs-ignore-pos sgml-markup-start)))))) + +(defun sgml-expand-shortref-to-entity (name) + (let ((end (point)) + (re-found nil) + before-change-function) + (goto-char sgml-markup-start) + (setq re-found (search-forward "\n" end t)) + (delete-region sgml-markup-start end) + (insert "&" name (if re-found "\n" ";")) + (setq sgml-goal (point-max)) ; May have changed size of buffer + (goto-char (setq sgml-rs-ignore-pos sgml-markup-start)))) + +(defun sgml-expand-all-shortrefs (to-entity) + "Expand all short references in the buffer. +Short references to text entities are expanded to the replacement text +of the entity other short references are expanded into general entity +references. If argument, TO-ENTITY, is non-nil, or if called +interactive with numeric prefix argument, all short references are +replaced by generaly entity references." + (interactive "*P") + (sgml-reparse-buffer + (if to-entity + (function sgml-expand-shortref-to-entity) + (function sgml-expand-shortref-to-text)))) + +(defun sgml-normalize (to-entity &optional element) + "Normalize buffer by filling in omitted tags and expanding empty tags. +Argument TO-ENTITY controls how short references are expanded as with +`sgml-expand-all-shortrefs'. An optional argument ELEMENT can be the +element to normalize insted of the whole buffer, if used no short +references will be expanded." + (interactive "*P") + (unless element + (sgml-expand-all-shortrefs to-entity)) + (let ((only-one (not (null element)))) + (setq element (or element (sgml-top-element))) + (goto-char (sgml-element-end element)) + (let ((before-change-function nil)) + (sgml-normalize-content element only-one))) + (sgml-note-change-at (sgml-element-start element)) + (sgml-message "Done")) + +(defun sgml-normalize-element () + (interactive "*") + (sgml-normalize nil (sgml-find-element-of (point)))) + +(defun sgml-normalize-content (element only-first) + "Normalize all elements in a content where ELEMENT is first element. +If sgml-normalize-trims is non-nil, trim off white space from ends of +elements with omitted end-tags." + (let ((content nil)) + (while element ; Build list of content elements + (push element content) + (setq element (if only-first + nil + (sgml-element-next element)))) + (while content + (setq element (car content)) + ;; Progress report + (sgml-lazy-message "Normalizing %d%% left" + (/ (point) (/ (+ (point-max) 100) 100))) + ;; Fix the end-tag + (sgml-normalize-end-tag element) + ;; Fix tags of content + (sgml-normalize-content (sgml-tree-content element) nil) + ;; Fix the start-tag + (sgml-normalize-start-tag element) + ;; Next content element + (setq content (cdr content))))) + +(defun sgml-normalize-start-tag (element) + (when (sgml-bpos-p (sgml-element-stag-epos element)) + (goto-char (min (point) (sgml-element-start element))) + (let ((name (sgml-element-gi element)) + (attlist (sgml-element-attlist element)) + (asl (sgml-element-attribute-specification-list element))) + (save-excursion + (assert (or (zerop (sgml-element-stag-len element)) + (= (point) (sgml-element-start element)))) + (delete-char (sgml-element-stag-len element)) + (sgml-insert-start-tag name asl attlist nil))))) + +(defun sgml-normalize-end-tag (element) + (unless (sgml-element-empty element) + (when (sgml-bpos-p (sgml-element-etag-epos element)) + (goto-char (min (point) (sgml-element-etag-start element))) + (if (and (zerop (sgml-element-etag-len element)) + sgml-normalize-trims) + (skip-chars-backward " \t\n\r")) + (delete-char (sgml-tree-etag-len element)) + (save-excursion (insert (sgml-end-tag-of element)))))) + + +(defun sgml-make-character-reference (&optional invert) + "Convert character after point into a character reference. +If called with a numeric argument, convert a character reference back +to a normal character. If called from a program, set optional +argument INVERT to non-nil." + (interactive "*P") + (cond + (invert + (or (looking-at "&#\\([0-9]+\\)[;\n]?") + (error "No character reference after point")) + (let ((c (string-to-int (buffer-substring (match-beginning 1) + (match-end 1))))) + (delete-region (match-beginning 0) + (match-end 0)) + (insert c))) + ;; Convert character to &#nn; + (t + (let ((c (following-char))) + (delete-char 1) + (insert (format "&#%d;" c)))))) + +(defun sgml-expand-entity-reference () + "Insert the text of the entity referenced at point." + (interactive) + (sgml-with-parser-syntax + (setq sgml-markup-start (point)) + (sgml-check-delim "ERO") + (let* ((ename (sgml-check-name t)) + (entity (sgml-lookup-entity ename + (sgml-dtd-entities + (sgml-pstate-dtd + sgml-buffer-parse-state))))) + (unless entity + (error "Undefined entity %s" ename)) + (or (sgml-parse-delim "REFC") + (sgml-parse-RE)) + (delete-region sgml-markup-start (point)) + (sgml-entity-insert-text entity)))) + + +;;;; SGML mode: TAB completion + +(defun sgml-complete () + "Complete the word/tag/entity before point. +If it is a tag (starts with < or </) complete with valid tags. +If it is an entity (starts with &) complete with declared entities. +If it is a markup declaration (starts with <!) complete with markup +declaration names. +If it is something else complete with ispell-complete-word." + (interactive "*") + (let ((tab ; The completion table + nil) + (pattern nil) + (c nil) + (here (point))) + (skip-chars-backward "^ \n\t</!&%") + (setq pattern (buffer-substring (point) here)) + (setq c (char-after (1- (point)))) + (cond + ;; entitiy + ((eq c ?&) + (sgml-need-dtd) + (setq tab + (sgml-entity-completion-table + (sgml-dtd-entities (sgml-pstate-dtd sgml-buffer-parse-state))))) + ;; start-tag + ((eq c ?<) + (save-excursion + (backward-char 1) + (sgml-parse-to-here) + (setq tab (sgml-eltype-completion-table + (sgml-current-list-of-valid-eltypes))))) + ;; end-tag + ((eq c ?/) + (save-excursion + (backward-char 2) + (sgml-parse-to-here) + (setq tab (sgml-eltype-completion-table + (sgml-current-list-of-endable-eltypes))))) + ;; markup declaration + ((eq c ?!) + (setq tab sgml-markup-declaration-table)) + (t + (goto-char here) + (ispell-complete-word))) + (when tab + (let ((completion (try-completion pattern tab))) + (cond ((null completion) + (goto-char here) + (message "Can't find completion for \"%s\"" pattern) + (ding)) + ((eq completion t) + (goto-char here) + (message "[Complete]")) + ((not (string= pattern completion)) + (delete-char (length pattern)) + (insert completion)) + (t + (goto-char here) + (message "Making completion list...") + (let ((list (all-completions pattern tab))) + (with-output-to-temp-buffer " *Completions*" + (display-completion-list list))) + (message "Making completion list...%s" "done"))))))) + + +;;;; SGML mode: Options menu + +(defun sgml-file-options-menu (&optional event) + (interactive "e") + (sgml-options-menu event sgml-file-options)) + +(defun sgml-user-options-menu (&optional event) + (interactive "e") + (sgml-options-menu event sgml-user-options)) + +(defun sgml-options-menu (event vars) + (let ((var + (let ((maxlen + (loop for var in vars + maximize (length (sgml-variable-description var))))) + (sgml-popup-menu + event "Options" + (loop for var in vars + for desc = (sgml-variable-description var) + collect + (cons + (format "%s%s [%s]" + desc + (make-string (- maxlen (length desc)) ? ) + (sgml-option-value-indicator var)) + var)))))) + (when var + (sgml-do-set-option var event)))) + +(defun sgml-do-set-option (var &optional event) + (let ((type (sgml-variable-type var)) + (val (symbol-value var))) + (cond + ((eq 'toggle type) + (message "%s set to %s" var (not val)) + (set var (not val))) + ((eq 'string type) + (describe-variable var) + (setq val (read-string (concat (sgml-variable-description var) ": "))) + (when (stringp val) + (set var val))) + ((consp type) + (let ((val + (sgml-popup-menu event + (sgml-variable-description var) + (loop for c in type collect + (cons + (if (consp c) (car c) (format "%s" c)) + (if (consp c) (cdr c) c)))))) + (set var val) + (message "%s set to %s" var val))) + (t + (describe-variable var) + (setq val (read-string (concat (sgml-variable-description var) + " (sexp): "))) + (when (stringp val) + (set var (car (read-from-string val))))))) + (force-mode-line-update)) + +(defun sgml-option-value-indicator (var) + (let ((type (sgml-variable-type var)) + (val (symbol-value var))) + (cond + ((eq type 'toggle) + (if val "Yes" "No")) + ((eq type 'string) + (if (stringp val) + (substring val 0 4) + "-")) + ((and (atom type) val) + "...") + ((consp type) + (or (car (rassq val type)) + val)) + (t + "-")))) + +;;;; NEW + +(defun sgml-trim-and-leave-element () + (interactive) + (goto-char (sgml-element-etag-start (sgml-last-element))) + (while (progn (forward-char -1) + (looking-at "\\s-")) + (delete-char 1)) + (sgml-up-element)) + + +;;; psgml-edit.el ends here