;" or "" (mapconcat's mapper
;; must always return strings). It's only filled as characters are
;; encountered, so that in a buffer with e.g. French text, it will
;; only ever contain French accented characters as keys. It's cleared
;; on each entry to htmlize-buffer-1 to allow modifications of
;; `htmlize-convert-nonascii-to-entities' to take effect.
(defvar htmlize-extended-character-cache (make-hash-table :test 'eq))
(defun htmlize-protect-string (string)
"HTML-protect string, escaping HTML metacharacters and I18N chars."
;; Only protecting strings that actually contain unsafe or non-ASCII
;; chars removes a lot of unnecessary consing.
(if (not (string-match "[^\r\n\t -%'-;=?-~]" string))
string
(mapconcat (lambda (char)
(cond
((< char 128)
;; ASCII: use htmlize-basic-character-table.
(aref htmlize-basic-character-table char))
((gethash char htmlize-extended-character-cache)
;; We've already seen this char; return the cached
;; string.
)
((not htmlize-convert-nonascii-to-entities)
;; If conversion to entities is not desired, always
;; copy the char literally.
(setf (gethash char htmlize-extended-character-cache)
(char-to-string char)))
((< char 256)
;; Latin 1: no need to call encode-char.
(setf (gethash char htmlize-extended-character-cache)
(format "%d;" char)))
((and (fboundp 'encode-char)
;; Have to check: encode-char fails for Arabic
;; and possibly other chars.
(encode-char char 'ucs))
(setf (gethash char htmlize-extended-character-cache)
(format "%d;" (encode-char char 'ucs))))
(t
;; encode-char doesn't work for this char. Copy it
;; unchanged and hope for the best.
(setf (gethash char htmlize-extended-character-cache)
(char-to-string char)))))
string "")))
(defun htmlize-buffer-substring-no-invisible (beg end)
;; Like buffer-substring-no-properties, but don't copy invisible
;; parts of the region.
(let ((pos beg)
visible-list invisible next-change)
;; Iterate over the changes in the `invisible' property and filter
;; out the portions where it's non-nil, i.e. where the text is
;; invisible.
(while (< pos end)
(setq invisible (get-char-property pos 'invisible)
next-change (htmlize-next-change pos 'invisible end))
(unless invisible
(push (buffer-substring-no-properties pos next-change)
visible-list))
(setq pos next-change))
(if (= (length visible-list) 1)
;; If VISIBLE-LIST consists of only one element, return it
;; without concatenation. This avoids additional consing in
;; regions without any invisible text.
(car visible-list)
(apply #'concat (nreverse visible-list)))))
(defconst htmlize-tab-spaces
;; A table of strings with spaces. (aref htmlize-tab-spaces 5) is
;; like (make-string 5 ?\ ), except it doesn't cons.
(let ((v (make-vector 32 nil)))
(dotimes (i (length v))
(setf (aref v i) (make-string i ?\ )))
v))
(defun htmlize-untabify (text start-column)
"Untabify TEXT, assuming it starts at START-COLUMN."
(let ((column start-column)
(last-match 0)
(chunk-start 0)
chunks match-pos tab-size)
(while (string-match "[\t\n]" text last-match)
(setq match-pos (match-beginning 0))
(cond ((eq (aref text match-pos) ?\t)
;; Encountered a tab: create a chunk of text followed by
;; the expanded tab.
(push (substring text chunk-start match-pos) chunks)
;; Increase COLUMN by the length of the text we've
;; skipped since last tab or newline. (Encountering
;; newline resets it.)
(incf column (- match-pos last-match))
;; Calculate tab size based on tab-width and COLUMN.
(setq tab-size (- tab-width (% column tab-width)))
;; Expand the tab.
(push (aref htmlize-tab-spaces tab-size) chunks)
(incf column tab-size)
(setq chunk-start (1+ match-pos)))
(t
;; Reset COLUMN at beginning of line.
(setq column 0)))
(setq last-match (1+ match-pos)))
;; If no chunks have been allocated, it means there have been no
;; tabs to expand. Return TEXT unmodified.
(if (null chunks)
text
(when (< chunk-start (length text))
;; Push the remaining chunk.
(push (substring text chunk-start) chunks))
;; Generate the output from the available chunks.
(apply #'concat (nreverse chunks)))))
(defun htmlize-despam-address (string)
"Replace every occurrence of '@' in STRING with @.
`htmlize-make-hyperlinks' uses this to spam-protect mailto links
without modifying their meaning."
;; Suggested by Ville Skytta.
(while (string-match "@" string)
(setq string (replace-match "@" nil t string)))
string)
(defun htmlize-make-hyperlinks ()
"Make hyperlinks in HTML."
;; Function originally submitted by Ville Skytta. Rewritten by
;; Hrvoje Niksic, then modified by Ville Skytta and Hrvoje Niksic.
(goto-char (point-min))
(while (re-search-forward
"<\\(\\(mailto:\\)?\\([-=+_.a-zA-Z0-9]+@[-_.a-zA-Z0-9]+\\)\\)>"
nil t)
(let ((address (match-string 3))
(link-text (match-string 1)))
(delete-region (match-beginning 0) (match-end 0))
(insert "<"
(htmlize-despam-address link-text)
">")))
(goto-char (point-min))
(while (re-search-forward "<\\(\\(URL:\\)?\\([a-zA-Z]+://[^;]+\\)\\)>"
nil t)
(let ((url (match-string 3))
(link-text (match-string 1)))
(delete-region (match-beginning 0) (match-end 0))
(insert "<" link-text ">"))))
;; Tests for htmlize-make-hyperlinks:
;;
;;
;;
;;
;;
;;
;;; Color handling.
(if (fboundp 'locate-file)
(defalias 'htmlize-locate-file 'locate-file)
(defun htmlize-locate-file (file path)
(dolist (dir path nil)
(when (file-exists-p (expand-file-name file dir))
(return (expand-file-name file dir))))))
(defvar htmlize-x-library-search-path
'("/usr/X11R6/lib/X11/"
"/usr/X11R5/lib/X11/"
"/usr/lib/X11R6/X11/"
"/usr/lib/X11R5/X11/"
"/usr/local/X11R6/lib/X11/"
"/usr/local/X11R5/lib/X11/"
"/usr/local/lib/X11R6/X11/"
"/usr/local/lib/X11R5/X11/"
"/usr/X11/lib/X11/"
"/usr/lib/X11/"
"/usr/local/lib/X11/"
"/usr/X386/lib/X11/"
"/usr/x386/lib/X11/"
"/usr/XFree86/lib/X11/"
"/usr/unsupported/lib/X11/"
"/usr/athena/lib/X11/"
"/usr/local/x11r5/lib/X11/"
"/usr/lpp/Xamples/lib/X11/"
"/usr/openwin/lib/X11/"
"/usr/openwin/share/lib/X11/"))
(defun htmlize-get-color-rgb-hash (&optional rgb-file)
"Return a hash table mapping X color names to RGB values.
The keys in the hash table are X11 color names, and the values are the
#rrggbb RGB specifications, extracted from `rgb.txt'.
If RGB-FILE is nil, the function will try hard to find a suitable file
in the system directories.
If no rgb.txt file is found, return nil."
(let ((rgb-file (or rgb-file (htmlize-locate-file
"rgb.txt"
htmlize-x-library-search-path)))
(hash nil))
(when rgb-file
(with-temp-buffer
(insert-file-contents rgb-file)
(setq hash (make-hash-table :test 'equal))
(while (not (eobp))
(cond ((looking-at "^\\s-*\\([!#]\\|$\\)")
;; Skip comments and empty lines.
)
((looking-at
"[ \t]*\\([0-9]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\(.*\\)")
(setf (gethash (downcase (match-string 4)) hash)
(format "#%02x%02x%02x"
(string-to-number (match-string 1))
(string-to-number (match-string 2))
(string-to-number (match-string 3)))))
(t
(error
"Unrecognized line in %s: %s"
rgb-file
(buffer-substring (point) (progn (end-of-line) (point))))))
(forward-line 1))))
hash))
;; Compile the RGB map when loaded. On systems where rgb.txt is
;; missing, the value of the variable will be nil, and rgb.txt will
;; not be used.
(defvar htmlize-color-rgb-hash (htmlize-get-color-rgb-hash))
;;; Face handling.
(defun htmlize-face-specifies-property (face prop)
;; Return t if face specifies PROP, as opposed to it being inherited
;; from the default face. The problem with e.g.
;; `face-foreground-instance' is that it returns an instance for
;; EVERY face because every face inherits from the default face.
;; However, we'd like htmlize-face-{fore,back}ground to return nil
;; when called with a face that doesn't specify its own foreground
;; or background.
(or (eq face 'default)
(assq 'global (specifier-spec-list (face-property face prop)))))
(defun htmlize-face-color-internal (face fg)
;; Used only under GNU Emacs. Return the color of FACE, but don't
;; return "unspecified-fg" or "unspecified-bg". If the face is
;; `default' and the color is unspecified, look up the color in
;; frame parameters.
(let ((color (if fg (face-foreground face) (face-background face))))
(when (and (eq face 'default) (null color))
(setq color (cdr (assq (if fg 'foreground-color 'background-color)
(frame-parameters)))))
(when (or (equal color "unspecified-fg")
(equal color "unspecified-bg"))
(setq color nil))
(when (and (eq face 'default)
(null color))
;; Assuming black on white doesn't seem right, but I can't think
;; of anything better to do.
(setq color (if fg "black" "white")))
color))
(defun htmlize-face-foreground (face)
;; Return the name of the foreground color of FACE. If FACE does
;; not specify a foreground color, return nil.
(cond (htmlize-running-xemacs
;; XEmacs.
(and (htmlize-face-specifies-property face 'foreground)
(color-instance-name (face-foreground-instance face))))
(t
;; GNU Emacs.
(htmlize-face-color-internal face t))))
(defun htmlize-face-background (face)
;; Return the name of the background color of FACE. If FACE does
;; not specify a background color, return nil.
(cond (htmlize-running-xemacs
;; XEmacs.
(and (htmlize-face-specifies-property face 'background)
(color-instance-name (face-background-instance face))))
(t
;; GNU Emacs.
(htmlize-face-color-internal face nil))))
;; Convert COLOR to the #RRGGBB string. If COLOR is already in that
;; format, it's left unchanged.
(defun htmlize-color-to-rgb (color)
(let ((rgb-string nil))
(cond ((null color)
;; Ignore nil COLOR because it means that the face is not
;; specifying any color. Hence (htmlize-color-to-rgb nil)
;; returns nil.
)
((string-match "\\`#" color)
;; The color is alredy in #rrggbb format.
(setq rgb-string color))
((and htmlize-use-rgb-txt
htmlize-color-rgb-hash)
;; Use of rgb.txt is requested, and it's available on the
;; system. Use it.
(setq rgb-string (gethash (downcase color) htmlize-color-rgb-hash)))
(t
;; We're getting the RGB components from Emacs.
(let ((rgb
;; Here I cannot conditionalize on (fboundp ...)
;; because ps-print under some versions of GNU Emacs
;; defines its own dummy version of
;; `color-instance-rgb-components'.
(if htmlize-running-xemacs
(mapcar (lambda (arg)
(/ arg 256))
(color-instance-rgb-components
(make-color-instance color)))
(mapcar (lambda (arg)
(/ arg 256))
(x-color-values color)))))
(when rgb
(setq rgb-string (apply #'format "#%02x%02x%02x" rgb))))))
;; If RGB-STRING is still nil, it means the color cannot be found,
;; for whatever reason. In that case just punt and return COLOR.
;; Most browsers support a decent set of color names anyway.
(or rgb-string color)))
;; We store the face properties we care about into an
;; `htmlize-fstruct' type. That way we only have to analyze face
;; properties, which can be time consuming, once per each face. The
;; mapping between Emacs faces and htmlize-fstructs is established by
;; htmlize-make-face-map. The name "fstruct" refers to variables of
;; type `htmlize-fstruct', while the term "face" is reserved for Emacs
;; faces.
(defstruct htmlize-fstruct
foreground ; foreground color, #rrggbb
background ; background color, #rrggbb
boldp ; whether face is bold
italicp ; whether face is italic
underlinep ; whether face is underlined
overlinep ; whether face is overlined
strikep ; whether face is striked through
css-name ; CSS name of face
)
(defun htmlize-face-emacs21-attr (fstruct attr value)
;; For ATTR and VALUE, set the equivalent value in FSTRUCT.
(case attr
(:foreground
(setf (htmlize-fstruct-foreground fstruct) (htmlize-color-to-rgb value)))
(:background
(setf (htmlize-fstruct-background fstruct) (htmlize-color-to-rgb value)))
(:weight
(when (string-match (symbol-name value) "bold")
(setf (htmlize-fstruct-boldp fstruct) t)))
(:slant
(setf (htmlize-fstruct-italicp fstruct) (or (eq value 'italic)
(eq value 'oblique))))
(:bold
(setf (htmlize-fstruct-boldp fstruct) value))
(:italic
(setf (htmlize-fstruct-italicp fstruct) value))
(:underline
(setf (htmlize-fstruct-underlinep fstruct) value))
(:overline
(setf (htmlize-fstruct-overlinep fstruct) value))
(:strike-through
(setf (htmlize-fstruct-strikep fstruct) value))))
(defun htmlize-face-to-fstruct (face)
"Convert Emacs face FACE to fstruct."
(let ((fstruct (make-htmlize-fstruct
:foreground (htmlize-color-to-rgb
(htmlize-face-foreground face))
:background (htmlize-color-to-rgb
(htmlize-face-background face)))))
(cond (htmlize-running-xemacs
;; XEmacs doesn't provide a way to detect whether a face is
;; bold or italic, so we need to examine the font instance.
;; #### This probably doesn't work under MS Windows and/or
;; GTK devices. I'll need help with those.
(let* ((font-instance (face-font-instance face))
(props (font-instance-properties font-instance)))
(when (equalp (cdr (assq 'WEIGHT_NAME props)) "bold")
(setf (htmlize-fstruct-boldp fstruct) t))
(when (or (equalp (cdr (assq 'SLANT props)) "i")
(equalp (cdr (assq 'SLANT props)) "o"))
(setf (htmlize-fstruct-italicp fstruct) t))
(setf (htmlize-fstruct-strikep fstruct)
(face-strikethru-p face))
(setf (htmlize-fstruct-underlinep fstruct)
(face-underline-p face))))
((fboundp 'face-attribute)
;; GNU Emacs 21.
(dolist (attr '(:weight :slant :underline :overline :strike-through))
(let ((value (face-attribute face attr)))
(when (and value (not (eq value 'unspecified)))
(htmlize-face-emacs21-attr fstruct attr value)))))
(t
;; Older GNU Emacs. Some of these functions are only
;; available under Emacs 20+, hence the guards.
(when (fboundp 'face-bold-p)
(setf (htmlize-fstruct-boldp fstruct) (face-bold-p face)))
(when (fboundp 'face-italic-p)
(setf (htmlize-fstruct-italicp fstruct) (face-italic-p face)))
(setf (htmlize-fstruct-underlinep fstruct)
(face-underline-p face))))
;; Generate the css-name property. Emacs places no restrictions
;; on the names of symbols that represent faces -- any characters
;; may be in the name, even ^@. We try hard to beat the face name
;; into shape, both esthetically and according to CSS1 specs.
(setf (htmlize-fstruct-css-name fstruct)
(let ((name (downcase (symbol-name face))))
(when (string-match "\\`font-lock-" name)
;; Change font-lock-FOO-face to FOO.
(setq name (replace-match "" t t name)))
(when (string-match "-face\\'" name)
;; Drop the redundant "-face" suffix.
(setq name (replace-match "" t t name)))
(while (string-match "[^-a-zA-Z0-9]" name)
;; Drop the non-alphanumerics.
(setq name (replace-match "X" t t name)))
(when (string-match "\\`[-0-9]" name)
;; CSS identifiers may not start with a digit.
(setq name (concat "X" name)))
;; After these transformations, the face could come
;; out empty.
(when (equal name "")
(setq name "face"))
;; Apply the prefix.
(setq name (concat htmlize-css-name-prefix name))
name))
fstruct))
;; GNU Emacs 20+ supports attribute lists in `face' properties. For
;; example, you can use `(:foreground "red" :weight bold)' as an
;; overlay's "face", or you can even use a list of such lists, etc.
;; We call those "attrlists".
;;
;; htmlize supports attrlist by converting them to fstructs, the same
;; as with regular faces.
(defun htmlize-attrlist-to-fstruct (attrlist)
;; Like htmlize-face-to-fstruct, but accepts an ATTRLIST as input.
(let ((fstruct (make-htmlize-fstruct)))
(cond ((eq (car attrlist) 'foreground-color)
;; ATTRLIST is (foreground-color . COLOR)
(setf (htmlize-fstruct-foreground fstruct)
(htmlize-color-to-rgb (cdr attrlist))))
((eq (car attrlist) 'background-color)
;; ATTRLIST is (background-color . COLOR)
(setf (htmlize-fstruct-background fstruct)
(htmlize-color-to-rgb (cdr attrlist))))
(t
;; ATTRLIST is a plist.
(while attrlist
(let ((attr (pop attrlist))
(value (pop attrlist)))
(when (and value (not (eq value 'unspecified)))
(htmlize-face-emacs21-attr fstruct attr value))))))
(setf (htmlize-fstruct-css-name fstruct) "ATTRLIST")
fstruct))
(defun htmlize-face-list-p (face-prop)
"Return non-nil if FACE-PROP is a list of faces, nil otherwise."
;; If not for attrlists, this would return (listp face-prop). This
;; way we have to be more careful because some an attrlist is also a
;; list!
(cond
((eq face-prop nil)
;; FACE-PROP being nil means empty list (no face), so return t.
t)
((symbolp face-prop)
;; A symbol other than nil means that it's only one face, so return
;; nil.
nil)
((not (consp face-prop))
;; Huh? Not a symbol or cons -- treat it as a single element.
nil)
(t
;; We know that FACE-PROP is a cons: check whether it looks like an
;; ATTRLIST.
(let* ((car (car face-prop))
(attrlist-p (and (symbolp car)
(or (eq car 'foreground-color)
(eq car 'background-color)
(eq (aref (symbol-name car) 0) ?:)))))
;; If FACE-PROP is not an ATTRLIST, it means it's a list of
;; faces.
(not attrlist-p)))))
(defun htmlize-make-face-map (faces)
;; Return a hash table mapping Emacs faces to htmlize's fstructs.
;; The keys are either face symbols or attrlists, so the test
;; function must be `equal'.
(let ((face-map (make-hash-table :test 'equal))
css-names)
(dolist (face faces)
(unless (gethash face face-map)
;; Haven't seen FACE yet; convert it to an fstruct and cache
;; it.
(let ((fstruct (if (symbolp face)
(htmlize-face-to-fstruct face)
(htmlize-attrlist-to-fstruct face))))
(setf (gethash face face-map) fstruct)
(let* ((css-name (htmlize-fstruct-css-name fstruct))
(new-name css-name)
(i 0))
;; Uniquify the face's css-name by using NAME-1, NAME-2,
;; etc.
(while (member new-name css-names)
(setq new-name (format "%s-%s" css-name (incf i))))
(unless (equal new-name css-name)
(setf (htmlize-fstruct-css-name fstruct) new-name))
(push new-name css-names)))))
face-map))
(defun htmlize-faces-in-buffer ()
"Return a list of faces used in the current buffer.
Under XEmacs, this returns the set of faces specified by the extents
with the `face' property. (This covers text properties as well.) Under
GNU Emacs, it returns the set of faces specified by the `face' text
property and by buffer overlays that specify `face'."
(let (faces)
;; Testing for (fboundp 'map-extents) doesn't work because W3
;; defines `map-extents' under FSF.
(if htmlize-running-xemacs
(let (face-prop)
(map-extents (lambda (extent ignored)
(setq face-prop (extent-face extent)
;; FACE-PROP can be a face or a list of
;; faces.
faces (if (listp face-prop)
(union face-prop faces)
(adjoin face-prop faces)))
nil)
nil
;; Specify endpoints explicitly to respect
;; narrowing.
(point-min) (point-max) nil nil 'face))
;; FSF Emacs code.
;; Faces used by text properties.
(let ((pos (point-min)) face-prop next)
(while (< pos (point-max))
(setq face-prop (get-text-property pos 'face)
next (or (next-single-property-change pos 'face) (point-max)))
;; FACE-PROP can be a face/attrlist or a list thereof.
(setq faces (if (htmlize-face-list-p face-prop)
(union face-prop faces :test 'equal)
(adjoin face-prop faces :test 'equal)))
(setq pos next)))
;; Faces used by overlays.
(dolist (overlay (overlays-in (point-min) (point-max)))
(let ((face-prop (overlay-get overlay 'face)))
;; FACE-PROP can be a face/attrlist or a list thereof.
(setq faces (if (htmlize-face-list-p face-prop)
(union face-prop faces :test 'equal)
(adjoin face-prop faces :test 'equal))))))
faces))
;; htmlize-faces-at-point returns the faces in use at point. The
;; faces are sorted by increasing priority, i.e. the last face takes
;; precedence.
;;
;; Under XEmacs, this returns all the faces in all the extents at
;; point. Under GNU Emacs, this returns all the faces in the `face'
;; property and all the faces in the overlays at point.
(cond (htmlize-running-xemacs
(defun htmlize-faces-at-point ()
(let (extent list face-prop)
(while (setq extent (extent-at (point) nil 'face extent))
(setq face-prop (extent-face extent))
(setq list (if (listp face-prop)
(nconc (reverse face-prop) list)
(cons face-prop list))))
;; No need to reverse the list: PUSH has already
;; constructed it in the reverse display order.
list)))
(t
(defun htmlize-faces-at-point ()
(let (all-faces)
;; Faces from text properties.
(let ((face-prop (get-text-property (point) 'face)))
(setq all-faces (if (htmlize-face-list-p face-prop)
(reverse face-prop)
(list face-prop))))
;; Faces from overlays.
(let ((overlays
;; Collect overlays at point that specify `face'.
(delete-if-not (lambda (o)
(overlay-get o 'face))
(overlays-at (point))))
list face-prop)
;; Sort the overlays so the smaller (more specific) ones
;; come later. The number of overlays at each one
;; position should be very small, so the sort shouldn't
;; slow things down.
(setq overlays (sort* overlays
;; Sort by ascending...
#'<
;; ...overlay size.
:key (lambda (o)
(- (overlay-end o)
(overlay-start o)))))
(dolist (overlay overlays)
(setq face-prop (overlay-get overlay 'face))
(setq list (if (htmlize-face-list-p face-prop)
(nconc (reverse face-prop) list)
(cons face-prop list))))
(setq all-faces (nconc all-faces list)))))))
;; htmlize supports generating HTML in two several fundamentally
;; different ways, one with the use of CSS and nested tags, and
;; the other with the use of the old tags. Rather than adding
;; a bunch of if's to many places, we take a semi-OO approach.
;; `htmlize-buffer-1' calls a number of "methods", which indirect to
;; the functions that depend on `htmlize-output-type'. The currently
;; used methods are `doctype', `insert-head', `body-tag', and
;; `insert-text'. Not all output types define all methods.
;;
;; Methods are called either with (htmlize-method METHOD ARGS...)
;; special form, or by accessing the function with
;; (htmlize-method-function 'METHOD) and calling (funcall FUNCTION).
;; The latter form is useful in tight loops because `htmlize-method'
;; conses.
;;
;; Currently defined output types are `css' and `font'.
(defmacro htmlize-method (method &rest args)
;; Expand to (htmlize-TYPE-METHOD ...ARGS...). TYPE is the value of
;; `htmlize-output-type' at run time.
`(funcall (htmlize-method-function ',method) ,@args))
(defun htmlize-method-function (method)
;; Return METHOD's function definition for the current output type.
;; The returned object can be safely funcalled.
(let ((sym (intern (format "htmlize-%s-%s" htmlize-output-type method))))
(indirect-function (if (fboundp sym) sym 'ignore))))
;;; CSS based output support.
(defun htmlize-css-doctype ()
nil ; no doc-string
"")
;; Internal function; not a method.
(defun htmlize-css-specs (fstruct)
(let (result)
(when (htmlize-fstruct-foreground fstruct)
(push (format "color: %s;" (htmlize-fstruct-foreground fstruct))
result))
(when (htmlize-fstruct-background fstruct)
(push (format "background-color: %s;"
(htmlize-fstruct-background fstruct))
result))
(when (htmlize-fstruct-boldp fstruct)
(push "font-weight: bold;" result))
(when (htmlize-fstruct-italicp fstruct)
(push "font-style: italic;" result))
(when (htmlize-fstruct-underlinep fstruct)
(push "text-decoration: underline;" result))
(when (htmlize-fstruct-overlinep fstruct)
(push "text-decoration: overline;" result))
(when (htmlize-fstruct-strikep fstruct)
(push "text-decoration: line-through;" result))
(nreverse result)))
(defun htmlize-css-insert-head (buffer-faces face-map)
(insert " \n"))
(defun htmlize-css-insert-text (text fstruct-list buffer)
;; Insert TEXT colored with FACES into BUFFER. In CSS mode, this is
;; easy: just nest the text in one tag for each
;; face in FSTRUCT-LIST.
(dolist (fstruct fstruct-list)
(princ "" buffer))
(princ text buffer)
(dolist (fstruct fstruct-list)
(ignore fstruct) ; shut up the byte-compiler
(princ "" buffer)))
;;; `font' tag based output support.
(defun htmlize-font-doctype ()
nil ; no doc-string
;; According to DTDs published by the W3C, it is illegal to embed
;; in . This makes sense in general, but is bad for
;; htmlize's intended usage of to specify the document color.
;; To make generated HTML legal, htmlize.el used to specify the SGML
;; declaration of "HTML Pro" DTD here. HTML Pro aka Silmaril DTD
;; was a project whose goal was to produce a DTD that would
;; encompass all the incompatible HTML extensions procured by
;; Netscape, MSIE, and other players in the field. Apparently the
;; project got abandoned, the last available version being "Draft 0
;; Revision 11" from January 1997, as documented at
;; .
;; Since by now (2001) HTML Pro is remembered by none but the most
;; die-hard early-web-days nostalgics and used by not even them,
;; there is no use in specifying it. So we return the standard HTML
;; 4.0 declaration, which makes generated HTML technically illegal.
;; If you have a problem with that, use the `css' generation engine
;; which I believe creates fully conformant HTML.
""
;; Now-abandoned HTML Pro declaration.
;""
)
(defun htmlize-font-body-tag (face-map)
(let ((fstruct (gethash 'default face-map)))
(format ""
(htmlize-fstruct-foreground fstruct)
(htmlize-fstruct-background fstruct))))
(defun htmlize-font-insert-text (text fstruct-list buffer)
;; In `font' mode, we use the traditional HTML means of altering
;; presentation: tag for colors, for bold, for
;; underline, and for strike-through.
(let (bold italic underline strike fg)
;; Merge the face attributes.
(dolist (fstruct fstruct-list)
;; A non-null boolean attribute in any face sets the attribute.
(and (htmlize-fstruct-boldp fstruct) (setq bold t))
(and (htmlize-fstruct-italicp fstruct) (setq italic t))
(and (htmlize-fstruct-underlinep fstruct) (setq underline t))
(and (htmlize-fstruct-strikep fstruct) (setq strike t))
;; The foreground/background of the last face in the list wins.
(and (htmlize-fstruct-foreground fstruct)
(setq fg (htmlize-fstruct-foreground fstruct))))
;; Generate the markup that reflects the merged attributes.
(princ (concat
(and fg (format "" fg))
(and bold "")
(and italic "")
(and underline "")
(and strike ""))
buffer)
;; Print the text.
(princ text buffer)
;; Close the tags we've opened.
(princ (concat
(and strike "")
(and underline "")
(and italic "")
(and bold "")
(and fg ""))
buffer)))
(defun htmlize-buffer-1 ()
;; Internal function; don't call it from outside this file. Htmlize
;; current buffer, writing the resulting HTML to a new buffer, and
;; return it. Unlike htmlize-buffer, this doesn't change current
;; buffer or use switch-to-buffer.
(save-excursion
;; Protect against the hook changing the current buffer.
(save-excursion
(run-hooks 'htmlize-before-hook))
;; Convince font-lock support modes to fontify the entire buffer
;; in advance.
(htmlize-ensure-fontified)
(clrhash htmlize-extended-character-cache)
(let* ((buffer-faces (htmlize-faces-in-buffer))
(face-map (htmlize-make-face-map (adjoin 'default buffer-faces)))
;; Generate the new buffer. It's important that it inherits
;; default-directory from the current buffer.
(htmlbuf (generate-new-buffer (if (buffer-file-name)
(htmlize-make-file-name
(file-name-nondirectory
(buffer-file-name)))
"*html*")))
(title (if (buffer-file-name)
(file-name-nondirectory (buffer-file-name))
(buffer-name))))
;; Initialize HTMLBUF and insert the HTML prolog.
(with-current-buffer htmlbuf
(buffer-disable-undo)
(insert (htmlize-method doctype) ?\n
(format "\n"
htmlize-version htmlize-output-type)
"\n \n"
" " (htmlize-protect-string title) " \n"
(if htmlize-html-charset
(format (concat " \n")
htmlize-html-charset)
"")
htmlize-head-tags)
(htmlize-method insert-head buffer-faces face-map)
(insert " "
"\n "
(or (htmlize-method body-tag face-map)
"")
"\n \n"))
(let ((insert-text-method
;; Get the inserter method, so we can funcall it inside
;; the loop. Not calling `htmlize-method' in the loop
;; body yields a measurable speed increase.
(htmlize-method-function 'insert-text))
;; Declare variables used in loop body outside the loop
;; because it's faster to establish `let' bindings only
;; once.
next-change text face-list fstruct-list)
;; This loop traverses and reads the source buffer, appending
;; the resulting HTML to HTMLBUF with `princ'. This method is
;; fast because: 1) it doesn't require examining the text
;; properties char by char (htmlize-next-change is used to
;; move between runs with the same face), and 2) it doesn't
;; require buffer switches, which are slow in Emacs.
(goto-char (point-min))
(while (not (eobp))
(setq next-change (htmlize-next-change (point) 'face))
;; Get faces in use between (point) and NEXT-CHANGE, and
;; convert them to fstructs.
(setq face-list (htmlize-faces-at-point)
fstruct-list (delq nil (mapcar (lambda (f)
(gethash f face-map))
face-list)))
;; Extract buffer text, sans the invisible parts. Then
;; untabify it and escape the HTML metacharacters.
(setq text (htmlize-buffer-substring-no-invisible
(point) next-change))
(setq text (htmlize-untabify text (current-column)))
(setq text (htmlize-protect-string text))
;; Don't bother writing anything if there's no text (this
;; happens in invisible regions).
(when (> (length text) 0)
;; Insert the text, along with the necessary markup to
;; represent faces in FSTRUCT-LIST.
(funcall insert-text-method text fstruct-list htmlbuf))
(goto-char next-change)))
;; Insert the epilog.
(with-current-buffer htmlbuf
(insert "\n \n\n")
(when htmlize-generate-hyperlinks
(htmlize-make-hyperlinks))
(goto-char (point-min))
(when htmlize-html-major-mode
;; What sucks about this is that the minor modes, most notably
;; font-lock-mode, won't be initialized. Oh well.
(funcall htmlize-html-major-mode))
(run-hooks 'htmlize-after-hook)
(buffer-enable-undo))
htmlbuf)))
;; Utility functions.
(defun htmlize-ensure-fontified ()
;; If font-lock is being used, ensure that the "support" modes
;; actually fontify the buffer. If font-lock is not in use, we
;; don't care because, except in htmlize-file, we don't force
;; font-lock on the user.
(when (and (boundp 'font-lock-mode)
font-lock-mode)
;; In part taken from ps-print-ensure-fontified in GNU Emacs 21.
(cond
((and (boundp 'jit-lock-mode)
(symbol-value 'jit-lock-mode))
(jit-lock-fontify-now (point-min) (point-max)))
((and (boundp 'lazy-lock-mode)
(symbol-value 'lazy-lock-mode))
(lazy-lock-fontify-region (point-min) (point-max)))
((and (boundp 'lazy-shot-mode)
(symbol-value 'lazy-shot-mode))
;; lazy-shot is amazing in that it must *refontify* the region,
;; even if the whole buffer has already been fontified.
(lazy-shot-fontify-region (point-min) (point-max)))
;; There's also fast-lock, but we don't need to handle specially,
;; I think. fast-lock doesn't really defer fontification, it
;; just saves it to an external cache so it's not done twice.
)))
;;;###autoload
(defun htmlize-buffer (&optional buffer)
"Convert BUFFER to HTML, preserving colors and decorations.
The generated HTML is available in a new buffer, which is returned.
When invoked interactively, the new buffer is selected in the current
window. The title of the generated document will be set to the buffer's
file name or, if that's not available, to the buffer's name.
Note that htmlize doesn't fontify your buffers, it only uses the
decorations that are already present. If you don't set up font-lock or
something else to fontify your buffers, the resulting HTML will be
plain. Likewise, if you don't like the choice of colors, fix the mode
that created them, or simply alter the faces it uses."
(interactive)
(let ((htmlbuf (with-current-buffer (or buffer (current-buffer))
(htmlize-buffer-1))))
(when (interactive-p)
(switch-to-buffer htmlbuf))
htmlbuf))
;;;###autoload
(defun htmlize-region (beg end)
"Convert the region to HTML, preserving colors and decorations.
See `htmlize-buffer' for details."
(interactive "r")
;; Don't let zmacs region highlighting end up in HTML.
(when (fboundp 'zmacs-deactivate-region)
(zmacs-deactivate-region))
(let ((htmlbuf (save-restriction
(narrow-to-region beg end)
(htmlize-buffer-1))))
(when (interactive-p)
(switch-to-buffer htmlbuf))
htmlbuf))
(defun htmlize-make-file-name (file)
"Make an HTML file name from FILE.
In its default implementation, this simply appends `.html' to FILE.
This function is called by htmlize to create the buffer file name, and
by `htmlize-file' to create the target file name.
More elaborate transformations are conceivable, such as changing FILE's
extension to `.html' (\"file.c\" -> \"file.html\"). If you want them,
overload this function to do it and htmlize will comply."
(concat file ".html"))
;; Older implementation of htmlize-make-file-name that changes FILE's
;; extension to ".html".
;(defun htmlize-make-file-name (file)
; (let ((extension (file-name-extension file))
; (sans-extension (file-name-sans-extension file)))
; (if (or (equal extension "html")
; (equal extension "htm")
; (equal sans-extension ""))
; (concat file ".html")
; (concat sans-extension ".html"))))
;;;###autoload
(defun htmlize-file (file &optional target)
"Load FILE, fontify it, convert it to HTML, and save the result.
Contents of FILE are inserted into a temporary buffer, whose major mode
is set with `normal-mode' as appropriate for the file type. The buffer
is subsequently fontified with `font-lock' and converted to HTML. Note
that, unlike `htmlize-buffer', this function explicitly turns on
font-lock. If a form of highlighting other than font-lock is desired,
please use `htmlize-buffer' directly on buffers so highlighted.
Buffers currently visiting FILE are unaffected by this function. The
function does not change current buffer or move the point.
If TARGET is specified and names a directory, the resulting file will be
saved there instead of to FILE's directory. If TARGET is specified and
does not name a directory, it will be used as output file name."
(interactive (list (read-file-name
"HTML-ize file: "
nil nil nil (and (buffer-file-name)
(file-name-nondirectory
(buffer-file-name))))))
(let ((output-file (if (and target (not (file-directory-p target)))
target
(expand-file-name
(htmlize-make-file-name (file-name-nondirectory file))
(or target (file-name-directory file)))))
;; Try to prevent `find-file-noselect' from triggering
;; font-lock because we'll fontify explicitly below.
(font-lock-mode nil)
(font-lock-auto-fontify nil)
(global-font-lock-mode nil)
;; Ignore the size limit for the purposes of htmlization.
(font-lock-maximum-size nil)
;; Disable font-lock support modes. This will only work in
;; more recent Emacs versions, so htmlize-buffer-1 still needs
;; to call htmlize-ensure-fontified.
(font-lock-support-mode nil))
(with-temp-buffer
;; Insert FILE into the temporary buffer.
(insert-file-contents file)
;; Set the file name so normal-mode and htmlize-buffer-1 pick it
;; up. Restore it afterwards so with-temp-buffer's kill-buffer
;; doesn't complain about killing a modified buffer.
(let ((buffer-file-name file))
;; Set the major mode for the sake of font-lock.
(normal-mode)
(font-lock-mode 1)
(unless font-lock-mode
;; In GNU Emacs (font-lock-mode 1) doesn't force font-lock,
;; contrary to the documentation. This seems to work.
(font-lock-fontify-buffer))
;; htmlize the buffer and save the HTML.
(with-current-buffer (htmlize-buffer-1)
(unwind-protect
(progn
(run-hooks 'htmlize-file-hook)
(write-region (point-min) (point-max) output-file))
(kill-buffer (current-buffer)))))))
;; I haven't decided on a useful return value yet, so just return
;; nil.
nil)
;;;###autoload
(defun htmlize-many-files (files &optional target-directory)
"Convert FILES to HTML and save the corresponding HTML versions.
FILES should be a list of file names to convert. This function calls
`htmlize-file' on each file; see that function for details. When
invoked interactively, you are prompted for a list of files to convert,
terminated with RET.
If TARGET-DIRECTORY is specified, the HTML files will be saved to that
directory. Normally, each HTML file is saved to the directory of the
corresponding source file."
(interactive
(list
(let (list file)
;; Use empty string as DEFAULT because setting DEFAULT to nil
;; defaults to the directory name, which is not what we want.
(while (not (equal (setq file (read-file-name
"HTML-ize file (RET to finish): "
(and list (file-name-directory
(car list)))
"" t))
""))
(push file list))
(nreverse list))))
;; Verify that TARGET-DIRECTORY is indeed a directory. If it's a
;; file, htmlize-file will use it as target, and that doesn't make
;; sense.
(and target-directory
(not (file-directory-p target-directory))
(error "target-directory must name a directory: %s" target-directory))
(dolist (file files)
(htmlize-file file target-directory)))
;;;###autoload
(defun htmlize-many-files-dired (arg &optional target-directory)
"HTMLize dired-marked files."
(interactive "P")
(htmlize-many-files (dired-get-marked-files nil arg) target-directory))
(provide 'htmlize)
;;; htmlize.el ends here