emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/docbook a59f87c 1/4: docbook: New package (Info-like vi


From: Stefan Monnier
Subject: [elpa] externals/docbook a59f87c 1/4: docbook: New package (Info-like viewer for DocBook documents).
Date: Tue, 1 Dec 2020 15:39:28 -0500 (EST)

branch: externals/docbook
commit a59f87c0dc04bcfcf9f55a124658c2e7a585dbd2
Author: Chong Yidong <cyd@gnu.org>
Commit: Chong Yidong <cyd@gnu.org>

    docbook: New package (Info-like viewer for DocBook documents).
---
 docbook.el | 1215 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1215 insertions(+)

diff --git a/docbook.el b/docbook.el
new file mode 100644
index 0000000..473ae5f
--- /dev/null
+++ b/docbook.el
@@ -0,0 +1,1215 @@
+;;; docbook.el --- Info-like viewer for DocBook  -*- lexical-binding: t -*-
+
+;; Copyright (C) 2013  Free Software Foundation, Inc.
+
+;; Author: Chong Yidong <cyd@gnu.org>
+;; Keywords: docs, help
+;; Version: 0.1
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs 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.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; An Info-like viewer for DocBook manuals.
+;;
+;; Entry point: M-x docbook-find-file
+
+;;; TODO:
+
+;; table
+;; informaltable
+;; graphics
+;;
+;; funcsynopsis
+;; classsynopsis
+;; citerefentry
+;;
+;; see, primaryie, secondaryie
+
+;;; Code:
+
+(require 'xml)
+
+(defgroup docbook nil
+  "The Emacs DocBook reader."
+  :group 'help
+  :group 'docs)
+
+(defface docbook-xref
+  '((t :inherit button))
+  "Face for DocBook cross references."
+  :group 'docbook)
+
+(defface docbook-warning
+  '((t :inherit font-lock-warning-face))
+  "Face for warning text in DocBook documents."
+  :group 'docbook)
+
+(defface docbook-emphasis
+  '((t :slant italic))
+  "Face for emphasized text in DocBook documents."
+  :group 'docbook)
+
+(defface docbook-literal
+  '((t :inherit (font-lock-constant-face fixed-pitch)))
+  "Face for DocBook text marked as being literal."
+  :group 'docbook)
+
+(defface docbook-computer
+  '((t :inherit (font-lock-type-face fixed-pitch)))
+  "Face for DocBook text marked as computer output."
+  :group 'docbook)
+
+(defface docbook-computer-term
+  '((t :inherit (font-lock-keyword-face fixed-pitch)))
+  "Face for DocBook text marked as computer terminology."
+  :group 'docbook)
+
+(defface docbook-replaceable
+  '((t :inherit (font-lock-string-face bold)))
+  "Face for DocBook text marked as replaceable."
+  :group 'docbook)
+
+(defface docbook-citation
+  '((t :slant italic))
+  "Face for DocBook text marked as non-xref citations."
+  :group 'docbook)
+
+(defface docbook-label
+  '((t :weight bold :underline t))
+  "Face for DocBook text marked as labels for Q&A entries,"
+  :group 'docbook)
+
+(defface docbook-small '((t :height 0.8))
+  "Face for DocBook text marked as small."
+  :group 'docbook)
+
+(defface docbook-chapter-title
+  '((((type tty pc) (class color) (background light))
+     :foreground "green" :weight bold :underline t)
+    (((type tty pc) (class color) (background dark))
+     :foreground "yellow" :weight bold :underline t)
+    (t :height 1.5 :inherit docbook-section-title))
+  "Face for DocBook chapter titles."
+  :group 'docbook)
+
+(defface docbook-section-title
+  '((((type tty pc) (class color))
+     :foreground "lightblue" :weight bold :underline t)
+    (t :height 1.2 :inherit docbook-subsection-title))
+  "Face for DocBook section titles."
+  :group 'docbook)
+
+(defface docbook-subsection-title
+  '((t :weight bold :height 1.1 :inherit variable-pitch))
+  "Face for DocBook subsection titles."
+  :group 'docbook)
+
+(defface docbook-misc-title '((t :weight bold :underline t))
+  "Face for miscellaneous DocBook titles."
+  :group 'docbook)
+
+(defvar docbook-title-markup-alist
+  '((book    . docbook-chapter-title)
+    (chapter . docbook-chapter-title)
+    (sect1   . docbook-section-title)
+    (sect2   . docbook-subsection-title)
+    (sect3   . docbook-subsection-title)
+    (sect4   . docbook-subsection-title)
+    (sect5   . docbook-subsection-title)
+    (section . docbook-section-title)
+    (simplesect . docbook-section-title))
+  "Alist mapping DocBook section types to title faces")
+
+(defvar docbook-text-markup-alist
+  '((emphasis . docbook-emphasis)
+    (foreignphrase . docbook-emphasis)
+    (firstterm . docbook-emphasis)
+    (bridgehead . docbook-section-title)
+    (refname . docbook-section-title)
+    (refpurpose . docbook-emphasis)
+    (citetitle . docbook-citation)
+    (subscript . docbook-small)
+    (superscript . docbook-small)
+    (replaceable . docbook-replaceable)
+    ;; Computer output
+    (accel . docbook-computer)
+    (computeroutput . docbook-computer)
+    (guibutton . docbook-computer)
+    (guiicon . docbook-computer)
+    (guilabel . docbook-computer)
+    (guimenu . docbook-computer)
+    (guimenuitem . docbook-computer)
+    (guisubmenu . docbook-computer)
+    (keycap . docbook-computer)
+    (keycode . docbook-computer)
+    (keycombo . docbook-computer)
+    (keysym . docbook-computer)
+    (markup . docbook-computer)
+    (menuchoice . docbook-computer)
+    (mousebutton . docbook-computer)
+    (msgset . docbook-computer)
+    (prompt . docbook-computer)
+    (shortcut . docbook-computer)
+    (tag . docbook-computer)
+    (userinput . docbook-computer)
+    ;; Computer terminology
+    (application . docbook-computer-term)
+    (classname . docbook-computer-term)
+    (command . docbook-computer-term)
+    (constant . docbook-computer-term)
+    (database . docbook-computer-term)
+    (envar . docbook-computer-term)
+    (errorcode . docbook-computer-term)
+    (errorname . docbook-computer-term)
+    (errortype . docbook-computer-term)
+    (filename . docbook-computer-term)
+    (function . docbook-computer-term)
+    (hardware . docbook-computer-term)
+    (option . docbook-computer-term)
+    (optional . docbook-computer-term)
+    (parameter . docbook-computer-term)
+    (property . docbook-computer-term)
+    (returnvalue . docbook-computer-term)
+    (symbol . docbook-computer-term)
+    (systemitem . docbook-computer-term)
+    (token . docbook-computer-term)
+    (type . docbook-computer-term)
+    (varname . docbook-computer-term)
+    ;; docbook-literal
+    (literal . docbook-literal)
+    ;; Admonitions
+    (caution . docbook-warning)
+    (important . docbook-emphasis)
+    (tip . docbook-emphasis)
+    (warning . docbook-warning))
+  "Alist mapping DocBook element types to markup faces.")
+
+(defvar docbook-page-types
+  '(acknowledgements appendix article bibliography book chapter colophon
+    dedication glossary part preface sect1 sect2 sect3 sect4 sect5
+    section set setindex toc)
+  "List of DocBook sectioning element types.
+DocBook mode shows one section at a time, as a single page.")
+
+(defvar docbook-block-types
+  '(para simpara formalpara equation informalequation
+    informalexample figure informalfigure
+    blockquote epigraph msgset sidebar
+    bridgehead caution important note tip warning
+    cmdsynopsis)
+  "List of DocBook block types which require no additional processing.")
+
+(defvar docbook-list-types
+  '(calloutlist bibliolist glosslist itemizedlist orderedlist
+    segmentedlist simplelist variablelist qandaset
+    task procedure substeps)
+  "List of DocBook block-level list types")
+
+(defvar docbook-literal-block-types
+  '(address literallayout programlisting screen screenco
+    screenshot synopsis)
+  "List of DocBook block element types which preserve whitespace.")
+
+(defvar docbook-suppressed-types
+  '(comment info bookinfo chapterinfo sectioninfo articleinfo label
+    refmeta refclass)
+  "List of DocBook element types which are not printed.")
+
+(defvar docbook-index-separator-column 30
+  "Column number of xrefs printed by `docbook--print-index'.")
+
+(defvar docbook-entity-alist
+  ;; makeinfo emits these entities, even though the DocBook spec does
+  ;; not appear to define them.
+  '(("lsquo" . "`")
+    ("rsquo" . "'")
+    ("ldquo" . "\"")
+    ("rdquo" . "\"")
+    ("copy" . "(C)")
+    ("tex" . "TeX")
+    ("latex" . "LaTeX")
+    ("hellip" . "...")
+    ("period" . ".")
+    ("minus" . "-")
+    ("colon" . ":")
+    ("mdash" . "--")
+    ("ndash" . "-"))
+  "Alist mapping XML entities to their replacement text.
+These elements are added to `xml-entity-alist' while parsing
+DocBook documents.")
+
+;;; Buffer setup
+
+(defvar docbook--parse-tree nil
+  "Parse tree of the current DocBook document.")
+
+(defvar docbook--id-table nil
+  "Hash table mapping DocBook IDs (symbols) to node contents.
+Each key should be a Lisp symbol.  Each XML node with an XML ID
+is keyed by an interned Lisp symbol with a matching symbol name.
+Sectioning (page) nodes which lack their own XML IDs are keyed
+using uninterned Lisp symbols created when parsing the XML tree.
+
+Each hash table value has one of these two forms:
+
+ (NODE TITLE-NODE PARENT-ID PREV NEXT SUBSECTIONS)
+ (NODE TITLE-NODE PARENT-ID)
+
+The first represents a node corresponding to a DocBook section,
+which is displayed as a separate page in the DocBook reader.
+The second represents a node which does not correspond to a
+DocBook section, e.g. a position within a section for a
+cross-reference to jump to.
+
+NODE is the Lisp list tree corresponding to the XML node.
+TITLE-NODE is the node corresponding to the node's title (a
+string), or nil.
+PARENT-ID is the ID of the node's parent page, or nil.
+PREV and NEXT are the IDs of the previous and next page.
+SUBSECTIONS is a list of IDs of child pages.")
+
+(defvar docbook-id-markers-alist nil
+  "Alist mapping DocBook node IDs to markers.
+Each key should be a Lisp symbol, but it is not required to be
+one of the keys in `docbook--id-table'.  This alist is used to
+record the positions of xref'ed elements on the current page.")
+
+(defvar docbook-top-page nil
+  "ID of the topmost (root) page in the current DocBook document.
+The value should be one of the keys in `docbook--id-table'.")
+
+(defvar docbook-current-page nil
+  "ID of the current DocBook page.
+The value should be one of the keys in `docbook--id-table'.")
+
+(defvar docbook--last-page-registered)
+(defvar docbook--last-page-id-registered)
+(defvar docbook--footnotes)
+(defvar docbook--indent-level 0)
+(defvar docbook--list-context nil)
+
+(defvar docbook--index-alist nil
+  "Alist mapping index types to index data.
+Each list element has the form (TYPE . ALIST), where TYPE is a
+symbol specifying the index type (nil for the default index) and
+ALIST is an alist (TERM . ID-LIST).")
+
+(defvar docbook-history nil
+  "List of DocBook node IDs which were previously viewed.")
+
+(defvar docbook-history-forward nil
+  "List of DocBook node IDs visited with `docbook-history-back'.")
+
+;; Used in place of the interned version of the string "nil".
+(defconst docbook--nil (make-symbol "nil"))
+
+(defun docbook-setup (parse-tree)
+  "Set up a DocBook buffer using the XML parse tree PARSE-TREE.
+PARSE-TREE should be a list of the sort returned by
+`xml-parse-file' or `xml-parse-buffer'."
+  (docbook-mode)
+  (setq docbook--parse-tree parse-tree
+       docbook--id-table (make-hash-table :test 'eq)
+       docbook--index-alist nil
+       docbook-history nil
+       docbook-history-forward nil)
+  (let ((docbook--last-page-registered nil)
+       (docbook--last-page-id-registered nil))
+    (docbook-register-node parse-tree nil nil))
+  ;; Sort indices
+  (dolist (index docbook--index-alist)
+    (setcdr index (sort (cdr index) (lambda (a b)
+                                     (string< (car a) (car b)))))
+    (dolist (entry (cdr index))
+      (setcdr entry (nreverse (cdr entry))))))
+
+(defun docbook-register-node (node parent-page-id parent-node-id)
+  "Register NODE.
+NODE should be a cons cell---a subnode of the tree returned by
+`xml-parse-file'.  PARENT is the registered node ID of the parent
+page (a symbol).  PARENT-NODE-ID is the registered node ID of the
+node's immediate parent (which may or may not correspond to a
+page node), or nil if the parent has no ID.
+
+If NODE is a page node, return its registered node ID (a symbol).
+Otherwise, return nil."
+  (let ((type (xml-node-name node)))
+    (cond
+     ((eq type 'comment))
+     ((eq type 'indexterm)
+      (docbook--register-indexterm node parent-page-id))
+     ((memq type docbook-page-types)
+      (docbook--register-page-node node parent-page-id))
+     (t
+      (docbook--register-nonpage-node node parent-page-id
+                                     parent-node-id)))))
+
+(defun docbook--register-indexterm (node parent-id)
+  (let ((id (docbook--attr 'id node)))
+    (if id (puthash id `(,node nil ,parent-id) docbook--id-table))
+    ;; HACK: Modify the XML tree to add an indexterm id (a symbol).
+    (setq id (make-symbol "indexterm"))
+    (setcar (cdr node) (cons (cons 'docbook-indexterm-id id)
+                            (xml-node-attributes node)))
+    (puthash id `(,node nil ,parent-id) docbook--id-table)
+    (let* ((type (docbook--attr 'type node))
+          (index (assq type docbook--index-alist)))
+      ;; If there is no index of the indicated type yet, add it.
+      (unless index
+       (setq docbook--index-alist
+             (cons (setq index (cons type nil))
+                   docbook--index-alist)))
+      (dolist (subnode (xml-node-children node))
+       (cond
+        ((not (consp subnode)))
+        ((memq (xml-node-name subnode) '(primary secondary tertiary))
+         (let* ((term (docbook--node-text subnode))
+                (entry (assoc term (cdr index))))
+           (if entry
+               (setcdr entry (cons id (cdr entry)))
+             (setcdr index (cons (list term id) (cdr index))))))))
+      nil)))
+
+(defun docbook--register-page-node (node parent-id)
+  (let ((id (docbook--attr 'id node)))
+    ;; If there is no ID, generate an uninterned symbol as the ID.
+    (unless id
+      (setq id (make-symbol "Unnamed section")))
+    (unless parent-id
+      (setq docbook-top-page id))
+    ;; Make the node record and update the NEXT record of the last node
+    ;; processed.  This must be done before descending into the tree.
+    (if docbook--last-page-registered
+       (setcar (nthcdr 4 docbook--last-page-registered) id))
+    (let ((record (list node nil parent-id
+                       docbook--last-page-id-registered nil nil)))
+      (setq docbook--last-page-registered record
+           docbook--last-page-id-registered id)
+      ;; Add the entry for this page node into the hash table.
+      (if id (puthash id record docbook--id-table))
+      ;; Descend into the children, registering them.
+      (let ((subnodes
+            (mapcar (lambda (subnode)
+                      (when (consp subnode)
+                        (docbook-register-node subnode id id)))
+                    (xml-node-children node))))
+       ;; If this is a section node, update its record with the IDs of
+       ;; the subsections, then return the ID of this node.
+       (setcar (nthcdr 5 record) (delq nil subnodes))))
+    id))
+
+(defun docbook--register-nonpage-node (node parent-page-id parent-node-id)
+  (let ((id (docbook--attr 'id node)))
+    ;; If this is a title node, register it in the parent node.
+    (when (and (eq (xml-node-name node) 'title) parent-node-id)
+      (let ((parent-record (docbook--node-record parent-node-id)))
+       (if parent-record (setcar (cdr parent-record) node))))
+    ;; Construct the node record.
+    (if id (puthash id `(,node nil ,parent-page-id) docbook--id-table))
+    ;; Descend into the children, registering them.
+    (dolist (subnode (xml-node-children node))
+      (when (consp subnode)
+       (docbook-register-node subnode parent-page-id id)))
+    nil))
+
+;;; Utility functions
+
+(defsubst docbook--node-record (&optional node-id)
+  "Return the record keyed by NODE-ID in `docbook--id-table'.
+If NODE-ID is nil, it defaults to ID of the current page."
+  (gethash (or node-id docbook-current-page) docbook--id-table))
+
+(defsubst docbook-add-fragment-link (id)
+  "If ID is non-nil, add a marker for it to `docbook-id-markers-alist'."
+  (if id (push (cons id (point-marker)) docbook-id-markers-alist)))
+
+(defun docbook--attr (attribute node)
+  "Return the value of attribute ATTRIBUTE in xml node NODE.
+The value is automatically converted to a Lisp symbol.  If the
+node lacks the specified attribute, return nil."
+  (let ((str (cdr (assq attribute (xml-node-attributes node)))))
+    (and (stringp str)
+        (not (equal str ""))
+        (if (equal str "nil") docbook--nil (intern str)))))
+
+(defun docbook--display-string (base-string fallback)
+  "Return a string which displays as BASE-STRING on graphical terminals.
+Use a display property so that on non-graphical terminals, the
+string displays as the FALLBACK string."
+  (propertize base-string
+             'display `(when (not (display-graphic-p)) . ,fallback)))
+
+(defun docbook--node-text (node)
+  "Return the contents of the DocBook node NODE, as a string."
+  (let ((str (mapconcat
+             (lambda (x)
+               (cond ((stringp x)
+                      (if (string-match "\\`\\s-+\\'" x) "" x))
+                     ((consp x)
+                      (docbook--node-text x))))
+             (xml-node-children node)
+             "")))
+    (if (string-match "\\`\\s-+" str)
+       (setq str (substring str (match-end 0))))
+    (if (string-match "\\s-+\\'" str)
+       (setq str (substring str 0 (match-beginning 0))))
+    str))
+
+(defun docbook--print-block-delimiter ()
+  "Insert newlines for the start or end of a DocBook block element."
+  (cond
+   ((bobp))
+   ((looking-back "\n\n"))
+   ((eq (char-before) ?\n) (insert ?\n))
+   (t (insert "\n\n"))))
+
+(defun docbook--print-string (str &optional literal face)
+  "Insert STR (a string) at point, unless it is useless whitespace.
+If LITERAL is non-nil, preserve whitespace.  If FACE is non-nil,
+apply it as the face for the inserted text."
+  (cond ((or literal (not (string-match "\\`\\s-+\\'" str)))
+        (insert (propertize str 'font-lock-face face)))
+       ((not (or (bolp) (memq (char-before) '(?\s ?\t))))
+        (insert " "))))
+
+(defun docbook--merge-face (base-face face)
+  "Return a face or list of faces, by merging BASE-FACE and FACE."
+  (cond
+   ((null base-face) face)
+   ((null face)      base-face)
+   ((eq face base-face) base-face)
+   (t
+    (append (if (consp face) face (list face))
+           (if (consp base-face) base-face (list base-face))))))
+
+(defun docbook--node-face (base-face type &optional parent)
+  "Return a face suitable for displaying DocBook node type TYPE.
+BASE-FACE is the face specified by the node's parent elements.
+If PARENT is non-nil, treat TYPE as the type of the parent node,
+and assume that we are looking up the face of a title node."
+  (let ((face (if parent
+                 (or (cdr (assq type docbook-title-markup-alist))
+                     'docbook-misc-title)
+               (cdr (assq type docbook-text-markup-alist)))))
+    (docbook--merge-face base-face face)))
+
+;;; Rendering DocBook
+
+(defun docbook-print-page (node-id &optional error-msg norecord)
+  "Print the DocBook section corresponding to NODE-ID.
+If NODE-ID is not a registered DocBook section node, signal an
+error.  The optional argument ERROR-MSG, if non-nil, specifies a
+default error message.
+
+If optional argument NORECORD is non-nil, do not record this node
+in `docbook-history'."
+  (let ((node-record (when (and node-id (symbolp node-id))
+                      (docbook--node-record node-id))))
+    (unless node-record
+      (funcall (if (fboundp 'user-error) 'user-error 'error)
+              (or error-msg "Node not found")))
+    (unless norecord
+      (push node-id docbook-history)
+      (setq docbook-history-forward nil))
+    (if (= (length node-record) 3)
+       ;; If the id points to a page fragment, visit the parent page
+       ;; and jump to the relevant marker within that page.
+       (progn
+         (docbook-print-page (nth 2 node-record) nil t)
+         (docbook--visit-xref-marker node-id))
+      ;; If the id points to a page, visit it.
+      (let* ((inhibit-read-only t)
+            (node (car node-record))
+            (subsections (nth 5 node-record))
+            (docbook--footnotes nil))
+       (erase-buffer)
+       ;; Add a fragment marker to the top of this page.
+       (setq docbook-id-markers-alist nil
+             docbook-current-page node-id)
+       (docbook-add-fragment-link node-id)
+       ;; Each section contains any number of blocks followed by any
+       ;; number of subsections.  Loop over subnodes, printing
+       ;; block-level nodes.
+       (dolist (subnode (xml-node-children node))
+         (cond ((null subnode))
+               ((stringp subnode)
+                (docbook--print-string subnode))
+               ((not (memq (xml-node-name subnode) docbook-page-types))
+                (docbook--print-node subnode (xml-node-name node)))))
+       ;; If there are footnotes, print them.
+       (docbook--print-footnotes)
+       ;; If there are subsections, print a submenu.
+       (when subsections
+         (docbook--print-block-delimiter)
+         (docbook--print-string "Menu" nil 'docbook-misc-title)
+         (insert "\n")
+         (let ((bullet (docbook--display-string "• " "* "))
+               opoint)
+           (dolist (id subsections)
+             (setq opoint (point))
+             (insert bullet)
+             (docbook-insert-xref id)
+             (insert ?\n)
+             (put-text-property opoint (point) 'docbook-menu-xref id))))
+       (goto-char (point-min))))))
+
+(defun docbook--print-node (node parent-type &optional literal face)
+  "Insert the contents of NODE at point.
+NODE should be a cons cell---a subnode of the tree returned by
+`xml-parse-file'.  PARENT-TYPE should be the node type of the
+parent node (a symbol), or nil if this is the topmost node.
+
+Optional arg LITERAL, if non-nil, means to preserve whitespace
+and newlines when printing this node.
+
+Optional arg FACE, if non-nil, should be a face or list of faces
+to use, by default, for printing this node.  The node may apply
+additional markup on top to of the specified FACE."
+  (let ((type (xml-node-name node)))
+    (cond
+     ((memq type docbook-suppressed-types)
+      (docbook-add-fragment-link (docbook--attr 'id node)))
+     ((eq type 'title)
+      (docbook--print-block node literal
+                           (docbook--node-face face parent-type t)))
+     ((progn
+       ;; For the sake of all the remaining node types, set FACE to
+       ;; the markup face for this node's type.
+       (setq face (docbook--node-face face type))
+       (memq type docbook-block-types))
+      (docbook--print-block node literal face))
+     ((progn
+       ;; For the sake of all remaining node types, apply the
+       ;; fragment ID if any.
+       (docbook-add-fragment-link (docbook--attr 'id node))
+       (eq type 'xref))
+      (docbook--print-xref node literal face))
+     ;; Index handling
+     ((eq type 'indexterm)
+      (docbook-add-fragment-link
+       (cdr (assq 'docbook-indexterm-id (xml-node-attributes node)))))
+     ((eq type 'index)
+      (docbook--print-index (docbook--attr 'type node)))
+     ;; Refentry and friends
+     ((eq type 'refnamediv)
+      (docbook--print-refnamediv node literal face))
+     ((eq type 'refsynopsisdiv)
+      (docbook--print-refsynopsisdiv node literal face))
+     ;; List handling
+     ((memq type docbook-list-types)
+      (docbook--print-list node literal face))
+     ((memq type '(listitem question answer step))
+      (docbook--print-listitem node literal face))
+     ((memq type '(term glossterm))
+      (docbook--print-term node literal face))
+     ;; Cross References
+     ((memq type '(link ulink))
+      (docbook--print-link node literal face))
+     ((eq type 'email)
+      (docbook--print-email node literal face))
+     ;; Misc markup
+     ((eq type 'quote)
+      (docbook--print-string (docbook--display-string "“" "`")
+                            literal face)
+      (docbook--print-children node literal face)
+      (docbook--print-string (docbook--display-string "”" "'")
+                            literal face))
+     ((eq type 'footnote)
+      (docbook--print-footnote-tag node))
+     ((eq type 'subscript)
+      (docbook--print-with-display-prop node literal face '(raise -0.2)))
+     ((eq type 'superscript)
+      (docbook--print-with-display-prop node literal face '(raise 0.2)))
+     ((eq type 'arg)
+      (docbook--print-arg node literal face))
+     ((eq type 'anchor))
+     (t
+      (docbook--print-children node literal face)))))
+
+(defun docbook--print-block (node literal face)
+  (docbook--print-block-delimiter)
+  (let* ((type (xml-node-name node))
+        (beg (point)))
+    ;; If the block has an ID tag, apply it.
+    (docbook-add-fragment-link (docbook--attr 'id node))
+    ;; Print the contents of the block.
+    (docbook--print-children node literal
+                          (docbook--node-face face type))
+    (unless literal
+      ;; Flush the beginning of the block to column zero, and fill.
+      (let ((stop (point)))
+       (save-excursion
+         (goto-char beg)
+         (skip-chars-forward "[:space:]" stop)
+         (delete-region beg (point))
+         (setq beg (point))))
+      (let ((left-margin docbook--indent-level))
+       (fill-region-as-paragraph beg (point))))
+    (docbook--print-block-delimiter)))
+
+(defun docbook--print-list (node literal face)
+  (docbook--print-block-delimiter)
+  (let ((type (xml-node-name node))
+       (docbook--indent-level docbook--indent-level)
+       (docbook--list-context docbook--list-context))
+    (cond
+     ((memq type '(procedure substeps))
+      ;; We use a version list to denote (sub)steps.
+      (let* ((version (if (eq (car-safe docbook--list-context) 'procedure)
+                         (append (cdr docbook--list-context) '(1))
+                       '(1)))
+            (str (mapconcat 'int-to-string version ".")))
+       (setq docbook--indent-level (+ (length str) 3 docbook--indent-level)
+             docbook--list-context (cons 'procedure version))))
+     ((eq type 'orderedlist)
+      (setq docbook--indent-level (+ 4 docbook--indent-level)
+           docbook--list-context 1))
+     ((memq type '(glosslist variablelist))
+      (setq docbook--indent-level (+ 4 docbook--indent-level)
+           docbook--list-context 'variablelist))
+     ((eq type 'qandaset)
+      (let ((label (docbook--attr 'defaultlabel node)))
+       (setq docbook--indent-level (+ 4 docbook--indent-level)
+             docbook--list-context (cons 'qandaset label))))
+     (t
+      (setq docbook--indent-level (+ 2 docbook--indent-level)
+           docbook--list-context 'itemizedlist)))
+    (docbook--print-children node literal face))
+  (docbook--print-block-delimiter))
+
+(defun docbook--print-term (node literal face)
+  (when (eq docbook--list-context 'variablelist)
+    (unless (eq (char-before) ?\n)
+      (insert "\n"))
+    (let ((opoint (point)))
+      (docbook--print-children node literal face)
+      (save-excursion
+       (let ((stop (point)))
+         (goto-char opoint)
+         (skip-chars-forward "[:space:]" stop)
+         (delete-region opoint (point))
+         (indent-line-to (- docbook--indent-level 4))
+         (docbook--print-string (docbook--display-string "• " "* ")
+                                literal face))))))
+
+(defun docbook--print-listitem (node literal face)
+  (let ((opoint (point)))
+    (docbook--print-children node literal face)
+    (when (not (memq docbook--list-context '(nil variablelist)))
+      (cond
+       ;; A step in a procedure
+       ((eq (car-safe docbook--list-context) 'procedure)
+       (let* ((version (cdr docbook--list-context))
+              (str (concat (mapconcat 'int-to-string version ".") ". "))
+              (subversion (nthcdr (1- (length version)) version)))
+         (docbook--print-listitem-1 opoint str (length str)
+                                    literal face)
+         (setcar subversion (1+ (car subversion)))))
+       ;; Question or answer
+       ((eq (car-safe docbook--list-context) 'qandaset)
+       (let ((subnodes (xml-node-children node))
+             label)
+         ;; Look for a label for the question or answer.
+         (while (and (null label) subnodes)
+           (when (and (consp (car subnodes))
+                      (eq (xml-node-name (car subnodes)) 'label))
+             (setq label (docbook--node-text (car subnodes))))
+           (setq subnodes (cdr subnodes)))
+         ;; If there is none, consult the default label.
+         (and (not (stringp label))
+              (eq (cdr docbook--list-context) 'qanda)
+              (setq label (if (eq (xml-node-name node) 'question)
+                              "Q:"
+                            "A:")))
+         (if (null label)
+             ;; Use a bullet, like an itemizedlist.
+             (docbook--print-listitem-1
+              opoint (docbook--display-string "• " "* ") 2 literal face)
+           (docbook--print-listitem-1
+            opoint label 0 literal
+            (docbook--merge-face face 'docbook-label) " " face))))
+       ;; orderedlist
+       ((integerp docbook--list-context)
+       (docbook--print-listitem-1
+        opoint (format "%2d. " docbook--list-context) 4 literal face)
+       (setq docbook--list-context (1+ docbook--list-context)))
+       ;; itemizedlist
+       (t
+       (docbook--print-listitem-1
+        opoint (docbook--display-string "• " "* ") 2 literal face))))))
+
+(defun docbook--print-listitem-1 (opoint bullet bullet-len literal face
+                                 &optional after-string after-string-face)
+  (save-excursion
+    (let ((stop (point)))
+      (goto-char opoint)
+      (skip-chars-forward "[:space:]" stop)
+      (indent-line-to (- docbook--indent-level bullet-len))
+      (docbook--print-string bullet literal face)
+      (if after-string
+         (docbook--print-string after-string literal
+                                after-string-face)))))
+
+(defun docbook--print-footnote-tag (node)
+  (when (boundp 'docbook--footnotes)
+    (let ((n (1+ (length docbook--footnotes)))
+         (tag-id (make-symbol "footnote-id"))
+         (footnote-id (make-symbol "footnote")))
+      (docbook-add-fragment-link tag-id)
+      (docbook-insert-xref footnote-id (format "(%d)" n))
+      (push (list tag-id footnote-id node) docbook--footnotes))))
+
+(defun docbook--print-footnotes ()
+  (when (bound-and-true-p docbook--footnotes)
+    (docbook--print-block-delimiter)
+    (docbook--print-string "--- Footnotes ---")
+    (let ((n 1) opoint)
+      (dolist (footnote (nreverse docbook--footnotes))
+       (docbook--print-block-delimiter)
+       (setq opoint (point))
+       (docbook--print-children (nth 2 footnote))
+       (save-excursion
+         (goto-char opoint)
+         (if (eq (char-after) ?\n) (forward-char))
+         (docbook-add-fragment-link (nth 1 footnote))
+         (docbook-insert-xref (car footnote) (format "(%d)" n))
+         (insert " "))
+       (setq n (1+ n))))))
+
+(defun docbook--print-with-display-prop (node literal face prop)
+  (let ((opoint (point)))
+    (docbook--print-children node literal face)
+    (put-text-property opoint (point) 'display prop)))
+
+(defun docbook--print-children (node &optional literal face)
+  "Print the child nodes of the DocBook node NODE.
+LITERAL and FACE mean the same as in `docbook--print-node'."
+  (dolist (subnode (xml-node-children node))
+    (cond
+     ((null subnode))
+     ((stringp subnode)
+      (docbook--print-string subnode literal face))
+     (t
+      (docbook--print-node subnode (xml-node-name node)
+                          literal face)))))
+
+(defun docbook--print-refnamediv (node literal face)
+  (docbook--print-block-delimiter)
+  (let (names purpose)
+    (dolist (subnode (xml-node-children node))
+      (cond ((not (consp subnode)))
+           ((eq (xml-node-name subnode) 'refname)
+            (push subnode names))
+           ((eq (xml-node-name subnode) 'refpurpose)
+            (setq purpose subnode))))
+    (setq names (nreverse names))
+    (indent-to docbook--indent-level)
+    (while names
+      (docbook--print-node (car names) 'refnamediv literal face)
+      (setq names (cdr names))
+      (if names (docbook--print-string ", " literal face)))
+    (when purpose
+      (or (eq (char-before) ?\n) (insert ?\n))
+      (indent-to docbook--indent-level)
+      (docbook--print-node purpose literal face)))
+  (docbook--print-block-delimiter))
+
+(defun docbook--print-refsynopsisdiv (node literal face)
+  (docbook--print-block-delimiter)
+  (indent-to docbook--indent-level)
+  (docbook--print-string "Synopsis" nil 'docbook-misc-title)
+  (docbook--print-block-delimiter)
+  (docbook--print-children node literal face))
+
+(defun docbook--print-arg (node literal face)
+  (let ((choice (docbook--attr 'choice node))
+       (repeat (docbook--attr 'rep node)))
+    (if (eq choice 'opt)
+       (docbook--print-string "[ " literal face))
+    (docbook--print-children node literal face)
+    (if (eq choice 'opt)
+       (docbook--print-string " ]" literal face))
+    (if (eq repeat 'repeat)
+       (docbook--print-string "..." literal face))))
+
+;;; Cross-reference handling
+
+(defun docbook--print-xref (node literal face)
+  "Insert the contents of an xref node NODE."
+  (let ((target (docbook--attr 'linkend node)))
+    (when target
+      (let ((endterm (docbook--attr 'endterm node)))
+       ;; If an endterm attribute is present, print its contents.
+       ;; FIXME: protect against a recursion bomb.
+       (if (and endterm
+                (setq endterm (car (docbook--node-record endterm))))
+           (docbook--print-link endterm literal face target)
+         (docbook-insert-xref target))))))
+
+(defun docbook--print-link (node literal face &optional linkend)
+  "Insert the contents of a link node NODE."
+  (let ((target (or linkend (docbook--attr 'linkend node)))
+       (opoint (point))
+       (action 'docbook-xref-button-action))
+    (unless target
+      ;; If there is no linkend attribute, look for an external URL.
+      (let ((attributes (xml-node-attributes node)))
+       (setq target
+             (or (cdr (assq 'xlink:href attributes))
+                 (cdr (assq 'href attributes))
+                 ;; Used by obsolete `url' elements.
+                 (cdr (assq 'url attributes))))
+       (setq action 'docbook-link-button-action)))
+    (docbook--print-children node literal face)
+    (make-text-button opoint (point)
+                     'action action
+                     'docbook-target target)))
+
+(defun docbook--print-email (node literal face)
+  "Insert the contents of a link node NODE."
+  (let ((opoint (point)))
+    (docbook--print-children node literal face)
+    (make-text-button opoint (point)
+                     'action 'docbook-email-button-action)))
+
+(defun docbook-insert-xref (node-id &optional label)
+  "Insert a cross reference to NODE-ID at point.
+NODE-ID should be a node ID, as either a symbol or a string.
+LABEL, if non-nil, specifies the text label."
+  (unless label
+    (setq label (docbook-node-label node-id)))
+  (insert-text-button label
+                     'action 'docbook-xref-button-action
+                     'docbook-target node-id))
+
+(defun docbook-node-label (node-id)
+  "Return an appropriate label for the node with ID NODE-ID."
+  (let* ((record (docbook--node-record node-id))
+        (attributes (xml-node-attributes (car record)))
+        ;; Use the target node's xreflabel attribute.
+        (label (cdr (assq 'xreflabel attributes))))
+    (when (memq label '(nil ""))
+      ;; Otherwise, use the target node's title.
+      (setq label (and (nth 1 record)
+                      (docbook--node-text (nth 1 record))))
+      (when (memq label '(nil ""))
+       ;; Otherwise, default to the node ID's name.
+       (setq label (symbol-name node-id))))
+    label))
+
+(defun docbook--visit-xref-marker (node-id &optional noerror)
+  "Visit the position of NODE-ID on the current DocBook page.
+Return non-nil if we found the element and jumped to it.
+Otherwise, signal an error if NOERROR is nil, and return nil if
+NOERROR is non-nil."
+  (let ((marker (cdr (assq node-id docbook-id-markers-alist))))
+    (cond
+     ((markerp marker)
+      (goto-char marker))
+     ((null noerror)
+      (error "Node not found")))))
+
+(defun docbook-visit-xref (node-id)
+  (or (docbook--visit-xref-marker node-id t)
+      (docbook-print-page node-id)))
+
+(defun docbook-xref-button-action (button)
+  "Visit the DocBook node indicated by BUTTON."
+  (docbook-visit-xref (button-get button 'docbook-target)))
+
+(defun docbook-link-button-action (button)
+  "Call `browse-url' to visit the link indicated by BUTTON."
+  (let ((target (button-get button 'docbook-target)))
+    (if (string-match "\\`mailto:"; target)
+       (compose-mail (substring-no-properties target (match-end 0)))
+      (browse-url (button-get button 'docbook-target)))))
+
+(defun docbook-email-button-action (button)
+  "Send mail to the address indicated by BUTTON."
+  (compose-mail (buffer-substring-no-properties
+                (button-start button) (button-end button))))
+
+;; Printing the index and history list
+
+(defun docbook--print-index (type)
+  "Insert the DocBook index of type TYPE at point."
+  (let ((index (assq type docbook--index-alist))
+       (bullet (docbook--display-string "• " "* "))
+       opoint)
+    (unless (eq (char-before) ?\n) (insert ?\n))
+    (dolist (entry (cdr index))
+      (setq opoint (point))
+      (insert bullet)
+      (insert (car entry))
+      (let* ((ids (cdr entry))
+            (id (car ids)))
+       (indent-to docbook-index-separator-column 2)
+       (docbook-insert-xref
+        id (docbook-node-label (nth 2 (docbook--node-record id))))
+       (insert ?\n)
+       (put-text-property opoint (point) 'docbook-menu-xref id)
+       (if (> (length ids) 1)
+           (dolist (id (cdr ids))
+             (setq opoint (point))
+             (indent-to docbook-index-separator-column 2)
+             (docbook-insert-xref
+              id (docbook-node-label
+                  (nth 2 (docbook--node-record id))))
+             (insert ?\n)
+             (put-text-property opoint (point) 'docbook-menu-xref id)))))
+    (insert ?\n)))
+
+(defun docbook--print-history ()
+  "Insert the DocBook navigation history menu at point."
+  (let ((bullet (docbook--display-string "◦ " "* ")))
+    (dolist (id (reverse (cdr docbook-history)))
+      (unless (eq (char-before) ?\n) (insert ?\n))
+      (insert bullet)
+      (docbook-insert-xref id))
+    ;; Indicate the current page with a more prominent bullet.
+    (unless (eq (char-before) ?\n) (insert ?\n))
+    (insert (docbook--display-string "• " "* "))
+    (docbook-insert-xref (car docbook-history))
+    (dolist (id docbook-history-forward)
+      (unless (eq (char-before) ?\n) (insert ?\n))
+      (insert bullet)
+      (docbook-insert-xref id))
+    (insert ?\n)))
+
+;;; Major mode
+
+(defvar docbook-mode-map
+  (let ((map (make-keymap)))
+    (set-keymap-parent map (make-composed-keymap button-buffer-map
+                                                 special-mode-map))
+    (define-key map "." 'beginning-of-buffer)
+    (define-key map " " 'docbook-scroll-up)
+    (define-key map "\177" 'docbook-scroll-down)
+    (define-key map "\C-m" 'docbook-follow-nearest-node)
+
+    (dotimes (n 9)
+      (define-key map (number-to-string (1+ n)) 'docbook-nth-menu-item))
+
+    (define-key map "b" 'beginning-of-buffer)
+    (define-key map "e" 'end-of-buffer)
+    (define-key map "\M-n" 'clone-buffer)
+
+    (define-key map "i" 'docbook-index)
+    (define-key map "I" 'docbook-index)
+    (define-key map "l" 'docbook-history-back)
+    (define-key map "r" 'docbook-history-forward)
+    (define-key map "L" 'docbook-history)
+
+    (define-key map "]" 'docbook-forward-page)
+    (define-key map "[" 'docbook-backward-page)
+    (define-key map "n" 'docbook-forward-page)
+    (define-key map "p" 'docbook-backward-page)
+
+    ;; (define-key map "f" 'docbook-follow-reference)
+    ;; (define-key map "g" 'docbook-goto-node)
+    ;; (define-key map "m" 'docbook-menu)
+
+    ;; (define-key map "s" 'docbook-search)
+    ;; (define-key map "S" 'docbook-search-case-sensitively)
+    ;; (define-key map "T" 'docbook-toc)
+    ;; (define-key map "," 'docbook-index-next)
+
+    (define-key map "t" 'docbook-top-page)
+    (define-key map "u" 'docbook-up)
+    (define-key map "^" 'docbook-up)
+    (define-key map [follow-link] 'mouse-face)
+    map)
+  "Keymap containing DocBook commands.")
+
+(define-derived-mode docbook-mode special-mode "DocBook"
+  "Major mode for viewing DocBook documents.
+Type \\[docbook-find-file] to visit DocBook files for viewing.
+Most of the commands in DocBook mode are similar to Info mode.
+
+DocBook documents are divided into \"section nodes\" (which
+includes chapters, sections, subsections, etc.).  DocBook mode
+displays one section node at a time, as a single page.
+Navigation commands and hyperlinks can be used to view other
+pages.
+
+Moving within a page:
+\\[docbook-scroll-up]  Normally, scroll forward a full screen.
+       If you have scrolled to the end of this page,
+       view the next page.
+\\[docbook-scroll-down]        Normally, scroll backward a full screen.
+       If you have scrolled to the beginning of this page,
+       view the preceding page.
+\\[beginning-of-buffer]        Jump to beginning of this page.
+
+Selecting other nodes:
+\\[docbook-follow-nearest-node]        Follow a node reference near point.
+\\[docbook-backward-page]      View the preceding page.
+\\[docbook-forward-page]       View the next page.
+\\[docbook-up] View the parent of the current page.
+\\[docbook-top-page]   View the topmost section of this document.
+\\[docbook-history-back]       View the last page you were at.
+\\[docbook-history-forward]    Move forward in history to the page you were at 
before using \\[docbook-history-back].
+\\[docbook-history]    View a menu of visited pages."
+  (make-local-variable 'docbook--parse-tree)
+  (make-local-variable 'docbook--id-table)
+  (make-local-variable 'docbook-current-page)
+  (make-local-variable 'docbook-top-page)
+  (make-local-variable 'docbook-id-markers-alist)
+  (make-local-variable 'docbook--index-alist)
+  (make-local-variable 'docbook-history)
+  (make-local-variable 'docbook-history-foward)
+  (setq-local adaptive-fill-mode nil)
+  (setq indent-tabs-mode nil)
+  (setq fill-prefix nil)
+  (setq use-hard-newlines t))
+
+;;; Navigation commands
+
+(defun docbook-up ()
+  "View the parent of the current DocBook page."
+  (interactive)
+  (docbook-print-page (nth 2 (docbook--node-record)) "No parent page"))
+
+(defun docbook-top-page ()
+  "View the topmost page in the current DocBook document."
+  (interactive)
+  (docbook-print-page docbook-top-page))
+
+(defun docbook-backward-page ()
+  "View the previous DocBook page."
+  (interactive)
+  (docbook-print-page (nth 3 (docbook--node-record)) "No previous page"))
+
+(defun docbook-forward-page ()
+  "View the next DocBook page."
+  (interactive)
+  (docbook-print-page (nth 4 (docbook--node-record)) "No following page"))
+
+(defun docbook-scroll-up ()
+  "Scroll forward, or view the next DocBook page."
+  (interactive)
+  (condition-case nil
+      (scroll-up nil)
+    (end-of-buffer (docbook-forward-page))))
+
+(defun docbook-scroll-down ()
+  "Scroll backward, or view the preceding DocBook page."
+  (interactive)
+  (condition-case nil
+      (scroll-down nil)
+    (beginning-of-buffer (docbook-backward-page))))
+
+(defun docbook-nth-menu-item ()
+  "View the Nth menu item, based on the key typed."
+  (interactive)
+  (let ((n (- (aref (this-command-keys)
+                   (1- (length (this-command-keys)))) ?0))
+       (node-record (docbook--node-record)))
+    (unless node-record
+      (funcall (if (fboundp 'user-error) 'user-error 'error)
+              "No menu in this node"))
+    (let ((id (nth (1- n) (nth 5 node-record))))
+      (unless id
+       (funcall (if (fboundp 'user-error) 'user-error 'error)
+                "Too few items in menu"))
+      (docbook-visit-xref id))))
+
+(defun docbook-follow-nearest-node ()
+  "Follow a node reference near point.
+If point is on a reference, follow that reference.  Otherwise,
+if point is in a menu item description, follow that menu item."
+  (interactive)
+  (let ((id (get-text-property (point) 'docbook-menu-xref)))
+    (if id
+       (docbook-visit-xref id)
+      (funcall (if (fboundp 'user-error) 'user-error 'error)
+              "Point neither in reference nor in menu item description"))))
+
+;; History commands
+
+(defun docbook-history-back (n)
+  "Go back in history to the previous DocBook page viewed."
+  (interactive "p")
+  (dotimes (_i n)
+    (unless (cdr docbook-history)
+      (funcall (if (fboundp 'user-error) 'user-error 'error)
+              "This is the first node you looked at"))
+    (push (pop docbook-history) docbook-history-forward)
+    (docbook-print-page (car docbook-history) nil t)))
+
+(defun docbook-history-forward (n)
+  "Go forward in history to the next DocBook page viewed."
+  (interactive "p")
+  (dotimes (_i n)
+    (if (null docbook-history-forward)
+       (funcall (if (fboundp 'user-error) 'user-error 'error)
+                "This is the last node you looked at"))
+    (let ((id (pop docbook-history-forward)))
+      (push id docbook-history)
+      (docbook-print-page id nil t))))
+
+(defun docbook-history ()
+  "Display a list of recently-visited DocBook pages."
+  (interactive)
+  (let ((inhibit-read-only t))
+    (erase-buffer)
+    (docbook--print-string "Recently visited pages"
+                          nil 'docbook-chapter-title)
+    (insert ?\n ?\n)
+    (docbook--print-history)))
+
+;; Misc commands
+
+(defun docbook-index (type)
+  "Display a list of index topics fo the current DocBook document.
+The argument TYPE is the index type; DocBook documents can define
+several indices for different topics.  If called interactively,
+prompt for TYPE."
+  (interactive (list (if (<= (length docbook--index-alist) 1)
+                        (caar docbook--index-alist)
+                      (completing-read
+                       (format "View index type%s: "
+                               (if (assq nil docbook--index-alist)
+                                   " (empty input for default index)"
+                                 ""))
+                       (cons "" (mapcar (lambda (x) (symbol-name (car x)))
+                                        docbook--index-alist))
+                       nil t))))
+  (unless (assq type docbook--index-alist)
+    (funcall (if (fboundp 'user-error) 'user-error 'error)
+            "Index is empty"))
+  (let ((inhibit-read-only t))
+    (erase-buffer)
+    (docbook--print-string (if type
+                              (format "Index: %s" (symbol-name type))
+                            "Index")
+                          nil 'docbook-chapter-title)
+    (insert ?\n ?\n)
+    (docbook--print-index type)))
+
+(defun docbook-find-file (filename)
+  "Visit FILENAME as a DocBook document."
+  (interactive "fView DocBook file: ")
+  (docbook-setup
+   (car (let ((xml-entity-alist (append docbook-entity-alist
+                                       xml-entity-alist)))
+         (xml-parse-file filename))))
+  (docbook-print-page docbook-top-page))
+
+(provide 'docbook)
+
+;;; docbook.el ends here



reply via email to

[Prev in Thread] Current Thread [Next in Thread]