(require 'cl)
(eval-when-compile
(if (string-match "XEmacs" emacs-version)
(byte-compiler-options
(warnings (- unresolved))))
(defvar font-lock-auto-fontify)
(defvar font-lock-support-mode)
(defvar global-font-lock-mode)
(when (and (eq emacs-major-version 19)
(not (string-match "XEmacs" emacs-version)))
(load "cl-extra")))
(defconst htmlize-version "1.34")
(eval-and-compile
(condition-case ()
(require 'custom)
(error nil))
(if (and (featurep 'custom) (fboundp 'custom-declare-variable))
nil (defmacro defgroup (&rest ignored) nil)
(defmacro defcustom (var value doc &rest ignored)
`(defvar ,var ,value ,doc))
(defmacro defface (face value doc &rest stuff)
`(make-face ,face))))
(defgroup htmlize nil
"Convert buffer text and faces to HTML."
:group 'hypermedia)
(defcustom htmlize-head-tags ""
"*Additional tags to insert within HEAD of the generated document."
:type 'string
:group 'htmlize)
(defcustom htmlize-output-type 'css
"*Output type of generated HTML, one of `css', `inline-css', or `font'.
When set to `css' (the default), htmlize will generate a style sheet
with description of faces, and use it in the HTML document, specifying
the faces in the actual text with <span class=\"FACE\">.
When set to `inline-css', the style will be generated as above, but
placed directly in the STYLE attribute of the span ELEMENT: <span
style=\"STYLE\">. This makes it easier to paste the resulting HTML to
other documents.
When set to `font', the properties will be set using layout tags
<font>, <b>, <i>, <u>, and <strike>.
`css' output is normally preferred, but `font' is still useful for
supporting old, pre-CSS browsers, and both `inline-css' and `font' for
easier embedding of colorized text in foreign HTML documents (no style
sheet to carry around)."
:type '(choice (const css) (const inline-css) (const font))
:group 'htmlize)
(defcustom htmlize-generate-hyperlinks t
"*Non-nil means generate the hyperlinks for URLs and mail addresses.
This is on by default; set it to nil if you don't want htmlize to
insert hyperlinks in the resulting HTML. (In which case you can still
do your own hyperlinkification from htmlize-after-hook.)"
:type 'boolean
:group 'htmlize)
(defcustom htmlize-hyperlink-style "
a {
color: inherit;
background-color: inherit;
font: inherit;
text-decoration: inherit;
}
a:hover {
text-decoration: underline;
}
"
"*The CSS style used for hyperlinks when in CSS mode."
:type 'string
:group 'htmlize)
(defcustom htmlize-replace-form-feeds t
"*Non-nil means replace form feeds in source code with HTML separators.
Form feeds are the ^L characters at line beginnings that are sometimes
used to separate sections of source code. If this variable is set to
`t', form feed characters are replaced with the <hr> separator. If this
is a string, it specifies the replacement to use. Note that <pre> is
temporarily closed before the separator is inserted, so the default
replacement is effectively \"</pre><hr /><pre>\". If you specify
another replacement, don't forget to close and reopen the <pre> if you
want the output to remain valid HTML.
If you need more elaborate processing, set this to nil and use
htmlize-after-hook."
:type 'boolean
:group 'htmlize)
(defcustom htmlize-html-charset nil
"*The charset declared by the resulting HTML documents.
When non-nil, causes htmlize to insert the following in the HEAD section
of the generated HTML:
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=CHARSET\">
where CHARSET is the value you've set for htmlize-html-charset. Valid
charsets are defined by MIME and include strings like \"iso-8859-1\",
\"iso-8859-15\", \"utf-8\", etc.
If you are using non-Latin-1 charsets, you might need to set this for
your documents to render correctly. Also, the W3C validator requires
submitted HTML documents to declare a charset. So if you care about
validation, you can use this to prevent the validator from bitching.
Needless to say, if you set this, you should actually make sure that
the buffer is in the encoding you're claiming it is in. (Under Mule
that is done by ensuring the correct \"file coding system\" for the
buffer.) If you don't understand what that means, this option is
probably not for you."
:type '(choice (const :tag "Unset" nil)
string)
:group 'htmlize)
(defcustom htmlize-convert-nonascii-to-entities (featurep 'mule)
"*Whether non-ASCII characters should be converted to HTML entities.
When this is non-nil, characters with codes in the 128-255 range will be
considered Latin 1 and rewritten as \"&#CODE;\". Characters with codes
above 255 will be converted to \"&#UCS;\", where UCS denotes the Unicode
code point of the character. If the code point cannot be determined,
the character will be copied unchanged, as would be the case if the
option were nil.
When the option is nil, the non-ASCII characters are copied to HTML
without modification. In that case, the web server and/or the browser
must be set to understand the encoding that was used when saving the
buffer. (You might also want to specify it by setting
`htmlize-html-charset'.)
Note that in an HTML entity \"&#CODE;\", CODE is always a UCS code point,
which has nothing to do with the charset the page is in. For example,
\"©\" *always* refers to the copyright symbol, regardless of charset
specified by the META tag or the charset sent by the HTTP server. In
other words, \"©\" is exactly equivalent to \"©\".
By default, entity conversion is turned on for Mule-enabled Emacsen and
turned off otherwise. This is because Mule knows the charset of
non-ASCII characters in the buffer. A non-Mule Emacs cannot tell
whether a character with code 0xA9 represents Latin 1 copyright symbol,
Latin 2 \"S with caron\", or something else altogether. Setting this to
t without Mule means asserting that 128-255 characters always mean Latin
1.
For most people htmlize will work fine with this option left at the
default setting; don't change it unless you know what you're doing."
:type 'sexp
:group 'htmlize)
(defcustom htmlize-ignore-face-size 'absolute
"*Whether face size should be ignored when generating HTML.
If this is nil, face sizes are used. If set to t, sizes are ignored
If set to `absolute', only absolute size specifications are ignored.
Please note that font sizes only work with CSS-based output types."
:type '(choice (const :tag "Don't ignore" nil)
(const :tag "Ignore all" t)
(const :tag "Ignore absolute" absolute))
:group 'htmlize)
(defcustom htmlize-css-name-prefix ""
"*The prefix used for CSS names.
The CSS names that htmlize generates from face names are often too
generic for CSS files; for example, `font-lock-type-face' is transformed
to `type'. Use this variable to add a prefix to the generated names.
The string \"htmlize-\" is an example of a reasonable prefix."
:type 'string
:group 'htmlize)
(defcustom htmlize-use-rgb-txt t
"*Whether `rgb.txt' should be used to convert color names to RGB.
This conversion means determining, for instance, that the color
\"IndianRed\" corresponds to the (205, 92, 92) RGB triple. `rgb.txt'
is the X color database that maps hundreds of color names to such RGB
triples. When this variable is non-nil, `htmlize' uses `rgb.txt' to
look up color names.
If this variable is nil, htmlize queries Emacs for RGB components of
colors using `color-instance-rgb-components' and `x-color-values'.
This can yield incorrect results on non-true-color displays.
If the `rgb.txt' file is not found (which will be the case if you're
running Emacs on non-X11 systems), this option is ignored."
:type 'boolean
:group 'htmlize)
(defcustom htmlize-html-major-mode nil
"The mode the newly created HTML buffer will be put in.
Set this to nil if you prefer the default (fundamental) mode."
:type '(radio (const :tag "No mode (fundamental)" nil)
(function-item html-mode)
(function :tag "User-defined major mode"))
:group 'htmlize)
(defvar htmlize-before-hook nil
"Hook run before htmlizing a buffer.
The hook functions are run in the source buffer (not the resulting HTML
buffer).")
(defvar htmlize-after-hook nil
"Hook run after htmlizing a buffer.
Unlike `htmlize-before-hook', these functions are run in the generated
HTML buffer. You may use them to modify the outlook of the final HTML
output.")
(defvar htmlize-file-hook nil
"Hook run by `htmlize-file' after htmlizing a file, but before saving it.")
(defvar htmlize-buffer-places)
(defconst htmlize-running-xemacs (string-match "XEmacs" emacs-version))
(eval-and-compile
(unless (fboundp 'save-current-buffer)
(defmacro save-current-buffer (&rest forms)
`(let ((__scb_current (current-buffer)))
(unwind-protect
(progn ,@forms)
(set-buffer __scb_current)))))
(unless (fboundp 'with-current-buffer)
(defmacro with-current-buffer (buffer &rest forms)
`(save-current-buffer (set-buffer ,buffer) ,@forms)))
(unless (fboundp 'with-temp-buffer)
(defmacro with-temp-buffer (&rest forms)
(let ((temp-buffer (gensym "tb-")))
`(let ((,temp-buffer
(get-buffer-create (generate-new-buffer-name " *temp*"))))
(unwind-protect
(with-current-buffer ,temp-buffer
,@forms)
(and (buffer-live-p ,temp-buffer)
(kill-buffer ,temp-buffer))))))))
(cond
(htmlize-running-xemacs
(defun htmlize-next-change (pos prop &optional limit)
(next-single-property-change pos prop nil (or limit (point-max)))))
((fboundp 'next-single-char-property-change)
(defun htmlize-next-change (pos prop &optional limit)
(next-single-char-property-change pos prop nil limit)))
((fboundp 'next-char-property-change)
(defun htmlize-next-change (pos prop &optional limit)
(let ((done nil)
(current-value (get-char-property pos prop))
newpos next-value)
(while (not done)
(setq newpos (next-char-property-change pos limit)
next-value (get-char-property newpos prop))
(cond ((eq newpos pos)
(setq done t))
((eq next-value current-value)
)
(t
(setq done t)))
(setq pos newpos))
pos)))
(t
(defun htmlize-next-change (pos prop &optional limit)
(unless limit
(setq limit (point-max)))
(let ((res (next-single-property-change pos prop)))
(if (or (null res)
(> res limit))
limit
res)))))
(defvar htmlize-basic-character-table
(let ((table (make-vector 128 ?\0)))
(dotimes (i 128)
(setf (aref table i) (if (and (>= i 32) (<= i 126))
(char-to-string i)
(format "&#%d;" i))))
(setf
(aref table ?\n) "\n"
(aref table ?\r) "\r"
(aref table ?\t) "\t"
(aref table ?&) "&"
(aref table ?<) "<"
(aref table ?>) ">"
)
table))
(defvar htmlize-extended-character-cache (make-hash-table :test 'eq))
(defun htmlize-protect-string (string)
"HTML-protect string, escaping HTML metacharacters and I18N chars."
(if (not (string-match "[^\r\n\t -%'-;=?-~]" string))
string
(mapconcat (lambda (char)
(cond
((< char 128)
(aref htmlize-basic-character-table char))
((gethash char htmlize-extended-character-cache)
)
((not htmlize-convert-nonascii-to-entities)
(setf (gethash char htmlize-extended-character-cache)
(char-to-string char)))
((< char 256)
(setf (gethash char htmlize-extended-character-cache)
(format "&#%d;" char)))
((and (fboundp 'encode-char)
(encode-char char 'ucs))
(setf (gethash char htmlize-extended-character-cache)
(format "&#%d;" (encode-char char 'ucs))))
(t
(setf (gethash char htmlize-extended-character-cache)
(char-to-string char)))))
string "")))
(defconst htmlize-ellipsis "...")
(put-text-property 0 (length htmlize-ellipsis) 'htmlize-ellipsis t htmlize-ellipsis)
(defun htmlize-buffer-substring-no-invisible (beg end)
(let ((pos beg)
visible-list invisible show next-change)
(while (< pos end)
(setq invisible (get-char-property pos 'invisible)
next-change (htmlize-next-change pos 'invisible end))
(if (not (listp buffer-invisibility-spec))
(setq show (not invisible))
(let (match)
(if (symbolp invisible)
(setq match (member* invisible buffer-invisibility-spec
:key (lambda (i)
(if (symbolp i) i (car i)))))
(setq match (block nil
(dolist (elem invisible)
(let ((m (member*
elem buffer-invisibility-spec
:key (lambda (i)
(if (symbolp i) i (car i))))))
(when m (return m))))
nil)))
(setq show (cond ((null match) t)
((and (cdr-safe (car match))
(not (eq show htmlize-ellipsis)))
htmlize-ellipsis)
(t nil)))))
(cond ((eq show t)
(push (buffer-substring-no-properties pos next-change) visible-list))
((stringp show)
(push show visible-list)))
(setq pos next-change))
(if (= (length visible-list) 1)
(car visible-list)
(apply #'concat (nreverse visible-list)))))
(defun htmlize-trim-ellipsis (text)
(if (get-text-property 0 'htmlize-ellipsis text)
(substring text (length htmlize-ellipsis))
text))
(defconst htmlize-tab-spaces
(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)
(push (substring text chunk-start match-pos) chunks)
(incf column (- match-pos last-match))
(setq tab-size (- tab-width (% column tab-width)))
(push (aref htmlize-tab-spaces tab-size) chunks)
(incf column tab-size)
(setq chunk-start (1+ match-pos)))
(t
(setq column 0)))
(setq last-match (1+ match-pos)))
(if (null chunks)
text
(when (< chunk-start (length text))
(push (substring text chunk-start) 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."
(while (string-match "@" string)
(setq string (replace-match "@" nil t string)))
string)
(defun htmlize-make-hyperlinks ()
"Make hyperlinks in HTML."
(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 "<<a href=\"mailto:"
(htmlize-despam-address address)
"\">"
(htmlize-despam-address link-text)
"</a>>")))
(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 "<<a href=\"" url "\">" link-text "</a>>"))))
(defun htmlize-defang-local-variables ()
(goto-char (point-min))
(while (search-forward "Local Variables:" nil t)
(replace-match "Local Variables:" nil t)))
(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-*\\([!#]\\|$\\)")
)
((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))
(defvar htmlize-color-rgb-hash (htmlize-get-color-rgb-hash))
(defun htmlize-face-specifies-property (face prop)
(or (eq face 'default)
(assq 'global (specifier-spec-list (face-property face prop)))))
(defun htmlize-face-color-internal (face fg)
(let* ((function (if fg #'face-foreground #'face-background))
color)
(if (>= emacs-major-version 22)
(setq color (funcall function face nil t))
(setq color (funcall function face))
(when (and (null color)
(fboundp 'face-attribute)
(face-attribute face :inherit)
(not (eq (face-attribute face :inherit) 'unspecified)))
(setq color (htmlize-face-color-internal
(face-attribute face :inherit) fg))))
(when (and (eq face 'default) (null color))
(setq color (cdr (assq (if fg 'foreground-color 'background-color)
(frame-parameters)))))
(when (or (eq color 'unspecified)
(equal color "unspecified-fg")
(equal color "unspecified-bg"))
(setq color nil))
(when (and (eq face 'default)
(null color))
(setq color (if fg "black" "white")))
color))
(defun htmlize-face-foreground (face)
(cond (htmlize-running-xemacs
(and (htmlize-face-specifies-property face 'foreground)
(color-instance-name (face-foreground-instance face))))
(t
(htmlize-face-color-internal face t))))
(defun htmlize-face-background (face)
(cond (htmlize-running-xemacs
(and (htmlize-face-specifies-property face 'background)
(color-instance-name (face-background-instance face))))
(t
(htmlize-face-color-internal face nil))))
(defun htmlize-color-to-rgb (color)
(let ((rgb-string nil))
(cond ((null color)
)
((string-match "\\`#" color)
(setq rgb-string color))
((and htmlize-use-rgb-txt
htmlize-color-rgb-hash)
(setq rgb-string (gethash (downcase color) htmlize-color-rgb-hash)))
(t
(let ((rgb
(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))))))
(or rgb-string color)))
(defstruct htmlize-fstruct
foreground background size boldp italicp underlinep overlinep strikep css-name )
(defun htmlize-face-emacs21-attr (fstruct attr value)
(case attr
(:foreground
(setf (htmlize-fstruct-foreground fstruct) (htmlize-color-to-rgb value)))
(:background
(setf (htmlize-fstruct-background fstruct) (htmlize-color-to-rgb value)))
(:height
(setf (htmlize-fstruct-size fstruct) 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-size (face)
(let ((size-list
(loop
for f = face then (face-attribute f :inherit)
until (eq f 'unspecified)
for h = (face-attribute f :height)
collect (if (eq h 'unspecified) nil h))))
(reduce 'htmlize-merge-size (cons nil size-list))))
(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
(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)
(dolist (attr '(:weight :slant :underline :overline :strike-through))
(let ((value (if (>= emacs-major-version 22)
(face-attribute face attr nil t)
(let ((face face))
(while (and (eq (face-attribute face attr)
'unspecified)
(not (eq (face-attribute face :inherit)
'unspecified)))
(setq face (face-attribute face :inherit)))
(face-attribute face attr)))))
(when (and value (not (eq value 'unspecified)))
(htmlize-face-emacs21-attr fstruct attr value))))
(let ((size (htmlize-face-size face)))
(unless (eql size 1.0) (setf (htmlize-fstruct-size fstruct) size))))
(t
(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))))
(setf (htmlize-fstruct-css-name fstruct)
(let ((name (downcase (symbol-name face))))
(when (string-match "\\`font-lock-" name)
(setq name (replace-match "" t t name)))
(when (string-match "-face\\'" name)
(setq name (replace-match "" t t name)))
(while (string-match "[^-a-zA-Z0-9]" name)
(setq name (replace-match "X" t t name)))
(when (string-match "\\`[-0-9]" name)
(setq name (concat "X" name)))
(when (equal name "")
(setq name "face"))
(setq name (concat htmlize-css-name-prefix name))
name))
fstruct))
(defmacro htmlize-copy-attr-if-set (attr-list dest source)
(cons 'progn
(loop for attr in attr-list
for attr-sym = (intern (format "htmlize-fstruct-%s" attr))
collect `(and (,attr-sym ,source)
(setf (,attr-sym ,dest) (,attr-sym ,source))))))
(defun htmlize-merge-size (merged next)
(cond ((null merged) next)
((integerp next) next)
((null next) merged)
((floatp merged) (* merged next))
((integerp merged) (round (* merged next)))))
(defun htmlize-merge-two-faces (merged next)
(htmlize-copy-attr-if-set
(foreground background boldp italicp underlinep overlinep strikep)
merged next)
(setf (htmlize-fstruct-size merged)
(htmlize-merge-size (htmlize-fstruct-size merged)
(htmlize-fstruct-size next)))
merged)
(defun htmlize-merge-faces (fstruct-list)
(cond ((null fstruct-list)
(make-htmlize-fstruct))
((null (cdr fstruct-list))
(car fstruct-list))
(t
(reduce #'htmlize-merge-two-faces
(cons (make-htmlize-fstruct) fstruct-list)))))
(defun htmlize-attrlist-to-fstruct (attrlist)
(let ((fstruct (make-htmlize-fstruct)))
(cond ((eq (car attrlist) 'foreground-color)
(setf (htmlize-fstruct-foreground fstruct)
(htmlize-color-to-rgb (cdr attrlist))))
((eq (car attrlist) 'background-color)
(setf (htmlize-fstruct-background fstruct)
(htmlize-color-to-rgb (cdr attrlist))))
(t
(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."
(cond
((eq face-prop nil)
t)
((symbolp face-prop)
nil)
((not (consp face-prop))
nil)
(t
(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) ?:)))))
(not attrlist-p)))))
(defun htmlize-make-face-map (faces)
(let ((face-map (make-hash-table :test 'equal))
css-names)
(dolist (face faces)
(unless (gethash face face-map)
(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))
(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-unstringify-face (face)
"If FACE is a string, return it interned, otherwise return it unchanged."
(if (stringp face)
(intern face)
face))
(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)
(if htmlize-running-xemacs
(let (face-prop)
(map-extents (lambda (extent ignored)
(setq face-prop (extent-face extent)
faces (if (listp face-prop)
(union face-prop faces)
(adjoin face-prop faces)))
nil)
nil
(point-min) (point-max) nil nil 'face))
(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)))
(setq faces (if (htmlize-face-list-p face-prop)
(nunion (mapcar #'htmlize-unstringify-face face-prop)
faces :test 'equal)
(adjoin (htmlize-unstringify-face face-prop)
faces :test 'equal)))
(setq pos next)))
(dolist (overlay (overlays-in (point-min) (point-max)))
(let ((face-prop (overlay-get overlay 'face)))
(setq faces (if (htmlize-face-list-p face-prop)
(nunion (mapcar #'htmlize-unstringify-face face-prop)
faces :test 'equal)
(adjoin (htmlize-unstringify-face face-prop)
faces :test 'equal))))))
faces))
(cond (htmlize-running-xemacs
(defun htmlize-faces-at-point ()
(let (extent extent-list face-list face-prop)
(while (setq extent (extent-at (point) nil 'face extent))
(push extent extent-list))
(setq extent-list (stable-sort extent-list #'<
:key #'extent-priority))
(dolist (extent extent-list)
(setq face-prop (extent-face extent))
(setq face-list (if (listp face-prop)
(append face-prop face-list)
(cons face-prop face-list))))
(nreverse face-list))))
(t
(defun htmlize-faces-at-point ()
(let (all-faces)
(let ((face-prop (get-text-property (point) 'face)))
(setq all-faces (if (htmlize-face-list-p face-prop)
(nreverse (mapcar #'htmlize-unstringify-face
face-prop))
(list (htmlize-unstringify-face face-prop)))))
(let ((overlays
(delete-if-not (lambda (o)
(overlay-get o 'face))
(overlays-at (point))))
list face-prop)
(setq overlays (sort* overlays
#'<
:key (lambda (o)
(- (overlay-end o)
(overlay-start o)))))
(setq overlays (stable-sort
overlays
#'<
:key (lambda (o)
(or (overlay-get o 'priority) 0))))
(dolist (overlay overlays)
(setq face-prop (overlay-get overlay 'face))
(setq list (if (htmlize-face-list-p face-prop)
(nconc (nreverse (mapcar
#'htmlize-unstringify-face
face-prop))
list)
(cons (htmlize-unstringify-face face-prop) list))))
(setq all-faces (nconc all-faces list)))
all-faces))))
(defmacro htmlize-method (method &rest args)
`(funcall (htmlize-method-function ',method) ,@args))
(defun htmlize-method-function (method)
(let ((sym (intern (format "htmlize-%s-%s" htmlize-output-type method))))
(indirect-function (if (fboundp sym)
sym
(let ((default (intern (concat "htmlize-default-"
(symbol-name method)))))
(if (fboundp default)
default
'ignore))))))
(defvar htmlize-memoization-table (make-hash-table :test 'equal))
(defmacro htmlize-memoize (key generator)
"Return the value of GENERATOR, memoized as KEY.
That means that GENERATOR will be evaluated and returned the first time
it's called with the same value of KEY. All other times, the cached
\(memoized) value will be returned."
(let ((value (gensym)))
`(let ((,value (gethash ,key htmlize-memoization-table)))
(unless ,value
(setq ,value ,generator)
(setf (gethash ,key htmlize-memoization-table) ,value))
,value)))
(defun htmlize-default-doctype ()
nil
"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\">"
)
(defun htmlize-default-body-tag (face-map)
nil "<body>")
(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))
(let ((size (htmlize-fstruct-size fstruct)))
(when (and size (not (eq htmlize-ignore-face-size t)))
(cond ((floatp size)
(push (format "font-size: %d%%;" (* 100 size)) result))
((not (eq htmlize-ignore-face-size 'absolute))
(push (format "font-size: %spt;" (/ size 10.0)) 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 " <style type=\"text/css\">\n <!--\n")
(insert " body {\n "
(mapconcat #'identity
(htmlize-css-specs (gethash 'default face-map))
"\n ")
"\n }\n")
(dolist (face (sort* (copy-list buffer-faces) #'string-lessp
:key (lambda (f)
(htmlize-fstruct-css-name (gethash f face-map)))))
(let* ((fstruct (gethash face face-map))
(cleaned-up-face-name
(let ((s
(prin1-to-string face)))
(while (string-match "--" s)
(setq s (replace-match "-" t t s)))
(while (string-match "\\*/" s)
(setq s (replace-match "XX" t t s)))
s))
(specs (htmlize-css-specs fstruct)))
(insert " ." (htmlize-fstruct-css-name fstruct))
(if (null specs)
(insert " {")
(insert " {\n /* " cleaned-up-face-name " */\n "
(mapconcat #'identity specs "\n ")))
(insert "\n }\n")))
(insert htmlize-hyperlink-style
" -->\n </style>\n"))
(defun htmlize-css-insert-text (text fstruct-list buffer)
(dolist (fstruct fstruct-list)
(princ "<span class=\"" buffer)
(princ (htmlize-fstruct-css-name fstruct) buffer)
(princ "\">" buffer))
(princ text buffer)
(dolist (fstruct fstruct-list)
(ignore fstruct) (princ "</span>" buffer)))
(defun htmlize-inline-css-body-tag (face-map)
(format "<body style=\"%s\">"
(mapconcat #'identity (htmlize-css-specs (gethash 'default face-map))
" ")))
(defun htmlize-inline-css-insert-text (text fstruct-list buffer)
(let* ((merged (htmlize-merge-faces fstruct-list))
(style (htmlize-memoize
merged
(let ((specs (htmlize-css-specs merged)))
(and specs
(mapconcat #'identity (htmlize-css-specs merged) " "))))))
(when style
(princ "<span style=\"" buffer)
(princ style buffer)
(princ "\">" buffer))
(princ text buffer)
(when style
(princ "</span>" buffer))))
(defun htmlize-font-body-tag (face-map)
(let ((fstruct (gethash 'default face-map)))
(format "<body text=\"%s\" bgcolor=\"%s\">"
(htmlize-fstruct-foreground fstruct)
(htmlize-fstruct-background fstruct))))
(defun htmlize-font-insert-text (text fstruct-list buffer)
(let* ((merged (htmlize-merge-faces fstruct-list))
(markup (htmlize-memoize
merged
(cons (concat
(and (htmlize-fstruct-foreground merged)
(format "<font color=\"%s\">" (htmlize-fstruct-foreground merged)))
(and (htmlize-fstruct-boldp merged) "<b>")
(and (htmlize-fstruct-italicp merged) "<i>")
(and (htmlize-fstruct-underlinep merged) "<u>")
(and (htmlize-fstruct-strikep merged) "<strike>"))
(concat
(and (htmlize-fstruct-strikep merged) "</strike>")
(and (htmlize-fstruct-underlinep merged) "</u>")
(and (htmlize-fstruct-italicp merged) "</i>")
(and (htmlize-fstruct-boldp merged) "</b>")
(and (htmlize-fstruct-foreground merged) "</font>"))))))
(princ (car markup) buffer)
(princ text buffer)
(princ (cdr markup) buffer)))
(defun htmlize-buffer-1 ()
(save-excursion
(save-excursion
(run-hooks 'htmlize-before-hook))
(htmlize-ensure-fontified)
(clrhash htmlize-extended-character-cache)
(clrhash htmlize-memoization-table)
(let* ((buffer-faces (htmlize-faces-in-buffer))
(face-map (htmlize-make-face-map (adjoin 'default buffer-faces)))
(htmlbuf (generate-new-buffer (if (buffer-file-name)
(htmlize-make-file-name
(file-name-nondirectory
(buffer-file-name)))
"*html*")))
(places '(nil nil))
(title (if (buffer-file-name)
(file-name-nondirectory (buffer-file-name))
(buffer-name))))
(with-current-buffer htmlbuf
(buffer-disable-undo)
(insert (htmlize-method doctype) ?\n
(format "<!-- Created by htmlize-%s in %s mode. -->\n"
htmlize-version htmlize-output-type)
"<html>\n ")
(plist-put places 'head-start (point-marker))
(insert "<head>\n"
" <title>" (htmlize-protect-string title) "</title>\n"
(if htmlize-html-charset
(format (concat " <meta http-equiv=\"Content-Type\" "
"content=\"text/html; charset=%s\">\n")
htmlize-html-charset)
"")
htmlize-head-tags)
(htmlize-method insert-head buffer-faces face-map)
(insert " </head>")
(plist-put places 'head-end (point-marker))
(insert "\n ")
(plist-put places 'body-start (point-marker))
(insert (htmlize-method body-tag face-map)
"\n ")
(plist-put places 'content-start (point-marker))
(insert "<pre>\n"))
(let ((insert-text-method
(htmlize-method-function 'insert-text))
next-change text face-list fstruct-list trailing-ellipsis)
(goto-char (point-min))
(while (not (eobp))
(setq next-change (htmlize-next-change (point) 'face))
(setq face-list (htmlize-faces-at-point)
fstruct-list (delq nil (mapcar (lambda (f)
(gethash f face-map))
face-list)))
(setq text (htmlize-buffer-substring-no-invisible
(point) next-change))
(when trailing-ellipsis
(setq text (htmlize-trim-ellipsis text)))
(when (> (length text) 0)
(setq trailing-ellipsis
(get-text-property (1- (length text))
'htmlize-ellipsis text)))
(setq text (htmlize-untabify text (current-column)))
(setq text (htmlize-protect-string text))