gnus-gravatar.el (gnus-treat-from-gravatar, gnus-treat-mail-gravatar): Bind gnus...
[bpt/emacs.git] / lisp / htmlfontify.el
CommitLineData
acca02b0
SM
1;;; htmlfontify.el --- htmlise a buffer/source tree with optional hyperlinks
2
114f9c96 3;; Copyright (C) 2002, 2003, 2009, 2010 Free Software Foundation, Inc.
acca02b0
SM
4
5;; Emacs Lisp Archive Entry
6;; Package: htmlfontify
7;; Filename: htmlfontify.el
8;; Version: 0.21
9;; Keywords: html, hypermedia, markup, etags
10;; Author: Vivek Dasmohapatra <vivek@etla.org>
11;; Maintainer: Vivek Dasmohapatra <vivek@etla.org>
12;; Created: 2002-01-05
13;; Description: htmlise a buffer/source tree with optional hyperlinks
14;; URL: http://rtfm.etla.org/emacs/htmlfontify/
15;; Compatibility: Emacs23, Emacs22
16;; Incompatibility: Emacs19, Emacs20, Emacs21
17;; Last Updated: Thu 2009-11-19 01:31:21 +0000
aad4679e 18;; Version: 0.21
acca02b0
SM
19
20;; This file is part of GNU Emacs.
21
22;; GNU Emacs is free software: you can redistribute it and/or modify
23;; it under the terms of the GNU General Public License as published by
24;; the Free Software Foundation, either version 3 of the License, or
25;; (at your option) any later version.
26
27;; GNU Emacs is distributed in the hope that it will be useful,
28;; but WITHOUT ANY WARRANTY; without even the implied warranty of
29;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30;; GNU General Public License for more details.
31
32;; You should have received a copy of the GNU General Public License
33;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
34
35;;; Commentary:
36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
37;; I have made some changes to make it work for Emacs 22. A lot of
38;; small bug fixes related to the format of text and overlay
39;; properties (which might have changed since the beginning of 2003
40;; when this file was originally written).
41;;
42;; The function `hfy-face-at' currently carries much of the burden of
43;; my lacking understanding of the formats mentioned above and should
44;; need some knowledgeable help.
45;;
46;; Another thing that maybe could be fixed is that overlay background
47;; colors which are now only seen where there is text (in the XHTML
48;; output). A bit of CSS tweaking is necessary there.
49;;
50;; The face 'default has a value :background "SystemWindow" for the
51;; background color. There is no explicit notion that this should be
52;; considered transparent, but I have assumed that it could be handled
53;; like if it was here. (I am unsure that background and foreground
54;; priorities are handled ok, but it looks ok in my tests now.)
55;;
56;; 2007-12-27 Lennart Borgman
57;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
58
59;; Here's some elisp code to html-pretty-print an Emacs buffer, preserving
60;; the Emacs syntax/whatever highlighting. It also knows how to drive etags
61;; (exuberant-ctags or Emacs etags) and hyperlink the code according
62;; to its (etags') output.
63
64;; NOTE: Currently the hyperlinking code only knows how to drive GNU find
65;; and the exuberant and GNU variants of etags : I do not know of any other
66;; etags variants, but mechanisms have been provided to allow htmlfontify
67;; to be taught how to drive them. As long as your version of find has
68;; the -path test and is reasonably sane, you should be fine.
69
70;; A sample of the htmlfontified / hyperlinked output of this module can be
71;; found at http://rtfm.etla.org/sql/dbishell/src/ - it's not perfect, but
72;; it's a hell of a lot faster and more thorough than I could hope to be
73;; doing this by hand.
74
75;; some user / horrified onlooker comments:
76;; What? No! There's something deeply wrong here... (R. Shufflebotham)
77;; You're a freak. (D. Silverstone)
78;; Aren't we giving you enough to do? (J. Busuttil)
79;; You're almost as messed up as Lexx is! (N. Graves-Morris)
80
81;;; History:
82;; Changes: moved to changelog (CHANGES) file.
83
84;;; Code:
85(eval-when-compile (require 'cl))
86(require 'faces)
87;; (`facep' `face-attr-construct' `x-color-values' `color-values' `face-name')
88(require 'custom)
89;; (`defgroup' `defcustom')
90(require 'font-lock)
91;; (`font-lock-fontify-region')
92(require 'cus-edit)
93
acca02b0
SM
94(defconst htmlfontify-version 0.21)
95
96(defconst hfy-meta-tags
97 (format "<meta name=\"generator\" content=\"emacs %s; htmlfontify %0.2f\" />"
98 emacs-version htmlfontify-version)
99 "The generator meta tag for this version of htmlfontify.")
100
101(defconst htmlfontify-manual "Htmlfontify Manual"
30afcdff
JB
102 "Copy and convert buffers and files to HTML, adding hyperlinks between files
103\(driven by etags) if requested.
acca02b0
SM
104\nInteractive functions:
105 `htmlfontify-buffer'
106 `htmlfontify-run-etags'
107 `htmlfontify-copy-and-link-dir'
108 `htmlfontify-load-rgb-file'
109 `htmlfontify-unload-rgb-file'\n
110In order to:\n
111fontify a file you have open: M-x htmlfontify-buffer
112prepare the etags map for a directory: M-x htmlfontify-run-etags
113copy a directory, fontifying as you go: M-x htmlfontify-copy-and-link-dir\n
114The following might be useful when running non-windowed or in batch mode:
30afcdff 115\(note that they shouldn't be necessary - we have a built in map)\n
acca02b0
SM
116load an X11 style rgb.txt file: M-x htmlfontify-load-rgb-file
117unload the current rgb.txt file: M-x htmlfontify-unload-rgb-file\n
118And here's a programmatic example:\n
30afcdff
JB
119\(defun rtfm-build-page-header (file style)
120 (format \"#define TEMPLATE red+black.html
acca02b0
SM
121#define DEBUG 1
122#include <build/menu-dirlist|>\\n
123html-css-url := /css/red+black.css
30afcdff 124title := rtfm.etla.org ( %s / src/%s )
acca02b0
SM
125bodytag :=
126head <=STYLESHEET;\\n
127%s
128STYLESHEET
129main-title := rtfm / %s / src/%s\\n
30afcdff
JB
130main-content <=MAIN_CONTENT;\\n\" rtfm-section file style rtfm-section file))
131
132\(defun rtfm-build-page-footer (file) \"\\nMAIN_CONTENT\\n\")
133
134\(defun rtfm-build-source-docs (section srcdir destdir)
135 (interactive
136 \"s section[eg- emacs / p4-blame]:\\nD source-dir: \\nD output-dir: \")
137 (require 'htmlfontify)
138 (hfy-load-tags-cache srcdir)
139 (let ((hfy-page-header 'rtfm-build-page-header)
140 (hfy-page-footer 'rtfm-build-page-footer)
141 (rtfm-section section)
142 (hfy-index-file \"index\"))
143 (htmlfontify-run-etags srcdir)
144 (htmlfontify-copy-and-link-dir srcdir destdir \".src\" \".html\")))")
acca02b0
SM
145
146(defgroup htmlfontify nil
b660eb70 147 "Convert buffers and files to HTML."
acca02b0 148 :group 'applications
b660eb70 149 :link '(variable-link htmlfontify-manual)
acca02b0
SM
150 :prefix "hfy-")
151
152(defcustom hfy-page-header 'hfy-default-header
30afcdff
JB
153 "Function called with two arguments (the filename relative to the top
154level source directory being etag'd and fontified), and a string containing
acca02b0
SM
155the <style>...</style> text to embed in the document- the string returned will
156be used as the header for the htmlfontified version of the source file.\n
30afcdff 157See also `hfy-page-footer'."
acca02b0 158 :group 'htmlfontify
72fe6b25
SM
159 ;; FIXME: Why place such a :tag everywhere? Isn't it imposing your
160 ;; own Custom preference on your users? --Stef
acca02b0
SM
161 :tag "page-header"
162 :type '(function))
163
164(defcustom hfy-split-index nil
72fe6b25 165 "Whether or not to split the index `hfy-index-file' alphabetically
acca02b0
SM
166on the first letter of each tag. Useful when the index would otherwise
167be large and take a long time to render or be difficult to navigate."
168 :group 'htmlfontify
169 :tag "split-index"
170 :type '(boolean))
171
172(defcustom hfy-page-footer 'hfy-default-footer
72fe6b25 173 "As `hfy-page-header', but generates the output footer
30afcdff 174\(and takes only one argument, the filename)."
acca02b0
SM
175 :group 'htmlfontify
176 :tag "page-footer"
177 :type '(function))
178
179(defcustom hfy-extn ".html"
72fe6b25 180 "File extension used for output files."
acca02b0
SM
181 :group 'htmlfontify
182 :tag "extension"
183 :type '(string))
184
185(defcustom hfy-src-doc-link-style "text-decoration: underline;"
30afcdff 186 "String to add to the '<style> a' variant of an htmlfontify CSS class."
acca02b0
SM
187 :group 'htmlfontify
188 :tag "src-doc-link-style"
189 :type '(string))
190
191(defcustom hfy-src-doc-link-unstyle " text-decoration: none;"
30afcdff 192 "Regex to remove from the <style> a variant of an htmlfontify CSS class."
acca02b0
SM
193 :group 'htmlfontify
194 :tag "src-doc-link-unstyle"
195 :type '(string))
196
197(defcustom hfy-link-extn nil
30afcdff
JB
198 "File extension used for href links.
199Useful where the htmlfontify output files are going to be processed
200again, with a resulting change in file extension. If nil, then any
201code using this should fall back to `hfy-extn'."
acca02b0
SM
202 :group 'htmlfontify
203 :tag "link-extension"
204 :type '(choice string (const nil)))
205
206(defcustom hfy-link-style-fun 'hfy-link-style-string
72fe6b25 207 "Set this to a function, which will be called with one argument
30afcdff 208\(a \"{ foo: bar; ...}\" CSS style-string) - it should return a copy of
acca02b0
SM
209its argument, altered so as to make any changes you want made for text which
210is a hyperlink, in addition to being in the class to which that style would
211normally be applied."
212 :group 'htmlfontify
213 :tag "link-style-function"
214 :type '(function))
215
30afcdff
JB
216(defcustom hfy-index-file "hfy-index"
217 "Name (sans extension) of the tag definition index file produced during
acca02b0
SM
218fontification-and-hyperlinking."
219 :group 'htmlfontify
220 :tag "index-file"
221 :type '(string))
222
30afcdff
JB
223(defcustom hfy-instance-file "hfy-instance"
224 "Name (sans extension) of the tag usage index file produced during
acca02b0
SM
225fontification-and-hyperlinking."
226 :group 'htmlfontify
227 :tag "instance-file"
228 :type '(string))
229
230(defcustom hfy-html-quote-regex "\\(<\\|\"\\|&\\|>\\)"
30afcdff
JB
231 "Regex to match (with a single back-reference per match) strings in HTML
232which should be quoted with `hfy-html-quote' (and `hfy-html-quote-map')
acca02b0
SM
233to make them safe."
234 :group 'htmlfontify
235 :tag "html-quote-regex"
236 :type '(regexp))
237
72fe6b25
SM
238(define-obsolete-variable-alias 'hfy-init-kludge-hooks 'hfy-init-kludge-hook
239 "23.2")
240(defcustom hfy-init-kludge-hook '(hfy-kludge-cperl-mode)
30afcdff
JB
241 "List of functions to call when starting `htmlfontify-buffer' to do any
242kludging necessary to get highlighting modes to behave as you want, even
acca02b0
SM
243when not running under a window system."
244 :group 'htmlfontify
245 :tag "init-kludge-hooks"
246 :type '(hook))
247
248(defcustom hfy-post-html-hooks nil
30afcdff
JB
249 "List of functions to call after creating and filling the HTML buffer.
250These functions will be called with the html buffer as the current buffer."
acca02b0
SM
251 :group 'htmlfontify
252 :tag "post-html-hooks"
253 :options '(set-auto-mode)
254 :type '(hook))
255
256(defcustom hfy-default-face-def nil
30afcdff
JB
257 "Fallback `defface' specification for the face 'default, used when
258`hfy-display-class' has been set (the normal htmlfontify way of extracting
259potentially non-current face information doesn't necessarily work for
260'default).\n
261Example: I customize this to:\n
262\((t :background \"black\" :foreground \"white\" :family \"misc-fixed\"))"
acca02b0
SM
263 :group 'htmlfontify
264 :tag "default-face-definition"
265 :type '(alist))
266
267(defcustom hfy-etag-regex (concat ".*"
268 "\x7f" "\\([^\x01\n]+\\)"
269 "\x01" "\\([0-9]+\\)"
270 "," "\\([0-9]+\\)$"
271 "\\|" ".*\x7f[0-9]+,[0-9]+$")
72fe6b25 272 "Regex used to parse an etags entry: must have 3 subexps, corresponding,
acca02b0
SM
273in order, to:\n
274 1 - The tag
275 2 - The line
30afcdff 276 3 - The char (point) at which the tag occurs."
acca02b0
SM
277 :group 'htmlfontify
278 :tag "etag-regex"
279 :type '(regexp))
280
281(defcustom hfy-html-quote-map '(("\"" "&quot;")
282 ("<" "&lt;" )
283 ("&" "&amp;" )
284 (">" "&gt;" ))
30afcdff 285 "Alist of char -> entity mappings used to make the text HTML-safe."
acca02b0
SM
286 :group 'htmlfontify
287 :tag "html-quote-map"
288 :type '(alist :key-type (string)))
289(eval-and-compile
290 (defconst hfy-e2x-etags-cmd "for src in `find . -type f`;
291do
292 ETAGS=%s;
293 case ${src} in
294 *.ad[absm]|*.[CFHMSacfhlmpsty]|*.def|*.in[cs]|*.s[as]|*.src|*.cc|\\
295 *.hh|*.[chy]++|*.[ch]pp|*.[chy]xx|*.pdb|*.[ch]s|*.[Cc][Oo][Bb]|\\
296 *.[eh]rl|*.f90|*.for|*.java|*.[cem]l|*.clisp|*.lisp|*.[Ll][Ss][Pp]|\\
297 [Mm]akefile*|*.pas|*.[Pp][LlMm]|*.psw|*.lm|*.pc|*.prolog|*.oak|\\
298 *.p[sy]|*.sch|*.scheme|*.[Ss][Cc][Mm]|*.[Ss][Mm]|*.bib|*.cl[os]|\\
299 *.ltx|*.sty|*.TeX|*.tex|*.texi|*.texinfo|*.txi|*.x[bp]m|*.yy|\\
300 *.[Ss][Qq][Ll])
301 ${ETAGS} -o- ${src};
302 ;;
303 *)
304 FTYPE=`file ${src}`;
305 case ${FTYPE} in
306 *script*text*)
307 ${ETAGS} -o- ${src};
308 ;;
309 *text*)
310 SHEBANG=`head -n1 ${src} | grep '#!' -c`;
311 if [ ${SHEBANG} -eq 1 ];
312 then
313 ${ETAGS} -o- ${src};
314 fi;
315 ;;
316 esac;
317 ;;
318 esac;
319done;")
320
321 (defconst hfy-etags-cmd-alist-default
322 `(("emacs etags" . ,hfy-e2x-etags-cmd)
323 ("exuberant ctags" . "%s -R -f -" )))
324
325 (defcustom hfy-etags-cmd-alist
326 hfy-etags-cmd-alist-default
72fe6b25 327 "Alist of possible shell commands that will generate etags output that
30afcdff 328`htmlfontify' can use. '%s' will be replaced by `hfy-etags-bin'."
acca02b0
SM
329 :group 'htmlfontify
330 :tag "etags-cmd-alist"
331 :type '(alist :key-type (string) :value-type (string)) ))
332
333(defcustom hfy-etags-bin "etags"
30afcdff 334 "Location of etags binary (we begin by assuming it's in your path).\n
acca02b0
SM
335Note that if etags is not in your path, you will need to alter the shell
336commands in `hfy-etags-cmd-alist'."
337 :group 'htmlfontify
338 :tag "etags-bin"
339 :type '(file))
340
341(defcustom hfy-shell-file-name "/bin/sh"
72fe6b25 342 "Shell (bourne or compatible) to invoke for complex shell operations."
acca02b0
SM
343 :group 'htmlfontify
344 :tag "shell-file-name"
345 :type '(file))
346
30afcdff 347(defcustom hfy-ignored-properties '(read-only
2ea1c4aa
SM
348 intangible
349 modification-hooks
350 insert-in-front-hooks
351 insert-behind-hooks
352 point-entered
353 point-left)
30afcdff 354 "Properties to omit when copying a fontified buffer for HTML transformation."
2ea1c4aa
SM
355 :group 'htmlfontify
356 :tag "ignored-properties"
357 :type '(repeat symbol))
358
acca02b0 359(defun hfy-which-etags ()
30afcdff 360 "Return a string indicating which flavour of etags we are using."
acca02b0
SM
361 (let ((v (shell-command-to-string (concat hfy-etags-bin " --version"))))
362 (cond ((string-match "exube" v) "exuberant ctags")
363 ((string-match "GNU E" v) "emacs etags" )) ))
364
365(defcustom hfy-etags-cmd
366 (eval-and-compile (cdr (assoc (hfy-which-etags) hfy-etags-cmd-alist)))
72fe6b25 367 "The etags equivalent command to run in a source directory to generate a tags
acca02b0
SM
368file for the whole source tree from there on down. The command should emit
369the etags output on stdout.\n
30afcdff
JB
370Two canned commands are provided - they drive Emacs' etags and
371exuberant-ctags' etags respectively."
acca02b0
SM
372 :group 'htmlfontify
373 :tag "etags-command"
374 :type (eval-and-compile
375 (let ((clist (list '(string))))
72fe6b25
SM
376 (dolist (C hfy-etags-cmd-alist)
377 (push (list 'const :tag (car C) (cdr C)) clist))
acca02b0
SM
378 (cons 'choice clist)) ))
379
380(defcustom hfy-istext-command "file %s | sed -e 's@^[^:]*:[ \t]*@@'"
72fe6b25 381 "Command to run with the name of a file, to see whether it is a text file
30afcdff
JB
382or not. The command should emit a string containing the word 'text' if
383the file is a text file, and a string not containing 'text' otherwise."
acca02b0
SM
384 :group 'htmlfontify
385 :tag "istext-command"
386 :type '(string))
387
388(defcustom hfy-find-cmd
389 "find . -type f \\! -name \\*~ \\! -name \\*.flc \\! -path \\*/CVS/\\*"
72fe6b25 390 "Find command used to harvest a list of files to attempt to fontify."
acca02b0
SM
391 :group 'htmlfontify
392 :tag "find-command"
393 :type '(string))
394
395(defcustom hfy-display-class nil
72fe6b25 396 "Display class to use to determine which display class to use when
30afcdff 397calculating a face's attributes. This is useful when, for example, you
acca02b0
SM
398are running Emacs on a tty or in batch mode, and want htmlfontify to have
399access to the face spec you would use if you were connected to an X display.\n
400Some valid class specification elements are:\n
30afcdff
JB
401 '(class color)
402 '(class grayscale)
403 '(background dark)
404 '(background light)
405 '(type x-toolkit)
406 '(type tty)
407 '(type motif)
408 '(type lucid)
acca02b0
SM
409Multiple values for a tag may be combined, to indicate that any one or more
410of these values in the specification key constitutes a match, eg:\n
30afcdff
JB
411'((class color grayscale) (type tty)) would match any of:\n
412 '((class color))
413 '((class grayscale))
414 '((class color grayscale))
415 '((class color foo))
416 '((type tty))
417 '((type tty) (class color))\n
acca02b0
SM
418and so on."
419 :type '(alist :key-type (symbol) :value-type (symbol))
420 :group 'htmlfontify
421 :tag "display-class"
422 :options '((type (choice (const :tag "X11" x-toolkit)
423 (const :tag "Terminal" tty )
424 (const :tag "Lucid Toolkit" lucid )
425 (const :tag "Motif Toolkit" motif )))
426
427 (class (choice (const :tag "Colour" color )
428 (const :tag "Greyscale" grayscale)))
429
430 (background (choice (const :tag "Dark" dark )
431 (const :tag "Bright" light ))) ))
432
433(defcustom hfy-optimisations (list 'keep-overlays)
30afcdff 434 "Optimizations to turn on: So far, the following have been implemented:\n
acca02b0
SM
435 merge-adjacent-tags: If two (or more) span tags are adjacent, identical and
436 separated by nothing more than whitespace, they will
437 be merged into one span.
438 zap-comment-links : Suppress hyperlinking of tags found in comments.
439 zap-string-links : Suppress hyperlinking of tags found in strings.
440 div-wrapper : Add <div class=\"default\"> </div> tags around the
441 output.
30afcdff
JB
442 keep-overlays : More of a bell (or possibly whistle) than an
443 optimization - If on, preserve overlay highlighting
444 (cf ediff or goo-font-lock) as well as basic faces.\n
acca02b0
SM
445 And the following are planned but not yet available:\n
446 kill-context-leak : Suppress hyperlinking between files highlighted by
447 different modes.\n
30afcdff 448Note: like compiler optimizations, these optimize the _output_ of the code,
acca02b0 449not the processing of the source itself, and are therefore likely to slow
30afcdff 450htmlfontify down, at least a little. Except for skip-refontification,
acca02b0
SM
451which can never slow you down, but may result in incomplete fontification."
452 :type '(set (const :tag "merge-adjacent-tags" merge-adjacent-tags )
453 (const :tag "zap-comment-links" zap-comment-links )
454 (const :tag "zap-string-links" zap-string-links )
455 (const :tag "skip-refontification" skip-refontification)
456 (const :tag "kill-context-leak" kill-context-leak )
457 (const :tag "div-wrapper" div-wrapper )
458 (const :tag "keep-overlays" keep-overlays ))
459 :group 'htmlfontify
30afcdff 460 :tag "optimizations")
acca02b0 461
30afcdff 462(defvar hfy-tags-cache nil
acca02b0 463 "Alist of the form:\n
30afcdff
JB
464\((\"/src/dir/0\" . tag-hash0) (\"/src/dir/1\" tag-hash1) ...)\n
465Each tag hash entry then contains entries of the form:\n
acca02b0 466\"tag_string\" => ((\"file/name.ext\" line char) ... )\n
30afcdff
JB
467ie an alist mapping (relative) file paths to line and character offsets.\n
468See also `hfy-load-tags-cache'.")
acca02b0 469
30afcdff
JB
470(defvar hfy-tags-sortl nil
471 "Alist of the form ((\"/src/dir\" . (tag0 tag1 tag2)) ... )\n
472where the tags are stored in descending order of length.\n
473See also `hfy-load-tags-cache'.")
acca02b0 474
30afcdff
JB
475(defvar hfy-tags-rmap nil
476 "Alist of the form ((\"/src/dir\" . tag-rmap-hash))\n
477where tag-rmap-hash has entries of the form:
acca02b0
SM
478\"tag_string\" => ( \"file/name.ext\" line char )
479Unlike `hfy-tags-cache' these are the locations of occurrences of
480tagged items, not the locations of their definitions.")
481
482(defvar hfy-style-assoc 'please-ignore-this-line
483 "An assoc representing/describing an Emacs face.
30afcdff
JB
484Properties may be repeated, in which case later properties should be
485treated as if they were inherited from a 'parent' font.
acca02b0
SM
486\(For some properties, only the first encountered value is of any importance,
487for others the values might be cumulative, and for others they might be
30afcdff 488cumulative in a complex way.)\n
acca02b0 489Some examples:\n
30afcdff
JB
490\(hfy-face-to-style 'default) =>
491 ((\"background\" . \"rgb(0, 0, 0)\")
492 (\"color\" . \"rgb(255, 255, 255)\")
493 (\"font-style\" . \"normal\")
494 (\"font-weight\" . \"500\")
495 (\"font-stretch\" . \"normal\")
496 (\"font-family\" . \"misc-fixed\")
497 (\"font-size\" . \"13pt\")
498 (\"text-decoration\" . \"none\"))\n
499\(hfy-face-to-style 'Info-title-3-face) =>
500 ((\"font-weight\" . \"700\")
501 (\"font-family\" . \"helv\")
502 (\"font-size\" . \"120%\")
503 (\"text-decoration\" . \"none\"))\n")
acca02b0
SM
504
505(defvar hfy-sheet-assoc 'please-ignore-this-line
30afcdff
JB
506 "An assoc with elements of the form (face-name style-name . style-string):\n
507'((default \"default\" . \"{background: black; color: white}\")
508 (font-lock-string-face \"string\" . \"{color: rgb(64,224,208)}\"))" )
acca02b0
SM
509
510(defvar hfy-facemap-assoc 'please-ignore-this-line
30afcdff 511 "An assoc of (point . FACE-SYMBOL) or (point . DEFFACE-LIST)
acca02b0 512and (point . 'end) elements, in descending order of point value
30afcdff
JB
513\(ie from the file's end to its beginning).\n
514The map is in reverse order because inserting a <style> tag (or any other
515string) at `point' invalidates the map for all entries with a greater value of
516point. By traversing the map from greatest to least point, we still invalidate
517the map as we go, but only those points we have already dealt with (and
518therefore no longer care about) will be invalid at any time.\n
519'((64820 . end)
520 (64744 . font-lock-comment-face)
521 (64736 . end)
522 (64722 . font-lock-string-face)
523 (64630 . end)
524 (64623 . font-lock-string-face)
525 (64449 . end)
526 (64446 . font-lock-keyword-face)
527 (64406 . end)
528 (64395 . font-lock-constant-face)
529 (64393 . end)
530 (64386 . font-lock-keyword-face)
531 (64379 . end)
acca02b0 532 ;; big similar section elided. You get the idea.
30afcdff
JB
533 (4285 . font-lock-constant-face)
534 (4285 . end)
535 (4221 . font-lock-comment-face)
536 (4221 . end)
537 (4197 . font-lock-constant-face)
538 (4197 . end)
539 (1 . font-lock-comment-face))")
acca02b0
SM
540
541(defvar hfy-tmpfont-stack nil
542 "An alist of derived fonts resulting from overlays.")
543
544(defconst hfy-hex-regex "[0-9A-Fa-f]")
545
546(defconst hfy-triplet-regex
547 (concat
548 "\\(" hfy-hex-regex hfy-hex-regex "\\)"
549 "\\(" hfy-hex-regex hfy-hex-regex "\\)"
550 "\\(" hfy-hex-regex hfy-hex-regex "\\)"))
551
552(defun hfy-interq (set-a set-b)
30afcdff 553 "Return the intersection (using `eq') of two lists SET-A and SET-B."
acca02b0
SM
554 (let ((sa set-a) (interq nil) (elt nil))
555 (while sa
556 (setq elt (car sa)
557 sa (cdr sa))
558 (if (memq elt set-b) (setq interq (cons elt interq)))) interq))
559
560(defun hfy-colour-vals (colour)
30afcdff
JB
561 "Where COLOUR is a color name or #XXXXXX style triplet, return a
562list of three (16 bit) rgb values for said color.\n
acca02b0
SM
563If a window system is unavailable, calls `hfy-fallback-colour-values'."
564 (if (string-match hfy-triplet-regex colour)
565 (mapcar
72fe6b25
SM
566 (lambda (x) (* (string-to-number (match-string x colour) 16) 257))
567 '(1 2 3))
acca02b0
SM
568 ;;(message ">> %s" colour)
569 (if window-system
570 (if (fboundp 'color-values)
571 (color-values colour)
572 ;;(message "[%S]" window-system)
573 (x-color-values colour))
574 ;; blarg - tty colours are no good - go fetch some X colours:
575 (hfy-fallback-colour-values colour))))
576
577(defvar hfy-cperl-mode-kludged-p nil)
578
579(defun hfy-kludge-cperl-mode ()
580 "CPerl mode does its damndest not to do some of its fontification when not
581in a windowing system - try to trick it..."
582 (if (not hfy-cperl-mode-kludged-p)
583 (progn (if (not window-system)
584 (let ((window-system 'htmlfontify))
585 (eval-and-compile (require 'cperl-mode))
586 (setq cperl-syntaxify-by-font-lock t)))
587 (setq hfy-cperl-mode-kludged-p t))) )
588
589(defun hfy-opt (symbol) "Is option SYMBOL set." (memq symbol hfy-optimisations))
590
591(defun hfy-default-header (file style)
592 "Default value for `hfy-page-header'.
593FILE is the name of the file.
594STYLE is the inline CSS stylesheet (or tag referring to an external sheet)."
595;; (format "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">
596;; <html>\n <head>\n <title>%s</title>\n %s\n </head>\n <body>\n" file style))
597 (format "<?xml version=\"1.0\" encoding=\"utf-8\"?>
598<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\"
599\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">
600<html xmlns=\"http://www.w3.org/1999/xhtml\">
601 <head>
602 <title>%s</title>
603%s
604 <script type=\"text/javascript\"><!--
605 // this function is needed to work around
606 // a bug in IE related to element attributes
607 function hasClass(obj)
608 {
609 var result = false;
610 if (obj.getAttributeNode(\"class\") != null)
611 {
612 result = obj.getAttributeNode(\"class\").value;
613 }
614 return result;
615 }
616
617 function stripe(id)
618 {
619 // the flag we'll use to keep track of
620 // whether the current row is odd or even
621 var even = false;
622
30afcdff 623 // if arguments are provided to specify the colors
acca02b0
SM
624 // of the even & odd rows, then use the them;
625 // otherwise use the following defaults:
626 var evenColor = arguments[1] ? arguments[1] : \"#fff\";
627 var oddColor = arguments[2] ? arguments[2] : \"#ddd\";
628
629 // obtain a reference to the desired table
630 // if no such table exists, abort
631 var table = document.getElementById(id);
632 if (! table) { return; }
633
634 // by definition, tables can have more than one tbody
635 // element, so we'll have to get the list of child
636 // &lt;tbody&gt;s
637 var tbodies = table.getElementsByTagName(\"tbody\");
638
639 // and iterate through them...
640 for (var h = 0; h < tbodies.length; h++)
641 {
642 // find all the &lt;tr&gt; elements...
643 var trs = tbodies[h].getElementsByTagName(\"tr\");
644
645 // ... and iterate through them
646 for (var i = 0; i < trs.length; i++)
647 {
648 // avoid rows that have a class attribute
649 // or backgroundColor style
650 if (! hasClass(trs[i]) &&
651 ! trs[i].style.backgroundColor)
652 {
653 // get all the cells in this row...
654 var tds = trs[i].getElementsByTagName(\"td\");
655
656 // and iterate through them...
657 for (var j = 0; j < tds.length; j++)
658 {
659 var mytd = tds[j];
660
661 // avoid cells that have a class attribute
662 // or backgroundColor style
663 if (! hasClass(mytd) &&
664 ! mytd.style.backgroundColor)
665 {
666 mytd.style.backgroundColor =
667 even ? evenColor : oddColor;
668 }
669 }
670 }
671 // flip from odd to even, or vice-versa
672 even = ! even;
673 }
674 }
675 }
85e0a536
SM
676
677 function toggle_invis( name )
678 {
679 var filter =
680 { acceptNode:
681 function( node )
682 { var classname = node.id;
683 if( classname )
684 { var classbase = classname.substr( 0, name.length );
685 if( classbase == name ) { return NodeFilter.FILTER_ACCEPT; } }
686 return NodeFilter.FILTER_SKIP; } };
687 var walker = document.createTreeWalker( document.body ,
688 NodeFilter.SHOW_ELEMENT ,
689 filter ,
690 false );
691 while( walker.nextNode() )
692 {
693 var e = walker.currentNode;
694 if( e.style.display == \"none\" ) { e.style.display = \"inline\"; }
695 else { e.style.display = \"none\"; }
696 }
697 }
acca02b0
SM
698--> </script>
699 </head>
700 <body onload=\"stripe('index'); return true;\">\n"
701 file style))
702
703(defun hfy-default-footer (file)
704 "Default value for `hfy-page-footer'.
705FILE is the name of the file being rendered, in case it is needed."
706 "\n </body>\n</html>\n")
707
708(defun hfy-link-style-string (style-string)
30afcdff 709 "Replace the end of a CSS style declaration STYLE-STRING with the contents
acca02b0
SM
710of the variable `hfy-src-doc-link-style', removing text matching the regex
711`hfy-src-doc-link-unstyle' first, if necessary."
712 ;;(message "hfy-colour-vals");;DBUG
713 (if (string-match hfy-src-doc-link-unstyle style-string)
714 (setq style-string (replace-match "" 'fixed-case 'literal style-string)))
715 (if (and (not (string-match hfy-src-doc-link-style style-string))
716 (string-match "} *$" style-string))
717 (concat (replace-match hfy-src-doc-link-style
718 'fixed-case
719 'literal
720 style-string) " }") style-string))
721
722;; utility functions - cast emacs style specification values into their
723;; css2 equivalents:
724(defun hfy-triplet (colour)
30afcdff 725 "Takes a COLOUR name (string) and return a CSS rgb(R, G, B) triplet string.
acca02b0 726Uses the definition of \"white\" to map the numbers to the 0-255 range, so
30afcdff
JB
727if you've redefined white, (esp. if you've redefined it to have a triplet
728member lower than that of the color you are processing) strange things
729may happen."
acca02b0
SM
730 ;;(message "hfy-colour-vals");;DBUG
731 (let ((white (mapcar (lambda (I) (float (1+ I))) (hfy-colour-vals "white")))
732 (rgb16 (mapcar (lambda (I) (float (1+ I))) (hfy-colour-vals colour))))
733 (if rgb16
734 ;;(apply 'format "rgb(%d, %d, %d)"
735 ;; Use #rrggbb instead, it is smaller
736 (apply 'format "#%02x%02x%02x"
737 (mapcar (lambda (X)
738 (* (/ (nth X rgb16)
72fe6b25
SM
739 (nth X white)) 255))
740 '(0 1 2))))))
acca02b0
SM
741
742(defun hfy-family (family) (list (cons "font-family" family)))
743(defun hfy-bgcol (colour) (list (cons "background" (hfy-triplet colour))))
744(defun hfy-colour (colour) (list (cons "color" (hfy-triplet colour))))
745(defun hfy-width (width) (list (cons "font-stretch" (symbol-name width))))
746
747(defcustom hfy-font-zoom 1.05
748 "Font scaling from Emacs to HTML."
749 :type 'float
750 :group 'htmlfontify)
751
30afcdff 752(defun hfy-size (height)
acca02b0
SM
753 "Derive a CSS font-size specifier from an Emacs font :height attribute HEIGHT.
754Does not cope with the case where height is a function to be applied to
755the height of the underlying font."
756 (list
757 (cond
758 ;;(t (cons "font-size" ": 1em"))
759 ((floatp height)
760 (cons "font-size" (format "%d%%" (* (* hfy-font-zoom height) 100))))
761 ((integerp height)
762 (cons "font-size" (format "%dpt" (/ (* hfy-font-zoom height) 10 )))) )) )
763
30afcdff
JB
764(defun hfy-slant (slant)
765 "Derive a font-style CSS specifier from the Emacs :slant attribute SLANT:
acca02b0
SM
766CSS does not define the reverse-* styles, so just maps those to the
767regular specifiers."
72fe6b25
SM
768 (list (cons "font-style"
769 (or (cdr (assq slant '((italic . "italic")
770 (reverse-italic . "italic" )
771 (oblique . "oblique")
772 (reverse-oblique . "oblique"))))
773 "normal"))))
acca02b0
SM
774
775(defun hfy-weight (weight)
30afcdff 776 "Derive a font-weight CSS specifier from an Emacs weight spec symbol WEIGHT."
72fe6b25
SM
777 (list (cons "font-weight" (cdr (assq weight '((ultra-bold . "900")
778 (extra-bold . "800")
779 (bold . "700")
780 (semi-bold . "600")
781 (normal . "500")
782 (semi-light . "400")
783 (light . "300")
784 (extra-light . "200")
785 (ultra-light . "100")))))))
30afcdff 786
acca02b0
SM
787(defun hfy-box-to-border-assoc (spec)
788 (if spec
789 (let ((tag (car spec))
790 (val (cadr spec)))
72fe6b25
SM
791 (cons (case tag
792 (:color (cons "colour" val))
793 (:width (cons "width" val))
794 (:style (cons "style" val)))
795 (hfy-box-to-border-assoc (cddr spec))))))
acca02b0
SM
796
797(defun hfy-box-to-style (spec)
798 (let* ((css (hfy-box-to-border-assoc spec))
799 (col (cdr (assoc "colour" css)))
800 (s (cdr (assoc "style" css))))
801 (list
802 (if col (cons "border-color" (cdr (assoc "colour" css))))
803 (cons "border-width" (format "%dpx" (or (cdr (assoc "width" css)) 1)))
72fe6b25
SM
804 (cons "border-style" (case s
805 (released-button "outset")
806 (pressed-button "inset" )
807 (t "solid" ))))))
acca02b0
SM
808
809(defun hfy-box (box)
810 "Derive CSS border-* attributes from the Emacs :box attribute BOX."
811 (if box
812 (cond
813 ((integerp box) (list (cons "border-width" (format "%dpx" box))))
814 ((stringp box) (list (cons "border" (format "solid %s 1px" box))))
815 ((listp box) (hfy-box-to-style box) ))) )
816
817(defun hfy-decor (tag val)
818 "Derive CSS text-decoration specifiers from various Emacs font attributes.
819TAG is an Emacs font attribute key (eg :underline).
820VAL is ignored."
821 (list
e3353a78 822 ;; FIXME: Why not '("text-decoration" . "underline")? --Stef
72fe6b25
SM
823 (case tag
824 (:underline (cons "text-decoration" "underline" ))
825 (:overline (cons "text-decoration" "overline" ))
826 (:strike-through (cons "text-decoration" "line-through")))))
acca02b0
SM
827
828(defun hfy-invisible (&optional val)
829 "This text should be invisible.
830Do something in CSS to make that happen.
831VAL is ignored here."
832 '(("display" . "none")))
833
834(defun hfy-combined-face-spec (face)
835 "Return a `defface' style alist of possible specifications for FACE.
30afcdff 836Entries resulting from customization (`custom-set-faces') will take
acca02b0
SM
837precedence."
838 (let ((spec nil))
839 (setq spec (append (or (get face 'saved-face) (list))
840 (or (get face 'face-defface-spec) (list))))
841 (if (and hfy-display-class hfy-default-face-def (eq face 'default))
842 (setq spec (append hfy-default-face-def spec))) spec))
843
844(defun hfy-face-attr-for-class (face &optional class)
845 "Return the face attributes for FACE.
30afcdff 846If CLASS is set, it must be a `defface' alist key [see below],
acca02b0 847in which case the first face specification returned by `hfy-combined-face-spec'
30afcdff 848which *doesn't* clash with CLASS is returned.\n
acca02b0 849\(A specification with a class of t is considered to match any class you
30afcdff
JB
850specify - this matches Emacs' behavior when deciding on which face attributes
851to use, to the best of my understanding).\n
acca02b0
SM
852If CLASS is nil, then you just get get whatever `face-attr-construct' returns,
853ie the current specification in effect for FACE.\n
30afcdff
JB
854*NOTE*: This function forces any face that is not 'default and which has
855no :inherit property to inherit from 'default (this is because 'default
acca02b0 856is magical in that Emacs' fonts behave as if they inherit implicitly from
30afcdff
JB
857'default, but no such behavior exists in HTML/CSS).\n
858See also `hfy-display-class' for details of valid values for CLASS."
72fe6b25
SM
859 (let ((face-spec
860 (if class
861 (let ((face-props (hfy-combined-face-spec face))
862 (face-specn nil)
863 (face-class nil)
864 (face-attrs nil)
865 (face-score -1)
866 (face-match nil))
867 (while face-props
868 (setq face-specn (car face-props)
869 face-class (car face-specn)
870 face-attrs (cdr face-specn)
871 face-props (cdr face-props))
872 ;; if the current element CEL of CLASS is t we match
873 ;; if the current face-class is t, we match
874 ;; if the cdr of CEL has a non-nil
875 ;; intersection with the cdr of the first member of
876 ;; the current face-class with the same car as CEL, we match
877 ;; if we actually clash, then we can't match
878 (let ((cbuf class)
879 (cel nil)
880 (key nil)
881 (val nil)
882 (x nil)
883 (next nil)
884 (score 0))
885 (while (and cbuf (not next))
886 (setq cel (car cbuf)
887 cbuf (cdr cbuf)
888 key (car cel)
889 val (cdr cel)
890 val (if (listp val) val (list val)))
891 (cond
892 ((or (eq cel t)
893 (memq face-class '(t default))) ;Default match.
894 (setq score 0) (ignore "t match"))
895 ((not (cdr (assq key face-class))) ;Neither good nor bad.
896 nil (ignore "non match, non collision"))
897 ((setq x (hfy-interq val (cdr (assq key face-class))))
898 (setq score (+ score (length x)))
899 (ignore "intersection"))
900 (t ;; nope.
901 (setq next t score -10) (ignore "collision")) ))
902 (if (> score face-score)
903 (progn
904 (setq face-match face-attrs
905 face-score score )
906 (ignore "%d << %S/%S" score face-class class))
907 (ignore "--- %d ---- (insufficient)" score)) ))
908 ;; matched ? last attrs : nil
909 (if face-match
910 (if (listp (car face-match)) (car face-match) face-match)
911 nil))
912 ;; Unfortunately the default face returns a
913 ;; :background. Fortunately we can remove it, but how do we do
914 ;; that in a non-system specific way?
915 (let ((spec (face-attr-construct face))
916 (new-spec nil))
917 (if (not (memq :background spec))
918 spec
919 (while spec
920 (let ((a (nth 0 spec))
921 (b (nth 1 spec)))
922 (unless (and (eq a :background)
923 (stringp b)
924 (string= b "SystemWindow"))
925 (setq new-spec (cons a (cons b new-spec)))))
926 (setq spec (cddr spec)))
927 new-spec)))))
acca02b0
SM
928 (if (or (memq :inherit face-spec) (eq 'default face))
929 face-spec
b7d4de51 930 (append face-spec (list :inherit 'default)))))
acca02b0
SM
931
932;; construct an assoc of (css-tag-name . css-tag-value) pairs
933;; from a face or assoc of face attributes:
934
935;; Some tests etc:
936;; (mumamo-message-with-face "testing face" 'highlight)
937;; (mumamo-message-with-face "testing face" '(:foreground "red" :background "yellow"))
938;; (hfy-face-to-style-i '(:inherit default foreground-color "red"))
939;; default face=(:stipple nil :background "SystemWindow" :foreground
940;; "SystemWindowText" :inverse-video nil :box nil :strike-through
941;; nil :overline nil :underline nil :slant normal :weight normal
942;; :height 98 :width normal :family "outline-courier new")
943(defun hfy-face-to-style-i (fn)
944 "The guts of `hfy-face-to-style': FN should be a `defface' font spec,
30afcdff
JB
945as returned by `face-attr-construct' or `hfy-face-attr-for-class'.
946Note that this function does not get font-sizes right if they are based
947on inherited modifiers (via the :inherit) attribute, and any other
acca02b0
SM
948modifiers that are cumulative if they appear multiple times need to be
949merged by the user - `hfy-flatten-style' should do this."
950 ;;(message "hfy-face-to-style-i");;DBUG
951
952 ;; fn's value could be something like
953 ;; (:inherit
954 ;; ((foreground-color . "blue"))
955 ;; (foreground-color . "blue")
956 ;; nil)
957
958 (when fn
959 (let ((key (car fn))
960 (val (cadr fn))
961 (next (cddr fn))
962 (that nil)
963 (this nil)
964 (parent nil))
965 (if (eq key :inherit)
966 (let ((vs (if (listp val) val (list val))))
967 ;; (let ((x '(a b))) (setq x (append '(c d) x)))
968 ;; (let ((x '(a b))) (setq x (append '(c d) x)))
969 (dolist (v vs)
970 (setq parent
971 (append
972 parent
973 (hfy-face-to-style-i
974 (hfy-face-attr-for-class v hfy-display-class)) ))))
975 (setq this
72fe6b25
SM
976 (if val (case key
977 (:family (hfy-family val))
978 (:width (hfy-width val))
979 (:weight (hfy-weight val))
980 (:slant (hfy-slant val))
981 (:foreground (hfy-colour val))
982 (:background (hfy-bgcol val))
983 (:box (hfy-box val))
984 (:height (hfy-size val))
985 (:underline (hfy-decor key val))
986 (:overline (hfy-decor key val))
987 (:strike-through (hfy-decor key val))
988 (:invisible (hfy-invisible val))
989 (:bold (hfy-weight 'bold))
990 (:italic (hfy-slant 'italic))))))
acca02b0
SM
991 (setq that (hfy-face-to-style-i next))
992 ;;(lwarn t :warning "%S => %S" fn (nconc this that parent))
993 (nconc this that parent))) )
994
995(defun hfy-size-to-int (spec)
30afcdff
JB
996 "Convert SPEC, a CSS font-size specifier, to an Emacs :height attribute value.
997Used while merging multiple font-size attributes."
acca02b0
SM
998 ;;(message "hfy-size-to-int");;DBUG
999 (list
1000 (if (string-match "\\([0-9]+\\)\\(%\\|pt\\)" spec)
1001 (cond ((string= "%" (match-string 2 spec))
1002 (/ (string-to-number (match-string 1 spec)) 100.0))
1003 ((string= "pt" (match-string 2 spec))
1004 (* (string-to-number (match-string 1 spec)) 10)))
1005 (string-to-number spec))) )
1006
1007;; size is different, in that in order to get it right at all,
1008;; we have to trawl the inheritance path, accumulating modifiers,
1009;; _until_ we get to an absolute (pt) specifier, then combine the lot
1010(defun hfy-flatten-style (style)
1011 "Take STYLE (see `hfy-face-to-style-i', `hfy-face-to-style') and merge
1012any multiple attributes appropriately. Currently only font-size is merged
1013down to a single occurrence - others may need special handling, but I
30afcdff 1014haven't encountered them yet. Returns a `hfy-style-assoc'."
acca02b0
SM
1015 ;;(message "(hfy-flatten-style %S)" style) ;;DBUG
1016 (let ((n 0)
1017 (m (list 1))
1018 (x nil)
1019 (r nil))
72fe6b25
SM
1020 (dolist (css style)
1021 (if (string= (car css) "font-size")
1022 (progn
1023 (when (not x) (setq m (nconc m (hfy-size-to-int (cdr css)))))
1024 (when (string-match "pt" (cdr css)) (setq x t)))
1025 (setq r (nconc r (list css)))))
acca02b0
SM
1026 ;;(message "r: %S" r)
1027 (setq n (apply '* m))
1028 (nconc r (hfy-size (if x (round n) (* n 1.0)))) ))
1029
ecb0ab90
CY
1030(defun hfy-face-resolve-face (fn)
1031 (cond
1032 ((facep fn)
1033 (hfy-face-attr-for-class fn hfy-display-class))
1034 ((and (symbolp fn)
1035 (facep (symbol-value fn)))
1036 ;; Obsolete faces like `font-lock-reference-face' are defined as
1037 ;; aliases for another face.
1038 (hfy-face-attr-for-class (symbol-value fn) hfy-display-class))
1039 (t nil)))
1040
1041
acca02b0
SM
1042(defun hfy-face-to-style (fn)
1043 "Take FN, a font or `defface' style font specification,
30afcdff 1044\(as returned by `face-attr-construct' or `hfy-face-attr-for-class')
acca02b0 1045and return a `hfy-style-assoc'.\n
30afcdff 1046See also `hfy-face-to-style-i', `hfy-flatten-style'."
acca02b0 1047 ;;(message "hfy-face-to-style");;DBUG
ecb0ab90 1048 (let ((face-def (hfy-face-resolve-face fn))
acca02b0
SM
1049 (final-style nil))
1050
1051 (setq final-style (hfy-flatten-style (hfy-face-to-style-i face-def)))
1052 ;;(message "%S" final-style)
1053 (if (not (assoc "text-decoration" final-style))
1054 (progn (setq final-style
1055 ;; Fix-me: there is no need for this since
1056 ;; text-decoration is not inherited.
1057 ;; but it's not wrong and if this ever changes it will
1058 ;; be needed, so I think it's better to leave it in? -- v
1059 (nconc final-style '(("text-decoration"."none"))))))
1060 final-style))
1061
1062;; strip redundant bits from a name. Technically, this could result in
1063;; a collision, but it is pretty unlikely - will fix later...
1064;; also handle ephemeral fonts created by overlays, which don't actually
1065;; have names:
1066(defun hfy-face-or-def-to-name (fn)
30afcdff 1067 "Render a font symbol or `defface' font spec FN into a name (string)."
acca02b0
SM
1068 ;;(message "generating name for %s" fn)
1069 (if (not (listp fn))
1070 (format "%s" fn)
1071 (let* ((key (format "%s" fn))
1072 (entry (assoc key hfy-tmpfont-stack))
1073 (base (cadr (memq :inherit fn)))
1074 (tag (cdr entry)))
1075 ;;(message "checking for key «%s» in font stack [%d]"
1076 ;; key (if entry 1 0))
1077 (if entry nil ;; noop
1078 (setq tag (format "%04d" (length hfy-tmpfont-stack))
1079 entry (cons key tag)
1080 hfy-tmpfont-stack (cons entry hfy-tmpfont-stack)))
1081 ;;(message " -> name: %s-%s" (or base 'default) tag)
1082 (format "%s-%s" (or base 'default) tag)) ))
1083
1084(defun hfy-css-name (fn)
1085 "Strip the boring bits from a font-name FN and return a CSS style name."
1086 ;;(message "hfy-css-name");;DBUG
1087 (let ((face-name (hfy-face-or-def-to-name fn)))
1088 (if (or (string-match "font-lock-\\(.*\\)" face-name)
1089 (string-match "cperl-\\(.*\\)" face-name)
1090 (string-match "^[Ii]nfo-\\(.*\\)" face-name))
1091 (progn
1092 (setq face-name (match-string 1 face-name))
1093 (if (string-match "\\(.*\\)-face$" face-name)
1094 (setq face-name (match-string 1 face-name))) face-name)
1095 face-name)) )
1096
1097;; construct an assoc of (stripped-name . "{ css-stuff-here }") pairs
1098;; from a face:
1099(defun hfy-face-to-css (fn)
30afcdff 1100 "Take FN, a font or `defface' specification (cf `face-attr-construct')
acca02b0 1101and return a CSS style specification.\n
30afcdff 1102See also `hfy-face-to-style'."
acca02b0
SM
1103 ;;(message "hfy-face-to-css");;DBUG
1104 (let ((css-list nil)
1105 (css-text nil)
acca02b0
SM
1106 (seen nil))
1107 ;;(message "(hfy-face-to-style %S)" fn)
1108 (setq css-list (hfy-face-to-style fn))
1109 (setq css-text
72fe6b25
SM
1110 (mapcar
1111 (lambda (E)
1112 (if (car E)
1113 (unless (member (car E) seen)
1114 (push (car E) seen)
1115 (format " %s: %s; " (car E) (cdr E)))))
1116 css-list))
acca02b0
SM
1117 (cons (hfy-css-name fn) (format "{%s}" (apply 'concat css-text)))) )
1118
1119;; extract a face from a list of char properties, if there is one:
1120(defun hfy-p-to-face (props)
30afcdff 1121 "Given PROPS, a list of text properties, return the value of the face
acca02b0
SM
1122property, or nil."
1123 (if props
1124 (if (string= (car props) "face")
1125 (let ((propval (cadr props)))
1126 (if (and (listp propval) (not (cdr propval)))
1127 (car propval)
1128 propval))
1129 (hfy-p-to-face (cddr props)))
1130 nil))
1131
1132(defun hfy-p-to-face-lennart (props)
30afcdff 1133 "Given PROPS, a list of text properties, return the value of the face
acca02b0
SM
1134property, or nil."
1135 (when props
1136 (let ((face (plist-get props 'face))
1137 (font-lock-face (plist-get props 'font-lock-face))
1138 (button (plist-get props 'button))
1139 ;;(face-rec (memq 'face props))
1140 ;;(button-rec (memq 'button props)))
1141 )
1142 (if button
1143 (let* ((category (plist-get props 'category))
1144 (face (when category (plist-get (symbol-plist category) 'face))))
1145 face)
72fe6b25
SM
1146 (or font-lock-face
1147 face)))))
acca02b0
SM
1148
1149;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1150;; (defun hfy-get-face-at (pos)
1151;; ;; (let ((face (get-char-property-and-overlay pos 'face)))
1152;; ;; (when (and face (listp face)) (setq face (car face)))
1153;; ;; (unless (listp face)
1154;; ;; face)))
1155;; ;;(get-char-property pos 'face)
1156;; ;; Overlays are handled later
1157;; (if (or (not show-trailing-whitespace)
1158;; (not (get-text-property pos 'hfy-show-trailing-whitespace)))
1159;; (get-text-property pos 'face)
1160;; (list 'trailing-whitespace (get-text-property pos 'face)))
1161;; )
1162
1163(defun hfy-prop-invisible-p (prop)
1164 "Is text property PROP an active invisibility property?"
1165 (or (and (eq buffer-invisibility-spec t) prop)
1166 (or (memq prop buffer-invisibility-spec)
1167 (assq prop buffer-invisibility-spec))))
1168
1169(defun hfy-find-invisible-ranges ()
1170 "Return a list of (start-point . end-point) cons cells of invisible regions."
1171 (let (invisible p i e s) ;; return-value pos invisible end start
1172 (save-excursion
1173 (setq p (goto-char (point-min)))
1174 (when (invisible-p p) (setq s p i t))
1175 (while (< p (point-max))
1176 (if i ;; currently invisible
1177 (when (not (invisible-p p)) ;; but became visible
1178 (setq e p
1179 i nil
1180 invisible (cons (cons s e) invisible)))
1181 ;; currently visible:
1182 (when (invisible-p p) ;; but have become invisible
1183 (setq s p i t)))
1184 (setq p (next-char-property-change p)))
1185 ;; still invisible at buffer end?
1186 (when i
1187 (setq e (point-max)
1188 invisible (cons (cons s e) invisible))) ) invisible))
1189
1190(defun hfy-invisible-name (point map)
1191 "Generate a CSS style name for an invisible section of the buffer.
1192POINT is the point inside the invisible region.
1193MAP is the invisibility map as returned by `hfy-find-invisible-ranges'."
1194 ;;(message "(hfy-invisible-name %S %S)" point map)
1195 (let (name)
72fe6b25
SM
1196 (dolist (range map)
1197 (when (and (>= point (car range))
1198 (< point (cdr range)))
1199 (setq name (format "invisible-%S-%S" (car range) (cdr range)))))
acca02b0
SM
1200 name))
1201
1202;; Fix-me: This function needs some cleanup by someone who understand
1203;; all the formats that face properties can have.
1204;;
1205;; overlay handling should be fine. haven't tested multiple stacked overlapping
1206;; overlays recently, but the common case of a text property face + an overlay
1207;; face produces the correct merged css style (or as close to it as css can get)
1208;; -- v
1209(defun hfy-face-at (p)
1210 "Find face in effect at point P.
30afcdff
JB
1211If overlays are to be considered (see `hfy-optimisations') then this may
1212return a `defface' style list of face properties instead of a face symbol."
acca02b0
SM
1213 ;;(message "hfy-face-at");;DBUG
1214 ;; Fix-me: clean up, remove face-name etc
1215 ;; not sure why we'd want to remove face-name? -- v
72fe6b25
SM
1216 (let ((overlay-data nil)
1217 (base-face nil)
1218 ;; restored hfy-p-to-face as it handles faces like (bold) as
1219 ;; well as face like 'bold - hfy-get-face-at doesn't dtrt -- v
1220 (face-name (hfy-p-to-face (text-properties-at p)))
1221 ;; (face-name (hfy-get-face-at p))
1222 (prop-seen nil)
1223 (extra-props nil)
1224 (text-props (text-properties-at p)))
1225 ;;(message "face-name: %S" face-name)
1226 (when (and face-name (listp face-name) (facep (car face-name)))
1227 ;;(message "face-name is a list %S" face-name)
1228 ;;(setq text-props (cons 'face face-name))
1229 (dolist (f face-name)
1230 (setq extra-props (if (listp f)
1231 ;; for things like (variable-pitch
1232 ;; (:foreground "red"))
1233 (cons f extra-props)
1234 (cons :inherit (cons f extra-props)))))
1235 (setq base-face (car face-name)
1236 face-name nil))
1237 ;; text-properties-at => (face (:foreground "red" ...))
1238 ;; or => (face (compilation-info underline)) list of faces
1239 ;; overlay-properties
1240 ;; format= (evaporate t face ((foreground-color . "red")))
1241
1242 ;; SO: if we have turned overlays off,
1243 ;; or if there's no overlay data
1244 ;; just bail out and return whatever face data we've accumulated so far
1245 (if (or (not (hfy-opt 'keep-overlays))
1246 (not (setq overlay-data (hfy-overlay-props-at p))))
1247 (progn
1248 ;;(message "· %d: %s; %S; %s"
1249 ;; p face-name extra-props text-props)
1250 (or face-name base-face)) ;; no overlays or extra properties
1251 ;; collect any face data and any overlay data for processing:
1252 (when text-props
1253 (push text-props overlay-data))
1254 (setq overlay-data (nreverse overlay-data))
1255 ;;(message "- %d: %s; %S; %s; %s"
1256 ;; p face-name extra-props text-props overlay-data)
1257 ;; remember the basic face name so we don't keep repeating its specs:
1258 (when face-name (setq base-face face-name))
1259 (dolist (P overlay-data)
1260 (let ((iprops (cadr (memq 'invisible P)))) ;FIXME: plist-get?
1261 ;;(message "(hfy-prop-invisible-p %S)" iprops)
1262 (when (and iprops (hfy-prop-invisible-p iprops))
1263 (setq extra-props
1264 (cons :invisible (cons t extra-props))) ))
1265 (let ((fprops (cadr (or (memq 'face P)
1266 (memq 'font-lock-face P)))))
1267 ;;(message "overlay face: %s" fprops)
1268 (if (not (listp fprops))
1269 (let ((this-face (if (stringp fprops) (intern fprops) fprops)))
1270 (when (not (eq this-face base-face))
1271 (setq extra-props
1272 (cons :inherit
1273 (cons this-face extra-props))) ))
1274 (while fprops
1275 (if (facep (car fprops))
1276 (let ((face (car fprops)))
1277 (when (stringp face) (setq face (intern fprops)))
1278 (setq extra-props
1279 (cons :inherit
1280 (cons face
1281 extra-props)))
1282 (setq fprops (cdr fprops)))
1283 (let (p v)
1284 ;; Sigh.
1285 (if (listp (car fprops))
1286 (if (nlistp (cdr (car fprops)))
1287 (progn
1288 ;; ((prop . val))
1289 (setq p (caar fprops))
1290 (setq v (cdar fprops))
1291 (setq fprops (cdr fprops)))
1292 ;; ((prop val))
1293 (setq p (caar fprops))
1294 (setq v (cadar fprops))
1295 (setq fprops (cdr fprops)))
1296 (if (listp (cdr fprops))
1297 (progn
1298 ;; (:prop val :prop val ...)
1299 (setq p (car fprops))
1300 (setq v (cadr fprops))
1301 (setq fprops (cddr fprops)))
1302 (if (and (listp fprops)
1303 (not (listp (cdr fprops))))
1304 ;;(and (consp x) (cdr (last x)))
1305 (progn
1306 ;; (prop . val)
1307 (setq p (car fprops))
1308 (setq v (cdr fprops))
1309 (setq fprops nil))
1310 (error "Eh... another format! fprops=%s" fprops) )))
1311 (setq p (case p
1312 ;; These are all the properties handled
1313 ;; in `hfy-face-to-style-i'.
1314 ;;
1315 ;; Are these translations right?
1316 ;; yes, they are -- v
1317 (family :family )
1318 (width :width )
1319 (height :height )
1320 (weight :weight )
1321 (slant :slant )
1322 (underline :underline )
1323 (overline :overline )
1324 (strike-through :strike-through)
1325 (box :box )
1326 (foreground-color :foreground)
1327 (background-color :background)
1328 (bold :bold )
1329 (italic :italic )
1330 (t p)))
1331 (if (memq p prop-seen) nil ;; noop
1332 (setq prop-seen (cons p prop-seen)
1333 extra-props (cons p (cons v extra-props))))))))))
1334 ;;(message "+ %d: %s; %S" p face-name extra-props)
1335 (if extra-props
1336 (if (listp face-name)
1337 (nconc extra-props face-name)
1338 (nconc extra-props (face-attr-construct face-name)))
1339 face-name)) ))
acca02b0
SM
1340
1341(defun hfy-overlay-props-at (p)
1342 "Grab overlay properties at point P.
1343The plists are returned in descending priority order."
72fe6b25
SM
1344 (sort (mapcar #'overlay-properties (overlays-at p))
1345 (lambda (A B) (> (or (cadr (memq 'priority A)) 0) ;FIXME: plist-get?
1346 (or (cadr (memq 'priority B)) 0)))))
acca02b0
SM
1347
1348;; construct an assoc of (face-name . (css-name . "{ css-style }")) elements:
1349(defun hfy-compile-stylesheet ()
1350 "Trawl the current buffer, construct and return a `hfy-sheet-assoc'."
1351 ;;(message "hfy-compile-stylesheet");;DBUG
1352 (let ((pt (point-min))
1353 ;; Make the font stack stay:
1354 ;;(hfy-tmpfont-stack nil)
1355 (fn nil)
acca02b0
SM
1356 (style nil))
1357 (save-excursion
1358 (goto-char pt)
1359 (while (< pt (point-max))
1360 (if (and (setq fn (hfy-face-at pt)) (not (assoc fn style)))
72fe6b25 1361 (push (cons fn (hfy-face-to-css fn)) style))
acca02b0 1362 (setq pt (next-char-property-change pt))) )
72fe6b25 1363 (push (cons 'default (hfy-face-to-css 'default)) style)))
acca02b0
SM
1364
1365(defun hfy-fontified-p ()
30afcdff 1366 "`font-lock' doesn't like to say it's been fontified when in batch
acca02b0
SM
1367mode, but we want to know if we should fontify or raw copy, so in batch
1368mode we check for non-default face properties. Otherwise we test
1369variable `font-lock-mode' and variable `font-lock-fontified' for truth."
1370 ;;(message "font-lock-fontified: %S" font-lock-fontified)
1371 ;;(message "noninteractive : %S" noninteractive)
1372 ;;(message "font-lock-mode : %S" font-lock-mode)
1373 (and font-lock-fontified
1374 (if noninteractive
1375 (let ((pt (point-min))
1376 (face-name nil))
1377 (save-excursion
1378 (goto-char pt)
1379 (while (and (< pt (point-max)) (not face-name))
1380 (setq face-name (hfy-face-at pt))
1381 (setq pt (next-char-property-change pt)))) face-name)
1382 font-lock-mode)))
1383
1384;; remember, the map is in reverse point order:
1385;; I wrote this while suffering the effects of a cold, and maybe a
1386;; mild fever - I think it's correct, but it might be a little warped
1387;; as my minfd keeps ... where was I? Oh yes, the bunnies...
1388(defun hfy-merge-adjacent-spans (face-map)
1389 "Where FACE-MAP is a `hfy-facemap-assoc' for the current buffer,
1390this function merges adjacent style blocks which are of the same value
1391and are separated by nothing more interesting than whitespace.\n
1392 <span class=\"foo\">narf</span> <span class=\"foo\">brain</span>\n
30afcdff 1393\(as interpreted from FACE-MAP) would become:\n
acca02b0
SM
1394 <span class=\"foo\">narf brain</span>\n
1395Returns a modified copy of FACE-MAP."
1396 (let ((tmp-map face-map)
1397 (map-buf nil)
1398 (first-start nil)
1399 (first-stop nil)
1400 (last-start nil)
1401 (last-stop nil)
1402 (span-stop nil)
1403 (span-start nil)
1404 (reduced-map nil))
72fe6b25
SM
1405 ;;(push (car tmp-map) reduced-map)
1406 ;;(push (cadr tmp-map) reduced-map)
acca02b0
SM
1407 (while tmp-map
1408 (setq first-start (cadddr tmp-map)
1409 first-stop (caddr tmp-map)
1410 last-start (cadr tmp-map)
1411 last-stop (car tmp-map)
1412 map-buf tmp-map
1413 span-start last-start
1414 span-stop last-stop )
1415 (while (and (equal (cdr first-start)
1416 (cdr last-start))
1417 (save-excursion
1418 (goto-char (car first-stop))
1419 (not (re-search-forward "[^ \t\n\r]" (car last-start) t))))
1420 (setq map-buf (cddr map-buf)
1421 span-start first-start
1422 first-start (cadddr map-buf)
1423 first-stop (caddr map-buf)
1424 last-start (cadr map-buf)
1425 last-stop (car map-buf)))
72fe6b25
SM
1426 (push span-stop reduced-map)
1427 (push span-start reduced-map)
acca02b0
SM
1428 (setq tmp-map (memq last-start tmp-map))
1429 (setq tmp-map (cdr tmp-map)))
1430 (setq reduced-map (nreverse reduced-map))))
1431
1432;; remember to generate 'synthetic' </span> entries -
1433;; emacs copes by just having a stack of styles in effect
1434;; and only using the top one: html has a more simplistic approach -
1435;; we have to explicitly end a style, there's no way of temporarily
1436;; overriding it w. another one... (afaik)
1437(defun hfy-compile-face-map ()
1438;; no need for special <a> version.
1439;; IME hyperlinks don't get underlined, esp when you htmlfontify a whole
1440;; source tree, so the <a> version is needed -- v
1441;; Fix-me: save table for multi-buffer
1442 "Compile and return a `hfy-facemap-assoc' for the current buffer."
1443 ;;(message "hfy-compile-face-map");;DBUG
1444 (let ((pt (point-min))
1445 (pt-narrow 1)
1446 (fn nil)
1447 (map nil)
1448 (prev-tag nil)) ;; t if the last tag-point was a span-start
1449 ;; nil if it was a span-stop
1450 (save-excursion
1451 (goto-char pt)
1452 (while (< pt (point-max))
1453 (if (setq fn (hfy-face-at pt))
72fe6b25
SM
1454 (progn (if prev-tag (push (cons pt-narrow 'end) map))
1455 (push (cons pt-narrow fn) map)
acca02b0 1456 (setq prev-tag t))
72fe6b25 1457 (if prev-tag (push (cons pt-narrow 'end) map))
acca02b0
SM
1458 (setq prev-tag nil))
1459 (setq pt (next-char-property-change pt))
1460 (setq pt-narrow (1+ (- pt (point-min)))))
1461 (if (and map (not (eq 'end (cdar map))))
72fe6b25 1462 (push (cons (- (point-max) (point-min)) 'end) map)))
acca02b0
SM
1463 (if (hfy-opt 'merge-adjacent-tags) (hfy-merge-adjacent-spans map) map)))
1464
1465(defun hfy-buffer ()
30afcdff
JB
1466 "Generate a buffer to hold the HTML output.
1467The filename of this buffer is derived from the source (current) buffer's
acca02b0
SM
1468variable `buffer-file-name', if it is set, plus `hfy-extn'.
1469Otherwise a plausible filename is constructed from `default-directory',
1470`buffer-name' and `hfy-extn'."
1471 (let* ((name (concat (buffer-name) hfy-extn))
1472 (src (buffer-file-name))
1473 (buf (get-buffer-create name)))
e3353a78
SM
1474 (with-current-buffer buf
1475 (setq buffer-file-name
1476 (if src (concat src hfy-extn)
1477 (expand-file-name (if (string-match "^.*/\\([^/]*\\)$" name)
1478 (match-string 1 name)
1479 name))))
acca02b0
SM
1480 buf)))
1481
1482(defun hfy-lookup (face style)
1483 "Get a CSS style name for FACE from STYLE."
1484 (cadr (assoc face style)))
1485
1486(defun hfy-link-style (style-string)
1487 "Copy, alter and return a STYLE-STRING to make it suitable for a hyperlink.
1488Uses `hfy-link-style-fun' to do this."
1489 (if (functionp hfy-link-style-fun)
1490 (funcall hfy-link-style-fun style-string)
1491 style-string))
1492
1493(defun hfy-sprintf-stylesheet (css file)
1494 "Return the inline CSS style sheet for FILE as a string."
1495 (let ((stylesheet nil))
1496 (setq stylesheet
1497 (concat
1498 hfy-meta-tags
1499 "\n<style type=\"text/css\"><!-- \n"
1500 ;; Fix-me: Add handling of page breaks here + scan for ^L
1501 ;; where appropriate.
1502 (format "body %s\n" (cddr (assq 'default css)))
1503 (apply 'concat
1504 (mapcar
1505 (lambda (style)
1506 (format
1507 "span.%s %s\nspan.%s a %s\n"
1508 (cadr style) (cddr style)
72fe6b25
SM
1509 (cadr style) (hfy-link-style (cddr style))))
1510 css))
acca02b0
SM
1511 " --></style>\n"))
1512 (funcall hfy-page-header file stylesheet)))
1513
acca02b0
SM
1514;; tag all the dangerous characters we want to escape
1515;; (ie any "<> chars we _didn't_ put there explicitly for css markup)
1516(defun hfy-html-enkludge-buffer ()
30afcdff 1517 "Mark dangerous [\"<>] characters with the `hfy-quoteme' property.\n
acca02b0
SM
1518See also `hfy-html-dekludge-buffer'."
1519 ;;(message "hfy-html-enkludge-buffer");;DBUG
1520 (save-excursion
1521 (goto-char (point-min))
1522 (while (re-search-forward hfy-html-quote-regex nil t)
1523 (put-text-property (match-beginning 0) (point) 'hfy-quoteme t))) )
1524
1525;; dangerous char -> &entity;
1526(defun hfy-html-quote (char-string)
30afcdff 1527 "Map CHAR-STRING to an HTML safe string (entity) if need be."
acca02b0
SM
1528 ;;(message "hfy-html-quote");;DBUG
1529 (or (cadr (assoc char-string hfy-html-quote-map)) char-string) )
1530
1531;; actually entity-ise dangerous chars.
1532;; note that we can't do this until _after_ we have inserted the css
1533;; markup, since we use a position-based map to insert this, and if we
1534;; enter any other text before we do this, we'd have to track another
1535;; map of offsets, which would be tedious...
1536(defun hfy-html-dekludge-buffer ()
30afcdff
JB
1537 "Transform all dangerous characters marked with the `hfy-quoteme' property
1538using `hfy-html-quote'.\n
acca02b0
SM
1539See also `hfy-html-enkludge-buffer'."
1540 ;;(message "hfy-html-dekludge-buffer");;DBUG
1541 (save-excursion
1542 (goto-char (point-min))
1543 (while (re-search-forward hfy-html-quote-regex nil t)
1544 (if (get-text-property (match-beginning 0) 'hfy-quoteme)
1545 (replace-match (hfy-html-quote (match-string 1))) )) ))
1546
1547;; Borrowed from font-lock.el
1548(defmacro hfy-save-buffer-state (varlist &rest body)
1549 "Bind variables according to VARLIST and eval BODY restoring buffer state.
1550Do not record undo information during evaluation of BODY."
1551 (declare (indent 1) (debug let))
1552 (let ((modified (make-symbol "modified")))
1553 `(let* ,(append varlist
1554 `((,modified (buffer-modified-p))
1555 (buffer-undo-list t)
1556 (inhibit-read-only t)
1557 (inhibit-point-motion-hooks t)
1558 (inhibit-modification-hooks t)
1559 deactivate-mark
1560 buffer-file-name
1561 buffer-file-truename))
1562 (progn
1563 ,@body)
1564 (unless ,modified
1565 (restore-buffer-modified-p nil)))))
1566
1567(defun hfy-mark-trailing-whitespace ()
1568 "Tag trailing whitespace with a hfy property if it is currently highlighted."
1569 (when show-trailing-whitespace
1570 (let ((inhibit-read-only t))
1571 (save-excursion
1572 (goto-char (point-min))
1573 (hfy-save-buffer-state nil
1574 (while (re-search-forward "[ \t]+$" nil t)
1575 (put-text-property (match-beginning 0) (match-end 0)
1576 'hfy-show-trailing-whitespace t)))))))
1577
1578(defun hfy-unmark-trailing-whitespace ()
1579 "Undo the effect of `hfy-mark-trailing-whitespace'."
1580 (when show-trailing-whitespace
1581 (hfy-save-buffer-state nil
1582 (remove-text-properties (point-min) (point-max)
1583 '(hfy-show-trailing-whitespace)))))
1584
1585(defun hfy-fontify-buffer (&optional srcdir file)
1586 "Implement the guts of `htmlfontify-buffer'.
1587SRCDIR, if set, is the directory being htmlfontified.
1588FILE, if set, is the file name."
1589 (if srcdir (setq srcdir (directory-file-name srcdir)))
e3353a78 1590 (let* ( (html-buffer (hfy-buffer))
acca02b0
SM
1591 (css-sheet nil)
1592 (css-map nil)
1593 (invis-ranges nil)
1594 (rovl nil)
1595 (orig-ovls (overlays-in (point-min) (point-max)))
1596 (rmin (when mark-active (region-beginning)))
1597 (rmax (when mark-active (region-end ))) )
1598 (when (and mark-active
1599 transient-mark-mode)
1600 (unless (and (= rmin (point-min))
1601 (= rmax (point-max)))
1602 (setq rovl (make-overlay rmin rmax))
1603 (overlay-put rovl 'priority 1000)
1604 (overlay-put rovl 'face 'region)))
1605 ;; copy the buffer, including fontification, and switch to it:
1606 (hfy-mark-trailing-whitespace)
1607 (setq css-sheet (hfy-compile-stylesheet )
1608 css-map (hfy-compile-face-map )
1609 invis-ranges (hfy-find-invisible-ranges))
1610 (hfy-unmark-trailing-whitespace)
1611 (when rovl
1612 (delete-overlay rovl))
1613 (copy-to-buffer html-buffer (point-min) (point-max))
1614 (set-buffer html-buffer)
2ea1c4aa
SM
1615 ;; rip out props that could interfere with our htmlisation of the buffer:
1616 (remove-text-properties (point-min) (point-max) hfy-ignored-properties)
acca02b0
SM
1617 ;; Apply overlay invisible spec
1618 (setq orig-ovls
1619 (sort orig-ovls
1620 (lambda (A B)
1621 (> (or (cadr (memq 'priority (overlay-properties A))) 0)
1622 (or (cadr (memq 'priority (overlay-properties B))) 0)))))
1623 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1624 ;; at this point, html-buffer retains the fontification of the parent:
1625 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1626 ;; we don't really need or want text in the html buffer to be invisible, as
1627 ;; that can make it look like we've rendered invalid xhtml when all that's
1628 ;; happened is some tags are in the invisible portions of the buffer:
1629 (setq buffer-invisibility-spec nil)
1630 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1631 ;; #####################################################################
1632 ;; if we are in etags mode, add properties to mark the anchors and links
1633 (if (and srcdir file)
1634 (progn
1635 (hfy-mark-tag-names srcdir file) ;; mark anchors
1636 (hfy-mark-tag-hrefs srcdir file))) ;; mark links
1637 ;; #####################################################################
1638 ;; mark the 'dangerous' characters
1639 ;;(message "marking dangerous characters")
1640 (hfy-html-enkludge-buffer)
1641 ;; trawl the position-based face-map, inserting span tags as we go
1642 ;; note that we cannot change any character positions before this point
1643 ;; or we will invalidate the map:
1644 ;; NB: This also means we have to trawl the map in descending file-offset
1645 ;; order, obviously.
1646 ;; ---------------------------------------------------------------------
1647 ;; Remember, inserting pushes properties to the right, which we don't
1648 ;; actually want to happen for link properties, so we have to flag
1649 ;; them and move them by hand - if you don't, you end up with
1650 ;;
1651 ;; <span class="foo"><a href="bar">texta</span><span class="bletch"></a>...
1652 ;;
1653 ;; instead of:
1654 ;;
1655 ;; <span class="foo"><a href="bar">texta</a></span><span class="bletch">...
1656 ;;
1657 ;; If my analysis of the problem is correct, we can detect link-ness by
1658 ;; either hfy-linkp or hfy-endl properties at the insertion point, but I
1659 ;; think we only need to relocate the hfy-endl property, as the hfy-linkp
1660 ;; property has already served its main purpose by this point.
1661 ;;(message "mapcar over the CSS-MAP")
1662 (message "invis-ranges:\n%S" invis-ranges)
72fe6b25
SM
1663 (dolist (point-face css-map)
1664 (let ((pt (car point-face))
1665 (fn (cdr point-face))
1666 (move-link nil))
1667 (goto-char pt)
1668 (setq move-link
1669 (or (get-text-property pt 'hfy-linkp)
1670 (get-text-property pt 'hfy-endl )))
1671 (if (eq 'end fn)
1672 (insert "</span>")
1673 (if (not (and srcdir file))
1674 nil
1675 (when move-link
1676 (remove-text-properties (point) (1+ (point)) '(hfy-endl nil))
1677 (put-text-property pt (1+ pt) 'hfy-endl t) ))
1678 ;; if we have invisible blocks, we need to do some extra magic:
1679 (if invis-ranges
1680 (let ((iname (hfy-invisible-name pt invis-ranges))
1681 (fname (hfy-lookup fn css-sheet )))
1682 (when (assq pt invis-ranges)
1683 (insert
1684 (format "<span onclick=\"toggle_invis('%s');\">" iname))
1685 (insert "…</span>"))
1686 (insert
1687 (format "<span class=\"%s\" id=\"%s-%d\">" fname iname pt)))
1688 (insert (format "<span class=\"%s\">" (hfy-lookup fn css-sheet))))
1689 (if (not move-link) nil
1690 ;;(message "removing prop2 @ %d" (point))
1691 (if (remove-text-properties (point) (1+ (point)) '(hfy-endl nil))
1692 (put-text-property pt (1+ pt) 'hfy-endl t))))))
acca02b0
SM
1693 ;; #####################################################################
1694 ;; Invisibility
1695 ;; Maybe just make the text invisible in XHTML?
1696 ;; DONE -- big block of obsolete invisibility code elided here -- v
1697 ;; #####################################################################
1698 ;; (message "checking to see whether we should link...")
1699 (if (and srcdir file)
1700 (let ((lp 'hfy-link)
1701 (pt nil)
1702 (pr nil)
1703 (rr nil))
1704 ;; (message " yes we should.")
1705 ;; translate 'hfy-anchor properties to anchors
1706 (setq pt (point-min))
1707 (while (setq pt (next-single-property-change pt 'hfy-anchor))
1708 (if (setq pr (get-text-property pt 'hfy-anchor))
1709 (progn (goto-char pt)
1710 (remove-text-properties pt (1+ pt) '(hfy-anchor nil))
1711 (insert (concat "<a name=\"" pr "\"></a>")))))
1712 ;; translate alternate 'hfy-link and 'hfy-endl props to opening
1713 ;; and closing links. (this should avoid those spurious closes
1714 ;; we sometimes get by generating only paired tags)
1715 (setq pt (point-min))
1716 (while (setq pt (next-single-property-change pt lp))
1717 (if (not (setq pr (get-text-property pt lp))) nil
1718 (goto-char pt)
1719 (remove-text-properties pt (1+ pt) (list lp nil))
72fe6b25
SM
1720 (case lp
1721 (hfy-link
acca02b0
SM
1722 (if (setq rr (get-text-property pt 'hfy-inst))
1723 (insert (format "<a name=\"%s\"></a>" rr)))
1724 (insert (format "<a href=\"%s\">" pr))
1725 (setq lp 'hfy-endl))
72fe6b25 1726 (hfy-endl
acca02b0
SM
1727 (insert "</a>") (setq lp 'hfy-link)) ))) ))
1728
1729 ;; #####################################################################
1730 ;; transform the dangerous chars. This changes character positions
1731 ;; since entities have > char length.
1732 ;; note that this deletes the dangerous characters, and therefore
1733 ;; destroys any properties they may contain (such as 'hfy-endl),
1734 ;; so we have to do this after we use said properties:
1735 ;; (message "munging dangerous characters")
1736 (hfy-html-dekludge-buffer)
1737 ;; insert the stylesheet at the top:
1738 (goto-char (point-min))
1739 ;;(message "inserting stylesheet")
1740 (insert (hfy-sprintf-stylesheet css-sheet file))
acca02b0
SM
1741 (if (hfy-opt 'div-wrapper) (insert "<div class=\"default\">"))
1742 (insert "\n<pre>")
1743 (goto-char (point-max))
1744 (insert "</pre>\n")
1745 (if (hfy-opt 'div-wrapper) (insert "</div>"))
1746 ;;(message "inserting footer")
1747 (insert (funcall hfy-page-footer file))
1748 ;; call any post html-generation hooks:
1749 (run-hooks 'hfy-post-html-hooks)
1750 ;; return the html buffer
1751 (set-buffer-modified-p nil)
1752 html-buffer))
1753
1754(defun hfy-force-fontification ()
30afcdff 1755 "Try to force font-locking even when it is optimized away."
72fe6b25 1756 (run-hooks 'hfy-init-kludge-hook)
acca02b0
SM
1757 (eval-and-compile (require 'font-lock))
1758 (if (boundp 'font-lock-cache-position)
1759 (or font-lock-cache-position
1760 (set 'font-lock-cache-position (make-marker))))
1761 (if (not noninteractive)
1762 (progn
1763 (message "hfy interactive mode (%S %S)" window-system major-mode)
1764 (when (and font-lock-defaults
1765 font-lock-mode)
1766 (font-lock-fontify-region (point-min) (point-max) nil)))
1767 (message "hfy batch mode (%s:%S)"
1768 (or (buffer-file-name) (buffer-name)) major-mode)
1769 (when font-lock-defaults
1770 (font-lock-fontify-buffer)) ))
1771
0433ffa6 1772;;;###autoload
acca02b0
SM
1773(defun htmlfontify-buffer (&optional srcdir file)
1774 "Create a new buffer, named for the current buffer + a .html extension,
30afcdff 1775containing an inline CSS-stylesheet and formatted CSS-markup HTML
acca02b0
SM
1776that reproduces the look of the current Emacs buffer as closely
1777as possible.
1778
30afcdff
JB
1779Dangerous characters in the existing buffer are turned into HTML
1780entities, so you should even be able to do HTML-within-HTML
acca02b0
SM
1781fontified display.
1782
1783You should, however, note that random control or eight-bit
1784characters such as ^L (\x0c) or ¤ (\xa4) won't get mapped yet.
1785
1786If the SRCDIR and FILE arguments are set, lookup etags derived
30afcdff 1787entries in the `hfy-tags-cache' and add HTML anchors and
acca02b0
SM
1788hyperlinks as appropriate."
1789 (interactive)
1790 ;; pick up the file name in case we didn't receive it
1791 (if (not file)
1792 (progn (setq file (or (buffer-file-name) (buffer-name)))
1793 (if (string-match "/\\([^/]*\\)$" file)
1794 (setq file (match-string 1 file)))) )
1795
1796 (if (not (hfy-opt 'skip-refontification))
1797 (save-excursion ;; Keep region
1798 (hfy-force-fontification)))
1799 (if (interactive-p) ;; display the buffer in interactive mode:
1800 (switch-to-buffer (hfy-fontify-buffer srcdir file))
1801 (hfy-fontify-buffer srcdir file)))
1802
1803;; recursive file listing
1804(defun hfy-list-files (directory)
1805 "Return a list of files under DIRECTORY.
1806Strips any leading \"./\" from each filename."
1807 ;;(message "hfy-list-files");;DBUG
72fe6b25 1808 ;; FIXME: this changes the dir of the currrent buffer. Is that right??
acca02b0
SM
1809 (cd directory)
1810 (mapcar (lambda (F) (if (string-match "^./\\(.*\\)" F) (match-string 1 F) F))
1811 (split-string (shell-command-to-string hfy-find-cmd))) )
1812
1813;; strip the filename off, return a directiry name
1814;; not a particularly thorough implementaion, but it will be
1815;; fed pretty carefully, so it should be Ok:
1816(defun hfy-dirname (file)
1817 "Return everything preceding the last \"/\" from a relative filename FILE,
30afcdff
JB
1818on the assumption that this will produce a relative directory name.
1819Hardly bombproof, but good enough in the context in which it is being used."
acca02b0
SM
1820 ;;(message "hfy-dirname");;DBUG
1821 (let ((f (directory-file-name file)))
1822 (and (string-match "^\\(.*\\)/" f) (match-string 1 f))))
1823
1824;; create a directory, cf mkdir -p
1825(defun hfy-make-directory (dir)
30afcdff 1826 "Approx. equivalent of mkdir -p DIR."
acca02b0
SM
1827 ;;(message "hfy-make-directory");;DBUG
1828 (if (file-exists-p dir)
1829 (if (file-directory-p dir) t)
1830 (make-directory dir t)))
1831
1832(defun hfy-text-p (srcdir file)
30afcdff 1833 "Is SRCDIR/FILE text? Uses `hfy-istext-command' to determine this."
e3353a78
SM
1834 (let* ((cmd (format hfy-istext-command (expand-file-name file srcdir)))
1835 (rsp (shell-command-to-string cmd)))
acca02b0
SM
1836 (if (string-match "text" rsp) t nil)))
1837
1838;; open a file, check fontification, if fontified, write a fontified copy
1839;; to the destination directory, otherwise just copy the file:
1840(defun hfy-copy-and-fontify-file (srcdir dstdir file)
1841 "Open FILE in SRCDIR - if fontified, write a fontified copy to DSTDIR
1842adding an extension of `hfy-extn'. Fontification is actually done by
1843`htmlfontify-buffer'. If the buffer is not fontified, just copy it."
1844 ;;(message "hfy-copy-and-fontify-file");;DBUG
1845 (let (;;(fast-lock-minimum-size hfy-fast-lock-save)
1846 ;;(font-lock-support-mode 'fast-lock-mode)
1847 ;;(window-system (or window-system 'htmlfontify))
1848 (target nil)
1849 (source nil)
1850 (html nil))
1851 (cd srcdir)
e3353a78
SM
1852 (with-current-buffer (setq source (find-file-noselect file))
1853 ;; FIXME: Shouldn't this use expand-file-name? --Stef
acca02b0
SM
1854 (setq target (concat dstdir "/" file))
1855 (hfy-make-directory (hfy-dirname target))
1856 (if (not (hfy-opt 'skip-refontification)) (hfy-force-fontification))
1857 (if (or (hfy-fontified-p) (hfy-text-p srcdir file))
1858 (progn (setq html (hfy-fontify-buffer srcdir file))
1859 (set-buffer html)
1860 (write-file (concat target hfy-extn))
1861 (kill-buffer html))
1862 ;; #o0200 == 128, but emacs20 doesn't know that
1863 (if (and (file-exists-p target) (not (file-writable-p target)))
1864 (set-file-modes target (logior (file-modes target) 128)))
1865 (copy-file (buffer-file-name source) target 'overwrite))
1866 (kill-buffer source)) ))
1867
1868;; list of tags in file in srcdir
1869(defun hfy-tags-for-file (srcdir file)
1870 "List of etags tags that have definitions in this FILE.
1871Looks up the tags cache in `hfy-tags-cache' using SRCDIR as the key."
1872 ;;(message "hfy-tags-for-file");;DBUG
1873 (let ((cache-entry (assoc srcdir hfy-tags-cache))
1874 (cache-hash nil)
1875 (tag-list nil))
1876 (if (setq cache-hash (cadr cache-entry))
1877 (maphash
1878 (lambda (K V)
1879 (if (assoc file V)
1880 (setq tag-list (cons K tag-list)))) cache-hash))
1881 tag-list))
1882
1883;; mark the tags native to this file for anchors
1884(defun hfy-mark-tag-names (srcdir file)
30afcdff 1885 "Mark tags in FILE (lookup SRCDIR in `hfy-tags-cache') with the `hfy-anchor'
acca02b0
SM
1886property, with a value of \"tag.line-number\"."
1887 ;;(message "(hfy-mark-tag-names %s %s)" srcdir file);;DBUG
1888 (let ((cache-entry (assoc srcdir hfy-tags-cache))
1889 (cache-hash nil))
1890 (if (setq cache-hash (cadr cache-entry))
1891 (mapcar
1892 (lambda (TAG)
1893 (mapcar
1894 (lambda (TLIST)
1895 (if (string= file (car TLIST))
1896 (let* ((line (cadr TLIST) )
1897 (chr (caddr TLIST) )
1898 (link (format "%s.%d" TAG line) ))
1899 (put-text-property (+ 1 chr)
1900 (+ 2 chr)
1901 'hfy-anchor link))))
1902 (gethash TAG cache-hash)))
1903 (hfy-tags-for-file srcdir file)))))
1904
1905(defun hfy-relstub (file &optional start)
1906 "Return a \"../\" stub of the appropriate length for the current source
30afcdff 1907tree depth, as determined from FILE (a filename).
acca02b0
SM
1908START is the offset at which to start looking for the / character in FILE."
1909 ;;(message "hfy-relstub");;DBUG
1910 (let ((c ""))
1911 (while (setq start (string-match "/" file start))
1912 (setq start (1+ start)) (setq c (concat c "../"))) c))
1913
1914(defun hfy-href-stub (this-file def-files tag)
30afcdff
JB
1915 "Return an href stub for a tag href in THIS-FILE.
1916If DEF-FILES (list of files containing definitions for the tag in question)
acca02b0
SM
1917contains only one entry, the href should link straight to that file.
1918Otherwise, the link should be to the index file.\n
1919We are not yet concerned with the file extensions/tag line number and so on at
1920this point.\n
1921If `hfy-split-index' is set, and the href wil be to an index file rather than
1922a source file, append a .X to `hfy-index-file', where X is the uppercased
1923first character of TAG.\n
30afcdff 1924See also `hfy-relstub', `hfy-index-file'."
acca02b0 1925 ;;(message "hfy-href-stub");;DBUG
e3353a78
SM
1926 ;; FIXME: Why not use something like
1927 ;; (file-relative-name (if ...) (file-name-directory this-file)) ? --Stef
acca02b0
SM
1928 (concat
1929 (hfy-relstub this-file)
1930 (if (= 1 (length def-files)) (car def-files)
1931 (if (not hfy-split-index) hfy-index-file
1932 (concat hfy-index-file "." (upcase (substring tag 0 1)))))) )
1933
1934(defun hfy-href (this-file def-files tag tag-map)
1935 "Return a relative href to the tag in question, based on\n
1936THIS-FILE `hfy-link-extn' `hfy-extn' DEF-FILES TAG and TAG-MAP\n
1937THIS-FILE is the current source file
1938DEF-FILES is a list of file containing possible link endpoints for TAG
30afcdff 1939TAG is the tag in question
acca02b0
SM
1940TAG-MAP is the entry in `hfy-tags-cache'."
1941 ;;(message "hfy-href");;DBUG
1942 (concat
1943 (hfy-href-stub this-file def-files tag)
1944 (or hfy-link-extn hfy-extn) "#" tag ;;(.src -> .html)
1945 (if (= 1 (length def-files))
1946 (concat "." (format "%d" (cadr (assoc (car def-files) tag-map)))))) )
1947
1948(defun hfy-word-regex (string)
1949 "Return a regex that matches STRING as the first `match-string', with non
1950word characters on either side."
e3353a78 1951 ;; FIXME: Should this use [^$[:alnum:]_] instead? --Stef
acca02b0
SM
1952 (concat "[^$A-Za-z_0-9]\\(" (regexp-quote string) "\\)[^A-Za-z_0-9]"))
1953
1954;; mark all tags for hyperlinking, except the tags at
1955;; their own points of definition, iyswim:
1956(defun hfy-mark-tag-hrefs (srcdir file)
30afcdff
JB
1957 "Mark href start points with the `hfy-link' prop (value: href string).\n
1958Mark href end points with the `hfy-endl' prop (value t).\n
acca02b0
SM
1959Avoid overlapping links, and mark links in descending length of
1960tag name in order to prevent subtags from usurping supertags,
1961\(eg \"term\" for \"terminal\").
1962SRCDIR is the directory being \"published\".
1963FILE is the specific file we are rendering."
1964 ;;(message "hfy-mark-tag-hrefs");;DBUG
1965 (let ((cache-entry (assoc srcdir hfy-tags-cache))
1966 (list-cache (assoc srcdir hfy-tags-sortl))
1967 (rmap-cache (assoc srcdir hfy-tags-rmap ))
1968 (no-comment (hfy-opt 'zap-comment-links))
1969 (no-strings (hfy-opt 'zap-string-links ))
1970 (cache-hash nil)
1971 (tags-list nil)
1972 (tags-rmap nil)
1973 (case-fold-search nil))
1974 ;; extract the tag mapping hashes (fwd and rev) and the tag list:
1975 (if (and (setq cache-hash (cadr cache-entry))
1976 (setq tags-rmap (cadr rmap-cache ))
1977 (setq tags-list (cadr list-cache )))
1978 (mapcar
1979 (lambda (TAG)
1980 (let* ((start nil)
1981 (stop nil)
1982 (href nil)
1983 (name nil)
1984 (case-fold-search nil)
1985 (tmp-point nil)
1986 (maybe-start nil)
1987 (face-at nil)
1988 (rmap-entry nil)
1989 (rnew-elt nil)
1990 (rmap-line nil)
1991 (tag-regex (hfy-word-regex TAG))
1992 (tag-map (gethash TAG cache-hash))
72fe6b25 1993 (tag-files (mapcar #'car tag-map)))
acca02b0
SM
1994 ;; find instances of TAG and do what needs to be done:
1995 (goto-char (point-min))
1996 (while (search-forward TAG nil 'NOERROR)
1997 (setq tmp-point (point)
1998 maybe-start (- (match-beginning 0) 1))
1999 (goto-char maybe-start)
2000 (if (not (looking-at tag-regex))
2001 nil
2002 (setq start (match-beginning 1))
2003 (setq stop (match-end 1))
2004 (setq face-at
2005 (and (or no-comment no-strings) (hfy-face-at start)))
2006 (if (listp face-at)
2007 (setq face-at (cadr (memq :inherit face-at))))
2008 (if (or (text-property-any start (1+ stop) 'hfy-linkp t)
2009 (and no-comment (eq 'font-lock-comment-face face-at))
2010 (and no-strings (eq 'font-lock-string-face face-at)))
2011 nil ;; already a link, NOOP
2012
2013 ;; set a reverse map entry:
2014 (setq rmap-line (line-number-at-pos)
2015 rmap-entry (gethash TAG tags-rmap)
2016 rnew-elt (list file rmap-line start)
2017 rmap-entry (cons rnew-elt rmap-entry)
2018 name (format "%s.%d" TAG rmap-line))
2019 (put-text-property start (1+ start) 'hfy-inst name)
2020 (puthash TAG rmap-entry tags-rmap)
2021
2022 ;; mark the link. link to index if the tag has > 1 def
2023 ;; add the line number to the #name if it does not:
2024 (setq href (hfy-href file tag-files TAG tag-map))
2025 (put-text-property start (1+ start) 'hfy-link href)
2026 (put-text-property stop (1+ stop ) 'hfy-endl t )
2027 (put-text-property start (1+ stop ) 'hfy-linkp t )))
2028 (goto-char tmp-point)) ))
2029 tags-list) )))
2030
2031(defun hfy-shell ()
2032 "Return `shell-file-name', or \"/bin/sh\" if it is a non-bourne shell."
2033 (if (string-match "\\<bash\\>\\|\\<sh\\>\\|\\<dash\\>" shell-file-name)
2034 shell-file-name
2035 (or hfy-shell-file-name "/bin/sh")))
2036
2037;; cache the #(tag => file line point) entries for files under srcdir
2038;; and cache the descending sorted list of tags in the relevant alist,
2039;; also keyed by srcdir:
2040(defun hfy-load-tags-cache (srcdir)
2041 "Run `hfy-etags-cmd' on SRCDIR, then call `hfy-parse-tags-buffer'."
2042 ;;(message "hfy-load-tags-cache");;DBUG
2043 (let ((etags-buffer (get-buffer-create "*hfy-tags*"))
2044 (etags-command (format hfy-etags-cmd hfy-etags-bin))
2045 (shell-file-name (hfy-shell)))
2046 (cd srcdir)
2047 (shell-command etags-command etags-buffer)
2048 (hfy-parse-tags-buffer srcdir etags-buffer)) )
2049
2050;; break this out from `hfy-load-tags-cache' to make the tar file
2051;; functionality easier to implement.
2052;; ( tar file functionality not merged here because it requires a
2053;; hacked copy of etags capable of tagging stdin: if Francesco
2054;; Potorti accepts a patch, or otherwise implements stdin tagging,
2055;; then I will provide a `htmlfontify-tar-file' defun )
2056(defun hfy-parse-tags-buffer (srcdir buffer)
2057 "Parse a BUFFER containing etags formatted output, loading the
2058`hfy-tags-cache' and `hfy-tags-sortl' entries for SRCDIR."
2059 (let ((cache-entry (assoc srcdir hfy-tags-cache))
2060 (tlist-cache (assoc srcdir hfy-tags-sortl))
2061 (trmap-cache (assoc srcdir hfy-tags-rmap ))
2062 (cache-hash nil) (trmap-hash nil) (tags-list nil)
2063 (hash-entry nil) (tag-string nil) (tag-line nil)
2064 (tag-point nil) (new-entry nil) (etags-file nil))
2065
2066 ;; (re)initialise the tag reverse map:
2067 (if trmap-cache (setq trmap-hash (cadr trmap-cache))
2068 (setq trmap-hash (make-hash-table :test 'equal))
2069 (setq hfy-tags-rmap (list (list srcdir trmap-hash) hfy-tags-rmap)))
2070 (clrhash trmap-hash)
2071
2072 ;; (re)initialise the tag cache:
2073 (if cache-entry (setq cache-hash (cadr cache-entry))
2074 (setq cache-hash (make-hash-table :test 'equal))
2075 (setq hfy-tags-cache (list (list srcdir cache-hash) hfy-tags-cache)))
2076 (clrhash cache-hash)
2077
2078 ;; cache the TAG => ((file line point) (file line point) ... ) entries:
e3353a78 2079 (with-current-buffer buffer
acca02b0
SM
2080 (goto-char (point-min))
2081
2082 (while (and (looking-at "^\x0c") (= 0 (forward-line 1)))
2083 ;;(message "^L boundary")
2084 (if (and (looking-at "^\\(.+\\),\\([0-9]+\\)$")
2085 (= 0 (forward-line 1)))
2086 (progn
2087 (setq etags-file (match-string 1))
2088 ;;(message "TAGS for file: %s" etags-file)
2089 (while (and (looking-at hfy-etag-regex) (= 0 (forward-line 1)))
2090 (setq tag-string (match-string 1))
2091 (if (= 0 (length tag-string)) nil ;; noop
2092 (setq tag-line (round (string-to-number (match-string 2))))
2093 (setq tag-point (round (string-to-number (match-string 3))))
2094 (setq hash-entry (gethash tag-string cache-hash))
2095 (setq new-entry (list etags-file tag-line tag-point))
72fe6b25 2096 (push new-entry hash-entry)
acca02b0
SM
2097 ;;(message "HASH-ENTRY %s %S" tag-string new-entry)
2098 (puthash tag-string hash-entry cache-hash)))) )))
2099
2100 ;; cache a list of tags in descending length order:
72fe6b25 2101 (maphash (lambda (K V) (push K tags-list)) cache-hash)
acca02b0
SM
2102 (setq tags-list (sort tags-list (lambda (A B) (< (length B) (length A)))))
2103
2104 ;; put the tag list into the cache:
2105 (if tlist-cache (setcar (cdr tlist-cache) tags-list)
72fe6b25 2106 (push (list srcdir tags-list) hfy-tags-sortl))
acca02b0
SM
2107
2108 ;; return the number of tags found:
2109 (length tags-list) ))
2110
2111(defun hfy-prepare-index-i (srcdir dstdir filename &optional stub map)
2112 "Prepare a tags index buffer for SRCDIR.
2113`hfy-tags-cache' must already have an entry for SRCDIR for this to work.
2114`hfy-page-header', `hfy-page-footer', `hfy-link-extn' and `hfy-extn'
2115all play a part here.\n
30afcdff 2116If STUB is set, prepare an (appropriately named) index buffer
acca02b0
SM
2117specifically for entries beginning with STUB.\n
2118If MAP is set, use that instead of `hfy-tags-cache'.
2119FILENAME is the name of the file being indexed.
2120DSTDIR is the output directory, where files will be written."
2121 ;;(message "hfy-write-index");;DBUG
2122 (let ((cache-entry (assoc srcdir (or map hfy-tags-cache)))
2123 (cache-hash nil)
2124 (tag-list nil)
2125 (index-file
2126 (concat filename (if stub (concat "." stub) "") hfy-extn))
2127 (index-buf nil))
2128 (if (not (and cache-entry
2129 (setq cache-hash (cadr cache-entry))
2130 (setq index-buf (get-buffer-create index-file))))
2131 nil ;; noop
72fe6b25 2132 (maphash (lambda (K V) (push K tag-list)) cache-hash)
acca02b0
SM
2133 (setq tag-list (sort tag-list 'string<))
2134 (set-buffer index-buf)
2135 (erase-buffer)
2136 (insert (funcall hfy-page-header filename "<!-- CSS -->"))
2137 (insert "<table class=\"index\">\n")
2138
72fe6b25
SM
2139 (dolist (TAG tag-list)
2140 (let ((tag-started nil))
2141 (dolist (DEF (gethash TAG cache-hash))
2142 (if (and stub (not (string-match (concat "^" stub) TAG)))
2143 nil ;; we have a stub and it didn't match: NOOP
2144 (let ((file (car DEF))
2145 (line (cadr DEF)))
2146 (insert
2147 (format
2148 (concat
2149 " <tr> \n"
2150 " <td>%s</td> \n"
2151 " <td><a href=\"%s%s\">%s</a></td> \n"
2152 " <td><a href=\"%s%s#%s.%d\">%d</a></td>\n"
2153 " </tr> \n")
2154 (if (string= TAG tag-started) "&nbsp;"
2155 (format "<a name=\"%s\">%s</a>" TAG TAG))
2156 file (or hfy-link-extn hfy-extn) file
2157 file (or hfy-link-extn hfy-extn) TAG line line))
2158 (setq tag-started TAG))))))
acca02b0
SM
2159 (insert "</table>\n")
2160 (insert (funcall hfy-page-footer filename))
2161 (and dstdir (cd dstdir))
2162 (set-visited-file-name index-file)
2163 index-buf) ))
2164
2165(defun hfy-prepare-index (srcdir dstdir)
30afcdff 2166 "Return a list of index buffer(s), as determined by `hfy-split-index'.
acca02b0
SM
2167SRCDIR and DSTDIR are the source and output directories respectively."
2168 (if (not hfy-split-index)
2169 (list (hfy-prepare-index-i srcdir dstdir hfy-index-file nil))
2170 (let ((stub-list nil)
2171 (cache-hash nil)
2172 (index-list nil)
2173 (cache-entry (assoc srcdir hfy-tags-cache)))
2174 (if (and cache-entry (setq cache-hash (cadr cache-entry)))
2175 (maphash
2176 (lambda (K V)
2177 (let ((stub (upcase (substring K 0 1))))
2178 (if (member stub stub-list)
2179 nil ;; seen this already: NOOP
2180 (setq
2181 stub-list (cons stub stub-list)
2182 index-list (cons (hfy-prepare-index-i srcdir
2183 dstdir
2184 hfy-index-file
2185 stub)
2186 index-list)) ))) cache-hash) ) index-list)))
2187
2188(defun hfy-prepare-tag-map (srcdir dstdir)
30afcdff
JB
2189 "Prepare the counterpart(s) to the index buffer(s) - a list of buffers
2190with the same structure, but listing (and linking to) instances of tags
2191\(as opposed to their definitions).\n
acca02b0 2192SRCDIR and DSTDIR are the source and output directories respectively.
30afcdff 2193See also `hfy-prepare-index', `hfy-split-index'."
acca02b0
SM
2194 (if (not hfy-split-index)
2195 (list (hfy-prepare-index-i srcdir
2196 dstdir
2197 hfy-instance-file
2198 nil
2199 hfy-tags-rmap))
2200 (let ((stub-list nil)
2201 (cache-hash nil)
2202 (index-list nil)
2203 (cache-entry (assoc srcdir hfy-tags-rmap)))
2204
2205 (if (and cache-entry (setq cache-hash (cadr cache-entry)))
2206 (maphash
2207 (lambda (K V)
2208 (let ((stub (upcase (substring K 0 1))))
2209 (if (member stub stub-list)
2210 nil ;; seen this already: NOOP
2211 (setq
2212 stub-list (cons stub stub-list)
2213 index-list (cons (hfy-prepare-index-i srcdir
2214 dstdir
2215 hfy-instance-file
2216 stub
2217 hfy-tags-rmap)
2218 index-list)) ))) cache-hash) ) index-list)))
2219
2220(defun hfy-subtract-maps (srcdir)
2221 "Internal function - strips definitions of tags from the instance map.
2222SRCDIR is the directory being \"published\".
30afcdff 2223See also `hfy-tags-cache', `hfy-tags-rmap'."
acca02b0
SM
2224 (let ((new-list nil)
2225 (old-list nil)
2226 (def-list nil)
2227 (exc-list nil)
2228 (fwd-map (cadr (assoc srcdir hfy-tags-cache)))
2229 (rev-map (cadr (assoc srcdir hfy-tags-rmap )))
2230 (taglist (cadr (assoc srcdir hfy-tags-sortl))))
72fe6b25
SM
2231 (dolist (TAG taglist)
2232 (setq def-list (gethash TAG fwd-map)
2233 old-list (gethash TAG rev-map)
2234 exc-list (mapcar (lambda (P) (list (car P) (cadr P))) def-list)
2235 new-list nil)
2236 (dolist (P old-list)
2237 (or (member (list (car P) (cadr P)) exc-list)
2238 (push P new-list)))
2239 (puthash TAG new-list rev-map))))
acca02b0
SM
2240
2241(defun htmlfontify-run-etags (srcdir)
2242 "Load the etags cache for SRCDIR.
30afcdff 2243See also `hfy-load-tags-cache'."
acca02b0
SM
2244 (interactive "D source directory: ")
2245 (setq srcdir (directory-file-name srcdir))
2246 (hfy-load-tags-cache srcdir))
2247
2248;;(defun hfy-test-read-args (foo bar)
2249;; (interactive "D source directory: \nD target directory: ")
2250;; (message "foo: %S\nbar: %S" foo bar))
2251
2252(defun hfy-save-kill-buffers (buffer-list &optional dstdir)
72fe6b25
SM
2253 (dolist (B buffer-list)
2254 (set-buffer B)
2255 (and dstdir (file-directory-p dstdir) (cd dstdir))
2256 (save-buffer)
2257 (kill-buffer B)))
acca02b0 2258
0433ffa6 2259;;;###autoload
acca02b0
SM
2260(defun htmlfontify-copy-and-link-dir (srcdir dstdir &optional f-ext l-ext)
2261 "Trawl SRCDIR and write fontified-and-hyperlinked output in DSTDIR.
2262F-EXT and L-EXT specify values for `hfy-extn' and `hfy-link-extn'.\n
2263You may also want to set `hfy-page-header' and `hfy-page-footer'."
2264 (interactive "D source directory: \nD output directory: ")
2265 ;;(message "htmlfontify-copy-and-link-dir")
2266 (setq srcdir (directory-file-name srcdir))
2267 (setq dstdir (directory-file-name dstdir))
2268 (let ((source-files "SETME: list of source files, relative to srcdir")
2269 (tr-cache (assoc srcdir hfy-tags-rmap))
2270 (hfy-extn (or f-ext ".html"))
2271 (hfy-link-extn (or l-ext ".html")))
2272 ;; oops, forgot to load etags for srcdir:
2273 (if tr-cache nil
2274 (message "autoload of tags cache")
2275 (hfy-load-tags-cache srcdir)
2276 (setq tr-cache (assoc srcdir hfy-tags-rmap)))
2277 ;; clear out the old cache:
2278 (clrhash (cadr tr-cache))
2279 (hfy-make-directory dstdir)
2280 (setq source-files (hfy-list-files srcdir))
72fe6b25
SM
2281 (dolist (file source-files)
2282 (hfy-copy-and-fontify-file srcdir dstdir file))
acca02b0
SM
2283 (hfy-subtract-maps srcdir)
2284 (hfy-save-kill-buffers (hfy-prepare-index srcdir dstdir) dstdir)
2285 (hfy-save-kill-buffers (hfy-prepare-tag-map srcdir dstdir) dstdir) ))
2286
2287;; name of the init file we want:
2288(defun hfy-initfile ()
2289 "Return the expected location of the htmlfontify specific init/custom file."
2290 (let* ((file (or (getenv "HFY_INITFILE") ".hfy.el")))
2291 (expand-file-name file "~") ))
2292
2293
2294;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2295;; incomplete as yet : transfer hook settings to hfy init file:
2296;; (defalias 'hfy-set-hooks 'custom-set-variables)
2297
2298;; (defun hfy-pp-hook (H)
2299;; (and (string-match "-hook$" (symbol-name H))
2300;; (boundp H)
2301;; (symbol-value H)
2302;; (insert (format "\n '(%S %S)" H (symbol-value H)))
2303;; )
2304;; )
2305
2306;; (defun hfy-save-hooks ()
2307;; (let ((custom-file (hfy-initfile)))
2308;; (custom-save-delete 'hfy-set-hooks)
2309;; (let ((standard-output (current-buffer)))
2310;; (princ "(hfy-set-hooks\n;;auto-generated, only one copy allowed\n")
2311;; (mapatoms 'hfy-pp-hook)
2312;; (insert "\n)")
2313;; )
2314;; )
2315;; )
2316;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2317
2318(defalias 'hfy-init-progn 'progn)
2319
2320(defun hfy-save-initvar (sym)
2321 (princ (format "(setq %s\n '" sym))
2322 (pp (symbol-value sym))
2323 (princ ")\n"))
2324
2325(defun htmlfontify-save-initfile ()
2326 "Save the htmlfontify settings to the htmlfontify init file."
2327 (interactive)
2328 (let* ((start-pos nil)
2329 (custom-file (hfy-initfile))
2330 (standard-output (find-file-noselect custom-file 'nowarn)))
2331 (save-excursion
2332 (custom-save-delete 'hfy-init-progn)
2333 (setq start-pos (point))
2334 (princ "(hfy-init-progn\n;;auto-generated, only one copy allowed\n")
72fe6b25
SM
2335 ;; FIXME: This saving&restoring of global customization
2336 ;; variables can interfere with other customization settings for
2337 ;; those vars (in .emacs or in Customize).
acca02b0 2338 (mapc 'hfy-save-initvar
72fe6b25 2339 '(auto-mode-alist interpreter-mode-alist))
acca02b0
SM
2340 (princ ")\n")
2341 (indent-region start-pos (point) nil))
2342 (custom-save-all) ))
2343
2344(defun htmlfontify-load-initfile ()
2345 "Load the htmlfontify specific init/custom file."
2346 (interactive)
2347 (let ((file (hfy-initfile)))
2348 (load file 'NOERROR nil nil) ))
2349
cbcfee6e
GM
2350\f
2351;;;### (autoloads (hfy-fallback-colour-values htmlfontify-load-rgb-file)
4de81ee0 2352;;;;;; "hfy-cmap" "hfy-cmap.el" "7e622e4b131ea5efbe9d258f719822d6")
cbcfee6e
GM
2353;;; Generated autoloads from hfy-cmap.el
2354
2355(autoload 'htmlfontify-load-rgb-file "hfy-cmap" "\
2356Load an X11 style rgb.txt FILE.
2357Search `hfy-rgb-load-path' if FILE is not specified.
2358Loads the variable `hfy-rgb-txt-colour-map', which is used by
2359`hfy-fallback-colour-values'.
2360
2361\(fn &optional FILE)" t nil)
2362
2363(autoload 'hfy-fallback-colour-values "hfy-cmap" "\
2364Use a fallback method for obtaining the rgb values for a color.
2365
2366\(fn COLOUR-STRING)" nil nil)
2367
2368;;;***
2369\f
2370
acca02b0 2371(provide 'htmlfontify)
acca02b0 2372
964f5b2b 2373;; arch-tag: 944e5e63-c81d-4baa-a82a-0275f9c30e61
cbcfee6e 2374;;; htmlfontify.el ends here