First checked in version
authorThomas Wolmer <thomas.wolmer@projectaon.org>
Wed, 25 May 2005 21:29:52 +0000 (21:29 +0000)
committerThomas Wolmer <thomas.wolmer@projectaon.org>
Wed, 25 May 2005 21:29:52 +0000 (21:29 +0000)
git-svn-id: https://projectaon.org/data/trunk@66 f6f3e2d7-ff33-0410-aaf5-b4bee2cdac11

scripts/aon.el [new file with mode: 0755]

diff --git a/scripts/aon.el b/scripts/aon.el
new file mode 100755 (executable)
index 0000000..b099dd9
--- /dev/null
@@ -0,0 +1,498 @@
+;;; aon.el --- utilities for implementing Project Aon errata
+
+;; Copyright (C) 2003, 2004, 2005 Thomas Wolmer & Project Aon
+
+;; Author: Thomas Wolmer <thomas@powerpuff.org>
+;; Created: 17 Aug 2003
+;; Version: 0.12
+;; Keywords: aon 
+
+;;; Commentary:
+
+;; This code provides support for implementing errata in the XML files
+;; Project Aon uses as "single sources" for the books it publishes. For
+;; more information on Project Aon, see http://www.projectaon.org/.
+
+;;; Change Log:
+
+;; 2003-08-17: 0.01: First nicely formatted version.
+;; 2003-08-24: 0.02: Fixed bugs in aon-find-errata-entry and aon-fix-markup,
+;;                   implemented aon-errata-jump and support functions.
+;; 2003-08-31: 0.03: Added default section id and title, and modified
+;;                   aon-get-sect-id and aon-get-sect-title, so that errata
+;;                   in the blurb gets listed correctly. Fixed some major
+;;                   problems in aon-errata-replace-all.
+;; 2003-09-07: 0.04: Improved usability: aon-errata-replace[-all] presents
+;;                   better prompt and a default text, and checks for "same"
+;;                   and null replacements. aon-errata-add also checks null
+;;                   input. All four user functions take an optional comment.
+;; 2003-09-14: 0.05: Now saves all input to the interactive functions to avoid
+;;                   losing them if the user changes the selection/point while
+;;                   answering the interactive questions. Fixed bug in
+;;                   aon-errata-add which asked you for a comment twice.
+;;                   Extended aon-fix-markup.
+;; 2003-10-12: 0.06: XEmacs does not put mouse-selected region in kill ring by
+;;                   default and does not have match-string-no-properties.
+;;                   Oops. Now things work in XEmacs, but regressions tests
+;;                   with GNU Emacs should probably be done too.
+;; 2003-10-31: 0.07: Used defconst to define some of the constants.
+;; 2003-11-12: 0.08: Flesh out the aon-nonindexed-sects definition
+;; 2004-05-09: 0.09: Fixed too greedy anchor tag regexp in aon-fix-markup &
+;;                   added more 04wotw sections to aon-nonindexed-sects
+;; 2005-02-05: 0.10: Added a function for inserting large illustrations
+;; 2005-05-06: 0.11: Added a function for inserting inline illustrations
+;; 2005-05-23: 0.12: Fixed bug in aon-re-get-errata-entry-id.
+
+;; A slight limitation: We can only handle one illustrator at a time.
+
+(defvar aon-illustrator-name "Paul Bonner"
+  "*")
+(defvar aon-large-illustration-width "386"
+  "*")
+
+
+;;; Code:
+
+(defconst aon-nonindexed-sects
+  (list
+   ;; kai disciplines
+   "camflage" "hunting" "sixthsns" "tracking" "healing" "wepnskll" "wepnskll"
+   "mndblst" "anmlknsp" "mindomtr"
+   ;; magnakai disciplines
+   "wpnmstry" "anmlctrl" "curing" "invsblty" "hntmstry" "pthmnshp" "psisurge"
+   "psiscrn" "nexus" "dvnation" "lcbonus"
+   ;; magnakai improved disciplines
+   "primate" "tutelary" "prncpln" "mentora" "scion" "archmstr"
+   ;; grandmaster disciplines
+   "mksumary" "wpnmstry" "anmlmstr" "deliver" "assimila" "hntmstry" "pthmnshp"
+   "kaisurge" "kaiscrn" "nexus" "gnosis" "magi" "alchemy"
+   ;; grandmaster improved disciplines
+   "guardian" "sunkght"
+   ;; gs lesser magicks
+   "lessmcks" "sorcery" "enchant" "elementl" "alchemy" "prophecy" "psycmncy"
+   "evcation" "staff"
+   ;; gs higher magicks
+   "highmcks" "thamtrgy" "telergy" "physirgy" "theurgy" "visionry" "necrmncy"
+   ;; general
+   "toc" "credits" "howcarry" "howmuch" "howuse" "evasion" "smevazn" "errintro"
+   "errerr" "primill" "secill")
+  "These sections are ignored for errata entries; the enclosing section is
+used instead (unless it is also 'nonindexed').")
+
+(defconst aon-default-sect-id "title"
+  "The default section to assign an erratum to if none is found.")
+(defconst aon-default-sect-title "Title Page"
+  "The title of the 'default section' (see `aon-default-sect-id').")
+
+;; Commonly used search regexps
+(defconst aon-re-errerr-sect "<section.*id=\"errerr\".*>"
+  "Regexp used to locate the start of the errata entry list.")
+(defconst aon-re-get-sect-id "<section.*id=\"\\(.+?\\)\".*>"
+  "Regexp used to find the id of asection.")
+(defconst aon-re-get-errata-entry-id "<p>(<a idref=\"\\(.+?\\)\".*>)"
+  "Regexp used to find the id of an errata list entry.")
+(defconst aon-re-get-title "<title>\\(.+?\\)</title>"
+  "Regexp used to find the title of a section.")
+
+;; The errata item texts (as templates to be fed to 'format')
+;; These are only (hardcoded) reader-visible texts inserted by this hack!
+;; TODO: rename these variabled. defconst?
+(setq replaceditemtext " Replaced <quote>%s</quote> with <quote>%s</quote>%s.")
+(setq addeditemtext " Added <quote>%s</quote>%s.")
+(setq deleteditemtext " Deleted <quote>%s</quote>%s.")
+(setq replacedallitemtext (concat " Replaced all occurrences of <quote>%s"
+                                  "</quote> with <quote>%s</quote>%s."))
+
+;; TODO: an optional second argument should be where the search starts,
+;; instead of at the beginning of the file
+(defun aon-get-sect-pos (sect)
+  "Returns the start position of the named section.
+Useful for comparing the order of sections."
+  (save-excursion
+    (goto-char (point-min))
+    (re-search-forward (format "<section.*id=\"%s\".*>" sect) nil t)
+    (match-beginning 0)))
+
+;; TODO: rewrite this property fetching stuff, it is way too inefficient
+
+(defun aon-get-sect-property (regexp)
+  "Returns a property, as selected by regexp, of the current indexed section."
+  (save-excursion
+    ;; if we're in an ignored section, go to its beginning and repeat search
+    (if (member (aon-get-unchecked-sect-id) aon-nonindexed-sects)
+        (progn
+          (search-backward "<section" nil t)
+          (aon-get-sect-property regexp))
+      (aon-get-unchecked-sect-property regexp))))
+
+(defun aon-get-unchecked-sect-property (regexp)
+  "Returns a property, as selected by regexp, of the current section.
+Unlike aon-get-sect-property, it does not checked if it is indexed."
+  (save-excursion
+    (and (re-search-backward regexp nil t)
+        ;;(match-string-no-properties 1)))) ; Doesn't work in XEmacs
+        (match-string 1))))
+
+(defun aon-get-unchecked-sect-id ()
+  "Returns the id of the current section.
+Unlike aon-get-sect-id, it does not check if it is indexed."
+  (or (aon-get-unchecked-sect-property aon-re-get-sect-id)
+      aon-default-sect-id)) ; workaround for the blurb and such
+
+(defun aon-get-sect-id ()
+  "Returns the id of the current indexed section."
+  (or (aon-get-sect-property aon-re-get-sect-id)
+      aon-default-sect-id)) ; workaround for the blurb and such
+
+(defun aon-get-sect-title ()
+  "Returns the title of the current indexed section."
+  (if (string= (aon-get-sect-id) "title")
+      aon-default-sect-title ; workaround for the blurb and such
+    (aon-get-sect-property aon-re-get-title)))
+
+(defun aon-find-errata-entry ()
+  "Finds the insertion position in the errata entry for the current section.
+If no errata entry exists, returns nil."
+  (save-excursion
+    (let ((id (aon-get-sect-id)))
+      (re-search-forward aon-re-errerr-sect)
+      ;; locate the end of the errata list so that we don't search too far and
+      ;; start finding footnotes instead
+      (when (search-forward (format "<p>(<a idref=\"%s\">" id)
+                            (save-excursion (search-forward "</section>"))
+                            t)
+        (search-forward "</p>") ; place position last in entry
+        (match-beginning 0)))))
+
+(defun aon-create-errata-entry ()
+  "Creates an errata entry for the current section.
+Returns the errata item insertion point in the new entry."
+  (interactive) ;; for testing only
+  (setq newerrataentry (format "<p>(<a idref=\"%s\">%s</a>)</p>\n"
+                               (aon-get-sect-id) (aon-get-sect-title)))
+  (save-excursion
+    (aon-goto-new-errata-list-entry-pos (point))
+    (insert newerrataentry)
+    (search-backward "</p>")
+    (indent-according-to-mode)
+    (point))) ; goto insertion point and make it be returned
+
+;; This function is HORRIBLY slow due to the calls to aon-get-sect-pos
+(defun aon-goto-new-errata-list-entry-pos (sectpos)
+  "Places the point at the position where a new errata entry shall be created."
+  (re-search-forward aon-re-errerr-sect)
+  (search-forward "<data>")
+  (setq endoferrata (save-excursion
+                      (if (search-forward "</data>")
+                          (point)
+                        (error "Could not find the end of the errata list!"))))
+  (while (let ((id (save-excursion
+                     (if (re-search-forward aon-re-get-errata-entry-id
+                                            endoferrata
+                                            t)
+                         ;;(match-string-no-properties 1) ; Boohoo XEmacs
+                        (match-string 1)
+                       "footnotz" ; hack warning! to avoid getting too far
+                       ))))
+           (unless (setq thissectpos (aon-get-sect-pos id))
+             (error "Section %s has an errata entry but does not exist!" id))
+           (< thissectpos sectpos))
+    (forward-line 1)))
+
+;; TODO: Figure out some way not to create an errata entry before we know that
+;; it will work OK. As it is now, an empty errata entry may be left behind
+;; if an error occurs. But maybe that's not a problem??
+(defun aon-insert-errata-entry (errataentry id)
+  "Adds an errata item to the current indexed section's errata entry.
+An errata entry is created if it does not exist."
+  (save-excursion
+    (goto-char (or (aon-find-errata-entry) ; if it is not found...
+                   (aon-create-errata-entry))) ; ...create the entry
+    (insert (aon-format-errata-item id errataentry))))
+
+(defun aon-format-errata-item (id errataentry)
+  ""
+  (format "<!--%s-ITEM-->%s<!--/%s-ITEM-->" id errataentry id))
+
+(defun aon-format-erratum (id newtext)
+  ""
+  (format "<!--%s-->%s<!--/%s-->" id newtext id))
+
+(defun aon-format-erratum-empty (id)
+  ""
+  (format "<!--%s/-->" id))
+
+(defun aon-errata-replace (beg end newtext oldtext &optional comment)
+  "Replaces the current region and records the replacement."
+  (interactive
+   ;; save all positions and string as the user may change the selection!
+   (let* ((xbeg (region-beginning))
+          (xend (region-end))
+          ;;(xoldtext (car kill-ring-yank-pointer)) ; Boohoo Xemacs
+         (xoldtext (buffer-substring-no-properties xbeg xend))
+          (xnewtext (read-string (format "Replace '%s' with: " xoldtext)
+                                 xoldtext))
+          (cmnt (read-string "Additional comment (optional): ")))
+     (list xbeg xend xnewtext xoldtext cmnt)))
+  (cond ((string= newtext "")
+         (error "No replacement! To delete text, use 'aon-errata-delete'."))
+        ((string= newtext oldtext)
+         (error "The replacement is the same as the original!")))
+  (let* ((id (aon-get-new-errata-id "RE"))
+         (errataentry (format replaceditemtext
+                              (aon-fix-markup oldtext)
+                              (aon-fix-markup newtext)
+                              comment)))
+    (kill-region beg end)
+    (insert (aon-format-erratum id newtext))
+    (aon-insert-errata-entry errataentry id)))
+
+(defun aon-errata-delete (beg end oldtext &optional comment)
+  "Deletes the current region and records the deletion."
+  (interactive
+   ;; save all positions and string as the user may change the selection!
+   (let* ((xbeg (region-beginning))
+         (xend (region-end))
+          ;;(xoldtext (car kill-ring-yank-pointer)) ; Boohoo Xemacs
+         (xoldtext (buffer-substring-no-properties xbeg xend))
+         (cmnt (read-string "Additional comment (optional): ")))
+     (list xbeg xend xoldtext cmnt)))
+  (let* ((id (aon-get-new-errata-id "DE"))
+         (errataentry (format deleteditemtext
+                              (aon-fix-markup oldtext)
+                              comment)))
+    (kill-region beg end)
+    (insert (aon-format-erratum-empty id))
+    (aon-insert-errata-entry errataentry id)))
+
+(defun aon-errata-add (pos newtext &optional comment)
+  "Adds text in the current position and records the addition."
+  (interactive
+   (let ((xpos (point))
+         (string (read-string "Insert text: "))
+         (cmnt (read-string "Additional comment (optional): ")))
+     (list xpos string cmnt)))
+  (if (string= newtext "")
+      (error "No text to add!"))
+  (let* ((id (aon-get-new-errata-id "AD"))
+         (errataentry (format addeditemtext
+                              (aon-fix-markup newtext)
+                              comment)))
+    (insert (aon-format-erratum id newtext))
+    (aon-insert-errata-entry errataentry id)))
+
+(defun aon-errata-replace-all (beg end newtext oldtext &optional comment)
+  "Replaces all occurrences of the current region and records the replacements.
+Stretches all over one indexed section, so it might affect text before the
+selected region as well! It is NOT very intelligent about abstaining from
+replacing text in markup that should not be touch, so don't even think about
+replacing, say, 'class'."
+  (interactive
+   ;; save all positions and string as the user may change the selection!
+   (let* ((xbeg (region-beginning))
+          (xend (region-end))
+          ;;(xoldtext (car kill-ring-yank-pointer)) ; Boohoo Xemacs
+         (xoldtext (buffer-substring-no-properties xbeg xend))
+          (xnewtext (read-string (format
+                                  "Replace all occurrences of '%s' with: "
+                                  xoldtext)
+                                 xoldtext))
+          (cmnt (read-string "Additional comment (optional): ")))
+     (list xbeg xend xnewtext xoldtext cmnt)))
+  (if (string= newtext oldtext)
+      (error "The replacement text is the same as the original!"))
+  (let* ((id (aon-get-new-errata-id "RA"))
+         (errataentry (format replacedallitemtext
+                              (aon-fix-markup oldtext)
+                              (aon-fix-markup newtext)
+                              comment))
+         (saved-case-fold-search case-fold-search))
+    (save-excursion
+      (aon-goto-sect-start)
+      (forward-line 1) ; workaround to avoid matching this section start
+      (setq sectend (aon-get-sect-end))
+      (setq case-fold-search nil) ; No case folded false matches thank you
+      (while (re-search-forward (format "[^\"]\\\(%s\\\)[^\"]" oldtext) sectend t)
+        (replace-match (aon-format-erratum id newtext) t t nil 1))
+      (setq case-fold-search saved-case-fold-search)) ; Reset case folding
+    (aon-insert-errata-entry errataentry id)))
+
+;; SLOW!!! And doesn't need to be recursive. Rewrite.
+(defun aon-goto-sect-start ()
+  "Places the point at the start of the current indexed section."
+  (interactive) ; for test purposes
+  (if (member (aon-get-unchecked-sect-id) aon-nonindexed-sects)
+        (progn
+          (search-backward "<section" nil t)
+          (aon-goto-sect-start)) 
+    (search-backward "<section" nil t)))
+
+(defun aon-get-sect-end ()
+  "Returns the end position of this section."
+  (let ((nextopensect (save-excursion
+                        (search-forward "<section")))
+        (nextclosesect (save-excursion
+                         (search-forward "</section>"))))
+    (if (< nextclosesect nextopensect)
+        nextclosesect ; return the end of this section
+      (save-excursion
+        (goto-char nextopensect) ; place inside nested section
+        (goto-char (aon-get-sect-end)) ; goto end of that section
+        (aon-get-sect-end))))) ; continue search for this section's end
+
+(defun aon-get-new-errata-id (type)
+  "Returns a new unique errata item id."
+  (let ((id (format "ERRTAG-%s-%s" type (point))))
+    (save-excursion
+      (while (search-forward (format "%s-" id) nil t)
+        (setq id (format "%s1" id)))) ; append '1' and try again
+    id))
+
+(defun aon-fix-markup (string)
+  "Converts some markup in text from section to fit in an errata item."
+  ;; Quotes - assume it is single quotes, double must be handled manually
+  (while (string-match "<quote>" string)
+    (setq string (replace-match "&lsquot;" nil nil string)))
+  (while (string-match "</quote>" string)
+    (setq string (replace-match "&rsquot;" nil nil string)))
+  ;; TODO: Except for tags that shall become entities, maybe the rest can be
+  ;; handled by a general transformation?
+  ;; Link texts
+  (while (string-match "<link-text>" string)
+    (setq string (replace-match "<!--link-text-->" nil nil string)))
+  (while (string-match "</link-text>" string)
+    (setq string (replace-match "<!--/link-text-->" nil nil string)))
+  ;; idrefs and similar
+  ;; TODO: a complete idref does not need to be replaced!
+  (while (string-match "<\\(a .*?\\)>" string)
+    (setq string (replace-match (format "<!--%s-->" (match-string 1 string))
+                                nil nil string)))
+  (while (string-match "</a>" string)
+    (setq string (replace-match "<!--/a-->" nil nil string)))  string)
+
+;; (defun aon-refix-markup (string)
+;;   "Converts some 'fixed' markup in an errata item text to fit in a section.
+;; To be used when an erratum is undone and the replaced or deleted text from
+;; an errata item shall be re-inserted in the section."
+;;   (while (or (string-match "&lsquot;" string)
+;;              (string-match "&ldquot;" string))
+;;     (setq string (replace-match "<quote>" nil nil string)))
+;;   (while (or (string-match "&rsquot;" string)
+;;              (string-match "&rdquot;" string))
+;;     (setq string (replace-match "</quote>" nil nil string)))
+;;   string)
+
+(defun aon-locate-errata-item-start ()
+  "Return the starting position of the errata block the point is in.
+Currently, the point may not be within a comment start or end tag.
+Returns an error message if the point is not within an errata tag or if
+the errata structure is corrupt."
+  (save-excursion
+    (let ((prevcommentend (save-excursion (search-backward "-->" nil t)))
+          (prevcommentstart (save-excursion (search-backward "<!--" nil t)))
+          (nextcommentend (save-excursion (search-forward "-->" nil t))))
+      (unless (and prevcommentstart nextcommentend)
+        (error "Point is not within an errata item!"))
+      (goto-char prevcommentstart)
+      (cond
+       ;; if we are at the start of an opening tag, it is OK
+       ((looking-at "<!--ERRTAG-..-[0-9]+\\(-ITEM\\)?-->")
+        (point))
+       ;; if we are at the start of a content-free tag, we must check that
+       ;; we were not outside that tag when we started.
+       ((looking-at "<!--ERRTAG-..-[0-9]+/-->")
+        (if (> prevcommentend prevcommentstart)
+            (error "Point is not within an errata item!")
+          (point)))
+       ;; if we are at the start of a closing tag, we must check that we were
+       ;; not outside that tag when we started, and then find the opening tag
+       ((looking-at "<!--/\\(ERRTAG-..-[0-9]+\\(-ITEM\\)?\\)-->")
+        (if (> prevcommentend prevcommentstart)
+            (error "Point is not within an errata item!")
+          (search-backward (format "<!--%s-->" (match-string 1)))))
+       ;; if the comment was not an errata tag
+       (t (error "Point is not within an errata item!"))))))
+
+(defun aon-errata-jump ()
+  "If the point is within tags, jump to the corresponding erratum/errata item.
+In the case of a \"replace all\" erratum, jumps to the first location."
+  (interactive)
+  (let ((pos
+         (save-excursion
+           (goto-char (aon-locate-errata-item-start)) ; inefficient!
+           (cond ((looking-at "<!--\\(ERRTAG-..-[0-9]+\\)-ITEM-->")
+                  (goto-char (point-min))
+                  (re-search-forward (format "<!--%s/?-->" (match-string 1)))
+                  (match-beginning 0))
+                 ((looking-at "<!--\\(ERRTAG-..-[0-9]+\\)/?-->")
+                  (goto-char (point-min))
+                  (search-forward (format "<!--%s-ITEM-->" (match-string 1)))
+                  (match-beginning 0))
+                 (t (error "This code can not be reached!"))))))
+    (if pos (goto-char pos)
+      (error "This code can not be reached!")))) ; should have received error
+
+;; TODO: Make the illustration functions add entries in the illustrations list
+
+(defun aon-illustration-large (number height caption)
+  "Adds a large illustration at the insertion point.
+The illustrations list is not updated (TBD)."
+  (interactive
+   (let ((xnum (read-string "Illustration number: "))
+         (xheight (read-string "Pixel height: "))
+         (xcaption (read-string "Caption: ")))
+     (list xnum xheight xcaption)))
+  (let ((startpos (point))
+        (endpos (save-excursion
+                  (insert (format "<illustration class=\"float\">\n        <meta>\n         <creator>%s</creator>\n         <description>%s</description>\n        </meta>\n        <instance class=\"html\" src=\"ill%s.gif\" width=\"%s\" height=\"%s\"/>\n        <instance class=\"pdf\" src=\"ill%s.pdf\" width=\"%s\" height=\"%s\"/>\n       </illustration>"
+                                  aon-illustrator-name caption number
+                                  aon-large-illustration-width height number
+                                  aon-large-illustration-width height))
+                  (point))))
+    (aon-indent-block startpos endpos)))
+
+(defun aon-illustration-inline (filename height)
+  "Adds an inline illustration at the insertion point.
+The illustrations list is not updated (TBD)."
+  (interactive
+   (let ((xfilename (read-string "File name: "))
+         (xheight (read-string "Pixel height: ")))
+     (list xfilename xheight)))
+  (let ((startpos (point))
+        (endpos (save-excursion
+                  (insert (format "<illustration class=\"inline\">\n<meta>\n<creator>%s</creator>\n</meta>\n<instance class=\"html\" src=\"%s.gif\" width=\"%s\" height=\"%s\" mime-type=\"image/gif\" />\n</illustration>"
+                                  aon-illustrator-name filename
+                                  aon-large-illustration-width height))
+                  (point))))
+    (aon-indent-block startpos endpos)))
+
+(defun aon-indent-block (startpos endpos)
+  "Indents the text between startpos and endpos.
+The positions need not be start or end of lines. Leaves point at end of block."
+  (dotimes (i (count-lines startpos endpos))
+    (indent-according-to-mode)
+    (forward-line 1)))
+
+(global-set-key "\C-cr" 'aon-errata-replace)
+(global-set-key "\C-cd" 'aon-errata-delete)
+(global-set-key "\C-ca" 'aon-errata-add)
+(global-set-key "\C-cR" 'aon-errata-replace-all)
+(global-set-key "\C-cj" 'aon-errata-jump)
+;;(global-set-key "\C-cu" 'aon-errata-revert)
+
+(global-set-key "\C-cl" 'aon-illustration-large)
+(global-set-key "\C-ci" 'aon-illustration-inline)
+
+
+
+;; Errata examples
+;; <!--ERRTAG-RE-123--> <!--/ERRTAG-RE-123-->
+;; <!--ERRTAG-DE-456/-->
+;; Errata item
+;; <!--ERRTAG-RE-123-ITEM--> <!--/ERRTAG-RE-123-ITEM-->
+;; RE - replace
+;; DE - delete
+;; AD - add
+;; RA - replace all
+
+;;; aon.el ends here
\ No newline at end of file