;;; psgml-fs.el --- Format a SGML-file according to a style file
;; Copyright (C) 1995 Lennart Staflin

;; Author: Lennart Staflin <lenst@lysator.liu.se>
;; Version: $Id: fs.el,v 1.3 1996/03/31 21:38:45 lenst Exp $
;; Keywords: 
;; Last edited: Thu Mar 21 22:32:27 1996 by lenst@triton.lstaflin.pp.se (Lennart Staflin)

;;; 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 1, 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.
;;;
;;; A copy of the GNU General Public License can be obtained from this
;;; program's author (send electronic mail to lenst@lysator.liu.se) or from
;;; the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
;;; 02139, USA.
;;;
;;; Commentary:

;; The function `style-format' formats the SGML-file in the current
;; buffer according to the style defined in the file `psgml-style.fs'
;; (or the file given by the variable `fs-style').

;; To try it load this file and open the test file example.sgml. Then
;; run the emacs command `M-x style-format'.

;; The style file should contain a single Lisp list. The elements of
;; this list, are them self lists, describe the style for an element type. 
;; The sublists begin with the generic identifier for the element types and
;; the rest of the list are characteristic/value pairs.

;; E.g.  ("p"  block t  left 4  top 2)

;; Defines the style for p-elements to be blocks with left margin 4 and
;; at least to blank lines before the block.


;;; Code:
(require 'psgml-api)

;;;; Formatting parameters

(defvar fs-char
  '((left . 0)
    (first . nil)
    (default-top . 0)
    (default-bottom . 0)
    (ignore-empty-para . nil)
    (literal . nil)))

(defvar fs-special-styles
  '(top bottom before after hang-from text)
  "Style attribues that should not be entered in the characteristics table.")


;;;; Formatting engine

(defun fs-char (p)
  (cdr (assq p fs-char)))

(defvar fs-para-acc ""
  "Accumulate text of paragraph")

(defvar fs-hang-from nil
  "Hanging indent of current pargraph")

(defvar fs-first-indent nil)
(defvar fs-left-indent nil)

(defvar fs-vspace 0
  "Vertical space after last paragraph")

(defun fs-addvspace (n)
  (when (> n fs-vspace)
    (princ (make-string (- n fs-vspace) ?\n))
    (setq fs-vspace n)))
	   

(defun fs-para ()
  (when (if (fs-char 'ignore-epmty-para)
	    (string-match "[^\t\n ]" fs-para-acc)
	  fs-left-indent)
    (assert fs-left-indent)
    (fs-output-para fs-para-acc fs-first-indent fs-left-indent
		    fs-hang-from
		    (fs-char 'literal))
    (setq fs-vspace 0
	  fs-hang-from nil))
  (setq fs-para-acc ""
	fs-first-indent nil
	fs-left-indent nil))

(defun fs-paraform-data (data)
  (unless fs-left-indent
    (setq fs-left-indent (fs-char 'left)
	  fs-first-indent (fs-char 'first)))
  (setq fs-para-acc (concat fs-para-acc data)))

(defun fs-output-para (text first-indent indent hang-from literal)
  (sgml-push-to-string text)
  (let ((indent-tabs-mode nil)
	(fill-prefix (make-string indent ? )))
    (cond
     (literal
      (goto-char (point-max))
      (unless (bolp)
	(insert ?\n))
      (goto-char (point-min))
      (while (not (eobp))
	(insert fill-prefix)
	(beginning-of-line 2)))
     (t
      (while (re-search-forward "[ \t\n\r]+" nil t)
	(replace-match " "))
      (goto-char (point-min))
      (delete-horizontal-space)
      (insert 
       (if hang-from
	   hang-from
	 (make-string (or first-indent indent) ? )))
      (fill-region-as-paragraph (point-min) (point-max))
      ))
    (princ (buffer-string)))
  (sgml-pop-entity))

(defun fs-element-content (e)
  (let ((fs-para-acc ""))
    (sgml-map-content e
		      (function fs-paraform-phrase)
		      (function fs-paraform-data)
		      nil
		      (function fs-paraform-entity))
    fs-para-acc))

(defun fs-paraform-phrase (e)
  (sgml-map-content e
		    (function fs-paraform-phrase)
		    (function fs-paraform-data)
		    nil
		    (function fs-paraform-entity)))

(defun fs-paraform-entity (entity)
  (let ((entity-map (fs-char 'entity-map))
	(text nil))
    (when entity-map
      (setq text
	    (loop for (name val) on entity-map by 'cddr
		  thereis (if (equal name (sgml-entity-name entity))
			      val))))
    (unless text
      (setq text (sgml-entity-text entity)))
    (fs-paraform-data text)))

;;;; Style driven engine

(defvar fs-style "psgml-style.fs"
  "*Style sheet to use for `style-format'.
The value can be the style-sheet list, or it can be a file name
\(string) of a file containing the style sheet or it can be the name
\(symbol) of a variable containing the style sheet." )

(defvar fs-cached-styles nil)

(defun fs-get-style (style)
  (cond ((stringp style)
	 (sgml-cache-catalog style
			     'fs-cached-styles
			     (function (lambda ()
					 (read (current-buffer))))))
	((symbolp style)
	 (fs-get-style (symbol-value style)))
	((listp style)
	 style)
	(t
	 (error "Illegal style value: %s" style))))

(defun fs-engine (e)
  (fs-do-style e
	       (cdr (or (assoc (sgml-element-gi e) fs-style)
			(assq t fs-style)))))

(defun fs-do-style (e style)
  (let ((hang-from (getf style 'hang-from)))
    (when hang-from
      (setq fs-hang-from 
	    (format "%s%s "
		    (make-string (fs-char 'left) ? )
		    (eval hang-from)))))
  (let ((fs-char (nconc
		  (loop for st on style by 'cddr
			unless (memq (car st) fs-special-styles)
			collect (cons (car st)
				      (eval (cadr st))))
		  fs-char)))
    (when (getf style 'block)
      (fs-para)
      (fs-addvspace (or (getf style 'top)
			(fs-char 'default-top))))
    (let ((before (getf style 'before)))
      (when before
	(fs-do-style e before)))
    (cond ((getf style 'text)
	   (fs-paraform-data (eval (getf style 'text))))
	  (t
	   (sgml-map-content e
			     (function fs-engine)
			     (function fs-paraform-data)
			     nil
			     (function fs-paraform-entity))))
    (let ((after (getf style 'after)))
      (when after
	(fs-do-style e after)))
    (when (getf style 'block)
      (fs-para)
      (fs-addvspace (or (getf style 'bottom)
			(fs-char 'default-bottom))))))

;;;###autoload
(defun style-format ()
  (interactive)
  (setq fs-para-acc "")
  (let ((fs-style (fs-get-style fs-style)))
    (with-output-to-temp-buffer "*Formatted*"
      (fs-engine (sgml-top-element))
      (fs-para))))
		 


;;;; Helper functions for use in style sheet

(defun fs-attval (name)
  (sgml-element-attval e name))


;;; psgml-fs.el ends here
